123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- /*
- GNU General Public License version 3 notice
- Copyright (C) 2012 Mihawk <luiz@netdome.biz>. All rights reserved.
- 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 3 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, see < http://www.gnu.org/licenses/ >.
- */
- #include "SshClient.h"
- #include "App.h"
- #include "Settings.h"
- #include <QProcess>
- #include <QRegExp>
- #include <QDateTime>
- #include <QTimerEvent>
- #include <QStringList>
- #include <Pinger.h>
- #include <QDebug>
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- SshClient::SshClient(App* app, QObject *parent) :
- QObject(parent),
- myApp(app),
- myProcess(new QProcess(this)),
- 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(.+)$")),
- myConnectedFlag(false),
- myConnectionTimerID(0),
- myPingTimerID(0),
- myPongTimerID(0)
- {
- connect(myProcess, SIGNAL(readyRead()), SLOT(read()));
- connect(myProcess, SIGNAL(finished(int)), SLOT(exited(int)));
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- SshClient::~SshClient()
- {
- delete myCommandRegex;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- bool SshClient::isConnected() const
- {
- return myConnectedFlag;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::read()
- {
- QString data(myProcess->readAll().trimmed());
- if(data.isEmpty())
- return;
- QStringList lines = data.split("\n", QString::SkipEmptyParts);
- QString line;
- foreach(line, lines)
- {
- line = line.trimmed();
- if(myCommandRegex->indexIn(line) != -1)
- {
- QDateTime time = QDateTime::fromString(myCommandRegex->cap(1), "yyyy-MM-dd HH:mm:ss");
- time = time.addSecs(-myCommandRegex->cap(2).toInt()/100*60*60);
- parse(time,
- myCommandRegex->cap(3).section(' ', 0, 0),
- myCommandRegex->cap(3).section(' ', 1)
- );
- }
- }
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::exited(int)
- {
- reconnect();
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::ping()
- {
- write("PING\n");
- myPingTimerID = startTimer(30000);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::pong()
- {
- killTimer(myPingTimerID);
- myPingTimerID = -1;
- myPongTimerID = startTimer(30000);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::write(const QString &data)
- {
- myProcess->write(data.toAscii());
- myProcess->waitForBytesWritten();
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::parse(const QDateTime &time, const QString &command, const QString &commandData)
- {
- /* JOINED - Another user has just joined central */
- if(command == "JOINED")
- {
- QRegExp a("^User '([a-zA-Z]+)'.+$");
- if(a.indexIn(commandData) != -1)
- {
- if(!myClients.contains(a.capturedTexts().at(1)))
- myClients.push_back(a.capturedTexts().at(1));
- }
- return;
- }
- /* PARTED - Another user has left central */
- if(command == "PARTED")
- {
- QRegExp a("^User '([a-zA-Z]+)'.+$");
- if(a.indexIn(commandData) != -1)
- myClients.removeAll(a.capturedTexts().at(1));
- return;
- }
- /* HELLO - Server acknowledge */
- if(command == "HELLO")
- {
- ping();
- myConnectedFlag = true;
- killTimer(myConnectionTimerID);
- myConnectionTimerID = -1;
- emit connected();
- return;
- }
- /* PING PONG - The connection is still up */
- if(command == "PONG")
- {
- pong();
- return;
- }
- /* SYS - Central generic message */
- if(command == "SYS")
- {
- myApp->print("Central Message: " + commandData + "\n");
- return;
- }
- /* BC - Broadcast order from central */
- if(command == "BC")
- {
- QRegExp a("^([A-Za-z0-9]+) (.+),(.+),(.+),'(.+)','(.+)'$");
- if(a.indexIn(commandData) == -1)
- return;
- if(a.cap(2) == "QDEV" && !Settings::globalInstance()->developerMode()) //developer mode not enabled ignore this message from developers
- return;
- int serverCount, userCount;
- myApp->broadcast(a.cap(3) + " " + a.cap(5) + " - " + a.cap(4) + " : " + a.cap(6), &serverCount, &userCount);
- if(userCount)
- write("BC_RE " + a.cap(1) + " Players=" + QString::number(userCount) + ",Servers=" + QString::number(serverCount) + "\n");
- return;
- }
- /* BC_ID - Broadcast reply with the ID of the broadcast you just generated */
- if(command == "BC_ID")
- {
- //BC_ID 4e5fca581569e168c7a86a0a9a91949f for: QDEV,-dev-,qw://123.123
- // qDebug() << commandData;
- QRegExp a("^([A-Za-z0-9]+) for: .+,-(.+)-,qw://(.+) [0-9]+/[0-9]+$");
- if(a.indexIn(commandData) == -1)
- return;
- QString hash = a.cap(1);
- //QString frequency = a.cap(2);
- QString server = a.cap(3);
- myApp->setReplyHashAndWaitForReply(server, hash);
- return;
- }
- /* BC_RE - Broadcast reply to increment the counters */
- if(command == "BC_RE")
- {
- QRegExp a("^([A-Za-z0-9]+) (Users|Players)=([0-9]+),(Channels|Servers)=([0-9]+)$");
- if(a.indexIn(commandData) == -1)
- return;
- QString hash = a.cap(1);
- QString userType = a.cap(2);
- QString channelType = a.cap(4);
- int userCount = 0;
- int channelCount = 0;
- int playerCount = 0;
- int serverCount = 0;
- if(userType == "Users")
- {
- userCount = a.cap(3).toInt();
- playerCount = 0;
- }
- else if(userType == "Players")
- {
- playerCount = a.cap(3).toInt();
- userCount = 0;
- }
- if(channelType == "Channels")
- {
- channelCount = a.cap(5).toInt();
- serverCount = 0;
- }
- else if(channelType == "Servers")
- {
- serverCount = a.cap(5).toInt();
- channelCount = 0;
- }
- // qDebug() << commandData;
- myApp->incrementReplyCounters(hash, userCount, channelCount, playerCount, serverCount);
- return;
- }
- /* DNS_RE - Reply from REQ_DNS, returns the HostName for the requested ip:port pair */
- if(command == "DNS_RE")
- {
- QRegExp a("^([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+:[0-9]+) ([0-9A-Za-z][A-Za-z0-9\\-\\.]+)$");
- if(a.indexIn(commandData) == -1)
- return;
- myApp->setServerHostName(a.cap(1), a.cap(2));
- return;
- }
- /* COMMANDS - List of commands allowed for me */
- if(command == "COMMANDS")
- {
- myCommands = commandData.split(", ", QString::SkipEmptyParts);
- return;
- }
- /* ROLES - List of roles I exert */
- if(command == "ROLES")
- {
- myRoles = commandData.split(", ", QString::SkipEmptyParts);
- return;
- }
- /* REQ_ASSIGNMENTS - Central is requesting the server list I have */
- if(command == "REQ_ASSIGNMENTS")
- {
- myApp->print("Server list requested... Sending it now!\n");
- // TODO: Maybe fetching server data directly from ActiveClients list is a better idea
- Settings::ServerList serverList = Settings::globalInstance()->serverList();
- Settings::Server server;
- foreach(server, serverList)
- {
- // Let the reply be send after the pinging is done
- Pinger* pinger = new Pinger(QHostAddress(server.address), server.port, this);
- connect(pinger, SIGNAL(finished(QHostAddress,quint16,int)), SLOT(assignmentsReply(QHostAddress,quint16,int)));
- pinger->ping();
- }
- return;
- }
- /* REQ_PING - Requests an given server ping */
- if(command == "REQ_PING")
- {
- QStringList splitCmd = commandData.split(":");
- if(splitCmd.size() < 2)
- {
- write("Malformed REQ_PING command.\n");
- return;
- }
- QString server = splitCmd.at(0);
- quint16 port = splitCmd.at(1).toUShort();
- Pinger* pinger = new Pinger(QHostAddress(server), port, this);
- connect(pinger, SIGNAL(finished(QHostAddress,quint16,int)), SLOT(serverPong(QHostAddress,quint16,int)));
- pinger->ping();
- return;
- }
- /* REQ_ASSIGN */
- if(command == "REQ_ASSIGN")
- {
- QRegExp rx("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d{1,5})");
- if(rx.indexIn(commandData) == -1)
- {
- write("Malformed REQ_ASSIGN request.\n");
- return;
- }
- if(myApp->addClient(rx.cap(1), rx.cap(2).toUShort()))
- write("ASSIGN_RE " + rx.cap(1) + ":" + rx.cap(2) + " OK\n");
- else
- write("ASSIGN_RE " + rx.cap(1) + ":" + rx.cap(2) + " FAILED Client already on my list.\n");
- return;
- }
- /* REQ_UNASSIGN */
- if(command == "REQ_UNASSIGN")
- {
- QRegExp rx("(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d{1,5})");
- if(rx.indexIn(commandData) == -1)
- {
- write("Malformed REQ_UNASSIGN request.\n");
- return;
- }
- if(myApp->removeClient(rx.cap(1), rx.cap(2).toUShort()))
- write("UNASSIGN_RE " + rx.cap(1) + ":" + rx.cap(2) + " OK\n");
- else
- write("UNASSIGN_RE " + rx.cap(1) + ":" + rx.cap(2) + " FAILED Client not found on my list.\n");
- return;
- }
- /* REQ_MAXSERVERS - Reply with the maximum number of servers we can handle */
- if(command == "REQ_MAXSERVERS")
- {
- write("MAXSERVERS_RE " + QString::number(Settings::globalInstance()->maxServers()) + "\n");
- return;
- }
- myApp->print("Unparsed cmd from central: " + time.toString() + " " + command + " " + commandData + "\n");
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- bool SshClient::connectToHost(const QString &user, const QString &host, quint16 port)
- {
- myProcess->start("ssh", QStringList() << user + "@" + host << "-p " + QString::number(port));
- myUsername = user;
- myHostname = host;
- myPort = port;
- bool r = myProcess->waitForStarted();
- if(!r)
- return false;
- else
- {
- myConnectionTimerID = startTimer(60000*3); // up to 3 minutes of waiting
- return true;
- }
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::reconnect()
- {
- if(!myUsername.isEmpty() && !myHostname.isEmpty())
- connectToHost(myUsername, myHostname, myPort);
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::disconnectFromHost()
- {
- killTimer(myConnectionTimerID);
- killTimer(myPingTimerID);
- killTimer(myPongTimerID);
- myConnectionTimerID = myPingTimerID = myPongTimerID = -1;
- myProcess->terminate();
- myProcess->waitForFinished();
- myConnectedFlag = false;
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::timerEvent(QTimerEvent *e)
- {
- if(e->timerId() == myConnectionTimerID)
- {
- myApp->print("Timedout while trying to connect to central...\n");
- disconnectFromHost();
- emit error(ConnectionTimedOutError);
- return;
- }
- if(e->timerId() == myPingTimerID)
- {
- myApp->print("Ping with no pong! Disconnecting from central...\n");
- disconnectFromHost();
- emit error(ConnectionTimedOutError);
- return;
- }
- if(e->timerId() == myPongTimerID)
- {
- killTimer(myPongTimerID);
- myPongTimerID = -1;
- ping();
- return;
- }
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::serverPong(const QHostAddress &host, quint16 port, int ms)
- {
- write("PING_RE " + host.toString() + ":" + QString::number(port) + "," + QString::number(ms) + "\n");
- sender()->deleteLater();
- }
- // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- void SshClient::assignmentsReply(const QHostAddress &host, quint16 port, int ms)
- {
- QString fullAddress = host.toString() + ":" + QString::number(port);
- write("ASSIGNMENTS_RE " + host.toString() + ":" + QString::number(port) + "," + QString::number(ms) + "\n");
- sender()->deleteLater();
- }
|