/* Copyright 1998, Merit Network, Inc. and the University of Michigan */
/* $Id: pgpchk.c,v 1.18 2002/10/17 20:16:14 ljb Exp $*/

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <regex.h>
#include <stdlib.h>
#include <errno.h>

#include <irrauth.h>
#include <pgp.h>

/* local yokel's */
static void dump_pgpv_output (trace_t *, char *, char *, char *, FILE *);
static long detached_fcopy   (trace_t *, FILE *, char *);

/* Decode the PGP file, and copy to the output stream any cookies */

/* PGP verify a user submission file.  
 *
 * If there is no pgp signature then the function defaults to
 * a pass through function.  This function can process both
 * regular signatures and detached signatures.  It will not
 * process multiple detached signatures.
 *
 * The function will filter out the pgp parts of the submission 
 * (ie, the '-----BEGIN PGP ...', etc....).  If the submission
 * was signed properly then the signed submission objects are
 * appended with auth cookies.  The auth cookies are picked
 * up later by the parser and an entry in the pipeline header is
 * made.  Then the auth checking module uses the pipeline header
 * to perform it's check.  The cookie information is the hex ID
 * of the verified signer.  The auth checking function will compare
 * the hex ID of the signer with a corresponding 'auth: PGPKEY-...'
 * line of a maintainer.
 *
 * If the pgp submission was not signed properly or the signature 
 * was not registered in the local rings, the function will not
 * add the auth cookies.
 *
 * It is important for users to realize that a 'key-cert' object 
 * must be registered in the DB and a corresponding 'auth: PGPKEY-...'
 * attr in their maintainer must exist for their pgp submissions 
 * to work.
 *
 * Input:
 *   -an intial user input file, can be telent or email file (infile)
 *   -an output file to write the processed submission to (outfn)
 *   -a log file pointer to make a carbon copy of the submission (log_fd)
 *   -a fully qualified path to the pgp rings directory (pgp_dir)
 *
 * Return:
 *   -0 if the submission file was processed without any unexpected errors
 *    (eg, disk error, file error, ...) 
 *   --1 otherwise
 */   
