/***************************************************************************
 * CVSID: $Id: polkit-session.c,v 1.4 2006-03-29 16:15:28 david Exp $
 *
 * polkit-session.c : Session object
 *
 * Copyright (C) 2006 David Zeuthen, <david@fubar.dk>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 **************************************************************************/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>

#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <security/pam_appl.h>

#include "polkit-session.h"

enum
{
	AUTH_STATE_NOT_STARTED,
	AUTH_STATE_IN_PROGRESS,
	AUTH_STATE_HAVE_QUESTIONS,
	AUTH_STATE_NEED_ANSWERS,
	AUTH_STATE_DONE
};

struct PolicyKitSessionPrivate
{
	int session_number;
	DBusGConnection *connection;
	DBusGProxy *proxy;
	PolicyKitManager *manager;

	char *auth_as_user;
	char *auth_with_pam_service;

	uid_t calling_uid;
	pid_t calling_pid;
	char *calling_dbus_name;

	uid_t grant_to_uid;
	char *grant_privilege;
	char *grant_resource;
	pid_t grant_pid_restriction;

	gboolean have_granted_temp_privileges;

	int auth_state;
	gboolean is_authenticated;
	char *auth_denied_reason;
	GSList *auth_questions;

	GPid child_pid;
	GIOChannel *pam_channel;
	GIOChannel *pam_channel_write;
};

enum
{
	HAVE_QUESTIONS,
	AUTHENTICATION_DONE,
	LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE(PolicyKitSession, polkit_session, G_TYPE_OBJECT)

static GObjectClass *parent_class = NULL;

static void
polkit_session_init (PolicyKitSession *session)
{
	session->priv = g_new0 (PolicyKitSessionPrivate, 1);
	session->priv->session_number = 42;
	session->priv->is_authenticated = FALSE;
	session->priv->auth_state = AUTH_STATE_NOT_STARTED;
}

static void
polkit_session_finalize (PolicyKitSession *session)
{
	g_io_channel_unref (session->priv->pam_channel);
	g_io_channel_unref (session->priv->pam_channel_write);
	dbus_g_connection_unref (session->priv->connection);

	g_free (session->priv->auth_as_user);
	g_free (session->priv->auth_with_pam_service);

	g_free (session->priv->calling_dbus_name);

	g_free (session->priv->grant_privilege);
	g_free (session->priv->grant_resource);

	g_free (session->priv->auth_denied_reason);
	if (session->priv->auth_questions != NULL) {
		g_slist_foreach (session->priv->auth_questions, (GFunc) g_free, NULL);
		g_slist_free (session->priv->auth_questions);
	}
	g_free (session->priv);

	G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT (session));
}

static void
polkit_session_class_init (PolicyKitSessionClass *klass)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

	signals[HAVE_QUESTIONS] =
		g_signal_new ("have_questions",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0,
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE, 0);

	signals[AUTHENTICATION_DONE] =
		g_signal_new ("authentication_done",
			      G_OBJECT_CLASS_TYPE (klass),
			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
			      0,
			      NULL, NULL,
			      g_cclosure_marshal_VOID__VOID,
			      G_TYPE_NONE, 0);


	gobject_class->finalize = (GObjectFinalizeFunc) polkit_session_finalize;
	parent_class = g_type_class_peek_parent (klass);
}


GQuark
polkit_session_error_quark (void)
{
	static GQuark ret = 0;
	if (ret == 0)
		ret = g_quark_from_static_string ("PolkitSessionObjectErrorQuark");
	return ret;
}

#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }

GType
polkit_session_error_get_type (void)
{
	static GType etype = 0;
	
	if (etype == 0) {
		static const GEnumValue values[] = {
			ENUM_ENTRY (POLKIT_SESSION_ERROR_AUTHENTICATION_IN_PROGRESS, "AuthenticationInProgress"),
			ENUM_ENTRY (POLKIT_SESSION_ERROR_AUTHENTICATION_ALREADY_INITIATED, "AuthenticationAlreadyInitiated"),
			ENUM_ENTRY (POLKIT_SESSION_ERROR_NO_QUESTIONS, "AuthenticationNoQuestions"),
			ENUM_ENTRY (POLKIT_SESSION_ERROR_AUTHENTICATION_WAS_NOT_DENIED, "AuthenticationWasNotDenied"),
			ENUM_ENTRY (POLKIT_SESSION_ERROR_NO_RESOURCES, "NoResources"),
			ENUM_ENTRY (POLKIT_SESSION_ERROR_AUTHENTICATION_NOT_DONE, "AuthenticationNotDone"),
			ENUM_ENTRY (POLKIT_SESSION_ERROR_AUTHENTICATION_FAILED, "AuthenticationFailed"),
			ENUM_ENTRY (POLKIT_SESSION_ERROR_NOT_INITIATOR, "NotInitiator"),
			{ 0, 0, 0 }
		};
		
		g_assert (POLKIT_SESSION_NUM_ERRORS == G_N_ELEMENTS (values) - 1);
		
		etype = g_enum_register_static ("PolkitSessionError", values);
	}
	
	return etype;
}


