/* SPAMKILLER.C - discard messages marked as spam by SpamAssassin.

            Written by M.C.Gregorie.

            Version history
               
            1.0   16/01/08   Initial incomplete version
            1.02  17/01/08   Initial production version
            1.03  16/02/08   Revised storage algorithm, added -v option
            1.1   27/09/09   Added sender and subject logging
            1.2   23/10/09   Added fired rules logging
            1.3   27/07/10   Added error checks to body output
            1.3.1 16/09/10   Revised memory allocation
            1.3.2 21/09/10   Header store starts large
            1.4   24/07/11   Added option to quarantine spam
*/

#define VERSION         "1.4"
#define COPYRIGHT       "(c) M.C.Gregorie, 2010,2011"

/* This file is part of the Spamkiller package.

   Spamkiller is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 3 as
   published by the Free Software Foundation.

   Spamkiller is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
*/                

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <errno.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>

#include <environ.h>

#include "spamkiller.h"

/*
   readargs() - This function scans the command line for options and does
   ==========   all the associated setup.
*/
int readargs(int argc,
             char *argv[],
             int flags[],
             char **cmd,
             char **prefix,
             char **qname,
             char **sdir)
{
   int         i; 
   int         opt;
   static char *nullstr = "";
   static char *sdir_default = QROOT;

   /* initialise flags */

   for (i=0 ; i<FLAGCNT ; i++) flags[i] = FALSE;

   /* set up options from the command line */

   optmaybe = MAYBEMARK;
   optbad = BADMARK;
   optind = 1;
   *cmd = nullstr;
   *prefix = nullstr;
   *qname = nullstr;
   *sdir = sdir_default;
   
   while ((opt=getopt(argc,argv,OPTLIST)) != EOF)   
   {
      switch (opt) {
         case 0:         continue;
         case BADMARK:   return(opt);
         case '?':       flags[HELP] = TRUE;       
                         break;
         case 'd':       flags[DEBUG]++;
                         break;
         case 'c':       flags[COMMAND] = TRUE;
                         if (optarg != NULL)
                         {
                            *cmd = (char*)malloc(max(strlen(optarg + 1), 1));
                            if (*cmd == NULL)
                            {
                               abandon(EINVAL,
                                     "can't allocate space",
                                     "for -c command");
                            }
                            
                            strcpy(*cmd, optarg);
                         }
                         break;
         case 'q':       flags[QUARANTINE] = TRUE;
                         if (optarg != NULL)
                         {
                            *qname = (char*)malloc(max(strlen(optarg + 1), 1));
                            if (*qname == NULL)
                            {
                               abandon(EINVAL,
                                     "can't allocate space",
                                     "for -q name");
                            }
                            
                            strcpy(*qname, optarg);
                         }
                         break;
         case 'r':       flags[RULES] = TRUE;
                         if (optarg != NULL)
                         {
                            *prefix = (char*)malloc(max(strlen(optarg + 1), 1));
                            if (*prefix == NULL)
                            {
                               abandon(EINVAL,
                                     "can't allocate space",
                                     "for -r prefix");
                            }
                            
                            strcpy(*prefix, optarg);
                         }
                         break;
         case 's':       flags[SPAMSTORE] = TRUE;
                         if (optarg != NULL)
                         {
                            *sdir = (char*)malloc(max(strlen(optarg + 1), 1));
                            if (*sdir == NULL)
                            {
                               abandon(EINVAL,
                                     "can't allocate space",
                                     "for -s directory name");
                            }
                            
                            strcpy(*sdir, optarg);
                         }
                         break;
         case 'v':       flags[VERBOSE]++;
                         break;
         default :       abandon(EINVAL,
                               "impossible happened parsing options",
                               "");
      }
   }
   return(OK);
}

/*
   showhelp() - Show the punter what the options are.
   ==========
*/
void showhelp()
{
   printf("\nspamkiller v%s %s\n\n",VERSION,COPYRIGHT);
   printf("Syntax  : spamkiller [<opts>]\n");
   printf("Function: Discard spam messages. Messages are read\n");
   printf("          from stdin and non-spam is forwarded using\n");
   printf("          the command given by the -c option\n");
   printf("Options :\n");
   printf("     -d           debug mode\n");
   printf("     -c=command   the command used for forwarding messages\n");
   printf("     -q=name      quarantine spam rather than delete it.\n");
   printf("                  If name is present, use this name for the\n");
   printf("                  quarantine file.\n");
   printf("                  'name' should only be used for testing.\n");
   printf("     -r=prefix    if -r is present, show the list of rules that\n");
   printf("                  fired. If 'prefix' is given, only show the\n");
   printf("                  rules that start with 'prefix'\n");
   printf("     -s=dirname   Names the directory to be used to store\n");
   printf("                  quarantined spam. Default is 'quarantined'\n");
   printf("     -v           verbose mode. Logs arguments\n");
}

