You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ch...@apache.org on 2016/03/30 19:13:40 UTC

[2/3] qpid-dispatch git commit: DISPATCH-188: Implement policy. merge from branch crolke-DISPATCH-188-1.

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/container.c
----------------------------------------------------------------------
diff --git a/src/container.c b/src/container.c
index ddd85ae..11575bc 100644
--- a/src/container.c
+++ b/src/container.c
@@ -21,6 +21,7 @@
 #include <string.h>
 #include "dispatch_private.h"
 #include "connection_manager_private.h"
+#include "policy.h"
 #include <qpid/dispatch/container.h>
 #include <qpid/dispatch/server.h>
 #include <qpid/dispatch/message.h>
@@ -267,6 +268,10 @@ static void notify_opened(qd_container_t *container, qd_connection_t *conn, void
     }
 }
 
+void policy_notify_opened(void *container, qd_connection_t *conn, void *context)
+{
+	notify_opened((qd_container_t *)container, (qd_connection_t *)conn, context);
+}
 
 static void notify_closed(qd_container_t *container, qd_connection_t *conn, void *context)
 {
@@ -367,11 +372,18 @@ int pn_event_handler(void *handler_context, void *conn_context, pn_event_t *even
 
     switch (pn_event_type(event)) {
     case PN_CONNECTION_REMOTE_OPEN :
-        if (pn_connection_state(conn) & PN_LOCAL_UNINIT)
-            pn_connection_open(conn);
-        qd_connection_set_user(qd_conn);
-        qd_connection_manager_connection_opened(qd_conn);
-        notify_opened(container, qd_conn, conn_context);
+        if (pn_connection_state(conn) & PN_LOCAL_UNINIT) {
+            // This Open is an externally initiated connection
+            // Let policy engine decide
+            qd_connection_set_event_stall(qd_conn, true);
+            qd_conn->open_container = (void *)container;
+            qd_conn->conn_context = conn_context;
+            qd_connection_invoke_deferred(qd_conn, qd_policy_amqp_open, qd_conn);
+        } else {
+            // This Open is in response to an internally initiated connection
+            qd_connection_manager_connection_opened(qd_conn);
+            notify_opened(container, qd_conn, conn_context);
+        }
         break;
 
     case PN_CONNECTION_REMOTE_CLOSE :
@@ -383,7 +395,13 @@ int pn_event_handler(void *handler_context, void *conn_context, pn_event_t *even
     case PN_SESSION_REMOTE_OPEN :
         ssn = pn_event_session(event);
         if (pn_session_state(ssn) & PN_LOCAL_UNINIT) {
-            pn_session_set_incoming_capacity(ssn, 1000000);
+            if (qd_conn->policy_settings) {
+                if (!qd_policy_approve_amqp_session(ssn, qd_conn)) {
+                    break;
+                }
+                qd_conn->n_sessions++;
+            }
+            qd_policy_apply_session_settings(ssn, qd_conn);
             pn_session_open(ssn);
         }
         break;
@@ -400,6 +418,15 @@ int pn_event_handler(void *handler_context, void *conn_context, pn_event_t *even
                 if (pn_link_session(pn_link) == ssn) {
                     qd_link_t *qd_link = (qd_link_t *)pn_link_get_context(pn_link);
                     if (qd_link && qd_link->node) {
+                        if (qd_conn->policy_settings) {
+                            if (qd_link->direction == QD_OUTGOING) {
+                                qd_conn->n_senders--;
+                                assert(qd_conn->n_senders >= 0);
+                            } else {
+                                qd_conn->n_receivers--;
+                                assert(qd_conn->n_receivers >= 0);
+                            }
+                        }
                         qd_log(container->log_source, QD_LOG_NOTICE,
                                "Aborting link '%s' due to parent session end",
                                pn_link_name(pn_link));
@@ -409,6 +436,9 @@ int pn_event_handler(void *handler_context, void *conn_context, pn_event_t *even
                 }
                 pn_link = pn_link_next(pn_link, PN_LOCAL_ACTIVE | PN_REMOTE_ACTIVE);
             }
+            if (qd_conn->policy_settings) {
+                qd_conn->n_sessions--;
+            }
             pn_session_close(ssn);
         }
         break;
@@ -416,10 +446,23 @@ int pn_event_handler(void *handler_context, void *conn_context, pn_event_t *even
     case PN_LINK_REMOTE_OPEN :
         pn_link = pn_event_link(event);
         if (pn_link_state(pn_link) & PN_LOCAL_UNINIT) {
-            if (pn_link_is_sender(pn_link))
+            if (pn_link_is_sender(pn_link)) {
+               if (qd_conn->policy_settings) {
+                   if (!qd_policy_approve_amqp_receiver_link(pn_link, qd_conn)) {
+                        break;
+                    }
+                    qd_conn->n_senders++;
+                }
                 setup_outgoing_link(container, pn_link);
-            else
+            } else {
+                if (qd_conn->policy_settings) {
+                    if (!qd_policy_approve_amqp_sender_link(pn_link, qd_conn)) {
+                        break;
+                    }
+                    qd_conn->n_receivers++;
+                }
                 setup_incoming_link(container, pn_link);
+            }
         } else if (pn_link_state(pn_link) & PN_LOCAL_ACTIVE)
             handle_link_open(container, pn_link);
         break;
@@ -433,8 +476,20 @@ int pn_event_handler(void *handler_context, void *conn_context, pn_event_t *even
             qd_detach_type_t dt = pn_event_type(event) == PN_LINK_REMOTE_CLOSE ? QD_CLOSED : QD_DETACHED;
             if (node)
                 node->ntype->link_detach_handler(node->context, qd_link, dt);
-            else if (qd_link->pn_link == pn_link)
+            else if (qd_link->pn_link == pn_link) {
+                if (qd_conn->policy_settings) {
+                    if (pn_link_is_sender(pn_link)) {
+                        qd_conn->n_senders--;
+                        assert (qd_conn->n_senders >= 0);
+                    } else {
+                        qd_conn->n_receivers--;
+                        assert (qd_conn->n_receivers >= 0);
+                    }
+                } else {
+                    // no policy - links not counted
+                }
                 pn_link_close(pn_link);
+            }
             if (qd_link->close_sess_with_link && qd_link->pn_sess &&
                 pn_link_state(pn_link) == (PN_LOCAL_CLOSED | PN_REMOTE_CLOSED))
                 pn_session_close(qd_link->pn_sess);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/dispatch.c
----------------------------------------------------------------------
diff --git a/src/dispatch.c b/src/dispatch.c
index c05d24c..73bd0b5 100644
--- a/src/dispatch.c
+++ b/src/dispatch.c
@@ -30,6 +30,7 @@
 #include "log_private.h"
 #include "router_private.h"
 #include "message_private.h"
+#include "policy.h"
 #include "entity.h"
 #include "entity_cache.h"
 #include <dlfcn.h>
@@ -42,6 +43,8 @@ qd_server_t    *qd_server(qd_dispatch_t *qd, int tc, const char *container_name,
 void            qd_server_free(qd_server_t *server);
 qd_container_t *qd_container(qd_dispatch_t *qd);
 void            qd_container_free(qd_container_t *container);
+qd_policy_t    *qd_policy(qd_dispatch_t *qd);
+void            qd_policy_free(qd_policy_t *policy);
 qd_router_t    *qd_router(qd_dispatch_t *qd, qd_router_mode_t mode, const char *area, const char *id);
 void            qd_router_setup_late(qd_dispatch_t *qd);
 void            qd_router_free(qd_router_t *router);
@@ -67,6 +70,7 @@ qd_dispatch_t *qd_dispatch(const char *python_pkgdir)
     qd_message_initialize();
     if (qd_error_code()) { qd_dispatch_free(qd); return 0; }
     qd->log_source = qd_log_source("DISPATCH");
+    qd->dl_handle = 0;
     return qd;
 }
 
@@ -76,15 +80,15 @@ STATIC_ASSERT(sizeof(long) >= sizeof(void*), pointer_is_bigger_than_long);
 
 qd_error_t qd_dispatch_load_config(qd_dispatch_t *qd, const char *config_path)
 {
-    void *handle = dlopen(QPID_DISPATCH_LIB, RTLD_LAZY | RTLD_NOLOAD);
-    if (!handle)
+    qd->dl_handle = dlopen(QPID_DISPATCH_LIB, RTLD_LAZY | RTLD_NOLOAD);
+    if (!qd->dl_handle)
         return qd_error(QD_ERROR_RUNTIME, "Cannot locate library %s", QPID_DISPATCH_LIB);
 
     qd_python_lock_state_t lock_state = qd_python_lock();
     PyObject *module = PyImport_ImportModule("qpid_dispatch_internal.management.config");
     PyObject *configure_dispatch = module ? PyObject_GetAttrString(module, "configure_dispatch") : NULL;
     Py_XDECREF(module);
-    PyObject *result = configure_dispatch ? PyObject_CallFunction(configure_dispatch, "(lls)", (long)qd, handle, config_path) : NULL;
+    PyObject *result = configure_dispatch ? PyObject_CallFunction(configure_dispatch, "(lls)", (long)qd, qd->dl_handle, config_path) : NULL;
     Py_XDECREF(configure_dispatch);
     if (!result) qd_error_py();
     Py_XDECREF(result);
@@ -155,12 +159,45 @@ qd_error_t qd_dispatch_configure_auto_link(qd_dispatch_t *qd, qd_entity_t *entit
     return qd_error_code();
 }
 
+qd_error_t qd_dispatch_configure_policy(qd_dispatch_t *qd, qd_entity_t *entity)
+{
+    qd_error_t err;
+    err = qd_entity_configure_policy(qd->policy, entity);
+    if (err)
+        return err;
+    return QD_ERROR_NONE;
+}
+
+
+qd_error_t qd_dispatch_register_policy_manager(qd_dispatch_t *qd, qd_entity_t *entity)
+{
+    return qd_register_policy_manager(qd->policy, entity);
+}
+
+
+long qd_dispatch_policy_c_counts_alloc()
+{
+    return qd_policy_c_counts_alloc();
+}
+
+
+void qd_dispatch_policy_c_counts_free(long ccounts)
+{
+    qd_policy_c_counts_free(ccounts);
+}
+
+void qd_dispatch_policy_c_counts_refresh(long ccounts, qd_entity_t *entity)
+{
+    qd_policy_c_counts_refresh(ccounts, entity);
+}
+
 qd_error_t qd_dispatch_prepare(qd_dispatch_t *qd)
 {
     qd->server             = qd_server(qd, qd->thread_count, qd->container_name, qd->sasl_config_path, qd->sasl_config_name);
     qd->container          = qd_container(qd);
     qd->router             = qd_router(qd, qd->router_mode, qd->router_area, qd->router_id);
     qd->connection_manager = qd_connection_manager(qd);
+    qd->policy             = qd_policy(qd);
     return qd_error_code();
 }
 
@@ -177,6 +214,7 @@ void qd_dispatch_free(qd_dispatch_t *qd)
     free(qd->container_name);
     free(qd->router_area);
     qd_connection_manager_free(qd->connection_manager);
+    qd_policy_free(qd->policy);
     Py_XDECREF((PyObject*) qd->agent);
     qd_router_free(qd->router);
     qd_container_free(qd->container);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/dispatch_private.h
----------------------------------------------------------------------
diff --git a/src/dispatch_private.h b/src/dispatch_private.h
index b955e10..d624f5d 100644
--- a/src/dispatch_private.h
+++ b/src/dispatch_private.h
@@ -40,6 +40,7 @@ typedef struct qd_config_address_t   qd_config_address_t;
 #include <qpid/dispatch/container.h>
 #include <qpid/dispatch/router.h>
 #include <qpid/dispatch/connection_manager.h>
+#include "policy.h"
 #include "server_private.h"
 #include "router_private.h"
 
@@ -49,6 +50,8 @@ struct qd_dispatch_t {
     qd_router_t             *router;
     void                    *agent;
     qd_connection_manager_t *connection_manager;
+    qd_policy_t             *policy;
+    void                    *dl_handle;
 
     int    thread_count;
     char  *container_name;
@@ -107,6 +110,16 @@ qd_error_t qd_dispatch_configure_lrp(qd_dispatch_t *qd, qd_entity_t *entity);
 qd_error_t qd_dispatch_configure_route(qd_dispatch_t *qd, qd_entity_t *entity);
 
 /**
+ * Configure security policy, must be called after qd_dispatch_prepare
+ */
+qd_error_t qd_dispatch_configure_policy(qd_dispatch_t *qd, qd_entity_t *entity);
+
+/**
+ * Configure security policy manager, must be called after qd_dispatch_prepare
+ */
+qd_error_t qd_dispatch_register_policy_manager(qd_dispatch_t *qd, qd_entity_t *entity);
+
+/**
  * \brief Configure the logging module from the
  *        parsed configuration file.  This must be called after the
  *        call to qd_dispatch_prepare completes.

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/policy.c
----------------------------------------------------------------------
diff --git a/src/policy.c b/src/policy.c
new file mode 100644
index 0000000..a0f5295
--- /dev/null
+++ b/src/policy.c
@@ -0,0 +1,758 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <Python.h>
+#include "qpid/dispatch/python_embedded.h"
+#include "policy.h"
+#include "policy_internal.h"
+#include <stdio.h>
+#include <string.h>
+#include "dispatch_private.h"
+#include "connection_manager_private.h"
+#include "qpid/dispatch/container.h"
+#include "qpid/dispatch/server.h"
+#include <proton/message.h>
+#include <proton/condition.h>
+#include <proton/connection.h>
+#include <proton/transport.h>
+#include <proton/error.h>
+#include <proton/event.h>
+
+
+//
+// TODO: when policy dev is more complete lower the log level
+//
+#define POLICY_LOG_LEVEL QD_LOG_CRITICAL
+
+//
+// The current statistics maintained globally through multiple
+// reconfiguration of policy settings.
+//
+static int n_connections = 0;
+static int n_denied = 0;
+static int n_processed = 0;
+
+//
+// error conditions signaled to effect denial
+//
+static char* RESOURCE_LIMIT_EXCEEDED     = "amqp:resource-limit-exceeded";
+//static char* UNAUTHORIZED_ACCESS         = "amqp:unauthorized-access";
+//static char* CONNECTION_FORCED           = "amqp:connection:forced";
+
+//
+// error descriptions signaled to effect denial
+//
+static char* CONNECTION_DISALLOWED         = "connection disallowed by local policy";
+static char* SESSION_DISALLOWED            = "session disallowed by local policy";
+static char* LINK_DISALLOWED               = "link disallowed by local policy";
+
+//
+// Policy configuration/statistics management interface
+//
+struct qd_policy_t {
+    qd_dispatch_t        *qd;
+    qd_log_source_t      *log_source;
+    void                 *py_policy_manager;
+                          // configured settings
+    int                   max_connection_limit;
+    char                 *policyFolder;
+    bool                  enableAccessRules;
+                          // live statistics
+    int                   connections_processed;
+    int                   connections_denied;
+    int                   connections_current;
+};
+
+/** Create the policy structure
+ * @param[in] qd pointer the the qd
+ **/
+qd_policy_t *qd_policy(qd_dispatch_t *qd)
+{
+    qd_policy_t *policy = NEW(qd_policy_t);
+
+    policy->qd                   = qd;
+    policy->log_source           = qd_log_source("POLICY");
+    policy->max_connection_limit = 0;
+    policy->policyFolder         = 0;
+    policy->enableAccessRules    = false;
+    policy->connections_processed= 0;
+    policy->connections_denied   = 0;
+    policy->connections_current  = 0;
+
+    qd_log(policy->log_source, QD_LOG_TRACE, "Policy Initialized");
+    return policy;
+}
+
+
+/** Free the policy structure
+ * @param[in] policy pointer to the policy
+ **/
+void qd_policy_free(qd_policy_t *policy)
+{
+    if (policy->policyFolder)
+        free(policy->policyFolder);
+    free(policy);
+}
+
+//
+//
+#define CHECK() if (qd_error_code()) goto error
+
+qd_error_t qd_entity_configure_policy(qd_policy_t *policy, qd_entity_t *entity)
+{
+    policy->max_connection_limit = qd_entity_opt_long(entity, "maximumConnections", 0); CHECK();
+    if (policy->max_connection_limit < 0)
+        return qd_error(QD_ERROR_CONFIG, "maximumConnections must be >= 0");
+    policy->policyFolder =
+        qd_entity_opt_string(entity, "policyFolder", 0); CHECK();
+    policy->enableAccessRules = qd_entity_opt_bool(entity, "enableAccessRules", false); CHECK();
+    qd_log(policy->log_source, QD_LOG_INFO, "Policy configured maximumConnections: %d, policyFolder: '%s', access rules enabled: '%s'",
+           policy->max_connection_limit, policy->policyFolder, (policy->enableAccessRules ? "true" : "false"));
+    return QD_ERROR_NONE;
+
+error:
+    if (policy->policyFolder)
+        free(policy->policyFolder);
+    qd_policy_free(policy);
+    return qd_error_code();
+}
+
+
+//
+//
+qd_error_t qd_register_policy_manager(qd_policy_t *policy, void *policy_manager)
+{
+    policy->py_policy_manager = policy_manager;
+    return QD_ERROR_NONE;
+}
+
+
+long qd_policy_c_counts_alloc()
+{
+    qd_policy_denial_counts_t * dc = NEW(qd_policy_denial_counts_t);
+    assert(dc);
+    memset(dc, 0, sizeof(qd_policy_denial_counts_t));
+    return (long)dc;
+}
+
+
+void qd_policy_c_counts_free(long ccounts)
+{
+    void *dc = (void *)ccounts;
+    assert(dc);
+    free(dc);
+}
+
+
+qd_error_t qd_policy_c_counts_refresh(long ccounts, qd_entity_t *entity)
+{
+    qd_policy_denial_counts_t *dc = (qd_policy_denial_counts_t*)ccounts;
+    if (!qd_entity_set_long(entity, "sessionDenied", dc->sessionDenied) &&
+        !qd_entity_set_long(entity, "senderDenied", dc->senderDenied) &&
+        !qd_entity_set_long(entity, "receiverDenied", dc->receiverDenied) &&
+        !qd_entity_set_long(entity, "dynamicSrcDenied", dc->dynamicSrcDenied) &&
+        !qd_entity_set_long(entity, "anonymousSenderDenied", dc->anonymousSenderDenied) &&
+        !qd_entity_set_long(entity, "linkSourceDenied", dc->linkSourceDenied) &&
+        !qd_entity_set_long(entity, "linkTargetDenied", dc->linkTargetDenied)
+    )
+        return QD_ERROR_NONE;
+    return qd_error_code();
+}
+
+
+/** Update the statistics in qdrouterd.conf["policy"]
+ * @param[in] entity pointer to the policy management object
+ **/
+qd_error_t qd_entity_refresh_policy(qd_entity_t* entity, void *unused) {
+    // Return global stats
+    if (!qd_entity_set_long(entity, "connectionsProcessed", n_processed) &&
+        !qd_entity_set_long(entity, "connectionsDenied", n_denied) &&
+        !qd_entity_set_long(entity, "connectionsCurrent", n_connections)
+    )
+        return QD_ERROR_NONE;
+    return qd_error_code();
+}
+
+
+//
+// Functions related to absolute connection counts.
+// These handle connections at the socket level with
+// no regard to user identity. Simple yes/no decisions
+// are made and there is no AMQP channel for returning
+// error conditions.
+//
+
+bool qd_policy_socket_accept(void *context, const char *hostname)
+{
+    qd_policy_t *policy = (qd_policy_t *)context;
+    bool result = true;
+
+    if (policy->max_connection_limit == 0) {
+        // Policy not in force; connection counted and allowed
+        n_connections += 1;
+    } else {
+        // Policy in force
+        if (n_connections < policy->max_connection_limit) {
+            // connection counted and allowed
+            n_connections += 1;
+            qd_log(policy->log_source, POLICY_LOG_LEVEL, "Connection '%s' allowed. N= %d", hostname, n_connections);
+        } else {
+            // connection denied
+            result = false;
+            n_denied += 1;
+            qd_log(policy->log_source, POLICY_LOG_LEVEL, "Connection '%s' denied, N=%d", hostname, n_connections);
+        }
+    }
+    n_processed += 1;
+    return result;
+}
+
+
+//
+//
+void qd_policy_socket_close(void *context, const qd_connection_t *conn)
+{
+    qd_policy_t *policy = (qd_policy_t *)context;
+
+    n_connections -= 1;
+    assert (n_connections >= 0);
+    if (policy->enableAccessRules) {
+        // HACK ALERT: TODO: This should be deferred to a Python thread
+        qd_python_lock_state_t lock_state = qd_python_lock();
+        PyObject *module = PyImport_ImportModule("qpid_dispatch_internal.policy.policy_manager");
+        if (module) {
+            PyObject *close_connection = PyObject_GetAttrString(module, "policy_close_connection");
+            if (close_connection) {
+                PyObject *result = PyObject_CallFunction(close_connection, "(OK)",
+                                                         (PyObject *)policy->py_policy_manager,
+                                                          conn->connection_id);
+                if (result) {
+                    Py_XDECREF(result);
+                } else {
+                    qd_log(policy->log_source, POLICY_LOG_LEVEL, "Connection close failed: result");
+                }
+                Py_XDECREF(close_connection);
+            } else {
+                qd_log(policy->log_source, POLICY_LOG_LEVEL, "Connection close failed: close_connection");
+            }
+            Py_XDECREF(module);
+        } else {
+            qd_log(policy->log_source, POLICY_LOG_LEVEL, "Connection close failed: module");
+        }
+        qd_python_unlock(lock_state);
+    }
+    if (policy->max_connection_limit > 0) {
+        const char *hostname = qdpn_connector_name(conn->pn_cxtr);
+        qd_log(policy->log_source, POLICY_LOG_LEVEL, "Connection '%s' closed. N connections=%d", hostname, n_connections);
+    }
+}
+
+
+//
+// Functions related to authenticated connection denial.
+// An AMQP Open has been received over some connection.
+// Evaluate the connection auth and the Open fields to
+// allow or deny the Open. Denied Open attempts are
+// effected by returning Open and then Close_with_condition.
+//
+/** Look up user/host/app in python policyRuleset and give the AMQP Open
+ *  a go-no_go decision. Return false if the mechanics of calling python
+ *  fails. A policy lookup will deny the connection by returning a blank
+ *  usergroup name in the name buffer.
+ *  Connection and connection denial counting is done in the python code.
+ * @param[in] policy pointer to policy
+ * @param[in] username authenticated user name
+ * @param[in] hostip numeric host ip address
+ * @param[in] app application name received in remote AMQP Open.hostname
+ * @param[in] conn_name connection name for tracking
+ * @param[out] name_buf pointer to settings name buffer
+ * @param[in] name_buf_size size of settings_buf
+ **/
+bool qd_policy_open_lookup_user(
+    qd_policy_t *policy,
+    const char *username,
+    const char *hostip,
+    const char *app,
+    const char *conn_name,
+    char       *name_buf,
+    int         name_buf_size,
+    uint64_t    conn_id,
+    qd_policy_settings_t *settings)
+{
+    // TODO: crolke 2016-03-24 - Workaround for PROTON-1133: Port number is included in Open hostname
+    // Strip the ':NNNN', if any, from the app name so that policy will work with proton 0.12
+    char appname[HOST_NAME_MAX + 1];
+    strncpy(appname, app, HOST_NAME_MAX);
+    appname[HOST_NAME_MAX] = 0;
+    char * colonp = strstr(appname, ":");
+    if (colonp) {
+        *colonp = 0;
+    }
+    // Lookup the user/host/app for allow/deny and to get settings name
+    bool res = false;
+    qd_python_lock_state_t lock_state = qd_python_lock();
+    PyObject *module = PyImport_ImportModule("qpid_dispatch_internal.policy.policy_manager");
+    if (module) {
+        PyObject *lookup_user = PyObject_GetAttrString(module, "policy_lookup_user");
+        if (lookup_user) {
+            PyObject *result = PyObject_CallFunction(lookup_user, "(OssssK)",
+                                                     (PyObject *)policy->py_policy_manager,
+                                                     username, hostip, appname, conn_name, conn_id);
+            if (result) {
+                const char *res_string = PyString_AsString(result);
+                strncpy(name_buf, res_string, name_buf_size);
+                Py_XDECREF(result);
+                res = true; // settings name returned
+            } else {
+                qd_log(policy->log_source, POLICY_LOG_LEVEL, "Internal: lookup_user: result");
+            }
+            Py_XDECREF(lookup_user);
+        } else {
+            qd_log(policy->log_source, POLICY_LOG_LEVEL, "Internal: lookup_user: lookup_user");
+        }
+    }
+    if (!res) {
+        if (module) {
+            Py_XDECREF(module);
+        }
+        qd_python_unlock(lock_state);
+        return false;
+    }
+
+    // 
+    if (name_buf[0]) {
+        // Go get the named settings
+        res = false;
+        PyObject *upolicy = PyDict_New();
+        if (upolicy) {
+            PyObject *lookup_settings = PyObject_GetAttrString(module, "policy_lookup_settings");
+            if (lookup_settings) {
+                PyObject *result2 = PyObject_CallFunction(lookup_settings, "(OssO)",
+                                                        (PyObject *)policy->py_policy_manager,
+                                                        appname, name_buf, upolicy);
+                if (result2) {
+                    settings->maxFrameSize         = qd_entity_opt_long((qd_entity_t*)upolicy, "maxFrameSize", 0);
+                    settings->maxMessageSize       = qd_entity_opt_long((qd_entity_t*)upolicy, "maxMessageSize", 0);
+                    settings->maxSessionWindow     = qd_entity_opt_long((qd_entity_t*)upolicy, "maxSessionWindow", 0);
+                    settings->maxSessions          = qd_entity_opt_long((qd_entity_t*)upolicy, "maxSessions", 0);
+                    settings->maxSenders           = qd_entity_opt_long((qd_entity_t*)upolicy, "maxSenders", 0);
+                    settings->maxReceivers         = qd_entity_opt_long((qd_entity_t*)upolicy, "maxReceivers", 0);
+                    settings->allowAnonymousSender = qd_entity_opt_bool((qd_entity_t*)upolicy, "allowAnonymousSender", false);
+                    settings->allowDynamicSrc      = qd_entity_opt_bool((qd_entity_t*)upolicy, "allowDynamicSrc", false);
+                    settings->sources              = qd_entity_get_string((qd_entity_t*)upolicy, "sources");
+                    settings->targets              = qd_entity_get_string((qd_entity_t*)upolicy, "targets");
+                    settings->denialCounts         = (qd_policy_denial_counts_t*)
+                                                    qd_entity_get_long((qd_entity_t*)upolicy, "denialCounts");
+                    Py_XDECREF(result2);
+                    res = true; // named settings content returned
+                } else {
+                    qd_log(policy->log_source, POLICY_LOG_LEVEL, "Internal: lookup_user: result2");
+                }
+                Py_XDECREF(lookup_settings);
+            } else {
+                qd_log(policy->log_source, POLICY_LOG_LEVEL, "Internal: lookup_user: lookup_settings");
+            }
+            Py_XDECREF(upolicy);
+        } else {
+            qd_log(policy->log_source, POLICY_LOG_LEVEL, "Internal: lookup_user: upolicy");
+        }
+    }
+    Py_XDECREF(module);
+    qd_python_unlock(lock_state);
+
+    qd_log(policy->log_source, 
+           POLICY_LOG_LEVEL, 
+           "Policy AMQP Open lookup_user: %s, hostip: %s, app: %s, connection: %s. Usergroup: '%s'%s",
+           username, hostip, appname, conn_name, name_buf, (res ? "" : " Internal error."));
+
+    return res;
+}
+
+
+//
+//
+void qd_policy_private_deny_amqp_connection(pn_connection_t *conn, const char *cond_name, const char *cond_descr)
+{
+    pn_condition_t * cond = pn_connection_condition(conn);
+    (void) pn_condition_set_name(       cond, cond_name);
+    (void) pn_condition_set_description(cond, cond_descr);
+    pn_connection_close(conn);
+}
+
+
+//
+//
+void qd_policy_deny_amqp_session(pn_session_t *ssn, qd_connection_t *qd_conn)
+{
+    pn_condition_t * cond = pn_session_condition(ssn);
+    (void) pn_condition_set_name(       cond, RESOURCE_LIMIT_EXCEEDED);
+    (void) pn_condition_set_description(cond, SESSION_DISALLOWED);
+    pn_session_close(ssn);
+
+    pn_connection_t *conn = qd_connection_pn(qd_conn);
+    qd_dispatch_t *qd = qd_conn->server->qd;
+    qd_policy_t *policy = qd->policy;
+    pn_transport_t *pn_trans = pn_connection_transport(conn);
+    const char *username = pn_transport_get_user(pn_trans);
+    const char *hostip = qdpn_connector_hostip(qd_conn->pn_cxtr);
+    const char *app = pn_connection_remote_hostname(conn);
+    qd_log(policy->log_source, 
+           POLICY_LOG_LEVEL, 
+           "Policy AMQP Begin Session denied due to session limit. user: %s, hostip: %s, app: %s", 
+           username, hostip, app);
+
+    qd_conn->policy_settings->denialCounts->sessionDenied++;
+}
+
+
+//
+//
+bool qd_policy_approve_amqp_session(pn_session_t *ssn, qd_connection_t *qd_conn)
+{
+    if (qd_conn->policy_settings) {
+        if (qd_conn->policy_settings->maxSessions) {
+            if (qd_conn->n_sessions == qd_conn->policy_settings->maxSessions) {
+                qd_policy_deny_amqp_session(ssn, qd_conn);
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+
+//
+//
+void qd_policy_apply_session_settings(pn_session_t *ssn, qd_connection_t *qd_conn)
+{
+    if (qd_conn->policy_settings && qd_conn->policy_settings->maxSessionWindow) {
+        pn_session_set_incoming_capacity(ssn, qd_conn->policy_settings->maxSessionWindow);
+    } else {
+        pn_session_set_incoming_capacity(ssn, 1000000);
+    }
+}
+
+//
+//
+void _qd_policy_deny_amqp_link(pn_link_t *link, qd_connection_t *qd_conn, char * s_or_r)
+{
+    pn_condition_t * cond = pn_link_condition(link);
+    (void) pn_condition_set_name(       cond, RESOURCE_LIMIT_EXCEEDED);
+    (void) pn_condition_set_description(cond, LINK_DISALLOWED);
+    pn_link_close(link);
+
+    pn_connection_t *conn = qd_connection_pn(qd_conn);
+    qd_dispatch_t *qd = qd_conn->server->qd;
+    qd_policy_t *policy = qd->policy;
+    pn_transport_t *pn_trans = pn_connection_transport(conn);
+    const char *username = pn_transport_get_user(pn_trans);
+    const char *hostip = qdpn_connector_hostip(qd_conn->pn_cxtr);
+    const char *app = pn_connection_remote_hostname(conn);
+    qd_log(policy->log_source, 
+           POLICY_LOG_LEVEL, 
+           "Policy AMQP Attach Link denied due to %s limit. user: %s, hostip: %s, app: %s", 
+           s_or_r, username, hostip, app);
+}
+
+
+//
+//
+void _qd_policy_deny_amqp_sender_link(pn_link_t *pn_link, qd_connection_t *qd_conn)
+{
+    _qd_policy_deny_amqp_link(pn_link, qd_conn, "sender");
+    qd_conn->policy_settings->denialCounts->senderDenied++;
+}
+
+
+//
+//
+void _qd_policy_deny_amqp_receiver_link(pn_link_t *pn_link, qd_connection_t *qd_conn)
+{
+    _qd_policy_deny_amqp_link(pn_link, qd_conn, "receiver");
+    qd_conn->policy_settings->denialCounts->receiverDenied++;
+}
+
+
+//
+//
+#define MIN(a,b) (((a)<(b))?(a):(b))
+
+char * _qd_policy_link_user_name_subst(const char *uname, const char *proposed, char *obuf, int osize)
+{
+    if (strlen(uname) == 0)
+        return NULL;
+    
+    const char *duser = "${user}";
+    char *retptr = obuf;
+    const char *wiptr = proposed;
+    const char *findptr = strstr(proposed, uname);
+    if (findptr == NULL) {
+        return NULL;
+    }
+
+    // Copy leading before match
+    int segsize = findptr - wiptr;
+    int copysize = MIN(osize, segsize);
+    if (copysize)
+        strncpy(obuf, wiptr, copysize);
+    wiptr += copysize;
+    osize -= copysize;
+    obuf  += copysize;
+
+    // Copy the substitution string
+    segsize = strlen(duser);
+    copysize = MIN(osize, segsize);
+    if (copysize)
+        strncpy(obuf, duser, copysize);
+    wiptr += strlen(uname);
+    osize -= copysize;
+    obuf  += copysize;
+
+    // Copy trailing after match
+    strncpy(obuf, wiptr, osize);
+    return retptr;
+}
+
+
+//
+//
+// Size of 'easy' temporary copy of allowed input string
+#define QPALN_SIZE 1024
+// Size of user-name-substituted proposed string.
+#define QPALN_USERBUFSIZE 300
+// C in the CSV string
+#define QPALN_COMMA_SEP ","
+// Wildcard character
+#define QPALN_WILDCARD '*'
+
+bool _qd_policy_approve_link_name(const char *username, const char *allowed, const char *proposed)
+{
+    // Verify string sizes are usable
+    size_t p_len = strlen(proposed);
+    if (p_len == 0) {
+        // degenerate case of blank name being opened. will never match anything.
+        return false;
+    }
+    size_t a_len = strlen(allowed);
+    if (a_len == 0) {
+        // no names in 'allowed'.
+        return false;
+    }
+
+    // Create a temporary writable copy of incoming allowed list
+    char t_allow[QPALN_SIZE + 1]; // temporary buffer for normal allow lists
+    char * pa = t_allow;
+    if (a_len > QPALN_SIZE) {
+        pa = (char *)malloc(a_len + 1); // malloc a buffer for larger allow lists
+    }
+    strncpy(pa, allowed, a_len);
+    pa[a_len] = 0;
+    // Do reverse user substitution into proposed
+    char substbuf[QPALN_USERBUFSIZE];
+    char * prop2 = _qd_policy_link_user_name_subst(username, proposed, substbuf, QPALN_USERBUFSIZE);
+    char *tok, *toknext;
+    tok = strtok_r(pa, QPALN_COMMA_SEP, &toknext);
+    assert (tok);
+    bool result = false;
+    while (tok != NULL) {
+        if (*tok == QPALN_WILDCARD) {
+            result = true;
+            break;
+        }
+        int matchlen = p_len;
+        int len = strlen(tok);
+        if (tok[len-1] == QPALN_WILDCARD) {
+            matchlen = len - 1;
+            assert(len > 0);
+        }
+        if (strncmp(tok, proposed, matchlen) == 0) {
+            result = true;
+            break;
+        }
+        if (prop2 && strncmp(tok, prop2, matchlen) == 0) {
+            result = true;
+            break;
+        }
+        tok = strtok_r(NULL, QPALN_COMMA_SEP, &toknext);
+    }
+    if (pa != t_allow) {
+        free(pa);
+    }
+    return result;
+}
+
+//
+//
+bool qd_policy_approve_amqp_sender_link(pn_link_t *pn_link, qd_connection_t *qd_conn)
+{
+    pn_connection_t *conn = qd_connection_pn(qd_conn);
+    pn_transport_t *pn_trans = pn_connection_transport(conn);
+    const char *username = pn_transport_get_user(pn_trans);
+    if (qd_conn->policy_settings->maxSenders) {
+        if (qd_conn->n_senders == qd_conn->policy_settings->maxSenders) {
+            // Max sender limit specified and violated.
+            _qd_policy_deny_amqp_sender_link(pn_link, qd_conn);
+            return false;
+        } else {
+            // max sender limit not violated
+        }
+    } else {
+        // max sender limit not specified
+    }
+    // Deny sender link based on target
+    const char * target = pn_terminus_get_address(pn_link_remote_target(pn_link));
+    bool lookup;
+    if (target && *target) {
+        // a target is specified
+        lookup = _qd_policy_approve_link_name(username, qd_conn->policy_settings->targets, target);
+
+        qd_log(qd_conn->server->qd->policy->log_source, QD_LOG_TRACE,
+            "Approve sender link '%s' for user '%s': %s",
+            target, username, (lookup ? "ALLOW" : "DENY"));
+
+        if (!lookup) {
+            _qd_policy_deny_amqp_receiver_link(pn_link, qd_conn);
+            return false;
+        }
+    } else {
+        // A sender with no remote target.
+        // This happens all the time with anonymous relay
+        lookup = qd_conn->policy_settings->allowAnonymousSender;
+        qd_log(qd_conn->server->qd->policy->log_source, QD_LOG_TRACE,
+            "Approve anonymous sender for user '%s': %s",
+            username, (lookup ? "ALLOW" : "DENY"));
+        if (!lookup) {
+            _qd_policy_deny_amqp_receiver_link(pn_link, qd_conn);
+            return false;
+        }
+    }
+    return true;
+}
+
+
+bool qd_policy_approve_amqp_receiver_link(pn_link_t *pn_link, qd_connection_t *qd_conn)
+{
+    pn_connection_t *conn = qd_connection_pn(qd_conn);
+    pn_transport_t *pn_trans = pn_connection_transport(conn);
+    const char *username = pn_transport_get_user(pn_trans);
+    if (qd_conn->policy_settings->maxReceivers) {
+        if (qd_conn->n_receivers == qd_conn->policy_settings->maxReceivers) {
+            // Max sender limit specified and violated.
+            _qd_policy_deny_amqp_receiver_link(pn_link, qd_conn);
+            return false;
+        } else {
+            // max receiver limit not violated
+        }
+    } else {
+        // max receiver limit not specified
+    }
+    // Deny receiver link based on source
+    bool dynamic_src = pn_terminus_is_dynamic(pn_link_remote_source(pn_link));
+    if (dynamic_src) {
+        bool lookup = qd_conn->policy_settings->allowDynamicSrc;
+        qd_log(qd_conn->server->qd->policy->log_source, QD_LOG_TRACE,
+            "Approve dynamic source for user '%s': %s",
+            username, (lookup ? "ALLOW" : "DENY"));
+        // Dynamic source policy rendered the decision
+        if (!lookup) {
+            _qd_policy_deny_amqp_receiver_link(pn_link, qd_conn);
+        }
+        return lookup;
+    }
+    const char * source = pn_terminus_get_address(pn_link_remote_source(pn_link));
+    if (source && *source) {
+        // a source is specified
+        bool lookup = _qd_policy_approve_link_name(username, qd_conn->policy_settings->sources, source);
+
+        qd_log(qd_conn->server->qd->policy->log_source, QD_LOG_TRACE,
+            "Approve receiver link '%s' for user '%s': %s",
+            source, username, (lookup ? "ALLOW" : "DENY"));
+
+        if (!lookup) {
+            _qd_policy_deny_amqp_receiver_link(pn_link, qd_conn);
+            return false;
+        }
+    } else {
+        // A receiver with no remote source.
+        qd_log(qd_conn->server->qd->policy->log_source, QD_LOG_TRACE,
+               "Approve receiver link '' for user '%s': DENY",
+               username);
+
+        _qd_policy_deny_amqp_receiver_link(pn_link, qd_conn);
+        return false;
+    }
+    return true;
+}
+
+
+//
+//
+void qd_policy_amqp_open(void *context, bool discard)
+{
+    qd_connection_t *qd_conn = (qd_connection_t *)context;
+    if (!discard) {
+        pn_connection_t *conn = qd_connection_pn(qd_conn);
+        qd_dispatch_t *qd = qd_conn->server->qd;
+        qd_policy_t *policy = qd->policy;
+        bool connection_allowed = true;
+
+        if (policy->enableAccessRules) {
+            // Open connection or not based on policy.
+            // username = pn_connection_get_user(conn) returns blank when
+            // the transport returns 'anonymous'.
+            pn_transport_t *pn_trans = pn_connection_transport(conn);
+            const char *username = pn_transport_get_user(pn_trans);
+
+            const char *hostip = qdpn_connector_hostip(qd_conn->pn_cxtr);
+            const char *app = pn_connection_remote_hostname(conn);
+            const char *conn_name = qdpn_connector_name(qd_conn->pn_cxtr);
+#define SETTINGS_NAME_SIZE 256
+            char settings_name[SETTINGS_NAME_SIZE];
+            uint32_t conn_id = qd_conn->connection_id;
+            qd_conn->policy_settings = NEW(qd_policy_settings_t); // TODO: memory pool for settings
+            memset(qd_conn->policy_settings, 0, sizeof(qd_policy_settings_t));
+
+            if (qd_policy_open_lookup_user(policy, username, hostip, app, conn_name, 
+                                           settings_name, SETTINGS_NAME_SIZE, conn_id,
+                                           qd_conn->policy_settings) &&
+                settings_name[0]) {
+                // This connection is allowed by policy.
+                // Apply tranport policy settings
+                if (qd_conn->policy_settings->maxFrameSize > 0)
+                    pn_transport_set_max_frame(pn_trans, qd_conn->policy_settings->maxFrameSize);
+                if (qd_conn->policy_settings->maxSessions > 0)
+                    pn_transport_set_channel_max(pn_trans, qd_conn->policy_settings->maxSessions);
+            } else {
+                // This connection is denied by policy.
+                connection_allowed = false;
+                qd_policy_private_deny_amqp_connection(conn, RESOURCE_LIMIT_EXCEEDED, CONNECTION_DISALLOWED);
+            }
+        } else {
+            // This connection not subject to policy and implicitly allowed.
+            // Note that connections not goverened by policy have no policy_settings.
+        }
+        if (connection_allowed) {
+            if (pn_connection_state(conn) & PN_LOCAL_UNINIT)
+                pn_connection_open(conn);
+            qd_connection_manager_connection_opened(qd_conn);
+            policy_notify_opened(qd_conn->open_container, qd_conn, qd_conn->conn_context);
+        }
+    }
+    qd_connection_set_event_stall(qd_conn, false);
+}

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/policy.h
----------------------------------------------------------------------
diff --git a/src/policy.h b/src/policy.h
new file mode 100644
index 0000000..d4fc2b2
--- /dev/null
+++ b/src/policy.h
@@ -0,0 +1,165 @@
+#ifndef __policy_h__
+#define __policy_h__
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "qpid/dispatch.h"
+#include "qpid/dispatch/server.h"
+#include "qpid/dispatch/ctools.h"
+#include "qpid/dispatch/static_assert.h"
+
+#include "config.h"
+#include "alloc.h"
+#include "entity.h"
+#include "entity_cache.h"
+#include <dlfcn.h>
+
+typedef struct qd_policy_denial_counts_s qd_policy_denial_counts_t;
+
+// TODO: Provide locking
+struct qd_policy_denial_counts_s {
+    int sessionDenied;
+    int senderDenied;
+    int receiverDenied;
+    int dynamicSrcDenied;
+    int anonymousSenderDenied;
+    int linkSourceDenied;
+    int linkTargetDenied;
+};
+
+typedef struct qd_policy_t qd_policy_t;
+
+struct qd_policy__settings_s {
+    int  maxFrameSize;
+    int  maxMessageSize;
+    int  maxSessionWindow;
+    int  maxSessions;
+    int  maxSenders;
+    int  maxReceivers;
+    bool allowDynamicSrc;
+    bool allowAnonymousSender;
+    char *sources;
+    char *targets;
+    qd_policy_denial_counts_t *denialCounts;
+};
+
+typedef struct qd_policy__settings_s qd_policy_settings_t;
+
+/** Configure the C policy entity from the settings in qdrouterd.conf["policy"]
+ * Called python-to-C during config processing.
+ * @param[in] policy pointer to the policy
+ * @param[in] entity pointer to the managed entity
+ * @return error or not. If error then the policy is freed.
+ **/
+qd_error_t qd_entity_configure_policy(qd_policy_t *policy, qd_entity_t *entity);
+
+/** Memorize the address of python policy_manager object.
+ * This python object gets called by C to execute user lookups 
+ * @param[in] policy pointer to the policy
+ * @param[in] policy_manager the address of the policy_manager object
+ **/
+qd_error_t qd_register_policy_manager(qd_policy_t *policy, void *policy_manager);
+
+
+/** Allocate counts statistics block.
+ * Called from Python
+ */
+long qd_policy_c_counts_alloc();
+
+/** Free counts statistics block.
+ * Called from Python
+ */
+void qd_policy_c_counts_free(long ccounts);
+
+/** Refresh a counts statistics block
+ * Called from Python
+ */
+qd_error_t qd_policy_c_counts_refresh(long ccounts, qd_entity_t*entity);
+
+
+/** Allow or deny an incoming connection based on connection count(s).
+ * A server listener has just accepted a socket.
+ * Allow or deny this connection based on the absolute number
+ *  of allowed connections.
+ * The identity of the connecting user has not been negotiated yet.
+ * @param[in] context the current policy
+ * @param[in] name the connector name
+ * @return the connection is allowed or not
+ **/
+bool qd_policy_socket_accept(void *context, const char *hostname);
+
+
+/** Record a closing connection.
+ * A server listener is closing a socket.
+ * Release the counted connection against provisioned limits
+ * 
+ * @param[in] context the current policy
+ * @param[in] conn qd_connection
+ **/
+void qd_policy_socket_close(void *context, const qd_connection_t *conn);
+
+
+/** Approve a new session based on connection's policy.
+ * Sessions denied are closed and counted.
+ *
+ * @param[in] ssn proton session being approved
+ * @param[in] qd_conn dispatch connection with policy settings and counts
+ **/
+bool qd_policy_approve_amqp_session(pn_session_t *ssn, qd_connection_t *qd_conn);
+
+
+/** Apply policy or default settings for a new session.
+ *
+ * @param[in] ssn proton session being set
+ * @param[in] qd_conn dispatch connection with policy settings and counts
+ **/
+void qd_policy_apply_session_settings(pn_session_t *ssn, qd_connection_t *qd_conn);
+
+
+/** Approve a new sender link based on connection's policy.
+ * Links denied are closed and counted.
+ *
+ * @param[in] pn_link proton link being approved
+ * @param[in] qd_conn dispatch connection with policy settings and counts
+ **/
+bool qd_policy_approve_amqp_sender_link(pn_link_t *pn_link, qd_connection_t *qd_conn);
+
+
+/** Approve a new receiver link based on connection's policy.
+ * Links denied are closed and counted.
+ *
+ * @param[in] pn_link proton link being approved
+ * @param[in] qd_conn dispatch connection with policy settings and counts
+ **/
+bool qd_policy_approve_amqp_receiver_link(pn_link_t *pn_link, qd_connection_t *qd_conn);
+
+
+/** Allow or deny an incoming connection.
+ * An Open performative was received over a new connection.
+ * Consult local policy to determine if this host/user is
+ *  allowed to make this connection.
+ * Denied pn_connections are closed with a condition.
+ * Allowed connections are signaled through qd_connection_manager.
+ * This function is called from the deferred queue.
+ * @param[in] context a qd_connection_t object
+ * @param[in] discard callback switch
+ **/
+void qd_policy_amqp_open(void *context, bool discard);
+
+#endif

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/policy_internal.h
----------------------------------------------------------------------
diff --git a/src/policy_internal.h b/src/policy_internal.h
new file mode 100644
index 0000000..9e865e6
--- /dev/null
+++ b/src/policy_internal.h
@@ -0,0 +1,103 @@
+#ifndef __policy_internal_h__
+#define __policy_internal_h__
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "policy.h"
+
+/**
+ * Private Function Prototypes
+ */
+/** Set the error condition and close the connection.
+ * Over the wire this will send an open frame followed
+ * immediately by a close frame with the error condition.
+ * @param[in] conn proton connection being closed
+ * @param[in] cond_name condition name
+ * @param[in] cond_descr condition description
+ **/ 
+void qd_policy_private_deny_amqp_connection(pn_connection_t *conn, const char *cond_name, const char *cond_descr);
+
+
+/** Internal function to deny an amqp session
+ * The session is closed with a condition and the denial is logged and counted.
+ * @param[in,out] ssn proton session being closed
+ * @param[in,out] qd_conn dispatch connection
+ */
+void qd_policy_deny_amqp_session(pn_session_t *ssn, qd_connection_t *qd_conn);
+
+
+/** Internal function to deny an amqp link
+ * The link is closed and the denial is logged but not counted.
+ * @param[in] link proton link being closed
+ * @param[in] qd_conn the qd conection
+ * @param[in] s_or_r 'sender' or 'receiver' for logging
+ */ 
+void _qd_policy_deny_amqp_link(pn_link_t *link, qd_connection_t *qd_conn, char * s_or_r);
+
+
+/** Internal function to deny a sender amqp link
+ * The link is closed and the denial is logged but not counted.
+ * @param[in] link proton link to close
+ * @param[in] qd_conn the qd conection
+ * @param[in] s_or_r 'sender' or 'receiver' for logging
+ */ 
+void _qd_policy_deny_amqp_sender_link(pn_link_t *pn_link, qd_connection_t *qd_conn);
+
+
+/** Internal function to deny a receiver amqp link
+ * The link is closed and the denial is logged but not counted.
+ * @param[in] link proton link to close
+ * @param[in] qd_conn the qd conection
+ * @param[in] s_or_r 'sender' or 'receiver' for logging
+ */ 
+void _qd_policy_deny_amqp_receiver_link(pn_link_t *pn_link, qd_connection_t *qd_conn);
+
+
+/** Perform user name substitution into proposed link name.
+ * The scheme is to substitute '${user}' into the incoming link name whereever the
+ * the username is present. Then it can be matched against the original template with
+ * a minimum of substitutions. For example:
+ * uname    : joe
+ * proposed : temp_joe_1
+ * obuf     : temp_${user}_1
+ * Note: substituted names are limited to osize characters
+ * Note: only the first (leftmost) user name is substituted.
+ *
+ * @param[in] uname auth user name
+ * @param[in] proposed the link name from the AMQP frame
+ * @param[out] obuf where the constructed link name is returned
+ * @param[in] osize size in bytes of obuf
+ * @return NULL if uname is not present in proposed link name.
+ */
+char * _qd_policy_link_user_name_subst(const char *uname, const char *proposed, char *obuf, int osize);
+
+
+/** Approve link by source/target name.
+ * This match supports trailing wildcard match:
+ *    proposed 'temp-305' matches allowed 'temp-*'
+ * This match supports username substitution:
+ *    user 'joe', proposed 'temp-joe' matches allowed 'temp-${user}'
+ * Both username substitution and wildcards are allowed:
+ *    user 'joe', proposed 'temp-joe-100' matches allowed 'temp-${user}*'
+ * @param[in] username authenticated user name
+ * @param[in] allowed policy settings source/target string in packed CSV form.
+ * @param[in] proposed the link target name to be approved
+ */
+bool _qd_policy_approve_link_name(const char *username, const char *allowed, const char *proposed);
+#endif

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/posix/driver.c
----------------------------------------------------------------------
diff --git a/src/posix/driver.c b/src/posix/driver.c
index 58c5e0c..98e1be4 100644
--- a/src/posix/driver.c
+++ b/src/posix/driver.c
@@ -104,6 +104,7 @@ struct qdpn_connector_t {
     DEQ_LINKS(qdpn_connector_t);
     qdpn_driver_t *driver;
     char name[PN_NAME_MAX];
+    char hostip[PN_NAME_MAX];
     pn_timestamp_t wakeup;
     pn_connection_t *connection;
     pn_transport_t *transport;
@@ -373,12 +374,13 @@ void qdpn_listener_set_context(qdpn_listener_t *listener, void *context)
     listener->context = context;
 }
 
-qdpn_connector_t *qdpn_listener_accept(qdpn_listener_t *l)
+qdpn_connector_t *qdpn_listener_accept(qdpn_listener_t *l, void *policy, bool (*policy_fn)(void *, const char *name))
 {
     if (!l || !l->pending) return NULL;
     char name[PN_NAME_MAX];
     char host[MAX_HOST];
     char serv[MAX_SERV];
+    char hostip[MAX_HOST];
 
     struct sockaddr_in addr = {0};
     addr.sin_family = AF_UNSPEC;
@@ -390,7 +392,8 @@ qdpn_connector_t *qdpn_listener_accept(qdpn_listener_t *l)
         return 0;
     } else {
         int code;
-        if ((code = getnameinfo((struct sockaddr *) &addr, addrlen, host, MAX_HOST, serv, MAX_SERV, 0))) {
+        if ((code = getnameinfo((struct sockaddr *) &addr, addrlen, host, MAX_HOST, serv, MAX_SERV, 0)) ||
+            (code = getnameinfo((struct sockaddr *) &addr, addrlen, hostip, MAX_HOST, 0, 0, NI_NUMERICHOST))) {
             qd_log(l->driver->log, QD_LOG_ERROR, "getnameinfo: %s\n", gai_strerror(code));
             close(sock);
             return 0;
@@ -400,11 +403,17 @@ qdpn_connector_t *qdpn_listener_accept(qdpn_listener_t *l)
         }
     }
 
+    if (policy_fn && !(*policy_fn)(policy, name)) {
+        close(sock);
+        return 0;
+    }
+    
     if (l->driver->trace & (PN_TRACE_FRM | PN_TRACE_RAW | PN_TRACE_DRV))
         fprintf(stderr, "Accepted from %s\n", name);
 
     qdpn_connector_t *c = qdpn_connector_fd(l->driver, sock, NULL);
     snprintf(c->name, PN_NAME_MAX, "%s", name);
+    snprintf(c->hostip, PN_NAME_MAX, "%s", hostip);
     c->listener = l;
     return c;
 }
@@ -608,6 +617,12 @@ const char *qdpn_connector_name(const qdpn_connector_t *ctor)
     return ctor->name;
 }
 
+const char *qdpn_connector_hostip(const qdpn_connector_t *ctor)
+{
+    if (!ctor) return 0;
+    return ctor->hostip;
+}
+
 qdpn_listener_t *qdpn_connector_listener(qdpn_connector_t *ctor)
 {
     return ctor ? ctor->listener : NULL;

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/server.c
----------------------------------------------------------------------
diff --git a/src/server.c b/src/server.c
index 173c175..5248540 100644
--- a/src/server.c
+++ b/src/server.c
@@ -25,6 +25,7 @@
 #include "entity.h"
 #include "entity_cache.h"
 #include "dispatch_private.h"
+#include "policy.h"
 #include "server_private.h"
 #include "timer_private.h"
 #include "alloc.h"
@@ -75,6 +76,19 @@ static qd_thread_t *thread(qd_server_t *qd_server, int id)
     return thread;
 }
 
+static void free_qd_connection(qd_connection_t *ctx)
+{
+    if (ctx->policy_settings) {
+        if (ctx->policy_settings->sources)
+            free(ctx->policy_settings->sources);
+        if (ctx->policy_settings->targets)
+            free(ctx->policy_settings->targets);
+        free (ctx->policy_settings);
+        ctx->policy_settings = 0;
+    }
+    free_qd_connection_t(ctx);
+}
+
 qd_error_t qd_entity_update_connection(qd_entity_t* entity, void *impl);
 
 /**
@@ -455,7 +469,7 @@ static void thread_process_listeners_LH(qd_server_t *qd_server)
     qd_connection_t  *ctx;
 
     for (listener = qdpn_driver_listener(driver); listener; listener = qdpn_driver_listener(driver)) {
-        cxtr = qdpn_listener_accept(listener);
+        cxtr = qdpn_listener_accept(listener, qd_server->qd->policy, &qd_policy_socket_accept);
         if (!cxtr)
             continue;
 
@@ -463,6 +477,7 @@ static void thread_process_listeners_LH(qd_server_t *qd_server)
 
         qd_log(qd_server->log_source, QD_LOG_DEBUG, "Accepting %s",
                log_incoming(logbuf, sizeof(logbuf), cxtr));
+        
         ctx = new_qd_connection_t();
         DEQ_ITEM_INIT(ctx);
         ctx->server        = qd_server;
@@ -480,8 +495,14 @@ static void thread_process_listeners_LH(qd_server_t *qd_server)
         ctx->link_context  = 0;
         ctx->ufd           = 0;
         ctx->connection_id = qd_server->next_connection_id++; // Increment the connection id so the next connection can use it
+        ctx->policy_settings = 0;
+        ctx->n_senders       = 0;
+        ctx->n_receivers     = 0;
+        ctx->open_container  = 0;
+        ctx->conn_context    = 0;
         DEQ_INIT(ctx->deferred_calls);
         ctx->deferred_call_lock = sys_mutex();
+        ctx->event_stall  = false;
 
         pn_connection_t *conn = pn_connection();
         ctx->collector = pn_collector();
@@ -652,40 +673,41 @@ static int process_connector(qd_server_t *qd_server, qdpn_connector_t *cxtr)
         pn_event_t      *event;
 
         events = 0;
-        event = pn_collector_peek(collector);
-        while (event) {
-            //
-            // If we are transitioning to the open state, notify the client via callback.
-            //
-            if (!ctx->opened && pn_event_type(event) == PN_CONNECTION_REMOTE_OPEN) {
-                ctx->opened = true;
-                qd_conn_event_t ce = QD_CONN_EVENT_LISTENER_OPEN;
-
-                if (ctx->connector) {
-                    ce = QD_CONN_EVENT_CONNECTOR_OPEN;
-                    ctx->connector->delay = 0;
-                } else
-                    assert(ctx->listener);
-
-                qd_server->conn_handler(qd_server->conn_handler_context,
-                                        ctx->context, ce, (qd_connection_t*) qdpn_connector_context(cxtr));
-                events = 1;
-                break;  // Break without popping this event.  It will be re-processed in OPERATIONAL state.
-            } else if (pn_event_type(event) == PN_TRANSPORT_ERROR) {
-                ctx->closed = true;
-                qdpn_connector_close(cxtr);
-                if (ctx->connector) {
-                    const qd_server_config_t *config = ctx->connector->config;
-                    qd_log(qd_server->log_source, QD_LOG_TRACE, "Connection to %s:%s failed", config->host, config->port);
+        if (!ctx->event_stall) {
+            event = pn_collector_peek(collector);
+            while (event) {
+                //
+                // If we are transitioning to the open state, notify the client via callback.
+                //
+                if (!ctx->opened && pn_event_type(event) == PN_CONNECTION_REMOTE_OPEN) {
+                    ctx->opened = true;
+                    qd_conn_event_t ce = QD_CONN_EVENT_LISTENER_OPEN;
+
+                    if (ctx->connector) {
+                        ce = QD_CONN_EVENT_CONNECTOR_OPEN;
+                        ctx->connector->delay = 0;
+                    } else
+                        assert(ctx->listener);
+
+                    qd_server->conn_handler(qd_server->conn_handler_context,
+                                            ctx->context, ce, (qd_connection_t*) qdpn_connector_context(cxtr));
+                    events = 1;
+                } else if (pn_event_type(event) == PN_TRANSPORT_ERROR) {
+                    ctx->closed = true;
+                    qdpn_connector_close(cxtr);
+                    if (ctx->connector) {
+                        const qd_server_config_t *config = ctx->connector->config;
+                        qd_log(qd_server->log_source, QD_LOG_TRACE, "Connection to %s:%s failed", config->host, config->port);
+                    }
                 }
-            }
 
-            events += qd_server->pn_event_handler(qd_server->conn_handler_context, ctx->context, event, qd_conn);
-            pn_collector_pop(collector);
-            event = pn_collector_peek(collector);
-        }
+                events += qd_server->pn_event_handler(qd_server->conn_handler_context, ctx->context, event, qd_conn);
+                pn_collector_pop(collector);
 
-        events += qd_server->conn_handler(qd_server->conn_handler_context, ctx->context, QD_CONN_EVENT_WRITABLE, qd_conn);
+                event = ctx->event_stall ? 0 : pn_collector_peek(collector);
+            }
+            events += qd_server->conn_handler(qd_server->conn_handler_context, ctx->context, QD_CONN_EVENT_WRITABLE, qd_conn);
+        }
     } while (events > 0);
 
     return passes > 1;
@@ -929,6 +951,10 @@ static void *thread_run(void *arg)
                 sys_mutex_lock(qd_server->lock);
                 DEQ_REMOVE(qd_server->connections, ctx);
 
+                if (!ctx->connector) {
+                    qd_policy_socket_close(qd_server->qd->policy, ctx);
+                }
+
                 qdpn_connector_free(cxtr);
                 if (conn) {
                     pn_connection_set_context(conn, 0);
@@ -938,7 +964,7 @@ static void *thread_run(void *arg)
                     pn_collector_free(ctx->collector);
                 invoke_deferred_calls(ctx, true);  // Discard any pending deferred calls
                 sys_mutex_free(ctx->deferred_call_lock);
-                free_qd_connection_t(ctx);
+                free_qd_connection(ctx);
                 qd_server->threads_active--;
                 sys_mutex_unlock(qd_server->lock);
             } else {
@@ -1027,9 +1053,15 @@ static void cxtr_try_open(void *context)
     ctx->user_context = 0;
     ctx->link_context = 0;
     ctx->ufd          = 0;
-
+    ctx->policy_settings = 0;
+    ctx->n_senders       = 0;
+    ctx->n_receivers     = 0;
+    ctx->open_container  = 0;
+    ctx->conn_context    = 0;
+    
     DEQ_INIT(ctx->deferred_calls);
     ctx->deferred_call_lock = sys_mutex();
+    ctx->event_stall  = false;
 
     qd_log(ct->server->log_source, QD_LOG_TRACE, "Connecting to %s:%s", ct->config->host, ct->config->port);
 
@@ -1053,7 +1085,7 @@ static void cxtr_try_open(void *context)
 
     if (ctx->pn_cxtr == 0) {
         sys_mutex_free(ctx->deferred_call_lock);
-        free_qd_connection_t(ctx);
+        free_qd_connection(ctx);
         ct->delay = 10000;
         qd_timer_schedule(ct->timer, ct->delay);
         return;
@@ -1489,6 +1521,14 @@ void qd_connection_invoke_deferred(qd_connection_t *conn, qd_deferred_t call, vo
 }
 
 
+void qd_connection_set_event_stall(qd_connection_t *conn, bool stall)
+{
+    conn->event_stall = stall;
+     if (!stall)
+         qd_server_activate(conn);
+}
+
+
 qd_listener_t *qd_server_listen(qd_dispatch_t *qd, const qd_server_config_t *config, void *context)
 {
     qd_server_t   *qd_server = qd->server;
@@ -1591,8 +1631,14 @@ qd_user_fd_t *qd_user_fd(qd_dispatch_t *qd, int fd, void *context)
     ctx->user_context = 0;
     ctx->link_context = 0;
     ctx->ufd          = ufd;
+    ctx->policy_settings = 0;
+    ctx->n_senders       = 0;
+    ctx->n_receivers     = 0;
+    ctx->open_container  = 0;
+    ctx->conn_context    = 0;
     DEQ_INIT(ctx->deferred_calls);
     ctx->deferred_call_lock = sys_mutex();
+    ctx->event_stall  = false;
 
     ufd->context = context;
     ufd->server  = qd_server;

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/src/server_private.h
----------------------------------------------------------------------
diff --git a/src/server_private.h b/src/server_private.h
index 7a0d1db..11da8e2 100644
--- a/src/server_private.h
+++ b/src/server_private.h
@@ -100,8 +100,15 @@ struct qd_connection_t {
     qd_user_fd_t             *ufd;
     uint64_t                  connection_id; // A unique identifier for the qd_connection_t. The underlying pn_connection already has one but it is long and clunky.
     const char               *user_id; // A unique identifier for the user on the connection. This is currently populated  from the client ssl cert. See ssl_uid_format in server.h for more info
+    qd_policy_settings_t     *policy_settings;
+    int                       n_sessions;
+    int                       n_senders;
+    int                       n_receivers;
+    void                     *open_container;
+    void                     *conn_context;
     qd_deferred_call_list_t   deferred_calls;
     sys_mutex_t              *deferred_call_lock;
+    bool                      event_stall;
 };
 
 DEQ_DECLARE(qd_connection_t, qd_connection_list_t);

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index f4f35d9..cd79939 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -28,6 +28,7 @@ include_directories(
 set(unit_test_SOURCES
     compose_test.c
     parse_test.c
+    policy_test.c
     run_unit_tests.c
     server_test.c
     timer_test.c
@@ -65,6 +66,7 @@ add_test(unit_tests            ${TEST_WRAP} --vg unit_tests ${CMAKE_CURRENT_SOUR
 # Unit test python modules
 add_test(router_engine_test    ${TEST_WRAP} -m unittest -v router_engine_test)
 add_test(managemet_test        ${TEST_WRAP} -m unittest -v management)
+add_test(router_policy_test    ${TEST_WRAP} -m unittest -v router_policy_test)
 
 # System test python modules
 foreach(py_test_module
@@ -72,6 +74,7 @@ foreach(py_test_module
     system_tests_link_routes
     system_tests_management
     system_tests_one_router
+    system_tests_policy
     system_tests_protocol_family
     system_tests_qdmanage
     system_tests_qdstat
@@ -93,9 +96,17 @@ list(APPEND SYSTEM_TEST_FILES
 
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-2/A-ssl.conf.in ${CMAKE_CURRENT_BINARY_DIR}/config-2/A-ssl.conf)
 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-2/B-ssl.conf.in ${CMAKE_CURRENT_BINARY_DIR}/config-2/B-ssl.conf)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/system_tests_policy.py.in ${CMAKE_CURRENT_BINARY_DIR}/system_tests_policy.py)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/policy-1/test-policy-conf-includes-folder.conf.in ${CMAKE_CURRENT_BINARY_DIR}/policy-1/test-policy-conf-includes-folder.conf)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/policy-2/policy-photoserver-sasl.conf.in ${CMAKE_CURRENT_BINARY_DIR}/policy-2/policy-photoserver-sasl.conf)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/policy-2/test-router-with-policy.json.in ${CMAKE_CURRENT_BINARY_DIR}/policy-2/test-router-with-policy.json)
 
 file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/ssl_certs DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
 file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/displayname_files DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-1/management-access.json  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/policy-1/)
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-1/policy-boardwalk.json   DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/policy-1/)
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-1/policy-safari.json      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/policy-1/)
+file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/policy-2/policy-photoserver-sasl.sasldb  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/policy-2)
 
 # following install() functions will be called only if you do a make "install"
 install(FILES ${SYSTEM_TEST_FILES}
@@ -114,4 +125,3 @@ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/config-2
 
 install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ssl_certs
         DESTINATION ${QPID_DISPATCH_HOME_INSTALLED}/tests)
-

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/config-1/A.json
----------------------------------------------------------------------
diff --git a/tests/config-1/A.json b/tests/config-1/A.json
new file mode 100644
index 0000000..01d62e1
--- /dev/null
+++ b/tests/config-1/A.json
@@ -0,0 +1,81 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##   http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied.  See the License for the
+## specific language governing permissions and limitations
+## under the License
+##
+
+
+[
+##
+## Container section - Configures the general operation of the AMQP container.
+##
+    ["container", {
+##
+## workerThreads - The number of threads that will be created to
+## process message traffic and other application work (timers, nonAmqp
+## file descriptors, etc.)
+##
+## The number of threads should be related to the number of available
+## processor cores.  To fully utilize a quad-core system, set the
+## number of threads to 4.
+##
+	"workerThreads": 4,
+	
+##
+## containerName - The name of the AMQP container.  If not specified,
+## the container name will be set to a value of the container's
+## choosing.  The automatically assigned container name is not
+## guaranteed to be persistent across restarts of the container.
+##
+	"containerName": "Qpid.Dispatch.Router.A"
+    }],
+
+    ["router", {
+	"mode": "standalone",
+	"routerId": "QDR"
+    }],
+
+##
+## Listeners and Connectors
+##
+    ["listener", {
+	"addr": "0.0.0.0",
+	"port": 20000,
+	"saslMechanisms": "ANONYMOUS"
+    }],
+    ["fixedAddress", {
+	"prefix": "/closest/",
+	"fanout": "single",
+	"bias": "closest"
+    }],
+
+    ["fixedAddress", {
+	"prefix": "/spread/",
+	"fanout": "single",
+	"bias": "spread"
+    }],
+
+    ["fixedAddress", {
+	"prefix": "/multicast/",
+	"fanout": "multiple"
+    }],
+
+    ["fixedAddress", {
+	"prefix": "/",
+	"fanout": "multiple"
+    }]
+]
+

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/policy-1/management-access.json
----------------------------------------------------------------------
diff --git a/tests/policy-1/management-access.json b/tests/policy-1/management-access.json
new file mode 100644
index 0000000..960c4a1
--- /dev/null
+++ b/tests/policy-1/management-access.json
@@ -0,0 +1,45 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##   http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied.  See the License for the
+## specific language governing permissions and limitations
+## under the License
+##
+
+# A policy to allow unrestricted access to management
+# from host 0.0.0.0
+[
+  ["policyRuleset", {
+      "applicationName": "0.0.0.0",
+      "maxConnections": 50,
+      "maxConnPerUser": 5,
+      "maxConnPerHost": 20,
+      "connectionAllowDefault": true,
+      "settings": {
+        "default" : {
+          "maxFrameSize":     222222,
+          "maxMessageSize":   222222,
+          "maxSessionWindow": 222222,
+          "maxSessions":           2,
+          "maxSenders":           22,
+          "maxReceivers":         22,
+          "allowDynamicSrc":      true,
+          "allowAnonymousSender": true,
+          "sources": "$management",
+          "targets": "$management"
+        }
+      }
+    }
+  ]
+]

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/policy-1/policy-boardwalk.json
----------------------------------------------------------------------
diff --git a/tests/policy-1/policy-boardwalk.json b/tests/policy-1/policy-boardwalk.json
new file mode 100644
index 0000000..174bdef
--- /dev/null
+++ b/tests/policy-1/policy-boardwalk.json
@@ -0,0 +1,96 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##   http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied.  See the License for the
+## specific language governing permissions and limitations
+## under the License
+##
+
+[
+    # The boardwalk policy ruleset
+    ["policyRuleset",
+        {
+            "applicationName": "boardwalk",
+            "maxConnections": 10,
+            "maxConnPerUser": 2,
+            "maxConnPerHost": 5,
+            "userGroups": {
+                "anonymous": "anonymous",
+                "users": "u1 u2 u3",
+                "superuser": "ellen"
+            },
+            "ingressHostGroups": {
+                "Ten18": "10.18.0.0-10.18.255.255",
+                "EllensWS": "72.135.2.9",
+                "TheLabs": "10.48.0.0-10.48.255.255, 192.168.0.0-192.168.255.255",
+                "Localhost": "127.0.0.1, ::1"
+            },
+            "ingressPolicies": {
+                "anonymous": "Ten18, TheLabs",
+                "superuser": "Localhost, EllensWS"
+            },
+            "connectionAllowDefault": true,
+            "settings": {
+                "anonymous": {
+                    "maxFrameSize": 111111,
+                    "maxMessageSize": 111111,
+                    "maxSessionWindow": 111111,
+                    "maxSessions": 1,
+                    "maxSenders": 11,
+                    "maxReceivers": 11,
+                    "allowDynamicSrc": false,
+                    "allowAnonymousSender": false,
+                    "sources": "public",
+                    "targets": ""
+                },
+                "users": {
+                    "maxFrameSize": 222222,
+                    "maxMessageSize": 222222,
+                    "maxSessionWindow": 222222,
+                    "maxSessions": 2,
+                    "maxSenders": 22,
+                    "maxReceivers": 22,
+                    "allowDynamicSrc": false,
+                    "allowAnonymousSender": false,
+                    "sources": "public, private",
+                    "targets": "public"
+                },
+                "superuser": {
+                    "maxFrameSize": 666666,
+                    "maxMessageSize": 666666,
+                    "maxSessionWindow": 666666,
+                    "maxSessions": 6,
+                    "maxSenders": 66,
+                    "maxReceivers": 66,
+                    "allowDynamicSrc": false,
+                    "allowAnonymousSender": false,
+                    "sources": "public, private, management, root",
+                    "targets": "public, private, management, root"
+                },
+                "default": {
+                    "maxFrameSize": 222222,
+                    "maxMessageSize": 222222,
+                    "maxSessionWindow": 222222,
+                    "maxSessions": 2,
+                    "maxSenders": 22,
+                    "maxReceivers": 22,
+                    "allowDynamicSrc": false,
+                    "allowAnonymousSender": false,
+                    "sources": "public, private",
+                    "targets": "public"
+                }
+            }
+        }
+    ]
+]

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/policy-1/policy-safari.json
----------------------------------------------------------------------
diff --git a/tests/policy-1/policy-safari.json b/tests/policy-1/policy-safari.json
new file mode 100644
index 0000000..a61bbd9
--- /dev/null
+++ b/tests/policy-1/policy-safari.json
@@ -0,0 +1,96 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##   http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied.  See the License for the
+## specific language governing permissions and limitations
+## under the License
+##
+[
+    # The safari policy ruleset
+    ["policyRuleset",
+        {
+            "applicationName": "safari",
+            "maxConnections": 10,
+            "maxConnPerUser": 2,
+            "maxConnPerHost": 5,
+            "userGroups": {
+                "anonymous": "anonymous",
+                "clients": "moja mbili",
+                "guides": "kata hapa"
+            },
+            "ingressHostGroups": {
+                "basecamp": "72.135.2.9",
+                "mobile": "10.48.0.0-10.48.255.255, 192.168.0.0-192.168.255.255",
+                "Localhost": "127.0.0.1, ::1",
+		"TheWorld": "*"
+            },
+            "ingressPolicies": {
+                "anonymous": "TheWorld",
+                "clients": "basecamp",
+		"guides": "basecamp, mobile, Localhost"
+            },
+            "connectionAllowDefault": true,
+            "settings": {
+                "anonymous": {
+                    "maxFrameSize": 111111,
+                    "maxMessageSize": 111111,
+                    "maxSessionWindow": 111111,
+                    "maxSessions": 1,
+                    "maxSenders": 11,
+                    "maxReceivers": 11,
+                    "allowDynamicSrc": false,
+                    "allowAnonymousSender": false,
+                    "sources": "public",
+                    "targets": ""
+                },
+                "clients": {
+                    "maxFrameSize": 222222,
+                    "maxMessageSize": 222222,
+                    "maxSessionWindow": 222222,
+                    "maxSessions": 2,
+                    "maxSenders": 22,
+                    "maxReceivers": 22,
+                    "allowDynamicSrc": false,
+                    "allowAnonymousSender": false,
+                    "sources": "public, private",
+                    "targets": "public"
+                },
+                "guides": {
+                    "maxFrameSize": 666666,
+                    "maxMessageSize": 666666,
+                    "maxSessionWindow": 666666,
+                    "maxSessions": 6,
+                    "maxSenders": 66,
+                    "maxReceivers": 66,
+                    "allowDynamicSrc": false,
+                    "allowAnonymousSender": false,
+                    "sources": "public, private, management, root",
+                    "targets": "public, private, management, root"
+                },
+                "default": {
+                    "maxFrameSize": 222222,
+                    "maxMessageSize": 222222,
+                    "maxSessionWindow": 222222,
+                    "maxSessions": 2,
+                    "maxSenders": 22,
+                    "maxReceivers": 22,
+                    "allowDynamicSrc": false,
+                    "allowAnonymousSender": false,
+                    "sources": "public, private",
+                    "targets": "public"
+                }
+            }
+        }
+    ]
+]

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/policy-1/test-policy-conf-includes-folder.conf.in
----------------------------------------------------------------------
diff --git a/tests/policy-1/test-policy-conf-includes-folder.conf.in b/tests/policy-1/test-policy-conf-includes-folder.conf.in
new file mode 100644
index 0000000..b6f0413
--- /dev/null
+++ b/tests/policy-1/test-policy-conf-includes-folder.conf.in
@@ -0,0 +1,64 @@
+##
+## Licensed to the Apache Software Foundation (ASF) under one
+## or more contributor license agreements.  See the NOTICE file
+## distributed with this work for additional information
+## regarding copyright ownership.  The ASF licenses this file
+## to you under the Apache License, Version 2.0 (the
+## "License"); you may not use this file except in compliance
+## with the License.  You may obtain a copy of the License at
+##
+##   http://www.apache.org/licenses/LICENSE-2.0
+##
+## Unless required by applicable law or agreed to in writing,
+## software distributed under the License is distributed on an
+## "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+## KIND, either express or implied.  See the License for the
+## specific language governing permissions and limitations
+## under the License
+##
+
+
+##
+## Container section - Configures the general operation of the AMQP container.
+##
+container {
+    ##
+    ## workerThreads - The number of threads that will be created to
+    ## process message traffic and other application work (timers, nonAmqp
+    ## file descriptors, etc.)
+    ##
+    ## The number of threads should be related to the number of available
+    ## processor cores.  To fully utilize a quad-core system, set the
+    ## number of threads to 4.
+    ##
+    workerThreads: 4
+
+    ##
+    ## containerName - The name of the AMQP container.  If not specified,
+    ## the container name will be set to a value of the container's
+    ## choosing.  The automatically assigned container name is not
+    ## guaranteed to be persistent across restarts of the container.
+    ##
+    containerName: Qpid.Dispatch.Router.A
+}
+
+router {
+    mode: standalone
+    routerId: QDR
+}
+
+
+##
+## Listeners and Connectors
+##
+listener {
+    addr: 0.0.0.0
+    port: 22000
+    saslMechanisms: ANONYMOUS
+}
+
+policy {
+    maximumConnections: 10
+    enableAccessRules: true
+    policyFolder: ${CMAKE_CURRENT_BINARY_DIR}/policy-1
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/policy-2/make-sasl.sh
----------------------------------------------------------------------
diff --git a/tests/policy-2/make-sasl.sh b/tests/policy-2/make-sasl.sh
new file mode 100755
index 0000000..d56d529
--- /dev/null
+++ b/tests/policy-2/make-sasl.sh
@@ -0,0 +1,63 @@
+#!/bin/bash -ex
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#
+# Generate sasl files for policy tests using test setup for 'photoserver'
+# This file is used to generate the files which are then committed to git
+# and distributed in the make install.
+#
+# sasldb file is generated and copied to tests/policy-2/ by cmake
+#
+export sasl_file=./policy-photoserver-sasl.sasldb
+
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd anonymous
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd u1
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd u2
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd p1
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd p2
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd zeke
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd ynot
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd alice
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd bob
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd ellen
+echo password | saslpasswd2 -c -p -f ${sasl_file} -u qdrouterd charlie
+
+echo password | saslpasswd2 -c -p -f ${sasl_file} anonymous
+echo password | saslpasswd2 -c -p -f ${sasl_file} u1
+echo password | saslpasswd2 -c -p -f ${sasl_file} u2
+echo password | saslpasswd2 -c -p -f ${sasl_file} p1
+echo password | saslpasswd2 -c -p -f ${sasl_file} p2
+echo password | saslpasswd2 -c -p -f ${sasl_file} zeke
+echo password | saslpasswd2 -c -p -f ${sasl_file} ynot
+echo password | saslpasswd2 -c -p -f ${sasl_file} alice
+echo password | saslpasswd2 -c -p -f ${sasl_file} bob
+echo password | saslpasswd2 -c -p -f ${sasl_file} ellen
+echo password | saslpasswd2 -c -p -f ${sasl_file} charlie
+
+sasldblistusers2                  -f ${sasl_file}
+
+# Make sasl conf file
+# sasl.conf is generated and 'config'd by cmake
+cat > ./policy-photoserver-sasl.conf.in << "EOF"
+pwcheck_method: auxprop
+auxprop_plugin: sasldb
+sasldb_path: ${CMAKE_CURRENT_BINARY_DIR}/policy-2/policy-photoserver-sasl.sasldb
+mech_list: PLAIN ANONYMOUS
+EOF

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/policy-2/policy-photoserver-sasl.conf.in
----------------------------------------------------------------------
diff --git a/tests/policy-2/policy-photoserver-sasl.conf.in b/tests/policy-2/policy-photoserver-sasl.conf.in
new file mode 100644
index 0000000..e9650b6
--- /dev/null
+++ b/tests/policy-2/policy-photoserver-sasl.conf.in
@@ -0,0 +1,4 @@
+pwcheck_method: auxprop
+auxprop_plugin: sasldb
+sasldb_path: ${CMAKE_CURRENT_BINARY_DIR}/policy-2/policy-photoserver-sasl.sasldb
+mech_list: PLAIN ANONYMOUS

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/policy-2/policy-photoserver-sasl.sasldb
----------------------------------------------------------------------
diff --git a/tests/policy-2/policy-photoserver-sasl.sasldb b/tests/policy-2/policy-photoserver-sasl.sasldb
new file mode 100644
index 0000000..f145b16
Binary files /dev/null and b/tests/policy-2/policy-photoserver-sasl.sasldb differ

http://git-wip-us.apache.org/repos/asf/qpid-dispatch/blob/48e81620/tests/policy-2/ssl_certs/gencerts.sh
----------------------------------------------------------------------
diff --git a/tests/policy-2/ssl_certs/gencerts.sh b/tests/policy-2/ssl_certs/gencerts.sh
new file mode 100755
index 0000000..faca0a2
--- /dev/null
+++ b/tests/policy-2/ssl_certs/gencerts.sh
@@ -0,0 +1,39 @@
+#!/bin/bash -ex
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+export SERVER=A1.Good.Server.domain.com
+export CLIENT=127.0.0.1
+
+keytool -storetype pkcs12 -keystore ca.pkcs12 -storepass ca-password -alias ca -keypass ca-password -genkey -dname "O=Trust Me Inc.,CN=Trusted.CA.com" -validity 99999
+openssl pkcs12 -nokeys -passin pass:ca-password -in ca.pkcs12 -passout pass:ca-password -out ca-certificate.pem
+
+keytool -storetype pkcs12 -keystore bad-ca.pkcs12 -storepass bad-ca-password -alias bad-ca -keypass bad-ca-password -genkey -dname "O=Trust Me Inc.,CN=Trusted.CA.com" -validity 99999
+openssl pkcs12 -nokeys -passin pass:bad-ca-password -in bad-ca.pkcs12 -passout pass:bad-ca-password -out bad-ca-certificate.pem
+
+keytool -storetype pkcs12 -keystore server.pkcs12 -storepass server-password -alias server-certificate -keypass server-password -genkey  -dname "O=Server,CN=$SERVER" -validity 99999
+keytool -storetype pkcs12 -keystore server.pkcs12 -storepass server-password -alias server-certificate -keypass server-password -certreq -file server-request.pem
+keytool -storetype pkcs12 -keystore ca.pkcs12 -storepass ca-password -alias ca -keypass ca-password -gencert -rfc -validity 99999 -infile server-request.pem -outfile server-certificate.pem
+openssl pkcs12 -nocerts -passin pass:server-password -in server.pkcs12 -passout pass:server-password -out server-private-key.pem
+
+keytool -storetype pkcs12 -keystore client.pkcs12 -storepass client-password -alias client-certificate -keypass client-password -genkey  -dname "O=Client,CN=$CLIENT" -validity 99999
+keytool -storetype pkcs12 -keystore client.pkcs12 -storepass client-password -alias client-certificate -keypass client-password -certreq -file client-request.pem
+keytool -storetype pkcs12 -keystore ca.pkcs12 -storepass ca-password -alias ca -keypass ca-password -gencert -rfc -validity 99999 -infile client-request.pem -outfile client-certificate.pem
+openssl pkcs12 -nocerts -passin pass:client-password -in client.pkcs12 -passout pass:client-password -out client-private-key.pem


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org