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

#include "Log.h"

// some stuff for PAM by GSS
// mostly ripped from mod_auth_pam for Apache 2.x
//
#include <security/pam_appl.h>
static const char *pam_servicename = "cdwserver";

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 {
	private: static pthread_mutex_t mutex;

	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;
		struct pam_conv conv_info = {&auth_pam_talker, (void *) &userinfo};
		pam_handle_t *pamh = NULL;

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

		try {
			pthread_mutex_lock(&mutex);

			/* initialize pam */
			if ((res = pam_start(pam_servicename,
					userinfo.name,
					&conv_info,
					&pamh)) != PAM_SUCCESS) {
				log.put("PAM4CDWserver::check() Error initializing the PAM subsysten !");
				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) {
				pam_end(pamh, PAM_SUCCESS);
				log.putv("PAM4CDWserver::check() PAM Error: %s",pam_strerror(pamh, res));
				throw new Exception("Could not authenticate with PAM");
			}
			pam_end(pamh, PAM_SUCCESS);

			pthread_mutex_unlock(&mutex);
		}
		catch (Exception *exception) {
			pthread_mutex_unlock(&mutex);
			throw exception;
		}
	}
};

pthread_mutex_t PAM4CDWserver::mutex = PTHREAD_MUTEX_INITIALIZER;
