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