| File: | src/libexec/spamd-setup/spamd-setup.c |
| Warning: | line 871, column 2 Value stored to 'name' is never read |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
| 1 | /* $OpenBSD: spamd-setup.c,v 1.50 2017/07/07 00:10:15 djm Exp $ */ |
| 2 | |
| 3 | /* |
| 4 | * Copyright (c) 2003 Bob Beck. All rights reserved. |
| 5 | * |
| 6 | * Redistribution and use in source and binary forms, with or without |
| 7 | * modification, are permitted provided that the following conditions |
| 8 | * are met: |
| 9 | * 1. Redistributions of source code must retain the above copyright |
| 10 | * notice, this list of conditions and the following disclaimer. |
| 11 | * 2. Redistributions in binary form must reproduce the above copyright |
| 12 | * notice, this list of conditions and the following disclaimer in the |
| 13 | * documentation and/or other materials provided with the distribution. |
| 14 | * |
| 15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
| 16 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| 17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
| 18 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
| 19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| 20 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 21 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 22 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 24 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 25 | */ |
| 26 | |
| 27 | #include <arpa/inet.h> |
| 28 | #include <sys/socket.h> |
| 29 | #include <sys/types.h> |
| 30 | |
| 31 | #include <err.h> |
| 32 | #include <errno(*__errno()).h> |
| 33 | #include <fcntl.h> |
| 34 | #include <netdb.h> |
| 35 | #include <pwd.h> |
| 36 | #include <stdio.h> |
| 37 | #include <stdlib.h> |
| 38 | #include <string.h> |
| 39 | #include <unistd.h> |
| 40 | #include <zlib.h> |
| 41 | |
| 42 | #define PATH_FTP"/usr/bin/ftp" "/usr/bin/ftp" |
| 43 | #define PATH_PFCTL"/sbin/pfctl" "/sbin/pfctl" |
| 44 | #define PATH_SPAMD_CONF"/etc/mail/spamd.conf" "/etc/mail/spamd.conf" |
| 45 | #define SPAMD_ARG_MAX256 256 /* max # of args to an exec */ |
| 46 | #define SPAMD_USER"_spamd" "_spamd" |
| 47 | |
| 48 | struct cidr { |
| 49 | u_int32_t addr; |
| 50 | u_int8_t bits; |
| 51 | }; |
| 52 | |
| 53 | struct bl { |
| 54 | u_int32_t addr; |
| 55 | int8_t b; |
| 56 | int8_t w; |
| 57 | }; |
| 58 | |
| 59 | struct blacklist { |
| 60 | char *name; |
| 61 | char *message; |
| 62 | struct bl *bl; |
| 63 | size_t blc, bls; |
| 64 | u_int8_t black; |
| 65 | }; |
| 66 | |
| 67 | u_int32_t imask(u_int8_t); |
| 68 | u_int8_t maxblock(u_int32_t, u_int8_t); |
| 69 | u_int8_t maxdiff(u_int32_t, u_int32_t); |
| 70 | struct cidr *range2cidrlist(struct cidr *, u_int *, u_int *, u_int32_t, |
| 71 | u_int32_t); |
| 72 | void cidr2range(struct cidr, u_int32_t *, u_int32_t *); |
| 73 | char *atop(u_int32_t); |
| 74 | int parse_netblock(char *, struct bl *, struct bl *, int); |
| 75 | int open_child(char *, char **, int); |
| 76 | int fileget(char *); |
| 77 | int open_file(char *, char *); |
| 78 | char *fix_quoted_colons(char *); |
| 79 | void do_message(FILE *, char *); |
| 80 | struct bl *add_blacklist(struct bl *, size_t *, size_t *, gzFile, int); |
| 81 | int cmpbl(const void *, const void *); |
| 82 | struct cidr *collapse_blacklist(struct bl *, size_t, u_int *); |
| 83 | int configure_spamd(u_short, char *, char *, struct cidr *, u_int); |
| 84 | int configure_pf(struct cidr *); |
| 85 | int getlist(char **, char *, struct blacklist *, struct blacklist *); |
| 86 | __dead__attribute__((__noreturn__)) void usage(void); |
| 87 | |
| 88 | uid_t spamd_uid; |
| 89 | gid_t spamd_gid; |
| 90 | int debug; |
| 91 | int dryrun; |
| 92 | int greyonly = 1; |
| 93 | |
| 94 | extern char *__progname; |
| 95 | |
| 96 | #define MAXIMUM(a,b)(((a)>(b))?(a):(b)) (((a)>(b))?(a):(b)) |
| 97 | |
| 98 | u_int32_t |
| 99 | imask(u_int8_t b) |
| 100 | { |
| 101 | if (b == 0) |
| 102 | return (0); |
| 103 | return (0xffffffffU << (32 - b)); |
| 104 | } |
| 105 | |
| 106 | u_int8_t |
| 107 | maxblock(u_int32_t addr, u_int8_t bits) |
| 108 | { |
| 109 | u_int32_t m; |
| 110 | |
| 111 | while (bits > 0) { |
| 112 | m = imask(bits - 1); |
| 113 | |
| 114 | if ((addr & m) != addr) |
| 115 | return (bits); |
| 116 | bits--; |
| 117 | } |
| 118 | return (bits); |
| 119 | } |
| 120 | |
| 121 | u_int8_t |
| 122 | maxdiff(u_int32_t a, u_int32_t b) |
| 123 | { |
| 124 | u_int8_t bits = 0; |
| 125 | u_int32_t m; |
| 126 | |
| 127 | b++; |
| 128 | while (bits < 32) { |
| 129 | m = imask(bits); |
| 130 | |
| 131 | if ((a & m) != (b & m)) |
| 132 | return (bits); |
| 133 | bits++; |
| 134 | } |
| 135 | return (bits); |
| 136 | } |
| 137 | |
| 138 | struct cidr * |
| 139 | range2cidrlist(struct cidr *list, u_int *cli, u_int *cls, u_int32_t start, |
| 140 | u_int32_t end) |
| 141 | { |
| 142 | u_int8_t maxsize, diff; |
| 143 | struct cidr *tmp; |
| 144 | |
| 145 | while (end >= start) { |
| 146 | maxsize = maxblock(start, 32); |
| 147 | diff = maxdiff(start, end); |
| 148 | |
| 149 | maxsize = MAXIMUM(maxsize, diff)(((maxsize)>(diff))?(maxsize):(diff)); |
| 150 | if (*cls <= *cli + 1) { /* one extra for terminator */ |
| 151 | tmp = reallocarray(list, *cls + 32, |
| 152 | sizeof(struct cidr)); |
| 153 | if (tmp == NULL((void*)0)) |
| 154 | err(1, NULL((void*)0)); |
| 155 | list = tmp; |
| 156 | *cls += 32; |
| 157 | } |
| 158 | list[*cli].addr = start; |
| 159 | list[*cli].bits = maxsize; |
| 160 | (*cli)++; |
| 161 | start = start + (1 << (32 - maxsize)); |
| 162 | } |
| 163 | return (list); |
| 164 | } |
| 165 | |
| 166 | void |
| 167 | cidr2range(struct cidr cidr, u_int32_t *start, u_int32_t *end) |
| 168 | { |
| 169 | *start = cidr.addr; |
| 170 | *end = cidr.addr + (1 << (32 - cidr.bits)) - 1; |
| 171 | } |
| 172 | |
| 173 | char * |
| 174 | atop(u_int32_t addr) |
| 175 | { |
| 176 | struct in_addr in; |
| 177 | |
| 178 | memset(&in, 0, sizeof(in)); |
| 179 | in.s_addr = htonl(addr)(__uint32_t)(__builtin_constant_p(addr) ? (__uint32_t)(((__uint32_t )(addr) & 0xff) << 24 | ((__uint32_t)(addr) & 0xff00 ) << 8 | ((__uint32_t)(addr) & 0xff0000) >> 8 | ((__uint32_t)(addr) & 0xff000000) >> 24) : __swap32md (addr)); |
| 180 | return (inet_ntoa(in)); |
| 181 | } |
| 182 | |
| 183 | int |
| 184 | parse_netblock(char *buf, struct bl *start, struct bl *end, int white) |
| 185 | { |
| 186 | char astring[16], astring2[16]; |
| 187 | unsigned maskbits; |
| 188 | struct cidr c; |
| 189 | |
| 190 | /* skip leading spaces */ |
| 191 | while (*buf == ' ') |
| 192 | buf++; |
| 193 | /* bail if it's a comment */ |
| 194 | if (*buf == '#') |
| 195 | return (0); |
| 196 | /* otherwise, look for a netblock of some sort */ |
| 197 | if (sscanf(buf, "%15[^/]/%u", astring, &maskbits) == 2) { |
| 198 | /* looks like a cidr */ |
| 199 | memset(&c.addr, 0, sizeof(c.addr)); |
| 200 | if (inet_net_pton(AF_INET2, astring, &c.addr, sizeof(c.addr)) |
| 201 | == -1) |
| 202 | return (0); |
| 203 | c.addr = ntohl(c.addr)(__uint32_t)(__builtin_constant_p(c.addr) ? (__uint32_t)(((__uint32_t )(c.addr) & 0xff) << 24 | ((__uint32_t)(c.addr) & 0xff00) << 8 | ((__uint32_t)(c.addr) & 0xff0000) >> 8 | ((__uint32_t)(c.addr) & 0xff000000) >> 24) : __swap32md (c.addr)); |
| 204 | if (maskbits > 32) |
| 205 | return (0); |
| 206 | c.bits = maskbits; |
| 207 | cidr2range(c, &start->addr, &end->addr); |
| 208 | end->addr += 1; |
| 209 | } else if (sscanf(buf, "%15[0123456789.]%*[ -]%15[0123456789.]", |
| 210 | astring, astring2) == 2) { |
| 211 | /* looks like start - end */ |
| 212 | memset(&start->addr, 0, sizeof(start->addr)); |
| 213 | memset(&end->addr, 0, sizeof(end->addr)); |
| 214 | if (inet_net_pton(AF_INET2, astring, &start->addr, |
| 215 | sizeof(start->addr)) == -1) |
| 216 | return (0); |
| 217 | start->addr = ntohl(start->addr)(__uint32_t)(__builtin_constant_p(start->addr) ? (__uint32_t )(((__uint32_t)(start->addr) & 0xff) << 24 | ((__uint32_t )(start->addr) & 0xff00) << 8 | ((__uint32_t)(start ->addr) & 0xff0000) >> 8 | ((__uint32_t)(start-> addr) & 0xff000000) >> 24) : __swap32md(start->addr )); |
| 218 | if (inet_net_pton(AF_INET2, astring2, &end->addr, |
| 219 | sizeof(end->addr)) == -1) |
| 220 | return (0); |
| 221 | end->addr = ntohl(end->addr)(__uint32_t)(__builtin_constant_p(end->addr) ? (__uint32_t )(((__uint32_t)(end->addr) & 0xff) << 24 | ((__uint32_t )(end->addr) & 0xff00) << 8 | ((__uint32_t)(end-> addr) & 0xff0000) >> 8 | ((__uint32_t)(end->addr ) & 0xff000000) >> 24) : __swap32md(end->addr)) + 1; |
| 222 | if (start > end) |
| 223 | return (0); |
| 224 | } else if (sscanf(buf, "%15[0123456789.]", astring) == 1) { |
| 225 | /* just a single address */ |
| 226 | memset(&start->addr, 0, sizeof(start->addr)); |
| 227 | if (inet_net_pton(AF_INET2, astring, &start->addr, |
| 228 | sizeof(start->addr)) == -1) |
| 229 | return (0); |
| 230 | start->addr = ntohl(start->addr)(__uint32_t)(__builtin_constant_p(start->addr) ? (__uint32_t )(((__uint32_t)(start->addr) & 0xff) << 24 | ((__uint32_t )(start->addr) & 0xff00) << 8 | ((__uint32_t)(start ->addr) & 0xff0000) >> 8 | ((__uint32_t)(start-> addr) & 0xff000000) >> 24) : __swap32md(start->addr )); |
| 231 | end->addr = start->addr + 1; |
| 232 | } else |
| 233 | return (0); |
| 234 | |
| 235 | if (white) { |
| 236 | start->b = 0; |
| 237 | start->w = 1; |
| 238 | end->b = 0; |
| 239 | end->w = -1; |
| 240 | } else { |
| 241 | start->b = 1; |
| 242 | start->w = 0; |
| 243 | end->b = -1; |
| 244 | end->w = 0; |
| 245 | } |
| 246 | return (1); |
| 247 | } |
| 248 | |
| 249 | void |
| 250 | drop_privileges(void) |
| 251 | { |
| 252 | if (setgroups(1, &spamd_gid) != 0) |
| 253 | err(1, "setgroups %ld", (long)spamd_gid); |
| 254 | if (setresgid(spamd_gid, spamd_gid, spamd_gid) != 0) |
| 255 | err(1, "setresgid %ld", (long)spamd_gid); |
| 256 | if (setresuid(spamd_uid, spamd_uid, spamd_uid) != 0) |
| 257 | err(1, "setresuid %ld", (long)spamd_uid); |
| 258 | } |
| 259 | |
| 260 | int |
| 261 | open_child(char *file, char **argv, int drop_privs) |
| 262 | { |
| 263 | int pdes[2]; |
| 264 | |
| 265 | if (pipe(pdes) != 0) |
| 266 | return (-1); |
| 267 | switch (fork()) { |
| 268 | case -1: |
| 269 | close(pdes[0]); |
| 270 | close(pdes[1]); |
| 271 | return (-1); |
| 272 | case 0: |
| 273 | /* child */ |
| 274 | close(pdes[0]); |
| 275 | if (pdes[1] != STDOUT_FILENO1) { |
| 276 | dup2(pdes[1], STDOUT_FILENO1); |
| 277 | close(pdes[1]); |
| 278 | } |
| 279 | if (drop_privs) |
| 280 | drop_privileges(); |
| 281 | closefrom(STDERR_FILENO2 + 1); |
| 282 | execvp(file, argv); |
| 283 | _exit(1); |
| 284 | } |
| 285 | |
| 286 | /* parent */ |
| 287 | close(pdes[1]); |
| 288 | return (pdes[0]); |
| 289 | } |
| 290 | |
| 291 | int |
| 292 | fileget(char *url) |
| 293 | { |
| 294 | char *argv[6]; |
| 295 | |
| 296 | argv[0] = "ftp"; |
| 297 | argv[1] = "-V"; |
| 298 | argv[2] = "-o"; |
| 299 | argv[3] = "-"; |
| 300 | argv[4] = url; |
| 301 | argv[5] = NULL((void*)0); |
| 302 | |
| 303 | if (debug) |
| 304 | fprintf(stderr(&__sF[2]), "Getting %s\n", url); |
| 305 | |
| 306 | return (open_child(PATH_FTP"/usr/bin/ftp", argv, 1)); |
| 307 | } |
| 308 | |
| 309 | int |
| 310 | open_file(char *method, char *file) |
| 311 | { |
| 312 | char *url; |
| 313 | char **ap, **argv; |
| 314 | int len, i, oerrno; |
| 315 | |
| 316 | if ((method == NULL((void*)0)) || (strcmp(method, "file") == 0)) |
| 317 | return (open(file, O_RDONLY0x0000)); |
| 318 | if (strcmp(method, "http") == 0 || strcmp(method, "https") == 0 || |
| 319 | strcmp(method, "ftp") == 0) { |
| 320 | if (asprintf(&url, "%s://%s", method, file) == -1) |
| 321 | return (-1); |
| 322 | i = fileget(url); |
| 323 | free(url); |
| 324 | return (i); |
| 325 | } else if (strcmp(method, "exec") == 0) { |
| 326 | len = strlen(file); |
| 327 | argv = calloc(len, sizeof(char *)); |
| 328 | if (argv == NULL((void*)0)) |
| 329 | return (-1); |
| 330 | for (ap = argv; ap < &argv[len - 1] && |
| 331 | (*ap = strsep(&file, " \t")) != NULL((void*)0);) { |
| 332 | if (**ap != '\0') |
| 333 | ap++; |
| 334 | } |
| 335 | *ap = NULL((void*)0); |
| 336 | i = open_child(argv[0], argv, 0); |
| 337 | oerrno = errno(*__errno()); |
| 338 | free(argv); |
| 339 | errno(*__errno()) = oerrno; |
| 340 | return (i); |
| 341 | } |
| 342 | errx(1, "Unknown method %s", method); |
| 343 | return (-1); /* NOTREACHED */ |
| 344 | } |
| 345 | |
| 346 | /* |
| 347 | * fix_quoted_colons walks through a buffer returned by cgetent. We |
| 348 | * look for quoted strings, to escape colons (:) in quoted strings for |
| 349 | * getcap by replacing them with \C so cgetstr() deals with it correctly |
| 350 | * without having to see the \C bletchery in a configuration file that |
| 351 | * needs to have urls in it. Frees the buffer passed to it, passes back |
| 352 | * another larger one, with can be used with cgetxxx(), like the original |
| 353 | * buffer, it must be freed by the caller. |
| 354 | * This should really be a temporary fix until there is a sanctioned |
| 355 | * way to make getcap(3) handle quoted strings like this in a nicer |
| 356 | * way. |
| 357 | */ |
| 358 | char * |
| 359 | fix_quoted_colons(char *buf) |
| 360 | { |
| 361 | int in = 0; |
| 362 | size_t i, j = 0; |
| 363 | char *newbuf, last; |
| 364 | |
| 365 | /* Allocate enough space for a buf of all colons (impossible). */ |
| 366 | newbuf = malloc(2 * strlen(buf) + 1); |
| 367 | if (newbuf == NULL((void*)0)) |
| 368 | return (NULL((void*)0)); |
| 369 | last = '\0'; |
| 370 | for (i = 0; i < strlen(buf); i++) { |
| 371 | switch (buf[i]) { |
| 372 | case ':': |
| 373 | if (in) { |
| 374 | newbuf[j++] = '\\'; |
| 375 | newbuf[j++] = 'C'; |
| 376 | } else |
| 377 | newbuf[j++] = buf[i]; |
| 378 | break; |
| 379 | case '"': |
| 380 | if (last != '\\') |
| 381 | in = !in; |
| 382 | newbuf[j++] = buf[i]; |
| 383 | break; |
| 384 | default: |
| 385 | newbuf[j++] = buf[i]; |
| 386 | } |
| 387 | last = buf[i]; |
| 388 | } |
| 389 | free(buf); |
| 390 | newbuf[j] = '\0'; |
| 391 | return (newbuf); |
| 392 | } |
| 393 | |
| 394 | void |
| 395 | do_message(FILE *sdc, char *msg) |
| 396 | { |
| 397 | size_t i, bs = 0, bu = 0, len; |
| 398 | ssize_t n; |
| 399 | char *buf = NULL((void*)0), last, *tmp; |
| 400 | int fd; |
| 401 | |
| 402 | len = strlen(msg); |
| 403 | if (msg[0] == '"' && msg[len - 1] == '"') { |
| 404 | /* quoted msg, escape newlines and send it out */ |
| 405 | msg[len - 1] = '\0'; |
| 406 | buf = msg + 1; |
| 407 | bu = len - 2; |
| 408 | goto sendit; |
| 409 | } else { |
| 410 | /* |
| 411 | * message isn't quoted - try to open a local |
| 412 | * file and read the message from it. |
| 413 | */ |
| 414 | fd = open(msg, O_RDONLY0x0000); |
| 415 | if (fd == -1) |
| 416 | err(1, "Can't open message from %s", msg); |
| 417 | for (;;) { |
| 418 | if (bu == bs) { |
| 419 | tmp = realloc(buf, bs + 8192); |
| 420 | if (tmp == NULL((void*)0)) |
| 421 | err(1, NULL((void*)0)); |
| 422 | bs += 8192; |
| 423 | buf = tmp; |
| 424 | } |
| 425 | |
| 426 | n = read(fd, buf + bu, bs - bu); |
| 427 | if (n == 0) { |
| 428 | goto sendit; |
| 429 | } else if (n == -1) { |
| 430 | err(1, "Can't read from %s", msg); |
| 431 | } else |
| 432 | bu += n; |
| 433 | } |
| 434 | buf[bu]='\0'; |
| 435 | } |
| 436 | sendit: |
| 437 | fprintf(sdc, ";\""); |
| 438 | last = '\0'; |
| 439 | for (i = 0; i < bu; i++) { |
| 440 | /* handle escaping the things spamd wants */ |
| 441 | switch (buf[i]) { |
| 442 | case 'n': |
| 443 | if (last == '\\') |
| 444 | fprintf(sdc, "\\\\n"); |
| 445 | else |
| 446 | fputc('n', sdc); |
| 447 | last = '\0'; |
| 448 | break; |
| 449 | case '\n': |
| 450 | fprintf(sdc, "\\n"); |
| 451 | last = '\0'; |
| 452 | break; |
| 453 | case '"': |
| 454 | fputc('\\', sdc); |
| 455 | /* FALLTHROUGH */ |
| 456 | default: |
| 457 | fputc(buf[i], sdc); |
| 458 | last = '\0'; |
| 459 | } |
| 460 | } |
| 461 | fputc('"', sdc); |
| 462 | if (bs != 0) |
| 463 | free(buf); |
| 464 | } |
| 465 | |
| 466 | /* retrieve a list from fd. add to blacklist bl */ |
| 467 | struct bl * |
| 468 | add_blacklist(struct bl *bl, size_t *blc, size_t *bls, gzFile gzf, int white) |
| 469 | { |
| 470 | int i, n, start, bu = 0, bs = 0, serrno = 0; |
| 471 | char *buf = NULL((void*)0), *tmp; |
| 472 | struct bl *blt; |
| 473 | |
| 474 | for (;;) { |
| 475 | /* read in gzf, then parse */ |
| 476 | if (bu == bs) { |
| 477 | tmp = realloc(buf, bs + (1024 * 1024) + 1); |
| 478 | if (tmp == NULL((void*)0)) { |
| 479 | serrno = errno(*__errno()); |
| 480 | free(buf); |
| 481 | buf = NULL((void*)0); |
| 482 | bs = 0; |
| 483 | goto bldone; |
| 484 | } |
| 485 | bs += 1024 * 1024; |
| 486 | buf = tmp; |
| 487 | } |
| 488 | |
| 489 | n = gzread(gzf, buf + bu, bs - bu); |
| 490 | if (n == 0) |
| 491 | goto parse; |
| 492 | else if (n == -1) { |
| 493 | serrno = errno(*__errno()); |
| 494 | goto bldone; |
| 495 | } else |
| 496 | bu += n; |
| 497 | } |
| 498 | parse: |
| 499 | start = 0; |
| 500 | /* we assume that there is an IP for every 14 bytes */ |
| 501 | if (*blc + bu / 7 >= *bls) { |
| 502 | *bls += bu / 7; |
| 503 | blt = reallocarray(bl, *bls, sizeof(struct bl)); |
| 504 | if (blt == NULL((void*)0)) { |
| 505 | *bls -= bu / 7; |
| 506 | serrno = errno(*__errno()); |
| 507 | goto bldone; |
| 508 | } |
| 509 | bl = blt; |
| 510 | } |
| 511 | for (i = 0; i <= bu; i++) { |
| 512 | if (*blc + 1 >= *bls) { |
| 513 | *bls += 1024; |
| 514 | blt = reallocarray(bl, *bls, sizeof(struct bl)); |
| 515 | if (blt == NULL((void*)0)) { |
| 516 | *bls -= 1024; |
| 517 | serrno = errno(*__errno()); |
| 518 | goto bldone; |
| 519 | } |
| 520 | bl = blt; |
| 521 | } |
| 522 | if (i == bu || buf[i] == '\n') { |
| 523 | buf[i] = '\0'; |
| 524 | if (parse_netblock(buf + start, |
| 525 | bl + *blc, bl + *blc + 1, white)) |
| 526 | *blc += 2; |
| 527 | start = i + 1; |
| 528 | } |
| 529 | } |
| 530 | if (bu == 0) |
| 531 | errno(*__errno()) = EIO5; |
| 532 | bldone: |
| 533 | free(buf); |
| 534 | if (serrno) |
| 535 | errno(*__errno()) = serrno; |
| 536 | return (bl); |
| 537 | } |
| 538 | |
| 539 | int |
| 540 | cmpbl(const void *a, const void *b) |
| 541 | { |
| 542 | if (((struct bl *)a)->addr > ((struct bl *) b)->addr) |
| 543 | return (1); |
| 544 | if (((struct bl *)a)->addr < ((struct bl *) b)->addr) |
| 545 | return (-1); |
| 546 | return (0); |
| 547 | } |
| 548 | |
| 549 | /* |
| 550 | * collapse_blacklist takes blacklist/whitelist entries sorts, removes |
| 551 | * overlaps and whitelist portions, and returns netblocks to blacklist |
| 552 | * as lists of nonoverlapping cidr blocks suitable for feeding in |
| 553 | * printable form to pfctl or spamd. |
| 554 | */ |
| 555 | struct cidr * |
| 556 | collapse_blacklist(struct bl *bl, size_t blc, u_int *clc) |
| 557 | { |
| 558 | int bs = 0, ws = 0, state=0; |
| 559 | u_int cli, cls, i; |
| 560 | u_int32_t bstart = 0; |
| 561 | struct cidr *cl; |
| 562 | int laststate; |
| 563 | u_int32_t addr; |
| 564 | |
| 565 | if (blc == 0) |
| 566 | return (NULL((void*)0)); |
| 567 | |
| 568 | /* |
| 569 | * Overallocate by 10% to avoid excessive realloc due to white |
| 570 | * entries splitting up CIDR blocks. |
| 571 | */ |
| 572 | cli = 0; |
| 573 | cls = (blc / 2) + (blc / 20) + 1; |
| 574 | cl = reallocarray(NULL((void*)0), cls, sizeof(struct cidr)); |
| 575 | if (cl == NULL((void*)0)) |
| 576 | return (NULL((void*)0)); |
| 577 | qsort(bl, blc, sizeof(struct bl), cmpbl); |
| 578 | for (i = 0; i < blc;) { |
| 579 | laststate = state; |
| 580 | addr = bl[i].addr; |
| 581 | |
| 582 | do { |
| 583 | bs += bl[i].b; |
| 584 | ws += bl[i].w; |
| 585 | i++; |
| 586 | } while (bl[i].addr == addr); |
| 587 | if (state == 1 && bs == 0) |
| 588 | state = 0; |
| 589 | else if (state == 0 && bs > 0) |
| 590 | state = 1; |
| 591 | if (ws > 0) |
| 592 | state = 0; |
| 593 | if (laststate == 0 && state == 1) { |
| 594 | /* start blacklist */ |
| 595 | bstart = addr; |
| 596 | } |
| 597 | if (laststate == 1 && state == 0) { |
| 598 | /* end blacklist */ |
| 599 | cl = range2cidrlist(cl, &cli, &cls, bstart, addr - 1); |
| 600 | } |
| 601 | laststate = state; |
| 602 | } |
| 603 | cl[cli].addr = 0; |
| 604 | *clc = cli; |
| 605 | return (cl); |
| 606 | } |
| 607 | |
| 608 | int |
| 609 | configure_spamd(u_short dport, char *name, char *message, |
| 610 | struct cidr *blacklists, u_int count) |
| 611 | { |
| 612 | int lport = IPPORT_RESERVED1024 - 1, s; |
| 613 | struct sockaddr_in sin; |
| 614 | FILE* sdc; |
| 615 | |
| 616 | s = rresvport(&lport); |
| 617 | if (s == -1) |
| 618 | return (-1); |
| 619 | memset(&sin, 0, sizeof sin); |
| 620 | sin.sin_len = sizeof(sin); |
| 621 | sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK)(__uint32_t)(__builtin_constant_p(((u_int32_t)(0x7f000001))) ? (__uint32_t)(((__uint32_t)(((u_int32_t)(0x7f000001))) & 0xff ) << 24 | ((__uint32_t)(((u_int32_t)(0x7f000001))) & 0xff00) << 8 | ((__uint32_t)(((u_int32_t)(0x7f000001)) ) & 0xff0000) >> 8 | ((__uint32_t)(((u_int32_t)(0x7f000001 ))) & 0xff000000) >> 24) : __swap32md(((u_int32_t)( 0x7f000001)))); |
| 622 | sin.sin_family = AF_INET2; |
| 623 | sin.sin_port = htons(dport)(__uint16_t)(__builtin_constant_p(dport) ? (__uint16_t)(((__uint16_t )(dport) & 0xffU) << 8 | ((__uint16_t)(dport) & 0xff00U) >> 8) : __swap16md(dport)); |
| 624 | if (connect(s, (struct sockaddr *)&sin, sizeof sin) == -1) |
| 625 | return (-1); |
| 626 | sdc = fdopen(s, "w"); |
| 627 | if (sdc == NULL((void*)0)) { |
| 628 | close(s); |
| 629 | return (-1); |
| 630 | } |
| 631 | fputs(name, sdc); |
| 632 | do_message(sdc, message); |
| 633 | fprintf(sdc, ";inet;%u", count); |
| 634 | while (blacklists->addr != 0) { |
| 635 | fprintf(sdc, ";%s/%u", atop(blacklists->addr), |
| 636 | blacklists->bits); |
| 637 | blacklists++; |
| 638 | } |
| 639 | fputc('\n', sdc); |
| 640 | fclose(sdc); |
| 641 | close(s); |
| 642 | return (0); |
| 643 | } |
| 644 | |
| 645 | |
| 646 | int |
| 647 | configure_pf(struct cidr *blacklists) |
| 648 | { |
| 649 | char *argv[9]= {"pfctl", "-q", "-t", "spamd", "-T", "replace", |
| 650 | "-f" "-", NULL((void*)0)}; |
| 651 | static FILE *pf = NULL((void*)0); |
| 652 | int pdes[2]; |
| 653 | |
| 654 | if (pf == NULL((void*)0)) { |
| 655 | if (pipe(pdes) != 0) |
| 656 | return (-1); |
| 657 | switch (fork()) { |
| 658 | case -1: |
| 659 | close(pdes[0]); |
| 660 | close(pdes[1]); |
| 661 | return (-1); |
| 662 | case 0: |
| 663 | /* child */ |
| 664 | close(pdes[1]); |
| 665 | if (pdes[0] != STDIN_FILENO0) { |
| 666 | dup2(pdes[0], STDIN_FILENO0); |
| 667 | close(pdes[0]); |
| 668 | } |
| 669 | closefrom(STDERR_FILENO2 + 1); |
| 670 | execvp(PATH_PFCTL"/sbin/pfctl", argv); |
| 671 | _exit(1); |
| 672 | } |
| 673 | |
| 674 | /* parent */ |
| 675 | close(pdes[0]); |
| 676 | pf = fdopen(pdes[1], "w"); |
| 677 | if (pf == NULL((void*)0)) { |
| 678 | close(pdes[1]); |
| 679 | return (-1); |
| 680 | } |
| 681 | } |
| 682 | while (blacklists->addr != 0) { |
| 683 | fprintf(pf, "%s/%u\n", atop(blacklists->addr), |
| 684 | blacklists->bits); |
| 685 | blacklists++; |
| 686 | } |
| 687 | return (0); |
| 688 | } |
| 689 | |
| 690 | int |
| 691 | getlist(char ** db_array, char *name, struct blacklist *blist, |
| 692 | struct blacklist *blistnew) |
| 693 | { |
| 694 | char *buf, *method, *file, *message; |
| 695 | int fd, black = 0, serror; |
| 696 | size_t blc, bls; |
| 697 | struct bl *bl = NULL((void*)0); |
| 698 | gzFile gzf; |
| 699 | |
| 700 | if (cgetent(&buf, db_array, name) != 0) |
| 701 | err(1, "Can't find \"%s\" in spamd config", name); |
| 702 | buf = fix_quoted_colons(buf); |
| 703 | if (cgetcap(buf, "black", ':') != NULL((void*)0)) { |
| 704 | /* use new list */ |
| 705 | black = 1; |
| 706 | blc = blistnew->blc; |
| 707 | bls = blistnew->bls; |
| 708 | bl = blistnew->bl; |
| 709 | } else if (cgetcap(buf, "white", ':') != NULL((void*)0)) { |
| 710 | /* apply to most recent blacklist */ |
| 711 | black = 0; |
| 712 | blc = blist->blc; |
| 713 | bls = blist->bls; |
| 714 | bl = blist->bl; |
| 715 | } else |
| 716 | errx(1, "Must have \"black\" or \"white\" in %s", name); |
| 717 | |
| 718 | switch (cgetstr(buf, "msg", &message)) { |
| 719 | case -1: |
| 720 | if (black) |
| 721 | errx(1, "No msg for blacklist \"%s\"", name); |
| 722 | break; |
| 723 | case -2: |
| 724 | err(1, NULL((void*)0)); |
| 725 | } |
| 726 | |
| 727 | switch (cgetstr(buf, "method", &method)) { |
| 728 | case -1: |
| 729 | method = NULL((void*)0); |
| 730 | break; |
| 731 | case -2: |
| 732 | err(1, NULL((void*)0)); |
| 733 | } |
| 734 | |
| 735 | switch (cgetstr(buf, "file", &file)) { |
| 736 | case -1: |
| 737 | errx(1, "No file given for %slist %s", |
| 738 | black ? "black" : "white", name); |
| 739 | case -2: |
| 740 | err(1, NULL((void*)0)); |
| 741 | default: |
| 742 | fd = open_file(method, file); |
| 743 | if (fd == -1) |
| 744 | err(1, "Can't open %s by %s method", |
| 745 | file, method ? method : "file"); |
| 746 | free(method); |
| 747 | free(file); |
| 748 | gzf = gzdopen(fd, "r"); |
| 749 | if (gzf == NULL((void*)0)) |
| 750 | errx(1, "gzdopen"); |
| 751 | } |
| 752 | free(buf); |
| 753 | bl = add_blacklist(bl, &blc, &bls, gzf, !black); |
| 754 | serror = errno(*__errno()); |
| 755 | gzclose(gzf); |
| 756 | if (bl == NULL((void*)0)) { |
| 757 | errno(*__errno()) = serror; |
| 758 | warn("Could not add %slist %s", black ? "black" : "white", |
| 759 | name); |
| 760 | return (0); |
| 761 | } |
| 762 | if (black) { |
| 763 | if (debug) |
| 764 | fprintf(stderr(&__sF[2]), "blacklist %s %zu entries\n", |
| 765 | name, blc / 2); |
| 766 | blistnew->message = message; |
| 767 | blistnew->name = name; |
| 768 | blistnew->black = black; |
| 769 | blistnew->bl = bl; |
| 770 | blistnew->blc = blc; |
| 771 | blistnew->bls = bls; |
| 772 | } else { |
| 773 | /* whitelist applied to last active blacklist */ |
| 774 | if (debug) |
| 775 | fprintf(stderr(&__sF[2]), "whitelist %s %zu entries\n", |
| 776 | name, (blc - blist->blc) / 2); |
| 777 | blist->bl = bl; |
| 778 | blist->blc = blc; |
| 779 | blist->bls = bls; |
| 780 | } |
| 781 | return (black); |
| 782 | } |
| 783 | |
| 784 | void |
| 785 | send_blacklist(struct blacklist *blist, in_port_t port) |
| 786 | { |
| 787 | struct cidr *cidrs; |
| 788 | u_int clc; |
| 789 | |
| 790 | if (blist->blc > 0) { |
| 791 | cidrs = collapse_blacklist(blist->bl, blist->blc, &clc); |
| 792 | if (cidrs == NULL((void*)0)) |
| 793 | err(1, NULL((void*)0)); |
| 794 | if (!dryrun) { |
| 795 | if (configure_spamd(port, blist->name, |
| 796 | blist->message, cidrs, clc) == -1) |
| 797 | err(1, "Can't connect to spamd on port %d", |
| 798 | port); |
| 799 | if (!greyonly && configure_pf(cidrs) == -1) |
| 800 | err(1, "pfctl failed"); |
| 801 | } |
| 802 | free(cidrs); |
| 803 | free(blist->bl); |
| 804 | } |
| 805 | } |
| 806 | |
| 807 | __dead__attribute__((__noreturn__)) void |
| 808 | usage(void) |
| 809 | { |
| 810 | |
| 811 | fprintf(stderr(&__sF[2]), "usage: %s [-bDdn]\n", __progname); |
| 812 | exit(1); |
| 813 | } |
| 814 | |
| 815 | int |
| 816 | main(int argc, char *argv[]) |
| 817 | { |
| 818 | size_t blc, bls, black, white; |
| 819 | char *db_array[2], *buf, *name; |
| 820 | struct blacklist *blists; |
| 821 | struct servent *ent; |
| 822 | int daemonize = 0, ch; |
| 823 | struct passwd *pw; |
| 824 | |
| 825 | while ((ch = getopt(argc, argv, "bdDn")) != -1) { |
| 826 | switch (ch) { |
| 827 | case 'n': |
| 828 | dryrun = 1; |
| 829 | break; |
| 830 | case 'd': |
| 831 | debug = 1; |
| 832 | break; |
| 833 | case 'b': |
| 834 | greyonly = 0; |
| 835 | break; |
| 836 | case 'D': |
| 837 | daemonize = 1; |
| 838 | break; |
| 839 | default: |
| 840 | usage(); |
| 841 | break; |
| 842 | } |
| 843 | } |
| 844 | argc -= optind; |
| 845 | argv += optind; |
| 846 | if (argc != 0) |
| 847 | usage(); |
| 848 | |
| 849 | if ((pw = getpwnam(SPAMD_USER"_spamd")) == NULL((void*)0)) |
| 850 | errx(1, "cannot find user %s", SPAMD_USER"_spamd"); |
| 851 | spamd_uid = pw->pw_uid; |
| 852 | spamd_gid = pw->pw_gid; |
| 853 | |
| 854 | if (pledge("stdio rpath inet proc exec id", NULL((void*)0)) == -1) |
| 855 | err(1, "pledge"); |
| 856 | |
| 857 | if (daemonize) |
| 858 | daemon(0, 0); |
| 859 | else if (chdir("/") != 0) |
| 860 | err(1, "chdir(\"/\")"); |
| 861 | |
| 862 | if ((ent = getservbyname("spamd-cfg", "tcp")) == NULL((void*)0)) |
| 863 | errx(1, "cannot find service \"spamd-cfg\" in /etc/services"); |
| 864 | ent->s_port = ntohs(ent->s_port)(__uint16_t)(__builtin_constant_p(ent->s_port) ? (__uint16_t )(((__uint16_t)(ent->s_port) & 0xffU) << 8 | ((__uint16_t )(ent->s_port) & 0xff00U) >> 8) : __swap16md(ent ->s_port)); |
| 865 | |
| 866 | db_array[0] = PATH_SPAMD_CONF"/etc/mail/spamd.conf"; |
| 867 | db_array[1] = NULL((void*)0); |
| 868 | |
| 869 | if (cgetent(&buf, db_array, "all") != 0) |
| 870 | err(1, "Can't find \"all\" in spamd config"); |
| 871 | name = strsep(&buf, ": \t"); /* skip "all" at start */ |
Value stored to 'name' is never read | |
| 872 | blists = NULL((void*)0); |
| 873 | blc = bls = 0; |
| 874 | while ((name = strsep(&buf, ": \t")) != NULL((void*)0)) { |
| 875 | if (*name) { |
| 876 | /* extract config in order specified in "all" tag */ |
| 877 | if (blc == bls) { |
| 878 | struct blacklist *tmp; |
| 879 | |
| 880 | bls += 32; |
| 881 | tmp = reallocarray(blists, bls, |
| 882 | sizeof(struct blacklist)); |
| 883 | if (tmp == NULL((void*)0)) |
| 884 | err(1, NULL((void*)0)); |
| 885 | blists = tmp; |
| 886 | } |
| 887 | if (blc == 0) |
| 888 | black = white = 0; |
| 889 | else { |
| 890 | white = blc - 1; |
| 891 | black = blc; |
| 892 | } |
| 893 | memset(&blists[black], 0, sizeof(struct blacklist)); |
| 894 | black = getlist(db_array, name, &blists[white], |
| 895 | &blists[black]); |
| 896 | if (black && blc > 0) { |
| 897 | /* collapse and free previous blacklist */ |
| 898 | send_blacklist(&blists[blc - 1], ent->s_port); |
| 899 | } |
| 900 | blc += black; |
| 901 | } |
| 902 | } |
| 903 | /* collapse and free last blacklist */ |
| 904 | if (blc > 0) |
| 905 | send_blacklist(&blists[blc - 1], ent->s_port); |
| 906 | return (0); |
| 907 | } |