summaryrefslogblamecommitdiffstats
path: root/src/client/net/serverconnection.cpp
blob: 807b6a014ef3b075792a17083d90ad698db1e3c8 (plain) (tree)
1
2
3
4
5
6
7
8







                             


                      



                              
                                  



                                         
                                                                                                                                                          
                                                                                                                                               





                                                                         
                                                                                                                             






                                                                          
                                    
                                                                          
                                                 





                                                                                                                                            





                                                              



                              


                                        










                                                                                   




                                                            

                                             












                                                       

 




                                                                   

                                  
                                                                          


































                                                                                                

                                                                               






                                                                                
                                                                 















































                                                                               

                                             

                                                                   








                                                                                 


                                                   


                                                     






















                                                                                                            




































                                                                                                                                                                                          
  




                                                     








                                                                    




















                                                                                             

 
  


        




                                                                        
















                                                                                       



                                                                             




                                                                                   
                                                      
            
                                                       



                                                                  



                                                               





































































                                                                                                                     
#include "serverconnection.h"
#include <QtCore>
#include <QPixmap>
#include <QApplication>
#include <QDesktopWidget>
#include <QHostInfo>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <pwd.h>

//#define verbose
#include "../vnc/vncserver.h"

#include "../../shared/util.h"
#include "../../shared/settings.h"
#include "../util/platform/blankscreen.h"

#define CHALLENGE_LEN 20

ServerConnection::ServerConnection(const QString& host, const quint16 port, const QByteArray& sessionName, const QByteArray& certHash, bool autoConnect) :
	QObject(NULL), _timerDelete(0), _jpegQuality(80), _authed(0), _autoConnect(autoConnect), _sessionName(sessionName), _certHash(certHash)
{
	_socket = new QSslSocket();
	_blank = new BlankScreen();
   connect(_socket, SIGNAL(encrypted()), this, SLOT(sock_connected()));
   connect(_socket, SIGNAL(readyRead()), this, SLOT(sock_dataArrival()));
   connect(_socket, SIGNAL(disconnected()), this, SLOT(sock_closed()));
	 connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(sock_error(QAbstractSocket::SocketError)));
   connect(_socket,
   		SIGNAL(sslErrors(const QList<QSslError> &)),
   		this,
   		SLOT(sslErrors(const QList<QSslError> &))
   		);
   qDebug("Connecting to %s on port %d", host.toUtf8().data(), (int)port);
   _socket->connectToHostEncrypted(host, port);
	_timerId = startTimer(4000);
	_lastData = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS;
	_timerConnectionCheck = startTimer(5000);
	// Connect the vnc start/stop signal to this class, so we can tell the server about successful vnc server startup
	connect(VncServer::instance(), SIGNAL(started(int, QString&, QString&)), this, SLOT(onVncServerStartStop(int, QString&, QString&)));
}

ServerConnection::~ServerConnection()
{
	if (_socket != NULL)
	{
		qCritical("**** SOCKET DELETE IN DESTRUCTOR");
		_socket->deleteLater();
	}
	qDebug("*** Server connection destroyed.");
	_blank->deleteLater();
	_blank = NULL;
}

/**
 * Send the given message to the server.
 */
void ServerConnection::sendMessage(NetworkMessage& message)
{
	if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState)
		return;
	message.writeMessage(_socket);
	if (!message.writeComplete())
	{
		qCritical() << "SendMessage to server failed!";
	}
}

/**
 * Disconnect from current server.
 * Do some cleanup also, like stopping any VNC server/client
 * activity, then finally close the connection.
 */
void ServerConnection::disconnectFromServer()
{
	if (_timerDelete == 0)
	{
		VncServer::instance()->stop();
		emit closeVnc();
		emit disconnected();
		_timerDelete = startTimer(500);
		qDebug("Closing connection to server");
		if (_socket != NULL)
		{
			_socket->blockSignals(true);
			_socket->abort();
		}
	}
}

/**
 * Handles an incoming message by the server.
 * This is somewhat of a long mess, maybe split this up some day or
 * make it OOP with a huge amount fancy features.
 */
