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