LCOV - code coverage report
Current view: top level - libaddr - validator_address.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 129 200 64.5 %
Date: 2024-06-02 17:59:01 Functions: 6 6 100.0 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.14

Snap C++ | List of projects | List of versions