Consistently include <config.h> in all C source files and never in header files.
[platform/upstream/dbus.git] / bus / connection.c
index 5121658..b2d6aa5 100644 (file)
@@ -1,9 +1,9 @@
-/* -*- mode: C; c-file-style: "gnu" -*- */
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 /* connection.c  Client connections
  *
  * Copyright (C) 2003  Red Hat, Inc.
  *
- * Licensed under the Academic Free License version 1.2
+ * Licensed under the Academic Free License version 2.1
  * 
  * 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
  * 
  * 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
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
+
+#include <config.h>
 #include "connection.h"
 #include "dispatch.h"
 #include "policy.h"
 #include "services.h"
 #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>
 
+/* Trim executed commands to this length; we want to keep logs readable */
+#define MAX_LOG_COMMAND_LEN 50
+
 static void bus_connection_remove_transactions (DBusConnection *connection);
 
+typedef struct
+{
+  BusExpireItem expire_item;
+
+  DBusConnection *will_get_reply;
+  DBusConnection *will_send_reply;
+
+  dbus_uint32_t reply_serial;
+  
+} BusPendingReply;
+
 struct BusConnections
 {
   int refcount;
@@ -41,6 +60,8 @@ struct BusConnections
   BusContext *context;
   DBusHashTable *completed_by_user; /**< Number of completed connections for each UID */
   DBusTimeout *expire_timeout; /**< Timeout for expiring incomplete connections. */
+  int stamp;                   /**< Incrementing number */
+  BusExpireList *pending_replies; /**< List of pending replies */
 };
 
 static dbus_int32_t connection_data_slot = -1;
@@ -52,16 +73,29 @@ typedef struct
   DBusConnection *connection;
   DBusList *services_owned;
   int n_services_owned;
+  DBusList *match_rules;
+  int n_match_rules;
   char *name;
   DBusList *transaction_messages; /**< Stuff we need to send as part of a transaction */
   DBusMessage *oom_message;
   DBusPreallocatedSend *oom_preallocated;
   BusClientPolicy *policy;
 
+  char *cached_loginfo_string;
+  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 */
 } BusConnectionData;
 
+static dbus_bool_t bus_pending_reply_expired (BusExpireList *list,
+                                              DBusList      *link,
+                                              void          *data);
+
+static void bus_connection_drop_pending_replies (BusConnections  *connections,
+                                                 DBusConnection  *connection);
+
 static dbus_bool_t expire_incomplete_timeout (void *data);
 
 #define BUS_CONNECTION_DATA(connection) (dbus_connection_get_data ((connection), connection_data_slot))
@@ -140,19 +174,27 @@ bus_connection_disconnected (DBusConnection *connection)
 {
   BusConnectionData *d;
   BusService *service;
-
+  BusMatchmaker *matchmaker;
+  
   d = BUS_CONNECTION_DATA (connection);
   _dbus_assert (d != NULL);
 
   _dbus_verbose ("%s disconnected, dropping all service ownership and releasing\n",
                  d->name ? d->name : "(inactive)");
+
+  /* Delete our match rules */
+  if (d->n_match_rules > 0)
+    {
+      matchmaker = bus_context_get_matchmaker (d->connections->context);
+      bus_matchmaker_disconnected (matchmaker, connection);
+    }
   
-  /* Drop any service ownership. FIXME Unfortunately, this requires
+  /* Drop any service ownership. Unfortunately, this requires
    * memory allocation and there doesn't seem to be a good way to
    * handle it other than sleeping; we can't "fail" the operation of
    * disconnecting a client, and preallocating a broadcast "service is
    * now gone" message for every client-service pair seems kind of
-   * involved. Probably we need to do that though.
+   * involved.
    */
   while ((service = _dbus_list_get_last (&d->services_owned)))
     {
@@ -163,12 +205,8 @@ bus_connection_disconnected (DBusConnection *connection)
       
       dbus_error_init (&error);
         
-      transaction = NULL;
-      while (transaction == NULL)
-        {
-          transaction = bus_transaction_new (d->connections->context);
-          _dbus_wait_for_memory ();
-        }
+      while ((transaction = bus_transaction_new (d->connections->context)) == NULL)
+        _dbus_wait_for_memory ();
         
       if (!bus_service_remove_owner (service, connection,
                                      transaction, &error))
@@ -210,7 +248,9 @@ bus_connection_disconnected (DBusConnection *connection)
   
   dbus_connection_set_unix_user_function (connection,
                                           NULL, NULL, NULL);
-
+  dbus_connection_set_windows_user_function (connection,
+                                             NULL, NULL, NULL);
+  
   dbus_connection_set_dispatch_status_function (connection,
                                                 NULL, NULL, NULL);
   
@@ -243,12 +283,14 @@ bus_connection_disconnected (DBusConnection *connection)
       _dbus_assert (d->connections->n_incomplete >= 0);
       _dbus_assert (d->connections->n_completed >= 0);
     }
+
+  bus_connection_drop_pending_replies (d->connections, connection);
   
   /* frees "d" as side effect */
   dbus_connection_set_data (connection,
                             connection_data_slot,
                             NULL, NULL);
-
+  
   dbus_connection_unref (connection);
 }
 
