From 1d9c703a8eebcf8f328e610879c7a1799258bba3 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" <AWilcox@Wilcox-Tech.com> Date: Sat, 4 Jul 2020 08:56:03 -0500 Subject: hscipt: Implement parse and validation of 'pppoe' key --- hscript/network.cc | 114 +++++++++++++++++++++++- hscript/network.hh | 25 ++++++ hscript/script.cc | 3 + hscript/script_e.cc | 9 +- hscript/script_i.hh | 17 ++++ hscript/script_v.cc | 4 + tests/fixtures/0241-pppoe-basic.installfile | 6 ++ tests/fixtures/0242-pppoe-auth.installfile | 6 ++ tests/fixtures/0243-pppoe-allkeys.installfile | 6 ++ tests/fixtures/0244-pppoe-invalid.installfile | 6 ++ tests/fixtures/0245-pppoe-valueless.installfile | 6 ++ tests/spec/validator_spec.rb | 35 +++++++- 12 files changed, 230 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/0241-pppoe-basic.installfile create mode 100644 tests/fixtures/0242-pppoe-auth.installfile create mode 100644 tests/fixtures/0243-pppoe-allkeys.installfile create mode 100644 tests/fixtures/0244-pppoe-invalid.installfile create mode 100644 tests/fixtures/0245-pppoe-valueless.installfile diff --git a/hscript/network.cc b/hscript/network.cc index 1e915b3..3d847a2 100644 --- a/hscript/network.cc +++ b/hscript/network.cc @@ -14,6 +14,7 @@ #include <arpa/inet.h> /* inet_pton */ #include <cstring> /* memcpy */ #include <fstream> /* ofstream for Net write */ +#include <set> /* for PPPoE valid param keys */ #ifdef HAS_INSTALL_ENV # include <linux/wireless.h> /* struct iwreq */ # include <string.h> /* strerror */ @@ -352,9 +353,7 @@ bool execute_address_eni(const NetAddress *addr) { return true; } -bool NetAddress::execute() const { - output_info(pos, "netaddress: adding configuration for " + _iface); - +NetConfigType::ConfigSystem current_system(const Horizon::Script *script) { NetConfigType::ConfigSystem system = NetConfigType::Netifrc; const Key *key = script->getOneValue("netconfigtype"); @@ -363,7 +362,13 @@ bool NetAddress::execute() const { system = nct->type(); } - switch(system) { + return system; +} + +bool NetAddress::execute() const { + output_info(pos, "netaddress: adding configuration for " + _iface); + + switch(current_system(script)) { case NetConfigType::Netifrc: default: return execute_address_netifrc(this); @@ -373,6 +378,107 @@ bool NetAddress::execute() const { } +Key *PPPoE::parseFromData(const std::string &data, const ScriptLocation &pos, + int *errors, int *, const Script *script) { + std::string::size_type spos, next; + std::map<std::string, std::string> params; + std::string iface; + + spos = data.find_first_of(' '); + iface = data.substr(0, spos); + if(iface.length() > IFNAMSIZ) { + if(errors) *errors += 1; + output_error(pos, "pppoe: invalid interface name '" + iface + "'", + "interface names must be 16 characters or less"); + return nullptr; + } + + while(spos != std::string::npos) { + std::string key, val; + std::string::size_type equals; + + spos++; + + next = data.find_first_of(' ', spos); + equals = data.find_first_of('=', spos); + if(equals != std::string::npos && equals < next) { + key = data.substr(spos, equals - spos); + if(next == std::string::npos) { + val = data.substr(equals + 1); + } else { + val = data.substr(equals + 1, next - equals); + } + } else { + if(next == std::string::npos) { + key = data.substr(spos); + } else { + key = data.substr(spos, next - spos); + } + } + + params[key] = val; + + spos = next; + } + + return new PPPoE(script, pos, iface, params); +} + +bool PPPoE::validate() const { + bool valid = true; + const std::set<std::string> valid_keys = {"mtu", "username", "password", + "lcp-echo-interval", + "lcp-echo-failure"}; + + for(const auto &elem : this->params()) { + if(valid_keys.find(elem.first) == valid_keys.end()) { + output_error(this->pos, "pppoe: invalid parameter", elem.first); + valid = false; + } + } + + return valid; +} + +static int ppp_link_count = 0; + +bool execute_pppoe_netifrc(const PPPoE *link) { + std::ofstream ethconfig("/tmp/horizon/netifrc/config_" + link->iface(), + std::ios_base::trunc); + if(!ethconfig) { + output_error(link->where(), "pppoe: couldn't write network " + "configuration for " + link->iface()); + return false; + } + + ethconfig << "null"; + + std::string linkiface{"ppp" + std::to_string(ppp_link_count)}; + + std::ofstream config("/tmp/horizon/netifrc/config_" + linkiface); + if(!config) { + output_error(link->where(), "pppoe: couldn't write network " + "configuration for " + linkiface); + return false; + } + config << "ppp"; + return true; +} + +bool PPPoE::execute() const { + output_info(pos, "pppoe: adding configuration for " + _iface); + + switch(current_system(script)) { + case NetConfigType::Netifrc: + default: + return execute_pppoe_netifrc(this); + case NetConfigType::ENI: + /* eni */ + return false; + } +} + + Key *Nameserver::parseFromData(const std::string &data, const ScriptLocation &pos, int *errors, int *, const Script *script) { diff --git a/hscript/network.hh b/hscript/network.hh index 18ffc34..2ed40ca 100644 --- a/hscript/network.hh +++ b/hscript/network.hh @@ -13,6 +13,8 @@ #ifndef __HSCRIPT_NETWORK_HH_ #define __HSCRIPT_NETWORK_HH_ +#include <map> + #include "key.hh" #include "util/output.hh" @@ -93,6 +95,29 @@ public: bool execute() const override; }; +class PPPoE : public Key { +private: + const std::string _iface; + const std::map<std::string, std::string> _params; + + PPPoE(const Script *_sc, const ScriptLocation &_pos, const std::string &_i, + const std::map<std::string, std::string> &_p) : Key(_sc, _pos), + _iface(_i), _params(_p) {} +public: + static Key *parseFromData(const std::string &, const ScriptLocation &, + int*, int*, const Script *); + + /*! Retrieve the interface to which this PPPoE link is associated. */ + const std::string iface() const { return this->_iface; } + /*! Retrieve the parameters for this PPPoE link. */ + const std::map<std::string, std::string> params() const { + return this->_params; + } + + bool validate() const override; + bool execute() const override; +}; + class Nameserver : public StringKey { private: Nameserver(const Script *_s, const ScriptLocation &_pos, diff --git a/hscript/script.cc b/hscript/script.cc index 897d5d4..20ccbe5 100644 --- a/hscript/script.cc +++ b/hscript/script.cc @@ -58,6 +58,7 @@ const std::map<std::string, key_parse_fn> valid_keys = { {"netaddress", &NetAddress::parseFromData}, {"nameserver", &Nameserver::parseFromData}, {"netssid", &NetSSID::parseFromData}, + {"pppoe", &PPPoE::parseFromData}, {"username", &Username::parseFromData}, {"useralias", &UserAlias::parseFromData}, @@ -98,6 +99,8 @@ bool Script::ScriptPrivate::store_key(const std::string &key_name, Key *obj, std::unique_ptr<NetSSID> ssid(dynamic_cast<NetSSID *>(obj)); this->ssids.push_back(std::move(ssid)); return true; + } else if(key_name == "pppoe") { + return store_pppoe(obj, pos, errors, warnings, opts); } else if(key_name == "hostname") { return store_hostname(obj, pos, errors, warnings, opts); } else if(key_name == "pkginstall") { diff --git a/hscript/script_e.cc b/hscript/script_e.cc index 866a836..87ade0f 100644 --- a/hscript/script_e.cc +++ b/hscript/script_e.cc @@ -287,7 +287,7 @@ bool Script::execute() const { } bool dhcp = false; - if(!internal->addresses.empty()) { + if(!internal->addresses.empty() || !internal->pppoes.empty()) { fs::path conf_dir, targ_netconf_dir, targ_netconf_file; switch(netconfsys) { case NetConfigType::Netifrc: @@ -319,6 +319,13 @@ bool Script::execute() const { if(addr->type() == NetAddress::DHCP) dhcp = true; } + int pppcnt = 0; + for(auto &ppp_link : internal->pppoes) { + EXECUTE_OR_FAIL("pppoe", ppp_link); + ifaces.insert(ppp_link->iface()); + ifaces.insert("ppp" + std::to_string(pppcnt++)); + } + std::ostringstream conf; if(netconfsys == NetConfigType::ENI) { diff --git a/hscript/script_i.hh b/hscript/script_i.hh index abe56ec..b82b807 100644 --- a/hscript/script_i.hh +++ b/hscript/script_i.hh @@ -72,6 +72,8 @@ struct Script::ScriptPrivate { std::vector< std::unique_ptr<Nameserver> > nses; /*! Wireless networking configuration */ std::vector< std::unique_ptr<NetSSID> > ssids; + /*! PPPoE configuration */ + std::vector< std::unique_ptr<PPPoE> > pppoes; /*! APK repositories */ std::vector< std::unique_ptr<Repository> > repos; @@ -153,6 +155,21 @@ struct Script::ScriptPrivate { return true; } + bool store_pppoe(Key *obj, const ScriptLocation &pos, int *errors, + int *, const ScriptOptions &) { + PPPoE *ppp = dynamic_cast<PPPoE *>(obj); + for(const auto &ppplink : pppoes) { + if(ppplink->iface() == ppp->iface()) { + DUPLICATE_ERROR(ppplink, "pppoe", ppplink->iface()); + return false; + } + } + + std::unique_ptr<PPPoE> uppp(ppp); + pppoes.push_back(std::move(uppp)); + return true; + } + bool store_hostname(Key* obj, const ScriptLocation &pos, int *errors, int *, const ScriptOptions &) { if(hostname) { diff --git a/hscript/script_v.cc b/hscript/script_v.cc index 1fc3726..41f1756 100644 --- a/hscript/script_v.cc +++ b/hscript/script_v.cc @@ -252,6 +252,10 @@ bool Horizon::Script::validate() const { if(!ssid->validate()) failures++; } + for(auto &pppoe : internal->pppoes) { + if(!pppoe->validate()) failures++; + } + /* REQ: Runner.Validate.hostname */ if(!internal->hostname->validate()) failures++; diff --git a/tests/fixtures/0241-pppoe-basic.installfile b/tests/fixtures/0241-pppoe-basic.installfile new file mode 100644 index 0000000..9e7d4e5 --- /dev/null +++ b/tests/fixtures/0241-pppoe-basic.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +pppoe eth0 diff --git a/tests/fixtures/0242-pppoe-auth.installfile b/tests/fixtures/0242-pppoe-auth.installfile new file mode 100644 index 0000000..250e820 --- /dev/null +++ b/tests/fixtures/0242-pppoe-auth.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +pppoe eth0 username=awilfox password=fuzzball diff --git a/tests/fixtures/0243-pppoe-allkeys.installfile b/tests/fixtures/0243-pppoe-allkeys.installfile new file mode 100644 index 0000000..6b70daa --- /dev/null +++ b/tests/fixtures/0243-pppoe-allkeys.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +pppoe eth0 username=awilfox password=fuzzball mtu=9001 lcp-echo-interval=10 lcp-echo-failure=5 diff --git a/tests/fixtures/0244-pppoe-invalid.installfile b/tests/fixtures/0244-pppoe-invalid.installfile new file mode 100644 index 0000000..8fc2768 --- /dev/null +++ b/tests/fixtures/0244-pppoe-invalid.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +pppoe eth0 cat=meow diff --git a/tests/fixtures/0245-pppoe-valueless.installfile b/tests/fixtures/0245-pppoe-valueless.installfile new file mode 100644 index 0000000..ee75450 --- /dev/null +++ b/tests/fixtures/0245-pppoe-valueless.installfile @@ -0,0 +1,6 @@ +network false +hostname test.machine +pkginstall adelie-base +rootpw $6$gumtLGmHwOVIRpQR$2M9PUO24hy5mofzWWf9a.YLbzOgOlUby1g0hDj.wG67E2wrrvys59fq02PPdxBdbgkLZFtjfEx6MHZwMBamwu/ +mount /dev/sda1 / +pppoe eth0 username password=fuzzball diff --git a/tests/spec/validator_spec.rb b/tests/spec/validator_spec.rb index 8fb6c95..d2d121a 100644 --- a/tests/spec/validator_spec.rb +++ b/tests/spec/validator_spec.rb @@ -278,6 +278,37 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do expect(last_command_started).to have_output(/error: .*nameserver.*valid IPv6/) end end + context "for 'pppoe' key" do + it "succeeds with only an interface" do + use_fixture '0241-pppoe-basic.installfile' + run_validate + expect(last_command_started).to have_output(PARSER_SUCCESS) + expect(last_command_started).to have_output(VALIDATOR_SUCCESS) + end + it "succeeds with autnentication credentials" do + use_fixture '0242-pppoe-auth.installfile' + run_validate + expect(last_command_started).to have_output(PARSER_SUCCESS) + expect(last_command_started).to have_output(VALIDATOR_SUCCESS) + end + it "succeeds with all valid keys" do + use_fixture '0243-pppoe-allkeys.installfile' + run_validate + expect(last_command_started).to have_output(PARSER_SUCCESS) + expect(last_command_started).to have_output(VALIDATOR_SUCCESS) + end + it "fails with an invalid key" do + use_fixture '0244-pppoe-invalid.installfile' + run_validate + expect(last_command_started).to have_output(/error: .*pppoe.*invalid/) + end + it "succeeds with a value-less key" do + use_fixture '0245-pppoe-valueless.installfile' + run_validate + expect(last_command_started).to have_output(PARSER_SUCCESS) + expect(last_command_started).to have_output(VALIDATOR_SUCCESS) + end + end context "for 'firmware' key" do it "always supports 'false' value" do use_fixture '0112-firmware-false.installfile' @@ -687,12 +718,12 @@ RSpec.describe 'HorizonScript validation', :type => :aruba do run_validate expect(last_command_started).to have_output(/error: .*svcenable.*invalid/) end - it "succeeds with a runlevel specified" do + it "succeeds with a runlevel specified" do use_fixture '0239-svcenable-runlevel.installfile' run_validate expect(last_command_started).to have_output(PARSER_SUCCESS) expect(last_command_started).to have_output(VALIDATOR_SUCCESS) - end + end end context "for 'diskid' key" do it "succeeds with basic disk identification" do -- cgit v1.2.3-70-g09d2