Fixed issue #160: invalid characters in hostname.
This commit is contained in:
parent
e973619d7e
commit
9a3d6880e8
@ -39,6 +39,7 @@
|
|||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <langinfo.h>
|
#include <langinfo.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.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;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const vmime::size_t p = str.find_first_of(".");
|
// Anything else will be OK, as long as it is a valid hostname
|
||||||
return p != vmime::string::npos && p > 0 && p != str.length() - 1;
|
return utility::stringUtils::isValidHostname(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const vmime::string posixHandler::getHostName() const
|
const vmime::string posixHandler::getHostName() const
|
||||||
{
|
{
|
||||||
char hostname[256];
|
// Try to get official canonical name of this host
|
||||||
|
|
||||||
// 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
|
|
||||||
struct addrinfo hints;
|
struct addrinfo hints;
|
||||||
memset(&hints, 0, sizeof hints);
|
memset(&hints, 0, sizeof hints);
|
||||||
hints.ai_family = AF_UNSPEC; // either IPV4 or IPV6
|
hints.ai_family = AF_UNSPEC; // either IPV4 or IPV6
|
||||||
@ -208,21 +200,49 @@ const vmime::string posixHandler::getHostName() const
|
|||||||
|
|
||||||
struct addrinfo* info;
|
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)
|
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);
|
const string hn(p->ai_canonname);
|
||||||
freeaddrinfo(info);
|
|
||||||
return ret;
|
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);
|
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;
|
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
|
const vmime::string windowsHandler::getHostName() const
|
||||||
{
|
{
|
||||||
char hostname[256];
|
char hostname[1024];
|
||||||
|
DWORD hostnameLen;
|
||||||
|
|
||||||
// Try with 'gethostname'
|
// First, try to get a Fully-Qualified Domain Name (FQDN)
|
||||||
::gethostname(hostname, sizeof(hostname));
|
for (int cnf = ComputerNameDnsHostname ; cnf <= ComputerNameDnsFullyQualified ; ++cnf)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
#if VMIME_HAVE_STRCPY_S
|
hostnameLen = sizeof(hostname);
|
||||||
::strcpy_s(hostname, "localhost");
|
|
||||||
#else
|
|
||||||
::strcpy(hostname, "localhost");
|
|
||||||
#endif // VMIME_HAVE_STRCPY_S
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to get canonical name for the hostname
|
if (GetComputerNameEx((COMPUTER_NAME_FORMAT) cnf, hostname, &hostnameLen))
|
||||||
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 (p->ai_canonname && isFQDN(p->ai_canonname))
|
const vmime::string hostnameStr(hostname);
|
||||||
{
|
|
||||||
const string ret(p->ai_canonname);
|
|
||||||
freeaddrinfo(info);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
} // utility
|
||||||
} // vmime
|
} // vmime
|
||||||
|
@ -224,6 +224,27 @@ public:
|
|||||||
* @return quoted string
|
* @return quoted string
|
||||||
*/
|
*/
|
||||||
static string quote(const string& str, const string& escapeSpecialChars, const string& escapeChar);
|
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(testCountASCIIChars)
|
||||||
|
|
||||||
VMIME_TEST(testUnquote)
|
VMIME_TEST(testUnquote)
|
||||||
|
|
||||||
|
VMIME_TEST(testIsValidHostname)
|
||||||
|
VMIME_TEST(testIsValidFQDN)
|
||||||
VMIME_TEST_LIST_END
|
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\""
|
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
|
VMIME_TEST_SUITE_END
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user