sync
[bitrig.git] / libexec / spamlogd / spamlogd.c
blob40fd6e5f51cb4caa9abd6c3074373c0b6ce9a105
1 /* $OpenBSD: spamlogd.c,v 1.21 2011/03/18 22:37:06 okan Exp $ */
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
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.
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.
24 /* watch pf log for mail connections, update whitelist entries. */
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <sys/ioctl.h>
30 #include <net/if.h>
31 #include <net/if_pflog.h>
33 #include <netinet/in.h>
34 #include <netinet/in_systm.h>
35 #include <netinet/ip.h>
36 #include <arpa/inet.h>
38 #include <net/pfvar.h>
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>
54 #include "grey.h"
55 #include "sync.h"
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 */
63 int debug = 1;
64 int greylist = 1;
65 FILE *grey = NULL;
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;
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);
86 void
87 logmsg(int pri, const char *msg, ...)
89 va_list ap;
90 va_start(ap, msg);
92 if (flag_debug) {
93 vfprintf(stderr, msg, ap);
94 fprintf(stderr, "\n");
95 } else
96 vsyslog_r(pri, &sdata, msg, ap);
98 va_end(ap);
101 /* ARGSUSED */
102 void
103 sighandler_close(int signal)
105 if (hpcap != NULL)
106 pcap_breakloop(hpcap); /* sighdlr safe */
110 init_pcap(void)
112 struct bpf_program bpfp;
113 char filter[PCAPFSIZ] = "ip and port 25 and action pass "
114 "and tcp[13]&0x12=0x2";
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);
122 if (pcap_datalink(hpcap) != DLT_PFLOG) {
123 logmsg(LOG_ERR, "Invalid datalink type");
124 pcap_close(hpcap);
125 hpcap = NULL;
126 return (-1);
129 if (networkif != NULL) {
130 strlcat(filter, " and on ", PCAPFSIZ);
131 strlcat(filter, networkif, PCAPFSIZ);
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);
140 pcap_freecode(&bpfp);
142 if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) {
143 logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno));
144 return (-1);
147 return (0);
150 /* ARGSUSED */
151 void
152 logpkt_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
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' };
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;
167 hdrlen = BPF_WORDALIGN(hdr->length);
169 if (caplen < hdrlen) {
170 logmsg(LOG_WARNING, "pflog header larger than caplen (%u/%u). "
171 "packet dropped.", hdrlen, caplen);
172 return;
175 /* We're interested in passed packets */
176 if (hdr->action != PF_PASS)
177 return;
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));
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);
200 dbupdate(char *dbname, char *ip)
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;
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);
218 if (inet_pton(AF_INET, ip, &ia) != 1) {
219 logmsg(LOG_NOTICE, "Invalid IP address %s", ip);
220 goto bad;
222 memset(&dbk, 0, sizeof(dbk));
223 dbk.size = strlen(ip);
224 dbk.data = ip;
225 memset(&dbd, 0, sizeof(dbd));
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;
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;
252 } else {
253 if (dbd.size != sizeof(gd)) {
254 /* whatever this is, it doesn't belong */
255 db->del(db, &dbk, 0);
256 goto bad;
258 memcpy(&gd, dbd.data, sizeof(gd));
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;
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);
284 void
285 usage(void)
287 fprintf(stderr,
288 "usage: %s [-DI] [-i interface] [-l pflog_interface] "
289 "[-W whiteexp] [-Y synctarget]\n",
290 __progname);
291 exit(1);
295 main(int argc, char **argv)
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;
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);
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 */
343 signal(SIGINT , sighandler_close);
344 signal(SIGQUIT, sighandler_close);
345 signal(SIGTERM, sighandler_close);
347 logmsg(LOG_DEBUG, "Listening on %s for %s %s", pflogif,
348 (networkif == NULL) ? "all interfaces." : networkif,
349 (flag_inbound) ? "Inbound direction only." : "");
351 if (init_pcap() == -1)
352 err(1, "couldn't initialize pcap");
354 if (syncsend) {
355 syncfd = sync_init(sync_iface, sync_baddr, sync_port);
356 if (syncfd == -1)
357 err(1, "sync init");
360 /* privdrop */
361 pw = getpwnam("_spamd");
362 if (pw == NULL)
363 errx(1, "User '_spamd' not found! ");
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");
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);
377 pcap_loop(hpcap, -1, phandler, NULL);
379 logmsg(LOG_NOTICE, "exiting");
380 if (!flag_debug)
381 closelog_r(&sdata);
383 exit(0);