| File: | src/libexec/spamd/grey.c |
| Warning: | line 558, column 3 Null pointer passed as 1st argument to memory copy function |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* $OpenBSD: grey.c,v 1.66 2018/10/25 06:42:35 mestre Exp $ */ | |||
| 2 | ||||
| 3 | /* | |||
| 4 | * Copyright (c) 2004-2006 Bob Beck. All rights reserved. | |||
| 5 | * | |||
| 6 | * Permission to use, copy, modify, and distribute this software for any | |||
| 7 | * purpose with or without fee is hereby granted, provided that the above | |||
| 8 | * copyright notice and this permission notice appear in all copies. | |||
| 9 | * | |||
| 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |||
| 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |||
| 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |||
| 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |||
| 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |||
| 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |||
| 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |||
| 17 | */ | |||
| 18 | ||||
| 19 | #include <sys/types.h> | |||
| 20 | #include <sys/socket.h> | |||
| 21 | #include <sys/ioctl.h> | |||
| 22 | #include <sys/wait.h> | |||
| 23 | #include <net/if.h> | |||
| 24 | #include <netinet/in.h> | |||
| 25 | #include <net/pfvar.h> | |||
| 26 | #include <ctype.h> | |||
| 27 | #include <db.h> | |||
| 28 | #include <errno(*__errno()).h> | |||
| 29 | #include <fcntl.h> | |||
| 30 | #include <pwd.h> | |||
| 31 | #include <signal.h> | |||
| 32 | #include <stdio.h> | |||
| 33 | #include <stdlib.h> | |||
| 34 | #include <string.h> | |||
| 35 | #include <syslog.h> | |||
| 36 | #include <time.h> | |||
| 37 | #include <unistd.h> | |||
| 38 | #include <netdb.h> | |||
| 39 | ||||
| 40 | #include "grey.h" | |||
| 41 | #include "sync.h" | |||
| 42 | ||||
| 43 | extern time_t passtime, greyexp, whiteexp, trapexp; | |||
| 44 | extern struct syslog_data sdata; | |||
| 45 | extern struct passwd *pw; | |||
| 46 | extern u_short cfg_port; | |||
| 47 | extern pid_t jail_pid; | |||
| 48 | extern FILE *trapcfg; | |||
| 49 | extern FILE *grey; | |||
| 50 | extern int debug; | |||
| 51 | extern int syncsend; | |||
| 52 | extern int greyback[2]; | |||
| 53 | ||||
| 54 | /* From netinet/in.h, but only _KERNEL_ gets them. */ | |||
| 55 | #define satosin(sa)((struct sockaddr_in *)(sa)) ((struct sockaddr_in *)(sa)) | |||
| 56 | #define satosin6(sa)((struct sockaddr_in6 *)(sa)) ((struct sockaddr_in6 *)(sa)) | |||
| 57 | ||||
| 58 | void configure_spamd(char **, u_int, FILE *); | |||
| 59 | int configure_pf(char **, int); | |||
| 60 | char *dequotetolower(const char *); | |||
| 61 | void readsuffixlists(void); | |||
| 62 | void freeaddrlists(void); | |||
| 63 | int addwhiteaddr(char *); | |||
| 64 | int addtrapaddr(char *); | |||
| 65 | int db_addrstate(DB *, char *); | |||
| 66 | int greyscan(char *); | |||
| 67 | int trapcheck(DB *, char *); | |||
| 68 | int twupdate(char *, char *, char *, char *, char *); | |||
| 69 | int twread(char *); | |||
| 70 | int greyreader(void); | |||
| 71 | void greyscanner(void); | |||
| 72 | ||||
| 73 | ||||
| 74 | u_int whitecount, whitealloc; | |||
| 75 | u_int trapcount, trapalloc; | |||
| 76 | char **whitelist; | |||
| 77 | char **traplist; | |||
| 78 | ||||
| 79 | char *traplist_name = "spamd-greytrap"; | |||
| 80 | char *traplist_msg = "\"Your address %A has mailed to spamtraps here\\n\""; | |||
| 81 | ||||
| 82 | pid_t db_pid = -1; | |||
| 83 | int pfdev; | |||
| 84 | ||||
| 85 | struct db_change { | |||
| 86 | SLIST_ENTRY(db_change)struct { struct db_change *sle_next; } entry; | |||
| 87 | char * key; | |||
| 88 | void * data; | |||
| 89 | size_t dsiz; | |||
| 90 | int act; | |||
| 91 | }; | |||
| 92 | ||||
| 93 | #define DBC_ADD1 1 | |||
| 94 | #define DBC_DEL2 2 | |||
| 95 | ||||
| 96 | /* db pending changes list */ | |||
| 97 | SLIST_HEAD(, db_change)struct { struct db_change *slh_first; } db_changes = SLIST_HEAD_INITIALIZER(db_changes){ ((void *)0) }; | |||
| 98 | ||||
| 99 | struct mail_addr { | |||
| 100 | SLIST_ENTRY(mail_addr)struct { struct mail_addr *sle_next; } entry; | |||
| 101 | char addr[MAX_MAIL1024]; | |||
| 102 | }; | |||
| 103 | ||||
| 104 | /* list of suffixes that must match TO: */ | |||
| 105 | SLIST_HEAD(, mail_addr)struct { struct mail_addr *slh_first; } match_suffix = SLIST_HEAD_INITIALIZER(match_suffix){ ((void *)0) }; | |||
| 106 | char *alloweddomains_file = PATH_SPAMD_ALLOWEDDOMAINS"/etc/mail/spamd.alloweddomains"; | |||
| 107 | ||||
| 108 | char *low_prio_mx_ip; | |||
| 109 | time_t startup; | |||
| 110 | ||||
| 111 | static char *pargv[11]= { | |||
| 112 | "pfctl", "-p", "/dev/pf", "-q", "-t", | |||
| 113 | "spamd-white", "-T", "replace", "-f", "-", NULL((void *)0) | |||
| 114 | }; | |||
| 115 | ||||
| 116 | /* If the parent gets a signal, kill off the children and exit */ | |||
| 117 | /* ARGSUSED */ | |||
| 118 | static void | |||
| 119 | sig_term_chld(int sig) | |||
| 120 | { | |||
| 121 | if (db_pid != -1) | |||
| 122 | kill(db_pid, SIGTERM15); | |||
| 123 | if (jail_pid != -1) | |||
| 124 | kill(jail_pid, SIGTERM15); | |||
| 125 | _exit(1); | |||
| 126 | } | |||
| 127 | ||||
| 128 | /* | |||
| 129 | * Greatly simplified version from spamd_setup.c - only | |||
| 130 | * sends one blacklist to an already open stream. Has no need | |||
| 131 | * to collapse cidr ranges since these are only ever single | |||
| 132 | * host hits. | |||
| 133 | */ | |||
| 134 | void | |||
| 135 | configure_spamd(char **addrs, u_int count, FILE *sdc) | |||
| 136 | { | |||
| 137 | u_int i; | |||
| 138 | ||||
| 139 | /* XXX - doesn't support IPV6 yet */ | |||
| 140 | fprintf(sdc, "%s;", traplist_name); | |||
| 141 | if (count != 0) { | |||
| 142 | fprintf(sdc, "%s;inet;%u", traplist_msg, count); | |||
| 143 | for (i = 0; i < count; i++) | |||
| 144 | fprintf(sdc, ";%s/32", addrs[i]); | |||
| 145 | } | |||
| 146 | fputc('\n', sdc); | |||
| 147 | if (fflush(sdc) == EOF(-1)) | |||
| 148 | syslog_r(LOG_DEBUG7, &sdata, "configure_spamd: fflush failed (%m)"); | |||
| 149 | } | |||
| 150 | ||||
| 151 | int | |||
| 152 | configure_pf(char **addrs, int count) | |||
| 153 | { | |||
| 154 | FILE *pf = NULL((void *)0); | |||
| 155 | int i, pdes[2], status; | |||
| 156 | pid_t pid; | |||
| 157 | char *fdpath; | |||
| 158 | struct sigaction sa; | |||
| 159 | ||||
| 160 | sigfillset(&sa.sa_mask); | |||
| 161 | sa.sa_flags = SA_RESTART0x0002; | |||
| 162 | sa.sa_handler__sigaction_u.__sa_handler = sig_term_chld; | |||
| 163 | ||||
| 164 | if (debug) | |||
| 165 | fprintf(stderr(&__sF[2]), "configure_pf - device on fd %d\n", pfdev); | |||
| 166 | ||||
| 167 | /* Because /dev/fd/ only contains device nodes for 0-63 */ | |||
| 168 | if (pfdev < 1 || pfdev > 63) | |||
| 169 | return(-1); | |||
| 170 | ||||
| 171 | if (asprintf(&fdpath, "/dev/fd/%d", pfdev) == -1) | |||
| 172 | return(-1); | |||
| 173 | pargv[2] = fdpath; | |||
| 174 | if (pipe(pdes) != 0) { | |||
| 175 | syslog_r(LOG_INFO6, &sdata, "pipe failed (%m)"); | |||
| 176 | free(fdpath); | |||
| 177 | fdpath = NULL((void *)0); | |||
| 178 | return(-1); | |||
| 179 | } | |||
| 180 | signal(SIGCHLD20, SIG_DFL(void (*)(int))0); | |||
| 181 | switch (pid = fork()) { | |||
| 182 | case -1: | |||
| 183 | syslog_r(LOG_INFO6, &sdata, "fork failed (%m)"); | |||
| 184 | free(fdpath); | |||
| 185 | fdpath = NULL((void *)0); | |||
| 186 | close(pdes[0]); | |||
| 187 | close(pdes[1]); | |||
| 188 | sigaction(SIGCHLD20, &sa, NULL((void *)0)); | |||
| 189 | return(-1); | |||
| 190 | case 0: | |||
| 191 | /* child */ | |||
| 192 | close(pdes[1]); | |||
| 193 | if (pdes[0] != STDIN_FILENO0) { | |||
| 194 | dup2(pdes[0], STDIN_FILENO0); | |||
| 195 | close(pdes[0]); | |||
| 196 | } | |||
| 197 | execvp(PATH_PFCTL"/sbin/pfctl", pargv); | |||
| 198 | syslog_r(LOG_ERR3, &sdata, "can't exec %s:%m", PATH_PFCTL"/sbin/pfctl"); | |||
| 199 | _exit(1); | |||
| 200 | } | |||
| 201 | ||||
| 202 | /* parent */ | |||
| 203 | free(fdpath); | |||
| 204 | fdpath = NULL((void *)0); | |||
| 205 | close(pdes[0]); | |||
| 206 | pf = fdopen(pdes[1], "w"); | |||
| 207 | if (pf == NULL((void *)0)) { | |||
| 208 | syslog_r(LOG_INFO6, &sdata, "fdopen failed (%m)"); | |||
| 209 | close(pdes[1]); | |||
| 210 | sigaction(SIGCHLD20, &sa, NULL((void *)0)); | |||
| 211 | return(-1); | |||
| 212 | } | |||
| 213 | for (i = 0; i < count; i++) | |||
| 214 | if (addrs[i] != NULL((void *)0)) | |||
| 215 | fprintf(pf, "%s/32\n", addrs[i]); | |||
| 216 | fclose(pf); | |||
| 217 | ||||
| 218 | waitpid(pid, &status, 0); | |||
| 219 | if (WIFEXITED(status)(((status) & 0177) == 0) && WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff) != 0) | |||
| 220 | syslog_r(LOG_ERR3, &sdata, "%s returned status %d", PATH_PFCTL"/sbin/pfctl", | |||
| 221 | WEXITSTATUS(status)(int)(((unsigned)(status) >> 8) & 0xff)); | |||
| 222 | else if (WIFSIGNALED(status)(((status) & 0177) != 0177 && ((status) & 0177 ) != 0)) | |||
| 223 | syslog_r(LOG_ERR3, &sdata, "%s died on signal %d", PATH_PFCTL"/sbin/pfctl", | |||
| 224 | WTERMSIG(status)(((status) & 0177))); | |||
| 225 | ||||
| 226 | sigaction(SIGCHLD20, &sa, NULL((void *)0)); | |||
| 227 | return(0); | |||
| 228 | } | |||
| 229 | ||||
| 230 | char * | |||
| 231 | dequotetolower(const char *addr) | |||
| 232 | { | |||
| 233 | static char buf[MAX_MAIL1024]; | |||
| 234 | char *cp; | |||
| 235 | ||||
| 236 | if (*addr == '<') | |||
| 237 | addr++; | |||
| 238 | (void) strlcpy(buf, addr, sizeof(buf)); | |||
| 239 | cp = strrchr(buf, '>'); | |||
| 240 | if (cp != NULL((void *)0) && cp[1] == '\0') | |||
| 241 | *cp = '\0'; | |||
| 242 | cp = buf; | |||
| 243 | while (*cp != '\0') { | |||
| 244 | *cp = tolower((unsigned char)*cp); | |||
| 245 | cp++; | |||
| 246 | } | |||
| 247 | return(buf); | |||
| 248 | } | |||
| 249 | ||||
| 250 | void | |||
| 251 | readsuffixlists(void) | |||
| 252 | { | |||
| 253 | FILE *fp; | |||
| 254 | char *buf; | |||
| 255 | size_t len; | |||
| 256 | struct mail_addr *m; | |||
| 257 | ||||
| 258 | while (!SLIST_EMPTY(&match_suffix)(((&match_suffix)->slh_first) == ((void *)0))) { | |||
| 259 | m = SLIST_FIRST(&match_suffix)((&match_suffix)->slh_first); | |||
| 260 | SLIST_REMOVE_HEAD(&match_suffix, entry)do { (&match_suffix)->slh_first = (&match_suffix)-> slh_first->entry.sle_next; } while (0); | |||
| 261 | free(m); | |||
| 262 | } | |||
| 263 | if ((fp = fopen(alloweddomains_file, "r")) != NULL((void *)0)) { | |||
| 264 | while ((buf = fgetln(fp, &len))) { | |||
| 265 | /* strip white space-characters */ | |||
| 266 | while (len > 0 && isspace((unsigned char)buf[len-1])) | |||
| 267 | len--; | |||
| 268 | while (len > 0 && isspace((unsigned char)*buf)) { | |||
| 269 | buf++; | |||
| 270 | len--; | |||
| 271 | } | |||
| 272 | if (len == 0) | |||
| 273 | continue; | |||
| 274 | /* jump over comments and blank lines */ | |||
| 275 | if (*buf == '#' || *buf == '\n') | |||
| 276 | continue; | |||
| 277 | if (buf[len-1] == '\n') | |||
| 278 | len--; | |||
| 279 | if ((len + 1) > sizeof(m->addr)) { | |||
| 280 | syslog_r(LOG_ERR3, &sdata, | |||
| 281 | "line too long in %s - file ignored", | |||
| 282 | alloweddomains_file); | |||
| 283 | goto bad; | |||
| 284 | } | |||
| 285 | if ((m = malloc(sizeof(struct mail_addr))) == NULL((void *)0)) | |||
| 286 | goto bad; | |||
| 287 | memcpy(m->addr, buf, len); | |||
| 288 | m->addr[len]='\0'; | |||
| 289 | syslog_r(LOG_ERR3, &sdata, "got suffix %s", m->addr); | |||
| 290 | SLIST_INSERT_HEAD(&match_suffix, m, entry)do { (m)->entry.sle_next = (&match_suffix)->slh_first ; (&match_suffix)->slh_first = (m); } while (0); | |||
| 291 | } | |||
| 292 | } | |||
| 293 | return; | |||
| 294 | bad: | |||
| 295 | while (!SLIST_EMPTY(&match_suffix)(((&match_suffix)->slh_first) == ((void *)0))) { | |||
| 296 | m = SLIST_FIRST(&match_suffix)((&match_suffix)->slh_first); | |||
| 297 | SLIST_REMOVE_HEAD(&match_suffix, entry)do { (&match_suffix)->slh_first = (&match_suffix)-> slh_first->entry.sle_next; } while (0); | |||
| 298 | free(m); | |||
| 299 | } | |||
| 300 | } | |||
| 301 | ||||
| 302 | void | |||
| 303 | freeaddrlists(void) | |||
| 304 | { | |||
| 305 | int i; | |||
| 306 | ||||
| 307 | if (whitelist != NULL((void *)0)) | |||
| 308 | for (i = 0; i < whitecount; i++) { | |||
| 309 | free(whitelist[i]); | |||
| 310 | whitelist[i] = NULL((void *)0); | |||
| 311 | } | |||
| 312 | whitecount = 0; | |||
| 313 | if (traplist != NULL((void *)0)) { | |||
| 314 | for (i = 0; i < trapcount; i++) { | |||
| 315 | free(traplist[i]); | |||
| 316 | traplist[i] = NULL((void *)0); | |||
| 317 | } | |||
| 318 | } | |||
| 319 | trapcount = 0; | |||
| 320 | } | |||
| 321 | ||||
| 322 | /* validate, then add to list of addrs to whitelist */ | |||
| 323 | int | |||
| 324 | addwhiteaddr(char *addr) | |||
| 325 | { | |||
| 326 | struct addrinfo hints, *res; | |||
| 327 | char ch; | |||
| 328 | ||||
| 329 | memset(&hints, 0, sizeof(hints)); | |||
| 330 | hints.ai_family = AF_INET2; /*for now*/ | |||
| 331 | hints.ai_socktype = SOCK_DGRAM2; /*dummy*/ | |||
| 332 | hints.ai_protocol = IPPROTO_UDP17; /*dummy*/ | |||
| 333 | hints.ai_flags = AI_NUMERICHOST4; | |||
| 334 | ||||
| 335 | if (getaddrinfo(addr, NULL((void *)0), &hints, &res) != 0) | |||
| 336 | return(-1); | |||
| 337 | ||||
| 338 | /* Check spamd blacklists in main process. */ | |||
| 339 | if (send(greyback[0], res->ai_addr, res->ai_addr->sa_len, 0) == -1) { | |||
| 340 | syslog_r(LOG_ERR3, &sdata, "%s: send: %m", __func__); | |||
| 341 | } else { | |||
| 342 | if (recv(greyback[0], &ch, sizeof(ch), 0) == 1) { | |||
| 343 | if (ch == '1') { | |||
| 344 | syslog_r(LOG_DEBUG7, &sdata, | |||
| 345 | "%s blacklisted, removing from whitelist", | |||
| 346 | addr); | |||
| 347 | freeaddrinfo(res); | |||
| 348 | return(-1); | |||
| 349 | } | |||
| 350 | } | |||
| 351 | } | |||
| 352 | ||||
| 353 | if (whitecount == whitealloc) { | |||
| 354 | char **tmp; | |||
| 355 | ||||
| 356 | tmp = reallocarray(whitelist, | |||
| 357 | whitealloc + 1024, sizeof(char *)); | |||
| 358 | if (tmp == NULL((void *)0)) { | |||
| 359 | freeaddrinfo(res); | |||
| 360 | return(-1); | |||
| 361 | } | |||
| 362 | whitelist = tmp; | |||
| 363 | whitealloc += 1024; | |||
| 364 | } | |||
| 365 | whitelist[whitecount] = strdup(addr); | |||
| 366 | if (whitelist[whitecount] == NULL((void *)0)) { | |||
| 367 | freeaddrinfo(res); | |||
| 368 | return(-1); | |||
| 369 | } | |||
| 370 | whitecount++; | |||
| 371 | freeaddrinfo(res); | |||
| 372 | return(0); | |||
| 373 | } | |||
| 374 | ||||
| 375 | /* validate, then add to list of addrs to traplist */ | |||
| 376 | int | |||
| 377 | addtrapaddr(char *addr) | |||
| 378 | { | |||
| 379 | struct addrinfo hints, *res; | |||
| 380 | ||||
| 381 | memset(&hints, 0, sizeof(hints)); | |||
| 382 | hints.ai_family = AF_INET2; /*for now*/ | |||
| 383 | hints.ai_socktype = SOCK_DGRAM2; /*dummy*/ | |||
| 384 | hints.ai_protocol = IPPROTO_UDP17; /*dummy*/ | |||
| 385 | hints.ai_flags = AI_NUMERICHOST4; | |||
| 386 | ||||
| 387 | if (getaddrinfo(addr, NULL((void *)0), &hints, &res) == 0) { | |||
| 388 | if (trapcount == trapalloc) { | |||
| 389 | char **tmp; | |||
| 390 | ||||
| 391 | tmp = reallocarray(traplist, | |||
| 392 | trapalloc + 1024, sizeof(char *)); | |||
| 393 | if (tmp == NULL((void *)0)) { | |||
| 394 | freeaddrinfo(res); | |||
| 395 | return(-1); | |||
| 396 | } | |||
| 397 | traplist = tmp; | |||
| 398 | trapalloc += 1024; | |||
| 399 | } | |||
| 400 | traplist[trapcount] = strdup(addr); | |||
| 401 | if (traplist[trapcount] == NULL((void *)0)) { | |||
| 402 | freeaddrinfo(res); | |||
| 403 | return(-1); | |||
| 404 | } | |||
| 405 | trapcount++; | |||
| 406 | freeaddrinfo(res); | |||
| 407 | } else | |||
| 408 | return(-1); | |||
| 409 | return(0); | |||
| 410 | } | |||
| 411 | ||||
| 412 | static int | |||
| 413 | queue_change(char *key, char *data, size_t dsiz, int act) | |||
| 414 | { | |||
| 415 | struct db_change *dbc; | |||
| 416 | ||||
| 417 | if ((dbc = malloc(sizeof(*dbc))) == NULL((void *)0)) { | |||
| 418 | syslog_r(LOG_DEBUG7, &sdata, "malloc failed (queue change)"); | |||
| 419 | return(-1); | |||
| 420 | } | |||
| 421 | if ((dbc->key = strdup(key)) == NULL((void *)0)) { | |||
| 422 | syslog_r(LOG_DEBUG7, &sdata, "malloc failed (queue change)"); | |||
| 423 | free(dbc); | |||
| 424 | return(-1); | |||
| 425 | } | |||
| 426 | if ((dbc->data = malloc(dsiz)) == NULL((void *)0)) { | |||
| 427 | syslog_r(LOG_DEBUG7, &sdata, "malloc failed (queue change)"); | |||
| 428 | free(dbc->key); | |||
| 429 | free(dbc); | |||
| 430 | return(-1); | |||
| 431 | } | |||
| 432 | memcpy(dbc->data, data, dsiz); | |||
| 433 | dbc->dsiz = dsiz; | |||
| 434 | dbc->act = act; | |||
| 435 | syslog_r(LOG_DEBUG7, &sdata, | |||
| 436 | "queueing %s of %s", ((act == DBC_ADD1) ? "add" : "deletion"), | |||
| 437 | dbc->key); | |||
| 438 | SLIST_INSERT_HEAD(&db_changes, dbc, entry)do { (dbc)->entry.sle_next = (&db_changes)->slh_first ; (&db_changes)->slh_first = (dbc); } while (0); | |||
| 439 | return(0); | |||
| 440 | } | |||
| 441 | ||||
| 442 | static int | |||
| 443 | do_changes(DB *db) | |||
| 444 | { | |||
| 445 | DBT dbk, dbd; | |||
| 446 | struct db_change *dbc; | |||
| 447 | int ret = 0; | |||
| 448 | ||||
| 449 | while (!SLIST_EMPTY(&db_changes)(((&db_changes)->slh_first) == ((void *)0))) { | |||
| 450 | dbc = SLIST_FIRST(&db_changes)((&db_changes)->slh_first); | |||
| 451 | switch (dbc->act) { | |||
| 452 | case DBC_ADD1: | |||
| 453 | memset(&dbk, 0, sizeof(dbk)); | |||
| 454 | dbk.size = strlen(dbc->key); | |||
| 455 | dbk.data = dbc->key; | |||
| 456 | memset(&dbd, 0, sizeof(dbd)); | |||
| 457 | dbd.size = dbc->dsiz; | |||
| 458 | dbd.data = dbc->data; | |||
| 459 | if (db->put(db, &dbk, &dbd, 0)) { | |||
| 460 | db->sync(db, 0); | |||
| 461 | syslog_r(LOG_ERR3, &sdata, | |||
| 462 | "can't add %s to spamd db (%m)", dbc->key); | |||
| 463 | ret = -1; | |||
| 464 | } | |||
| 465 | db->sync(db, 0); | |||
| 466 | break; | |||
| 467 | case DBC_DEL2: | |||
| 468 | memset(&dbk, 0, sizeof(dbk)); | |||
| 469 | dbk.size = strlen(dbc->key); | |||
| 470 | dbk.data = dbc->key; | |||
| 471 | if (db->del(db, &dbk, 0)) { | |||
| 472 | syslog_r(LOG_ERR3, &sdata, | |||
| 473 | "can't delete %s from spamd db (%m)", | |||
| 474 | dbc->key); | |||
| 475 | ret = -1; | |||
| 476 | } | |||
| 477 | break; | |||
| 478 | default: | |||
| 479 | syslog_r(LOG_ERR3, &sdata, "Unrecognized db change"); | |||
| 480 | ret = -1; | |||
| 481 | } | |||
| 482 | free(dbc->key); | |||
| 483 | dbc->key = NULL((void *)0); | |||
| 484 | free(dbc->data); | |||
| 485 | dbc->data = NULL((void *)0); | |||
| 486 | dbc->act = 0; | |||
| 487 | dbc->dsiz = 0; | |||
| 488 | SLIST_REMOVE_HEAD(&db_changes, entry)do { (&db_changes)->slh_first = (&db_changes)-> slh_first->entry.sle_next; } while (0); | |||
| 489 | free(dbc); | |||
| 490 | ||||
| 491 | } | |||
| 492 | return(ret); | |||
| 493 | } | |||
| 494 | ||||
| 495 | /* -1=error, 0=notfound, 1=TRAPPED, 2=WHITE */ | |||
| 496 | int | |||
| 497 | db_addrstate(DB *db, char *key) | |||
| 498 | { | |||
| 499 | DBT dbk, dbd; | |||
| 500 | struct gdata gd; | |||
| 501 | ||||
| 502 | memset(&dbk, 0, sizeof(dbk)); | |||
| 503 | dbk.size = strlen(key); | |||
| 504 | dbk.data = key; | |||
| 505 | memset(&dbd, 0, sizeof(dbd)); | |||
| 506 | switch (db->get(db, &dbk, &dbd, 0)) { | |||
| 507 | case 1: | |||
| 508 | /* not found */ | |||
| 509 | return (0); | |||
| 510 | case 0: | |||
| 511 | if (gdcopyin(&dbd, &gd) != -1) | |||
| 512 | return (gd.pcount == -1 ? 1 : 2); | |||
| 513 | /* FALLTHROUGH */ | |||
| 514 | default: | |||
| 515 | /* error */ | |||
| 516 | return (-1); | |||
| 517 | } | |||
| 518 | } | |||
| 519 | ||||
| 520 | ||||
| 521 | int | |||
| 522 | greyscan(char *dbname) | |||
| 523 | { | |||
| 524 | HASHINFO hashinfo; | |||
| 525 | DBT dbk, dbd; | |||
| 526 | DB *db; | |||
| 527 | struct gdata gd; | |||
| 528 | int r; | |||
| 529 | char *a = NULL((void *)0); | |||
| 530 | size_t asiz = 0; | |||
| 531 | time_t now = time(NULL((void *)0)); | |||
| 532 | ||||
| 533 | /* walk db, expire, and whitelist */ | |||
| 534 | memset(&hashinfo, 0, sizeof(hashinfo)); | |||
| 535 | db = dbopen(dbname, O_EXLOCK0x0020|O_RDWR0x0002, 0600, DB_HASH, &hashinfo); | |||
| 536 | if (db == NULL((void *)0)) { | |||
| 537 | syslog_r(LOG_INFO6, &sdata, "dbopen failed (%m)"); | |||
| 538 | return(-1); | |||
| 539 | } | |||
| 540 | memset(&dbk, 0, sizeof(dbk)); | |||
| 541 | memset(&dbd, 0, sizeof(dbd)); | |||
| 542 | for (r = db->seq(db, &dbk, &dbd, R_FIRST3); !r; | |||
| 543 | r = db->seq(db, &dbk, &dbd, R_NEXT7)) { | |||
| 544 | if ((dbk.size < 1) || gdcopyin(&dbd, &gd) == -1) { | |||
| 545 | syslog_r(LOG_ERR3, &sdata, "bogus entry in spamd database"); | |||
| 546 | goto bad; | |||
| 547 | } | |||
| 548 | if (asiz < dbk.size + 1) { | |||
| 549 | char *tmp; | |||
| 550 | ||||
| 551 | tmp = reallocarray(a, dbk.size, 2); | |||
| 552 | if (tmp == NULL((void *)0)) | |||
| 553 | goto bad; | |||
| 554 | a = tmp; | |||
| 555 | asiz = dbk.size * 2; | |||
| 556 | } | |||
| 557 | memset(a, 0, asiz); | |||
| 558 | memcpy(a, dbk.data, dbk.size); | |||
| ||||
| 559 | if (gd.expire <= now && gd.pcount != -2) { | |||
| 560 | /* get rid of entry */ | |||
| 561 | if (queue_change(a, NULL((void *)0), 0, DBC_DEL2) == -1) | |||
| 562 | goto bad; | |||
| 563 | } else if (gd.pcount == -1) { | |||
| 564 | /* this is a greytrap hit */ | |||
| 565 | if ((addtrapaddr(a) == -1) && | |||
| 566 | (queue_change(a, NULL((void *)0), 0, DBC_DEL2) == -1)) | |||
| 567 | goto bad; | |||
| 568 | } else if (gd.pcount >= 0 && gd.pass <= now) { | |||
| 569 | int tuple = 0; | |||
| 570 | char *cp; | |||
| 571 | int state; | |||
| 572 | ||||
| 573 | /* | |||
| 574 | * if not already TRAPPED, | |||
| 575 | * add address to whitelist | |||
| 576 | * add an address-keyed entry to db | |||
| 577 | */ | |||
| 578 | cp = strchr(a, '\n'); | |||
| 579 | if (cp != NULL((void *)0)) { | |||
| 580 | tuple = 1; | |||
| 581 | *cp = '\0'; | |||
| 582 | } | |||
| 583 | ||||
| 584 | state = db_addrstate(db, a); | |||
| 585 | if (state != 1 && addwhiteaddr(a) == -1) { | |||
| 586 | if (cp != NULL((void *)0)) | |||
| 587 | *cp = '\n'; | |||
| 588 | if (queue_change(a, NULL((void *)0), 0, DBC_DEL2) == -1) | |||
| 589 | goto bad; | |||
| 590 | } | |||
| 591 | ||||
| 592 | if (tuple && state <= 0) { | |||
| 593 | if (cp != NULL((void *)0)) | |||
| 594 | *cp = '\0'; | |||
| 595 | /* re-add entry, keyed only by ip */ | |||
| 596 | gd.expire = now + whiteexp; | |||
| 597 | dbd.size = sizeof(gd); | |||
| 598 | dbd.data = &gd; | |||
| 599 | if (queue_change(a, (void *) &gd, sizeof(gd), | |||
| 600 | DBC_ADD1) == -1) | |||
| 601 | goto bad; | |||
| 602 | syslog_r(LOG_DEBUG7, &sdata, | |||
| 603 | "whitelisting %s in %s", a, dbname); | |||
| 604 | } | |||
| 605 | if (debug) | |||
| 606 | fprintf(stderr(&__sF[2]), "whitelisted %s\n", a); | |||
| 607 | } | |||
| 608 | } | |||
| 609 | (void) do_changes(db); | |||
| 610 | db->close(db); | |||
| 611 | db = NULL((void *)0); | |||
| 612 | configure_pf(whitelist, whitecount); | |||
| 613 | configure_spamd(traplist, trapcount, trapcfg); | |||
| 614 | ||||
| 615 | freeaddrlists(); | |||
| 616 | free(a); | |||
| 617 | a = NULL((void *)0); | |||
| 618 | return(0); | |||
| 619 | bad: | |||
| 620 | (void) do_changes(db); | |||
| 621 | db->close(db); | |||
| 622 | db = NULL((void *)0); | |||
| 623 | freeaddrlists(); | |||
| 624 | free(a); | |||
| 625 | a = NULL((void *)0); | |||
| 626 | return(-1); | |||
| 627 | } | |||
| 628 | ||||
| 629 | int | |||
| 630 | trapcheck(DB *db, char *to) | |||
| 631 | { | |||
| 632 | int i, j, smatch = 0; | |||
| 633 | DBT dbk, dbd; | |||
| 634 | struct mail_addr *m; | |||
| 635 | char * trap; | |||
| 636 | size_t s; | |||
| 637 | ||||
| 638 | trap = dequotetolower(to); | |||
| 639 | if (!SLIST_EMPTY(&match_suffix)(((&match_suffix)->slh_first) == ((void *)0))) { | |||
| 640 | s = strlen(trap); | |||
| 641 | SLIST_FOREACH(m, &match_suffix, entry)for((m) = ((&match_suffix)->slh_first); (m) != ((void * )0); (m) = ((m)->entry.sle_next)) { | |||
| 642 | j = s - strlen(m->addr); | |||
| 643 | if ((j >= 0) && (strcasecmp(trap+j, m->addr) == 0)) | |||
| 644 | smatch = 1; | |||
| 645 | } | |||
| 646 | if (!smatch) | |||
| 647 | /* no suffixes match, so trap it */ | |||
| 648 | return (0); | |||
| 649 | } | |||
| 650 | memset(&dbk, 0, sizeof(dbk)); | |||
| 651 | dbk.size = strlen(trap); | |||
| 652 | dbk.data = trap; | |||
| 653 | memset(&dbd, 0, sizeof(dbd)); | |||
| 654 | i = db->get(db, &dbk, &dbd, 0); | |||
| 655 | if (i == -1) | |||
| 656 | return (-1); | |||
| 657 | if (i) | |||
| 658 | /* didn't exist - so this doesn't match a known spamtrap */ | |||
| 659 | return (1); | |||
| 660 | else | |||
| 661 | /* To: address is a spamtrap, so add as a greytrap entry */ | |||
| 662 | return (0); | |||
| 663 | } | |||
| 664 | ||||
| 665 | int | |||
| 666 | twupdate(char *dbname, char *what, char *ip, char *source, char *expires) | |||
| 667 | { | |||
| 668 | /* we got a TRAP or WHITE update from someone else */ | |||
| 669 | HASHINFO hashinfo; | |||
| 670 | DBT dbk, dbd; | |||
| 671 | DB *db; | |||
| 672 | struct gdata gd; | |||
| 673 | time_t now, expire; | |||
| 674 | int r, spamtrap; | |||
| 675 | ||||
| 676 | now = time(NULL((void *)0)); | |||
| 677 | /* expiry times have to be in the future */ | |||
| 678 | expire = strtonum(expires, now, | |||
| 679 | sizeof(time_t) == sizeof(int) ? INT_MAX2147483647 : LLONG_MAX9223372036854775807LL, NULL((void *)0)); | |||
| 680 | if (expire == 0) | |||
| 681 | return(-1); | |||
| 682 | ||||
| 683 | if (strcmp(what, "TRAP") == 0) | |||
| 684 | spamtrap = 1; | |||
| 685 | else if (strcmp(what, "WHITE") == 0) | |||
| 686 | spamtrap = 0; | |||
| 687 | else | |||
| 688 | return(-1); | |||
| 689 | ||||
| 690 | memset(&hashinfo, 0, sizeof(hashinfo)); | |||
| 691 | db = dbopen(dbname, O_EXLOCK0x0020|O_RDWR0x0002, 0600, DB_HASH, &hashinfo); | |||
| 692 | if (db == NULL((void *)0)) | |||
| 693 | return(-1); | |||
| 694 | ||||
| 695 | memset(&dbk, 0, sizeof(dbk)); | |||
| 696 | dbk.size = strlen(ip); | |||
| 697 | dbk.data = ip; | |||
| 698 | memset(&dbd, 0, sizeof(dbd)); | |||
| 699 | r = db->get(db, &dbk, &dbd, 0); | |||
| 700 | if (r == -1) | |||
| 701 | goto bad; | |||
| 702 | if (r) { | |||
| 703 | /* new entry */ | |||
| 704 | memset(&gd, 0, sizeof(gd)); | |||
| 705 | gd.first = now; | |||
| 706 | gd.pcount = spamtrap ? -1 : 0; | |||
| 707 | gd.expire = expire; | |||
| 708 | memset(&dbk, 0, sizeof(dbk)); | |||
| 709 | dbk.size = strlen(ip); | |||
| 710 | dbk.data = ip; | |||
| 711 | memset(&dbd, 0, sizeof(dbd)); | |||
| 712 | dbd.size = sizeof(gd); | |||
| 713 | dbd.data = &gd; | |||
| 714 | r = db->put(db, &dbk, &dbd, 0); | |||
| 715 | db->sync(db, 0); | |||
| 716 | if (r) | |||
| 717 | goto bad; | |||
| 718 | if (debug) | |||
| 719 | fprintf(stderr(&__sF[2]), "added %s %s\n", | |||
| 720 | spamtrap ? "trap entry for" : "", ip); | |||
| 721 | syslog_r(LOG_DEBUG7, &sdata, | |||
| 722 | "new %s from %s for %s, expires %s", what, source, ip, | |||
| 723 | expires); | |||
| 724 | } else { | |||
| 725 | /* existing entry */ | |||
| 726 | if (gdcopyin(&dbd, &gd) == -1) { | |||
| 727 | /* whatever this is, it doesn't belong */ | |||
| 728 | db->del(db, &dbk, 0); | |||
| 729 | db->sync(db, 0); | |||
| 730 | goto bad; | |||
| 731 | } | |||
| 732 | if (spamtrap) { | |||
| 733 | gd.pcount = -1; | |||
| 734 | gd.bcount++; | |||
| 735 | } else | |||
| 736 | gd.pcount++; | |||
| 737 | memset(&dbk, 0, sizeof(dbk)); | |||
| 738 | dbk.size = strlen(ip); | |||
| 739 | dbk.data = ip; | |||
| 740 | memset(&dbd, 0, sizeof(dbd)); | |||
| 741 | dbd.size = sizeof(gd); | |||
| 742 | dbd.data = &gd; | |||
| 743 | r = db->put(db, &dbk, &dbd, 0); | |||
| 744 | db->sync(db, 0); | |||
| 745 | if (r) | |||
| 746 | goto bad; | |||
| 747 | if (debug) | |||
| 748 | fprintf(stderr(&__sF[2]), "updated %s\n", ip); | |||
| 749 | } | |||
| 750 | db->close(db); | |||
| 751 | return(0); | |||
| 752 | bad: | |||
| 753 | db->close(db); | |||
| 754 | return(-1); | |||
| 755 | ||||
| 756 | } | |||
| 757 | ||||
| 758 | int | |||
| 759 | greyupdate(char *dbname, char *helo, char *ip, char *from, char *to, int sync, | |||
| 760 | char *cip) | |||
| 761 | { | |||
| 762 | HASHINFO hashinfo; | |||
| 763 | DBT dbk, dbd; | |||
| 764 | DB *db; | |||
| 765 | char *key = NULL((void *)0); | |||
| 766 | char *lookup; | |||
| 767 | struct gdata gd; | |||
| 768 | time_t now, expire; | |||
| 769 | int r, spamtrap; | |||
| 770 | ||||
| 771 | now = time(NULL((void *)0)); | |||
| 772 | ||||
| 773 | /* open with lock, find record, update, close, unlock */ | |||
| 774 | memset(&hashinfo, 0, sizeof(hashinfo)); | |||
| 775 | db = dbopen(dbname, O_EXLOCK0x0020|O_RDWR0x0002, 0600, DB_HASH, &hashinfo); | |||
| 776 | if (db == NULL((void *)0)) | |||
| 777 | return(-1); | |||
| 778 | if (asprintf(&key, "%s\n%s\n%s\n%s", ip, helo, from, to) == -1) | |||
| 779 | goto bad; | |||
| 780 | r = trapcheck(db, to); | |||
| 781 | switch (r) { | |||
| 782 | case 1: | |||
| 783 | /* do not trap */ | |||
| 784 | spamtrap = 0; | |||
| 785 | lookup = key; | |||
| 786 | expire = greyexp; | |||
| 787 | break; | |||
| 788 | case 0: | |||
| 789 | /* trap */ | |||
| 790 | spamtrap = 1; | |||
| 791 | lookup = ip; | |||
| 792 | expire = trapexp; | |||
| 793 | syslog_r(LOG_DEBUG7, &sdata, "Trapping %s for tuple %s", ip, | |||
| 794 | key); | |||
| 795 | break; | |||
| 796 | default: | |||
| 797 | goto bad; | |||
| 798 | break; | |||
| 799 | } | |||
| 800 | memset(&dbk, 0, sizeof(dbk)); | |||
| 801 | dbk.size = strlen(lookup); | |||
| 802 | dbk.data = lookup; | |||
| 803 | memset(&dbd, 0, sizeof(dbd)); | |||
| 804 | r = db->get(db, &dbk, &dbd, 0); | |||
| 805 | if (r == -1) | |||
| 806 | goto bad; | |||
| 807 | if (r) { | |||
| 808 | /* new entry */ | |||
| 809 | if (sync && low_prio_mx_ip && | |||
| 810 | (strcmp(cip, low_prio_mx_ip) == 0) && | |||
| 811 | ((startup + 60) < now)) { | |||
| 812 | /* we haven't seen a greylist entry for this tuple, | |||
| 813 | * and yet the connection was to a low priority MX | |||
| 814 | * which we know can't be hit first if the client | |||
| 815 | * is adhering to the RFC's - soo.. kill it! | |||
| 816 | */ | |||
| 817 | spamtrap = 1; | |||
| 818 | lookup = ip; | |||
| 819 | expire = trapexp; | |||
| 820 | syslog_r(LOG_DEBUG7, &sdata, | |||
| 821 | "Trapping %s for trying %s first for tuple %s", | |||
| 822 | ip, low_prio_mx_ip, key); | |||
| 823 | } | |||
| 824 | memset(&gd, 0, sizeof(gd)); | |||
| 825 | gd.first = now; | |||
| 826 | gd.bcount = 1; | |||
| 827 | gd.pcount = spamtrap ? -1 : 0; | |||
| 828 | gd.pass = now + expire; | |||
| 829 | gd.expire = now + expire; | |||
| 830 | memset(&dbk, 0, sizeof(dbk)); | |||
| 831 | dbk.size = strlen(lookup); | |||
| 832 | dbk.data = lookup; | |||
| 833 | memset(&dbd, 0, sizeof(dbd)); | |||
| 834 | dbd.size = sizeof(gd); | |||
| 835 | dbd.data = &gd; | |||
| 836 | r = db->put(db, &dbk, &dbd, 0); | |||
| 837 | db->sync(db, 0); | |||
| 838 | if (r) | |||
| 839 | goto bad; | |||
| 840 | if (debug) | |||
| 841 | fprintf(stderr(&__sF[2]), "added %s %s\n", | |||
| 842 | spamtrap ? "greytrap entry for" : "", lookup); | |||
| 843 | syslog_r(LOG_DEBUG7, &sdata, | |||
| 844 | "new %sentry %s from %s to %s, helo %s", | |||
| 845 | spamtrap ? "greytrap " : "", ip, from, to, helo); | |||
| 846 | } else { | |||
| 847 | /* existing entry */ | |||
| 848 | if (gdcopyin(&dbd, &gd) == -1) { | |||
| 849 | /* whatever this is, it doesn't belong */ | |||
| 850 | db->del(db, &dbk, 0); | |||
| 851 | db->sync(db, 0); | |||
| 852 | goto bad; | |||
| 853 | } | |||
| 854 | gd.bcount++; | |||
| 855 | gd.pcount = spamtrap ? -1 : 0; | |||
| 856 | if (gd.first + passtime < now) | |||
| 857 | gd.pass = now; | |||
| 858 | memset(&dbk, 0, sizeof(dbk)); | |||
| 859 | dbk.size = strlen(lookup); | |||
| 860 | dbk.data = lookup; | |||
| 861 | memset(&dbd, 0, sizeof(dbd)); | |||
| 862 | dbd.size = sizeof(gd); | |||
| 863 | dbd.data = &gd; | |||
| 864 | r = db->put(db, &dbk, &dbd, 0); | |||
| 865 | db->sync(db, 0); | |||
| 866 | if (r) | |||
| 867 | goto bad; | |||
| 868 | if (debug) | |||
| 869 | fprintf(stderr(&__sF[2]), "updated %s\n", lookup); | |||
| 870 | } | |||
| 871 | free(key); | |||
| 872 | key = NULL((void *)0); | |||
| 873 | db->close(db); | |||
| 874 | db = NULL((void *)0); | |||
| 875 | ||||
| 876 | /* Entry successfully update, sent out sync message */ | |||
| 877 | if (syncsend && sync) { | |||
| 878 | if (spamtrap) { | |||
| 879 | syslog_r(LOG_DEBUG7, &sdata, | |||
| 880 | "sync_trap %s", ip); | |||
| 881 | sync_trapped(now, now + expire, ip); | |||
| 882 | } | |||
| 883 | else | |||
| 884 | sync_update(now, helo, ip, from, to); | |||
| 885 | } | |||
| 886 | return(0); | |||
| 887 | bad: | |||
| 888 | free(key); | |||
| 889 | key = NULL((void *)0); | |||
| 890 | db->close(db); | |||
| 891 | db = NULL((void *)0); | |||
| 892 | return(-1); | |||
| 893 | } | |||
| 894 | ||||
| 895 | int | |||
| 896 | twread(char *buf) | |||
| 897 | { | |||
| 898 | if ((strncmp(buf, "WHITE:", 6) == 0) || | |||
| 899 | (strncmp(buf, "TRAP:", 5) == 0)) { | |||
| 900 | char **ap, *argv[5]; | |||
| 901 | int argc = 0; | |||
| 902 | ||||
| 903 | for (ap = argv; | |||
| 904 | ap < &argv[4] && (*ap = strsep(&buf, ":")) != NULL((void *)0);) { | |||
| 905 | if (**ap != '\0') | |||
| 906 | ap++; | |||
| 907 | argc++; | |||
| 908 | } | |||
| 909 | *ap = NULL((void *)0); | |||
| 910 | if (argc != 4) | |||
| 911 | return (-1); | |||
| 912 | twupdate(PATH_SPAMD_DB"/var/db/spamd", argv[0], argv[1], argv[2], argv[3]); | |||
| 913 | return (0); | |||
| 914 | } else | |||
| 915 | return (-1); | |||
| 916 | } | |||
| 917 | ||||
| 918 | int | |||
| 919 | greyreader(void) | |||
| 920 | { | |||
| 921 | char cip[32], ip[32], helo[MAX_MAIL1024], from[MAX_MAIL1024], to[MAX_MAIL1024]; | |||
| 922 | char *buf; | |||
| 923 | size_t len; | |||
| 924 | int state, sync; | |||
| 925 | struct addrinfo hints, *res; | |||
| 926 | ||||
| 927 | memset(&hints, 0, sizeof(hints)); | |||
| 928 | hints.ai_family = AF_INET2; /*for now*/ | |||
| 929 | hints.ai_socktype = SOCK_DGRAM2; /*dummy*/ | |||
| 930 | hints.ai_protocol = IPPROTO_UDP17; /*dummy*/ | |||
| 931 | hints.ai_flags = AI_NUMERICHOST4; | |||
| 932 | ||||
| 933 | state = 0; | |||
| 934 | sync = 1; | |||
| 935 | if (grey == NULL((void *)0)) { | |||
| 936 | syslog_r(LOG_ERR3, &sdata, "No greylist pipe stream!\n"); | |||
| 937 | return (-1); | |||
| 938 | } | |||
| 939 | ||||
| 940 | /* grab trap suffixes */ | |||
| 941 | readsuffixlists(); | |||
| 942 | ||||
| 943 | while ((buf = fgetln(grey, &len))) { | |||
| 944 | if (buf[len - 1] == '\n') | |||
| 945 | buf[len - 1] = '\0'; | |||
| 946 | else | |||
| 947 | /* all valid lines end in \n */ | |||
| 948 | continue; | |||
| 949 | if (strlen(buf) < 4) | |||
| 950 | continue; | |||
| 951 | ||||
| 952 | if (strcmp(buf, "SYNC") == 0) { | |||
| 953 | sync = 0; | |||
| 954 | continue; | |||
| 955 | } | |||
| 956 | ||||
| 957 | switch (state) { | |||
| 958 | case 0: | |||
| 959 | if (twread(buf) == 0) { | |||
| 960 | state = 0; | |||
| 961 | break; | |||
| 962 | } | |||
| 963 | if (strncmp(buf, "HE:", 3) != 0) { | |||
| 964 | if (strncmp(buf, "CO:", 3) == 0) | |||
| 965 | strlcpy(cip, buf+3, sizeof(cip)); | |||
| 966 | state = 0; | |||
| 967 | break; | |||
| 968 | } | |||
| 969 | strlcpy(helo, buf+3, sizeof(helo)); | |||
| 970 | state = 1; | |||
| 971 | break; | |||
| 972 | case 1: | |||
| 973 | if (strncmp(buf, "IP:", 3) != 0) | |||
| 974 | break; | |||
| 975 | strlcpy(ip, buf+3, sizeof(ip)); | |||
| 976 | if (getaddrinfo(ip, NULL((void *)0), &hints, &res) == 0) { | |||
| 977 | freeaddrinfo(res); | |||
| 978 | state = 2; | |||
| 979 | } else | |||
| 980 | state = 0; | |||
| 981 | break; | |||
| 982 | case 2: | |||
| 983 | if (strncmp(buf, "FR:", 3) != 0) { | |||
| 984 | state = 0; | |||
| 985 | break; | |||
| 986 | } | |||
| 987 | strlcpy(from, buf+3, sizeof(from)); | |||
| 988 | state = 3; | |||
| 989 | break; | |||
| 990 | case 3: | |||
| 991 | if (strncmp(buf, "TO:", 3) != 0) { | |||
| 992 | state = 0; | |||
| 993 | break; | |||
| 994 | } | |||
| 995 | strlcpy(to, buf+3, sizeof(to)); | |||
| 996 | if (debug) | |||
| 997 | fprintf(stderr(&__sF[2]), | |||
| 998 | "Got Grey HELO %s, IP %s from %s to %s\n", | |||
| 999 | helo, ip, from, to); | |||
| 1000 | greyupdate(PATH_SPAMD_DB"/var/db/spamd", helo, ip, from, to, sync, cip); | |||
| 1001 | sync = 1; | |||
| 1002 | state = 0; | |||
| 1003 | break; | |||
| 1004 | } | |||
| 1005 | } | |||
| 1006 | return (0); | |||
| 1007 | } | |||
| 1008 | ||||
| 1009 | void | |||
| 1010 | greyscanner(void) | |||
| 1011 | { | |||
| 1012 | for (;;) { | |||
| 1013 | if (greyscan(PATH_SPAMD_DB"/var/db/spamd") == -1) | |||
| 1014 | syslog_r(LOG_NOTICE5, &sdata, "scan of %s failed", | |||
| 1015 | PATH_SPAMD_DB"/var/db/spamd"); | |||
| 1016 | sleep(DB_SCAN_INTERVAL60); | |||
| 1017 | } | |||
| 1018 | } | |||
| 1019 | ||||
| 1020 | static void | |||
| 1021 | drop_privs(void) | |||
| 1022 | { | |||
| 1023 | /* | |||
| 1024 | * lose root, continue as non-root user | |||
| 1025 | */ | |||
| 1026 | if (setgroups(1, &pw->pw_gid) || | |||
| 1027 | setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || | |||
| 1028 | setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) { | |||
| 1029 | syslog_r(LOG_ERR3, &sdata, "failed to drop privs (%m)"); | |||
| 1030 | exit(1); | |||
| 1031 | } | |||
| 1032 | } | |||
| 1033 | ||||
| 1034 | void | |||
| 1035 | check_spamd_db(void) | |||
| 1036 | { | |||
| 1037 | HASHINFO hashinfo; | |||
| 1038 | int i = -1; | |||
| 1039 | DB *db; | |||
| 1040 | ||||
| 1041 | /* check to see if /var/db/spamd exists, if not, create it */ | |||
| 1042 | memset(&hashinfo, 0, sizeof(hashinfo)); | |||
| 1043 | db = dbopen(PATH_SPAMD_DB"/var/db/spamd", O_EXLOCK0x0020|O_RDWR0x0002, 0600, DB_HASH, &hashinfo); | |||
| 1044 | ||||
| 1045 | if (db == NULL((void *)0)) { | |||
| 1046 | switch (errno(*__errno())) { | |||
| 1047 | case ENOENT2: | |||
| 1048 | i = open(PATH_SPAMD_DB"/var/db/spamd", O_RDWR0x0002|O_CREAT0x0200, 0644); | |||
| 1049 | if (i == -1) { | |||
| 1050 | syslog_r(LOG_ERR3, &sdata, | |||
| 1051 | "create %s failed (%m)", PATH_SPAMD_DB"/var/db/spamd"); | |||
| 1052 | exit(1); | |||
| 1053 | } | |||
| 1054 | /* if we are dropping privs, chown to that user */ | |||
| 1055 | if (pw && (fchown(i, pw->pw_uid, pw->pw_gid) == -1)) { | |||
| 1056 | syslog_r(LOG_ERR3, &sdata, | |||
| 1057 | "chown %s failed (%m)", PATH_SPAMD_DB"/var/db/spamd"); | |||
| 1058 | exit(1); | |||
| 1059 | } | |||
| 1060 | close(i); | |||
| 1061 | return; | |||
| 1062 | break; | |||
| 1063 | default: | |||
| 1064 | syslog_r(LOG_ERR3, &sdata, "open of %s failed (%m)", | |||
| 1065 | PATH_SPAMD_DB"/var/db/spamd"); | |||
| 1066 | exit(1); | |||
| 1067 | } | |||
| 1068 | } | |||
| 1069 | db->sync(db, 0); | |||
| 1070 | db->close(db); | |||
| 1071 | } | |||
| 1072 | ||||
| 1073 | ||||
| 1074 | int | |||
| 1075 | greywatcher(void) | |||
| 1076 | { | |||
| 1077 | struct sigaction sa; | |||
| 1078 | ||||
| 1079 | drop_privs(); | |||
| 1080 | ||||
| 1081 | if (unveil(PATH_SPAMD_DB"/var/db/spamd", "rw") == -1) { | |||
| ||||
| 1082 | syslog_r(LOG_ERR3, &sdata, "unveil failed (%m)"); | |||
| 1083 | exit(1); | |||
| 1084 | } | |||
| 1085 | if (unveil(alloweddomains_file, "r") == -1) { | |||
| 1086 | syslog_r(LOG_ERR3, &sdata, "unveil failed (%m)"); | |||
| 1087 | exit(1); | |||
| 1088 | } | |||
| 1089 | if (unveil(PATH_PFCTL"/sbin/pfctl", "x") == -1) { | |||
| 1090 | syslog_r(LOG_ERR3, &sdata, "unveil failed (%m)"); | |||
| 1091 | exit(1); | |||
| 1092 | } | |||
| 1093 | if (pledge("stdio rpath wpath inet flock proc exec", NULL((void *)0)) == -1) { | |||
| 1094 | syslog_r(LOG_ERR3, &sdata, "pledge failed (%m)"); | |||
| 1095 | exit(1); | |||
| 1096 | } | |||
| 1097 | ||||
| 1098 | startup = time(NULL((void *)0)); | |||
| 1099 | db_pid = fork(); | |||
| 1100 | switch (db_pid) { | |||
| 1101 | case -1: | |||
| 1102 | syslog_r(LOG_ERR3, &sdata, "fork failed (%m)"); | |||
| 1103 | exit(1); | |||
| 1104 | case 0: | |||
| 1105 | /* | |||
| 1106 | * child, talks to jailed spamd over greypipe, | |||
| 1107 | * updates db. has no access to pf. | |||
| 1108 | */ | |||
| 1109 | close(pfdev); | |||
| 1110 | setproctitle("(%s update)", PATH_SPAMD_DB"/var/db/spamd"); | |||
| 1111 | if (greyreader() == -1) { | |||
| 1112 | syslog_r(LOG_ERR3, &sdata, "greyreader failed (%m)"); | |||
| 1113 | _exit(1); | |||
| 1114 | } | |||
| 1115 | _exit(0); | |||
| 1116 | } | |||
| 1117 | ||||
| 1118 | ||||
| 1119 | fclose(grey); | |||
| 1120 | /* | |||
| 1121 | * parent, scans db periodically for changes and updates | |||
| 1122 | * pf whitelist table accordingly. | |||
| 1123 | */ | |||
| 1124 | ||||
| 1125 | sigfillset(&sa.sa_mask); | |||
| 1126 | sa.sa_flags = SA_RESTART0x0002; | |||
| 1127 | sa.sa_handler__sigaction_u.__sa_handler = sig_term_chld; | |||
| 1128 | sigaction(SIGTERM15, &sa, NULL((void *)0)); | |||
| 1129 | sigaction(SIGHUP1, &sa, NULL((void *)0)); | |||
| 1130 | sigaction(SIGCHLD20, &sa, NULL((void *)0)); | |||
| 1131 | sigaction(SIGINT2, &sa, NULL((void *)0)); | |||
| 1132 | ||||
| 1133 | setproctitle("(pf <spamd-white> update)"); | |||
| 1134 | greyscanner(); | |||
| 1135 | exit(1); | |||
| 1136 | } |