/*
   main() - The main program starts here.
   ======
*/
int main(int argc, char *argv[])
{
   char        *command;
   char        *prefix;
   int         flags[FLAGCNT];
   int         reply = 0;

   char  buff[MAXLINE+1];
   int   freply;
   FILE  *fwd;
   int   head_end;
   char  *headers;
   int   header_size;
   int   header_used;
   char  line[BUFFINC + 1];
   int   line_no;
   int   line_size;
   FILE  *qfile;
   char  *qdir = NULL;
   char  qname[MAXNAME + 1];
   char  *qtestname = NULL;
   char  *rules = NULL;
   char  *sender = NULL;
   int   spam_found;
   int   status_found;
   char  *subject = NULL;

   /*
      Parse and set up command options
   */
   setprog("spamkiller");
   if (readargs(argc,
                argv,
                flags,
                &command,
                &prefix,
                &qtestname,
                &qdir) == BADMARK)
   {
      return(1);
   }

   /*
      If help was asked for, give it and quit.
   */
   if (flags[HELP]) 
   {
      showhelp();
      return(ECONNABORTED);
   }

   if (strlen(command) == 0)
      abandon(EINVAL, "No command supplied for forwarding messages", "");

   /*
      Check that the command doesn't contain junk
   */
   check_command(command, flags[VERBOSE], flags[DEBUG]); 

   /*
      The following code drives the application. 
   */
   optind = 1;
   if (flags[DEBUG])
      fprintf(stderr,
              "========== Start of spamkiller -c=%s ==========\n",
              command);

   /*
      Initialise the empty header store, assuming the message is not
      spam.
   */
   header_size = BUFFINC * 2; 
   headers = get_space(header_size);
   strcpy(headers, EMPTYSTRING);
   header_used = strlen(EMPTYSTRING);
   head_end = FALSE;
   line_no = 0;
   spam_found = FALSE;
   status_found = FALSE;
   rules = get_space(strlen(EMPTYSTRING) + 1);
   strcpy(rules, EMPTYSTRING);

   /*
      Read the header into the store, at the same time scanning for
      the X-Spam-Flag header.

      Headers are stored because, until they have been fully scanned,
      we don't know whether this message will be delivered or
      discarded.
   */
   while (!head_end && (freply = fgets(line, BUFFINC, stdin) != NULL))
   {
      line_no++;
      line_size = strlen(line);
      if (flags[DEBUG])
         fprintf(stderr, "%3d (%4d b):%s", line_no, line_size, line);

      while (header_used + line_size + 1 > header_size)
      {
         if (flags[DEBUG] > 1)
            fprintf(stderr,
                    "***** header store is %d bytes, %d required, now %d\n",
                    header_size,
                    header_used + line_size,
                    header_size + BUFFINC);

         headers = expand_space(headers, header_size + BUFFINC + 1);
         header_size += BUFFINC;
      }

      if (strcmp(line, "\r\n") == 0 || strcmp(line, "\n") == 0)
      {
         head_end = TRUE;
         if (flags[DEBUG] > 1)
            fprintf(stderr,
                    "***** end of header%s\n",
                    (spam_found ? " - SPAM" : ""));
      }
 
      strcat(headers, line);
      header_used += line_size;
      if (is_flag(X_SPAM_FLAG, line, flags[DEBUG]))
         spam_found = TRUE;
         
      if (header_matches(SUBJECT, line, flags[DEBUG]))
         store_value(&subject, line, flags[DEBUG]);

      if (header_matches(SENDER, line, flags[DEBUG]))
         store_value(&sender, line, flags[DEBUG]);
         
      if (isalpha(line[0]))
         status_found = is_flag(X_SPAM_STATUS, line, flags[DEBUG]);
         
      if (status_found)
         status_found = store_rules(&rules, line, prefix, flags[DEBUG]);
   }

   if (freply == 0 && errno != 0)
      abandon(errno, strerror(errno), ": reading headers");

   /*
      Check whether the message is spam
      and deal with it appropriately if it is.
   */
   if (spam_found)
   {
      if (flags[QUARANTINE])
      {
         /*
            The message is spam to be quarantined.
            Open a file in the quarantine area.
            Write the headers to the file.
            Copy the rest of the message to the file and close it.

            Start by making the quarantine directory in the login directory
            of the user we-re running under.
         */

         freply = mkdir(qdir,
                        S_IRUSR | S_IWUSR | S_IXUSR |
                        S_IRGRP | S_IXGRP |
                        S_IROTH | S_IXOTH );
         if (freply == -1)
         {
            if (errno != EEXIST)
               abandon(errno,
                       strerror(errno),
                       append_filename(buff, MAXLINE,
                                       ": can't find or create",
                                       qdir));
         }

         /*
	    Now open a file to write the spam to hold the quarantined spam.
         */
         qfile = open_quarantine_file(qdir, qname, qtestname, flags[DEBUG]);
         if (qfile)
         {
            /*
               Write out the headers.
            */
            if (fputs(headers, qfile) == EOF)
               abandon(errno,
                       strerror(errno),
                       append_filename(buff, MAXLINE,
                                       "writing headers to ",
                                       qname));

            /*
               Copy out the message body.
            */         
            while (freply = (fgets(line, BUFFINC, stdin) != 0))
            {
               if (flags[DEBUG])
               {
                  line_no++;
                  line_size = strlen(line);
                  fprintf(stderr, "%3d (%4d b):%s", line_no, line_size, line);
               }
               
               fprintf(qfile, "%s", line);
            }

            if (ferror(stdin))
               abandon(errno, strerror(errno), "reading spam to quarantine it");

            if (ferror(qfile))
               abandon(errno,
                       strerror(errno),
                       append_filename(buff, MAXLINE,
                                       "writing spam to ",
                                       qname));

            close_quarantine_file(qfile);
         }
         else
         {
            if (flags[DEBUG])
               fprintf(stderr, "Failed to open %s: %s:%s\n",
                               qname,
                               strerror(errno),
                               "discarding spam message");
                                           
            log_message(errno,
                        strerror(errno),
                        ": discarding spam message");

            flags[QUARANTINE] = FALSE;
         }
      }
   
      if (!flags[QUARANTINE])
      {
         /*
            The message is spam to be disposed of.
            Eat the rest of the message.

            NOTE: This can trigger if the spam was meant to be
            quarantined but a quarantine file can't be opened.
            If this is the case, flags[QUARANTINE] will have
            been unset by the quarantining code.
         */   
         while (freply = (fgets(line, BUFFINC, stdin) != NULL))
         {
            if (flags[DEBUG])
            {
               line_no++;
               line_size = strlen(line);
               fprintf(stderr, "%3d (%4d b):%s", line_no, line_size, line);
            }
         }

         if (freply == 0 && errno != 0)
            abandon(errno,
                    strerror(errno),
                    ": reading and discarding spam message body");
      }

      /*
         Log the spamminess of the message.
      */
      show_classification(spam_found,
                          flags[QUARANTINE],
                          qname,
                          subject,
                          sender,
                          rules,
                          command,
                          flags[DEBUG],
                          flags[RULES]);

      /*
         Tidy up and exit, reporting the reason as needed.
         Exit with a zero termination status so that the pipeline's
         parent thinks that the mail was delivered.

         NOTE: This code is separated from the spam quarantine/discard
         logic because under some  
      */
      tidy_up(headers, subject, sender, rules);
      if (flags[DEBUG])
         fprintf(stderr,
                 "========== End of spamkiller: spam discarded (reply=%d)\n",
                 reply);
      
      return(reply);
   }

   /*
      The message is not spam.
      Open a pipeline to the delivery agent.
      Write the headers to the pipeline.
   */
   fwd = popen(command, "w");
   if (!fwd)
      abandon(errno, strerror(errno), ": can't start delivery agent");
      
   if (fputs(headers, fwd) == EOF)
      abandon(errno,
              strerror(errno),
              ": sending headers to delivery agent");
              
   /*
      Copy the message body. This is copied directly to the delivery
      agent because its potentially a lot bigger than the headers
      and there's no reason to store it temporarily.
   */
   while ((freply = fgets(line, BUFFINC, stdin) != NULL))
   {
      line_no++;
      line_size = strlen(line);
      if (flags[DEBUG])
         fprintf(stderr, "%3d (%4d b):%s", line_no, line_size, line);

      if (fputs(line, fwd) == EOF)
         abandon(errno,
                 strerror(errno),
                 ": sending message body to delivery agent");
   }

   if (freply == 0 && errno != 0)
      abandon(errno, strerror(errno), ": reading message body for delivery");
      
   /*
      Close the pipeline and wait while the delivery agent does its thing.
   */
   reply = pclose(fwd);
   if (flags[DEBUG] > 1)
      fprintf(stderr,
              "***** pclose returned %d - %s\n",
              reply,
              strerror(errno));
      
   if (reply < 0)
      abandon(errno, strerror(errno), ": delivery agent error");

   /*
      Log the spamminess of the message.
   */
   show_classification(spam_found,
                       flags[QUARANTINE],
                       qname,
                       subject,
                       sender,
                       rules,
                       command,
                       flags[DEBUG],
                       flags[RULES]);

   /*
      Tidy up and exit, report the reason as needed.
   */
   tidy_up(headers, subject, sender, rules);
   if (flags[DEBUG])
      fprintf(stderr, "========== End of spamkiller (reply=%d)\n", reply);

   return(reply);
}

