diff --git a/.gitignore b/.gitignore index 5c94a3dd..d9028172 100644 --- a/.gitignore +++ b/.gitignore @@ -67,3 +67,4 @@ tests/test-parse tests/test-private tests/*/*.out tests/*/test.sh +tests/testzmq.json diff --git a/src/io.c b/src/io.c index 4a602969..5e5db9af 100644 --- a/src/io.c +++ b/src/io.c @@ -48,22 +48,30 @@ _strip_prefix_if_exists(const char *str, const char *prefix) { return str + strlen(prefix); } -void -add_sock_input(nmsgtool_ctx *c, const char *ss) { +nmsg_res +add_sock_input(nmsgtool_ctx *c, const char *ss) +{ char *t; int pa, pz, pn, pl; t = strchr(ss, '/'); - if (t == NULL) - usage("argument to -l needs a /"); + if (t == NULL) { + fprintf(stderr, "%s: usage error: argument to -l needs a /\n", argv_program); + return (nmsg_res_failure); + } + if (sscanf(t + 1, "%d..%d", &pa, &pz) == 2) { - if (pa > pz || pz - pa > 20) - usage("bad port range in -l argument"); + if (pa > pz || pz - pa > 20) { + fprintf(stderr, "%s: usage error: bad port range in -l argument\n", argv_program); + return (nmsg_res_failure); + } } else if (sscanf(t + 1, "%d", &pa) == 1) { pz = pa; } else { - usage("need a port number or range after /"); + fprintf(stderr, "%s: usage error: need a port number or range after /\n", argv_program); + return (nmsg_res_failure); } + pl = t - ss; for (pn = pa; pn <= pz; pn++) { char *spec; @@ -74,22 +82,26 @@ add_sock_input(nmsgtool_ctx *c, const char *ss) { nmsg_asprintf(&spec, "%*.*s/%d", pl, pl, ss, pn); pf = getsock(&su, spec, NULL, NULL); - if (c->debug >= 2) + if (c->debug >= 2) { fprintf(stderr, "%s: nmsg socket input: %s\n", argv_program, spec); + } + free(spec); - if (pf < 0) - usage("bad -l socket"); + + if (pf < 0) { + fprintf(stderr, "%s: usage error: bad -l socket\n", argv_program); + return (nmsg_res_failure); + } s = socket(pf, SOCK_DGRAM, 0); if (s < 0) { - perror("socket"); - exit(1); + fprintf(stderr, "%s: failed to create socket: %s\n", argv_program, strerror(errno)); + return (nmsg_res_failure); } Setsockopt(s, SOL_SOCKET, SO_REUSEADDR, on); #ifdef SO_REUSEPORT Setsockopt(s, SOL_SOCKET, SO_REUSEPORT, on); #endif - #ifdef __linux__ # ifdef SO_RCVBUFFORCE if (geteuid() == 0) { @@ -106,46 +118,55 @@ add_sock_input(nmsgtool_ctx *c, const char *ss) { } # endif #endif - if (bind(s, &su.sa, NMSGTOOL_SA_LEN(su.sa)) < 0) { perror("bind"); - exit(1); + return (nmsg_res_failure); } input = nmsg_input_open_sock(s); if (input == NULL) { fprintf(stderr, "%s: nmsg_input_open_sock() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } setup_nmsg_input(c, input); res = nmsg_io_add_input(c->io, input, NULL); if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_input() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } c->n_inputs += 1; } + + return (nmsg_res_success); } -void -add_sock_output(nmsgtool_ctx *c, const char *ss) { +nmsg_res +add_sock_output(nmsgtool_ctx *c, const char *ss) +{ nmsg_rate_t nr = NULL; char *r, *t; int pa, pz, pn, pl; t = strchr(ss, '/'); r = strchr(ss, ','); - if (t == NULL) - usage("argument to -s needs a /"); + if (t == NULL) { + fprintf(stderr, "%s: usage error: argument to -s needs a /\n", argv_program); + return (nmsg_res_failure); + } + if (sscanf(t + 1, "%d..%d", &pa, &pz) == 2) { - if (pa > pz || pz - pa > 20) - usage("bad port range in -s argument"); + if (pa > pz || pz - pa > 20) { + fprintf(stderr, "%s: usage error: bad port range in -s argument\n", argv_program); + return (nmsg_res_failure); + } } else if (sscanf(t + 1, "%d", &pa) == 1) { pz = pa; } else { - usage("need a port number or range after /"); + fprintf(stderr, "%s: usage error: need a port number or range after /\n", argv_program); + return (nmsg_res_failure); } + pl = t - ss; for (pn = pa; pn <= pz; pn++) { char *spec; @@ -167,25 +188,28 @@ add_sock_output(nmsgtool_ctx *c, const char *ss) { fprintf(stderr, "%s: nmsg socket rate: %u freq: %u\n", argv_program, rate, freq); free(spec); - if (pf < 0) - usage("bad -s socket"); + if (pf < 0) { + fprintf(stderr, "%s: usage error: bad -s socket\n", argv_program); + return (nmsg_res_failure); + } + s = socket(pf, SOCK_DGRAM, 0); if (s < 0) { - perror("socket"); - exit(1); + fprintf(stderr, "%s: failed to open socket: %s\n", argv_program, strerror(errno)); + return (nmsg_res_failure); } Setsockopt(s, SOL_SOCKET, SO_BROADCAST, on); len = 32 * 1024; Setsockopt(s, SOL_SOCKET, SO_SNDBUF, len); if (connect(s, &su.sa, NMSGTOOL_SA_LEN(su.sa)) < 0) { perror("connect"); - exit(1); + return (nmsg_res_failure); } output = nmsg_output_open_sock(s, c->mtu); if (output == NULL) { fprintf(stderr, "%s: nmsg_output_open_sock() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } setup_nmsg_output(c, output); if (rate > 0) { @@ -203,10 +227,12 @@ add_sock_output(nmsgtool_ctx *c, const char *ss) { if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_output() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } c->n_outputs += 1; } + + return (nmsg_res_success); } #if (defined HAVE_LIBRDKAFKA) @@ -411,53 +437,56 @@ _add_kafka_nmsg_output(nmsgtool_ctx *c __attribute__((unused)), } #endif /* HAVE_LIBRDKAFKA */ -void -add_kafka_input(nmsgtool_ctx *c, const char *str_address) { +nmsg_res +add_kafka_input(nmsgtool_ctx *c, const char *str_address) +{ const char *addr = _strip_prefix_if_exists(str_address, "nmsg:"); if (addr != NULL) { _add_kafka_nmsg_input(c, addr); - return; + return (nmsg_res_success); } addr = _strip_prefix_if_exists(str_address, "nmsgp:"); if (addr != NULL) { _add_kafka_payload_input(c, addr); - return; + return (nmsg_res_success); } addr = _strip_prefix_if_exists(str_address, "json:"); if (addr != NULL) { _add_kafka_json_input(c, addr); - return; + return (nmsg_res_success); } fprintf(stderr, "%s: Error: nmsg, nmsgp, or json protocol must be set for Kafka endpoint\n", argv_program); - exit(EXIT_FAILURE); + return (nmsg_res_failure); } -void -add_kafka_output(nmsgtool_ctx *c, const char *str_address) { +nmsg_res +add_kafka_output(nmsgtool_ctx *c, const char *str_address) +{ const char *addr = _strip_prefix_if_exists(str_address, "nmsg:"); if (addr != NULL) { _add_kafka_nmsg_output(c, addr); - return; + return (nmsg_res_success); } addr = _strip_prefix_if_exists(str_address, "nmsgp:"); if (addr != NULL) { _add_kafka_payload_output(c, addr); - return; + return (nmsg_res_success); } addr = _strip_prefix_if_exists(str_address, "json:"); if (addr != NULL) { _add_kafka_json_output(c, addr); - return; + return (nmsg_res_success); } fprintf(stderr, "%s: Error: nmsg, nmsgp, or json protocol must be set for Kafka endpoint\n", argv_program); - exit(EXIT_FAILURE); + return (nmsg_res_failure); } #ifdef HAVE_LIBZMQ -void -add_zsock_input(nmsgtool_ctx *c, const char *str_socket) { +nmsg_res +add_zsock_input(nmsgtool_ctx *c, const char *str_socket) +{ nmsg_res res; nmsg_input_t input; @@ -466,30 +495,33 @@ add_zsock_input(nmsgtool_ctx *c, const char *str_socket) { fprintf(stderr, "%s: nmsg ZMQ input: %s\n", argv_program, str_socket); if (input == NULL) { fprintf(stderr, "%s: nmsg_input_open_zmq_endpoint() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } setup_nmsg_input(c, input); res = nmsg_io_add_input(c->io, input, NULL); if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_input() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } c->n_inputs += 1; + + return (nmsg_res_success); } #else /* HAVE_LIBZMQ */ -void +nmsg_res add_zsock_input(nmsgtool_ctx *c __attribute__((unused)), const char *str_socket __attribute__((unused))) { fprintf(stderr, "%s: Error: compiled without libzmq support\n", argv_program); - exit(EXIT_FAILURE); + return (nmsg_res_failure); } #endif /* HAVE_LIBZMQ */ #ifdef HAVE_LIBZMQ -void -add_zsock_output(nmsgtool_ctx *c, const char *str_socket) { +nmsg_res +add_zsock_output(nmsgtool_ctx *c, const char *str_socket) +{ nmsg_res res; nmsg_output_t output; @@ -498,7 +530,7 @@ add_zsock_output(nmsgtool_ctx *c, const char *str_socket) { fprintf(stderr, "%s: nmsg ZMQ output: %s\n", argv_program, str_socket); if (output == NULL) { fprintf(stderr, "%s: nmsg_output_open_zmq_endpoint() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } setup_nmsg_output(c, output); if (c->kicker != NULL) @@ -507,23 +539,25 @@ add_zsock_output(nmsgtool_ctx *c, const char *str_socket) { res = nmsg_io_add_output(c->io, output, NULL); if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_output() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } c->n_outputs += 1; + return (nmsg_res_success); } #else /* HAVE_LIBZMQ */ -void +nmsg_res add_zsock_output(nmsgtool_ctx *c __attribute__((unused)), const char *str_socket __attribute__((unused))) { fprintf(stderr, "%s: Error: compiled without libzmq support\n", argv_program); - exit(EXIT_FAILURE); + return (nmsg_res_failure); } #endif /* HAVE_LIBZMQ */ -void -add_file_input(nmsgtool_ctx *c, const char *fname) { +nmsg_res +add_file_input(nmsgtool_ctx *c, const char *fname) +{ nmsg_input_t input; nmsg_res res; @@ -531,7 +565,7 @@ add_file_input(nmsgtool_ctx *c, const char *fname) { if (input == NULL) { fprintf(stderr, "%s: nmsg_input_open_file() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } if (c->debug >= 2) fprintf(stderr, "%s: nmsg file input: %s\n", argv_program, @@ -547,13 +581,15 @@ add_file_input(nmsgtool_ctx *c, const char *fname) { if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_input() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } c->n_inputs += 1; + return (nmsg_res_success); } -void -add_file_output(nmsgtool_ctx *c, const char *fname) { +nmsg_res +add_file_output(nmsgtool_ctx *c, const char *fname) +{ nmsg_output_t output; nmsg_res res; @@ -573,7 +609,7 @@ add_file_output(nmsgtool_ctx *c, const char *fname) { if (output == NULL) { fprintf(stderr, "%s: nmsg_output_open_file() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } setup_nmsg_output(c, output); res = nmsg_io_add_output(c->io, output, (void *) kf); @@ -584,7 +620,7 @@ add_file_output(nmsgtool_ctx *c, const char *fname) { if (output == NULL) { fprintf(stderr, "%s: nmsg_output_open_file() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } setup_nmsg_output(c, output); res = nmsg_io_add_output(c->io, output, NULL); @@ -592,16 +628,18 @@ add_file_output(nmsgtool_ctx *c, const char *fname) { if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_output() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } if (c->debug >= 2) fprintf(stderr, "%s: nmsg file output: %s\n", argv_program, fname); c->n_outputs += 1; + return (nmsg_res_success); } -void -add_pcapfile_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *fname) { +nmsg_res +add_pcapfile_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *fname) +{ char errbuf[PCAP_ERRBUF_SIZE]; nmsg_input_t input; nmsg_pcap_t pcap; @@ -612,43 +650,46 @@ add_pcapfile_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *fname) { if (phandle == NULL) { fprintf(stderr, "%s: unable to add pcap file input %s: %s\n", argv_program, fname, errbuf); - exit(1); + return (nmsg_res_failure); } pcap = nmsg_pcap_input_open(phandle); if (pcap == NULL) { fprintf(stderr, "%s: nmsg_pcap_input_open() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } input = nmsg_input_open_pcap(pcap, mod); if (input == NULL) { fprintf(stderr, "%s: nmsg_input_open_pcap() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } if (c->bpfstr != NULL) { res = nmsg_pcap_input_setfilter(pcap, c->bpfstr); if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_pcap_input_setfilter() failed\n", argv_program); - exit(1); + return (res); } } res = nmsg_io_add_input(c->io, input, NULL); if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_input() failed\n", argv_program); - exit(1); + return (res); } if (c->debug >= 2) fprintf(stderr, "%s: pcap file input: %s\n", argv_program, fname); c->n_inputs += 1; + + return (nmsg_res_success); } -void -add_pcapif_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *arg) { +nmsg_res +add_pcapif_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *arg) +{ char errbuf[PCAP_ERRBUF_SIZE]; char *iface, *ssnaplen, *spromisc; char *saveptr = NULL; @@ -673,7 +714,7 @@ add_pcapif_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *arg) { fprintf(stderr, "%s: parse error: " "'%s' is not a valid snaplen\n", argv_program, ssnaplen); - exit(1); + return (nmsg_res_failure); } } if (spromisc != NULL) { @@ -686,44 +727,44 @@ add_pcapif_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *arg) { if (phandle == NULL) { fprintf(stderr, "%s: unable to add pcap interface input " "%s: %s\n", argv_program, iface, errbuf); - exit(1); + return (nmsg_res_failure); } rc = pcap_set_promisc(phandle, promisc); if (rc != 0) { fprintf(stderr, "%s: pcap_set_promisc() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } rc = pcap_set_snaplen(phandle, snaplen); if (rc != 0) { fprintf(stderr, "%s: pcap_set_snaplen() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } rc = pcap_set_timeout(phandle, 1000); if (rc != 0) { fprintf(stderr, "%s: pcap_set_timeout() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } rc = pcap_set_buffer_size(phandle, 16777216); if (rc != 0) { fprintf(stderr, "%s: pcap_set_buffer_size() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } rc = pcap_activate(phandle); if (rc != 0) { fprintf(stderr, "%s: pcap_activate() failed: %d\n", argv_program, rc); - exit(1); + return (nmsg_res_failure); } #else phandle = pcap_open_live(iface, snaplen, promisc, 1000, errbuf); if (phandle == NULL) { fprintf(stderr, "%s: unable to add pcap interface input " "%s: %s\n", argv_program, iface, errbuf); - exit(1); + return (nmsg_res_failure); } #endif @@ -731,24 +772,24 @@ add_pcapif_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *arg) { if (pcap == NULL) { fprintf(stderr, "%s: nmsg_pcap_input_open() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } input = nmsg_input_open_pcap(pcap, mod); if (input == NULL) { fprintf(stderr, "%s: nmsg_input_open_pcap() failed\n", argv_program); - exit(1); + return (nmsg_res_failure); } if (c->bpfstr != NULL) { res = nmsg_pcap_input_setfilter(pcap, c->bpfstr); if (res != nmsg_res_success) - exit(1); + return (res); } res = nmsg_io_add_input(c->io, input, NULL); if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_input() failed\n", argv_program); - exit(1); + return (res); } if (c->debug >= 2) @@ -757,10 +798,13 @@ add_pcapif_input(nmsgtool_ctx *c, nmsg_msgmod_t mod, const char *arg) { c->n_inputs += 1; free(tmp); + + return (res); } -void -add_pres_output(nmsgtool_ctx *c, const char *fname) { +nmsg_res +add_pres_output(nmsgtool_ctx *c, const char *fname) +{ nmsg_output_t output; nmsg_res res; @@ -785,16 +829,19 @@ add_pres_output(nmsgtool_ctx *c, const char *fname) { if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_output() failed\n", argv_program); - exit(1); + return (res); } if (c->debug >= 2) fprintf(stderr, "%s: nmsg pres output: %s\n", argv_program, fname); c->n_outputs += 1; + + return (res); } -void -add_json_input(nmsgtool_ctx *c, const char *fname) { +nmsg_res +add_json_input(nmsgtool_ctx *c, const char *fname) +{ nmsg_input_t input; nmsg_res res; @@ -803,16 +850,18 @@ add_json_input(nmsgtool_ctx *c, const char *fname) { if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_input() failed\n", argv_program); - exit(1); + return (res); } if (c->debug >= 2) fprintf(stderr, "%s: nmsg json input: %s\n", argv_program, fname); c->n_inputs += 1; + return (res); } -void -add_json_output(nmsgtool_ctx *c, const char *fname) { +nmsg_res +add_json_output(nmsgtool_ctx *c, const char *fname) +{ nmsg_output_t output; nmsg_res res; @@ -838,16 +887,19 @@ add_json_output(nmsgtool_ctx *c, const char *fname) { if (res != nmsg_res_success) { fprintf(stderr, "%s: nmsg_io_add_output() failed\n", argv_program); - exit(1); + return (res); } if (c->debug >= 2) fprintf(stderr, "%s: nmsg json output: %s\n", argv_program, fname); c->n_outputs += 1; + + return (res); } -void -add_filter_module(nmsgtool_ctx *c, const char *args) { +nmsg_res +add_filter_module(nmsgtool_ctx *c, const char *args) +{ nmsg_res res; char *tmp = NULL; char *saveptr = NULL; @@ -871,14 +923,16 @@ add_filter_module(nmsgtool_ctx *c, const char *args) { if (c->debug >= 2) fprintf(stderr, "%s: nmsg_io_add_filter_module() failed for %s,%s: %s (%d)\n", argv_program, mod_name, mod_param, nmsg_res_lookup(res), res); - exit(EXIT_FAILURE); + return (res); } my_free(tmp); + return (res); } -void -add_stats_module(nmsgtool_ctx *c, const char *args) { +nmsg_res +add_stats_module(nmsgtool_ctx *c, const char *args) +{ char *tmp = NULL; char *saveptr = NULL; char *mod_name = NULL; @@ -903,7 +957,7 @@ add_stats_module(nmsgtool_ctx *c, const char *args) { if (c->debug >= 2) fprintf(stderr, "%s: nmsg_statsmod_init() failed for %s,%s\n", argv_program, mod_name, mod_param); - exit(EXIT_FAILURE); + return (nmsg_res_failure); } /* Instrument io in stats module */ @@ -913,4 +967,6 @@ add_stats_module(nmsgtool_ctx *c, const char *args) { statsmod_vec_add(c->statsmods_loaded, smod); my_free(tmp); + + return (nmsg_res_success); } diff --git a/src/nmsgtool.c b/src/nmsgtool.c index 3d870345..7446b784 100644 --- a/src/nmsgtool.c +++ b/src/nmsgtool.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 DomainTools LLC + * Copyright (c) 2023-2024, 2026 DomainTools LLC * Copyright (c) 2008-2021 by Farsight Security, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +15,6 @@ * limitations under the License. */ -/* Import. */ - #include #include #include @@ -31,8 +29,6 @@ #include "nmsgtool.h" #include "kickfile.h" -/* Globals. */ - static nmsgtool_ctx ctx; static argv_t args[] = { @@ -335,50 +331,103 @@ static void io_close(struct nmsg_io_close_event *); static void setup_signals(void); static void signal_handler(int); -/* Functions. */ - -int main(int argc, char **argv) { +int +main(int argc, char **argv) +{ nmsg_res res; /* parse command line arguments */ argv_process(args, argc, argv); + if (ctx.help) { + argv_usage(args, ARGV_USAGE_DEFAULT); + argv_cleanup(args); + return (EXIT_SUCCESS); + } + + if (ctx.version) { + int support = 0; + fprintf(stderr, "%s: version %s", argv_program, PACKAGE_VERSION); +#ifndef HAVE_LIBZMQ + support |= 1; +#endif +#ifndef HAVE_LIBRDKAFKA + support |= 2; +#endif + if (support > 0) { + fprintf(stderr, " ("); + switch (support) { + case 1: + fprintf(stderr, "without libzmq support"); + break; + case 2: + fprintf(stderr, "without librdkafka support"); + break; + case 3: + fprintf(stderr, "without libzmq and librdkafka support"); + break; + default: + break; + } + fprintf(stderr, ")"); + } + fprintf(stderr, "\n"); + argv_cleanup(args); + return (EXIT_SUCCESS); + } + if (ctx.debug < 1) ctx.debug = 1; + nmsg_set_debug(ctx.debug); + res = nmsg_init(); if (res != nmsg_res_success) { - fprintf(stderr, "nmsgtool: unable to initialize libnmsg\n"); + fprintf(stderr, "%s: unable to initialize libnmsg\n", argv_program); return (EXIT_FAILURE); } - if (ctx.debug >= 2) + + if (ctx.debug >= 2) { #ifdef HAVE_LIBZMQ - fprintf(stderr, "nmsgtool: version " VERSION "\n"); -#else /* HAVE_LIBZMQ */ - fprintf(stderr, "nmsgtool: version " VERSION " (without libzmq support)\n"); -#endif /* HAVE_LIBZMQ */ + fprintf(stderr, "%s: version " VERSION "\n", argv_program); +#else + fprintf(stderr, "%s: version " VERSION " (without libzmq support)\n", argv_program); +#endif + } ctx.statsmods_loaded = statsmod_vec_init(1); /* initialize the nmsg_io engine */ ctx.io = nmsg_io_init(); if (ctx.io == NULL) { - fprintf(stderr, "Error: failed to initialize NMSG IO engine\n"); - exit(EXIT_FAILURE); + fprintf(stderr, "%s: ERROR: failed to initialize NMSG IO engine\n", argv_program); + return (EXIT_FAILURE); } + nmsg_io_set_close_fp(ctx.io, io_close); /* process arguments and load inputs/outputs into the nmsg_io engine */ - process_args(&ctx); - - setup_signals(); + res = process_args(&ctx); + switch (res) { + case nmsg_res_success: + setup_signals(); - /* run the nmsg_io engine */ - res = nmsg_io_loop(ctx.io); + res = nmsg_io_loop(ctx.io); - /* print stats, if requested */ - if (ctx.debug >= 2) { - print_io_stats(ctx.io); + if (ctx.debug >= 2) { + print_io_stats(ctx.io); + if (res != nmsg_res_success || ctx.signal != 0) { + if (ctx.signal == 0) + fprintf(stderr, "%s: nmsg_io_loop() failed: %s (%d)\n", + argv_program, nmsg_res_lookup(res), res); + else + fprintf(stderr, "%s: received signal: %s\n", + argv_program, strsignal(ctx.signal)); + } + } + break; + default: + break; } /* cleanup */ @@ -398,37 +447,13 @@ int main(int argc, char **argv) { #ifdef HAVE_LIBZMQ if (ctx.zmq_ctx) zmq_term(ctx.zmq_ctx); -#endif /* HAVE_LIBZMQ */ +#endif free(ctx.endline_str); argv_cleanup(args); - if (res != nmsg_res_success || ctx.signal != 0) { - if (ctx.debug >= 2) { - if (ctx.signal == 0) - fprintf(stderr, "%s: nmsg_io_loop() failed: %s (%d)\n", argv_program, nmsg_res_lookup(res), - res); - else - fprintf(stderr, "%s: received signal: %s\n", argv_program, strsignal(ctx.signal)); - } - return EXIT_FAILURE; - } - return EXIT_SUCCESS; -} - -void -usage(const char *msg) { - if (msg != NULL) - fprintf(stderr, "%s: usage error: %s\n", argv_program, msg); - else - argv_usage(args, ARGV_USAGE_DEFAULT); - - for (size_t i = 0; i < statsmod_vec_size(ctx.statsmods_loaded); i++) { - nmsg_statsmod_destroy(&statsmod_vec_data(ctx.statsmods_loaded)[i]); - } - statsmod_vec_destroy(&ctx.statsmods_loaded); - - nmsg_io_destroy(&ctx.io); - exit(msg == NULL ? EXIT_SUCCESS : EXIT_FAILURE); + if (res == nmsg_res_success && ctx.signal == 0) + return (EXIT_SUCCESS); + return (EXIT_FAILURE); } void diff --git a/src/nmsgtool.h b/src/nmsgtool.h index 2d1c99b8..ca4812e6 100644 --- a/src/nmsgtool.h +++ b/src/nmsgtool.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 DomainTools LLC + * Copyright (c) 2023-2024, 2026 DomainTools LLC * Copyright (c) 2008-2019, 2021 by Farsight Security, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -121,25 +121,24 @@ FILE *pidfile_open(const char *pidfile); int getsock(nmsgtool_sockaddr *, const char *, unsigned *, unsigned *); int open_rfile(const char *); int open_wfile(const char *); -void add_file_input(nmsgtool_ctx *, const char *); -void add_file_output(nmsgtool_ctx *, const char *); -void add_pcapfile_input(nmsgtool_ctx *, nmsg_msgmod_t, const char *); -void add_pcapif_input(nmsgtool_ctx *, nmsg_msgmod_t, const char *); -void add_pres_output(nmsgtool_ctx *, const char *); -void add_json_input(nmsgtool_ctx *, const char *); -void add_json_output(nmsgtool_ctx *, const char *); -void add_sock_input(nmsgtool_ctx *, const char *); -void add_sock_output(nmsgtool_ctx *, const char *); -void add_kafka_input(nmsgtool_ctx *, const char *); -void add_kafka_output(nmsgtool_ctx *, const char *); -void add_zsock_input(nmsgtool_ctx *, const char *); -void add_zsock_output(nmsgtool_ctx *, const char *); -void add_filter_module(nmsgtool_ctx *, const char *); -void add_stats_module(nmsgtool_ctx *, const char *); +nmsg_res add_file_input(nmsgtool_ctx *, const char *); +nmsg_res add_file_output(nmsgtool_ctx *, const char *); +nmsg_res add_pcapfile_input(nmsgtool_ctx *, nmsg_msgmod_t, const char *); +nmsg_res add_pcapif_input(nmsgtool_ctx *, nmsg_msgmod_t, const char *); +nmsg_res add_pres_output(nmsgtool_ctx *, const char *); +nmsg_res add_json_input(nmsgtool_ctx *, const char *); +nmsg_res add_json_output(nmsgtool_ctx *, const char *); +nmsg_res add_sock_input(nmsgtool_ctx *, const char *); +nmsg_res add_sock_output(nmsgtool_ctx *, const char *); +nmsg_res add_kafka_input(nmsgtool_ctx *, const char *); +nmsg_res add_kafka_output(nmsgtool_ctx *, const char *); +nmsg_res add_zsock_input(nmsgtool_ctx *, const char *); +nmsg_res add_zsock_output(nmsgtool_ctx *, const char *); +nmsg_res add_filter_module(nmsgtool_ctx *, const char *); +nmsg_res add_stats_module(nmsgtool_ctx *, const char *); void pidfile_write(FILE *); -void process_args(nmsgtool_ctx *); +nmsg_res process_args(nmsgtool_ctx *); void setup_nmsg_input(nmsgtool_ctx *, nmsg_input_t); void setup_nmsg_output(nmsgtool_ctx *, nmsg_output_t); -void usage(const char *); #endif /* NMSGTOOL_H */ diff --git a/src/process_args.c b/src/process_args.c index c9c49419..90445fe4 100644 --- a/src/process_args.c +++ b/src/process_args.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023-2024 DomainTools LLC + * Copyright (c) 2023-2024, 2026 DomainTools LLC * Copyright (c) 2008-2015, 2019, 2021 by Farsight Security, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,25 +27,28 @@ #include "nmsgtool.h" -static void -droproot(nmsgtool_ctx *c, FILE *fp_pidfile) { +static nmsg_res +droproot(nmsgtool_ctx *c, FILE *fp_pidfile) +{ struct passwd *pw = NULL; if (c->username == NULL) - return; + return (nmsg_res_success); pw = getpwnam(c->username); if (pw == NULL) { fprintf(stderr, "%s: username %s does not exist\n", argv_program, c->username); - exit(1); + return (nmsg_res_failure); } if (fp_pidfile != NULL) { int fd = fileno(fp_pidfile); if (fd != -1) { if (fchown(fd, pw->pw_uid, pw->pw_gid) != 0) { - fprintf(stderr,"%s: fchown() on pid file failed: %s\n", argv_program, strerror(errno)); + fprintf(stderr,"%s: fchown() on pid file failed: %s\n", argv_program, + strerror(errno)); + return (nmsg_res_failure); } } } @@ -55,12 +58,14 @@ droproot(nmsgtool_ctx *c, FILE *fp_pidfile) { { fprintf(stderr, "%s: unable to change to user %s\n", argv_program, c->username); - exit(1); + return (nmsg_res_failure); } if (c->debug >= 2) fprintf(stderr, "%s: switched to user %s\n", argv_program, c->username); + + return (nmsg_res_success); } /* Convert string to non-zero unsigned 32 bit val, returning zero on failure. */ @@ -79,44 +84,45 @@ read_uint32_nz(const char *str) return (uint32_t)val; } -void -process_args(nmsgtool_ctx *c) { - char *t; - FILE *fp_pidfile = NULL; - nmsg_msgmod_t mod = NULL; +static nmsg_res +process_args_loop(nmsgtool_ctx *c, argv_array_t *arry, nmsg_res (*f)(nmsgtool_ctx *, const char *)) +{ + nmsg_res res = nmsg_res_success; - if (c->help) - usage(NULL); + for (int i = 0; i < ARGV_ARRAY_COUNT(*arry); i++) { + res = f(c, *ARGV_ARRAY_ENTRY_P(*arry, char *, i)); + if (res != nmsg_res_success) { + break; + } + } - if (c->version) { - int support = 0; - fprintf(stderr, "%s: version %s", argv_program, PACKAGE_VERSION); -#ifndef HAVE_LIBZMQ - support |= 1; -#endif /* HAVE_LIBZMQ */ -#ifndef HAVE_LIBRDKAFKA - support |= 2; -#endif - if (support > 0) { - fprintf(stderr, " ("); - switch(support) { - case 1: - fprintf(stderr, "without libzmq support"); - break; - case 2: - fprintf(stderr, "without librdkafka support"); - break; - case 3: - fprintf(stderr, "without libzmq and librdkafka support"); - default: - break; - } - fprintf(stderr, ")"); + return (res); +} + +static nmsg_res +process_args_loop_mod(nmsgtool_ctx *c, argv_array_t *arry, nmsg_res (*f)(nmsgtool_ctx *, nmsg_msgmod_t, const char *), + nmsg_msgmod_t *mod) +{ + nmsg_res res = nmsg_res_success; + + for (int i = 0; i < ARGV_ARRAY_COUNT(*arry); i++) { + res = f(c, *mod, *ARGV_ARRAY_ENTRY_P(*arry, char *, i)); + if (res != nmsg_res_success) { + break; } - fprintf(stderr, "\n"); - exit(EXIT_SUCCESS); } + return (res); +} + +nmsg_res +process_args(nmsgtool_ctx *c) +{ + char *t; + FILE *fp_pidfile = NULL; + nmsg_msgmod_t mod = NULL; + nmsg_res res; + if (c->endline == NULL) c->endline_str = strdup("\n"); else @@ -129,21 +135,29 @@ process_args(nmsgtool_ctx *c) { c->vname = "base"; if (c->vname != NULL) { - if (c->mname == NULL) - usage("-V requires -T"); + if (c->mname == NULL) { + fprintf(stderr, "%s: usage error: -V requires -T\n", argv_program); + return (nmsg_res_failure); + } c->vid = nmsg_msgmod_vname_to_vid(c->vname); - if (c->vid == 0) - usage("invalid vendor ID"); + if (c->vid == 0) { + fprintf(stderr, "%s: usage error: invalid vendor ID\n", argv_program); + return (nmsg_res_failure); + } if (c->debug >= 2) fprintf(stderr, "%s: input vendor = %s\n", argv_program, c->vname); } if (c->mname != NULL) { - if (c->vname == NULL) - usage("-T requires -V"); + if (c->vname == NULL) { + fprintf(stderr, "%s: usage error: -T requires -V\n", argv_program); + return (nmsg_res_failure); + } c->msgtype = nmsg_msgmod_mname_to_msgtype(c->vid, c->mname); - if (c->msgtype == 0) - usage("invalid message type"); + if (c->msgtype == 0) { + fprintf(stderr, "%s: invalid message type\n", argv_program); + return (nmsg_res_failure); + } if (c->debug >= 2) fprintf(stderr, "%s: input msgtype = %s\n", argv_program, c->mname); @@ -187,8 +201,10 @@ process_args(nmsgtool_ctx *c) { /* set source, operator, group */ if (c->set_source_str != NULL) { c->set_source = read_uint32_nz(c->set_source_str); - if (c->set_source == 0) - usage("invalid source ID"); + if (c->set_source == 0) { + fprintf(stderr, "%s: usage error: invalid source ID\n", argv_program); + return (nmsg_res_failure); + } if (c->debug >= 2) fprintf(stderr, "%s: nmsg source set to %#.08x\n", argv_program, c->set_source); @@ -198,8 +214,10 @@ process_args(nmsgtool_ctx *c) { c->set_operator_str); if (c->set_operator == 0) c->set_operator = read_uint32_nz(c->set_operator_str); - if (c->set_operator == 0) - usage("unknown operator name"); + if (c->set_operator == 0) { + fprintf(stderr, "%s: usage error: unknown operator name\n", argv_program); + return (nmsg_res_failure); + } if (c->debug >= 2) fprintf(stderr, "%s: nmsg operator set to '%s' (%u)\n", argv_program, @@ -212,8 +230,10 @@ process_args(nmsgtool_ctx *c) { c->set_group_str); if (c->set_group == 0) c->set_group = read_uint32_nz(c->set_group_str); - if (c->set_group == 0) - usage("unknown group name"); + if (c->set_group == 0) { + fprintf(stderr, "%s: usage error: unknown group name\n", argv_program); + return (nmsg_res_failure); + } if (c->debug >= 2) fprintf(stderr, "%s: nmsg group set to '%s' (%u)\n", argv_program, @@ -224,8 +244,10 @@ process_args(nmsgtool_ctx *c) { /* get source, operator, group */ if (c->get_source_str != NULL) { c->get_source = read_uint32_nz(c->get_source_str); - if (c->get_source == 0) - usage("invalid filter source ID"); + if (c->get_source == 0) { + fprintf(stderr, "%s: usage error: invalid filter source ID\n", argv_program); + return (nmsg_res_failure); + } if (c->debug >= 2) fprintf(stderr, "%s: nmsg source filter set to " "%#.08x\n", @@ -237,8 +259,10 @@ process_args(nmsgtool_ctx *c) { c->get_operator_str); if (c->get_operator == 0) c->get_operator = read_uint32_nz(c->get_operator_str); - if (c->get_operator == 0) - usage("unknown filter operator name"); + if (c->get_operator == 0) { + fprintf(stderr, "%s: usage error: unknown filter operator name\n", argv_program); + return (nmsg_res_failure); + } if (c->debug >= 2) fprintf(stderr, "%s: nmsg filter operator set to " "'%s' (%u)\n", @@ -252,8 +276,10 @@ process_args(nmsgtool_ctx *c) { c->get_group_str); if (c->get_group == 0) c->get_group = read_uint32_nz(c->get_group_str); - if (c->get_group == 0) - usage("unknown filter group name"); + if (c->get_group == 0) { + fprintf(stderr, "%s: usage error: unknown filter group name\n", argv_program); + return (nmsg_res_failure); + } if (c->debug >= 2) fprintf(stderr, "%s: nmsg filter group set to " "'%s' (%u)\n", @@ -266,25 +292,22 @@ process_args(nmsgtool_ctx *c) { if (ARGV_ARRAY_COUNT(c->r_pcapfile) > 0 || ARGV_ARRAY_COUNT(c->r_pcapif) > 0) { - if (c->vname == NULL || c->mname == NULL) - usage("reading pcap data requires -V, -T"); + if (c->vname == NULL || c->mname == NULL) { + fprintf(stderr, "%s: usage error: reading pcap data requires -V, -T\n", argv_program); + return (nmsg_res_failure); + } mod = nmsg_msgmod_lookup(c->vid, c->msgtype); - if (mod == NULL) - usage("unknown msgmod"); + if (mod == NULL) { + fprintf(stderr, "%s: usage error: unknown msgmod\n", argv_program); + return (nmsg_res_failure); + } } -#define process_args_loop(arry, func) do { \ - for (int i = 0; i < ARGV_ARRAY_COUNT(arry); i++) \ - func(c, *ARGV_ARRAY_ENTRY_P(arry, char *, i)); \ -} while(0) - -#define process_args_loop_mod(arry, func, mod) do { \ - for (int i = 0; i < ARGV_ARRAY_COUNT(arry); i++) \ - func(c, mod, *ARGV_ARRAY_ENTRY_P(arry, char *, i)); \ -} while(0) - /* pcap interface inputs */ - process_args_loop_mod(c->r_pcapif, add_pcapif_input, mod); + res = process_args_loop_mod(c, &c->r_pcapif, add_pcapif_input, &mod); + if (res != nmsg_res_success) { + return (res); + } /* open pidfile if necessary */ if (c->pidfile != NULL) @@ -293,11 +316,18 @@ process_args(nmsgtool_ctx *c) { fp_pidfile = NULL; /* drop privileges */ - if (c->username != NULL) - droproot(c, fp_pidfile); + if (c->username != NULL) { + res = droproot(c, fp_pidfile); + if (res != nmsg_res_success) { + return (res); + } + } /* pcap file inputs */ - process_args_loop_mod(c->r_pcapfile, add_pcapfile_input, mod); + res = process_args_loop_mod(c, &c->r_pcapfile, add_pcapfile_input, &mod); + if (res != nmsg_res_success) { + return (res); + } /* ZMQ context */ if (ARGV_ARRAY_COUNT(c->r_zsock) > 0 || @@ -309,25 +339,15 @@ process_args(nmsgtool_ctx *c) { if (c->zmq_ctx == NULL) { fprintf(stderr, "%s: zmq_ctx_new() failed: %s\n", argv_program, strerror(errno)); - exit(EXIT_FAILURE); + return (nmsg_res_failure); } #else /* HAVE_LIBZMQ */ fprintf(stderr, "%s: Error: compiled without libzmq support\n", argv_program); - exit(EXIT_FAILURE); + return (nmsg_res_failure); #endif /* HAVE_LIBZMQ */ } - /* nmsg inputs and outputs */ - process_args_loop(c->r_sock, add_sock_input); - process_args_loop(c->w_sock, add_sock_output); - process_args_loop(c->r_zsock, add_zsock_input); - process_args_loop(c->w_zsock, add_zsock_output); - process_args_loop(c->r_kafka, add_kafka_input); - process_args_loop(c->w_kafka, add_kafka_output); - process_args_loop(c->r_nmsg, add_file_input); - process_args_loop(c->w_nmsg, add_file_output); - for (int i = 0; i < ARGV_ARRAY_COUNT(c->r_channel); i++) { char *ch; char **alias = NULL; @@ -337,12 +357,20 @@ process_args(nmsgtool_ctx *c) { if (c->debug >= 2) fprintf(stderr, "%s: looking up channel '%s'\n", argv_program, ch); num_aliases = nmsg_chalias_lookup(ch, &alias); - if (num_aliases <= 0) - usage("channel alias lookup failed"); + if (num_aliases <= 0) { + fprintf(stderr, "%s: usage error: channel alias lookup failed\n", argv_program); + return (nmsg_res_failure); + } for (int j = 0; j < num_aliases; j++) { - if (strstr(alias[j], "://")) - usage("channel alias appears to be a ZeroMQ endpoint"); - add_sock_input(c, alias[j]); + if (strstr(alias[j], "://")) { + fprintf(stderr, "%s: usage error: channel alias appears to be a ZeroMQ endpoint\n", + argv_program); + return (nmsg_res_failure); + } + res = add_sock_input(c, alias[j]); + if (res != nmsg_res_success) { + return (res); + } } nmsg_chalias_free(&alias); } @@ -356,29 +384,24 @@ process_args(nmsgtool_ctx *c) { if (c->debug >= 2) fprintf(stderr, "%s: looking up zchannel '%s'\n", argv_program, ch); num_aliases = nmsg_chalias_lookup(ch, &alias); - if (num_aliases <= 0) - usage("zchannel alias lookup failed"); + if (num_aliases <= 0) { + fprintf(stderr, "%s: usage error: zchannel alias lookup failed\n", argv_program); + return (nmsg_res_failure); + } for (int j = 0; j < num_aliases; j++) { - if (!strstr(alias[j], "://")) - usage("zchannel alias needs to be a ZeroMQ endpoint"); - add_zsock_input(c, alias[j]); + if (!strstr(alias[j], "://")) { + fprintf(stderr, "%s: usage error: zchannel alias needs to be a ZeroMQ endpoint\n", + argv_program); + return (nmsg_res_failure); + } + res = add_zsock_input(c, alias[j]); + if (res != nmsg_res_success) { + return (res); + } } nmsg_chalias_free(&alias); } - /* pres outputs */ - process_args_loop(c->w_pres, add_pres_output); - - /* json inputs and outputs */ - process_args_loop(c->r_json, add_json_input); - process_args_loop(c->w_json, add_json_output); - - /* stats modules */ - process_args_loop(c->statsmods, add_stats_module); - - /* filter modules */ - process_args_loop(c->filters, add_filter_module); - /* filter policy */ if (ARGV_ARRAY_COUNT(c->filters) > 0 && c->filter_policy != NULL) { if (strcasecmp(c->filter_policy, "ACCEPT") == 0) { @@ -394,31 +417,98 @@ process_args(nmsgtool_ctx *c) { } else { fprintf(stderr, "%s: unknown filter policy '%s'\n", argv_program, c->filter_policy); - exit(EXIT_FAILURE); + return (nmsg_res_failure); } } -#undef process_args_loop -#undef process_args_loop_mod + /* nmsg inputs and outputs that create files */ + res = process_args_loop(c, &c->r_sock, add_sock_input); + if (res != nmsg_res_success) { + return (res); + } + + res = process_args_loop(c, &c->w_sock, add_sock_output); + if (res != nmsg_res_success) { + return (res); + } + res = process_args_loop(c, &c->r_zsock, add_zsock_input); + if (res != nmsg_res_success) { + return (res); + } + res = process_args_loop(c, &c->w_zsock, add_zsock_output); + if (res != nmsg_res_success) { + return (res); + } + res = process_args_loop(c, &c->r_kafka, add_kafka_input); + if (res != nmsg_res_success) { + return (res); + } + res = process_args_loop(c, &c->w_kafka, add_kafka_output); + if (res != nmsg_res_success) { + return (res); + } + res = process_args_loop(c, &c->r_nmsg, add_file_input); + if (res != nmsg_res_success) { + return (res); + } + + /* json input */ + res = process_args_loop(c, &c->r_json, add_json_input); + if (res != nmsg_res_success) { + return (res); + } + + /* stats modules */ + res = process_args_loop(c, &c->statsmods, add_stats_module); + if (res != nmsg_res_success) { + return (res); + } + + /* filter modules */ + res = process_args_loop(c, &c->filters, add_filter_module); + if (res != nmsg_res_success) { + return (res); + } /* validation */ - if (c->n_inputs == 0) - usage("no data sources specified (-h for more help)"); + if (c->n_inputs == 0) { + fprintf(stderr, "%s: usage error: no data sources specified (-h for more help)\n", argv_program); + return (nmsg_res_failure); + } + + /* file outputs: deferred until inputs are validated */ + res = process_args_loop(c, &c->w_nmsg, add_file_output); + if (res != nmsg_res_success) { + return (res); + } + res = process_args_loop(c, &c->w_pres, add_pres_output); + if (res != nmsg_res_success) { + return (res); + } + res = process_args_loop(c, &c->w_json, add_json_output); + if (res != nmsg_res_success) { + return (res); + } + if (c->n_outputs == 0) { /* implicit "-o -" */ - add_pres_output(c, "-"); + res = add_pres_output(c, "-"); + if (res != nmsg_res_success) { + return (res); + } } /* daemonize if necessary */ if (c->daemon) { if (!daemonize()) { - fprintf(stderr, "nmsgtool: unable to daemonize: %s\n", - strerror(errno)); - exit(EXIT_FAILURE); + fprintf(stderr, "%s: unable to daemonize: %s\n", argv_program, strerror(errno)); + return (nmsg_res_failure); } } /* write pidfile if necessary */ if (c->pidfile != NULL && fp_pidfile != NULL) pidfile_write(fp_pidfile); + + return (nmsg_res_success); }