xref: /openbsd/libexec/spamd/sync.c (revision 09467b48)
1 /*	$OpenBSD: sync.c,v 1.13 2019/06/28 13:32:53 deraadt 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
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
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
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
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
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 	HMAC_CTX_init(&ctx);
444 	HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1());
445 
446 	sglen = sizeof(sg) + fromlen + tolen + helolen;
447 	padlen = SPAM_ALIGN(sglen) - sglen;
448 
449 	/* Add SPAM sync packet header */
450 	hdr.sh_version = SPAM_SYNC_VERSION;
451 	hdr.sh_af = AF_INET;
452 	hdr.sh_counter = htonl(sync_counter++);
453 	hdr.sh_length = htons(sizeof(hdr) + sglen + padlen + sizeof(end));
454 	iov[i].iov_base = &hdr;
455 	iov[i].iov_len = sizeof(hdr);
456 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
457 	i++;
458 
459 	/* Add single SPAM sync greylisting entry */
460 	sg.sg_type = htons(SPAM_SYNC_GREY);
461 	sg.sg_length = htons(sglen + padlen);
462 	sg.sg_timestamp = htonl(now);
463 	sg.sg_ip = inet_addr(ip);
464 	sg.sg_from_length = htons(fromlen);
465 	sg.sg_to_length = htons(tolen);
466 	sg.sg_helo_length = htons(helolen);
467 	iov[i].iov_base = &sg;
468 	iov[i].iov_len = sizeof(sg);
469 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
470 	i++;
471 
472 	iov[i].iov_base = from;
473 	iov[i].iov_len = fromlen;
474 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
475 	i++;
476 
477 	iov[i].iov_base = to;
478 	iov[i].iov_len = tolen;
479 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
480 	i++;
481 
482 	iov[i].iov_base = helo;
483 	iov[i].iov_len = helolen;
484 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
485 	i++;
486 
487 	iov[i].iov_base = pad;
488 	iov[i].iov_len = padlen;
489 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
490 	i++;
491 
492 	/* Add end marker */
493 	end.st_type = htons(SPAM_SYNC_END);
494 	end.st_length = htons(sizeof(end));
495 	iov[i].iov_base = &end;
496 	iov[i].iov_len = sizeof(end);
497 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
498 	i++;
499 
500 	HMAC_Final(&ctx, hdr.sh_hmac, &hmac_len);
501 
502 	/* Send message to the target hosts */
503 	sync_send(iov, i);
504 	HMAC_CTX_cleanup(&ctx);
505 }
506 
507 void
508 sync_addr(time_t now, time_t expire, char *ip, u_int16_t type)
509 {
510 	struct iovec iov[3];
511 	struct spam_synchdr hdr;
512 	struct spam_synctlv_addr sd;
513 	struct spam_synctlv_hdr end;
514 	int i = 0;
515 	HMAC_CTX ctx;
516 	u_int hmac_len;
517 
518 	if (debug)
519 		fprintf(stderr, "sync %s %s\n",
520 			type == SPAM_SYNC_WHITE ? "white" : "trapped", ip);
521 
522 	memset(&hdr, 0, sizeof(hdr));
523 	memset(&sd, 0, sizeof(sd));
524 
525 	HMAC_CTX_init(&ctx);
526 	HMAC_Init(&ctx, sync_key, strlen(sync_key), EVP_sha1());
527 
528 	/* Add SPAM sync packet header */
529 	hdr.sh_version = SPAM_SYNC_VERSION;
530 	hdr.sh_af = AF_INET;
531 	hdr.sh_counter = htonl(sync_counter++);
532 	hdr.sh_length = htons(sizeof(hdr) + sizeof(sd) + sizeof(end));
533 	iov[i].iov_base = &hdr;
534 	iov[i].iov_len = sizeof(hdr);
535 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
536 	i++;
537 
538 	/* Add single SPAM sync address entry */
539 	sd.sd_type = htons(type);
540 	sd.sd_length = htons(sizeof(sd));
541 	sd.sd_timestamp = htonl(now);
542 	sd.sd_expire = htonl(expire);
543 	sd.sd_ip = inet_addr(ip);
544 	iov[i].iov_base = &sd;
545 	iov[i].iov_len = sizeof(sd);
546 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
547 	i++;
548 
549 	/* Add end marker */
550 	end.st_type = htons(SPAM_SYNC_END);
551 	end.st_length = htons(sizeof(end));
552 	iov[i].iov_base = &end;
553 	iov[i].iov_len = sizeof(end);
554 	HMAC_Update(&ctx, iov[i].iov_base, iov[i].iov_len);
555 	i++;
556 
557 	HMAC_Final(&ctx, hdr.sh_hmac, &hmac_len);
558 
559 	/* Send message to the target hosts */
560 	sync_send(iov, i);
561 	HMAC_CTX_cleanup(&ctx);
562 }
563 
564 void
565 sync_white(time_t now, time_t expire, char *ip)
566 {
567 	sync_addr(now, expire, ip, SPAM_SYNC_WHITE);
568 }
569 
570 void
571 sync_trapped(time_t now, time_t expire, char *ip)
572 {
573 	sync_addr(now, expire, ip, SPAM_SYNC_TRAPPED);
574 }
575