diff options
Diffstat (limited to 'user/sddm/rootless-xorg.patch')
-rw-r--r-- | user/sddm/rootless-xorg.patch | 2012 |
1 files changed, 2012 insertions, 0 deletions
diff --git a/user/sddm/rootless-xorg.patch b/user/sddm/rootless-xorg.patch new file mode 100644 index 000000000..99a2e189d --- /dev/null +++ b/user/sddm/rootless-xorg.patch @@ -0,0 +1,2012 @@ +From 9881225a92ffe6cf3d4a84273192b08b2d2f6046 Mon Sep 17 00:00:00 2001 +From: Pier Luigi Fiorini <pierluigi.fiorini@liri.io> +Date: Tue, 27 Aug 2019 22:31:00 +0200 +Subject: [PATCH] X11 display server without root privileges + +Introduce an alternative display server option to run the greeter with +the X11 server as an unprivileged user. + +Root privileges are required by default, but this will change in the +future. + +Greeter output is forwarded to the helper process, instead of saving it +into a separate file like user sessions do. +This means the greeter output is available in the journal with the +daemon and helper logs. + +Display start and stop commands are executed as sddm user and this might +break the workflow of our users. +That is the reason why we still run Xorg as root for the greeter by +default. + +X11 user session always spawn a new display server without root +privileges. + +Closes: #246 +--- + CMakeLists.txt | 1 + + data/man/sddm.conf.rst.in | 14 +- + src/auth/Auth.cpp | 24 ++- + src/auth/Auth.h | 14 ++ + src/auth/AuthMessages.h | 1 + + src/common/Configuration.h | 3 + + src/common/Session.cpp | 10 ++ + src/common/Session.h | 4 + + src/common/XAuth.cpp | 127 ++++++++++++++ + src/common/XAuth.h | 55 ++++++ + src/daemon/CMakeLists.txt | 6 +- + src/daemon/Display.cpp | 64 +++++-- + src/daemon/Display.h | 11 ++ + src/daemon/Greeter.cpp | 41 +++-- + src/daemon/Greeter.h | 5 + + src/daemon/XorgDisplayServer.cpp | 78 ++------- + src/daemon/XorgDisplayServer.h | 10 +- + src/daemon/XorgUserDisplayServer.cpp | 102 +++++++++++ + src/daemon/XorgUserDisplayServer.h | 53 ++++++ + src/greeter/GreeterApp.cpp | 9 + + src/helper/Backend.cpp | 5 + + src/helper/Backend.h | 2 + + src/helper/CMakeLists.txt | 4 + + src/helper/HelperApp.cpp | 30 +++- + src/helper/HelperApp.h | 1 + + src/helper/UserSession.cpp | 202 +++++++++++++-------- + src/helper/UserSession.h | 20 ++- + src/helper/backend/PamBackend.cpp | 6 +- + src/helper/xorguserhelper.cpp | 252 +++++++++++++++++++++++++++ + src/helper/xorguserhelper.h | 64 +++++++ + 30 files changed, 1036 insertions(+), 182 deletions(-) + create mode 100644 src/common/XAuth.cpp + create mode 100644 src/common/XAuth.h + create mode 100644 src/daemon/XorgUserDisplayServer.cpp + create mode 100644 src/daemon/XorgUserDisplayServer.h + create mode 100644 src/helper/xorguserhelper.cpp + create mode 100644 src/helper/xorguserhelper.h + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 9614b4e1e..41aee21d4 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -16,6 +16,7 @@ set(SDDM_VERSION_MAJOR 0) + set(SDDM_VERSION_MINOR 19) + set(SDDM_VERSION_PATCH 0) + set(SDDM_VERSION_STRING "${SDDM_VERSION_MAJOR}.${SDDM_VERSION_MINOR}.${SDDM_VERSION_PATCH}") ++add_compile_definitions("SDDM_VERSION=\"${SDDM_VERSION_STRING}\"") + + # Set up packaging + set(CPACK_PACKAGE_NAME "sddm") +diff --git a/data/man/sddm.conf.rst.in b/data/man/sddm.conf.rst.in +index 1061540c0..960e65c68 100644 +--- a/data/man/sddm.conf.rst.in ++++ b/data/man/sddm.conf.rst.in +@@ -6,7 +6,7 @@ + sddm display manager configuration + ---------------------------------- + +-:Date: August 2015 ++:Date: March 2021 + :Version: sddm @SDDM_VERSION_STRING@ + :Manual section: 5 + :Manual group: sddm +@@ -36,6 +36,14 @@ OPTIONS + + [General] section: + ++`DisplayServer=` ++ Select the display server to use for the greeter. ++ Valid values are: ++ * `x11`: X server running as root. ++ * `x11-user`: X server running as unprivileged user. ++ Default value is "x11". ++ For `x11-user` you might need to configure Xorg.wrap(1). ++ + `HaltCommand=` + Halt command. + Default value is "@HALT_COMMAND@". +@@ -134,10 +142,14 @@ OPTIONS + + `DisplayCommand=` + Path of script to execute when starting the display server. ++ The script will be executed as root when General.DisplayServer ++ is "x11", otherwise as sddm user. + Default value is "@DATA_INSTALL_DIR@/scripts/Xsetup". + + `DisplayStopCommand=` + Path of script to execute when stopping the display server. ++ The script will be executed as root when General.DisplayServer ++ is "x11", otherwise as sddm user. + Default value is "@DATA_INSTALL_DIR@/scripts/Xstop". + + `MinimumVT=` +diff --git a/src/auth/Auth.cpp b/src/auth/Auth.cpp +index 403186ee1..4450cc606 100644 +--- a/src/auth/Auth.cpp ++++ b/src/auth/Auth.cpp +@@ -62,6 +62,7 @@ namespace SDDM { + AuthRequest *request { nullptr }; + QProcess *child { nullptr }; + QLocalSocket *socket { nullptr }; ++ QString displayServerCmd; + QString sessionPath { }; + QString user { }; + QString cookie { }; +@@ -202,6 +203,15 @@ namespace SDDM { + str.send(); + break; + } ++ case DISPLAY_SERVER_STARTED: { ++ QString displayName; ++ str >> displayName; ++ Q_EMIT auth->displayServerReady(displayName); ++ str.reset(); ++ str << DISPLAY_SERVER_STARTED; ++ str.send(); ++ break; ++ } + default: { + Q_EMIT auth->error(QStringLiteral("Auth: Unexpected value received: %1").arg(m), ERROR_INTERNAL); + } +@@ -210,7 +220,9 @@ namespace SDDM { + + void Auth::Private::childExited(int exitCode, QProcess::ExitStatus exitStatus) { + if (exitStatus != QProcess::NormalExit) { +- qWarning("Auth: sddm-helper crashed (exit code %d)", exitCode); ++ qWarning("Auth: sddm-helper (%s) crashed (exit code %d)", ++ qPrintable(child->arguments().join(QLatin1Char(' '))), ++ HelperExitStatus(exitStatus)); + Q_EMIT qobject_cast<Auth*>(parent())->error(child->errorString(), ERROR_INTERNAL); + } + +@@ -334,6 +346,14 @@ namespace SDDM { + } + } + ++ void Auth::setDisplayServerCommand(const QString &command) ++ { ++ if (d->displayServerCmd != command) { ++ d->displayServerCmd = command; ++ Q_EMIT displayServerCommandChanged(); ++ } ++ } ++ + void Auth::setSession(const QString& path) { + if (path != d->sessionPath) { + d->sessionPath = path; +@@ -361,6 +381,8 @@ namespace SDDM { + args << QStringLiteral("--user") << d->user; + if (d->autologin) + args << QStringLiteral("--autologin"); ++ if (!d->displayServerCmd.isEmpty()) ++ args << QStringLiteral("--display-server") << d->displayServerCmd; + if (d->greeter) + args << QStringLiteral("--greeter"); + d->child->start(QStringLiteral("%1/sddm-helper").arg(QStringLiteral(LIBEXEC_INSTALL_DIR)), args); +diff --git a/src/auth/Auth.h b/src/auth/Auth.h +index f7cb8acd3..dc3df24d1 100644 +--- a/src/auth/Auth.h ++++ b/src/auth/Auth.h +@@ -141,6 +141,12 @@ namespace SDDM { + */ + void setUser(const QString &user); + ++ /** ++ * Set the display server command to be started before the greeter. ++ * @param command Command of the display server to be started ++ */ ++ void setDisplayServerCommand(const QString &command); ++ + /** + * Set the session to be started after authenticating. + * @param path Path of the session executable to be started +@@ -165,6 +171,7 @@ namespace SDDM { + void verboseChanged(); + void cookieChanged(); + void userChanged(); ++ void displayServerCommandChanged(); + void sessionChanged(); + void requestChanged(); + +@@ -186,6 +193,13 @@ namespace SDDM { + */ + void sessionStarted(bool success, qint64 pid); + ++ /** ++ * Emitted when the display server is ready. ++ * ++ * @param displayName display name ++ */ ++ void displayServerReady(const QString &displayName); ++ + /** + * Emitted when the helper quits, either after authentication or when the session ends. + * Or, when something goes wrong. +diff --git a/src/auth/AuthMessages.h b/src/auth/AuthMessages.h +index 3bc97b6ba..6aea7483f 100644 +--- a/src/auth/AuthMessages.h ++++ b/src/auth/AuthMessages.h +@@ -97,6 +97,7 @@ namespace SDDM { + REQUEST, + AUTHENTICATED, + SESSION_STATUS, ++ DISPLAY_SERVER_STARTED, + MSG_LAST, + }; + +diff --git a/src/common/Configuration.h b/src/common/Configuration.h +index b79871988..47bfa2710 100644 +--- a/src/common/Configuration.h ++++ b/src/common/Configuration.h +@@ -37,6 +37,9 @@ namespace SDDM { + enum NumState { NUM_NONE, NUM_SET_ON, NUM_SET_OFF }; + + // Name Type Default value Description ++ // TODO: Change default to x11-user in a future release ++ Entry(DisplayServer, QString, _S("x11"), _S("Which display server should be used.\n" ++ "Valid values are: x11, x11-user.")); + Entry(HaltCommand, QString, _S(HALT_COMMAND), _S("Halt command")); + Entry(RebootCommand, QString, _S(REBOOT_COMMAND), _S("Reboot command")); + Entry(Numlock, NumState, NUM_NONE, _S("Initial NumLock state. Can be on, off or none.\n" +diff --git a/src/common/Session.cpp b/src/common/Session.cpp +index a026c1f87..1b932c57a 100644 +--- a/src/common/Session.cpp ++++ b/src/common/Session.cpp +@@ -52,6 +52,16 @@ namespace SDDM { + return m_type; + } + ++ int Session::vt() const ++ { ++ return m_vt; ++ } ++ ++ void Session::setVt(int vt) ++ { ++ m_vt = vt; ++ } ++ + QString Session::xdgSessionType() const + { + return m_xdgSessionType; +diff --git a/src/common/Session.h b/src/common/Session.h +index aa196e9c6..3abc993fb 100644 +--- a/src/common/Session.h ++++ b/src/common/Session.h +@@ -43,6 +43,9 @@ namespace SDDM { + + Type type() const; + ++ int vt() const; ++ void setVt(int vt); ++ + QString xdgSessionType() const; + + QDir directory() const; +@@ -70,6 +73,7 @@ namespace SDDM { + QProcessEnvironment parseEnv(const QString &list); + bool m_valid; + Type m_type; ++ int m_vt = 0; + QDir m_dir; + QString m_name; + QString m_fileName; +diff --git a/src/common/XAuth.cpp b/src/common/XAuth.cpp +new file mode 100644 +index 000000000..bc7b10caf +--- /dev/null ++++ b/src/common/XAuth.cpp +@@ -0,0 +1,127 @@ ++/*************************************************************************** ++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> ++* Copyright (c) 2013 Abdurrahman AVCI <abdurrahmanavci@gmail.com> ++* ++* 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 <QDebug> ++#include <QDir> ++#include <QUuid> ++ ++#include "Configuration.h" ++#include "Constants.h" ++#include "XAuth.h" ++ ++#include <random> ++ ++namespace SDDM { ++ ++XAuth::XAuth() ++{ ++ m_authDir = QStringLiteral(RUNTIME_DIR); ++} ++ ++QString XAuth::authDirectory() const ++{ ++ return m_authDir; ++} ++ ++void XAuth::setAuthDirectory(const QString &path) ++{ ++ if (m_setup) { ++ qWarning("Unable to set xauth directory after setup"); ++ return; ++ } ++ ++ m_authDir = path; ++} ++ ++QString XAuth::authPath() const ++{ ++ return m_authPath; ++} ++ ++QString XAuth::cookie() const ++{ ++ return m_cookie; ++} ++ ++void XAuth::setup() ++{ ++ if (m_setup) ++ return; ++ ++ m_setup = true; ++ ++ // Create directory if not existing ++ QDir().mkpath(m_authDir); ++ ++ // Set path ++ m_authPath = QStringLiteral("%1/%2").arg(m_authDir).arg(QUuid::createUuid().toString(QUuid::WithoutBraces)); ++ qDebug() << "Xauthority path:" << m_authPath; ++ ++ // Generate cookie ++ std::random_device rd; ++ std::mt19937 gen(rd()); ++ std::uniform_int_distribution<> dis(0, 15); ++ ++ // Reseve 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] = QLatin1Char(digits[dis(gen)]); ++} ++ ++bool XAuth::addCookie(const QString &display) ++{ ++ if (!m_setup) { ++ qWarning("Please setup xauth before adding a cookie"); ++ return false; ++ } ++ ++ return XAuth::addCookieToFile(display, m_authPath, m_cookie); ++} ++ ++bool XAuth::addCookieToFile(const QString &display, const QString &fileName, ++ const QString &cookie) ++{ ++ qDebug() << "Adding cookie to" << fileName; ++ ++ // Touch file ++ QFile file_handler(fileName); ++ file_handler.open(QIODevice::Append); ++ file_handler.close(); ++ ++ QString cmd = QStringLiteral("%1 -f %2 -q").arg(mainConfig.X11.XauthPath.get()).arg(fileName); ++ ++ // Execute xauth ++ FILE *fp = ::popen(qPrintable(cmd), "w"); ++ ++ // Check file ++ if (!fp) ++ return false; ++ fprintf(fp, "remove %s\n", qPrintable(display)); ++ fprintf(fp, "add %s . %s\n", qPrintable(display), qPrintable(cookie)); ++ fprintf(fp, "exit\n"); ++ ++ // Close pipe ++ return pclose(fp) == 0; ++} ++ ++} // namespace SDDM +diff --git a/src/common/XAuth.h b/src/common/XAuth.h +new file mode 100644 +index 000000000..3e80f4ead +--- /dev/null ++++ b/src/common/XAuth.h +@@ -0,0 +1,55 @@ ++/*************************************************************************** ++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> ++* Copyright (c) 2013 Abdurrahman AVCI <abdurrahmanavci@gmail.com> ++* ++* 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_XAUTH_H ++#define SDDM_XAUTH_H ++ ++#include <QString> ++ ++namespace SDDM { ++ ++class XAuth ++{ ++public: ++ XAuth(); ++ ++ QString authDirectory() const; ++ void setAuthDirectory(const QString &path); ++ ++ QString authPath() const; ++ QString cookie() const; ++ ++ void setup(); ++ bool addCookie(const QString &display); ++ ++ static bool addCookieToFile(const QString &display, ++ const QString &fileName, ++ const QString &cookie); ++ ++private: ++ bool m_setup = false; ++ QString m_authDir; ++ QString m_authPath; ++ QString m_cookie; ++}; ++ ++} // namespace SDDM ++ ++#endif // SDDM_XAUTH_H +diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt +index 86d014bec..b411e42bd 100644 +--- a/src/daemon/CMakeLists.txt ++++ b/src/daemon/CMakeLists.txt +@@ -13,6 +13,8 @@ set(DAEMON_SOURCES + ${CMAKE_SOURCE_DIR}/src/common/ThemeMetadata.cpp + ${CMAKE_SOURCE_DIR}/src/common/Session.cpp + ${CMAKE_SOURCE_DIR}/src/common/SocketWriter.cpp ++ ${CMAKE_SOURCE_DIR}/src/common/XAuth.cpp ++ ${CMAKE_SOURCE_DIR}/src/common/XAuth.h + ${CMAKE_SOURCE_DIR}/src/auth/Auth.cpp + ${CMAKE_SOURCE_DIR}/src/auth/AuthPrompt.cpp + ${CMAKE_SOURCE_DIR}/src/auth/AuthRequest.cpp +@@ -22,13 +24,15 @@ set(DAEMON_SOURCES + DisplayManager.cpp + DisplayServer.cpp + LogindDBusTypes.cpp +- XorgDisplayServer.cpp + Greeter.cpp + PowerManager.cpp + Seat.cpp + SeatManager.cpp + SignalHandler.cpp + SocketServer.cpp ++ XorgDisplayServer.cpp ++ XorgUserDisplayServer.cpp ++ XorgUserDisplayServer.h + ) + + # Different implementations of the VT switching code +diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp +index 3c77454fc..26ca3bb60 100644 +--- a/src/daemon/Display.cpp ++++ b/src/daemon/Display.cpp +@@ -25,6 +25,7 @@ + #include "DaemonApp.h" + #include "DisplayManager.h" + #include "XorgDisplayServer.h" ++#include "XorgUserDisplayServer.h" + #include "Seat.h" + #include "SocketServer.h" + #include "Greeter.h" +@@ -56,7 +57,6 @@ + namespace SDDM { + Display::Display(Seat *parent) : QObject(parent), + m_auth(new Auth(this)), +- m_displayServer(new XorgDisplayServer(this)), + m_seat(parent), + m_socketServer(new SocketServer(this)), + m_greeter(new Greeter(this)) { +@@ -64,6 +64,32 @@ namespace SDDM { + // Allocate vt + m_terminalId = VirtualTerminal::setUpNewVt(); + ++ // Save display server type ++ const QString &displayServerType = mainConfig.DisplayServer.get().toLower(); ++ if (displayServerType == QLatin1String("x11")) ++ m_displayServerType = X11DisplayServerType; ++ else if (displayServerType == QStringLiteral("x11-user")) ++ m_displayServerType = X11UserDisplayServerType; ++ else { ++ qWarning("\"%s\" is an invalid value for General.DisplayServer: fall back to \"x11\"", ++ qPrintable(displayServerType)); ++ m_displayServerType = X11DisplayServerType; ++ } ++ ++ // Create display server ++ switch (m_displayServerType) { ++ case X11DisplayServerType: ++ m_displayServer = new XorgDisplayServer(this); ++ break; ++ case X11UserDisplayServerType: ++ m_displayServer = new XorgUserDisplayServer(this); ++ m_greeter->setDisplayServerCommand(XorgUserDisplayServer::command(this)); ++ break; ++ } ++ ++ // Print what VT we are using for more information ++ qDebug("Using VT %d", m_terminalId); ++ + // respond to authentication requests + m_auth->setVerbose(true); + connect(m_auth, &Auth::requestChanged, this, &Display::slotRequestChanged); +@@ -89,6 +115,16 @@ namespace SDDM { + stop(); + } + ++ Display::DisplayServerType Display::displayServerType() const ++ { ++ return m_displayServerType; ++ } ++ ++ DisplayServer *Display::displayServer() const ++ { ++ return m_displayServer; ++ } ++ + QString Display::displayId() const { + return m_displayServer->display(); + } +@@ -181,7 +217,8 @@ namespace SDDM { + + // set greeter params + m_greeter->setDisplay(this); +- m_greeter->setAuthPath(qobject_cast<XorgDisplayServer *>(m_displayServer)->authPath()); ++ if (qobject_cast<XorgDisplayServer *>(m_displayServer)) ++ m_greeter->setAuthPath(qobject_cast<XorgDisplayServer *>(m_displayServer)->authPath()); + m_greeter->setSocket(m_socketServer->socketAddress()); + m_greeter->setTheme(findGreeterTheme()); + +@@ -317,20 +354,16 @@ namespace SDDM { + // last session later, in slotAuthenticationFinished() + m_sessionName = session.fileName(); + ++ // New VT ++ m_lastSession.setVt(VirtualTerminal::setUpNewVt()); ++ + // some information + qDebug() << "Session" << m_sessionName << "selected, command:" << session.exec(); + + QProcessEnvironment env; + env.insert(session.additionalEnv()); + +- if (seat()->name() == QLatin1String("seat0")) { +- // Use the greeter VT, for wayland sessions the helper overwrites this +- env.insert(QStringLiteral("XDG_VTNR"), QString::number(terminalId())); +- } +- + env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); +- if (session.xdgSessionType() == QLatin1String("x11")) +- env.insert(QStringLiteral("DISPLAY"), name()); + env.insert(QStringLiteral("XDG_SEAT_PATH"), daemonApp->displayManager()->seatPath(seat()->name())); + env.insert(QStringLiteral("XDG_SESSION_PATH"), daemonApp->displayManager()->sessionPath(QStringLiteral("Session%1").arg(daemonApp->newSessionId()))); + env.insert(QStringLiteral("DESKTOP_SESSION"), session.desktopSession()); +@@ -338,10 +371,16 @@ namespace SDDM { + env.insert(QStringLiteral("XDG_SESSION_CLASS"), QStringLiteral("user")); + env.insert(QStringLiteral("XDG_SESSION_TYPE"), session.xdgSessionType()); + env.insert(QStringLiteral("XDG_SEAT"), seat()->name()); ++ env.insert(QStringLiteral("XDG_VTNR"), QString::number(m_lastSession.vt())); + env.insert(QStringLiteral("XDG_SESSION_DESKTOP"), session.desktopNames()); + + m_auth->insertEnvironment(env); + ++ if (session.xdgSessionType() == QLatin1String("x11")) ++ m_auth->setDisplayServerCommand(XorgUserDisplayServer::command(this)); ++ else ++ m_auth->setDisplayServerCommand(QStringLiteral()); ++ + m_auth->setUser(user); + if (m_reuseSessionId.isNull()) { + m_auth->setSession(session.exec()); +@@ -358,7 +397,8 @@ namespace SDDM { + manager.UnlockSession(m_reuseSessionId); + manager.ActivateSession(m_reuseSessionId); + } else { +- m_auth->setCookie(qobject_cast<XorgDisplayServer *>(m_displayServer)->cookie()); ++ if (qobject_cast<XorgDisplayServer *>(m_displayServer)) ++ m_auth->setCookie(qobject_cast<XorgDisplayServer *>(m_displayServer)->cookie()); + } + + // save last user and last session +@@ -411,6 +451,10 @@ namespace SDDM { + // greeter + if (status != Auth::HELPER_AUTH_ERROR) + stop(); ++ ++ // Start the greeter again as soon as the user session is closed ++ if (m_auth->user() != QLatin1String("sddm")) ++ m_greeter->start(); + } + + void Display::slotRequestChanged() { +diff --git a/src/daemon/Display.h b/src/daemon/Display.h +index 61dd9f630..a31542cda 100644 +--- a/src/daemon/Display.h ++++ b/src/daemon/Display.h +@@ -41,9 +41,18 @@ namespace SDDM { + Q_OBJECT + Q_DISABLE_COPY(Display) + public: ++ enum DisplayServerType { ++ X11DisplayServerType, ++ X11UserDisplayServerType ++ }; ++ Q_ENUM(DisplayServerType) ++ + explicit Display(Seat *parent); + ~Display(); + ++ DisplayServerType displayServerType() const; ++ DisplayServer *displayServer() const; ++ + QString displayId() const; + const int terminalId() const; + +@@ -76,6 +85,8 @@ namespace SDDM { + void startAuth(const QString &user, const QString &password, + const Session &session); + ++ DisplayServerType m_displayServerType = X11DisplayServerType; ++ + bool m_relogin { true }; + bool m_started { false }; + +diff --git a/src/daemon/Greeter.cpp b/src/daemon/Greeter.cpp +index 436ecc3d5..158d8bbbe 100644 +--- a/src/daemon/Greeter.cpp ++++ b/src/daemon/Greeter.cpp +@@ -27,6 +27,7 @@ + #include "ThemeConfig.h" + #include "ThemeMetadata.h" + #include "Display.h" ++#include "XorgUserDisplayServer.h" + + #include <QtCore/QDebug> + #include <QtCore/QProcess> +@@ -71,6 +72,16 @@ namespace SDDM { + } + } + ++ QString Greeter::displayServerCommand() const ++ { ++ return m_displayServerCmd; ++ } ++ ++ void Greeter::setDisplayServerCommand(const QString &cmd) ++ { ++ m_displayServerCmd = cmd; ++ } ++ + bool Greeter::start() { + // check flag + if (m_started) +@@ -111,17 +122,16 @@ namespace SDDM { + // log message + qDebug() << "Greeter starting..."; + +- // set process environment ++ args << QStringLiteral("--test-mode"); ++ ++ // set process environment + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert(QStringLiteral("DISPLAY"), m_display->name()); + env.insert(QStringLiteral("XAUTHORITY"), m_authPath); + env.insert(QStringLiteral("XCURSOR_THEME"), xcursorTheme); +- env.insert(QStringLiteral("QT_IM_MODULE"), mainConfig.InputMethod.get()); + m_process->setProcessEnvironment(env); + + // start greeter +- if (daemonApp->testing()) +- args << QStringLiteral("--test-mode"); + m_process->start(QStringLiteral("%1/sddm-greeter").arg(QStringLiteral(BIN_INSTALL_DIR)), args); + + //if we fail to start bail immediately, and don't block in waitForStarted +@@ -149,11 +159,12 @@ namespace SDDM { + m_auth->setVerbose(true); + connect(m_auth, &Auth::requestChanged, this, &Greeter::onRequestChanged); + connect(m_auth, &Auth::sessionStarted, this, &Greeter::onSessionStarted); ++ connect(m_auth, &Auth::displayServerReady, this, &Greeter::onDisplayServerReady); + connect(m_auth, &Auth::finished, this, &Greeter::onHelperFinished); + connect(m_auth, &Auth::info, this, &Greeter::authInfo); + connect(m_auth, &Auth::error, this, &Greeter::authError); + +- // greeter command ++ // command + QStringList cmd; + cmd << QStringLiteral("%1/sddm-greeter").arg(QStringLiteral(BIN_INSTALL_DIR)) + << args; +@@ -173,8 +184,6 @@ namespace SDDM { + }, sysenv, env); + + env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); +- env.insert(QStringLiteral("DISPLAY"), m_display->name()); +- env.insert(QStringLiteral("XAUTHORITY"), m_authPath); + env.insert(QStringLiteral("XCURSOR_THEME"), xcursorTheme); + env.insert(QStringLiteral("XDG_SEAT"), m_display->seat()->name()); + env.insert(QStringLiteral("XDG_SEAT_PATH"), daemonApp->displayManager()->seatPath(m_display->seat()->name())); +@@ -183,11 +192,10 @@ namespace SDDM { + env.insert(QStringLiteral("XDG_VTNR"), QString::number(m_display->terminalId())); + env.insert(QStringLiteral("XDG_SESSION_CLASS"), QStringLiteral("greeter")); + env.insert(QStringLiteral("XDG_SESSION_TYPE"), m_display->sessionType()); +- env.insert(QStringLiteral("QT_IM_MODULE"), mainConfig.InputMethod.get()); +- +- //some themes may use KDE components and that will automatically load KDE's crash handler which we don't want +- //counterintuitively setting this env disables that handler +- env.insert(QStringLiteral("KDE_DEBUG"), QStringLiteral("1")); ++ if (m_display->displayServerType() == Display::X11DisplayServerType) { ++ env.insert(QStringLiteral("DISPLAY"), m_display->name()); ++ env.insert(QStringLiteral("XAUTHORITY"), m_authPath); ++ } + m_auth->insertEnvironment(env); + + // log message +@@ -195,6 +203,7 @@ namespace SDDM { + + // start greeter + m_auth->setUser(QStringLiteral("sddm")); ++ m_auth->setDisplayServerCommand(m_displayServerCmd); + m_auth->setGreeter(true); + m_auth->setSession(cmd.join(QLatin1Char(' '))); + m_auth->start(); +@@ -261,6 +270,14 @@ namespace SDDM { + qDebug() << "Greeter session failed to start"; + } + ++ void Greeter::onDisplayServerReady(const QString &displayName) ++ { ++ auto *displayServer = m_display->displayServer(); ++ auto *xorgUser = qobject_cast<XorgUserDisplayServer *>(displayServer); ++ if (xorgUser) ++ xorgUser->setDisplayName(displayName); ++ } ++ + void Greeter::onHelperFinished(Auth::HelperExitStatus status) { + // reset flag + m_started = false; +diff --git a/src/daemon/Greeter.h b/src/daemon/Greeter.h +index 7391a3597..bf472375f 100644 +--- a/src/daemon/Greeter.h ++++ b/src/daemon/Greeter.h +@@ -43,6 +43,9 @@ namespace SDDM { + void setSocket(const QString &socket); + void setTheme(const QString &theme); + ++ QString displayServerCommand() const; ++ void setDisplayServerCommand(const QString &cmd); ++ + public slots: + bool start(); + void stop(); +@@ -51,6 +54,7 @@ namespace SDDM { + private slots: + void onRequestChanged(); + void onSessionStarted(bool success); ++ void onDisplayServerReady(const QString &displayName); + void onHelperFinished(Auth::HelperExitStatus status); + void onReadyReadStandardOutput(); + void onReadyReadStandardError(); +@@ -64,6 +68,7 @@ namespace SDDM { + QString m_authPath; + QString m_socket; + QString m_themePath; ++ QString m_displayServerCmd; + ThemeMetadata *m_metadata { nullptr }; + ThemeConfig *m_themeConfig { nullptr }; + +diff --git a/src/daemon/XorgDisplayServer.cpp b/src/daemon/XorgDisplayServer.cpp +index 331adcda7..fc61ee2dd 100644 +--- a/src/daemon/XorgDisplayServer.cpp ++++ b/src/daemon/XorgDisplayServer.cpp +@@ -41,31 +41,9 @@ + + namespace SDDM { + XorgDisplayServer::XorgDisplayServer(Display *parent) : DisplayServer(parent) { +- // get auth directory +- QString authDir = QStringLiteral(RUNTIME_DIR); +- +- // use "." as authdir in test mode + if (daemonApp->testing()) +- authDir = QStringLiteral("."); +- +- // create auth dir if not existing +- QDir().mkpath(authDir); +- +- // set auth path +- m_authPath = QStringLiteral("%1/%2").arg(authDir).arg(QUuid::createUuid().toString()); +- +- // 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)]; ++ m_xauth.setAuthDirectory(QStringLiteral(".")); ++ m_xauth.setup(); + } + + XorgDisplayServer::~XorgDisplayServer() { +@@ -76,41 +54,16 @@ namespace SDDM { + return m_display; + } + +- const QString &XorgDisplayServer::authPath() const { +- return m_authPath; ++ QString XorgDisplayServer::authPath() const { ++ return m_xauth.authPath(); + } + + QString XorgDisplayServer::sessionType() const { + return QStringLiteral("x11"); + } + +- const QString &XorgDisplayServer::cookie() const { +- return m_cookie; +- } +- +- bool XorgDisplayServer::addCookie(const QString &file) { +- // log message +- qDebug() << "Adding cookie to" << file; +- +- // Touch file +- QFile file_handler(file); +- file_handler.open(QIODevice::Append); +- file_handler.close(); +- +- QString cmd = QStringLiteral("%1 -f %2 -q").arg(mainConfig.X11.XauthPath.get()).arg(file); +- +- // execute xauth +- FILE *fp = popen(qPrintable(cmd), "w"); +- +- // check file +- if (!fp) +- return false; +- fprintf(fp, "remove %s\n", qPrintable(m_display)); +- fprintf(fp, "add %s . %s\n", qPrintable(m_display), qPrintable(m_cookie)); +- fprintf(fp, "exit\n"); +- +- // close pipe +- return pclose(fp) == 0; ++ QString XorgDisplayServer::cookie() const { ++ return m_xauth.cookie(); + } + + bool XorgDisplayServer::start() { +@@ -136,7 +89,7 @@ namespace SDDM { + // For the X server's copy, the display number doesn't matter. + // An empty file would result in no access control! + m_display = QStringLiteral(":0"); +- if(!addCookie(m_authPath)) { ++ if(!m_xauth.addCookie(m_display)) { + qCritical() << "Failed to write xauth file"; + return false; + } +@@ -159,18 +112,15 @@ namespace SDDM { + process->setProgram(mainConfig.X11.ServerPath.get()); + args << mainConfig.X11.ServerArguments.get().split(QLatin1Char(' '), QString::SkipEmptyParts) + << QStringLiteral("-background") << QStringLiteral("none") +- << QStringLiteral("-seat") << displayPtr()->seat()->name(); +- +- if (displayPtr()->seat()->name() == QLatin1String("seat0")) { +- args << QStringLiteral("vt%1").arg(displayPtr()->terminalId()); +- } ++ << QStringLiteral("-seat") << displayPtr()->seat()->name() ++ << QStringLiteral("vt%1").arg(displayPtr()->terminalId()); + } else { + process->setProgram(mainConfig.X11.XephyrPath.get()); + args << QStringLiteral("-br") + << QStringLiteral("-screen") << QStringLiteral("800x600"); + } + +- args << QStringLiteral("-auth") << m_authPath ++ args << QStringLiteral("-auth") << m_xauth.authPath() + << QStringLiteral("-noreset") + << QStringLiteral("-displayfd") << QString::number(pipeFds[1]); + +@@ -222,13 +172,13 @@ namespace SDDM { + // The file is also used by the greeter, which does care about the + // display number. Write the proper entry, if it's different. + if(m_display != QStringLiteral(":0")) { +- if(!addCookie(m_authPath)) { ++ if(!m_xauth.addCookie(m_display)) { + qCritical() << "Failed to write xauth file"; + stop(); + return false; + } + } +- changeOwner(m_authPath); ++ changeOwner(m_xauth.authPath()); + + emit started(); + +@@ -297,7 +247,7 @@ namespace SDDM { + displayStopScript = nullptr; + + // remove authority file +- QFile::remove(m_authPath); ++ QFile::remove(m_xauth.authPath()); + + // emit signal + emit stopped(); +@@ -316,7 +266,7 @@ namespace SDDM { + env.insert(QStringLiteral("DISPLAY"), m_display); + env.insert(QStringLiteral("HOME"), QStringLiteral("/")); + env.insert(QStringLiteral("PATH"), mainConfig.Users.DefaultPath.get()); +- env.insert(QStringLiteral("XAUTHORITY"), m_authPath); ++ env.insert(QStringLiteral("XAUTHORITY"), m_xauth.authPath()); + env.insert(QStringLiteral("SHELL"), QStringLiteral("/bin/sh")); + env.insert(QStringLiteral("XCURSOR_THEME"), mainConfig.Theme.CursorTheme.get()); + setCursor->setProcessEnvironment(env); +diff --git a/src/daemon/XorgDisplayServer.h b/src/daemon/XorgDisplayServer.h +index e97a0b531..ebf189920 100644 +--- a/src/daemon/XorgDisplayServer.h ++++ b/src/daemon/XorgDisplayServer.h +@@ -22,6 +22,7 @@ + #define SDDM_XORGDISPLAYSERVER_H + + #include "DisplayServer.h" ++#include "XAuth.h" + + class QProcess; + +@@ -34,13 +35,11 @@ namespace SDDM { + ~XorgDisplayServer(); + + const QString &display() const; +- const QString &authPath() const; ++ QString authPath() const; + + QString sessionType() const; + +- const QString &cookie() const; +- +- bool addCookie(const QString &file); ++ QString cookie() const; + + public slots: + bool start(); +@@ -49,8 +48,7 @@ namespace SDDM { + void setupDisplay(); + + private: +- QString m_authPath; +- QString m_cookie; ++ XAuth m_xauth; + + QProcess *process { nullptr }; + +diff --git a/src/daemon/XorgUserDisplayServer.cpp b/src/daemon/XorgUserDisplayServer.cpp +new file mode 100644 +index 000000000..1e7c008a4 +--- /dev/null ++++ b/src/daemon/XorgUserDisplayServer.cpp +@@ -0,0 +1,102 @@ ++/*************************************************************************** ++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> ++* ++* 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 "Configuration.h" ++#include "DaemonApp.h" ++#include "Display.h" ++#include "Seat.h" ++#include "XorgUserDisplayServer.h" ++ ++namespace SDDM { ++ ++XorgUserDisplayServer::XorgUserDisplayServer(Display *parent) ++ : DisplayServer(parent) ++{ ++} ++ ++XorgUserDisplayServer::~XorgUserDisplayServer() ++{ ++ stop(); ++} ++ ++QString XorgUserDisplayServer::sessionType() const ++{ ++ return QStringLiteral("x11"); ++} ++ ++void XorgUserDisplayServer::setDisplayName(const QString &displayName) ++{ ++ m_display = displayName; ++} ++ ++QString XorgUserDisplayServer::command(Display *display) ++{ ++ QStringList args; ++ ++ if (daemonApp->testing()) { ++ args << mainConfig.X11.XephyrPath.get() ++ << QStringLiteral("-br") ++ << QStringLiteral("-screen") << QStringLiteral("800x600"); ++ } else { ++ args << mainConfig.X11.ServerPath.get() ++ << mainConfig.X11.ServerArguments.get().split(QLatin1Char(' '), Qt::SkipEmptyParts) ++ << QStringLiteral("-background") << QStringLiteral("none") ++ << QStringLiteral("-seat") << display->seat()->name() ++ << QStringLiteral("-noreset") ++ << QStringLiteral("-keeptty") ++ << QStringLiteral("-novtswitch") ++ << QStringLiteral("-verbose") << QStringLiteral("3"); ++ } ++ ++ return args.join(QLatin1Char(' ')); ++} ++ ++bool XorgUserDisplayServer::start() ++{ ++ // Check flag ++ if (m_started) ++ return false; ++ ++ // Set flag ++ m_started = true; ++ emit started(); ++ ++ return true; ++} ++ ++void XorgUserDisplayServer::stop() ++{ ++ // Check flag ++ if (!m_started) ++ return; ++ ++ // Reset flag ++ m_started = false; ++ emit stopped(); ++} ++ ++void XorgUserDisplayServer::finished() ++{ ++} ++ ++void XorgUserDisplayServer::setupDisplay() ++{ ++} ++ ++} // namespace SDDM +diff --git a/src/daemon/XorgUserDisplayServer.h b/src/daemon/XorgUserDisplayServer.h +new file mode 100644 +index 000000000..aa7cbe4a8 +--- /dev/null ++++ b/src/daemon/XorgUserDisplayServer.h +@@ -0,0 +1,53 @@ ++/*************************************************************************** ++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> ++* ++* 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_XORGUSERDISPLAYSERVER_H ++#define SDDM_XORGUSERDISPLAYSERVER_H ++ ++#include "DisplayServer.h" ++#include "XAuth.h" ++ ++class QProcess; ++ ++namespace SDDM { ++ ++class XorgUserDisplayServer : public DisplayServer ++{ ++ Q_OBJECT ++ Q_DISABLE_COPY(XorgUserDisplayServer) ++public: ++ explicit XorgUserDisplayServer(Display *parent); ++ ~XorgUserDisplayServer(); ++ ++ QString sessionType() const; ++ ++ void setDisplayName(const QString &displayName); ++ ++ static QString command(Display *display); ++ ++public Q_SLOTS: ++ bool start(); ++ void stop(); ++ void finished(); ++ void setupDisplay(); ++}; ++ ++} // namespace SDDM ++ ++#endif // SDDM_XORGUSERDISPLAYSERVER_H +diff --git a/src/greeter/GreeterApp.cpp b/src/greeter/GreeterApp.cpp +index 01f53fafb..b7f740f41 100644 +--- a/src/greeter/GreeterApp.cpp ++++ b/src/greeter/GreeterApp.cpp +@@ -337,6 +337,15 @@ int main(int argc, char **argv) + QSurfaceFormat::setDefaultFormat(format); + } + ++ // Some themes may use KDE components and that will automatically load KDE's ++ // crash handler which we don't want counterintuitively setting this env ++ // disables that handler ++ qputenv("KDE_DEBUG", "1"); ++ ++ // Qt IM module ++ if (!SDDM::mainConfig.InputMethod.get().isEmpty()) ++ qputenv("QT_IM_MODULE", SDDM::mainConfig.InputMethod.get().toLocal8Bit().constData()); ++ + QGuiApplication app(argc, argv); + + QCommandLineParser parser; +diff --git a/src/helper/Backend.cpp b/src/helper/Backend.cpp +index a324b39fb..9a36a62ba 100644 +--- a/src/helper/Backend.cpp ++++ b/src/helper/Backend.cpp +@@ -54,6 +54,11 @@ namespace SDDM { + m_autologin = on; + } + ++ void Backend::setDisplayServer(bool on) ++ { ++ m_displayServer = on; ++ } ++ + void Backend::setGreeter(bool on) { + m_greeter = on; + } +diff --git a/src/helper/Backend.h b/src/helper/Backend.h +index b790e0011..915d09ca8 100644 +--- a/src/helper/Backend.h ++++ b/src/helper/Backend.h +@@ -36,6 +36,7 @@ namespace SDDM { + static Backend *get(HelperApp *parent); + + void setAutologin(bool on = true); ++ void setDisplayServer(bool on = true); + void setGreeter(bool on = true); + + public slots: +@@ -50,6 +51,7 @@ namespace SDDM { + Backend(HelperApp *parent); + HelperApp *m_app; + bool m_autologin { false }; ++ bool m_displayServer = false; + bool m_greeter { false }; + }; + } +diff --git a/src/helper/CMakeLists.txt b/src/helper/CMakeLists.txt +index 8914ea757..24f36eb97 100644 +--- a/src/helper/CMakeLists.txt ++++ b/src/helper/CMakeLists.txt +@@ -10,9 +10,13 @@ set(HELPER_SOURCES + ${CMAKE_SOURCE_DIR}/src/common/Configuration.cpp + ${CMAKE_SOURCE_DIR}/src/common/ConfigReader.cpp + ${CMAKE_SOURCE_DIR}/src/common/SafeDataStream.cpp ++ ${CMAKE_SOURCE_DIR}/src/common/XAuth.cpp ++ ${CMAKE_SOURCE_DIR}/src/common/XAuth.h + Backend.cpp + HelperApp.cpp + UserSession.cpp ++ xorguserhelper.cpp ++ xorguserhelper.h + ) + + # Different implementations of the VT switching code +diff --git a/src/helper/HelperApp.cpp b/src/helper/HelperApp.cpp +index 3f92f1d8f..059e9c156 100644 +--- a/src/helper/HelperApp.cpp ++++ b/src/helper/HelperApp.cpp +@@ -88,6 +88,16 @@ namespace SDDM { + m_user = args[pos + 1]; + } + ++ if ((pos = args.indexOf(QStringLiteral("--display-server"))) >= 0) { ++ if (pos >= args.length() - 1) { ++ qCritical() << "This application is not supposed to be executed manually"; ++ exit(Auth::HELPER_OTHER_ERROR); ++ return; ++ } ++ m_session->setDisplayServerCommand(args[pos + 1]); ++ m_backend->setDisplayServer(true); ++ } ++ + if ((pos = args.indexOf(QStringLiteral("--autologin"))) >= 0) { + m_backend->setAutologin(true); + } +@@ -103,7 +113,7 @@ namespace SDDM { + } + + connect(m_socket, &QLocalSocket::connected, this, &HelperApp::doAuth); +- connect(m_session, QOverload<int>::of(&QProcess::finished), this, &HelperApp::sessionFinished); ++ connect(m_session, &UserSession::finished, this, &HelperApp::sessionFinished); + m_socket->connectToServer(server, QIODevice::ReadWrite | QIODevice::Unbuffered); + } + +@@ -131,11 +141,6 @@ namespace SDDM { + + if (!m_session->path().isEmpty()) { + env.insert(m_session->processEnvironment()); +- // Allocate a new VT for the wayland session +- if(env.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("wayland")) { +- int vtNumber = VirtualTerminal::setUpNewVt(); +- env.insert(QStringLiteral("XDG_VTNR"), QString::number(vtNumber)); +- } + m_session->setProcessEnvironment(env); + + if (!m_backend->openSession()) { +@@ -216,6 +221,19 @@ namespace SDDM { + } + } + ++ void HelperApp::displayServerStarted(const QString &displayName) ++ { ++ Msg m = Msg::MSG_UNKNOWN; ++ SafeDataStream str(m_socket); ++ str << Msg::DISPLAY_SERVER_STARTED << displayName; ++ str.send(); ++ str.receive(); ++ str >> m; ++ if (m != DISPLAY_SERVER_STARTED) { ++ qCritical() << "Received a wrong opcode instead of DISPLAY_SERVER_STARTED:" << m; ++ } ++ } ++ + UserSession *HelperApp::session() { + return m_session; + } +diff --git a/src/helper/HelperApp.h b/src/helper/HelperApp.h +index 632435ee5..1a75c2d67 100644 +--- a/src/helper/HelperApp.h ++++ b/src/helper/HelperApp.h +@@ -48,6 +48,7 @@ namespace SDDM { + void error(const QString &message, Auth::Error type); + QProcessEnvironment authenticated(const QString &user); + void sessionOpened(bool success); ++ void displayServerStarted(const QString &displayName); + + private slots: + void setUp(); +diff --git a/src/helper/UserSession.cpp b/src/helper/UserSession.cpp +index 8e36f2113..f02a42c21 100644 +--- a/src/helper/UserSession.cpp ++++ b/src/helper/UserSession.cpp +@@ -19,10 +19,14 @@ + * + */ + ++#include <QSocketNotifier> ++ + #include "Configuration.h" + #include "UserSession.h" + #include "HelperApp.h" + #include "VirtualTerminal.h" ++#include "XAuth.h" ++#include "xorguserhelper.h" + + #include <sys/types.h> + #include <sys/ioctl.h> +@@ -37,11 +41,19 @@ + + namespace SDDM { + UserSession::UserSession(HelperApp *parent) +- : QProcess(parent) { +- } +- +- UserSession::~UserSession() { +- ++ : QObject(parent) ++ , m_process(new QProcess(this)) ++ , m_xorgUser(new XOrgUserHelper(this)) ++ { ++ connect(m_process, QOverload<int>::of(&QProcess::finished), this, &UserSession::finished); ++ connect(m_xorgUser, &XOrgUserHelper::displayChanged, this, [this, parent](const QString &display) { ++ auto env = processEnvironment(); ++ env.insert(QStringLiteral("DISPLAY"), m_xorgUser->display()); ++ env.insert(QStringLiteral("XAUTHORITY"), m_xorgUser->xauthPath()); ++ setProcessEnvironment(env); ++ ++ parent->displayServerStarted(display); ++ }); + } + + bool UserSession::start() { +@@ -49,21 +61,68 @@ namespace SDDM { + + setup(); + +- if (env.value(QStringLiteral("XDG_SESSION_CLASS")) == QLatin1String("greeter")) { +- QProcess::start(m_path); +- } else if (env.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("x11")) { +- const QString cmd = QStringLiteral("%1 \"%2\"").arg(mainConfig.X11.SessionCommand.get()).arg(m_path); +- qInfo() << "Starting:" << cmd; +- QProcess::start(cmd); ++ if (!m_displayServerCmd.isEmpty()) { ++ m_xorgUser->setEnvironment(env); ++ if (!m_xorgUser->start(m_displayServerCmd)) ++ return false; ++ } ++ ++ if (env.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("x11")) { ++ if (env.value(QStringLiteral("XDG_SESSION_CLASS")) == QLatin1String("greeter")) { ++ qInfo() << "Starting X11 greeter session:" << m_path; ++ auto args = QProcess::splitCommand(m_path); ++ const auto program = args.takeFirst(); ++ m_process->start(program, args); ++ } else { ++ const QString cmd = QStringLiteral("%1 \"%2\"").arg(mainConfig.X11.SessionCommand.get()).arg(m_path); ++ qInfo() << "Starting X11 user session:" << cmd; ++ m_process->start(mainConfig.X11.SessionCommand.get(), QStringList{m_path}); ++ } + } else if (env.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("wayland")) { + const QString cmd = QStringLiteral("%1 %2").arg(mainConfig.Wayland.SessionCommand.get()).arg(m_path); +- qInfo() << "Starting:" << cmd; +- QProcess::start(cmd); ++ qInfo() << "Starting Wayland user session:" << cmd; ++ m_process->start(mainConfig.Wayland.SessionCommand.get(), QStringList{m_path}); + } else { + qCritical() << "Unable to run user session: unknown session type"; + } + +- return waitForStarted(); ++ if (m_process->waitForStarted()) { ++ int vtNumber = processEnvironment().value(QStringLiteral("XDG_VTNR")).toInt(); ++ VirtualTerminal::jumpToVt(vtNumber, true); ++ return true; ++ } ++ ++ return false; ++ } ++ ++ void UserSession::stop() ++ { ++ m_process->terminate(); ++ if (!m_process->waitForFinished(5000)) ++ m_process->kill(); ++ ++ if (!m_displayServerCmd.isEmpty()) ++ m_xorgUser->stop(); ++ } ++ ++ QProcessEnvironment UserSession::processEnvironment() const ++ { ++ return m_process->processEnvironment(); ++ } ++ ++ void UserSession::setProcessEnvironment(const QProcessEnvironment &env) ++ { ++ m_process->setProcessEnvironment(env); ++ } ++ ++ QString UserSession::displayServerCommand() const ++ { ++ return m_displayServerCmd; ++ } ++ ++ void UserSession::setDisplayServerCommand(const QString &command) ++ { ++ m_displayServerCmd = command; + } + + void UserSession::setPath(const QString& path) { +@@ -74,13 +133,22 @@ namespace SDDM { + return m_path; + } + ++ qint64 UserSession::processId() const ++ { ++ return m_process->processId(); ++ } ++ + void UserSession::setupChildProcess() { + // Session type + QString sessionType = processEnvironment().value(QStringLiteral("XDG_SESSION_TYPE")); +- +- // For Wayland sessions we leak the VT into the session as stdin so +- // that it stays open without races +- if (sessionType == QLatin1String("wayland")) { ++ QString sessionClass = processEnvironment().value(QStringLiteral("XDG_SESSION_CLASS")); ++ const bool hasDisplayServer = !m_displayServerCmd.isEmpty(); ++ const bool x11UserSession = sessionType == QLatin1String("x11") && sessionClass == QLatin1String("user"); ++ const bool waylandUserSession = sessionType == QLatin1String("wayland") && sessionClass == QLatin1String("user"); ++ ++ // When the display server is part of the session, we leak the VT into ++ // the session as stdin so that it stays open without races ++ if (hasDisplayServer || waylandUserSession) { + // open VT and get the fd + int vtNumber = processEnvironment().value(QStringLiteral("XDG_VTNR")).toInt(); + QString ttyString = QStringLiteral("/dev/tty%1").arg(vtNumber); +@@ -226,74 +294,56 @@ namespace SDDM { + qCritical() << "verify directory exist and has sufficient permissions"; + exit(Auth::HELPER_OTHER_ERROR); + } +- const QString homeDir = QString::fromLocal8Bit(pw.pw_dir); +- +- //we cannot use setStandardError file as this code is run in the child process +- //we want to redirect after we setuid so that the log file is owned by the user +- +- // determine stderr log file based on session type +- QString sessionLog = QStringLiteral("%1/%2") +- .arg(homeDir) +- .arg(sessionType == QLatin1String("x11") +- ? mainConfig.X11.SessionLogFile.get() +- : mainConfig.Wayland.SessionLogFile.get()); +- +- // create the path +- QFileInfo finfo(sessionLog); +- QDir().mkpath(finfo.absolutePath()); +- +- //swap the stderr pipe of this subprcess into a file +- int fd = ::open(qPrintable(sessionLog), O_WRONLY | O_CREAT | O_TRUNC, 0600); +- if (fd >= 0) +- { +- dup2 (fd, STDERR_FILENO); +- ::close(fd); +- } else { +- qWarning() << "Could not open stderr to" << sessionLog; +- } +- +- //redirect any stdout to /dev/null +- fd = ::open("/dev/null", O_WRONLY); +- if (fd >= 0) +- { +- dup2 (fd, STDOUT_FILENO); +- ::close(fd); +- } else { +- qWarning() << "Could not redirect stdout"; +- } + +- // set X authority for X11 sessions only +- if (sessionType != QLatin1String("x11")) +- return; +- QString cookie = qobject_cast<HelperApp*>(parent())->cookie(); +- if (!cookie.isEmpty()) { +- QString file = processEnvironment().value(QStringLiteral("XAUTHORITY")); +- QString display = processEnvironment().value(QStringLiteral("DISPLAY")); +- qDebug() << "Adding cookie to" << file; ++ if (sessionClass != QLatin1String("greeter")) { ++ //we cannot use setStandardError file as this code is run in the child process ++ //we want to redirect after we setuid so that the log file is owned by the user + ++ // determine stderr log file based on session type ++ QString sessionLog = QStringLiteral("%1/%2") ++ .arg(QString::fromLocal8Bit(pw.pw_dir)) ++ .arg(sessionType == QLatin1String("x11") ++ ? mainConfig.X11.SessionLogFile.get() ++ : mainConfig.Wayland.SessionLogFile.get()); + + // create the path +- QFileInfo finfo(file); ++ QFileInfo finfo(sessionLog); + QDir().mkpath(finfo.absolutePath()); + +- QFile file_handler(file); +- file_handler.open(QIODevice::Append); +- file_handler.close(); ++ //swap the stderr pipe of this subprcess into a file ++ int fd = ::open(qPrintable(sessionLog), O_WRONLY | O_CREAT | O_TRUNC, 0600); ++ if (fd >= 0) ++ { ++ dup2 (fd, STDERR_FILENO); ++ ::close(fd); ++ } else { ++ qWarning() << "Could not open stderr to" << sessionLog; ++ } + +- QString cmd = QStringLiteral("%1 -f %2 -q").arg(mainConfig.X11.XauthPath.get()).arg(file); ++ //redirect any stdout to /dev/null ++ fd = ::open("/dev/null", O_WRONLY); ++ if (fd >= 0) ++ { ++ dup2 (fd, STDOUT_FILENO); ++ ::close(fd); ++ } else { ++ qWarning() << "Could not redirect stdout"; ++ } ++ } + +- // execute xauth +- FILE *fp = popen(qPrintable(cmd), "w"); ++ // set X authority for X11 sessions only ++ if (x11UserSession) { ++ QString cookie = qobject_cast<HelperApp*>(parent())->cookie(); ++ if (!cookie.isEmpty()) { ++ QString file = processEnvironment().value(QStringLiteral("XAUTHORITY")); ++ QString display = processEnvironment().value(QStringLiteral("DISPLAY")); + +- // check file +- if (!fp) +- return; +- fprintf(fp, "remove %s\n", qPrintable(display)); +- fprintf(fp, "add %s . %s\n", qPrintable(display), qPrintable(cookie)); +- fprintf(fp, "exit\n"); ++ // Create the path ++ QFileInfo finfo(file); ++ QDir().mkpath(finfo.absolutePath()); + +- // close pipe +- pclose(fp); ++ XAuth::addCookieToFile(display, file, cookie); ++ } + } + } + } +diff --git a/src/helper/UserSession.h b/src/helper/UserSession.h +index c2383f068..11bff1d1b 100644 +--- a/src/helper/UserSession.h ++++ b/src/helper/UserSession.h +@@ -23,41 +23,55 @@ + #define SDDM_AUTH_SESSION_H + + #include <QtCore/QObject> +-#include <QtCore/QString> + #include <QtCore/QProcess> + + namespace SDDM { + class HelperApp; +- class UserSession : public QProcess ++ class XOrgUserHelper; ++ class UserSession : public QObject + { + Q_OBJECT + public: + explicit UserSession(HelperApp *parent); +- virtual ~UserSession(); + + bool start(); ++ void stop(); ++ ++ QProcessEnvironment processEnvironment() const; ++ void setProcessEnvironment(const QProcessEnvironment &env); ++ ++ QString displayServerCommand() const; ++ void setDisplayServerCommand(const QString &command); + + void setPath(const QString &path); + QString path() const; + + /*! + \brief Sets m_cachedProcessId. Needed for getting the PID of a finished UserSession + and calling HelperApp::utmpLogout + \param pid The process ID + */ + void setCachedProcessId(qint64 pid); + + /*! + \brief Gets m_cachedProcessId + \return The cached process ID + */ + qint64 cachedProcessId(); + ++ qint64 processId() const; ++ ++ Q_SIGNALS: ++ void finished(int exitCode); ++ + protected: + void setupChildProcess(); + + private: + QString m_path { }; + qint64 m_cachedProcessId; ++ QProcess *m_process = nullptr; ++ XOrgUserHelper *m_xorgUser = nullptr; ++ QString m_displayServerCmd; + }; + } + +diff --git a/src/helper/backend/PamBackend.cpp b/src/helper/backend/PamBackend.cpp +index f86d77d63..c97056a66 100644 +--- a/src/helper/backend/PamBackend.cpp ++++ b/src/helper/backend/PamBackend.cpp +@@ -248,7 +248,9 @@ namespace SDDM { + } + + QProcessEnvironment sessionEnv = m_app->session()->processEnvironment(); +- if (sessionEnv.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("x11")) { ++ const auto sessionType = sessionEnv.value(QStringLiteral("XDG_SESSION_TYPE")); ++ const auto sessionClass = sessionEnv.value(QStringLiteral("XDG_SESSION_CLASS")); ++ if (sessionType == QLatin1String("x11") && (sessionClass == QLatin1String("user") || !m_displayServer)) { + QString display = sessionEnv.value(QStringLiteral("DISPLAY")); + if (!display.isEmpty()) { + #ifdef PAM_XDISPLAY +@@ -256,7 +258,7 @@ namespace SDDM { + #endif + m_pam->setItem(PAM_TTY, qPrintable(display)); + } +- } else if (sessionEnv.value(QStringLiteral("XDG_SESSION_TYPE")) == QLatin1String("wayland")) { ++ } else { + QString tty = QStringLiteral("/dev/tty%1").arg(sessionEnv.value(QStringLiteral("XDG_VTNR"))); + m_pam->setItem(PAM_TTY, qPrintable(tty)); + } +diff --git a/src/helper/xorguserhelper.cpp b/src/helper/xorguserhelper.cpp +new file mode 100644 +index 000000000..573ed101f +--- /dev/null ++++ b/src/helper/xorguserhelper.cpp +@@ -0,0 +1,252 @@ ++/*************************************************************************** ++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> ++* ++* 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 <QCoreApplication> ++#include <QFile> ++#include <QStandardPaths> ++ ++#include "Configuration.h" ++ ++#include "xorguserhelper.h" ++ ++#include <fcntl.h> ++#include <unistd.h> ++ ++namespace SDDM { ++ ++XOrgUserHelper::XOrgUserHelper(QObject *parent) ++ : QObject(parent) ++{ ++} ++ ++QProcessEnvironment XOrgUserHelper::environment() const ++{ ++ return m_environment; ++} ++ ++void XOrgUserHelper::setEnvironment(const QProcessEnvironment &env) ++{ ++ m_environment = env; ++} ++ ++QString XOrgUserHelper::display() const ++{ ++ return m_display; ++} ++ ++QString XOrgUserHelper::xauthPath() const ++{ ++ return m_xauth.authPath(); ++} ++ ++bool XOrgUserHelper::start(const QString &cmd) ++{ ++ // Create xauthority ++ m_xauth.setAuthDirectory(m_environment.value(QStringLiteral("XDG_RUNTIME_DIR"))); ++ m_xauth.setup(); ++ ++ // Start server process ++ if (!startServer(cmd)) ++ return false; ++ ++ // Setup display ++ startDisplayCommand(); ++ ++ return true; ++} ++ ++void XOrgUserHelper::stop() ++{ ++ if (m_serverProcess) { ++ qInfo("Stopping server..."); ++ m_serverProcess->terminate(); ++ if (!m_serverProcess->waitForFinished(5000)) ++ m_serverProcess->kill(); ++ m_serverProcess->deleteLater(); ++ m_serverProcess = nullptr; ++ ++ displayFinished(); ++ } ++} ++ ++bool XOrgUserHelper::startProcess(const QString &cmd, ++ const QProcessEnvironment &env, ++ QProcess **p) ++{ ++ auto args = QProcess::splitCommand(cmd); ++ const auto program = args.takeFirst(); ++ ++ // Make sure to forward the input of this process into the Xorg ++ // server, otherwise it will complain that only console users are allowed ++ auto *process = new QProcess(this); ++ process->setInputChannelMode(QProcess::ForwardedInputChannel); ++ process->setProcessChannelMode(QProcess::ForwardedChannels); ++ process->setProcessEnvironment(env); ++ ++ connect(process, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), ++ process, [](int exitCode, QProcess::ExitStatus exitStatus) { ++ if (exitCode != 0 || exitStatus != QProcess::NormalExit) ++ QCoreApplication::instance()->quit(); ++ }); ++ ++ process->start(program, args); ++ if (!process->waitForStarted(10000)) { ++ qWarning("Failed to start \"%s\": %s", ++ qPrintable(cmd), ++ qPrintable(process->errorString())); ++ return false; ++ } ++ ++ if (p) ++ *p = process; ++ ++ return true; ++} ++ ++bool XOrgUserHelper::startServer(const QString &cmd) ++{ ++ QString serverCmd = cmd; ++ ++ // Create pipe for communicating with X server ++ // 0 == read from X, 1 == write to X ++ int pipeFds[2]; ++ if (::pipe(pipeFds) != 0) { ++ qCritical("Could not create pipe to start X server"); ++ return false; ++ } ++ ++ // Do not leak the read endpoint to the X server process ++ fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC); ++ ++ // Server environment ++ // Not setting XORG_RUN_AS_USER_OK=1 will make Xorg require root privileges ++ // under Fedora and all distros that use their patch. ++ // https://src.fedoraproject.org/rpms/xorg-x11-server/blob/rawhide/f/0001-Fedora-hack-Make-the-suid-root-wrapper-always-start-.patch ++ // https://fedoraproject.org/wiki/Changes/XorgWithoutRootRights ++ QProcessEnvironment serverEnv = m_environment; ++ serverEnv.insert(QStringLiteral("XORG_RUN_AS_USER_OK"), QStringLiteral("1")); ++ ++ // Append xauth and display fd to the command ++ auto args = QStringList() ++ << QStringLiteral("-auth") << m_xauth.authPath() ++ << QStringLiteral("-displayfd") << QString::number(pipeFds[1]); ++ ++ // Append VT from environment ++ args << QStringLiteral("vt%1").arg(serverEnv.value(QStringLiteral("XDG_VTNR"))); ++ ++ // Log to stdout ++ args << QStringLiteral("-logfile") << QStringLiteral("/dev/null"); ++ ++ // Command string ++ serverCmd += QLatin1Char(' ') + args.join(QLatin1Char(' ')); ++ ++ // Start the server process ++ qInfo("Running server: %s", qPrintable(serverCmd)); ++ if (!startProcess(serverCmd, serverEnv, &m_serverProcess)) { ++ ::close(pipeFds[0]); ++ return false; ++ } ++ ++ // Close the other side of pipe in our process, otherwise reading ++ // from it may stuck even X server exit ++ ::close(pipeFds[1]); ++ ++ // Read the display number from the pipe ++ QFile readPipe; ++ if (!readPipe.open(pipeFds[0], QIODevice::ReadOnly)) { ++ qCritical("Failed to open pipe to start X Server"); ++ ::close(pipeFds[0]); ++ return false; ++ } ++ QByteArray displayNumber = readPipe.readLine(); ++ if (displayNumber.size() < 2) { ++ // X server gave nothing (or a whitespace) ++ qCritical("Failed to read display number from pipe"); ++ ::close(pipeFds[0]); ++ return false; ++ } ++ displayNumber.prepend(QByteArray(":")); ++ displayNumber.remove(displayNumber.size() -1, 1); // trim trailing whitespace ++ m_display = QString::fromLocal8Bit(displayNumber); ++ qDebug("X11 display: %s", qPrintable(m_display)); ++ Q_EMIT displayChanged(m_display); ++ ++ // Generate xauthority file ++ // For the X server's copy, the display number doesn't matter. ++ // An empty file would result in no access control! ++ if (!m_xauth.addCookie(m_display)) { ++ qCritical("Failed to write xauth file"); ++ return false; ++ } ++ ++ // Close our pipe ++ ::close(pipeFds[0]); ++ ++ return true; ++} ++ ++void XOrgUserHelper::startDisplayCommand() ++{ ++ auto env = QProcessEnvironment::systemEnvironment(); ++ env.insert(QStringLiteral("DISPLAY"), m_display); ++ env.insert(QStringLiteral("XAUTHORITY"), m_xauth.authPath()); ++ ++ // Set cursor ++ qInfo("Setting default cursor..."); ++ QProcess *setCursor = nullptr; ++ if (startProcess(QStringLiteral("xsetroot -cursor_name left_ptr"), env, &setCursor)) { ++ if (!setCursor->waitForFinished(1000)) { ++ qWarning() << "Could not setup default cursor"; ++ setCursor->kill(); ++ } ++ setCursor->deleteLater(); ++ } ++ ++ // Display setup script ++ auto cmd = mainConfig.X11.DisplayCommand.get(); ++ qInfo("Running display setup script: %s", qPrintable(cmd)); ++ QProcess *displayScript = nullptr; ++ if (startProcess(cmd, env, &displayScript)) { ++ if (!displayScript->waitForFinished(30000)) ++ displayScript->kill(); ++ displayScript->deleteLater(); ++ } ++} ++ ++void XOrgUserHelper::displayFinished() ++{ ++ auto env = QProcessEnvironment::systemEnvironment(); ++ env.insert(QStringLiteral("DISPLAY"), m_display); ++ env.insert(QStringLiteral("XAUTHORITY"), m_xauth.authPath()); ++ env.insert(QStringLiteral("QT_QPA_PLATFORM"), QStringLiteral("xcb")); ++ ++ auto cmd = mainConfig.X11.DisplayStopCommand.get(); ++ qInfo("Running display stop script: %s", qPrintable(cmd)); ++ QProcess *displayStopScript = nullptr; ++ if (startProcess(cmd, env, &displayStopScript)) { ++ if (!displayStopScript->waitForFinished(5000)) ++ displayStopScript->kill(); ++ displayStopScript->deleteLater(); ++ } ++ ++ // Remove xauthority file ++ QFile::remove(m_xauth.authPath()); ++} ++ ++} // namespace SDDM +diff --git a/src/helper/xorguserhelper.h b/src/helper/xorguserhelper.h +new file mode 100644 +index 000000000..73536965a +--- /dev/null ++++ b/src/helper/xorguserhelper.h +@@ -0,0 +1,64 @@ ++/*************************************************************************** ++* Copyright (c) 2021 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com> ++* ++* 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 XORGUSERHELPER_H ++#define XORGUSERHELPER_H ++ ++#include <QProcess> ++ ++#include "XAuth.h" ++ ++namespace SDDM { ++ ++class XOrgUserHelper : public QObject ++{ ++ Q_OBJECT ++ Q_PROPERTY(QString display READ display NOTIFY displayChanged) ++public: ++ explicit XOrgUserHelper(QObject *parent = nullptr); ++ ++ QProcessEnvironment environment() const; ++ void setEnvironment(const QProcessEnvironment &env); ++ ++ QString display() const; ++ ++ QString xauthPath() const; ++ ++ bool start(const QString &cmd); ++ void stop(); ++ ++Q_SIGNALS: ++ void displayChanged(const QString &display); ++ ++private: ++ QString m_display = QStringLiteral(":0"); ++ XAuth m_xauth; ++ QProcessEnvironment m_environment; ++ QProcess *m_serverProcess = nullptr; ++ ++ bool startProcess(const QString &cmd, const QProcessEnvironment &env, ++ QProcess **p = nullptr); ++ bool startServer(const QString &cmd); ++ void startDisplayCommand(); ++ void displayFinished(); ++}; ++ ++} // namespace SDDM ++ ++#endif // XORGUSERHELPER_H |