/*
 * 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
 */

#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif

#define _XOPEN_SOURCE
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <rpc/rpc.h>
#include <shadow.h>
#include <rpc/key_prot.h>
#include <rpcsvc/ypclnt.h>
#include <sys/wait.h>
#include <netdb.h>
#include <pwd.h>
#include <string.h>
#include <sys/resource.h>
#include <rpcsvc/nis.h>
#include <getopt.h>
#include <unistd.h>
#include <locale.h>
#include <libintl.h>
#include "key_common.h"

#define _(String) gettext (String)

#define	MAXMAPNAMELEN 256

#define	PK_FILES	1
#define	PK_YP		2
#define	PK_NISPLUS	3

extern int xencrypt (char *, char *);

static void usage (void);
static int setpublicmap (int, char *, char *, char *, nis_name);
static char *get_password (uid_t, char *);

static char YPDBPATH[] = "/var/yp";
static char PKMAP[] = "publickey.byname";
static char PKFILE[] = "/etc/publickey";
char program_name[256];

int
main (int argc, char *argv[])
{
  char domain[MAXHOSTNAMELEN + 1];
  char name[MAXNETNAMELEN + 1];
  char public[HEXKEYBYTES + 1];
  char secret[HEXKEYBYTES + 1];
  char crypt1[HEXKEYBYTES + KEYCHECKSUMSIZE + 1];
  int status, pk_database;
  char *pass, *target_host = NULL, *username = NULL, *pk_service = NULL;
  struct passwd *pw;
  uid_t uid;
  char *nprinc = NULL;		/* nisplus principal name */
  char host_pname[NIS_MAXNAMELEN];

  strcpy (program_name, argv[0]);

  while (1)
    {
      int c;
      int option_index = 0;
      static struct option long_options[] =
      {
	{"version", no_argument, NULL, '\255'},
	{"help", no_argument, NULL, '\254'},
	{"user", required_argument, NULL, 'u'},
	{"host", required_argument, NULL, 'h'},
	{"service", required_argument, NULL, 's'},
	{NULL, 0, NULL, '\0'}
      };


      c = getopt_long (argc, argv, "h:u:s:", long_options, &option_index);
      if (c == EOF)
	break;
      switch (c)
	{
	case 's':
	  if (pk_service == NULL)
	    pk_service = optarg;
	  else
	    usage ();
	  break;
	case 'u':
	  if (username || target_host)
	    usage ();
	  username = optarg;
	  break;
	case 'h':
	  if (username || target_host)
	    usage ();
	  target_host = optarg;
	  break;
	case '\254':
	  usage ();
	  break;
	case '\255':
	  fputs ("newkey ("PACKAGE") "VERSION"\n", stderr);
	  exit (0);
	default:
	  usage ();
	}
    }

  if (optind < argc || (username == 0 && target_host == 0))
    usage ();

  if ((pk_database = get_pk_source (pk_service)) == 0)
    usage ();

  if (geteuid () != 0)
    {
      fprintf (stderr, _("Must be superuser to run %s\n"), program_name);
      exit (1);
    }

  if (username)
    {
      pw = getpwnam (username);
      if (pw == NULL)
	{
	  fprintf (stderr, _("%s: unknown user: '%s'\n"),
		   program_name, username);
	  exit (1);
	}
      uid = pw->pw_uid;
      if (uid == 0)
	{
	  if (!getnetname (name))
	    {
	      fprintf (stderr,
		       _("%s: could not get the equivalent netname for %s\n"),
		       program_name, username);
	      usage ();
	    }
	  if (pk_database == PK_NISPLUS)
	    target_host = nis_local_host ();
	  else
	    {
	      if (gethostname (host_pname, NIS_MAXNAMELEN) < 0)
		{
		  fprintf (stderr,
			   _("%s: could not get the hostname for %s\n"),
			   program_name, username);
		  usage ();
		}
	      target_host = host_pname;
	    }
	}
      getdomainname (domain, MAXHOSTNAMELEN);
      getnetnameof (name, uid, domain);
      if (pk_database == PK_NISPLUS)
	nprinc = get_nisplus_principal (nis_local_directory (), uid);
    }
  else
    {
      /* -h hostname option */
#if 0
      /* XXX Add some code to check for a known host ! */
      if (!validhost)
	{
	  fprintf (stderr, _("%s: unknown host: %s\n"),
		   program_name, target_host);
	  exit (1);
	}
#endif
      host2netname (name, target_host, (char *) NULL);
      if (pk_database == PK_NISPLUS)
	{
	  if (target_host[strlen (target_host) - 1] != '.')
	    {
	      sprintf (host_pname, "%s.%s", target_host, nis_local_directory ());
	      nprinc = host_pname;
	    }
	  else
	    nprinc = target_host;
	}
      uid = 0;
    }

  fprintf (stdout, _("Adding new key for %s.\n"), name);
  pass = get_password (uid, target_host);

  if (pass == NULL)
    exit (1);

  genkeys (public, secret, pass);

  memcpy (crypt1, secret, HEXKEYBYTES);
  memcpy (crypt1 + HEXKEYBYTES, secret, KEYCHECKSUMSIZE);
  crypt1[HEXKEYBYTES + KEYCHECKSUMSIZE] = 0;
  xencrypt (crypt1, pass);

  if ((status = setpublicmap (pk_database, name, public, crypt1, nprinc)))
    {
      switch (pk_database)
	{
	case PK_YP:
	  fprintf (stderr, _("%s: unable to update NIS database (%u): %s\n"),
		   program_name, status,
		   yperr_string (status));
	  break;
	case PK_FILES:
	  fprintf (stderr,
		   _("%s: unable to update publickey database\n"),
		   program_name);
	  break;
	case PK_NISPLUS:
	  fprintf (stderr, _("%s: unable to update nisplus database\n"),
		   program_name);
	  break;
	default:
	  fprintf (stderr, _("%s: could not update unknown database: %d\n"),
		   program_name, pk_database);
	}
      exit (1);
    }
  exit (0);
  /* NOTREACHED */
}

