/*  spawn.c

    Source file for spawn operations for  PGPsendmail  (wrapper to sendmail).

    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   31-MAY-1994

    Updated by      Richard Gooch   31-MAY-1994: Extracted from  pgpsendmail.c

    Updated by      Richard Gooch   18-JUN-1994: Made error messages more
  explicit.

    Updated by      Richard Gooch   27-JUN-1994: Copied  set_env  from
  pgpdaemon.c

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


*/
#include <stdio.h>
#include "pgpsendmail.h"

#define BUF_SIZE 16384


int spawn_job (path, argv, in_fd, out_fd, err_fd)
/*  This routine will fork(2) and execve(2) a process.
    The file to execute must be pointed to by  path  .
    The NULL terminated list of arguments which will be passed to  main  must
    be pointed to by  argv  .
    The input file descriptor (fd = 0) for the process must be pointed to by
    in_fd  .If the value here is less than 0, then a pipe to the process is
    opened and the writeable end is written to the storage pointed to by  in_fd
    The standard output file descriptor (fd = 1) for the process must be
    pointed to by  out_fd  .If the value here is less than 0, then a pipe to
    the process is opened and the readable end is written to the storage
    pointed to by  out_fd  .
    The standard error output file descriptor (fd = 2) for the process must be
    pointed to by  err_fd  .If the value here is less than 0, then a pipe to
    the process is opened and the readable end is written to the storage
    pointed to by  err_fd  .
    The routine returns the child process ID on success, else it returns -1.
*/
char *path;
char *argv[];
int *in_fd;
int *out_fd;
int *err_fd;
{
    int child_pid;
    int in_fds[2], out_fds[2], err_fds[2];
    ERRNO_TYPE errno;
    extern char *sys_errlist[];

    if (*in_fd < 0)
    {
	/*  Open a pipe  */
	if (pipe (in_fds) != 0)
	{
	    (void) fprintf (stderr, "Could not open input pipe\t%s\n",
			    sys_errlist[errno]);
	    return (-1);
	}
	/*  fd[0]: READ    fd[1]: WRITE  */
    }
    if (*out_fd < 0)
    {
	/*  Open a pipe  */
	if (pipe (out_fds) != 0)
	{
	    (void) fprintf (stderr, "Could not open output pipe\t%s\n",
			    sys_errlist[errno]);
	    return (-1);
	}
	/*  fd[0]: READ    fd[1]: WRITE  */
    }
    if (*err_fd < 0)
    {
	/*  Open a pipe  */
	if (pipe (err_fds) != 0)
	{
	    (void) fprintf (stderr, "Could not open error output pipe\t%s\n",
			    sys_errlist[errno]);
	    return (-1);
	}
	/*  fd[0]: READ    fd[1]: WRITE  */
    }
    /*  Fork and exec  */
    switch ( child_pid = fork () )
    {
      case 0:
	/*  Child: exec  */
	if (*in_fd < 0)
	{
	    (void) close (in_fds[1]);
	    dup2 (in_fds[0], 0);
	}
	else
	{
	    dup2 (*in_fd, 0);
	}
	if (*out_fd < 0)
	{
	    (void) close (out_fds[0]);
	    dup2 (out_fds[1], 1);
	}
	else
	{
	    dup2 (*out_fd, 1);
	}
	if (*err_fd < 0)
	{
	    (void) close (err_fds[0]);
	    dup2 (err_fds[1], 1);
	}
	else
	{
	    dup2 (*err_fd, 2);
	}
	(void) execvp (path, argv);
	(void) fprintf (stderr, "Could not exec: \"%s\"\t%s\n",
			path, sys_errlist[errno]);
	exit (1);
	break;
      case -1:
	/*  Error  */
	(void) fprintf (stderr, "Could not fork\t%s\n", sys_errlist[errno]);
	return (-1);
	break;
      default:
	/*  Parent  */
	break;
    }
    /*  Parent only  */
    if (*in_fd < 0)
    {
	(void) close (in_fds[0]);
	*in_fd = in_fds[1];
    }
    if (*out_fd < 0)
    {
	(void) close (out_fds[1]);
	*out_fd = out_fds[0];
    }
    if (*err_fd < 0)
    {
	(void) close (err_fds[1]);
	*err_fd  = err_fds[0];
    }
    return (child_pid);
}   /*  End Function spawn_job  */

flag copy_data (out_fd, in_fd, return_on_terminator)
/*  This routine will copy data from one file descriptor to another, until the
    input closes (EndOfFile is reached).
    The output file descriptor must be given by  out_fd  .
    The input file descriptor must be given by  in_fd  .
    If the value of  return_on_terminator  is TRUE, then the routine will
    return when a string terminator '\0' character is received.
    The routine returns TRUE on success, else it returns FALSE.
*/
int out_fd;
int in_fd;
flag return_on_terminator;
{
    int len;
    int count = -1;
    char buffer[BUF_SIZE];
    ERRNO_TYPE errno;
    extern char *sys_errlist[];

    while ( ( len = read (in_fd, buffer, BUF_SIZE) ) > 0 )
    {
	if (return_on_terminator)
	{
	    for (count = len - 1; (buffer[count] != '\0') && (count >= 0);
		 --count);
	    if (count >= 0) len = count;
	}
	if (write (out_fd, buffer, len) < len)
	{
	    (void) fprintf (stderr, "Error writing data\t%s\n",
			    sys_errlist[errno]);
	    return (FALSE);
	}
	if (count >= 0) return (TRUE);
    }
    if (len == 0) return (TRUE);
    (void) fprintf (stderr, "Error reading data\t%s\n", sys_errlist[errno]);
    return (TRUE);
}   /*  End Function copy_data  */

int set_env (env_name, env_value)
/*  This routine will set an environment variable. Because different platforms
    have different C library calls to do this: I do it myself.
    The environment variable to create or change must be named by  env_name  .
    The string value to set the variable to must be pointed to by  env_value  .
    The routine returns 0 on success, else it returns -1.
*/
CONST char *env_name;
CONST char *env_value;
{
    int num_strings;
    char *str, **env;
    char env_string[STRING_LENGTH];
    extern char **environ;
    static char **old_environ = NULL;

    (void) sprintf (env_string, "%s=%s", env_name, env_value);
    if ( ( str = m_alloc (strlen (env_string) + 1) ) == NULL )
    {
	(void) fprintf (stderr, "Error allocating string\n");
	return (-1);
    }
    (void) strcpy (str, env_string);
    for (num_strings = 0; environ[num_strings] != NULL; ++num_strings);
    if ( ( env = (char **) m_alloc ( sizeof *env * (num_strings + 2) ) )
	== NULL )
    {
	m_free (str);
	(void) fprintf (stderr, "Error allocating environment\n");
	return (-1);
    }
    m_copy ( (char *) env, (char *) environ, num_strings * sizeof *env );
    env[num_strings++] = str;
    env[num_strings] = NULL;
    if (old_environ == environ) m_free ( (char *) environ );
    environ = env;
    old_environ = env;
    return (0);
}   /*  End Function set_env  */