@@ -334,9 +376,9 @@ dispatch_status_function (DBusConnection    *connection,
 }
 
 static dbus_bool_t
-allow_user_function (DBusConnection *connection,
-                     unsigned long   uid,
-                     void           *data)
+allow_unix_user_function (DBusConnection *connection,
+                          unsigned long   uid,
+                          void           *data)
 {
   BusConnectionData *d;
     
@@ -344,7 +386,7 @@ allow_user_function (DBusConnection *connection,
 
   _dbus_assert (d != NULL);
   
-  return bus_context_allow_user (d->connections->context, uid);
+  return bus_context_allow_unix_user (d->connections->context, uid);
 }
 
 static void
@@ -366,6 +408,11 @@ free_connection_data (void *data)
 
   if (d->policy)
     bus_client_policy_unref (d->policy);
+
+  if (d->selinux_id)
+    bus_selinux_id_unref (d->selinux_id);
+  
+  dbus_free (d->cached_loginfo_string);
   
   dbus_free (d->name);
   
@@ -405,16 +452,25 @@ bus_connections_new (BusContext *context)
 
   _dbus_timeout_set_enabled (connections->expire_timeout, FALSE);
 
+  connections->pending_replies = bus_expire_list_new (bus_context_get_loop (context),
+                                                      bus_context_get_reply_timeout (context),
+                                                      bus_pending_reply_expired,
+                                                      connections);
+  if (connections->pending_replies == NULL)
+    goto failed_4;
+  
   if (!_dbus_loop_add_timeout (bus_context_get_loop (context),
                                connections->expire_timeout,
                                call_timeout_callback, NULL, NULL))
-    goto failed_4;
+    goto failed_5;
   
   connections->refcount = 1;
   connections->context = context;
   
   return connections;
 
+ failed_5:
+  bus_expire_list_free (connections->pending_replies);
  failed_4:
   _dbus_timeout_unref (connections->expire_timeout);
  failed_3:
@@ -427,11 +483,13 @@ bus_connections_new (BusContext *context)
   return NULL;
 }
 
-void
+BusConnections *
 bus_connections_ref (BusConnections *connections)
 {
   _dbus_assert (connections->refcount > 0);
   connections->refcount += 1;
+
+  return connections;
 }
 
 void
@@ -449,7 +507,7 @@ bus_connections_unref (BusConnections *connections)
           connection = connections->incomplete->data;
 
           dbus_connection_ref (connection);
-          dbus_connection_disconnect (connection);
+          dbus_connection_close (connection);
           bus_connection_disconnected (connection);
           dbus_connection_unref (connection);
         }
@@ -464,13 +522,15 @@ bus_connections_unref (BusConnections *connections)
           connection = connections->completed->data;
 
           dbus_connection_ref (connection);
-          dbus_connection_disconnect (connection);
+          dbus_connection_close (connection);
           bus_connection_disconnected (connection);
-          dbus_connection_unref (connection);          
+          dbus_connection_unref (connection);
         }
 
       _dbus_assert (connections->n_completed == 0);
 
+      bus_expire_list_free (connections->pending_replies);
+      
       _dbus_loop_remove_timeout (bus_context_get_loop (connections->context),
                                  connections->expire_timeout,
                                  call_timeout_callback, NULL);
@@ -485,12 +545,72 @@ bus_connections_unref (BusConnections *connections)
     }
 }
 
