/* Copyright (C) 1998, 1999 Thorsten Kukuk
   Author: Thorsten Kukuk <kukuk@suse.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.  */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#define _GNU_SOURCE

#include <crypt.h>
#include <fcntl.h>
#include <locale.h>
#include <libintl.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_FILIO_H
#include <sys/filio.h>
#endif
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <rpc/rpc.h>
#ifdef HAVE_RPC_SVC_SOC_H
#include <rpc/svc_soc.h>
#endif
#include <rpc/des_crypt.h>
#include <rpc/key_prot.h>
#include <rpcsvc/nis.h>

#include "npd_nis.h"
#include "log_msg.h"
#include "key_common.h"

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

extern int key_get_conv (char *pkey, des_block *deskey);
extern int xencrypt (char *, char *);

char *program_name = "rpc.nispasswdd";

struct npd_cache {
  bool_t in_use;
  time_t last_failed;
  u_int xrandval;
  u_int tries;
  char *username;
  char *domain;
  char *oldpassword;
  nis_object *obj;
  des_block CK;
};

static struct npd_cache *cache;
static int cache_not_init = 1;
static u_int cache_ident_base = 0;

#define MAX_CACHE_ENTRIES 100

static nis_object *
get_userinfo (char *user, char *domain)
{
  nis_result *result;
  nis_object *obj;
  char buf[strlen (user == NULL ? "" : user) + 24 +
	  strlen (domain == NULL ? "" : user)];

  sprintf (buf, "[name=%s],passwd.org_dir.%s", user, domain);

  result = nis_list (buf, FOLLOW_PATH | FOLLOW_LINKS | EXPAND_NAME, NULL,
		     NULL);

  if (result == NULL || result->status != NIS_SUCCESS ||
      __type_of (result->objects.objects_val) != NIS_ENTRY_OBJ)
    {
      if (result)
	nis_freeresult (result);
      return NULL;
    }
  obj = nis_clone_object (NIS_RES_OBJECT(result), NULL);
  nis_freeresult (result);
  return obj;
}


static u_int
get_xrandval (void)
{
  u_int result;
  int i, seed, shift;
  struct timeval tv;
  int on = 1;
  int fd = open ("/dev/random", O_RDONLY);

  if (fd >= 0 && ioctl (fd, FIONBIO, &on) == 0 &&
      read (fd, &result, sizeof (result)) == sizeof (result))
    {
      close (fd);
      return result;
    }

  if (fd >= 0)
    close (fd);

  seed = 0;
  for (i = 0; i < 1024; ++i)
    {
      gettimeofday (&tv, (struct timezone *) NULL);
      shift = i % 8 * sizeof (int);
      seed ^= (tv.tv_usec << shift) | (tv.tv_usec >> (32 - shift));
    }
  srandom (seed);
  result = random ();
  srandom (seed);

  return result;
}

/* XXX Add isexpired test !!! */
#if 0
static int
isexpired (const struct spwd *sp)
{
  long now = time ((time_t *) 0) / (24L*3600L);

  if (sp->sp_expire > 0 && now >= sp->sp_expire)
    return 1;

  if (sp->sp_lstchg > 0 && sp->sp_max >= 0 && sp->sp_inact >= 0 &&
      now >= sp->sp_lstchg + sp->sp_max + sp->sp_inact)
    return 1;
  if (sp->sp_lstchg == -1 ||
      sp->sp_max == -1 || sp->sp_max >= (10000L))
    return 0;
  if (now >= sp->sp_lstchg + sp->sp_max)
    return 1;
  return 0;
}
#endif

#if !( defined __GLIBC__ && __GLIBC__ >= 2)
extern  bool_t xdr_nis_object (XDR *, nis_object*);

static void
nis_free_object (nis_object *obj)
{
  if (obj != NULL)
    {
      xdr_free ((xdrproc_t)xdr_nis_object, (char *)obj);
      free (obj);
    }
}
#endif

static int
password_ok (char *plain, char *crypted)
{
  if (crypted[0] == '\0')
    return 1;
  if (strcmp (crypt (plain, crypted), crypted) == 0)
    return 1;

  return 0;
}

