/* -*-c-*-
 * $Id: support.-c,v 1.3 1996/09/05 06:49:36 shadow Exp $
 *
 * $Log: support.-c,v $
 * Revision 1.1  1996/08/29 13:27:51  shadow
 * Initial revision
 *
 * Copyright information at end of file.
 */

/*
 * here is the string to inform the user that the new passwords they
 * typed were not the same.
 */

#define MISTYPED_PASS "Sorry, passwords do not match."

/* type definition for the control options */

typedef struct {
     const char *token;
     unsigned int mask;            /* shall assume 32 bits of flags */
     unsigned int flag;
} KRB4_Ctrls;

/*
 * macro to determine if a given flag is on
 */

#define on(x,ctrl)  (krb4_args[x].flag & ctrl)

/*
 * macro to determine that a given flag is NOT on
 */

#define off(x,ctrl) (!on(x,ctrl))

/*
 * macro to turn on/off a ctrl flag manually
 */

#define set(x,ctrl)   (ctrl = ((ctrl)&krb4_args[x].mask)|krb4_args[x].flag)
#define unset(x,ctrl) (ctrl &= ~(krb4_args[x].flag))

/* the generic mask */

#define _ALL_ON_  (~0U)

/* end of macro definitions definitions for the control flags */

/* ****************************************************************** *
 * ctrl flags proper..
 */

/*
 * here are the various options recognized by the krb4 module. They
 * are enumerated here and then defined below. Internal arguments are
 * given NULL tokens.
 */

#define KRB4__OLD_PASSWD          0     /* internal */
#define KRB4__VERIFY_PASSWD       1     /* internal */
#define KRB4_USE_AFS_STK          2     

#define KRB4_DEBUG                3
#define KRB4_USE_FIRST_PASS       4
#define KRB4_TRY_FIRST_PASS       5
#define KRB4_NOT_SET_PASS         6     /* don't set the AUTHTOK items */

#define KRB4__PRELIM              7     /* internal */
#define KRB4__UPDATE              8     /* internal */
#define KRB4__NONULL              9     /* internal */
#define KRB4__QUIET              10     /* internal */
#define KRB4_USE_AUTHTOK         11     /* insist on reading PAM_AUTHTOK */

/* -------------- */
#define KRB4_CTRLS_              12     /* number of ctrl arguments defined */


static KRB4_Ctrls krb4_args[KRB4_CTRLS_] = {
/* symbol                 token name          ctrl mask      ctrl       *
 * ------------------     ------------------  -------------- ---------- */

/* KRB4__OLD_PASSWD */    {  NULL,            _ALL_ON_,              01 },
/* KRB4__VERIFY_PASSWD */ {  NULL,            _ALL_ON_,              02 },
/* KRB4_USE_AFS_STK */    { "afskey",         _ALL_ON_,              04 },
/* KRB4_DEBUG */          { "debug",          _ALL_ON_,             010 },
/* KRB4_USE_FIRST_PASS */ { "use_first_pass", _ALL_ON_^(060),       020 },
/* KRB4_TRY_FIRST_PASS */ { "try_first_pass", _ALL_ON_^(060),       040 },
/* KRB4_NOT_SET_PASS */   { "not_set_pass",   _ALL_ON_,            0100 },
/* KRB4__PRELIM */        {  NULL,            _ALL_ON_^(0600),     0200 },
/* KRB4__UPDATE */        {  NULL,            _ALL_ON_^(0600),     0400 },
/* KRB4__NONULL */        {  NULL,            _ALL_ON_,           01000 },
/* KRB4__QUIET */         {  NULL,            _ALL_ON_,           02000 },
/* KRB4_USE_AUTHTOK */    { "use_authtok",    _ALL_ON_,           04000 },
};

#define KRB4_DEFAULTS  0

/* syslogging function for errors and other information */

static void _log_err(int err, const char *format, ...)
{
    va_list args;

    va_start(args, format);
    openlog("PAM-Krb4", LOG_CONS|LOG_PID, LOG_AUTH);
    vsyslog(err, format, args);
    va_end(args);
    closelog();
}