+/* Used for logging */
+static dbus_bool_t
+cache_peer_loginfo_string (BusConnectionData *d, 
+                           DBusConnection    *connection)
+{
+  DBusString loginfo_buf;
+  unsigned long uid;
+  unsigned long pid;
+  char *windows_sid;
+  dbus_bool_t prev_added;
+
+  if (!_dbus_string_init (&loginfo_buf))
+    return FALSE;
+  
+  prev_added = FALSE;
+  if (dbus_connection_get_unix_user (connection, &uid))
+    {
+      if (!_dbus_string_append_printf (&loginfo_buf, "uid=%ld", uid))
+        goto oom;
+      else
+        prev_added = TRUE;
+    }
+
+  if (dbus_connection_get_unix_process_id (connection, &pid))
+    {
+      if (prev_added)
+        {
+          if (!_dbus_string_append_byte (&loginfo_buf, ' '))
+            goto oom;
+        }
+      if (!_dbus_string_append_printf (&loginfo_buf, "pid=%ld comm=\"", pid))
+        goto oom;
+      /* Ignore errors here; we may not have permissions to read the
+       * proc file. */
+      _dbus_command_for_pid (pid, &loginfo_buf, MAX_LOG_COMMAND_LEN, NULL);
+      if (!_dbus_string_append_byte (&loginfo_buf, '"'))
+        goto oom;
+    }
+
+  if (dbus_connection_get_windows_user (connection, &windows_sid))
+    {
+      if (!_dbus_string_append_printf (&loginfo_buf, "sid=\"%s\" ", windows_sid))
+        goto oom;
+      dbus_free (windows_sid);
+    }
+
+  if (!_dbus_string_steal_data (&loginfo_buf, &(d->cached_loginfo_string)))
+    goto oom;
+
+  _dbus_string_free (&loginfo_buf); 
+
+  return TRUE;
+oom:
+   _dbus_string_free (&loginfo_buf);
+   return FALSE;
+}
+
 dbus_bool_t
 bus_connections_setup_connection (BusConnections *connections,
                                   DBusConnection *connection)
 {
+
   BusConnectionData *d;
   dbus_bool_t retval;
+  DBusError error;
+
   
   d = dbus_new0 (BusConnectionData, 1);
   
@@ -513,8 +633,24 @@ bus_connections_setup_connection (BusConnections *connections,
       return FALSE;
     }
 
-  retval = FALSE;
+  dbus_connection_set_route_peer_messages (connection, TRUE);
   
+  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,
                                             remove_connection_watch,
@@ -529,9 +665,14 @@ bus_connections_setup_connection (BusConnections *connections,
                                               NULL,
                                               connection, NULL))
     goto out;
-  
+
+  /* For now we don't need to set a Windows user function because
+   * there are no policies in the config file controlling what
+   * Windows users can connect. The default 'same user that owns the
+   * bus can connect' behavior of DBusConnection is fine on Windows.
+   */
   dbus_connection_set_unix_user_function (connection,
-                                          allow_user_function,
+                                          allow_unix_user_function,
                                           NULL, NULL);
 
   dbus_connection_set_dispatch_status_function (connection,
@@ -584,14 +725,18 @@ bus_connections_setup_connection (BusConnections *connections,
        * completing authentication. But random may or may not really
        * help with that, a more elaborate solution might be required.
        */
-      dbus_connection_disconnect (connections->incomplete->data);
+      dbus_connection_close (connections->incomplete->data);
     }
   
   retval = TRUE;
 
  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,
@@ -607,6 +752,9 @@ bus_connections_setup_connection (BusConnections *connections,
       dbus_connection_set_unix_user_function (connection,
                                               NULL, NULL, NULL);
 
+      dbus_connection_set_windows_user_function (connection,
+                                                 NULL, NULL, NULL);
+      
       dbus_connection_set_dispatch_status_function (connection,
                                                     NULL, NULL, NULL);
 
@@ -659,13 +807,14 @@ bus_connections_expire_incomplete (BusConnections *connections)
       
           _dbus_assert (d != NULL);
       
-          elapsed = ((double) tv_sec - (double) d->connection_tv_sec) * 1000.0 +
-            ((double) tv_usec - (double) d->connection_tv_usec) / 1000.0;
+          elapsed = ELAPSED_MILLISECONDS_SINCE (d->connection_tv_sec,
+                                                d->connection_tv_usec,
+                                                tv_sec, tv_usec);
 
           if (elapsed >= (double) auth_timeout)
             {
               _dbus_verbose ("Timing out authentication for connection %p\n", connection);
-              dbus_connection_disconnect (connection);
+              dbus_connection_close (connection);
             }
           else
             {
@@ -680,25 +829,9 @@ bus_connections_expire_incomplete (BusConnections *connections)
           link = next;
         }
     }
-  
-  if (next_interval >= 0)
-    {
-      _dbus_timeout_set_interval (connections->expire_timeout,
-                                  next_interval);
-      _dbus_timeout_set_enabled (connections->expire_timeout, TRUE);
-
-      _dbus_verbose ("Enabled incomplete connections timeout with interval %d, %d incomplete connections\n",
-                     next_interval, connections->n_incomplete);
-    }
-  else if (dbus_timeout_get_enabled (connections->expire_timeout))
-    {
-      _dbus_timeout_set_enabled (connections->expire_timeout, FALSE);
 
-      _dbus_verbose ("Disabled incomplete connections timeout, %d incomplete connections\n",
-                     connections->n_incomplete);
-    }
-  else
-    _dbus_verbose ("No need to disable incomplete connections timeout\n");
+  bus_expire_timeout_set_interval (connections->expire_timeout,
+                                   next_interval);
 }
 
 static dbus_bool_t