#define NISENTRYVAL(col,obj) \
        ((obj)->EN_data.en_cols.en_cols_val[(col)].ec_value.ec_value_val)
#define NISENTRYLEN(col,obj) \
        ((obj)->EN_data.en_cols.en_cols_val[(col)].ec_value.ec_value_len)
#define NISENTRYFLAG(col,obj) \
        ((obj)->EN_data.en_cols.en_cols_val[(col)].ec_flags)

bool_t
nispasswd_authenticate_1_svc (npd_request *argp, nispasswd_authresult *result,
			      struct svc_req *rqstp)
{
  bool_t retval = TRUE;
  u_int i, idx = (u_int)-1;
  des_block CK;
  des_block cryptbuf;
  int error;
  char ivec[8];
  char oldpwd[18];
  nis_object *obj;
  struct dir_list *tmp_list;
  long *ixdr;

  if (cache_not_init)
    {
      cache = calloc (1, MAX_CACHE_ENTRIES * sizeof (struct npd_cache));
      if (cache == NULL)
	return FALSE;
      cache_ident_base = random () % MAX_CACHE_ENTRIES + MAX_CACHE_ENTRIES;
      cache_not_init = 0;
    }

  if (debug_flag)
    {
      struct sockaddr_in *rqhost;

      rqhost = svc_getcaller (rqstp->rq_xprt);
      log_msg (LOG_DEBUG, "nispasswd_authenticate() [From: %s:%d]",
               inet_ntoa (rqhost->sin_addr), ntohs (rqhost->sin_port));

      log_msg (LOG_DEBUG, "\t\tusername = \"%s\"", argp->username);
      log_msg (LOG_DEBUG, "\t\tdomain   = \"%s\"", argp->domain);
      log_msg (LOG_DEBUG, "\t\tkey_type = \"%s\"", argp->key_type);
      log_msg (LOG_DEBUG, "\t\tident    = \"%lu\"", argp->ident);
    }

  i = 0;
  tmp_list = dirlist;
  while (tmp_list != NULL)
    {
      if (strcmp (argp->domain, tmp_list->name) == 0)
	{
	  i = 1;
	  break;
	}
      tmp_list = tmp_list->next;
    }
  if (i == 0) /* We are not the master of this domain */
    {
      log_msg (LOG_ERR, _("received NIS+ auth request for %s"),
	       argp->username);
      log_msg (LOG_ERR, _("We are not the master for domain %s !"),
	       argp->domain);

      result->status = NPD_FAILED;
      result->nispasswd_authresult_u.npd_err = NPD_NOTMASTER;
      return retval;
    }

  /* Generate common DES key from public user key and secret host key */
  if (key_get_conv (argp->user_pub_key.user_pub_key_val, &CK) != 0)
    {
      log_msg (LOG_ERR, _("received NIS+ auth request for %s"),
	       argp->username);
      log_msg (LOG_ERR, _("Could not create conversion key !"));

      result->status = NPD_FAILED;
      result->nispasswd_authresult_u.npd_err = NPD_CKGENFAILED;
      return retval;
    }

  if (argp->npd_authpass.npd_authpass_len != 16)
    {
      log_msg (LOG_ERR, _("received NIS+ auth request for %s"),
	       argp->username);
      log_msg (LOG_ERR, _("password length wrong"));

      result->status = NPD_FAILED;
      result->nispasswd_authresult_u.npd_err = NPD_PASSINVALID;
      return retval;
    }

  memset (&ivec, 0, 8);
  memcpy (&oldpwd, argp->npd_authpass.npd_authpass_val, 16);
  oldpwd[16] = '\0';
  error = cbc_crypt ((char *) &CK, (char *) &oldpwd, 16, DES_DECRYPT | DES_HW,
		     ivec);
  if (DES_FAILED (error))
    {
      log_msg (LOG_ERR, _("received NIS+ auth request for %s"),
	       argp->username);
      log_msg (LOG_ERR, _("DES decryption failure!"));
      result->status = NPD_FAILED;
      result->nispasswd_authresult_u.npd_err = NPD_DECRYPTFAIL;
      return retval;
    }

