1 /* $OpenBSD: spamlogd.c,v 1.21 2011/03/18 22:37:06 okan Exp $ */
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.
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>
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>
57 #define MIN_PFLOG_HDRLEN 45
59 #define PCAPTIMO 500 /* ms */
60 #define PCAPOPTZ 1 /* optimize filter */
61 #define PCAPFSIZ 512 /* pcap filter string size */
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
];
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);
82 void logpkt_handler(u_char
*, const struct pcap_pkthdr
*, const u_char
*);
83 int dbupdate(char *, char *);
87 logmsg(int pri
, const char *msg
, ...)
93 vfprintf(stderr
, msg
, ap
);
94 fprintf(stderr
, "\n");
96 vsyslog_r(pri
, &sdata
, msg
, ap
);
103 sighandler_close(int signal
)
106 pcap_breakloop(hpcap
); /* sighdlr safe */
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
,
118 logmsg(LOG_ERR
, "Failed to initialize: %s", errbuf
);
122 if (pcap_datalink(hpcap
) != DLT_PFLOG
) {
123 logmsg(LOG_ERR
, "Invalid datalink type");
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
));
140 pcap_freecode(&bpfp
);
142 if (ioctl(pcap_fileno(hpcap
), BIOCLOCK
) < 0) {
143 logmsg(LOG_ERR
, "BIOCLOCK: %s", strerror(errno
));
152 logpkt_handler(u_char
*user
, const struct pcap_pkthdr
*h
, const u_char
*sp
)
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
);
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
);
175 /* We're interested in passed packets */
176 if (hdr
->action
!= PF_PASS
)
181 ip
= (const struct ip
*)(sp
+ hdrlen
);
182 if (hdr
->dir
== PF_IN
)
183 inet_ntop(af
, &ip
->ip_src
, ipstraddr
,
185 else if (hdr
->dir
== PF_OUT
&& !flag_inbound
)
186 inet_ntop(af
, &ip
->ip_dst
, ipstraddr
,
190 if (ipstraddr
[0] != '\0') {
191 if (hdr
->dir
== PF_IN
)
192 logmsg(LOG_DEBUG
,"inbound %s", ipstraddr
);
194 logmsg(LOG_DEBUG
,"outbound %s", ipstraddr
);
195 dbupdate(PATH_SPAMD_DB
, ipstraddr
);
200 dbupdate(char *dbname
, char *ip
)
211 memset(&hashinfo
, 0, sizeof(hashinfo
));
212 db
= dbopen(dbname
, O_EXLOCK
|O_RDWR
, 0600, DB_HASH
, &hashinfo
);
214 logmsg(LOG_ERR
, "Can not open db %s: %s", dbname
,
218 if (inet_pton(AF_INET
, ip
, &ia
) != 1) {
219 logmsg(LOG_NOTICE
, "Invalid IP address %s", ip
);
222 memset(&dbk
, 0, sizeof(dbk
));
223 dbk
.size
= strlen(ip
);
225 memset(&dbd
, 0, sizeof(dbd
));
227 /* add or update whitelist entry */
228 r
= db
->get(db
, &dbk
, &dbd
, 0);
230 logmsg(LOG_NOTICE
, "db->get failed (%m)");
236 memset(&gd
, 0, sizeof(gd
));
240 gd
.expire
= now
+ whiteexp
;
241 memset(&dbk
, 0, sizeof(dbk
));
242 dbk
.size
= strlen(ip
);
244 memset(&dbd
, 0, sizeof(dbd
));
245 dbd
.size
= sizeof(gd
);
247 r
= db
->put(db
, &dbk
, &dbd
, 0);
249 logmsg(LOG_NOTICE
, "db->put failed (%m)");
253 if (dbd
.size
!= sizeof(gd
)) {
254 /* whatever this is, it doesn't belong */
255 db
->del(db
, &dbk
, 0);
258 memcpy(&gd
, dbd
.data
, sizeof(gd
));
260 gd
.expire
= now
+ whiteexp
;
261 memset(&dbk
, 0, sizeof(dbk
));
262 dbk
.size
= strlen(ip
);
264 memset(&dbd
, 0, sizeof(dbd
));
265 dbd
.size
= sizeof(gd
);
267 r
= db
->put(db
, &dbk
, &dbd
, 0);
269 logmsg(LOG_NOTICE
, "db->put failed (%m)");
276 sync_white(now
, now
+ whiteexp
, ip
);
288 "usage: %s [-DI] [-i interface] [-l pflog_interface] "
289 "[-W whiteexp] [-Y synctarget]\n",
295 main(int argc
, char **argv
)
299 pcap_handler phandler
= logpkt_handler
;
302 char *sync_iface
= NULL
;
303 char *sync_baddr
= NULL
;
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) {
325 /* limit whiteexp to 2160 hours (90 days) */
326 whiteexp
= strtonum(optarg
, 1, (24 * 90), &errstr
);
329 /* convert to seconds from hours */
330 whiteexp
*= (60 * 60);
333 if (sync_addhost(optarg
, sync_port
) != 0)
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");
355 syncfd
= sync_init(sync_iface
, sync_baddr
, sync_port
);
361 pw
= getpwnam("_spamd");
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");
371 if (daemon(0, 0) == -1)
374 openlog_r("spamlogd", LOG_PID
| LOG_NDELAY
, LOG_DAEMON
, &sdata
);
377 pcap_loop(hpcap
, -1, phandler
, NULL
);
379 logmsg(LOG_NOTICE
, "exiting");