1 /** @file
2 
3   User agent control by static IP address.
4 
5   This enables specifying the set of methods usable by a user agent based on the remove IP address
6   for a user agent connection.
7 
8   @section license License
9 
10   Licensed to the Apache Software Foundation (ASF) under one
11   or more contributor license agreements.  See the NOTICE file
12   distributed with this work for additional information
13   regarding copyright ownership.  The ASF licenses this file
14   to you under the Apache License, Version 2.0 (the
15   "License"); you may not use this file except in compliance
16   with the License.  You may obtain a copy of the License at
17 
18       http://www.apache.org/licenses/LICENSE-2.0
19 
20   Unless required by applicable law or agreed to in writing, software
21   distributed under the License is distributed on an "AS IS" BASIS,
22   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
23   See the License for the specific language governing permissions and
24   limitations under the License.
25  */
26 
27 #include <sstream>
28 #include "IPAllow.h"
29 #include "tscore/BufferWriter.h"
30 #include "tscore/ts_file.h"
31 #include "tscore/ink_memory.h"
32 #include "tscore/Filenames.h"
33 
34 #include "yaml-cpp/yaml.h"
35 
36 using ts::TextView;
37 
38 namespace
39 {
40 void
SignalError(ts::BufferWriter & w,bool & flag)41 SignalError(ts::BufferWriter &w, bool &flag)
42 {
43   if (!flag) {
44     flag = true;
45     pmgmt->signalManager(MGMT_SIGNAL_CONFIG_ERROR, w.data());
46   }
47   Error("%s", w.data());
48 }
49 
50 template <typename... Args>
51 void
ParseError(ts::TextView fmt,Args &&...args)52 ParseError(ts::TextView fmt, Args &&... args)
53 {
54   ts::LocalBufferWriter<1024> w;
55   w.printv(fmt, std::forward_as_tuple(args...));
56   w.write('\0');
57   Warning("%s", w.data());
58 }
59 
60 } // namespace
61 
62 namespace ts
63 {
64 BufferWriter &
bwformat(BufferWriter & w,BWFSpec const & spec,IpAllow const * obj)65 bwformat(BufferWriter &w, BWFSpec const &spec, IpAllow const *obj)
66 {
67   return w.print("{}[{}]", obj->MODULE_NAME, obj->get_config_file().c_str());
68 }
69 
70 BufferWriter &
bwformat(BufferWriter & w,BWFSpec const & spec,YAML::Mark const & mark)71 bwformat(BufferWriter &w, BWFSpec const &spec, YAML::Mark const &mark)
72 {
73   return w.print("Line {}", mark.line);
74 }
75 
76 BufferWriter &
bwformat(BufferWriter & w,BWFSpec const & spec,std::error_code const & ec)77 bwformat(BufferWriter &w, BWFSpec const &spec, std::error_code const &ec)
78 {
79   return w.print("[{}:{}]", ec.value(), ec.message());
80 }
81 
82 } // namespace ts
83 
84 namespace YAML
85 {
86 template <> struct convert<ts::TextView> {
87   static Node
encodeYAML::convert88   encode(ts::TextView const &tv)
89   {
90     Node zret;
91     zret = std::string(tv.data(), tv.size());
92     return zret;
93   }
94   static bool
decodeYAML::convert95   decode(const Node &node, ts::TextView &tv)
96   {
97     if (!node.IsScalar()) {
98       return false;
99     }
100     tv.assign(node.Scalar());
101     return true;
102   }
103 };
104 
105 } // namespace YAML
106 
107 enum AclOp {
108   ACL_OP_ALLOW, ///< Allow access.
109   ACL_OP_DENY,  ///< Deny access.
110 };
111 
112 const IpAllow::Record IpAllow::ALLOW_ALL_RECORD(ALL_METHOD_MASK);
113 const IpAllow::ACL IpAllow::DENY_ALL_ACL;
114 
115 size_t IpAllow::configid     = 0;
116 bool IpAllow::accept_check_p = true; // initializing global flag for fast deny
117 
118 static ConfigUpdateHandler<IpAllow> *ipAllowUpdate;
119 
120 //
121 //   Begin API functions
122 //
123 void
startup()124 IpAllow::startup()
125 {
126   // Should not have been initialized before
127   ink_assert(IpAllow::configid == 0);
128 
129   ipAllowUpdate = new ConfigUpdateHandler<IpAllow>();
130   ipAllowUpdate->attach("proxy.config.cache.ip_allow.filename");
131 
132   reconfigure();
133 
134   ConfigInfo *config = configProcessor.get(configid);
135   if (config == nullptr) {
136     configid = configProcessor.set(configid, new self_type("proxy.config.cache.ip_allow.filename"));
137     Warning("%s not loaded; All IP Addresses will be blocked.", ts::filename::IP_ALLOW);
138   }
139 }
140 
141 void
reconfigure()142 IpAllow::reconfigure()
143 {
144   self_type *new_table;
145 
146   Note("%s loading ...", ts::filename::IP_ALLOW);
147 
148   new_table     = new self_type("proxy.config.cache.ip_allow.filename");
149   int retStatus = new_table->BuildTable();
150   if (retStatus) {
151     Error("%s failed to load", ts::filename::IP_ALLOW);
152     delete new_table;
153   } else {
154     configid = configProcessor.set(configid, new_table);
155     Note("%s finished loading", ts::filename::IP_ALLOW);
156   }
157 }
158 
159 IpAllow *
acquire()160 IpAllow::acquire()
161 {
162   return static_cast<IpAllow *>(configProcessor.get(configid));
163 }
164 
165 void
release(IpAllow * config)166 IpAllow::release(IpAllow *config)
167 {
168   configProcessor.release(configid, config);
169 }
170 
171 void
release()172 IpAllow::release()
173 {
174   configProcessor.release(configid, this);
175 }
176 
177 IpAllow::ACL
match(sockaddr const * ip,match_key_t key)178 IpAllow::match(sockaddr const *ip, match_key_t key)
179 {
180   self_type *self = acquire();
181   void *raw       = nullptr;
182   if (SRC_ADDR == key) {
183     self->_src_map.contains(ip, &raw);
184     Record *r = static_cast<Record *>(raw);
185     // Special check - if checking in accept is enabled and the record is a deny all,
186     // then return a missing record instead to force an immediate deny. Otherwise it's delayed
187     // until after remap, to allow remap rules to tweak the result.
188     if (raw && r->_method_mask == 0 && r->_nonstandard_methods.empty() && accept_check_p) {
189       raw = nullptr;
190     }
191   } else {
192     self->_dst_map.contains(ip, &raw);
193   }
194   if (raw == nullptr) {
195     self->release();
196     self = nullptr;
197   }
198   return ACL{static_cast<Record *>(raw), self};
199 }
200 
201 //
202 //   End API functions
203 //
204 
IpAllow(const char * config_var)205 IpAllow::IpAllow(const char *config_var) : config_file(ats_scoped_str(RecConfigReadConfigPath(config_var)).get()) {}
206 
207 void
PrintMap(const IpMap * map) const208 IpAllow::PrintMap(const IpMap *map) const
209 {
210   std::ostringstream s;
211   s << map->count() << " ACL entries.";
212   for (auto &spot : *map) {
213     char text[INET6_ADDRSTRLEN];
214     Record const *ar = static_cast<Record const *>(spot.data());
215 
216     s << std::endl << "  Line " << ar->_src_line << ": " << ats_ip_ntop(spot.min(), text, sizeof text);
217     if (0 != ats_ip_addr_cmp(spot.min(), spot.max())) {
218       s << " - " << ats_ip_ntop(spot.max(), text, sizeof text);
219     }
220     s << " method=";
221     uint32_t mask = ALL_METHOD_MASK & ar->_method_mask;
222     if (ALL_METHOD_MASK == mask) {
223       s << "ALL";
224     } else if (0 == mask) {
225       s << "NONE";
226     } else {
227       bool leader        = false; // need leading vbar?
228       uint32_t test_mask = 1;     // mask for current method.
229       for (int i = 0; i < HTTP_WKSIDX_METHODS_CNT; ++i, test_mask <<= 1) {
230         if (mask & test_mask) {
231           if (leader) {
232             s << '|';
233           }
234           s << hdrtoken_index_to_wks(i + HTTP_WKSIDX_CONNECT);
235           leader = true;
236         }
237       }
238     }
239     if (!ar->_nonstandard_methods.empty()) {
240       s << " other methods=";
241       bool leader = false; // need leading vbar?
242       for (const auto &_nonstandard_method : ar->_nonstandard_methods) {
243         if (leader) {
244           s << '|';
245         }
246         s << _nonstandard_method;
247         leader = true;
248       }
249     }
250   }
251   Debug("ip-allow", "%s", s.str().c_str());
252 }
253 
254 void
Print() const255 IpAllow::Print() const
256 {
257   Debug("ip-allow", "Printing src map");
258   PrintMap(&_src_map);
259   Debug("ip-allow", "Printing dest map");
260   PrintMap(&_dst_map);
261 }
262 
263 int
BuildTable()264 IpAllow::BuildTable()
265 {
266   // Table should be empty
267   ink_assert(_src_map.count() == 0 && _dst_map.count() == 0);
268 
269   std::error_code ec;
270   std::string content{ts::file::load(config_file, ec)};
271   if (ec.value() == 0) {
272     // If it's a .yaml or the root tag is present, treat as YAML.
273     if (TextView{config_file.view()}.take_suffix_at('.') == "yaml" || std::string::npos != content.find(YAML_TAG_ROOT)) {
274       try {
275         this->YAMLBuildTable(content);
276       } catch (std::exception &ex) {
277         ParseError("{} - Invalid config: {}", this, ex.what());
278         return 1;
279       }
280     } else {
281       this->ATSBuildTable(content);
282     }
283 
284     if (_src_map.count() == 0 && _dst_map.count() == 0) {
285       ParseError("{} - No entries found. All IP Addresses will be blocked", this);
286       return 1;
287     }
288 
289     // convert the coloring from indices to pointers.
290     for (auto &item : _src_map) {
291       item.setData(&_src_acls[reinterpret_cast<size_t>(item.data())]);
292     }
293     for (auto &item : _dst_map) {
294       item.setData(&_dst_acls[reinterpret_cast<size_t>(item.data())]);
295     }
296     if (is_debug_tag_set("ip-allow")) {
297       Print();
298     }
299   } else {
300     ParseError("{} Failed to load {}. All IP Addresses will be blocked", this, ec);
301     return 1;
302   }
303   return 0;
304 }
305 
306 bool
YAMLLoadMethod(const YAML::Node & node,Record & rec)307 IpAllow::YAMLLoadMethod(const YAML::Node &node, Record &rec)
308 {
309   const std::string &value{node.Scalar()};
310 
311   if (0 == strcasecmp(value, YAML_VALUE_METHODS_ALL)) {
312     rec._method_mask = ALL_METHOD_MASK;
313   } else {
314     int method_idx = hdrtoken_tokenize(value.data(), value.size());
315     if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
316       rec._nonstandard_methods.push_back(value);
317       Debug("ip-allow", "Found nonstandard method '%s' at line %d", value.c_str(), node.Mark().line);
318     } else { // valid method.
319       rec._method_mask |= ACL::MethodIdxToMask(method_idx);
320     }
321   }
322   return true;
323 }
324 
325 bool
YAMLLoadIPAddrRange(const YAML::Node & node,IpMap * map,void * mark)326 IpAllow::YAMLLoadIPAddrRange(const YAML::Node &node, IpMap *map, void *mark)
327 {
328   if (node.IsScalar()) {
329     IpAddr min, max;
330     if (0 == ats_ip_range_parse(node.Scalar(), min, max)) {
331       map->fill(min, max, mark);
332       return true;
333     } else {
334       ParseError("{} {} - '{}' is not a valid range.", this, node.Mark(), node.Scalar());
335     }
336   }
337   return false;
338 }
339 
340 bool
YAMLLoadEntry(const YAML::Node & entry)341 IpAllow::YAMLLoadEntry(const YAML::Node &entry)
342 {
343   AclOp op = ACL_OP_DENY; // "shut up", I explained to the compiler.
344   YAML::Node node;
345   IpAddr min, max;
346   std::string value;
347   Record rec;
348   std::vector<Record> *acls{nullptr};
349   IpMap *map = nullptr;
350 
351   if (!entry.IsMap()) {
352     ParseError("{} {} - ACL items must be maps.", this, entry.Mark());
353     return false;
354   }
355 
356   if (entry[YAML_TAG_APPLY]) {
357     auto apply_node{entry[YAML_TAG_APPLY]};
358     if (apply_node.IsScalar()) {
359       ts::TextView value{apply_node.Scalar()};
360       if (0 == strcasecmp(value, YAML_VALUE_APPLY_IN)) {
361         acls = &_src_acls;
362         map  = &_src_map;
363       } else if (0 == strcasecmp(value, YAML_VALUE_APPLY_OUT)) {
364         acls = &_dst_acls;
365         map  = &_dst_map;
366       } else {
367         ParseError(R"("{}" value at {} must be "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN,
368                    YAML_VALUE_APPLY_OUT);
369         return false;
370       }
371     } else {
372       ParseError(R"("{}" value at {} must be a scalar, "{}" or "{}")", YAML_TAG_APPLY, entry.Mark(), YAML_VALUE_APPLY_IN,
373                  YAML_VALUE_APPLY_OUT);
374       return false;
375     }
376   } else {
377     ParseError(R"("Object at {} must have a "{}" key.)", entry.Mark(), YAML_TAG_APPLY);
378     return false;
379   }
380 
381   void *ipmap_mark = reinterpret_cast<void *>(acls->size());
382   if (entry[YAML_TAG_IP_ADDRS]) {
383     auto addr_node{entry[YAML_TAG_IP_ADDRS]};
384     if (addr_node.IsSequence()) {
385       for (auto const &n : addr_node) {
386         if (!this->YAMLLoadIPAddrRange(n, map, ipmap_mark)) {
387           return false;
388         }
389       }
390     } else if (!this->YAMLLoadIPAddrRange(addr_node, map, ipmap_mark)) {
391       return false;
392     }
393   }
394 
395   if (!entry[YAML_TAG_ACTION]) {
396     ParseError("{} {} - item ignored, required '{}' key not found.", this, entry.Mark(), YAML_TAG_ACTION);
397     return false;
398   }
399 
400   node = entry[YAML_TAG_ACTION];
401   if (!node.IsScalar()) {
402     ParseError("{} {} - item ignored, value for tag '{}' must be a string", this, node.Mark(), YAML_TAG_ACTION);
403     return false;
404   }
405   value = node.as<std::string>();
406   if (value == YAML_VALUE_ACTION_ALLOW) {
407     op = ACL_OP_ALLOW;
408   } else if (value == YAML_VALUE_ACTION_DENY) {
409     op = ACL_OP_DENY;
410   } else {
411     ParseError("{} {} - item ignored, value for tag '{}' must be '{}' or '{}'", this, node.Mark(), YAML_TAG_ACTION,
412                YAML_VALUE_ACTION_ALLOW, YAML_VALUE_ACTION_DENY);
413     return false;
414   }
415   if (!entry[YAML_TAG_METHODS]) {
416     rec._method_mask = ALL_METHOD_MASK;
417   } else {
418     node = entry[YAML_TAG_METHODS];
419     if (node.IsScalar()) {
420       this->YAMLLoadMethod(node, rec);
421     } else if (node.IsSequence()) {
422       for (auto const &elt : node) {
423         if (elt.IsScalar()) {
424           this->YAMLLoadMethod(elt, rec);
425           if (rec._method_mask == ALL_METHOD_MASK) {
426             break; // we're done here, nothing else matters.
427           }
428         } else {
429           ParseError("{} {} - item ignored, all values for '{}' must be strings.", this, elt.Mark(), YAML_TAG_METHODS);
430           return false;
431         }
432       }
433     } else {
434       ParseError("{} {} - item ignored, value for '{}' must be a single string or a list of strings.", this, node.Mark(),
435                  YAML_TAG_METHODS);
436     }
437   }
438   if (op == ACL_OP_DENY) {
439     rec._method_mask              = ALL_METHOD_MASK & ~rec._method_mask;
440     rec._deny_nonstandard_methods = true;
441   }
442   rec._src_line = entry.Mark().line;
443   // If we get here, everything parsed OK, add the record.
444   acls->emplace_back(std::move(rec));
445   return true;
446 }
447 
448 int
449 IpAllow::YAMLBuildTable(std::string const &content)
450 {
451   YAML::Node root{YAML::Load(content)};
452   if (!root.IsMap()) {
453     ParseError("{} - top level object was not a map. All IP Addresses will be blocked", this);
454     return 1;
455   }
456 
457   YAML::Node data{root[YAML_TAG_ROOT]};
458   if (!data) {
459     ParseError("{} - root tag '{}' not found. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
460   } else if (data.IsSequence()) {
461     for (auto const &entry : data) {
462       if (!this->YAMLLoadEntry(entry)) {
463         return 1;
464       }
465     }
466   } else if (data.IsMap()) {
467     this->YAMLLoadEntry(data); // singleton, just load it.
468   } else {
469     ParseError("{} - root tag '{}' is not an map or sequence. All IP Addresses will be blocked", this, YAML_TAG_ROOT);
470     return 1;
471   }
472   return 0;
473 }
474 
475 int
476 IpAllow::ATSBuildTable(std::string const &content)
477 {
478   int line_num = 0;
479   IpAddr addr1;
480   IpAddr addr2;
481   bool alarmAlready = false;
482   ts::LocalBufferWriter<1024> bw_err;
483 
484   TextView src(content);
485   TextView line;
486   auto err_prefix = [&]() -> ts::BufferWriter & {
487     return bw_err.reset().print("{} discarding '{}' entry at line {} : ", MODULE_NAME, config_file.c_str(), line_num);
488   };
489 
490   while (!(line = src.take_prefix_at('\n')).empty()) {
491     ++line_num;
492     line.trim_if(&isspace);
493 
494     if (!line.empty() && *line != '#') {
495       TextView token = line.take_prefix_if(&isspace);
496       TextView value = token.split_suffix_at('=');
497       match_key_t match;
498       if (value.empty()) {
499         err_prefix().print("No value found in token '{}'.\0", token);
500         SignalError(bw_err, alarmAlready);
501         continue;
502       } else if (strcasecmp(token, OPT_MATCH_SRC) == 0) {
503         match = SRC_ADDR;
504       } else if (strcasecmp(token, OPT_MATCH_DST) == 0) {
505         match = DST_ADDR;
506       } else {
507         err_prefix().print("'{}' is not a valid key.\0", token);
508         SignalError(bw_err, alarmAlready);
509         continue;
510       }
511 
512       if (0 == ats_ip_range_parse(value, addr1, addr2)) {
513         uint32_t acl_method_mask      = 0;
514         bool op_found_p               = false;
515         bool method_found_p           = false;
516         bool all_found_p              = false;
517         bool deny_nonstandard_methods = false;
518         bool line_valid_p             = true;
519         AclOp op                      = ACL_OP_DENY; // "shut up", I explained to the compiler.
520         MethodNames nonstandard_methods;
521 
522         while (line_valid_p && !line.ltrim_if(&isspace).empty()) {
523           token = line.take_prefix_if(&isspace);
524           value = token.split_suffix_at('=');
525 
526           if (value.empty()) {
527             err_prefix().print("No value found in token '{}'\0", token);
528             SignalError(bw_err, alarmAlready);
529             line_valid_p = false;
530           } else if (strcasecmp(token, OPT_ACTION_TAG) == 0) {
531             if (strcasecmp(value, OPT_ACTION_ALLOW) == 0) {
532               op_found_p = true, op = ACL_OP_ALLOW;
533             } else if (strcasecmp(value, OPT_ACTION_DENY) == 0) {
534               op_found_p = true, op = ACL_OP_DENY;
535             } else {
536               err_prefix().print("'{}' is not a valid action\0", value);
537               SignalError(bw_err, alarmAlready);
538               line_valid_p = false;
539             }
540           } else if (strcasecmp(token, OPT_METHOD) == 0) {
541             // Parse method="GET|HEAD"
542             while (!value.empty()) {
543               TextView method_name = value.take_prefix_at('|');
544               if (strcasecmp(method_name, OPT_METHOD_ALL) == 0) {
545                 all_found_p = true;
546                 break;
547               } else {
548                 int method_idx = hdrtoken_tokenize(method_name.data(), method_name.size());
549                 if (method_idx < HTTP_WKSIDX_CONNECT || method_idx >= HTTP_WKSIDX_CONNECT + HTTP_WKSIDX_METHODS_CNT) {
550                   nonstandard_methods.emplace_back(std::string(method_name.data(), method_name.size()));
551                   Debug("ip-allow", "%s",
552                         bw_err.reset().print("Found nonstandard method '{}' on line {}\0", method_name, line_num).data());
553                 } else { // valid method.
554                   acl_method_mask |= ACL::MethodIdxToMask(method_idx);
555                 }
556                 method_found_p = true;
557               }
558             }
559           } else {
560             err_prefix().print("'{}' is not a valid token\0", token);
561             SignalError(bw_err, alarmAlready);
562             line_valid_p = false;
563           }
564         }
565         if (!line_valid_p) {
566           continue; // error parsing the line, go on to the next.
567         }
568         if (!op_found_p) {
569           err_prefix().print("No action found.\0");
570           SignalError(bw_err, alarmAlready);
571           continue;
572         }
573         // If method not specified, default to ALL
574         if (all_found_p || !method_found_p) {
575           method_found_p  = true;
576           acl_method_mask = ALL_METHOD_MASK;
577           nonstandard_methods.clear();
578         }
579         // When deny, use bitwise complement.  (Make the rule 'allow for all
580         // methods except those specified')
581         if (op == ACL_OP_DENY) {
582           acl_method_mask          = ALL_METHOD_MASK & ~acl_method_mask;
583           deny_nonstandard_methods = true;
584         }
585 
586         if (method_found_p) {
587           std::vector<Record> &acls = match == DST_ADDR ? _dst_acls : _src_acls;
588           IpMap &map                = match == DST_ADDR ? _dst_map : _src_map;
589           acls.emplace_back(acl_method_mask, line_num, std::move(nonstandard_methods), deny_nonstandard_methods);
590           // Color with index in acls because at this point the address is volatile.
591           map.fill(addr1, addr2, reinterpret_cast<void *>(acls.size() - 1));
592         } else {
593           err_prefix().print("No valid method found\0"); // changed by YTS Team, yamsat bug id -59022
594           SignalError(bw_err, alarmAlready);
595         }
596       } else {
597         err_prefix().print("'{}' is not a valid IP address range\0", value);
598         SignalError(bw_err, alarmAlready);
599       }
600     }
601   }
602   return 0;
603 }
604