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