/*  pgpdaemon.c

    Source file for  PGPdaemon  (automatic signer/encrpytor for PGP).

    Copyright (C) 1994  Richard Gooch

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    Richard Gooch may be reached by email at  rgooch@atnf.csiro.au
    The postal address is:
      Richard Gooch, c/o ATNF, P. O. Box 76, Epping, N.S.W., 2121, Australia.
*/

/*  This programme intercepts messages sent by user mail agents to the
    sendmail daemon and checks to see if messages can be encrypted using the
    recipient's PGP public keys.


    Written by      Richard Gooch   4-JUN-1994

    Updated by      Richard Gooch   5-JUN-1994

    Updated by      Richard Gooch   10-JUN-1994: Changed from using  stricmp
  to  st_icmp  ,since some platforms don't have  stricmp  .

    Updated by      Richard Gooch   20-JUN-1994: Added mailbox decrypting
  functionality.

    Updated by      Richard Gooch   21-JUN-1994: Added  #include <sys/types.h>

    Updated by      Richard Gooch   22-JUN-1994: Added IN_SPOOL_DIR  and
  OUT_SPOOL_FILE  keywords for config file.

    Updated by      Richard Gooch   23-JUN-1994: Added MAIL_CHECK_INTERVAL
  keyword for config file.

    Updated by      Richard Gooch   23-JUN-1994: Added  #ifdef _AIX  and made
  set_lockfile  a little more verbose if lockfiles can't be created.

    Updated by      Richard Gooch   27-JUN-1994: Moved  set_env  to
  spawn.c  and added  -detach  and  -pgppath  options.

    Updated by      Richard Gooch   28-JUN-1994: Improved logging information
  when processing mail.

    Updated by      Richard Gooch   1-JUL-1994: Used [-u userID] option to PGP
  when signing messages.

    Updated by      Richard Gooch   2-JUL-1994: Trap unterminated PGP messages.

    Updated by      Richard Gooch   2-JUL-1994: Truncate input mail spool
  rather than removing, since  sendmail  under OSF/1 will create the spoolfile
  even if the lockfiles exist, it just waits for the lockfiles to go before it
  starts writing to the spoolfile. This caused some messages to be lost, since
  the file  sendmail  would eventually write to was unlinked (ie. only
  sendmail  had a handle to the file it was writing to).

    Updated by      Richard Gooch   3-JUL-1994: Added some imformative
  messages when decryption of mail fails.

    Updated by      Richard Gooch   3-JUL-1994: Recover from unterminated PGP
  messages so that subsequent messages can be decrypted.

    Updated by      Richard Gooch   3-JUL-1994: Trap when PGP stops when
  decrypting mail. Usually because message encrypted "For-your-eyes-only".

    Updated by      Richard Gooch   4-JUL-1994: Added  #ifdef O_SYNC  and
  #ifdef __bsdi__  for MAIL_SPOOL_DIR  .

    Last updated by Richard Gooch   5-JUL-1994: Changed to use of  m_clear  .


*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#ifdef _AIX
#  include <sys/select.h>
#endif
#include <sys/socket.h>
#include <sys/un.h>
#include "pgpsendmail.h"

#if defined(sparc) || defined(linux)
#  define MAIL_SPOOL_DIR "/var/spool/mail"
#else
#  ifdef __bsdi__
#    define MAIL_SPOOL_DIR "/var/mail"
#  else
#    define MAIL_SPOOL_DIR "/usr/spool/mail"
#  endif
#endif

#define MIN_INTERVAL 1
#define DEFAULT_INTERVAL 15
#define MAX_INTERVAL 60

STATIC_FUNCTION (flag service_connect, (int sock) );
STATIC_FUNCTION (flag read_line, (int fd, char *buffer, unsigned int length) );
STATIC_FUNCTION (void sig_handler, () );
STATIC_FUNCTION (flag process_message, (int fd, flag sign) );
STATIC_FUNCTION (void myexit, (int code) );
STATIC_FUNCTION (flag set_lockfile, (char *filename, flag lock) );
STATIC_FUNCTION (flag set_fd_lock, (int fd, flag lock) );
STATIC_FUNCTION (void check_and_process_mail, () );
STATIC_FUNCTION (void read_config, (char *pgppath, flag *mailwait,
				    long *interval) );


/*  Private data  */
static char *pgppath = NULL;
static char passphrase[STRING_LENGTH];
static SECRING_SIZE_TYPE secring_bytes = 0;
static char *secring_buf = NULL;
static flag keep_going = TRUE;
static flag restart = FALSE;
static flag incoming_locked = FALSE;
static char incoming_spool[STRING_LENGTH];
static char outgoing_spool[STRING_LENGTH];
static char my_userid[STRING_LENGTH];


/*  Code for  myexit  must come first.  */

static void myexit (code)
/*  This routine will first scrub sensitive information prior to calling the
    exit(3) function.
    The exit code must be given by  code  .
*/
int code;
{
    extern SECRING_SIZE_TYPE secring_bytes;
    extern char passphrase[STRING_LENGTH];
    extern char *secring_buf;

    (void) fprintf (stderr, "Erasing sensitive information...\n");
    m_clear (passphrase, STRING_LENGTH);
    if (secring_bytes > 0) m_clear (secring_buf, secring_bytes);
    (void) sync ();
    exit (code);
#undef exit
#define exit ___illegal_use_of_exit___
}   /*  End Function myexit  */


/*  Now everything else may follow.  */