static gboolean
polkit_session_check_caller (PolicyKitSession      *session,
			     DBusGMethodInvocation *context)
{
	char *sender;
	gboolean same_caller;

	same_caller = FALSE;

	sender = dbus_g_method_get_sender (context);
	if (sender != NULL) {
		if (strcmp (session->priv->calling_dbus_name, sender) == 0) {
			same_caller = TRUE;
		}
	}
			      
	if (!same_caller) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_NOT_INITIATOR,
							 "Only the session initiator can invoke methods on this interface. This incident will be reported."));
		/* TODO: log this attack to syslog */
	}

	return same_caller;
}

gboolean 
polkit_session_is_authenticated (PolicyKitSession      *session, 
				 DBusGMethodInvocation *context)
{
	/*g_debug ("is_authenticated");*/

	if (!polkit_session_check_caller (session, context))
		return FALSE;

	if (session->priv->auth_state != AUTH_STATE_DONE) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_AUTHENTICATION_IN_PROGRESS,
							 "This method cannot be invoked before the AuthenticationDone signal is emitted."));
		return FALSE;
	}

	dbus_g_method_return (context, session->priv->is_authenticated);
	return TRUE;
}

gboolean
polkit_session_get_auth_denied_reason (PolicyKitSession      *session, 
				       DBusGMethodInvocation *context)
{
	/*g_debug ("get_auth_denied_reason");*/

	if (!polkit_session_check_caller (session, context))
		return FALSE;

	if (session->priv->auth_state != AUTH_STATE_DONE) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_AUTHENTICATION_IN_PROGRESS,
							 "This method cannot be invoked before the AuthenticationDone signal is emitted."));
		return FALSE;
	}

	if (session->priv->is_authenticated) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_AUTHENTICATION_WAS_NOT_DENIED,
							 "The authentication was not denied."));
		return FALSE;
	}

	dbus_g_method_return (context, session->priv->auth_denied_reason);
	return TRUE;
}


/*
 * Interaction diagram
 * -------------------
 *
 *   some app                                                    polkitd
 *   ========                                                    =======
 *
 *      -> manager.InitiatePrivilegeGrant(user, privilege, resource) ->
 *                     <- Returns session object <-
 *
 *                   -> session.GetAuthDetails() ->
 *      <- Returns {<username we auth as>, <service_name used> ...} <-   # can we include what pam module? prolly not
 *
 *                     -> session.InitiateAuth() ->
 *                         <- Returns TRUE <-
 *               
 *                                                                       # app now waits for the AuthenticationDone()
 *                                                                       # or HaveQuestions() signals
 *                                .....
 *               
 *                      <- signal: HaveQuestions() <-
 *
 *                       -> session.GetQuestions() ->
 *                 <- Returns {question_1, question_2, ...} <-
 *
 *           -> session.ProvideAnswers({answer_1, answer_2, ...}) ->
 *                         <- Returns TRUE <-
 *
 *                                .....
 *
 *                 <- signal: AuthenticationDone() <-
 *               
 *                                .....
 *
 *                    -> session.IsAuthenticated() ->
 *                      <- Returns TRUE or FALSE <-
 *                                                                       
 *                                .....
 *
 *                    -> session.GetAuthFailureReason() ->               # Only if IsAuthenticated() returns FALSE
 *                     <- Returns <reason as string> <-
 *
 *                                .....
 *
 * Assume now IsAuthenticated() returned TRUE. There are a few different
 * scenarios.
 *
 *
 * SCENARIO 1: App needs the privilege only temporarily; e.g. not persistent 
 *             across reboots. The app may even restrict users of the privilege
 *             to his own process id. The app may ask for the privilege to
 *             not be revoked when it ends the session - if the app should
 *             disconnect from the bus before session.Close() the privilege
 *             is revoked though.
 *
 *             Example: gnome-mount needs privs to do work, restricts the
 *                      privs to it's own PID and asks for revocation when
 *                      it's done with it's work.
 *
 *             Example: g-d-m temporarily gives the privilege 'local-console-user'
 *                      when a new desktop session starts. It manually revokes
 *                      this when the session ends.
 *
 *      -> session.GrantPrivilegeTemporary(bool restrictToCallersPID) -> # add uid, pid of client to the 
 *                          <- Returns TRUE <-                           # temp_allow_list
 *
 *                                .....
 *
 *        (the app is now doing something useful with the privilege obtained)
 *
 *                                .....
 *
 *                 -> session.Close(bool doNotRevokePrivilege) ->                           
 *                         <- Returns TRUE <-                            # Remove uid, pid of client from the
 *                                                                       # temp_allow_list IFF revokePrivile is true
 */

