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