From 1b8049cc72265f58d73febbc216edb258742f137 Mon Sep 17 00:00:00 2001 From: April & May & June Date: Thu, 8 Jan 2026 00:07:43 +0800 Subject: [PATCH] fix: fix cannot login more than 1 user The original design is wrong: pam_open_session() should only be called inside child process. Move it into childModifier (after fork()), and execve manually to insert environ into the child process. The internal API is also changed to adapt the new code structure. The Pam.cpp is merged into Auth.cpp, and some change happened in Display.cpp --- REUSE.toml | 2 +- services/ddm.service.in | 4 +- src/daemon/Auth.cpp | 345 +++++++++++++++++++++++++------------ src/daemon/Auth.h | 85 +++++---- src/daemon/CMakeLists.txt | 1 - src/daemon/Display.cpp | 69 ++++---- src/daemon/Display.h | 3 - src/daemon/Pam.cpp | 202 ---------------------- src/daemon/Pam.h | 61 ------- src/daemon/UserSession.cpp | 39 +++-- src/daemon/UserSession.h | 11 +- 11 files changed, 341 insertions(+), 481 deletions(-) delete mode 100644 src/daemon/Pam.cpp delete mode 100644 src/daemon/Pam.h diff --git a/REUSE.toml b/REUSE.toml index 9049026..06dd21a 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -69,7 +69,7 @@ SPDX-FileCopyrightText = "None" SPDX-License-Identifier = "GPL-2.0-or-later" [[annotations]] -path = ["releng/prepare-relnotes", "src/daemon/Auth.cpp", "src/daemon/Auth.h", "src/common/ConfigReader.cpp", "src/common/ConfigReader.h", "src/common/Configuration.cpp", "src/common/Configuration.h", "src/common/MessageHandler.h", "src/common/Messages.h", "src/common/Session.cpp", "src/common/Session.h", "src/common/SignalHandler.cpp", "src/common/SignalHandler.h", "src/common/SocketWriter.cpp", "src/common/SocketWriter.h", "src/common/VirtualTerminal.cpp", "src/common/VirtualTerminal.h", "src/common/XAuth.cpp", "src/common/XAuth.h", "src/daemon/DaemonApp.cpp", "src/daemon/DaemonApp.h", "src/daemon/Display.cpp", "src/daemon/Display.h", "src/daemon/DisplayManager.cpp", "src/daemon/DisplayManager.h", "src/daemon/PowerManager.cpp", "src/daemon/PowerManager.h", "src/daemon/SeatManager.cpp", "src/daemon/SeatManager.h", "src/daemon/SocketServer.cpp", "src/daemon/SocketServer.h", "src/daemon/TreelandConnector.cpp", "src/daemon/TreelandConnector.h", "src/daemon/Utils.h", "src/daemon/XorgDisplayServer.cpp", "src/daemon/XorgDisplayServer.h", "src/greeter/GreeterApp.h", "src/greeter/GreeterProxy.cpp", "src/greeter/GreeterProxy.h", "src/greeter/SessionModel.cpp", "src/greeter/SessionModel.h", "src/greeter/UserModel.cpp", "src/greeter/UserModel.h", "src/daemon/Pam.cpp", "src/daemon/Pam.h", "src/daemon/UserSession.cpp", "src/daemon/UserSession.h", "src/common/LogindDBusTypes.cpp", "src/common/LogindDBusTypes.h", "src/greeter/GreeterApp.cpp"] +path = ["releng/prepare-relnotes", "src/daemon/Auth.cpp", "src/daemon/Auth.h", "src/common/ConfigReader.cpp", "src/common/ConfigReader.h", "src/common/Configuration.cpp", "src/common/Configuration.h", "src/common/MessageHandler.h", "src/common/Messages.h", "src/common/Session.cpp", "src/common/Session.h", "src/common/SignalHandler.cpp", "src/common/SignalHandler.h", "src/common/SocketWriter.cpp", "src/common/SocketWriter.h", "src/common/VirtualTerminal.cpp", "src/common/VirtualTerminal.h", "src/common/XAuth.cpp", "src/common/XAuth.h", "src/daemon/DaemonApp.cpp", "src/daemon/DaemonApp.h", "src/daemon/Display.cpp", "src/daemon/Display.h", "src/daemon/DisplayManager.cpp", "src/daemon/DisplayManager.h", "src/daemon/PowerManager.cpp", "src/daemon/PowerManager.h", "src/daemon/SeatManager.cpp", "src/daemon/SeatManager.h", "src/daemon/SocketServer.cpp", "src/daemon/SocketServer.h", "src/daemon/TreelandConnector.cpp", "src/daemon/TreelandConnector.h", "src/daemon/Utils.h", "src/daemon/XorgDisplayServer.cpp", "src/daemon/XorgDisplayServer.h", "src/greeter/GreeterApp.h", "src/greeter/GreeterProxy.cpp", "src/greeter/GreeterProxy.h", "src/greeter/SessionModel.cpp", "src/greeter/SessionModel.h", "src/greeter/UserModel.cpp", "src/greeter/UserModel.h", "src/daemon/UserSession.cpp", "src/daemon/UserSession.h", "src/common/LogindDBusTypes.cpp", "src/common/LogindDBusTypes.h", "src/greeter/GreeterApp.cpp"] precedence = "aggregate" SPDX-FileCopyrightText = "None" SPDX-License-Identifier = "GPL-2.0-or-later" diff --git a/services/ddm.service.in b/services/ddm.service.in index e48b29a..c74e3dd 100644 --- a/services/ddm.service.in +++ b/services/ddm.service.in @@ -14,14 +14,12 @@ Before=seatd.service ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/ddm Restart=always -CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_KILL CAP_SETGID CAP_SETUID CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_TTY_CONFIG +CapabilityBoundingSet=CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE CAP_CHOWN CAP_DAC_OVERRIDE CAP_DAC_READ_SEARCH CAP_FOWNER CAP_KILL CAP_SETGID CAP_SETUID CAP_SYS_ADMIN CAP_SYS_BOOT CAP_SYS_CHROOT CAP_SYS_TTY_CONFIG CAP_SYS_RESOURCE OOMScoreAdjust=-300 Nice=-15 PrivateIPC=true ProtectClock=true -ProtectKernelTunables=true ProtectKernelModules=true -RestrictSUIDSGID=true [Install] Alias=display-manager.service diff --git a/src/daemon/Auth.cpp b/src/daemon/Auth.cpp index f998c51..c91e4f0 100644 --- a/src/daemon/Auth.cpp +++ b/src/daemon/Auth.cpp @@ -1,73 +1,43 @@ -/* - * Qt Authentication Library - * Copyright (C) 2013 Martin Bříza - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-2.0-or-later #include "Auth.h" -#include "Pam.h" #include "UserSession.h" +#include "Login1Manager.h" +#include "Login1Session.h" +#include "VirtualTerminal.h" + #include +#include #include #include #include namespace DDM { - ////////////////////// - // Helper functions // - ////////////////////// - - /** - * Write utmp/wtmp/btmp records when a user logs in - * - * @param vt Virtual terminal (tty7, tty8,...) - * @param displayName Display (:0, :1,...) - * @param user User logging in - * @param pid User process ID (e.g. PID of startkde) - * @param authSuccessful Was authentication successful - */ - static void utmpLogin(const QString &vt, const QString &displayName, const QString &user, qint64 pid, bool authSuccessful) { + /////////////////////////// + // utmp helper functions // + /////////////////////////// + + void Auth::utmpLogin(bool success) { struct utmpx entry { }; struct timeval tv; entry.ut_type = USER_PROCESS; - entry.ut_pid = pid; + entry.ut_pid = sessionProcessId; // ut_line: vt - if (!vt.isEmpty()) { - QString tty = QStringLiteral("tty"); - tty.append(vt); - QByteArray ttyBa = tty.toLocal8Bit(); - const char* ttyChar = ttyBa.constData(); - strncpy(entry.ut_line, ttyChar, sizeof(entry.ut_line) - 1); - } + if (tty > 0) + strncpy(entry.ut_line, QStringLiteral("tty%1").arg(tty).toLocal8Bit().constData(), sizeof(entry.ut_line) - 1); // ut_host: displayName - QByteArray displayBa = displayName.toLocal8Bit(); - const char* displayChar = displayBa.constData(); - strncpy(entry.ut_host, displayChar, sizeof(entry.ut_host) - 1); + if (!display.isEmpty()) + strncpy(entry.ut_host, display.toLocal8Bit().constData(), sizeof(entry.ut_host) - 1); // ut_user: user - QByteArray userBa = user.toLocal8Bit(); - const char* userChar = userBa.constData(); - strncpy(entry.ut_user, userChar, sizeof(entry.ut_user) -1); + strncpy(entry.ut_user, user.toLocal8Bit().constData(), sizeof(entry.ut_user) -1); gettimeofday(&tv, NULL); entry.ut_tv.tv_sec = tv.tv_sec; @@ -80,7 +50,7 @@ namespace DDM { endutxent(); // append to failed login database btmp - if (!authSuccessful) { + if (!success) { updwtmpx("/var/log/btmp", &entry); } else { // append to wtmp @@ -88,33 +58,20 @@ namespace DDM { } } - /** - * Write utmp/wtmp records when a user logs out - * - * @param vt Virtual terminal (tty7, tty8,...) - * @param displayName Display (:0, :1,...) - * @param pid User process ID (e.g. PID of startkde) - */ - static void utmpLogout(const QString &vt, const QString &displayName, qint64 pid) { + void Auth::utmpLogout() { struct utmpx entry { }; struct timeval tv; entry.ut_type = DEAD_PROCESS; - entry.ut_pid = pid; + entry.ut_pid = sessionProcessId; // ut_line: vt - if (!vt.isEmpty()) { - QString tty = QStringLiteral("tty"); - tty.append(vt); - QByteArray ttyBa = tty.toLocal8Bit(); - const char* ttyChar = ttyBa.constData(); - strncpy(entry.ut_line, ttyChar, sizeof(entry.ut_line) - 1); - } + if (tty > 0) + strncpy(entry.ut_line, QStringLiteral("tty%1").arg(tty).toLocal8Bit().constData(), sizeof(entry.ut_line) - 1); // ut_host: displayName - QByteArray displayBa = displayName.toLocal8Bit(); - const char* displayChar = displayBa.constData(); - strncpy(entry.ut_host, displayChar, sizeof(entry.ut_host) - 1); + if (!display.isEmpty()) + strncpy(entry.ut_host, display.toLocal8Bit().constData(), sizeof(entry.ut_host) - 1); gettimeofday(&tv, NULL); entry.ut_tv.tv_sec = tv.tv_sec; @@ -130,15 +87,85 @@ namespace DDM { updwtmpx("/var/log/wtmp", &entry); } + /////////////////////////////// + // PAM conversation function // + /////////////////////////////// + + /** PAM conversation function */ + static int converse(int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *secret_ptr) { + *resp = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response)); + if (!*resp) + return PAM_BUF_ERR; + + // We only handle secret (password) sending here, which is + // prompt by PAM_PROMPT_ECHO_OFF. Message types (error/info) + // are just logged. + // + // Prompts with PAM_PROMPT_ECHO_ON (most likely asking for + // username) are not expected, since we required username is + // set before. + for (int i = 0; i < num_msg; ++i) { + switch (msg[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + resp[i]->resp = strdup(*static_cast(secret_ptr)); + resp[i]->resp_retcode = 0; + break; + case PAM_ERROR_MSG: + qWarning() << "[Converse] Error message:" << msg[i]->msg; + resp[i]->resp = nullptr; + resp[i]->resp_retcode = 0; + break; + case PAM_TEXT_INFO: + qInfo() << "[Converse] Info message:" << msg[i]->msg; + resp[i]->resp = nullptr; + resp[i]->resp_retcode = 0; + break; + default: + qCritical("[Converse] Unsupported message style %d: %s", msg[i]->msg_style, msg[i]->msg); + for (int j = 0; j < i; j++) { + free(resp[j]->resp); + resp[j]->resp = nullptr; + } + free(*resp); + *resp = nullptr; + return PAM_CONV_ERR; + } + } + return PAM_SUCCESS; + } + ///////////////////////// // Auth implementation // ///////////////////////// + class AuthPrivate : public QObject { + Q_OBJECT + public: + AuthPrivate(Auth *parent) + : QObject(parent) + , secretPtr(new const char *) + , conv({ converse, static_cast(secretPtr) }) { + *secretPtr = nullptr; + } + + ~AuthPrivate() { + delete secretPtr; + } + + pam_handle_t *handle{ nullptr }; + const char **secretPtr{}; + pam_conv conv{}; + int ret{}; + }; + Auth::Auth(QObject *parent, QString user) : QObject(parent) , user(user) - , m_pam(new Pam(this)) - , m_session(new UserSession(this)) { + , m_session(new UserSession(this)) + , d(new AuthPrivate(this)) { connect(m_session, QOverload::of(&QProcess::finished), this, @@ -146,36 +173,56 @@ namespace DDM { } Auth::~Auth() { - stop(); - } - - bool Auth::authenticate(const QByteArray &secret) { - m_pam->user = user; - if (!m_pam->start()) { - utmpLogin(std::to_string(tty).c_str(), display, user, 0, false); - return false; + if (m_session->state() != QProcess::NotRunning) + m_session->stop(); + if (sessionOpened) { + closeSession(); + utmpLogout(); } - if (!m_pam->authenticate(secret)) { - utmpLogin(std::to_string(tty).c_str(), display, user, 0, false); - return false; + if (d->handle) { + d->ret = pam_end(d->handle, d->ret); + if (d->ret != PAM_SUCCESS) + qWarning() << "[Auth] end:" << pam_strerror(d->handle, d->ret); } - active = true; - return true; + qDebug() << "[Auth] Auth for user" << user << "ended."; } - int Auth::openSession(const QProcessEnvironment &env) { - Q_ASSERT(active); - auto ret = m_pam->openSession(env); - if (!ret.has_value()) - return -1; - m_env = *ret; - xdgSessionId = m_env.value(QStringLiteral("XDG_SESSION_ID")).toInt(); - return xdgSessionId; +#define CHECK_RET_AUTH \ + if (d->ret != PAM_SUCCESS) { \ + qWarning() << "[Auth] Authenticate:" << pam_strerror(d->handle, d->ret); \ + utmpLogin(false); \ + return false; \ + } + bool Auth::authenticate(const QByteArray &secret) { + Q_ASSERT(!user.isEmpty()); + + qDebug() << "[Auth] Starting..."; + d->ret = pam_start("ddm", user.toLocal8Bit().constData(), &d->conv, &d->handle); + CHECK_RET_AUTH + + qDebug() << "[Auth] Authenticating user" << user; + + // Set the secret, authenticate, then clear the secret + // immediately to avoid leak + *d->secretPtr = secret.constData(); + d->ret = pam_authenticate(d->handle, 0); + *d->secretPtr = nullptr; + + CHECK_RET_AUTH + qDebug() << "[Auth] Authenticated."; + + d->ret = pam_acct_mgmt(d->handle, 0); + CHECK_RET_AUTH + + authenticated = true; + return true; } - void Auth::startUserProcess(const QString &command, const QByteArray &cookie) { - Q_ASSERT(!m_env.isEmpty()); - QProcessEnvironment env = m_env; + int Auth::openSession(const QString &command, + QProcessEnvironment env, + const QByteArray &cookie) { + Q_ASSERT(authenticated); + // Insert necessary environment struct passwd *pw = getpwnam(qPrintable(user)); if (pw) { env.insert(QStringLiteral("HOME"), QString::fromLocal8Bit(pw->pw_dir)); @@ -185,30 +232,104 @@ namespace DDM { env.insert(QStringLiteral("LOGNAME"), QString::fromLocal8Bit(pw->pw_name)); } m_session->setProcessEnvironment(env); + + // RUN!!! m_session->start(command, type, cookie); + if (!m_session->waitForStarted()) { + qWarning() << "[Auth] Failed to start user process."; + return -1; + } + sessionOpened = true; - // write successful login to utmp/wtmp - const QString displayId = env.value(QStringLiteral("DISPLAY")); - const QString vt = env.value(QStringLiteral("XDG_VTNR")); // cache pid for session end - utmpLogin(vt, displayId, user, m_session->processId(), true); + sessionProcessId = m_session->processId(); + // write successful login to utmp/wtmp + utmpLogin(true); + // Get XDG_SESSION_ID via Logind + OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), + Logind::managerPath(), + QDBusConnection::systemBus()); + auto reply = manager.GetSessionByPID(static_cast(sessionProcessId)); + reply.waitForFinished(); + if (reply.error().isValid()) { + qWarning() << "[Auth] GetSessionByPID:" << reply.error().message(); + return -1; + } + QDBusObjectPath path = reply.value(); + OrgFreedesktopLogin1SessionInterface session(Logind::serviceName(), + path.path(), + QDBusConnection::systemBus()); + bool ok; + xdgSessionId = session.property("Id").toInt(&ok); + if (!ok) { + qWarning() << "[Auth] Failed to get XDG_SESSION_ID for user" << user; + return -1; + } + qDebug() << "[Auth] Session opened with XDG_SESSION_ID =" << xdgSessionId; + return xdgSessionId; } - void Auth::stop() { - if (!active) - return; - active = false; - qint64 pid = m_session->processId(); - QString vt = m_env.value(QStringLiteral("XDG_VTNR")); - QString displayId = m_env.value(QStringLiteral("DISPLAY")); - if (m_session->state() != QProcess::NotRunning) - m_session->stop(); - if (m_pam->sessionOpened) - m_pam->closeSession(); +#define CHECK_RET_CLOSE \ + if (d->ret != PAM_SUCCESS) { \ + qWarning() << "[Auth] closeSession:" << pam_strerror(d->handle, d->ret); \ + return false; \ + } + bool Auth::closeSession() { + if (!sessionOpened) { + qWarning() << "[Auth] closeSession: Session was not opened."; + return true; + } + qDebug() << "[Auth] Closing session for user" << user; - // write logout to utmp/wtmp - if (pid > 0) { - utmpLogout(vt, displayId, pid); + d->ret = pam_close_session(d->handle, 0); + CHECK_RET_CLOSE + + sessionOpened = false; + d->ret = pam_setcred(d->handle, PAM_DELETE_CRED); + CHECK_RET_CLOSE + + qDebug() << "[Auth] Session closed."; + return true; + } + +#define CHECK_RET_OPEN \ + if (d->ret != PAM_SUCCESS) { \ + qWarning() << "[Auth] openSessionInternal:" << pam_strerror(d->handle, d->ret); \ + return nullptr; \ + } + char **Auth::openSessionInternal(const QProcessEnvironment &sessionEnv) { + qDebug() << "[Auth] Opening session for user" << user; + + d->ret = pam_setcred(d->handle, PAM_ESTABLISH_CRED); + CHECK_RET_OPEN + + // Set PAM_TTY + QString tty = VirtualTerminal::path(sessionEnv.value(QStringLiteral("XDG_VTNR")).toInt()); + d->ret = pam_set_item(d->handle, PAM_TTY, qPrintable(tty)); + CHECK_RET_OPEN + + // Set PAM_XDISPLAY + QString display = sessionEnv.value(QStringLiteral("DISPLAY")); + if (!display.isEmpty()) { + d->ret = pam_set_item(d->handle, PAM_XDISPLAY, qPrintable(display)); + CHECK_RET_OPEN + } + + // Insert environments into new session + QStringList envStrs = sessionEnv.toStringList(); + for (const QString &s : std::as_const(envStrs)) { + d->ret = pam_putenv(d->handle, qPrintable(s)); + CHECK_RET_OPEN } + + // OPEN!!! + d->ret = pam_open_session(d->handle, 0); + CHECK_RET_OPEN + + qDebug() << "[Auth] Session opened."; + + return pam_getenvlist(d->handle); } } // namespace DDM + +#include "Auth.moc" diff --git a/src/daemon/Auth.h b/src/daemon/Auth.h index e32393a..6a31fdd 100644 --- a/src/daemon/Auth.h +++ b/src/daemon/Auth.h @@ -1,22 +1,5 @@ -/* - * Qt Authentication library - * Copyright (C) 2013 Martin Bříza - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ +// Copyright (C) 2026 UnionTech Software Technology Co., Ltd. +// SPDX-License-Identifier: GPL-2.0-or-later #ifndef DDM_AUTH_H #define DDM_AUTH_H @@ -27,7 +10,7 @@ #include namespace DDM { - class Pam; + class AuthPrivate; class UserSession; /** Authentication handler, manage login and session */ @@ -37,8 +20,11 @@ namespace DDM { Auth(QObject *parent, QString user); ~Auth(); - /** Indicates whether the Auth is active (PAM module started) */ - bool active{ false }; + /** Indicates whether authenticated (authenticate() is called and succeed) */ + bool authenticated{ false }; + + /** Indicates whether a session is opened with this handle */ + bool sessionOpened{ false }; /** Username. Must be set before authenticate() */ QString user{}; @@ -55,9 +41,12 @@ namespace DDM { /** Virtual terminal number (e.g. 7 for tty7) */ int tty{ 0 }; - /** Logind session ID (the XDG_SESSION_ID env var) */ + /** Logind session ID (the XDG_SESSION_ID env var). Positive values are valid */ int xdgSessionId{ 0 }; + /** PID of the child process. Positive values are valid */ + qint64 sessionProcessId{ 0 }; + public Q_SLOTS: /** * Sets up the environment and starts the authentication. @@ -68,31 +57,33 @@ namespace DDM { bool authenticate(const QByteArray &secret); /** - * Opens user session via PAM and returns the XDG_SESSION_ID. - * Must be called after authenticate(). + * Starts user process, opens Logind session and returns the + * XDG_SESSION_ID. Must be called after authenticate(). * + * @param command Command to execute as user process * @param env Environment variables to set for the session - * @return XDG_SESSION_ID on success, -1 on failure + * @param cookie XAuth cookie, must be provided if type=X11 + * @return A valid XDG_SESSION_ID on success, zero or negative on failure */ - int openSession(const QProcessEnvironment &env); + int openSession(const QString &command, + QProcessEnvironment env, + const QByteArray &cookie = QByteArray()); /** - * Starts process inside opened session. - * Must be called after openSession(). - * Only 1 process can be started per Auth instance, - * userProcessFinished() is emitted when the process ends. - * Implemented in UserSession. - * - * @param command Command to exec - * @param cookie XAuth cookie (must be set for X11) + * Close PAM session + * @return true on success, false on failure */ - void startUserProcess(const QString &command, const QByteArray &cookie = QByteArray()); + bool closeSession(); /** - * Stop PAM, close opened session and end up user process. - * This will be automatically called in the destructor. + * Opens PAM session and returns the environment variables set + * by PAM modules. Must be called in the child process after + * fork() + * + * @param sessionEnv Environment variables to set for the session + * @return result of pam_getenvlist on success, nullptr on failure */ - void stop(); + char **openSessionInternal(const QProcessEnvironment &sessionEnv); Q_SIGNALS: /** @@ -103,14 +94,22 @@ namespace DDM { void userProcessFinished(int status); private: - /** The PAM module */ - Pam *m_pam{ nullptr }; + /** + * Write utmp/wtmp/btmp records when a user logs in + * + * @param success Was authentication successful + */ + void utmpLogin(bool success); + + /** + * Write utmp/wtmp records when a user logs out + */ + void utmpLogout(); /** The user process */ UserSession *m_session{ nullptr }; - /** Cached environment inside the opened logind session, for the user process */ - QProcessEnvironment m_env{}; + AuthPrivate *d{ nullptr }; }; } diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index bff56f0..d9c834d 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -28,7 +28,6 @@ set(DAEMON_SOURCES DaemonApp.cpp Display.cpp DisplayManager.cpp - Pam.cpp PowerManager.cpp SeatManager.cpp SocketServer.cpp diff --git a/src/daemon/Display.cpp b/src/daemon/Display.cpp index a209870..809c800 100644 --- a/src/daemon/Display.cpp +++ b/src/daemon/Display.cpp @@ -124,8 +124,6 @@ namespace DDM { } Display::~Display() { - for (auto *item : std::as_const(auths)) - disconnect(item, &Auth::userProcessFinished, this, &Display::userProcessFinished); stop(); } @@ -173,8 +171,12 @@ namespace DDM { if (!m_started) return; - for (auto *item : std::as_const(auths)) - item->stop(); + for (auto *auth : std::as_const(auths)) { + disconnect(auth, &Auth::userProcessFinished, nullptr, nullptr); + daemonApp->displayManager()->RemoveSession(auth->sessionId); + delete auth; + } + auths.clear(); // stop socket server m_socketServer->stop(); @@ -196,7 +198,7 @@ namespace DDM { // send logged in users (for possible crash recovery) SocketWriter writer(socket); for (Auth *auth : std::as_const(auths)) { - if (auth->active) + if (auth->sessionOpened) writer << quint32(DaemonMessages::UserLoggedIn) << auth->user << auth->xdgSessionId; } } @@ -221,7 +223,7 @@ namespace DDM { if (!auth) auth = new Auth(this, user); - if (auth->active) { + if (auth->authenticated) { qWarning() << "Existing authentication ongoing, aborting"; return; } @@ -241,7 +243,6 @@ namespace DDM { } // Run password check - auth->user = user; if (session.isSingleMode()) auth->tty = VirtualTerminal::setUpNewVt(); else @@ -273,8 +274,6 @@ namespace DDM { // session id const QString sessionId = QStringLiteral("Session%1").arg(daemonApp->newSessionId()); - daemonApp->displayManager()->AddSession(sessionId, name, user, auth->tty); - daemonApp->displayManager()->setLastSession(sessionId); env.insert(QStringLiteral("XDG_SESSION_PATH"), daemonApp->displayManager()->sessionPath(sessionId)); auth->sessionId = sessionId; @@ -312,6 +311,7 @@ namespace DDM { return; } m_x11Server->setupDisplay(); + auth->display = m_x11Server->display; env.insert(QStringLiteral("DISPLAY"), m_x11Server->display); cookie = m_x11Server->cookie(); } else { @@ -321,26 +321,31 @@ namespace DDM { QThread::msleep(500); // give some time to treeland to stop properly } - // Open Logind session - int xdgSessionId = auth->openSession(env); + // Open Logind session & Exec the desktop process + VirtualTerminal::setVtSignalHandler(nullptr, nullptr); + int xdgSessionId = auth->openSession(session.exec(), env, cookie); + daemonApp->treelandConnector()->setSignalHandler(); + if (xdgSessionId <= 0) { qCritical() << "Failed to open logind session for user" << user; - auth->stop(); delete auth; return; } + if (auth->type == Treeland) { Q_EMIT loginSucceeded(socket, user); // Tell Treeland to enter the session activateSession(auth->user, xdgSessionId); } - // Exec the desktop process - connect(auth, &Auth::userProcessFinished, this, &Display::userProcessFinished); - VirtualTerminal::jumpToVt(auth->tty, false, false); - VirtualTerminal::setVtSignalHandler(nullptr, nullptr); - auth->startUserProcess(session.exec(), cookie); - daemonApp->treelandConnector()->setSignalHandler(); + connect(auth, &Auth::userProcessFinished, this, [this, auth](int status) { + qWarning() << "Session for user" << auth->user << "finished with status" << status; + auths.removeAll(auth); + daemonApp->displayManager()->RemoveSession(auth->sessionId); + delete auth; + }); + daemonApp->displayManager()->AddSession(sessionId, name, user, auth->tty); + daemonApp->displayManager()->setLastSession(sessionId); // The user process is ongoing, append to active auths // The auth will be delete later in userProcessFinished() @@ -348,10 +353,18 @@ namespace DDM { } void Display::logout([[maybe_unused]] QLocalSocket *socket, int id) { - for (Auth *auth : std::as_const(auths)) - if (auth->xdgSessionId == id) - auth->stop(); - OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), Logind::managerPath(), QDBusConnection::systemBus()); + for (Auth *auth : std::as_const(auths)) { + if (auth->xdgSessionId == id) { + disconnect(auth, &Auth::userProcessFinished, nullptr, nullptr); + auths.removeAll(auth); + daemonApp->displayManager()->RemoveSession(auth->sessionId); + delete auth; + break; + } + } + OrgFreedesktopLogin1ManagerInterface manager(Logind::serviceName(), + Logind::managerPath(), + QDBusConnection::systemBus()); manager.TerminateSession(QString::number(id)); } @@ -368,7 +381,6 @@ namespace DDM { // No user process execution, so the auth can be thrown away // immediately after use Auth auth(this, user); - auth.user = user; if (!auth.authenticate(password.toLocal8Bit())) { Q_EMIT loginFailed(socket, user); return; @@ -398,15 +410,4 @@ namespace DDM { } Q_EMIT loginFailed(socket, user); } - - void Display::userProcessFinished([[maybe_unused]] int status) { - Auth* auth = qobject_cast(sender()); - - daemonApp->displayManager()->RemoveSession(auth->sessionId); - - auths.removeAll(auth); - delete auth; - - activateSession("dde", 0); - } } diff --git a/src/daemon/Display.h b/src/daemon/Display.h index 2fdb5d5..4f517a5 100644 --- a/src/daemon/Display.h +++ b/src/daemon/Display.h @@ -130,9 +130,6 @@ namespace DDM { /** Socket server for communication with Treeland */ SocketServer *m_socketServer { nullptr }; - - private slots: - void userProcessFinished(int status); }; } diff --git a/src/daemon/Pam.cpp b/src/daemon/Pam.cpp deleted file mode 100644 index 200ac19..0000000 --- a/src/daemon/Pam.cpp +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright (C) 2025 UnionTech Software Technology Co., Ltd. -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Pam.h" - -#include "VirtualTerminal.h" - -#include - -#include - -namespace DDM { - /** PAM conversation function */ - static int converse(int num_msg, - const struct pam_message **msg, - struct pam_response **resp, - void *secret_ptr) { - *resp = (struct pam_response *) calloc(num_msg, sizeof(struct pam_response)); - if (!*resp) - return PAM_BUF_ERR; - - // We only handle secret (password) sending here, which is - // prompt by PAM_PROMPT_ECHO_OFF. Message types (error/info) - // are just logged. - // - // Prompts with PAM_PROMPT_ECHO_ON (most likely asking for - // username) are not expected, since we required username is - // set before. - for (int i = 0; i < num_msg; ++i) { - switch (msg[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: - resp[i]->resp = strdup(*static_cast(secret_ptr)); - resp[i]->resp_retcode = 0; - break; - case PAM_ERROR_MSG: - qWarning() << "[PAM] Error message:" << msg[i]->msg; - resp[i]->resp = nullptr; - resp[i]->resp_retcode = 0; - break; - case PAM_TEXT_INFO: - qInfo() << "[PAM] Info message:" << msg[i]->msg; - resp[i]->resp = nullptr; - resp[i]->resp_retcode = 0; - break; - default: - qCritical("[PAM] Unsupported message style %d: %s", msg[i]->msg_style, msg[i]->msg); - for (int j = 0; j < i; j++) { - free(resp[j]->resp); - resp[j]->resp = nullptr; - } - free(*resp); - *resp = nullptr; - return PAM_CONV_ERR; - } - } - return PAM_SUCCESS; - } - - class PamPrivate : public QObject { - Q_OBJECT - public: - PamPrivate(Pam *parent) - : QObject(parent) - , secretPtr(new const char *) - , conv({ converse, static_cast(secretPtr) }) { - *secretPtr = nullptr; - } - - ~PamPrivate() { - delete secretPtr; - } - - pam_handle_t *handle{ nullptr }; - const char **secretPtr{}; - pam_conv conv{}; - int ret{}; - }; - - Pam::Pam(QObject *parent, QString user) - : QObject(parent) - , user(user) - , d(new PamPrivate(this)) { } - - Pam::~Pam() { - if (!d->handle) - return; - if (sessionOpened) - closeSession(); - d->ret = pam_end(d->handle, d->ret); - if (d->ret != PAM_SUCCESS) - qWarning() << "[PAM] end:" << pam_strerror(d->handle, d->ret); - else - qDebug() << "[PAM] Ended."; - } - - bool Pam::start() { - d->ret = pam_start("ddm", user.toLocal8Bit().constData(), &d->conv, &d->handle); - if (d->ret != PAM_SUCCESS) { - qWarning() << "[PAM] start" << pam_strerror(d->handle, d->ret); - return false; - } - qDebug() << "[PAM] Starting..."; - return true; - } - - bool Pam::authenticate(const QByteArray &secret) { - qDebug() << "[PAM] Authenticating user" << user; - - // Set the secret, authenticate, then clear the secret - // immediately to avoid leak - *d->secretPtr = secret.constData(); - d->ret = pam_authenticate(d->handle, 0); - *d->secretPtr = nullptr; - - if (d->ret != PAM_SUCCESS) { - qWarning() << "[PAM] authenticate:" << pam_strerror(d->handle, d->ret); - return false; - } - qDebug() << "[PAM] Authenticated."; - - d->ret = pam_acct_mgmt(d->handle, 0); - if (d->ret != PAM_SUCCESS) { - qWarning() << "[PAM] acct_mgmt:" << pam_strerror(d->handle, d->ret); - return false; - } - return true; - } - -#define CHECK_RET_OPEN \ - if (d->ret != PAM_SUCCESS) { \ - qWarning() << "[PAM] openSession:" << pam_strerror(d->handle, d->ret); \ - return std::nullopt; \ - } - std::optional Pam::openSession(const QProcessEnvironment &sessionEnv) { - qDebug() << "[PAM] Opening session for user" << user; - - d->ret = pam_setcred(d->handle, PAM_ESTABLISH_CRED); - CHECK_RET_OPEN - - // Set PAM_TTY - QString tty = VirtualTerminal::path(sessionEnv.value(QStringLiteral("XDG_VTNR")).toInt()); - d->ret = pam_set_item(d->handle, PAM_TTY, qPrintable(tty)); - CHECK_RET_OPEN - - // Set PAM_XDISPLAY - QString display = sessionEnv.value(QStringLiteral("DISPLAY")); - if (!display.isEmpty()) { - d->ret = pam_set_item(d->handle, PAM_XDISPLAY, qPrintable(display)); - CHECK_RET_OPEN - } - - // Insert environments into new session - for (const QString &s : sessionEnv.toStringList()) { - d->ret = pam_putenv(d->handle, qPrintable(s)); - CHECK_RET_OPEN - } - - // OPEN!!! - d->ret = pam_open_session(d->handle, 0); - CHECK_RET_OPEN - - qDebug() << "[PAM] Session opened."; - sessionOpened = true; - - // Retrieve env vars in new session, which contain XDG_SESSION_ID we need. - QProcessEnvironment env; - char **envlist = pam_getenvlist(d->handle); - if (envlist) { - for (int i = 0; envlist[i] != nullptr; ++i) { - QString str = QString::fromLocal8Bit(envlist[i]); - int equalPos = str.indexOf('='); - if (equalPos != -1) - env.insert(str.left(equalPos), str.mid(equalPos + 1)); - free(envlist[i]); - } - free(envlist); - } - return env; - } - -#define CHECK_RET_CLOSE \ - if (d->ret != PAM_SUCCESS) { \ - qWarning() << "[PAM] closeSession:" << pam_strerror(d->handle, d->ret); \ - return false; \ - } - bool Pam::closeSession() { - qDebug() << "[PAM] Closing session for user" << user; - - d->ret = pam_close_session(d->handle, 0); - CHECK_RET_CLOSE - - sessionOpened = false; - d->ret = pam_setcred(d->handle, PAM_DELETE_CRED); - CHECK_RET_CLOSE - - qDebug() << "[PAM] Session closed."; - return true; - } - -} // namespace DDM - -#include "Pam.moc" diff --git a/src/daemon/Pam.h b/src/daemon/Pam.h deleted file mode 100644 index a0aecbf..0000000 --- a/src/daemon/Pam.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2025 UnionTech Software Technology Co., Ltd. -// SPDX-License-Identifier: GPL-2.0-or-later - -#ifndef Pam_H -#define Pam_H - -#include -#include - -#include - -namespace DDM { - class PamPrivate; - - /** - * PAM authenticate module. - * - * The `user' property must be set before calling start(). - * To validate user's secret, call start() then authenticate(). - * To open a session, call openSession() after authenticate(). - * Existing opened session will be closed automatically on destruction, - * but you can also close it manually with closeSession(). - */ - class Pam : public QObject { - Q_OBJECT - public: - Pam(QObject *parent = nullptr, QString user = QString()); - ~Pam(); - - /** - * Start PAM transaction - * @return true on success, false on failure - */ - bool start(); - /** - * Authenticate user with given secret (e.g. password) - * @return true on success, false on failure - */ - bool authenticate(const QByteArray &secret); - /** - * Open PAM session with given environment variables - * @return Environment variables set by PAM modules on success, std::nullopt on failure - */ - std::optional openSession(const QProcessEnvironment &sessionEnv); - /** - * Close PAM session - * @return true on success, false on failure - */ - bool closeSession(); - - /** Username. Must be set before calling start() */ - QString user{}; - /** A boolean value indicating whether a session is opened with this PAM handle */ - bool sessionOpened{ false }; - - private: - PamPrivate *d{ nullptr }; - }; -} // namespace DDM - -#endif // Pam_H diff --git a/src/daemon/UserSession.cpp b/src/daemon/UserSession.cpp index d8982b4..21f356d 100644 --- a/src/daemon/UserSession.cpp +++ b/src/daemon/UserSession.cpp @@ -22,11 +22,10 @@ #include #include +#include "Auth.h" #include "Configuration.h" -#include "DaemonApp.h" #include "TreelandConnector.h" #include "UserSession.h" -#include "Auth.h" #include "VirtualTerminal.h" #include "XAuth.h" @@ -50,7 +49,9 @@ namespace DDM { setChildProcessModifier(std::bind(&UserSession::childModifier, this)); } - void UserSession::start(const QString &command, Display::DisplayServerType type, const QByteArray &cookie) { + void UserSession::start(const QString &command, + Display::DisplayServerType type, + const QByteArray &cookie) { QProcessEnvironment env = processEnvironment(); switch (type) { @@ -127,16 +128,19 @@ namespace DDM { } void UserSession::childModifier() { - // Session type - QString sessionType = processEnvironment().value(QStringLiteral("XDG_SESSION_TYPE")); - const bool waylandUserSession = sessionType == QLatin1String("wayland"); + // Open session + Auth *auth = qobject_cast(parent()); + char **env = auth->openSessionInternal(processEnvironment()); + if (!env) { + qCritical() << "Failed to open PAM session in user session process"; + _exit(1); + } // 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 (waylandUserSession) { + if (auth->type != Display::X11) { // open VT and get the fd - int vtNumber = processEnvironment().value(QStringLiteral("XDG_VTNR")).toInt(); - QString ttyString = VirtualTerminal::path(vtNumber); + QString ttyString = VirtualTerminal::path(auth->tty); int vtFd = ::open(qPrintable(ttyString), O_RDWR | O_NOCTTY); // when this is true we'll take control of the tty @@ -172,6 +176,7 @@ namespace DDM { } } } + VirtualTerminal::jumpToVt(auth->tty, false); // enter Linux namespaces for (const QString &ns: mainConfig.Namespaces.get()) { @@ -189,7 +194,7 @@ namespace DDM { } // switch user - const QByteArray username = qobject_cast(parent())->user.toLocal8Bit(); + const QByteArray username = auth->user.toLocal8Bit(); struct passwd pw; struct passwd *rpw; long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); @@ -286,7 +291,7 @@ namespace DDM { // determine stderr log file based on session type QString sessionLog = QStringLiteral("%1/%2") .arg(QString::fromLocal8Bit(pw.pw_dir)) - .arg(sessionType == QLatin1String("x11") + .arg(auth->type == Display::X11 ? mainConfig.X11.SessionLogFile.get() : mainConfig.Wayland.SessionLogFile.get()); @@ -313,5 +318,17 @@ namespace DDM { } else { qWarning() << "Could not redirect stdout"; } + + // We execve manually, to be able to pass the PAM environment + QString program = this->program(); + QStringList args = this->arguments(); + QList c_args; + c_args.push_back(strdup(qPrintable(program))); + for (const QString &arg : args) + c_args.push_back(strdup(qPrintable(arg))); + c_args.push_back(nullptr); + execve(qPrintable(program), c_args.data(), env); + qCritical() << "execve(" << program << ") failed:" << strerror(errno); + exit(1); } } diff --git a/src/daemon/UserSession.h b/src/daemon/UserSession.h index 1e35f53..b04d353 100644 --- a/src/daemon/UserSession.h +++ b/src/daemon/UserSession.h @@ -29,11 +29,7 @@ #include "Display.h" namespace DDM { - class Auth; - class XOrgUserHelper; - class WaylandHelper; - class UserSession : public QProcess - { + class UserSession : public QProcess { Q_OBJECT public: explicit UserSession(Auth *parent); @@ -43,11 +39,6 @@ namespace DDM { const QByteArray &cookie = QByteArray()); void stop(); - /** - * Needed for getting the PID of a finished UserSession and calling HelperApp::utmpLogout - */ - qint64 cachedProcessId = -1; - private: // Don't call it directly, it will be invoked by the child process only void childModifier();