Mercurial > dnsbl
comparison src/dnsbl.cpp @ 0:96a9758165cd original
Initial revision
author | carl |
---|---|
date | Tue, 20 Apr 2004 20:02:29 -0700 |
parents | |
children | bd0b1a153f67 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:96a9758165cd |
---|---|
1 /* | |
2 | |
3 Copyright (c) 2004 Carl Byington - 510 Software Group, released under | |
4 the GPL version 2 or any later version at your choice available at | |
5 http://www.fsf.org/licenses/gpl.txt | |
6 | |
7 Based on a sample milter Copyright (c) 2000-2003 Sendmail, Inc. and its | |
8 suppliers. Inspired by the DCC by Rhyolite Software | |
9 | |
10 -p port The port through which the MTA will connect to this milter. | |
11 -t sec The timeout value. | |
12 | |
13 TODO: | |
14 | |
15 1) Add "include-dcc NAME fn" to read a dcc whiteclnt file looking | |
16 for many substitute mail-host domain, and add the equivalent "env_from | |
17 domain black" into the NAME mapping. That allows clients to use just the | |
18 DCC for white/blacklisting, but the backup mx machines can use dnsbl | |
19 and get the same effect. | |
20 | |
21 */ | |
22 | |
23 | |
24 // from sendmail sample | |
25 #include <sys/types.h> | |
26 #include <sys/stat.h> | |
27 #include <errno.h> | |
28 #include <stdio.h> | |
29 #include <stdlib.h> | |
30 #include <string.h> | |
31 #include <sysexits.h> | |
32 #include <unistd.h> | |
33 | |
34 // needed for socket io | |
35 #include <sys/ioctl.h> | |
36 #include <net/if.h> | |
37 #include <arpa/inet.h> | |
38 #include <netinet/in.h> | |
39 #include <netinet/tcp.h> | |
40 #include <netdb.h> | |
41 #include <sys/socket.h> | |
42 | |
43 // needed for thread | |
44 #include <pthread.h> | |
45 | |
46 // needed for std c++ collections | |
47 #include <set> | |
48 #include <map> | |
49 #include <list> | |
50 | |
51 // for the dns resolver | |
52 #include <netinet/in.h> | |
53 #include <arpa/nameser.h> | |
54 #include <resolv.h> | |
55 | |
56 // misc stuff needed here | |
57 #include <ctype.h> | |
58 #include <fstream> | |
59 #include <syslog.h> | |
60 | |
61 | |
62 using namespace std; | |
63 | |
64 | |
65 extern "C" { | |
66 #include "libmilter/mfapi.h" | |
67 sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr); | |
68 sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv); | |
69 sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv); | |
70 sfsistat mlfi_eom_or_abort(SMFICTX *ctx); | |
71 sfsistat mlfi_close(SMFICTX *ctx); | |
72 } | |
73 | |
74 #ifndef bool | |
75 # define bool int | |
76 # define TRUE 1 | |
77 # define FALSE 0 | |
78 #endif /* ! bool */ | |
79 | |
80 struct ltstr { | |
81 bool operator()(char* s1, char* s2) const { | |
82 return strcmp(s1, s2) < 0; | |
83 } | |
84 }; | |
85 | |
86 struct DNSBL { | |
87 char *suffix; // blacklist suffix like blackholes.five-ten-sg.com | |
88 char *message; // error message with one or two %s operators for the ip address replacement | |
89 DNSBL(char *s, char *m); | |
90 }; | |
91 DNSBL::DNSBL(char *s, char *m) { | |
92 suffix = s; | |
93 message = m; | |
94 } | |
95 | |
96 typedef DNSBL * DNSBLP; | |
97 typedef list<DNSBLP> DNSBLL; | |
98 typedef DNSBLL * DNSBLLP; | |
99 typedef map<char *, char *, ltstr> string_map; | |
100 typedef map<char *, string_map *, ltstr> from_map; | |
101 typedef map<char *, DNSBLP, ltstr> dnsblp_map; | |
102 typedef map<char *, DNSBLLP, ltstr> dnsbllp_map; | |
103 typedef set<char *, ltstr> string_set; | |
104 typedef list<char *> string_list; | |
105 | |
106 struct CONFIG { | |
107 // the only mutable stuff once it has been loaded from the config file | |
108 int reference_count; // protected by the global config_mutex | |
109 // all the rest is constant after loading from the config file | |
110 time_t load_time; | |
111 string_list config_files; | |
112 dnsblp_map dnsbls; | |
113 dnsbllp_map dnsblls; | |
114 from_map env_from; | |
115 string_map env_to_dnsbll; // map recipient to a named dnsbll | |
116 string_map env_to_chkfrom; // map recipient to a named from map | |
117 CONFIG(); | |
118 ~CONFIG(); | |
119 }; | |
120 CONFIG::CONFIG() { | |
121 reference_count = 0; | |
122 load_time = 0; | |
123 } | |
124 CONFIG::~CONFIG() { | |
125 for (dnsblp_map::iterator i=dnsbls.begin(); i!=dnsbls.end(); i++) { | |
126 DNSBLP d = (*i).second; | |
127 delete d; | |
128 } | |
129 for (dnsbllp_map::iterator i=dnsblls.begin(); i!=dnsblls.end(); i++) { | |
130 DNSBLLP d = (*i).second; | |
131 delete d; | |
132 } | |
133 for (from_map::iterator i=env_from.begin(); i!=env_from.end(); i++) { | |
134 string_map *d = (*i).second; | |
135 delete d; | |
136 } | |
137 } | |
138 | |
139 static string_set all_strings; // owns all the strings, only modified by the config loader thread | |
140 static CONFIG * config = NULL; // protected by the config_mutex | |
141 | |
142 static pthread_mutex_t config_mutex; | |
143 static pthread_mutex_t syslog_mutex; | |
144 static pthread_mutex_t resolve_mutex; | |
145 | |
146 | |
147 | |
148 //////////////////////////////////////////////// | |
149 // predefined names | |
150 // | |
151 #define DEFAULT "default" | |
152 #define WHITE "white" | |
153 #define BLACK "black" | |
154 | |
155 | |
156 //////////////////////////////////////////////// | |
157 // mail filter private data, held for us by sendmail | |
158 // | |
159 enum status {oksofar, // not rejected yet | |
160 white, // whitelisted by envelope from | |
161 black, // blacklisted by envelope from or to | |
162 reject}; // rejected by a dns list | |
163 struct mlfiPriv | |
164 { | |
165 CONFIG *pc; // global context with our maps | |
166 int ip; // ip4 address of the smtp client | |
167 char *mailaddr; // envelope from value | |
168 bool authenticated; // client authenticated? if so, suppress all dnsbl checks | |
169 map<DNSBLP, status> checked; // status from those lists | |
170 mlfiPriv(); | |
171 ~mlfiPriv(); | |
172 }; | |
173 mlfiPriv::mlfiPriv() { | |
174 pthread_mutex_lock(&config_mutex); | |
175 pc = config; | |
176 pc->reference_count++; | |
177 pthread_mutex_unlock(&config_mutex); | |
178 ip = 0; | |
179 mailaddr = NULL; | |
180 } | |
181 mlfiPriv::~mlfiPriv() { | |
182 pthread_mutex_lock(&config_mutex); | |
183 pc->reference_count--; | |
184 pthread_mutex_unlock(&config_mutex); | |
185 if (mailaddr) free(mailaddr); | |
186 } | |
187 | |
188 #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) | |
189 | |
190 | |
191 //////////////////////////////////////////////// | |
192 // syslog a message | |
193 // | |
194 static void my_syslog(char *text); | |
195 static void my_syslog(char *text) { | |
196 pthread_mutex_lock(&syslog_mutex); | |
197 openlog("dnsbl", LOG_PID, LOG_MAIL); | |
198 syslog(LOG_NOTICE, "%s", text); | |
199 closelog(); | |
200 pthread_mutex_unlock(&syslog_mutex); | |
201 } | |
202 | |
203 | |
204 //////////////////////////////////////////////// | |
205 // register a global string | |
206 // | |
207 static char* register_string(char *name); | |
208 static char* register_string(char *name) { | |
209 string_set::iterator i = all_strings.find(name); | |
210 if (i != all_strings.end()) return *i; | |
211 char *x = strdup(name); | |
212 all_strings.insert(x); | |
213 return x; | |
214 } | |
215 | |
216 | |
217 static char* next_token(char *delim); | |
218 static char* next_token(char *delim) { | |
219 char *name = strtok(NULL, delim); | |
220 if (!name) return name; | |
221 return register_string(name); | |
222 } | |
223 | |
224 | |
225 //////////////////////////////////////////////// | |
226 // lookup an email address in the env_from or env_to maps | |
227 // | |
228 static char* lookup1(char *email, string_map map); | |
229 static char* lookup1(char *email, string_map map) { | |
230 string_map::iterator i = map.find(email); | |
231 if (i != map.end()) return (*i).second; | |
232 char *x = strchr(email, '@'); | |
233 if (!x) return DEFAULT; | |
234 x++; | |
235 i = map.find(x); | |
236 if (i != map.end()) return (*i).second; | |
237 return DEFAULT; | |
238 } | |
239 | |
240 | |
241 //////////////////////////////////////////////// | |
242 // lookup an email address in the env_from or env_to maps | |
243 // this email address is passed in from sendmail, and will | |
244 // always be enclosed in <>. It may have mixed case, just | |
245 // as the mail client sent it. | |
246 // | |
247 static char* lookup(char* email, string_map map); | |
248 static char* lookup(char* email, string_map map) { | |
249 int n = strlen(email)-2; | |
250 if (n < 1) return DEFAULT; // malformed | |
251 char *key = strdup(email+1); | |
252 key[n] = '\0'; | |
253 for (int i=0; i<n; i++) key[i] = tolower(key[i]); | |
254 char *rc = lookup1(key, map); | |
255 free(key); | |
256 return rc; | |
257 } | |
258 | |
259 | |
260 //////////////////////////////////////////////// | |
261 // find the dnsbl with a specific name | |
262 // | |
263 static DNSBLP find_dnsbl(CONFIG &dc, char *name); | |
264 static DNSBLP find_dnsbl(CONFIG &dc, char *name) { | |
265 dnsblp_map::iterator i = dc.dnsbls.find(name); | |
266 if (i == dc.dnsbls.end()) return NULL; | |
267 return (*i).second; | |
268 } | |
269 | |
270 | |
271 //////////////////////////////////////////////// | |
272 // find the dnsbll with a specific name | |
273 // | |
274 static DNSBLLP find_dnsbll(CONFIG &dc, char *name); | |
275 static DNSBLLP find_dnsbll(CONFIG &dc, char *name) { | |
276 dnsbllp_map::iterator i = dc.dnsblls.find(name); | |
277 if (i == dc.dnsblls.end()) return NULL; | |
278 return (*i).second; | |
279 } | |
280 | |
281 | |
282 //////////////////////////////////////////////// | |
283 // find the envfrom map with a specific name | |
284 // | |
285 static string_map* find_from_map(CONFIG &dc, char *name); | |
286 static string_map* find_from_map(CONFIG &dc, char *name) { | |
287 from_map::iterator i = dc.env_from.find(name); | |
288 if (i == dc.env_from.end()) return NULL; | |
289 return (*i).second; | |
290 } | |
291 | |
292 | |
293 static string_map& really_find_from_map(CONFIG &dc, char *name); | |
294 static string_map& really_find_from_map(CONFIG &dc, char *name) { | |
295 string_map *sm = find_from_map(dc, name); | |
296 if (!sm) { | |
297 sm = new string_map; | |
298 dc.env_from[name] = sm; | |
299 } | |
300 return *sm; | |
301 } | |
302 | |
303 | |
304 //////////////////////////////////////////////// | |
305 // check a single dnsbl - we don't try very hard, just | |
306 // using the default resolver retry settings. If we cannot | |
307 // get an answer, we just accept the mail. The caller | |
308 // must ensure thread safety. | |
309 // | |
310 static status check_single(int ip, DNSBL &bl); | |
311 static status check_single(int ip, DNSBL &bl) { | |
312 // make a dns question | |
313 const u_char *src = (const u_char *)&ip; | |
314 if (src[0] == 127) return oksofar; // don't do dns lookups on localhost | |
315 char question[NS_MAXDNAME]; | |
316 snprintf(question, sizeof(question), "%u.%u.%u.%u.%s.", src[3], src[2], src[1], src[0], bl.suffix); | |
317 // ask the question | |
318 u_char answer[NS_PACKETSZ]; | |
319 int length = res_search(question, ns_c_in, ns_t_a, answer, sizeof(answer)); | |
320 if (length < 0) return oksofar; // error in getting answer | |
321 // parse the answer | |
322 ns_msg handle; | |
323 ns_rr rr; | |
324 if (ns_initparse(answer, length, &handle) != 0) return oksofar; | |
325 int rrnum = 0; | |
326 while (ns_parserr(&handle, ns_s_an, rrnum++, &rr) == 0) { | |
327 if (ns_rr_type(rr) == ns_t_a) { | |
328 // we see an A record, implies blacklisted ip address | |
329 return reject; | |
330 } | |
331 } | |
332 return oksofar; | |
333 } | |
334 | |
335 | |
336 //////////////////////////////////////////////// | |
337 // check the dnsbls specified for this recipient | |
338 // | |
339 static status check_dnsbl(mlfiPriv &priv, DNSBLLP dnsbllp, DNSBLP &rejectlist); | |
340 static status check_dnsbl(mlfiPriv &priv, DNSBLLP dnsbllp, DNSBLP &rejectlist) { | |
341 if (priv.authenticated) return oksofar; | |
342 if (!dnsbllp) return oksofar; | |
343 DNSBLL &dnsbll = *dnsbllp; | |
344 for (DNSBLL::iterator i=dnsbll.begin(); i!=dnsbll.end(); i++) { | |
345 DNSBLP dp = *i; // non null by construction | |
346 status st; | |
347 map<DNSBLP, status>::iterator f = priv.checked.find(dp); | |
348 if (f == priv.checked.end()) { | |
349 // have not checked this list yet | |
350 pthread_mutex_lock(&resolve_mutex); | |
351 st = check_single(priv.ip, *dp); | |
352 pthread_mutex_unlock(&resolve_mutex); | |
353 rejectlist = dp; | |
354 priv.checked[dp] = st; | |
355 } | |
356 else { | |
357 st = (*f).second; | |
358 rejectlist = (*f).first; | |
359 } | |
360 if (st == reject) return st; | |
361 } | |
362 return oksofar; | |
363 } | |
364 | |
365 | |
366 //////////////////////////////////////////////// | |
367 // start of sendmail milter interfaces | |
368 // | |
369 sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr) | |
370 { | |
371 // allocate some private memory | |
372 mlfiPriv *priv = new mlfiPriv; | |
373 if (hostaddr->sa_family == AF_INET) { | |
374 priv->ip = ((struct sockaddr_in *)hostaddr)->sin_addr.s_addr; | |
375 } | |
376 | |
377 // save the private data | |
378 smfi_setpriv(ctx, (void*)priv); | |
379 | |
380 // continue processing | |
381 return SMFIS_CONTINUE; | |
382 } | |
383 | |
384 sfsistat mlfi_envfrom(SMFICTX *ctx, char **from) | |
385 { | |
386 mlfiPriv &priv = *MLFIPRIV; | |
387 priv.mailaddr = strdup(from[0]); | |
388 priv.authenticated = (smfi_getsymval(ctx, "{auth_authen}") != NULL); | |
389 return SMFIS_CONTINUE; | |
390 } | |
391 | |
392 sfsistat mlfi_envrcpt(SMFICTX *ctx, char **rcpt) | |
393 { | |
394 DNSBLP rejectlist = NULL; // list that caused the reject | |
395 status st = oksofar; | |
396 mlfiPriv &priv = *MLFIPRIV; | |
397 CONFIG &dc = *priv.pc; | |
398 char *rcptaddr = rcpt[0]; | |
399 char *dnsname = lookup(rcptaddr, dc.env_to_dnsbll); | |
400 char *fromname = lookup(rcptaddr, dc.env_to_chkfrom); | |
401 if ((strcmp(dnsname, BLACK) == 0) || | |
402 (strcmp(fromname, BLACK) == 0)) { | |
403 st = black; // two options to blacklist this recipient | |
404 } | |
405 else if (strcmp(fromname, WHITE) == 0) { | |
406 st = white; | |
407 } | |
408 else { | |
409 // check an env_from map | |
410 string_map *sm = find_from_map(dc, fromname); | |
411 if (sm != NULL) { | |
412 fromname = lookup(priv.mailaddr, *sm); // returns default if name not in map | |
413 if (strcmp(fromname, BLACK) == 0) { | |
414 st = black; // blacklist this envelope from value | |
415 } | |
416 if (strcmp(fromname, WHITE) == 0) { | |
417 st = white; // blacklist this envelope from value | |
418 } | |
419 } | |
420 } | |
421 if ((st == oksofar) && (strcmp(dnsname, WHITE) != 0)) { | |
422 // check dns lists | |
423 st = check_dnsbl(priv, find_dnsbll(dc, dnsname), rejectlist); | |
424 } | |
425 | |
426 if (st == reject) { | |
427 // reject the recipient based on some dnsbl | |
428 char adr[sizeof "255.255.255.255"]; | |
429 adr[0] = '\0'; | |
430 const char *rc = inet_ntop(AF_INET, (const u_char *)&priv.ip, adr, sizeof(adr)); | |
431 char buf[2000]; | |
432 snprintf(buf, sizeof(buf), rejectlist->message, adr, adr); | |
433 smfi_setreply(ctx, "550", "5.7.1", buf); | |
434 return SMFIS_REJECT; | |
435 } | |
436 else if (st == black) { | |
437 // reject the recipient based on blacklisting either from or to | |
438 smfi_setreply(ctx, "550", "5.7.1", "no such user"); | |
439 return SMFIS_REJECT; | |
440 } | |
441 else { | |
442 // accept the recipient | |
443 return SMFIS_CONTINUE; | |
444 } | |
445 } | |
446 | |
447 sfsistat mlfi_eom_or_abort(SMFICTX *ctx) | |
448 { | |
449 mlfiPriv &priv = *MLFIPRIV; | |
450 if (priv.mailaddr) { | |
451 free(priv.mailaddr); | |
452 priv.mailaddr = NULL; | |
453 } | |
454 return SMFIS_CONTINUE; | |
455 } | |
456 | |
457 sfsistat mlfi_close(SMFICTX *ctx) | |
458 { | |
459 mlfiPriv *priv = MLFIPRIV; | |
460 if (!priv) return SMFIS_CONTINUE; | |
461 delete priv; | |
462 smfi_setpriv(ctx, NULL); | |
463 return SMFIS_CONTINUE; | |
464 } | |
465 | |
466 struct smfiDesc smfilter = | |
467 { | |
468 "DNSBL", // filter name | |
469 SMFI_VERSION, // version code -- do not change | |
470 SMFIF_DELRCPT, // flags | |
471 mlfi_connect, // connection info filter | |
472 NULL, // SMTP HELO command filter | |
473 mlfi_envfrom, // envelope sender filter | |
474 mlfi_envrcpt, // envelope recipient filter | |
475 NULL, // header filter | |
476 NULL, // end of header | |
477 NULL, // body block filter | |
478 mlfi_eom_or_abort, // end of message | |
479 mlfi_eom_or_abort, // message aborted | |
480 mlfi_close, // connection cleanup | |
481 }; | |
482 | |
483 | |
484 static void dumpit(char *name, string_map map); | |
485 static void dumpit(char *name, string_map map) { | |
486 fprintf(stderr, "\n"); | |
487 for (string_map::iterator i=map.begin(); i!=map.end(); i++) { | |
488 fprintf(stderr, "%s %s->%s\n", name, (*i).first, (*i).second); | |
489 } | |
490 } | |
491 | |
492 | |
493 static void dumpit(from_map map); | |
494 static void dumpit(from_map map) { | |
495 fprintf(stderr, "\n"); | |
496 for (from_map::iterator i=map.begin(); i!=map.end(); i++) { | |
497 fprintf(stderr, "envfrom map %s\n", (*i).first); | |
498 string_map *sm = (*i).second; | |
499 dumpit("envelope from", *sm); | |
500 } | |
501 } | |
502 | |
503 | |
504 static void dumpit(); | |
505 static void dumpit() { | |
506 CONFIG &dc = *config; | |
507 fprintf(stderr, "dnsbls\n"); | |
508 for (dnsblp_map::iterator i=dc.dnsbls.begin(); i!=dc.dnsbls.end(); i++) { | |
509 fprintf(stderr, "%s %s %s\n", (*i).first, (*i).second->suffix, (*i).second->message); | |
510 } | |
511 fprintf(stderr, "dnsbl_lists\n"); | |
512 for (dnsbllp_map::iterator i=dc.dnsblls.begin(); i!=dc.dnsblls.end(); i++) { | |
513 char *name = (*i).first; | |
514 DNSBLL &dl = *((*i).second); | |
515 fprintf(stderr, "%s", name); | |
516 for (DNSBLL::iterator j=dl.begin(); j!=dl.end(); j++) { | |
517 DNSBL &d = **j; | |
518 fprintf(stderr, " %s", d.suffix); | |
519 } | |
520 fprintf(stderr, "\n"); | |
521 } | |
522 } | |
523 | |
524 | |
525 //////////////////////////////////////////////// | |
526 // load a single config file | |
527 // | |
528 static void load_conf(CONFIG &dc, char *fn); | |
529 static void load_conf(CONFIG &dc, char *fn) { | |
530 dc.config_files.push_back(fn); | |
531 map<char*, int, ltstr> commands; | |
532 enum {dummy, dnsbl, dnsbll, envfrom, envto, include}; | |
533 commands["dnsbl" ] = dnsbl; | |
534 commands["dnsbl_list"] = dnsbll; | |
535 commands["env_from" ] = envfrom; | |
536 commands["env_to" ] = envto; | |
537 commands["include" ] = include; | |
538 const int LINE_SIZE = 2000; | |
539 ifstream is(fn); | |
540 if (is.fail()) return; | |
541 char line[LINE_SIZE]; | |
542 char orig[LINE_SIZE]; | |
543 char *delim = " \t"; | |
544 int curline = 0; | |
545 while (!is.eof()) { | |
546 is.getline(line, LINE_SIZE); | |
547 snprintf(orig, sizeof(orig), "%s", line); | |
548 curline++; | |
549 int n = strlen(line); | |
550 for (int i=0; i<n; i++) line[i] = tolower(line[i]); | |
551 char *cmd = strtok(line, delim); | |
552 if (cmd && (cmd[0] != '#') && (cmd[0] != '\0')) { | |
553 // have a decent command | |
554 bool processed = false; | |
555 switch (commands[cmd]) { | |
556 case dnsbl: { | |
557 // have a new dnsbl to use | |
558 char *name = next_token(delim); | |
559 if (!name) break; // no name name | |
560 if (find_dnsbl(dc, name)) break; // duplicate entry | |
561 char *suff = strtok(NULL, delim); | |
562 if (!suff) break; // no dns suffic | |
563 char *msg = suff + strlen(suff); | |
564 if ((msg - line) >= strlen(orig)) break; // line ended with the dns suffix | |
565 msg = strchr(msg+1, '\''); | |
566 if (!msg) break; // no reply message template | |
567 msg++; // move over the leading ' | |
568 if ((msg - line) >= strlen(orig)) break; // line ended with the leading quote | |
569 char *last = strchr(msg, '\''); | |
570 if (!last) break; // no trailing quote | |
571 *last = '\0'; // make it a null terminator | |
572 dc.dnsbls[name] = new DNSBL(register_string(suff), register_string(msg)); | |
573 processed = true; | |
574 } break; | |
575 | |
576 case dnsbll: { | |
577 // define a new combination of dnsbls | |
578 char *name = next_token(delim); | |
579 if (!name) break; | |
580 if (find_dnsbll(dc, name)) break; // duplicate entry | |
581 char *list = next_token(delim); | |
582 if (!list || (*list == '\0') || (*list == '#')) break; | |
583 DNSBLLP d = new DNSBLL; | |
584 DNSBLP p = find_dnsbl(dc, list); | |
585 if (p) d->push_back(p); | |
586 while (true) { | |
587 list = next_token(delim); | |
588 if (!list || (*list == '\0') || (*list == '#')) break; | |
589 DNSBLP p = find_dnsbl(dc, list); | |
590 if (p) d->push_back(p); | |
591 } | |
592 dc.dnsblls[name] = d; | |
593 processed = true; | |
594 } break; | |
595 | |
596 case envfrom: { | |
597 // add an entry into the named string_map | |
598 char *name = next_token(delim); | |
599 if (!name) break; | |
600 char *from = next_token(delim); | |
601 if (!from) break; | |
602 char *list = next_token(delim); | |
603 if (!list) break; | |
604 if ((strcmp(list, WHITE) == 0) || | |
605 (strcmp(list, BLACK) == 0)) { | |
606 string_map &fm = really_find_from_map(dc, name); | |
607 fm[from] = list; | |
608 processed = true; | |
609 } | |
610 else { | |
611 // list may be the name of a previously defined from_map | |
612 string_map *m = find_from_map(dc, list); | |
613 if (m && (strcmp(list,name) != 0)) { | |
614 string_map &pm = *m; | |
615 string_map &fm = really_find_from_map(dc, name); | |
616 fm.insert(pm.begin(), pm.end()); | |
617 processed = true; | |
618 } | |
619 } | |
620 } break; | |
621 | |
622 case envto: { | |
623 // define the dnsbl_list and env_from maps to use for this recipient | |
624 char *to = next_token(delim); | |
625 if (!to) break; | |
626 char *list = next_token(delim); | |
627 if (!list) break; | |
628 char *from = next_token(delim); | |
629 if (!from) break; | |
630 dc.env_to_dnsbll[to] = list; | |
631 dc.env_to_chkfrom[to] = from; | |
632 processed = true; | |
633 } break; | |
634 | |
635 case include: { | |
636 char *fn = next_token(delim); | |
637 if (fn) { | |
638 bool ok = true; | |
639 for (string_list::iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) { | |
640 char *f = *i; | |
641 if (strcmp(f, fn) == 0) { | |
642 my_syslog("recursive include file detected"); | |
643 ok = false; | |
644 break; | |
645 } | |
646 } | |
647 if (ok) { | |
648 load_conf(dc, fn); | |
649 processed = true; | |
650 } | |
651 } | |
652 } break; | |
653 | |
654 default: { | |
655 } break; | |
656 } | |
657 if (!processed) { | |
658 pthread_mutex_lock(&syslog_mutex); | |
659 openlog("dnsbl", LOG_PID, LOG_MAIL); | |
660 syslog(LOG_ERR, "ignoring file %s line %d : %s\n", fn, curline, orig); | |
661 closelog(); | |
662 pthread_mutex_unlock(&syslog_mutex); | |
663 } | |
664 } | |
665 } | |
666 is.close(); | |
667 } | |
668 | |
669 | |
670 //////////////////////////////////////////////// | |
671 // reload the config | |
672 // | |
673 static CONFIG* new_conf(); | |
674 static CONFIG* new_conf() { | |
675 my_syslog("loading new configuration"); | |
676 CONFIG *newc = new CONFIG; | |
677 load_conf(*newc, "dnsbl.conf"); | |
678 newc->load_time = time(NULL); | |
679 return newc; | |
680 } | |
681 | |
682 | |
683 //////////////////////////////////////////////// | |
684 // thread to watch the old config files for changes | |
685 // and reload when needed. we also cleanup old | |
686 // configs whose reference count has gone to zero. | |
687 // | |
688 static void* config_loader(void *arg); | |
689 static void* config_loader(void *arg) { | |
690 typedef set<CONFIG *> configp_set; | |
691 configp_set old_configs; | |
692 while (true) { | |
693 sleep(180); // look for modifications every 3 minutes | |
694 CONFIG &dc = *config; | |
695 time_t then = dc.load_time; | |
696 struct stat st; | |
697 bool reload = false; | |
698 for (string_list::iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) { | |
699 char *fn = *i; | |
700 if (stat(fn, &st)) reload = true; // file disappeared | |
701 else if (st.st_mtime > then) reload = true; // file modified | |
702 if (reload) break; | |
703 } | |
704 if (reload) { | |
705 CONFIG *newc = new_conf(); | |
706 // replace the global config pointer | |
707 pthread_mutex_lock(&config_mutex); | |
708 CONFIG *old = config; | |
709 config = newc; | |
710 pthread_mutex_unlock(&config_mutex); | |
711 // dumpit(env_from); | |
712 // dumpit("envelope to dnsbl", env_to_dnsbl); | |
713 // dumpit("envelope to check from", env_to_chkfrom); | |
714 // dumpit(); | |
715 if (old) old_configs.insert(old); | |
716 } | |
717 // now look for old configs with zero ref counts | |
718 for (configp_set::iterator i=old_configs.begin(); i!=old_configs.end(); ) { | |
719 CONFIG *old = *i; | |
720 if (!old->reference_count) { | |
721 delete old; // destructor does all the work | |
722 old_configs.erase(i++); | |
723 } | |
724 else i++; | |
725 } | |
726 } | |
727 } | |
728 | |
729 | |
730 static void usage(char *prog); | |
731 static void usage(char *prog) | |
732 { | |
733 fprintf(stderr, "Usage: %s -p socket-addr [-t timeout]\n", prog); | |
734 fprintf(stderr, "where socket-addr is for the connection to sendmail and should be one of\n"); | |
735 fprintf(stderr, " inet:port@local-ip-address\n"); | |
736 fprintf(stderr, " local:local-domain-socket-file-name\n"); | |
737 } | |
738 | |
739 | |
740 int main(int argc, char**argv) | |
741 { | |
742 bool setconn = FALSE; | |
743 int c; | |
744 const char *args = "p:s:h"; | |
745 extern char *optarg; | |
746 | |
747 // Process command line options | |
748 while ((c = getopt(argc, argv, args)) != -1) { | |
749 switch (c) { | |
750 case 'p': | |
751 if (optarg == NULL || *optarg == '\0') { | |
752 fprintf(stderr, "Illegal conn: %s\n", optarg); | |
753 exit(EX_USAGE); | |
754 } | |
755 if (smfi_setconn(optarg) == MI_FAILURE) { | |
756 fprintf(stderr, "smfi_setconn failed\n"); | |
757 exit(EX_SOFTWARE); | |
758 } | |
759 | |
760 if (strncasecmp(optarg, "unix:", 5) == 0) unlink(optarg + 5); | |
761 else if (strncasecmp(optarg, "local:", 6) == 0) unlink(optarg + 6); | |
762 setconn = TRUE; | |
763 break; | |
764 | |
765 case 't': | |
766 if (optarg == NULL || *optarg == '\0') { | |
767 fprintf(stderr, "Illegal timeout: %s\n", optarg); | |
768 exit(EX_USAGE); | |
769 } | |
770 if (smfi_settimeout(atoi(optarg)) == MI_FAILURE) { | |
771 fprintf(stderr, "smfi_settimeout failed\n"); | |
772 exit(EX_SOFTWARE); | |
773 } | |
774 break; | |
775 | |
776 case 'h': | |
777 default: | |
778 usage(argv[0]); | |
779 exit(EX_USAGE); | |
780 } | |
781 } | |
782 if (!setconn) { | |
783 fprintf(stderr, "%s: Missing required -p argument\n", argv[0]); | |
784 usage(argv[0]); | |
785 exit(EX_USAGE); | |
786 } | |
787 if (smfi_register(smfilter) == MI_FAILURE) { | |
788 fprintf(stderr, "smfi_register failed\n"); | |
789 exit(EX_UNAVAILABLE); | |
790 } | |
791 | |
792 // switch to background mode | |
793 if (daemon(1,0) < 0) { | |
794 fprintf(stderr, "daemon() call failed\n"); | |
795 exit(EX_UNAVAILABLE); | |
796 } | |
797 | |
798 // initialize the thread sync objects | |
799 pthread_mutex_init(&config_mutex, 0); | |
800 pthread_mutex_init(&syslog_mutex, 0); | |
801 pthread_mutex_init(&resolve_mutex, 0); | |
802 | |
803 // load the initial config | |
804 config = new_conf(); | |
805 | |
806 // only create threads after the fork() in daemon | |
807 pthread_t tid; | |
808 if (pthread_create(&tid, 0, config_loader, 0)) | |
809 my_syslog("failed to create config loader thread"); | |
810 if (pthread_detach(tid)) | |
811 my_syslog("failed to detach config loader thread"); | |
812 | |
813 // write the pid | |
814 const char *pidpath = "/var/run/dnsbl.pid"; | |
815 unlink(pidpath); | |
816 FILE *f = fopen(pidpath, "w"); | |
817 if (f) { | |
818 fprintf(f, "-%d\n", (u_int)getpgrp()); | |
819 fclose(f); | |
820 } | |
821 | |
822 int rc = smfi_main(); | |
823 } |