  obj = get_userinfo (argp->username, argp->domain);
  if (obj == NULL)
    {
      log_msg (LOG_ERR, _("received NIS+ auth request for %s"),
	       argp->username);
      log_msg (LOG_ERR, _("no passwd entry exists for this user!"));
      result->status = NPD_FAILED;
      result->nispasswd_authresult_u.npd_err = NPD_NOSUCHENTRY;
      return retval;
    }

  /* The following shouldn't be necessary, but without, idx isn't -1 */
  idx = (u_int)-1;
  for (i = 0; i < MAX_CACHE_ENTRIES; ++i)
    {
      if (cache[i].in_use == FALSE)
	{
	  if (idx == (u_int)-1)
	    idx = i; /* We don't stop, we could find an old try */
	}
      else
	if (strcmp (cache[i].username, argp->username) == 0 &&
	    strcmp (cache[i].domain, argp->domain) ==  0)
	  {
	    idx = i; /* Found an old try */
	    break;
	  }
    }

  if (idx == (u_int)-1 || idx >= MAX_CACHE_ENTRIES)
    {
      log_msg (LOG_ERR, _("received NIS+ auth request for %s"),
	       argp->username);
      log_msg (LOG_ERR, _("system error!"));
      result->status = NPD_FAILED;
      result->nispasswd_authresult_u.npd_err = NPD_SYSTEMERR;
      return retval;
    }

  if (cache[idx].in_use)
    {
      nis_free_object (obj);
      obj = cache[idx].obj;
      free (cache[idx].oldpassword);
    }
  else
    {
      cache[idx].in_use = TRUE;
      cache[idx].xrandval = get_xrandval ();
      cache[idx].tries = 0;
      cache[idx].username = strdup (argp->username);
      cache[idx].domain = strdup (argp->domain);
      cache[idx].obj = obj;
      memcpy (&cache[idx].CK, &CK, sizeof (des_block));
    }
  cache[idx].tries++;
  if (cache[idx].tries > (u_int)attempts)
    {
      time_t now;

      time (&now);
      if (cache[idx].last_failed + (minutes * 60) > now)
	{
	  log_msg (LOG_ERR, _("received NIS+ auth request for %s"),
		   argp->username);
	  log_msg (LOG_ERR, _("too many failed attempts for %s"),
		   argp->username);
	  result->status = NPD_FAILED;
	  result->nispasswd_authresult_u.npd_err = NPD_PASSINVALID;
	  return retval;
	}
      else
	cache[idx].tries = 1;
    }
  time (&cache[idx].last_failed);

  cache[idx].oldpassword = strdup (oldpwd);

  result->status = NPD_SUCCESS;
  result->nispasswd_authresult_u.npd_verf.npd_xid =
    htonl (cache_ident_base + idx);
  result->nispasswd_authresult_u.npd_verf.npd_xrandval =
    htonl (cache[idx].xrandval);

#if 1
  /* This is for Little Endian machines. Should also work with big endian. */
  memset (&cryptbuf, '\0', sizeof (cryptbuf));
  memcpy (&cryptbuf, (char *)&result->nispasswd_authresult_u.npd_verf,
	  sizeof (nispasswd_verf));
  error = ecb_crypt ((char *) &CK, (char*)&cryptbuf, 8, DES_ENCRYPT | DES_HW);
  ixdr = (long *) &result->nispasswd_authresult_u.npd_verf.npd_xid;
  IXDR_PUT_U_INT32 (ixdr, cryptbuf.key.high);
  /* ixdr = (long *) &result->nispasswd_authresult_u.npd_verf.npd_xrandval; */
  IXDR_PUT_U_INT32 (ixdr, cryptbuf.key.low);
#else
  error = ecb_crypt ((char *) &CK,
		     (char *)&result->nispasswd_authresult_u.npd_verf,
		     8, DES_ENCRYPT | DES_HW);
#endif
  if (DES_FAILED (error))
    {
      log_msg (LOG_ERR, _("received NIS+ auth request for %s"),
	       argp->username);
      log_msg (LOG_ERR, _("DES decryption failure!"));
      result->status = NPD_FAILED;
      result->nispasswd_authresult_u.npd_err = NPD_ENCRYPTFAIL;
      return retval;
    }


