2024-12-16 18:58:36 +00:00
# include "Pam.hpp"
# include "../core/hyprlock.hpp"
2024-04-10 23:41:31 +02:00
# include "../helpers/Log.hpp"
2024-12-16 18:58:36 +00:00
# include "../config/ConfigManager.hpp"
2024-04-10 23:41:31 +02:00
# include <filesystem>
# include <unistd.h>
# include <pwd.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 ;
Debug : : log ( LOG , " PAM_PROMPT: {} " , PROMPT ) ;
if ( PROMPTCHANGED )
g_pHyprlock - > enqueueForceUpdateTimers ( ) ;
// 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.
2024-07-08 14:25:06 +02:00
if ( ! initialPrompt & & PROMPTCHANGED ) {
2024-04-10 23:41:31 +02:00
CONVERSATIONSTATE - > prompt = PROMPT ;
2024-12-16 18:58:36 +00:00
CONVERSATIONSTATE - > waitForInput ( ) ;
2024-04-10 23:41:31 +02:00
}
// Needed for unlocks via SIGUSR1
2024-07-17 15:22:42 +02:00
if ( g_pHyprlock - > isUnlocked ( ) )
2024-04-10 23:41:31 +02:00
return PAM_CONV_ERR ;
pamReply [ i ] . resp = strdup ( CONVERSATIONSTATE - > input . c_str ( ) ) ;
initialPrompt = false ;
} break ;
case PAM_ERROR_MSG : Debug : : log ( ERR , " PAM: {} " , msg [ i ] - > msg ) ; break ;
2024-07-30 18:52:50 +02:00
case PAM_TEXT_INFO :
Debug : : log ( LOG , " PAM: {} " , msg [ i ] - > msg ) ;
// 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 " ) ) {
CONVERSATIONSTATE - > failText = std : : move ( MSG ) ;
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 ( ) {
static auto * const PPAMMODULE = ( Hyprlang : : STRING * ) ( g_pConfigManager - > getValuePtr ( " auth:pam:module " ) ) ;
2024-04-10 23:41:31 +02:00
m_sPamModule = * PPAMMODULE ;
if ( ! std : : filesystem : : exists ( std : : filesystem : : path ( " /etc/pam.d/ " ) / m_sPamModule ) ) {
2024-07-05 22:54:40 +02:00
Debug : : log ( ERR , " 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
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
// Initial input
m_sConversationState . prompt = " Password: " ;
waitForInput ( ) ;
2024-07-17 15:22:42 +02:00
2024-12-16 18:58:36 +00:00
// For grace or SIGUSR1 unlocks
if ( g_pHyprlock - > isUnlocked ( ) )
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
if ( g_pHyprlock - > isUnlocked ( ) )
return ;
2024-04-10 23:41:31 +02:00
2024-12-16 18:58:36 +00:00
if ( ! AUTHENTICATED )
g_pAuth - > enqueueFail ( ) ;
else {
g_pAuth - > enqueueUnlock ( ) ;
return ;
}
}
} ) ;
2024-04-10 23:41:31 +02:00
}
2024-12-16 18:58:36 +00:00
bool CPam : : auth ( ) {
2024-04-10 23:41:31 +02:00
const pam_conv localConv = { conv , ( void * ) & m_sConversationState } ;
pam_handle_t * handle = NULL ;
auto uidPassword = getpwuid ( getuid ( ) ) ;
2024-12-23 20:53:27 +00:00
RASSERT ( uidPassword & & uidPassword - > pw_name , " Failed to get username (getpwuid) " ) ;
2024-04-10 23:41:31 +02:00
2024-12-23 20:53:27 +00:00
int ret = pam_start ( m_sPamModule . c_str ( ) , uidPassword - > pw_name , & localConv , & handle ) ;
2024-04-10 23:41:31 +02:00
if ( ret ! = PAM_SUCCESS ) {
m_sConversationState . failText = " pam_start failed " ;
Debug : : log ( ERR , " auth: pam_start failed for {} " , m_sPamModule ) ;
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 ;
2024-12-16 18:58:36 +00:00
g_pAuth - > postActivity ( AUTH_IMPL_PAM ) ;
2024-04-10 23:41:31 +02:00
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 " ;
2024-04-10 23:41:31 +02:00
Debug : : log ( ERR , " auth: {} for {} " , m_sConversationState . failText , m_sPamModule ) ;
return false ;
}
m_sConversationState . failText = " Successfully authenticated " ;
Debug : : log ( LOG , " auth: authenticated for {} " , m_sPamModule ) ;
return true ;
}
// clearing the input must be done from the main thread
static void clearInputTimerCallback ( std : : shared_ptr < CTimer > self , void * data ) {
g_pHyprlock - > clearPasswordBuffer ( ) ;
}
2024-12-16 18:58:36 +00:00
void CPam : : waitForInput ( ) {
2024-04-10 23:41:31 +02:00
g_pHyprlock - > addTimer ( std : : chrono : : milliseconds ( 1 ) , clearInputTimerCallback , nullptr ) ;
std : : unique_lock < std : : mutex > lk ( m_sConversationState . inputMutex ) ;
m_bBlockInput = false ;
m_sConversationState . waitingForPamAuth = false ;
m_sConversationState . inputRequested = true ;
m_sConversationState . inputSubmittedCondition . wait ( lk , [ this ] { return ! m_sConversationState . inputRequested | | g_pHyprlock - > m_bTerminate ; } ) ;
m_bBlockInput = true ;
}
2024-12-16 18:58:36 +00:00
void CPam : : handleInput ( const std : : string & input ) {
g_pAuth - > postActivity ( AUTH_IMPL_PAM ) ;
2024-04-10 23:41:31 +02:00
std : : unique_lock < std : : mutex > lk ( m_sConversationState . inputMutex ) ;
if ( ! m_sConversationState . inputRequested )
Debug : : log ( ERR , " SubmitInput called, but the auth thread is not waiting for input! " ) ;
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
}