summaryrefslogtreecommitdiffstats
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
From 9bea6ec0497391b6af41daca18d7868af2656cef Mon Sep 17 00:00:00 2001
From: Jacek Bukarewicz <j.bukarewicz@samsung.com>
Date: Fri, 28 Nov 2014 12:07:39 +0100
Subject: [PATCH 2/5] Disable message dispatching when send rule result is not
 known
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When unicast message is sent to addressed recipient and policy result
is not available message dispatch from the sender is disabled.
This also means that any further messages from the given connection are
put into the incoming queue without being processed. If response is received
message dispatching is resumed. This time answer is attached to the message
which is now processed synchronously.
Receive rule result unavailability is not yet handled - such messages are
rejected. Also, if message is sent to non-addressed recipient and policy result
is unknown, message is silently dropped.

Change-Id: I57eccbf973525fd51369c7d4e58908292f44da80

Cherry-picked from b1b87ad9f20b2052c28431b48e81073078a745ce
by Jose Bollo.

Updated for dbus 1.12.10 by Scott Murray.

Signed-off-by: José Bollo <jose.bollo@iot.bzh>
Signed-off-by: Scott Murray <scott.murray@konsulko.com>

diff --git a/bus/activation.c b/bus/activation.c
index 451179d..5f02153 100644
--- a/bus/activation.c
+++ b/bus/activation.c
@@ -32,6 +32,7 @@
 #include "services.h"
 #include "test.h"
 #include "utils.h"
+#include <dbus/dbus-connection-internal.h>
 #include <dbus/dbus-internals.h>
 #include <dbus/dbus-hash.h>
 #include <dbus/dbus-list.h>
@@ -94,6 +95,8 @@ struct BusPendingActivationEntry
   DBusConnection *connection;
 
   dbus_bool_t auto_activation;
+
+  dbus_bool_t is_put_back;
 };
 
 typedef struct
