From a94066d62a33e25ddb1eafce78627f2a3361f4ca Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Sat, 12 Oct 2019 06:43:55 -0500 Subject: hscript: Parse network addresses --- hscript/network.cc | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 116 insertions(+), 3 deletions(-) (limited to 'hscript') diff --git a/hscript/network.cc b/hscript/network.cc index 9bcd9a8..f14381f 100644 --- a/hscript/network.cc +++ b/hscript/network.cc @@ -11,6 +11,7 @@ */ #include +#include #include "network.hh" #include "util/output.hh" @@ -34,8 +35,10 @@ bool Network::execute(ScriptOptions) const { Key *NetAddress::parseFromData(const std::string data, int lineno, int *errors, int *warnings) { long elements = std::count(data.cbegin(), data.cend(), ' ') + 1; - std::string::size_type type_pos, addr_pos, prefix_pos, gw_pos; + std::string::size_type type_pos, addr_pos, prefix_pos, gw_pos, next_end; std::string iface, type, addr, prefix, gw; + int real_prefix; + char addr_buf[16]; if(elements < 2) { if(errors) *errors += 1; @@ -49,7 +52,9 @@ Key *NetAddress::parseFromData(const std::string data, int lineno, int *errors, iface = data.substr(0, type_pos); /* theory: addr_pos could be npos, but that means 'read to end' anyway */ addr_pos = data.find_first_of(' ', type_pos + 1); - type = data.substr(type_pos + 1, (addr_pos - type_pos - 1)); + if(addr_pos == std::string::npos) next_end = std::string::npos; + else next_end = addr_pos - type_pos - 1; + type = data.substr(type_pos + 1, next_end); /* ensure type is lower-cased, in case someone types 'DHCP' or 'SLAAC' */ std::transform(type.begin(), type.end(), type.begin(), ::tolower); @@ -96,7 +101,115 @@ Key *NetAddress::parseFromData(const std::string data, int lineno, int *errors, return nullptr; } - return nullptr; + prefix_pos = data.find_first_of(' ', addr_pos + 1); + addr = data.substr(addr_pos + 1, (prefix_pos - addr_pos - 1)); + gw_pos = data.find_first_of(' ', prefix_pos + 1); + if(gw_pos == std::string::npos) next_end = std::string::npos; + else next_end = gw_pos - prefix_pos - 1; + prefix = data.substr(prefix_pos + 1, next_end); + if(gw_pos != std::string::npos) { + gw = data.substr(gw_pos + 1); + } + + if(addr.find(':') != std::string::npos) { + /* IPv6 */ + if(::inet_pton(AF_INET6, addr.c_str(), &addr_buf) != 1) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: '" + addr + "' is not a valid IPv6 address", + "hint: a ':' was found, indicating this address is IPv6"); + return nullptr; + } + + try { + real_prefix = std::stoi(prefix); + } catch(const std::exception &) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: prefix length is not a number", + "prefix must be a decimal value between 1 and 128"); + return nullptr; + } + + if(real_prefix < 1 || real_prefix > 128) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: invalid IPv6 prefix length: " + prefix, + "prefix must be a decimal value between 1 and 128"); + return nullptr; + } + + if(gw.size() > 0 && + ::inet_pton(AF_INET6, gw.c_str(), &addr_buf) != 1) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: '" + gw + + "' is not a valid IPv6 gateway", + "an IPv6 address must have an IPv6 gateway"); + return nullptr; + } + + return new NetAddress(lineno, iface, AddressType::Static, addr, + static_cast(real_prefix), gw); + } else if(addr.find('.') != std::string::npos) { + /* IPv4 */ + if(::inet_pton(AF_INET, addr.c_str(), &addr_buf) != 1) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: '" + addr + "' is not a valid IPv4 address"); + return nullptr; + } + + /* There are two kinds of prefixes for IPv4: prefix length, like IPv6, + * and mask, which is the old style used by i.e. Windows. */ + if(::inet_pton(AF_INET, prefix.c_str(), &addr_buf) != 1) { + try { + real_prefix = std::stoi(prefix); + } catch(const std::exception &) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: can't parse prefix length/mask", + "a network mask or prefix length is required"); + return nullptr; + } + } else { + uint32_t tmp; + memcpy(&tmp, addr_buf, 4); + tmp = ntohl(tmp); + real_prefix = 1; + while((tmp <<= 1) & 0x80000000) { + real_prefix++; + } + } + + if(real_prefix < 1 || real_prefix > 32) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: invalid IPv4 prefix length: " + prefix, + "prefix must be between 1 and 32"); + return nullptr; + } + + if(gw.size() > 0 && + ::inet_pton(AF_INET, gw.c_str(), &addr_buf) != 1) { + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: '" + gw + + "' is not a valid IPv4 gateway", + "an IPv4 address must have an IPv4 gateway"); + return nullptr; + } + + return new NetAddress(lineno, iface, AddressType::Static, addr, + static_cast(real_prefix), gw); + } else { + /* IPvBad */ + if(errors) *errors += 1; + output_error("installfile:" + std::to_string(lineno), + "netaddress: invalid address of unknown type", + "an IPv4 or IPv6 address is required"); + return nullptr; + } } bool NetAddress::validate(ScriptOptions) const { -- cgit v1.2.3-60-g2f50