typedef struct {
	int fd;
	int fdread;
} ConversationData;


/* TODO: is this a secure way of clearing memory? */
static void *
safe_memset (void *buf, int c, size_t len)
{ 
	return memset (buf, c, len);
}


static int
my_conversation (int n,
		 const struct pam_message **msg,
		 struct pam_response **resp,
		 void *data)
{
	GString *str;
	ConversationData *cd = (ConversationData *) data;
	struct pam_response *aresp;
	int i;
	int j;
	int num_real_questions = 0;
	int strl;
	char *cstr;
	int num_bytes_read;
	char *p;
	char readbuf[1024];
	char **answers = NULL;
	int num_answers;

	/*g_debug ("in my_conv");*/

	if (n <= 0 || n > PAM_MAX_NUM_MSG) {
		return PAM_CONV_ERR;
	}

	if ((aresp = calloc (n, sizeof (struct pam_response))) == NULL) {
		return PAM_BUF_ERR;
	}

	str = g_string_new ("Q");

	for (i = 0; i < n; ++i) {
		g_string_append_c (str, '\0');
		switch (msg[i]->msg_style) {
		case PAM_PROMPT_ECHO_OFF:
			g_string_append (str, "PamPromptEchoOff");
			num_real_questions++;
			break;
		case PAM_PROMPT_ECHO_ON:
			g_string_append (str, "PamPromptEchoOn");
			num_real_questions++;
			break;
		case PAM_ERROR_MSG:
			g_string_append (str, "PamErrorMsg");
			break;
		case PAM_TEXT_INFO:
			g_string_append (str, "PamTextInfo");
			break;

		default:
			/* TODO */
			break;
		}
		g_string_append_c (str, '\0');
		g_string_append_printf (str, "%s", msg[i]->msg);
	}

	strl = str->len;
	cstr = g_string_free (str, FALSE);
	/*g_debug ("strlen = %d", strl);*/
	write (cd->fd, (void *) cstr, (size_t) strl);
	g_free (cstr);

	answers = g_new0 (char *, num_real_questions + 1);

	/* now wait for parent to write answers */
	num_bytes_read = read (cd->fdread, readbuf, sizeof (readbuf));
	/*g_debug ("actually read = %d", num_bytes_read);*/
	p = readbuf;
	num_answers = 0;
	do {
		if (num_answers > num_real_questions) {
			g_warning ("num_answers > num_real_questions");
			goto error;
		}

		answers [num_answers++] = g_strdup (p);
		/*g_debug ("answer -> '%s'", p);*/
		
		p = p + strlen(p) + 1;
		
	} while (p < readbuf + num_bytes_read);
	answers[num_answers] = NULL;

	if (num_answers != num_real_questions) {
		g_warning ("num_answers != num_real_questions");
		goto error;
	}

	/*g_debug ("giving answers back to PAM");*/

	j = 0;
	for (i = 0; i < n; ++i) {
		aresp[i].resp_retcode = 0;
		aresp[i].resp = NULL;

		switch (msg[i]->msg_style) {
		case PAM_PROMPT_ECHO_OFF: /* explicit fallthrough */
		case PAM_PROMPT_ECHO_ON:
			aresp[i].resp = strdup (answers[j++]);
			break;

		default:
			/* explicitly left blank */
			break;
		}
	}

	/* zero out the secrets */
	safe_memset (readbuf, 0, sizeof (readbuf));
	if (answers != NULL) {
		for (i = 0; answers[i] != NULL; i++) {
			safe_memset (answers[i], 0, strlen (answers[i]));
		}
		g_strfreev (answers);
	}

	*resp = aresp;
	return PAM_SUCCESS;

error:
	/* zero out the secrets */
	safe_memset (readbuf, 0, sizeof (readbuf));
	if (answers != NULL) {
		for (i = 0; answers[i] != NULL; i++) {
			safe_memset (answers[i], 0, strlen (answers[i]));
		}
		g_strfreev (answers);
	}

	/* prepare reply to PAM */
        for (i = 0; i < n; ++i) {
                if (aresp[i].resp != NULL) {
                        safe_memset (aresp[i].resp, 0, strlen(aresp[i].resp));
                        free (aresp[i].resp);
                }
        }
        safe_memset (aresp, 0, n * sizeof (struct pam_response));
	*resp = NULL;

	return PAM_CONV_ERR;
}

