/* 
 * $Id: support.-c,v 1.3 1999/07/04 23:22:39 morgan Exp $
 *
 * 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;
} UNIX_Ctrls;

/*
 * macro to determine if a given flag is on
 */

#define on(x,ctrl)  (unix_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)&unix_args[x].mask)|unix_args[x].flag)
#define unset(x,ctrl) (ctrl &= ~(unix_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 unix module. They
 * are enumerated here and then defined below. Internal arguments are
 * given NULL tokens.
 */

#define UNIX__OLD_PASSWD          0     /* internal */
#define UNIX__VERIFY_PASSWD       1     /* internal */
#define UNIX__IAMROOT             2     /* internal */

#define UNIX_AUDIT                3     /* print more things than debug..
					   some information may be sensitive */
#define UNIX_USE_FIRST_PASS       4
#define UNIX_TRY_FIRST_PASS       5
#define UNIX_NOT_SET_PASS         6     /* don't set the AUTHTOK items */

#define UNIX__PRELIM              7     /* internal */
#define UNIX__UPDATE              8     /* internal */
#define UNIX__NONULL              9     /* internal */
#define UNIX__QUIET              10     /* internal */
#define UNIX_USE_AUTHTOK         11     /* insist on reading PAM_AUTHTOK */
#define UNIX_SHADOW              12     /* signal shadow on */
#define UNIX_MD5_PASS            13     /* force the use of MD5 passwords */
#define UNIX__NULLOK             14     /* Null token ok */
#define UNIX_RADIUS              15     /* wish to use RADIUS for password */
#define UNIX__SET_DB             16     /* internal - signals redirect to db */
#define UNIX_DEBUG               17     /* send more info to syslog(3) */
#define UNIX_NODELAY             18     /* admin does not want a fail-delay */
#define UNIX_UNIX                19     /* wish to use /etc/passwd for pwd */
#define UNIX_BIGCRYPT            20     /* use DEC-C2 crypt()^x function */
#define UNIX_LIKE_AUTH           21     /* need to auth for setcred to work */
/* -------------- */
#define UNIX_CTRLS_              22     /* number of ctrl arguments defined */


static const UNIX_Ctrls unix_args[UNIX_CTRLS_] = {
/* symbol                 token name          ctrl mask      ctrl       *
 * ------------------     ------------------  -------------- ---------- */

/* UNIX__OLD_PASSWD */    {  NULL,            _ALL_ON_,                 01 },
/* UNIX__VERIFY_PASSWD */ {  NULL,            _ALL_ON_,                 02 },
/* UNIX__IAMROOT */       {  NULL,            _ALL_ON_,                 04 },
/* UNIX_AUDIT */          { "audit",          _ALL_ON_,                010 },
/* UNIX_USE_FIRST_PASS */ { "use_first_pass", _ALL_ON_^(060),          020 },
/* UNIX_TRY_FIRST_PASS */ { "try_first_pass", _ALL_ON_^(060),          040 },
/* UNIX_NOT_SET_PASS */   { "not_set_pass",   _ALL_ON_,               0100 },
/* UNIX__PRELIM */        {  NULL,            _ALL_ON_^(0600),        0200 },
/* UNIX__UPDATE */        {  NULL,            _ALL_ON_^(0600),        0400 },
/* UNIX__NONULL */        {  NULL,            _ALL_ON_,              01000 },
/* UNIX__QUIET */         {  NULL,            _ALL_ON_,              02000 },
/* UNIX_USE_AUTHTOK */    { "use_authtok",    _ALL_ON_,              04000 },
/* UNIX_SHADOW */         { "shadow",         _ALL_ON_^(0140000),   010000 },
/* UNIX_MD5_PASS */       { "md5",            _ALL_ON_^(02000000),  020000 },
/* UNIX__NULLOK */        { "nullok",         _ALL_ON_^(01000),          0 },
/* UNIX_RADIUS */	  { "radius",         _ALL_ON_^(0110000),   040000 },
/* UNIX__SET_DB */	  {  NULL,            _ALL_ON_,            0100000 },
/* UNIX_DEBUG */          { "debug",          _ALL_ON_,            0200000 },
/* UNIX_NODELAY */        { "nodelay",        _ALL_ON_,            0400000 },
/* UNIX_UNIX */		  { "unix",           _ALL_ON_^(050000),  01000000 },
/* UNIX_BIGCRYPT */	  { "bigcrypt",       _ALL_ON_^(020000),  02000000 },
/* UNIX_LIKE_AUTH */	  { "likeauth",       _ALL_ON_,           04000000 },
};

