/*
 * (POP3Lite) pam - 3lite POP3 Daemon (pam authentication module)
 * Copyright (C) 2000, 2001 Gergely Nagy <8@free.bsd.hu>
 *
 * This file is part of POP3Lite.
 *
 * POP3Lite is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * POP3Lite is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

/*
 * Based on authenticate.c (C) 1999 Jerzy Balamut <jurek@dione.ids.pl>
 * (from the solid-pop3d sources)
 */

#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#ifndef PAM_SERVICE_NAME
#	define PAM_SERVICE_NAME "pop3lite"
#endif /* ! PAM_SERVICE_NAME */

#include <pop3lite.h>
#include <stdlib.h>
#include <security/pam_appl.h>
#include <glib.h>
#include <syslog.h>

static const char rcsid[]="$Id: pam.c,v 1.6 2001/01/12 15:58:59 algernon Exp $";

static void freeresp ( int numresp, struct pam_response *resp );
static int pconv ( int num_msg, const struct pam_message **msg, struct
		   pam_response **result, void *appdata_ptr );
static int auth_checkpassword ( GHashTable *data, const char *ausername, const char *apassword );

static AuthResult pam_sys_authenticate ( P3LControl *control,
					 const char *user,
					 const char *pass );

int pam_LTX_module_init ( P3LControl *control );
int pam_LTX_module_done ( P3LControl *control );

static P3LSysCon_authenticate B_pam_sys_authenticate;

/**
 * freeresp: free PAM response struct
 * @numresp: number of responses
 * @resp: the response struct
 *
 * Frees the struct
 *
 * Returns: nothing.
 **/

static void
freeresp ( int numresp, struct pam_response *resp )
{
	int respcount;
	
	for ( respcount = 0; respcount < numresp; respcount++ )
		if ( resp[respcount].resp != NULL )
			g_free ( resp[respcount].resp );
	g_free ( resp );
}

/**
 * pconv: PAM conversation function
 *
 * See the PAM docs :)
 **/

static int
pconv ( int num_msg, const struct pam_message **msg, struct
        pam_response **result, void *appdata_ptr )
{
	int account;
	struct pam_response *resp;

	/*
	 * Allocate memory
	 */

	resp = (struct pam_response *) g_malloc ( num_msg * sizeof ( struct pam_response ) );

	if ( resp == NULL )
		return PAM_CONV_ERR;

	/*
	 * Do the conversation...
	 */

	for ( account = 0; account < num_msg; account++ )
		switch ( msg[account]->msg_style )
		{
			case PAM_PROMPT_ECHO_OFF:
				resp[account].resp =
					(char *) g_strdup ( ((char **) appdata_ptr)[1] );
				if ( resp[account].resp == NULL )
				{
					freeresp ( account, resp );
					return PAM_CONV_ERR;
				}
				resp[account].resp_retcode = PAM_SUCCESS;
				break;
			case PAM_PROMPT_ECHO_ON:
				resp[account].resp =
					(char *) g_strdup ( ((char **) appdata_ptr)[0] );
				if ( resp[account].resp == NULL )
				{
					freeresp ( account, resp );
					return PAM_CONV_ERR;
				}
				resp[account].resp_retcode = PAM_SUCCESS;
				break;
			case PAM_TEXT_INFO:
			case PAM_ERROR_MSG:
				resp[account].resp_retcode = PAM_SUCCESS;
				resp[account].resp = NULL;
				break;
			default:
				freeresp ( account, resp );
				return PAM_CONV_ERR;
		}
	*result = resp;
	return PAM_SUCCESS;
}

/**
 * auth_checkpassword: check the password
 * @ausername: user name
 * @password:
 *
 * Checks the validity of the username/password pair.
 *
 * Returns: PAM_SUCCESS on succes, PAM_* on failure
 **/

