/*
 * Author: Damien Miller
 * Copyright (c) 1999 Damien Miller <djm@mindrot.org>
 *                    All rights reserved
 * Created: Thursday December 30 1999
 * PAM authentication and session management code.
 */

#include "includes.h"

#ifdef USE_PAM
#include "ssh.h"
#include "xmalloc.h"
#include "servconf.h"

RCSID("$Id: auth-pam.c,v 1.4 2000/04/29 14:47:29 damien Exp $");

/* Callbacks */
static int pamconv(int num_msg, const struct pam_message **msg,
	  struct pam_response **resp, void *appdata_ptr);
void pam_cleanup_proc(void *context);

/* module-local variables */
static struct pam_conv conv = {
	pamconv,
	NULL
};
static struct pam_handle_t *pamh = NULL;
static const char *pampasswd = NULL;
static char *pamconv_msg = NULL;

/* PAM conversation function. This is really a kludge to get the password */
/* into PAM and to pick up any messages generated by PAM into pamconv_msg */
static int pamconv(int num_msg, const struct pam_message **msg,
	struct pam_response **resp, void *appdata_ptr)
{
	struct pam_response *reply;
	int count;
	size_t msg_len;
	char *p;

	/* PAM will free this later */
	reply = malloc(num_msg * sizeof(*reply));
	if (reply == NULL)
		return PAM_CONV_ERR; 

	for(count = 0; count < num_msg; count++) {
		switch (msg[count]->msg_style) {
			case PAM_PROMPT_ECHO_OFF:
				if (pampasswd == NULL) {
					free(reply);
					return PAM_CONV_ERR;
				}
				reply[count].resp_retcode = PAM_SUCCESS;
				reply[count].resp = xstrdup(pampasswd);
				break;

			case PAM_TEXT_INFO:
				reply[count].resp_retcode = PAM_SUCCESS;
				reply[count].resp = xstrdup("");

				if (msg[count]->msg == NULL)
					break;

				debug("Adding PAM message: %s", msg[count]->msg);

				msg_len = strlen(msg[count]->msg);
				if (pamconv_msg) {
					size_t n = strlen(pamconv_msg);
					pamconv_msg = xrealloc(pamconv_msg, n + msg_len + 2);
					p = pamconv_msg + n;
				} else {
					pamconv_msg = p = xmalloc(msg_len + 2);
				}
				memcpy(p, msg[count]->msg, msg_len);
				p[msg_len] = '\n';
				p[msg_len + 1] = '\0';
				break;

			case PAM_PROMPT_ECHO_ON:
			case PAM_ERROR_MSG:
			default:
				free(reply);
				return PAM_CONV_ERR;
		}
	}

	*resp = reply;

	return PAM_SUCCESS;
}

