1 //
2 //
3 // $Id: spamass-milter.cpp,v 1.100 2014/08/15 02:46:50 kovert Exp $
4 //
5 // SpamAss-Milter
6 // - a rather trivial SpamAssassin Sendmail Milter plugin
7 //
8 // for information about SpamAssassin please see
9 // http://www.spamassassin.org
10 //
11 // for information about Sendmail please see
12 // http://www.sendmail.org
13 //
14 // Copyright (c) 2002 Georg C. F. Greve <greve@gnu.org>,
15 // all rights maintained by FSF Europe e.V.,
16 // Villa Vogelsang, Antonienallee 1, 45279 Essen, Germany
17 //
18
19 // {{{ License, Contact, Notes & Includes
20
21 // This program is free software; you can redistribute it and/or modify
22 // it under the terms of the GNU General Public License as published by
23 // the Free Software Foundation; either version 2 of the License, or
24 // (at your option) any later version.
25 //
26 // This program is distributed in the hope that it will be useful,
27 // but WITHOUT ANY WARRANTY; without even the implied warranty of
28 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 // GNU General Public License for more details.
30 //
31 // You should have received a copy of the GNU General Public License
32 // along with this program; if not, write to the Free Software
33 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34 //
35 // Contact:
36 // Michael Brown <michaelb@opentext.com>
37 //
38
39 // Notes:
40 //
41 // The libmilter for sendmail works callback-oriented, so if you have no
42 // experience with event-driven programming, the following may be hard for
43 // you to understand.
44 //
45 // The code should be reasonably thread-safe. No guarantees, though.
46 //
47 // This program roughly does the following steps:
48 //
49 // 1. register filter with libmilter & set up socket
50 // 2. register the callback functions defined in this file
51 // -- wait for mail to show up --
52 // 3. start spamc client
53 // 4. assemble mail since libmilter passes it in pieces and put
54 // these parts in the output pipe to spamc.
55 // 5. when the mail is complete, close the pipe.
56 // 6. read output from spamc, close input pipe and clean up PID
57 // 7. check for the flags affected by SpamAssassin and set/change
58 // them accordingly
59 // 8. replace the body with the one provided by SpamAssassin if the
60 // mail was rated spam, unless -m is specified
61 // 9. free all temporary data
62 // 10. tell sendmail to let the mail to go on (default) or be discarded
63 // -- wait for mail to show up -- (restart at 3)
64 //
65
66 // Includes
67 #include "config.h"
68
69 #include <arpa/inet.h>
70 #include <sys/types.h>
71 #include <sys/wait.h>
72 #include <sys/stat.h>
73 #include <netinet/in.h>
74 #include <stdio.h>
75 #include <stdlib.h>
76 #include <stdarg.h>
77 #include <string.h>
78 #include <strings.h>
79 #include <sysexits.h>
80 #include <unistd.h>
81 #include <fcntl.h>
82 #include <syslog.h>
83 #include <signal.h>
84 #include <pthread.h>
85 #ifdef HAVE_POLL_H
86 #include <poll.h>
87 #else
88 #include "subst_poll.h"
89 #endif
90 #include <errno.h>
91 #include <netdb.h>
92
93 // C++ includes
94 #include <cstdio>
95 #include <cstddef>
96 #include <csignal>
97 #include <string>
98 #include <iostream>
99
100 #ifdef __cplusplus
101 extern "C" {
102 #endif
103
104 #include "libmilter/mfapi.h"
105 //#include "libmilter/mfdef.h"
106
107 #if !HAVE_DECL_STRSEP
108 char *strsep(char **stringp, const char *delim);
109 #endif
110
111 #if !HAVE_DECL_DAEMON
112 int daemon(int nochdir, int noclose);
113 #endif
114
115 #ifdef __cplusplus
116 }
117 #endif
118
119 #include "spamass-milter.h"
120
121 #ifdef WITH_DMALLOC
122 #include "dmalloc.h"
123 #endif
124
125 #ifndef INADDR_LOOPBACK
126 #define INADDR_LOOPBACK 0x7F000001
127 #endif
128
129 // }}}
130
131 static const char Id[] = "$Id: spamass-milter.cpp,v 1.100 2014/08/15 02:46:50 kovert Exp $";
132
133 static char FilterName[] = "SpamAssassin";
134
135 struct smfiDesc smfilter =
136 {
137 FilterName, // filter name
138 SMFI_VERSION, // version code -- leave untouched
139 SMFIF_ADDHDRS|SMFIF_CHGHDRS|SMFIF_CHGBODY, // flags
140 mlfi_connect, // info filter callback
141 mlfi_helo, // HELO filter callback
142 mlfi_envfrom, // envelope sender filter callback
143 mlfi_envrcpt, // envelope recipient filter callback
144 mlfi_header, // header filter callback
145 mlfi_eoh, // end of header callback
146 mlfi_body, // body filter callback
147 mlfi_eom, // end of message callback
148 mlfi_abort, // message aborted callback
149 mlfi_close, // connection cleanup callback
150 };
151
152 const char *const debugstrings[] = {
153 "ALL", "FUNC", "POLL", "UORI", "STR", "MISC", "NET", "SPAMC", "RCPT",
154 "COPY",
155 NULL
156 };
157
158 int flag_debug = (1<<D_ALWAYS);
159 bool flag_reject = false;
160 int reject_score = -1;
161 bool dontmodifyspam = false; // Don't modify/add body or spam results headers
162 bool dontmodify = false; // Don't add SA headers, ever.
163 bool flag_sniffuser = false;
164 char *defaultuser; /* Username to send to spamc if there are multiple recipients */
165 char *defaultdomain; /* Domain to append if incoming address has none */
166 char *path_to_sendmail = (char *) SENDMAIL;
167 char *spamdhost;
168 char *rejecttext = NULL; /* If we reject a mail, then use this text */
169 char *rejectcode = NULL; /* If we reject a mail, then use code */
170 struct networklist ignorenets;
171 int spamc_argc;
172 char **spamc_argv;
173 bool flag_bucket = false;
174 bool flag_bucket_only = false;
175 char *spambucket;
176 bool flag_full_email = false; /* pass full email address to spamc */
177 bool flag_expand = false; /* alias/virtusertable expansion */
178 bool warnedmacro = false; /* have we logged that we couldn't fetch a macro? */
179 bool auth = false; /* don't scan authenticated users */
180
181 // {{{ main()
182
183 int
main(int argc,char * argv[])184 main(int argc, char* argv[])
185 {
186 int c, err = 0;
187 const char *args = "afd:mMp:P:r:u:D:i:b:B:e:xS:R:C:";
188 char *sock = NULL;
189 bool dofork = false;
190 char *pidfilename = NULL;
191 FILE *pidfile = NULL;
192
193 #ifdef HAVE_VERBOSE_TERMINATE_HANDLER
194 std::set_terminate (__gnu_cxx::__verbose_terminate_handler);
195 #endif
196
197 openlog("spamass-milter", LOG_PID, LOG_MAIL);
198
199
200 /* Process command line options */
201 while ((c = getopt(argc, argv, args)) != -1) {
202 switch (c) {
203 case 'a':
204 auth = true;
205 break;
206 case 'f':
207 dofork = true;
208 break;
209 case 'd':
210 parse_debuglevel(optarg);
211 break;
212 case 'D':
213 spamdhost = strdup(optarg);
214 break;
215 case 'e':
216 flag_full_email = true;
217 defaultdomain = strdup(optarg);
218 break;
219 case 'i':
220 debug(D_MISC, "Parsing ignore list");
221 parse_networklist(optarg, &ignorenets);
222 break;
223 case 'm':
224 dontmodifyspam = true;
225 smfilter.xxfi_flags &= ~SMFIF_CHGBODY;
226 break;
227 case 'M':
228 dontmodify = true;
229 dontmodifyspam = true;
230 smfilter.xxfi_flags &= ~(SMFIF_CHGBODY|SMFIF_CHGHDRS);
231 break;
232 case 'p':
233 sock = strdup(optarg);
234 break;
235 case 'P':
236 pidfilename = strdup(optarg);
237 break;
238 case 'r':
239 flag_reject = true;
240 reject_score = atoi(optarg);
241 break;
242 case 'S':
243 path_to_sendmail = strdup(optarg);
244 break;
245 case 'C':
246 rejectcode = strdup (optarg);
247 break;
248 case 'R':
249 rejecttext = strdup (optarg);
250 break;
251 case 'u':
252 flag_sniffuser = true;
253 defaultuser = strdup(optarg);
254 break;
255 case 'b':
256 case 'B':
257 if (flag_bucket)
258 {
259 fprintf(stderr, "Can only have one -b or -B flag\n");
260 err = 1;
261 break;
262 }
263 flag_bucket = true;
264 if (c == 'b')
265 {
266 flag_bucket_only = true;
267 smfilter.xxfi_flags |= SMFIF_DELRCPT; // May delete recipients
268 }
269 // we will modify the recipient list; if spamc returns
270 // indicating that this mail is spam, the message will be
271 // sent to <optarg>@localhost
272 smfilter.xxfi_flags |= SMFIF_ADDRCPT; // May add recipients
273 // XXX we should probably verify that optarg is vaguely sane
274 spambucket = strdup( optarg );
275 break;
276 case 'x':
277 flag_expand = true;
278 break;
279 case '?':
280 err = 1;
281 break;
282 }
283 }
284
285 if (flag_full_email && !flag_sniffuser)
286 {
287 fprintf(stderr, "-e flag requires -u\n");
288 err=1;
289 }
290
291 /* remember the remainer of the arguments so we can pass them to spamc */
292 spamc_argc = argc - optind;
293 spamc_argv = argv + optind;
294
295 if (!sock || err) {
296 cout << PACKAGE_NAME << " - Version " << PACKAGE_VERSION << endl;
297 cout << "SpamAssassin Sendmail Milter Plugin" << endl;
298 cout << "Usage: spamass-milter -p socket [-b|-B bucket] [-d xx[,yy...]] [-D host]" << endl;
299 cout << " [-e defaultdomain] [-f] [-i networks] [-m] [-M]" << endl;
300 cout << " [-P pidfile] [-r nn] [-u defaultuser] [-x] [-a]" << endl;
301 cout << " [-C rejectcode] [ -R rejectmsg ]" << endl;
302 cout << " [-- spamc args ]" << endl;
303 cout << " -p socket: path to create socket" << endl;
304 cout << " -b bucket: redirect spam to this mail address. The orignal" << endl;
305 cout << " recipient(s) will not receive anything." << endl;
306 cout << " -B bucket: add this mail address as a BCC recipient of spam." << endl;
307 cout << " -C RejectCode: using this Reject Code." << endl;
308 cout << " -d xx[,yy ...]: set debug flags. Logs to syslog" << endl;
309 cout << " -D host: connect to spamd at remote host (deprecated)" << endl;
310 cout << " -e defaultdomain: pass full email address to spamc instead of just\n"
311 " username. Uses 'defaultdomain' if there was none" << endl;
312 cout << " -f: fork into background" << endl;
313 cout << " -i: skip (ignore) checks from these IPs or netblocks" << endl;
314 cout << " example: -i 192.168.12.5,10.0.0.0/8,172.16.0.0/255.255.0.0" << endl;
315 cout << " -m: don't modify body, Content-type: or Subject:" << endl;
316 cout << " -M: don't modify the message at all" << endl;
317 cout << " -P pidfile: Put processid in pidfile" << endl;
318 cout << " -r nn: reject messages with a score >= nn with an SMTP error.\n"
319 " use -1 to reject any messages tagged by SA." << endl;
320 cout << " -R RejectText: using this Reject Text." << endl;
321 cout << " -u defaultuser: pass the recipient's username to spamc.\n"
322 " Uses 'defaultuser' if there are multiple recipients." << endl;
323 cout << " -x: pass email address through alias and virtusertable expansion." << endl;
324 cout << " -a: don't scan messages over an authenticated connection." << endl;
325 cout << " -- spamc args: pass the remaining flags to spamc." << endl;
326
327 exit(EX_USAGE);
328 }
329
330 /* Set standard reject text */
331 if (rejecttext == NULL) {
332 rejecttext = strdup ("Blocked by SpamAssassin");
333 }
334 if (rejectcode == NULL) {
335 rejectcode = strdup ("5.7.1");
336 }
337
338 if (pidfilename)
339 {
340 unlink(pidfilename);
341 pidfile = fopen(pidfilename,"w");
342 if (!pidfile)
343 {
344 fprintf(stderr, "Could not open pidfile: %s\n", strerror(errno));
345 exit(1);
346 }
347 /* leave the file open through the fork, since we don't know our pid
348 yet
349 */
350 }
351
352
353 if (dofork == true)
354 {
355 if (daemon(0, 0) == -1)
356 {
357 fprintf(stderr, "daemon() failed: %s\n", strerror(errno));
358 exit(1);
359 }
360 }
361
362 if (pidfile)
363 {
364 fprintf(pidfile, "%ld\n", (long)getpid());
365 fclose(pidfile);
366 pidfile = NULL;
367 }
368
369 {
370 struct stat junk;
371 if (stat(sock,&junk) == 0) unlink(sock);
372 }
373
374 (void) smfi_setconn(sock);
375 if (smfi_register(smfilter) == MI_FAILURE) {
376 fprintf(stderr, "smfi_register failed\n");
377 exit(EX_UNAVAILABLE);
378 } else {
379 debug(D_MISC, "smfi_register succeeded");
380 }
381 debug(D_ALWAYS, "spamass-milter %s starting", PACKAGE_VERSION);
382 err = smfi_main();
383 debug(D_ALWAYS, "spamass-milter %s exiting", PACKAGE_VERSION);
384 if (pidfilename)
385 unlink(pidfilename);
386 return err;
387 }
388
389 // }}}
390
391 /* Update a header if SA changes it, or add it if it is new. */
update_or_insert(SpamAssassin * assassin,SMFICTX * ctx,string oldstring,t_setter setter,const char * header)392 void update_or_insert(SpamAssassin* assassin, SMFICTX* ctx, string oldstring, t_setter setter, const char *header )
393 {
394 string::size_type eoh1 = assassin->d().find("\n\n");
395 string::size_type eoh2 = assassin->d().find("\n\r\n");
396 string::size_type eoh = ( eoh1 < eoh2 ? eoh1 : eoh2 );
397
398 string newstring;
399 string::size_type oldsize;
400
401 debug(D_UORI, "u_or_i: looking at <%s>", header);
402 debug(D_UORI, "u_or_i: oldstring: <%s>", oldstring.c_str());
403
404 newstring = retrieve_field(assassin->d().substr(0, eoh), header);
405 debug(D_UORI, "u_or_i: newstring: <%s>", newstring.c_str());
406
407 oldsize = callsetter(*assassin,setter)(newstring);
408
409 if (!dontmodify)
410 {
411 if (newstring != oldstring)
412 {
413 /* change if old one was present, append if non-null */
414 char* cstr = const_cast<char*>(newstring.c_str());
415 if (oldsize > 0)
416 {
417 debug(D_UORI, "u_or_i: changing");
418 smfi_chgheader(ctx, const_cast<char*>(header), 1, newstring.size() > 0 ?
419 cstr : NULL );
420 } else if (newstring.size() > 0)
421 {
422 debug(D_UORI, "u_or_i: inserting");
423 smfi_addheader(ctx, const_cast<char*>(header), cstr);
424 }
425 } else
426 {
427 debug(D_UORI, "u_or_i: no change");
428 }
429 }
430 }
431
432 // {{{ Assassinate
433
434 //
435 // implement the changes suggested by SpamAssassin for the mail. Returns
436 // the milter error code.
437 int
assassinate(SMFICTX * ctx,SpamAssassin * assassin)438 assassinate(SMFICTX* ctx, SpamAssassin* assassin)
439 {
440 // find end of header (eol in last line of header)
441 // and beginning of body
442 string::size_type eoh1 = assassin->d().find("\n\n");
443 string::size_type eoh2 = assassin->d().find("\n\r\n");
444 string::size_type eoh = (eoh1 < eoh2) ? eoh1 : eoh2;
445 string::size_type bob = assassin->d().find_first_not_of("\r\n", eoh);
446
447 if (bob == string::npos)
448 bob = assassin->d().size();
449
450 update_or_insert(assassin, ctx, assassin->spam_flag(), &SpamAssassin::set_spam_flag, "X-Spam-Flag");
451 update_or_insert(assassin, ctx, assassin->spam_status(), &SpamAssassin::set_spam_status, "X-Spam-Status");
452
453 /* Summarily reject the message if SA tagged it, or if we have a minimum
454 score, reject if it exceeds that score. */
455 if (flag_reject)
456 {
457 bool do_reject = false;
458 if (reject_score == -1 && !assassin->spam_flag().empty())
459 do_reject = true;
460 if (reject_score != -1)
461 {
462 int score, rv;
463 const char *spam_status = assassin->spam_status().c_str();
464 /* SA 3.0 uses the keyword "score" */
465 rv = sscanf(spam_status,"%*s score=%d", &score);
466 if (rv != 1)
467 {
468 /* SA 2.x uses the keyword "hits" */
469 rv = sscanf(spam_status,"%*s hits=%d", &score);
470 }
471 if (rv != 1)
472 debug(D_ALWAYS, "Could not extract score from <%s>", spam_status);
473 else
474 {
475 debug(D_MISC, "SA score: %d", score);
476 if (score >= reject_score)
477 do_reject = true;
478 }
479 }
480 if (do_reject)
481 {
482 debug(D_MISC, "Rejecting");
483 smfi_setreply(ctx, const_cast<char*>("550"), rejectcode, rejecttext);
484
485
486 if (flag_bucket)
487 {
488 /* If we also want a copy of the spam, shell out to sendmail and
489 send another copy. The milter API will not let you send the
490 message AND return a failure code to the sender, so this is
491 the only way to do it. */
492 char *popen_argv[3];
493 FILE *p;
494 pid_t pid;
495
496 popen_argv[0] = path_to_sendmail;
497 popen_argv[1] = spambucket;
498 popen_argv[2] = NULL;
499
500 debug(D_COPY, "calling %s %s", path_to_sendmail, spambucket);
501 p = popenv(popen_argv, "w", &pid);
502 if (!p)
503 {
504 debug(D_COPY, "popenv failed(%s). Will not send a copy to spambucket", strerror(errno));
505 } else
506 {
507 // Send message provided by SpamAssassin
508 fwrite(assassin->d().c_str(), assassin->d().size(), 1, p);
509 fclose(p); p = NULL;
510 waitpid(pid, NULL, 0);
511 }
512 }
513 return SMFIS_REJECT;
514 }
515 }
516
517 /* Drop the message into the spam bucket if it's spam */
518 if ( flag_bucket ) {
519 if (!assassin->spam_flag().empty()) {
520 // first, add the spambucket address
521 if ( smfi_addrcpt( ctx, spambucket ) != MI_SUCCESS ) {
522 throw string( "Failed to add spambucket to recipients" );
523 }
524 if (flag_bucket_only) {
525 // Move recipients to a non-active header, one at a
526 // time. Note, this may generate multiple X-Spam-Orig-To
527 // headers, but that's okay.
528 while( !assassin->recipients.empty()) {
529 if ( smfi_addheader( ctx, const_cast<char *>("X-Spam-Orig-To"), (char *)assassin->recipients.front().c_str()) != MI_SUCCESS ) {
530 throw string( "Failed to save recipient" );
531 }
532
533 // It's not 100% important that this succeeds, so we'll just warn on failure rather than bailing out.
534 if ( smfi_delrcpt( ctx, (char *)assassin->recipients.front().c_str()) != MI_SUCCESS ) {
535 // throw_error really just logs a warning as opposed to actually throw()ing
536 debug(D_ALWAYS, "Failed to remove recipient %s from the envelope", assassin->recipients.front().c_str() );
537 }
538 assassin->recipients.pop_front();
539 }
540 }
541 }
542 }
543
544 update_or_insert(assassin, ctx, assassin->spam_report(), &SpamAssassin::set_spam_report, "X-Spam-Report");
545 update_or_insert(assassin, ctx, assassin->spam_prev_content_type(), &SpamAssassin::set_spam_prev_content_type, "X-Spam-Prev-Content-Type");
546 update_or_insert(assassin, ctx, assassin->spam_level(), &SpamAssassin::set_spam_level, "X-Spam-Level");
547 update_or_insert(assassin, ctx, assassin->spam_checker_version(), &SpamAssassin::set_spam_checker_version, "X-Spam-Checker-Version");
548
549 //
550 // If SpamAssassin thinks it is spam, replace
551 // Subject:
552 // Content-Type:
553 // <Body>
554 //
555 // However, only issue the header replacement calls if the content has
556 // actually changed. If SA didn't change subject or content-type, don't
557 // replace here unnecessarily.
558 if (!dontmodifyspam && assassin->spam_flag().size()>0)
559 {
560 update_or_insert(assassin, ctx, assassin->subject(), &SpamAssassin::set_subject, "Subject");
561 update_or_insert(assassin, ctx, assassin->content_type(), &SpamAssassin::set_content_type, "Content-Type");
562
563 // Replace body with the one SpamAssassin provided
564 string::size_type body_size = assassin->d().size() - bob;
565 string body=assassin->d().substr(bob, string::npos);
566 if ( smfi_replacebody(ctx, (unsigned char *)body.c_str(), body_size) == MI_FAILURE )
567 throw string("error. could not replace body.");
568
569 }
570
571 return SMFIS_CONTINUE;
572 }
573
574 // retrieve the content of a specific field in the header
575 // and return it.
576 string
old_retrieve_field(const string & header,const string & field)577 old_retrieve_field(const string& header, const string& field)
578 {
579 // look for beginning of content
580 string::size_type pos = find_nocase(header, "\n" + field + ": ");
581
582 // return empty string if not found
583 if (pos == string::npos)
584 {
585 debug(D_STR, "r_f: failed");
586 return string("");
587 }
588
589 // look for end of field name
590 pos = find_nocase(header, " ", pos) + 1;
591
592 string::size_type pos2(pos);
593
594 // is field empty?
595 if (pos2 == find_nocase(header, "\n", pos2))
596 return string("");
597
598 // look for end of content
599 do {
600
601 pos2 = find_nocase(header, "\n", pos2+1);
602
603 }
604 while ( pos2 < string::npos &&
605 isspace(header[pos2+1]) );
606
607 return header.substr(pos, pos2-pos);
608
609 }
610
611 // retrieve the content of a specific field in the header
612 // and return it.
613 string
retrieve_field(const string & header,const string & field)614 retrieve_field(const string& header, const string& field)
615 {
616 // Find the field
617 string::size_type field_start = string::npos;
618 string::size_type field_end = string::npos;
619 string::size_type idx = 0;
620
621 while( field_start == string::npos ) {
622 idx = find_nocase( header, field + ":", idx );
623
624 // no match
625 if ( idx == string::npos ) {
626 return string( "" );
627 }
628
629 // The string we've found needs to be either at the start of the
630 // headers string, or immediately following a "\n"
631 if ( idx != 0 ) {
632 if ( header[ idx - 1 ] != '\n' ) {
633 idx++; // so we don't get stuck in an infinite loop
634 continue; // loop around again
635 }
636 }
637
638 field_start = idx;
639 }
640
641 // A mail field starts just after the header. Ideally, there's a
642 // space, but it's possible that there isn't.
643 field_start += field.length() + 1;
644 if ( field_start < ( header.length() - 1 ) && header[ field_start ] == ' ' ) {
645 field_start++;
646 }
647
648 // See if there's anything left, to shortcut the rest of the
649 // function.
650 if ( field_start == header.length() - 1 ) {
651 return string( "" );
652 }
653
654 // The field continues to the end of line. If the start of the next
655 // line is whitespace, then the field continues.
656 idx = field_start;
657 while( field_end == string::npos ) {
658 idx = header.find( "\n", idx );
659
660 // if we don't find a "\n", gobble everything to the end of the headers
661 if ( idx == string::npos ) {
662 field_end = header.length();
663 } else {
664 // check the next character
665 if (( idx + 1 ) < header.length() && ( isspace( header[ idx + 1 ] ))) {
666 idx ++; // whitespace found, so wrap to the next line
667 } else {
668 field_end = idx;
669 }
670 }
671 }
672
673 /* if the header line ends in \r\n, don't return the \r */
674 if (header[field_end-1] == '\r')
675 field_end--;
676
677 string data = header.substr( field_start, field_end - field_start );
678
679 /* Replace all CRLF pairs with LF */
680 idx = 0;
681 while ( (idx = data.find("\r\n", idx)) != string::npos )
682 {
683 data.replace(idx,2,"\n");
684 }
685
686 return data;
687 }
688
689
690 // }}}
691
692 // {{{ MLFI callbacks
693
694 //
695 // Gets called once when a client connects to sendmail
696 //
697 // gets the originating IP address and checks it against the ignore list
698 // if it isn't in the list, store the IP in a structure and store a
699 // pointer to it in the private data area.
700 //
701 sfsistat
mlfi_connect(SMFICTX * ctx,char * hostname,_SOCK_ADDR * hostaddr)702 mlfi_connect(SMFICTX * ctx, char *hostname, _SOCK_ADDR * hostaddr)
703 {
704 struct context *sctx;
705 int rv;
706
707 debug(D_FUNC, "mlfi_connect: enter");
708
709 /* allocate a structure to store the IP address (and SA object) in */
710 sctx = (struct context *)malloc(sizeof(*sctx));
711 if (!hostaddr)
712 {
713 static struct sockaddr_in localhost;
714
715 /* not a socket; probably a local user calling sendmail directly */
716 /* set to 127.0.0.1 */
717 strcpy(sctx->connect_ip, "127.0.0.1");
718 localhost.sin_family = AF_INET;
719 localhost.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
720 hostaddr = (struct sockaddr*) &localhost;
721 } else
722 {
723 getnameinfo(hostaddr, sizeof(struct sockaddr_in6),
724 sctx->connect_ip, 63, NULL, 0, NI_NUMERICHOST);
725 debug(D_FUNC, "Remote address: %s", sctx->connect_ip);
726 }
727 sctx->assassin = NULL;
728 sctx->helo = NULL;
729
730 /* store a pointer to it with setpriv */
731 rv = smfi_setpriv(ctx, sctx);
732 if (rv != MI_SUCCESS)
733 {
734 debug(D_ALWAYS, "smfi_setpriv failed!");
735 return SMFIS_TEMPFAIL;
736 }
737 /* debug(D_ALWAYS, "ZZZ set private context to %p", sctx); */
738
739 //debug(D_FUNC, "sctx->connect_ip: `%d'", sctx->connect_ip.sin_family);
740
741 if (ip_in_networklist(hostaddr, &ignorenets))
742 {
743 debug(D_NET, "%s is in our ignore list - accepting message",
744 sctx->connect_ip);
745 debug(D_FUNC, "mlfi_connect: exit ignore");
746 return SMFIS_ACCEPT;
747 }
748
749 // Tell Milter to continue
750 debug(D_FUNC, "mlfi_connect: exit");
751
752 return SMFIS_CONTINUE;
753 }
754
755 //
756 // Gets called on every "HELO"
757 //
758 // stores the result in the context structure
759 //
mlfi_helo(SMFICTX * ctx,char * helohost)760 sfsistat mlfi_helo(SMFICTX * ctx, char * helohost)
761 {
762 struct context *sctx = (struct context*)smfi_getpriv(ctx);
763 if (sctx->helo)
764 free(sctx->helo);
765 sctx->helo = strdup(helohost);
766
767 return SMFIS_CONTINUE;
768 }
769
770 //
771 // Gets called first for all messages
772 //
773 // creates SpamAssassin object and makes pointer to it
774 // private data of this filter process
775 //
776 sfsistat
mlfi_envfrom(SMFICTX * ctx,char ** envfrom)777 mlfi_envfrom(SMFICTX* ctx, char** envfrom)
778 {
779 SpamAssassin* assassin;
780 struct context *sctx = (struct context *)smfi_getpriv(ctx);
781 const char *queueid;
782
783 if (sctx == NULL)
784 {
785 debug(D_ALWAYS, "smfi_getpriv failed!");
786 return SMFIS_TEMPFAIL;
787 }
788 /* debug(D_ALWAYS, "ZZZ got private context %p", sctx); */
789
790 if (auth) {
791 const char *auth_type = smfi_getsymval(ctx,
792 const_cast<char *>("{auth_type}"));
793
794 if (auth_type) {
795 debug(D_MISC, "auth_type=%s", auth_type);
796 return SMFIS_ACCEPT;
797 }
798 }
799
800 debug(D_FUNC, "mlfi_envfrom: enter");
801 try {
802 // launch new SpamAssassin
803 assassin=new SpamAssassin;
804 } catch (string& problem)
805 {
806 throw_error(problem);
807 return SMFIS_TEMPFAIL;
808 };
809
810 assassin->set_connectip(string(sctx->connect_ip));
811
812 // Store a pointer to the assassin object in our context struct
813 sctx->assassin = assassin;
814
815 // remember the MAIL FROM address
816 assassin->set_from(string(envfrom[0]));
817
818 queueid=smfi_getsymval(ctx, const_cast<char *>("i"));
819 if (!queueid)
820 {
821 queueid="unknown";
822 warnmacro("i", "ENVFROM");
823 }
824 assassin->queueid = queueid;
825
826 debug(D_MISC, "queueid=%s", queueid);
827
828 // tell Milter to continue
829 debug(D_FUNC, "mlfi_envfrom: exit");
830
831 return SMFIS_CONTINUE;
832 }
833
834 //
835 // Gets called once for each recipient
836 //
837 // stores the first recipient in the spamassassin object and
838 // stores all addresses and the number thereof (some redundancy)
839 //
840
841
842 sfsistat
mlfi_envrcpt(SMFICTX * ctx,char ** envrcpt)843 mlfi_envrcpt(SMFICTX* ctx, char** envrcpt)
844 {
845 struct context *sctx = (struct context*)smfi_getpriv(ctx);
846 SpamAssassin* assassin = sctx->assassin;
847 FILE *p;
848
849 debug(D_FUNC, "mlfi_envrcpt: enter");
850
851 if (flag_expand)
852 {
853 /* open a pipe to sendmail so we can do address expansion */
854
855 char buf[1024];
856 char *popen_argv[4];
857 pid_t pid;
858
859 popen_argv[0] = path_to_sendmail;
860 popen_argv[1] = (char *)"-bv";
861 popen_argv[2] = envrcpt[0];
862 popen_argv[3] = NULL;
863
864 debug(D_RCPT, "calling %s -bv %s", path_to_sendmail, envrcpt[0]);
865
866 p = popenv(popen_argv, "r", &pid);
867 if (!p)
868 {
869 debug(D_RCPT, "popenv failed(%s). Will not expand aliases", strerror(errno));
870 assassin->expandedrcpt.push_back(envrcpt[0]);
871 } else
872 {
873 while (fgets(buf, sizeof(buf), p) != NULL)
874 {
875 int i = strlen(buf);
876 /* strip trailing EOLs */
877 while (i > 0 && buf[i - 1] <= ' ')
878 i--;
879 buf[i] = '\0';
880 debug(D_RCPT, "sendmail output: %s", buf);
881 /* From a quick scan of the sendmail source, a valid email
882 address gets printed via either
883 "deliverable: mailer %s, host %s, user %s"
884 or "deliverable: mailer %s, user %s"
885 */
886 if (strstr(buf, "... deliverable: mailer "))
887 {
888 char *p=strstr(buf,", user ");
889 /* anything after ", user " is the email address */
890 debug(D_RCPT, "user: %s", p+7);
891 assassin->expandedrcpt.push_back(p+7);
892 }
893 }
894 fclose(p); p = NULL;
895 waitpid(pid, NULL, 0);
896 }
897 } else
898 {
899 assassin->expandedrcpt.push_back(envrcpt[0]);
900 }
901 debug(D_RCPT, "Total of %d actual recipients", (int)assassin->expandedrcpt.size());
902
903 if (assassin->numrcpt() == 0)
904 {
905 /* Send the envelope headers as X-Envelope-From: and
906 X-Envelope-To: so that SpamAssassin can use them in its
907 whitelist checks. Also forge as complete a dummy
908 Received: header as possible because SA gets a lot of
909 info from it.
910
911 HReceived: $?sfrom $s $.$?_($?s$|from $.$_)
912 $.$?{auth_type}(authenticated$?{auth_ssf} bits=${auth_ssf}$.)
913 $.by $j ($v/$Z)$?r with $r$. id $i$?{tls_version}
914 (version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u
915 for $u; $|;
916 $.$b$?g
917 (envelope-from $g)$.
918
919 */
920 const char *macro_b, *macro_i, *macro_j, *macro_r,
921 *macro_s, *macro_v, *macro_Z, *macro__;
922 char date[32];
923
924 /* RFC 822 date. */
925 macro_b = smfi_getsymval(ctx, const_cast<char *>("b"));
926 if (!macro_b)
927 {
928 time_t tval;
929 time(&tval);
930 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&tval));
931 macro_b = date;
932 warnmacro("b", "ENVRCPT");
933 }
934
935 /* queue ID */
936 macro_i = smfi_getsymval(ctx, const_cast<char *>("i"));
937 if (!macro_i)
938 {
939 macro_i = "unknown";
940 warnmacro("i", "ENVRCPT");
941 }
942
943 /* FQDN of this site */
944 macro_j = smfi_getsymval(ctx, const_cast<char *>("j"));
945 if (!macro_j)
946 {
947 macro_j = "localhost";
948 warnmacro("j", "ENVRCPT");
949 }
950
951 /* Protocol used to receive the message */
952 macro_r = smfi_getsymval(ctx, const_cast<char *>("r"));
953 if (!macro_r)
954 {
955 macro_r = "SMTP";
956 warnmacro("r", "ENVRCPT");
957 }
958
959 /* Sendmail currently cannot pass us the {s} macro, but
960 I do not know why. Leave this in for the day sendmail is
961 fixed. Until that day, use the value remembered by
962 mlfi_helo()
963 */
964 macro_s = smfi_getsymval(ctx, const_cast<char *>("s"));
965 if (!macro_s)
966 macro_s = sctx->helo;
967 if (!macro_s)
968 macro_s = "nohelo";
969
970 /* Sendmail binary version */
971 macro_v = smfi_getsymval(ctx, const_cast<char *>("v"));
972 if (!macro_v)
973 {
974 macro_v = "8.13.0";
975 warnmacro("v", "ENVRCPT");
976 }
977
978 /* Sendmail .cf version */
979 macro_Z = smfi_getsymval(ctx, const_cast<char *>("Z"));
980 if (!macro_Z)
981 {
982 macro_Z = "8.13.0";
983 warnmacro("Z", "ENVRCPT");
984 }
985
986 /* Validated sending site's address */
987 macro__ = smfi_getsymval(ctx, const_cast<char *>("_"));
988 if (!macro__)
989 {
990 macro__ = "unknown";
991 warnmacro("_", "ENVRCPT");
992 }
993
994 assassin->output((string)"X-Envelope-From: "+assassin->from()+"\r\n");
995 assassin->output((string)"X-Envelope-To: "+envrcpt[0]+"\r\n");
996
997 assassin->output((string)
998 "Received: from "+macro_s+" ("+macro__+")\r\n\t"+
999 "by "+macro_j+"("+macro_v+"/"+macro_Z+") with "+macro_r+" id "+macro_i+";\r\n\t"+
1000 macro_b+"\r\n\t"+
1001 "(envelope-from "+assassin->from()+")\r\n");
1002
1003 } else
1004 assassin->output((string)"X-Envelope-To: "+envrcpt[0]+"\r\n");
1005
1006 /* increment RCPT TO: count */
1007 assassin->set_numrcpt();
1008
1009 /* If we expanded to at least one user and we haven't recorded one yet,
1010 record the first one */
1011 if (!assassin->expandedrcpt.empty() && (assassin->rcpt().size() == 0))
1012 {
1013 debug(D_RCPT, "remembering %s for spamc", assassin->expandedrcpt.front().c_str());
1014 assassin->set_rcpt(assassin->expandedrcpt.front());
1015 }
1016
1017 debug(D_RCPT, "remembering recipient %s", envrcpt[0]);
1018 assassin->recipients.push_back( envrcpt[0] ); // XXX verify that this worked
1019
1020 debug(D_FUNC, "mlfi_envrcpt: exit");
1021
1022 return SMFIS_CONTINUE;
1023 }
1024
1025 //
1026 // Gets called repeatedly for all header fields
1027 //
1028 // assembles the headers and passes them on to the SpamAssassin client
1029 // through the pipe.
1030 //
1031 // only exception: SpamAssassin header fields (X-Spam-*) get suppressed
1032 // but are being stored in the SpamAssassin element.
1033 //
1034 // this function also starts the connection with the SPAMC program the
1035 // first time it is called.
1036 //
1037
1038 sfsistat
mlfi_header(SMFICTX * ctx,char * headerf,char * headerv)1039 mlfi_header(SMFICTX* ctx, char* headerf, char* headerv)
1040 {
1041 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1042 debug(D_FUNC, "mlfi_header: enter");
1043
1044 // Check if the SPAMC program has already been run, if not we run it.
1045 if ( !(assassin->connected) )
1046 {
1047 try {
1048 assassin->connected = 1; // SPAMC is getting ready to run
1049 assassin->Connect();
1050 }
1051 catch (string& problem) {
1052 throw_error(problem);
1053 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1054 delete assassin;
1055 debug(D_FUNC, "mlfi_header: exit error connect");
1056 return SMFIS_TEMPFAIL;
1057 };
1058 }
1059
1060 // Is it a "X-Spam-" header field?
1061 if ( cmp_nocase_partial("X-Spam-", headerf) == 0 )
1062 {
1063 int suppress = 1;
1064 // memorize content of old fields
1065
1066 if ( cmp_nocase_partial("X-Spam-Status", headerf) == 0 )
1067 assassin->set_spam_status(headerv);
1068 else if ( cmp_nocase_partial("X-Spam-Flag", headerf) == 0 )
1069 assassin->set_spam_flag(headerv);
1070 else if ( cmp_nocase_partial("X-Spam-Report", headerf) == 0 )
1071 assassin->set_spam_report(headerv);
1072 else if ( cmp_nocase_partial("X-Spam-Prev-Content-Type", headerf) == 0 )
1073 assassin->set_spam_prev_content_type(headerv);
1074 else if ( cmp_nocase_partial("X-Spam-Level", headerf) == 0 )
1075 assassin->set_spam_level(headerv);
1076 else if ( cmp_nocase_partial("X-Spam-Checker-Version", headerf) == 0 )
1077 assassin->set_spam_checker_version(headerv);
1078 else
1079 {
1080 /* Hm. X-Spam header, but not one we recognize. Pass it through. */
1081 suppress = 0;
1082 }
1083
1084 if (suppress)
1085 {
1086 debug(D_FUNC, "mlfi_header: suppress");
1087 return SMFIS_CONTINUE;
1088 }
1089 }
1090
1091 // Content-Type: will be stored if present
1092 if ( cmp_nocase_partial("Content-Type", headerf) == 0 )
1093 assassin->set_content_type(headerv);
1094
1095 // Subject: should be stored
1096 if ( cmp_nocase_partial("Subject", headerf) == 0 )
1097 assassin->set_subject(headerv);
1098
1099 // assemble header to be written to SpamAssassin
1100 string header = headerv;
1101
1102 // Replace all LF with CRLF
1103 // As milter documentation says:
1104 // headerv Header field value. The content of the header may
1105 // include folded white space, i.e., multiple lines with following
1106 // white space where lines are separated by LF (not CR/LF). The
1107 // trailing line terminator (CR/LF) is removed.
1108 // Need to make sure folded header line breaks are sent to SA as CRLF
1109 string::size_type idx = header.size();
1110 while ( (idx = header.rfind("\n", idx)) != string::npos )
1111 {
1112 header.replace(idx,1,"\r\n");
1113 }
1114
1115 // final assembly
1116 header = string(headerf) + ": " + header + "\r\n";
1117
1118 try {
1119 // write to SpamAssassin client
1120 assassin->output(header.c_str(),header.size());
1121 } catch (string& problem)
1122 {
1123 throw_error(problem);
1124 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1125 delete assassin;
1126 debug(D_FUNC, "mlfi_header: exit error output");
1127 return SMFIS_TEMPFAIL;
1128 };
1129
1130 // go on...
1131 debug(D_FUNC, "mlfi_header: exit");
1132
1133 return SMFIS_CONTINUE;
1134 }
1135
1136 //
1137 // Gets called once when the header is finished.
1138 //
1139 // writes empty line to SpamAssassin client to separate
1140 // headers from body.
1141 //
1142 sfsistat
mlfi_eoh(SMFICTX * ctx)1143 mlfi_eoh(SMFICTX* ctx)
1144 {
1145 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1146
1147 debug(D_FUNC, "mlfi_eoh: enter");
1148
1149 // Check if the SPAMC program has already been run, if not we run it.
1150 if ( !(assassin->connected) )
1151 {
1152 try {
1153 assassin->connected = 1; // SPAMC is getting ready to run
1154 assassin->Connect();
1155 }
1156 catch (string& problem) {
1157 throw_error(problem);
1158 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1159 delete assassin;
1160
1161 debug(D_FUNC, "mlfi_eoh: exit error connect");
1162 return SMFIS_TEMPFAIL;
1163 };
1164 }
1165
1166 try {
1167 // add blank line between header and body
1168 assassin->output("\r\n",2);
1169 } catch (string& problem)
1170 {
1171 throw_error(problem);
1172 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1173 delete assassin;
1174
1175 debug(D_FUNC, "mlfi_eoh: exit error output");
1176 return SMFIS_TEMPFAIL;
1177 };
1178
1179 // go on...
1180
1181 debug(D_FUNC, "mlfi_eoh: exit");
1182 return SMFIS_CONTINUE;
1183 }
1184
1185 //
1186 // Gets called repeatedly to transmit the body
1187 //
1188 // writes everything directly to SpamAssassin client
1189 //
1190 sfsistat
mlfi_body(SMFICTX * ctx,u_char * bodyp,size_t bodylen)1191 mlfi_body(SMFICTX* ctx, u_char *bodyp, size_t bodylen)
1192 {
1193 debug(D_FUNC, "mlfi_body: enter");
1194 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1195
1196
1197 try {
1198 assassin->output(bodyp, bodylen);
1199 } catch (string& problem)
1200 {
1201 throw_error(problem);
1202 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1203 delete assassin;
1204 debug(D_FUNC, "mlfi_body: exit error");
1205 return SMFIS_TEMPFAIL;
1206 };
1207
1208 // go on...
1209 debug(D_FUNC, "mlfi_body: exit");
1210 return SMFIS_CONTINUE;
1211 }
1212
1213 //
1214 // Gets called once at the end of mail processing
1215 //
1216 // tells SpamAssassin client that the mail is complete
1217 // through EOF and then modifies the mail accordingly by
1218 // calling the "assassinate" function
1219 //
1220 sfsistat
mlfi_eom(SMFICTX * ctx)1221 mlfi_eom(SMFICTX* ctx)
1222 {
1223 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1224 int milter_status;
1225
1226 debug(D_FUNC, "mlfi_eom: enter");
1227 try {
1228
1229 // close output pipe to signal EOF to SpamAssassin
1230 assassin->close_output();
1231
1232 // read what the Assassin is telling us
1233 assassin->input();
1234
1235 milter_status = assassinate(ctx, assassin);
1236
1237 // now cleanup the element.
1238 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1239 delete assassin;
1240
1241 } catch (string& problem)
1242 {
1243 throw_error(problem);
1244 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1245 delete assassin;
1246 debug(D_FUNC, "mlfi_eom: exit error");
1247 return SMFIS_TEMPFAIL;
1248 };
1249
1250 // go on...
1251 debug(D_FUNC, "mlfi_eom: exit");
1252 return milter_status;
1253 }
1254
1255 //
1256 // Gets called on session-basis. This keeps things nice & quiet.
1257 //
1258 sfsistat
mlfi_close(SMFICTX * ctx)1259 mlfi_close(SMFICTX* ctx)
1260 {
1261 struct context *sctx;
1262 debug(D_FUNC, "mlfi_close");
1263
1264 sctx = (struct context*)smfi_getpriv(ctx);
1265 if (sctx == NULL)
1266 return SMFIS_ACCEPT;
1267
1268 if (sctx->helo)
1269 free(sctx->helo);
1270 free(sctx);
1271 smfi_setpriv(ctx, NULL);
1272
1273 return SMFIS_ACCEPT;
1274 }
1275
1276 //
1277 // Gets called when things are being aborted.
1278 //
1279 // kills the SpamAssassin object, its destructor should
1280 // take care of everything.
1281 //
1282 sfsistat
mlfi_abort(SMFICTX * ctx)1283 mlfi_abort(SMFICTX* ctx)
1284 {
1285 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1286
1287 debug(D_FUNC, "mlfi_abort");
1288 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1289 delete assassin;
1290
1291 return SMFIS_ACCEPT;
1292 }
1293
1294 // }}}
1295
1296 // {{{ SpamAssassin Class
1297
1298 //
1299 // This is a new constructor for the SpamAssassin object. It simply
1300 // initializes two variables. The original constructor has been
1301 // renamed to Connect().
1302 //
SpamAssassin()1303 SpamAssassin::SpamAssassin():
1304 error(false),
1305 running(false),
1306 connected(false),
1307 _numrcpt(0)
1308 {
1309 }
1310
1311
~SpamAssassin()1312 SpamAssassin::~SpamAssassin()
1313 {
1314 if (connected)
1315 {
1316 // close all pipes that are still open
1317 if (pipe_io[0][0] > -1) close(pipe_io[0][0]);
1318 if (pipe_io[0][1] > -1) close(pipe_io[0][1]);
1319 if (pipe_io[1][0] > -1) close(pipe_io[1][0]);
1320 if (pipe_io[1][1] > -1) close(pipe_io[1][1]);
1321
1322 // child still running?
1323 if (running)
1324 {
1325 // make sure the pid is valid
1326 if (pid > 0) {
1327 // slaughter child
1328 kill(pid, SIGKILL);
1329
1330 // wait for child to terminate
1331 int status;
1332 waitpid(pid, &status, 0);
1333 }
1334 }
1335 }
1336
1337 // Clean up the recip list. Might be overkill, but it's good housekeeping.
1338 while( !recipients.empty())
1339 {
1340 recipients.pop_front();
1341 }
1342 // Clean up the recip list. Might be overkill, but it's good housekeeping.
1343 while( !expandedrcpt.empty())
1344 {
1345 expandedrcpt.pop_front();
1346 }
1347 }
1348
1349 //
1350 // This is the old SpamAssassin constructor. It has been renamed Connect(),
1351 // and is now called at the beginning of the mlfi_header() function.
1352 //
1353
Connect()1354 void SpamAssassin::Connect()
1355 {
1356 // set up pipes for in- and output
1357 if (pipe(pipe_io[0]))
1358 throw string(string("pipe error: ")+string(strerror(errno)));
1359 if (pipe(pipe_io[1]))
1360 throw string(string("pipe error: ")+string(strerror(errno)));
1361
1362 // now execute SpamAssassin client for contact with SpamAssassin spamd
1363
1364 // start child process
1365 switch(pid = fork())
1366 {
1367 case -1:
1368 // forking trouble. throw error.
1369 throw string(string("fork error: ")+string(strerror(errno)));
1370 break;
1371 case 0:
1372 // +++ CHILD +++
1373
1374 // close unused pipes
1375 close(pipe_io[1][0]);
1376 close(pipe_io[0][1]);
1377
1378 // redirect stdin(0), stdout(1) and stderr(2)
1379 dup2(pipe_io[0][0],0);
1380 dup2(pipe_io[1][1],1);
1381 dup2(pipe_io[1][1],2);
1382
1383 closeall(3);
1384
1385 // execute spamc
1386 // absolute path (determined in autoconf)
1387 // should be a little more secure
1388 // XXX arbitrary 100-argument max
1389 int argc = 0;
1390 char** argv = (char**) malloc(100*sizeof(char*));
1391 argv[argc++] = strdup(SPAMC);
1392 if (flag_sniffuser)
1393 {
1394 argv[argc++] = strdup("-u");
1395 if ( expandedrcpt.size() != 1 )
1396 {
1397 // More (or less?) than one recipient, so we pass the default
1398 // username to SPAMC. This way special rules can be defined for
1399 // multi recipient messages.
1400 debug(D_RCPT, "%d recipients; spamc gets default username %s", (int)expandedrcpt.size(), defaultuser);
1401 argv[argc++] = defaultuser;
1402 } else
1403 {
1404 // There is only 1 recipient so we pass the username
1405 // (converted to lowercase) to SPAMC. Don't worry about
1406 // freeing this memory as we're exec()ing anyhow.
1407 if (flag_full_email)
1408 argv[argc] = strlwr(strdup(full_user().c_str()));
1409 else
1410 argv[argc] = strlwr(strdup(local_user().c_str()));
1411
1412 debug(D_RCPT, "spamc gets %s", argv[argc]);
1413
1414 argc++;
1415 }
1416 }
1417 if (spamdhost)
1418 {
1419 argv[argc++] = strdup("-d");
1420 argv[argc++] = spamdhost;
1421 }
1422 if (spamc_argc)
1423 {
1424 memcpy(argv+argc, spamc_argv, spamc_argc * sizeof(char *));
1425 argc += spamc_argc;
1426 }
1427 argv[argc++] = 0;
1428
1429 execvp(argv[0] , argv); // does not return!
1430
1431 // execution failed
1432 throw_error(string("execution error: ")+string(strerror(errno)));
1433 _exit(1);
1434 break;
1435 }
1436
1437 // +++ PARENT +++
1438
1439 // close unused pipes
1440 close(pipe_io[0][0]);
1441 close(pipe_io[1][1]);
1442 pipe_io[0][0]=-1;
1443 pipe_io[1][1]=-1;
1444
1445 // mark the pipes non-blocking
1446 if(fcntl(pipe_io[0][1], F_SETFL, O_NONBLOCK) == -1)
1447 throw string(string("Cannot set pipe01 nonblocking: ")+string(strerror(errno)));
1448 #if 0 /* don't really need to make the sink pipe nonblocking */
1449 if(fcntl(pipe_io[1][0], F_SETFL, O_NONBLOCK) == -1)
1450 throw string(string("Cannot set pipe10 nonblocking: ")+string(strerror(errno)));
1451 #endif
1452
1453 // we have to assume the client is running now.
1454 running=true;
1455
1456 /* If we have any buffered output, write it now. */
1457 if (outputbuffer.size())
1458 {
1459 output(outputbuffer);
1460 outputbuffer="";
1461 }
1462 }
1463
1464 // write to SpamAssassin
1465 void
output(const void * buffer,long size)1466 SpamAssassin::output(const void* buffer, long size)
1467 {
1468 debug(D_FUNC, "::output enter");
1469
1470 debug(D_SPAMC, "output \"%*.*s\"", (int)size, (int)size, (char *)buffer);
1471
1472 // if there are problems, fail.
1473 if (error)
1474 throw string("tried output despite problems. failed.");
1475
1476 /* If we haven't launched spamc yet, just store the data */
1477 if (!connected)
1478 {
1479 /* Silly C++ can't tell the difference between
1480 (const char*, string::size_type) and
1481 (string::size_type, char), so we have to cast the parameters.
1482 */
1483 outputbuffer.append((const char *)buffer,(string::size_type)size);
1484 debug(D_FUNC, "::output exit1");
1485 return;
1486 }
1487
1488 // send to SpamAssassin
1489 long total = 0;
1490 long wsize = 0;
1491 string reason;
1492 int status;
1493 do {
1494 struct pollfd fds[2];
1495 int nfds = 2, nready;
1496 fds[0].fd = pipe_io[0][1];
1497 fds[0].events = POLLOUT;
1498 fds[1].fd = pipe_io[1][0];
1499 fds[1].events = POLLIN;
1500
1501 debug(D_POLL, "polling fds %d and %d", pipe_io[0][1], pipe_io[1][0]);
1502 nready = poll(fds, nfds, 1000);
1503 if (nready == -1)
1504 throw("poll failed");
1505
1506 debug(D_POLL, "poll returned %d, fd0=%d, fd1=%d", nready, fds[0].revents, fds[1].revents);
1507
1508 if (fds[1].revents & (POLLERR|POLLNVAL|POLLHUP))
1509 {
1510 throw string("poll says my read pipe is busted");
1511 }
1512
1513 if (fds[0].revents & (POLLERR|POLLNVAL|POLLHUP))
1514 {
1515 throw string("poll says my write pipe is busted");
1516 }
1517
1518 if (fds[1].revents & POLLIN)
1519 {
1520 debug(D_POLL, "poll says I can read");
1521 read_pipe();
1522 }
1523
1524 if (fds[0].revents & POLLOUT)
1525 {
1526 debug(D_POLL, "poll says I can write");
1527 switch(wsize = write(pipe_io[0][1], (char *)buffer + total, size - total))
1528 {
1529 case -1:
1530 if (errno == EAGAIN)
1531 continue;
1532 reason = string(strerror(errno));
1533
1534 // close the pipes
1535 close(pipe_io[0][1]);
1536 close(pipe_io[1][0]);
1537 pipe_io[0][1]=-1;
1538 pipe_io[1][0]=-1;
1539
1540 // Slaughter child
1541 kill(pid, SIGKILL);
1542
1543 // set flags
1544 error = true;
1545 running = false;
1546
1547 // wait until child is dead
1548 waitpid(pid, &status, 0);
1549
1550 throw string(string("write error: ")+reason);
1551 break;
1552 default:
1553 total += wsize;
1554 debug(D_POLL, "wrote %ld bytes", wsize);
1555 break;
1556 }
1557 }
1558 } while ( total < size );
1559
1560 debug(D_FUNC, "::output exit2");
1561 }
1562
output(const void * buffer)1563 void SpamAssassin::output(const void* buffer)
1564 {
1565 output(buffer, strlen((const char *)buffer));
1566 }
1567
output(string buffer)1568 void SpamAssassin::output(string buffer)
1569 {
1570 output(buffer.c_str(), buffer.size());
1571 }
1572
1573 // close output pipe
1574 void
close_output()1575 SpamAssassin::close_output()
1576 {
1577 if(close(pipe_io[0][1]))
1578 throw string(string("close error: ")+string(strerror(errno)));
1579 pipe_io[0][1]=-1;
1580 }
1581
1582 void
input()1583 SpamAssassin::input()
1584 {
1585 debug(D_FUNC, "::input enter");
1586 // if the child has exited or we experienced an error, return
1587 // immediately.
1588 if (!running || error)
1589 {
1590 debug(D_FUNC, "::input exit1");
1591 return;
1592 }
1593
1594 // keep reading from input pipe until it is empty
1595 empty_and_close_pipe();
1596
1597 // that's it, we're through
1598 running = false;
1599
1600 // wait until child is dead
1601 int status;
1602 if(waitpid(pid, &status, 0)<0)
1603 {
1604 error = true;
1605 throw string(string("waitpid error: ")+string(strerror(errno)));
1606 };
1607 debug(D_FUNC, "::input exit2");
1608 }
1609
1610 //
1611 // return reference to mail
1612 //
1613 string&
d()1614 SpamAssassin::d()
1615 {
1616 return mail;
1617 }
1618
1619 //
1620 // get values of the different SpamAssassin fields
1621 //
1622 string&
spam_status()1623 SpamAssassin::spam_status()
1624 {
1625 return x_spam_status;
1626 }
1627
1628 string&
spam_flag()1629 SpamAssassin::spam_flag()
1630 {
1631 return x_spam_flag;
1632 }
1633
1634 string&
spam_report()1635 SpamAssassin::spam_report()
1636 {
1637 return x_spam_report;
1638 }
1639
1640 string&
spam_prev_content_type()1641 SpamAssassin::spam_prev_content_type()
1642 {
1643 return x_spam_prev_content_type;
1644 }
1645
1646 string&
spam_checker_version()1647 SpamAssassin::spam_checker_version()
1648 {
1649 return x_spam_checker_version;
1650 }
1651
1652 string&
spam_level()1653 SpamAssassin::spam_level()
1654 {
1655 return x_spam_level;
1656 }
1657
1658 string&
content_type()1659 SpamAssassin::content_type()
1660 {
1661 return _content_type;
1662 }
1663
1664 string&
subject()1665 SpamAssassin::subject()
1666 {
1667 return _subject;
1668 }
1669
1670 string&
rcpt()1671 SpamAssassin::rcpt()
1672 {
1673 return _rcpt;
1674 }
1675
1676 string&
from()1677 SpamAssassin::from()
1678 {
1679 return _from;
1680 }
1681
1682 string&
connectip()1683 SpamAssassin::connectip()
1684 {
1685 return _connectip;
1686 }
1687
1688
1689 string
local_user()1690 SpamAssassin::local_user()
1691 {
1692 // assuming we have a recipient in the form: <username@somehost.somedomain>
1693 // (angle brackets optional) we return 'username'
1694 if (_rcpt[0] == '<')
1695 return _rcpt.substr(1, _rcpt.find_first_of("@+")-1);
1696 else
1697 return _rcpt.substr(0, _rcpt.find_first_of("@+"));
1698 }
1699
1700 string
full_user()1701 SpamAssassin::full_user()
1702 {
1703 string name;
1704 // assuming we have a recipient in the form: <username@somehost.somedomain>
1705 // (angle brackets optional) we return 'username@somehost.somedomain'
1706 if (_rcpt[0] == '<')
1707 name = _rcpt.substr(1, _rcpt.find('>')-1);
1708 else
1709 name = _rcpt;
1710 if(name.find('@') == string::npos)
1711 {
1712 /* if the name had no domain part (local delivery), append the default one */
1713 name = name + "@" + defaultdomain;
1714 }
1715 return name;
1716 }
1717
1718 int
numrcpt()1719 SpamAssassin::numrcpt()
1720 {
1721 return _numrcpt;
1722 }
1723
1724 int
set_numrcpt()1725 SpamAssassin::set_numrcpt()
1726 {
1727 _numrcpt++;
1728 return _numrcpt;
1729 }
1730
1731 int
set_numrcpt(const int val)1732 SpamAssassin::set_numrcpt(const int val)
1733 {
1734 _numrcpt = val;
1735 return _numrcpt;
1736 }
1737
1738 //
1739 // set the values of the different SpamAssassin
1740 // fields in our element. Returns former size of field
1741 //
1742 string::size_type
set_spam_status(const string & val)1743 SpamAssassin::set_spam_status(const string& val)
1744 {
1745 string::size_type old = x_spam_status.size();
1746 x_spam_status = val;
1747 return (old);
1748 }
1749
1750 string::size_type
set_spam_flag(const string & val)1751 SpamAssassin::set_spam_flag(const string& val)
1752 {
1753 string::size_type old = x_spam_flag.size();
1754 x_spam_flag = val;
1755 return (old);
1756 }
1757
1758 string::size_type
set_spam_report(const string & val)1759 SpamAssassin::set_spam_report(const string& val)
1760 {
1761 string::size_type old = x_spam_report.size();
1762 x_spam_report = val;
1763 return (old);
1764 }
1765
1766 string::size_type
set_spam_prev_content_type(const string & val)1767 SpamAssassin::set_spam_prev_content_type(const string& val)
1768 {
1769 string::size_type old = x_spam_prev_content_type.size();
1770 x_spam_prev_content_type = val;
1771 return (old);
1772 }
1773
1774 string::size_type
set_spam_checker_version(const string & val)1775 SpamAssassin::set_spam_checker_version(const string& val)
1776 {
1777 string::size_type old = x_spam_checker_version.size();
1778 x_spam_checker_version = val;
1779 return (old);
1780 }
1781
1782 string::size_type
set_spam_level(const string & val)1783 SpamAssassin::set_spam_level(const string& val)
1784 {
1785 string::size_type old = x_spam_level.size();
1786 x_spam_level = val;
1787 return (old);
1788 }
1789
1790 string::size_type
set_content_type(const string & val)1791 SpamAssassin::set_content_type(const string& val)
1792 {
1793 string::size_type old = _content_type.size();
1794 _content_type = val;
1795 return (old);
1796 }
1797
1798 string::size_type
set_subject(const string & val)1799 SpamAssassin::set_subject(const string& val)
1800 {
1801 string::size_type old = _subject.size();
1802 _subject = val;
1803 return (old);
1804 }
1805
1806 string::size_type
set_rcpt(const string & val)1807 SpamAssassin::set_rcpt(const string& val)
1808 {
1809 string::size_type old = _rcpt.size();
1810 _rcpt = val;
1811 return (old);
1812 }
1813
1814 string::size_type
set_from(const string & val)1815 SpamAssassin::set_from(const string& val)
1816 {
1817 string::size_type old = _from.size();
1818 _from = val;
1819 return (old);
1820 }
1821
1822 string::size_type
set_connectip(const string & val)1823 SpamAssassin::set_connectip(const string& val)
1824 {
1825 string::size_type old = _connectip.size();
1826 _connectip = val;
1827 return (old);
1828 }
1829
1830 //
1831 // Read available output from SpamAssassin client
1832 //
1833 int
read_pipe()1834 SpamAssassin::read_pipe()
1835 {
1836 long size;
1837 int status;
1838 char iobuff[1024];
1839 string reason;
1840
1841 debug(D_FUNC, "::read_pipe enter");
1842
1843 if (pipe_io[1][0] == -1)
1844 {
1845 debug(D_FUNC, "::read_pipe exit - shouldn't have been called?");
1846 return 0;
1847 }
1848
1849 size = read(pipe_io[1][0], iobuff, 1024);
1850
1851 if (size < 0)
1852 {
1853 // Error.
1854 reason = string(strerror(errno));
1855
1856 // Close remaining pipe.
1857 close(pipe_io[1][0]);
1858 pipe_io[1][0] = -1;
1859
1860 // Slaughter child
1861 kill(pid, SIGKILL);
1862
1863 // set flags
1864 error = true;
1865 running = false;
1866
1867 // wait until child is dead
1868 waitpid(pid, &status, 0);
1869
1870 // throw the error message that caused this trouble
1871 throw string(string("read error: ")+reason);
1872 } else if ( size == 0 )
1873 {
1874
1875 // EOF. Close the pipe
1876 if(close(pipe_io[1][0]))
1877 throw string(string("close error: ")+string(strerror(errno)));
1878 pipe_io[1][0] = -1;
1879
1880 } else
1881 {
1882 // append to mail buffer
1883 mail.append(iobuff, size);
1884 debug(D_POLL, "read %ld bytes", size);
1885 debug(D_SPAMC, "input \"%*.*s\"", (int)size, (int)size, iobuff);
1886 }
1887 debug(D_FUNC, "::read_pipe exit");
1888 return size;
1889 }
1890
1891 //
1892 // Read all output from SpamAssassin client
1893 // and close the pipe
1894 //
1895 void
empty_and_close_pipe()1896 SpamAssassin::empty_and_close_pipe()
1897 {
1898 debug(D_FUNC, "::empty_and_close_pipe enter");
1899 while (read_pipe())
1900 ;
1901 debug(D_FUNC, "::empty_and_close_pipe exit");
1902 }
1903
1904 // }}}
1905
1906 // {{{ Some small subroutines without much relation to functionality
1907
1908 // output error message to syslog facility
1909 void
throw_error(const string & errmsg)1910 throw_error(const string& errmsg)
1911 {
1912 if (errmsg.c_str())
1913 syslog(LOG_ERR, "Thrown error: %s", errmsg.c_str());
1914 else
1915 syslog(LOG_ERR, "Unknown error");
1916 }
1917
1918 /* Takes a comma or space-delimited string of debug tokens and sets the
1919 appropriate bits in flag_debug. "all" sets all the bits.
1920 */
parse_debuglevel(char * string)1921 void parse_debuglevel(char* string)
1922 {
1923 char *token;
1924
1925 /* make a copy so we don't overwrite argv[] */
1926 string = strdup(string);
1927
1928 /* handle the old numeric values too */
1929 switch(atoi(string))
1930 {
1931 case 3:
1932 flag_debug |= (1<<D_UORI) | (1<<D_STR);
1933 case 2:
1934 flag_debug |= (1<<D_POLL);
1935 case 1:
1936 flag_debug |= (1<<D_MISC) | (1<<D_FUNC);
1937 debug(D_ALWAYS, "Setting debug level to 0x%0x", flag_debug);
1938 free(string);
1939 return;
1940 default:
1941 break;
1942 }
1943
1944 while ((token = strsep(&string, ", ")))
1945 {
1946 int i;
1947 for (i=0; debugstrings[i]; i++)
1948 {
1949 if(strcasecmp(token, "ALL")==0)
1950 {
1951 flag_debug = (1<<D_MAX)-1;
1952 break;
1953 }
1954 if(strcasecmp(token, debugstrings[i])==0)
1955 {
1956 flag_debug |= (1<<i);
1957 break;
1958 }
1959 }
1960
1961 if (!debugstrings[i])
1962 {
1963 fprintf(stderr, "Invalid debug token \"%s\"\n", token);
1964 exit(1);
1965 }
1966 }
1967 debug(D_ALWAYS, "Setting debug level to 0x%0x", flag_debug);
1968 free(string);
1969 }
1970
1971 /*
1972 Output a line to syslog using print format, but only if the appropriate
1973 debug level is set. The D_ALWAYS level is always enabled.
1974 */
debug(enum debuglevel level,const char * fmt,...)1975 void debug(enum debuglevel level, const char* fmt, ...)
1976 {
1977 if ((1<<level) & flag_debug)
1978 {
1979 #if defined(HAVE_VSYSLOG)
1980 va_list vl;
1981 va_start(vl, fmt);
1982 vsyslog(LOG_ERR, fmt, vl);
1983 va_end(vl);
1984 #else
1985 #if defined(HAVE_VASPRINTF)
1986 char *buf;
1987 #else
1988 char buf[1024];
1989 #endif
1990 va_list vl;
1991 va_start(vl, fmt);
1992 #if defined(HAVE_VASPRINTF)
1993 vasprintf(&buf, fmt, vl);
1994 #else
1995 #if defined(HAVE_VSNPRINTF)
1996 vsnprintf(buf, sizeof(buf)-1, fmt, vl);
1997 #else
1998 /* XXX possible buffer overflow here; be careful what you pass to debug() */
1999 vsprintf(buf, fmt, vl);
2000 #endif
2001 #endif
2002 va_end(vl);
2003 syslog(LOG_ERR, "%s", buf);
2004 #if defined(HAVE_VASPRINTF)
2005 free(buf);
2006 #endif
2007 #endif /* vsyslog */
2008 }
2009 }
2010
2011 // case-insensitive search
2012 string::size_type
find_nocase(const string & array,const string & pattern,string::size_type start)2013 find_nocase(const string& array, const string& pattern, string::size_type start)
2014 {
2015 string::size_type pos(start);
2016
2017 while (pos < array.size())
2018 {
2019 string::size_type ctr = 0;
2020
2021 while( (pos+ctr) < array.size() &&
2022 toupper(array[pos+ctr]) == toupper(pattern[ctr]) )
2023 {
2024 ++ctr;
2025 if (ctr == pattern.size())
2026 {
2027 debug(D_STR, "f_nc: <%s><%s>: hit", array.c_str(), pattern.c_str());
2028 return pos;
2029 }
2030 };
2031
2032 ++pos;
2033 };
2034
2035 debug(D_STR, "f_nc: <%s><%s>: nohit", array.c_str(), pattern.c_str());
2036 return string::npos;
2037 }
2038
2039 // compare case-insensitive
2040 int
cmp_nocase_partial(const string & s,const string & s2)2041 cmp_nocase_partial(const string& s, const string& s2)
2042 {
2043 string::const_iterator p=s.begin();
2044 string::const_iterator p2=s2.begin();
2045
2046 while ( p != s.end() && p2 <= s2.end() ) {
2047 if (toupper(*p) != toupper(*p2))
2048 {
2049 debug(D_STR, "c_nc_p: <%s><%s> : miss", s.c_str(), s2.c_str());
2050 return (toupper(*p) < toupper(*p2)) ? -1 : 1;
2051 }
2052 ++p;
2053 ++p2;
2054 };
2055
2056 debug(D_STR, "c_nc_p: <%s><%s> : hit", s.c_str(), s2.c_str());
2057 return 0;
2058
2059 }
2060
2061 /* closeall() - close all FDs >= a specified value */
closeall(int fd)2062 void closeall(int fd)
2063 {
2064 int fdlimit = sysconf(_SC_OPEN_MAX);
2065 while (fd < fdlimit)
2066 close(fd++);
2067 }
2068
parse_networklist(char * string,struct networklist * list)2069 void parse_networklist(char *string, struct networklist *list)
2070 {
2071 char *token;
2072
2073 /* make a copy so we don't overwrite argv[] */
2074 string = strdup(string);
2075
2076 while ((token = strsep(&string, ", ")))
2077 {
2078 char *tnet = strsep(&token, "/");
2079 char *tmask = token;
2080 struct in_addr net;
2081 struct in6_addr net6;
2082
2083 if (list->num_nets % 10 == 0)
2084 list->nets = (union net*)realloc(list->nets, sizeof(*list->nets) * (list->num_nets + 10));
2085
2086 if (inet_pton(AF_INET, tnet, &net))
2087 {
2088 struct in_addr mask;
2089
2090 if (tmask)
2091 {
2092 if (strchr(tmask, '.') == NULL)
2093 {
2094 /* CIDR */
2095 unsigned int bits;
2096 int ret;
2097 ret = sscanf(tmask, "%u", &bits);
2098 if (ret != 1 || bits > 32)
2099 {
2100 fprintf(stderr,"%s: bad CIDR value", tmask);
2101 exit(1);
2102 }
2103 mask.s_addr = htonl(~((1L << (32 - bits)) - 1) & 0xffffffff);
2104 } else if (!inet_pton(AF_INET6, tmask, &mask))
2105 {
2106 fprintf(stderr, "Could not parse \"%s\" as a netmask\n", tmask);
2107 exit(1);
2108 }
2109 } else
2110 mask.s_addr = 0xffffffff;
2111
2112 {
2113 char *snet = strdup(inet_ntoa(net));
2114 debug(D_MISC, "Adding %s/%s to network list", snet, inet_ntoa(mask));
2115 free(snet);
2116 }
2117
2118 net.s_addr = net.s_addr & mask.s_addr;
2119 list->nets[list->num_nets].net4.af = AF_INET;
2120 list->nets[list->num_nets].net4.network = net;
2121 list->nets[list->num_nets].net4.netmask = mask;
2122 list->num_nets++;
2123 } else if (inet_pton(AF_INET6, tnet, &net6))
2124 {
2125 int mask;
2126
2127 if (tmask)
2128 {
2129 if (sscanf(tmask, "%d", &mask) != 1 || mask > 128)
2130 {
2131 fprintf(stderr,"%s: bad CIDR value", tmask);
2132 exit(1);
2133 }
2134 } else
2135 mask = 128;
2136
2137 list->nets[list->num_nets].net6.af = AF_INET6;
2138 list->nets[list->num_nets].net6.network = net6;
2139 list->nets[list->num_nets].net6.netmask = mask;
2140 list->num_nets++;
2141 } else
2142 {
2143 fprintf(stderr, "Could not parse \"%s\" as a network\n", tnet);
2144 exit(1);
2145 }
2146
2147 }
2148 free(string);
2149 }
2150
ip_in_networklist(struct sockaddr * addr,struct networklist * list)2151 int ip_in_networklist(struct sockaddr *addr, struct networklist *list)
2152 {
2153 int i;
2154
2155 if (list->num_nets == 0)
2156 return 0;
2157
2158 //debug(D_NET, "Checking %s against:", inet_ntoa(ip));
2159 for (i = 0; i < list->num_nets; i++)
2160 {
2161 if (list->nets[i].net.af == AF_INET && addr->sa_family == AF_INET)
2162 {
2163 struct in_addr ip = ((struct sockaddr_in *)addr)->sin_addr;
2164
2165 debug(D_NET, "%s", inet_ntoa(list->nets[i].net4.network));
2166 debug(D_NET, "/%s", inet_ntoa(list->nets[i].net4.netmask));
2167 if ((ip.s_addr & list->nets[i].net4.netmask.s_addr) == list->nets[i].net4.network.s_addr)
2168 {
2169 debug(D_NET, "Hit!");
2170 return 1;
2171 }
2172 } else if (list->nets[i].net.af == AF_INET6 && addr->sa_family == AF_INET6)
2173 {
2174 u_int8_t *ip = ((struct sockaddr_in6 *)addr)->sin6_addr.s6_addr;
2175 int mask, j;
2176
2177 mask = list->nets[i].net6.netmask;
2178 for (j = 0; j < 16 && mask > 0; j++, mask -= 8)
2179 {
2180 unsigned char bytemask;
2181
2182 bytemask = (mask < 8) ? ~((1L << (8 - mask)) - 1) : 0xff;
2183
2184 if ((ip[j] & bytemask) != (list->nets[i].net6.network.s6_addr[j] & bytemask))
2185 break;
2186 }
2187
2188 if (mask <= 0)
2189 {
2190 debug(D_NET, "Hit!");
2191 return 1;
2192 }
2193 }
2194 }
2195
2196 return 0;
2197 }
2198
strlwr(char * str)2199 char *strlwr(char *str)
2200 {
2201 char *s = str;
2202 while (*s)
2203 {
2204 *s = tolower(*s);
2205 s++;
2206 }
2207 return str;
2208 }
2209
2210 /* Log a message about missing milter macros, but only the first time */
warnmacro(const char * macro,const char * scope)2211 void warnmacro(const char *macro, const char *scope)
2212 {
2213 if (warnedmacro)
2214 return;
2215 debug(D_ALWAYS, "Could not retrieve sendmail macro \"%s\"!. Please add it to confMILTER_MACROS_%s for better spamassassin results",
2216 macro, scope);
2217 warnedmacro = true;
2218 }
2219
2220 /*
2221 untrusted-argument-safe popen function - only supports "r" and "w" modes
2222 for simplicity, and always reads stdout and stderr in "r" mode. Call
2223 fclose to close the FILE, and waitpid to reap the child process (pid).
2224 */
popenv(char * const argv[],const char * type,pid_t * pid)2225 FILE *popenv(char *const argv[], const char *type, pid_t *pid)
2226 {
2227 FILE *iop;
2228 int pdes[2];
2229 int save_errno;
2230
2231 if ((*type != 'r' && *type != 'w') || type[1])
2232 {
2233 errno = EINVAL;
2234 return (NULL);
2235 }
2236 if (pipe(pdes) < 0)
2237 return (NULL);
2238 switch (*pid = fork()) {
2239
2240 case -1: /* Error. */
2241 save_errno = errno;
2242 (void)close(pdes[0]);
2243 (void)close(pdes[1]);
2244 errno = save_errno;
2245 return (NULL);
2246 /* NOTREACHED */
2247 case 0: /* Child. */
2248 if (*type == 'r') {
2249 /*
2250 * The dup2() to STDIN_FILENO is repeated to avoid
2251 * writing to pdes[1], which might corrupt the
2252 * parent's copy. This isn't good enough in
2253 * general, since the exit() is no return, so
2254 * the compiler is free to corrupt all the local
2255 * variables.
2256 */
2257 (void)close(pdes[0]);
2258 (void)dup2(pdes[1], STDOUT_FILENO);
2259 (void)dup2(pdes[1], STDERR_FILENO);
2260 if (pdes[1] != STDOUT_FILENO && pdes[1] != STDERR_FILENO) {
2261 (void)close(pdes[1]);
2262 }
2263 } else {
2264 if (pdes[0] != STDIN_FILENO) {
2265 (void)dup2(pdes[0], STDIN_FILENO);
2266 (void)close(pdes[0]);
2267 }
2268 (void)close(pdes[1]);
2269 }
2270 execv(argv[0], argv);
2271 exit(127);
2272 /* NOTREACHED */
2273 }
2274
2275 /* Parent; assume fdopen can't fail. */
2276 if (*type == 'r') {
2277 iop = fdopen(pdes[0], type);
2278 (void)close(pdes[1]);
2279 } else {
2280 iop = fdopen(pdes[1], type);
2281 (void)close(pdes[0]);
2282 }
2283
2284 return (iop);
2285 }
2286
2287 // }}}
2288 // vim6:ai:noexpandtab
2289