summaryrefslogtreecommitdiff
path: root/qpid/cpp/src
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/cpp/src')
-rw-r--r--qpid/cpp/src/CMakeLists.txt1
-rw-r--r--qpid/cpp/src/qpid/AclHost.cpp151
-rw-r--r--qpid/cpp/src/qpid/AclHost.h95
-rw-r--r--qpid/cpp/src/qpid/acl/Acl.cpp11
-rw-r--r--qpid/cpp/src/qpid/acl/Acl.h1
-rw-r--r--qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp76
-rw-r--r--qpid/cpp/src/qpid/acl/AclConnectionCounter.h7
-rw-r--r--qpid/cpp/src/qpid/acl/AclData.cpp47
-rw-r--r--qpid/cpp/src/qpid/acl/AclData.h64
-rw-r--r--qpid/cpp/src/qpid/acl/AclLexer.cpp6
-rw-r--r--qpid/cpp/src/qpid/acl/AclLexer.h3
-rw-r--r--qpid/cpp/src/qpid/acl/AclReader.cpp64
-rw-r--r--qpid/cpp/src/qpid/acl/AclReader.h9
-rw-r--r--qpid/cpp/src/qpid/acl/AclValidator.cpp1
-rw-r--r--qpid/cpp/src/qpid/broker/AclModule.h2
-rw-r--r--qpid/cpp/src/qpid/sys/SocketAddress.h27
-rw-r--r--qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp207
-rw-r--r--qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp205
-rw-r--r--qpid/cpp/src/tests/Acl.cpp21
-rw-r--r--qpid/cpp/src/tests/AclHost.cpp119
-rw-r--r--qpid/cpp/src/tests/CMakeLists.txt1
21 files changed, 1057 insertions, 61 deletions
diff --git a/qpid/cpp/src/CMakeLists.txt b/qpid/cpp/src/CMakeLists.txt
index 21762965cb..ebdff57961 100644
--- a/qpid/cpp/src/CMakeLists.txt
+++ b/qpid/cpp/src/CMakeLists.txt
@@ -740,6 +740,7 @@ set (qpidcommon_SOURCES
${qpidcommon_sasl_source}
${sslcommon_SOURCES}
qpid/assert.cpp
+ qpid/AclHost.cpp
qpid/Address.cpp
qpid/DataDir.cpp
qpid/Exception.cpp
diff --git a/qpid/cpp/src/qpid/AclHost.cpp b/qpid/cpp/src/qpid/AclHost.cpp
new file mode 100644
index 0000000000..1581d3b46a
--- /dev/null
+++ b/qpid/cpp/src/qpid/AclHost.cpp
@@ -0,0 +1,151 @@
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed 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/AclHost.h"
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include "qpid/StringUtils.h"
+#include "qpid/log/Logger.h"
+#include "qpid/sys/SocketAddress.h"
+
+#include <vector>
+#include <string>
+
+using namespace std;
+
+namespace qpid {
+
+AclHost::Invalid::Invalid(const string& s) : Exception(s) {}
+
+string AclHost::str() const {
+ if (cache.empty()) {
+ ostringstream os;
+ os << *this;
+ cache = os.str();
+ }
+ return cache;
+}
+
+std::string undecorateIPv6Name(std::string& host) {
+ std::string s(host);
+ if (host.length() >= 3 && host.find("[") == 0 && host.rfind("]") == host.length()-1)
+ s = host.substr(1, host.length()-2);
+ return s;
+}
+
+ostream& operator<<(ostream& os, const AclHost& aclhost) {
+ os << aclhost.comparisonDetails();
+ return os;
+}
+
+class AclHostParser {
+ public:
+ AclHostParser(AclHost& ah, const std::string& hSpec) :
+ aclhost(ah), hostSpec(hSpec) {}
+
+ bool parse() {
+ // Convert given host spec into vector of host names
+ // Blank host name means "all addresses. Create AclHost
+ // with no SocketAddress objects
+ if (hostSpec.compare("") == 0) {
+ aclhost.allAddresses = true;
+ return true;
+ }
+ std::vector<string> hostList;
+ split(hostList, hostSpec, ",");
+ if (hostList.size() == 0 || hostList.size() > 2) {
+ throw AclHost::Invalid(
+ QPID_MSG("Invalid AclHost: hostlist must be one name or "
+ "two names separated with a comma : " << hostSpec));
+ }
+ // Create pairs of SocketAddress objects representing the host range
+ if (hostList.size() == 1) {
+ hostList[0] = undecorateIPv6Name(hostList[0]);
+ aclhost.loSAptr = AclHost::SAptr(new sys::SocketAddress(hostList[0], ""));
+ aclhost.hiSAptr = AclHost::SAptr(new sys::SocketAddress(hostList[0], ""));
+ } else {
+ hostList[0] = undecorateIPv6Name(hostList[0]);
+ hostList[1] = undecorateIPv6Name(hostList[1]);
+ aclhost.loSAptr = AclHost::SAptr(new sys::SocketAddress(hostList[0], ""));
+ aclhost.hiSAptr = AclHost::SAptr(new sys::SocketAddress(hostList[1], ""));
+ }
+ // Make sure that this pair will work for run-time comparisons
+ if (!aclhost.loSAptr->isComparable(*aclhost.hiSAptr)) {
+ throw AclHost::Invalid(
+ QPID_MSG("AclHost specifies hosts that cannot be compared : " << hostSpec));
+ }
+
+ return true;
+ }
+
+ AclHost& aclhost;
+ const std::string& hostSpec;
+};
+
+void AclHost::parse(const std::string& hostSpec) {
+ parseNoThrow(hostSpec);
+ if (isEmpty() && !allAddresses)
+ throw AclHost::Invalid(QPID_MSG("Invalid AclHost : " << hostSpec));
+}
+
+void AclHost::parseNoThrow(const std::string& hostSpec) {
+ clear();
+ try {
+ if (!AclHostParser(*this, hostSpec).parse())
+ clear();
+ } catch (...) {
+ clear();
+ }
+}
+
+std::istream& operator>>(std::istream& is, AclHost& aclhost) {
+ std::string s;
+ is >> s;
+ aclhost.parse(s);
+ return is;
+}
+
+/**
+ * Given a connecting host's numeric IP address as a string
+ * Return true if the host is in the range of any of our kept
+ * SocketAddress's binary address ranges.
+ */
+bool AclHost::match(const std::string& hostIp) const {
+ try {
+ sys::SocketAddress sa1(hostIp, "");
+ return match(sa1);
+ } catch (...) {
+ return false;
+ }
+}
+
+/**
+ * Given a connecting host's SocketAddress
+ * Return true if the host is in the range of any of our kept
+ * SocketAddress's binary address ranges.
+ */
+bool AclHost::match(const sys::SocketAddress& peer) const {
+ if (!loSAptr.get()) {
+ // No kept socket address means "all addresses"
+ return true;
+ }
+ bool result = peer.inRange(*loSAptr, *hiSAptr);
+ return result;
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/AclHost.h b/qpid/cpp/src/qpid/AclHost.h
new file mode 100644
index 0000000000..decaf3cad3
--- /dev/null
+++ b/qpid/cpp/src/qpid/AclHost.h
@@ -0,0 +1,95 @@
+#ifndef QPID_ACLHOST_H
+#define QPID_ACLHOST_H
+
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed 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/sys/SocketAddress.h"
+#include "qpid/Exception.h"
+#include <utility>
+#include <string>
+#include <vector>
+#include <new>
+#include <ostream>
+#include <boost/shared_ptr.hpp>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+
+/** An AclHost contains shared_ptrs to two SocketAddresses
+ * representing a low-high pair of addresses.
+ */
+class AclHost {
+ public:
+ typedef boost::shared_ptr<sys::SocketAddress> SAptr;
+
+ struct Invalid : public Exception { QPID_COMMON_EXTERN Invalid(const std::string& s); };
+
+ /** Convert to string form. */
+ QPID_COMMON_EXTERN std::string str() const;
+
+ /** Empty AclHost. */
+ AclHost() : allAddresses(false) {}
+
+ explicit AclHost(const std::string& hostSpec) { parse(hostSpec); }
+
+ QPID_COMMON_EXTERN std::string comparisonDetails() const {
+ if (loSAptr.get()) {
+ return loSAptr->comparisonDetails(*hiSAptr);
+ } else {
+ return "(all)";
+ }
+ }
+
+ QPID_COMMON_EXTERN bool match(const std::string& hostIp) const;
+
+ QPID_COMMON_EXTERN bool match(const sys::SocketAddress& sa) const;
+
+ QPID_COMMON_EXTERN void parse( const std::string& hostSpec);
+ QPID_COMMON_EXTERN void parseNoThrow(const std::string& hostSpec);
+
+ QPID_COMMON_EXTERN void clear() {
+ cache.clear();
+ loSAptr.reset();
+ hiSAptr.reset();
+ }
+
+ QPID_COMMON_EXTERN bool isEmpty() const {
+ return !loSAptr.get() && !hiSAptr.get(); }
+
+ QPID_COMMON_EXTERN bool isAllAddresses() const { return allAddresses; }
+
+ private:
+ mutable std::string cache; // cache string form for efficiency.
+
+ bool allAddresses;
+ SAptr loSAptr;
+ SAptr hiSAptr;
+
+ friend class AclHostParser;
+};
+
+inline bool operator==(const AclHost& a, const AclHost& b) { return a.str()==b.str(); }
+inline bool operator!=(const AclHost& a, const AclHost& b) { return a.str()!=b.str(); }
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& os, const AclHost& aclhost);
+QPID_COMMON_EXTERN std::istream& operator>>(std::istream& is, AclHost& aclhost);
+
+} // namespace qpid
+
+#endif /*!QPID_ACLHOST_H*/
diff --git a/qpid/cpp/src/qpid/acl/Acl.cpp b/qpid/cpp/src/qpid/acl/Acl.cpp
index 6e2c50ff43..cc3a08c754 100644
--- a/qpid/cpp/src/qpid/acl/Acl.cpp
+++ b/qpid/cpp/src/qpid/acl/Acl.cpp
@@ -169,8 +169,17 @@ bool Acl::approveConnection(const qpid::broker::Connection& conn)
}
(void) dataLocal->getConnQuotaForUser(userName, &connectionLimit);
+ boost::shared_ptr<const AclData::bwHostRuleSet> globalRules = dataLocal->getGlobalConnectionRules();
+ boost::shared_ptr<const AclData::bwHostRuleSet> userRules = dataLocal->getUserConnectionRules(userName);
- return connectionCounter->approveConnection(conn, dataLocal->enforcingConnectionQuotas(), connectionLimit);
+
+ return connectionCounter->approveConnection(conn,
+ userName,
+ dataLocal->enforcingConnectionQuotas(),
+ connectionLimit,
+ globalRules,
+ userRules
+ );
}
bool Acl::approveCreateQueue(const std::string& userId, const std::string& queueName)
diff --git a/qpid/cpp/src/qpid/acl/Acl.h b/qpid/cpp/src/qpid/acl/Acl.h
index 655e1554f6..df2fb66c82 100644
--- a/qpid/cpp/src/qpid/acl/Acl.h
+++ b/qpid/cpp/src/qpid/acl/Acl.h
@@ -23,6 +23,7 @@
#include "qpid/acl/AclReader.h"
+#include "qpid/AclHost.h"
#include "qpid/RefCounted.h"
#include "qpid/broker/AclModule.h"
#include "qpid/management/Manageable.h"
diff --git a/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp b/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp
index 7a5d0ab53d..75cca29b02 100644
--- a/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp
+++ b/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp
@@ -24,6 +24,7 @@
#include "qpid/broker/Connection.h"
#include "qpid/log/Statement.h"
#include "qpid/sys/Mutex.h"
+#include "qpid/sys/SocketAddress.h"
#include <assert.h>
#include <sstream>
@@ -211,11 +212,13 @@ void ConnectionCounter::closed(broker::Connection& connection) {
//
bool ConnectionCounter::approveConnection(
const broker::Connection& connection,
+ const std::string& userName,
bool enforcingConnectionQuotas,
- uint16_t connectionUserQuota )
+ uint16_t connectionUserQuota,
+ boost::shared_ptr<const AclData::bwHostRuleSet> globalBWRules,
+ boost::shared_ptr<const AclData::bwHostRuleSet> userBWRules)
{
const std::string& hostName(getClientHost(connection.getMgmtId()));
- const std::string& userName( connection.getUserId());
Mutex::ScopedLock locker(dataLock);
@@ -223,6 +226,75 @@ bool ConnectionCounter::approveConnection(
(void) countConnectionLH(connectProgressMap, connection.getMgmtId(),
C_OPENED, false, false);
+ // Run global black/white list check
+ //
+ // TODO: The global check could be run way back in AsynchIO where
+ // disapproval would mean that the socket is not accepted. Or
+ // it may be accepted and closed right away without running any
+ // protocol and creating the connection churn that gets here.
+ //
+ sys::SocketAddress sa(hostName, "");
+ if (sa.isIp()) {
+ if (boost::shared_ptr<const AclData::bwHostRuleSet>() != globalBWRules) {
+ AclData::bwHostRuleSet::const_iterator it;
+ for (it=globalBWRules->begin(); it!=globalBWRules->end(); it++) {
+ if (it->getAclHost().match(hostName)) {
+ // This host matches a global spec and controls the
+ // allow/deny decision for this connection.
+ AclResult res = it->getAclResult();
+ if (res == DENY || res == DENYLOG) {
+ // The result is deny
+ QPID_LOG(trace, "ACL ConnectionApprover global rule " << it->toString()
+ << " denies connection for host " << hostName << ", user "
+ << userName);
+ acl.reportConnectLimit(userName, hostName);
+ return false;
+ } else {
+ // The result is allow
+ QPID_LOG(trace, "ACL ConnectionApprover global rule " << it->toString()
+ << " allows connection for host " << hostName << ", user "
+ << userName);
+ break;
+ }
+ } else {
+ // This rule in the global spec doesn't match and
+ // does not control the allow/deny decision.
+ }
+ }
+ }
+
+ // Run user black/white list check
+ if (boost::shared_ptr<const AclData::bwHostRuleSet>() != userBWRules) {
+ AclData::bwHostRuleSet::const_iterator it;
+ for (it=userBWRules->begin(); it!=userBWRules->end(); it++) {
+ if (it->getAclHost().match(hostName)) {
+ // This host matches a user spec and controls the
+ // allow/deny decision for this connection.
+ AclResult res = it->getAclResult();
+ if (res == DENY || res == DENYLOG) {
+ // The result is deny
+ QPID_LOG(trace, "ACL ConnectionApprover user rule " << it->toString()
+ << " denies connection for host " << hostName << ", user "
+ << userName);
+ acl.reportConnectLimit(userName, hostName);
+ return false;
+ } else {
+ // The result is allow
+ QPID_LOG(trace, "ACL ConnectionApprover user rule " << it->toString()
+ << " allows connection for host " << hostName << ", user "
+ << userName);
+ break;
+ }
+ } else {
+ // This rule in the user's spec doesn't match and
+ // does not control the allow/deny decision.
+ }
+ }
+ }
+ } else {
+ // Non-IP hosts don't get subjected to blacklist and whitelist
+ // checks.
+ }
// Approve total connections
bool okTotal = true;
if (totalLimit > 0) {
diff --git a/qpid/cpp/src/qpid/acl/AclConnectionCounter.h b/qpid/cpp/src/qpid/acl/AclConnectionCounter.h
index 43182e1815..6b8f396867 100644
--- a/qpid/cpp/src/qpid/acl/AclConnectionCounter.h
+++ b/qpid/cpp/src/qpid/acl/AclConnectionCounter.h
@@ -24,6 +24,7 @@
#include "qpid/broker/ConnectionObserver.h"
#include "qpid/sys/Mutex.h"
+#include "qpid/acl/AclData.h"
#include <map>
@@ -93,8 +94,12 @@ public:
// Connection counting
bool approveConnection(const broker::Connection& conn,
+ const std::string& userName,
bool enforcingConnectionQuotas,
- uint16_t connectionLimit );
+ uint16_t connectionLimit,
+ boost::shared_ptr<const AclData::bwHostRuleSet> globalBWRules,
+ boost::shared_ptr<const AclData::bwHostRuleSet> userBWRules
+ );
};
}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/acl/AclData.cpp b/qpid/cpp/src/qpid/acl/AclData.cpp
index 48b5e462e5..f193eec372 100644
--- a/qpid/cpp/src/qpid/acl/AclData.cpp
+++ b/qpid/cpp/src/qpid/acl/AclData.cpp
@@ -20,6 +20,8 @@
#include "qpid/log/Statement.h"
#include "qpid/sys/IntegerTypes.h"
#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/make_shared.hpp>
namespace qpid {
namespace acl {
@@ -48,10 +50,10 @@ AclData::AclData():
decisionMode(qpid::acl::DENY),
transferAcl(false),
aclSource("UNKNOWN"),
- connQuotaRulesExist(false),
connQuotaRuleSettings(new quotaRuleSet),
- queueQuotaRulesExist(false),
- queueQuotaRuleSettings(new quotaRuleSet)
+ queueQuotaRuleSettings(new quotaRuleSet),
+ connBWHostsGlobalRules(new bwHostRuleSet),
+ connBWHostsRuleSettings(new bwHostUserRuleMap)
{
for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) {
actionList[cnt]=0;
@@ -73,10 +75,10 @@ void AclData::clear ()
delete[] actionList[cnt];
}
transferAcl = false;
- connQuotaRulesExist = false;
connQuotaRuleSettings->clear();
- queueQuotaRulesExist = false;
queueQuotaRuleSettings->clear();
+ connBWHostsGlobalRules->clear();
+ connBWHostsRuleSettings->clear();
}
@@ -509,9 +511,8 @@ AclResult AclData::lookup(
//
//
void AclData::setConnQuotaRuleSettings (
- bool rulesExist, boost::shared_ptr<quotaRuleSet> quotaPtr)
+ boost::shared_ptr<quotaRuleSet> quotaPtr)
{
- connQuotaRulesExist = rulesExist;
connQuotaRuleSettings = quotaPtr;
}
@@ -530,7 +531,7 @@ void AclData::setConnQuotaRuleSettings (
//
bool AclData::getConnQuotaForUser(const std::string& theUserName,
uint16_t* theResult) const {
- if (connQuotaRulesExist) {
+ if (this->enforcingConnectionQuotas()) {
// look for this user explicitly
quotaRuleSetItr nameItr = (*connQuotaRuleSettings).find(theUserName);
if (nameItr != (*connQuotaRuleSettings).end()) {
@@ -557,16 +558,15 @@ bool AclData::getConnQuotaForUser(const std::string& theUserName,
<< " unavailable; quota settings are not specified. Return value : 0");
*theResult = 0;
}
- return connQuotaRulesExist;
+ return this->enforcingConnectionQuotas();
}
//
//
//
void AclData::setQueueQuotaRuleSettings (
- bool rulesExist, boost::shared_ptr<quotaRuleSet> quotaPtr)
+ boost::shared_ptr<quotaRuleSet> quotaPtr)
{
- queueQuotaRulesExist = rulesExist;
queueQuotaRuleSettings = quotaPtr;
}
@@ -585,7 +585,7 @@ void AclData::setQueueQuotaRuleSettings (
//
bool AclData::getQueueQuotaForUser(const std::string& theUserName,
uint16_t* theResult) const {
- if (queueQuotaRulesExist) {
+ if (this->enforcingQueueQuotas()) {
// look for this user explicitly
quotaRuleSetItr nameItr = (*queueQuotaRuleSettings).find(theUserName);
if (nameItr != (*queueQuotaRuleSettings).end()) {
@@ -612,7 +612,28 @@ bool AclData::getQueueQuotaForUser(const std::string& theUserName,
<< " unavailable; quota settings are not specified. Return value : 0");
*theResult = 0;
}
- return queueQuotaRulesExist;
+ return this->enforcingQueueQuotas();
+}
+
+void AclData::setConnGlobalRules (boost::shared_ptr<bwHostRuleSet> cgr) {
+ connBWHostsGlobalRules = cgr;
+}
+
+void AclData::setConnUserRules (boost::shared_ptr<bwHostUserRuleMap> hurm) {
+ connBWHostsRuleSettings = hurm;
+}
+
+
+//
+// Get user-specific black/white connection rule list
+//
+boost::shared_ptr<const AclData::bwHostRuleSet> AclData::getUserConnectionRules(const std::string& name){
+ AclData::bwHostUserRuleMapItr itrRule = connBWHostsRuleSettings->find(name);
+ if (itrRule == connBWHostsRuleSettings->end()) {
+ return boost::shared_ptr<const bwHostRuleSet>();
+ } else {
+ return boost::make_shared<const bwHostRuleSet>(itrRule->second);
+ }
}
diff --git a/qpid/cpp/src/qpid/acl/AclData.h b/qpid/cpp/src/qpid/acl/AclData.h
index fd0eac5b13..2167af66b8 100644
--- a/qpid/cpp/src/qpid/acl/AclData.h
+++ b/qpid/cpp/src/qpid/acl/AclData.h
@@ -21,6 +21,7 @@
*/
#include "qpid/broker/AclModule.h"
+#include "qpid/AclHost.h"
#include "AclTopicMatch.h"
#include "qpid/log/Statement.h"
#include "boost/shared_ptr.hpp"
@@ -30,6 +31,32 @@
namespace qpid {
namespace acl {
+/** A rule for tracking black/white host connection settings.
+ * When a connection is attempted, the remote host is verified
+ * against lists of these rules. When the remote host is in
+ * the range specified by this aclHost then the AclResult is
+ * applied as allow/deny.
+ */
+class AclBWHostRule {
+public:
+ AclBWHostRule(AclResult r, std::string h) :
+ aclResult(r), aclHost(h) {}
+
+ std::string toString () const {
+ std::ostringstream ruleStr;
+ ruleStr << "[ruleMode = " << AclHelper::getAclResultStr(aclResult)
+ << " {" << aclHost.str() << "}";
+ return ruleStr.str();
+ }
+ const AclHost& getAclHost() const { return aclHost; }
+ const AclResult& getAclResult() const { return aclResult; }
+
+private:
+ AclResult aclResult;
+ AclHost aclHost;
+};
+
+
class AclData {
@@ -116,6 +143,10 @@ public:
typedef actionObject* aclAction;
typedef std::map<std::string, uint16_t> quotaRuleSet; // <username, N>
typedef quotaRuleSet::const_iterator quotaRuleSetItr;
+ typedef std::vector<AclBWHostRule> bwHostRuleSet; // allow/deny hosts-vector
+ typedef bwHostRuleSet::const_iterator bwHostRuleSetItr;
+ typedef std::map<std::string, bwHostRuleSet> bwHostUserRuleMap; //<username, hosts-vector>
+ typedef bwHostUserRuleMap::const_iterator bwHostUserRuleMapItr;
// Action*[] -> Object*[] -> map<user -> set<Rule> >
aclAction* actionList[qpid::acl::ACTIONSIZE];
@@ -137,6 +168,12 @@ public:
const std::string& ExchangeName,
const std::string& RoutingKey);
+ boost::shared_ptr<const bwHostRuleSet> getGlobalConnectionRules() {
+ return connBWHostsGlobalRules;
+ }
+
+ boost::shared_ptr<const AclData::bwHostRuleSet> getUserConnectionRules(const std::string& name);
+
bool matchProp(const std::string & src, const std::string& src1);
void clear ();
static const std::string ACL_KEYWORD_USER_SUBST;
@@ -164,18 +201,24 @@ public:
// Per user connection quotas extracted from acl rule file
// Set by reader
- void setConnQuotaRuleSettings (bool, boost::shared_ptr<quotaRuleSet>);
+ void setConnQuotaRuleSettings (boost::shared_ptr<quotaRuleSet>);
// Get by connection approvers
- bool enforcingConnectionQuotas() { return connQuotaRulesExist; }
+ bool enforcingConnectionQuotas() const { return connQuotaRuleSettings->size() > 0; }
bool getConnQuotaForUser(const std::string&, uint16_t*) const;
// Per user queue quotas extracted from acl rule file
// Set by reader
- void setQueueQuotaRuleSettings (bool, boost::shared_ptr<quotaRuleSet>);
+ void setQueueQuotaRuleSettings (boost::shared_ptr<quotaRuleSet>);
// Get by queue approvers
- bool enforcingQueueQuotas() { return queueQuotaRulesExist; }
+ bool enforcingQueueQuotas() const { return queueQuotaRuleSettings->size() > 0; }
bool getQueueQuotaForUser(const std::string&, uint16_t*) const;
+ // Global connection Black/White list rules
+ void setConnGlobalRules (boost::shared_ptr<bwHostRuleSet>);
+
+ // Per-user connection Black/White list rules map
+ void setConnUserRules (boost::shared_ptr<bwHostUserRuleMap>);
+
/** getConnectMaxSpec
* Connection quotas are held in uint16_t variables.
* This function specifies the largest value that a user is allowed
@@ -225,11 +268,16 @@ private:
bool theMaxFlag);
// Per-user connection quota
- bool connQuotaRulesExist;
- boost::shared_ptr<quotaRuleSet> connQuotaRuleSettings; // Map of user-to-N values from rule file
+ boost::shared_ptr<quotaRuleSet> connQuotaRuleSettings;
+
// Per-user queue quota
- bool queueQuotaRulesExist;
- boost::shared_ptr<quotaRuleSet> queueQuotaRuleSettings; // Map of user-to-N values from rule file
+ boost::shared_ptr<quotaRuleSet> queueQuotaRuleSettings;
+
+ // Global host connection black/white rule set
+ boost::shared_ptr<bwHostRuleSet> connBWHostsGlobalRules;
+
+ // Per-user host connection black/white rule set map
+ boost::shared_ptr<bwHostUserRuleMap> connBWHostsRuleSettings;
};
}} // namespace qpid::acl
diff --git a/qpid/cpp/src/qpid/acl/AclLexer.cpp b/qpid/cpp/src/qpid/acl/AclLexer.cpp
index 1b24193e7c..d0f149db84 100644
--- a/qpid/cpp/src/qpid/acl/AclLexer.cpp
+++ b/qpid/cpp/src/qpid/acl/AclLexer.cpp
@@ -32,7 +32,7 @@ namespace acl {
// ObjectType
const std::string objectNames[OBJECTSIZE] = {
- "queue", "exchange", "broker", "link", "method", "query" };
+ "queue", "exchange", "broker", "link", "method", "query", "connection" };
ObjectType AclHelper::getObjectType(const std::string& str) {
for (int i=0; i< OBJECTSIZE; ++i) {
@@ -69,7 +69,7 @@ const std::string& AclHelper::getActionStr(const Action a) {
const std::string propertyNames[PROPERTYSIZE] = {
"name", "durable", "owner", "routingkey", "autodelete", "exclusive", "type",
"alternate", "queuename", "exchangename", "schemapackage",
- "schemaclass", "policytype", "paging",
+ "schemaclass", "policytype", "paging", "host",
"maxpages", "maxpagefactor",
"maxqueuesize", "maxqueuecount", "maxfilesize", "maxfilecount"};
@@ -91,7 +91,7 @@ const std::string& AclHelper::getPropertyStr(const Property p) {
const std::string specPropertyNames[SPECPROPSIZE] = {
"name", "durable", "owner", "routingkey", "autodelete", "exclusive", "type",
"alternate", "queuename", "exchangename", "schemapackage",
- "schemaclass", "policytype", "paging",
+ "schemaclass", "policytype", "paging", "host",
"queuemaxsizelowerlimit", "queuemaxsizeupperlimit",
"queuemaxcountlowerlimit", "queuemaxcountupperlimit",
diff --git a/qpid/cpp/src/qpid/acl/AclLexer.h b/qpid/cpp/src/qpid/acl/AclLexer.h
index 18789c093e..a8032ddee7 100644
--- a/qpid/cpp/src/qpid/acl/AclLexer.h
+++ b/qpid/cpp/src/qpid/acl/AclLexer.h
@@ -49,6 +49,7 @@ namespace acl {
OBJ_LINK,
OBJ_METHOD,
OBJ_QUERY,
+ OBJ_CONNECTION,
OBJECTSIZE }; // OBJECTSIZE must be last in list
// Action shared between ACL spec and ACL authorise interface
@@ -83,6 +84,7 @@ namespace acl {
PROP_SCHEMACLASS,
PROP_POLICYTYPE,
PROP_PAGING,
+ PROP_HOST,
PROP_MAXPAGES,
PROP_MAXPAGEFACTOR,
PROP_MAXQUEUESIZE,
@@ -110,6 +112,7 @@ namespace acl {
SPECPROP_SCHEMACLASS = PROP_SCHEMACLASS,
SPECPROP_POLICYTYPE = PROP_POLICYTYPE,
SPECPROP_PAGING = PROP_PAGING,
+ SPECPROP_HOST = PROP_HOST,
SPECPROP_MAXQUEUESIZELOWERLIMIT,
SPECPROP_MAXQUEUESIZEUPPERLIMIT,
diff --git a/qpid/cpp/src/qpid/acl/AclReader.cpp b/qpid/cpp/src/qpid/acl/AclReader.cpp
index 496d9b6132..a0b4c67bf3 100644
--- a/qpid/cpp/src/qpid/acl/AclReader.cpp
+++ b/qpid/cpp/src/qpid/acl/AclReader.cpp
@@ -102,6 +102,29 @@ namespace acl {
QPID_LOG(debug, "ACL: FoundMode "
<< AclHelper::getAclResultStr(d->decisionMode));
foundmode = true;
+ } else if ((*i)->action == acl::ACT_CREATE && (*i)->object == acl::OBJ_CONNECTION) {
+ // Intercept CREATE CONNECTION rules process them into separate lists to
+ // be consumed in the connection approval code path.
+ propMap::const_iterator pHost = (*i)->props.find(SPECPROP_HOST);
+ if (pHost == (*i)->props.end()) {
+ throw Exception(QPID_MSG("ACL: CREATE CONNECTION rule " << cnt << " has no 'host' property"));
+ }
+ // create the connection rule
+ AclBWHostRule bwRule((*i)->res,
+ (pHost->second.compare(AclData::ACL_KEYWORD_ALL) != 0 ? pHost->second : ""));
+
+ // apply the rule globally or to user list
+ if ((*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0) {
+ // Rules for user "all" go into the gloabl list
+ globalHostRules->insert( globalHostRules->begin(), bwRule );
+ } else {
+ // other rules go into binned rule sets for each user
+ for (nsCitr itr = (*i)->names.begin();
+ itr != (*i)->names.end();
+ itr++) {
+ (*userHostRules)[(*itr)].insert( (*userHostRules)[(*itr)].begin(), bwRule);
+ }
+ }
} else {
AclData::Rule rule(cnt, (*i)->res, (*i)->props);
@@ -223,9 +246,13 @@ namespace acl {
}
// connection quota
- d->setConnQuotaRuleSettings(connQuotaRulesExist, connQuota);
+ d->setConnQuotaRuleSettings(connQuota);
// queue quota
- d->setQueueQuotaRuleSettings(queueQuotaRulesExist, queueQuota);
+ d->setQueueQuotaRuleSettings(queueQuota);
+ // global B/W connection rules
+ d->setConnGlobalRules(globalHostRules);
+ // user B/W connection rules
+ d->setConnUserRules(userHostRules);
}
@@ -250,7 +277,9 @@ namespace acl {
connQuota(new AclData::quotaRuleSet),
cliMaxQueuesPerUser (theCliMaxQueuesPerUser),
queueQuotaRulesExist(false),
- queueQuota(new AclData::quotaRuleSet) {
+ queueQuota(new AclData::quotaRuleSet),
+ globalHostRules(new AclData::bwHostRuleSet),
+ userHostRules(new AclData::bwHostUserRuleMap) {
names.insert(AclData::ACL_KEYWORD_WILDCARD);
}
@@ -310,7 +339,14 @@ namespace acl {
printRules();
printQuotas(AclData::ACL_KEYWORD_QUOTA_CONNECTIONS, connQuota);
printQuotas(AclData::ACL_KEYWORD_QUOTA_QUEUES, queueQuota);
- loadDecisionData(d);
+ try {
+ loadDecisionData(d);
+ } catch (const std::exception& e) {
+ errorStream << "Error loading decision data : " << e.what();
+ return -6;
+ }
+ printGlobalConnectRules();
+ printUserConnectRules();
return 0;
}
@@ -693,6 +729,26 @@ namespace acl {
}
}
+ void AclReader::printConnectionRules(const std::string name, const AclData::bwHostRuleSet& rules) const {
+ QPID_LOG(debug, "ACL: " << name << " Connection Rule list : " << rules.size() << " rules found :");
+ int cnt = 1;
+ for (AclData::bwHostRuleSetItr i=rules.begin(); i<rules.end(); i++,cnt++) {
+ QPID_LOG(debug, "ACL: " << std::setfill(' ') << std::setw(2) << cnt << " " << i->toString());
+ }
+ }
+
+ void AclReader::printGlobalConnectRules() const {
+ printConnectionRules("global", *globalHostRules);
+ }
+
+ void AclReader::printUserConnectRules() const {
+ QPID_LOG(debug, "ACL: User Connection Rule lists : " << userHostRules->size() << " user lists found :");
+ int cnt = 1;
+ for (AclData::bwHostUserRuleMapItr i=userHostRules->begin(); i!=userHostRules->end(); i++,cnt++) {
+ printConnectionRules(std::string((*i).first), (*i).second);
+ }
+ }
+
// Static function
// Return true if the name is well-formed (ie contains legal characters)
bool AclReader::isValidGroupName(const std::string& name) {
diff --git a/qpid/cpp/src/qpid/acl/AclReader.h b/qpid/cpp/src/qpid/acl/AclReader.h
index e3a345998d..1f3cf6dca1 100644
--- a/qpid/cpp/src/qpid/acl/AclReader.h
+++ b/qpid/cpp/src/qpid/acl/AclReader.h
@@ -87,6 +87,9 @@ class AclReader {
typedef keywordSet::const_iterator ksCitr;
typedef std::pair<std::string, std::string> nvPair; // Name-Value pair
+ typedef boost::shared_ptr<std::vector<acl::AclBWHostRule> > aclGlobalHostRuleSet;
+ typedef boost::shared_ptr<std::map<std::string, std::vector<acl::AclBWHostRule> > > aclUserHostRuleSet;
+
std::string fileName;
int lineNumber;
bool contFlag;
@@ -116,6 +119,9 @@ class AclReader {
bool processAclLine(tokList& toks);
void printRules() const; // debug aid
+ void printConnectionRules(const std::string name, const AclData::bwHostRuleSet& rules) const;
+ void printGlobalConnectRules() const;
+ void printUserConnectRules() const;
bool isValidUserName(const std::string& name);
bool processQuotaLine(tokList& toks);
@@ -133,6 +139,9 @@ class AclReader {
const uint16_t cliMaxQueuesPerUser;
bool queueQuotaRulesExist;
aclQuotaRuleSet queueQuota;
+
+ aclGlobalHostRuleSet globalHostRules;
+ aclUserHostRuleSet userHostRules;
};
}} // namespace qpid::acl
diff --git a/qpid/cpp/src/qpid/acl/AclValidator.cpp b/qpid/cpp/src/qpid/acl/AclValidator.cpp
index 7bed3da5a5..655e7942fe 100644
--- a/qpid/cpp/src/qpid/acl/AclValidator.cpp
+++ b/qpid/cpp/src/qpid/acl/AclValidator.cpp
@@ -153,6 +153,7 @@ namespace acl {
RP("Authorise::outgoing", ACT_BIND, OBJ_EXCHANGE, "queuename routingkey");
RP("MessageHandlerImpl::subscribe", ACT_CONSUME, OBJ_QUEUE);
RP("Authorise::outgoing", ACT_CONSUME, OBJ_QUEUE);
+ RP("ConnectionHandler", ACT_CREATE, OBJ_CONNECTION, "host");
RP("Broker::createQueue", ACT_CREATE, OBJ_QUEUE, "alternate durable exclusive autodelete policytype paging maxpages maxpagefactor maxqueuecount maxqueuesize maxfilecount maxfilesize");
RP("Broker::createExchange", ACT_CREATE, OBJ_EXCHANGE, "type alternate durable autodelete");
RP("ConnectionHandler::Handler::open", ACT_CREATE, OBJ_LINK);
diff --git a/qpid/cpp/src/qpid/broker/AclModule.h b/qpid/cpp/src/qpid/broker/AclModule.h
index b7bbb38bc2..6f3b69390b 100644
--- a/qpid/cpp/src/qpid/broker/AclModule.h
+++ b/qpid/cpp/src/qpid/broker/AclModule.h
@@ -35,7 +35,7 @@ namespace broker {
public:
// Some ACLs are invoked on every message transfer.
- // doTransferAcl pervents time consuming ACL calls on a per-message basis.
+ // doTransferAcl prevents time consuming ACL calls on a per-message basis.
virtual bool doTransferAcl()=0;
virtual uint16_t getMaxConnectTotal()=0;
diff --git a/qpid/cpp/src/qpid/sys/SocketAddress.h b/qpid/cpp/src/qpid/sys/SocketAddress.h
index c54877ea0b..00b05f4c6c 100644
--- a/qpid/cpp/src/qpid/sys/SocketAddress.h
+++ b/qpid/cpp/src/qpid/sys/SocketAddress.h
@@ -42,13 +42,34 @@ public:
QPID_COMMON_EXTERN SocketAddress& operator=(const SocketAddress&);
QPID_COMMON_EXTERN ~SocketAddress();
- QPID_COMMON_EXTERN bool nextAddress();
- QPID_COMMON_EXTERN std::string asString(bool numeric=true) const;
+ QPID_COMMON_EXTERN void firstAddress() const;
+ QPID_COMMON_EXTERN bool nextAddress() const;
+ QPID_COMMON_EXTERN std::string asString(bool numeric=true,
+ bool dispNameOnly=false,
+ bool hideDecoration=false) const;
QPID_COMMON_EXTERN std::string getHost() const;
- QPID_COMMON_EXTERN static std::string asString(::sockaddr const * const addr, size_t addrlen);
+ QPID_COMMON_EXTERN static std::string asString(::sockaddr const * const addr,
+ size_t addrlen,
+ bool dispNameOnly=false,
+ bool hideDecoration=false);
QPID_COMMON_EXTERN static uint16_t getPort(::sockaddr const * const addr);
+ QPID_COMMON_EXTERN bool isIp() const;
+
+ QPID_COMMON_EXTERN std::string comparisonDetails(const SocketAddress& ) const;
+
+ QPID_COMMON_EXTERN bool isComparable(const SocketAddress& saHi) const;
+
+ QPID_COMMON_EXTERN bool inRange(const SocketAddress& ,
+ const SocketAddress& ) const;
+ QPID_COMMON_EXTERN bool inRange(const struct addrinfo&,
+ const struct addrinfo&,
+ const struct addrinfo&) const;
+
+ QPID_COMMON_EXTERN bool compareAddresses(const struct addrinfo&,
+ const struct addrinfo&,
+ int&) const;
private:
std::string host;
diff --git a/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp b/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp
index e22d23d675..9f100947c1 100644
--- a/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp
+++ b/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp
@@ -23,11 +23,14 @@
#include "qpid/Exception.h"
#include "qpid/Msg.h"
+#include "qpid/log/Logger.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <string.h>
+#include <arpa/inet.h>
+#include <iosfwd>
namespace qpid {
namespace sys {
@@ -63,24 +66,32 @@ SocketAddress::~SocketAddress()
}
}
-std::string SocketAddress::asString(::sockaddr const * const addr, size_t addrlen)
+std::string SocketAddress::asString(::sockaddr const * const addr, size_t addrlen, bool dispNameOnly, bool hideDecoration)
{
char servName[NI_MAXSERV];
char dispName[NI_MAXHOST];
if (int rc=::getnameinfo(addr, addrlen,
- dispName, sizeof(dispName),
+ dispName, sizeof(dispName),
servName, sizeof(servName),
NI_NUMERICHOST | NI_NUMERICSERV) != 0)
throw qpid::Exception(QPID_MSG(gai_strerror(rc)));
std::string s;
switch (addr->sa_family) {
case AF_INET: s += dispName; break;
- case AF_INET6: s += "["; s += dispName; s+= "]"; break;
+ case AF_INET6:
+ if (!hideDecoration) {
+ s += "["; s += dispName; s+= "]";
+ } else {
+ s += dispName;
+ }
+ break;
case AF_UNIX: s += "UNIX:"; break;
default: throw Exception(QPID_MSG("Unexpected socket type"));
}
- s += ":";
- s += servName;
+ if (!dispNameOnly) {
+ s += ":";
+ s += servName;
+ }
return s;
}
@@ -93,14 +104,14 @@ uint16_t SocketAddress::getPort(::sockaddr const * const addr)
}
}
-std::string SocketAddress::asString(bool numeric) const
+std::string SocketAddress::asString(bool numeric, bool dispNameOnly, bool hideDecoration) const
{
if (!numeric)
return host + ":" + port;
// Canonicalise into numeric id
const ::addrinfo& ai = getAddrInfo(*this);
- return asString(ai.ai_addr, ai.ai_addrlen);
+ return asString(ai.ai_addr, ai.ai_addrlen, dispNameOnly, hideDecoration);
}
std::string SocketAddress::getHost() const
@@ -108,7 +119,187 @@ std::string SocketAddress::getHost() const
return host;
}
-bool SocketAddress::nextAddress() {
+/**
+ * Return true if this SocketAddress is IPv4 or IPv6
+ */
+bool SocketAddress::isIp() const
+{
+ const ::addrinfo& ai = getAddrInfo(*this);
+ return ai.ai_family == AF_INET || ai.ai_family == AF_INET6;
+}
+
+/**
+ * this represents the low address of an ACL address range.
+ * Given rangeHi that represents the high address,
+ * return a string showing the numeric comparisons that the
+ * inRange checks will do for address pair.
+ */
+std::string SocketAddress::comparisonDetails(const SocketAddress& rangeHi) const
+{
+ std::ostringstream os;
+ SocketAddress thisSa(*this);
+ SocketAddress rangeHiSa(rangeHi);
+ (void) getAddrInfo(thisSa);
+ (void) getAddrInfo(rangeHiSa);
+ os << "(" << thisSa.asString(true, true, false) <<
+ "," << rangeHiSa.asString(true, true, false) << ")";
+ while (thisSa.nextAddress()) {
+ if (!rangeHiSa.nextAddress()) {
+ throw(Exception(QPID_MSG("Comparison iteration fails: " + (*this).asString() +
+ rangeHi.asString())));
+ }
+ os << ",(" << thisSa.asString(true, true, false) <<
+ "," << rangeHiSa.asString(true, true, false) << ")";
+ }
+ if (rangeHiSa.nextAddress()) {
+ throw(Exception(QPID_MSG("Comparison iteration fails: " + (*this).asString() +
+ rangeHi.asString())));
+ }
+ std::string result = os.str();
+ return result;
+}
+
+/**
+ * For ACL address matching make sure that the two addresses, *this
+ * which is the low address and hiPeer which is the high address, are
+ * both numeric ip addresses of the same family and that hi > *this.
+ *
+ * Note that if the addresses resolve to more than one struct addrinfo
+ * then this and the hiPeer must be equal. This avoids having to do
+ * difficult range checks where the this and hiPeer both resolve to
+ * multiple IPv4 or IPv6 addresses.
+ *
+ * This check is run at acl file load time and not at run tme.
+ */
+bool SocketAddress::isComparable(const SocketAddress& hiPeer) const {
+ // May only compare if this socket is IPv4 or IPv6
+ SocketAddress lo(*this);
+ const ::addrinfo& peerLoInfo = getAddrInfo(lo);
+ if (!(peerLoInfo.ai_family == AF_INET || peerLoInfo.ai_family == AF_INET6)) {
+ return false;
+ }
+ // May only compare if peer socket is same family
+ SocketAddress hi(hiPeer);
+ const ::addrinfo& peerHiInfo = getAddrInfo(hi);
+ if (peerLoInfo.ai_family != peerHiInfo.ai_family) {
+ return false;
+ }
+ // Host names that resolve to lists are allowed if they are equal.
+ // For example: localhost, or fjord.lab.example.com
+ if ((*this).asString() == hiPeer.asString()) {
+ return true;
+ }
+ // May only compare if this and peer resolve to single address.
+ if (lo.nextAddress() || hi.nextAddress()) {
+ return false;
+ }
+ // Make sure that the lo/hi relationship is ok
+ int res;
+ if (!compareAddresses(peerLoInfo, peerHiInfo, res) || res < 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * *this SocketAddress was created from the numeric IP address of a
+ * connecting host.
+ * The lo and hi addresses are the limit checks from the ACL file.
+ * Return true if this address is in range of any of the address pairs
+ * in the limit check range.
+ *
+ * This check is executed on every incoming connection.
+ */
+bool SocketAddress::inRange(const SocketAddress& lo,
+ const SocketAddress& hi) const
+{
+ (*this).firstAddress();
+ lo.firstAddress();
+ hi.firstAddress();
+ const ::addrinfo& thisInfo = getAddrInfo(*this);
+ const ::addrinfo& loInfo = getAddrInfo(lo);
+ const ::addrinfo& hiInfo = getAddrInfo(hi);
+ if (inRange(thisInfo, loInfo, hiInfo)) {
+ return true;
+ }
+ while (lo.nextAddress()) {
+ if (!hi.nextAddress()) {
+ assert (false);
+ throw(Exception(QPID_MSG("Comparison iteration fails: " +
+ lo.asString() + hi.asString())));
+ }
+ const ::addrinfo& loInfo = getAddrInfo(lo);
+ const ::addrinfo& hiInfo = getAddrInfo(hi);
+ if (inRange(thisInfo, loInfo, hiInfo)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * *this SocketAddress was created from the numeric IP address of a
+ * connecting host.
+ * The lo and hi addresses are one binary address pair from a range
+ * given in an ACL file.
+ * Return true if this binary address is '>= lo' and '<= hi'.
+ */
+bool SocketAddress::inRange(const ::addrinfo& thisInfo,
+ const ::addrinfo& lo,
+ const ::addrinfo& hi) const
+{
+ int resLo;
+ int resHi;
+ if (!compareAddresses(lo, thisInfo, resLo)) {
+ return false;
+ }
+ if (!compareAddresses(hi, thisInfo, resHi)) {
+ return false;
+ }
+ if (resLo < 0) {
+ return false;
+ }
+ if (resHi > 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Compare this address against two binary low/high addresses.
+ * return true with result holding the comparison.
+ */
+bool SocketAddress::compareAddresses(const struct addrinfo& lo,
+ const struct addrinfo& hi,
+ int& result) const
+{
+ if (lo.ai_family != hi.ai_family) {
+ return false;
+ }
+ if (lo.ai_family == AF_INET) {
+ struct sockaddr_in* sin4lo = (struct sockaddr_in*)lo.ai_addr;
+ struct sockaddr_in* sin4hi = (struct sockaddr_in*)hi.ai_addr;
+ result = memcmp(&sin4hi->sin_addr, &sin4lo->sin_addr, sizeof(in_addr));
+ } else if (lo.ai_family == AF_INET6) {
+ struct sockaddr_in6* sin6lo = (struct sockaddr_in6*)lo.ai_addr;
+ struct sockaddr_in6* sin6hi = (struct sockaddr_in6*)hi.ai_addr;
+ result = memcmp(&sin6hi->sin6_addr, &sin6lo->sin6_addr, sizeof(in6_addr));
+ } else {
+ assert (false);
+ return false;
+ }
+ return true;
+}
+
+void SocketAddress::firstAddress() const {
+ if (addrInfo) {
+ currentAddrInfo = addrInfo;
+ } else {
+ (void) getAddrInfo(*this);
+ }
+}
+
+bool SocketAddress::nextAddress() const {
bool r = currentAddrInfo->ai_next != 0;
if (r)
currentAddrInfo = currentAddrInfo->ai_next;
diff --git a/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp b/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp
index b5146678af..547be0bf26 100644
--- a/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp
+++ b/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp
@@ -23,6 +23,7 @@
#include "qpid/Exception.h"
#include "qpid/Msg.h"
+#include "qpid/log/Logger.h"
// Ensure we get all of winsock2.h
#ifndef _WIN32_WINNT
@@ -67,23 +68,31 @@ SocketAddress::~SocketAddress()
}
}
-std::string SocketAddress::asString(::sockaddr const * const addr, size_t addrlen)
+std::string SocketAddress::asString(::sockaddr const * const addr, size_t addrlen, bool dispNameOnly, bool hideDecoration)
{
char servName[NI_MAXSERV];
char dispName[NI_MAXHOST];
if (int rc=::getnameinfo(addr, addrlen,
- dispName, sizeof(dispName),
+ dispName, sizeof(dispName),
servName, sizeof(servName),
NI_NUMERICHOST | NI_NUMERICSERV) != 0)
throw qpid::Exception(QPID_MSG(gai_strerror(rc)));
std::string s;
switch (addr->sa_family) {
case AF_INET: s += dispName; break;
- case AF_INET6: s += "["; s += dispName; s+= "]"; break;
+ case AF_INET6:
+ if (!hideDecoration) {
+ s += "["; s += dispName; s+= "]";
+ } else {
+ s += dispName;
+ }
+ break;
default: throw Exception(QPID_MSG("Unexpected socket type"));
}
- s += ":";
- s += servName;
+ if (!dispNameOnly) {
+ s += ":";
+ s += servName;
+ }
return s;
}
@@ -96,14 +105,14 @@ uint16_t SocketAddress::getPort(::sockaddr const * const addr)
}
}
-std::string SocketAddress::asString(bool numeric) const
+std::string SocketAddress::asString(bool numeric, bool dispNameOnly, bool hideDecoration) const
{
if (!numeric)
return host + ":" + port;
// Canonicalise into numeric id
const ::addrinfo& ai = getAddrInfo(*this);
- return asString(ai.ai_addr, ai.ai_addrlen);
+ return asString(ai.ai_addr, ai.ai_addrlen, dispNameOnly, hideDecoration);
}
std::string SocketAddress::getHost() const
@@ -111,7 +120,187 @@ std::string SocketAddress::getHost() const
return host;
}
-bool SocketAddress::nextAddress() {
+/**
+ * Return true if this SocketAddress is IPv4 or IPv6
+ */
+bool SocketAddress::isIp() const
+{
+ const ::addrinfo& ai = getAddrInfo(*this);
+ return ai.ai_family == AF_INET || ai.ai_family == AF_INET6;
+}
+
+/**
+ * this represents the low address of an ACL address range.
+ * Given rangeHi that represents the high address,
+ * return a string showing the numeric comparisons that the
+ * inRange checks will do for address pair.
+ */
+std::string SocketAddress::comparisonDetails(const SocketAddress& rangeHi) const
+{
+ std::ostringstream os;
+ SocketAddress thisSa(*this);
+ SocketAddress rangeHiSa(rangeHi);
+ (void) getAddrInfo(thisSa);
+ (void) getAddrInfo(rangeHiSa);
+ os << "(" << thisSa.asString(true, true, false) <<
+ "," << rangeHiSa.asString(true, true, false) << ")";
+ while (thisSa.nextAddress()) {
+ if (!rangeHiSa.nextAddress()) {
+ throw(Exception(QPID_MSG("Comparison iteration fails: " + (*this).asString() +
+ rangeHi.asString())));
+ }
+ os << ",(" << thisSa.asString(true, true, false) <<
+ "," << rangeHiSa.asString(true, true, false) << ")";
+ }
+ if (rangeHiSa.nextAddress()) {
+ throw(Exception(QPID_MSG("Comparison iteration fails: " + (*this).asString() +
+ rangeHi.asString())));
+ }
+ std::string result = os.str();
+ return result;
+}
+
+/**
+ * For ACL address matching make sure that the two addresses, *this
+ * which is the low address and hiPeer which is the high address, are
+ * both numeric ip addresses of the same family and that hi > *this.
+ *
+ * Note that if the addresses resolve to more than one struct addrinfo
+ * then this and the hiPeer must be equal. This avoids having to do
+ * difficult range checks where the this and hiPeer both resolve to
+ * multiple IPv4 or IPv6 addresses.
+ *
+ * This check is run at acl file load time and not at run tme.
+ */
+bool SocketAddress::isComparable(const SocketAddress& hiPeer) const {
+ // May only compare if this socket is IPv4 or IPv6
+ SocketAddress lo(*this);
+ const ::addrinfo& peerLoInfo = getAddrInfo(lo);
+ if (!(peerLoInfo.ai_family == AF_INET || peerLoInfo.ai_family == AF_INET6)) {
+ return false;
+ }
+ // May only compare if peer socket is same family
+ SocketAddress hi(hiPeer);
+ const ::addrinfo& peerHiInfo = getAddrInfo(hi);
+ if (peerLoInfo.ai_family != peerHiInfo.ai_family) {
+ return false;
+ }
+ // Host names that resolve to lists are allowed if they are equal.
+ // For example: localhost, or fjord.lab.example.com
+ if ((*this).asString() == hiPeer.asString()) {
+ return true;
+ }
+ // May only compare if this and peer resolve to single address.
+ if (lo.nextAddress() || hi.nextAddress()) {
+ return false;
+ }
+ // Make sure that the lo/hi relationship is ok
+ int res;
+ if (!compareAddresses(peerLoInfo, peerHiInfo, res) || res < 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * *this SocketAddress was created from the numeric IP address of a
+ * connecting host.
+ * The lo and hi addresses are the limit checks from the ACL file.
+ * Return true if this address is in range of any of the address pairs
+ * in the limit check range.
+ *
+ * This check is executed on every incoming connection.
+ */
+bool SocketAddress::inRange(const SocketAddress& lo,
+ const SocketAddress& hi) const
+{
+ (*this).firstAddress();
+ lo.firstAddress();
+ hi.firstAddress();
+ const ::addrinfo& thisInfo = getAddrInfo(*this);
+ const ::addrinfo& loInfo = getAddrInfo(lo);
+ const ::addrinfo& hiInfo = getAddrInfo(hi);
+ if (inRange(thisInfo, loInfo, hiInfo)) {
+ return true;
+ }
+ while (lo.nextAddress()) {
+ if (!hi.nextAddress()) {
+ assert (false);
+ throw(Exception(QPID_MSG("Comparison iteration fails: " +
+ lo.asString() + hi.asString())));
+ }
+ const ::addrinfo& loInfo = getAddrInfo(lo);
+ const ::addrinfo& hiInfo = getAddrInfo(hi);
+ if (inRange(thisInfo, loInfo, hiInfo)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * *this SocketAddress was created from the numeric IP address of a
+ * connecting host.
+ * The lo and hi addresses are one binary address pair from a range
+ * given in an ACL file.
+ * Return true if this binary address is '>= lo' and '<= hi'.
+ */
+bool SocketAddress::inRange(const ::addrinfo& thisInfo,
+ const ::addrinfo& lo,
+ const ::addrinfo& hi) const
+{
+ int resLo;
+ int resHi;
+ if (!compareAddresses(lo, thisInfo, resLo)) {
+ return false;
+ }
+ if (!compareAddresses(hi, thisInfo, resHi)) {
+ return false;
+ }
+ if (resLo < 0) {
+ return false;
+ }
+ if (resHi > 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Compare this address against two binary low/high addresses.
+ * return true with result holding the comparison.
+ */
+bool SocketAddress::compareAddresses(const struct addrinfo& lo,
+ const struct addrinfo& hi,
+ int& result) const
+{
+ if (lo.ai_family != hi.ai_family) {
+ return false;
+ }
+ if (lo.ai_family == AF_INET) {
+ struct sockaddr_in* sin4lo = (struct sockaddr_in*)lo.ai_addr;
+ struct sockaddr_in* sin4hi = (struct sockaddr_in*)hi.ai_addr;
+ result = memcmp(&sin4hi->sin_addr, &sin4lo->sin_addr, sizeof(in_addr));
+ } else if (lo.ai_family == AF_INET6) {
+ struct sockaddr_in6* sin6lo = (struct sockaddr_in6*)lo.ai_addr;
+ struct sockaddr_in6* sin6hi = (struct sockaddr_in6*)hi.ai_addr;
+ result = memcmp(&sin6hi->sin6_addr, &sin6lo->sin6_addr, sizeof(in6_addr));
+ } else {
+ assert (false);
+ return false;
+ }
+ return true;
+}
+
+void SocketAddress::firstAddress() const {
+ if (addrInfo) {
+ currentAddrInfo = addrInfo;
+ } else {
+ (void) getAddrInfo(*this);
+ }
+}
+
+bool SocketAddress::nextAddress() const {
bool r = currentAddrInfo->ai_next != 0;
if (r)
currentAddrInfo = currentAddrInfo->ai_next;
diff --git a/qpid/cpp/src/tests/Acl.cpp b/qpid/cpp/src/tests/Acl.cpp
index d5180a11a8..75a52c8ca1 100644
--- a/qpid/cpp/src/tests/Acl.cpp
+++ b/qpid/cpp/src/tests/Acl.cpp
@@ -37,13 +37,14 @@ QPID_AUTO_TEST_SUITE(AclTestSuite)
BOOST_CHECK_EQUAL(AclHelper::getObjectType((s)),(e))
QPID_AUTO_TEST_CASE(TestLexerObjectEnums) {
- BOOST_CHECK_EQUAL(OBJECTSIZE, 6);
- OBJ_ENUMS(OBJ_QUEUE, "queue");
- OBJ_ENUMS(OBJ_EXCHANGE, "exchange");
- OBJ_ENUMS(OBJ_BROKER, "broker");
- OBJ_ENUMS(OBJ_LINK, "link");
- OBJ_ENUMS(OBJ_METHOD, "method");
- OBJ_ENUMS(OBJ_QUERY, "query");
+ BOOST_CHECK_EQUAL(OBJECTSIZE, 7);
+ OBJ_ENUMS(OBJ_QUEUE, "queue");
+ OBJ_ENUMS(OBJ_EXCHANGE, "exchange");
+ OBJ_ENUMS(OBJ_BROKER, "broker");
+ OBJ_ENUMS(OBJ_LINK, "link");
+ OBJ_ENUMS(OBJ_METHOD, "method");
+ OBJ_ENUMS(OBJ_QUERY, "query");
+ OBJ_ENUMS(OBJ_CONNECTION, "connection");
}
#define ACT_ENUMS(e, s) \
@@ -71,7 +72,7 @@ QPID_AUTO_TEST_CASE(TestLexerActionEnums) {
BOOST_CHECK_EQUAL(AclHelper::getProperty((s)),(e))
QPID_AUTO_TEST_CASE(TestLexerPropertyEnums) {
- BOOST_CHECK_EQUAL(PROPERTYSIZE, 20);
+ BOOST_CHECK_EQUAL(PROPERTYSIZE, 21);
PROP_ENUMS(PROP_NAME, "name");
PROP_ENUMS(PROP_DURABLE, "durable");
PROP_ENUMS(PROP_OWNER, "owner");
@@ -86,6 +87,7 @@ QPID_AUTO_TEST_CASE(TestLexerPropertyEnums) {
PROP_ENUMS(PROP_SCHEMACLASS, "schemaclass");
PROP_ENUMS(PROP_POLICYTYPE, "policytype");
PROP_ENUMS(PROP_PAGING, "paging");
+ PROP_ENUMS(PROP_HOST, "host");
PROP_ENUMS(PROP_MAXPAGES, "maxpages");
PROP_ENUMS(PROP_MAXPAGEFACTOR, "maxpagefactor");
PROP_ENUMS(PROP_MAXQUEUESIZE, "maxqueuesize");
@@ -100,7 +102,7 @@ QPID_AUTO_TEST_CASE(TestLexerPropertyEnums) {
BOOST_CHECK_EQUAL(AclHelper::getSpecProperty((s)),(e))
QPID_AUTO_TEST_CASE(TestLexerSpecPropertyEnums) {
- BOOST_CHECK_EQUAL(SPECPROPSIZE, 26);
+ BOOST_CHECK_EQUAL(SPECPROPSIZE, 27);
SPECPROP_ENUMS(SPECPROP_NAME, "name");
SPECPROP_ENUMS(SPECPROP_DURABLE, "durable");
SPECPROP_ENUMS(SPECPROP_OWNER, "owner");
@@ -115,6 +117,7 @@ QPID_AUTO_TEST_CASE(TestLexerSpecPropertyEnums) {
SPECPROP_ENUMS(SPECPROP_SCHEMACLASS, "schemaclass");
SPECPROP_ENUMS(SPECPROP_POLICYTYPE, "policytype");
SPECPROP_ENUMS(SPECPROP_PAGING, "paging");
+ SPECPROP_ENUMS(SPECPROP_HOST, "host");
SPECPROP_ENUMS(SPECPROP_MAXQUEUESIZELOWERLIMIT, "queuemaxsizelowerlimit");
SPECPROP_ENUMS(SPECPROP_MAXQUEUESIZEUPPERLIMIT, "queuemaxsizeupperlimit");
SPECPROP_ENUMS(SPECPROP_MAXQUEUECOUNTLOWERLIMIT, "queuemaxcountlowerlimit");
diff --git a/qpid/cpp/src/tests/AclHost.cpp b/qpid/cpp/src/tests/AclHost.cpp
new file mode 100644
index 0000000000..068b1cae84
--- /dev/null
+++ b/qpid/cpp/src/tests/AclHost.cpp
@@ -0,0 +1,119 @@
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed 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 "unit_test.h"
+#include "test_tools.h"
+#include "qpid/AclHost.h"
+#include <boost/assign.hpp>
+
+using namespace std;
+using namespace qpid;
+using namespace boost::assign;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(AclHostTestSuite)
+
+#define ACLURL_CHECK_INVALID(STR) BOOST_CHECK_THROW(AclHost(STR), AclHost::Invalid)
+
+QPID_AUTO_TEST_CASE(TestParseTcpIPv4) {
+ BOOST_CHECK_EQUAL(AclHost("1.1.1.1").str(), "(1.1.1.1,1.1.1.1)");
+ BOOST_CHECK_EQUAL(AclHost("1.1.1.1,2.2.2.2").str(), "(1.1.1.1,2.2.2.2)");
+}
+
+QPID_AUTO_TEST_CASE(TestParseTcpIPv6) {
+ BOOST_CHECK_EQUAL(AclHost("[::1]").str(), "([::1],[::1])");
+ BOOST_CHECK_EQUAL(AclHost("[::1],::5").str(), "([::1],[::5])");
+}
+
+QPID_AUTO_TEST_CASE(TestParseAll) {
+ BOOST_CHECK_EQUAL(AclHost("").str(), "(all)");
+}
+
+QPID_AUTO_TEST_CASE(TestInvalidMixedIpFamilies) {
+ ACLURL_CHECK_INVALID("1.1.1.1,[::1]");
+ ACLURL_CHECK_INVALID("[::1],1.1.1.1");
+}
+
+QPID_AUTO_TEST_CASE(TestMalformedIPv4) {
+ ACLURL_CHECK_INVALID("1.1.1.1.1");
+ ACLURL_CHECK_INVALID("1.1.1.777");
+ ACLURL_CHECK_INVALID("1.1.1.1abcd");
+ ACLURL_CHECK_INVALID("1.1.1.*");
+}
+
+QPID_AUTO_TEST_CASE(TestRangeWithInvertedSizeOrder) {
+ ACLURL_CHECK_INVALID("1.1.1.100,1.1.1.1");
+ ACLURL_CHECK_INVALID("[FF::1],[::1]");
+}
+
+QPID_AUTO_TEST_CASE(TestSingleHostResolvesMultipleAddresses) {
+ AclHost XX("localhost");
+}
+
+QPID_AUTO_TEST_CASE(TestMatchSingleAddresses) {
+ AclHost host1("1.1.1.1");
+ BOOST_CHECK(host1.match("1.1.1.1") == true);
+ BOOST_CHECK(host1.match("1.2.1.1") == false);
+
+ AclHost host2("FF::1");
+ BOOST_CHECK(host2.match("00FF:0000::1") == true);
+}
+
+QPID_AUTO_TEST_CASE(TestMatchMultipleAddresses) {
+ AclHost host1("localhost");
+ BOOST_CHECK(host1.match("127.0.0.1") == true);
+ BOOST_CHECK(host1.match("::1") == true);
+ BOOST_CHECK(host1.match("128.1.1.1") == false);
+ BOOST_CHECK(host1.match("::abcd") == false);
+}
+
+QPID_AUTO_TEST_CASE(TestMatchIPv4Range) {
+ AclHost host1("192.168.0.0,192.168.255.255");
+ BOOST_CHECK(host1.match("128.1.1.1") == false);
+ BOOST_CHECK(host1.match("192.167.255.255") == false);
+ BOOST_CHECK(host1.match("192.168.0.0") == true);
+ BOOST_CHECK(host1.match("192.168.0.1") == true);
+ BOOST_CHECK(host1.match("192.168.1.0") == true);
+ BOOST_CHECK(host1.match("192.168.255.254") == true);
+ BOOST_CHECK(host1.match("192.168.255.255") == true);
+ BOOST_CHECK(host1.match("192.169.0.0") == false);
+ BOOST_CHECK(host1.match("::1") == false);
+}
+
+QPID_AUTO_TEST_CASE(TestMatchIPv6Range) {
+ AclHost host1("::10,::1:0");
+ BOOST_CHECK(host1.match("::1") == false);
+ BOOST_CHECK(host1.match("::f") == false);
+ BOOST_CHECK(host1.match("::10") == true);
+ BOOST_CHECK(host1.match("::11") == true);
+ BOOST_CHECK(host1.match("::ffff") == true);
+ BOOST_CHECK(host1.match("::1:0") == true);
+ BOOST_CHECK(host1.match("::1:1") == false);
+ BOOST_CHECK(host1.match("192.169.0.0") == false);
+ AclHost host2("[fc00::],[fc00::ff]");
+ BOOST_CHECK(host2.match("fc00::") == true);
+ BOOST_CHECK(host2.match("fc00::1") == true);
+ BOOST_CHECK(host2.match("fc00::ff") == true);
+ BOOST_CHECK(host2.match("fc00::100") == false);
+}
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/CMakeLists.txt b/qpid/cpp/src/tests/CMakeLists.txt
index b556464db8..b314b966f5 100644
--- a/qpid/cpp/src/tests/CMakeLists.txt
+++ b/qpid/cpp/src/tests/CMakeLists.txt
@@ -167,6 +167,7 @@ set (qpid_test_boost_libs
set(all_unit_tests
AccumulatedAckTest
Acl
+ AclHost
Array
AsyncCompletion
AtomicValue