2019-07-04 18:35:39 +02:00
/* SPDX-License-Identifier: LGPL-2.1+ */
# include <stddef.h>
# include <sys/mount.h>
# include "chown-recursive.h"
# include "copy.h"
# include "fd-util.h"
# include "fileio.h"
2020-05-01 19:42:56 +02:00
# include "fs-util.h"
2019-07-04 18:35:39 +02:00
# include "home-util.h"
# include "homework-cifs.h"
# include "homework-directory.h"
2020-04-14 19:18:09 +02:00
# include "homework-fido2.h"
2019-07-04 18:35:39 +02:00
# include "homework-fscrypt.h"
# include "homework-luks.h"
# include "homework-mount.h"
# include "homework-pkcs11.h"
# include "homework.h"
# include "main-func.h"
# include "memory-util.h"
# include "missing_magic.h"
# include "mount-util.h"
# include "path-util.h"
# include "rm-rf.h"
# include "stat-util.h"
# include "strv.h"
# include "tmpfile-util.h"
# include "user-util.h"
# include "virt.h"
/* Make sure a bad password always results in a 3s delay, no matter what */
# define BAD_PASSWORD_DELAY_USEC (3 * USEC_PER_SEC)
2020-04-14 19:18:09 +02:00
void password_cache_free ( PasswordCache * cache ) {
if ( ! cache )
return ;
cache - > pkcs11_passwords = strv_free_erase ( cache - > pkcs11_passwords ) ;
cache - > fido2_passwords = strv_free_erase ( cache - > fido2_passwords ) ;
}
2019-07-04 18:35:39 +02:00
int user_record_authenticate (
UserRecord * h ,
UserRecord * secret ,
2020-04-14 19:18:09 +02:00
PasswordCache * cache ,
2020-05-20 18:38:07 +02:00
bool strict_verify ) {
2019-07-04 18:35:39 +02:00
2020-04-14 19:18:09 +02:00
bool need_password = false , need_token = false , need_pin = false , need_protected_authentication_path_permitted = false , need_user_presence_permitted = false ,
pin_locked = false , pin_incorrect = false , pin_incorrect_few_tries_left = false , pin_incorrect_one_try_left = false , token_action_timeout = false ;
2019-07-04 18:35:39 +02:00
int r ;
assert ( h ) ;
assert ( secret ) ;
/* Tries to authenticate a user record with the supplied secrets. i.e. checks whether at least one
* supplied plaintext passwords matches a hashed password field of the user record . Or if a
2020-04-14 19:18:09 +02:00
* configured PKCS # 11 or FIDO2 token is around and can unlock the record .
2019-07-04 18:35:39 +02:00
*
2020-04-14 19:18:09 +02:00
* Note that the ' cache ' parameter is both an input and output parameter : it contains lists of
* configured , decrypted PKCS # 11 / FIDO2 passwords . We typically have to call this function multiple
* times over the course of an operation ( think : on login we authenticate the host user record , the
* record embedded in the LUKS record and the one embedded in $ HOME ) . Hence we keep a list of
* passwords we already decrypted , so that we don ' t have to do the ( slow and potentially interactive )
* PKCS # 11 / FIDO2 dance for the relevant token again and again . */
2019-07-04 18:35:39 +02:00
/* First, let's see if the supplied plain-text passwords work? */
r = user_record_test_secret ( h , secret ) ;
if ( r = = - ENOKEY ) {
log_info_errno ( r , " None of the supplied plaintext passwords unlocks the user record's hashed passwords. " ) ;
need_password = true ;
} else if ( r = = - ENXIO )
log_debug_errno ( r , " User record has no hashed passwords, plaintext passwords not tested. " ) ;
else if ( r < 0 )
return log_error_errno ( r , " Failed to validate password of record: %m " ) ;
else {
log_info ( " Provided password unlocks user record. " ) ;
2020-05-20 18:38:07 +02:00
return 1 ;
2019-07-04 18:35:39 +02:00
}
2020-04-14 19:18:09 +02:00
/* Second, test cached PKCS#11 passwords */
2020-03-31 09:29:26 +02:00
for ( size_t n = 0 ; n < h - > n_pkcs11_encrypted_key ; n + + ) {
2019-07-04 18:35:39 +02:00
char * * pp ;
2020-04-14 19:18:09 +02:00
STRV_FOREACH ( pp , cache - > pkcs11_passwords ) {
r = test_password_one ( h - > pkcs11_encrypted_key [ n ] . hashed_password , * pp ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return log_error_errno ( r , " Failed to check supplied PKCS#11 password: %m " ) ;
if ( r > 0 ) {
log_info ( " Previously acquired PKCS#11 password unlocks user record. " ) ;
2020-05-20 18:38:07 +02:00
return 1 ;
2019-07-04 18:35:39 +02:00
}
}
2020-04-14 19:18:09 +02:00
}
/* Third, test cached FIDO2 passwords */
for ( size_t n = 0 ; n < h - > n_fido2_hmac_salt ; n + + ) {
char * * pp ;
/* See if any of the previously calculated passwords work */
STRV_FOREACH ( pp , cache - > fido2_passwords ) {
r = test_password_one ( h - > fido2_hmac_salt [ n ] . hashed_password , * pp ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to check supplied FIDO2 password: %m " ) ;
if ( r > 0 ) {
log_info ( " Previously acquired FIDO2 password unlocks user record. " ) ;
return 0 ;
}
}
}
/* Fourth, let's see if any of the PKCS#11 security tokens are plugged in and help us */
for ( size_t n = 0 ; n < h - > n_pkcs11_encrypted_key ; n + + ) {
# if HAVE_P11KIT
_cleanup_ ( pkcs11_callback_data_release ) struct pkcs11_callback_data data = {
. user_record = h ,
. secret = secret ,
. encrypted_key = h - > pkcs11_encrypted_key + n ,
} ;
2019-07-04 18:35:39 +02:00
r = pkcs11_find_token ( data . encrypted_key - > uri , pkcs11_callback , & data ) ;
switch ( r ) {
case - EAGAIN :
need_token = true ;
break ;
case - ENOANO :
need_pin = true ;
break ;
case - ERFKILL :
need_protected_authentication_path_permitted = true ;
break ;
case - EOWNERDEAD :
pin_locked = true ;
break ;
case - ENOLCK :
pin_incorrect = true ;
break ;
case - ETOOMANYREFS :
pin_incorrect = pin_incorrect_few_tries_left = true ;
break ;
case - EUCLEAN :
pin_incorrect = pin_incorrect_few_tries_left = pin_incorrect_one_try_left = true ;
break ;
default :
if ( r < 0 )
return r ;
r = test_password_one ( data . encrypted_key - > hashed_password , data . decrypted_password ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to test PKCS#11 password: %m " ) ;
if ( r = = 0 )
return log_error_errno ( SYNTHETIC_ERRNO ( EPERM ) , " Configured PKCS#11 security token %s does not decrypt encrypted key correctly. " , data . encrypted_key - > uri ) ;
log_info ( " Decrypted password from PKCS#11 security token %s unlocks user record. " , data . encrypted_key - > uri ) ;
2020-04-14 19:18:09 +02:00
r = strv_extend ( & cache - > pkcs11_passwords , data . decrypted_password ) ;
if ( r < 0 )
return log_oom ( ) ;
return 0 ;
}
# else
need_token = true ;
break ;
# endif
}
/* Fifth, let's see if any of the FIDO2 security tokens are plugged in and help us */
for ( size_t n = 0 ; n < h - > n_fido2_hmac_salt ; n + + ) {
# if HAVE_LIBFIDO2
_cleanup_ ( erase_and_freep ) char * decrypted_password = NULL ;
r = fido2_use_token ( h , secret , h - > fido2_hmac_salt + n , & decrypted_password ) ;
switch ( r ) {
case - EAGAIN :
need_token = true ;
break ;
case - ENOANO :
need_pin = true ;
break ;
case - EOWNERDEAD :
pin_locked = true ;
break ;
case - ENOLCK :
pin_incorrect = true ;
break ;
case - EMEDIUMTYPE :
need_user_presence_permitted = true ;
break ;
case - ENOSTR :
token_action_timeout = true ;
break ;
default :
if ( r < 0 )
return r ;
r = test_password_one ( h - > fido2_hmac_salt [ n ] . hashed_password , decrypted_password ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to test FIDO2 password: %m " ) ;
if ( r = = 0 )
return log_error_errno ( SYNTHETIC_ERRNO ( EPERM ) , " Configured FIDO2 security token does not decrypt encrypted key correctly. " ) ;
log_info ( " Decrypted password from FIDO2 security token unlocks user record. " ) ;
r = strv_extend ( & cache - > fido2_passwords , decrypted_password ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return log_oom ( ) ;
2020-05-20 18:38:07 +02:00
return 1 ;
2019-07-04 18:35:39 +02:00
}
# else
need_token = true ;
break ;
# endif
}
/* Ordered by "relevance", i.e. the most "important" or "interesting" error condition is returned. */
if ( pin_incorrect_one_try_left )
return - EUCLEAN ;
if ( pin_incorrect_few_tries_left )
return - ETOOMANYREFS ;
if ( pin_incorrect )
return - ENOLCK ;
if ( pin_locked )
return - EOWNERDEAD ;
2020-04-14 19:18:09 +02:00
if ( token_action_timeout )
return - ENOSTR ;
2019-07-04 18:35:39 +02:00
if ( need_protected_authentication_path_permitted )
return - ERFKILL ;
2020-04-14 19:18:09 +02:00
if ( need_user_presence_permitted )
return - EMEDIUMTYPE ;
2019-07-04 18:35:39 +02:00
if ( need_pin )
return - ENOANO ;
if ( need_token )
return - EBADSLT ;
if ( need_password )
return - ENOKEY ;
2020-04-14 19:18:09 +02:00
/* Hmm, this means neither PCKS#11/FIDO2 nor classic hashed passwords were supplied, we cannot
* authenticate this reasonably */
2020-05-20 18:38:07 +02:00
if ( strict_verify )
return log_debug_errno ( SYNTHETIC_ERRNO ( EKEYREVOKED ) ,
2020-04-14 19:18:09 +02:00
" No hashed passwords and no PKCS#11/FIDO2 tokens defined, cannot authenticate user record, refusing. " ) ;
2020-05-20 18:38:07 +02:00
/* If strict verification is off this means we are possibly in the case where we encountered an
* unfixated record , i . e . a synthetic one that accordingly lacks any authentication data . In this
* case , allow the authentication to pass for now , so that the second ( or third ) authentication level
* ( the ones of the user record in the LUKS header or inside the home directory ) will then catch
* invalid passwords . The second / third authentication always runs in strict verification mode . */
log_debug ( " No hashed passwords and no PKCS#11 tokens defined in record, cannot authenticate user record. "
" Deferring to embedded user record. " ) ;
return 0 ;
2019-07-04 18:35:39 +02:00
}
int home_setup_undo ( HomeSetup * setup ) {
int r = 0 , q ;
assert ( setup ) ;
2020-05-05 15:13:20 +02:00
if ( setup - > root_fd > = 0 ) {
if ( setup - > do_offline_fitrim ) {
q = run_fitrim ( setup - > root_fd ) ;
if ( q < 0 )
r = q ;
}
setup - > root_fd = safe_close ( setup - > root_fd ) ;
}
2019-07-04 18:35:39 +02:00
if ( setup - > undo_mount ) {
q = umount_verbose ( " /run/systemd/user-home-mount " ) ;
if ( q < 0 )
r = q ;
}
if ( setup - > undo_dm & & setup - > crypt_device & & setup - > dm_name ) {
q = crypt_deactivate ( setup - > crypt_device , setup - > dm_name ) ;
if ( q < 0 )
r = q ;
}
2020-05-05 15:13:20 +02:00
if ( setup - > image_fd > = 0 ) {
if ( setup - > do_offline_fallocate ) {
q = run_fallocate ( setup - > image_fd , NULL ) ;
if ( q < 0 )
r = q ;
}
setup - > image_fd = safe_close ( setup - > image_fd ) ;
}
2019-07-04 18:35:39 +02:00
setup - > undo_mount = false ;
setup - > undo_dm = false ;
2020-05-05 15:13:20 +02:00
setup - > do_offline_fitrim = false ;
setup - > do_offline_fallocate = false ;
2019-07-04 18:35:39 +02:00
setup - > dm_name = mfree ( setup - > dm_name ) ;
setup - > dm_node = mfree ( setup - > dm_node ) ;
setup - > loop = loop_device_unref ( setup - > loop ) ;
crypt_free ( setup - > crypt_device ) ;
setup - > crypt_device = NULL ;
explicit_bzero_safe ( setup - > volume_key , setup - > volume_key_size ) ;
setup - > volume_key = mfree ( setup - > volume_key ) ;
setup - > volume_key_size = 0 ;
return r ;
}
int home_prepare (
UserRecord * h ,
bool already_activated ,
2020-04-14 19:18:09 +02:00
PasswordCache * cache ,
2019-07-04 18:35:39 +02:00
HomeSetup * setup ,
UserRecord * * ret_header_home ) {
int r ;
assert ( h ) ;
assert ( setup ) ;
assert ( ! setup - > loop ) ;
assert ( ! setup - > crypt_device ) ;
assert ( setup - > root_fd < 0 ) ;
assert ( ! setup - > undo_dm ) ;
assert ( ! setup - > undo_mount ) ;
/* Makes a home directory accessible (through the root_fd file descriptor, not by path!). */
switch ( user_record_storage ( h ) ) {
case USER_LUKS :
2020-04-14 19:18:09 +02:00
return home_prepare_luks ( h , already_activated , NULL , cache , setup , ret_header_home ) ;
2019-07-04 18:35:39 +02:00
case USER_SUBVOLUME :
case USER_DIRECTORY :
r = home_prepare_directory ( h , already_activated , setup ) ;
break ;
case USER_FSCRYPT :
2020-04-14 19:18:09 +02:00
r = home_prepare_fscrypt ( h , already_activated , cache , setup ) ;
2019-07-04 18:35:39 +02:00
break ;
case USER_CIFS :
r = home_prepare_cifs ( h , already_activated , setup ) ;
break ;
default :
return log_error_errno ( SYNTHETIC_ERRNO ( ENOLINK ) , " Processing home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
}
if ( r < 0 )
return r ;
if ( ret_header_home )
* ret_header_home = NULL ;
return r ;
}
int home_sync_and_statfs ( int root_fd , struct statfs * ret ) {
assert ( root_fd > = 0 ) ;
/* Let's sync this to disk, so that the disk space reported by fstatfs() below is accurate (for file
* systems such as btrfs where this is determined lazily ) . */
if ( syncfs ( root_fd ) < 0 )
return log_error_errno ( errno , " Failed to synchronize file system: %m " ) ;
if ( ret )
if ( fstatfs ( root_fd , ret ) < 0 )
return log_error_errno ( errno , " Failed to statfs() file system : % m " ) ;
log_info ( " Synchronized disk. " ) ;
return 0 ;
}
static int read_identity_file ( int root_fd , JsonVariant * * ret ) {
_cleanup_ ( fclosep ) FILE * identity_file = NULL ;
_cleanup_close_ int identity_fd = - 1 ;
unsigned line , column ;
int r ;
assert ( root_fd > = 0 ) ;
assert ( ret ) ;
identity_fd = openat ( root_fd , " .identity " , O_RDONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW | O_NONBLOCK ) ;
if ( identity_fd < 0 )
return log_error_errno ( errno , " Failed to open .identity file in home directory: %m " ) ;
r = fd_verify_regular ( identity_fd ) ;
if ( r < 0 )
return log_error_errno ( r , " Embedded identity file is not a regular file, refusing: %m " ) ;
2020-03-31 10:07:21 +02:00
identity_file = take_fdopen ( & identity_fd , " r " ) ;
2019-07-04 18:35:39 +02:00
if ( ! identity_file )
return log_oom ( ) ;
r = json_parse_file ( identity_file , " .identity " , JSON_PARSE_SENSITIVE , ret , & line , & column ) ;
if ( r < 0 )
return log_error_errno ( r , " [.identity:%u:%u] Failed to parse JSON data: %m " , line , column ) ;
log_info ( " Read embedded .identity file. " ) ;
return 0 ;
}
static int write_identity_file ( int root_fd , JsonVariant * v , uid_t uid ) {
_cleanup_ ( json_variant_unrefp ) JsonVariant * normalized = NULL ;
_cleanup_ ( fclosep ) FILE * identity_file = NULL ;
_cleanup_close_ int identity_fd = - 1 ;
_cleanup_free_ char * fn = NULL ;
int r ;
assert ( root_fd > = 0 ) ;
assert ( v ) ;
normalized = json_variant_ref ( v ) ;
r = json_variant_normalize ( & normalized ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to normalize user record, ignoring: %m " ) ;
r = tempfn_random ( " .identity " , NULL , & fn ) ;
if ( r < 0 )
return r ;
identity_fd = openat ( root_fd , fn , O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW , 0600 ) ;
if ( identity_fd < 0 )
return log_error_errno ( errno , " Failed to create .identity file in home directory: %m " ) ;
2020-03-31 10:07:21 +02:00
identity_file = take_fdopen ( & identity_fd , " w " ) ;
2019-07-04 18:35:39 +02:00
if ( ! identity_file ) {
r = log_oom ( ) ;
goto fail ;
}
json_variant_dump ( normalized , JSON_FORMAT_PRETTY , identity_file , NULL ) ;
r = fflush_and_check ( identity_file ) ;
if ( r < 0 ) {
log_error_errno ( r , " Failed to write .identity file: %m " ) ;
goto fail ;
}
if ( fchown ( fileno ( identity_file ) , uid , uid ) < 0 ) {
log_error_errno ( r , " Failed to change ownership of identity file: %m " ) ;
goto fail ;
}
if ( renameat ( root_fd , fn , root_fd , " .identity " ) < 0 ) {
r = log_error_errno ( errno , " Failed to move identity file into place: %m " ) ;
goto fail ;
}
log_info ( " Wrote embedded .identity file. " ) ;
return 0 ;
fail :
( void ) unlinkat ( root_fd , fn , 0 ) ;
return r ;
}
int home_load_embedded_identity (
UserRecord * h ,
int root_fd ,
UserRecord * header_home ,
UserReconcileMode mode ,
2020-04-14 19:18:09 +02:00
PasswordCache * cache ,
2019-07-04 18:35:39 +02:00
UserRecord * * ret_embedded_home ,
UserRecord * * ret_new_home ) {
_cleanup_ ( user_record_unrefp ) UserRecord * embedded_home = NULL , * intermediate_home = NULL , * new_home = NULL ;
_cleanup_ ( json_variant_unrefp ) JsonVariant * v = NULL ;
int r ;
assert ( h ) ;
assert ( root_fd > = 0 ) ;
r = read_identity_file ( root_fd , & v ) ;
if ( r < 0 )
return r ;
embedded_home = user_record_new ( ) ;
if ( ! embedded_home )
return log_oom ( ) ;
r = user_record_load ( embedded_home , v , USER_RECORD_LOAD_EMBEDDED ) ;
if ( r < 0 )
return r ;
if ( ! user_record_compatible ( h , embedded_home ) )
2020-03-31 12:50:13 +02:00
return log_error_errno ( SYNTHETIC_ERRNO ( EREMCHG ) , " Embedded home record not compatible with host record, refusing. " ) ;
2019-07-04 18:35:39 +02:00
/* Insist that credentials the user supplies also unlocks any embedded records. */
2020-04-14 19:18:09 +02:00
r = user_record_authenticate ( embedded_home , h , cache , /* strict_verify= */ true ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
2020-05-20 18:38:07 +02:00
assert ( r > 0 ) ; /* Insist that a password was verified */
2019-07-04 18:35:39 +02:00
/* At this point we have three records to deal with:
*
* · The record we got passed from the host
* · The record included in the LUKS header ( only if LUKS is used )
* · The record in the home directory itself ( ~ . identity )
*
* Now we have to reconcile all three , and let the newest one win . */
if ( header_home ) {
/* Note we relax the requirements here. Instead of insisting that the host record is strictly
* newer , let ' s also be OK if its equally new . If it is , we ' ll however insist that the
* embedded record must be newer , so that we update at least one of the two . */
r = user_record_reconcile ( h , header_home , mode = = USER_RECONCILE_REQUIRE_NEWER ? USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL : mode , & intermediate_home ) ;
if ( r = = - EREMCHG ) /* this was supposed to be checked earlier already, but let's check this again */
return log_error_errno ( r , " Identity stored on host and in header don't match, refusing. " ) ;
if ( r = = - ESTALE )
return log_error_errno ( r , " Embedded identity record is newer than supplied record, refusing. " ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to reconcile host and header identities: %m " ) ;
if ( r = = USER_RECONCILE_EMBEDDED_WON )
log_info ( " Reconciling header user identity completed (header version was newer). " ) ;
else if ( r = = USER_RECONCILE_HOST_WON ) {
log_info ( " Reconciling header user identity completed (host version was newer). " ) ;
if ( mode = = USER_RECONCILE_REQUIRE_NEWER ) /* Host version is newer than the header
* version , hence we ' ll update
* something . This means we can relax the
* requirements on the embedded
* identity . */
mode = USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL ;
} else {
assert ( r = = USER_RECONCILE_IDENTICAL ) ;
log_info ( " Reconciling user identities completed (host and header version were identical). " ) ;
}
h = intermediate_home ;
}
r = user_record_reconcile ( h , embedded_home , mode , & new_home ) ;
if ( r = = - EREMCHG )
return log_error_errno ( r , " Identity stored on host and in home don't match, refusing. " ) ;
if ( r = = - ESTALE )
return log_error_errno ( r , " Embedded identity record is equally new or newer than supplied record, refusing. " ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to reconcile host and embedded identities: %m " ) ;
if ( r = = USER_RECONCILE_EMBEDDED_WON )
log_info ( " Reconciling embedded user identity completed (embedded version was newer). " ) ;
else if ( r = = USER_RECONCILE_HOST_WON )
log_info ( " Reconciling embedded user identity completed (host version was newer). " ) ;
else {
assert ( r = = USER_RECONCILE_IDENTICAL ) ;
log_info ( " Reconciling embedded user identity completed (host and embedded version were identical). " ) ;
}
if ( ret_embedded_home )
* ret_embedded_home = TAKE_PTR ( embedded_home ) ;
if ( ret_new_home )
* ret_new_home = TAKE_PTR ( new_home ) ;
return 0 ;
}
int home_store_embedded_identity ( UserRecord * h , int root_fd , uid_t uid , UserRecord * old_home ) {
_cleanup_ ( user_record_unrefp ) UserRecord * embedded = NULL ;
int r ;
assert ( h ) ;
assert ( root_fd > = 0 ) ;
assert ( uid_is_valid ( uid ) ) ;
r = user_record_clone ( h , USER_RECORD_EXTRACT_EMBEDDED , & embedded ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to determine new embedded record: %m " ) ;
if ( old_home & & user_record_equal ( old_home , embedded ) ) {
log_debug ( " Not updating embedded home record. " ) ;
return 0 ;
}
/* The identity has changed, let's update it in the image */
r = write_identity_file ( root_fd , embedded - > json , h - > uid ) ;
if ( r < 0 )
return r ;
return 1 ;
}
static const char * file_system_type_fd ( int fd ) {
struct statfs sfs ;
assert ( fd > = 0 ) ;
if ( fstatfs ( fd , & sfs ) < 0 ) {
log_debug_errno ( errno , " Failed to statfs(): %m " ) ;
return NULL ;
}
if ( is_fs_type ( & sfs , XFS_SB_MAGIC ) )
return " xfs " ;
if ( is_fs_type ( & sfs , EXT4_SUPER_MAGIC ) )
return " ext4 " ;
if ( is_fs_type ( & sfs , BTRFS_SUPER_MAGIC ) )
return " btrfs " ;
return NULL ;
}
int home_extend_embedded_identity ( UserRecord * h , UserRecord * used , HomeSetup * setup ) {
int r ;
assert ( h ) ;
assert ( used ) ;
assert ( setup ) ;
r = user_record_add_binding (
h ,
user_record_storage ( used ) ,
user_record_image_path ( used ) ,
setup - > found_partition_uuid ,
setup - > found_luks_uuid ,
setup - > found_fs_uuid ,
setup - > crypt_device ? crypt_get_cipher ( setup - > crypt_device ) : NULL ,
setup - > crypt_device ? crypt_get_cipher_mode ( setup - > crypt_device ) : NULL ,
setup - > crypt_device ? luks_volume_key_size_convert ( setup - > crypt_device ) : UINT64_MAX ,
file_system_type_fd ( setup - > root_fd ) ,
user_record_home_directory ( used ) ,
used - > uid ,
( gid_t ) used - > uid ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to update binding in record: %m " ) ;
return 0 ;
}
static int chown_recursive_directory ( int root_fd , uid_t uid ) {
int r ;
assert ( root_fd > = 0 ) ;
assert ( uid_is_valid ( uid ) ) ;
r = fd_chown_recursive ( root_fd , uid , ( gid_t ) uid , 0777 ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to change ownership of files and directories: %m " ) ;
if ( r = = 0 )
log_info ( " Recursive changing of ownership not necessary, skipped. " ) ;
else
log_info ( " Recursive changing of ownership completed. " ) ;
return 0 ;
}
int home_refresh (
UserRecord * h ,
HomeSetup * setup ,
UserRecord * header_home ,
2020-04-14 19:18:09 +02:00
PasswordCache * cache ,
2019-07-04 18:35:39 +02:00
struct statfs * ret_statfs ,
UserRecord * * ret_new_home ) {
_cleanup_ ( user_record_unrefp ) UserRecord * embedded_home = NULL , * new_home = NULL ;
int r ;
assert ( h ) ;
assert ( setup ) ;
assert ( ret_new_home ) ;
/* When activating a home directory, does the identity work: loads the identity from the $HOME
* directory , reconciles it with our idea , chown ( ) s everything . */
2020-04-14 19:18:09 +02:00
r = home_load_embedded_identity ( h , setup - > root_fd , header_home , USER_RECONCILE_ANY , cache , & embedded_home , & new_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
r = home_store_header_identity_luks ( new_home , setup , header_home ) ;
if ( r < 0 )
return r ;
r = home_store_embedded_identity ( new_home , setup - > root_fd , h - > uid , embedded_home ) ;
if ( r < 0 )
return r ;
r = chown_recursive_directory ( setup - > root_fd , h - > uid ) ;
if ( r < 0 )
return r ;
r = home_sync_and_statfs ( setup - > root_fd , ret_statfs ) ;
if ( r < 0 )
return r ;
* ret_new_home = TAKE_PTR ( new_home ) ;
return 0 ;
}
static int home_activate ( UserRecord * h , UserRecord * * ret_home ) {
2020-04-14 19:18:09 +02:00
_cleanup_ ( password_cache_free ) PasswordCache cache = { } ;
2019-07-04 18:35:39 +02:00
_cleanup_ ( user_record_unrefp ) UserRecord * new_home = NULL ;
int r ;
assert ( h ) ;
if ( ! h - > user_name )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record lacks user name, refusing. " ) ;
if ( ! uid_is_valid ( h - > uid ) )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record lacks UID, refusing. " ) ;
if ( ! IN_SET ( user_record_storage ( h ) , USER_LUKS , USER_DIRECTORY , USER_SUBVOLUME , USER_FSCRYPT , USER_CIFS ) )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) , " Activating home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
2020-04-14 19:18:09 +02:00
r = user_record_authenticate ( h , h , & cache , /* strict_verify= */ false ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
r = user_record_test_home_directory_and_warn ( h ) ;
if ( r < 0 )
return r ;
if ( r = = USER_TEST_MOUNTED )
return log_error_errno ( SYNTHETIC_ERRNO ( EALREADY ) , " Home directory %s is already mounted, refusing. " , user_record_home_directory ( h ) ) ;
r = user_record_test_image_path_and_warn ( h ) ;
if ( r < 0 )
return r ;
if ( r = = USER_TEST_ABSENT )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOENT ) , " Image path %s is missing, refusing. " , user_record_image_path ( h ) ) ;
switch ( user_record_storage ( h ) ) {
case USER_LUKS :
2020-04-14 19:18:09 +02:00
r = home_activate_luks ( h , & cache , & new_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
break ;
case USER_SUBVOLUME :
case USER_DIRECTORY :
case USER_FSCRYPT :
2020-04-14 19:18:09 +02:00
r = home_activate_directory ( h , & cache , & new_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
break ;
case USER_CIFS :
2020-04-14 19:18:09 +02:00
r = home_activate_cifs ( h , & cache , & new_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
break ;
default :
assert_not_reached ( " unexpected type " ) ;
}
/* Note that the returned object might either be a reference to an updated version of the existing
* home object , or a reference to a newly allocated home object . The caller has to be able to deal
* with both , and consider the old object out - of - date . */
if ( user_record_equal ( h , new_home ) ) {
* ret_home = NULL ;
return 0 ; /* no identity change */
}
* ret_home = TAKE_PTR ( new_home ) ;
return 1 ; /* identity updated */
}
static int home_deactivate ( UserRecord * h , bool force ) {
bool done = false ;
int r ;
assert ( h ) ;
if ( ! h - > user_name )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record incomplete, refusing. " ) ;
if ( ! IN_SET ( user_record_storage ( h ) , USER_LUKS , USER_DIRECTORY , USER_SUBVOLUME , USER_FSCRYPT , USER_CIFS ) )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) , " Deactivating home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
r = user_record_test_home_directory_and_warn ( h ) ;
if ( r < 0 )
return r ;
if ( r = = USER_TEST_MOUNTED ) {
2020-05-05 15:13:20 +02:00
if ( user_record_storage ( h ) = = USER_LUKS ) {
r = home_trim_luks ( h ) ;
if ( r < 0 )
return r ;
}
2019-07-04 18:35:39 +02:00
if ( umount2 ( user_record_home_directory ( h ) , UMOUNT_NOFOLLOW | ( force ? MNT_FORCE | MNT_DETACH : 0 ) ) < 0 )
return log_error_errno ( errno , " Failed to unmount %s: %m " , user_record_home_directory ( h ) ) ;
log_info ( " Unmounting completed. " ) ;
done = true ;
} else
log_info ( " Directory %s is already unmounted. " , user_record_home_directory ( h ) ) ;
if ( user_record_storage ( h ) = = USER_LUKS ) {
r = home_deactivate_luks ( h ) ;
if ( r < 0 )
return r ;
if ( r > 0 )
done = true ;
}
if ( ! done )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOEXEC ) , " Home is not active. " ) ;
log_info ( " Everything completed. " ) ;
return 0 ;
}
static int copy_skel ( int root_fd , const char * skel ) {
int r ;
assert ( root_fd > = 0 ) ;
r = copy_tree_at ( AT_FDCWD , skel , root_fd , " . " , UID_INVALID , GID_INVALID , COPY_MERGE | COPY_REPLACE ) ;
if ( r = = - ENOENT ) {
log_info ( " Skeleton directory %s missing, ignoring. " , skel ) ;
return 0 ;
}
if ( r < 0 )
return log_error_errno ( r , " Failed to copy in %s: %m " , skel ) ;
log_info ( " Copying in %s completed. " , skel ) ;
return 0 ;
}
static int change_access_mode ( int root_fd , mode_t m ) {
assert ( root_fd > = 0 ) ;
if ( fchmod ( root_fd , m ) < 0 )
return log_error_errno ( errno , " Failed to change access mode of top-level directory: %m " ) ;
log_info ( " Changed top-level directory access mode to 0%o. " , m ) ;
return 0 ;
}
int home_populate ( UserRecord * h , int dir_fd ) {
int r ;
assert ( h ) ;
assert ( dir_fd > = 0 ) ;
r = copy_skel ( dir_fd , user_record_skeleton_directory ( h ) ) ;
if ( r < 0 )
return r ;
r = home_store_embedded_identity ( h , dir_fd , h - > uid , NULL ) ;
if ( r < 0 )
return r ;
r = chown_recursive_directory ( dir_fd , h - > uid ) ;
if ( r < 0 )
return r ;
r = change_access_mode ( dir_fd , user_record_access_mode ( h ) ) ;
if ( r < 0 )
return r ;
return 0 ;
}
static int user_record_compile_effective_passwords (
UserRecord * h ,
2020-04-14 19:18:09 +02:00
PasswordCache * cache ,
char * * * ret_effective_passwords ) {
2019-07-04 18:35:39 +02:00
2020-04-14 19:18:09 +02:00
_cleanup_ ( strv_free_erasep ) char * * effective = NULL ;
2019-07-04 18:35:39 +02:00
size_t n ;
char * * i ;
int r ;
assert ( h ) ;
2020-04-14 19:18:09 +02:00
assert ( cache ) ;
2019-07-04 18:35:39 +02:00
/* We insist on at least one classic hashed password to be defined in addition to any PKCS#11 one, as
* a safe fallback , but also to simplify the password changing algorithm : there we require providing
* the old literal password only ( and do not care for the old PKCS # 11 token ) */
if ( strv_isempty ( h - > hashed_password ) )
return log_error_errno ( EINVAL , " User record has no hashed passwords, refusing. " ) ;
/* Generates the list of plaintext passwords to propagate to LUKS/fscrypt devices, and checks whether
* we have a plaintext password for each hashed one . If we are missing one we ' ll fail , since we
* couldn ' t sync fscrypt / LUKS to the login account properly . */
STRV_FOREACH ( i , h - > hashed_password ) {
bool found = false ;
char * * j ;
log_debug ( " Looking for plaintext password for: %s " , * i ) ;
/* Let's scan all provided plaintext passwords */
STRV_FOREACH ( j , h - > password ) {
r = test_password_one ( * i , * j ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to test plain text password: %m " ) ;
if ( r > 0 ) {
if ( ret_effective_passwords ) {
r = strv_extend ( & effective , * j ) ;
if ( r < 0 )
return log_oom ( ) ;
}
log_debug ( " Found literal plaintext password. " ) ;
found = true ;
break ;
}
}
if ( ! found )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOKEY ) , " Missing plaintext password for defined hashed password " ) ;
}
for ( n = 0 ; n < h - > n_pkcs11_encrypted_key ; n + + ) {
# if HAVE_P11KIT
_cleanup_ ( pkcs11_callback_data_release ) struct pkcs11_callback_data data = {
. user_record = h ,
. secret = h ,
. encrypted_key = h - > pkcs11_encrypted_key + n ,
} ;
r = pkcs11_find_token ( data . encrypted_key - > uri , pkcs11_callback , & data ) ;
if ( r = = - EAGAIN )
return - EBADSLT ;
if ( r < 0 )
return r ;
r = test_password_one ( data . encrypted_key - > hashed_password , data . decrypted_password ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to test PKCS#11 password: %m " ) ;
if ( r = = 0 )
return log_error_errno ( SYNTHETIC_ERRNO ( EPERM ) , " Decrypted password from token is not correct, refusing. " ) ;
if ( ret_effective_passwords ) {
r = strv_extend ( & effective , data . decrypted_password ) ;
if ( r < 0 )
return log_oom ( ) ;
}
2020-04-14 19:18:09 +02:00
r = strv_extend ( & cache - > pkcs11_passwords , data . decrypted_password ) ;
if ( r < 0 )
return log_oom ( ) ;
# else
return - EBADSLT ;
# endif
}
for ( n = 0 ; n < h - > n_fido2_hmac_salt ; n + + ) {
# if HAVE_LIBFIDO2
_cleanup_ ( erase_and_freep ) char * decrypted_password = NULL ;
r = fido2_use_token ( h , h , h - > fido2_hmac_salt + n , & decrypted_password ) ;
if ( r < 0 )
return r ;
r = test_password_one ( h - > fido2_hmac_salt [ n ] . hashed_password , decrypted_password ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to test FIDO2 password: %m " ) ;
if ( r = = 0 )
return log_error_errno ( SYNTHETIC_ERRNO ( EPERM ) , " Decrypted password from token is not correct, refusing. " ) ;
if ( ret_effective_passwords ) {
r = strv_extend ( & effective , decrypted_password ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return log_oom ( ) ;
}
2020-04-14 19:18:09 +02:00
r = strv_extend ( & cache - > fido2_passwords , decrypted_password ) ;
if ( r < 0 )
return log_oom ( ) ;
2019-07-04 18:35:39 +02:00
# else
return - EBADSLT ;
# endif
}
if ( ret_effective_passwords )
* ret_effective_passwords = TAKE_PTR ( effective ) ;
return 0 ;
}
2020-05-01 19:42:56 +02:00
static int determine_default_storage ( UserStorage * ret ) {
UserStorage storage = _USER_STORAGE_INVALID ;
const char * e ;
int r ;
assert ( ret ) ;
/* homed tells us via an environment variable which default storage to use */
e = getenv ( " SYSTEMD_HOME_DEFAULT_STORAGE " ) ;
if ( e ) {
storage = user_storage_from_string ( e ) ;
if ( storage < 0 )
log_warning ( " $SYSTEMD_HOME_DEFAULT_STORAGE set to invalid storage type, ignoring: %s " , e ) ;
else {
log_info ( " Using configured default storage '%s'. " , user_storage_to_string ( storage ) ) ;
* ret = storage ;
return 0 ;
}
}
/* When neither user nor admin specified the storage type to use, fix it to be LUKS — unless we run
* in a container where loopback devices and LUKS / DM are not available . Also , if / home is encrypted
* anyway , let ' s avoid duplicate encryption . Note that we typically default to the assumption of
* " classic " storage for most operations . However , if we create a new home , then let ' s user LUKS if
* nothing is specified . */
r = detect_container ( ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to determine whether we are in a container: %m " ) ;
if ( r = = 0 ) {
r = path_is_encrypted ( " /home " ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to determine if /home is encrypted, ignoring: %m " ) ;
if ( r < = 0 ) {
log_info ( " Using automatic default storage of '%s'. " , user_storage_to_string ( USER_LUKS ) ) ;
* ret = USER_LUKS ;
return 0 ;
}
log_info ( " /home is encrypted, not using '%s' storage, in order to avoid double encryption. " , user_storage_to_string ( USER_LUKS ) ) ;
} else
log_info ( " Running in container, not using '%s' storage. " , user_storage_to_string ( USER_LUKS ) ) ;
r = path_is_fs_type ( " /home " , BTRFS_SUPER_MAGIC ) ;
if ( r < 0 )
log_warning_errno ( r , " Failed to determine file system of /home, ignoring: %m " ) ;
if ( r > 0 ) {
log_info ( " /home is on btrfs, using '%s' as storage. " , user_storage_to_string ( USER_SUBVOLUME ) ) ;
* ret = USER_SUBVOLUME ;
} else {
log_info ( " /home is on simple file system, using '%s' as storage. " , user_storage_to_string ( USER_DIRECTORY ) ) ;
* ret = USER_DIRECTORY ;
}
return 0 ;
}
2019-07-04 18:35:39 +02:00
static int home_create ( UserRecord * h , UserRecord * * ret_home ) {
2020-04-14 19:18:09 +02:00
_cleanup_ ( strv_free_erasep ) char * * effective_passwords = NULL ;
2019-07-04 18:35:39 +02:00
_cleanup_ ( user_record_unrefp ) UserRecord * new_home = NULL ;
2020-04-14 19:18:09 +02:00
_cleanup_ ( password_cache_free ) PasswordCache cache = { } ;
2020-05-01 19:42:56 +02:00
UserStorage new_storage = _USER_STORAGE_INVALID ;
const char * new_fs = NULL ;
2019-07-04 18:35:39 +02:00
int r ;
assert ( h ) ;
if ( ! h - > user_name )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record lacks name, refusing. " ) ;
if ( ! uid_is_valid ( h - > uid ) )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record lacks UID, refusing. " ) ;
2020-04-14 19:18:09 +02:00
r = user_record_compile_effective_passwords ( h , & cache , & effective_passwords ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
r = user_record_test_home_directory_and_warn ( h ) ;
if ( r < 0 )
return r ;
if ( r ! = USER_TEST_ABSENT )
return log_error_errno ( SYNTHETIC_ERRNO ( EEXIST ) , " Home directory %s already exists, refusing. " , user_record_home_directory ( h ) ) ;
if ( h - > storage < 0 ) {
2020-05-01 19:42:56 +02:00
r = determine_default_storage ( & new_storage ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
2020-05-01 19:42:56 +02:00
return r ;
}
2019-07-04 18:35:39 +02:00
2020-05-01 19:42:56 +02:00
if ( ( h - > storage = = USER_LUKS | |
( h - > storage < 0 & & new_storage = = USER_LUKS ) ) & &
! h - > file_system_type )
new_fs = getenv ( " SYSTEMD_HOME_DEFAULT_FILE_SYSTEM_TYPE " ) ;
2019-07-04 18:35:39 +02:00
2020-05-01 19:42:56 +02:00
if ( new_storage > = 0 | | new_fs ) {
2019-07-04 18:35:39 +02:00
r = user_record_add_binding (
h ,
new_storage ,
NULL ,
SD_ID128_NULL ,
SD_ID128_NULL ,
SD_ID128_NULL ,
NULL ,
NULL ,
UINT64_MAX ,
2020-05-01 19:42:56 +02:00
new_fs ,
2019-07-04 18:35:39 +02:00
NULL ,
UID_INVALID ,
GID_INVALID ) ;
if ( r < 0 )
return log_error_errno ( r , " Failed to change storage type to LUKS: %m " ) ;
}
r = user_record_test_image_path_and_warn ( h ) ;
if ( r < 0 )
return r ;
if ( ! IN_SET ( r , USER_TEST_ABSENT , USER_TEST_UNDEFINED , USER_TEST_MAYBE ) )
return log_error_errno ( SYNTHETIC_ERRNO ( EEXIST ) , " Image path %s already exists, refusing. " , user_record_image_path ( h ) ) ;
switch ( user_record_storage ( h ) ) {
case USER_LUKS :
2020-04-14 19:18:09 +02:00
r = home_create_luks ( h , & cache , effective_passwords , & new_home ) ;
2019-07-04 18:35:39 +02:00
break ;
case USER_DIRECTORY :
case USER_SUBVOLUME :
r = home_create_directory_or_subvolume ( h , & new_home ) ;
break ;
case USER_FSCRYPT :
r = home_create_fscrypt ( h , effective_passwords , & new_home ) ;
break ;
case USER_CIFS :
r = home_create_cifs ( h , & new_home ) ;
break ;
default :
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) ,
" Creating home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
}
if ( r < 0 )
return r ;
if ( user_record_equal ( h , new_home ) ) {
* ret_home = NULL ;
return 0 ;
}
* ret_home = TAKE_PTR ( new_home ) ;
return 1 ;
}
static int home_remove ( UserRecord * h ) {
bool deleted = false ;
const char * ip , * hd ;
int r ;
assert ( h ) ;
if ( ! h - > user_name )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record lacks user name, refusing. " ) ;
if ( ! IN_SET ( user_record_storage ( h ) , USER_LUKS , USER_DIRECTORY , USER_SUBVOLUME , USER_FSCRYPT , USER_CIFS ) )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) , " Removing home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
hd = user_record_home_directory ( h ) ;
r = user_record_test_home_directory_and_warn ( h ) ;
if ( r < 0 )
return r ;
if ( r = = USER_TEST_MOUNTED )
return log_error_errno ( SYNTHETIC_ERRNO ( EBUSY ) , " Directory %s is still mounted, refusing. " , hd ) ;
assert ( hd ) ;
r = user_record_test_image_path_and_warn ( h ) ;
if ( r < 0 )
return r ;
ip = user_record_image_path ( h ) ;
switch ( user_record_storage ( h ) ) {
case USER_LUKS : {
struct stat st ;
assert ( ip ) ;
if ( stat ( ip , & st ) < 0 ) {
2020-05-20 18:10:58 +02:00
if ( errno ! = ENOENT )
2020-03-31 12:50:13 +02:00
return log_error_errno ( errno , " Failed to stat() % s : % m " , ip) ;
2019-07-04 18:35:39 +02:00
} else {
if ( S_ISREG ( st . st_mode ) ) {
if ( unlink ( ip ) < 0 ) {
if ( errno ! = ENOENT )
return log_error_errno ( errno , " Failed to remove %s: %m " , ip ) ;
} else
deleted = true ;
} else if ( S_ISBLK ( st . st_mode ) )
log_info ( " Not removing file system on block device %s. " , ip ) ;
else
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTBLK ) , " Image file %s is neither block device, nor regular, refusing removal. " , ip ) ;
}
break ;
}
case USER_SUBVOLUME :
case USER_DIRECTORY :
case USER_FSCRYPT :
assert ( ip ) ;
r = rm_rf ( ip , REMOVE_ROOT | REMOVE_PHYSICAL | REMOVE_SUBVOLUME ) ;
if ( r < 0 ) {
if ( r ! = - ENOENT )
return log_warning_errno ( r , " Failed to remove %s: %m " , ip ) ;
} else
deleted = true ;
/* If the image path and the home directory are the same invalidate the home directory, so
* that we don ' t remove it anymore */
if ( path_equal ( ip , hd ) )
hd = NULL ;
break ;
case USER_CIFS :
/* Nothing else to do here: we won't remove remote stuff. */
log_info ( " Not removing home directory on remote server. " ) ;
break ;
default :
assert_not_reached ( " unknown storage type " ) ;
}
if ( hd ) {
if ( rmdir ( hd ) < 0 ) {
if ( errno ! = ENOENT )
return log_error_errno ( errno , " Failed to remove %s, ignoring: %m " , hd ) ;
} else
deleted = true ;
}
if ( deleted )
log_info ( " Everything completed. " ) ;
2020-04-21 20:23:33 +02:00
else
return log_notice_errno ( SYNTHETIC_ERRNO ( EALREADY ) ,
" Nothing to remove. " ) ;
2019-07-04 18:35:39 +02:00
return 0 ;
}
static int home_validate_update ( UserRecord * h , HomeSetup * setup ) {
bool has_mount = false ;
int r ;
assert ( h ) ;
assert ( setup ) ;
if ( ! h - > user_name )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record lacks user name, refusing. " ) ;
if ( ! uid_is_valid ( h - > uid ) )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record lacks UID, refusing. " ) ;
if ( ! IN_SET ( user_record_storage ( h ) , USER_LUKS , USER_DIRECTORY , USER_SUBVOLUME , USER_FSCRYPT , USER_CIFS ) )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) , " Processing home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
r = user_record_test_home_directory_and_warn ( h ) ;
if ( r < 0 )
return r ;
has_mount = r = = USER_TEST_MOUNTED ;
r = user_record_test_image_path_and_warn ( h ) ;
if ( r < 0 )
return r ;
if ( r = = USER_TEST_ABSENT )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOENT ) , " Image path %s does not exist " , user_record_image_path ( h ) ) ;
switch ( user_record_storage ( h ) ) {
case USER_DIRECTORY :
case USER_SUBVOLUME :
case USER_FSCRYPT :
case USER_CIFS :
break ;
case USER_LUKS : {
r = home_validate_update_luks ( h , setup ) ;
if ( r < 0 )
return r ;
if ( ( r > 0 ) ! = has_mount )
return log_error_errno ( SYNTHETIC_ERRNO ( EBUSY ) , " Home mount incompletely set up. " ) ;
break ;
}
default :
assert_not_reached ( " unexpected storage type " ) ;
}
return has_mount ; /* return true if the home record is already active */
}
static int home_update ( UserRecord * h , UserRecord * * ret ) {
_cleanup_ ( user_record_unrefp ) UserRecord * new_home = NULL , * header_home = NULL , * embedded_home = NULL ;
_cleanup_ ( home_setup_undo ) HomeSetup setup = HOME_SETUP_INIT ;
2020-04-14 19:18:09 +02:00
_cleanup_ ( password_cache_free ) PasswordCache cache = { } ;
2019-07-04 18:35:39 +02:00
bool already_activated = false ;
int r ;
assert ( h ) ;
assert ( ret ) ;
2020-04-14 19:18:09 +02:00
r = user_record_authenticate ( h , h , & cache , /* strict_verify= */ true ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
2020-05-20 18:38:07 +02:00
assert ( r > 0 ) ; /* Insist that a password was verified */
2019-07-04 18:35:39 +02:00
r = home_validate_update ( h , & setup ) ;
if ( r < 0 )
return r ;
already_activated = r > 0 ;
2020-04-14 19:18:09 +02:00
r = home_prepare ( h , already_activated , & cache , & setup , & header_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
2020-04-14 19:18:09 +02:00
r = home_load_embedded_identity ( h , setup . root_fd , header_home , USER_RECONCILE_REQUIRE_NEWER , & cache , & embedded_home , & new_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
r = home_store_header_identity_luks ( new_home , & setup , header_home ) ;
if ( r < 0 )
return r ;
r = home_store_embedded_identity ( new_home , setup . root_fd , h - > uid , embedded_home ) ;
if ( r < 0 )
return r ;
r = home_extend_embedded_identity ( new_home , h , & setup ) ;
if ( r < 0 )
return r ;
r = home_sync_and_statfs ( setup . root_fd , NULL ) ;
if ( r < 0 )
return r ;
r = home_setup_undo ( & setup ) ;
if ( r < 0 )
return r ;
log_info ( " Everything completed. " ) ;
* ret = TAKE_PTR ( new_home ) ;
return 0 ;
}
static int home_resize ( UserRecord * h , UserRecord * * ret ) {
_cleanup_ ( home_setup_undo ) HomeSetup setup = HOME_SETUP_INIT ;
2020-04-14 19:18:09 +02:00
_cleanup_ ( password_cache_free ) PasswordCache cache = { } ;
2019-07-04 18:35:39 +02:00
bool already_activated = false ;
int r ;
assert ( h ) ;
assert ( ret ) ;
if ( h - > disk_size = = UINT64_MAX )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " No target size specified, refusing. " ) ;
2020-04-14 19:18:09 +02:00
r = user_record_authenticate ( h , h , & cache , /* strict_verify= */ true ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
2020-05-20 18:38:07 +02:00
assert ( r > 0 ) ; /* Insist that a password was verified */
2019-07-04 18:35:39 +02:00
r = home_validate_update ( h , & setup ) ;
if ( r < 0 )
return r ;
already_activated = r > 0 ;
switch ( user_record_storage ( h ) ) {
case USER_LUKS :
2020-04-14 19:18:09 +02:00
return home_resize_luks ( h , already_activated , & cache , & setup , ret ) ;
2019-07-04 18:35:39 +02:00
case USER_DIRECTORY :
case USER_SUBVOLUME :
case USER_FSCRYPT :
2020-04-14 19:18:09 +02:00
return home_resize_directory ( h , already_activated , & cache , & setup , ret ) ;
2019-07-04 18:35:39 +02:00
default :
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) , " Resizing home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
}
}
static int home_passwd ( UserRecord * h , UserRecord * * ret_home ) {
_cleanup_ ( user_record_unrefp ) UserRecord * header_home = NULL , * embedded_home = NULL , * new_home = NULL ;
2020-04-14 19:18:09 +02:00
_cleanup_ ( strv_free_erasep ) char * * effective_passwords = NULL ;
2019-07-04 18:35:39 +02:00
_cleanup_ ( home_setup_undo ) HomeSetup setup = HOME_SETUP_INIT ;
2020-04-14 19:18:09 +02:00
_cleanup_ ( password_cache_free ) PasswordCache cache = { } ;
2019-07-04 18:35:39 +02:00
bool already_activated = false ;
int r ;
assert ( h ) ;
assert ( ret_home ) ;
if ( ! IN_SET ( user_record_storage ( h ) , USER_LUKS , USER_DIRECTORY , USER_SUBVOLUME , USER_FSCRYPT ) )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) , " Changing password of home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
2020-04-14 19:18:09 +02:00
r = user_record_compile_effective_passwords ( h , & cache , & effective_passwords ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
r = home_validate_update ( h , & setup ) ;
if ( r < 0 )
return r ;
already_activated = r > 0 ;
2020-04-14 19:18:09 +02:00
r = home_prepare ( h , already_activated , & cache , & setup , & header_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
2020-04-14 19:18:09 +02:00
r = home_load_embedded_identity ( h , setup . root_fd , header_home , USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL , & cache , & embedded_home , & new_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
switch ( user_record_storage ( h ) ) {
case USER_LUKS :
2020-04-14 19:18:09 +02:00
r = home_passwd_luks ( h , & setup , & cache , effective_passwords ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
break ;
case USER_FSCRYPT :
2020-04-14 19:18:09 +02:00
r = home_passwd_fscrypt ( h , & setup , & cache , effective_passwords ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
break ;
default :
break ;
}
r = home_store_header_identity_luks ( new_home , & setup , header_home ) ;
if ( r < 0 )
return r ;
r = home_store_embedded_identity ( new_home , setup . root_fd , h - > uid , embedded_home ) ;
if ( r < 0 )
return r ;
r = home_extend_embedded_identity ( new_home , h , & setup ) ;
if ( r < 0 )
return r ;
r = home_sync_and_statfs ( setup . root_fd , NULL ) ;
if ( r < 0 )
return r ;
r = home_setup_undo ( & setup ) ;
if ( r < 0 )
return r ;
log_info ( " Everything completed. " ) ;
* ret_home = TAKE_PTR ( new_home ) ;
return 1 ;
}
static int home_inspect ( UserRecord * h , UserRecord * * ret_home ) {
_cleanup_ ( user_record_unrefp ) UserRecord * header_home = NULL , * new_home = NULL ;
_cleanup_ ( home_setup_undo ) HomeSetup setup = HOME_SETUP_INIT ;
2020-04-14 19:18:09 +02:00
_cleanup_ ( password_cache_free ) PasswordCache cache = { } ;
2019-07-04 18:35:39 +02:00
bool already_activated = false ;
int r ;
assert ( h ) ;
assert ( ret_home ) ;
2020-04-14 19:18:09 +02:00
r = user_record_authenticate ( h , h , & cache , /* strict_verify= */ false ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
r = home_validate_update ( h , & setup ) ;
if ( r < 0 )
return r ;
already_activated = r > 0 ;
2020-04-14 19:18:09 +02:00
r = home_prepare ( h , already_activated , & cache , & setup , & header_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
2020-04-14 19:18:09 +02:00
r = home_load_embedded_identity ( h , setup . root_fd , header_home , USER_RECONCILE_ANY , & cache , NULL , & new_home ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
r = home_extend_embedded_identity ( new_home , h , & setup ) ;
if ( r < 0 )
return r ;
r = home_setup_undo ( & setup ) ;
if ( r < 0 )
return r ;
log_info ( " Everything completed. " ) ;
* ret_home = TAKE_PTR ( new_home ) ;
return 1 ;
}
static int home_lock ( UserRecord * h ) {
int r ;
assert ( h ) ;
if ( ! h - > user_name )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record incomplete, refusing. " ) ;
if ( user_record_storage ( h ) ! = USER_LUKS )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) , " Locking home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
r = user_record_test_home_directory_and_warn ( h ) ;
if ( r < 0 )
return r ;
if ( r ! = USER_TEST_MOUNTED )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOEXEC ) , " Home directory of %s is not mounted, can't lock. " , h - > user_name ) ;
r = home_lock_luks ( h ) ;
if ( r < 0 )
return r ;
log_info ( " Everything completed. " ) ;
return 1 ;
}
static int home_unlock ( UserRecord * h ) {
2020-04-14 19:18:09 +02:00
_cleanup_ ( password_cache_free ) PasswordCache cache = { } ;
2019-07-04 18:35:39 +02:00
int r ;
assert ( h ) ;
if ( ! h - > user_name )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " User record incomplete, refusing. " ) ;
if ( user_record_storage ( h ) ! = USER_LUKS )
return log_error_errno ( SYNTHETIC_ERRNO ( ENOTTY ) , " Unlocking home directories of type '%s' currently not supported. " , user_storage_to_string ( user_record_storage ( h ) ) ) ;
/* Note that we don't check if $HOME is actually mounted, since we want to avoid disk accesses on
* that mount until we have resumed the device . */
2020-04-14 19:18:09 +02:00
r = user_record_authenticate ( h , h , & cache , /* strict_verify= */ false ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
2020-04-14 19:18:09 +02:00
r = home_unlock_luks ( h , & cache ) ;
2019-07-04 18:35:39 +02:00
if ( r < 0 )
return r ;
log_info ( " Everything completed. " ) ;
return 1 ;
}
static int run ( int argc , char * argv [ ] ) {
_cleanup_ ( user_record_unrefp ) UserRecord * home = NULL , * new_home = NULL ;
_cleanup_ ( json_variant_unrefp ) JsonVariant * v = NULL ;
_cleanup_ ( fclosep ) FILE * opened_file = NULL ;
unsigned line = 0 , column = 0 ;
const char * json_path = NULL ;
FILE * json_file ;
usec_t start ;
int r ;
start = now ( CLOCK_MONOTONIC ) ;
log_setup_service ( ) ;
umask ( 0022 ) ;
if ( argc < 2 | | argc > 3 )
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " This program takes one or two arguments. " ) ;
if ( argc > 2 ) {
json_path = argv [ 2 ] ;
opened_file = fopen ( json_path , " re " ) ;
if ( ! opened_file )
return log_error_errno ( errno , " Failed to open %s: %m " , json_path ) ;
json_file = opened_file ;
} else {
json_path = " <stdin> " ;
json_file = stdin ;
}
r = json_parse_file ( json_file , json_path , JSON_PARSE_SENSITIVE , & v , & line , & column ) ;
if ( r < 0 )
return log_error_errno ( r , " [%s:%u:%u] Failed to parse JSON data: %m " , json_path , line , column ) ;
home = user_record_new ( ) ;
if ( ! home )
return log_oom ( ) ;
r = user_record_load ( home , v , USER_RECORD_LOAD_FULL | USER_RECORD_LOG ) ;
if ( r < 0 )
return r ;
/* Well known return values of these operations, that systemd-homed knows and converts to proper D-Bus errors:
*
2020-04-16 17:50:21 +02:00
* EMSGSIZE → file systems of this type cannot be shrunk
* ETXTBSY → file systems of this type can only be shrunk offline
2019-07-04 18:35:39 +02:00
* ERANGE → file system size too small
* ENOLINK → system does not support selected storage backend
* EPROTONOSUPPORT → system does not support selected file system
* ENOTTY → operation not support on this storage
* ESOCKTNOSUPPORT → operation not support on this file system
* ENOKEY → password incorrect ( or not sufficient , or not supplied )
* EBADSLT → similar , but PKCS # 11 device is defined and might be able to provide password , if it was plugged in which it is not
2020-04-14 19:18:09 +02:00
* ENOANO → suitable PKCS # 11 / FIDO2 device found , but PIN is missing to unlock it
2019-07-04 18:35:39 +02:00
* ERFKILL → suitable PKCS # 11 device found , but OK to ask for on - device interactive authentication not given
2020-04-14 19:18:09 +02:00
* EMEDIUMTYPE → suitable FIDO2 device found , but OK to ask for user presence not given
* ENOSTR → suitable FIDO2 device found , but user didn ' t react to action request on token quickly enough
* EOWNERDEAD → suitable PKCS # 11 / FIDO2 device found , but its PIN is locked
* ENOLCK → suitable PKCS # 11 / FIDO2 device found , but PIN incorrect
2019-07-04 18:35:39 +02:00
* ETOOMANYREFS → suitable PKCS # 11 device found , but PIN incorrect , and only few tries left
* EUCLEAN → suitable PKCS # 11 device found , but PIN incorrect , and only one try left
* EBUSY → file system is currently active
* ENOEXEC → file system is currently not active
* ENOSPC → not enough disk space for operation
2020-05-20 17:02:12 +02:00
* EKEYREVOKED → user record has not suitable hashed password or pkcs # 11 entry , we cannot authenticate
2019-07-04 18:35:39 +02:00
*/
if ( streq ( argv [ 1 ] , " activate " ) )
r = home_activate ( home , & new_home ) ;
else if ( streq ( argv [ 1 ] , " deactivate " ) )
r = home_deactivate ( home , false ) ;
else if ( streq ( argv [ 1 ] , " deactivate-force " ) )
r = home_deactivate ( home , true ) ;
else if ( streq ( argv [ 1 ] , " create " ) )
r = home_create ( home , & new_home ) ;
else if ( streq ( argv [ 1 ] , " remove " ) )
r = home_remove ( home ) ;
else if ( streq ( argv [ 1 ] , " update " ) )
r = home_update ( home , & new_home ) ;
else if ( streq ( argv [ 1 ] , " resize " ) )
r = home_resize ( home , & new_home ) ;
else if ( streq ( argv [ 1 ] , " passwd " ) )
r = home_passwd ( home , & new_home ) ;
else if ( streq ( argv [ 1 ] , " inspect " ) )
r = home_inspect ( home , & new_home ) ;
else if ( streq ( argv [ 1 ] , " lock " ) )
r = home_lock ( home ) ;
else if ( streq ( argv [ 1 ] , " unlock " ) )
r = home_unlock ( home ) ;
else
return log_error_errno ( SYNTHETIC_ERRNO ( EINVAL ) , " Unknown verb '%s'. " , argv [ 1 ] ) ;
if ( r = = - ENOKEY & & ! strv_isempty ( home - > password ) ) { /* There were passwords specified but they were incorrect */
usec_t end , n , d ;
/* Make sure bad password replies always take at least 3s, and if longer multiples of 3s, so
* that it ' s not clear how long we actually needed for our calculations . */
n = now ( CLOCK_MONOTONIC ) ;
assert ( n > = start ) ;
d = usec_sub_unsigned ( n , start ) ;
if ( d > BAD_PASSWORD_DELAY_USEC )
end = start + DIV_ROUND_UP ( d , BAD_PASSWORD_DELAY_USEC ) * BAD_PASSWORD_DELAY_USEC ;
else
end = start + BAD_PASSWORD_DELAY_USEC ;
if ( n < end )
( void ) usleep ( usec_sub_unsigned ( end , n ) ) ;
}
if ( r < 0 )
return r ;
/* We always pass the new record back, regardless if it changed or not. This allows our caller to
* prepare a fresh record , send to us , and only if it works use it without having to keep a local
* copy . */
if ( new_home )
json_variant_dump ( new_home - > json , JSON_FORMAT_NEWLINE , stdout , NULL ) ;
return 0 ;
}
DEFINE_MAIN_FUNCTION ( run ) ;