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