#ifdef DEBUG

/* This is for debugging purposes ONLY DO NOT use on life systems !!!
 * You have been warned :-) - CG
 *
 * to get automated debugging to the log file, it must be created manually.
 * LOGFILE must exist, mode 666
 */
#define LOGFILE "/tmp/pam-debug.log"

static void _debug_pam(const char *format, ...)
{
     va_list args;
     FILE *logfile;
    
     va_start(args, format);

     if (!(logfile = fopen(LOGFILE,"a"))) {
	  vfprintf(stderr, format, args);
     } else {
	  vfprintf(logfile, format, args);
	  fflush(logfile);
	  fclose(logfile);
     }

     va_end(args);
}

#undef LOGFILE
#endif /* DEBUG */

/* this is a front-end for module-application conversations */

static int converse(pam_handle_t *pamh, int ctrl, int nargs
		    , struct pam_message **message
		    , struct pam_response **response)
{
     int retval;
     struct pam_conv *conv;

     D(("begin to converse\n"));

     retval = pam_get_item( pamh, PAM_CONV, (const void **) &conv ) ; 
     if ( retval == PAM_SUCCESS ) {

	  retval = conv->conv(nargs, ( const struct pam_message ** ) message
			      , response, conv->appdata_ptr);

	  D(("returned from application's conversation function\n"));

	  if (retval != PAM_SUCCESS && on(KRB4_DEBUG,ctrl) ) {
	       _log_err(LOG_DEBUG, "conversation failure [%s]"
			, pam_strerror(retval));
	  }

     } else {
	  _log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
	       , pam_strerror(retval));
     }

     D(("ready to return from module conversation\n"));

     return retval;                  /* propagate error status */
}

static int make_remark(pam_handle_t *pamh, unsigned int ctrl
		       , int type, const char *text)
{
     if ( off(KRB4__QUIET, ctrl) ) {
	  struct pam_message *pmsg[1], msg[1];
	  struct pam_response *resp;
	  
	  pmsg[0] = &msg[0];
	  msg[0].msg = text;
	  msg[0].msg_style = type;
	  resp = NULL;

	  return converse(pamh, ctrl, 1, pmsg, &resp);
     }
     return PAM_SUCCESS;
}

/*
 * set the control flags for the KRB4 module.
 */

static int set_ctrl(int flags, int argc, const char **argv)
{
     unsigned int ctrl;

     ctrl = KRB4_DEFAULTS;         /* the default selection of options */

     /* set some flags manually */

     if ( flags & PAM_UPDATE_AUTHTOK ) {
	  set(KRB4__UPDATE, ctrl);
     }
     if ( flags & PAM_PRELIM_CHECK ) {
	  set(KRB4__PRELIM, ctrl);
     }
     if ( flags & PAM_DISALLOW_NULL_AUTHTOK ) {
	  set(KRB4__NONULL, ctrl);
     }
     if ( flags & PAM_SILENT ) {
	  set(KRB4__QUIET, ctrl);
     }

     /* now parse the arguments to this module */

     while (argc-- > 0) {
	  int j;

	  D(("pam_krb4 arg: %s\n",*argv));

	  for (j=0; j<KRB4_CTRLS_; ++j) {
	       if (krb4_args[j].token
		   && ! strcmp(*argv, krb4_args[j].token) ) {
		    break;
	       }
	  }

	  if ( j >= KRB4_CTRLS_ ) {
	       _log_err(LOG_ERR, "unrecognized option [%s]",*argv);
	  } else {
	       ctrl &= krb4_args[j].mask;    /* for turning things off */
	       ctrl |= krb4_args[j].flag;    /* for turning things on  */
	  }

	  ++argv;                           /* step to next argument */
     }

     /* return the set of flags */

     return ctrl;
}

/* a convenient helper function to overwrite passwords */

static void _pam_overwrite(register char *xx)
{
     if (xx != NULL)
          while (*xx)
               *xx++ = '\0';
}

/* use this to free password strings */

