/* xqtsub.c
   System dependent functions used only by uuxqt.

   Copyright (C) 1991, 1992, 1993, 1995, 2002 Ian Lance Taylor

   This file is part of the Taylor UUCP package.

   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.

   The author of the program may be contacted at ian@airs.com.
   */

#include "uucp.h"

#if USE_RCS_ID
const char xqtsub_rcsid[] = "$Id: xqtsub.c,v 1.24 2002/03/05 19:10:42 ian Rel $";
#endif

#include "uudefs.h"
#include "uuconf.h"
#include "system.h"
#include "sysdep.h"

#include <ctype.h>
#include <errno.h>

#if HAVE_FCNTL_H
#include <fcntl.h>
#else
#if HAVE_SYS_FILE_H
#include <sys/file.h>
#endif
#endif

#ifndef O_RDONLY
#define O_RDONLY 0
#define O_WRONLY 1
#define O_RDWR 2
#endif

#ifndef O_NOCTTY
#define O_NOCTTY 0
#endif

#ifndef FD_CLOEXEC
#define FD_CLOEXEC 1
#endif

#if HAVE_OPENDIR
#if HAVE_DIRENT_H
#include <dirent.h>
#else /* ! HAVE_DIRENT_H */
#include <sys/dir.h>
#define dirent direct
#endif /* ! HAVE_DIRENT_H */
#endif /* HAVE_OPENDIR */

/* Get a value for EX_TEMPFAIL.  */

#if HAVE_SYSEXITS_H
#include <sysexits.h>
#endif

#ifndef EX_TEMPFAIL
#define EX_TEMPFAIL 75
#endif

static boolean fclean_uuxqt_dir P((const char *zxqtdir));

/* Get the full pathname of the command to execute, given the list of
   permitted commands and the allowed path.  */

char *
zsysdep_find_command (zcmd, pzcmds, pzpath, pferr)
     const char *zcmd;
     char **pzcmds;
     char **pzpath;
     boolean *pferr;
{
  char **pz;
  struct stat s;

  *pferr = FALSE;

  for (pz = pzcmds; *pz != NULL; pz++)
    {
      char *zslash;

      if (strcmp (*pz, "ALL") == 0)
	break;

      zslash = strrchr (*pz, '/');
      if (zslash != NULL)
	++zslash;
      else
	zslash = *pz;
      if (strcmp (zslash, zcmd) == 0
	  || strcmp (*pz, zcmd) == 0)
	{
	  /* If we already have an absolute path, we can get out
	     immediately.  */
	  if (**pz == '/')
	    {
	      /* Quick error check.  */
	      if (stat (*pz, &s) != 0)
		{
		  ulog (LOG_ERROR, "%s: %s", *pz, strerror (errno));
		  *pferr = TRUE;
		  return NULL;
		}
	      return zbufcpy (*pz);
	    }
	  break;
	}
    }

  /* If we didn't find this command, get out.  */
  if (*pz == NULL)
    return NULL;

  /* We didn't find an absolute pathname, so we must look through
     the path.  */
  for (pz = pzpath; *pz != NULL; pz++)
    {
      char *zname;

      zname = zsysdep_in_dir (*pz, zcmd);
      if (stat (zname, &s) == 0)
	return zname;
    }

  return NULL;
}

/* Expand a local filename for uuxqt.  This is special because uuxqt
   only wants to expand filenames that start with ~ (it does not want
   to prepend the current directory to other names) and if the ~ is
   double, it is turned into a single ~.  This returns NULL to
   indicate that no change was required; it has no way to return
   error.  */

char *
zsysdep_xqt_local_file (qsys, zfile)
     const struct uuconf_system *qsys;
     const char *zfile;
{
  if (*zfile != '~')
    return NULL;
  if (zfile[1] == '~')
    {
      size_t clen;
      char *zret;

      clen = strlen (zfile);
      zret = zbufalc (clen);
      memcpy (zret, zfile + 1, clen);
      return zret;
    }
  return zsysdep_local_file (zfile, qsys->uuconf_zpubdir,
			     (boolean *) NULL);
}