/* Called at exit to cleanly shutdown PAM */
void pam_cleanup_proc(void *context)
{
	int pam_retval;

	if (pamh != NULL)
	{
		pam_retval = pam_close_session((pam_handle_t *)pamh, 0);
		if (pam_retval != PAM_SUCCESS) {
			log("Cannot close PAM session: %.200s", 
			PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
		}

		pam_retval = pam_setcred((pam_handle_t *)pamh, PAM_DELETE_CRED);
		if (pam_retval != PAM_SUCCESS) {
			log("Cannot delete credentials: %.200s", 
			PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
		}

		pam_retval = pam_end((pam_handle_t *)pamh, pam_retval);
		if (pam_retval != PAM_SUCCESS) {
			log("Cannot release PAM authentication: %.200s", 
			PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
		}
	}
}

/* Attempt password authentation using PAM */
int auth_pam_password(struct passwd *pw, const char *password)
{
	extern ServerOptions options;
	int pam_retval;

	/* deny if no user. */
	if (pw == NULL)
		return 0;
	if (pw->pw_uid == 0 && options.permit_root_login == 2)
		return 0;
	if (*password == '\0' && options.permit_empty_passwd == 0)
		return 0;

	pampasswd = password;
	
	pam_retval = pam_authenticate((pam_handle_t *)pamh, 0);
	if (pam_retval == PAM_SUCCESS) {
		debug("PAM Password authentication accepted for user \"%.100s\"", pw->pw_name);
		return 1;
	} else {
		debug("PAM Password authentication for \"%.100s\" failed: %s", 
			pw->pw_name, PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
		return 0;
	}
}

/* Do account management using PAM */
int do_pam_account(char *username, char *remote_user)
{
	int pam_retval;

	debug("PAM setting rhost to \"%.200s\"", get_canonical_hostname());
	pam_retval = pam_set_item((pam_handle_t *)pamh, PAM_RHOST, 
		get_canonical_hostname());
	if (pam_retval != PAM_SUCCESS) {
		fatal("PAM set rhost failed: %.200s", PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
	}

	if (remote_user != NULL) {
		debug("PAM setting ruser to \"%.200s\"", remote_user);
		pam_retval = pam_set_item((pam_handle_t *)pamh, PAM_RUSER, remote_user);
		if (pam_retval != PAM_SUCCESS) {
			fatal("PAM set ruser failed: %.200s", PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
		}
	}

	pam_retval = pam_acct_mgmt((pam_handle_t *)pamh, 0);
	if (pam_retval != PAM_SUCCESS) {
		log("PAM rejected by account configuration: %.200s", PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
		return(0);
	}
	
	return(1);
}

/* Do PAM-specific session initialisation */
void do_pam_session(char *username, const char *ttyname)
{
	int pam_retval;

	if (ttyname != NULL) {
		debug("PAM setting tty to \"%.200s\"", ttyname);
		pam_retval = pam_set_item((pam_handle_t *)pamh, PAM_TTY, ttyname);
		if (pam_retval != PAM_SUCCESS)
			fatal("PAM set tty failed: %.200s", PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
	}

	pam_retval = pam_open_session((pam_handle_t *)pamh, 0);
	if (pam_retval != PAM_SUCCESS)
		fatal("PAM session setup failed: %.200s", PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
}

/* Set PAM credentials */ 
void do_pam_setcred()
{
	int pam_retval;
 
	debug("PAM establishing creds");
	pam_retval = pam_setcred((pam_handle_t *)pamh, PAM_ESTABLISH_CRED);
	if (pam_retval != PAM_SUCCESS)
		fatal("PAM setcred failed: %.200s", PAM_STRERROR((pam_handle_t *)pamh, pam_retval));
}

/* Cleanly shutdown PAM */
void finish_pam(void)
{
	pam_cleanup_proc(NULL);
	fatal_remove_cleanup(&pam_cleanup_proc, NULL);
}

/* Start PAM authentication for specified account */
void start_pam(struct passwd *pw)
{
	int pam_retval;

	debug("Starting up PAM with username \"%.200s\"", pw->pw_name);

	pam_retval = pam_start(SSHD_PAM_SERVICE, pw->pw_name, &conv, 
		(pam_handle_t**)&pamh);
	if (pam_retval != PAM_SUCCESS)
		fatal("PAM initialisation failed: %.200s", PAM_STRERROR((pam_handle_t *)pamh, pam_retval));

	fatal_add_cleanup(&pam_cleanup_proc, NULL);
}

/* Return list of PAM enviornment strings */
char **fetch_pam_environment(void)
{
#ifdef HAVE_PAM_GETENVLIST
	return(pam_getenvlist((pam_handle_t *)pamh));
#else /* HAVE_PAM_GETENVLIST */
	return(NULL);
#endif /* HAVE_PAM_GETENVLIST */
}

/* Print any messages that have been generated during authentication */
/* or account checking to stderr */
void print_pam_messages(void)
{
	if (pamconv_msg != NULL)
		fprintf(stderr, pamconv_msg);
}

#endif /* USE_PAM */
