/* SPDX-License-Identifier: GPL-2.0-only */ #include "m7a.h" #include #include #include #include #include #include #include #include #include #include #include #include void die(const char *fmt, ...) { char *str; va_list argp; int ret; va_start(argp, fmt); ret = vasprintf(&str, fmt, argp); va_end(argp); if (ret < 0) { /* give up */ EMERG_LOG("OOM\n"); exit(EXIT_FAILURE); } m7a_log(LL_EMERG, "%s\n", str); exit(EXIT_FAILURE); } void die_errno(const char *fmt, ...) { char *str; va_list argp; int ret, save_errno = errno; va_start(argp, fmt); ret = vasprintf(&str, fmt, argp); va_end(argp); if (ret < 0) { EMERG_LOG("OOM\n"); exit(EXIT_FAILURE); } m7a_log(LL_EMERG, "%s: %s\n", str, strerror(save_errno)); exit(EXIT_FAILURE); } void *xrealloc(void *p, size_t size) { assert(size > 0); assert((p = realloc(p, size))); return p; } void *xmalloc(size_t size) { return xrealloc(NULL, size); } void *xzmalloc(size_t size) { void *p = xrealloc(NULL, size); memset(p, 0, size); return p; } void *xstrdup(const char *s) { char *ret = strdup(s? s: ""); assert(ret); return ret; } char *msg(const char *fmt, ...) { char *m; size_t size = 100; m = xmalloc(size); while (1) { int n; va_list ap; /* Try to print in the allocated space. */ va_start(ap, fmt); n = vsnprintf(m, size, fmt, ap); va_end(ap); /* If that worked, return the string. */ if (n < size) return m; /* Else try again with more space. */ size = n + 1; /* precisely what is needed */ m = xrealloc(m, size); } } char *xstrcat(char *a, const char *b) { char *tmp; if (!a) return xstrdup(b); if (!b) return a; tmp = msg("%s%s", a, b); free(a); return tmp; } void die_empty_arg(const char *opt) { die("argument to --%s must not be empty", opt); } __attribute__ ((noreturn)) static void die_range(const char *opt) { die("argument to --%s is out of range", opt); } void check_range(uint32_t val, uint32_t min, uint32_t max, const char *opt) { if (val < min || val > max) die_range(opt); } bool fd2buf(int fd, const struct iovec *iov) { ssize_t ret, nread = 0, max; char *buf = iov->iov_base; assert(iov->iov_len > 1); max = iov->iov_len - 1; for (;;) { ret = read(fd, buf + nread, max - nread); if (ret < 0) { if (errno == EAGAIN || errno == EINTR) continue; ERROR_LOG("read error: %s\n", strerror(errno)); return false; } if (ret == 0) { buf[nread] = '\0'; DEBUG_LOG("read %zd bytes\n", nread); return true; } nread += ret; if (nread >= max) { ERROR_LOG("cmd output truncated\n"); return false; } } } bool xexec(char * const argv[]) { pid_t pid; unsigned n; for (n = 0; argv[n]; n++) DEBUG_LOG("argv[%u]=%s\n", n, argv[n]); if ((pid = fork()) < 0) die_errno("fork"); if (pid > 0) { /* parent */ int wstatus; if (waitpid(pid, &wstatus, WUNTRACED) < 0) die_errno("waitp"); /* * If the user shuts down the container from an interactive * com_enter() shell, the nsenter process receives SIGSTOP * because the container shuts down the controlling tty, * causing the shell to hang. Avoid this by letting the process * continue once. */ if (WIFSTOPPED(wstatus)) kill(pid, SIGCONT); if (!WIFEXITED(wstatus)) return false; if (WEXITSTATUS(wstatus) != EXIT_SUCCESS) return false; return true; } execvp(argv[0], argv); EMERG_LOG("execvp error: %s\n", strerror(errno)); _exit(EXIT_FAILURE); } void valid_fd012(void) { /* Ensure that file descriptors 0, 1, and 2 are valid. */ while (1) { int fd = open("/dev/null", O_RDWR); if (fd < 0) die_errno("open"); if (fd > 2) { close(fd); break; } } } void check_name(const char *arg) { size_t m, len; char c; len = strlen(arg); if (len == 0) die("empty name"); if (len > 32) die("name too long: %s", arg); for (m = 0; m < len; m++) { c = arg[m]; if (!isascii(c)) goto invalid; if (!isalnum(c) && c != '-') goto invalid; } return; invalid: die("invalid character '%c' in name %s", c, arg); } /* allocates two new strings that should be freed by the caller */ void parse_compound_arg(const char *arg, const char *opt, char **name, char **val) { char *copy, *p; if (arg[0] == '\0') die_empty_arg(opt); copy = xstrdup(arg); p = strchr(copy, ':'); if (!p) die("could not parse argument to --%s", opt); *p = '\0'; check_name(copy); *name = copy; p++; *val = xstrdup(p); } char *parse_cgroup_acl(const char *arg) { if (!strncmp(arg, "allow ", 6)) return msg("a%s", arg + 6); if (!strncmp(arg, "deny ", 5)) return msg("d%s", arg + 5); die("invalid cgroup access specifier: %s", arg); } void parse_ifspec(const char *arg, char **bridge, uint8_t *hwaddr) { const char *colon = strchr(arg, ':'); size_t len; unsigned n, x[6]; if (colon) { len = colon - arg; *bridge = xmalloc(len + 1); memcpy(*bridge, arg, len); (*bridge)[len] = '\0'; } else *bridge = xstrdup(arg); check_name(*bridge); if (!colon) { memset(hwaddr, 0, 6); return; } if (sscanf(colon + 1, "%02x:%02x:%02x:%02x:%02x:%02x", x, x + 1, x + 2, x + 3, x + 4, x + 5) != 6) die("invalid hwaddress for ifspec %s", arg); if (colon[1 + 6 * 2 + 5] != '\0') die("trailing garbage at the end of ifspec %s", arg); for (n = 0; n < 6; n++) hwaddr[n] = x[n]; } uint32_t atou32(const char *str, const char *opt) { char *endptr; long long tmp; errno = 0; /* To distinguish success/failure after call */ tmp = strtoll(str, &endptr, 10); if (errno == ERANGE && (tmp == LLONG_MAX || tmp == LLONG_MIN)) die_range(opt); if (tmp < 0 || tmp > (uint32_t)-1) die_range(opt); /* * If there were no digits at all, strtoll() stores the original value * of str in *endptr. */ if (endptr == str) die_empty_arg(opt); /* * The implementation may also set errno and return 0 in case no * conversion was performed. */ if (errno != 0 && tmp == 0) die_empty_arg(opt); if (*endptr != '\0') /* Further characters after number */ die("--%s: trailing characters after number", opt); return tmp; } bool remove_subdirs_recursively(const char *path) { DIR *d = opendir(path); struct dirent *entry; int dfd; struct stat stat; if (!d) { ERROR_LOG("opendir %s: %m\n", path); return false; } dfd = dirfd(d); assert(dfd >= 0); while ((entry = readdir(d))) { char *subpath; if (!strcmp(entry->d_name, ".")) continue; if (!strcmp(entry->d_name, "..")) continue; if (fstatat(dfd, entry->d_name, &stat, 0) == -1) { WARNING_LOG("%s/%s: %m", path, entry->d_name); continue; } if (!S_ISDIR(stat.st_mode)) continue; subpath = msg("%s/%s", path, entry->d_name); remove_subdirs_recursively(subpath); DEBUG_LOG("removing %s\n", subpath); if (rmdir(subpath) < 0) { ERROR_LOG("rmdir %s: %m\n", subpath); return false; } free(subpath); } closedir(d); return true; } void daemonize(const char *logfile) { pid_t pid; int nullfd, logfd; if ((pid = fork()) < 0) die_errno("fork"); if (pid) /* parent exits */ exit(EXIT_SUCCESS); valid_fd012(); /* become session leader */ if (setsid() < 0) die_errno("setsid"); if ((nullfd = open("/dev/null", O_RDWR)) < 0) die_errno("open /dev/null"); logfile = logfile? logfile : "/dev/null"; if ((logfd = open(logfile, O_WRONLY | O_APPEND | O_CREAT, 0666)) < 0) die_errno("open %s", logfile); NOTICE_LOG("subsequent log messages go to %s\n", logfile); if (dup2(nullfd, STDIN_FILENO) < 0) die_errno("dup2"); close(nullfd); if (dup2(logfd, STDOUT_FILENO) < 0) die_errno("dup2"); if (dup2(logfd, STDERR_FILENO) < 0) die_errno("dup2"); close(logfd); if (chdir("/") < 0) die_errno("chdir"); } static int super_dull_hash(const char *input) { const uint8_t *x = (typeof(x))input; const unsigned p1 = 16777619, p2 = 2971215073; unsigned n, m, h, result = 0; for (n = 0; n < 4; n++) { h = p1 * (x[0] + n); for (m = 1; x[m] != 0; m++) h = p2 * (h ^ x[m]); result = (result << 8) | (h % 256); } return result >> 1; } /** * We use a semaphore set with two semaphores. The first semaphore is modified * in all locking related functions while the second semaphore is modified only * in try_lock() and aquire_lock(). This allows us to obtain the PID of the * lock holder by querying the PID that last performed an operation on the * second semaphore. This is achieved by passing GETPID as the control * operation to semctl(). */ static bool get_lock(const char *string, pid_t *pid, bool wait) { int semid, ret; struct sembuf sops[4]; key_t key = super_dull_hash(string); bool success; short sem_flg = SEM_UNDO; if (!wait) sem_flg |= IPC_NOWAIT; ret = semget(key, 2, IPC_CREAT | 0600); if (ret < 0) { ERROR_LOG("semget: %m\n"); return false; } semid = ret; DEBUG_LOG("key: 0x%0x, semid: %d\n", (unsigned)key, semid); ret = semctl(semid, 1, GETPID); if (ret < 0) return false; if (pid) *pid = ret; sops[0].sem_num = 0; sops[0].sem_op = 0; sops[0].sem_flg = sem_flg; sops[1].sem_num = 0; sops[1].sem_op = 1; sops[1].sem_flg = sem_flg; sops[2].sem_num = 1; sops[2].sem_op = 0; sops[2].sem_flg = sem_flg; sops[3].sem_num = 1; sops[3].sem_op = 1; sops[3].sem_flg = sem_flg; success = semop(semid, sops, 4) >= 0; if (!success) INFO_LOG("semop: %m\n"); return success; } bool try_lock(const char *string, pid_t *pid) { return get_lock(string, pid, false /* don't wait */); } bool acquire_lock(const char *string) { return get_lock(string, NULL /* don't need pid */, true /* do wait */); } bool release_lock(const char *string) { int semid, ret; struct sembuf sops[2]; key_t key = super_dull_hash(string); bool success; ret = semget(key, 2, IPC_CREAT | 0600); if (ret < 0) { ERROR_LOG("semget: %m\n"); return false; } semid = ret; DEBUG_LOG("key: 0x%0x, semid: %d\n", (unsigned)key, semid); sops[0].sem_num = 0; sops[0].sem_op = -1; sops[0].sem_flg = SEM_UNDO; sops[1].sem_num = 1; sops[1].sem_op = -1; sops[1].sem_flg = SEM_UNDO; success = semop(semid, sops, 2) >= 0; if (!success) INFO_LOG("semop: %m\n"); return success; } bool is_locked(const char *string, pid_t *pid) { int ret, semid; struct sembuf sops = { .sem_num = 0, .sem_op = 0, .sem_flg = SEM_UNDO | IPC_NOWAIT }; key_t key = super_dull_hash(string); if (pid) *pid = 0; ret = semget(key, 2, 0); if (ret < 0) return false; semid = ret; DEBUG_LOG("key: 0x%0x, semid: %d\n", (unsigned)key, semid); if (semop(semid, &sops, 1) >= 0) return false; ret = semctl(semid, 1, GETPID); if (ret < 0) return false; if (pid) *pid = ret; return true; } bool attach_to_bridge(const char *iface, const char *bridge) { int fd, idx; struct ifreq ifr; bool success; INFO_LOG("adding interface %s to bridge %s\n", iface, bridge); if (!(idx = if_nametoindex(iface))) { ERROR_LOG("no index for %s: %m\n", iface); return false; } if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { ERROR_LOG("socket: %m\n"); return false; } strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1); ifr.ifr_name[IFNAMSIZ - 1] = '\0'; ifr.ifr_ifindex = idx; success = ioctl(fd, SIOCBRADDIF, &ifr) == 0; if (!success) ERROR_LOG("interface %s, bridge %s: ioctl SIOCBRADDIF: %m\n", iface, bridge); close(fd); return success; } #define NLMSG_TAIL(nmsg) \ ((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) static void addattr_l(struct nlmsghdr *nlh, int type, const void *data, int alen) { int len = RTA_LENGTH(alen); struct rtattr *rta; rta = NLMSG_TAIL(nlh); rta->rta_type = type; rta->rta_len = len; if (alen > 0) memcpy(RTA_DATA(rta), data, alen); nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len); } static struct rtattr *addattr_nest(struct nlmsghdr *n, int type) { struct rtattr *nest = NLMSG_TAIL(n); addattr_l(n, type, NULL, 0); return nest; } static void end_nest(struct nlmsghdr *nlh, struct rtattr *attr) { attr->rta_len = (void *)NLMSG_TAIL(nlh) - (void *)attr; } static struct mnl_socket *get_and_bind_netlink_socket(void) { struct mnl_socket *nl = mnl_socket_open(NETLINK_ROUTE); if (!nl) { ERROR_LOG("mnl_socket_open error\n"); return NULL; } if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { ERROR_LOG("mnl_socket_bind\n"); mnl_socket_close(nl); return NULL; } return nl; } static struct nlmsghdr *prepare_netlink_msg_header(char *buf) { struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); nlh->nlmsg_flags = NLM_F_REQUEST; nlh->nlmsg_seq = time(NULL); return nlh; } bool rename_interface(const char *before, const char *after) { int idx; struct mnl_socket *nl; char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct ifinfomsg *ifm; bool success; INFO_LOG("%s -> %s\n", before, after); if (!(idx = if_nametoindex(before))) { ERROR_LOG("no index for %s\n", before); return false; } if (!(nl = get_and_bind_netlink_socket())) return false; nlh = prepare_netlink_msg_header(buf); nlh->nlmsg_type = RTM_NEWLINK; ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); ifm->ifi_family = AF_UNSPEC; ifm->ifi_index = idx; addattr_l(nlh, IFLA_IFNAME, after, strlen(after) + 1); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { ERROR_LOG("mnl_socket_sendto failed\n"); success = false; goto close; } success = true; close: mnl_socket_close(nl); return success; } void pretty_print_hwaddr(const uint8_t *hwaddr, char *result) { sprintf(result, "%02x:%02x:%02x:%02x:%02x:%02x", hwaddr[0], hwaddr[1], hwaddr[2], hwaddr[3], hwaddr[4], hwaddr[5]); } bool set_hwaddr(const char *iface, const uint8_t *hwaddr) { struct mnl_socket *nl; char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct ifinfomsg *ifm; bool success; char pretty_hwaddr[18]; pretty_print_hwaddr(hwaddr, pretty_hwaddr); INFO_LOG("hardware address of %s: %s\n", iface, pretty_hwaddr); if (!(nl = get_and_bind_netlink_socket())) return false; nlh = prepare_netlink_msg_header(buf); nlh->nlmsg_type = RTM_NEWLINK; ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); ifm->ifi_family = AF_UNSPEC; addattr_l(nlh, IFLA_ADDRESS, hwaddr, 6); addattr_l(nlh, IFLA_IFNAME, iface, strlen(iface) + 1); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { ERROR_LOG("%s: mnl_socket_sendto failed\n", iface); success = false; goto close; } success = true; close: mnl_socket_close(nl); return success; } bool link_del(const char *iface) { struct mnl_socket *nl; char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct ifinfomsg *ifm; bool success; INFO_LOG("removing interface %s\n", iface); if (!(nl = get_and_bind_netlink_socket())) return false; nlh = prepare_netlink_msg_header(buf); nlh->nlmsg_type = RTM_DELLINK; ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); ifm->ifi_family = AF_UNSPEC; ifm->ifi_change = IFF_UP; ifm->ifi_flags = IFF_UP; addattr_l(nlh, IFLA_IFNAME, iface, strlen(iface) + 1); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { ERROR_LOG("%s: mnl_socket_sendto failed\n", iface); success = false; goto close; } success = true; close: mnl_socket_close(nl); return success; } bool link_up(const char *iface) { struct mnl_socket *nl; char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct ifinfomsg *ifm; bool success; INFO_LOG("activating interface %s\n", iface); if (!(nl = get_and_bind_netlink_socket())) return false; nlh = prepare_netlink_msg_header(buf); nlh->nlmsg_type = RTM_NEWLINK; ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); ifm->ifi_family = AF_UNSPEC; ifm->ifi_change = IFF_UP; ifm->ifi_flags = IFF_UP; addattr_l(nlh, IFLA_IFNAME, iface, strlen(iface) + 1); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { ERROR_LOG("%s: mnl_socket_sendto failed\n", iface); success = false; goto close; } success = true; close: mnl_socket_close(nl); return success; } #ifndef VETH_INFO_PEER #define VETH_INFO_PEER 1 #endif bool create_veth_device_pair(const char *name, char *peer) { struct mnl_socket *nl; char buf[MNL_SOCKET_BUFFER_SIZE]; struct rtattr *n1, *n2, *n3; struct nlmsghdr *nlh; struct ifinfomsg *ifm; bool success; INFO_LOG("new pair: %s <-> %s\n", name, peer); if (!(nl = get_and_bind_netlink_socket())) return false; nlh = prepare_netlink_msg_header(buf); nlh->nlmsg_type = RTM_NEWLINK; nlh->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); ifm->ifi_family = AF_UNSPEC; n1 = addattr_nest(nlh, IFLA_LINKINFO); addattr_l(nlh, IFLA_INFO_KIND, "veth", 5); n2 = addattr_nest(nlh, IFLA_INFO_DATA); n3 = addattr_nest(nlh, VETH_INFO_PEER); ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); ifm->ifi_family = AF_UNSPEC; addattr_l(nlh, IFLA_IFNAME, peer, strlen(peer) + 1); end_nest(nlh, n3); end_nest(nlh, n2); end_nest(nlh, n1); addattr_l(nlh, IFLA_IFNAME, name, strlen(name) + 1); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { ERROR_LOG("%s: mnl_socket_sendto\n", name); success = false; goto close; } success = true; close: mnl_socket_close(nl); return success; } bool set_netns(const char *iface, pid_t pid) { struct mnl_socket *nl; char buf[MNL_SOCKET_BUFFER_SIZE]; struct nlmsghdr *nlh; struct ifinfomsg *ifm; INFO_LOG("changing net namespace of interface %s to pid %d\n", iface, (int)pid); if (!(nl = get_and_bind_netlink_socket())) return false; nlh = prepare_netlink_msg_header(buf); nlh->nlmsg_type = RTM_NEWLINK; ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); ifm->ifi_family = AF_UNSPEC; ifm->ifi_change = 0; ifm->ifi_flags = 0; addattr_l(nlh, IFLA_NET_NS_PID, &pid, sizeof(pid)); mnl_attr_put_str(nlh, IFLA_IFNAME, iface); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { ERROR_LOG("%s: mnl_socket_sendto failed\n", iface); return false; } mnl_socket_close(nl); return true; } #ifndef UNIX_PATH_MAX #define UNIX_PATH_MAX (sizeof(((struct sockaddr_un *)0)->sun_path)) #endif static bool init_unix_socket(const char *socket_path, int *socketfd, struct sockaddr_un *sau) { int fd; *socketfd = -1; if (strlen(socket_path) + 1 >= UNIX_PATH_MAX) { ERROR_LOG("socket path to long: %s\n", socket_path); return false; } memset(sau, 0, sizeof(struct sockaddr_un)); sau->sun_family = PF_UNIX; sau->sun_path[0] = '\0'; /* use the abstract socket namespace */ strcpy(sau->sun_path + 1, socket_path); fd = socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { ERROR_LOG("socket: %m\n"); return false; } *socketfd = fd; return true; } bool listen_on_unix_socket(const char *socket_path, int *result) { struct sockaddr_un sau; int fd, flags; bool success = false; if (!init_unix_socket(socket_path, &fd, &sau)) return false; flags = fcntl(fd, F_GETFL); if (flags < 0) { ERROR_LOG("fcntl (F_GETFL): %m\n"); goto fail; } flags = fcntl(fd, F_SETFL, ((long)flags) | O_NONBLOCK); if (flags < 0) { ERROR_LOG("fcntl (F_SETFL): %m\n"); goto fail; } if (bind(fd, (struct sockaddr *)&sau, sizeof(sau)) < 0) { ERROR_LOG("bind: %m\n"); goto fail; } if (listen(fd , 5) < 0) { ERROR_LOG("listen: %m\n"); goto fail; } *result = fd; NOTICE_LOG("listening on fd %d\n", fd); return true; fail: close(fd); return success; } /* * Send a buffer and the credentials of the current process to a socket. * * buf must be zero-terminated. * return the return value of the underlying call to sendmsg(). */ static bool send_cred_buffer(int sock, char *buf) { char control[255] __attribute__((__aligned__(8))); struct msghdr msg; struct cmsghdr *cmsg; static struct iovec iov; struct ucred c; /* Response data */ iov.iov_base = buf; iov.iov_len = strlen(buf) + 1; c.pid = getpid(); c.uid = getuid(); c.gid = getgid(); /* compose the message */ memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); /* attach the ucred struct */ cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_CREDENTIALS; cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); *(struct ucred *)CMSG_DATA(cmsg) = c; msg.msg_controllen = cmsg->cmsg_len; if (sendmsg(sock, &msg, 0) < 0) { ERROR_LOG("sendmsg: %m\n"); return false; } return true; } static void dispose_fds(int *fds, unsigned num) { int i; for (i = 0; i < num; i++) close(fds[i]); } /* Receive a buffer and the Unix credentials of the sending process. */ bool recv_cred_buffer(int socketfd, char *buf, size_t size, int *clientfd, uid_t *uid) { char control[255] __attribute__((__aligned__(8))); struct msghdr msg; struct cmsghdr *cmsg; struct iovec iov; int yes = 1, cfd, ret; struct ucred cred; struct sockaddr_un sau; socklen_t sizeof_sau = sizeof(sau); ret = accept(socketfd, (struct sockaddr *)&sau, &sizeof_sau); if (ret < 0) { ERROR_LOG("accept: %m\n"); return false; } cfd = ret; setsockopt(cfd, SOL_SOCKET, SO_PASSCRED, &yes, sizeof(int)); memset(&msg, 0, sizeof(msg)); iov.iov_base = buf; iov.iov_len = size; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); if (recvmsg(cfd, &msg, 0) < 0) { ERROR_LOG("recvmsg: %m\n"); goto fail; } cmsg = CMSG_FIRSTHDR(&msg); while (cmsg) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDENTIALS) { memcpy(&cred, CMSG_DATA(cmsg), sizeof(struct ucred)); *uid = cred.uid; *clientfd = cfd; return true; } else if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { dispose_fds((int *)CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); } cmsg = CMSG_NXTHDR(&msg, cmsg); } fail: close(*clientfd); *clientfd = -1; return false; } bool pass_fd(int passfd, int socketfd) { struct msghdr msg = {.msg_iov = NULL}; struct cmsghdr *cmsg; char control[255] __attribute__((__aligned__(8))); struct iovec iov; char buf[] = "\0OK"; iov.iov_base = buf; iov.iov_len = sizeof(buf); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); cmsg = CMSG_FIRSTHDR(&msg); cmsg->cmsg_level = SOL_SOCKET; cmsg->cmsg_type = SCM_RIGHTS; cmsg->cmsg_len = CMSG_LEN(sizeof(int)); *(int *)CMSG_DATA(cmsg) = passfd; /* Sum of the length of all control messages in the buffer */ msg.msg_controllen = cmsg->cmsg_len; DEBUG_LOG("passing %s and fd %d\n", buf, passfd); if (sendmsg(socketfd, &msg, 0) < 0) { ERROR_LOG("sendmsg: %m\n"); return false; } return true; } static bool recv_fd(int socketfd, int *recvfd) { char control[255] __attribute__((__aligned__(8))); struct msghdr msg = {.msg_iov = NULL}; struct cmsghdr *cmsg; struct iovec iov; char buf[100]; ssize_t sz = sizeof(buf), ssz; *recvfd = -1; iov.iov_base = buf; iov.iov_len = sz - 1; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = control; msg.msg_controllen = sizeof(control); memset(buf, 0, sz); ssz = recvmsg(socketfd, &msg, 0); if (ssz < 0) { ERROR_LOG("recvmsg: %m\n"); return false; } buf[ssz] = '\0'; INFO_LOG("server response: %u (%s)\n", (unsigned)buf[0], buf + 1); for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) continue; if ((cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int) != 1) continue; *recvfd = *(int *)CMSG_DATA(cmsg); return true; } return false; } int request_fd(const char *socket_path, char *msg, int *result) { struct sockaddr_un sau; int socketfd, receivefd; if (!init_unix_socket(socket_path, &socketfd, &sau)) die("could not init socket"); if (connect(socketfd, (struct sockaddr *)&sau, sizeof(sau)) < 0) die_errno("connect"); if (!send_cred_buffer(socketfd, msg)) die("could not send cred buffer"); if (!recv_fd(socketfd, &receivefd)) die("did not receive tty fd"); NOTICE_LOG("received fd %d\n", receivefd); *result = receivefd; return socketfd; } bool request_int(const char *socket_path, char *msg, int *result) { struct sockaddr_un sau; int socketfd; bool success = false; char buf[100]; ssize_t ssz; *result = -1; if (!init_unix_socket(socket_path, &socketfd, &sau)) return false; if (connect(socketfd, (struct sockaddr *)&sau, sizeof(sau)) < 0) { ERROR_LOG("connect: %m\n"); goto close; } if (!send_cred_buffer(socketfd, msg)) { ERROR_LOG("could not send cred msg \"%s\"\n", msg); goto close; } ssz = read(socketfd, buf, sizeof(buf) - 1); if (ssz < 0) { ERROR_LOG("did not receive integer: %m\n"); goto close; } if (buf[0] != 0) { ERROR_LOG("did not receive integer: %s\n", buf + 1); goto close; } if (ssz != sizeof(int) + 1) { ERROR_LOG("protocol mismatch, server msg: %s\n", buf + 1); goto close; } memcpy(result, buf + 1, sizeof(int)); DEBUG_LOG("received integer: %d\n", *result); success = true; close: close(socketfd); return success; } int signal_pipe[2]; static void signal_handler(int signum) { uint8_t u = signum; int save_errno = errno; assert(signum > 0 && signum < 256); if (write(signal_pipe[1], &u, 1) < 0) ERROR_LOG("write to signal pipe: %m\n"); errno = save_errno; } void init_signal_handling(void) { struct sigaction act; if (pipe(signal_pipe) < 0) die_errno("signal pipe"); act.sa_handler = signal_handler; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; if (sigaction(SIGINT, &act, NULL) < 0) die_errno("sigaction"); if (sigaction(SIGTERM, &act, NULL) < 0) die_errno("sigaction"); if (sigaction(SIGCHLD, &act, NULL) < 0) die_errno("sigaction"); if (sigaction(SIGUSR1, &act, NULL) < 0) die_errno("sigaction"); } int next_signal(void) { uint8_t u = 0; again: if (read(signal_pipe[0], &u, 1) < 0) { if (errno != EINTR) die_errno("read"); goto again; } DEBUG_LOG("process %d received signal %u\n", getpid(), u); return u; }