#define UNIX_DEFAULTS  (unix_args[UNIX__NONULL].flag)

/* 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_pwdb", LOG_CONS|LOG_PID, LOG_AUTH);
    vsyslog(err, format, args);
    va_end(args);
    closelog();
}

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

    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"));

	if (retval != PAM_SUCCESS && on(UNIX_DEBUG,ctrl) ) {
	    _log_err(LOG_DEBUG, "conversation failure [%s]"
		     , pam_strerror(pamh, retval));
	}

    } else if (retval != PAM_CONV_AGAIN) {
	_log_err(LOG_ERR, "couldn't obtain coversation function [%s]"
		 , pam_strerror(pamh, retval));
    }

    D(("ready to return from module conversation"));

    return retval;                  /* propagate error status */
}

static int make_remark(pam_handle_t *pamh, unsigned int ctrl
		       , int type, const char *text)
{
    int retval=PAM_SUCCESS;

    if ( off(UNIX__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;
	retval = converse(pamh, ctrl, 1, pmsg, &resp);

	if (resp) {
	    _pam_drop_reply(resp, 1);
	}
    }
    return retval;
}

/*
 * set the control flags for the UNIX module.
 */

static int set_ctrl(int flags, int argc, const char **argv)
{
    unsigned int ctrl;

    D(("called."));

    ctrl = UNIX_DEFAULTS;         /* the default selection of options */

    /* set some flags manually */

    if ( getuid() == 0 && !(flags & PAM_CHANGE_EXPIRED_AUTHTOK) ) {
	set(UNIX__IAMROOT, ctrl);
    }
    if ( flags & PAM_UPDATE_AUTHTOK ) {
	set(UNIX__UPDATE, ctrl);
    }
    if ( flags & PAM_PRELIM_CHECK ) {
	set(UNIX__PRELIM, ctrl);
    }
    if ( flags & PAM_DISALLOW_NULL_AUTHTOK ) {
	set(UNIX__NONULL, ctrl);
    }
    if ( flags & PAM_SILENT ) {
	set(UNIX__QUIET, ctrl);
    }

    /* now parse the arguments to this module */

    while (argc-- > 0) {
	int j;

	D(("pam_pwdb arg: %s",*argv));

	for (j=0; j<UNIX_CTRLS_; ++j) {
	    if (unix_args[j].token
		&& ! strcmp(*argv, unix_args[j].token) ) {
		break;
	    }
	}

	if ( j >= UNIX_CTRLS_ ) {
	    _log_err(LOG_ERR, "unrecognized option [%s]",*argv);
	} else {
	    ctrl &= unix_args[j].mask;    /* for turning things off */
	    ctrl |= unix_args[j].flag;    /* for turning things on  */
	}

	++argv;                            /* step to next argument */
    }

    /* these are used for updating passwords in specific places */

    if (on(UNIX_SHADOW,ctrl) || on(UNIX_RADIUS,ctrl) || on(UNIX_UNIX,ctrl)) {
	set(UNIX__SET_DB, ctrl);
    }

    /* auditing is a more sensitive version of debug */

    if ( on(UNIX_AUDIT,ctrl) ) {
	set(UNIX_DEBUG, ctrl);
    }

    /* return the set of flags */

    D(("done."));
    return ctrl;
}

/* use this to free strings. ESPECIALLY password strings */

static char *_pam_delete(register char *xx)
{
    _pam_overwrite(xx);
    _pam_drop(xx);
    return NULL;
}

static void _cleanup(pam_handle_t *pamh, void *x, int error_status)
{
    x = _pam_delete( (char *) x );
}

/* ************************************************************** *
 * Useful non-trivial functions                                   *
 * ************************************************************** */

#include "pam_unix_md.-c"

/*
 * the following is used to keep track of the number of times a user fails
 * to authenticate themself.
 */

#define FAIL_PREFIX                   "-UN*X-FAIL-"
#define UNIX_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;

    D(("called"));

    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 > 1 ) {
		(void) pam_get_item(pamh, PAM_SERVICE
				    , (const void **)&service);
		_log_err(LOG_NOTICE
			 , "%d more authentication failure%s; %s(uid=%d) -> "
			 "%s for %s service"
			 , failure->count-1, failure->count==2 ? "":"s"
			 , failure->name
			 , failure->id
			 , failure->user
			 , service == NULL ? "**unknown**":service
		    );
		if ( failure->count > UNIX_MAX_RETRIES ) {
		    _log_err(LOG_ALERT
			     , "service(%s) ignoring max retries; %d > %d"
			     , service == NULL ? "**unknown**":service
			     , failure->count
			     , UNIX_MAX_RETRIES );
		}
	    }
	}
	failure->user = _pam_delete(failure->user);            /* tidy up */
	failure->name = _pam_delete(failure->name);            /* tidy up */
	free(failure);
    }
}

