/* * Converts an IPv4/IPv6 sockaddr into a printable string representation. * Returns an allocated buffer which must be freed by caller, or NULL on error. */ char * sys_sockaddr_str(struct sockaddr *addr, socklen_t addrlen) { char host[INET6_ADDRSTRLEN], serv[6]; char *buf; int rv; size_t bufsz; bufsz = sizeof(host) + sizeof(serv) + 3; buf = malloc(bufsz); if (!buf) { log_err_printf("Cannot allocate memory\n"); return NULL; } rv = getnameinfo(addr, addrlen, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV); if (rv != 0) { log_err_printf("Cannot get nameinfo for socket address: %s\n", gai_strerror(rv)); free(buf); return NULL; } snprintf(buf, bufsz, "[%s]:%s", host, serv); return buf; }
void log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int direction) { logbuf_t *head; time_t epoch; struct tm *utc; char *header; if (!ctx->open) { log_err_printf("log_content_submit called on closed ctx\n"); return; } if (!(header = direction ? ctx->header_out : ctx->header_in)) goto out; /* prepend size tag and newline */ head = logbuf_new_printf(lb->fd, lb, " (%zu):\n", logbuf_size(lb)); if (!head) { log_err_printf("Failed to allocate memory\n"); logbuf_free(lb); return; } lb = head; /* prepend header */ head = logbuf_new_copy(header, strlen(header), lb->fd, lb); if (!head) { log_err_printf("Failed to allocate memory\n"); logbuf_free(lb); return; } lb = head; /* prepend timestamp */ head = logbuf_new_alloc(32, lb->fd, lb); if (!head) { log_err_printf("Failed to allocate memory\n"); logbuf_free(lb); return; } lb = head; time(&epoch); utc = gmtime(&epoch); lb->sz = strftime((char*)lb->buf, lb->sz, "%Y-%m-%d %H:%M:%S UTC ", utc); out: lb->fd = ctx->fd; logger_submit(content_log, lb); }
/* * Callback to load a cert/chain/key combo from a single PEM file. */ static void main_loadtgcrt(const char *filename, void *arg) { opts_t *opts = arg; cert_t *cert; char **names; cert = cert_new_load(filename); if (!cert) { log_err_printf("Failed to load cert and key from PEM file " "'%s'\n", filename); log_fini(); exit(EXIT_FAILURE); /* XXX */ } if (X509_check_private_key(cert->crt, cert->key) != 1) { log_err_printf("Cert does not match key in PEM file " "'%s':\n", filename); ERR_print_errors_fp(stderr); log_fini(); exit(EXIT_FAILURE); /* XXX */ } #ifdef DEBUG_CERTIFICATE log_dbg_printf("Loaded '%s':\n", filename); log_dbg_print_free(ssl_x509_to_str(cert->crt)); log_dbg_print_free(ssl_x509_to_pem(cert->crt)); #endif /* DEBUG_CERTIFICATE */ if (OPTS_DEBUG(opts)) { log_dbg_printf("Targets for '%s':", filename); } names = ssl_x509_names(cert->crt); for (char **p = names; *p; p++) { /* be deliberately vulnerable to NULL prefix attacks */ char *sep; if ((sep = strchr(*p, '!'))) { *sep = '\0'; } if (OPTS_DEBUG(opts)) { log_dbg_printf(" '%s'", *p); } cachemgr_tgcrt_set(*p, cert); free(*p); } if (OPTS_DEBUG(opts)) { log_dbg_printf("\n"); } free(names); cert_free(cert); }
static logbuf_t * log_content_file_prepcb(void *fh, unsigned long prepflags, logbuf_t *lb) { log_content_ctx_t *ctx = fh; int is_request = !!(prepflags & PREPFLAG_REQUEST); logbuf_t *head; time_t epoch; struct tm *utc; char *header; if (!(header = is_request ? ctx->u.file.header_req : ctx->u.file.header_resp)) goto out; /* prepend size tag and newline */ head = logbuf_new_printf(lb->fh, lb, " (%zu):\n", logbuf_size(lb)); if (!head) { log_err_printf("Failed to allocate memory\n"); logbuf_free(lb); return NULL; } lb = head; /* prepend header */ head = logbuf_new_copy(header, strlen(header), lb->fh, lb); if (!head) { log_err_printf("Failed to allocate memory\n"); logbuf_free(lb); return NULL; } lb = head; /* prepend timestamp */ head = logbuf_new_alloc(32, lb->fh, lb); if (!head) { log_err_printf("Failed to allocate memory\n"); logbuf_free(lb); return NULL; } lb = head; time(&epoch); utc = gmtime(&epoch); lb->sz = strftime((char*)lb->buf, lb->sz, "%Y-%m-%d %H:%M:%S UTC ", utc); out: return lb; }
static void pxy_log_connect_http(pxy_conn_ctx_t *ctx) { char *msg; int rv; #ifdef DEBUG_PROXY if (ctx->passthrough) { log_err_printf("Warning: pxy_log_connect_http called while in " "passthrough mode\n"); return; } #endif if (!ctx->spec->ssl) { rv = asprintf(&msg, "http %s %s %s %s %s%s\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->http_host), STRORDASH(ctx->http_method), STRORDASH(ctx->http_uri), ctx->ocsp_denied ? " ocsp:denied" : ""); } else { rv = asprintf(&msg, "https %s %s %s %s %s " "sni:%s crt:%s origcrt:%s%s\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->http_host), STRORDASH(ctx->http_method), STRORDASH(ctx->http_uri), STRORDASH(ctx->sni), STRORDASH(ctx->ssl_names), STRORDASH(ctx->ssl_orignames), ctx->ocsp_denied ? " ocsp:denied" : ""); } if ((rv == -1) || !msg) { ctx->enomem = 1; return; } if (!ctx->opts->detach) { log_err_printf("%s", msg); } if (ctx->opts->connectlog) { log_connect_print_free(msg); } else { free(msg); } }
void log_content_open(log_content_ctx_t *ctx, char *srcaddr, char *dstaddr) { if (ctx->open) return; if (content_fd != -1) { ctx->fd = content_fd; asprintf(&ctx->header_in, "%s -> %s", srcaddr, dstaddr); asprintf(&ctx->header_out, "%s -> %s", dstaddr, srcaddr); } else { char filename[1024]; char timebuf[24]; time_t epoch; struct tm *utc; time(&epoch); utc = gmtime(&epoch); strftime(timebuf, sizeof(timebuf), "%Y%m%dT%H%M%SZ", utc); snprintf(filename, sizeof(filename), "%s/%s-%s-%s.log", content_basedir, timebuf, srcaddr, dstaddr); ctx->fd = open(filename, O_WRONLY|O_APPEND|O_CREAT, 0660); if (ctx->fd == -1) { log_err_printf("Failed to open '%s': %s\n", filename, strerror(errno)); } } ctx->open = 1; }
/* * Dump information on a certificate to the debug log. */ static void pxy_debug_crt(X509 *crt) { char *sj = ssl_x509_subject(crt); if (sj) { log_dbg_printf("Subject DN: %s\n", sj); free(sj); } char *names = ssl_x509_names_to_str(crt); if (names) { log_dbg_printf("Common Names: %s\n", names); free(names); } unsigned char fpr[SSL_X509_FPRSZ]; if (ssl_x509_fingerprint_sha1(crt, fpr) == -1) { log_err_printf("Warning: Error generating X509 fingerprint\n"); } else { log_dbg_printf("Fingerprint: " "%02x:%02x:%02x:%02x:" "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:" "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", fpr[0], fpr[1], fpr[2], fpr[3], fpr[4], fpr[5], fpr[6], fpr[7], fpr[8], fpr[9], fpr[10], fpr[11], fpr[12], fpr[13], fpr[14], fpr[15], fpr[16], fpr[17], fpr[18], fpr[19]); } #ifdef DEBUG_CERTIFICATE /* dump certificate */ log_dbg_print_free(ssl_x509_to_str(crt)); log_dbg_print_free(ssl_x509_to_pem(crt)); #endif /* DEBUG_CERTIFICATE */ }
static void pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx) { char *msg; int rv; if (!ctx->spec->ssl || ctx->passthrough) { rv = asprintf(&msg, "%s %s %s\n", ctx->passthrough ? "passthrough" : "tcp", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str)); } else { rv = asprintf(&msg, "ssl %s %s " "sni:%s crt:%s origcrt:%s\n", STRORDASH(ctx->src_str), STRORDASH(ctx->dst_str), STRORDASH(ctx->sni), STRORDASH(ctx->ssl_names), STRORDASH(ctx->ssl_orignames)); } if ((rv == -1) || !msg) { ctx->enomem = 1; return; } if (!ctx->opts->detach) { log_err_printf("%s", msg); } if (ctx->opts->connectlog) { log_connect_print_free(msg); } else { free(msg); } }
/* * Parse an ascii host/IP and port tuple into a sockaddr_storage. * On success, returns address family and fills in addr, addrlen. * Returns -1 on error. */ int sys_sockaddr_parse(struct sockaddr_storage *addr, socklen_t *addrlen, char *naddr, char *nport, int af, int flags) { struct evutil_addrinfo hints; struct evutil_addrinfo *ai; int rv; memset(&hints, 0, sizeof(hints)); hints.ai_family = af; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = EVUTIL_AI_ADDRCONFIG | flags; rv = evutil_getaddrinfo(naddr, nport, &hints, &ai); if (rv != 0) { log_err_printf("Cannot resolve address '%s' port '%s': %s\n", naddr, nport, gai_strerror(rv)); return -1; } memcpy(addr, ai->ai_addr, ai->ai_addrlen); *addrlen = ai->ai_addrlen; af = ai->ai_family; freeaddrinfo(ai); return af; }
static int log_connect_preinit(const char *logfile) { connect_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, DFLT_FILEMODE); if (connect_fd == -1) { log_err_printf("Failed to open '%s' for writing: %s (%i)\n", logfile, strerror(errno), errno); return -1; } if (!(connect_fn = realpath(logfile, NULL))) { log_err_printf("Failed to realpath '%s': %s (%i)\n", logfile, strerror(errno), errno); close(connect_fd); connect_fd = -1; return -1; } return 0; }
/* * Open and lock process ID file fn. * Returns open file descriptor on success or -1 on errors. */ int sys_pidf_open(const char *fn) { int fd; if ((fd = open(fn, O_RDWR|O_CREAT, 0640)) == -1) { log_err_printf("Failed to open '%s': %s\n", fn, strerror(errno)); return -1; } if (flock(fd, LOCK_EX|LOCK_NB) == -1) { log_err_printf("Failed to lock '%s': %s\n", fn, strerror(errno)); close(fd); return -1; } return fd; }
/* * Permanently drop from root privileges to an unprivileged user account. * Sets the real, effective and stored user and group ID and the list of * ancillary groups. This is only safe if the effective user ID is 0. * If username is unset and the effective uid != uid, drop privs to uid. * This is to support setuid bit configurations. * If jaildir is set, also chroot to jaildir after reading system files * but before dropping privileges. * Returns 0 on success, -1 on failure. */ int sys_privdrop(const char *username, const char *jaildir) { struct passwd *pw = NULL; int ret = -1; if (username) { if (!(pw = getpwnam(username))) { log_err_printf("Failed to getpwnam user '%s': %s\n", username, strerror(errno)); goto error; } if (initgroups(username, pw->pw_gid) == -1) { log_err_printf("Failed to initgroups user '%s': %s\n", username, strerror(errno)); goto error; } } if (jaildir) { if (chroot(jaildir) == -1) { log_err_printf("Failed to chroot to '%s': %s\n", jaildir, strerror(errno)); goto error; } if (chdir("/") == -1) { log_err_printf("Failed to chdir to '/': %s\n", strerror(errno)); goto error; } } if (username) { if (setgid(pw->pw_gid) == -1) { log_err_printf("Failed to setgid to %i: %s\n", pw->pw_gid, strerror(errno)); goto error; } if (setuid(pw->pw_uid) == -1) { log_err_printf("Failed to setuid to %i: %s\n", pw->pw_uid, strerror(errno)); goto error; } } else if (getuid() != geteuid()) { if (setuid(getuid()) == -1) { log_err_printf("Failed to setuid(getuid()): %s\n", strerror(errno)); goto error; } } ret = 0; error: if (pw) { endpwent(); } return ret; }
static int log_content_open_singlefile(const char *logfile) { content_fd = open(logfile, O_WRONLY|O_APPEND|O_CREAT, 0660); if (content_fd == -1) { log_err_printf("Failed to open '%s' for writing: %s\n", logfile, strerror(errno)); return -1; } return 0; }
static ssize_t log_content_file_writecb(void *fh, const void *buf, size_t sz) { UNUSED log_content_ctx_t *ctx = fh; if (write(content_file_fd, buf, sz) == -1) { log_err_printf("Warning: Failed to write to content log: %s\n", strerror(errno)); return -1; } return sz; }
/* * Do the actual write to the open connection log file descriptor. * We prepend a timestamp here, which means that timestamps are slightly * delayed from the time of actual logging. Since we only have second * resolution that should not make any difference. */ static ssize_t log_connect_writecb(UNUSED int fd, const void *buf, size_t sz) { char timebuf[32]; time_t epoch; struct tm *utc; size_t n; time(&epoch); utc = gmtime(&epoch); n = strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S UTC ", utc); if (n == 0) { log_err_printf("Error from strftime(): buffer too small\n"); return -1; } if ((write(connect_fd, timebuf, n) == -1) || (write(connect_fd, buf, sz) == -1)) { log_err_printf("Warning: Failed to write to connect log: %s\n", strerror(errno)); } return 0; }
static int log_content_file_reopencb(void) { close(content_file_fd); content_file_fd = open(content_file_fn, O_WRONLY|O_APPEND|O_CREAT, DFLT_FILEMODE); if (content_file_fd == -1) { log_err_printf("Failed to open '%s' for writing: %s (%i)\n", content_file_fn, strerror(errno), errno); return -1; } return 0; }
/* * OpenSSL temporary DH callback which loads DH parameters from static memory. */ DH * ssl_tmp_dh_callback(UNUSED SSL *s, int is_export, int keylength) { DH *dh; if (!(dh = DH_new())) { log_err_printf("DH_new() failed\n"); return NULL; } switch (keylength) { case 512: dh->p = BN_bin2bn(dh512_p, sizeof(dh512_p), NULL); break; case 1024: dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); break; case 2048: dh->p = BN_bin2bn(dh2048_p, sizeof(dh2048_p), NULL); break; case 4096: dh->p = BN_bin2bn(dh4096_p, sizeof(dh4096_p), NULL); break; default: log_err_printf("Unhandled DH keylength %i%s\n", keylength, (is_export ? " (export)" : "")); DH_free(dh); return NULL; } dh->g = BN_bin2bn(dh_g, sizeof(dh_g), NULL); if (!dh->p || !dh->g) { log_err_printf("Failed to load DH p and g from memory\n"); DH_free(dh); return NULL; } return(dh); }
static int log_content_spec_opencb(void *fh) { log_content_ctx_t *ctx = fh; if ((ctx->u.spec.fd = privsep_client_openfile(content_clisock, ctx->u.spec.filename, 1)) == -1) { log_err_printf("Opening logspec file '%s' failed: %s (%i)\n", ctx->u.spec.filename, strerror(errno), errno); return -1; } return 0; }
static ssize_t log_cert_writecb(void *fh, const void *buf, size_t sz) { char *fn = fh; int fd; if ((fd = privsep_client_certfile(cert_clisock, fn)) == -1) { if (errno != EEXIST) { log_err_printf("Failed to open '%s': %s (%i)\n", fn, strerror(errno), errno); return -1; } return sz; } if (write(fd, buf, sz) == -1) { log_err_printf("Warning: Failed to write to '%s': %s (%i)\n", fn, strerror(errno), errno); close(fd); return -1; } close(fd); return sz; }
static int log_connect_reopencb(void) { close(connect_fd); connect_fd = open(connect_fn, O_WRONLY|O_APPEND|O_CREAT, DFLT_FILEMODE); if (connect_fd == -1) { log_err_printf("Failed to open '%s' for writing: %s\n", connect_fn, strerror(errno)); free(connect_fn); connect_fn = NULL; return -1; } return 0; }
int log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int is_request) { unsigned long prepflags = 0; if (!ctx->open) { log_err_printf("log_content_submit called on closed ctx\n"); return -1; } if (is_request) prepflags |= PREPFLAG_REQUEST; return logger_submit(content_log, ctx, prepflags, lb); }
/* * Do the actual write to the open connection log file descriptor. * We prepend a timestamp here, which means that timestamps are slightly * delayed from the time of actual logging. Since we only have second * resolution that should not make any difference. */ static ssize_t log_content_writecb(int fd, const void *buf, size_t sz) { if (!buf) { close(fd); return 0; } if (write(fd, buf, sz) == -1) { log_err_printf("Warning: Failed to write to content log: %s\n", strerror(errno)); return -1; } return 0; }
/* * Iterate over all files in a directory hierarchy, calling the callback * cb for each file, passing the filename and arg as arguments. Files and * directories beginning with a dot are skipped, symlinks are followed. */ int sys_dir_eachfile(const char *dirname, sys_dir_eachfile_cb_t cb, void *arg) { FTS *tree; FTSENT *node; char * paths[2]; paths[1] = NULL; paths[0] = strdup(dirname); if (!paths[0]) return -1; tree = fts_open(paths, FTS_NOCHDIR | FTS_LOGICAL, NULL); if (!tree) { log_err_printf("Cannot open directory '%s': %s\n", dirname, strerror(errno)); return -1; } while ((node = fts_read(tree))) { if (node->fts_level > 0 && node->fts_name[0] == '.') fts_set(tree, node, FTS_SKIP); else if (node->fts_info & FTS_F) { cb(node->fts_path, arg); } } if (errno) { log_err_printf("Error reading directory entry: %s\n", strerror(errno)); return -1; } fts_close(tree); free(paths[0]); return 0; }
/* * Converts a local gid into a printable string representation. * Returns an allocated buffer which must be freed by caller, or NULL on error. */ char * sys_group_str(gid_t gid) { static int bufsize = 0; if (!bufsize) { /* on some platforms this compiles, but does not succeed */ if ((bufsize = sysconf(_SC_GETGR_R_SIZE_MAX)) == -1) { bufsize = 64; } } char *buf, *newbuf; struct group grp, *result = NULL; int rv; char *name; if (!(buf = malloc(bufsize))) return NULL; do { rv = getgrgid_r(gid, &grp, buf, bufsize, &result); if (rv == 0) { if (result) { name = strdup(grp.gr_name); free(buf); return name; } free(buf); /* no entry found; return the integer representation */ if (asprintf(&name, "%llu", (long long) gid) < 0) { return NULL; } return name; } bufsize *= 2; if (!(newbuf = realloc(buf, bufsize))) { free(buf); return NULL; } buf = newbuf; } while (rv == ERANGE); free(buf); log_err_printf("Failed to lookup gid: %s (%i)\n", strerror(rv), rv); return NULL; }
/* * Called by OpenSSL when a new src SSL session is created. * Return 0 means remove session from internal session cache. */ #ifdef DISABLE_SSLV2_SESSION_CACHE #define MAYBE_UNUSED #else /* !DISABLE_SSLV2_SESSION_CACHE */ #define MAYBE_UNUSED UNUSED #endif /* !DISABLE_SSLV2_SESSION_CACHE */ static int pxy_ossl_sessnew_cb(MAYBE_UNUSED SSL *ssl, SSL_SESSION *sess) #undef MAYBE_UNUSED { #ifdef DEBUG_SESSION_CACHE log_dbg_printf("===> OpenSSL new session callback:\n"); if (sess) { log_dbg_print_free(ssl_session_to_str(sess)); } #endif /* DEBUG_SESSION_CACHE */ #ifdef DISABLE_SSLV2_SESSION_CACHE /* Session resumption seems to fail for SSLv2 with protocol * parsing errors, so we disable caching for SSLv2. */ if (SSL_version(ssl) == SSL2_VERSION) { log_err_printf("Warning: Session resumption denied to SSLv2" "client.\n"); return 0; } #endif /* DISABLE_SSLV2_SESSION_CACHE */ cachemgr_ssess_set(sess); return 0; }
/* * Garbage collect all the cache contents; free's up resources occupied by * certificates and sessions which are no longer valid. * This function returns after the cleanup completed and all threads are * joined. */ void cachemgr_gc(void) { pthread_t fkcrt_thr, dsess_thr, ssess_thr; int rv; /* the tgcrt cache does not need cleanup */ rv = pthread_create(&fkcrt_thr, NULL, cachemgr_gc_thread, cachemgr_fkcrt); if (rv) { log_err_printf("cachemgr_gc: pthread_create failed: %s\n", strerror(rv)); } rv = pthread_create(&ssess_thr, NULL, cachemgr_gc_thread, cachemgr_ssess); if (rv) { log_err_printf("cachemgr_gc: pthread_create failed: %s\n", strerror(rv)); } rv = pthread_create(&dsess_thr, NULL, cachemgr_gc_thread, cachemgr_dsess); if (rv) { log_err_printf("cachemgr_gc: pthread_create failed: %s\n", strerror(rv)); } rv = pthread_join(fkcrt_thr, NULL); if (rv) { log_err_printf("cachemgr_gc: pthread_join failed: %s\n", strerror(rv)); } rv = pthread_join(ssess_thr, NULL); if (rv) { log_err_printf("cachemgr_gc: pthread_join failed: %s\n", strerror(rv)); } rv = pthread_join(dsess_thr, NULL); if (rv) { log_err_printf("cachemgr_gc: pthread_join failed: %s\n", strerror(rv)); } }
/* * Main entry point. */ int main(int argc, char *argv[]) { const char *argv0; int ch; opts_t *opts; char *natengine; int pidfd = -1; int rv = EXIT_FAILURE; argv0 = argv[0]; opts = opts_new(); natengine = strdup(nat_getdefaultname()); while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z "k:c:C:K:t:OPs:e:Eu:j:p:l:L:S:dDVh")) != -1) { switch (ch) { case 'c': if (opts->cacrt) X509_free(opts->cacrt); opts->cacrt = ssl_x509_load(optarg); if (!opts->cacrt) { fprintf(stderr, "%s: error loading CA " "cert from '%s':\n", argv0, optarg); if (errno) { fprintf(stderr, "%s\n", strerror(errno)); } else { ERR_print_errors_fp(stderr); } exit(EXIT_FAILURE); } ssl_x509_refcount_inc(opts->cacrt); sk_X509_insert(opts->chain, opts->cacrt, 0); if (!opts->cakey) { opts->cakey = ssl_key_load(optarg); } #ifndef OPENSSL_NO_DH if (!opts->dh) { opts->dh = ssl_dh_load(optarg); } #endif /* !OPENSSL_NO_DH */ break; case 'k': if (opts->cakey) EVP_PKEY_free(opts->cakey); opts->cakey = ssl_key_load(optarg); if (!opts->cakey) { fprintf(stderr, "%s: error loading CA " "key from '%s':\n", argv0, optarg); if (errno) { fprintf(stderr, "%s\n", strerror(errno)); } else { ERR_print_errors_fp(stderr); } exit(EXIT_FAILURE); } if (!opts->cacrt) { opts->cacrt = ssl_x509_load(optarg); if (opts->cacrt) { ssl_x509_refcount_inc( opts->cacrt); sk_X509_insert(opts->chain, opts->cacrt, 0); } } #ifndef OPENSSL_NO_DH if (!opts->dh) { opts->dh = ssl_dh_load(optarg); } #endif /* !OPENSSL_NO_DH */ break; case 'C': if (ssl_x509chain_load(NULL, &opts->chain, optarg) == -1) { fprintf(stderr, "%s: error loading " "chain from '%s':\n", argv0, optarg); if (errno) { fprintf(stderr, "%s\n", strerror(errno)); } else { ERR_print_errors_fp(stderr); } exit(EXIT_FAILURE); } break; case 'K': if (opts->key) EVP_PKEY_free(opts->key); opts->key = ssl_key_load(optarg); if (!opts->key) { fprintf(stderr, "%s: error loading lea" "f key from '%s':\n", argv0, optarg); if (errno) { fprintf(stderr, "%s\n", strerror(errno)); } else { ERR_print_errors_fp(stderr); } exit(EXIT_FAILURE); } #ifndef OPENSSL_NO_DH if (!opts->dh) { opts->dh = ssl_dh_load(optarg); } #endif /* !OPENSSL_NO_DH */ break; case 't': if (!sys_isdir(optarg)) { fprintf(stderr, "%s: '%s' is not a " "directory\n", argv0, optarg); exit(EXIT_FAILURE); } if (opts->tgcrtdir) free(opts->tgcrtdir); opts->tgcrtdir = strdup(optarg); break; case 'O': opts->deny_ocsp = 1; break; case 'P': opts->passthrough = 1; break; #ifndef OPENSSL_NO_DH case 'g': if (opts->dh) DH_free(opts->dh); opts->dh = ssl_dh_load(optarg); if (!opts->dh) { fprintf(stderr, "%s: error loading DH " "params from '%s':\n", argv0, optarg); if (errno) { fprintf(stderr, "%s\n", strerror(errno)); } else { ERR_print_errors_fp(stderr); } exit(EXIT_FAILURE); } break; #endif /* !OPENSSL_NO_DH */ #ifndef OPENSSL_NO_ECDH case 'G': { EC_KEY *ec; if (opts->ecdhcurve) free(opts->ecdhcurve); if (!(ec = ssl_ec_by_name(optarg))) { fprintf(stderr, "%s: unknown curve " "'%s'\n", argv0, optarg); exit(EXIT_FAILURE); } EC_KEY_free(ec); opts->ecdhcurve = strdup(optarg); break; } #endif /* !OPENSSL_NO_ECDH */ #ifdef SSL_OP_NO_COMPRESSION case 'Z': opts->sslcomp = 0; break; #endif /* SSL_OP_NO_COMPRESSION */ case 's': if (opts->ciphers) free(opts->ciphers); opts->ciphers = strdup(optarg); break; case 'e': free(natengine); natengine = strdup(optarg); break; case 'E': nat_list_engines(); exit(EXIT_SUCCESS); break; case 'u': if (opts->dropuser) free(opts->dropuser); opts->dropuser = strdup(optarg); break; case 'p': if (opts->pidfile) free(opts->pidfile); opts->pidfile = strdup(optarg); break; case 'j': if (opts->jaildir) free(opts->jaildir); opts->jaildir = strdup(optarg); break; case 'l': if (opts->connectlog) free(opts->connectlog); opts->connectlog = strdup(optarg); break; case 'L': if (opts->contentlog) free(opts->contentlog); opts->contentlog = strdup(optarg); opts->contentlogdir = 0; break; case 'S': if (opts->contentlog) free(opts->contentlog); opts->contentlog = strdup(optarg); opts->contentlogdir = 1; break; case 'd': opts->detach = 1; break; case 'D': log_dbg_mode(LOG_DBG_MODE_ERRLOG); opts->debug = 1; break; case 'V': main_version(); exit(EXIT_SUCCESS); case 'h': main_usage(); exit(EXIT_SUCCESS); case '?': exit(EXIT_FAILURE); default: main_usage(); exit(EXIT_FAILURE); } } argc -= optind; argv += optind; opts->spec = proxyspec_parse(&argc, &argv, natengine); /* usage checks */ if (opts->detach && OPTS_DEBUG(opts)) { fprintf(stderr, "%s: -d and -D are mutually exclusive.\n", argv0); exit(EXIT_FAILURE); } if (!opts->spec) { fprintf(stderr, "%s: no proxyspec specified.\n", argv0); exit(EXIT_FAILURE); } for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) { if (spec->connect_addrlen || spec->sni_port) continue; if (!spec->natengine) { fprintf(stderr, "%s: no supported NAT engines " "on this platform.\n" "Only static addr and SNI proxyspecs " "supported.\n", argv0); exit(EXIT_FAILURE); } if (spec->listen_addr.ss_family == AF_INET6 && !nat_ipv6ready(spec->natengine)) { fprintf(stderr, "%s: IPv6 not supported by '%s'\n", argv0, spec->natengine); exit(EXIT_FAILURE); } spec->natlookup = nat_getlookupcb(spec->natengine); spec->natsocket = nat_getsocketcb(spec->natengine); } if (opts_has_ssl_spec(opts)) { if ((opts->cacrt || !opts->tgcrtdir) && !opts->cakey) { fprintf(stderr, "%s: no CA key specified (-k).\n", argv0); exit(EXIT_FAILURE); } if (opts->cakey && !opts->cacrt) { fprintf(stderr, "%s: no CA cert specified (-c).\n", argv0); exit(EXIT_FAILURE); } if (opts->cakey && opts->cacrt && (X509_check_private_key(opts->cacrt, opts->cakey) != 1)) { fprintf(stderr, "%s: CA cert does not match key.\n", argv0); ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } } /* prevent multiple instances running */ if (opts->pidfile) { pidfd = sys_pidf_open(opts->pidfile); if (pidfd == -1) { fprintf(stderr, "%s: cannot open PID file '%s' " "- process already running?\n", argv0, opts->pidfile); exit(EXIT_FAILURE); } } /* dynamic defaults */ if (!opts->ciphers) { opts->ciphers = strdup("ALL:-aNULL"); if (!opts->ciphers) { fprintf(stderr, "%s: out of memory.\n", argv0); exit(EXIT_FAILURE); } } if (!opts->jaildir && (geteuid() == 0) && !opts->contentlogdir) { opts->jaildir = strdup("/var/empty"); } if (!opts->dropuser && !geteuid() && !getuid() && !opts->contentlogdir) { opts->dropuser = strdup("nobody"); } if (opts_has_ssl_spec(opts) && !opts->key) { opts->key = ssl_key_genrsa(1024); if (!opts->key) { fprintf(stderr, "%s: error generating RSA key:\n", argv0); ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } if (OPTS_DEBUG(opts)) { log_dbg_printf("Generated RSA key for leaf certs.\n"); } } /* debugging */ if (OPTS_DEBUG(opts)) { main_version(); log_dbg_printf("proxyspecs:\n"); for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) { char *lbuf, *cbuf = NULL; lbuf = sys_sockaddr_str((struct sockaddr *) &spec->listen_addr, spec->listen_addrlen); if (spec->connect_addrlen) { cbuf = sys_sockaddr_str((struct sockaddr *) &spec->connect_addr, spec->connect_addrlen); } if (spec->sni_port) { asprintf(&cbuf, "sni %i", spec->sni_port); } log_dbg_printf("- %s %s %s %s\n", lbuf, (spec->ssl ? "ssl" : "tcp"), (spec->http ? "http" : "plain"), (spec->natengine ? spec->natengine : cbuf)); if (lbuf) free(lbuf); if (cbuf) free(cbuf); } if (opts->cacrt) { char *subj = ssl_x509_subject(opts->cacrt); log_dbg_printf("Loaded CA: '%s'\n", subj); free(subj); #ifdef DEBUG_CERTIFICATE log_dbg_print_free(ssl_x509_to_str(opts->cacrt)); log_dbg_print_free(ssl_x509_to_pem(opts->cacrt)); #endif /* DEBUG_CERTIFICATE */ } else { log_dbg_printf("No CA loaded.\n"); } } /* * Initialize as much as possible before daemon() in order to be * able to provide direct feedback to the user when failing. */ if (cachemgr_preinit() == -1) { fprintf(stderr, "%s: failed to preinit cachemgr.\n", argv0); exit(EXIT_FAILURE); } if (log_preinit(opts) == -1) { fprintf(stderr, "%s: failed to preinit logging.\n", argv0); exit(EXIT_FAILURE); } if (nat_preinit() == -1) { fprintf(stderr, "%s: failed to preinit NAT lookup.\n", argv0); exit(EXIT_FAILURE); } /* Bind listeners before dropping privileges */ proxy_ctx_t *proxy = proxy_new(opts); if (!proxy) { fprintf(stderr, "%s: failed to initialize proxy.\n", argv0); exit(EXIT_FAILURE); } /* Drop privs, chroot, detach from TTY */ if (sys_privdrop(opts->dropuser, opts->jaildir) == -1) { fprintf(stderr, "%s: failed to drop privileges: %s\n", argv0, strerror(errno)); exit(EXIT_FAILURE); } if (opts->detach) { if (OPTS_DEBUG(opts)) { log_dbg_printf("Detaching from TTY, see syslog for " "errors after this point\n"); } if (daemon(1, 0) == -1) { fprintf(stderr, "%s: failed to detach from TTY: %s\n", argv0, strerror(errno)); exit(EXIT_FAILURE); } log_err_mode(LOG_ERR_MODE_SYSLOG); ssl_reinit(); } /* Post-privdrop/chroot/detach initialization, thread spawning */ if (log_init(opts) == -1) { fprintf(stderr, "%s: failed to init log facility.\n", argv0); goto out_log_failed; } if (opts->pidfile && (sys_pidf_write(pidfd) == -1)) { log_err_printf("Failed to write PID to PID file '%s': %s\n", opts->pidfile, strerror(errno)); goto out_pidwrite_failed; } if (cachemgr_init() == -1) { log_err_printf("Failed to init cache manager.\n"); goto out_cachemgr_failed; } if (nat_init() == -1) { log_err_printf("Failed to init NAT state table lookup.\n"); goto out_nat_failed; } if (opts->tgcrtdir) { sys_dir_eachfile(opts->tgcrtdir, main_loadtgcrt, opts); } rv = EXIT_SUCCESS; proxy_run(proxy); proxy_free(proxy); nat_fini(); out_nat_failed: cachemgr_fini(); out_cachemgr_failed: if (opts->pidfile) { sys_pidf_close(pidfd, opts->pidfile); } out_pidwrite_failed: log_fini(); out_log_failed: opts_free(opts); ssl_fini(); return rv; }
/* * Initialize OpenSSL and verify the random number generator works. * Returns -1 on failure, 0 on success. */ int ssl_init(void) { #ifndef PURIFY int fd; #endif /* !PURIFY */ char buf[256]; if (ssl_initialized) return 0; /* general initialization */ SSL_library_init(); #ifdef PURIFY CRYPTO_umalloc_init(); CRYPTO_mem_ctrl(CRYPTO_MEM_CHECK_ON); #endif /* PURIFY */ SSL_load_error_strings(); OpenSSL_add_all_algorithms(); /* thread-safety */ #ifdef OPENSSL_THREADS ssl_mutex_num = CRYPTO_num_locks(); ssl_mutex = umalloc(ssl_mutex_num * sizeof(*ssl_mutex)); int i; for (i = 0; i < ssl_mutex_num; i++) { pthread_mutex_init(&ssl_mutex[i], NULL); } CRYPTO_set_locking_callback(ssl_thr_locking_cb); CRYPTO_set_dynlock_create_callback(ssl_thr_dyn_create_cb); CRYPTO_set_dynlock_lock_callback(ssl_thr_dyn_lock_cb); CRYPTO_set_dynlock_destroy_callback(ssl_thr_dyn_destroy_cb); #ifdef OPENSSL_NO_THREADID CRYPTO_set_id_callback(ssl_thr_id_cb); #else /* !OPENSSL_NO_THREADID */ CRYPTO_THREADID_set_callback(ssl_thr_id_cb); #endif /* !OPENSSL_NO_THREADID */ #endif /* OPENSSL_THREADS */ /* randomness */ #ifndef PURIFY if ((fd = open("/dev/urandom", O_RDONLY)) == -1) { log_err_printf("Error opening /dev/urandom for reading: %s\n", strerror(errno)); return -1; } while (!RAND_status()) { if (read(fd, buf, sizeof(buf)) == -1) { log_err_printf("Error reading from /dev/urandom: %s\n", strerror(errno)); close(fd); return -1; } RAND_seed(buf, sizeof(buf)); } close(fd); if (!RAND_poll()) { log_err_printf("RAND_poll() failed.\n"); return -1; } #else /* PURIFY */ log_err_printf("Warning: not seeding OpenSSL RAND due to PURITY!\n"); memset(buf, 0, sizeof(buf)); while (!RAND_status()) { RAND_seed(buf, sizeof(buf)); } #endif /* PURIFY */ #ifdef USE_FOOTPRINT_HACKS /* HACK: disable compression by zeroing the global comp algo stack. * This lowers the per-connection memory footprint by ~500k. */ STACK_OF(SSL_COMP)* comp_methods = SSL_COMP_get_compression_methods(); sk_SSL_COMP_zero(comp_methods); #endif /* USE_FOOTPRINT_HACKS */ ssl_initialized = 1; return 0; }
log_content_format_pathspec(const char *logspec, char *srchost, char *srcport, char *dsthost, char *dstport, char *exec_path, char *user, char *group) { /* set up buffer to hold our generated file path */ size_t path_buflen = PATH_BUF_INC; char *path_buf = malloc(path_buflen); if (path_buf == NULL) { log_err_printf("failed to allocate path buffer\n"); return NULL; } /* initialize the buffer as an empty C string */ path_buf[0] = '\0'; /* iterate over format specifiers */ size_t path_len = 0; for (const char *p = logspec; *p != '\0'; p++) { const char *elem = NULL; size_t elem_len = 0; const char iso8601[] = "%Y%m%dT%H%M%SZ"; char timebuf[24]; /* sized for ISO 8601 format */ char addrbuf[INET6_ADDRSTRLEN + 8]; /* [host]:port */ /* parse the format string and generate the next path element */ switch (*p) { case '%': p++; /* handle format specifiers. */ switch (*p) { case '\0': /* unexpected eof; backtrack and discard * invalid format spec */ p--; elem_len = 0; break; case '%': elem = p; elem_len = 1; break; case 'd': if (snprintf(addrbuf, sizeof(addrbuf), "%s,%s", dsthost, dstport) < 0) { addrbuf[0] = '?'; addrbuf[1] = '\0'; } elem = addrbuf; elem_len = strlen(addrbuf); break; case 'D': elem = dsthost; elem_len = strlen(dsthost); break; case 'p': elem = dstport; elem_len = strlen(dstport); break; case 's': if (snprintf(addrbuf, sizeof(addrbuf), "%s,%s", srchost, srcport) < 0) { addrbuf[0] = '?'; addrbuf[1] = '\0'; } elem = addrbuf; elem_len = strlen(addrbuf); break; case 'S': elem = srchost; elem_len = strlen(srchost); break; case 'q': elem = srcport; elem_len = strlen(srcport); break; case 'x': if (exec_path) { char *match = exec_path; while ((match = strchr(match, '/')) != NULL) { match++; elem = match; } elem_len = elem ? strlen(elem) : 0; } else { elem_len = 0; } break; case 'X': elem = exec_path; elem_len = exec_path ? strlen(exec_path) : 0; break; case 'u': elem = user; elem_len = user ? strlen(user) : 0; break; case 'g': elem = group; elem_len = group ? strlen(group) : 0; break; case 'T': { time_t epoch; struct tm *utc; time(&epoch); utc = gmtime(&epoch); strftime(timebuf, sizeof(timebuf), iso8601, utc); elem = timebuf; elem_len = sizeof(timebuf); break; }} break; default: elem = p; elem_len = 1; break; } if (elem_len > 0) { /* growing the buffer to fit elem_len + terminating \0 */ if (path_buflen - path_len < elem_len + 1) { /* Grow in PATH_BUF_INC chunks. * Note that the use of `PATH_BUF_INC' provides * our guaranteed space for a trailing '\0' */ path_buflen += elem_len + PATH_BUF_INC; char *newbuf = realloc(path_buf, path_buflen); if (newbuf == NULL) { log_err_printf("failed to reallocate" " path buffer\n"); free(path_buf); return NULL; } path_buf = newbuf; } strncat(path_buf, elem, elem_len); path_len += elem_len; } } /* apply terminating NUL */ assert(path_buflen > path_len); path_buf[path_len] = '\0'; return path_buf; }
int log_content_open(log_content_ctx_t **pctx, opts_t *opts, char *srchost, char *srcport, char *dsthost, char *dstport, char *exec_path, char *user, char *group) { log_content_ctx_t *ctx; if (*pctx) return 0; *pctx = malloc(sizeof(log_content_ctx_t)); if (!*pctx) 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; } srchost_clean = sys_ip46str_sanitize(srchost); if (!srchost_clean) { log_err_printf("Failed to sanitize srchost\n"); goto errout; } dsthost_clean = sys_ip46str_sanitize(dsthost); if (!dsthost_clean) { log_err_printf("Failed to sanitize dsthost\n"); 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; } dsthost_clean = sys_ip46str_sanitize(dsthost); if (!dsthost_clean) { log_err_printf("Failed to sanitize dsthost\n"); free(srchost_clean); goto errout; } 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; } } 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; } } /* submit an open event */ if (logger_open(content_log, ctx) == -1) goto errout; ctx->open = 1; return 0; errout: free(ctx); *pctx = NULL; return -1; }