/*  spawn.c

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

    Copyright (C) 1994-1998  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

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

    Updated by      Richard Gooch   14-JUL-1994: Moved  copy_data  and  set_env
  to  misc.c

    Updated by      Richard Gooch   3-DEC-1994: Fixed bug for externally set
  error descriptor.

    Updated by      Richard Gooch   25-SEP-1997: Used new ERRSTRING macro.

    Last updated by Richard Gooch   10-JUL-1998: Removed definitions of system
  errlist array.


*/

#include "rip.h"

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

/* #include "pgpsendmail.h" */

#ifndef ERRSTRING
#define ERRSTRING strerror(errno)
#endif /* ERRSTRING */

#define LINE_LENGTH 1024
#define STRING_LENGTH 255

enum NFSL {
  NFSL_SYSF,
  NFSL_SECV,
  NFSL_LOCKED,
  NFSL_OK,
  NFSL_STOLEN,
  NFSL_LOST
};


int sd1[2] /*, sd2[2] */;  /* sd2 is used to have a collaborative 
		                  dialogue between processes          */
int spawn_job (char *path, char *argv[], int *in_fd, int *out_fd, int *err_fd)
/*  This routine will fork(2) and execvp(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.
*/
{
    int child_pid;
    /*     char txt[LINE_LENGTH];  */

    if (pipe(sd1) == -1)
      {
	perror("pipe failed");
	return(1);
      }

    /*  Fork and exec  */
    switch ( child_pid = fork () )
    {
      case 0:
	/*  Child: exec  */
	close(sd1[0]); 

	dup2( sd1[1], 1 );   /* stdout */

	/*	fprintf (stderr, "dup2 1 result: %s\n", ERRSTRING); */
	dup2( sd1[1], 2 );    /* stderr */
	/*	fprintf (stderr, "dup2 2 result: %s\n", ERRSTRING); */

	execvp (path, argv);

	fprintf (stderr, "Could not exec: \"%s\"\t%s\n", path, ERRSTRING);
	exit (1); 
	break;
      case -1:
	/*  Error  */
	fprintf (stderr, "Could not fork\t%s\n", ERRSTRING);
	return (-1);
	break;
      default:
	/*  Parent  */
	break;
    }
    /*  Parent only  */

    close(sd1[1]); 

    dup2 (sd1[0], 0); 
    
    /*     fprintf (stderr, "dup2 0 result: %s\n", ERRSTRING);  */
    /*     fprintf(stderr, "Reading child output\n");
    while (read(0, txt, 1000) != 0)
      fprintf(stderr, "child read %s\n", txt);
    
      fprintf(stderr, "Finished reading child output\n");   */
    
    return (child_pid);
}   /*  End Function spawn_job  */


#define DEFAULT_LOCKTIME 300;

time_t nfslock(char *path, char *namelock, int max_age, int notify)
{
  int tries = 0, oldlck = 0, tmpfd;
  struct stat old_stat, our_tmp;
  char tmp[32];
  char *tpath, *fullpath;

  srandom(getpid()+time(0));
  max_age = max_age ? max_age : DEFAULT_LOCKTIME;

  /*
   * 1. create a tmp file with a psuedo random file name. we also make
   *    tpath which is a buffer to store the full pathname of the tmp file.
   */

  sprintf(tmp,"slock%d.%d", (int)getpid(), (int)random());

  tpath = UT_malloc(strlen(path) + 1 + strlen(tmp) + 1);
  if (tpath == NULL) return(NFSL_SYSF);
  sprintf(tpath,"%s/%s", path, tmp);

  tmpfd = open(tpath, O_CREAT|O_WRONLY|O_EXCL, S_IRUSR|S_IWUSR);

  if (tmpfd < 0) {      /* open failed */
    close(tmpfd);
    unlink(tpath);
    UT_free(tpath);
    return(NFSL_SYSF);
  }

  if (getuid()==0) {    /* we're root, so be careful! */
    if (fstat(tmpfd, &our_tmp) < 0) {   /* stat failed... shouldn't happen */
      close(tmpfd);
      unlink(tpath);
      UT_free(tpath);
      return(NFSL_SYSF);
    }
    if (our_tmp.st_nlink != 1) {        /* someone's trying to mess with us */
      fprintf(stderr,"nfslock: bad link count on %s\n",tpath);
      close(tmpfd);
      unlink(tpath);
      UT_free(tpath);
      return(NFSL_SECV);
    }
  }

  /*
   * 2. make fullpath, a buffer for the full pathname of the lock file.
   *    then start looping trying to lock it
   */

  fullpath = UT_malloc(strlen(path) + 1 + strlen(namelock) + 1);
  if (fullpath == NULL) {
    close(tmpfd);
    unlink(tpath);
    UT_free(tpath);
    return(NFSL_SYSF);
  }
  sprintf(fullpath,"%s/%s", path, namelock);

  while (tries < 10) {

    /*
     * 3. link tmp file to lock file.  if it goes, we win and we clean
     *    up and return the st_ctime of the lock file.
     */

    if (link(tpath, fullpath) == 0) {
      unlink(tpath); /* got it! */
      UT_free(tpath);
      close(tmpfd);
      if (lstat(fullpath, &our_tmp) < 0) {      /* stat failed... shouldn't happen */
        unlink(fullpath);
        UT_free(fullpath);
        return (NFSL_SYSF);
      }
      UT_free(fullpath);
      return(our_tmp.st_ctime);
    }

    /*
     * 4. the lock failed.  check for a stale lock file, being mindful
     *    of NFS and the fact the time is set from the NFS server.  we
     *    do a write on the tmp file to update its time to the server's
     *    idea of "now."
     */

    oldlck = lstat(fullpath, &old_stat);

    if (write(tmpfd, "\0", 1) != 1 || fstat(tmpfd, &our_tmp) < 0)
      break; /* something bogus is going on */

    if (oldlck != -1 && old_stat.st_ctime + max_age < our_tmp.st_ctime) {
      unlink(fullpath); /* break the stale lock */
      if (notify) fprintf(stderr,"Breaking stale lock on %s.\n",fullpath);
      tries++;
      sleep(1+(random() % 10)); /* It is CRITICAL that we sleep after breaking
                                 * the lock. Otherwise, we could race with
                                 * another process and unlink it's newly-
                                 * created file.
                                 */
      continue;
    }

    /*
     * 5. try again
     */

    if (notify) {
      if (tries==0) fprintf(stderr,"Waiting for lock on file %s.\n",fullpath);
      else fprintf(stderr,"Still waiting...\n");
    }
    tries++;
    sleep(1+(random() % 10));
  }

  /*
   * 6. give up, failure.
   */

  errno = EEXIST;
  unlink(tpath);
  UT_free(tpath);
  UT_free(fullpath);
  close(tmpfd);
  return(NFSL_LOCKED);
}