/*
   abandon() - Abandon the run with a one or two part error message
   =========   after ensuring that errno is set and writing an entry
               to the mail log system.
*/
void  abandon(int err, char *msg1, char *msg2)
{
   errno = err;
   log_message(err, msg1, msg2);
   error(progname(), msg1, msg2);
}

/*
   append_filename() - Assemble a line with the format 'caption: name'
   =================   and return a pointer to it.
*/
char *append_filename(char *buff, int bufflen, char *caption, char* name)
{
   snprintf(buff, bufflen, "%s: %s", caption, name);
   return buff;
}
	 
/*
   check_command() - Check that the command doesn't contain junk.
   ===============
*/
void check_command(char *command, int verbose, int debug)
{
   unsigned char  c;
   int   i;
   int   n = strlen(command);
   int   bad = 0;

   for (i = 0; i < n; i++)
   {
      c = command[i];
      if (c < 0x20 || c > 0x7E)
         bad++;
   }

   if (bad)
   {
      if (debug)
      {
         fprintf(stderr,
                 "Error: command [%s] %s\n",
                 command,
                 "contains invalid characters");
      }
      else
      {
         openlog(progname(), LOG_ODELAY, LOG_MAIL);
         syslog(LOG_MAIL | LOG_ERR,
                "Error: command [%s] %s\n",
                command,
                "contains invalid characters");
         closelog();
      }
   }
   else
   if (verbose)
   {
      openlog(progname(), LOG_ODELAY, LOG_MAIL);
      syslog(LOG_MAIL | LOG_ERR,
             "version %s received [%s]\n",
             VERSION,
             command);
      closelog();
   }

   if (debug)
      fprintf(stderr,
              "check_command([%s], &d, %d) - badcount=%d\n",
              command,
              verbose,
              debug,
              bad);
} 

