Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:trusted-data
//#define QHOSTINFO_DEBUG
#include "qhostinfo_p.h"
#include <qbytearray.h>
#include <qfile.h>
#include <qplatformdefs.h>
#include <qurl.h>
#include <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#if QT_CONFIG(libresolv)
# include <resolv.h>
#endif
#ifndef _PATH_RESCONF
# define _PATH_RESCONF "/etc/resolv.conf"
#endif
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static void maybeRefreshResolver()
{
#if defined(RES_NORELOAD)
// If RES_NORELOAD is defined, then the libc is capable of watching
// /etc/resolv.conf for changes and reloading as necessary. So accept
// whatever is configured.
return;
#elif defined(Q_OS_DARWIN)
// Apple's libsystem_info.dylib:getaddrinfo() uses the
// libsystem_dnssd.dylib to resolve hostnames. Using res_init() has no
// effect on it and is thread-unsafe.
return;
#elif defined(Q_OS_FREEBSD)
// FreeBSD automatically refreshes:
return;
#elif defined(Q_OS_OPENBSD)
// OpenBSD automatically refreshes:
return;
#elif defined(Q_OS_QNX)
// res_init() is not thread-safe; executing it leads to state corruption.
// Whether it reloads resolv.conf on its own is unknown.
return;
#endif
#if QT_CONFIG(libresolv)
// OSes known or thought to reach here: AIX, NetBSD, Solaris,
// Linux with MUSL (though res_init() does nothing and is unnecessary)
Q_CONSTINIT static QT_STATBUF lastStat = {};
Q_CONSTINIT static QBasicMutex mutex = {};
if (QT_STATBUF st; QT_STAT(_PATH_RESCONF, &st) == 0) {
QMutexLocker locker(&mutex);
bool refresh = false;
if ((_res.options & RES_INIT) == 0)
refresh = true;
else if (lastStat.st_ctime != st.st_ctime)
refresh = true; // file was updated
else if (lastStat.st_dev != st.st_dev || lastStat.st_ino != st.st_ino)
refresh = true; // file was replaced
if (refresh) {
lastStat = st;
res_init();
}
}
#endif
}
QHostInfo QHostInfoAgent::fromName(const QString &hostName)
{
QHostInfo results;
#if defined(QHOSTINFO_DEBUG)
qDebug("QHostInfoAgent::fromName(%s) looking up...",
hostName.toLatin1().constData());
#endif
maybeRefreshResolver();
QHostAddress address;
if (address.setAddress(hostName))
return reverseLookup(address);
return lookup(hostName);
}
QString QHostInfo::localDomainName()
{
#if QT_CONFIG(libresolv)
auto domainNameFromRes = [](res_state r) {
QString domainName;
if (r->defdname[0])
domainName = QUrl::fromAce(r->defdname);
if (domainName.isEmpty())
domainName = QUrl::fromAce(r->dnsrch[0]);
return domainName;
};
std::remove_pointer_t<res_state> state = {};
if (res_ninit(&state) == 0) {
// using thread-safe version
auto guard = qScopeGuard([&] { res_nclose(&state); });
return domainNameFromRes(&state);
}
// using thread-unsafe version
maybeRefreshResolver();
return domainNameFromRes(&_res);
#endif // !QT_CONFIG(libresolv)
// nothing worked, try doing it by ourselves:
QFile resolvconf;
resolvconf.setFileName(_PATH_RESCONF ""_L1);
if (!resolvconf.open(QIODevice::ReadOnly))
return QString(); // failure
QString domainName;
QByteArray lineArray;
while (resolvconf.readLineInto(&lineArray)) {
QByteArrayView line = QByteArrayView(lineArray).trimmed();
constexpr QByteArrayView domainWithSpace = "domain ";
if (line.startsWith(domainWithSpace))
return QUrl::fromAce(line.mid(domainWithSpace.size()).trimmed().toByteArray());
// in case there's no "domain" line, fall back to the first "search" entry
constexpr QByteArrayView searchWithSpace = "search ";
if (domainName.isEmpty() && line.startsWith(searchWithSpace)) {
QByteArrayView searchDomain = line.mid(searchWithSpace.size()).trimmed();
int pos = searchDomain.indexOf(' ');
if (pos != -1)
searchDomain.truncate(pos);
domainName = QUrl::fromAce(searchDomain.toByteArray());
}
}
// return the fallen-back-to searched domain
return domainName;
}
QT_END_NAMESPACE