int nfsunlock(char *path, char *namelock, int max_age, time_t birth)
{
  int tmpfd;
  struct stat old_stat, our_tmp;
  char *tpath, *fullpath;

  srandom(getpid()+time(0));
  max_age = max_age ? max_age : DEFAULT_LOCKTIME;

  /*
   * 1. Build a temp file and stat that to get an idea of what the server
   *    thinks the current time is (our_tmp.st_ctime)..
   */

  tpath = UT_malloc(strlen(path) + 25);   /* small slop factor- 23 s/b enough */
  if (tpath == NULL) return(NFSL_SYSF);
  sprintf(tpath,"%s/slock%d.%d", path, (int)getpid(), (int)random());

  tmpfd = open(tpath, O_CREAT|O_WRONLY|O_EXCL, S_IRUSR|S_IWUSR);

  if ((tmpfd < 0) || (write(tmpfd, "\0", 1) != 1)
                  || (fstat(tmpfd, &our_tmp) < 0)) {
    /* The open failed, or we can't write the file, or we can't stat it */
    close(tmpfd);
    unlink(tpath);
    UT_free(tpath);
    return(NFSL_SYSF);
  }

  close(tmpfd);         /* We don't need this once we have our_tmp.st_ctime. */
  unlink(tpath);
  UT_free(tpath);

  /*
   * 2. make fullpath, a buffer for the full pathname of the lock file
   */

  fullpath = UT_malloc(strlen(path) + 1 + strlen(namelock) + 1);
  if (fullpath == NULL)
    return(NFSL_SYSF);
  sprintf(fullpath,"%s/%s", path, namelock);

  /*
   * 3. If the ctime hasn't been modified, unlink the file and return. If the
   *    lock has expired, sleep the usual random interval before returning.
   *    If we didn't sleep, there could be a race if the caller immediately
   *    tries to relock the file.
   */

  if ( !lstat(fullpath, &old_stat) &&   /* stat succeeds so file is there */
      (old_stat.st_ctime == birth)) {   /* hasn't been modified since birth */
    unlink(fullpath);                   /* so the lock is ours to remove */
    if (our_tmp.st_ctime >= birth + max_age)    /* the lock has expired */
      sleep(1+(random() % 10));         /* so sleep a bit */
    UT_free(fullpath);
    return(NFSL_OK);                    /* success */
  }

  UT_free(fullpath);       /* we don't use fullpath anymore */

  /*
   * 4. Either ctime has been modified, or the entire lock file is missing.
   *    If the lock should still be ours, based on the ctime of the temp
   *    file, return with NFSL_STOLEN. If not, then our lock is expired and
   *    someone else has grabbed the file, so return NFSL_LOST.
   */

  if (our_tmp.st_ctime < birth + max_age)       /* lock was stolen */
    return(NFSL_STOLEN);

  return(NFSL_LOST);    /* The lock must have expired first. */
}



