3 // $Id: spamass-milter.cpp,v 1.100 2014/08/15 02:46:50 kovert Exp $
6 // - a rather trivial SpamAssassin Sendmail Milter plugin
8 // for information about SpamAssassin please see
9 // http://www.spamassassin.org
11 // for information about Sendmail please see
12 // http://www.sendmail.org
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
19 // {{{ License, Contact, Notes & Includes
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.
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.
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
36 // Michael Brown <michaelb@opentext.com>
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
45 // The code should be reasonably thread-safe. No guarantees, though.
47 // This program roughly does the following steps:
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
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)
69 #include <arpa/inet.h>
70 #include <sys/types.h>
73 #include <netinet/in.h>
88 #include "subst_poll.h"
104 #include "libmilter/mfapi.h"
105 //#include "libmilter/mfdef.h"
107 #if !HAVE_DECL_STRSEP
108 char *strsep(char **stringp, const char *delim);
111 #if !HAVE_DECL_DAEMON
112 int daemon(int nochdir, int noclose);
119 #include "spamass-milter.h"
125 #ifndef INADDR_LOOPBACK
126 #define INADDR_LOOPBACK 0x7F000001
131 static const char Id[] = "$Id: spamass-milter.cpp,v 1.100 2014/08/15 02:46:50 kovert Exp $";
133 static char FilterName[] = "SpamAssassin";
135 struct smfiDesc smfilter =
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
152 const char *const debugstrings[] = {
153 "ALL", "FUNC", "POLL", "UORI", "STR", "MISC", "NET", "SPAMC", "RCPT",
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;
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;
173 bool flag_bucket = false;
174 bool flag_bucket_only = false;
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 */
184 main(int argc, char* argv[])
187 const char *args = "afd:mMp:P:r:u:D:i:b:B:e:xS:R:C:";
190 char *pidfilename = NULL;
191 FILE *pidfile = NULL;
193 #ifdef HAVE_VERBOSE_TERMINATE_HANDLER
194 std::set_terminate (__gnu_cxx::__verbose_terminate_handler);
197 openlog("spamass-milter", LOG_PID, LOG_MAIL);
200 /* Process command line options */
201 while ((c = getopt(argc, argv, args)) != -1) {
210 parse_debuglevel(optarg);
213 spamdhost = strdup(optarg);
216 flag_full_email = true;
217 defaultdomain = strdup(optarg);
220 debug(D_MISC, "Parsing ignore list");
221 parse_networklist(optarg, &ignorenets);
224 dontmodifyspam = true;
225 smfilter.xxfi_flags &= ~SMFIF_CHGBODY;
229 dontmodifyspam = true;
230 smfilter.xxfi_flags &= ~(SMFIF_CHGBODY|SMFIF_CHGHDRS);
233 sock = strdup(optarg);
236 pidfilename = strdup(optarg);
240 reject_score = atoi(optarg);
243 path_to_sendmail = strdup(optarg);
246 rejectcode = strdup (optarg);
249 rejecttext = strdup (optarg);
252 flag_sniffuser = true;
253 defaultuser = strdup(optarg);
259 fprintf(stderr, "Can only have one -b or -B flag\n");
266 flag_bucket_only = true;
267 smfilter.xxfi_flags |= SMFIF_DELRCPT; // May delete recipients
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 );
285 if (flag_full_email && !flag_sniffuser)
287 fprintf(stderr, "-e flag requires -u\n");
291 /* remember the remainer of the arguments so we can pass them to spamc */
292 spamc_argc = argc - optind;
293 spamc_argv = argv + optind;
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;
330 /* Set standard reject text */
331 if (rejecttext == NULL) {
332 rejecttext = strdup ("Blocked by SpamAssassin");
334 if (rejectcode == NULL) {
335 rejectcode = strdup ("5.7.1");
341 pidfile = fopen(pidfilename,"w");
344 fprintf(stderr, "Could not open pidfile: %s\n", strerror(errno));
347 /* leave the file open through the fork, since we don't know our pid
355 if (daemon(0, 0) == -1)
357 fprintf(stderr, "daemon() failed: %s\n", strerror(errno));
364 fprintf(pidfile, "%ld\n", (long)getpid());
371 if (stat(sock,&junk) == 0) unlink(sock);
374 (void) smfi_setconn(sock);
375 if (smfi_register(smfilter) == MI_FAILURE) {
376 fprintf(stderr, "smfi_register failed\n");
377 exit(EX_UNAVAILABLE);
379 debug(D_MISC, "smfi_register succeeded");
381 debug(D_ALWAYS, "spamass-milter %s starting", PACKAGE_VERSION);
383 debug(D_ALWAYS, "spamass-milter %s exiting", PACKAGE_VERSION);
391 /* Update a header if SA changes it, or add it if it is new. */
392 void update_or_insert(SpamAssassin* assassin, SMFICTX* ctx, string oldstring, t_setter setter, const char *header )
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 );
399 string::size_type oldsize;
401 debug(D_UORI, "u_or_i: looking at <%s>", header);
402 debug(D_UORI, "u_or_i: oldstring: <%s>", oldstring.c_str());
404 newstring = retrieve_field(assassin->d().substr(0, eoh), header);
405 debug(D_UORI, "u_or_i: newstring: <%s>", newstring.c_str());
407 oldsize = callsetter(*assassin,setter)(newstring);
411 if (newstring != oldstring)
413 /* change if old one was present, append if non-null */
414 char* cstr = const_cast<char*>(newstring.c_str());
417 debug(D_UORI, "u_or_i: changing");
418 smfi_chgheader(ctx, const_cast<char*>(header), 1, newstring.size() > 0 ?
420 } else if (newstring.size() > 0)
422 debug(D_UORI, "u_or_i: inserting");
423 smfi_addheader(ctx, const_cast<char*>(header), cstr);
427 debug(D_UORI, "u_or_i: no change");
435 // implement the changes suggested by SpamAssassin for the mail. Returns
436 // the milter error code.
438 assassinate(SMFICTX* ctx, SpamAssassin* assassin)
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);
447 if (bob == string::npos)
448 bob = assassin->d().size();
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");
453 /* Summarily reject the message if SA tagged it, or if we have a minimum
454 score, reject if it exceeds that score. */
457 bool do_reject = false;
458 if (reject_score == -1 && !assassin->spam_flag().empty())
460 if (reject_score != -1)
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);
468 /* SA 2.x uses the keyword "hits" */
469 rv = sscanf(spam_status,"%*s hits=%d", &score);
472 debug(D_ALWAYS, "Could not extract score from <%s>", spam_status);
475 debug(D_MISC, "SA score: %d", score);
476 if (score >= reject_score)
482 debug(D_MISC, "Rejecting");
483 smfi_setreply(ctx, const_cast<char*>("550"), rejectcode, rejecttext);
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. */
496 popen_argv[0] = path_to_sendmail;
497 popen_argv[1] = spambucket;
498 popen_argv[2] = NULL;
500 debug(D_COPY, "calling %s %s", path_to_sendmail, spambucket);
501 p = popenv(popen_argv, "w", &pid);
504 debug(D_COPY, "popenv failed(%s). Will not send a copy to spambucket", strerror(errno));
507 // Send message provided by SpamAssassin
508 fwrite(assassin->d().c_str(), assassin->d().size(), 1, p);
510 waitpid(pid, NULL, 0);
517 /* Drop the message into the spam bucket if it's spam */
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" );
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" );
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() );
538 assassin->recipients.pop_front();
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");
550 // If SpamAssassin thinks it is spam, replace
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)
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");
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.");
571 return SMFIS_CONTINUE;
574 // retrieve the content of a specific field in the header
577 old_retrieve_field(const string& header, const string& field)
579 // look for beginning of content
580 string::size_type pos = find_nocase(header, "\n" + field + ": ");
582 // return empty string if not found
583 if (pos == string::npos)
585 debug(D_STR, "r_f: failed");
589 // look for end of field name
590 pos = find_nocase(header, " ", pos) + 1;
592 string::size_type pos2(pos);
595 if (pos2 == find_nocase(header, "\n", pos2))
598 // look for end of content
601 pos2 = find_nocase(header, "\n", pos2+1);
604 while ( pos2 < string::npos &&
605 isspace(header[pos2+1]) );
607 return header.substr(pos, pos2-pos);
611 // retrieve the content of a specific field in the header
614 retrieve_field(const string& header, const string& field)
617 string::size_type field_start = string::npos;
618 string::size_type field_end = string::npos;
619 string::size_type idx = 0;
621 while( field_start == string::npos ) {
622 idx = find_nocase( header, field + ":", idx );
625 if ( idx == string::npos ) {
629 // The string we've found needs to be either at the start of the
630 // headers string, or immediately following a "\n"
632 if ( header[ idx - 1 ] != '\n' ) {
633 idx++; // so we don't get stuck in an infinite loop
634 continue; // loop around again
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 ] == ' ' ) {
648 // See if there's anything left, to shortcut the rest of the
650 if ( field_start == header.length() - 1 ) {
654 // The field continues to the end of line. If the start of the next
655 // line is whitespace, then the field continues.
657 while( field_end == string::npos ) {
658 idx = header.find( "\n", idx );
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();
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
673 /* if the header line ends in \r\n, don't return the \r */
674 if (header[field_end-1] == '\r')
677 string data = header.substr( field_start, field_end - field_start );
679 /* Replace all CRLF pairs with LF */
681 while ( (idx = data.find("\r\n", idx)) != string::npos )
683 data.replace(idx,2,"\n");
692 // {{{ MLFI callbacks
695 // Gets called once when a client connects to sendmail
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.
702 mlfi_connect(SMFICTX * ctx, char *hostname, _SOCK_ADDR * hostaddr)
704 struct context *sctx;
707 debug(D_FUNC, "mlfi_connect: enter");
709 /* allocate a structure to store the IP address (and SA object) in */
710 sctx = (struct context *)malloc(sizeof(*sctx));
713 static struct sockaddr_in localhost;
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;
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);
727 sctx->assassin = NULL;
730 /* store a pointer to it with setpriv */
731 rv = smfi_setpriv(ctx, sctx);
732 if (rv != MI_SUCCESS)
734 debug(D_ALWAYS, "smfi_setpriv failed!");
735 return SMFIS_TEMPFAIL;
737 /* debug(D_ALWAYS, "ZZZ set private context to %p", sctx); */
739 //debug(D_FUNC, "sctx->connect_ip: `%d'", sctx->connect_ip.sin_family);
741 if (ip_in_networklist(hostaddr, &ignorenets))
743 debug(D_NET, "%s is in our ignore list - accepting message",
745 debug(D_FUNC, "mlfi_connect: exit ignore");
749 // Tell Milter to continue
750 debug(D_FUNC, "mlfi_connect: exit");
752 return SMFIS_CONTINUE;
756 // Gets called on every "HELO"
758 // stores the result in the context structure
760 sfsistat mlfi_helo(SMFICTX * ctx, char * helohost)
762 struct context *sctx = (struct context*)smfi_getpriv(ctx);
765 sctx->helo = strdup(helohost);
767 return SMFIS_CONTINUE;
771 // Gets called first for all messages
773 // creates SpamAssassin object and makes pointer to it
774 // private data of this filter process
777 mlfi_envfrom(SMFICTX* ctx, char** envfrom)
779 SpamAssassin* assassin;
780 struct context *sctx = (struct context *)smfi_getpriv(ctx);
785 debug(D_ALWAYS, "smfi_getpriv failed!");
786 return SMFIS_TEMPFAIL;
788 /* debug(D_ALWAYS, "ZZZ got private context %p", sctx); */
791 const char *auth_type = smfi_getsymval(ctx,
792 const_cast<char *>("{auth_type}"));
795 debug(D_MISC, "auth_type=%s", auth_type);
800 debug(D_FUNC, "mlfi_envfrom: enter");
802 // launch new SpamAssassin
803 assassin=new SpamAssassin;
804 } catch (string& problem)
806 throw_error(problem);
807 return SMFIS_TEMPFAIL;
810 assassin->set_connectip(string(sctx->connect_ip));
812 // Store a pointer to the assassin object in our context struct
813 sctx->assassin = assassin;
815 // remember the MAIL FROM address
816 assassin->set_from(string(envfrom[0]));
818 queueid=smfi_getsymval(ctx, const_cast<char *>("i"));
822 warnmacro("i", "ENVFROM");
824 assassin->queueid = queueid;
826 debug(D_MISC, "queueid=%s", queueid);
828 // tell Milter to continue
829 debug(D_FUNC, "mlfi_envfrom: exit");
831 return SMFIS_CONTINUE;
835 // Gets called once for each recipient
837 // stores the first recipient in the spamassassin object and
838 // stores all addresses and the number thereof (some redundancy)
843 mlfi_envrcpt(SMFICTX* ctx, char** envrcpt)
845 struct context *sctx = (struct context*)smfi_getpriv(ctx);
846 SpamAssassin* assassin = sctx->assassin;
849 debug(D_FUNC, "mlfi_envrcpt: enter");
853 /* open a pipe to sendmail so we can do address expansion */
859 popen_argv[0] = path_to_sendmail;
860 popen_argv[1] = (char *)"-bv";
861 popen_argv[2] = envrcpt[0];
862 popen_argv[3] = NULL;
864 debug(D_RCPT, "calling %s -bv %s", path_to_sendmail, envrcpt[0]);
866 p = popenv(popen_argv, "r", &pid);
869 debug(D_RCPT, "popenv failed(%s). Will not expand aliases", strerror(errno));
870 assassin->expandedrcpt.push_back(envrcpt[0]);
873 while (fgets(buf, sizeof(buf), p) != NULL)
876 /* strip trailing EOLs */
877 while (i > 0 && buf[i - 1] <= ' ')
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"
886 if (strstr(buf, "... deliverable: mailer "))
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);
895 waitpid(pid, NULL, 0);
899 assassin->expandedrcpt.push_back(envrcpt[0]);
901 debug(D_RCPT, "Total of %d actual recipients", (int)assassin->expandedrcpt.size());
903 if (assassin->numrcpt() == 0)
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
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
920 const char *macro_b, *macro_i, *macro_j, *macro_r,
921 *macro_s, *macro_v, *macro_Z, *macro__;
925 macro_b = smfi_getsymval(ctx, const_cast<char *>("b"));
930 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&tval));
932 warnmacro("b", "ENVRCPT");
936 macro_i = smfi_getsymval(ctx, const_cast<char *>("i"));
940 warnmacro("i", "ENVRCPT");
943 /* FQDN of this site */
944 macro_j = smfi_getsymval(ctx, const_cast<char *>("j"));
947 macro_j = "localhost";
948 warnmacro("j", "ENVRCPT");
951 /* Protocol used to receive the message */
952 macro_r = smfi_getsymval(ctx, const_cast<char *>("r"));
956 warnmacro("r", "ENVRCPT");
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
964 macro_s = smfi_getsymval(ctx, const_cast<char *>("s"));
966 macro_s = sctx->helo;
970 /* Sendmail binary version */
971 macro_v = smfi_getsymval(ctx, const_cast<char *>("v"));
975 warnmacro("v", "ENVRCPT");
978 /* Sendmail .cf version */
979 macro_Z = smfi_getsymval(ctx, const_cast<char *>("Z"));
983 warnmacro("Z", "ENVRCPT");
986 /* Validated sending site's address */
987 macro__ = smfi_getsymval(ctx, const_cast<char *>("_"));
991 warnmacro("_", "ENVRCPT");
994 assassin->output((string)"X-Envelope-From: "+assassin->from()+"\r\n");
995 assassin->output((string)"X-Envelope-To: "+envrcpt[0]+"\r\n");
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"+
1001 "(envelope-from "+assassin->from()+")\r\n");
1004 assassin->output((string)"X-Envelope-To: "+envrcpt[0]+"\r\n");
1006 /* increment RCPT TO: count */
1007 assassin->set_numrcpt();
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))
1013 debug(D_RCPT, "remembering %s for spamc", assassin->expandedrcpt.front().c_str());
1014 assassin->set_rcpt(assassin->expandedrcpt.front());
1017 debug(D_RCPT, "remembering recipient %s", envrcpt[0]);
1018 assassin->recipients.push_back( envrcpt[0] ); // XXX verify that this worked
1020 debug(D_FUNC, "mlfi_envrcpt: exit");
1022 return SMFIS_CONTINUE;
1026 // Gets called repeatedly for all header fields
1028 // assembles the headers and passes them on to the SpamAssassin client
1029 // through the pipe.
1031 // only exception: SpamAssassin header fields (X-Spam-*) get suppressed
1032 // but are being stored in the SpamAssassin element.
1034 // this function also starts the connection with the SPAMC program the
1035 // first time it is called.
1039 mlfi_header(SMFICTX* ctx, char* headerf, char* headerv)
1041 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1042 debug(D_FUNC, "mlfi_header: enter");
1044 // Check if the SPAMC program has already been run, if not we run it.
1045 if ( !(assassin->connected) )
1048 assassin->connected = 1; // SPAMC is getting ready to run
1049 assassin->Connect();
1051 catch (string& problem) {
1052 throw_error(problem);
1053 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1055 debug(D_FUNC, "mlfi_header: exit error connect");
1056 return SMFIS_TEMPFAIL;
1060 // Is it a "X-Spam-" header field?
1061 if ( cmp_nocase_partial("X-Spam-", headerf) == 0 )
1064 // memorize content of old fields
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);
1080 /* Hm. X-Spam header, but not one we recognize. Pass it through. */
1086 debug(D_FUNC, "mlfi_header: suppress");
1087 return SMFIS_CONTINUE;
1091 // Content-Type: will be stored if present
1092 if ( cmp_nocase_partial("Content-Type", headerf) == 0 )
1093 assassin->set_content_type(headerv);
1095 // Subject: should be stored
1096 if ( cmp_nocase_partial("Subject", headerf) == 0 )
1097 assassin->set_subject(headerv);
1099 // assemble header to be written to SpamAssassin
1100 string header = string(headerf) + ": " + headerv + "\r\n";
1103 // write to SpamAssassin client
1104 assassin->output(header.c_str(),header.size());
1105 } catch (string& problem)
1107 throw_error(problem);
1108 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1110 debug(D_FUNC, "mlfi_header: exit error output");
1111 return SMFIS_TEMPFAIL;
1115 debug(D_FUNC, "mlfi_header: exit");
1117 return SMFIS_CONTINUE;
1121 // Gets called once when the header is finished.
1123 // writes empty line to SpamAssassin client to separate
1124 // headers from body.
1127 mlfi_eoh(SMFICTX* ctx)
1129 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1131 debug(D_FUNC, "mlfi_eoh: enter");
1133 // Check if the SPAMC program has already been run, if not we run it.
1134 if ( !(assassin->connected) )
1137 assassin->connected = 1; // SPAMC is getting ready to run
1138 assassin->Connect();
1140 catch (string& problem) {
1141 throw_error(problem);
1142 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1145 debug(D_FUNC, "mlfi_eoh: exit error connect");
1146 return SMFIS_TEMPFAIL;
1151 // add blank line between header and body
1152 assassin->output("\r\n",2);
1153 } catch (string& problem)
1155 throw_error(problem);
1156 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1159 debug(D_FUNC, "mlfi_eoh: exit error output");
1160 return SMFIS_TEMPFAIL;
1165 debug(D_FUNC, "mlfi_eoh: exit");
1166 return SMFIS_CONTINUE;
1170 // Gets called repeatedly to transmit the body
1172 // writes everything directly to SpamAssassin client
1175 mlfi_body(SMFICTX* ctx, u_char *bodyp, size_t bodylen)
1177 debug(D_FUNC, "mlfi_body: enter");
1178 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1182 assassin->output(bodyp, bodylen);
1183 } catch (string& problem)
1185 throw_error(problem);
1186 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1188 debug(D_FUNC, "mlfi_body: exit error");
1189 return SMFIS_TEMPFAIL;
1193 debug(D_FUNC, "mlfi_body: exit");
1194 return SMFIS_CONTINUE;
1198 // Gets called once at the end of mail processing
1200 // tells SpamAssassin client that the mail is complete
1201 // through EOF and then modifies the mail accordingly by
1202 // calling the "assassinate" function
1205 mlfi_eom(SMFICTX* ctx)
1207 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1210 debug(D_FUNC, "mlfi_eom: enter");
1213 // close output pipe to signal EOF to SpamAssassin
1214 assassin->close_output();
1216 // read what the Assassin is telling us
1219 milter_status = assassinate(ctx, assassin);
1221 // now cleanup the element.
1222 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1225 } catch (string& problem)
1227 throw_error(problem);
1228 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1230 debug(D_FUNC, "mlfi_eom: exit error");
1231 return SMFIS_TEMPFAIL;
1235 debug(D_FUNC, "mlfi_eom: exit");
1236 return milter_status;
1240 // Gets called on session-basis. This keeps things nice & quiet.
1243 mlfi_close(SMFICTX* ctx)
1245 struct context *sctx;
1246 debug(D_FUNC, "mlfi_close");
1248 sctx = (struct context*)smfi_getpriv(ctx);
1250 return SMFIS_ACCEPT;
1255 smfi_setpriv(ctx, NULL);
1257 return SMFIS_ACCEPT;
1261 // Gets called when things are being aborted.
1263 // kills the SpamAssassin object, its destructor should
1264 // take care of everything.
1267 mlfi_abort(SMFICTX* ctx)
1269 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1271 debug(D_FUNC, "mlfi_abort");
1272 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1275 return SMFIS_ACCEPT;
1280 // {{{ SpamAssassin Class
1283 // This is a new constructor for the SpamAssassin object. It simply
1284 // initializes two variables. The original constructor has been
1285 // renamed to Connect().
1287 SpamAssassin::SpamAssassin():
1296 SpamAssassin::~SpamAssassin()
1300 // close all pipes that are still open
1301 if (pipe_io[0][0] > -1) close(pipe_io[0][0]);
1302 if (pipe_io[0][1] > -1) close(pipe_io[0][1]);
1303 if (pipe_io[1][0] > -1) close(pipe_io[1][0]);
1304 if (pipe_io[1][1] > -1) close(pipe_io[1][1]);
1306 // child still running?
1309 // make sure the pid is valid
1314 // wait for child to terminate
1316 waitpid(pid, &status, 0);
1321 // Clean up the recip list. Might be overkill, but it's good housekeeping.
1322 while( !recipients.empty())
1324 recipients.pop_front();
1326 // Clean up the recip list. Might be overkill, but it's good housekeeping.
1327 while( !expandedrcpt.empty())
1329 expandedrcpt.pop_front();
1334 // This is the old SpamAssassin constructor. It has been renamed Connect(),
1335 // and is now called at the beginning of the mlfi_header() function.
1338 void SpamAssassin::Connect()
1340 // set up pipes for in- and output
1341 if (pipe(pipe_io[0]))
1342 throw string(string("pipe error: ")+string(strerror(errno)));
1343 if (pipe(pipe_io[1]))
1344 throw string(string("pipe error: ")+string(strerror(errno)));
1346 // now execute SpamAssassin client for contact with SpamAssassin spamd
1348 // start child process
1349 switch(pid = fork())
1352 // forking trouble. throw error.
1353 throw string(string("fork error: ")+string(strerror(errno)));
1358 // close unused pipes
1359 close(pipe_io[1][0]);
1360 close(pipe_io[0][1]);
1362 // redirect stdin(0), stdout(1) and stderr(2)
1363 dup2(pipe_io[0][0],0);
1364 dup2(pipe_io[1][1],1);
1365 dup2(pipe_io[1][1],2);
1370 // absolute path (determined in autoconf)
1371 // should be a little more secure
1372 // XXX arbitrary 100-argument max
1374 char** argv = (char**) malloc(100*sizeof(char*));
1375 argv[argc++] = strdup(SPAMC);
1378 argv[argc++] = strdup("-u");
1379 if ( expandedrcpt.size() != 1 )
1381 // More (or less?) than one recipient, so we pass the default
1382 // username to SPAMC. This way special rules can be defined for
1383 // multi recipient messages.
1384 debug(D_RCPT, "%d recipients; spamc gets default username %s", (int)expandedrcpt.size(), defaultuser);
1385 argv[argc++] = defaultuser;
1388 // There is only 1 recipient so we pass the username
1389 // (converted to lowercase) to SPAMC. Don't worry about
1390 // freeing this memory as we're exec()ing anyhow.
1391 if (flag_full_email)
1392 argv[argc] = strlwr(strdup(full_user().c_str()));
1394 argv[argc] = strlwr(strdup(local_user().c_str()));
1396 debug(D_RCPT, "spamc gets %s", argv[argc]);
1403 argv[argc++] = strdup("-d");
1404 argv[argc++] = spamdhost;
1408 memcpy(argv+argc, spamc_argv, spamc_argc * sizeof(char *));
1413 execvp(argv[0] , argv); // does not return!
1416 throw_error(string("execution error: ")+string(strerror(errno)));
1423 // close unused pipes
1424 close(pipe_io[0][0]);
1425 close(pipe_io[1][1]);
1429 // mark the pipes non-blocking
1430 if(fcntl(pipe_io[0][1], F_SETFL, O_NONBLOCK) == -1)
1431 throw string(string("Cannot set pipe01 nonblocking: ")+string(strerror(errno)));
1432 #if 0 /* don't really need to make the sink pipe nonblocking */
1433 if(fcntl(pipe_io[1][0], F_SETFL, O_NONBLOCK) == -1)
1434 throw string(string("Cannot set pipe10 nonblocking: ")+string(strerror(errno)));
1437 // we have to assume the client is running now.
1440 /* If we have any buffered output, write it now. */
1441 if (outputbuffer.size())
1443 output(outputbuffer);
1448 // write to SpamAssassin
1450 SpamAssassin::output(const void* buffer, long size)
1452 debug(D_FUNC, "::output enter");
1454 debug(D_SPAMC, "output \"%*.*s\"", (int)size, (int)size, (char *)buffer);
1456 // if there are problems, fail.
1458 throw string("tried output despite problems. failed.");
1460 /* If we haven't launched spamc yet, just store the data */
1463 /* Silly C++ can't tell the difference between
1464 (const char*, string::size_type) and
1465 (string::size_type, char), so we have to cast the parameters.
1467 outputbuffer.append((const char *)buffer,(string::size_type)size);
1468 debug(D_FUNC, "::output exit1");
1472 // send to SpamAssassin
1478 struct pollfd fds[2];
1479 int nfds = 2, nready;
1480 fds[0].fd = pipe_io[0][1];
1481 fds[0].events = POLLOUT;
1482 fds[1].fd = pipe_io[1][0];
1483 fds[1].events = POLLIN;
1485 debug(D_POLL, "polling fds %d and %d", pipe_io[0][1], pipe_io[1][0]);
1486 nready = poll(fds, nfds, 1000);
1488 throw("poll failed");
1490 debug(D_POLL, "poll returned %d, fd0=%d, fd1=%d", nready, fds[0].revents, fds[1].revents);
1492 if (fds[1].revents & (POLLERR|POLLNVAL|POLLHUP))
1494 throw string("poll says my read pipe is busted");
1497 if (fds[0].revents & (POLLERR|POLLNVAL|POLLHUP))
1499 throw string("poll says my write pipe is busted");
1502 if (fds[1].revents & POLLIN)
1504 debug(D_POLL, "poll says I can read");
1508 if (fds[0].revents & POLLOUT)
1510 debug(D_POLL, "poll says I can write");
1511 switch(wsize = write(pipe_io[0][1], (char *)buffer + total, size - total))
1514 if (errno == EAGAIN)
1516 reason = string(strerror(errno));
1519 close(pipe_io[0][1]);
1520 close(pipe_io[1][0]);
1531 // wait until child is dead
1532 waitpid(pid, &status, 0);
1534 throw string(string("write error: ")+reason);
1538 debug(D_POLL, "wrote %ld bytes", wsize);
1542 } while ( total < size );
1544 debug(D_FUNC, "::output exit2");
1547 void SpamAssassin::output(const void* buffer)
1549 output(buffer, strlen((const char *)buffer));
1552 void SpamAssassin::output(string buffer)
1554 output(buffer.c_str(), buffer.size());
1557 // close output pipe
1559 SpamAssassin::close_output()
1561 if(close(pipe_io[0][1]))
1562 throw string(string("close error: ")+string(strerror(errno)));
1567 SpamAssassin::input()
1569 debug(D_FUNC, "::input enter");
1570 // if the child has exited or we experienced an error, return
1572 if (!running || error)
1574 debug(D_FUNC, "::input exit1");
1578 // keep reading from input pipe until it is empty
1579 empty_and_close_pipe();
1581 // that's it, we're through
1584 // wait until child is dead
1586 if(waitpid(pid, &status, 0)<0)
1589 throw string(string("waitpid error: ")+string(strerror(errno)));
1591 debug(D_FUNC, "::input exit2");
1595 // return reference to mail
1604 // get values of the different SpamAssassin fields
1607 SpamAssassin::spam_status()
1609 return x_spam_status;
1613 SpamAssassin::spam_flag()
1619 SpamAssassin::spam_report()
1621 return x_spam_report;
1625 SpamAssassin::spam_prev_content_type()
1627 return x_spam_prev_content_type;
1631 SpamAssassin::spam_checker_version()
1633 return x_spam_checker_version;
1637 SpamAssassin::spam_level()
1639 return x_spam_level;
1643 SpamAssassin::content_type()
1645 return _content_type;
1649 SpamAssassin::subject()
1655 SpamAssassin::rcpt()
1661 SpamAssassin::from()
1667 SpamAssassin::connectip()
1674 SpamAssassin::local_user()
1676 // assuming we have a recipient in the form: <username@somehost.somedomain>
1677 // (angle brackets optional) we return 'username'
1678 if (_rcpt[0] == '<')
1679 return _rcpt.substr(1, _rcpt.find_first_of("@+")-1);
1681 return _rcpt.substr(0, _rcpt.find_first_of("@+"));
1685 SpamAssassin::full_user()
1688 // assuming we have a recipient in the form: <username@somehost.somedomain>
1689 // (angle brackets optional) we return 'username@somehost.somedomain'
1690 if (_rcpt[0] == '<')
1691 name = _rcpt.substr(1, _rcpt.find('>')-1);
1694 if(name.find('@') == string::npos)
1696 /* if the name had no domain part (local delivery), append the default one */
1697 name = name + "@" + defaultdomain;
1703 SpamAssassin::numrcpt()
1709 SpamAssassin::set_numrcpt()
1716 SpamAssassin::set_numrcpt(const int val)
1723 // set the values of the different SpamAssassin
1724 // fields in our element. Returns former size of field
1727 SpamAssassin::set_spam_status(const string& val)
1729 string::size_type old = x_spam_status.size();
1730 x_spam_status = val;
1735 SpamAssassin::set_spam_flag(const string& val)
1737 string::size_type old = x_spam_flag.size();
1743 SpamAssassin::set_spam_report(const string& val)
1745 string::size_type old = x_spam_report.size();
1746 x_spam_report = val;
1751 SpamAssassin::set_spam_prev_content_type(const string& val)
1753 string::size_type old = x_spam_prev_content_type.size();
1754 x_spam_prev_content_type = val;
1759 SpamAssassin::set_spam_checker_version(const string& val)
1761 string::size_type old = x_spam_checker_version.size();
1762 x_spam_checker_version = val;
1767 SpamAssassin::set_spam_level(const string& val)
1769 string::size_type old = x_spam_level.size();
1775 SpamAssassin::set_content_type(const string& val)
1777 string::size_type old = _content_type.size();
1778 _content_type = val;
1783 SpamAssassin::set_subject(const string& val)
1785 string::size_type old = _subject.size();
1791 SpamAssassin::set_rcpt(const string& val)
1793 string::size_type old = _rcpt.size();
1799 SpamAssassin::set_from(const string& val)
1801 string::size_type old = _from.size();
1807 SpamAssassin::set_connectip(const string& val)
1809 string::size_type old = _connectip.size();
1815 // Read available output from SpamAssassin client
1818 SpamAssassin::read_pipe()
1825 debug(D_FUNC, "::read_pipe enter");
1827 if (pipe_io[1][0] == -1)
1829 debug(D_FUNC, "::read_pipe exit - shouldn't have been called?");
1833 size = read(pipe_io[1][0], iobuff, 1024);
1838 reason = string(strerror(errno));
1840 // Close remaining pipe.
1841 close(pipe_io[1][0]);
1851 // wait until child is dead
1852 waitpid(pid, &status, 0);
1854 // throw the error message that caused this trouble
1855 throw string(string("read error: ")+reason);
1856 } else if ( size == 0 )
1859 // EOF. Close the pipe
1860 if(close(pipe_io[1][0]))
1861 throw string(string("close error: ")+string(strerror(errno)));
1866 // append to mail buffer
1867 mail.append(iobuff, size);
1868 debug(D_POLL, "read %ld bytes", size);
1869 debug(D_SPAMC, "input \"%*.*s\"", (int)size, (int)size, iobuff);
1871 debug(D_FUNC, "::read_pipe exit");
1876 // Read all output from SpamAssassin client
1877 // and close the pipe
1880 SpamAssassin::empty_and_close_pipe()
1882 debug(D_FUNC, "::empty_and_close_pipe enter");
1885 debug(D_FUNC, "::empty_and_close_pipe exit");
1890 // {{{ Some small subroutines without much relation to functionality
1892 // output error message to syslog facility
1894 throw_error(const string& errmsg)
1897 syslog(LOG_ERR, "Thrown error: %s", errmsg.c_str());
1899 syslog(LOG_ERR, "Unknown error");
1902 /* Takes a comma or space-delimited string of debug tokens and sets the
1903 appropriate bits in flag_debug. "all" sets all the bits.
1905 void parse_debuglevel(char* string)
1909 /* make a copy so we don't overwrite argv[] */
1910 string = strdup(string);
1912 /* handle the old numeric values too */
1913 switch(atoi(string))
1916 flag_debug |= (1<<D_UORI) | (1<<D_STR);
1918 flag_debug |= (1<<D_POLL);
1920 flag_debug |= (1<<D_MISC) | (1<<D_FUNC);
1921 debug(D_ALWAYS, "Setting debug level to 0x%0x", flag_debug);
1928 while ((token = strsep(&string, ", ")))
1931 for (i=0; debugstrings[i]; i++)
1933 if(strcasecmp(token, "ALL")==0)
1935 flag_debug = (1<<D_MAX)-1;
1938 if(strcasecmp(token, debugstrings[i])==0)
1940 flag_debug |= (1<<i);
1945 if (!debugstrings[i])
1947 fprintf(stderr, "Invalid debug token \"%s\"\n", token);
1951 debug(D_ALWAYS, "Setting debug level to 0x%0x", flag_debug);
1956 Output a line to syslog using print format, but only if the appropriate
1957 debug level is set. The D_ALWAYS level is always enabled.
1959 void debug(enum debuglevel level, const char* fmt, ...)
1961 if ((1<<level) & flag_debug)
1963 #if defined(HAVE_VSYSLOG)
1966 vsyslog(LOG_ERR, fmt, vl);
1969 #if defined(HAVE_VASPRINTF)
1976 #if defined(HAVE_VASPRINTF)
1977 vasprintf(&buf, fmt, vl);
1979 #if defined(HAVE_VSNPRINTF)
1980 vsnprintf(buf, sizeof(buf)-1, fmt, vl);
1982 /* XXX possible buffer overflow here; be careful what you pass to debug() */
1983 vsprintf(buf, fmt, vl);
1987 syslog(LOG_ERR, "%s", buf);
1988 #if defined(HAVE_VASPRINTF)
1991 #endif /* vsyslog */
1995 // case-insensitive search
1997 find_nocase(const string& array, const string& pattern, string::size_type start)
1999 string::size_type pos(start);
2001 while (pos < array.size())
2003 string::size_type ctr = 0;
2005 while( (pos+ctr) < array.size() &&
2006 toupper(array[pos+ctr]) == toupper(pattern[ctr]) )
2009 if (ctr == pattern.size())
2011 debug(D_STR, "f_nc: <%s><%s>: hit", array.c_str(), pattern.c_str());
2019 debug(D_STR, "f_nc: <%s><%s>: nohit", array.c_str(), pattern.c_str());
2020 return string::npos;
2023 // compare case-insensitive
2025 cmp_nocase_partial(const string& s, const string& s2)
2027 string::const_iterator p=s.begin();
2028 string::const_iterator p2=s2.begin();
2030 while ( p != s.end() && p2 <= s2.end() ) {
2031 if (toupper(*p) != toupper(*p2))
2033 debug(D_STR, "c_nc_p: <%s><%s> : miss", s.c_str(), s2.c_str());
2034 return (toupper(*p) < toupper(*p2)) ? -1 : 1;
2040 debug(D_STR, "c_nc_p: <%s><%s> : hit", s.c_str(), s2.c_str());
2045 /* closeall() - close all FDs >= a specified value */
2046 void closeall(int fd)
2048 int fdlimit = sysconf(_SC_OPEN_MAX);
2049 while (fd < fdlimit)
2053 void parse_networklist(char *string, struct networklist *list)
2057 /* make a copy so we don't overwrite argv[] */
2058 string = strdup(string);
2060 while ((token = strsep(&string, ", ")))
2062 char *tnet = strsep(&token, "/");
2063 char *tmask = token;
2065 struct in6_addr net6;
2067 if (list->num_nets % 10 == 0)
2068 list->nets = (union net*)realloc(list->nets, sizeof(*list->nets) * (list->num_nets + 10));
2070 if (inet_pton(AF_INET, tnet, &net))
2072 struct in_addr mask;
2076 if (strchr(tmask, '.') == NULL)
2081 ret = sscanf(tmask, "%u", &bits);
2082 if (ret != 1 || bits > 32)
2084 fprintf(stderr,"%s: bad CIDR value", tmask);
2087 mask.s_addr = htonl(~((1L << (32 - bits)) - 1) & 0xffffffff);
2088 } else if (!inet_pton(AF_INET6, tmask, &mask))
2090 fprintf(stderr, "Could not parse \"%s\" as a netmask\n", tmask);
2094 mask.s_addr = 0xffffffff;
2097 char *snet = strdup(inet_ntoa(net));
2098 debug(D_MISC, "Adding %s/%s to network list", snet, inet_ntoa(mask));
2102 net.s_addr = net.s_addr & mask.s_addr;
2103 list->nets[list->num_nets].net4.af = AF_INET;
2104 list->nets[list->num_nets].net4.network = net;
2105 list->nets[list->num_nets].net4.netmask = mask;
2107 } else if (inet_pton(AF_INET6, tnet, &net6))
2113 if (sscanf(tmask, "%d", &mask) != 1 || mask > 128)
2115 fprintf(stderr,"%s: bad CIDR value", tmask);
2121 list->nets[list->num_nets].net6.af = AF_INET6;
2122 list->nets[list->num_nets].net6.network = net6;
2123 list->nets[list->num_nets].net6.netmask = mask;
2127 fprintf(stderr, "Could not parse \"%s\" as a network\n", tnet);
2135 int ip_in_networklist(struct sockaddr *addr, struct networklist *list)
2139 if (list->num_nets == 0)
2142 //debug(D_NET, "Checking %s against:", inet_ntoa(ip));
2143 for (i = 0; i < list->num_nets; i++)
2145 if (list->nets[i].net.af == AF_INET && addr->sa_family == AF_INET)
2147 struct in_addr ip = ((struct sockaddr_in *)addr)->sin_addr;
2149 debug(D_NET, "%s", inet_ntoa(list->nets[i].net4.network));
2150 debug(D_NET, "/%s", inet_ntoa(list->nets[i].net4.netmask));
2151 if ((ip.s_addr & list->nets[i].net4.netmask.s_addr) == list->nets[i].net4.network.s_addr)
2153 debug(D_NET, "Hit!");
2156 } else if (list->nets[i].net.af == AF_INET6 && addr->sa_family == AF_INET6)
2158 u_int8_t *ip = ((struct sockaddr_in6 *)addr)->sin6_addr.s6_addr;
2161 mask = list->nets[i].net6.netmask;
2162 for (j = 0; j < 16 && mask > 0; j++, mask -= 8)
2164 unsigned char bytemask;
2166 bytemask = (mask < 8) ? ~((1L << (8 - mask)) - 1) : 0xff;
2168 if ((ip[j] & bytemask) != (list->nets[i].net6.network.s6_addr[j] & bytemask))
2174 debug(D_NET, "Hit!");
2183 char *strlwr(char *str)
2194 /* Log a message about missing milter macros, but only the first time */
2195 void warnmacro(const char *macro, const char *scope)
2199 debug(D_ALWAYS, "Could not retrieve sendmail macro \"%s\"!. Please add it to confMILTER_MACROS_%s for better spamassassin results",
2205 untrusted-argument-safe popen function - only supports "r" and "w" modes
2206 for simplicity, and always reads stdout and stderr in "r" mode. Call
2207 fclose to close the FILE, and waitpid to reap the child process (pid).
2209 FILE *popenv(char *const argv[], const char *type, pid_t *pid)
2215 if ((*type != 'r' && *type != 'w') || type[1])
2222 switch (*pid = fork()) {
2224 case -1: /* Error. */
2226 (void)close(pdes[0]);
2227 (void)close(pdes[1]);
2231 case 0: /* Child. */
2234 * The dup2() to STDIN_FILENO is repeated to avoid
2235 * writing to pdes[1], which might corrupt the
2236 * parent's copy. This isn't good enough in
2237 * general, since the exit() is no return, so
2238 * the compiler is free to corrupt all the local
2241 (void)close(pdes[0]);
2242 (void)dup2(pdes[1], STDOUT_FILENO);
2243 (void)dup2(pdes[1], STDERR_FILENO);
2244 if (pdes[1] != STDOUT_FILENO && pdes[1] != STDERR_FILENO) {
2245 (void)close(pdes[1]);
2248 if (pdes[0] != STDIN_FILENO) {
2249 (void)dup2(pdes[0], STDIN_FILENO);
2250 (void)close(pdes[0]);
2252 (void)close(pdes[1]);
2254 execv(argv[0], argv);
2259 /* Parent; assume fdopen can't fail. */
2261 iop = fdopen(pdes[0], type);
2262 (void)close(pdes[1]);
2264 iop = fdopen(pdes[1], type);
2265 (void)close(pdes[0]);
2272 // vim6:ai:noexpandtab