1 /* $NetBSD: haproxy_srvr.c,v 1.3 2022/10/08 16:12:45 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* haproxy_srvr 3
6 /* SUMMARY
7 /* server-side haproxy protocol support
8 /* SYNOPSIS
9 /* #include <haproxy_srvr.h>
10 /*
11 /* const char *haproxy_srvr_parse(str, str_len, non_proxy,
12 /* smtp_client_addr, smtp_client_port,
13 /* smtp_server_addr, smtp_server_port)
14 /* const char *str;
15 /* ssize_t *str_len;
16 /* int *non_proxy;
17 /* MAI_HOSTADDR_STR *smtp_client_addr,
18 /* MAI_SERVPORT_STR *smtp_client_port,
19 /* MAI_HOSTADDR_STR *smtp_server_addr,
20 /* MAI_SERVPORT_STR *smtp_server_port;
21 /*
22 /* const char *haproxy_srvr_receive(fd, non_proxy,
23 /* smtp_client_addr, smtp_client_port,
24 /* smtp_server_addr, smtp_server_port)
25 /* int fd;
26 /* int *non_proxy;
27 /* MAI_HOSTADDR_STR *smtp_client_addr,
28 /* MAI_SERVPORT_STR *smtp_client_port,
29 /* MAI_HOSTADDR_STR *smtp_server_addr,
30 /* MAI_SERVPORT_STR *smtp_server_port;
31 /* DESCRIPTION
32 /* haproxy_srvr_parse() parses a haproxy v1 or v2 protocol
33 /* message. The result is null in case of success, a pointer
34 /* to text (with the error type) in case of error. If both
35 /* IPv6 and IPv4 support are enabled, IPV4_IN_IPV6 address
36 /* form (::ffff:1.2.3.4) is converted to IPV4 form. In case
37 /* of success, the str_len argument is updated with the number
38 /* of bytes parsed, and the non_proxy argument is true or false
39 /* if the haproxy message specifies a non-proxied connection.
40 /*
41 /* haproxy_srvr_receive() receives and parses a haproxy protocol
42 /* handshake. This must be called before any I/O is done on
43 /* the specified file descriptor. The result is 0 in case of
44 /* success, -1 in case of error. All errors are logged.
45 /*
46 /* The haproxy v2 protocol support is limited to TCP over IPv4,
47 /* TCP over IPv6, and non-proxied connections. In the latter
48 /* case, the caller is responsible for any local or remote
49 /* address/port lookup.
50 /* LICENSE
51 /* .ad
52 /* .fi
53 /* The Secure Mailer license must be distributed with this software.
54 /* AUTHOR(S)
55 /* Wietse Venema
56 /* IBM T.J. Watson Research
57 /* P.O. Box 704
58 /* Yorktown Heights, NY 10598, USA
59 /*
60 /* Wietse Venema
61 /* Google, Inc.
62 /* 111 8th Avenue
63 /* New York, NY 10011, USA
64 /*--*/
65
66 /* System library. */
67
68 #include <sys_defs.h>
69 #include <stdarg.h>
70 #include <stdlib.h>
71 #include <string.h>
72
73 #ifdef STRCASECMP_IN_STRINGS_H
74 #include <strings.h>
75 #endif
76
77 /* Utility library. */
78
79 #include <msg.h>
80 #include <myaddrinfo.h>
81 #include <valid_hostname.h>
82 #include <stringops.h>
83 #include <mymalloc.h>
84 #include <inet_proto.h>
85 #include <split_at.h>
86 #include <sock_addr.h>
87
88 /* Global library. */
89
90 #include <haproxy_srvr.h>
91
92 /* Application-specific. */
93
94 /*
95 * The haproxy protocol assumes that a haproxy header will normally not
96 * exceed the default IPv4 TCP MSS, i.e. 576-40=536 bytes (the IPv6 default
97 * is larger: 1280-60=1220). With a proxy header that contains IPv6
98 * addresses, that leaves room for 536-52=484 bytes of TLVs. The Postfix
99 * implementation does not support headers with UNIX-domain addresses.
100 */
101 #define HAPROXY_HEADER_MAX_LEN 536
102
103 /*
104 * Begin protocol v2 definitions from haproxy/include/types/connection.h.
105 */
106 #define PP2_SIGNATURE "\r\n\r\n\0\r\nQUIT\n"
107 #define PP2_SIGNATURE_LEN 12
108 #define PP2_HEADER_LEN 16
109
110 /* ver_cmd byte */
111 #define PP2_CMD_LOCAL 0x00
112 #define PP2_CMD_PROXY 0x01
113 #define PP2_CMD_MASK 0x0F
114
115 #define PP2_VERSION 0x20
116 #define PP2_VERSION_MASK 0xF0
117
118 /* fam byte */
119 #define PP2_TRANS_UNSPEC 0x00
120 #define PP2_TRANS_STREAM 0x01
121 #define PP2_TRANS_DGRAM 0x02
122 #define PP2_TRANS_MASK 0x0F
123
124 #define PP2_FAM_UNSPEC 0x00
125 #define PP2_FAM_INET 0x10
126 #define PP2_FAM_INET6 0x20
127 #define PP2_FAM_UNIX 0x30
128 #define PP2_FAM_MASK 0xF0
129
130 /* len field (2 bytes) */
131 #define PP2_ADDR_LEN_UNSPEC (0)
132 #define PP2_ADDR_LEN_INET (4 + 4 + 2 + 2)
133 #define PP2_ADDR_LEN_INET6 (16 + 16 + 2 + 2)
134 #define PP2_ADDR_LEN_UNIX (108 + 108)
135
136 #define PP2_HDR_LEN_UNSPEC (PP2_HEADER_LEN + PP2_ADDR_LEN_UNSPEC)
137 #define PP2_HDR_LEN_INET (PP2_HEADER_LEN + PP2_ADDR_LEN_INET)
138 #define PP2_HDR_LEN_INET6 (PP2_HEADER_LEN + PP2_ADDR_LEN_INET6)
139 #define PP2_HDR_LEN_UNIX (PP2_HEADER_LEN + PP2_ADDR_LEN_UNIX)
140
141 struct proxy_hdr_v2 {
142 uint8_t sig[PP2_SIGNATURE_LEN]; /* PP2_SIGNATURE */
143 uint8_t ver_cmd; /* protocol version | command */
144 uint8_t fam; /* protocol family and transport */
145 uint16_t len; /* length of remainder */
146 union {
147 struct { /* for TCP/UDP over IPv4, len = 12 */
148 uint32_t src_addr;
149 uint32_t dst_addr;
150 uint16_t src_port;
151 uint16_t dst_port;
152 } ip4;
153 struct { /* for TCP/UDP over IPv6, len = 36 */
154 uint8_t src_addr[16];
155 uint8_t dst_addr[16];
156 uint16_t src_port;
157 uint16_t dst_port;
158 } ip6;
159 struct { /* for AF_UNIX sockets, len = 216 */
160 uint8_t src_addr[108];
161 uint8_t dst_addr[108];
162 } unx;
163 } addr;
164 };
165
166 /*
167 * End protocol v2 definitions from haproxy/include/types/connection.h.
168 */
169
170 static const INET_PROTO_INFO *proto_info;
171
172 #define STR_OR_NULL(str) ((str) ? (str) : "(null)")
173
174 /* haproxy_srvr_parse_lit - extract and validate string literal */
175
haproxy_srvr_parse_lit(const char * str,...)176 static int haproxy_srvr_parse_lit(const char *str,...)
177 {
178 va_list ap;
179 const char *cp;
180 int result = -1;
181 int count;
182
183 if (msg_verbose)
184 msg_info("haproxy_srvr_parse: %s", STR_OR_NULL(str));
185
186 if (str != 0) {
187 va_start(ap, str);
188 for (count = 0; (cp = va_arg(ap, const char *)) != 0; count++) {
189 if (strcmp(str, cp) == 0) {
190 result = count;
191 break;
192 }
193 }
194 va_end(ap);
195 }
196 return (result);
197 }
198
199 /* haproxy_srvr_parse_proto - parse and validate the protocol type */
200
haproxy_srvr_parse_proto(const char * str,int * addr_family)201 static int haproxy_srvr_parse_proto(const char *str, int *addr_family)
202 {
203 if (msg_verbose)
204 msg_info("haproxy_srvr_parse: proto=%s", STR_OR_NULL(str));
205
206 if (str == 0)
207 return (-1);
208 #ifdef AF_INET6
209 if (strcasecmp(str, "TCP6") == 0) {
210 if (strchr((char *) proto_info->sa_family_list, AF_INET6) != 0) {
211 *addr_family = AF_INET6;
212 return (0);
213 }
214 } else
215 #endif
216 if (strcasecmp(str, "TCP4") == 0) {
217 if (strchr((char *) proto_info->sa_family_list, AF_INET) != 0) {
218 *addr_family = AF_INET;
219 return (0);
220 }
221 }
222 return (-1);
223 }
224
225 /* haproxy_srvr_parse_addr - extract and validate IP address */
226
haproxy_srvr_parse_addr(const char * str,MAI_HOSTADDR_STR * addr,int addr_family)227 static int haproxy_srvr_parse_addr(const char *str, MAI_HOSTADDR_STR *addr,
228 int addr_family)
229 {
230 struct addrinfo *res = 0;
231 int err;
232
233 if (msg_verbose)
234 msg_info("haproxy_srvr_parse: addr=%s proto=%d",
235 STR_OR_NULL(str), addr_family);
236
237 if (str == 0 || strlen(str) >= sizeof(MAI_HOSTADDR_STR))
238 return (-1);
239
240 switch (addr_family) {
241 #ifdef AF_INET6
242 case AF_INET6:
243 err = !valid_ipv6_hostaddr(str, DONT_GRIPE);
244 break;
245 #endif
246 case AF_INET:
247 err = !valid_ipv4_hostaddr(str, DONT_GRIPE);
248 break;
249 default:
250 msg_panic("haproxy_srvr_parse: unexpected address family: %d",
251 addr_family);
252 }
253 if (err == 0)
254 err = (hostaddr_to_sockaddr(str, (char *) 0, 0, &res)
255 || sockaddr_to_hostaddr(res->ai_addr, res->ai_addrlen,
256 addr, (MAI_SERVPORT_STR *) 0, 0));
257 if (res)
258 freeaddrinfo(res);
259 if (err)
260 return (-1);
261 if (addr->buf[0] == ':' && strncasecmp("::ffff:", addr->buf, 7) == 0
262 && strchr((char *) proto_info->sa_family_list, AF_INET) != 0)
263 memmove(addr->buf, addr->buf + 7, strlen(addr->buf) + 1 - 7);
264 return (0);
265 }
266
267 /* haproxy_srvr_parse_port - extract and validate TCP port */
268
haproxy_srvr_parse_port(const char * str,MAI_SERVPORT_STR * port)269 static int haproxy_srvr_parse_port(const char *str, MAI_SERVPORT_STR *port)
270 {
271 if (msg_verbose)
272 msg_info("haproxy_srvr_parse: port=%s", STR_OR_NULL(str));
273 if (str == 0 || strlen(str) >= sizeof(MAI_SERVPORT_STR)
274 || !valid_hostport(str, DONT_GRIPE)) {
275 return (-1);
276 } else {
277 memcpy(port->buf, str, strlen(str) + 1);
278 return (0);
279 }
280 }
281
282 /* haproxy_srvr_parse_v2_addr_v4 - parse IPv4 info from v2 header */
283
haproxy_srvr_parse_v2_addr_v4(uint32_t sin_addr,unsigned sin_port,MAI_HOSTADDR_STR * addr,MAI_SERVPORT_STR * port)284 static int haproxy_srvr_parse_v2_addr_v4(uint32_t sin_addr,
285 unsigned sin_port,
286 MAI_HOSTADDR_STR *addr,
287 MAI_SERVPORT_STR *port)
288 {
289 struct sockaddr_in sin;
290
291 memset((void *) &sin, 0, sizeof(sin));
292 sin.sin_family = AF_INET;
293 sin.sin_addr.s_addr = sin_addr;
294 sin.sin_port = sin_port;
295 if (sockaddr_to_hostaddr((struct sockaddr *) &sin, sizeof(sin),
296 addr, port, 0) < 0)
297 return (-1);
298 return (0);
299 }
300
301 #ifdef AF_INET6
302
303 /* haproxy_srvr_parse_v2_addr_v6 - parse IPv6 info from v2 header */
304
haproxy_srvr_parse_v2_addr_v6(uint8_t * sin6_addr,unsigned sin6_port,MAI_HOSTADDR_STR * addr,MAI_SERVPORT_STR * port)305 static int haproxy_srvr_parse_v2_addr_v6(uint8_t *sin6_addr,
306 unsigned sin6_port,
307 MAI_HOSTADDR_STR *addr,
308 MAI_SERVPORT_STR *port)
309 {
310 struct sockaddr_in6 sin6;
311
312 memset((void *) &sin6, 0, sizeof(sin6));
313 sin6.sin6_family = AF_INET6;
314 memcpy(&sin6.sin6_addr, sin6_addr, 16);
315 sin6.sin6_port = sin6_port;
316 if (sockaddr_to_hostaddr((struct sockaddr *) &sin6,
317 sizeof(sin6), addr, port, 0) < 0)
318 return (-1);
319 if (addr->buf[0] == ':'
320 && strncasecmp("::ffff:", addr->buf, 7) == 0
321 && strchr((char *) proto_info->sa_family_list, AF_INET) != 0)
322 memmove(addr->buf, addr->buf + 7,
323 strlen(addr->buf) + 1 - 7);
324 return (0);
325 }
326
327 #endif
328
329 /* haproxy_srvr_parse_v2_hdr - parse v2 header address info */
330
haproxy_srvr_parse_v2_hdr(const char * str,ssize_t * str_len,int * non_proxy,MAI_HOSTADDR_STR * smtp_client_addr,MAI_SERVPORT_STR * smtp_client_port,MAI_HOSTADDR_STR * smtp_server_addr,MAI_SERVPORT_STR * smtp_server_port)331 static const char *haproxy_srvr_parse_v2_hdr(const char *str, ssize_t *str_len,
332 int *non_proxy,
333 MAI_HOSTADDR_STR *smtp_client_addr,
334 MAI_SERVPORT_STR *smtp_client_port,
335 MAI_HOSTADDR_STR *smtp_server_addr,
336 MAI_SERVPORT_STR *smtp_server_port)
337 {
338 const char myname[] = "haproxy_srvr_parse_v2_hdr";
339 struct proxy_hdr_v2 *hdr_v2;
340
341 if (*str_len < PP2_HEADER_LEN)
342 return ("short protocol header");
343 hdr_v2 = (struct proxy_hdr_v2 *) str;
344 if (memcmp(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN) != 0)
345 return ("unrecognized protocol header");
346 if ((hdr_v2->ver_cmd & PP2_VERSION_MASK) != PP2_VERSION)
347 return ("unrecognized protocol version");
348 if (*str_len < PP2_HEADER_LEN + ntohs(hdr_v2->len))
349 return ("short version 2 protocol header");
350
351 switch (hdr_v2->ver_cmd & PP2_CMD_MASK) {
352
353 /*
354 * Proxied connection, use the proxy-provided connection info.
355 */
356 case PP2_CMD_PROXY:
357 switch (hdr_v2->fam) {
358 case PP2_FAM_INET | PP2_TRANS_STREAM:{ /* TCP4 */
359 if (strchr((char *) proto_info->sa_family_list, AF_INET) == 0)
360 return ("Postfix IPv4 support is disabled");
361 if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET)
362 return ("short address field");
363 if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.src_addr,
364 hdr_v2->addr.ip4.src_port,
365 smtp_client_addr, smtp_client_port) < 0)
366 return ("client network address conversion error");
367 if (msg_verbose)
368 msg_info("%s: smtp_client_addr=%s smtp_client_port=%s",
369 myname, smtp_client_addr->buf, smtp_client_port->buf);
370 if (haproxy_srvr_parse_v2_addr_v4(hdr_v2->addr.ip4.dst_addr,
371 hdr_v2->addr.ip4.dst_port,
372 smtp_server_addr, smtp_server_port) < 0)
373 return ("server network address conversion error");
374 if (msg_verbose)
375 msg_info("%s: smtp_server_addr=%s smtp_server_port=%s",
376 myname, smtp_server_addr->buf, smtp_server_port->buf);
377 break;
378 }
379 case PP2_FAM_INET6 | PP2_TRANS_STREAM:{/* TCP6 */
380 #ifdef AF_INET6
381 if (strchr((char *) proto_info->sa_family_list, AF_INET6) == 0)
382 return ("Postfix IPv6 support is disabled");
383 if (ntohs(hdr_v2->len) < PP2_ADDR_LEN_INET6)
384 return ("short address field");
385 if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.src_addr,
386 hdr_v2->addr.ip6.src_port,
387 smtp_client_addr,
388 smtp_client_port) < 0)
389 return ("client network address conversion error");
390 if (msg_verbose)
391 msg_info("%s: smtp_client_addr=%s smtp_client_port=%s",
392 myname, smtp_client_addr->buf, smtp_client_port->buf);
393 if (haproxy_srvr_parse_v2_addr_v6(hdr_v2->addr.ip6.dst_addr,
394 hdr_v2->addr.ip6.dst_port,
395 smtp_server_addr,
396 smtp_server_port) < 0)
397 return ("server network address conversion error");
398 if (msg_verbose)
399 msg_info("%s: smtp_server_addr=%s smtp_server_port=%s",
400 myname, smtp_server_addr->buf, smtp_server_port->buf);
401 break;
402 #else
403 return ("Postfix IPv6 support is not compiled in");
404 #endif
405 }
406 default:
407 return ("unsupported network protocol");
408 }
409 /* For now, skip and ignore TLVs. */
410 *str_len = PP2_HEADER_LEN + ntohs(hdr_v2->len);
411 return (0);
412
413 /*
414 * Non-proxied connection, use the proxy-to-server connection info.
415 */
416 case PP2_CMD_LOCAL:
417 /* For now, skip and ignore TLVs. */
418 *non_proxy = 1;
419 *str_len = PP2_HEADER_LEN + ntohs(hdr_v2->len);
420 return (0);
421 default:
422 return ("bad command in proxy header");
423 }
424 }
425
426 /* haproxy_srvr_parse - parse haproxy line */
427
haproxy_srvr_parse(const char * str,ssize_t * str_len,int * non_proxy,MAI_HOSTADDR_STR * smtp_client_addr,MAI_SERVPORT_STR * smtp_client_port,MAI_HOSTADDR_STR * smtp_server_addr,MAI_SERVPORT_STR * smtp_server_port)428 const char *haproxy_srvr_parse(const char *str, ssize_t *str_len,
429 int *non_proxy,
430 MAI_HOSTADDR_STR *smtp_client_addr,
431 MAI_SERVPORT_STR *smtp_client_port,
432 MAI_HOSTADDR_STR *smtp_server_addr,
433 MAI_SERVPORT_STR *smtp_server_port)
434 {
435 const char *err;
436
437 if (proto_info == 0)
438 proto_info = inet_proto_info();
439
440 *non_proxy = 0;
441
442 /*
443 * XXX We don't accept connections with the "UNKNOWN" protocol type,
444 * because those would sidestep address-based access control mechanisms.
445 */
446
447 /*
448 * Try version 1 protocol.
449 */
450 if (strncmp(str, "PROXY ", 6) == 0) {
451 char *saved_str = mystrndup(str, *str_len);
452 char *cp = saved_str;
453 char *beyond_header = split_at(saved_str, '\n');
454 int addr_family;
455
456 #define NEXT_TOKEN mystrtok(&cp, " \r")
457 if (beyond_header == 0)
458 err = "missing protocol header terminator";
459 else if (haproxy_srvr_parse_lit(NEXT_TOKEN, "PROXY", (char *) 0) < 0)
460 err = "bad or missing protocol header";
461 else if (haproxy_srvr_parse_proto(NEXT_TOKEN, &addr_family) < 0)
462 err = "bad or missing protocol type";
463 else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_client_addr,
464 addr_family) < 0)
465 err = "bad or missing client address";
466 else if (haproxy_srvr_parse_addr(NEXT_TOKEN, smtp_server_addr,
467 addr_family) < 0)
468 err = "bad or missing server address";
469 else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_client_port) < 0)
470 err = "bad or missing client port";
471 else if (haproxy_srvr_parse_port(NEXT_TOKEN, smtp_server_port) < 0)
472 err = "bad or missing server port";
473 else {
474 err = 0;
475 *str_len = beyond_header - saved_str;
476 }
477 myfree(saved_str);
478
479 return (err);
480 }
481
482 /*
483 * Try version 2 protocol.
484 */
485 else {
486 return (haproxy_srvr_parse_v2_hdr(str, str_len, non_proxy,
487 smtp_client_addr, smtp_client_port,
488 smtp_server_addr, smtp_server_port));
489 }
490 }
491
492 /* haproxy_srvr_receive - receive and parse haproxy protocol handshake */
493
haproxy_srvr_receive(int fd,int * non_proxy,MAI_HOSTADDR_STR * smtp_client_addr,MAI_SERVPORT_STR * smtp_client_port,MAI_HOSTADDR_STR * smtp_server_addr,MAI_SERVPORT_STR * smtp_server_port)494 int haproxy_srvr_receive(int fd, int *non_proxy,
495 MAI_HOSTADDR_STR *smtp_client_addr,
496 MAI_SERVPORT_STR *smtp_client_port,
497 MAI_HOSTADDR_STR *smtp_server_addr,
498 MAI_SERVPORT_STR *smtp_server_port)
499 {
500 const char *err;
501 VSTRING *escape_buf;
502 char read_buf[HAPROXY_HEADER_MAX_LEN + 1];
503 ssize_t read_len;
504
505 /*
506 * We must not read(2) past the end of the HaProxy handshake. The v2
507 * protocol assumes that the handshake will never be fragmented,
508 * therefore we peek, parse the entire input, then read(2) only the
509 * number of bytes parsed.
510 */
511 if ((read_len = recv(fd, read_buf, sizeof(read_buf) - 1, MSG_PEEK)) <= 0) {
512 msg_warn("haproxy read: EOF");
513 return (-1);
514 }
515
516 /*
517 * Parse the haproxy handshake, and determine the handshake length.
518 */
519 read_buf[read_len] = 0;
520
521 if ((err = haproxy_srvr_parse(read_buf, &read_len, non_proxy,
522 smtp_client_addr, smtp_client_port,
523 smtp_server_addr, smtp_server_port)) != 0) {
524 escape_buf = vstring_alloc(read_len * 2);
525 escape(escape_buf, read_buf, read_len);
526 msg_warn("haproxy read: %s: %s", err, vstring_str(escape_buf));
527 vstring_free(escape_buf);
528 return (-1);
529 }
530
531 /*
532 * Try to pop the haproxy handshake off the input queue.
533 */
534 if (recv(fd, read_buf, read_len, 0) != read_len) {
535 msg_warn("haproxy read: %m");
536 return (-1);
537 }
538 return (0);
539 }
540
541 /*
542 * Test program.
543 */
544 #ifdef TEST
545
546 /*
547 * Test cases with inputs and expected outputs. A request may contain
548 * trailing garbage, and it may be too short. A v1 request may also contain
549 * malformed address or port information.
550 */
551 typedef struct TEST_CASE {
552 const char *haproxy_request; /* v1 or v2 request including thrash */
553 ssize_t haproxy_req_len; /* request length including thrash */
554 ssize_t exp_req_len; /* parsed request length */
555 int exp_non_proxy; /* request is not proxied */
556 const char *exp_return; /* expected error string */
557 const char *exp_client_addr; /* expected client address string */
558 const char *exp_server_addr; /* expected client port string */
559 const char *exp_client_port; /* expected client address string */
560 const char *exp_server_port; /* expected server port string */
561 } TEST_CASE;
562 static TEST_CASE v1_test_cases[] = {
563 /* IPv6. */
564 {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
565 {"PROXY TCP6 FC:00:00:00:1:2:3:4 FC:00:00:00:4:3:2:1 123 321\n", 0, 0, 0, 0, "fc::1:2:3:4", "fc::4:3:2:1", "123", "321"},
566 {"PROXY TCP6 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
567 {"PROXY TCP6 fc:00:00:00:1:2:3:4 4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
568 /* IPv4 in IPv6. */
569 {"PROXY TCP6 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
570 {"PROXY TCP6 ::FFFF:1.2.3.4 ::FFFF:4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
571 {"PROXY TCP4 ::ffff:1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing client address"},
572 {"PROXY TCP4 1.2.3.4 ::ffff:4.3.2.1 123 321\n", 0, 0, 0, "bad or missing server address"},
573 /* IPv4. */
574 {"PROXY TCP4 1.2.3.4 4.3.2.1 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
575 {"PROXY TCP4 01.02.03.04 04.03.02.01 123 321\n", 0, 0, 0, 0, "1.2.3.4", "4.3.2.1", "123", "321"},
576 {"PROXY TCP4 1.2.3.4 4.3.2.1 123456 321\n", 0, 0, 0, "bad or missing client port"},
577 {"PROXY TCP4 1.2.3.4 4.3.2.1 123 654321\n", 0, 0, 0, "bad or missing server port"},
578 {"PROXY TCP4 1.2.3.4 4.3.2.1 0123 321\n", 0, 0, 0, "bad or missing client port"},
579 {"PROXY TCP4 1.2.3.4 4.3.2.1 123 0321\n", 0, 0, 0, "bad or missing server port"},
580 /* Missing fields. */
581 {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1 123\n", 0, 0, 0, "bad or missing server port"},
582 {"PROXY TCP6 fc:00:00:00:1:2:3:4 fc:00:00:00:4:3:2:1\n", 0, 0, 0, "bad or missing client port"},
583 {"PROXY TCP6 fc:00:00:00:1:2:3:4\n", 0, 0, 0, "bad or missing server address"},
584 {"PROXY TCP6\n", 0, 0, 0, "bad or missing client address"},
585 {"PROXY TCP4 1.2.3.4 4.3.2.1 123\n", 0, 0, 0, "bad or missing server port"},
586 {"PROXY TCP4 1.2.3.4 4.3.2.1\n", 0, 0, 0, "bad or missing client port"},
587 {"PROXY TCP4 1.2.3.4\n", 0, 0, 0, "bad or missing server address"},
588 {"PROXY TCP4\n", 0, 0, 0, "bad or missing client address"},
589 /* Other. */
590 {"PROXY BLAH\n", 0, 0, 0, "bad or missing protocol type"},
591 {"PROXY\n", 0, 0, 0, "short protocol header"},
592 {"BLAH\n", 0, 0, 0, "short protocol header"},
593 {"\n", 0, 0, 0, "short protocol header"},
594 {"", 0, 0, 0, "short protocol header"},
595 0,
596 };
597
598 static struct proxy_hdr_v2 v2_local_request = {
599 PP2_SIGNATURE, PP2_VERSION | PP2_CMD_LOCAL,
600 };
601 static TEST_CASE v2_non_proxy_test = {
602 (char *) &v2_local_request, PP2_HEADER_LEN, PP2_HEADER_LEN, 1,
603 };
604
605 #define STR(x) vstring_str(x)
606 #define LEN(x) VSTRING_LEN(x)
607
608 /* evaluate_test_case - evaluate one test case */
609
evaluate_test_case(const char * test_label,const TEST_CASE * test_case)610 static int evaluate_test_case(const char *test_label,
611 const TEST_CASE *test_case)
612 {
613 /* Actual results. */
614 const char *act_return;
615 ssize_t act_req_len;
616 int act_non_proxy;
617 MAI_HOSTADDR_STR act_smtp_client_addr;
618 MAI_HOSTADDR_STR act_smtp_server_addr;
619 MAI_SERVPORT_STR act_smtp_client_port;
620 MAI_SERVPORT_STR act_smtp_server_port;
621 int test_failed;
622
623 if (msg_verbose)
624 msg_info("test case=%s exp_client_addr=%s exp_server_addr=%s "
625 "exp_client_port=%s exp_server_port=%s",
626 test_label, STR_OR_NULL(test_case->exp_client_addr),
627 STR_OR_NULL(test_case->exp_server_addr),
628 STR_OR_NULL(test_case->exp_client_port),
629 STR_OR_NULL(test_case->exp_server_port));
630
631 /*
632 * Start the test.
633 */
634 test_failed = 0;
635 act_req_len = test_case->haproxy_req_len;
636 act_return =
637 haproxy_srvr_parse(test_case->haproxy_request, &act_req_len,
638 &act_non_proxy,
639 &act_smtp_client_addr, &act_smtp_client_port,
640 &act_smtp_server_addr, &act_smtp_server_port);
641 if (act_return != test_case->exp_return) {
642 msg_warn("test case %s return expected=%s actual=%s",
643 test_label, STR_OR_NULL(test_case->exp_return),
644 STR_OR_NULL(act_return));
645 test_failed = 1;
646 return (test_failed);
647 }
648 if (act_req_len != test_case->exp_req_len) {
649 msg_warn("test case %s str_len expected=%ld actual=%ld",
650 test_label,
651 (long) test_case->exp_req_len, (long) act_req_len);
652 test_failed = 1;
653 return (test_failed);
654 }
655 if (act_non_proxy != test_case->exp_non_proxy) {
656 msg_warn("test case %s non_proxy expected=%d actual=%d",
657 test_label,
658 test_case->exp_non_proxy, act_non_proxy);
659 test_failed = 1;
660 return (test_failed);
661 }
662 if (test_case->exp_non_proxy || test_case->exp_return != 0)
663 /* No expected address/port results. */
664 return (test_failed);
665
666 /*
667 * Compare address/port results against expected results.
668 */
669 if (strcmp(test_case->exp_client_addr, act_smtp_client_addr.buf)) {
670 msg_warn("test case %s client_addr expected=%s actual=%s",
671 test_label,
672 test_case->exp_client_addr, act_smtp_client_addr.buf);
673 test_failed = 1;
674 }
675 if (strcmp(test_case->exp_server_addr, act_smtp_server_addr.buf)) {
676 msg_warn("test case %s server_addr expected=%s actual=%s",
677 test_label,
678 test_case->exp_server_addr, act_smtp_server_addr.buf);
679 test_failed = 1;
680 }
681 if (strcmp(test_case->exp_client_port, act_smtp_client_port.buf)) {
682 msg_warn("test case %s client_port expected=%s actual=%s",
683 test_label,
684 test_case->exp_client_port, act_smtp_client_port.buf);
685 test_failed = 1;
686 }
687 if (strcmp(test_case->exp_server_port, act_smtp_server_port.buf)) {
688 msg_warn("test case %s server_port expected=%s actual=%s",
689 test_label,
690 test_case->exp_server_port, act_smtp_server_port.buf);
691 test_failed = 1;
692 }
693 return (test_failed);
694 }
695
696 /* convert_v1_proxy_req_to_v2 - convert well-formed v1 proxy request to v2 */
697
convert_v1_proxy_req_to_v2(VSTRING * buf,const char * req,ssize_t req_len)698 static void convert_v1_proxy_req_to_v2(VSTRING *buf, const char *req,
699 ssize_t req_len)
700 {
701 const char myname[] = "convert_v1_proxy_req_to_v2";
702 const char *err;
703 int non_proxy;
704 MAI_HOSTADDR_STR smtp_client_addr;
705 MAI_SERVPORT_STR smtp_client_port;
706 MAI_HOSTADDR_STR smtp_server_addr;
707 MAI_SERVPORT_STR smtp_server_port;
708 struct proxy_hdr_v2 *hdr_v2;
709 struct addrinfo *src_res;
710 struct addrinfo *dst_res;
711
712 /*
713 * Allocate buffer space for the largest possible protocol header, so we
714 * don't have to worry about hidden realloc() calls.
715 */
716 VSTRING_RESET(buf);
717 VSTRING_SPACE(buf, sizeof(struct proxy_hdr_v2));
718 hdr_v2 = (struct proxy_hdr_v2 *) STR(buf);
719
720 /*
721 * Fill in the header,
722 */
723 memcpy(hdr_v2->sig, PP2_SIGNATURE, PP2_SIGNATURE_LEN);
724 hdr_v2->ver_cmd = PP2_VERSION | PP2_CMD_PROXY;
725 if ((err = haproxy_srvr_parse(req, &req_len, &non_proxy, &smtp_client_addr,
726 &smtp_client_port, &smtp_server_addr,
727 &smtp_server_port)) != 0 || non_proxy)
728 msg_fatal("%s: malformed or non-proxy request: %s",
729 myname, req);
730
731 if (hostaddr_to_sockaddr(smtp_client_addr.buf, smtp_client_port.buf, 0,
732 &src_res) != 0)
733 msg_fatal("%s: unable to convert source address %s port %s",
734 myname, smtp_client_addr.buf, smtp_client_port.buf);
735 if (hostaddr_to_sockaddr(smtp_server_addr.buf, smtp_server_port.buf, 0,
736 &dst_res) != 0)
737 msg_fatal("%s: unable to convert destination address %s port %s",
738 myname, smtp_server_addr.buf, smtp_server_port.buf);
739 if (src_res->ai_family != dst_res->ai_family)
740 msg_fatal("%s: mixed source/destination address families", myname);
741 #ifdef AF_INET6
742 if (src_res->ai_family == PF_INET6) {
743 hdr_v2->fam = PP2_FAM_INET6 | PP2_TRANS_STREAM;
744 hdr_v2->len = htons(PP2_ADDR_LEN_INET6);
745 memcpy(hdr_v2->addr.ip6.src_addr,
746 &SOCK_ADDR_IN6_ADDR(src_res->ai_addr),
747 sizeof(hdr_v2->addr.ip6.src_addr));
748 hdr_v2->addr.ip6.src_port = SOCK_ADDR_IN6_PORT(src_res->ai_addr);
749 memcpy(hdr_v2->addr.ip6.dst_addr,
750 &SOCK_ADDR_IN6_ADDR(dst_res->ai_addr),
751 sizeof(hdr_v2->addr.ip6.dst_addr));
752 hdr_v2->addr.ip6.dst_port = SOCK_ADDR_IN6_PORT(dst_res->ai_addr);
753 } else
754 #endif
755 if (src_res->ai_family == PF_INET) {
756 hdr_v2->fam = PP2_FAM_INET | PP2_TRANS_STREAM;
757 hdr_v2->len = htons(PP2_ADDR_LEN_INET);
758 hdr_v2->addr.ip4.src_addr = SOCK_ADDR_IN_ADDR(src_res->ai_addr).s_addr;
759 hdr_v2->addr.ip4.src_port = SOCK_ADDR_IN_PORT(src_res->ai_addr);
760 hdr_v2->addr.ip4.dst_addr = SOCK_ADDR_IN_ADDR(dst_res->ai_addr).s_addr;
761 hdr_v2->addr.ip4.dst_port = SOCK_ADDR_IN_PORT(dst_res->ai_addr);
762 } else {
763 msg_panic("unknown address family 0x%x", src_res->ai_family);
764 }
765 vstring_set_payload_size(buf, PP2_SIGNATURE_LEN + ntohs(hdr_v2->len));
766 freeaddrinfo(src_res);
767 freeaddrinfo(dst_res);
768 }
769
main(int argc,char ** argv)770 int main(int argc, char **argv)
771 {
772 VSTRING *test_label;
773 TEST_CASE *v1_test_case;
774 TEST_CASE v2_test_case;
775 TEST_CASE mutated_test_case;
776 VSTRING *v2_request_buf;
777 VSTRING *mutated_request_buf;
778
779 /* Findings. */
780 int tests_failed = 0;
781 int test_failed;
782
783 test_label = vstring_alloc(100);
784 v2_request_buf = vstring_alloc(100);
785 mutated_request_buf = vstring_alloc(100);
786
787 for (tests_failed = 0, v1_test_case = v1_test_cases;
788 v1_test_case->haproxy_request != 0;
789 tests_failed += test_failed, v1_test_case++) {
790
791 /*
792 * Fill in missing string length info in v1 test data.
793 */
794 if (v1_test_case->haproxy_req_len == 0)
795 v1_test_case->haproxy_req_len =
796 strlen(v1_test_case->haproxy_request);
797 if (v1_test_case->exp_req_len == 0)
798 v1_test_case->exp_req_len = v1_test_case->haproxy_req_len;
799
800 /*
801 * Evaluate each v1 test case.
802 */
803 vstring_sprintf(test_label, "%d", (int) (v1_test_case - v1_test_cases));
804 test_failed = evaluate_test_case(STR(test_label), v1_test_case);
805
806 /*
807 * If the v1 test input is malformed, skip the mutation tests.
808 */
809 if (v1_test_case->exp_return != 0)
810 continue;
811
812 /*
813 * Mutation test: a well-formed v1 test case should still pass after
814 * appending a byte, and should return the actual parsed header
815 * length. The test uses the implicit VSTRING null safety byte.
816 */
817 vstring_sprintf(test_label, "%d (one byte appended)",
818 (int) (v1_test_case - v1_test_cases));
819 mutated_test_case = *v1_test_case;
820 mutated_test_case.haproxy_req_len += 1;
821 /* reuse v1_test_case->exp_req_len */
822 test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
823
824 /*
825 * Mutation test: a well-formed v1 test case should fail after
826 * stripping the terminator.
827 */
828 vstring_sprintf(test_label, "%d (last byte stripped)",
829 (int) (v1_test_case - v1_test_cases));
830 mutated_test_case = *v1_test_case;
831 mutated_test_case.exp_return = "missing protocol header terminator";
832 mutated_test_case.haproxy_req_len -= 1;
833 mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
834 test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
835
836 /*
837 * A 'well-formed' v1 test case should pass after conversion to v2.
838 */
839 vstring_sprintf(test_label, "%d (converted to v2)",
840 (int) (v1_test_case - v1_test_cases));
841 v2_test_case = *v1_test_case;
842 convert_v1_proxy_req_to_v2(v2_request_buf,
843 v1_test_case->haproxy_request,
844 v1_test_case->haproxy_req_len);
845 v2_test_case.haproxy_request = STR(v2_request_buf);
846 v2_test_case.haproxy_req_len = PP2_HEADER_LEN
847 + ntohs(((struct proxy_hdr_v2 *) STR(v2_request_buf))->len);
848 v2_test_case.exp_req_len = v2_test_case.haproxy_req_len;
849 test_failed += evaluate_test_case(STR(test_label), &v2_test_case);
850
851 /*
852 * Mutation test: a well-formed v2 test case should still pass after
853 * appending a byte, and should return the actual parsed header
854 * length. The test uses the implicit VSTRING null safety byte.
855 */
856 vstring_sprintf(test_label, "%d (converted to v2, one byte appended)",
857 (int) (v1_test_case - v1_test_cases));
858 mutated_test_case = v2_test_case;
859 mutated_test_case.haproxy_req_len += 1;
860 /* reuse v2_test_case->exp_req_len */
861 test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
862
863 /*
864 * Mutation test: a well-formed v2 test case should fail after
865 * stripping one byte
866 */
867 vstring_sprintf(test_label, "%d (converted to v2, last byte stripped)",
868 (int) (v1_test_case - v1_test_cases));
869 mutated_test_case = v2_test_case;
870 mutated_test_case.haproxy_req_len -= 1;
871 mutated_test_case.exp_req_len = mutated_test_case.haproxy_req_len;
872 mutated_test_case.exp_return = "short version 2 protocol header";
873 test_failed += evaluate_test_case(STR(test_label), &mutated_test_case);
874 }
875
876 /*
877 * Additional V2-only tests.
878 */
879 test_failed +=
880 evaluate_test_case("v2 non-proxy request", &v2_non_proxy_test);
881
882 /*
883 * Clean up.
884 */
885 vstring_free(v2_request_buf);
886 vstring_free(mutated_request_buf);
887 vstring_free(test_label);
888 if (tests_failed)
889 msg_info("tests failed: %d", tests_failed);
890 exit(tests_failed != 0);
891 }
892
893 #endif
894