forked from quickshell/quickshell
		
	service/pam: add pam service
This commit is contained in:
		
							parent
							
								
									f655875547
								
							
						
					
					
						commit
						7e5d128a91
					
				
					 11 changed files with 740 additions and 0 deletions
				
			
		| 
						 | 
					@ -14,6 +14,7 @@ Checks: >
 | 
				
			||||||
  -cppcoreguidelines-avoid-const-or-ref-data-members,
 | 
					  -cppcoreguidelines-avoid-const-or-ref-data-members,
 | 
				
			||||||
  -cppcoreguidelines-non-private-member-variables-in-classes,
 | 
					  -cppcoreguidelines-non-private-member-variables-in-classes,
 | 
				
			||||||
  -cppcoreguidelines-avoid-goto,
 | 
					  -cppcoreguidelines-avoid-goto,
 | 
				
			||||||
 | 
					  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
 | 
				
			||||||
  google-build-using-namespace.
 | 
					  google-build-using-namespace.
 | 
				
			||||||
  google-explicit-constructor,
 | 
					  google-explicit-constructor,
 | 
				
			||||||
  google-global-names-in-headers,
 | 
					  google-global-names-in-headers,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ option(HYPRLAND_FOCUS_GRAB "Hyprland Focus Grabbing" ON)
 | 
				
			||||||
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
 | 
					option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
 | 
				
			||||||
option(SERVICE_PIPEWIRE "PipeWire service" ON)
 | 
					option(SERVICE_PIPEWIRE "PipeWire service" ON)
 | 
				
			||||||
option(SERVICE_MPRIS "Mpris service" ON)
 | 
					option(SERVICE_MPRIS "Mpris service" ON)
 | 
				
			||||||
 | 
					option(SERVICE_PAM "Pam service" ON)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
message(STATUS "Quickshell configuration")
 | 
					message(STATUS "Quickshell configuration")
 | 
				
			||||||
message(STATUS "  Jemalloc: ${USE_JEMALLOC}")
 | 
					message(STATUS "  Jemalloc: ${USE_JEMALLOC}")
 | 
				
			||||||
| 
						 | 
					@ -39,6 +40,7 @@ message(STATUS "  Services")
 | 
				
			||||||
