xref: /openbsd/usr.sbin/smtpd/proxy.c (revision 771fbea0)
1 /*
2  * Copyright (c) 2017 Antoine Kaufmann <toni@famkaufmann.info>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <sys/types.h>
18 #include <sys/socket.h>
19 #include <sys/tree.h>
20 #include <sys/un.h>
21 
22 #include <errno.h>
23 #include <event.h>
24 #include <imsg.h>
25 #include <inttypes.h>
26 #include <limits.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 
32 #include "log.h"
33 #include "smtpd.h"
34 
35 
36 /*
37  * The PROXYv2 protocol is described here:
38  * http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt
39  */
40 
41 #define PROXY_CLOCAL 0x0
42 #define PROXY_CPROXY 0x1
43 #define PROXY_AF_UNSPEC 0x0
44 #define PROXY_AF_INET 0x1
45 #define PROXY_AF_INET6 0x2
46 #define PROXY_AF_UNIX 0x3
47 #define PROXY_TF_UNSPEC 0x0
48 #define PROXY_TF_STREAM 0x1
49 #define PROXY_TF_DGRAM 0x2
50 
51 #define PROXY_SESSION_TIMEOUT	300
52 
53 static const uint8_t pv2_signature[] = {
54 	0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D,
55 	0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A
56 };
57 
58 struct proxy_hdr_v2 {
59 	uint8_t sig[12];
60 	uint8_t ver_cmd;
61 	uint8_t fam;
62 	uint16_t len;
63 } __attribute__((packed));
64 
65 
66 struct proxy_addr_ipv4 {
67 	uint32_t src_addr;
68 	uint32_t dst_addr;
69 	uint16_t src_port;
70 	uint16_t dst_port;
71 } __attribute__((packed));
72 
73 struct proxy_addr_ipv6 {
74 	uint8_t src_addr[16];
75 	uint8_t dst_addr[16];
76 	uint16_t src_port;
77 	uint16_t dst_port;
78 } __attribute__((packed));
79 
80 struct proxy_addr_unix {
81 	uint8_t src_addr[108];
82 	uint8_t dst_addr[108];
83 } __attribute__((packed));
84 
85 union proxy_addr {
86 	struct proxy_addr_ipv4 ipv4;
87 	struct proxy_addr_ipv6 ipv6;
88 	struct proxy_addr_unix un;
89 } __attribute__((packed));
90 
91 struct proxy_session {
92 	struct listener	*l;
93 	struct io	*io;
94 
95 	uint64_t	id;
96 	int		fd;
97 	uint16_t	header_len;
98 	uint16_t	header_total;
99 	uint16_t	addr_len;
100 	uint16_t	addr_total;
101 
102 	struct sockaddr_storage	ss;
103 	struct proxy_hdr_v2	hdr;
104 	union proxy_addr	addr;
105 
106 	void (*cb_accepted)(struct listener *, int,
107 	    const struct sockaddr_storage *, struct io *);
108 	void (*cb_dropped)(struct listener *, int,
109 	    const struct sockaddr_storage *);
110 };
111 
112 static void proxy_io(struct io *, int, void *);
113 static void proxy_error(struct proxy_session *, const char *, const char *);
114 static int proxy_header_validate(struct proxy_session *);
115 static int proxy_translate_ss(struct proxy_session *);
116 
117 int
118 proxy_session(struct listener *listener, int sock,
119     const struct sockaddr_storage *ss,
120     void (*accepted)(struct listener *, int,
121 	const struct sockaddr_storage *, struct io *),
122     void (*dropped)(struct listener *, int,
123 	const struct sockaddr_storage *));
124 
125 int
126 proxy_session(struct listener *listener, int sock,
127     const struct sockaddr_storage *ss,
128     void (*accepted)(struct listener *, int,
129 	const struct sockaddr_storage *, struct io *),
130     void (*dropped)(struct listener *, int,
131 	const struct sockaddr_storage *))
132 {
133 	struct proxy_session *s;
134 
135 	if ((s = calloc(1, sizeof(*s))) == NULL)
136 		return (-1);
137 
138 	if ((s->io = io_new()) == NULL) {
139 		free(s);
140 		return (-1);
141 	}
142 
143 	s->id = generate_uid();
144 	s->l = listener;
145 	s->fd = sock;
146 	s->header_len = 0;
147 	s->addr_len = 0;
148 	s->ss = *ss;
149 	s->cb_accepted = accepted;
150 	s->cb_dropped = dropped;
151 
152 	io_set_callback(s->io, proxy_io, s);
153 	io_set_fd(s->io, sock);
154 	io_set_timeout(s->io, PROXY_SESSION_TIMEOUT * 1000);
155 	io_set_read(s->io);
156 
157 	log_info("%016"PRIx64" smtp event=proxy address=%s",
158 	    s->id, ss_to_text(&s->ss));
159 
160 	return 0;
161 }
162 
163 static void
164 proxy_io(struct io *io, int evt, void *arg)
165 {
166 	struct proxy_session	*s = arg;
167 	struct proxy_hdr_v2	*h = &s->hdr;
168 	uint8_t *buf;
169 	size_t len, off;
170 
171 	switch (evt) {
172 
173 	case IO_DATAIN:
174 		buf = io_data(io);
175 		len = io_datalen(io);
176 
177 		if (s->header_len < sizeof(s->hdr)) {
178 			/* header is incomplete */
179 			off = sizeof(s->hdr) - s->header_len;
180 			off = (len < off ? len : off);
181 			memcpy((uint8_t *) &s->hdr + s->header_len, buf, off);
182 
183 			s->header_len += off;
184 			buf += off;
185 			len -= off;
186 			io_drop(s->io, off);
187 
188 			if (s->header_len < sizeof(s->hdr)) {
189 				/* header is still not complete */
190 				return;
191 			}
192 
193 			if (proxy_header_validate(s) != 0)
194 				return;
195 		}
196 
197 		if (s->addr_len < s->addr_total) {
198 			/* address is incomplete */
199 			off = s->addr_total - s->addr_len;
200 			off = (len < off ? len : off);
201 			memcpy((uint8_t *) &s->addr + s->addr_len, buf, off);
202 
203 			s->header_len += off;
204 			s->addr_len += off;
205 			buf += off;
206 			len -= off;
207 			io_drop(s->io, off);
208 
209 			if (s->addr_len < s->addr_total) {
210 				/* address is still not complete */
211 				return;
212 			}
213 		}
214 
215 		if (s->header_len < s->header_total) {
216 			/* additional parameters not complete */
217 			/* these are ignored for now, but we still need to drop
218 			 * the bytes from the buffer */
219 			off = s->header_total - s->header_len;
220 			off = (len < off ? len : off);
221 
222 			s->header_len += off;
223 			io_drop(s->io, off);
224 
225 			if (s->header_len < s->header_total)
226 				/* not complete yet */
227 				return;
228 		}
229 
230 		switch(h->ver_cmd & 0xF) {
231 		case PROXY_CLOCAL:
232 			/* local address, no need to modify ss */
233 			break;
234 
235 		case PROXY_CPROXY:
236 			if (proxy_translate_ss(s) != 0)
237 				return;
238 			break;
239 
240 		default:
241 			proxy_error(s, "protocol error", "unknown command");
242 			return;
243 		}
244 
245 		s->cb_accepted(s->l, s->fd, &s->ss, s->io);
246 		/* we passed off s->io, so it does not need to be freed here */
247 		free(s);
248 		break;
249 
250 	case IO_TIMEOUT:
251 		proxy_error(s, "timeout", NULL);
252 		break;
253 
254 	case IO_DISCONNECTED:
255 		proxy_error(s, "disconnected", NULL);
256 		break;
257 
258 	case IO_ERROR:
259 		proxy_error(s, "IO error", io_error(io));
260 		break;
261 
262 	default:
263 		fatalx("proxy_io()");
264 	}
265 
266 }
267 
268 static void
269 proxy_error(struct proxy_session *s, const char *reason, const char *extra)
270 {
271 	if (extra)
272 		log_info("proxy %p event=closed address=%s reason=\"%s (%s)\"",
273 		    s, ss_to_text(&s->ss), reason, extra);
274 	else
275 		log_info("proxy %p event=closed address=%s reason=\"%s\"",
276 		    s, ss_to_text(&s->ss), reason);
277 
278 	s->cb_dropped(s->l, s->fd, &s->ss);
279 	io_free(s->io);
280 	free(s);
281 }
282 
283 static int
284 proxy_header_validate(struct proxy_session *s)
285 {
286 	struct proxy_hdr_v2 *h = &s->hdr;
287 
288 	if (memcmp(h->sig, pv2_signature,
289 		sizeof(pv2_signature)) != 0) {
290 		proxy_error(s, "protocol error", "invalid signature");
291 		return (-1);
292 	}
293 
294 	if ((h->ver_cmd >> 4) != 2) {
295 		proxy_error(s, "protocol error", "invalid version");
296 		return (-1);
297 	}
298 
299 	switch (h->fam) {
300 	case (PROXY_AF_UNSPEC << 4 | PROXY_TF_UNSPEC):
301 		s->addr_total = 0;
302 		break;
303 
304 	case (PROXY_AF_INET << 4 | PROXY_TF_STREAM):
305 		s->addr_total = sizeof(s->addr.ipv4);
306 		break;
307 
308 	case (PROXY_AF_INET6 << 4 | PROXY_TF_STREAM):
309 		s->addr_total = sizeof(s->addr.ipv6);
310 		break;
311 
312 	case (PROXY_AF_UNIX << 4 | PROXY_TF_STREAM):
313 		s->addr_total = sizeof(s->addr.un);
314 		break;
315 
316 	default:
317 		proxy_error(s, "protocol error", "unsupported address family");
318 		return (-1);
319 	}
320 
321 	s->header_total = ntohs(h->len);
322 	if (s->header_total > UINT16_MAX - sizeof(struct proxy_hdr_v2)) {
323 		proxy_error(s, "protocol error", "header too long");
324 		return (-1);
325 	}
326 	s->header_total += sizeof(struct proxy_hdr_v2);
327 
328 	if (s->header_total < sizeof(struct proxy_hdr_v2) + s->addr_total) {
329 		proxy_error(s, "protocol error", "address info too short");
330 		return (-1);
331 	}
332 
333 	return 0;
334 }
335 
336 static int
337 proxy_translate_ss(struct proxy_session *s)
338 {
339 	struct sockaddr_in *sin = (struct sockaddr_in *) &s->ss;
340 	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &s->ss;
341 	struct sockaddr_un *sun = (struct sockaddr_un *) &s->ss;
342 	size_t sun_len;
343 
344 	switch (s->hdr.fam) {
345 	case (PROXY_AF_UNSPEC << 4 | PROXY_TF_UNSPEC):
346 		/* unspec: only supported for local */
347 		proxy_error(s, "address translation", "UNSPEC family not "
348 		    "supported for PROXYing");
349 		return (-1);
350 
351 	case (PROXY_AF_INET << 4 | PROXY_TF_STREAM):
352 		memset(&s->ss, 0, sizeof(s->ss));
353 		sin->sin_family = AF_INET;
354 		sin->sin_port = s->addr.ipv4.src_port;
355 		sin->sin_addr.s_addr = s->addr.ipv4.src_addr;
356 		break;
357 
358 	case (PROXY_AF_INET6 << 4 | PROXY_TF_STREAM):
359 		memset(&s->ss, 0, sizeof(s->ss));
360 		sin6->sin6_family = AF_INET6;
361 		sin6->sin6_port = s->addr.ipv6.src_port;
362 		memcpy(sin6->sin6_addr.s6_addr, s->addr.ipv6.src_addr,
363 		    sizeof(s->addr.ipv6.src_addr));
364 		break;
365 
366 	case (PROXY_AF_UNIX << 4 | PROXY_TF_STREAM):
367 		memset(&s->ss, 0, sizeof(s->ss));
368 		sun_len = strnlen(s->addr.un.src_addr,
369 		    sizeof(s->addr.un.src_addr));
370 		if (sun_len > sizeof(sun->sun_path)) {
371 			proxy_error(s, "address translation", "Unix socket path"
372 			    " longer than supported");
373 			return (-1);
374 		}
375 		sun->sun_family = AF_UNIX;
376 		memcpy(sun->sun_path, s->addr.un.src_addr, sun_len);
377 		break;
378 
379 	default:
380 		fatalx("proxy_translate_ss()");
381 	}
382 
383 	return 0;
384 }
385