blob: 45908bdb34e03e6ad37fe7f980f045a50a65eea8 [file]
/*
* 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_private.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 <qpid/dispatch/message.h>
#include <proton/engine.h>
#include <proton/message.h>
#include <proton/condition.h>
#include <proton/connection.h>
#include <proton/error.h>
#include <proton/event.h>
#include <qpid/dispatch/ctools.h>
#include <qpid/dispatch/hash.h>
#include <qpid/dispatch/threading.h>
#include <qpid/dispatch/iterator.h>
#include <qpid/dispatch/log.h>
//
// TODO: get a real policy engine
// This engine accepts every other connection
//
static bool allow_this = true;
bool policy_engine()
{
return allow_this;
}
void policy_engine_step()
{
// allow_this = !allow_this;
}
//
// 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";
//
// Policy configuration/statistics management interface
//
struct qd_policy_t {
qd_dispatch_t *qd;
qd_log_source_t *log_source;
// configured settings
int max_connection_limit;
char *policyDb;
// live statistics
int connections_processed;
int connections_denied;
int connections_current;
};
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->policyDb = 0;
policy->connections_processed= 0;
policy->connections_denied = 0;
policy->connections_current = 0;
qd_log(policy->log_source, QD_LOG_TRACE, "Policy Initialized");
return policy;
}
void qd_policy_free(qd_policy_t *policy)
{
if (policy->policyDb)
free(policy->policyDb);
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->policyDb =
qd_entity_opt_string(entity, "policyDb", 0); CHECK();
qd_log(policy->log_source, QD_LOG_INFO, "Configured maximumConnections: %d", policy->max_connection_limit);
return QD_ERROR_NONE;
error:
qd_policy_free(policy);
return qd_error_code();
}
//
//
qd_error_t qd_entity_refresh_policy(qd_entity_t* entity, void *impl) {
// 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 char *hostname)
{
qd_policy_t *policy = (qd_policy_t *)context;
n_connections -= 1;
assert (n_connections >= 0);
if (policy->max_connection_limit > 0) {
qd_log(policy->log_source, POLICY_LOG_LEVEL, "Connection '%s' closed, N=%d", hostname, n_connections);
}
qd_log(policy->log_source, POLICY_LOG_LEVEL, "Connection '%s' closed, N=%d", hostname, n_connections); // HACK EXTRA
}
//
// 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 with a returned Open-Close_with_condition.
//
void qd_policy_private_deny_amqp_connection(pn_connection_t *conn, const char *cond_name, const char *cond_descr)
{
// 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.
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_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);
// Consult policy engine for this connection attempt
if ( policy_engine() ) { // TODO: get rid of this phony policy engine
// This connection is allowed.
if (pn_connection_state(conn) & PN_LOCAL_UNINIT)
pn_connection_open(conn);
qd_connection_manager_connection_opened(qd_conn);
} else {
// This connection is denied.
qd_policy_private_deny_amqp_connection(conn, RESOURCE_LIMIT_EXCEEDED, CONNECTION_DISALLOWED);
}
// update the phony policy engine
policy_engine_step();
}
qd_connection_set_event_stall(qd_conn, false);
}