/*
   header_matches() - Recognise headers that match 'key'. Returns TRUE
   ================   if a matching header is found.
*/
int header_matches(char *key, char *line, int debug)
{
   int   n = 0;
   int   hit = TRUE;
   int   i;

   n = strlen(key);
   if (n > strlen(line))
   {
      hit = FALSE;
   }
   else
   { 
      for (i = 0; i < n; i++)
      {
         if (line[i] != key[i])
         {
            hit = FALSE;
            break;
         }
      }
   }

   if (debug > 1 && hit)
      fprintf(stderr, "***** %s\n", key);

   return hit;
}

/*
   is_flag() - Recognise headers that match marker.
   =========   Returns TRUE if a match is found.
*/
int is_flag(char *marker, char *line, int debug)
{
   int   n = 0;
   int   hit = TRUE;
   int   i;

   n = strlen(marker);
   if (n > strlen(line))
   {
      hit = FALSE;
   }
   else
   {
      for (i = 0; i < n; i++)
      {
         if (line[i] != marker[i])
         {
            hit = FALSE;
            break;
         }
      }
   }

   if (debug > 1 && hit)
      fprintf(stderr, "***** %s\n", marker);

   return hit;
}

/*
   log_message() - Write a message to the system mail log.
   =============
*/
void log_message(int err, char *msg1, char *msg2)
{
   openlog(progname(), LOG_ODELAY, LOG_MAIL);
   syslog(LOG_MAIL | LOG_ERR, "errno=%d, %s %s\n", err, msg1, msg2);
   closelog();
}