#if ! ALLOW_FILENAME_ARGUMENTS

/* Check to see whether an argument specifies a file name; if it does,
   make sure that the file may legally be sent and/or received.  For
   Unix, we do not permit any occurrence of "/../" in the name, nor
   may it start with "../".  Otherwise, if it starts with "/" we check
   against the list of permitted files.  */

boolean
fsysdep_xqt_check_file (qsys, zfile)
     const struct uuconf_system *qsys;
     const char *zfile;
{
  size_t clen;

  /* Disallow exact "..", prefix "../", suffix "/..", internal "/../",
     and restricted absolute paths.  */
  clen = strlen (zfile);
  if ((clen == sizeof ".." - 1
       && strcmp (zfile, "..") == 0)
      || strncmp (zfile, "../", sizeof "../" - 1) == 0
      || (clen >= sizeof "/.." - 1
	  && strcmp (zfile + clen - (sizeof "/.." - 1), "/..") == 0)
      || strstr (zfile, "/../") != NULL
      || (*zfile == '/'
	  && (! fin_directory_list (zfile, qsys->uuconf_pzremote_send,
				    qsys->uuconf_zpubdir, TRUE, FALSE,
				    (const char *) NULL)
	      || ! fin_directory_list (zfile, qsys->uuconf_pzremote_receive,
				       qsys->uuconf_zpubdir, TRUE, FALSE,
				       (const char *) NULL))))
    {
      ulog (LOG_ERROR, "Not permitted to refer to file \"%s\"", zfile);
      return FALSE;
    }

  return TRUE;
}

#endif /* ! ALLOW_FILENAME_ARGUMENTS */

/* Invoke the command specified by an execute file.  */

