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

#include <pwd.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <shadow.h>
#include <netdb.h>
#include <malloc.h>
#include <mp.h>
#include <rpcsvc/nis.h>
#include <rpc/key_prot.h>
#include <locale.h>
#include <libintl.h>

#include "key_common.h"

#define _(String) gettext (String)

extern char program_name[];

/* ******************* netname generation *************************** */
#define	MAXIPRINT	11
#define	OPSYS_LEN	4
static const char *OPSYS = "unix";

int
getnetnameof (char *netname, uid_t uid, char *domain)
{
  int status;

  if (uid == 0)
    status = host2netname (netname, (char *) NULL, domain);
  else
    {
      /* generate netname using uid and domain information. */
      int len;
      len = strlen (domain);
      if ((len + OPSYS_LEN + 3 + MAXIPRINT) > MAXNETNAMELEN)
	{
	  fprintf (stderr, _("Domain name too long: %s\n"), domain);
	  return -1;
	}
      sprintf (netname, "%s.%ld@%s", OPSYS, (u_long)uid, domain);
      len = strlen (netname);
      if (netname[len - 1] == '.')
	netname[len - 1] = '\0';

      status = 1;
    }

  return status;
}

/* ****************************** nisplus stuff ************************* */
/* most of it gotten from nisaddcred */

/* Check that someone else don't have the same auth information already */
static nis_error
auth_exists (char *cname, char *auth_name, const char *auth_type,
	     char *domain)
{
  char sname[NIS_MAXNAMELEN + 1];
  nis_result *res;
  char *foundprinc;

  sprintf (sname, "[auth_name=%s,auth_type=%s],cred.org_dir.%s",
	   auth_name, auth_type, domain);
  if (sname[strlen (sname) - 1] != '.')
    strcat (sname, ".");
  /* Don't want FOLLOW_PATH here */
  res = nis_list (sname, MASTER_ONLY + USE_DGRAM + NO_AUTHINFO + FOLLOW_LINKS,
		  NULL, NULL);

  switch (res->status)
    {
    case NIS_PARTIAL:
    case NIS_NOTFOUND:
      break;
    case NIS_TRYAGAIN:
      fprintf (stderr, _("%s: NIS+ server busy, try again later.\n"),
	       program_name);
      return 1;
    case NIS_PERMISSION:
      fprintf (stderr,
	       _("%s: insufficent permission to look up old credentials.\n"),
	       program_name);
      return 1;
    case NIS_SUCCESS:
      foundprinc = ENTRY_VAL (res->objects.objects_val, 0);
      if (nis_dir_cmp (foundprinc, cname) != SAME_NAME)
	{
	  fprintf (stderr,
	  _("%s: %s credentials with auth_name '%s' already belongs to '%s'.\n"),
		   program_name, auth_type, auth_name, foundprinc);
	  return 1;
	}
      break;
    default:
      fprintf (stderr, _("%s: error looking at cred table, NIS+ error: %s\n"),
	       program_name, nis_sperrno (res->status));
      return 1;
    }
  nis_freeresult (res);
  return 0;
}

/* Returns 0 if check fails; 1 if successful. */
int
sanity_checks (char *cname, char *netname, char *domain)
{
  char netdomainaux[MAXHOSTNAMELEN + 1];
  char *princdomain, *netdomain;
  int len;

  /* Sanity check 0: Do we have a nis+ principal name to work with? */
  if (cname == NULL)
    {
      fprintf (stderr,
	       _("%s: you must create a \"LOCAL\" credential for '%s' first.\n"),
	       program_name, netname);
      fprintf (stderr, _("\tSee nisaddcred(1).\n"));
      return 0;
    }

  /* Sanity check 1:  NIS+ principal (cname) names must be dotted. */
  len = strlen (cname);
  if (cname[len - 1] != '.')
    {
      fprintf (stderr,
	       _("%s: invalid principal name: '%s' (forgot ending dot?).\n"),
	       program_name, cname);
      return 0;
    }

  /* Sanity check 2:  We only deal with one type of netnames. */
  if (strncmp (netname, OPSYS, OPSYS_LEN) != 0)
    {
      fprintf (stderr, _("%s: unrecognized netname type: '%s'.\n"),
	       program_name, netname);
      return 0;
    }

  /* Sanity check 3:  Should only add DES cred in home domain. */
  princdomain = nis_domain_of (cname);
  if (strcasecmp (princdomain, domain) != 0)
    {
      fprintf (stderr,
	       _("%s: domain of principal '%s' does not match destination domain '%s'.\n"),
	       program_name, cname, domain);
      fprintf (stderr,
	_("Should only add DES credential of principal in its home domain\n"));
      return 0;
    }


  /* Sanity check 4:  Make sure netname's domain same as principal's
     and don't have extraneous dot at the end */
  netdomain = (char *) strchr (netname, '@');
  if (!netdomain || netname[strlen (netname) - 1] == '.')
    {
      fprintf (stderr, _("%s: invalid netname: '%s'. \n"), program_name,
	       netname);
      return 0;
    }
  netdomain++;			/* skip '@' */
  strcpy (netdomainaux, netdomain);
  if (netdomainaux[strlen (netdomainaux) - 1] != '.')
    strcat (netdomainaux, ".");

  if (strcasecmp (princdomain, netdomainaux) != 0)
    {
      fprintf (stderr,
	       _("%s: domain of netname %s should be same as that of principal %s\n"),
	       program_name, netname, cname);
      return 0;
    }

  /* Another principal owns same credentials? */
  return !auth_exists (cname, netname, "DES", domain);
}

