diff options
Diffstat (limited to 'src/lib/fdpass.c')
-rw-r--r-- | src/lib/fdpass.c | 212 |
1 files changed, 212 insertions, 0 deletions
diff --git a/src/lib/fdpass.c b/src/lib/fdpass.c new file mode 100644 index 0000000..bd54443 --- /dev/null +++ b/src/lib/fdpass.c @@ -0,0 +1,212 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +/* + fdpass.c - File descriptor passing between processes via UNIX sockets + + This isn't fully portable, but pretty much all UNIXes nowadays should + support this. If you're having runtime problems with fd_read(), check the + end of fd_read() and play with the if condition. If you're having problems + with fd_send(), try defining BUGGY_CMSG_MACROS. + + If this file doesn't compile at all, you should check if this is supported + in your system at all. It may require some extra #define to enable it. + If not, you're pretty much out of luck. Cygwin didn't last I checked. +*/ + +#define _XPG4_2 + +#if defined(irix) || defined (__irix__) || defined(sgi) || defined (__sgi__) +# define _XOPEN_SOURCE 4 /* for IRIX */ +#endif + +#if !defined(_AIX) && !defined(_XOPEN_SOURCE_EXTENDED) +# define _XOPEN_SOURCE_EXTENDED /* for Tru64, breaks AIX */ +#endif + +#ifdef HAVE_CONFIG_H +# include "lib.h" +#else +# define i_assert(x) +#endif + +#include <string.h> +#include <limits.h> +#include <sys/types.h> + +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/uio.h> + +#include "fdpass.h" + +#ifndef HAVE_CONFIG_H +struct const_iovec { + const void *iov_base; + size_t iov_len; +}; +#endif + +/* RFC 2292 defines CMSG_*() macros, but some operating systems don't have them + so we'll define our own if they don't exist. + + CMSG_LEN(data) is used to calculate size of sizeof(struct cmsghdr) + + sizeof(data) and padding between them. + + CMSG_SPACE(data) also calculates the padding needed after the data, in case + multiple objects are sent. + + cmsghdr contains cmsg_len field and two integers. cmsg_len is sometimes + defined as sockaddr_t and sometimes size_t, so it can be either 32bit or + 64bit. This padding is added by compiler in sizeof(struct cmsghdr). + + Padding required by CMSG_DATA() can vary. Usually it wants size_t or 32bit. + With Solaris it's in _CMSG_DATA_ALIGNMENT (32bit), we assume others want + size_t. + + We don't really need CMSG_SPACE() to be exactly correct, because currently + we send only one object at a time. But anyway I'm trying to keep that + correct in case it's sometimes needed.. +*/ + +#ifdef BUGGY_CMSG_MACROS +/* Some OSes have broken CMSG macros in 64bit systems. The macros use 64bit + alignment while kernel uses 32bit alignment. */ +# undef CMSG_SPACE +# undef CMSG_LEN +# undef CMSG_DATA +# define CMSG_DATA(cmsg) ((char *)((cmsg) + 1)) +# define _CMSG_DATA_ALIGNMENT 4 +# define _CMSG_HDR_ALIGNMENT 4 +#endif + +#ifndef CMSG_SPACE +# define MY_ALIGN(len, align) \ + (((len) + align - 1) & ~(align - 1)) + +/* Alignment between cmsghdr and data */ +# ifndef _CMSG_DATA_ALIGNMENT +# define _CMSG_DATA_ALIGNMENT sizeof(size_t) +# endif +/* Alignment between data and next cmsghdr */ +# ifndef _CMSG_HDR_ALIGNMENT +# define _CMSG_HDR_ALIGNMENT sizeof(size_t) +# endif + +# define CMSG_SPACE(len) \ + (MY_ALIGN(sizeof(struct cmsghdr), _CMSG_DATA_ALIGNMENT) + \ + MY_ALIGN(len, _CMSG_HDR_ALIGNMENT)) +# define CMSG_LEN(len) \ + (MY_ALIGN(sizeof(struct cmsghdr), _CMSG_DATA_ALIGNMENT) + (len)) +#endif + +#ifdef SCM_RIGHTS + +ssize_t fd_send(int handle, int send_fd, const void *data, size_t size) +{ + struct msghdr msg; + struct const_iovec iov; + struct cmsghdr *cmsg; + char buf[CMSG_SPACE(sizeof(int))]; + + /* at least one byte is required to be sent with fd passing */ + i_assert(size > 0 && size < INT_MAX); + + memset(&msg, 0, sizeof(struct msghdr)); + + iov.iov_base = data; + iov.iov_len = size; + + msg.msg_iov = (void *)&iov; + msg.msg_iovlen = 1; + + if (send_fd != -1) { + /* set the control and controllen before CMSG_FIRSTHDR(). */ + memset(buf, 0, sizeof(buf)); + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(send_fd)); + + /* set the real length we want to use. Do it after all is + set just in case CMSG macros required the extra padding + in the end. */ + msg.msg_controllen = cmsg->cmsg_len; + } + + return sendmsg(handle, &msg, 0); +} + +#ifdef LINUX20 +/* Linux 2.0.x doesn't set any cmsg fields. Note that this might make some + attacks possible so don't do it unless you really have to. */ +# define CHECK_CMSG(cmsg) ((cmsg) != NULL) +#else +# define CHECK_CMSG(cmsg) \ + ((cmsg) != NULL && \ + (size_t)(cmsg)->cmsg_len >= (size_t)CMSG_LEN(sizeof(int)) && \ + (cmsg)->cmsg_level == SOL_SOCKET && (cmsg)->cmsg_type == SCM_RIGHTS) +#endif + +ssize_t fd_read(int handle, void *data, size_t size, int *fd) +{ + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsg; + ssize_t ret; + char buf[CMSG_SPACE(sizeof(int))]; + + i_assert(size > 0 && size < INT_MAX); + + memset(&msg, 0, sizeof (struct msghdr)); + + iov.iov_base = data; + iov.iov_len = size; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + memset(buf, 0, sizeof(buf)); + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + + ret = recvmsg(handle, &msg, 0); + if (ret <= 0) { + *fd = -1; + return ret; + } + + /* at least one byte transferred - we should have the fd now. + do extra checks to make sure it really is an fd that is being + transferred to avoid potential DoS conditions. some systems don't + set all these values correctly however so CHECK_CMSG() is somewhat + system dependent */ + cmsg = CMSG_FIRSTHDR(&msg); + if (!CHECK_CMSG(cmsg)) + *fd = -1; + else + memcpy(fd, CMSG_DATA(cmsg), sizeof(*fd)); + return ret; +} + +#else +# ifdef __GNUC__ +# warning SCM_RIGHTS not supported, privilege separation not possible +# endif +ssize_t fd_send(int handle ATTR_UNUSED, int send_fd ATTR_UNUSED, + const void *data ATTR_UNUSED, size_t size ATTR_UNUSED) +{ + errno = ENOSYS; + return -1; +} + +ssize_t fd_read(int handle ATTR_UNUSED, void *data ATTR_UNUSED, + size_t size ATTR_UNUSED, int *fd ATTR_UNUSED) +{ + errno = ENOSYS; + return -1; +} +#endif |