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