From 1a5709501f94014d41987b956338bb6424b9f90c Mon Sep 17 00:00:00 2001 From: sr Date: Mon, 4 Feb 2013 19:50:31 +0100 Subject: Initial commit --- src/server/clicklabel/clicklabel.cpp | 14 + src/server/clicklabel/clicklabel.h | 25 + src/server/connectionframe/connectionframe.cpp | 278 +++++++++ src/server/connectionframe/connectionframe.h | 77 +++ src/server/main.cpp | 41 ++ src/server/mainwindow/mainwindow.cpp | 633 +++++++++++++++++++++ src/server/mainwindow/mainwindow.h | 76 +++ src/server/net/certmanager.cpp | 92 +++ src/server/net/certmanager.h | 29 + src/server/net/client.cpp | 288 ++++++++++ src/server/net/client.h | 104 ++++ src/server/net/discoverylistener.cpp | 165 ++++++ src/server/net/discoverylistener.h | 40 ++ src/server/net/listenserver.cpp | 40 ++ src/server/net/listenserver.h | 30 + src/server/net/sslserver.cpp | 113 ++++ src/server/net/sslserver.h | 50 ++ src/server/sessionnamewindow/sessionnamewindow.cpp | 68 +++ src/server/sessionnamewindow/sessionnamewindow.h | 38 ++ src/server/util/global.cpp | 17 + src/server/util/global.h | 29 + src/server/util/util.cpp | 14 + src/server/util/util.h | 17 + 23 files changed, 2278 insertions(+) create mode 100644 src/server/clicklabel/clicklabel.cpp create mode 100644 src/server/clicklabel/clicklabel.h create mode 100644 src/server/connectionframe/connectionframe.cpp create mode 100644 src/server/connectionframe/connectionframe.h create mode 100644 src/server/main.cpp create mode 100644 src/server/mainwindow/mainwindow.cpp create mode 100644 src/server/mainwindow/mainwindow.h create mode 100644 src/server/net/certmanager.cpp create mode 100644 src/server/net/certmanager.h create mode 100644 src/server/net/client.cpp create mode 100644 src/server/net/client.h create mode 100644 src/server/net/discoverylistener.cpp create mode 100644 src/server/net/discoverylistener.h create mode 100644 src/server/net/listenserver.cpp create mode 100644 src/server/net/listenserver.h create mode 100644 src/server/net/sslserver.cpp create mode 100644 src/server/net/sslserver.h create mode 100644 src/server/sessionnamewindow/sessionnamewindow.cpp create mode 100644 src/server/sessionnamewindow/sessionnamewindow.h create mode 100644 src/server/util/global.cpp create mode 100644 src/server/util/global.h create mode 100644 src/server/util/util.cpp create mode 100644 src/server/util/util.h (limited to 'src/server') diff --git a/src/server/clicklabel/clicklabel.cpp b/src/server/clicklabel/clicklabel.cpp new file mode 100644 index 0000000..2989542 --- /dev/null +++ b/src/server/clicklabel/clicklabel.cpp @@ -0,0 +1,14 @@ +#include "clicklabel.h" + +ClickLabel::ClickLabel(QWidget *parent) : QLabel(parent) +{ + QFont f(this->font()); + f.setPixelSize(20); + this->setFont(f); + this->setMaximumHeight(22); +} + +void ClickLabel::mouseReleaseEvent(QMouseEvent* e) +{ + emit clicked(); +} diff --git a/src/server/clicklabel/clicklabel.h b/src/server/clicklabel/clicklabel.h new file mode 100644 index 0000000..6a05152 --- /dev/null +++ b/src/server/clicklabel/clicklabel.h @@ -0,0 +1,25 @@ +#ifndef _CLICKLABEL_H_ +#define _CLICKLABEL_H_ + +#include +#include + + +class ClickLabel : public QLabel +{ + Q_OBJECT + +public: + ClickLabel(QWidget *parent); + +protected: + void mouseReleaseEvent(QMouseEvent* e); + + +signals: + void clicked(); + +}; + + +#endif diff --git a/src/server/connectionframe/connectionframe.cpp b/src/server/connectionframe/connectionframe.cpp new file mode 100644 index 0000000..1544c76 --- /dev/null +++ b/src/server/connectionframe/connectionframe.cpp @@ -0,0 +1,278 @@ +/* +# Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg +# +# This program is free software distributed under the GPL version 2. +# See http://openslx.org/COPYING +# +# If you have any feedback please consult http://openslx.org/feedback and +# send your suggestions, praise, or complaints to feedback@openslx.org +# +# General information about OpenSLX can be found at http://openslx.org/ +# ----------------------------------------------------------------------------- +# src/gui/connectionFrame.cpp +# ----------------------------------------------------------------------------- +*/ + + +#include "connectionframe.h" +#include "../net/client.h" +#include +#include +#include + +static QIcon *term = NULL, *cam = NULL, *eye = NULL; + +static QString backgroundStyle("background-color: %1; margin: 1px; border: 1px solid black; border-radius: 6px"); + +ConnectionFrame::ConnectionFrame(QWidget *parent, int width, int height) : + QGroupBox(parent), _client(NULL), _timerId(0), _timerCounter(0), _selected(false), _isTutor(false) +{ + + //defines the ui-stuff + // load icons first + if (term == NULL) + { + term = new QIcon(":terminal"); + cam = new QIcon(":cam32"); + eye = new QIcon(":eye"); + } + + //this->setAttribute(Qt::WA_OpaquePaintEvent); + + _mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); + _mainLayout->setSpacing(1); + _mainLayout->setMargin(3); + _mainLayout->setAlignment(Qt::AlignHCenter); + + _iconLayout = new QBoxLayout(QBoxLayout::RightToLeft, NULL); + _iconLayout->setSpacing(1); + _iconLayout->setMargin(3); + + _lblUserName = new QLabel("Test", this); + _lblUserName->setStyleSheet(QString::fromUtf8("background-color: white; color: black;")); + _lblUserName->setAlignment(Qt::AlignHCenter); + //_lblUsername->resize(_lblUsername->width(), _lblUsername->font().pixelSize()); + + _lblHostName = new QLabel("PC", this); + _lblHostName->setStyleSheet(QString::fromUtf8("background-color: white; color: black;")); + _lblHostName->setAlignment(Qt::AlignHCenter); + + _icoCam = addIcon(cam); + _icoEye = addIcon(eye); + + _iconLayout->addWidget(_icoCam); + _iconLayout->addWidget(_icoEye); + _iconLayout->addStretch(); + + //_layout->addWidget(_imgScreen); + _mainLayout->addLayout(_iconLayout); + _mainLayout->addStretch(); + _mainLayout->addWidget(_lblUserName); + _mainLayout->addWidget(_lblHostName); + this->setLayout(_mainLayout); + this->setSize(width, height); + this->updateColor(); +} + +ConnectionFrame::~ConnectionFrame() +{ + // +} + +QLabel* ConnectionFrame::addIcon(const QIcon* icon) +{ + QLabel *label = new QLabel(this); + label->setPixmap(icon->pixmap(24, 24, QIcon::Normal, QIcon::On)); + label->setAttribute(Qt::WA_TranslucentBackground); + label->hide(); + _icons.append(label); + return label; +} + +void ConnectionFrame::setSize(int width, int height) +{ + this->resize(width, height); +} + +void ConnectionFrame::assignClient(Client* client) +{ + assert(_client == NULL); + connect(client, SIGNAL(destroyed(QObject*)), this, SLOT(onClientDisconnected(QObject*))); + connect(client, SIGNAL(thumbUpdated(Client*, const QPixmap&)), this, SLOT(onThumbUpdated(Client*, const QPixmap&))); + connect(client, SIGNAL(vncServerStateChange(Client*)), this, SLOT(onVncServerStateChange(Client*))); + connect(client, SIGNAL(vncClientStateChange(Client*)), this, SLOT(onVncClientStateChange(Client*))); + _client = client; + _computerId = client->computerId(); + _lblHostName->setText(client->ip()); + _lblHostName->setToolTip(client->host()); + _lblUserName->setText(client->name()); + showDefaultThumb(); + if (_timerId == 0) + _timerId = startTimer(1000 + qrand() % 150); + this->updateColor(); +} + +void ConnectionFrame::showDefaultThumb() +{ + const int width = this->width() - 6; + const int height = this->height() - 8 - _lblHostName->height() - _lblUserName->height(); + _remoteScreen = term->pixmap(width, height, QIcon::Normal, QIcon::On); + this->repaint(); + //_imgScreen->setPixmap(_remoteScreen); +} + +void ConnectionFrame::mouseReleaseEvent(QMouseEvent* event) +{ + event->accept(); + if (event->button() == Qt::LeftButton) + { + QApplication::setOverrideCursor(QCursor(Qt::OpenHandCursor)); + if (this->pos() != _previousPosition) { + qDebug("Moved"); + emit frameMoved(this); + } + else + { + qDebug("Clicked"); + emit clicked(this); + } + } +} + +void ConnectionFrame::enterEvent(QEvent* event) +{ + QApplication::setOverrideCursor(QCursor(Qt::OpenHandCursor)); + event->accept(); +} + +void ConnectionFrame::leaveEvent(QEvent* event) +{ + QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor)); + event->accept(); +} + +void ConnectionFrame::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::RightButton) + { + // Menu... + } + else + { + _clickPoint = event->pos(); + _previousPosition = this->pos(); + QApplication::setOverrideCursor(QCursor(Qt::ClosedHandCursor)); + } + // On click, the window has to be on the top-level. + activateWindow(); + raise(); + update(); + event->accept(); +} + +void ConnectionFrame::mouseMoveEvent(QMouseEvent *event) +{ + QApplication::setOverrideCursor(QCursor(Qt::ClosedHandCursor)); + move(mapToParent(event->pos()-_clickPoint)); + event->accept(); +} + +void ConnectionFrame::mouseDoubleClickEvent(QMouseEvent* event) +{ + emit doubleClicked(this); + event->accept(); +} + +void ConnectionFrame::paintEvent(QPaintEvent *event) +{ + QGroupBox::paintEvent(event); + if (_remoteScreen.isNull()) + return; + QPainter painter(this); + painter.drawPixmap((this->width() - _remoteScreen.width()) / 2, 4, _remoteScreen); + event->accept(); +} + +void ConnectionFrame::timerEvent(QTimerEvent* event) +{ + if (_client == NULL) + return; + ++_timerCounter; + if (_client->isActiveVncServer() && _timerCounter % 5 != 0) + return; + const int width = this->width() - 8; + const int height = this->height() - 9 - _lblHostName->height() - _lblUserName->height(); + _client->requestThumb(width, height); +} + +void ConnectionFrame::setSelection(bool selected) +{ + if (_selected == selected) + return; + _selected = selected; + this->updateColor(); +} + +void ConnectionFrame::updateColor() +{ + if (_client == NULL) + { + // Unconnected Frame + if (_selected) + this->setStyleSheet(backgroundStyle.arg("rgb(140, 140, 170)")); + else + this->setStyleSheet(backgroundStyle.arg("rgb(140, 140, 140)")); + for (QList::iterator it(_icons.begin()); it != _icons.end(); ++it) + (**it).hide(); + return; + } + _icoCam->setVisible(_client->isActiveVncServer()); + _icoEye->setVisible(_client->isActiveVncClient()); + + // Normal client, no special stuff active + + if (_selected && _isTutor) + this->setStyleSheet(backgroundStyle.arg("rgb(255, 220, 180)")); + else if (_isTutor) + this->setStyleSheet(backgroundStyle.arg("rgb(255, 180, 180)")); + else if (_selected) + this->setStyleSheet(backgroundStyle.arg("rgb(180, 180, 210)")); + else + this->setStyleSheet(backgroundStyle.arg("rgb(180, 180, 180)")); +} + +/** + * Slots + */ + +void ConnectionFrame::onClientDisconnected(QObject* client) +{ + assert(client == _client); + if (_timerId != 0) + { + killTimer(_timerId); + _timerId = 0; + } + _client = NULL; + _lblUserName->setText(QString()); + showDefaultThumb(); + this->updateColor(); +} + +void ConnectionFrame::onThumbUpdated(Client* client, const QPixmap& thumb) +{ + assert(client == _client); + _remoteScreen = thumb; + //_imgScreen->setPixmap(_remoteScreen); + this->repaint(); +} + +void ConnectionFrame::onVncServerStateChange(Client* client) +{ + this->updateColor(); +} + +void ConnectionFrame::onVncClientStateChange(Client* client) +{ + this->updateColor(); +} diff --git a/src/server/connectionframe/connectionframe.h b/src/server/connectionframe/connectionframe.h new file mode 100644 index 0000000..2b78056 --- /dev/null +++ b/src/server/connectionframe/connectionframe.h @@ -0,0 +1,77 @@ +#ifndef _CONNECTIONFRAME_H_ +#define _CONNECTIONFRAME_H_ +#include + +class Client; + +class ConnectionFrame : public QGroupBox +{ + +Q_OBJECT + +private: + + QString _computerId; + + QBoxLayout *_mainLayout; + QBoxLayout *_iconLayout; + QLabel *_lblUserName; + QLabel *_lblHostName; + + QLabel *_icoCam, *_icoEye; + QList _icons; + + QPixmap _remoteScreen; + + QPoint _clickPoint; + QPoint _previousPosition; + + Client *_client; + + int _timerId, _timerCounter; + bool _selected; + bool _isTutor; + + void showDefaultThumb(); + void updateColor(); + QLabel* addIcon(const QIcon* icon); + +public: + ConnectionFrame(QWidget* parent, int width, int height); + virtual ~ConnectionFrame(); + + const QPixmap& getFramePixmap() const { return _remoteScreen; } + void setSize(int width, int height); + void assignClient(Client *client); + void setSelection(bool selected); + const inline bool selected() const { return _selected; } + + const QString& computerId() const { return _computerId; } + Client* client() const { return _client; } + + inline const bool isTutor() const { return _isTutor; } + inline void setTutor(bool b) { _isTutor = b; } + +protected: + void mouseDoubleClickEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* e); + void enterEvent(QEvent* event); + void leaveEvent(QEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void paintEvent(QPaintEvent *event); + void timerEvent(QTimerEvent* event); + +signals: + void frameMoved(ConnectionFrame* frame); + void doubleClicked(ConnectionFrame* frame); + void clicked(ConnectionFrame* frame); + +private slots: + void onClientDisconnected(QObject* client); + void onThumbUpdated(Client* client, const QPixmap& thumb); + void onVncServerStateChange(Client* client); + void onVncClientStateChange(Client* client); +}; + +#endif diff --git a/src/server/main.cpp b/src/server/main.cpp new file mode 100644 index 0000000..e7c709a --- /dev/null +++ b/src/server/main.cpp @@ -0,0 +1,41 @@ +#include "mainwindow/mainwindow.h" +#include "util/util.h" + +int main(int argc, char** argv) +{ + QApplication app(argc, argv); + app.setOrganizationName("openslx"); + app.setOrganizationDomain("openslx.org"); + app.setApplicationName("pvsmgr"); + + qsrand((uint)QDateTime::currentMSecsSinceEpoch()); + + // Make sure settings directory exists + do { + USER_SETTINGS(settings); + QFileInfo fi(settings.fileName()); + QDir path(fi.path()); + qDebug() << "Settings directory is " << fi.path(); + if (!path.exists()) + path.mkpath(path.absolutePath()); + // Now check if settings file exists. If not, copy system default (if available) + if (!fi.exists()) + { + SYSTEM_SETTINGS(sys); + QFileInfo sysfi(sys.fileName()); + if (sysfi.exists()) + { + if (!QFile::copy(sys.fileName(), settings.fileName())) + qDebug() << "Copying default settings from " << sys.fileName() << " to " << settings.fileName() << " failed."; + } + } + } while (0); + + // use system locale as language to translate gui + QTranslator translator; + translator.load(":pvsmgr"); + app.installTranslator(&translator); + + MainWindow pvsmgr; + return app.exec(); +} diff --git a/src/server/mainwindow/mainwindow.cpp b/src/server/mainwindow/mainwindow.cpp new file mode 100644 index 0000000..8c57b91 --- /dev/null +++ b/src/server/mainwindow/mainwindow.cpp @@ -0,0 +1,633 @@ +/* + # Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + # ----------------------------------------------------------------------------- + # mainWindow.cpp + This is the Main class for the pvsManager. The GUI is constructed here. + # ----------------------------------------------------------------------------- + */ +// Self +#include "mainwindow.h" +// QT stuff +#include +#include +// Other custom UI elements +#include "../clicklabel/clicklabel.h" +#include "../sessionnamewindow/sessionnamewindow.h" +#include "../connectionframe/connectionframe.h" +// Network +#include "../net/listenserver.h" +#include "../net/client.h" +#include "../net/discoverylistener.h" +#include "../../shared/networkmessage.h" +// Others +#include "../../shared/settings.h" +#include "../util/util.h" +#include "../util/global.h" +// Auto-generated ui class +#include "ui_mainwindow.h" + +MainWindow::MainWindow(QWidget* parent) : + QMainWindow(parent), ui(new Ui::MainWindow), _tbIconSize(24), _tbArea(Qt::LeftToolBarArea), + _buttonBlockTime(0) + +{ + _sessionNameWindow = new SessionNameWindow(this); + ui->setupUi(this); + + Global::setSessionName("1234"); + + //conWin = new ConnectionWindow(ui->widget); + //ui->VconWinLayout->addWidget(conWin); + //conList = new ConnectionList(ui->ClWidget); + //ui->ClientGLayout->addWidget(conList); + + // Show only session name label, not textbox + _sessionNameLabel = new ClickLabel(this); + ui->mainLayout->addWidget(_sessionNameLabel); + + ui->frmRoom->setStyleSheet("background-color:#444"); + + // toolbar and actions in pvsmgr + ui->action_Exit->setStatusTip(tr("Exit")); + ui->action_Lock->setStatusTip(tr("Lock or Unlock all Clients")); + + // Close button in tool bar + connect(ui->action_Exit, SIGNAL(triggered()), this, SLOT(onButtonExit())); + connect(ui->action_BroadcastScreen, SIGNAL(triggered()), this, SLOT(onButtonBroadcast())); + connect(ui->action_Lock, SIGNAL(toggled(bool)), this, SLOT(onButtonLock(bool))); + // Clicking the session name label shows the edit field for it + connect(_sessionNameLabel, SIGNAL(clicked()), this, SLOT(onSessionNameClick())); + // Listen to updates to the session name through the session name window + connect(_sessionNameWindow, SIGNAL(updateSessionName()), this, + SLOT(onSessionNameUpdate())); + + setAttribute(Qt::WA_QuitOnClose); + setUnifiedTitleAndToolBarOnMac(true); + this->showMaximized(); // show the Mainwindow maximized + + _tilesX = 9; + _tilesY = 7; + _tileWidth = 10; + _tileHeight = 10; + + _timerId = 0; + _timerTimeout = 0; + + _listenServer = new ListenServer(CLIENT_PORT); + connect(_listenServer, SIGNAL(newClient(Client*)), this, SLOT(onClientConnected(Client*))); + _discoveryListener = new DiscoveryListener(); + + // Finally + this->onSessionNameUpdate(); + _timerId = startTimer(10); + _timerTimeout = 8; +} + +MainWindow::~MainWindow() +{ + _sessionNameLabel->deleteLater(); + delete ui; +} + +void MainWindow::placeFrameInFreeSlot(ConnectionFrame* frame) +{ + // Get occupied cell of each frame and store status in an array + const int elems = _tilesX * _tilesY; + int currentIndex = 0; + bool grid[elems]; + memset(grid, 0, sizeof(bool) * elems); // set everything to false + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + const QPoint &oldpos = (*it)->frameGeometry().topLeft(); + const int tx = oldpos.x() / _tileWidth; + const int ty = oldpos.y() / _tileHeight; + const int index = tx + ty * _tilesX; + if (frame == *it) + { + // Current frame is the frame to place, remember its old index + currentIndex = index; + continue; + } + if (index < 0 || index >= elems) + { + // Current frame is out of bounds, place in top left corner + qDebug("Move A"); + (*it)->move(0, 0); + grid[0] = true; + continue; + } + // Just mark cell as occupied in array + grid[index] = true; + } + // Safety + if (currentIndex < 0 || currentIndex >= elems) + currentIndex = 0; + // Now place in next free cell, start looking at current position of frame + for (int i = 0; i < elems; ++i) + { + const int index = (i + currentIndex) % elems; + if (grid[index]) + continue; + const int x = (index % _tilesX) * _tileWidth; + const int y = (index / _tilesX) * _tileHeight; + //qDebug() << "Index=" << index << " i=" << i << " cI=" << currentIndex << " elems=" << elems << " x=" << x << " y=" << y; + qDebug("Move B"); + frame->move(x, y); + break; + } +} + +ConnectionFrame* MainWindow::createFrame() +{ + // Allocate and resize + ConnectionFrame *cf = new ConnectionFrame(ui->frmRoom, _tileWidth, _tileHeight); + _clientFrames.append(cf); + cf->show(); + connect(cf, SIGNAL(frameMoved(ConnectionFrame *)), this, SLOT(onPlaceFrame(ConnectionFrame *))); + connect(cf, SIGNAL(clicked(ConnectionFrame *)), this, SLOT(onFrameClicked(ConnectionFrame *))); + return cf; +} + +bool MainWindow::loadPosition(QSettings& settings, const QString& id, int& x, int& y) +{ + settings.beginGroup("client_position"); + const QVariant retval(settings.value(id)); + settings.endGroup(); + if (retval.type() != QVariant::Point) + return false; + const QPoint point(retval.toPoint()); + x = point.x(); + y = point.y(); + return true; +} + +void MainWindow::savePosition(ConnectionFrame *cf) +{ + Client *client = cf->client(); + if (client == NULL) + return; + QPoint pos(cf->pos().x() / _tileWidth, cf->pos().y() / _tileHeight); + USER_SETTINGS(settings); + settings.beginGroup("client_position"); + settings.setValue(client->computerId(), pos); + settings.endGroup(); +} + +/* + * Overridden methods + */ + +void MainWindow::closeEvent(QCloseEvent* e) +{ + int ret = QMessageBox::question(this, "Test", "Beenden?", 0, 1, 2); + if (ret == 1) + QApplication::exit(0); + else + e->ignore(); +} + +void MainWindow::changeEvent(QEvent* e) +{ + QMainWindow::changeEvent(e); +} + +void MainWindow::resizeEvent(QResizeEvent* e) +{ + QMainWindow::resizeEvent(e); + if (_timerId == 0) + { + _timerId = startTimer(5); + } + _timerTimeout = 8; +} + +void MainWindow::mouseReleaseEvent(QMouseEvent* e) +{ + // Try to figure out if the user clicked inside the "room" frame + // No idea if there is a more elegant way to do this without + // sub-classing that frame and overriding its mouseReleaseEvent + const QPoint pos(ui->frmRoom->mapFrom(this, e->pos())); + if (pos.x() < 0 || pos.y() < 0) + return; + const QSize frame(ui->frmRoom->size()); + if (frame.width() > pos.x() && frame.height() > pos.y()) + { + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) + { + (**it).setSelection(false); + } + } +} + +void MainWindow::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == _timerId) + { + _timerTimeout -= 1; + if (_timerTimeout == 1) + { + // Move toolbar if necessary + const int iconSize = this->size().width() / 30 + 14; + const int barSpace = ui->toolBar->actions().size() * (iconSize + 8); + const Qt::ToolBarArea area = + (barSpace < this->size().height() ? Qt::LeftToolBarArea : Qt::TopToolBarArea); + if (_tbIconSize != iconSize) + { + _tbIconSize = iconSize; + ui->toolBar->setIconSize(QSize(iconSize, iconSize)); + } + if (_tbArea != area) + { + _tbArea = area; + this->addToolBar(area, ui->toolBar); + } + return; + } + // Kill timer when done + if (_timerTimeout <= 0) + { + killTimer(_timerId); + _timerId = 0; + // Resize all connection windows + if (ui->frmRoom->size().width() < 100 || ui->frmRoom->size().height() < 100 || _tilesX <= 0 || _tilesY <= 0) + return; + const int nw = ui->frmRoom->size().width() / _tilesX; + const int nh = ui->frmRoom->size().height() / _tilesY; + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) + { + const QPoint &oldpos = (*it)->frameGeometry().topLeft(); + qDebug("Move C"); + (*it)->move((oldpos.x() / _tileWidth) * nw, (oldpos.y() / _tileHeight) * nh); + (*it)->setSize(nw, nh); + } + _tileWidth = nw; + _tileHeight = nh; + } + } + else + { + // Unknown timer ID, kill + killTimer(event->timerId()); + } +} + +/* + * Slots + */ + +void MainWindow::onPlaceFrame(ConnectionFrame* frame) +{ + if (_tilesX <= 0 || _tilesY <= 0) + return; + const QPoint &p = frame->frameGeometry().center(); + const QSize &s = ui->frmRoom->geometry().size(); + int x = (p.x() / _tileWidth) * _tileWidth; + int y = (p.y() / _tileHeight) * _tileHeight; + if (x < 0) + x = 0; + else if (x > s.width() - _tileWidth) + x = (_tilesX - 1) * _tileWidth; + if (y < 0) + y = 0; + else if (y > s.height() - _tileHeight) + y = (_tilesY - 1) * _tileHeight; + qDebug("Move D"); + frame->move(x, y); + savePosition(frame); + const QPoint &newpos = frame->frameGeometry().topLeft(); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if (*it == frame) + continue; + if ((*it)->frameGeometry().topLeft() == newpos) + { + placeFrameInFreeSlot(*it); + } + } +} + +void MainWindow::onFrameClicked(ConnectionFrame* frame) +{ + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if (*it == frame) + frame->setSelection(true); + else + (**it).setSelection(false); + } +} + +void MainWindow::onSessionNameClick() +{ + _sessionNameWindow->show(Global::sessionName()); +} + +void MainWindow::onSessionNameUpdate() +{ + _sessionNameLabel->setText(tr("Session Name: %1 [click to edit]").arg(Global::sessionName())); +} + +void MainWindow::onButtonSettings() +{ + // Move to any free tile + placeFrameInFreeSlot(createFrame()); +} + +void MainWindow::onButtonChat() +{ + /* + for (QList::iterator it = _clientFrames.begin(); it != _clientFrames.end(); ++it) { + (*it)->setSize(100 + qrand() % 200); + } + */ +} + +void MainWindow::onButtonBroadcast() +{ + const qint64 now = QDateTime::currentMSecsSinceEpoch(); + if (now < _buttonBlockTime) + return; + _buttonBlockTime = now + 3000; + Client *source = NULL; // the desired source (selected frame) + Client *oldSource = NULL; // if there is a client that is already broadcaster, save it here + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if ((**it).selected()) + source = c; + if (c != NULL && c->isProjectionSource()) + oldSource = c; + } + if (source == NULL && oldSource == NULL) + { + QMessageBox::information(this, tr("No Projection Source selected"), tr("You did not select any active client as the connection source.")); + return; + } + if (oldSource != NULL) + { + // Is already broadcast source, disable + oldSource->setProjectionSource(false); + oldSource->stopVncServer(); + if (source == NULL || source->id() == oldSource->id()) + return; + } + source->setProjectionSource(true); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || c->id() == source->id()) + continue; + //c->setDesiredProjectionSource(source->id()); + c->setProjectionSource(false); + } + source->startVncServer(); +} + +void MainWindow::onButtonLock(bool checked) +{ + NetworkMessage msg; + msg.setField(_ID, _LOCK); + if (checked) + msg.setField("ENABLE", QByteArray("1")); + else + msg.setField("ENABLE", QByteArray("0")); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || (**it).isTutor()) + continue; // Don't lock the tutor + c->sendMessage(msg); + } +} + +void MainWindow::onButtonExit() +{ + this->close(); +} + +void MainWindow::onClientConnected(Client* client) +{ + qDebug("ListenServer told MainWindow about new connection"); + connect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*))); + connect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*))); + connect(client, SIGNAL(vncClientStateChange(Client*)), this, SLOT(onVncClientStateChange(Client*))); +} + +void MainWindow::onClientAuthenticating(Client* client, ClientLogin* request) +{ + disconnect(client, SIGNAL(authenticating(Client*, ClientLogin*)), this, SLOT(onClientAuthenticating(Client*, ClientLogin*))); + if (!request->accept) // Another receiver of that signal already rejected the client, so nothing to do + return; + bool inuse; + QString check = request->name; + int addnum = 1; + do + { + inuse = false; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; + if (!c->isAuthed()) + continue; + if (c->ip() == request->ip) + { + request->accept = false; + return; + } + if (c->name() == check) + { + inuse = true; + check = request->name + " (" + QString::number(++addnum) + ")"; + break; + } + } + } while (inuse && addnum < 100); + if (inuse) + request->accept = false; + else + request->name = check; +} + +void MainWindow::onClientAuthenticated(Client* client) +{ + disconnect(client, SIGNAL(authenticated(Client*)), this, SLOT(onClientAuthenticated(Client*))); + connect(client, SIGNAL(vncServerStateChange(Client*)), this, SLOT(onVncServerStateChange(Client*))); + bool hasActiveTutor = false; + ConnectionFrame *deadTutor = NULL; + bool anyClient = false; + ConnectionFrame *existing = NULL; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + if ((*it)->computerId() == client->computerId()) + existing = *it; + if ((*it)->client() != NULL) + anyClient = true; + if ((*it)->isTutor()) + { + if ((*it)->client() == NULL) + deadTutor = (*it)->client(); + else + hasActiveTutor = true; + } + } + if (!anyClient && deadTutor != NULL) + deadTutor->setTutor(false); + if (existing != NULL) + { + if (!anyClient && !hasActiveTutor) + existing->setTutor(true); + existing->assignClient(client); + return; + } + // New one, create + ConnectionFrame *cf = createFrame(); + // Try to load last known position + int x, y; + bool ok; + USER_SETTINGS(usr); + ok = loadPosition(usr, client->computerId(), x, y); + if (!ok) + { + SYSTEM_SETTINGS(sys); + ok = loadPosition(sys, client->computerId(), x, y); + } + if (x >= _tilesX || y >= _tilesY) + ok = false; + if (ok) + { + if (x < 0) + x = 0; + if (y < 0) + y = 0; + qDebug("Move E"); + cf->move(x * _tileWidth, y * _tileHeight); + onPlaceFrame(cf); + } + else + { + // Move to any free tile + placeFrameInFreeSlot(cf); + } + // Assign client instance + cf->assignClient(client); + // Make first active client tutor + if (!anyClient && !hasActiveTutor) + cf->setTutor(true); + // ################ + NetworkMessage msg; + // If clients are currently locked, tell this new client + if (ui->action_Lock->isChecked()) + { + msg.reset(); + msg.setField(_ID, _LOCK); + msg.setField("ENABLE", QByteArray("1")); + client->sendMessage(msg); + } + // Same for VNC projections + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c != NULL && c != client && c->isActiveVncServer() && c->isProjectionSource()) + { + msg.reset(); + msg.setField(_ID, _VNCCLIENT); + msg.setField("HOST", c->ip()); + msg.setField("PORT", QString::number(c->vncPort())); + msg.setField("ROPASS", c->vncRoPass()); + msg.setField("CAPTION", c->name() + " @ " + c->host()); + client->sendMessage(msg); + break; + } + } +} + +void MainWindow::onVncServerStateChange(Client* client) +{ + if (client->vncPort() > 0) + { + // VNC Server started on some client - start projection on all clients interested in that client's screen + NetworkMessage msg; + msg.setField(_ID, _VNCCLIENT); + msg.setField("HOST", client->ip()); + msg.setField("PORT", QString::number(client->vncPort())); + msg.setField("ROPASS", client->vncRoPass()); + msg.setField("CAPTION", client->name() + " @ " + client->host()); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; // Offline + if (c->id() == client->id() || + (c->desiredProjectionSource() != client->id() && !client->isProjectionSource())) + continue; // Not interested + c->sendMessage(msg); + } + } + else + { + // VNC server stopped on some client or failed to start - reset local pending projection information + // If at least one other client seemed to be waiting for this client's screen, pop up a message + // on the console + bool wasInterested = client->isProjectionSource(); + client->setProjectionSource(true); + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL) + continue; + if (c->desiredProjectionSource() == client->id()) + { + wasInterested = true; + c->setDesiredProjectionSource(0); + } + } + if (wasInterested) + { + QMessageBox::information(this, + tr("Projection Error"), + tr("Could not send screen contents of %1 to other clients: VNC Startup failed.").arg(client->name()) + ); + } + } +} + +void MainWindow::onVncClientStateChange(Client* client) +{ + // If the client started projection, ignore event. Also if + // the source is not known (anymore), we cannot do anything meaningful here + if (client->isActiveVncClient() || client->currentProjectionSource() == 0) + return; + // Client is not a vnc client anymore. Check if the VNC server it was (supposedly) connected + // to is still running, and kill it if there are no more clients connected to it. + // (client->currentProjectionSource() will still contain the id of the server it was connected to during this event) + // 1. check if vnc server is still running + // 2. check if there are any other clients "using" that server + bool inUse = false; + Client *server = NULL; + for (QList::iterator it(_clientFrames.begin()); it != _clientFrames.end(); ++it) + { + Client *c = (**it).client(); + if (c == NULL || c == client) + continue; + if (c->currentProjectionSource() == client->currentProjectionSource()) + { + inUse = true; + break; + } + if (c->id() == client->currentProjectionSource()) + server = c; + } + // 3. kill server if applicable + if (!inUse && server != NULL) + server->stopVncServer(); +} diff --git a/src/server/mainwindow/mainwindow.h b/src/server/mainwindow/mainwindow.h new file mode 100644 index 0000000..4f9d142 --- /dev/null +++ b/src/server/mainwindow/mainwindow.h @@ -0,0 +1,76 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ + +#include +#include + +class SessionNameWindow; +class ConnectionFrame; +class ListenServer; +class DiscoveryListener; +class Client; +class ClientLogin; + +namespace Ui +{ + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +private: + Ui::MainWindow *ui; + SessionNameWindow *_sessionNameWindow; + QLabel *_sessionNameLabel; + QList _clientFrames; + int _tileWidth, _tileHeight; + int _tilesX, _tilesY; + int _tbIconSize; + Qt::ToolBarArea _tbArea; + int _timerId, _timerTimeout; + ListenServer *_listenServer; + DiscoveryListener *_discoveryListener; + qint64 _buttonBlockTime; + + void placeFrameInFreeSlot(ConnectionFrame* frame); + ConnectionFrame* createFrame(); + bool loadPosition(QSettings& settings, const QString& id, int& x, int& y); + void savePosition(ConnectionFrame *cf); + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + +protected: + void closeEvent(QCloseEvent *e); + void changeEvent(QEvent *e); + void resizeEvent(QResizeEvent *e); + void mouseReleaseEvent(QMouseEvent* e); + void timerEvent(QTimerEvent* event); + + +protected slots: + // This + void onSessionNameClick(); + void onSessionNameUpdate(); + void onButtonSettings(); + void onButtonChat(); + void onButtonBroadcast(); + void onButtonLock(bool checked); + void onButtonExit(); + // connection frame + void onPlaceFrame(ConnectionFrame* frame); + void onFrameClicked(ConnectionFrame* frame); + // Net + void onClientConnected(Client* client); + void onClientAuthenticating(Client* client, ClientLogin* request); + void onClientAuthenticated(Client* client); + void onVncServerStateChange(Client* client); + void onVncClientStateChange(Client* client); + +}; + + +#endif diff --git a/src/server/net/certmanager.cpp b/src/server/net/certmanager.cpp new file mode 100644 index 0000000..e661a70 --- /dev/null +++ b/src/server/net/certmanager.cpp @@ -0,0 +1,92 @@ +/* + # Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + # ----------------------------------------------------------------------------- + # src/util/CertManager.cpp + # - Manage SSL certificates + # - provide access by name + # ----------------------------------------------------------------------------- + */ + +#include "certmanager.h" +#include "../util/util.h" +#include +#include +#include +#include + +namespace CertManager +{ +static QMap _certs; +static QMap _keys; + +static void generateFiles(QString& key, QString& cert); +static bool loadFiles(QString& keyFile, QString& certFile, QSslKey &key, QSslCertificate &cert); + +bool getPrivateKeyAndCert(const QString &name, QSslKey &key, QSslCertificate &cert) +{ + if (_keys.contains(name)) + { + key = _keys[name]; + cert = _certs[name]; + return true; + } + USER_SETTINGS(settings); + QString certFile = settings.fileName().append(".").append(name); + QString keyFile = certFile; + keyFile.append(".rsa"); + certFile.append(".crt"); + // + if (!loadFiles(keyFile, certFile, key, cert)) + { + generateFiles(keyFile, certFile); + if (!loadFiles(keyFile, certFile, key, cert)) + return false; + } + _certs.insert(name, cert); + _keys.insert(name, key); + return true; +} + +static bool loadFiles(QString& keyFile, QString& certFile, QSslKey &key, QSslCertificate &cert) +{ + QFileInfo keyInfo(keyFile); + QFileInfo certInfo(certFile); + if (keyInfo.exists() && certInfo.exists()) + { // Both files exist, see if they're valid and return + QFile kf(keyFile); + kf.open(QFile::ReadOnly); + key = QSslKey(&kf, QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey); + QList certlist = QSslCertificate::fromPath(certFile); + if (!key.isNull() && !certlist.empty()) + { + cert = certlist.first(); + if (!cert.isNull()) + { + return true; + } + } + } + return false; +} + +static void generateFiles(QString& key, QString& cert) +{ + char tmp[1000]; + unlink(key.toLocal8Bit().data()); + unlink(cert.toLocal8Bit().data()); + snprintf(tmp, 1000, + "openssl req -x509 -nodes -days 3650 -newkey rsa:1024 -subj '/C=DE/ST=BaWue/L=Freiburg/CN=openslx.org' -keyout \"%s\" -out \"%s\"", + key.toLocal8Bit().data(), cert.toLocal8Bit().data()); + system(tmp); + snprintf(tmp, 1000, "chmod 0600 \"%s\" \"%s\"", key.toLocal8Bit().data(), cert.toLocal8Bit().data()); + system(tmp); +} +} diff --git a/src/server/net/certmanager.h b/src/server/net/certmanager.h new file mode 100644 index 0000000..c69bc23 --- /dev/null +++ b/src/server/net/certmanager.h @@ -0,0 +1,29 @@ +/* +# Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg +# +# This program is free software distributed under the GPL version 2. +# See http://openslx.org/COPYING +# +# If you have any feedback please consult http://openslx.org/feedback and +# send your suggestions, praise, or complaints to feedback@openslx.org +# +# General information about OpenSLX can be found at http://openslx.org/ +# ----------------------------------------------------------------------------- +# src/util/CertManager.cpp +# - Manage SSL certificates +# - provide access by name +# ----------------------------------------------------------------------------- +*/ + +#ifndef CERTMANAGER_H_ +#define CERTMANAGER_H_ + +#include +#include + +namespace CertManager +{ + bool getPrivateKeyAndCert(const QString &name, QSslKey &key, QSslCertificate &cert); +} + +#endif /* CERTMANAGER_H_ */ diff --git a/src/server/net/client.cpp b/src/server/net/client.cpp new file mode 100644 index 0000000..ca91e76 --- /dev/null +++ b/src/server/net/client.cpp @@ -0,0 +1,288 @@ +/* + * client.cpp + * + * Created on: 18.01.2013 + * Author: sr + */ + +#include "client.h" +#include "../util/global.h" +#include "../../shared/settings.h" +#include "../../shared/util.h" +#include +#include +#include +#include + +#define CHALLENGE_LEN 20 + +ClientId Client::_clientIdCounter = 0; + +Client::Client(QSslSocket* socket) : + _socket(socket), _authed(0), _desiredProjectionSource(0), _isProjectionSource(false), + _currentProjectionSource(0), _vncPort(0), _activeVncClient(false) +{ + assert(socket != NULL); + _id = ++_clientIdCounter; + _pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS; + _ip = _socket->peerAddress().toString(); + qDebug("*** Client %s created.", qPrintable(_ip)); + // Connect important signals + connect(_socket, SIGNAL(disconnected()), this, SLOT(onClosed())); + connect(_socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError))); + connect(_socket, SIGNAL(sslErrors(const QList &)), this, SLOT(onSslErrors(const QList &))); + connect(_socket, SIGNAL(readyRead()), this, SLOT(onDataArrival())); + // Send challenge + _challenge.resize(CHALLENGE_LEN); + for (int i = 0; i < CHALLENGE_LEN; ++i) + _challenge[i] = qrand() & 0xff; + _toClient.reset(); + _toClient.setField(_ID, _CHALLENGE); + _toClient.setField(_CHALLENGE, _challenge); + _toClient.writeMessage(_socket); + // give client 3 seconds to complete handshake + _timerIdAuthTimeout = startTimer(3000); +} + +Client::~Client() +{ + if (_socket != NULL) + { + qDebug("**** DELETE IN DESTRUCTOR"); + _socket->deleteLater(); + } + qDebug("*** Client %s destroyed.", qPrintable(_ip)); +} + +void Client::timerEvent(QTimerEvent* event) +{ + if (event->timerId() == _timerIdAuthTimeout) + { + // Client did not send login request within 3 seconds + killTimer(_timerIdAuthTimeout); + _timerIdAuthTimeout = 0; + this->disconnect(); + } +} + +void Client::sendMessage(NetworkMessage& message) +{ + if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState) + return; + message.writeMessage(_socket); + if (!message.writeComplete()) + { + qCritical() << "SendMessage to client " << _name << "@" << _ip << " failed!"; + } +} + +void Client::requestThumb(const int width, const int height) +{ + if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState) + { + qDebug("requestThumb called in bad state"); + return; + } + _toClient.reset(); + _toClient.setField(_ID, _THUMB); + _toClient.setField(_X, QString::number(width)); + _toClient.setField(_Y, QString::number(height)); + _toClient.writeMessage(_socket); +} + +void Client::onDataArrival() +{ + _pingTimeout = QDateTime::currentMSecsSinceEpoch() + PING_TIMEOUT_MS; + // + if (_socket == NULL || _socket->state() != QAbstractSocket::ConnectedState) + { + qDebug("dataArrival called in bad state"); + return; + } + + //qDebug() << _socket->bytesAvailable() << " bytes to read"; + bool ret; + while (_socket->bytesAvailable()) + { + ret = _fromClient.readMessage(_socket); // let the message read data from socket + if (!ret) // error parsing msg, disconnect client! + { + this->disconnect(); + return; + } + if (_fromClient.readComplete()) // message is complete + { + this->handleMsg(); + if (_socket == NULL) + return; + _fromClient.reset(); + } + } +} + +void Client::onSslErrors(const QList & errors) +{ + this->disconnect(); +} + +void Client::onClosed() +{ + this->disconnect(); +} + +void Client::onError(QAbstractSocket::SocketError errcode) +{ + this->disconnect(); +} + +void Client::handleMsg() +{ + const QString &id = _fromClient.getFieldString(_ID); + if (id.isEmpty()) + { + qDebug("Received message with empty ID field. ignored."); + return; + } + qDebug() << "Received message " << id; + + if (_authed == 2) + { + // Following messages are only valid of the client is already authenticated + if (id == _THUMB) + { + QPixmap pixmap; + if (!pixmap.loadFromData(_fromClient.getFieldBytes("IMG"))) + { + qDebug("Could not decode thumbnail image from client."); + return; + } + emit thumbUpdated(this, pixmap); + } + 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 << " (" << _ip << ") failed."; + // TODO: Show message on manager + } + } + else + { + _vncRoPass = _fromClient.getFieldString("ROPASS"); + _vncRwPass = _fromClient.getFieldString("RWPASS"); + } + _vncPort = port; + emit vncServerStateChange(this); + } + else if (id == _VNCCLIENT) + { + // Client tells us that it started or stopped displaying a remote screen via VNC + _activeVncClient = (_fromClient.getFieldString("ENABLED").toInt() != 0); + const ClientId other = (ClientId)_fromClient.getFieldString("CLIENTID").toInt(); + + if (!_activeVncClient && other == 0) + _desiredProjectionSource = 0; + + emit vncClientStateChange(this); + + if (!_activeVncClient) + _currentProjectionSource = 0; + else + _currentProjectionSource = other; + } + return; + } + + if (_authed == 1) // Not authed yet, only care about login requests + { + if (id == _LOGIN) + { + killTimer(_timerIdAuthTimeout); + _timerIdAuthTimeout = 0; + ClientLogin request; + request.accept = true; + request.host = _fromClient.getFieldString("HOST"); + request.name = _fromClient.getFieldString("NAME"); + request.ip = _socket->peerAddress().toString(); + qDebug() << "Login request by " << request.name; + // 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; + _toClient.reset(); + _toClient.setField(_ID, _LOGIN); + _toClient.writeMessage(_socket); + emit authenticated(this); + } + return; + } + + if (_authed == 0) + { + // Waiting for challenge reply by client + if (id == _CHALLENGE) + { + QByteArray hash(_fromClient.getFieldBytes(_HASH)); + QByteArray challenge(_fromClient.getFieldBytes(_CHALLENGE)); + if (genSha1(&Global::sessionNameArray(), &_challenge) != hash) + { // Challenge reply is invalid, drop client + _toClient.buildErrorMessage("Challenge reply invalid."); + _toClient.writeMessage(_socket); + this->deleteLater(); + return; + } + // Now answer to challenge by client + _toClient.reset(); + _toClient.setField(_ID, _CHALLENGE); + _toClient.setField(_HASH, genSha1(&Global::sessionNameArray(), &challenge)); + _toClient.writeMessage(_socket); + _authed = 1; + qDebug("client's challenge reply was valid, step <- 1"); + } + return; + } + +} + +void Client::startVncServer() +{ + _vncPort = 0; + _toClient.reset(); + _toClient.setField(_ID, _VNCSERVER); + _toClient.setField("ENABLE", QByteArray("1")); + sendMessage(_toClient); +} + +void Client::stopVncServer() +{ + _toClient.reset(); + _toClient.setField(_ID, _VNCSERVER); + _toClient.setField("ENABLE", QByteArray("0")); + sendMessage(_toClient); +} + +void Client::disconnect() +{ + if (_socket != NULL) + { + qDebug("*** Client %s disconnected.", qPrintable(_ip)); + _socket->blockSignals(true); + _socket->abort(); + _socket->deleteLater(); + _socket = NULL; + this->deleteLater(); + } +} diff --git a/src/server/net/client.h b/src/server/net/client.h new file mode 100644 index 0000000..8004ebb --- /dev/null +++ b/src/server/net/client.h @@ -0,0 +1,104 @@ +#ifndef CLIENT_H_ +#define CLIENT_H_ + +#include +#include +#include +#include "../../shared/networkmessage.h" + +class QSslSocket; + +struct ClientLogin +{ + bool accept; + QString name; + QString host; + QString ip; +}; + +typedef int ClientId; + +class Client : public QObject +{ + Q_OBJECT + +private: + static ClientId _clientIdCounter; + + QSslSocket *_socket; + int _authed; // 0 = challenge sent, awaiting reply 1 = challenge ok, client challenge replied, awaiting login, 2 = ESTABLISHED + QString _name; + QString _host; + QString _ip; + QByteArray _challenge; + qint64 _pingTimeout; + NetworkMessage _toClient, _fromClient; + int _timerIdAuthTimeout; + + ClientId _id; // this client's unique id + + // If this client should be projected to from another client, the other client's id is set here. 0 otherwise. + // This is not currently used and it is questionable if this makes sense, as it might just be confusing if + // several groups students watch different other students. + // Also, visualizing such a situation in the GUI in a meaningful way would be hard. + ClientId _desiredProjectionSource; + // This boolean tells whether this client is currently the VNC broadcast source. This + // version only allows "one to all others" setups + bool _isProjectionSource; + + ClientId _currentProjectionSource; + + QString _vncRwPass, _vncRoPass; + int _vncPort; + bool _activeVncClient; + + void handleMsg(); + void disconnect(); + +protected: + void timerEvent(QTimerEvent* event); + +public: + explicit Client(QSslSocket* socket); + ~Client(); + void requestThumb(const int width, const int height); + void sendMessage(NetworkMessage& message); + //void acceptData(); + const inline bool isAuthed() const { return _authed == 2; } + const inline QString& name() const { return _name; } + const inline QString& host() const { return _host; } + const inline QString& ip() const { return _ip; } + // The computer ID (used eg. for saving the frame positions) is currently the IP, but this is an extra method for easier modification later on + const inline QString& computerId() const { return _ip; } + const inline ClientId id() const { return _id; } + + inline const QString& vncRwPass() const { return _vncRwPass; } + inline const QString& vncRoPass() const { return _vncRoPass; } + inline const int vncPort() const { return _vncPort; } + inline const bool isActiveVncClient() const { return _activeVncClient; } + inline const bool isActiveVncServer() const { return _vncPort > 0; } + inline const ClientId desiredProjectionSource() const { return _desiredProjectionSource; } + inline void setDesiredProjectionSource(ClientId source) { _desiredProjectionSource = source; } + inline const bool isProjectionSource() const { return _isProjectionSource; } + inline void setProjectionSource(bool enable) { _isProjectionSource = enable; } + inline const ClientId currentProjectionSource() const { return _currentProjectionSource; } + inline void setCurrentProjectionSource(ClientId source) { _currentProjectionSource = source; } + void startVncServer(); + void stopVncServer(); + +signals: + void authenticating(Client* client, ClientLogin* request); + void authenticated(Client* client); + void thumbUpdated(Client* client, const QPixmap& thumb); + void vncServerStateChange(Client* client); + void vncClientStateChange(Client* client); + +private slots: + void onSslErrors(const QList & errors); // triggered for errors that occur during SSL negotiation + void onDataArrival(); // triggered if data is available for reading + void onClosed(); // triggered if the socket is closed + void onError(QAbstractSocket::SocketError errcode); // triggered if an error occurs on the socket + +}; + +#endif /* CLIENT_H_ */ diff --git a/src/server/net/discoverylistener.cpp b/src/server/net/discoverylistener.cpp new file mode 100644 index 0000000..051a972 --- /dev/null +++ b/src/server/net/discoverylistener.cpp @@ -0,0 +1,165 @@ +/* + * discoverylistener.cpp + * + * Created on: 25.01.2013 + * Author: sr + */ + +#include "discoverylistener.h" +#include "certmanager.h" +#include "../util/global.h" +#include "../../shared/settings.h" +#include "../../shared/network.h" +#include "../../shared/util.h" +#include +#include +#include + +#define UDPBUFSIZ 9000 +#define SPAM_CUTOFF 50 +#define SPAM_MODERATE_INTERVAL 6787 +#define SPAM_MODERATE_AT_ONCE 100 + +// static objects + + +// +++ static ++++ hash ip address +++ + +static quint16 hash(const QHostAddress& host) +{ + static quint16 seed1 = 0, seed2 = 0; + while (seed1 == 0) // Make sure the algorithm uses different seeds each time the program is + { // run to prevent hash collision attacks + seed1 = (quint16)(qrand() & 0xffff); + seed2 = (quint16)(qrand() & 0xffff); + } + quint8 data[16], len; + if (host.protocol() == QAbstractSocket::IPv4Protocol) + { + // IPv4 + quint32 addr = host.toIPv4Address(); + len = 4; + memcpy(data, &addr, len); + } + else if (QAbstractSocket::IPv6Protocol) + { + // IPv6 + len = 16; + // Fast version (might break with future qt versions) + memcpy(data, host.toIPv6Address().c, len); + /* // --- Safe version (but better try to figure out a new fast way if above stops working ;)) + Q_IPV6ADDR addr = host.toIPv6Address(); + for (int i = 0; i < len; ++i) + data[i] = addr[i]; + */ + } + else + { + // Durr? + len = 2; + data[0] = (quint16)qrand(); + data[1] = (quint16)qrand(); + } + quint16 result = 0; + quint16 mod = seed1; + for (quint8 i = 0; i < len; ++i) + { + result = ((result << 1) + data[i]) ^ mod; // because of the shift this algo is not suitable for len(input) > 8 + mod += seed2 + data[i]; + } + return result; +} + +// +++++++++++++++++++++++++++++++++++ + +DiscoveryListener::DiscoveryListener() : + _socket(this), _counterResetPos(0) +{ + if (!_socket.bind(SERVICE_DISCOVERY_PORT)) + qFatal("Could not bind to service discovery port %d", (int)SERVICE_DISCOVERY_PORT); + connect(&_socket, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + for (int i = 0; i < SD_PACKET_TABLE_SIZE; ++i) + _packetCounter[i] = 0; + startTimer((SPAM_MODERATE_AT_ONCE * SPAM_MODERATE_INTERVAL) / SD_PACKET_TABLE_SIZE + 1); +} + +DiscoveryListener::~DiscoveryListener() +{ + // TODO Auto-generated destructor stub +} + +/** + * Overrides + */ + +/** + * Decrease packet counters + */ +void DiscoveryListener::timerEvent(QTimerEvent* event) +{ + for (int i = 0; i < SPAM_MODERATE_AT_ONCE; ++i) + { + if (++_counterResetPos >= SD_PACKET_TABLE_SIZE) + _counterResetPos = 0; + if (_packetCounter[_counterResetPos] > 10) + _packetCounter[_counterResetPos] -= 10; + else if (_packetCounter[_counterResetPos] != 0) + _packetCounter[_counterResetPos] = 0; + } +} + +/** + * Slots + */ + +void DiscoveryListener::onReadyRead() +{ + char data[UDPBUFSIZ]; + QHostAddress addr; + quint16 port; + while (_socket.hasPendingDatagrams()) + { + const qint64 size = _socket.readDatagram(data, UDPBUFSIZ, &addr, &port); + if (size <= 0) + continue; + const quint16 bucket = hash(addr) % SD_PACKET_TABLE_SIZE; + if (_packetCounter[bucket] > SPAM_CUTOFF) + { + qDebug() << "SD: Potential (D)DoS from " << _socket.peerAddress().toString(); + // emit some signal and pop up a big warning that someone is flooding/ddosing the PVS SD + // ... on the other hand, will the user understand? ;) + continue; + } + ++_packetCounter[bucket]; + _packet.reset(); + if (!_packet.readMessage(data, (quint32)size)) + continue; + // Valid packet, process it: + const QByteArray iplist(_packet.getFieldBytes(_IPLIST)); + const QByteArray hash(_packet.getFieldBytes(_HASH)); + const QByteArray salt1(_packet.getFieldBytes(_SALT1)); + const QByteArray salt2(_packet.getFieldBytes(_SALT2)); + // For security, the salt has to be at least 16 bytes long + if (salt1.size() < 16 || salt2.size() < 16) + continue; // To make this more secure, you could remember the last X salts used, and ignore new packets using the same + // Check if the source IP of the packet matches any of the addresses given in the IP list + if (!Network::isAddressInList(QString::fromUtf8(iplist), addr.toString())) + continue; + // If so, check if the submitted hash seems valid + if (genSha1(&Global::sessionNameArray(), &salt1, &iplist) != hash) + continue; // did not match local session name + qDebug("Got matching discovery request..."); + QByteArray myiplist(Network::interfaceAddressesToString().toUtf8()); + QSslKey key; + QSslCertificate cert; + CertManager::getPrivateKeyAndCert("manager", key, cert); + QByteArray certhash(cert.digest(QCryptographicHash::Sha1)); + // Reply to client + _packet.reset(); + _packet.setField(_HASH, genSha1(&Global::sessionNameArray(), &salt2, &myiplist, &CLIENT_PORT_ARRAY, &certhash)); + _packet.setField(_IPLIST, myiplist); + _packet.setField(_PORT, CLIENT_PORT_ARRAY); + _packet.setField(_CERT, certhash); + _packet.writeMessage(&_socket, addr, port); + } +} diff --git a/src/server/net/discoverylistener.h b/src/server/net/discoverylistener.h new file mode 100644 index 0000000..64d4351 --- /dev/null +++ b/src/server/net/discoverylistener.h @@ -0,0 +1,40 @@ +/* + * discoverylistener.h + * + * Created on: 25.01.2013 + * Author: sr + */ + +#ifndef DISCOVERYLISTENER_H_ +#define DISCOVERYLISTENER_H_ + +#include +#include +#include "../../shared/networkmessage.h" + +#define SD_PACKET_TABLE_SIZE 20000 + +class DiscoveryListener : public QObject +{ + Q_OBJECT + +private: + QUdpSocket _socket; + NetworkMessage _packet; + int _counterResetPos; + + quint8 _packetCounter[SD_PACKET_TABLE_SIZE]; // count packets per source address to ignore spammers + +protected: + void timerEvent(QTimerEvent* event); + +public: + DiscoveryListener(); + virtual ~DiscoveryListener(); + +private slots: + void onReadyRead(); + +}; + +#endif /* DISCOVERYLISTENER_H_ */ diff --git a/src/server/net/listenserver.cpp b/src/server/net/listenserver.cpp new file mode 100644 index 0000000..706962d --- /dev/null +++ b/src/server/net/listenserver.cpp @@ -0,0 +1,40 @@ +#include "listenserver.h" +#include "client.h" +#include + +#define MAX_CLIENTS 50 + +ListenServer::ListenServer(quint16 port) +{ + if (!_server.listen(QHostAddress::Any, port) || !_server.isListening()) + qFatal("Cannot bind to TCP port %d (incoming SSL clients)", (int)port); + connect(&_server, SIGNAL(newConnection()), this, SLOT(newClientConnection())); +} + +ListenServer::~ListenServer() +{ + _server.close(); + for (int i = 0; i < _clients.size(); ++i) + _clients[i]->deleteLater(); +} + +/** + * Slots + */ + +void ListenServer::newClientConnection() +{ + QSslSocket* sock; + while ((sock = (QSslSocket*)_server.nextPendingConnection()) != NULL) + { + if (_clients.size() >= MAX_CLIENTS) + { + sock->abort(); + sock->deleteLater(); + continue; + } + Client* client = new Client(sock); + _clients.append(client); // create new client class and add to list + emit newClient(client); + } +} diff --git a/src/server/net/listenserver.h b/src/server/net/listenserver.h new file mode 100644 index 0000000..f237703 --- /dev/null +++ b/src/server/net/listenserver.h @@ -0,0 +1,30 @@ +#ifndef LISTENSERVER_H_ +#define LISTENSERVER_H_ + +#include +#include +#include "sslserver.h" + +class Client; + +class ListenServer : public QObject +{ + Q_OBJECT + +private: + SslServer _server; + QList _clients; + +public: + explicit ListenServer(quint16 port); + virtual ~ListenServer(); + +private slots: + void newClientConnection(); + +signals: + void newClient(Client* client); + +}; + +#endif /* LISTENSERVER_H_ */ diff --git a/src/server/net/sslserver.cpp b/src/server/net/sslserver.cpp new file mode 100644 index 0000000..70daea4 --- /dev/null +++ b/src/server/net/sslserver.cpp @@ -0,0 +1,113 @@ +/* + # Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + # ----------------------------------------------------------------------------- + # src/net/SslServer.cpp + # - provide QTcpServer-like behaviour for SSL + # ----------------------------------------------------------------------------- + */ + +#include "sslserver.h" +#include +#include +#include "certmanager.h" + +SslServer::SslServer() +{ + _tmr = startTimer(5123); + //QSslSocket::setDefaultCiphers(QSslSocket::supportedCiphers()); +} + +SslServer::~SslServer() +{ + killTimer((_tmr)); +} + +void SslServer::incomingConnection(int socketDescriptor) +{ + QSslSocket *serverSocket = new QSslSocket(NULL); + connect(serverSocket, SIGNAL(sslErrors(const QList &)), this, SLOT(sslErrors(const QList &))); + QSslKey key; + QSslCertificate cert; + CertManager::getPrivateKeyAndCert("manager", key, cert); + serverSocket->setPrivateKey(key); + serverSocket->setLocalCertificate(cert); + serverSocket->setPeerVerifyMode(QSslSocket::VerifyNone); + serverSocket->setProtocol(QSsl::SslV3); + //printf("Keylen %d\n", serverSocket->privateKey().length()); + if (serverSocket->setSocketDescriptor(socketDescriptor)) + { + // Once the connection is successfully encrypted, raise our newConnection event + connect(serverSocket, SIGNAL(encrypted()), this, SIGNAL(newConnection())); + serverSocket->startServerEncryption(); + _pending.push_back(serverSocket); + } + else + { + serverSocket->deleteLater(); + } +} + +void SslServer::sslErrors(const QList & errors) +{ + //qDebug("FIXME: SSL ERRORS on SERVER: %s", qPrintable(errors.begin()->errorString())); +} + +void SslServer::timerEvent(QTimerEvent* event) +{ + // Remove all sockets marked for deletion + while (!_delete.isEmpty()) + { + QSslSocket *sock = _delete.takeFirst(); + sock->blockSignals(true); + sock->deleteLater(); + } + _delete = _pending; + _pending.clear(); +} + +bool SslServer::hasPendingConnections() +{ + for (QList::iterator it(_pending.begin()); it != _pending.end(); it++) + { + qDebug("State: %d - Encrypted: %d", (int)(*it)->state(), (*it)->isEncrypted()); + if ((*it)->state() == QAbstractSocket::ConnectedState && (*it)->isEncrypted()) + return true; + } + return false; +} + +QTcpSocket* SslServer::nextPendingConnection() +{ + for (QList::iterator it(_pending.begin()); it != _pending.end(); it++) + { + if ((*it)->state() == QAbstractSocket::ConnectedState && (*it)->isEncrypted()) + { + QSslSocket *sock = *it; + QObject::disconnect(sock, SIGNAL(encrypted()), this, SIGNAL(newConnection())); + _pending.removeAll(sock); + _delete.removeAll(sock); + return sock; + } + } + for (QList::iterator it(_delete.begin()); it != _delete.end(); it++) + { + if ((*it)->state() == QAbstractSocket::ConnectedState && (*it)->isEncrypted()) + { + QSslSocket *sock = *it; + QObject::disconnect(sock, SIGNAL(encrypted()), this, SIGNAL(newConnection())); + _pending.removeAll(sock); + _delete.removeAll(sock); + return sock; + } + } + return NULL; +} + diff --git a/src/server/net/sslserver.h b/src/server/net/sslserver.h new file mode 100644 index 0000000..400bf6a --- /dev/null +++ b/src/server/net/sslserver.h @@ -0,0 +1,50 @@ +/* +# Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg +# +# This program is free software distributed under the GPL version 2. +# See http://openslx.org/COPYING +# +# If you have any feedback please consult http://openslx.org/feedback and +# send your suggestions, praise, or complaints to feedback@openslx.org +# +# General information about OpenSLX can be found at http://openslx.org/ +# ----------------------------------------------------------------------------- +# src/net/SslServer.cpp +# - provide QTcpServer-like behaviour for SSL +# ----------------------------------------------------------------------------- +*/ + +#ifndef SSLSERVER_H_ +#define SSLSERVER_H_ + +#include +#include +#include + +class QSslSocket; + +class SslServer : public QTcpServer +{ + Q_OBJECT + +private Q_SLOTS: + void sslErrors ( const QList & errors ); + +public: + SslServer(); + virtual ~SslServer(); + + bool hasPendingConnections (); + // This one has to return a TcpSocket as we're overwriting from the base class + // just cast it to QSslSocket later + virtual QTcpSocket* nextPendingConnection(); + +protected: + void incomingConnection(int socketDescriptor); + void timerEvent (QTimerEvent* event); + QList _pending; + QList _delete; + int _tmr; +}; + +#endif /* SSLSERVER_H_ */ diff --git a/src/server/sessionnamewindow/sessionnamewindow.cpp b/src/server/sessionnamewindow/sessionnamewindow.cpp new file mode 100644 index 0000000..1d8c699 --- /dev/null +++ b/src/server/sessionnamewindow/sessionnamewindow.cpp @@ -0,0 +1,68 @@ +/* + # Copyright (c) 2009 - OpenSLX Project, Computer Center University of Freiburg + # + # This program is free software distributed under the GPL version 2. + # See http://openslx.org/COPYING + # + # If you have any feedback please consult http://openslx.org/feedback and + # send your suggestions, praise, or complaints to feedback@openslx.org + # + # General information about OpenSLX can be found at http://openslx.org/ + # ----------------------------------------------------------------------------- + # mainWindow.cpp + This is the Main class for the pvsManager. The GUI is contructed here. + # ----------------------------------------------------------------------------- + */ + +#include +#include "sessionnamewindow.h" +#include "../util/global.h" + +#include "ui_sessionname.h" + +SessionNameWindow::SessionNameWindow(QWidget *parent) : + QDialog(parent), ui(new Ui::SessionName) + +{ + ui->setupUi(this); + connect(ui->bboxOkCancel, SIGNAL(accepted()), this, SLOT(onOkClicked())); + connect(ui->bboxOkCancel, SIGNAL(rejected()), this, SLOT(close())); + connect(ui->cmdRandom, SIGNAL(clicked(bool)), this, SLOT(onGenerateRandomName())); +} + +SessionNameWindow::~SessionNameWindow() +{ + delete ui; +} + +void SessionNameWindow::show(const QString& name) +{ + ui->txtName->setText(name); + this->showNormal(); +} + +/* + * Overridden methods + */ + +void SessionNameWindow::closeEvent(QCloseEvent *e) +{ + e->ignore(); + this->hide(); +} + +/** + * Slots + */ + +void SessionNameWindow::onOkClicked() +{ + Global::setSessionName(ui->txtName->text()); + emit updateSessionName(); + this->hide(); +} + +void SessionNameWindow::onGenerateRandomName() +{ + ui->txtName->setText(QString::number(qrand() % 9000 + 1000)); +} diff --git a/src/server/sessionnamewindow/sessionnamewindow.h b/src/server/sessionnamewindow/sessionnamewindow.h new file mode 100644 index 0000000..ed944c1 --- /dev/null +++ b/src/server/sessionnamewindow/sessionnamewindow.h @@ -0,0 +1,38 @@ +#ifndef _SESSIONNAMEWINDOW_H_ +#define _SESSIONNAMEWINDOW_H_ + +#include + + +namespace Ui +{ +class SessionName; +} + +class SessionNameWindow : public QDialog +{ + Q_OBJECT + +private: + Ui::SessionName *ui; + +public: + SessionNameWindow(QWidget *parent = 0); + ~SessionNameWindow(); + + void show(const QString& name); + +protected: + void closeEvent(QCloseEvent *e); + +private slots: + void onOkClicked(); + void onGenerateRandomName(); + +signals: + void updateSessionName(); + +}; + + +#endif diff --git a/src/server/util/global.cpp b/src/server/util/global.cpp new file mode 100644 index 0000000..9b7b4f2 --- /dev/null +++ b/src/server/util/global.cpp @@ -0,0 +1,17 @@ +/* + * global.cpp + * + * Created on: 29.01.2013 + * Author: sr + */ + +#include "global.h" + +QString Global::_sessionName = QString(); +QByteArray Global::_sessionNameArray = QByteArray(); + +void Global::setSessionName(const QString& name) +{ + Global::_sessionName = name; + Global::_sessionNameArray = name.toUtf8(); +} diff --git a/src/server/util/global.h b/src/server/util/global.h new file mode 100644 index 0000000..38eec6d --- /dev/null +++ b/src/server/util/global.h @@ -0,0 +1,29 @@ +/* + * global.h + * + * Created on: 29.01.2013 + * Author: sr + */ + +#ifndef GLOBAL_H_ +#define GLOBAL_H_ + +#include +#include + +class Global +{ +private: + Global(){} + ~Global(){} + + static QString _sessionName; + static QByteArray _sessionNameArray; + +public: + static const QString& sessionName() { return Global::_sessionName; } + static const QByteArray& sessionNameArray() { return Global::_sessionNameArray; } + static void setSessionName(const QString& name); +}; + +#endif /* GLOBAL_H_ */ diff --git a/src/server/util/util.cpp b/src/server/util/util.cpp new file mode 100644 index 0000000..7ff9404 --- /dev/null +++ b/src/server/util/util.cpp @@ -0,0 +1,14 @@ +/* + * Util.cpp + * + * Created on: 18.01.2013 + * Author: sr + */ + +#include "util.h" + +namespace Util +{ + +} + diff --git a/src/server/util/util.h b/src/server/util/util.h new file mode 100644 index 0000000..e2d6a62 --- /dev/null +++ b/src/server/util/util.h @@ -0,0 +1,17 @@ +#ifndef UTIL_H_ +#define UTIL_H_ + +// Helper for getting a settings object in various places, so if you ever change the organization, location, +// file format or anything, you won't have to edit in 100 places. +// Use like this: +// USER_SETTINGS(settings) +// settings.value("somekey") +#define USER_SETTINGS(name) QSettings name (QSettings::IniFormat, QSettings::UserScope, "openslx", "pvs2mgr") +#define SYSTEM_SETTINGS(name) QSettings name (QSettings::IniFormat, QSettings::SystemScope, "openslx", "pvs2mgr") + +namespace Util +{ + +} + +#endif /* UTIL_H_ */ -- cgit v1.2.3-55-g7522