/* GNU General Public License version 3 notice Copyright (C) 2012 Mihawk . 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 #include #include #include #include #include #include // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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.toLatin1()); 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(); }