diff --git a/proxy/IPAllow.cc b/proxy/IPAllow.cc index 33917af148c..f0d3bd7e45f 100644 --- a/proxy/IPAllow.cc +++ b/proxy/IPAllow.cc @@ -25,83 +25,40 @@ */ #include + #include "IPAllow.h" -#include "tscore/BufferWriter.h" -#include "tscore/ts_file.h" -#include "tscore/ink_memory.h" #include "tscore/Filenames.h" +#include "tscpp/util/ts_errata.h" -#include "yaml-cpp/yaml.h" - -using ts::TextView; +#include "swoc/Vectray.h" +#include "swoc/BufferWriter.h" +#include "swoc/bwf_std.h" +#include "swoc/bwf_ex.h" +#include "swoc/bwf_ip.h" -namespace -{ -void -SignalError(ts::BufferWriter &w, bool &flag) -{ - if (!flag) { - flag = true; - } - Error("%s", w.data()); -} +#include "yaml-cpp/yaml.h" -template -void -ParseError(ts::TextView fmt, Args &&... args) -{ - ts::LocalBufferWriter<1024> w; - w.printv(fmt, std::forward_as_tuple(args...)); - w.write('\0'); - Warning("%s", w.data()); -} +using swoc::TextView; -} // namespace +using swoc::BufferWriter; +using swoc::bwf::Spec; -namespace ts +namespace swoc { BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, IpAllow const *obj) +bwformat(BufferWriter &w, Spec const &spec, IpAllow const *obj) { return w.print("{}[{}]", obj->MODULE_NAME, obj->get_config_file().c_str()); } +// This needs to be in namespace "swoc" or "YAML" or ADL doesn't find the overload. BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, YAML::Mark const &mark) +bwformat(BufferWriter &w, Spec const &spec, YAML::Mark const &mark) { return w.print("Line {}", mark.line); } -BufferWriter & -bwformat(BufferWriter &w, BWFSpec const &spec, std::error_code const &ec) -{ - return w.print("[{}:{}]", ec.value(), ec.message()); -} - -} // namespace ts - -namespace YAML -{ -template <> struct convert { - static Node - encode(ts::TextView const &tv) - { - Node zret; - zret = std::string(tv.data(), tv.size()); - return zret; - } - static bool - decode(const Node &node, ts::TextView &tv) - { - if (!node.IsScalar()) { - return false; - } - tv.assign(node.Scalar()); - return true; - } -}; - -} // namespace YAML +} // namespace swoc enum AclOp { ACL_OP_ALLOW, ///< Allow access. @@ -119,6 +76,15 @@ static ConfigUpdateHandler *ipAllowUpdate; // // Begin API functions // +swoc::TextView +IpAllow::localize(swoc::TextView src) +{ + auto span = _arena.alloc(src.size() + 1).rebind(); // always make a C-str if copying. + memcpy(span.data(), src.data(), src.size()); + span[src.size()] = '\0'; + return span.remove_suffix(1); // don't put the extra terminating nul in the view. +} + void IpAllow::startup() { @@ -144,10 +110,12 @@ IpAllow::reconfigure() Note("%s loading ...", ts::filename::IP_ALLOW); - new_table = new self_type("proxy.config.cache.ip_allow.filename"); - int retStatus = new_table->BuildTable(); - if (retStatus) { - Error("%s failed to load", ts::filename::IP_ALLOW); + new_table = new self_type("proxy.config.cache.ip_allow.filename"); + auto errata = new_table->BuildTable(); + if (!errata.is_ok()) { + std::string text; + swoc::bwprint(text, "{} failed to load\n{}", ts::filename::IP_ALLOW, errata); + Error("%s", text.c_str()); delete new_table; } else { configid = configProcessor.set(configid, new_table); @@ -174,27 +142,30 @@ IpAllow::release() } IpAllow::ACL -IpAllow::match(sockaddr const *ip, match_key_t key) +IpAllow::match(swoc::IPAddr const &addr, match_key_t key) { - self_type *self = acquire(); - void *raw = nullptr; + self_type *self = acquire(); + Record const *record = nullptr; if (SRC_ADDR == key) { - self->_src_map.contains(ip, &raw); - Record *r = static_cast(raw); - // Special check - if checking in accept is enabled and the record is a deny all, - // then return a missing record instead to force an immediate deny. Otherwise it's delayed - // until after remap, to allow remap rules to tweak the result. - if (raw && r->_method_mask == 0 && r->_nonstandard_methods.empty() && accept_check_p) { - raw = nullptr; + if (auto spot = self->_src_map.find(addr); spot != self->_src_map.end()) { + auto r = std::get<1>(*spot); + // Special case - if checking in accept is enabled and the record is a deny all, + // then return a missing record instead to force an immediate deny. Otherwise it's delayed + // until after remap, to allow remap rules to tweak the result. + if (!(accept_check_p && r->_method_mask == 0 && r->_nonstandard_methods.empty())) { + record = r; + } } - } else { - self->_dst_map.contains(ip, &raw); + } else if (auto spot = self->_dst_map.find(addr); spot != self->_dst_map.end()) { + record = std::get<1>(*spot); } - if (raw == nullptr) { - self->release(); - self = nullptr; + + if (record == nullptr) { + self->release(); // no record, don't keep a reference to the config. + return {}; } - return ACL{static_cast(raw), self}; + + return ACL{record, self}; // Note this keeps the config in memory. } // @@ -203,400 +174,272 @@ IpAllow::match(sockaddr const *ip, match_key_t key) IpAllow::IpAllow(const char *config_var) : config_file(ats_scoped_str(RecConfigReadConfigPath(config_var)).get()) {} -void -IpAllow::PrintMap(const IpMap *map) const +BufferWriter & +bwformat(BufferWriter &w, Spec const &spec, IpAllow::IpMap const &map) { - std::ostringstream s; - s << map->count() << " ACL entries."; - for (auto &spot : *map) { - char text[INET6_ADDRSTRLEN]; - Record const *ar = static_cast(spot.data()); - - s << std::endl << " Line " << ar->_src_line << ": " << ats_ip_ntop(spot.min(), text, sizeof text); - if (0 != ats_ip_addr_cmp(spot.min(), spot.max())) { - s << " - " << ats_ip_ntop(spot.max(), text, sizeof text); - } - s << " method="; - uint32_t mask = ALL_METHOD_MASK & ar->_method_mask; - if (ALL_METHOD_MASK == mask) { - s << "ALL"; + w.print("{} entries", map.count()); + for (auto const &spot : map) { + auto const *r = std::get<1>(spot); + w.print("\n Line {}: {} methods=", r->_src_line, std::get<0>(spot)); + uint32_t mask = IpAllow::ALL_METHOD_MASK & r->_method_mask; + if (IpAllow::ALL_METHOD_MASK == mask) { + w.write("ALL"); } else if (0 == mask) { - s << "NONE"; + w.write("NONE"); } else { bool leader = false; // need leading vbar? uint32_t test_mask = 1; // mask for current method. for (int i = 0; i < HTTP_WKSIDX_METHODS_CNT; ++i, test_mask <<= 1) { if (mask & test_mask) { - if (leader) { - s << '|'; - } - s << hdrtoken_index_to_wks(i + HTTP_WKSIDX_CONNECT); + w.print("{}{}", swoc::bwf::If(leader, "|"), hdrtoken_index_to_wks(i + HTTP_WKSIDX_CONNECT)); leader = true; } } } - if (!ar->_nonstandard_methods.empty()) { - s << " other methods="; + + if (!r->_nonstandard_methods.empty()) { + w.print(" {}=", r->_deny_nonstandard_methods ? IpAllow::YAML_VALUE_ACTION_ALLOW : IpAllow::YAML_VALUE_ACTION_DENY); bool leader = false; // need leading vbar? - for (const auto &_nonstandard_method : ar->_nonstandard_methods) { - if (leader) { - s << '|'; - } - s << _nonstandard_method; + for (auto const &name : r->_nonstandard_methods) { + w.print("{}{}", swoc::bwf::If(leader, "|"), name); leader = true; } } } - Debug("ip_allow", "%s", s.str().c_str()); + return w; +} + +void +IpAllow::DebugMap(const IpMap &map) const +{ + std::string out; + out.resize(8192); + swoc::bwprint(out, "{}", map); + Debug("ip_allow", "%s", out.c_str()); } void IpAllow::Print() const { Debug("ip_allow", "Printing src map"); - PrintMap(&_src_map); + DebugMap(_src_map); Debug("ip_allow", "Printing dest map"); - PrintMap(&_dst_map); + DebugMap(_dst_map); } -int +swoc::Errata IpAllow::BuildTable() { // Table should be empty ink_assert(_src_map.count() == 0 && _dst_map.count() == 0); std::error_code ec; - std::string content{ts::file::load(config_file, ec)}; + std::string content{swoc::file::load(config_file, ec)}; + swoc::Errata errata; if (ec.value() == 0) { - // If it's a .yaml or the root tag is present, treat as YAML. - if (TextView{config_file.view()}.take_suffix_at('.') == "yaml" || std::string::npos != content.find(YAML_TAG_ROOT)) { - try { - this->YAMLBuildTable(content); - } catch (std::exception &ex) { - ParseError("{} - Invalid config: {}", this, ex.what()); - return 1; - } - } else { - this->ATSBuildTable(content); + try { + errata = this->YAMLBuildTable(content); + } catch (std::exception &ex) { + return swoc::Errata(ec, ERRATA_ERROR, "{} - Invalid config: {}", this, ex.what()); + } + if (!errata.is_ok()) { + errata.note("While parsing config file"); + return errata; } if (_src_map.count() == 0 && _dst_map.count() == 0) { - ParseError("{} - No entries found. All IP Addresses will be blocked", this); - return 1; + return swoc::Errata(ERRATA_ERROR, "{} - No entries found. All IP Addresses will be blocked", this); } - // convert the coloring from indices to pointers. - for (auto &item : _src_map) { - item.setData(&_src_acls[reinterpret_cast(item.data())]); - } - for (auto &item : _dst_map) { - item.setData(&_dst_acls[reinterpret_cast(item.data())]); - } if (is_debug_tag_set("ip_allow")) { Print(); } } else { - ParseError("{} Failed to load {}. All IP Addresses will be blocked", this, ec); - return 1; + return swoc::Errata(ERRATA_ERROR, "{} Failed to load {}. All IP Addresses will be blocked", this, ec); } - return 0; + return {}; } -bool +swoc::Errata IpAllow::YAMLLoadMethod(const YAML::Node &node, Record &rec) { - const std::string &value{node.Scalar()}; + swoc::TextView value{node.Scalar()}; + swoc::Vectray names; + // Process a single token. Required to deal with the variable number of tokens. + auto parse_method = [&](swoc::TextView value) -> void { + if (0 == strcasecmp(value, YAML_VALUE_METHODS_ALL)) { + rec._method_mask = ALL_METHOD_MASK; + } else { + int method_idx = hdrtoken_tokenize(value.data(), value.size()); + if (HTTP_WKSIDX_CONNECT <= method_idx && method_idx < HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) { + rec._method_mask |= ACL::MethodIdxToMask(method_idx); + } else { + names.push_back(value); + Debug("ip_allow", "Found nonstandard method '%.*s' at line %d", int(value.size()), value.data(), node.Mark().line); + } + } + }; - if (0 == strcasecmp(value, YAML_VALUE_METHODS_ALL)) { - rec._method_mask = ALL_METHOD_MASK; + if (node.IsScalar()) { + parse_method(swoc::TextView(node.Scalar())); + } else if (node.IsSequence()) { + for (auto const &elt : node) { + if (elt.IsScalar()) { + parse_method(swoc::TextView(elt.Scalar())); + if (rec._method_mask == ALL_METHOD_MASK) { + break; // we're done here, nothing else matters. + } + } else { + return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, all values for '{}' must be strings.", this, elt.Mark(), + YAML_TAG_METHODS); + } + } } else { - int method_idx = hdrtoken_tokenize(value.data(), value.size()); - if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) { - rec._nonstandard_methods.push_back(value); - Debug("ip_allow", "Found nonstandard method '%s' at line %d", value.c_str(), node.Mark().line); - } else { // valid method. - rec._method_mask |= ACL::MethodIdxToMask(method_idx); + return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for '{}' must be a single string or a list of strings.", this, + node.Mark(), YAML_TAG_METHODS); + } + + // copy over to local memory if it's not all methods and there are non-standard ones. + if (rec._method_mask != ALL_METHOD_MASK && !names.empty()) { + rec._nonstandard_methods = _arena.alloc_span(names.size()); + for (unsigned idx = 0; idx < names.size(); ++idx) { + rec._nonstandard_methods[idx] = this->localize(names[idx]); } } - return true; + return {}; } -bool -IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, void *mark) +swoc::Errata +IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, IpAllow::Record const *record) { - if (node.IsScalar()) { - IpAddr min, max; - if (0 == ats_ip_range_parse(node.Scalar(), min, max)) { - map->fill(min, max, mark); - return true; - } else { - ParseError("{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar()); - } + if (!node.IsScalar()) { + return swoc::Errata(ERRATA_ERROR, "{} Expected IP address range at {}, found non-literal.", this, node.Mark()); + } + + swoc::TextView debug(node.Scalar()); + (void)debug; + if (swoc::IPRange r; r.load(node.Scalar())) { + map->fill(r, record); + } else { + return swoc::Errata(ERRATA_ERROR, "{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar()); } - return false; + return {}; } -bool +swoc::Errata IpAllow::YAMLLoadEntry(const YAML::Node &entry) { AclOp op = ACL_OP_DENY; // "shut up", I explained to the compiler. YAML::Node node; - IpAddr min, max; - std::string value; - Record rec; - std::vector *acls{nullptr}; - IpMap *map = nullptr; + auto record = _arena.make(); + IpMap *map = nullptr; // src or dst map. if (!entry.IsMap()) { - ParseError("{} {} - ACL items must be maps.", this, entry.Mark()); - return false; + return swoc::Errata(ERRATA_ERROR, "{} {} - ACL items must be maps.", this, entry.Mark()); } - if (entry[YAML_TAG_APPLY]) { - auto apply_node{entry[YAML_TAG_APPLY]}; + if (YAML::Node apply_node{entry[YAML_TAG_APPLY]}; apply_node) { if (apply_node.IsScalar()) { - ts::TextView value{apply_node.Scalar()}; + swoc::TextView value{apply_node.Scalar()}; if (0 == strcasecmp(value, YAML_VALUE_APPLY_IN)) { - acls = &_src_acls; - map = &_src_map; + map = &_src_map; } else if (0 == strcasecmp(value, YAML_VALUE_APPLY_OUT)) { - acls = &_dst_acls; - map = &_dst_map; + map = &_dst_map; } else { - ParseError(R"("{}" value at {} must be "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN, - YAML_VALUE_APPLY_OUT); - return false; + return swoc::Errata(ERRATA_ERROR, R"("{}" value at {} must be "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), + YAML_VALUE_APPLY_IN, YAML_VALUE_APPLY_OUT); } } else { - ParseError(R"("{}" value at {} must be a scalar, "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN, - YAML_VALUE_APPLY_OUT); - return false; + return swoc::Errata(ERRATA_ERROR, R"("{}" value at {} must be a scalar, "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), + YAML_VALUE_APPLY_IN, YAML_VALUE_APPLY_OUT); } } else { - ParseError(R"("Object at {} must have a "{}" key.)", entry.Mark(), YAML_TAG_APPLY); - return false; + return swoc::Errata(ERRATA_ERROR, R"("Object at {} must have a "{}" key.)", entry.Mark(), YAML_TAG_APPLY); } - void *ipmap_mark = reinterpret_cast(acls->size()); - if (entry[YAML_TAG_IP_ADDRS]) { - auto addr_node{entry[YAML_TAG_IP_ADDRS]}; - if (addr_node.IsSequence()) { - for (auto const &n : addr_node) { - if (!this->YAMLLoadIPAddrRange(n, map, ipmap_mark)) { - return false; - } + if (node = entry[YAML_TAG_ACTION]; node) { + if (node.IsScalar()) { + swoc::TextView value(node.Scalar()); + if (value == YAML_VALUE_ACTION_ALLOW) { + op = ACL_OP_ALLOW; + } else if (value == YAML_VALUE_ACTION_DENY) { + op = ACL_OP_DENY; + } else { + return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for tag '{}' must be '{}' or '{}'", this, node.Mark(), + YAML_TAG_ACTION, YAML_VALUE_ACTION_ALLOW, YAML_VALUE_ACTION_DENY); } - } else if (!this->YAMLLoadIPAddrRange(addr_node, map, ipmap_mark)) { - return false; + } else { + return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, value for tag '{}' must be a string", this, node.Mark(), + YAML_TAG_ACTION); } - } - - if (!entry[YAML_TAG_ACTION]) { - ParseError("{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_ACTION); - return false; - } - - node = entry[YAML_TAG_ACTION]; - if (!node.IsScalar()) { - ParseError("{} {} - item ignored, value for tag '{}' must be a string", this, node.Mark(), YAML_TAG_ACTION); - return false; - } - value = node.as(); - if (value == YAML_VALUE_ACTION_ALLOW) { - op = ACL_OP_ALLOW; - } else if (value == YAML_VALUE_ACTION_DENY) { - op = ACL_OP_DENY; } else { - ParseError("{} {} - item ignored, value for tag '{}' must be '{}' or '{}'", this, node.Mark(), YAML_TAG_ACTION, - YAML_VALUE_ACTION_ALLOW, YAML_VALUE_ACTION_DENY); - return false; + return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_ACTION); } - if (!entry[YAML_TAG_METHODS]) { - rec._method_mask = ALL_METHOD_MASK; - } else { - node = entry[YAML_TAG_METHODS]; - if (node.IsScalar()) { - this->YAMLLoadMethod(node, rec); - } else if (node.IsSequence()) { - for (auto const &elt : node) { - if (elt.IsScalar()) { - this->YAMLLoadMethod(elt, rec); - if (rec._method_mask == ALL_METHOD_MASK) { - break; // we're done here, nothing else matters. - } + + if (YAML::Node addr_node = entry[YAML_TAG_IP_ADDRS]; addr_node) { + bool marked_p = false; + if (addr_node.IsSequence()) { + for (auto const &n : addr_node) { + if (auto errata = this->YAMLLoadIPAddrRange(n, map, record); errata.is_ok()) { + marked_p = true; } else { - ParseError("{} {} - item ignored, all values for '{}' must be strings.", this, elt.Mark(), YAML_TAG_METHODS); - return false; + errata.note(R"(In record at {})", entry.Mark()); + return errata; } } } else { - ParseError("{} {} - item ignored, value for '{}' must be a single string or a list of strings.", this, node.Mark(), - YAML_TAG_METHODS); + if (auto errata = this->YAMLLoadIPAddrRange(addr_node, map, record); errata.is_ok()) { + marked_p = true; + } else { + errata.note(R"(In record at {})", entry.Mark()); + return errata; + } + } + if (!marked_p) { + return swoc::Errata(ERRATA_ERROR, "No valid addresses for rule at {}", node.Mark()); } + } else { + return swoc::Errata(ERRATA_ERROR, "{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_IP_ADDRS); } + + if (node = entry[YAML_TAG_METHODS]; node) { + if (auto errata = this->YAMLLoadMethod(node, *record); !errata.is_ok()) { + return errata; + } + } else { + record->_method_mask = ALL_METHOD_MASK; + } + if (op == ACL_OP_DENY) { - rec._method_mask = ALL_METHOD_MASK & ~rec._method_mask; - rec._deny_nonstandard_methods = true; + record->_method_mask = ALL_METHOD_MASK & ~record->_method_mask; + record->_deny_nonstandard_methods = true; } - rec._src_line = entry.Mark().line; - // If we get here, everything parsed OK, add the record. - acls->emplace_back(std::move(rec)); - return true; + + record->_src_line = entry.Mark().line; + return {}; } -int +swoc::Errata IpAllow::YAMLBuildTable(std::string const &content) { YAML::Node root{YAML::Load(content)}; if (!root.IsMap()) { - ParseError("{} - top level object was not a map. All IP Addresses will be blocked", this); - return 1; + return swoc::Errata("{} - top level object was not a map. All IP Addresses will be blocked", this); } - YAML::Node data{root[YAML_TAG_ROOT]}; + YAML::Node data{root[YAML_TAG_ROOT.data()]}; if (!data) { - ParseError("{} - root tag '{}' not found. All IP Addresses will be blocked", this, YAML_TAG_ROOT); + return swoc::Errata("{} - root tag '{}' not found. All IP Addresses will be blocked", this, YAML_TAG_ROOT); } else if (data.IsSequence()) { for (auto const &entry : data) { - if (!this->YAMLLoadEntry(entry)) { - return 1; + if (auto errata = this->YAMLLoadEntry(entry); !errata.is_ok()) { + return errata; } } } else if (data.IsMap()) { - this->YAMLLoadEntry(data); // singleton, just load it. + return this->YAMLLoadEntry(data); // singleton, just load it. } else { - ParseError("{} - root tag '{}' is not an map or sequence. All IP Addresses will be blocked", this, YAML_TAG_ROOT); - return 1; - } - return 0; -} - -int -IpAllow::ATSBuildTable(std::string const &content) -{ - int line_num = 0; - IpAddr addr1; - IpAddr addr2; - bool alarmAlready = false; - ts::LocalBufferWriter<1024> bw_err; - - TextView src(content); - TextView line; - auto err_prefix = [&]() -> ts::BufferWriter & { - return bw_err.reset().print("{} discarding '{}' entry at line {} : ", MODULE_NAME, config_file.c_str(), line_num); - }; - - while (!(line = src.take_prefix_at('\n')).empty()) { - ++line_num; - line.trim_if(&isspace); - - if (!line.empty() && *line != '#') { - TextView token = line.take_prefix_if(&isspace); - TextView value = token.split_suffix_at('='); - match_key_t match; - if (value.empty()) { - err_prefix().print("No value found in token '{}'.\0", token); - SignalError(bw_err, alarmAlready); - continue; - } else if (strcasecmp(token, OPT_MATCH_SRC) == 0) { - match = SRC_ADDR; - } else if (strcasecmp(token, OPT_MATCH_DST) == 0) { - match = DST_ADDR; - } else { - err_prefix().print("'{}' is not a valid key.\0", token); - SignalError(bw_err, alarmAlready); - continue; - } - - if (0 == ats_ip_range_parse(value, addr1, addr2)) { - uint32_t acl_method_mask = 0; - bool op_found_p = false; - bool method_found_p = false; - bool all_found_p = false; - bool deny_nonstandard_methods = false; - bool line_valid_p = true; - AclOp op = ACL_OP_DENY; // "shut up", I explained to the compiler. - MethodNames nonstandard_methods; - - while (line_valid_p && !line.ltrim_if(&isspace).empty()) { - token = line.take_prefix_if(&isspace); - value = token.split_suffix_at('='); - - if (value.empty()) { - err_prefix().print("No value found in token '{}'\0", token); - SignalError(bw_err, alarmAlready); - line_valid_p = false; - } else if (strcasecmp(token, OPT_ACTION_TAG) == 0) { - if (strcasecmp(value, OPT_ACTION_ALLOW) == 0) { - op_found_p = true, op = ACL_OP_ALLOW; - } else if (strcasecmp(value, OPT_ACTION_DENY) == 0) { - op_found_p = true, op = ACL_OP_DENY; - } else { - err_prefix().print("'{}' is not a valid action\0", value); - SignalError(bw_err, alarmAlready); - line_valid_p = false; - } - } else if (strcasecmp(token, OPT_METHOD) == 0) { - // Parse method="GET|HEAD" - while (!value.empty()) { - TextView method_name = value.take_prefix_at('|'); - if (strcasecmp(method_name, OPT_METHOD_ALL) == 0) { - all_found_p = true; - break; - } else { - int method_idx = hdrtoken_tokenize(method_name.data(), method_name.size()); - if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) { - nonstandard_methods.emplace_back(std::string(method_name.data(), method_name.size())); - Debug("ip_allow", "%s", - bw_err.reset().print("Found nonstandard method '{}' on line {}\0", method_name, line_num).data()); - } else { // valid method. - acl_method_mask |= ACL::MethodIdxToMask(method_idx); - } - method_found_p = true; - } - } - } else { - err_prefix().print("'{}' is not a valid token\0", token); - SignalError(bw_err, alarmAlready); - line_valid_p = false; - } - } - if (!line_valid_p) { - continue; // error parsing the line, go on to the next. - } - if (!op_found_p) { - err_prefix().print("No action found.\0"); - SignalError(bw_err, alarmAlready); - continue; - } - // If method not specified, default to ALL - if (all_found_p || !method_found_p) { - method_found_p = true; - acl_method_mask = ALL_METHOD_MASK; - nonstandard_methods.clear(); - } - // When deny, use bitwise complement. (Make the rule 'allow for all - // methods except those specified') - if (op == ACL_OP_DENY) { - acl_method_mask = ALL_METHOD_MASK & ~acl_method_mask; - deny_nonstandard_methods = true; - } - - if (method_found_p) { - std::vector &acls = match == DST_ADDR ? _dst_acls : _src_acls; - IpMap &map = match == DST_ADDR ? _dst_map : _src_map; - acls.emplace_back(acl_method_mask, line_num, std::move(nonstandard_methods), deny_nonstandard_methods); - // Color with index in acls because at this point the address is volatile. - map.fill(addr1, addr2, reinterpret_cast(acls.size() - 1)); - } else { - err_prefix().print("No valid method found\0"); // changed by YTS Team, yamsat bug id -59022 - SignalError(bw_err, alarmAlready); - } - } else { - err_prefix().print("'{}' is not a valid IP address range\0", value); - SignalError(bw_err, alarmAlready); - } - } + return swoc::Errata("{} - root tag '{}' is not an map or sequence. All IP Addresses will be blocked", this, YAML_TAG_ROOT); } - return 0; + return {}; } diff --git a/proxy/IPAllow.h b/proxy/IPAllow.h index 9221e7525bd..4dd9053fa92 100644 --- a/proxy/IPAllow.h +++ b/proxy/IPAllow.h @@ -36,9 +36,10 @@ #include "hdrs/HTTP.h" #include "ProxyConfig.h" -#include "tscore/IpMap.h" -#include "tscpp/util/TextView.h" -#include "tscore/ts_file.h" +#include "swoc/TextView.h" +#include "swoc/swoc_file.h" +#include "swoc/swoc_ip.h" +#include "swoc/Errata.h" // forward declare in name only so it can be a friend. struct IpAllowUpdate; @@ -53,7 +54,7 @@ class IpAllow : public ConfigInfo { friend struct IpAllowUpdate; - using MethodNames = std::vector; + using MethodNames = swoc::MemSpan; static constexpr uint32_t ALL_METHOD_MASK = ~0; // Mask for all methods. @@ -65,40 +66,54 @@ class IpAllow : public ConfigInfo /// Present only to make Vec<> happy, do not use. Record() = default; Record(Record &&that) = default; + /** Construct from mask. + * + * @param method_mask Bit mask of allowed methods. + */ explicit Record(uint32_t method_mask); + + /** Construct from values. + * + * @param method_mask Well known method mask. + * @param line Source line in configuration file. + * @param nonstandard_methods Allowed methods that are not well known. + * @param deny_nonstandard_methods Denied methods that are not well known. + */ Record(uint32_t method_mask, int line, MethodNames &&nonstandard_methods, bool deny_nonstandard_methods); - uint32_t _method_mask{0}; - int _src_line{0}; - MethodNames _nonstandard_methods; - bool _deny_nonstandard_methods{false}; + uint32_t _method_mask{0}; ///< Well known method mask. + int _src_line{0}; ///< Configuration file sourc line. + MethodNames _nonstandard_methods; ///< Allowed methods that are not well known. + bool _deny_nonstandard_methods{false}; ///< Denied methods that are not well known. }; public: using self_type = IpAllow; ///< Self reference type. using scoped_config = ConfigProcessor::scoped_config; + using IpMap = swoc::IPSpace; // indicator for whether we should be checking the acl record for src ip or dest ip enum match_key_t { SRC_ADDR, DST_ADDR }; + /// Token strings for configuration - static constexpr ts::TextView OPT_MATCH_SRC{"src_ip"}; - static constexpr ts::TextView OPT_MATCH_DST{"dest_ip"}; - static constexpr ts::TextView OPT_ACTION_TAG{"action"}; - static constexpr ts::TextView OPT_ACTION_ALLOW{"ip_allow"}; - static constexpr ts::TextView OPT_ACTION_DENY{"ip_deny"}; - static constexpr ts::TextView OPT_METHOD{"method"}; - static constexpr ts::TextView OPT_METHOD_ALL{"all"}; - - static constexpr ts::TextView YAML_TAG_ROOT{"ip_allow"}; - static constexpr ts::TextView YAML_TAG_IP_ADDRS{"ip_addrs"}; - static constexpr ts::TextView YAML_TAG_APPLY{"apply"}; - static constexpr ts::TextView YAML_VALUE_APPLY_IN{"in"}; - static constexpr ts::TextView YAML_VALUE_APPLY_OUT{"out"}; - static constexpr ts::TextView YAML_TAG_ACTION{"action"}; - static constexpr ts::TextView YAML_VALUE_ACTION_ALLOW{"allow"}; - static constexpr ts::TextView YAML_VALUE_ACTION_DENY{"deny"}; - static constexpr ts::TextView YAML_TAG_METHODS{"methods"}; - static constexpr ts::TextView YAML_VALUE_METHODS_ALL{"all"}; + static constexpr swoc::TextView OPT_MATCH_SRC{"src_ip"}; + static constexpr swoc::TextView OPT_MATCH_DST{"dest_ip"}; + static constexpr swoc::TextView OPT_ACTION_TAG{"action"}; + static constexpr swoc::TextView OPT_ACTION_ALLOW{"ip_allow"}; + static constexpr swoc::TextView OPT_ACTION_DENY{"ip_deny"}; + static constexpr swoc::TextView OPT_METHOD{"method"}; + static constexpr swoc::TextView OPT_METHOD_ALL{"all"}; + + static const inline std::string YAML_TAG_ROOT{"ip_allow"}; + static const inline std::string YAML_TAG_IP_ADDRS{"ip_addrs"}; + static const inline std::string YAML_TAG_APPLY{"apply"}; + static const inline std::string YAML_VALUE_APPLY_IN{"in"}; + static const inline std::string YAML_VALUE_APPLY_OUT{"out"}; + static const inline std::string YAML_TAG_ACTION{"action"}; + static const inline std::string YAML_VALUE_ACTION_ALLOW{"allow"}; + static const inline std::string YAML_VALUE_ACTION_DENY{"deny"}; + static const inline std::string YAML_TAG_METHODS{"methods"}; + static const inline std::string YAML_VALUE_METHODS_ALL{"all"}; static constexpr const char *MODULE_NAME = "IPAllow"; @@ -121,6 +136,11 @@ class IpAllow : public ConfigInfo void clear(); ///< Drop data and config reference. + /** Convert well known string index to mask. + * + * @param wksidx Well known string index. + * @return A mask for that method. + */ static uint32_t MethodIdxToMask(int wksidx); /// Check if the ACL is valid (i.e. not uninitialized or missing). @@ -149,8 +169,9 @@ class IpAllow : public ConfigInfo void Print() const; - static ACL match(sockaddr const *ip, match_key_t key); - static ACL match(IpEndpoint const *ip, match_key_t key); + static ACL match(swoc::IPAddr const &addr, match_key_t key); + static ACL match(swoc::IPEndpoint const *addr, match_key_t key); + static ACL match(sockaddr const *sa, match_key_t key); static void startup(); static void reconfigure(); @@ -180,27 +201,31 @@ class IpAllow : public ConfigInfo */ static bool isAcceptCheckEnabled(); - const ts::file::path &get_config_file() const; + const swoc::file::path &get_config_file() const; private: static size_t configid; ///< Configuration ID for update management. static const Record ALLOW_ALL_RECORD; ///< Static record that allows all access. static bool accept_check_p; ///< @c true if deny all can be enforced during accept. - void PrintMap(const IpMap *map) const; + void DebugMap(IpMap const &map) const; - int BuildTable(); - int ATSBuildTable(const std::string &); - int YAMLBuildTable(const std::string &); - bool YAMLLoadEntry(const YAML::Node &); - bool YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, void *mark); - bool YAMLLoadMethod(const YAML::Node &node, Record &rec); + swoc::Errata BuildTable(); + swoc::Errata YAMLBuildTable(const std::string &); + swoc::Errata YAMLLoadEntry(const YAML::Node &); + swoc::Errata YAMLLoadIPAddrRange(const YAML::Node &, IpMap *map, Record const *mark); + swoc::Errata YAMLLoadMethod(const YAML::Node &node, Record &rec); - ts::file::path config_file; ///< Path to configuration file. + /// Copy @a src to the local arena and review a view of the copy. + swoc::TextView localize(swoc::TextView src); + + swoc::file::path config_file; ///< Path to configuration file. IpMap _src_map; IpMap _dst_map; - std::vector _src_acls; - std::vector _dst_acls; + /// Storage for records. + swoc::MemArena _arena; + + friend swoc::BufferWriter &bwformat(swoc::BufferWriter &w, swoc::bwf::Spec const &spec, IpAllow::IpMap const &map); }; // ------ Record methods -------- @@ -321,9 +346,15 @@ IpAllow::isAcceptCheckEnabled() } inline auto -IpAllow::match(IpEndpoint const *ip, match_key_t key) -> ACL +IpAllow::match(swoc::IPEndpoint const *addr, match_key_t key) -> ACL +{ + return self_type::match(swoc::IPAddr(&addr->sa), key); +} + +inline auto +IpAllow::match(sockaddr const *sa, match_key_t key) -> ACL { - return self_type::match(&ip->sa, key); + return self_type::match(swoc::IPAddr(sa), key); } inline auto @@ -332,7 +363,7 @@ IpAllow::makeAllowAllACL() -> ACL return {&ALLOW_ALL_RECORD, nullptr}; } -inline const ts::file::path & +inline const swoc::file::path & IpAllow::get_config_file() const { return config_file;