File indexing completed on 2024-04-28 16:43:06

0001 // SPDX-FileCopyrightText: 2022 Michael Lang <criticaltemp@protonmail.com>
0002 //
0003 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0004 
0005 #include "ecurl.h"
0006 #include "modemcontroller.h"
0007 #include "settingsmanager.h"
0008 
0009 #include <netdb.h>
0010 
0011 ECurl::ECurl()
0012 {
0013     curl_global_init(CURL_GLOBAL_DEFAULT);
0014     ares_library_init(ARES_LIB_INIT_ALL);
0015 }
0016 
0017 ECurl::~ECurl()
0018 {
0019     ares_library_cleanup();
0020     curl_global_cleanup();
0021 }
0022 
0023 QByteArray ECurl::networkRequest(const QString &url, const QByteArray &data) const
0024 {
0025     CURL *curl = curl_easy_init();
0026     if (!curl) {
0027         return QByteArray();
0028     }
0029     struct curl_slist *host = NULL;
0030 
0031     // provide a buffer to store errors in
0032     char errbuf[CURL_ERROR_SIZE];
0033     curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
0034 
0035     QString ifaceName = ModemController::instance().ifaceName;
0036     curl_easy_setopt(curl, CURLOPT_INTERFACE, (SL("if!") + ifaceName).toUtf8().constData());
0037 
0038     // only attempt to resolve if not using proxy
0039     if (SettingsManager::self()->mmsProxy().isEmpty()) {
0040         QString dnsServers = ModemController::instance().dnsServers;
0041         CURLcode supported = curl_easy_setopt(curl, CURLOPT_DNS_INTERFACE, ifaceName.toUtf8().constData());
0042 
0043         // use c-ares if curl was built without dns resolver support
0044         if (supported == CURLE_UNKNOWN_OPTION || supported == CURLE_NOT_BUILT_IN) {
0045             ares_channel channel;
0046 
0047             int aresReturn = ares_init(&channel);
0048 
0049             if (aresReturn != ARES_SUCCESS) {
0050                 qDebug() << "Ares init failed:" << ares_strerror(aresReturn);
0051             } else {
0052                 ares_set_local_dev(channel, ifaceName.toUtf8().constData());
0053                 ares_set_servers_csv(channel, dnsServers.toUtf8().constData());
0054 
0055                 CURLU *curlUrl = curl_url();
0056                 curl_url_set(curlUrl, CURLUPART_URL, url.toUtf8().constData(), 0);
0057                 char *hostname = url.toUtf8().data();
0058                 curl_url_get(curlUrl, CURLUPART_HOST, &hostname, 0);
0059                 curl_url_cleanup(curlUrl);
0060 
0061                 char *hostIp = NULL;
0062                 ares_gethostbyname(channel, hostname, AF_UNSPEC, aresResolveCallback, (void *)&hostIp);
0063                 aresResolveWait(channel);
0064 
0065                 if (hostIp == NULL) {
0066                     qDebug() << "Failed to resolve:" << hostname;
0067                 }
0068 
0069                 const char *resolve = QByteArray("+").append(hostname).append(":80:[").append(hostIp).append("]").constData();
0070                 host = curl_slist_append(NULL, resolve);
0071                 curl_easy_setopt(curl, CURLOPT_RESOLVE, host);
0072 
0073                 curl_free(hostname);
0074                 ares_destroy(channel);
0075             }
0076         } else {
0077             curl_easy_setopt(curl, CURLOPT_DNS_SERVERS, dnsServers.toUtf8().constData());
0078         }
0079     }
0080 
0081     curl_easy_setopt(curl, CURLOPT_URL, url.toUtf8().constData());
0082     curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
0083     curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L);
0084     curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 30L);
0085 
0086     // Fake user agent for carrier network compatibility
0087     curl_easy_setopt(curl, CURLOPT_USERAGENT, "Android MmsLib/1.0");
0088 
0089     if (!SettingsManager::self()->mmsProxy().isEmpty()) {
0090         QString proxy = SettingsManager::self()->mmsProxy() + SL(":") + QString::number(SettingsManager::self()->mmsPort());
0091         curl_easy_setopt(curl, CURLOPT_PROXY, proxy.toUtf8().constData());
0092     }
0093 
0094     if (!data.isEmpty()) {
0095         curl_easy_setopt(curl, CURLOPT_POST, 1L);
0096         curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.constData());
0097         curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data.length());
0098 
0099         QString len = QString::number(data.length());
0100         struct curl_slist *headers = NULL;
0101         headers = curl_slist_append(headers, "Content-Type: application/vnd.wap.mms-message");
0102         headers = curl_slist_append(headers, (SL("Content-Length: ") + len).toUtf8().constData());
0103         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
0104     }
0105 
0106     QByteArray response;
0107     const QByteArray *pointer = &response;
0108     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlWriteFunction);
0109     curl_easy_setopt(curl, CURLOPT_WRITEDATA, pointer);
0110 
0111     CURLcode res = curl_easy_perform(curl);
0112     curl_easy_cleanup(curl);
0113     curl_slist_free_all(host);
0114 
0115     if (res != CURLE_OK) {
0116         qDebug() << "Network Error:" << errbuf;
0117         return QByteArray();
0118     } else {
0119         return response;
0120     }
0121 }
0122 
0123 size_t ECurl::curlWriteFunction(char *chunk, size_t size, size_t len, QByteArray *response)
0124 {
0125     response->append(chunk, static_cast<int>(size * len));
0126     return size * len;
0127 }
0128 
0129 void ECurl::aresResolveCallback(void *arg, int status, int timeouts, struct hostent *host)
0130 {
0131     char **hostIp = (char **)arg;
0132 
0133     // when the ares handle is getting destroyed, the 'arg' pointer may not
0134     // be valid so only defer it when we know the 'status' says its fine!
0135     if (!host || status != ARES_SUCCESS) {
0136         return;
0137     }
0138 
0139     char ip[INET6_ADDRSTRLEN];
0140 
0141     for (int i = 0; host->h_addr_list[i]; ++i) {
0142         ares_inet_ntop(host->h_addrtype, host->h_addr_list[i], ip, sizeof(ip));
0143         if (i > 0) {
0144             strcat(*hostIp, ",");
0145             strcat(*hostIp, strdup(ip));
0146         } else {
0147             *hostIp = strdup(ip);
0148         }
0149 
0150         qDebug() << "Found IP for" << host->h_name << *hostIp;
0151     }
0152 
0153     (void)timeouts; // ignored
0154 }
0155 
0156 void ECurl::aresResolveWait(ares_channel channel)
0157 {
0158     qint64 start = QTime::currentTime().msecsSinceStartOfDay();
0159 
0160     for (;;) {
0161         struct timeval *tvp, tv;
0162         fd_set read_fds, write_fds;
0163         int nfds;
0164 
0165         FD_ZERO(&read_fds);
0166         FD_ZERO(&write_fds);
0167         nfds = ares_fds(channel, &read_fds, &write_fds);
0168         if (nfds == 0) {
0169             break;
0170         }
0171         tvp = ares_timeout(channel, NULL, &tv);
0172         int count = select(nfds, &read_fds, &write_fds, NULL, tvp);
0173         if (count == -1) {
0174             qDebug() << "Error waiting for c-ares read/write descriptors";
0175             break;
0176         }
0177         if (QTime::currentTime().msecsSinceStartOfDay() - start > 15 * 1000) {
0178             qDebug() << "Resovler timeout";
0179             break;
0180         }
0181         ares_process(channel, &read_fds, &write_fds);
0182     }
0183 }