From e89035b18c80d6514068403b8fd9bd89e07827f5 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Tue, 18 Jun 2024 03:29:25 -0700 Subject: [PATCH] service/pam: move pam execution to subprocess to allow killing it Many pam modules can't be aborted well without this. --- .clang-tidy | 1 + src/services/pam/CMakeLists.txt | 2 + src/services/pam/conversation.cpp | 215 ++++++++++++------------------ src/services/pam/conversation.hpp | 63 ++++----- src/services/pam/ipc.cpp | 69 ++++++++++ src/services/pam/ipc.hpp | 43 ++++++ src/services/pam/qml.cpp | 16 +-- src/services/pam/qml.hpp | 4 +- src/services/pam/subprocess.cpp | 209 +++++++++++++++++++++++++++++ src/services/pam/subprocess.hpp | 31 +++++ 10 files changed, 480 insertions(+), 173 deletions(-) create mode 100644 src/services/pam/ipc.cpp create mode 100644 src/services/pam/ipc.hpp create mode 100644 src/services/pam/subprocess.cpp create mode 100644 src/services/pam/subprocess.hpp diff --git a/.clang-tidy b/.clang-tidy index 41de1fd..19c3547 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -28,6 +28,7 @@ Checks: > -modernize-return-braced-init-list, -modernize-use-trailing-return-type, performance-*, + -performance-avoid-endl, portability-std-allocator-const, readability-*, -readability-function-cognitive-complexity, diff --git a/src/services/pam/CMakeLists.txt b/src/services/pam/CMakeLists.txt index 1a7b29b..3de9b5d 100644 --- a/src/services/pam/CMakeLists.txt +++ b/src/services/pam/CMakeLists.txt @@ -3,6 +3,8 @@ qt_add_library(quickshell-service-pam STATIC qml.cpp conversation.cpp + ipc.cpp + subprocess.cpp ) qt_add_qml_module(quickshell-service-pam URI Quickshell.Services.Pam diff --git a/src/services/pam/conversation.cpp b/src/services/pam/conversation.cpp index d73c426..a06ee88 100644 --- a/src/services/pam/conversation.cpp +++ b/src/services/pam/conversation.cpp @@ -1,21 +1,22 @@ #include "conversation.hpp" -#include #include #include -#include #include +#include #include #include -#include -#include +#include + +#include "ipc.hpp" Q_LOGGING_CATEGORY(logPam, "quickshell.service.pam", QtWarningMsg); QString PamError::toString(PamError::Enum value) { switch (value) { - case ConnectionFailed: return "Failed to connect to pam"; + case StartFailed: return "Failed to start the PAM session"; case TryAuthFailed: return "Failed to try authenticating"; + case InternalError: return "Internal error occurred"; default: return "Invalid error"; } } @@ -26,160 +27,116 @@ QString PamResult::toString(PamResult::Enum value) { case Failed: return "Failed"; case Error: return "Error occurred while authenticating"; case MaxTries: return "The authentication method has no more attempts available"; - // case Expired: return "The account has expired"; - // case PermissionDenied: return "Permission denied"; default: return "Invalid result"; } } -void PamConversation::run() { - auto conv = pam_conv { - .conv = &PamConversation::conversation, - .appdata_ptr = this, - }; +PamConversation::~PamConversation() { this->abort(); } - pam_handle_t* handle = nullptr; - - qCInfo(logPam) << this << "Starting pam session for user" << this->user << "with config" - << this->config << "in configdir" << this->configDir; - - auto result = pam_start_confdir( - this->config.toStdString().c_str(), - this->user.toStdString().c_str(), - &conv, - this->configDir.toStdString().c_str(), - &handle - ); - - if (result != PAM_SUCCESS) { - qCCritical(logPam) << this << "Unable to start pam conversation with error" - << QString(pam_strerror(handle, result)); - emit this->error(PamError::ConnectionFailed); - this->deleteLater(); +void PamConversation::start(const QString& configDir, const QString& config, const QString& user) { + this->childPid = PamConversation::createSubprocess(&this->pipes, configDir, config, user); + if (this->childPid == 0) { + qCCritical(logPam) << "Failed to create pam subprocess."; + emit this->error(PamError::InternalError); return; } - result = pam_authenticate(handle, 0); - - // Seems to require root and quickshell should not run as root. - // if (result == PAM_SUCCESS) { - // result = pam_acct_mgmt(handle, 0); - // } - - switch (result) { - case PAM_SUCCESS: - qCInfo(logPam) << this << "ended with successful authentication."; - emit this->completed(PamResult::Success); - break; - case PAM_AUTH_ERR: - qCInfo(logPam) << this << "ended with failed authentication."; - emit this->completed(PamResult::Failed); - break; - case PAM_MAXTRIES: - qCInfo(logPam) << this << "ended with failure: max tries."; - emit this->completed(PamResult::MaxTries); - break; - /*case PAM_ACCT_EXPIRED: - qCInfo(logPam) << this << "ended with failure: account expiration."; - emit this->completed(PamResult::Expired); - break; - case PAM_PERM_DENIED: - qCInfo(logPam) << this << "ended with failure: permission denied."; - emit this->completed(PamResult::PermissionDenied); - break;*/ - default: - qCCritical(logPam) << this << "ended with error:" << QString(pam_strerror(handle, result)); - emit this->error(PamError::TryAuthFailed); - break; - } - - result = pam_end(handle, result); - if (result != PAM_SUCCESS) { - qCCritical(logPam) << this << "Failed to end pam conversation with error code" - << QString(pam_strerror(handle, result)); - } - - this->deleteLater(); + QObject::connect(&this->notifier, &QSocketNotifier::activated, this, &PamConversation::onMessage); + this->notifier.setSocket(this->pipes.fdIn); + this->notifier.setEnabled(true); } void PamConversation::abort() { - qCDebug(logPam) << "Abort requested for" << this; - auto locker = QMutexLocker(&this->wakeMutex); - this->mAbort = true; - this->waker.wakeOne(); + if (this->childPid != 0) { + qCDebug(logPam) << "Killing subprocess for" << this; + kill(this->childPid, SIGKILL); // NOLINT (include) + waitpid(this->childPid, nullptr, 0); + this->childPid = 0; + } } -void PamConversation::respond(QString response) { - qCDebug(logPam) << "Set response for" << this; - auto locker = QMutexLocker(&this->wakeMutex); - this->response = std::move(response); - this->hasResponse = true; - this->waker.wakeOne(); +void PamConversation::internalError() { + if (this->childPid != 0) { + qCDebug(logPam) << "Killing subprocess for" << this; + kill(this->childPid, SIGKILL); // NOLINT (include) + waitpid(this->childPid, nullptr, 0); + this->childPid = 0; + emit this->error(PamError::InternalError); + } } -int PamConversation::conversation( - int msgCount, - const pam_message** msgArray, - pam_response** responseArray, - void* appdata -) { - auto* delegate = static_cast(appdata); +void PamConversation::respond(const QString& response) { + qCDebug(logPam) << "Sending response for" << this; + if (!this->pipes.writeString(response.toStdString())) { + qCCritical(logPam) << "Failed to write response to subprocess."; + this->internalError(); + } +} +void PamConversation::onMessage() { { - auto locker = QMutexLocker(&delegate->wakeMutex); - if (delegate->mAbort) { - return PAM_ERROR_MSG; - } - } + qCDebug(logPam) << "Got message from subprocess."; - // freed by libc so must be alloc'd by it. - auto* responses = static_cast(calloc(msgCount, sizeof(pam_response))); // NOLINT + auto type = PamIpcEvent::Exit; - for (auto i = 0; i < msgCount; i++) { - const auto* message = msgArray[i]; // NOLINT - auto& response = responses[i]; // NOLINT + auto ok = this->pipes.readBytes( + reinterpret_cast(&type), // NOLINT + sizeof(PamIpcEvent) + ); - auto msgString = QString(message->msg); - auto messageChanged = true; // message->msg_style != PAM_PROMPT_ECHO_OFF; - auto isError = message->msg_style == PAM_ERROR_MSG; - auto responseRequired = - message->msg_style == PAM_PROMPT_ECHO_OFF || message->msg_style == PAM_PROMPT_ECHO_ON; + if (!ok) goto fail; - qCDebug(logPam) << delegate << "got new message message:" << msgString - << "messageChanged:" << messageChanged << "isError:" << isError - << "responseRequired" << responseRequired; + if (type == PamIpcEvent::Exit) { + auto code = PamIpcExitCode::OtherError; - delegate->hasResponse = false; - emit delegate->message(msgString, messageChanged, isError, responseRequired); + ok = this->pipes.readBytes( + reinterpret_cast(&code), // NOLINT + sizeof(PamIpcExitCode) + ); - { - auto locker = QMutexLocker(&delegate->wakeMutex); + if (!ok) goto fail; - if (delegate->mAbort) { - free(responses); // NOLINT - return PAM_ERROR_MSG; + qCDebug(logPam) << "Subprocess exited with code" << static_cast(code); + + switch (code) { + case PamIpcExitCode::Success: emit this->completed(PamResult::Success); break; + case PamIpcExitCode::AuthFailed: emit this->completed(PamResult::Failed); break; + case PamIpcExitCode::StartFailed: emit this->error(PamError::StartFailed); break; + case PamIpcExitCode::MaxTries: emit this->completed(PamResult::MaxTries); break; + case PamIpcExitCode::PamError: emit this->error(PamError::TryAuthFailed); break; + case PamIpcExitCode::OtherError: emit this->error(PamError::InternalError); break; } - if (responseRequired) { - if (!delegate->hasResponse) { - delegate->waker.wait(locker.mutex()); + waitpid(this->childPid, nullptr, 0); + this->childPid = 0; + } else if (type == PamIpcEvent::Request) { + PamIpcRequestFlags flags {}; - if (delegate->mAbort) { - free(responses); // NOLINT - return PAM_ERROR_MSG; - } - } + ok = this->pipes.readBytes( + reinterpret_cast(&flags), // NOLINT + sizeof(PamIpcRequestFlags) + ); - if (!delegate->hasResponse) { - qCCritical(logPam - ) << "Pam conversation requires response and does not have one. This should not happen."; - } + if (!ok) goto fail; - response.resp = strdup(delegate->response.toStdString().c_str()); // NOLINT (include error) - } + auto message = this->pipes.readString(&ok); + + if (!ok) goto fail; + + this->message( + QString::fromUtf8(message), + /*flags.echo*/ true, + flags.error, + flags.responseRequired + ); + } else { + qCCritical(logPam) << "Unexpected message from subprocess."; + goto fail; } } + return; - *responseArray = responses; - return PAM_SUCCESS; +fail: + qCCritical(logPam) << "Failed to read subprocess request."; + this->internalError(); } diff --git a/src/services/pam/conversation.hpp b/src/services/pam/conversation.hpp index ff58980..9719d16 100644 --- a/src/services/pam/conversation.hpp +++ b/src/services/pam/conversation.hpp @@ -2,13 +2,16 @@ #include -#include +#include #include #include -#include +#include +#include #include -#include -#include + +#include "ipc.hpp" + +Q_DECLARE_LOGGING_CATEGORY(logPam); /// The result of an authentication. class PamResult: public QObject { @@ -26,10 +29,6 @@ public: Error = 2, /// The authentication method ran out of tries and should not be used again. MaxTries = 3, - // The account has expired. - // Expired 4, - // Permission denied. - // PermissionDenied 5, }; Q_ENUM(Enum); @@ -44,52 +43,56 @@ class PamError: public QObject { public: enum Enum { - /// Failed to initiate the pam connection. - ConnectionFailed = 1, + /// Failed to start the pam session. + StartFailed = 1, /// Failed to try to authenticate the user. /// This is not the same as the user failing to authenticate. TryAuthFailed = 2, + /// An error occurred inside quickshell's pam interface. + InternalError = 3, }; Q_ENUM(Enum); Q_INVOKABLE static QString toString(PamError::Enum value); }; -class PamConversation: public QThread { +// PAM has no way to abort a running module except when it sends a message, +// meaning aborts for things like fingerprint scanners +// and hardware keys don't actually work without aborting the process... +// so we have a subprocess. +class PamConversation: public QObject { Q_OBJECT; public: - explicit PamConversation(QString config, QString configDir, QString user) - : config(std::move(config)) - , configDir(std::move(configDir)) - , user(std::move(user)) {} + explicit PamConversation(QObject* parent): QObject(parent) {} + ~PamConversation() override; + Q_DISABLE_COPY_MOVE(PamConversation); public: - void run() override; + void start(const QString& configDir, const QString& config, const QString& user); void abort(); - void respond(QString response); + void respond(const QString& response); signals: void completed(PamResult::Enum result); void error(PamError::Enum error); void message(QString message, bool messageChanged, bool isError, bool responseRequired); +private slots: + void onMessage(); + private: - static int conversation( - int msgCount, - const pam_message** msgArray, - pam_response** responseArray, - void* appdata + static pid_t createSubprocess( + PamIpcPipes* pipes, + const QString& configDir, + const QString& config, + const QString& user ); - QString config; - QString configDir; - QString user; + void internalError(); - QMutex wakeMutex; - QWaitCondition waker; - bool mAbort = false; - bool hasResponse = false; - QString response; + pid_t childPid = 0; + PamIpcPipes pipes; + QSocketNotifier notifier {QSocketNotifier::Read}; }; diff --git a/src/services/pam/ipc.cpp b/src/services/pam/ipc.cpp new file mode 100644 index 0000000..2b0e00b --- /dev/null +++ b/src/services/pam/ipc.cpp @@ -0,0 +1,69 @@ +#include "ipc.hpp" +#include +#include +#include + +#include + +PamIpcPipes::~PamIpcPipes() { + if (this->fdIn != 0) close(this->fdIn); + if (this->fdOut != 0) close(this->fdOut); +} + +bool PamIpcPipes::readBytes(char* buffer, size_t length) const { + size_t i = 0; + + while (i < length) { + auto count = read(this->fdIn, buffer + i, length - i); // NOLINT + + if (count == -1 || count == 0) { + return false; + } + + i += count; + } + + return true; +} + +bool PamIpcPipes::writeBytes(const char* buffer, size_t length) const { + size_t i = 0; + while (i < length) { + auto count = write(this->fdOut, buffer + i, length - i); // NOLINT + + if (count == -1 || count == 0) { + return false; + } + + i += count; + } + + return true; +} + +std::string PamIpcPipes::readString(bool* ok) const { + if (ok != nullptr) *ok = false; + + uint32_t length = 0; + if (!this->readBytes(reinterpret_cast(&length), sizeof(length))) { // NOLINT + return ""; + } + + char data[length]; // NOLINT + if (!this->readBytes(data, length)) { + return ""; + } + + if (ok != nullptr) *ok = true; + + return std::string(data, length); +} + +bool PamIpcPipes::writeString(const std::string& string) const { + uint32_t length = string.length(); + if (!this->writeBytes(reinterpret_cast(&length), sizeof(length))) { // NOLINT + return false; + } + + return this->writeBytes(string.data(), string.length()); +} diff --git a/src/services/pam/ipc.hpp b/src/services/pam/ipc.hpp new file mode 100644 index 0000000..7bfcc6f --- /dev/null +++ b/src/services/pam/ipc.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +#include + +enum class PamIpcEvent : uint8_t { + Request, + Exit, +}; + +enum class PamIpcExitCode : uint8_t { + Success, + StartFailed, + AuthFailed, + MaxTries, + PamError, + OtherError, +}; + +struct PamIpcRequestFlags { + bool echo; + bool error; + bool responseRequired; +}; + +class PamIpcPipes { +public: + explicit PamIpcPipes() = default; + explicit PamIpcPipes(int fdIn, int fdOut): fdIn(fdIn), fdOut(fdOut) {} + ~PamIpcPipes(); + Q_DISABLE_COPY_MOVE(PamIpcPipes); + + [[nodiscard]] bool readBytes(char* buffer, size_t length) const; + [[nodiscard]] bool writeBytes(const char* buffer, size_t length) const; + [[nodiscard]] std::string readString(bool* ok) const; + [[nodiscard]] bool writeString(const std::string& string) const; + + int fdIn = 0; + int fdOut = 0; +}; diff --git a/src/services/pam/qml.cpp b/src/services/pam/qml.cpp index ee8b45f..c849f35 100644 --- a/src/services/pam/qml.cpp +++ b/src/services/pam/qml.cpp @@ -13,12 +13,6 @@ #include "conversation.hpp" -PamContext::~PamContext() { - if (this->conversation != nullptr && this->conversation->isRunning()) { - this->conversation->abort(); - } -} - void PamContext::componentComplete() { this->postInit = true; @@ -91,12 +85,12 @@ void PamContext::startConversation() { } } - this->conversation = new PamConversation(this->mConfig, this->mConfigDirectory, user); + this->conversation = new PamConversation(this); QObject::connect(this->conversation, &PamConversation::completed, this, &PamContext::onCompleted); QObject::connect(this->conversation, &PamConversation::error, this, &PamContext::onError); QObject::connect(this->conversation, &PamConversation::message, this, &PamContext::onMessage); emit this->activeChanged(); - this->conversation->start(); + this->conversation->start(this->mConfigDirectory, this->mConfig, user); } void PamContext::abortConversation() { @@ -104,7 +98,7 @@ void PamContext::abortConversation() { this->mTargetActive = false; QObject::disconnect(this->conversation, nullptr, this, nullptr); - if (this->conversation->isRunning()) this->conversation->abort(); + this->conversation->deleteLater(); this->conversation = nullptr; emit this->activeChanged(); @@ -124,9 +118,9 @@ void PamContext::abortConversation() { } } -void PamContext::respond(QString response) { +void PamContext::respond(const QString& response) { if (this->isActive() && this->mIsResponseRequired) { - this->conversation->respond(std::move(response)); + this->conversation->respond(response); } else { qWarning() << "PamContext response was ignored as this context does not require one."; } diff --git a/src/services/pam/qml.hpp b/src/services/pam/qml.hpp index 6f9df4d..5d741f8 100644 --- a/src/services/pam/qml.hpp +++ b/src/services/pam/qml.hpp @@ -51,8 +51,6 @@ class PamContext public: explicit PamContext(QObject* parent = nullptr): QObject(parent) {} - ~PamContext() override; - Q_DISABLE_COPY_MOVE(PamContext); void classBegin() override {} void componentComplete() override; @@ -69,7 +67,7 @@ public: /// Respond to pam. /// /// May not be called unless `responseRequired` is true. - Q_INVOKABLE void respond(QString response); + Q_INVOKABLE void respond(const QString& response); [[nodiscard]] bool isActive() const; void setActive(bool active); diff --git a/src/services/pam/subprocess.cpp b/src/services/pam/subprocess.cpp new file mode 100644 index 0000000..276e8b9 --- /dev/null +++ b/src/services/pam/subprocess.cpp @@ -0,0 +1,209 @@ +#include "subprocess.hpp" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "conversation.hpp" +#include "ipc.hpp" + +pid_t PamConversation::createSubprocess( + PamIpcPipes* pipes, + const QString& configDir, + const QString& config, + const QString& user +) { + auto toSubprocess = std::array(); + auto fromSubprocess = std::array(); + + if (pipe(toSubprocess.data()) == -1 || pipe(fromSubprocess.data()) == -1) { + qCDebug(logPam) << "Failed to create pipes for subprocess."; + return 0; + } + + auto* configDirF = strdup(configDir.toStdString().c_str()); // NOLINT (include) + auto* configF = strdup(config.toStdString().c_str()); // NOLINT (include) + auto* userF = strdup(user.toStdString().c_str()); // NOLINT (include) + auto log = logPam().isDebugEnabled(); + + auto pid = fork(); + + if (pid < 0) { + qCDebug(logPam) << "Failed to fork for subprocess."; + } else if (pid == 0) { + close(toSubprocess[1]); // close w + close(fromSubprocess[0]); // close r + + { + auto subprocess = PamSubprocess(log, toSubprocess[0], fromSubprocess[1]); + auto code = subprocess.exec(configDirF, configF, userF); + subprocess.sendCode(code); + } + + free(configDirF); // NOLINT + free(configF); // NOLINT + free(userF); // NOLINT + + // do not do cleanup that may affect the parent + _exit(0); + } else { + close(toSubprocess[0]); // close r + close(fromSubprocess[1]); // close w + + pipes->fdIn = fromSubprocess[0]; + pipes->fdOut = toSubprocess[1]; + + free(configDirF); // NOLINT + free(configF); // NOLINT + free(userF); // NOLINT + + return pid; + } + + return -1; // should never happen but lint +} + +PamIpcExitCode PamSubprocess::exec(const char* configDir, const char* config, const char* user) { + logIf(this->log) << "Waiting for parent confirmation..." << std::endl; + + auto conv = pam_conv { + .conv = &PamSubprocess::conversation, + .appdata_ptr = this, + }; + + pam_handle_t* handle = nullptr; + + logIf(this->log) << "Starting pam session for user \"" << user << "\" with config \"" << config + << "\" in dir \"" << configDir << "\"" << std::endl; + + auto result = pam_start_confdir(config, user, &conv, configDir, &handle); + + if (result != PAM_SUCCESS) { + logIf(true) << "Unable to start pam conversation with error \"" << pam_strerror(handle, result) + << "\" (code " << result << ")" << std::endl; + return PamIpcExitCode::StartFailed; + } + + result = pam_authenticate(handle, 0); + PamIpcExitCode code = PamIpcExitCode::OtherError; + + switch (result) { + case PAM_SUCCESS: + logIf(this->log) << "Authenticated successfully." << std::endl; + code = PamIpcExitCode::Success; + break; + case PAM_AUTH_ERR: + logIf(this->log) << "Failed to authenticate." << std::endl; + code = PamIpcExitCode::AuthFailed; + break; + case PAM_MAXTRIES: + logIf(this->log) << "Failed to authenticate due to hitting max tries." << std::endl; + code = PamIpcExitCode::MaxTries; + break; + default: + logIf(true) << "Error while authenticating: \"" << pam_strerror(handle, result) << "\" (code " + << result << ")" << std::endl; + code = PamIpcExitCode::PamError; + break; + } + + result = pam_end(handle, result); + if (result != PAM_SUCCESS) { + logIf(true) << "Error in pam_end: \"" << pam_strerror(handle, result) << "\" (code " << result + << ")" << std::endl; + } + + return code; +} + +void PamSubprocess::sendCode(PamIpcExitCode code) { + { + auto eventType = PamIpcEvent::Exit; + auto ok = this->pipes.writeBytes( + reinterpret_cast(&eventType), // NOLINT + sizeof(PamIpcEvent) + ); + + if (!ok) goto fail; + + ok = this->pipes.writeBytes( + reinterpret_cast(&code), // NOLINT + sizeof(PamIpcExitCode) + ); + + if (!ok) goto fail; + + return; + } + +fail: + _exit(1); +} + +int PamSubprocess::conversation( + int msgCount, + const pam_message** msgArray, + pam_response** responseArray, + void* appdata +) { + auto* delegate = static_cast(appdata); + + // freed by libc so must be alloc'd by it. + auto* responses = static_cast(calloc(msgCount, sizeof(pam_response))); // NOLINT + + for (auto i = 0; i < msgCount; i++) { + const auto* message = msgArray[i]; // NOLINT + auto& response = responses[i]; // NOLINT + + auto msgString = std::string(message->msg); + auto req = PamIpcRequestFlags { + .echo = message->msg_style != PAM_PROMPT_ECHO_OFF, + .error = message->msg_style == PAM_ERROR_MSG, + .responseRequired = + message->msg_style == PAM_PROMPT_ECHO_OFF || message->msg_style == PAM_PROMPT_ECHO_ON, + }; + + logIf(delegate->log) << "Relaying pam message: \"" << msgString << "\" echo: " << req.echo + << " error: " << req.error << " responseRequired: " << req.responseRequired + << std::endl; + + auto eventType = PamIpcEvent::Request; + auto ok = delegate->pipes.writeBytes( + reinterpret_cast(&eventType), // NOLINT + sizeof(PamIpcEvent) + ); + + if (!ok) goto fail; + + ok = delegate->pipes.writeBytes( + reinterpret_cast(&req), // NOLINT + sizeof(PamIpcRequestFlags) + ); + + if (!ok) goto fail; + if (!delegate->pipes.writeString(msgString)) goto fail; + + if (req.responseRequired) { + auto ok = false; + auto resp = delegate->pipes.readString(&ok); + if (!ok) _exit(static_cast(PamIpcExitCode::OtherError)); + logIf(delegate->log) << "Got response for request."; + + response.resp = strdup(resp.c_str()); // NOLINT (include) + } + } + + *responseArray = responses; + return PAM_SUCCESS; + +fail: + free(responseArray); // NOLINT + _exit(1); +} diff --git a/src/services/pam/subprocess.hpp b/src/services/pam/subprocess.hpp new file mode 100644 index 0000000..11f738f --- /dev/null +++ b/src/services/pam/subprocess.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +#include "ipc.hpp" + +// endls are intentional as it makes debugging much easier when the buffer actually flushes. +// NOLINTBEGIN +#define logIf(log) \ + if (log) std::cout << "quickshell.service.pam.subprocess: " +// NOLINTEND + +class PamSubprocess { +public: + explicit PamSubprocess(bool log, int fdIn, int fdOut): log(log), pipes(fdIn, fdOut) {} + PamIpcExitCode exec(const char* configDir, const char* config, const char* user); + void sendCode(PamIpcExitCode code); + +private: + static int conversation( + int msgCount, + const pam_message** msgArray, + pam_response** responseArray, + void* appdata + ); + + bool log; + PamIpcPipes pipes; +};