/* Copyright (c) 1997, 1998, 1999 Thorsten Kukuk

   Author: Thorsten Kukuk <kukuk@vt.uni-paderborn.de>

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

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

#define _XOPEN_SOURCE
#define _GNU_SOURCE

#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <shadow.h>
#include <locale.h>
#include <libintl.h>
#include <rpcsvc/nis.h>
#include <rpc/key_prot.h>

#include "key_common.h"

#ifndef _
#define _(String) gettext (String)
#endif

extern int xencrypt (char *secret, char *passwd);
char program_name[] = "nisaddcred";
static char ROOTKEY_FILE[] = "/etc/.rootkey";

/* 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);
    }
}

/* Print the version information.  */
static inline void
print_version (void)
{
  fprintf (stdout, "nisaddcred (%s) %s\n", PACKAGE, VERSION);
  fprintf (stdout, gettext ("\
Copyright (C) %s Thorsten Kukuk.\n\
This is free software; see the source for copying conditions.  There is NO\n\
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
"), "1998");
  /* fprintf (stdout, _("Written by %s.\n"), "Thorsten Kukuk"); */
}

static inline void
print_usage (void)
{
  fputs (_("Usage: nisaddcred [-p  principal] [-P nis_principal] [-l password] auth_type\n                    [domainname]\n"),
	 stdout);
  fputs (_("       nisaddcred -r nis_principal [domainname]\n"), stdout);
}

static void
print_help (void)
{
  print_usage ();
  fputs (_("nisaddcred - create NIS+ credentials\n\n"),
         stdout);
  fputs (_("  -p principal      Secure RPC netname\n"), stdout);
  fputs (_("  -P nis_principal  NIS+ principal name\n"), stdout);
  fputs (_("  -l password       Use password instead of asking for one\n"),
	 stdout);
  fputs (_("  -r nis_principal  Remove all credentials\n"), stdout);
  fputs (_("  --help            Give this help list\n"), stdout);
  fputs (_("  --usage           Give a short usage message\n"), stdout);
  fputs (_("  --version         Print program version\n"), stdout);
  fputs (_("\nSupported auth_types are DES and LOCAL\n\n"), stdout);
}

static inline void
print_error (void)
{
  fprintf (stderr,
           _("Try `%s --help' or `%s --usage' for more information.\n"),
           program_name, program_name);
}

int
main (int argc, char *argv[])
{
  char *auth_name = NULL;	/* principal: netname & uid */
  char *cname = NULL;		/* nis_principal */
  char *password = NULL;
  char *domainname = NULL;
  char remove_principal = 0;
  int no_pflag = 1;

  setlocale (LC_MESSAGES, "");
  bindtextdomain (PACKAGE, LOCALEDIR);
  textdomain (PACKAGE);

  while (1)
    {
      int c;
      int option_index = 0;
      static struct option long_options[] =
      {
	{"version", no_argument, NULL, '\255'},
	{"usage", no_argument, NULL, '\254'},
	{"help", no_argument, NULL, '\253'},
	{NULL, 0, NULL, '\0'}
      };

      c = getopt_long (argc, argv, "uhp:P:l:r:", long_options, &option_index);
      if (c == (-1))
	break;
      switch (c)
	{
	case 'P':		/* usr.foo.bar. */
	  cname = optarg;
	  no_pflag = 0;
	  break;
	case 'p':		/* unix.uid@foo.bar */
	  auth_name = optarg;
	  no_pflag = 0;
	  break;
	case 'l':
	  password = optarg;
	  break;
	case 'r':
	  remove_principal = 1;
	  cname = optarg;
	  break;
	case '\253':
	  print_help ();
	  return 0;
	case '\255':
	  print_version ();
	  return 0;
	case '\254':
	  print_usage ();
	  return 0;
	default:
	  print_error ();
	  return 1;
	}
    }

  argc -= optind;
  argv += optind;

  if ((argc < 1 && !remove_principal) || argc > 2 ||
      (remove_principal && argc > 1))
    {
      fprintf (stderr, _("%s: Wrong number of arguments\n"), "nisaddcred");
      print_error ();
      return 1;
    }

  if (argc == 2)
    domainname = argv[1];
  else
    domainname = nis_local_directory ();

  if (remove_principal)
    {
      nis_result *res;
      char *buf, *cp;

      buf = alloca (strlen (cname) + strlen (domainname) + 25);
      if (buf == NULL)
	{
	  fputs (_("Out of memory!\n"), stderr);
	  return 1;
	}

      cp = stpcpy (buf, "[cname=");
      cp = stpcpy (cp, cname);
      cp = stpcpy (cp, "],cred.org_dir.");
      cp = stpcpy (cp, domainname);

      if (cp[-1] != '.')
	{
	  *cp++ = '.';
	  *cp = '\0';
	}

      res = nis_remove_entry (buf, NULL, REM_MULTIPLE);

      if (res->status != NIS_SUCCESS)
	{
	  fprintf (stderr, "%s: %s\n", cname, nis_sperrno(res->status));
	  nis_freeresult(res);
	  return 1;
	}

      nis_freeresult(res);
      return 0;
    }

  /* cname is user.domain.bar. */
  if (cname == NULL)
    {
      uid_t uid = getuid ();

      if (uid == 0)
	cname = nis_local_host ();
      else
	{
	  struct passwd *pwd = getpwuid (uid);
	  char *cp;

	  if (pwd == NULL)
	    {
	      fprintf (stderr, _("Couldn't find user data for uid %d\n"),
		       uid);
	      return 1;
	    }
	  cname = malloc (strlen (pwd->pw_name) +
			  strlen (nis_local_directory ()) + 5);
	  cp = stpcpy (cname, pwd->pw_name);
	  *cp++ = '.';
	  cp = stpcpy (cp, nis_local_directory ());
	  if (cp[-1] != '.')
	    {
	      *cp++ = '.';
	      *cp = '\0';
		}
	}
    }

  if (strcasecmp (argv[0], "des") == 0)
    {
      char public[HEXKEYBYTES + 1] = "\0";
      char secret[HEXKEYBYTES + 1] = "\0";
      char crypt1[HEXKEYBYTES + KEYCHECKSUMSIZE + 1];

      /* auth_name should be secure RPC netname user.id@domain.bar */
      if (auth_name == NULL)
	{
	  char name[MAXNETNAMELEN + 2];
	  getnetnameof (name, getuid (), domainname);
	  auth_name = strdup (name);
	}

      fprintf (stdout, _("DES principal name : %s\n"), auth_name);
      fprintf (stdout, _("Adding key pair for %s (%s).\n"), auth_name, cname);

      if (!sanity_checks (cname, auth_name, domainname))
	return 1;

      if (password == NULL)
	password = getpass (_("Enter login password:"));
      if (password == NULL)
	return 1;

#if 0
      /* Verify that password supplied matches login password */
      pwd = getpwnam (cname);
      if (pwd != NULL)
	{
	  if (strcmp (crypt (password, pwd->pw_passwd), pwd->pw_passwd) != 0)
	    {
	      char *pass;

	      fputs (_("nisaddcred: WARNING: password differs from login password.\n"),
		     stderr);
	      pass = getpass (_("Retype password"));
	      if (pass != password)
		{
		  fputs (_("nisaddcred: password incorrect.\n"), stderr);
		  fputs (_("nisaddcred: unable to create credential.\n"),
			 stderr);
		  return 1;
		}
	    }
	}
#endif

      genkeys (public, secret, password);

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

      if (nisplus_update (auth_name, "DES", public, crypt1, cname))
	{
	  fputs (_("nisaddcred: unable to update nisplus database\n"), stderr);
	  return 1;
	}

      if (getuid () == 0 && no_pflag)
	write_rootkey (secret);
    }
  else if (strcasecmp (argv[0], "local") == 0)
    {
      char public[1024];
      struct group *gr;
      char name[strlen (cname) + 51];
      char *cp;
      size_t i;

      strcpy (name, cname);
      cp = strchr (name, '.');
      if (cp == NULL)
	{
	  fputs (_("nisaddcred: illegal nis_principal\n"), stderr);
	  return 1;
	}
      *cp = '\0';

      if (auth_name == NULL)
	{
	  char buf[51];
	  struct passwd *pwd;

	  pwd = getpwnam (name);

	  if (pwd == NULL) /* Ups, I think we have a hostname ? */
	    {
	      fputs (_("nisaddcred: LOCAL is not supported for hostnames\n"),
		     stderr);
	      return 1;
	    }

	  snprintf (buf, 50, "%u", pwd->pw_uid);
	  auth_name = strdup (buf);
	}

      public[0] = '\0';
      setgrent ();
      while ((gr = getgrent ()) != NULL)
	{
	  i = 0;

	  while (gr->gr_mem[i] != NULL)
	    {
	      if (strcmp (gr->gr_mem[i], name) == 0)
		{
		  char buf[51];

		  snprintf (buf, 50, "%u", gr->gr_gid);
		  if (strlen (public) + strlen (buf) + 1 < sizeof (public))
		    {
		      if (strlen (public) > 0)
			strcat (public, ",");
		      strcat (public, buf);
		    }
		}
	      ++i;
	    }
	}
      endgrent ();

      if (nisplus_update (auth_name, "LOCAL", public, NULL, cname))
	{
	  fprintf (stderr, _("nisaddcred: unable to update nisplus database\n"));
	  return 1;
	}
    }
  else
    {
      fprintf (stderr, _("nisaddcred: unknown auth_type %s\n"), argv[0]);
      return 1;
    }

  return 0;
}