static int
auth_checkpassword ( GHashTable *data,
		     const char *ausername,
		     const char *apassword )
{
	pam_handle_t *pamhandle;
	char *app_data[2];
	struct pam_conv pamconv;
	int result;

	app_data[0] = g_strdup ( ausername );
	app_data[1] = g_strdup ( apassword );

	pamconv.conv = &pconv;
	pamconv.appdata_ptr = app_data;

	/*
	 * Start conversation...
	 */

	if ( ( result = pam_start ( SERVICE_NAME, ausername, 
				    &pamconv, &pamhandle ) ) != PAM_SUCCESS )
		return result;

	g_hash_table_insert ( data, "PAM.HANDLE", (gpointer) pamhandle );

	/*
	 * Do the actual authentication
	 */

	if ( ( result = pam_authenticate ( pamhandle, 0 ) ) != PAM_SUCCESS )
		return result;

	/*
	 * Account management: this checks for expired password
	 * and the like
	 */

	if ( ( result = pam_acct_mgmt ( pamhandle, 0 ) ) != PAM_SUCCESS )
		return result;

	/*
	 * Set credentials...
	 */

	if ( ( result = pam_setcred ( pamhandle, PAM_ESTABLISH_CRED ) ) != PAM_SUCCESS )
		return result;

	/*
	 * Open session
	 */
	if ( ( result = pam_open_session ( pamhandle, 0 ) ) != PAM_SUCCESS )
		return result;

	/*
	 * Return
	 */

	return PAM_SUCCESS;
}

/**
 * pam_sys_authenticate: do user authentication
 * @control: the control struct
 * @name: username
 * @pass: password
 *
 * Does the authentication via PAM.
 *
 * Returns: the result of the conversation
 **/

static AuthResult
pam_sys_authenticate ( P3LControl *control,
		       const char *user,
		       const char *pass )
{
	int result;
	pam_handle_t *pamhandle;

#ifdef DEBUG
	control->system->log ( control, LOG_DEBUG, "%s:%d: Authenticating user: %s",
			       __FILE__, __LINE__, user );
#endif

	/*
	 * Do the authentication
	 */

	result = auth_checkpassword ( control->data, user, pass );

	/*
	 * This is to clean up after PAM
	 */

	control->system->closelog ( control );
	control->system->openlog ( control );

	/*
	 * Check the results. If auth failed:
	 *	(1) Fall back to the previous AUTH handler
	 *	(2) Bail out (default)
	 */

	if ( result != PAM_SUCCESS )
	{
		pamhandle = (pam_handle_t *) g_hash_table_lookup ( control->data, "PAM.HANDLE" );
		control->system->log ( control, LOG_WARNING, "PAM message: %s",
				       pam_strerror ( pamhandle, result ) );
		g_hash_table_remove ( control->data, "PAM.HANDLE" );
		pam_end ( pamhandle, result );

		if ( B_pam_sys_authenticate != NULL &&
		     p3l_is_enabled ( P3L_GET_FIRST_OPTION ("PAM.FALLBACK" ) ) )
			return (*B_pam_sys_authenticate) ( control, user, pass );
	}
	else
		return AUTH_RESULT_OK;

	return AUTH_RESULT_FAIL;
}

int
pam_LTX_module_init ( P3LControl *control )
{
#ifdef DEBUG
	control->system->log ( control, LOG_DEBUG, "%s:%d: init mod-PAM",
			       __FILE__, __LINE__ );
#endif
	B_pam_sys_authenticate = control->system->authenticate;
	control->system->authenticate = pam_sys_authenticate;

	return 0;
}

int
pam_LTX_module_done ( P3LControl *control )
{
	pam_handle_t *pamhandle;

#ifdef DEBUG
	control->system->log ( control, LOG_DEBUG, "%s:%d: done mod-PAM",
			       __FILE__, __LINE__ );
#endif

	pamhandle = (pam_handle_t *)
		g_hash_table_lookup ( control->data, "PAM.HANDLE" );

	if ( pamhandle != NULL )
	{
		pam_end ( pamhandle, pam_close_session ( pamhandle, 0 ) );
		/*
		 * This is to clean up after PAM
		 */

		control->system->closelog ( control );
		control->system->openlog ( control );
	}

	control->system->authenticate = B_pam_sys_authenticate;

	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1