File indexing completed on 2025-01-05 04:37:28

0001 /*
0002     SPDX-FileCopyrightText: 2005-2007 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <cstdlib>
0008 
0009 #include <QDir>
0010 #include <QDomElement>
0011 #include <QNetworkRequest>
0012 #include <QStringList>
0013 
0014 #include <KIO/StoredTransferJob>
0015 #include <KLocalizedString>
0016 
0017 #include "httprequest.h"
0018 #include "soap.h"
0019 #include "upnpdescriptionparser.h"
0020 #include "upnprouter.h"
0021 #include <peer/accessmanager.h>
0022 #include <torrent/globals.h>
0023 #include <util/array.h>
0024 #include <util/error.h>
0025 #include <util/fileops.h>
0026 #include <util/functions.h>
0027 #include <util/log.h>
0028 #include <util/waitjob.h>
0029 #include <version.h>
0030 
0031 using namespace net;
0032 
0033 namespace bt
0034 {
0035 struct Forwarding {
0036     net::Port port;
0037     HTTPRequest *pending_req;
0038     const UPnPService *service;
0039 };
0040 
0041 class UPnPRouter::UPnPRouterPrivate
0042 {
0043 public:
0044     UPnPRouterPrivate(const QString &server, const QUrl &location, bool verbose, UPnPRouter *parent);
0045     ~UPnPRouterPrivate();
0046 
0047     HTTPRequest *sendSoapQuery(const QString &query, const QString &soapact, const QString &controlurl, bool at_exit = false);
0048     void forward(const UPnPService *srv, const net::Port &port);
0049     void undoForward(const UPnPService *srv, const net::Port &port, bt::WaitJob *waitjob);
0050     void httpRequestDone(HTTPRequest *r, bool erase_fwd);
0051     void getExternalIP();
0052 
0053 public:
0054     QString server;
0055     QUrl location;
0056     UPnPDeviceDescription desc;
0057     QList<UPnPService> services;
0058     QList<Forwarding> fwds;
0059     QList<HTTPRequest *> active_reqs;
0060     QString error;
0061     bool verbose;
0062     UPnPRouter *parent;
0063     QString external_ip;
0064 };
0065 
0066 ////////////////////////////////////
0067 
0068 UPnPService::UPnPService()
0069 {
0070 }
0071 
0072 UPnPService::UPnPService(const UPnPService &s)
0073 {
0074     this->servicetype = s.servicetype;
0075     this->controlurl = s.controlurl;
0076     this->eventsuburl = s.eventsuburl;
0077     this->serviceid = s.serviceid;
0078     this->scpdurl = s.scpdurl;
0079 }
0080 
0081 void UPnPService::setProperty(const QString &name, const QString &value)
0082 {
0083     if (name == "serviceType")
0084         servicetype = value;
0085     else if (name == "controlURL")
0086         controlurl = value;
0087     else if (name == "eventSubURL")
0088         eventsuburl = value;
0089     else if (name == "SCPDURL")
0090         scpdurl = value;
0091     else if (name == "serviceId")
0092         serviceid = value;
0093 }
0094 
0095 void UPnPService::clear()
0096 {
0097     servicetype = controlurl = eventsuburl = scpdurl = serviceid = "";
0098 }
0099 
0100 UPnPService &UPnPService::operator=(const UPnPService &s)
0101 {
0102     this->servicetype = s.servicetype;
0103     this->controlurl = s.controlurl;
0104     this->eventsuburl = s.eventsuburl;
0105     this->serviceid = s.serviceid;
0106     this->scpdurl = s.scpdurl;
0107     return *this;
0108 }
0109 
0110 ///////////////////////////////////////
0111 
0112 void UPnPDeviceDescription::setProperty(const QString &name, const QString &value)
0113 {
0114     if (name == "friendlyName")
0115         friendlyName = value;
0116     else if (name == "manufacturer")
0117         manufacturer = value;
0118     else if (name == "modelDescription")
0119         modelDescription = value;
0120     else if (name == "modelName")
0121         modelName = value;
0122     else if (name == "modelNumber")
0123         modelNumber = value;
0124 }
0125 
0126 ///////////////////////////////////////
0127 
0128 UPnPRouter::UPnPRouter(const QString &server, const QUrl &location, bool verbose)
0129     : d(new UPnPRouterPrivate(server, location, verbose, this))
0130 {
0131 }
0132 
0133 UPnPRouter::~UPnPRouter()
0134 {
0135     delete d;
0136 }
0137 
0138 void UPnPRouter::addService(UPnPService s)
0139 {
0140     for (const UPnPService &os : std::as_const(d->services)) {
0141         if (s.servicetype == os.servicetype)
0142             return;
0143     }
0144     if (s.controlurl.startsWith("/")) {
0145         s.controlurl = "http://" + d->location.host() + ":" + QString::number(d->location.port()) + s.controlurl;
0146     }
0147     if (s.eventsuburl.startsWith("/")) {
0148         s.controlurl = "http://" + d->location.host() + ":" + QString::number(d->location.port()) + s.eventsuburl;
0149     }
0150     d->services.append(s);
0151 }
0152 
0153 void UPnPRouter::downloadFinished(KJob *j)
0154 {
0155     if (j->error()) {
0156         d->error = i18n("Failed to download %1: %2", d->location.toDisplayString(), j->errorString());
0157         Out(SYS_PNP | LOG_IMPORTANT) << d->error << endl;
0158         return;
0159     }
0160 
0161     KIO::StoredTransferJob *st = (KIO::StoredTransferJob *)j;
0162     // load in the file (target is always local)
0163     UPnPDescriptionParser desc_parse;
0164     bool ret = desc_parse.parse(st->data(), this);
0165     if (!ret) {
0166         d->error = i18n("Error parsing router description.");
0167     }
0168 
0169     xmlFileDownloaded(this, ret);
0170     d->getExternalIP();
0171 }
0172 
0173 void UPnPRouter::downloadXMLFile()
0174 {
0175     d->error = QString();
0176     // downlaod XML description into a temporary file in /tmp
0177     Out(SYS_PNP | LOG_DEBUG) << "Downloading XML file " << d->location << endl;
0178     KIO::Job *job = KIO::storedGet(d->location, KIO::NoReload, KIO::Overwrite | KIO::HideProgressInfo);
0179     connect(job, &KIO::Job::result, this, &UPnPRouter::downloadFinished);
0180 }
0181 
0182 void UPnPRouter::forward(const net::Port &port)
0183 {
0184     if (!d->error.isEmpty()) {
0185         d->error = QString();
0186         stateChanged();
0187     }
0188 
0189     bool found = false;
0190     Out(SYS_PNP | LOG_NOTICE) << "Forwarding port " << port.number << " (" << (port.proto == UDP ? "UDP" : "TCP") << ")" << endl;
0191     // first find the right service
0192     for (const UPnPService &s : std::as_const(d->services)) {
0193         if (s.servicetype.contains("WANIPConnection") || s.servicetype.contains("WANPPPConnection")) {
0194             d->forward(&s, port);
0195             found = true;
0196         }
0197     }
0198 
0199     if (!found) {
0200         d->error = i18n("Forwarding failed:\nDevice does not have a WANIPConnection or WANPPPConnection.");
0201         Out(SYS_PNP | LOG_IMPORTANT) << d->error << endl;
0202         stateChanged();
0203     }
0204 }
0205 
0206 void UPnPRouter::undoForward(const net::Port &port, bt::WaitJob *waitjob)
0207 {
0208     Out(SYS_PNP | LOG_NOTICE) << "Undoing forward of port " << port.number << " (" << (port.proto == UDP ? "UDP" : "TCP") << ")" << endl;
0209 
0210     QList<Forwarding>::iterator itr = d->fwds.begin();
0211     while (itr != d->fwds.end()) {
0212         Forwarding &wd = *itr;
0213         if (wd.port == port) {
0214             d->undoForward(wd.service, wd.port, waitjob);
0215             itr = d->fwds.erase(itr);
0216         } else {
0217             ++itr;
0218         }
0219     }
0220 
0221     stateChanged();
0222 }
0223 
0224 void UPnPRouter::forwardResult(HTTPRequest *r)
0225 {
0226     if (r->succeeded()) {
0227         d->httpRequestDone(r, false);
0228     } else {
0229         d->httpRequestDone(r, true);
0230         if (d->fwds.count() == 0) {
0231             d->error = r->errorString();
0232             stateChanged();
0233         }
0234     }
0235 }
0236 
0237 void UPnPRouter::undoForwardResult(HTTPRequest *r)
0238 {
0239     d->active_reqs.removeAll(r);
0240     r->deleteLater();
0241 }
0242 
0243 void UPnPRouter::getExternalIPResult(HTTPRequest *r)
0244 {
0245     d->active_reqs.removeAll(r);
0246     if (r->succeeded()) {
0247         QDomDocument doc;
0248         if (!doc.setContent(r->replyData())) {
0249             Out(SYS_PNP | LOG_DEBUG) << "UPnP: GetExternalIP failed: invalid reply" << endl;
0250         } else {
0251             QDomNodeList nodes = doc.elementsByTagName("NewExternalIPAddress");
0252             if (nodes.count() > 0) {
0253                 d->external_ip = nodes.item(0).firstChild().nodeValue();
0254                 Out(SYS_PNP | LOG_DEBUG) << "UPnP: External IP: " << d->external_ip << endl;
0255                 // Keep track of external IP so AccessManager can block it, makes no sense to connect to ourselves
0256                 AccessManager::instance().addExternalIP(d->external_ip);
0257             } else
0258                 Out(SYS_PNP | LOG_DEBUG) << "UPnP: GetExternalIP failed: no IP address returned" << endl;
0259         }
0260     } else {
0261         Out(SYS_PNP | LOG_DEBUG) << "UPnP: GetExternalIP failed: " << r->errorString() << endl;
0262     }
0263 
0264     r->deleteLater();
0265 }
0266 
0267 QString UPnPRouter::getExternalIP() const
0268 {
0269     return d->external_ip;
0270 }
0271 
0272 #if 0
0273 
0274 void UPnPRouter::isPortForwarded(const net::Port& port)
0275 {
0276     // first find the right service
0277     QList<UPnPService>::iterator i = findPortForwardingService();
0278     if (i == services.end())
0279         throw Error(i18n("Cannot find port forwarding service in the device's description."));
0280 
0281     // add all the arguments for the command
0282     QList<SOAP::Arg> args;
0283     SOAP::Arg a;
0284     a.element = "NewRemoteHost";
0285     args.append(a);
0286 
0287     // the external port
0288     a.element = "NewExternalPort";
0289     a.value = QString::number(port.number);
0290     args.append(a);
0291 
0292     // the protocol
0293     a.element = "NewProtocol";
0294     a.value = port.proto == TCP ? "TCP" : "UDP";
0295     args.append(a);
0296 
0297     UPnPService& s = *i;
0298     QString action = "GetSpecificPortMappingEntry";
0299     QString comm = SOAP::createCommand(action, s.servicetype, args);
0300     sendSoapQuery(comm, s.servicetype + "#" + action, s.controlurl);
0301 }
0302 #endif
0303 
0304 void UPnPRouter::setVerbose(bool v)
0305 {
0306     d->verbose = v;
0307 }
0308 
0309 QString UPnPRouter::getServer() const
0310 {
0311     return d->server;
0312 }
0313 
0314 QUrl UPnPRouter::getLocation() const
0315 {
0316     return d->location;
0317 }
0318 
0319 UPnPDeviceDescription &UPnPRouter::getDescription()
0320 {
0321     return d->desc;
0322 }
0323 
0324 const UPnPDeviceDescription &UPnPRouter::getDescription() const
0325 {
0326     return d->desc;
0327 }
0328 
0329 QString UPnPRouter::getError() const
0330 {
0331     return d->error;
0332 }
0333 
0334 void UPnPRouter::visit(UPnPRouter::Visitor *visitor) const
0335 {
0336     for (const Forwarding &fwd : std::as_const(d->fwds)) {
0337         visitor->forwarding(fwd.port, fwd.pending_req != nullptr, fwd.service);
0338     }
0339 }
0340 
0341 ////////////////////////////////////
0342 
0343 UPnPRouter::UPnPRouterPrivate::UPnPRouterPrivate(const QString &server, const QUrl &location, bool verbose, UPnPRouter *parent)
0344     : server(server)
0345     , location(location)
0346     , verbose(verbose)
0347     , parent(parent)
0348 {
0349 }
0350 
0351 UPnPRouter::UPnPRouterPrivate::~UPnPRouterPrivate()
0352 {
0353     for (HTTPRequest *r : std::as_const(active_reqs)) {
0354         r->deleteLater();
0355     }
0356 }
0357 
0358 HTTPRequest *UPnPRouter::UPnPRouterPrivate::sendSoapQuery(const QString &query, const QString &soapact, const QString &controlurl, bool at_exit)
0359 {
0360     // if port is not set, 0 will be returned
0361     // thanks to Diego R. Brogna for spotting this bug
0362     if (location.port() <= 0)
0363         location.setPort(80);
0364 
0365     QUrl ctrlurl(controlurl);
0366     QString host = !ctrlurl.host().isEmpty() ? ctrlurl.host() : location.host();
0367     bt::Uint16 port = ctrlurl.port() != -1 ? ctrlurl.port() : location.port(80);
0368 
0369     QNetworkRequest networkReq;
0370     networkReq.setUrl(ctrlurl);
0371     networkReq.setRawHeader("Host", host.toLatin1() + QByteArrayLiteral(":") + QByteArray::number(port));
0372     networkReq.setRawHeader("User-Agent", bt::GetVersionString().toLatin1());
0373     networkReq.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("text/xml"));
0374     networkReq.setRawHeader("SOAPAction", soapact.toLatin1());
0375 
0376     HTTPRequest *r = new HTTPRequest(networkReq, query, host, port, verbose);
0377     if (!at_exit) {
0378         // Only listen for results when we are not exiting
0379         active_reqs.append(r);
0380     }
0381     r->start();
0382     return r;
0383 }
0384 
0385 void UPnPRouter::UPnPRouterPrivate::forward(const UPnPService *srv, const net::Port &port)
0386 {
0387     // add all the arguments for the command
0388     QList<SOAP::Arg> args;
0389     SOAP::Arg a;
0390 
0391     // the external port
0392     a.element = "NewExternalPort";
0393     a.value = QString::number(port.number);
0394     args.append(a);
0395 
0396     // the protocol
0397     a.element = "NewProtocol";
0398     a.value = port.proto == net::TCP ? "TCP" : "UDP";
0399     args.append(a);
0400 
0401     // the local port
0402     a.element = "NewInternalPort";
0403     a.value = QString::number(port.number);
0404     args.append(a);
0405 
0406     // the local IP address
0407     a.element = "NewInternalClient";
0408     a.value = "$LOCAL_IP"; // will be replaced by our local ip in HTTPRequest
0409     args.append(a);
0410 
0411     a.element = "NewEnabled";
0412     a.value = "1";
0413     args.append(a);
0414 
0415     a.element = "NewPortMappingDescription";
0416     static Uint32 cnt = 0;
0417     a.value = QString("KTorrent UPNP %1").arg(cnt++); // TODO: change this
0418     args.append(a);
0419 
0420     a.element = "NewLeaseDuration";
0421     a.value = "0";
0422     args.append(a);
0423 
0424     QString action = "AddPortMapping";
0425     QString comm = SOAP::createCommand(action, srv->servicetype, args);
0426 
0427     Forwarding fw = {port, nullptr, srv};
0428     // erase old forwarding if one exists
0429     QList<Forwarding>::iterator itr = fwds.begin();
0430     while (itr != fwds.end()) {
0431         Forwarding &fwo = *itr;
0432         if (fwo.port == port && fwo.service == srv)
0433             itr = fwds.erase(itr);
0434         else
0435             ++itr;
0436     }
0437 
0438     fw.pending_req = sendSoapQuery(comm, srv->servicetype + "#" + action, srv->controlurl);
0439     connect(fw.pending_req, &HTTPRequest::result, parent, &UPnPRouter::forwardResult);
0440     fwds.append(fw);
0441 }
0442 
0443 void UPnPRouter::UPnPRouterPrivate::undoForward(const UPnPService *srv, const net::Port &port, bt::WaitJob *waitjob)
0444 {
0445     // add all the arguments for the command
0446     QList<SOAP::Arg> args;
0447     SOAP::Arg a;
0448     // a.element = "NewRemoteHost";
0449     // args.append(a);
0450 
0451     // the external port
0452     a.element = "NewExternalPort";
0453     a.value = QString::number(port.number);
0454     args.append(a);
0455 
0456     // the protocol
0457     a.element = "NewProtocol";
0458     a.value = port.proto == net::TCP ? "TCP" : "UDP";
0459     args.append(a);
0460 
0461     QString action = "DeletePortMapping";
0462     QString comm = SOAP::createCommand(action, srv->servicetype, args);
0463     HTTPRequest *r = sendSoapQuery(comm, srv->servicetype + "#" + action, srv->controlurl, waitjob != nullptr);
0464 
0465     if (waitjob)
0466         waitjob->addExitOperation(r);
0467     else
0468         connect(r, &HTTPRequest::result, parent, &UPnPRouter::undoForwardResult);
0469 }
0470 
0471 void UPnPRouter::UPnPRouterPrivate::getExternalIP()
0472 {
0473     for (const UPnPService &s : std::as_const(services)) {
0474         if (s.servicetype.contains("WANIPConnection") || s.servicetype.contains("WANPPPConnection")) {
0475             QString action = "GetExternalIPAddress";
0476             QString comm = SOAP::createCommand(action, s.servicetype);
0477             HTTPRequest *r = sendSoapQuery(comm, s.servicetype + "#" + action, s.controlurl);
0478             connect(r, &HTTPRequest::result, parent, &UPnPRouter::getExternalIPResult);
0479             break;
0480         }
0481     }
0482 }
0483 
0484 void UPnPRouter::UPnPRouterPrivate::httpRequestDone(HTTPRequest *r, bool erase_fwd)
0485 {
0486     int idx = 0;
0487     bool found = false;
0488     for (const Forwarding &fw : std::as_const(fwds)) {
0489         if (fw.pending_req == r) {
0490             found = true;
0491             break;
0492         }
0493         idx++;
0494     }
0495 
0496     if (found) {
0497         Forwarding &fw = fwds[idx];
0498         fw.pending_req = nullptr;
0499         if (erase_fwd)
0500             fwds.removeAt(idx);
0501     }
0502 
0503     active_reqs.removeAll(r);
0504     r->deleteLater();
0505 }
0506 }
0507 
0508 #include "moc_upnprouter.cpp"