Line data Source code
1 : // Copyright (c) 2022-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/libaddr
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Implementation of the address validator.
22 : *
23 : * The address validator allows us to verify that a parameter represents
24 : * an address as supported by the addr_parser class.
25 : *
26 : * The input parameters understood by the address validator are:
27 : *
28 : * * `address=[yes|no|required] [commas] [spaces] [newlines] [range] [lookup]`
29 : *
30 : * If `yes`, then an address is allowed. This is the default if none of
31 : * `yes`, `no`, and `required` were specified.
32 : *
33 : * If `no`, an address is not allowed.
34 : *
35 : * If `required`, an address must be specified (it may still come from
36 : * the default address). This option is mutually exclusive with the `yes`
37 : * and the `no`. Only one of the three can be specified.
38 : *
39 : * The `commas`, `spaces`, `newlines` allow for multiple addresses
40 : * separated by commas (,), spaces ( ), and newlines (\\n). None,
41 : * one, or a mix can be specified. The newlines should probably not
42 : * be used with the command line validators since in all likelihood
43 : * users won't pass a parameter with newlines in it.
44 : *
45 : * The `range` allows address ranges (addresses separated by a dash (-)).
46 : *
47 : * The `lookup` -- if a hostname is specified, a lookup is allowed
48 : *
49 : * * `default=\<address>:\<port>/\<mask> ...`
50 : *
51 : * Specify a default address, port, and mask.
52 : *
53 : * You can specify two of these: one for IPv4 and one for IPv6. To make
54 : * sure that an address is viewed as an IPv6 address, make sure to use
55 : * brackets around the address and the mask.
56 : *
57 : * * `comment=yes|all|hash|semicolon|no`
58 : *
59 : * Whether comments are allowed or not. In most likelihood, it should not
60 : * be used with the validator since parameters passed on the command line
61 : * cannot be separated by newlines.
62 : *
63 : * The "yes" and "all" mean the same thing. All the different types of
64 : * comments will be allowed.
65 : *
66 : * The "hash" means the '#' character starts a comment.
67 : *
68 : * The "semicolon" means the ';' character starts a comment.
69 : *
70 : * * `port=yes|no|required`
71 : *
72 : * Whether the port is allowed (`yes`), not allowed (`no`), or must be
73 : * specified (`required`).
74 : *
75 : * * `mask=yes|no|address`
76 : *
77 : * If `yes` then a mask is allowed.
78 : *
79 : * If `no` then no mask is allowed.
80 : *
81 : * If `address` then a mask is allowed and it can be written as an IP
82 : * address (opposed to just a CIDR number).
83 : *
84 : * \todo
85 : * Once support for multi-ports and ranges is available, add such to the
86 : * validator.
87 : */
88 :
89 : // self
90 : //
91 : #include "libaddr/validator_address.h"
92 :
93 :
94 : // cppthread
95 : //
96 : #include <cppthread/log.h>
97 :
98 :
99 : // snapdev
100 : //
101 : #include <snapdev/not_used.h>
102 :
103 :
104 : // C++
105 : //
106 : #include <cassert>
107 :
108 :
109 : // last include
110 : //
111 : #include <snapdev/poison.h>
112 :
113 :
114 :
115 :
116 : namespace addr
117 : {
118 :
119 :
120 :
121 : namespace
122 : {
123 :
124 :
125 :
126 : class validator_address_factory
127 : : public advgetopt::validator_factory
128 : {
129 : public:
130 2 : validator_address_factory()
131 2 : {
132 2 : advgetopt::validator::register_validator(*this);
133 2 : }
134 :
135 4 : virtual std::string get_name() const override
136 : {
137 4 : return std::string("address");
138 : }
139 :
140 3 : virtual std::shared_ptr<advgetopt::validator> create(advgetopt::string_list_t const & data) const override
141 : {
142 3 : return std::make_shared<validator_address>(data);
143 : }
144 : };
145 :
146 : validator_address_factory g_validator_address_factory;
147 :
148 :
149 :
150 : } // no name namespace
151 :
152 :
153 :
154 :
155 :
156 : /** \brief Initialize a validator_address object.
157 : *
158 : * This function parses the specified \p address as a default set of
159 : * parameters to the addr_parser class. Note that the function accepts
160 : * a list but the list is expected to be empty or have exactly one
161 : * parameter.
162 : *
163 : * \param[in] data The address defining defaults.
164 : */
165 3 : validator_address::validator_address(advgetopt::string_list_t const & data)
166 : {
167 11 : for(auto const & p : data)
168 : {
169 : // this should not happen in the regular advgetopt since the empty
170 : // string does not get added to the string_list_t vector; but a
171 : // user could do that
172 : //
173 8 : if(p.empty())
174 : {
175 2 : continue;
176 : }
177 :
178 6 : std::string name;
179 6 : advgetopt::string_list_t values;
180 6 : std::string::size_type const equal(p.find('='));
181 6 : if(equal == std::string::npos)
182 : {
183 1 : name = p;
184 1 : values.push_back("yes");
185 : }
186 : else
187 : {
188 5 : name = p.substr(0, equal);
189 10 : advgetopt::split_string(p.substr(equal + 1), values, {" "});
190 5 : if(values.empty())
191 : {
192 1 : values.push_back("yes");
193 : }
194 : }
195 :
196 6 : assert(!values.empty());
197 :
198 6 : switch(name[0])
199 : {
200 1 : case 'a':
201 1 : if(name == "address")
202 : {
203 : // first reset it all
204 : //
205 1 : f_parser.set_allow(allow_t::ALLOW_ADDRESS, false);
206 1 : f_parser.set_allow(allow_t::ALLOW_REQUIRED_ADDRESS, false);
207 1 : f_parser.set_allow(allow_t::ALLOW_MULTI_ADDRESSES_COMMAS, false);
208 1 : f_parser.set_allow(allow_t::ALLOW_MULTI_ADDRESSES_SPACES, false);
209 1 : f_parser.set_allow(allow_t::ALLOW_MULTI_ADDRESSES_NEWLINES, false);
210 1 : f_parser.set_allow(allow_t::ALLOW_ADDRESS_RANGE, false);
211 1 : f_parser.set_allow(allow_t::ALLOW_ADDRESS_LOOKUP, false);
212 :
213 1 : if(std::find(values.begin(), values.end(), std::string("no")) != values.end())
214 : {
215 0 : if(values.size() != 1)
216 : {
217 : cppthread::log
218 0 : << cppthread::log_level_t::error
219 0 : << "the \"no\" option in the address=... option must be used by itsefl."
220 0 : << cppthread::end;
221 : }
222 : }
223 : else
224 : {
225 1 : f_parser.set_allow(allow_t::ALLOW_ADDRESS, true);
226 6 : for(auto const & v : values)
227 : {
228 5 : switch(v[0])
229 : {
230 1 : case 'c':
231 1 : if(v == "commas")
232 : {
233 1 : f_parser.set_allow(allow_t::ALLOW_MULTI_ADDRESSES_COMMAS, true);
234 1 : continue;
235 : }
236 0 : break;
237 :
238 1 : case 'l':
239 1 : if(v == "lookup")
240 : {
241 1 : f_parser.set_allow(allow_t::ALLOW_ADDRESS_LOOKUP, true);
242 1 : continue;
243 : }
244 0 : break;
245 :
246 1 : case 'n':
247 1 : if(v == "newlines")
248 : {
249 1 : f_parser.set_allow(allow_t::ALLOW_MULTI_ADDRESSES_NEWLINES, true);
250 1 : continue;
251 : }
252 0 : break;
253 :
254 1 : case 'r':
255 1 : if(v == "range")
256 : {
257 1 : f_parser.set_allow(allow_t::ALLOW_ADDRESS_RANGE, true);
258 1 : continue;
259 : }
260 0 : if(v == "required")
261 : {
262 0 : f_parser.set_allow(allow_t::ALLOW_REQUIRED_ADDRESS, true);
263 0 : continue;
264 : }
265 0 : break;
266 :
267 1 : case 's':
268 1 : if(v == "spaces")
269 : {
270 1 : f_parser.set_allow(allow_t::ALLOW_MULTI_ADDRESSES_SPACES, true);
271 1 : continue;
272 : }
273 0 : break;
274 :
275 0 : case 'y':
276 0 : if(v == "yes")
277 : {
278 0 : if(std::find(values.begin(), values.end(), std::string("required")) != values.end())
279 : {
280 : cppthread::log
281 0 : << cppthread::log_level_t::error
282 0 : << "the \"yes\" and \"required\" options are mutually exclusive, use one or the other (or none, which defaults to \"yes\")."
283 0 : << cppthread::end;
284 : }
285 0 : continue;
286 : }
287 0 : break;
288 :
289 : }
290 :
291 : cppthread::log
292 0 : << cppthread::log_level_t::error
293 0 : << "the \""
294 0 : << v
295 0 : << "\" parameter is not understood by the \"address\" options."
296 0 : << cppthread::end;
297 : }
298 : }
299 1 : continue;
300 1 : }
301 0 : break;
302 :
303 2 : case 'c':
304 2 : if(name == "comment")
305 : {
306 1 : f_parser.set_allow(allow_t::ALLOW_COMMENT_HASH, false);
307 1 : f_parser.set_allow(allow_t::ALLOW_COMMENT_SEMICOLON, false);
308 :
309 1 : if(values.size() != 1)
310 : {
311 : cppthread::log
312 0 : << cppthread::log_level_t::error
313 0 : << "the \"comment\" option expects one of \"yes\", \"all\", \"hash\", \"semicolon\" or \"no\"."
314 0 : << cppthread::end;
315 : }
316 1 : else if(values[0] == "yes"
317 1 : || values[0] == "all")
318 : {
319 1 : f_parser.set_allow(allow_t::ALLOW_COMMENT_HASH, true);
320 1 : f_parser.set_allow(allow_t::ALLOW_COMMENT_SEMICOLON, true);
321 : }
322 0 : else if(values[0] == "hash")
323 : {
324 0 : f_parser.set_allow(allow_t::ALLOW_COMMENT_HASH, true);
325 : }
326 0 : else if(values[0] == "semicolon")
327 : {
328 0 : f_parser.set_allow(allow_t::ALLOW_COMMENT_SEMICOLON, true);
329 : }
330 0 : else if(values[0] != "no")
331 : {
332 : cppthread::log
333 0 : << cppthread::log_level_t::error
334 0 : << "value \""
335 0 : << values[0]
336 0 : << "\" is unexpected for the \"comment\" option which expects one of \"yes\", \"all\", \"hash\", \"semicolon\" or \"no\"."
337 0 : << cppthread::end;
338 : }
339 1 : continue;
340 : }
341 1 : break;
342 :
343 1 : case 'd':
344 1 : if(name == "default"
345 1 : || name == "defaults")
346 : {
347 : // `values` cannot be empty, instead it gets set to "yes"
348 : // and in case of the defaults, it means clear the defaults
349 : //
350 1 : if(values.size() == 1
351 1 : && values[0] == "yes")
352 : {
353 : // remove all defaults
354 : //
355 0 : f_parser.set_default_address(std::string());
356 0 : f_parser.set_default_port(std::string());
357 0 : f_parser.set_default_mask(std::string());
358 : }
359 : else
360 : {
361 : // TODO we need to properly distinguish between
362 : // IPv4 and IPv6
363 : //
364 2 : for(auto const & param : values)
365 : {
366 1 : addr_parser parser;
367 1 : parser.set_allow(allow_t::ALLOW_MASK, true);
368 1 : parser.set_protocol(IPPROTO_TCP);
369 1 : addr_range::vector_t v(parser.parse(param));
370 1 : if(v.size() == 1
371 1 : && v[0].has_from()
372 2 : && !v[0].has_to())
373 : {
374 : // TODO: try to find a way to avoid getting multiple
375 : // IP addresses; by default the system gives
376 : // us one IP per Proto (TCP, UDP, IP)
377 : //
378 1 : addr const a(v[0].get_from());
379 :
380 1 : if(a.get_network_type() != network_type_t::NETWORK_TYPE_ANY)
381 : {
382 1 : f_parser.set_default_address(a.to_ipv4or6_string(STRING_IP_BRACKET_ADDRESS));
383 : }
384 :
385 1 : if(a.get_port() != 0)
386 : {
387 1 : f_parser.set_default_port(a.get_str_port());
388 : }
389 :
390 1 : int const mask(a.get_mask_size());
391 1 : if(mask != -1
392 1 : && mask != 128)
393 : {
394 1 : if(a.is_ipv4())
395 : {
396 1 : f_parser.set_default_mask(std::to_string(mask));
397 : }
398 : else
399 : {
400 : // add the '[...]' to make it an IPv6 mask
401 : //
402 : #pragma GCC diagnostic push
403 : #pragma GCC diagnostic ignored "-Wrestrict"
404 0 : f_parser.set_default_mask("[" + std::to_string(mask) + "]");
405 : #pragma GCC diagnostic pop
406 : }
407 : }
408 1 : }
409 : else
410 : {
411 : cppthread::log
412 0 : << cppthread::log_level_t::error
413 0 : << "the default address \""
414 0 : << param
415 0 : << "\" could not be parsed properly. Error: "
416 0 : << parser.error_messages()
417 0 : << cppthread::end;
418 : }
419 1 : }
420 : }
421 1 : continue;
422 1 : }
423 0 : break;
424 :
425 1 : case 'm':
426 1 : if(name == "mask")
427 : {
428 1 : f_parser.set_allow(allow_t::ALLOW_MASK, false);
429 1 : f_parser.set_allow(allow_t::ALLOW_ADDRESS_MASK, false);
430 :
431 1 : if(values.size() != 1)
432 : {
433 : cppthread::log
434 0 : << cppthread::log_level_t::error
435 0 : << "the \"mask\" option expects one of \"yes\", \"no\", or \"address\"."
436 0 : << cppthread::end;
437 : }
438 1 : else if(values[0] != "no")
439 : {
440 1 : f_parser.set_allow(allow_t::ALLOW_MASK, true);
441 :
442 1 : if(values[0] == "address")
443 : {
444 1 : f_parser.set_allow(allow_t::ALLOW_ADDRESS_MASK, true);
445 : }
446 0 : else if(values[0] != "yes")
447 : {
448 : cppthread::log
449 0 : << cppthread::log_level_t::error
450 0 : << "value \""
451 0 : << values[0]
452 0 : << "\" is unexpected for the \"mask\" option which expects one of \"yes\", \"no\", or \"address\"."
453 0 : << cppthread::end;
454 : }
455 : }
456 1 : continue;
457 : }
458 0 : break;
459 :
460 1 : case 'p':
461 1 : if(name == "port")
462 : {
463 1 : f_parser.set_allow(allow_t::ALLOW_PORT, false);
464 1 : f_parser.set_allow(allow_t::ALLOW_REQUIRED_PORT, false);
465 :
466 1 : if(values.size() != 1)
467 : {
468 : cppthread::log
469 0 : << cppthread::log_level_t::error
470 0 : << "the port=... option expects one of \"yes\", \"no\", or \"required\"."
471 0 : << cppthread::end;
472 : }
473 1 : else if(values[0] != "no")
474 : {
475 1 : f_parser.set_allow(allow_t::ALLOW_PORT, true);
476 :
477 1 : if(values[0] != "yes")
478 : {
479 0 : if(values[0] == "required")
480 : {
481 0 : f_parser.set_allow(allow_t::ALLOW_REQUIRED_PORT, true);
482 : }
483 : else
484 : {
485 : cppthread::log
486 0 : << cppthread::log_level_t::error
487 0 : << "unexpected value \""
488 0 : << values[0]
489 0 : << "\" for the port=... option which expects one of \"yes\", \"no\", or \"required\"."
490 0 : << cppthread::end;
491 : }
492 : }
493 : }
494 1 : continue;
495 : }
496 0 : break;
497 :
498 : }
499 :
500 : cppthread::log
501 2 : << cppthread::log_level_t::error
502 1 : << "\""
503 1 : << p
504 1 : << "\" is not a known option for the address validator."
505 2 : << cppthread::end;
506 11 : }
507 3 : }
508 :
509 :
510 : /** \brief Return the name of this validator.
511 : *
512 : * This function returns "address".
513 : *
514 : * \return "address".
515 : */
516 3 : std::string validator_address::name() const
517 : {
518 3 : return std::string("address");
519 : }
520 :
521 :
522 : /** \brief Check the value against a regular expression.
523 : *
524 : * This function is used to match the value of an argument against a
525 : * regular expression. It returns true when it does match.
526 : *
527 : * \param[in] value The value to be validated.
528 : *
529 : * \return true on a match.
530 : */
531 13 : bool validator_address::validate(std::string const & value) const
532 : {
533 13 : f_parser.clear_errors();
534 13 : snapdev::NOT_USED(f_parser.parse(value));
535 :
536 : //if(f_parser.has_errors())
537 : //{
538 : //std::cerr << "--- parser errors: [" << f_parser.error_messages() << "]\n";
539 : //}
540 :
541 13 : return !f_parser.has_errors();
542 : }
543 :
544 :
545 :
546 : } // namespace advgetopt
547 : // vim: ts=4 sw=4 et
|