Skip to content

Commit

Permalink
Fixed issue #160: invalid characters in hostname.
Browse files Browse the repository at this point in the history
  • Loading branch information
vincent-richard committed Feb 10, 2017
1 parent e973619 commit 9a3d688
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 67 deletions.
66 changes: 43 additions & 23 deletions src/vmime/platforms/posix/posixHandler.cpp
Expand Up @@ -39,6 +39,7 @@
#include <locale.h>
#include <langinfo.h>
#include <errno.h>
#include <limits.h>

#include <sys/types.h>
#include <sys/stat.h>
Expand Down Expand Up @@ -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
Expand All @@ -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;
}

Expand Down
66 changes: 22 additions & 44 deletions src/vmime/platforms/windows/windowsHandler.cpp
Expand Up @@ -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";
}


Expand Down
77 changes: 77 additions & 0 deletions src/vmime/utility/stringUtils.cpp
Expand Up @@ -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
21 changes: 21 additions & 0 deletions src/vmime/utility/stringUtils.hpp
Expand Up @@ -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);
};


Expand Down
28 changes: 28 additions & 0 deletions tests/utility/stringUtilsTest.cpp
Expand Up @@ -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


Expand Down Expand Up @@ -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.