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