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"