/*
   open_quarantine_file() - Open a file to accept quarantined spam.
   ======================   The pathname is yyyy-mm-dd_hh:mm:ss
                            prefixed with the quarantine directory name.
                            If this file already exists we try appending
                            'a' thru 'z' to the name. If this succeeds,
                            the file reference is returned. If it fails,
                            NULL is returned.
*/
FILE* open_quarantine_file(char *qdir, char *name, char *testname, int debug)
{
   int         i;
   time_t      clock;
   struct tm   *qtime;
   char        suffix;
   FILE*       f;
    
   clock = time(NULL);
   qtime = localtime(&clock);
   if (testname == NULL || strlen(testname) == 0)
   {
      for (i = 0; i < 27 && f == NULL; i++)
      {
         if (i > 0)
         {
            suffix = 'a' + i -1;
            snprintf(name,
                    MAXNAME,
                    "%s/%04d-%02d-%02d_%02d:%02d:%02d%c",
                    qdir,
                    qtime->tm_year + 1900,
                    qtime->tm_mon + 1,
                    qtime->tm_mday,
                    qtime->tm_hour,
                    qtime->tm_min,
                    qtime->tm_sec,
                    suffix);
         }
         else
         {
            snprintf(name,
                    MAXNAME,
                    "%s/%04d-%02d-%02d_%02d:%02d:%02d",
                    qdir,
                    qtime->tm_year + 1900,
                    qtime->tm_mon + 1,
                    qtime->tm_mday,
                    qtime->tm_hour,
                    qtime->tm_min,
                    qtime->tm_sec);
         }
         
         f = fopen(name,"w");            
      }

      if (debug)
         fprintf(stderr, "Quarantining as %s\n", name);
   }
   else
   {
      snprintf(name, MAXNAME, "%s/%s", qdir, testname);
      if (debug)
         fprintf(stderr, "Quarantining as %s\n", name);

      f = fopen(name,"w");            
   }
   
   if (debug && !f)
      fprintf(stderr, "Quarantine file %s NOT opened\n", name);
      
   return f;
}

/*
   close_quarantine_file() - Close the quarantine file.
   =======================
*/
void close_quarantine_file(FILE* f)
{
   fclose(f);
}

/*
   show_classification() - Log the sender and subject before logging
   =====================   whether the message will be passed to the
                           MTA or discarded. Logging is to the mail
                           logging system.
*/
void show_classification(int spam,
                         int quarantined,
                         char *qname,
                         char *subject,
                         char *sender,
                         char *rules,
                         char *command,
                         int debug,
                         int show_rules)
{
   if (debug)
   {
      if (subject == NULL)
         fprintf(stderr, "Subject: is NULL\n");
      else
         fprintf(stderr, "Subject:  %s\n", subject);

      if (sender == NULL)
         fprintf(stderr, "From: is NULL\n");
      else
         fprintf(stderr, "From:     %s\n", sender);

      if (show_rules && strlen(rules) > 0)
         fprintf(stderr, "Rules:    %s\n", rules);

      if (spam)
      {
         if (quarantined)
            fprintf(stderr, "Quarantined as %s\n", qname);
         else
            fprintf(stderr, "Discarded\n");
      }
      else
         fprintf(stderr, "Passed to '%s'\n", command);
   }
   else
   {
      openlog(progname(), LOG_ODELAY, LOG_MAIL);
      if (subject != NULL)
         syslog(LOG_MAIL | LOG_ERR, "Subject:  %s\n", subject);

      if (sender != NULL)
         syslog(LOG_MAIL | LOG_ERR, "From:     %s\n", sender);

      if (show_rules && strlen(rules) > 0)
         syslog(LOG_MAIL | LOG_ERR, "Rules:    %s\n", rules);

      if (spam)
      {
         if (quarantined)
            syslog(LOG_MAIL | LOG_ERR, "Quarantined as %s\n", qname);
         else
            syslog(LOG_MAIL | LOG_ERR, "Discarded\n");
      }
      else
         syslog(LOG_MAIL | LOG_ERR, "Passed to '%s'\n", command);
      closelog();
   }
}

