2019-07-04 18:35:39 +02:00
/* SPDX-License-Identifier: LGPL-2.1+ */
# if HAVE_LINUX_MEMFD_H
# include <linux/memfd.h>
# endif
# include <sys/mman.h>
# include <sys/quota.h>
# include <sys/vfs.h>
# include "blockdev-util.h"
# include "btrfs-util.h"
# include "bus-common-errors.h"
# include "env-util.h"
# include "errno-list.h"
# include "errno-util.h"
# include "fd-util.h"
# include "fileio.h"
# include "home-util.h"
# include "homed-home-bus.h"
# include "homed-home.h"
# include "missing_syscall.h"
# include "mkdir.h"
# include "path-util.h"
# include "process-util.h"
# include "pwquality-util.h"
# include "quota-util.h"
# include "resize-fs.h"
# include "set.h"
# include "signal-util.h"
# include "stat-util.h"
# include "string-table.h"
# include "strv.h"
# include "user-record-sign.h"
# include "user-record-util.h"
# include "user-record.h"
# include "user-util.h"
# define HOME_USERS_MAX 500
# define PENDING_OPERATIONS_MAX 100
assert_cc ( HOME_UID_MIN < = HOME_UID_MAX ) ;
assert_cc ( HOME_USERS_MAX < = ( HOME_UID_MAX - HOME_UID_MIN + 1 ) ) ;
static int home_start_work ( Home * h , const char * verb , UserRecord * hr , UserRecord * secret ) ;
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR ( operation_hash_ops , void , trivial_hash_func , trivial_compare_func , Operation , operation_unref ) ;
static int suitable_home_record ( UserRecord * hr ) {
int r ;
assert ( hr ) ;
if ( ! hr - > user_name )
return - EUNATCH ;
/* We are a bit more restrictive with what we accept as homed-managed user than what we accept in
* home records in general . Let ' s enforce the stricter rule here . */
if ( ! suitable_user_name ( hr - > user_name ) )
return - EINVAL ;
if ( ! uid_is_valid ( hr - > uid ) )
return - EINVAL ;
/* Insist we are outside of the dynamic and system range */
if ( uid_is_system ( hr - > uid ) | | gid_is_system ( user_record_gid ( hr ) ) | |
uid_is_dynamic ( hr - > uid ) | | gid_is_dynamic ( user_record_gid ( hr ) ) )
return - EADDRNOTAVAIL ;
/* Insist that GID and UID match */
if ( user_record_gid ( hr ) ! = ( gid_t ) hr - > uid )
return - EBADSLT ;
/* Similar for the realm */
if ( hr - > realm ) {
r = suitable_realm ( hr - > realm ) ;
if ( r < 0 )
return r ;
if ( r = = 0 )
return - EINVAL ;
}
return 0 ;
}
int home_new ( Manager * m , UserRecord * hr , const char * sysfs , Home * * ret ) {
_cleanup_ ( home_freep ) Home * home = NULL ;
_cleanup_free_ char * nm = NULL , * ns = NULL ;
int r ;
assert ( m ) ;
assert ( hr ) ;
r = suitable_home_record ( hr ) ;
if ( r < 0 )
return r ;
if ( hashmap_contains ( m - > homes_by_name , hr - > user_name ) )
return - EBUSY ;
if ( hashmap_contains ( m - > homes_by_uid , UID_TO_PTR ( hr - > uid ) ) )
return - EBUSY ;
if ( sysfs & & hashmap_contains ( m - > homes_by_sysfs , sysfs ) )
return - EBUSY ;
if ( hashmap_size ( m - > homes_by_name ) > = HOME_USERS_MAX )
return - EUSERS ;
nm = strdup ( hr - > user_name ) ;
if ( ! nm )
return - ENOMEM ;
if ( sysfs ) {
ns = strdup ( sysfs ) ;
if ( ! ns )
return - ENOMEM ;
}
home = new ( Home , 1 ) ;
if ( ! home )
return - ENOMEM ;
* home = ( Home ) {
. manager = m ,
. user_name = TAKE_PTR ( nm ) ,
. uid = hr - > uid ,
. state = _HOME_STATE_INVALID ,
. worker_stdout_fd = - 1 ,
. sysfs = TAKE_PTR ( ns ) ,
. signed_locally = - 1 ,
} ;
r = hashmap_put ( m - > homes_by_name , home - > user_name , home ) ;
if ( r < 0 )
return r ;
r = hashmap_put ( m - > homes_by_uid , UID_TO_PTR ( home - > uid ) , home ) ;
if ( r < 0 )
return r ;
if ( home - > sysfs ) {
r = hashmap_put ( m - > homes_by_sysfs , home - > sysfs , home ) ;
if ( r < 0 )
return r ;
}
r = user_record_clone ( hr , USER_RECORD_LOAD_MASK_SECRET , & home - > record ) ;
if ( r < 0 )
return r ;
( void ) bus_manager_emit_auto_login_changed ( m ) ;
( void ) bus_home_emit_change ( home ) ;
if ( ret )
* ret = TAKE_PTR ( home ) ;
else
TAKE_PTR ( home ) ;
return 0 ;
}
Home * home_free ( Home * h ) {
if ( ! h )
return NULL ;
if ( h - > manager ) {
( void ) bus_home_emit_remove ( h ) ;
( void ) bus_manager_emit_auto_login_changed ( h - > manager ) ;
if ( h - > user_name )
( void ) hashmap_remove_value ( h - > manager - > homes_by_name , h - > user_name , h ) ;
if ( uid_is_valid ( h - > uid ) )
( void ) hashmap_remove_value ( h - > manager - > homes_by_uid , UID_TO_PTR ( h - > uid ) , h ) ;
if ( h - > sysfs )
( void ) hashmap_remove_value ( h - > manager - > homes_by_sysfs , h - > sysfs , h ) ;
if ( h - > worker_pid > 0 )
( void ) hashmap_remove_value ( h - > manager - > homes_by_worker_pid , PID_TO_PTR ( h - > worker_pid ) , h ) ;
if ( h - > manager - > gc_focus = = h )
h - > manager - > gc_focus = NULL ;
}
user_record_unref ( h - > record ) ;
user_record_unref ( h - > secret ) ;
h - > worker_event_source = sd_event_source_unref ( h - > worker_event_source ) ;
safe_close ( h - > worker_stdout_fd ) ;
free ( h - > user_name ) ;
free ( h - > sysfs ) ;
h - > ref_event_source_please_suspend = sd_event_source_unref ( h - > ref_event_source_please_suspend ) ;
h - > ref_event_source_dont_suspend = sd_event_source_unref ( h - > ref_event_source_dont_suspend ) ;
h - > pending_operations = ordered_set_free ( h - > pending_operations ) ;
h - > pending_event_source = sd_event_source_unref ( h - > pending_event_source ) ;
h - > deferred_change_event_source = sd_event_source_unref ( h - > deferred_change_event_source ) ;
h - > current_operation = operation_unref ( h - > current_operation ) ;
return mfree ( h ) ;
}
int home_set_record ( Home * h , UserRecord * hr ) {
_cleanup_ ( user_record_unrefp ) UserRecord * new_hr = NULL ;
Home * other ;
int r ;
assert ( h ) ;
assert ( h - > user_name ) ;
assert ( h - > record ) ;
assert ( hr ) ;
if ( user_record_equal ( h - > record , hr ) )
return 0 ;
r = suitable_home_record ( hr ) ;
if ( r < 0 )
return r ;
if ( ! user_record_compatible ( h - > record , hr ) )
return - EREMCHG ;
if ( ! FLAGS_SET ( hr - > mask , USER_RECORD_REGULAR ) | |
FLAGS_SET ( hr - > mask , USER_RECORD_SECRET ) )
return - EINVAL ;
if ( FLAGS_SET ( h - > record - > mask , USER_RECORD_STATUS ) ) {
_cleanup_ ( json_variant_unrefp ) JsonVariant * v = NULL ;
/* Hmm, the existing record has status fields? If so, copy them over */
v = json_variant_ref ( hr - > json ) ;
r = json_variant_set_field ( & v , " status " , json_variant_by_key ( h - > record - > json , " status " ) ) ;
if ( r < 0 )
return r ;
new_hr = user_record_new ( ) ;
if ( ! new_hr )
return - ENOMEM ;
r = user_record_load ( new_hr , v , USER_RECORD_LOAD_REFUSE_SECRET ) ;
if ( r < 0 )
return r ;
hr = new_hr ;
}
other = hashmap_get ( h - > manager - > homes_by_uid , UID_TO_PTR ( hr - > uid ) ) ;
if ( other & & other ! = h )
return - EBUSY ;
if ( h - > uid ! = hr - > uid ) {
r = hashmap_remove_and_replace ( h - > manager - > homes_by_uid , UID_TO_PTR ( h - > uid ) , UID_TO_PTR ( hr - > uid ) , h ) ;
if ( r < 0 )
return r ;
}
user_record_unref ( h - > record ) ;
h - > record = user_record_ref ( hr ) ;
h - > uid = h - > record - > uid ;
/* The updated record might have a different autologin setting, trigger a PropertiesChanged event for it */
( void ) bus_manager_emit_auto_login_changed ( h - > manager ) ;
( void ) bus_home_emit_change ( h ) ;
return 0 ;
}
int home_save_record ( Home * h ) {
_cleanup_ ( json_variant_unrefp ) JsonVariant * v = NULL ;
_cleanup_free_ char * text = NULL ;
const char * fn ;
int r ;
assert ( h ) ;
v = json_variant_ref ( h - > record - > json ) ;
r = json_variant_normalize ( & v ) ;
if ( r < 0 )
log_warning_errno ( r , " User record could not be normalized. " ) ;
r = json_variant_format ( v , JSON_FORMAT_PRETTY | JSON_FORMAT_NEWLINE , & text ) ;
if ( r < 0 )
return r ;
( void ) mkdir ( " /var/lib/systemd/ " , 0755 ) ;
( void ) mkdir ( " /var/lib/systemd/home/ " , 0700 ) ;
fn = strjoina ( " /var/lib/systemd/home/ " , h - > user_name , " .identity " ) ;
r = write_string_file ( fn , text , WRITE_STRING_FILE_ATOMIC | WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_MODE_0600 ) ;
if ( r < 0 )
return r ;
return 0 ;
}
int home_unlink_record ( Home * h ) {
const char * fn ;
assert ( h ) ;
fn = strjoina ( " /var/lib/systemd/home/ " , h - > user_name , " .identity " ) ;
if ( unlink ( fn ) < 0 & & errno ! = ENOENT )
return - errno ;
fn = strjoina ( " /run/systemd/home/ " , h - > user_name , " .ref " ) ;
if ( unlink ( fn ) < 0 & & errno ! = ENOENT )
return - errno ;
return 0 ;
}
static void home_set_state ( Home * h , HomeState state ) {
HomeState old_state , new_state ;
assert ( h ) ;
old_state = home_get_state ( h ) ;
h - > state = state ;
new_state = home_get_state ( h ) ; /* Query the new state, since the 'state' variable might be set to -1,
* in which case we synthesize an high - level state on demand */
log_info ( " %s: changing state %s → %s " , h - > user_name ,
home_state_to_string ( old_state ) ,
home_state_to_string ( new_state ) ) ;
if ( HOME_STATE_IS_EXECUTING_OPERATION ( old_state ) & & ! HOME_STATE_IS_EXECUTING_OPERATION ( new_state ) ) {
/* If we just finished executing some operation, process the queue of pending operations. And
* enqueue it for GC too . */
home_schedule_operation ( h , NULL , NULL ) ;
manager_enqueue_gc ( h - > manager , h ) ;
}
}
static int home_parse_worker_stdout ( int _fd , UserRecord * * ret ) {
_cleanup_ ( json_variant_unrefp ) JsonVariant * v = NULL ;
_cleanup_close_ int fd = _fd ; /* take possession, even on failure */
_cleanup_ ( user_record_unrefp ) UserRecord * hr = NULL ;
_cleanup_fclose_ FILE * f = NULL ;
unsigned line , column ;
struct stat st ;
int r ;
if ( fstat ( fd , & st ) < 0 )
return log_error_errno ( errno , " Failed to stat stdout fd: %m " ) ;
assert ( S_ISREG ( st . st_mode ) ) ;
if ( st . st_size = = 0 ) { /* empty record */
* ret = NULL ;
return 0 ;
}
if ( lseek ( fd , SEEK_SET , 0 ) = = ( off_t ) - 1 )
return log_error_errno ( errno , " Failed to seek to beginning of memfd: %m " ) ;
2020-03-31 10:07:21 +02:00
f = take_fdopen ( & fd , " r " ) ;
2019-07-04 18:35:39 +02:00
if ( ! f )
return log_error_errno ( errno , " Failed to reopen memfd: %m " ) ;
if ( DEBUG_LOGGING ) {
_cleanup_free_ char * text = NULL ;
r = read_full_stream ( f , & text , NULL ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to read from client: %m " ) ;
log_debug ( " Got from worker: %s " , text ) ;
rewind ( f ) ;
}
r = json_parse_file ( f , " stdout " , JSON_PARSE_SENSITIVE , & v , & line , & column ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to parse identity at %u:%u: %m " , line , column ) ;
hr = user_record_new ( ) ;
if ( ! hr )
return log_oom ( ) ;
r = user_record_load ( hr , v , USER_RECORD_LOAD_REFUSE_SECRET ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to load home record identity: %m " ) ;
* ret = TAKE_PTR ( hr ) ;
return 1 ;
}
static int home_verify_user_record ( Home * h , UserRecord * hr , bool * ret_signed_locally , sd_bus_error * ret_error ) {
int is_signed ;
assert ( h ) ;
assert ( hr ) ;
assert ( ret_signed_locally ) ;
is_signed = manager_verify_user_record ( h - > manager , hr ) ;
switch ( is_signed ) {
case USER_RECORD_SIGNED_EXCLUSIVE :
log_info ( " Home %s is signed exclusively by our key, accepting. " , hr - > user_name ) ;
* ret_signed_locally = true ;
return 0 ;
case USER_RECORD_SIGNED :
log_info ( " Home %s is signed by our key (and others), accepting. " , hr - > user_name ) ;
* ret_signed_locally = false ;
return 0 ;
case USER_RECORD_FOREIGN :
log_info ( " Home %s is signed by foreign key we like, accepting. " , hr - > user_name ) ;
* ret_signed_locally = false ;
return 0 ;
case USER_RECORD_UNSIGNED :
sd_bus_error_setf ( ret_error , BUS_ERROR_BAD_SIGNATURE , " User record %s is not signed at all, refusing. " , hr - > user_name ) ;
return log_error_errno ( SYNTHETIC_ERRNO ( EPERM ) , " Home %s contains user record that is not signed at all, refusing. " , hr - > user_name ) ;
case - ENOKEY :
sd_bus_error_setf ( ret_error , BUS_ERROR_BAD_SIGNATURE , " User record %s is not signed by any known key, refusing. " , hr - > user_name ) ;
2020-03-31 12:50:13 +02:00
return log_error_errno ( is_signed , " Home %s contains user record that is not signed by any known key, refusing. " , hr - > user_name ) ;
2019-07-04 18:35:39 +02:00
default :
assert ( is_signed < 0 ) ;
return log_error_errno ( is_signed , " Failed to verify signature on user record for %s, refusing fixation: %m " , hr - > user_name ) ;
}
}
static int convert_worker_errno ( Home * h , int e , sd_bus_error * error ) {
/* Converts the error numbers the worker process returned into somewhat sensible dbus errors */
switch ( e ) {
case - EMSGSIZE :
2020-04-16 17:50:21 +02:00
return sd_bus_error_setf ( error , BUS_ERROR_BAD_HOME_SIZE , " File systems of this type cannot be shrunk " ) ;
2019-07-04 18:35:39 +02:00
case - ETXTBSY :
2020-04-16 17:50:21 +02:00
return sd_bus_error_setf ( error , BUS_ERROR_BAD_HOME_SIZE , " File systems of this type can only be shrunk offline " ) ;
2019-07-04 18:35:39 +02:00
case - ERANGE :
return sd_bus_error_setf ( error , BUS_ERROR_BAD_HOME_SIZE , " File system size too small " ) ;
case - ENOLINK :
return sd_bus_error_setf ( error , SD_BUS_ERROR_NOT_SUPPORTED , " System does not support selected storage backend " ) ;
case - EPROTONOSUPPORT :
return sd_bus_error_setf ( error , SD_BUS_ERROR_NOT_SUPPORTED , " System does not support selected file system " ) ;
case - ENOTTY :
return sd_bus_error_setf ( error , SD_BUS_ERROR_NOT_SUPPORTED , " Operation not supported on storage backend " ) ;
case - ESOCKTNOSUPPORT :
return sd_bus_error_setf ( error , SD_BUS_ERROR_NOT_SUPPORTED , " Operation not supported on file system " ) ;
case - ENOKEY :
return sd_bus_error_setf ( error , BUS_ERROR_BAD_PASSWORD , " Password for home %s is incorrect or not sufficient for authentication. " , h - > user_name ) ;
case - EBADSLT :
return sd_bus_error_setf ( error , BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN , " Password for home %s is incorrect or not sufficient, and configured security token not found either. " , h - > user_name ) ;
case - ENOANO :
return sd_bus_error_setf ( error , BUS_ERROR_TOKEN_PIN_NEEDED , " PIN for security token required. " ) ;
case - ERFKILL :
return sd_bus_error_setf ( error , BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED , " Security token requires protected authentication path. " ) ;
case - EOWNERDEAD :
return sd_bus_error_setf ( error , BUS_ERROR_TOKEN_PIN_LOCKED , " PIN of security token locked. " ) ;
case - ENOLCK :
return sd_bus_error_setf ( error , BUS_ERROR_TOKEN_BAD_PIN , " Bad PIN of security token. " ) ;
case - ETOOMANYREFS :
return sd_bus_error_setf ( error , BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT , " Bad PIN of security token, and only a few tries left. " ) ;
case - EUCLEAN :
return sd_bus_error_setf ( error , BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT , " Bad PIN of security token, and only one try left. " ) ;
case - EBUSY :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " Home %s is currently being used, or an operation on home %s is currently being executed. " , h - > user_name , h - > user_name ) ;
case - ENOEXEC :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_NOT_ACTIVE , " Home %s is currently not active " , h - > user_name ) ;
case - ENOSPC :
return sd_bus_error_setf ( error , BUS_ERROR_NO_DISK_SPACE , " Not enough disk space for home %s " , h - > user_name ) ;
}
return 0 ;
}
static void home_count_bad_authentication ( Home * h , bool save ) {
int r ;
assert ( h ) ;
r = user_record_bad_authentication ( h - > record ) ;
if ( r < 0 ) {
log_warning_errno ( r , " Failed to increase bad authentication counter, ignoring: %m " ) ;
return ;
}
if ( save ) {
r = home_save_record ( h ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to write home record to disk, ignoring: %m " ) ;
}
}
static void home_fixate_finish ( Home * h , int ret , UserRecord * hr ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
_cleanup_ ( user_record_unrefp ) UserRecord * secret = NULL ;
bool signed_locally ;
int r ;
assert ( h ) ;
assert ( IN_SET ( h - > state , HOME_FIXATING , HOME_FIXATING_FOR_ACTIVATION , HOME_FIXATING_FOR_ACQUIRE ) ) ;
secret = TAKE_PTR ( h - > secret ) ; /* Take possession */
if ( ret < 0 ) {
if ( ret = = - ENOKEY )
( void ) home_count_bad_authentication ( h , false ) ;
( void ) convert_worker_errno ( h , ret , & error ) ;
r = log_error_errno ( ret , " Fixation failed: %m " ) ;
goto fail ;
}
if ( ! hr ) {
r = log_error_errno ( SYNTHETIC_ERRNO ( EIO ) , " Did not receive user record from worker process, fixation failed. " ) ;
goto fail ;
}
r = home_verify_user_record ( h , hr , & signed_locally , & error ) ;
if ( r < 0 )
goto fail ;
r = home_set_record ( h , hr ) ;
if ( r < 0 ) {
log_error_errno ( r , " Failed to update home record: %m " ) ;
goto fail ;
}
h - > signed_locally = signed_locally ;
/* When we finished fixating (and don't follow-up with activation), let's count this as good authentication */
if ( h - > state = = HOME_FIXATING ) {
r = user_record_good_authentication ( h - > record ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to increase good authentication counter, ignoring: %m " ) ;
}
r = home_save_record ( h ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to write home record to disk, ignoring: %m " ) ;
if ( IN_SET ( h - > state , HOME_FIXATING_FOR_ACTIVATION , HOME_FIXATING_FOR_ACQUIRE ) ) {
r = home_start_work ( h , " activate " , h - > record , secret ) ;
if ( r < 0 ) {
h - > current_operation = operation_result_unref ( h - > current_operation , r , NULL ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
} else
home_set_state ( h , h - > state = = HOME_FIXATING_FOR_ACTIVATION ? HOME_ACTIVATING : HOME_ACTIVATING_FOR_ACQUIRE ) ;
return ;
}
log_debug ( " Fixation of %s completed. " , h - > user_name ) ;
h - > current_operation = operation_result_unref ( h - > current_operation , 0 , NULL ) ;
/* Reset the state to "invalid", which makes home_get_state() test if the image exists and returns
* HOME_ABSENT vs . HOME_INACTIVE as necessary . */
home_set_state ( h , _HOME_STATE_INVALID ) ;
return ;
fail :
/* If fixation fails, we stay in unfixated state! */
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
home_set_state ( h , HOME_UNFIXATED ) ;
}
static void home_activate_finish ( Home * h , int ret , UserRecord * hr ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( IN_SET ( h - > state , HOME_ACTIVATING , HOME_ACTIVATING_FOR_ACQUIRE ) ) ;
if ( ret < 0 ) {
if ( ret = = - ENOKEY )
home_count_bad_authentication ( h , true ) ;
( void ) convert_worker_errno ( h , ret , & error ) ;
r = log_error_errno ( ret , " Activation failed: %m " ) ;
goto finish ;
}
if ( hr ) {
bool signed_locally ;
r = home_verify_user_record ( h , hr , & signed_locally , & error ) ;
if ( r < 0 )
goto finish ;
r = home_set_record ( h , hr ) ;
if ( r < 0 ) {
log_error_errno ( r , " Failed to update home record, ignoring: %m " ) ;
goto finish ;
}
h - > signed_locally = signed_locally ;
r = user_record_good_authentication ( h - > record ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to increase good authentication counter, ignoring: %m " ) ;
r = home_save_record ( h ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to write home record to disk, ignoring: %m " ) ;
}
log_debug ( " Activation of %s completed. " , h - > user_name ) ;
r = 0 ;
finish :
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
}
static void home_deactivate_finish ( Home * h , int ret , UserRecord * hr ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( h - > state = = HOME_DEACTIVATING ) ;
assert ( ! hr ) ; /* We don't expect a record on this operation */
if ( ret < 0 ) {
( void ) convert_worker_errno ( h , ret , & error ) ;
r = log_error_errno ( ret , " Deactivation of %s failed: %m " , h - > user_name ) ;
goto finish ;
}
log_debug ( " Deactivation of %s completed. " , h - > user_name ) ;
r = 0 ;
finish :
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
}
static void home_remove_finish ( Home * h , int ret , UserRecord * hr ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
Manager * m ;
int r ;
assert ( h ) ;
assert ( h - > state = = HOME_REMOVING ) ;
assert ( ! hr ) ; /* We don't expect a record on this operation */
m = h - > manager ;
if ( ret < 0 & & ret ! = - EALREADY ) {
( void ) convert_worker_errno ( h , ret , & error ) ;
r = log_error_errno ( ret , " Removing %s failed: %m " , h - > user_name ) ;
goto fail ;
}
/* For a couple of storage types we can't delete the actual data storage when called (such as LUKS on
* partitions like USB sticks , or so ) . Sometimes these storage locations are among those we normally
* automatically discover in / home or in udev . When such a home is deleted let ' s hence issue a rescan
* after completion , so that " unfixated " entries are rediscovered . */
if ( ! IN_SET ( user_record_test_image_path ( h - > record ) , USER_TEST_UNDEFINED , USER_TEST_ABSENT ) )
manager_enqueue_rescan ( m ) ;
/* The image is now removed from disk. Now also remove our stored record */
r = home_unlink_record ( h ) ;
if ( r < 0 ) {
log_error_errno ( r , " Removing record file failed: %m " ) ;
goto fail ;
}
log_debug ( " Removal of %s completed. " , h - > user_name ) ;
h - > current_operation = operation_result_unref ( h - > current_operation , 0 , NULL ) ;
/* Unload this record from memory too now. */
h = home_free ( h ) ;
return ;
fail :
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
}
static void home_create_finish ( Home * h , int ret , UserRecord * hr ) {
int r ;
assert ( h ) ;
assert ( h - > state = = HOME_CREATING ) ;
if ( ret < 0 ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
( void ) convert_worker_errno ( h , ret , & error ) ;
log_error_errno ( ret , " Operation on %s failed: %m " , h - > user_name ) ;
h - > current_operation = operation_result_unref ( h - > current_operation , ret , & error ) ;
if ( h - > unregister_on_failure ) {
( void ) home_unlink_record ( h ) ;
h = home_free ( h ) ;
return ;
}
home_set_state ( h , _HOME_STATE_INVALID ) ;
return ;
}
if ( hr ) {
r = home_set_record ( h , hr ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to update home record, ignoring: %m " ) ;
}
r = home_save_record ( h ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to save record to disk, ignoring: %m " ) ;
log_debug ( " Creation of %s completed. " , h - > user_name ) ;
h - > current_operation = operation_result_unref ( h - > current_operation , 0 , NULL ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
}
static void home_change_finish ( Home * h , int ret , UserRecord * hr ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
if ( ret < 0 ) {
if ( ret = = - ENOKEY )
( void ) home_count_bad_authentication ( h , true ) ;
( void ) convert_worker_errno ( h , ret , & error ) ;
r = log_error_errno ( ret , " Change operation failed: %m " ) ;
goto finish ;
}
if ( hr ) {
r = home_set_record ( h , hr ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to update home record, ignoring: %m " ) ;
else {
r = user_record_good_authentication ( h - > record ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to increase good authentication counter, ignoring: %m " ) ;
r = home_save_record ( h ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to write home record to disk, ignoring: %m " ) ;
}
}
log_debug ( " Change operation of %s completed. " , h - > user_name ) ;
r = 0 ;
finish :
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
}
static void home_locking_finish ( Home * h , int ret , UserRecord * hr ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( h - > state = = HOME_LOCKING ) ;
if ( ret < 0 ) {
( void ) convert_worker_errno ( h , ret , & error ) ;
r = log_error_errno ( ret , " Locking operation failed: %m " ) ;
goto finish ;
}
log_debug ( " Locking operation of %s completed. " , h - > user_name ) ;
h - > current_operation = operation_result_unref ( h - > current_operation , 0 , NULL ) ;
home_set_state ( h , HOME_LOCKED ) ;
return ;
finish :
/* If a specific home doesn't know the concept of locking, then that's totally OK, don't propagate
* the error if we are executing a LockAllHomes ( ) operation . */
if ( h - > current_operation - > type = = OPERATION_LOCK_ALL & & r = = - ENOTTY )
h - > current_operation = operation_result_unref ( h - > current_operation , 0 , NULL ) ;
else
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
}
static void home_unlocking_finish ( Home * h , int ret , UserRecord * hr ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( IN_SET ( h - > state , HOME_UNLOCKING , HOME_UNLOCKING_FOR_ACQUIRE ) ) ;
if ( ret < 0 ) {
if ( ret = = - ENOKEY )
( void ) home_count_bad_authentication ( h , true ) ;
( void ) convert_worker_errno ( h , ret , & error ) ;
r = log_error_errno ( ret , " Unlocking operation failed: %m " ) ;
/* Revert to locked state */
home_set_state ( h , HOME_LOCKED ) ;
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
return ;
}
r = user_record_good_authentication ( h - > record ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to increase good authentication counter, ignoring: %m " ) ;
else {
r = home_save_record ( h ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to write home record to disk, ignoring: %m " ) ;
}
log_debug ( " Unlocking operation of %s completed. " , h - > user_name ) ;
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
return ;
}
static void home_authenticating_finish ( Home * h , int ret , UserRecord * hr ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( IN_SET ( h - > state , HOME_AUTHENTICATING , HOME_AUTHENTICATING_WHILE_ACTIVE , HOME_AUTHENTICATING_FOR_ACQUIRE ) ) ;
if ( ret < 0 ) {
if ( ret = = - ENOKEY )
( void ) home_count_bad_authentication ( h , true ) ;
( void ) convert_worker_errno ( h , ret , & error ) ;
r = log_error_errno ( ret , " Authentication failed: %m " ) ;
goto finish ;
}
if ( hr ) {
r = home_set_record ( h , hr ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to update home record, ignoring: %m " ) ;
else {
r = user_record_good_authentication ( h - > record ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to increase good authentication counter, ignoring: %m " ) ;
r = home_save_record ( h ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to write home record to disk, ignoring: %m " ) ;
}
}
log_debug ( " Authentication of %s completed. " , h - > user_name ) ;
r = 0 ;
finish :
h - > current_operation = operation_result_unref ( h - > current_operation , r , & error ) ;
home_set_state ( h , _HOME_STATE_INVALID ) ;
}
static int home_on_worker_process ( sd_event_source * s , const siginfo_t * si , void * userdata ) {
_cleanup_ ( user_record_unrefp ) UserRecord * hr = NULL ;
Home * h = userdata ;
int ret ;
assert ( s ) ;
assert ( si ) ;
assert ( h ) ;
assert ( h - > worker_pid = = si - > si_pid ) ;
assert ( h - > worker_event_source ) ;
assert ( h - > worker_stdout_fd > = 0 ) ;
( void ) hashmap_remove_value ( h - > manager - > homes_by_worker_pid , PID_TO_PTR ( h - > worker_pid ) , h ) ;
h - > worker_pid = 0 ;
h - > worker_event_source = sd_event_source_unref ( h - > worker_event_source ) ;
if ( si - > si_code ! = CLD_EXITED ) {
assert ( IN_SET ( si - > si_code , CLD_KILLED , CLD_DUMPED ) ) ;
ret = log_debug_errno ( SYNTHETIC_ERRNO ( EPROTO ) , " Worker process died abnormally with signal %s. " , signal_to_string ( si - > si_status ) ) ;
} else if ( si - > si_status ! = EXIT_SUCCESS ) {
/* If we received an error code via sd_notify(), use it */
if ( h - > worker_error_code ! = 0 )
ret = log_debug_errno ( h - > worker_error_code , " Worker reported error code %s. " , errno_to_name ( h - > worker_error_code ) ) ;
else
ret = log_debug_errno ( SYNTHETIC_ERRNO ( EPROTO ) , " Worker exited with exit code %i. " , si - > si_status ) ;
} else
ret = home_parse_worker_stdout ( TAKE_FD ( h - > worker_stdout_fd ) , & hr ) ;
h - > worker_stdout_fd = safe_close ( h - > worker_stdout_fd ) ;
switch ( h - > state ) {
case HOME_FIXATING :
case HOME_FIXATING_FOR_ACTIVATION :
case HOME_FIXATING_FOR_ACQUIRE :
home_fixate_finish ( h , ret , hr ) ;
break ;
case HOME_ACTIVATING :
case HOME_ACTIVATING_FOR_ACQUIRE :
home_activate_finish ( h , ret , hr ) ;
break ;
case HOME_DEACTIVATING :
home_deactivate_finish ( h , ret , hr ) ;
break ;
case HOME_LOCKING :
home_locking_finish ( h , ret , hr ) ;
break ;
case HOME_UNLOCKING :
case HOME_UNLOCKING_FOR_ACQUIRE :
home_unlocking_finish ( h , ret , hr ) ;
break ;
case HOME_CREATING :
home_create_finish ( h , ret , hr ) ;
break ;
case HOME_REMOVING :
home_remove_finish ( h , ret , hr ) ;
break ;
case HOME_UPDATING :
case HOME_UPDATING_WHILE_ACTIVE :
case HOME_RESIZING :
case HOME_RESIZING_WHILE_ACTIVE :
case HOME_PASSWD :
case HOME_PASSWD_WHILE_ACTIVE :
home_change_finish ( h , ret , hr ) ;
break ;
case HOME_AUTHENTICATING :
case HOME_AUTHENTICATING_WHILE_ACTIVE :
case HOME_AUTHENTICATING_FOR_ACQUIRE :
home_authenticating_finish ( h , ret , hr ) ;
break ;
default :
assert_not_reached ( " Unexpected state after worker exited " ) ;
}
return 0 ;
}
static int home_start_work ( Home * h , const char * verb , UserRecord * hr , UserRecord * secret ) {
_cleanup_ ( json_variant_unrefp ) JsonVariant * v = NULL ;
_cleanup_ ( erase_and_freep ) char * formatted = NULL ;
_cleanup_close_ int stdin_fd = - 1 , stdout_fd = - 1 ;
pid_t pid = 0 ;
int r ;
assert ( h ) ;
assert ( verb ) ;
assert ( hr ) ;
if ( h - > worker_pid ! = 0 )
return - EBUSY ;
assert ( h - > worker_stdout_fd < 0 ) ;
assert ( ! h - > worker_event_source ) ;
v = json_variant_ref ( hr - > json ) ;
if ( secret ) {
JsonVariant * sub = NULL ;
sub = json_variant_by_key ( secret - > json , " secret " ) ;
if ( ! sub )
return - ENOKEY ;
r = json_variant_set_field ( & v , " secret " , sub ) ;
if ( r < 0 )
return r ;
}
r = json_variant_format ( v , 0 , & formatted ) ;
if ( r < 0 )
return r ;
stdin_fd = acquire_data_fd ( formatted , strlen ( formatted ) , 0 ) ;
if ( stdin_fd < 0 )
return stdin_fd ;
log_debug ( " Sending to worker: %s " , formatted ) ;
stdout_fd = memfd_create ( " homework-stdout " , MFD_CLOEXEC ) ;
if ( stdout_fd < 0 )
return - errno ;
r = safe_fork_full ( " (sd-homework) " ,
( int [ ] ) { stdin_fd , stdout_fd } , 2 ,
FORK_RESET_SIGNALS | FORK_CLOSE_ALL_FDS | FORK_DEATHSIG | FORK_LOG , & pid ) ;
if ( r < 0 )
return r ;
if ( r = = 0 ) {
2020-05-05 18:27:37 +02:00
const char * homework ;
2019-07-04 18:35:39 +02:00
/* Child */
if ( setenv ( " NOTIFY_SOCKET " , " /run/systemd/home/notify " , 1 ) < 0 ) {
log_error_errno ( errno , " Failed to set $NOTIFY_SOCKET: %m " ) ;
_exit ( EXIT_FAILURE ) ;
}
2020-05-05 09:57:04 +02:00
if ( h - > manager - > default_storage > = 0 )
if ( setenv ( " SYSTEMD_HOME_DEFAULT_STORAGE " , user_storage_to_string ( h - > manager - > default_storage ) , 1 ) < 0 ) {
log_error_errno ( errno , " Failed to set $SYSTEMD_HOME_DEFAULT_STORAGE: %m " ) ;
_exit ( EXIT_FAILURE ) ;
}
if ( h - > manager - > default_file_system_type )
if ( setenv ( " SYSTEMD_HOME_DEFAULT_FILE_SYSTEM_TYPE " , h - > manager - > default_file_system_type , 1 ) < 0 ) {
log_error_errno ( errno , " Failed to set $SYSTEMD_HOME_DEFAULT_FILE_SYSTEM_TYPE: %m " ) ;
_exit ( EXIT_FAILURE ) ;
}
2019-07-04 18:35:39 +02:00
r = rearrange_stdio ( stdin_fd , stdout_fd , STDERR_FILENO ) ;
if ( r < 0 ) {
log_error_errno ( r , " Failed to rearrange stdin/stdout/stderr: %m " ) ;
_exit ( EXIT_FAILURE ) ;
}
stdin_fd = stdout_fd = - 1 ; /* have been invalidated by rearrange_stdio() */
2020-05-05 18:27:37 +02:00
/* Allow overriding the homework path via an environment variable, to make debugging
* easier . */
homework = getenv ( " SYSTEMD_HOMEWORK_PATH " ) ? : SYSTEMD_HOMEWORK_PATH ;
execl ( homework , homework , verb , NULL ) ;
2019-07-04 18:35:39 +02:00
log_error_errno ( errno , " Failed to invoke " SYSTEMD_HOMEWORK_PATH " : %m " ) ;
_exit ( EXIT_FAILURE ) ;
}
r = sd_event_add_child ( h - > manager - > event , & h - > worker_event_source , pid , WEXITED , home_on_worker_process , h ) ;
if ( r < 0 )
return r ;
( void ) sd_event_source_set_description ( h - > worker_event_source , " worker " ) ;
r = hashmap_put ( h - > manager - > homes_by_worker_pid , PID_TO_PTR ( pid ) , h ) ;
if ( r < 0 ) {
h - > worker_event_source = sd_event_source_unref ( h - > worker_event_source ) ;
return r ;
}
h - > worker_stdout_fd = TAKE_FD ( stdout_fd ) ;
h - > worker_pid = pid ;
h - > worker_error_code = 0 ;
return 0 ;
}
static int home_ratelimit ( Home * h , sd_bus_error * error ) {
int r , ret ;
assert ( h ) ;
ret = user_record_ratelimit ( h - > record ) ;
if ( ret < 0 )
return ret ;
if ( h - > state ! = HOME_UNFIXATED ) {
r = home_save_record ( h ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to save updated record, ignoring: %m " ) ;
}
if ( ret = = 0 ) {
char buf [ FORMAT_TIMESPAN_MAX ] ;
usec_t t , n ;
n = now ( CLOCK_REALTIME ) ;
t = user_record_ratelimit_next_try ( h - > record ) ;
if ( t ! = USEC_INFINITY & & t > n )
return sd_bus_error_setf ( error , BUS_ERROR_AUTHENTICATION_LIMIT_HIT , " Too many login attempts, please try again in %s! " ,
format_timespan ( buf , sizeof ( buf ) , t - n , USEC_PER_SEC ) ) ;
return sd_bus_error_setf ( error , BUS_ERROR_AUTHENTICATION_LIMIT_HIT , " Too many login attempts, please try again later. " ) ;
}
return 0 ;
}
static int home_fixate_internal (
Home * h ,
UserRecord * secret ,
HomeState for_state ,
sd_bus_error * error ) {
int r ;
assert ( h ) ;
assert ( IN_SET ( for_state , HOME_FIXATING , HOME_FIXATING_FOR_ACTIVATION , HOME_FIXATING_FOR_ACQUIRE ) ) ;
r = home_start_work ( h , " inspect " , h - > record , secret ) ;
if ( r < 0 )
return r ;
if ( for_state = = HOME_FIXATING_FOR_ACTIVATION ) {
/* Remember the secret data, since we need it for the activation again, later on. */
user_record_unref ( h - > secret ) ;
h - > secret = user_record_ref ( secret ) ;
}
home_set_state ( h , for_state ) ;
return 0 ;
}
int home_fixate ( Home * h , UserRecord * secret , sd_bus_error * error ) {
int r ;
assert ( h ) ;
switch ( home_get_state ( h ) ) {
case HOME_ABSENT :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_ABSENT , " Home %s is currently missing or not plugged in. " , h - > user_name ) ;
case HOME_INACTIVE :
case HOME_ACTIVE :
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_ALREADY_FIXATED , " Home %s is already fixated. " , h - > user_name ) ;
case HOME_UNFIXATED :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
r = home_ratelimit ( h , error ) ;
if ( r < 0 )
return r ;
return home_fixate_internal ( h , secret , HOME_FIXATING , error ) ;
}
static int home_activate_internal ( Home * h , UserRecord * secret , HomeState for_state , sd_bus_error * error ) {
int r ;
assert ( h ) ;
assert ( IN_SET ( for_state , HOME_ACTIVATING , HOME_ACTIVATING_FOR_ACQUIRE ) ) ;
r = home_start_work ( h , " activate " , h - > record , secret ) ;
if ( r < 0 )
return r ;
home_set_state ( h , for_state ) ;
return 0 ;
}
int home_activate ( Home * h , UserRecord * secret , sd_bus_error * error ) {
int r ;
assert ( h ) ;
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
return home_fixate_internal ( h , secret , HOME_FIXATING_FOR_ACTIVATION , error ) ;
case HOME_ABSENT :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_ABSENT , " Home %s is currently missing or not plugged in. " , h - > user_name ) ;
case HOME_ACTIVE :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_ALREADY_ACTIVE , " Home %s is already active. " , h - > user_name ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
case HOME_INACTIVE :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
r = home_ratelimit ( h , error ) ;
if ( r < 0 )
return r ;
return home_activate_internal ( h , secret , HOME_ACTIVATING , error ) ;
}
static int home_authenticate_internal ( Home * h , UserRecord * secret , HomeState for_state , sd_bus_error * error ) {
int r ;
assert ( h ) ;
assert ( IN_SET ( for_state , HOME_AUTHENTICATING , HOME_AUTHENTICATING_WHILE_ACTIVE , HOME_AUTHENTICATING_FOR_ACQUIRE ) ) ;
r = home_start_work ( h , " inspect " , h - > record , secret ) ;
if ( r < 0 )
return r ;
home_set_state ( h , for_state ) ;
return 0 ;
}
int home_authenticate ( Home * h , UserRecord * secret , sd_bus_error * error ) {
HomeState state ;
int r ;
assert ( h ) ;
state = home_get_state ( h ) ;
switch ( state ) {
case HOME_ABSENT :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_ABSENT , " Home %s is currently missing or not plugged in. " , h - > user_name ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
case HOME_UNFIXATED :
case HOME_INACTIVE :
case HOME_ACTIVE :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
r = home_ratelimit ( h , error ) ;
if ( r < 0 )
return r ;
return home_authenticate_internal ( h , secret , state = = HOME_ACTIVE ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING , error ) ;
}
static int home_deactivate_internal ( Home * h , bool force , sd_bus_error * error ) {
int r ;
assert ( h ) ;
r = home_start_work ( h , force ? " deactivate-force " : " deactivate " , h - > record , NULL ) ;
if ( r < 0 )
return r ;
home_set_state ( h , HOME_DEACTIVATING ) ;
return 0 ;
}
int home_deactivate ( Home * h , bool force , sd_bus_error * error ) {
assert ( h ) ;
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
case HOME_ABSENT :
case HOME_INACTIVE :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_NOT_ACTIVE , " Home %s not active. " , h - > user_name ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
case HOME_ACTIVE :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
return home_deactivate_internal ( h , force , error ) ;
}
int home_create ( Home * h , UserRecord * secret , sd_bus_error * error ) {
int r ;
assert ( h ) ;
switch ( home_get_state ( h ) ) {
case HOME_INACTIVE :
if ( h - > record - > storage < 0 )
break ; /* if no storage is defined we don't know what precisely to look for, hence
* HOME_INACTIVE is OK in that case too . */
if ( IN_SET ( user_record_test_image_path ( h - > record ) , USER_TEST_MAYBE , USER_TEST_UNDEFINED ) )
break ; /* And if the image path test isn't conclusive, let's also go on */
_fallthrough_ ;
case HOME_UNFIXATED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_EXISTS , " Home of user %s already exists. " , h - > user_name ) ;
case HOME_ABSENT :
break ;
case HOME_ACTIVE :
case HOME_LOCKED :
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " Home %s is currently being used, or an operation on home %s is currently being executed. " , h - > user_name , h - > user_name ) ;
}
if ( h - > record - > enforce_password_policy = = false )
log_debug ( " Password quality check turned off for account, skipping. " ) ;
else {
r = quality_check_password ( h - > record , secret , error ) ;
if ( r < 0 )
return r ;
}
r = home_start_work ( h , " create " , h - > record , secret ) ;
if ( r < 0 )
return r ;
home_set_state ( h , HOME_CREATING ) ;
return 0 ;
}
int home_remove ( Home * h , sd_bus_error * error ) {
HomeState state ;
int r ;
assert ( h ) ;
state = home_get_state ( h ) ;
switch ( state ) {
case HOME_ABSENT : /* If the home directory is absent, then this is just like unregistering */
return home_unregister ( h , error ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
case HOME_UNFIXATED :
case HOME_INACTIVE :
break ;
case HOME_ACTIVE :
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " Home %s is currently being used, or an operation on home %s is currently being executed. " , h - > user_name , h - > user_name ) ;
}
r = home_start_work ( h , " remove " , h - > record , NULL ) ;
if ( r < 0 )
return r ;
home_set_state ( h , HOME_REMOVING ) ;
return 0 ;
}
static int user_record_extend_with_binding ( UserRecord * hr , UserRecord * with_binding , UserRecordLoadFlags flags , UserRecord * * ret ) {
_cleanup_ ( json_variant_unrefp ) JsonVariant * v = NULL ;
_cleanup_ ( user_record_unrefp ) UserRecord * nr = NULL ;
JsonVariant * binding ;
int r ;
assert ( hr ) ;
assert ( with_binding ) ;
assert ( ret ) ;
assert_se ( v = json_variant_ref ( hr - > json ) ) ;
binding = json_variant_by_key ( with_binding - > json , " binding " ) ;
if ( binding ) {
r = json_variant_set_field ( & v , " binding " , binding ) ;
if ( r < 0 )
return r ;
}
nr = user_record_new ( ) ;
if ( ! nr )
return - ENOMEM ;
r = user_record_load ( nr , v , flags ) ;
if ( r < 0 )
return r ;
* ret = TAKE_PTR ( nr ) ;
return 0 ;
}
static int home_update_internal ( Home * h , const char * verb , UserRecord * hr , UserRecord * secret , sd_bus_error * error ) {
_cleanup_ ( user_record_unrefp ) UserRecord * new_hr = NULL , * saved_secret = NULL , * signed_hr = NULL ;
int r , c ;
assert ( h ) ;
assert ( verb ) ;
assert ( hr ) ;
if ( ! user_record_compatible ( hr , h - > record ) )
return sd_bus_error_setf ( error , BUS_ERROR_HOME_RECORD_MISMATCH , " Updated user record is not compatible with existing one. " ) ;
c = user_record_compare_last_change ( hr , h - > record ) ; /* refuse downgrades */
if ( c < 0 )
return sd_bus_error_setf ( error , BUS_ERROR_HOME_RECORD_DOWNGRADE , " Refusing to update to older home record. " ) ;
if ( ! secret & & FLAGS_SET ( hr - > mask , USER_RECORD_SECRET ) ) {
r = user_record_clone ( hr , USER_RECORD_EXTRACT_SECRET , & saved_secret ) ;
if ( r < 0 )
return r ;
secret = saved_secret ;
}
r = manager_verify_user_record ( h - > manager , hr ) ;
switch ( r ) {
case USER_RECORD_UNSIGNED :
if ( h - > signed_locally < = 0 ) /* If the existing record is not owned by us, don't accept an
* unsigned new record . i . e . only implicitly sign new records
* that where previously signed by us too . */
return sd_bus_error_setf ( error , BUS_ERROR_HOME_RECORD_SIGNED , " Home %s is signed and cannot be modified locally. " , h - > user_name ) ;
/* The updated record is not signed, then do so now */
r = manager_sign_user_record ( h - > manager , hr , & signed_hr , error ) ;
if ( r < 0 )
return r ;
hr = signed_hr ;
break ;
case USER_RECORD_SIGNED_EXCLUSIVE :
case USER_RECORD_SIGNED :
case USER_RECORD_FOREIGN :
/* Has already been signed. Great! */
break ;
case - ENOKEY :
default :
return r ;
}
r = user_record_extend_with_binding ( hr , h - > record , USER_RECORD_LOAD_MASK_SECRET , & new_hr ) ;
if ( r < 0 )
return r ;
if ( c = = 0 ) {
/* different payload but same lastChangeUSec field? That's not cool! */
r = user_record_masked_equal ( new_hr , h - > record , USER_RECORD_REGULAR | USER_RECORD_PRIVILEGED | USER_RECORD_PER_MACHINE ) ;
if ( r < 0 )
return r ;
if ( r = = 0 )
return sd_bus_error_setf ( error , BUS_ERROR_HOME_RECORD_MISMATCH , " Home record different but timestamp remained the same, refusing. " ) ;
}
r = home_start_work ( h , verb , new_hr , secret ) ;
if ( r < 0 )
return r ;
return 0 ;
}
int home_update ( Home * h , UserRecord * hr , sd_bus_error * error ) {
HomeState state ;
int r ;
assert ( h ) ;
assert ( hr ) ;
state = home_get_state ( h ) ;
switch ( state ) {
case HOME_UNFIXATED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_UNFIXATED , " Home %s has not been fixated yet. " , h - > user_name ) ;
case HOME_ABSENT :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_ABSENT , " Home %s is currently missing or not plugged in. " , h - > user_name ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
case HOME_INACTIVE :
case HOME_ACTIVE :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
r = home_ratelimit ( h , error ) ;
if ( r < 0 )
return r ;
r = home_update_internal ( h , " update " , hr , NULL , error ) ;
if ( r < 0 )
return r ;
home_set_state ( h , state = = HOME_ACTIVE ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING ) ;
return 0 ;
}
int home_resize ( Home * h , uint64_t disk_size , UserRecord * secret , sd_bus_error * error ) {
_cleanup_ ( user_record_unrefp ) UserRecord * c = NULL ;
HomeState state ;
int r ;
assert ( h ) ;
state = home_get_state ( h ) ;
switch ( state ) {
case HOME_UNFIXATED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_UNFIXATED , " Home %s has not been fixated yet. " , h - > user_name ) ;
case HOME_ABSENT :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_ABSENT , " Home %s is currently missing or not plugged in. " , h - > user_name ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
case HOME_INACTIVE :
case HOME_ACTIVE :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
r = home_ratelimit ( h , error ) ;
if ( r < 0 )
return r ;
if ( disk_size = = UINT64_MAX | | disk_size = = h - > record - > disk_size ) {
if ( h - > record - > disk_size = = UINT64_MAX )
2020-03-31 12:50:13 +02:00
return sd_bus_error_setf ( error , SD_BUS_ERROR_INVALID_ARGS , " No disk size to resize to specified. " ) ;
2019-07-04 18:35:39 +02:00
c = user_record_ref ( h - > record ) ; /* Shortcut if size is unspecified or matches the record */
} else {
_cleanup_ ( user_record_unrefp ) UserRecord * signed_c = NULL ;
if ( h - > signed_locally < = 0 ) /* Don't allow changing of records not signed only by us */
return sd_bus_error_setf ( error , BUS_ERROR_HOME_RECORD_SIGNED , " Home %s is signed and cannot be modified locally. " , h - > user_name ) ;
r = user_record_clone ( h - > record , USER_RECORD_LOAD_REFUSE_SECRET , & c ) ;
if ( r < 0 )
return r ;
r = user_record_set_disk_size ( c , disk_size ) ;
if ( r = = - ERANGE )
return sd_bus_error_setf ( error , BUS_ERROR_BAD_HOME_SIZE , " Requested size for home %s out of acceptable range. " , h - > user_name ) ;
if ( r < 0 )
return r ;
r = user_record_update_last_changed ( c , false ) ;
if ( r = = - ECHRNG )
return sd_bus_error_setf ( error , BUS_ERROR_HOME_RECORD_MISMATCH , " Record last change time of %s is newer than current time, cannot update. " , h - > user_name ) ;
if ( r < 0 )
return r ;
r = manager_sign_user_record ( h - > manager , c , & signed_c , error ) ;
if ( r < 0 )
return r ;
user_record_unref ( c ) ;
c = TAKE_PTR ( signed_c ) ;
}
r = home_update_internal ( h , " resize " , c , secret , error ) ;
if ( r < 0 )
return r ;
home_set_state ( h , state = = HOME_ACTIVE ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING ) ;
return 0 ;
}
static int home_may_change_password (
Home * h ,
sd_bus_error * error ) {
int r ;
assert ( h ) ;
r = user_record_test_password_change_required ( h - > record ) ;
if ( IN_SET ( r , - EKEYREVOKED , - EOWNERDEAD , - EKEYEXPIRED ) )
2020-04-21 20:46:53 +02:00
return 0 ; /* expired in some form, but changing is allowed */
2019-07-04 18:35:39 +02:00
if ( IN_SET ( r , - EKEYREJECTED , - EROFS ) )
return sd_bus_error_setf ( error , SD_BUS_ERROR_ACCESS_DENIED , " Expiration settings of account %s do not allow changing of password. " , h - > user_name ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to test password expiry: %m " ) ;
return 0 ; /* not expired */
}
int home_passwd ( Home * h ,
UserRecord * new_secret ,
UserRecord * old_secret ,
sd_bus_error * error ) {
_cleanup_ ( user_record_unrefp ) UserRecord * c = NULL , * merged_secret = NULL , * signed_c = NULL ;
HomeState state ;
int r ;
assert ( h ) ;
if ( h - > signed_locally < = 0 ) /* Don't allow changing of records not signed only by us */
return sd_bus_error_setf ( error , BUS_ERROR_HOME_RECORD_SIGNED , " Home %s is signed and cannot be modified locally. " , h - > user_name ) ;
state = home_get_state ( h ) ;
switch ( state ) {
case HOME_UNFIXATED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_UNFIXATED , " Home %s has not been fixated yet. " , h - > user_name ) ;
case HOME_ABSENT :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_ABSENT , " Home %s is currently missing or not plugged in. " , h - > user_name ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
case HOME_INACTIVE :
case HOME_ACTIVE :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
r = home_ratelimit ( h , error ) ;
if ( r < 0 )
return r ;
r = home_may_change_password ( h , error ) ;
if ( r < 0 )
return r ;
r = user_record_clone ( h - > record , USER_RECORD_LOAD_REFUSE_SECRET , & c ) ;
if ( r < 0 )
return r ;
merged_secret = user_record_new ( ) ;
if ( ! merged_secret )
return - ENOMEM ;
r = user_record_merge_secret ( merged_secret , old_secret ) ;
if ( r < 0 )
return r ;
r = user_record_merge_secret ( merged_secret , new_secret ) ;
if ( r < 0 )
return r ;
if ( ! strv_isempty ( new_secret - > password ) ) {
/* Update the password only if one is specified, otherwise let's just reuse the old password
* data . This is useful as a way to propagate updated user records into the LUKS backends
* properly . */
r = user_record_make_hashed_password ( c , new_secret - > password , /* extend = */ false ) ;
if ( r < 0 )
return r ;
r = user_record_set_password_change_now ( c , - 1 /* remove */ ) ;
if ( r < 0 )
return r ;
}
r = user_record_update_last_changed ( c , true ) ;
if ( r = = - ECHRNG )
return sd_bus_error_setf ( error , BUS_ERROR_HOME_RECORD_MISMATCH , " Record last change time of %s is newer than current time, cannot update. " , h - > user_name ) ;
if ( r < 0 )
return r ;
r = manager_sign_user_record ( h - > manager , c , & signed_c , error ) ;
if ( r < 0 )
return r ;
if ( c - > enforce_password_policy = = false )
log_debug ( " Password quality check turned off for account, skipping. " ) ;
else {
r = quality_check_password ( c , merged_secret , error ) ;
if ( r < 0 )
return r ;
}
r = home_update_internal ( h , " passwd " , signed_c , merged_secret , error ) ;
if ( r < 0 )
return r ;
home_set_state ( h , state = = HOME_ACTIVE ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD ) ;
return 0 ;
}
int home_unregister ( Home * h , sd_bus_error * error ) {
int r ;
assert ( h ) ;
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_UNFIXATED , " Home %s is not registered. " , h - > user_name ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
case HOME_ABSENT :
case HOME_INACTIVE :
break ;
case HOME_ACTIVE :
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " Home %s is currently being used, or an operation on home %s is currently being executed. " , h - > user_name , h - > user_name ) ;
}
r = home_unlink_record ( h ) ;
if ( r < 0 )
return r ;
/* And destroy the whole entry. The caller needs to be prepared for that. */
h = home_free ( h ) ;
return 1 ;
}
int home_lock ( Home * h , sd_bus_error * error ) {
int r ;
assert ( h ) ;
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
case HOME_ABSENT :
case HOME_INACTIVE :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_NOT_ACTIVE , " Home %s is not active. " , h - > user_name ) ;
case HOME_LOCKED :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_LOCKED , " Home %s is already locked. " , h - > user_name ) ;
case HOME_ACTIVE :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
r = home_start_work ( h , " lock " , h - > record , NULL ) ;
if ( r < 0 )
return r ;
home_set_state ( h , HOME_LOCKING ) ;
return 0 ;
}
static int home_unlock_internal ( Home * h , UserRecord * secret , HomeState for_state , sd_bus_error * error ) {
int r ;
assert ( h ) ;
assert ( IN_SET ( for_state , HOME_UNLOCKING , HOME_UNLOCKING_FOR_ACQUIRE ) ) ;
r = home_start_work ( h , " unlock " , h - > record , secret ) ;
if ( r < 0 )
return r ;
home_set_state ( h , for_state ) ;
return 0 ;
}
int home_unlock ( Home * h , UserRecord * secret , sd_bus_error * error ) {
int r ;
assert ( h ) ;
r = home_ratelimit ( h , error ) ;
if ( r < 0 )
return r ;
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
case HOME_ABSENT :
case HOME_INACTIVE :
case HOME_ACTIVE :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_NOT_LOCKED , " Home %s is not locked. " , h - > user_name ) ;
case HOME_LOCKED :
break ;
default :
return sd_bus_error_setf ( error , BUS_ERROR_HOME_BUSY , " An operation on home %s is currently being executed. " , h - > user_name ) ;
}
return home_unlock_internal ( h , secret , HOME_UNLOCKING , error ) ;
}
HomeState home_get_state ( Home * h ) {
assert ( h ) ;
/* When the state field is initialized, it counts. */
if ( h - > state > = 0 )
return h - > state ;
/* Otherwise, let's see if the home directory is mounted. If so, we assume for sure the home
* directory is active */
if ( user_record_test_home_directory ( h - > record ) = = USER_TEST_MOUNTED )
return HOME_ACTIVE ;
/* And if we see the image being gone, we report this as absent */
if ( user_record_test_image_path ( h - > record ) = = USER_TEST_ABSENT )
return HOME_ABSENT ;
/* And for all other cases we return "inactive". */
return HOME_INACTIVE ;
}
void home_process_notify ( Home * h , char * * l ) {
const char * e ;
int error ;
int r ;
assert ( h ) ;
e = strv_env_get ( l , " ERRNO " ) ;
if ( ! e ) {
log_debug ( " Got notify message lacking ERRNO= field, ignoring. " ) ;
return ;
}
r = safe_atoi ( e , & error ) ;
if ( r < 0 ) {
2020-04-16 17:50:21 +02:00
log_debug_errno ( r , " Failed to parse received error number, ignoring: %s " , e ) ;
2019-07-04 18:35:39 +02:00
return ;
}
if ( error < = 0 ) {
log_debug ( " Error number is out of range: %i " , error ) ;
return ;
}
h - > worker_error_code = error ;
}
int home_killall ( Home * h ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
_cleanup_free_ char * unit = NULL ;
int r ;
assert ( h ) ;
if ( ! uid_is_valid ( h - > uid ) )
return 0 ;
assert ( h - > uid > 0 ) ; /* We never should be UID 0 */
/* Let's kill everything matching the specified UID */
r = safe_fork ( " (sd-killer) " , FORK_RESET_SIGNALS | FORK_CLOSE_ALL_FDS | FORK_DEATHSIG | FORK_WAIT | FORK_LOG , NULL ) ;
if ( r < 0 )
return r ;
if ( r = = 0 ) {
gid_t gid ;
/* Child */
gid = user_record_gid ( h - > record ) ;
if ( setresgid ( gid , gid , gid ) < 0 ) {
log_error_errno ( errno , " Failed to change GID to " GID_FMT " : %m " , gid ) ;
_exit ( EXIT_FAILURE ) ;
}
if ( setgroups ( 0 , NULL ) < 0 ) {
log_error_errno ( errno , " Failed to reset auxiliary groups list: %m " ) ;
_exit ( EXIT_FAILURE ) ;
}
if ( setresuid ( h - > uid , h - > uid , h - > uid ) < 0 ) {
log_error_errno ( errno , " Failed to change UID to " UID_FMT " : %m " , h - > uid ) ;
_exit ( EXIT_FAILURE ) ;
}
if ( kill ( - 1 , SIGKILL ) < 0 ) {
log_error_errno ( errno , " Failed to kill all processes of UID " UID_FMT " : %m " , h - > uid ) ;
_exit ( EXIT_FAILURE ) ;
}
_exit ( EXIT_SUCCESS ) ;
}
/* Let's also kill everything in the user's slice */
if ( asprintf ( & unit , " user- " UID_FMT " .slice " , h - > uid ) < 0 )
return log_oom ( ) ;
r = sd_bus_call_method (
h - > manager - > bus ,
" org.freedesktop.systemd1 " ,
" /org/freedesktop/systemd1 " ,
" org.freedesktop.systemd1.Manager " ,
" KillUnit " ,
& error ,
NULL ,
" ssi " , unit , " all " , SIGKILL ) ;
if ( r < 0 )
log_full_errno ( sd_bus_error_has_name ( & error , BUS_ERROR_NO_SUCH_UNIT ) ? LOG_DEBUG : LOG_WARNING ,
r , " Failed to kill login processes of user, ignoring: %s " , bus_error_message ( & error , r ) ) ;
return 1 ;
}
static int home_get_disk_status_luks (
Home * h ,
HomeState state ,
uint64_t * ret_disk_size ,
uint64_t * ret_disk_usage ,
uint64_t * ret_disk_free ,
uint64_t * ret_disk_ceiling ,
uint64_t * ret_disk_floor ) {
uint64_t disk_size = UINT64_MAX , disk_usage = UINT64_MAX , disk_free = UINT64_MAX ,
disk_ceiling = UINT64_MAX , disk_floor = UINT64_MAX ,
stat_used = UINT64_MAX , fs_size = UINT64_MAX , header_size = 0 ;
struct statfs sfs ;
const char * hd ;
int r ;
assert ( h ) ;
assert ( ret_disk_size ) ;
assert ( ret_disk_usage ) ;
assert ( ret_disk_free ) ;
assert ( ret_disk_ceiling ) ;
if ( state ! = HOME_ABSENT ) {
const char * ip ;
ip = user_record_image_path ( h - > record ) ;
if ( ip ) {
struct stat st ;
if ( stat ( ip , & st ) < 0 )
log_debug_errno ( errno , " Failed to stat() %s, ignoring: %m " , ip ) ;
else if ( S_ISREG ( st . st_mode ) ) {
_cleanup_free_ char * parent = NULL ;
disk_size = st . st_size ;
stat_used = st . st_blocks * 512 ;
parent = dirname_malloc ( ip ) ;
if ( ! parent )
return log_oom ( ) ;
if ( statfs ( parent , & sfs ) < 0 )
log_debug_errno ( errno , " Failed to statfs() %s, ignoring: %m " , parent ) ;
else
disk_ceiling = stat_used + sfs . f_bsize * sfs . f_bavail ;
} else if ( S_ISBLK ( st . st_mode ) ) {
_cleanup_free_ char * szbuf = NULL ;
char p [ SYS_BLOCK_PATH_MAX ( " /size " ) ] ;
/* Let's read the size off sysfs, so that we don't have to open the device */
xsprintf_sys_block_path ( p , " /size " , st . st_rdev ) ;
r = read_one_line_file ( p , & szbuf ) ;
if ( r < 0 )
log_debug_errno ( r , " Failed to read %s, ignoring: %m " , p ) ;
else {
uint64_t sz ;
r = safe_atou64 ( szbuf , & sz ) ;
if ( r < 0 )
log_debug_errno ( r , " Failed to parse %s, ignoring: %s " , p , szbuf ) ;
else
disk_size = sz * 512 ;
}
} else
log_debug ( " Image path is not a block device or regular file, not able to acquire size. " ) ;
}
}
if ( ! HOME_STATE_IS_ACTIVE ( state ) )
goto finish ;
hd = user_record_home_directory ( h - > record ) ;
if ( ! hd )
goto finish ;
if ( statfs ( hd , & sfs ) < 0 ) {
2020-03-31 12:50:13 +02:00
log_debug_errno ( errno , " Failed to statfs() %s, ignoring: %m " , hd ) ;
2019-07-04 18:35:39 +02:00
goto finish ;
}
disk_free = sfs . f_bsize * sfs . f_bavail ;
fs_size = sfs . f_bsize * sfs . f_blocks ;
if ( disk_size ! = UINT64_MAX & & disk_size > fs_size )
header_size = disk_size - fs_size ;
/* We take a perspective from the user here (as opposed to from the host): the used disk space is the
* difference from the limit and what ' s free . This makes a difference if sparse mode is not used : in
* that case the image is pre - allocated and thus appears all used from the host PoV but is not used
* up at all yet from the user ' s PoV .
*
* That said , we use use the stat ( ) reported loopback file size as upper boundary : our footprint can
* never be larger than what we take up on the lowest layers . */
if ( disk_size ! = UINT64_MAX & & disk_size > disk_free ) {
disk_usage = disk_size - disk_free ;
if ( stat_used ! = UINT64_MAX & & disk_usage > stat_used )
disk_usage = stat_used ;
} else
disk_usage = stat_used ;
/* If we have the magic, determine floor preferably by magic */
disk_floor = minimal_size_by_fs_magic ( sfs . f_type ) + header_size ;
finish :
/* If we don't know the magic, go by file system name */
if ( disk_floor = = UINT64_MAX )
disk_floor = minimal_size_by_fs_name ( user_record_file_system_type ( h - > record ) ) ;
* ret_disk_size = disk_size ;
* ret_disk_usage = disk_usage ;
* ret_disk_free = disk_free ;
* ret_disk_ceiling = disk_ceiling ;
* ret_disk_floor = disk_floor ;
return 0 ;
}
static int home_get_disk_status_directory (
Home * h ,
HomeState state ,
uint64_t * ret_disk_size ,
uint64_t * ret_disk_usage ,
uint64_t * ret_disk_free ,
uint64_t * ret_disk_ceiling ,
uint64_t * ret_disk_floor ) {
uint64_t disk_size = UINT64_MAX , disk_usage = UINT64_MAX , disk_free = UINT64_MAX ,
disk_ceiling = UINT64_MAX , disk_floor = UINT64_MAX ;
struct statfs sfs ;
struct dqblk req ;
const char * path = NULL ;
int r ;
assert ( ret_disk_size ) ;
assert ( ret_disk_usage ) ;
assert ( ret_disk_free ) ;
assert ( ret_disk_ceiling ) ;
assert ( ret_disk_floor ) ;
if ( HOME_STATE_IS_ACTIVE ( state ) )
path = user_record_home_directory ( h - > record ) ;
if ( ! path ) {
if ( state = = HOME_ABSENT )
goto finish ;
path = user_record_image_path ( h - > record ) ;
}
if ( ! path )
goto finish ;
if ( statfs ( path , & sfs ) < 0 )
log_debug_errno ( errno , " Failed to statfs() %s, ignoring: %m " , path ) ;
else {
disk_free = sfs . f_bsize * sfs . f_bavail ;
disk_size = sfs . f_bsize * sfs . f_blocks ;
/* We don't initialize disk_usage from statfs() data here, since the device is likely not used
* by us alone , and disk_usage should only reflect our own use . */
}
if ( IN_SET ( h - > record - > storage , USER_CLASSIC , USER_DIRECTORY , USER_SUBVOLUME ) ) {
r = btrfs_is_subvol ( path ) ;
if ( r < 0 )
log_debug_errno ( r , " Failed to determine whether %s is a btrfs subvolume: %m " , path ) ;
else if ( r > 0 ) {
BtrfsQuotaInfo qi ;
r = btrfs_subvol_get_subtree_quota ( path , 0 , & qi ) ;
if ( r < 0 )
log_debug_errno ( r , " Failed to query btrfs subtree quota, ignoring: %m " ) ;
else {
disk_usage = qi . referenced ;
if ( disk_free ! = UINT64_MAX ) {
disk_ceiling = qi . referenced + disk_free ;
if ( disk_size ! = UINT64_MAX & & disk_ceiling > disk_size )
disk_ceiling = disk_size ;
}
if ( qi . referenced_max ! = UINT64_MAX ) {
if ( disk_size ! = UINT64_MAX )
disk_size = MIN ( qi . referenced_max , disk_size ) ;
else
disk_size = qi . referenced_max ;
}
if ( disk_size ! = UINT64_MAX ) {
if ( disk_size > disk_usage )
disk_free = disk_size - disk_usage ;
else
disk_free = 0 ;
}
}
goto finish ;
}
}
if ( IN_SET ( h - > record - > storage , USER_CLASSIC , USER_DIRECTORY , USER_FSCRYPT ) ) {
r = quotactl_path ( QCMD_FIXED ( Q_GETQUOTA , USRQUOTA ) , path , h - > uid , & req ) ;
if ( r < 0 ) {
if ( ERRNO_IS_NOT_SUPPORTED ( r ) ) {
log_debug_errno ( r , " No UID quota support on %s. " , path ) ;
goto finish ;
}
if ( r ! = - ESRCH ) {
log_debug_errno ( r , " Failed to query disk quota for UID " UID_FMT " : %m " , h - > uid ) ;
goto finish ;
}
disk_usage = 0 ; /* No record of this user? then nothing was used */
} else {
if ( FLAGS_SET ( req . dqb_valid , QIF_SPACE ) & & disk_free ! = UINT64_MAX ) {
disk_ceiling = req . dqb_curspace + disk_free ;
if ( disk_size ! = UINT64_MAX & & disk_ceiling > disk_size )
disk_ceiling = disk_size ;
}
if ( FLAGS_SET ( req . dqb_valid , QIF_BLIMITS ) ) {
uint64_t q ;
/* Take the minimum of the quota and the available disk space here */
q = req . dqb_bhardlimit * QIF_DQBLKSIZE ;
if ( disk_size ! = UINT64_MAX )
disk_size = MIN ( disk_size , q ) ;
else
disk_size = q ;
}
if ( FLAGS_SET ( req . dqb_valid , QIF_SPACE ) ) {
disk_usage = req . dqb_curspace ;
if ( disk_size ! = UINT64_MAX ) {
if ( disk_size > disk_usage )
disk_free = disk_size - disk_usage ;
else
disk_free = 0 ;
}
}
}
}
finish :
* ret_disk_size = disk_size ;
* ret_disk_usage = disk_usage ;
* ret_disk_free = disk_free ;
* ret_disk_ceiling = disk_ceiling ;
* ret_disk_floor = disk_floor ;
return 0 ;
}
int home_augment_status (
Home * h ,
UserRecordLoadFlags flags ,
UserRecord * * ret ) {
uint64_t disk_size = UINT64_MAX , disk_usage = UINT64_MAX , disk_free = UINT64_MAX , disk_ceiling = UINT64_MAX , disk_floor = UINT64_MAX ;
_cleanup_ ( json_variant_unrefp ) JsonVariant * j = NULL , * v = NULL , * m = NULL , * status = NULL ;
_cleanup_ ( user_record_unrefp ) UserRecord * ur = NULL ;
char ids [ SD_ID128_STRING_MAX ] ;
HomeState state ;
sd_id128_t id ;
int r ;
assert ( h ) ;
assert ( ret ) ;
/* We are supposed to add this, this can't be on hence. */
assert ( ! FLAGS_SET ( flags , USER_RECORD_STRIP_STATUS ) ) ;
r = sd_id128_get_machine ( & id ) ;
if ( r < 0 )
return r ;
state = home_get_state ( h ) ;
switch ( h - > record - > storage ) {
case USER_LUKS :
r = home_get_disk_status_luks ( h , state , & disk_size , & disk_usage , & disk_free , & disk_ceiling , & disk_floor ) ;
if ( r < 0 )
return r ;
break ;
case USER_CLASSIC :
case USER_DIRECTORY :
case USER_SUBVOLUME :
case USER_FSCRYPT :
case USER_CIFS :
r = home_get_disk_status_directory ( h , state , & disk_size , & disk_usage , & disk_free , & disk_ceiling , & disk_floor ) ;
if ( r < 0 )
return r ;
break ;
default :
; /* unset */
}
if ( disk_floor = = UINT64_MAX | | ( disk_usage ! = UINT64_MAX & & disk_floor < disk_usage ) )
disk_floor = disk_usage ;
if ( disk_floor = = UINT64_MAX | | disk_floor < USER_DISK_SIZE_MIN )
disk_floor = USER_DISK_SIZE_MIN ;
if ( disk_ceiling = = UINT64_MAX | | disk_ceiling > USER_DISK_SIZE_MAX )
disk_ceiling = USER_DISK_SIZE_MAX ;
r = json_build ( & status ,
JSON_BUILD_OBJECT (
JSON_BUILD_PAIR ( " state " , JSON_BUILD_STRING ( home_state_to_string ( state ) ) ) ,
JSON_BUILD_PAIR ( " service " , JSON_BUILD_STRING ( " io.systemd.Home " ) ) ,
JSON_BUILD_PAIR_CONDITION ( disk_size ! = UINT64_MAX , " diskSize " , JSON_BUILD_UNSIGNED ( disk_size ) ) ,
JSON_BUILD_PAIR_CONDITION ( disk_usage ! = UINT64_MAX , " diskUsage " , JSON_BUILD_UNSIGNED ( disk_usage ) ) ,
JSON_BUILD_PAIR_CONDITION ( disk_free ! = UINT64_MAX , " diskFree " , JSON_BUILD_UNSIGNED ( disk_free ) ) ,
JSON_BUILD_PAIR_CONDITION ( disk_ceiling ! = UINT64_MAX , " diskCeiling " , JSON_BUILD_UNSIGNED ( disk_ceiling ) ) ,
JSON_BUILD_PAIR_CONDITION ( disk_floor ! = UINT64_MAX , " diskFloor " , JSON_BUILD_UNSIGNED ( disk_floor ) ) ,
JSON_BUILD_PAIR_CONDITION ( h - > signed_locally > = 0 , " signedLocally " , JSON_BUILD_BOOLEAN ( h - > signed_locally ) )
) ) ;
if ( r < 0 )
return r ;
j = json_variant_ref ( h - > record - > json ) ;
v = json_variant_ref ( json_variant_by_key ( j , " status " ) ) ;
m = json_variant_ref ( json_variant_by_key ( v , sd_id128_to_string ( id , ids ) ) ) ;
r = json_variant_filter ( & m , STRV_MAKE ( " diskSize " , " diskUsage " , " diskFree " , " diskCeiling " , " diskFloor " , " signedLocally " ) ) ;
if ( r < 0 )
return r ;
r = json_variant_merge ( & m , status ) ;
if ( r < 0 )
return r ;
r = json_variant_set_field ( & v , ids , m ) ;
if ( r < 0 )
return r ;
r = json_variant_set_field ( & j , " status " , v ) ;
if ( r < 0 )
return r ;
ur = user_record_new ( ) ;
if ( ! ur )
return - ENOMEM ;
r = user_record_load ( ur , j , flags ) ;
if ( r < 0 )
return r ;
ur - > incomplete =
FLAGS_SET ( h - > record - > mask , USER_RECORD_PRIVILEGED ) & &
! FLAGS_SET ( ur - > mask , USER_RECORD_PRIVILEGED ) ;
* ret = TAKE_PTR ( ur ) ;
return 0 ;
}
static int on_home_ref_eof ( sd_event_source * s , int fd , uint32_t revents , void * userdata ) {
_cleanup_ ( operation_unrefp ) Operation * o = NULL ;
Home * h = userdata ;
assert ( s ) ;
assert ( h ) ;
if ( h - > ref_event_source_please_suspend = = s )
h - > ref_event_source_please_suspend = sd_event_source_disable_unref ( h - > ref_event_source_please_suspend ) ;
if ( h - > ref_event_source_dont_suspend = = s )
h - > ref_event_source_dont_suspend = sd_event_source_disable_unref ( h - > ref_event_source_dont_suspend ) ;
if ( h - > ref_event_source_dont_suspend | | h - > ref_event_source_please_suspend )
return 0 ;
log_info ( " Got notification that all sessions of user %s ended, deactivating automatically. " , h - > user_name ) ;
o = operation_new ( OPERATION_PIPE_EOF , NULL ) ;
if ( ! o ) {
log_oom ( ) ;
return 0 ;
}
home_schedule_operation ( h , o , NULL ) ;
return 0 ;
}
int home_create_fifo ( Home * h , bool please_suspend ) {
_cleanup_close_ int ret_fd = - 1 ;
sd_event_source * * ss ;
const char * fn , * suffix ;
int r ;
assert ( h ) ;
if ( please_suspend ) {
suffix = " .please-suspend " ;
ss = & h - > ref_event_source_please_suspend ;
} else {
suffix = " .dont-suspend " ;
ss = & h - > ref_event_source_dont_suspend ;
}
fn = strjoina ( " /run/systemd/home/ " , h - > user_name , suffix ) ;
if ( ! * ss ) {
_cleanup_close_ int ref_fd = - 1 ;
( void ) mkdir ( " /run/systemd/home/ " , 0755 ) ;
if ( mkfifo ( fn , 0600 ) < 0 & & errno ! = EEXIST )
return log_error_errno ( errno , " Failed to create FIFO %s: %m " , fn ) ;
ref_fd = open ( fn , O_RDONLY | O_CLOEXEC | O_NONBLOCK ) ;
if ( ref_fd < 0 )
return log_error_errno ( errno , " Failed to open FIFO %s for reading: %m " , fn ) ;
r = sd_event_add_io ( h - > manager - > event , ss , ref_fd , 0 , on_home_ref_eof , h ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to allocate reference FIFO event source: %m " ) ;
( void ) sd_event_source_set_description ( * ss , " acquire-ref " ) ;
r = sd_event_source_set_priority ( * ss , SD_EVENT_PRIORITY_IDLE - 1 ) ;
if ( r < 0 )
return r ;
r = sd_event_source_set_io_fd_own ( * ss , true ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to pass ownership of FIFO event fd to event source: %m " ) ;
TAKE_FD ( ref_fd ) ;
}
ret_fd = open ( fn , O_WRONLY | O_CLOEXEC | O_NONBLOCK ) ;
if ( ret_fd < 0 )
return log_error_errno ( errno , " Failed to open FIFO %s for writing: %m " , fn ) ;
return TAKE_FD ( ret_fd ) ;
}
static int home_dispatch_acquire ( Home * h , Operation * o ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int ( * call ) ( Home * h , UserRecord * secret , HomeState for_state , sd_bus_error * error ) = NULL ;
HomeState for_state ;
int r ;
assert ( h ) ;
assert ( o ) ;
assert ( o - > type = = OPERATION_ACQUIRE ) ;
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
for_state = HOME_FIXATING_FOR_ACQUIRE ;
call = home_fixate_internal ;
break ;
case HOME_ABSENT :
r = sd_bus_error_setf ( & error , BUS_ERROR_HOME_ABSENT , " Home %s is currently missing or not plugged in. " , h - > user_name ) ;
break ;
case HOME_INACTIVE :
for_state = HOME_ACTIVATING_FOR_ACQUIRE ;
call = home_activate_internal ;
break ;
case HOME_ACTIVE :
for_state = HOME_AUTHENTICATING_FOR_ACQUIRE ;
call = home_authenticate_internal ;
break ;
case HOME_LOCKED :
for_state = HOME_UNLOCKING_FOR_ACQUIRE ;
call = home_unlock_internal ;
break ;
default :
/* All other cases means we are currently executing an operation, which means the job remains
* pending . */
return 0 ;
}
assert ( ! h - > current_operation ) ;
if ( call ) {
r = home_ratelimit ( h , & error ) ;
if ( r > = 0 )
r = call ( h , o - > secret , for_state , & error ) ;
}
if ( r ! = 0 ) /* failure or completed */
operation_result ( o , r , & error ) ;
else /* ongoing */
h - > current_operation = operation_ref ( o ) ;
return 1 ;
}
static int home_dispatch_release ( Home * h , Operation * o ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( o ) ;
assert ( o - > type = = OPERATION_RELEASE ) ;
if ( h - > ref_event_source_dont_suspend | | h - > ref_event_source_please_suspend )
/* If there's now a reference again, then let's abort the release attempt */
r = sd_bus_error_setf ( & error , BUS_ERROR_HOME_BUSY , " Home %s is currently referenced. " , h - > user_name ) ;
else {
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
case HOME_ABSENT :
case HOME_INACTIVE :
r = 0 ; /* done */
break ;
case HOME_LOCKED :
r = sd_bus_error_setf ( & error , BUS_ERROR_HOME_LOCKED , " Home %s is currently locked. " , h - > user_name ) ;
break ;
case HOME_ACTIVE :
r = home_deactivate_internal ( h , false , & error ) ;
break ;
default :
/* All other cases means we are currently executing an operation, which means the job remains
* pending . */
return 0 ;
}
}
assert ( ! h - > current_operation ) ;
if ( r < = 0 ) /* failure or completed */
operation_result ( o , r , & error ) ;
else /* ongoing */
h - > current_operation = operation_ref ( o ) ;
return 1 ;
}
static int home_dispatch_lock_all ( Home * h , Operation * o ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( o ) ;
assert ( o - > type = = OPERATION_LOCK_ALL ) ;
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
case HOME_ABSENT :
case HOME_INACTIVE :
log_info ( " Home %s is not active, no locking necessary. " , h - > user_name ) ;
r = 0 ; /* done */
break ;
case HOME_LOCKED :
log_info ( " Home %s is already locked. " , h - > user_name ) ;
r = 0 ; /* done */
break ;
case HOME_ACTIVE :
log_info ( " Locking home %s. " , h - > user_name ) ;
r = home_lock ( h , & error ) ;
break ;
default :
/* All other cases means we are currently executing an operation, which means the job remains
* pending . */
return 0 ;
}
assert ( ! h - > current_operation ) ;
if ( r ! = 0 ) /* failure or completed */
operation_result ( o , r , & error ) ;
else /* ongoing */
h - > current_operation = operation_ref ( o ) ;
return 1 ;
}
static int home_dispatch_pipe_eof ( Home * h , Operation * o ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( o ) ;
assert ( o - > type = = OPERATION_PIPE_EOF ) ;
if ( h - > ref_event_source_please_suspend | | h - > ref_event_source_dont_suspend )
return 1 ; /* Hmm, there's a reference again, let's cancel this */
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
case HOME_ABSENT :
case HOME_INACTIVE :
log_info ( " Home %s already deactivated, no automatic deactivation needed. " , h - > user_name ) ;
break ;
case HOME_DEACTIVATING :
log_info ( " Home %s is already being deactivated, automatic deactivated unnecessary. " , h - > user_name ) ;
break ;
case HOME_ACTIVE :
r = home_deactivate_internal ( h , false , & error ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to deactivate %s, ignoring: %s " , h - > user_name , bus_error_message ( & error , r ) ) ;
break ;
case HOME_LOCKED :
default :
/* If the device is locked or any operation is being executed, let's leave this pending */
return 0 ;
}
/* Note that we don't call operation_fail() or operation_success() here, because this kind of
* operation has no message associated with it , and thus there ' s no need to propagate success . */
assert ( ! o - > message ) ;
return 1 ;
}
static int home_dispatch_deactivate_force ( Home * h , Operation * o ) {
_cleanup_ ( sd_bus_error_free ) sd_bus_error error = SD_BUS_ERROR_NULL ;
int r ;
assert ( h ) ;
assert ( o ) ;
assert ( o - > type = = OPERATION_DEACTIVATE_FORCE ) ;
switch ( home_get_state ( h ) ) {
case HOME_UNFIXATED :
case HOME_ABSENT :
case HOME_INACTIVE :
log_debug ( " Home %s already deactivated, no forced deactivation due to unplug needed. " , h - > user_name ) ;
break ;
case HOME_DEACTIVATING :
log_debug ( " Home %s is already being deactivated, forced deactivation due to unplug unnecessary. " , h - > user_name ) ;
break ;
case HOME_ACTIVE :
case HOME_LOCKED :
r = home_deactivate_internal ( h , true , & error ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to forcibly deactivate %s, ignoring: %s " , h - > user_name , bus_error_message ( & error , r ) ) ;
break ;
default :
/* If any operation is being executed, let's leave this pending */
return 0 ;
}
/* Note that we don't call operation_fail() or operation_success() here, because this kind of
* operation has no message associated with it , and thus there ' s no need to propagate success . */
assert ( ! o - > message ) ;
return 1 ;
}
static int on_pending ( sd_event_source * s , void * userdata ) {
Home * h = userdata ;
Operation * o ;
int r ;
assert ( s ) ;
assert ( h ) ;
o = ordered_set_first ( h - > pending_operations ) ;
if ( o ) {
static int ( * const operation_table [ _OPERATION_MAX ] ) ( Home * h , Operation * o ) = {
[ OPERATION_ACQUIRE ] = home_dispatch_acquire ,
[ OPERATION_RELEASE ] = home_dispatch_release ,
[ OPERATION_LOCK_ALL ] = home_dispatch_lock_all ,
[ OPERATION_PIPE_EOF ] = home_dispatch_pipe_eof ,
[ OPERATION_DEACTIVATE_FORCE ] = home_dispatch_deactivate_force ,
} ;
assert ( operation_table [ o - > type ] ) ;
r = operation_table [ o - > type ] ( h , o ) ;
if ( r ! = 0 ) {
/* The operation completed, let's remove it from the pending list, and exit while
* leaving the event source enabled as it is . */
assert_se ( ordered_set_remove ( h - > pending_operations , o ) = = o ) ;
operation_unref ( o ) ;
return 0 ;
}
}
/* Nothing to do anymore, let's turn off this event source */
r = sd_event_source_set_enabled ( s , SD_EVENT_OFF ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to disable event source: %m " ) ;
return 0 ;
}
int home_schedule_operation ( Home * h , Operation * o , sd_bus_error * error ) {
int r ;
assert ( h ) ;
if ( o ) {
if ( ordered_set_size ( h - > pending_operations ) > = PENDING_OPERATIONS_MAX )
return sd_bus_error_setf ( error , BUS_ERROR_TOO_MANY_OPERATIONS , " Too many client operations requested " ) ;
r = ordered_set_ensure_allocated ( & h - > pending_operations , & operation_hash_ops ) ;
if ( r < 0 )
return r ;
r = ordered_set_put ( h - > pending_operations , o ) ;
if ( r < 0 )
return r ;
operation_ref ( o ) ;
}
if ( ! h - > pending_event_source ) {
r = sd_event_add_defer ( h - > manager - > event , & h - > pending_event_source , on_pending , h ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to allocate pending defer event source: %m " ) ;
( void ) sd_event_source_set_description ( h - > pending_event_source , " pending " ) ;
r = sd_event_source_set_priority ( h - > pending_event_source , SD_EVENT_PRIORITY_IDLE ) ;
if ( r < 0 )
return r ;
}
r = sd_event_source_set_enabled ( h - > pending_event_source , SD_EVENT_ON ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to trigger pending event source: %m " ) ;
return 0 ;
}
static int home_get_image_path_seat ( Home * h , char * * ret ) {
_cleanup_ ( sd_device_unrefp ) sd_device * d = NULL ;
_cleanup_free_ char * c = NULL ;
const char * ip , * seat ;
struct stat st ;
int r ;
assert ( h ) ;
if ( user_record_storage ( h - > record ) ! = USER_LUKS )
return - ENXIO ;
ip = user_record_image_path ( h - > record ) ;
if ( ! ip )
return - ENXIO ;
if ( ! path_startswith ( ip , " /dev/ " ) )
return - ENXIO ;
if ( stat ( ip , & st ) < 0 )
return - errno ;
if ( ! S_ISBLK ( st . st_mode ) )
return - ENOTBLK ;
r = sd_device_new_from_devnum ( & d , ' b ' , st . st_rdev ) ;
if ( r < 0 )
return r ;
r = sd_device_get_property_value ( d , " ID_SEAT " , & seat ) ;
if ( r = = - ENOENT ) /* no property means seat0 */
seat = " seat0 " ;
else if ( r < 0 )
return r ;
c = strdup ( seat ) ;
if ( ! c )
return - ENOMEM ;
* ret = TAKE_PTR ( c ) ;
return 0 ;
}
int home_auto_login ( Home * h , char * * * ret_seats ) {
_cleanup_free_ char * seat = NULL , * seat2 = NULL ;
assert ( h ) ;
assert ( ret_seats ) ;
( void ) home_get_image_path_seat ( h , & seat ) ;
if ( h - > record - > auto_login > 0 & & ! streq_ptr ( seat , " seat0 " ) ) {
/* For now, when the auto-login boolean is set for a user, let's make it mean
* " seat0 " . Eventually we can extend the concept and allow configuration of any kind of seat ,
* but let ' s keep simple initially , most likely the feature is interesting on single - user
* systems anyway , only .
*
* We filter out users marked for auto - login in we know for sure their home directory is
* absent . */
if ( user_record_test_image_path ( h - > record ) ! = USER_TEST_ABSENT ) {
seat2 = strdup ( " seat0 " ) ;
if ( ! seat2 )
return - ENOMEM ;
}
}
if ( seat | | seat2 ) {
_cleanup_strv_free_ char * * list = NULL ;
size_t i = 0 ;
list = new ( char * , 3 ) ;
if ( ! list )
return - ENOMEM ;
if ( seat )
list [ i + + ] = TAKE_PTR ( seat ) ;
if ( seat2 )
list [ i + + ] = TAKE_PTR ( seat2 ) ;
list [ i ] = NULL ;
* ret_seats = TAKE_PTR ( list ) ;
return 1 ;
}
* ret_seats = NULL ;
return 0 ;
}
int home_set_current_message ( Home * h , sd_bus_message * m ) {
assert ( h ) ;
if ( ! m )
return 0 ;
if ( h - > current_operation )
return - EBUSY ;
h - > current_operation = operation_new ( OPERATION_IMMEDIATE , m ) ;
if ( ! h - > current_operation )
return - ENOMEM ;
return 1 ;
}
static const char * const home_state_table [ _HOME_STATE_MAX ] = {
[ HOME_UNFIXATED ] = " unfixated " ,
[ HOME_ABSENT ] = " absent " ,
[ HOME_INACTIVE ] = " inactive " ,
[ HOME_FIXATING ] = " fixating " ,
[ HOME_FIXATING_FOR_ACTIVATION ] = " fixating-for-activation " ,
[ HOME_FIXATING_FOR_ACQUIRE ] = " fixating-for-acquire " ,
[ HOME_ACTIVATING ] = " activating " ,
[ HOME_ACTIVATING_FOR_ACQUIRE ] = " activating-for-acquire " ,
[ HOME_DEACTIVATING ] = " deactivating " ,
[ HOME_ACTIVE ] = " active " ,
[ HOME_LOCKING ] = " locking " ,
[ HOME_LOCKED ] = " locked " ,
[ HOME_UNLOCKING ] = " unlocking " ,
[ HOME_UNLOCKING_FOR_ACQUIRE ] = " unlocking-for-acquire " ,
[ HOME_CREATING ] = " creating " ,
[ HOME_REMOVING ] = " removing " ,
[ HOME_UPDATING ] = " updating " ,
[ HOME_UPDATING_WHILE_ACTIVE ] = " updating-while-active " ,
[ HOME_RESIZING ] = " resizing " ,
[ HOME_RESIZING_WHILE_ACTIVE ] = " resizing-while-active " ,
[ HOME_PASSWD ] = " passwd " ,
[ HOME_PASSWD_WHILE_ACTIVE ] = " passwd-while-active " ,
[ HOME_AUTHENTICATING ] = " authenticating " ,
[ HOME_AUTHENTICATING_WHILE_ACTIVE ] = " authenticating-while-active " ,
[ HOME_AUTHENTICATING_FOR_ACQUIRE ] = " authenticating-for-acquire " ,
} ;
DEFINE_STRING_TABLE_LOOKUP ( home_state , HomeState ) ;