diff options
Diffstat (limited to 'qpid/cpp/src')
| -rw-r--r-- | qpid/cpp/src/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/AclHost.cpp | 151 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/AclHost.h | 95 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/Acl.cpp | 11 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/Acl.h | 1 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp | 76 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclConnectionCounter.h | 7 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclData.cpp | 47 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclData.h | 64 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclLexer.cpp | 6 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclLexer.h | 3 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclReader.cpp | 64 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclReader.h | 9 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/acl/AclValidator.cpp | 1 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/broker/AclModule.h | 2 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/sys/SocketAddress.h | 27 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp | 207 | ||||
| -rw-r--r-- | qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp | 205 | ||||
| -rw-r--r-- | qpid/cpp/src/tests/Acl.cpp | 21 | ||||
| -rw-r--r-- | qpid/cpp/src/tests/AclHost.cpp | 119 | ||||
| -rw-r--r-- | qpid/cpp/src/tests/CMakeLists.txt | 1 |
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 |
