/*- * 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 /* * 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 (something wrong was found in local domain * control messages), -2 if some system error occurred. If test * function returns -2, then a program exits. * * Each test function completely control what to do (eg. fork or * do not fork a client process). 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 test function returns -2. * * Each test function and complete program are not optimized * a lot to allow easy to modify tests. * * Each function which can block, is run under TIMEOUT, if timeout * occurs, then test function returns -2 or a client process exits * with nonzero return code. */ #ifndef LISTENQ # define LISTENQ 1 #endif #ifndef TIMEOUT # define TIMEOUT 60 #endif #define EXTRA_CMSG_SPACE 512 /* Memory for not expected control data. */ static int t_cmsgcred(void), t_sockcred_stream1(void); static int t_sockcred_stream2(void), t_cmsgcred_sockcred(void); static int t_sockcred_dgram(void), t_timestamp(void); struct test_func { int (*func)(void); /* Pointer to function. */ const char *desc; /* Test description. */ }; static struct test_func test_stream_tbl[] = { { NULL, " 0: All tests" }, { t_cmsgcred, " 1: Sending, receiving cmsgcred" }, { t_sockcred_stream1, " 2: Receiving sockcred (listening socket has LOCAL_CREDS)" }, { t_sockcred_stream2, " 3: Receiving sockcred (accepted socket has LOCAL_CREDS)" }, { t_cmsgcred_sockcred, " 4: Sending cmsgcred, receiving sockcred" }, { t_timestamp, " 5: Sending, receiving timestamp" }, { NULL, NULL } }; static struct test_func test_dgram_tbl[] = { { NULL, " 0: All tests" }, { t_cmsgcred, " 1: Sending, receiving cmsgcred" }, { t_sockcred_dgram, " 2: Receiving sockcred" }, { t_cmsgcred_sockcred, " 3: Sending cmsgcred, receiving sockcred" }, { t_timestamp, " 4: Sending, receiving timestamp" }, { NULL, NULL } }; #define TEST_STREAM_NO_MAX (sizeof(test_stream_tbl) / sizeof(struct test_func) - 2) #define TEST_DGRAM_NO_MAX (sizeof(test_dgram_tbl) / sizeof(struct test_func) - 2) static const char *myname = "SERVER"; /* "SERVER" or "CLIENT" */ static int debug = 0; /* 1, if -d. */ static int no_control_data = 0; /* 1, if -z. */ static u_int nfailed = 0; /* Number of failed tests. */ static int sock_type; /* SOCK_STREAM or SOCK_DGRAM */ static const char *sock_type_str; /* "SOCK_STREAM" or "SOCK_DGRAN" */ static char tempdir[] = "/tmp/unix_cmsg.XXXXXXX"; static char serv_sock_path[PATH_MAX]; static char ipc_message[] = "hello"; #define IPC_MESSAGE_SIZE (sizeof(ipc_message)) static struct sockaddr_un servaddr; /* Server address. */ static sigjmp_buf env_alrm; static uid_t my_uid; static uid_t my_euid; static gid_t my_gid; static gid_t my_egid; /* * my_gids[0] is EGID, next items are supplementary GIDs, * my_ngids determines valid items in my_gids array. */ static gid_t my_gids[NGROUPS_MAX]; static int my_ngids; static pid_t client_pid; /* PID of forked client. */ #define dbgmsg(x) do { \ if (debug) \ logmsgx x ; \ } while (/* CONSTCOND */0) static void logmsg(const char *, ...) __printflike(1, 2); static void logmsgx(const char *, ...) __printflike(1, 2); static void output(const char *, ...) __printflike(1, 2); extern char *__progname; /* The name of program. */ /* * Output the help message (-h switch). */ static void usage(int quick) { const struct test_func *test_func; fprintf(stderr, "Usage: %s [-dhz] [-t ] [testno]\n", __progname); if (quick) return; fprintf(stderr, "\n Options are:\n\ -d\t\t\tOutput debugging information\n\ -h\t\t\tOutput this help message and exit\n\ -t \t\tRun test only for the given socket type:\n\ \t\t\tstream or dgram\n\ -z\t\t\tDo not send real control data if possible\n\n"); fprintf(stderr, " Available tests for stream sockets:\n"); for (test_func = test_stream_tbl; test_func->desc != NULL; ++test_func) fprintf(stderr, " %s\n", test_func->desc); fprintf(stderr, "\n Available tests for datagram sockets:\n"); for (test_func = test_dgram_tbl; test_func->desc != NULL; ++test_func) fprintf(stderr, " %s\n", test_func->desc); } /* * printf-like function for outputting to STDOUT_FILENO. */ static void output(const char *format, ...) { char buf[128]; va_list ap; va_start(ap, format); if (vsnprintf(buf, sizeof(buf), format, ap) < 0) err(EX_SOFTWARE, "output: vsnprintf failed"); write(STDOUT_FILENO, buf, strlen(buf)); va_end(ap); } /* * printf-like function for logging, also outputs message for errno. */ static void logmsg(const char *format, ...) { char buf[128]; va_list ap; int errno_save; errno_save = errno; /* Save errno. */ va_start(ap, format); if (vsnprintf(buf, sizeof(buf), format, ap) < 0) err(EX_SOFTWARE, "logmsg: vsnprintf failed"); if (errno_save == 0) output("%s: %s\n", myname, buf); else output("%s: %s: %s\n", myname, buf, strerror(errno_save)); va_end(ap); errno = errno_save; /* Restore errno. */ } /* * printf-like function for logging, do not output message for errno. */ static void logmsgx(const char *format, ...) { char buf[128]; va_list ap; va_start(ap, format); if (vsnprintf(buf, sizeof(buf), format, ap) < 0) err(EX_SOFTWARE, "logmsgx: vsnprintf failed"); output("%s: %s\n", myname, buf); va_end(ap); } /* * Run tests from testno1 to testno2. */ static int run_tests(u_int testno1, u_int testno2) { const struct test_func *test_func; u_int i, nfailed1; output("Running tests for %s sockets:\n", sock_type_str); test_func = (sock_type == SOCK_STREAM ? test_stream_tbl : test_dgram_tbl) + testno1; nfailed1 = 0; for (i = testno1; i <= testno2; ++test_func, ++i) { output(" %s\n", test_func->desc); switch (test_func->func()) { case -1: ++nfailed1; break; case -2: logmsgx("some system error occurred, exiting"); return (-1); } } nfailed += nfailed1; if (testno1 != testno2) { if (nfailed1 == 0) output("-- all tests were passed!\n"); else output("-- %u test%s failed!\n", nfailed1, nfailed1 == 1 ? "" : "s"); } else { if (nfailed == 0) output("-- test was passed!\n"); else output("-- test failed!\n"); } return (0); } /* ARGSUSED */ static void sig_alrm(int signo __unused) { siglongjmp(env_alrm, 1); } /* * Initialize signals handlers. */ static void sig_init(void) { struct sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(SIGPIPE, &sa, (struct sigaction *)NULL) < 0) err(EX_OSERR, "sigaction(SIGPIPE)"); sa.sa_handler = sig_alrm; if (sigaction(SIGALRM, &sa, (struct sigaction *)NULL) < 0) err(EX_OSERR, "sigaction(SIGALRM)"); } int main(int argc, char *argv[]) { const char *errstr; int opt, dgramflag, streamflag; u_int testno1, testno2; dgramflag = streamflag = 0; while ((opt = getopt(argc, argv, "dht:z")) != -1) switch (opt) { case 'd': debug = 1; break; case 'h': usage(0); return (EX_OK); case 't': if (strcmp(optarg, "stream") == 0) streamflag = 1; else if (strcmp(optarg, "dgram") == 0) dgramflag = 1; else errx(EX_USAGE, "wrong socket type in -t option"); break; case 'z': no_control_data = 1; break; case '?': default: usage(1); return (EX_USAGE); } if (optind < argc) { if (optind + 1 != argc) errx(EX_USAGE, "too many arguments"); testno1 = strtonum(argv[optind], 0, UINT_MAX, &errstr); if (errstr != NULL) errx(EX_USAGE, "wrong test number: %s", errstr); } else testno1 = 0; if (dgramflag == 0 && streamflag == 0) dgramflag = streamflag = 1; if (dgramflag && streamflag && testno1 != 0) errx(EX_USAGE, "you can use particular test, only with datagram or stream sockets"); if (streamflag) { if (testno1 > TEST_STREAM_NO_MAX) errx(EX_USAGE, "given test %u for stream sockets does not exist", testno1); } else { if (testno1 > TEST_DGRAM_NO_MAX) errx(EX_USAGE, "given test %u for datagram sockets does not exist", testno1); } my_uid = getuid(); my_euid = geteuid(); my_gid = getgid(); my_egid = getegid(); switch (my_ngids = getgroups(sizeof(my_gids) / sizeof(my_gids[0]), my_gids)) { case -1: err(EX_SOFTWARE, "getgroups"); /* NOTREACHED */ case 0: errx(EX_OSERR, "getgroups returned 0 groups"); } sig_init(); if (mkdtemp(tempdir) == NULL) err(EX_OSERR, "mkdtemp"); if (streamflag) { sock_type = SOCK_STREAM; sock_type_str = "SOCK_STREAM"; if (testno1 == 0) { testno1 = 1; testno2 = TEST_STREAM_NO_MAX; } else testno2 = testno1; if (run_tests(testno1, testno2) < 0) goto failed; testno1 = 0; } if (dgramflag) { sock_type = SOCK_DGRAM; sock_type_str = "SOCK_DGRAM"; if (testno1 == 0) { testno1 = 1; testno2 = TEST_DGRAM_NO_MAX; } else testno2 = testno1; if (run_tests(testno1, testno2) < 0) goto failed; } if (rmdir(tempdir) < 0) { logmsg("rmdir(%s)", tempdir); return (EX_OSERR); } return (nfailed ? EX_OSERR : EX_OK); failed: if (rmdir(tempdir) < 0) logmsg("rmdir(%s)", tempdir); return (EX_OSERR); } /* * Create PF_LOCAL socket, if sock_path is not equal to NULL, then * bind() it. Return socket address in addr. Return file descriptor * or -1 if some error occurred. */ static int create_socket(char *sock_path, size_t sock_path_len, struct sockaddr_un *addr) { int rv, fd; if ((fd = socket(PF_LOCAL, sock_type, 0)) < 0) { logmsg("create_socket: socket(PF_LOCAL, %s, 0)", sock_type_str); return (-1); } if (sock_path != NULL) { if ((rv = snprintf(sock_path, sock_path_len, "%s/%s", tempdir, myname)) < 0) { logmsg("create_socket: snprintf failed"); goto failed; } if ((size_t)rv >= sock_path_len) { logmsgx("create_socket: too long path name for given buffer"); goto failed; } memset(addr, 0, sizeof(addr)); addr->sun_family = AF_LOCAL; if (strlen(sock_path) >= sizeof(addr->sun_path)) { logmsgx("create_socket: too long path name (>= %lu) for local domain socket", (u_long)sizeof(addr->sun_path)); goto failed; } strcpy(addr->sun_path, sock_path); if (bind(fd, (struct sockaddr *)addr, SUN_LEN(addr)) < 0) { logmsg("create_socket: bind(%s)", sock_path); goto failed; } } return (fd); failed: if (close(fd) < 0) logmsg("create_socket: close"); return (-1); } /* * Call create_socket() for server listening socket. * Return socket descriptor or -1 if some error occurred. */ static int create_server_socket(void) { return (create_socket(serv_sock_path, sizeof(serv_sock_path), &servaddr)); } /* * Create unbound socket. */ static int create_unbound_socket(void) { return (create_socket((char *)NULL, 0, (struct sockaddr_un *)NULL)); } /* * Close socket descriptor, if sock_path is not equal to NULL, * then unlink the given path. */ static int close_socket(const char *sock_path, int fd) { int error = 0; if (close(fd) < 0) { logmsg("close_socket: close"); error = -1; } if (sock_path != NULL) if (unlink(sock_path) < 0) { logmsg("close_socket: unlink(%s)", sock_path); error = -1; } return (error); } /* * Connect to server (socket address in servaddr). */ static int connect_server(int fd) { dbgmsg(("connecting to %s", serv_sock_path)); /* * If PF_LOCAL listening socket's queue is full, then connect() * returns ECONNREFUSED immediately, do not need timeout. */ if (connect(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { logmsg("connect_server: connect(%s)", serv_sock_path); return (-1); } return (0); } /* * sendmsg() with timeout. */ static int sendmsg_timeout(int fd, struct msghdr *msg, size_t n) { ssize_t nsent; dbgmsg(("sending %lu bytes", (u_long)n)); if (sigsetjmp(env_alrm, 1) != 0) { logmsgx("sendmsg_timeout: cannot send message to %s (timeout)", serv_sock_path); return (-1); } (void)alarm(TIMEOUT); nsent = sendmsg(fd, msg, 0); (void)alarm(0); if (nsent < 0) { logmsg("sendmsg_timeout: sendmsg"); return (-1); } if ((size_t)nsent != n) { logmsgx("sendmsg_timeout: sendmsg: short send: %ld of %lu bytes", (long)nsent, (u_long)n); return (-1); } return (0); } /* * accept() with timeout. */ static int accept_timeout(int listenfd) { int fd; dbgmsg(("accepting connection")); if (sigsetjmp(env_alrm, 1) != 0) { logmsgx("accept_timeout: cannot accept connection (timeout)"); return (-1); } (void)alarm(TIMEOUT); fd = accept(listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL); (void)alarm(0); if (fd < 0) { logmsg("accept_timeout: accept"); return (-1); } return (fd); } /* * recvmsg() with timeout. */ static int recvmsg_timeout(int fd, struct msghdr *msg, size_t n) { ssize_t nread; dbgmsg(("receiving %lu bytes", (u_long)n)); if (sigsetjmp(env_alrm, 1) != 0) { logmsgx("recvmsg_timeout: cannot receive message (timeout)"); return (-1); } (void)alarm(TIMEOUT); nread = recvmsg(fd, msg, MSG_WAITALL); (void)alarm(0); if (nread < 0) { logmsg("recvmsg_timeout: recvmsg"); return (-1); } if ((size_t)nread != n) { logmsgx("recvmsg_timeout: recvmsg: short read: %ld of %lu bytes", (long)nread, (u_long)n); return (-1); } return (0); } /* * Wait for synchronization message (1 byte) with timeout. */ static int sync_recv(int fd) { ssize_t nread; char buf; dbgmsg(("waiting for sync message")); if (sigsetjmp(env_alrm, 1) != 0) { logmsgx("sync_recv: cannot receive sync message (timeout)"); return (-1); } (void)alarm(TIMEOUT); nread = read(fd, &buf, 1); (void)alarm(0); if (nread < 0) { logmsg("sync_recv: read"); return (-1); } if (nread != 1) { logmsgx("sync_recv: read: short read: %ld of 1 byte", (long)nread); return (-1); } return (0); } /* * Send synchronization message (1 byte) with timeout. */ static int sync_send(int fd) { ssize_t nsent; dbgmsg(("sending sync message")); if (sigsetjmp(env_alrm, 1) != 0) { logmsgx("sync_send: cannot send sync message (timeout)"); return (-1); } (void)alarm(TIMEOUT); nsent = write(fd, "", 1); (void)alarm(0); if (nsent < 0) { logmsg("sync_send: write"); return (-1); } if (nsent != 1) { logmsgx("sync_send: write: short write: %ld of 1 byte", (long)nsent); return (-1); } return (0); } /* * waitpid() for client with timeout. */ static int wait_client(void) { int status; pid_t pid; if (sigsetjmp(env_alrm, 1) != 0) { logmsgx("wait_client: cannot get exit status of client PID %ld (timeout)", (long)client_pid); return (-1); } (void)alarm(TIMEOUT); pid = waitpid(client_pid, &status, 0); (void)alarm(0); if (pid == (pid_t)-1) { logmsg("wait_client: waitpid"); return (-1); } if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { logmsgx("wait_client: exit status of client PID %ld is %d", (long)client_pid, WEXITSTATUS(status)); return (-1); } } else { if (WIFSIGNALED(status)) logmsgx("wait_client: abnormal termination of client PID %ld, signal %d%s", (long)client_pid, WTERMSIG(status), WCOREDUMP(status) ? " (core file generated)" : ""); else logmsgx("wait_client: termination of client PID %ld, unknown status", (long)client_pid); return (-1); } return (0); } /* * Check if n supplementary GIDs in gids are correct. (my_gids + 1) * has (my_ngids - 1) supplementary GIDs of current process. */ static int check_groups(const gid_t *gids, int n) { char match[NGROUPS_MAX] = { 0 }; int error, i, j; if (n != my_ngids - 1) { logmsgx("wrong number of groups %d != %d (returned from getgroups() - 1)", n, my_ngids - 1); error = -1; } else error = 0; for (i = 0; i < n; ++i) { for (j = 1; j < my_ngids; ++j) { if (gids[i] == my_gids[j]) { if (match[j]) { logmsgx("duplicated GID %lu", (u_long)gids[i]); error = -1; } else match[j] = 1; break; } } if (j == my_ngids) { logmsgx("unexpected GID %lu", (u_long)gids[i]); error = -1; } } for (j = 1; j < my_ngids; ++j) if (match[j] == 0) { logmsgx("did not receive supplementary GID %u", my_gids[j]); error = -1; } return (error); } /* * Send n messages with data and control message with SCM_CREDS type * to server and exit. */ static void t_cmsgcred_client(u_int n) { union { struct cmsghdr cm; char control[CMSG_SPACE(sizeof(struct cmsgcred))]; } control_un; struct msghdr msg; struct iovec iov[1]; struct cmsghdr *cmptr; int fd; u_int i; assert(n == 1 || n == 2); if ((fd = create_unbound_socket()) < 0) goto failed; if (connect_server(fd) < 0) goto failed_close; iov[0].iov_base = ipc_message; iov[0].iov_len = IPC_MESSAGE_SIZE; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control_un.control; msg.msg_controllen = no_control_data ? sizeof(struct cmsghdr) : sizeof(control_un.control); msg.msg_flags = 0; cmptr = CMSG_FIRSTHDR(&msg); cmptr->cmsg_len = CMSG_LEN(no_control_data ? 0 : sizeof(struct cmsgcred)); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_CREDS; for (i = 0; i < n; ++i) { dbgmsg(("#%u msg_controllen = %u, cmsg_len = %u", i, (u_int)msg.msg_controllen, (u_int)cmptr->cmsg_len)); if (sendmsg_timeout(fd, &msg, IPC_MESSAGE_SIZE) < 0) goto failed_close; } if (close_socket((const char *)NULL, fd) < 0) goto failed; _exit(0); failed_close: (void)close_socket((const char *)NULL, fd); failed: _exit(1); } /* * Receive two messages with data and control message with SCM_CREDS * type followed by struct cmsgcred{} from client. fd1 is a listen * socket for stream sockets or simply socket for datagram sockets. */ static int t_cmsgcred_server(int fd1) { char buf[IPC_MESSAGE_SIZE]; union { struct cmsghdr cm; char control[CMSG_SPACE(sizeof(struct cmsgcred)) + EXTRA_CMSG_SPACE]; } control_un; struct msghdr msg; struct iovec iov[1]; struct cmsghdr *cmptr; const struct cmsgcred *cmcredptr; socklen_t controllen; int error, error2, fd2; u_int i; if (sock_type == SOCK_STREAM) { if ((fd2 = accept_timeout(fd1)) < 0) return (-2); } else fd2 = fd1; error = 0; controllen = sizeof(control_un.control); for (i = 0; i < 2; ++i) { iov[0].iov_base = buf; iov[0].iov_len = sizeof(buf); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control_un.control; msg.msg_controllen = controllen; msg.msg_flags = 0; controllen = CMSG_SPACE(sizeof(struct cmsgcred)); if (recvmsg_timeout(fd2, &msg, sizeof(buf)) < 0) goto failed; if (msg.msg_flags & MSG_CTRUNC) { logmsgx("#%u control data was truncated, MSG_CTRUNC flag is on", i); goto next_error; } if (msg.msg_controllen < sizeof(struct cmsghdr)) { logmsgx("#%u msg_controllen %u < %lu (sizeof(struct cmsghdr))", i, (u_int)msg.msg_controllen, (u_long)sizeof(struct cmsghdr)); goto next_error; } if ((cmptr = CMSG_FIRSTHDR(&msg)) == NULL) { logmsgx("CMSG_FIRSTHDR is NULL"); goto next_error; } dbgmsg(("#%u msg_controllen = %u, cmsg_len = %u", i, (u_int)msg.msg_controllen, (u_int)cmptr->cmsg_len)); if (cmptr->cmsg_level != SOL_SOCKET) { logmsgx("#%u cmsg_level %d != SOL_SOCKET", i, cmptr->cmsg_level); goto next_error; } if (cmptr->cmsg_type != SCM_CREDS) { logmsgx("#%u cmsg_type %d != SCM_CREDS", i, cmptr->cmsg_type); goto next_error; } if (cmptr->cmsg_len != CMSG_LEN(sizeof(struct cmsgcred))) { logmsgx("#%u cmsg_len %u != %lu (CMSG_LEN(sizeof(struct cmsgcred))", i, (u_int)cmptr->cmsg_len, (u_long)CMSG_LEN(sizeof(struct cmsgcred))); goto next_error; } cmcredptr = (const struct cmsgcred *)CMSG_DATA(cmptr); error2 = 0; if (cmcredptr->cmcred_pid != client_pid) { logmsgx("#%u cmcred_pid %ld != %ld (PID of client)", i, (long)cmcredptr->cmcred_pid, (long)client_pid); error2 = 1; } if (cmcredptr->cmcred_uid != my_uid) { logmsgx("#%u cmcred_uid %lu != %lu (UID of current process)", i, (u_long)cmcredptr->cmcred_uid, (u_long)my_uid); error2 = 1; } if (cmcredptr->cmcred_euid != my_euid) { logmsgx("#%u cmcred_euid %lu != %lu (EUID of current process)", i, (u_long)cmcredptr->cmcred_euid, (u_long)my_euid); error2 = 1; } if (cmcredptr->cmcred_gid != my_gid) { logmsgx("#%u cmcred_gid %lu != %lu (GID of current process)", i, (u_long)cmcredptr->cmcred_gid, (u_long)my_gid); error2 = 1; } if (cmcredptr->cmcred_ngroups == 0) { logmsgx("#%u cmcred_ngroups = 0, this is wrong", i); error2 = 1; } else { if (cmcredptr->cmcred_ngroups > NGROUPS_MAX) { logmsgx("#%u cmcred_ngroups %d > %u (NGROUPS_MAX)", i, cmcredptr->cmcred_ngroups, NGROUPS_MAX); error2 = 1; } else if (cmcredptr->cmcred_ngroups < 0) { logmsgx("#%u cmcred_ngroups %d < 0", i, cmcredptr->cmcred_ngroups); error2 = 1; } else { dbgmsg(("#%u cmcred_ngroups = %d", i, cmcredptr->cmcred_ngroups)); if (cmcredptr->cmcred_groups[0] != my_egid) { logmsgx("#%u cmcred_groups[0] %lu != %lu (EGID of current process)", i, (u_long)cmcredptr->cmcred_groups[0], (u_long)my_egid); error2 = 1; } if (check_groups(cmcredptr->cmcred_groups + 1, cmcredptr->cmcred_ngroups - 1) < 0) { logmsgx("#%u cmcred_groups has wrong GIDs", i); error2 = 1; } } } if (error2) goto next_error; if ((cmptr = CMSG_NXTHDR(&msg, cmptr)) != NULL) { logmsgx("#%u control data has extra header", i); goto next_error; } continue; next_error: error = -1; } if (sock_type == SOCK_STREAM) if (close(fd2) < 0) { logmsg("close"); return (-2); } return (error); failed: if (sock_type == SOCK_STREAM) if (close(fd2) < 0) logmsg("close"); return (-2); } static int t_cmsgcred(void) { int error, fd; if ((fd = create_server_socket()) < 0) return (-2); if (sock_type == SOCK_STREAM) if (listen(fd, LISTENQ) < 0) { logmsg("listen"); goto failed; } if ((client_pid = fork()) == (pid_t)-1) { logmsg("fork"); goto failed; } if (client_pid == 0) { myname = "CLIENT"; if (close_socket((const char *)NULL, fd) < 0) _exit(1); t_cmsgcred_client(2); } if ((error = t_cmsgcred_server(fd)) == -2) { (void)wait_client(); goto failed; } if (wait_client() < 0) goto failed; if (close_socket(serv_sock_path, fd) < 0) { logmsgx("close_socket failed"); return (-2); } return (error); failed: if (close_socket(serv_sock_path, fd) < 0) logmsgx("close_socket failed"); return (-2); } /* * Send two messages with data to server and exit. */ static void t_sockcred_client(int type) { struct msghdr msg; struct iovec iov[1]; int fd; u_int i; assert(type == 0 || type == 1); if ((fd = create_unbound_socket()) < 0) goto failed; if (connect_server(fd) < 0) goto failed_close; if (type == 1) if (sync_recv(fd) < 0) goto failed_close; iov[0].iov_base = ipc_message; iov[0].iov_len = IPC_MESSAGE_SIZE; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; for (i = 0; i < 2; ++i) if (sendmsg_timeout(fd, &msg, IPC_MESSAGE_SIZE) < 0) goto failed_close; if (close_socket((const char *)NULL, fd) < 0) goto failed; _exit(0); failed_close: (void)close_socket((const char *)NULL, fd); failed: _exit(1); } /* * Receive one message with data and control message with SCM_CREDS * type followed by struct sockcred{} and if n is not equal 1, then * receive another one message with data. fd1 is a listen socket for * stream sockets or simply socket for datagram sockets. If type is * 1, then set LOCAL_CREDS option for accepted stream socket. */ static int t_sockcred_server(int type, int fd1, u_int n) { char buf[IPC_MESSAGE_SIZE]; union { struct cmsghdr cm; char control[CMSG_SPACE(SOCKCREDSIZE(NGROUPS_MAX)) + EXTRA_CMSG_SPACE]; } control_un; struct msghdr msg; struct iovec iov[1]; struct cmsghdr *cmptr; const struct sockcred *sockcred; int error, error2, fd2, optval; u_int i; assert(n == 1 || n == 2); assert(type == 0 || type == 1); if (sock_type == SOCK_STREAM) { if ((fd2 = accept_timeout(fd1)) < 0) return (-2); if (type == 1) { optval = 1; if (setsockopt(fd2, 0, LOCAL_CREDS, &optval, sizeof optval) < 0) { logmsg("setsockopt(LOCAL_CREDS) for accepted socket"); if (errno == ENOPROTOOPT) { error = -1; goto done_close; } goto failed; } if (sync_send(fd2) < 0) goto failed; } } else fd2 = fd1; error = 0; for (i = 0; i < n; ++i) { iov[0].iov_base = buf; iov[0].iov_len = sizeof buf; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control_un.control; msg.msg_controllen = sizeof control_un.control; msg.msg_flags = 0; if (recvmsg_timeout(fd2, &msg, sizeof buf) < 0) goto failed; if (msg.msg_flags & MSG_CTRUNC) { logmsgx("control data was truncated, MSG_CTRUNC flag is on"); goto next_error; } if (i != 0 && sock_type == SOCK_STREAM) { if (msg.msg_controllen != 0) { logmsgx("second message has control data, this is wrong for stream sockets"); goto next_error; } dbgmsg(("#%u msg_controllen = %u", i, (u_int)msg.msg_controllen)); continue; } if (msg.msg_controllen < sizeof(struct cmsghdr)) { logmsgx("#%u msg_controllen %u < %lu (sizeof(struct cmsghdr))", i, (u_int)msg.msg_controllen, (u_long)sizeof(struct cmsghdr)); goto next_error; } if ((cmptr = CMSG_FIRSTHDR(&msg)) == NULL) { logmsgx("CMSG_FIRSTHDR is NULL"); goto next_error; } dbgmsg(("#%u msg_controllen = %u, cmsg_len = %u", i, (u_int)msg.msg_controllen, (u_int)cmptr->cmsg_len)); if (cmptr->cmsg_level != SOL_SOCKET) { logmsgx("#%u cmsg_level %d != SOL_SOCKET", i, cmptr->cmsg_level); goto next_error; } if (cmptr->cmsg_type != SCM_CREDS) { logmsgx("#%u cmsg_type %d != SCM_CREDS", i, cmptr->cmsg_type); goto next_error; } if (cmptr->cmsg_len < CMSG_LEN(SOCKCREDSIZE(1))) { logmsgx("#%u cmsg_len %u != %lu (CMSG_LEN(SOCKCREDSIZE(1)))", i, (u_int)cmptr->cmsg_len, (u_long)CMSG_LEN(SOCKCREDSIZE(1))); goto next_error; } sockcred = (const struct sockcred *)CMSG_DATA(cmptr); error2 = 0; if (sockcred->sc_uid != my_uid) { logmsgx("#%u sc_uid %lu != %lu (UID of current process)", i, (u_long)sockcred->sc_uid, (u_long)my_uid); error2 = 1; } if (sockcred->sc_euid != my_euid) { logmsgx("#%u sc_euid %lu != %lu (EUID of current process)", i, (u_long)sockcred->sc_euid, (u_long)my_euid); error2 = 1; } if (sockcred->sc_gid != my_gid) { logmsgx("#%u sc_gid %lu != %lu (GID of current process)", i, (u_long)sockcred->sc_gid, (u_long)my_gid); error2 = 1; } if (sockcred->sc_egid != my_egid) { logmsgx("#%u sc_egid %lu != %lu (EGID of current process)", i, (u_long)sockcred->sc_gid, (u_long)my_egid); error2 = 1; } if (sockcred->sc_ngroups > NGROUPS_MAX) { logmsgx("#%u sc_ngroups %d > %u (NGROUPS_MAX)", i, sockcred->sc_ngroups, NGROUPS_MAX); error2 = 1; } else if (sockcred->sc_ngroups < 0) { logmsgx("#%u sc_ngroups %d < 0", i, sockcred->sc_ngroups); error2 = 1; } else { dbgmsg(("#%u sc_ngroups = %d", i, sockcred->sc_ngroups)); if (check_groups(sockcred->sc_groups, sockcred->sc_ngroups) < 0) { logmsgx("#%u sc_groups has wrong GIDs", i); error2 = 1; } } if (error2) goto next_error; if ((cmptr = CMSG_NXTHDR(&msg, cmptr)) != NULL) { logmsgx("#%u control data has extra header, this is wrong", i); goto next_error; } continue; next_error: error = -1; } done_close: if (sock_type == SOCK_STREAM) if (close(fd2) < 0) { logmsg("close"); return (-2); } return (error); failed: if (sock_type == SOCK_STREAM) if (close(fd2) < 0) logmsg("close"); return (-2); } static int t_sockcred(int type) { int error, fd, optval; assert(type == 0 || type == 1); if ((fd = create_server_socket()) < 0) return (-2); if (sock_type == SOCK_STREAM) if (listen(fd, LISTENQ) < 0) { logmsg("listen"); goto failed; } if (type == 0) { optval = 1; if (setsockopt(fd, 0, LOCAL_CREDS, &optval, sizeof optval) < 0) { logmsg("setsockopt(LOCAL_CREDS) for %s socket", sock_type == SOCK_STREAM ? "stream listening" : "datagram"); if (errno == ENOPROTOOPT) { error = -1; goto done_close; } goto failed; } } if ((client_pid = fork()) == (pid_t)-1) { logmsg("fork"); goto failed; } if (client_pid == 0) { myname = "CLIENT"; if (close_socket((const char *)NULL, fd) < 0) _exit(1); t_sockcred_client(type); } if ((error = t_sockcred_server(type, fd, 2)) == -2) { (void)wait_client(); goto failed; } if (wait_client() < 0) goto failed; done_close: if (close_socket(serv_sock_path, fd) < 0) { logmsgx("close_socket failed"); return (-2); } return (error); failed: if (close_socket(serv_sock_path, fd) < 0) logmsgx("close_socket failed"); return (-2); } static int t_sockcred_stream1(void) { return (t_sockcred(0)); } static int t_sockcred_stream2(void) { return (t_sockcred(1)); } static int t_sockcred_dgram(void) { return (t_sockcred(0)); } static int t_cmsgcred_sockcred(void) { int error, fd, optval; if ((fd = create_server_socket()) < 0) return (-2); if (sock_type == SOCK_STREAM) if (listen(fd, LISTENQ) < 0) { logmsg("listen"); goto failed; } optval = 1; if (setsockopt(fd, 0, LOCAL_CREDS, &optval, sizeof optval) < 0) { logmsg("setsockopt(LOCAL_CREDS) for %s socket", sock_type == SOCK_STREAM ? "stream listening" : "datagram"); if (errno == ENOPROTOOPT) { error = -1; goto done_close; } goto failed; } if ((client_pid = fork()) == (pid_t)-1) { logmsg("fork"); goto failed; } if (client_pid == 0) { myname = "CLIENT"; if (close_socket((const char *)NULL, fd) < 0) _exit(1); t_cmsgcred_client(1); } if ((error = t_sockcred_server(0, fd, 1)) == -2) { (void)wait_client(); goto failed; } if (wait_client() < 0) goto failed; done_close: if (close_socket(serv_sock_path, fd) < 0) { logmsgx("close_socket failed"); return (-2); } return (error); failed: if (close_socket(serv_sock_path, fd) < 0) logmsgx("close_socket failed"); return (-2); } /* * Send one message with data and control message with SCM_TIMESTAMP * type to server and exit. */ static void t_timestamp_client(void) { union { struct cmsghdr cm; char control[CMSG_SPACE(sizeof(struct timeval))]; } control_un; struct msghdr msg; struct iovec iov[1]; struct cmsghdr *cmptr; int fd; if ((fd = create_unbound_socket()) < 0) goto failed; if (connect_server(fd) < 0) goto failed_close; iov[0].iov_base = ipc_message; iov[0].iov_len = IPC_MESSAGE_SIZE; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control_un.control; msg.msg_controllen = no_control_data ? sizeof(struct cmsghdr) :sizeof control_un.control; msg.msg_flags = 0; cmptr = CMSG_FIRSTHDR(&msg); cmptr->cmsg_len = CMSG_LEN(no_control_data ? 0 : sizeof(struct timeval)); cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_TIMESTAMP; dbgmsg(("msg_controllen = %u, cmsg_len = %u", (u_int)msg.msg_controllen, (u_int)cmptr->cmsg_len)); if (sendmsg_timeout(fd, &msg, IPC_MESSAGE_SIZE) < 0) goto failed_close; if (close_socket((const char *)NULL, fd) < 0) goto failed; _exit(0); failed_close: (void)close_socket((const char *)NULL, fd); failed: _exit(1); } /* * Receive one message with data and control message with SCM_TIMESTAMP * type followed by struct timeval{} from client. */ static int t_timestamp_server(int fd1) { union { struct cmsghdr cm; char control[CMSG_SPACE(sizeof(struct timeval)) + EXTRA_CMSG_SPACE]; } control_un; char buf[IPC_MESSAGE_SIZE]; int error, fd2; struct msghdr msg; struct iovec iov[1]; struct cmsghdr *cmptr; const struct timeval *timeval; if (sock_type == SOCK_STREAM) { if ((fd2 = accept_timeout(fd1)) < 0) return (-2); } else fd2 = fd1; iov[0].iov_base = buf; iov[0].iov_len = sizeof buf; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = control_un.control; msg.msg_controllen = sizeof control_un.control;; msg.msg_flags = 0; if (recvmsg_timeout(fd2, &msg, sizeof buf) < 0) goto failed; error = -1; if (msg.msg_flags & MSG_CTRUNC) { logmsgx("control data was truncated, MSG_CTRUNC flag is on"); goto done; } if (msg.msg_controllen < sizeof(struct cmsghdr)) { logmsgx("msg_controllen %u < %lu (sizeof(struct cmsghdr))", (u_int)msg.msg_controllen, (u_long)sizeof(struct cmsghdr)); goto done; } if ((cmptr = CMSG_FIRSTHDR(&msg)) == NULL) { logmsgx("CMSG_FIRSTHDR is NULL"); goto done; } dbgmsg(("msg_controllen = %u, cmsg_len = %u", (u_int)msg.msg_controllen, (u_int)cmptr->cmsg_len)); if (cmptr->cmsg_level != SOL_SOCKET) { logmsgx("cmsg_level %d != SOL_SOCKET", cmptr->cmsg_level); goto done; } if (cmptr->cmsg_type != SCM_TIMESTAMP) { logmsgx("cmsg_type %d != SCM_TIMESTAMP", cmptr->cmsg_type); goto done; } if (cmptr->cmsg_len != CMSG_LEN(sizeof(struct timeval))) { logmsgx("cmsg_len %u != %lu (CMSG_LEN(sizeof(struct timeval))", (u_int)cmptr->cmsg_len, (u_long)CMSG_LEN(sizeof(struct timeval))); goto done; } timeval = (const struct timeval *)CMSG_DATA(cmptr); dbgmsg(("timeval tv_sec %jd, tv_usec %jd", (intmax_t)timeval->tv_sec, (intmax_t)timeval->tv_usec)); if ((cmptr = CMSG_NXTHDR(&msg, cmptr)) != NULL) { logmsgx("control data has extra header"); goto done; } error = 0; done: if (sock_type == SOCK_STREAM) if (close(fd2) < 0) { logmsg("close"); return (-2); } return (error); failed: if (sock_type == SOCK_STREAM) if (close(fd2) < 0) logmsg("close"); return (-2); } static int t_timestamp(void) { int error, fd; if ((fd = create_server_socket()) < 0) return (-2); if (sock_type == SOCK_STREAM) if (listen(fd, LISTENQ) < 0) { logmsg("listen"); goto failed; } if ((client_pid = fork()) == (pid_t)-1) { logmsg("fork"); goto failed; } if (client_pid == 0) { myname = "CLIENT"; if (close_socket((const char *)NULL, fd) < 0) _exit(1); t_timestamp_client(); } if ((error = t_timestamp_server(fd)) == -2) { (void)wait_client(); goto failed; } if (wait_client() < 0) goto failed; if (close_socket(serv_sock_path, fd) < 0) { logmsgx("close_socket failed"); return (-2); } return (error); failed: if (close_socket(serv_sock_path, fd) < 0) logmsgx("close_socket failed"); return (-2); }