/* Code and variables that is common to both fork and select-based * servers. * * No code here should assume whether sockets are blocking or not. **/ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "common.h" #include "probe.h" #include "log.h" #include "sslh-conf.h" #include "proxyprotocol.h" #if HAVE_LIBCAP #include #include #endif /* Added to make the code compilable under CYGWIN * */ #ifndef SA_NOCLDWAIT #define SA_NOCLDWAIT 0 #endif /* Make use of systemd socket activation * */ #ifdef SYSTEMD #include #endif #ifdef HAVE_LIBBSD #include #endif /* * Settings that depend on the command line or the config file */ struct sslhcfg_item cfg; struct addrinfo *addr_listen = NULL; /* what addresses do we listen to? */ #ifdef HAVE_LIBWRAP #include int allow_severity =0, deny_severity = 0; #endif typedef enum { CR_DIE, CR_WARN } CR_ACTION; /* check result and die, printing the offending address and error */ void check_res_dump(CR_ACTION act, int res, struct addrinfo *addr, char* syscall) { char buf[NI_MAXHOST]; if (res == -1) { print_message(msg_system_error, "%s:%s: %s\n", sprintaddr(buf, sizeof(buf), addr), syscall, strerror(errno)); if (act == CR_DIE) exit(1); } } int get_fd_sockets(struct listen_endpoint *sockfd[]) { int sd = 0; #ifdef SYSTEMD sd = sd_listen_fds(0); if (sd < 0) { print_message(msg_system_error, "sd_listen_fds(): %s\n", strerror(-sd)); exit(1); } if (sd > 0) { int i; *sockfd = malloc(sd * sizeof(*sockfd[0])); CHECK_ALLOC(*sockfd, "malloc"); for (i = 0; i < sd; i++) { (*sockfd)[i].socketfd = SD_LISTEN_FDS_START + i; (*sockfd)[i].type = SOCK_STREAM; } } #endif return sd; } /* Set TCP_FASTOPEN on listening socket if all client protocols support it */ int make_listen_tfo(int s) { int i, qlen = 5; /* Don't do it if not supported */ if (!TCP_FASTOPEN) return 0; /* Don't do it if any protocol does not specify it */ for (i = 0; i < cfg.protocols_len; i++) { if (! cfg.protocols[i].tfo_ok) return 0; } return setsockopt(s, SOL_SOCKET, TCP_FASTOPEN, (char*)&qlen, sizeof(qlen)); } /* Starts listening on a single address * Returns a socket filehandle, or dies with message in case of major error */ int listen_single_addr(struct addrinfo* addr, int keepalive, int udp) { struct sockaddr_storage *saddr; int sockfd, one, res; saddr = (struct sockaddr_storage*)addr->ai_addr; sockfd = socket(saddr->ss_family, udp ? SOCK_DGRAM : SOCK_STREAM, 0); check_res_dump(CR_DIE, sockfd, addr, "socket"); one = 1; res = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&one, sizeof(one)); check_res_dump(CR_DIE, res, addr, "setsockopt(SO_REUSEADDR)"); res = make_listen_tfo(sockfd); check_res_dump(CR_WARN, res, addr, "setsockopt(TCP_FASTOPEN)"); if (keepalive) { res = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one)); check_res_dump(CR_DIE, res, addr, "setsockopt(SO_KEEPALIVE)"); } if (IP_FREEBIND) { res = setsockopt(sockfd, IPPROTO_IP, IP_FREEBIND, (char*)&one, sizeof(one)); check_res_dump(CR_WARN, res, addr, "setsockopt(IP_FREEBIND)"); } if (addr->ai_addr->sa_family == AF_INET6) { res = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one)); check_res_dump(CR_WARN, res, addr, "setsockopt(IPV6_V6ONLY)"); } res = bind(sockfd, addr->ai_addr, addr->ai_addrlen); check_res_dump(CR_DIE, res, addr, "bind"); if (!udp) { res = listen (sockfd, 50); check_res_dump(CR_DIE, res, addr, "listen"); } return sockfd; } /* Start listening internet sockets for configuration entry 'index' * OUT: *sockfd[]: pointer to array of listen_endpoint object; we append new * endpoints to that array * IN: num_addr: how many entries are in sockfd[] * *cfg: configuration data for the endpoint we are adding * Return: new value of num_addr * */ static int start_listen_inet(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg) { struct addrinfo *addr, *start_addr; char buf[NI_MAXHOST]; int res; res = resolve_split_name(&start_addr, cfg->host, cfg->port); if (res) exit(4); for (addr = start_addr; addr; addr = addr->ai_next) { num_addr++; *sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0])); (*sockfd)[num_addr-1].socketfd = listen_single_addr(addr, cfg->keepalive, cfg->is_udp); (*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM; (*sockfd)[num_addr-1].family = AF_INET; print_message(msg_config, "%d:\t%s\t[%s] [%s]\n", (*sockfd)[num_addr-1].socketfd, sprintaddr(buf, sizeof(buf), addr), cfg->keepalive ? "keepalive" : "", cfg->is_udp ? "udp" : ""); } freeaddrinfo(start_addr); return num_addr; } /* Same, but for UNIX sockets */ static int start_listen_unix(struct listen_endpoint *sockfd[], int num_addr, struct sslhcfg_listen_item* cfg) { int fd = socket(AF_UNIX, cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM, 0); CHECK_RES_DIE(fd, "socket(AF_UNIX)"); int res = unlink(cfg->host); if ((res == -1) && (errno != ENOENT)) { print_message(msg_config_error, "unlink unix socket `%s':%d:%s\n", cfg->host, errno, strerror(errno)); exit(4); } struct sockaddr_un sun; sun.sun_family = AF_UNIX; strncpy(sun.sun_path, cfg->host, sizeof(sun.sun_path)-1); printf("binding [%s]\n", sun.sun_path); res = bind(fd, (struct sockaddr*)&sun, sizeof(sun)); CHECK_RES_DIE(res, "bind(AF_UNIX)"); res = listen(fd, 50); num_addr++; *sockfd = realloc(*sockfd, num_addr * sizeof(*sockfd[0])); (*sockfd)[num_addr-1].socketfd = fd; (*sockfd)[num_addr-1].type = cfg->is_udp ? SOCK_DGRAM : SOCK_STREAM; (*sockfd)[num_addr-1].family = AF_INET; return num_addr; } /* Starts listening sockets on specified addresses. * OUT: *sockfd[] pointer to newly-allocated array of listen_endpoint objects * Returns number of addresses bound */ int start_listen_sockets(struct listen_endpoint *sockfd[]) { int i; int num_addr = 0, sd_socks = 0; sd_socks = get_fd_sockets(sockfd); if (sd_socks > 0) { return sd_socks; } *sockfd = NULL; print_message(msg_config, "Listening to:\n"); for (i = 0; i < cfg.listen_len; i++) { if (cfg.listen[i].is_unix) { num_addr = start_listen_unix(sockfd, num_addr, &cfg.listen[i]); } else { num_addr = start_listen_inet(sockfd, num_addr, &cfg.listen[i]); } } return num_addr; } /* returns 1 if given address is on the local machine: iterate through all * network interfaces and check their addresses */ int is_same_machine(struct addrinfo* from) { struct ifaddrs *ifaddrs_p = NULL, *ifa; int match = 0; getifaddrs(&ifaddrs_p); for (ifa = ifaddrs_p; ifa != NULL; ifa = ifa->ifa_next) { if (!ifa->ifa_addr) continue; if (from->ai_addr->sa_family == ifa->ifa_addr->sa_family) { int family = ifa->ifa_addr->sa_family; if (family == AF_INET) { struct sockaddr_in *from_addr = (struct sockaddr_in*)from->ai_addr; struct sockaddr_in *ifa_addr = (struct sockaddr_in*)ifa->ifa_addr; if (from_addr->sin_addr.s_addr == ifa_addr->sin_addr.s_addr) { match = 1; break; } } else if (family == AF_INET6) { struct sockaddr_in6 *from_addr = (struct sockaddr_in6*)from->ai_addr; struct sockaddr_in6 *ifa_addr = (struct sockaddr_in6*)ifa->ifa_addr; if (!memcmp(from_addr->sin6_addr.s6_addr, ifa_addr->sin6_addr.s6_addr, 16)) { match = 1; break; } } } } freeifaddrs(ifaddrs_p); return match; } /* Transparent proxying: bind the peer address of fd to the peer address of * fd_from */ #ifndef IP_TRANSPARENT #define IP_TRANSPARENT 19 #endif #ifndef IP_BIND_ADDRESS_NO_PORT #define IP_BIND_ADDRESS_NO_PORT 24 #endif int bind_peer(int fd, int fd_from) { struct addrinfo from; struct sockaddr_storage ss; int res, enable = 1, disable = 0; memset(&from, 0, sizeof(from)); from.ai_addr = (struct sockaddr*)&ss; from.ai_addrlen = sizeof(ss); /* getpeername can fail with ENOTCONN if connection was dropped before we * got here */ res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen); CHECK_RES_RETURN(res, "getpeername", res); /* if the destination is the same machine, there's no need to do bind */ if (is_same_machine(&from)) return 0; #ifndef IP_BINDANY /* use IP_TRANSPARENT */ res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &enable, sizeof(enable)); CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT"); res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR"); #else if (from.ai_addr->sa_family==AF_INET) { /* IPv4 */ res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &enable, sizeof(enable)); CHECK_RES_RETURN(res, "setsockopt IP_BINDANY", res); #ifdef IPV6_BINDANY } else { /* IPv6 */ res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &enable, sizeof(enable)); CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY", res); #endif /* IPV6_BINDANY */ } #endif /* IP_TRANSPARENT / IP_BINDANY */ res = bind(fd, from.ai_addr, from.ai_addrlen); if (res == -1) { if (errno != EADDRINUSE) { print_message(msg_system_error, "%s:%d:%s:%d:%s\n", __FILE__, __LINE__, "bind", errno, strerror(errno)); return res; } res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &disable, sizeof(disable)); CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR"); res = setsockopt(fd, IPPROTO_IP, IP_BIND_ADDRESS_NO_PORT, &enable, sizeof(enable)); CHECK_RES_RETURN(res, "setsockopt IP_BIND_ADDRESS_NO_PORT", res); ((struct sockaddr_in *)from.ai_addr)->sin_port = 0; res = bind(fd, from.ai_addr, from.ai_addrlen); CHECK_RES_RETURN(res, "bind", res); /* * There was a serious problem, when daisy-chaining programs using the same * ip transparent mechanism, as sslh uses. stunnel was mentioned in a previous * comment. This problem should now be solved through the two methods, getting * a connection established: * In the first try, SO_REUSEADDR is set to socket, which will allow the same * IP-address:port tuple, as it is used by another application. The check for * inconsistency with other connections (same 4-value-tupel) is done at the * moment, when the connection gets established. * If that fails, SO_REUSEADDR gets removed and IP_BIND_ADDRESS_NO_PORT get set. * This will search for a free port, which will not collide with current * connections. Read more in this excellent blog-post: * https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections * The problem will still appear, if the another application in the daisy-chain * does not use similar mechanisms. In that case you must either pull this * application at the beginning of the chain, or get it fixed. */ } return 0; } /* Make the file descriptor non-block */ int set_nonblock(int fd) { int flags; flags = fcntl(fd, F_GETFL); CHECK_RES_RETURN(flags, "fcntl", -1); flags |= O_NONBLOCK; flags = fcntl(fd, F_SETFL, flags); CHECK_RES_RETURN(flags, "fcntl", -1); return flags; } /* Connects to INET/INET6 domain sockets and return a fd */ static int connect_inet(struct connection *cnx, int fd_from, connect_blocking blocking) { struct addrinfo *a, from; struct sockaddr_storage ss; char buf[NI_MAXHOST]; int fd, res, one; int transparent = cnx->proto->transparent || cfg.transparent; memset(&from, 0, sizeof(from)); from.ai_addr = (struct sockaddr*)&ss; from.ai_addrlen = sizeof(ss); res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen); CHECK_RES_RETURN(res, "getpeername", res); if (cnx->proto->resolve_on_forward) { resolve_split_name(&(cnx->proto->saddr), cnx->proto->host, cnx->proto->port); } for (a = cnx->proto->saddr; a; a = a->ai_next) { /* When transparent or using proxyprotocol, make sure both * connections use the same address family (e.g. IP4 on both sides) */ if ((transparent || cnx->proto->proxyprotocol_is_present) && (a->ai_family != from.ai_addr->sa_family)) continue; print_message(msg_connections_try, "trying to connect to %s family %d len %d\n", sprintaddr(buf, sizeof(buf), a), a->ai_addr->sa_family, a->ai_addrlen); /* XXX Needs to match ai_family from fd_from when being transparent! */ fd = socket(a->ai_family, SOCK_STREAM, 0); if (fd == -1) { print_message(msg_connections_error, "forward to %s failed:socket: %s\n", cnx->proto->name, strerror(errno)); } else { one = 1; setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN_CONNECT, &one, sizeof(one)); /* no need to check return value; if it's not supported, that's okay */ if (blocking == NON_BLOCKING) { set_nonblock(fd); } if (transparent) { res = bind_peer(fd, fd_from); if (res == -1) close(fd); CHECK_RES_RETURN(res, "bind_peer", res); } res = connect(fd, a->ai_addr, a->ai_addrlen); /* EINPROGRESS indicates it might take time. If it eventually * fails, it'll be caught as a failed read */ if ((res == -1) && (errno != EINPROGRESS)) { print_message(msg_connections_error, "forward to %s failed:connect: %s\n", cnx->proto->name, strerror(errno)); close(fd); continue; /* Try the next address */ } if (cnx->proto->keepalive) { res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char*)&one, sizeof(one)); CHECK_RES_RETURN(res, "setsockopt(SO_KEEPALIVE)", res); } return fd; } } return -1; } /* Connects to AF_UNIX domain sockets */ static int connect_unix(struct connection *cnx, int fd_from, connect_blocking blocking) { struct sockaddr_storage ss; struct sockaddr_un* sun = (struct sockaddr_un*)&ss; int fd = socket(AF_UNIX, SOCK_STREAM, 0); sun->sun_family = AF_UNIX; strncpy(sun->sun_path, cnx->proto->host, sizeof(sun->sun_path)-1); int res = connect(fd, (struct sockaddr*)sun, sizeof(*sun)); CHECK_RES_RETURN(res, "connect", res); if (blocking == NON_BLOCKING) { set_nonblock(fd); } return fd; } /* * Connect to the first backend server address that works and updates the *cnx * object accordingly (in cnx->q[1].fd). Set that to -1 in case of failure. * * If transparent proxying is on, use fd_from peer address on external address * of new file descriptor. * If proxyprotocol is used, write header on new backend server connection * */ void connect_addr(struct connection *cnx, int fd_from, connect_blocking blocking) { int fd; if (cnx->proto->is_unix) { fd = connect_unix(cnx, fd_from, blocking); } else { fd = connect_inet(cnx, fd_from, blocking); } cnx->q[1].fd = fd; if (cnx->proto->proxyprotocol_is_present) { pp_write_header(cnx->proto->proxyprotocol, cnx); /* If pp_write_header() fails, it already logs a message and there is * nothing much we can do. The server side will probably close the * connection */ } } /* Store some data to write to the queue later */ int defer_write(struct queue *q, void* data, ssize_t data_size) { char *p; ptrdiff_t data_offset = q->deferred_data - q->begin_deferred_data; print_message(msg_fd, "writing deferred on fd %d\n", q->fd); p = realloc(q->begin_deferred_data, data_offset + q->deferred_data_size + data_size); CHECK_ALLOC(p, "realloc"); q->begin_deferred_data = p; q->deferred_data = p + data_offset; p += data_offset + q->deferred_data_size; q->deferred_data_size += (int)data_size; memcpy(p, data, data_size); return 0; } /* Store some data to write *before* what's already in the queue */ int defer_write_before(struct queue *q, void* data, ssize_t data_size) { char *p; print_message(msg_fd, "writing deferred to beginning on fd %d\n", q->fd); p = malloc(q->deferred_data_size + data_size); CHECK_ALLOC(p, "malloc"); memcpy(p, data, data_size); memcpy(p + data_size, q->deferred_data, q->deferred_data_size); free(q->begin_deferred_data); q->begin_deferred_data = p; q->deferred_data = p; q->deferred_data_size += (int)data_size; return 0; } /* tries to flush some of the data for specified queue * Upon success, the number of bytes written is returned. * Upon failure, -1 returned (e.g. connexion closed) * */ int flush_deferred(struct queue *q) { ssize_t n; print_message(msg_fd, "flushing deferred data to fd %d\n", q->fd); n = write(q->fd, q->deferred_data, q->deferred_data_size); if (n == -1) return (int)n; if (n == q->deferred_data_size) { /* All has been written -- release the memory */ free(q->begin_deferred_data); q->begin_deferred_data = NULL; q->deferred_data = NULL; q->deferred_data_size = 0; } else { /* There is data left */ q->deferred_data += n; q->deferred_data_size -= (int)n; } return (int)n; } void init_cnx(struct connection *cnx) { memset(cnx, 0, sizeof(*cnx)); cnx->q[0].fd = -1; cnx->q[1].fd = -1; cnx->proto = NULL; } void dump_connection(struct connection *cnx) { print_message(msg_int_error, "type: %s\n", cnx->type == SOCK_DGRAM ? "UDP" : "TCP"); print_message(msg_int_error, "state: %d\n", cnx->state); print_message(msg_int_error, "0: fd %d, %d deferred\n", cnx->q[0].fd, cnx->q[0].deferred_data_size); hexdump(msg_int_error, cnx->q[0].deferred_data, cnx->q[0].deferred_data_size); print_message(msg_int_error, "1: fd %d, %d deferred\n", cnx->q[1].fd, cnx->q[1].deferred_data_size); hexdump(msg_int_error, cnx->q[1].deferred_data, cnx->q[1].deferred_data_size); } /* * moves data from one fd to other * * returns number of bytes copied if success * returns 0 (FD_CNXCLOSED) if incoming socket closed * returns FD_NODATA if no data was available * returns FD_STALLED if data was read, could not be written, and has been * stored in temporary buffer. */ int fd2fd(struct queue *target_q, struct queue *from_q) { char buffer[BUFSIZ]; int target, from; ssize_t size_r, size_w; target = target_q->fd; from = from_q->fd; size_r = read(from, buffer, sizeof(buffer)); if (size_r == -1) { switch (errno) { case EAGAIN: return FD_NODATA; case ECONNRESET: case ENOTSOCK: case EPIPE: return FD_CNXCLOSED; } } CHECK_RES_RETURN(size_r, "read",FD_CNXCLOSED); if (size_r == 0) return FD_CNXCLOSED; size_w = write(target, buffer, size_r); /* process -1 when we know how to deal with it */ if (size_w == -1) { switch (errno) { case EAGAIN: /* write blocked: Defer data */ defer_write(target_q, buffer, size_r); return FD_STALLED; case ECONNRESET: case EPIPE: /* remote end closed -- drop the connection */ return FD_CNXCLOSED; } } else if (size_w < size_r) { /* incomplete write -- defer the rest of the data */ defer_write(target_q, buffer + size_w, size_r - size_w); return FD_STALLED; } CHECK_RES_RETURN(size_w, "write", FD_CNXCLOSED); return (int)size_w; } /* returns a string that prints the IP and port of the sockaddr */ char* sprintaddr(char* buf, size_t size, struct addrinfo *a) { char host[NI_MAXHOST], serv[NI_MAXSERV]; int res; memset(host, 0, sizeof(host)); memset(serv, 0, sizeof(serv)); res = getnameinfo(a->ai_addr, a->ai_addrlen, host, sizeof(host), serv, sizeof(serv), cfg.numeric ? NI_NUMERICHOST | NI_NUMERICSERV : 0 ); if (res) { print_message(msg_system_error, "sprintaddr:getnameinfo: %s\n", gai_strerror(res)); /* Name resolution failed: do it numerically instead */ res = getnameinfo(a->ai_addr, a->ai_addrlen, host, sizeof(host), serv, sizeof(serv), NI_NUMERICHOST | NI_NUMERICSERV); /* should not fail but... */ if (res) { print_message(msg_system_error, "sprintaddr:getnameinfo(NUM): %s\n", gai_strerror(res)); strcpy(host, "?"); strcpy(serv, "?"); } } snprintf(buf, size, "%s:%s", host, serv); return buf; } /* Turns a hostname and port (or service) into a list of struct addrinfo * On success, returns 0 * On failure, returns -1 or one of getaddrinfo() codes */ int resolve_split_name(struct addrinfo **out, char* host, char* serv) { struct addrinfo hint; char *end; int res; memset(&hint, 0, sizeof(hint)); hint.ai_family = PF_UNSPEC; hint.ai_socktype = SOCK_STREAM; /* If it is a RFC-Compliant IPv6 address ("[1234::12]:443"), remove brackets * around IP address */ if (host[0] == '[') { end = strrchr(host, ']'); if (!end) { print_message(msg_config_error, "%s: no closing bracket in IPv6 address?\n", host); return -1; } host++; /* skip first bracket */ *end = 0; /* remove last bracket */ } res = getaddrinfo(host, serv, &hint, out); if (res) print_message(msg_system_error, "resolve_split_name: %s `%s:%s'\n", gai_strerror(res), host, serv); return res; } /* turns a "hostname:port" string into a list of struct addrinfo; out: list of newly allocated addrinfo (see getaddrinfo(3)); freeaddrinfo(3) when done fullname: input string -- it gets clobbered */ void resolve_name(struct addrinfo **out, char* fullname) { char *serv, *host; int res; /* Find port */ char *sep = strrchr(fullname, ':'); if (!sep) { /* No separator: parameter is just a port */ print_message(msg_config_error, "%s: names must be fully specified as hostname:port\n", fullname); exit(1); } serv = sep+1; *sep = 0; host = fullname; res = resolve_split_name(out, host, serv); if (res) { print_message(msg_config_error, "%s `%s'\n", gai_strerror(res), fullname); if (res == EAI_SERVICE) print_message(msg_config_error, "(Check you have specified all ports)\n"); exit(4); } } /* Fills a connection description; returns 0 on failure */ int get_connection_desc(struct connection_desc* desc, const struct connection *cnx) { int res; struct addrinfo addr; struct sockaddr_storage ss; addr.ai_addr = (struct sockaddr*)&ss; addr.ai_addrlen = sizeof(ss); res = getpeername(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen); if (res == -1) return 0; /* Can happen if connection drops before we get here. In that case, don't log anything (there is no connection) */ sprintaddr(desc->peer, sizeof(desc->peer), &addr); addr.ai_addrlen = sizeof(ss); res = getsockname(cnx->q[0].fd, addr.ai_addr, &addr.ai_addrlen); if (res == -1) return 0; sprintaddr(desc->service, sizeof(desc->service), &addr); addr.ai_addrlen = sizeof(ss); res = getpeername(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen); if (res == -1) return 0; sprintaddr(desc->target, sizeof(desc->target), &addr); addr.ai_addrlen = sizeof(ss); res = getsockname(cnx->q[1].fd, addr.ai_addr, &addr.ai_addrlen); if (res == -1) return 0; sprintaddr(desc->local, sizeof(desc->local), &addr); return 1; } void set_proctitle_shovel(struct connection_desc* desc, const struct connection *cnx) { #ifdef HAVE_LIBBSD struct connection_desc d; if (!desc) { desc = &d; get_connection_desc(desc, cnx); } setproctitle("shovel %s %s->%s => %s->%s", cnx->proto->name, desc->peer, desc->service, desc->local, desc->target); #endif } /* libwrap (tcpd): check the connection is legal. This is necessary because * the actual server will only see a connection coming from localhost and can't * apply the rules itself. * * Returns -1 if access is denied, 0 otherwise */ int check_access_rights(int in_socket, const char* service) { #ifdef HAVE_LIBWRAP union { struct sockaddr saddr; struct sockaddr_storage ss; } peer; socklen_t size = sizeof(peer); char addr_str[NI_MAXHOST], host[NI_MAXHOST]; int res; res = getpeername(in_socket, &peer.saddr, &size); CHECK_RES_RETURN(res, "getpeername", res); /* extract peer address */ res = getnameinfo(&peer.saddr, size, addr_str, sizeof(addr_str), NULL, 0, NI_NUMERICHOST); if (res) { print_message(msg_system_error, "getnameinfo(NI_NUMERICHOST):%s\n", gai_strerror(res)); strcpy(addr_str, STRING_UNKNOWN); } /* extract peer name */ strcpy(host, STRING_UNKNOWN); if (!cfg.numeric) { res = getnameinfo(&peer.saddr, size, host, sizeof(host), NULL, 0, NI_NAMEREQD); if (res) { print_message(msg_system_error, "getnameinfo(NI_NAMEREQD):%s\n", gai_strerror(res)); } } if (!hosts_ctl((char*)service, host, addr_str, STRING_UNKNOWN)) { print_message(msg_connections, "connection from %s(%s): access denied", host, addr_str); close(in_socket); return -1; } #endif return 0; } void setup_signals(void) { int res; struct sigaction action; /* Request no SIGCHLD is sent upon termination of * the children */ memset(&action, 0, sizeof(action)); action.sa_handler = NULL; action.sa_flags = SA_NOCLDWAIT; res = sigaction(SIGCHLD, &action, NULL); CHECK_RES_DIE(res, "sigaction"); /* Set SIGTERM to exit. For some reason if it's not set explicitly, * coverage information is lost when killing the process */ memset(&action, 0, sizeof(action)); action.sa_handler = exit; res = sigaction(SIGTERM, &action, NULL); CHECK_RES_DIE(res, "sigaction"); /* Ignore SIGPIPE . */ action.sa_handler = SIG_IGN; res = sigaction(SIGPIPE, &action, NULL); CHECK_RES_DIE(res, "sigaction"); } /* Ask OS to keep capabilities over a setuid(nonzero) */ void set_keepcaps(int val) { #if HAVE_LIBCAP int res; res = prctl(PR_SET_KEEPCAPS, val, 0, 0, 0); if (res) { perror("prctl"); exit(1); } #endif } /* Returns true if anything requires transparent proxying. */ static int use_transparent(void) { #if HAVE_LIBCAP if (cfg.transparent) return 1; for (int i = 0; i < cfg.protocols_len; i++) if (cfg.protocols[i].transparent) return 1; #endif return 0; } /* set needed capabilities for effective and permitted, clear rest * IN: cap_net_admin: set to 1 to set CAP_NET_RAW * */ void set_capabilities(int cap_net_admin) { #if HAVE_LIBCAP int res; cap_t caps; cap_value_t cap_list[10]; int ncap = 0; if (cap_net_admin) cap_list[ncap++] = CAP_NET_RAW; caps = cap_init(); #define _cap_set_flag(flag) do { \ res = cap_clear_flag(caps, flag); \ CHECK_RES_DIE(res, "cap_clear_flag(" #flag ")"); \ if (ncap > 0) { \ res = cap_set_flag(caps, flag, ncap, cap_list, CAP_SET); \ CHECK_RES_DIE(res, "cap_set_flag(" #flag ")"); \ } \ } while(0) _cap_set_flag(CAP_EFFECTIVE); _cap_set_flag(CAP_PERMITTED); #undef _cap_set_flag res = cap_set_proc(caps); CHECK_RES_DIE(res, "cap_set_proc"); res = cap_free(caps); if (res) { perror("cap_free"); exit(1); } #endif } /* We don't want to run as root -- drop privileges if required */ void drop_privileges(const char* user_name, const char* chroot_path) { int res; struct passwd *pw = NULL; if (user_name) { pw = getpwnam(user_name); if (!pw) { print_message(msg_config_error, "%s: not found\n", user_name); exit(2); } print_message(msg_config, "turning into %s\n", user_name); } if (chroot_path) { print_message(msg_config, "chrooting into %s\n", chroot_path); res = chroot(chroot_path); CHECK_RES_DIE(res, "chroot"); } if (user_name) { set_keepcaps(1); /* remove extraneous groups in case we belong to several extra groups * that may have unwanted rights. If non-root when calling setgroups(), * it fails, which is fine because... we have no unwanted rights * (see POS36-C for security context) * */ setgroups(0, NULL); res = setgid(pw->pw_gid); CHECK_RES_DIE(res, "setgid"); res = setuid(pw->pw_uid); CHECK_RES_DIE(res, "setuid"); set_capabilities(use_transparent()); set_keepcaps(0); } } /* Writes my PID */ void write_pid_file(const char* pidfile) { FILE *f; int res; f = fopen(pidfile, "w"); if (!f) { print_message(msg_system_error, "write_pid_file: %s: %s\n", pidfile, strerror(errno)); return; } res = fprintf(f, "%d\n", getpid()); if (res < 0) { print_message(msg_system_error, "write_pid_file: fprintf: %s\n", strerror(errno)); } res = fclose(f); if (res == EOF) { print_message(msg_system_error, "write_pid_file: fclose: %s\n", strerror(errno)); return; } }