From d4a25b83467d51d9c32a1ba3584dc9e4e992d793 Mon Sep 17 00:00:00 2001 From: Syping Date: Sat, 19 Sep 2020 09:31:57 +0200 Subject: [PATCH] add WebSockets support --- CMakeLists.txt | 3 +- SMSubServer.cpp | 234 +++++++++++++++++++++++++++++++----------------- SMSubServer.h | 22 +++-- main.cpp | 66 ++++++++++---- smsub.pro | 2 +- 5 files changed, 223 insertions(+), 104 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 98577be..51afae4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt5 COMPONENTS Network REQUIRED) +find_package(Qt5 COMPONENTS WebSockets QUIET) set(SMSUB_SOURCES main.cpp @@ -30,6 +31,6 @@ add_executable(smsub ${SMSUB_SOURCES} ) -target_link_libraries(smsub PRIVATE Qt5::Network) +target_link_libraries(smsub PRIVATE Qt5::Network Qt5::WebSockets) install(TARGETS smsub DESTINATION bin) diff --git a/SMSubServer.cpp b/SMSubServer.cpp index d5f843e..2e6242e 100644 --- a/SMSubServer.cpp +++ b/SMSubServer.cpp @@ -24,16 +24,59 @@ SMSubServer::SMSubServer(SMSubServerSettings *serverSettings, const QString &socket) : serverSettings(serverSettings) { - setSocketOptions(QLocalServer::UserAccessOption | QLocalServer::GroupAccessOption); - listen(socket); + QLocalServer *localServer = new QLocalServer(this); + localServer->setSocketOptions(QLocalServer::UserAccessOption | QLocalServer::GroupAccessOption); + localServer->listen(socket); + + QObject::connect(localServer, &QLocalServer::newConnection, this, &SMSubServer::newConnection); + + type = ServerType::Local; + server = localServer; } -void SMSubServer::incomingConnection(quintptr socketDescriptor) +SMSubServer::SMSubServer(SMSubServerSettings *serverSettings, const QString &serverName, const quint16 &port) : serverSettings(serverSettings) { - QLocalSocket *socket = new QLocalSocket(); - socket->setSocketDescriptor(socketDescriptor); - QObject::connect(socket, &QLocalSocket::readyRead, this, &SMSubServer::readyRead); - QObject::connect(socket, &QLocalSocket::disconnected, this, &SMSubServer::deleteSocket); + QWebSocketServer *webSocketServer = new QWebSocketServer(serverName, QWebSocketServer::NonSecureMode, this); + webSocketServer->listen(QHostAddress::LocalHost, port); + + QObject::connect(webSocketServer, &QWebSocketServer::newConnection, this, &SMSubServer::newConnection); + + type = ServerType::WebSocket; + server = webSocketServer; +} + +bool SMSubServer::isListening() +{ + if (likely(type == ServerType::Local)) { + return static_cast(server)->isListening(); + } + else if (type == ServerType::WebSocket) { + return static_cast(server)->isListening(); + } + return false; +} + +void SMSubServer::newConnection() +{ + QObject *socket; + if (likely(type == ServerType::Local)) { + QLocalSocket *localSocket = static_cast(server)->nextPendingConnection(); + QObject::connect(localSocket, &QLocalSocket::readyRead, this, &SMSubServer::lsReadyRead); + QObject::connect(localSocket, &QLocalSocket::disconnected, this, &SMSubServer::deleteSocket); + localSocket->write(QString("SMSub Version %1\n").arg(QCoreApplication::applicationVersion()).toUtf8()); + socket = localSocket; + } + else if (type == ServerType::WebSocket) { + QWebSocket *webSocket = static_cast(server)->nextPendingConnection(); + QObject::connect(webSocket, &QWebSocket::binaryMessageReceived, this, &SMSubServer::wsMessageReceived); + QObject::connect(webSocket, &QWebSocket::disconnected, this, &SMSubServer::deleteSocket); + webSocket->sendBinaryMessage(QString("SMSub Version %1\n").arg(QCoreApplication::applicationVersion()).toUtf8()); + socket = webSocket; + } + else { + // Just for being sure + return; + } // Set authentication state if (serverSettings->isLocal) { @@ -43,103 +86,118 @@ void SMSubServer::incomingConnection(quintptr socketDescriptor) else { socket->setProperty("Authenticated", false); } - - // Initial open writing - socket->write(QString("SMSub Version %1\n").arg(QCoreApplication::applicationVersion()).toUtf8()); } -void SMSubServer::readyRead() +void SMSubServer::messageReceived(QObject *socket, const QByteArray &message) { - // Manage client input - QLocalSocket *socket = (QLocalSocket*)sender(); + // Only allow commands being sent if authenticated bool isAuthenticated = socket->property("Authenticated").toBool(); - while (socket->canReadLine()) { - const QByteArray readData = socket->readLine().trimmed(); - - // Only allow commands being sent if authenticated - if (likely(isAuthenticated)) { - if (readData.startsWith("+dbg")) { - socket->setProperty("ReceiveDbgMsg", true); - socket->write("Debug messages enabled!\n"); - } - else if (readData.startsWith("-dbg")) { - socket->setProperty("ReceiveDbgMsg", false); - socket->write("Debug messages disabled!\n"); - } - else if (readData.startsWith("+log")) { - socket->setProperty("ReceiveLog", true); - debugOutput(socket, "Log output enabled!"); - } - else if (readData.startsWith("-log")) { - socket->setProperty("ReceiveLog", false); - debugOutput(socket, "Log output disabled!"); - } - else if (readData.startsWith("+reg")) { - if (likely(serverSettings->canRegister)) { - QByteArray authUuid = QUuid::createUuid().toByteArray(QUuid::Id128); - authUuid = QByteArray::fromHex(authUuid).toBase64(QByteArray::OmitTrailingEquals); - emit tokenRegistered(QString::fromUtf8(authUuid)); - socket->write("Token: " + authUuid + '\n'); - } - else { - socket->write("Permission denied!\n"); - } - } - else if (readData.startsWith("kill")) { - emit killRequested(); - debugOutput(socket, "Killing server!"); - } - else if (readData.startsWith("stop")) { - emit stopRequested(); - debugOutput(socket, "Stopping server!"); - } - else if (readData.startsWith("wl")) { - const QByteArray writeData = readData.mid(3); - emit inputWritten(writeData + '\n'); - debugOutput(socket, "Write line \"" + writeData + "\"!"); - } - else if (readData.startsWith("w")) { - const QByteArray writeData = readData.mid(2); - emit inputWritten(writeData); - debugOutput(socket, "Write \"" + writeData + "\"!"); - } + if (likely(isAuthenticated)) { + if (message.startsWith("+dbg")) { + socket->setProperty("ReceiveDbgMsg", true); + sendMessage(socket, "Debug messages enabled!\n"); } - else { - // Authenticate when token is valid, otherwise disconnect - if (unlikely(tokens.contains(QString::fromUtf8(readData)))) { - // Set client as authenticated and add it to vector - socket->setProperty("Authenticated", true); - socket->write("Login successful!\n"); - isAuthenticated = true; - sockets << socket; + else if (message.startsWith("-dbg")) { + socket->setProperty("ReceiveDbgMsg", false); + sendMessage(socket, "Debug messages disabled!\n"); + } + else if (message.startsWith("+log")) { + socket->setProperty("ReceiveLog", true); + debugOutput(socket, "Log output enabled!"); + } + else if (message.startsWith("-log")) { + socket->setProperty("ReceiveLog", false); + debugOutput(socket, "Log output disabled!"); + } + else if (message.startsWith("+reg")) { + if (likely(serverSettings->canRegister)) { + QByteArray authUuid = QUuid::createUuid().toByteArray(QUuid::Id128); + authUuid = QByteArray::fromHex(authUuid).toBase64(QByteArray::OmitTrailingEquals); + emit tokenRegistered(QString::fromUtf8(authUuid)); + sendMessage(socket, "Token: " + authUuid + '\n'); } else { - // Stop receiving data and disconnect socket - QObject::disconnect(socket, &QLocalSocket::readyRead, this, &SMSubServer::readyRead); - socket->write("Incorrect token!\n"); - socket->disconnectFromServer(); - return; + sendMessage(socket, "Permission denied!\n"); } } + else if (message.startsWith("kill")) { + emit killRequested(); + debugOutput(socket, "Killing server!"); + } + else if (message.startsWith("stop")) { + emit stopRequested(); + debugOutput(socket, "Stopping server!"); + } + else if (message.startsWith("wl")) { + const QByteArray writeData = message.mid(3); + emit inputWritten(writeData + '\n'); + debugOutput(socket, "Write line \"" + writeData + "\"!"); + } + else if (message.startsWith("w")) { + const QByteArray writeData = message.mid(2); + emit inputWritten(writeData); + debugOutput(socket, "Write \"" + writeData + "\"!"); + } + } + else { + // Authenticate when token is valid, otherwise disconnect + if (unlikely(tokens.contains(QString::fromUtf8(message)))) { + // Set client as authenticated and add it to vector + socket->setProperty("Authenticated", true); + sendMessage(socket, "Login successful!\n"); + isAuthenticated = true; + sockets << socket; + } + else { + // Stop receiving data and disconnect socket + if (likely(type == ServerType::Local)) { + QLocalSocket *localSocket = static_cast(socket); + QObject::disconnect(localSocket, &QLocalSocket::readyRead, this, &SMSubServer::lsReadyRead); + localSocket->write("Incorrect token!\n"); + localSocket->disconnectFromServer(); + } + else if (type == ServerType::WebSocket) { + QWebSocket *webSocket = static_cast(socket); + QObject::disconnect(webSocket, &QWebSocket::binaryMessageReceived, this, &SMSubServer::wsMessageReceived); + webSocket->sendBinaryMessage("Incorrect token!\n"); + webSocket->close(QWebSocketProtocol::CloseCodeNormal); + } + return; + } + } +} + +void SMSubServer::wsMessageReceived(const QByteArray &message) +{ + QWebSocket *socket = static_cast(sender()); + messageReceived(socket, message.trimmed()); +} + +void SMSubServer::lsReadyRead() +{ + QLocalSocket *socket = static_cast(sender()); + while (socket->canReadLine()) { + const QByteArray message = socket->readLine().trimmed(); + messageReceived(socket, message); } } void SMSubServer::deleteSocket() { // Delete socket and remove from index - QLocalSocket *socket = (QLocalSocket*)sender(); + QObject *socket = sender(); sockets.removeAll(socket); socket->deleteLater(); } -void SMSubServer::debugOutput(QLocalSocket *socket, const QByteArray &message) +void SMSubServer::debugOutput(QObject *socket, const QByteArray &message) { // Only send debug messages when the client opted-in const QVariant variant = socket->property("ReceiveDbgMsg"); if (unlikely(variant.type() == QVariant::Bool)) { bool receiveDbgMsg = variant.toBool(); if (likely(receiveDbgMsg)) { - socket->write(message + '\n'); + sendMessage(socket, message + '\n'); } } } @@ -147,20 +205,32 @@ void SMSubServer::debugOutput(QLocalSocket *socket, const QByteArray &message) void SMSubServer::writeOutput(const QByteArray &output) { // Read process output when client opted-in for log - QVector::const_iterator it = sockets.constBegin(); - QVector::const_iterator end = sockets.constEnd(); + QVector::const_iterator it = sockets.constBegin(); + QVector::const_iterator end = sockets.constEnd(); while (it != end) { const QVariant variant = (*it)->property("ReceiveLog"); if (unlikely(variant.type() == QVariant::Bool)) { bool receiveLog = variant.toBool(); if (likely(receiveLog)) { - (*it)->write(output); + sendMessage(*it, output); } } it++; } } +void SMSubServer::sendMessage(QObject *socket, const QByteArray &message) +{ + if (likely(type == ServerType::Local)) { + QLocalSocket *localSocket = static_cast(socket); + localSocket->write(message); + } + else if (type == ServerType::WebSocket) { + QWebSocket *webSocket = static_cast(socket); + webSocket->sendBinaryMessage(message); + } +} + void SMSubServer::registerToken(const QString &token) { // Register temporary token for a secure remote connection diff --git a/SMSubServer.h b/SMSubServer.h index e39a178..b6692dc 100644 --- a/SMSubServer.h +++ b/SMSubServer.h @@ -19,8 +19,10 @@ #ifndef SMSUBSERVER_H #define SMSUBSERVER_H +#include #include #include +#include #include struct SMSubServerSettings @@ -29,26 +31,36 @@ struct SMSubServerSettings bool isLocal; }; -class SMSubServer : public QLocalServer +class SMSubServer : public QObject { Q_OBJECT public: SMSubServer(SMSubServerSettings *serverSettings, const QString &socket); + SMSubServer(SMSubServerSettings *serverSettings, const QString &serverName, const quint16 &port); + bool isListening(); + + enum ServerType { Local, WebSocket }; + Q_ENUM(ServerType) public slots: void writeOutput(const QByteArray &output); void registerToken(const QString &token); private slots: - void incomingConnection(quintptr socketDescriptor); + void wsMessageReceived(const QByteArray &message); + void lsReadyRead(); + void newConnection(); void deleteSocket(); - void readyRead(); private: - inline void debugOutput(QLocalSocket *socket, const QByteArray &message); + inline void debugOutput(QObject *socket, const QByteArray &message); + inline void sendMessage(QObject *socket, const QByteArray &message); + void messageReceived(QObject *socket, const QByteArray &message); SMSubServerSettings *serverSettings; - QVector sockets; + QVector sockets; QVector tokens; + ServerType type; + QObject *server; signals: void tokenRegistered(const QString &password); diff --git a/main.cpp b/main.cpp index 4ef7316..28a7e1f 100644 --- a/main.cpp +++ b/main.cpp @@ -67,8 +67,8 @@ void catchUnixSignals(std::initializer_list quitSignals) { struct sigaction sa; sa.sa_handler = handler; - sa.sa_mask = blocking_mask; - sa.sa_flags = 0; + sa.sa_mask = blocking_mask; + sa.sa_flags = 0; for (int sig : quitSignals) sigaction(sig, &sa, nullptr); @@ -79,7 +79,7 @@ int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); a.setApplicationName("Server Manager Subprocess"); - a.setApplicationVersion("0.2.1"); + a.setApplicationVersion("0.3"); #ifdef Q_OS_UNIX catchUnixSignals({SIGINT, SIGHUP, SIGQUIT, SIGTERM}); @@ -105,6 +105,9 @@ int main(int argc, char *argv[]) #endif commandLineParser.addOption(subprocessSocket); + QCommandLineOption subprocessRemotePort("rport", "WebSockets port used for remote communication.", "rport"); + commandLineParser.addOption(subprocessRemotePort); + #ifdef Q_OS_WIN QCommandLineOption subprocessRemoteSocket(QStringList() << "rsock" << "rsocket", "IPC socket used for remote communication.", "rsock"); #else @@ -122,6 +125,15 @@ int main(int argc, char *argv[]) return 1; } + if (unlikely(commandLineParser.isSet(subprocessRemotePort) && commandLineParser.isSet(subprocessRemoteSocket))) { +#ifdef Q_OS_WIN + QTextStream(stderr) << "You can't define a WebSockets port and a IPC socket at same time!" << endl; +#else + QTextStream(stderr) << "You can't define a WebSockets port and a Unix socket at same time!" << endl; +#endif + return 1; + } + bool timeoutSet = false; int termTimeout = 60000; if (unlikely(commandLineParser.isSet(processTimeout))) { @@ -239,13 +251,29 @@ int main(int argc, char *argv[]) rsocket = commandLineParser.value(subprocessRemoteSocket); } + bool rportSet = false; + quint16 rport; + if (unlikely(commandLineParser.isSet(subprocessRemotePort))) { + bool ok; + rport = commandLineParser.value(subprocessRemotePort).toUShort(&ok); + if (!ok) { + QTextStream(stderr) << "WebSockets port is not valid!" << endl; + return 1; + } + else { + rportSet = true; + } + } + + SMSubProcess subProcess(executable, argumentList, workingDirectory, termTimeout); + SMSubServerSettings remoteSettings; remoteSettings.canRegister = false; remoteSettings.isLocal = false; - SMSubServer subRemote(&remoteSettings, rsocket); if (unlikely(!rsocket.isEmpty())) { - if (unlikely(!subRemote.isListening())) { + SMSubServer *subRemote = new SMSubServer(&remoteSettings, rsocket); + if (unlikely(!subRemote->isListening())) { #ifdef Q_OS_WIN QTextStream(stderr) << "Failed to start remote IPC socket!" << endl; #else @@ -254,27 +282,35 @@ int main(int argc, char *argv[]) return 1; } localSettings.canRegister = true; + QObject::connect(&subLocal, &SMSubServer::tokenRegistered, subRemote, &SMSubServer::registerToken); + QObject::connect(&subProcess, &SMSubProcess::outputWritten, subRemote, &SMSubServer::writeOutput); + QObject::connect(subRemote, &SMSubServer::inputWritten, &subProcess, &SMSubProcess::writeInput); + QObject::connect(subRemote, &SMSubServer::killRequested, &subProcess, &SMSubProcess::killProcess); + QObject::connect(subRemote, &SMSubServer::stopRequested, &subProcess, &SMSubProcess::stopProcess); + } + else if (unlikely(rportSet)) { + SMSubServer *subRemote = new SMSubServer(&remoteSettings, QString(), rport); + if (unlikely(!subRemote->isListening())) { + QTextStream(stderr) << "Failed to start remote WebSockets server!" << endl; + return 1; + } + localSettings.canRegister = true; + QObject::connect(&subLocal, &SMSubServer::tokenRegistered, subRemote, &SMSubServer::registerToken); + QObject::connect(&subProcess, &SMSubProcess::outputWritten, subRemote, &SMSubServer::writeOutput); + QObject::connect(subRemote, &SMSubServer::inputWritten, &subProcess, &SMSubProcess::writeInput); + QObject::connect(subRemote, &SMSubServer::killRequested, &subProcess, &SMSubProcess::killProcess); + QObject::connect(subRemote, &SMSubServer::stopRequested, &subProcess, &SMSubProcess::stopProcess); } else { localSettings.canRegister = false; } - SMSubProcess subProcess(executable, argumentList, workingDirectory, termTimeout); - QObject::connect(&subProcess, &SMSubProcess::outputWritten, &subLocal, &SMSubServer::writeOutput); QObject::connect(&subLocal, &SMSubServer::inputWritten, &subProcess, &SMSubProcess::writeInput); QObject::connect(&subLocal, &SMSubServer::killRequested, &subProcess, &SMSubProcess::killProcess); QObject::connect(&subLocal, &SMSubServer::stopRequested, &subProcess, &SMSubProcess::stopProcess); QObject::connect(&a, &QCoreApplication::aboutToQuit, &subProcess, &SMSubProcess::aboutToQuit); - if (unlikely(!rsocket.isEmpty())) { - QObject::connect(&subLocal, &SMSubServer::tokenRegistered, &subRemote, &SMSubServer::registerToken); - QObject::connect(&subProcess, &SMSubProcess::outputWritten, &subRemote, &SMSubServer::writeOutput); - QObject::connect(&subRemote, &SMSubServer::inputWritten, &subProcess, &SMSubProcess::writeInput); - QObject::connect(&subRemote, &SMSubServer::killRequested, &subProcess, &SMSubProcess::killProcess); - QObject::connect(&subRemote, &SMSubServer::stopRequested, &subProcess, &SMSubProcess::stopProcess); - } - subProcess.start(); return a.exec(); diff --git a/smsub.pro b/smsub.pro index cdf6410..7ab8c6b 100644 --- a/smsub.pro +++ b/smsub.pro @@ -17,7 +17,7 @@ ############################################################################### QT -= gui -QT += network +QT += network websockets CONFIG += c++11 console CONFIG -= app_bundle