@@ -1241,20 +1244,23 @@ bus_activation_send_pending_auto_activation_messages (BusActivation  *activation
       BusPendingActivationEntry *entry = link->data;
       DBusList *next = _dbus_list_get_next_link (&pending_activation->entries, link);
 
-      if (entry->auto_activation && (entry->connection == NULL || dbus_connection_get_is_connected (entry->connection)))
+      if (entry->auto_activation && !entry->is_put_back &&
+          (entry->connection == NULL || dbus_connection_get_is_connected (entry->connection)))
         {
           DBusConnection *addressed_recipient;
           DBusError error;
+          BusResult res;
 
           dbus_error_init (&error);
 
           addressed_recipient = bus_service_get_primary_owners_connection (service);
 
           /* Resume dispatching where we left off in bus_dispatch() */
-          if (!bus_dispatch_matches (transaction,
-                                     entry->connection,
-                                     addressed_recipient,
-                                     entry->activation_message, &error))
+          res = bus_dispatch_matches (transaction,
+                                      entry->connection,
+                                      addressed_recipient,
+                                      entry->activation_message, &error);
+          if (res == BUS_RESULT_FALSE)
             {
               /* If permission is denied, we just want to return the error
                * to the original method invoker; in particular, we don't
@@ -1266,9 +1272,40 @@ bus_activation_send_pending_auto_activation_messages (BusActivation  *activation
                   bus_connection_send_oom_error (entry->connection,
                                                  entry->activation_message);
                 }
+            }
+          else if (res == BUS_RESULT_LATER)
+            {
+              DBusList *putback_message_link = link;
+              DBusMessage *last_inserted_message = NULL;
+
+              /* NULL entry->connection implies sending pending ActivationRequest message to systemd */
+              if (entry->connection == NULL)
+                {
+                  _dbus_assert_not_reached ("bus_dispatch_matches returned BUS_RESULT_LATER unexpectedly when sender is NULL");
+                  link = next;
+                  continue;
+                }
 
-              link = next;
-              continue;
+              /**
+               * Getting here means that policy check result is not yet available and dispatching
+               * messages from entry->connection has been disabled.
+               * Let's put back all messages for the given connection in the incoming queue and mark
+               * this entry as put back so they are not handled twice.
+               */
+              while (putback_message_link != NULL)
+                {
+                  BusPendingActivationEntry *putback_message = putback_message_link->data;
+                  if (putback_message->connection == entry->connection)
+                    {
+                      if (!_dbus_connection_putback_message (putback_message->connection, last_inserted_message,
+                            putback_message->activation_message, &error))
+                        goto error;
+                      last_inserted_message = putback_message->activation_message;
+                      putback_message->is_put_back = TRUE;
+                    }
+
+                  putback_message_link = _dbus_list_get_next_link(&pending_activation->entries, putback_message_link);
+                }
             }
         }
 
@@ -1286,6 +1323,19 @@ bus_activation_send_pending_auto_activation_messages (BusActivation  *activation
   return TRUE;
 
  error:
+  /* remove all messages that have been put to connections' incoming queues */
+  link = _dbus_list_get_first_link (&pending_activation->entries);
+  while (link != NULL)
+    {
+      BusPendingActivationEntry *entry = link->data;
+      if (entry->is_put_back)
+        {
+          _dbus_connection_remove_message(entry->connection, entry->activation_message);
+          entry->is_put_back = FALSE;
+        }
+      link = _dbus_list_get_next_link(&pending_activation->entries, link);
+    }
+
   return FALSE;
 }
 
@@ -2078,6 +2128,7 @@ bus_activation_activate_service (BusActivation  *activation,
 
           if (service != NULL)
             {
+              BusResult res;
               bus_context_log (activation->context,
                                DBUS_SYSTEM_LOG_INFO, "Activating via systemd: service name='%s' unit='%s' requested by '%s' (%s)",
                                service_name,
@@ -2085,8 +2136,17 @@ bus_activation_activate_service (BusActivation  *activation,
                                bus_connection_get_name (connection),
                                bus_connection_get_loginfo (connection));
               /* Wonderful, systemd is connected, let's just send the msg */
-              retval = bus_dispatch_matches (activation_transaction, NULL,
-                                             systemd, message, error);
+              res = bus_dispatch_matches (activation_transaction, NULL,
+                                          systemd, message, error);
+
+              if (res == BUS_RESULT_TRUE)
+                retval = TRUE;
+              else
+                {
+                  retval = FALSE;
+                  if (res == BUS_RESULT_LATER)
+                    _dbus_verbose("Unexpectedly need time to check message from bus driver to systemd - dropping the message.\n");
+                }
             }
           else
             {
diff --git a/bus/check.c b/bus/check.c
index 5b72d31..4b8a699 100644
--- a/bus/check.c
+++ b/bus/check.c
@@ -55,6 +55,8 @@ typedef struct BusDeferredMessage
   BusCheckResponseFunc response_callback;
 } BusDeferredMessage;
 
+static dbus_int32_t deferred_message_data_slot = -1;
+
 BusCheck *
 bus_check_new (BusContext *context, DBusError *error)
 {
@@ -67,11 +69,19 @@ bus_check_new (BusContext *context, DBusError *error)
       return NULL;
     }
 
+  if (!dbus_message_allocate_data_slot(&deferred_message_data_slot))
+    {
+      dbus_free(check);
+      BUS_SET_OOM(error);
+      return NULL;
+    }
+
   check->refcount = 1;
   check->context = context;
   check->cynara = bus_cynara_new(check, error);
   if (dbus_error_is_set(error))
     {
+      dbus_message_free_data_slot(&deferred_message_data_slot);
       dbus_free(check);
       return NULL;
     }
@@ -98,6 +108,7 @@ bus_check_unref (BusCheck *check)
   if (check->refcount == 0)
     {
       bus_cynara_unref(check->cynara);
+      dbus_message_free_data_slot(&deferred_message_data_slot);
       dbus_free(check);
     }
 }
@@ -114,6 +125,45 @@ bus_check_get_cynara (BusCheck *check)
   return check->cynara;
 }
 
+static void
+bus_check_enable_dispatch_callback (BusDeferredMessage *deferred_message,
+                                    BusResult result)
+{
+  _dbus_verbose("bus_check_enable_dispatch_callback called deferred_message=%p\n", deferred_message);
+
+  deferred_message->response = result;
+  _dbus_connection_enable_dispatch(deferred_message->sender);
+}
+
+static void
+deferred_message_free_function(void *data)
+{
+  BusDeferredMessage *deferred_message = (BusDeferredMessage *)data;
+  bus_deferred_message_unref(deferred_message);
+}
+
+void
+bus_deferred_message_disable_sender (BusDeferredMessage *deferred_message)
+{
+  _dbus_assert(deferred_message != NULL);
+  _dbus_assert(deferred_message->sender != NULL);
+
+  if (dbus_message_get_data(deferred_message->message, deferred_message_data_slot) == NULL)
+    {
+      if (dbus_message_set_data(deferred_message->message, deferred_message_data_slot, deferred_message,
+          deferred_message_free_function))
+        bus_deferred_message_ref(deferred_message);
+    }
+
+  _dbus_connection_disable_dispatch(deferred_message->sender);
+  deferred_message->response_callback = bus_check_enable_dispatch_callback;
+}
+
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
+BusResult (*bus_check_test_override) (DBusConnection *connection,
+                                        const char *privilege);
+#endif
+
 BusResult
 bus_check_privilege (BusCheck *check,
                      DBusMessage *message,
@@ -124,6 +174,7 @@ bus_check_privilege (BusCheck *check,
                      BusDeferredMessageStatus check_type,
                      BusDeferredMessage **deferred_message)
 {
+  BusDeferredMessage *previous_deferred_message;
   BusResult result = BUS_RESULT_FALSE;
 #ifdef DBUS_ENABLE_CYNARA
   BusCynara *cynara;
@@ -137,16 +188,54 @@ bus_check_privilege (BusCheck *check,
       return BUS_RESULT_FALSE;
     }
 
-  /* ask policy checkers */
-#ifdef DBUS_ENABLE_CYNARA
-  cynara = bus_check_get_cynara(check);
-  result = bus_cynara_check_privilege(cynara, message, sender, addressed_recipient,
-      proposed_recipient, privilege, check_type, deferred_message);
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
+  if (bus_check_test_override)
+    return bus_check_test_override (connection, privilege);
 #endif
 
-  if (result == BUS_RESULT_LATER && deferred_message != NULL)
+  previous_deferred_message = dbus_message_get_data(message, deferred_message_data_slot);
+  /* check if message blocked at sender's queue is being processed */
+  if (previous_deferred_message != NULL)
+    {
+      if ((check_type & BUS_DEFERRED_MESSAGE_CHECK_SEND) &&
+          !(previous_deferred_message->status & BUS_DEFERRED_MESSAGE_CHECK_SEND))
+        {
+          /**
+           * Message has been deferred due to receive or own rule which means that sending this message
+           * is allowed - it must have been checked previously.
+           * This might happen when client calls RequestName method which depending on security
+           * policy might result in both "can_send" and "can_own" Cynara checks.
+           */
+          result = BUS_RESULT_TRUE;
+        }
+      else
+        {
+          result = previous_deferred_message->response;
+          if (result == BUS_RESULT_LATER)
+            {
+              /* result is still not known - reuse deferred message object */
+              if (deferred_message != NULL)
+                *deferred_message = previous_deferred_message;
+            }
+          else
+            {
+              /* result is available - we can remove deferred message from the processed message */
+              dbus_message_set_data(message, deferred_message_data_slot, NULL, NULL);
+            }
+        }
+    }
+  else
     {
-      (*deferred_message)->status |= check_type;
+      /* ask policy checkers */
+#ifdef DBUS_ENABLE_CYNARA
+      cynara = bus_check_get_cynara(check);
+      result = bus_cynara_check_privilege(cynara, message, sender, addressed_recipient,
+          proposed_recipient, privilege, check_type, deferred_message);
+#endif
+      if (result == BUS_RESULT_LATER && deferred_message != NULL)
+        {
+          (*deferred_message)->status |= check_type;
+        }
     }
   return result;
 }
@@ -206,6 +295,12 @@ bus_deferred_message_unref (BusDeferredMessage *deferred_message)
      }
 }
 
