forked from quickshell/quickshell
		
	service/pam: move pam execution to subprocess to allow killing it
Many pam modules can't be aborted well without this.
This commit is contained in:
		
							parent
							
								
									b5c8774a79
								
							
						
					
					
						commit
						e89035b18c
					
				
					 10 changed files with 480 additions and 173 deletions
				
			
		| 
						 | 
					@ -28,6 +28,7 @@ Checks: >
 | 
				
			||||||
  -modernize-return-braced-init-list,
 | 
					  -modernize-return-braced-init-list,
 | 
				
			||||||
  -modernize-use-trailing-return-type,
 | 
					  -modernize-use-trailing-return-type,
 | 
				
			||||||
  performance-*,
 | 
					  performance-*,
 | 
				
			||||||
 | 
					  -performance-avoid-endl,
 | 
				
			||||||
  portability-std-allocator-const,
 | 
					  portability-std-allocator-const,
 | 
				
			||||||
  readability-*,
 | 
					  readability-*,
 | 
				
			||||||
  -readability-function-cognitive-complexity,
 | 
					  -readability-function-cognitive-complexity,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,8 @@
 | 
				
			||||||
qt_add_library(quickshell-service-pam STATIC
 | 
					qt_add_library(quickshell-service-pam STATIC
 | 
				
			||||||
	qml.cpp
 | 
						qml.cpp
 | 
				
			||||||
	conversation.cpp
 | 
						conversation.cpp
 | 
				
			||||||
 | 
						ipc.cpp
 | 
				
			||||||
 | 
						subprocess.cpp
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
qt_add_qml_module(quickshell-service-pam
 | 
					qt_add_qml_module(quickshell-service-pam
 | 
				
			||||||
	URI Quickshell.Services.Pam
 | 
						URI Quickshell.Services.Pam
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,22 @@
 | 
				
			||||||
#include "conversation.hpp"
 | 
					#include "conversation.hpp"
 | 
				
			||||||
#include <utility>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qlogging.h>
 | 
					#include <qlogging.h>
 | 
				
			||||||
#include <qloggingcategory.h>
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
#include <qmutex.h>
 | 
					 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qsocketnotifier.h>
 | 
				
			||||||
#include <qstring.h>
 | 
					#include <qstring.h>
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
#include <security/_pam_types.h>
 | 
					#include <sys/wait.h>
 | 
				
			||||||
#include <security/pam_appl.h>
 | 
					
 | 
				
			||||||
 | 
					#include "ipc.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Q_LOGGING_CATEGORY(logPam, "quickshell.service.pam", QtWarningMsg);
 | 
					Q_LOGGING_CATEGORY(logPam, "quickshell.service.pam", QtWarningMsg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QString PamError::toString(PamError::Enum value) {
 | 
					QString PamError::toString(PamError::Enum value) {
 | 
				
			||||||
	switch (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 TryAuthFailed: return "Failed to try authenticating";
 | 
				
			||||||
 | 
						case InternalError: return "Internal error occurred";
 | 
				
			||||||
	default: return "Invalid error";
 | 
						default: return "Invalid error";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -26,160 +27,116 @@ QString PamResult::toString(PamResult::Enum value) {
 | 
				
			||||||
	case Failed: return "Failed";
 | 
						case Failed: return "Failed";
 | 
				
			||||||
	case Error: return "Error occurred while authenticating";
 | 
						case Error: return "Error occurred while authenticating";
 | 
				
			||||||
	case MaxTries: return "The authentication method has no more attempts available";
 | 
						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";
 | 
						default: return "Invalid result";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void PamConversation::run() {
 | 
					PamConversation::~PamConversation() { this->abort(); }
 | 
				
			||||||
	auto conv = pam_conv {
 | 
					 | 
				
			||||||
	    .conv = &PamConversation::conversation,
 | 
					 | 
				
			||||||
	    .appdata_ptr = this,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pam_handle_t* handle = nullptr;
 | 
					void PamConversation::start(const QString& configDir, const QString& config, const QString& user) {
 | 
				
			||||||
 | 
						this->childPid = PamConversation::createSubprocess(&this->pipes, configDir, config, user);
 | 
				
			||||||
	qCInfo(logPam) << this << "Starting pam session for user" << this->user << "with config"
 | 
						if (this->childPid == 0) {
 | 
				
			||||||
	               << this->config << "in configdir" << this->configDir;
 | 
							qCCritical(logPam) << "Failed to create pam subprocess.";
 | 
				
			||||||
 | 
							emit this->error(PamError::InternalError);
 | 
				
			||||||
	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();
 | 
					 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	result = pam_authenticate(handle, 0);
 | 
						QObject::connect(&this->notifier, &QSocketNotifier::activated, this, &PamConversation::onMessage);
 | 
				
			||||||
 | 
						this->notifier.setSocket(this->pipes.fdIn);
 | 
				
			||||||
	// Seems to require root and quickshell should not run as root.
 | 
						this->notifier.setEnabled(true);
 | 
				
			||||||
	// 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();
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void PamConversation::abort() {
 | 
					void PamConversation::abort() {
 | 
				
			||||||
	qCDebug(logPam) << "Abort requested for" << this;
 | 
						if (this->childPid != 0) {
 | 
				
			||||||
	auto locker = QMutexLocker(&this->wakeMutex);
 | 
							qCDebug(logPam) << "Killing subprocess for" << this;
 | 
				
			||||||
	this->mAbort = true;
 | 
							kill(this->childPid, SIGKILL); // NOLINT (include)
 | 
				
			||||||
	this->waker.wakeOne();
 | 
							waitpid(this->childPid, nullptr, 0);
 | 
				
			||||||
 | 
							this->childPid = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void PamConversation::respond(QString response) {
 | 
					void PamConversation::internalError() {
 | 
				
			||||||
	qCDebug(logPam) << "Set response for" << this;
 | 
						if (this->childPid != 0) {
 | 
				
			||||||
	auto locker = QMutexLocker(&this->wakeMutex);
 | 
							qCDebug(logPam) << "Killing subprocess for" << this;
 | 
				
			||||||
	this->response = std::move(response);
 | 
							kill(this->childPid, SIGKILL); // NOLINT (include)
 | 
				
			||||||
	this->hasResponse = true;
 | 
							waitpid(this->childPid, nullptr, 0);
 | 
				
			||||||
	this->waker.wakeOne();
 | 
							this->childPid = 0;
 | 
				
			||||||
 | 
							emit this->error(PamError::InternalError);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int PamConversation::conversation(
 | 
					void PamConversation::respond(const QString& response) {
 | 
				
			||||||
    int msgCount,
 | 
						qCDebug(logPam) << "Sending response for" << this;
 | 
				
			||||||
    const pam_message** msgArray,
 | 
						if (!this->pipes.writeString(response.toStdString())) {
 | 
				
			||||||
    pam_response** responseArray,
 | 
							qCCritical(logPam) << "Failed to write response to subprocess.";
 | 
				
			||||||
    void* appdata
 | 
							this->internalError();
 | 
				
			||||||
) {
 | 
						}
 | 
				
			||||||
	auto* delegate = static_cast<PamConversation*>(appdata);
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PamConversation::onMessage() {
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		auto locker = QMutexLocker(&delegate->wakeMutex);
 | 
							qCDebug(logPam) << "Got message from subprocess.";
 | 
				
			||||||
		if (delegate->mAbort) {
 | 
					
 | 
				
			||||||
			return PAM_ERROR_MSG;
 | 
							auto type = PamIpcEvent::Exit;
 | 
				
			||||||
		}
 | 
					
 | 
				
			||||||
 | 
							auto ok = this->pipes.readBytes(
 | 
				
			||||||
 | 
							    reinterpret_cast<char*>(&type), // NOLINT
 | 
				
			||||||
 | 
							    sizeof(PamIpcEvent)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!ok) goto fail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (type == PamIpcEvent::Exit) {
 | 
				
			||||||
 | 
								auto code = PamIpcExitCode::OtherError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ok = this->pipes.readBytes(
 | 
				
			||||||
 | 
								    reinterpret_cast<char*>(&code), // NOLINT
 | 
				
			||||||
 | 
								    sizeof(PamIpcExitCode)
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!ok) goto fail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								qCDebug(logPam) << "Subprocess exited with code" << static_cast<int>(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;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// freed by libc so must be alloc'd by it.
 | 
								waitpid(this->childPid, nullptr, 0);
 | 
				
			||||||
	auto* responses = static_cast<pam_response*>(calloc(msgCount, sizeof(pam_response))); // NOLINT
 | 
								this->childPid = 0;
 | 
				
			||||||
 | 
							} else if (type == PamIpcEvent::Request) {
 | 
				
			||||||
 | 
								PamIpcRequestFlags flags {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (auto i = 0; i < msgCount; i++) {
 | 
								ok = this->pipes.readBytes(
 | 
				
			||||||
		const auto* message = msgArray[i]; // NOLINT
 | 
								    reinterpret_cast<char*>(&flags), // NOLINT
 | 
				
			||||||
		auto& response = responses[i];     // NOLINT
 | 
								    sizeof(PamIpcRequestFlags)
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		auto msgString = QString(message->msg);
 | 
								if (!ok) goto fail;
 | 
				
			||||||
		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;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		qCDebug(logPam) << delegate << "got new message message:" << msgString
 | 
								auto message = this->pipes.readString(&ok);
 | 
				
			||||||
		                << "messageChanged:" << messageChanged << "isError:" << isError
 | 
					 | 
				
			||||||
		                << "responseRequired" << responseRequired;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		delegate->hasResponse = false;
 | 
								if (!ok) goto fail;
 | 
				
			||||||
		emit delegate->message(msgString, messageChanged, isError, responseRequired);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		{
 | 
								this->message(
 | 
				
			||||||
			auto locker = QMutexLocker(&delegate->wakeMutex);
 | 
								    QString::fromUtf8(message),
 | 
				
			||||||
 | 
								    /*flags.echo*/ true,
 | 
				
			||||||
			if (delegate->mAbort) {
 | 
								    flags.error,
 | 
				
			||||||
				free(responses); // NOLINT
 | 
								    flags.responseRequired
 | 
				
			||||||
				return PAM_ERROR_MSG;
 | 
								);
 | 
				
			||||||
			}
 | 
							} else {
 | 
				
			||||||
 | 
								qCCritical(logPam) << "Unexpected message from subprocess.";
 | 
				
			||||||
			if (responseRequired) {
 | 
								goto fail;
 | 
				
			||||||
				if (!delegate->hasResponse) {
 | 
					 | 
				
			||||||
					delegate->waker.wait(locker.mutex());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					if (delegate->mAbort) {
 | 
					 | 
				
			||||||
						free(responses); // NOLINT
 | 
					 | 
				
			||||||
						return PAM_ERROR_MSG;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				if (!delegate->hasResponse) {
 | 
					fail:
 | 
				
			||||||
					qCCritical(logPam
 | 
						qCCritical(logPam) << "Failed to read subprocess request.";
 | 
				
			||||||
					) << "Pam conversation requires response and does not have one. This should not happen.";
 | 
						this->internalError();
 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				response.resp = strdup(delegate->response.toStdString().c_str()); // NOLINT (include error)
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	*responseArray = responses;
 | 
					 | 
				
			||||||
	return PAM_SUCCESS;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,13 +2,16 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <utility>
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <qmutex.h>
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
#include <qobject.h>
 | 
					#include <qobject.h>
 | 
				
			||||||
#include <qqmlintegration.h>
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
#include <qthread.h>
 | 
					#include <qsocketnotifier.h>
 | 
				
			||||||
 | 
					#include <qtclasshelpermacros.h>
 | 
				
			||||||
#include <qtmetamacros.h>
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
#include <qwaitcondition.h>
 | 
					
 | 
				
			||||||
#include <security/pam_appl.h>
 | 
					#include "ipc.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Q_DECLARE_LOGGING_CATEGORY(logPam);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// The result of an authentication.
 | 
					/// The result of an authentication.
 | 
				
			||||||
class PamResult: public QObject {
 | 
					class PamResult: public QObject {
 | 
				
			||||||
| 
						 | 
					@ -26,10 +29,6 @@ public:
 | 
				
			||||||
		Error = 2,
 | 
							Error = 2,
 | 
				
			||||||
		/// The authentication method ran out of tries and should not be used again.
 | 
							/// The authentication method ran out of tries and should not be used again.
 | 
				
			||||||
		MaxTries = 3,
 | 
							MaxTries = 3,
 | 
				
			||||||
		// The account has expired.
 | 
					 | 
				
			||||||
		// Expired  4,
 | 
					 | 
				
			||||||
		// Permission denied.
 | 
					 | 
				
			||||||
		// PermissionDenied  5,
 | 
					 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	Q_ENUM(Enum);
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,52 +43,56 @@ class PamError: public QObject {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	enum Enum {
 | 
						enum Enum {
 | 
				
			||||||
		/// Failed to initiate the pam connection.
 | 
							/// Failed to start the pam session.
 | 
				
			||||||
		ConnectionFailed = 1,
 | 
							StartFailed = 1,
 | 
				
			||||||
		/// Failed to try to authenticate the user.
 | 
							/// Failed to try to authenticate the user.
 | 
				
			||||||
		/// This is not the same as the user failing to authenticate.
 | 
							/// This is not the same as the user failing to authenticate.
 | 
				
			||||||
		TryAuthFailed = 2,
 | 
							TryAuthFailed = 2,
 | 
				
			||||||
 | 
							/// An error occurred inside quickshell's pam interface.
 | 
				
			||||||
 | 
							InternalError = 3,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
	Q_ENUM(Enum);
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Q_INVOKABLE static QString toString(PamError::Enum value);
 | 
						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;
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit PamConversation(QString config, QString configDir, QString user)
 | 
						explicit PamConversation(QObject* parent): QObject(parent) {}
 | 
				
			||||||
	    : config(std::move(config))
 | 
						~PamConversation() override;
 | 
				
			||||||
	    , configDir(std::move(configDir))
 | 
						Q_DISABLE_COPY_MOVE(PamConversation);
 | 
				
			||||||
	    , user(std::move(user)) {}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	void run() override;
 | 
						void start(const QString& configDir, const QString& config, const QString& user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void abort();
 | 
						void abort();
 | 
				
			||||||
	void respond(QString response);
 | 
						void respond(const QString& response);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
signals:
 | 
					signals:
 | 
				
			||||||
	void completed(PamResult::Enum result);
 | 
						void completed(PamResult::Enum result);
 | 
				
			||||||
	void error(PamError::Enum error);
 | 
						void error(PamError::Enum error);
 | 
				
			||||||
	void message(QString message, bool messageChanged, bool isError, bool responseRequired);
 | 
						void message(QString message, bool messageChanged, bool isError, bool responseRequired);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void onMessage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
	static int conversation(
 | 
						static pid_t createSubprocess(
 | 
				
			||||||
	    int msgCount,
 | 
						    PamIpcPipes* pipes,
 | 
				
			||||||
	    const pam_message** msgArray,
 | 
						    const QString& configDir,
 | 
				
			||||||
	    pam_response** responseArray,
 | 
						    const QString& config,
 | 
				
			||||||
	    void* appdata
 | 
						    const QString& user
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString config;
 | 
						void internalError();
 | 
				
			||||||
	QString configDir;
 | 
					 | 
				
			||||||
	QString user;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QMutex wakeMutex;
 | 
						pid_t childPid = 0;
 | 
				
			||||||
	QWaitCondition waker;
 | 
						PamIpcPipes pipes;
 | 
				
			||||||
	bool mAbort = false;
 | 
						QSocketNotifier notifier {QSocketNotifier::Read};
 | 
				
			||||||
	bool hasResponse = false;
 | 
					 | 
				
			||||||
	QString response;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										69
									
								
								src/services/pam/ipc.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/services/pam/ipc.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					#include "ipc.hpp"
 | 
				
			||||||
 | 
					#include <cstddef>
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <unistd.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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<char*>(&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<char*>(&length), sizeof(length))) { // NOLINT
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return this->writeBytes(string.data(), string.length());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/services/pam/ipc.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/services/pam/ipc.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,43 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cstddef>
 | 
				
			||||||
 | 
					#include <cstdint>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qtclasshelpermacros.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -13,12 +13,6 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "conversation.hpp"
 | 
					#include "conversation.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PamContext::~PamContext() {
 | 
					 | 
				
			||||||
	if (this->conversation != nullptr && this->conversation->isRunning()) {
 | 
					 | 
				
			||||||
		this->conversation->abort();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void PamContext::componentComplete() {
 | 
					void PamContext::componentComplete() {
 | 
				
			||||||
	this->postInit = true;
 | 
						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::completed, this, &PamContext::onCompleted);
 | 
				
			||||||
	QObject::connect(this->conversation, &PamConversation::error, this, &PamContext::onError);
 | 
						QObject::connect(this->conversation, &PamConversation::error, this, &PamContext::onError);
 | 
				
			||||||
	QObject::connect(this->conversation, &PamConversation::message, this, &PamContext::onMessage);
 | 
						QObject::connect(this->conversation, &PamConversation::message, this, &PamContext::onMessage);
 | 
				
			||||||
	emit this->activeChanged();
 | 
						emit this->activeChanged();
 | 
				
			||||||
	this->conversation->start();
 | 
						this->conversation->start(this->mConfigDirectory, this->mConfig, user);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void PamContext::abortConversation() {
 | 
					void PamContext::abortConversation() {
 | 
				
			||||||
| 
						 | 
					@ -104,7 +98,7 @@ void PamContext::abortConversation() {
 | 
				
			||||||
	this->mTargetActive = false;
 | 
						this->mTargetActive = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QObject::disconnect(this->conversation, nullptr, this, nullptr);
 | 
						QObject::disconnect(this->conversation, nullptr, this, nullptr);
 | 
				
			||||||
	if (this->conversation->isRunning()) this->conversation->abort();
 | 
						this->conversation->deleteLater();
 | 
				
			||||||
	this->conversation = nullptr;
 | 
						this->conversation = nullptr;
 | 
				
			||||||
	emit this->activeChanged();
 | 
						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) {
 | 
						if (this->isActive() && this->mIsResponseRequired) {
 | 
				
			||||||
		this->conversation->respond(std::move(response));
 | 
							this->conversation->respond(response);
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		qWarning() << "PamContext response was ignored as this context does not require one.";
 | 
							qWarning() << "PamContext response was ignored as this context does not require one.";
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,8 +51,6 @@ class PamContext
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
	explicit PamContext(QObject* parent = nullptr): QObject(parent) {}
 | 
						explicit PamContext(QObject* parent = nullptr): QObject(parent) {}
 | 
				
			||||||
	~PamContext() override;
 | 
					 | 
				
			||||||
	Q_DISABLE_COPY_MOVE(PamContext);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void classBegin() override {}
 | 
						void classBegin() override {}
 | 
				
			||||||
	void componentComplete() override;
 | 
						void componentComplete() override;
 | 
				
			||||||
| 
						 | 
					@ -69,7 +67,7 @@ public:
 | 
				
			||||||
	/// Respond to pam.
 | 
						/// Respond to pam.
 | 
				
			||||||
	///
 | 
						///
 | 
				
			||||||
	/// May not be called unless `responseRequired` is true.
 | 
						/// 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;
 | 
						[[nodiscard]] bool isActive() const;
 | 
				
			||||||
	void setActive(bool active);
 | 
						void setActive(bool active);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										209
									
								
								src/services/pam/subprocess.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								src/services/pam/subprocess.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,209 @@
 | 
				
			||||||
 | 
					#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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/services/pam/subprocess.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/services/pam/subprocess.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <security/pam_appl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#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;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue