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();