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