+BusDeferredMessageStatus
+bus_deferred_message_get_status (BusDeferredMessage *deferred_message)
+{
+  return deferred_message->status;
+}
+
 void
 bus_deferred_message_response_received (BusDeferredMessage *deferred_message,
                                         BusResult result)
diff --git a/bus/check.h b/bus/check.h
index c3fcaf9..d177549 100644
--- a/bus/check.h
+++ b/bus/check.h
@@ -55,6 +55,7 @@ BusResult   bus_check_privilege   (BusCheck *check,
                                    BusDeferredMessageStatus check_type,
                                    BusDeferredMessage **deferred_message);
 
+
 BusDeferredMessage *bus_deferred_message_new                (DBusMessage *message,
                                                              DBusConnection *sender,
                                                              DBusConnection *addressed_recipient,
@@ -65,4 +66,13 @@ BusDeferredMessage *bus_deferred_message_ref                (BusDeferredMessage
 void                bus_deferred_message_unref              (BusDeferredMessage *deferred_message);
 void                bus_deferred_message_response_received  (BusDeferredMessage *deferred_message,
                                                              BusResult result);
+void                bus_deferred_message_disable_sender     (BusDeferredMessage *deferred_message);
+
+BusDeferredMessageStatus  bus_deferred_message_get_status   (BusDeferredMessage *deferred_message);
+
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
+extern BusResult (*bus_check_test_override) (DBusConnection *connection,
+                                               const char *privilege);
+#endif
+
 #endif /* BUS_CHECK_H */
diff --git a/bus/cynara.c b/bus/cynara.c
index 57a4c45..77aed62 100644
--- a/bus/cynara.c
+++ b/bus/cynara.c
@@ -36,7 +36,6 @@
 #include <cynara-client-async.h>
 #endif
 
-
 #ifdef DBUS_ENABLE_CYNARA
 typedef struct BusCynara
 {
diff --git a/bus/dispatch.c b/bus/dispatch.c
index 7e51bc1..0250b53 100644
--- a/bus/dispatch.c
+++ b/bus/dispatch.c
@@ -35,6 +35,7 @@
 #include "signals.h"
 #include "test.h"
 #include <dbus/dbus-internals.h>
+#include <dbus/dbus-connection-internal.h>
 #include <dbus/dbus-misc.h>
 #include <string.h>
 
@@ -122,7 +123,7 @@ send_one_message (DBusConnection *connection,
   return TRUE;
 }
 
-dbus_bool_t
+BusResult
 bus_dispatch_matches (BusTransaction *transaction,
                       DBusConnection *sender,
                       DBusConnection *addressed_recipient,
@@ -158,13 +159,29 @@ bus_dispatch_matches (BusTransaction *transaction,
                                                message, NULL, error,
                                                &deferred_message);
       if (res == BUS_RESULT_FALSE)
-        return FALSE;
+        return BUS_RESULT_FALSE;
       else if (res == BUS_RESULT_LATER)
         {
-          dbus_set_error (error,
-                          DBUS_ERROR_ACCESS_DENIED,
-                          "Rejecting message because time is needed to check security policy");
-          return FALSE;
+          BusDeferredMessageStatus status;
+          status = bus_deferred_message_get_status(deferred_message);
+
+          if (status & BUS_DEFERRED_MESSAGE_CHECK_SEND)
+            {
+              /* send rule result not available - disable dispatching messages from the sender */
+              bus_deferred_message_disable_sender(deferred_message);
+              return BUS_RESULT_LATER;
+            }
+          else if (status & BUS_DEFERRED_MESSAGE_CHECK_RECEIVE)
+            {
+              dbus_set_error (error, DBUS_ERROR_ACCESS_DENIED,
+                              "Rejecting message because time is needed to check security policy");
+              return BUS_RESULT_FALSE;
+            }
+          else
+            {
+              _dbus_verbose("deferred message has no status field set to send or receive unexpectedly\n");
+              return BUS_RESULT_FALSE;
+            }
         }
 
       if (dbus_message_contains_unix_fds (message) &&
@@ -175,14 +192,14 @@ bus_dispatch_matches (BusTransaction *transaction,
                           DBUS_ERROR_NOT_SUPPORTED,
                           "Tried to send message with Unix file descriptors"
                           "to a client that doesn't support that.");
-          return FALSE;
-      }
+          return BUS_RESULT_FALSE;
+        }
 
       /* Dispatch the message */
       if (!bus_transaction_send (transaction, addressed_recipient, message))
         {
           BUS_SET_OOM (error);
-          return FALSE;
+          return BUS_RESULT_FALSE;
         }
     }
 
@@ -197,7 +214,7 @@ bus_dispatch_matches (BusTransaction *transaction,
                                       &recipients))
     {
       BUS_SET_OOM (error);
-      return FALSE;
+      return BUS_RESULT_FALSE;
     }
 
   link = _dbus_list_get_first_link (&recipients);
@@ -219,10 +236,10 @@ bus_dispatch_matches (BusTransaction *transaction,
   if (dbus_error_is_set (&tmp_error))
     {
       dbus_move_error (&tmp_error, error);
-      return FALSE;
+      return BUS_RESULT_FALSE;
     }
   else
-    return TRUE;
+    return BUS_RESULT_TRUE;
 }
 
 static DBusHandlerResult
@@ -410,10 +427,12 @@ bus_dispatch (DBusConnection *connection,
         }
       else if (res == BUS_RESULT_LATER)
         {
-          dbus_set_error (&error,
-                          DBUS_ERROR_ACCESS_DENIED,
-                          "Rejecting message because time is needed to check security policy");
-          _dbus_verbose ("Security policy needs time to check policy. Dropping message\n");
+          /* Disable dispatching messages from the sender,
+           * roll back and dispatch the message once the policy result is available */
+          bus_deferred_message_disable_sender(deferred_message);
+          bus_transaction_cancel_and_free (transaction);
+          transaction = NULL;
+          result = DBUS_HANDLER_RESULT_LATER;
           goto out;
         }
 
@@ -515,8 +534,14 @@ bus_dispatch (DBusConnection *connection,
    * addressed_recipient == NULL), and match it against other connections'
    * match rules.
    */
-  if (!bus_dispatch_matches (transaction, connection, addressed_recipient, message, &error))
-    goto out;
+  if (BUS_RESULT_LATER == bus_dispatch_matches (transaction, connection, addressed_recipient,
+                                                message, &error))
+    {
+      /* Roll back and dispatch the message once the policy result is available */
+      bus_transaction_cancel_and_free (transaction);
+      transaction = NULL;
+      result = DBUS_HANDLER_RESULT_LATER;
+    }
 
  out:
   if (dbus_error_is_set (&error))
@@ -5061,9 +5086,132 @@ bus_dispatch_test_conf_fail (const DBusString *test_data_dir,
 }
 #endif
 
+typedef struct {
+  DBusTimeout *timeout;
+  DBusConnection *connection;
+  dbus_bool_t timedout;
+  int check_counter;
+} BusTestCheckData;
+
+static BusTestCheckData *cdata;
+
+static dbus_bool_t
+bus_dispatch_test_check_timeout (void *data)
+{
+  _dbus_verbose ("timeout triggered - pretend that privilege check result is available\n");
+
+  /* should only happen once during the test */
+  _dbus_assert (!cdata->timedout);
+  cdata->timedout = TRUE;
+  _dbus_connection_enable_dispatch (cdata->connection);
+
+  /* don't call this again */
+  _dbus_loop_remove_timeout (bus_connection_get_loop (cdata->connection),
+                             cdata->timeout);
+  dbus_connection_unref (cdata->connection);
+  cdata->connection = NULL;
+  return TRUE;
+}
+
+static BusResult
+bus_dispatch_test_check_override (DBusConnection *connection,
+                                  const char *privilege)
+{
+  _dbus_verbose ("overriding privilege check %s #%d\n", privilege, cdata->check_counter);
+  cdata->check_counter++;
+  if (!cdata->timedout)
+    {
+      dbus_bool_t added;
+
+      /* Should be the first privilege check for the "Echo" method. */
+      _dbus_assert (cdata->check_counter == 1);
+      cdata->timeout = _dbus_timeout_new (1, bus_dispatch_test_check_timeout,
+                                          NULL, NULL);
+      _dbus_assert (cdata->timeout);
+      added = _dbus_loop_add_timeout (bus_connection_get_loop (connection),
+                                      cdata->timeout);
+      _dbus_assert (added);
+      cdata->connection = connection;
+      dbus_connection_ref (connection);
+      _dbus_connection_disable_dispatch (connection);
+      return BUS_RESULT_LATER;
+    }
+  else
+    {
+      /* Should only be checked one more time, and this time succeeds. */
+      _dbus_assert (cdata->check_counter == 2);
+      return BUS_RESULT_TRUE;
+    }
+}
+
+static dbus_bool_t
+bus_dispatch_test_check (const DBusString *test_data_dir)
+{
+  const char *filename = "valid-config-files/debug-check-some.conf";
+  BusContext *context;
+  DBusConnection *foo;
+  DBusError error;
+  dbus_bool_t result = TRUE;
+  BusTestCheckData data;
+
+  /* save the config name for the activation helper */
+  if (!setenv_TEST_LAUNCH_HELPER_CONFIG (test_data_dir, filename))
+    _dbus_assert_not_reached ("no memory setting TEST_LAUNCH_HELPER_CONFIG");
+
+  dbus_error_init (&error);
+
+  context = bus_context_new_test (test_data_dir, filename);
+  if (context == NULL)
+    return FALSE;
+
+  foo = dbus_connection_open_private (TEST_DEBUG_PIPE, &error);
+  if (foo == NULL)
+    _dbus_assert_not_reached ("could not alloc connection");
+
+  if (!bus_setup_debug_client (foo))
+    _dbus_assert_not_reached ("could not set up connection");
+
+  spin_connection_until_authenticated (context, foo);
+
+  if (!check_hello_message (context, foo))
+    _dbus_assert_not_reached ("hello message failed");
+
+  if (!check_double_hello_message (context, foo))
+    _dbus_assert_not_reached ("double hello message failed");
+
+  if (!check_add_match (context, foo, ""))
+    _dbus_assert_not_reached ("AddMatch message failed");
+
+  /*
+   * Cause bus_check_send_privilege() to return BUS_RESULT_LATER in the
+   * first call, then BUS_RESULT_TRUE.
+   */
+  cdata = &data;
+  memset (cdata, 0, sizeof(*cdata));
+  bus_check_test_override = bus_dispatch_test_check_override;
+
+  result = check_existent_service_auto_start (context, foo);
+
+  _dbus_assert (cdata->check_counter == 2);
+  _dbus_assert (cdata->timedout);
+  _dbus_assert (cdata->timeout);
+  _dbus_assert (!cdata->connection);
+  _dbus_timeout_unref (cdata->timeout);
+
+  kill_client_connection_unchecked (foo);
+
+  bus_context_unref (context);
+
+  return result;
+}
+
 dbus_bool_t
 bus_dispatch_test (const DBusString *test_data_dir)
 {
+  _dbus_verbose ("<check> tests\n");
+  if (!bus_dispatch_test_check (test_data_dir))
+    return FALSE;
+
   /* run normal activation tests */
   _dbus_verbose ("Normal activation tests\n");
   if (!bus_dispatch_test_conf (test_data_dir,
diff --git a/bus/dispatch.h b/bus/dispatch.h
index fb5ba7a..afba6a2 100644
--- a/bus/dispatch.h
+++ b/bus/dispatch.h
@@ -29,7 +29,7 @@
 
 dbus_bool_t bus_dispatch_add_connection    (DBusConnection *connection);
 void        bus_dispatch_remove_connection (DBusConnection *connection);
-dbus_bool_t bus_dispatch_matches           (BusTransaction *transaction,
+BusResult   bus_dispatch_matches           (BusTransaction *transaction,
                                             DBusConnection *sender,
                                             DBusConnection *recipient,
                                             DBusMessage    *message,
diff --git a/bus/driver.c b/bus/driver.c
index cd0a714..f414f64 100644
--- a/bus/driver.c
+++ b/bus/driver.c
@@ -218,6 +218,7 @@ bus_driver_send_service_owner_changed (const char     *service_name,
 {
   DBusMessage *message;
   dbus_bool_t retval;
+  BusResult res;
   const char *null_service;
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
@@ -253,7 +254,16 @@ bus_driver_send_service_owner_changed (const char     *service_name,
   if (!bus_transaction_capture (transaction, NULL, NULL, message))
     goto oom;
 
-  retval = bus_dispatch_matches (transaction, NULL, NULL, message, error);
+  res = bus_dispatch_matches (transaction, NULL, NULL, message, error);
+  if (res == BUS_RESULT_TRUE)
+    retval = TRUE;
+  else
+    {
+      retval = FALSE;
+      if (res == BUS_RESULT_LATER)
+        /* should never happen */
+        _dbus_assert_not_reached ("bus_dispatch_matches returned BUS_RESULT_LATER unexpectedly");
+    }
   dbus_message_unref (message);
 
   return retval;
diff --git a/dbus/dbus-connection-internal.h b/dbus/dbus-connection-internal.h
index 4835732..94b1c95 100644
--- a/dbus/dbus-connection-internal.h
+++ b/dbus/dbus-connection-internal.h
@@ -118,6 +118,21 @@ DBUS_PRIVATE_EXPORT
 dbus_bool_t       _dbus_connection_get_linux_security_label       (DBusConnection  *connection,
                                                                    char           **label_p);
 
+DBUS_PRIVATE_EXPORT
+void              _dbus_connection_enable_dispatch                (DBusConnection *connection);
+DBUS_PRIVATE_EXPORT
+void              _dbus_connection_disable_dispatch               (DBusConnection *connection);
+
+DBUS_PRIVATE_EXPORT
+dbus_bool_t       _dbus_connection_putback_message                (DBusConnection *connection,
+                                                                   DBusMessage    *after_message,
+                                                                   DBusMessage    *message,
+                                                                   DBusError      *error);
+
+DBUS_PRIVATE_EXPORT
+dbus_bool_t       _dbus_connection_remove_message                 (DBusConnection *connection,
+                                                                   DBusMessage    *message);
+
 /* if DBUS_ENABLE_STATS */
 DBUS_PRIVATE_EXPORT
 void _dbus_connection_get_stats (DBusConnection *connection,
diff --git a/dbus/dbus-connection.c b/dbus/dbus-connection.c
index c525b6d..f1b0ea0 100644
--- a/dbus/dbus-connection.c
+++ b/dbus/dbus-connection.c
@@ -311,7 +311,8 @@ struct DBusConnection
    */
   dbus_bool_t dispatch_acquired; /**< Someone has dispatch path (can drain incoming queue) */
   dbus_bool_t io_path_acquired;  /**< Someone has transport io path (can use the transport to read/write messages) */
-  
+
+  unsigned int dispatch_disabled : 1;  /**< if true, then dispatching incoming messages is stopped until enabled again */
   unsigned int shareable : 1; /**< #TRUE if libdbus owns a reference to the connection and can return it from dbus_connection_open() more than once */
   
   unsigned int exit_on_disconnect : 1; /**< If #TRUE, exit after handling disconnect signal */
@@ -439,6 +440,39 @@ _dbus_connection_wakeup_mainloop (DBusConnection *connection)
     (*connection->wakeup_main_function) (connection->wakeup_main_data);
 }
 
+static void
+_dbus_connection_set_dispatch(DBusConnection *connection,
+                              dbus_bool_t disabled)
+{
+  CONNECTION_LOCK (connection);
+  if (connection->dispatch_disabled != disabled)
+    {
+      DBusDispatchStatus status;
+
+      connection->dispatch_disabled = disabled;
+      status = _dbus_connection_get_dispatch_status_unlocked (connection);
+      _dbus_connection_update_dispatch_status_and_unlock (connection, status);
+    }
+  else
+    {
+      CONNECTION_UNLOCK (connection);
+    }
+}
+
+
+void
+_dbus_connection_enable_dispatch (DBusConnection *connection)
+{
+  _dbus_connection_set_dispatch (connection, FALSE);
+}
+
+void
+ _dbus_connection_disable_dispatch (DBusConnection *connection)
+{
+  _dbus_connection_set_dispatch (connection, TRUE);
+}
+
+
 #ifdef DBUS_ENABLE_EMBEDDED_TESTS
 /**
  * Gets the locks so we can examine them
@@ -4069,6 +4103,82 @@ _dbus_connection_putback_message_link_unlocked (DBusConnection *connection,
       "_dbus_connection_putback_message_link_unlocked");
 }
 
+dbus_bool_t
+_dbus_connection_putback_message (DBusConnection *connection,
+                                  DBusMessage    *after_message,
+                                  DBusMessage    *message,
+                                  DBusError      *error)
+{
+  DBusDispatchStatus status;
+  DBusList *message_link = _dbus_list_alloc_link (message);
+  DBusList *after_link;
+  if (message_link == NULL)
+    {
+      _DBUS_SET_OOM (error);
+      return FALSE;
+    }
+  dbus_message_ref (message);
+
+  CONNECTION_LOCK (connection);
+  _dbus_connection_acquire_dispatch (connection);
+  HAVE_LOCK_CHECK (connection);
+
+  after_link = _dbus_list_find_first(&connection->incoming_messages, after_message);
+  _dbus_list_insert_after_link (&connection->incoming_messages, after_link, message_link);
+  connection->n_incoming += 1;
+
+  _dbus_verbose ("Message %p (%s %s %s '%s') put back into queue %p, %d incoming\n",
+                 message_link->data,
+                 dbus_message_type_to_string (dbus_message_get_type (message_link->data)),
+                 dbus_message_get_interface (message_link->data) ?
+                 dbus_message_get_interface (message_link->data) :
+                 "no interface",
+                 dbus_message_get_member (message_link->data) ?
+                 dbus_message_get_member (message_link->data) :
+                 "no member",
+                 dbus_message_get_signature (message_link->data),
+                 connection, connection->n_incoming);
+
+  _dbus_message_trace_ref (message_link->data, -1, -1,
+      "_dbus_connection_putback_message");
+
+  _dbus_connection_release_dispatch (connection);
+
+  status = _dbus_connection_get_dispatch_status_unlocked (connection);
+  _dbus_connection_update_dispatch_status_and_unlock (connection, status);
+
+  return TRUE;
+}
+
+dbus_bool_t
+_dbus_connection_remove_message (DBusConnection *connection,
+                                 DBusMessage *message)
+{
+  DBusDispatchStatus status;
+  dbus_bool_t removed;
+
+  CONNECTION_LOCK (connection);
+  _dbus_connection_acquire_dispatch (connection);
+  HAVE_LOCK_CHECK (connection);
+
+  removed = _dbus_list_remove(&connection->incoming_messages, message);
+
+  if (removed)
+    {
+      connection->n_incoming -= 1;
+      dbus_message_unref(message);
+      _dbus_verbose ("Message %p removed from incoming queue\n", message);
+    }
+  else
+      _dbus_verbose ("Message %p not found in the incoming queue\n", message);
+
+  _dbus_connection_release_dispatch (connection);
+
+  status = _dbus_connection_get_dispatch_status_unlocked (connection);
+  _dbus_connection_update_dispatch_status_and_unlock (connection, status);
+  return removed;
+}
+
 /**
  * Returns the first-received message from the incoming message queue,
  * removing it from the queue. The caller owns a reference to the
@@ -4252,8 +4362,9 @@ static DBusDispatchStatus
 _dbus_connection_get_dispatch_status_unlocked (DBusConnection *connection)
 {
   HAVE_LOCK_CHECK (connection);
-  
-  if (connection->n_incoming > 0)
+  if (connection->dispatch_disabled && dbus_connection_get_is_connected(connection))
+    return DBUS_DISPATCH_COMPLETE;
+  else if (connection->n_incoming > 0)
     return DBUS_DISPATCH_DATA_REMAINS;
   else if (!_dbus_transport_queue_messages (connection->transport))
     return DBUS_DISPATCH_NEED_MEMORY;
@@ -4716,6 +4827,8 @@ dbus_connection_dispatch (DBusConnection *connection)
   
   CONNECTION_LOCK (connection);
 
+  if (result == DBUS_HANDLER_RESULT_LATER)
+      goto out;
   if (result == DBUS_HANDLER_RESULT_NEED_MEMORY)
     {
       _dbus_verbose ("No memory\n");
@@ -4838,9 +4951,11 @@ dbus_connection_dispatch (DBusConnection *connection)
                  connection);
   
  out:
-  if (result == DBUS_HANDLER_RESULT_NEED_MEMORY)
+  if (result == DBUS_HANDLER_RESULT_LATER ||
+      result == DBUS_HANDLER_RESULT_NEED_MEMORY)
     {
-      _dbus_verbose ("out of memory\n");
+      if (result == DBUS_HANDLER_RESULT_NEED_MEMORY)
+        _dbus_verbose ("out of memory\n");
       
       /* Put message back, and we'll start over.
        * Yes this means handlers must be idempotent if they
diff --git a/dbus/dbus-list.c b/dbus/dbus-list.c
index 8e713c0..32ea871 100644
--- a/dbus/dbus-list.c
+++ b/dbus/dbus-list.c
@@ -458,6 +458,35 @@ _dbus_list_remove_last (DBusList **list,
     return FALSE;
 }
 
+/**
+ * Finds a value in the list. Returns the first link
+ * with value equal to the given data pointer.
+ * This is a linear-time operation.
+ * Returns #NULL if no value found that matches.
+ *
+ * @param list address of the list head.
+ * @param data the value to find.
+ * @returns the link if found
+ */
+DBusList*
+_dbus_list_find_first (DBusList **list,
+                       void      *data)
+{
+  DBusList *link;
+
+  link = _dbus_list_get_first_link (list);
+
+  while (link != NULL)
+    {
+      if (link->data == data)
+        return link;
+
+      link = _dbus_list_get_next_link (list, link);
+    }
+
+  return NULL;
+}
+
 /**
  * Finds a value in the list. Returns the last link
  * with value equal to the given data pointer.
diff --git a/dbus/dbus-list.h b/dbus/dbus-list.h
index 9350a0d..fee9f1b 100644
--- a/dbus/dbus-list.h
+++ b/dbus/dbus-list.h
@@ -68,6 +68,9 @@ DBUS_PRIVATE_EXPORT
 void        _dbus_list_remove_link        (DBusList **list,
                                            DBusList  *link);
 DBUS_PRIVATE_EXPORT
+DBusList*   _dbus_list_find_first         (DBusList **list,
+                                           void      *data);
+DBUS_PRIVATE_EXPORT
 DBusList*   _dbus_list_find_last          (DBusList **list,
                                            void      *data);
 DBUS_PRIVATE_EXPORT
diff --git a/dbus/dbus-shared.h b/dbus/dbus-shared.h
index 7ab9103..e5bfbed 100644
--- a/dbus/dbus-shared.h
+++ b/dbus/dbus-shared.h
@@ -67,7 +67,8 @@ typedef enum
 {
   DBUS_HANDLER_RESULT_HANDLED,         /**< Message has had its effect - no need to run more handlers. */ 
   DBUS_HANDLER_RESULT_NOT_YET_HANDLED, /**< Message has not had any effect - see if other handlers want it. */
-  DBUS_HANDLER_RESULT_NEED_MEMORY      /**< Need more memory in order to return #DBUS_HANDLER_RESULT_HANDLED or #DBUS_HANDLER_RESULT_NOT_YET_HANDLED. Please try again later with more memory. */
+  DBUS_HANDLER_RESULT_NEED_MEMORY,     /**< Need more memory in order to return #DBUS_HANDLER_RESULT_HANDLED or #DBUS_HANDLER_RESULT_NOT_YET_HANDLED. Please try again later with more memory. */
+  DBUS_HANDLER_RESULT_LATER            /**< Message dispatch deferred due to pending policy check */
 } DBusHandlerResult;
 
 /* Bus names */