  /* XXX Check with help of DES auth, if we change our password, or
     for another user. (sameuser=?) */

  if (verbose)
    {
      log_msg (LOG_INFO, _("received NIS+ auth request for %s"),
	       argp->username);
      log_msg (LOG_INFO, _("user=%s, sameuser=?"), argp->username);
      log_msg (LOG_INFO, _("ident=%u, rval=%u"), cache_ident_base + idx,
	       cache[idx].xrandval);
      log_msg (LOG_INFO, _("attempt=%d, domain=%s"), cache[idx].tries,
	       cache[idx].domain);

    }

  /* Get the encrypted password and compare it */
  {
    char buffer[NISENTRYLEN (1, obj) + 1];

    strncpy (buffer, NISENTRYVAL (1, obj), NISENTRYLEN (1, obj));
    buffer [NISENTRYLEN (1, obj)] = '\0';
    if (!password_ok (oldpwd, buffer))
      {
	result->status = NPD_TRYAGAIN;
	log_msg (LOG_INFO, _("received NIS+ auth request for %s"),
		 argp->username);
	log_msg (LOG_INFO, _("password incorrect"));

	return retval;
      }
  }

  return retval;
}

#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')

bool_t
nispasswd_update_1_svc (npd_update *argp, nispasswd_updresult *result,
			struct svc_req *rqstp)
{
  int error;
  char ivec[8];
  bool_t retval = TRUE;
  u_int idx = -1;


  if (debug_flag)
    {
      struct sockaddr_in *rqhost;

      rqhost = svc_getcaller (rqstp->rq_xprt);
      log_msg (LOG_DEBUG, "nispasswd_update() [From: %s:%d]",
               inet_ntoa (rqhost->sin_addr), ntohs (rqhost->sin_port));

      log_msg (LOG_DEBUG, "\t\tident    = \"%lu\"", argp->ident);
      log_msg (LOG_DEBUG, "\t\tpw_gecos = \"%s\"", argp->pass_info.pw_gecos);
      log_msg (LOG_DEBUG, "\t\tpw_shell = \"%s\"", argp->pass_info.pw_shell);
    }


  if (argp->ident >= cache_ident_base)
    idx = argp->ident - cache_ident_base;
  else
    idx = (u_int)-1;

  if (idx == (u_int)-1 || idx >= MAX_CACHE_ENTRIES ||
      cache[idx].in_use == FALSE)
    {
      log_msg (LOG_ERR, _("received NIS+ update request for %u"),
	       argp->ident);
      log_msg (LOG_ERR, _("system error!"));
      result->status = NPD_FAILED;
      result->nispasswd_updresult_u.npd_err = NPD_SYSTEMERR;
      return retval;
    }

  /* argp->xnewpass.npd_xrandval was changed in XDR */
  argp->xnewpass.npd_xrandval = ntohl (argp->xnewpass.npd_xrandval);

  memset (ivec, 0, 8);
  error = cbc_crypt ((char *) &cache[idx].CK, (char *)&argp->xnewpass, 16,
		     DES_DECRYPT | DES_HW, ivec);
  if (DES_FAILED (error))
    {
      if (verbose)
	{
	  log_msg (LOG_ERR, _("received NIS+ update request"));
	  log_msg (LOG_ERR, _("DES decryption failure!"));
	}
      result->status = NPD_FAILED;
      result->nispasswd_updresult_u.npd_err = NPD_DECRYPTFAIL;
      return retval;
    }

  /* Revert the changes for decrypting. */
  argp->xnewpass.npd_xrandval = ntohl (argp->xnewpass.npd_xrandval);

