3 // $Id: spamass-milter.cpp,v 1.90 2006/03/23 21:41:36 dnelson 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"
103 #include "libmilter/mfapi.h"
104 //#include "libmilter/mfdef.h"
106 #if !HAVE_DECL_STRSEP
107 char *strsep(char **stringp, const char *delim);
110 #if !HAVE_DECL_DAEMON
111 int daemon(int nochdir, int noclose);
118 #include "spamass-milter.h"
124 #ifndef INADDR_LOOPBACK
125 #define INADDR_LOOPBACK 0x7F000001
130 static const char Id[] = "$Id: spamass-milter.cpp,v 1.90 2006/03/23 21:41:36 dnelson Exp $";
132 struct smfiDesc smfilter =
134 "SpamAssassin", // filter name
135 SMFI_VERSION, // version code -- leave untouched
136 SMFIF_ADDHDRS|SMFIF_CHGHDRS|SMFIF_CHGBODY, // flags
137 mlfi_connect, // info filter callback
138 mlfi_helo, // HELO filter callback
139 mlfi_envfrom, // envelope sender filter callback
140 mlfi_envrcpt, // envelope recipient filter callback
141 mlfi_header, // header filter callback
142 mlfi_eoh, // end of header callback
143 mlfi_body, // body filter callback
144 mlfi_eom, // end of message callback
145 mlfi_abort, // message aborted callback
146 mlfi_close, // connection cleanup callback
149 const char *const debugstrings[] = {
150 "ALL", "FUNC", "POLL", "UORI", "STR", "MISC", "NET", "SPAMC", "RCPT",
155 int flag_debug = (1<<D_ALWAYS);
156 bool flag_reject = false;
157 int reject_score = -1;
158 bool dontmodifyspam = false; // Don't modify/add body or spam results headers
159 bool dontmodify = false; // Don't add SA headers, ever.
160 bool flag_sniffuser = false;
161 char *defaultuser; /* Username to send to spamc if there are multiple recipients */
162 char *defaultdomain; /* Domain to append if incoming address has none */
164 struct networklist ignorenets;
167 bool flag_bucket = false;
168 bool flag_bucket_only = false;
170 bool flag_full_email = false; /* pass full email address to spamc */
171 bool flag_expand = false; /* alias/virtusertable expansion */
172 bool ignore_authenticated_senders = false;
173 bool warnedmacro = false; /* have we logged that we couldn't fetch a macro? */
178 main(int argc, char* argv[])
181 const char *args = "fd:mMp:P:r:u:D:i:Ib:B:e:x";
184 char *pidfilename = NULL;
185 FILE *pidfile = NULL;
186 struct sigaction children_sigaction;
188 #ifdef HAVE_VERBOSE_TERMINATE_HANDLER
189 std::set_terminate (__gnu_cxx::__verbose_terminate_handler);
192 openlog("spamass-milter", LOG_PID, LOG_MAIL);
194 /* Process command line options */
195 while ((c = getopt(argc, argv, args)) != -1) {
201 parse_debuglevel(optarg);
204 spamdhost = strdup(optarg);
207 flag_full_email = true;
208 defaultdomain = strdup(optarg);
211 debug(D_MISC, "Parsing ignore list");
212 parse_networklist(optarg, &ignorenets);
215 debug(D_MISC, "Ignore authenticated senders");
216 ignore_authenticated_senders = true;
219 dontmodifyspam = true;
220 smfilter.xxfi_flags &= ~SMFIF_CHGBODY;
224 dontmodifyspam = true;
225 smfilter.xxfi_flags &= ~(SMFIF_CHGBODY|SMFIF_CHGHDRS);
228 sock = strdup(optarg);
231 pidfilename = strdup(optarg);
235 reject_score = atoi(optarg);
238 flag_sniffuser = true;
239 defaultuser = strdup(optarg);
245 fprintf(stderr, "Can only have one -b or -B flag\n");
252 flag_bucket_only = true;
253 smfilter.xxfi_flags |= SMFIF_DELRCPT; // May delete recipients
255 // we will modify the recipient list; if spamc returns
256 // indicating that this mail is spam, the message will be
257 // sent to <optarg>@localhost
258 smfilter.xxfi_flags |= SMFIF_ADDRCPT; // May add recipients
259 // XXX we should probably verify that optarg is vaguely sane
260 spambucket = strdup( optarg );
271 if (flag_full_email && !flag_sniffuser)
273 fprintf(stderr, "-e flag requires -u\n");
277 /* remember the remainer of the arguments so we can pass them to spamc */
278 spamc_argc = argc - optind;
279 spamc_argv = argv + optind;
282 cout << PACKAGE_NAME << " - Version " << PACKAGE_VERSION << endl;
283 cout << "SpamAssassin Sendmail Milter Plugin" << endl;
284 cout << "Usage: spamass-milter -p socket [-b|-B bucket] [-d xx[,yy...]] [-D host]" << endl;
285 cout << " [-e defaultdomain] [-f] [-i networks] [-I] [-m] [-M]" << endl;
286 cout << " [-P pidfile] [-r nn] [-u defaultuser] [-x]" << endl;
287 cout << " [-- spamc args ]" << endl;
288 cout << " -p socket: path to create socket" << endl;
289 cout << " -b bucket: redirect spam to this mail address. The orignal" << endl;
290 cout << " recipient(s) will not receive anything." << endl;
291 cout << " -B bucket: add this mail address as a BCC recipient of spam." << endl;
292 cout << " -d xx[,yy ...]: set debug flags. Logs to syslog" << endl;
293 cout << " -D host: connect to spamd at remote host (deprecated)" << endl;
294 cout << " -e defaultdomain: pass full email address to spamc instead of just\n"
295 " username. Uses 'defaultdomain' if there was none" << endl;
296 cout << " -f: fork into background" << endl;
297 cout << " -i: skip (ignore) checks from these IPs or netblocks" << endl;
298 cout << " example: -i 192.168.12.5,10.0.0.0/8,172.16.0.0/255.255.0.0" << endl;
299 cout << " -I: skip (ignore) checks if sender is authenticated" << endl;
300 cout << " -m: don't modify body, Content-type: or Subject:" << endl;
301 cout << " -M: don't modify the message at all" << endl;
302 cout << " -P pidfile: Put processid in pidfile" << endl;
303 cout << " -r nn: reject messages with a score >= nn with an SMTP error.\n"
304 " use -1 to reject any messages tagged by SA." << endl;
305 cout << " -u defaultuser: pass the recipient's username to spamc.\n"
306 " Uses 'defaultuser' if there are multiple recipients." << endl;
307 cout << " -x: pass email address through alias and virtusertable expansion." << endl;
308 cout << " -- spamc args: pass the remaining flags to spamc." << endl;
316 pidfile = fopen(pidfilename,"w");
319 fprintf(stderr, "Could not open pidfile: %s\n", strerror(errno));
322 /* leave the file open through the fork, since we don't know our pid
330 if (daemon(0, 0) == -1)
332 fprintf(stderr, "daemon() failed: %s\n", strerror(errno));
339 fprintf(pidfile, "%ld\n", (long)getpid());
346 if (stat(sock,&junk) == 0) unlink(sock);
349 /* We don't care about any of our children, so ignore all of them */
350 /* Set up sigaction to avoid having to reap children */
351 memset(&children_sigaction, 0, sizeof children_sigaction);
352 children_sigaction.sa_flags = SA_NOCLDWAIT;
353 sigaction(SIGCHLD,&children_sigaction,0);
356 (void) smfi_setconn(sock);
357 if (smfi_register(smfilter) == MI_FAILURE) {
358 fprintf(stderr, "smfi_register failed\n");
359 exit(EX_UNAVAILABLE);
361 debug(D_MISC, "smfi_register succeeded");
363 debug(D_ALWAYS, "spamass-milter %s starting", PACKAGE_VERSION);
365 debug(D_ALWAYS, "spamass-milter %s exiting", PACKAGE_VERSION);
373 /* Update a header if SA changes it, or add it if it is new. */
374 void update_or_insert(SpamAssassin* assassin, SMFICTX* ctx, string oldstring, t_setter setter, char *header )
376 string::size_type eoh1 = assassin->d().find("\n\n");
377 string::size_type eoh2 = assassin->d().find("\n\r\n");
378 string::size_type eoh = ( eoh1 < eoh2 ? eoh1 : eoh2 );
381 string::size_type oldsize;
383 debug(D_UORI, "u_or_i: looking at <%s>", header);
384 debug(D_UORI, "u_or_i: oldstring: <%s>", oldstring.c_str());
386 newstring = retrieve_field(assassin->d().substr(0, eoh), header);
387 debug(D_UORI, "u_or_i: newstring: <%s>", newstring.c_str());
389 oldsize = callsetter(*assassin,setter)(newstring);
393 if (newstring != oldstring)
395 /* change if old one was present, append if non-null */
396 char* cstr = const_cast<char*>(newstring.c_str());
399 debug(D_UORI, "u_or_i: changing");
400 smfi_chgheader(ctx, header, 1, newstring.size() > 0 ?
402 } else if (newstring.size() > 0)
404 debug(D_UORI, "u_or_i: inserting");
405 smfi_addheader(ctx, header, cstr);
409 debug(D_UORI, "u_or_i: no change");
417 // implement the changes suggested by SpamAssassin for the mail. Returns
418 // the milter error code.
420 assassinate(SMFICTX* ctx, SpamAssassin* assassin)
422 // find end of header (eol in last line of header)
423 // and beginning of body
424 string::size_type eoh1 = assassin->d().find("\n\n");
425 string::size_type eoh2 = assassin->d().find("\n\r\n");
426 string::size_type eoh = (eoh1 < eoh2) ? eoh1 : eoh2;
427 string::size_type bob = assassin->d().find_first_not_of("\r\n", eoh);
429 if (bob == string::npos)
430 bob = assassin->d().size();
432 update_or_insert(assassin, ctx, assassin->spam_flag(), &SpamAssassin::set_spam_flag, "X-Spam-Flag");
433 update_or_insert(assassin, ctx, assassin->spam_status(), &SpamAssassin::set_spam_status, "X-Spam-Status");
435 /* Summarily reject the message if SA tagged it, or if we have a minimum
436 score, reject if it exceeds that score. */
439 bool do_reject = false;
440 if (reject_score == -1 && !assassin->spam_flag().empty())
442 if (reject_score != -1)
445 const char *spam_status = assassin->spam_status().c_str();
446 /* SA 3.0 uses the keyword "score" */
447 rv = sscanf(spam_status,"%*s score=%d", &score);
450 /* SA 2.x uses the keyword "hits" */
451 rv = sscanf(spam_status,"%*s hits=%d", &score);
454 debug(D_ALWAYS, "Could not extract score from <%s>", spam_status);
457 debug(D_MISC, "SA score: %d", score);
458 if (score >= reject_score)
464 debug(D_MISC, "Rejecting");
465 smfi_setreply(ctx, "550", "5.7.1", "Blocked by SpamAssassin");
470 /* If we also want a copy of the spam, shell out to sendmail and
471 send another copy. The milter API will not let you send the
472 message AND return a failure code to the sender, so this is
473 the only way to do it. */
477 popen_argv[0] = SENDMAIL;
478 popen_argv[1] = spambucket;
479 popen_argv[2] = NULL;
481 debug(D_COPY, "calling %s %s", SENDMAIL, spambucket);
482 p = popenv(popen_argv, "w");
485 debug(D_COPY, "popenv failed(%s). Will not send a copy to spambucket", strerror(errno));
488 // Send message provided by SpamAssassin
489 fwrite(assassin->d().c_str(), assassin->d().size(), 1, p);
497 /* Drop the message into the spam bucket if it's spam */
499 if (!assassin->spam_flag().empty()) {
500 // first, add the spambucket address
501 if ( smfi_addrcpt( ctx, spambucket ) != MI_SUCCESS ) {
502 throw string( "Failed to add spambucket to recipients" );
504 if (flag_bucket_only) {
505 // Move recipients to a non-active header, one at a
506 // time. Note, this may generate multiple X-Spam-Orig-To
507 // headers, but that's okay.
508 while( !assassin->recipients.empty()) {
509 if ( smfi_addheader( ctx, "X-Spam-Orig-To", (char *)assassin->recipients.front().c_str()) != MI_SUCCESS ) {
510 throw string( "Failed to save recipient" );
513 // It's not 100% important that this succeeds, so we'll just warn on failure rather than bailing out.
514 if ( smfi_delrcpt( ctx, (char *)assassin->recipients.front().c_str()) != MI_SUCCESS ) {
515 // throw_error really just logs a warning as opposed to actually throw()ing
516 debug(D_ALWAYS, "Failed to remove recipient %s from the envelope", assassin->recipients.front().c_str() );
518 assassin->recipients.pop_front();
524 update_or_insert(assassin, ctx, assassin->spam_report(), &SpamAssassin::set_spam_report, "X-Spam-Report");
525 update_or_insert(assassin, ctx, assassin->spam_prev_content_type(), &SpamAssassin::set_spam_prev_content_type, "X-Spam-Prev-Content-Type");
526 update_or_insert(assassin, ctx, assassin->spam_level(), &SpamAssassin::set_spam_level, "X-Spam-Level");
527 update_or_insert(assassin, ctx, assassin->spam_checker_version(), &SpamAssassin::set_spam_checker_version, "X-Spam-Checker-Version");
530 // If SpamAssassin thinks it is spam, replace
535 // However, only issue the header replacement calls if the content has
536 // actually changed. If SA didn't change subject or content-type, don't
537 // replace here unnecessarily.
538 if (!dontmodifyspam && assassin->spam_flag().size()>0)
540 update_or_insert(assassin, ctx, assassin->subject(), &SpamAssassin::set_subject, "Subject");
541 update_or_insert(assassin, ctx, assassin->content_type(), &SpamAssassin::set_content_type, "Content-Type");
543 // Replace body with the one SpamAssassin provided
544 string::size_type body_size = assassin->d().size() - bob;
545 string body=assassin->d().substr(bob, string::npos);
546 if ( smfi_replacebody(ctx, (unsigned char *)body.c_str(), body_size) == MI_FAILURE )
547 throw string("error. could not replace body.");
551 return SMFIS_CONTINUE;
554 // retrieve the content of a specific field in the header
557 old_retrieve_field(const string& header, const string& field)
559 // look for beginning of content
560 string::size_type pos = find_nocase(header, "\n" + field + ": ");
562 // return empty string if not found
563 if (pos == string::npos)
565 debug(D_STR, "r_f: failed");
569 // look for end of field name
570 pos = find_nocase(header, " ", pos) + 1;
572 string::size_type pos2(pos);
575 if (pos2 == find_nocase(header, "\n", pos2))
578 // look for end of content
581 pos2 = find_nocase(header, "\n", pos2+1);
584 while ( pos2 < string::npos &&
585 isspace(header[pos2+1]) );
587 return header.substr(pos, pos2-pos);
591 // retrieve the content of a specific field in the header
594 retrieve_field(const string& header, const string& field)
597 string::size_type field_start = string::npos;
598 string::size_type field_end = string::npos;
599 string::size_type idx = 0;
601 while( field_start == string::npos ) {
602 idx = find_nocase( header, field + ":", idx );
605 if ( idx == string::npos ) {
609 // The string we've found needs to be either at the start of the
610 // headers string, or immediately following a "\n"
612 if ( header[ idx - 1 ] != '\n' ) {
613 idx++; // so we don't get stuck in an infinite loop
614 continue; // loop around again
621 // A mail field starts just after the header. Ideally, there's a
622 // space, but it's possible that there isn't.
623 field_start += field.length() + 1;
624 if ( field_start < ( header.length() - 1 ) && header[ field_start ] == ' ' ) {
628 // See if there's anything left, to shortcut the rest of the
630 if ( field_start == header.length() - 1 ) {
634 // The field continues to the end of line. If the start of the next
635 // line is whitespace, then the field continues.
637 while( field_end == string::npos ) {
638 idx = header.find( "\n", idx );
640 // if we don't find a "\n", gobble everything to the end of the headers
641 if ( idx == string::npos ) {
642 field_end = header.length();
644 // check the next character
645 if (( idx + 1 ) < header.length() && ( isspace( header[ idx + 1 ] ))) {
646 idx ++; // whitespace found, so wrap to the next line
653 /* if the header line ends in \r\n, don't return the \r */
654 if (header[field_end-1] == '\r')
657 string data = header.substr( field_start, field_end - field_start );
659 /* Replace all CRLF pairs with LF */
661 while ( (idx = data.find("\r\n", idx)) != string::npos )
663 data.replace(idx,2,"\n");
672 // {{{ MLFI callbacks
675 // Gets called once when a client connects to sendmail
677 // gets the originating IP address and checks it against the ignore list
678 // if it isn't in the list, store the IP in a structure and store a
679 // pointer to it in the private data area.
682 mlfi_connect(SMFICTX * ctx, char *hostname, _SOCK_ADDR * hostaddr)
684 struct context *sctx;
687 debug(D_FUNC, "mlfi_connect: enter");
689 /* allocate a structure to store the IP address (and SA object) in */
690 sctx = (struct context *)malloc(sizeof(*sctx));
693 /* not a socket; probably a local user calling sendmail directly */
694 /* set to 127.0.0.1 */
695 sctx->connect_ip.s_addr = htonl(INADDR_LOOPBACK);
698 sctx->connect_ip = ((struct sockaddr_in *) hostaddr)->sin_addr;
700 sctx->assassin = NULL;
703 /* store a pointer to it with setpriv */
704 rv = smfi_setpriv(ctx, sctx);
705 if (rv != MI_SUCCESS)
707 debug(D_ALWAYS, "smfi_setpriv failed!");
708 return SMFIS_TEMPFAIL;
710 /* debug(D_ALWAYS, "ZZZ set private context to %p", sctx); */
712 if (ip_in_networklist(sctx->connect_ip, &ignorenets))
714 debug(D_NET, "%s is in our ignore list - accepting message",
715 inet_ntoa(sctx->connect_ip));
716 debug(D_FUNC, "mlfi_connect: exit ignore");
720 // Tell Milter to continue
721 debug(D_FUNC, "mlfi_connect: exit");
723 return SMFIS_CONTINUE;
727 // Gets called on every "HELO"
729 // stores the result in the context structure
731 sfsistat mlfi_helo(SMFICTX * ctx, char * helohost)
733 struct context *sctx = (struct context*)smfi_getpriv(ctx);
736 sctx->helo = strdup(helohost);
738 return SMFIS_CONTINUE;
742 // Gets called first for all messages
744 // creates SpamAssassin object and makes pointer to it
745 // private data of this filter process
748 mlfi_envfrom(SMFICTX* ctx, char** envfrom)
750 SpamAssassin* assassin;
751 struct context *sctx = (struct context *)smfi_getpriv(ctx);
756 debug(D_ALWAYS, "smfi_getpriv failed!");
757 return SMFIS_TEMPFAIL;
759 /* debug(D_ALWAYS, "ZZZ got private context %p", sctx); */
761 if (ignore_authenticated_senders)
765 auth_authen = smfi_getsymval(ctx, "{auth_authen}");
766 debug(D_MISC, "auth_authen=%s", auth_authen ?: "<unauthenticated>");
770 debug(D_MISC, "sender authenticated (%s) - accepting message",
772 debug(D_FUNC, "mlfi_envfrom: exit ignore");
777 debug(D_FUNC, "mlfi_envfrom: enter");
779 // launch new SpamAssassin
780 assassin=new SpamAssassin;
781 } catch (string& problem)
783 throw_error(problem);
784 return SMFIS_TEMPFAIL;
787 assassin->set_connectip(string(inet_ntoa(sctx->connect_ip)));
789 // Store a pointer to the assassin object in our context struct
790 sctx->assassin = assassin;
792 // remember the MAIL FROM address
793 assassin->set_from(string(envfrom[0]));
795 queueid=smfi_getsymval(ctx,"i");
799 warnmacro("i", "ENVFROM");
801 assassin->queueid = queueid;
803 debug(D_MISC, "queueid=%s", queueid);
805 // tell Milter to continue
806 debug(D_FUNC, "mlfi_envfrom: exit");
808 return SMFIS_CONTINUE;
812 // Gets called once for each recipient
814 // stores the first recipient in the spamassassin object and
815 // stores all addresses and the number thereof (some redundancy)
820 mlfi_envrcpt(SMFICTX* ctx, char** envrcpt)
822 struct context *sctx = (struct context*)smfi_getpriv(ctx);
823 SpamAssassin* assassin = sctx->assassin;
825 #if defined(__FreeBSD__)
829 debug(D_FUNC, "mlfi_envrcpt: enter");
833 /* open a pipe to sendmail so we can do address expansion */
838 popen_argv[0] = SENDMAIL;
839 popen_argv[1] = "-bv";
840 popen_argv[2] = envrcpt[0];
841 popen_argv[3] = NULL;
843 debug(D_RCPT, "calling %s -bv %s", SENDMAIL, envrcpt[0]);
845 p = popenv(popen_argv, "r");
848 debug(D_RCPT, "popenv failed(%s). Will not expand aliases", strerror(errno));
849 assassin->expandedrcpt.push_back(envrcpt[0]);
852 while (fgets(buf, sizeof(buf), p) != NULL)
855 /* strip trailing EOLs */
856 while (i > 0 && buf[i - 1] <= ' ')
859 debug(D_RCPT, "sendmail output: %s", buf);
860 /* From a quick scan of the sendmail source, a valid email
861 address gets printed via either
862 "deliverable: mailer %s, host %s, user %s"
863 or "deliverable: mailer %s, user %s"
865 if (strstr(buf, "... deliverable: mailer "))
867 char *p=strstr(buf,", user ");
868 /* anything after ", user " is the email address */
869 debug(D_RCPT, "user: %s", p+7);
870 assassin->expandedrcpt.push_back(p+7);
877 assassin->expandedrcpt.push_back(envrcpt[0]);
879 debug(D_RCPT, "Total of %d actual recipients", (int)assassin->expandedrcpt.size());
881 if (assassin->numrcpt() == 0)
883 /* Send the envelope headers as X-Envelope-From: and
884 X-Envelope-To: so that SpamAssassin can use them in its
885 whitelist checks. Also forge as complete a dummy
886 Received: header as possible because SA gets a lot of
889 HReceived: $?sfrom $s $.$?_($?s$|from $.$_)
890 $.$?{auth_type}(authenticated$?{auth_ssf} bits=${auth_ssf}$.)
891 $.by $j ($v/$Z)$?r with $r$. id $i$?{tls_version}
892 (version=${tls_version} cipher=${cipher} bits=${cipher_bits} verify=${verify})$.$?u
898 const char *macro_b, *macro_i, *macro_j, *macro_r,
899 *macro_s, *macro_v, *macro_Z, *macro__;
903 macro_b = smfi_getsymval(ctx, "b");
908 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&tval));
910 warnmacro("b", "ENVRCPT");
914 macro_i = smfi_getsymval(ctx, "i");
918 warnmacro("i", "ENVRCPT");
921 /* FQDN of this site */
922 macro_j = smfi_getsymval(ctx, "j");
925 macro_j = "localhost";
926 warnmacro("j", "ENVRCPT");
929 /* Protocol used to receive the message */
930 macro_r = smfi_getsymval(ctx, "r");
934 warnmacro("r", "ENVRCPT");
937 /* Sendmail currently cannot pass us the {s} macro, but
938 I do not know why. Leave this in for the day sendmail is
939 fixed. Until that day, use the value remembered by
942 macro_s = smfi_getsymval(ctx, "s");
944 macro_s = sctx->helo;
948 /* Sendmail binary version */
949 macro_v = smfi_getsymval(ctx, "v");
953 warnmacro("v", "ENVRCPT");
956 /* Sendmail .cf version */
957 macro_Z = smfi_getsymval(ctx, "Z");
961 warnmacro("Z", "ENVRCPT");
964 /* Validated sending site's address */
965 macro__ = smfi_getsymval(ctx, "_");
969 warnmacro("_", "ENVRCPT");
972 assassin->output((string)"X-Envelope-From: "+assassin->from()+"\r\n");
973 assassin->output((string)"X-Envelope-To: "+envrcpt[0]+"\r\n");
975 assassin->output((string)
976 "Received: from "+macro_s+" ("+macro__+")\r\n\t"+
977 "by "+macro_j+" ("+macro_v+"/"+macro_Z+") with "+macro_r+" id "+macro_i+"\r\n\t"+
979 "(envelope-from "+assassin->from()+")\r\n");
982 assassin->output((string)"X-Envelope-To: "+envrcpt[0]+"\r\n");
984 /* increment RCPT TO: count */
985 assassin->set_numrcpt();
987 /* If we expanded to at least one user and we haven't recorded one yet,
988 record the first one */
989 if (!assassin->expandedrcpt.empty() && (assassin->rcpt().size() == 0))
991 debug(D_RCPT, "remembering %s for spamc", assassin->expandedrcpt.front().c_str());
992 assassin->set_rcpt(assassin->expandedrcpt.front());
995 debug(D_RCPT, "remembering recipient %s", envrcpt[0]);
996 assassin->recipients.push_back( envrcpt[0] ); // XXX verify that this worked
998 debug(D_FUNC, "mlfi_envrcpt: exit");
1000 return SMFIS_CONTINUE;
1004 // Gets called repeatedly for all header fields
1006 // assembles the headers and passes them on to the SpamAssassin client
1007 // through the pipe.
1009 // only exception: SpamAssassin header fields (X-Spam-*) get suppressed
1010 // but are being stored in the SpamAssassin element.
1012 // this function also starts the connection with the SPAMC program the
1013 // first time it is called.
1017 mlfi_header(SMFICTX* ctx, char* headerf, char* headerv)
1019 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1020 debug(D_FUNC, "mlfi_header: enter");
1022 // Check if the SPAMC program has already been run, if not we run it.
1023 if ( !(assassin->connected) )
1026 assassin->connected = 1; // SPAMC is getting ready to run
1027 assassin->Connect();
1029 catch (string& problem) {
1030 throw_error(problem);
1031 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1033 debug(D_FUNC, "mlfi_header: exit error connect");
1034 return SMFIS_TEMPFAIL;
1038 // Is it a "X-Spam-" header field?
1039 if ( cmp_nocase_partial("X-Spam-", headerf) == 0 )
1042 // memorize content of old fields
1044 if ( cmp_nocase_partial("X-Spam-Status", headerf) == 0 )
1045 assassin->set_spam_status(headerv);
1046 else if ( cmp_nocase_partial("X-Spam-Flag", headerf) == 0 )
1047 assassin->set_spam_flag(headerv);
1048 else if ( cmp_nocase_partial("X-Spam-Report", headerf) == 0 )
1049 assassin->set_spam_report(headerv);
1050 else if ( cmp_nocase_partial("X-Spam-Prev-Content-Type", headerf) == 0 )
1051 assassin->set_spam_prev_content_type(headerv);
1052 else if ( cmp_nocase_partial("X-Spam-Level", headerf) == 0 )
1053 assassin->set_spam_level(headerv);
1054 else if ( cmp_nocase_partial("X-Spam-Checker-Version", headerf) == 0 )
1055 assassin->set_spam_checker_version(headerv);
1058 /* Hm. X-Spam header, but not one we recognize. Pass it through. */
1064 debug(D_FUNC, "mlfi_header: suppress");
1065 return SMFIS_CONTINUE;
1069 // Content-Type: will be stored if present
1070 if ( cmp_nocase_partial("Content-Type", headerf) == 0 )
1071 assassin->set_content_type(headerv);
1073 // Subject: should be stored
1074 if ( cmp_nocase_partial("Subject", headerf) == 0 )
1075 assassin->set_subject(headerv);
1077 // assemble header to be written to SpamAssassin
1078 string header = string(headerf) + ": " + headerv + "\r\n";
1081 // write to SpamAssassin client
1082 assassin->output(header.c_str(),header.size());
1083 } catch (string& problem)
1085 throw_error(problem);
1086 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1088 debug(D_FUNC, "mlfi_header: exit error output");
1089 return SMFIS_TEMPFAIL;
1093 debug(D_FUNC, "mlfi_header: exit");
1095 return SMFIS_CONTINUE;
1099 // Gets called once when the header is finished.
1101 // writes empty line to SpamAssassin client to separate
1102 // headers from body.
1105 mlfi_eoh(SMFICTX* ctx)
1107 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1109 debug(D_FUNC, "mlfi_eoh: enter");
1111 // Check if the SPAMC program has already been run, if not we run it.
1112 if ( !(assassin->connected) )
1115 assassin->connected = 1; // SPAMC is getting ready to run
1116 assassin->Connect();
1118 catch (string& problem) {
1119 throw_error(problem);
1120 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1123 debug(D_FUNC, "mlfi_eoh: exit error connect");
1124 return SMFIS_TEMPFAIL;
1129 // add blank line between header and body
1130 assassin->output("\r\n",2);
1131 } catch (string& problem)
1133 throw_error(problem);
1134 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1137 debug(D_FUNC, "mlfi_eoh: exit error output");
1138 return SMFIS_TEMPFAIL;
1143 debug(D_FUNC, "mlfi_eoh: exit");
1144 return SMFIS_CONTINUE;
1148 // Gets called repeatedly to transmit the body
1150 // writes everything directly to SpamAssassin client
1153 mlfi_body(SMFICTX* ctx, u_char *bodyp, size_t bodylen)
1155 debug(D_FUNC, "mlfi_body: enter");
1156 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1160 assassin->output(bodyp, bodylen);
1161 } catch (string& problem)
1163 throw_error(problem);
1164 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1166 debug(D_FUNC, "mlfi_body: exit error");
1167 return SMFIS_TEMPFAIL;
1171 debug(D_FUNC, "mlfi_body: exit");
1172 return SMFIS_CONTINUE;
1176 // Gets called once at the end of mail processing
1178 // tells SpamAssassin client that the mail is complete
1179 // through EOF and then modifies the mail accordingly by
1180 // calling the "assassinate" function
1183 mlfi_eom(SMFICTX* ctx)
1185 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1188 debug(D_FUNC, "mlfi_eom: enter");
1191 // close output pipe to signal EOF to SpamAssassin
1192 assassin->close_output();
1194 // read what the Assassin is telling us
1197 milter_status = assassinate(ctx, assassin);
1199 // now cleanup the element.
1200 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1203 } catch (string& problem)
1205 throw_error(problem);
1206 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1208 debug(D_FUNC, "mlfi_eom: exit error");
1209 return SMFIS_TEMPFAIL;
1213 debug(D_FUNC, "mlfi_eom: exit");
1214 return milter_status;
1218 // Gets called on session-basis. This keeps things nice & quiet.
1221 mlfi_close(SMFICTX* ctx)
1223 struct context *sctx;
1224 debug(D_FUNC, "mlfi_close");
1226 sctx = (struct context*)smfi_getpriv(ctx);
1228 return SMFIS_ACCEPT;
1233 smfi_setpriv(ctx, NULL);
1235 return SMFIS_ACCEPT;
1239 // Gets called when things are being aborted.
1241 // kills the SpamAssassin object, its destructor should
1242 // take care of everything.
1245 mlfi_abort(SMFICTX* ctx)
1247 SpamAssassin* assassin = ((struct context *)smfi_getpriv(ctx))->assassin;
1249 debug(D_FUNC, "mlfi_abort");
1250 ((struct context *)smfi_getpriv(ctx))->assassin=NULL;
1253 return SMFIS_ACCEPT;
1258 // {{{ SpamAssassin Class
1261 // This is a new constructor for the SpamAssassin object. It simply
1262 // initializes two variables. The original constructor has been
1263 // renamed to Connect().
1265 SpamAssassin::SpamAssassin():
1274 SpamAssassin::~SpamAssassin()
1278 // close all pipes that are still open
1279 if (pipe_io[0][0] > -1) close(pipe_io[0][0]);
1280 if (pipe_io[0][1] > -1) close(pipe_io[0][1]);
1281 if (pipe_io[1][0] > -1) close(pipe_io[1][0]);
1282 if (pipe_io[1][1] > -1) close(pipe_io[1][1]);
1284 // child still running?
1287 // make sure the pid is valid
1292 // wait for child to terminate
1294 waitpid(pid, &status, 0);
1299 // Clean up the recip list. Might be overkill, but it's good housekeeping.
1300 while( !recipients.empty())
1302 recipients.pop_front();
1304 // Clean up the recip list. Might be overkill, but it's good housekeeping.
1305 while( !expandedrcpt.empty())
1307 expandedrcpt.pop_front();
1312 // This is the old SpamAssassin constructor. It has been renamed Connect(),
1313 // and is now called at the beginning of the mlfi_header() function.
1316 void SpamAssassin::Connect()
1318 // set up pipes for in- and output
1319 if (pipe(pipe_io[0]))
1320 throw string(string("pipe error: ")+string(strerror(errno)));
1321 if (pipe(pipe_io[1]))
1322 throw string(string("pipe error: ")+string(strerror(errno)));
1324 // now execute SpamAssassin client for contact with SpamAssassin spamd
1326 // start child process
1327 switch(pid = fork())
1330 // forking trouble. throw error.
1331 throw string(string("fork error: ")+string(strerror(errno)));
1336 // close unused pipes
1337 close(pipe_io[1][0]);
1338 close(pipe_io[0][1]);
1340 // redirect stdin(0), stdout(1) and stderr(2)
1341 dup2(pipe_io[0][0],0);
1342 dup2(pipe_io[1][1],1);
1343 dup2(pipe_io[1][1],2);
1348 // absolute path (determined in autoconf)
1349 // should be a little more secure
1350 // XXX arbitrary 100-argument max
1352 char** argv = (char**) malloc(100*sizeof(char*));
1353 argv[argc++] = SPAMC;
1356 argv[argc++] = "-u";
1357 if ( expandedrcpt.size() != 1 )
1359 // More (or less?) than one recipient, so we pass the default
1360 // username to SPAMC. This way special rules can be defined for
1361 // multi recipient messages.
1362 debug(D_RCPT, "%d recipients; spamc gets default username %s", (int)expandedrcpt.size(), defaultuser);
1363 argv[argc++] = defaultuser;
1366 // There is only 1 recipient so we pass the username
1367 // (converted to lowercase) to SPAMC. Don't worry about
1368 // freeing this memory as we're exec()ing anyhow.
1369 if (flag_full_email)
1370 argv[argc] = strlwr(strdup(full_user().c_str()));
1372 argv[argc] = strlwr(strdup(local_user().c_str()));
1374 debug(D_RCPT, "spamc gets %s", argv[argc]);
1381 argv[argc++] = "-d";
1382 argv[argc++] = spamdhost;
1386 memcpy(argv+argc, spamc_argv, spamc_argc * sizeof(char *));
1391 execvp(argv[0] , argv); // does not return!
1394 throw_error(string("execution error: ")+string(strerror(errno)));
1401 // close unused pipes
1402 close(pipe_io[0][0]);
1403 close(pipe_io[1][1]);
1407 // mark the pipes non-blocking
1408 if(fcntl(pipe_io[0][1], F_SETFL, O_NONBLOCK) == -1)
1409 throw string(string("Cannot set pipe01 nonblocking: ")+string(strerror(errno)));
1410 #if 0 /* don't really need to make the sink pipe nonblocking */
1411 if(fcntl(pipe_io[1][0], F_SETFL, O_NONBLOCK) == -1)
1412 throw string(string("Cannot set pipe10 nonblocking: ")+string(strerror(errno)));
1415 // we have to assume the client is running now.
1418 /* If we have any buffered output, write it now. */
1419 if (outputbuffer.size())
1421 output(outputbuffer);
1426 // write to SpamAssassin
1428 SpamAssassin::output(const void* buffer, long size)
1430 debug(D_FUNC, "::output enter");
1432 debug(D_SPAMC, "output \"%*.*s\"", (int)size, (int)size, (char *)buffer);
1434 // if there are problems, fail.
1436 throw string("tried output despite problems. failed.");
1438 /* If we haven't launched spamc yet, just store the data */
1441 /* Silly C++ can't tell the difference between
1442 (const char*, string::size_type) and
1443 (string::size_type, char), so we have to cast the parameters.
1445 outputbuffer.append((const char *)buffer,(string::size_type)size);
1446 debug(D_FUNC, "::output exit1");
1450 // send to SpamAssassin
1456 struct pollfd fds[2];
1457 int nfds = 2, nready;
1458 fds[0].fd = pipe_io[0][1];
1459 fds[0].events = POLLOUT;
1460 fds[1].fd = pipe_io[1][0];
1461 fds[1].events = POLLIN;
1463 debug(D_POLL, "polling fds %d and %d", pipe_io[0][1], pipe_io[1][0]);
1464 nready = poll(fds, nfds, 1000);
1466 throw("poll failed");
1468 debug(D_POLL, "poll returned %d, fd0=%d, fd1=%d", nready, fds[0].revents, fds[1].revents);
1470 if (fds[1].revents & (POLLERR|POLLNVAL|POLLHUP))
1472 throw string("poll says my read pipe is busted");
1475 if (fds[0].revents & (POLLERR|POLLNVAL|POLLHUP))
1477 throw string("poll says my write pipe is busted");
1480 if (fds[1].revents & POLLIN)
1482 debug(D_POLL, "poll says I can read");
1486 if (fds[0].revents & POLLOUT)
1488 debug(D_POLL, "poll says I can write");
1489 switch(wsize = write(pipe_io[0][1], (char *)buffer + total, size - total))
1492 if (errno == EAGAIN)
1494 reason = string(strerror(errno));
1497 close(pipe_io[0][1]);
1498 close(pipe_io[1][0]);
1509 // wait until child is dead
1510 waitpid(pid, &status, 0);
1512 throw string(string("write error: ")+reason);
1516 debug(D_POLL, "wrote %ld bytes", wsize);
1520 } while ( total < size );
1522 debug(D_FUNC, "::output exit2");
1525 void SpamAssassin::output(const void* buffer)
1527 output(buffer, strlen((const char *)buffer));
1530 void SpamAssassin::output(string buffer)
1532 output(buffer.c_str(), buffer.size());
1535 // close output pipe
1537 SpamAssassin::close_output()
1539 if(close(pipe_io[0][1]))
1540 throw string(string("close error: ")+string(strerror(errno)));
1545 SpamAssassin::input()
1547 debug(D_FUNC, "::input enter");
1548 // if the child has exited or we experienced an error, return
1550 if (!running || error)
1552 debug(D_FUNC, "::input exit1");
1556 // keep reading from input pipe until it is empty
1557 empty_and_close_pipe();
1559 // that's it, we're through
1562 // wait until child is dead
1564 if(waitpid(pid, &status, 0)<0)
1567 throw string(string("waitpid error: ")+string(strerror(errno)));
1569 debug(D_FUNC, "::input exit2");
1573 // return reference to mail
1582 // get values of the different SpamAssassin fields
1585 SpamAssassin::spam_status()
1587 return x_spam_status;
1591 SpamAssassin::spam_flag()
1597 SpamAssassin::spam_report()
1599 return x_spam_report;
1603 SpamAssassin::spam_prev_content_type()
1605 return x_spam_prev_content_type;
1609 SpamAssassin::spam_checker_version()
1611 return x_spam_checker_version;
1615 SpamAssassin::spam_level()
1617 return x_spam_level;
1621 SpamAssassin::content_type()
1623 return _content_type;
1627 SpamAssassin::subject()
1633 SpamAssassin::rcpt()
1639 SpamAssassin::from()
1645 SpamAssassin::connectip()
1652 SpamAssassin::local_user()
1654 // assuming we have a recipient in the form: <username@somehost.somedomain>
1655 // (angle brackets optional) we return 'username'
1656 if (_rcpt[0] == '<')
1657 return _rcpt.substr(1, _rcpt.find_first_of("@+")-1);
1659 return _rcpt.substr(0, _rcpt.find_first_of("@+"));
1663 SpamAssassin::full_user()
1666 // assuming we have a recipient in the form: <username@somehost.somedomain>
1667 // (angle brackets optional) we return 'username@somehost.somedomain'
1668 if (_rcpt[0] == '<')
1669 name = _rcpt.substr(1, _rcpt.find('>')-1);
1672 if(name.find('@') == string::npos)
1674 /* if the name had no domain part (local delivery), append the default one */
1675 name = name + "@" + defaultdomain;
1681 SpamAssassin::numrcpt()
1687 SpamAssassin::set_numrcpt()
1694 SpamAssassin::set_numrcpt(const int val)
1701 // set the values of the different SpamAssassin
1702 // fields in our element. Returns former size of field
1705 SpamAssassin::set_spam_status(const string& val)
1707 string::size_type old = x_spam_status.size();
1708 x_spam_status = val;
1713 SpamAssassin::set_spam_flag(const string& val)
1715 string::size_type old = x_spam_flag.size();
1721 SpamAssassin::set_spam_report(const string& val)
1723 string::size_type old = x_spam_report.size();
1724 x_spam_report = val;
1729 SpamAssassin::set_spam_prev_content_type(const string& val)
1731 string::size_type old = x_spam_prev_content_type.size();
1732 x_spam_prev_content_type = val;
1737 SpamAssassin::set_spam_checker_version(const string& val)
1739 string::size_type old = x_spam_checker_version.size();
1740 x_spam_checker_version = val;
1745 SpamAssassin::set_spam_level(const string& val)
1747 string::size_type old = x_spam_level.size();
1753 SpamAssassin::set_content_type(const string& val)
1755 string::size_type old = _content_type.size();
1756 _content_type = val;
1761 SpamAssassin::set_subject(const string& val)
1763 string::size_type old = _subject.size();
1769 SpamAssassin::set_rcpt(const string& val)
1771 string::size_type old = _rcpt.size();
1777 SpamAssassin::set_from(const string& val)
1779 string::size_type old = _from.size();
1785 SpamAssassin::set_connectip(const string& val)
1787 string::size_type old = _connectip.size();
1793 // Read available output from SpamAssassin client
1796 SpamAssassin::read_pipe()
1803 debug(D_FUNC, "::read_pipe enter");
1805 if (pipe_io[1][0] == -1)
1807 debug(D_FUNC, "::read_pipe exit - shouldn't have been called?");
1811 size = read(pipe_io[1][0], iobuff, 1024);
1816 reason = string(strerror(errno));
1818 // Close remaining pipe.
1819 close(pipe_io[1][0]);
1829 // wait until child is dead
1830 waitpid(pid, &status, 0);
1832 // throw the error message that caused this trouble
1833 throw string(string("read error: ")+reason);
1834 } else if ( size == 0 )
1837 // EOF. Close the pipe
1838 if(close(pipe_io[1][0]))
1839 throw string(string("close error: ")+string(strerror(errno)));
1844 // append to mail buffer
1845 mail.append(iobuff, size);
1846 debug(D_POLL, "read %ld bytes", size);
1847 debug(D_SPAMC, "input \"%*.*s\"", (int)size, (int)size, iobuff);
1849 debug(D_FUNC, "::read_pipe exit");
1854 // Read all output from SpamAssassin client
1855 // and close the pipe
1858 SpamAssassin::empty_and_close_pipe()
1860 debug(D_FUNC, "::empty_and_close_pipe enter");
1863 debug(D_FUNC, "::empty_and_close_pipe exit");
1868 // {{{ Some small subroutines without much relation to functionality
1870 // output error message to syslog facility
1872 throw_error(const string& errmsg)
1875 syslog(LOG_ERR, "Thrown error: %s", errmsg.c_str());
1877 syslog(LOG_ERR, "Unknown error");
1880 /* Takes a comma or space-delimited string of debug tokens and sets the
1881 appropriate bits in flag_debug. "all" sets all the bits.
1883 void parse_debuglevel(char* string)
1887 /* make a copy so we don't overwrite argv[] */
1888 string = strdup(string);
1890 /* handle the old numeric values too */
1891 switch(atoi(string))
1894 flag_debug |= (1<<D_UORI) | (1<<D_STR);
1896 flag_debug |= (1<<D_POLL);
1898 flag_debug |= (1<<D_MISC) | (1<<D_FUNC);
1899 debug(D_ALWAYS, "Setting debug level to 0x%0x", flag_debug);
1906 while ((token = strsep(&string, ", ")))
1909 for (i=0; debugstrings[i]; i++)
1911 if(strcasecmp(token, "ALL")==0)
1913 flag_debug = (1<<D_MAX)-1;
1916 if(strcasecmp(token, debugstrings[i])==0)
1918 flag_debug |= (1<<i);
1923 if (!debugstrings[i])
1925 fprintf(stderr, "Invalid debug token \"%s\"\n", token);
1929 debug(D_ALWAYS, "Setting debug level to 0x%0x", flag_debug);
1934 Output a line to syslog using print format, but only if the appropriate
1935 debug level is set. The D_ALWAYS level is always enabled.
1937 void debug(enum debuglevel level, const char* fmt, ...)
1939 if ((1<<level) & flag_debug)
1941 #if defined(HAVE_VSYSLOG)
1944 vsyslog(LOG_ERR, fmt, vl);
1947 #if defined(HAVE_VASPRINTF)
1954 #if defined(HAVE_VASPRINTF)
1955 vasprintf(&buf, fmt, vl);
1957 #if defined(HAVE_VSNPRINTF)
1958 vsnprintf(buf, sizeof(buf)-1, fmt, vl);
1960 /* XXX possible buffer overflow here; be careful what you pass to debug() */
1961 vsprintf(buf, fmt, vl);
1965 syslog(LOG_ERR, "%s", buf);
1966 #if defined(HAVE_VASPRINTF)
1969 #endif /* vsyslog */
1973 // case-insensitive search
1975 find_nocase(const string& array, const string& pattern, string::size_type start)
1977 string::size_type pos(start);
1979 while (pos < array.size())
1981 string::size_type ctr = 0;
1983 while( (pos+ctr) < array.size() &&
1984 toupper(array[pos+ctr]) == toupper(pattern[ctr]) )
1987 if (ctr == pattern.size())
1989 debug(D_STR, "f_nc: <%s><%s>: hit", array.c_str(), pattern.c_str());
1997 debug(D_STR, "f_nc: <%s><%s>: nohit", array.c_str(), pattern.c_str());
1998 return string::npos;
2001 // compare case-insensitive
2003 cmp_nocase_partial(const string& s, const string& s2)
2005 string::const_iterator p=s.begin();
2006 string::const_iterator p2=s2.begin();
2008 while ( p != s.end() && p2 <= s2.end() ) {
2009 if (toupper(*p) != toupper(*p2))
2011 debug(D_STR, "c_nc_p: <%s><%s> : miss", s.c_str(), s2.c_str());
2012 return (toupper(*p) < toupper(*p2)) ? -1 : 1;
2018 debug(D_STR, "c_nc_p: <%s><%s> : hit", s.c_str(), s2.c_str());
2023 /* closeall() - close all FDs >= a specified value */
2024 void closeall(int fd)
2026 int fdlimit = sysconf(_SC_OPEN_MAX);
2027 while (fd < fdlimit)
2031 void parse_networklist(char *string, struct networklist *list)
2035 /* make a copy so we don't overwrite argv[] */
2036 string = strdup(string);
2038 while ((token = strsep(&string, ", ")))
2040 char *tnet = strsep(&token, "/");
2041 char *tmask = token;
2042 struct in_addr net, mask;
2044 if (list->num_nets % 10 == 0)
2045 list->nets = (struct net*)realloc(list->nets, sizeof(*list->nets) * (list->num_nets + 10));
2047 if (!inet_aton(tnet, &net))
2049 fprintf(stderr, "Could not parse \"%s\" as a network\n", tnet);
2055 if (strchr(tmask, '.') == NULL)
2060 ret = sscanf(tmask, "%u", &bits);
2061 if (ret != 1 || bits > 32)
2063 fprintf(stderr,"%s: bad CIDR value", tmask);
2066 mask.s_addr = htonl(~((1L << (32 - bits)) - 1) & 0xffffffff);
2067 } else if (!inet_aton(tmask, &mask))
2069 fprintf(stderr, "Could not parse \"%s\" as a netmask\n", tmask);
2073 mask.s_addr = 0xffffffff;
2076 char *snet = strdup(inet_ntoa(net));
2077 debug(D_MISC, "Adding %s/%s to network list", snet, inet_ntoa(mask));
2081 net.s_addr = net.s_addr & mask.s_addr;
2082 list->nets[list->num_nets].network = net;
2083 list->nets[list->num_nets].netmask = mask;
2089 int ip_in_networklist(struct in_addr ip, struct networklist *list)
2093 if (list->num_nets == 0)
2096 debug(D_NET, "Checking %s against:", inet_ntoa(ip));
2097 for (i = 0; i < list->num_nets; i++)
2099 debug(D_NET, "%s", inet_ntoa(list->nets[i].network));
2100 debug(D_NET, "/%s", inet_ntoa(list->nets[i].netmask));
2101 if ((ip.s_addr & list->nets[i].netmask.s_addr) == list->nets[i].network.s_addr)
2103 debug(D_NET, "Hit!");
2111 char *strlwr(char *str)
2122 /* Log a message about missing milter macros, but only the first time */
2123 void warnmacro(char *macro, char *scope)
2127 debug(D_ALWAYS, "Could not retrieve sendmail macro \"%s\"!. Please add it to confMILTER_MACROS_%s for better spamassassin results",
2133 untrusted-argument-safe popen function - only supports "r" and "w" modes
2134 for simplicity, and always reads stdout and stderr in "r" mode. Call
2135 fclose to close the FILE.
2137 FILE *popenv(char *const argv[], const char *type)
2144 if ((*type != 'r' && *type != 'w') || type[1])
2155 case -1: /* Error. */
2157 (void)close(pdes[0]);
2158 (void)close(pdes[1]);
2162 case 0: /* Child. */
2165 * The dup2() to STDIN_FILENO is repeated to avoid
2166 * writing to pdes[1], which might corrupt the
2167 * parent's copy. This isn't good enough in
2168 * general, since the exit() is no return, so
2169 * the compiler is free to corrupt all the local
2172 (void)close(pdes[0]);
2173 (void)dup2(pdes[1], STDOUT_FILENO);
2174 (void)dup2(pdes[1], STDERR_FILENO);
2175 if (pdes[1] != STDOUT_FILENO && pdes[1] != STDERR_FILENO) {
2176 (void)close(pdes[1]);
2179 if (pdes[0] != STDIN_FILENO) {
2180 (void)dup2(pdes[0], STDIN_FILENO);
2181 (void)close(pdes[0]);
2183 (void)close(pdes[1]);
2185 execv(argv[0], argv);
2190 /* Parent; assume fdopen can't fail. */
2192 iop = fdopen(pdes[0], type);
2193 (void)close(pdes[1]);
2195 iop = fdopen(pdes[1], type);
2196 (void)close(pdes[0]);
2203 // vim6:ai:noexpandtab