setup-app-layer.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. #! /usr/bin/env python3
  2. #
  3. # Script to provision a new application layer parser and/or logger.
  4. import sys
  5. import os
  6. import os.path
  7. import argparse
  8. import io
  9. import re
  10. class SetupError(Exception):
  11. """Functions in this script can raise this error which will cause the
  12. application to abort displaying the provided error message, but
  13. without a stack trace.
  14. """
  15. pass
  16. progname = os.path.basename(sys.argv[0])
  17. def fail_if_exists(filename):
  18. if os.path.exists(filename):
  19. raise SetupError("%s already exists" % (filename))
  20. def common_copy_templates(proto, pairs, replacements=()):
  21. upper = proto.upper()
  22. lower = proto.lower()
  23. for (src, dst) in pairs:
  24. fail_if_exists(dst)
  25. for (src, dst) in pairs:
  26. dstdir = os.path.dirname(dst)
  27. if not os.path.exists(dstdir):
  28. print("Creating directory %s." % (dstdir))
  29. os.makedirs(dstdir)
  30. print("Generating %s." % (dst))
  31. output = open(dst, "w")
  32. with open(src) as template_in:
  33. skip = False
  34. for line in template_in:
  35. if line.find("TEMPLATE_START_REMOVE") > -1:
  36. skip = True
  37. continue
  38. elif line.find("TEMPLATE_END_REMOVE") > -1:
  39. skip = False
  40. continue
  41. if skip:
  42. continue
  43. for (old, new) in replacements:
  44. line = line.replace(old, new)
  45. line = re.sub("TEMPLATE(_RUST)?", upper, line)
  46. line = re.sub("template(-rust)?", lower, line)
  47. line = re.sub("Template(Rust)?", proto, line)
  48. output.write(line)
  49. output.close()
  50. def copy_app_layer_templates(proto, rust):
  51. lower = proto.lower()
  52. upper = proto.upper()
  53. if rust:
  54. pairs = (
  55. ("src/app-layer-template-rust.c",
  56. "src/app-layer-%s.c" % (lower)),
  57. ("src/app-layer-template-rust.h",
  58. "src/app-layer-%s.h" % (lower)),
  59. ("rust/src/applayertemplate/mod.rs",
  60. "rust/src/applayer%s/mod.rs" % (lower)),
  61. ("rust/src/applayertemplate/template.rs",
  62. "rust/src/applayer%s/%s.rs" % (lower, lower)),
  63. ("rust/src/applayertemplate/parser.rs",
  64. "rust/src/applayer%s/parser.rs" % (lower)),
  65. )
  66. else:
  67. pairs = (
  68. ("src/app-layer-template.c",
  69. "src/app-layer-%s.c" % (lower)),
  70. ("src/app-layer-template.h",
  71. "src/app-layer-%s.h" % (lower)),
  72. )
  73. common_copy_templates(proto, pairs)
  74. def patch_makefile_am(protoname):
  75. print("Patching src/Makefile.am.")
  76. output = io.StringIO()
  77. with open("src/Makefile.am") as infile:
  78. for line in infile:
  79. if line.lstrip().startswith("app-layer-template."):
  80. output.write(line.replace("template", protoname.lower()))
  81. output.write(line)
  82. open("src/Makefile.am", "w").write(output.getvalue())
  83. def patch_rust_lib_rs(protoname):
  84. filename = "rust/src/lib.rs"
  85. print("Patching %s." % (filename))
  86. output = io.StringIO()
  87. with open(filename) as infile:
  88. for line in infile:
  89. if line.startswith("pub mod applayertemplate;"):
  90. output.write(line.replace("template", protoname.lower()))
  91. output.write(line)
  92. open(filename, "w").write(output.getvalue())
  93. def patch_rust_applayer_mod_rs(protoname):
  94. lower = protoname.lower()
  95. filename = "rust/src/applayer%s/mod.rs" % (lower)
  96. print("Patching %s." % (filename))
  97. output = io.StringIO()
  98. done = False
  99. with open(filename) as infile:
  100. for line in infile:
  101. if not done and line.find("mod parser") > -1:
  102. output.write("pub mod logger;\n")
  103. done = True
  104. output.write(line)
  105. open(filename, "w").write(output.getvalue())
  106. def patch_app_layer_protos_h(protoname):
  107. filename = "src/app-layer-protos.h"
  108. print("Patching %s." % (filename))
  109. output = io.StringIO()
  110. with open(filename) as infile:
  111. for line in infile:
  112. if line.find("ALPROTO_TEMPLATE,") > -1:
  113. output.write(line.replace("TEMPLATE", protoname.upper()))
  114. output.write(line)
  115. open(filename, "w").write(output.getvalue())
  116. def patch_app_layer_protos_c(protoname):
  117. filename = "src/app-layer-protos.c"
  118. print("Patching %s." % (filename))
  119. output = io.StringIO()
  120. # Read in all the lines as we'll be doing some multi-line
  121. # duplications.
  122. inlines = open(filename).readlines()
  123. for i, line in enumerate(inlines):
  124. if line.find("case ALPROTO_TEMPLATE:") > -1:
  125. # Duplicate the section starting an this line and
  126. # including the following 2 lines.
  127. for j in range(i, i + 3):
  128. temp = inlines[j]
  129. temp = temp.replace("TEMPLATE", protoname.upper())
  130. temp = temp.replace("template", protoname.lower())
  131. output.write(temp)
  132. if line.find("return ALPROTO_TEMPLATE;") > -1:
  133. output.write(
  134. line.replace("TEMPLATE", protoname.upper()).replace(
  135. "template", protoname.lower()))
  136. output.write(line)
  137. open(filename, "w").write(output.getvalue())
  138. def patch_app_layer_detect_proto_c(proto):
  139. filename = "src/app-layer-detect-proto.c"
  140. print("Patching %s." % (filename))
  141. output = io.StringIO()
  142. inlines = open(filename).readlines()
  143. for i, line in enumerate(inlines):
  144. if line.find("== ALPROTO_TEMPLATE)") > -1:
  145. output.write(inlines[i].replace("TEMPLATE", proto.upper()))
  146. output.write(inlines[i+1].replace("TEMPLATE", proto.upper()))
  147. output.write(line)
  148. open(filename, "w").write(output.getvalue())
  149. def patch_app_layer_parser_c(proto):
  150. filename = "src/app-layer-parser.c"
  151. print("Patching %s." % (filename))
  152. output = io.StringIO()
  153. inlines = open(filename).readlines()
  154. for line in inlines:
  155. if line.find("app-layer-template.h") > -1:
  156. output.write(line.replace("template", proto.lower()))
  157. if line.find("RegisterTemplateParsers()") > -1:
  158. output.write(line.replace("Template", proto))
  159. output.write(line)
  160. open(filename, "w").write(output.getvalue())
  161. def patch_suricata_yaml_in(proto):
  162. filename = "suricata.yaml.in"
  163. print("Patching %s." % (filename))
  164. output = io.StringIO()
  165. inlines = open(filename).readlines()
  166. for i, line in enumerate(inlines):
  167. if line.find("protocols:") > -1:
  168. if inlines[i-1].find("app-layer:") > -1:
  169. output.write(line)
  170. output.write(""" %s:
  171. enabled: yes
  172. """ % (proto.lower()))
  173. # Skip writing out the current line, already done.
  174. continue
  175. output.write(line)
  176. open(filename, "w").write(output.getvalue())
  177. def logger_patch_suricata_yaml_in(proto):
  178. filename = "suricata.yaml.in"
  179. print("Patching %s." % (filename))
  180. output = io.StringIO()
  181. inlines = open(filename).readlines()
  182. # This is a bit tricky. We want to find the first occurrence of
  183. # "types:" after "eve-log:".
  184. n = 0
  185. for i, line in enumerate(inlines):
  186. if n == 0 and line.find("eve-log:") > -1:
  187. n += 1
  188. if n == 1 and line.find("types:") > -1:
  189. output.write(line)
  190. output.write(" - %s\n" % (proto.lower()))
  191. n += 1
  192. continue
  193. output.write(line)
  194. open(filename, "w").write(output.getvalue())
  195. def logger_patch_suricata_common_h(proto):
  196. filename = "src/suricata-common.h"
  197. print("Patching %s." % (filename))
  198. output = io.StringIO()
  199. with open(filename) as infile:
  200. for line in infile:
  201. if line.find("LOGGER_JSON_TEMPLATE,") > -1:
  202. output.write(line.replace("TEMPLATE", proto.upper()))
  203. output.write(line)
  204. open(filename, "w").write(output.getvalue())
  205. def logger_patch_output_c(proto):
  206. filename = "src/output.c"
  207. print("Patching %s." % (filename))
  208. output = io.StringIO()
  209. inlines = open(filename).readlines()
  210. for i, line in enumerate(inlines):
  211. if line.find("output-json-template.h") > -1:
  212. output.write(line.replace("template", proto.lower()))
  213. if line.find("/* Template JSON logger.") > -1:
  214. output.write(inlines[i].replace("Template", proto))
  215. output.write(inlines[i+1].replace("Template", proto))
  216. output.write(line)
  217. open(filename, "w").write(output.getvalue())
  218. def logger_copy_templates(proto, rust):
  219. lower = proto.lower()
  220. if rust:
  221. pairs = (
  222. ("src/output-json-template-rust.h",
  223. "src/output-json-%s.h" % (lower)),
  224. ("src/output-json-template-rust.c",
  225. "src/output-json-%s.c" % (lower)),
  226. ("rust/src/applayertemplate/logger.rs",
  227. "rust/src/applayer%s/logger.rs" % (lower)),
  228. )
  229. else:
  230. pairs = (
  231. ("src/output-json-template.h",
  232. "src/output-json-%s.h" % (lower)),
  233. ("src/output-json-template.c",
  234. "src/output-json-%s.c" % (lower)),
  235. )
  236. common_copy_templates(proto, pairs)
  237. def logger_patch_makefile_am(protoname):
  238. filename = "src/Makefile.am"
  239. print("Patching %s." % (filename))
  240. output = io.StringIO()
  241. with open(filename) as infile:
  242. for line in infile:
  243. if line.lstrip().startswith("output-json-template."):
  244. output.write(line.replace("template", protoname.lower()))
  245. output.write(line)
  246. open(filename, "w").write(output.getvalue())
  247. def logger_patch_util_profiling_c(proto):
  248. filename = "src/util-profiling.c"
  249. print("Patching %s." % (filename))
  250. output = io.StringIO()
  251. with open(filename) as infile:
  252. for line in infile:
  253. if line.find("(LOGGER_JSON_TEMPLATE);") > -1:
  254. output.write(line.replace("TEMPLATE", proto.upper()))
  255. output.write(line)
  256. open(filename, "w").write(output.getvalue())
  257. def detect_copy_templates(proto, buffername, rust):
  258. lower = proto.lower()
  259. buffername_lower = buffername.lower()
  260. if rust:
  261. pairs = (
  262. ("src/detect-template-rust-buffer.h",
  263. "src/detect-%s-%s.h" % (lower, buffername_lower)),
  264. ("src/detect-template-rust-buffer.c",
  265. "src/detect-%s-%s.c" % (lower, buffername_lower)),
  266. )
  267. replacements = (
  268. ("TEMPLATE_RUST_BUFFER", "%s_%s" % (
  269. proto.upper(), buffername.upper())),
  270. ("template-rust-buffer", "%s-%s" % (
  271. proto.lower(), buffername.lower())),
  272. ("template_rust_buffer", "%s_%s" % (
  273. proto.lower(), buffername.lower())),
  274. ("TemplateRustBuffer", "%s%s" % (proto, buffername)),
  275. )
  276. else:
  277. pairs = (
  278. ("src/detect-template-buffer.h",
  279. "src/detect-%s-%s.h" % (lower, buffername_lower)),
  280. ("src/detect-template-buffer.c",
  281. "src/detect-%s-%s.c" % (lower, buffername_lower)),
  282. ("src/tests/detect-template-buffer.c",
  283. "src/tests/detect-%s-%s.c" % (lower, buffername_lower)),
  284. )
  285. replacements = (
  286. ("TEMPLATE_BUFFER", "%s_%s" % (proto.upper(), buffername.upper())),
  287. ("template-buffer", "%s-%s" % (proto.lower(), buffername.lower())),
  288. ("template_buffer", "%s_%s" % (proto.lower(), buffername.lower())),
  289. ("TemplateBuffer", "%s%s" % (proto, buffername)),
  290. )
  291. common_copy_templates(proto, pairs, replacements)
  292. def detect_patch_makefile_am(protoname, buffername):
  293. filename = "src/Makefile.am"
  294. print("Patching %s." % (filename))
  295. output = io.StringIO()
  296. with open(filename) as infile:
  297. for line in infile:
  298. if line.lstrip().startswith("detect-template-buffer."):
  299. new = line.replace("template-buffer", "%s-%s" % (
  300. protoname.lower(), buffername.lower()))
  301. output.write(new)
  302. output.write(line)
  303. open(filename, "w").write(output.getvalue())
  304. def detect_patch_detect_engine_register_c(protoname, buffername):
  305. filename = "src/detect-engine-register.c"
  306. print("Patching %s." % (filename))
  307. output = io.StringIO()
  308. with open(filename) as infile:
  309. for line in infile:
  310. if line.find("detect-template-buffer.h") > -1:
  311. new = line.replace("template-buffer", "%s-%s" % (
  312. protoname.lower(), buffername.lower()))
  313. output.write(new)
  314. if line.find("DetectTemplateBufferRegister") > -1:
  315. new = line.replace("TemplateBuffer", "%s%s" % (
  316. protoname, buffername))
  317. output.write(new)
  318. output.write(line)
  319. open(filename, "w").write(output.getvalue())
  320. def detect_patch_detect_engine_register_h(protoname, buffername):
  321. filename = "src/detect-engine-register.h"
  322. print("Patching %s." % (filename))
  323. output = io.StringIO()
  324. with open(filename) as infile:
  325. for line in infile:
  326. if line.find("DETECT_AL_TEMPLATE_BUFFER") > -1:
  327. new = line.replace("TEMPLATE_BUFFER", "%s_%s" % (
  328. protoname.upper(), buffername.upper()))
  329. output.write(new)
  330. output.write(line)
  331. open(filename, "w").write(output.getvalue())
  332. def proto_exists(proto):
  333. upper = proto.upper()
  334. for line in open("src/app-layer-protos.h"):
  335. if line.find("ALPROTO_%s," % (upper)) > -1:
  336. return True
  337. return False
  338. epilog = """
  339. This script will provision a new app-layer parser for the protocol
  340. name specified on the command line. This is done by copying and
  341. patching src/app-layer-template.[ch] then linking the new files into
  342. the build system.
  343. By default both the parser and logger will be generated. To generate
  344. just one or the other use the --parser or --logger command line flags.
  345. Examples:
  346. %(progname)s --logger DNP3
  347. %(progname)s --parser Gopher
  348. This script can also setup a detect buffer. This is a separate
  349. operation that must be done after creating the parser.
  350. Examples:
  351. %(progname)s --detect Gopher Request
  352. """ % { "progname": progname, }
  353. def main():
  354. parser = argparse.ArgumentParser(
  355. formatter_class=argparse.RawDescriptionHelpFormatter,
  356. epilog=epilog)
  357. parser.add_argument("--rust", action="store_true", default=False,
  358. help="Setup Rust protocol template.")
  359. parser.add_argument("--logger", action="store_true", default=False,
  360. help="Generate logger.")
  361. parser.add_argument("--parser", action="store_true", default=False,
  362. help="Generate parser.")
  363. parser.add_argument("--detect", action="store_true", default=False,
  364. help="Generate detect module.")
  365. parser.add_argument("proto", help="Name of protocol")
  366. parser.add_argument("buffer", help="Name of buffer (for --detect)",
  367. nargs="?")
  368. args = parser.parse_args()
  369. proto = args.proto
  370. # The protocol name must start with an upper case letter.
  371. if proto[0] != proto.upper()[0]:
  372. raise SetupError("protocol name must begin with an upper case letter")
  373. # Determine what to generate.
  374. parser = False
  375. logger = False
  376. detect = False
  377. # If no --parser or no --logger, generate both.
  378. if not args.parser and not args.logger and not args.detect:
  379. parser = True
  380. logger = True
  381. else:
  382. parser = args.parser
  383. logger = args.logger
  384. detect = args.detect
  385. if detect:
  386. if args.buffer is None:
  387. raise SetupError("--detect requires a buffer name")
  388. # Make sure we are in the correct directory.
  389. if os.path.exists("./suricata.c"):
  390. os.chdir("..")
  391. elif not os.path.exists("./src/suricata.c"):
  392. raise SetupError(
  393. "this does not appear to be a Suricata source directory.")
  394. if parser:
  395. if proto_exists(proto):
  396. raise SetupError("protocol already exists: %s" % (proto))
  397. copy_app_layer_templates(proto, args.rust)
  398. if args.rust:
  399. patch_rust_lib_rs(proto)
  400. patch_makefile_am(proto)
  401. patch_app_layer_protos_h(proto)
  402. patch_app_layer_protos_c(proto)
  403. patch_app_layer_detect_proto_c(proto)
  404. patch_app_layer_parser_c(proto)
  405. patch_suricata_yaml_in(proto)
  406. if logger:
  407. if not proto_exists(proto):
  408. raise SetupError("no app-layer parser exists for %s" % (proto))
  409. logger_copy_templates(proto, args.rust)
  410. if args.rust:
  411. patch_rust_applayer_mod_rs(proto)
  412. logger_patch_makefile_am(proto)
  413. logger_patch_suricata_common_h(proto)
  414. logger_patch_output_c(proto)
  415. logger_patch_suricata_yaml_in(proto)
  416. logger_patch_util_profiling_c(proto)
  417. if detect:
  418. if not proto_exists(proto):
  419. raise SetupError("no app-layer parser exists for %s" % (proto))
  420. detect_copy_templates(proto, args.buffer, args.rust)
  421. detect_patch_makefile_am(proto, args.buffer)
  422. detect_patch_detect_engine_register_c(proto, args.buffer)
  423. detect_patch_detect_engine_register_h(proto, args.buffer)
  424. if parser:
  425. if args.rust:
  426. print("""
  427. An application detector and parser for the protocol %(proto)s have
  428. now been setup in the files:
  429. rust/src/applayer%(proto_lower)s/mod.rs
  430. rust/src/applayer%(proto_lower)s/parser.rs""" % {
  431. "proto": proto,
  432. "proto_lower": proto.lower(),
  433. })
  434. else:
  435. print("""
  436. An application detector and parser for the protocol %(proto)s have
  437. now been setup in the files:
  438. src/app-layer-%(proto_lower)s.h
  439. src/app-layer-%(proto_lower)s.c""" % {
  440. "proto": proto,
  441. "proto_lower": proto.lower(),
  442. })
  443. if logger:
  444. if args.rust:
  445. print("""
  446. A JSON application layer transaction logger for the protocol
  447. %(proto)s has now been set in the file:
  448. rust/src/applayer%(proto_lower)s/logger.rs""" % {
  449. "proto": proto,
  450. "proto_lower": proto.lower(),
  451. })
  452. else:
  453. print("""
  454. A JSON application layer transaction logger for the protocol
  455. %(proto)s has now been set in the files:
  456. src/output-json-%(proto_lower)s.h
  457. src/output-json-%(proto_lower)s.c""" % {
  458. "proto": proto,
  459. "proto_lower": proto.lower(),
  460. })
  461. if detect:
  462. print("""
  463. The following files have been created and linked into the build:
  464. detect-%(protoname_lower)s-%(buffername_lower)s.h
  465. detect-%(protoname_lower)s-%(buffername_lower)s.c
  466. """ % {
  467. "protoname_lower": proto.lower(),
  468. "buffername_lower": args.buffer.lower(),
  469. })
  470. if parser or logger:
  471. print("""
  472. Suricata should now build cleanly. Try running "./configure" and "make".
  473. """)
  474. if __name__ == "__main__":
  475. try:
  476. sys.exit(main())
  477. except SetupError as err:
  478. print("error: %s" % (err))
  479. sys.exit(1)