/*
 * Set the entry in the public key file
 */
static int
setpublicmap (int database, char *name, char *public, char *secret,
	      nis_name nis_princ)
{
  char pkent[1024];
  char *domain = NULL;
  char *master = NULL;
  char hostname[MAXHOSTNAMELEN + 1];

  sprintf (pkent, "%s:%s", public, secret);
  switch (database)
    {
    case PK_YP:
      /* check that we're on the master server */
      (void) yp_get_default_domain (&domain);
      if (yp_master (domain, PKMAP, &master) != 0)
	{
	  fprintf (stderr,
		   _("%s: cannot find master of NIS publickey database\n"),
		   program_name);
	  exit (1);
	}
      if (gethostname (hostname, MAXHOSTNAMELEN) < 0)
	{
	  fprintf (stderr, _("%s: cannot find my own host name\n"),
		   program_name);
	  exit (1);
	}
      if (strcmp (master, hostname) != 0)
	{
	  fprintf (stderr,
		   _("%s: can only be used on NIS master machine '%s'\n"),
		   program_name, master);
	  exit (1);
	}

      if (chdir (YPDBPATH) < 0)
	{
	  fprintf (stderr, _("%s: cannot chdir to %s"),
		   program_name, YPDBPATH);
	}
      fputs(_("Please wait for the database to get updated ...\n"), stdout);
      return (mapupdate (name, PKMAP, YPOP_STORE, pkent));
    case PK_FILES:
      return (localupdate (name, PKFILE, YPOP_STORE, pkent));
    case PK_NISPLUS:
      return (nisplus_update (name, "DES", public, secret, nis_princ));
    default:
      break;
    }
  return (1);
}

static void
usage (void)
{
  fprintf (stderr, _("usage:\t%s -u username [-s nisplus | nis | files]\n"),
	   program_name);
  fprintf (stderr, _("\t%s -h hostname [-s nisplus | nis | files]\n"),
	   program_name);
  exit (1);
}

static char *
get_password (uid_t uid, char *target_host)
{
  static char password[256];
  char prompt[256];
  char *encrypted_password, *login_password = NULL, *pass = NULL;
  struct passwd *pw;
  struct spwd *spw;
  int passwords_matched = 0;

  pw = getpwuid (uid);
  if (!pw)
    {
      fprintf (stderr,
	       _("%s: unable to locate passwd record for uid %d\n"),
	       program_name, uid);
      return (0);
    }
  spw = getspnam (pw->pw_name);
  if (spw)
    login_password = spw->sp_pwdp;
  else
    login_password = pw->pw_passwd;

  if (!login_password || (strlen (login_password) == 0))
    {
      fprintf (stderr,
	       _("%s: unable to locate passwd record for %s\n"),
	       program_name, pw->pw_name);
      return (0);
    }

  if (uid == 0)
    {
      sprintf (prompt, _("Enter %s's root login password:"),
	       target_host);
    }
  else
    (void) sprintf (prompt, _("Enter %s's login password:"),
		    pw->pw_name);

  pass = getpass (prompt);
  if (pass && strlen (pass) == 0)
    {
      fprintf (stderr, _("%s: Password unchanged.\n"), program_name);
      return 0;
    }
  strcpy (password, pass);

  /* Verify that password supplied matches login password */
  encrypted_password = crypt (password, login_password);
  if (strcmp (encrypted_password, login_password) == 0)
    passwords_matched = 1;
  else
    {
      fprintf (stderr,
	       _("%s: ERROR, password differs from login password.\n"),
	       program_name);
      return 0;
    }

  /* Check for mis-typed password */
  if (!passwords_matched)
    {
      pass = getpass (_("Please retype password:"));
      if (pass && (strcmp (password, pass) != 0))
	{
	  fprintf (stderr,
		   _("%s: password incorrect.\n"), program_name);
	  return 0;
	}
    }
  return password;
}
