//------------------------------------------------------------------------------
//! @file AclCmd.cc
//------------------------------------------------------------------------------
/************************************************************************
* EOS - the CERN Disk Storage System *
* Copyright (C) 2017 CERN/Switzerland *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see .*
************************************************************************/
#include "AclCmd.hh"
#include "mgm/Acl.hh"
#include "mgm/XrdMgmOfs.hh"
#include "common/StringTokenizer.hh"
#include "common/ErrnoToString.hh"
#include "namespace/Prefetcher.hh"
#include
#include
#include
EOSMGMNAMESPACE_BEGIN
//------------------------------------------------------------------------------
// Process request
//------------------------------------------------------------------------------
eos::console::ReplyProto
AclCmd::ProcessRequest() noexcept
{
using eos::console::AclProto;
eos::console::ReplyProto reply;
eos::console::AclProto acl = mReqProto.acl();
std::string err_msg;
if (acl.op() == AclProto::LIST) {
std::string acl_val;
GetAcls(acl.path(), acl_val, acl.sys_acl());
if (acl_val.empty()) {
mErr = "error: ";
mErr += eos::common::ErrnoToString(ENODATA);
reply.set_std_err(mErr);
reply.set_retc(ENODATA);
} else {
// Convert to username if possible, ignore errors
(void) Acl::ConvertIds(acl_val, true);
reply.set_std_out(acl_val);
reply.set_retc(0);
}
} else if (acl.op() == AclProto::MODIFY) {
int retc = ModifyAcls(acl);
reply.set_retc(retc);
reply.set_std_out("");
if (retc) {
reply.set_std_err(mErr);
}
} else {
reply.set_retc(EINVAL);
reply.set_std_err("error: not supported");
}
return reply;
}
//------------------------------------------------------------------------------
// Get sys.acl and user.acl for a given path
//------------------------------------------------------------------------------
void
AclCmd::GetAcls(const std::string& path, std::string& acl, bool is_sys,
bool take_lock)
{
XrdOucString value;
XrdOucErrInfo error;
std::string acl_key = (is_sys ? "sys.acl" : "user.acl");
if (gOFS->_attr_get(path.c_str(), error, mVid, 0, acl_key.c_str(), value)) {
value = "";
}
acl = value.c_str();
}
//------------------------------------------------------------------------------
// Modify acls
//------------------------------------------------------------------------------
int
AclCmd::ModifyAcls(const eos::console::AclProto& acl)
{
XrdOucString m_err = "";
// Parse acl modification command into bitmask rule format
if (!ParseRule(acl.rule())) {
mErr = "error: failed to parse input rule or unknown id";
return EINVAL;
}
bool fine_grained_write = !acl.sync_write();
std::list paths;
eos::Prefetcher::prefetchContainerMDAndWait(gOFS->eosView, acl.path(), false);
eos::common::RWMutexWriteLock ns_wr_lock;
if (!fine_grained_write) {
ns_wr_lock.Grab(gOFS->eosViewRWMutex);
}
if (acl.recursive()) {
// @todo (esindril): get list of all directories recursively
XrdOucErrInfo error;
std::map> dirs;
m_err.erase();
(void) gOFS->_find(acl.path().c_str(), error, m_err, mVid, dirs, nullptr,
nullptr, true, 0, false, 0, nullptr);
if (m_err.length()) {
mErr = m_err.c_str();
return EINVAL;
}
// Save all the directories in the current subtree
for (const auto& elem : dirs) {
paths.push_back(elem.first);
}
} else {
paths.push_back(acl.path());
}
std::string acl_key = (acl.sys_acl() ? "sys.acl" : "user.acl");
RuleMap rule_map;
std::string dir_acls, new_acl_val;
XrdOucErrInfo error;
for (const auto& elem : paths) {
GetAcls(elem, dir_acls, acl.sys_acl(), false);
GenerateRuleMap(dir_acls, rule_map);
// ACL position is 1-indexed as 0 is the default numeric protobuf val
auto [err, acl_pos] = GetRulePosition(rule_map.size(), acl.position());
if (err) {
mErr = "error: rule position cannot be met!";
return err;
}
ApplyRule(rule_map, acl_pos);
new_acl_val = GenerateAclString(rule_map);
// Set xattr taking the namespace lock
if (gOFS->_attr_set(elem.c_str(), error, mVid, 0, acl_key.c_str(),
new_acl_val.c_str())) {
mErr = "error: failed to set new acl for path=";
mErr += elem.c_str();
eos_err("%s", mErr.c_str());
return errno;
}
}
return 0;
}
//------------------------------------------------------------------------------
// Get ACL rule from string by creating a pair of identifier for the ACL and
// the bitmask representation
//------------------------------------------------------------------------------
Rule AclCmd::GetRuleFromString(const std::string& single_acl)
{
Rule ret;
auto acl_delimiter = single_acl.rfind(':');
ret.first = std::string(single_acl.begin(),
single_acl.begin() + acl_delimiter);
unsigned short rule_int = 0;
for (auto i = acl_delimiter + 1, size = single_acl.length(); i < size; ++i) {
switch (single_acl.at(i)) {
case 'r' :
rule_int = rule_int | AclCmd::R;
break;
case 'w' :
// Check for wo case
if ((i + 1 < size) && single_acl.at(i + 1) == 'o') {
i++;
rule_int = rule_int | AclCmd::WO;
} else {
rule_int = rule_int | AclCmd::W;
}
break;
case 'x' :
rule_int = rule_int | AclCmd::X;
break;
case 'm' :
rule_int = rule_int | AclCmd::M;
break;
case 'q' :
rule_int = rule_int | AclCmd::Q;
break;
case 'c' :
rule_int = rule_int | AclCmd::C;
break;
case 'a':
rule_int = rule_int | AclCmd::A;
break;
case '+' :
// There are only two + flags in current acl permissions +d and +u
i++;
if (single_acl.at(i) == 'd') {
rule_int = rule_int | AclCmd::pD;
} else {
rule_int = rule_int | AclCmd::pU;
}
break;
case '!' :
i++;
if (single_acl.at(i) == 'd') {
rule_int = rule_int | AclCmd::nD;
}
if (single_acl.at(i) == 'u') {
rule_int = rule_int | AclCmd::nU;
}
if (single_acl.at(i) == 'm') {
rule_int = rule_int | AclCmd::nM;
}
if (single_acl.at(i) == 'r') {
rule_int = rule_int | AclCmd::nR;
}
if (single_acl.at(i) == 'w') {
rule_int = rule_int | AclCmd::nW;
}
if (single_acl.at(i) == 'x') {
rule_int = rule_int | AclCmd::nX;
}
break;
default:
break;
}
}
ret.second = rule_int;
return ret;
}
//------------------------------------------------------------------------------
// Generate rule map from the string representation of the acls
//------------------------------------------------------------------------------
void
AclCmd::GenerateRuleMap(const std::string& acl_string, RuleMap& rmap)
{
if (acl_string.empty()) {
return;
}
rmap.clear();
size_t curr_pos = 0, pos = 0;
while (true) {
pos = acl_string.find(',', curr_pos);
if (pos == std::string::npos) {
pos = acl_string.length();
}
std::string single_acl = std::string(acl_string.begin() + curr_pos,
acl_string.begin() + pos);
insert_or_assign(rmap, GetRuleFromString(single_acl));
curr_pos = pos + 1;
if (curr_pos > acl_string.length()) {
break;
}
}
return;
}
//------------------------------------------------------------------------------
// Convert acl modification command into bitmask rule format
//------------------------------------------------------------------------------
bool AclCmd::GetRuleBitmask(const std::string& input, bool set)
{
bool lambda_happen = false;
unsigned short int ret = 0, add_ret = 0, rm_ret = 0;
auto add_lambda = [&add_ret, &ret](AclCmd::ACLPos pos) {
add_ret = add_ret | pos;
ret = ret | pos;
};
auto remove_lambda = [&rm_ret, &ret](AclCmd::ACLPos pos) {
rm_ret = rm_ret | pos;
ret = ret & (~pos);
};
std::functioncurr_lambda = add_lambda;
for (auto flag = input.begin(); flag != input.end(); ++flag) {
// Check for add/rm rules
if (*flag == '-') {
curr_lambda = remove_lambda;
lambda_happen = true;
continue;
}
if (*flag == '+') {
auto temp_iter = flag + 1;
if (temp_iter == input.end()) {
continue;
}
if (*temp_iter != 'd' && *temp_iter != 'u') {
lambda_happen = true;
curr_lambda = add_lambda;
continue;
}
}
// If there is no +/- character non-"set" mode
if (!set && !lambda_happen) {
return false;
}
// Check for flags
if (*flag == 'r') {
curr_lambda(AclCmd::R);
continue;
}
if (*flag == 'w') {
auto temp_iter = flag;
++temp_iter;
if ((temp_iter != input.end()) && (*temp_iter == 'o')) {
curr_lambda(AclCmd::WO);
++flag;
} else {
curr_lambda(AclCmd::W);
}
continue;
}
if (*flag == 'x') {
curr_lambda(AclCmd::X);
continue;
}
if (*flag == 'm') {
curr_lambda(AclCmd::M);
continue;
}
if (*flag == 'q') {
curr_lambda(AclCmd::Q);
continue;
}
if (*flag == 'a') {
curr_lambda(AclCmd::A);
continue;
}
if (*flag == 'c') {
curr_lambda(AclCmd::C);
continue;
}
if (*flag == '!') {
++flag;
if (flag == input.end()) {
goto error_label;
}
if (*flag == 'd') {
curr_lambda(AclCmd::nD);
continue;
}
if (*flag == 'u') {
curr_lambda(AclCmd::nU);
continue;
}
if (*flag == 'm') {
curr_lambda(AclCmd::nM);
continue;
}
if (*flag == 'r') {
curr_lambda(AclCmd::nR);
continue;
}
if (*flag == 'w') {
curr_lambda(AclCmd::nW);
continue;
}
if (*flag == 'x') {
curr_lambda(AclCmd::nX);
continue;
}
goto error_label;
}
if (*flag == '+') {
++flag;
if (*flag == 'd') {
curr_lambda(AclCmd::pD);
continue;
}
if (*flag == 'u') {
curr_lambda(AclCmd::pU);
continue;
}
}
goto error_label;
}
// Set the mask of flags which are going to be added or removed
mAddRule = ((add_ret == 0) ? 0 : ret & add_ret);
mRmRule = ((rm_ret == 0) ? 0 : ~ret & rm_ret);
return true;
error_label:
return false;
}
//------------------------------------------------------------------------------
// Parse command line (modification) rule given by the client
//------------------------------------------------------------------------------
bool AclCmd::ParseRule(const std::string& input)
{
size_t pos_del_first, pos_del_last, pos_equal;
pos_del_first = input.find(":");
pos_del_last = input.rfind(":");
pos_equal = input.find("=");
std::string id, srule;
if ((pos_del_first == pos_del_last) && (pos_equal != std::string::npos)) {
// u:id=rw+x | g:id=rw+x
mSet = true;
// Check if id and rule are correct
id = std::string(input.begin(), input.begin() + pos_equal);
if (!CheckCorrectId(id)) {
return false;
}
// Convert it to numeric format, add dummy ":r" and then remove it so that
// the format is what ConvertIds expects
id += ":r";
if (Acl::ConvertIds(id, false)) {
return false;
}
id = id.erase(id.rfind(':'));
mId = id;
eos_info("mId=%s", mId.c_str());
srule = std::string(input.begin() + pos_equal + 1, input.end());
if (!GetRuleBitmask(srule, mSet)) {
mErr = "error: failed to get input rule as bitmask";
return false;
}
} else {
if ((pos_del_first != pos_del_last) &&
(pos_del_first != std::string::npos) &&
(pos_del_last != std::string::npos)) {
mSet = false;
// u:id:+rw | g:id:rw+x
// Check if id and rule are correct
id = std::string(input.begin(), input.begin() + pos_del_last);
if (!CheckCorrectId(id)) {
mErr = "error: input rule has incorrect format for id";
return false;
}
// Convert it to numeric format, add dummy ":r" and then remove it so that
// the format is what ConvertIds expects
id += ":r";
if (Acl::ConvertIds(id, false)) {
return false;
}
id = id.erase(id.rfind(':'));
mId = id;
srule = std::string(input.begin() + pos_del_last + 1, input.end());
if (!GetRuleBitmask(srule, mSet)) {
mErr = "error: failed to get input rule as bitmask";
return false;
}
} else {
return false;
}
}
return true;
}
//------------------------------------------------------------------------------
// Check if id has the correct format
//------------------------------------------------------------------------------
bool
AclCmd::CheckCorrectId(const std::string& id) const
{
std::string allowed_chars =
"abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_-";
if ((id.at(0) == 'u' && id.at(1) == ':') ||
(id.at(0) == 'k' && id.at(1) == ':') ||
(id.at(0) == 'g' && id.at(1) == ':')) {
return id.find_first_not_of(allowed_chars, 2) == std::string::npos;
}
if (id.find("egroup") == 0 && id.at(6) == ':') {
return id.find_first_not_of(allowed_chars, 7) == std::string::npos;
}
return false;
}
//------------------------------------------------------------------------------
// Apply client modification rule(s) to the acls of the current entry
//------------------------------------------------------------------------------
void AclCmd::ApplyRule(RuleMap& rules, size_t pos)
{
unsigned short temp_rule = 0;
if (!mSet) {
auto it = std::find_if(rules.begin(),
rules.end(),
[&](const Rule & rule) -> bool {
return rule.first == mId;
});
if (it != rules.end()) {
temp_rule = it->second;
}
}
if (mAddRule != 0) {
temp_rule = temp_rule | mAddRule;
}
if (mRmRule != 0) {
temp_rule = temp_rule & (~mRmRule);
}
if (pos != 0) {
auto [it, err] = get_iterator(rules, pos);
if (err != 0) {
mErr = "Invalid position of rule, errc=" + std::to_string(err);
}
insert_or_assign(rules, mId, temp_rule, it, true);
return;
}
insert_or_assign(rules, mId, temp_rule);
}
//------------------------------------------------------------------------------
// Generate acl string representation from a rule map
//------------------------------------------------------------------------------
std::string
AclCmd::GenerateAclString(const RuleMap& rmap)
{
std::string ret = "";
for (const auto& elem : rmap) {
if (elem.second != 0) {
ret += elem.first + ":" + AclCmd::AclBitmaskToString(elem.second) + ",";
}
}
// Remove last ','
if (ret != "") {
ret = ret.substr(0, ret.size() - 1);
}
return ret;
}
//------------------------------------------------------------------------------
// Convert ACL bitmask to string representation
//------------------------------------------------------------------------------
std::string
AclCmd::AclBitmaskToString(const unsigned short int in)
{
std::string ret = "";
if (in & AclCmd::R) {
ret.append("r");
}
if (in & AclCmd::W) {
ret.append("w");
}
if (in & AclCmd::WO) {
ret.append("wo");
}
if (in & AclCmd::X) {
ret.append("x");
}
if (in & AclCmd::M) {
ret.append("m");
}
if (in & AclCmd::nM) {
ret.append("!m");
}
if (in & AclCmd::nD) {
ret.append("!d");
}
if (in & AclCmd::pD) {
ret.append("+d");
}
if (in & AclCmd::nU) {
ret.append("!u");
}
if (in & AclCmd::pU) {
ret.append("+u");
}
if (in & AclCmd::Q) {
ret.append("q");
}
if (in & AclCmd::C) {
ret.append("c");
}
if (in & AclCmd::A) {
ret.append("a");
}
if (in & AclCmd::nR) {
ret.append("!r");
}
if (in & AclCmd::nW) {
ret.append("!w");
}
if (in & AclCmd::nX) {
ret.append("!x");
}
return ret;
}
std::pair
AclCmd::GetRulePosition(size_t rule_map_sz, size_t rule_pos)
{
std::pair result {0, 0};
// Trivial case, nothing is set
if (!rule_map_sz && !rule_pos) {
return result;
}
if (!rule_map_sz) {
// Only valid case here is that the client asks the first position!
if (rule_pos != 1) {
result.first = EINVAL;
}
}
if (rule_map_sz && rule_pos) {
if (rule_pos > rule_map_sz) {
result.first = EINVAL;
}
result.second = rule_pos;
}
return result;
}
EOSMGMNAMESPACE_END