/* Similar to nis_local_principal (nis_subr.c) except
 * this gets the results from the MASTER_ONLY and no FOLLOW_PATH.
 * We only want the master because we'll be making updates there,
 * and also the replicas may not have seen the 'nisaddacred local'
 * that may have just occurred.
 * Returns NULL if not found.
 */
char *
get_nisplus_principal (char *directory, uid_t uid)
{
  static char principal_name[NIS_MAXNAMELEN + 1];
  nis_result *res;
  char buf[NIS_MAXNAMELEN + 1];

  if (uid == 0)
    return (nis_local_host ());

  sprintf (buf, "[auth_name=%ld,auth_type=LOCAL],cred.org_dir.%s",
	   (u_long) uid, directory);

  if (buf[strlen (buf) - 1] != '.')
    strcat (buf, ".");

  res = nis_list (buf, MASTER_ONLY + USE_DGRAM + NO_AUTHINFO + FOLLOW_LINKS,
		  NULL, NULL);

  if (res == NULL)
    {
      fprintf (stderr, _("%s: unable to get result from NIS+ server.\n"),
	       program_name);
      exit (1);
    }
  switch (res->status)
    {
    case NIS_SUCCESS:
      if (res->objects.objects_len > 1)
	{
	  /*
	   * More than one principal with same uid?
	   * something wrong with cred table. Should be unique
	   * Warn user and continue.
	   */
	  fprintf (stderr,
		   _("%s: LOCAL entry for %ld in directory %s not unique\n"),
		   program_name, (u_long) uid, directory);
	}
      strcpy (principal_name, ENTRY_VAL (res->objects.objects_val, 0));
      nis_freeresult (res);
      return (principal_name);

    case NIS_PARTIAL:
    case NIS_NOTFOUND:
      nis_freeresult (res);
      return NULL;

    case NIS_TRYAGAIN:
      fprintf (stderr, _("%s: NIS+ server busy, try again later.\n"),
	       program_name);
      exit (1);

    case NIS_PERMISSION:
      fprintf (stderr, _("%s: insufficent permission to update credentials.\n"),
	       program_name);
      exit (1);

    default:
      fprintf (stderr, _("%s: error talking to server, NIS+ error: %s.\n"),
	       program_name, nis_sperrno (res->status));
      exit (1);
    }
  return NULL;
}

/* Check whether this principal already has this type of credentials */
static nis_error
cred_exists (char *cname, const char *flavor, char *domain)
{
  char sname[NIS_MAXNAMELEN + 1];
  nis_result *res;
  nis_error status;

  sprintf (sname, "[cname=%s,auth_type=%s],cred.org_dir.%s",
	   cname, flavor, domain);
  if (sname[strlen (sname) - 1] != '.')
    strcat (sname, ".");
  /* Don't want FOLLOW_PATH here */
  res = nis_list (sname, MASTER_ONLY + USE_DGRAM + NO_AUTHINFO + FOLLOW_LINKS,
		  NULL, NULL);

  status = res->status;
  switch (status)
    {
    case NIS_PARTIAL:
      status = NIS_NOTFOUND;
      break;
    case NIS_NOTFOUND:
    case NIS_SUCCESS:
    case NIS_S_SUCCESS:
      break;
    case NIS_TRYAGAIN:
      fprintf (stderr, _("%s: NIS+ server busy, try again later.\n"),
	       program_name);
      exit (1);
    case NIS_PERMISSION:
      fprintf (stderr,
	       _("%s: insufficent permission to look at credentials table\n"),
	       program_name);
      exit (1);
    default:
      fprintf (stderr, _("%s: error looking at cred table, NIS+ error: %s\n"),
	       program_name, nis_sperrno (status));
      exit (1);
    }
  nis_freeresult (res);
  return status;
}


