/*
 * 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
 */
/*
 * Copyright (C) 1986, Sun Microsystems, Inc.
 */

/*
 * Command to change one's public key in the public key database
 */


#if defined(HAVE_CONFIG_H)
#include <config.h>
#endif

#define _GNU_SOURCE

#include <crypt.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef HAVE_GETOPT_H
#include <getopt.h>
#else
#include "lib/compat/getopt.h"
#endif
#include <netdb.h>
#include <pwd.h>
#include <shadow.h>
#include <string.h>
#include <rpc/rpc.h>
#include <rpc/key_prot.h>
#include <rpcsvc/ypclnt.h>
#include <rpcsvc/nis.h>
#include <fcntl.h>
#include <locale.h>
#include <libintl.h>

#include "key_common.h"

#define _(String) gettext (String)

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


extern int xencrypt (char *, char *);
extern int key_setnet (struct key_netstarg *arg);

char program_name[256];
static int getpasswd (uid_t, char *, char *, char *);
static void Usage (int);
static void write_rootkey (char *);
static int keylogin (char *, char *);
static int setpublicmap (char *, char *, char *, int);
static char PKMAP[] = "publickey.byname";
static char PKFILE[] = "/etc/publickey";
static char ROOTKEY_FILE[] = "/etc/.rootkey";

static int reencrypt_only = 0;

int
main (int argc, char **argv)
{
  char netname[MAXNETNAMELEN + 1];
  char domain[MAXNETNAMELEN + 1];
  char public[HEXKEYBYTES + 1];
  char secret[HEXKEYBYTES + 1];
  char crypt1[HEXKEYBYTES + KEYCHECKSUMSIZE + 1];
  char newpass[9];		/* as per getpass() man page */
  int status;
  uid_t uid;
  int pk_database = 0;
  char *pk_service = NULL;

  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, 'h'},
	{"usage", no_argument, NULL, 'h'},
	{NULL, 0, NULL, '\0'}
      };

      c = getopt_long (argc, argv, "ps:h", long_options, &option_index);
      if (c == EOF)
	break;
      switch (c)
	{
	case 'p':
	  reencrypt_only = 1;
	  break;
	case 's':
	  if (pk_service == NULL)
	    pk_service = optarg;
	  else
	    Usage (1);
	  break;
	case 'h':
	  Usage (0);
	  break;
	case '\255':
	  fputs ("chkey (" PACKAGE ") " VERSION "\n", stdout);
	  exit (0);
	default:
	  Usage (1);
	}
    }

  argc -= optind;
  argv += optind;

  if (argc != 0)
    Usage (1);

  if ((pk_database = get_pk_source (pk_service)) == 0)
    Usage (1);

  getdomainname (domain, MAXHOSTNAMELEN);
  /*
   * note: we're using getuid() and not geteuid() as
   * chkey is a root set uid program.
   */
  if (getnetnameof (netname, uid = getuid (), domain) < 0)
    {
      fprintf (stderr, _("%s: cannot generate netname for uid %d\n"),
	       program_name, uid);
      exit (1);
    }

  if (reencrypt_only)
    fprintf (stdout, _("Reencrypting key for '%s'.\n"), netname);
  else
    fprintf (stdout, _("Generating new key for '%s'.\n"), netname);

  if (!getpasswd (uid, netname, newpass, secret))
    exit (1);
  /* at this point geteuid() == uid */

  if (reencrypt_only)
    {
      if (!getpublickey (netname, public))
	{
	  fprintf (stderr, _("%s: cannot get public key for %s.\n"),
		   program_name, netname);
	  exit (1);
	}
    }
  else
    genkeys (public, secret, newpass);

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

  status = setpublicmap (netname, public, crypt1, pk_database);

  if (status)
    {
      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, _("could not update; database %d unknown\n"),
		   pk_database);
	}
      exit (1);
    }

  if (uid == 0 && !reencrypt_only)
    {
      /*
       * Root users store their key in /etc/$ROOTKEY so
       * that they can auto reboot without having to be
       * around to type a password.
       */
      write_rootkey (secret);
    }
  if (!reencrypt_only)
    keylogin (netname, secret);
  exit (0);
}