static char *_pam_delete(register char *xx)
{
     _pam_overwrite(xx);
     free(xx);
     return NULL;
}

static void _cleanup(pam_handle_t *pamh, void *x, int error_status)
{
     x = _pam_delete( (char *) x );
}

/* safe duplication of character strings */

static char *xstrdup(const char *x)
{
     char *new=NULL;

     if (x != NULL) {
	  if ((new = malloc(strlen(x)+1)) == NULL) {
	       _log_err(LOG_CRIT, "out of memory in xstrdup");
	  } else {
	       strcpy(new,x);
	  }
     }

     return new;           /* return the duplicate or NULL on error */
}

/* ************************************************************** *
 * Useful non-trivial functions                                   *
 * ************************************************************** */
/*
 * the following is used to keep track of the number of times a user fails
 * to authenticate themself.
 */

#define FAIL_PREFIX                   "-KRB4-FAIL-"
#define KRB4_MAX_RETRIES              3

struct _pam_failed_auth {
     char *user;                  /* user that's failed to be authenticated */
     char *name;                  /* attempt from user with name */
     int id;                      /* uid of name'd user */
     int count;                   /* number of failures so far */
};

#ifndef PAM_DATA_REPLACE
#error "Need to get an updated libpam 0.52 or better"
#endif

static void _cleanup_failures(pam_handle_t *pamh, void *fl, int err)
{
     int quiet;
     const char *service=NULL;
     struct _pam_failed_auth *failure;

     quiet = err & PAM_DATA_SILENT;     /* should we log something? */
     err  &= PAM_DATA_REPLACE;          /* are we just replacing data? */
     failure = (struct _pam_failed_auth *) fl;

     if ( failure != NULL ) {

	  if ( !quiet && !err ) {   /* under advisement from Sun/May go away */

	       /* log the number of authentication failures */
	       if ( failure->count != 0 ) {
		    (void) pam_get_item(pamh, PAM_SERVICE
					, (const void **)&service);
		    _log_err(LOG_NOTICE
			     , "%d authentication failure%s; %s(uid=%d) -> "
			       "%s for %s service"
			     , failure->count, failure->count==1 ? "":"s"
			     , failure->name
			     , failure->id
			     , failure->user
			     , service == NULL ? "**unknown**":service
			 );
		    if ( failure->count > KRB4_MAX_RETRIES ) {
			 _log_err(LOG_ALERT
				  , "service(%s) ignoring max retries; %d > %d"
				  , service == NULL ? "**unknown**":service
				  , failure->count
				  , KRB4_MAX_RETRIES );
		    }
	       }
	  }
	  free(failure->user);	                      /* tidy up */
	  free(failure);
     }
}

/*
 * Verify the Kerberos ticket-granting ticket just retrieved for the
 * user.  If the Kerberos server doesn't respond, assume the user is
 * trying to fake us out (since we DID just get a TGT from what is
 * supposedly our KDC).  If the rcmd.<host> service is unknown (i.e.,
 * the local /etc/srvtab doesn't have it), let her in.
 */