int pgpdecodefile_new (FILE *infile, char *outfn, FILE *log_fd, 
		       char *pgp_dir, trace_t *tr) {
  int n, fd, inpgpsig = 0;
  long fpos;
  enum PGPKEY_TYPE sig_type = NO_SIG;
  pgp_data_t pdat;
  regex_t pgpbegre, pgpendre, cookie_re, pgpbegdet_re;
  char pgpinfn[256], pgpoutfn[256], pgpcookie[128];
  char curline[MAXLINE + 8], *tmp;
  FILE *pipeline_file, *pgpinfile = NULL;

  /* open a pipeline file to place our output */
  pipeline_file = myfopen(tr, outfn, "w+", "pgpdecodefile(): output file");
  if (pipeline_file == NULL)
    return (-1);
 
  /* compile our regex's */
  regcomp (&pgpbegre,     pgpbegin,  REG_EXTENDED|REG_NOSUB);
  regcomp (&pgpendre,     pgpend,    REG_EXTENDED|REG_NOSUB);
  regcomp (&cookie_re,    cookie,    REG_EXTENDED|REG_NOSUB);
  regcomp (&pgpbegdet_re, pgpbegdet, REG_EXTENDED|REG_NOSUB);

  /* process the user submission file */
  while (fgets (curline, MAXLINE + 6, infile) != NULL) {
    /* get rid of those nasty '\r' char's which appear on telnet connections */
    if ((tmp = strchr (curline, '\r')) != NULL) {
      *tmp   = '\n';
      *++tmp = '\0';
    }

    /* telent/inetd connection quit */
    if (!strcmp (curline, "!q\n") || 
	!strcmp (curline, "!q"))
      break;
    
    /* log a carbon copy of the intial user submision */
    if (log_fd != NULL)
      fputs (curline, log_fd);

    /* line too long truncation */
    if (strlen (curline) >= MAXLINE) {
      trace (ERROR, tr, "pgpdecodefile () Line too long! Truncating...\n");
      curline[MAXLINE]     = '\0';
      curline[MAXLINE - 1] = '\n';
      
      /* Throw away the rest of the line */
      while ((n = fgetc (infile)) != EOF && 
	     n != (int) "\n");
    }
    
    /* If we don't have pgp installed then don't look for a signature */
#ifdef PGP
    if (!inpgpsig) {
      if (regexec (&pgpbegre, curline, 0, 0, 0) == 0) {
	sig_type = REGULAR_SIG;
	inpgpsig = 1;
      }
      else if (regexec (&pgpbegdet_re, curline, 0, 0, 0) == 0) {
	sig_type = DETACHED_SIG;
	inpgpsig = 1;
      }
    }
#endif

    switch (inpgpsig) {
 
      case 0: /* Normal, copy to pipeline output file mode, ie, no signature
	       * found yet */
	/* filter out 'COOKIE...' lines if the user supplies any 
	 * (you know those tricky users!) */
	if (regexec (&cookie_re, curline, 0, 0, 0) != 0)
	  fputs (curline, pipeline_file);
        break;

      case 1: /* got a PGP beginning line... */
	/* create a file name to put a copy of the regular signature
	 * and enclosed text or detached signature, file will be used as 
	 * input by pgpv */

	pgpinfile = myfopen(tr, pgpinfn, "w", "pgpdecodefile(): pgpinfile");
        if (pgpinfile == NULL)  {
        strcpy (pgpinfn, tmpfntmpl);
          fclose(pipeline_file);
          unlink(outfn);
          return (-1);
        }

	inpgpsig = 2;
	fputs (curline, pgpinfile);
        break;

      case 2: /* in the middle of a PGP regular sig or detached sig, 
	       * dump to file */
        fputs (curline, pgpinfile);

	/* found the end of the signature  */
        if (regexec (&pgpendre, curline, 0, 0, 0) == 0) {

	  /* close the file copy of the regular signature and enclosed text 
	   * or deteached signature */
	  fclose (pgpinfile);
	  pgpinfile = NULL;

	  /* create a temp file name for the pgpv output */
          strcpy (pgpoutfn, tmpfntmpl);
          fd = mkstemp (pgpoutfn);
          if (fd == -1) {
            trace (ERROR, tr, "pgpdecodefile: create pgp out tempfile error : %s.\n", 
	       strerror (errno));
            fclose(pipeline_file);
            unlink(outfn);
            return (-1);
          }
	  close(fd); /* pgp decode function will reopen the file */

	  /* regular signature processing */
	  if (sig_type == REGULAR_SIG) {
	    trace (NORM, tr, "pgpdecodefile_new (): process reg sig...\n");
	    /* verify the submission */
	    if (pgp_verify_regular (tr, pgp_dir, pgpinfn, pgpoutfn, &pdat))
	      sprintf (pgpcookie, "%s%s\n", PGP_KEY, pdat.hex_first->key);
	    /* file was not signed properly */
	    else 
	      pgpcookie[0] = '\0'; 
	    
	    /* dump the pgp verify output, adding our auth cookies to each
	     * object and placing the stream onto the pipeline file */
	    dump_pgpv_output (tr, pgpinfn, pgpoutfn, pgpcookie, pipeline_file);
	  }
	  /* detached signature processing */
	  else {
	    trace (NORM, tr, "pgpdecodefile_new (): process detached sig...\n");
	    /* grab the submission objects to file (pgpoutfn), omit an email
	     * header if present, then verify with the sig file (pgpinfn) */
	    if ((fpos = detached_fcopy (tr, pipeline_file, pgpoutfn)) >= 0 &&
		pgp_verify_detached (tr, pgp_dir, pgpoutfn, pgpinfn, &pdat)) {
	      /* move the filepointer to the beginning of objects and rewrite
	       * them with 'cookies' appended */
	      sprintf (pgpcookie, "%s%s\n", PGP_KEY, pdat.hex_first->key);
	      fseek (pipeline_file, fpos, SEEK_SET);
	      dump_pgpv_output (tr, NULL, pgpoutfn, pgpcookie, pipeline_file);
	    }
	  }

	  /* clean up our temp files */
	  unlink (pgpinfn);
	  unlink (pgpoutfn);

	  inpgpsig  = 0;
        }
        break;

    } /* switch */
  } /* while !done */

  /* check for a mal-formed/truncated pgp submission, ie, we didn't find
   * the end of the signature */
  if (inpgpsig &&
      pgpinfile != NULL) {
    /* rewind */
    fseek (pgpinfile, 0L, SEEK_END);

    /* copy the truncated pgp submission to the pipeline output file */
    while (fread (curline, 1, MAXLINE, pgpinfile))
      fwrite (curline, 1, MAXLINE, pipeline_file);
    fclose (pgpinfile);
  }

  /* clean up */
  regfree (&pgpbegre);
  regfree (&pgpendre);
  regfree (&cookie_re);
  regfree (&pgpbegdet_re);
  fclose  (pipeline_file);

  return 0;
}

/* Copy the pgpv outfile with the signature removed to the pipeline
 * output file.  And if the signature is good then add cookies
 * to the pipeline stream with the hex ID of the signer.  The cookies
 * are appended to 'source:' attributes.
 *
 * Input:
 *   -fully qualified file name of copy of the regular signature
 *    and enclosed text (origfn)
 *   -fully qualified file name of the output from pgpv. (pgpvfn)
 *    ie, the regular signature stripped away.
 *   -an auth cookie string line to be appended to each object (auth_cookie)
 *   -a streams point to the pipeline output file (pipeline_file)
 *
 * Return:
 *   void
 *
 *   the orignal submission with the pgp signature stripped away and
 *   auth cookies added are dumped to the (pipeline_file) file stream.
 *
 *   routine also filters out 'cookie' lines the user might have in
 *   the submission (you know those tricky user :)
 */
