/*- * Copyright (c) 2005 Andrey Simonenko * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * There are tables with tests descriptions and pointers to test * functions. Each t_*() function returns 0 if its test passed, * -1 if its test failed, -2 if some system error occurred. * If a test function returns -2, then a program exits. * * If a test function forks a client process, then it waits for its * termination. If a return code of a client process is not equal * to zero, or if a client process was terminated by a signal, then * a test function returns -1 or -2 depending on exit status of * a client process. * * Each function which can block, is run under TIMEOUT. If timeout * occurs, then a test function returns -2 or a client process exits * with a non-zero return code. */ #ifndef LISTENQ # define LISTENQ 1 #endif #ifndef TIMEOUT # define TIMEOUT 2 #endif static int t_cmsgcred(void); static int t_sockcred_1(void); static int t_sockcred_2(void); static int t_cmsgcred_sockcred(void); static int t_timeval(void); static int t_bintime(void); static int t_cmsg_len(void); static int t_peercred(void); struct test_func { int (*func)(void); const char *desc; }; static const struct test_func test_stream_tbl[] = { { .func = NULL, .desc = "All tests" }, { .func = t_cmsgcred, .desc = "Sending, receiving cmsgcred" }, { .func = t_sockcred_1, .desc = "Receiving sockcred (listening socket)" }, { .func = t_sockcred_2, .desc = "Receiving sockcred (accepted socket)" }, { .func = t_cmsgcred_sockcred, .desc = "Sending cmsgcred, receiving sockcred" }, { .func = t_timeval, .desc = "Sending, receiving timeval" }, { .func = t_bintime, .desc = "Sending, receiving bintime" }, { .func = t_cmsg_len, .desc = "Check cmsghdr.cmsg_len" }, { .func = t_peercred, .desc = "Check LOCAL_PEERCRED socket option" } }; #define TEST_STREAM_TBL_SIZE \ (sizeof(test_stream_tbl) / sizeof(test_stream_tbl[0])) static const struct test_func test_dgram_tbl[] = { { .func = NULL, .desc = "All tests" }, { .func = t_cmsgcred, .desc = "Sending, receiving cmsgcred" }, { .func = t_sockcred_2, .desc = "Receiving sockcred" }, { .func = t_cmsgcred_sockcred, .desc = "Sending cmsgcred, receiving sockcred" }, { .func = t_timeval, .desc = "Sending, receiving timeval" }, { .func = t_bintime, .desc = "Sending, receiving bintime" }, { .func = t_cmsg_len, .desc = "Check cmsghdr.cmsg_len" } }; #define TEST_DGRAM_TBL_SIZE \ (sizeof(test_dgram_tbl) / sizeof(test_dgram_tbl[0])) static bool debug = false; static bool server_flag = true; static bool send_data_flag = true; static bool send_array_flag = true; static bool failed_flag = false; static int sock_type; static const char *sock_type_str; static const char *proc_name; static char work_dir[] = _PATH_TMP "unix_cmsg.XXXXXXX"; static int serv_sock_fd; static struct sockaddr_un serv_addr_sun; static struct { char *buf_send; char *buf_recv; size_t buf_size; u_int msg_num; } ipc_msg; #define IPC_MSG_NUM_DEF 5 #define IPC_MSG_NUM_MAX 10 #define IPC_MSG_SIZE_DEF 7 #define IPC_MSG_SIZE_MAX 128 static struct { uid_t uid; uid_t euid; gid_t gid; gid_t egid; gid_t *gid_arr; int gid_num; } proc_cred; static pid_t client_pid; #define SYNC_SERVER 0 #define SYNC_CLIENT 1 #define SYNC_RECV 0 #define SYNC_SEND 1 static int sync_fd[2][2]; #define LOGMSG_SIZE 128 static void logmsg(const char *, ...) __printflike(1, 2); static void logmsgx(const char *, ...) __printflike(1, 2); static void dbgmsg(const char *, ...) __printflike(1, 2); static void output(const char *, ...) __printflike(1, 2); static void usage(bool verbose) { u_int i; printf("usage: %s [-dh] [-n num] [-s size] [-t type] " "[-z value] [testno]\n", getprogname()); if (!verbose) return; printf("\n Options are:\n\ -d Output debugging information\n\ -h Output the help message and exit\n\ -n num Number of messages to send\n\ -s size Specify size of data for IPC\n\ -t type Specify socket type (stream, dgram) for tests\n\ -z value Do not send data in a message (bit 0x1), do not send\n\ data array associated with a cmsghdr structure (bit 0x2)\n\ testno Run one test by its number (require the -t option)\n\n"); printf(" Available tests for stream sockets:\n"); for (i = 0; i < TEST_STREAM_TBL_SIZE; ++i) printf(" %u: %s\n", i, test_stream_tbl[i].desc); printf("\n Available tests for datagram sockets:\n"); for (i = 0; i < TEST_DGRAM_TBL_SIZE; ++i) printf(" %u: %s\n", i, test_dgram_tbl[i].desc); } static void output(const char *format, ...) { char buf[LOGMSG_SIZE]; va_list ap; va_start(ap, format); if (vsnprintf(buf, sizeof(buf), format, ap) < 0) err(EXIT_FAILURE, "output: vsnprintf failed"); write(STDOUT_FILENO, buf, strlen(buf)); va_end(ap); } static void logmsg(const char *format, ...) { char buf[LOGMSG_SIZE]; va_list ap; int errno_save; errno_save = errno; va_start(ap, format); if (vsnprintf(buf, sizeof(buf), format, ap) < 0) err(EXIT_FAILURE, "logmsg: vsnprintf failed"); if (errno_save == 0) output("%s: %s\n", proc_name, buf); else output("%s: %s: %s\n", proc_name, buf, strerror(errno_save)); va_end(ap); errno = errno_save; } static void vlogmsgx(const char *format, va_list ap) { char buf[LOGMSG_SIZE]; if (vsnprintf(buf, sizeof(buf), format, ap) < 0) err(EXIT_FAILURE, "logmsgx: vsnprintf failed"); output("%s: %s\n", proc_name, buf); } static void logmsgx(const char *format, ...) { va_list ap; va_start(ap, format); vlogmsgx(format, ap); va_end(ap); } static void dbgmsg(const char *format, ...) { va_list ap; if (debug) { va_start(ap, format); vlogmsgx(format, ap); va_end(ap); } } static int run_tests(int type, u_int testno1) { const struct test_func *tf; u_int i, testno2, failed_num; sock_type = type; if (type == SOCK_STREAM) { sock_type_str = "SOCK_STREAM"; tf = test_stream_tbl; i = TEST_STREAM_TBL_SIZE - 1; } else { sock_type_str = "SOCK_DGRAM"; tf = test_dgram_tbl; i = TEST_DGRAM_TBL_SIZE - 1; } if (testno1 == 0) { testno1 = 1; testno2 = i; } else testno2 = testno1; output("Running tests for %s sockets:\n", sock_type_str); failed_num = 0; for (i = testno1, tf += testno1; i <= testno2; ++tf, ++i) { output(" %u: %s\n", i, tf->desc); switch (tf->func()) { case -1: ++failed_num; break; case -2: logmsgx("some system error or timeout occurred"); return (-1); } } if (failed_num != 0) failed_flag = true; if (testno1 != testno2) { if (failed_num == 0) output("-- all tests passed!\n"); else output("-- %u test%s failed!\n", failed_num, failed_num == 1 ? "" : "s"); } else { if (failed_num == 0) output("-- test passed!\n"); else output("-- test failed!\n"); } return (0); } static int init(void) { struct sigaction sigact; size_t idx; int rv; proc_name = "SERVER"; sigact.sa_handler = SIG_IGN; sigact.sa_flags = 0; sigemptyset(&sigact.sa_mask); if (sigaction(SIGPIPE, &sigact, (struct sigaction *)NULL) < 0) { logmsg("init: sigaction"); return (-1); } if (ipc_msg.buf_size == 0) ipc_msg.buf_send = ipc_msg.buf_recv = NULL; else { ipc_msg.buf_send = malloc(ipc_msg.buf_size); ipc_msg.buf_recv = malloc(ipc_msg.buf_size); if (ipc_msg.buf_send == NULL || ipc_msg.buf_recv == NULL) { logmsg("init: malloc"); return (-1); } for (idx = 0; idx < ipc_msg.buf_size; ++idx) ipc_msg.buf_send[idx] = (char)idx; } proc_cred.uid = getuid(); proc_cred.euid = geteuid(); proc_cred.gid = getgid(); proc_cred.egid = getegid(); proc_cred.gid_num = getgroups(0, (gid_t *)NULL); if (proc_cred.gid_num < 0) { logmsg("init: getgroups"); return (-1); } proc_cred.gid_arr = malloc(proc_cred.gid_num * sizeof(*proc_cred.gid_arr)); if (proc_cred.gid_arr == NULL) { logmsg("init: malloc"); return (-1); } if (getgroups(proc_cred.gid_num, proc_cred.gid_arr) < 0) { logmsg("init: getgroups"); return (-1); } memset(&serv_addr_sun, 0, sizeof(serv_addr_sun)); rv = snprintf(serv_addr_sun.sun_path, sizeof(serv_addr_sun.sun_path), "%s/%s", work_dir, proc_name); if (rv < 0) { logmsg("init: snprintf"); return (-1); } if ((size_t)rv >= sizeof(serv_addr_sun.sun_path)) { logmsgx("init: not enough space for socket pathname"); return (-1); } serv_addr_sun.sun_family = PF_LOCAL; serv_addr_sun.sun_len = SUN_LEN(&serv_addr_sun); return (0); } static int client_fork(void) { int fd1, fd2; if (pipe(sync_fd[SYNC_SERVER]) < 0 || pipe(sync_fd[SYNC_CLIENT]) < 0) { logmsg("client_fork: pipe"); return (-1); } client_pid = fork(); if (client_pid == (pid_t)-1) { logmsg("client_fork: fork"); return (-1); } if (client_pid == 0) { proc_name = "CLIENT"; server_flag = false; fd1 = sync_fd[SYNC_SERVER][SYNC_RECV]; fd2 = sync_fd[SYNC_CLIENT][SYNC_SEND]; } else { fd1 = sync_fd[SYNC_SERVER][SYNC_SEND]; fd2 = sync_fd[SYNC_CLIENT][SYNC_RECV]; } if (close(fd1) < 0 || close(fd2) < 0) { logmsg("client_fork: close"); return (-1); } return (client_pid != 0); } static void client_exit(int rv) { if (close(sync_fd[SYNC_SERVER][SYNC_SEND]) < 0 || close(sync_fd[SYNC_CLIENT][SYNC_RECV]) < 0) { logmsg("client_exit: close"); rv = -1; } rv = rv == 0 ? EXIT_SUCCESS : -rv; dbgmsg("exit: code %d", rv); _exit(rv); } static int client_wait(void) { int status; pid_t pid; dbgmsg("waiting for client"); if (close(sync_fd[SYNC_SERVER][SYNC_RECV]) < 0 || close(sync_fd[SYNC_CLIENT][SYNC_SEND]) < 0) { logmsg("client_wait: close"); return (-1); } pid = waitpid(client_pid, &status, 0); if (pid == (pid_t)-1) { logmsg("client_wait: waitpid"); return (-1); } if (WIFEXITED(status)) { if (WEXITSTATUS(status) != EXIT_SUCCESS) { logmsgx("client exit status is %d", WEXITSTATUS(status)); return (-WEXITSTATUS(status)); } } else { if (WIFSIGNALED(status)) logmsgx("abnormal termination of client, signal %d%s", WTERMSIG(status), WCOREDUMP(status) ? " (core file generated)" : ""); else logmsgx("termination of client, unknown status"); return (-1); } return (0); } int main(int argc, char *argv[]) { const char *errstr; u_int testno, zvalue; int opt, rv; bool dgram_flag, stream_flag; ipc_msg.buf_size = IPC_MSG_SIZE_DEF; ipc_msg.msg_num = IPC_MSG_NUM_DEF; dgram_flag = stream_flag = false; while ((opt = getopt(argc, argv, "dhn:s:t:z:")) != -1) switch (opt) { case 'd': debug = true; break; case 'h': usage(true); return (EXIT_SUCCESS); case 'n': ipc_msg.msg_num = strtonum(optarg, 1, IPC_MSG_NUM_MAX, &errstr); if (errstr != NULL) errx(EXIT_FAILURE, "option -n: number is %s", errstr); break; case 's': ipc_msg.buf_size = strtonum(optarg, 0, IPC_MSG_SIZE_MAX, &errstr); if (errstr != NULL) errx(EXIT_FAILURE, "option -s: number is %s", errstr); break; case 't': if (strcmp(optarg, "stream") == 0) stream_flag = true; else if (strcmp(optarg, "dgram") == 0) dgram_flag = true; else errx(EXIT_FAILURE, "option -t: " "wrong socket type"); break; case 'z': zvalue = strtonum(optarg, 0, 3, &errstr); if (errstr != NULL) errx(EXIT_FAILURE, "option -z: number is %s", errstr); if (zvalue & 0x1) send_data_flag = false; if (zvalue & 0x2) send_array_flag = false; break; default: usage(false); return (EXIT_FAILURE); } if (optind < argc) { if (optind + 1 != argc) errx(EXIT_FAILURE, "too many arguments"); testno = strtonum(argv[optind], 0, UINT_MAX, &errstr); if (errstr != NULL) errx(EXIT_FAILURE, "test number is %s", errstr); if (stream_flag && testno >= TEST_STREAM_TBL_SIZE) errx(EXIT_FAILURE, "given test %u for stream " "sockets does not exist", testno); if (dgram_flag && testno >= TEST_DGRAM_TBL_SIZE) errx(EXIT_FAILURE, "given test %u for datagram " "sockets does not exist", testno); } else testno = 0; if (!dgram_flag && !stream_flag) { if (testno != 0) errx(EXIT_FAILURE, "particular test number " "can be used with the -t option only"); dgram_flag = stream_flag = true; } if (mkdtemp(work_dir) == NULL) err(EXIT_FAILURE, "mkdtemp(%s)", work_dir); rv = EXIT_FAILURE; if (init() < 0) goto done; if (stream_flag) if (run_tests(SOCK_STREAM, testno) < 0) goto done; if (dgram_flag) if (run_tests(SOCK_DGRAM, testno) < 0) goto done; rv = EXIT_SUCCESS; done: if (rmdir(work_dir) < 0) { logmsg("rmdir(%s)", work_dir); rv = EXIT_FAILURE; } return (failed_flag ? EXIT_FAILURE : rv); } static int socket_close(int fd) { int rv; rv = 0; if (close(fd) < 0) { logmsg("socket_close: close"); rv = -1; } if (server_flag && fd == serv_sock_fd) if (unlink(serv_addr_sun.sun_path) < 0) { logmsg("socket_close: unlink(%s)", serv_addr_sun.sun_path); rv = -1; } return (rv); } static int socket_create(void) { struct timeval tv; int fd; fd = socket(PF_LOCAL, sock_type, 0); if (fd < 0) { logmsg("socket_create: socket(PF_LOCAL, %s, 0)", sock_type_str); return (-1); } if (server_flag) serv_sock_fd = fd; tv.tv_sec = TIMEOUT; tv.tv_usec = 0; if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 || setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) { logmsg("socket_create: setsockopt(SO_RCVTIMEO/SO_SNDTIMEO)"); goto failed; } if (server_flag) { if (bind(fd, (struct sockaddr *)&serv_addr_sun, serv_addr_sun.sun_len) < 0) { logmsg("socket_create: bind(%s)", serv_addr_sun.sun_path); goto failed; } if (sock_type == SOCK_STREAM) { int val; if (listen(fd, LISTENQ) < 0) { logmsg("socket_create: listen"); goto failed; } val = fcntl(fd, F_GETFL, 0); if (val < 0) { logmsg("socket_create: fcntl(F_GETFL)"); goto failed; } if (fcntl(fd, F_SETFL, val | O_NONBLOCK) < 0) { logmsg("socket_create: fcntl(F_SETFL)"); goto failed; } } } return (fd); failed: if (close(fd) < 0) logmsg("socket_create: close"); if (server_flag) if (unlink(serv_addr_sun.sun_path) < 0) logmsg("socket_close: unlink(%s)", serv_addr_sun.sun_path); return (-1); } static int socket_connect(int fd) { dbgmsg("connect"); if (connect(fd, (struct sockaddr *)&serv_addr_sun, serv_addr_sun.sun_len) < 0) { logmsg("socket_connect: connect(%s)", serv_addr_sun.sun_path); return (-1); } return (0); } static int sync_recv(void) { ssize_t ssize; int fd; char buf; dbgmsg("sync: wait"); fd = sync_fd[server_flag ? SYNC_SERVER : SYNC_CLIENT][SYNC_RECV]; ssize = read(fd, &buf, 1); if (ssize < 0) { logmsg("sync_recv: read"); return (-1); } if (ssize < 1) { logmsgx("sync_recv: read %zd of 1 byte", ssize); return (-1); } dbgmsg("sync: received"); return (0); } static int sync_send(void) { ssize_t ssize; int fd; dbgmsg("sync: send"); fd = sync_fd[server_flag ? SYNC_CLIENT : SYNC_SERVER][SYNC_SEND]; ssize = write(fd, "", 1); if (ssize < 0) { logmsg("sync_send: write"); return (-1); } if (ssize < 1) { logmsgx("sync_send: sent %zd of 1 byte", ssize); return (-1); } return (0); } static int message_send(int fd, const struct msghdr *msghdr) { const struct cmsghdr *cmsghdr; size_t size; ssize_t ssize; size = msghdr->msg_iov != 0 ? msghdr->msg_iov->iov_len : 0; dbgmsg("send: data size %zu", size); dbgmsg("send: msghdr.msg_controllen %u", (u_int)msghdr->msg_controllen); cmsghdr = CMSG_FIRSTHDR(msghdr); if (cmsghdr != NULL) dbgmsg("send: cmsghdr.cmsg_len %u", (u_int)cmsghdr->cmsg_len); ssize = sendmsg(fd, msghdr, 0); if (ssize < 0) { logmsg("message_send: sendmsg"); return (-1); } if ((size_t)ssize != size) { logmsgx("message_send: sendmsg: sent %zd of %zu bytes", ssize, size); return (-1); } if (!send_data_flag) if (sync_send() < 0) return (-1); return (0); } static int message_sendn(int fd, struct msghdr *msghdr) { u_int i; for (i = 1; i <= ipc_msg.msg_num; ++i) { dbgmsg("message #%u", i); if (message_send(fd, msghdr) < 0) return (-1); } return (0); } static int message_recv(int fd, struct msghdr *msghdr) { const struct cmsghdr *cmsghdr; size_t size; ssize_t ssize; if (!send_data_flag) if (sync_recv() < 0) return (-1); size = msghdr->msg_iov != NULL ? msghdr->msg_iov->iov_len : 0; ssize = recvmsg(fd, msghdr, MSG_WAITALL); if (ssize < 0) { logmsg("message_recv: recvmsg"); return (-1); } if ((size_t)ssize != size) { logmsgx("message_recv: recvmsg: received %zd of %zu bytes", ssize, size); return (-1); } dbgmsg("recv: data size %zd", ssize); dbgmsg("recv: msghdr.msg_controllen %u", (u_int)msghdr->msg_controllen); cmsghdr = CMSG_FIRSTHDR(msghdr); if (cmsghdr != NULL) dbgmsg("recv: cmsghdr.cmsg_len %u", (u_int)cmsghdr->cmsg_len); if (memcmp(ipc_msg.buf_recv, ipc_msg.buf_send, size) != 0) { logmsgx("message_recv: received message has wrong content"); return (-1); } return (0); } static int socket_accept(int listenfd) { fd_set rset; struct timeval tv; int fd, rv, val; dbgmsg("accept"); FD_ZERO(&rset); FD_SET(listenfd, &rset); tv.tv_sec = TIMEOUT; tv.tv_usec = 0; rv = select(listenfd + 1, &rset, (fd_set *)NULL, (fd_set *)NULL, &tv); if (rv < 0) { logmsg("socket_accept: select"); return (-1); } if (rv == 0) { logmsgx("socket_accept: select timeout"); return (-1); } fd = accept(listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL); if (fd < 0) { logmsg("socket_accept: accept"); return (-1); } val = fcntl(fd, F_GETFL, 0); if (val < 0) { logmsg("socket_accept: fcntl(F_GETFL)"); goto failed; } if (fcntl(fd, F_SETFL, val & ~O_NONBLOCK) < 0) { logmsg("socket_accept: fcntl(F_SETFL)"); goto failed; } return (fd); failed: if (close(fd) < 0) logmsg("socket_accept: close"); return (-1); } static int check_msghdr(const struct msghdr *msghdr, size_t size) { if (msghdr->msg_flags & MSG_TRUNC) { logmsgx("msghdr.msg_flags has MSG_TRUNC"); return (-1); } if (msghdr->msg_flags & MSG_CTRUNC) { logmsgx("msghdr.msg_flags has MSG_CTRUNC"); return (-1); } if (msghdr->msg_controllen < size) { logmsgx("msghdr.msg_controllen %u < %zu", (u_int)msghdr->msg_controllen, size); return (-1); } if (msghdr->msg_controllen > 0 && size == 0) { logmsgx("msghdr.msg_controllen %u > 0", (u_int)msghdr->msg_controllen); return (-1); } return (0); } static int check_cmsghdr(const struct cmsghdr *cmsghdr, int type, size_t size) { if (cmsghdr == NULL) { logmsgx("cmsghdr is NULL"); return (-1); } if (cmsghdr->cmsg_level != SOL_SOCKET) { logmsgx("cmsghdr.cmsg_level %d != SOL_SOCKET", cmsghdr->cmsg_level); return (-1); } if (cmsghdr->cmsg_type != type) { logmsgx("cmsghdr.cmsg_type %d != %d", cmsghdr->cmsg_type, type); return (-1); } if (cmsghdr->cmsg_len != CMSG_LEN(size)) { logmsgx("cmsghdr.cmsg_len %u != %zu", (u_int)cmsghdr->cmsg_len, CMSG_LEN(size)); return (-1); } return (0); } static int check_groups(const char *gid_arr_str, const gid_t *gid_arr, const char *gid_num_str, int gid_num, bool all_gids) { int i; for (i = 0; i < gid_num; ++i) dbgmsg("%s[%d] %lu", gid_arr_str, i, (u_long)gid_arr[i]); if (all_gids) { if (gid_num != proc_cred.gid_num) { logmsgx("%s %d != %d", gid_num_str, gid_num, proc_cred.gid_num); return (-1); } } else { if (gid_num > proc_cred.gid_num) { logmsgx("%s %d > %d", gid_num_str, gid_num, proc_cred.gid_num); return (-1); } } if (memcmp(gid_arr, proc_cred.gid_arr, gid_num * sizeof(*gid_arr)) != 0) { logmsgx("%s content is wrong", gid_arr_str); for (i = 0; i < gid_num; ++i) if (gid_arr[i] != proc_cred.gid_arr[i]) { logmsgx("%s[%d] %lu != %lu", gid_arr_str, i, (u_long)gid_arr[i], (u_long)proc_cred.gid_arr[i]); break; } return (-1); } return (0); } static int check_xucred(const struct xucred *xucred, socklen_t len) { if (len != sizeof(*xucred)) { logmsgx("option value size %zu != %zu", (size_t)len, sizeof(*xucred)); return (-1); } dbgmsg("xucred.cr_version %u", xucred->cr_version); dbgmsg("xucred.cr_uid %lu", (u_long)xucred->cr_uid); dbgmsg("xucred.cr_ngroups %d", xucred->cr_ngroups); if (xucred->cr_version != XUCRED_VERSION) { logmsgx("xucred.cr_version %u != %d", xucred->cr_version, XUCRED_VERSION); return (-1); } if (xucred->cr_uid != proc_cred.euid) { logmsgx("xucred.cr_uid %lu != %lu (EUID)", (u_long)xucred->cr_uid, (u_long)proc_cred.euid); return (-1); } if (xucred->cr_ngroups == 0) { logmsgx("xucred.cr_ngroups == 0"); return (-1); } if (xucred->cr_ngroups < 0) { logmsgx("xucred.cr_ngroups < 0"); return (-1); } if (xucred->cr_ngroups > XU_NGROUPS) { logmsgx("xucred.cr_ngroups %hu > %u (max)", xucred->cr_ngroups, XU_NGROUPS); return (-1); } if (xucred->cr_groups[0] != proc_cred.egid) { logmsgx("xucred.cr_groups[0] %lu != %lu (EGID)", (u_long)xucred->cr_groups[0], (u_long)proc_cred.egid); return (-1); } if (check_groups("xucred.cr_groups", xucred->cr_groups, "xucred.cr_ngroups", xucred->cr_ngroups, false) < 0) return (-1); return (0); } static int check_scm_creds_cmsgcred(struct cmsghdr *cmsghdr) { const struct cmsgcred *cmsgcred; if (check_cmsghdr(cmsghdr, SCM_CREDS, sizeof(*cmsgcred)) < 0) return (-1); cmsgcred = (struct cmsgcred *)CMSG_DATA(cmsghdr); dbgmsg("cmsgcred.cmcred_pid %ld", (long)cmsgcred->cmcred_pid); dbgmsg("cmsgcred.cmcred_uid %lu", (u_long)cmsgcred->cmcred_uid); dbgmsg("cmsgcred.cmcred_euid %lu", (u_long)cmsgcred->cmcred_euid); dbgmsg("cmsgcred.cmcred_gid %lu", (u_long)cmsgcred->cmcred_gid); dbgmsg("cmsgcred.cmcred_ngroups %d", cmsgcred->cmcred_ngroups); if (cmsgcred->cmcred_pid != client_pid) { logmsgx("cmsgcred.cmcred_pid %ld != %ld", (long)cmsgcred->cmcred_pid, (long)client_pid); return (-1); } if (cmsgcred->cmcred_uid != proc_cred.uid) { logmsgx("cmsgcred.cmcred_uid %lu != %lu", (u_long)cmsgcred->cmcred_uid, (u_long)proc_cred.uid); return (-1); } if (cmsgcred->cmcred_euid != proc_cred.euid) { logmsgx("cmsgcred.cmcred_euid %lu != %lu", (u_long)cmsgcred->cmcred_euid, (u_long)proc_cred.euid); return (-1); } if (cmsgcred->cmcred_gid != proc_cred.gid) { logmsgx("cmsgcred.cmcred_gid %lu != %lu", (u_long)cmsgcred->cmcred_gid, (u_long)proc_cred.gid); return (-1); } if (cmsgcred->cmcred_ngroups == 0) { logmsgx("cmsgcred.cmcred_ngroups == 0"); return (-1); } if (cmsgcred->cmcred_ngroups < 0) { logmsgx("cmsgcred.cmcred_ngroups %d < 0", cmsgcred->cmcred_ngroups); return (-1); } if (cmsgcred->cmcred_ngroups > CMGROUP_MAX) { logmsgx("cmsgcred.cmcred_ngroups %d > %d", cmsgcred->cmcred_ngroups, CMGROUP_MAX); return (-1); } if (cmsgcred->cmcred_groups[0] != proc_cred.egid) { logmsgx("cmsgcred.cmcred_groups[0] %lu != %lu (EGID)", (u_long)cmsgcred->cmcred_groups[0], (u_long)proc_cred.egid); return (-1); } if (check_groups("cmsgcred.cmcred_groups", cmsgcred->cmcred_groups, "cmsgcred.cmcred_ngroups", cmsgcred->cmcred_ngroups, false) < 0) return (-1); return (0); } static int check_scm_creds_sockcred(struct cmsghdr *cmsghdr) { const struct sockcred *sockcred; if (check_cmsghdr(cmsghdr, SCM_CREDS, SOCKCREDSIZE(proc_cred.gid_num)) < 0) return (-1); sockcred = (struct sockcred *)CMSG_DATA(cmsghdr); dbgmsg("sockcred.sc_uid %lu", (u_long)sockcred->sc_uid); dbgmsg("sockcred.sc_euid %lu", (u_long)sockcred->sc_euid); dbgmsg("sockcred.sc_gid %lu", (u_long)sockcred->sc_gid); dbgmsg("sockcred.sc_egid %lu", (u_long)sockcred->sc_egid); dbgmsg("sockcred.sc_ngroups %d", sockcred->sc_ngroups); if (sockcred->sc_uid != proc_cred.uid) { logmsgx("sockcred.sc_uid %lu != %lu", (u_long)sockcred->sc_uid, (u_long)proc_cred.uid); return (-1); } if (sockcred->sc_euid != proc_cred.euid) { logmsgx("sockcred.sc_euid %lu != %lu", (u_long)sockcred->sc_euid, (u_long)proc_cred.euid); return (-1); } if (sockcred->sc_gid != proc_cred.gid) { logmsgx("sockcred.sc_gid %lu != %lu", (u_long)sockcred->sc_gid, (u_long)proc_cred.gid); return (-1); } if (sockcred->sc_egid != proc_cred.egid) { logmsgx("sockcred.sc_egid %lu != %lu", (u_long)sockcred->sc_egid, (u_long)proc_cred.egid); return (-1); } if (sockcred->sc_ngroups == 0) { logmsgx("sockcred.sc_ngroups == 0"); return (-1); } if (sockcred->sc_ngroups < 0) { logmsgx("sockcred.sc_ngroups %d < 0", sockcred->sc_ngroups); return (-1); } if (sockcred->sc_ngroups != proc_cred.gid_num) { logmsgx("sockcred.sc_ngroups %d != %u", sockcred->sc_ngroups, proc_cred.gid_num); return (-1); } if (check_groups("sockcred.sc_groups", sockcred->sc_groups, "sockcred.sc_ngroups", sockcred->sc_ngroups, true) < 0) return (-1); return (0); } static int check_scm_timestamp(struct cmsghdr *cmsghdr) { const struct timeval *timeval; if (check_cmsghdr(cmsghdr, SCM_TIMESTAMP, sizeof(struct timeval)) < 0) return (-1); timeval = (struct timeval *)CMSG_DATA(cmsghdr); dbgmsg("timeval.tv_sec %"PRIdMAX", timeval.tv_usec %"PRIdMAX, (intmax_t)timeval->tv_sec, (intmax_t)timeval->tv_usec); return (0); } static int check_scm_bintime(struct cmsghdr *cmsghdr) { const struct bintime *bintime; if (check_cmsghdr(cmsghdr, SCM_BINTIME, sizeof(struct bintime)) < 0) return (-1); bintime = (struct bintime *)CMSG_DATA(cmsghdr); dbgmsg("bintime.sec %"PRIdMAX", bintime.frac %"PRIu64, (intmax_t)bintime->sec, bintime->frac); return (0); } static void msghdr_init_generic(struct msghdr *msghdr, struct iovec *iov, void *cmsg_data) { msghdr->msg_name = NULL; msghdr->msg_namelen = 0; if (send_data_flag) { iov->iov_base = server_flag ? ipc_msg.buf_recv : ipc_msg.buf_send; iov->iov_len = ipc_msg.buf_size; msghdr->msg_iov = iov; msghdr->msg_iovlen = 1; } else { msghdr->msg_iov = NULL; msghdr->msg_iovlen = 0; } msghdr->msg_control = cmsg_data; msghdr->msg_flags = 0; } static void msghdr_init_server(struct msghdr *msghdr, struct iovec *iov, void *cmsg_data, size_t cmsg_size) { msghdr_init_generic(msghdr, iov, cmsg_data); msghdr->msg_controllen = cmsg_size; dbgmsg("init: data size %zu", msghdr->msg_iov != NULL ? msghdr->msg_iov->iov_len : (size_t)0); dbgmsg("init: msghdr.msg_controllen %u", (u_int)msghdr->msg_controllen); } static void msghdr_init_client(struct msghdr *msghdr, struct iovec *iov, void *cmsg_data, size_t cmsg_size, int type, size_t arr_size) { struct cmsghdr *cmsghdr; msghdr_init_generic(msghdr, iov, cmsg_data); if (cmsg_data != NULL) { msghdr->msg_controllen = send_array_flag ? cmsg_size : CMSG_SPACE(0); cmsghdr = CMSG_FIRSTHDR(msghdr); cmsghdr->cmsg_level = SOL_SOCKET; cmsghdr->cmsg_type = type; cmsghdr->cmsg_len = CMSG_LEN(send_array_flag ? arr_size : 0); } else msghdr->msg_controllen = 0; } static int t_generic(int (*client_func)(int), int (*server_func)(int)) { int fd, rv, rv_client; switch (client_fork()) { case 0: fd = socket_create(); if (fd < 0) rv = -2; else { rv = client_func(fd); if (socket_close(fd) < 0) rv = -2; } client_exit(rv); break; case 1: fd = socket_create(); if (fd < 0) rv = -2; else { rv = server_func(fd); rv_client = client_wait(); if (rv == 0 || (rv == -2 && rv_client != 0)) rv = rv_client; if (socket_close(fd) < 0) rv = -2; } break; default: rv = -2; } return (rv); } static int t_cmsgcred_client(int fd) { struct msghdr msghdr; struct iovec iov[1]; void *cmsg_data; size_t cmsg_size; int rv; if (sync_recv() < 0) return (-2); rv = -2; cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred)); cmsg_data = malloc(cmsg_size); if (cmsg_data == NULL) { logmsg("malloc"); goto done; } msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size, SCM_CREDS, sizeof(struct cmsgcred)); if (socket_connect(fd) < 0) goto done; if (message_sendn(fd, &msghdr) < 0) goto done; rv = 0; done: free(cmsg_data); return (rv); } static int t_cmsgcred_server(int fd1) { struct msghdr msghdr; struct iovec iov[1]; struct cmsghdr *cmsghdr; void *cmsg_data; size_t cmsg_size; u_int i; int fd2, rv; if (sync_send() < 0) return (-2); fd2 = -1; rv = -2; cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred)); cmsg_data = malloc(cmsg_size); if (cmsg_data == NULL) { logmsg("malloc"); goto done; } if (sock_type == SOCK_STREAM) { fd2 = socket_accept(fd1); if (fd2 < 0) goto done; } else fd2 = fd1; rv = -1; for (i = 1; i <= ipc_msg.msg_num; ++i) { dbgmsg("message #%u", i); msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); if (message_recv(fd2, &msghdr) < 0) { rv = -2; break; } if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) break; cmsghdr = CMSG_FIRSTHDR(&msghdr); if (check_scm_creds_cmsgcred(cmsghdr) < 0) break; } if (i > ipc_msg.msg_num) rv = 0; done: free(cmsg_data); if (sock_type == SOCK_STREAM && fd2 >= 0) if (socket_close(fd2) < 0) rv = -2; return (rv); } static int t_cmsgcred(void) { return (t_generic(t_cmsgcred_client, t_cmsgcred_server)); } static int t_sockcred_client(int type, int fd) { struct msghdr msghdr; struct iovec iov[1]; int rv; if (sync_recv() < 0) return (-2); rv = -2; msghdr_init_client(&msghdr, iov, NULL, 0, 0, 0); if (socket_connect(fd) < 0) goto done; if (type == 2) if (sync_recv() < 0) goto done; if (message_sendn(fd, &msghdr) < 0) goto done; rv = 0; done: return (rv); } static int t_sockcred_server(int type, int fd1) { struct msghdr msghdr; struct iovec iov[1]; struct cmsghdr *cmsghdr; void *cmsg_data; size_t cmsg_size; u_int i; int fd2, rv, val; fd2 = -1; rv = -2; cmsg_size = CMSG_SPACE(SOCKCREDSIZE(proc_cred.gid_num)); cmsg_data = malloc(cmsg_size); if (cmsg_data == NULL) { logmsg("malloc"); goto done; } if (type == 1) { dbgmsg("setting LOCAL_CREDS"); val = 1; if (setsockopt(fd1, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) { logmsg("setsockopt(LOCAL_CREDS)"); goto done; } } if (sync_send() < 0) goto done; if (sock_type == SOCK_STREAM) { fd2 = socket_accept(fd1); if (fd2 < 0) goto done; } else fd2 = fd1; if (type == 2) { dbgmsg("setting LOCAL_CREDS"); val = 1; if (setsockopt(fd2, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) { logmsg("setsockopt(LOCAL_CREDS)"); goto done; } if (sync_send() < 0) goto done; } rv = -1; for (i = 1; i <= ipc_msg.msg_num; ++i) { dbgmsg("message #%u", i); msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); if (message_recv(fd2, &msghdr) < 0) { rv = -2; break; } if (i > 1 && sock_type == SOCK_STREAM) { if (check_msghdr(&msghdr, 0) < 0) break; } else { if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) break; cmsghdr = CMSG_FIRSTHDR(&msghdr); if (check_scm_creds_sockcred(cmsghdr) < 0) break; } } if (i > ipc_msg.msg_num) rv = 0; done: free(cmsg_data); if (sock_type == SOCK_STREAM && fd2 >= 0) if (socket_close(fd2) < 0) rv = -2; return (rv); } static int t_sockcred_1(void) { u_int i; int fd, rv, rv_client; switch (client_fork()) { case 0: for (i = 1; i <= 2; ++i) { dbgmsg("client #%u", i); fd = socket_create(); if (fd < 0) rv = -2; else { rv = t_sockcred_client(1, fd); if (socket_close(fd) < 0) rv = -2; } if (rv != 0) break; } client_exit(rv); break; case 1: fd = socket_create(); if (fd < 0) rv = -2; else { rv = t_sockcred_server(1, fd); if (rv == 0) rv = t_sockcred_server(3, fd); rv_client = client_wait(); if (rv == 0 || (rv == -2 && rv_client != 0)) rv = rv_client; if (socket_close(fd) < 0) rv = -2; } break; default: rv = -2; } return (rv); } static int t_sockcred_2_client(int fd) { return (t_sockcred_client(2, fd)); } static int t_sockcred_2_server(int fd) { return (t_sockcred_server(2, fd)); } static int t_sockcred_2(void) { return (t_generic(t_sockcred_2_client, t_sockcred_2_server)); } static int t_cmsgcred_sockcred_server(int fd1) { struct msghdr msghdr; struct iovec iov[1]; struct cmsghdr *cmsghdr; void *cmsg_data, *cmsg1_data, *cmsg2_data; size_t cmsg_size, cmsg1_size, cmsg2_size; u_int i; int fd2, rv, val; fd2 = -1; rv = -2; cmsg1_size = CMSG_SPACE(SOCKCREDSIZE(proc_cred.gid_num)); cmsg2_size = CMSG_SPACE(sizeof(struct cmsgcred)); cmsg1_data = malloc(cmsg1_size); cmsg2_data = malloc(cmsg2_size); if (cmsg1_data == NULL || cmsg2_data == NULL) { logmsg("malloc"); goto done; } dbgmsg("setting LOCAL_CREDS"); val = 1; if (setsockopt(fd1, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) { logmsg("setsockopt(LOCAL_CREDS)"); goto done; } if (sync_send() < 0) goto done; if (sock_type == SOCK_STREAM) { fd2 = socket_accept(fd1); if (fd2 < 0) goto done; } else fd2 = fd1; cmsg_data = cmsg1_data; cmsg_size = cmsg1_size; rv = -1; for (i = 1; i <= ipc_msg.msg_num; ++i) { dbgmsg("message #%u", i); msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); if (message_recv(fd2, &msghdr) < 0) { rv = -2; break; } if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) break; cmsghdr = CMSG_FIRSTHDR(&msghdr); if (i == 1 || sock_type == SOCK_DGRAM) { if (check_scm_creds_sockcred(cmsghdr) < 0) break; } else { if (check_scm_creds_cmsgcred(cmsghdr) < 0) break; } cmsg_data = cmsg2_data; cmsg_size = cmsg2_size; } if (i > ipc_msg.msg_num) rv = 0; done: free(cmsg1_data); free(cmsg2_data); if (sock_type == SOCK_STREAM && fd2 >= 0) if (socket_close(fd2) < 0) rv = -2; return (rv); } static int t_cmsgcred_sockcred(void) { return (t_generic(t_cmsgcred_client, t_cmsgcred_sockcred_server)); } static int t_timeval_client(int fd) { struct msghdr msghdr; struct iovec iov[1]; void *cmsg_data; size_t cmsg_size; int rv; if (sync_recv() < 0) return (-2); rv = -2; cmsg_size = CMSG_SPACE(sizeof(struct timeval)); cmsg_data = malloc(cmsg_size); if (cmsg_data == NULL) { logmsg("malloc"); goto done; } msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size, SCM_TIMESTAMP, sizeof(struct timeval)); if (socket_connect(fd) < 0) goto done; if (message_sendn(fd, &msghdr) < 0) goto done; rv = 0; done: free(cmsg_data); return (rv); } static int t_timeval_server(int fd1) { struct msghdr msghdr; struct iovec iov[1]; struct cmsghdr *cmsghdr; void *cmsg_data; size_t cmsg_size; u_int i; int fd2, rv; if (sync_send() < 0) return (-2); fd2 = -1; rv = -2; cmsg_size = CMSG_SPACE(sizeof(struct timeval)); cmsg_data = malloc(cmsg_size); if (cmsg_data == NULL) { logmsg("malloc"); goto done; } if (sock_type == SOCK_STREAM) { fd2 = socket_accept(fd1); if (fd2 < 0) goto done; } else fd2 = fd1; rv = -1; for (i = 1; i <= ipc_msg.msg_num; ++i) { dbgmsg("message #%u", i); msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); if (message_recv(fd2, &msghdr) < 0) { rv = -2; break; } if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) break; cmsghdr = CMSG_FIRSTHDR(&msghdr); if (check_scm_timestamp(cmsghdr) < 0) break; } if (i > ipc_msg.msg_num) rv = 0; done: free(cmsg_data); if (sock_type == SOCK_STREAM && fd2 >= 0) if (socket_close(fd2) < 0) rv = -2; return (rv); } static int t_timeval(void) { return (t_generic(t_timeval_client, t_timeval_server)); } static int t_bintime_client(int fd) { struct msghdr msghdr; struct iovec iov[1]; void *cmsg_data; size_t cmsg_size; int rv; if (sync_recv() < 0) return (-2); rv = -2; cmsg_size = CMSG_SPACE(sizeof(struct bintime)); cmsg_data = malloc(cmsg_size); if (cmsg_data == NULL) { logmsg("malloc"); goto done; } msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size, SCM_BINTIME, sizeof(struct bintime)); if (socket_connect(fd) < 0) goto done; if (message_sendn(fd, &msghdr) < 0) goto done; rv = 0; done: free(cmsg_data); return (rv); } static int t_bintime_server(int fd1) { struct msghdr msghdr; struct iovec iov[1]; struct cmsghdr *cmsghdr; void *cmsg_data; size_t cmsg_size; u_int i; int fd2, rv; if (sync_send() < 0) return (-2); fd2 = -1; rv = -2; cmsg_size = CMSG_SPACE(sizeof(struct bintime)); cmsg_data = malloc(cmsg_size); if (cmsg_data == NULL) { logmsg("malloc"); goto done; } if (sock_type == SOCK_STREAM) { fd2 = socket_accept(fd1); if (fd2 < 0) goto done; } else fd2 = fd1; rv = -1; for (i = 1; i <= ipc_msg.msg_num; ++i) { dbgmsg("message #%u", i); msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); if (message_recv(fd2, &msghdr) < 0) { rv = -2; break; } if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) break; cmsghdr = CMSG_FIRSTHDR(&msghdr); if (check_scm_bintime(cmsghdr) < 0) break; } if (i > ipc_msg.msg_num) rv = 0; done: free(cmsg_data); if (sock_type == SOCK_STREAM && fd2 >= 0) if (socket_close(fd2) < 0) rv = -2; return (rv); } static int t_bintime(void) { return (t_generic(t_bintime_client, t_bintime_server)); } static int t_cmsg_len_client(int fd) { struct msghdr msghdr; struct iovec iov[1]; struct cmsghdr *cmsghdr; void *cmsg_data; size_t size, cmsg_size; socklen_t socklen; int rv; if (sync_recv() < 0) return (-2); rv = -2; cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred)); cmsg_data = malloc(cmsg_size); if (cmsg_data == NULL) { logmsg("malloc"); goto done; } msghdr_init_client(&msghdr, iov, cmsg_data, cmsg_size, SCM_CREDS, sizeof(struct cmsgcred)); cmsghdr = CMSG_FIRSTHDR(&msghdr); if (socket_connect(fd) < 0) goto done; size = msghdr.msg_iov != NULL ? msghdr.msg_iov->iov_len : 0; rv = -1; for (socklen = 0; socklen < CMSG_LEN(0); ++socklen) { cmsghdr->cmsg_len = socklen; dbgmsg("send: data size %zu", size); dbgmsg("send: msghdr.msg_controllen %u", (u_int)msghdr.msg_controllen); dbgmsg("send: cmsghdr.cmsg_len %u", (u_int)cmsghdr->cmsg_len); if (sendmsg(fd, &msghdr, 0) < 0) continue; logmsgx("sent message with cmsghdr.cmsg_len %u < %u", (u_int)cmsghdr->cmsg_len, (u_int)CMSG_LEN(0)); break; } if (socklen == CMSG_LEN(0)) rv = 0; if (sync_send() < 0) { rv = -2; goto done; } done: free(cmsg_data); return (rv); } static int t_cmsg_len_server(int fd1) { int fd2, rv; if (sync_send() < 0) return (-2); rv = -2; if (sock_type == SOCK_STREAM) { fd2 = socket_accept(fd1); if (fd2 < 0) goto done; } else fd2 = fd1; if (sync_recv() < 0) goto done; rv = 0; done: if (sock_type == SOCK_STREAM && fd2 >= 0) if (socket_close(fd2) < 0) rv = -2; return (rv); } static int t_cmsg_len(void) { return (t_generic(t_cmsg_len_client, t_cmsg_len_server)); } static int t_peercred_client(int fd) { struct xucred xucred; socklen_t len; if (sync_recv() < 0) return (-1); if (socket_connect(fd) < 0) return (-1); len = sizeof(xucred); if (getsockopt(fd, 0, LOCAL_PEERCRED, &xucred, &len) < 0) { logmsg("getsockopt(LOCAL_PEERCRED)"); return (-1); } if (check_xucred(&xucred, len) < 0) return (-1); return (0); } static int t_peercred_server(int fd1) { struct xucred xucred; socklen_t len; int fd2, rv; if (sync_send() < 0) return (-2); fd2 = socket_accept(fd1); if (fd2 < 0) return (-2); len = sizeof(xucred); if (getsockopt(fd2, 0, LOCAL_PEERCRED, &xucred, &len) < 0) { logmsg("getsockopt(LOCAL_PEERCRED)"); rv = -2; goto done; } if (check_xucred(&xucred, len) < 0) { rv = -1; goto done; } rv = 0; done: if (socket_close(fd2) < 0) rv = -2; return (rv); } static int t_peercred(void) { return (t_generic(t_peercred_client, t_peercred_server)); }