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 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 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 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 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 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