/*
   store_rules() - Store the rules list from the header in 'line'
   =============   in 'buffer'. If 'prefix' isn't empty, only retain
                   rules starting with it.
                   Returns TRUE if this is not the last line in the
                   X-Spam-Status header and false if it is the last line. 
*/
int store_rules(char **buffer, char *line, char *prefix, int debug)
{
   char  *copy;
   char  *start;
   char  *end;
   int   i;
   int   n;
   int   scanning;
   int   wanted; 
   char  *x;
   int   bufflen;

   if (debug > 2)
      fprintf(stderr, "store_rules('%s','%s','%s',%d)\n",
                      *buffer, line, prefix, debug);

   /*
      Make a copy of the line because it will get damaged
   */
   n = strlen(line) - 1;
   line[n] = 0;
   copy = get_space(n + 1);
   strcpy(copy, line);

   /*
      The rules list starts with 'tests=' and ends with ' autolearn'
   */
   start = strstr(copy, "tests=");
   if (start)
   {
      start += 6; /* Start scanning after 'tests=' */
   }
   else
   {
      i = 0;      /* continuation line: skip whitespace */
      while ((copy[i] == ' ' || copy[i] == '\t') && i < n)
         i++;

      start = copy + i;
   }

   end = strstr(start, "autolearn");
   if (end)
   {
      scanning = FALSE;
      *end = 0;
   }
   else
      scanning = TRUE;
   
   if (debug > 2)   
      fprintf(stderr,
              "scanning rules '%s' %s\n",
              start,
              (scanning ? "not last line" : "final line"));

   while (x = strsep(&start, ", "))
   {
      if (strlen(x) == 0)
         break;
         
      if (strlen(prefix) == 0)
         wanted == TRUE;
      else
         wanted = (strstr(x, prefix) == x);

      if (debug > 2)   
         fprintf(stderr, "wanted=%d  rule='%s'\n", wanted, x);

      if (wanted)
      {
         bufflen = strlen(*buffer) + strlen(x) + 2;
         *buffer = expand_space(*buffer, bufflen);
         strncat(*buffer, x, bufflen);
         strncat(*buffer, ",", bufflen);
         if (debug > 2)
            fprintf(stderr, "Wanted rules '%s'\n", *buffer);
      }
   }
   
   if (!scanning)
   {
      n = strlen(*buffer)-1;
      x = *buffer;
      x[n] = 0;
   }
   
   if (debug > 1)
      fprintf(stderr,
              "%d = store_rules('%s','%s','%s')\n",
              scanning,
              *buffer,
              line,
              prefix);

   return scanning;
}

/*
   store_value() - Store the value from the header in 'line'
   =============   in 'buffer'.
*/
void store_value(char **buffer, char *line, int debug)
{
   char *x;
   int i;
   int n = strlen(line);

   /*
      Find the ':' at the end of the header name and
      then skip the following whitespace.
   */ 
   for (i = 0; i < n; i++)
   {
      if (line[i] == ':')
      {
         i++;
         break;
      }
   }

   while ((line[i] == ' ' || line[i] == '\t') && i < n)
      i++;

   /*
      Remove trailing newline.
   */
   while ((line[n -1] == '\n' || line[n-1] == '\r') && n > 0 )
   {
      line[n-1] = 0;
      n--;
   }

   /*
      Get space to store the value, copy it into the buffer
      and return a pointer to it.
   */
   x = &line[i];
   if (debug > 2)
      fprintf(stderr, "Found header value '%s' at %d\n", x, i);

   n = n - i;

   *buffer = get_space(n + 1);
   strncpy(*buffer, x, n);
   if (debug > 1)
      fprintf(stderr, "store_value('%s', '%s')\n", *buffer, line);
}

/*
   tidy_up - Tidy up by releasing memory.
   =======
*/   
void tidy_up(char *headers, char *subject, char *sender, char *rules)
{
   release_space(headers);
   if (subject)
      release_space(subject);
      
   if (sender)
      release_space(sender);
      
   if (rules)
      release_space(rules);
} 