void main (argc, argv, envp)
int argc;
char *argv[];
char *envp[];
{
    flag mailwait = FALSE;
    flag login_session = TRUE;
    int ppid;
    int sock, fd;
    int tmp, count;
    long timeout_s = DEFAULT_INTERVAL;
    struct sockaddr_un un_addr;
    fd_set input_fds, output_fds, exception_fds;
    struct timeval timeout;
    struct sigaction new_action;
    char socket_filename[STRING_LENGTH];
    char txt[LINE_LENGTH];
    extern flag keep_going;
    extern flag restart;
    extern flag incoming_locked;
    extern SECRING_SIZE_TYPE secring_bytes;
    extern char *pgppath;
    extern char passphrase[STRING_LENGTH];
    ERRNO_TYPE errno;
    static char usage_string[] =
    "Usage:\tpgpdaemon [-mailwait] [-detach] [-pgppath <path>]";
    extern char *sys_errlist[];

    /*  Read arguments  */
    for (count = 1; count < argc; ++count)
    {
	if (strcmp (argv[count], "-mailwait") == 0) mailwait = TRUE;
	else if (strcmp (argv[count], "-detach") == 0) login_session = FALSE;
	else if (strcmp (argv[count], "-pgppath") == 0)
	{
	    if (++count >= argc)
	    {
		(void) fprintf (stderr, "%s\n", usage_string);
		myexit (RV_BAD_PARAM);
	    }
	    if (set_env ("PGPPATH", argv[count]) != 0)
	    {
		(void)fprintf (stderr,
			       "Error setting PGPPATH environment variable\n");
		myexit (RV_UNDEF_ERROR);
	    }
	}
	else
	{
	    (void) fprintf (stderr, "%s\n", usage_string);
	    myexit (RV_BAD_PARAM);
	}
    }
    if ( ( ppid = getppid () ) < 0 )
    {
	(void) fprintf (stderr, "Error getting parent process ID\t%s\n",
			sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
    if (ppid == 1)
    {
	/*  Init was parent!  */
	(void) fprintf (stderr,
			"Cannot operate with  init  as parent process\n");
	myexit (RV_UNDEF_ERROR);
    }
    if ( ( pgppath = getenv ("PGPPATH") ) == NULL )
    {
	(void) fprintf (stderr, "No PGPPATH environment variable\n");
	myexit (RV_UNDEF_ERROR);
    }
    (void) umask ( ~(S_IRUSR | S_IWUSR) );
    m_clear (passphrase, STRING_LENGTH);
/*
    (void) signal (SIGINT, sig_handler);
    (void) signal (SIGTERM, sig_handler);
    (void) signal (SIGHUP, sig_handler);
    (void) signal (SIGPIPE, sig_handler);
    (void) signal (SIGTTIN, sig_handler);
    (void) signal (SIGTTOU, sig_handler);
*/
    /*  Use  sigaction  instead because it should make the signals restart  */
    new_action.sa_handler = sig_handler;
    sigemptyset (&new_action.sa_mask);
    new_action.sa_flags = 0;
#ifdef SA_RESTART
    new_action.sa_flags |= SA_RESTART;
#endif
    sigaction (SIGINT, &new_action, (struct sigaction *) NULL);
    sigaction (SIGTERM, &new_action, (struct sigaction *) NULL);
    sigaction (SIGHUP, &new_action, (struct sigaction *) NULL);
    sigaction (SIGPIPE, &new_action, (struct sigaction *) NULL);
    sigaction (SIGTTIN, &new_action, (struct sigaction *) NULL);
    sigaction (SIGTTOU, &new_action, (struct sigaction *) NULL);
    if (set_env ("PGPPASSFD", "0") != 0)
    {
	(void) fprintf (stderr,
			"Error setting PGPPASSFD environment variable\n");
	myexit (RV_UNDEF_ERROR);
    }
    /*  Sanity checks for files the user should have  */
    (void) sprintf (txt, "%s/randseed.bin", pgppath);
    if (access (txt, R_OK) != 0)
    {
	(void) fprintf (stderr, "Could not find file: \"%s\"\t%s\n",
			txt, sys_errlist[errno]);
	myexit (RV_UNDEF_ERROR);
    }
    /*  Open logfile  */
    (void) sprintf (txt, "%s/PGPdaemon.log", pgppath);
    if ( ( fd = open (txt, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR) )
	< 0 )
    {
	(void) fprintf (stderr, "Error opening file: \"%s\"\t%s\n",
			txt, sys_errlist[errno]);
	myexit (RV_UNDEF_ERROR);
    }
    if (dup2 (fd, 1) == -1)
    {
	(void) fprintf (stderr, "Error dup2'ing\t%s\n", sys_errlist[errno]);
	myexit (RV_UNDEF_ERROR);
    }
    if (dup2 (fd, 2) == -1)
    {
	(void) fprintf (stderr, "Error dup2'ing\t%s\n", sys_errlist[errno]);
	myexit (RV_UNDEF_ERROR);
    }
    (void) fprintf (stderr,
		    "-----------------------------------------------------\n");
    (void) fprintf (stderr, "PGPdaemon %s: appending to logfile\n", VERSION);
    read_config (pgppath, &mailwait, &timeout_s);
    (void) sprintf (socket_filename, "%s/.pgpd.socket", pgppath);
    if (access (socket_filename, F_OK) == 0)
    {
	(void) fprintf (stderr, "File: \"%s\" exists:\n", socket_filename);
	(void) fprintf (stderr, "PGPdaemon did not clean up last time\n");
	myexit (RV_UNDEF_ERROR);
    }
    if (errno != ENOENT)
    {
	(void) fprintf (stderr, "Error testing for socket: \"%s\"\t%s\n",
			socket_filename, sys_errlist[errno]);
	myexit (RV_UNDEF_ERROR);
    }
    /*  I guess the socket doesn't exist: make it  */
    if ( ( sock = socket (AF_UNIX, SOCK_STREAM, 0) ) < 0 )
    {
	(void) fprintf (stderr, "Error creating Unix socket\t%s\n",
			sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
    un_addr.sun_family = AF_UNIX;
    (void) sprintf (un_addr.sun_path, "%s", socket_filename);
    /*  Try to bind to Unix port  */
    if (bind (sock, (struct sockaddr *) &un_addr, (int) sizeof un_addr) != 0)
    {
	/*  Could not bind to socket  */
	if (errno != EADDRINUSE)
	{
	    (void) fprintf (stderr, "Error binding Unix socket\t%s\n",
			    sys_errlist[errno]);
	    if (close (sock) != 0)
	    {
		(void) fprintf (stderr, "Error closing Unix socket\t%s\n",
				sys_errlist[errno]);
	    }
	    myexit (RV_SYS_ERROR);
	}
	/*  Socket already in use  */
	(void) fprintf (stderr, "Unix socket already in use\n");
	myexit (RV_UNDEF_ERROR);
    }
    /*  Bound  */
    if (chmod (socket_filename, S_IRUSR | S_IWUSR) != 0)
    {
	(void) fprintf (stderr, "Error changing mode of Unix socket\t%s\n",
			sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
    /*  Set close-on-exec flag  */
    if (fcntl (sock, F_SETFD, 1) == -1)
    {
	(void) fprintf (stderr,
			"Error setting close-on-exec flag for Unix socket\t%s\n",
			sys_errlist[errno]);
	if (close (sock) != 0)
	{
	    (void) fprintf (stderr, "Error closing Unix socket\t%s\n",
			    sys_errlist[errno]);
	}
	myexit (RV_SYS_ERROR);
    }
    if (listen (sock, 1) != 0)
    {
	(void) fprintf (stderr, "Error listening to Unix dock\t%s\n",
			sys_errlist[errno]);
	if (close (sock) != 0)
	{
	    (void) fprintf (stderr, "Error closing Unix socket\t%s\n",
			    sys_errlist[errno]);
	}
	myexit (RV_SYS_ERROR);
    }
    while (keep_going)
    {
	/*  Keep passphrase in RAM (hopefully out of swap) by touching it  */
	for (count = 0, tmp = 0; count < STRING_LENGTH; ++count)
	{
	    tmp += passphrase[count];
	}
	tmp = 0;
	if ( login_session && (getppid () != ppid) )
	{
	    keep_going = FALSE;
	    continue;
	}
	/*  Check and process incoming mail spool file  */
	if ( !mailwait || (secring_bytes > 0) ) check_and_process_mail ();
	/*  Wait for connections to socket  */
	timeout.tv_sec = timeout_s;
	timeout.tv_usec = 0;
	/*  Set up for  select(2)  */
	FD_ZERO (&input_fds);
	FD_ZERO (&output_fds);
	FD_ZERO (&exception_fds);
	FD_SET (sock, &input_fds);
	switch ( select (FD_SETSIZE, &input_fds, &output_fds, &exception_fds,
			 &timeout) )
	{
	  case 0:
	    /*  Timeout  */
	    continue;
	    break;
	  case -1:
	    if (errno == EINTR)
	    {
		continue;
	    }
	    /*  Failure  */
	    (void) fprintf (stderr, "Error calling  select(2)\t%s\n",
			    sys_errlist[errno]);
	    myexit (RV_SYS_ERROR);
	    break;
	  default:
	    /*  Success  */
	    (void) fprintf (stderr, "connect\n");
	    (void) service_connect (sock);
	    break;
	}
    }
    (void) fprintf (stderr, "Removing socket\n");
    (void) unlink (socket_filename);
    if (restart)
    {
	(void) fprintf (stderr, "Erasing sensitive information...\n");
	m_clear (passphrase, STRING_LENGTH);
	if (secring_bytes > 0) m_clear (secring_buf, secring_bytes);
	(void) sync ();
	(void) fprintf (stderr, "Restarting\n");
	(void) execvp (argv[0], argv);
	(void) fprintf (stderr, "Error calling  execvp(3)\t%s\n",
			sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
    myexit (RV_OK);
}   /*  End Function main  */

static flag service_connect (sock)
/*  This routine will service a connection to a socket.
    The socket must be given by  sock  .
    The routine returns TRUE on succes, else it returns FALSE.
*/
int sock;
{
    int un_addr_len;
    struct sockaddr_un un_addr;
    int fd;
    char command[STRING_LENGTH];
    extern SECRING_SIZE_TYPE secring_bytes;
    extern char passphrase[STRING_LENGTH];
    extern char *secring_buf;
    ERRNO_TYPE errno;
    extern char *sys_errlist[];

    un_addr_len = sizeof un_addr;
    (void) m_clear ( (char *) &un_addr, un_addr_len );
    /*  Listen for connections  */
    if ( ( fd = accept (sock, (struct sockaddr *) &un_addr, &un_addr_len) )
	< 0 )
    {
	(void) fprintf (stderr, "Error accepting unix connection\t%s\n",
			sys_errlist[errno]);
	return (FALSE);
    }
    if ( !read_line (fd, command, STRING_LENGTH) )
    {
	(void) close (fd);
	return (TRUE);
    }
    /*  Process command  */
    if (st_icmp (command, "passphrase") == 0)
    {
	if ( !read_line (fd, passphrase, STRING_LENGTH - 1) )
	{
	    (void) fprintf (stderr, "Error reading passphrase\t%s\n",
			    sys_errlist[errno]);
	    (void) close (fd);
	    return (FALSE);
	}
	passphrase[strlen (passphrase)] = '\n';
	if (secring_bytes > 0)
	{
	    m_clear (secring_buf, secring_bytes);
	    m_free (secring_buf);
	}
	secring_bytes = 0;
	secring_buf = NULL;
	if (read (fd, (char *) &secring_bytes, sizeof secring_bytes) <
	    sizeof secring_bytes)
	{
	    (void) fprintf (stderr,
			    "Error reading secret keyring length\t%s\n",
			    sys_errlist[errno]);
	    (void) close (fd);
	    myexit (RV_WRITE_ERROR);
	}
	if ( ( secring_buf = m_alloc (secring_bytes) ) == NULL )
	{
	    (void) fprintf (stderr, "Error allocating: %d bytes\n",
			    secring_bytes);
	    (void) close (fd);
	    myexit (RV_MEM_ERROR);
	}
	if (read (fd, secring_buf, secring_bytes) < secring_bytes)
	{
	    (void) fprintf (stderr, "Error reading secret keyring\t%s\n",
			    sys_errlist[errno]);
	    (void) close (fd);
	    myexit (RV_READ_ERROR);
	}
	(void) fprintf (stderr,
			"Passphrase and secret keyring (%d bytes) received\n",
			secring_bytes);
	(void) close (fd);
	return (TRUE);
    }
    if (st_icmp (command, "SIGN") == 0)
    {
	if ( !process_message (fd, TRUE) )
	{
	    (void) close (fd);
	    return (FALSE);
	}
	(void) close (fd);
	return (TRUE);
    }
    if (st_icmp (command, "DECRYPT") == 0)
    {
	if ( !process_message (fd, FALSE) )
	{
	    (void) close (fd);
	    return (FALSE);
	}
	(void) close (fd);
	return (TRUE);
    }
    (void) fprintf (stderr, "Illegal command: \"%s\"\n", command);
    (void) close (fd);
    return (FALSE);
}   /*  End Function service_connect  */

static flag read_line (fd, buffer, length)
/*  This routine will read a character string from a file into a buffer.
    The file descriptor must be given by  fd  .
    The buffer to write the data to must be pointed to by  buffer  .
    The routine will write a NULL terminator character at the end of the
    string.
    NOTE: the newline chanacter '\n' is NOT copied into the buffer.
    The length of the buffer must be given by  length  .If the buffer is not
    large enough to contain the string, then the remainder of the string is NOT
    read.
    The routine returns TRUE on success,
    else it returns FALSE (indicating end-of-file was encountered).
*/
int fd;
char *buffer;
unsigned int length;
{
    flag return_value = TRUE;
    flag another = TRUE;

    while (another)
    {
	if (read (fd, buffer, 1) < 1)
	{
	    /*  Error occurred  */
	    another = FALSE;
	    return_value = FALSE;
	    continue;
	}
	if (*buffer == '\n')
	{
	    another = FALSE;
	    continue;
	}
	++buffer;
	--length;
	if (length < 2)
	{
	    another = FALSE;
	}
    }
    /*  Write NULL terminator  */
    *buffer = '\0';
    return (return_value);
}   /*  End Function read_line  */

static void sig_handler (sig, code, scp, addr)
/*  This routine will handle signals. It sets the global flag  keep_going  to
    FALSE.
    The signal number will be given by  sig  .
    The routine returns nothing.
*/
int sig;
int code;
struct sigcontext *scp;
char *addr;
{
    extern flag keep_going;
    extern flag restart;

    switch (sig)
    {
      case SIGINT:
      case SIGTERM:
      case SIGPIPE:
	keep_going = FALSE;
	break;
      case SIGHUP:
	keep_going = FALSE;
	restart = TRUE;
	break;
      case SIGTTIN:
      case SIGTTOU:
	break;
      default:
	(void) fprintf (stderr, "Illegal signal: %d\n", sig);
	myexit (RV_UNDEF_ERROR);
	break;
    }
    /*  Install the signal handler again  */
/*
    (void) signal (sig, sig_handler);
*/
}   /*  End Function sig_handler  */

static flag process_message (fd, sign)
/*  This routine will process a message, either signing or decrypting it.
    The file descriptor from which the message should be read must be given by
    fd  .The processed message is written to this descriptor.
    If the value of  sign  is TRUE, the message is signed, else it is decrypted
    The routine returns TRUE on success, else it returns FALSE.
*/
int fd;
flag sign;
{
    int in_fd = -1;
    int out_fd = fd;
    int err_fd = ERROR_FD;
    int child;
    int status;
    int len;
    char *argv[7];
    extern char my_userid[STRING_LENGTH];
    extern char passphrase[STRING_LENGTH];
    ERRNO_TYPE errno;
    extern char *sys_errlist[];

    /*  Setup arguments to PGP  */
    if (sign)
    {
	/*  pgp -fsta +clearsig=on +batchmode  */
	argv[0] = "pgp";
	argv[1] = "-fsta";
	argv[2] = "+clearsig=on";
	argv[3] = "+batchmode";
	argv[4] = "-u";
	argv[5] = my_userid;
	argv[6] = NULL;
	/*  The following line is a hack so that PGPsendmail won't drop the
	    first line, which is the signed message delimiter.
	    Maybe one day I'll work out *why* PGP often (but not always!)
	    drops the first line of a message it encrypts.  */
	(void) write (fd, "\n", 1);
	(void) fprintf (stderr, "Request to sign a message\n");
    }
    else
    {
	/*  pgp -f  */
	argv[0] = "pgp";
	argv[1] = "-f";
	argv[2] = NULL;
	(void) fprintf (stderr, "Request to encrypt a message\n");
    }
    /*  Start PGP  */
    if ( ( child = spawn_job ("pgp", argv, &in_fd, &out_fd, &err_fd) ) < 0 )
    {
	(void) fprintf (stderr, "Error spawning PGP\n");
	return (FALSE);
    }
    /*  Send passphrase  */
    len = strlen (passphrase);
    if (write (in_fd, passphrase, len) < len)
    {
	(void) fprintf (stderr, "Error writing passphrase\t%s\n",
			sys_errlist[errno]);
	(void) close (in_fd);
	(void) waitpid (child, &status, 0);
	return (FALSE);
    }
    /*  Copy input message to PGP  */
    if ( !copy_data (in_fd, fd, TRUE) )
    {
	(void) fprintf (stderr, "Error writing data\t%s\n",
			sys_errlist[errno]);
	(void) close (in_fd);
	(void) waitpid (child, &status, 0);
	return (FALSE);
    }
    (void) close (in_fd);
    if (waitpid (child, &status, 0) < 0)
    {
	(void) fprintf (stderr, "Error reaping child\t%s\n",
			sys_errlist[errno]);
	return (FALSE);
    }
    if (status != 0)
    {
	(void) fprintf (stderr, "Bad child status: %d\n", status);
	return (FALSE);
    }
    return (TRUE);
}   /*  End Function process_message  */

static flag set_lockfile (filename, lock)
/*  This routine will create or delete a lockfile for a specified filename.
    The filename must be pointed to by  filename  .
    The routine creates or deletes the following lock files:
        <filename>.lock
	/tmp/<basename>.mlk
    where  basename  is the trailing path component of  filename  .
    The file will be created if the value of  lock  is TRUE, else it will be
    deleted.
    The routine returns TRUE on success, else it returns FALSE.
*/
char *filename;
flag lock;
{
    int fd;
    char *ptr;
    char lock_name[STRING_LENGTH];
    char mlk_name[STRING_LENGTH];
    ERRNO_TYPE errno;
    extern char *sys_errlist[];

    (void) strcpy (lock_name, filename);
    (void) strcat (lock_name, ".lock");
    ptr = strrchr (filename, '/');
    ptr = (ptr == NULL) ? filename : ptr + 1;
    (void) strcpy (mlk_name, "/tmp/");
    (void) strcat (mlk_name, ptr);
    (void) strcat (mlk_name, ".mlk");
    if (!lock)
    {
	if (unlink (lock_name) != 0)
	{
	    (void) fprintf (stderr, "Error removing file: \"%s\"\t%s\n",
			    lock_name, sys_errlist[errno]);
	    (void) unlink (mlk_name);
	    return (FALSE);
	}
	if (unlink (mlk_name) != 0)
	{
	    (void) fprintf (stderr, "Error removing file: \"%s\"\t%s\n",
			    mlk_name, sys_errlist[errno]);
	    return (FALSE);
	}
	return (TRUE);
    }
    if ( ( fd = open (lock_name, O_RDWR | O_CREAT | O_EXCL,
		      S_IRUSR | S_IWUSR) ) < 0 )
    {
	if (errno == EEXIST)
	{
	    (void) fprintf (stderr, "lockfile: \"%s\" exists\n", lock_name);
	    return (FALSE);
	}
	(void) fprintf (stderr, "Error creating file: \"%s\"\t%s\n",
			lock_name, sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
    (void) close (fd);
    if ( ( fd = open (mlk_name, O_RDWR | O_CREAT | O_EXCL,
		      S_IRUSR | S_IWUSR) ) < 0 )
    {
	(void) unlink (lock_name);
	if (errno == EEXIST)
	{
	    (void) fprintf (stderr, "lockfile: \"%s\" exists\n", mlk_name);
	    return (FALSE);
	}
	(void) fprintf (stderr, "Error creating file: \"%s\"\t%s\n",
			mlk_name, sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
    (void) close (fd);
    return (TRUE);
}   /*  End Function set_lockfile  */

static flag set_fd_lock (fd, lock)
/*  This routine will (un)lock a file associated with a file descriptor.
    The file descriptor must be given by  fd  .
    The file will be lock if the value of  lock  is TRUE, else it will be
    unlocked.
    The routine returns TRUE on success, else it returns FALSE.
*/
int fd;
flag lock;
{
    long pos;
#ifdef F_SETLKW
    struct flock fl;
#endif
    ERRNO_TYPE errno;
    extern char *sys_errlist[];

#undef LOCKING_WORKS
#ifdef F_SETLKW
#define LOCKING_WORKS
    fl.l_type = lock ? F_WRLCK : F_UNLCK;
    fl.l_whence = 0;
    fl.l_start = 0L;
    fl.l_len = 0L;
    if (fcntl (fd, F_SETLKW, &fl) == -1)
    {
	(void) fprintf (stderr, "Error %slocking file: %d with  fcntl\t%s\n",
			lock ? "" : "un", fd, sys_errlist[errno]);
	return (FALSE);
    }
    return (TRUE);
#endif

#if !defined(LOCKING_WORKS) && defined(F_LOCK)
#define LOCKING_WORKS
    if ( (pos = lseek (fd, 0L, 0) ) == -1)
    {
	(void) fprintf (stderr, "Error seeking in file\t%s\n",
			sys_errlist[errno]);
	return (FALSE);
    }
    if (lockf (fd, lock ? F_LOCK : F_ULOCK, 0L) != 0)
    {
	
	(void) fprintf (stderr, "Error locking file with  lockf\t%s\n",
			sys_errlist[errno]);
	return (FALSE);
    }
    if ( (pos = lseek (fd, pos, 0) ) == -1)
    {
	(void) fprintf (stderr, "Error seeking in file\t%s\n",
			sys_errlist[errno]);
	return (FALSE);
    }
    return (TRUE);
#endif

#ifndef LOCKING_WORKS
    (void) fprintf (stderr, "File locking not implemented\n");
    my_exit (RV_UNDEF_ERROR);
    return (FALSE);
#endif
}   /*  End Function set_fd_lock  */

static void check_and_process_mail ()
/*  This routine will check for any incoming mail and will process that mail.
    The routine returns nothing.
*/
{
    flag pgp_data, bad_previous_pgp, read_more;
    int ch, len;
    int fd, oflags;
    int in_fd, out_fd, err_fd;
    int child;
    int status;
    long bytes_read, pgp_offset;
    int line_length;
    FILE *in_fp, *out_fp;
    struct stat statbuf;
    char line[STRING_LENGTH];
    char *argv[3];
    char *pgp_start_line = "-----BEGIN PGP MESSAGE-----\n";
    extern flag incoming_locked;
    extern char incoming_spool[STRING_LENGTH];
    extern char outgoing_spool[STRING_LENGTH];
    extern char passphrase[STRING_LENGTH];
    ERRNO_TYPE errno;
    extern char *sys_errlist[];

    if (stat (incoming_spool, &statbuf) != 0)
    {
	if (errno == ENOENT) return;
	(void) fprintf (stderr, "Error stat'ing file: \"%s\"\t%s\n",
			incoming_spool, sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
    if (statbuf.st_size < 1) return;
    (void) fprintf (stderr, "%d bytes of mail...", statbuf.st_size);
    /*  Lock output file  */
    if ( !set_lockfile (outgoing_spool, TRUE) ) return;
    if ( ( out_fp = fopen (outgoing_spool, "a") ) == NULL )
    {
	(void) fprintf (stderr, "Error opening file: \"%s\"\t%s\n",
			outgoing_spool, sys_errlist[errno]);
	(void) set_lockfile (outgoing_spool, FALSE);
	myexit (RV_SYS_ERROR);
    }
    if ( !set_fd_lock (fileno (out_fp), TRUE) )
    {
	(void) fprintf (stderr, "Error locking output desriptor\n");
	myexit (RV_UNDEF_ERROR);
    }
    /*  Now lock input file  */
    if ( !set_lockfile (incoming_spool, TRUE) )
    {
	(void) set_fd_lock (fileno (out_fp), FALSE);
	(void) fclose (out_fp);
	(void) set_lockfile (outgoing_spool, FALSE);
	return;
    }
    if ( ( in_fp = fopen(incoming_spool, "r+" ) ) == NULL )
    {
	(void) fprintf (stderr, "Error opening file: \"%s\"\t%s\n",
			incoming_spool, sys_errlist[errno]);
	(void) set_fd_lock (fileno (out_fp), FALSE);
	(void) fclose (out_fp);
	(void) set_lockfile (outgoing_spool, FALSE);
	(void) set_lockfile (incoming_spool, FALSE);
	myexit (RV_SYS_ERROR);
    }
    if ( !set_fd_lock (fileno (in_fp), TRUE) )
    {
	(void) fprintf (stderr, "Error locking input desriptor\n");
	(void) set_fd_lock (fileno (out_fp), FALSE);
	(void) fclose (out_fp);
	(void) set_lockfile (outgoing_spool, FALSE);
	(void) fclose (in_fp);
	(void) set_lockfile (incoming_spool, FALSE);
	myexit (RV_UNDEF_ERROR);
    }
    /*  Process incoming and produce outgoing  */
    bytes_read = 0;
    pgp_data = FALSE;
    bad_previous_pgp = FALSE;
    (void) fprintf (stderr, "\nSpoolfiles locked\n");
    read_more = TRUE;
    while (read_more)
    {
	if (fgets (line, STRING_LENGTH, in_fp) == NULL)
	{
	    /*  Must be EndOfFile  */
	    if (!pgp_data)
	    {
		/*  Nothing special, just stop trying to read  */
		read_more = FALSE;
		continue;
	    }
	    (void) fprintf (stderr, "\nPGP message terminated prematurely\n");
	    (void) fprintf (stderr, "Message not decrypted\n");
	    (void) fprintf (out_fp, "PGP message terminated prematurely\n");
	    (void) fprintf (out_fp, "Message not decrypted\n\n");
	    /*  PGP message terminated without end line: plain copy  */
	    (void) kill (child, SIGKILL);
	    (void) waitpid (child, &status, 0);
	    if (fseek (in_fp, pgp_offset, SEEK_SET) != 0)
	    {
		(void) fprintf (stderr, "Error seeking\t%s\n",
				sys_errlist[errno]);
		myexit (RV_SYS_ERROR);
	    }
	    pgp_data = FALSE;
	    bad_previous_pgp = TRUE;
	    bytes_read = pgp_offset;
	    continue;
	}
	/*  New line of data is available  */
	line_length = strlen (line);
	bytes_read += line_length;
	if (pgp_data)
	{
	    /*  Check to see if PGP is still running  */
	    switch ( waitpid (child, &status, WNOHANG | WUNTRACED) )
	    {
	      case 0:
		/*  PGP still running  */
		break;
	      case -1:
		(void) fprintf (stderr, "Error getting child status\t%s\n",
				sys_errlist[errno]);
		myexit (RV_SYS_ERROR);
		break;
	      default:
		if ( WIFSTOPPED (status) )
		{
		    /*  PGP was stopped: probably because message was encrypted
			with that fucking "For-your-eyes-only" option, and so
			PGP *insists* on asking if you want to view the
			message.  */
		    /*  Note that at this point, this condition is not likely,
			rather, reaping the child will show this condition.  */
		    (void) fprintf(stderr,
				   "\nPGP stopped while decrypting, probably");
		    (void) fprintf (stderr,
				    " because it was encrypted with that\n");
		    (void) fprintf (stderr,
				    "fucking \"For-your-eyes-only\" option. ");
		    (void) fprintf (out_fp,
				    "PGP stopped while decrypting, probably");
		    (void) fprintf (out_fp,
				    " because it was encrypted with that\n");
		    (void) fprintf (out_fp,
				    "fucking \"For-your-eyes-only\" option. ");
		}
		else
		{
		    (void) fprintf (stderr, "\nBad PGP child status: %d\n",
				    status);
		    (void) fprintf (out_fp, "Bad PGP child status: %d\n",
				    status);
		}
		(void) fprintf (stderr, "Message not decrypted\n");
		(void) fprintf (out_fp, "Message not decrypted\n");
		(void) kill (child, SIGKILL);
		(void) waitpid (child, &status, 0);
		if (fseek (in_fp, pgp_offset, SEEK_SET) != 0)
		{
		    (void) fprintf (stderr, "Error seeking\t%s\n",
				    sys_errlist[errno]);
		    myexit (RV_SYS_ERROR);
		}
		pgp_data = FALSE;
		bad_previous_pgp = TRUE;
		bytes_read = pgp_offset;
		continue;
/*
		break;
*/
	    }
	}
	if (st_nicmp (line, "subject:", 8) == 0)
	{
	    (void) fprintf (stderr, "> %s", line);
	}
	if ( (strcmp (line, pgp_start_line) == 0) && pgp_data )
	{
	    /*  Hm. Looks like the last PGP message wasn't terminated
		properly. I suppose PGP gave no output  */
	    (void) fprintf (stderr,
			    "\nStart of new PGP message without termination");
	    (void) fprintf (stderr,
			    " of previous message\nMessage not decrypted\n");
	    (void) fprintf (out_fp,
			    "Start of new PGP message without termination");
	    (void) fprintf (out_fp,
			    " of previous message\nMessage not decrypted\n");
	    /*  PGP message terminated without end line: plain copy  */
	    (void) kill (child, SIGKILL);
	    (void) waitpid (child, &status, 0);
	    if (fseek (in_fp, pgp_offset, SEEK_SET) != 0)
	    {
		(void) fprintf (stderr, "Error seeking\t%s\n",
				sys_errlist[errno]);
		myexit (RV_SYS_ERROR);
	    }
	    pgp_data = FALSE;
	    bad_previous_pgp = TRUE;
	    bytes_read = pgp_offset;
	    continue;
	}
	if ( (strcmp (line, pgp_start_line) == 0) &&
	    (passphrase[0] != '\0') && (!bad_previous_pgp) )
	{
	    pgp_data = TRUE;
	    pgp_offset = ftell (in_fp) - line_length;
	    in_fd = -1;
	    out_fd = fileno (out_fp);
	    err_fd = ERROR_FD;
	    (void) fprintf (out_fp,
			    "PGPdaemon %s: automatically decrypted message:\n\n",
			    VERSION);
	    (void) fflush (out_fp);
	    (void) fprintf (stderr, "DECRYPTING...\n");
	    /*  Start PGP  */
	    /*  pgp -f  */
	    argv[0] = "pgp";
	    argv[1] = "-f";
	    argv[2] = NULL;
	    if ( ( child = spawn_job ("pgp", argv, &in_fd, &out_fd, &err_fd) )
		< 0 )
	    {
		(void) fprintf (stderr, "Error spawning PGP\n");
		myexit (RV_SYS_ERROR);
	    }
	    /*  Send passphrase  */
	    len = strlen (passphrase);
	    if (write (in_fd, passphrase, len) < len)
	    {
		(void) fprintf (stderr, "Error writing passphrase\t%s\n",
				sys_errlist[errno]);
		(void) close (in_fd);
		(void) waitpid (child, &status, 0);
		myexit (RV_SYS_ERROR);
	    }
	}
	if (pgp_data)
	{
	    if (write (in_fd, line, line_length) < line_length)
	    {
		if (errno == EPIPE)
		{
		    /*  PGP dropped out for some reason: plain copy  */
		    (void) kill (child, SIGKILL);
		    (void) waitpid (child, &status, 0);
		    if (fseek (in_fp, pgp_offset, SEEK_SET) != 0)
		    {
			(void) fprintf (stderr, "Error seeking\t%s\n",
					sys_errlist[errno]);
			myexit (RV_SYS_ERROR);
		    }
		    pgp_data = FALSE;
		    bad_previous_pgp = TRUE;
		    bytes_read = pgp_offset;
		    continue;
		}
		(void) fprintf (stderr, "Error writing data to PGP\t%s\n",
				sys_errlist[errno]);
		myexit (RV_SYS_ERROR);
	    }
	}
	else
	{
	    if (fputs (line, out_fp) == EOF)
	    {
		(void) fprintf (stderr, "Error writing\t%s\n",
				sys_errlist[errno]);
		(void) fprintf (stderr, "Error copying data\n");
		(void) set_fd_lock (fileno (out_fp), FALSE);
		(void) fclose (out_fp);
		(void) set_lockfile (outgoing_spool, FALSE);
		(void) set_fd_lock (fileno (in_fp), FALSE);
		(void) fclose (in_fp);
		(void) set_lockfile (incoming_spool, FALSE);
		myexit (RV_UNDEF_ERROR);
	    }
	}
	if ( (strcmp (line, pgp_start_line) == 0) && bad_previous_pgp )
	{
	    /*  This line started off a bad PGP message on the previous round.
		Now that we've gone past the line, we can clear the
		bad_previous_pgp  flag.  */
	    bad_previous_pgp = FALSE;
	    pgp_data = FALSE;  /*  What the hell, clear it just in case  */
	    continue;
	}
	if (strcmp (line, "-----END PGP MESSAGE-----\n") == 0)
	{
	    bad_previous_pgp = FALSE;
	    if (!pgp_data) continue;
	    pgp_data = FALSE;
	    (void) close (in_fd);
	    (void) waitpid (child, &status, WUNTRACED);
	    if (status != 0)
	    {
		if ( WIFSTOPPED (status) )
		{
		    /*  PGP was stopped: probably because message was encrypted
			with that fucking "For-your-eyes-only" option, and so
			PGP *insists* on asking if you want to view the
			message.  */
		    (void) kill (child, SIGKILL);
		    (void) fprintf(stderr,
				   "\nPGP stopped while decrypting, probably");
		    (void) fprintf (stderr,
				    " because it was encrypted with that\n");
		    (void) fprintf (stderr,
				    "fucking \"For-your-eyes-only\" option. ");
		    (void) fprintf (out_fp,
				    "PGP stopped while decrypting, probably");
		    (void) fprintf (out_fp,
				    " because it was encrypted with that\n");
		    (void) fprintf (out_fp,
				    "fucking \"For-your-eyes-only\" option. ");
		}
		else
		{
		    (void) fprintf (stderr, "Bad PGP child status: %d\n",
				    status);
		    (void) fprintf (out_fp, "Bad PGP child status: %d\n",
				    status);
		}
		(void) fprintf (stderr, "Message not decrypted\n");
		(void) fprintf (out_fp, "Message not decrypted\n\n");
		if (fseek (in_fp, pgp_offset, SEEK_SET) != 0)
		{
		    (void) fprintf (stderr, "Error seeking\t%s\n",
				    sys_errlist[errno]);
		    myexit (RV_SYS_ERROR);
		}
		bad_previous_pgp = TRUE;
		bytes_read = pgp_offset;
		continue;
	    }
	    (void) fprintf (stderr, "\nDECRYPTED\n");
	    (void) fprintf (out_fp, "\nEnd automatically decrypted message\n");
	}
    }
    if (bytes_read < statbuf.st_size)
    {
	(void) fprintf (stderr, "Error reading\t%s\n", sys_errlist[errno]);
	(void) fprintf (stderr, "Error copying data\n");
	(void) set_fd_lock (fileno (out_fp), FALSE);
	(void) fclose (out_fp);
	(void) set_lockfile (outgoing_spool, FALSE);
	(void) set_fd_lock (fileno (in_fp), FALSE);
	(void) fclose (in_fp);
	(void) set_lockfile (incoming_spool, FALSE);
	myexit (RV_UNDEF_ERROR);
    }
    (void) fsync ( fileno (out_fp) );
    (void) fprintf (stderr, "Unlocking spoolfiles...");
    /*  Unlock output spoolfile  */
    (void) set_fd_lock (fileno (out_fp), FALSE);
    (void) fclose (out_fp);
    (void) set_lockfile (outgoing_spool, FALSE);
#ifdef dummy
    /*  Remove input spoolfile  */
    if (unlink (incoming_spool) != 0)
    {
	(void) fprintf (stderr, "Error removing file: \"%s\"\t%s\n",
			incoming_spool, sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
    /*  Unlock input spoolfile  */
#else
    /*  Empty input spoolfile  */
    oflags = O_WRONLY | O_TRUNC;
#ifdef O_SYNC
    oflags |= O_SYNC;
#endif
    if ( ( fd = open (incoming_spool, oflags, 0) ) < 0 )
    {
	(void) fprintf (stderr, "Error truncating file: \"%s\"\t%s\n",
			incoming_spool, sys_errlist[errno]);
	myexit (RV_SYS_ERROR);
    }
#ifndef O_SYNC
    (void) fsync (fd);
#endif
    (void) close (fd);
    (void) set_fd_lock (fileno (in_fp), FALSE);
#endif
    (void) fclose (in_fp);
    (void) set_lockfile (incoming_spool, FALSE);
    (void) fprintf (stderr, "\tmail processed\n");
}   /*  End Function check_and_process_mail  */

static void read_config (pgppath, mailwait, interval)
/*  This routine will read the configuration file:  $PGPPATH/PGPdaemon.config
    The value of the PGPPATH environment variable must be pointed to by
    pgppath  .
    If the "MAILWAIT" keyword is present, the value TRUE will be written to the
    storage pointed to by  mailwait  .
    If the "MAIL_CHECK_INTERVAL" keyword is present, the associated integer
    value will be written to the storage pointed to by  interval  .
    The routine returns nothing.
*/
char *pgppath;
flag *mailwait;
long *interval;
{
    char *keyword, *rest;
    FILE *fp;
    char config_filename[STRING_LENGTH];
    char txt[STRING_LENGTH];
    extern char my_userid[STRING_LENGTH];
    extern char incoming_spool[STRING_LENGTH];
    extern char outgoing_spool[STRING_LENGTH];
    ERRNO_TYPE errno;
    extern char *sys_errlist[];

    (void) sprintf (config_filename, "%s/PGPdaemon.config", pgppath);
    if ( ( fp = fopen (config_filename, "r") ) == NULL )
    {
	(void) fprintf (stderr, "Error reading file: \"%s\"\t%s\n",
			config_filename, sys_errlist[errno]);
	myexit (RV_CANNOT_OPEN);
    }
    /*  Read in lines  */
    m_clear (my_userid, STRING_LENGTH);
    m_clear (incoming_spool, STRING_LENGTH);
    m_clear (outgoing_spool, STRING_LENGTH);
    while (fgets (txt, STRING_LENGTH - 1, fp) != NULL)
    {
	/*  Strip newline  */
	txt[strlen (txt) - 1] = '\0';
	if (strlen (txt) < 1) continue;
	if (txt[0] == '#') continue;
	if ( ( keyword = ex_str (txt, &rest) ) == NULL ) continue;
	if (strcmp (keyword, "USERID") == 0) (void) strcpy (my_userid, rest);
	else if (strcmp (keyword, "MAILWAIT") == 0) *mailwait = TRUE;
	else if (strcmp (keyword, "IN_SPOOL_DIR") == 0)
	{
	    (void) strcpy (incoming_spool, rest);
	}
	else if (strcmp (keyword, "OUT_SPOOL_FILE") == 0)
	{
	    (void) strcpy (outgoing_spool, rest);
	}
	else if (strcmp (keyword, "MAIL_CHECK_INTERVAL") == 0)
	{
	    if (rest == NULL)
	    {
		(void) fprintf (stderr, "No interval specified\n");
		myexit (RV_BAD_DATA);
	    }
	    if ( ( *interval = atol (rest) ) < MIN_INTERVAL )
	    {
		(void) fprintf (stderr,
				"Interval: %ld is less than minimum: %ld\n",
				*interval, MIN_INTERVAL);
		myexit (RV_BAD_DATA);
	    }
	    if (*interval > MAX_INTERVAL)
	    {
		(void) fprintf (stderr,
				"Interval: %ld is greater than maximum: %ld\n",
				*interval, MAX_INTERVAL);
		myexit (RV_BAD_DATA);
	    }
	}
	else
	{
	    (void) fprintf (stderr, "Illegal config line: \"%s\"\n", txt);
	    myexit (RV_BAD_DATA);
	}
	m_free (keyword);
    }
    if (my_userid[0] == '\0')
    {
	(void) fprintf (stderr, "No USERID specified in config file\n");
	myexit (RV_BAD_DATA);
    }
    if (incoming_spool[0] == '\0') (void)strcpy(incoming_spool,MAIL_SPOOL_DIR);
    (void) strcat (incoming_spool, "/");
    (void) strcat ( incoming_spool, getenv ("USER") );
    if (outgoing_spool[0] == '\0')
    {
	(void) sprintf ( outgoing_spool, "%s/decrypted-mail",
			getenv ("HOME") );
    }
    (void) fprintf (stderr, "USERID is: \"%s\"\n", my_userid);
    (void) fprintf (stderr, "Incoming spool file is: \"%s\"\n",
		    incoming_spool);
    (void) fprintf (stderr, "Outgoing spool file is: \"%s\"\n",
		    outgoing_spool);
    (void) fprintf (stderr, "Mail check interval: %ld seconds\n", *interval);
}   /*  End Function read_config  */
