1/****************************************************************************
2**
3** Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtNetwork module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qdnslookup_p.h"
41
42#if QT_CONFIG(library)
43#include <qlibrary.h>
44#endif
45#include <qvarlengtharray.h>
46#include <qscopedpointer.h>
47#include <qurl.h>
48#include <private/qnativesocketengine_p.h>
49
50#include <sys/types.h>
51#include <netinet/in.h>
52#include <arpa/nameser.h>
53#if !defined(Q_OS_OPENBSD)
54# include <arpa/nameser_compat.h>
55#endif
56#include <resolv.h>
57
58#if defined(__GNU_LIBRARY__) && !defined(__UCLIBC__)
59# include <gnu/lib-names.h>
60#endif
61
62#if defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen)
63# include <dlfcn.h>
64#endif
65
66#include <cstring>
67
68QT_BEGIN_NAMESPACE
69
70#if QT_CONFIG(library)
71
72#if defined(Q_OS_OPENBSD)
73typedef struct __res_state* res_state;
74#endif
75typedef int (*dn_expand_proto)(const unsigned char *, const unsigned char *, const unsigned char *, char *, int);
76static dn_expand_proto local_dn_expand = nullptr;
77typedef void (*res_nclose_proto)(res_state);
78static res_nclose_proto local_res_nclose = nullptr;
79typedef int (*res_ninit_proto)(res_state);
80static res_ninit_proto local_res_ninit = nullptr;
81typedef int (*res_nquery_proto)(res_state, const char *, int, int, unsigned char *, int);
82static res_nquery_proto local_res_nquery = nullptr;
83
84// Custom deleter to close resolver state.
85
86struct QDnsLookupStateDeleter
87{
88 static inline void cleanup(struct __res_state *pointer)
89 {
90 local_res_nclose(pointer);
91 }
92};
93
94static QFunctionPointer resolveSymbol(QLibrary &lib, const char *sym)
95{
96 if (lib.isLoaded())
97 return lib.resolve(sym);
98
99#if defined(RTLD_DEFAULT) && (defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen))
100 return reinterpret_cast<QFunctionPointer>(dlsym(RTLD_DEFAULT, sym));
101#else
102 return nullptr;
103#endif
104}
105
106static bool resolveLibraryInternal()
107{
108 QLibrary lib;
109#ifdef LIBRESOLV_SO
110 lib.setFileName(QStringLiteral(LIBRESOLV_SO));
111 if (!lib.load())
112#endif
113 {
114 lib.setFileName(QLatin1String("resolv"));
115 lib.load();
116 }
117
118 local_dn_expand = dn_expand_proto(resolveSymbol(lib, "__dn_expand"));
119 if (!local_dn_expand)
120 local_dn_expand = dn_expand_proto(resolveSymbol(lib, "dn_expand"));
121
122 local_res_nclose = res_nclose_proto(resolveSymbol(lib, "__res_nclose"));
123 if (!local_res_nclose)
124 local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_9_nclose"));
125 if (!local_res_nclose)
126 local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_nclose"));
127
128 local_res_ninit = res_ninit_proto(resolveSymbol(lib, "__res_ninit"));
129 if (!local_res_ninit)
130 local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_9_ninit"));
131 if (!local_res_ninit)
132 local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_ninit"));
133
134 local_res_nquery = res_nquery_proto(resolveSymbol(lib, "__res_nquery"));
135 if (!local_res_nquery)
136 local_res_nquery = res_nquery_proto(resolveSymbol(lib, "res_9_nquery"));
137 if (!local_res_nquery)
138 local_res_nquery = res_nquery_proto(resolveSymbol(lib, "res_nquery"));
139
140 return true;
141}
142Q_GLOBAL_STATIC_WITH_ARGS(bool, resolveLibrary, (resolveLibraryInternal()))
143
144void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
145{
146 // Load dn_expand, res_ninit and res_nquery on demand.
147 resolveLibrary();
148
149 // If dn_expand, res_ninit or res_nquery is missing, fail.
150 if (!local_dn_expand || !local_res_nclose || !local_res_ninit || !local_res_nquery) {
151 reply->error = QDnsLookup::ResolverError;
152 reply->errorString = tr("Resolver functions not found");
153 return;
154 }
155
156 // Initialize state.
157 struct __res_state state;
158 std::memset(&state, 0, sizeof(state));
159 if (local_res_ninit(&state) < 0) {
160 reply->error = QDnsLookup::ResolverError;
161 reply->errorString = tr("Resolver initialization failed");
162 return;
163 }
164
165 //Check if a nameserver was set. If so, use it
166 if (!nameserver.isNull()) {
167 if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) {
168 state.nsaddr_list[0].sin_addr.s_addr = htonl(nameserver.toIPv4Address());
169 state.nscount = 1;
170 } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) {
171#if defined(Q_OS_LINUX)
172 struct sockaddr_in6 *ns;
173 ns = state._u._ext.nsaddrs[0];
174 // nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf
175 if (!ns) {
176 // Memory allocated here will be free'd in res_close() as we
177 // have done res_init() above.
178 ns = (struct sockaddr_in6*) calloc(1, sizeof(struct sockaddr_in6));
179 Q_CHECK_PTR(ns);
180 state._u._ext.nsaddrs[0] = ns;
181 }
182#ifndef __UCLIBC__
183 // Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address
184 // See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html
185 state._u._ext.nsmap[0] = MAXNS + 1;
186#endif
187 state._u._ext.nscount6 = 1;
188 ns->sin6_family = AF_INET6;
189 ns->sin6_port = htons(53);
190 SetSALen::set(ns, sizeof(*ns));
191
192 Q_IPV6ADDR ipv6Address = nameserver.toIPv6Address();
193 for (int i=0; i<16; i++) {
194 ns->sin6_addr.s6_addr[i] = ipv6Address[i];
195 }
196#else
197 qWarning("%s", QDnsLookupPrivate::msgNoIpV6NameServerAdresses);
198 reply->error = QDnsLookup::ResolverError;
199 reply->errorString = tr(QDnsLookupPrivate::msgNoIpV6NameServerAdresses);
200 return;
201#endif
202 }
203 }
204#ifdef QDNSLOOKUP_DEBUG
205 state.options |= RES_DEBUG;
206#endif
207 QScopedPointer<struct __res_state, QDnsLookupStateDeleter> state_ptr(&state);
208
209 // Perform DNS query.
210 QVarLengthArray<unsigned char, PACKETSZ> buffer(PACKETSZ);
211 std::memset(buffer.data(), 0, buffer.size());
212 int responseLength = local_res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size());
213 if (Q_UNLIKELY(responseLength > PACKETSZ)) {
214 buffer.resize(responseLength);
215 std::memset(buffer.data(), 0, buffer.size());
216 responseLength = local_res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size());
217 if (Q_UNLIKELY(responseLength > buffer.size())) {
218 // Ok, we give up.
219 reply->error = QDnsLookup::ResolverError;
220 reply->errorString.clear(); // We cannot be more specific, alas.
221 return;
222 }
223 }
224
225 unsigned char *response = buffer.data();
226 // Check the response header. Though res_nquery returns -1 as a
227 // responseLength in case of error, we still can extract the
228 // exact error code from the response.
229 HEADER *header = (HEADER*)response;
230 const int answerCount = ntohs(header->ancount);
231 switch (header->rcode) {
232 case NOERROR:
233 break;
234 case FORMERR:
235 reply->error = QDnsLookup::InvalidRequestError;
236 reply->errorString = tr("Server could not process query");
237 return;
238 case SERVFAIL:
239 reply->error = QDnsLookup::ServerFailureError;
240 reply->errorString = tr("Server failure");
241 return;
242 case NXDOMAIN:
243 reply->error = QDnsLookup::NotFoundError;
244 reply->errorString = tr("Non existent domain");
245 return;
246 case REFUSED:
247 reply->error = QDnsLookup::ServerRefusedError;
248 reply->errorString = tr("Server refused to answer");
249 return;
250 default:
251 reply->error = QDnsLookup::InvalidReplyError;
252 reply->errorString = tr("Invalid reply received");
253 return;
254 }
255
256 // Check the reply is valid.
257 if (responseLength < int(sizeof(HEADER))) {
258 reply->error = QDnsLookup::InvalidReplyError;
259 reply->errorString = tr("Invalid reply received");
260 return;
261 }
262
263 // Skip the query host, type (2 bytes) and class (2 bytes).
264 char host[PACKETSZ], answer[PACKETSZ];
265 unsigned char *p = response + sizeof(HEADER);
266 int status = local_dn_expand(response, response + responseLength, p, host, sizeof(host));
267 if (status < 0) {
268 reply->error = QDnsLookup::InvalidReplyError;
269 reply->errorString = tr("Could not expand domain name");
270 return;
271 }
272 p += status + 4;
273
274 // Extract results.
275 int answerIndex = 0;
276 while ((p < response + responseLength) && (answerIndex < answerCount)) {
277 status = local_dn_expand(response, response + responseLength, p, host, sizeof(host));
278 if (status < 0) {
279 reply->error = QDnsLookup::InvalidReplyError;
280 reply->errorString = tr("Could not expand domain name");
281 return;
282 }
283 const QString name = QUrl::fromAce(host);
284
285 p += status;
286 const quint16 type = (p[0] << 8) | p[1];
287 p += 2; // RR type
288 p += 2; // RR class
289 const quint32 ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
290 p += 4;
291 const quint16 size = (p[0] << 8) | p[1];
292 p += 2;
293
294 if (type == QDnsLookup::A) {
295 if (size != 4) {
296 reply->error = QDnsLookup::InvalidReplyError;
297 reply->errorString = tr("Invalid IPv4 address record");
298 return;
299 }
300 const quint32 addr = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
301 QDnsHostAddressRecord record;
302 record.d->name = name;
303 record.d->timeToLive = ttl;
304 record.d->value = QHostAddress(addr);
305 reply->hostAddressRecords.append(record);
306 } else if (type == QDnsLookup::AAAA) {
307 if (size != 16) {
308 reply->error = QDnsLookup::InvalidReplyError;
309 reply->errorString = tr("Invalid IPv6 address record");
310 return;
311 }
312 QDnsHostAddressRecord record;
313 record.d->name = name;
314 record.d->timeToLive = ttl;
315 record.d->value = QHostAddress(p);
316 reply->hostAddressRecords.append(record);
317 } else if (type == QDnsLookup::CNAME) {
318 status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
319 if (status < 0) {
320 reply->error = QDnsLookup::InvalidReplyError;
321 reply->errorString = tr("Invalid canonical name record");
322 return;
323 }
324 QDnsDomainNameRecord record;
325 record.d->name = name;
326 record.d->timeToLive = ttl;
327 record.d->value = QUrl::fromAce(answer);
328 reply->canonicalNameRecords.append(record);
329 } else if (type == QDnsLookup::NS) {
330 status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
331 if (status < 0) {
332 reply->error = QDnsLookup::InvalidReplyError;
333 reply->errorString = tr("Invalid name server record");
334 return;
335 }
336 QDnsDomainNameRecord record;
337 record.d->name = name;
338 record.d->timeToLive = ttl;
339 record.d->value = QUrl::fromAce(answer);
340 reply->nameServerRecords.append(record);
341 } else if (type == QDnsLookup::PTR) {
342 status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
343 if (status < 0) {
344 reply->error = QDnsLookup::InvalidReplyError;
345 reply->errorString = tr("Invalid pointer record");
346 return;
347 }
348 QDnsDomainNameRecord record;
349 record.d->name = name;
350 record.d->timeToLive = ttl;
351 record.d->value = QUrl::fromAce(answer);
352 reply->pointerRecords.append(record);
353 } else if (type == QDnsLookup::MX) {
354 const quint16 preference = (p[0] << 8) | p[1];
355 status = local_dn_expand(response, response + responseLength, p + 2, answer, sizeof(answer));
356 if (status < 0) {
357 reply->error = QDnsLookup::InvalidReplyError;
358 reply->errorString = tr("Invalid mail exchange record");
359 return;
360 }
361 QDnsMailExchangeRecord record;
362 record.d->exchange = QUrl::fromAce(answer);
363 record.d->name = name;
364 record.d->preference = preference;
365 record.d->timeToLive = ttl;
366 reply->mailExchangeRecords.append(record);
367 } else if (type == QDnsLookup::SRV) {
368 const quint16 priority = (p[0] << 8) | p[1];
369 const quint16 weight = (p[2] << 8) | p[3];
370 const quint16 port = (p[4] << 8) | p[5];
371 status = local_dn_expand(response, response + responseLength, p + 6, answer, sizeof(answer));
372 if (status < 0) {
373 reply->error = QDnsLookup::InvalidReplyError;
374 reply->errorString = tr("Invalid service record");
375 return;
376 }
377 QDnsServiceRecord record;
378 record.d->name = name;
379 record.d->target = QUrl::fromAce(answer);
380 record.d->port = port;
381 record.d->priority = priority;
382 record.d->timeToLive = ttl;
383 record.d->weight = weight;
384 reply->serviceRecords.append(record);
385 } else if (type == QDnsLookup::TXT) {
386 unsigned char *txt = p;
387 QDnsTextRecord record;
388 record.d->name = name;
389 record.d->timeToLive = ttl;
390 while (txt < p + size) {
391 const unsigned char length = *txt;
392 txt++;
393 if (txt + length > p + size) {
394 reply->error = QDnsLookup::InvalidReplyError;
395 reply->errorString = tr("Invalid text record");
396 return;
397 }
398 record.d->values << QByteArray((char*)txt, length);
399 txt += length;
400 }
401 reply->textRecords.append(record);
402 }
403 p += size;
404 answerIndex++;
405 }
406}
407
408#else
409void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
410{
411 Q_UNUSED(requestType);
412 Q_UNUSED(requestName);
413 Q_UNUSED(nameserver);
414 reply->error = QDnsLookup::ResolverError;
415 reply->errorString = tr("Resolver library can't be loaded: No runtime library loading support");
416 return;
417}
418
419#endif /* QT_CONFIG(library) */
420
421QT_END_NAMESPACE
422