summaryrefslogblamecommitdiffstats
path: root/src/server/net/client.cpp
blob: 51ffb618365dfaeb3918e4ee8ba0dc12aa474c00 (plain) (tree)
1
2
3
4
5
6
7
8
9







                          
                                   

                                  
 

                  
                            

                     
                 


                        
                                 

                                  
 
                                  
                                   
                                    
                                 

                                                                                        
                                    
                                                   


                                                             
                                                                                                 


                                                              


                                                                                             


                                                                  


                                                

                                         
                                                 
                                                       
         



                                                      
                                                      

                                               
                                                                             



                 
                                                             




                                        



                                           

                                                                         
                                                     
                                                                                         
                 
                                                             


                                                                     
                                                                               
              
                                            



                                                 
                                                                

                                      
                                       
                                                                                                                           


         









                                       
                                            
 
                                                                  


                                                           

                                     

                                                            
                                     



                            
          
                                                                  



                                                          
                                               
                                                                                                    
                                                                                     
                                                                                    

                               

                                              
                                                                        
                                          
                                            



                 

                        
                                                                             
                                                            
                           


                                                                         
 
                           
                                                                                           
                                   

                                                                            
                                                                                                              

                                                                            


                                                                                                
                                                               
                         
                                              

                                                                                    

                                                    
                                                                                                                                                                                             
                                        

                                                                                                    
                                

                                                                                  
                                                                                            


                                                        
                                            
                                              
                                                                                                        
                                                                                                    
                                                                                 
                                                                                                                                  
                                                          
                                                                     
                                                                
                                
                                                                                                                                 
                                                           
                                                                
                         
                                            


                                                                                        



                       
                                                         

                                   





                                                                          
                                                                                           
                                                                       

                                                                                                                      

                                                                 
                                              
                                                                                  






                                                   


                                                       




                                                 
                                     
                           
                                                        
                                       

                                                                                    

                                                                                                                                 
                                                                          
                                                                             


                                                            

                                                           
                                                                                                      
                                                       









                                                                                



                                      



                            



                                       

 
                                                    
 
                                                               
                                                                    

                                      
                                                                    
                                                           


                                                               
                                              

                                                           
                         

 
                            
 
                                 



                                              
                                   

 
   


                                                           
                                     
 

                                                                                 

                                                    


                     

                                  



                                             
                               

                                         
                                                   

                                 
                            

 
                                           
 






                                                                                                   
                                    

                            
 




                                                 
/*
 * 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 <QPixmap>
#include <cassert>
#include <QNetworkInterface>
#include <QTcpSocket>
#include <QSslSocket>
#include <QTimer>

#define CHALLENGE_LEN 20

int Client::_clientIdCounter = 0;
Client::Client(QTcpSocket* socket)
		: _socket(socket)
{
	assert(socket != nullptr);
	_desiredSource = NO_SOURCE;
	_socket->setParent(nullptr);
	_id = ++_clientIdCounter;
	//_ip = _socket->peerAddress().toString();
	qDebug("*** Client %s created.", qPrintable(_socket->peerAddress().toString()));
	// Connect important signals
	connect(_socket, &QTcpSocket::disconnected,
			[this]() {
		this->disconnect("Client closed connection");
	});
	connect(_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
			[this](QAbstractSocket::SocketError) {
		this->disconnect("Client socket error");
	});
	auto *ssl = qobject_cast<QSslSocket*>(_socket);
	if (ssl != nullptr) {
		connect(ssl, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
				[this](const QList<QSslError> &) {
			this->disconnect("Client SSL Errors");
		});
	}
	connect(_socket, &QTcpSocket::readyRead,
	        this, &Client::onDataArrival);
	// Send challenge
	_challenge.resize(CHALLENGE_LEN);
	for (int i = 0; i < CHALLENGE_LEN; ++i) {
		_challenge[i] = char(slxrand() & 0xff);
	}
	NetworkMessage msgChallenge;
	msgChallenge.setField(_ID, _CHALLENGE);
	msgChallenge.setField(_CHALLENGE, _challenge);
	msgChallenge.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->blockSignals(true);
	QTcpSocket *sck = _socket;
	QTimer::singleShot(10, [sck]() {
		sck->deleteLater();
	});
}

void Client::timerEvent(QTimerEvent* event)
{
	if (event->timerId() == _timerPingTimeout) {
		if (_pingTimeout < QDateTime::currentMSecsSinceEpoch()) {
			killTimer(_timerPingTimeout);
			this->disconnect("Disconnecting client because of ping timeout");
		}
	} else if (event->timerId() == _timerIdAuthTimeout) {
		// Client did not send login request within 3 seconds
		killTimer(_timerIdAuthTimeout);
		_timerIdAuthTimeout = 0;
		this->disconnect("Did not authenticate withing three seconds");
	} 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("Malformed message received from client.");
			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) {
				this->disconnect("Login request denied."); // 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
				this->disconnect("Challenge reply invalid.");
				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)
{
	if (_isActiveVncClient && _projectionSource == to->_id)
		return; // Already watching given target, do nothing
	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() const
{
	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(const char *errmsg)
{
	qDebug() << "*** Client" << _socket->peerAddress().toString() << "disconnected:" << errmsg;
	if (_socket->state() == QAbstractSocket::ConnectedState) {
		NetworkMessage msgErr;
		msgErr.buildErrorMessage(errmsg);
		msgErr.writeMessage(_socket);
		_socket->flush();
	}
	_socket->blockSignals(true);
	this->deleteLater();
	emit disconnected();
}

QString Client::ip() const
{
	return _socket->peerAddress().toString();
}