/* pam_item.c */

/*
 * $Id: pam_item.c,v 1.8 1997/02/15 15:58:49 morgan Exp morgan $
 *
 * $Log: pam_item.c,v $
 * Revision 1.8  1997/02/15 15:58:49  morgan
 * service name is now stored in lower case only
 *
 * Revision 1.7  1997/01/04 20:08:43  morgan
 * revised debugging
 *
 * Revision 1.6  1996/12/01 03:14:13  morgan
 * use _pam_macros.h
 *
 * Revision 1.5  1996/11/10 20:04:39  morgan
 * name convention enforced (_pam_ prefix) and also reworking of
 * pam_get_user code.
 *
 * Revision 1.4  1996/04/07 07:24:44  morgan
 * ammedments to the previous fix. It seems that there is no good way of
 * handling the case where the pam_set_item() function is called with a
 * pointer to the second character of the existing item. Since the
 * previous item is free()'d the argument pointer is no longer valid
 * after the call.
 *
 * Revision 1.3  1996/03/29 02:25:50  morgan
 * Allow for the possibilty of a call to set an item using the memory
 * returned by pam_get_item (or something that overlaps with it). Now
 * copy the memory of the item before free()'ing the old one.
 *
 * Revision 1.2  1996/03/16 21:53:31  morgan
 * deleted comments about how to restrict the access to AUTHTOK
 * things... this is dealt with by having moved the tokens to
 * pam_modules.h
 * Overwrite passwords with zeros before freeing their memory
 *
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>

#include "pam_private.h"

#define RESET(X, Y)                    \
{                                      \
    char *_TMP_ = (X);                 \
    if (_TMP_ != (Y)) {                \
	 (X) = (Y) ? _pam_strdup(Y) : NULL; \
	 if (_TMP_)                    \
	      free(_TMP_);             \
    }                                  \
}

/* functions */

int pam_set_item (
    pam_handle_t *pamh,
    int item_type,
    const void *item)
{
    int retval;

    D(("called"));

    IF_NO_PAMH("pam_set_item",pamh,PAM_SYSTEM_ERR);
    
    retval = PAM_SUCCESS;

    switch (item_type) {
    case PAM_SERVICE:
	/* Setting handlers_loaded to 0 will cause the handlers
	 * to be reloaded on the next call to a service module.
	 */
	pamh->handlers.handlers_loaded = 0;
	RESET(pamh->service_name, item);
	{
	    char *tmp;
	    for (tmp=pamh->service_name; *tmp; ++tmp)
		*tmp = tolower(*tmp);                 /* require lower case */
	}
	break;
    case PAM_USER:
	RESET(pamh->user, item);
	break;
    case PAM_USER_PROMPT:
	RESET(pamh->prompt, item);
	break;
    case PAM_TTY:
	D(("setting tty to %s", item));
	RESET(pamh->tty, item);
	break;
    case PAM_RUSER:
	RESET(pamh->ruser, item);
	break;
    case PAM_RHOST:
	RESET(pamh->rhost, item);
	break;
    case PAM_AUTHTOK:
	 /*
	  * The man page says this is only supposed to be available to
	  * the module providers.  In order to use this item the app
	  * has to #include <security/pam_modules.h>. This is something
	  * it is *not* supposed to do with "Linux-"PAM!  - AGM.
	  */
    {
	char *_TMP_ = pamh->authtok;
	if (_TMP_ == item)            /* not changed so leave alone */
	     break;
	pamh->authtok = (item) ? _pam_strdup(item) : NULL;
	if (_TMP_) {
	    _pam_overwrite(_TMP_);
	    free(_TMP_);
	}
	break;
    }
    case PAM_OLDAUTHTOK:
	 /* See note above. */
    {
	char *_TMP_ = pamh->oldauthtok;
	if (_TMP_ == item)            /* not changed so leave alone */
	     break;
	pamh->oldauthtok = (item) ? _pam_strdup(item) : NULL;
	if (_TMP_) {
	    _pam_overwrite(_TMP_);
	    free(_TMP_);
	}
	break;
    }
    case PAM_CONV:              /* want to change the conversation function */
	if (item == NULL) {
	    _pam_log_error("pam_set_item: attempt to set conv() to NULL");
	    retval = PAM_PERM_DENIED;
	} else {
	    struct pam_conv *tconv;
	    
	    if ((tconv=
		 (struct pam_conv *) malloc(sizeof(struct pam_conv))
		) == NULL) {
		_pam_log_error("pam_set_item: malloc failed for pam_conv");
		retval = PAM_BUF_ERR;
	    } else {
		memcpy(tconv, item, sizeof(struct pam_conv));
		_pam_drop(pamh->pam_conversation);
		pamh->pam_conversation = tconv;
	    }
	}
        break;
    default:
	retval = PAM_BAD_ITEM;
    }

    return (retval);
}

