quickshell/src/services/pam/qml.cpp
outfoxxed e89035b18c
service/pam: move pam execution to subprocess to allow killing it
Many pam modules can't be aborted well without this.
2024-06-18 03:29:25 -07:00

232 lines
5.9 KiB
C++

#include "qml.hpp"
#include <utility>
#include <pwd.h>
#include <qdir.h>
#include <qfileinfo.h>
#include <qlogging.h>
#include <qobject.h>
#include <qqmlcontext.h>
#include <qqmlengine.h>
#include <qtmetamacros.h>
#include <unistd.h>
#include "conversation.hpp"
void PamContext::componentComplete() {
this->postInit = true;
if (this->mTargetActive) {
this->startConversation();
}
}
void PamContext::startConversation() {
if (!this->postInit || this->conversation != nullptr) return;
QString user;
{
auto configDirInfo = QFileInfo(this->mConfigDirectory);
if (!configDirInfo.isDir()) {
qCritical() << "Cannot start" << this << "because specified config directory"
<< this->mConfigDirectory << "is not a directory.";
this->mTargetActive = false;
return;
}
auto configFilePath = QDir(this->mConfigDirectory).filePath(this->mConfig);
auto configFileInfo = QFileInfo(configFilePath);
if (!configFileInfo.isFile()) {
qCritical() << "Cannot start" << this << "because specified config file" << configFilePath
<< "is not a file.";
this->mTargetActive = false;
return;
}
auto pwuidbufSize = sysconf(_SC_GETPW_R_SIZE_MAX);
if (pwuidbufSize == -1) pwuidbufSize = 8192;
char pwuidbuf[pwuidbufSize]; // NOLINT
passwd pwuid {};
passwd* pwuidResult = nullptr;
if (this->mUser.isEmpty()) {
auto r = getpwuid_r(getuid(), &pwuid, pwuidbuf, pwuidbufSize, &pwuidResult);
if (pwuidResult == nullptr) {
qCritical() << "Cannot start" << this << "due to error in getpwuid_r: " << r;
this->mTargetActive = false;
return;
}
user = pwuid.pw_name;
} else {
auto r = getpwnam_r(
this->mUser.toStdString().c_str(),
&pwuid,
pwuidbuf,
pwuidbufSize,
&pwuidResult
);
if (pwuidResult == nullptr) {
if (r == 0) {
qCritical() << "Cannot start" << this
<< "because specified user was not found: " << this->mUser;
} else {
qCritical() << "Cannot start" << this << "due to error in getpwnam_r: " << r;
}
this->mTargetActive = false;
return;
}
user = pwuid.pw_name;
}
}
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->mConfigDirectory, this->mConfig, user);
}
void PamContext::abortConversation() {
if (this->conversation == nullptr) return;
this->mTargetActive = false;
QObject::disconnect(this->conversation, nullptr, this, nullptr);
this->conversation->deleteLater();
this->conversation = nullptr;
emit this->activeChanged();
if (!this->mMessage.isEmpty()) {
this->mMessage.clear();
emit this->messageChanged();
}
if (this->mMessageIsError) {
this->mMessageIsError = false;
emit this->messageIsErrorChanged();
}
if (this->mIsResponseRequired) {
this->mIsResponseRequired = false;
emit this->responseRequiredChanged();
}
}
void PamContext::respond(const QString& response) {
if (this->isActive() && this->mIsResponseRequired) {
this->conversation->respond(response);
} else {
qWarning() << "PamContext response was ignored as this context does not require one.";
}
}
bool PamContext::start() {
this->setActive(true);
return this->isActive();
}
void PamContext::abort() { this->setActive(false); }
bool PamContext::isActive() const { return this->conversation != nullptr; }
void PamContext::setActive(bool active) {
if (active == this->mTargetActive) return;
this->mTargetActive = active;
if (active) this->startConversation();
else this->abortConversation();
}
QString PamContext::config() const { return this->mConfig; }
void PamContext::setConfig(QString config) {
if (config == this->mConfig) return;
if (this->isActive()) {
qCritical() << "Cannot set config on PamContext while it is active.";
return;
}
this->mConfig = std::move(config);
emit this->configChanged();
}
QString PamContext::configDirectory() const { return this->mConfigDirectory; }
void PamContext::setConfigDirectory(QString configDirectory) {
if (configDirectory == this->mConfigDirectory) return;
if (this->isActive()) {
qCritical() << "Cannot set configDirectory on PamContext while it is active.";
return;
}
auto* context = QQmlEngine::contextForObject(this);
if (context != nullptr) {
configDirectory = context->resolvedUrl(configDirectory).path();
}
this->mConfigDirectory = std::move(configDirectory);
emit this->configDirectoryChanged();
}
QString PamContext::user() const { return this->mUser; }
void PamContext::setUser(QString user) {
if (user == this->mUser) return;
if (this->isActive()) {
qCritical() << "Cannot set user on PamContext while it is active.";
return;
}
this->mUser = std::move(user);
emit this->userChanged();
}
QString PamContext::message() const { return this->mMessage; }
bool PamContext::messageIsError() const { return this->mMessageIsError; }
bool PamContext::isResponseRequired() const { return this->mIsResponseRequired; }
void PamContext::onCompleted(PamResult::Enum result) {
this->abortConversation();
emit this->completed(result);
}
void PamContext::onError(PamError::Enum error) {
this->abortConversation();
emit this->error(error);
emit this->completed(PamResult::Error);
}
void PamContext::onMessage(
QString message,
bool messageChanged,
bool isError,
bool responseRequired
) {
if (messageChanged) {
if (message != this->mMessage) {
this->mMessage = std::move(message);
emit this->messageChanged();
}
if (isError != this->mMessageIsError) {
this->mMessageIsError = isError;
emit this->messageIsErrorChanged();
}
}
if (responseRequired != this->mIsResponseRequired) {
this->mIsResponseRequired = responseRequired;
emit this->responseRequiredChanged();
}
emit this->pamMessage();
}