static void
Usage (int exitcode)
{
  fprintf (stderr, _("Usage: %s [-p] [-s nisplus | nis | files] \n"),
	   program_name);
  exit (exitcode);
}


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

  sprintf (pkent, "%s:%s", public, secret);
  switch (database)
    {
    case PK_YP:
      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);
	}
      fprintf (stdout, _("Sending key change request to %s ...\n"), master);
      return (yp_update (domain, PKMAP, YPOP_STORE,
			 netname, strlen (netname), pkent,
			 strlen (pkent)));
    case PK_FILES:
      if (geteuid () != 0)
	{
	  fprintf (stderr,
		   _("%s: non-root users cannot change their key-pair in %s\n"),
		   program_name, PKFILE);
	  exit (1);
	}
      return (localupdate (netname, PKFILE, YPOP_STORE, pkent));
    case PK_NISPLUS:
      nis_princ = get_nisplus_principal (nis_local_directory (),
					 geteuid ());
      return (nisplus_update (netname, "DES", public, secret, nis_princ));
    default:
      break;
    }
  return (1);
}

/* write unencrypted secret key into root key file */
static void
write_rootkey (char *secret)
{
  char sbuf[HEXKEYBYTES + 2];
  int fd, len;

  strcpy (sbuf, secret);
  strcat (sbuf, "\n");
  len = strlen (sbuf);
  sbuf[len] = '\0';
  unlink (ROOTKEY_FILE);
  if ((fd = open (ROOTKEY_FILE, O_WRONLY + O_CREAT, 0600)) != -1)
    {
      write (fd, sbuf, len + 1);
      close (fd);
      fprintf (stdout, _("Wrote secret key into %s\n"), ROOTKEY_FILE);
    }
  else
    {
      fprintf (stderr, _("Could not open %s for update\n"), ROOTKEY_FILE);
      perror (ROOTKEY_FILE);
    }
}

/*
 * populate 'newpass' and 'secret' and pass back
 * force will be only supported for a while
 *      -- it is NOT documented as of s1093
 */