static int 
verify_krb_tgt (char *realm)
{
  char hostname[256], phost[BUFSIZ];
  struct hostent *hp;
  KTEXT_ST ticket;
  AUTH_DAT authdata;
  unsigned long addr;
  static char my_rcmd[] = "rcmd";
  char key[8];
  int krbval, retval, have_keys;
  
  if (gethostname(hostname, sizeof(hostname)) == -1) {
    return PAM_SYSTEM_ERR;
  }
  strncpy (phost, krb_get_phost (hostname), sizeof (phost));
  phost[sizeof(phost)-1] = 0;
  hp = gethostbyname (hostname);
  if (!hp) {
    return PAM_SYSTEM_ERR;
  }
  memcpy((char *) &addr, (char *)hp->h_addr, sizeof (addr));

  /* Do we have rcmd.<host> keys? */
  if (getuid() != 0) {
    return PAM_SUCCESS; /* Not really, we can't check since we aren't root */
  }
  
  have_keys = read_service_key (my_rcmd, phost, realm, 0, "/etc/srvtab", key)
    ? 0 : 1;

  krbval = krb_mk_req (&ticket, my_rcmd, phost, realm, 0);

  if (krbval == KDC_PR_UNKNOWN) {
    /*
     * Our rcmd.<host> principal isn't known -- just assume valid
     * for now?  This is one case that the user _could_ fake out.
     */
    if (have_keys)
      return PAM_AUTH_ERR;
    else
      return PAM_SUCCESS; /* Not exactly */
  } else 
    if (krbval != KSUCCESS) {
      return PAM_AUTH_ERR;
    }

  /* got ticket, try to use it */
  krbval = krb_rd_req (&ticket, my_rcmd, phost, addr, &authdata, (char *)"");

  if (krbval != KSUCCESS) {
    if (krbval == RD_AP_UNDEC && !have_keys)
      retval = PAM_SUCCESS; /* Not exactly */
    else {
      retval = PAM_AUTH_ERR;
    }
    goto EGRESS;
  }

  /*
   * The rcmd.<host> ticket has been received _and_ verified.
   */
  retval = PAM_SUCCESS;

  /* do cleanup and return */
EGRESS:
  memset(&ticket, 0, sizeof (ticket));
  memset(&authdata, 0, sizeof (authdata));
  return retval;
}

static int _krb4_set_cred(pam_handle_t *pamh, unsigned int ctrl)
{
     int retval = 0;

     /* We need to deal with: 
      * PAM_CRED_ESTABLISH (get new cred)
      * PAM_CRED_DELETE (nuke creds)
      * PAM_CRED_REINITIALIZE (not sure that we need to deal with this)
      * PAM_CRED_REFRESH (ha ha)
      */

     D(("%d\n", ctrl));

     switch (ctrl) {
     case PAM_CRED_REINITIALIZE:
     case PAM_CRED_REFRESH:
       retval = PAM_CRED_UNAVAIL;
       break;

     case PAM_CRED_DELETE:
       dest_tkt();
       retval = PAM_SUCCESS;
       break;

     case PAM_CRED_ESTABLISH:
     default:
       {
	 CREDENTIALS tgtcred;
	 const char *padcred;
	 char tkfile[256];
	 char *tktfile;
	 const char *user_name;
	 struct passwd *pw;
	 
	 if (pam_get_data(pamh, "krbcred", (const char **)&padcred) != PAM_SUCCESS)
	   {
	     return PAM_CRED_UNAVAIL;
	   }

	 if ((pam_get_item( pamh, PAM_USER, (void*) &user_name ) 
	      != PAM_SUCCESS) || ( user_name == NULL))
	   return PAM_SESSION_ERR;
	
	 pw = getpwnam(user_name);

	 if (pw && (getuid() == 0)) 
	   sprintf(tkfile, "%s_%d.%d", TKT_ROOT, pw->pw_uid, getpid());
	 else
	   sprintf(tkfile, "%s_%d.%d", TKT_ROOT, getuid(), getpid());
	 
	 krb_set_tkt_string(tkfile);

	 /* Requires PAM 0.54 or greater */
	 tktfile = (char *)malloc(strlen(tkfile)+12);
	 sprintf(tktfile, "KRBTKFILE=%s", tkfile);

	 memcpy(&tgtcred, padcred, sizeof(tgtcred));
	 
	 in_tkt(tgtcred.pname, tgtcred.pinst);
	 retval = tf_init(tkt_string(), W_TKT_FIL);
	 if (retval == NO_TKT_FIL) {
	   (void) in_tkt(tgtcred.pname, tgtcred.pinst);
	   retval = tf_init(tkt_string(), W_TKT_FIL);
	 }
	 if (!retval && !tf_save_cred(tgtcred.service, tgtcred.instance,
				      tgtcred.realm, tgtcred.session, 
				      tgtcred.lifetime, tgtcred.kvno,
				      &tgtcred.ticket_st, 
				      tgtcred.issue_date)) {
	   retval = pam_putenv(pamh, tktfile);
	   
	   if (retval != 0)
	     retval = PAM_SYSTEM_ERR;
	 } else {
	   retval = PAM_CRED_ERR;
	 }
	 tf_close();
	 
	 break;
       }
     }     
	 
     return retval;
}

