dbus/bus Makefile.am, 1.29, 1.30 bus.c, 1.52, 1.53 bus.h, 1.23,
1.24 config-parser.c, 1.30, 1.31 config-parser.h, 1.14,
1.15 connection.c, 1.54, 1.55 connection.h, 1.19,
1.20 dbus-daemon-1.1.in, 1.7, 1.8 main.c, 1.23, 1.24 policy.c,
1.19, 1.20 selinux.c, NONE, 1.1 selinux.h, NONE,
1.1 services.c, 1.21, 1.22 services.h, 1.10, 1.11 test-main.c,
1.16, 1.17
Havoc Pennington
hp at freedesktop.org
Thu Jul 29 22:59:37 PDT 2004
- Previous message: dbus ChangeLog,1.539,1.540 configure.in,1.91,1.92
- Next message: dbus/dbus dbus-connection.c, 1.82, 1.83 dbus-connection.h, 1.30,
1.31 dbus-transport-protected.h, 1.11,
1.12 dbus-transport-unix.c, 1.38, 1.39 dbus-transport.c, 1.39,
1.40 dbus-transport.h, 1.15, 1.16
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
Update of /cvs/dbus/dbus/bus
In directory pdx:/tmp/cvs-serv30925/bus
Modified Files:
Makefile.am bus.c bus.h config-parser.c config-parser.h
connection.c connection.h dbus-daemon-1.1.in main.c policy.c
services.c services.h test-main.c
Added Files:
selinux.c selinux.h
Log Message:
2004-07-24 Havoc Pennington <hp at redhat.com>
SELinux support from Matthew Rickard <mjricka at epoch.ncsc.mil>
* bus/selinux.c, bus/selinux.h: new file encapsulating selinux
functionality
* configure.in: add --enable-selinux
* bus/policy.c (bus_policy_merge): add FIXME to a comment
* bus/main.c (main): initialize and shut down selinux
* bus/connection.c: store SELinux ID on each connection, to avoid
repeated getting of the string context and converting it into
an ID
* bus/bus.c (bus_context_get_policy): new accessor, though it
isn't used
(bus_context_check_security_policy): check whether the security
context of sender connection can send to the security context of
recipient connection
* bus/config-parser.c: add parsing for <selinux> and <associate>
* dbus/dbus-transport.c (_dbus_transport_get_unix_fd): to
implement dbus_connection_get_unix_fd()
* dbus/dbus-connection.c (dbus_connection_get_unix_fd): new
function, used by the selinux stuff
Index: Makefile.am
===================================================================
RCS file: /cvs/dbus/dbus/bus/Makefile.am,v
retrieving revision 1.29
retrieving revision 1.30
diff -u -d -r1.29 -r1.30
--- Makefile.am 11 Oct 2003 06:20:28 -0000 1.29
+++ Makefile.am 30 Jul 2004 05:59:33 -0000 1.30
@@ -10,7 +10,7 @@
CONFIG_IN_FILES= \
session.conf.in \
- system.conf.in
+ system.conf.in
config_DATA= \
session.conf \
@@ -44,6 +44,8 @@
expirelist.h \
policy.c \
policy.h \
+ selinux.h \
+ selinux.c \
services.c \
services.h \
signals.c \
Index: bus.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/bus.c,v
retrieving revision 1.52
retrieving revision 1.53
diff -u -d -r1.52 -r1.53
--- bus.c 29 May 2004 04:17:16 -0000 1.52
+++ bus.c 30 Jul 2004 05:59:33 -0000 1.53
@@ -29,6 +29,7 @@
#include "policy.h"
#include "config-parser.h"
#include "signals.h"
+#include "selinux.h"
#include <dbus/dbus-list.h>
#include <dbus/dbus-hash.h>
#include <dbus/dbus-internals.h>
@@ -403,6 +404,7 @@
{
DBusString full_address;
DBusList *link;
+ DBusHashTable *service_sid_table;
dbus_bool_t retval;
@@ -480,6 +482,11 @@
goto failed;
}
+ service_sid_table = bus_config_parser_steal_service_sid_table (parser);
+ bus_registry_set_service_sid_table (context->registry,
+ service_sid_table);
+ _dbus_hash_table_unref (service_sid_table);
+
_DBUS_ASSERT_ERROR_IS_CLEAR (error);
retval = TRUE;
@@ -569,6 +576,13 @@
goto failed;
}
+ context->registry = bus_registry_new (context);
+ if (context->registry == NULL)
+ {
+ BUS_SET_OOM (error);
+ goto failed;
+ }
+
if (!load_config (context, FALSE, error))
{
_DBUS_ASSERT_ERROR_IS_SET (error);
@@ -637,13 +651,6 @@
goto failed;
}
- context->registry = bus_registry_new (context);
- if (context->registry == NULL)
- {
- BUS_SET_OOM (error);
- goto failed;
- }
-
context->matchmaker = bus_matchmaker_new ();
if (context->matchmaker == NULL)
{
@@ -958,6 +965,12 @@
uid);
}
+BusPolicy *
+bus_context_get_policy (BusContext *context)
+{
+ return context->policy;
+}
+
BusClientPolicy*
bus_context_create_client_policy (BusContext *context,
DBusConnection *connection,
@@ -1088,6 +1101,28 @@
if (sender != NULL)
{
+ /* First verify the SELinux access controls. If allowed then
+ * go on with the standard checks.
+ */
+ if (!bus_selinux_allows_send (sender, proposed_recipient))
+ {
+ const char *dest = dbus_message_get_destination (message);
+ dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
+ "An SELinux policy prevents this sender "
+ "from sending this message to this recipient "
+ "(rejected message had interface \"%s\" "
+ "member \"%s\" error name \"%s\" destination \"%s\")",
+ dbus_message_get_interface (message) ?
+ dbus_message_get_interface (message) : "(unset)",
+ dbus_message_get_member (message) ?
+ dbus_message_get_member (message) : "(unset)",
+ dbus_message_get_error_name (message) ?
+ dbus_message_get_error_name (message) : "(unset)",
+ dest ? dest : DBUS_SERVICE_ORG_FREEDESKTOP_DBUS);
+ _dbus_verbose ("SELinux security check denying send to service\n");
+ return FALSE;
+ }
+
if (bus_connection_is_active (sender))
{
sender_policy = bus_connection_get_policy (sender);
Index: bus.h
===================================================================
RCS file: /cvs/dbus/dbus/bus/bus.h,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -d -r1.23 -r1.24
--- bus.h 16 Apr 2004 15:01:25 -0000 1.23
+++ bus.h 30 Jul 2004 05:59:33 -0000 1.24
@@ -38,6 +38,7 @@
typedef struct BusClientPolicy BusClientPolicy;
typedef struct BusPolicyRule BusPolicyRule;
typedef struct BusRegistry BusRegistry;
+typedef struct BusSELinuxID BusSELinuxID;
typedef struct BusService BusService;
typedef struct BusTransaction BusTransaction;
typedef struct BusMatchmaker BusMatchmaker;
@@ -78,8 +79,11 @@
BusMatchmaker* bus_context_get_matchmaker (BusContext *context);
DBusLoop* bus_context_get_loop (BusContext *context);
DBusUserDatabase* bus_context_get_user_database (BusContext *context);
+
dbus_bool_t bus_context_allow_user (BusContext *context,
unsigned long uid);
+BusPolicy* bus_context_get_policy (BusContext *context);
+
BusClientPolicy* bus_context_create_client_policy (BusContext *context,
DBusConnection *connection,
DBusError *error);
@@ -101,5 +105,4 @@
DBusMessage *message,
DBusError *error);
-
#endif /* BUS_BUS_H */
Index: config-parser.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/config-parser.c,v
retrieving revision 1.30
retrieving revision 1.31
diff -u -d -r1.30 -r1.31
--- config-parser.c 29 May 2004 04:17:16 -0000 1.30
+++ config-parser.c 30 Jul 2004 05:59:34 -0000 1.31
@@ -24,6 +24,7 @@
#include "test.h"
#include "utils.h"
#include "policy.h"
+#include "selinux.h"
#include <dbus/dbus-list.h>
#include <dbus/dbus-internals.h>
#include <string.h>
@@ -44,7 +45,9 @@
ELEMENT_PIDFILE,
ELEMENT_SERVICEDIR,
ELEMENT_INCLUDEDIR,
- ELEMENT_TYPE
+ ELEMENT_TYPE,
+ ELEMENT_SELINUX,
+ ELEMENT_ASSOCIATE
} ElementType;
typedef enum
@@ -117,6 +120,8 @@
DBusList *included_files; /**< Included files stack */
+ DBusHashTable *service_sid_table; /**< Map service names to SELinux contexts */
+
unsigned int fork : 1; /**< TRUE to fork into daemon mode */
unsigned int is_toplevel : 1; /**< FALSE if we are a sub-config-file inside another one */
@@ -157,6 +162,10 @@
return "includedir";
case ELEMENT_TYPE:
return "type";
+ case ELEMENT_SELINUX:
+ return "selinux";
+ case ELEMENT_ASSOCIATE:
+ return "associate";
}
_dbus_assert_not_reached ("bad element type");
@@ -235,6 +244,7 @@
DBusError *error)
{
DBusList *link;
+ DBusHashTable *table;
if (!bus_policy_merge (parser->policy,
included->policy))
@@ -242,6 +252,17 @@
BUS_SET_OOM (error);
return FALSE;
}
+
+ table = bus_selinux_id_table_union (parser->service_sid_table,
+ included->service_sid_table);
+ if (table == NULL)
+ {
+ BUS_SET_OOM (error);
+ return FALSE;
+ }
+
+ _dbus_hash_table_unref (parser->service_sid_table);
+ parser->service_sid_table = table;
if (included->user != NULL)
{
@@ -317,12 +338,17 @@
}
if (((parser->policy = bus_policy_new ()) == NULL) ||
- !_dbus_string_copy (basedir, 0, &parser->basedir, 0))
+ !_dbus_string_copy (basedir, 0, &parser->basedir, 0) ||
+ ((parser->service_sid_table = bus_selinux_id_table_new ()) == NULL))
{
if (parser->policy)
bus_policy_unref (parser->policy);
_dbus_string_free (&parser->basedir);
+
+ if (parser->service_sid_table == NULL)
+ _dbus_hash_table_unref (parser->service_sid_table);
+
dbus_free (parser);
return NULL;
}
@@ -428,6 +454,9 @@
if (parser->policy)
bus_policy_unref (parser->policy);
+ if (parser->service_sid_table)
+ _dbus_hash_table_unref (parser->service_sid_table);
+
dbus_free (parser);
}
}
@@ -658,7 +687,7 @@
BUS_SET_OOM (error);
return FALSE;
}
-
+
return TRUE;
}
else if (strcmp (element_name, "includedir") == 0)
@@ -843,6 +872,22 @@
return TRUE;
}
+ else if (strcmp (element_name, "selinux") == 0)
+ {
+ Element *e;
+ const char *name;
+
+ if (!check_no_attributes (parser, "selinux", attribute_names, attribute_values, error))
+ return FALSE;
+
+ if (push_element (parser, ELEMENT_SELINUX) == NULL)
+ {
+ BUS_SET_OOM (error);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -1390,6 +1435,58 @@
}
}
+static dbus_bool_t
+start_selinux_child (BusConfigParser *parser,
+ const char *element_name,
+ const char **attribute_names,
+ const char **attribute_values,
+ DBusError *error)
+{
+ if (strcmp (element_name, "associate") == 0)
+ {
+ const char *own;
+ const char *context;
+
+ if (!locate_attributes (parser, "associate",
+ attribute_names,
+ attribute_values,
+ error,
+ "own", &own,
+ "context", &context,
+ NULL))
+ return FALSE;
+
+ if (push_element (parser, ELEMENT_ASSOCIATE) == NULL)
+ {
+ BUS_SET_OOM (error);
+ return FALSE;
+ }
+
+ if (own == NULL || context == NULL)
+ {
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "Element <associate> must have attributes own=\"<servicename>\" and context=\"<selinux context>\"");
+ return FALSE;
+ }
+
+ if (!bus_selinux_id_table_insert (parser->service_sid_table,
+ own, context))
+ {
+ BUS_SET_OOM (error);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ dbus_set_error (error, DBUS_ERROR_FAILED,
+ "Element <%s> not allowed inside <%s> in configuration file",
+ element_name, "selinux");
+ return FALSE;
+ }
+}
+
dbus_bool_t
bus_config_parser_start_element (BusConfigParser *parser,
const char *element_name,
@@ -1440,6 +1537,12 @@
attribute_names, attribute_values,
error);
}
+ else if (t == ELEMENT_SELINUX)
+ {
+ return start_selinux_child (parser, element_name,
+ attribute_names, attribute_values,
+ error);
+ }
else
{
dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -1635,6 +1738,8 @@
case ELEMENT_ALLOW:
case ELEMENT_DENY:
case ELEMENT_FORK:
+ case ELEMENT_SELINUX:
+ case ELEMENT_ASSOCIATE:
break;
}
@@ -1867,6 +1972,8 @@
case ELEMENT_ALLOW:
case ELEMENT_DENY:
case ELEMENT_FORK:
+ case ELEMENT_SELINUX:
+ case ELEMENT_ASSOCIATE:
if (all_whitespace (content))
return TRUE;
else
@@ -2160,6 +2267,20 @@
*limits = parser->limits;
}
+DBusHashTable*
+bus_config_parser_steal_service_sid_table (BusConfigParser *parser)
+{
+ DBusHashTable *table;
+
+ _dbus_assert (parser->service_sid_table != NULL); /* can only steal once */
+
+ table = parser->service_sid_table;
+
+ parser->service_sid_table = NULL;
+
+ return table;
+}
+
#ifdef DBUS_BUILD_TESTS
#include <stdio.h>
@@ -2494,6 +2615,8 @@
/* FIXME: compare policy */
+ /* FIXME: compare service selinux ID table */
+
if (! limits_equal (&a->limits, &b->limits))
return FALSE;
Index: config-parser.h
===================================================================
RCS file: /cvs/dbus/dbus/bus/config-parser.h,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -d -r1.14 -r1.15
--- config-parser.h 9 Apr 2004 19:50:29 -0000 1.14
+++ config-parser.h 30 Jul 2004 05:59:34 -0000 1.15
@@ -29,6 +29,7 @@
#include <dbus/dbus.h>
#include <dbus/dbus-string.h>
#include <dbus/dbus-list.h>
+#include <dbus/dbus-hash.h>
#include "bus.h"
/* Whatever XML library we're using just pushes data into this API */
@@ -70,6 +71,8 @@
void bus_config_parser_get_limits (BusConfigParser *parser,
BusLimits *limits);
+DBusHashTable* bus_config_parser_steal_service_sid_table (BusConfigParser *parser);
+
/* Loader functions (backended off one of the XML parsers). Returns a
* finished ConfigParser.
*/
Index: connection.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/connection.c,v
retrieving revision 1.54
retrieving revision 1.55
diff -u -d -r1.54 -r1.55
--- connection.c 20 May 2004 19:47:34 -0000 1.54
+++ connection.c 30 Jul 2004 05:59:34 -0000 1.55
@@ -27,6 +27,7 @@
#include "utils.h"
#include "signals.h"
#include "expirelist.h"
+#include "selinux.h"
#include <dbus/dbus-list.h>
#include <dbus/dbus-hash.h>
#include <dbus/dbus-timeout.h>
@@ -75,6 +76,8 @@
DBusPreallocatedSend *oom_preallocated;
BusClientPolicy *policy;
+ BusSELinuxID *selinux_id;
+
long connection_tv_sec; /**< Time when we connected (seconds component) */
long connection_tv_usec; /**< Time when we connected (microsec component) */
int stamp; /**< connections->stamp last time we were traversed */
@@ -401,6 +404,9 @@
if (d->policy)
bus_client_policy_unref (d->policy);
+
+ if (d->selinux_id)
+ bus_selinux_id_unref (d->selinux_id);
dbus_free (d->name);
@@ -539,6 +545,7 @@
{
BusConnectionData *d;
dbus_bool_t retval;
+ DBusError error;
d = dbus_new0 (BusConnectionData, 1);
@@ -562,6 +569,20 @@
}
retval = FALSE;
+
+ dbus_error_init (&error);
+ d->selinux_id = bus_selinux_init_connection_id (connection,
+ &error);
+ if (dbus_error_is_set (&error))
+ {
+ /* This is a bit bogus because we pretend all errors
+ * are OOM; this is done because we know that in bus.c
+ * an OOM error disconnects the connection, which is
+ * the same thing we want on any other error.
+ */
+ dbus_error_free (&error);
+ goto out;
+ }
if (!dbus_connection_set_watch_functions (connection,
add_connection_watch,
@@ -639,7 +660,11 @@
out:
if (!retval)
- {
+ {
+ if (d->selinux_id)
+ bus_selinux_id_unref (d->selinux_id);
+ d->selinux_id = NULL;
+
if (!dbus_connection_set_watch_functions (connection,
NULL, NULL, NULL,
connection,
@@ -1008,6 +1033,18 @@
return bus_context_get_matchmaker (d->connections->context);
}
+BusSELinuxID*
+bus_connection_get_selinux_id (DBusConnection *connection)
+{
+ BusConnectionData *d;
+
+ d = BUS_CONNECTION_DATA (connection);
+
+ _dbus_assert (d != NULL);
+
+ return d->selinux_id;
+}
+
/**
* Checks whether the connection is registered with the message bus.
*
Index: connection.h
===================================================================
RCS file: /cvs/dbus/dbus/bus/connection.h,v
retrieving revision 1.19
retrieving revision 1.20
diff -u -d -r1.19 -r1.20
--- connection.h 2 Dec 2003 10:44:21 -0000 1.19
+++ connection.h 30 Jul 2004 05:59:34 -0000 1.20
@@ -1,7 +1,7 @@
/* -*- mode: C; c-file-style: "gnu" -*- */
/* connection.h Client connections
*
- * Copyright (C) 2003 Red Hat, Inc.
+ * Copyright (C) 2003, 2004 Red Hat, Inc.
*
* Licensed under the Academic Free License version 2.0
*
@@ -50,6 +50,7 @@
BusRegistry* bus_connection_get_registry (DBusConnection *connection);
BusActivation* bus_connection_get_activation (DBusConnection *connection);
BusMatchmaker* bus_connection_get_matchmaker (DBusConnection *connection);
+BusSELinuxID* bus_connection_get_selinux_id (DBusConnection *connection);
dbus_bool_t bus_connections_check_limits (BusConnections *connections,
DBusConnection *requesting_completion,
DBusError *error);
Index: dbus-daemon-1.1.in
===================================================================
RCS file: /cvs/dbus/dbus/bus/dbus-daemon-1.1.in,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -d -r1.7 -r1.8
--- dbus-daemon-1.1.in 29 May 2004 04:17:16 -0000 1.7
+++ dbus-daemon-1.1.in 30 Jul 2004 05:59:34 -0000 1.8
@@ -463,6 +463,110 @@
Be careful with send_interface/receive_interface, because the
interface field in messages is optional.
+.TP
+.I "<selinux>"
+
+.PP
+The <selinux> element contains settings related to Security Enhanced Linux.
+More details below.
+
+.TP
+.I "<associate>"
+
+.PP
+An <associate> element appears below an <selinux> element and
+creates a mapping. Right now only one kind of association is possible:
+.nf
+ <associate own="org.freedesktop.Foobar" context="foo_t"/>
+.fi
+
+.PP
+This means that if a connection asks to own the service
+"org.freedesktop.Foobar" then the source context will be the context
+of the connection and the target context will be "foo_t" - see the
+short discussion of SELinux below.
+
+.PP
+Note, the context here is the target context when acquiring a service,
+NOT the context of the connection owning the service.
+
+.PP
+There's currently no way to set a default for owning any service, if
+we add this syntax it will look like:
+.nf
+ <associate own="*" context="foo_t"/>
+.fi
+If you find a reason this is useful, let the developers know.
+Right now the default will be the security context of the bus itself.
+
+.PP
+If two <associate> elements specify the same service name,
+the element appearing later in the configuration file will
+be used.
+
+.SH SELinux
+
+.PP
+See http://www.nsa.gov/selinux/ for full details on SELinux. Some useful excerpts:
+
+.IP "" 8
+Every subject (process) and object (e.g. file, socket, IPC object,
+etc) in the system is assigned a collection of security attributes,
+known as a security context. A security context contains all of the
+security attributes associated with a particular subject or object
+that are relevant to the security policy.
+
+.IP "" 8
+In order to better encapsulate security contexts and to provide
+greater efficiency, the policy enforcement code of SELinux typically
+handles security identifiers (SIDs) rather than security contexts. A
+SID is an integer that is mapped by the security server to a security
+context at runtime.
+
+.IP "" 8
+When a security decision is required, the policy enforcement code
+passes a pair of SIDs (typically the SID of a subject and the SID of
+an object, but sometimes a pair of subject SIDs or a pair of object
+SIDs), and an object security class to the security server. The object
+security class indicates the kind of object, e.g. a process, a regular
+file, a directory, a TCP socket, etc.
+
+.IP "" 8
+Access decisions specify whether or not a permission is granted for a
+given pair of SIDs and class. Each object class has a set of
+associated permissions defined to control operations on objects with
+that class.
+
+.PP
+D-BUS performs SELinux security checks in two places.
+
+.PP
+First, any time a message is routed from one connection to another
+connection, the bus daemon will check permissions with the security context of
+the first connection as source, security context of the second connection
+as target, object class "dbus" and requested permission "send_msg".
+
+.PP
+If a security context is not available for a connection
+(impossible when using UNIX domain sockets), then the target
+context used is the context of the bus daemon itself.
+There is currently no way to change this default, because we're
+assuming that only UNIX domain sockets will be used to
+connect to the systemwide bus. If this changes, we'll
+probably add a way to set the default connection context.
+
+.PP
+Second, any time a connection asks to own a service,
+the bus daemon will check permissions with the security
+context of the connection as source, the security context specified
+for the service name with an <associate> element as target, object
+class "dbus" and requested permission "acquire_svc".
+
+.PP
+If the service name has no security context associated in the
+configuration file, the security context of the bus daemon
+itself will be used.
+
.SH AUTHOR
See http://www.freedesktop.org/software/dbus/doc/AUTHORS
Index: main.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/main.c,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -d -r1.23 -r1.24
--- main.c 20 May 2004 18:45:16 -0000 1.23
+++ main.c 30 Jul 2004 05:59:34 -0000 1.24
@@ -28,6 +28,7 @@
#include <string.h>
#include <signal.h>
#include <errno.h>
+#include "selinux.h"
static BusContext *context;
@@ -371,7 +372,13 @@
print_pid_fd = val;
}
}
-
+
+ if (!bus_selinux_init ())
+ {
+ _dbus_warn ("SELinux initialization failed\n");
+ exit (1);
+ }
+
dbus_error_init (&error);
context = bus_context_new (&config_file, force_fork,
print_addr_fd, print_pid_fd,
@@ -395,6 +402,7 @@
bus_context_shutdown (context);
bus_context_unref (context);
+ bus_selinux_shutdown ();
return 0;
}
Index: policy.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/policy.c,v
retrieving revision 1.19
retrieving revision 1.20
diff -u -d -r1.19 -r1.20
--- policy.c 29 May 2004 04:17:16 -0000 1.19
+++ policy.c 30 Jul 2004 05:59:34 -0000 1.20
@@ -177,7 +177,7 @@
free_rule_list_func);
if (policy->rules_by_gid == NULL)
goto failed;
-
+
return policy;
failed:
@@ -594,9 +594,10 @@
bus_policy_merge (BusPolicy *policy,
BusPolicy *to_absorb)
{
- /* Not properly atomic, but as used for configuration files
- * we don't rely on it.
- */
+ /* FIXME Not properly atomic, but as used for configuration files we
+ * don't rely on it quite so much.
+ */
+
if (!append_copy_of_policy_list (&policy->default_rules,
&to_absorb->default_rules))
return FALSE;
@@ -670,7 +671,7 @@
NULL);
_dbus_list_clear (&policy->rules);
-
+
dbus_free (policy);
}
}
--- NEW FILE: selinux.c ---
/* selinux.c SELinux security checks for D-BUS
*
* Author: Matthew Rickard <mjricka at epoch.ncsc.mil>
*
* Licensed under the Academic Free License version 2.0
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <dbus/dbus-internals.h>
#include <dbus/dbus-string.h>
#include "selinux.h"
#include "services.h"
#include "policy.h"
#include "config-parser.h"
#ifdef HAVE_SELINUX
#include <errno.h>
#include <syslog.h>
#include <selinux/selinux.h>
#include <selinux/avc.h>
#include <selinux/av_permissions.h>
#include <selinux/flask.h>
#include <stdarg.h>
#endif /* HAVE_SELINUX */
#define BUS_SID_FROM_SELINUX(sid) ((BusSELinuxID*) (sid))
#define SELINUX_SID_FROM_BUS(sid) ((security_id_t) (sid))
#ifdef HAVE_SELINUX
/* Store the value telling us if SELinux is enabled in the kernel. */
static dbus_bool_t selinux_enabled = FALSE;
/* Store an avc_entry_ref to speed AVC decisions. */
static struct avc_entry_ref aeref;
static security_id_t bus_sid = SECSID_WILD;
#endif /* HAVE_SELINUX */
/**
* Log callback to log denial messages from the AVC.
* This is used in avc_init. Logs to both standard
* error and syslogd.
*
* @param fmt the format string
* @param variable argument list
*/
#ifdef HAVE_SELINUX
static void
log_callback (const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsyslog (LOG_INFO, fmt, ap);
va_end(ap);
}
#endif /* HAVE_SELINUX */
/**
* Initialize the user space access vector cache (AVC) for D-BUS and set up
* logging callbacks.
*/
dbus_bool_t
bus_selinux_init (void)
{
#ifdef HAVE_SELINUX
struct avc_log_callback log_cb = {(void*)log_callback, NULL};
int r;
char *bus_context;
_dbus_assert (bus_sid == SECSID_WILD);
/* Determine if we are running an SELinux kernel. */
r = is_selinux_enabled ();
if (r < 0)
{
_dbus_warn ("Could not tell if SELinux is enabled: %s\n",
_dbus_strerror (errno));
return FALSE;
}
selinux_enabled = r != 0;
if (!selinux_enabled)
{
_dbus_verbose ("SELinux not enabled in this kernel.\n");
return TRUE;
}
_dbus_verbose ("SELinux is enabled in this kernel.\n");
avc_entry_ref_init (&aeref);
if (avc_init ("avc", NULL, &log_cb, NULL, NULL) < 0)
{
_dbus_warn ("Failed to start Access Vector Cache (AVC).\n");
return FALSE;
}
else
{
openlog ("dbus", LOG_PERROR, LOG_USER);
_dbus_verbose ("Access Vector Cache (AVC) started.\n");
}
bus_context = NULL;
bus_sid = SECSID_WILD;
if (getcon (&bus_context) < 0)
{
_dbus_verbose ("Error getting context of bus: %s\n",
_dbus_strerror (errno));
return FALSE;
}
if (avc_context_to_sid (bus_context, &bus_sid) < 0)
{
_dbus_verbose ("Error getting SID from bus context: %s\n",
_dbus_strerror (errno));
freecon (bus_context);
return FALSE;
}
freecon (bus_context);
return TRUE;
#else
return TRUE;
#endif /* HAVE_SELINUX */
}
/**
* Decrement SID reference count.
*
* @param sid the SID to decrement
*/
void
bus_selinux_id_unref (BusSELinuxID *sid)
{
#ifdef HAVE_SELINUX
if (!selinux_enabled)
return;
_dbus_assert (sid != NULL);
sidput (SELINUX_SID_FROM_BUS (sid));
#endif /* HAVE_SELINUX */
}
void
bus_selinux_id_ref (BusSELinuxID *sid)
{
#ifdef HAVE_SELINUX
if (!selinux_enabled)
return;
_dbus_assert (sid != NULL);
sidget (SELINUX_SID_FROM_BUS (sid));
#endif /* HAVE_SELINUX */
}
/**
* Determine if the SELinux security policy allows the given sender
* security context to go to the given recipient security context.
* This function determines if the requested permissions are to be
* granted from the connection to the message bus or to another
* optionally supplied security identifier (e.g. for a service
* context). Currently these permissions are either send_msg or
* acquire_svc in the dbus class.
*
* @param sender_sid source security context
* @param override_sid is the target security context. If SECSID_WILD this will
* use the context of the bus itself (e.g. the default).
* @param target_class is the target security class.
* @param requested is the requested permissions.
* @returns #TRUE if security policy allows the send.
*/
#ifdef HAVE_SELINUX
static dbus_bool_t
bus_selinux_check (BusSELinuxID *sender_sid,
BusSELinuxID *override_sid,
security_class_t target_class,
access_vector_t requested)
{
if (!selinux_enabled)
return TRUE;
/* Make the security check. AVC checks enforcing mode here as well. */
if (avc_has_perm (SELINUX_SID_FROM_BUS (sender_sid),
override_sid ?
SELINUX_SID_FROM_BUS (override_sid) :
SELINUX_SID_FROM_BUS (bus_sid),
target_class, requested, &aeref, NULL) < 0)
{
_dbus_verbose ("SELinux denying due to security policy.\n");
return FALSE;
}
else
return TRUE;
}
#endif /* HAVE_SELINUX */
/**
* Returns true if the given connection can acquire a service,
* assuming the given security ID is needed for that service.
*
* @param connection connection that wants to own the service
* @param service_sid the SID of the service from the table
* @returns #TRUE if acquire is permitted.
*/
dbus_bool_t
bus_selinux_allows_acquire_service (DBusConnection *connection,
BusSELinuxID *service_sid)
{
#ifdef HAVE_SELINUX
BusSELinuxID *connection_sid;
if (!selinux_enabled)
return TRUE;
connection_sid = bus_connection_get_selinux_id (connection);
return bus_selinux_check (connection_sid,
service_sid,
SECCLASS_DBUS,
DBUS__ACQUIRE_SVC);
#else
return TRUE;
#endif /* HAVE_SELINUX */
}
/**
* Check if SELinux security controls allow the message to be sent to a
* particular connection based on the security context of the sender and
* that of the receiver. The destination connection need not be the
* addressed recipient, it could be an "eavesdropper"
*
* @param sender the sender of the message.
* @param proposed_recipient the connection the message is to be sent to.
* @returns whether to allow the send
*/
dbus_bool_t
bus_selinux_allows_send (DBusConnection *sender,
DBusConnection *proposed_recipient)
{
#ifdef HAVE_SELINUX
BusSELinuxID *recipient_sid;
BusSELinuxID *sender_sid;
if (!selinux_enabled)
return TRUE;
sender_sid = bus_connection_get_selinux_id (sender);
/* A NULL proposed_recipient means the bus itself. */
if (proposed_recipient)
recipient_sid = bus_connection_get_selinux_id (proposed_recipient);
else
recipient_sid = BUS_SID_FROM_SELINUX (bus_sid);
return bus_selinux_check (sender_sid, recipient_sid,
SECCLASS_DBUS, DBUS__SEND_MSG);
#else
return TRUE;
#endif /* HAVE_SELINUX */
}
/**
* Gets the security context of a connection to the bus. It is up to
* the caller to freecon() when they are done.
*
* @param connection the connection to get the context of.
* @param con the location to store the security context.
* @returns #TRUE if context is successfully obtained.
*/
#ifdef HAVE_SELINUX
static dbus_bool_t
bus_connection_read_selinux_context (DBusConnection *connection,
char **con)
{
int fd;
if (!selinux_enabled)
return FALSE;
_dbus_assert (connection != NULL);
if (!dbus_connection_get_unix_fd (connection, &fd))
{
_dbus_verbose ("Failed to get file descriptor of socket.\n");
return FALSE;
}
if (getpeercon (fd, con) < 0)
{
_dbus_verbose ("Error getting context of socket peer: %s\n",
_dbus_strerror (errno));
return FALSE;
}
_dbus_verbose ("Successfully read connection context.\n");
return TRUE;
}
#endif /* HAVE_SELINUX */
/**
* Read the SELinux ID from the connection.
*
* @param connection the connection to read from
* @returns the SID if successfully determined, #NULL otherwise.
*/
BusSELinuxID*
bus_selinux_init_connection_id (DBusConnection *connection,
DBusError *error)
{
#ifdef HAVE_SELINUX
char *con;
security_id_t sid;
if (!selinux_enabled)
return NULL;
if (!bus_connection_read_selinux_context (connection, &con))
{
dbus_set_error (error, DBUS_ERROR_FAILED,
"Failed to read an SELinux context from connection");
_dbus_verbose ("Error getting peer context.\n");
return NULL;
}
_dbus_verbose ("Converting context to SID to store on connection\n");
if (avc_context_to_sid (con, &sid) < 0)
{
if (errno == ENOMEM)
BUS_SET_OOM (error);
else
dbus_set_error (error, DBUS_ERROR_FAILED,
"Error getting SID from context: %s\n",
_dbus_strerror (errno));
_dbus_warn ("Error getting SID from context: %s\n",
_dbus_strerror (errno));
freecon (con);
return NULL;
}
freecon (con);
return BUS_SID_FROM_SELINUX (sid);
#else
return NULL;
#endif /* HAVE_SELINUX */
}
/* Function for freeing hash table data. These SIDs
* should no longer be referenced.
*/
static void
bus_selinux_id_table_free_value (BusSELinuxID *sid)
{
#ifdef HAVE_SELINUX
/* NULL sometimes due to how DBusHashTable works */
if (sid)
bus_selinux_id_unref (sid);
#endif /* HAVE_SELINUX */
}
/**
* Creates a new table mapping service names to security ID.
* A security ID is a "compiled" security context, a security
* context is just a string.
*
* @returns the new table or #NULL if no memory
*/
DBusHashTable*
bus_selinux_id_table_new (void)
{
return _dbus_hash_table_new (DBUS_HASH_STRING,
(DBusFreeFunction) dbus_free,
(DBusFreeFunction) bus_selinux_id_table_free_value);
}
/**
* Hashes a service name and service context into the service SID
* table as a string and a SID.
*
* @param service_name is the name of the service.
* @param service_context is the context of the service.
* @param service_table is the table to hash them into.
* @return #FALSE if not enough memory
*/
dbus_bool_t
bus_selinux_id_table_insert (DBusHashTable *service_table,
const char *service_name,
const char *service_context)
{
#ifdef HAVE_SELINUX
dbus_bool_t retval;
security_id_t sid;
char *key;
if (!selinux_enabled)
return TRUE;
sid = SECSID_WILD;
retval = FALSE;
key = _dbus_strdup (service_name);
if (key == NULL)
return retval;
if (avc_context_to_sid (service_context, &sid) < 0)
{
_dbus_assert (errno == ENOMEM);
goto out;
}
if (!_dbus_hash_table_insert_string (service_table,
key,
BUS_SID_FROM_SELINUX (sid)))
goto out;
_dbus_verbose ("Parsed \tservice: %s \n\t\tcontext: %s\n",
key,
sid->ctx);
/* These are owned by the hash, so clear them to avoid unref */
key = NULL;
sid = SECSID_WILD;
retval = TRUE;
out:
if (sid != SECSID_WILD)
sidput (sid);
if (key)
dbus_free (key);
return retval;
#else
return TRUE;
#endif /* HAVE_SELINUX */
}
/**
* Find the security identifier associated with a particular service
* name. Return a pointer to this SID, or #NULL/SECSID_WILD if the
* service is not found in the hash table. This should be nearly a
* constant time operation. If SELinux support is not available,
* always return NULL.
*
* @todo This should return a const security_id_t since we don't
* want the caller to mess with it.
*
* @param service_table the hash table to check for service name.
* @param service_name the name of the service to look for.
* @returns the SELinux ID associated with the service
*/
BusSELinuxID*
bus_selinux_id_table_lookup (DBusHashTable *service_table,
const DBusString *service_name)
{
#ifdef HAVE_SELINUX
security_id_t sid;
sid = SECSID_WILD; /* default context */
if (!selinux_enabled)
return NULL;
_dbus_verbose ("Looking up service SID for %s\n",
_dbus_string_get_const_data (service_name));
sid = _dbus_hash_table_lookup_string (service_table,
_dbus_string_get_const_data (service_name));
if (sid == SECSID_WILD)
_dbus_verbose ("Service %s not found\n",
_dbus_string_get_const_data (service_name));
else
_dbus_verbose ("Service %s found\n",
_dbus_string_get_const_data (service_name));
return BUS_SID_FROM_SELINUX (sid);
#endif /* HAVE_SELINUX */
return NULL;
}
#ifdef HAVE_SELINUX
static dbus_bool_t
bus_selinux_id_table_copy_over (DBusHashTable *dest,
DBusHashTable *override)
{
const char *key;
char *key_copy;
BusSELinuxID *sid;
DBusHashIter iter;
_dbus_hash_iter_init (override, &iter);
while (_dbus_hash_iter_next (&iter))
{
key = _dbus_hash_iter_get_string_key (&iter);
sid = _dbus_hash_iter_get_value (&iter);
key_copy = _dbus_strdup (key);
if (key_copy == NULL)
return FALSE;
if (!_dbus_hash_table_insert_string (dest,
key_copy,
sid))
{
dbus_free (key_copy);
return FALSE;
}
bus_selinux_id_ref (sid);
}
return TRUE;
}
#endif /* HAVE_SELINUX */
/**
* Creates the union of the two tables (each table maps a service
* name to a security ID). In case of the same service name in
* both tables, the security ID from "override" will be used.
*
* @param base the base table
* @param override the table that takes precedence in the merge
* @returns the new table, or #NULL if out of memory
*/
DBusHashTable*
bus_selinux_id_table_union (DBusHashTable *base,
DBusHashTable *override)
{
DBusHashTable *combined_table;
combined_table = bus_selinux_id_table_new ();
if (combined_table == NULL)
return NULL;
#ifdef HAVE_SELINUX
if (!selinux_enabled)
return combined_table;
if (!bus_selinux_id_table_copy_over (combined_table, base))
{
_dbus_hash_table_unref (combined_table);
return NULL;
}
if (!bus_selinux_id_table_copy_over (combined_table, override))
{
_dbus_hash_table_unref (combined_table);
return NULL;
}
#endif /* HAVE_SELINUX */
return combined_table;
}
/**
* For debugging: Print out the current hash table of service SIDs.
*/
void
bus_selinux_id_table_print (DBusHashTable *service_table)
{
#ifdef DBUS_ENABLE_VERBOSE_MODE
#ifdef HAVE_SELINUX
DBusHashIter iter;
if (!selinux_enabled)
return;
_dbus_verbose ("Service SID Table:\n");
_dbus_hash_iter_init (service_table, &iter);
while (_dbus_hash_iter_next (&iter))
{
const char *key = _dbus_hash_iter_get_string_key (&iter);
security_id_t sid = _dbus_hash_iter_get_value (&iter);
_dbus_verbose ("The key is %s\n", key);
_dbus_verbose ("The context is %s\n", sid->ctx);
_dbus_verbose ("The refcount is %d\n", sid->refcnt);
}
#endif /* HAVE_SELINUX */
#endif /* DBUS_ENABLE_VERBOSE_MODE */
}
#ifdef DBUS_ENABLE_VERBOSE_MODE
#ifdef HAVE_SELINUX
/**
* Print out some AVC statistics.
*/
static void
bus_avc_print_stats (void)
{
struct avc_cache_stats cstats;
if (!selinux_enabled)
return;
_dbus_verbose ("AVC Statistics:\n");
avc_cache_stats (&cstats);
avc_av_stats ();
_dbus_verbose ("AVC Cache Statistics:\n");
_dbus_verbose ("Entry lookups: %d\n", cstats.entry_lookups);
_dbus_verbose ("Entry hits: %d\n", cstats.entry_hits);
_dbus_verbose ("Entry misses %d\n", cstats.entry_misses);
_dbus_verbose ("Entry discards: %d\n", cstats.entry_discards);
_dbus_verbose ("CAV lookups: %d\n", cstats.cav_lookups);
_dbus_verbose ("CAV hits: %d\n", cstats.cav_hits);
_dbus_verbose ("CAV probes: %d\n", cstats.cav_probes);
_dbus_verbose ("CAV misses: %d\n", cstats.cav_misses);
}
#endif /* HAVE_SELINUX */
#endif /* DBUS_ENABLE_VERBOSE_MODE */
/**
* Destroy the AVC before we terminate.
*/
void
bus_selinux_shutdown (void)
{
#ifdef HAVE_SELINUX
if (!selinux_enabled)
return;
sidput (bus_sid);
bus_sid = SECSID_WILD;
#ifdef DBUS_ENABLE_VERBOSE_MODE
bus_avc_print_stats ();
#endif /* DBUS_ENABLE_VERBOSE_MODE */
avc_destroy ();
#endif /* HAVE_SELINUX */
}
--- NEW FILE: selinux.h ---
/* selinux.h SELinux security check headers for D-BUS
*
* Author: Matthew Rickard <mjricka at epoch.ncsc.mil>
*
* Licensed under the Academic Free License version 2.0
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef BUS_SELINUX_H
#define BUS_SELINUX_H
#include <dbus/dbus-hash.h>
#include <dbus/dbus-connection.h>
#include "services.h"
dbus_bool_t bus_selinux_init (void);
void bus_selinux_shutdown (void);
void bus_selinux_id_ref (BusSELinuxID *sid);
void bus_selinux_id_unref (BusSELinuxID *sid);
DBusHashTable* bus_selinux_id_table_new (void);
BusSELinuxID* bus_selinux_id_table_lookup (DBusHashTable *service_table,
const DBusString *service_name);
dbus_bool_t bus_selinux_id_table_insert (DBusHashTable *service_table,
const char *service_name,
const char *service_context);
DBusHashTable* bus_selinux_id_table_union (DBusHashTable *base,
DBusHashTable *override);
void bus_selinux_id_table_print (DBusHashTable *service_table);
dbus_bool_t bus_selinux_allows_acquire_service (DBusConnection *connection,
BusSELinuxID *service_sid);
dbus_bool_t bus_selinux_allows_send (DBusConnection *sender,
DBusConnection *proposed_recipient);
BusSELinuxID* bus_selinux_init_connection_id (DBusConnection *connection,
DBusError *error);
#endif /* BUS_SELINUX_H */
Index: services.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/services.c,v
retrieving revision 1.21
retrieving revision 1.22
diff -u -d -r1.21 -r1.22
--- services.c 22 Jul 2004 07:07:01 -0000 1.21
+++ services.c 30 Jul 2004 05:59:34 -0000 1.22
@@ -31,6 +31,8 @@
#include "utils.h"
#include "activation.h"
#include "policy.h"
+#include "bus.h"
+#include "selinux.h"
struct BusService
{
@@ -51,6 +53,8 @@
DBusHashTable *service_hash;
DBusMemPool *service_pool;
+
+ DBusHashTable *service_sid_table;
};
BusRegistry*
@@ -75,6 +79,8 @@
if (registry->service_pool == NULL)
goto failed;
+ registry->service_sid_table = NULL;
+
return registry;
failed:
@@ -103,7 +109,9 @@
_dbus_hash_table_unref (registry->service_hash);
if (registry->service_pool)
_dbus_mem_pool_free (registry->service_pool);
-
+ if (registry->service_sid_table)
+ _dbus_hash_table_unref (registry->service_sid_table);
+
dbus_free (registry);
}
}
@@ -263,6 +271,7 @@
BusClientPolicy *policy;
BusService *service;
BusActivation *activation;
+ BusSELinuxID *sid;
retval = FALSE;
@@ -292,6 +301,24 @@
policy = bus_connection_get_policy (connection);
_dbus_assert (policy != NULL);
+ /* Note that if sid is #NULL then the bus's own context gets used
+ * in bus_connection_selinux_allows_acquire_service()
+ */
+ sid = bus_selinux_id_table_lookup (registry->service_sid_table,
+ service_name);
+
+ if (!bus_selinux_allows_acquire_service (connection, sid))
+ {
+ dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
+ "Connection \"%s\" is not allowed to own the service \"%s\" due "
+ "to SELinux policy",
+ bus_connection_is_active (connection) ?
+ bus_connection_get_name (connection) :
+ "(inactive)",
+ service_name);
+ goto out;
+ }
+
if (!bus_client_policy_check_can_own (policy, connection,
service_name))
{
@@ -387,6 +414,19 @@
return retval;
}
+void
+bus_registry_set_service_sid_table (BusRegistry *registry,
+ DBusHashTable *table)
+{
+ _dbus_assert (registry->service_sid_table != table);
+
+ if (registry->service_sid_table)
+ _dbus_hash_table_unref (registry->service_sid_table);
+
+ registry->service_sid_table = table;
+ _dbus_hash_table_ref (table);
+}
+
static void
bus_service_unlink_owner (BusService *service,
DBusConnection *owner)
Index: services.h
===================================================================
RCS file: /cvs/dbus/dbus/bus/services.h,v
retrieving revision 1.10
retrieving revision 1.11
diff -u -d -r1.10 -r1.11
--- services.h 2 Dec 2003 10:44:21 -0000 1.10
+++ services.h 30 Jul 2004 05:59:34 -0000 1.11
@@ -26,6 +26,7 @@
#include <dbus/dbus.h>
#include <dbus/dbus-string.h>
+#include <dbus/dbus-hash.h>
#include "connection.h"
#include "bus.h"
@@ -55,6 +56,8 @@
dbus_uint32_t *result,
BusTransaction *transaction,
DBusError *error);
+void bus_registry_set_service_sid_table (BusRegistry *registry,
+ DBusHashTable *table);
BusService* bus_service_ref (BusService *service);
void bus_service_unref (BusService *service);
Index: test-main.c
===================================================================
RCS file: /cvs/dbus/dbus/bus/test-main.c,v
retrieving revision 1.16
retrieving revision 1.17
diff -u -d -r1.16 -r1.17
--- test-main.c 12 Mar 2004 14:07:16 -0000 1.16
+++ test-main.c 30 Jul 2004 05:59:34 -0000 1.17
@@ -27,6 +27,7 @@
#include <dbus/dbus-string.h>
#include <dbus/dbus-sysdeps.h>
#include <dbus/dbus-internals.h>
+#include "selinux.h"
#ifdef DBUS_BUILD_TESTS
static void
@@ -69,6 +70,9 @@
return 1;
}
+ if (!bus_selinux_init ())
+ die ("could not init selinux support");
+
_dbus_string_init_const (&test_data_dir, dir);
#if 0
@@ -119,6 +123,8 @@
check_memleaks (argv[0]);
printf ("%s: Success\n", argv[0]);
+
+ bus_selinux_shutdown ();
return 0;
#else /* DBUS_BUILD_TESTS */
- Previous message: dbus ChangeLog,1.539,1.540 configure.in,1.91,1.92
- Next message: dbus/dbus dbus-connection.c, 1.82, 1.83 dbus-connection.h, 1.30,
1.31 dbus-transport-protected.h, 1.11,
1.12 dbus-transport-unix.c, 1.38, 1.39 dbus-transport.c, 1.39,
1.40 dbus-transport.h, 1.15, 1.16
- Messages sorted by:
[ date ]
[ thread ]
[ subject ]
[ author ]
More information about the dbus-commit
mailing list