PageRenderTime 26ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/konversation-1.4/src/upnp/upnprouter.cpp

#
C++ | 413 lines | 301 code | 82 blank | 30 comment | 70 complexity | a36ba3bd3e4794d3f71ad5e1645d2ec8 MD5 | raw file
Possible License(s): AGPL-1.0, CC-BY-SA-3.0
  1. /*
  2. This program is free software; you can redistribute it and/or modify
  3. it under the terms of the GNU General Public License as published by
  4. the Free Software Foundation; either version 2 of the License, or
  5. (at your option) any later version.
  6. */
  7. /*
  8. Copyright (C) 2005-2007 Joris Guisson <joris.guisson@gmail.com>
  9. Copyright (C) 2009 Michael Kreitzer <mrgrim@gr1m.org>
  10. */
  11. #include "upnprouter.h"
  12. #include "upnpdescriptionparser.h"
  13. #include "soap.h"
  14. #include <QCoreApplication>
  15. #include <QNetworkReply>
  16. #include <KDebug>
  17. #include <KLocale>
  18. #include <KIO/Job>
  19. #include <KIO/NetAccess>
  20. #include <kio/jobclasses.h>
  21. #include <stdlib.h>
  22. namespace Konversation
  23. {
  24. namespace UPnP
  25. {
  26. UPnPService::UPnPService()
  27. {
  28. ready = false;
  29. }
  30. UPnPService::UPnPService(const UPnPService & s)
  31. {
  32. this->servicetype = s.servicetype;
  33. this->controlurl = s.controlurl;
  34. this->eventsuburl = s.eventsuburl;
  35. this->serviceid = s.serviceid;
  36. this->scpdurl = s.scpdurl;
  37. ready = false;
  38. }
  39. void UPnPService::setProperty(const QString & name,const QString & value)
  40. {
  41. if (name == "serviceType")
  42. servicetype = value;
  43. else if (name == "controlURL")
  44. controlurl = value;
  45. else if (name == "eventSubURL")
  46. eventsuburl = value;
  47. else if (name == "SCPDURL")
  48. scpdurl = value;
  49. else if (name == "serviceId")
  50. serviceid = value;
  51. }
  52. void UPnPService::clear()
  53. {
  54. servicetype = controlurl = eventsuburl = scpdurl = serviceid = "";
  55. }
  56. UPnPService & UPnPService::operator = (const UPnPService & s)
  57. {
  58. this->servicetype = s.servicetype;
  59. this->controlurl = s.controlurl;
  60. this->eventsuburl = s.eventsuburl;
  61. this->serviceid = s.serviceid;
  62. this->scpdurl = s.scpdurl;
  63. return *this;
  64. }
  65. ///////////////////////////////////////
  66. void UPnPDeviceDescription::setProperty(const QString & name,const QString & value)
  67. {
  68. if (name == "friendlyName")
  69. friendlyName = value;
  70. else if (name == "manufacturer")
  71. manufacturer = value;
  72. else if (name == "modelDescription")
  73. modelDescription = value;
  74. else if (name == "modelName")
  75. modelName = value;
  76. else if (name == "modelNumber")
  77. modelNumber == value;
  78. }
  79. ///////////////////////////////////////
  80. UPnPRouter::UPnPRouter(const QString & server,const KUrl & location,const QString & uuid) : server(server),location(location),uuid(uuid)
  81. {
  82. }
  83. UPnPRouter::~UPnPRouter()
  84. {
  85. QListIterator<Forwarding*> itr(forwards);
  86. while (itr.hasNext())
  87. {
  88. Forwarding *check = itr.next();
  89. undoForward(check->port, check->proto);
  90. }
  91. // We need to give time for the QNetworkManager to process the undo forward commands. Continue
  92. // Processing the event loop from here until there are no more forwards.
  93. while(forwards.size() > 0)
  94. {
  95. QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
  96. }
  97. }
  98. void UPnPRouter::addService(const UPnPService & s)
  99. {
  100. if (!( s.servicetype.contains("WANIPConnection") ||
  101. s.servicetype.contains("WANPPPConnection") ))
  102. return;
  103. // Confirm this service is connected. Place in pending queue.
  104. KJob *req = getStatusInfo(s);
  105. if (req) pending_services[req] = s;
  106. }
  107. void UPnPRouter::downloadFinished(KJob* j)
  108. {
  109. if (j->error())
  110. {
  111. error = i18n("Failed to download %1: %2",location.prettyUrl(),j->errorString());
  112. kDebug() << error << endl;
  113. return;
  114. }
  115. KIO::StoredTransferJob* st = (KIO::StoredTransferJob*)j;
  116. // load in the file (target is always local)
  117. UPnPDescriptionParser desc_parse;
  118. bool ret = desc_parse.parse(st->data(),this);
  119. if (!ret)
  120. {
  121. error = i18n("Error parsing router description.");
  122. }
  123. emit xmlFileDownloaded(this,ret);
  124. }
  125. void UPnPRouter::downloadXMLFile()
  126. {
  127. error.clear();
  128. // downlaod XML description into a temporary file in /tmp
  129. kDebug() << "Downloading XML file " << location << endl;
  130. KIO::Job* job = KIO::storedGet(location,KIO::NoReload, KIO::Overwrite | KIO::HideProgressInfo);
  131. connect(job,SIGNAL(result(KJob*)),this,SLOT(downloadFinished(KJob*)));
  132. }
  133. KJob *UPnPRouter::getStatusInfo(UPnPService s)
  134. {
  135. kDebug() << "UPnP - Checking service status: " << s.servicetype << endl;
  136. QString action = "GetStatusInfo";
  137. QString comm = SOAP::createCommand(action,s.servicetype);
  138. return sendSoapQuery(comm,s.servicetype + '#' + action,s.controlurl);
  139. }
  140. bool UPnPRouter::forward(const QHostAddress & host, quint16 port, QAbstractSocket::SocketType proto)
  141. {
  142. kDebug() << "Forwarding port " << host.toString() << port << " (" << (proto == QAbstractSocket::TcpSocket ? "TCP" : "UDP") << ")" << endl;
  143. if (service.ready)
  144. {
  145. // add all the arguments for the command
  146. QList<SOAP::Arg> args;
  147. SOAP::Arg a;
  148. a.element = "NewRemoteHost";
  149. args.append(a);
  150. // the external port
  151. a.element = "NewExternalPort";
  152. a.value = QString::number(port);
  153. args.append(a);
  154. // the protocol
  155. a.element = "NewProtocol";
  156. a.value = proto == QAbstractSocket::TcpSocket ? "TCP" : "UDP";
  157. args.append(a);
  158. // the local port
  159. a.element = "NewInternalPort";
  160. a.value = QString::number(port);
  161. args.append(a);
  162. // the local IP address
  163. a.element = "NewInternalClient";
  164. a.value = host.toString();
  165. args.append(a);
  166. a.element = "NewEnabled";
  167. a.value = '1';
  168. args.append(a);
  169. a.element = "NewPortMappingDescription";
  170. a.value = QString("Konversation UPNP");
  171. args.append(a);
  172. a.element = "NewLeaseDuration";
  173. a.value = '0';
  174. args.append(a);
  175. QString action = "AddPortMapping";
  176. QString comm = SOAP::createCommand(action,service.servicetype,args);
  177. Forwarding *forward = new Forwarding;
  178. forward->port = port;
  179. forward->host = host;
  180. forward->proto = proto;
  181. if (KJob *req = sendSoapQuery(comm,service.servicetype + '#' + action,service.controlurl))
  182. {
  183. // erase old forwarding if one exists
  184. // The UPnP spec states if an IGD receives a forward request that matches an existing request that it must accept it.
  185. QListIterator<Forwarding*> itr(forwards);
  186. while (itr.hasNext())
  187. {
  188. Forwarding *check = itr.next();
  189. if (check->port == forward->port && check->host == forward->host && check->proto == forward->proto)
  190. {
  191. forwards.removeAll(check);
  192. delete check;
  193. }
  194. }
  195. forwards.append(forward);
  196. pending_forwards[req] = forward;
  197. return true;
  198. }
  199. kDebug() << "Forwarding Failed: Failed to send SOAP query.";
  200. delete forward;
  201. }
  202. kDebug() << "Forwarding Failed: No UPnP Service.";
  203. return false;
  204. }
  205. bool UPnPRouter::undoForward(quint16 port, QAbstractSocket::SocketType proto)
  206. {
  207. kDebug() << "Undoing forward of port " << port
  208. << " (" << (proto == QAbstractSocket::TcpSocket ? "TCP" : "UDP") << ")" << endl;
  209. if (service.ready)
  210. {
  211. Forwarding *forward = NULL;
  212. QListIterator<Forwarding*> itr(forwards);
  213. while (itr.hasNext())
  214. {
  215. Forwarding *check = itr.next();
  216. if (check->port == port && check->proto == proto)
  217. forward = check;
  218. }
  219. if (forward == NULL || pending_forwards.keys(forward).size() > 0)
  220. return false; // Either forward not found or forward is still pending
  221. // add all the arguments for the command
  222. QList<SOAP::Arg> args;
  223. SOAP::Arg a;
  224. a.element = "NewRemoteHost";
  225. args.append(a);
  226. // the external port
  227. a.element = "NewExternalPort";
  228. a.value = QString::number(forward->port);
  229. args.append(a);
  230. // the protocol
  231. a.element = "NewProtocol";
  232. a.value = forward->proto == QAbstractSocket::TcpSocket ? "TCP" : "UDP";
  233. args.append(a);
  234. QString action = "DeletePortMapping";
  235. QString comm = SOAP::createCommand(action,service.servicetype,args);
  236. if (KJob *req = sendSoapQuery(comm,service.servicetype + '#' + action,service.controlurl))
  237. {
  238. pending_unforwards[req] = forward;
  239. return true;
  240. }
  241. kDebug() << "Undo forwarding Failed: Failed to send SOAP query.";
  242. }
  243. kDebug() << "Undo forwarding Failed: No UPnP Service.";
  244. return false;
  245. }
  246. KJob *UPnPRouter::sendSoapQuery(const QString & query,const QString & soapact,const QString & controlurl)
  247. {
  248. // if port is not set, 0 will be returned
  249. // thanks to Diego R. Brogna for spotting this bug
  250. if (location.port()<=0)
  251. location.setPort(80);
  252. KUrl address;
  253. address.setProtocol(QString("http"));
  254. address.setHost(location.host());
  255. address.setPort(location.port());
  256. address.setPath(controlurl);
  257. KIO::TransferJob *req = KIO::http_post( address, query.toAscii(), KIO::HideProgressInfo );
  258. req->addMetaData("content-type", QString("text/xml"));
  259. req->addMetaData("UserAgent", QString("Konversation UPnP"));
  260. req->addMetaData("customHTTPHeader", QString("SOAPAction: ") + soapact);
  261. soap_data_out[req] = QByteArray();
  262. soap_data_in[req] = QByteArray();
  263. connect( req, SIGNAL(data(KIO::Job*,QByteArray)), this, SLOT(recvSoapData(KIO::Job*,QByteArray)) );
  264. connect( req, SIGNAL(dataReq(KIO::Job*,QByteArray&)), this, SLOT(sendSoapData(KIO::Job*,QByteArray&)) );
  265. connect( req, SIGNAL(result(KJob*)), this, SLOT(onRequestFinished(KJob*)) );
  266. return req;
  267. }
  268. void UPnPRouter::sendSoapData(KIO::Job *job, QByteArray &data)
  269. {
  270. data.append(soap_data_out[job]);
  271. soap_data_out[job].clear();
  272. }
  273. void UPnPRouter::recvSoapData(KIO::Job *job, const QByteArray &data)
  274. {
  275. soap_data_in[job].append(data);
  276. }
  277. void UPnPRouter::onRequestFinished(KJob *r)
  278. {
  279. if (r->error())
  280. {
  281. kDebug() << "UPnPRouter : Error: " << r->errorString() << endl;
  282. if (pending_services.contains(r))
  283. {
  284. pending_services.remove(r);
  285. }
  286. else if (pending_forwards.contains(r))
  287. {
  288. emit forwardComplete(true, pending_forwards[r]->port);
  289. forwards.removeAll(pending_forwards[r]);
  290. pending_forwards.remove(r);
  291. }
  292. else if (pending_unforwards.contains(r))
  293. {
  294. emit unforwardComplete(true, pending_unforwards[r]->port);
  295. forwards.removeAll(pending_unforwards[r]);
  296. pending_unforwards.remove(r);
  297. }
  298. }
  299. else
  300. {
  301. QString reply(soap_data_in[r]);
  302. soap_data_in[r].clear();
  303. kDebug() << "UPnPRouter : OK:" << endl;
  304. if (pending_services.contains(r))
  305. {
  306. if (reply.contains("Connected"))
  307. {
  308. // Lets just deal with one connected service for now. Last one wins.
  309. service = pending_services[r];
  310. service.ready = true;
  311. kDebug() << "Found connected service: " << service.servicetype << endl;
  312. }
  313. pending_services.remove(r);
  314. }
  315. else if (pending_forwards.contains(r))
  316. {
  317. emit forwardComplete(false, pending_forwards[r]->port);
  318. pending_forwards.remove(r);
  319. }
  320. else if (pending_unforwards.contains(r))
  321. {
  322. emit unforwardComplete(false, pending_unforwards[r]->port);
  323. forwards.removeAll(pending_unforwards[r]);
  324. pending_unforwards.remove(r);
  325. }
  326. }
  327. soap_data_in.remove(r);
  328. soap_data_out.remove(r);
  329. }
  330. }
  331. }