From 6004df9dfd37722d1a3a746a371407b12af35d1d Mon Sep 17 00:00:00 2001 From: "Gerd v. Egidy" Date: Mon, 11 Jul 2022 20:05:11 +0200 Subject: [PATCH 1/3] Do proper shutdown upon SIGTERM, SIGINT or SIGQUIT Before this patch the process was just terminated by the default signal handler. This could lead for example to incompletely written pcap files. So proper shutdown like in this patch is always a good idea. But it becomes mandadory when implementing more complex file I/O with buffering, for example with gzip streaming. To not conflict with the threading used in sngrep, the signal handler just sets a atomic flag. This flag is then checked in one of the two main loops (with/without curses UI). --- src/curses/ui_manager.c | 3 +++ src/main.c | 4 +++- src/util.c | 37 +++++++++++++++++++++++++++++++++++++ src/util.h | 12 ++++++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/curses/ui_manager.c b/src/curses/ui_manager.c index e3bf62d9..3b07d826 100644 --- a/src/curses/ui_manager.c +++ b/src/curses/ui_manager.c @@ -192,6 +192,9 @@ ui_wait_for_input() // While there are still panels while ((panel = panel_below(NULL))) { + if (was_sigterm_received()) + return 0; + // Get panel interface structure ui = ui_find_by_panel(panel); diff --git a/src/main.c b/src/main.c index 921b9abd..0caad691 100644 --- a/src/main.c +++ b/src/main.c @@ -327,6 +327,8 @@ main(int argc, char* argv[]) } } + setup_sigterm_handler(); + #if defined(WITH_GNUTLS) || defined(WITH_OPENSSL) // Set capture decrypt key file capture_set_keyfile(keyfile); @@ -443,7 +445,7 @@ main(int argc, char* argv[]) ui_wait_for_input(); } else { setbuf(stdout, NULL); - while(capture_is_running()) { + while(capture_is_running() && !was_sigterm_received()) { if (!quiet) printf("\rDialog count: %d", sip_calls_count_unrotated()); usleep(500 * 1000); diff --git a/src/util.c b/src/util.c index 6a8ead3a..afa1a331 100644 --- a/src/util.c +++ b/src/util.c @@ -32,8 +32,45 @@ #include #include #include +#include #include "util.h" +#if __STDC_VERSION__ >= 201112L && __STDC_NO_ATOMICS__ != 1 +// modern C with atomics +#include +typedef atomic_int signal_flag_type; +#else +// no atomics available +typedef volatile sig_atomic_t signal_flag_type; +#endif + +static signal_flag_type sigterm_received = 0; + +static void sigterm_handler(int signum) +{ + sigterm_received = 1; +} + +void setup_sigterm_handler(void) +{ + // set up SIGTERM handler (also used for SIGINT and SIGQUIT) + // the handler will be served by any of the running threads + // so we just set a flag and check it in one of the two available + // main loops of the program (main for --no-interface and + // ui_wait_for_input() for curses) + + if (signal(SIGTERM, sigterm_handler) == SIG_ERR) + exit(EXIT_FAILURE); + if (signal(SIGINT, sigterm_handler) == SIG_ERR) + exit(EXIT_FAILURE); + if (signal(SIGQUIT, sigterm_handler) == SIG_ERR) + exit(EXIT_FAILURE); +} + +bool was_sigterm_received(void) +{ + return (sigterm_received == 1); +} void * sng_malloc(size_t size) diff --git a/src/util.h b/src/util.h index 4d6b9066..0b1f4a55 100644 --- a/src/util.h +++ b/src/util.h @@ -99,4 +99,16 @@ timeval_to_delta(struct timeval start, struct timeval end, char *out); char * strtrim(char *str); +/** + * @brief Set up handler for SIGTERM, SIGINT and SIGQUIT + */ +void setup_sigterm_handler(void); + +/** + * @brief Check if SIGTERM, SIGINT or SIGQUIT were received + * + * @return true if any of the exit signals were received + */ +bool was_sigterm_received(void); + #endif /* __SNGREP_UTIL_H */ From c02f439d73f33c727909131cb4d9d01029d7492f Mon Sep 17 00:00:00 2001 From: "Gerd v. Egidy" Date: Mon, 11 Jul 2022 20:40:03 +0200 Subject: [PATCH 2/3] implement autoconf/automake support for libz and fopencookie usage --- configure.ac | 25 +++++++++++++++++++++++++ src/Makefile.am | 5 +++++ 2 files changed, 30 insertions(+) diff --git a/configure.ac b/configure.ac index e66fd9e2..d9505874 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,9 @@ AC_PROG_EGREP AC_LANG(C) AM_PROG_CC_C_O +# we might want to use this with zlib for compressed pcap support +AC_CHECK_FUNCS([fopencookie]) + ####################################################################### # Check for other REQUIRED libraries AC_CHECK_LIB([pthread], [pthread_create], [], [ @@ -255,12 +258,33 @@ AS_IF([test "x$USE_EEP" = "xyes"], [ AC_DEFINE([USE_EEP],[],[Compile With EEP support]) ], []) +#### +#### zlib Support +#### +AC_ARG_WITH([zlib], + AS_HELP_STRING([--with-zlib], [Enable zlib to support gzip compressed pcap files]), + [AC_SUBST(WITH_ZLIB, $withval)], + [AC_SUBST(WITH_ZLIB, no)] +) + +AS_IF([test "x$WITH_ZLIB" = "xyes"], [ + AC_CHECK_HEADER([zlib.h], [], [ + AC_MSG_ERROR([ You need libz header files installed to compile with zlib support.]) + ]) + AC_CHECK_LIB([z], [gzdopen], [], [ + AC_MSG_ERROR([ You need libz library installed to compile with zlib support.]) + ]) + AC_DEFINE([WITH_ZLIB],[],[Compile With zlib support]) +], []) + + # Conditional Source inclusion AM_CONDITIONAL([WITH_PCRE2], [test "x$WITH_PCRE2" = "xyes"]) AM_CONDITIONAL([WITH_GNUTLS], [test "x$WITH_GNUTLS" = "xyes"]) AM_CONDITIONAL([WITH_OPENSSL], [test "x$WITH_OPENSSL" = "xyes"]) AM_CONDITIONAL([USE_EEP], [test "x$USE_EEP" = "xyes"]) +AM_CONDITIONAL([WITH_ZLIB], [test "x$WITH_ZLIB" = "xyes"]) ###################################################################### @@ -291,6 +315,7 @@ AC_MSG_NOTICE( Perl Expressions Support : ${WITH_PCRE} ) AC_MSG_NOTICE( Perl Expressions Support (v2): ${WITH_PCRE2} ) AC_MSG_NOTICE( IPv6 Support : ${USE_IPV6} ) AC_MSG_NOTICE( EEP Support : ${USE_EEP} ) +AC_MSG_NOTICE( Zlib Support : ${WITH_ZLIB} ) AC_MSG_NOTICE( ====================================================== ) AC_MSG_NOTICE diff --git a/src/Makefile.am b/src/Makefile.am index c6d7a1dd..309c707b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,6 +20,11 @@ if WITH_PCRE2 sngrep_CFLAGS+=$(PCRE2_CFLAGS) sngrep_LDADD+=$(PCRE2_LIBS) endif +if WITH_ZLIB +sngrep_CFLAGS+=$(ZLIB_CFLAGS) +sngrep_LDADD+=$(ZLIB_LIBS) +endif + sngrep_SOURCES+=address.c packet.c sip.c sip_call.c sip_msg.c sip_attr.c main.c sngrep_SOURCES+=option.c group.c filter.c keybinding.c media.c setting.c rtp.c sngrep_SOURCES+=util.c hash.c vector.c curses/ui_panel.c curses/scrollbar.c From 58b7601d17dfdb60d9dfab20223340e36028f0b0 Mon Sep 17 00:00:00 2001 From: "Gerd v. Egidy" Date: Mon, 11 Jul 2022 21:24:56 +0200 Subject: [PATCH 3/3] Implement reading & writing of gzip compressed pcap files libpcap doesn't directly support this, so this is implemented using the Linux call fopencookie which rereoutes the read,write,seek,close functions. *BSD seems to have something similar (funopen) which is not implemented in this patch because I'm not familiar enough with BSD. gzip detection for read is done by first directly opening the given file like before. If this fails, we retry with gzip. gzip detection for write is done by looking at the filename to write to. If it ends in ".gz" we activate gzip compression. This currently just works for the commandline option --output because only there you get to set the filename suffix freely. To make this usable in the curses gui, the save dialog would have to be extended to allow setting a .pcap.gz filename extension. gzip compression must be compiled in to be active. This is done with the --with-zlib configure option. --- README | 2 ++ src/capture.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/README b/README index bcc31b56..38e9460d 100644 --- a/README +++ b/README @@ -27,6 +27,7 @@ Prerequisites - gnutls - (optional) for TLS transport decrypt using GnuTLS and libgcrypt - libncursesw5 - (optional) for UI, windows, panels (wide-character support) - libpcre - (optional) for Perl Compatible regular expressions + - zlib - (optional) for gzip compressed pcap files On most systems the commands to build will be the standard autotools procedure: @@ -42,6 +43,7 @@ You can pass following flags to ./configure to enable some features | `--with-openssl` | Adds OpenSSL support to parse TLS captured messages (req. libssl) | | `--with-gnutls` | Adds GnuTLS support to parse TLS captured messages (req. gnutls) | | `--with-pcre`| Adds Perl Compatible regular expressions support in regexp fields | +| `--with-zlib`| Enable zlib to support gzip compressed pcap files | | `--enable-unicode` | Adds Ncurses UTF-8/Unicode support (req. libncursesw5) | | `--enable-ipv6` | Enable IPv6 packet capture support. | | `--enable-eep` | Enable EEP packet send/receive support. | diff --git a/src/capture.c b/src/capture.c index 65488725..825ee126 100644 --- a/src/capture.c +++ b/src/capture.c @@ -46,6 +46,9 @@ #ifdef WITH_OPENSSL #include "capture_openssl.h" #endif +#ifdef WITH_ZLIB +#include +#endif #include "sip.h" #include "rtp.h" #include "setting.h" @@ -71,6 +74,26 @@ void sighup_handler(int signum) sighup_received = 1; } +#if defined(WITH_ZLIB) +static ssize_t +gzip_cookie_write(void *cookie, const char *buf, size_t size) +{ + return gzwrite((gzFile)cookie, (voidpc)buf, size); +} + +static ssize_t +gzip_cookie_read(void *cookie, char *buf, size_t size) +{ + return gzread((gzFile)cookie, (voidp)buf, size); +} + +static int +gzip_cookie_close(void *cookie) +{ + return gzclose((gzFile)cookie); +} +#endif + void capture_init(size_t limit, bool rtp_capture, bool rotate, size_t pcap_buffer_size) { @@ -235,9 +258,36 @@ capture_offline(const char *infile) // Open PCAP file if ((capinfo->handle = pcap_open_offline(infile, errbuf)) == NULL) { +#if defined(HAVE_FOPENCOOKIE) && defined(WITH_ZLIB) + // we can't directly parse the file as pcap - could it be gzip compressed? + gzFile zf = gzopen(infile, "rb"); + if (!zf) + goto openerror; + + static cookie_io_functions_t cookiefuncs = { + gzip_cookie_read, NULL, NULL, gzip_cookie_close + }; + + // reroute the file access functions + // use the gzip read+close functions when accessing the file + FILE *fp = fopencookie(zf, "r", cookiefuncs); + if (!fp) + { + gzclose(zf); + goto openerror; + } + + if ((capinfo->handle = pcap_fopen_offline(fp, errbuf)) == NULL) { +openerror: + fprintf(stderr, "Couldn't open pcap file %s: %s\n", infile, errbuf); + return 1; + } + } +#else fprintf(stderr, "Couldn't open pcap file %s: %s\n", infile, errbuf); return 1; } +#endif // Reopen tty for ncurses after pcap have used stdin if (!strncmp(infile, "/dev/stdin", 10)) { @@ -1310,6 +1360,17 @@ datalink_size(int datalink) } +bool +is_gz_filename(const char *filename) +{ + // does the filename end on ".gz"? + char *dotpos = strrchr(filename, '.'); + if (dotpos && (strcmp(dotpos, ".gz") == 0)) + return true; + else + return false; +} + pcap_dumper_t * dump_open(const char *dumpfile, ino_t* dump_inode) { @@ -1335,6 +1396,30 @@ dump_open(const char *dumpfile, ino_t* dump_inode) *dump_inode = sb.st_ino; } + if (is_gz_filename(dumpfile)) + { +#if defined(HAVE_FOPENCOOKIE) && defined(WITH_ZLIB) + // create a gzip file stream out of the already opened file + gzFile zf = gzdopen(fileno(fp), "w"); + if (!zf) + return NULL; + + static cookie_io_functions_t cookiefuncs = { + NULL, gzip_cookie_write, NULL, gzip_cookie_close + }; + + // reroute the file access functions + // use the gzip write+close functions when accessing the file + fp = fopencookie(zf, "w", cookiefuncs); + if (!fp) + return NULL; +#else + // no support for gzip compressed pcap files compiled in -> abort + fclose(fp); + return NULL; +#endif + } + return pcap_dump_fopen(capinfo->handle, fp); } return NULL;