summaryrefslogtreecommitdiffstats
path: root/src/network/kernel/qhostinfo_unix.cpp
blob: 80d386a13d96eb3c9b424d335b2f977088764a73 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// 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

//#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:
    // https://github.com/freebsd/freebsd-src/blob/b3fe5d932264445cbf9a1c4eab01afb6179b499b/lib/libc/resolv/res_state.c#L69
    return;
#elif defined(Q_OS_OPENBSD)
    // OpenBSD automatically refreshes:
    // https://github.com/ligurio/openbsd-src/blob/b1ce0da17da254cc15b8aff25b3d55d3c7a82cec/lib/libc/asr/asr.c#L367
    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;
    while (!resolvconf.atEnd()) {
        const QByteArray lineArray = resolvconf.readLine();
        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