/*
 * verify the password of a user
 */

static int _krb4_verify_password(pam_handle_t *pamh
				 , const char *name, const char *p
				 , unsigned int ctrl)
{
     char *data_name;
     char *tf_nam = NULL;
     int retval;
     char lrealm[REALM_SZ];
     int tkt_life = DEFAULT_TKT_LIFE;
     CREDENTIALS tgtcred;

     D(("_krb4_verify_password () called.\n"));
     NID("name = %s\n",name);
     NID("password = %s\n",p);
     NID("ctrl = %d\n",ctrl);
     
     /* We don't allow null Kerberos passwords */
     if ( !p )
       return PAM_AUTH_ERR;

     data_name = (char *) malloc(sizeof(FAIL_PREFIX)+strlen(name));
     if ( data_name == NULL ) {
	  _log_err(LOG_CRIT, "no memory for data-name");
     }
     strcpy(data_name, FAIL_PREFIX);
     strcpy(data_name + sizeof(FAIL_PREFIX)-1, name);

     if (krb_get_lrealm(lrealm, 1) != KSUCCESS) {
       return PAM_AUTHINFO_UNAVAIL;
     }

     NID("get_lrealm %s\n", lrealm);

     tf_nam = tmpnam(NULL);

     if (tf_nam != NULL) {
       krb_set_tkt_string(tf_nam);
     } else {
       return PAM_AUTHINFO_UNAVAIL;
     }

     NID("krb_set_tkt_string %s\n", tf_nam);

     /* Perhaps support for kname_parse should be added here? */

     if (off(KRB4__PRELIM, ctrl)) {
       retval = krb_get_in_tkt((void *)name, (void *)"", lrealm, (void *)"krbtgt", lrealm, tkt_life, passwd_to_key, NULL, (void *)p);
     } else {
       retval = krb_get_in_tkt((void *)name, (void *)"", lrealm, (void *)PWSERV_NAME, (void *)KRB_MASTER, tkt_life, passwd_to_key, NULL, (void *)p);
     }

#ifdef AFS_SUPPORT
     if (retval != 0) {
       if (off(KRB4__PRELIM, ctrl)) {
	 retval = krb_get_in_tkt((void *)name, (void *)"", lrealm, (void *)"krbtgt", lrealm, tkt_life, passwd_to_afskey, NULL, (void *)p);
       } else {
	 retval = krb_get_in_tkt((void *)name, (void *)"", lrealm, (void *)PWSERV_NAME, (void *)KRB_MASTER, tkt_life, passwd_to_afskey, NULL, (void *)p);
       }
     }
#endif

     if (retval == 0) {
       if (on(KRB4__PRELIM, ctrl) || verify_krb_tgt(lrealm) == PAM_SUCCESS) {
	 if (data_name) {                     /* reset failures */
	   pam_set_data(pamh, data_name, NULL, _cleanup_failures);
	 }
	 retval = PAM_SUCCESS;
	 if ((off(KRB4__PRELIM, ctrl) && 
	      krb_get_cred((void *)"krbtgt", lrealm, 
			   lrealm, &tgtcred) == KSUCCESS)) {
	   void *padcred = malloc(sizeof(tgtcred));

	   memcpy((char *)padcred, &tgtcred, sizeof(tgtcred));
	   pam_set_data(pamh,"krbcred", padcred, _cleanup);
	   dest_tkt();
	 } else {
	   /* We failed to get_cred; This should never happen, actually */
	 }
       } else {
	 retval = PAM_AUTH_ERR;
       }
     } else {
       retval = PAM_AUTH_ERR;
       if (data_name != NULL) {
	 struct _pam_failed_auth *new=NULL;
	 const struct _pam_failed_auth *old=NULL;
	 
	 /* get a failure recorder */
	 
	 new = (struct _pam_failed_auth *)
	   malloc(sizeof(struct _pam_failed_auth));
	 
	 if (new != NULL) {

	   /* any previous failures for this user ? */
	   pam_get_data(pamh, data_name, (void *)&old );
	   
	   if (old != NULL) {
	     new->count = old->count +1;
	     if (new->count >= KRB4_MAX_RETRIES) {
	       retval = PAM_MAXTRIES;
	     }
	   } else {
	     new->count = 1;
	   }
	   new->user = xstrdup(name);
	   new->id = getuid();
	   new->name = xstrdup(getlogin());
	   
	   pam_set_data(pamh, data_name, new, _cleanup_failures);
	   
	 } else {
	   _log_err(LOG_CRIT, "no memory for failure recorder");
	 }
       }
     }

     free(data_name);
     return retval;
}

