1 /* $OpenBSD: spamlogd.c,v 1.19 2007/03/05 14:55:09 beck 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 extern char *__progname; 77 78 void logmsg(int , const char *, ...); 79 void sighandler_close(int); 80 int init_pcap(void); 81 void logpkt_handler(u_char *, const struct pcap_pkthdr *, const u_char *); 82 int dbupdate(char *, char *); 83 void usage(void); 84 85 void 86 logmsg(int pri, const char *msg, ...) 87 { 88 va_list ap; 89 va_start(ap, msg); 90 91 if (flag_debug) { 92 vfprintf(stderr, msg, ap); 93 fprintf(stderr, "\n"); 94 } else 95 vsyslog_r(pri, &sdata, msg, ap); 96 97 va_end(ap); 98 } 99 100 /* ARGSUSED */ 101 void 102 sighandler_close(int signal) 103 { 104 if (hpcap != NULL) 105 pcap_breakloop(hpcap); /* sighdlr safe */ 106 } 107 108 int 109 init_pcap(void) 110 { 111 struct bpf_program bpfp; 112 char filter[PCAPFSIZ] = "ip and port 25 and action pass " 113 "and tcp[13]&0x12=0x2"; 114 115 if ((hpcap = pcap_open_live(pflogif, PCAPSNAP, 1, PCAPTIMO, 116 errbuf)) == NULL) { 117 logmsg(LOG_ERR, "Failed to initialize: %s", errbuf); 118 return (-1); 119 } 120 121 if (pcap_datalink(hpcap) != DLT_PFLOG) { 122 logmsg(LOG_ERR, "Invalid datalink type"); 123 pcap_close(hpcap); 124 hpcap = NULL; 125 return (-1); 126 } 127 128 if (networkif != NULL) { 129 strlcat(filter, " and on ", PCAPFSIZ); 130 strlcat(filter, networkif, PCAPFSIZ); 131 } 132 133 if (pcap_compile(hpcap, &bpfp, filter, PCAPOPTZ, 0) == -1 || 134 pcap_setfilter(hpcap, &bpfp) == -1) { 135 logmsg(LOG_ERR, "%s", pcap_geterr(hpcap)); 136 return (-1); 137 } 138 139 pcap_freecode(&bpfp); 140 141 if (ioctl(pcap_fileno(hpcap), BIOCLOCK) < 0) { 142 logmsg(LOG_ERR, "BIOCLOCK: %s", strerror(errno)); 143 return (-1); 144 } 145 146 return (0); 147 } 148 149 /* ARGSUSED */ 150 void 151 logpkt_handler(u_char *user, const struct pcap_pkthdr *h, const u_char *sp) 152 { 153 sa_family_t af; 154 u_int8_t hdrlen; 155 u_int32_t caplen = h->caplen; 156 const struct ip *ip = NULL; 157 const struct pfloghdr *hdr; 158 char ipstraddr[40] = { '\0' }; 159 160 hdr = (const struct pfloghdr *)sp; 161 if (hdr->length < MIN_PFLOG_HDRLEN) { 162 logmsg(LOG_WARNING, "invalid pflog header length (%u/%u). " 163 "packet dropped.", hdr->length, MIN_PFLOG_HDRLEN); 164 return; 165 } 166 hdrlen = BPF_WORDALIGN(hdr->length); 167 168 if (caplen < hdrlen) { 169 logmsg(LOG_WARNING, "pflog header larger than caplen (%u/%u). " 170 "packet dropped.", hdrlen, caplen); 171 return; 172 } 173 174 /* We're interested in passed packets */ 175 if (hdr->action != PF_PASS) 176 return; 177 178 af = hdr->af; 179 if (af == AF_INET) { 180 ip = (const struct ip *)(sp + hdrlen); 181 if (hdr->dir == PF_IN) 182 inet_ntop(af, &ip->ip_src, ipstraddr, 183 sizeof(ipstraddr)); 184 else if (hdr->dir == PF_OUT && !flag_inbound) 185 inet_ntop(af, &ip->ip_dst, ipstraddr, 186 sizeof(ipstraddr)); 187 } 188 189 if (ipstraddr[0] != '\0') { 190 if (hdr->dir == PF_IN) 191 logmsg(LOG_DEBUG,"inbound %s", ipstraddr); 192 else 193 logmsg(LOG_DEBUG,"outbound %s", ipstraddr); 194 dbupdate(PATH_SPAMD_DB, ipstraddr); 195 } 196 } 197 198 int 199 dbupdate(char *dbname, char *ip) 200 { 201 HASHINFO hashinfo; 202 DBT dbk, dbd; 203 DB *db; 204 struct gdata gd; 205 time_t now; 206 int r; 207 struct in_addr ia; 208 209 now = time(NULL); 210 memset(&hashinfo, 0, sizeof(hashinfo)); 211 db = dbopen(dbname, O_EXLOCK|O_RDWR, 0600, DB_HASH, &hashinfo); 212 if (db == NULL) { 213 logmsg(LOG_ERR, "Can not open db %s: %s", dbname, 214 strerror(errno)); 215 return (-1); 216 } 217 if (inet_pton(AF_INET, ip, &ia) != 1) { 218 logmsg(LOG_NOTICE, "Invalid IP address %s", ip); 219 goto bad; 220 } 221 memset(&dbk, 0, sizeof(dbk)); 222 dbk.size = strlen(ip); 223 dbk.data = ip; 224 memset(&dbd, 0, sizeof(dbd)); 225 226 /* add or update whitelist entry */ 227 r = db->get(db, &dbk, &dbd, 0); 228 if (r == -1) { 229 logmsg(LOG_NOTICE, "db->get failed (%m)"); 230 goto bad; 231 } 232 233 if (r) { 234 /* new entry */ 235 memset(&gd, 0, sizeof(gd)); 236 gd.first = now; 237 gd.bcount = 1; 238 gd.pass = now; 239 gd.expire = now + WHITEEXP; 240 memset(&dbk, 0, sizeof(dbk)); 241 dbk.size = strlen(ip); 242 dbk.data = ip; 243 memset(&dbd, 0, sizeof(dbd)); 244 dbd.size = sizeof(gd); 245 dbd.data = &gd; 246 r = db->put(db, &dbk, &dbd, 0); 247 if (r) { 248 logmsg(LOG_NOTICE, "db->put failed (%m)"); 249 goto bad; 250 } 251 } else { 252 if (dbd.size != sizeof(gd)) { 253 /* whatever this is, it doesn't belong */ 254 db->del(db, &dbk, 0); 255 goto bad; 256 } 257 memcpy(&gd, dbd.data, sizeof(gd)); 258 gd.pcount++; 259 gd.expire = now + WHITEEXP; 260 memset(&dbk, 0, sizeof(dbk)); 261 dbk.size = strlen(ip); 262 dbk.data = ip; 263 memset(&dbd, 0, sizeof(dbd)); 264 dbd.size = sizeof(gd); 265 dbd.data = &gd; 266 r = db->put(db, &dbk, &dbd, 0); 267 if (r) { 268 logmsg(LOG_NOTICE, "db->put failed (%m)"); 269 goto bad; 270 } 271 } 272 db->close(db); 273 db = NULL; 274 if (syncsend) 275 sync_white(now, now + WHITEEXP, ip); 276 return (0); 277 bad: 278 db->close(db); 279 db = NULL; 280 return (-1); 281 } 282 283 void 284 usage(void) 285 { 286 fprintf(stderr, 287 "usage: %s [-DI] [-i interface] [-l pflog_interface] [-Y synctarget]\n", 288 __progname); 289 exit(1); 290 } 291 292 int 293 main(int argc, char **argv) 294 { 295 int ch; 296 struct passwd *pw; 297 pcap_handler phandler = logpkt_handler; 298 int syncfd = 0; 299 struct servent *ent; 300 char *sync_iface = NULL; 301 char *sync_baddr = NULL; 302 303 if ((ent = getservbyname("spamd-sync", "udp")) == NULL) 304 errx(1, "Can't find service \"spamd-sync\" in /etc/services"); 305 sync_port = ntohs(ent->s_port); 306 307 while ((ch = getopt(argc, argv, "DIi:l:Y:")) != -1) { 308 switch (ch) { 309 case 'D': 310 flag_debug = 1; 311 break; 312 case 'I': 313 flag_inbound = 1; 314 break; 315 case 'i': 316 networkif = optarg; 317 break; 318 case 'l': 319 pflogif = optarg; 320 break; 321 case 'Y': 322 if (sync_addhost(optarg, sync_port) != 0) 323 sync_iface = optarg; 324 syncsend++; 325 break; 326 default: 327 usage(); 328 /* NOTREACHED */ 329 } 330 } 331 332 signal(SIGINT , sighandler_close); 333 signal(SIGQUIT, sighandler_close); 334 signal(SIGTERM, sighandler_close); 335 336 logmsg(LOG_DEBUG, "Listening on %s for %s %s", pflogif, 337 (networkif == NULL) ? "all interfaces." : networkif, 338 (flag_inbound) ? "Inbound direction only." : ""); 339 340 if (init_pcap() == -1) 341 err(1, "couldn't initialize pcap"); 342 343 if (syncsend) { 344 syncfd = sync_init(sync_iface, sync_baddr, sync_port); 345 if (syncfd == -1) 346 err(1, "sync init"); 347 } 348 349 /* privdrop */ 350 pw = getpwnam("_spamd"); 351 if (pw == NULL) 352 errx(1, "User '_spamd' not found! "); 353 354 if (setgroups(1, &pw->pw_gid) || 355 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 356 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 357 err(1, "failed to drop privs"); 358 359 if (!flag_debug) { 360 if (daemon(0, 0) == -1) 361 err(1, "daemon"); 362 tzset(); 363 openlog_r("spamlogd", LOG_PID | LOG_NDELAY, LOG_DAEMON, &sdata); 364 } 365 366 pcap_loop(hpcap, -1, phandler, NULL); 367 368 logmsg(LOG_NOTICE, "exiting"); 369 if (!flag_debug) 370 closelog_r(&sdata); 371 372 exit(0); 373 } 374