xref: /openbsd-src/libexec/spamlogd/spamlogd.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: spamlogd.c,v 1.19 2007/03/05 14:55:09 beck 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 
30 #include <net/if.h>
31 #include <net/if_pflog.h>
32 
33 #include <netinet/in.h>
34 #include <netinet/in_systm.h>
35 #include <netinet/ip.h>
36 #include <arpa/inet.h>
37 
38 #include <net/pfvar.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 extern char		*__progname;
77 
78 void	logmsg(int , const char *, ...);
79 void	sighandler_close(int);
80 int	init_pcap(void);
81 void	logpkt_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
82 int	dbupdate(char *, char *);
83 void	usage(void);
84 
85 void
86 logmsg(int pri, const char *msg, ...)
87 {
88 	va_list	ap;
89 	va_start(ap, msg);
90 
91 	if (flag_debug) {
92 		vfprintf(stderr, msg, ap);
93 		fprintf(stderr, "\n");
94 	} else
95 		vsyslog_r(pri, &sdata, msg, ap);
96 
97 	va_end(ap);
98 }
99 
100 /* ARGSUSED */
101 void
102 sighandler_close(int signal)
103 {
104 	if (hpcap != NULL)
105 		pcap_breakloop(hpcap);	/* sighdlr safe */
106 }
107 
108 int
109 init_pcap(void)
110 {
111 	struct bpf_program	bpfp;
112 	char	filter[PCAPFSIZ] = "ip and port 25 and action pass "
113 		    "and tcp[13]&0x12=0x2";
114 
115 	if ((hpcap = pcap_open_live(pflogif, PCAPSNAP, 1, PCAPTIMO,
116 	    errbuf)) == NULL) {
117 		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
118 		return (-1);
119 	}
120 
121 	if (pcap_datalink(hpcap) != DLT_PFLOG) {
122 		logmsg(LOG_ERR, "Invalid datalink type");
123 		pcap_close(hpcap);
124 		hpcap = NULL;
125 		return (-1);
126 	}
127 
128 	if (networkif != NULL) {
129 		strlcat(filter, " and on ", PCAPFSIZ);
130 		strlcat(filter, networkif, PCAPFSIZ);
131 	}
132 
133 	if (pcap_compile(hpcap, &bpfp, filter, PCAPOPTZ, 0) == -1 ||
134 	    pcap_setfilter(hpcap, &bpfp) == -1) {
135 		logmsg(LOG_ERR, "%s", pcap_geterr(hpcap));
136 		return (-1);
137 	}
138 
139 	pcap_freecode(&bpfp);
140 
141 	if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) {
142 		logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
143 		return (-1);
144 	}
145 
146 	return (0);
147 }
148 
149 /* ARGSUSED */
150 void
151 logpkt_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
152 {
153 	sa_family_t		 af;
154 	u_int8_t		 hdrlen;
155 	u_int32_t		 caplen = h->caplen;
156 	const struct ip		*ip = NULL;
157 	const struct pfloghdr	*hdr;
158 	char			 ipstraddr[40] = { '\0' };
159 
160 	hdr = (const struct pfloghdr *)sp;
161 	if (hdr->length < MIN_PFLOG_HDRLEN) {
162 		logmsg(LOG_WARNING, "invalid pflog header length (%u/%u). "
163 		    "packet dropped.", hdr->length, MIN_PFLOG_HDRLEN);
164 		return;
165 	}
166 	hdrlen = BPF_WORDALIGN(hdr->length);
167 
168 	if (caplen < hdrlen) {
169 		logmsg(LOG_WARNING, "pflog header larger than caplen (%u/%u). "
170 		    "packet dropped.", hdrlen, caplen);
171 		return;
172 	}
173 
174 	/* We're interested in passed packets */
175 	if (hdr->action != PF_PASS)
176 		return;
177 
178 	af = hdr->af;
179 	if (af == AF_INET) {
180 		ip = (const struct ip *)(sp + hdrlen);
181 		if (hdr->dir == PF_IN)
182 			inet_ntop(af, &ip->ip_src, ipstraddr,
183 			    sizeof(ipstraddr));
184 		else if (hdr->dir == PF_OUT && !flag_inbound)
185 			inet_ntop(af, &ip->ip_dst, ipstraddr,
186 			    sizeof(ipstraddr));
187 	}
188 
189 	if (ipstraddr[0] != '\0') {
190 		if (hdr->dir == PF_IN)
191 			logmsg(LOG_DEBUG,"inbound %s", ipstraddr);
192 		else
193 			logmsg(LOG_DEBUG,"outbound %s", ipstraddr);
194 		dbupdate(PATH_SPAMD_DB, ipstraddr);
195 	}
196 }
197 
198 int
199 dbupdate(char *dbname, char *ip)
200 {
201 	HASHINFO	hashinfo;
202 	DBT		dbk, dbd;
203 	DB		*db;
204 	struct gdata	gd;
205 	time_t		now;
206 	int		r;
207 	struct in_addr	ia;
208 
209 	now = time(NULL);
210 	memset(&hashinfo, 0, sizeof(hashinfo));
211 	db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo);
212 	if (db == NULL) {
213 		logmsg(LOG_ERR, "Can not open db %s: %s", dbname,
214 		    strerror(errno));
215 		return (-1);
216 	}
217 	if (inet_pton(AF_INET, ip, &ia) != 1) {
218 		logmsg(LOG_NOTICE, "Invalid IP address %s", ip);
219 		goto bad;
220 	}
221 	memset(&dbk, 0, sizeof(dbk));
222 	dbk.size = strlen(ip);
223 	dbk.data = ip;
224 	memset(&dbd, 0, sizeof(dbd));
225 
226 	/* add or update whitelist entry */
227 	r = db->get(db, &dbk, &dbd, 0);
228 	if (r == -1) {
229 		logmsg(LOG_NOTICE, "db->get failed (%m)");
230 		goto bad;
231 	}
232 
233 	if (r) {
234 		/* new entry */
235 		memset(&gd, 0, sizeof(gd));
236 		gd.first = now;
237 		gd.bcount = 1;
238 		gd.pass = now;
239 		gd.expire = now + WHITEEXP;
240 		memset(&dbk, 0, sizeof(dbk));
241 		dbk.size = strlen(ip);
242 		dbk.data = ip;
243 		memset(&dbd, 0, sizeof(dbd));
244 		dbd.size = sizeof(gd);
245 		dbd.data = &gd;
246 		r = db->put(db, &dbk, &dbd, 0);
247 		if (r) {
248 			logmsg(LOG_NOTICE, "db->put failed (%m)");
249 			goto bad;
250 		}
251 	} else {
252 		if (dbd.size != sizeof(gd)) {
253 			/* whatever this is, it doesn't belong */
254 			db->del(db, &dbk, 0);
255 			goto bad;
256 		}
257 		memcpy(&gd, dbd.data, sizeof(gd));
258 		gd.pcount++;
259 		gd.expire = now + WHITEEXP;
260 		memset(&dbk, 0, sizeof(dbk));
261 		dbk.size = strlen(ip);
262 		dbk.data = ip;
263 		memset(&dbd, 0, sizeof(dbd));
264 		dbd.size = sizeof(gd);
265 		dbd.data = &gd;
266 		r = db->put(db, &dbk, &dbd, 0);
267 		if (r) {
268 			logmsg(LOG_NOTICE, "db->put failed (%m)");
269 			goto bad;
270 		}
271 	}
272 	db->close(db);
273 	db = NULL;
274 	if (syncsend)
275 		sync_white(now, now + WHITEEXP, ip);
276 	return (0);
277  bad:
278 	db->close(db);
279 	db = NULL;
280 	return (-1);
281 }
282 
283 void
284 usage(void)
285 {
286 	fprintf(stderr,
287 	    "usage: %s [-DI] [-i interface] [-l pflog_interface] [-Y synctarget]\n",
288 	    __progname);
289 	exit(1);
290 }
291 
292 int
293 main(int argc, char **argv)
294 {
295 	int		 ch;
296 	struct passwd	*pw;
297 	pcap_handler	 phandler = logpkt_handler;
298 	int syncfd = 0;
299 	struct servent *ent;
300 	char *sync_iface = NULL;
301 	char *sync_baddr = NULL;
302 
303 	if ((ent = getservbyname("spamd-sync", "udp")) == NULL)
304 		errx(1, "Can't find service \"spamd-sync\" in /etc/services");
305 	sync_port = ntohs(ent->s_port);
306 
307 	while ((ch = getopt(argc, argv, "DIi:l:Y:")) != -1) {
308 		switch (ch) {
309 		case 'D':
310 			flag_debug = 1;
311 			break;
312 		case 'I':
313 			flag_inbound = 1;
314 			break;
315 		case 'i':
316 			networkif = optarg;
317 			break;
318 		case 'l':
319 			pflogif = optarg;
320 			break;
321 		case 'Y':
322 			if (sync_addhost(optarg, sync_port) != 0)
323 				sync_iface = optarg;
324 			syncsend++;
325 			break;
326 		default:
327 			usage();
328 			/* NOTREACHED */
329 		}
330 	}
331 
332 	signal(SIGINT , sighandler_close);
333 	signal(SIGQUIT, sighandler_close);
334 	signal(SIGTERM, sighandler_close);
335 
336 	logmsg(LOG_DEBUG, "Listening on %s for %s %s", pflogif,
337 	    (networkif == NULL) ? "all interfaces." : networkif,
338 	    (flag_inbound) ? "Inbound direction only." : "");
339 
340 	if (init_pcap() == -1)
341 		err(1, "couldn't initialize pcap");
342 
343 	if (syncsend) {
344 		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
345 		if (syncfd == -1)
346 			err(1, "sync init");
347 	}
348 
349 	/* privdrop */
350 	pw = getpwnam("_spamd");
351 	if (pw == NULL)
352 		errx(1, "User '_spamd' not found! ");
353 
354 	if (setgroups(1, &pw->pw_gid) ||
355 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
356 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
357 		err(1, "failed to drop privs");
358 
359 	if (!flag_debug) {
360 		if (daemon(0, 0) == -1)
361 			err(1, "daemon");
362 		tzset();
363 		openlog_r("spamlogd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
364 	}
365 
366 	pcap_loop(hpcap, -1, phandler, NULL);
367 
368 	logmsg(LOG_NOTICE, "exiting");
369 	if (!flag_debug)
370 		closelog_r(&sdata);
371 
372 	exit(0);
373 }
374