  if (cache[idx].xrandval != argp->xnewpass.npd_xrandval)
    {
      if (verbose)
	{
	  log_msg (LOG_ERR, _("received NIS+ update request"));
	  log_msg (LOG_ERR, _("invalid verifier: %u"),
		   argp->xnewpass.npd_xrandval);
	}
      result->status = NPD_FAILED;
      result->nispasswd_updresult_u.npd_err = NPD_VERFINVALID;
      return retval;
    }

  result->status = NPD_SUCCESS;

  if (argp->xnewpass.pass != NULL &&
      strcmp (cache[idx].oldpassword, argp->xnewpass.pass) != 0)
    {
      char buf2[strlen (cache[idx].obj->zo_name) +
	       strlen (cache[idx].obj->zo_domain) + 10];
      char buf3[NISENTRYLEN (2, cache[idx].obj) + 2];
      nis_result *res;
      nis_object *obj;
      char salt[2], *cryptstr;
      time_t tm;
      uid_t uid;

      time (&tm);
      salt[0] = bin_to_ascii (tm & 0x3f);
      salt[1] = bin_to_ascii ((tm >> 6) & 0x3f);
      cryptstr = crypt (argp->xnewpass.pass, salt);

      obj = nis_clone_object (cache[idx].obj, NULL);

      free (NISENTRYVAL (1, obj));
      NISENTRYVAL (1, obj) = strdup (cryptstr);
      NISENTRYLEN (1, obj) = strlen (cryptstr) + 1;
      NISENTRYFLAG (1, obj) =
	NISENTRYFLAG (1, obj) | EN_MODIFIED;

      strncpy (buf3, NISENTRYVAL (2, obj), NISENTRYLEN (2, obj));
      buf3[NISENTRYLEN (2, cache[idx].obj)] = '\0';
      uid = atol (buf3);

      sprintf (buf2, "%s.%s", obj->zo_name, obj->zo_domain);
      res = nis_modify_entry (buf2, obj, MOD_SAMEOBJ);
      if (res->status != NIS_SUCCESS)
	{
	  if (verbose)
	    log_msg (LOG_ERR, "nis_modify_entry for passwd failed: %s",
		     nis_sperrno (res->status));
	  result->status = NPD_PARTIALSUCCESS;
	  result->nispasswd_updresult_u.reason.npd_field = NPD_PASSWD;
	  result->nispasswd_updresult_u.reason.npd_code = NPD_NISERROR;
	  result->nispasswd_updresult_u.reason.next = NULL;
	}
      else
	{
	  char netname[MAXNETNAMELEN + 1];
	  char public[HEXKEYBYTES + 1];
	  char secret[HEXKEYBYTES + 1];

	  if (getnetnameof (netname, uid, cache[idx].domain) < 0)
	    {
	      log_msg (LOG_ERR, _("cannot generate netname for uid %u."),
		       (u_long)uid);
	      result->status = NPD_PARTIALSUCCESS;
	      result->nispasswd_updresult_u.reason.npd_field = NPD_SECRETKEY;
	      result->nispasswd_updresult_u.reason.npd_code = NPD_KEYNOTREENC;
	      result->nispasswd_updresult_u.reason.next = NULL;
	    }
	  else if (!getsecretkey (netname, secret, cache[idx].oldpassword))
	    {
	      log_msg (LOG_ERR, _("cannot get secret key for %s."),
		       netname);
	      result->status = NPD_PARTIALSUCCESS;
	      result->nispasswd_updresult_u.reason.npd_field = NPD_SECRETKEY;
	      result->nispasswd_updresult_u.reason.npd_code = NPD_KEYNOTREENC;
	      result->nispasswd_updresult_u.reason.next = NULL;
	    }
	  else if (secret[0] == 0)
	    {
	      log_msg (LOG_ERR, _("unable to decrypt secret key for %s."),
		       netname);
	      result->status = NPD_PARTIALSUCCESS;
	      result->nispasswd_updresult_u.reason.npd_field = NPD_SECRETKEY;
	      result->nispasswd_updresult_u.reason.npd_code = NPD_KEYNOTREENC;
	      result->nispasswd_updresult_u.reason.next = NULL;
	    }
	  else if (!getpublickey (netname, public))
	    {
	      log_msg (LOG_ERR, _("cannot get public key for %s."),
		       netname);
	      result->status = NPD_PARTIALSUCCESS;
	      result->nispasswd_updresult_u.reason.npd_field = NPD_SECRETKEY;
	      result->nispasswd_updresult_u.reason.npd_code = NPD_KEYNOTREENC;
	      result->nispasswd_updresult_u.reason.next = NULL;
	    }
	  else
	    {
	      char crypt1[HEXKEYBYTES + KEYCHECKSUMSIZE + 1];
	      char nis_princ[strlen (cache[idx].username) +
			    strlen (cache[idx].domain) + 5];
#ifdef HAVE_STPCPY
	      char *p;
#endif

	      memcpy (crypt1, secret, HEXKEYBYTES);
	      memcpy (crypt1 + HEXKEYBYTES, secret, KEYCHECKSUMSIZE);
	      crypt1[HEXKEYBYTES + KEYCHECKSUMSIZE] = 0;
	      xencrypt (crypt1, argp->xnewpass.pass);
#ifdef HAVE_STPCPY
	      p = stpcpy (nis_princ, cache[idx].username);
	      *p++ = '.';
	      p = stpcpy (p, cache[idx].domain);
	      if (p[-1] != '.')
		*p++ = '.';
	      *p = '\0';
#else
	      strcpy (nis_princ, cache[idx].username);
	      strcat (nis_princ, ".");
	      strcat (nis_princ, cache[idx].domain);
	      if (nis_princ[strlen(nis_princ) - 1] != '.')
		strcat (nis_princ, ".");
#endif
	      if (nisplus_update (netname, "DES", public, crypt1, nis_princ)<0)
		{
		  log_msg (LOG_ERR, _("cannot change secret key for %s."),
			   nis_princ);
		  result->status = NPD_PARTIALSUCCESS;
		  result->nispasswd_updresult_u.reason.npd_field =
		    NPD_SECRETKEY;
		  result->nispasswd_updresult_u.reason.npd_code =
		    NPD_KEYNOTREENC;
		  result->nispasswd_updresult_u.reason.next = NULL;
		}
	    }
	}
      nis_free_object (obj);
      nis_freeresult (res);
    }