void dump_pgpv_output (trace_t *tr, char *origfn, char *pgpvfn, 
		       char *auth_cookie, FILE *pipeline_file) {
  int io_errors = 0;
  regex_t cookie_re, cookieins_re;
  char curline[MAXLINE];
  FILE *pgpvfile;

  trace (NORM, tr, "dump_pgpv_output (): enter...\n");

  /* open the pgpv outfile with the signature removed ... */
  if ((pgpvfile = fopen (pgpvfn, "r")) == NULL) {
    trace (ERROR, tr, 
	   "dump_pgpv_output () open pgpv error (%s): %s\n", pgpvfn, strerror (errno));

    /* try using the initial file.  this will cause some syntax
     * errors but the submission object will be appeded with 
     * the cookies */
    if ((pgpvfile = fopen (origfn, "r")) == NULL) {
     trace (ERROR, tr, 
	   "dump_pgpv_output () We are hosed!  user file error (%s)\n", origfn); 
     exit (0);
    }

    io_errors = 1;
  }

  /* compile our regex's */
  regcomp (&cookie_re,    cookie,    REG_EXTENDED|REG_NOSUB);
  regcomp (&cookieins_re, cookieins, REG_EXTENDED|REG_NOSUB|REG_ICASE);

  /* ... copy it to our pipeline outfile */
  while (fgets (curline, MAXLINE, pgpvfile) != NULL) {
    /* strip out any 'COOKIE...' lines from the users submission */
    if (regexec(&cookie_re, curline, 0, 0, 0) == 0)
      continue;
    
    /* dump the user lines to the pipeline file */
    fputs (curline, pipeline_file);    

    /* append our cookie lines when we see a 'source' line.
     * the cookie line causes the parser to make a pipeline
     * header field of the hex ID of the signer.  Then the auth
     * routine will use the hex ID header field for it's checking */
    if (!io_errors   &&
	*auth_cookie &&
	regexec (&cookieins_re, curline, 0, 0, 0) == 0) {
      
      /* make sure our cookie does not concat with the prior line */
      if (curline[strlen (curline) - 1] != '\n')
	fputs ("\n", pipeline_file);
      
      /* append our authorization cookie line */
      fputs (auth_cookie, pipeline_file);
    }
  }
  
  /* make sure there is a blank line seperator */
  fputs("\n", pipeline_file);
  
  trace (NORM, tr, "dump_pgpv_output (): io_errors (%d) exit...\n", io_errors);

  fclose  (pgpvfile);
  regfree (&cookie_re);
  regfree (&cookieins_re);
}

/* Given a file, extract all objects from it and write them
 * to (outfn).  There maybe an email header in (infile) which should 
 * be filtered out.  Also return the file position of the start of submission
 * objects in (infile).
 *
 * This function is used in detached pgp submission processing.  It's purpose 
 * is to pull the submission objects (minus any email header) from the 
 * pipeline file to a seperate file.  Then this file (outfn) can be verified 
 * using the associated signature file.  The function return value (fpos) 
 * tells the calling routine where the submission objects begin.  Then the 
 * calling routine can overwrite the objects with auth cookies inserted to 
 * the output pipeline file.
 *
 * Input:
 *   -an initial submission file (infile)
 *   -a file to write the output to (outfn)
 *    (outfn) and (infn) may turn out to be identical.
 *    (outfn) is guaranteed to have email header information
 *    stripped away.
 *
 * Return:
 *   -a file position value in (infile) of the start of submission objects (fpos)
 *   --1 otherwise
 */
long detached_fcopy (trace_t *tr, FILE *infile, char *outfn) {
  FILE *fout;
  long fpos = -1;
  int first_line = 1, in_headers = 1;
  char curline[MAXLINE];
  regex_t blankline_re, mailfrom_re;
  char *blankline  = "^[ \t]*[\r]?[\n]?$";
  char *mailfrom   = "^From[ \t]";

  /* need to copy the initial file */
  if ((fout = fopen (outfn, "w")) == NULL) {
    trace (ERROR, tr, "detached_fcopy () Output file open error (%s):(%s)\n", 
	   outfn, strerror (errno)); 
    return -1;
  }
  
  /* compile our regex */
  regcomp (&blankline_re, blankline, REG_EXTENDED|REG_NOSUB);
  regcomp (&mailfrom_re,  mailfrom,  REG_EXTENDED|REG_NOSUB);

  /* rewind */
  fseek (infile, 0L, SEEK_SET);

  /* loop through the input file (infile) and write the objects submission
   * minus the email header (if present) to the output file (outfn) */
  while (fgets (curline, MAXLINE - 1, infile) != NULL) {
    if (first_line && regexec (&mailfrom_re, curline, 0, 0, 0) != 0) {
      in_headers = 0;

      /* we have a telnet submission */
      fpos = 0;
    }
    
    first_line = 0;
    
    /* skip email header */
    if (in_headers) {
      /* wait till we come out of the email header and get
       * the file postion.  note that the first blank line
       * after the email header is not considered part of
       * the submission */
      if (!(in_headers = regexec (&blankline_re, curline, 0, 0, 0)))
	/* we have an email submission */
	fpos = ftell (infile);
    }
    /* write a line to the outfile */
    else
      fputs (curline, fout);
  }
  
  fclose (fout);

  return fpos;
}


syntax highlighted by Code2HTML, v. 0.9.1