Fixed issue #160: invalid characters in hostname.

This commit is contained in:
Vincent Richard 2017-02-10 21:20:22 +01:00
parent e973619d7e
commit 9a3d6880e8
5 changed files with 194 additions and 70 deletions

View File

@ -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 && 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 (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)
{
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;
}

View File

@ -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];
char hostname[1024];
DWORD hostnameLen;
// 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)
// 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;
struct addrinfo* info;
if (getaddrinfo(hostname, "http", &hints, &info) == 0)
{
for (struct addrinfo* p = info ; p != NULL ; p = p->ai_next)
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::isValidFQDN(hostnameStr))
return hostnameStr;
}
}
return hostname;
// Anything else will be OK, as long as it is a valid hostname
for (int cnf = 0 ; cnf < ComputerNameMax ; ++cnf)
{
hostnameLen = sizeof(hostname);
if (GetComputerNameEx((COMPUTER_NAME_FORMAT) cnf, hostname, &hostnameLen))
{
const vmime::string hostnameStr(hostname);
if (utility::stringUtils::isValidHostname(hostnameStr))
return hostnameStr;
}
}
return "localhost.localdomain";
}

View File

@ -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

View File

@ -224,6 +224,27 @@ public:
* @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);
};

View File

@ -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