SshClient.cpp 12 KB

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