/*************************************************************************** * Copyright (C) 2005 by Joris Guisson * * joris.guisson@gmail.com * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "upnprouter.h" #include "upnpdescriptionparser.h" #include "soap.h" using namespace bt; using namespace net; namespace kt { UPnPService::UPnPService() { } UPnPService::UPnPService(const UPnPService & s) { this->servicetype = s.servicetype; this->controlurl = s.controlurl; this->eventsuburl = s.eventsuburl; this->serviceid = s.serviceid; this->scpdurl = s.scpdurl; } void UPnPService::setProperty(const TQString & name,const TQString & value) { if (name == "serviceType") servicetype = value; else if (name == "controlURL") controlurl = value; else if (name == "eventSubURL") eventsuburl = value; else if (name == "SCPDURL") scpdurl = value; else if (name == "serviceId") serviceid = value; } void UPnPService::clear() { servicetype = controlurl = eventsuburl = scpdurl = serviceid = ""; } void UPnPService::debugPrintData() { Out(SYS_PNP|LOG_DEBUG) << " servicetype = " << servicetype << endl; Out(SYS_PNP|LOG_DEBUG) << " controlurl = " << controlurl << endl; Out(SYS_PNP|LOG_DEBUG) << " eventsuburl = " << eventsuburl << endl; Out(SYS_PNP|LOG_DEBUG) << " scpdurl = " << scpdurl << endl; Out(SYS_PNP|LOG_DEBUG) << " serviceid = " << serviceid << endl; } UPnPService & UPnPService::operator = (const UPnPService & s) { this->servicetype = s.servicetype; this->controlurl = s.controlurl; this->eventsuburl = s.eventsuburl; this->serviceid = s.serviceid; this->scpdurl = s.scpdurl; return *this; } /////////////////////////////////////// void UPnPDeviceDescription::setProperty(const TQString & name,const TQString & value) { if (name == "friendlyName") friendlyName = value; else if (name == "manufacturer") manufacturer = value; else if (name == "modelDescription") modelDescription = value; else if (name == "modelName") modelName = value; else if (name == "modelNumber") modelNumber == value; } /////////////////////////////////////// UPnPRouter::UPnPRouter(const TQString & server,const KURL & location,bool verbose) : server(server),location(location),verbose(verbose) { // make the tmp_file unique, current time * a random number should be enough tmp_file = TQString("/tmp/ktorrent_upnp_description-%1.xml").arg(bt::GetCurrentTime() * rand()); } UPnPRouter::~UPnPRouter() { TQValueList::iterator i = active_reqs.begin(); while (i != active_reqs.end()) { (*i)->deleteLater(); i++; } } void UPnPRouter::addService(const UPnPService & s) { TQValueList::iterator i = services.begin(); while (i != services.end()) { UPnPService & os = *i; if (s.servicetype == os.servicetype) return; i++; } services.append(s); } void UPnPRouter::downloadFinished(KIO::Job* j) { if (j->error()) { Out(SYS_PNP|LOG_IMPORTANT) << "Failed to download " << location << " : " << j->errorString() << endl; return; } TQString target = tmp_file; // load in the file (target is always local) UPnPDescriptionParser desc_parse; bool ret = desc_parse.parse(target,this); if (!ret) { Out(SYS_PNP|LOG_IMPORTANT) << "Error parsing router description !" << endl; TQString dest = TDEGlobal::dirs()->saveLocation("data","ktorrent") + "upnp_failure"; KIO::file_copy(target,dest,-1,true,false,false); } else { if (verbose) debugPrintData(); } xmlFileDownloaded(this,ret); bt::Delete(target); } void UPnPRouter::downloadXMLFile() { // downlaod XML description into a temporary file in /tmp KIO::Job* job = KIO::file_copy(location,tmp_file,-1,true,false,false); connect(job,TQT_SIGNAL(result(KIO::Job *)),this,TQT_SLOT(downloadFinished( KIO::Job* ))); } void UPnPRouter::debugPrintData() { Out(SYS_PNP|LOG_DEBUG) << "UPnPRouter : " << endl; Out(SYS_PNP|LOG_DEBUG) << "Friendly name = " << desc.friendlyName << endl; Out(SYS_PNP|LOG_DEBUG) << "Manufacterer = " << desc.manufacturer << endl; Out(SYS_PNP|LOG_DEBUG) << "Model description = " << desc.modelDescription << endl; Out(SYS_PNP|LOG_DEBUG) << "Model name = " << desc.modelName << endl; Out(SYS_PNP|LOG_DEBUG) << "Model number = " << desc.modelNumber << endl; for (TQValueList::iterator i = services.begin();i != services.end();i++) { UPnPService & s = *i; Out() << "Service : " << endl; s.debugPrintData(); Out(SYS_PNP|LOG_DEBUG) << "Done" << endl; } Out(SYS_PNP|LOG_DEBUG) << "Done" << endl; } void UPnPRouter::forward(UPnPService* srv,const net::Port & port) { // add all the arguments for the command TQValueList args; SOAP::Arg a; a.element = "NewRemoteHost"; args.append(a); // the external port a.element = "NewExternalPort"; a.value = TQString::number(port.number); args.append(a); // the protocol a.element = "NewProtocol"; a.value = port.proto == TCP ? "TCP" : "UDP"; args.append(a); // the local port a.element = "NewInternalPort"; a.value = TQString::number(port.number); args.append(a); // the local IP address a.element = "NewInternalClient"; a.value = "$LOCAL_IP";// will be replaced by our local ip in bt::HTTPRequest args.append(a); a.element = "NewEnabled"; a.value = "1"; args.append(a); a.element = "NewPortMappingDescription"; static Uint32 cnt = 0; a.value = TQString("KTorrent UPNP %1").arg(cnt++); // TODO: change this args.append(a); a.element = "NewLeaseDuration"; a.value = "0"; args.append(a); TQString action = "AddPortMapping"; TQString comm = SOAP::createCommand(action,srv->servicetype,args); Forwarding fw = {port,0,srv}; // erase old forwarding if one exists TQValueList::iterator itr = fwds.begin(); while (itr != fwds.end()) { Forwarding & fwo = *itr; if (fwo.port == port && fwo.service == srv) itr = fwds.erase(itr); else itr++; } fw.pending_req = sendSoapQuery(comm,srv->servicetype + "#" + action,srv->controlurl); fwds.append(fw); } void UPnPRouter::forward(const net::Port & port) { Out(SYS_PNP|LOG_NOTICE) << "Forwarding port " << port.number << " (" << (port.proto == UDP ? "UDP" : "TCP") << ")" << endl; // first find the right service TQValueList::iterator i = services.begin(); while (i != services.end()) { UPnPService & s = *i; if (s.servicetype == "urn:schemas-upnp-org:service:WANIPConnection:1" || s.servicetype == "urn:schemas-upnp-org:service:WANPPPConnection:1") { forward(&s,port); } i++; } } void UPnPRouter::undoForward(UPnPService* srv,const net::Port & port,bt::WaitJob* waitjob) { // add all the arguments for the command TQValueList args; SOAP::Arg a; a.element = "NewRemoteHost"; args.append(a); // the external port a.element = "NewExternalPort"; a.value = TQString::number(port.number); args.append(a); // the protocol a.element = "NewProtocol"; a.value = port.proto == TCP ? "TCP" : "UDP"; args.append(a); TQString action = "DeletePortMapping"; TQString comm = SOAP::createCommand(action,srv->servicetype,args); bt::HTTPRequest* r = sendSoapQuery(comm,srv->servicetype + "#" + action,srv->controlurl,waitjob != 0); if (waitjob) waitjob->addExitOperation(r); updateGUI(); } void UPnPRouter::undoForward(const net::Port & port,bt::WaitJob* waitjob) { Out(SYS_PNP|LOG_NOTICE) << "Undoing forward of port " << port.number << " (" << (port.proto == UDP ? "UDP" : "TCP") << ")" << endl; TQValueList::iterator itr = fwds.begin(); while (itr != fwds.end()) { Forwarding & wd = *itr; if (wd.port == port) { undoForward(wd.service,wd.port,waitjob); itr = fwds.erase(itr); } else { itr++; } } } bt::HTTPRequest* UPnPRouter::sendSoapQuery(const TQString & query,const TQString & soapact,const TQString & controlurl,bool at_exit) { // if port is not set, 0 will be returned // thanks to Diego R. Brogna for spotting this bug if (location.port()==0) location.setPort(80); TQString http_hdr = TQString( "POST %1 HTTP/1.1\r\n" "HOST: %2:%3\r\n" "Content-length: $CONTENT_LENGTH\r\n" "Content-Type: text/xml\r\n" "SOAPAction: \"%4\"\r\n" "\r\n").arg(controlurl).arg(location.host()).arg(location.port()).arg(soapact); HTTPRequest* r = new HTTPRequest(http_hdr,query,location.host(),location.port(),verbose); connect(r,TQT_SIGNAL(replyError(bt::HTTPRequest* ,const TQString& )), this,TQT_SLOT(onReplyError(bt::HTTPRequest* ,const TQString& ))); connect(r,TQT_SIGNAL(replyOK(bt::HTTPRequest* ,const TQString& )), this,TQT_SLOT(onReplyOK(bt::HTTPRequest* ,const TQString& ))); connect(r,TQT_SIGNAL(error(bt::HTTPRequest*, bool )), this,TQT_SLOT(onError(bt::HTTPRequest*, bool ))); r->start(); if (!at_exit) active_reqs.append(r); return r; } void UPnPRouter::httpRequestDone(bt::HTTPRequest* r,bool erase_fwd) { TQValueList::iterator i = fwds.begin(); while (i != fwds.end()) { Forwarding & fw = *i; if (fw.pending_req == r) { fw.pending_req = 0; if (erase_fwd) fwds.erase(i); break; } i++; } updateGUI(); active_reqs.remove(r); r->deleteLater(); } void UPnPRouter::onReplyOK(bt::HTTPRequest* r,const TQString &) { if (verbose) Out(SYS_PNP|LOG_NOTICE) << "UPnPRouter : OK" << endl; httpRequestDone(r,false); } void UPnPRouter::onReplyError(bt::HTTPRequest* r,const TQString &) { if (verbose) Out(SYS_PNP|LOG_IMPORTANT) << "UPnPRouter : Error" << endl; httpRequestDone(r,true); } void UPnPRouter::onError(bt::HTTPRequest* r,bool) { httpRequestDone(r,true); } #if 0 TQValueList::iterator UPnPRouter::findPortForwardingService() { TQValueList::iterator i = services.begin(); while (i != services.end()) { UPnPService & s = *i; if (s.servicetype == "urn:schemas-upnp-org:service:WANIPConnection:1" || s.servicetype == "urn:schemas-upnp-org:service:WANPPPConnection:1") return i; i++; } return services.end(); } void UPnPRouter::getExternalIP() { // first find the right service TQValueList::iterator i = findPortForwardingService(); if (i == services.end()) throw Error(i18n("Cannot find port forwarding service in the device's description!")); UPnPService & s = *i; TQString action = "GetExternalIPAddress"; TQString comm = SOAP::createCommand(action,s.servicetype); sendSoapQuery(comm,s.servicetype + "#" + action,s.controlurl); } void UPnPRouter::isPortForwarded(const net::Port & port) { // first find the right service TQValueList::iterator i = findPortForwardingService(); if (i == services.end()) throw Error(i18n("Cannot find port forwarding service in the device's description!")); // add all the arguments for the command TQValueList args; SOAP::Arg a; a.element = "NewRemoteHost"; args.append(a); // the external port a.element = "NewExternalPort"; a.value = TQString::number(port.number); args.append(a); // the protocol a.element = "NewProtocol"; a.value = port.proto == TCP ? "TCP" : "UDP"; args.append(a); UPnPService & s = *i; TQString action = "GetSpecificPortMappingEntry"; TQString comm = SOAP::createCommand(action,s.servicetype,args); sendSoapQuery(comm,s.servicetype + "#" + action,s.controlurl); } #endif } #include "upnprouter.moc"