1 /* $OpenBSD: spamlogd.c,v 1.21 2011/03/18 22:37:06 okan 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 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 if (dbd.size != sizeof(gd)) { 254 /* whatever this is, it doesn't belong */ 255 db->del(db, &dbk, 0); 256 goto bad; 257 } 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; 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