File: | src/libexec/spamd-setup/spamd-setup.c |
Warning: | line 845, column 2 Value stored to 'argv' 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; |
Value stored to 'argv' is never read | |
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 */ |
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 | } |