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