void ServerConnection::handleMsg()
{
	_lastData = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS;
	const QString &id = _fromServer.getFieldString(_ID);

	if (_authed == 0)
	{
		if (id == _CHALLENGE)
		{
			// Initial challenge request by server
			emit stateChange(ConnectWindow::AwaitingChallengeResponse);
			_myChallenge.resize(CHALLENGE_LEN);
			for (int i = 0; i < CHALLENGE_LEN; ++i)
				_myChallenge[i] = qrand() & 0xff;
			QByteArray serverChallenge(_fromServer.getFieldBytes(_CHALLENGE));
			_toServer.reset();
			_toServer.setField(_ID, _CHALLENGE);
			_toServer.setField(_HASH, genSha1(&_sessionName, &serverChallenge));
			_toServer.setField(_CHALLENGE, _myChallenge);
			_toServer.writeMessage(_socket);
			qDebug("Received challenge, replying, sending own challenge step <- 1");
			_authed = 1;
		}
		return;
	}

	if (_authed == 1)
	{
		if (id == _CHALLENGE)
		{
			qDebug("Received challenge reply");
			if (_timerId != 0)
			{
				killTimer(_timerId);
				_timerId = 0;
			}
			// Check challenge response
			QByteArray serverhash(_fromServer.getFieldBytes(_HASH));
			if (serverhash != genSha1(&_sessionName, &_myChallenge)
					&& !_autoConnect)
			{
				qDebug("invalid. STOP.");
				emit stateChange(ConnectWindow::InvalidSslHash);
				this->disconnectFromServer();
				return;
			}
			emit stateChange(ConnectWindow::LoggingIn);
			char *user = getpwuid(getuid())->pw_name;
			if (user == NULL || *user == '\0')
				user = getenv("USER");
			if (user == NULL || *user == '\0')
				user = getenv("USERNAME");
			if (user == NULL || *user == '\0')
				user = (char*)"Hans Affe";
			_toServer.reset();
			_toServer.setField(_ID, _LOGIN);
			_toServer.setField("HOST", QHostInfo::localHostName());
			_toServer.setField("NAME", QString(user));
			qDebug("Sending login request!");
			if (_toServer.writeMessage(_socket))
			{
				_authed = 2;
				qDebug("valid, step <- 2");
			}
			else
			{
				this->disconnectFromServer();
			}
		}
		return;
	}

	if (_authed == 2)
	{
		if (id == _LOGIN)
		{
			qDebug("login accepted, step <- 3");
			_authed = 3;
			emit stateChange(ConnectWindow::Connected);
		}
		return;
	}

	// message THUMB - server requests screenshot as thumbnail
	if (id == _THUMB)
	{
		int x = _fromServer.getFieldString(_X).toInt();
		int y = _fromServer.getFieldString(_Y).toInt();
		if (x < 32)
			x = 32;
		else if (x > 400)
			x = 400;
		if (y < 18)
			y = 18;
		else if (y > 300)
			y = 300;

		// Get rect of primary screen
		const QDesktopWidget primary;
		const QRect primaryRect = primary.screenGeometry();

		QPixmap desktop(
					QPixmap::grabWindow(
						QApplication::desktop()->winId(),
						primaryRect.x(),
						primaryRect.y(),
						primaryRect.width(),
						primaryRect.height()
						).scaled(
			x, y,
			Qt::KeepAspectRatio,
			Qt::SmoothTransformation));
		QByteArray bytes;
		QBuffer jpgBuffer(&bytes);
		jpgBuffer.open(QIODevice::WriteOnly);
		if (desktop.save(&jpgBuffer, "JPG", _jpegQuality)) // writes pixmap into bytes in JPG format
		{
			// Try to adjust quality so we stay between 3 and 4.5 KB
			if (_jpegQuality < 90 && bytes.size() < 3000)
				_jpegQuality += 7;
			else if (_jpegQuality > 40 && bytes.size() > 4500)
				_jpegQuality -= 7;
		}
		else
		{	// FALLBACK
			bytes.clear();
			QBuffer pngBuffer(&bytes);
			pngBuffer.open(QIODevice::WriteOnly);
			if (!desktop.save(&pngBuffer, "PNG")) // writes pixmap into bytes in PNG format
			{
				qDebug("Could not convert screenshot to PNG nor JPG");
				return; // Failed :-(
			}
		}
		_toServer.reset();
		_toServer.setField(_ID, _THUMB);
		_toServer.setField(_IMG, bytes);
		sendMessage(_toServer);
	} // message VNCSERVER - start local vncserver
	else if (id == _VNCSERVER)
	{
		const bool enable = (_fromServer.getFieldString("ENABLE").toInt() != 0);
		if (enable)
		{
			emit closeVnc(); // In case we are watching some other client, stop doing so
			VncServer::instance()->start();
		}
		else
		{
			VncServer::instance()->stop();
		}
	}
	else if (id == _VNCCLIENT)
	{
		const QString host(_fromServer.getFieldString("HOST"));
		const int port = _fromServer.getFieldString("PORT").toInt();
		if (host.isEmpty() || port <= 0)
		{
			emit closeVnc();
		}
		else
		{
			emit openVnc(host, port, _fromServer.getFieldString("ROPASS"), true, true, _fromServer.getFieldString("CAPTION"), _fromServer.getFieldString("CLIENTID").toInt());
		}
	}
	else if (id == _LOCK)
	{
		const bool enable = (_fromServer.getFieldString("ENABLE").toInt() != 0);
		if (enable)
			_blank->lock(_fromServer.getFieldString("MESSAGE"));
		else
			_blank->unlock();
	}
}

