/*
 * pam_interpose
 *
 * (C)opyright in 2001,2001,2003,2004
 *             by Karsten Petersen <kapet@hrz.tu-chemnitz.de>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// tell the C-Library what we want
#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <dlfcn.h>

#include <security/pam_appl.h>
#include <security/pam_modules.h>

//----------------------------------------------------------------------------
// always:  pam_start, pam_end, pam_authenticate, pam_setcred, pam_acct_mgmt,
//          pam_open_session, pam_close_session, pam_chauthtok, pam_fail_delay

// (conversation function)
#define ____WITH_CONV

// pam_set_item, pam_get_item, pam_get_user
#define ____WITH_ITEM

// pam_putenv, pam_getenv, pam_getenvlist
#define ____WITH_ENV

// pam_set_data, pam_get_data
#define ____WITH_DATA

//----------------------------------------------------------------------------

// name of file to log to
#define LOGFILE	"/tmp/pam_interpose.log"

static void _log(int inc, const char *format, ...)
{
	static int inclen = 0;
	va_list args;
	int fd, i;
	static char buffer[256];

	if (inc < 0)
		inclen += inc;
	if (inclen < 0) {
		inclen = 0;
		_log(0, "***** ERROR: inclen<0 in _log *****");
	}

	memset(buffer, ' ', inclen);
	i = inclen;

	va_start(args, format);
	i += vsprintf(&buffer[i], format, args);
	buffer[i++] = '\n';
	va_end(args);

	if ((fd = open(LOGFILE, O_WRONLY|O_NOFOLLOW|O_APPEND)) != -1) {
		write(fd, buffer, i);
		close(fd);
	}
//	else {
//		write(STDERR_FILENO, buffer, i);
//	}

	if (inc > 0)
		inclen += inc;
}

//----------------------------------------------------------------------------

#define TEST(mask)				\
	if (flags & mask) {			\
		strcat(buffer, #mask " ");	\
		flags = flags ^ mask;		\
	}

static char *pamflags (int flags)
{
	static char buffer[128];
	buffer[0] = '\0';

	TEST(PAM_SILENT)
	TEST(PAM_DISALLOW_NULL_AUTHTOK)
	TEST(PAM_ESTABLISH_CRED)
	TEST(PAM_DELETE_CRED)
	TEST(PAM_REINITIALIZE_CRED)
	TEST(PAM_REFRESH_CRED)
	TEST(PAM_CHANGE_EXPIRED_AUTHTOK)

	if (flags != 0) {
		char *buff = buffer + strlen(buffer);
		sprintf(buff, "    ** unknown flags: %i **", flags);
	}

	if (!buffer[0])
		strcpy(buffer, "(no flags)");

	return buffer;
}

#undef TEST

//----------------------------------------------------------------------------

#define TEST(value)				\
	case value :				\
		strcpy(buffer, #value);		\
		break;

static char *pamretval (int retval)
{
	static char buffer[128];

	switch (retval) {
	TEST(PAM_SUCCESS)
	TEST(PAM_OPEN_ERR)
	TEST(PAM_SYMBOL_ERR)
	TEST(PAM_SERVICE_ERR)
	TEST(PAM_SYSTEM_ERR)
	TEST(PAM_BUF_ERR)
	TEST(PAM_PERM_DENIED)
	TEST(PAM_AUTH_ERR)
	TEST(PAM_CRED_INSUFFICIENT)
	TEST(PAM_AUTHINFO_UNAVAIL)
	TEST(PAM_USER_UNKNOWN)
	TEST(PAM_MAXTRIES)
	TEST(PAM_NEW_AUTHTOK_REQD)
	TEST(PAM_ACCT_EXPIRED)
	TEST(PAM_SESSION_ERR)
	TEST(PAM_CRED_UNAVAIL)
	TEST(PAM_CRED_EXPIRED)
	TEST(PAM_CRED_ERR)
	TEST(PAM_NO_MODULE_DATA)
	TEST(PAM_CONV_ERR)
	TEST(PAM_AUTHTOK_ERR)
	TEST(PAM_AUTHTOK_RECOVER_ERR)
	TEST(PAM_AUTHTOK_LOCK_BUSY)
	TEST(PAM_AUTHTOK_DISABLE_AGING)
	TEST(PAM_TRY_AGAIN)
	TEST(PAM_IGNORE)
	TEST(PAM_ABORT)
	TEST(PAM_AUTHTOK_EXPIRED)
	TEST(PAM_MODULE_UNKNOWN)
	TEST(PAM_BAD_ITEM)
	TEST(PAM_CONV_AGAIN)
	TEST(PAM_INCOMPLETE)

	default :
		sprintf(buffer, "** unknown return value: %i **", retval);
	}

	return buffer;
}

#undef TEST

//----------------------------------------------------------------------------

#ifdef ____WITH_CONV

static int pamconv (int num_msg, const struct pam_message **msg,
		    struct pam_response **resp, void *appdata_ptr)
{
	int retval, i;
	struct pam_conv *origconv = (struct pam_conv *) appdata_ptr;
	char buffer[256];

	_log( 0, "pamconv c%8.8X       %i",
			origconv, num_msg);

	for (i=0; i<num_msg; i++) {
		switch (msg[i]->msg_style) {
		case PAM_PROMPT_ECHO_OFF :
			sprintf(buffer, "%i - PAM_PROMPT_ECHO_OFF - \"%s\"",
					i, msg[i]->msg);
			break;
		case PAM_PROMPT_ECHO_ON :
			sprintf(buffer, "%i - PAM_PROMPT_ECHO_ON - \"%s\"",
					i, msg[i]->msg);
			break;
		case PAM_ERROR_MSG :
			sprintf(buffer, "%i - PAM_ERROR_MSG - \"%s\"",
					i, msg[i]->msg);
			break;
		case PAM_TEXT_INFO :
			sprintf(buffer, "%i - PAM_TEXT_INFO - \"%s\"",
					i, msg[i]->msg);
			break;
		case PAM_RADIO_TYPE :
			sprintf(buffer, "%i - PAM_RADIO_TYPE - "
					"(how to display?)",
					i);
			break;
		case PAM_BINARY_PROMPT :
			sprintf(buffer, "%i - PAM_BINARY_PROMPT - "
					"(binary data)",
					i);
			break;
		default :
			sprintf(buffer, "%i - unknown message style %i",
					i, msg[i]->msg_style);
		}
		if (i == num_msg - 1) {
			_log( 4, "                        msg  %s",
					buffer);
		} else {
			_log( 0, "                        msg  %s",
					buffer);
		}
	}

	retval = (origconv->conv(num_msg, msg, resp, origconv->appdata_ptr));

	if (retval != PAM_SUCCESS) {
		_log(-4, "                        %s",
				pamretval(retval));

		return retval;
	}

	for (i=0; i<num_msg; i++) {
		switch (msg[i]->msg_style) {
		case PAM_PROMPT_ECHO_OFF :
			sprintf(buffer, "%i - %u - (string not shown)",
					i, (*resp)[i].resp_retcode);
			//sprintf(buffer, "%i - %u - \"%s\"",
			//		i, (*resp)[i].resp_retcode,
			//		(*resp)[i].resp);
			break;
		case PAM_PROMPT_ECHO_ON :
		case PAM_ERROR_MSG :
		case PAM_TEXT_INFO :
			sprintf(buffer, "%i - %u - \"%s\"",
					i, (*resp)[i].resp_retcode,
					(*resp)[i].resp);
			break;
		case PAM_RADIO_TYPE :
			sprintf(buffer, "%i - %u - (how to display?)",
					i, (*resp)[i].resp_retcode);
			break;
		case PAM_BINARY_PROMPT :
			sprintf(buffer, "%i - %u - (binary data)",
					i, (*resp)[i].resp_retcode);
			break;
		default :
			sprintf(buffer, "%i - %u - unknown message style %i",
					i, (*resp)[i].resp_retcode, 
					msg[i]->msg_style);
		}
		if (i == 0) {
			_log(-4, "                        resp %s",
					buffer);
		} else {
			_log( 0, "                        resp %s",
					buffer);
		}
	}
	_log( 0, "                        %s",
			pamretval(retval));

	return retval;
}

static struct pam_conv *pamconv_prep (const struct pam_conv *origconv)
{
	struct pam_conv *retval;

	retval = (struct pam_conv *) malloc(sizeof(struct pam_conv));
	if (!retval) {
		printf("***** ERROR:  out of memory *****");
		exit(EXIT_FAILURE);
	}

//	_log( 0, "pamconv_prep");

	retval -> conv = pamconv;
	retval -> appdata_ptr = (struct pam_conv *) origconv;

	return retval;
}

#else // ____WITH_CONV

static struct pam_conv *pamconv_prep (const struct pam_conv *origconv)
{
	return (struct pam_conv *) origconv;
}

#endif // ____WITH_CONV

//----------------------------------------------------------------------------

#define interpose_prep(f) 					\
	static int (*func) ();					\
	if (!func) {						\
		func = (int (*)()) dlsym(RTLD_NEXT, f);	\
		if (!func) {					\
			printf("*** wrapper error *** "		\
			       "function " f " not found\n");	\
			exit(EXIT_FAILURE);			\
		}						\
	}

//----------------------------------------------------------------------------

int pam_start (const char *service_name, const char *user, 
	       const struct pam_conv *pam_conversation, 
	       pam_handle_t **pamh)
{	
	int retval;
	interpose_prep("pam_start");

	_log( 4, "pam_start               \"%s\" / \"%s\" / c%8.8X",
			service_name, user, pam_conversation);

	retval = (func (service_name, user, pamconv_prep(pam_conversation),
				pamh));

	_log(-4, "                        %s / h%8.8X",
			pamretval(retval), *pamh);

	return retval;
}

int pam_end (pam_handle_t *pamh, int pam_status)
{
	int retval;
	interpose_prep("pam_end");
	
	_log( 4, "pam_end                 h%8.8X / %i",
			pamh, pam_status);

	retval = (func (pamh, pam_status));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_authenticate(pam_handle_t *pamh, int flags)
{
	int retval;
	interpose_prep("pam_authenticate");

	_log( 4, "pam_authenticate        h%8.8X / %s",
			pamh, pamflags(flags));

	retval = (func (pamh, flags));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_setcred(pam_handle_t *pamh, int flags)
{
	int retval;
	interpose_prep("pam_setcred");

	_log( 4, "pam_setcred             h%8.8X / %s",
			pamh, pamflags(flags));

	retval = (func (pamh, flags));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_acct_mgmt(pam_handle_t *pamh, int flags)
{
	int retval;
	interpose_prep("pam_acct_mgmt");

	_log( 4, "pam_acct_mgmt           h%8.8X / %s",
			pamh, pamflags(flags));

	retval = (func (pamh, flags));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_open_session(pam_handle_t *pamh, int flags)
{
	int retval;
	interpose_prep("pam_open_session");

	_log( 4, "pam_open_session        h%8.8X / %s",
			pamh, pamflags(flags));

	retval = (func (pamh, flags));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_close_session(pam_handle_t *pamh, int flags)
{
	int retval;
	interpose_prep("pam_close_session");

	_log( 4, "pam_close_session       h%8.8X / %s",
			pamh, pamflags(flags));

	retval = (func (pamh, flags));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_chauthtok(pam_handle_t *pamh, int flags)
{
	int retval;
	interpose_prep("pam_chauthtok");

	_log( 4, "pam_chauthtok           h%8.8X / %s",
			pamh, pamflags(flags));

	retval = (func (pamh, flags));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_fail_delay(pam_handle_t *pamh, unsigned int musec_delay)
{
	int retval;
	interpose_prep("pam_fail_delay");

	_log( 4, "pam_fail_delay          h%8.8X / %u",
			pamh, musec_delay);

	retval = (func (pamh, musec_delay));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

//----------------------------------------------------------------------------

#ifdef ____WITH_ITEM

#define TEST(value)				\
	case value :				\
		strcpy(buffer, #value);		\
		break;

static char *pamitems (int item)
{
	static char buffer[128];

	switch(item) {
	TEST(PAM_SERVICE)
	TEST(PAM_USER)
	TEST(PAM_TTY)
	TEST(PAM_RHOST)
	TEST(PAM_CONV)
	TEST(PAM_AUTHTOK)
	TEST(PAM_OLDAUTHTOK)
	TEST(PAM_RUSER)
	TEST(PAM_USER_PROMPT)
	TEST(PAM_FAIL_DELAY)

	default :
		sprintf(buffer, "** unknown item: %i **", item);
	}

	return buffer;
}

#undef TEST

int pam_set_item(pam_handle_t *pamh, int item_type, const void *item)
{
	int retval;
	const char *s;
	char buffer[256];
	struct pam_conv *conv;
	interpose_prep("pam_set_item");

	switch (item_type) {
	case PAM_SERVICE :
	case PAM_USER :
	case PAM_TTY :
	case PAM_RHOST :
	case PAM_RUSER :
	case PAM_USER_PROMPT :
		sprintf(buffer, "\"%s\"", item);
		s = buffer;
		break;

	case PAM_CONV :
		conv = (struct pam_conv *) item;
		sprintf(buffer, "c%8.8X", conv);
		s = buffer;
		item = (const void *) pamconv_prep(conv);
		break;

	case PAM_FAIL_DELAY :
		sprintf(buffer, "p%8.8X", item);
		s = buffer;
		break;

	case PAM_AUTHTOK :
	case PAM_OLDAUTHTOK :
		if (item == NULL) {
			strcpy(buffer, "(null)");
			s = buffer;
		} else {
			strcpy(buffer, "(string not shown)");
			//strcpy(buffer, item);
			s = buffer;
		}
		break;

	default :
		strcpy(buffer, "** unknown item type **");
		s = buffer;
	}

	_log( 4, "pam_set_item            h%8.8X / %s / %s",
			pamh, pamitems(item_type), s);

	retval = (func (pamh, item_type, item));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item)
{
	int retval;
	const char *s;
	char buffer[256];
	struct pam_conv *conv;
	interpose_prep("pam_get_item");

	_log( 4, "pam_get_item            h%8.8X / %s",
			pamh, pamitems(item_type));

	retval = (func (pamh, item_type, item));

	switch (item_type) {
	case PAM_SERVICE :
	case PAM_USER :
	case PAM_TTY :
	case PAM_RHOST :
	case PAM_RUSER :
	case PAM_USER_PROMPT :
		sprintf(buffer, "\"%s\"", *item);
		s = buffer;
		break;

	case PAM_CONV :
		conv = (struct pam_conv *) *item;
#ifdef ____WITH_CONV
		sprintf(buffer, "c%8.8X", conv->appdata_ptr);
#else
		sprintf(buffer, "c%8.8X", conv);
#endif
		s = buffer;
		break;

	case PAM_FAIL_DELAY :
		sprintf(buffer, "p%8.8X", *item);
		s = buffer;
		break;
	
	case PAM_AUTHTOK :
	case PAM_OLDAUTHTOK :
		if (item == NULL) {
			strcpy(buffer, "(null)");
			s = buffer;
		} else {
			strcpy(buffer, "(string not shown)");
			//strcpy(buffer, *item);
			s = buffer;
		}
		break;

	default :
		strcpy(buffer, "** unknown item type **");
		s = buffer;
	}

	_log(-4, "                        %s / %s",
			pamretval(retval), s);

	return retval;
}

int pam_get_user(pam_handle_t *pamh, const char **user, const char *prompt)
{
	int retval;
	interpose_prep("pam_get_user");

	_log( 4, "pam_get_user            h%8.8X / \"%s\"",
			pamh, prompt);

	retval = (func (pamh, user, prompt));

	_log(-4, "                        %s / \"%s\"",
			pamretval(retval), *user);

	return retval;
}

#endif // ____WITH_ITEM

//----------------------------------------------------------------------------

#ifdef ____WITH_ENV

int pam_putenv(pam_handle_t *pamh, const char *name_value)
{
	int retval;
	interpose_prep("pam_putenv");

	_log( 4, "pam_putenv              h%8.8X / \"%s\"",
			pamh, name_value);

	retval = (func (pamh, name_value));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

const char *pam_getenv(pam_handle_t *pamh, const char *name)
{
	const char *retval;
	static const char * (*func) ();
	if (!func) {
		func = (const char * (*)()) dlsym(RTLD_NEXT, "pam_getenv");
		if (!func) {
			printf("*** wrapper error *** "
			       "function pam_getenv not found\n");
			exit(EXIT_FAILURE);
		}
	}

	_log( 4, "pam_getenv              h%8.8X / \"%s\"",
			pamh, name);

	retval = (func (pamh, name));

	_log(-4, "                        \"%s\"",
			retval);

	return retval;
}

char **pam_getenvlist(pam_handle_t *pamh)
{
	char **retval;
	static char ** (*func) ();
	if (!func) {
		func = (char ** (*)()) dlsym(RTLD_NEXT, "pam_getenvlist");
		if (!func) {
			printf("*** wrapper error *** "
			       "function pam_getenvlist not found\n");
			exit(EXIT_FAILURE);
		}
	}

	_log( 4, "pam_getenvlist          h%8.8X",
			pamh);

	retval = (func (pamh));

	_log(-4, "                        p%8.8X",
			retval);

	return retval;
}

#endif // ____WITH_ENV

//----------------------------------------------------------------------------

#ifdef ____WITH_DATA

int pam_set_data(pam_handle_t *pamh, const char *module_data_name, void *data,
		 void (*cleanup)(pam_handle_t *pamh, void *data, 
			         int error_status))
{
	int retval;
	interpose_prep("pam_set_data");

	_log( 4, "pam_set_data            h%8.8X / \"%s\" / p%8.8X / p%8.8X",
			pamh, module_data_name, data, cleanup);

	retval = (func (pamh, module_data_name, data, cleanup));

	_log(-4, "                        %s",
			pamretval(retval));

	return retval;
}

int pam_get_data(const pam_handle_t *pamh, const char *module_data_name,
		 const void **data)
{
	int retval;
	interpose_prep("pam_get_data");

	_log( 4, "pam_get_data            h%8.8X / \"%s\"",
			pamh, module_data_name);

	retval = (func (pamh, module_data_name, data));

	_log(-4, "                        %s / p%8.8X",
			pamretval(retval), *data);

	return retval;
}

#endif // ____WITH_DATA

