diff options
author | pluknet <pluknet@FreeBSD.org> | 2013-02-11 12:56:23 +0000 |
---|---|---|
committer | pluknet <pluknet@FreeBSD.org> | 2013-02-11 12:56:23 +0000 |
commit | ac15ee1b8d3652b835789a0144da61b3b08d1012 (patch) | |
tree | cb088accf5952c6cd2c3db4821a81376eeeb269a | |
parent | 22ab64ccb2461dded21f4c8a09029daf1027c2f8 (diff) | |
download | FreeBSD-src-ac15ee1b8d3652b835789a0144da61b3b08d1012.zip FreeBSD-src-ac15ee1b8d3652b835789a0144da61b3b08d1012.tar.gz |
Major update for unix_cmsg from Andrey Simonenko.
Quoting the submitter:
- Added tests for SCM_BINTIME, LOCAL_PEERCRED, cmsghdr.cmsg_len
- Code that checks correctness of groups was corrected (getgroups(2) change)
- unix_cmsg.c was completely redesigned and simplified
- Use less timeout value in unix_cmsg.c for faster work
- Added support for not sending data in a message, not sending data and
data array associated with a cmsghdr structure in a message
- Existent tests were improved
- unix_cmsg.t was redesigned and simplified
Correctness of unix_cmsg verified on 7.1-STABLE, 9.1-STABLE and 10-CURRENT.
PR: bin/131567
Submitted by: Andrey Simonenko <simon@comsys.ntu-kpi.kiev.ua>
MFC after: 2 weeks
-rw-r--r-- | tools/regression/sockets/unix_cmsg/README | 211 | ||||
-rw-r--r-- | tools/regression/sockets/unix_cmsg/unix_cmsg.c | 2599 | ||||
-rw-r--r-- | tools/regression/sockets/unix_cmsg/unix_cmsg.t | 91 |
3 files changed, 1652 insertions, 1249 deletions
diff --git a/tools/regression/sockets/unix_cmsg/README b/tools/regression/sockets/unix_cmsg/README index 359da43..df51723 100644 --- a/tools/regression/sockets/unix_cmsg/README +++ b/tools/regression/sockets/unix_cmsg/README @@ -1,127 +1,160 @@ $FreeBSD$ About unix_cmsg -================ +=============== -This program is a collection of regression tests for ancillary (control) -data for PF_LOCAL sockets (local domain or Unix domain sockets). There -are tests for stream and datagram sockets. +This program is a collection of regression tests for ancillary data +(control information) for PF_LOCAL sockets (local domain or Unix domain +sockets). There are tests for stream and datagram sockets. -Usually each test does following steps: create Server, fork Client, -Client sends something to Server, Server verifies if everything -is correct in received message. Sometimes Client sends several -messages to Server. +Usually each test does following steps: creates Server, forks Client, +Client sends something to Server, Server verifies whether everything is +correct in received message(s). It is better to change the owner of unix_cmsg to some safe user -(eg. nobody:nogroup) and set SUID and SGID bits, else some tests -can give correct results for wrong implementation. +(eg. nobody:nogroup) and set SUID and SGID bits, else some tests that +check credentials can give correct results for wrong implementation. + +It is better to run this program by a user that belongs to more +than 16 groups. Available options ================= --d Output debugging information, values of different fields of - received messages, etc. Will produce many lines of information. - --h Output help message and exit. +usage: unix_cmsg [-dh] [-n num] [-s size] [-t type] [-z value] [testno] + + Options are: + -d Output debugging information + -h Output the help message and exit + -n num Number of messages to send + -s size Specify size of data for IPC + -t type Specify socket type (stream, dgram) for tests + -z value Do not send data in a message (bit 0x1), do not send + data array associated with a cmsghdr structure (bit 0x2) + testno Run one test by its number (require the -t option) + +Description +=========== + +If Client sends something to Server, then it sends 5 messages by default. +Number of messages can be changed in the -n command line option. Number +of messages will be given as N in the following descriptions. + +If Client sends something to Server, then it sends some data (few bytes) +in each message by default. The size of this data can be changed by the -s +command line option. The "-s 0" command line option means, that Client will +send zero bytes represented by { NULL, 0 } value of struct iovec{}, referenced +by the msg_iov field from struct msghdr{}. The "-z 1" or "-z 3" command line +option means, that Client will send zero bytes represented by the NULL value +in the msg_iov field from struct msghdr{}. + +If Client sends some ancillary data object, then this ancillary data object +always has associated data array by default. The "-z 2" or "-z 3" option +means, that Client will not send associated data array if possible. --t <socktype> - Run tests only for the given socket type: "stream" or "dgram". - With this option it is possible to run only particular test, - not all of them. +For SOCK_STREAM sockets: +----------------------- --z Do not send real control data if possible. Struct cmsghdr{} - should be followed by real control data. It is not clear if - a sender should give control data in all cases (this is not - documented and an arbitrary application can choose anything). + 1: Sending, receiving cmsgcred - At least for PF_LOCAL sockets' control messages with types - SCM_CREDS and SCM_TIMESTAMP the kernel does not need any - control data. This option allow to not send real control data - for SCM_CREDS and SCM_TIMESTAMP control messages. + Client connects to Server and sends N messages with SCM_CREDS ancillary + data object. Server should receive N messages, each message should + have SCM_CREDS ancillary data object followed by struct cmsgcred{}. -Description of tests -==================== + 2: Receiving sockcred (listening socket) -For SOCK_STREAM sockets: ------------------------ + Server creates a listening stream socket and sets the LOCAL_CREDS + socket option for it. Client connects to Server two times, each time + it sends N messages. Server accepts two connections and receives N + messages from each connection. The first message from each connection + should have SCM_CREDS ancillary data object followed by struct sockcred{}, + next messages from the same connection should not have ancillary data. - 1: Sending, receiving cmsgcred + 3: Receiving sockcred (accepted socket) - Client connects to Server and sends two messages with data and - control message with SCM_CREDS type to Server. Server should - receive two messages, in both messages there should be data and - control message with SCM_CREDS type followed by struct cmsgcred{} - and this structure should contain correct information. - - 2: Receiving sockcred (listening socket has LOCAL_CREDS) - - Server creates listen socket and set socket option LOCAL_CREDS - for it. Client connects to Server and sends two messages with data - to Server. Server should receive two messages, in first message - there should be data and control message with SCM_CREDS type followed - by struct sockcred{} and this structure should contain correct - information, in second message there should be data and no control - message. - - 3: Receiving sockcred (accepted socket has LOCAL_CREDS) - - Client connects to Server and sends two messages with data. Server - accepts connection and set socket option LOCAL_CREDS for just accepted - socket (here synchronization is used, to allow Client to see just set - flag on Server's socket before sending messages to Server). Server - should receive two messages, in first message there should be data and - control message with SOCK_CRED type followed by struct sockcred{} and - this structure should contain correct information, in second message - there should be data and no control message. + Client connects to Server. Server accepts connection and sets the + LOCAL_CREDS socket option for just accepted socket. Client sends N + messages to Server. Server should receive N messages, the first + message should have SCM_CREDS ancillary data object followed by + struct sockcred{}, next messages should not have ancillary data. 4: Sending cmsgcred, receiving sockcred - Server creates listen socket and set socket option LOCAL_CREDS - for it. Client connects to Server and sends one message with data - and control message with SCM_CREDS type to Server. Server should - receive one message with data and control message with SCM_CREDS type - followed by struct sockcred{} and this structure should contain - correct information. + Server creates a listening stream socket and sets the LOCAL_CREDS + socket option for it. Client connects to Server and sends N messages + with SCM_CREDS ancillary data object. Server should receive N messages, + the first message should have SCM_CREDS ancillary data object followed + by struct sockcred{}, each of next messages should have SCM_CREDS + ancillary data object followed by struct cmsgcred{}. + + 5: Sending, receiving timeval + + Client connects to Server and sends message with SCM_TIMESTAMP ancillary + data object. Server should receive one message with SCM_TIMESTAMP + ancillary data object followed by struct timeval{}. - 5: Sending, receiving timestamp + 6: Sending, receiving bintime - Client connects to Server and sends message with data and control - message with SCM_TIMESTAMP type to Server. Server should receive - message with data and control message with SCM_TIMESTAMP type - followed by struct timeval{}. + Client connects to Server and sends message with SCM_BINTIME ancillary + data object. Server should receive one message with SCM_BINTIME + ancillary data object followed by struct bintime{}. + + 7: Checking cmsghdr.cmsg_len + + Client connects to Server and tries to send several messages with + SCM_CREDS ancillary data object that has wrong cmsg_len field in its + struct cmsghdr{}. All these attempts should fail, since cmsg_len + in all requests is less than CMSG_LEN(0). + + 8: Check LOCAL_PEERCRED socket option + + This test does not use ancillary data, but can be implemented here. + Client connects to Server. Both Client and Server verify that + credentials of the peer are correct using LOCAL_PEERCRED socket option. For SOCK_DGRAM sockets: ---------------------- 1: Sending, receiving cmsgcred - Client sends to Server two messages with data and control message - with SCM_CREDS type to Server. Server should receive two messages, - in both messages there should be data and control message with - SCM_CREDS type followed by struct cmsgcred{} and this structure - should contain correct information. + Client connects to Server and sends N messages with SCM_CREDS ancillary + data object. Server should receive N messages, each message should + have SCM_CREDS ancillary data object followed by struct cmsgcred{}. 2: Receiving sockcred - Server creates datagram socket and set socket option LOCAL_CREDS - for it. Client sends two messages with data to Server. Server should - receive two messages, in both messages there should be data and control - message with SCM_CREDS type followed by struct sockcred{} and this - structure should contain correct information. + Server creates datagram socket and sets the LOCAL_CREDS socket option + for it. Client sends N messages to Server. Server should receive N + messages, each message should have SCM_CREDS ancillary data object + followed by struct sockcred{}. 3: Sending cmsgcred, receiving sockcred - - Server creates datagram socket and set socket option LOCAL_CREDS - for it. Client sends one message with data and control message with - SOCK_CREDS type to Server. Server should receive one message with - data and control message with SCM_CREDS type followed by struct - sockcred{} and this structure should contain correct information. - 4: Sending, receiving timestamp + Server creates datagram socket and sets the LOCAL_CREDS socket option + for it. Client sends N messages with SCM_CREDS ancillary data object + to Server. Server should receive N messages, the first message should + have SCM_CREDS ancillary data object followed by struct sockcred{}, + each of next messages should have SCM_CREDS ancillary data object + followed by struct cmsgcred{}. + + 4: Sending, receiving timeval + + Client sends one message with SCM_TIMESTAMP ancillary data object + to Server. Server should receive one message with SCM_TIMESTAMP + ancillary data object followed by struct timeval{}. + + 5: Sending, receiving bintime + + Client sends one message with SCM_BINTIME ancillary data object + to Server. Server should receive one message with SCM_BINTIME + ancillary data object followed by struct bintime{}. + + 6: Checking cmsghdr.cmsg_len - Client sends message with data and control message with SCM_TIMESTAMP - type to Server. Server should receive message with data and control - message with SCM_TIMESTAMP type followed by struct timeval{}. + Client tries to send Server several messages with SCM_CREDS ancillary + data object that has wrong cmsg_len field in its struct cmsghdr{}. + All these attempts should fail, since cmsg_len in all requests is less + than CMSG_LEN(0). - Andrey Simonenko -simon@comsys.ntu-kpi.kiev.ua +andreysimonenko@users.sourceforge.net diff --git a/tools/regression/sockets/unix_cmsg/unix_cmsg.c b/tools/regression/sockets/unix_cmsg/unix_cmsg.c index 06728d7..d91cef4 100644 --- a/tools/regression/sockets/unix_cmsg/unix_cmsg.c +++ b/tools/regression/sockets/unix_cmsg/unix_cmsg.c @@ -27,48 +27,46 @@ #include <sys/cdefs.h> __FBSDID("$FreeBSD$"); -#include <sys/types.h> +#include <sys/param.h> #include <sys/resource.h> #include <sys/time.h> +#include <sys/select.h> #include <sys/socket.h> +#include <sys/ucred.h> #include <sys/un.h> #include <sys/wait.h> -#include <assert.h> #include <ctype.h> #include <err.h> #include <errno.h> +#include <fcntl.h> #include <inttypes.h> #include <limits.h> -#include <setjmp.h> +#include <paths.h> #include <signal.h> #include <stdarg.h> +#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sysexits.h> #include <unistd.h> /* * 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. + * -1 if its test failed, -2 if some system error occurred. + * If a 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. + * 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 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. + * 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 @@ -76,207 +74,290 @@ __FBSDID("$FreeBSD$"); #endif #ifndef TIMEOUT -# define TIMEOUT 60 +# define TIMEOUT 2 #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); +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); /* Pointer to function. */ - const char *desc; /* Test description. */ + int (*func)(void); + const char *desc; }; -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 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" + } }; -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_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_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) +#define TEST_DGRAM_TBL_SIZE \ + (sizeof(test_dgram_tbl) / sizeof(test_dgram_tbl[0])) -static const char *myname = "SERVER"; /* "SERVER" or "CLIENT" */ +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 debug = 0; /* 1, if -d. */ -static int no_control_data = 0; /* 1, if -z. */ +static int sock_type; +static const char *sock_type_str; -static u_int nfailed = 0; /* Number of failed tests. */ +static const char *proc_name; -static int sock_type; /* SOCK_STREAM or SOCK_DGRAM */ -static const char *sock_type_str; /* "SOCK_STREAM" or "SOCK_DGRAN" */ +static char work_dir[] = _PATH_TMP "unix_cmsg.XXXXXXX"; +static int serv_sock_fd; +static struct sockaddr_un serv_addr_sun; -static char tempdir[] = "/tmp/unix_cmsg.XXXXXXX"; -static char serv_sock_path[PATH_MAX]; +static struct { + char *buf_send; + char *buf_recv; + size_t buf_size; + u_int msg_num; +} ipc_msg; -static char ipc_message[] = "hello"; +#define IPC_MSG_NUM_DEF 5 +#define IPC_MSG_NUM_MAX 10 +#define IPC_MSG_SIZE_DEF 7 +#define IPC_MSG_SIZE_MAX 128 -#define IPC_MESSAGE_SIZE (sizeof(ipc_message)) +static struct { + uid_t uid; + uid_t euid; + gid_t gid; + gid_t egid; + gid_t *gid_arr; + int gid_num; +} proc_cred; -static struct sockaddr_un servaddr; /* Server address. */ +static pid_t client_pid; -static sigjmp_buf env_alrm; +#define SYNC_SERVER 0 +#define SYNC_CLIENT 1 +#define SYNC_RECV 0 +#define SYNC_SEND 1 -static uid_t my_uid; -static uid_t my_euid; -static gid_t my_gid; -static gid_t my_egid; +static int sync_fd[2][2]; -/* - * 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) +#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); -extern char *__progname; /* The name of program. */ - -/* - * Output the help message (-h switch). - */ static void -usage(int quick) +usage(bool verbose) { - const struct test_func *test_func; + u_int i; - fprintf(stderr, "Usage: %s [-dhz] [-t <socktype>] [testno]\n", - __progname); - if (quick) + printf("usage: %s [-dh] [-n num] [-s size] [-t type] " + "[-z value] [testno]\n", getprogname()); + if (!verbose) 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 <socktype>\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("\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); } -/* - * printf-like function for outputting to STDOUT_FILENO. - */ static void output(const char *format, ...) { - char buf[128]; + char buf[LOGMSG_SIZE]; va_list ap; va_start(ap, format); if (vsnprintf(buf, sizeof(buf), format, ap) < 0) - err(EX_SOFTWARE, "output: vsnprintf failed"); + err(EXIT_FAILURE, "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]; + char buf[LOGMSG_SIZE]; va_list ap; int errno_save; - errno_save = errno; /* Save errno. */ - + errno_save = errno; va_start(ap, format); if (vsnprintf(buf, sizeof(buf), format, ap) < 0) - err(EX_SOFTWARE, "logmsg: vsnprintf failed"); + err(EXIT_FAILURE, "logmsg: vsnprintf failed"); if (errno_save == 0) - output("%s: %s\n", myname, buf); + output("%s: %s\n", proc_name, buf); else - output("%s: %s: %s\n", myname, buf, strerror(errno_save)); + 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); - 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); + vlogmsgx(format, ap); va_end(ap); } -/* - * Run tests from testno1 to testno2. - */ +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(u_int testno1, u_int testno2) +run_tests(int type, u_int testno1) { - const struct test_func *test_func; - u_int i, nfailed1; + const struct test_func *tf; + u_int i, testno2, failed_num; - output("Running tests for %s sockets:\n", sock_type_str); - test_func = (sock_type == SOCK_STREAM ? - test_stream_tbl : test_dgram_tbl) + testno1; + 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; - nfailed1 = 0; - for (i = testno1; i <= testno2; ++test_func, ++i) { - output(" %s\n", test_func->desc); - switch (test_func->func()) { + 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: - ++nfailed1; + ++failed_num; break; case -2: - logmsgx("some system error occurred, exiting"); + logmsgx("some system error or timeout occurred"); return (-1); } } - nfailed += nfailed1; + if (failed_num != 0) + failed_flag = true; if (testno1 != testno2) { - if (nfailed1 == 0) - output("-- all tests were passed!\n"); + if (failed_num == 0) + output("-- all tests passed!\n"); else - output("-- %u test%s failed!\n", nfailed1, - nfailed1 == 1 ? "" : "s"); + output("-- %u test%s failed!\n", + failed_num, failed_num == 1 ? "" : "s"); } else { - if (nfailed == 0) - output("-- test was passed!\n"); + if (failed_num == 0) + output("-- test passed!\n"); else output("-- test failed!\n"); } @@ -284,183 +365,322 @@ run_tests(u_int testno1, u_int testno2) return (0); } -/* ARGSUSED */ -static void -sig_alrm(int signo __unused) +static int +init(void) { - siglongjmp(env_alrm, 1); + 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); } -/* - * Initialize signals handlers. - */ static void -sig_init(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) { - struct sigaction sa; + 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); + } - 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)"); + 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); + } - sa.sa_handler = sig_alrm; - if (sigaction(SIGALRM, &sa, (struct sigaction *)NULL) < 0) - err(EX_OSERR, "sigaction(SIGALRM)"); + return (0); } 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) + 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 = 1; + debug = true; break; case 'h': - usage(0); - return (EX_OK); + 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) - streamflag = 1; + stream_flag = true; else if (strcmp(optarg, "dgram") == 0) - dgramflag = 1; + dgram_flag = true; else - errx(EX_USAGE, "wrong socket type in -t option"); + errx(EXIT_FAILURE, "option -t: " + "wrong socket type"); break; case 'z': - no_control_data = 1; + 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; - case '?': default: - usage(1); - return (EX_USAGE); + usage(false); + return (EXIT_FAILURE); } if (optind < argc) { if (optind + 1 != argc) - errx(EX_USAGE, "too many arguments"); - testno1 = strtonum(argv[optind], 0, UINT_MAX, &errstr); + errx(EXIT_FAILURE, "too many arguments"); + testno = strtonum(argv[optind], 0, UINT_MAX, &errstr); if (errstr != NULL) - errx(EX_USAGE, "wrong test number: %s", errstr); + 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 - testno1 = 0; + testno = 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"); + 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; } - sig_init(); - - if (mkdtemp(tempdir) == NULL) - err(EX_OSERR, "mkdtemp"); + if (mkdtemp(work_dir) == NULL) + err(EXIT_FAILURE, "mkdtemp(%s)", work_dir); - 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; - } + rv = EXIT_FAILURE; + if (init() < 0) + goto done; - 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 (stream_flag) + if (run_tests(SOCK_STREAM, testno) < 0) + goto done; + if (dgram_flag) + if (run_tests(SOCK_DGRAM, testno) < 0) + goto done; - if (rmdir(tempdir) < 0) { - logmsg("rmdir(%s)", tempdir); - return (EX_OSERR); + rv = EXIT_SUCCESS; +done: + if (rmdir(work_dir) < 0) { + logmsg("rmdir(%s)", work_dir); + rv = EXIT_FAILURE; } + return (failed_flag ? EXIT_FAILURE : rv); +} - return (nfailed ? EX_OSERR : EX_OK); +static int +socket_close(int fd) +{ + int rv; -failed: - if (rmdir(tempdir) < 0) - logmsg("rmdir(%s)", tempdir); - return (EX_OSERR); + 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); } -/* - * 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) +socket_create(void) { - int rv, fd; + struct timeval tv; + int fd; - if ((fd = socket(PF_LOCAL, sock_type, 0)) < 0) { - logmsg("create_socket: socket(PF_LOCAL, %s, 0)", sock_type_str); + 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 (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)); + 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; } - strcpy(addr->sun_path, sock_path); + if (sock_type == SOCK_STREAM) { + int val; - if (bind(fd, (struct sockaddr *)addr, SUN_LEN(addr)) < 0) { - logmsg("create_socket: bind(%s)", sock_path); - goto failed; + 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; + } } } @@ -468,1163 +688,1282 @@ create_socket(char *sock_path, size_t sock_path_len, struct sockaddr_un *addr) failed: if (close(fd) < 0) - logmsg("create_socket: close"); + 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); } -/* - * Call create_socket() for server listening socket. - * Return socket descriptor or -1 if some error occurred. - */ static int -create_server_socket(void) +socket_connect(int fd) { - return (create_socket(serv_sock_path, sizeof(serv_sock_path), &servaddr)); + 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); } -/* - * Create unbound socket. - */ static int -create_unbound_socket(void) +sync_recv(void) { - return (create_socket((char *)NULL, 0, (struct sockaddr_un *)NULL)); + 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); } -/* - * 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) +sync_send(void) { - int error = 0; + ssize_t ssize; + int fd; - if (close(fd) < 0) { - logmsg("close_socket: close"); - error = -1; + 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 (sock_path != NULL) - if (unlink(sock_path) < 0) { - logmsg("close_socket: unlink(%s)", sock_path); - error = -1; - } - return (error); + if (ssize < 1) { + logmsgx("sync_send: sent %zd of 1 byte", ssize); + return (-1); + } + + return (0); } -/* - * Connect to server (socket address in servaddr). - */ static int -connect_server(int fd) +message_send(int fd, const struct msghdr *msghdr) { - 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); + 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); } -/* - * sendmsg() with timeout. - */ static int -sendmsg_timeout(int fd, struct msghdr *msg, size_t n) +message_sendn(int fd, struct msghdr *msghdr) { - ssize_t nsent; - - dbgmsg(("sending %lu bytes", (u_long)n)); + u_int i; - if (sigsetjmp(env_alrm, 1) != 0) { - logmsgx("sendmsg_timeout: cannot send message to %s (timeout)", serv_sock_path); - return (-1); + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); + if (message_send(fd, msghdr) < 0) + return (-1); } + return (0); +} - (void)alarm(TIMEOUT); - - nsent = sendmsg(fd, msg, 0); +static int +message_recv(int fd, struct msghdr *msghdr) +{ + const struct cmsghdr *cmsghdr; + size_t size; + ssize_t ssize; - (void)alarm(0); + if (!send_data_flag) + if (sync_recv() < 0) + return (-1); - if (nsent < 0) { - logmsg("sendmsg_timeout: sendmsg"); + 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); } - if ((size_t)nsent != n) { - logmsgx("sendmsg_timeout: sendmsg: short send: %ld of %lu bytes", - (long)nsent, (u_long)n); + 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); } -/* - * accept() with timeout. - */ static int -accept_timeout(int listenfd) +socket_accept(int listenfd) { - int fd; - - dbgmsg(("accepting connection")); - - if (sigsetjmp(env_alrm, 1) != 0) { - logmsgx("accept_timeout: cannot accept connection (timeout)"); + 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); } - - (void)alarm(TIMEOUT); fd = accept(listenfd, (struct sockaddr *)NULL, (socklen_t *)NULL); - - (void)alarm(0); - if (fd < 0) { - logmsg("accept_timeout: accept"); + 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); } -/* - * recvmsg() with timeout. - */ static int -recvmsg_timeout(int fd, struct msghdr *msg, size_t n) +check_msghdr(const struct msghdr *msghdr, size_t size) { - ssize_t nread; - - dbgmsg(("receiving %lu bytes", (u_long)n)); - - if (sigsetjmp(env_alrm, 1) != 0) { - logmsgx("recvmsg_timeout: cannot receive message (timeout)"); + if (msghdr->msg_flags & MSG_TRUNC) { + logmsgx("msghdr.msg_flags has MSG_TRUNC"); return (-1); } - - (void)alarm(TIMEOUT); - - nread = recvmsg(fd, msg, MSG_WAITALL); - - (void)alarm(0); - - if (nread < 0) { - logmsg("recvmsg_timeout: recvmsg"); + if (msghdr->msg_flags & MSG_CTRUNC) { + logmsgx("msghdr.msg_flags has MSG_CTRUNC"); return (-1); } - - if ((size_t)nread != n) { - logmsgx("recvmsg_timeout: recvmsg: short read: %ld of %lu bytes", - (long)nread, (u_long)n); + 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); } -/* - * Wait for synchronization message (1 byte) with timeout. - */ static int -sync_recv(int fd) +check_groups(const char *gid_arr_str, const gid_t *gid_arr, + const char *gid_num_str, int gid_num, bool all_gids) { - ssize_t nread; - char buf; + int i; - dbgmsg(("waiting for sync message")); + for (i = 0; i < gid_num; ++i) + dbgmsg("%s[%d] %lu", gid_arr_str, i, (u_long)gid_arr[i]); - if (sigsetjmp(env_alrm, 1) != 0) { - logmsgx("sync_recv: cannot receive sync message (timeout)"); + 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); +} - (void)alarm(TIMEOUT); - - nread = read(fd, &buf, 1); +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); + } - (void)alarm(0); + 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 (nread < 0) { - logmsg("sync_recv: read"); + if (xucred->cr_version != XUCRED_VERSION) { + logmsgx("xucred.cr_version %u != %d", + xucred->cr_version, XUCRED_VERSION); return (-1); } - - if (nread != 1) { - logmsgx("sync_recv: read: short read: %ld of 1 byte", - (long)nread); + 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); } -/* - * Send synchronization message (1 byte) with timeout. - */ static int -sync_send(int fd) +check_scm_creds_cmsgcred(struct cmsghdr *cmsghdr) { - ssize_t nsent; - - dbgmsg(("sending sync message")); + const struct cmsgcred *cmsgcred; - if (sigsetjmp(env_alrm, 1) != 0) { - logmsgx("sync_send: cannot send sync message (timeout)"); + if (check_cmsghdr(cmsghdr, SCM_CREDS, sizeof(*cmsgcred)) < 0) return (-1); - } - - (void)alarm(TIMEOUT); - nsent = write(fd, "", 1); + cmsgcred = (struct cmsgcred *)CMSG_DATA(cmsghdr); - (void)alarm(0); + 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 (nsent < 0) { - logmsg("sync_send: write"); + if (cmsgcred->cmcred_pid != client_pid) { + logmsgx("cmsgcred.cmcred_pid %ld != %ld", + (long)cmsgcred->cmcred_pid, (long)client_pid); return (-1); } - - if (nsent != 1) { - logmsgx("sync_send: write: short write: %ld of 1 byte", - (long)nsent); + 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); } -/* - * waitpid() for client with timeout. - */ static int -wait_client(void) +check_scm_creds_sockcred(struct cmsghdr *cmsghdr) { - int status; - pid_t pid; + const struct sockcred *sockcred; - if (sigsetjmp(env_alrm, 1) != 0) { - logmsgx("wait_client: cannot get exit status of client PID %ld (timeout)", - (long)client_pid); + if (check_cmsghdr(cmsghdr, SCM_CREDS, + SOCKCREDSIZE(proc_cred.gid_num)) < 0) return (-1); - } - (void)alarm(TIMEOUT); - - pid = waitpid(client_pid, &status, 0); + sockcred = (struct sockcred *)CMSG_DATA(cmsghdr); - (void)alarm(0); + 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 (pid == (pid_t)-1) { - logmsg("wait_client: waitpid"); + 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 (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); + 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); } -/* - * 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) +check_scm_timestamp(struct cmsghdr *cmsghdr) { - char match[NGROUPS_MAX] = { 0 }; - int error, i, j; + const struct timeval *timeval; - 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); -} + if (check_cmsghdr(cmsghdr, SCM_TIMESTAMP, sizeof(struct timeval)) < 0) + return (-1); -/* - * 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; + timeval = (struct timeval *)CMSG_DATA(cmsghdr); - assert(n == 1 || n == 2); + dbgmsg("timeval.tv_sec %"PRIdMAX", timeval.tv_usec %"PRIdMAX, + (intmax_t)timeval->tv_sec, (intmax_t)timeval->tv_usec); - if ((fd = create_unbound_socket()) < 0) - goto failed; + return (0); +} - if (connect_server(fd) < 0) - goto failed_close; +static int +check_scm_bintime(struct cmsghdr *cmsghdr) +{ + const struct bintime *bintime; + + if (check_cmsghdr(cmsghdr, SCM_BINTIME, sizeof(struct bintime)) < 0) + return (-1); - iov[0].iov_base = ipc_message; - iov[0].iov_len = IPC_MESSAGE_SIZE; + bintime = (struct bintime *)CMSG_DATA(cmsghdr); - 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; + dbgmsg("bintime.sec %"PRIdMAX", bintime.frac %"PRIu64, + (intmax_t)bintime->sec, bintime->frac); - 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; + return (0); +} - 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; +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; +} - if (close_socket((const char *)NULL, fd) < 0) - goto failed; +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); +} - _exit(0); +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; +} -failed_close: - (void)close_socket((const char *)NULL, fd); +static int +t_generic(int (*client_func)(int), int (*server_func)(int)) +{ + int fd, rv, rv_client; -failed: - _exit(1); + 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); } -/* - * 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) +t_cmsgcred_client(int fd) { - 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 msghdr msghdr; 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; + void *cmsg_data; + size_t cmsg_size; + int rv; - controllen = sizeof(control_un.control); - - for (i = 0; i < 2; ++i) { - iov[0].iov_base = buf; - iov[0].iov_len = sizeof(buf); + if (sync_recv() < 0) + return (-2); - 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; + rv = -2; - controllen = CMSG_SPACE(sizeof(struct cmsgcred)); + 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 (recvmsg_timeout(fd2, &msg, sizeof(buf)) < 0) - goto failed; + if (socket_connect(fd) < 0) + goto done; - if (msg.msg_flags & MSG_CTRUNC) { - logmsgx("#%u control data was truncated, MSG_CTRUNC flag is on", - i); - goto next_error; - } + if (message_sendn(fd, &msghdr) < 0) + goto done; - 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; - } + rv = 0; +done: + free(cmsg_data); + return (rv); +} - if ((cmptr = CMSG_FIRSTHDR(&msg)) == NULL) { - logmsgx("CMSG_FIRSTHDR is NULL"); - goto next_error; - } +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; - dbgmsg(("#%u msg_controllen = %u, cmsg_len = %u", i, - (u_int)msg.msg_controllen, (u_int)cmptr->cmsg_len)); + if (sync_send() < 0) + return (-2); - if (cmptr->cmsg_level != SOL_SOCKET) { - logmsgx("#%u cmsg_level %d != SOL_SOCKET", i, - cmptr->cmsg_level); - goto next_error; - } + fd2 = -1; + rv = -2; - if (cmptr->cmsg_type != SCM_CREDS) { - logmsgx("#%u cmsg_type %d != SCM_CREDS", i, - cmptr->cmsg_type); - goto next_error; - } + cmsg_size = CMSG_SPACE(sizeof(struct cmsgcred)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } - 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; - } + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; - cmcredptr = (const struct cmsgcred *)CMSG_DATA(cmptr); + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); - 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; - } - } + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; } - if (error2) - goto next_error; - - if ((cmptr = CMSG_NXTHDR(&msg, cmptr)) != NULL) { - logmsgx("#%u control data has extra header", i); - goto next_error; - } + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; - continue; -next_error: - error = -1; + cmsghdr = CMSG_FIRSTHDR(&msghdr); + if (check_scm_creds_cmsgcred(cmsghdr) < 0) + break; } - - 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); + 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) { - int error, fd; + return (t_generic(t_cmsgcred_client, t_cmsgcred_server)); +} - if ((fd = create_server_socket()) < 0) - return (-2); +static int +t_sockcred_client(int type, int fd) +{ + struct msghdr msghdr; + struct iovec iov[1]; + int rv; - if (sock_type == SOCK_STREAM) - if (listen(fd, LISTENQ) < 0) { - logmsg("listen"); - goto failed; - } + if (sync_recv() < 0) + return (-2); - if ((client_pid = fork()) == (pid_t)-1) { - logmsg("fork"); - goto failed; - } + rv = -2; - if (client_pid == 0) { - myname = "CLIENT"; - if (close_socket((const char *)NULL, fd) < 0) - _exit(1); - t_cmsgcred_client(2); - } + msghdr_init_client(&msghdr, iov, NULL, 0, 0, 0); - if ((error = t_cmsgcred_server(fd)) == -2) { - (void)wait_client(); - goto failed; - } + if (socket_connect(fd) < 0) + goto done; - if (wait_client() < 0) - goto failed; + if (type == 2) + if (sync_recv() < 0) + goto done; - if (close_socket(serv_sock_path, fd) < 0) { - logmsgx("close_socket failed"); - return (-2); - } - return (error); + if (message_sendn(fd, &msghdr) < 0) + goto done; -failed: - if (close_socket(serv_sock_path, fd) < 0) - logmsgx("close_socket failed"); - return (-2); + rv = 0; +done: + return (rv); } -/* - * Send two messages with data to server and exit. - */ -static void -t_sockcred_client(int type) +static int +t_sockcred_server(int type, int fd1) { - struct msghdr msg; + struct msghdr msghdr; struct iovec iov[1]; - int fd; + struct cmsghdr *cmsghdr; + void *cmsg_data; + size_t cmsg_size; u_int i; + int fd2, rv, val; - assert(type == 0 || type == 1); + fd2 = -1; + rv = -2; - if ((fd = create_unbound_socket()) < 0) - goto failed; - - if (connect_server(fd) < 0) - goto failed_close; + 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) - if (sync_recv(fd) < 0) - goto failed_close; + 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; + } + } - iov[0].iov_base = ipc_message; - iov[0].iov_len = IPC_MESSAGE_SIZE; + if (sync_send() < 0) + goto done; - 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; + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; - for (i = 0; i < 2; ++i) - if (sendmsg_timeout(fd, &msg, IPC_MESSAGE_SIZE) < 0) - goto failed_close; + 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; + } - if (close_socket((const char *)NULL, fd) < 0) - goto failed; + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); - _exit(0); + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; + } -failed_close: - (void)close_socket((const char *)NULL, fd); + if (i > 1 && sock_type == SOCK_STREAM) { + if (check_msghdr(&msghdr, 0) < 0) + break; + } else { + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; -failed: - _exit(1); + 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); } -/* - * 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) +t_sockcred_1(void) { - 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; + int fd, rv, rv_client; - 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; + 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 (sync_send(fd2) < 0) - goto failed; + if (rv != 0) + break; } - } else - fd2 = fd1; + 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; + } - error = 0; + return (rv); +} - for (i = 0; i < n; ++i) { - iov[0].iov_base = buf; - iov[0].iov_len = sizeof buf; +static int +t_sockcred_2_client(int fd) +{ + return (t_sockcred_client(2, fd)); +} - 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; +static int +t_sockcred_2_server(int fd) +{ + return (t_sockcred_server(2, fd)); +} - if (recvmsg_timeout(fd2, &msg, sizeof buf) < 0) - goto failed; +static int +t_sockcred_2(void) +{ + return (t_generic(t_sockcred_2_client, t_sockcred_2_server)); +} - if (msg.msg_flags & MSG_CTRUNC) { - logmsgx("control data was truncated, MSG_CTRUNC flag is on"); - goto next_error; - } +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; - 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; - } + fd2 = -1; + rv = -2; - 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; - } + 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; + } - if ((cmptr = CMSG_FIRSTHDR(&msg)) == NULL) { - logmsgx("CMSG_FIRSTHDR is NULL"); - goto next_error; - } + dbgmsg("setting LOCAL_CREDS"); + val = 1; + if (setsockopt(fd1, 0, LOCAL_CREDS, &val, sizeof(val)) < 0) { + logmsg("setsockopt(LOCAL_CREDS)"); + goto done; + } - dbgmsg(("#%u msg_controllen = %u, cmsg_len = %u", i, - (u_int)msg.msg_controllen, (u_int)cmptr->cmsg_len)); + if (sync_send() < 0) + goto done; - if (cmptr->cmsg_level != SOL_SOCKET) { - logmsgx("#%u cmsg_level %d != SOL_SOCKET", i, - cmptr->cmsg_level); - goto next_error; - } + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; - if (cmptr->cmsg_type != SCM_CREDS) { - logmsgx("#%u cmsg_type %d != SCM_CREDS", i, - cmptr->cmsg_type); - goto next_error; - } + cmsg_data = cmsg1_data; + cmsg_size = cmsg1_size; + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); - 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; + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; } - sockcred = (const struct sockcred *)CMSG_DATA(cmptr); + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; - 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; + cmsghdr = CMSG_FIRSTHDR(&msghdr); + if (i == 1 || sock_type == SOCK_DGRAM) { + if (check_scm_creds_sockcred(cmsghdr) < 0) + break; } 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; + if (check_scm_creds_cmsgcred(cmsghdr) < 0) + break; } - continue; -next_error: - error = -1; + cmsg_data = cmsg2_data; + cmsg_size = cmsg2_size; } - -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); + 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_sockcred(int type) +t_cmsgcred_sockcred(void) { - int error, fd, optval; + return (t_generic(t_cmsgcred_client, t_cmsgcred_sockcred_server)); +} - assert(type == 0 || type == 1); +static int +t_timeval_client(int fd) +{ + struct msghdr msghdr; + struct iovec iov[1]; + void *cmsg_data; + size_t cmsg_size; + int rv; - if ((fd = create_server_socket()) < 0) + if (sync_recv() < 0) return (-2); - if (sock_type == SOCK_STREAM) - if (listen(fd, LISTENQ) < 0) { - logmsg("listen"); - goto failed; - } + rv = -2; - 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; - } + 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 ((client_pid = fork()) == (pid_t)-1) { - logmsg("fork"); - goto failed; - } + if (socket_connect(fd) < 0) + goto done; - if (client_pid == 0) { - myname = "CLIENT"; - if (close_socket((const char *)NULL, fd) < 0) - _exit(1); - t_sockcred_client(type); - } + if (message_sendn(fd, &msghdr) < 0) + goto done; - if ((error = t_sockcred_server(type, fd, 2)) == -2) { - (void)wait_client(); - goto failed; - } + rv = 0; +done: + free(cmsg_data); + return (rv); +} - if (wait_client() < 0) - goto failed; +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; -done_close: - if (close_socket(serv_sock_path, fd) < 0) { - logmsgx("close_socket failed"); + 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; } - return (error); -failed: - if (close_socket(serv_sock_path, fd) < 0) - logmsgx("close_socket failed"); - return (-2); -} + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; -static int -t_sockcred_stream1(void) -{ - return (t_sockcred(0)); -} + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); -static int -t_sockcred_stream2(void) -{ - return (t_sockcred(1)); + 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_sockcred_dgram(void) +t_timeval(void) { - return (t_sockcred(0)); + return (t_generic(t_timeval_client, t_timeval_server)); } static int -t_cmsgcred_sockcred(void) +t_bintime_client(int fd) { - int error, fd, optval; + struct msghdr msghdr; + struct iovec iov[1]; + void *cmsg_data; + size_t cmsg_size; + int rv; - if ((fd = create_server_socket()) < 0) + if (sync_recv() < 0) return (-2); - if (sock_type == SOCK_STREAM) - if (listen(fd, LISTENQ) < 0) { - logmsg("listen"); - goto failed; - } + rv = -2; - 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; + 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 (wait_client() < 0) - goto failed; + if (socket_connect(fd) < 0) + goto done; -done_close: - if (close_socket(serv_sock_path, fd) < 0) { - logmsgx("close_socket failed"); - return (-2); - } - return (error); + if (message_sendn(fd, &msghdr) < 0) + goto done; -failed: - if (close_socket(serv_sock_path, fd) < 0) - logmsgx("close_socket failed"); - return (-2); + rv = 0; +done: + free(cmsg_data); + return (rv); } -/* - * Send one message with data and control message with SCM_TIMESTAMP - * type to server and exit. - */ -static void -t_timestamp_client(void) +static int +t_bintime_server(int fd1) { - union { - struct cmsghdr cm; - char control[CMSG_SPACE(sizeof(struct timeval))]; - } control_un; - struct msghdr msg; + struct msghdr msghdr; 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; + struct cmsghdr *cmsghdr; + void *cmsg_data; + size_t cmsg_size; + u_int i; + int fd2, rv; - iov[0].iov_base = ipc_message; - iov[0].iov_len = IPC_MESSAGE_SIZE; + if (sync_send() < 0) + return (-2); - 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; + fd2 = -1; + rv = -2; - 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; + cmsg_size = CMSG_SPACE(sizeof(struct bintime)); + cmsg_data = malloc(cmsg_size); + if (cmsg_data == NULL) { + logmsg("malloc"); + goto done; + } - dbgmsg(("msg_controllen = %u, cmsg_len = %u", - (u_int)msg.msg_controllen, (u_int)cmptr->cmsg_len)); + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; - if (sendmsg_timeout(fd, &msg, IPC_MESSAGE_SIZE) < 0) - goto failed_close; + rv = -1; + for (i = 1; i <= ipc_msg.msg_num; ++i) { + dbgmsg("message #%u", i); - if (close_socket((const char *)NULL, fd) < 0) - goto failed; + msghdr_init_server(&msghdr, iov, cmsg_data, cmsg_size); + if (message_recv(fd2, &msghdr) < 0) { + rv = -2; + break; + } - _exit(0); + if (check_msghdr(&msghdr, sizeof(*cmsghdr)) < 0) + break; -failed_close: - (void)close_socket((const char *)NULL, fd); + 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); +} -failed: - _exit(1); +static int +t_bintime(void) +{ + return (t_generic(t_bintime_client, t_bintime_server)); } -/* - * 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) +t_cmsg_len_client(int fd) { - 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 msghdr msghdr; struct iovec iov[1]; - struct cmsghdr *cmptr; - const struct timeval *timeval; + struct cmsghdr *cmsghdr; + void *cmsg_data; + size_t size, cmsg_size; + socklen_t socklen; + int rv; - 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; + if (sync_recv() < 0) + return (-2); - error = -1; + rv = -2; - if (msg.msg_flags & MSG_CTRUNC) { - logmsgx("control data was truncated, MSG_CTRUNC flag is on"); + 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 (msg.msg_controllen < sizeof(struct cmsghdr)) { - logmsgx("msg_controllen %u < %lu (sizeof(struct cmsghdr))", - (u_int)msg.msg_controllen, (u_long)sizeof(struct cmsghdr)); + if (socket_connect(fd) < 0) goto done; - } - if ((cmptr = CMSG_FIRSTHDR(&msg)) == NULL) { - logmsgx("CMSG_FIRSTHDR is NULL"); - 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; - 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); + if (sync_send() < 0) { + rv = -2; goto done; } +done: + free(cmsg_data); + return (rv); +} - if (cmptr->cmsg_type != SCM_TIMESTAMP) { - logmsgx("cmsg_type %d != SCM_TIMESTAMP", cmptr->cmsg_type); - goto done; - } +static int +t_cmsg_len_server(int fd1) +{ + int fd2, rv; - 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; - } + if (sync_send() < 0) + return (-2); - timeval = (const struct timeval *)CMSG_DATA(cmptr); + rv = -2; - dbgmsg(("timeval tv_sec %jd, tv_usec %jd", - (intmax_t)timeval->tv_sec, (intmax_t)timeval->tv_usec)); + if (sock_type == SOCK_STREAM) { + fd2 = socket_accept(fd1); + if (fd2 < 0) + goto done; + } else + fd2 = fd1; - if ((cmptr = CMSG_NXTHDR(&msg, cmptr)) != NULL) { - logmsgx("control data has extra header"); + if (sync_recv() < 0) goto done; - } - - error = 0; + rv = 0; done: - if (sock_type == SOCK_STREAM) - if (close(fd2) < 0) { - logmsg("close"); - return (-2); - } - return (error); + if (sock_type == SOCK_STREAM && fd2 >= 0) + if (socket_close(fd2) < 0) + rv = -2; + return (rv); +} -failed: - if (sock_type == SOCK_STREAM) - if (close(fd2) < 0) - logmsg("close"); - return (-2); +static int +t_cmsg_len(void) +{ + return (t_generic(t_cmsg_len_client, t_cmsg_len_server)); } static int -t_timestamp(void) +t_peercred_client(int fd) { - int error, fd; + struct xucred xucred; + socklen_t len; - if ((fd = create_server_socket()) < 0) - return (-2); + if (sync_recv() < 0) + return (-1); - if (sock_type == SOCK_STREAM) - if (listen(fd, LISTENQ) < 0) { - logmsg("listen"); - goto failed; - } + if (socket_connect(fd) < 0) + return (-1); - if ((client_pid = fork()) == (pid_t)-1) { - logmsg("fork"); - goto failed; + len = sizeof(xucred); + if (getsockopt(fd, 0, LOCAL_PEERCRED, &xucred, &len) < 0) { + logmsg("getsockopt(LOCAL_PEERCRED)"); + return (-1); } - if (client_pid == 0) { - myname = "CLIENT"; - if (close_socket((const char *)NULL, fd) < 0) - _exit(1); - t_timestamp_client(); - } + if (check_xucred(&xucred, len) < 0) + return (-1); - if ((error = t_timestamp_server(fd)) == -2) { - (void)wait_client(); - goto failed; - } + return (0); +} - if (wait_client() < 0) - goto failed; +static int +t_peercred_server(int fd1) +{ + struct xucred xucred; + socklen_t len; + int fd2, rv; - if (close_socket(serv_sock_path, fd) < 0) { - logmsgx("close_socket failed"); + 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; } - return (error); -failed: - if (close_socket(serv_sock_path, fd) < 0) - logmsgx("close_socket failed"); - return (-2); + 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)); } diff --git a/tools/regression/sockets/unix_cmsg/unix_cmsg.t b/tools/regression/sockets/unix_cmsg/unix_cmsg.t index c8dea15..70d9277 100644 --- a/tools/regression/sockets/unix_cmsg/unix_cmsg.t +++ b/tools/regression/sockets/unix_cmsg/unix_cmsg.t @@ -11,47 +11,78 @@ n=0 run() { - result=`${cmd} -t $2 $3 $4 2>&1` - if [ $? -eq 0 ]; then - echo -n "ok $1" - else - echo -n "not ok $1" + result=`${cmd} -t $2 $3 ${5%% *} 2>&1` + if [ $? -ne 0 ]; then + echo -n "not " fi - echo " -" $5 + echo "ok $1 - $4 ${5#* }" echo ${result} | grep -E "SERVER|CLIENT" | while read line; do echo "# ${line}" done } -echo "1..15" +echo "1..47" -for desc in \ - "Sending, receiving cmsgcred" \ - "Receiving sockcred (listening socket has LOCAL_CREDS) # TODO" \ - "Receiving sockcred (accepted socket has LOCAL_CREDS) # TODO" \ - "Sending cmsgcred, receiving sockcred # TODO" \ - "Sending, receiving timestamp" +for t1 in \ + "1 Sending, receiving cmsgcred" \ + "4 Sending cmsgcred, receiving sockcred" \ + "5 Sending, receiving timeval" \ + "6 Sending, receiving bintime" \ + "7 Check cmsghdr.cmsg_len" do - n=`expr ${n} + 1` - run ${n} stream "" ${n} "STREAM ${desc}" + for t2 in \ + "0 " \ + "1 (no data)" \ + "2 (no array)" \ + "3 (no data, array)" + do + n=$((n + 1)) + run ${n} stream "-z ${t2%% *}" STREAM "${t1} ${t2#* }" + done done -i=0 -for desc in \ - "Sending, receiving cmsgcred" \ - "Receiving sockcred # TODO" \ - "Sending cmsgcred, receiving sockcred # TODO" \ - "Sending, receiving timestamp" +for t1 in \ + "2 Receiving sockcred (listening socket)" \ + "3 Receiving sockcred (accepted socket)" do - i=`expr ${i} + 1` - n=`expr ${n} + 1` - run ${n} dgram "" ${i} "DGRAM ${desc}" + for t2 in \ + "0 " \ + "1 (no data)" + do + n=$((n + 1)) + run ${n} stream "-z ${t2%% *}" STREAM "${t1} ${t2#* }" + done done -run 10 stream -z 1 "STREAM Sending, receiving cmsgcred (no control data)" -run 11 stream -z 4 "STREAM Sending cmsgcred, receiving sockcred (no control data) # TODO" -run 12 stream -z 5 "STREAM Sending, receiving timestamp (no control data)" +n=$((n + 1)) +run ${n} stream "-z 0" STREAM "8 Check LOCAL_PEERCRED socket option" -run 13 dgram -z 1 "DGRAM Sending, receiving cmsgcred (no control data)" -run 14 dgram -z 3 "DGRAM Sending cmsgcred, receiving sockcred (no control data) # TODO" -run 15 dgram -z 4 "DGRAM Sending, receiving timestamp (no control data)" +for t1 in \ + "1 Sending, receiving cmsgcred" \ + "3 Sending cmsgcred, receiving sockcred" \ + "4 Sending, receiving timeval" \ + "5 Sending, receiving bintime" \ + "6 Check cmsghdr.cmsg_len" +do + for t2 in \ + "0 " \ + "1 (no data)" \ + "2 (no array)" \ + "3 (no data, array)" + do + n=$((n + 1)) + run ${n} dgram "-z ${t2%% *}" DGRAM "${t1} ${t2#* }" + done +done + +for t1 in \ + "2 Receiving sockcred" +do + for t2 in \ + "0 " \ + "1 (no data)" + do + n=$((n + 1)) + run ${n} dgram "-z ${t2%% *}" DGRAM "${t1} ${t2#* }" + done +done |