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