/*
 * @(#)PAM.h
 *
 * This file is part of webCDwriter - Network CD/DVD Writing.
 *
 * webCDwriter is free software. See CDWserver.cpp for details.
 */

// some stuff for PAM by GSS
// mostly ripped from mod_auth_pam for Apache 2.x

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

static const char *pam_servicename = "cdwserver";

#ifndef CDWserver
class Exception {
	private: const char *msg;
	public: Exception(const char *msg): msg(msg) { }
	public: const char *toString() { return msg; }
};
#endif

typedef struct {
	char *name, *pw;
} auth_pam_userinfo;

/*
 *  * Solaris 2.6.x has a broken conversation function and needs this
 *   * as a global variable
 *    * I refused to pollute code for other platforms with this,
 *     * so all Solaris 2.6 specific stuff is if'd like the following
 *      */
#if SOLARIS2 == 260
auth_pam_userinfo *global_userinfo;
#endif

/*
 * auth_pam_talker: supply authentication information to PAM when asked
 *
 * Assumptions:
 *   A password is asked for by requesting input without echoing
 *   A username is asked for by requesting input _with_ echoing
 *
 */
static
int auth_pam_talker(int num_msg,
		        const struct pam_message ** msg,
		        struct pam_response ** resp,
		        void *appdata_ptr)
{
    unsigned short i = 0;
    auth_pam_userinfo *userinfo = (auth_pam_userinfo *) appdata_ptr;
    struct pam_response *response = 0;

#if SOLARIS2 == 260
    if (!userinfo)
	userinfo = global_userinfo;
    /* fprintf(stderr,"%s : %s", userinfo->name, userinfo->pw); */
#endif

	/* parameter sanity checking */
	if (!resp || !msg || !userinfo)
		return PAM_CONV_ERR;

	/* allocate memory to store response */
	response = (struct pam_response *) malloc(num_msg * sizeof(struct pam_response));
	if (!response)
		return PAM_CONV_ERR;

	/* copy values */
	for (i = 0; i < num_msg; i++) {
		/* initialize to safe values */
		response[i].resp_retcode = 0;
		response[i].resp = 0;

		/* select response based on requested output style */
		switch (msg[i]->msg_style) {
			case PAM_PROMPT_ECHO_ON:
				/* on memory allocation failure, auth fails */
				response[i].resp = strdup(userinfo->name);
			break;
			case PAM_PROMPT_ECHO_OFF:
				response[i].resp = strdup(userinfo->pw);
				break;
			default:
				if (response)
					free(response);
				return PAM_CONV_ERR;
		}
	}

	/* everything okay, set PAM response values */
	*resp = response;
	return PAM_SUCCESS;
}

class PAM4CDWserver {
#ifdef CDWserver
	private: static pthread_mutex_t mutex;
#endif

	public: static void check(const char *ID, const char *passwd) {
		int res = 0;
		/* PAM specific */
		auth_pam_userinfo userinfo = {NULL, NULL};
		char ID2[strlen(ID)+1];
		strcpy(ID2,ID);
		userinfo.name = ID2;
		char passwd2[strlen(passwd)+1];
		strcpy(passwd2,passwd);
		userinfo.pw = passwd2;
#ifdef CDWserver
		log.debug("PAM4CDWserver", S.e
			+ "<" + userinfo.name + "> <" + userinfo.pw + ">");
		if (true) {
			checkExternal(ID, passwd);
			return;
		}
#endif
		struct pam_conv conv_info = {&auth_pam_talker, (void *) &userinfo};
		pam_handle_t *pamh = NULL;

#if SOLARIS2 == 260
		global_userinfo = &userinfo;
#endif

		try {
#ifdef CDWserver
			pthread_mutex_lock(&mutex);
#endif
			/* initialize pam */
			if ((res = pam_start(pam_servicename,
					userinfo.name,
					&conv_info,
					&pamh)) != PAM_SUCCESS) {
#ifdef CDWserver
				log.put(3, S.e
					+ "PAM4CDWserver::check()"
					+ " Error initializing the PAM subsysten!");
#endif
				throw new Exception("Cannot initialize PAM subsystem!");
			}

			/* Authenticate to PAM, throw Exceptions on error */
			if ((res = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK)) != PAM_SUCCESS) {
#ifdef CDWserver
				log.put(3, S.e
					+ "PAM4CDWserver::check() PAM Error: "
					+ pam_strerror(pamh, res));
#endif
				pam_end(pamh, PAM_SUCCESS);
				throw new Exception("Could not authenticate with PAM");
			}
			pam_end(pamh, PAM_SUCCESS);
#ifdef CDWserver
			pthread_mutex_unlock(&mutex);
#endif
		}
		catch (Exception *exception) {
#ifdef CDWserver
			pthread_mutex_unlock(&mutex);
#endif
			throw exception;
		}
	}

#ifdef CDWserver
	public: static void checkExternal(
			const char *ID, const char *passwd) {
		Process p(config.getActiveValue("rootGatePath"));
		p.addArg("-pam");
		p.show();
		p.start();

		String str = "";
		str = str + ID + "\n" + passwd + "\n";
		const char *outBuf = str.getBytes();
		int out0 = 0, outN = str.length();
		bool authenticated = false;

		while (true) {
			fd_set outFdSet;
			FD_ZERO(&outFdSet);
			FD_SET(p.getOutputStream(), &outFdSet);
			int n = ::select(FD_SETSIZE, NULL, &outFdSet, NULL, NULL);
			if (n <= 0)
				break;

			if (FD_ISSET(p.getOutputStream(), &outFdSet)) {
				int n = write(p.getOutputStream(),
					&outBuf[out0], outN - out0);
				if (n > 0)
					out0 += n;
			}

			const char *lineStr = p.readLine(30);
			if (lineStr == NULL)
				break;
			String line = lineStr;
			log.put(3, S.e + line);
			if (line.indexOf("authenticated") >= 0)
				authenticated = true;
		}

		if (p.getResult() != 0 || !authenticated)
			throw new Exception("Could not authenticate with PAM");
	}
#endif
};

#ifdef CDWserver
pthread_mutex_t PAM4CDWserver::mutex = PTHREAD_MUTEX_INITIALIZER;
#endif