/*
 * this function obtains the name of the current user and ensures
 * that the PAM_USER item is set to this value
 */

static int _krb4_get_user(pam_handle_t *pamh, unsigned int ctrl
			  , const char *prompt, const char **user)
{
     int retval;
     char *luser;

     retval = pam_get_user(pamh, &luser, prompt);
     *user = luser;

     if (retval == PAM_SUCCESS) {
	  if (on(KRB4_DEBUG,ctrl)) {
	       _log_err(LOG_DEBUG, "username [%s] obtained", *user);
	  }
	  retval = pam_set_item(pamh, PAM_USER, *user);
     }

     if (retval != PAM_SUCCESS) {
	  _log_err(LOG_ERR, "unable to set username [%s]"
		   , pam_strerror(retval));
     }
     
     return retval;
}


/*
 * obtain a password from the user
 */

static int _krb4_read_password( pam_handle_t *pamh
				, unsigned int ctrl
				, const char *comment
				, const char *prompt1
				, const char *prompt2
				, const char *data_name
				, const char **pass )
{
     int authtok_flag;
     int retval;
     const char *item;
     char *token;

     /*
      * make sure nothing inappropriate gets returned
      */

     *pass = token = NULL;

     /*
      * which authentication token are we getting?
      */

     authtok_flag = on(KRB4__OLD_PASSWD,ctrl) ? PAM_OLDAUTHTOK:PAM_AUTHTOK ;

     /*
      * should we obtain the password from a PAM item ?
      */

     if ( on(KRB4_TRY_FIRST_PASS,ctrl) || on(KRB4_USE_FIRST_PASS,ctrl) ) {
	  retval = pam_get_item(pamh, authtok_flag, (const void **) &item);
	  if (retval != PAM_SUCCESS ) {
	       /* very strange. */
	       _log_err(LOG_ALERT
			, "pam_get_item returned error to krb4-read-password"
		    );
	       return retval;
	  } else if (item != NULL) {    /* we have a password! */
	       *pass = item;
	       item = NULL;
	       return PAM_SUCCESS;
	  } else if (on(KRB4_USE_FIRST_PASS,ctrl)) {
	       return PAM_AUTHTOK_RECOVER_ERR;       /* didn't work */
	  } else if (on(KRB4_USE_AUTHTOK, ctrl)
		     && off(KRB4__OLD_PASSWD, ctrl)) {
	       return PAM_AUTHTOK_RECOVER_ERR;
	  }
     }

     /*
      * getting here implies we will have to get the password from the
      * user directly.
      */

     {
	  struct pam_message msg[3],*pmsg[3];
	  struct pam_response *resp;
	  int i,expect;

	  /* prepare to converse */

	  if ( comment != NULL && off(KRB4__QUIET, ctrl) ) {
	       pmsg[0] = &msg[0];
	       msg[0].msg_style = PAM_TEXT_INFO;
	       msg[0].msg = comment;
	       i = 1;
	  } else {
	       i = 0;
	  }

	  pmsg[i] = &msg[i];
	  msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
	  msg[i++].msg = prompt1;

	  if ( prompt2 != NULL ) {
	       pmsg[i] = &msg[i];
	       msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
	       msg[i++].msg = prompt2;
	       expect = 2;
	  } else
	       expect = 1;

	  resp = NULL;

	  /* so call the conversation expecting 'expect' responses */

	  retval = converse(pamh, ctrl, i, pmsg, &resp);

	  if (resp != NULL) {

	       /* interpret the response */

	       if (retval == PAM_SUCCESS) {     /* a good conversation */

		    token = xstrdup(resp[0].resp);
		    if (token != NULL) {
			 if (expect == 2) {

			      /* verify that password entered correctly */
			      if (!resp[1].resp
				  || strcmp(token,resp[1].resp)) {
				   token = _pam_delete(token); /* mistyped */
				   retval = PAM_AUTHTOK_RECOVER_ERR;
				   make_remark(pamh, ctrl
					       , PAM_ERROR_MSG, MISTYPED_PASS);
			      }
			 }

		    } else {
			 _log_err(LOG_NOTICE
				  , "could not recover authentication token");
		    }

	       }

	       /*
		* tidy up the conversation (resp_retcode) is ignored
		* -- what is it for anyway? AGM
		*/

	       for (i=0; i<expect; ++i) {    /* tidy up */
		 resp[i].resp = _pam_delete(resp[i].resp);
	       }
	       free(resp);

	  } else {
	       retval = (retval == PAM_SUCCESS)
		    ? PAM_AUTHTOK_RECOVER_ERR:retval ;
	  }
     }

     if (retval != PAM_SUCCESS) {
	  if ( on(KRB4_DEBUG,ctrl) )
	       _log_err(LOG_DEBUG,"unable to obtain a password");
	  return retval;
     }

     /* 'token' is the entered password */

     if ( off(KRB4_NOT_SET_PASS, ctrl) ) {

	  /* we store this password as an item */

	  retval = pam_set_item(pamh, authtok_flag, token);
	  token = _pam_delete(token);   /* clean it up */
	  if ( retval != PAM_SUCCESS
	      || (retval = pam_get_item(pamh, authtok_flag
					, (const void **)&item))
	       != PAM_SUCCESS ) {

	       _log_err(LOG_CRIT, "error manipulating password");
	       return retval;

	  }

     } else {
	  /*
	   * then store it as data specific to this module. pam_end()
	   * will arrange to clean it up.
	   */

	  retval = pam_set_data(pamh, data_name, (void *) token, _cleanup);
	  if (retval != PAM_SUCCESS) {
	       _log_err(LOG_CRIT, "error manipulating password data [%s]"
		    , pam_strerror(retval) );
	       token = _pam_delete(token);
	       return retval;
	  }
	  item = token;
	  token = NULL;           /* break link to password */

     }

     *pass = item;
     item = NULL;                 /* break link to password */

     return PAM_SUCCESS;
}