static void
write_back_to_parent (int fd, char code, const char *message)
{
	GString *str;
	gsize strl;
	char *cstr;
	
	str = g_string_new ("");
	g_string_append_c (str, code);
	g_string_append_c (str, '\0');

	if (message != NULL) {
		g_string_append (str, message);
		g_string_append_c (str, '\0');
	}

	strl = str->len;
	cstr = g_string_free (str, FALSE);
	write (fd, cstr, strl);
	g_free (cstr);
}

static void
do_pam_auth (int fd, int fdread, const PolicyKitSessionPrivate *priv)
{
	int rc;
	struct pam_conv pam_conversation;
	pam_handle_t *pam_h;
	ConversationData d;
	char *authed_user;

	/*g_debug ("in %s", __FUNCTION__);*/

	pam_conversation.conv        = my_conversation;
	pam_conversation.appdata_ptr = (void *) &d;
	d.fd = fd;
	d.fdread = fdread;

	rc = pam_start (priv->auth_with_pam_service,
			priv->auth_as_user, 
			&pam_conversation,
			&pam_h);
	if (rc != PAM_SUCCESS) {
		g_warning ("pam_start failed: %s", pam_strerror (pam_h, rc));
		write_back_to_parent (fd, 'F', pam_strerror (pam_h, rc));
		goto out;
	}


	/*g_debug ("invoking pam_authenticate");*/

	/* is user really user? */
	rc = pam_authenticate (pam_h, 0);
	if (rc != PAM_SUCCESS) {
		g_warning ("pam_authenticated failed: %s", pam_strerror (pam_h, rc));
		write_back_to_parent (fd, 'N', pam_strerror (pam_h, rc));
		goto out;
	}

	/*g_debug ("invoking pam_acct_mgmt");*/

	/* permitted access? */
	rc = pam_acct_mgmt (pam_h, 0);
	if (rc != PAM_SUCCESS) {
		g_warning ("pam_acct_mgmt failed: %s", pam_strerror (pam_h, rc));
		write_back_to_parent (fd, 'N', pam_strerror (pam_h, rc));
		goto out;
	}

	/*g_debug ("checking we authed the right user");*/

	rc = pam_get_item (pam_h, PAM_USER, (const void **) &authed_user);
	if (rc != PAM_SUCCESS) {
		g_warning ("pam_get_item failed: %s", pam_strerror (pam_h, rc));
		write_back_to_parent (fd, 'N', pam_strerror (pam_h, rc));
		goto out;
	}

	/*g_debug ("Authed user '%s'", authed_user);*/

	if (strcmp (authed_user, priv->auth_as_user) != 0) {
		char *err;
		err = g_strdup_printf ("Tried to auth user '%s' but we got auth for user '%s' instead",
				       priv->auth_as_user, authed_user);
		g_warning (err);
		write_back_to_parent (fd, 'N', err);
		g_free (err);
		goto out;
	}

	/*g_debug ("user authenticated, exiting");*/
	write_back_to_parent (fd, 'S', NULL);

out:
	exit (0);
}