int pam_get_item (
    const pam_handle_t *pamh,
    int item_type,
    const void **item)
{
    D(("called."));
    IF_NO_PAMH("pam_get_item",pamh,PAM_SYSTEM_ERR);

    if (item == NULL) {
	_pam_log_error("pam_get_item: nowhere to place requested item");
	return PAM_PERM_DENIED;
    }

    switch (item_type) {
    case PAM_SERVICE:
	*item = pamh->service_name;
	break;
    case PAM_USER:
	*item = pamh->user;
	break;
    case PAM_USER_PROMPT:
	*item = pamh->prompt;
	break;
    case PAM_TTY:
	D(("returning tty=%s", pamh->tty));
	*item = pamh->tty;
	break;
    case PAM_RUSER:
	*item = pamh->ruser;
	break;
    case PAM_RHOST:
	*item = pamh->rhost;
	break;
    case PAM_AUTHTOK:
	*item = pamh->authtok;
	break;
    case PAM_OLDAUTHTOK:
	*item = pamh->oldauthtok;
	break;
    case PAM_CONV:
	*item = pamh->pam_conversation;
	break;
    default:
	/* XXX - I made this up */
	return PAM_BAD_ITEM;
    }
  
    return PAM_SUCCESS;
}

/* added by AGM 1996/3/2 */

int pam_get_user(pam_handle_t *pamh, const char **user, const char *prompt)
{
    const char *use_prompt;
    int retval;
    struct pam_message msg,*pmsg;
    struct pam_response *resp;

    D(("called."));
    IF_NO_PAMH("pam_get_user",pamh,PAM_SYSTEM_ERR);

    if (pamh->pam_conversation == NULL) {
	_pam_log_error("pam_get_user: no conv element in pamh");
	return PAM_SERVICE_ERR;
    }

    if (user == NULL) {  /* ensure the the module has suplied a destination */
	_pam_log_error("pam_get_user: nowhere to record username");
	return PAM_PERM_DENIED;
    } else
	*user = NULL;
    
    if (pamh->user) {    /* have one so return it */
	*user = pamh->user;
	return PAM_SUCCESS;
    }

    /* will need a prompt */

    use_prompt = prompt;
    if (use_prompt == NULL) {
	use_prompt = pamh->prompt;
	if (use_prompt == NULL) {
	    use_prompt = PAM_DEFAULT_PROMPT;
	}
    }

    /* converse with application -- prompt user for a username */

    pmsg = &msg;
    msg.msg_style = PAM_PROMPT_ECHO_ON;
    msg.msg = use_prompt;
    resp = NULL;

    retval = pamh->pam_conversation
	->conv(1, (const struct pam_message **) &pmsg
	       , &resp, pamh->pam_conversation->appdata_ptr);

    if (resp == NULL) {
	_pam_log_error("pam_get_user: no username obtained");
	return PAM_CONV_ERR;
    }

    if (retval == PAM_SUCCESS) {            /* copy the username */
	/*
	 * now we set the PAM_USER item -- this was missing from pre.53
	 * releases. However, re-reading the Sun manual, it is part of
	 * the standard API.
	 */

	RESET(pamh->user, resp->resp);
	*user = pamh->user;

	/*
	 * note 'resp' is allocated by the application and is
         * correctly free()'d here
	 */

	_pam_drop_reply(resp, 1);
    }

    return retval;        /* pass on any error from conversation */
}
