From b430b4bb2ec1fe6fc06e22a8831119bffee0b838 Mon Sep 17 00:00:00 2001 From: Soner Tari Date: Thu, 13 Sep 2018 13:17:26 +0300 Subject: [PATCH] Add pcap and mirror logging - Do not clear libnet arp request packet, we send the same packet on each iteration of the loop, so no need for a separate function to send an arp request - Fix ether type of ipv6 packets, needs testing - Refactor for better log messages, remove redundant vars, free vars, and use static vars and functions amap - Fix var free and destroy on exit - Refactor for var init and names - Use correct print functions - Increase mirror target loop count and pcap_handler packet processing count for busy networks - Use libpcap to recv arp replies instead of socket(2), because socket(2) cannot recv any arp packets on OpenBSD, and fails sometimes on Linux too - Get mirror target ethernet address and check if reply is from target, so we know target is up - Exit failing if mirror target is down - Refactor arp code, init libnet (and pcap) only once - Remove new lines in libnet error messages, libnet already appends new lines - Move all pcap and mirror logging code into logpkt.c/h, use logpkt_ prefix now - Fix values of nh and hl params of libnet_build_ipv6(), needs testing - Add libnet ipv6 support to pcap logging, not tested with ipv6 yet, but does not break ipv4 support, needs review and testing - Keep the privsep socket open for pcap logging, now due to support for separate files per connection - Refactor for the new options - Add -Y and -y options for pcap logging to separate files, similar to -S and -F options, update conffile and its man page accordingly - Fix documentation - Remove redundant content log flags - Handle error conditions while creating new lbs - Prevent possible memory leak: create min number of lbs based on enabled content loggers - Create opts_set_* functions and conffile options for pcap and mirror logging - Separate content loggers, so they don't exclude each other now - Prevent running as root by moving libnet_init() to somewhere before privsep fork - Fix compiler warnings for type mismatches, etc. - Fix memory leak introduced in logbuf_write_free() by freeing buf - Apply coding style and clean up - Fix excessive fragmentation in HTTP packets in pcap logs by calling write callback only once with a new buffer combining all accumulated log buffers in the linked list Have to remove NONNULL(1) from logbuf_write_free declaration now, because we check lb for NULL in while condition - Replace recursion in logbuf_write_free() with while loop - Add libnet autodetection, temporary fix, needs review - Add OPENBSD directive to fix OpenBSD specific differences, temporary fix, needs review - Fix memory leaks - Fix build warnings - Add license headers - Improve log messages - Fix coding style - Clean-up - Merge remote-tracking branch 'cihankom/master' # Conflicts: # main.c # opts.h --- GNUmakefile | 71 ++++- log.c | 798 ++++++++++++++++++++++++++++++++++++++++++------ log.h | 1 + logbuf.c | 59 ++-- logbuf.h | 4 +- logpkt.c | 476 +++++++++++++++++++++++++++++ logpkt.h | 100 ++++++ main.c | 41 ++- opts.c | 139 +++++++-- opts.h | 15 + privsep.c | 34 ++- privsep.h | 2 +- pxyconn.c | 2 +- sslsplit.conf | 23 +- sslsplit.conf.5 | 17 +- sys.c | 16 + sys.h | 1 + 17 files changed, 1652 insertions(+), 147 deletions(-) create mode 100644 logpkt.c create mode 100644 logpkt.h diff --git a/GNUmakefile b/GNUmakefile index 5de1ef62..35a618e7 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -7,6 +7,7 @@ # # OPENSSL_BASE Prefix of OpenSSL library and headers to build against # LIBEVENT_BASE Prefix of libevent library and headers to build against +# LIBNET_BASE Prefix of libnet library and headers to build against # CHECK_BASE Prefix of check library and headers to build against (optional) # PKGCONFIG Name/path of pkg-config program to use for auto-detection # PCFLAGS Additional pkg-config flags @@ -141,7 +142,11 @@ ifneq ($(wildcard /usr/include/linux/netfilter.h),) FEATURES+= -DHAVE_NETFILTER endif - +# Autodetect OpenBSD +ifeq ($(shell uname),OpenBSD) +FEATURES+= -DOPENBSD +endif + ### Variables you might need to override PREFIX?= /usr/local @@ -225,6 +230,14 @@ PKGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --exists libevent_openssl \ PKGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --exists libevent_pthreads \ && echo libevent_pthreads) endif +ifndef LIBNET_BASE +PKGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --exists libnet \ + && echo libnet) +endif +ifndef LIBPCAP_BASE +PKGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --exists libpcap \ + && echo libpcap) +endif TPKGS:= ifndef CHECK_BASE TPKGS+= $(shell $(PKGCONFIG) $(PCFLAGS) --exists check \ @@ -269,6 +282,46 @@ $(error dependency 'libevent 2.x' not found; \ install it or point LIBEVENT_BASE to base path) endif endif +ifeq (,$(filter libnet,$(PKGS))) +# Linux /usr/include/libnet.h +# OpenBSD /usr/local/include/libnet-1.1/libnet.h +# XXX? +LIBNET_PAT:= libnet.h +ifdef LIBNET_BASE +LIBNET_FIND:= $(wildcard $(LIBNET_BASE)/$(LIBNET_PAT)) +else +LIBNET_FIND:= $(wildcard \ + /usr/local/opt/libnet/$(LIBNET_PAT) \ + /opt/local/include/$(LIBNET_PAT) \ + /usr/local/include/$(LIBNET_PAT) \ + /usr/local/include/libnet-1.1/$(LIBNET_PAT) \ + /usr/include/$(LIBNET_PAT)) +endif +LIBNET_AVAIL:= $(LIBNET_FIND:/$(LIBNET_PAT)=) +LIBNET_FOUND:= $(word 1,$(LIBNET_AVAIL)) +ifndef LIBNET_FOUND +$(error dependency 'libnet' not found; \ + install it or point LIBNET_BASE to base path) +endif +endif +ifeq (,$(filter libpcap,$(PKGS))) +LIBPCAP_PAT:= include/pcap.h +ifdef LIBPCAP_BASE +LIBPCAP_FIND:= $(wildcard $(LIBPCAP_BASE)/$(LIBPCAP_PAT)) +else +LIBPCAP_FIND:= $(wildcard \ + /usr/local/opt/libpcap/$(LIBPCAP_PAT) \ + /opt/local/$(LIBPCAP_PAT) \ + /usr/local/$(LIBPCAP_PAT) \ + /usr/$(LIBPCAP_PAT)) +endif +LIBPCAP_AVAIL:=$(LIBPCAP_FIND:/$(LIBPCAP_PAT)=) +LIBPCAP_FOUND:=$(word 1,$(LIBPCAP_AVAIL)) +ifndef LIBPCAP_FOUND +$(error dependency 'libpcap' not found; \ + install it or point LIBPCAP_BASE to base path) +endif +endif ifeq (,$(filter check,$(TPKGS))) CHECK_PAT:= include/check.h ifdef CHECK_BASE @@ -303,6 +356,19 @@ endif ifeq (,$(filter libevent_pthreads,$(PKGS))) PKG_LIBS+= -levent_pthreads endif +ifdef LIBNET_FOUND +# XXX? +LIBNET_INC_BASE:= $(LIBNET_FOUND:/libnet-1.1=) +PKG_CPPFLAGS+= -I$(LIBNET_INC_BASE) +LIBNET_LIB_BASE:= $(LIBNET_INC_BASE:/include=) +PKG_LDFLAGS+= -L$(LIBNET_LIB_BASE)/lib +PKG_LIBS+= -lnet +endif +ifdef LIBPCAP_FOUND +PKG_CPPFLAGS+= -I$(LIBPCAP_FOUND)/include +PKG_LDFLAGS+= -L$(LIBPCAP_FOUND)/lib +PKG_LIBS+= -lpcap +endif ifdef CHECK_FOUND TPKG_CPPFLAGS+= -I$(CHECK_FOUND)/include TPKG_LDFLAGS+= -L$(CHECK_FOUND)/lib @@ -377,6 +443,9 @@ endif ifdef LIBEVENT_FOUND $(info LIBEVENT_BASE: $(strip $(LIBEVENT_FOUND))) endif +ifdef LIBNET_FOUND +$(info LIBNET_BASE: $(strip $(LIBNET_FOUND))) +endif ifdef CHECK_FOUND $(info CHECK_BASE: $(strip $(CHECK_FOUND))) endif diff --git a/log.c b/log.c index d231e8e4..0d3162e4 100644 --- a/log.c +++ b/log.c @@ -331,8 +331,12 @@ log_connect_fini(void) #define PREPFLAG_REQUEST 1 #define PREPFLAG_EOF 2 +// TODO: Each *_closecb() function runs on its own thread and decrements the open field? Is this going to cause multithreading issues? +// Same logctx instance may be used by multiple loggers running on separate threads struct log_content_ctx { - unsigned int open : 1; + // keeps the ref count of logctx + unsigned int open; + unsigned int is_request; union { struct { char *header_req; @@ -347,10 +351,30 @@ struct log_content_ctx { char *filename; } spec; } u; + struct { + union { + struct { + int fd; + char *filename; + } dir; + struct { + int fd; + char *filename; + } spec; + } u; + pcap_packet_t *request; + pcap_packet_t *response; + } pcap; + struct { + pcap_packet_t *request; + pcap_packet_t *response; + } mirror; }; -static logger_t *content_log = NULL; static int content_clisock = -1; /* privsep client socket for content logger */ +static logger_t *content_log = NULL; +static logger_t *pcap_log = NULL; +static logger_t *mirror_log = NULL; /* * Split a pathname into static LHS (including final slashes) and dynamic RHS. @@ -580,28 +604,31 @@ log_content_open(log_content_ctx_t **pctx, opts_t *opts, return -1; ctx = *pctx; - if (opts->contentlog_isdir) { - /* per-connection-file content log (-S) */ - char timebuf[24]; - time_t epoch; - struct tm *utc; - char *dsthost_clean, *srchost_clean; - - if (time(&epoch) == -1) { - log_err_printf("Failed to get time\n"); - goto errout; - } - if ((utc = gmtime(&epoch)) == NULL) { - log_err_printf("Failed to convert time: %s (%i)\n", - strerror(errno), errno); - goto errout; - } - if (!strftime(timebuf, sizeof(timebuf), - "%Y%m%dT%H%M%SZ", utc)) { - log_err_printf("Failed to format time: %s (%i)\n", - strerror(errno), errno); - goto errout; + char timebuf[24]; + time_t epoch; + struct tm *utc; + char *dsthost_clean = NULL, *srchost_clean = NULL; + int is_spec_or_dir = opts->contentlog_isdir || opts->pcaplog_isdir || opts->contentlog_isspec || opts->pcaplog_isspec; + + if (is_spec_or_dir) { + if (opts->contentlog_isdir || opts->pcaplog_isdir) { + if (time(&epoch) == -1) { + log_err_printf("Failed to get time\n"); + goto errout; + } + if ((utc = gmtime(&epoch)) == NULL) { + log_err_printf("Failed to convert time: %s (%i)\n", + strerror(errno), errno); + goto errout; + } + if (!strftime(timebuf, sizeof(timebuf), + "%Y%m%dT%H%M%SZ", utc)) { + log_err_printf("Failed to format time: %s (%i)\n", + strerror(errno), errno); + goto errout; + } } + srchost_clean = sys_ip46str_sanitize(srchost); if (!srchost_clean) { log_err_printf("Failed to sanitize srchost\n"); @@ -613,60 +640,130 @@ log_content_open(log_content_ctx_t **pctx, opts_t *opts, free(srchost_clean); goto errout; } - if (asprintf(&ctx->u.dir.filename, "%s/%s-%s,%s-%s,%s.log", - opts->contentlog, timebuf, - srchost_clean, srcport, - dsthost_clean, dstport) < 0) { - log_err_printf("Failed to format filename: %s (%i)\n", - strerror(errno), errno); - free(srchost_clean); - free(dsthost_clean); - goto errout; - } - free(srchost_clean); - free(dsthost_clean); - } else if (opts->contentlog_isspec) { - /* per-connection-file content log with logspec (-F) */ - char *dsthost_clean, *srchost_clean; - srchost_clean = sys_ip46str_sanitize(srchost); - if (!srchost_clean) { - log_err_printf("Failed to sanitize srchost\n"); - goto errout; + } + + if (opts->contentlog) { + if (opts->contentlog_isdir) { + /* per-connection-file content log (-S) */ + if (asprintf(&ctx->u.dir.filename, "%s/%s-%s,%s-%s,%s.log", + opts->contentlog, timebuf, + srchost_clean, srcport, + dsthost_clean, dstport) < 0) { + log_err_printf("Failed to format filename: %s (%i)\n", + strerror(errno), errno); + goto errout2; + } + } else if (opts->contentlog_isspec) { + /* per-connection-file content log with logspec (-F) */ + ctx->u.spec.filename = log_content_format_pathspec( + opts->contentlog, + srchost_clean, srcport, + dsthost_clean, dstport, + exec_path, user, group); + if (!ctx->u.spec.filename) { + goto errout2; + } + } else { + /* single-file content log (-L) */ + if (asprintf(&ctx->u.file.header_req, "[%s]:%s -> [%s]:%s", + srchost, srcport, dsthost, dstport) < 0) { + goto errout; + } + if (asprintf(&ctx->u.file.header_resp, "[%s]:%s -> [%s]:%s", + dsthost, dstport, srchost, srcport) < 0) { + free(ctx->u.file.header_req); + goto errout; + } } - dsthost_clean = sys_ip46str_sanitize(dsthost); - if (!dsthost_clean) { - log_err_printf("Failed to sanitize dsthost\n"); - free(srchost_clean); - goto errout; + + /* submit an open event */ + if (logger_open(content_log, ctx) == -1) + goto errout2; + ctx->open++; + } + + if (opts->pcaplog) { + // TODO: Check init + unsigned char dst_ether[ETHER_ADDR_LEN] = {0x2B, 0xDE, 0x7C, 0x01, 0x7C, 0xA9}; + + ctx->pcap.request = malloc(sizeof(pcap_packet_t)); + ctx->pcap.response = malloc(sizeof(pcap_packet_t)); + + memcpy(ctx->pcap.request->dst_ether, dst_ether, ETHER_ADDR_LEN); + memcpy(ctx->pcap.response->dst_ether, dst_ether, ETHER_ADDR_LEN); + + if (!ctx->pcap.request || !ctx->pcap.response) { + free(ctx->pcap.request); + free(ctx->pcap.response); + goto errout2; } - ctx->u.spec.filename = log_content_format_pathspec( - opts->contentlog, - srchost_clean, srcport, - dsthost_clean, dstport, - exec_path, user, group); - free(srchost_clean); - free(dsthost_clean); - if (!ctx->u.spec.filename) { - goto errout; + if (logpkt_set_packet_fields(libnet_pcap, ctx->pcap.request, srchost, srcport, dsthost, dstport) == -1) + goto errout2; + if (logpkt_set_packet_fields(libnet_pcap, ctx->pcap.response, dsthost, dstport, srchost, srcport) == -1) + goto errout2; + + if (opts->pcaplog_isdir) { + /* per-connection-file pcap log (-Y) */ + if (asprintf(&ctx->pcap.u.dir.filename, "%s/%s-%s,%s-%s,%s.pcap", + opts->pcaplog, timebuf, + srchost_clean, srcport, + dsthost_clean, dstport) < 0) { + log_err_printf("Failed to format filename: %s (%i)\n", + strerror(errno), errno); + goto errout2; + } + } else if (opts->pcaplog_isspec) { + /* per-connection-file pcap log with logspec (-y) */ + ctx->pcap.u.spec.filename = log_content_format_pathspec( + opts->pcaplog, + srchost_clean, srcport, + dsthost_clean, dstport, + exec_path, user, group); + if (!ctx->pcap.u.spec.filename) { + goto errout2; + } } - } else { - /* single-file content log (-L) */ - if (asprintf(&ctx->u.file.header_req, "[%s]:%s -> [%s]:%s", - srchost, srcport, dsthost, dstport) < 0) { + + /* submit an open event */ + if (logger_open(pcap_log, ctx) == -1) + goto errout2; + ctx->open++; + } + + if (opts->mirrorif) { + ctx->mirror.request = malloc(sizeof(pcap_packet_t)); + ctx->mirror.response = malloc(sizeof(pcap_packet_t)); + + memcpy(ctx->mirror.request->dst_ether, opts->mirrortarget_ether, ETHER_ADDR_LEN); + memcpy(ctx->mirror.response->dst_ether, opts->mirrortarget_ether, ETHER_ADDR_LEN); + + if (!ctx->mirror.request || !ctx->mirror.response) { + free(ctx->mirror.request); + free(ctx->mirror.response); goto errout; } - if (asprintf(&ctx->u.file.header_resp, "[%s]:%s -> [%s]:%s", - dsthost, dstport, srchost, srcport) < 0) { - free(ctx->u.file.header_req); + if (logpkt_set_packet_fields(libnet_mirror, ctx->mirror.request, srchost, srcport, dsthost, dstport) == -1) + goto errout2; + if (logpkt_set_packet_fields(libnet_mirror, ctx->mirror.response, dsthost, dstport, srchost, srcport) == -1) + goto errout2; + + /* submit an open event */ + if (logger_open(mirror_log, ctx) == -1) goto errout; - } + ctx->open++; } - /* submit an open event */ - if (logger_open(content_log, ctx) == -1) - goto errout; - ctx->open = 1; + if (is_spec_or_dir) { + free(srchost_clean); + free(dsthost_clean); + } return 0; + +errout2: + if (is_spec_or_dir) { + free(srchost_clean); + free(dsthost_clean); + } errout: free(ctx); *pctx = NULL; @@ -685,7 +782,51 @@ log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int is_request) if (is_request) prepflags |= PREPFLAG_REQUEST; - return logger_submit(content_log, ctx, prepflags, lb); + + // At least one content logger must be on at this point + logbuf_t *lbpcap = lb; + logbuf_t *lbmirror = lb; + if (content_log) { + if (pcap_log) { + lbpcap = logbuf_new_rcopy(lb); + if (!lbpcap) { + goto out; + } + } + if (mirror_log) { + lbmirror = logbuf_new_rcopy(lb); + if (!lbmirror) { + if (pcap_log) { + logbuf_free(lbpcap); + } + goto out; + } + } + } else if (pcap_log) { + lbmirror = logbuf_new_rcopy(lb); + if (!lbmirror) { + goto out; + } + } + + if (content_log) { + if (logger_submit(content_log, ctx, prepflags, lb) == -1) { + goto out; + } + } + if (pcap_log) { + if (logger_submit(pcap_log, ctx, prepflags, lbpcap) == -1) { + goto out; + } + } + if (mirror_log) { + if (logger_submit(mirror_log, ctx, prepflags, lbmirror) == -1) { + goto out; + } + } + return 0; +out: + return -1; } int @@ -698,12 +839,32 @@ log_content_close(log_content_ctx_t **pctx, int by_requestor) return -1; if (by_requestor) prepflags |= PREPFLAG_REQUEST; - if (logger_submit(content_log, (*pctx), prepflags, NULL) == -1) { - rv = -1; - goto out; + if (content_log) { + if (logger_submit(content_log, (*pctx), prepflags, NULL) == -1) { + rv = -1; + goto out; + } + if (logger_close(content_log, *pctx) == -1) { + rv = -1; + } } - if (logger_close(content_log, *pctx) == -1) { - rv = -1; + if (pcap_log) { + if (logger_submit(pcap_log, (*pctx), prepflags, NULL) == -1) { + rv = -1; + goto out; + } + if (logger_close(pcap_log, *pctx) == -1) { + rv = -1; + } + } + if (mirror_log) { + if (logger_submit(mirror_log, (*pctx), prepflags, NULL) == -1) { + rv = -1; + goto out; + } + if (logger_close(mirror_log, *pctx) == -1) { + rv = -1; + } } out: *pctx = NULL; @@ -724,7 +885,7 @@ log_content_dir_opencb(void *fh) if ((ctx->u.dir.fd = privsep_client_openfile(content_clisock, ctx->u.dir.filename, - 0)) == -1) { + 0, 0)) == -1) { log_err_printf("Opening logdir file '%s' failed: %s (%i)\n", ctx->u.dir.filename, strerror(errno), errno); return -1; @@ -741,7 +902,8 @@ log_content_dir_closecb(void *fh) free(ctx->u.dir.filename); if (ctx->u.dir.fd != 1) close(ctx->u.dir.fd); - free(ctx); + if (--ctx->open == 0) + free(ctx); } static ssize_t @@ -764,7 +926,7 @@ log_content_spec_opencb(void *fh) if ((ctx->u.spec.fd = privsep_client_openfile(content_clisock, ctx->u.spec.filename, - 1)) == -1) { + 1, 0)) == -1) { log_err_printf("Opening logspec file '%s' failed: %s (%i)\n", ctx->u.spec.filename, strerror(errno), errno); return -1; @@ -781,7 +943,8 @@ log_content_spec_closecb(void *fh) free(ctx->u.spec.filename); if (ctx->u.spec.fd != -1) close(ctx->u.spec.fd); - free(ctx); + if (--ctx->open == 0) + free(ctx); } static ssize_t @@ -814,7 +977,7 @@ log_content_file_preinit(const char *logfile) log_err_printf("Failed to realpath '%s': %s (%i)\n", logfile, strerror(errno), errno); close(content_file_fd); - connect_fd = -1; + content_file_fd = -1; return -1; } return 0; @@ -847,14 +1010,6 @@ log_content_file_reopencb(void) return 0; } -/* -static int -log_content_file_opencb(void *fh) -{ - return 0; -} -*/ - static void log_content_file_closecb(void *fh) { @@ -866,8 +1021,9 @@ log_content_file_closecb(void *fh) if (ctx->u.file.header_resp) { free(ctx->u.file.header_resp); } - - free(ctx); + if (--ctx->open == 0) { + free(ctx); + } } static ssize_t @@ -937,6 +1093,368 @@ log_content_file_prepcb(void *fh, unsigned long prepflags, logbuf_t *lb) return lb; } +/* + * Pcap writer for -X/-Y/-y options. + */ +static int content_pcap_fd = -1; +static char *content_pcap_fn = NULL; + +static int +log_pcap_preinit(const char *pcapfile) { + unlink(pcapfile); + content_pcap_fd = open(pcapfile, O_WRONLY | O_APPEND | O_CREAT, + DFLT_FILEMODE); + if (content_pcap_fd == -1) { + log_err_printf("Failed to open '%s' for writing: %s (%i)\n", + pcapfile, strerror(errno), errno); + return -1; + } + + if (logpkt_write_global_pcap_hdr(content_pcap_fd) == -1) { + close(content_pcap_fd); + content_pcap_fd = -1; + return -1; + } + + if (!(content_pcap_fn = realpath(pcapfile, NULL))) { + log_err_printf("Failed to realpath '%s': %s (%i)\n", + pcapfile, strerror(errno), errno); + close(content_pcap_fd); + content_pcap_fd = -1; + return -1; + } + return 0; +} + +static void +log_content_pcap_fini(void) +{ + if (content_pcap_fn) { + free(content_pcap_fn); + content_pcap_fn = NULL; + } + if (content_pcap_fd != -1) { + close(content_pcap_fd); + content_pcap_fd = -1; + } + if (libnet_pcap) { + libnet_destroy(libnet_pcap); + } +} + +static int +log_pcap_reopencb(void) { + close(content_pcap_fd); + content_pcap_fd = open(content_pcap_fn, + O_WRONLY | O_APPEND | O_CREAT, DFLT_FILEMODE); + if (content_pcap_fd == -1) { + log_err_printf("Failed to open '%s' for writing: %s (%i)\n", + content_pcap_fn, strerror(errno), errno); + return -1; + } + return 0; +} + +static void +log_pcap_closecb_base(void *fh, int fd) { + log_content_ctx_t *ctx = fh; + + if (ctx->pcap.request->seq > 0 && ctx->pcap.request->ack > 0) { + if (logpkt_write_packet(libnet_pcap, fd, ctx->pcap.request, TH_FIN | TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + } + ctx->pcap.response->ack += 1; + if (logpkt_write_packet(libnet_pcap, fd, ctx->pcap.response, TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + } + if (logpkt_write_packet(libnet_pcap, fd, ctx->pcap.response, TH_FIN | TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + } + ctx->pcap.request->ack += 1; + ctx->pcap.request->seq += 1; + + if (logpkt_write_packet(libnet_pcap, fd, ctx->pcap.request, TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + } + } + + if (ctx->pcap.request) { + free(ctx->pcap.request); + } + if (ctx->pcap.response) { + free(ctx->pcap.response); + } +} + +static void +log_pcap_closecb(void *fh) { + log_content_ctx_t *ctx = fh; + + log_pcap_closecb_base(fh, content_pcap_fd); + + if (--ctx->open == 0) { + free(ctx); + } +} + +static ssize_t +log_pcap_writecb_base(void *fh, const void *buf, size_t sz, int fd) { + log_content_ctx_t *ctx = fh; + char flags = TH_PUSH | TH_ACK; + pcap_packet_t * from = ctx->is_request ? ctx->pcap.request : ctx->pcap.response; + pcap_packet_t * to = ctx->is_request ? ctx->pcap.response : ctx->pcap.request; + + if (ctx->is_request) { + if (ctx->pcap.request->seq == 0) { + if (logpkt_write_packet(libnet_pcap, fd, ctx->pcap.request, TH_SYN, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + return -1; + } + + ctx->pcap.response->ack = ctx->pcap.request->seq + 1; + + if (logpkt_write_packet(libnet_pcap, fd, ctx->pcap.response, TH_SYN | TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + return -1; + } + + ctx->pcap.request->ack = ctx->pcap.response->seq + 1; + ctx->pcap.request->seq += 1; + if (logpkt_write_packet(libnet_pcap, fd, ctx->pcap.request, TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + return -1; + } + + ctx->pcap.response->seq += 1; + } + } + + if (logpkt_write_payload(libnet_pcap, fd, from, to, flags, buf, sz) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + return -1; + } + + return sz; +} + +static ssize_t +log_pcap_writecb(void *fh, const void *buf, size_t sz) { + return log_pcap_writecb_base(fh, buf, sz, content_pcap_fd); +} + +static int +log_pcap_dir_opencb(void *fh) +{ + log_content_ctx_t *ctx = fh; + + if ((ctx->pcap.u.dir.fd = privsep_client_openfile(content_clisock, + ctx->pcap.u.dir.filename, + 0, 1)) == -1) { + log_err_printf("Opening pcapdir file '%s' failed: %s (%i)\n", + ctx->pcap.u.dir.filename, strerror(errno), errno); + return -1; + } + return logpkt_write_global_pcap_hdr(ctx->pcap.u.dir.fd); +} + +static void +log_pcap_dir_closecb(void *fh) +{ + log_content_ctx_t *ctx = fh; + + log_pcap_closecb_base(fh, ctx->pcap.u.dir.fd); + + if (ctx->pcap.u.dir.filename) + free(ctx->pcap.u.dir.filename); + if (ctx->pcap.u.dir.fd != 1) + close(ctx->pcap.u.dir.fd); + if (--ctx->open == 0) + free(ctx); +} + +static ssize_t +log_pcap_dir_writecb(void *fh, const void *buf, size_t sz) +{ + log_content_ctx_t *ctx = fh; + return log_pcap_writecb_base(fh, buf, sz, ctx->pcap.u.dir.fd); +} + +static int +log_pcap_spec_opencb(void *fh) +{ + log_content_ctx_t *ctx = fh; + + if ((ctx->pcap.u.spec.fd = privsep_client_openfile(content_clisock, + ctx->pcap.u.spec.filename, + 1, 1)) == -1) { + log_err_printf("Opening pcapspec file '%s' failed: %s (%i)\n", + ctx->pcap.u.spec.filename, strerror(errno), errno); + return -1; + } + return logpkt_write_global_pcap_hdr(ctx->pcap.u.spec.fd); +} + +static void +log_pcap_spec_closecb(void *fh) +{ + log_content_ctx_t *ctx = fh; + + log_pcap_closecb_base(fh, ctx->pcap.u.spec.fd); + + if (ctx->pcap.u.spec.filename) + free(ctx->pcap.u.spec.filename); + if (ctx->pcap.u.spec.fd != -1) + close(ctx->pcap.u.spec.fd); + if (--ctx->open == 0) + free(ctx); +} + +static ssize_t +log_pcap_spec_writecb(void *fh, const void *buf, size_t sz) +{ + log_content_ctx_t *ctx = fh; + return log_pcap_writecb_base(fh, buf, sz, ctx->pcap.u.spec.fd); +} + +/* + * Mirror writer for -T/-I options. + */ +static char *content_ifname = NULL; + +static int +log_mirror_preinit(const char *ifname) { + char errbuf[LIBNET_ERRBUF_SIZE]; + + content_ifname = strdup(ifname); + + libnet_mirror = libnet_init(LIBNET_LINK, content_ifname, errbuf); + if (libnet_mirror == NULL) { + log_err_printf("Failed to init mirror libnet: %s", errbuf); + free(content_ifname); + content_ifname = NULL; + return -1; + } + + mirrorsender_ether = libnet_get_hwaddr(libnet_mirror); + if (mirrorsender_ether == NULL) { + log_err_printf("Failed to get our own ethernet address: %s", libnet_geterror(libnet_mirror)); + free(content_ifname); + content_ifname = NULL; + return -1; + } + + libnet_seed_prand(libnet_mirror); + return 0; +} + +static void +log_content_mirror_fini(void) +{ + if (content_ifname) { + free(content_ifname); + content_ifname = NULL; + } + if (libnet_mirror) { + libnet_destroy(libnet_mirror); + } +} + +static void +log_mirror_closecb(void *fh) { + log_content_ctx_t *ctx = fh; + + if (ctx->mirror.request->seq > 0 && ctx->mirror.request->ack > 0) { + if (logpkt_write_packet(libnet_mirror, 0, ctx->mirror.request, TH_FIN | TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to mirror log: %s\n", + strerror(errno)); + } + ctx->mirror.response->ack += 1; + if (logpkt_write_packet(libnet_mirror, 0, ctx->mirror.response, TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to mirror log: %s\n", + strerror(errno)); + } + if (logpkt_write_packet(libnet_mirror, 0, ctx->mirror.response, TH_FIN | TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to mirror log: %s\n", + strerror(errno)); + } + ctx->mirror.request->ack += 1; + ctx->mirror.request->seq += 1; + + if (logpkt_write_packet(libnet_mirror, 0, ctx->mirror.request, TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to mirror log: %s\n", + strerror(errno)); + } + } + + if (ctx->mirror.request) { + free(ctx->mirror.request); + } + if (ctx->mirror.response) { + free(ctx->mirror.response); + } + if (--ctx->open == 0) { + free(ctx); + } +} + +static ssize_t +log_mirror_writecb(void *fh, const void *buf, size_t sz) { + log_content_ctx_t *ctx = fh; + char flags = TH_PUSH | TH_ACK; + pcap_packet_t * from = ctx->is_request ? ctx->mirror.request : ctx->mirror.response; + pcap_packet_t * to = ctx->is_request ? ctx->mirror.response : ctx->mirror.request; + + if (ctx->is_request) { + if (ctx->mirror.request->seq == 0) { + if (logpkt_write_packet(libnet_mirror, 0, ctx->mirror.request, TH_SYN, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to mirror log: %s\n", + strerror(errno)); + return -1; + } + + ctx->mirror.response->ack = ctx->mirror.request->seq + 1; + + if (logpkt_write_packet(libnet_mirror, 0, ctx->mirror.response, TH_SYN | TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to mirror log: %s\n", + strerror(errno)); + return -1; + } + + ctx->mirror.request->ack = ctx->mirror.response->seq + 1; + ctx->mirror.request->seq += 1; + if (logpkt_write_packet(libnet_mirror, 0, ctx->mirror.request, TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to mirror log: %s\n", + strerror(errno)); + return -1; + } + + ctx->mirror.response->seq += 1; + } + } + + if (logpkt_write_payload(libnet_mirror, 0, from, to, flags, buf, sz) == -1) { + log_err_printf("Warning: Failed to write to mirror log: %s\n", + strerror(errno)); + return -1; + } + + return sz; +} + +static logbuf_t * +log_packet_prepcb(void *fh, unsigned long prepflags, logbuf_t *lb) { + log_content_ctx_t *ctx = fh; + ctx->is_request = !!(prepflags & PREPFLAG_REQUEST); + return lb; +} /* * Certificate writer for -w/-W options. @@ -1038,6 +1556,58 @@ log_preinit(opts_t *opts) goto out; } } + if(opts->pcaplog) { + char errbuf[LIBNET_ERRBUF_SIZE]; + libnet_pcap = libnet_init(LIBNET_LINK, NULL, errbuf); + if (libnet_pcap == NULL) { + log_err_printf("Failed to init pcap libnet: %s", errbuf); + goto out; + } + libnet_seed_prand(libnet_pcap); + + if (opts->pcaplog_isdir) { + reopencb = NULL; + opencb = log_pcap_dir_opencb; + closecb = log_pcap_dir_closecb; + writecb = log_pcap_dir_writecb; + prepcb = NULL; + } else if (opts->pcaplog_isspec) { + reopencb = NULL; + opencb = log_pcap_spec_opencb; + closecb = log_pcap_spec_closecb; + writecb = log_pcap_spec_writecb; + prepcb = NULL; + } else { + if (log_pcap_preinit(opts->pcaplog) == -1) + goto out; + reopencb = log_pcap_reopencb; + opencb = NULL; + closecb = log_pcap_closecb; + writecb = log_pcap_writecb; + prepcb = log_packet_prepcb; + } + if (!(pcap_log = logger_new(reopencb, opencb, closecb, + writecb, prepcb, + log_exceptcb))) { + log_content_pcap_fini(); + goto out; + } + } + if (opts->mirrorif) { + if (log_mirror_preinit(opts->mirrorif) == -1) + goto out; + reopencb = NULL; + opencb = NULL; + closecb = log_mirror_closecb; + writecb = log_mirror_writecb; + prepcb = log_packet_prepcb; + if (!(mirror_log = logger_new(reopencb, opencb, closecb, + writecb, prepcb, + log_exceptcb))) { + log_content_mirror_fini(); + goto out; + } + } if (opts->connectlog) { if (log_connect_preinit(opts->connectlog) == -1) goto out; @@ -1086,6 +1656,14 @@ log_preinit(opts_t *opts) log_masterkey_fini(); logger_free(masterkey_log); } + if (pcap_log) { + log_content_pcap_fini(); + logger_free(pcap_log); + } + if (mirror_log) { + log_content_mirror_fini(); + logger_free(mirror_log); + } return -1; } @@ -1109,6 +1687,14 @@ log_preinit_undo(void) log_masterkey_fini(); logger_free(masterkey_log); } + if (pcap_log) { + log_content_pcap_fini(); + logger_free(pcap_log); + } + if (mirror_log) { + log_content_mirror_fini(); + logger_free(mirror_log); + } } /* @@ -1135,7 +1721,17 @@ log_init(opts_t *opts, proxy_ctx_t *ctx, int clisock1, int clisock2) content_clisock = clisock1; if (logger_start(content_log) == -1) return -1; - } else { + } + if (pcap_log) { + content_clisock = clisock1; + if (logger_start(pcap_log) == -1) + return -1; + } + if (mirror_log) { + if (logger_start(mirror_log) == -1) + return -1; + } + if (content_clisock == -1) { privsep_client_close(clisock1); } if (cert_log) { @@ -1169,6 +1765,10 @@ log_fini(void) logger_leave(connect_log); if (err_log) logger_leave(err_log); + if (pcap_log) + logger_leave(pcap_log); + if (mirror_log) + logger_leave(mirror_log); if (cert_log) logger_join(cert_log); @@ -1180,6 +1780,10 @@ log_fini(void) logger_join(connect_log); if (err_log) logger_join(err_log); + if (pcap_log) + logger_join(pcap_log); + if (mirror_log) + logger_join(mirror_log); if (cert_log) logger_free(cert_log); @@ -1191,6 +1795,10 @@ log_fini(void) logger_free(connect_log); if (err_log) logger_free(err_log); + if (pcap_log) + logger_free(pcap_log); + if (mirror_log) + logger_free(mirror_log); if (masterkey_log) log_masterkey_fini(); @@ -1198,6 +1806,10 @@ log_fini(void) log_content_file_fini(); if (connect_log) log_connect_fini(); + if (pcap_log) + log_content_pcap_fini(); + if (mirror_log) + log_content_mirror_fini(); if (cert_clisock != -1) privsep_client_close(cert_clisock); @@ -1219,6 +1831,12 @@ log_reopen(void) if (connect_log) if (logger_reopen(connect_log) == -1) rv = -1; + if (pcap_log) + if (logger_reopen(pcap_log) == -1) + rv = -1; + if (mirror_log) + if (logger_reopen(mirror_log) == -1) + rv = -1; return rv; } diff --git a/log.h b/log.h index 591c8984..2849dcac 100644 --- a/log.h +++ b/log.h @@ -33,6 +33,7 @@ #include "proxy.h" #include "logger.h" #include "attrib.h" +#include "logpkt.h" int log_err_printf(const char *, ...) PRINTF(1,2); void log_err_mode(int); diff --git a/logbuf.c b/logbuf.c index 8c5f5b5f..5fcde71b 100644 --- a/logbuf.c +++ b/logbuf.c @@ -105,6 +105,22 @@ logbuf_new_copy(const void *buf, size_t sz, void *fh, logbuf_t *next) return lb; } +/* + * Create new logbuf from lb, recursively creating next logbuf. + */ +logbuf_t * +logbuf_new_rcopy(logbuf_t *lb) +{ + logbuf_t *lbnew = NULL; + if (lb) { + lbnew = logbuf_new_copy(lb->buf, lb->sz, lb->fh, NULL); + if (!lbnew) + return NULL; + lbnew->next = logbuf_new_rcopy(lb->next); + } + return lbnew; +} + /* * Create new logbuf using printf, setting fh and next. */ @@ -147,30 +163,39 @@ logbuf_size(logbuf_t *lb) /* * Write content of logbuf using writefunc and free all buffers. * Returns -1 on errors and sets errno according to write(). - * Returns total of bytes written by 1 .. n write() calls on success. + * Returns total of bytes written by write() call on success. */ ssize_t logbuf_write_free(logbuf_t *lb, writefunc_t writefunc) { - ssize_t rv1, rv2 = 0; + unsigned char *buf = NULL; + ssize_t sz = 0; + logbuf_t *lbnext; + int rv; + // Save fh, as only lb has fh set and lb is freed in while loop + void *fh = lb->fh; + + while (lb) { + buf = realloc(buf, sz + lb->sz); + if (!buf) { + logbuf_free(lb); + return -1; + } - rv1 = writefunc(lb->fh, lb->buf, lb->sz); - if (lb->buf) { - free(lb->buf); - } - if (lb->next) { - if (rv1 == -1) { - logbuf_free(lb->next); - } else { - lb->next->fh = lb->fh; - rv2 = logbuf_write_free(lb->next, writefunc); + memcpy(buf + sz, lb->buf, lb->sz); + sz += lb->sz; + + lbnext = lb->next; + if (lb->buf) { + free(lb->buf); } + free(lb); + lb = lbnext; } - free(lb); - if (rv1 == -1 || rv2 == -1) - return -1; - else - return rv1 + rv2; + + rv = writefunc(fh, buf, sz); + free(buf); + return rv; } /* diff --git a/logbuf.h b/logbuf.h index c371f907..fa840684 100644 --- a/logbuf.h +++ b/logbuf.h @@ -30,6 +30,7 @@ #define LOGBUF_H #include "attrib.h" +#include "logpkt.h" #include #include @@ -47,10 +48,11 @@ typedef ssize_t (*writefunc_t)(void *, const void *, size_t); logbuf_t * logbuf_new(void *, size_t, void *, logbuf_t *) MALLOC; logbuf_t * logbuf_new_alloc(size_t, void *, logbuf_t *) MALLOC; logbuf_t * logbuf_new_copy(const void *, size_t, void *, logbuf_t *) MALLOC; +logbuf_t * logbuf_new_rcopy(logbuf_t *); logbuf_t * logbuf_new_printf(void *, logbuf_t *, const char *, ...) MALLOC PRINTF(3,4); ssize_t logbuf_size(logbuf_t *) NONNULL(1) WUNRES; -ssize_t logbuf_write_free(logbuf_t *, writefunc_t) NONNULL(1); +ssize_t logbuf_write_free(logbuf_t *, writefunc_t); void logbuf_free(logbuf_t *) NONNULL(1); #define logbuf_ctl_clear(x) (x)->ctl = 0 diff --git a/logpkt.c b/logpkt.c new file mode 100644 index 00000000..83d71204 --- /dev/null +++ b/logpkt.c @@ -0,0 +1,476 @@ +/*- + * SSLsplit - transparent SSL/TLS interception + * https://www.roe.ch/SSLsplit + * + * Copyright (c) 2009-2018, Daniel Roethlisberger . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "logpkt.h" +#include "sys.h" +#include "log.h" + +libnet_t *libnet_pcap = NULL; +libnet_t *libnet_mirror = NULL; +struct libnet_ether_addr *mirrorsender_ether = NULL; + +static unsigned int mirrortarget_ip = 0; /* Pcap handler input */ +static unsigned char mirrortarget_ether[ETHER_ADDR_LEN]; /* Pcap handler output */ +static int mirrortarget_result = -1; /* Pcap handler retval */ + +int +logpkt_write_global_pcap_hdr(int fd) +{ + pcap_file_hdr_t hdr; + + memset(&hdr, 0x0, sizeof(hdr)); + + hdr.magic_number = 0xa1b2c3d4; + hdr.version_major = 2; + hdr.version_minor = 4; + hdr.snaplen = 1500; + hdr.network = 1; + + if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + return -1; + } + return 0; +} + +/* + * Returns -1 if addr is equal to ip6 error addr, 0 otherwise. + */ +static int +logpkt_ip6addr_error(struct libnet_in6_addr addr) +{ + uint32_t *p1 = (uint32_t*)&addr.__u6_addr; + uint32_t *p2 = (uint32_t*)&in6addr_error.__u6_addr; + + if ((p1[0] == p2[0]) && (p1[1] == p2[1]) && (p1[2] == p2[2]) && (p1[3] == p2[3])) { + return -1; + } + return 0; +} + +static int +logpkt_str2ip46addr(libnet_t *libnet, char *addr, int af, unsigned int *ip4addr, struct libnet_in6_addr *ip6addr) +{ + if (af == AF_INET) { + *ip4addr = inet_addr(addr); + if (*ip4addr == 0) { + log_err_printf("Error converting IPv4 address: %s\n", addr); + goto out; + } + } else { + *ip6addr = libnet_name2addr6(libnet, addr, LIBNET_DONT_RESOLVE); + if (logpkt_ip6addr_error(*ip6addr) == -1) { + log_err_printf("Error converting IPv6 address: %s\n", addr); + goto out; + } + } + return 0; +out: + return -1; +} + +int +logpkt_set_packet_fields(libnet_t *libnet, pcap_packet_t *pcap, char *src_addr, char *src_port, char *dst_addr, char *dst_port) +{ + pcap->af = sys_get_af(src_addr); + if (pcap->af == AF_UNSPEC) { + log_err_printf("Unspec address family: %s\n", src_addr); + goto out; + } + if (sys_get_af(dst_addr) != pcap->af) { + log_err_printf("Src and dst address families do not match: %s, %s\n", src_addr, dst_addr); + goto out; + } + + if (logpkt_str2ip46addr(libnet, src_addr, pcap->af, &pcap->src_ip, &pcap->src_ip6) == -1) { + goto out; + } + pcap->src_port = atoi(src_port); + + if (logpkt_str2ip46addr(libnet, dst_addr, pcap->af, &pcap->dst_ip, &pcap->dst_ip6) == -1) { + goto out; + } + pcap->dst_port = atoi(dst_port); + + pcap->epoch = time(NULL); + pcap->seq = 0; + pcap->ack = 0; + return 0; +out: + return -1; +} + +static int +logpkt_write_pcap_record(int fd) +{ + u_int32_t len; + u_int8_t *packet = NULL; + pcap_rec_hdr_t packet_record_hdr; + struct timeval tv; + int rv = -1; + + if (libnet_pblock_coalesce(libnet_pcap, &packet, &len) == -1) { + log_err_printf("Error in libnet_pblock_coalesce(): %s", libnet_geterror(libnet_pcap)); + goto out; + } + + gettimeofday(&tv, NULL); + packet_record_hdr.ts_sec = tv.tv_sec; + packet_record_hdr.ts_usec = tv.tv_usec; + packet_record_hdr.orig_len = packet_record_hdr.incl_len = len; + + if (write(fd, &packet_record_hdr, sizeof(packet_record_hdr)) == sizeof(packet_record_hdr)) { + if (write(fd, packet, len) != (int)len) { + log_err_printf("Error writing pcap record packet: %s\n", strerror(errno)); + goto out2; + } + } else { + log_err_printf("Error writing pcap record hdr: %s\n", strerror(errno)); + goto out2; + } + + rv = 0; +out2: + if (libnet_pcap->aligner > 0) { + // Don't forget to free aligned bytes + packet = packet - libnet_pcap->aligner; + } + free(packet); +out: + return rv; +} + +int +logpkt_write_payload(libnet_t *libnet, int fd, pcap_packet_t *from, pcap_packet_t *to, char flags, const unsigned char *payload, size_t payloadlen) +{ + int sendsize = 0; + + while (payloadlen > 0) { + payload += sendsize; + sendsize = payloadlen > MSS_VAL ? MSS_VAL : payloadlen; + + if (logpkt_write_packet(libnet, fd, from, flags, payload, sendsize) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + return -1; + } + + to->ack += sendsize; + payloadlen -= sendsize; + } + + if (logpkt_write_packet(libnet, fd, to, TH_ACK, NULL, 0) == -1) { + log_err_printf("Warning: Failed to write to pcap log: %s\n", + strerror(errno)); + return -1; + } + return 0; +} + +static int +logpkt_build_packet(libnet_t *libnet, unsigned char src_ether[], pcap_packet_t *pcap, char flags, const unsigned char *payload, size_t payloadlen) +{ + libnet_ptag_t ptag; + + if (flags & TH_SYN) { + pcap->seq = libnet_get_prand(LIBNET_PRu32); + } + + ptag = libnet_build_tcp( + pcap->src_port, /* source port */ + pcap->dst_port, /* destination port */ + pcap->seq, /* sequence number */ + pcap->ack, /* acknowledgement num */ + flags, /* control flags */ + 32767, /* window size */ + 0, /* checksum */ + 0, /* urgent pointer */ + LIBNET_TCP_H + payloadlen, /* TCP packet size */ + // payload type differs in different libnet versions + (unsigned char *)payload, /* payload */ + payloadlen, /* payload size */ + libnet, /* libnet handle */ + 0); /* libnet id */ + if (ptag == -1) { + log_err_printf("Error building tcp header: %s", libnet_geterror(libnet)); + goto out; + } + + if (pcap->af == AF_INET) { + ptag = libnet_build_ipv4( + LIBNET_IPV4_H + LIBNET_TCP_H + payloadlen, /* length */ + 0, /* TOS */ + (u_int16_t)libnet_get_prand(LIBNET_PRu16), /* IP ID */ + 0x4000, /* IP Frag */ + 64, /* TTL */ + IPPROTO_TCP, /* protocol */ + 0, /* checksum */ + pcap->src_ip, /* source IP */ + pcap->dst_ip, /* destination IP */ + NULL, /* payload */ + 0, /* payload size */ + libnet, /* libnet handle */ + 0); /* libnet id */ + } else { + // TODO: Check values of tc, fl, nh, and hl + ptag = libnet_build_ipv6( + 0, /* traffic class */ + 0, /* flow label */ + LIBNET_IPV6_H + LIBNET_TCP_H + payloadlen, /* total length of the IP packet */ + IPPROTO_TCP, /* next header */ + 255, /* hop limit */ + pcap->src_ip6, /* source IPv6 address */ + pcap->dst_ip6, /* destination IPv6 address */ + NULL, /* optional payload or NULL */ + 0, /* payload length or 0 */ + libnet, /* pointer to a libnet context */ + 0); /* protocol tag to modify an existing header, 0 to build a new one */ + } + if (ptag == -1) { + log_err_printf("Error building ip header: %s", libnet_geterror(libnet)); + goto out; + } + + ptag = libnet_build_ethernet( + pcap->dst_ether, /* ethernet destination */ + src_ether, /* ethernet source */ + pcap->af == AF_INET ? ETHERTYPE_IP : ETHERTYPE_IPV6, /* protocol type */ + NULL, /* payload */ + 0, /* payload size */ + libnet, /* libnet handle */ + 0); /* libnet id */ + if (ptag == -1) { + log_err_printf("Error building ethernet header: %s", libnet_geterror(libnet)); + goto out; + } + + pcap->seq += payloadlen; +out: + return ptag; +} + +static int +logpkt_write_pcap_packet(libnet_t *libnet, int fd, pcap_packet_t *pcap, char flags, const unsigned char *payload, size_t payloadlen) +{ + // TODO: Check init + unsigned char src_ether[ETHER_ADDR_LEN] = {0x84, 0x34, 0xC3, 0x50, 0x68, 0x8A}; + int rv = -1; + + if (logpkt_build_packet(libnet, src_ether, pcap, flags, payload, payloadlen) == -1) { + log_err_printf("Error building pcap packet\n"); + goto out; + } + rv = logpkt_write_pcap_record(fd); + if (rv == -1) { + log_err_printf("Error writing pcap record\n"); + } +out: + return rv; +} + +static int +logpkt_write_mirror_packet(libnet_t *libnet, pcap_packet_t *pcap, char flags, const unsigned char *payload, size_t payloadlen) +{ + int rv = -1; + + if (logpkt_build_packet(libnet, mirrorsender_ether->ether_addr_octet, pcap, flags, payload, payloadlen) == -1) { + log_err_printf("Error building mirror packet\n"); + goto out; + } + rv = libnet_write(libnet); + if (rv == -1) { + log_err_printf("Error writing mirror packet: %s", libnet_geterror(libnet)); + } +out: + return rv; +} + +int +logpkt_write_packet(libnet_t *libnet, int fd, pcap_packet_t *pcap, char flags, const unsigned char *payload, size_t payloadlen) +{ + int rv; + + if (libnet == libnet_pcap) { + rv = logpkt_write_pcap_packet(libnet, fd, pcap, flags, payload, payloadlen); + } else { + rv = logpkt_write_mirror_packet(libnet, pcap, flags, payload, payloadlen); + } + libnet_clear_packet(libnet); + return rv; +} + +/* Pcap handler */ +static void +logpkt_recv_arp_reply(UNUSED const char *user, UNUSED struct pcap_pkthdr *h, uint8_t *packet) +{ + struct libnet_802_3_hdr *heth; + struct libnet_arp_hdr *harp; + unsigned char *ether; + uint32_t ip; + + heth = (void*)packet; + harp = (void*)((char*)heth + LIBNET_ETH_H); + + /* Check if ARP reply */ + if (htons(harp->ar_op) != ARPOP_REPLY) { + /* Not an error, as we filter to recv all arp packets */ + return; + } + + /* Check if IPv4 address reply */ + if (htons(harp->ar_pro) != ETHERTYPE_IP) { + log_err_printf("Not ETHERTYPE_IP: %u\n", harp->ar_pro); + return; + } + + /* Check if ethernet address reply */ + if (htons(harp->ar_hrd) != ARPHRD_ETHER) { + log_err_printf("Not ARPHRD_ETHER: %u\n", harp->ar_hrd); + return; + } + + /* Check if IPv4 address is the one we asked for */ + memcpy(&ip, (char*)harp + harp->ar_hln + LIBNET_ARP_H, 4); + if (mirrortarget_ip != ip) { + log_err_printf("Reply not for mirror target ip: %.2x != %.2x\n", ip, mirrortarget_ip); + return; + } + + /* Must be sent from mirror target, so we know it is reachable */ + if (memcmp((u_char*)harp + sizeof(struct libnet_arp_hdr), + heth->_802_3_shost, ETHER_ADDR_LEN)) { + ether = heth->_802_3_shost; + log_err_printf("Reply not from mirror target ether: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", + ether[0], ether[1], ether[2], ether[3], ether[4], ether[5]); + return; + } + + /* Success: Got ethernet address of mirror target, and it is up */ + memcpy(&mirrortarget_ether, (u_char*)harp + sizeof(struct libnet_arp_hdr), 6); + mirrortarget_result = 0; +} + +int +logpkt_check_mirrortarget(char *ip, char *ether, char *mirrorif) +{ + char errbuf[LIBNET_ERRBUF_SIZE > PCAP_ERRBUF_SIZE ? LIBNET_ERRBUF_SIZE : PCAP_ERRBUF_SIZE]; + unsigned int src_ip; + struct libnet_ether_addr *src_ether; + unsigned char broadcast_ether[ETHER_ADDR_LEN] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; + unsigned char zero_ether[ETHER_ADDR_LEN] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; + struct bpf_program bp; + int count = 50; + + libnet_t *libnet = libnet_init(LIBNET_LINK, mirrorif, errbuf); + if (libnet == NULL) { + log_err_printf("Error initializing libnet: %s", errbuf); + goto out; + } + + /* Get destination IP address */ + mirrortarget_ip = libnet_name2addr4(libnet, ip, LIBNET_DONT_RESOLVE); + if ((int)mirrortarget_ip == -1) { + log_err_printf("Error converting IP address\n"); + goto out2; + } + + // TODO: IPv6? + /* Get our own IP and ethernet addresses */ + src_ip = libnet_get_ipaddr4(libnet); + if ((int32_t)src_ip == -1) { + log_err_printf("Error getting IP address: %s", libnet_geterror(libnet)); + goto out2; + } + + src_ether = libnet_get_hwaddr(libnet); + if (src_ether == NULL) { + log_err_printf("Error getting ethernet address: %s", libnet_geterror(libnet)); + goto out2; + } + + /* Build ARP header */ + if (libnet_autobuild_arp(ARPOP_REQUEST, + src_ether->ether_addr_octet, + (u_int8_t*)&src_ip, zero_ether, + (u_int8_t*)&mirrortarget_ip, libnet) == -1) { + log_err_printf("Error building arp header: %s", libnet_geterror(libnet)); + goto out2; + } + + /* Build ethernet header */ + if (libnet_autobuild_ethernet(broadcast_ether, ETHERTYPE_ARP, libnet) == -1) { + log_err_printf("Error building ethernet header: %s", libnet_geterror(libnet)); + goto out2; + } + + pcap_t *pcap = pcap_open_live(mirrorif, 100, 0, 10, errbuf); + if (pcap == NULL) { + log_err_printf("Error in pcap_open_live(): %s\n", errbuf); + goto out2; + } + + /* Interested in ARP packets only */ + if (pcap_compile(pcap, &bp, "arp", 0, -1) == -1) { + log_err_printf("Error in pcap_compile(): %s\n", pcap_geterr(pcap)); + goto out3; + } + if (pcap_setfilter(pcap, &bp) == -1) { + log_err_printf("Error in pcap_setfilter(): %s\n", pcap_geterr(pcap)); + goto out4; + } + + do { + fprintf(stderr, "."); + + if (libnet_write(libnet) != -1) { + /* Limit # of packets to process, so we can loop to send arp requests on busy networks */ + if (pcap_dispatch(pcap, 1000, (pcap_handler)logpkt_recv_arp_reply, NULL) < 0) { + log_err_printf("Error in pcap_dispatch(): %s\n", pcap_geterr(pcap)); + } + } else { + log_err_printf("Error writing arp packet: %s", libnet_geterror(libnet)); + } + + sleep(1); + } while (mirrortarget_result == -1 && --count > 0); + + fprintf(stderr, "\n"); + + if (mirrortarget_result == 0) { + memcpy(ether, &mirrortarget_ether, 6); + fprintf(stderr, "Mirroring target is up: %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n", + ether[0], ether[1], ether[2], ether[3], ether[4], ether[5]); + } +out4: + pcap_freecode(&bp); +out3: + pcap_close(pcap); +out2: + libnet_destroy(libnet); +out: + return mirrortarget_result; +} diff --git a/logpkt.h b/logpkt.h new file mode 100644 index 00000000..208ac16f --- /dev/null +++ b/logpkt.h @@ -0,0 +1,100 @@ +/*- + * SSLsplit - transparent SSL/TLS interception + * https://www.roe.ch/SSLsplit + * + * Copyright (c) 2009-2018, Daniel Roethlisberger . + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LOGPKT_H +#define LOGPKT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef OPENBSD +#include +#include +#else /* !OPENBSD */ +#include +#include +#endif /* !OPENBSD */ + +#define MSS_VAL 1420 + +#ifdef OPENBSD +#define PF_PACKET PF_ROUTE /* Packet family */ +#endif /* OPENBSD */ + +typedef struct pcap_file_hdr { + unsigned int magic_number; /* magic number */ + unsigned short version_major; /* major version number */ + unsigned short version_minor; /* minor version number */ + unsigned int thiszone; /* GMT to local correction */ + unsigned int sigfigs; /* accuracy of timestamps */ + unsigned int snaplen; /* max length of captured packets, in octets */ + unsigned int network; /* data link type */ +} pcap_file_hdr_t; + +typedef struct pcap_rec_hdr { + unsigned int ts_sec; /* timestamp seconds */ + unsigned int ts_usec; /* timestamp microseconds */ + unsigned int incl_len; /* number of octets of packet saved in file */ + unsigned int orig_len; /* actual length of packet */ +} pcap_rec_hdr_t; + +typedef struct pcap_packet { + time_t epoch; + unsigned int src_ip; + struct libnet_in6_addr src_ip6; + unsigned int dst_ip; + struct libnet_in6_addr dst_ip6; + int af; + unsigned short src_port; + unsigned short dst_port; + unsigned int ack; + unsigned int seq; + unsigned char dst_ether[ETHER_ADDR_LEN]; +} pcap_packet_t; + +libnet_t *libnet_pcap; +libnet_t *libnet_mirror; +struct libnet_ether_addr *mirrorsender_ether; + +int logpkt_write_global_pcap_hdr(int); +int logpkt_set_packet_fields(libnet_t *, pcap_packet_t *, char *, char *, char *, char *); +int logpkt_write_packet(libnet_t *, int, pcap_packet_t *, char, const unsigned char *, size_t); +int logpkt_write_payload(libnet_t *, int, pcap_packet_t *, pcap_packet_t *, char, const unsigned char *, size_t); +int logpkt_check_mirrortarget(char *, char *, char *); + +#endif /* !LOGPKT_H */ diff --git a/main.c b/main.c index ebbb1d5e..f091b0c8 100644 --- a/main.c +++ b/main.c @@ -42,6 +42,7 @@ #include "log.h" #include "build.h" #include "defaults.h" +#include "logpkt.h" #include #include @@ -202,6 +203,12 @@ main_usage(void) #define OPT_i #endif /* HAVE_LOCAL_PROCINFO */ " -M logfile log master keys to logfile in SSLKEYLOGFILE format\n" +" -X pcapfile pcap log: packets to pcapfile (excludes -Y/-y)\n" +" -Y pcapdir pcap log: packets to separate files in dir (excludes -X/-y)\n" +" -y pathspec pcap log: packets to sep files with %% subst (excl. -X/-Y):\n" +" see option -F for pathspec format\n" +" -I if mirror packets to interface\n" +" -T addr mirror packets to target address (used with -I)\n" " -d daemon mode: run in background, log error messages to syslog\n" " -D debug mode: run in foreground, log debug messages on stderr\n" " -V print version information and exit\n" @@ -307,7 +314,7 @@ main(int argc, char *argv[]) while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z OPT_i OPT_x "k:c:C:K:t:OPa:b:s:r:R:e:Eu:m:j:p:l:L:S:F:M:" - "dDVhW:w:q:f:o:")) != -1) { + "dDVhW:w:q:f:o:X:Y:y:I:T:")) != -1) { switch (ch) { case 'f': if (opts->conffile) @@ -416,16 +423,30 @@ main(int argc, char *argv[]) case 'S': opts_set_contentlogdir(opts, argv0, optarg); break; - case 'F': { + case 'F': opts_set_contentlogpathspec(opts, argv0, optarg); break; + case 'X': + opts_set_pcaplog(opts, argv0, optarg); + break; + case 'Y': + opts_set_pcaplogdir(opts, argv0, optarg); + break; + case 'y': + opts_set_pcaplogpathspec(opts, argv0, optarg); + break; + case 'I': + opts_set_mirrorif(opts, argv0, optarg); + break; + case 'T': + opts_set_mirrortarget(opts, argv0, optarg); + break; case 'W': opts_set_certgendir_writeall(opts, argv0, optarg); break; case 'w': opts_set_certgendir_writegencerts(opts, argv0, optarg); break; - } #ifdef HAVE_LOCAL_PROCINFO case 'i': opts_set_lprocinfo(opts); @@ -566,6 +587,20 @@ main(int argc, char *argv[]) exit(EXIT_FAILURE); } + /* mirror logging checks */ + if (opts->mirrortarget) { + if (opts->mirrorif) { + fprintf(stderr, "Checking mirroring target: %s\n", opts->mirrortarget); + if (logpkt_check_mirrortarget(opts->mirrortarget, opts->mirrortarget_ether, opts->mirrorif) == -1) { + log_err_printf("Error checking mirroring target\n"); + exit(EXIT_FAILURE); + } + } else { + fprintf(stderr, "%s: -T depends on -I.\n", argv0); + exit(EXIT_FAILURE); + } + } + /* debug log, part 1 */ if (OPTS_DEBUG(opts)) { main_version(); diff --git a/opts.c b/opts.c index 42e61f72..55f598ca 100644 --- a/opts.c +++ b/opts.c @@ -141,6 +141,18 @@ opts_free(opts_t *opts) if (opts->masterkeylog) { free(opts->masterkeylog); } + if (opts->pcaplog) { + free(opts->pcaplog); + } + if (opts->pcaplog_basedir) { + free(opts->pcaplog_basedir); + } + if (opts->mirrorif) { + free(opts->mirrorif); + } + if (opts->mirrortarget) { + free(opts->mirrortarget); + } memset(opts, 0, sizeof(opts_t)); free(opts); } @@ -295,17 +307,10 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine, proxyspec_t **o break; case 2: /* listenport */ - if (strstr(addr, ":")) - af = AF_INET6; - else if (!strpbrk(addr, "abcdefghijklmnopqrstu" - "vwxyzABCDEFGHIJKLMNOP" - "QRSTUVWXYZ-")) - af = AF_INET; - else - af = AF_UNSPEC; af = sys_sockaddr_parse(&spec->listen_addr, &spec->listen_addrlen, - addr, **argv, af, + addr, **argv, + sys_get_af(addr), EVUTIL_AI_PASSIVE); if (af == -1) { exit(EXIT_FAILURE); @@ -1004,15 +1009,15 @@ opts_set_contentlogdir(opts_t *opts, const char *argv0, const char *optarg) log_dbg_printf("ContentLogDir: %s\n", opts->contentlog); } -void -opts_set_contentlogpathspec(opts_t *opts, const char *argv0, const char *optarg) +static void +opts_set_logbasedir(const char *argv0, const char *optarg, char **basedir, char **log) { char *lhs, *rhs, *p, *q; size_t n; - if (opts->contentlog_basedir) - free(opts->contentlog_basedir); - if (opts->contentlog) - free(opts->contentlog); + if (*basedir) + free(*basedir); + if (*log) + free(*log); if (log_content_split_pathspec(optarg, &lhs, &rhs) == -1) { fprintf(stderr, "%s: Failed to split " @@ -1038,8 +1043,8 @@ opts_set_contentlogpathspec(opts_t *opts, const char *argv0, const char *optarg) strerror(errno), errno); exit(EXIT_FAILURE); } - opts->contentlog_basedir = realpath(lhs, NULL); - if (!opts->contentlog_basedir) { + *basedir = realpath(lhs, NULL); + if (!*basedir) { fprintf(stderr, "%s: Failed to " "canonicalize '%s': " "%s (%i)\n", @@ -1047,19 +1052,19 @@ opts_set_contentlogpathspec(opts_t *opts, const char *argv0, const char *optarg) strerror(errno), errno); exit(EXIT_FAILURE); } - /* count '%' in opts->contentlog_basedir */ - for (n = 0, p = opts->contentlog_basedir; + /* count '%' in basedir */ + for (n = 0, p = *basedir; *p; p++) { if (*p == '%') n++; } free(lhs); - n += strlen(opts->contentlog_basedir); + n += strlen(*basedir); if (!(lhs = malloc(n + 1))) oom_die(argv0); /* re-encoding % to %%, copying basedir to lhs */ - for (p = opts->contentlog_basedir, q = lhs; + for (p = *basedir, q = lhs; *p; p++, q++) { *q = *p; @@ -1068,13 +1073,19 @@ opts_set_contentlogpathspec(opts_t *opts, const char *argv0, const char *optarg) } *q = '\0'; /* lhs contains encoded realpathed basedir */ - if (asprintf(&opts->contentlog, + if (asprintf(log, "%s/%s", lhs, rhs) < 0) oom_die(argv0); - opts->contentlog_isdir = 0; - opts->contentlog_isspec = 1; free(lhs); free(rhs); +} + +void +opts_set_contentlogpathspec(opts_t *opts, const char *argv0, const char *optarg) +{ + opts_set_logbasedir(argv0, optarg, &opts->contentlog_basedir, &opts->contentlog); + opts->contentlog_isdir = 0; + opts->contentlog_isspec = 1; log_dbg_printf("ContentLogPathSpec: basedir=%s, %s\n", opts->contentlog_basedir, opts->contentlog); } @@ -1104,6 +1115,76 @@ opts_set_masterkeylog(opts_t *opts, const char *argv0, const char *optarg) log_dbg_printf("MasterKeyLog: %s\n", opts->masterkeylog); } +void +opts_set_pcaplog(opts_t *opts, const char *argv0, const char *optarg) +{ + if (opts->pcaplog) + free(opts->pcaplog); + opts->pcaplog = strdup(optarg); + if (!opts->pcaplog) + oom_die(argv0); + opts->pcaplog_isdir = 0; + opts->pcaplog_isspec = 0; + log_dbg_printf("PcapLog: %s\n", opts->pcaplog); +} + +void +opts_set_pcaplogdir(opts_t *opts, const char *argv0, const char *optarg) +{ + if (!sys_isdir(optarg)) { + fprintf(stderr, "%s: '%s' is not a " + "directory\n", + argv0, optarg); + exit(EXIT_FAILURE); + } + if (opts->pcaplog) + free(opts->pcaplog); + opts->pcaplog = realpath(optarg, NULL); + if (!opts->pcaplog) { + fprintf(stderr, "%s: Failed to " + "canonicalize '%s': " + "%s (%i)\n", + argv0, optarg, + strerror(errno), errno); + exit(EXIT_FAILURE); + } + opts->pcaplog_isdir = 1; + opts->pcaplog_isspec = 0; + log_dbg_printf("PcapLogDir: %s\n", opts->pcaplog); +} + +void +opts_set_pcaplogpathspec(opts_t *opts, const char *argv0, const char *optarg) +{ + opts_set_logbasedir(argv0, optarg, &opts->pcaplog_basedir, &opts->pcaplog); + opts->pcaplog_isdir = 0; + opts->pcaplog_isspec = 1; + log_dbg_printf("PcapLogPathSpec: basedir=%s, %s\n", + opts->pcaplog_basedir, opts->pcaplog); +} + +void +opts_set_mirrorif(opts_t *opts, const char *argv0, const char *optarg) +{ + if (opts->mirrorif) + free(opts->mirrorif); + opts->mirrorif = strdup(optarg); + if (!opts->mirrorif) + oom_die(argv0); + log_dbg_printf("MirrorIf: %s\n", opts->mirrorif); +} + +void +opts_set_mirrortarget(opts_t *opts, const char *argv0, const char *optarg) +{ + if (opts->mirrortarget) + free(opts->mirrortarget); + opts->mirrortarget = strdup(optarg); + if (!opts->mirrortarget) + oom_die(argv0); + log_dbg_printf("MirrorTarget: %s\n", opts->mirrortarget); +} + void opts_set_daemon(opts_t *opts) { @@ -1271,6 +1352,16 @@ set_option(opts_t *opts, const char *argv0, const char *name, char *value, char #endif /* HAVE_LOCAL_PROCINFO */ } else if (!strncmp(name, "MasterKeyLog", 13)) { opts_set_masterkeylog(opts, argv0, value); + } else if (!strncmp(name, "PcapLog", 8)) { + opts_set_pcaplog(opts, argv0, value); + } else if (!strncmp(name, "PcapLogDir", 11)) { + opts_set_pcaplogdir(opts, argv0, value); + } else if (!strncmp(name, "PcapLogPathSpec", 16)) { + opts_set_pcaplogpathspec(opts, argv0, value); + } else if (!strncmp(name, "MirrorIf", 9)) { + opts_set_mirrorif(opts, argv0, value); + } else if (!strncmp(name, "MirrorTarget", 13)) { + opts_set_mirrortarget(opts, argv0, value); } else if (!strncmp(name, "Daemon", 7)) { yes = check_value_yesno(value, "Daemon", line_num); if (yes == -1) { diff --git a/opts.h b/opts.h index c4bdbc5a..d462ab38 100644 --- a/opts.h +++ b/opts.h @@ -33,6 +33,7 @@ #include "nat.h" #include "ssl.h" #include "attrib.h" +#include "logpkt.h" #include #include @@ -79,6 +80,8 @@ typedef struct opts { unsigned int deny_ocsp : 1; unsigned int contentlog_isdir : 1; unsigned int contentlog_isspec : 1; + unsigned int pcaplog_isdir : 1; + unsigned int pcaplog_isspec : 1; #ifdef HAVE_LOCAL_PROCINFO unsigned int lprocinfo : 1; #endif /* HAVE_LOCAL_PROCINFO */ @@ -98,6 +101,11 @@ typedef struct opts { char *contentlog; char *contentlog_basedir; /* static part of logspec, for privsep srv */ char *masterkeylog; + char *pcaplog; + char *pcaplog_basedir; /* static part of pcap logspec, for privsep srv */ + char *mirrorif; + char *mirrortarget; + char mirrortarget_ether[ETHER_ADDR_LEN]; CONST_SSL_METHOD *(*sslmethod)(void); #if (OPENSSL_VERSION_NUMBER >= 0x10100000L) && !defined(LIBRESSL_VERSION_NUMBER) int sslversion; @@ -173,6 +181,13 @@ void opts_set_contentlogpathspec(opts_t *, const char *, const char *) void opts_set_lprocinfo(opts_t *) NONNULL(1); #endif /* HAVE_LOCAL_PROCINFO */ void opts_set_masterkeylog(opts_t *, const char *, const char *) NONNULL(1,2,3); +void opts_set_pcaplog(opts_t *, const char *, const char *) NONNULL(1,2,3); +void opts_set_pcaplogdir(opts_t *, const char *, const char *) + NONNULL(1,2,3); +void opts_set_pcaplogpathspec(opts_t *, const char *, const char *) + NONNULL(1,2,3); +void opts_set_mirrorif(opts_t *, const char *, const char *) NONNULL(1,2,3); +void opts_set_mirrortarget(opts_t *, const char *, const char *) NONNULL(1,2,3); void opts_set_daemon(opts_t *) NONNULL(1); void opts_set_debug(opts_t *) NONNULL(1); int opts_set_option(opts_t *, const char *, const char *, char **) diff --git a/privsep.c b/privsep.c index b5d3489a..65f58a3e 100644 --- a/privsep.c +++ b/privsep.c @@ -65,6 +65,9 @@ #define PRIVSEP_REQ_OPENFILE_P 2 /* open content log file w/mkpath */ #define PRIVSEP_REQ_OPENSOCK 3 /* open socket and pass fd */ #define PRIVSEP_REQ_CERTFILE 4 /* open cert file in certgendir */ +#define PRIVSEP_REQ_OPENPCAPFILE_P 5 /* open pcap log file */ +#define PRIVSEP_REQ_OPENPCAPFILE 6 /* open pcap log file w/mkpath */ + /* response byte */ #define PRIVSEP_ANS_SUCCESS 0 /* success */ #define PRIVSEP_ANS_UNK_CMD 1 /* unknown command */ @@ -187,6 +190,20 @@ privsep_server_openfile(char *fn, int mkpath) return fd; } +static int WUNRES +privsep_server_openpcapfile_verify(opts_t *opts, char *fn, int mkpath) +{ + if (mkpath && !opts->pcaplog_isspec) + return -1; + if (!mkpath && !opts->pcaplog_isdir) + return -1; + if (strstr(fn, mkpath ? opts->pcaplog_basedir + : opts->pcaplog) != fn || + strstr(fn, "/../")) + return -1; + return 0; +} + static int WUNRES privsep_server_opensock_verify(opts_t *opts, void *arg) { @@ -311,11 +328,16 @@ privsep_server_handle_req(opts_t *opts, int srvsock) return 1; } case PRIVSEP_REQ_OPENFILE_P: + case PRIVSEP_REQ_OPENPCAPFILE_P: mkpath = 1; /* fall through */ - case PRIVSEP_REQ_OPENFILE: { + case PRIVSEP_REQ_OPENFILE: + case PRIVSEP_REQ_OPENPCAPFILE: { char *fn; int fd; + int (*verifyfunc)(opts_t *, char *, int) = + (req[0] == PRIVSEP_REQ_OPENFILE_P || req[0] == PRIVSEP_REQ_OPENFILE) ? + privsep_server_openfile_verify : privsep_server_openpcapfile_verify; if (n < 2) { ans[0] = PRIVSEP_ANS_INVALID; @@ -338,7 +360,7 @@ privsep_server_handle_req(opts_t *opts, int srvsock) } memcpy(fn, req + 1, n - 1); fn[n - 1] = '\0'; - if (privsep_server_openfile_verify(opts, fn, mkpath) == -1) { + if (verifyfunc(opts, fn, mkpath) == -1) { free(fn); ans[0] = PRIVSEP_ANS_DENIED; if (sys_sendmsgfd(srvsock, ans, 1, -1) == -1) { @@ -651,14 +673,18 @@ privsep_server(opts_t *opts, int sigpipe, int srvsock[], size_t nsrvsock, } int -privsep_client_openfile(int clisock, const char *fn, int mkpath) +privsep_client_openfile(int clisock, const char *fn, int mkpath, int pcapreq) { char ans[PRIVSEP_MAX_ANS_SIZE]; char req[1 + strlen(fn)]; int fd = -1; ssize_t n; - req[0] = mkpath ? PRIVSEP_REQ_OPENFILE_P : PRIVSEP_REQ_OPENFILE; + if (pcapreq) { + req[0] = mkpath ? PRIVSEP_REQ_OPENPCAPFILE_P : PRIVSEP_REQ_OPENPCAPFILE; + } else { + req[0] = mkpath ? PRIVSEP_REQ_OPENFILE_P : PRIVSEP_REQ_OPENFILE; + } memcpy(req + 1, fn, sizeof(req) - 1); if (sys_sendmsgfd(clisock, req, sizeof(req), -1) == -1) { diff --git a/privsep.h b/privsep.h index b59dab44..8e562872 100644 --- a/privsep.h +++ b/privsep.h @@ -34,7 +34,7 @@ int privsep_fork(opts_t *, int[], size_t); -int privsep_client_openfile(int, const char *, int); +int privsep_client_openfile(int, const char *, int, int); int privsep_client_opensock(int, const proxyspec_t *spec); int privsep_client_certfile(int, const char *); int privsep_client_close(int); diff --git a/pxyconn.c b/pxyconn.c index 45e615eb..c38100b6 100644 --- a/pxyconn.c +++ b/pxyconn.c @@ -188,7 +188,7 @@ typedef struct pxy_conn_ctx { } pxy_conn_ctx_t; #define WANT_CONNECT_LOG(ctx) ((ctx)->opts->connectlog||!(ctx)->opts->detach) -#define WANT_CONTENT_LOG(ctx) ((ctx)->opts->contentlog&&!(ctx)->passthrough) +#define WANT_CONTENT_LOG(ctx) (((ctx)->opts->contentlog||(ctx)->opts->pcaplog||(ctx)->opts->mirrorif)&&!(ctx)->passthrough) static pxy_conn_ctx_t * pxy_conn_ctx_new(proxyspec_t *spec, opts_t *opts, diff --git a/sslsplit.conf b/sslsplit.conf index 5984c97d..96988d89 100644 --- a/sslsplit.conf +++ b/sslsplit.conf @@ -75,14 +75,14 @@ Ciphers MEDIUM:HIGH # Connect log: log one line summary per connection to logfile #ConnectLog /var/log/sslsplit/connect.log -# Content log: full data to file or named pipe (excludes -S/-F) +# Content log: full data to file or named pipe (excludes ContentLogDir/ContentLogPathSpec) #ContentLog /var/log/sslsplit/content.log -# Content log: full data to separate files in dir (excludes -L/-F) +# Content log: full data to separate files in dir (excludes ContentLog/ContentLogPathSpec) #ContentLogDir /var/log/sslsplit/content -# Content log: full data to sep files with %% subst (excl. -L/-S) -#ContentLogPathSpec /var/log/sslsplit/%%X/%%u-%%s-%%d-%%T.log +# Content log: full data to sep files with % subst (excludes ContentLog/ContentLogDir) +#ContentLogPathSpec /var/log/sslsplit/%X/%u-%s-%d-%T.log # Look up local process owning each connection for logging #LogProcInfo yes @@ -90,6 +90,21 @@ Ciphers MEDIUM:HIGH # Log master keys to logfile in SSLKEYLOGFILE format #MasterKeyLog /var/log/sslsplit/masterkeys.log +# Pcap log: packets to pcapfile (excludes PcapLogDir/PcapLogPathSpec) +#PcapLog /var/log/sslsplit/content.pcap + +# Pcap log: packets to separate files in dir (excludes PcapLog/PcapLogPathSpec) +#PcapLogDir /var/log/sslsplit/pcap + +# Pcap log: packets to sep files with % subst (excludes PcapLog/PcapLogDir) +#PcapLogPathSpec /var/log/sslsplit/%X/%u-%s-%d-%T.pcap + +# Mirror packets to interface +#MirrorIf lo + +# Mirror packets to target address, used with MirrorIf +#MirrorTarget 127.0.0.1 + # Daemon mode: run in background, log error messages to syslog Daemon yes diff --git a/sslsplit.conf.5 b/sslsplit.conf.5 index 868648a4..fcc27690 100644 --- a/sslsplit.conf.5 +++ b/sslsplit.conf.5 @@ -146,7 +146,7 @@ Content log: full data to file or named pipe (excludes ContentLogDir/ContentLogP Content log: full data to separate files in dir (excludes ContentLog/ContentLogPathSpec). .TP \fBContentLogPathSpec STRING\fR -Content log: full data to sep files with %% subst (excludes ContentLog/ContentLogDir). +Content log: full data to sep files with % subst (excludes ContentLog/ContentLogDir). .TP \fBLogProcInfo BOOL\fR Look up local process owning each connection for logging. @@ -154,6 +154,21 @@ Look up local process owning each connection for logging. \fBMasterKeyLog STRING\fR Log master keys to logfile in SSLKEYLOGFILE format. .TP +\fBPcapLog STRING\fR +Pcap log: packets to pcapfile (excludes PcapLogDir/PcapLogPathSpec). +.TP +\fBPcapLogDir STRING\fR +Pcap log: packets to separate files in dir (excludes PcapLog/PcapLogPathSpec). +.TP +\fBPcapLogPathSpec STRING\fR +Pcap log: packets to sep files with % subst (excludes PcapLog/PcapLogDir). +.TP +\fBMirrorIf STRING\fR +Mirror packets to interface. +.TP +\fBMirrorTarget STRING\fR +Mirror packets to target address (used with MirrorIf). +.TP \fBDaemon BOOL\fR Daemon mode: run in background, log error messages to syslog. .TP diff --git a/sys.c b/sys.c index 2db0a93a..08d62fbb 100644 --- a/sys.c +++ b/sys.c @@ -352,6 +352,22 @@ sys_group_str(gid_t gid) return NULL; } +/* + * Determine address family of addr + */ +int +sys_get_af(char *addr) +{ + if (strstr(addr, ":")) + return AF_INET6; + else if (!strpbrk(addr, "abcdefghijklmnopqrstu" + "vwxyzABCDEFGHIJKLMNOP" + "QRSTUVWXYZ-")) + return AF_INET; + else + return AF_UNSPEC; +} + /* * Parse an ascii host/IP and port tuple into a sockaddr_storage. * On success, returns address family and fills in addr, addrlen. diff --git a/sys.h b/sys.h index 6c1382a8..ea564f84 100644 --- a/sys.h +++ b/sys.h @@ -46,6 +46,7 @@ int sys_isgroup(const char *) NONNULL(1) WUNRES; char * sys_user_str(uid_t) MALLOC; char * sys_group_str(gid_t) MALLOC; +int sys_get_af(char *); int sys_sockaddr_parse(struct sockaddr_storage *, socklen_t *, char *, char *, int, int) NONNULL(1,2,3,4) WUNRES; int sys_sockaddr_str(struct sockaddr *, socklen_t,