@@ -715,31 +848,25 @@ expire_incomplete_timeout (void *data)
 }
 
 dbus_bool_t
-bus_connection_get_groups  (DBusConnection   *connection,
-                            unsigned long   **groups,
-                            int              *n_groups,
-                            DBusError        *error)
+bus_connection_get_unix_groups  (DBusConnection   *connection,
+                                 unsigned long   **groups,
+                                 int              *n_groups,
+                                 DBusError        *error)
 {
   BusConnectionData *d;
   unsigned long uid;
-  DBusUserDatabase *user_database;
   
   d = BUS_CONNECTION_DATA (connection);
 
   _dbus_assert (d != NULL);
 
-  user_database = bus_context_get_user_database (d->connections->context);
-  
   *groups = NULL;
   *n_groups = 0;
 
   if (dbus_connection_get_unix_user (connection, &uid))
     {
-      if (!_dbus_user_database_get_groups (user_database,
-                                           uid, groups, n_groups,
-                                           error))
+      if (!_dbus_unix_groups_from_uid (uid, groups, n_groups))
         {
-          _DBUS_ASSERT_ERROR_IS_SET (error);
           _dbus_verbose ("Did not get any groups for UID %lu\n",
                          uid);
           return FALSE;
@@ -756,15 +883,15 @@ bus_connection_get_groups  (DBusConnection   *connection,
 }
 
 dbus_bool_t
-bus_connection_is_in_group (DBusConnection *connection,
-                            unsigned long   gid)
+bus_connection_is_in_unix_group (DBusConnection *connection,
+                                 unsigned long   gid)
 {
   int i;
   unsigned long *group_ids;
   int n_group_ids;
 
-  if (!bus_connection_get_groups (connection, &group_ids, &n_group_ids,
-                                  NULL))
+  if (!bus_connection_get_unix_groups (connection, &group_ids, &n_group_ids,
+                                       NULL))
     return FALSE;
 
   i = 0;
@@ -782,6 +909,18 @@ bus_connection_is_in_group (DBusConnection *connection,
   return FALSE;
 }
 
+const char *
+bus_connection_get_loginfo (DBusConnection        *connection)
+{
+  BusConnectionData *d;
+    
+  d = BUS_CONNECTION_DATA (connection);
+
+  if (!bus_connection_is_active (connection))
+    return "inactive";
+  return d->cached_loginfo_string;  
+}
+
 BusClientPolicy*
 bus_connection_get_policy (DBusConnection *connection)
 {
@@ -881,6 +1020,40 @@ bus_connections_get_context (BusConnections *connections)
   return connections->context;
 }
 
+/*
+ * This is used to avoid covering the same connection twice when
+ * traversing connections. Note that it assumes we will
+ * bus_connection_mark_stamp() each connection at least once per
+ * INT_MAX increments of the global stamp, or wraparound would break
+ * things.
+ */
+void
+bus_connections_increment_stamp (BusConnections *connections)
+{
+  connections->stamp += 1;
+}
+
+/* Mark connection with current stamp, return TRUE if it
+ * didn't already have that stamp
+ */
+dbus_bool_t
+bus_connection_mark_stamp (DBusConnection *connection)
+{
+  BusConnectionData *d;
+  
+  d = BUS_CONNECTION_DATA (connection);
+  
+  _dbus_assert (d != NULL);
+
+  if (d->stamp == d->connections->stamp)
+    return FALSE;
+  else
+    {
+      d->stamp = d->connections->stamp;
+      return TRUE;
+    }
+}
+
 BusContext*
 bus_connection_get_context (DBusConnection *connection)
 {
@@ -929,6 +1102,30 @@ bus_connection_get_activation (DBusConnection *connection)
   return bus_context_get_activation (d->connections->context);
 }
 
+BusMatchmaker*
+bus_connection_get_matchmaker (DBusConnection *connection)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+
+  _dbus_assert (d != NULL);
+
+  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.
  *
@@ -963,18 +1160,18 @@ bus_connection_preallocate_oom_error (DBusConnection *connection)
   if (preallocated == NULL)
     return FALSE;
 
-  /* d->name may be NULL, but that is OK */
-  message = dbus_message_new (DBUS_ERROR_NO_MEMORY,
-                              d->name);
+  message = dbus_message_new (DBUS_MESSAGE_TYPE_ERROR);
+
   if (message == NULL)
     {
       dbus_connection_free_preallocated_send (connection, preallocated);
       return FALSE;
     }
 
-  dbus_message_set_is_error (message, TRUE);
-
-  if (!dbus_message_set_sender (message,
+  /* d->name may be NULL, but that is OK */
+  if (!dbus_message_set_error_name (message, DBUS_ERROR_NO_MEMORY) ||
+      !dbus_message_set_destination (message, d->name) ||
+      !dbus_message_set_sender (message,
                                 DBUS_SERVICE_DBUS))
     {
       dbus_connection_free_preallocated_send (connection, preallocated);
@@ -1025,6 +1222,62 @@ bus_connection_send_oom_error (DBusConnection *connection,
 }
 
 void
+bus_connection_add_match_rule_link (DBusConnection *connection,
+                                    DBusList       *link)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert (d != NULL);
+
+  _dbus_list_append_link (&d->match_rules, link);
+
+  d->n_match_rules += 1;
+}
+
+dbus_bool_t
+bus_connection_add_match_rule (DBusConnection *connection,
+                               BusMatchRule   *rule)
+{
+    DBusList *link;
+
+  link = _dbus_list_alloc_link (rule);
+
+  if (link == NULL)
+    return FALSE;
+
+  bus_connection_add_match_rule_link (connection, link);
+
+  return TRUE;
+}
+
+void
+bus_connection_remove_match_rule (DBusConnection *connection,
+                                  BusMatchRule   *rule)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert (d != NULL);
+
+  _dbus_list_remove_last (&d->match_rules, rule);
+
+  d->n_match_rules -= 1;
+  _dbus_assert (d->n_match_rules >= 0);
+}
+
+int
+bus_connection_get_n_match_rules (DBusConnection *connection)
+{
+  BusConnectionData *d;
+
+  d = BUS_CONNECTION_DATA (connection);
+  _dbus_assert (d != NULL);
+  
+  return d->n_match_rules;
+}
+
+void
 bus_connection_add_owned_service_link (DBusConnection *connection,
                                        DBusList       *link)
 {
@@ -1092,6 +1345,8 @@ bus_connection_complete (DBusConnection   *connection,
   _dbus_assert (d != NULL);
   _dbus_assert (d->name == NULL);
   _dbus_assert (d->policy == NULL);
+
+  _dbus_assert (!bus_connection_is_active (connection));
   
   if (!_dbus_string_copy_data (name, &d->name))
     {
@@ -1126,14 +1381,15 @@ bus_connection_complete (DBusConnection   *connection,
     {
       if (!adjust_connections_for_uid (d->connections,
                                        uid, 1))
-        {
-          BUS_SET_OOM (error);
-          dbus_free (d->name);
-          d->name = NULL;
-          return FALSE;
-        }
+        goto fail;
     }
-  
+
+  /* Create and cache a string which holds information about the 
+   * peer process; used for logging purposes.
+   */
+  if (!cache_peer_loginfo_string (d, connection))
+    goto fail;
+
   /* Now the connection is active, move it between lists */
   _dbus_list_unlink (&d->connections->incomplete,
                      d->link_in_connection_list);
@@ -1147,8 +1403,18 @@ bus_connection_complete (DBusConnection   *connection,
 
   /* See if we can remove the timeout */
   bus_connections_expire_incomplete (d->connections);
+
+  _dbus_assert (bus_connection_is_active (connection));
   
   return TRUE;
+fail:
+  BUS_SET_OOM (error);
+  dbus_free (d->name);
+  d->name = NULL;
+  if (d->policy)
+    bus_client_policy_unref (d->policy);
+  d->policy = NULL;
+  return FALSE;
 }
 
 const char *
@@ -1202,6 +1468,421 @@ bus_connections_check_limits (BusConnections  *connections,
   return TRUE;
 }
 
+static void
+bus_pending_reply_free (BusPendingReply *pending)
+{
+  _dbus_verbose ("Freeing pending reply %p, replier %p receiver %p serial %u\n",
+                 pending,
+                 pending->will_send_reply,
+                 pending->will_get_reply,
+                 pending->reply_serial);
+
+  dbus_free (pending);
+}
+
+static dbus_bool_t
+bus_pending_reply_send_no_reply (BusConnections  *connections,
+                                 BusTransaction  *transaction,
+                                 BusPendingReply *pending)
+{
+  DBusMessage *message;
+  DBusMessageIter iter;
+  dbus_bool_t retval;
+  const char *errmsg;
+
+  retval = FALSE;
+  
+  message = dbus_message_new (DBUS_MESSAGE_TYPE_ERROR);
+  if (message == NULL)
+    return FALSE;
+  
+  dbus_message_set_no_reply (message, TRUE);
+  
+  if (!dbus_message_set_reply_serial (message,
+                                      pending->reply_serial))
+    goto out;
+
+  if (!dbus_message_set_error_name (message,
+                                    DBUS_ERROR_NO_REPLY))
+    goto out;
+
+  errmsg = "Message did not receive a reply (timeout by message bus)";
+  dbus_message_iter_init_append (message, &iter);
+  if (!dbus_message_iter_append_basic (&iter, DBUS_TYPE_STRING, &errmsg))
+    goto out;
+    
+  if (!bus_transaction_send_from_driver (transaction, pending->will_get_reply,
+                                         message))
+    goto out;
+
+  retval = TRUE;
+
+ out:
+  dbus_message_unref (message);
+  return retval;
+}
+
+static dbus_bool_t
+bus_pending_reply_expired (BusExpireList *list,
+                           DBusList      *link,
+                           void          *data)
+{
+  BusPendingReply *pending = link->data;
+  BusConnections *connections = data;
+  BusTransaction *transaction;
+  
+  /* No reply is forthcoming. So nuke it if we can. If not,
+   * leave it in the list to try expiring again later when we
+   * get more memory.
+   */
+
+  _dbus_verbose ("Expiring pending reply %p, replier %p receiver %p serial %u\n",
+                 pending,
+                 pending->will_send_reply,
+                 pending->will_get_reply,
+                 pending->reply_serial);
+  
+  transaction = bus_transaction_new (connections->context);
+  if (transaction == NULL)
+    return FALSE;
+  
+  if (!bus_pending_reply_send_no_reply (connections,
+                                        transaction,
+                                        pending))
+    {
+      bus_transaction_cancel_and_free (transaction);
+      return FALSE;
+    }
+
+  bus_expire_list_remove_link (connections->pending_replies, link);
+
+  bus_pending_reply_free (pending);
+  bus_transaction_execute_and_free (transaction);
+
+  return TRUE;
+}
+
+static void
+bus_connection_drop_pending_replies (BusConnections  *connections,
+                                     DBusConnection  *connection)
+{
+  /* The DBusConnection is almost 100% finalized here, so you can't
+   * do anything with it except check for pointer equality
+   */
+  DBusList *link;
+
+  _dbus_verbose ("Dropping pending replies that involve connection %p\n",
+                 connection);
+  
+  link = bus_expire_list_get_first_link (connections->pending_replies);
+  while (link != NULL)
+    {
+      DBusList *next;
+      BusPendingReply *pending;
+
+      next = bus_expire_list_get_next_link (connections->pending_replies,
+                                            link);
+      pending = link->data;
+
+      if (pending->will_get_reply == connection)
+        {
+          /* We don't need to track this pending reply anymore */
+
+          _dbus_verbose ("Dropping pending reply %p, replier %p receiver %p serial %u\n",
+                         pending,
+                         pending->will_send_reply,
+                         pending->will_get_reply,
+                         pending->reply_serial);
+          
+          bus_expire_list_remove_link (connections->pending_replies,
+                                       link);
+          bus_pending_reply_free (pending);
+        }
+      else if (pending->will_send_reply == connection)
+        {
+          /* The reply isn't going to be sent, so set things
+           * up so it will be expired right away
+           */
+          _dbus_verbose ("Will expire pending reply %p, replier %p receiver %p serial %u\n",
+                         pending,
+                         pending->will_send_reply,
+                         pending->will_get_reply,
+                         pending->reply_serial);
+          
+          pending->will_send_reply = NULL;
+          pending->expire_item.added_tv_sec = 0;
+          pending->expire_item.added_tv_usec = 0;
+
+          bus_expire_list_recheck_immediately (connections->pending_replies);
+        }
+      
+      link = next;
+    }
+}
+
+
+typedef struct
+{
+  BusPendingReply *pending;
+  BusConnections  *connections;
+} CancelPendingReplyData;
+
+static void
+cancel_pending_reply (void *data)
+{
+  CancelPendingReplyData *d = data;
+
+  _dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d);
+  
+  if (!bus_expire_list_remove (d->connections->pending_replies,
+                               &d->pending->expire_item))
+    _dbus_assert_not_reached ("pending reply did not exist to be cancelled");
+
+  bus_pending_reply_free (d->pending); /* since it's been cancelled */
+}
+
+static void
+cancel_pending_reply_data_free (void *data)
+{
+  CancelPendingReplyData *d = data;
+
+  _dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d);
+  
+  /* d->pending should be either freed or still
+   * in the list of pending replies (owned by someone
+   * else)
+   */
+  
+  dbus_free (d);
+}
+
+/*
+ * Record that a reply is allowed; return TRUE on success.
+ */
+dbus_bool_t
+bus_connections_expect_reply (BusConnections  *connections,
+                              BusTransaction  *transaction,
+                              DBusConnection  *will_get_reply,
+                              DBusConnection  *will_send_reply,
+                              DBusMessage     *reply_to_this,
+                              DBusError       *error)
+{
+  BusPendingReply *pending;
+  dbus_uint32_t reply_serial;
+  DBusList *link;
+  CancelPendingReplyData *cprd;
+  int count;
+
+  _dbus_assert (will_get_reply != NULL);
+  _dbus_assert (will_send_reply != NULL);
+  _dbus_assert (reply_to_this != NULL);
+  
+  if (dbus_message_get_no_reply (reply_to_this))
+    return TRUE; /* we won't allow a reply, since client doesn't care for one. */
+  
+  reply_serial = dbus_message_get_serial (reply_to_this);
+
+  link = bus_expire_list_get_first_link (connections->pending_replies);
+  count = 0;
+  while (link != NULL)
+    {
+      pending = link->data;
+
+      if (pending->reply_serial == reply_serial &&
+          pending->will_get_reply == will_get_reply &&
+          pending->will_send_reply == will_send_reply)
+        {
+          dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
+                          "Message has the same reply serial as a currently-outstanding existing method call");
+          return FALSE;
+        }
+      
+      link = bus_expire_list_get_next_link (connections->pending_replies,
+                                            link);
+      if (pending->will_get_reply == will_get_reply)
+        ++count;
+    }
+  
+  if (count >=
+      bus_context_get_max_replies_per_connection (connections->context))
+    {
+      dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
+                     "The maximum number of pending replies per connection has been reached");
+      return FALSE;
+    }
+
+  pending = dbus_new0 (BusPendingReply, 1);
+  if (pending == NULL)
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+  /* so we can see a not-yet-added pending reply */
+  pending->expire_item.added_tv_sec = 1;
+  pending->expire_item.added_tv_usec = 1;
+#endif
+
+  pending->will_get_reply = will_get_reply;
+  pending->will_send_reply = will_send_reply;
+  pending->reply_serial = reply_serial;
+  
+  cprd = dbus_new0 (CancelPendingReplyData, 1);
+  if (cprd == NULL)
+    {
+      BUS_SET_OOM (error);
+      bus_pending_reply_free (pending);
+      return FALSE;
+    }
+  
+  if (!bus_expire_list_add (connections->pending_replies,
+                            &pending->expire_item))
+    {
+      BUS_SET_OOM (error);
+      dbus_free (cprd);
+      bus_pending_reply_free (pending);
+      return FALSE;
+    }
+
+  if (!bus_transaction_add_cancel_hook (transaction,
+                                        cancel_pending_reply,
+                                        cprd,
+                                        cancel_pending_reply_data_free))
+    {
+      BUS_SET_OOM (error);
+      bus_expire_list_remove (connections->pending_replies, &pending->expire_item);
+      dbus_free (cprd);
+      bus_pending_reply_free (pending);
+      return FALSE;
+    }
+                                        
+  cprd->pending = pending;
+  cprd->connections = connections;
+  
+  _dbus_get_current_time (&pending->expire_item.added_tv_sec,
+                          &pending->expire_item.added_tv_usec);
+
+  _dbus_verbose ("Added pending reply %p, replier %p receiver %p serial %u\n",
+                 pending,
+                 pending->will_send_reply,
+                 pending->will_get_reply,
+                 pending->reply_serial);
+  
+  return TRUE;
+}
+
+typedef struct
+{
+  DBusList        *link;
+  BusConnections  *connections;
+} CheckPendingReplyData;
+
+static void
+cancel_check_pending_reply (void *data)
+{
+  CheckPendingReplyData *d = data;
+
+  _dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d);
+
+  bus_expire_list_add_link (d->connections->pending_replies,
+                            d->link);
+  d->link = NULL;
+}
+
+static void
+check_pending_reply_data_free (void *data)
+{
+  CheckPendingReplyData *d = data;
+
+  _dbus_verbose ("%s: d = %p\n", _DBUS_FUNCTION_NAME, d);
+  
+  if (d->link != NULL)
+    {
+      BusPendingReply *pending = d->link->data;
+      
+      _dbus_assert (!bus_expire_list_contains_item (d->connections->pending_replies,
+                                                    &pending->expire_item));
+      
+      bus_pending_reply_free (pending);
+      _dbus_list_free_link (d->link);
+    }
+  
+  dbus_free (d);
+}
+
+/*
+ * Check whether a reply is allowed, remove BusPendingReply
+ * if so, return TRUE if so.
+ */
+dbus_bool_t
+bus_connections_check_reply (BusConnections *connections,
+                             BusTransaction *transaction,
+                             DBusConnection *sending_reply,
+                             DBusConnection *receiving_reply,
+                             DBusMessage    *reply,
+                             DBusError      *error)
+{
+  CheckPendingReplyData *cprd;
+  DBusList *link;
+  dbus_uint32_t reply_serial;
+  
+  _dbus_assert (sending_reply != NULL);
+  _dbus_assert (receiving_reply != NULL);
+
+  reply_serial = dbus_message_get_reply_serial (reply);
+
+  link = bus_expire_list_get_first_link (connections->pending_replies);
+  while (link != NULL)
+    {
+      BusPendingReply *pending = link->data;
+
+      if (pending->reply_serial == reply_serial &&
+          pending->will_get_reply == receiving_reply &&
+          pending->will_send_reply == sending_reply)
+        {
+          _dbus_verbose ("Found pending reply with serial %u\n", reply_serial);
+          break;
+        }
+      
+      link = bus_expire_list_get_next_link (connections->pending_replies,
+                                            link);
+    }
+
+  if (link == NULL)
+    {
+      _dbus_verbose ("No pending reply expected\n");
+
+      return FALSE;
+    }
+
+  cprd = dbus_new0 (CheckPendingReplyData, 1);
+  if (cprd == NULL)
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+  
+  if (!bus_transaction_add_cancel_hook (transaction,
+                                        cancel_check_pending_reply,
+                                        cprd,
+                                        check_pending_reply_data_free))
+    {
+      BUS_SET_OOM (error);
+      dbus_free (cprd);
+      return FALSE;
+    }
+
+  cprd->link = link;
+  cprd->connections = connections;
+  
+  bus_expire_list_unlink (connections->pending_replies,
+                          link);
+  
+  _dbus_assert (!bus_expire_list_contains_item (connections->pending_replies, link->data));
+
+  return TRUE;
+}
 
 /*
  * Transactions
@@ -1312,17 +1993,33 @@ bus_transaction_send_from_driver (BusTransaction *transaction,
    * to check security policy since it was not done in
    * dispatch.c
    */
-  _dbus_verbose ("Sending %s from driver\n",
-                 dbus_message_get_name (message));
-  
+  _dbus_verbose ("Sending %s %s %s from driver\n",
+                 dbus_message_get_interface (message) ?
+                 dbus_message_get_interface (message) : "(no interface)",
+                 dbus_message_get_member (message) ?
+                 dbus_message_get_member (message) : "(no member)",
+                 dbus_message_get_error_name (message) ?
+                 dbus_message_get_error_name (message) : "(no error name)");
+                 
   if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS))
     return FALSE;
 
+  if (bus_connection_is_active (connection))
+    {
+      if (!dbus_message_set_destination (message,
+                                         bus_connection_get_name (connection)))
+        return FALSE;
+    }
+  
+  /* bus driver never wants a reply */
+  dbus_message_set_no_reply (message, TRUE);
+  
   /* If security policy doesn't allow the message, we silently
    * eat it; the driver doesn't care about getting a reply.
    */
   if (!bus_context_check_security_policy (bus_transaction_get_context (transaction),
-                                          NULL, connection, message, NULL))
+                                          transaction,
+                                          NULL, connection, connection, message, NULL))
     return TRUE;
 
   return bus_transaction_send (transaction, connection, message);
