1 /* $OpenBSD: sync.c,v 1.14 2021/12/15 17:06:01 tb Exp $ */
2
3 /*
4 * Copyright (c) 2006, 2007 Reyk Floeter <reyk@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <sys/socket.h>
20 #include <sys/uio.h>
21 #include <sys/ioctl.h>
22 #include <sys/queue.h>
23
24 #include <net/if.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
27
28 #include <errno.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 #include <sha1.h>
34 #include <syslog.h>
35 #include <stdint.h>
36
37 #include <netdb.h>
38
39 #include <openssl/hmac.h>
40
41 #include "sdl.h"
42 #include "grey.h"
43 #include "sync.h"
44
45 extern struct syslog_data sdata;
46 extern int debug;
47 extern FILE *grey;
48 extern int greylist;
49
50 u_int32_t sync_counter;
51 int syncfd;
52 int sendmcast;
53 struct sockaddr_in sync_in;
54 struct sockaddr_in sync_out;
55 static char *sync_key;
56
57 struct sync_host {
58 LIST_ENTRY(sync_host) h_entry;
59
60 char *h_name;
61 struct sockaddr_in sh_addr;
62 };
63 LIST_HEAD(synchosts, sync_host) sync_hosts = LIST_HEAD_INITIALIZER(sync_hosts);
64
65 void sync_send(struct iovec *, int);
66 void sync_addr(time_t, time_t, char *, u_int16_t);
67
68 int
sync_addhost(const char * name,u_short port)69 sync_addhost(const char *name, u_short port)
70 {
71 struct addrinfo hints, *res, *res0;
72 struct sync_host *shost;
73 struct sockaddr_in *addr = NULL;
74
75 memset(&hints, 0, sizeof(hints));
76 hints.ai_family = PF_UNSPEC;
77 hints.ai_socktype = SOCK_STREAM;
78 if (getaddrinfo(name, NULL, &hints, &res0) != 0)
79 return (EINVAL);
80 for (res = res0; res != NULL; res = res->ai_next) {
81 if (addr == NULL && res->ai_family == AF_INET) {
82 addr = (struct sockaddr_in *)res->ai_addr;
83 break;
84 }
85 }
86 if (addr == NULL) {
87 freeaddrinfo(res0);
88 return (EINVAL);
89 }
90 if ((shost = (struct sync_host *)
91 calloc(1, sizeof(struct sync_host))) == NULL) {
92 freeaddrinfo(res0);
93 return (ENOMEM);
94 }
95 if ((shost->h_name = strdup(name)) == NULL) {
96 free(shost);
97 freeaddrinfo(res0);
98 return (ENOMEM);
99 }
100
101 shost->sh_addr.sin_family = AF_INET;
102 shost->sh_addr.sin_port = htons(port);
103 shost->sh_addr.sin_addr.s_addr = addr->sin_addr.s_addr;
104 freeaddrinfo(res0);
105
106 LIST_INSERT_HEAD(&sync_hosts, shost, h_entry);
107
108 if (debug)
109 fprintf(stderr, "added spam sync host %s "
110 "(address %s, port %d)\n", shost->h_name,
111 inet_ntoa(shost->sh_addr.sin_addr), port);
112
113 return (0);
114 }
115
116 int
sync_init(const char * iface,const char * baddr,u_short port)117 sync_init(const char *iface, const char *baddr, u_short port)
118 {
119 int one = 1;
120 u_int8_t ttl;
121 struct ifreq ifr;
122 struct ip_mreq mreq;
123 struct sockaddr_in *addr;
124 char ifnam[IFNAMSIZ], *ttlstr;
125 const char *errstr;
126 struct in_addr ina;
127
128 if (iface != NULL)
129 sendmcast++;
130
131 memset(&ina, 0, sizeof(ina));
132 if (baddr != NULL) {
133 if (inet_pton(AF_INET, baddr, &ina) != 1) {
134 ina.s_addr = htonl(INADDR_ANY);
135 if (iface == NULL)
136 iface = baddr;
137 else if (iface != NULL && strcmp(baddr, iface) != 0) {
138 fprintf(stderr, "multicast interface does "
139 "not match");
140 return (-1);
141 }
142 }
143 }
144
145 sync_key = SHA1File(SPAM_SYNC_KEY, NULL);
146 if (sync_key == NULL) {
147 if (errno != ENOENT) {
148 fprintf(stderr, "failed to open sync key: %s\n",
149 strerror(errno));
150 return (-1);
151 }
152 /* Use empty key by default */
153 sync_key = "";
154 }
155
156 syncfd = socket(AF_INET, SOCK_DGRAM, 0);
157 if (syncfd == -1)
158 return (-1);
159
160 if (setsockopt(syncfd, SOL_SOCKET, SO_REUSEADDR, &one,
161 sizeof(one)) == -1)
162 goto fail;
163
164 memset(&sync_out, 0, sizeof(sync_out));
165 sync_out.sin_family = AF_INET;
166 sync_out.sin_len = sizeof(sync_out);
167 sync_out.sin_addr.s_addr = ina.s_addr;
168 if (baddr == NULL && iface == NULL)
169 sync_out.sin_port = 0;
170 else
171 sync_out.sin_port = htons(port);
172
173 if (bind(syncfd, (struct sockaddr *)&sync_out, sizeof(sync_out)) == -1)
174 goto fail;
175
176 /* Don't use multicast messages */
177 if (iface == NULL)
178 return (syncfd);
179
180 strlcpy(ifnam, iface, sizeof(ifnam));
181 ttl = SPAM_SYNC_MCASTTTL;
182 if ((ttlstr = strchr(ifnam, ':')) != NULL) {
183 *ttlstr++ = '\0';
184 ttl = (u_int8_t)strtonum(ttlstr, 1, UINT8_MAX, &errstr);
185 if (errstr) {
186 fprintf(stderr, "invalid multicast ttl %s: %s",
187 ttlstr, errstr);
188 goto fail;
189 }
190 }
191
192 memset(&ifr, 0, sizeof(ifr));
193 strlcpy(ifr.ifr_name, ifnam, sizeof(ifr.ifr_name));
194 if (ioctl(syncfd, SIOCGIFADDR, &ifr) == -1)
195 goto fail;
196
197 memset(&sync_in, 0, sizeof(sync_in));
198 addr = (struct sockaddr_in *)&ifr.ifr_addr;
199 sync_in.sin_family = AF_INET;
200 sync_in.sin_len = sizeof(sync_in);
201 sync_in.sin_addr.s_addr = addr->sin_addr.s_addr;
202 sync_in.sin_port = htons(port);
203
204 memset(&mreq, 0, sizeof(mreq));
205 sync_out.sin_addr.s_addr = inet_addr(SPAM_SYNC_MCASTADDR);
206 mreq.imr_multiaddr.s_addr = inet_addr(SPAM_SYNC_MCASTADDR);
207 mreq.imr_interface.s_addr = sync_in.sin_addr.s_addr;
208
209 if (setsockopt(syncfd, IPPROTO_IP,
210 IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) {
211 fprintf(stderr, "failed to add multicast membership to %s: %s",
212 SPAM_SYNC_MCASTADDR, strerror(errno));
213 goto fail;
214 }
215 if (setsockopt(syncfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl,
216 sizeof(ttl)) == -1) {
217 fprintf(stderr, "failed to set multicast ttl to "
218 "%u: %s\n", ttl, strerror(errno));
219 setsockopt(syncfd, IPPROTO_IP,
220 IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
221 goto fail;
222 }
223
224 if (debug)
225 printf("using multicast spam sync %smode "
226 "(ttl %u, group %s, port %d)\n",
227 sendmcast ? "" : "receive ",
228 ttl, inet_ntoa(sync_out.sin_addr), port);
229
230 return (syncfd);
231
232 fail:
233 close(syncfd);
234 return (-1);
235 }
236
237 void
sync_recv(void)238 sync_recv(void)
239 {
240 struct spam_synchdr *hdr;
241 struct sockaddr_in addr;
242 struct spam_synctlv_hdr *tlv;
243 struct spam_synctlv_grey *sg;
244 struct spam_synctlv_addr *sd;
245 u_int8_t buf[SPAM_SYNC_MAXSIZE];
246 u_int8_t hmac[2][SPAM_SYNC_HMAC_LEN];
247 struct in_addr ip;
248 char *from, *to, *helo;
249 u_int8_t *p;
250 socklen_t addr_len;
251 ssize_t len;
252 u_int hmac_len;
253 u_int32_t expire;
254
255 memset(&addr, 0, sizeof(addr));
256 memset(buf, 0, sizeof(buf));
257
258 addr_len = sizeof(addr);
259 if ((len = recvfrom(syncfd, buf, sizeof(buf), 0,
260 (struct sockaddr *)&addr, &addr_len)) < 1)
261 return;
262 if (addr.sin_addr.s_addr != htonl(INADDR_ANY) &&
263 bcmp(&sync_in.sin_addr, &addr.sin_addr,
264 sizeof(addr.sin_addr)) == 0)
265 return;
266
267 /* Ignore invalid or truncated packets */
268 hdr = (struct spam_synchdr *)buf;
269 if (len < sizeof(struct spam_synchdr) ||
270 hdr->sh_version != SPAM_SYNC_VERSION ||
271 hdr->sh_af != AF_INET ||
272 len < ntohs(hdr->sh_length))
273 goto trunc;
274 len = ntohs(hdr->sh_length);
275
276 /* Compute and validate HMAC */
277 memcpy(hmac[0], hdr->sh_hmac, SPAM_SYNC_HMAC_LEN);
278 explicit_bzero(hdr->sh_hmac, SPAM_SYNC_HMAC_LEN);
279 HMAC(EVP_sha1(), sync_key, strlen(sync_key), buf, len,
280 hmac[1], &hmac_len);
281 if (bcmp(hmac[0], hmac[1], SPAM_SYNC_HMAC_LEN) != 0)
282 goto trunc;
283
284 if (debug)
285 fprintf(stderr,
286 "%s(sync): received packet of %d bytes\n",
287 inet_ntoa(addr.sin_addr), (int)len);
288
289 p = (u_int8_t *)(hdr + 1);
290 while (len) {
291 tlv = (struct spam_synctlv_hdr *)p;
292
293 if (len < sizeof(struct spam_synctlv_hdr) ||
294 len < ntohs(tlv->st_length))
295 goto trunc;
296
297 switch (ntohs(tlv->st_type)) {
298 case SPAM_SYNC_GREY:
299 sg = (struct spam_synctlv_grey *)tlv;
300 if ((sizeof(*sg) +
301 ntohs(sg->sg_from_length) +
302 ntohs(sg->sg_to_length) +
303 ntohs(sg->sg_helo_length)) >
304 ntohs(tlv->st_length))
305 goto trunc;
306
307 ip.s_addr = sg->sg_ip;
308 from = (char *)(sg + 1);
309 to = from + ntohs(sg->sg_from_length);
310 helo = to + ntohs(sg->sg_to_length);
311 if (debug) {
312 fprintf(stderr, "%s(sync): "
313 "received grey entry ",
314 inet_ntoa(addr.sin_addr));
315 fprintf(stderr, "helo %s ip %s "
316 "from %s to %s\n",
317 helo, inet_ntoa(ip), from, to);
318 }
319 if (greylist) {
320 /* send this info to the greylister */
321 fprintf(grey,
322 "SYNC\nHE:%s\nIP:%s\nFR:%s\nTO:%s\n",
323 helo, inet_ntoa(ip), from, to);
324 fflush(grey);
325 }
326 break;
327 case SPAM_SYNC_WHITE:
328 sd = (struct spam_synctlv_addr *)tlv;
329 if (sizeof(*sd) != ntohs(tlv->st_length))
330 goto trunc;
331
332 ip.s_addr = sd->sd_ip;
333 expire = ntohl(sd->sd_expire);
334 if (debug) {
335 fprintf(stderr, "%s(sync): "
336 "received white entry ",
337 inet_ntoa(addr.sin_addr));
338 fprintf(stderr, "ip %s ", inet_ntoa(ip));
339 }
340 if (greylist) {
341 /* send this info to the greylister */
342 fprintf(grey, "WHITE:%s:", inet_ntoa(ip));
343 fprintf(grey, "%s:%u\n",
344 inet_ntoa(addr.sin_addr), expire);
345 fflush(grey);
346 }
347 break;
348 case SPAM_SYNC_TRAPPED:
349 sd = (struct spam_synctlv_addr *)tlv;
350 if (sizeof(*sd) != ntohs(tlv->st_length))
351 goto trunc;
352
353 ip.s_addr = sd->sd_ip;
354 expire = ntohl(sd->sd_expire);
355 if (debug) {
356 fprintf(stderr, "%s(sync): "
357 "received trapped entry ",
358 inet_ntoa(addr.sin_addr));
359 fprintf(stderr, "ip %s ", inet_ntoa(ip));
360 }
361 if (greylist) {
362 /* send this info to the greylister */
363 fprintf(grey, "TRAP:%s:", inet_ntoa(ip));
364 fprintf(grey, "%s:%u\n",
365 inet_ntoa(addr.sin_addr), expire);
366 fflush(grey);
367 }
368 break;
369 case SPAM_SYNC_END:
370 goto done;
371 default:
372 printf("invalid type: %d\n", ntohs(tlv->st_type));
373 goto trunc;
374 }
375 len -= ntohs(tlv->st_length);
376 p = ((u_int8_t *)tlv) + ntohs(tlv->st_length);
377 }
378
379 done:
380 return;
381
382 trunc:
383 if (debug)
384 fprintf(stderr, "%s(sync): truncated or invalid packet\n",
385 inet_ntoa(addr.sin_addr));
386 }
387
388 void
sync_send(struct iovec * iov,int iovlen)389 sync_send(struct iovec *iov, int iovlen)
390 {
391 struct sync_host *shost;
392 struct msghdr msg;
393
394 /* setup buffer */
395 memset(&msg, 0, sizeof(msg));
396 msg.msg_iov = iov;
397 msg.msg_iovlen = iovlen;
398
399 if (sendmcast) {
400 if (debug)
401 fprintf(stderr, "sending multicast sync message\n");
402 msg.msg_name = &sync_out;
403 msg.msg_namelen = sizeof(sync_out);
404 sendmsg(syncfd, &msg, 0);
405 }
406
407 LIST_FOREACH(shost, &sync_hosts, h_entry) {
408 if (debug)
409 fprintf(stderr, "sending sync message to %s (%s)\n",
410 shost->h_name, inet_ntoa(shost->sh_addr.sin_addr));
411 msg.msg_name = &shost->sh_addr;
412 msg.msg_namelen = sizeof(shost->sh_addr);
413 sendmsg(syncfd, &msg, 0);
414 }
415 }
416
417 void
sync_update(time_t now,char * helo,char * ip,char * from,char * to)418 sync_update(time_t now, char *helo, char *ip, char *from, char *to)
419 {
420 struct iovec iov[7];
421 struct spam_synchdr hdr;
422 struct spam_synctlv_grey sg;
423 struct spam_synctlv_hdr end;
424 u_int16_t sglen, fromlen, tolen, helolen, padlen;
425 char pad[SPAM_ALIGNBYTES];
426 int i = 0;
427 HMAC_CTX *ctx;
428 u_int hmac_len;
429
430 if (debug)
431 fprintf(stderr,
432 "sync grey update helo %s ip %s from %s to %s\n",
433 helo, ip, from, to);
434
435 memset(&hdr, 0, sizeof(hdr));
436 memset(&sg, 0, sizeof(sg));
437 memset(&pad, 0, sizeof(pad));
438
439 fromlen = strlen(from) + 1;
440 tolen = strlen(to) + 1;
441 helolen = strlen(helo) + 1;
442
443 if ((ctx = HMAC_CTX_new()) == NULL)
444 goto bad;
445 if (!HMAC_Init_ex(ctx, sync_key, strlen(sync_key), EVP_sha1(), NULL))
446 goto bad;
447
448 sglen = sizeof(sg) + fromlen + tolen + helolen;
449 padlen = SPAM_ALIGN(sglen) - sglen;
450
451 /* Add SPAM sync packet header */
452 hdr.sh_version = SPAM_SYNC_VERSION;
453 hdr.sh_af = AF_INET;
454 hdr.sh_counter = htonl(sync_counter++);
455 hdr.sh_length = htons(sizeof(hdr) + sglen + padlen + sizeof(end));
456 iov[i].iov_base = &hdr;
457 iov[i].iov_len = sizeof(hdr);
458 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
459 goto bad;
460 i++;
461
462 /* Add single SPAM sync greylisting entry */
463 sg.sg_type = htons(SPAM_SYNC_GREY);
464 sg.sg_length = htons(sglen + padlen);
465 sg.sg_timestamp = htonl(now);
466 sg.sg_ip = inet_addr(ip);
467 sg.sg_from_length = htons(fromlen);
468 sg.sg_to_length = htons(tolen);
469 sg.sg_helo_length = htons(helolen);
470 iov[i].iov_base = &sg;
471 iov[i].iov_len = sizeof(sg);
472 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
473 goto bad;
474 i++;
475
476 iov[i].iov_base = from;
477 iov[i].iov_len = fromlen;
478 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
479 goto bad;
480 i++;
481
482 iov[i].iov_base = to;
483 iov[i].iov_len = tolen;
484 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
485 goto bad;
486 i++;
487
488 iov[i].iov_base = helo;
489 iov[i].iov_len = helolen;
490 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
491 goto bad;
492 i++;
493
494 iov[i].iov_base = pad;
495 iov[i].iov_len = padlen;
496 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
497 goto bad;
498 i++;
499
500 /* Add end marker */
501 end.st_type = htons(SPAM_SYNC_END);
502 end.st_length = htons(sizeof(end));
503 iov[i].iov_base = &end;
504 iov[i].iov_len = sizeof(end);
505 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
506 goto bad;
507 i++;
508
509 if (!HMAC_Final(ctx, hdr.sh_hmac, &hmac_len))
510 goto bad;
511
512 /* Send message to the target hosts */
513 sync_send(iov, i);
514
515 bad:
516 HMAC_CTX_free(ctx);
517 }
518
519 void
sync_addr(time_t now,time_t expire,char * ip,u_int16_t type)520 sync_addr(time_t now, time_t expire, char *ip, u_int16_t type)
521 {
522 struct iovec iov[3];
523 struct spam_synchdr hdr;
524 struct spam_synctlv_addr sd;
525 struct spam_synctlv_hdr end;
526 int i = 0;
527 HMAC_CTX *ctx;
528 u_int hmac_len;
529
530 if (debug)
531 fprintf(stderr, "sync %s %s\n",
532 type == SPAM_SYNC_WHITE ? "white" : "trapped", ip);
533
534 memset(&hdr, 0, sizeof(hdr));
535 memset(&sd, 0, sizeof(sd));
536
537 if ((ctx = HMAC_CTX_new()) == NULL)
538 goto bad;
539 if (!HMAC_Init_ex(ctx, sync_key, strlen(sync_key), EVP_sha1(), NULL))
540 goto bad;
541
542 /* Add SPAM sync packet header */
543 hdr.sh_version = SPAM_SYNC_VERSION;
544 hdr.sh_af = AF_INET;
545 hdr.sh_counter = htonl(sync_counter++);
546 hdr.sh_length = htons(sizeof(hdr) + sizeof(sd) + sizeof(end));
547 iov[i].iov_base = &hdr;
548 iov[i].iov_len = sizeof(hdr);
549 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
550 goto bad;
551 i++;
552
553 /* Add single SPAM sync address entry */
554 sd.sd_type = htons(type);
555 sd.sd_length = htons(sizeof(sd));
556 sd.sd_timestamp = htonl(now);
557 sd.sd_expire = htonl(expire);
558 sd.sd_ip = inet_addr(ip);
559 iov[i].iov_base = &sd;
560 iov[i].iov_len = sizeof(sd);
561 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
562 goto bad;
563 i++;
564
565 /* Add end marker */
566 end.st_type = htons(SPAM_SYNC_END);
567 end.st_length = htons(sizeof(end));
568 iov[i].iov_base = &end;
569 iov[i].iov_len = sizeof(end);
570 if (!HMAC_Update(ctx, iov[i].iov_base, iov[i].iov_len))
571 goto bad;
572 i++;
573
574 if (!HMAC_Final(ctx, hdr.sh_hmac, &hmac_len))
575 goto bad;
576
577 /* Send message to the target hosts */
578 sync_send(iov, i);
579
580 bad:
581 HMAC_CTX_free(ctx);
582 }
583
584 void
sync_white(time_t now,time_t expire,char * ip)585 sync_white(time_t now, time_t expire, char *ip)
586 {
587 sync_addr(now, expire, ip, SPAM_SYNC_WHITE);
588 }
589
590 void
sync_trapped(time_t now,time_t expire,char * ip)591 sync_trapped(time_t now, time_t expire, char *ip)
592 {
593 sync_addr(now, expire, ip, SPAM_SYNC_TRAPPED);
594 }
595