diff options
Diffstat (limited to 'cpp/src/qpid/acl/AclReader.cpp')
-rw-r--r-- | cpp/src/qpid/acl/AclReader.cpp | 461 |
1 files changed, 446 insertions, 15 deletions
diff --git a/cpp/src/qpid/acl/AclReader.cpp b/cpp/src/qpid/acl/AclReader.cpp index 0a9517bc76..e8828a55bb 100644 --- a/cpp/src/qpid/acl/AclReader.cpp +++ b/cpp/src/qpid/acl/AclReader.cpp @@ -18,47 +18,274 @@ #include "qpid/acl/AclReader.h" +#include <cctype> #include <cstring> -//#include <iostream> // debug #include <fstream> +#include <sstream> +#include "qpid/log/Statement.h" +#include "qpid/Exception.h" + +#include <iomanip> // degug +#include <iostream> // debug + +#define ACL_FORMAT_ERR_LOG_PREFIX "ACL format error: " << fileName << ":" << lineNumber << ": " namespace qpid { namespace acl { +AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups) : res(r), actionAll(true), objStatus(NONE) { + processName(n, groups); +} +AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a) : res(r), actionAll(false), action(a), objStatus(NONE) { + processName(n, groups); +} + +void AclReader::aclRule::setObjectType(const ObjectType o) { + objStatus = VALUE; + object = o; +} + +void AclReader::aclRule::setObjectTypeAll() { + objStatus = ALL; +} + +bool AclReader::aclRule::addProperty(const Property p, const std::string v) { + return props.insert(propNvPair(p, v)).second; +} + +bool AclReader::aclRule::validate(const AclHelper::objectMapPtr& /*validationMap*/) { + // TODO - invalid rules won't ever be called in real life... + return true; +} + +// Debug aid +std::string AclReader::aclRule::toString() { + std::ostringstream oss; + oss << AclHelper::getAclResultStr(res) << " ["; + for (nsCitr itr = names.begin(); itr != names.end(); itr++) { + if (itr != names.begin()) oss << ", "; + oss << *itr; + } + oss << "]"; + if (actionAll) { + oss << " *"; + } else { + oss << " " << AclHelper::getActionStr(action); + } + if (objStatus == ALL) { + oss << " *"; + } else if (objStatus == VALUE) { + oss << " " << AclHelper::getObjectTypeStr(object); + } + for (pmCitr i=props.begin(); i!=props.end(); i++) { + oss << " " << AclHelper::getPropertyStr(i->first) << "=" << i->second; + } + return oss.str(); +} + +void AclReader::loadDecisionData( boost::shared_ptr<AclData> d ) +{ + d->clear(); + QPID_LOG(debug, "ACL Load Rules"); + int cnt = rules.size(); + bool foundmode = false; + for (rlCitr i=rules.end()-1; cnt; i--,cnt--) { + QPID_LOG(debug, "ACL Processing " << std::setfill(' ') << std::setw(2) << cnt << " " << (*i)->toString()); + + if (!foundmode && (*i)->actionAll && (*i)->names.size()==1 && (*((*i)->names.begin())).compare("*")==0 ){ + d->decisionMode = (*i)->res; + QPID_LOG(debug, "ACL FoundMode " << AclHelper::getAclResultStr(d->decisionMode)); + foundmode=true; + }else{ + AclData::rule rule((*i)->props); + bool addrule= true; + + switch ((*i)->res) + { + case qpid::acl::ALLOWLOG: + rule.log = true; + if (d->decisionMode == qpid::acl::ALLOW || d->decisionMode == qpid::acl::ALLOWLOG) + rule.logOnly = true; + break; + case qpid::acl::ALLOW: + if (d->decisionMode == qpid::acl::ALLOW || d->decisionMode == qpid::acl::ALLOWLOG) + addrule = false; + break; + case qpid::acl::DENYLOG: + rule.log = true; + if (d->decisionMode == qpid::acl::DENY || d->decisionMode == qpid::acl::DENYLOG) + rule.logOnly = true; + break; + case qpid::acl::DENY: + if (d->decisionMode == qpid::acl::DENY || d->decisionMode == qpid::acl::DENYLOG) + addrule = false; + break; + default: + throw Exception("Invalid ACL Result loading rules."); + } + + + // Action -> Object -> map<user -> set<Rule> > + if (addrule){ + for (int acnt= ((*i)->actionAll?0:(*i)->action); + acnt< acl::ACTIONSIZE; (*i)->actionAll?acnt++:acnt=acl::ACTIONSIZE ) { + + if (acnt == acl::PUBLISH) d->transferAcl = true; // we have transfer ACL + + QPID_LOG(debug, "ACL Adding action:" << AclHelper::getActionStr((Action)acnt) ); + + //find the Action, create if not exist + if (d->actionList[acnt]==NULL) { + d->actionList[acnt] = new AclData::aclAction[qpid::acl::OBJECTSIZE]; + for (int j=0;j<qpid::acl::OBJECTSIZE; j++) + d->actionList[acnt][j] = NULL; + } + + // optimize this loop to limit to valid options only!! + for (int ocnt= ((*i)->objStatus!=aclRule::VALUE ?0:(*i)->object); + ocnt< acl::OBJECTSIZE; + (*i)->objStatus!=aclRule::VALUE?ocnt++:ocnt=acl::OBJECTSIZE ) { + + QPID_LOG(debug, "ACL Adding object:" << AclHelper::getObjectTypeStr((ObjectType)ocnt) ); + + //find the Object, create if not exist + if (d->actionList[acnt][ocnt] == NULL) + d->actionList[acnt][ocnt] = new AclData::actionObject; + + // add users and Rule to object set + bool allNames=false; + // check to see if names.begin is '*' + if ( (*(*i)->names.begin()).compare("*")==0 ) allNames = true; + + for (nsCitr itr = (allNames?names.begin():(*i)->names.begin()); + itr != (allNames?names.end():(*i)->names.end()); itr++) { + AclData::actObjItr itrRule = d->actionList[acnt][ocnt]->find(*itr); + if (itrRule == d->actionList[acnt][ocnt]->end()) { + QPID_LOG(debug, "ACL Adding rule & user:" << *itr); + AclData::ruleSet rSet; + rSet.push_back(rule); + d->actionList[acnt][ocnt]->insert(make_pair( std::string(*itr) , rSet) ); + }else{ + + // TODO add code to check for dead rules + // allow peter create queue name=tmp <-- dead rule!! + // allow peter create queue + + itrRule->second.push_back(rule); + QPID_LOG(debug, "ACL Adding rule to user:" << *itr); + } + } + + } + + } + }else{ + QPID_LOG(debug, "ACL Skipping based on Mode:" << AclHelper::getAclResultStr(d->decisionMode) ); + } + } + + } + + +} + + + + +void AclReader::aclRule::processName(const std::string& name, const groupMap& groups) { + if (name.compare("all") == 0) { + names.insert("*"); + } else { + gmCitr itr = groups.find(name); + if (itr == groups.end()) { + names.insert(name); + } else { + names.insert(itr->second->begin(), itr->second->end()); + } + } +} + +AclReader::AclReader() : lineNumber(0), contFlag(false), validationMap(new AclHelper::objectMap) { + AclHelper::loadValidationMap(validationMap); +} + +AclReader::~AclReader() {} + int AclReader::read(const std::string& fn, boost::shared_ptr<AclData> d) { -//std::cout << "AclReader::read(" << fn << ")" << std::endl << std::flush; + fileName = fn; + lineNumber = 0; char buff[1024]; std::ifstream ifs(fn.c_str(), std::ios_base::in); if (!ifs.good()) { - // error/exception - file open error + QPID_LOG(error, "Unable to open ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F")); return -1; } try { + bool err = false; while (ifs.good()) { ifs.getline(buff, 1024); - processLine(buff, d); + lineNumber++; + if (std::strlen(buff) > 0 && buff[0] != '#') // Ignore blank lines and comments + err |= !processLine(buff); + } + if (!ifs.eof()) + { + QPID_LOG(error, "Unable to read ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F")); + ifs.close(); + return -2; } ifs.close(); + if (err) return -3; + QPID_LOG(notice, "Read ACL file \"" << fn << "\""); + } catch (const std::exception& e) { + QPID_LOG(error, "Unable to read ACL file \"" << fn << "\": " << e.what()); + ifs.close(); + return -4; } catch (...) { - // error/exception - file read/processing error + QPID_LOG(error, "Unable to read ACL file \"" << fn << "\": Unknown exception"); ifs.close(); - return -2; + return -5; } + printNames(); + printRules(); + loadDecisionData(d); + return 0; } +bool AclReader::processLine(char* line) { + bool ret = false; + std::vector<std::string> toks; + + // Check for continuation + char* contCharPtr = std::strrchr(line, '\\'); + bool cont = contCharPtr != 0; + if (cont) *contCharPtr = 0; -void AclReader::processLine(char* line, boost::shared_ptr<AclData> /*d*/) { - std::vector<std::string> toks; - int numToks = tokenizeLine(line, toks); - for (int i=0; i<numToks; i++) { -// DO MAGIC STUFF HERE -//std::cout << "tok " << i << ": " << toks[i] << std::endl << std::flush; - } + int numToks = tokenize(line, toks); + if (numToks && (toks[0].compare("group") == 0 || contFlag)) { + ret = processGroupLine(toks, cont); + } else if (numToks && toks[0].compare("acl") == 0) { + ret = processAclLine(toks); + } else { + // Check for whitespace only line, ignore these + bool ws = true; + for (unsigned i=0; i<std::strlen(line) && ws; i++) { + if (!std::isspace(line[i])) ws = false; + } + if (ws) { + ret = true; + } else { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Non-continuation line must start with \"group\" or \"acl\"."); + ret = false; + } + } + contFlag = cont; + return ret; } -int AclReader::tokenizeLine(char* line, std::vector<std::string>& toks) { - const char* tokChars = " \t\n"; +int AclReader::tokenize(char* line, std::vector<std::string>& toks) { + const char* tokChars = " \t\n\f\v\r"; int cnt = 0; char* cp = std::strtok(line, tokChars); while (cp != 0) { @@ -69,5 +296,209 @@ int AclReader::tokenizeLine(char* line, std::vector<std::string>& toks) { return cnt; } +// Return true if the line is successfully processed without errors +// If cont is true, then groupName must be set to the continuation group name +bool AclReader::processGroupLine(tokList& toks, const bool cont) { + const unsigned toksSize = toks.size(); + if (contFlag) { + gmCitr citr = groups.find(groupName); + for (unsigned i = 0; i < toksSize; i++) { + if (!checkName(toks[i])) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Name \"" << toks[i] << "\" contains illegal characters."); + return false; + } + addName(toks[i], citr->second); + } + } else { + if (toksSize < (cont ? 2 : 3)) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Insufficient tokens for group definition."); + return false; + } + if (!checkName(toks[1])) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Group name \"" << toks[1] << "\" contains illegal characters."); + return false; + } + gmCitr citr = addGroup(toks[1]); + if (citr == groups.end()) return false; + for (unsigned i = 2; i < toksSize; i++) { + if (!checkName(toks[i])) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Name \"" << toks[i] << "\" contains illegal characters."); + return false; + } + addName(toks[i], citr->second); + } + } + return true; +} + +// Return true if sucessfully added group +AclReader::gmCitr AclReader::addGroup(const std::string& newGroupName) { + gmCitr citr = groups.find(newGroupName); + if (citr != groups.end()) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Duplicate group name \"" << newGroupName << "\"."); + return groups.end(); + } + groupPair p(newGroupName, nameSetPtr(new nameSet)); + gmRes res = groups.insert(p); + assert(res.second); + groupName = newGroupName; + return res.first; +} + +void AclReader::addName(const std::string& name, nameSetPtr groupNameSet) { + gmCitr citr = groups.find(name); + if (citr != groups.end()) { + // This is a previously defined group: add all the names in that group to this group + groupNameSet->insert(citr->second->begin(), citr->second->end()); + } else { + // Not a known group name + groupNameSet->insert(name); + addName(name); + } +} + +void AclReader::addName(const std::string& name) { + names.insert(name); +} + +// Debug aid +void AclReader::printNames() const { + QPID_LOG(debug, "Group list: " << groups.size() << " groups found:" ); + std::string tmp; + for (gmCitr i=groups.begin(); i!= groups.end(); i++) { + tmp += " \""; + tmp += i->first; + tmp += "\":"; + for (nsCitr j=i->second->begin(); j!=i->second->end(); j++) { + tmp += " "; + tmp += *j; + } + QPID_LOG(debug, tmp); + tmp.clear(); + } + QPID_LOG(debug, "Name list: " << names.size() << " names found:" ); + tmp.clear(); + for (nsCitr k=names.begin(); k!=names.end(); k++) { + tmp += " "; + tmp += *k; + } + QPID_LOG(debug, tmp); +} + +bool AclReader::processAclLine(tokList& toks) { + const unsigned toksSize = toks.size(); + if (toksSize < 4) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Insufficient tokens for acl definition."); + return false; + } + + AclResult res; + try { + res = AclHelper::getAclResult(toks[1]); + } catch (...) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Unknown ACL permission \"" << toks[1] << "\"."); + return false; + } + + bool actionAllFlag = toks[3].compare("all") == 0; + Action action; + if (actionAllFlag) { + if (toksSize > 4) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Tokens found after action \"all\"."); + return false; + } + action = CONSUME; // dummy; compiler must initialize action for this code path + } else { + try { + action = AclHelper::getAction(toks[3]); + } catch (...) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Unknown action \"" << toks[3] << "\"."); + return false; + } + } + + // Create rule obj; then add object (if any) and properties (if any) + aclRulePtr rule; + if (actionAllFlag) { + rule.reset(new aclRule(res, toks[2], groups)); + } else { + rule.reset(new aclRule(res, toks[2], groups, action)); + } + + if (toksSize >= 5) { // object name-value pair + if (toks[4].compare("all") == 0) { + rule->setObjectTypeAll(); + } else { + try { + rule->setObjectType(AclHelper::getObjectType(toks[4])); + } catch (...) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Unknown object \"" << toks[4] << "\"."); + return false; + } + } + } + + if (toksSize >= 6) { // property name-value pair(s) + for (unsigned i=5; i<toksSize; i++) { + nvPair propNvp = splitNameValuePair(toks[i]); + if (propNvp.second.size() == 0) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Badly formed property name-value pair \"" << propNvp.first << "\". (Must be name=value)"); + return false; + } + Property prop; + try { + prop = AclHelper::getProperty(propNvp.first); + } catch (...) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Unknown property \"" << propNvp.first << "\"."); + return false; + } + rule->addProperty(prop, propNvp.second); + } + } + // Check if name (toks[2]) is group; if not, add as name of individual + if (toks[2].compare("all") != 0) { + if (groups.find(toks[2]) == groups.end()) { + addName(toks[2]); + } + } + + // If rule validates, add to rule list + if (!rule->validate(validationMap)) { + QPID_LOG(error, ACL_FORMAT_ERR_LOG_PREFIX << "Invalid object/action/property combination."); + return false; + } + rules.push_back(rule); + + return true; +} + +// Debug aid +void AclReader::printRules() const { + QPID_LOG(debug, "Rule list: " << rules.size() << " ACL rules found:"); + int cnt = 0; + for (rlCitr i=rules.begin(); i<rules.end(); i++,cnt++) { + QPID_LOG(debug, " " << std::setfill(' ') << std::setw(2) << cnt << " " << (*i)->toString()); + } +} + +// Static function +// Return true if the name is well-formed (ie contains legal characters) +bool AclReader::checkName(const std::string& name) { + for (unsigned i=0; i<name.size(); i++) { + const char ch = name.at(i); + if (!std::isalnum(ch) && ch != '-' && ch != '_') return false; + } + return true; +} + +// Static function +// Split name-value pair around '=' char of the form "name=value" +AclReader::nvPair AclReader::splitNameValuePair(const std::string& nvpString) { + std::size_t pos = nvpString.find("="); + if (pos == std::string::npos || pos == nvpString.size() - 1) { + return nvPair(nvpString, ""); + } + return nvPair(nvpString.substr(0, pos), nvpString.substr(pos+1)); +} }} // namespace qpid::acl |