/*
 * verify the password of a user
 */

#include <sys/types.h>
#include <sys/wait.h>

static int pwdb_run_helper_binary(pam_handle_t *pamh, const char *passwd)
{
    int retval, child, fds[2];

    D(("called."));
    /* create a pipe for the password */
    if (pipe(fds) != 0) {
	D(("could not make pipe"));
	return PAM_AUTH_ERR;
    }

    /* fork */
    child = fork();
    if (child == 0) {
	static char *args[] = { NULL, NULL };
	static char *envp[] = { NULL };

	/* XXX - should really tidy up PAM here too */
	while (pwdb_end() == PWDB_SUCCESS);

	/* reopen stdin as pipe */
	close(fds[1]);
	dup2(fds[0], STDIN_FILENO);

	/* exec binary helper */
	args[0] = x_strdup(CHKPWD_HELPER);
	execve(CHKPWD_HELPER, args, envp);

	/* should not get here: exit with error */
	D(("helper binary is not available"));
	exit(PWDB_SUCCESS+1);
    } else if (child > 0) {
	/* wait for child */
	close(fds[0]);
	if (passwd != NULL) {            /* send the password to the child */
	    write(fds[1], passwd, strlen(passwd)+1);
	    passwd = NULL;
	} else {
	    write(fds[1], "", 1);                        /* blank password */
	}
	close(fds[1]);
	(void) waitpid(child, &retval, 0);  /* wait for helper to complete */
	retval = (retval == PWDB_SUCCESS) ? PAM_SUCCESS:PAM_AUTH_ERR;
    } else {
	D(("fork failed"));
	retval = PAM_AUTH_ERR;
    }

    D(("returning %d", retval));
    return retval;
}