/*
 * Override
 */

void ServerConnection::timerEvent(QTimerEvent *event)
{
	if (event->timerId() == _timerConnectionCheck)
	{
		if (_lastData < QDateTime::currentMSecsSinceEpoch())
		{
			this->disconnectFromServer();
			killTimer(_timerConnectionCheck);
		}
	}
	else if (event->timerId() == _timerId)
	{
		killTimer(_timerId);
		_timerId = 0;
		this->disconnectFromServer();
	}
	else if (event->timerId() == _timerDelete)
	{
		if (_socket == NULL || _socket->state() == QAbstractSocket::UnconnectedState)
		{
			if (_socket != NULL)
				_socket->deleteLater();
			_socket = NULL;
			killTimer(_timerDelete);
			this->deleteLater();
			return;
		}
		_socket->abort();
		qDebug("A socket is still pending...");
	}
	else
		killTimer(event->timerId());
}

/*
 * Slots
 */

/**
 * This slot is triggered by the vnc server runner once the external VNC
 * server was succesfully started, or was terminated (either planned or
 * crashed).
 */
void ServerConnection::onVncServerStartStop(int port, QString& ropass, QString& rwpass)
{
	_toServer.reset();
	_toServer.setField(_ID, _VNCSERVER);
	if (port <= 0)
	{
		_toServer.setField("PORT", QByteArray("0"));
	}
	else
	{
		_toServer.setField("PORT", QString::number(port));
		_toServer.setField("ROPASS", ropass);
		_toServer.setField("RWPASS", rwpass);
	}
	sendMessage(_toServer);
}

/**
 * This slot is triggered once the internal VNC viewer has started or stopped
 * displaying a VNC stream. We'll inform the server about the state change.
 */
void ServerConnection::onVncViewerStartStop(const bool started, const int clientId)
{
	_toServer.reset();
	_toServer.setField(_ID, _VNCCLIENT);
	if (started)
		_toServer.setField("ENABLED", __TRUE);
	else
		_toServer.setField("ENABLED", __FALSE);
	_toServer.setField("CLIENTID", QString::number(clientId));
	sendMessage(_toServer);
}

/**
 * An ssl error happened. If it's an expected one, we ignore it
 * and keep going. Otherwise the connection will be terminated.
 */
void ServerConnection::sslErrors(const QList<QSslError> & errors)
{
	for (QList<QSslError>::const_iterator it = errors.begin(); it != errors.end(); it++)
	{
		QSslError err = *it;
		qDebug("Connect SSL: %s", qPrintable(err.errorString()));
		if (err.error() == QSslError::HostNameMismatch)
			continue; // We don't pay attention to hostnames for validation
		if (err.error() == QSslError::SelfSignedCertificate)
			continue; // Also, this will always be the case; we check the fingerprint later
		if (err.error() == QSslError::CertificateNotYetValid || err.error() == QSslError::CertificateExpired)
			continue;
		// Some other SSL error - do not ignore
		return;
	}
	_socket->ignoreSslErrors();
}

void ServerConnection::sock_dataArrival()
{
	if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState)
	{
		qDebug("dataArrival called in bad state");
		return;
	}

	while (_socket->bytesAvailable())
	{
		bool retval;
		retval = _fromServer.readMessage(_socket); // let the message read data from socket
		if (!retval)   // error parsing msg, disconnect client!
		{
			this->disconnectFromServer();
			return;
		}
		if (_fromServer.readComplete())   // message is complete
		{
			this->handleMsg();
			if (_socket == NULL)
				return;
			_fromServer.reset();
		}
	}
}

void ServerConnection::sock_closed()
{
	// should this be unreliable in some way i suggest using the signal "stateChanged()" instead
	// and check if the state changed to unconnected.
	qDebug("Socket was closed... oh well..");
	this->disconnectFromServer();
}

void ServerConnection::sock_error(QAbstractSocket::SocketError errcode)
{
	qDebug("Connection error: %d", (int)errcode);
	this->disconnectFromServer();
}

void ServerConnection::sock_connected()
{
   QByteArray cert(_socket->peerCertificate().digest(QCryptographicHash::Sha1));
	if (_certHash != cert)
	{
		emit stateChange(ConnectWindow::InvalidCert);
		this->disconnectFromServer();
		return;
	}
	emit stateChange(ConnectWindow::AwaitingChallenge);
}