summaryrefslogtreecommitdiffstats
path: root/src/lib/fdpass.c
blob: bd544432b4daa6a4f01ca5762a3e1eb6e61cd8c7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
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