/*ARGSUSED*/
boolean
fsysdep_execute (qsys, zuser, pazargs, zfullcmd, zinput, zoutput,
		 fshell, iseq, pzerror, pftemp)
     const struct uuconf_system *qsys;
     const char *zuser;
     const char **pazargs;
     const char *zfullcmd ATTRIBUTE_UNUSED;
     const char *zinput;
     const char *zoutput;
     boolean fshell;
     int iseq;
     char **pzerror;
     boolean *pftemp;
{
  int aidescs[3];
  boolean ferr;
  pid_t ipid;
  int ierr;
  char abxqtdir[sizeof XQTDIR + 4];
  const char *zxqtdir;
  int istat;
  char *zpath;
#if ALLOW_SH_EXECUTION
  const char *azshargs[4];
#endif

  *pzerror = NULL;
  *pftemp = FALSE;

  aidescs[0] = SPAWN_NULL;
  aidescs[1] = SPAWN_NULL;
  aidescs[2] = SPAWN_NULL;

  ferr = FALSE;

  if (zinput != NULL)
    {
      aidescs[0] = open ((char *) zinput, O_RDONLY | O_NOCTTY, 0);
      if (aidescs[0] < 0)
	{
	  ulog (LOG_ERROR, "open (%s): %s", zinput, strerror (errno));
	  ferr = TRUE;
	}
      else if (fcntl (aidescs[0], F_SETFD,
		      fcntl (aidescs[0], F_GETFD, 0) | FD_CLOEXEC) < 0)
	{
	  ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno));
	  ferr = TRUE;
	}	
    }
  
  if (! ferr && zoutput != NULL)
    {
      aidescs[1] = creat ((char *) zoutput, IPRIVATE_FILE_MODE);
      if (aidescs[1] < 0)
	{
	  if (errno == ENOENT && zoutput[0] != '/')
	    {
	      if (! fsysdep_make_dirs (zoutput, FALSE))
		{
		  *pftemp = TRUE;
		  ferr = TRUE;
		}
	      else
		aidescs[1] = creat ((char *) zoutput, IPRIVATE_FILE_MODE);
	    }
	  if (! ferr && aidescs[1] < 0)
	    {
	      ulog (LOG_ERROR, "creat (%s): %s", zoutput, strerror (errno));
	      *pftemp = TRUE;
	      ferr = TRUE;
	    }
	}
      if (! ferr
	  && fcntl (aidescs[1], F_SETFD,
		    fcntl (aidescs[1], F_GETFD, 0) | FD_CLOEXEC) < 0)
	{
	  ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno));
	  ferr = TRUE;
	}	
    }

  if (! ferr)
    {
      *pzerror = zstemp_file (qsys);
      aidescs[2] = creat (*pzerror, IPRIVATE_FILE_MODE);
      if (aidescs[2] < 0)
	{
	  if (errno == ENOENT)
	    {
	      if (! fsysdep_make_dirs (*pzerror, FALSE))
		{
		  *pftemp = TRUE;
		  ferr = TRUE;
		}
	      else
		aidescs[2] = creat (*pzerror, IPRIVATE_FILE_MODE);
	    }
	  if (! ferr && aidescs[2] < 0)
	    {
	      ulog (LOG_ERROR, "creat (%s): %s", *pzerror, strerror (errno));
	      *pftemp = TRUE;
	      ferr = TRUE;
	    }
	}
      if (! ferr
	  && fcntl (aidescs[2], F_SETFD,
		    fcntl (aidescs[2], F_GETFD, 0) | FD_CLOEXEC) < 0)
	{
	  ulog (LOG_ERROR, "fcntl (FD_CLOEXEC): %s", strerror (errno));
	  ferr = TRUE;
	}	
    }

  if (iseq == 0)
    zxqtdir = XQTDIR;
  else
    {
      sprintf (abxqtdir, "%s%04d", XQTDIR, iseq);
      zxqtdir = abxqtdir;
    }

  if (ferr)
    {
      if (aidescs[0] != SPAWN_NULL)
	(void) close (aidescs[0]);
      if (aidescs[1] != SPAWN_NULL)
	(void) close (aidescs[1]);
      if (aidescs[2] != SPAWN_NULL)
	(void) close (aidescs[2]);
      ubuffree (*pzerror);
      return FALSE;
    }

#if ALLOW_SH_EXECUTION
  if (fshell)
    {
      azshargs[0] = "/bin/sh";
      azshargs[1] = "-c";
      azshargs[2] = zfullcmd;
      azshargs[3] = NULL;
      pazargs = azshargs;
    }
#else
  fshell = FALSE;
#endif

  if (qsys->uuconf_pzpath == NULL)
    zpath = NULL;
  else
    {
      size_t c;
      char **pz;

      c = 0;
      for (pz = qsys->uuconf_pzpath; *pz != NULL; pz++)
	c += strlen (*pz) + 1;
      zpath = zbufalc (c);
      *zpath = '\0';
      for (pz = qsys->uuconf_pzpath; *pz != NULL; pz++)
	{
	  strcat (zpath, *pz);
	  if (pz[1] != NULL)
	    strcat (zpath, ":");
	}
    }

  /* Pass zchdir as zxqtdir, fnosigs as TRUE, fshell as TRUE if we
     aren't already using the shell.  */
  ipid = ixsspawn (pazargs, aidescs, TRUE, FALSE, zxqtdir, TRUE,
		   ! fshell, zpath, qsys->uuconf_zname, zuser);

  ierr = errno;

  ubuffree (zpath);

  if (aidescs[0] != SPAWN_NULL)
    (void) close (aidescs[0]);
  if (aidescs[1] != SPAWN_NULL)
    (void) close (aidescs[1]);
  if (aidescs[2] != SPAWN_NULL)
    (void) close (aidescs[2]);

  if (ipid < 0)
    {
      ulog (LOG_ERROR, "ixsspawn: %s", strerror (ierr));
      *pftemp = TRUE;
      return FALSE;
    }

  istat = ixswait ((unsigned long) ipid, "Execution");

  if (istat == EX_TEMPFAIL)
    *pftemp = TRUE;

  return istat == 0;
}

