SshClient.cpp 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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 <QDebug>
  24. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  25. SshClient::SshClient(App* app, QObject *parent) :
  26. QObject(parent),
  27. myApp(app),
  28. myProcess(new QProcess(this)),
  29. 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(.+)$")),
  30. myConnectedFlag(false),
  31. myConnectionTimerID(0),
  32. myPingTimerID(0),
  33. myPongTimerID(0)
  34. {
  35. connect(myProcess, SIGNAL(readyRead()), SLOT(read()));
  36. connect(myProcess, SIGNAL(finished(int)), SLOT(exited(int)));
  37. }
  38. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  39. SshClient::~SshClient()
  40. {
  41. delete myCommandRegex;
  42. }
  43. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  44. bool SshClient::isConnected() const
  45. {
  46. return myConnectedFlag;
  47. }
  48. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  49. void SshClient::read()
  50. {
  51. QString data(myProcess->readAll().trimmed());
  52. if(data.isEmpty())
  53. return;
  54. QStringList lines = data.split("\n", QString::SkipEmptyParts);
  55. QString line;
  56. foreach(line, lines)
  57. {
  58. line = line.trimmed();
  59. if(myCommandRegex->indexIn(line) != -1)
  60. {
  61. QDateTime time = QDateTime::fromString(myCommandRegex->cap(1), "yyyy-MM-dd HH:mm:ss");
  62. time = time.addSecs(-myCommandRegex->cap(2).toInt()/100*60*60);
  63. parse(time,
  64. myCommandRegex->cap(3).section(' ', 0, 0),
  65. myCommandRegex->cap(3).section(' ', 1)
  66. );
  67. }
  68. }
  69. }
  70. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  71. void SshClient::exited(int)
  72. {
  73. reconnect();
  74. }
  75. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  76. void SshClient::ping()
  77. {
  78. write("PING\n");
  79. myPingTimerID = startTimer(30000);
  80. }
  81. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  82. void SshClient::pong()
  83. {
  84. killTimer(myPingTimerID);
  85. myPongTimerID = startTimer(30000);
  86. }
  87. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  88. void SshClient::write(const QString &data)
  89. {
  90. myProcess->write(data.toAscii());
  91. myProcess->waitForBytesWritten();
  92. }
  93. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  94. void SshClient::parse(const QDateTime &time, const QString &command, const QString &commandData)
  95. {
  96. /* JOINED - Another user has just joined central */
  97. if(command == "JOINED")
  98. {
  99. QRegExp a("^User '([a-zA-Z]+)'.+$");
  100. if(a.indexIn(commandData) != -1)
  101. {
  102. if(!myClients.contains(a.capturedTexts().at(1)))
  103. myClients.push_back(a.capturedTexts().at(1));
  104. }
  105. return;
  106. }
  107. /* PARTED - Another user has left central */
  108. if(command == "PARTED")
  109. {
  110. QRegExp a("^User '([a-zA-Z]+)'.+$");
  111. if(a.indexIn(commandData) != -1)
  112. myClients.removeAll(a.capturedTexts().at(1));
  113. return;
  114. }
  115. /* HELLO - Server acknowledge */
  116. if(command == "HELLO")
  117. {
  118. ping();
  119. myConnectedFlag = true;
  120. killTimer(myConnectionTimerID);
  121. emit connected();
  122. return;
  123. }
  124. /* PING PONG - The connection is still up */
  125. if(command == "PONG")
  126. {
  127. pong();
  128. return;
  129. }
  130. /* SYS - Central generic message */
  131. if(command == "SYS")
  132. {
  133. myApp->print("Central Message: " + commandData + "\n");
  134. return;
  135. }
  136. /* BC - Broadcast order from central */
  137. if(command == "BC")
  138. {
  139. QRegExp a("^([A-Za-z0-9]+) (.+),(.+),(.+),'(.+)','(.+)'$");
  140. if(a.indexIn(commandData) == -1)
  141. return;
  142. if(a.cap(2) == "QDEV" && !Settings::globalInstance()->developerMode()) //developer mode not enabled ignore this message from developers
  143. return;
  144. int serverCount, userCount;
  145. myApp->broadcast(a.cap(3) + " " + a.cap(5) + " - " + a.cap(4) + " : " + a.cap(6), &serverCount, &userCount);
  146. if(userCount)
  147. write("BC_RE " + a.cap(1) + " Players=" + QString::number(userCount) + ",Servers=" + QString::number(serverCount) + "\n");
  148. return;
  149. }
  150. /* BC_ID - Broadcast reply with the ID of the broadcast you just generated */
  151. if(command == "BC_ID")
  152. {
  153. //BC_ID 4e5fca581569e168c7a86a0a9a91949f for: QDEV,-dev-,qw://123.123
  154. // qDebug() << commandData;
  155. QRegExp a("^([A-Za-z0-9]+) for: .+,-(.+)-,qw://(.+) [0-9]+/[0-9]+$");
  156. if(a.indexIn(commandData) == -1)
  157. return;
  158. QString hash = a.cap(1);
  159. //QString frequency = a.cap(2);
  160. QString server = a.cap(3);
  161. myApp->setReplyHashAndWaitForReply(server, hash);
  162. return;
  163. }
  164. /* BC_RE - Broadcast reply to increment the counters */
  165. if(command == "BC_RE")
  166. {
  167. QRegExp a("^([A-Za-z0-9]+) (Users|Players)=([0-9]+),(Channels|Servers)=([0-9]+)$");
  168. if(a.indexIn(commandData) == -1)
  169. return;
  170. QString hash = a.cap(1);
  171. QString userType = a.cap(2);
  172. QString channelType = a.cap(4);
  173. int userCount = 0;
  174. int channelCount = 0;
  175. int playerCount = 0;
  176. int serverCount = 0;
  177. if(userType == "Users")
  178. {
  179. userCount = a.cap(3).toInt();
  180. playerCount = 0;
  181. }
  182. else if(userType == "Players")
  183. {
  184. playerCount = a.cap(3).toInt();
  185. userCount = 0;
  186. }
  187. if(channelType == "Channels")
  188. {
  189. channelCount = a.cap(5).toInt();
  190. serverCount = 0;
  191. }
  192. else if(channelType == "Servers")
  193. {
  194. serverCount = a.cap(5).toInt();
  195. channelCount = 0;
  196. }
  197. // qDebug() << commandData;
  198. myApp->incrementReplyCounters(hash, userCount, channelCount, playerCount, serverCount);
  199. return;
  200. }
  201. /* DNS_RE - Reply from REQ_DNS, returns the HostName for the requested ip:port pair */
  202. if(command == "DNS_RE")
  203. {
  204. QRegExp a("^([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]+) ([0-9A-Za-z][A-Za-z0-9\\-\\.]+)$");
  205. if(a.indexIn(commandData) == -1)
  206. return;
  207. myApp->setServerHostName(a.cap(1), a.cap(2));
  208. return;
  209. }
  210. /* COMMANDS - List of commands allowed for me */
  211. if(command == "COMMANDS")
  212. {
  213. myCommands = commandData.split(", ", QString::SkipEmptyParts);
  214. return;
  215. }
  216. /* ROLES - List of roles I exert */
  217. if(command == "ROLES")
  218. {
  219. myRoles = commandData.split(", ", QString::SkipEmptyParts);
  220. return;
  221. }
  222. myApp->print("Unparsed cmd from central: " + time.toString() + " " + command + " " + commandData + "\n");
  223. }
  224. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  225. bool SshClient::connectToHost(const QString &user, const QString &host)
  226. {
  227. myProcess->start("ssh", QStringList() << user + "@" + host);
  228. myUsername = user;
  229. myHostname = host;
  230. bool r = myProcess->waitForStarted();
  231. if(!r)
  232. return false;
  233. else
  234. {
  235. myConnectionTimerID = startTimer(60000*3); // up to 3 minutes of waiting
  236. return true;
  237. }
  238. }
  239. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  240. void SshClient::reconnect()
  241. {
  242. if(!myUsername.isEmpty() && !myHostname.isEmpty())
  243. connectToHost(myUsername, myHostname);
  244. }
  245. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  246. void SshClient::disconnectFromHost()
  247. {
  248. killTimer(myConnectionTimerID);
  249. killTimer(myPingTimerID);
  250. killTimer(myPongTimerID);
  251. myProcess->terminate();
  252. myProcess->waitForFinished();
  253. myConnectedFlag = false;
  254. }
  255. // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  256. void SshClient::timerEvent(QTimerEvent *e)
  257. {
  258. if(e->timerId() == myConnectionTimerID)
  259. {
  260. myApp->print("Timedout while trying to connect to central...\n");
  261. disconnectFromHost();
  262. emit error(ConnectionTimedOutError);
  263. return;
  264. }
  265. if(e->timerId() == myPingTimerID)
  266. {
  267. myApp->print("Ping with no pong! Disconnecting from central...\n");
  268. disconnectFromHost();
  269. emit error(ConnectionTimedOutError);
  270. return;
  271. }
  272. if(e->timerId() == myPongTimerID)
  273. {
  274. killTimer(myPongTimerID);
  275. ping();
  276. return;
  277. }
  278. }