message(STATUS "    StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
 | 
					message(STATUS "    StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
 | 
				
			||||||
message(STATUS "    PipeWire: ${SERVICE_PIPEWIRE}")
 | 
					message(STATUS "    PipeWire: ${SERVICE_PIPEWIRE}")
 | 
				
			||||||
message(STATUS "    Mpris: ${SERVICE_MPRIS}")
 | 
					message(STATUS "    Mpris: ${SERVICE_MPRIS}")
 | 
				
			||||||
 | 
					message(STATUS "    Pam: ${SERVICE_PAM}")
 | 
				
			||||||
message(STATUS "  Hyprland: ${HYPRLAND}")
 | 
					message(STATUS "  Hyprland: ${HYPRLAND}")
 | 
				
			||||||
if (HYPRLAND)
 | 
					if (HYPRLAND)
 | 
				
			||||||
	message(STATUS "    IPC: ${HYPRLAND_IPC}")
 | 
						message(STATUS "    IPC: ${HYPRLAND_IPC}")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +45,7 @@ quickshell.packages.<system>.default.override {
 | 
				
			||||||
  withWayland = true;
 | 
					  withWayland = true;
 | 
				
			||||||
  withX11 = true;
 | 
					  withX11 = true;
 | 
				
			||||||
  withPipewire = true;
 | 
					  withPipewire = true;
 | 
				
			||||||
 | 
					  withPam = true;
 | 
				
			||||||
  withHyprland = true;
 | 
					  withHyprland = true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@
 | 
				
			||||||
  wayland-protocols,
 | 
					  wayland-protocols,
 | 
				
			||||||
  xorg,
 | 
					  xorg,
 | 
				
			||||||
  pipewire,
 | 
					  pipewire,
 | 
				
			||||||
 | 
					  pam,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  gitRev ? (let
 | 
					  gitRev ? (let
 | 
				
			||||||
    headExists = builtins.pathExists ./.git/HEAD;
 | 
					    headExists = builtins.pathExists ./.git/HEAD;
 | 
				
			||||||
| 
						 | 
					@ -31,6 +32,7 @@
 | 
				
			||||||
  withWayland ? true,
 | 
					  withWayland ? true,
 | 
				
			||||||
  withX11 ? true,
 | 
					  withX11 ? true,
 | 
				
			||||||
  withPipewire ? true,
 | 
					  withPipewire ? true,
 | 
				
			||||||
 | 
					  withPam ? true,
 | 
				
			||||||
  withHyprland ? true,
 | 
					  withHyprland ? true,
 | 
				
			||||||
}: buildStdenv.mkDerivation {
 | 
					}: buildStdenv.mkDerivation {
 | 
				
			||||||
  pname = "quickshell${lib.optionalString debug "-debug"}";
 | 
					  pname = "quickshell${lib.optionalString debug "-debug"}";
 | 
				
			||||||
| 
						 | 
					@ -55,6 +57,7 @@
 | 
				
			||||||
  ++ (lib.optional withQtSvg qt6.qtsvg)
 | 
					  ++ (lib.optional withQtSvg qt6.qtsvg)
 | 
				
			||||||
  ++ (lib.optionals withWayland [ qt6.qtwayland wayland ])
 | 
					  ++ (lib.optionals withWayland [ qt6.qtwayland wayland ])
 | 
				
			||||||
  ++ (lib.optional withX11 xorg.libxcb)
 | 
					  ++ (lib.optional withX11 xorg.libxcb)
 | 
				
			||||||
 | 
					  ++ (lib.optional withPam pam)
 | 
				
			||||||
  ++ (lib.optional withPipewire pipewire);
 | 
					  ++ (lib.optional withPipewire pipewire);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  QTWAYLANDSCANNER = lib.optionalString withWayland "${qt6.qtwayland}/libexec/qtwaylandscanner";
 | 
					  QTWAYLANDSCANNER = lib.optionalString withWayland "${qt6.qtwayland}/libexec/qtwaylandscanner";
 | 
				
			||||||
| 
						 | 
					@ -74,6 +77,7 @@
 | 
				
			||||||
  ++ lib.optional (!withJemalloc) "-DUSE_JEMALLOC=OFF"
 | 
					  ++ lib.optional (!withJemalloc) "-DUSE_JEMALLOC=OFF"
 | 
				
			||||||
  ++ lib.optional (!withWayland) "-DWAYLAND=OFF"
 | 
					  ++ lib.optional (!withWayland) "-DWAYLAND=OFF"
 | 
				
			||||||
  ++ lib.optional (!withPipewire) "-DSERVICE_PIPEWIRE=OFF"
 | 
					  ++ lib.optional (!withPipewire) "-DSERVICE_PIPEWIRE=OFF"
 | 
				
			||||||
 | 
					  ++ lib.optional (!withPam) "-DSERVICE_PAM=OFF"
 | 
				
			||||||
  ++ lib.optional (!withHyprland) "-DHYPRLAND=OFF";
 | 
					  ++ lib.optional (!withHyprland) "-DHYPRLAND=OFF";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  buildPhase = "ninjaBuildPhase";
 | 
					  buildPhase = "ninjaBuildPhase";
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,3 +9,7 @@ endif()
 | 
				
			||||||
if (SERVICE_MPRIS)
 | 
					if (SERVICE_MPRIS)
 | 
				
			||||||
	add_subdirectory(mpris)
 | 
						add_subdirectory(mpris)
 | 
				
			||||||
endif()
 | 
					endif()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (SERVICE_PAM)
 | 
				
			||||||
 | 
						add_subdirectory(pam)
 | 
				
			||||||
 | 
					endif()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/services/pam/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/services/pam/CMakeLists.txt
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,17 @@
 | 
				
			||||||
 | 
					#find_package(PAM REQUIRED)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qt_add_library(quickshell-service-pam STATIC
 | 
				
			||||||
 | 
						qml.cpp
 | 
				
			||||||
 | 
						conversation.cpp
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					qt_add_qml_module(quickshell-service-pam
 | 
				
			||||||
 | 
						URI Quickshell.Services.Pam
 | 
				
			||||||
 | 
						VERSION 0.1
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(quickshell-service-pam PRIVATE ${QT_DEPS} pam ${PAM_LIBRARIES})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					qs_pch(quickshell-service-pam)
 | 
				
			||||||
 | 
					qs_pch(quickshell-service-pamplugin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					target_link_libraries(quickshell PRIVATE quickshell-service-pamplugin)
 | 
				
			||||||
							
								
								
									
										185
									
								
								src/services/pam/conversation.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/services/pam/conversation.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,185 @@
 | 
				
			||||||
 | 
					#include "conversation.hpp"
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qlogging.h>
 | 
				
			||||||
 | 
					#include <qloggingcategory.h>
 | 
				
			||||||
 | 
					#include <qmutex.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qstring.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <security/_pam_types.h>
 | 
				
			||||||
 | 
					#include <security/pam_appl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 TryAuthFailed: return "Failed to try authenticating";
 | 
				
			||||||
 | 
						default: return "Invalid error";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QString PamResult::toString(PamResult::Enum value) {
 | 
				
			||||||
 | 
						switch (value) {
 | 
				
			||||||
 | 
						case Success: return "Success";
 | 
				
			||||||
 | 
						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,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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();
 | 
				
			||||||
 | 
							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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PamConversation::abort() {
 | 
				
			||||||
 | 
						qCDebug(logPam) << "Abort requested for" << this;
 | 
				
			||||||
 | 
						auto locker = QMutexLocker(&this->wakeMutex);
 | 
				
			||||||
 | 
						this->mAbort = true;
 | 
				
			||||||
 | 
						this->waker.wakeOne();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int PamConversation::conversation(
 | 
				
			||||||
 | 
					    int msgCount,
 | 
				
			||||||
 | 
					    const pam_message** msgArray,
 | 
				
			||||||
 | 
					    pam_response** responseArray,
 | 
				
			||||||
 | 
					    void* appdata
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						auto* delegate = static_cast<PamConversation*>(appdata);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							auto locker = QMutexLocker(&delegate->wakeMutex);
 | 
				
			||||||
 | 
							if (delegate->mAbort) {
 | 
				
			||||||
 | 
								return PAM_ERROR_MSG;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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 = 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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							qCDebug(logPam) << delegate << "got new message message:" << msgString
 | 
				
			||||||
 | 
							                << "messageChanged:" << messageChanged << "isError:" << isError
 | 
				
			||||||
 | 
							                << "responseRequired" << responseRequired;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							delegate->hasResponse = false;
 | 
				
			||||||
 | 
							emit delegate->message(msgString, messageChanged, isError, responseRequired);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								auto locker = QMutexLocker(&delegate->wakeMutex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (delegate->mAbort) {
 | 
				
			||||||
 | 
									free(responses); // NOLINT
 | 
				
			||||||
 | 
									return PAM_ERROR_MSG;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (responseRequired) {
 | 
				
			||||||
 | 
									if (!delegate->hasResponse) {
 | 
				
			||||||
 | 
										delegate->waker.wait(locker.mutex());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (delegate->mAbort) {
 | 
				
			||||||
 | 
											free(responses); // NOLINT
 | 
				
			||||||
 | 
											return PAM_ERROR_MSG;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (!delegate->hasResponse) {
 | 
				
			||||||
 | 
										qCCritical(logPam
 | 
				
			||||||
 | 
										) << "Pam conversation requires response and does not have one. This should not happen.";
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									response.resp = strdup(delegate->response.toStdString().c_str()); // NOLINT (include error)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						*responseArray = responses;
 | 
				
			||||||
 | 
						return PAM_SUCCESS;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										95
									
								
								src/services/pam/conversation.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								src/services/pam/conversation.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,95 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <utility>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qmutex.h>
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qthread.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <qwaitcondition.h>
 | 
				
			||||||
 | 
					#include <security/pam_appl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// The result of an authentication.
 | 
				
			||||||
 | 
					class PamResult: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						enum Enum {
 | 
				
			||||||
 | 
							/// Authentication was successful.
 | 
				
			||||||
 | 
							Success = 0,
 | 
				
			||||||
 | 
							/// Authentication failed.
 | 
				
			||||||
 | 
							Failed = 1,
 | 
				
			||||||
 | 
							/// An error occurred while trying to authenticate.
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q_INVOKABLE static QString toString(PamResult::Enum value);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// An error that occurred during an authentication.
 | 
				
			||||||
 | 
					class PamError: public QObject {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
						QML_SINGLETON;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						enum Enum {
 | 
				
			||||||
 | 
							/// Failed to initiate the pam connection.
 | 
				
			||||||
 | 
							ConnectionFailed = 1,
 | 
				
			||||||
 | 
							/// Failed to try to authenticate the user.
 | 
				
			||||||
 | 
							/// This is not the same as the user failing to authenticate.
 | 
				
			||||||
 | 
							TryAuthFailed = 2,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						Q_ENUM(Enum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Q_INVOKABLE static QString toString(PamError::Enum value);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class PamConversation: public QThread {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit PamConversation(QString config, QString configDir, QString user)
 | 
				
			||||||
 | 
						    : config(std::move(config))
 | 
				
			||||||
 | 
						    , configDir(std::move(configDir))
 | 
				
			||||||
 | 
						    , user(std::move(user)) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						void run() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void abort();
 | 
				
			||||||
 | 
						void respond(QString response);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						void completed(PamResult::Enum result);
 | 
				
			||||||
 | 
						void error(PamError::Enum error);
 | 
				
			||||||
 | 
						void message(QString message, bool messageChanged, bool isError, bool responseRequired);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						static int conversation(
 | 
				
			||||||
 | 
						    int msgCount,
 | 
				
			||||||
 | 
						    const pam_message** msgArray,
 | 
				
			||||||
 | 
						    pam_response** responseArray,
 | 
				
			||||||
 | 
						    void* appdata
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QString config;
 | 
				
			||||||
 | 
						QString configDir;
 | 
				
			||||||
 | 
						QString user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QMutex wakeMutex;
 | 
				
			||||||
 | 
						QWaitCondition waker;
 | 
				
			||||||
 | 
						bool mAbort = false;
 | 
				
			||||||
 | 
						bool hasResponse = false;
 | 
				
			||||||
 | 
						QString response;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										67
									
								
								src/services/pam/module.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/services/pam/module.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					name = "Quickshell.Services.Pam"
 | 
				
			||||||
 | 
					description = "Pam authentication"
 | 
				
			||||||
 | 
					headers = [
 | 
				
			||||||
 | 
						"qml.hpp",
 | 
				
			||||||
 | 
						"conversation.hpp",
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Writing pam configurations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					It is a good idea to write pam configurations specifically for quickshell
 | 
				
			||||||
 | 
					if you want to do anything other than match the default login flow.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A good example of this is having a configuration that allows entering a password
 | 
				
			||||||
 | 
					or fingerprint in any order.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Structure of a pam configuration.
 | 
				
			||||||
 | 
					Pam configuration files are a list of rules, each on a new line in the following form:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					<type> <control_flag> <module_path> [options]
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Each line runs in order.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PamContext currently only works with the `auth` type, as other types require root
 | 
				
			||||||
 | 
					access to check.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Control flags
 | 
				
			||||||
 | 
					The control flags you're likely to use are `required` and `sufficient`.
 | 
				
			||||||
 | 
					- `required` rules must pass for authentication to succeed.
 | 
				
			||||||
 | 
					- `sufficient` rules will bypass any remaining rules and return on success.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note that you should have at least one required rule or pam will fail with an undocumented error.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#### Modules
 | 
				
			||||||
 | 
					Pam works with a set of modules that handle various authentication mechanisms.
 | 
				
			||||||
 | 
					Some common ones include `pam_unix.so` which handles passwords and `pam_fprintd.so`
 | 
				
			||||||
 | 
					which handles fingerprints.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					These modules have options but none are required for basic functionality.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Authenticate with only a password:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					auth required pam_unix.so
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Authenticate with only a fingerprint:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					auth required pam_fprintd.so
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Try to authenticate with a fingerprint first, but if that fails fall back to a password:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					auth sufficient pam_fprintd.so
 | 
				
			||||||
 | 
					auth required pam_unix.so
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Require both a fingerprint and a password:
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					auth required pam_fprintd.so
 | 
				
			||||||
 | 
					auth required pam_unix.so
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					See also: [Oracle: PAM configuration file](https://docs.oracle.com/cd/E19683-01/816-4883/pam-32/index.html)
 | 
				
			||||||
							
								
								
									
										238
									
								
								src/services/pam/qml.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								src/services/pam/qml.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,238 @@
 | 
				
			||||||
 | 
					#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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					PamContext::~PamContext() {
 | 
				
			||||||
 | 
						if (this->conversation != nullptr && this->conversation->isRunning()) {
 | 
				
			||||||
 | 
							this->conversation->abort();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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->mConfig, this->mConfigDirectory, user);
 | 
				
			||||||
 | 
						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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PamContext::abortConversation() {
 | 
				
			||||||
 | 
						if (this->conversation == nullptr) return;
 | 
				
			||||||
 | 
						this->mTargetActive = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QObject::disconnect(this->conversation, nullptr, this, nullptr);
 | 
				
			||||||
 | 
						if (this->conversation->isRunning()) this->conversation->abort();
 | 
				
			||||||
 | 
						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(QString response) {
 | 
				
			||||||
 | 
						if (this->isActive() && this->mIsResponseRequired) {
 | 
				
			||||||
 | 
							this->conversation->respond(std::move(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) {
 | 
				
			||||||
 | 
						emit this->completed(result);
 | 
				
			||||||
 | 
						this->abortConversation();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void PamContext::onError(PamError::Enum error) {
 | 
				
			||||||
 | 
						emit this->error(error);
 | 
				
			||||||
 | 
						emit this->completed(PamResult::Error);
 | 
				
			||||||
 | 
						this->abortConversation();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										126
									
								
								src/services/pam/qml.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/services/pam/qml.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,126 @@
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <qobject.h>
 | 
				
			||||||
 | 
					#include <qqmlintegration.h>
 | 
				
			||||||
 | 
					#include <qqmlparserstatus.h>
 | 
				
			||||||
 | 
					#include <qtclasshelpermacros.h>
 | 
				
			||||||
 | 
					#include <qthread.h>
 | 
				
			||||||
 | 
					#include <qtmetamacros.h>
 | 
				
			||||||
 | 
					#include <security/_pam_types.h>
 | 
				
			||||||
 | 
					#include <security/pam_appl.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "conversation.hpp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					///! Connection to pam.
 | 
				
			||||||
 | 
					/// Connection to pam. See [the module documentation](../) for pam configuration advice.
 | 
				
			||||||
 | 
					class PamContext
 | 
				
			||||||
 | 
					    : public QObject
 | 
				
			||||||
 | 
					    , public QQmlParserStatus {
 | 
				
			||||||
 | 
						Q_OBJECT;
 | 
				
			||||||
 | 
						// clang-format off
 | 
				
			||||||
 | 
						/// If the pam context is actively performing an authentication.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Setting this value behaves exactly the same as calling `start()` and `abort()`.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool active READ isActive WRITE setActive NOTIFY activeChanged);
 | 
				
			||||||
 | 
						/// The pam configuration to use. Defaults to "login".
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// The configuration should name a file inside `configDirectory`.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// This property may not be set while `active` is true.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString config READ config WRITE setConfig NOTIFY configChanged);
 | 
				
			||||||
 | 
						/// The pam configuration directory to use. Defaults to "/etc/pam.d".
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// The configuration directory is resolved relative to the current file if not an absolute path.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// This property may not be set while `active` is true.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString configDirectory READ configDirectory WRITE setConfigDirectory NOTIFY configDirectoryChanged);
 | 
				
			||||||
 | 
						/// The user to authenticate as. If unset the current user will be used.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// This property may not be set while `active` is true.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString user READ user WRITE setUser NOTIFY userChanged);
 | 
				
			||||||
 | 
						/// The last message sent by pam.
 | 
				
			||||||
 | 
						Q_PROPERTY(QString message READ message NOTIFY messageChanged);
 | 
				
			||||||
 | 
						/// If the last message should be shown as an error.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool messageIsError READ messageIsError NOTIFY messageIsErrorChanged);
 | 
				
			||||||
 | 
						/// If pam currently wants a response.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// Responses can be returned with the `respond()` function.
 | 
				
			||||||
 | 
						Q_PROPERTY(bool responseRequired READ isResponseRequired NOTIFY responseRequiredChanged);
 | 
				
			||||||
 | 
						// clang-format on
 | 
				
			||||||
 | 
						QML_ELEMENT;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
						explicit PamContext(QObject* parent = nullptr): QObject(parent) {}
 | 
				
			||||||
 | 
						~PamContext() override;
 | 
				
			||||||
 | 
						Q_DISABLE_COPY_MOVE(PamContext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void classBegin() override {}
 | 
				
			||||||
 | 
						void componentComplete() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void startConversation();
 | 
				
			||||||
 | 
						void abortConversation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Start an authentication session. Returns if the session was started successfully.
 | 
				
			||||||
 | 
						Q_INVOKABLE bool start();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Abort a running authentication session.
 | 
				
			||||||
 | 
						Q_INVOKABLE void abort();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Respond to pam.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// May not be called unless `responseRequired` is true.
 | 
				
			||||||
 | 
						Q_INVOKABLE void respond(QString response);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] bool isActive() const;
 | 
				
			||||||
 | 
						void setActive(bool active);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString config() const;
 | 
				
			||||||
 | 
						void setConfig(QString config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString configDirectory() const;
 | 
				
			||||||
 | 
						void setConfigDirectory(QString configDirectory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString user() const;
 | 
				
			||||||
 | 
						void setUser(QString user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[[nodiscard]] QString message() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool messageIsError() const;
 | 
				
			||||||
 | 
						[[nodiscard]] bool isResponseRequired() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals:
 | 
				
			||||||
 | 
						/// Emitted whenever authentication completes.
 | 
				
			||||||
 | 
						void completed(PamResult::Enum result);
 | 
				
			||||||
 | 
						/// Emitted if pam fails to perform authentication normally.
 | 
				
			||||||
 | 
						///
 | 
				
			||||||
 | 
						/// A `completed(false)` will be emitted after this event.
 | 
				
			||||||
 | 
						void error(PamError::Enum error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/// Emitted whenever pam sends a new message, after the change signals for
 | 
				
			||||||
 | 
						/// `message`, `messageIsError`, and `responseRequired`.
 | 
				
			||||||
 | 
						void pamMessage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void activeChanged();
 | 
				
			||||||
 | 
						void configChanged();
 | 
				
			||||||
 | 
						void configDirectoryChanged();
 | 
				
			||||||
 | 
						void userChanged();
 | 
				
			||||||
 | 
						void messageChanged();
 | 
				
			||||||
 | 
						void messageIsErrorChanged();
 | 
				
			||||||
 | 
						void responseRequiredChanged();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private slots:
 | 
				
			||||||
 | 
						void onCompleted(PamResult::Enum result);
 | 
				
			||||||
 | 
						void onError(PamError::Enum error);
 | 
				
			||||||
 | 
						void onMessage(QString message, bool messageChanged, bool isError, bool responseRequired);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
						PamConversation* conversation = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool postInit = false;
 | 
				
			||||||
 | 
						bool mTargetActive = false;
 | 
				
			||||||
 | 
						QString mConfig = "login";
 | 
				
			||||||
 | 
						QString mConfigDirectory = "/etc/pam.d";
 | 
				
			||||||
 | 
						QString mUser;
 | 
				
			||||||
 | 
						QString mMessage;
 | 
				
			||||||
 | 
						bool mMessageIsError = false;
 | 
				
			||||||
 | 
						bool mIsResponseRequired = false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue