diff --git a/data/sddm.conf.in b/data/sddm.conf.in index 9522ffd..204dc52 100644 --- a/data/sddm.conf.in +++ b/data/sddm.conf.in @@ -89,3 +89,6 @@ MinimumVT=7 # Valid values: on|off|none # If property is set to none, numlock won't be changed Numlock=none + +# Enables the XDMCP Server +XDMCPServer=false \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a82ae45..1d6d117 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -20,6 +20,9 @@ set(DAEMON_SOURCES daemon/Session.cpp daemon/SignalHandler.cpp daemon/SocketServer.cpp + daemon/xdmcp/Packet.cpp + daemon/xdmcp/Server.cpp + daemon/xdmcp/Utils.cpp ) if(USE_QT5) diff --git a/src/common/Configuration.cpp b/src/common/Configuration.cpp index 2095b05..4b780b3 100644 --- a/src/common/Configuration.cpp +++ b/src/common/Configuration.cpp @@ -63,6 +63,7 @@ namespace SDDM { bool autoRelogin { false }; Configuration::NumState numlock { Configuration::NUM_NONE }; + bool xdmcpServerEnabled { false }; }; Configuration::Configuration(const QString &configPath, QObject *parent) : QObject(parent), d(new ConfigurationPrivate()) { @@ -122,6 +123,7 @@ namespace SDDM { } else { d->numlock = Configuration::NUM_NONE; } + d->xdmcpServerEnabled = settings.value("XDMCPServer", d->xdmcpServerEnabled).toBool(); } void Configuration::save() { @@ -158,6 +160,8 @@ namespace SDDM { settings.setValue("Numlock", "on"); else if (d->numlock == NUM_SET_OFF) settings.setValue("Numlock", "off"); + + settings.setValue("XDMCPServer", d->xdmcpServerEnabled); } Configuration *Configuration::instance() { @@ -261,4 +265,9 @@ namespace SDDM { const Configuration::NumState Configuration::numlock() const { return d->numlock; } + + bool Configuration::xdmcpServerEnabled() const { + return d->xdmcpServerEnabled; + } + } diff --git a/src/common/Configuration.h b/src/common/Configuration.h index cbef261..4b610d8 100644 --- a/src/common/Configuration.h +++ b/src/common/Configuration.h @@ -79,6 +79,8 @@ namespace SDDM { enum NumState { NUM_NONE, NUM_SET_ON, NUM_SET_OFF }; const NumState numlock() const; + bool xdmcpServerEnabled() const; + bool first { true }; bool testing { false }; diff --git a/src/daemon/DaemonApp.cpp b/src/daemon/DaemonApp.cpp index 9ad226b..9feb734 100644 --- a/src/daemon/DaemonApp.cpp +++ b/src/daemon/DaemonApp.cpp @@ -25,6 +25,7 @@ #include "PowerManager.h" #include "SeatManager.h" #include "SignalHandler.h" +#include "xdmcp/Server.h" #ifdef USE_QT5 #include "MessageHandler.h" @@ -65,6 +66,12 @@ namespace SDDM { // create seat manager m_seatManager = new SeatManager(this); + // start the XDMCP server + if (configuration()->xdmcpServerEnabled()) { + m_xdmcpServer = XDMCP::Server::instance(this); + m_xdmcpServer->start(); + } + // connect with display manager connect(m_seatManager, SIGNAL(seatCreated(QString)), m_displayManager, SLOT(AddSeat(QString))); connect(m_seatManager, SIGNAL(seatRemoved(QString)), m_displayManager, SLOT(RemoveSeat(QString))); diff --git a/src/daemon/DaemonApp.h b/src/daemon/DaemonApp.h index 81f955c..2088010 100644 --- a/src/daemon/DaemonApp.h +++ b/src/daemon/DaemonApp.h @@ -29,6 +29,9 @@ namespace SDDM { class DisplayManager; class PowerManager; class SeatManager; + namespace XDMCP { + class Server; + } class DaemonApp : public QCoreApplication { Q_OBJECT @@ -57,6 +60,7 @@ namespace SDDM { DisplayManager *m_displayManager { nullptr }; PowerManager *m_powerManager { nullptr }; SeatManager *m_seatManager { nullptr }; + XDMCP::Server *m_xdmcpServer { nullptr }; }; } diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp index f1a54b4..48137e6 100644 --- a/src/daemon/Display.cpp +++ b/src/daemon/Display.cpp @@ -53,6 +53,17 @@ namespace SDDM { return name; } + Display::Display(const QString& hostname, const int displayId, QObject* parent) : QObject(parent), + m_displayId(displayId), + m_authenticator(new Authenticator(this)), + m_displayServer(nullptr), + m_socketServer(new SocketServer(this)), + m_greeter(new Greeter(this)) { + m_display = QString("%1:%2").arg(hostname).arg(displayId); + + init(); + } + Display::Display(const int displayId, const int terminalId, Seat *parent) : QObject(parent), m_displayId(displayId), m_terminalId(terminalId), m_authenticator(new Authenticator(this)), @@ -63,12 +74,17 @@ namespace SDDM { m_display = QString(":%1").arg(m_displayId); - // restart display after user session ended - connect(m_authenticator, SIGNAL(stopped()), this, SLOT(stop())); - // restart display after display server ended connect(m_displayServer, SIGNAL(stopped()), this, SLOT(stop())); + init(); + } + + void Display::init() + { + // restart display after user session ended + connect(m_authenticator, SIGNAL(stopped()), this, SLOT(stop())); + // connect login signal connect(m_socketServer, SIGNAL(login(QLocalSocket*,QString,QString,QString)), this, SLOT(login(QLocalSocket*,QString,QString,QString))); @@ -91,6 +107,22 @@ namespace SDDM { // set socket name m_socket = QString("sddm-%1-%2").arg(m_display).arg(generateName(6)); + + // generate cookie + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 15); + + // resever 32 bytes + m_cookie.reserve(32); + + // create a random hexadecimal number + const char *digits = "0123456789abcdef"; + for (int i = 0; i < 32; ++i) + m_cookie[i] = digits[dis(gen)]; + + // generate auth file + addCookie(m_authPath); } Display::~Display() { @@ -113,6 +145,16 @@ namespace SDDM { return m_cookie; } + const QByteArray Display::rawCookie() const { + QByteArray cookie; + for (int i = 0; i < m_cookie.length() / 2; i++) { + // horrible, just horrible + quint8 byte = QString("%1%2").arg(m_cookie[i*2]).arg(m_cookie[i*2+1]).toUInt(nullptr, 16); + cookie.append(byte); + } + return cookie; + } + Seat *Display::seat() const { return m_seat; } @@ -144,28 +186,14 @@ namespace SDDM { if (m_started) return; - // generate cookie - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(0, 15); + if (m_displayServer != nullptr) { + // set display server params + m_displayServer->setDisplay(m_display); + m_displayServer->setAuthPath(m_authPath); - // resever 32 bytes - m_cookie.reserve(32); - - // create a random hexadecimal number - const char *digits = "0123456789abcdef"; - for (int i = 0; i < 32; ++i) - m_cookie[i] = digits[dis(gen)]; - - // generate auth file - addCookie(m_authPath); - - // set display server params - m_displayServer->setDisplay(m_display); - m_displayServer->setAuthPath(m_authPath); - - // start display server - m_displayServer->start(); + // start display server + m_displayServer->start(); + } if ((daemonApp->configuration()->first || daemonApp->configuration()->autoRelogin()) && !daemonApp->configuration()->autoUser().isEmpty() && !daemonApp->configuration()->lastSession().isEmpty()) { @@ -221,9 +249,11 @@ namespace SDDM { m_socketServer->stop(); // stop display server - m_displayServer->blockSignals(true); - m_displayServer->stop(); - m_displayServer->blockSignals(false); + if (m_displayServer != nullptr) { + m_displayServer->blockSignals(true); + m_displayServer->stop(); + m_displayServer->blockSignals(false); + } // remove authority file QFile::remove(m_authPath); diff --git a/src/daemon/Display.h b/src/daemon/Display.h index 46d320b..9556209 100644 --- a/src/daemon/Display.h +++ b/src/daemon/Display.h @@ -35,6 +35,7 @@ namespace SDDM { Q_OBJECT Q_DISABLE_COPY(Display) public: + explicit Display(const QString& hostname, const int displayId, QObject *parent = 0); explicit Display(const int displayId, const int terminalId, Seat *parent); ~Display(); @@ -44,6 +45,7 @@ namespace SDDM { const QString &name() const; const QString &cookie() const; + const QByteArray rawCookie() const; void addCookie(const QString &file); Seat *seat() const; @@ -61,6 +63,8 @@ namespace SDDM { void loginSucceeded(QLocalSocket *socket); private: + void init(); + bool m_relogin { true }; bool m_started { false }; diff --git a/src/daemon/xdmcp/Packet.cpp b/src/daemon/xdmcp/Packet.cpp new file mode 100644 index 0000000..3a0c3d9 --- /dev/null +++ b/src/daemon/xdmcp/Packet.cpp @@ -0,0 +1,397 @@ +/* + * Packet type handling for X Display Control Protocol + * Copyright (C) 2013 Martin Bříza + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "Packet.h" +#include "Server.h" +#include "../Display.h" + +namespace SDDM { +namespace XDMCP { + +/******************************************************************************* +* PLUMBING + ******************************************************************************/ + + Packet::Packet(const QHostAddress &host, quint16 port) : m_host(host), + m_port(port), + m_valid(true) { + + } + + Packet::Packet(const QHostAddress &host, quint16 port, Reader &r) : m_host(host), + m_port(port), + m_valid(false) { + + } + + Packet::~Packet() { + + } + + bool Packet::isValid() const { + return m_valid; + } + + // static + Packet *Packet::decode(const QByteArray &data, const QHostAddress &host, quint16 port) { + Reader reader(data); + uint16_t version, opcode, length; + + reader >> version >> opcode >> length; + + if (version != 1) + return nullptr; + if (length != data.size() - 6) + return nullptr; + + switch (opcode) { + case _Query: + return new Query(host, port, reader); + case _BroadcastQuery: + return new BroadcastQuery(host, port, reader); + case _IndirectQuery: + return new IndirectQuery(host, port, reader); + case _ForwardQuery: + return new ForwardQuery(host, port, reader); + case _Willing: + return new Willing(host, port, reader); + case _Unwilling: + return new Unwilling(host, port, reader); + case _Request: + return new Request(host, port, reader); + case _Accept: + return new Accept(host, port, reader); + case _Decline: + return new Decline(host, port, reader); + case _Manage: + return new Manage(host, port, reader); + case _Refuse: + return new Refuse(host, port, reader); + case _Failed: + return new Failed(host, port, reader); + case _KeepAlive: + return new KeepAlive(host, port, reader); + case _Alive: + return new Alive(host, port, reader); + default: + qDebug() << " XDMCP: Got packet of an unknown type" << opcode; + return nullptr; + } + } + + void Packet::setHost(const QHostAddress &host) { + m_host = QHostAddress(host); + } + + const QHostAddress& Packet::host() const { + return m_host; + } + + void Packet::setPort(quint16 port) { + m_port = port; + } + + quint16 Packet::port() const { + return m_port; + } + + QByteArray Packet::encode() const { + return QByteArray(); + } + + Packet *Packet::onClientReceived() const { + qDebug() << " XDMCP: Client received a wrong packet type (no action assigned)"; + return nullptr; + } + + Packet *Packet::onServerReceived() const { + qDebug() << " XDMCP: Server received a wrong packet type (no action assigned)"; + return nullptr; + } + + Packet::Query::Query(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_authenticationNames; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Query::encode() const { + Writer w; + w << m_authenticationNames; + return w.finalize(Packet::_Query); + } + + Packet::BroadcastQuery::BroadcastQuery(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_authenticationNames; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::BroadcastQuery::encode() const { + Writer w; + w << m_authenticationNames; + return w.finalize(Packet::_BroadcastQuery); + } + + Packet::IndirectQuery::IndirectQuery(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_authenticationNames; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::IndirectQuery::encode() const { + Writer w; + w << m_authenticationNames; + return w.finalize(Packet::_IndirectQuery); + } + + Packet::ForwardQuery::ForwardQuery(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_clientAddress >> m_clientPort >> m_authenticationNames; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::ForwardQuery::encode() const { + Writer w; + w << m_clientAddress << m_clientPort << m_authenticationNames; + return w.finalize(Packet::_ForwardQuery); + } + + Packet::Willing::Willing(const QHostAddress &host, quint16 port, const QString &authenticationName, const QString &hostname, const QString &status) : Packet(host, port), + m_authenticationName(authenticationName.toLatin1()), + m_hostname(hostname.toLatin1()), + m_status(status.toLatin1()) { + qDebug() << " XDMCP: Prepared Willing reply for" << host << port << "with contents" << authenticationName << hostname << status; + } + + Packet::Willing::Willing(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_authenticationName >> m_hostname >> m_status; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Willing::encode() const { + Writer w; + w << m_authenticationName << m_hostname << m_status; + return w.finalize(Packet::_Willing); + } + + Packet::Unwilling::Unwilling(const QHostAddress &host, quint16 port, const QString &hostname, const QString &status) : Packet(host, port), + m_hostname(hostname.toLatin1()), + m_status(status.toLatin1()) { + qDebug() << " XDMCP: Prepared Unwilling reply for" << host << port << "with contents" << hostname << status; + } + + Packet::Unwilling::Unwilling(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_hostname >> m_status; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Unwilling::encode() const { + Writer w; + w << m_hostname << m_status; + return w.finalize(Packet::_Unwilling); + } + + Packet::Request::Request(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_displayNumber >> m_connectionTypes >> m_connectionAddresses >> m_authenticationName >> m_authenticationData >> m_authorizationNames >> m_manufacturerDisplayID; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Request::encode() const { + Writer w; + w << m_displayNumber << m_connectionTypes << m_connectionAddresses << m_authenticationName << m_authenticationData << m_authorizationNames << m_manufacturerDisplayID; + return w.finalize(Packet::_Request); + } + + Packet::Accept::Accept(const QHostAddress &host, quint16 port, uint32_t sessionId, const QString authenticationName, const QByteArray authenticationData, const QString authorizationName, const QByteArray authorizationData) : Packet(host, port), + m_sessionID(sessionId), + m_authenticationName(authenticationName.toLatin1()), + m_authenticationData(authenticationData), + m_authorizationName(authorizationName.toLatin1()), + m_authorizationData(authorizationData) { + qDebug() << " XDMCP: Prepared Accept reply for" << host << port << "with contents" << sessionId << authenticationName << authenticationData << authorizationName << authorizationData; + } + + Packet::Accept::Accept(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_sessionID >> m_authenticationName >> m_authenticationData >> m_authorizationName >> m_authorizationData; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Accept::encode() const { + Writer w; + w << m_sessionID << m_authenticationName << m_authenticationData << m_authorizationName << m_authorizationData; + return w.finalize(Packet::_Accept); + } + + Packet::Decline::Decline(const QHostAddress &host, quint16 port, const QString status, const QString authenticationName, const QByteArray authenticationData) : Packet(host, port), + m_status(status.toLatin1()), + m_authenticationName(authenticationName.toLatin1()), + m_authenticationData(authenticationData) { + qDebug() << " XDMCP: Prepared Decline reply for" << host << port << "with contents" << status << authenticationName << authenticationData; + } + + Packet::Decline::Decline(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_status >> m_authenticationName >> m_authenticationData; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Decline::encode() const { + Writer w; + w << m_status << m_authenticationName << m_authenticationData; + return w.finalize(Packet::_Decline); + } + + Packet::Manage::Manage(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_sessionID >> m_displayNumber >> m_displayClass; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Manage::encode() const { + Writer w; + w << m_sessionID << m_displayNumber << m_displayClass; + return w.finalize(Packet::_Manage); + } + + Packet::Refuse::Refuse(const QHostAddress &host, quint16 port, uint32_t sessionID) : Packet(host, port), + m_sessionID(sessionID) { + qDebug() << " XDMCP: Prepared Refuse reply for" << host << port << "with contents" << sessionID; + } + + Packet::Refuse::Refuse(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_sessionID; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Refuse::encode() const { + Writer w; + w << m_sessionID; + return w.finalize(Packet::_Refuse); + } + + Packet::Failed::Failed(const QHostAddress &host, quint16 port, uint32_t sessionID, const QString &status) : Packet(host, port), + m_sessionID(sessionID), + m_status(status.toLatin1()) { + qDebug() << " XDMCP: Prepared Failed reply for" << host << port << "with contents" << sessionID << status; + } + + Packet::Failed::Failed(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_sessionID >> m_status; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Failed::encode() const { + Writer w; + w << m_sessionID << m_status; + return w.finalize(Packet::_Failed); + } + + Packet::KeepAlive::KeepAlive(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_displayNumber >> m_sessionID; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::KeepAlive::encode() const { + Writer w; + w << m_displayNumber << m_sessionID; + return w.finalize(Packet::_KeepAlive); + } + + Packet::Alive::Alive(const QHostAddress &host, quint16 port, uint8_t sessionRunning, uint32_t sessionID) : Packet(host, port), + m_sessionRunning(sessionRunning), + m_sessionID(sessionID) { + qDebug() << " XDMCP: Prepared Alive reply for" << host << port << "with contents" << sessionRunning << sessionID; + } + + Packet::Alive::Alive(const QHostAddress &host, quint16 port, Reader &r) : Packet(host, port, r) { + r >> m_sessionRunning >> m_sessionID; + if (r.isFinished()) + m_valid = true; + } + + QByteArray Packet::Alive::encode() const { + Writer w; + w << m_sessionRunning << m_sessionID; + return w.finalize(Packet::_Alive); + } + +/******************************************************************************* + * SERVER IMPLEMENTATIONS + ******************************************************************************/ + + Packet *Packet::Query::onServerReceived() const { + if (m_authenticationNames.isEmpty()) + return new Willing(m_host, m_port, "", Server::instance()->hostname(), Server::instance()->status()); + + return new Unwilling(m_host, m_port, Server::instance()->hostname(), "Server does not support authentication"); + } + + Packet* Packet::Request::onServerReceived() const { + qDebug() << " XDMCP: Server: Received Request" << m_displayNumber << m_connectionTypes << m_connectionAddresses << m_authenticationName << m_authenticationData << m_authorizationNames << m_manufacturerDisplayID; + + if (m_authorizationNames.contains("MIT-MAGIC-COOKIE-1")) { + uint32_t sessionId = Server::instance()->newSessionId(); + // FIXME for obvious reasons + QHostAddress addr(QString("%1.%2.%3.%4").arg((uint) m_connectionAddresses.first()[0]).arg((uint) m_connectionAddresses.first()[1]).arg((uint) m_connectionAddresses.first()[2]).arg((uint) m_connectionAddresses.first()[3])); + Display *display = Server::instance()->newDisplay(sessionId, addr.toString(), m_displayNumber); + + return new Accept(m_host, m_port, sessionId, m_authenticationName, m_authenticationData, "MIT-MAGIC-COOKIE-1", display->rawCookie()); + } + + return new Decline(m_host, m_port, Server::instance()->status(), m_authenticationName, m_authenticationData); + } + + Packet* Packet::Manage::onServerReceived() const { + Display *display = Server::instance()->getDisplay(m_sessionID); + + if (display != nullptr) { + display->start(); + return nullptr; // this packet doesn't have any response on success + } + + return new Refuse(m_host, m_port, m_sessionID); + } + + Packet* Packet::KeepAlive::onServerReceived() const { + Display *display = Server::instance()->getDisplay(m_sessionID); + + if (display == nullptr) + return new Alive(m_host, m_port, 0, m_sessionID); + + if (display->displayId() != m_displayNumber) + return new Alive(m_host, m_port, 0, m_sessionID); + + return new Alive(m_host, m_port, 1, m_sessionID); + } + +/******************************************************************************* + * CLIENT IMPLEMENTATIONS + ******************************************************************************/ + +}; +}; diff --git a/src/daemon/xdmcp/Packet.h b/src/daemon/xdmcp/Packet.h new file mode 100644 index 0000000..9246541 --- /dev/null +++ b/src/daemon/xdmcp/Packet.h @@ -0,0 +1,394 @@ +/* + * Packet type handling for X Display Control Protocol + * Copyright (C) 2013 Martin Bříza + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef SDDM_XDMCP_PACKET_H +#define SDDM_XDMCP_PACKET_H + +#include +#include + +#include "Utils.h" + +namespace SDDM { +namespace XDMCP { + class Reader; + /** + * XDMCP Packet main class + * + * Defines interface and static methods to work with the protocol data + */ + class Packet { + public: + virtual ~Packet(); + + /** + * Get the packet's validity (especially for responses) + * + * \return validity + */ + bool isValid() const; + + /** + * Defines server behavior on receiving this packet + */ + virtual Packet *onServerReceived() const; + + /** + * Defines client behavior on receiving this packet + */ + virtual Packet *onClientReceived() const; + + /** + * Encode the packet to raw data according to the protocol + * + * \return Data byte array + */ + virtual QByteArray encode() const; + + /** + * Decode raw packet data and create a packet for further processing + * + * \param data Raw data from the socket + * \param host Source host of the packet + * \param port Source port of the packet + * \return Parsed packet + */ + static Packet *decode(const QByteArray &data, const QHostAddress &host = QHostAddress(), quint16 port = 0); + + /** + * Set the packet's source/destination host + * + * \param host The host + */ + void setHost(const QHostAddress &host); + + /** + * Get the packet's source/destination host + * + * \return The host + */ + const QHostAddress& host() const; + + /** + * Set the packet's source/destination host + * + * \param port The port + */ + void setPort(quint16 port); + + /** + * Get the packet's source/destination host + * + * \return The port + */ + quint16 port() const; + + /** + * Redundancy for everyone! + */ + enum Opcode { + _None = 0, + _BroadcastQuery = 1, + _Query, + _IndirectQuery, + _ForwardQuery, + _Willing, + _Unwilling, + _Request, + _Accept, + _Decline, + _Manage, + _Refuse, + _Failed, + _KeepAlive, + _Alive, + }; + + class BroadcastQuery; + class Query; + class IndirectQuery; + class ForwardQuery; + class Willing; + class Unwilling; + class Request; + class Accept; + class Decline; + class Manage; + class Refuse; + class Failed; + class KeepAlive; + class Alive; + + protected: + /** + * C'tor targetted for creating response packets + * + * Automatically sets the packet to be valid + * \param host Destination host for the response + * \param port Destination port for the response + */ + Packet(const QHostAddress &host, quint16 port); + /** + * C'tor targetted for parsing raw data + * + * \param host Destination host for the response + * \param port Destination port for the response + * \param r Reader containing the packet's raw data + */ + Packet(const QHostAddress &host, quint16 port, Reader &r); + + QHostAddress m_host; + quint16 m_port { 0 }; + bool m_valid { false }; + }; + + class Packet::BroadcastQuery : public Packet { + public: + BroadcastQuery(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + private: + QVector m_authenticationNames; + }; + + class Packet::Query : public Packet { + public: + Query(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + /** + * Server side handling of Query packet + * + * Client sends a list of authorization names + * + * If the list is empty (and we are willing to continue without + * authorization), we reply with \ref Willing with empty + * authenticationName + * + * Otherwise, we choose the one name that complies to the supported ones + * in the server and use it as authenticationName for the \ref Willing + * packet + * + * If none of the names complies and/or we don't want to continue + * without authorization, the reply is \ref Unwilling + * + * \return Response + */ + virtual Packet *onServerReceived() const; + private: + QVector m_authenticationNames; + }; + + class Packet::IndirectQuery : public Packet { + public: + IndirectQuery(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + private: + QVector m_authenticationNames; + }; + + class Packet::ForwardQuery : public Packet { + public: + ForwardQuery(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + private: + QByteArray m_clientAddress; + QByteArray m_clientPort; + QVector m_authenticationNames; + }; + + class Packet::Willing : public Packet { + public: + Willing(const QHostAddress &host, quint16 port, + const QString &authenticationName, const QString &hostname, + const QString &status); + Willing(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + /** + * Client side handling of Willing packet + * + * Description TBD + * + * Reply on success is \ref Request + * + * \return Response + */ +// virtual Packet *onClientReceived() const; + private: + QByteArray m_authenticationName; + QByteArray m_hostname; + QByteArray m_status; + }; + + class Packet::Unwilling : public Packet { + public: + Unwilling(const QHostAddress &host, quint16 port, + const QString &hostname, const QString &status); + Unwilling(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + private: + QByteArray m_hostname; + QByteArray m_status; + }; + + class Packet::Request : public Packet { + public: + Request(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + /** + * Server side handling of Request packet + * + * Client informs there will be displey displayNumber running on + * connectionAddresses accessible via connectionTypes. + * It also authorizes with the authenticationName and authenticationData. + * It sends a list of propsed authorizationNames for the server to choose + * one of them and reply using the \ref Accept packet where the chosen + * authorizationName will be stored along with the authorizationData for + * the client and the new sessionID. + * + * If the display cannot be used, the server replies using the \ref Decline + * packet with its status. + * + * \return Response + */ + virtual Packet *onServerReceived() const; + private: + uint16_t m_displayNumber; + QVector m_connectionTypes; + QVector m_connectionAddresses; + QByteArray m_authenticationName; + QByteArray m_authenticationData; + QVector m_authorizationNames; + QByteArray m_manufacturerDisplayID; + }; + + class Packet::Accept : public Packet { + public: + Accept(const QHostAddress &host, quint16 port, uint32_t sessionId, + const QString authenticationName, const QByteArray authenticationData, + const QString authorizationName, const QByteArray authorizationData); + Accept(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + /** + * Client side handling of Accept packet + * + * Description TBD + * + * Reply on succes is \ref Manage + * + * \return Response + */ +// virtual Packet *onClientReceived() const; + private: + uint32_t m_sessionID; + QByteArray m_authenticationName; + QByteArray m_authenticationData; + QByteArray m_authorizationName; + QByteArray m_authorizationData; + }; + + class Packet::Decline : public Packet { + public: + Decline(const QHostAddress &host, quint16 port, const QString status, + const QString authenticationName, const QByteArray authenticationData); + Decline(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + private: + QByteArray m_status; + QByteArray m_authenticationName; + QByteArray m_authenticationData; + }; + + class Packet::Manage : public Packet { + public: + Manage(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + /** + * Server side handling of Manage packet + * + * Client asks the server to open a connection to its opened display + * specified in the previous Request packet. + * + * There is no answer on success, just opening a connection. + * + * If the connection is specified wrong (erroneous sessionID, etc.), then + * the server replies with a \ref Refuse packet. + * + * If the connection cannot be opened due to an internal error, + * \ref Failed packet is sent to the client. + * + * \return Response + */ + virtual Packet *onServerReceived() const; + private: + uint32_t m_sessionID; + uint16_t m_displayNumber; + QByteArray m_displayClass; + }; + + class Packet::Refuse : public Packet { + public: + Refuse(const QHostAddress &host, quint16 port, uint32_t sessionID); + Refuse(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + private: + uint32_t m_sessionID; + }; + + class Packet::Failed : public Packet { + public: + Failed(const QHostAddress &host, quint16 port, uint32_t sessionID, const QString &status); + Failed(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + private: + uint32_t m_sessionID; + QByteArray m_status; + }; + + class Packet::KeepAlive : public Packet { + public: + KeepAlive(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + /** + * Server side handling of KeepAlive packet + * + * Clients asks the server if the session is still alive. + * + * Server replies with \ref Alive packet with either sessionRunning == 0 + * for a dead session or sessionRunning != 0 for a live one + */ + virtual Packet *onServerReceived() const; + private: + uint16_t m_displayNumber; + uint32_t m_sessionID; + }; + + class Packet::Alive : public Packet { + public: + Alive(const QHostAddress &host, quint16 port, uint8_t sessionRunning, uint32_t sessionID); + Alive(const QHostAddress &host, quint16 port, Reader &r); + virtual QByteArray encode() const; + private: + uint8_t m_sessionRunning; + uint32_t m_sessionID; + }; + +} // namespace XDMCP +} // namespace SDDM + +#endif // SDDM_XDMCP_PACKET_H diff --git a/src/daemon/xdmcp/Server.cpp b/src/daemon/xdmcp/Server.cpp new file mode 100644 index 0000000..b4dc6e1 --- /dev/null +++ b/src/daemon/xdmcp/Server.cpp @@ -0,0 +1,148 @@ +/* + * Server implementation for X Display Control Protocol + * Copyright (C) 2013 Martin Bříza + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "Server.h" +#include "Packet.h" +#include "../DaemonApp.h" +#include "../Display.h" + +#include + +namespace SDDM { +namespace XDMCP { + + Server *Server::self = nullptr; + + Server* Server::instance(DaemonApp* parent) { + if (self == nullptr) { + self = new Server(parent); + } + return self; + } + + Server::Server(DaemonApp* parent) : QUdpSocket(parent), + m_hostname(QHostInfo::localHostName()) { + + } + + Server::~Server() { + + } + + bool Server::start() { + qDebug() << " XDMCP: Server: Starting..."; + connect(this, SIGNAL(readyRead()), this, SLOT(newData())); + bool result = bind(m_address, m_port); + if (!result) { + qDebug() << " XDMCP: Server: Cannot bind" << m_address << m_port << errorString(); + } + else { + m_started = true; + m_status = "online"; + qDebug() << " XDMCP: Server: Started and listening on" << m_address << ":" << m_port; + } + return result; + } + + void Server::socketError(QAbstractSocket::SocketError socketError) { + qDebug() << " XDMCP: Error:" << errorString(); + // TODO: error recovery + m_started = false; + m_status = "error"; + } + + QString Server::hostname() const { + return m_hostname; + } + + QString Server::status() const { + return m_status; + } + + bool Server::isStarted() const { + return m_started; + } + + uint32_t Server::newSessionId() { + // realistically, can this serve more than 4 billion clients to actually cause trouble in removeDisplay? + while (m_displays.keys().contains(m_lastSession)) + m_lastSession++; + return m_lastSession++; + } + + Display* Server::newDisplay(uint32_t sessionId, const QString &hostName, uint32_t displayNumber) { + if (m_displays.contains(sessionId)) + return nullptr; + Display *display = new Display(hostName, displayNumber, this); + connect(display, SIGNAL(destroyed(QObject*)), SLOT(removeDisplay(QObject*))); + m_displays[sessionId] = display; + return display; + } + + Display* Server::getDisplay(uint32_t id) { + if (m_displays.contains(id)) + return m_displays[id]; + return nullptr; + } + + void Server::removeDisplay(QObject* obj) { + int key = m_displays.key(qobject_cast(obj), -1); + + if (key == -1) + return; + + m_displays.remove(key); + } + + void Server::setAddress(QHostAddress address) { + m_address = address; + } + + void Server::setPort(int port) { + m_port = port; + } + + void Server::newData() { + while (hasPendingDatagrams()) { + QByteArray data; + QHostAddress sender; + quint16 port; + data.resize(pendingDatagramSize()); + + readDatagram(data.data(), data.size(), &sender, &port); + + Packet *toProcess = Packet::decode(data, sender, port); + if (toProcess && toProcess->isValid()) { + Packet *response = toProcess->onServerReceived(); + if (response && response->isValid()) { + writeDatagram(response->encode(), response->host(), response->port()); + } + delete response; + } else { + qDebug() << " XDMCP: Server: Received packet wasn't decoded as valid"; + } + delete toProcess; + } + } + +} // namespace XDMCP +} // namespace SDDM + +#include "Server.moc" diff --git a/src/daemon/xdmcp/Server.h b/src/daemon/xdmcp/Server.h new file mode 100644 index 0000000..6f7bdae --- /dev/null +++ b/src/daemon/xdmcp/Server.h @@ -0,0 +1,118 @@ +/* + * Server implementation for X Display Control Protocol + * Copyright (C) 2013 Martin Bříza + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef SDDM_XDMCP_SERVER_H +#define SDDM_XDMCP_SERVER_H + +#include +#include +#include +#include + +// the same as in +#define XDM_UDP_PORT 177 + +namespace SDDM { + +class Display; + class DaemonApp; +namespace XDMCP { + + class Server : protected QUdpSocket + { + Q_OBJECT + public: + /** + * Get an instance of the XDMCP server. If there isn't any, construct a + * new one + * + * \param parent Parent for the eventual construction + * \return Singleton XDMCP Server instance + */ + static Server *instance(DaemonApp *parent = nullptr); + /** + * D'tor + */ + virtual ~Server(); + + /** + * Set port to listen on + * + * \param port The port + */ + void setPort(int port); + + /** + * Set address to listen on + * + * \param address The address + */ + void setAddress(QHostAddress address); + + /** + * Start the server + * + * \return True if successful + */ + bool start(); + + /** + * Get server online status + * + * \return True if running + */ + bool isStarted() const; + + + /** + * Returns a new session ID for incoming requests + */ + uint32_t newSessionId(); + + /** + * Create a new display + */ + Display *newDisplay(uint32_t sessionId, const QString &hostName, uint32_t displayNumber); + Display *getDisplay(uint32_t id); + QString status() const; + QString hostname() const; + + private slots: + void newData(); + void socketError(QAbstractSocket::SocketError socketError); + void removeDisplay(QObject *obj); + + private: + static Server *self; + explicit Server(DaemonApp *parent = nullptr); + + QString m_status { "offline" }; + QString m_hostname { "localhost" }; + QHostAddress m_address { QHostAddress::Any }; + quint16 m_port { XDM_UDP_PORT }; + bool m_started { false }; + uint32_t m_lastSession { 0 }; + QMap m_displays; + QMap m_timers; + }; +} +} + +#endif // SDDM_XDMCP_SERVER_H diff --git a/src/daemon/xdmcp/Utils.cpp b/src/daemon/xdmcp/Utils.cpp new file mode 100644 index 0000000..53ac7e3 --- /dev/null +++ b/src/daemon/xdmcp/Utils.cpp @@ -0,0 +1,143 @@ +/* + * Utilities for X Display Control Protocol + * Copyright (C) 2013 Martin Bříza + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#include "Utils.h" + +#include + +namespace SDDM { +namespace XDMCP { + + Reader::Reader(const QByteArray &data) : m_data(data), + m_stream(&m_data, QIODevice::ReadOnly | QIODevice::Unbuffered) { + m_stream.setByteOrder(QDataStream::BigEndian); + } + + Reader& Reader::operator>>(uint8_t &byte) { + m_stream >> byte; + return *this; + } + + Reader& Reader::operator>>(uint16_t &word) { + m_stream >> word; + return *this; + } + + Reader& Reader::operator>>(uint32_t &doubleword) { + m_stream >> doubleword; + return *this; + } + + Reader& Reader::operator>>(QByteArray &array) { + uint16_t arrayLen; + *this >> arrayLen; + while (arrayLen--) { + uint8_t byte; + *this >> byte; + array.append(byte); + } + return *this; + } + + Reader& Reader::operator>>(QVector< uint16_t > &wordArray) { + uint8_t arrayLen; + *this >> arrayLen; + while (arrayLen--) { + uint16_t word; + *this >> word; + wordArray.append(word); + } + return *this; + } + + Reader& Reader::operator>>(QVector< QByteArray > &arrayOfArrays) { + uint8_t arrayCount; + *this >> arrayCount; + while (arrayCount--) { + QByteArray array; + *this >> array; + arrayOfArrays.append(array); + } + return *this; + } + + bool Reader::isFinished() const { + if ((m_stream.status() == QDataStream::Ok) && m_stream.atEnd()) + return true; + else + return false; + } + + Writer::Writer() : m_data(), + m_stream(&m_data, QIODevice::WriteOnly | QIODevice::Unbuffered) { + m_stream.setByteOrder(QDataStream::BigEndian); + } + + Writer& Writer::operator<<(const uint8_t byte) { + qDebug() << "Appending:" << byte << QChar(byte); + m_stream << byte; + return *this; + } + + Writer& Writer::operator<<(const uint16_t word) { + m_stream << word; + return *this; + } + + Writer& Writer::operator<<(const uint32_t doubleword) { + m_stream << doubleword; + return *this; + } + + Writer& Writer::operator<<(const QByteArray &array) { + *this << (uint16_t) array.count(); + for (uint8_t c : array) + m_stream << c; + return *this; + } + + Writer& Writer::operator<<(const QVector< uint16_t > &wordArray) { + *this << (uint8_t) wordArray.count(); + for (const uint16_t &i : wordArray) + *this << i; + return *this; + } + + Writer& Writer::operator<<(const QVector< QByteArray > &arrayOfArrays) { + *this << (uint16_t) arrayOfArrays.count(); + for (const QByteArray &i : arrayOfArrays) + *this << i; + return *this; + } + + QByteArray Writer::finalize(uint16_t opcode) { + QByteArray result; + QDataStream finalStream(&result, QIODevice::WriteOnly | QIODevice::Unbuffered); + finalStream.setByteOrder(QDataStream::BigEndian); + finalStream << (uint16_t) 1; + finalStream << (uint16_t) opcode; + finalStream << (uint16_t) m_data.size(); + for (uint8_t c : m_data) + finalStream << c; + return result; + } + +} // namespace XDMCP +} // namespace SDDM \ No newline at end of file diff --git a/src/daemon/xdmcp/Utils.h b/src/daemon/xdmcp/Utils.h new file mode 100644 index 0000000..bd96708 --- /dev/null +++ b/src/daemon/xdmcp/Utils.h @@ -0,0 +1,93 @@ +/* + * Utilities for X Display Control Protocol + * Copyright (C) 2013 Martin Bříza + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ + +#ifndef SDDM_XDMCP_UTILS_H +#define SDDM_XDMCP_UTILS_H + +#include +#include + +#include "Packet.h" + +namespace SDDM { +namespace XDMCP { + + /** + * Class for reading information from raw packets and setting the right byte order + * + * Workflow is as follows: + * * Construct Reader from the data received + * * Using the stream operator extract all required variables + * * Check if the stream is at its end by isFinished() + */ + class Reader { + public: + Reader(const QByteArray &data); + ~Reader() {} + Reader& operator>>(uint8_t &byte); + Reader& operator>>(uint16_t &word); + Reader& operator>>(uint32_t &doubleword); + Reader& operator>>(QByteArray &array); + Reader& operator>>(QVector &wordArray); + Reader& operator>>(QVector &arrayOfArrays); + /** + * Returns true if the stream is at its end and no errors occured + * + * \return Finished status + */ + bool isFinished() const; + private: + QByteArray m_data; + QDataStream m_stream; + }; + + /** + * Class for writing information to raw packets and setting the right byte order + * + * Workflow is as follows: + * * Construct empty writer + * * Using the stream operator insert all contained variables + * * Get a complete packet by the finalize(opcode) method + */ + class Writer { + public: + Writer(); + Writer& operator<<(const uint8_t byte); + Writer& operator<<(const uint16_t word); + Writer& operator<<(const uint32_t doubleword); + Writer& operator<<(const QByteArray &array); + Writer& operator<<(const QVector &wordArray); + Writer& operator<<(const QVector &arrayOfArrays); + /** + * Finalizes building of the packet + * + * \param opcode XDMCP protocol code of the packet type + * \return Raw packet data + */ + QByteArray finalize(uint16_t opcode); + private: + QByteArray m_data; + QDataStream m_stream; + }; + +} // namespace XDMCP +} // namespace SDDM + +#endif // SDDM_XDMCP_UTILS_H