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