| 1 | // Copyright (C) 2016 The Qt Company Ltd. |
|---|---|
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | //#define QHOSTINFO_DEBUG |
| 5 | |
| 6 | #include "qhostinfo_p.h" |
| 7 | |
| 8 | #include <qbytearray.h> |
| 9 | #include <qfile.h> |
| 10 | #include <qplatformdefs.h> |
| 11 | #include <qurl.h> |
| 12 | |
| 13 | #include <sys/types.h> |
| 14 | #include <netdb.h> |
| 15 | #include <netinet/in.h> |
| 16 | |
| 17 | #if QT_CONFIG(libresolv) |
| 18 | # include <resolv.h> |
| 19 | #endif |
| 20 | |
| 21 | #ifndef _PATH_RESCONF |
| 22 | # define _PATH_RESCONF "/etc/resolv.conf" |
| 23 | #endif |
| 24 | |
| 25 | QT_BEGIN_NAMESPACE |
| 26 | |
| 27 | using namespace Qt::StringLiterals; |
| 28 | |
| 29 | static void maybeRefreshResolver() |
| 30 | { |
| 31 | #if defined(RES_NORELOAD) |
| 32 | // If RES_NORELOAD is defined, then the libc is capable of watching |
| 33 | // /etc/resolv.conf for changes and reloading as necessary. So accept |
| 34 | // whatever is configured. |
| 35 | return; |
| 36 | #elif defined(Q_OS_DARWIN) |
| 37 | // Apple's libsystem_info.dylib:getaddrinfo() uses the |
| 38 | // libsystem_dnssd.dylib to resolve hostnames. Using res_init() has no |
| 39 | // effect on it and is thread-unsafe. |
| 40 | return; |
| 41 | #elif defined(Q_OS_FREEBSD) |
| 42 | // FreeBSD automatically refreshes: |
| 43 | // https://github.com/freebsd/freebsd-src/blob/b3fe5d932264445cbf9a1c4eab01afb6179b499b/lib/libc/resolv/res_state.c#L69 |
| 44 | return; |
| 45 | #elif defined(Q_OS_OPENBSD) |
| 46 | // OpenBSD automatically refreshes: |
| 47 | // https://github.com/ligurio/openbsd-src/blob/b1ce0da17da254cc15b8aff25b3d55d3c7a82cec/lib/libc/asr/asr.c#L367 |
| 48 | return; |
| 49 | #elif defined(Q_OS_QNX) |
| 50 | // res_init() is not thread-safe; executing it leads to state corruption. |
| 51 | // Whether it reloads resolv.conf on its own is unknown. |
| 52 | return; |
| 53 | #endif |
| 54 | |
| 55 | #if QT_CONFIG(libresolv) |
| 56 | // OSes known or thought to reach here: AIX, NetBSD, Solaris, |
| 57 | // Linux with MUSL (though res_init() does nothing and is unnecessary) |
| 58 | |
| 59 | Q_CONSTINIT static QT_STATBUF lastStat = {}; |
| 60 | Q_CONSTINIT static QBasicMutex mutex = {}; |
| 61 | if (QT_STATBUF st; QT_STAT(_PATH_RESCONF, buf: &st) == 0) { |
| 62 | QMutexLocker locker(&mutex); |
| 63 | bool refresh = false; |
| 64 | if ((_res.options & RES_INIT) == 0) |
| 65 | refresh = true; |
| 66 | else if (lastStat.st_ctime != st.st_ctime) |
| 67 | refresh = true; // file was updated |
| 68 | else if (lastStat.st_dev != st.st_dev || lastStat.st_ino != st.st_ino) |
| 69 | refresh = true; // file was replaced |
| 70 | if (refresh) { |
| 71 | lastStat = st; |
| 72 | res_init(); |
| 73 | } |
| 74 | } |
| 75 | #endif |
| 76 | } |
| 77 | |
| 78 | QHostInfo QHostInfoAgent::fromName(const QString &hostName) |
| 79 | { |
| 80 | QHostInfo results; |
| 81 | |
| 82 | #if defined(QHOSTINFO_DEBUG) |
| 83 | qDebug("QHostInfoAgent::fromName(%s) looking up...", |
| 84 | hostName.toLatin1().constData()); |
| 85 | #endif |
| 86 | |
| 87 | maybeRefreshResolver(); |
| 88 | |
| 89 | QHostAddress address; |
| 90 | if (address.setAddress(hostName)) |
| 91 | return reverseLookup(address); |
| 92 | |
| 93 | return lookup(hostName); |
| 94 | } |
| 95 | |
| 96 | QString QHostInfo::localDomainName() |
| 97 | { |
| 98 | #if QT_CONFIG(libresolv) |
| 99 | auto domainNameFromRes = [](res_state r) { |
| 100 | QString domainName; |
| 101 | if (r->defdname[0]) |
| 102 | domainName = QUrl::fromAce(domain: r->defdname); |
| 103 | if (domainName.isEmpty()) |
| 104 | domainName = QUrl::fromAce(domain: r->dnsrch[0]); |
| 105 | return domainName; |
| 106 | }; |
| 107 | std::remove_pointer_t<res_state> state = {}; |
| 108 | if (res_ninit(&state) == 0) { |
| 109 | // using thread-safe version |
| 110 | auto guard = qScopeGuard(f: [&] { res_nclose(&state); }); |
| 111 | return domainNameFromRes(&state); |
| 112 | } |
| 113 | |
| 114 | // using thread-unsafe version |
| 115 | maybeRefreshResolver(); |
| 116 | return domainNameFromRes(&_res); |
| 117 | #endif // !QT_CONFIG(libresolv) |
| 118 | |
| 119 | // nothing worked, try doing it by ourselves: |
| 120 | QFile resolvconf; |
| 121 | resolvconf.setFileName(_PATH_RESCONF ""_L1); |
| 122 | if (!resolvconf.open(flags: QIODevice::ReadOnly)) |
| 123 | return QString(); // failure |
| 124 | |
| 125 | QString domainName; |
| 126 | while (!resolvconf.atEnd()) { |
| 127 | const QByteArray lineArray = resolvconf.readLine(); |
| 128 | QByteArrayView line = QByteArrayView(lineArray).trimmed(); |
| 129 | constexpr QByteArrayView domainWithSpace = "domain "; |
| 130 | if (line.startsWith(other: domainWithSpace)) |
| 131 | return QUrl::fromAce(domain: line.mid(pos: domainWithSpace.size()).trimmed().toByteArray()); |
| 132 | |
| 133 | // in case there's no "domain" line, fall back to the first "search" entry |
| 134 | constexpr QByteArrayView searchWithSpace = "search "; |
| 135 | if (domainName.isEmpty() && line.startsWith(other: searchWithSpace)) { |
| 136 | QByteArrayView searchDomain = line.mid(pos: searchWithSpace.size()).trimmed(); |
| 137 | int pos = searchDomain.indexOf(ch: ' '); |
| 138 | if (pos != -1) |
| 139 | searchDomain.truncate(n: pos); |
| 140 | domainName = QUrl::fromAce(domain: searchDomain.toByteArray()); |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | // return the fallen-back-to searched domain |
| 145 | return domainName; |
| 146 | } |
| 147 | |
| 148 | QT_END_NAMESPACE |
| 149 |