static int _unix_verify_password(pam_handle_t *pamh, const char *name
				 , const char *p, unsigned int ctrl)
{
    const struct pwdb *pw=NULL;
    const struct pwdb_entry *pwe=NULL;

    const char *salt;
    char *pp;
    char *data_name;
    int retval;
    int verify_result;

    D(("called"));

#ifdef HAVE_PAM_FAIL_DELAY
    if ( off(UNIX_NODELAY, ctrl) ) {
	D(("setting delay"));
	(void) pam_fail_delay(pamh, 1000000);  /* 1 sec delay for on failure */
    }
#endif

    /* locate the entry for this user */

    D(("locating user's record"));
    retval = pwdb_locate("user", PWDB_DEFAULT, name, PWDB_ID_UNKNOWN, &pw);
    if (retval == PWDB_PASS_PHRASE_REQD) {
	/*
	 * give the password to the pwdb library. It may be needed to
	 * access the database
	 */

	retval = pwdb_set_entry( pw, "pass_phrase", p, 1+strlen(p)
				 , NULL, NULL, 0);
	if (retval != PWDB_SUCCESS) {
	    _log_err(LOG_ALERT, "find pass; %s", pwdb_strerror(retval));
	    (void) pwdb_delete(&pw);
	    p = NULL;
	    return PAM_CRED_INSUFFICIENT;
	}

	retval = pwdb_locate("user", pw->source, name, PWDB_ID_UNKNOWN, &pw);
    }

    if (retval != PWDB_SUCCESS) {
	D(("user's record unavailable"));
	if ( on(UNIX_AUDIT, ctrl) ) {
	    /* this might be a typo and the user has given a password
	       instead of a username. Careful with this. */
	    _log_err(LOG_ALERT, "check pass; user (%s) unknown", name);
	} else {
	    _log_err(LOG_ALERT, "check pass; user unknown");
	}
	(void) pwdb_delete(&pw);
	p = NULL;
	return PAM_USER_UNKNOWN;
    }

    /*
     * courtesy of PWDB the password for the user is stored in
     * encrypted form in the "passwd" entry of pw.
     */

    retval = pwdb_get_entry(pw, "passwd", &pwe);
    if (retval != PWDB_SUCCESS) {
	if (geteuid()) {
	    /* we are not root perhaps this is the reason? Run helper */
	    D(("running helper binary"));
	    retval = pwdb_run_helper_binary(pamh, p);
	} else {
	    retval = PAM_AUTHINFO_UNAVAIL;
	    _log_err(LOG_ALERT, "get passwd; %s", pwdb_strerror(retval));
	}
	(void) pwdb_delete(&pw);
	p = NULL;
	return retval;
    }
    salt = (const char *) pwe->value;

    /*
     * XXX: Cristian, the above is not the case for RADIUS(?) Some
     * lines should be added for RADIUS to verify the password in
     * clear text...
     */

    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 ( ( !salt ) && ( !p ) ) {

	/* the stored password is NULL */
        pp = NULL;
	if ( off(UNIX__NONULL, ctrl ) ) {    /* this means we've succeeded */
	    verify_result = PAM_SUCCESS;
	} else {
	    verify_result = PAM_AUTH_ERR;
	}

    } else {

        pp = _pam_md(p, salt);

        /* the moment of truth -- do we agree with the password? */
        D(("comparing state of pp[%s] and salt[%s]", pp, salt));

	if ( strcmp( pp, salt ) == 0 ) {
	    verify_result = PAM_SUCCESS;
	} else {
	    _pam_delete(pp);
	    pp = _pam_md_compat(p, salt);
	    if ( strcmp( pp, salt ) == 0 ) {
		verify_result = PAM_SUCCESS;
	    } else {
		verify_result = PAM_AUTH_ERR;
	    }
	}

        p = NULL;                                /* no longer needed here */

    }

    if ( verify_result == PAM_SUCCESS ) {

	retval = PAM_SUCCESS;
	if (data_name) {                     /* reset failures */
	    pam_set_data(pamh, data_name, NULL, _cleanup_failures);
	}

    } 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) {

		new->user = x_strdup(name);
		new->id = getuid();
		new->name = x_strdup(getlogin() ? getlogin():"" );

                /* any previous failures for this user ? */
		pam_get_data(pamh, data_name, (const void **)&old );

		if (old != NULL) {
		    new->count = old->count +1;
		    if (new->count >= UNIX_MAX_RETRIES) {
			retval = PAM_MAXTRIES;
		    }
		} else {
                    const char *service=NULL;
                    (void) pam_get_item(pamh, PAM_SERVICE
                                        , (const void **)&service);
                    _log_err(LOG_NOTICE
                             , "authentication failure; %s(uid=%d) -> "
                             "%s for %s service"
                             , new->name
                             , new->id
                             , new->user
                             , service == NULL ? "**unknown**":service
                        );
		    new->count = 1;
		}

		pam_set_data(pamh, data_name, new, _cleanup_failures);

	    } else {
		_log_err(LOG_CRIT, "no memory for failure recorder");
	    }
	}

    }

    (void) pwdb_entry_delete(&pwe);
    (void) pwdb_delete(&pw);
    salt = NULL;
    _pam_delete(data_name);
    _pam_delete(pp);

    D(("done [%d].", retval));

    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 _unix_get_user(pam_handle_t *pamh, unsigned int ctrl
			  , const char *prompt, const char **user)
{
    int retval;

    D(("called"));

    retval = pam_get_user(pamh, user, prompt);
    if (retval != PAM_SUCCESS) {
	D(("trouble reading username"));
	return retval;
    }

    /*
     * Various libraries at various times have had bugs related to
     * '+' or '-' as the first character of a user name. Don't take
     * any chances here. Require that the username starts with an
     * alphanumeric character.
     */

    if (*user == NULL || !isalnum(**user)) {
	D(("bad username"));
	if (on(UNIX_DEBUG,ctrl)) {
	    _log_err(LOG_ERR, "bad username [%s]", *user);
	}
	return PAM_USER_UNKNOWN;
    }

    if (retval == PAM_SUCCESS && on(UNIX_DEBUG,ctrl)) {
	_log_err(LOG_DEBUG, "username [%s] obtained", *user);
    }

    return retval;
}

/*
 * _unix_blankpasswd() is a quick check for a blank password
 *
 * returns TRUE if user does not have a password
 * - to avoid prompting for one in such cases (CG)
 */

