quickshell/src/services/pam/subprocess.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

209 lines
5.6 KiB
C++

#include "subprocess.hpp"
#include <array>
#include <ostream>
#include <string>
#include <qlogging.h>
#include <qloggingcategory.h>
#include <qstring.h>
#include <sched.h>
#include <security/_pam_types.h>
#include <security/pam_appl.h>
#include <unistd.h>
#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<int, 2>();
auto fromSubprocess = std::array<int, 2>();
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<char*>(&eventType), // NOLINT
sizeof(PamIpcEvent)
);
if (!ok) goto fail;
ok = this->pipes.writeBytes(
reinterpret_cast<char*>(&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<PamSubprocess*>(appdata);
// freed by libc so must be alloc'd by it.
auto* responses = static_cast<pam_response*>(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<char*>(&eventType), // NOLINT
sizeof(PamIpcEvent)
);
if (!ok) goto fail;
ok = delegate->pipes.writeBytes(
reinterpret_cast<const char*>(&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<int>(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);
}