]> git.ipfire.org Git - people/ms/suricata.git/blame - scripts/setup-app-layer.py
templates: fix typos
[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 73 ("rust/src/applayertemplate/template.rs",
13049ae0 74 "rust/src/applayer%s/%s.rs" % (lower, 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:
4748826d 93 if line.lstrip().startswith("app-layer-template."):
e232fcc4
JI
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:
4748826d 280 if line.lstrip().startswith("output-json-template."):
e232fcc4
JI
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)),
0b5a2ab4
VJ
322 ("src/tests/detect-template-buffer.c",
323 "src/tests/detect-%s-%s.c" % (lower, buffername_lower)),
7ec7d85e
JI
324 )
325 replacements = (
326 ("TEMPLATE_BUFFER", "%s_%s" % (proto.upper(), buffername.upper())),
327 ("template-buffer", "%s-%s" % (proto.lower(), buffername.lower())),
328 ("template_buffer", "%s_%s" % (proto.lower(), buffername.lower())),
329 ("TemplateBuffer", "%s%s" % (proto, buffername)),
330 )
331
332 common_copy_templates(proto, pairs, replacements)
333
334def detect_patch_makefile_am(protoname, buffername):
335 filename = "src/Makefile.am"
336 print("Patching %s." % (filename))
337 output = io.StringIO()
338 with open(filename) as infile:
339 for line in infile:
4748826d 340 if line.lstrip().startswith("detect-template-buffer."):
7ec7d85e
JI
341 new = line.replace("template-buffer", "%s-%s" % (
342 protoname.lower(), buffername.lower()))
343 output.write(new)
344 output.write(line)
345 open(filename, "w").write(output.getvalue())
346
c6a35d09 347def detect_patch_detect_engine_register_c(protoname, buffername):
7ec7d85e
JI
348 filename = "src/detect-engine-register.c"
349 print("Patching %s." % (filename))
350 output = io.StringIO()
351 with open(filename) as infile:
352 for line in infile:
353
354 if line.find("detect-template-buffer.h") > -1:
355 new = line.replace("template-buffer", "%s-%s" % (
356 protoname.lower(), buffername.lower()))
357 output.write(new)
358
359 if line.find("DetectTemplateBufferRegister") > -1:
360 new = line.replace("TemplateBuffer", "%s%s" % (
361 protoname, buffername))
362 output.write(new)
363
364 output.write(line)
365 open(filename, "w").write(output.getvalue())
366
c6a35d09 367def detect_patch_detect_engine_register_h(protoname, buffername):
7ec7d85e
JI
368 filename = "src/detect-engine-register.h"
369 print("Patching %s." % (filename))
370 output = io.StringIO()
371 with open(filename) as infile:
372 for line in infile:
373
374 if line.find("DETECT_AL_TEMPLATE_BUFFER") > -1:
375 new = line.replace("TEMPLATE_BUFFER", "%s_%s" % (
376 protoname.upper(), buffername.upper()))
377 output.write(new)
378
379 output.write(line)
380 open(filename, "w").write(output.getvalue())
381
e232fcc4
JI
382def proto_exists(proto):
383 upper = proto.upper()
384 for line in open("src/app-layer-protos.h"):
385 if line.find("ALPROTO_%s," % (upper)) > -1:
386 return True
387 return False
388
389epilog = """
390This script will provision a new app-layer parser for the protocol
391name specified on the command line. This is done by copying and
392patching src/app-layer-template.[ch] then linking the new files into
393the build system.
394
c6a35d09 395By default both the parser and logger will be generated. To generate
e232fcc4
JI
396just one or the other use the --parser or --logger command line flags.
397
398Examples:
399
4748826d
JF
400 %(progname)s --logger DNP3
401 %(progname)s --parser Gopher
7ec7d85e
JI
402
403This script can also setup a detect buffer. This is a separate
404operation that must be done after creating the parser.
405
406Examples:
407
408 %(progname)s --detect Gopher Request
e232fcc4
JI
409""" % { "progname": progname, }
410
411def main():
412 parser = argparse.ArgumentParser(
413 formatter_class=argparse.RawDescriptionHelpFormatter,
414 epilog=epilog)
415 parser.add_argument("--rust", action="store_true", default=False,
416 help="Setup Rust protocol template.")
417 parser.add_argument("--logger", action="store_true", default=False,
418 help="Generate logger.")
419 parser.add_argument("--parser", action="store_true", default=False,
420 help="Generate parser.")
7ec7d85e
JI
421 parser.add_argument("--detect", action="store_true", default=False,
422 help="Generate detect module.")
e232fcc4 423 parser.add_argument("proto", help="Name of protocol")
7ec7d85e
JI
424 parser.add_argument("buffer", help="Name of buffer (for --detect)",
425 nargs="?")
e232fcc4
JI
426 args = parser.parse_args()
427
428 proto = args.proto
429
430 # The protocol name must start with an upper case letter.
431 if proto[0] != proto.upper()[0]:
432 raise SetupError("protocol name must begin with an upper case letter")
433
434 # Determine what to generate.
435 parser = False
436 logger = False
7ec7d85e 437 detect = False
e232fcc4
JI
438
439 # If no --parser or no --logger, generate both.
7ec7d85e 440 if not args.parser and not args.logger and not args.detect:
e232fcc4
JI
441 parser = True
442 logger = True
443 else:
444 parser = args.parser
445 logger = args.logger
7ec7d85e
JI
446 detect = args.detect
447
448 if detect:
449 if args.buffer is None:
450 raise SetupError("--detect requires a buffer name")
e232fcc4 451
15922dcd
JI
452 # Make sure we are in the correct directory.
453 if os.path.exists("./suricata.c"):
454 os.chdir("..")
455 elif not os.path.exists("./src/suricata.c"):
456 raise SetupError(
457 "this does not appear to be a Suricata source directory.")
458
e232fcc4
JI
459 if parser:
460 if proto_exists(proto):
461 raise SetupError("protocol already exists: %s" % (proto))
462 copy_app_layer_templates(proto, args.rust)
463 if args.rust:
464 patch_rust_lib_rs(proto)
465 patch_makefile_am(proto)
466 patch_app_layer_protos_h(proto)
467 patch_app_layer_protos_c(proto)
468 patch_app_layer_detect_proto_c(proto)
469 patch_app_layer_parser_c(proto)
470 patch_suricata_yaml_in(proto)
471
472 if logger:
473 if not proto_exists(proto):
474 raise SetupError("no app-layer parser exists for %s" % (proto))
475 logger_copy_templates(proto, args.rust)
476 if args.rust:
477 patch_rust_applayer_mod_rs(proto)
478 logger_patch_makefile_am(proto)
479 logger_patch_suricata_common_h(proto)
480 logger_patch_output_c(proto)
481 logger_patch_suricata_yaml_in(proto)
482 logger_patch_util_profiling_c(proto)
483
7ec7d85e
JI
484 if detect:
485 if not proto_exists(proto):
486 raise SetupError("no app-layer parser exists for %s" % (proto))
487 detect_copy_templates(proto, args.buffer, args.rust)
488 detect_patch_makefile_am(proto, args.buffer)
c6a35d09
JF
489 detect_patch_detect_engine_register_c(proto, args.buffer)
490 detect_patch_detect_engine_register_h(proto, args.buffer)
7ec7d85e 491
e232fcc4
JI
492 if parser:
493 if args.rust:
494 print("""
c6a35d09 495An application detector and parser for the protocol %(proto)s have
e232fcc4
JI
496now been setup in the files:
497
498 rust/src/applayer%(proto_lower)s/mod.rs
499 rust/src/applayer%(proto_lower)s/parser.rs""" % {
500 "proto": proto,
501 "proto_lower": proto.lower(),
502 })
503 else:
504 print("""
c6a35d09 505An application detector and parser for the protocol %(proto)s have
e232fcc4
JI
506now been setup in the files:
507
508 src/app-layer-%(proto_lower)s.h
509 src/app-layer-%(proto_lower)s.c""" % {
510 "proto": proto,
511 "proto_lower": proto.lower(),
512 })
513
514 if logger:
515 if args.rust:
516 print("""
517A JSON application layer transaction logger for the protocol
518%(proto)s has now been set in the file:
519
520 rust/src/applayer%(proto_lower)s/logger.rs""" % {
521 "proto": proto,
522 "proto_lower": proto.lower(),
523 })
524 else:
525 print("""
526A JSON application layer transaction logger for the protocol
527%(proto)s has now been set in the files:
528
529 src/output-json-%(proto_lower)s.h
530 src/output-json-%(proto_lower)s.c""" % {
531 "proto": proto,
532 "proto_lower": proto.lower(),
533 })
534
7ec7d85e
JI
535 if detect:
536 print("""
537The following files have been created and linked into the build:
538
539 detect-%(protoname_lower)s-%(buffername_lower)s.h
540 detect-%(protoname_lower)s-%(buffername_lower)s.c
541""" % {
542 "protoname_lower": proto.lower(),
543 "buffername_lower": args.buffer.lower(),
544})
545
e232fcc4
JI
546 if parser or logger:
547 print("""
4748826d 548Suricata should now build cleanly. Try running "./configure" and "make".
e232fcc4
JI
549""")
550
551if __name__ == "__main__":
552 try:
553 sys.exit(main())
554 except SetupError as err:
555 print("error: %s" % (err))
556 sys.exit(1)