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