2024-12-16 18:58:36 +00:00
# include "Pam.hpp"
2026-03-23 16:21:13 +00:00
# include "../config/ConfigManager.hpp"
2024-12-16 18:58:36 +00:00
# include "../core/hyprlock.hpp"
2024-04-10 23:41:31 +02:00
# include "../helpers/Log.hpp"
2026-03-23 16:21:13 +00:00
# include "../helpers/MiscFunctions.hpp"
2024-04-10 23:41:31 +02:00
# include <filesystem>
# include <unistd.h>
# include <security/pam_appl.h>
# if __has_include(<security / pam_misc.h>)
# include <security/pam_misc.h>
# endif
# include <cstring>
# include <thread>
int conv ( int num_msg , const struct pam_message * * msg , struct pam_response * * resp , void * appdata_ptr ) {
2024-12-16 18:58:36 +00:00
const auto CONVERSATIONSTATE = ( CPam : : SPamConversationState * ) appdata_ptr ;
2024-04-10 23:41:31 +02:00
struct pam_response * pamReply = ( struct pam_response * ) calloc ( num_msg , sizeof ( struct pam_response ) ) ;
bool initialPrompt = true ;
for ( int i = 0 ; i < num_msg ; + + i ) {
switch ( msg [ i ] - > msg_style ) {
case PAM_PROMPT_ECHO_OFF :
case PAM_PROMPT_ECHO_ON : {
const auto PROMPT = std : : string ( msg [ i ] - > msg ) ;
const auto PROMPTCHANGED = PROMPT ! = CONVERSATIONSTATE - > prompt ;
2026-03-31 14:56:08 +00:00
Log : : logger - > log ( Log : : INFO , " PAM_PROMPT: {} " , PROMPT ) ;
2024-04-10 23:41:31 +02:00
// Some pam configurations ask for the password twice for whatever reason (Fedora su for example)
// When the prompt is the same as the last one, I guess our answer can be the same.
2026-04-29 17:35:26 +00:00
if ( initialPrompt | | PROMPTCHANGED ) {
2024-04-10 23:41:31 +02:00
CONVERSATIONSTATE - > prompt = PROMPT ;
2026-04-29 17:35:26 +00:00
g_pHyprlock - > enqueueForceUpdateTimers ( ) ;
2024-12-16 18:58:36 +00:00
CONVERSATIONSTATE - > waitForInput ( ) ;
2024-04-10 23:41:31 +02:00
}
// Needed for unlocks via SIGUSR1
2026-04-29 19:20:09 +02:00
if ( g_pHyprlock - > isFadingOutOrTerminating ( ) )
2024-04-10 23:41:31 +02:00
return PAM_CONV_ERR ;
pamReply [ i ] . resp = strdup ( CONVERSATIONSTATE - > input . c_str ( ) ) ;
initialPrompt = false ;
} break ;
2026-03-31 14:56:08 +00:00
case PAM_ERROR_MSG : Log : : logger - > log ( Log : : ERR , " PAM: {} " , msg [ i ] - > msg ) ; break ;
2024-07-30 18:52:50 +02:00
case PAM_TEXT_INFO :
2026-03-31 14:56:08 +00:00
Log : : logger - > log ( Log : : INFO , " PAM: {} " , msg [ i ] - > msg ) ;
2024-07-30 18:52:50 +02:00
// Targets this log from pam_faillock: https://github.com/linux-pam/linux-pam/blob/fa3295e079dbbc241906f29bde5fb71bc4172771/modules/pam_faillock/pam_faillock.c#L417
if ( const auto MSG = std : : string ( msg [ i ] - > msg ) ; MSG . contains ( " left to unlock " ) ) {
2025-02-06 16:36:08 +05:00
CONVERSATIONSTATE - > failText = MSG ;
2024-07-30 18:52:50 +02:00
CONVERSATIONSTATE - > failTextFromPam = true ;
}
break ;
2024-04-10 23:41:31 +02:00
}
}
* resp = pamReply ;
return PAM_SUCCESS ;
}
2024-12-16 18:58:36 +00:00
CPam : : CPam ( ) {
2025-01-29 22:10:27 +00:00
static const auto PAMMODULE = g_pConfigManager - > getValue < Hyprlang : : STRING > ( " auth:pam:module " ) ;
m_sPamModule = * PAMMODULE ;
2024-04-10 23:41:31 +02:00
if ( ! std : : filesystem : : exists ( std : : filesystem : : path ( " /etc/pam.d/ " ) / m_sPamModule ) ) {
2026-03-31 14:56:08 +00:00
Log : : logger - > log ( Log : : ERR , R " (Pam module " / etc / pam . d / { } " does not exist! Falling back to " / etc / pam . d / su " ) " , m_sPamModule ) ;
2024-04-10 23:41:31 +02:00
m_sPamModule = " su " ;
}
2024-12-16 18:58:36 +00:00
2026-03-23 16:21:13 +00:00
m_username = getUsernameForCurrentUid ( ) ;
2024-12-16 18:58:36 +00:00
m_sConversationState . waitForInput = [ this ] ( ) { this - > waitForInput ( ) ; } ;
2024-04-10 23:41:31 +02:00
}
2024-12-16 18:58:36 +00:00
CPam : : ~ CPam ( ) {
;
2024-04-10 23:41:31 +02:00
}
2024-12-16 18:58:36 +00:00
void CPam : : init ( ) {
m_thread = std : : thread ( [ this ] ( ) {
while ( true ) {
resetConversation ( ) ;
2024-07-17 15:22:42 +02:00
2024-12-16 18:58:36 +00:00
// For grace or SIGUSR1 unlocks
2026-04-29 19:20:09 +02:00
if ( g_pHyprlock - > isFadingOutOrTerminating ( ) )
2024-12-16 18:58:36 +00:00
return ;
2024-07-17 15:22:42 +02:00
2024-12-16 18:58:36 +00:00
const auto AUTHENTICATED = auth ( ) ;
2024-07-17 15:22:42 +02:00
2024-12-16 18:58:36 +00:00
// For SIGUSR1 unlocks
2026-04-29 19:20:09 +02:00
if ( g_pHyprlock - > isFadingOutOrTerminating ( ) )
2024-12-16 18:58:36 +00:00
return ;
2024-04-10 23:41:31 +02:00
2024-12-16 18:58:36 +00:00
if ( ! AUTHENTICATED )
2025-01-12 17:18:18 +00:00
g_pAuth - > enqueueFail ( m_sConversationState . failText , AUTH_IMPL_PAM ) ;
2024-12-16 18:58:36 +00:00
else {
g_pAuth - > enqueueUnlock ( ) ;
return ;
}
}
} ) ;
2024-04-10 23:41:31 +02:00
}
2024-12-16 18:58:36 +00:00
bool CPam : : auth ( ) {
2026-03-23 16:21:13 +00:00
const pam_conv localConv = { . conv = conv , . appdata_ptr = ( void * ) & m_sConversationState } ;
pam_handle_t * handle = nullptr ;
if ( m_username . empty ( ) ) {
m_sConversationState . failText = " Username not set " ;
return false ;
}
2024-04-10 23:41:31 +02:00
2026-03-23 16:21:13 +00:00
int ret = pam_start ( m_sPamModule . c_str ( ) , m_username . c_str ( ) , & localConv , & handle ) ;
2024-04-10 23:41:31 +02:00
if ( ret ! = PAM_SUCCESS ) {
m_sConversationState . failText = " pam_start failed " ;
2026-03-31 14:56:08 +00:00
Log : : logger - > log ( Log : : ERR , " auth: pam_start failed for {} " , m_sPamModule ) ;
2024-04-10 23:41:31 +02:00
return false ;
}
ret = pam_authenticate ( handle , 0 ) ;
2024-07-05 22:54:40 +02:00
pam_end ( handle , ret ) ;
handle = nullptr ;
2024-04-10 23:41:31 +02:00
m_sConversationState . waitingForPamAuth = false ;
if ( ret ! = PAM_SUCCESS ) {
2024-07-30 18:52:50 +02:00
if ( ! m_sConversationState . failTextFromPam )
m_sConversationState . failText = ret = = PAM_AUTH_ERR ? " Authentication failed " : " pam_authenticate failed " ;
2026-03-31 14:56:08 +00:00
Log : : logger - > log ( Log : : ERR , " auth: {} for {} " , m_sConversationState . failText , m_sPamModule ) ;
2024-04-10 23:41:31 +02:00
return false ;
}
m_sConversationState . failText = " Successfully authenticated " ;
2026-03-31 14:56:08 +00:00
Log : : logger - > log ( Log : : INFO , " auth: authenticated for {} " , m_sPamModule ) ;
2024-04-10 23:41:31 +02:00
return true ;
}
2024-12-16 18:58:36 +00:00
void CPam : : waitForInput ( ) {
2024-04-10 23:41:31 +02:00
std : : unique_lock < std : : mutex > lk ( m_sConversationState . inputMutex ) ;
m_bBlockInput = false ;
m_sConversationState . waitingForPamAuth = false ;
m_sConversationState . inputRequested = true ;
2026-04-29 19:20:09 +02:00
m_sConversationState . inputSubmittedCondition . wait ( lk , [ this ] { return ! m_sConversationState . inputRequested | | g_pHyprlock - > isFadingOutOrTerminating ( ) ; } ) ;
2024-04-10 23:41:31 +02:00
m_bBlockInput = true ;
}
2024-12-16 18:58:36 +00:00
void CPam : : handleInput ( const std : : string & input ) {
2024-04-10 23:41:31 +02:00
std : : unique_lock < std : : mutex > lk ( m_sConversationState . inputMutex ) ;
if ( ! m_sConversationState . inputRequested )
2026-03-31 14:56:08 +00:00
Log : : logger - > log ( Log : : ERR , " SubmitInput called, but the auth thread is not waiting for input! " ) ;
2024-04-10 23:41:31 +02:00
m_sConversationState . input = input ;
m_sConversationState . inputRequested = false ;
m_sConversationState . waitingForPamAuth = true ;
m_sConversationState . inputSubmittedCondition . notify_all ( ) ;
}
2024-12-16 18:58:36 +00:00
std : : optional < std : : string > CPam : : getLastFailText ( ) {
2024-04-10 23:41:31 +02:00
return m_sConversationState . failText . empty ( ) ? std : : nullopt : std : : optional ( m_sConversationState . failText ) ;
}
2024-12-16 18:58:36 +00:00
std : : optional < std : : string > CPam : : getLastPrompt ( ) {
2024-04-10 23:41:31 +02:00
return m_sConversationState . prompt . empty ( ) ? std : : nullopt : std : : optional ( m_sConversationState . prompt ) ;
}
2024-12-16 18:58:36 +00:00
bool CPam : : checkWaiting ( ) {
2024-04-10 23:41:31 +02:00
return m_bBlockInput | | m_sConversationState . waitingForPamAuth ;
}
2024-12-16 18:58:36 +00:00
void CPam : : terminate ( ) {
2024-04-10 23:41:31 +02:00
m_sConversationState . inputSubmittedCondition . notify_all ( ) ;
2024-12-16 18:58:36 +00:00
if ( m_thread . joinable ( ) )
m_thread . join ( ) ;
2024-04-10 23:41:31 +02:00
}
2024-12-16 18:58:36 +00:00
void CPam : : resetConversation ( ) {
2024-04-10 23:41:31 +02:00
m_sConversationState . input = " " ;
m_sConversationState . waitingForPamAuth = false ;
m_sConversationState . inputRequested = false ;
2024-07-30 18:52:50 +02:00
m_sConversationState . failTextFromPam = false ;
2024-04-10 23:41:31 +02:00
}