/* * SSLsplit - transparent and scalable SSL/TLS interception * Copyright (c) 2009-2014, Daniel Roethlisberger * All rights reserved. * http://www.roe.ch/SSLsplit * * 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 unmodified, 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 AUTHOR ``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 AUTHOR 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 "sys.h" #include "log.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _SC_NPROCESSORS_ONLN #include #endif /* !_SC_NPROCESSORS_ONLN */ #if HAVE_DARWIN_LIBPROC #include #endif #include /* * 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 groupname is set, it will be used instead of the user's default primary * group. * 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 *groupname, const char *jaildir) { struct passwd *pw = NULL; struct group *gr = NULL; int ret = -1; if (groupname) { if (!(gr = getgrnam(groupname))) { log_err_printf("Failed to getgrnam group '%s': %s\n", groupname, strerror(errno)); goto error; } } if (username) { if (!(pw = getpwnam(username))) { log_err_printf("Failed to getpwnam user '%s': %s\n", username, strerror(errno)); goto error; } if (gr != NULL) { pw->pw_gid = gr->gr_gid; } 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; } /* * 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; } /* * Write process ID to open process ID file descriptor fd. * Returns 0 on success, -1 on errors. */ int sys_pidf_write(int fd) { char pidbuf[4*sizeof(pid_t)]; int rv; rv = snprintf(pidbuf, sizeof(pidbuf), "%d\n", getpid()); if (rv == -1 || rv >= (int)sizeof(pidbuf)) return -1; write(fd, pidbuf, strlen(pidbuf)); fsync(fd); fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC); return 0; } /* * Close and remove open process ID file before quitting. */ void sys_pidf_close(int fd, const char *fn) { unlink(fn); close(fd); } /* * Converts a local uid into a printable string representation. * Returns an allocated buffer which must be freed by caller, or NULL on error. */ char * sys_user_str(uid_t uid) { static int bufsize = 0; if (!bufsize) { /* on some platforms this compiles, but does not succeed */ if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) { bufsize = 64; } } char *buf, *newbuf; struct passwd pwd, *result = NULL; int rv; char *name; if (!(buf = malloc(bufsize))) return NULL; do { rv = getpwuid_r(uid, &pwd, buf, bufsize, &result); if (rv == 0) { if (result) { name = strdup(pwd.pw_name); free(buf); return name; } free(buf); /* no entry found; return the integer representation */ if (asprintf(&name, "%llu", (long long) uid) < 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 uid: %s (%i)\n", strerror(rv), rv); return NULL; } /* * 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; } /* * 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; } /* * 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; } /* * Returns 1 if path points to an existing directory node in the filesystem. * Returns 0 if path is NULL, does not exist, or points to a file of some kind. */ int sys_isdir(const char *path) { struct stat s; if (stat(path, &s) == -1) return 0; if (s.st_mode & S_IFDIR) return 1; return 0; } /* * Create directory including parent directories with mode_t. * Mode of existing parent directories is not changed. * Returns 0 on success, -1 and sets errno on error. */ int sys_mkpath(const char *path, mode_t mode) { char parent[strlen(path)+1]; char *p; memcpy(parent, path, sizeof(parent)); p = parent; do { /* skip leading '/' characters */ while (*p == '/') p++; p = strchr(p, '/'); if (p) { /* overwrite '/' to terminate the string at the next * parent directory */ *p = '\0'; } struct stat sbuf; if (stat(parent, &sbuf) == -1) { if (errno == ENOENT) { if (mkdir(parent, mode) != 0) return -1; } else { return -1; } } else if (!S_ISDIR(sbuf.st_mode)) { errno = ENOTDIR; return -1; } if (p) { /* replace the overwritten slash */ *p = '/'; p++; } } while (p); 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; } /* * Portably get the number of CPU cores online in the system. */ uint32_t sys_get_cpu_cores(void) { #ifdef _SC_NPROCESSORS_ONLN return sysconf(_SC_NPROCESSORS_ONLN); #else /* !_SC_NPROCESSORS_ONLN */ int mib[2]; uint32_t n; size_t len = sizeof(n); mib[0] = CTL_HW; mib[1] = HW_AVAILCPU; sysctl(mib, sizeof(mib)/sizeof(int), &n, &len, NULL, 0); if (n < 1) { mib[1] = HW_NCPU; sysctl(mib, sizeof(mib)/sizeof(int), &n, &len, NULL, 0); if (n < 1) { n = 1; } } return n; #endif /* !_SC_NPROCESSORS_ONLN */ } /* * Send a message and optional file descriptor on a connected AF_UNIX * SOCKET_DGRAM socket s. Returns the return value of sendmsg(). * If fd is -1, no file descriptor is passed. */ ssize_t sys_sendmsgfd(int sock, void *buf, size_t bufsz, int fd) { struct iovec iov; struct msghdr msg; struct cmsghdr *cmsg; char cmsgbuf[CMSG_SPACE(sizeof(int))]; iov.iov_base = buf; iov.iov_len = bufsz; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; if (fd != -1) { msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_len = CMSG_LEN(sizeof(int)); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; *((int *) CMSG_DATA(cmsg)) = fd; } else { msg.msg_control = NULL; msg.msg_controllen = 0; } #ifdef MSG_NOSIGNAL return sendmsg(sock, &msg, MSG_NOSIGNAL); #else /* !MSG_NOSIGNAL */ return sendmsg(sock, &msg, 0); #endif /* !MSG_NOSIGNAL */ } /* * Receive a message and optional file descriptor on a connected AF_UNIX * SOCKET_DGRAM socket s. Returns the return value of recvmsg()/recv() * and sets errno to EINVAL if the received message is malformed. * If pfd is NULL, no file descriptor is received; if a file descriptor was * part of the received message and pfd is NULL, then the kernel will close it. */ ssize_t sys_recvmsgfd(int sock, void *buf, size_t bufsz, int *pfd) { ssize_t n; if (pfd) { struct iovec iov; struct msghdr msg; struct cmsghdr *cmsg; unsigned char cmsgbuf[CMSG_SPACE(sizeof(int))]; iov.iov_base = buf; iov.iov_len = bufsz; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); if ((n = recvmsg(sock, &msg, 0)) <= 0) return n; cmsg = CMSG_FIRSTHDR(&msg); if (cmsg && cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { if (cmsg->cmsg_level != SOL_SOCKET) { errno = EINVAL; return -1; } if (cmsg->cmsg_type != SCM_RIGHTS) { errno = EINVAL; return -1; } *pfd = *((int *) CMSG_DATA(cmsg)); } else { *pfd = -1; } } else { n = recv(sock, buf, bufsz, 0); } return n; } /* vim: set noet ft=c: */