Mercurial > dnsbl
comparison src/dnsbl.cpp @ 8:dbe18921f741
integration work on url scanner
author | carl |
---|---|
date | Thu, 22 Apr 2004 11:25:45 -0700 |
parents | 793ac9cc114d |
children | 8c65411cd7ab |
comparison
equal
deleted
inserted
replaced
7:93ff6d1ef647 | 8:dbe18921f741 |
---|---|
17 | 17 |
18 // from sendmail sample | 18 // from sendmail sample |
19 #include <sys/types.h> | 19 #include <sys/types.h> |
20 #include <sys/stat.h> | 20 #include <sys/stat.h> |
21 #include <errno.h> | 21 #include <errno.h> |
22 #include <stdio.h> | 22 //#include <stdio.h> |
23 #include <stdlib.h> | 23 #include <stdlib.h> |
24 #include <string.h> | 24 #include <string.h> |
25 #include <sysexits.h> | 25 #include <sysexits.h> |
26 #include <unistd.h> | 26 #include <unistd.h> |
27 | 27 |
50 // misc stuff needed here | 50 // misc stuff needed here |
51 #include <ctype.h> | 51 #include <ctype.h> |
52 #include <fstream> | 52 #include <fstream> |
53 #include <syslog.h> | 53 #include <syslog.h> |
54 | 54 |
55 | 55 static char* dnsbl_version="$Id$"; |
56 static char* version="$Id$"; | 56 |
57 #define DEFAULT "default" | |
58 #define WHITE "white" | |
59 #define BLACK "black" | |
60 #define OK "ok" | |
61 #define MANY "many" | |
62 | |
63 enum status {oksofar, // not rejected yet | |
64 white, // whitelisted by envelope from | |
65 black, // blacklisted by envelope from or to | |
66 reject}; // rejected by a dns list | |
57 | 67 |
58 using namespace std; | 68 using namespace std; |
59 | |
60 | 69 |
61 extern "C" { | 70 extern "C" { |
62 #include "libmilter/mfapi.h" | 71 #include "libmilter/mfapi.h" |
63 sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr); | 72 sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr); |
64 sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv); | 73 sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv); |
65 sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv); | 74 sfsistat mlfi_envrcpt(SMFICTX *ctx, char **argv); |
66 sfsistat mlfi_eom_or_abort(SMFICTX *ctx); | 75 sfsistat mlfi_body(SMFICTX *ctx, u_char *data, size_t len); |
76 sfsistat mlfi_eom(SMFICTX *ctx); | |
77 sfsistat mlfi_abort(SMFICTX *ctx); | |
67 sfsistat mlfi_close(SMFICTX *ctx); | 78 sfsistat mlfi_close(SMFICTX *ctx); |
68 } | 79 } |
69 | 80 |
70 struct ltstr { | 81 struct ltstr { |
71 bool operator()(char* s1, char* s2) const { | 82 bool operator()(char* s1, char* s2) const { |
102 dnsblp_map dnsbls; | 113 dnsblp_map dnsbls; |
103 dnsbllp_map dnsblls; | 114 dnsbllp_map dnsblls; |
104 from_map env_from; | 115 from_map env_from; |
105 string_map env_to_dnsbll; // map recipient to a named dnsbll | 116 string_map env_to_dnsbll; // map recipient to a named dnsbll |
106 string_map env_to_chkfrom; // map recipient to a named from map | 117 string_map env_to_chkfrom; // map recipient to a named from map |
118 char * content_suffix; // for sbl url body filtering | |
119 char * content_message; | |
107 CONFIG(); | 120 CONFIG(); |
108 ~CONFIG(); | 121 ~CONFIG(); |
109 }; | 122 }; |
110 CONFIG::CONFIG() { | 123 CONFIG::CONFIG() { |
111 reference_count = 0; | 124 reference_count = 0; |
112 load_time = 0; | 125 load_time = 0; |
126 content_suffix = NULL; | |
127 content_message = NULL; | |
113 } | 128 } |
114 CONFIG::~CONFIG() { | 129 CONFIG::~CONFIG() { |
115 for (dnsblp_map::iterator i=dnsbls.begin(); i!=dnsbls.end(); i++) { | 130 for (dnsblp_map::iterator i=dnsbls.begin(); i!=dnsbls.end(); i++) { |
116 DNSBLP d = (*i).second; | 131 DNSBLP d = (*i).second; |
117 delete d; | 132 delete d; |
133 static pthread_mutex_t syslog_mutex; | 148 static pthread_mutex_t syslog_mutex; |
134 static pthread_mutex_t resolve_mutex; | 149 static pthread_mutex_t resolve_mutex; |
135 | 150 |
136 | 151 |
137 | 152 |
138 //////////////////////////////////////////////// | 153 // include the content scanner |
139 // predefined names | 154 #include "scanner.cpp" |
140 // | 155 |
141 #define DEFAULT "default" | 156 |
142 #define WHITE "white" | 157 //////////////////////////////////////////////// |
143 #define BLACK "black" | 158 // helper to discard the strings held by a string_set |
144 #define OK "ok" | 159 // |
145 #define MANY "many" | 160 static void discard(string_set s); |
161 static void discard(string_set s) { | |
162 for (string_set::iterator i=s.begin(); i!=s.end(); i++) { | |
163 free(*i); | |
164 } | |
165 } | |
146 | 166 |
147 | 167 |
148 //////////////////////////////////////////////// | 168 //////////////////////////////////////////////// |
149 // mail filter private data, held for us by sendmail | 169 // mail filter private data, held for us by sendmail |
150 // | 170 // |
151 enum status {oksofar, // not rejected yet | |
152 white, // whitelisted by envelope from | |
153 black, // blacklisted by envelope from or to | |
154 reject}; // rejected by a dns list | |
155 struct mlfiPriv | 171 struct mlfiPriv |
156 { | 172 { |
157 CONFIG *pc; // global context with our maps | 173 // connection specific data |
158 int ip; // ip4 address of the smtp client | 174 CONFIG *pc; // global context with our maps |
175 int ip; // ip4 address of the smtp client | |
176 map<DNSBLP, status> checked; // status from those lists | |
177 // message specific data | |
159 char *mailaddr; // envelope from value | 178 char *mailaddr; // envelope from value |
160 bool authenticated; // client authenticated? if so, suppress all dnsbl checks | 179 bool authenticated; // client authenticated? if so, suppress all dnsbl checks |
161 map<DNSBLP, status> checked; // status from those lists | 180 bool have_whites; // have at least one whitelisted recipient? need to accept content and remove all non-whitelisted recipients if it fails |
181 bool only_whites; // every recipient is whitelisted? | |
182 url_scanner *scanner; // object to handle body scanning | |
183 string_set non_whites; // remember the non-whitelisted recipients so we can remove them if need be | |
184 string_set urls; // remember the urls that we have checked | |
162 mlfiPriv(); | 185 mlfiPriv(); |
163 ~mlfiPriv(); | 186 ~mlfiPriv(); |
187 void reset(bool final = false); // for a new message | |
164 }; | 188 }; |
165 mlfiPriv::mlfiPriv() { | 189 mlfiPriv::mlfiPriv() { |
166 pthread_mutex_lock(&config_mutex); | 190 pthread_mutex_lock(&config_mutex); |
167 pc = config; | 191 pc = config; |
168 pc->reference_count++; | 192 pc->reference_count++; |
169 pthread_mutex_unlock(&config_mutex); | 193 pthread_mutex_unlock(&config_mutex); |
170 ip = 0; | 194 ip = 0; |
171 mailaddr = NULL; | 195 mailaddr = NULL; |
196 authenticated = false; | |
197 have_whites = false; | |
198 only_whites = true; | |
199 scanner = new url_scanner(&urls); | |
172 } | 200 } |
173 mlfiPriv::~mlfiPriv() { | 201 mlfiPriv::~mlfiPriv() { |
174 pthread_mutex_lock(&config_mutex); | 202 pthread_mutex_lock(&config_mutex); |
175 pc->reference_count--; | 203 pc->reference_count--; |
176 pthread_mutex_unlock(&config_mutex); | 204 pthread_mutex_unlock(&config_mutex); |
205 reset(true); | |
206 } | |
207 void mlfiPriv::reset(bool final) { | |
177 if (mailaddr) free(mailaddr); | 208 if (mailaddr) free(mailaddr); |
209 delete scanner; | |
210 discard(non_whites); | |
211 discard(urls); | |
212 if (!final) { | |
213 mailaddr = NULL; | |
214 authenticated = false; | |
215 have_whites = false; | |
216 only_whites = true; | |
217 scanner = new url_scanner(&urls); | |
218 } | |
178 } | 219 } |
179 | 220 |
180 #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) | 221 #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) |
181 | 222 |
182 | 223 |
292 return *sm; | 333 return *sm; |
293 } | 334 } |
294 | 335 |
295 | 336 |
296 //////////////////////////////////////////////// | 337 //////////////////////////////////////////////// |
297 // check a single dnsbl - we don't try very hard, just | 338 // |
298 // using the default resolver retry settings. If we cannot | 339 // ask a dns question and get an A record answer - we don't try |
299 // get an answer, we just accept the mail. The caller | 340 // very hard, just using the default resolver retry settings. |
300 // must ensure thread safety. | 341 // If we cannot get an answer, we just accept the mail. The |
301 // | 342 // caller must ensure thread safety. |
302 static status check_single(int ip, DNSBL &bl); | 343 // |
303 static status check_single(int ip, DNSBL &bl) { | 344 // |
345 static int dns_interface(char *question); | |
346 static int dns_interface(char *question) { | |
347 u_char answer[NS_PACKETSZ]; | |
348 int length = res_search(question, ns_c_in, ns_t_a, answer, sizeof(answer)); | |
349 if (length < 0) return 0; // error in getting answer | |
350 // parse the answer | |
351 ns_msg handle; | |
352 ns_rr rr; | |
353 if (ns_initparse(answer, length, &handle) != 0) return 0; | |
354 int rrnum = 0; | |
355 while (ns_parserr(&handle, ns_s_an, rrnum++, &rr) == 0) { | |
356 if (ns_rr_type(rr) == ns_t_a) { | |
357 int address; | |
358 memcpy(&address, ns_rr_rdata(rr), sizeof(address)); | |
359 return address; | |
360 } | |
361 } | |
362 return 0; | |
363 } | |
364 | |
365 static int protected_dns_interface(char *question); | |
366 static int protected_dns_interface(char *question) { | |
367 int ans; | |
368 pthread_mutex_lock(&resolve_mutex); | |
369 ans = dns_interface(question); | |
370 pthread_mutex_unlock(&resolve_mutex); | |
371 return ans; | |
372 | |
373 } | |
374 | |
375 //////////////////////////////////////////////// | |
376 // check a single dnsbl | |
377 // | |
378 static status check_single(int ip, char *suffix); | |
379 static status check_single(int ip, char *suffix) { | |
304 // make a dns question | 380 // make a dns question |
305 const u_char *src = (const u_char *)&ip; | 381 const u_char *src = (const u_char *)&ip; |
306 if (src[0] == 127) return oksofar; // don't do dns lookups on localhost | 382 if (src[0] == 127) return oksofar; // don't do dns lookups on localhost |
307 char question[NS_MAXDNAME]; | 383 char question[NS_MAXDNAME]; |
308 snprintf(question, sizeof(question), "%u.%u.%u.%u.%s.", src[3], src[2], src[1], src[0], bl.suffix); | 384 snprintf(question, sizeof(question), "%u.%u.%u.%u.%s.", src[3], src[2], src[1], src[0], suffix); |
309 // ask the question | 385 // ask the question, if we get an A record it implies a blacklisted ip address |
310 u_char answer[NS_PACKETSZ]; | 386 return (protected_dns_interface(question)) ? reject : oksofar; |
311 int length = res_search(question, ns_c_in, ns_t_a, answer, sizeof(answer)); | 387 } |
312 if (length < 0) return oksofar; // error in getting answer | 388 |
313 // parse the answer | 389 |
314 ns_msg handle; | 390 //////////////////////////////////////////////// |
315 ns_rr rr; | 391 // check a single dnsbl |
316 if (ns_initparse(answer, length, &handle) != 0) return oksofar; | 392 // |
317 int rrnum = 0; | 393 static status check_single(int ip, DNSBL &bl); |
318 while (ns_parserr(&handle, ns_s_an, rrnum++, &rr) == 0) { | 394 static status check_single(int ip, DNSBL &bl) { |
319 if (ns_rr_type(rr) == ns_t_a) { | 395 return check_single(ip, bl.suffix); |
320 // we see an A record, implies blacklisted ip address | |
321 return reject; | |
322 } | |
323 } | |
324 return oksofar; | |
325 } | 396 } |
326 | 397 |
327 | 398 |
328 //////////////////////////////////////////////// | 399 //////////////////////////////////////////////// |
329 // check the dnsbls specified for this recipient | 400 // check the dnsbls specified for this recipient |
337 DNSBLP dp = *i; // non null by construction | 408 DNSBLP dp = *i; // non null by construction |
338 status st; | 409 status st; |
339 map<DNSBLP, status>::iterator f = priv.checked.find(dp); | 410 map<DNSBLP, status>::iterator f = priv.checked.find(dp); |
340 if (f == priv.checked.end()) { | 411 if (f == priv.checked.end()) { |
341 // have not checked this list yet | 412 // have not checked this list yet |
342 pthread_mutex_lock(&resolve_mutex); | 413 st = check_single(priv.ip, *dp); |
343 st = check_single(priv.ip, *dp); | |
344 pthread_mutex_unlock(&resolve_mutex); | |
345 rejectlist = dp; | 414 rejectlist = dp; |
346 priv.checked[dp] = st; | 415 priv.checked[dp] = st; |
347 } | 416 } |
348 else { | 417 else { |
349 st = (*f).second; | 418 st = (*f).second; |
350 rejectlist = (*f).first; | 419 rejectlist = (*f).first; |
351 } | 420 } |
352 if (st == reject) return st; | 421 if (st == reject) return st; |
353 } | 422 } |
354 return oksofar; | 423 return oksofar; |
424 } | |
425 | |
426 | |
427 //////////////////////////////////////////////// | |
428 // check the dnsbls specified for this recipient | |
429 // | |
430 static status check_urls(mlfiPriv &priv, char *&url, int &ip); | |
431 static status check_urls(mlfiPriv &priv, char *&url, int &ip) { | |
432 CONFIG &dc = *priv.pc; | |
433 if (!dc.content_suffix) return oksofar; | |
434 int count = 0; | |
435 for (string_set::iterator i=priv.urls.begin(); i!=priv.urls.end(); i++) { | |
436 count++; | |
437 if (count > 20) break; // silly to check too many urls | |
438 url = *i; | |
439 char buf[200]; | |
440 snprintf(buf, sizeof(buf), "looking for url %s", url); | |
441 my_syslog(buf); | |
442 ip = protected_dns_interface(url); | |
443 if (ip) { | |
444 status st = check_single(ip, dc.content_suffix); | |
445 if (st == reject) return st; | |
446 } | |
447 } | |
355 } | 448 } |
356 | 449 |
357 | 450 |
358 //////////////////////////////////////////////// | 451 //////////////////////////////////////////////// |
359 // start of sendmail milter interfaces | 452 // start of sendmail milter interfaces |
417 | 510 |
418 if (st == reject) { | 511 if (st == reject) { |
419 // reject the recipient based on some dnsbl | 512 // reject the recipient based on some dnsbl |
420 char adr[sizeof "255.255.255.255"]; | 513 char adr[sizeof "255.255.255.255"]; |
421 adr[0] = '\0'; | 514 adr[0] = '\0'; |
422 const char *rc = inet_ntop(AF_INET, (const u_char *)&priv.ip, adr, sizeof(adr)); | 515 inet_ntop(AF_INET, (const u_char *)&priv.ip, adr, sizeof(adr)); |
423 char buf[2000]; | 516 char buf[2000]; |
424 snprintf(buf, sizeof(buf), rejectlist->message, adr, adr); | 517 snprintf(buf, sizeof(buf), rejectlist->message, adr, adr); |
425 smfi_setreply(ctx, "550", "5.7.1", buf); | 518 smfi_setreply(ctx, "550", "5.7.1", buf); |
426 return SMFIS_REJECT; | 519 return SMFIS_REJECT; |
427 } | 520 } |
430 smfi_setreply(ctx, "550", "5.7.1", "no such user"); | 523 smfi_setreply(ctx, "550", "5.7.1", "no such user"); |
431 return SMFIS_REJECT; | 524 return SMFIS_REJECT; |
432 } | 525 } |
433 else { | 526 else { |
434 // accept the recipient | 527 // accept the recipient |
528 if (st == oksofar) { | |
529 // but remember the non-whites | |
530 priv.non_whites.insert(strdup(rcptaddr)); | |
531 priv.only_whites = false; | |
532 } | |
533 if (st == white) { | |
534 priv.have_whites = true; | |
535 } | |
435 return SMFIS_CONTINUE; | 536 return SMFIS_CONTINUE; |
436 } | 537 } |
437 } | 538 } |
438 | 539 |
439 sfsistat mlfi_eom_or_abort(SMFICTX *ctx) | 540 sfsistat mlfi_body(SMFICTX *ctx, u_char *data, size_t len) |
440 { | 541 { |
441 mlfiPriv &priv = *MLFIPRIV; | 542 mlfiPriv &priv = *MLFIPRIV; |
442 if (priv.mailaddr) { | 543 if (priv.authenticated) return SMFIS_CONTINUE; |
443 free(priv.mailaddr); | 544 if (priv.only_whites) return SMFIS_CONTINUE; |
444 priv.mailaddr = NULL; | 545 priv.scanner->scan(data, len); |
445 } | 546 } |
547 | |
548 sfsistat mlfi_eom(SMFICTX *ctx) | |
549 { | |
550 sfsistat rc; | |
551 mlfiPriv &priv = *MLFIPRIV; | |
552 char *url = NULL; | |
553 int ip; | |
554 // process end of message | |
555 if (priv.authenticated || | |
556 priv.only_whites || | |
557 (check_urls(priv, url, ip) == oksofar)) rc = SMFIS_CONTINUE; | |
558 else { | |
559 if (!priv.have_whites) { | |
560 // can reject the entire message | |
561 char adr[sizeof "255.255.255.255"]; | |
562 adr[0] = '\0'; | |
563 inet_ntop(AF_INET, (const u_char *)&ip, adr, sizeof(adr)); | |
564 char buf[2000]; | |
565 snprintf(buf, sizeof(buf), priv.pc->content_message, url, adr); | |
566 smfi_setreply(ctx, "550", "5.7.1", buf); | |
567 rc = SMFIS_REJECT; | |
568 } | |
569 else { | |
570 // need to accept it but remove the recipients that don't want it | |
571 for (string_set::iterator i=priv.non_whites.begin(); i!=priv.non_whites.end(); i++) { | |
572 char *rcpt = *i; | |
573 smfi_delrcpt(ctx, rcpt); | |
574 } | |
575 rc = SMFIS_CONTINUE; | |
576 } | |
577 } | |
578 // reset for a new message on the same connection | |
579 mlfi_abort(ctx); | |
580 return rc; | |
581 } | |
582 | |
583 sfsistat mlfi_abort(SMFICTX *ctx) | |
584 { | |
585 mlfiPriv &priv = *MLFIPRIV; | |
586 priv.reset(); | |
446 return SMFIS_CONTINUE; | 587 return SMFIS_CONTINUE; |
447 } | 588 } |
448 | 589 |
449 sfsistat mlfi_close(SMFICTX *ctx) | 590 sfsistat mlfi_close(SMFICTX *ctx) |
450 { | 591 { |
464 NULL, // SMTP HELO command filter | 605 NULL, // SMTP HELO command filter |
465 mlfi_envfrom, // envelope sender filter | 606 mlfi_envfrom, // envelope sender filter |
466 mlfi_envrcpt, // envelope recipient filter | 607 mlfi_envrcpt, // envelope recipient filter |
467 NULL, // header filter | 608 NULL, // header filter |
468 NULL, // end of header | 609 NULL, // end of header |
469 NULL, // body block filter | 610 mlfi_body, // body block filter |
470 mlfi_eom_or_abort, // end of message | 611 mlfi_eom, // end of message |
471 mlfi_eom_or_abort, // message aborted | 612 mlfi_abort, // message aborted |
472 mlfi_close, // connection cleanup | 613 mlfi_close, // connection cleanup |
473 }; | 614 }; |
474 | 615 |
475 | 616 |
476 static void dumpit(char *name, string_map map); | 617 static void dumpit(char *name, string_map map); |
608 | 749 |
609 static void load_conf(CONFIG &dc, char *fn); | 750 static void load_conf(CONFIG &dc, char *fn); |
610 static void load_conf(CONFIG &dc, char *fn) { | 751 static void load_conf(CONFIG &dc, char *fn) { |
611 dc.config_files.push_back(fn); | 752 dc.config_files.push_back(fn); |
612 map<char*, int, ltstr> commands; | 753 map<char*, int, ltstr> commands; |
613 enum {dummy, dnsbl, dnsbll, envfrom, envto, include, includedcc}; | 754 enum {dummy, content, dnsbl, dnsbll, envfrom, envto, include, includedcc}; |
755 commands["content" ] = content; | |
614 commands["dnsbl" ] = dnsbl; | 756 commands["dnsbl" ] = dnsbl; |
615 commands["dnsbl_list" ] = dnsbll; | 757 commands["dnsbl_list" ] = dnsbll; |
616 commands["env_from" ] = envfrom; | 758 commands["env_from" ] = envfrom; |
617 commands["env_to" ] = envto; | 759 commands["env_to" ] = envto; |
618 commands["include" ] = include; | 760 commands["include" ] = include; |
633 char *cmd = strtok(line, delim); | 775 char *cmd = strtok(line, delim); |
634 if (cmd && (cmd[0] != '#') && (cmd[0] != '\0')) { | 776 if (cmd && (cmd[0] != '#') && (cmd[0] != '\0')) { |
635 // have a decent command | 777 // have a decent command |
636 bool processed = false; | 778 bool processed = false; |
637 switch (commands[cmd]) { | 779 switch (commands[cmd]) { |
780 case content: { | |
781 char *suff = strtok(NULL, delim); | |
782 if (!suff) break; // no dns suffic | |
783 char *msg = suff + strlen(suff); | |
784 if ((msg - line) >= strlen(orig)) break; // line ended with the dns suffix | |
785 msg = strchr(msg+1, '\''); | |
786 if (!msg) break; // no reply message template | |
787 msg++; // move over the leading ' | |
788 if ((msg - line) >= strlen(orig)) break; // line ended with the leading quote | |
789 char *last = strchr(msg, '\''); | |
790 if (!last) break; // no trailing quote | |
791 *last = '\0'; // make it a null terminator | |
792 dc.content_suffix = register_string(suff); | |
793 dc.content_message = register_string(msg); | |
794 processed = true; | |
795 } break; | |
796 | |
638 case dnsbl: { | 797 case dnsbl: { |
639 // have a new dnsbl to use | 798 // have a new dnsbl to use |
640 char *name = next_token(delim); | 799 char *name = next_token(delim); |
641 if (!name) break; // no name name | 800 if (!name) break; // no name name |
642 if (find_dnsbl(dc, name)) break; // duplicate entry | 801 if (find_dnsbl(dc, name)) break; // duplicate entry |
909 fclose(f); | 1068 fclose(f); |
910 } | 1069 } |
911 | 1070 |
912 return smfi_main(); | 1071 return smfi_main(); |
913 } | 1072 } |
1073 |