/*
 * src/clients/ksu/pam.c
 *
 * Copyright 2007,2009,2010 Red Hat, Inc.
 *
 * 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 Red Hat, Inc. 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 THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
 * 
 * Convenience wrappers for using PAM.
 */

#include "autoconf.h"
#ifdef USE_PAM
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "k5-int.h"
#include "pam.h"

#ifndef MAXPWSIZE
#define MAXPWSIZE 128
#endif

static int appl_pam_started;
static pid_t appl_pam_starter = -1;
static int appl_pam_session_opened;
static int appl_pam_creds_initialized;
static int appl_pam_pwchange_required;
static pam_handle_t *appl_pamh;
static struct pam_conv appl_pam_conv;
static char *appl_pam_user;
struct appl_pam_non_interactive_args {
	const char *user;
	const char *password;
};

int
appl_pam_enabled(krb5_context context, const char *section)
{
	int enabled = 1;
	if ((context != NULL) && (context->profile != NULL)) {
		if (profile_get_boolean(context->profile,
					section,
					USE_PAM_CONFIGURATION_KEYWORD,
					NULL,
					enabled, &enabled) != 0) {
			enabled = 1;
		}
	}
	return enabled;
}

void
appl_pam_cleanup(void)
{
	if (getpid() != appl_pam_starter) {
		return;
	}
#ifdef DEBUG
	printf("Called to clean up PAM.\n");
#endif
	if (appl_pam_creds_initialized) {
#ifdef DEBUG
		printf("Deleting PAM credentials.\n");
#endif
		pam_setcred(appl_pamh, PAM_DELETE_CRED);
		appl_pam_creds_initialized = 0;
	}
	if (appl_pam_session_opened) {
#ifdef DEBUG
		printf("Closing PAM session.\n");
#endif
		pam_close_session(appl_pamh, 0);
		appl_pam_session_opened = 0;
	}
	appl_pam_pwchange_required = 0;
	if (appl_pam_started) {
#ifdef DEBUG
		printf("Shutting down PAM.\n");
#endif
		pam_end(appl_pamh, 0);
		appl_pam_started = 0;
		appl_pam_starter = -1;
		free(appl_pam_user);
		appl_pam_user = NULL;
	}
}
static int
appl_pam_interactive_converse(int num_msg, const struct pam_message **msg,
			      struct pam_response **presp, void *appdata_ptr)
{
	const struct pam_message *message;
	struct pam_response *resp;
	int i, code;
	char *pwstring, pwbuf[MAXPWSIZE];
	unsigned int pwsize;
	resp = malloc(sizeof(struct pam_response) * num_msg);
	if (resp == NULL) {
		return PAM_BUF_ERR;
	}
	memset(resp, 0, sizeof(struct pam_response) * num_msg);
	code = PAM_SUCCESS;
	for (i = 0; i < num_msg; i++) {
		message = &(msg[0][i]); /* XXX */
		message = msg[i]; /* XXX */
		pwstring = NULL;
		switch (message->msg_style) {
		case PAM_TEXT_INFO:
		case PAM_ERROR_MSG:
			printf("[%s]\n", message->msg ? message->msg : "");
			fflush(stdout);
			resp[i].resp = NULL;
			resp[i].resp_retcode = PAM_SUCCESS;
			break;
		case PAM_PROMPT_ECHO_ON:
		case PAM_PROMPT_ECHO_OFF:
			if (message->msg_style == PAM_PROMPT_ECHO_ON) {
				if (fgets(pwbuf, sizeof(pwbuf),
					  stdin) != NULL) {
					pwbuf[strcspn(pwbuf, "\r\n")] = '\0';
					pwstring = pwbuf;
				}
			} else {
				pwstring = getpass(message->msg ?
						   message->msg :
						   "");
			}
			if ((pwstring != NULL) && (pwstring[0] != '\0')) {
				pwsize = strlen(pwstring);
				resp[i].resp = malloc(pwsize + 1);
				if (resp[i].resp == NULL) {
					resp[i].resp_retcode = PAM_BUF_ERR;
				} else {
					memcpy(resp[i].resp, pwstring, pwsize);
					resp[i].resp[pwsize] = '\0';
					resp[i].resp_retcode = PAM_SUCCESS;
				}
			} else {
				resp[i].resp_retcode = PAM_CONV_ERR;
				code = PAM_CONV_ERR;
			}
			break;
		default:
			break;
		}
	}
	*presp = resp;
	return code;
}
static int
appl_pam_non_interactive_converse(int num_msg,
				  const struct pam_message **msg,
				  struct pam_response **presp,
				  void *appdata_ptr)
{
	const struct pam_message *message;
	struct pam_response *resp;
	int i, code;
	unsigned int pwsize;
	struct appl_pam_non_interactive_args *args;
	const char *pwstring;
	resp = malloc(sizeof(struct pam_response) * num_msg);
	if (resp == NULL) {
		return PAM_BUF_ERR;
	}
	args = appdata_ptr;
	memset(resp, 0, sizeof(struct pam_response) * num_msg);
	code = PAM_SUCCESS;
	for (i = 0; i < num_msg; i++) {
		message = &((*msg)[i]);
		message = msg[i];
		pwstring = NULL;
		switch (message->msg_style) {
		case PAM_TEXT_INFO:
		case PAM_ERROR_MSG:
			break;
		case PAM_PROMPT_ECHO_ON:
		case PAM_PROMPT_ECHO_OFF:
			if (message->msg_style == PAM_PROMPT_ECHO_ON) {
				/* assume "user" */
				pwstring = args->user;
			} else {
				/* assume "password" */
				pwstring = args->password;
			}
			if ((pwstring != NULL) && (pwstring[0] != '\0')) {
				pwsize = strlen(pwstring);
				resp[i].resp = malloc(pwsize + 1);
				if (resp[i].resp == NULL) {
					resp[i].resp_retcode = PAM_BUF_ERR;
				} else {
					memcpy(resp[i].resp, pwstring, pwsize);
					resp[i].resp[pwsize] = '\0';
					resp[i].resp_retcode = PAM_SUCCESS;
				}
			} else {
				resp[i].resp_retcode = PAM_CONV_ERR;
				code = PAM_CONV_ERR;
			}
			break;
		default:
			break;
		}
	}
	*presp = resp;
	return code;
}
static int
appl_pam_start(const char *service, int interactive,
	       const char *login_username,
	       const char *non_interactive_password,
	       const char *hostname,
	       const char *ruser,
	       const char *tty)
{
	static int exit_handler_registered;
	static struct appl_pam_non_interactive_args args;
	int ret = 0;
	if (appl_pam_started &&
	    (strcmp(login_username, appl_pam_user) != 0)) {
		appl_pam_cleanup();
		appl_pam_user = NULL;
	}
	if (!appl_pam_started) {
#ifdef DEBUG
		printf("Starting PAM up (service=\"%s\",user=\"%s\").\n",
		       service, login_username);
#endif
		memset(&appl_pam_conv, 0, sizeof(appl_pam_conv));
		appl_pam_conv.conv = interactive ?
				     &appl_pam_interactive_converse :
				     &appl_pam_non_interactive_converse;
		memset(&args, 0, sizeof(args));
		args.user = strdup(login_username);
		args.password = non_interactive_password ?
				strdup(non_interactive_password) :
				NULL;
		appl_pam_conv.appdata_ptr = &args;
		ret = pam_start(service, login_username,
				&appl_pam_conv, &appl_pamh);
		if (ret == 0) {
			if (hostname != NULL) {
#ifdef DEBUG
				printf("Setting PAM_RHOST to \"%s\".\n", hostname);
#endif
				pam_set_item(appl_pamh, PAM_RHOST, hostname);
			}
			if (ruser != NULL) {
#ifdef DEBUG
				printf("Setting PAM_RUSER to \"%s\".\n", ruser);
#endif
				pam_set_item(appl_pamh, PAM_RUSER, ruser);
			}
			if (tty != NULL) {
#ifdef DEBUG
				printf("Setting PAM_TTY to \"%s\".\n", tty);
#endif
				pam_set_item(appl_pamh, PAM_TTY, tty);
			}
			if (!exit_handler_registered &&
			    (atexit(appl_pam_cleanup) != 0)) {
				pam_end(appl_pamh, 0);
				appl_pamh = NULL;
				ret = -1;
			} else {
				appl_pam_started = 1;
				appl_pam_starter = getpid();
				appl_pam_user = strdup(login_username);
				exit_handler_registered = 1;
			}
		}
	}
	return ret;
}
int
appl_pam_acct_mgmt(const char *service, int interactive,
		   const char *login_username,
		   const char *non_interactive_password,
		   const char *hostname,
		   const char *ruser,
		   const char *tty)
{
	int ret;
	appl_pam_pwchange_required = 0;
	ret = appl_pam_start(service, interactive, login_username,
			     non_interactive_password, hostname, ruser, tty);
	if (ret == 0) {
#ifdef DEBUG
		printf("Calling pam_acct_mgmt().\n");
#endif
		ret = pam_acct_mgmt(appl_pamh, 0);
		switch (ret) {
		case PAM_IGNORE:
			ret = 0;
			break;
		case PAM_NEW_AUTHTOK_REQD:
			appl_pam_pwchange_required = 1;
			ret = 0;
			break;
		default:
			break;
		}
	}
	return ret;
}
int
appl_pam_requires_chauthtok(void)
{
	return appl_pam_pwchange_required;
}
int
appl_pam_session_open(void)
{
	int ret = 0;
	if (appl_pam_started) {
#ifdef DEBUG
		printf("Opening PAM session.\n");
#endif
		ret = pam_open_session(appl_pamh, 0);
		if (ret == 0) {
			appl_pam_session_opened = 1;
		}
	}
	return ret;
}
int
appl_pam_setenv(void)
{
	int ret = 0;
#ifdef HAVE_PAM_GETENVLIST
#ifdef HAVE_PUTENV
	int i;
	char **list;
	if (appl_pam_started) {
		list = pam_getenvlist(appl_pamh);
		for (i = 0; ((list != NULL) && (list[i] != NULL)); i++) {
#ifdef DEBUG
			printf("Setting \"%s\" in environment.\n", list[i]);
#endif
			putenv(list[i]);
		}
	}
#endif
#endif
	return ret;
}
int
appl_pam_cred_init(void)
{
	int ret = 0;
	if (appl_pam_started) {
#ifdef DEBUG
		printf("Initializing PAM credentials.\n");
#endif
		ret = pam_setcred(appl_pamh, PAM_ESTABLISH_CRED);
		if (ret == 0) {
			appl_pam_creds_initialized = 1;
		}
	}
	return ret;
}
#endif
