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