  /* Change the gecos field */
  if (argp->pass_info.pw_gecos != NULL &&
      strlen (argp->pass_info.pw_gecos) > 0 &&
      strncmp (NISENTRYVAL (4, cache[idx].obj), argp->pass_info.pw_gecos,
	       NISENTRYLEN (4, cache[idx].obj)) != 0)
    {
      char buf2[strlen (cache[idx].obj->zo_name) +
	       strlen (cache[idx].obj->zo_domain) + 10];
      nis_result *res;
      nis_object *obj;

      obj = nis_clone_object (cache[idx].obj, NULL);

      free (NISENTRYVAL (4, obj));
      NISENTRYVAL (4, obj) = strdup (argp->pass_info.pw_gecos);
      NISENTRYLEN (4, obj) = strlen (argp->pass_info.pw_gecos) + 1;
      NISENTRYFLAG (4, obj) =
	NISENTRYFLAG (4, obj) | EN_MODIFIED;

      sprintf (buf2, "%s.%s", obj->zo_name, obj->zo_domain);
      res = nis_modify_entry (buf2, obj, MOD_SAMEOBJ);
      if (res->status != NIS_SUCCESS)
	{
	  if (verbose)
	    log_msg (LOG_ERR, "nis_modify_entry for gecos field failed: %s",
		     nis_sperrno (res->status));
	  if (result->status == NPD_PARTIALSUCCESS)
	    {
	      /* XXX In the moment, we could only have one error !
		 Be careful, if you add more error cases before */
	      result->nispasswd_updresult_u.reason.next =
		malloc (sizeof (nispasswd_error));
	      result->nispasswd_updresult_u.reason.next->npd_field = NPD_GECOS;
	      result->nispasswd_updresult_u.reason.next->npd_code =
		NPD_NISERROR;
	      result->nispasswd_updresult_u.reason.next->next = NULL;
	    }
	  else
	    {
	      result->status = NPD_PARTIALSUCCESS;
	      result->nispasswd_updresult_u.reason.npd_field = NPD_GECOS;
	      result->nispasswd_updresult_u.reason.npd_code = NPD_NISERROR;
	      result->nispasswd_updresult_u.reason.next = NULL;
	    }
	}
      nis_free_object (obj);
      nis_freeresult (res);
    }