/* Lock a uuxqt process.  */

int
ixsysdep_lock_uuxqt (zcmd, cmaxuuxqts)
     const char *zcmd;
     int cmaxuuxqts;
{
  char ab[sizeof "LCK.XQT.9999"];
  int i;

  if (cmaxuuxqts <= 0 || cmaxuuxqts >= 10000)
    cmaxuuxqts = 9999;
  for (i = 0; i < cmaxuuxqts; i++)
    {
      sprintf (ab, "LCK.XQT.%d", i);
      if (fsdo_lock (ab, TRUE, (boolean *) NULL))
	break;
    }
  if (i >= cmaxuuxqts)
    return -1;

  if (zcmd != NULL)
    {
      char abcmd[sizeof "LXQ.123456789"];

      sprintf (abcmd, "LXQ.%.9s", zcmd);
      abcmd[strcspn (abcmd, " \t/")] = '\0';
      if (! fsdo_lock (abcmd, TRUE, (boolean *) NULL))
	{
	  (void) fsdo_unlock (ab, TRUE);
	  return -1;
	}
    }

  return i;
}

/* Unlock a uuxqt process.  */

boolean
fsysdep_unlock_uuxqt (iseq, zcmd, cmaxuuxqts)
     int iseq;
     const char *zcmd;
     int cmaxuuxqts ATTRIBUTE_UNUSED;
{
  char ab[sizeof "LCK.XQT.9999"];
  boolean fret;

  fret = TRUE;

  sprintf (ab, "LCK.XQT.%d", iseq);
  if (! fsdo_unlock (ab, TRUE))
    fret = FALSE;

  if (zcmd != NULL)
    {
      char abcmd[sizeof "LXQ.123456789"];

      sprintf (abcmd, "LXQ.%.9s", zcmd);
      abcmd[strcspn (abcmd, " \t/")] = '\0';
      if (! fsdo_unlock (abcmd, TRUE))
	fret = FALSE;
    }

  return fret;
}

/* See whether a particular uuxqt command is locked (this depends on
   the implementation of fsdo_lock).  */

boolean
fsysdep_uuxqt_locked (zcmd)
     const char *zcmd;
{
  char ab[sizeof "LXQ.123456789"];
  struct stat s;

  sprintf (ab, "LXQ.%.9s", zcmd);
  return stat (ab, &s) == 0;
}

/* Lock a particular execute file.  */

boolean
fsysdep_lock_uuxqt_file (zfile)
     const char *zfile;
{
  char *zcopy, *z;
  boolean fret;

  zcopy = zbufcpy (zfile);

  z = strrchr (zcopy, '/');
  if (z == NULL)
    *zcopy = 'L';
  else
    *(z + 1) = 'L';

  fret = fsdo_lock (zcopy, TRUE, (boolean *) NULL);
  ubuffree (zcopy);
  return fret;
}

/* Unlock a particular execute file.  */

boolean
fsysdep_unlock_uuxqt_file (zfile)
     const char *zfile;
{
  char *zcopy, *z;
  boolean fret;

  zcopy = zbufcpy (zfile);

  z = strrchr (zcopy, '/');
  if (z == NULL)
    *zcopy = 'L';
  else
    *(z + 1) = 'L';

  fret = fsdo_unlock (zcopy, TRUE);
  ubuffree (zcopy);
  return fret;
}

/* Lock the execute directory.  Since we use a different directory
   depending on which LCK.XQT.dddd file we got, there is actually no
   need to create a lock file.  We do make sure that the directory
   exists, though, and that it is empty.  */

