// Copyright (C) 2012 Jeremy Lainé // Copyright (C) 2023 Intel Corporation. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qdnslookup_p.h" #include #include #include #include #include // for setSockAddr #include QT_REQUIRE_CONFIG(res_ninit); #include #include #include #if __has_include() # include #endif #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; #if QT_CONFIG(res_setservers) // https://www.ibm.com/docs/en/i/7.3?topic=ssw_ibm_i_73/apis/ressetservers.html // https://docs.oracle.com/cd/E86824_01/html/E54774/res-setservers-3resolv.html static const char *applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port) { if (nameserver.isNull()) return nullptr; union res_sockaddr_union u; setSockaddr(reinterpret_cast(&u.sin), nameserver, port); res_setservers(state, &u, 1); return nullptr; } #else template void setNsMap(T &ext, std::enable_if_t v) { // Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address // See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html // Unneeded since glibc 2.22 (2015), but doesn't hurt to set it // See: https://sourceware.org/git/?p=glibc.git;a=commit;h=2212c1420c92a33b0e0bd9a34938c9814a56c0f7 ext.nsmap[0] = v; } template void setNsMap(T &, ...) { // fallback } template using EnableIfIPv6 = std::enable_if_t; template bool setIpv6NameServer(State *state, EnableIfIPv6()._u._ext.nsaddrs) != 0> addr, quint16 port) { // glibc-like API to set IPv6 name servers struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0]; // nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf if (!ns) { // Memory allocated here will be free()'d in res_close() as we // have done res_init() above. ns = static_cast(calloc(1, sizeof(struct sockaddr_in6))); Q_CHECK_PTR(ns); state->_u._ext.nsaddrs[0] = ns; } setNsMap(state->_u._ext, MAXNS + 1); state->_u._ext.nscount6 = 1; setSockaddr(ns, *addr, port); return true; } template bool setIpv6NameServer(State *, const void *, quint16) { // fallback return false; } static const char *applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port) { if (nameserver.isNull()) return nullptr; state->nscount = 1; state->nsaddr_list[0].sin_family = AF_UNSPEC; if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) { if (!setIpv6NameServer(state, &nameserver, port)) return QDnsLookupPrivate::msgNoIpV6NameServerAdresses; } else { setSockaddr(&state->nsaddr_list[0], nameserver, port); } return nullptr; } #endif // !QT_CONFIG(res_setservers) void QDnsLookupRunnable::query(QDnsLookupReply *reply) { // Initialize state. std::remove_pointer_t state = {}; if (res_ninit(&state) < 0) { reply->error = QDnsLookup::ResolverError; reply->errorString = tr("Resolver initialization failed"); return; } auto guard = qScopeGuard([&] { res_nclose(&state); }); //Check if a nameserver was set. If so, use it if (const char *msg = applyNameServer(&state, nameserver, DnsPort)) { qWarning("%s", msg); reply->error = QDnsLookup::ResolverError; reply->errorString = tr(msg); return; } #ifdef QDNSLOOKUP_DEBUG state.options |= RES_DEBUG; #endif // Perform DNS query. QVarLengthArray buffer(PACKETSZ); std::memset(buffer.data(), 0, buffer.size()); int responseLength = res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size()); if (Q_UNLIKELY(responseLength > PACKETSZ)) { buffer.resize(responseLength); std::memset(buffer.data(), 0, buffer.size()); responseLength = res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size()); if (Q_UNLIKELY(responseLength > buffer.size())) { // Ok, we give up. reply->error = QDnsLookup::ResolverError; reply->errorString.clear(); // We cannot be more specific, alas. return; } } unsigned char *response = buffer.data(); // Check the response header. Though res_nquery returns -1 as a // responseLength in case of error, we still can extract the // exact error code from the response. HEADER *header = (HEADER*)response; switch (header->rcode) { case NOERROR: break; case FORMERR: reply->error = QDnsLookup::InvalidRequestError; reply->errorString = tr("Server could not process query"); return; case SERVFAIL: case NOTIMP: reply->error = QDnsLookup::ServerFailureError; reply->errorString = tr("Server failure"); return; case NXDOMAIN: reply->error = QDnsLookup::NotFoundError; reply->errorString = tr("Non existent domain"); return; case REFUSED: reply->error = QDnsLookup::ServerRefusedError; reply->errorString = tr("Server refused to answer"); return; default: reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid reply received"); return; } // Check the reply is valid. if (responseLength < int(sizeof(HEADER))) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid reply received"); return; } char host[PACKETSZ], answer[PACKETSZ]; qptrdiff offset = sizeof(HEADER); int status; if (ntohs(header->qdcount) == 1) { // Skip the query host, type (2 bytes) and class (2 bytes). status = dn_expand(response, response + responseLength, response + offset, host, sizeof(host)); if (status < 0) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Could not expand domain name"); return; } if (offset + status + 4 >= responseLength) header->qdcount = 0xffff; // invalid reply below else offset += status + 4; } if (ntohs(header->qdcount) > 1) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid reply received"); return; } // Extract results. const int answerCount = ntohs(header->ancount); int answerIndex = 0; while ((offset < responseLength) && (answerIndex < answerCount)) { status = dn_expand(response, response + responseLength, response + offset, host, sizeof(host)); if (status < 0) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Could not expand domain name"); return; } const QString name = QUrl::fromAce(host); offset += status; if (offset + RRFIXEDSZ > responseLength) { // probably just a truncated reply, return what we have return; } const quint16 type = qFromBigEndian(response + offset); const qint16 rrclass = qFromBigEndian(response + offset + 2); const quint32 ttl = qFromBigEndian(response + offset + 4); const quint16 size = qFromBigEndian(response + offset + 8); offset += RRFIXEDSZ; if (offset + size > responseLength) return; // truncated if (rrclass != C_IN) continue; if (type == QDnsLookup::A) { if (size != 4) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid IPv4 address record"); return; } const quint32 addr = qFromBigEndian(response + offset); QDnsHostAddressRecord record; record.d->name = name; record.d->timeToLive = ttl; record.d->value = QHostAddress(addr); reply->hostAddressRecords.append(record); } else if (type == QDnsLookup::AAAA) { if (size != 16) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid IPv6 address record"); return; } QDnsHostAddressRecord record; record.d->name = name; record.d->timeToLive = ttl; record.d->value = QHostAddress(response + offset); reply->hostAddressRecords.append(record); } else if (type == QDnsLookup::CNAME) { status = dn_expand(response, response + responseLength, response + offset, answer, sizeof(answer)); if (status < 0) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid canonical name record"); return; } QDnsDomainNameRecord record; record.d->name = name; record.d->timeToLive = ttl; record.d->value = QUrl::fromAce(answer); reply->canonicalNameRecords.append(record); } else if (type == QDnsLookup::NS) { status = dn_expand(response, response + responseLength, response + offset, answer, sizeof(answer)); if (status < 0) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid name server record"); return; } QDnsDomainNameRecord record; record.d->name = name; record.d->timeToLive = ttl; record.d->value = QUrl::fromAce(answer); reply->nameServerRecords.append(record); } else if (type == QDnsLookup::PTR) { status = dn_expand(response, response + responseLength, response + offset, answer, sizeof(answer)); if (status < 0) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid pointer record"); return; } QDnsDomainNameRecord record; record.d->name = name; record.d->timeToLive = ttl; record.d->value = QUrl::fromAce(answer); reply->pointerRecords.append(record); } else if (type == QDnsLookup::MX) { const quint16 preference = qFromBigEndian(response + offset); status = dn_expand(response, response + responseLength, response + offset + 2, answer, sizeof(answer)); if (status < 0) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid mail exchange record"); return; } QDnsMailExchangeRecord record; record.d->exchange = QUrl::fromAce(answer); record.d->name = name; record.d->preference = preference; record.d->timeToLive = ttl; reply->mailExchangeRecords.append(record); } else if (type == QDnsLookup::SRV) { const quint16 priority = qFromBigEndian(response + offset); const quint16 weight = qFromBigEndian(response + offset + 2); const quint16 port = qFromBigEndian(response + offset + 4); status = dn_expand(response, response + responseLength, response + offset + 6, answer, sizeof(answer)); if (status < 0) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid service record"); return; } QDnsServiceRecord record; record.d->name = name; record.d->target = QUrl::fromAce(answer); record.d->port = port; record.d->priority = priority; record.d->timeToLive = ttl; record.d->weight = weight; reply->serviceRecords.append(record); } else if (type == QDnsLookup::TXT) { QDnsTextRecord record; record.d->name = name; record.d->timeToLive = ttl; qptrdiff txt = offset; while (txt < offset + size) { const unsigned char length = response[txt]; txt++; if (txt + length > offset + size) { reply->error = QDnsLookup::InvalidReplyError; reply->errorString = tr("Invalid text record"); return; } record.d->values << QByteArrayView(response + txt, length).toByteArray(); txt += length; } reply->textRecords.append(record); } offset += size; answerIndex++; } } QT_END_NAMESPACE