File indexing completed on 2024-04-28 15:27:11

0001 /*
0002     SPDX-FileCopyrightText: 2003 Malte Starostik <malte@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <config-kpac.h>
0008 // #include <config-prefix.h>
0009 #include <netdb.h>
0010 #include <unistd.h>
0011 
0012 #include <sys/types.h>
0013 
0014 #if HAVE_NETINET_IN_H
0015 #include <netinet/in.h>
0016 #endif
0017 #include <arpa/nameser.h>
0018 #if HAVE_ARPA_NAMESER8_COMPAT_H
0019 #include <arpa/nameser8_compat.h>
0020 #else
0021 #if HAVE_ARPA_NAMESER_COMPAT_H
0022 #include <arpa/nameser_compat.h>
0023 #endif
0024 #endif
0025 #if HAVE_SYS_PARAM_H
0026 // Basically, the BSDs need this before resolv.h
0027 #include <sys/param.h>
0028 #endif
0029 
0030 #include <resolv.h>
0031 #include <sys/utsname.h>
0032 
0033 #include <QHostInfo>
0034 #include <QProcess>
0035 #include <QStandardPaths>
0036 #include <QTimer>
0037 #include <QUrl>
0038 
0039 #include "moc_discovery.cpp"
0040 #include <KLocalizedString>
0041 
0042 namespace KPAC
0043 {
0044 Discovery::Discovery(QObject *parent)
0045     : Downloader(parent)
0046     , m_helper(new QProcess(this))
0047 {
0048     m_helper->setProcessChannelMode(QProcess::SeparateChannels);
0049     connect(m_helper, &QProcess::readyReadStandardOutput, this, &Discovery::helperOutput);
0050     connect(m_helper, qOverload<int, QProcess::ExitStatus>(&QProcess::finished), this, &Discovery::failed);
0051     m_helper->start(QStringLiteral(KDE_INSTALL_FULL_LIBEXECDIR_KF "/kpac_dhcp_helper"), QStringList());
0052     if (!m_helper->waitForStarted()) {
0053         QTimer::singleShot(0, this, &Discovery::failed);
0054     }
0055 }
0056 
0057 bool Discovery::initDomainName()
0058 {
0059     m_domainName = QHostInfo::localDomainName();
0060     return !m_domainName.isEmpty();
0061 }
0062 
0063 bool Discovery::checkDomain() const
0064 {
0065     // If a domain has a SOA record, don't traverse any higher.
0066     // Returns true if no SOA can be found (domain is "ok" to use)
0067     // Stick to old resolver interface for portability reasons.
0068     union {
0069         HEADER header;
0070         unsigned char buf[PACKETSZ];
0071     } response;
0072 
0073     int len = res_query(m_domainName.toLocal8Bit().constData(), C_IN, T_SOA, response.buf, sizeof(response.buf));
0074     if (len <= int(sizeof(response.header)) || ntohs(response.header.ancount) != 1) {
0075         return true;
0076     }
0077 
0078     unsigned char *pos = response.buf + sizeof(response.header);
0079     unsigned char *end = response.buf + len;
0080 
0081     // skip query section
0082     pos += dn_skipname(pos, end) + QFIXEDSZ;
0083     if (pos >= end) {
0084         return true;
0085     }
0086 
0087     // skip answer domain
0088     pos += dn_skipname(pos, end);
0089     short type;
0090     GETSHORT(type, pos);
0091     return type != T_SOA;
0092 }
0093 
0094 void Discovery::failed()
0095 {
0096     setError(i18n("Could not find a usable proxy configuration script"));
0097 
0098     // If this is the first DNS query, initialize our host name or abort
0099     // on failure. Otherwise abort if the current domain (which was already
0100     // queried for a host called "wpad" contains a SOA record)
0101     const bool firstQuery = m_domainName.isEmpty();
0102     if ((firstQuery && !initDomainName()) || (!firstQuery && !checkDomain())) {
0103         Q_EMIT result(false);
0104         return;
0105     }
0106 
0107     const int dot = m_domainName.indexOf(QLatin1Char('.'));
0108     if (dot > -1 || firstQuery) {
0109         const QString address = QLatin1String("http://wpad.") + m_domainName + QLatin1String("/wpad.dat");
0110         if (dot > -1) {
0111             m_domainName.remove(0, dot + 1); // remove one domain level
0112         }
0113         download(QUrl(address));
0114         return;
0115     }
0116 
0117     Q_EMIT result(false);
0118 }
0119 
0120 void Discovery::helperOutput()
0121 {
0122     m_helper->disconnect(this);
0123     const QByteArray line = m_helper->readLine();
0124     const QUrl url(QString::fromLocal8Bit(line.constData(), line.length()).trimmed());
0125     download(url);
0126 }
0127 }