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