SshClient.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. /*
  2. GNU General Public License version 3 notice
  3. Copyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see < http://www.gnu.org/licenses/ >.
  14. */
  15. #include "SshClient.h"
  16. #include "App.h"
  17. #include "Settings.h"
  18. #include <QProcess>
  19. #include <QRegExp>
  20. #include <QDateTime>
  21. #include <QTimerEvent>
  22. #include <QStringList>
  23. #include <Pinger.h>
  24. #include <QDebug>
  25. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  26. SshClient::SshClient(App* app, QObject *parent) :
  27. QObject(parent),
  28. myApp(app),
  29. myProcess(new QProcess(this)),
  30. myCommandRegex(new QRegExp("^([0-9]{4}-[0-9]{2}-[0-9]{2}\\s[0-9]{2}:[0-9]{2}:[0-9]{2})\\s(\\+[0-9]{4}):\\s(.+)$")),
  31. myConnectedFlag(false),
  32. myConnectionTimerID(0),
  33. myPingTimerID(0),
  34. myPongTimerID(0)
  35. {
  36. connect(myProcess, SIGNAL(readyRead()), SLOT(read()));
  37. connect(myProcess, SIGNAL(finished(int)), SLOT(exited(int)));
  38. }
  39. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  40. SshClient::~SshClient()
  41. {
  42. delete myCommandRegex;
  43. }
  44. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  45. bool SshClient::isConnected() const
  46. {
  47. return myConnectedFlag;
  48. }
  49. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  50. void SshClient::read()
  51. {
  52. QString data(myProcess->readAll().trimmed());
  53. if(data.isEmpty())
  54. return;
  55. QStringList lines = data.split("\n", QString::SkipEmptyParts);
  56. QString line;
  57. foreach(line, lines)
  58. {
  59. line = line.trimmed();
  60. if(myCommandRegex->indexIn(line) != -1)
  61. {
  62. QDateTime time = QDateTime::fromString(myCommandRegex->cap(1), "yyyy-MM-dd HH:mm:ss");
  63. time = time.addSecs(-myCommandRegex->cap(2).toInt()/100*60*60);
  64. parse(time,
  65. myCommandRegex->cap(3).section(' ', 0, 0),
  66. myCommandRegex->cap(3).section(' ', 1)
  67. );
  68. }
  69. }
  70. }
  71. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  72. void SshClient::exited(int)
  73. {
  74. reconnect();
  75. }
  76. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  77. void SshClient::ping()
  78. {
  79. write("PING\n");
  80. myPingTimerID = startTimer(30000);
  81. }
  82. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  83. void SshClient::pong()
  84. {
  85. killTimer(myPingTimerID);
  86. myPingTimerID = -1;
  87. myPongTimerID = startTimer(30000);
  88. }
  89. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  90. void SshClient::write(const QString &data)
  91. {
  92. myProcess->write(data.toAscii());
  93. myProcess->waitForBytesWritten();
  94. }
  95. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  96. void SshClient::parse(const QDateTime &time, const QString &command, const QString &commandData)
  97. {
  98. /* JOINED - Another user has just joined central */
  99. if(command == "JOINED")
  100. {
  101. QRegExp a("^User '([a-zA-Z]+)'.+$");
  102. if(a.indexIn(commandData) != -1)
  103. {
  104. if(!myClients.contains(a.capturedTexts().at(1)))
  105. myClients.push_back(a.capturedTexts().at(1));
  106. }
  107. return;
  108. }
  109. /* PARTED - Another user has left central */
  110. if(command == "PARTED")
  111. {
  112. QRegExp a("^User '([a-zA-Z]+)'.+$");
  113. if(a.indexIn(commandData) != -1)
  114. myClients.removeAll(a.capturedTexts().at(1));
  115. return;
  116. }
  117. /* HELLO - Server acknowledge */
  118. if(command == "HELLO")
  119. {
  120. ping();
  121. myConnectedFlag = true;
  122. killTimer(myConnectionTimerID);
  123. myConnectionTimerID = -1;
  124. emit connected();
  125. return;
  126. }
  127. /* PING PONG - The connection is still up */
  128. if(command == "PONG")
  129. {
  130. pong();
  131. return;
  132. }
  133. /* SYS - Central generic message */
  134. if(command == "SYS")
  135. {
  136. myApp->print("Central Message: " + commandData + "\n");
  137. return;
  138. }
  139. /* BC - Broadcast order from central */
  140. if(command == "BC")
  141. {
  142. QRegExp a("^([A-Za-z0-9]+) (.+),(.+),(.+),'(.+)','(.+)'$");
  143. if(a.indexIn(commandData) == -1)
  144. return;
  145. if(a.cap(2) == "QDEV" && !Settings::globalInstance()->developerMode()) //developer mode not enabled ignore this message from developers
  146. return;
  147. int serverCount, userCount;
  148. myApp->broadcast(a.cap(3) + " " + a.cap(5) + " - " + a.cap(4) + " : " + a.cap(6), &serverCount, &userCount);
  149. if(userCount)
  150. write("BC_RE " + a.cap(1) + " Players=" + QString::number(userCount) + ",Servers=" + QString::number(serverCount) + "\n");
  151. return;
  152. }
  153. /* BC_ID - Broadcast reply with the ID of the broadcast you just generated */
  154. if(command == "BC_ID")
  155. {
  156. //BC_ID 4e5fca581569e168c7a86a0a9a91949f for: QDEV,-dev-,qw://123.123
  157. // qDebug() << commandData;
  158. QRegExp a("^([A-Za-z0-9]+) for: .+,-(.+)-,qw://(.+) [0-9]+/[0-9]+$");
  159. if(a.indexIn(commandData) == -1)
  160. return;
  161. QString hash = a.cap(1);
  162. //QString frequency = a.cap(2);
  163. QString server = a.cap(3);
  164. myApp->setReplyHashAndWaitForReply(server, hash);
  165. return;
  166. }
  167. /* BC_RE - Broadcast reply to increment the counters */
  168. if(command == "BC_RE")
  169. {
  170. QRegExp a("^([A-Za-z0-9]+) (Users|Players)=([0-9]+),(Channels|Servers)=([0-9]+)$");
  171. if(a.indexIn(commandData) == -1)
  172. return;
  173. QString hash = a.cap(1);
  174. QString userType = a.cap(2);
  175. QString channelType = a.cap(4);
  176. int userCount = 0;
  177. int channelCount = 0;
  178. int playerCount = 0;
  179. int serverCount = 0;
  180. if(userType == "Users")
  181. {
  182. userCount = a.cap(3).toInt();
  183. playerCount = 0;
  184. }
  185. else if(userType == "Players")
  186. {
  187. playerCount = a.cap(3).toInt();
  188. userCount = 0;
  189. }
  190. if(channelType == "Channels")
  191. {
  192. channelCount = a.cap(5).toInt();
  193. serverCount = 0;
  194. }
  195. else if(channelType == "Servers")
  196. {
  197. serverCount = a.cap(5).toInt();
  198. channelCount = 0;
  199. }
  200. // qDebug() << commandData;
  201. myApp->incrementReplyCounters(hash, userCount, channelCount, playerCount, serverCount);
  202. return;
  203. }
  204. /* DNS_RE - Reply from REQ_DNS, returns the HostName for the requested ip:port pair */
  205. if(command == "DNS_RE")
  206. {
  207. QRegExp a("^([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]+) ([0-9A-Za-z][A-Za-z0-9\\-\\.]+)$");
  208. if(a.indexIn(commandData) == -1)
  209. return;
  210. myApp->setServerHostName(a.cap(1), a.cap(2));
  211. return;
  212. }
  213. /* COMMANDS - List of commands allowed for me */
  214. if(command == "COMMANDS")
  215. {
  216. myCommands = commandData.split(", ", QString::SkipEmptyParts);
  217. return;
  218. }
  219. /* ROLES - List of roles I exert */
  220. if(command == "ROLES")
  221. {
  222. myRoles = commandData.split(", ", QString::SkipEmptyParts);
  223. return;
  224. }
  225. /* REQ_ASSIGNMENTS - Central is requesting the server list I have */
  226. if(command == "REQ_ASSIGNMENTS")
  227. {
  228. myApp->print("Server list requested... Sending it now!\n");
  229. // TODO: Maybe fetching server data directly from ActiveClients list is a better idea
  230. Settings::ServerList serverList = Settings::globalInstance()->serverList();
  231. Settings::Server server;
  232. foreach(server, serverList)
  233. {
  234. // Let the reply be send after the pinging is done
  235. Pinger* pinger = new Pinger(QHostAddress(server.address), server.port, this);
  236. connect(pinger, SIGNAL(finished(QHostAddress,quint16,int)), SLOT(assignmentsReply(QHostAddress,quint16,int)));
  237. pinger->ping();
  238. }
  239. return;
  240. }
  241. /* REQ_PING - Requests an given server ping */
  242. if(command == "REQ_PING")
  243. {
  244. QStringList splitCmd = commandData.split(":");
  245. if(splitCmd.size() < 2)
  246. {
  247. write("Malformed REQ_PING command.\n");
  248. return;
  249. }
  250. QString server = splitCmd.at(0);
  251. quint16 port = splitCmd.at(1).toUShort();
  252. Pinger* pinger = new Pinger(QHostAddress(server), port, this);
  253. connect(pinger, SIGNAL(finished(QHostAddress,quint16,int)), SLOT(serverPong(QHostAddress,quint16,int)));
  254. pinger->ping();
  255. return;
  256. }
  257. /* REQ_ASSIGN */
  258. if(command == "REQ_ASSIGN")
  259. {
  260. QRegExp rx("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d{1,5})");
  261. if(rx.indexIn(commandData) == -1)
  262. {
  263. write("Malformed REQ_ASSIGN request.\n");
  264. return;
  265. }
  266. if(myApp->addClient(rx.cap(1), rx.cap(2).toUShort()))
  267. write("ASSIGN_RE " + rx.cap(1) + ":" + rx.cap(2) + " OK\n");
  268. else
  269. write("ASSIGN_RE " + rx.cap(1) + ":" + rx.cap(2) + " FAILED Client already on my list.\n");
  270. return;
  271. }
  272. /* REQ_UNASSIGN */
  273. if(command == "REQ_UNASSIGN")
  274. {
  275. QRegExp rx("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d{1,5})");
  276. if(rx.indexIn(commandData) == -1)
  277. {
  278. write("Malformed REQ_UNASSIGN request.\n");
  279. return;
  280. }
  281. if(myApp->removeClient(rx.cap(1), rx.cap(2).toUShort()))
  282. write("UNASSIGN_RE " + rx.cap(1) + ":" + rx.cap(2) + " OK\n");
  283. else
  284. write("UNASSIGN_RE " + rx.cap(1) + ":" + rx.cap(2) + " FAILED Client not found on my list.\n");
  285. return;
  286. }
  287. /* REQ_MAXSERVERS - Reply with the maximum number of servers we can handle */
  288. if(command == "REQ_MAXSERVERS")
  289. {
  290. write("MAXSERVERS_RE " + QString::number(Settings::globalInstance()->maxServers()) + "\n");
  291. return;
  292. }
  293. myApp->print("Unparsed cmd from central: " + time.toString() + " " + command + " " + commandData + "\n");
  294. }
  295. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  296. bool SshClient::connectToHost(const QString &user, const QString &host, quint16 port)
  297. {
  298. myProcess->start("ssh", QStringList() << user + "@" + host << "-p " + QString::number(port));
  299. myUsername = user;
  300. myHostname = host;
  301. myPort = port;
  302. bool r = myProcess->waitForStarted();
  303. if(!r)
  304. return false;
  305. else
  306. {
  307. myConnectionTimerID = startTimer(60000*3); // up to 3 minutes of waiting
  308. return true;
  309. }
  310. }
  311. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  312. void SshClient::reconnect()
  313. {
  314. if(!myUsername.isEmpty() && !myHostname.isEmpty())
  315. connectToHost(myUsername, myHostname, myPort);
  316. }
  317. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  318. void SshClient::disconnectFromHost()
  319. {
  320. killTimer(myConnectionTimerID);
  321. killTimer(myPingTimerID);
  322. killTimer(myPongTimerID);
  323. myConnectionTimerID = myPingTimerID = myPongTimerID = -1;
  324. myProcess->terminate();
  325. myProcess->waitForFinished();
  326. myConnectedFlag = false;
  327. }
  328. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  329. void SshClient::timerEvent(QTimerEvent *e)
  330. {
  331. if(e->timerId() == myConnectionTimerID)
  332. {
  333. myApp->print("Timedout while trying to connect to central...\n");
  334. disconnectFromHost();
  335. emit error(ConnectionTimedOutError);
  336. return;
  337. }
  338. if(e->timerId() == myPingTimerID)
  339. {
  340. myApp->print("Ping with no pong! Disconnecting from central...\n");
  341. disconnectFromHost();
  342. emit error(ConnectionTimedOutError);
  343. return;
  344. }
  345. if(e->timerId() == myPongTimerID)
  346. {
  347. killTimer(myPongTimerID);
  348. myPongTimerID = -1;
  349. ping();
  350. return;
  351. }
  352. }
  353. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  354. void SshClient::serverPong(const QHostAddress &host, quint16 port, int ms)
  355. {
  356. write("PING_RE " + host.toString() + ":" + QString::number(port) + "," + QString::number(ms) + "\n");
  357. sender()->deleteLater();
  358. }
  359. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  360. void SshClient::assignmentsReply(const QHostAddress &host, quint16 port, int ms)
  361. {
  362. QString fullAddress = host.toString() + ":" + QString::number(port);
  363. write("ASSIGNMENTS_RE " + host.toString() + ":" + QString::number(port) + "," + QString::number(ms) + "\n");
  364. sender()->deleteLater();
  365. }