/*
 * This is a PAM module whose purpose is to easily provide user-defined
 * prompt strings. It always returns authentication success
 * (so make it "required", not "sufficient").
 *
 * It leaks memory and has other bugs.
 *
 * By Ted Percival <ted@midg3t.net>, 2007-02-01.
 *
 * Copyright © 2007, Quest Software
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Quest Software nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY QUEST SOFTWARE ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL QUEST SOFTWARE BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

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

#define PAM_SM_AUTH
#include <security/pam_modules.h>
#include <security/_pam_macros.h> /* Only available with Linux-PAM */

static int read_config (struct pam_message ***messages);

PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) {
	int num_messages, i;
	struct pam_message **pammsg = NULL;
	struct pam_conv *pconv;
	struct pam_response *pamresp;

	if (flags & PAM_SILENT)
		return PAM_SUCCESS;

	num_messages = read_config(&pammsg);
	if (num_messages == -1)
		return PAM_SUCCESS; /* If there are no prompts, stay out of the way */

	if (PAM_SUCCESS != pam_get_item(pamh, PAM_CONV, (void*)&pconv))
		goto fail;

	/* Most modules only send a single prompt at a time, even though PAM
	 * encourages sending several at once for the benefit of GUIs. */
#if 1 /* Send one at a time */
	for (i = 0; i < num_messages; ++i) {
		if (PAM_SUCCESS == pconv->conv(1, (const struct pam_message **)&pammsg[i], &pamresp, pconv->appdata_ptr)) {
			/* Pretend to process the responses... */
			/* ... */

			_pam_drop_reply(pamresp, 1);
		}
	}

	return PAM_SUCCESS;

#else /* Send all prompts at once */
	if (PAM_SUCCESS == pconv->conv(num_messages, (const struct pam_message **)pammsg, &pamresp, pconv->appdata_ptr)) {
		/* Pretend to process the responses... */
		/* ... */

		_pam_drop_reply(pamresp, num_messages);
		return PAM_SUCCESS;
	}
#endif

fail:
	/* This looks leaky */
	if (pammsg)
		free(pammsg);

	return PAM_AUTH_ERR;
}

static void eval_newlines(char *txt) {
	char *src, *dest;
	for (src = dest = txt; *src; ++src, ++dest) {
		if (src[0] == '\\' && src[1] == 'n')
			*++src = '\n';
		*dest = *src;
	}
	*dest = '\0';
}

#ifndef CONFIG_FILE
#define CONFIG_FILE "/etc/pam_prompt.conf"
#endif

/*
 * Return values
 * -1 error
 *  0 nothing to prompt for
 * >0 number of messages/prompts
 *
 * There are a bunch of memory allocation and freeing bugs in this function.
 * Probably the most significant is the freeing of an uninitialised pointer
 * (this_msg->msg) during the error path, and the fact that that memory
 * is never freed on the success path.
 *
 */
static int read_config(struct pam_message ***messages) {
	FILE *cf = NULL;
	char buf[255];
	char *txt_msg;
	int num_alloc = 4, this_msg_count = 0;
	struct pam_message *this_msg, *top_msg;
	struct pam_message **msg_ptrs = NULL;
	int i;

	cf = fopen(CONFIG_FILE, "r");
	if (!cf)
		goto fail;

	msg_ptrs = malloc(sizeof(struct pam_message*) * num_alloc);
	top_msg = malloc(sizeof(struct pam_message) * num_alloc);
	if (!top_msg || !msg_ptrs)
		goto fail; /* ENOMEM */

	for ( ; fgets(buf, sizeof(buf), cf); ++this_msg_count) {
		if (!strchr(buf, '\n'))
			goto fail; /* Line too long or no newline at end of file*/

		if (this_msg_count >= num_alloc) {
			msg_ptrs = realloc(msg_ptrs, sizeof(struct pam_message*) * (num_alloc *= 2));
			top_msg = realloc(top_msg, sizeof(struct pam_message) * num_alloc);
			if (!top_msg || !msg_ptrs)
				goto fail; /* ENOMEM */
		}

		/* Set the first whitespace character to NUL to split the line into
		 * two C strings */
		txt_msg = buf;
		while (!isspace(*txt_msg) && *txt_msg != '\n' && *txt_msg != '\0')
			++txt_msg;
		*txt_msg++ = '\0';

		/* Remove the trailing newline */
		char *n = strchr(txt_msg, '\n');
		if (n) *n = '\0';

		/* Expand any embedded newlines */
		eval_newlines(txt_msg);

		this_msg = &top_msg[this_msg_count];

		/* FIXME: This memory leaks, except on the error path */
		if (NULL == (this_msg->msg = strdup(txt_msg)))
			goto fail;

		msg_ptrs[this_msg_count] = this_msg;

		if (0 == strcmp(buf, "PAM_PROMPT_ECHO_OFF"))
			this_msg->msg_style = PAM_PROMPT_ECHO_OFF;
		else if (0 == strcmp(buf, "PAM_PROMPT_ECHO_ON"))
			this_msg->msg_style = PAM_PROMPT_ECHO_ON;
		else if (0 == strcmp(buf, "PAM_ERROR_MSG"))
			this_msg->msg_style = PAM_ERROR_MSG;
		else if (0 == strcmp(buf, "PAM_TEXT_INFO"))
			this_msg->msg_style = PAM_TEXT_INFO;
		else
			this_msg->msg_style = PAM_ERROR_MSG; /* Wow, what a craphack */
	}

	/* Successorise! */

	*messages = msg_ptrs;

	if (cf)
		fclose(cf); /* Duplicated code */

	return this_msg_count;

fail:

	if (cf)
		fclose(cf);

	if (msg_ptrs) {
		for (i = 0; i < this_msg_count; ++i) {
			free(msg_ptrs[i]->msg); /* Might be freeing an uninitialised pointer */
			free(msg_ptrs[i]);
		}
		free(msg_ptrs);
	}

	return -1;
}

/* vim:ts=4:sw=4:noet
 */
