/* 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 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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; } myApp->print("Unparsed cmd from central: " + time.toString() + " " + command + " " + commandData + "\n"); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool SshClient::connectToHost(const QString &user, const QString &host) { myProcess->start("ssh", QStringList() << user + "@" + host); myUsername = user; myHostname = host; 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); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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; } }