/***************************************************************************
* CVSID: $Id: polkit-manager.c,v 1.2 2006-04-22 23:27:14 david Exp $
*
* polkit-manager.c : Manager 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 <string.h>
#define DBUS_API_SUBJECT_TO_CHANGE
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#include <dbus/dbus-glib-lowlevel.h>
#include "polkit-marshal.h"
#include "polkit-manager.h"
#include "polkit-session.h"
#include "policy.h"
typedef struct
{
uid_t user;
char *privilege;
char *resource;
pid_t pid_restriction;
} TemporaryPrivilege;
struct PolicyKitManagerPrivate
{
DBusGConnection *connection;
DBusGProxy *bus_proxy;
GList *temporary_privileges;
GHashTable *connection_name_to_caller_info;
GHashTable *connection_name_to_session_object;
};
G_DEFINE_TYPE(PolicyKitManager, polkit_manager, G_TYPE_OBJECT)
static GObjectClass *parent_class = NULL;
typedef struct {
uid_t uid;
pid_t pid;
} CallerInfo;
static void
caller_info_delete (gpointer data)
{
CallerInfo *caller_info = (CallerInfo *) data;
g_free (caller_info);
}
static void
polkit_manager_init (PolicyKitManager *manager)
{
manager->priv = g_new0 (PolicyKitManagerPrivate, 1);
manager->priv->connection = NULL;
manager->priv->temporary_privileges = NULL;
manager->priv->connection_name_to_caller_info = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
caller_info_delete);
manager->priv->connection_name_to_session_object = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
NULL);
}
static void
polkit_manager_finalize (PolicyKitManager *manager)
{
dbus_g_connection_unref (manager->priv->connection);
g_hash_table_destroy (manager->priv->connection_name_to_caller_info);
g_free (manager->priv);
G_OBJECT_CLASS (parent_class)->finalize (G_OBJECT (manager));
}
static void
polkit_manager_class_init (PolicyKitManagerClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = (GObjectFinalizeFunc) polkit_manager_finalize;
parent_class = g_type_class_peek_parent (klass);
}
GQuark
polkit_manager_error_quark (void)
{
static GQuark ret = 0;
if (ret == 0)
ret = g_quark_from_static_string ("PolkitManagerObjectErrorQuark");
return ret;
}
#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
GType
polkit_manager_error_get_type (void)
{
static GType etype = 0;
if (etype == 0) {
static const GEnumValue values[] = {
ENUM_ENTRY (POLKIT_MANAGER_ERROR_NO_SUCH_USER, "NoSuchUser"),
ENUM_ENTRY (POLKIT_MANAGER_ERROR_NO_SUCH_PRIVILEGE, "NoSuchPrivilege"),
ENUM_ENTRY (POLKIT_MANAGER_ERROR_NOT_PRIVILEGED, "NotPrivileged"),
ENUM_ENTRY (POLKIT_MANAGER_ERROR_ERROR, "Error"),
{ 0, 0, 0 }
};
g_assert (POLKIT_MANAGER_NUM_ERRORS == G_N_ELEMENTS (values) - 1);
etype = g_enum_register_static ("PolkitManagerError", values);
}
return etype;
}
static void
bus_name_owner_changed (DBusGProxy *bus_proxy,
const char *service_name,
const char *old_service_name,
const char *new_service_name,
gpointer user_data)
{
PolicyKitManager *manager = POLKIT_MANAGER (user_data);
/* track disconnects of clients */
if (strlen (new_service_name) == 0) {
CallerInfo *caller_info;
PolicyKitSession *session;
/* evict CallerInfo from cache */
caller_info = (CallerInfo *) g_hash_table_lookup (manager->priv->connection_name_to_caller_info,
old_service_name);
if (caller_info != NULL) {
g_hash_table_remove (manager->priv->connection_name_to_caller_info, old_service_name);
}
/* session object */
session = POLKIT_SESSION (g_hash_table_lookup (manager->priv->connection_name_to_session_object,
old_service_name));
if (session != NULL) {
/* possibly revoke temporary privileges granted */
polkit_session_initiator_disconnected (session);
/* end the session */
g_object_unref (session);
g_hash_table_remove (manager->priv->connection_name_to_session_object, old_service_name);
}
}
/*g_message ("NameOwnerChanged: service_name='%s', old_service_name='%s' new_service_name='%s'",
service_name, old_service_name, new_service_name);*/
}
static gboolean
session_remover (gpointer key,
gpointer value,
gpointer user_data)
{
if (value == user_data) {
return TRUE;
}
return FALSE;
}
static void
session_finalized (gpointer data,
GObject *where_the_object_was)
{
PolicyKitManager *manager = POLKIT_MANAGER (data);
g_hash_table_foreach_remove (manager->priv->connection_name_to_session_object,
session_remover,
where_the_object_was);
}
PolicyKitManager *
polkit_manager_new (DBusGConnection *connection, DBusGProxy *bus_proxy)
{
PolicyKitManager *manager;
manager = g_object_new (POLKIT_TYPE_MANAGER, NULL);
manager->priv->connection = dbus_g_connection_ref (connection);
dbus_g_connection_register_g_object (manager->priv->connection,
"/org/freedesktop/PolicyKit/Manager",
G_OBJECT (manager));
manager->priv->bus_proxy = bus_proxy;
dbus_g_object_register_marshaller (polkit_marshal_VOID__STRING_STRING_STRING,
G_TYPE_NONE,
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
dbus_g_proxy_add_signal (bus_proxy, "NameOwnerChanged", G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INVALID);
dbus_g_proxy_connect_signal (bus_proxy, "NameOwnerChanged", G_CALLBACK (bus_name_owner_changed),
manager, NULL);
return manager;
}
static uid_t
uid_from_username (const char *user)
{
uid_t uid;
if (g_ascii_isdigit (user[0])) {
char *endp;
uid = (uid_t) g_ascii_strtoull (user, &endp, 0);
if (endp[0] != '\0') {
uid = (uid_t) -1;
}
} else {
uid = policy_util_name_to_uid (user, NULL);
}
return uid;
}
/* remote methods */
static int
safe_strcmp (const char *s1, const char *s2)
{
if (s1 == NULL || s2 == NULL)
return 0;
else
return strcmp (s1, s2);
}
gboolean
polkit_manager_get_caller_info (PolicyKitManager *manager,
const char *sender,
uid_t *calling_uid,
pid_t *calling_pid)
{
gboolean res;
CallerInfo *caller_info;
GError *error = NULL;
res = FALSE;
if (sender == NULL)
goto out;
caller_info = g_hash_table_lookup (manager->priv->connection_name_to_caller_info,
sender);
if (caller_info != NULL) {
res = TRUE;
*calling_uid = caller_info->uid;
*calling_pid = caller_info->pid;
/*g_message ("uid = %d (cached)", *calling_uid);
g_message ("pid = %d (cached)", *calling_pid);*/
goto out;
}
if (!dbus_g_proxy_call (manager->priv->bus_proxy, "GetConnectionUnixUser", &error,
G_TYPE_STRING, sender,
G_TYPE_INVALID,
G_TYPE_UINT, calling_uid,
G_TYPE_INVALID)) {
g_warning ("GetConnectionUnixUser() failed: %s", error->message);
g_error_free (error);
goto out;
}
if (!dbus_g_proxy_call (manager->priv->bus_proxy, "GetConnectionUnixProcessID", &error,
G_TYPE_STRING, sender,
G_TYPE_INVALID,
G_TYPE_UINT, calling_pid,
G_TYPE_INVALID)) {
g_warning ("GetConnectionUnixProcessID() failed: %s", error->message);
g_error_free (error);
goto out;
}
caller_info = g_new0 (CallerInfo, 1);
caller_info->uid = *calling_uid;
caller_info->pid = *calling_pid;
g_hash_table_insert (manager->priv->connection_name_to_caller_info,
g_strdup (sender),
caller_info);
res = TRUE;
/*g_message ("uid = %d", *calling_uid);
g_message ("pid = %d", *calling_pid);*/
out:
return res;
}
gboolean
polkit_manager_initiate_temporary_privilege_grant (PolicyKitManager *manager,
char *user,
char *privilege,
char *resource,
DBusGMethodInvocation *context)
{
uid_t calling_uid;
pid_t calling_pid;
uid_t uid;
PolicyKitSession *session;
char *sender;
/* TODO: need to handle limit number of session to prevent DOS.
* Or is dbus-daemon sufficient for that; I think so..
*/
if (!polkit_manager_get_caller_info (manager,
dbus_g_method_get_sender (context),
&calling_uid,
&calling_pid)) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_ERROR,
"An error occured."));
return FALSE;
}
sender = dbus_g_method_get_sender (context);
uid = uid_from_username (user);
if (uid == (uid_t) -1) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NO_SUCH_USER,
"There is no user '%s'.",
user));
return FALSE;
}
session = polkit_session_new (manager->priv->connection,
manager,
calling_uid,
calling_pid,
sender,
uid,
privilege,
strlen (resource) > 0 ? resource : NULL);
g_object_weak_ref (G_OBJECT (session),
session_finalized,
manager);
g_hash_table_insert (manager->priv->connection_name_to_session_object,
sender,
session);
//g_timeout_add (5 * 1000, destroy_session_after_timeout, session);
dbus_g_method_return (context,
g_strdup (((char *) g_object_get_data (G_OBJECT (session), "dbus_glib_object_path"))));
return TRUE;
}
gboolean
polkit_manager_is_user_privileged (PolicyKitManager *manager,
int pid,
char *user,
char *privilege,
char *resource,
DBusGMethodInvocation *context)
{
uid_t calling_uid;
pid_t calling_pid;
uid_t uid;
PolicyResult res;
gboolean is_privileged;
gboolean is_temporary;
if (!polkit_manager_get_caller_info (manager,
dbus_g_method_get_sender (context),
&calling_uid,
&calling_pid)) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_ERROR,
"An error occured."));
return FALSE;
}
is_privileged = FALSE;
uid = uid_from_username (user);
if (uid == (uid_t) -1) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NO_SUCH_USER,
"There is no user '%s'.",
user));
return FALSE;
}
/* TODO: check if given uid is privileged to ask for this */
if (FALSE) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NOT_PRIVILEGED,
"You are not authorized to know this."));
return FALSE;
}
res = policy_is_uid_allowed_for_policy (uid,
privilege,
strlen (resource) > 0 ? resource : NULL,
&is_privileged);
switch (res) {
case POLICY_RESULT_OK:
break;
case POLICY_RESULT_NO_SUCH_POLICY:
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NO_SUCH_PRIVILEGE,
"There is no such privilege '%s'.",
privilege));
return FALSE;
default: /* explicit fallthrough */
case POLICY_RESULT_ERROR:
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_ERROR,
"An error occured."));
return FALSE;
}
is_temporary = FALSE;
/* check temporary lists */
if (!is_privileged) {
GList *i;
TemporaryPrivilege *p;
for (i = manager->priv->temporary_privileges; i != NULL; i = g_list_next (i)) {
p = (TemporaryPrivilege *) i->data;
gboolean res_match;
if (strlen (resource) == 0)
res_match = (p->resource == NULL);
else
res_match = (safe_strcmp (p->resource, resource) == 0);
if ((strcmp (p->privilege, privilege) == 0) &&
res_match &&
(p->user == uid) &&
((p->pid_restriction == -1) || (p->pid_restriction == pid))) {
is_privileged = TRUE;
is_temporary = TRUE;
break;
}
}
}
dbus_g_method_return (context, is_privileged, is_temporary);
return TRUE;
}
gboolean
polkit_manager_get_allowed_resources_for_privilege (PolicyKitManager *manager,
char *user,
char *privilege,
DBusGMethodInvocation *context)
{
uid_t calling_uid;
pid_t calling_pid;
int n;
GList *i;
GList *resources;
uid_t uid;
PolicyResult res;
TemporaryPrivilege *p;
char **resource_list;
int num_non_temporary;
if (!polkit_manager_get_caller_info (manager,
dbus_g_method_get_sender (context),
&calling_uid,
&calling_pid)) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_ERROR,
"An error occured."));
return FALSE;
}
uid = uid_from_username (user);
if (uid == (uid_t) -1) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NO_SUCH_USER,
"There is no user '%s'.",
user));
return FALSE;
}
/* TODO: check if given uid is privileged to ask for this */
if (FALSE) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NOT_PRIVILEGED,
"You are not authorized to know this."));
return FALSE;
}
res = policy_get_allowed_resources_for_policy_for_uid (uid,
privilege,
&resources);
switch (res) {
case POLICY_RESULT_OK:
break;
case POLICY_RESULT_NO_SUCH_POLICY:
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NO_SUCH_PRIVILEGE,
"There is no such privilege '%s'.",
privilege));
return FALSE;
default: /* explicit fallthrough */
case POLICY_RESULT_ERROR:
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_ERROR,
"An error occured."));
return FALSE;
}
num_non_temporary = g_list_length (resources);
/* check temporary list */
for (i = manager->priv->temporary_privileges; i != NULL; i = g_list_next (i)) {
p = (TemporaryPrivilege *) i->data;
if ((strcmp (p->privilege, privilege) == 0) &&
(p->resource != NULL) &&
(p->user == uid) &&
(p->pid_restriction == -1)) {
resources = g_list_append (resources, g_strdup (p->resource));
}
}
resource_list = g_new0 (char *, g_list_length (resources) + 1);
for (i = resources, n = 0; i != NULL; i = g_list_next (i)) {
char *resource = (char *) i->data;
resource_list[n] = g_strdup (resource);
n++;
}
resource_list[n] = NULL;
g_list_foreach (resources, (GFunc) g_free, NULL);
g_list_free (resources);
dbus_g_method_return (context, resource_list, num_non_temporary);
return TRUE;
}
gboolean
polkit_manager_list_privileges (PolicyKitManager *manager,
DBusGMethodInvocation *context)
{
uid_t calling_uid;
pid_t calling_pid;
int n;
GList *i;
GList *privileges;
PolicyResult res;
char **privilege_list;
if (!polkit_manager_get_caller_info (manager,
dbus_g_method_get_sender (context),
&calling_uid,
&calling_pid)) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_ERROR,
"An error occured."));
return FALSE;
}
/* TODO: check if given uid is privileged to ask for this */
if (FALSE) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NOT_PRIVILEGED,
"You are not authorized to know this."));
return FALSE;
}
res = policy_get_policies (&privileges);
switch (res) {
case POLICY_RESULT_OK:
break;
default: /* explicit fallthrough */
case POLICY_RESULT_ERROR:
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_ERROR,
"An error occured."));
return FALSE;
}
privilege_list = g_new0 (char *, g_list_length (privileges) + 1);
for (i = privileges, n = 0; i != NULL; i = g_list_next (i)) {
char *privilege = (char *) i->data;
privilege_list[n++] = g_strdup (privilege);
}
privilege_list[n] = NULL;
g_list_foreach (privileges, (GFunc) g_free, NULL);
g_list_free (privileges);
dbus_g_method_return (context, privilege_list);
return TRUE;
}
gboolean
polkit_manager_revoke_temporary_privilege (PolicyKitManager *manager,
char *user,
char *privilege,
char *resource,
DBusGMethodInvocation *context)
{
uid_t uid;
uid_t calling_uid;
pid_t calling_pid;
gboolean result;
if (!polkit_manager_get_caller_info (manager,
dbus_g_method_get_sender (context),
&calling_uid,
&calling_pid)) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_ERROR,
"An error occured."));
return FALSE;
}
uid = uid_from_username (user);
if (uid == (uid_t) -1) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NO_SUCH_USER,
"There is no user '%s'.",
user));
return FALSE;
}
/* check if given uid is privileged to revoke privilege; only allow own user to do this */
/* TODO: also allow callers with privilege 'polkit-manage-privileges-TODO-RENAME' */
if (uid != calling_uid) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NOT_PRIVILEGED,
"You are not authorized to revoke the privilege."));
return FALSE;
}
if (resource != NULL && strlen (resource) == 0)
resource = NULL;
if (!polkit_manager_remove_temporary_privilege (manager,
uid,
privilege,
resource,
-1)) {
dbus_g_method_return_error (context,
g_error_new (POLKIT_MANAGER_ERROR,
POLKIT_MANAGER_ERROR_NO_SUCH_PRIVILEGE,
"There is no such privilege '%s'.",
privilege));
return FALSE;
}
result = TRUE;
dbus_g_method_return (context, result);
return TRUE;
}
/* local methods */
gboolean
polkit_manager_add_temporary_privilege (PolicyKitManager *manager,
uid_t user,
const char *privilege,
const char *resource,
pid_t pid_restriction)
{
GList *i;
TemporaryPrivilege *p;
for (i = manager->priv->temporary_privileges; i != NULL; i = g_list_next (i)) {
p = (TemporaryPrivilege *) i->data;
if ((strcmp (p->privilege, privilege) == 0) &&
((resource != NULL) && (safe_strcmp (p->resource, resource)) == 0) &&
(p->user == user) &&
(p->pid_restriction == pid_restriction))
return FALSE;
}
p = g_new0 (TemporaryPrivilege, 1);
p->user = user;
p->privilege = g_strdup (privilege);
p->resource = g_strdup (resource);
p->pid_restriction = pid_restriction;
manager->priv->temporary_privileges = g_list_append (manager->priv->temporary_privileges, p);
return TRUE;
}
gboolean
polkit_manager_remove_temporary_privilege (PolicyKitManager *manager,
uid_t user,
const char *privilege,
const char *resource,
pid_t pid_restriction)
{
GList *i;
TemporaryPrivilege *p;
for (i = manager->priv->temporary_privileges; i != NULL; i = g_list_next (i)) {
p = (TemporaryPrivilege *) i->data;
if ((strcmp (p->privilege, privilege) == 0) &&
((resource == NULL) ? (p->resource == NULL)
: ((p->resource != NULL) ? (strcmp (p->resource, resource) == 0) : FALSE)) &&
(p->user == user) &&
(p->pid_restriction == pid_restriction)) {
g_free (p->privilege);
g_free (p->resource);
manager->priv->temporary_privileges = g_list_remove (
manager->priv->temporary_privileges, p);
return TRUE;
}
}
return FALSE;
}
syntax highlighted by Code2HTML, v. 0.9.1