| /* |
| * |
| * Copyright (c) 2006 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/acl/AclReader.h" |
| |
| #include <cctype> |
| #include <cstring> |
| #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::ACT_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); |
| names.insert("*"); |
| } |
| |
| AclReader::~AclReader() {} |
| |
| std::string AclReader::getError() { |
| return errorStream.str(); |
| } |
| |
| int AclReader::read(const std::string& fn, boost::shared_ptr<AclData> d) { |
| fileName = fn; |
| lineNumber = 0; |
| char buff[1024]; |
| std::ifstream ifs(fn.c_str(), std::ios_base::in); |
| if (!ifs.good()) { |
| errorStream << "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); |
| lineNumber++; |
| if (std::strlen(buff) > 0 && buff[0] != '#') // Ignore blank lines and comments |
| err |= !processLine(buff); |
| } |
| if (!ifs.eof()) |
| { |
| errorStream << "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) { |
| errorStream << "Unable to read ACL file \"" << fn << "\": " << e.what(); |
| ifs.close(); |
| return -4; |
| } catch (...) { |
| errorStream << "Unable to read ACL file \"" << fn << "\": Unknown exception"; |
| ifs.close(); |
| 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; |
| |
| 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 { |
| errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Non-continuation line must start with \"group\" or \"acl\"."; |
| ret = false; |
| } |
| } |
| contFlag = cont; |
| return ret; |
| } |
| |
| 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) { |
| toks.push_back(std::string(cp)); |
| cnt++; |
| cp = std::strtok(0, tokChars); |
| } |
| 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])) { |
| errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Name \"" << toks[i] << "\" contains illegal characters."; |
| return false; |
| } |
| addName(toks[i], citr->second); |
| } |
| } else { |
| if (toksSize < (cont ? 2 : 3)) { |
| errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Insufficient tokens for group definition."; |
| return false; |
| } |
| if (!checkName(toks[1])) { |
| errorStream << 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])) { |
| errorStream << 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()) { |
| errorStream << 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() && citr->first != name){ |
| // 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) { |
| errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Insufficient tokens for acl definition."; |
| return false; |
| } |
| |
| AclResult res; |
| try { |
| res = AclHelper::getAclResult(toks[1]); |
| } catch (...) { |
| errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Unknown ACL permission \"" << toks[1] << "\"."; |
| return false; |
| } |
| |
| bool actionAllFlag = toks[3].compare("all") == 0; |
| bool userAllFlag = toks[2].compare("all") == 0; |
| Action action; |
| if (actionAllFlag) { |
| |
| if (userAllFlag && toksSize > 4) { |
| errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Tokens found after action \"all\"."; |
| return false; |
| } |
| action = ACT_CONSUME; // dummy; compiler must initialize action for this code path |
| } else { |
| try { |
| action = AclHelper::getAction(toks[3]); |
| } catch (...) { |
| errorStream << 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 (...) { |
| errorStream << 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) { |
| errorStream << 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 (...) { |
| errorStream << 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)) { |
| errorStream << 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 != '_' && 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 |