Fixed issue #160: invalid characters in hostname.
This commit is contained in:
parent
e973619d7e
commit
9a3d6880e8
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
}
|
||||
|
||||
|
||||
|
@ -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 @@ 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);
|
||||
};
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user