1 /* $NetBSD: unix_send_fd.c,v 1.8 2023/12/23 20:30:46 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* unix_send_fd 3 6 /* SUMMARY 7 /* send file descriptor 8 /* SYNOPSIS 9 /* #include <iostuff.h> 10 /* 11 /* int unix_send_fd(fd, sendfd) 12 /* int fd; 13 /* int sendfd; 14 /* DESCRIPTION 15 /* unix_send_fd() sends a file descriptor over the specified 16 /* UNIX-domain socket. 17 /* 18 /* Arguments: 19 /* .IP fd 20 /* File descriptor that connects the sending and receiving processes. 21 /* .IP sendfd 22 /* The file descriptor to be sent. 23 /* DIAGNOSTICS 24 /* unix_send_fd() returns -1 upon failure. 25 /* LICENSE 26 /* .ad 27 /* .fi 28 /* The Secure Mailer license must be distributed with this software. 29 /* AUTHOR(S) 30 /* Wietse Venema 31 /* IBM T.J. Watson Research 32 /* P.O. Box 704 33 /* Yorktown Heights, NY 10598, USA 34 /* 35 /* Wietse Venema 36 /* Google, Inc. 37 /* 111 8th Avenue 38 /* New York, NY 10011, USA 39 /*--*/ 40 41 /* System library. */ 42 43 #include <sys_defs.h> /* includes <sys/types.h> */ 44 #include <sys/socket.h> 45 #include <sys/uio.h> 46 #include <string.h> 47 48 /* Utility library. */ 49 50 #include <msg.h> 51 #include <iostuff.h> 52 53 /* unix_send_fd - send file descriptor */ 54 55 int unix_send_fd(int fd, int sendfd) 56 { 57 58 /* 59 * This code does not work with version <2.2 Linux kernels, and it does 60 * not compile with version <2 Linux libraries. 61 */ 62 #ifdef CANT_USE_SEND_RECV_MSG 63 const char *myname = "unix_send_fd"; 64 65 msg_warn("%s: your system has no support for file descriptor passing", 66 myname); 67 return (-1); 68 #else 69 struct msghdr msg; 70 struct iovec iov[1]; 71 72 /* 73 * Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1, 74 * Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for 75 * portability to some LP64 environments. See also unix_recv_fd.c. 76 */ 77 #if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL) 78 union { 79 struct cmsghdr just_for_alignment; 80 char control[CMSG_SPACE(sizeof(sendfd))]; 81 } control_un; 82 struct cmsghdr *cmptr; 83 84 memset((void *) &msg, 0, sizeof(msg)); /* Fix 200512 */ 85 memset((void *) &control_un, 0, sizeof(control_un)); /* Fix 202302 */ 86 msg.msg_control = control_un.control; 87 if (unix_pass_fd_fix & UNIX_PASS_FD_FIX_CMSG_LEN) { 88 msg.msg_controllen = CMSG_LEN(sizeof(sendfd)); /* Fix 200506 */ 89 } else { 90 msg.msg_controllen = CMSG_SPACE(sizeof(sendfd)); /* normal */ 91 } 92 cmptr = CMSG_FIRSTHDR(&msg); 93 cmptr->cmsg_len = CMSG_LEN(sizeof(sendfd)); 94 cmptr->cmsg_level = SOL_SOCKET; 95 cmptr->cmsg_type = SCM_RIGHTS; 96 *(int *) CMSG_DATA(cmptr) = sendfd; 97 #else 98 msg.msg_accrights = (char *) &sendfd; 99 msg.msg_accrightslen = sizeof(sendfd); 100 #endif 101 102 msg.msg_name = 0; 103 msg.msg_namelen = 0; 104 105 /* 106 * XXX We don't want to pass any data, just a file descriptor. However, 107 * setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble. See the 108 * comments in the unix_recv_fd() routine. 109 */ 110 iov->iov_base = ""; 111 iov->iov_len = 1; 112 msg.msg_iov = iov; 113 msg.msg_iovlen = 1; 114 115 /* 116 * The CMSG_LEN send/receive workaround was originally developed for 117 * OpenBSD 3.6 on SPARC64. After the workaround was verified to not break 118 * Solaris 8 on SPARC64, it was hard-coded with Postfix 2.3 for all 119 * platforms because of increasing pressure to work on other things. The 120 * workaround does nothing for 32-bit systems. 121 * 122 * The investigation was reopened with Postfix 2.7 because the workaround 123 * broke with NetBSD 5.0 on 64-bit architectures. This time it was found 124 * that OpenBSD <= 4.3 on AMD64 and SPARC64 needed the workaround for 125 * sending only. The following platforms worked with and without the 126 * workaround: OpenBSD 4.5 on AMD64 and SPARC64, FreeBSD 7.2 on AMD64, 127 * Solaris 8 on SPARC64, and Linux 2.6-11 on x86_64. 128 * 129 * As this appears to have been an OpenBSD-specific problem, we revert to 130 * the Postfix 2.2 behavior. Instead of hard-coding the workaround for 131 * all platforms, we now detect sendmsg() errors at run time and turn on 132 * the workaround dynamically. 133 * 134 * The workaround was made run-time configurable to investigate the problem 135 * on multiple platforms. Though set_unix_pass_fd_fix() is over-kill for 136 * this specific problem, it is left in place so that it can serve as an 137 * example of how to add run-time configurable workarounds to Postfix. 138 */ 139 if (sendmsg(fd, &msg, 0) >= 0) 140 return (0); 141 if (unix_pass_fd_fix == 0) { 142 if (msg_verbose) 143 msg_info("sendmsg error (%m). Trying CMSG_LEN workaround."); 144 unix_pass_fd_fix = UNIX_PASS_FD_FIX_CMSG_LEN; 145 return (unix_send_fd(fd, sendfd)); 146 } else { 147 return (-1); 148 } 149 #endif 150 } 151 152 #ifdef TEST 153 154 /* 155 * Proof-of-concept program. Open a file and send the descriptor, presumably 156 * to the unix_recv_fd test program. 157 */ 158 #include <unistd.h> 159 #include <string.h> 160 #include <stdlib.h> 161 #include <fcntl.h> 162 #include <split_at.h> 163 #include <connect.h> 164 165 int main(int argc, char **argv) 166 { 167 char *transport; 168 char *endpoint; 169 char *path; 170 int server_sock; 171 int client_fd; 172 173 msg_verbose = 1; 174 175 if (argc < 3 176 || (endpoint = split_at(transport = argv[1], ':')) == 0 177 || *endpoint == 0 || *transport == 0) 178 msg_fatal("usage: %s transport:endpoint file...", argv[0]); 179 180 if (strcmp(transport, "unix") == 0) { 181 server_sock = unix_connect(endpoint, BLOCKING, 0); 182 } else { 183 msg_fatal("invalid transport name: %s", transport); 184 } 185 if (server_sock < 0) 186 msg_fatal("connect %s:%s: %m", transport, endpoint); 187 188 argv += 2; 189 while ((path = *argv++) != 0) { 190 if ((client_fd = open(path, O_RDONLY, 0)) < 0) 191 msg_fatal("open %s: %m", path); 192 msg_info("path=%s fd=%d", path, client_fd); 193 if (unix_send_fd(server_sock, client_fd) < 0) 194 msg_fatal("send file descriptor: %m"); 195 if (close(client_fd) != 0) 196 msg_fatal("close(%d): %m", client_fd); 197 } 198 exit(0); 199 } 200 201 #endif 202