boolean
fsysdep_lock_uuxqt_dir (iseq)
     int iseq;
{
  const char *zxqtdir;
  char abxqtdir[sizeof XQTDIR + 4];

  if (iseq == 0)
    zxqtdir = XQTDIR;
  else
    {
      sprintf (abxqtdir, "%s%04d", XQTDIR, iseq);
      zxqtdir = abxqtdir;
    }

  if (mkdir (zxqtdir, S_IRWXU) < 0
      && errno != EEXIST
      && errno != EISDIR)
    {
      ulog (LOG_ERROR, "mkdir (%s): %s", zxqtdir, strerror (errno));
      return FALSE;
    }

  return fclean_uuxqt_dir (zxqtdir);
}

/* Unlock the execute directory and clear it out.  The lock is
   actually the LCK.XQT.dddd file, so we don't unlock it, but we do
   remove all the files.  */

boolean
fsysdep_unlock_uuxqt_dir (iseq)
     int iseq;
{
  const char *zxqtdir;
  char abxqtdir[sizeof XQTDIR + 4];

  if (iseq == 0)
    zxqtdir = XQTDIR;
  else
    {
      sprintf (abxqtdir, "%s%04d", XQTDIR, iseq);
      zxqtdir = abxqtdir;
    }

  return fclean_uuxqt_dir (zxqtdir);
}

static boolean
fclean_uuxqt_dir (zxqtdir)
     const char *zxqtdir;
{
  DIR *qdir;

  qdir = opendir ((char *) zxqtdir);
  if (qdir != NULL)
    {
      struct dirent *qentry;

      while ((qentry = readdir (qdir)) != NULL)
	{
	  char *z;

	  if (strcmp (qentry->d_name, ".") == 0
	      || strcmp (qentry->d_name, "..") == 0)
	    continue;
	  z = zsysdep_in_dir (zxqtdir, qentry->d_name);
	  if (remove (z) < 0)
	    {
	      int ierr;

	      ierr = errno;
	      if (! fsysdep_directory (z))
		ulog (LOG_ERROR, "remove (%s): %s", z,
		      strerror (ierr));
	      else
		(void) fsysdep_rmdir (z);
	    }
	  ubuffree (z);
	}

      closedir (qdir);
    }

  return TRUE;
}

/* Move files into the execution directory.  */

boolean
fsysdep_copy_uuxqt_files (cfiles, pzfrom, pzto, iseq, pzinput)
     int cfiles;
     const char *const *pzfrom;
     const char *const *pzto;
     int iseq;
     char **pzinput;
{
  char *zinput;
  const char *zxqtdir;
  char abxqtdir[sizeof XQTDIR + 4];
  int i;

  if (pzinput == NULL)
    zinput = NULL;
  else
    zinput = *pzinput;

  if (iseq == 0)
    zxqtdir = XQTDIR;
  else
    {
      sprintf (abxqtdir, "%s%04d", XQTDIR, iseq);
      zxqtdir = abxqtdir;
    }

  for (i = 0; i < cfiles; i++)
    {
      const char *zfrom, *zto;
      char *zfree;

      if (pzto[i] == NULL)
	continue;

      zfree = zsysdep_in_dir (zxqtdir, pzto[i]);

      zfrom = pzfrom[i];
      zto = zfree;

      if (zinput != NULL && strcmp (zinput, zfrom) == 0)
	{
	  *pzinput = zbufcpy (zto);
	  zinput = NULL;
	}

      if (link (zfrom, zto) < 0)
	{
	  if (errno != EXDEV && errno != EEXIST && errno != EMLINK)
	    {
	      ulog (LOG_ERROR, "link (%s, %s): %s", zfrom, zto,
		    strerror (errno));
	      ubuffree (zfree);
	      return FALSE;
	    }

	  if (! fcopy_file (zfrom, zto, FALSE, FALSE, FALSE))
	    {
	      ubuffree (zfree);
	      return FALSE;
	    }
	}

      (void) chmod (zto, IPUBLIC_FILE_MODE);

      ubuffree (zfree);
    }

  return TRUE;
}
