/* to be conditionally included into passwd.c */
/* originally from wu-ftpd's ftpd.c ; mods for slforum by Jeremy Monin */

#ifdef USE_PAM
#if defined(USE_SHADOW) 
#error No other auth methods are allowed with PAM.
#endif
static int pam_check_pass(char *user, char *passwd, char *remotehost);
#endif


#ifdef USE_PAM
/* This is rather an abuse of PAM, but the FTP protocol doesn't allow much
 * flexibility here.  :-(
 */

#include <security/pam_appl.h>
/* Static variables used to communicate between the conversation function
 * and the server_login function
 */
static char *PAM_password;

/* PAM conversation function
 * Here we assume (for now, at least) that echo on means login name, and
 * echo off means password.
 */
static int PAM_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{
    int replies = 0;
    struct pam_response *reply = NULL;

#define COPY_STRING(s) (s) ? strdup(s) : NULL

    reply = malloc(sizeof(struct pam_response) * num_msg);
    if (!reply)
	return PAM_CONV_ERR;

    for (replies = 0; replies < num_msg; replies++) {
	switch (msg[replies]->msg_style) {
	case PAM_PROMPT_ECHO_ON:
	    return PAM_CONV_ERR;
	    break;
	case PAM_PROMPT_ECHO_OFF:
	    reply[replies].resp_retcode = PAM_SUCCESS;
	    reply[replies].resp = COPY_STRING(PAM_password);
	    /* PAM frees resp */
	    break;
	case PAM_TEXT_INFO:
	    /* ignore it... */
	    reply[replies].resp_retcode = PAM_SUCCESS;
	    reply[replies].resp = NULL;
	    break;
	case PAM_ERROR_MSG:
	    /* ignore it... */
	    reply[replies].resp_retcode = PAM_SUCCESS;
	    reply[replies].resp = NULL;
	    break;
	default:
	    /* Must be an error of some sort... */
	    return PAM_CONV_ERR;
	}
    }
    *resp = reply;
    return PAM_SUCCESS;
}
static struct pam_conv PAM_conversation =
{
    &PAM_conv,
    NULL
};

static int pam_check_pass(char *user, char *passwd, char *remotehost)
{
    pam_handle_t *pamh;
    int pam_error;

    /* Now use PAM to do authentication.  For now, we won't worry about
     * session logging, only authentication.  Bail out if there are any
     * errors.  Since this is a limited protocol, and an even more limited
     * function within a server speaking this protocol, we can't be as
     * verbose as would otherwise make sense.
     */
#define PAM_BAIL if (pam_error != PAM_SUCCESS) { Lprintf("passwd: PAM failed on line %d: error is %d %s\n", __LINE__, pam_error, pam_strerror(pamh,pam_error)); pam_end(pamh,0); return 0; }

    PAM_password = passwd;
    pam_error = pam_start("login", user, &PAM_conversation, &pamh);
    PAM_BAIL;
    pam_set_item(pamh, PAM_RHOST, remotehost);
    PAM_BAIL;
    pam_error = pam_authenticate(pamh, 0);
    PAM_BAIL;
    pam_error = pam_acct_mgmt(pamh, 0);
    PAM_BAIL;
#ifdef PAM_ESTABLISH_CRED
    pam_error = pam_setcred(pamh, PAM_ESTABLISH_CRED);
#else
    pam_error = pam_setcred(pamh, PAM_CRED_ESTABLISH);
#endif
    PAM_BAIL;
    pam_end(pamh, PAM_SUCCESS);
    /* If this point is reached, the user has been authenticated. */
    return 1;
}


static struct passwd *pam_validate(char *uname, char *passwd, char *remhost)
{
  if (0 == pam_check_pass(uname, passwd, remhost))
    return NULL;

  return getpwnam(uname);
}

#endif
