/* * client.cpp * * Created on: 18.01.2013 * Author: sr */ #include "client.h" #include "../serverapp/serverapp.h" #include "../../shared/settings.h" #include "../../shared/util.h" #include #include #include #define CHALLENGE_LEN 20 int Client::_clientIdCounter = 0; /******************************************************************************/ Client::Client(QTcpSocket* socket) : _socket(socket) { assert(socket != nullptr); _authed = 0; _projectionSource = 0; _desiredSource = NO_SOURCE; _isActiveVncClient = false; _vncPort = 0; _isTutor = false; _locked = false; _wantsAttention = false; _id = ++_clientIdCounter; //_ip = _socket->peerAddress().toString(); qDebug("*** Client %s created.", qPrintable(_socket->peerAddress().toString())); // Connect important signals connect(_socket, SIGNAL(disconnected()), this, SLOT(disconnect())); connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(disconnect())); connect(_socket, SIGNAL(sslErrors(const QList &)), this, SLOT(disconnect())); connect(_socket, SIGNAL(readyRead()), this, SLOT(onDataArrival())); // Send challenge _challenge.resize(CHALLENGE_LEN); for (int i = 0; i < CHALLENGE_LEN; ++i) { _challenge[i] = char(qrand() & 0xff); } NetworkMessage msgChlng; msgChlng.setField(_ID, _CHALLENGE); msgChlng.setField(_CHALLENGE, _challenge); msgChlng.writeMessage(_socket); // give client 3 seconds to complete handshake _timerIdAuthTimeout = startTimer(3000); _timerPingTimeout = startTimer(3000); _pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS; } /******************************************************************************/ Client::~Client() { qDebug() << "*** Client" << _host << " destroyed."; _socket->deleteLater(); } /******************************************************************************/ void Client::timerEvent(QTimerEvent* event) { if (event->timerId() == _timerPingTimeout) { if (_pingTimeout < QDateTime::currentMSecsSinceEpoch()) { qDebug() << "Client" << _socket->peerAddress().toString() << "has a ping timeout."; killTimer(_timerPingTimeout); this->disconnect(); } } else if (event->timerId() == _timerIdAuthTimeout) { // Client did not send login request within 3 seconds killTimer(_timerIdAuthTimeout); _timerIdAuthTimeout = 0; this->disconnect(); } else killTimer(event->timerId()); } /******************************************************************************/ void Client::sendMessage(NetworkMessage& message) { if (_socket->state() != QAbstractSocket::ConnectedState) return; message.writeMessage(_socket); if (!message.writeComplete()) { qCritical() << "SendMessage to client " << _name << "@" << _socket->peerAddress().toString() << " failed!"; } } /******************************************************************************/ void Client::removeAttentionInternal() { NetworkMessage msg; _wantsAttention = false; msg.setField(_ID, _ATTENTION); msg.setField(_ENABLE, __FALSE); sendMessage(msg); emit stateChanged(); } /******************************************************************************/ void Client::requestThumb(const QSize& size) { if (_socket->state() != QAbstractSocket::ConnectedState) { qDebug("requestThumb called in bad state"); return; } NetworkMessage msgTmb; msgTmb.setField(_ID, _THUMB); msgTmb.setField(_X, QString::number(size.width())); msgTmb.setField(_Y, QString::number(size.height())); msgTmb.writeMessage(_socket); } /******************************************************************************/ void Client::onDataArrival() { // if (_socket->state() != QAbstractSocket::ConnectedState) { qDebug("dataArrival called in bad state"); return; } while (_socket->bytesAvailable() > 0) { int ret = _fromClient.readMessage(_socket); // let the message read data from socket if (ret == NM_READ_FAILED) { // error parsing msg, disconnect client! this->disconnect(); return; } if (ret == NM_READ_INCOMPLETE) return; if (_fromClient.readComplete()) { // message is complete this->handleMsg(); _fromClient.reset(); } } } /******************************************************************************/ void Client::handleMsg() { _pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS; const QString &id = _fromClient.getFieldString(_ID); if (id.isEmpty()) { qDebug("Received message with empty ID field. ignored."); return; } if (_authed == 2) { // Following messages are only valid of the client is already authenticated if (id == _THUMB) { QImage image; _rawRemoteScreen = _fromClient.getFieldBytes("IMG"); /* size 0 means the client is in exam-mode and therefore doesn't send any thumbnail */ if (_rawRemoteScreen.size() > 0) { if (!image.loadFromData(_rawRemoteScreen)) { qDebug("Could not decode thumbnail image from client."); return; } emit thumbUpdated(this, image); } } else if (id == _VNCSERVER) { // Client tells about startup of vnc server const int port = _fromClient.getFieldString("PORT").toInt(); if (port <= 0) { if (_vncPort <= 0) { qDebug() << "Starting VNC server on client" << _name << " (" << _socket->peerAddress().toString() << ":" << QString::number(_vncPort) << ") failed."; } else { qDebug() << "Client " << _name << " stopped its VNC server"; } } else { _vncRoPass = _fromClient.getFieldString("ROPASS"); _vncRwPass = _fromClient.getFieldString("RWPASS"); qDebug() << "Client " << _name << " started its VNC server"; } _vncPort = port; emit vncServerStateChange(this); emit stateChanged(); } else if (id == _VNCCLIENT) { // Client tells us that it started or stopped displaying a remote screen via VNC const int projectionSource = _fromClient.getFieldString("CLIENTID").toInt(); if (_fromClient.getFieldString("ENABLED").toInt() != 0) { qDebug() << "Client " << _name << " started its VNC client (watching " << projectionSource << ")"; _isActiveVncClient = true; _projectionSource = projectionSource; emit vncClientStateChange(this); } else { qDebug() << "Client " << _name << " stopped its VNC client (watched " << projectionSource << ")"; _isActiveVncClient = false; emit vncClientStateChange(this); } emit stateChanged(); } else if (id == _ATTENTION) { _wantsAttention = _fromClient.getFieldString(_ENABLE) == __TRUE; emit stateChanged(); } return; } // Not authed yet, only care about login requests if (_authed == 1) { if (id == _LOGIN) { killTimer(_timerIdAuthTimeout); _timerIdAuthTimeout = 0; ClientLogin request; request.accept = true; request.host = _fromClient.getFieldString("HOST"); request.name = _fromClient.getFieldString("NAME"); request.examMode = _fromClient.getFieldString(_EXAMMODE) == __TRUE; request.ip = _socket->peerAddress().toString(); qDebug() << "Login request by " << request.name << (request.examMode ? "(in exam mode)" : ""); // emit event, see if request is accepted emit authenticating(this, &request); if (!request.accept) { qDebug("Request denied."); this->disconnect(); // Nope return; } // Accepted _authed = 2; qDebug("valid, step <- 2"); _name = request.name; _host = request.host; NetworkMessage msgLogin; msgLogin.setField(_ID, _LOGIN); msgLogin.writeMessage(_socket); emit authenticated(this); } return; } // Did not pass challenge yet if (_authed == 0) { // Waiting for challenge reply by client if (id == _CHALLENGE) { QByteArray hash(_fromClient.getFieldBytes(_HASH)); QByteArray challenge(_fromClient.getFieldBytes(_CHALLENGE)); if (genSha1(&serverApp->sessionNameArray(), &_challenge) != hash && !(serverApp->getCurrentRoom()->clientPositions.contains(_socket->peerAddress().toString()))) { // Challenge reply is invalid, drop client NetworkMessage msgErr; msgErr.buildErrorMessage("Challenge reply invalid."); msgErr.writeMessage(_socket); this->disconnect(); return; } // Now answer to challenge by client NetworkMessage msgChlng; msgChlng.setField(_ID, _CHALLENGE); msgChlng.setField(_HASH, genSha1(&serverApp->sessionNameArray(), &challenge)); msgChlng.writeMessage(_socket); _authed = 1; qDebug("client's challenge reply was valid, step <- 1"); } return; } } /******************************************************************************/ void Client::startVncServer() { NetworkMessage msg; msg.setField(_ID, _VNCSERVER); msg.setField(_ENABLE, __TRUE); sendMessage(msg); } /******************************************************************************/ void Client::stopVncServer() { NetworkMessage msg; msg.setField(_ID, _VNCSERVER); msg.setField(_ENABLE, __FALSE); sendMessage(msg); } /******************************************************************************/ void Client::startVncClient(const Client * const to) { NetworkMessage msg; msg.setField(_ID, _VNCCLIENT); msg.setField("HOST", to->_socket->peerAddress().toString()); msg.setField(_PORT, QString::number(to->_vncPort)); msg.setField("ROPASS", to->_vncRoPass); msg.setField("CLIENTID", QString::number(to->_id)); msg.setField("CAPTION", to->_name + " @ " + to->_host); if (!to->_rawRemoteScreen.isEmpty()) { msg.setField(_THUMB, to->_rawRemoteScreen); } sendMessage(msg); } /******************************************************************************/ void Client::stopVncClient() { if (_isActiveVncClient) { NetworkMessage msg; msg.setField(_ID, _VNCCLIENT); sendMessage(msg); } _desiredSource = NO_SOURCE; } /***************************************************************************//** * Checks if client and manager runs on same machine. * @return Return true, if pvsmanager is running on client. */ bool Client::isManagerMachine() { foreach (const QHostAddress & address, QNetworkInterface::allAddresses()) if (address != QHostAddress(QHostAddress::LocalHost) && this->ip() == address.toString()) return true; return false; } /******************************************************************************/ void Client::lockScreen(bool lock) { if (_isTutor || isManagerMachine()) { lock = false; } if (_locked != lock) { _locked = lock; NetworkMessage msg; msg.setField(_ID, _LOCK); msg.setField(_ENABLE, _BOOL(lock)); sendMessage(msg); } emit stateChanged(); } /******************************************************************************/ void Client::disconnect() { qDebug("*** Client %s disconnected.", qPrintable(_socket->peerAddress().toString())); _socket->blockSignals(true); _socket->abort(); this->deleteLater(); emit disconnected(); }