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