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