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