static gboolean
data_from_pam (GIOChannel *source,
	       GIOCondition condition,
	       gpointer data)
{
	PolicyKitSession *session = POLKIT_SESSION (data);

	if (condition & G_IO_IN) {
		char buf[1024];
		gsize num_bytes_read;

		/*g_debug ("in %s - data", __FUNCTION__);*/

		g_io_channel_read (source,
				   buf,
				   sizeof (buf) - 1,
				   &num_bytes_read);
		/*g_debug ("read %d bytes, first one is '%c' = %d", num_bytes_read, buf[0], buf[0]);*/
		buf[num_bytes_read] = '\0';

		switch (buf[0]) {
		case 'F':
			g_warning ("PAM failed: '%s'", buf + 2);
			session->priv->auth_denied_reason = g_strdup (buf + 2);
			session->priv->auth_state = AUTH_STATE_DONE;
			g_signal_emit (session, signals[AUTHENTICATION_DONE], 0);
			break;

		case 'N':
			g_warning ("Not authenticated: '%s'", buf + 2);
			session->priv->auth_denied_reason = g_strdup (buf + 2);
			session->priv->auth_state = AUTH_STATE_DONE;
			g_signal_emit (session, signals[AUTHENTICATION_DONE], 0);
			break;

		case 'S':
			/*g_debug ("Success, user authenticated");*/
			session->priv->is_authenticated = TRUE;
			session->priv->auth_state = AUTH_STATE_DONE;
			g_signal_emit (session, signals[AUTHENTICATION_DONE], 0);
			break;

		case 'Q':
			g_slist_foreach (session->priv->auth_questions, (GFunc) g_free, NULL);
			g_slist_free (session->priv->auth_questions);
			session->priv->auth_questions = NULL;

			char *p = buf + 2;
			do {
				session->priv->auth_questions = g_slist_append (session->priv->auth_questions,
										g_strdup (p));
				/*g_debug ("p -> '%s'", p);*/
				p = p + strlen(p) + 1;

			} while (p < buf + num_bytes_read);

			/*g_debug ("Put %d questions on list", g_slist_length (session->priv->auth_questions));*/

			if ((g_slist_length (session->priv->auth_questions) & 1) != 0) {
				g_warning ("Uneven number of question items from PAM; aborting conversation");
				kill (session->priv->child_pid, SIGTERM);
				session->priv->auth_state = AUTH_STATE_DONE;
				session->priv->auth_denied_reason = g_strdup ("Unexpected internal PAM error");
				g_signal_emit (session, signals[AUTHENTICATION_DONE], 0);
			} else {
				session->priv->auth_state = AUTH_STATE_HAVE_QUESTIONS;
				g_signal_emit (session, signals[HAVE_QUESTIONS], 0);
			}
			break;

		default:
			/* left intentionally blank */
			break;
		}

	}


	if (condition & G_IO_HUP) {
		/*g_debug ("in %s - hangup", __FUNCTION__);*/
		if (session->priv->child_pid != 0) {
			int status;
			/*g_debug ("  reaping child with pid %d", session->priv->child_pid);*/
			session->priv->child_pid = 0;
			waitpid (session->priv->child_pid, &status, 0);
		}

		/* release the ref we made when creating the child */
		g_object_unref (session);

		/* remove the source */
		return FALSE;
	}

	return TRUE;
}

gboolean
polkit_session_get_auth_details (PolicyKitSession      *session, 
				 DBusGMethodInvocation *context)
{
	if (!polkit_session_check_caller (session, context))
		return FALSE;

	if (session->priv->auth_state != AUTH_STATE_NOT_STARTED) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_AUTHENTICATION_ALREADY_INITIATED,
							 "This method cannot be invoked after InitiateAuth() is invoked."));
		return FALSE;
	}
	
	dbus_g_method_return (context, 
			      g_strdup (session->priv->auth_as_user),
			      g_strdup (session->priv->auth_with_pam_service));
	return TRUE;
}

