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