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
45extern struct syslog_data sdata;
46extern int debug;
47extern FILE *grey;
48extern int greylist;
49
50u_int32_t sync_counter;
51int syncfd;
52int sendmcast;
53struct sockaddr_in sync_in;
54struct sockaddr_in sync_out;
55static char *sync_key;
56
57struct sync_host {
58	LIST_ENTRY(sync_host)	h_entry;
59
60	char			*h_name;
61	struct sockaddr_in	sh_addr;
62};
63LIST_HEAD(synchosts, sync_host) sync_hosts = LIST_HEAD_INITIALIZER(sync_hosts);
64
65void	 sync_send(struct iovec *, int);
66void	 sync_addr(time_t, time_t, char *, u_int16_t);
67
68int
69sync_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
116int
117sync_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
237void
238sync_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
388void
389sync_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
417void
418sync_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
519void
520sync_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
584void
585sync_white(time_t now, time_t expire, char *ip)
586{
587	sync_addr(now, expire, ip, SPAM_SYNC_WHITE);
588}
589
590void
591sync_trapped(time_t now, time_t expire, char *ip)
592{
593	sync_addr(now, expire, ip, SPAM_SYNC_TRAPPED);
594}
595