static int _pam_krb4_approve_pass(pam_handle_t *pamh
				  , unsigned int ctrl
				  , const char *pass_old
				  , const char *pass_new)
{
     D(("&new=%p, &old=%p\n",pass_old,pass_new));
     D(("new=[%s]\n",pass_new));
     D(("old=[%s]\n",pass_old));

     if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) {
	  if ( on(KRB4_DEBUG, ctrl) ) {
	       _log_err(LOG_DEBUG, "bad authentication token");
	  }
	  return PAM_AUTHTOK_ERR;
     }

     /*
      * if one wanted to hardwire authentication token strength
      * checking this would be the place
      */

     return PAM_SUCCESS;
}

/* ****************************************************************** *
 * Copyright (c) Andrew G. Morgan, 1996.
 * Copyright (c) Alex 0. Yuriev, 1996.
 * Copyright (c) Cristian Gafton 1996.
 * Copyright (c) Derrick J. Brashear 1996.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, and the entire permission notice in its entirety,
 *    including the disclaimer of warranties.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 * 
 * ALTERNATIVELY, this product may be distributed under the terms of
 * the GNU Public License, in which case the provisions of the GPL are
 * required INSTEAD OF the above restrictions.  (This clause is
 * necessary due to a potential bad interaction between the GPL and
 * the restrictions contained in a BSD-style copyright.)
 * 
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

