From 9881225a92ffe6cf3d4a84273192b08b2d2f6046 Mon Sep 17 00:00:00 2001 From: Pier Luigi Fiorini 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(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 +* Copyright (c) 2013 Abdurrahman AVCI +* +* 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 +#include +#include + +#include "Configuration.h" +#include "Constants.h" +#include "XAuth.h" + +#include + +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 +* Copyright (c) 2013 Abdurrahman AVCI +* +* 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 + +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(m_displayServer)->authPath()); + if (qobject_cast(m_displayServer)) + m_greeter->setAuthPath(qobject_cast(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(m_displayServer)->cookie()); + if (qobject_cast(m_displayServer)) + m_auth->setCookie(qobject_cast(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 #include @@ -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(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 +* +* 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 +* +* 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::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 + #include "Configuration.h" #include "UserSession.h" #include "HelperApp.h" #include "VirtualTerminal.h" +#include "XAuth.h" +#include "xorguserhelper.h" #include #include @@ -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::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(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(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 -#include #include 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 +* +* 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 +#include +#include + +#include "Configuration.h" + +#include "xorguserhelper.h" + +#include +#include + +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::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 +* +* 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 + +#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