GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: libexec/spamlogd/spamlogd.c Lines: 0 178 0.0 %
Date: 2017-11-13 Branches: 0 91 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: spamlogd.c,v 1.27 2016/03/16 14:47:04 mestre 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
#define SPAMD_USER		"_spamd"
64
65
int debug = 1;
66
int greylist = 1;
67
FILE *grey = NULL;
68
69
u_short sync_port;
70
int syncsend;
71
u_int8_t		 flag_debug = 0;
72
u_int8_t		 flag_inbound = 0;
73
char			*networkif = NULL;
74
char			*pflogif = "pflog0";
75
char			 errbuf[PCAP_ERRBUF_SIZE];
76
pcap_t			*hpcap = NULL;
77
struct syslog_data	 sdata	= SYSLOG_DATA_INIT;
78
time_t			 whiteexp = WHITEEXP;
79
extern char		*__progname;
80
81
void	logmsg(int , const char *, ...);
82
void	sighandler_close(int);
83
int	init_pcap(void);
84
void	logpkt_handler(u_char *, const struct pcap_pkthdr *, const u_char *);
85
int	dbupdate(char *, char *);
86
__dead void	usage(void);
87
88
void
89
logmsg(int pri, const char *msg, ...)
90
{
91
	va_list	ap;
92
	va_start(ap, msg);
93
94
	if (flag_debug) {
95
		vfprintf(stderr, msg, ap);
96
		fprintf(stderr, "\n");
97
	} else
98
		vsyslog_r(pri, &sdata, msg, ap);
99
100
	va_end(ap);
101
}
102
103
void
104
sighandler_close(int signal)
105
{
106
	if (hpcap != NULL)
107
		pcap_breakloop(hpcap);	/* sighdlr safe */
108
}
109
110
int
111
init_pcap(void)
112
{
113
	struct bpf_program	bpfp;
114
	char	filter[PCAPFSIZ] = "ip and port 25 and action pass "
115
		    "and tcp[13]&0x12=0x2";
116
117
	if ((hpcap = pcap_open_live(pflogif, PCAPSNAP, 1, PCAPTIMO,
118
	    errbuf)) == NULL) {
119
		logmsg(LOG_ERR, "Failed to initialize: %s", errbuf);
120
		return (-1);
121
	}
122
123
	if (pcap_datalink(hpcap) != DLT_PFLOG) {
124
		logmsg(LOG_ERR, "Invalid datalink type");
125
		pcap_close(hpcap);
126
		hpcap = NULL;
127
		return (-1);
128
	}
129
130
	if (networkif != NULL) {
131
		strlcat(filter, " and on ", PCAPFSIZ);
132
		strlcat(filter, networkif, PCAPFSIZ);
133
	}
134
135
	if (pcap_compile(hpcap, &bpfp, filter, PCAPOPTZ, 0) == -1 ||
136
	    pcap_setfilter(hpcap, &bpfp) == -1) {
137
		logmsg(LOG_ERR, "%s", pcap_geterr(hpcap));
138
		return (-1);
139
	}
140
141
	pcap_freecode(&bpfp);
142
143
	if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) {
144
		logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
145
		return (-1);
146
	}
147
148
	return (0);
149
}
150
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 (geteuid())
307
		errx(1, "need root privileges");
308
309
	if ((ent = getservbyname("spamd-sync", "udp")) == NULL)
310
		errx(1, "Can't find service \"spamd-sync\" in /etc/services");
311
	sync_port = ntohs(ent->s_port);
312
313
	while ((ch = getopt(argc, argv, "DIi:l:W:Y:")) != -1) {
314
		switch (ch) {
315
		case 'D':
316
			flag_debug = 1;
317
			break;
318
		case 'I':
319
			flag_inbound = 1;
320
			break;
321
		case 'i':
322
			networkif = optarg;
323
			break;
324
		case 'l':
325
			pflogif = optarg;
326
			break;
327
		case 'W':
328
			/* limit whiteexp to 2160 hours (90 days) */
329
			whiteexp = strtonum(optarg, 1, (24 * 90), &errstr);
330
			if (errstr)
331
				usage();
332
			/* convert to seconds from hours */
333
			whiteexp *= (60 * 60);
334
			break;
335
		case 'Y':
336
			if (sync_addhost(optarg, sync_port) != 0)
337
				sync_iface = optarg;
338
			syncsend++;
339
			break;
340
		default:
341
			usage();
342
		}
343
	}
344
345
	signal(SIGINT , sighandler_close);
346
	signal(SIGQUIT, sighandler_close);
347
	signal(SIGTERM, sighandler_close);
348
349
	logmsg(LOG_DEBUG, "Listening on %s for %s %s", pflogif,
350
	    (networkif == NULL) ? "all interfaces." : networkif,
351
	    (flag_inbound) ? "Inbound direction only." : "");
352
353
	if (init_pcap() == -1)
354
		err(1, "couldn't initialize pcap");
355
356
	if (syncsend) {
357
		syncfd = sync_init(sync_iface, sync_baddr, sync_port);
358
		if (syncfd == -1)
359
			err(1, "sync init");
360
	}
361
362
	/* privdrop */
363
	if ((pw = getpwnam(SPAMD_USER)) == NULL)
364
		errx(1, "no such user %s", SPAMD_USER);
365
366
	if (setgroups(1, &pw->pw_gid) ||
367
	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
368
	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
369
		err(1, "failed to drop privs");
370
371
	if (!flag_debug) {
372
		if (daemon(0, 0) == -1)
373
			err(1, "daemon");
374
		tzset();
375
		openlog_r("spamlogd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata);
376
	}
377
378
	if (syncsend) {
379
		if (pledge("stdio rpath wpath inet flock cpath", NULL) == -1)
380
			err(1, "pledge");
381
	} else {
382
		if (pledge("stdio rpath wpath flock cpath", NULL) == -1)
383
			err(1, "pledge");
384
	}
385
386
	pcap_loop(hpcap, -1, phandler, NULL);
387
388
	logmsg(LOG_NOTICE, "exiting");
389
	if (!flag_debug)
390
		closelog_r(&sdata);
391
392
	exit(0);
393
}