diff --git a/ChangeLog b/ChangeLog index 593818605..77f7d46c5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * promote errors in parsing listen_interfaces to post listen_failed_alert * fix bug in protocol encryption/obfuscation * fix buffer overflow in SOCKS5 UDP logic * fix issue of rapid calls to file_priority() clobbering each other diff --git a/include/libtorrent/string_util.hpp b/include/libtorrent/string_util.hpp index 0a9f69158..ed3b0859e 100644 --- a/include/libtorrent/string_util.hpp +++ b/include/libtorrent/string_util.hpp @@ -36,6 +36,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/config.hpp" #include "libtorrent/string_view.hpp" #include "libtorrent/span.hpp" +#include "libtorrent/error_code.hpp" #include #include @@ -60,6 +61,9 @@ namespace libtorrent { url += '/'; } + // internal + TORRENT_EXTRA_EXPORT string_view strip_string(string_view in); + TORRENT_EXTRA_EXPORT bool is_print(char c); TORRENT_EXTRA_EXPORT bool is_space(char c); TORRENT_EXTRA_EXPORT char to_lower(char c); @@ -80,16 +84,25 @@ namespace libtorrent { std::string device; int port; bool ssl; + friend bool operator==(listen_interface_t const& lhs, listen_interface_t const& rhs) + { + return lhs.device == rhs.device + && lhs.port == rhs.port + && lhs.ssl == rhs.ssl; + } }; // this parses the string that's used as the listen_interfaces setting. // it is a comma-separated list of IP or device names with ports. For // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" TORRENT_EXTRA_EXPORT std::vector parse_listen_interfaces( - std::string const& in); + std::string const& in, std::vector& errors); +#if TORRENT_ABI_VERSION == 1 \ + || !defined TORRENT_DISABLE_LOGGING TORRENT_EXTRA_EXPORT std::string print_listen_interfaces( std::vector const& in); +#endif // this parses the string that's used as the listen_interfaces setting. // it is a comma-separated list of IP or device names with ports. For diff --git a/src/session_impl.cpp b/src/session_impl.cpp index a7f423244..3746e13f7 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -5192,8 +5192,9 @@ namespace aux { // this function maps the previous functionality of just setting the ssl // listen port in order to enable the ssl listen sockets, to the new // mechanism where SSL sockets are specified in listen_interfaces. + std::vector ignore; auto current_ifaces = parse_listen_interfaces( - m_settings.get_str(settings_pack::listen_interfaces)); + m_settings.get_str(settings_pack::listen_interfaces), ignore); // these are the current interfaces we have, first remove all the SSL // interfaces current_ifaces.erase(std::remove_if(current_ifaces.begin(), current_ifaces.end() @@ -5227,16 +5228,18 @@ namespace aux { INVARIANT_CHECK; std::string const net_interfaces = m_settings.get_str(settings_pack::listen_interfaces); - m_listen_interfaces = parse_listen_interfaces(net_interfaces); + std::vector err; + m_listen_interfaces = parse_listen_interfaces(net_interfaces, err); + + for (auto const& e : err) + { + m_alerts.emplace_alert(e, lt::address{}, 0 + , operation_t::parse_address, errors::invalid_port, lt::socket_type_t::tcp); + } #ifndef TORRENT_DISABLE_LOGGING if (should_log()) { - if (!net_interfaces.empty() && m_listen_interfaces.empty()) - { - session_log("ERROR: failed to parse listen_interfaces setting: %s" - , net_interfaces.c_str()); - } session_log("update listen interfaces: %s", net_interfaces.c_str()); session_log("parsed listen interfaces count: %d, ifaces: %s" , int(m_listen_interfaces.size()) diff --git a/src/string_util.cpp b/src/string_util.cpp index 25939e97c..f2b750940 100644 --- a/src/string_util.cpp +++ b/src/string_util.cpp @@ -149,6 +149,8 @@ namespace libtorrent { return tmp; } +#if TORRENT_ABI_VERSION == 1 \ + || !defined TORRENT_DISABLE_LOGGING std::string print_listen_interfaces(std::vector const& in) { std::string ret; @@ -176,92 +178,101 @@ namespace libtorrent { return ret; } +#endif + + string_view strip_string(string_view in) + { + while (!in.empty() && is_space(in.front())) + in.remove_prefix(1); + + while (!in.empty() && is_space(in.back())) + in.remove_suffix(1); + return in; + } // this parses the string that's used as the listen_interfaces setting. // it is a comma-separated list of IP or device names with ports. For // example: "eth0:6881,eth1:6881" or "127.0.0.1:6881" - std::vector parse_listen_interfaces(std::string const& in) + std::vector parse_listen_interfaces(std::string const& in + , std::vector& err) { std::vector out; - std::string::size_type start = 0; - - while (start < in.size()) + string_view rest = in; + while (!rest.empty()) { - // skip leading spaces - while (start < in.size() && is_space(in[start])) - ++start; + string_view element; + std::tie(element, rest) = split_string(rest, ','); - if (start == in.size()) return out; + element = strip_string(element); + if (element.size() > 1 && element.front() == '"' && element.back() == '"') + element = element.substr(1, element.size() - 2); + if (element.empty()) continue; listen_interface_t iface; iface.ssl = false; - if (in[start] == '[') + string_view port; + if (element.front() == '[') { - ++start; - // IPv6 address - while (start < in.size() && in[start] != ']') - iface.device += in[start++]; + auto const pos = find_first_of(element, ']', 0); + if (pos == string_view::npos + || pos+1 >= element.size() + || element[pos+1] != ':') + { + err.emplace_back(element); + continue; + } - // skip to the colon - while (start < in.size() && in[start] != ':') - ++start; + iface.device = strip_string(element.substr(1, pos - 1)).to_string(); + + port = strip_string(element.substr(pos + 2)); } else { // consume device name - while (start < in.size() && !is_space(in[start]) && in[start] != ':') - iface.device += in[start++]; + auto const pos = find_first_of(element, ':', 0); + iface.device = strip_string(element.substr(0, pos)).to_string(); + if (pos == string_view::npos) + { + err.emplace_back(element); + continue; + } + port = strip_string(element.substr(pos + 1)); } - // skip spaces - while (start < in.size() && is_space(in[start])) - ++start; - - if (start == in.size() || in[start] != ':') return out; - ++start; // skip colon - - // skip spaces - while (start < in.size() && is_space(in[start])) - ++start; - // consume port - std::string port; - while (start < in.size() && is_digit(in[start]) && in[start] != ',') - port += in[start++]; + std::string port_str; + for (std::size_t i = 0; i < port.size() && is_digit(port[i]); ++i) + port_str += port[i]; - if (port.empty() || port.size() > 5) + if (port_str.empty() || port_str.size() > 5) { - iface.port = -1; - } - else - { - iface.port = std::atoi(port.c_str()); - if (iface.port < 0 || iface.port > 65535) iface.port = -1; + err.emplace_back(element); + continue; } - // skip spaces - while (start < in.size() && is_space(in[start])) - ++start; + iface.port = std::atoi(port_str.c_str()); + if (iface.port < 0 || iface.port > 65535) + { + err.emplace_back(element); + continue; + } + + port.remove_prefix(port_str.size()); + port = strip_string(port); // consume potential SSL 's' - if (start < in.size() && in[start] == 's') + for (auto const c : port) { - iface.ssl = true; - ++start; + switch (c) + { + case 's': iface.ssl = true; break; + } } - // skip until end or comma - while (start < in.size() && in[start] != ',') - ++start; - - if (iface.port >= 0) out.push_back(iface); - - // skip the comma - if (start < in.size() && in[start] == ',') - ++start; - + TORRENT_ASSERT(iface.port >= 0); + out.emplace_back(iface); } return out; diff --git a/test/test_string.cpp b/test/test_string.cpp index f1ea06cdb..ab7a936b1 100644 --- a/test/test_string.cpp +++ b/test/test_string.cpp @@ -346,19 +346,23 @@ TORRENT_TEST(path) namespace { void test_parse_interface(char const* input - , std::vector expected - , std::string output) + , std::vector const expected + , std::vector const expected_e + , string_view const output) { std::printf("parse interface: %s\n", input); - auto const list = parse_listen_interfaces(input); + std::vector err; + auto const list = parse_listen_interfaces(input, err); TEST_EQUAL(list.size(), expected.size()); - if (list.size() == expected.size()) - { - TEST_CHECK(std::equal(list.begin(), list.end(), expected.begin() - , [&](listen_interface_t const& lhs, listen_interface_t const& rhs) - { return lhs.device == rhs.device && lhs.port == rhs.port && lhs.ssl == rhs.ssl; })); - } + TEST_CHECK(list == expected); + TEST_CHECK(err == expected_e); +#if TORRENT_ABI_VERSION == 1 \ + || !defined TORRENT_DISABLE_LOGGING TEST_EQUAL(print_listen_interfaces(list), output); + std::cout << "RESULT: " << print_listen_interfaces(list) << '\n'; +#endif + for (auto const& e : err) + std::cout << "ERR: \"" << e << "\"\n"; } } // anonymous namespace @@ -367,46 +371,68 @@ TORRENT_TEST(parse_list) { std::vector list; parse_comma_separated_string(" a,b, c, d ,e \t,foobar\n\r,[::1]", list); - TEST_EQUAL(list.size(), 7); - TEST_EQUAL(list[0], "a"); - TEST_EQUAL(list[1], "b"); - TEST_EQUAL(list[2], "c"); - TEST_EQUAL(list[3], "d"); - TEST_EQUAL(list[4], "e"); - TEST_EQUAL(list[5], "foobar"); - TEST_EQUAL(list[6], "[::1]"); + TEST_CHECK((list == std::vector{"a", "b", "c", "d", "e", "foobar", "[::1]"})); +} +TORRENT_TEST(parse_interface) +{ test_parse_interface(" a:4,b:35, c : 1000s, d: 351 ,e \t:42,foobar:1337s\n\r,[2001::1]:6881" , {{"a", 4, false}, {"b", 35, false}, {"c", 1000, true}, {"d", 351, false} , {"e", 42, false}, {"foobar", 1337, true}, {"2001::1", 6881, false}} + , {} , "a:4,b:35,c:1000s,d:351,e:42,foobar:1337s,[2001::1]:6881"); // IPv6 address test_parse_interface("[2001:ffff::1]:6882s" , {{"2001:ffff::1", 6882, true}} + , {} , "[2001:ffff::1]:6882s"); // IPv4 address test_parse_interface("127.0.0.1:6882" , {{"127.0.0.1", 6882, false}} + , {} , "127.0.0.1:6882"); // maximum padding test_parse_interface(" nic\r\n:\t 12\r s " , {{"nic", 12, true}} + , {} , "nic:12s"); // negative tests - test_parse_interface("nic:99999999999999999999999", {}, ""); - test_parse_interface("nic: -3", {}, ""); - test_parse_interface("nic: ", {}, ""); - test_parse_interface("nic :", {}, ""); - test_parse_interface("nic ", {}, ""); - test_parse_interface("nic s", {}, ""); + test_parse_interface("nic:99999999999999999999999", {}, {"nic:99999999999999999999999"}, ""); + test_parse_interface("nic: -3", {}, {"nic: -3"}, ""); + test_parse_interface("nic: ", {}, {"nic:"}, ""); + test_parse_interface("nic :", {}, {"nic :"}, ""); + test_parse_interface("nic ", {}, {"nic"}, ""); + test_parse_interface("nic s", {}, {"nic s"}, ""); // parse interface with port 0 - test_parse_interface("127.0.0.1:0" - , {{"127.0.0.1", 0, false}}, "127.0.0.1:0"); + test_parse_interface("127.0.0.1:0", {{"127.0.0.1", 0, false}}, {}, "127.0.0.1:0"); + + // IPv6 error + test_parse_interface("[aaaa::1", {}, {"[aaaa::1"}, ""); + test_parse_interface("[aaaa::1]", {}, {"[aaaa::1]"}, ""); + test_parse_interface("[aaaa::1]:", {}, {"[aaaa::1]:"}, ""); + test_parse_interface("[aaaa::1]:s", {}, {"[aaaa::1]:s"}, ""); + test_parse_interface("[aaaa::1] :6881", {}, {"[aaaa::1] :6881"}, ""); + test_parse_interface("[aaaa::1]:6881", {{"aaaa::1", 6881, false}}, {}, "[aaaa::1]:6881"); + + // unterminated [ + test_parse_interface("[aaaa::1,foobar:0", {{"foobar", 0, false}}, {"[aaaa::1"}, "foobar:0"); + + // multiple errors + test_parse_interface("foo:,bar", {}, {"foo:", "bar"}, ""); + + // quoted elements + test_parse_interface("\"abc,.\",bar", {}, {"abc,.", "bar"}, ""); + + // silent error + test_parse_interface("\"", {}, {"\""}, ""); + + // multiple errors and one correct + test_parse_interface("foo,bar,0.0.0.0:6881", {{"0.0.0.0", 6881, false}}, {"foo", "bar"}, "0.0.0.0:6881"); } TORRENT_TEST(split_string) @@ -508,3 +534,15 @@ TORRENT_TEST(find_first_of) TEST_EQUAL(find_first_of(test, "61", 3), 6); TEST_EQUAL(find_first_of(test, "61", 4), 6); } + +TORRENT_TEST(strip_string) +{ + TEST_EQUAL(strip_string(" ab"), "ab"); + TEST_EQUAL(strip_string(" ab "), "ab"); + TEST_EQUAL(strip_string(" "), ""); + TEST_EQUAL(strip_string(""), ""); + TEST_EQUAL(strip_string("a b"), "a b"); + TEST_EQUAL(strip_string(" a b "), "a b"); + TEST_EQUAL(strip_string(" \t \t ab\t\t\t"), "ab"); +} +