Commit a26607ab authored by Havoc Pennington's avatar Havoc Pennington

2003-03-23 Havoc Pennington <hp@pobox.com>

	* bus/policy.c, bus/bus.c, bus/connection.c: implement allow/deny
	policies code

	* dbus/dbus-hash.h: add ULONG hash keys

	* dbus/dbus-sysdeps.c (_dbus_get_groups): new
	(_dbus_get_group_id): new function
parent b6ffea17
2003-03-23 Havoc Pennington <hp@pobox.com>
* bus/policy.c, bus/bus.c, bus/connection.c: implement allow/deny
policies code
* dbus/dbus-hash.h: add ULONG hash keys
* dbus/dbus-sysdeps.c (_dbus_get_groups): new
(_dbus_get_group_id): new function
2003-03-20 Havoc Pennington <hp@redhat.com>
* dbus/dbus-connection.c (dbus_connection_set_unix_user_function):
......
......@@ -41,7 +41,7 @@ struct BusContext
BusActivation *activation;
BusRegistry *registry;
DBusList *default_rules; /**< Default policy rules */
DBusList *override_rules; /**< Override policy rules */
DBusList *mandatory_rules; /**< Mandatory policy rules */
DBusHashTable *rules_by_uid; /**< per-UID policy rules */
DBusHashTable *rules_by_gid; /**< per-GID policy rules */
};
......@@ -117,13 +117,26 @@ new_connection_callback (DBusServer *server,
}
static void
free_rule_func (void *data)
free_rule_func (void *data,
void *user_data)
{
BusPolicyRule *rule = data;
bus_policy_rule_unref (rule);
}
static void
free_rule_list_func (void *data)
{
DBusList **list = data;
_dbus_list_foreach (list, free_rule_func, NULL);
_dbus_list_clear (list);
dbus_free (list);
}
BusContext*
bus_context_new (const char *address,
const char **service_dirs,
......@@ -179,18 +192,18 @@ bus_context_new (const char *address,
goto failed;
}
context->rules_by_uid = _dbus_hash_table_new (DBUS_HASH_INT,
context->rules_by_uid = _dbus_hash_table_new (DBUS_HASH_ULONG,
NULL,
free_rule_func);
free_rule_list_func);
if (context->rules_by_uid == NULL)
{
BUS_SET_OOM (error);
goto failed;
}
context->rules_by_gid = _dbus_hash_table_new (DBUS_HASH_INT,
context->rules_by_gid = _dbus_hash_table_new (DBUS_HASH_ULONG,
NULL,
free_rule_func);
free_rule_list_func);
if (context->rules_by_gid == NULL)
{
BUS_SET_OOM (error);
......@@ -328,3 +341,183 @@ bus_context_get_activation (BusContext *context)
{
return context->activation;
}
static dbus_bool_t
list_allows_user (dbus_bool_t def,
DBusList **list,
unsigned long uid,
const unsigned long *group_ids,
int n_group_ids)
{
DBusList *link;
dbus_bool_t allowed;
allowed = def;
link = _dbus_list_get_first_link (list);
while (link != NULL)
{
BusPolicyRule *rule = link->data;
link = _dbus_list_get_next_link (list, link);
if (rule->type == BUS_POLICY_RULE_USER)
{
if (rule->d.user.uid != uid)
continue;
}
else if (rule->type == BUS_POLICY_RULE_GROUP)
{
int i;
i = 0;
while (i < n_group_ids)
{
if (rule->d.group.gid == group_ids[i])
break;
++i;
}
if (i == n_group_ids)
continue;
}
else
continue;
allowed = rule->allow;
}
return allowed;
}
dbus_bool_t
bus_context_allow_user (BusContext *context,
unsigned long uid)
{
dbus_bool_t allowed;
unsigned long *group_ids;
int n_group_ids;
/* On OOM or error we always reject the user */
if (!_dbus_get_groups (uid, &group_ids, &n_group_ids))
{
_dbus_verbose ("Did not get any groups for UID %lu\n",
uid);
return FALSE;
}
allowed = FALSE;
allowed = list_allows_user (allowed,
&context->default_rules,
uid,
group_ids, n_group_ids);
allowed = list_allows_user (allowed,
&context->mandatory_rules,
uid,
group_ids, n_group_ids);
dbus_free (group_ids);
return allowed;
}
static dbus_bool_t
add_list_to_policy (DBusList **list,
BusPolicy *policy)
{
DBusList *link;
link = _dbus_list_get_first_link (list);
while (link != NULL)
{
BusPolicyRule *rule = link->data;
link = _dbus_list_get_next_link (list, link);
switch (rule->type)
{
case BUS_POLICY_RULE_USER:
case BUS_POLICY_RULE_GROUP:
/* These aren't per-connection policies */
break;
case BUS_POLICY_RULE_OWN:
case BUS_POLICY_RULE_SEND:
case BUS_POLICY_RULE_RECEIVE:
/* These are per-connection */
if (!bus_policy_append_rule (policy, rule))
return FALSE;
break;
}
}
return TRUE;
}
BusPolicy*
bus_context_create_connection_policy (BusContext *context,
DBusConnection *connection)
{
BusPolicy *policy;
unsigned long uid;
DBusList **list;
_dbus_assert (dbus_connection_get_is_authenticated (connection));
policy = bus_policy_new ();
if (policy == NULL)
return NULL;
if (!add_list_to_policy (&context->default_rules,
policy))
goto failed;
/* we avoid the overhead of looking up user's groups
* if we don't have any group rules anyway
*/
if (_dbus_hash_table_get_n_entries (context->rules_by_gid) > 0)
{
const unsigned long *groups;
int n_groups;
int i;
if (!bus_connection_get_groups (connection, &groups, &n_groups))
goto failed;
i = 0;
while (i < n_groups)
{
list = _dbus_hash_table_lookup_ulong (context->rules_by_gid,
groups[i]);
if (list != NULL)
{
if (!add_list_to_policy (list, policy))
goto failed;
}
++i;
}
}
if (!dbus_connection_get_unix_user (connection, &uid))
goto failed;
list = _dbus_hash_table_lookup_ulong (context->rules_by_uid,
uid);
if (!add_list_to_policy (list, policy))
goto failed;
if (!add_list_to_policy (&context->mandatory_rules,
policy))
goto failed;
bus_policy_optimize (policy);
return policy;
failed:
bus_policy_unref (policy);
return NULL;
}
......@@ -32,19 +32,25 @@
typedef struct BusActivation BusActivation;
typedef struct BusConnections BusConnections;
typedef struct BusContext BusContext;
typedef struct BusPolicy BusPolicy;
typedef struct BusPolicyRule BusPolicyRule;
typedef struct BusRegistry BusRegistry;
typedef struct BusService BusService;
typedef struct BusTransaction BusTransaction;
BusContext* bus_context_new (const char *address,
const char **service_dirs,
DBusError *error);
void bus_context_shutdown (BusContext *context);
void bus_context_ref (BusContext *context);
void bus_context_unref (BusContext *context);
BusRegistry* bus_context_get_registry (BusContext *context);
BusConnections* bus_context_get_connections (BusContext *context);
BusActivation* bus_context_get_activation (BusContext *context);
BusContext* bus_context_new (const char *address,
const char **service_dirs,
DBusError *error);
void bus_context_shutdown (BusContext *context);
void bus_context_ref (BusContext *context);
void bus_context_unref (BusContext *context);
BusRegistry* bus_context_get_registry (BusContext *context);
BusConnections* bus_context_get_connections (BusContext *context);
BusActivation* bus_context_get_activation (BusContext *context);
dbus_bool_t bus_context_allow_user (BusContext *context,
unsigned long uid);
BusPolicy* bus_context_create_connection_policy (BusContext *context,
DBusConnection *connection);
#endif /* BUS_BUS_H */
......@@ -23,6 +23,7 @@
#include "connection.h"
#include "dispatch.h"
#include "loop.h"
#include "policy.h"
#include "services.h"
#include "utils.h"
#include <dbus/dbus-list.h>
......@@ -48,6 +49,9 @@ typedef struct
DBusList *transaction_messages; /**< Stuff we need to send as part of a transaction */
DBusMessage *oom_message;
DBusPreallocatedSend *oom_preallocated;
unsigned long *group_ids;
int n_group_ids;
BusPolicy *policy;
} BusConnectionData;
#define BUS_CONNECTION_DATA(connection) (dbus_connection_get_data ((connection), connection_data_slot))
......@@ -231,6 +235,20 @@ remove_connection_timeout (DBusTimeout *timeout,
bus_loop_remove_timeout (timeout, connection_timeout_callback, connection);
}
static dbus_bool_t
allow_user_function (DBusConnection *connection,
unsigned long uid,
void *data)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
return bus_context_allow_user (d->connections->context, uid);
}
static void
free_connection_data (void *data)
{
......@@ -246,6 +264,11 @@ free_connection_data (void *data)
if (d->oom_message)
dbus_message_unref (d->oom_message);
if (d->policy)
bus_policy_unref (d->policy);
dbus_free (d->group_ids);
dbus_free (d->name);
......@@ -333,6 +356,9 @@ bus_connections_setup_connection (BusConnections *connections,
}
retval = FALSE;
d->n_group_ids = 0;
d->group_ids = NULL;
if (!dbus_connection_set_watch_functions (connection,
(DBusAddWatchFunction) add_connection_watch,
......@@ -387,6 +413,103 @@ bus_connections_setup_connection (BusConnections *connections,
return retval;
}
dbus_bool_t
bus_connection_get_groups (DBusConnection *connection,
const unsigned long **groups,
int *n_groups)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
*groups = NULL;
*n_groups = 0;
/* we do a lazy lookup on groups a user is in for two reasons:
* 1) we can't do it on connection setup since the user
* hasn't authenticated and 2) it might be expensive
* and we don't need to do it if there are no group-based
* rules in the config file
*/
if (d->n_group_ids == 0)
{
unsigned long uid;
if (dbus_connection_get_unix_user (connection, &uid))
{
if (!_dbus_get_groups (uid, &d->group_ids, &d->n_group_ids))
{
_dbus_verbose ("Did not get any groups for UID %lu\n",
uid);
return FALSE;
}
}
}
*groups = d->group_ids;
*n_groups = d->n_group_ids;
return TRUE;
}
dbus_bool_t
bus_connection_is_in_group (DBusConnection *connection,
unsigned long gid)
{
int i;
const unsigned long *group_ids;
int n_group_ids;
if (!bus_connection_get_groups (connection, &group_ids, &n_group_ids))
return FALSE;
i = 0;
while (i < n_group_ids)
{
if (group_ids[i] == gid)
return TRUE;
++i;
}
return FALSE;
}
BusPolicy*
bus_connection_get_policy (DBusConnection *connection)
{
BusConnectionData *d;
d = BUS_CONNECTION_DATA (connection);
_dbus_assert (d != NULL);
if (!dbus_connection_get_is_authenticated (connection))
{
_dbus_verbose ("Tried to get policy for unauthenticated connection!\n");
return NULL;
}
/* We do lazy creation of the policy because
* it can only be done post-authentication.
*/
if (d->policy == NULL)
{
d->policy =
bus_context_create_connection_policy (d->connections->context,
connection);
/* we may have a NULL policy on OOM or error getting list of
* groups for a user. In the latter case we don't handle it so
* well currently, just keep pretending we're out of memory,
* which is kind of bizarre.
*/
}
return d->policy;
}
/**
* Calls function on each connection; if the function returns
......
......@@ -66,6 +66,13 @@ const char *bus_connection_get_name (DBusConnection *connection);
/* called by dispatch.c when the connection is dropped */
void bus_connection_disconnected (DBusConnection *connection);
dbus_bool_t bus_connection_is_in_group (DBusConnection *connection,
unsigned long gid);
dbus_bool_t bus_connection_get_groups (DBusConnection *connection,
const unsigned long **groups,
int *n_groups);
BusPolicy* bus_connection_get_policy (DBusConnection *connection);
/* transaction API so we can send or not send a block of messages as a whole */
BusTransaction* bus_transaction_new (BusContext *context);
BusContext* bus_transaction_get_context (BusTransaction *transaction);
......
......@@ -156,7 +156,7 @@ remove_rules_by_type_up_to (BusPolicy *policy,
}
}
static void
void
bus_policy_optimize (BusPolicy *policy)
{
DBusList *link;
......@@ -175,6 +175,9 @@ bus_policy_optimize (BusPolicy *policy)
* file.
*/
_dbus_verbose ("Optimizing policy with %d rules\n",
_dbus_list_get_length (&policy->rules));
link = _dbus_list_get_first (&policy->rules);
while (link != NULL)
{
......@@ -208,6 +211,21 @@ bus_policy_optimize (BusPolicy *policy)
link = next;
}
_dbus_verbose ("After optimization, policy has %d rules\n",
_dbus_list_get_length (&policy->rules));
}
dbus_bool_t
bus_policy_append_rule (BusPolicy *policy,
BusPolicyRule *rule)
{
if (!_dbus_list_append (policy->rules, rule))
return FALSE;
bus_policy_rule_ref (rule);
return TRUE;
}
dbus_bool_t
......
......@@ -28,14 +28,13 @@
#include <dbus/dbus-string.h>
#include "bus.h"
typedef struct BusPolicy BusPolicy;
typedef struct BusPolicyRule BusPolicyRule;
typedef enum
{
BUS_POLICY_RULE_SEND,
BUS_POLICY_RULE_RECEIVE,
BUS_POLICY_RULE_OWN
BUS_POLICY_RULE_OWN,
BUS_POLICY_RULE_USER,
BUS_POLICY_RULE_GROUP
} BusPolicyRuleType;
struct BusPolicyRule
......@@ -68,6 +67,18 @@ struct BusPolicyRule
char *service_name;
} own;
struct
{
char *user;
unsigned long uid;
} user;
struct
{
char *group;
unsigned long gid;
} group;
} d;
};
......@@ -90,7 +101,8 @@ dbus_bool_t bus_policy_check_can_receive (BusPolicy *policy,
dbus_bool_t bus_policy_check_can_own (BusPolicy *policy,
DBusConnection *connection,
const DBusString *service_name);
dbus_bool_t bus_policy_append_rule (BusPolicy *policy,
BusPolicyRule *rule);
void bus_policy_optimize (BusPolicy *policy);
#endif /* BUS_POLICY_H */
......@@ -134,7 +134,7 @@ AC_C_BIGENDIAN
AC_CHECK_LIB(socket,socket)
AC_CHECK_LIB(nsl,gethostbyname)
AC_CHECK_FUNCS(vsnprintf vasprintf nanosleep usleep poll setenv socketpair)
AC_CHECK_FUNCS(vsnprintf vasprintf nanosleep usleep poll setenv socketpair getgrouplist)
AC_CHECK_HEADERS(execinfo.h, [AC_CHECK_FUNCS(backtrace)])
......
......@@ -2214,6 +2214,8 @@ dbus_connection_handle_watch (DBusConnection *connection,
* Gets the UNIX user ID of the connection if any.
* Returns #TRUE if the uid is filled in.
* Always returns #FALSE on non-UNIX platforms.
* Always returns #FALSE prior to authenticating the
* connection.
*
* @param connection the connection
* @param uid return location for the user ID
......@@ -2226,8 +2228,12 @@ dbus_connection_get_unix_user (DBusConnection *connection,
dbus_bool_t result;
dbus_mutex_lock (connection->mutex);
result = _dbus_transport_get_unix_user (connection->transport,
uid);
if (!_dbus_transport_get_is_authenticated (connection->transport))
result = FALSE;
else
result = _dbus_transport_get_unix_user (connection->transport,
uid);
dbus_mutex_unlock (connection->mutex);
return result;
......
......@@ -313,6 +313,7 @@ _dbus_hash_table_new (DBusHashType type,
{
case DBUS_HASH_INT:
case DBUS_HASH_POINTER:
case DBUS_HASH_ULONG:
table->find_function = find_direct_function;
break;
case DBUS_HASH_STRING:
......@@ -642,6 +643,25 @@ _dbus_hash_iter_get_int_key (DBusHashIter *iter)
return _DBUS_POINTER_TO_INT (real->entry->key);
}
/**
* Gets the key for the current entry.
* Only works for hash tables of type #DBUS_HASH_ULONG.
*
* @param iter the hash table iterator.
*/
unsigned long
_dbus_hash_iter_get_ulong_key (DBusHashIter *iter)
{
DBusRealHashIter *real;
real = (DBusRealHashIter*) iter;
_dbus_assert (real->table != NULL);
_dbus_assert (real->entry != NULL);
return (unsigned long) real->entry->key;
}
/**
* Gets the key for the current entry.
* Only works for hash tables of type #DBUS_HASH_STRING
......@@ -963,6 +983,7 @@ rebuild_table (DBusHashTable *table)
idx = string_hash (entry->key) & table->mask;
break;
case DBUS_HASH_INT:
case DBUS_HASH_ULONG:
case DBUS_HASH_POINTER:
idx = RANDOM_INDEX (table, entry->key);
break;
......@@ -1059,6 +1080,31 @@ _dbus_hash_table_lookup_pointer (DBusHashTable *table,
return NULL;
}
/**
* Looks up the value for a given integer in a hash table
* of type #DBUS_HASH_ULONG. Returns %NULL if the value
* is not present. (A not-present entry is indistinguishable
* from an entry with a value of %NULL.)
* @param table the hash table.
* @param key the integer to look up.
* @returns the value of the hash entry.
*/
void*
_dbus_hash_table_lookup_ulong (DBusHashTable *table,
unsigned long key)
{
DBusHashEntry *entry;
_dbus_assert (table->key_type == DBUS_HASH_ULONG);
entry = (* table->find_function) (table, (void*) key, FALSE, NULL);
if (entry)
return entry->value;
else
return NULL;
}
/**
* Removes the hash entry for the given key. If no hash entry
* for the key exists, does nothing.
......@@ -1144,6 +1190,34 @@ _dbus_hash_table_remove_pointer (DBusHashTable *table,
}
/**
* Removes the hash entry for the given key. If no hash entry
* for the key exists, does nothing.
*
* @param table the hash table.
* @param key the hash key.
* @returns #TRUE if the entry existed
*/
dbus_bool_t
_dbus_hash_table_remove_ulong (DBusHashTable *table,
unsigned long key)
{
DBusHashEntry *entry;
DBusHashEntry **bucket;
_dbus_assert (table->key_type == DBUS_HASH_ULONG);
entry = (* table->find_function) (table, (void*) key, FALSE, &bucket);
if (entry)
{
remove_entry (table, bucket, entry);
return TRUE;
}
else
return FALSE;
}
/**
* Creates a hash entry with the given key and value.
* The key and value are not copied; they are stored
......@@ -1267,6 +1341,48 @@ _dbus_hash_table_insert_pointer (DBusHashTable *table,
return TRUE;
}
/**
* Creates a hash entry with the given key and value.
* The key and value are not copied; they are stored
* in the hash table by reference. If an entry with the
* given key already exists, the previous key and value
* are overwritten (and freed if the hash table has
* a key_free_function and/or value_free_function).
*
* Returns #FALSE if memory for the new hash entry
* can't be allocated.
*
* @param table the hash table.
* @param key the hash entry key.
* @param value the hash entry value.
*/
dbus_bool_t
_dbus_hash_table_insert_ulong (DBusHashTable *table,
unsigned long key,
void *value)
{
DBusHashEntry *entry;
_dbus_assert (table->key_type == DBUS_HASH_ULONG);
entry = (* table->find_function) (table, (void*) key, TRUE, NULL);
if (entry == NULL)
return FALSE; /* no memory */
if (table->free_key_function && entry->key != (void*) key)
(* table->free_key_function) (entry->key);
if (table->free_value_function && entry->value != value)
(* table->free_value_function) (entry->value);
entry->key = (void*) key;
entry->value = value;
return TRUE;
}
/**
* Gets the number of hash entries in a hash table.
*
......@@ -1316,6 +1432,7 @@ _dbus_hash_test (void)
int