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.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" |
43 |
|
|
#define PATH_PFCTL "/sbin/pfctl" |
44 |
|
|
#define PATH_SPAMD_CONF "/etc/mail/spamd.conf" |
45 |
|
|
#define SPAMD_ARG_MAX 256 /* max # of args to an exec */ |
46 |
|
|
#define SPAMD_USER "_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 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)) |
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); |
150 |
|
|
if (*cls <= *cli + 1) { /* one extra for terminator */ |
151 |
|
|
tmp = reallocarray(list, *cls + 32, |
152 |
|
|
sizeof(struct cidr)); |
153 |
|
|
if (tmp == NULL) |
154 |
|
|
err(1, NULL); |
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); |
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_INET, astring, &c.addr, sizeof(c.addr)) |
201 |
|
|
== -1) |
202 |
|
|
return (0); |
203 |
|
|
c.addr = ntohl(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_INET, astring, &start->addr, |
215 |
|
|
sizeof(start->addr)) == -1) |
216 |
|
|
return (0); |
217 |
|
|
start->addr = ntohl(start->addr); |
218 |
|
|
if (inet_net_pton(AF_INET, astring2, &end->addr, |
219 |
|
|
sizeof(end->addr)) == -1) |
220 |
|
|
return (0); |
221 |
|
|
end->addr = ntohl(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_INET, astring, &start->addr, |
228 |
|
|
sizeof(start->addr)) == -1) |
229 |
|
|
return (0); |
230 |
|
|
start->addr = ntohl(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_FILENO) { |
276 |
|
|
dup2(pdes[1], STDOUT_FILENO); |
277 |
|
|
close(pdes[1]); |
278 |
|
|
} |
279 |
|
|
if (drop_privs) |
280 |
|
|
drop_privileges(); |
281 |
|
|
closefrom(STDERR_FILENO + 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; |
302 |
|
|
|
303 |
|
|
if (debug) |
304 |
|
|
fprintf(stderr, "Getting %s\n", url); |
305 |
|
|
|
306 |
|
|
return (open_child(PATH_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) || (strcmp(method, "file") == 0)) |
317 |
|
|
return (open(file, O_RDONLY)); |
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) |
329 |
|
|
return (-1); |
330 |
|
|
for (ap = argv; ap < &argv[len - 1] && |
331 |
|
|
(*ap = strsep(&file, " \t")) != NULL;) { |
332 |
|
|
if (**ap != '\0') |
333 |
|
|
ap++; |
334 |
|
|
} |
335 |
|
|
*ap = NULL; |
336 |
|
|
i = open_child(argv[0], argv, 0); |
337 |
|
|
oerrno = errno; |
338 |
|
|
free(argv); |
339 |
|
|
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) |
368 |
|
|
return (NULL); |
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, 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_RDONLY); |
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) |
421 |
|
|
err(1, NULL); |
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, *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) { |
479 |
|
|
serrno = errno; |
480 |
|
|
free(buf); |
481 |
|
|
buf = NULL; |
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; |
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) { |
505 |
|
|
*bls -= bu / 7; |
506 |
|
|
serrno = 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) { |
516 |
|
|
*bls -= 1024; |
517 |
|
|
serrno = 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 = EIO; |
532 |
|
|
bldone: |
533 |
|
|
free(buf); |
534 |
|
|
if (serrno) |
535 |
|
|
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); |
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, cls, sizeof(struct cidr)); |
575 |
|
|
if (cl == NULL) |
576 |
|
|
return (NULL); |
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_RESERVED - 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); |
622 |
|
|
sin.sin_family = AF_INET; |
623 |
|
|
sin.sin_port = htons(dport); |
624 |
|
|
if (connect(s, (struct sockaddr *)&sin, sizeof sin) == -1) |
625 |
|
|
return (-1); |
626 |
|
|
sdc = fdopen(s, "w"); |
627 |
|
|
if (sdc == NULL) { |
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}; |
651 |
|
|
static FILE *pf = NULL; |
652 |
|
|
int pdes[2]; |
653 |
|
|
|
654 |
|
|
if (pf == NULL) { |
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_FILENO) { |
666 |
|
|
dup2(pdes[0], STDIN_FILENO); |
667 |
|
|
close(pdes[0]); |
668 |
|
|
} |
669 |
|
|
closefrom(STDERR_FILENO + 1); |
670 |
|
|
execvp(PATH_PFCTL, argv); |
671 |
|
|
_exit(1); |
672 |
|
|
} |
673 |
|
|
|
674 |
|
|
/* parent */ |
675 |
|
|
close(pdes[0]); |
676 |
|
|
pf = fdopen(pdes[1], "w"); |
677 |
|
|
if (pf == NULL) { |
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; |
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) { |
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) { |
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); |
725 |
|
|
} |
726 |
|
|
|
727 |
|
|
switch (cgetstr(buf, "method", &method)) { |
728 |
|
|
case -1: |
729 |
|
|
method = NULL; |
730 |
|
|
break; |
731 |
|
|
case -2: |
732 |
|
|
err(1, NULL); |
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); |
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) |
750 |
|
|
errx(1, "gzdopen"); |
751 |
|
|
} |
752 |
|
|
free(buf); |
753 |
|
|
bl = add_blacklist(bl, &blc, &bls, gzf, !black); |
754 |
|
|
serror = errno; |
755 |
|
|
gzclose(gzf); |
756 |
|
|
if (bl == NULL) { |
757 |
|
|
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, "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, "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) |
793 |
|
|
err(1, NULL); |
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 void |
808 |
|
|
usage(void) |
809 |
|
|
{ |
810 |
|
|
|
811 |
|
|
fprintf(stderr, "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)) == NULL) |
850 |
|
|
errx(1, "cannot find user %s", SPAMD_USER); |
851 |
|
|
spamd_uid = pw->pw_uid; |
852 |
|
|
spamd_gid = pw->pw_gid; |
853 |
|
|
|
854 |
|
|
if (pledge("stdio rpath inet proc exec id flock cpath wpath", NULL) == -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) |
863 |
|
|
errx(1, "cannot find service \"spamd-cfg\" in /etc/services"); |
864 |
|
|
ent->s_port = ntohs(ent->s_port); |
865 |
|
|
|
866 |
|
|
db_array[0] = PATH_SPAMD_CONF; |
867 |
|
|
db_array[1] = NULL; |
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; |
873 |
|
|
blc = bls = 0; |
874 |
|
|
while ((name = strsep(&buf, ": \t")) != NULL) { |
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) |
884 |
|
|
err(1, NULL); |
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 |
|
|
} |