static int
getpasswd (uid_t uid, char *netname, char *newpass, char *secret)
{
  char *rpcpass = NULL, *x_rpcpass = NULL;
  char *l_pass = NULL, *x_lpass = NULL;
  struct passwd *pw;
  struct spwd *spw;
  char *login_passwd = NULL;
  char prompt[256];


  /*
   * get the shadow passwd from the repository
   */
  if ((pw = getpwuid (uid)) == 0)
    {
      fprintf (stderr, _("%s: unable to locate passwd entry for uid %d\n"),
	       program_name, uid);
      return 0;
    }
  if ((spw = getspnam (pw->pw_name)) != NULL)
    login_passwd = spw->sp_pwdp;
  else
    login_passwd = pw->pw_passwd;

  /*
   * now set effective uid to users uid
   */
  seteuid (uid);

  /*
   * get the old encryption passwd - this may or may not be
   * their old login passwd
   */
  sprintf (prompt, _("Please enter the Secure-RPC password for %s:"),
	   pw->pw_name);
  rpcpass = getpass (prompt);
  if (rpcpass == NULL)
    {
      fprintf (stderr, _("%s: key-pair unchanged for %s.\n"),
	       program_name, pw->pw_name);
      return (0);
    }

  /*
   * get the secret key from the key respository
   */
  if (!getsecretkey (netname, secret, rpcpass))
    {
      fprintf (stderr, _("%s: could not get secret key for '%s'\n"),
	       program_name, netname);
      return (0);
    }
  /*
   * check if it is zero -> if it is then ask for the passwd again.
   * then attempt to get the secret again
   */
  if (secret[0] == 0)
    {
      sprintf (prompt, _("Try again. Enter the Secure-RPC password for %s:"),
	       pw->pw_name);
      rpcpass = getpass (prompt);
      if (rpcpass == NULL)
	{
	  fprintf (stderr, _("%s: key-pair unchanged for %s.\n"),
		   program_name, pw->pw_name);
	  return (0);
	}
      if (!getsecretkey (netname, secret, rpcpass))
	{
	  fprintf (stderr, _("%s: could not get secret key for '%s'\n"),
		   program_name, netname);
	  return (0);
	}
      if (secret[0] == 0)
	{
	  fprintf (stderr, _("%s: Unable to decrypt secret key for %s.\n"),
		   program_name, netname);
	  return (0);
	}
    }
  /*
   * check if 'user' has a key cached with keyserv.
   * if there is no key cached, then keylogin the user.
   * if uid == 0, might aswell write it to /etc/$ROOTKEY,
   * assuming that if there is a /etc/$ROOTKEY then the
   * roots' secret key should already be cached.
   */
  if (!key_secretkey_is_set ())
    {
      keylogin (netname, secret);
      if ((uid == 0) && (reencrypt_only))
	write_rootkey (secret);
    }

#if 0
  if (force)
    {
      /* simply get the new passwd - no checks */
      sprintf (prompt, _("Please enter New password:"));
      l_pass = getpass (prompt);
      if (l_pass && (strlen (l_pass) != 0))
	{
	  strcpy (newpass, l_pass);
	  return (1);
	}
      fprintf (stderr, _("%s: key-pair unchanged for %s.\n"),
	       program_name, pw->pw_name);
      return (0);
    }
#endif

  /*
   * check if the secure-rpc passwd given above matches the
   * the unix login passwd
   */
  if (login_passwd && (strlen (login_passwd) != 0))
    {
      /* NOTE: 1st 2 chars of an encrypted passwd = salt */
      x_rpcpass = crypt (rpcpass, login_passwd);
      if (strcmp (x_rpcpass, login_passwd) != 0)
	{
	  /*
	   * the passwds don't match, get the unix
	   * login passwd
	   */
	  sprintf (prompt, _("Please enter the login password for %s:"),
		   pw->pw_name);
	  l_pass = getpass (prompt);
	  if (l_pass && (strlen (l_pass) != 0))
	    {
	      x_lpass = crypt (l_pass, login_passwd);
	      if (strcmp (x_lpass, login_passwd) != 0)
		{
		  /* try again ... */
		  sprintf (prompt,
		     _("Try again. Please enter the login password for %s:"),
			   pw->pw_name);
		  l_pass = getpass (prompt);
		  if (l_pass && (strlen (l_pass) != 0))
		    {
		      x_lpass = crypt (l_pass, login_passwd);
		      if (strcmp (x_lpass, l_pass) != 0)
			{
			  fputs (_("Sorry.\n"), stderr);
			  return 0;
			}
		      /* passwds match */
		      strcpy (newpass, l_pass);
		    }
		  else
		    {
		      fprintf (stderr, _("%s: key-pair unchanged for %s.\n"),
			       program_name, pw->pw_name);
		      return 0;
		    }
		}
	      /* passwds match */
	      strcpy (newpass, l_pass);
	    }
	  else
	    {
	      /* need a passwd */
	      sprintf (prompt,
		 _("Need a password. Please enter the login password for %s:"),
		       pw->pw_name);
	      l_pass = getpass (prompt);
	      if (l_pass && (strlen (l_pass) != 0))
		{
		  x_lpass = crypt (l_pass, login_passwd);
		  if (strcmp (x_lpass, l_pass) != 0)
		    {
		      fputs (_("Sorry.\n"), stderr);
		      return 0;
		    }
		  /* passwds match */
		  strcpy (newpass, l_pass);
		}
	      else
		{
		  fprintf (stderr, _("%s: key-pair unchanged for %s.\n"),
			   program_name, pw->pw_name);
		  return 0;
		}
	    }
	}
      /* rpc and login passwds match */
      strcpy (newpass, rpcpass);
    }
  else
    {				/* no login passwd entry */
      fprintf (stderr,
	      _("%s: no passwd found for %s in the shadow passwd entry.\n"),
	       program_name, pw->pw_name);
      return 0;
    }
  return 1;
}

static int
keylogin (char *netname, char *secret)
{
  struct key_netstarg netst;

  netst.st_pub_key[0] = 0;
  strcpy (netst.st_priv_key, secret);
  netst.st_netname = netname;

  /* do actual key login */
  if (key_setnet (&netst) < 0)
    {
      fprintf (stderr, _("Could not set %s's secret key\n"), netname);
      fprintf (stderr, _("May be the keyserv is down?\n"));
      return 0;
    }
  return 1;
}
