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", NULL) == -1) 380 err(1, "pledge"); 381 } else { 382 if (pledge("stdio rpath wpath flock", 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 } 394