/*************************************************************************** * 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 "httprequest.h" #include "upnprouter.h" #include "upnpdescriptionparser.h" #include "soap.h" #include "../functions.h" #include #include #include 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 QString & name,const QString & 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() { kdDebug() << " servicetype = " << servicetype << endl; kdDebug() << " controlurl = " << controlurl << endl; kdDebug() << " eventsuburl = " << eventsuburl << endl; kdDebug() << " scpdurl = " << scpdurl << endl; kdDebug() << " 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 QString & name,const QString & 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 QString & server,const KURL & location,bool verbose) : server(server),location(location),verbose(verbose) { forwardedPortList = new ForwardPortList(); // make the tmp_file unique, current time * a random number should be enough tmp_file = QString("/tmp/tork_upnp_description-%1.xml").arg(bt::GetCurrentTime() * rand()); } UPnPRouter::~UPnPRouter() { QValueList::iterator i = active_reqs.begin(); while (i != active_reqs.end()) { (*i)->deleteLater(); i++; } } void UPnPRouter::addService(const UPnPService & s) { QValueList::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()) { kdDebug() << "Failed to download " << location << " : " << j->errorString() << endl; return; } QString target = tmp_file; // load in the file (target is always local) UPnPDescriptionParser desc_parse; bool ret = desc_parse.parse(target,this); if (!ret) { kdDebug() << "Error parsing router description !" << endl; QString dest = KGlobal::dirs()->saveLocation("data","tork") + "upnp_failure"; KIO::file_copy(target,dest,-1,true,false,false); } else { if (verbose) debugPrintData(); } xmlFileDownloaded(this,ret); remove(QFile::encodeName(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,SIGNAL(result(KIO::Job *)),this,SLOT(downloadFinished( KIO::Job* ))); } void UPnPRouter::debugPrintData() { kdDebug() << "UPnPRouter : " << endl; kdDebug() << "Friendly name = " << desc.friendlyName << endl; kdDebug() << "Manufacterer = " << desc.manufacturer << endl; kdDebug() << "Model description = " << desc.modelDescription << endl; kdDebug() << "Model name = " << desc.modelName << endl; kdDebug() << "Model number = " << desc.modelNumber << endl; for (QValueList::iterator i = services.begin();i != services.end();i++) { UPnPService & s = *i; kdDebug() << "Service : " << endl; s.debugPrintData(); kdDebug() << "Done" << endl; } kdDebug() << "Done" << endl; } void UPnPRouter::forward(UPnPService* srv,const net::Port & externalport, const net::Port & internalport) { // add all the arguments for the command QValueList args; SOAP::Arg a; a.element = "NewRemoteHost"; args.append(a); // the external port a.element = "NewExternalPort"; a.value = QString::number(externalport.number); args.append(a); // the protocol a.element = "NewProtocol"; a.value = externalport.proto == TCP ? "TCP" : "UDP"; args.append(a); // the local port a.element = "NewInternalPort"; if (internalport.number) a.value = QString::number(internalport.number); else a.value = QString::number(externalport.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 = QString("TorK UPNP %1").arg(cnt++); // TODO: change this args.append(a); a.element = "NewLeaseDuration"; a.value = "0"; args.append(a); QString action = "AddPortMapping"; QString comm = SOAP::createCommand(action,srv->servicetype,args); Forwarding fw = {externalport,internalport,0,srv}; // erase old forwarding if one exists QValueList::iterator itr = fwds.begin(); while (itr != fwds.end()) { Forwarding & fwo = *itr; if (fwo.extport == externalport && fwo.intport == internalport && fwo.service == srv) itr = fwds.erase(itr); else itr++; } fw.pending_req = sendSoapQuery(comm,srv->servicetype + "#" + action,srv->controlurl, true); fwds.append(fw); //Track the forwarding request so we can know whether it was successful or not //and keep a map of successfully forwarded ports in forwardedPorts ForwardingRequest fwreq = {externalport,internalport,fw.pending_req}; QValueList::iterator itrq = fwdreqs.begin(); while (itrq != fwdreqs.end()) { ForwardingRequest & fwo = *itrq; if (fwo.extport == externalport && fwo.intport == internalport) itrq = fwdreqs.erase(itrq); else itrq++; } fwdreqs.append(fwreq); } void UPnPRouter::forward(const net::Port & externalport, const net::Port & internalport, bool force) { if ((forwardedPortList->contains(net::ForwardPort(externalport.number, internalport.number, net::TCP,false))) && (!force)) return; kdDebug() << "Forwarding port " << externalport.number << " (" << (externalport.proto == UDP ? "UDP" : "TCP") << ")" << endl; // first find the right service QValueList::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") { if (internalport.number) forward(&s,externalport,internalport); else forward(&s,externalport); } i++; } } void UPnPRouter::undoForward(UPnPService* srv,const net::Port & extport, const net::Port & intport,bt::WaitJob* waitjob) { // add all the arguments for the command QValueList args; SOAP::Arg a; a.element = "NewRemoteHost"; args.append(a); // the external port a.element = "NewExternalPort"; a.value = QString::number(extport.number); args.append(a); // the protocol a.element = "NewProtocol"; a.value = extport.proto == TCP ? "TCP" : "UDP"; args.append(a); QString action = "DeletePortMapping"; QString comm = SOAP::createCommand(action,srv->servicetype,args); bt::HTTPRequest* r = sendSoapQuery(comm,srv->servicetype + "#" + action,srv->controlurl,waitjob != 0,false); ForwardingRequest fwreq = {extport,intport,r}; QValueList::iterator itrq = fwdreqs.begin(); while (itrq != fwdreqs.end()) { ForwardingRequest & fwo = *itrq; if (fwo.extport == extport && fwo.intport == intport) itrq = fwdreqs.erase(itrq); else itrq++; } fwdreqs.append(fwreq); if (waitjob) waitjob->addExitOperation(r); updateGUI(); } void UPnPRouter::undoForward(const net::Port & externalport, const net::Port & ,bt::WaitJob* waitjob) { kdDebug() << "Undoing forward of port " << externalport.number << " (" << (externalport.proto == UDP ? "UDP" : "TCP") << ")" << endl; QValueList::iterator itr = fwds.begin(); while (itr != fwds.end()) { Forwarding & wd = *itr; if (wd.extport == externalport) { undoForward(wd.service,wd.extport,wd.intport,waitjob); itr = fwds.erase(itr); } else { itr++; } } } bt::HTTPRequest* UPnPRouter::sendSoapQuery(const QString & query,const QString & soapact,const QString & controlurl, bool fwd,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); QString http_hdr = QString( "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, fwd); connect(r,SIGNAL(replyError(bt::HTTPRequest* ,const QString& ,bool)), this,SLOT(onReplyError(bt::HTTPRequest* ,const QString& ,bool))); connect(r,SIGNAL(replyOK(bt::HTTPRequest* ,const QString& ,bool)), this,SLOT(onReplyOK(bt::HTTPRequest* ,const QString& ,bool))); connect(r,SIGNAL(error(bt::HTTPRequest*, bool )), this,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) { QValueList::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 QString & s,bool fwd) { kdDebug() << "UPnPRouter : OK" << endl; kdDebug() << "FWD : " << fwd << endl; QValueList::iterator i = fwdreqs.begin(); while (i != fwdreqs.end()) { ForwardingRequest & fw = *i; if (fw.pending_req == r) { if (fwd) forwardedPortList->addNewForwardPort(fw.extport.number, fw.intport.number,net::TCP,false); else forwardedPortList->removeForwardPort(fw.extport.number, fw.intport.number,net::TCP); break; } i++; } emit replyOK(this,r,s,fwd); httpRequestDone(r,false); } void UPnPRouter::onReplyError(bt::HTTPRequest* r,const QString & s,bool fwd) { if (verbose) kdDebug() << "UPnPRouter : Error" << endl; kdDebug() << r->showPayload() << endl; httpRequestDone(r,true); emit replyError(this,r,s,fwd); } void UPnPRouter::onError(bt::HTTPRequest* r,bool) { httpRequestDone(r,true); } } namespace bt { WaitJob::WaitJob(Uint32 millis) : KIO::Job(false) { connect(&timer,SIGNAL(timeout()),this,SLOT(timerDone())); timer.start(millis,true); } WaitJob::~WaitJob() {} void WaitJob::kill(bool) { m_error = 0; emitResult(); } void WaitJob::timerDone() { // set the error to null and emit the result m_error = 0; emitResult(); } void WaitJob::addExitOperation(kt::ExitOperation* op) { exit_ops.append(op); connect(op,SIGNAL(operationFinished( kt::ExitOperation* )), this,SLOT(operationFinished( kt::ExitOperation* ))); } void WaitJob::operationFinished(kt::ExitOperation* op) { if (exit_ops.count() > 0) { exit_ops.remove(op); if (op->deleteAllowed()) op->deleteLater(); if (exit_ops.count() == 0) timerDone(); } } void WaitJob::execute(WaitJob* job) { KIO::NetAccess::synchronousRun(job,0); } void SynchronousWait(Uint32 millis) { kdDebug() << "SynchronousWait" << endl; WaitJob* j = new WaitJob(millis); KIO::NetAccess::synchronousRun(j,0); } // void UpdateCurrentTime() // { // global_time_stamp = Now(); // } } #include "upnprouter.moc"