gboolean 
polkit_session_initiate_auth (PolicyKitSession      *session, 
			      DBusGMethodInvocation *context)
{
	int fds[2];
	int fdsb[2];
	pid_t pid;

	if (!polkit_session_check_caller (session, context))
		return FALSE;

	if (session->priv->auth_state != AUTH_STATE_NOT_STARTED) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_AUTHENTICATION_ALREADY_INITIATED,
							 "Authentication already initiated."));
		return FALSE;
	}

	/*g_debug ("in %s", __FUNCTION__);*/

	/* pipe for parent reading from child */
	if (pipe(fds) != 0) {
		g_warning ("pipe() failed: %s", strerror (errno));
		goto fail;
	}

	/* pipe for parent writing to child */
	if (pipe(fdsb) != 0) {
		g_warning ("pipe() failed: %s", strerror (errno));
		goto fail;
	}
	
	switch (pid = fork()) {
	case -1:
		g_warning ("fork() failed: %s", strerror (errno));
		goto fail;
		
	case 0:
		/* child; close unused ends */
		close (fds[0]);
		close (fdsb[1]);

		do_pam_auth (fds[1], fdsb[0], session->priv);
		break;
		
	default:
		session->priv->auth_state = AUTH_STATE_IN_PROGRESS;

		/* parent; close unused ends */
		close (fds[1]);
		close (fdsb[0]);

		session->priv->child_pid = (GPid) pid;
		session->priv->pam_channel_write = g_io_channel_unix_new (fdsb[1]);
		session->priv->pam_channel = g_io_channel_unix_new (fds[0]);

		/* ref because we need the object in data_from_pam */
		g_object_ref (session);

		g_io_add_watch (session->priv->pam_channel, 
				G_IO_IN | G_IO_ERR | G_IO_HUP,
				data_from_pam,
				session);

		break;
	}

	dbus_g_method_return (context);
	return TRUE;

fail:
	dbus_g_method_return_error (context, 
				    g_error_new (POLKIT_SESSION_ERROR,
						 POLKIT_SESSION_ERROR_NO_RESOURCES,
						 "InitiateAuth() failed due to lack of resources. Try again later."));

	return FALSE;
}

gboolean
polkit_session_get_questions    (PolicyKitSession      *session, 
				 DBusGMethodInvocation *context)
{
	int n;
	GSList *i;
	char **questions;

	if (!polkit_session_check_caller (session, context))
		return FALSE;

	/*g_debug ("in %s", __FUNCTION__);*/

	if (session->priv->auth_state != AUTH_STATE_HAVE_QUESTIONS) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_NO_QUESTIONS,
							 "There are currently no questions available."));
		return FALSE;
	}

	session->priv->auth_state = AUTH_STATE_NEED_ANSWERS;

	questions = g_new0 (char *, g_slist_length (session->priv->auth_questions) + 1);
	for (i = session->priv->auth_questions, n = 0; i != NULL; i = g_slist_next (i)) {
		char *question = (char *) i->data;
		questions[n++] = g_strdup (question);
	}
	questions[n] = NULL;

	dbus_g_method_return (context, questions);
	return TRUE;
}

gboolean
polkit_session_provide_answers  (PolicyKitSession      *session, 
				 char                 **answers, 
				 DBusGMethodInvocation *context)
{
	int i;
	GString *str;
	char *cstr;
	gsize strl;
	gsize num_bytes_written;

	if (!polkit_session_check_caller (session, context))
		return FALSE;

	/*g_debug ("in %s", __FUNCTION__);*/

	if (session->priv->auth_state != AUTH_STATE_NEED_ANSWERS) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_NO_QUESTIONS,
							 "There are currently no questions pending answers."));
		return FALSE;
	}

	session->priv->auth_state = AUTH_STATE_IN_PROGRESS;

	str = g_string_new ("");
	for (i = 0; answers[i] != NULL; i++) {
		/*g_debug ("answer %d: %s", i, answers[i]);*/
		g_string_append (str, answers[i]);
		g_string_append_c (str, '\0');
	}
	strl = str->len;
	cstr = g_string_free (str, FALSE);
	g_io_channel_write (session->priv->pam_channel_write, cstr, strl, &num_bytes_written);
	g_free (cstr);

	/*g_debug ("wanted to write %d bytes, wrote %d bytes", strl, num_bytes_written);*/

	dbus_g_method_return (context);
	return TRUE;
}

gboolean
polkit_session_close (PolicyKitSession      *session, 
		      gboolean               do_not_revoke_privilege,
		      DBusGMethodInvocation *context)
{
	/*g_debug ("in %s", __FUNCTION__);*/

	if (!polkit_session_check_caller (session, context))
		return FALSE;

	/* if we have a child... kill it  */
	if (session->priv->child_pid != 0)
		kill (session->priv->child_pid, SIGTERM);

	if (!do_not_revoke_privilege && session->priv->have_granted_temp_privileges) {

		if (!polkit_manager_remove_temporary_privilege (session->priv->manager,
								session->priv->grant_to_uid,
								session->priv->grant_privilege,
								session->priv->grant_resource,
								session->priv->grant_pid_restriction)) {
			g_warning ("Could not remove tmp priv '%s' to uid %d for resource '%s' on pid %d",
				   session->priv->grant_privilege,
				   session->priv->grant_to_uid,
				   session->priv->grant_resource,
				   session->priv->grant_pid_restriction);
		}
	}

	g_object_unref (session);

	dbus_g_method_return (context);
	return TRUE;
}

