/***************************************************************************** * smsub Server Manager Subprocess * Copyright (C) 2020-2024 Syping * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * This software is provided as-is, no warranties are given to you, we are not * responsible for anything with use of the software, you are self responsible. *****************************************************************************/ #include #include #include #include "SMSubServer.h" #include "smsub.h" SMSubServer::SMSubServer(SMSubServerSettings *serverSettings, const QString &socket) : serverSettings(serverSettings) { 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; status = false; startTime = QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); stopTime = startTime; } SMSubServer::SMSubServer(SMSubServerSettings *serverSettings, const QString &serverName, const quint16 &port) : serverSettings(serverSettings) { 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; status = false; startTime = QDateTime::currentDateTimeUtc().toSecsSinceEpoch(); stopTime = startTime; } bool SMSubServer::isListening() { if (Q_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 (Q_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; QTextStream(stderr) << "LocalSocket connected!" << smsub_endl; } 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; QTextStream(stderr) << QString("WebSocket %1:%2 connected!").arg(webSocket->peerName(), QString::number(webSocket->peerPort())) << smsub_endl; } else { // Just for being sure return; } // Set authentication state if (serverSettings->isLocal) { socket->setProperty("Authenticated", true); sockets << socket; } else { socket->setProperty("Authenticated", false); } } bool SMSubServer::messageReceived(QObject *socket, const QByteArray &message) { // Only allow commands being sent if authenticated const bool isAuthenticated = socket->property("Authenticated").toBool(); if (Q_LIKELY(isAuthenticated)) { if (message.startsWith("+dbg")) { socket->setProperty("ReceiveDbgMsg", true); sendMessage(socket, "Debug messages enabled!\n"); } 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 (Q_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 { sendMessage(socket, "Permission denied!\n"); } } else if (message.startsWith("status")) { if (status) { sendMessage(socket, QString("Status: on\nLast Start: %1\nLast Stop: %2\n").arg(QString::number(startTime), QString::number(stopTime)).toUtf8()); } else { sendMessage(socket, QString("Status: off\nLast Start: %1\nLast Stop: %2\n").arg(QString::number(startTime), QString::number(stopTime)).toUtf8()); } } else if (message.startsWith("start")) { emit startRequested(); debugOutput(socket, "Starting server!"); } else if (message.startsWith("stop")) { emit stopRequested(); debugOutput(socket, "Stopping server!"); } else if (message.startsWith("kill")) { emit killRequested(); debugOutput(socket, "Killing server!"); } else if (message.startsWith("quit")) { QTimer::singleShot(0, qApp, &QCoreApplication::quit); debugOutput(socket, "Qutting smsub!"); } 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 (Q_UNLIKELY(tokens.contains(QString::fromUtf8(message)))) { // Set client as authenticated and add it to vector socket->setProperty("Authenticated", true); sendMessage(socket, "Login successful!\n"); sockets << socket; } else { // Stop receiving data and disconnect socket if (Q_LIKELY(type == ServerType::Local)) { QLocalSocket *localSocket = static_cast(socket); QObject::disconnect(localSocket, &QLocalSocket::readyRead, this, &SMSubServer::lsReadyRead); localSocket->write("Incorrect token!\n"); localSocket->disconnectFromServer(); return false; } 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 false; } } } return true; } void SMSubServer::wsMessageReceived(const QByteArray &message) { QWebSocket *socket = static_cast(sender()); messageReceived(socket, message.trimmed()); } void SMSubServer::lsReadyRead() { QLocalSocket *socket = static_cast(sender()); #ifdef SMSUB_IODEBUG QTextStream(stderr) << "LocalSocket I/O RR!" << smsub_endl; #endif while (socket->canReadLine()) { #ifdef SMSUB_IODEBUG QTextStream(stderr) << "LocalSocket I/O WL!" << smsub_endl; #endif const QByteArray message = socket->readLine().trimmed(); if (!messageReceived(socket, message)) return; } } void SMSubServer::deleteSocket() { // Delete socket and remove from index QObject *socket = sender(); sockets.removeAll(socket); socket->deleteLater(); } void SMSubServer::debugOutput(QObject *socket, const QByteArray &message) { // Only send debug messages when the client opted-in const QVariant variant = socket->property("ReceiveDbgMsg"); #if QT_VERSION >= 0x060000 if (Q_UNLIKELY(variant.typeId() == QMetaType::Bool)) { #else if (Q_UNLIKELY(variant.type() == QVariant::Bool)) { #endif bool receiveDbgMsg = variant.toBool(); if (Q_LIKELY(receiveDbgMsg)) { sendMessage(socket, message + '\n'); } } } void SMSubServer::writeOutput(const QByteArray &output) { // Read process output when client opted-in for log for (auto it = sockets.constBegin(); it != sockets.constEnd(); it++) { const QVariant variant = (*it)->property("ReceiveLog"); #if QT_VERSION >= 0x060000 if (Q_UNLIKELY(variant.typeId() == QMetaType::Bool)) { #else if (Q_UNLIKELY(variant.type() == QVariant::Bool)) { #endif bool receiveLog = variant.toBool(); if (Q_LIKELY(receiveLog)) { sendMessage(*it, output); } } } } void SMSubServer::sendMessage(QObject *socket, const QByteArray &message) { if (Q_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 tokens << token; QTimer::singleShot(30000, this, [=]() { tokens.removeAll(token); }); } void SMSubServer::statusUpdated(const bool status_, const qint64 time) { status = status_; status ? (startTime = time) : (stopTime = time); }