diff options
Diffstat (limited to 'plugins/upnp')
-rw-r--r-- | plugins/upnp/Makefile.am | 38 | ||||
-rw-r--r-- | plugins/upnp/ktupnpplugin.desktop | 26 | ||||
-rw-r--r-- | plugins/upnp/ktupnpplugin.kcfg | 13 | ||||
-rw-r--r-- | plugins/upnp/soap.cpp | 53 | ||||
-rw-r--r-- | plugins/upnp/soap.h | 62 | ||||
-rw-r--r-- | plugins/upnp/upnpdescriptionparser.cpp | 220 | ||||
-rw-r--r-- | plugins/upnp/upnpdescriptionparser.h | 49 | ||||
-rw-r--r-- | plugins/upnp/upnpmcastsocket.cpp | 312 | ||||
-rw-r--r-- | plugins/upnp/upnpmcastsocket.h | 91 | ||||
-rw-r--r-- | plugins/upnp/upnpplugin.cpp | 95 | ||||
-rw-r--r-- | plugins/upnp/upnpplugin.h | 51 | ||||
-rw-r--r-- | plugins/upnp/upnppluginsettings.kcfgc | 7 | ||||
-rw-r--r-- | plugins/upnp/upnpprefpage.cpp | 67 | ||||
-rw-r--r-- | plugins/upnp/upnpprefpage.h | 58 | ||||
-rw-r--r-- | plugins/upnp/upnpprefwidget.cpp | 253 | ||||
-rw-r--r-- | plugins/upnp/upnpprefwidget.h | 83 | ||||
-rw-r--r-- | plugins/upnp/upnprouter.cpp | 459 | ||||
-rw-r--r-- | plugins/upnp/upnprouter.h | 223 | ||||
-rw-r--r-- | plugins/upnp/upnpwidget.ui | 139 |
19 files changed, 2299 insertions, 0 deletions
diff --git a/plugins/upnp/Makefile.am b/plugins/upnp/Makefile.am new file mode 100644 index 0000000..8432f90 --- /dev/null +++ b/plugins/upnp/Makefile.am @@ -0,0 +1,38 @@ +INCLUDES = -I$(srcdir)/../../libktorrent $(all_includes) +METASOURCES = AUTO + +libktupnp_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libktupnp.la +libktupnp_la_SOURCES = soap.cpp upnpdescriptionparser.cpp upnpmcastsocket.cpp\ + upnprouter.cpp + +kde_module_LTLIBRARIES = ktupnpplugin.la +noinst_HEADERS = upnpplugin.h upnpmcastsocket.h upnprouter.h upnpprefpage.h \ + upnpprefwidget.h upnpdescriptionparser.h soap.h +ktupnpplugin_la_SOURCES = upnpplugin.cpp upnpprefpage.cpp upnpwidget.ui \ + upnpprefwidget.cpp upnppluginsettings.kcfgc + +# Libs needed by the plugin +ktupnpplugin_la_LIBADD = libktupnp.la \ + $(LIB_KPARTS) ../../libktorrent/libktorrent.la \ + $(LIB_QT) \ + $(LIB_KDECORE) $(LIB_KDEUI) $(LIB_KFILE) + + + +# LD flags for the plugin +# -module says: this is a module, i.e. something you're going to dlopen +# so e.g. it has no version number like a normal shared lib would have. +ktupnpplugin_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries) + +# rc file containing the GUI for the plugin +# pluginsdir = $(kde_datadir)/ktsearchplugin +# plugins_DATA = ktsearchpluginui.rc + +# Install the desktop file needed to detect the plugin +kde_services_DATA = ktupnpplugin.desktop + +kde_kcfg_DATA = ktupnpplugin.kcfg + + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/plugins/upnp/ktupnpplugin.desktop b/plugins/upnp/ktupnpplugin.desktop new file mode 100644 index 0000000..67948d7 --- /dev/null +++ b/plugins/upnp/ktupnpplugin.desktop @@ -0,0 +1,26 @@ +[Desktop Entry] +Name=UPnPPlugin +Name[bg]=Приставка UPnP +Name[br]=Lugent UPnP +Name[de]=UPnP-Modul +Name[el]=Πρόσθετο UPnP +Name[es]=Complemento UPnP +Name[et]=UPnP plugin +Name[it]=Plugin UPnP +Name[nb]=UPnP-modul +Name[nds]=UPnP-Moduul +Name[nl]=UPnP-plugin +Name[pl]=Wtyczka UPnP +Name[pt]='Plugin' UPnP +Name[pt_BR]=Plugin UPnP +Name[sk]=UPnP Plugin +Name[sr]=Прикључак за UPnP +Name[sr@Latn]=Priključak za UPnP +Name[sv]=UPnP-insticksprogram +Name[tr]=UPnP Eklentisi +Name[xx]=xxUPnPPluginxx +Name[zh_CN]=UPnP 插件 +Name[zh_TW]=UPnP外掛程式 +ServiceTypes=KTorrent/Plugin +Type=Service +X-KDE-Library=ktupnpplugin diff --git a/plugins/upnp/ktupnpplugin.kcfg b/plugins/upnp/ktupnpplugin.kcfg new file mode 100644 index 0000000..acce783 --- /dev/null +++ b/plugins/upnp/ktupnpplugin.kcfg @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + + <kcfgfile name="ktupnppluginrc"/> + <group name="general"> + <entry name="defaultDevice" type="String"> + <label>Default UPnP device to use</label> + </entry> + </group> +</kcfg> diff --git a/plugins/upnp/soap.cpp b/plugins/upnp/soap.cpp new file mode 100644 index 0000000..b155b55 --- /dev/null +++ b/plugins/upnp/soap.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * 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 "soap.h" + +namespace kt +{ + + QString SOAP::createCommand(const QString & action,const QString & service) + { + QString comm = QString("<?xml version=\"1.0\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:%1 xmlns:m=\"%2\"/>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>" + "\r\n").arg(action).arg(service); + + return comm; + } + + QString SOAP::createCommand(const QString & action,const QString & service,const QValueList<Arg> & args) + { + QString comm = QString("<?xml version=\"1.0\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:%1 xmlns:m=\"%2\">").arg(action).arg(service); + + for (QValueList<Arg>::const_iterator i = args.begin();i != args.end();i++) + { + const Arg & a = *i; + comm += "<" + a.element + ">" + a.value + "</" + a.element + ">"; + } + + comm += QString("</m:%1></SOAP-ENV:Body></SOAP-ENV:Envelope>\r\n").arg(action); + return comm; + } +} diff --git a/plugins/upnp/soap.h b/plugins/upnp/soap.h new file mode 100644 index 0000000..c11e2ed --- /dev/null +++ b/plugins/upnp/soap.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef KTSOAP_H +#define KTSOAP_H + +#include <qvaluelist.h> +#include <qstring.h> + +namespace kt +{ + + /** + @author Joris Guisson + */ + class SOAP + { + public: + + /** + * Create a simple UPnP SOAP command without parameters. + * @param action The name of the action + * @param service The name of the service + * @return The command + */ + static QString createCommand(const QString & action,const QString & service); + + struct Arg + { + QString element; + QString value; + }; + + /** + * Create a UPnP SOAP command with parameters. + * @param action The name of the action + * @param service The name of the service + * @param args Arguments for command + * @return The command + */ + static QString createCommand(const QString & action,const QString & service,const QValueList<Arg> & args); + }; + +} + +#endif diff --git a/plugins/upnp/upnpdescriptionparser.cpp b/plugins/upnp/upnpdescriptionparser.cpp new file mode 100644 index 0000000..43afbc3 --- /dev/null +++ b/plugins/upnp/upnpdescriptionparser.cpp @@ -0,0 +1,220 @@ +/*************************************************************************** + * 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 <qxml.h> +#include <qvaluestack.h> +#include <util/fileops.h> +#include <util/log.h> +#include <torrent/globals.h> +#include "upnprouter.h" +#include "upnpdescriptionparser.h" + +using namespace bt; + +namespace kt +{ + + class XMLContentHandler : public QXmlDefaultHandler + { + enum Status + { + TOPLEVEL,ROOT,DEVICE,SERVICE,FIELD,OTHER + }; + + QString tmp; + UPnPRouter* router; + UPnPService curr_service; + QValueStack<Status> status_stack; + public: + XMLContentHandler(UPnPRouter* router); + virtual ~XMLContentHandler(); + + + bool startDocument(); + bool endDocument(); + bool startElement(const QString &, const QString & localName, const QString &, + const QXmlAttributes & atts); + bool endElement(const QString & , const QString & localName, const QString & ); + bool characters(const QString & ch); + + bool interestingDeviceField(const QString & name); + bool interestingServiceField(const QString & name); + }; + + + UPnPDescriptionParser::UPnPDescriptionParser() + {} + + + UPnPDescriptionParser::~UPnPDescriptionParser() + {} + + bool UPnPDescriptionParser::parse(const QString & file,UPnPRouter* router) + { + bool ret = true; + { + QFile fptr(file); + if (!fptr.open(IO_ReadOnly)) + return false; + + QXmlInputSource input(&fptr); + XMLContentHandler chandler(router); + QXmlSimpleReader reader; + + reader.setContentHandler(&chandler); + ret = reader.parse(&input,false); + } + + if (!ret) + { + Out(SYS_PNP|LOG_IMPORTANT) << "Error parsing XML" << endl; + return false; + } + return true; + } + + ///////////////////////////////////////////////////////////////////////////////// + + + XMLContentHandler::XMLContentHandler(UPnPRouter* router) : router(router) + {} + + XMLContentHandler::~XMLContentHandler() + {} + + + bool XMLContentHandler::startDocument() + { + status_stack.push(TOPLEVEL); + return true; + } + + bool XMLContentHandler::endDocument() + { + status_stack.pop(); + return true; + } + + bool XMLContentHandler::interestingDeviceField(const QString & name) + { + return name == "friendlyName" || name == "manufacturer" || name == "modelDescription" || + name == "modelName" || name == "modelNumber"; + } + + + bool XMLContentHandler::interestingServiceField(const QString & name) + { + return name == "serviceType" || name == "serviceId" || name == "SCPDURL" || + name == "controlURL" || name == "eventSubURL"; + } + + bool XMLContentHandler::startElement(const QString &, const QString & localName, const QString &, + const QXmlAttributes & ) + { + tmp = ""; + switch (status_stack.top()) + { + case TOPLEVEL: + // from toplevel we can only go to root + if (localName == "root") + status_stack.push(ROOT); + else + return false; + break; + case ROOT: + // from the root we can go to device or specVersion + // we are not interested in the specVersion + if (localName == "device") + status_stack.push(DEVICE); + else + status_stack.push(OTHER); + break; + case DEVICE: + // see if it is a field we are interested in + if (interestingDeviceField(localName)) + status_stack.push(FIELD); + else + status_stack.push(OTHER); + break; + case SERVICE: + if (interestingServiceField(localName)) + status_stack.push(FIELD); + else + status_stack.push(OTHER); + break; + case OTHER: + if (localName == "service") + status_stack.push(SERVICE); + else if (localName == "device") + status_stack.push(DEVICE); + else + status_stack.push(OTHER); + break; + case FIELD: + break; + } + return true; + } + + bool XMLContentHandler::endElement(const QString & , const QString & localName, const QString & ) + { + switch (status_stack.top()) + { + case FIELD: + // we have a field so set it + status_stack.pop(); + if (status_stack.top() == DEVICE) + { + // if we are in a device + router->getDescription().setProperty(localName,tmp); + } + else if (status_stack.top() == SERVICE) + { + // set a property of a service + curr_service.setProperty(localName,tmp); + } + break; + case SERVICE: + // add the service + router->addService(curr_service); + curr_service.clear(); + // pop the stack + status_stack.pop(); + break; + default: + status_stack.pop(); + break; + } + + // reset tmp + tmp = ""; + return true; + } + + + bool XMLContentHandler::characters(const QString & ch) + { + if (ch.length() > 0) + { + tmp += ch; + } + return true; + } + +} diff --git a/plugins/upnp/upnpdescriptionparser.h b/plugins/upnp/upnpdescriptionparser.h new file mode 100644 index 0000000..5d4bf1e --- /dev/null +++ b/plugins/upnp/upnpdescriptionparser.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef KTUPNPDESCRIPTIONPARSER_H +#define KTUPNPDESCRIPTIONPARSER_H + +namespace kt +{ + class UPnPRouter; + + /** + * @author Joris Guisson + * + * Parses the xml description of a router. + */ + class UPnPDescriptionParser + { + public: + UPnPDescriptionParser(); + virtual ~UPnPDescriptionParser(); + + /** + * Parse the xml description. + * @param file File it is located in + * @param router The router off the xml description + * @return true upon success + */ + bool parse(const QString & file,UPnPRouter* router); + }; + +} + +#endif diff --git a/plugins/upnp/upnpmcastsocket.cpp b/plugins/upnp/upnpmcastsocket.cpp new file mode 100644 index 0000000..47712ea --- /dev/null +++ b/plugins/upnp/upnpmcastsocket.cpp @@ -0,0 +1,312 @@ +/*************************************************************************** + * 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 <kurl.h> +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include <qstringlist.h> +#include <ksocketdevice.h> +#include <ksocketaddress.h> +#include <util/log.h> +#include <torrent/globals.h> +#include <qfile.h> +#include <qtextstream.h> +#include "upnpmcastsocket.h" + + + +using namespace KNetwork; +using namespace bt; + +namespace kt +{ + + UPnPMCastSocket::UPnPMCastSocket(bool verbose) : verbose(verbose) + { + routers.setAutoDelete(true); + QObject::connect(this,SIGNAL(readyRead()),this,SLOT(onReadyRead())); + QObject::connect(this,SIGNAL(gotError(int)),this,SLOT(onError(int))); + setAddressReuseable(true); + setFamily(KNetwork::KResolver::IPv4Family); + setBlocking(true); + for (Uint32 i = 0;i < 10;i++) + { + if (!bind(QString::null,QString::number(1900 + i))) + Out(SYS_PNP|LOG_IMPORTANT) << "Cannot bind to UDP port 1900" << endl; + else + break; + } + setBlocking(false); + joinUPnPMCastGroup(); + } + + + UPnPMCastSocket::~UPnPMCastSocket() + { + leaveUPnPMCastGroup(); + QObject::disconnect(this,SIGNAL(readyRead()),this,SLOT(onReadyRead())); + QObject::disconnect(this,SIGNAL(gotError(int)),this,SLOT(onError(int))); + } + + void UPnPMCastSocket::discover() + { + Out(SYS_PNP|LOG_NOTICE) << "Trying to find UPnP devices on the local network" << endl; + + // send a HTTP M-SEARCH message to 239.255.255.250:1900 + const char* data = "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" + "MAN:\"ssdp:discover\"\r\n" + "MX:3\r\n" + "\r\n\0"; + + if (verbose) + { + Out(SYS_PNP|LOG_NOTICE) << "Sending : " << endl; + Out(SYS_PNP|LOG_NOTICE) << data << endl; + } + + KDatagramSocket::send(KNetwork::KDatagramPacket(data,strlen(data),KInetSocketAddress("239.255.255.250",1900))); + } + + void UPnPMCastSocket::onXmlFileDownloaded(UPnPRouter* r,bool success) + { + if (!success) + { + // we couldn't download and parse the XML file so + // get rid of it + r->deleteLater(); + } + else + { + // add it to the list and emit the signal + if (!routers.contains(r->getServer())) + { + routers.insert(r->getServer(),r); + discovered(r); + } + else + { + r->deleteLater(); + } + } + } + + void UPnPMCastSocket::onReadyRead() + { + if (bytesAvailable() == 0) + { + Out(SYS_PNP|LOG_NOTICE) << "0 byte UDP packet " << endl; + // KDatagramSocket wrongly handles UDP packets with no payload + // so we need to deal with it oursleves + int fd = socketDevice()->socket(); + char tmp; + read(fd,&tmp,1); + return; + } + + KNetwork::KDatagramPacket p = KDatagramSocket::receive(); + if (p.isNull()) + return; + + if (verbose) + { + Out(SYS_PNP|LOG_NOTICE) << "Received : " << endl; + Out(SYS_PNP|LOG_NOTICE) << QString(p.data()) << endl; + } + + // try to make a router of it + UPnPRouter* r = parseResponse(p.data()); + if (r) + { + QObject::connect(r,SIGNAL(xmlFileDownloaded( UPnPRouter*, bool )), + this,SLOT(onXmlFileDownloaded( UPnPRouter*, bool ))); + + // download it's xml file + r->downloadXMLFile(); + } + } + + UPnPRouter* UPnPMCastSocket::parseResponse(const QByteArray & arr) + { + QStringList lines = QStringList::split("\r\n",QString(arr),false); + QString server; + KURL location; + + /* + Out(SYS_PNP|LOG_DEBUG) << "Received : " << endl; + for (Uint32 idx = 0;idx < lines.count(); idx++) + Out(SYS_PNP|LOG_DEBUG) << lines[idx] << endl; + */ + + // first read first line and see if contains a HTTP 200 OK message + QString line = lines.first(); + if (!line.contains("HTTP")) + { + // it is either a 200 OK or a NOTIFY + if (!line.contains("NOTIFY") && !line.contains("200")) + return 0; + } + else if (line.contains("M-SEARCH")) // ignore M-SEARCH + return 0; + + // quick check that the response being parsed is valid + bool validDevice = false; + for (Uint32 idx = 0;idx < lines.count() && !validDevice; idx++) + { + line = lines[idx]; + if ((line.contains("ST:") || line.contains("NT:")) && line.contains("InternetGatewayDevice")) + { + validDevice = true; + } + } + if (!validDevice) + { + // Out(SYS_PNP|LOG_IMPORTANT) << "Not a valid Internet Gateway Device" << endl; + return 0; + } + + // read all lines and try to find the server and location fields + for (Uint32 i = 1;i < lines.count();i++) + { + line = lines[i]; + if (line.startsWith("Location") || line.startsWith("LOCATION") || line.startsWith("location")) + { + location = line.mid(line.find(':') + 1).stripWhiteSpace(); + if (!location.isValid()) + return 0; + } + else if (line.startsWith("Server") || line.startsWith("server") || line.startsWith("SERVER")) + { + server = line.mid(line.find(':') + 1).stripWhiteSpace(); + if (server.length() == 0) + return 0; + + } + } + + if (routers.contains(server)) + { + return 0; + } + else + { + Out(SYS_PNP|LOG_NOTICE) << "Detected IGD " << server << endl; + // everything OK, make a new UPnPRouter + return new UPnPRouter(server,location,verbose); + } + } + + void UPnPMCastSocket::onError(int) + { + Out(SYS_PNP|LOG_IMPORTANT) << "UPnPMCastSocket Error : " << errorString() << endl; + } + + void UPnPMCastSocket::saveRouters(const QString & file) + { + QFile fptr(file); + if (!fptr.open(IO_WriteOnly)) + { + Out(SYS_PNP|LOG_IMPORTANT) << "Cannot open file " << file << " : " << fptr.errorString() << endl; + return; + } + + // file format is simple : 2 lines per router, + // one containing the server, the other the location + QTextStream fout(&fptr); + bt::PtrMap<QString,UPnPRouter>::iterator i = routers.begin(); + while (i != routers.end()) + { + UPnPRouter* r = i->second; + fout << r->getServer() << endl; + fout << r->getLocation().prettyURL() << endl; + i++; + } + } + + void UPnPMCastSocket::loadRouters(const QString & file) + { + QFile fptr(file); + if (!fptr.open(IO_ReadOnly)) + { + Out(SYS_PNP|LOG_IMPORTANT) << "Cannot open file " << file << " : " << fptr.errorString() << endl; + return; + } + + // file format is simple : 2 lines per router, + // one containing the server, the other the location + QTextStream fin(&fptr); + + while (!fin.atEnd()) + { + QString server, location; + server = fin.readLine(); + location = fin.readLine(); + if (!routers.contains(server)) + { + UPnPRouter* r = new UPnPRouter(server,location); + // download it's xml file + QObject::connect(r,SIGNAL(xmlFileDownloaded( UPnPRouter*, bool )),this,SLOT(onXmlFileDownloaded( UPnPRouter*, bool ))); + r->downloadXMLFile(); + } + } + } + + void UPnPMCastSocket::joinUPnPMCastGroup() + { + int fd = socketDevice()->socket(); + struct ip_mreq mreq; + + memset(&mreq,0,sizeof(struct ip_mreq)); + + inet_aton("239.255.255.250",&mreq.imr_multiaddr); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + + if (setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(struct ip_mreq)) < 0) + { + Out(SYS_PNP|LOG_NOTICE) << "Failed to join multicast group 239.255.255.250" << endl; + } + } + + void UPnPMCastSocket::leaveUPnPMCastGroup() + { + int fd = socketDevice()->socket(); + struct ip_mreq mreq; + + memset(&mreq,0,sizeof(struct ip_mreq)); + + inet_aton("239.255.255.250",&mreq.imr_multiaddr); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + + if (setsockopt(fd,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(struct ip_mreq)) < 0) + { + Out(SYS_PNP|LOG_NOTICE) << "Failed to leave multicast group 239.255.255.250" << endl; + } + } +} + + + +#include "upnpmcastsocket.moc" diff --git a/plugins/upnp/upnpmcastsocket.h b/plugins/upnp/upnpmcastsocket.h new file mode 100644 index 0000000..493c5b9 --- /dev/null +++ b/plugins/upnp/upnpmcastsocket.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef KTUPNPMCASTSOCKET_H +#define KTUPNPMCASTSOCKET_H + +#include <util/ptrmap.h> +#include <kdatagramsocket.h> +#include <util/constants.h> +#include "upnprouter.h" + +using bt::Uint32; + +namespace kt +{ + class UPnPRouter; + + /** + * @author Joris Guisson + * + * Socket used to discover UPnP devices. This class will keep track + * of all discovered devices. + */ + class UPnPMCastSocket : public KNetwork::KDatagramSocket + { + Q_OBJECT + public: + UPnPMCastSocket(bool verbose = false); + virtual ~UPnPMCastSocket(); + + /// Get the number of routers discovered + Uint32 getNumDevicesDiscovered() const {return routers.count();} + + /// Find a router using it's server name + UPnPRouter* findDevice(const QString & name) {return routers.find(name);} + + /// Save all routers to a file (for convenience at startup) + void saveRouters(const QString & file); + + /// Load all routers from a file + void loadRouters(const QString & file); + + public slots: + /** + * Try to discover a UPnP device on the network. + * A signal will be emitted when a device is found. + */ + void discover(); + + private slots: + void onReadyRead(); + void onError(int); + void onXmlFileDownloaded(UPnPRouter* r,bool success); + + signals: + /** + * Emitted when a router or internet gateway device is detected. + * @param router The router + */ + void discovered(UPnPRouter* router); + + public: + UPnPRouter* parseResponse(const QByteArray & arr); + + private: + void joinUPnPMCastGroup(); + void leaveUPnPMCastGroup(); + + private: + bt::PtrMap<QString,UPnPRouter> routers; + bool verbose; + }; +} + +#endif diff --git a/plugins/upnp/upnpplugin.cpp b/plugins/upnp/upnpplugin.cpp new file mode 100644 index 0000000..dbe58b4 --- /dev/null +++ b/plugins/upnp/upnpplugin.cpp @@ -0,0 +1,95 @@ +/*************************************************************************** + * 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 <kgenericfactory.h> +#include <kglobal.h> +#include <klocale.h> +#include <kiconloader.h> +#include <kstandarddirs.h> +#include <kstdaction.h> +#include <kpopupmenu.h> +#include <interfaces/guiinterface.h> +#include <util/fileops.h> +#include "upnpplugin.h" +#include "upnpmcastsocket.h" +#include "upnpprefpage.h" + + +#define NAME "UPnP" +#define AUTHOR "Joris Guisson" +#define EMAIL "joris.guisson@gmail.com" + + + +K_EXPORT_COMPONENT_FACTORY(ktupnpplugin,KGenericFactory<kt::UPnPPlugin>("ktupnpplugin")) + +namespace kt +{ + + UPnPPlugin::UPnPPlugin(QObject* parent, const char* name, const QStringList& args) + : Plugin(parent, name, args,NAME,i18n("UPnP"),AUTHOR,EMAIL,i18n("Uses UPnP to automatically forward ports on your router"),"ktupnp") + { + sock = 0; + pref = 0; + } + + + UPnPPlugin::~UPnPPlugin() + { + delete sock; + delete pref; + } + + + void UPnPPlugin::load() + { + //KIconLoader* iload = KGlobal::iconLoader(); + sock = new UPnPMCastSocket(); + pref = new UPnPPrefPage(sock); + this->getGUI()->addPrefPage(pref); + // load the routers list + QString routers_file = KGlobal::dirs()->saveLocation("data","ktorrent") + "routers"; + if (bt::Exists(routers_file)) + sock->loadRouters(routers_file); + sock->discover(); + } + + void UPnPPlugin::unload() + { + QString routers_file = KGlobal::dirs()->saveLocation("data","ktorrent") + "routers"; + sock->saveRouters(routers_file); + this->getGUI()->removePrefPage(pref); + sock->close(); + delete pref; + pref = 0; + delete sock; + sock = 0; + } + + void UPnPPlugin::shutdown(bt::WaitJob* job) + { + pref->shutdown(job); + } + + bool UPnPPlugin::versionCheck(const QString & version) const + { + return version == KT_VERSION_MACRO; + } +} +#include "upnpplugin.moc" diff --git a/plugins/upnp/upnpplugin.h b/plugins/upnp/upnpplugin.h new file mode 100644 index 0000000..a6ca78a --- /dev/null +++ b/plugins/upnp/upnpplugin.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef KTSEARCHPLUGIN_H +#define KTSEARCHPLUGIN_H + +#include <interfaces/plugin.h> + +namespace kt +{ + class UPnPMCastSocket; + class UPnPPrefPage; + + /** + @author Joris Guisson + */ + class UPnPPlugin : public Plugin + { + Q_OBJECT + public: + UPnPPlugin(QObject* parent, const char* name, const QStringList& args); + virtual ~UPnPPlugin(); + + virtual void load(); + virtual void unload(); + virtual void shutdown(bt::WaitJob* job); + virtual bool versionCheck(const QString& version) const; + private: + UPnPMCastSocket* sock; + UPnPPrefPage* pref; + }; + +} + +#endif diff --git a/plugins/upnp/upnppluginsettings.kcfgc b/plugins/upnp/upnppluginsettings.kcfgc new file mode 100644 index 0000000..6cab465 --- /dev/null +++ b/plugins/upnp/upnppluginsettings.kcfgc @@ -0,0 +1,7 @@ +# Code generation options for kconfig_compiler +File=ktupnpplugin.kcfg +ClassName=UPnPPluginSettings +Namespace=kt +Singleton=true +Mutators=true +# will create the necessary code for setting those variables diff --git a/plugins/upnp/upnpprefpage.cpp b/plugins/upnp/upnpprefpage.cpp new file mode 100644 index 0000000..dc50c2f --- /dev/null +++ b/plugins/upnp/upnpprefpage.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + * 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 <klocale.h> +#include <kglobal.h> +#include <kiconloader.h> +#include "upnpprefpage.h" +#include "upnpprefwidget.h" +#include "upnprouter.h" +#include "upnpmcastsocket.h" + +namespace kt +{ + + UPnPPrefPage::UPnPPrefPage(UPnPMCastSocket* sock): PrefPageInterface(i18n("UPnP"), i18n("UPnP Devices"),KGlobal::iconLoader()->loadIcon("ktupnp",KIcon::NoGroup)),sock(sock) + { + widget = 0; + } + + + UPnPPrefPage::~UPnPPrefPage() + {} + + + bool UPnPPrefPage::apply() + { + return true; + } + + void UPnPPrefPage::createWidget(QWidget* parent) + { + widget = new UPnPPrefWidget(parent); + QObject::connect(sock,SIGNAL(discovered(UPnPRouter* )),widget,SLOT(addDevice(UPnPRouter* ))); + QObject::connect(widget,SIGNAL(rescan()),sock,SLOT(discover())); + } + + void UPnPPrefPage::deleteWidget() + { + delete widget; + widget = 0; + } + + void UPnPPrefPage::updateData() + { + } + + void UPnPPrefPage::shutdown(bt::WaitJob* job) + { + widget->shutdown(job); + } +} diff --git a/plugins/upnp/upnpprefpage.h b/plugins/upnp/upnpprefpage.h new file mode 100644 index 0000000..7d5b4f5 --- /dev/null +++ b/plugins/upnp/upnpprefpage.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef KTUPNPPREFPAGE_H +#define KTUPNPPREFPAGE_H + +#include <interfaces/prefpageinterface.h> + +namespace bt +{ + class WaitJob; +} + +namespace kt +{ + class UPnPMCastSocket; + class UPnPPrefWidget; + + /** + * @author Joris Guisson + * + * Page in the preference dialog for the UPnP plugin. + */ + class UPnPPrefPage : public PrefPageInterface + { + UPnPMCastSocket* sock; + UPnPPrefWidget* widget; + public: + UPnPPrefPage(UPnPMCastSocket* sock); + virtual ~UPnPPrefPage(); + + virtual bool apply(); + virtual void createWidget(QWidget* parent); + virtual void deleteWidget(); + virtual void updateData(); + + void shutdown(bt::WaitJob* job); + }; + +} + +#endif diff --git a/plugins/upnp/upnpprefwidget.cpp b/plugins/upnp/upnpprefwidget.cpp new file mode 100644 index 0000000..43e2aec --- /dev/null +++ b/plugins/upnp/upnpprefwidget.cpp @@ -0,0 +1,253 @@ +/*************************************************************************** + * 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 <klistview.h> +#include <kmessagebox.h> +#include <kpushbutton.h> +#include <torrent/udptrackersocket.h> +#include <torrent/globals.h> +#include <torrent/server.h> +#include <kademlia/dhtbase.h> +#include "upnpprefwidget.h" +#include <util/log.h> +#include <util/error.h> +#include <util/waitjob.h> +#include <util/httprequest.h> +#include <torrent/globals.h> +#include "upnppluginsettings.h" + +using namespace bt; + +namespace kt +{ + UPnPPrefWidget::UPnPPrefWidget(QWidget* parent, const char* name, WFlags fl) + : UPnPWidget(parent,name,fl) + { + def_router = 0; + connect(m_forward_btn,SIGNAL(clicked()),this,SLOT(onForwardBtnClicked())); + connect(m_undo_forward_btn,SIGNAL(clicked()),this,SLOT(onUndoForwardBtnClicked())); + connect(m_rescan,SIGNAL(clicked()),this,SLOT(onRescanClicked())); + bt::Globals::instance().getPortList().setListener(this); + } + + UPnPPrefWidget::~UPnPPrefWidget() + { + bt::Globals::instance().getPortList().setListener(0); + } + + void UPnPPrefWidget::shutdown(bt::WaitJob* job) + { + if (!def_router) + return; + + net::PortList & pl = bt::Globals::instance().getPortList(); + if (pl.count() == 0) + return; + + for (net::PortList::iterator i = pl.begin(); i != pl.end();i++) + { + net::Port & p = *i; + if (p.forward) + def_router->undoForward(p,job); + } + } + + + void UPnPPrefWidget::addDevice(UPnPRouter* r) + { + connect(r,SIGNAL(updateGUI()),this,SLOT(updatePortMappings())); + KListViewItem* item = new KListViewItem(m_device_list,r->getDescription().friendlyName); + item->setMultiLinesEnabled(true); + itemmap[item] = r; + // if we have discovered the default device or there is none + // forward it's ports + QString def_dev = UPnPPluginSettings::defaultDevice(); + if (def_dev == r->getServer() || def_dev.length() == 0) + { + Out(SYS_PNP|LOG_DEBUG) << "Doing default port mappings ..." << endl; + UPnPPluginSettings::setDefaultDevice(r->getServer()); + UPnPPluginSettings::writeConfig(); + + try + { + net::PortList & pl = bt::Globals::instance().getPortList(); + + for (net::PortList::iterator i = pl.begin(); i != pl.end();i++) + { + net::Port & p = *i; + if (p.forward) + r->forward(p); + } + + def_router = r; + } + catch (Error & e) + { + KMessageBox::error(this,e.toString()); + } + } + } + + void UPnPPrefWidget::onForwardBtnClicked() + { + KListViewItem* item = (KListViewItem*)m_device_list->currentItem();; + if (!item) + return; + + UPnPRouter* r = itemmap[item]; + if (!r) + return; + + try + { + net::PortList & pl = bt::Globals::instance().getPortList(); + + for (net::PortList::iterator i = pl.begin(); i != pl.end();i++) + { + net::Port & p = *i; + if (p.forward) + r->forward(p); + } + + QString def_dev = UPnPPluginSettings::defaultDevice(); + if (def_dev != r->getServer()) + { + UPnPPluginSettings::setDefaultDevice(r->getServer()); + UPnPPluginSettings::writeConfig(); + def_router = r; + } + + } + catch (Error & e) + { + KMessageBox::error(this,e.toString()); + } + } + + void UPnPPrefWidget::onRescanClicked() + { + // clear the list and emit the signal + rescan(); + } + + void UPnPPrefWidget::onUndoForwardBtnClicked() + { + KListViewItem* item = (KListViewItem*)m_device_list->currentItem();; + if (!item) + return; + + UPnPRouter* r = itemmap[item]; + if (!r) + return; + + try + { + net::PortList & pl = bt::Globals::instance().getPortList(); + + for (net::PortList::iterator i = pl.begin(); i != pl.end();i++) + { + net::Port & p = *i; + if (p.forward) + r->undoForward(p,false); + } + + QString def_dev = UPnPPluginSettings::defaultDevice(); + if (def_dev == r->getServer()) + { + UPnPPluginSettings::setDefaultDevice(QString::null); + UPnPPluginSettings::writeConfig(); + def_router = 0; + } + } + catch (Error & e) + { + KMessageBox::error(this,e.toString()); + } + } + + + void UPnPPrefWidget::updatePortMappings() + { + // update all port mappings + QMap<KListViewItem*,UPnPRouter*>::iterator i = itemmap.begin(); + while (i != itemmap.end()) + { + UPnPRouter* r = i.data(); + KListViewItem* item = i.key(); + QString msg,services; + QValueList<UPnPRouter::Forwarding>::iterator j = r->beginPortMappings(); + while (j != r->endPortMappings()) + { + UPnPRouter::Forwarding & f = *j; + if (!f.pending_req) + { + msg += QString::number(f.port.number) + " ("; + QString prot = (f.port.proto == net::UDP ? "UDP" : "TCP"); + msg += prot + ")"; + if (f.service->servicetype.contains("WANPPPConnection")) + services += "PPP"; + else + services += "IP"; + } + j++; + if (j != r->endPortMappings()) + { + msg += "\n"; + services += "\n"; + } + } + item->setText(1,msg); + item->setText(2,services); + i++; + } + } + + + void UPnPPrefWidget::portAdded(const net::Port & port) + { + try + { + if (def_router && port.forward) + def_router->forward(port); + } + catch (Error & e) + { + Out(SYS_PNP|LOG_DEBUG) << "Error : " << e.toString() << endl; + } + } + + void UPnPPrefWidget::portRemoved(const net::Port & port) + { + try + { + if (def_router && port.forward) + def_router->undoForward(port,false); + } + catch (Error & e) + { + Out(SYS_PNP|LOG_DEBUG) << "Error : " << e.toString() << endl; + } + } +} + + + +#include "upnpprefwidget.moc" + diff --git a/plugins/upnp/upnpprefwidget.h b/plugins/upnp/upnpprefwidget.h new file mode 100644 index 0000000..16fa31b --- /dev/null +++ b/plugins/upnp/upnpprefwidget.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ + +#ifndef UPNPPREFWIDGET_H +#define UPNPPREFWIDGET_H + +#include <qmap.h> +#include "upnprouter.h" +#include "upnpwidget.h" + +class KListViewItem; + +namespace bt +{ + class WaitJob; +} + +namespace kt +{ + + /** + * Widget for the UPnP pref dialog page. + */ + class UPnPPrefWidget : public UPnPWidget,public net::PortListener + { + Q_OBJECT + + public: + UPnPPrefWidget(QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); + virtual ~UPnPPrefWidget(); + + void shutdown(bt::WaitJob* job); + + + public slots: + /** + * Add a device to the list. + * @param r The device + */ + void addDevice(UPnPRouter* r); + + signals: + /** + * Emitted when the user presses the rescan button. + */ + void rescan(); + + + protected slots: + void onForwardBtnClicked(); + void onUndoForwardBtnClicked(); + void onRescanClicked(); + void updatePortMappings(); + + private: + virtual void portAdded(const net::Port & port); + virtual void portRemoved(const net::Port & port); + + private: + QMap<KListViewItem*,UPnPRouter*> itemmap; + UPnPRouter* def_router; + }; +} + +#endif + diff --git a/plugins/upnp/upnprouter.cpp b/plugins/upnp/upnprouter.cpp new file mode 100644 index 0000000..617abf5 --- /dev/null +++ b/plugins/upnp/upnprouter.cpp @@ -0,0 +1,459 @@ +/*************************************************************************** + * 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 <klocale.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <qstringlist.h> +#include <kio/netaccess.h> +#include <kio/job.h> +#include <torrent/globals.h> +#include <util/log.h> +#include <util/array.h> +#include <util/error.h> +#include <util/functions.h> +#include <util/fileops.h> +#include <util/httprequest.h> +#include <util/waitjob.h> +#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 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() + { + 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 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) + { + // make the tmp_file unique, current time * a random number should be enough + tmp_file = QString("/tmp/ktorrent_upnp_description-%1.xml").arg(bt::GetCurrentTime() * rand()); + } + + + UPnPRouter::~UPnPRouter() + { + QValueList<HTTPRequest*>::iterator i = active_reqs.begin(); + while (i != active_reqs.end()) + { + (*i)->deleteLater(); + i++; + } + } + + void UPnPRouter::addService(const UPnPService & s) + { + QValueList<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(KIO::Job* j) + { + if (j->error()) + { + Out(SYS_PNP|LOG_IMPORTANT) << "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) + { + Out(SYS_PNP|LOG_IMPORTANT) << "Error parsing router description !" << endl; + QString dest = KGlobal::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,SIGNAL(result(KIO::Job *)),this,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 (QValueList<UPnPService>::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 + QValueList<SOAP::Arg> args; + SOAP::Arg a; + a.element = "NewRemoteHost"; + args.append(a); + + // the external port + a.element = "NewExternalPort"; + a.value = QString::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 = QString::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 = QString("KTorrent 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 = {port,0,srv}; + // erase old forwarding if one exists + QValueList<Forwarding>::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 + QValueList<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") + { + forward(&s,port); + } + i++; + } + + } + + void UPnPRouter::undoForward(UPnPService* srv,const net::Port & port,bt::WaitJob* waitjob) + { + // add all the arguments for the command + QValueList<SOAP::Arg> args; + SOAP::Arg a; + a.element = "NewRemoteHost"; + args.append(a); + + // the external port + a.element = "NewExternalPort"; + a.value = QString::number(port.number); + args.append(a); + + // the protocol + a.element = "NewProtocol"; + a.value = port.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); + + 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; + + QValueList<Forwarding>::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 QString & query,const QString & soapact,const QString & 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); + + 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); + connect(r,SIGNAL(replyError(bt::HTTPRequest* ,const QString& )), + this,SLOT(onReplyError(bt::HTTPRequest* ,const QString& ))); + connect(r,SIGNAL(replyOK(bt::HTTPRequest* ,const QString& )), + this,SLOT(onReplyOK(bt::HTTPRequest* ,const QString& ))); + 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<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 QString &) + { + if (verbose) + Out(SYS_PNP|LOG_NOTICE) << "UPnPRouter : OK" << endl; + + httpRequestDone(r,false); + } + + void UPnPRouter::onReplyError(bt::HTTPRequest* r,const QString &) + { + 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 + QValueList<UPnPService>::iterator UPnPRouter::findPortForwardingService() + { + QValueList<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") + return i; + i++; + } + return services.end(); + } + + + void UPnPRouter::getExternalIP() + { + // first find the right service + QValueList<UPnPService>::iterator i = findPortForwardingService(); + if (i == services.end()) + throw Error(i18n("Cannot find port forwarding service in the device's description!")); + + UPnPService & s = *i; + QString action = "GetExternalIPAddress"; + QString 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 + QValueList<UPnPService>::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 + QValueList<SOAP::Arg> args; + SOAP::Arg a; + a.element = "NewRemoteHost"; + args.append(a); + + // the external port + a.element = "NewExternalPort"; + a.value = QString::number(port.number); + args.append(a); + + // the protocol + a.element = "NewProtocol"; + a.value = port.proto == TCP ? "TCP" : "UDP"; + args.append(a); + + UPnPService & s = *i; + QString action = "GetSpecificPortMappingEntry"; + QString comm = SOAP::createCommand(action,s.servicetype,args); + sendSoapQuery(comm,s.servicetype + "#" + action,s.controlurl); + } +#endif + + +} + +#include "upnprouter.moc" diff --git a/plugins/upnp/upnprouter.h b/plugins/upnp/upnprouter.h new file mode 100644 index 0000000..a4d32b4 --- /dev/null +++ b/plugins/upnp/upnprouter.h @@ -0,0 +1,223 @@ +/*************************************************************************** + * 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. * + ***************************************************************************/ +#ifndef KTUPNPROUTER_H +#define KTUPNPROUTER_H + +#include <kurl.h> +#include <qstringlist.h> +#include <kstreamsocket.h> +#include <net/portlist.h> + +using bt::Uint16; + +namespace bt +{ + class HTTPRequest; + class WaitJob; +} + +namespace KIO +{ + class Job; +} + +namespace kt +{ + /** + * Structure describing a UPnP service found in an xml file. + */ + struct UPnPService + { + QString serviceid; + QString servicetype; + QString controlurl; + QString eventsuburl; + QString scpdurl; + + UPnPService(); + UPnPService(const UPnPService & s); + + /** + * Set a property of the service. + * @param name Name of the property (matches to variable names) + * @param value Value of the property + */ + void setProperty(const QString & name,const QString & value); + + /** + * Set all strings to empty. + */ + void clear(); + + /// Print the data of this service + void debugPrintData(); + + /** + * Assignment operator + * @param s The service to copy + * @return *this + */ + UPnPService & operator = (const UPnPService & s); + }; + + /** + * Struct to hold the description of a device + */ + struct UPnPDeviceDescription + { + QString friendlyName; + QString manufacturer; + QString modelDescription; + QString modelName; + QString modelNumber; + + /** + * Set a property of the description + * @param name Name of the property (matches to variable names) + * @param value Value of the property + */ + void setProperty(const QString & name,const QString & value); + }; + + /** + * @author Joris Guisson + * + * Class representing a UPnP enabled router. This class is also used to communicate + * with the router. + */ + class UPnPRouter : public QObject + { + Q_OBJECT + + public: + struct Forwarding + { + net::Port port; + bt::HTTPRequest* pending_req; + UPnPService* service; + }; + private: + QString server; + QString tmp_file; + KURL location; + UPnPDeviceDescription desc; + QValueList<UPnPService> services; + QValueList<Forwarding> fwds; + QValueList<bt::HTTPRequest*> active_reqs; + public: + /** + * Construct a router. + * @param server The name of the router + * @param location The location of it's xml description file + * @param verbose Print lots of debug info + */ + UPnPRouter(const QString & server,const KURL & location,bool verbose = false); + virtual ~UPnPRouter(); + + /// Get the name of the server + QString getServer() const {return server;} + + /// Get the location of it's xml description + KURL getLocation() const {return location;} + + /// Get the device description + UPnPDeviceDescription & getDescription() {return desc;} + + /// Get the device description (const version) + const UPnPDeviceDescription & getDescription() const {return desc;} + + /** + * Download the XML File of the router. + */ + void downloadXMLFile(); + + /** + * Add a service to the router. + * @param s The service + */ + void addService(const UPnPService & s); + +#if 0 + /** + * See if a port is forwarded + * @param port The Port + */ + void isPortForwarded(const net::Port & port); + + /** + * Get the external IP address. + */ + void getExternalIP(); +#endif + + /** + * Forward a local port + * @param port The local port to forward + */ + void forward(const net::Port & port); + + /** + * Undo forwarding + * @param port The port + * @param waitjob When this is set the jobs needs to be added to the waitjob, + * so we can wait for their completeion at exit + */ + void undoForward(const net::Port & port,bt::WaitJob* waitjob = 0); + + void debugPrintData(); + + QValueList<Forwarding>::iterator beginPortMappings() {return fwds.begin();} + QValueList<Forwarding>::iterator endPortMappings() {return fwds.end();} + + private slots: + void onReplyOK(bt::HTTPRequest* r,const QString &); + void onReplyError(bt::HTTPRequest* r,const QString &); + void onError(bt::HTTPRequest* r,bool); + void downloadFinished(KIO::Job* j); + + + + signals: + /** + * Tell the GUI that it needs to be updated. + */ + void updateGUI(); + + /** + * Signal which indicates that the XML was downloaded successfully or not. + * @param r The router which emitted the signal + * @param success Wether or not it succeeded + */ + void xmlFileDownloaded(UPnPRouter* r,bool success); + + private: + QValueList<UPnPService>::iterator findPortForwardingService(); + + bt::HTTPRequest* sendSoapQuery(const QString & query,const QString & soapact,const QString & controlurl,bool at_exit = false); + bool verbose; + + void forward(UPnPService* srv,const net::Port & port); + void undoForward(UPnPService* srv,const net::Port & port,bt::WaitJob* waitjob); + void httpRequestDone(bt::HTTPRequest* r,bool erase_fwd); + }; + +} + +#endif diff --git a/plugins/upnp/upnpwidget.ui b/plugins/upnp/upnpwidget.ui new file mode 100644 index 0000000..a8f0f7a --- /dev/null +++ b/plugins/upnp/upnpwidget.ui @@ -0,0 +1,139 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>UPnPWidget</class> +<widget class="QWidget"> + <property name="name"> + <cstring>UPnPWidget</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>600</width> + <height>561</height> + </rect> + </property> + <property name="caption"> + <string>UPnP</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout1</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Detected devices:</string> + </property> + </widget> + <widget class="KListView"> + <column> + <property name="text"> + <string>Device</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>Ports Forwarded</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <column> + <property name="text"> + <string>WAN Connection</string> + </property> + <property name="clickable"> + <bool>true</bool> + </property> + <property name="resizable"> + <bool>true</bool> + </property> + </column> + <property name="name"> + <cstring>m_device_list</cstring> + </property> + </widget> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_forward_btn</cstring> + </property> + <property name="text"> + <string>Forw&ard Ports</string> + </property> + </widget> + <widget class="KPushButton"> + <property name="name"> + <cstring>m_undo_forward_btn</cstring> + </property> + <property name="text"> + <string>Undo Port Forwarding</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer2</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>70</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>m_rescan</cstring> + </property> + <property name="text"> + <string>Rescan</string> + </property> + </widget> + </hbox> + </widget> + </vbox> +</widget> +<customwidgets> +</customwidgets> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>klistview.h</includehint> + <includehint>kpushbutton.h</includehint> + <includehint>kpushbutton.h</includehint> +</includehints> +</UI> |