static int _unix_blankpasswd(unsigned int ctrl, const char *name)
{
    const struct pwdb *pw=NULL;
    const struct pwdb_entry *pwe=NULL;
    int retval;

    D(("called"));

    /*
     * This function does not have to be too smart if something goes
     * wrong, return FALSE and let this case to be treated somewhere
     * else (CG)
     */

    if ( on(UNIX__NONULL, ctrl) )
	return 0;                     /* will fail but don't let on yet */

    /* find the user's database entry */

    retval = pwdb_locate("user", PWDB_DEFAULT, name, PWDB_ID_UNKNOWN, &pw);
    if (retval != PWDB_SUCCESS || pw == NULL ) {

	retval = 0;

    } else {

	/* Does this user have a password? */

	retval = pwdb_get_entry(pw, "passwd", &pwe);
	if ( retval != PWDB_SUCCESS || pwe == NULL )
	    retval = 0;
	else if ( pwe->value == NULL || ((char *)pwe->value)[0] == '\0' )
	    retval = 1;
	else
	    retval = 0;

    }

    /* tidy up */

    if ( pw ) {
	(void) pwdb_delete(&pw);
	if ( pwe )
	    (void) pwdb_entry_delete(&pwe);
    }

    return retval;
}

/*
 * obtain a password from the user
 */

static int _unix_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;

    D(("called"));

    /*
     * make sure nothing inappropriate gets returned
     */

    *pass = token = NULL;

    /*
     * which authentication token are we getting?
     */

    authtok_flag = on(UNIX__OLD_PASSWD,ctrl) ? PAM_OLDAUTHTOK:PAM_AUTHTOK ;

    /*
     * should we obtain the password from a PAM item ?
     */

    if ( on(UNIX_TRY_FIRST_PASS,ctrl) || on(UNIX_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 unix-read-password"
		);
	    return retval;
	} else if (item != NULL) {    /* we have a password! */
	    *pass = item;
	    item = NULL;
	    return PAM_SUCCESS;
	} else if (on(UNIX_USE_FIRST_PASS,ctrl)) {
	    return PAM_AUTHTOK_RECOVER_ERR;       /* didn't work */
	} else if (on(UNIX_USE_AUTHTOK, ctrl)
		   && off(UNIX__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, replies;

	/* prepare to converse */

	if ( comment != NULL && off(UNIX__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;
	replies = 1;

	if ( prompt2 != NULL ) {
	    pmsg[i] = &msg[i];
	    msg[i].msg_style = PAM_PROMPT_ECHO_OFF;
	    msg[i++].msg = prompt2;
	    ++replies;
	}

	/* so call the conversation expecting i responses */
	resp = NULL;
	retval = converse(pamh, ctrl, i, pmsg, &resp);

	if (resp != NULL) {

	    /* interpret the response */

	    if (retval == PAM_SUCCESS) {     /* a good conversation */

		token = x_strdup(resp[i-replies].resp);
		if (token != NULL) {
		    if (replies == 2) {

			/* verify that password entered correctly */
			if (!resp[i-1].resp
			    || strcmp(token,resp[i-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
	     */

	    _pam_drop_reply(resp, i);

	} else {
	    retval = (retval == PAM_SUCCESS)
		? PAM_AUTHTOK_RECOVER_ERR:retval ;
	}
    }

    if (retval != PAM_SUCCESS) {
	if ( on(UNIX_DEBUG,ctrl) )
	    _log_err(LOG_DEBUG,"unable to obtain a password");
	return retval;
    }

    /* 'token' is the entered password */

    if ( off(UNIX_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(pamh, 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_unix_approve_pass(pam_handle_t *pamh
				  , unsigned int ctrl
				  , const char *pass_old
				  , const char *pass_new)
{
     D(("&new=%p, &old=%p",pass_old,pass_new));
     D(("new=[%s]",pass_new));
     D(("old=[%s]",pass_old));

     if (pass_new == NULL || (pass_old && !strcmp(pass_old,pass_new))) {
	  if ( on(UNIX_DEBUG, ctrl) ) {
	       _log_err(LOG_DEBUG, "bad authentication token");
	  }
	  make_remark(pamh, ctrl, PAM_ERROR_MSG, pass_new == NULL ?
		       "No password supplied":"Password unchanged" );
	  return PAM_AUTHTOK_ERR;
     }

     /*
      * if one wanted to hardwire authentication token strength
      * checking this would be the place - AGM
      */

     return PAM_SUCCESS;
}

/* ****************************************************************** *
 * Copyright (c) Andrew G. Morgan 1996-8.
 * Copyright (c) Alex O. Yuriev, 1996.
 * Copyright (c) Cristian Gafton 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.
 */

