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