Permalink
Browse files

Fixed issue #160: invalid characters in hostname.

  • Loading branch information...
1 parent e973619 commit 9a3d6880e844231404a4cfafe4e0349798eaacad @vincent-richard vincent-richard committed Feb 10, 2017
@@ -39,6 +39,7 @@
#include <locale.h>
#include <langinfo.h>
#include <errno.h>
+#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -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;
}
@@ -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";
}
@@ -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
@@ -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);
};
@@ -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

0 comments on commit 9a3d688

Please sign in to comment.