Mercurial > dnsbl
comparison src/dnsbl.cpp @ 72:e6a2d0be7c5e
start coding on new config syntax
author | carl |
---|---|
date | Sun, 10 Jul 2005 13:28:33 -0700 |
parents | 1ab70970c8c8 |
children | 2b369f7db7bf |
comparison
equal
deleted
inserted
replaced
71:dd21c8e13074 | 72:e6a2d0be7c5e |
---|---|
65 #include <arpa/nameser.h> | 65 #include <arpa/nameser.h> |
66 #include <resolv.h> | 66 #include <resolv.h> |
67 | 67 |
68 // misc stuff needed here | 68 // misc stuff needed here |
69 #include <ctype.h> | 69 #include <ctype.h> |
70 #include <fstream> | |
71 #include <syslog.h> | 70 #include <syslog.h> |
72 #include <pwd.h> | 71 #include <pwd.h> |
73 #include <sys/wait.h> /* header for waitpid() and various macros */ | 72 #include <sys/wait.h> /* header for waitpid() and various macros */ |
74 #include <signal.h> /* header for signal functions */ | 73 #include <signal.h> /* header for signal functions */ |
75 | 74 |
76 static char* dnsbl_version="$Id$"; | 75 #include "includes.h" |
77 | 76 |
78 #define DEFAULT "default" | 77 static char* dnsbl_version="$Id:"; |
79 #define WHITE "white" | 78 |
80 #define BLACK "black" | |
81 #define OK "ok" | |
82 #define MANY "many" | |
83 | |
84 enum status {oksofar, // not rejected yet | |
85 white, // whitelisted by envelope from | |
86 black, // blacklisted by envelope from or to | |
87 reject, // rejected by a dns list | |
88 reject_tag, // too many bad html tags | |
89 reject_host}; // too many hosts/urls in body | |
90 | |
91 using namespace std; | |
92 | 79 |
93 extern "C" { | 80 extern "C" { |
94 #include "libmilter/mfapi.h" | 81 #include "libmilter/mfapi.h" |
95 sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr); | 82 sfsistat mlfi_connect(SMFICTX *ctx, char *hostname, _SOCK_ADDR *hostaddr); |
96 sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv); | 83 sfsistat mlfi_envfrom(SMFICTX *ctx, char **argv); |
100 sfsistat mlfi_abort(SMFICTX *ctx); | 87 sfsistat mlfi_abort(SMFICTX *ctx); |
101 sfsistat mlfi_close(SMFICTX *ctx); | 88 sfsistat mlfi_close(SMFICTX *ctx); |
102 void sig_chld(int signo); | 89 void sig_chld(int signo); |
103 } | 90 } |
104 | 91 |
105 struct ltstr { | 92 bool debug_syslog = false; |
106 bool operator()(char* s1, char* s2) const { | 93 bool syslog_opened = false; |
107 return strcmp(s1, s2) < 0; | 94 bool loader_run = true; // used to stop the config loader thread |
108 } | 95 CONFIG * config = NULL; // protected by the config_mutex |
109 }; | 96 int generation = 0; // protected by the config_mutex |
110 | 97 |
111 struct DNSBL { | 98 pthread_mutex_t config_mutex; |
112 char *suffix; // blacklist suffix like blackholes.five-ten-sg.com | 99 pthread_mutex_t syslog_mutex; |
113 char *message; // error message with one or two %s operators for the ip address replacement | 100 pthread_mutex_t resolve_mutex; |
114 DNSBL(char *s, char *m); | 101 pthread_mutex_t fd_pool_mutex; |
115 }; | 102 |
116 DNSBL::DNSBL(char *s, char *m) { | 103 std::set<int> fd_pool; |
117 suffix = s; | 104 int NULL_SOCKET = -1; |
118 message = m; | 105 char *resolver_port = NULL; // unix domain socket to talk to the dns resolver process |
119 } | 106 int resolver_socket = NULL_SOCKET; // socket used to listen for resolver requests |
120 | 107 time_t ERROR_SOCKET_TIME = 60; // number of seconds between attempts to open the spam filter socket |
121 typedef DNSBL * DNSBLP; | 108 time_t last_error_time; |
122 typedef list<DNSBLP> DNSBLL; | 109 int resolver_sock_count = 0; // protected with fd_pool_mutex |
123 typedef DNSBLL * DNSBLLP; | 110 int resolver_pool_size = 0; // protected with fd_pool_mutex |
124 typedef map<char *, char *, ltstr> string_map; | 111 |
125 typedef map<char *, string_map *, ltstr> from_map; | |
126 typedef map<char *, DNSBLP, ltstr> dnsblp_map; | |
127 typedef map<char *, DNSBLLP, ltstr> dnsbllp_map; | |
128 typedef set<char *, ltstr> string_set; | |
129 typedef set<int> int_set; | |
130 typedef list<char *> string_list; | |
131 typedef map<char *, int, ltstr> ns_mapper; | |
132 | 112 |
133 struct ns_map { | 113 struct ns_map { |
134 // all the strings are owned by the keys/values in the ns_host string map | 114 // all the strings are owned by the keys/values in the ns_host string map |
135 string_map ns_host; // nameserver name -> host name that uses this name server | 115 string_map ns_host; // nameserver name -> host name that uses this name server |
136 ns_mapper ns_ip; // nameserver name -> ip address of the name server | 116 ns_mapper ns_ip; // nameserver name -> ip address of the name server |
117 ~ns_map(); | |
118 void add(char *name, char *refer); | |
137 }; | 119 }; |
138 | 120 |
139 struct CONFIG { | 121 |
140 // the only mutable stuff once it has been loaded from the config file | 122 void ns_map::~ns_map() { |
141 int reference_count; // protected by the global config_mutex | 123 for (string_map::iterator i=ns_host.begin(); i!=ns_host.end(); i++) { |
142 // all the rest is constant after loading from the config file | 124 char *x = (*i).first; |
143 int generation; | 125 char *y = (*i).second; |
144 time_t load_time; | 126 free(x); |
145 string_list config_files; | 127 free(y); |
146 dnsblp_map dnsbls; | 128 } |
147 dnsbllp_map dnsblls; | 129 ns_ip.clear(); |
148 from_map env_from; | 130 ns_host.clear(); |
149 string_map env_to_dnsbll; // map recipient to a named dnsbll | 131 } |
150 string_map env_to_chkfrom; // map recipient to a named from map | 132 |
151 char * content_suffix; // for sbl url body filtering | 133 |
152 char * content_message; // "" | 134 void ns_map::add(char *name, char *refer) { |
153 string_set content_host_ignore;// hosts to ignore for content sbl checking | 135 string_map::iterator i = ns_host.find(name); |
154 char * host_limit_message; // error message for excessive host names | 136 if (i != ns_host.end()) return; |
155 int host_limit; // limit on host names | 137 char *x = strdup(name); |
156 bool host_random; // pick a random selection of host names rather than error for excessive hosts | 138 char *y = strdup(refer); |
157 char * tag_limit_message; // error message for excessive bad html tags | 139 ns_ip[x] = 0; |
158 int tag_limit; // limit on bad html tags | 140 ns_host[x] = y; |
159 string_set html_tags; // set of valid html tags | 141 |
160 string_set tlds; // set of valid tld components | 142 } |
161 CONFIG(); | |
162 ~CONFIG(); | |
163 }; | |
164 CONFIG::CONFIG() { | |
165 reference_count = 0; | |
166 generation = 0; | |
167 load_time = 0; | |
168 content_suffix = NULL; | |
169 content_message = NULL; | |
170 host_limit_message = NULL; | |
171 host_limit = 0; | |
172 host_random = false; | |
173 tag_limit_message = NULL; | |
174 tag_limit = 0; | |
175 } | |
176 CONFIG::~CONFIG() { | |
177 for (dnsblp_map::iterator i=dnsbls.begin(); i!=dnsbls.end(); i++) { | |
178 DNSBLP d = (*i).second; | |
179 // delete the underlying DNSBL objects. | |
180 delete d; | |
181 } | |
182 for (dnsbllp_map::iterator i=dnsblls.begin(); i!=dnsblls.end(); i++) { | |
183 DNSBLLP d = (*i).second; | |
184 // *d is a list of pointers to DNSBL objects, but | |
185 // the underlying objects have already been deleted above. | |
186 delete d; | |
187 } | |
188 for (from_map::iterator i=env_from.begin(); i!=env_from.end(); i++) { | |
189 string_map *d = (*i).second; | |
190 delete d; | |
191 } | |
192 } | |
193 | |
194 static bool debug_syslog = false; | |
195 static bool loader_run = true; // used to stop the config loader thread | |
196 static string_set all_strings; // owns all the strings, only modified by the config loader thread | |
197 static CONFIG * config = NULL; // protected by the config_mutex | |
198 static int generation = 0; // protected by the config_mutex | |
199 | |
200 static pthread_mutex_t config_mutex; | |
201 static pthread_mutex_t syslog_mutex; | |
202 static pthread_mutex_t resolve_mutex; | |
203 static pthread_mutex_t fd_pool_mutex; | |
204 | |
205 static std::set<int> fd_pool; | |
206 static int NULL_SOCKET = -1; | |
207 static char *resolver_port = NULL; // unix domain socket to talk to the dns resolver process | |
208 static int resolver_socket = NULL_SOCKET; // socket used to listen for resolver requests | |
209 static time_t ERROR_SOCKET_TIME = 60; // number of seconds between attempts to open the spam filter socket | |
210 static time_t last_error_time; | |
211 static int resolver_sock_count = 0; // protected with fd_pool_mutex | |
212 static int resolver_pool_size = 0; // protected with fd_pool_mutex | |
213 | |
214 | 143 |
215 // packed structure to allow a single socket write to dump the | 144 // packed structure to allow a single socket write to dump the |
216 // length and the following answer. The packing attribute is gcc specific. | 145 // length and the following answer. The packing attribute is gcc specific. |
217 struct glommer { | 146 struct glommer { |
218 int length; | 147 int length; |
221 #else | 150 #else |
222 int answer; // without a resolver, we return a single ip4 address, 0 == no answer | 151 int answer; // without a resolver, we return a single ip4 address, 0 == no answer |
223 #endif | 152 #endif |
224 } __attribute__ ((packed)); | 153 } __attribute__ ((packed)); |
225 | 154 |
226 struct mlfiPriv; | 155 |
227 | 156 //////////////////////////////////////////////// |
228 | 157 // helper to discard the strings held by a context_map |
229 //////////////////////////////////////////////// | 158 // |
230 // helper to discard the strings and objects held by an ns_map | 159 void discard(context_map &cm); |
231 // | 160 void discard(context_map &cm) { |
232 static void discard(ns_map &s); | 161 for (context_map::iterator i=cm.begin(); i!=cm.end(); i++) { |
233 static void discard(ns_map &s) { | |
234 for (string_map::iterator i=s.ns_host.begin(); i!=s.ns_host.end(); i++) { | |
235 char *x = (*i).first; | 162 char *x = (*i).first; |
236 char *y = (*i).second; | |
237 free(x); | 163 free(x); |
238 free(y); | 164 } |
239 } | 165 } |
240 s.ns_ip.clear(); | 166 |
241 s.ns_host.clear(); | 167 |
242 } | 168 //////////////////////////////////////////////// |
243 | 169 // helper to register a string in a context_map |
244 //////////////////////////////////////////////// | 170 // |
245 // helper to register a string in an ns_map | 171 void register_string(context_map &cm, char *name, CONTEXT *con); |
246 // | 172 void register_string(context_map &cm, char *name, CONTEXT *con) { |
247 static void register_string(ns_map &s, char *name, char *refer); | 173 context_map::iterator i = cm.find(name); |
248 static void register_string(ns_map &s, char *name, char *refer) { | 174 if (i != cm.end()) return; |
249 string_map::iterator i = s.ns_host.find(name); | |
250 if (i != s.ns_host.end()) return; | |
251 char *x = strdup(name); | 175 char *x = strdup(name); |
252 char *y = strdup(refer); | 176 cm[x] = con; |
253 s.ns_ip[x] = 0; | 177 } |
254 s.ns_host[x] = y; | |
255 | |
256 } | |
257 | |
258 //////////////////////////////////////////////// | |
259 // helper to discard the strings held by a string_set | |
260 // | |
261 static void discard(string_set &s); | |
262 static void discard(string_set &s) { | |
263 for (string_set::iterator i=s.begin(); i!=s.end(); i++) { | |
264 free(*i); | |
265 } | |
266 s.clear(); | |
267 } | |
268 | |
269 //////////////////////////////////////////////// | |
270 // helper to register a string in a string set | |
271 // | |
272 static char* register_string(string_set &s, char *name); | |
273 static char* register_string(string_set &s, char *name) { | |
274 string_set::iterator i = s.find(name); | |
275 if (i != s.end()) return *i; | |
276 char *x = strdup(name); | |
277 s.insert(x); | |
278 return x; | |
279 } | |
280 | |
281 //////////////////////////////////////////////// | |
282 // syslog a message | |
283 // | |
284 static void my_syslog(mlfiPriv *priv, char *text); | |
285 | |
286 | |
287 // include the content scanner | |
288 #include "scanner.cpp" | |
289 | 178 |
290 | 179 |
291 //////////////////////////////////////////////// | 180 //////////////////////////////////////////////// |
292 // disconnect the fd from the dns resolver process | 181 // disconnect the fd from the dns resolver process |
293 // | 182 // |
294 void my_disconnect(int sock, bool decrement = true); | 183 void my_disconnect(int sock, bool decrement = true); |
295 void my_disconnect(int sock, bool decrement) | 184 void my_disconnect(int sock, bool decrement) { |
296 { | |
297 if (sock != NULL_SOCKET) { | 185 if (sock != NULL_SOCKET) { |
298 if (decrement) { | 186 if (decrement) { |
299 pthread_mutex_lock(&fd_pool_mutex); | 187 pthread_mutex_lock(&fd_pool_mutex); |
300 resolver_sock_count--; | 188 resolver_sock_count--; |
301 pthread_mutex_unlock(&fd_pool_mutex); | 189 pthread_mutex_unlock(&fd_pool_mutex); |
308 | 196 |
309 //////////////////////////////////////////////// | 197 //////////////////////////////////////////////// |
310 // return fd connected to the dns resolver process | 198 // return fd connected to the dns resolver process |
311 // | 199 // |
312 int my_connect(); | 200 int my_connect(); |
313 int my_connect() | 201 int my_connect() { |
314 { | |
315 // if we have had recent errors, don't even try to open the socket | 202 // if we have had recent errors, don't even try to open the socket |
316 time_t now = time(NULL); | 203 time_t now = time(NULL); |
317 if ((now - last_error_time) < ERROR_SOCKET_TIME) return NULL_SOCKET; | 204 if ((now - last_error_time) < ERROR_SOCKET_TIME) return NULL_SOCKET; |
318 | 205 |
319 // nothing recent, maybe this time it will work | 206 // nothing recent, maybe this time it will work |
339 } | 226 } |
340 return sock; | 227 return sock; |
341 } | 228 } |
342 | 229 |
343 | 230 |
344 //////////////////////////////////////////////// | |
345 // mail filter private data, held for us by sendmail | |
346 // | |
347 struct mlfiPriv | |
348 { | |
349 // connection specific data | |
350 CONFIG *pc; // global context with our maps | |
351 int fd; // to talk to dns resolvers process | |
352 bool err; // did we get any errors on the resolver socket? | |
353 int ip; // ip4 address of the smtp client | |
354 map<DNSBLP, status> checked; // status from those lists | |
355 // message specific data | |
356 char *mailaddr; // envelope from value | |
357 char *queueid; // sendmail queue id | |
358 bool authenticated; // client authenticated? if so, suppress all dnsbl checks | |
359 bool have_whites; // have at least one whitelisted recipient? need to accept content and remove all non-whitelisted recipients if it fails | |
360 bool only_whites; // every recipient is whitelisted? | |
361 string_set non_whites; // remember the non-whitelisted recipients so we can remove them if need be | |
362 recorder *memory; // memory for the content scanner | |
363 url_scanner *scanner; // object to handle body scanning | |
364 mlfiPriv(); | |
365 ~mlfiPriv(); | |
366 void reset(bool final = false); // for a new message | |
367 void get_fd(); | |
368 void return_fd(); | |
369 int my_read(char *buf, int len); | |
370 int my_write(char *buf, int len); | |
371 }; | |
372 | |
373 mlfiPriv::mlfiPriv() { | 231 mlfiPriv::mlfiPriv() { |
374 pthread_mutex_lock(&config_mutex); | 232 pthread_mutex_lock(&config_mutex); |
375 pc = config; | 233 pc = config; |
376 pc->reference_count++; | 234 pc->reference_count++; |
377 pthread_mutex_unlock(&config_mutex); | 235 pthread_mutex_unlock(&config_mutex); |
380 mailaddr = NULL; | 238 mailaddr = NULL; |
381 queueid = NULL; | 239 queueid = NULL; |
382 authenticated = false; | 240 authenticated = false; |
383 have_whites = false; | 241 have_whites = false; |
384 only_whites = true; | 242 only_whites = true; |
385 memory = new recorder(this, &pc->html_tags, &pc->tlds); | 243 memory = new recorder(this, pc->get_html_tags(), pc->get_content_tlds()); |
386 scanner = new url_scanner(memory); | 244 scanner = new url_scanner(memory); |
387 } | 245 } |
388 | 246 |
389 mlfiPriv::~mlfiPriv() { | 247 mlfiPriv::~mlfiPriv() { |
390 return_fd(); | 248 return_fd(); |
395 } | 253 } |
396 | 254 |
397 void mlfiPriv::reset(bool final) { | 255 void mlfiPriv::reset(bool final) { |
398 if (mailaddr) free(mailaddr); | 256 if (mailaddr) free(mailaddr); |
399 if (queueid) free(queueid); | 257 if (queueid) free(queueid); |
400 discard(non_whites); | 258 discard(env_to); |
401 delete memory; | 259 delete memory; |
402 delete scanner; | 260 delete scanner; |
403 if (!final) { | 261 if (!final) { |
404 mailaddr = NULL; | 262 mailaddr = NULL; |
405 queueid = NULL; | 263 queueid = NULL; |
406 authenticated = false; | 264 authenticated = false; |
407 have_whites = false; | 265 have_whites = false; |
408 only_whites = true; | 266 only_whites = true; |
409 memory = new recorder(this, &pc->html_tags, &pc->tlds); | 267 memory = new recorder(this, pc->get_html_tags(), pc->get_content_tlds()); |
410 scanner = new url_scanner(memory); | 268 scanner = new url_scanner(memory); |
411 } | 269 } |
412 } | 270 } |
413 | 271 |
414 void mlfiPriv::get_fd() | 272 void mlfiPriv::get_fd() { |
415 { | |
416 err = true; | 273 err = true; |
417 fd = NULL_SOCKET; | 274 fd = NULL_SOCKET; |
418 int result = pthread_mutex_lock(&fd_pool_mutex); | 275 int result = pthread_mutex_lock(&fd_pool_mutex); |
419 if (!result) { | 276 if (!result) { |
420 std::set<int>::iterator i; | 277 std::set<int>::iterator i; |
439 fd = my_connect(); | 296 fd = my_connect(); |
440 err = (fd == NULL_SOCKET); | 297 err = (fd == NULL_SOCKET); |
441 } | 298 } |
442 } | 299 } |
443 | 300 |
444 void mlfiPriv::return_fd() | 301 void mlfiPriv::return_fd() { |
445 { | |
446 if (err) { | 302 if (err) { |
447 // this fd got a socket error, so close it, rather than returning it to the pool | 303 // this fd got a socket error, so close it, rather than returning it to the pool |
448 my_disconnect(fd); | 304 my_disconnect(fd); |
449 } | 305 } |
450 else { | 306 else { |
468 my_disconnect(fd); | 324 my_disconnect(fd); |
469 } | 325 } |
470 } | 326 } |
471 } | 327 } |
472 | 328 |
473 int mlfiPriv::my_write(char *buf, int len) | 329 int mlfiPriv::my_write(char *buf, int len) { |
474 { | |
475 if (err) return 0; | 330 if (err) return 0; |
476 int rs = 0; | 331 int rs = 0; |
477 while (len) { | 332 while (len) { |
478 int ws = write(fd, buf, len); | 333 int ws = write(fd, buf, len); |
479 if (ws > 0) { | 334 if (ws > 0) { |
489 } | 344 } |
490 } | 345 } |
491 return rs; | 346 return rs; |
492 } | 347 } |
493 | 348 |
494 int mlfiPriv::my_read(char *buf, int len) | 349 int mlfiPriv::my_read(char *buf, int len) { |
495 { | |
496 if (err) return 0; | 350 if (err) return 0; |
497 int rs = 0; | 351 int rs = 0; |
498 while (len > 1) { | 352 while (len > 1) { |
499 int ws = read(fd, buf, len); | 353 int ws = read(fd, buf, len); |
500 if (ws > 0) { | 354 if (ws > 0) { |
510 } | 364 } |
511 } | 365 } |
512 return rs; | 366 return rs; |
513 } | 367 } |
514 | 368 |
369 void mlfiPriv::need_content_filter(char *rcpt, CONTEXT &con) { | |
370 register_string(env_to, rcpt, &con); | |
371 } | |
372 | |
515 #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) | 373 #define MLFIPRIV ((struct mlfiPriv *) smfi_getpriv(ctx)) |
516 | 374 |
517 | 375 |
518 //////////////////////////////////////////////// | 376 //////////////////////////////////////////////// |
519 // syslog a message | 377 // syslog a message |
520 // | 378 // |
521 static void my_syslog(mlfiPriv *priv, char *text) { | 379 void my_syslog(mlfiPriv *priv, char *text) { |
522 char buf[1000]; | 380 char buf[1000]; |
523 if (priv) { | 381 if (priv) { |
524 snprintf(buf, sizeof(buf), "%s: %s", priv->queueid, text); | 382 snprintf(buf, sizeof(buf), "%s: %s", priv->queueid, text); |
525 text = buf; | 383 text = buf; |
526 } | 384 } |
527 pthread_mutex_lock(&syslog_mutex); | 385 pthread_mutex_lock(&syslog_mutex); |
528 openlog("dnsbl", LOG_PID, LOG_MAIL); | 386 if (!syslog_opened) { |
387 openlog("dnsbl", LOG_PID, LOG_MAIL); | |
388 syslog_opened = true; | |
389 } | |
529 syslog(LOG_NOTICE, "%s", text); | 390 syslog(LOG_NOTICE, "%s", text); |
530 closelog(); | |
531 pthread_mutex_unlock(&syslog_mutex); | 391 pthread_mutex_unlock(&syslog_mutex); |
532 } | 392 } |
533 | 393 |
534 static void my_syslog(char *text); | 394 void my_syslog(char *text) { |
535 static void my_syslog(char *text) { | |
536 my_syslog(NULL, text); | 395 my_syslog(NULL, text); |
537 } | 396 } |
538 | |
539 //////////////////////////////////////////////// | |
540 // register a global string | |
541 // | |
542 static char* register_string(char *name); | |
543 static char* register_string(char *name) { | |
544 return register_string(all_strings, name); | |
545 } | |
546 | |
547 | |
548 static char* next_token(char *delim); | |
549 static char* next_token(char *delim) { | |
550 char *name = strtok(NULL, delim); | |
551 if (!name) return name; | |
552 return register_string(name); | |
553 } | |
554 | |
555 | |
556 //////////////////////////////////////////////// | |
557 // lookup an email address in the env_from or env_to maps | |
558 // | |
559 static char* lookup1(char *email, string_map map); | |
560 static char* lookup1(char *email, string_map map) { | |
561 string_map::iterator i = map.find(email); | |
562 if (i != map.end()) return (*i).second; | |
563 char *x = strchr(email, '@'); | |
564 if (!x) return DEFAULT; | |
565 x++; | |
566 i = map.find(x); | |
567 if (i != map.end()) return (*i).second; | |
568 return DEFAULT; | |
569 } | |
570 | |
571 | |
572 //////////////////////////////////////////////// | |
573 // lookup an email address in the env_from or env_to maps | |
574 // this email address is passed in from sendmail, and will | |
575 // always be enclosed in <>. It may have mixed case, just | |
576 // as the mail client sent it. | |
577 // | |
578 static char* lookup(char* email, string_map map); | |
579 static char* lookup(char* email, string_map map) { | |
580 int n = strlen(email)-2; | |
581 if (n < 1) return DEFAULT; // malformed | |
582 char *key = strdup(email+1); | |
583 key[n] = '\0'; | |
584 for (int i=0; i<n; i++) key[i] = tolower(key[i]); | |
585 char *rc = lookup1(key, map); | |
586 free(key); | |
587 return rc; | |
588 } | |
589 | |
590 | |
591 //////////////////////////////////////////////// | |
592 // find the dnsbl with a specific name | |
593 // | |
594 static DNSBLP find_dnsbl(CONFIG &dc, char *name); | |
595 static DNSBLP find_dnsbl(CONFIG &dc, char *name) { | |
596 dnsblp_map::iterator i = dc.dnsbls.find(name); | |
597 if (i == dc.dnsbls.end()) return NULL; | |
598 return (*i).second; | |
599 } | |
600 | |
601 | |
602 //////////////////////////////////////////////// | |
603 // find the dnsbll with a specific name | |
604 // | |
605 static DNSBLLP find_dnsbll(CONFIG &dc, char *name); | |
606 static DNSBLLP find_dnsbll(CONFIG &dc, char *name) { | |
607 dnsbllp_map::iterator i = dc.dnsblls.find(name); | |
608 if (i == dc.dnsblls.end()) return NULL; | |
609 return (*i).second; | |
610 } | |
611 | |
612 | |
613 //////////////////////////////////////////////// | |
614 // find the envfrom map with a specific name | |
615 // | |
616 static string_map* find_from_map(CONFIG &dc, char *name); | |
617 static string_map* find_from_map(CONFIG &dc, char *name) { | |
618 from_map::iterator i = dc.env_from.find(name); | |
619 if (i == dc.env_from.end()) return NULL; | |
620 return (*i).second; | |
621 } | |
622 | |
623 | |
624 static string_map& really_find_from_map(CONFIG &dc, char *name); | |
625 static string_map& really_find_from_map(CONFIG &dc, char *name) { | |
626 string_map *sm = find_from_map(dc, name); | |
627 if (!sm) { | |
628 sm = new string_map; | |
629 dc.env_from[name] = sm; | |
630 } | |
631 return *sm; | |
632 } | |
633 | |
634 | 397 |
635 | 398 |
636 //////////////////////////////////////////////// | 399 //////////////////////////////////////////////// |
637 // read a resolver request from the socket, process it, and | 400 // read a resolver request from the socket, process it, and |
638 // write the result back to the socket. | 401 // write the result back to the socket. |
639 | 402 |
640 static void process_resolver_requests(int socket); | 403 void process_resolver_requests(int socket); |
641 static void process_resolver_requests(int socket) { | 404 void process_resolver_requests(int socket) { |
642 #ifdef NS_MAXDNAME | 405 #ifdef NS_MAXDNAME |
643 char question[NS_MAXDNAME]; | 406 char question[NS_MAXDNAME]; |
644 #else | 407 #else |
645 char question[1000]; | 408 char question[1000]; |
646 #endif | 409 #endif |
711 // ask a dns question and get an A record answer - we don't try | 474 // ask a dns question and get an A record answer - we don't try |
712 // very hard, just using the default resolver retry settings. | 475 // very hard, just using the default resolver retry settings. |
713 // If we cannot get an answer, we just accept the mail. | 476 // If we cannot get an answer, we just accept the mail. |
714 // | 477 // |
715 // | 478 // |
716 static int dns_interface(mlfiPriv &priv, char *question, bool maybe_ip, ns_map *nameservers); | 479 int dns_interface(mlfiPriv &priv, char *question, bool maybe_ip, ns_map *nameservers); |
717 static int dns_interface(mlfiPriv &priv, char *question, bool maybe_ip, ns_map *nameservers) { | 480 int dns_interface(mlfiPriv &priv, char *question, bool maybe_ip, ns_map *nameservers) { |
718 // this part can be done without locking the resolver mutex. Each | 481 // this part can be done without locking the resolver mutex. Each |
719 // milter thread is talking over its own socket to a separate resolver | 482 // milter thread is talking over its own socket to a separate resolver |
720 // process, which does the actual dns resolution. | 483 // process, which does the actual dns resolution. |
721 if (priv.err) return 0; // cannot ask more questions on this socket. | 484 if (priv.err) return 0; // cannot ask more questions on this socket. |
722 priv.my_write(question, strlen(question)+1); // write the question including the null terminator | 485 priv.my_write(question, strlen(question)+1); // write the question including the null terminator |
770 *(n++) = '.'; | 533 *(n++) = '.'; |
771 } | 534 } |
772 } | 535 } |
773 if (n-nam) n--; // remove trailing . | 536 if (n-nam) n--; // remove trailing . |
774 *n = '\0'; // null terminate it | 537 *n = '\0'; // null terminate it |
775 register_string(ns, nam, question); // ns host to lookup later | 538 ns.add(nam, question); // ns host to lookup later |
776 } | 539 } |
777 } | 540 } |
778 rrnum = 0; | 541 rrnum = 0; |
779 while (ns_parserr(&handle, ns_s_ar, rrnum++, &rr) == 0) { | 542 while (ns_parserr(&handle, ns_s_ar, rrnum++, &rr) == 0) { |
780 if (ns_rr_type(rr) == ns_t_a) { | 543 if (ns_rr_type(rr) == ns_t_a) { |
815 | 578 |
816 | 579 |
817 //////////////////////////////////////////////// | 580 //////////////////////////////////////////////// |
818 // check a single dnsbl | 581 // check a single dnsbl |
819 // | 582 // |
820 static status check_single(mlfiPriv &priv, int ip, char *suffix); | 583 bool check_single(mlfiPriv &priv, int ip, char *suffix); |
821 static status check_single(mlfiPriv &priv, int ip, char *suffix) { | 584 bool check_single(mlfiPriv &priv, int ip, char *suffix) { |
822 // make a dns question | 585 // make a dns question |
823 const u_char *src = (const u_char *)&ip; | 586 const u_char *src = (const u_char *)&ip; |
824 if (src[0] == 127) return oksofar; // don't do dns lookups on localhost | 587 if (src[0] == 127) return oksofar; // don't do dns lookups on localhost |
825 #ifdef NS_MAXDNAME | 588 #ifdef NS_MAXDNAME |
826 char question[NS_MAXDNAME]; | 589 char question[NS_MAXDNAME]; |
827 #else | 590 #else |
828 char question[1000]; | 591 char question[1000]; |
829 #endif | 592 #endif |
830 snprintf(question, sizeof(question), "%u.%u.%u.%u.%s.", src[3], src[2], src[1], src[0], suffix); | 593 snprintf(question, sizeof(question), "%u.%u.%u.%u.%s.", src[3], src[2], src[1], src[0], suffix); |
831 // ask the question, if we get an A record it implies a blacklisted ip address | 594 // ask the question, if we get an A record it implies a blacklisted ip address |
832 return (dns_interface(priv, question, false, NULL)) ? reject : oksofar; | 595 return dns_interface(priv, question, false, NULL); |
833 } | 596 } |
834 | 597 |
835 | 598 |
836 //////////////////////////////////////////////// | 599 //////////////////////////////////////////////// |
837 // check a single dnsbl | 600 // check a single dnsbl |
838 // | 601 // |
839 static status check_single(mlfiPriv &priv, int ip, DNSBL &bl); | 602 bool check_single(mlfiPriv &priv, int ip, DNSBL &bl); |
840 static status check_single(mlfiPriv &priv, int ip, DNSBL &bl) { | 603 bool check_single(mlfiPriv &priv, int ip, DNSBL &bl) { |
841 return check_single(priv, ip, bl.suffix); | 604 return check_single(priv, ip, bl.suffix); |
842 } | 605 } |
843 | 606 |
844 | 607 |
845 //////////////////////////////////////////////// | 608 //////////////////////////////////////////////// |
846 // check the dnsbls specified for this recipient | 609 // check the dnsbls specified for this recipient |
847 // | 610 // |
848 static status check_dnsbl(mlfiPriv &priv, DNSBLLP dnsbllp, DNSBLP &rejectlist); | 611 bool check_dnsbl(mlfiPriv &priv, dnsblp_list &dnsbll, DNSBLP &rejectlist); |
849 static status check_dnsbl(mlfiPriv &priv, DNSBLLP dnsbllp, DNSBLP &rejectlist) { | 612 bool check_dnsbl(mlfiPriv &priv, dnsblp_list &dnsbll, DNSBLP &rejectlist) { |
850 if (priv.authenticated) return oksofar; | 613 if (priv.authenticated) return oksofar; |
851 if (!dnsbllp) return oksofar; | 614 for (dnsblp_list::iterator i=dnsbll.begin(); i!=dnsbll.end(); i++) { |
852 DNSBLL &dnsbll = *dnsbllp; | |
853 for (DNSBLL::iterator i=dnsbll.begin(); i!=dnsbll.end(); i++) { | |
854 DNSBLP dp = *i; // non null by construction | 615 DNSBLP dp = *i; // non null by construction |
855 status st; | 616 bool st; |
856 map<DNSBLP, status>::iterator f = priv.checked.find(dp); | 617 map<DNSBLP, bool>::iterator f = priv.checked.find(dp); |
857 if (f == priv.checked.end()) { | 618 if (f == priv.checked.end()) { |
858 // have not checked this list yet | 619 // have not checked this list yet |
859 st = check_single(priv, priv.ip, *dp); | 620 st = check_single(priv, priv.ip, *dp); |
860 rejectlist = dp; | 621 rejectlist = dp; |
861 priv.checked[dp] = st; | 622 priv.checked[dp] = st; |
862 } | 623 } |
863 else { | 624 else { |
864 st = (*f).second; | 625 st = (*f).second; |
865 rejectlist = (*f).first; | 626 rejectlist = (*f).first; |
866 } | 627 } |
867 if (st == reject) return st; | 628 if (st) return st; |
868 } | 629 } |
869 return oksofar; | 630 return false; |
870 } | 631 } |
871 | 632 |
872 | 633 |
873 //////////////////////////////////////////////// | 634 //////////////////////////////////////////////// |
874 // check the hosts from the body against the content dnsbl | 635 // check the hosts from the body against the content dnsbl |
875 // | 636 // |
876 static status check_hosts(mlfiPriv &priv, char *&host, int &ip); | 637 bool check_hosts(mlfiPriv &priv, bool random, int limit, char *&host, int ip); |
877 static status check_hosts(mlfiPriv &priv, char *&host, int &ip) { | 638 bool check_hosts(mlfiPriv &priv) { |
639 static buf[2000]; | |
878 CONFIG &dc = *priv.pc; | 640 CONFIG &dc = *priv.pc; |
641 string_set &hosts = priv.memory->hosts; | |
642 string_set &ignore = dc.get_content_host_ignore(); | |
643 | |
879 int count = 0; | 644 int count = 0; |
880 ns_map nameservers; | 645 int cnt = hosts.size(); // number of hosts we could look at |
881 bool ran = priv.pc->host_random; | 646 int_set ips; |
882 int lim = priv.pc->host_limit; // we should not look at more than this many hosts | 647 ns_map nameservers; |
883 int cnt = priv.memory->hosts.size(); // number of hosts we could look at | 648 for (string_set::iterator i=hosts.begin(); i!=hosts.end(); i++) { |
884 int_set ips; // remove duplicate ip addresses | 649 host = *i; // a reference into hosts, which will live until this smtp transaction is closed |
885 for (string_set::iterator i=priv.memory->hosts.begin(); i!=priv.memory->hosts.end(); i++) { | |
886 host = *i; // a reference into priv.memory->hosts, which will live until this smtp transaction is closed | |
887 | 650 |
888 // don't bother looking up hosts on the ignore list | 651 // don't bother looking up hosts on the ignore list |
889 string_set::iterator j = priv.pc->content_host_ignore.find(host); | 652 string_set::iterator j = ignore.find(host); |
890 if (j != priv.pc->content_host_ignore.end()) continue; | 653 if (j != ignore.end()) continue; |
891 | 654 |
892 // try to only look at lim/cnt fraction of the available cnt host names in random mode | 655 // try to only look at limit/cnt fraction of the available cnt host names in random mode |
893 if ((cnt > lim) && (lim > 0) && ran) { | 656 if ((cnt > limit) && (limit > 0) && random) { |
894 int r = rand() % cnt; | 657 int r = rand() % cnt; |
895 if (r >= lim) { | 658 if (r >= limit) { |
896 char buf[1000]; | 659 char buf[1000]; |
897 snprintf(buf, sizeof(buf), "host %s skipped", host); | 660 snprintf(buf, sizeof(buf), "host %s skipped", host); |
898 my_syslog(&priv, buf); | 661 my_syslog(&priv, buf); |
899 continue; | 662 continue; |
900 } | 663 } |
901 } | 664 } |
902 count++; | 665 count++; |
903 if ((count > lim) && (lim > 0) && (!ran)) { | |
904 discard(nameservers); | |
905 return reject_host; | |
906 } | |
907 ip = dns_interface(priv, host, true, &nameservers); | 666 ip = dns_interface(priv, host, true, &nameservers); |
908 if (debug_syslog) { | 667 if (debug_syslog) { |
909 char buf[1000]; | 668 char buf[1000]; |
910 if (ip) { | 669 if (ip) { |
911 char adr[sizeof "255.255.255.255"]; | 670 char adr[sizeof "255.255.255.255"]; |
920 } | 679 } |
921 if (ip) { | 680 if (ip) { |
922 int_set::iterator i = ips.find(ip); | 681 int_set::iterator i = ips.find(ip); |
923 if (i == ips.end()) { | 682 if (i == ips.end()) { |
924 ips.insert(ip); | 683 ips.insert(ip); |
925 status st = check_single(priv, ip, dc.content_suffix); | 684 if (check_single(priv, ip, dc.content_suffix)) { |
926 if (st == reject) { | 685 return true; |
927 discard(nameservers); | |
928 return st; | |
929 } | 686 } |
930 } | 687 } |
931 } | 688 } |
932 } | 689 } |
933 lim *= 4; // allow average of 3 ns per host name | 690 limit *= 4; // allow average of 3 ns per host name |
934 for (ns_mapper::iterator i=nameservers.ns_ip.begin(); i!=nameservers.ns_ip.end(); i++) { | 691 for (ns_mapper::iterator i=nameservers.ns_ip.begin(); i!=nameservers.ns_ip.end(); i++) { |
935 count++; | 692 count++; |
936 if ((count > lim) && (lim > 0)) { | 693 if ((count > limit) && (limit > 0)) { |
937 if (ran) continue; // don't complain | 694 if (random) continue; // don't complain |
938 discard(nameservers); | 695 return true; |
939 return reject_host; | |
940 } | 696 } |
941 host = (*i).first; // a transient reference that needs to be replaced before we return it | 697 host = (*i).first; // a transient reference that needs to be replaced before we return it |
942 ip = (*i).second; | 698 ip = (*i).second; |
943 if (!ip) ip = dns_interface(priv, host, false, NULL); | 699 if (!ip) ip = dns_interface(priv, host, false, NULL); |
944 if (debug_syslog) { | 700 if (debug_syslog) { |
956 } | 712 } |
957 if (ip) { | 713 if (ip) { |
958 int_set::iterator i = ips.find(ip); | 714 int_set::iterator i = ips.find(ip); |
959 if (i == ips.end()) { | 715 if (i == ips.end()) { |
960 ips.insert(ip); | 716 ips.insert(ip); |
961 status st = check_single(priv, ip, dc.content_suffix); | 717 if (check_single(priv, ip, dc.content_suffix)) { |
962 if (st == reject) { | |
963 string_map::iterator j = nameservers.ns_host.find(host); | 718 string_map::iterator j = nameservers.ns_host.find(host); |
964 if (j != nameservers.ns_host.end()) { | 719 if (j != nameservers.ns_host.end()) { |
965 char *refer = (*j).second; | 720 char *refer = (*j).second; |
966 char buf[1000]; | 721 char buf[1000]; |
967 snprintf(buf, sizeof(buf), "%s with nameserver %s", refer, host); | 722 snprintf(buf, sizeof(buf), "%s with nameserver %s", refer, host); |
968 host = register_string(priv.memory->hosts, buf); // put a copy into priv.memory->hosts, and return that reference | 723 host = register_string(hosts, buf); // put a copy into hosts, and return that reference |
969 } | 724 } |
970 else { | 725 else { |
971 host = register_string(priv.memory->hosts, host); // put a copy into priv.memory->hosts, and return that reference | 726 host = register_string(hosts, host); // put a copy into hosts, and return that reference |
972 } | 727 } |
973 discard(nameservers); | 728 return true; |
974 return st; | |
975 } | 729 } |
976 } | 730 } |
977 } | 731 } |
978 } | 732 } |
979 discard(nameservers); | 733 return false; |
980 host = NULL; | |
981 int bin = priv.memory->binary_tags; | |
982 int bad = priv.memory->bad_html_tags; | |
983 lim = priv.pc->tag_limit; | |
984 if (3*bin > bad) return oksofar; // probably .zip or .tar.gz with random content | |
985 if ((bad > lim) && (lim > 0)) return reject_tag; | |
986 return oksofar; | |
987 } | 734 } |
988 | 735 |
989 | 736 |
990 //////////////////////////////////////////////// | 737 //////////////////////////////////////////////// |
991 // start of sendmail milter interfaces | 738 // start of sendmail milter interfaces |
1014 } | 761 } |
1015 | 762 |
1016 sfsistat mlfi_envrcpt(SMFICTX *ctx, char **rcpt) | 763 sfsistat mlfi_envrcpt(SMFICTX *ctx, char **rcpt) |
1017 { | 764 { |
1018 DNSBLP rejectlist = NULL; // list that caused the reject | 765 DNSBLP rejectlist = NULL; // list that caused the reject |
1019 status st = oksofar; | |
1020 mlfiPriv &priv = *MLFIPRIV; | 766 mlfiPriv &priv = *MLFIPRIV; |
1021 CONFIG &dc = *priv.pc; | 767 CONFIG &dc = *priv.pc; |
1022 if (!priv.queueid) priv.queueid = strdup(smfi_getsymval(ctx, "i")); | 768 if (!priv.queueid) priv.queueid = strdup(smfi_getsymval(ctx, "i")); |
1023 char *rcptaddr = rcpt[0]; | 769 char *rcptaddr = rcpt[0]; |
1024 char *dnsname = lookup(rcptaddr, dc.env_to_dnsbll); | 770 CONTEXT &con = *(dc.find_context(rcptaddr, priv.mailaddr)); |
1025 char *fromname = lookup(rcptaddr, dc.env_to_chkfrom); | 771 char *fromvalue = con.find_from(priv.mailaddr); |
1026 if ((strcmp(dnsname, BLACK) == 0) || | 772 status st; |
1027 (strcmp(fromname, BLACK) == 0)) { | 773 if (fromvalue == token_black) { |
1028 st = black; // two options to blacklist this recipient | 774 st = black; |
1029 } | 775 } |
1030 else if (strcmp(fromname, WHITE) == 0) { | 776 else if (fromvalue == token_white) { |
1031 st = white; | 777 st = white; |
1032 } | 778 } |
1033 else { | 779 else { |
1034 // check an env_from map | 780 // check the dns based lists |
1035 string_map *sm = find_from_map(dc, fromname); | 781 st = check_dnsbl(priv, con.get_dnsbl_list(), rejectlist); |
1036 if (sm != NULL) { | 782 } |
1037 fromname = lookup(priv.mailaddr, *sm); // returns default if name not in map | |
1038 if (strcmp(fromname, BLACK) == 0) { | |
1039 st = black; // blacklist this envelope from value | |
1040 } | |
1041 if (strcmp(fromname, WHITE) == 0) { | |
1042 st = white; // blacklist this envelope from value | |
1043 } | |
1044 } | |
1045 } | |
1046 if ((st == oksofar) && (strcmp(dnsname, WHITE) != 0)) { | |
1047 // check dns lists | |
1048 st = check_dnsbl(priv, find_dnsbll(dc, dnsname), rejectlist); | |
1049 } | |
1050 | |
1051 if (st == reject) { | 783 if (st == reject) { |
1052 // reject the recipient based on some dnsbl | 784 // reject the recipient based on some dnsbl |
1053 char adr[sizeof "255.255.255.255"]; | 785 char adr[sizeof "255.255.255.255"]; |
1054 adr[0] = '\0'; | 786 adr[0] = '\0'; |
1055 inet_ntop(AF_INET, (const u_char *)&priv.ip, adr, sizeof(adr)); | 787 inet_ntop(AF_INET, (const u_char *)&priv.ip, adr, sizeof(adr)); |
1063 smfi_setreply(ctx, "550", "5.7.1", "no such user"); | 795 smfi_setreply(ctx, "550", "5.7.1", "no such user"); |
1064 return SMFIS_REJECT; | 796 return SMFIS_REJECT; |
1065 } | 797 } |
1066 else { | 798 else { |
1067 // accept the recipient | 799 // accept the recipient |
800 if (!con.get_content_filtering()) st = white; | |
1068 if (st == oksofar) { | 801 if (st == oksofar) { |
1069 // but remember the non-whites | 802 // but remember the non-whites |
1070 register_string(priv.non_whites, rcptaddr); | 803 priv.need_content_filter(rcptaddr, con); |
1071 priv.only_whites = false; | 804 priv.only_whites = false; |
1072 } | 805 } |
1073 if (st == white) { | 806 if (st == white) { |
1074 priv.have_whites = true; | 807 priv.have_whites = true; |
1075 } | 808 } |
1080 sfsistat mlfi_body(SMFICTX *ctx, u_char *data, size_t len) | 813 sfsistat mlfi_body(SMFICTX *ctx, u_char *data, size_t len) |
1081 { | 814 { |
1082 mlfiPriv &priv = *MLFIPRIV; | 815 mlfiPriv &priv = *MLFIPRIV; |
1083 if (priv.authenticated) return SMFIS_CONTINUE; | 816 if (priv.authenticated) return SMFIS_CONTINUE; |
1084 if (priv.only_whites) return SMFIS_CONTINUE; | 817 if (priv.only_whites) return SMFIS_CONTINUE; |
1085 if (!priv.pc->content_suffix) return SMFIS_CONTINUE; | |
1086 priv.scanner->scan(data, len); | 818 priv.scanner->scan(data, len); |
1087 return SMFIS_CONTINUE; | 819 return SMFIS_CONTINUE; |
1088 } | 820 } |
1089 | 821 |
1090 sfsistat mlfi_eom(SMFICTX *ctx) | 822 sfsistat mlfi_eom(SMFICTX *ctx) |
1091 { | 823 { |
1092 sfsistat rc; | 824 sfsistat rc; |
1093 mlfiPriv &priv = *MLFIPRIV; | 825 mlfiPriv &priv = *MLFIPRIV; |
826 CONFIG &dc = *priv.pc; | |
1094 char *host = NULL; | 827 char *host = NULL; |
1095 int ip; | 828 int ip; |
1096 status st; | 829 status st; |
1097 // process end of message | 830 // process end of message |
1098 if (priv.authenticated || | 831 if (priv.authenticated || priv.only_whites) rc = SMFIS_CONTINUE; |
1099 priv.only_whites || | |
1100 (!priv.pc->content_suffix) || | |
1101 ((st=check_hosts(priv, host, ip)) == oksofar)) rc = SMFIS_CONTINUE; | |
1102 else { | 832 else { |
1103 if (!priv.have_whites) { | 833 char *msg = NULL; |
1104 // can reject the entire message | 834 string_set alive; |
1105 char buf[2000]; | 835 bool random = false; |
1106 if (st == reject_tag) { | 836 bool limit = 0; |
1107 // rejected due to excessive bad html tags | 837 for (context_map::iterator i=env_to.begin(); i!=env_to.end(); i++) { |
1108 snprintf(buf, sizeof(buf), priv.pc->tag_limit_message); | 838 char *rcpt = (*i).first; |
1109 } | 839 CONTEXT &con = *((*i).second); |
1110 else if (st == reject_host) { | 840 if (!con.acceptable_content(priv.memory, msg)) { |
1111 // rejected due to excessive unique host/urls | 841 // bad html tags or excessive hosts |
1112 snprintf(buf, sizeof(buf), priv.pc->host_limit_message); | 842 smfi_delrcpt(ctx, rcpt); |
1113 } | 843 } |
1114 else { | 844 else { |
845 alive.insert(rcpt); | |
846 random |= con.get_host_random(); | |
847 limit = max(limit, con.get_host_limit()); | |
848 } | |
849 } | |
850 bool rejecting = alive.empty(); | |
851 if (!rejecting) { | |
852 rejecting = check_hosts(priv, random, limit, host, ip); | |
853 if (rejecting) { | |
854 static char buf[2000]; | |
1115 char adr[sizeof "255.255.255.255"]; | 855 char adr[sizeof "255.255.255.255"]; |
1116 adr[0] = '\0'; | 856 adr[0] = '\0'; |
1117 inet_ntop(AF_INET, (const u_char *)&ip, adr, sizeof(adr)); | 857 inet_ntop(AF_INET, (const u_char *)&ip, adr, sizeof(adr)); |
1118 snprintf(buf, sizeof(buf), priv.pc->content_message, host, adr); | 858 snprintf(buf, sizeof(buf), dc.get_content_message(), host, adr); |
1119 } | 859 msg = buf; |
1120 smfi_setreply(ctx, "550", "5.7.1", buf); | 860 } |
861 } | |
862 if (!rejecting) { | |
863 rc = SMFIS_CONTINUE; | |
864 } | |
865 else if (!priv.have_whites && alive.empty()) { | |
866 // can reject the entire message | |
867 smfi_setreply(ctx, "550", "5.7.1", msg); | |
1121 rc = SMFIS_REJECT; | 868 rc = SMFIS_REJECT; |
1122 } | 869 } |
1123 else { | 870 else { |
1124 // need to accept it but remove the recipients that don't want it | 871 // need to accept it but remove the recipients that don't want it |
1125 for (string_set::iterator i=priv.non_whites.begin(); i!=priv.non_whites.end(); i++) { | 872 for (string_set::iterator i=alive.begin(); i!=alive.end(); i++) { |
1126 char *rcpt = *i; | 873 char *rcpt = *i; |
1127 smfi_delrcpt(ctx, rcpt); | 874 smfi_delrcpt(ctx, rcpt); |
1128 } | 875 } |
1129 rc = SMFIS_CONTINUE; | 876 rc = SMFIS_CONTINUE; |
1130 } | 877 } |
1166 mlfi_abort, // message aborted | 913 mlfi_abort, // message aborted |
1167 mlfi_close, // connection cleanup | 914 mlfi_close, // connection cleanup |
1168 }; | 915 }; |
1169 | 916 |
1170 | 917 |
1171 static void dumpit(char *name, string_map map); | |
1172 static void dumpit(char *name, string_map map) { | |
1173 fprintf(stdout, "\n"); | |
1174 for (string_map::iterator i=map.begin(); i!=map.end(); i++) { | |
1175 fprintf(stdout, "%s %s->%s\n", name, (*i).first, (*i).second); | |
1176 } | |
1177 } | |
1178 | |
1179 | |
1180 static void dumpit(from_map map); | |
1181 static void dumpit(from_map map) { | |
1182 for (from_map::iterator i=map.begin(); i!=map.end(); i++) { | |
1183 char buf[2000]; | |
1184 snprintf(buf, sizeof(buf), "envelope from map for %s", (*i).first); | |
1185 string_map *sm = (*i).second; | |
1186 dumpit(buf, *sm); | |
1187 } | |
1188 } | |
1189 | |
1190 | |
1191 static void dumpit(CONFIG &dc); | |
1192 static void dumpit(CONFIG &dc) { | |
1193 dumpit(dc.env_from); | |
1194 dumpit("envelope to (dnsbl list)", dc.env_to_dnsbll); | |
1195 dumpit("envelope to (from map)", dc.env_to_chkfrom); | |
1196 fprintf(stdout, "\ndnsbls\n"); | |
1197 for (dnsblp_map::iterator i=dc.dnsbls.begin(); i!=dc.dnsbls.end(); i++) { | |
1198 fprintf(stdout, "%s %s %s\n", (*i).first, (*i).second->suffix, (*i).second->message); | |
1199 } | |
1200 fprintf(stdout, "\ndnsbl_lists\n"); | |
1201 for (dnsbllp_map::iterator i=dc.dnsblls.begin(); i!=dc.dnsblls.end(); i++) { | |
1202 char *name = (*i).first; | |
1203 DNSBLL &dl = *((*i).second); | |
1204 fprintf(stdout, "%s", name); | |
1205 for (DNSBLL::iterator j=dl.begin(); j!=dl.end(); j++) { | |
1206 DNSBL &d = **j; | |
1207 fprintf(stdout, " %s", d.suffix); | |
1208 } | |
1209 fprintf(stdout, "\n"); | |
1210 } | |
1211 if (dc.content_suffix) { | |
1212 fprintf(stdout, "\ncontent filtering enabled with %s %s\n", dc.content_suffix, dc.content_message); | |
1213 } | |
1214 for (string_set::iterator i=dc.content_host_ignore.begin(); i!=dc.content_host_ignore.end(); i++) { | |
1215 fprintf(stdout, "ignore %s\n", (*i)); | |
1216 } | |
1217 if (dc.host_limit && !dc.host_random) { | |
1218 fprintf(stdout, "\ncontent filtering for host names hard limit %d %s\n", dc.host_limit, dc.host_limit_message); | |
1219 } | |
1220 if (dc.host_limit && dc.host_random) { | |
1221 fprintf(stdout, "\ncontent filtering for host names soft limit %d\n", dc.host_limit); | |
1222 } | |
1223 if (dc.tag_limit) { | |
1224 fprintf(stdout, "\ncontent filtering for excessive html tags enabled with limit %d %s\n", dc.tag_limit, dc.tag_limit_message); | |
1225 } | |
1226 fprintf(stdout, "\nfiles\n"); | |
1227 for (string_list::iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) { | |
1228 char *f = *i; | |
1229 fprintf(stdout, "config includes %s\n", f); | |
1230 } | |
1231 } | |
1232 | |
1233 | |
1234 //////////////////////////////////////////////// | |
1235 // check for redundant or recursive include files | |
1236 // | |
1237 static bool ok_to_include(CONFIG &dc, char *fn); | |
1238 static bool ok_to_include(CONFIG &dc, char *fn) { | |
1239 if (!fn) return false; | |
1240 bool ok = true; | |
1241 for (string_list::iterator i=dc.config_files.begin(); i!=dc.config_files.end(); i++) { | |
1242 char *f = *i; | |
1243 if (strcmp(f, fn) == 0) { | |
1244 my_syslog("redundant or recursive include file detected"); | |
1245 ok = false; | |
1246 break; | |
1247 } | |
1248 } | |
1249 return ok; | |
1250 } | |
1251 | |
1252 | |
1253 //////////////////////////////////////////////// | |
1254 // load a single config file | |
1255 // | |
1256 static void load_conf_dcc(CONFIG &dc, char *name, char *fn); | |
1257 static void load_conf_dcc(CONFIG &dc, char *name, char *fn) { | |
1258 ifstream is(fn); | |
1259 if (is.fail()) { | |
1260 char buf[1000]; | |
1261 snprintf(buf, sizeof(buf), "include file %s not found", fn); | |
1262 my_syslog(buf); | |
1263 return; | |
1264 } | |
1265 dc.config_files.push_back(fn); | |
1266 const int LINE_SIZE = 2000; | |
1267 char line[LINE_SIZE]; | |
1268 char *list = BLACK; | |
1269 char *delim = " \t"; | |
1270 int curline = 0; | |
1271 while (!is.eof()) { | |
1272 is.getline(line, LINE_SIZE); | |
1273 curline++; | |
1274 int n = strlen(line); | |
1275 if (!n) continue; | |
1276 for (int i=0; i<n; i++) line[i] = tolower(line[i]); | |
1277 if (line[0] == '#') continue; | |
1278 char *head = line; | |
1279 if (strspn(line, delim) == 0) { | |
1280 // have a leading ok/many tag to fetch | |
1281 char *cmd = strtok(line, delim); | |
1282 if (strcmp(cmd, MANY) == 0) list = BLACK; | |
1283 else if (strcmp(cmd, OK) == 0) list = WHITE; | |
1284 head = cmd + strlen(cmd) + 1; | |
1285 } | |
1286 char *cmd = strtok(head, delim); | |
1287 if (!cmd) continue; | |
1288 if (strcmp(cmd, "env_from") == 0) { | |
1289 char *from = next_token(delim); | |
1290 if (from) { | |
1291 string_map &fm = really_find_from_map(dc, name); | |
1292 fm[from] = list; | |
1293 } | |
1294 } | |
1295 else if (strcmp(cmd, "env_to") == 0) { | |
1296 char *to = next_token(delim); | |
1297 if (to) { | |
1298 dc.env_to_dnsbll[to] = list; | |
1299 dc.env_to_chkfrom[to] = list; | |
1300 } | |
1301 } | |
1302 else if (strcmp(cmd, "substitute") == 0) { | |
1303 char *tag = next_token(delim); | |
1304 if (tag && (strcmp(tag, "mail_host") == 0)) { | |
1305 char *from = next_token(delim); | |
1306 if (from) { | |
1307 string_map &fm = really_find_from_map(dc, name); | |
1308 fm[from] = list; | |
1309 } | |
1310 } | |
1311 } | |
1312 else if (strcmp(cmd, "include") == 0) { | |
1313 char *fn = next_token(delim); | |
1314 if (ok_to_include(dc, fn)) { | |
1315 load_conf_dcc(dc, name, fn); | |
1316 } | |
1317 } | |
1318 | |
1319 } | |
1320 is.close(); | |
1321 } | |
1322 | |
1323 | |
1324 static void load_conf(CONFIG &dc, char *fn); | |
1325 static void load_conf(CONFIG &dc, char *fn) { | |
1326 ifstream is(fn); | |
1327 if (is.fail()) { | |
1328 char buf[1000]; | |
1329 snprintf(buf, sizeof(buf), "include file %s not found", fn); | |
1330 my_syslog(buf); | |
1331 return; | |
1332 } | |
1333 dc.config_files.push_back(fn); | |
1334 map<char*, int, ltstr> commands; | |
1335 enum {dummy, tld, content, ignore, hostlimit, hostslimit, htmllimit, htmltag, dnsbl, dnsbll, envfrom, envto, include, includedcc}; | |
1336 commands["tld" ] = tld; | |
1337 commands["content" ] = content; | |
1338 commands["ignore" ] = ignore; | |
1339 commands["host_limit" ] = hostlimit; | |
1340 commands["host_soft_limit"] = hostslimit; | |
1341 commands["html_limit" ] = htmllimit; | |
1342 commands["html_tag" ] = htmltag; | |
1343 commands["dnsbl" ] = dnsbl; | |
1344 commands["dnsbl_list" ] = dnsbll; | |
1345 commands["env_from" ] = envfrom; | |
1346 commands["env_to" ] = envto; | |
1347 commands["include" ] = include; | |
1348 commands["include_dcc" ] = includedcc; | |
1349 const int LINE_SIZE = 2000; | |
1350 char line[LINE_SIZE]; | |
1351 char orig[LINE_SIZE]; | |
1352 char *delim = " \t"; | |
1353 int curline = 0; | |
1354 while (!is.eof()) { | |
1355 is.getline(line, LINE_SIZE); | |
1356 snprintf(orig, sizeof(orig), "%s", line); | |
1357 curline++; | |
1358 int n = strlen(line); | |
1359 for (int i=0; i<n; i++) line[i] = tolower(line[i]); | |
1360 char *cmd = strtok(line, delim); | |
1361 if (cmd && (cmd[0] != '#') && (cmd[0] != '\0')) { | |
1362 // have a decent command | |
1363 bool processed = false; | |
1364 switch (commands[cmd]) { | |
1365 case tld: { | |
1366 char *tld = next_token(delim); | |
1367 if (!tld) break; // no tld value | |
1368 dc.tlds.insert(tld); | |
1369 processed = true; | |
1370 } break; | |
1371 | |
1372 case content: { | |
1373 char *suff = strtok(NULL, delim); | |
1374 if (!suff) break; // no dns suffix | |
1375 char *msg = suff + strlen(suff); | |
1376 if ((msg - line) >= strlen(orig)) break; // line ended with the dns suffix | |
1377 msg = strchr(msg+1, '\''); | |
1378 if (!msg) break; // no reply message template | |
1379 msg++; // move over the leading ' | |
1380 if ((msg - line) >= strlen(orig)) break; // line ended with the leading quote | |
1381 char *last = strchr(msg, '\''); | |
1382 if (!last) break; // no trailing quote | |
1383 *last = '\0'; // make it a null terminator | |
1384 dc.content_suffix = register_string(suff); | |
1385 dc.content_message = register_string(msg); | |
1386 processed = true; | |
1387 } break; | |
1388 | |
1389 case ignore: { | |
1390 char *host = next_token(delim); | |
1391 if (!host) break; | |
1392 dc.content_host_ignore.insert(host); | |
1393 processed = true; | |
1394 } break; | |
1395 | |
1396 case hostlimit: { | |
1397 char *limit = strtok(NULL, delim); | |
1398 if (!limit) break; // no integer limit | |
1399 char *msg = limit + strlen(limit); | |
1400 if ((msg - line) >= strlen(orig)) break; // line ended with the limit | |
1401 msg = strchr(msg+1, '\''); | |
1402 if (!msg) break; // no reply message template | |
1403 msg++; // move over the leading ' | |
1404 if ((msg - line) >= strlen(orig)) break; // line ended with the leading quote | |
1405 char *last = strchr(msg, '\''); | |
1406 if (!last) break; // no trailing quote | |
1407 *last = '\0'; // make it a null terminator | |
1408 dc.host_limit = atoi(limit); | |
1409 dc.host_limit_message = register_string(msg); | |
1410 dc.host_random = false; | |
1411 processed = true; | |
1412 } break; | |
1413 | |
1414 case hostslimit: { | |
1415 char *limit = next_token(delim); | |
1416 if (!limit) break; // no integer limit | |
1417 dc.host_limit = atoi(limit); | |
1418 dc.host_random = true; | |
1419 processed = true; | |
1420 } break; | |
1421 | |
1422 case htmllimit: { | |
1423 char *limit = strtok(NULL, delim); | |
1424 if (!limit) break; // no integer limit | |
1425 char *msg = limit + strlen(limit); | |
1426 if ((msg - line) >= strlen(orig)) break; // line ended with the limit | |
1427 msg = strchr(msg+1, '\''); | |
1428 if (!msg) break; // no reply message template | |
1429 msg++; // move over the leading ' | |
1430 if ((msg - line) >= strlen(orig)) break; // line ended with the leading quote | |
1431 char *last = strchr(msg, '\''); | |
1432 if (!last) break; // no trailing quote | |
1433 *last = '\0'; // make it a null terminator | |
1434 dc.tag_limit = atoi(limit); | |
1435 dc.tag_limit_message = register_string(msg); | |
1436 processed = true; | |
1437 } break; | |
1438 | |
1439 case htmltag: { | |
1440 char *tag = next_token(delim); | |
1441 if (!tag) break; // no html tag value | |
1442 dc.html_tags.insert(tag); // base version | |
1443 char buf[200]; | |
1444 snprintf(buf, sizeof(buf), "/%s", tag); | |
1445 dc.html_tags.insert(register_string(buf)); // leading / | |
1446 snprintf(buf, sizeof(buf), "%s/", tag); | |
1447 dc.html_tags.insert(register_string(buf)); // trailing / | |
1448 processed = true; | |
1449 } break; | |
1450 | |
1451 case dnsbl: { | |
1452 // have a new dnsbl to use | |
1453 char *name = next_token(delim); | |
1454 if (!name) break; // no name name | |
1455 if (find_dnsbl(dc, name)) break; // duplicate entry | |
1456 char *suff = strtok(NULL, delim); | |
1457 if (!suff) break; // no dns suffic | |
1458 char *msg = suff + strlen(suff); | |
1459 if ((msg - line) >= strlen(orig)) break; // line ended with the dns suffix | |
1460 msg = strchr(msg+1, '\''); | |
1461 if (!msg) break; // no reply message template | |
1462 msg++; // move over the leading ' | |
1463 if ((msg - line) >= strlen(orig)) break; // line ended with the leading quote | |
1464 char *last = strchr(msg, '\''); | |
1465 if (!last) break; // no trailing quote | |
1466 *last = '\0'; // make it a null terminator | |
1467 dc.dnsbls[name] = new DNSBL(register_string(suff), register_string(msg)); | |
1468 processed = true; | |
1469 } break; | |
1470 | |
1471 case dnsbll: { | |
1472 // define a new combination of dnsbls | |
1473 char *name = next_token(delim); | |
1474 if (!name) break; | |
1475 if (find_dnsbll(dc, name)) break; // duplicate entry | |
1476 char *list = next_token(delim); | |
1477 if (!list || (*list == '\0') || (*list == '#')) break; | |
1478 DNSBLLP d = new DNSBLL; | |
1479 DNSBLP p = find_dnsbl(dc, list); | |
1480 if (p) d->push_back(p); | |
1481 while (true) { | |
1482 list = next_token(delim); | |
1483 if (!list || (*list == '\0') || (*list == '#')) break; | |
1484 DNSBLP p = find_dnsbl(dc, list); | |
1485 if (p) d->push_back(p); | |
1486 } | |
1487 dc.dnsblls[name] = d; | |
1488 processed = true; | |
1489 } break; | |
1490 | |
1491 case envfrom: { | |
1492 // add an entry into the named string_map | |
1493 char *name = next_token(delim); | |
1494 if (!name) break; | |
1495 char *from = next_token(delim); | |
1496 if (!from) break; | |
1497 char *list = next_token(delim); | |
1498 if (!list) break; | |
1499 if ((strcmp(list, WHITE) == 0) || | |
1500 (strcmp(list, BLACK) == 0)) { | |
1501 string_map &fm = really_find_from_map(dc, name); | |
1502 fm[from] = list; | |
1503 processed = true; | |
1504 } | |
1505 else { | |
1506 // list may be the name of a previously defined from_map | |
1507 string_map *m = find_from_map(dc, list); | |
1508 if (m && (strcmp(list,name) != 0)) { | |
1509 string_map &pm = *m; | |
1510 string_map &fm = really_find_from_map(dc, name); | |
1511 fm.insert(pm.begin(), pm.end()); | |
1512 processed = true; | |
1513 } | |
1514 } | |
1515 } break; | |
1516 | |
1517 case envto: { | |
1518 // define the dnsbl_list and env_from maps to use for this recipient | |
1519 char *to = next_token(delim); | |
1520 if (!to) break; | |
1521 char *list = next_token(delim); | |
1522 if (!list) break; | |
1523 char *from = next_token(delim); | |
1524 if (!from) break; | |
1525 dc.env_to_dnsbll[to] = list; | |
1526 dc.env_to_chkfrom[to] = from; | |
1527 processed = true; | |
1528 } break; | |
1529 | |
1530 case include: { | |
1531 char *fn = next_token(delim); | |
1532 if (ok_to_include(dc, fn)) { | |
1533 load_conf(dc, fn); | |
1534 processed = true; | |
1535 } | |
1536 } break; | |
1537 | |
1538 case includedcc: { | |
1539 char *name = next_token(delim); | |
1540 if (!name) break; | |
1541 char *fn = next_token(delim); | |
1542 if (ok_to_include(dc, fn)) { | |
1543 load_conf_dcc(dc, name, fn); | |
1544 processed = true; | |
1545 } | |
1546 } break; | |
1547 | |
1548 default: { | |
1549 } break; | |
1550 } | |
1551 if (!processed) { | |
1552 pthread_mutex_lock(&syslog_mutex); | |
1553 openlog("dnsbl", LOG_PID, LOG_MAIL); | |
1554 syslog(LOG_ERR, "ignoring file %s line %d : %s\n", fn, curline, orig); | |
1555 closelog(); | |
1556 pthread_mutex_unlock(&syslog_mutex); | |
1557 } | |
1558 } | |
1559 } | |
1560 is.close(); | |
1561 } | |
1562 | |
1563 | |
1564 //////////////////////////////////////////////// | 918 //////////////////////////////////////////////// |
1565 // reload the config | 919 // reload the config |
1566 // | 920 // |
1567 static CONFIG* new_conf(); | 921 CONFIG* new_conf(); |
1568 static CONFIG* new_conf() { | 922 CONFIG* new_conf() { |
1569 CONFIG *newc = new CONFIG; | 923 CONFIG *newc = new CONFIG; |
1570 pthread_mutex_lock(&config_mutex); | 924 pthread_mutex_lock(&config_mutex); |
1571 newc->generation = generation++; | 925 newc->generation = generation++; |
1572 pthread_mutex_unlock(&config_mutex); | 926 pthread_mutex_unlock(&config_mutex); |
1573 char buf[200]; | 927 char buf[200]; |
1574 snprintf(buf, sizeof(buf), "loading configuration generation %d", newc->generation); | 928 snprintf(buf, sizeof(buf), "loading configuration generation %d", newc->generation); |
1575 my_syslog(buf); | 929 my_syslog(buf); |
1576 load_conf(*newc, "dnsbl.conf"); | 930 if (load_conf(*newc, "dnsbl.conf") { |
1577 newc->load_time = time(NULL); | 931 newc->load_time = time(NULL); |
1578 return newc; | 932 return newc; |
933 } | |
934 delete newc; | |
935 return NULL; | |
1579 } | 936 } |
1580 | 937 |
1581 | 938 |
1582 //////////////////////////////////////////////// | 939 //////////////////////////////////////////////// |
1583 // thread to watch the old config files for changes | 940 // thread to watch the old config files for changes |
1584 // and reload when needed. we also cleanup old | 941 // and reload when needed. we also cleanup old |
1585 // configs whose reference count has gone to zero. | 942 // configs whose reference count has gone to zero. |
1586 // | 943 // |
1587 static void* config_loader(void *arg); | 944 void* config_loader(void *arg); |
1588 static void* config_loader(void *arg) { | 945 void* config_loader(void *arg) { |
1589 typedef set<CONFIG *> configp_set; | 946 typedef set<CONFIG *> configp_set; |
1590 configp_set old_configs; | 947 configp_set old_configs; |
1591 while (loader_run) { | 948 while (loader_run) { |
1592 sleep(180); // look for modifications every 3 minutes | 949 sleep(180); // look for modifications every 3 minutes |
1593 if (!loader_run) break; | 950 if (!loader_run) break; |
1625 } | 982 } |
1626 return NULL; | 983 return NULL; |
1627 } | 984 } |
1628 | 985 |
1629 | 986 |
1630 static void usage(char *prog); | 987 void usage(char *prog); |
1631 static void usage(char *prog) | 988 void usage(char *prog) |
1632 { | 989 { |
1633 fprintf(stderr, "Usage: %s [-d] [-c] -r port -p sm-sock-addr [-t timeout]\n", prog); | 990 fprintf(stderr, "Usage: %s [-d] [-c] -r port -p sm-sock-addr [-t timeout]\n", prog); |
1634 fprintf(stderr, "where port is for the connection to our own dns resolver processes\n"); | 991 fprintf(stderr, "where port is for the connection to our own dns resolver processes\n"); |
1635 fprintf(stderr, " and should be local-domain-socket-file-name\n"); | 992 fprintf(stderr, " and should be local-domain-socket-file-name\n"); |
1636 fprintf(stderr, "where sm-sock-addr is for the connection to sendmail\n"); | 993 fprintf(stderr, "where sm-sock-addr is for the connection to sendmail\n"); |
1641 fprintf(stderr, "-d will add some syslog debug messages\n"); | 998 fprintf(stderr, "-d will add some syslog debug messages\n"); |
1642 } | 999 } |
1643 | 1000 |
1644 | 1001 |
1645 | 1002 |
1646 static void setup_socket(char *sock); | 1003 void setup_socket(char *sock); |
1647 static void setup_socket(char *sock) { | 1004 void setup_socket(char *sock) { |
1648 unlink(sock); | 1005 unlink(sock); |
1649 // sockaddr_un addr; | 1006 // sockaddr_un addr; |
1650 // memset(&addr, '\0', sizeof addr); | 1007 // memset(&addr, '\0', sizeof addr); |
1651 // addr.sun_family = AF_UNIX; | 1008 // addr.sun_family = AF_UNIX; |
1652 // strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1); | 1009 // strncpy(addr.sun_path, sock, sizeof(addr.sun_path)-1); |
1670 } | 1027 } |
1671 | 1028 |
1672 | 1029 |
1673 int main(int argc, char**argv) | 1030 int main(int argc, char**argv) |
1674 { | 1031 { |
1032 token_init(); | |
1675 bool check = false; | 1033 bool check = false; |
1676 bool setconn = false; | 1034 bool setconn = false; |
1677 bool setreso = false; | 1035 bool setreso = false; |
1678 int c; | 1036 int c; |
1679 const char *args = "r:p:t:hcd"; | 1037 const char *args = "r:p:t:hcd"; |
1731 exit(EX_USAGE); | 1089 exit(EX_USAGE); |
1732 } | 1090 } |
1733 } | 1091 } |
1734 | 1092 |
1735 if (check) { | 1093 if (check) { |
1736 CONFIG &dc = *new_conf(); | 1094 CONFIG *conf = new_conf(); |
1737 dumpit(dc); | 1095 if (conf) { |
1738 return 0; | 1096 conf->dump(); |
1097 delete conf; | |
1098 return 0; | |
1099 } | |
1100 else { | |
1101 return 1; // config failed to load | |
1102 } | |
1739 } | 1103 } |
1740 | 1104 |
1741 if (!setconn) { | 1105 if (!setconn) { |
1742 fprintf(stderr, "%s: Missing required -p argument\n", argv[0]); | 1106 fprintf(stderr, "%s: Missing required -p argument\n", argv[0]); |
1743 usage(argv[0]); | 1107 usage(argv[0]); |