gboolean 
polkit_session_grant_privilege_temporarily (PolicyKitSession      *session, 
					    gboolean               restrict_to_callers_pid,
					    DBusGMethodInvocation *context)
{
	if (!polkit_session_check_caller (session, context))
		return FALSE;

	if (session->priv->auth_state != AUTH_STATE_DONE) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_AUTHENTICATION_NOT_DONE,
							 "Authentication is not done."));
		return FALSE;
	}

	if (!session->priv->is_authenticated) {
		dbus_g_method_return_error (context, 
					    g_error_new (POLKIT_SESSION_ERROR,
							 POLKIT_SESSION_ERROR_AUTHENTICATION_FAILED,
							 "User failed authentication."));
		return FALSE;
	}

	session->priv->grant_pid_restriction = restrict_to_callers_pid ? session->priv->calling_pid : (pid_t) -1;
	if (!polkit_manager_add_temporary_privilege (session->priv->manager,
						     session->priv->grant_to_uid,
						     session->priv->grant_privilege,
						     session->priv->grant_resource,
						     session->priv->grant_pid_restriction)) {
		g_warning ("Could not add tmp priv '%s' to uid %d for resource '%s' on pid %d",
			   session->priv->grant_privilege,
			   session->priv->grant_to_uid,
			   session->priv->grant_resource,
			   session->priv->grant_pid_restriction);
	}

	session->priv->have_granted_temp_privileges = TRUE;

	dbus_g_method_return (context);
	return TRUE;
}

PolicyKitSession *
polkit_session_new (DBusGConnection    *connection, 
		    PolicyKitManager   *manager,
		    uid_t               calling_uid,
		    pid_t               calling_pid,
		    const char         *calling_dbus_name,
		    uid_t               uid,
		    const char         *privilege,
		    const char         *resource)
{
	char *objpath;
	PolicyKitSession *session;
	static int session_number_base = 0;

	session = POLKIT_SESSION (g_object_new (POLKIT_TYPE_SESSION, NULL));
	session->priv->connection = dbus_g_connection_ref (connection);
	session->priv->session_number = session_number_base++;
	session->priv->manager = manager;
	objpath = g_strdup_printf ("/org/freedesktop/PolicyKit/sessions/%d", session->priv->session_number);
	dbus_g_connection_register_g_object (connection, objpath, G_OBJECT (session));
	g_free (objpath);

	session->priv->calling_uid = calling_uid;
	session->priv->calling_pid = calling_pid;
	session->priv->calling_dbus_name = g_strdup (calling_dbus_name);

	session->priv->grant_to_uid = uid;
	session->priv->grant_privilege = g_strdup (privilege);
	session->priv->grant_resource = g_strdup (resource);

	/* TODO: look up auth_as_user, auth_with_pam_service from privilege configuration files */
	session->priv->auth_as_user = g_strdup ("root");
	session->priv->auth_with_pam_service = g_strdup ("policy-kit");

	return session;
}


void
polkit_session_initiator_disconnected (PolicyKitSession *session)
{
	/*g_debug ("initiator disconnected");*/

	/* if we have a child... kill it  */
	if (session->priv->child_pid != 0)
		kill (session->priv->child_pid, SIGTERM);

	if (session->priv->have_granted_temp_privileges) {
		if (!polkit_manager_remove_temporary_privilege (session->priv->manager,
								session->priv->grant_to_uid,
								session->priv->grant_privilege,
								session->priv->grant_resource,
								session->priv->grant_pid_restriction)) {
			g_warning ("Could not remove tmp priv '%s' to uid %d for resource '%s' on pid %d",
				   session->priv->grant_privilege,
				   session->priv->grant_to_uid,
				   session->priv->grant_resource,
				   session->priv->grant_pid_restriction);
		}
	}
}


syntax highlighted by Code2HTML, v. 0.9.1