xref: /openbsd/libexec/spamlogd/spamlogd.c (revision 8529ddd3)
1 /*	$OpenBSD: spamlogd.c,v 1.25 2015/01/21 21:50:33 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 2006 Henning Brauer <henning@openbsd.org>
5  * Copyright (c) 2006 Berk D. Demir.
6  * Copyright (c) 2004-2007 Bob Beck.
7  * Copyright (c) 2001 Theo de Raadt.
8  * Copyright (c) 2001 Can Erkin Acar.
9  * All rights reserved
10  *
11  * Permission to use, copy, modify, and distribute this software for any
12  * purpose with or without fee is hereby granted, provided that the above
13  * copyright notice and this permission notice appear in all copies.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22  */
23 
24 /* watch pf log for mail connections, update whitelist entries. */
25 
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <sys/ioctl.h>
29 #include <sys/signal.h>
30 
31 #include <net/if.h>
32 
33 #include <netinet/in.h>
34 #include <netinet/ip.h>
35 #include <arpa/inet.h>
36 
37 #include <net/pfvar.h>
38 #include <net/if_pflog.h>
39 
40 #include <db.h>
41 #include <err.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <netdb.h>
45 #include <pwd.h>
46 #include <stdio.h>
47 #include <stdarg.h>
48 #include <stdlib.h>
49 #include <syslog.h>
50 #include <string.h>
51 #include <unistd.h>
52 #include <pcap.h>
53 
54 #include "grey.h"
55 #include "sync.h"
56 
57 #define MIN_PFLOG_HDRLEN	45
58 #define PCAPSNAP		512
59 #define PCAPTIMO		500	/* ms */
60 #define PCAPOPTZ		1	/* optimize filter */
61 #define PCAPFSIZ		512	/* pcap filter string size */
62 
63 int debug = 1;
64 int greylist = 1;
65 FILE *grey = NULL;
66 
67 u_short sync_port;
68 int syncsend;
69 u_int8_t		 flag_debug = 0;
70 u_int8_t		 flag_inbound = 0;
71 char			*networkif = NULL;
72 char			*pflogif = "pflog0";
73 char			 errbuf[PCAP_ERRBUF_SIZE];
74 pcap_t			*hpcap = NULL;
75 struct syslog_data	 sdata	= SYSLOG_DATA_INIT;
76 time_t			 whiteexp = WHITEEXP;
77 extern char		*__progname;
78 
79 void	logmsg(int , const char *, ...);
80 void	sighandler_close(int);
81 int	init_pcap(void);
82 void	logpkt_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
83 int	dbupdate(char *, char *);
84 void	usage(void);
85 
86 void
87 logmsg(int pri, const char *msg, ...)
88 {
89 	va_list	ap;
90 	va_start(ap, msg);
91 
92 	if (flag_debug) {
93 		vfprintf(stderr, msg, ap);
94 		fprintf(stderr, "\n");
95 	} else
96 		vsyslog_r(pri, &sdata, msg, ap);
97 
98 	va_end(ap);
99 }
100 
101 /* ARGSUSED */
102 void
103 sighandler_close(int signal)
104 {
105 	if (hpcap != NULL)
106 		pcap_breakloop(hpcap);	/* sighdlr safe */
107 }
108 
109 int
110 init_pcap(void)
111 {
112 	struct bpf_program	bpfp;
113 	char	filter[PCAPFSIZ] = "ip and port 25 and action pass "
114 		    "and tcp[13]&0x12=0x2";
115 
116 	if ((hpcap = pcap_open_live(pflogif, PCAPSNAP, 1, PCAPTIMO,
117 	    errbuf)) == NULL) {
118 		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
119 		return (-1);
120 	}
121 
122 	if (pcap_datalink(hpcap) != DLT_PFLOG) {
123 		logmsg(LOG_ERR, "Invalid datalink type");
124 		pcap_close(hpcap);
125 		hpcap = NULL;
126 		return (-1);
127 	}
128 
129 	if (networkif != NULL) {
130 		strlcat(filter, " and on ", PCAPFSIZ);
131 		strlcat(filter, networkif, PCAPFSIZ);
132 	}
133 
134 	if (pcap_compile(hpcap, &bpfp, filter, PCAPOPTZ, 0) == -1 ||
135 	    pcap_setfilter(hpcap, &bpfp) == -1) {
136 		logmsg(LOG_ERR, "%s", pcap_geterr(hpcap));
137 		return (-1);
138 	}
139 
140 	pcap_freecode(&bpfp);
141 
142 	if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) {
143 		logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
144 		return (-1);
145 	}
146 
147 	return (0);
148 }
149 
150 /* ARGSUSED */
151 void
152 logpkt_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
153 {
154 	sa_family_t		 af;
155 	u_int8_t		 hdrlen;
156 	u_int32_t		 caplen = h->caplen;
157 	const struct ip		*ip = NULL;
158 	const struct pfloghdr	*hdr;
159 	char			 ipstraddr[40] = { '\0' };
160 
161 	hdr = (const struct pfloghdr *)sp;
162 	if (hdr->length < MIN_PFLOG_HDRLEN) {
163 		logmsg(LOG_WARNING, "invalid pflog header length (%u/%u). "
164 		    "packet dropped.", hdr->length, MIN_PFLOG_HDRLEN);
165 		return;
166 	}
167 	hdrlen = BPF_WORDALIGN(hdr->length);
168 
169 	if (caplen < hdrlen) {
170 		logmsg(LOG_WARNING, "pflog header larger than caplen (%u/%u). "
171 		    "packet dropped.", hdrlen, caplen);
172 		return;
173 	}
174 
175 	/* We're interested in passed packets */
176 	if (hdr->action != PF_PASS)
177 		return;
178 
179 	af = hdr->af;
180 	if (af == AF_INET) {
181 		ip = (const struct ip *)(sp + hdrlen);
182 		if (hdr->dir == PF_IN)
183 			inet_ntop(af, &ip->ip_src, ipstraddr,
184 			    sizeof(ipstraddr));
185 		else if (hdr->dir == PF_OUT && !flag_inbound)
186 			inet_ntop(af, &ip->ip_dst, ipstraddr,
187 			    sizeof(ipstraddr));
188 	}
189 
190 	if (ipstraddr[0] != '\0') {
191 		if (hdr->dir == PF_IN)
192 			logmsg(LOG_DEBUG,"inbound %s", ipstraddr);
193 		else
194 			logmsg(LOG_DEBUG,"outbound %s", ipstraddr);
195 		dbupdate(PATH_SPAMD_DB, ipstraddr);
196 	}
197 }
198 
199 int
200 dbupdate(char *dbname, char *ip)
201 {
202 	HASHINFO	hashinfo;
203 	DBT		dbk, dbd;
204 	DB		*db;
205 	struct gdata	gd;
206 	time_t		now;
207 	int		r;
208 	struct in_addr	ia;
209 
210 	now = time(NULL);
211 	memset(&hashinfo, 0, sizeof(hashinfo));
212 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
213 	if (db == NULL) {
214 		logmsg(LOG_ERR, "Can not open db %s: %s", dbname,
215 		    strerror(errno));
216 		return (-1);
217 	}
218 	if (inet_pton(AF_INET, ip, &ia) != 1) {
219 		logmsg(LOG_NOTICE, "Invalid IP address %s", ip);
220 		goto bad;
221 	}
222 	memset(&dbk, 0, sizeof(dbk));
223 	dbk.size = strlen(ip);
224 	dbk.data = ip;
225 	memset(&dbd, 0, sizeof(dbd));
226 
227 	/* add or update whitelist entry */
228 	r = db->get(db, &dbk, &dbd, 0);
229 	if (r == -1) {
230 		logmsg(LOG_NOTICE, "db->get failed (%m)");
231 		goto bad;
232 	}
233 
234 	if (r) {
235 		/* new entry */
236 		memset(&gd, 0, sizeof(gd));
237 		gd.first = now;
238 		gd.bcount = 1;
239 		gd.pass = now;
240 		gd.expire = now + whiteexp;
241 		memset(&dbk, 0, sizeof(dbk));
242 		dbk.size = strlen(ip);
243 		dbk.data = ip;
244 		memset(&dbd, 0, sizeof(dbd));
245 		dbd.size = sizeof(gd);
246 		dbd.data = &gd;
247 		r = db->put(db, &dbk, &dbd, 0);
248 		if (r) {
249 			logmsg(LOG_NOTICE, "db->put failed (%m)");
250 			goto bad;
251 		}
252 	} else {
253 		/* XXX - backwards compat */
254 		if (gdcopyin(&dbd, &gd) == -1) {
255 			/* whatever this is, it doesn't belong */
256 			db->del(db, &dbk, 0);
257 			goto bad;
258 		}
259 		gd.pcount++;
260 		gd.expire = now + whiteexp;
261 		memset(&dbk, 0, sizeof(dbk));
262 		dbk.size = strlen(ip);
263 		dbk.data = ip;
264 		memset(&dbd, 0, sizeof(dbd));
265 		dbd.size = sizeof(gd);
266 		dbd.data = &gd;
267 		r = db->put(db, &dbk, &dbd, 0);
268 		if (r) {
269 			logmsg(LOG_NOTICE, "db->put failed (%m)");
270 			goto bad;
271 		}
272 	}
273 	db->close(db);
274 	db = NULL;
275 	if (syncsend)
276 		sync_white(now, now + whiteexp, ip);
277 	return (0);
278  bad:
279 	db->close(db);
280 	db = NULL;
281 	return (-1);
282 }
283 
284 void
285 usage(void)
286 {
287 	fprintf(stderr,
288 	    "usage: %s [-DI] [-i interface] [-l pflog_interface] "
289 	    "[-W whiteexp] [-Y synctarget]\n",
290 	    __progname);
291 	exit(1);
292 }
293 
294 int
295 main(int argc, char **argv)
296 {
297 	int		 ch;
298 	struct passwd	*pw;
299 	pcap_handler	 phandler = logpkt_handler;
300 	int syncfd = 0;
301 	struct servent *ent;
302 	char *sync_iface = NULL;
303 	char *sync_baddr = NULL;
304 	const char *errstr;
305 
306 	if ((ent = getservbyname("spamd-sync", "udp")) == NULL)
307 		errx(1, "Can't find service \"spamd-sync\" in /etc/services");
308 	sync_port = ntohs(ent->s_port);
309 
310 	while ((ch = getopt(argc, argv, "DIi:l:W:Y:")) != -1) {
311 		switch (ch) {
312 		case 'D':
313 			flag_debug = 1;
314 			break;
315 		case 'I':
316 			flag_inbound = 1;
317 			break;
318 		case 'i':
319 			networkif = optarg;
320 			break;
321 		case 'l':
322 			pflogif = optarg;
323 			break;
324 		case 'W':
325 			/* limit whiteexp to 2160 hours (90 days) */
326 			whiteexp = strtonum(optarg, 1, (24 * 90), &errstr);
327 			if (errstr)
328 				usage();
329 			/* convert to seconds from hours */
330 			whiteexp *= (60 * 60);
331 			break;
332 		case 'Y':
333 			if (sync_addhost(optarg, sync_port) != 0)
334 				sync_iface = optarg;
335 			syncsend++;
336 			break;
337 		default:
338 			usage();
339 			/* NOTREACHED */
340 		}
341 	}
342 
343 	signal(SIGINT , sighandler_close);
344 	signal(SIGQUIT, sighandler_close);
345 	signal(SIGTERM, sighandler_close);
346 
347 	logmsg(LOG_DEBUG, "Listening on %s for %s %s", pflogif,
348 	    (networkif == NULL) ? "all interfaces." : networkif,
349 	    (flag_inbound) ? "Inbound direction only." : "");
350 
351 	if (init_pcap() == -1)
352 		err(1, "couldn't initialize pcap");
353 
354 	if (syncsend) {
355 		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
356 		if (syncfd == -1)
357 			err(1, "sync init");
358 	}
359 
360 	/* privdrop */
361 	pw = getpwnam("_spamd");
362 	if (pw == NULL)
363 		errx(1, "User '_spamd' not found! ");
364 
365 	if (setgroups(1, &pw->pw_gid) ||
366 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
367 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
368 		err(1, "failed to drop privs");
369 
370 	if (!flag_debug) {
371 		if (daemon(0, 0) == -1)
372 			err(1, "daemon");
373 		tzset();
374 		openlog_r("spamlogd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
375 	}
376 
377 	pcap_loop(hpcap, -1, phandler, NULL);
378 
379 	logmsg(LOG_NOTICE, "exiting");
380 	if (!flag_debug)
381 		closelog_r(&sdata);
382 
383 	exit(0);
384 }
385