1/****************************************************************************
2**
3** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4** Contact: http://www.qt-project.org/legal
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 Digia. For licensing terms and
14** conditions see http://qt.digia.com/licensing. For further information
15** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 2.1 requirements
23** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24**
25** In addition, as a special exception, Digia gives you certain additional
26** rights. These rights are described in the Digia Qt LGPL Exception
27** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28**
29** GNU General Public License Usage
30** Alternatively, this file may be used under the terms of the GNU
31** General Public License version 3.0 as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL included in the
33** packaging of this file. Please review the following information to
34** ensure the GNU General Public License version 3.0 requirements will be
35** met: http://www.gnu.org/copyleft/gpl.html.
36**
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42//#define QHOSTINFO_DEBUG
43
44#include "qplatformdefs.h"
45
46#include "qhostinfo_p.h"
47#include "private/qnativesocketengine_p.h"
48#include "qiodevice.h"
49#include <qbytearray.h>
50#include <qlibrary.h>
51#include <qurl.h>
52#include <qfile.h>
53#include <private/qmutexpool_p.h>
54#include <private/qnet_unix_p.h>
55
56#include <sys/types.h>
57#include <netdb.h>
58#include <arpa/inet.h>
59#if defined(Q_OS_VXWORKS)
60# include <hostLib.h>
61#else
62# include <resolv.h>
63#endif
64
65#if defined (QT_NO_GETADDRINFO)
66#include <qmutex.h>
67QT_BEGIN_NAMESPACE
68Q_GLOBAL_STATIC(QMutex, getHostByNameMutex)
69QT_END_NAMESPACE
70#endif
71
72QT_BEGIN_NAMESPACE
73
74// Almost always the same. If not, specify in qplatformdefs.h.
75#if !defined(QT_SOCKOPTLEN_T)
76# define QT_SOCKOPTLEN_T QT_SOCKLEN_T
77#endif
78
79// HP-UXi has a bug in getaddrinfo(3) that makes it thread-unsafe
80// with this flag. So disable it in that platform.
81#if defined(AI_ADDRCONFIG) && !defined(Q_OS_HPUX)
82# define Q_ADDRCONFIG AI_ADDRCONFIG
83#endif
84
85typedef struct __res_state *res_state_ptr;
86
87typedef int (*res_init_proto)(void);
88static res_init_proto local_res_init = 0;
89typedef int (*res_ninit_proto)(res_state_ptr);
90static res_ninit_proto local_res_ninit = 0;
91typedef void (*res_nclose_proto)(res_state_ptr);
92static res_nclose_proto local_res_nclose = 0;
93static res_state_ptr local_res = 0;
94
95static void resolveLibrary()
96{
97#if !defined(QT_NO_LIBRARY) && !defined(Q_OS_QNX)
98 QLibrary lib(QLatin1String("resolv"));
99 lib.setLoadHints(QLibrary::ImprovedSearchHeuristics);
100 if (!lib.load())
101 return;
102
103 local_res_init = res_init_proto(lib.resolve("__res_init"));
104 if (!local_res_init)
105 local_res_init = res_init_proto(lib.resolve("res_init"));
106
107 local_res_ninit = res_ninit_proto(lib.resolve("__res_ninit"));
108 if (!local_res_ninit)
109 local_res_ninit = res_ninit_proto(lib.resolve("res_ninit"));
110
111 if (!local_res_ninit) {
112 // if we can't get a thread-safe context, we have to use the global _res state
113 local_res = res_state_ptr(lib.resolve("_res"));
114 } else {
115 local_res_nclose = res_nclose_proto(lib.resolve("res_nclose"));
116 if (!local_res_nclose)
117 local_res_nclose = res_nclose_proto(lib.resolve("__res_nclose"));
118 if (!local_res_nclose)
119 local_res_ninit = 0;
120 }
121#endif
122}
123
124QHostInfo QHostInfoAgent::fromName(const QString &hostName)
125{
126 QHostInfo results;
127
128#if defined(QHOSTINFO_DEBUG)
129 qDebug("QHostInfoAgent::fromName(%s) looking up...",
130 hostName.toLatin1().constData());
131#endif
132
133 // Load res_init on demand.
134 static volatile bool triedResolve = false;
135 if (!triedResolve) {
136 QMutexLocker locker(QMutexPool::globalInstanceGet(&local_res_init));
137 if (!triedResolve) {
138 resolveLibrary();
139 triedResolve = true;
140 }
141 }
142
143 // If res_init is available, poll it.
144 if (local_res_init)
145 local_res_init();
146
147 QHostAddress address;
148 if (address.setAddress(hostName)) {
149 // Reverse lookup
150// Reverse lookups using getnameinfo are broken on darwin, use gethostbyaddr instead.
151#if !defined (QT_NO_GETADDRINFO) && !defined (Q_OS_DARWIN)
152 sockaddr_in sa4;
153#ifndef QT_NO_IPV6
154 sockaddr_in6 sa6;
155#endif
156 sockaddr *sa = 0;
157 QT_SOCKLEN_T saSize = 0;
158 if (address.protocol() == QAbstractSocket::IPv4Protocol) {
159 sa = (sockaddr *)&sa4;
160 saSize = sizeof(sa4);
161 memset(&sa4, 0, sizeof(sa4));
162 sa4.sin_family = AF_INET;
163 sa4.sin_addr.s_addr = htonl(address.toIPv4Address());
164 }
165#ifndef QT_NO_IPV6
166 else {
167 sa = (sockaddr *)&sa6;
168 saSize = sizeof(sa6);
169 memset(&sa6, 0, sizeof(sa6));
170 sa6.sin6_family = AF_INET6;
171 memcpy(sa6.sin6_addr.s6_addr, address.toIPv6Address().c, sizeof(sa6.sin6_addr.s6_addr));
172 }
173#endif
174
175 char hbuf[NI_MAXHOST];
176 if (sa && getnameinfo(sa, saSize, hbuf, sizeof(hbuf), 0, 0, 0) == 0)
177 results.setHostName(QString::fromLatin1(hbuf));
178#else
179 in_addr_t inetaddr = qt_safe_inet_addr(hostName.toLatin1().constData());
180 struct hostent *ent = gethostbyaddr((const char *)&inetaddr, sizeof(inetaddr), AF_INET);
181 if (ent)
182 results.setHostName(QString::fromLatin1(ent->h_name));
183#endif
184
185 if (results.hostName().isEmpty())
186 results.setHostName(address.toString());
187 results.setAddresses(QList<QHostAddress>() << address);
188 return results;
189 }
190
191 // IDN support
192 QByteArray aceHostname = QUrl::toAce(hostName);
193 results.setHostName(hostName);
194 if (aceHostname.isEmpty()) {
195 results.setError(QHostInfo::HostNotFound);
196 results.setErrorString(hostName.isEmpty() ?
197 QCoreApplication::translate("QHostInfoAgent", "No host name given") :
198 QCoreApplication::translate("QHostInfoAgent", "Invalid hostname"));
199 return results;
200 }
201
202#if !defined (QT_NO_GETADDRINFO)
203 // Call getaddrinfo, and place all IPv4 addresses at the start and
204 // the IPv6 addresses at the end of the address list in results.
205 addrinfo *res = 0;
206 struct addrinfo hints;
207 memset(&hints, 0, sizeof(hints));
208 hints.ai_family = PF_UNSPEC;
209#ifdef Q_ADDRCONFIG
210 hints.ai_flags = Q_ADDRCONFIG;
211#endif
212
213 int result = getaddrinfo(aceHostname.constData(), 0, &hints, &res);
214# ifdef Q_ADDRCONFIG
215 if (result == EAI_BADFLAGS) {
216 // if the lookup failed with AI_ADDRCONFIG set, try again without it
217 hints.ai_flags = 0;
218 result = getaddrinfo(aceHostname.constData(), 0, &hints, &res);
219 }
220# endif
221
222 if (result == 0) {
223 addrinfo *node = res;
224 QList<QHostAddress> addresses;
225 while (node) {
226#ifdef QHOSTINFO_DEBUG
227 qDebug() << "getaddrinfo node: flags:" << node->ai_flags << "family:" << node->ai_family << "ai_socktype:" << node->ai_socktype << "ai_protocol:" << node->ai_protocol << "ai_addrlen:" << node->ai_addrlen;
228#endif
229 if (node->ai_family == AF_INET) {
230 QHostAddress addr;
231 addr.setAddress(ntohl(((sockaddr_in *) node->ai_addr)->sin_addr.s_addr));
232 if (!addresses.contains(addr))
233 addresses.append(addr);
234 }
235#ifndef QT_NO_IPV6
236 else if (node->ai_family == AF_INET6) {
237 QHostAddress addr;
238 sockaddr_in6 *sa6 = (sockaddr_in6 *) node->ai_addr;
239 addr.setAddress(sa6->sin6_addr.s6_addr);
240 if (sa6->sin6_scope_id)
241 addr.setScopeId(QString::number(sa6->sin6_scope_id));
242 if (!addresses.contains(addr))
243 addresses.append(addr);
244 }
245#endif
246 node = node->ai_next;
247 }
248 if (addresses.isEmpty() && node == 0) {
249 // Reached the end of the list, but no addresses were found; this
250 // means the list contains one or more unknown address types.
251 results.setError(QHostInfo::UnknownError);
252 results.setErrorString(tr("Unknown address type"));
253 }
254
255 results.setAddresses(addresses);
256 freeaddrinfo(res);
257 } else if (result == EAI_NONAME
258 || result == EAI_FAIL
259#ifdef EAI_NODATA
260 // EAI_NODATA is deprecated in RFC 3493
261 || result == EAI_NODATA
262#endif
263 ) {
264 results.setError(QHostInfo::HostNotFound);
265 results.setErrorString(tr("Host not found"));
266 } else {
267 results.setError(QHostInfo::UnknownError);
268 results.setErrorString(QString::fromLocal8Bit(gai_strerror(result)));
269 }
270
271#else
272 // Fall back to gethostbyname for platforms that don't define
273 // getaddrinfo. gethostbyname does not support IPv6, and it's not
274 // reentrant on all platforms. For now this is okay since we only
275 // use one QHostInfoAgent, but if more agents are introduced, locking
276 // must be provided.
277 QMutexLocker locker(::getHostByNameMutex());
278 hostent *result = gethostbyname(aceHostname.constData());
279 if (result) {
280 if (result->h_addrtype == AF_INET) {
281 QList<QHostAddress> addresses;
282 for (char **p = result->h_addr_list; *p != 0; p++) {
283 QHostAddress addr;
284 addr.setAddress(ntohl(*((quint32 *)*p)));
285 if (!addresses.contains(addr))
286 addresses.prepend(addr);
287 }
288 results.setAddresses(addresses);
289 } else {
290 results.setError(QHostInfo::UnknownError);
291 results.setErrorString(tr("Unknown address type"));
292 }
293#if !defined(Q_OS_VXWORKS)
294 } else if (h_errno == HOST_NOT_FOUND || h_errno == NO_DATA
295 || h_errno == NO_ADDRESS) {
296 results.setError(QHostInfo::HostNotFound);
297 results.setErrorString(tr("Host not found"));
298#endif
299 } else {
300 results.setError(QHostInfo::UnknownError);
301 results.setErrorString(tr("Unknown error"));
302 }
303#endif // !defined (QT_NO_GETADDRINFO)
304
305#if defined(QHOSTINFO_DEBUG)
306 if (results.error() != QHostInfo::NoError) {
307 qDebug("QHostInfoAgent::fromName(): error #%d %s",
308 h_errno, results.errorString().toLatin1().constData());
309 } else {
310 QString tmp;
311 QList<QHostAddress> addresses = results.addresses();
312 for (int i = 0; i < addresses.count(); ++i) {
313 if (i != 0) tmp += ", ";
314 tmp += addresses.at(i).toString();
315 }
316 qDebug("QHostInfoAgent::fromName(): found %i entries for \"%s\": {%s}",
317 addresses.count(), hostName.toLatin1().constData(),
318 tmp.toLatin1().constData());
319 }
320#endif
321 return results;
322}
323
324QString QHostInfo::localHostName()
325{
326 char hostName[512];
327 if (gethostname(hostName, sizeof(hostName)) == -1)
328 return QString();
329 hostName[sizeof(hostName) - 1] = '\0';
330 return QString::fromLocal8Bit(hostName);
331}
332
333QString QHostInfo::localDomainName()
334{
335#if !defined(Q_OS_VXWORKS)
336 resolveLibrary();
337 if (local_res_ninit) {
338 // using thread-safe version
339 res_state_ptr state = res_state_ptr(qMalloc(sizeof(*state)));
340 Q_CHECK_PTR(state);
341 memset(state, 0, sizeof(*state));
342 local_res_ninit(state);
343 QString domainName = QUrl::fromAce(state->defdname);
344 if (domainName.isEmpty())
345 domainName = QUrl::fromAce(state->dnsrch[0]);
346 local_res_nclose(state);
347 qFree(state);
348
349 return domainName;
350 }
351
352 if (local_res_init && local_res) {
353 // using thread-unsafe version
354
355#if defined(QT_NO_GETADDRINFO)
356 // We have to call res_init to be sure that _res was initialized
357 // So, for systems without getaddrinfo (which is thread-safe), we lock the mutex too
358 QMutexLocker locker(::getHostByNameMutex());
359#endif
360 local_res_init();
361 QString domainName = QUrl::fromAce(local_res->defdname);
362 if (domainName.isEmpty())
363 domainName = QUrl::fromAce(local_res->dnsrch[0]);
364 return domainName;
365 }
366#endif
367 // nothing worked, try doing it by ourselves:
368 QFile resolvconf;
369#if defined(_PATH_RESCONF)
370 resolvconf.setFileName(QFile::decodeName(_PATH_RESCONF));
371#else
372 resolvconf.setFileName(QLatin1String("/etc/resolv.conf"));
373#endif
374 if (!resolvconf.open(QIODevice::ReadOnly))
375 return QString(); // failure
376
377 QString domainName;
378 while (!resolvconf.atEnd()) {
379 QByteArray line = resolvconf.readLine().trimmed();
380 if (line.startsWith("domain "))
381 return QUrl::fromAce(line.mid(sizeof "domain " - 1).trimmed());
382
383 // in case there's no "domain" line, fall back to the first "search" entry
384 if (domainName.isEmpty() && line.startsWith("search ")) {
385 QByteArray searchDomain = line.mid(sizeof "search " - 1).trimmed();
386 int pos = searchDomain.indexOf(' ');
387 if (pos != -1)
388 searchDomain.truncate(pos);
389 domainName = QUrl::fromAce(searchDomain);
390 }
391 }
392
393 // return the fallen-back-to searched domain
394 return domainName;
395}
396
397QT_END_NAMESPACE
398