You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tork/src/upnp/upnprouter.cpp

532 lines
15 KiB

/***************************************************************************
* 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 <stdlib.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <ntqstringlist.h>
#include <tdeio/netaccess.h>
#include <tdeio/job.h>
#include "httprequest.h"
#include "upnprouter.h"
#include "upnpdescriptionparser.h"
#include "soap.h"
#include "../functions.h"
#include <fcntl.h>
#include <ntqdir.h>
#include <ntqfile.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()
{
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 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)
{
forwardedPortList = new ForwardPortList();
// make the tmp_file unique, current time * a random number should be enough
tmp_file = TQString("/tmp/tork_upnp_description-%1.xml").arg(bt::GetCurrentTime() * rand());
}
UPnPRouter::~UPnPRouter()
{
TQValueList<HTTPRequest*>::iterator i = active_reqs.begin();
while (i != active_reqs.end())
{
(*i)->deleteLater();
i++;
}
}
void UPnPRouter::addService(const UPnPService & s)
{
TQValueList<UPnPService>::iterator i = services.begin();
while (i != services.end())
{
UPnPService & os = *i;
if (s.servicetype == os.servicetype)
return;
i++;
}
services.append(s);
}
void UPnPRouter::downloadFinished(TDEIO::Job* j)
{
if (j->error())
{
kdDebug() << "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)
{
kdDebug() << "Error parsing router description !" << endl;
TQString dest = TDEGlobal::dirs()->saveLocation("data","tork") + "upnp_failure";
TDEIO::file_copy(target,dest,-1,true,false,false);
}
else
{
if (verbose)
debugPrintData();
}
xmlFileDownloaded(this,ret);
remove(TQFile::encodeName(target));
}
void UPnPRouter::downloadXMLFile()
{
// downlaod XML description into a temporary file in /tmp
TDEIO::Job* job = TDEIO::file_copy(location,tmp_file,-1,true,false,false);
connect(job,SIGNAL(result(TDEIO::Job *)),this,SLOT(downloadFinished( TDEIO::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 (TQValueList<UPnPService>::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
TQValueList<SOAP::Arg> args;
SOAP::Arg a;
a.element = "NewRemoteHost";
args.append(a);
// the external port
a.element = "NewExternalPort";
a.value = TQString::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 = TQString::number(internalport.number);
else
a.value = TQString::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 = TQString("TorK 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 = {externalport,internalport,0,srv};
// erase old forwarding if one exists
TQValueList<Forwarding>::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};
TQValueList<ForwardingRequest>::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
TQValueList<UPnPService>::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
TQValueList<SOAP::Arg> args;
SOAP::Arg a;
a.element = "NewRemoteHost";
args.append(a);
// the external port
a.element = "NewExternalPort";
a.value = TQString::number(extport.number);
args.append(a);
// the protocol
a.element = "NewProtocol";
a.value = extport.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,false);
ForwardingRequest fwreq = {extport,intport,r};
TQValueList<ForwardingRequest>::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;
TQValueList<Forwarding>::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 TQString & query,const TQString & soapact,const TQString & 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);
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, fwd);
connect(r,SIGNAL(replyError(bt::HTTPRequest* ,const TQString& ,bool)),
this,SLOT(onReplyError(bt::HTTPRequest* ,const TQString& ,bool)));
connect(r,SIGNAL(replyOK(bt::HTTPRequest* ,const TQString& ,bool)),
this,SLOT(onReplyOK(bt::HTTPRequest* ,const TQString& ,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)
{
TQValueList<Forwarding>::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 & s,bool fwd)
{
kdDebug() << "UPnPRouter : OK" << endl;
kdDebug() << "FWD : " << fwd << endl;
TQValueList<ForwardingRequest>::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 TQString & 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) : TDEIO::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)
{
TDEIO::NetAccess::synchronousRun(job,0);
}
void SynchronousWait(Uint32 millis)
{
kdDebug() << "SynchronousWait" << endl;
WaitJob* j = new WaitJob(millis);
TDEIO::NetAccess::synchronousRun(j,0);
}
// void UpdateCurrentTime()
// {
// global_time_stamp = Now();
// }
}
#include "upnprouter.moc"