1 /* $OpenBSD: spamlogd.c,v 1.31 2019/07/25 17:32:33 brynet 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-int.h> 53 #include <pcap.h> 54 55 #include "grey.h" 56 #include "sync.h" 57 58 #define MIN_PFLOG_HDRLEN 45 59 #define PCAPSNAP 512 60 #define PCAPTIMO 500 /* ms */ 61 #define PCAPOPTZ 1 /* optimize filter */ 62 #define PCAPFSIZ 512 /* pcap filter string size */ 63 64 #define SPAMD_USER "_spamd" 65 66 int debug = 1; 67 int greylist = 1; 68 FILE *grey = NULL; 69 70 u_short sync_port; 71 int syncsend; 72 u_int8_t flag_debug = 0; 73 u_int8_t flag_inbound = 0; 74 char *networkif = NULL; 75 char *pflogif = "pflog0"; 76 char errbuf[PCAP_ERRBUF_SIZE]; 77 pcap_t *hpcap = NULL; 78 struct syslog_data sdata = SYSLOG_DATA_INIT; 79 time_t whiteexp = WHITEEXP; 80 extern char *__progname; 81 82 void logmsg(int , const char *, ...); 83 void sighandler_close(int); 84 int init_pcap(void); 85 void logpkt_handler(u_char *, const struct pcap_pkthdr *, const u_char *); 86 int dbupdate(char *, char *); 87 __dead void usage(void); 88 89 void 90 logmsg(int pri, const char *msg, ...) 91 { 92 va_list ap; 93 va_start(ap, msg); 94 95 if (flag_debug) { 96 vfprintf(stderr, msg, ap); 97 fprintf(stderr, "\n"); 98 } else 99 vsyslog_r(pri, &sdata, msg, ap); 100 101 va_end(ap); 102 } 103 104 void 105 sighandler_close(int signal) 106 { 107 if (hpcap != NULL) 108 pcap_breakloop(hpcap); /* sighdlr safe */ 109 } 110 111 pcap_t * 112 pflog_read_live(const char *source, int slen, int promisc, int to_ms, 113 char *ebuf) 114 { 115 int fd; 116 struct bpf_version bv; 117 struct ifreq ifr; 118 u_int v, dlt = DLT_PFLOG; 119 pcap_t *p; 120 121 if (source == NULL || slen <= 0) 122 return (NULL); 123 124 p = pcap_create(source, ebuf); 125 if (p == NULL) 126 return (NULL); 127 128 /* Open bpf(4) read only */ 129 if ((fd = open("/dev/bpf", O_RDONLY)) == -1) 130 return (NULL); 131 132 if (ioctl(fd, BIOCVERSION, &bv) == -1) { 133 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s", 134 pcap_strerror(errno)); 135 goto bad; 136 } 137 138 if (bv.bv_major != BPF_MAJOR_VERSION || 139 bv.bv_minor < BPF_MINOR_VERSION) { 140 snprintf(ebuf, PCAP_ERRBUF_SIZE, 141 "kernel bpf filter out of date"); 142 goto bad; 143 } 144 145 strlcpy(ifr.ifr_name, source, sizeof(ifr.ifr_name)); 146 if (ioctl(fd, BIOCSETIF, &ifr) == -1) { 147 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSETIF: %s", 148 pcap_strerror(errno)); 149 goto bad; 150 } 151 152 if (dlt != (u_int) -1 && ioctl(fd, BIOCSDLT, &dlt)) { 153 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSDLT: %s", 154 pcap_strerror(errno)); 155 goto bad; 156 } 157 158 p->fd = fd; 159 p->snapshot = slen; 160 p->linktype = dlt; 161 162 /* set timeout */ 163 if (to_ms != 0) { 164 struct timeval to; 165 to.tv_sec = to_ms / 1000; 166 to.tv_usec = (to_ms * 1000) % 1000000; 167 if (ioctl(p->fd, BIOCSRTIMEOUT, &to) == -1) { 168 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSRTIMEOUT: %s", 169 pcap_strerror(errno)); 170 goto bad; 171 } 172 } 173 if (promisc) 174 /* this is allowed to fail */ 175 ioctl(fd, BIOCPROMISC, NULL); 176 177 if (ioctl(fd, BIOCGBLEN, &v) == -1) { 178 snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s", 179 pcap_strerror(errno)); 180 goto bad; 181 } 182 p->bufsize = v; 183 p->buffer = malloc(p->bufsize); 184 if (p->buffer == NULL) { 185 snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s", 186 pcap_strerror(errno)); 187 goto bad; 188 } 189 p->activated = 1; 190 return (p); 191 192 bad: 193 pcap_close(p); 194 return (NULL); 195 } 196 197 int 198 init_pcap(void) 199 { 200 struct bpf_program bpfp; 201 char filter[PCAPFSIZ] = "ip and port 25 and action pass " 202 "and tcp[13]&0x12=0x2"; 203 204 if ((hpcap = pflog_read_live(pflogif, PCAPSNAP, 1, PCAPTIMO, 205 errbuf)) == NULL) { 206 logmsg(LOG_ERR, "Failed to initialize: %s", errbuf); 207 return (-1); 208 } 209 210 if (pcap_datalink(hpcap) != DLT_PFLOG) { 211 logmsg(LOG_ERR, "Invalid datalink type"); 212 pcap_close(hpcap); 213 hpcap = NULL; 214 return (-1); 215 } 216 217 if (networkif != NULL) { 218 strlcat(filter, " and on ", PCAPFSIZ); 219 strlcat(filter, networkif, PCAPFSIZ); 220 } 221 222 if (pcap_compile(hpcap, &bpfp, filter, PCAPOPTZ, 0) == -1 || 223 pcap_setfilter(hpcap, &bpfp) == -1) { 224 logmsg(LOG_ERR, "%s", pcap_geterr(hpcap)); 225 return (-1); 226 } 227 228 pcap_freecode(&bpfp); 229 230 if (ioctl(pcap_fileno(hpcap), BIOCLOCK) == -1) { 231 logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno)); 232 return (-1); 233 } 234 235 return (0); 236 } 237 238 void 239 logpkt_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) 240 { 241 sa_family_t af; 242 u_int8_t hdrlen; 243 u_int32_t caplen = h->caplen; 244 const struct ip *ip = NULL; 245 const struct pfloghdr *hdr; 246 char ipstraddr[40] = { '\0' }; 247 248 hdr = (const struct pfloghdr *)sp; 249 if (hdr->length < MIN_PFLOG_HDRLEN) { 250 logmsg(LOG_WARNING, "invalid pflog header length (%u/%u). " 251 "packet dropped.", hdr->length, MIN_PFLOG_HDRLEN); 252 return; 253 } 254 hdrlen = BPF_WORDALIGN(hdr->length); 255 256 if (caplen < hdrlen) { 257 logmsg(LOG_WARNING, "pflog header larger than caplen (%u/%u). " 258 "packet dropped.", hdrlen, caplen); 259 return; 260 } 261 262 /* We're interested in passed packets */ 263 if (hdr->action != PF_PASS) 264 return; 265 266 af = hdr->af; 267 if (af == AF_INET) { 268 ip = (const struct ip *)(sp + hdrlen); 269 if (hdr->dir == PF_IN) 270 inet_ntop(af, &ip->ip_src, ipstraddr, 271 sizeof(ipstraddr)); 272 else if (hdr->dir == PF_OUT && !flag_inbound) 273 inet_ntop(af, &ip->ip_dst, ipstraddr, 274 sizeof(ipstraddr)); 275 } 276 277 if (ipstraddr[0] != '\0') { 278 if (hdr->dir == PF_IN) 279 logmsg(LOG_DEBUG,"inbound %s", ipstraddr); 280 else 281 logmsg(LOG_DEBUG,"outbound %s", ipstraddr); 282 dbupdate(PATH_SPAMD_DB, ipstraddr); 283 } 284 } 285 286 int 287 dbupdate(char *dbname, char *ip) 288 { 289 HASHINFO hashinfo; 290 DBT dbk, dbd; 291 DB *db; 292 struct gdata gd; 293 time_t now; 294 int r; 295 struct in_addr ia; 296 297 now = time(NULL); 298 memset(&hashinfo, 0, sizeof(hashinfo)); 299 db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo); 300 if (db == NULL) { 301 logmsg(LOG_ERR, "Can not open db %s: %s", dbname, 302 strerror(errno)); 303 return (-1); 304 } 305 if (inet_pton(AF_INET, ip, &ia) != 1) { 306 logmsg(LOG_NOTICE, "Invalid IP address %s", ip); 307 goto bad; 308 } 309 memset(&dbk, 0, sizeof(dbk)); 310 dbk.size = strlen(ip); 311 dbk.data = ip; 312 memset(&dbd, 0, sizeof(dbd)); 313 314 /* add or update whitelist entry */ 315 r = db->get(db, &dbk, &dbd, 0); 316 if (r == -1) { 317 logmsg(LOG_NOTICE, "db->get failed (%m)"); 318 goto bad; 319 } 320 321 if (r) { 322 /* new entry */ 323 memset(&gd, 0, sizeof(gd)); 324 gd.first = now; 325 gd.bcount = 1; 326 gd.pass = now; 327 gd.expire = now + whiteexp; 328 memset(&dbk, 0, sizeof(dbk)); 329 dbk.size = strlen(ip); 330 dbk.data = ip; 331 memset(&dbd, 0, sizeof(dbd)); 332 dbd.size = sizeof(gd); 333 dbd.data = &gd; 334 r = db->put(db, &dbk, &dbd, 0); 335 if (r) { 336 logmsg(LOG_NOTICE, "db->put failed (%m)"); 337 goto bad; 338 } 339 } else { 340 /* XXX - backwards compat */ 341 if (gdcopyin(&dbd, &gd) == -1) { 342 /* whatever this is, it doesn't belong */ 343 db->del(db, &dbk, 0); 344 goto bad; 345 } 346 gd.pcount++; 347 gd.expire = now + whiteexp; 348 memset(&dbk, 0, sizeof(dbk)); 349 dbk.size = strlen(ip); 350 dbk.data = ip; 351 memset(&dbd, 0, sizeof(dbd)); 352 dbd.size = sizeof(gd); 353 dbd.data = &gd; 354 r = db->put(db, &dbk, &dbd, 0); 355 if (r) { 356 logmsg(LOG_NOTICE, "db->put failed (%m)"); 357 goto bad; 358 } 359 } 360 db->close(db); 361 db = NULL; 362 if (syncsend) 363 sync_white(now, now + whiteexp, ip); 364 return (0); 365 bad: 366 db->close(db); 367 db = NULL; 368 return (-1); 369 } 370 371 void 372 usage(void) 373 { 374 fprintf(stderr, 375 "usage: %s [-DI] [-i interface] [-l pflog_interface] " 376 "[-W whiteexp] [-Y synctarget]\n", 377 __progname); 378 exit(1); 379 } 380 381 int 382 main(int argc, char **argv) 383 { 384 int ch; 385 struct passwd *pw; 386 pcap_handler phandler = logpkt_handler; 387 int syncfd = 0; 388 struct servent *ent; 389 char *sync_iface = NULL; 390 char *sync_baddr = NULL; 391 const char *errstr; 392 393 if (geteuid()) 394 errx(1, "need root privileges"); 395 396 if ((ent = getservbyname("spamd-sync", "udp")) == NULL) 397 errx(1, "Can't find service \"spamd-sync\" in /etc/services"); 398 sync_port = ntohs(ent->s_port); 399 400 while ((ch = getopt(argc, argv, "DIi:l:W:Y:")) != -1) { 401 switch (ch) { 402 case 'D': 403 flag_debug = 1; 404 break; 405 case 'I': 406 flag_inbound = 1; 407 break; 408 case 'i': 409 networkif = optarg; 410 break; 411 case 'l': 412 pflogif = optarg; 413 break; 414 case 'W': 415 /* limit whiteexp to 2160 hours (90 days) */ 416 whiteexp = strtonum(optarg, 1, (24 * 90), &errstr); 417 if (errstr) 418 usage(); 419 /* convert to seconds from hours */ 420 whiteexp *= (60 * 60); 421 break; 422 case 'Y': 423 if (sync_addhost(optarg, sync_port) != 0) 424 sync_iface = optarg; 425 syncsend++; 426 break; 427 default: 428 usage(); 429 } 430 } 431 432 signal(SIGINT , sighandler_close); 433 signal(SIGQUIT, sighandler_close); 434 signal(SIGTERM, sighandler_close); 435 436 logmsg(LOG_DEBUG, "Listening on %s for %s %s", pflogif, 437 (networkif == NULL) ? "all interfaces." : networkif, 438 (flag_inbound) ? "Inbound direction only." : ""); 439 440 if (init_pcap() == -1) 441 err(1, "couldn't initialize pcap"); 442 443 if (syncsend) { 444 syncfd = sync_init(sync_iface, sync_baddr, sync_port); 445 if (syncfd == -1) 446 err(1, "sync init"); 447 } 448 449 /* privdrop */ 450 if ((pw = getpwnam(SPAMD_USER)) == NULL) 451 errx(1, "no such user %s", SPAMD_USER); 452 453 if (setgroups(1, &pw->pw_gid) || 454 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 455 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 456 err(1, "failed to drop privs"); 457 458 if (!flag_debug) { 459 if (daemon(0, 0) == -1) 460 err(1, "daemon"); 461 tzset(); 462 openlog_r("spamlogd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata); 463 } 464 465 if (unveil(PATH_SPAMD_DB, "rw") == -1) 466 err(1, "unveil"); 467 if (syncsend) { 468 if (pledge("stdio rpath wpath inet flock", NULL) == -1) 469 err(1, "pledge"); 470 } else { 471 if (pledge("stdio rpath wpath flock", NULL) == -1) 472 err(1, "pledge"); 473 } 474 475 pcap_loop(hpcap, -1, phandler, NULL); 476 477 logmsg(LOG_NOTICE, "exiting"); 478 if (!flag_debug) 479 closelog_r(&sdata); 480 481 exit(0); 482 } 483