From 9a3d6880e844231404a4cfafe4e0349798eaacad Mon Sep 17 00:00:00 2001 From: Vincent Richard Date: Fri, 10 Feb 2017 21:20:22 +0100 Subject: [PATCH] Fixed issue #160: invalid characters in hostname. --- src/vmime/platforms/posix/posixHandler.cpp | 66 ++++++++++------ .../platforms/windows/windowsHandler.cpp | 66 ++++++---------- src/vmime/utility/stringUtils.cpp | 77 +++++++++++++++++++ src/vmime/utility/stringUtils.hpp | 21 +++++ tests/utility/stringUtilsTest.cpp | 28 +++++++ 5 files changed, 191 insertions(+), 67 deletions(-) diff --git a/src/vmime/platforms/posix/posixHandler.cpp b/src/vmime/platforms/posix/posixHandler.cpp index c0ba6cca..a73d42d7 100644 --- a/src/vmime/platforms/posix/posixHandler.cpp +++ b/src/vmime/platforms/posix/posixHandler.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -174,32 +175,23 @@ const vmime::charset posixHandler::getLocalCharset() const } -static inline bool isFQDN(const vmime::string& str) +static inline bool isAcceptableHostname(const vmime::string& str) { - if (utility::stringUtils::isStringEqualNoCase(str, "localhost", 9)) + // At least, try to find something better than "localhost" + if (utility::stringUtils::isStringEqualNoCase(str, "localhost", 9) || + utility::stringUtils::isStringEqualNoCase(str, "localhost.localdomain", 21)) + { return false; + } - const vmime::size_t p = str.find_first_of("."); - return p != vmime::string::npos && p > 0 && p != str.length() - 1; + // Anything else will be OK, as long as it is a valid hostname + return utility::stringUtils::isValidHostname(str); } const vmime::string posixHandler::getHostName() const { - char hostname[256]; - - // Try with 'gethostname' - ::gethostname(hostname, sizeof(hostname)); - hostname[sizeof(hostname) - 1] = '\0'; - - // If this is a Fully-Qualified Domain Name (FQDN), return immediately - if (isFQDN(hostname)) - return hostname; - - if (::strlen(hostname) == 0) - ::strcpy(hostname, "localhost"); - - // Try to get canonical name for the hostname + // Try to get official canonical name of this host struct addrinfo hints; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; // either IPV4 or IPV6 @@ -208,21 +200,49 @@ const vmime::string posixHandler::getHostName() const struct addrinfo* info; - if (getaddrinfo(hostname, "http", &hints, &info) == 0) + if (getaddrinfo(NULL, "http", &hints, &info) == 0) { + // First, try to get a Fully-Qualified Domain Name (FQDN) + for (struct addrinfo* p = info ; p != NULL ; p = p->ai_next) + { + if (p->ai_canonname) + { + const string hn(p->ai_canonname); + + if (utility::stringUtils::isValidFQDN(hn)) + { + freeaddrinfo(info); + return hn; + } + } + } + + // Then, try to find an acceptable host name for (struct addrinfo* p = info ; p != NULL ; p = p->ai_next) { - if (p->ai_canonname && isFQDN(p->ai_canonname)) + if (p->ai_canonname) { - const string ret(p->ai_canonname); - freeaddrinfo(info); - return ret; + const string hn(p->ai_canonname); + + if (isAcceptableHostname(hn)) + { + freeaddrinfo(info); + return hn; + } } } freeaddrinfo(info); } + // Get host name + char hostname[HOST_NAME_MAX]; + ::gethostname(hostname, sizeof(hostname)); + hostname[sizeof(hostname) - 1] = '\0'; + + if (::strlen(hostname) == 0 || !isAcceptableHostname(hostname)) + ::strcpy(hostname, "localhost.localdomain"); + return hostname; } diff --git a/src/vmime/platforms/windows/windowsHandler.cpp b/src/vmime/platforms/windows/windowsHandler.cpp index 7376606e..294a62d9 100644 --- a/src/vmime/platforms/windows/windowsHandler.cpp +++ b/src/vmime/platforms/windows/windowsHandler.cpp @@ -201,62 +201,40 @@ const vmime::charset windowsHandler::getLocalCharset() const } -static inline bool isFQDN(const vmime::string& str) -{ - if (utility::stringUtils::isStringEqualNoCase(str, "localhost", 9)) - return false; - - const vmime::size_t p = str.find_first_of("."); - return p != vmime::string::npos && p > 0 && p != str.length() - 1; -} - - const vmime::string windowsHandler::getHostName() const { - char hostname[256]; - - // Try with 'gethostname' - ::gethostname(hostname, sizeof(hostname)); - hostname[sizeof(hostname) - 1] = '\0'; + char hostname[1024]; + DWORD hostnameLen; - // If this is a Fully-Qualified Domain Name (FQDN), return immediately - if (isFQDN(hostname)) - return hostname; - - if (::strlen(hostname) == 0) + // First, try to get a Fully-Qualified Domain Name (FQDN) + for (int cnf = ComputerNameDnsHostname ; cnf <= ComputerNameDnsFullyQualified ; ++cnf) { -#if VMIME_HAVE_STRCPY_S - ::strcpy_s(hostname, "localhost"); -#else - ::strcpy(hostname, "localhost"); -#endif // VMIME_HAVE_STRCPY_S - } + hostnameLen = sizeof(hostname); - // Try to get canonical name for the hostname - struct addrinfo hints; - memset(&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; // either IPV4 or IPV6 - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_CANONNAME; + if (GetComputerNameEx((COMPUTER_NAME_FORMAT) cnf, hostname, &hostnameLen)) + { + const vmime::string hostnameStr(hostname); - struct addrinfo* info; + if (utility::stringUtils::isValidFQDN(hostnameStr)) + return hostnameStr; + } + } - if (getaddrinfo(hostname, "http", &hints, &info) == 0) + // Anything else will be OK, as long as it is a valid hostname + for (int cnf = 0 ; cnf < ComputerNameMax ; ++cnf) { - for (struct addrinfo* p = info ; p != NULL ; p = p->ai_next) + hostnameLen = sizeof(hostname); + + if (GetComputerNameEx((COMPUTER_NAME_FORMAT) cnf, hostname, &hostnameLen)) { - if (p->ai_canonname && isFQDN(p->ai_canonname)) - { - const string ret(p->ai_canonname); - freeaddrinfo(info); - return ret; - } - } + const vmime::string hostnameStr(hostname); - freeaddrinfo(info); + if (utility::stringUtils::isValidHostname(hostnameStr)) + return hostnameStr; + } } - return hostname; + return "localhost.localdomain"; } diff --git a/src/vmime/utility/stringUtils.cpp b/src/vmime/utility/stringUtils.cpp index dd99d845..9567eac6 100644 --- a/src/vmime/utility/stringUtils.cpp +++ b/src/vmime/utility/stringUtils.cpp @@ -238,5 +238,82 @@ string stringUtils::quote } +bool stringUtils::isValidHostname(const vmime::string& hostname) +{ + short numberOfDots = 0; + return isValidFQDNImpl(hostname, &numberOfDots); +} + + +bool stringUtils::isValidFQDN(const vmime::string& fqdn) +{ + short numberOfDots = 0; + return isValidFQDNImpl(fqdn, &numberOfDots) && numberOfDots >= 2; +} + + +bool stringUtils::isValidFQDNImpl(const vmime::string& fqdn, short* numberOfDots) +{ + bool alphanumOnly = true; + bool invalid = false; + bool previousIsDot = true; // dot is not allowed as the first char + bool previousIsDash = true; // dash is not allowed as the first char + + *numberOfDots = 0; + + for (size_t i = 0, n = fqdn.length() ; alphanumOnly && !invalid && i < n ; ++i) + { + const char c = fqdn[i]; + + alphanumOnly = ( + (c >= '0' && c <= '9') // DIGIT + || (c >= 'a' && c <= 'z') // ALPHA + || (c >= 'A' && c <= 'Z') // ALPHA + || (c == '.') + || (c == '-') + ); + + if (c == '-') + { + if (previousIsDot) + { + invalid = true; // dash is not allowed as the first char + } + + previousIsDot = false; + previousIsDash = true; + } + else if (c == '.') + { + if (previousIsDash) + { + invalid = true; // dash is not allowed as the first char + } + else if (previousIsDot) + { + invalid = true; // consecutive dots are not allowed + } + else + { + ++*numberOfDots; + previousIsDot = true; + } + + previousIsDash = false; + } + else + { + previousIsDot = false; + previousIsDash = false; + } + } + + return alphanumOnly && + !previousIsDot && + !previousIsDash && + !invalid; +} + + } // utility } // vmime diff --git a/src/vmime/utility/stringUtils.hpp b/src/vmime/utility/stringUtils.hpp index a3062af8..012b08e5 100644 --- a/src/vmime/utility/stringUtils.hpp +++ b/src/vmime/utility/stringUtils.hpp @@ -224,6 +224,27 @@ class VMIME_EXPORT stringUtils * @return quoted string */ static string quote(const string& str, const string& escapeSpecialChars, const string& escapeChar); + + /** Return whether the specified string is a valid host name + * or domain name. + * + * @param hostname string to test + * @return true if the string is a valid host name or domain + * name, or false otherwise + */ + static bool isValidHostname(const vmime::string& hostname); + + /** Return whether the specified string is a valid fully + * qualified domain name (FQDN). + * + * @param fqdn string to test + * @return true if the string seems to be a FQDN, false otherwise + */ + static bool isValidFQDN(const vmime::string& fqdn); + +private: + + static bool isValidFQDNImpl(const vmime::string& fqdn, short* minNumberOfDots); }; diff --git a/tests/utility/stringUtilsTest.cpp b/tests/utility/stringUtilsTest.cpp index a21c2742..d86bbda0 100644 --- a/tests/utility/stringUtilsTest.cpp +++ b/tests/utility/stringUtilsTest.cpp @@ -43,6 +43,9 @@ VMIME_TEST_SUITE_BEGIN(stringUtilsTest) VMIME_TEST(testCountASCIIChars) VMIME_TEST(testUnquote) + + VMIME_TEST(testIsValidHostname) + VMIME_TEST(testIsValidFQDN) VMIME_TEST_LIST_END @@ -160,5 +163,30 @@ VMIME_TEST_SUITE_BEGIN(stringUtilsTest) VASSERT_EQ("4", "quoted with \"escape\"", stringUtils::unquote("\"quoted with \\\"escape\\\"\"")); // "quoted with \"escape\"" } + void testIsValidHostname() + { + VASSERT_TRUE ("1", stringUtils::isValidHostname("localhost")); + VASSERT_TRUE ("2", stringUtils::isValidHostname("localhost.localdomain")); + VASSERT_TRUE ("3", stringUtils::isValidHostname("example.com")); + VASSERT_TRUE ("4", stringUtils::isValidHostname("host.example.com")); + VASSERT_FALSE("5", stringUtils::isValidHostname(".example.com")); + VASSERT_FALSE("6", stringUtils::isValidHostname(".-example.com")); + VASSERT_FALSE("7", stringUtils::isValidHostname(".example-.com")); + VASSERT_FALSE("8", stringUtils::isValidHostname(".exa--mple.com")); + VASSERT_FALSE("9", stringUtils::isValidHostname("-example.com")); + } + + void testIsValidFQDN() + { + VASSERT_FALSE("1", stringUtils::isValidFQDN("localhost")); + VASSERT_FALSE("2", stringUtils::isValidFQDN("localhost.localdomain")); + VASSERT_FALSE("3", stringUtils::isValidFQDN("example.com")); + VASSERT_TRUE ("4", stringUtils::isValidFQDN("host.example.com")); + VASSERT_FALSE("5", stringUtils::isValidFQDN(".example.com")); + VASSERT_FALSE("6", stringUtils::isValidFQDN(".-example.com")); + VASSERT_FALSE("7", stringUtils::isValidFQDN(".example-.com")); + VASSERT_FALSE("8", stringUtils::isValidFQDN(".exa--mple.com")); + } + VMIME_TEST_SUITE_END