/*
 * Sun RPC is a product of Sun Microsystems, Inc. and is provided for
 * unrestricted use provided that this legend is included on all tape
 * media and as a part of the software program in whole or part.  Users
 * may copy or modify Sun RPC without charge, but are not authorized
 * to license or distribute it to anyone else except as part of a product or
 * program developed by the user.
 *
 * SUN RPC IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING THE
 * WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE.
 *
 * Sun RPC is provided with no support and without any obligation on the
 * part of Sun Microsystems, Inc. to assist in its use, correction,
 * modification or enhancement.
 *
 * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE
 * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY SUN RPC
 * OR ANY PART THEREOF.
 *
 * In no event will Sun Microsystems, Inc. be liable for any lost revenue
 * or profits or other special, indirect and consequential damages, even if
 * Sun has been advised of the possibility of such damages.
 *
 * Sun Microsystems, Inc.
 * 2550 Garcia Avenue
 * Mountain View, California  94043
 */

/*
 * Administrative tool to add a new user to the publickey database
 */

#define _GNU_SOURCE

#include <errno.h>
#include <locale.h>
#include <libintl.h>
#include <libgen.h>
#include <malloc.h>
#include <unistd.h>
#include <stdio.h>
#include <rpc/rpc.h>
#include <rpc/key_prot.h>
#include <rpcsvc/ypclnt.h>
#include <sys/wait.h>
#include <netdb.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/stat.h>

#include "key_common.h"

#define _(String) gettext (String)

#define	MAXMAPNAMELEN 256

extern char program_name[];

static char SHELL[] = "/bin/sh";
static char UPDATEFILE[] = "updaters";
static char MAKE[] = "/usr/bin/make";

static int _openchild (char *, FILE **, FILE **);

/*
 * Determine if requester is allowed to update the given map,
 * and update it if so. Returns the yp status, which is zero
 * if there is no access violation.
 */
int
mapupdate (char *name, char *mapname, u_int op, char *data)
{
  char updater[MAXMAPNAMELEN + 40];
  FILE *childargs;
  FILE *childrslt;
  int status;
  pid_t pid;
  u_int yperrno;
  int namelen, datalen;
  struct stat stbuf;

#ifdef DEBUG
  fprintf (stderr, "%s %s\n", name, data);
#endif
  namelen = strlen (name);
  datalen = strlen (data);
  errno = 0;
  if (stat (MAKE, &stbuf) < 0)
    switch (errno)
      {
      case ENOENT:
	fprintf (stderr,
		 _("%s: %s not found, please install on the system\n"),
		 program_name, MAKE);
	return (1);
      default:
	fprintf (stderr,
		 _("%s: cannot access %s, errno=%d.\n"),
		 program_name, MAKE, errno);
	return (1);
      }
  sprintf (updater, "%s -s -f %s %s",
	   MAKE, UPDATEFILE, mapname);
  pid = _openchild (updater, &childargs, &childrslt);
  if (pid < 0)
    return (YPERR_YPERR);

  /*
   * Write to child
   */
  fprintf (childargs, "%s\n", name);
  fprintf (childargs, "%u\n", op);
  fprintf (childargs, "%u\n", namelen);
  fwrite (name, namelen, 1, childargs);
  fprintf (childargs, "\n");
  fprintf (childargs, "%u\n", datalen);
  fwrite (data, datalen, 1, childargs);
  fprintf (childargs, "\n");
  fclose (childargs);

  /*
   * Read from child
   */
  fscanf (childrslt, "%d", &yperrno);
  fclose (childrslt);

  wait (&status);
  if (WEXITSTATUS (status) != 0)
    {
      return (YPERR_YPERR);
    }
  return (yperrno);
}

/*
 * returns pid, or -1 for failure
 */