  /* Change the shell field */
  if (argp->pass_info.pw_shell != NULL &&
      strlen (argp->pass_info.pw_shell) > 0 &&
      strncmp (NISENTRYVAL (6, cache[idx].obj), argp->pass_info.pw_shell,
	       NISENTRYLEN (6, cache[idx].obj)) != 0)
    {
      char buf2[strlen (cache[idx].obj->zo_name) +
	       strlen (cache[idx].obj->zo_domain) + 10];
      nis_result *res;
      nis_object *obj;

      obj = nis_clone_object (cache[idx].obj, NULL);

      free (NISENTRYVAL (6, obj));
      NISENTRYVAL (6, obj) = strdup (argp->pass_info.pw_shell);
      NISENTRYLEN (6, obj) = strlen (argp->pass_info.pw_shell) + 1;
      NISENTRYFLAG (6, obj) =
	NISENTRYFLAG (6, obj) | EN_MODIFIED;


      sprintf (buf2, "%s.%s", obj->zo_name, obj->zo_domain);
      res = nis_modify_entry (buf2, obj, MOD_SAMEOBJ);
      if (res->status != NIS_SUCCESS)
	{
	  if (verbose)
	    log_msg (LOG_ERR, "nis_modify_entry for shell field failed: %s",
		     nis_sperrno (res->status));
	  /* XXX We could have one or two errors, not more. Be careful,
	     if you add more error cases above. */
	  if (result->status == NPD_PARTIALSUCCESS)
	    {
	      if (result->nispasswd_updresult_u.reason.next != NULL)
		{
		  result->nispasswd_updresult_u.reason.next->next =
		    malloc (sizeof (nispasswd_error));
		  result->nispasswd_updresult_u.reason.next->next->npd_field =
		    NPD_GECOS;
		  result->nispasswd_updresult_u.reason.next->next->npd_code =
		    NPD_NISERROR;
		  result->nispasswd_updresult_u.reason.next->next->next = NULL;
		}
	      else if (result->nispasswd_updresult_u.reason.next == NULL)
		{
		  result->nispasswd_updresult_u.reason.next =
		    malloc (sizeof (nispasswd_error));
		  result->nispasswd_updresult_u.reason.next->npd_field =
		    NPD_GECOS;
		  result->nispasswd_updresult_u.reason.next->npd_code =
		    NPD_NISERROR;
		  result->nispasswd_updresult_u.reason.next->next = NULL;
		}
	      else
		{
		  result->status = NPD_PARTIALSUCCESS;
		  result->nispasswd_updresult_u.reason.npd_field = NPD_GECOS;
		  result->nispasswd_updresult_u.reason.npd_code = NPD_NISERROR;
		  result->nispasswd_updresult_u.reason.next = NULL;
		}
	    }
	  result->status = NPD_PARTIALSUCCESS;
	  result->nispasswd_updresult_u.reason.npd_field = NPD_SHELL;
	  result->nispasswd_updresult_u.reason.npd_code = NPD_NISERROR;
	  result->nispasswd_updresult_u.reason.next = NULL;
	}
      nis_free_object (obj);
      nis_freeresult (res);
    }

  free (cache[idx].username);
  free (cache[idx].domain);
  free (cache[idx].oldpassword);
  nis_free_object (cache[idx].obj);
  cache[idx].in_use = FALSE;

  return retval;
}

int
nispasswd_prog_1_freeresult (xdrproc_t xdr_result, caddr_t result)
{
  xdr_free (xdr_result, result);
  return 1;
}