@@ -1337,11 +2034,16 @@ bus_transaction_send (BusTransaction *transaction,
   BusConnectionData *d;
   DBusList *link;
 
-  _dbus_verbose ("  trying to add %s %s to transaction%s\n",
-                 dbus_message_get_is_error (message) ? "error" :
+  _dbus_verbose ("  trying to add %s interface=%s member=%s error=%s to transaction%s\n",
+                 dbus_message_get_type (message) == DBUS_MESSAGE_TYPE_ERROR ? "error" :
                  dbus_message_get_reply_serial (message) != 0 ? "reply" :
                  "message",
-                 dbus_message_get_name (message),
+                 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)",
                  dbus_connection_get_is_connected (connection) ?
                  "" : " (disconnected)");
 
@@ -1550,13 +2252,13 @@ bus_transaction_send_error_reply (BusTransaction  *transaction,
   
   _dbus_assert (error != NULL);
   _DBUS_ASSERT_ERROR_IS_SET (error);
-
+  
   _dbus_verbose ("Sending error reply %s \"%s\"\n",
                  error->name, error->message);
 
-  reply = dbus_message_new_error_reply (in_reply_to,
-                                        error->name,
-                                        error->message);
+  reply = dbus_message_new_error (in_reply_to,
+                                  error->name,
+                                  error->message);
   if (reply == NULL)
     return FALSE;
 
@@ -1582,6 +2284,9 @@ bus_transaction_add_cancel_hook (BusTransaction               *transaction,
   ch = dbus_new (CancelHook, 1);
   if (ch == NULL)
     return FALSE;
+
+  _dbus_verbose ("     adding cancel hook function = %p data = %p\n",
+                 cancel_function, data);
   
   ch->cancel_function = cancel_function;
   ch->data = data;