/* Returns 0 if operation fails; 1 if successful. */
static int
modify_cred_obj (nis_object * obj, char *domain)
{
  int status = 0;
  char sname[NIS_MAXNAMELEN + 1];
  nis_result *res;

  sprintf (sname, "cred.org_dir.%s", domain);
  res = nis_modify_entry (sname, obj, 0);
  switch (res->status)
    {
    case NIS_TRYAGAIN:
      fprintf (stderr, _("%s: NIS+ server busy, try again later.\n"),
	       program_name);
      exit (1);
    case NIS_PERMISSION:
      fprintf (stderr, _("%s: insufficent permission to update credentials.\n"),
	       program_name);
      exit (1);
    case NIS_SUCCESS:
      status = 1;
      break;
    default:
      fprintf (stderr, _("%s: error modifying credential, NIS+ error: %s.\n"),
	       program_name, nis_sperrno (res->status));
      exit (1);
    }
  nis_freeresult (res);
  return (status);
}

static int
add_cred_obj (nis_object * obj, char *domain)
{
  int status = 0;
  char sname[NIS_MAXNAMELEN + 1];
  nis_result *res;

  /* Assume check for cred_exists performed already */

  sprintf (sname, "cred.org_dir.%s", domain);
  res = nis_add_entry (sname, obj, 0);
  switch (res->status)
    {
    case NIS_TRYAGAIN:
      fprintf (stderr, _("%s: NIS+ server busy, try again later.\n"),
	       program_name);
      exit (1);
    case NIS_PERMISSION:
      fprintf (stderr, _("%s: insufficent permission to update credentials.\n"),
	       program_name);
      exit (1);
    case NIS_SUCCESS:
      status = 1;
      break;
    default:
      fprintf (stderr, _("%s: error creating credential, NIS+ error: %s.\n"),
	       program_name, nis_sperrno (res->status));
      exit (1);
    }
  nis_freeresult (res);
  return (status);
}

static nis_object *
init_entry (void)
{
  static nis_object obj;
  static entry_col cred_data[10];
  entry_obj *eo;

  memset ((char *) (&obj), 0, sizeof (obj));
  memset ((char *) (cred_data), 0, sizeof (entry_col) * 10);

  obj.zo_name = (char *) "cred";
  obj.zo_group = (char *) "";
  obj.zo_ttl = 43200;
  obj.zo_data.zo_type = NIS_ENTRY_OBJ;
  eo = &(obj.EN_data);
  eo->en_type = (char *) "cred_tbl";
  eo->en_cols.en_cols_val = cred_data;
  eo->en_cols.en_cols_len = 5;
  cred_data[4].ec_flags |= EN_CRYPT;
  return (&obj);
}

/* Returns 0 if successful; -1 if failure. (expected by setpublicmap). */

int
nisplus_update (char *netname, const char *auth_type, char *public,
		char *secret, nis_name cname)
{
  nis_object *obj = init_entry ();
  char *domain = nis_local_directory ();
  int status, addition;

  if (strcmp (auth_type, "DES") == 0 &&
      sanity_checks (cname, netname, domain) == 0)
    return (-1);

  addition = (cred_exists (cname, auth_type, domain) == NIS_NOTFOUND);

  /* Now we have a key pair, build up the cred entry */
  ENTRY_VAL (obj, 0) = cname;
  ENTRY_LEN (obj, 0) = strlen (cname) + 1;

  ENTRY_VAL (obj, 1) = (char *)auth_type;
  ENTRY_LEN (obj, 1) = strlen (auth_type) + 1;

  ENTRY_VAL (obj, 2) = netname;
  ENTRY_LEN (obj, 2) = strlen (netname) + 1;

  ENTRY_VAL (obj, 3) = public;
  ENTRY_LEN (obj, 3) = strlen (public) + 1;

  if (secret != NULL && strlen (secret) > 0)
    {
      ENTRY_VAL (obj, 4) = secret;
      ENTRY_LEN (obj, 4) = strlen (secret) + 1;
    }
  else
    {
      ENTRY_VAL (obj, 4) = NULL;
      ENTRY_LEN (obj, 4) = 0;
    }

  if (addition)
    {
      obj->zo_owner = cname;
      obj->zo_group = nis_local_group ();
      obj->zo_domain = domain;
      /* owner: r, group: rmcd */
      obj->zo_access = ((NIS_READ_ACC << 16) |
			(NIS_READ_ACC | NIS_MODIFY_ACC | NIS_CREATE_ACC |
			 NIS_DESTROY_ACC) << 8);
      status = add_cred_obj (obj, domain);
    }
  else
    {
      obj->EN_data.en_cols.en_cols_val[3].ec_flags |= EN_MODIFIED;
      obj->EN_data.en_cols.en_cols_val[4].ec_flags |= EN_MODIFIED;
      status = modify_cred_obj (obj, domain);
    }

  return (status == 1 ? 0 : -1);
}