static int
_openchild (char *command, FILE ** fto, FILE ** ffrom)
{
  int i;
  pid_t pid;
  int pdto[2];
  int pdfrom[2];
  char *com;
  struct rlimit rl;

  if (pipe (pdto) < 0)
    {
      goto error1;
    }
  if (pipe (pdfrom) < 0)
    {
      goto error2;
    }

  switch (pid = fork ())
    {
    case -1:
      goto error3;

    case 0:
      /*
       * child: read from pdto[0], write into pdfrom[1]
       */
      close (0);
      dup (pdto[0]);
      close (1);
      dup (pdfrom[1]);
      getrlimit (RLIMIT_NOFILE, &rl);
      for (i = rl.rlim_max - 1; i >= 3; i--)
	{
	  close (i);
	}
      com = malloc ((unsigned) strlen (command) + 6);
      if (com == NULL)
	{
	  _exit (~0);
	}
      sprintf (com, "exec %s", command);
      execl (SHELL, basename (SHELL), "-c", com, NULL);
      _exit (~0);

    default:
      /*
       * parent: write into pdto[1], read from pdfrom[0]
       */
      *fto = fdopen (pdto[1], "w");
      close (pdto[0]);
      *ffrom = fdopen (pdfrom[0], "r");
      close (pdfrom[1]);
      break;
    }
  return (pid);

  /*
   * error cleanup and return
   */
error3:
  close (pdfrom[0]);
  close (pdfrom[1]);
error2:
  close (pdto[0]);
  close (pdto[1]);
error1:
  return (-1);
}

static int
match (char *line, char *name)
{
  int len;

  len = strlen (name);
  return (strncmp (line, name, len) == 0 &&
	  (line[len] == ' ' || line[len] == '\t'));
}

/*
 * Determine if requester is allowed to update the given map,
 * and update it if so. Returns the status, which is zero
 * if there is no access violation, 1 otherwise.
 * This function updates the local file.
 */
int
localupdate (char *name, char *filename, u_int op, char *data)
{
  char line[256];
  FILE *rf;
  FILE *wf;
  char tmpname[80];
  int err;

  /*
   * Check permission
   */
  if (strcmp (name, "nobody") == 0)
    {
      /* cannot change keys for nobody */
      fprintf (stderr,
	       _("%s: cannot change key-pair for %s\n"),
	       program_name, name);
      return (1);
    }

  /*
   * Open files
   */
  memset (tmpname, 0, 80);
  sprintf (tmpname, "%s.tmp", filename);
  rf = fopen (filename, "r");
  if (rf == NULL)
    {
      fprintf (stderr,
	       _("%s: cannot read %s\n"), program_name, filename);
      return (1);
    }
  wf = fopen (tmpname, "w");
  if (wf == NULL)
    {
      fprintf (stderr, _("%s: cannot create '%s' to write to.\n"),
	       program_name, tmpname);
      return (1);
    }
  err = -1;
  while (fgets (line, sizeof (line), rf))
    {
      if (err < 0 && match (line, name))
	{
	  switch (op)
	    {
	    case YPOP_INSERT:
	      err = 1;
	      break;
	    case YPOP_STORE:
	    case YPOP_CHANGE:
	      fprintf (wf, "%s\t%s\n", name, data);
	      err = 0;
	      break;
	    case YPOP_DELETE:
	      /* do nothing */
	      err = 0;
	      break;
	    }
	}
      else
	{
	  fputs (line, wf);
	}
    }
  if (err < 0)
    {
      switch (op)
	{
	case YPOP_CHANGE:
	case YPOP_DELETE:
	  err = 1;
	  break;
	case YPOP_INSERT:
	case YPOP_STORE:
	  err = 0;
	  fprintf (wf, "%s\t%s\n", name, data);
	  break;
	}
    }
  fclose (wf);
  fclose (rf);
  if (err == 0)
    {
      if (rename (tmpname, filename) < 0)
	{
	  fprintf (stderr, _("%s: cannot rename %s to %s\n"),
		   program_name, tmpname, filename);
	  return (1);
	}
    }
  else
    {
      if (unlink (tmpname) < 0)
	{
	  fprintf (stderr, _("%s: cannot delete %s\n"),
		   program_name, tmpname);
	  return (1);
	}
    }
  return (err);
}
