2010-11-08 07:02:45 +03:00
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd .
Copyright 2010 Lennart Poettering
systemd is free software ; you can redistribute it and / or modify it
2012-04-12 02:20:58 +04:00
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation ; either version 2.1 of the License , or
2010-11-08 07:02:45 +03:00
( at your option ) any later version .
systemd is distributed in the hope that it will be useful , but
WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
2012-04-12 02:20:58 +04:00
Lesser General Public License for more details .
2010-11-08 07:02:45 +03:00
2012-04-12 02:20:58 +04:00
You should have received a copy of the GNU Lesser General Public License
2010-11-08 07:02:45 +03:00
along with systemd ; If not , see < http : //www.gnu.org/licenses/>.
* * */
# include <string.h>
2010-11-12 02:39:17 +03:00
# include <errno.h>
2010-11-16 05:23:52 +03:00
# include <sys/mman.h>
2011-02-23 20:46:27 +03:00
# include <mntent.h>
2010-11-12 02:39:17 +03:00
# include <libcryptsetup.h>
2010-11-19 01:34:42 +03:00
# include <libudev.h>
2010-11-08 07:02:45 +03:00
# include "log.h"
# include "util.h"
2012-05-07 23:36:12 +04:00
# include "path-util.h"
2011-02-23 03:12:07 +03:00
# include "strv.h"
2010-11-12 02:39:17 +03:00
# include "ask-password-api.h"
2011-03-17 06:02:35 +03:00
# include "def.h"
2010-11-12 02:39:17 +03:00
2010-11-14 03:53:46 +03:00
static const char * opt_type = NULL ; /* LUKS1 or PLAIN */
2010-11-12 02:39:17 +03:00
static char * opt_cipher = NULL ;
2010-11-14 03:53:46 +03:00
static unsigned opt_key_size = 0 ;
2012-08-03 14:47:24 +04:00
static unsigned opt_keyfile_size = 0 ;
2012-06-29 16:36:37 +04:00
static unsigned opt_keyfile_offset = 0 ;
2010-11-12 02:39:17 +03:00
static char * opt_hash = NULL ;
2010-11-14 03:53:46 +03:00
static unsigned opt_tries = 0 ;
2010-11-12 02:39:17 +03:00
static bool opt_readonly = false ;
static bool opt_verify = false ;
2012-05-19 19:05:50 +04:00
static bool opt_discards = false ;
2013-04-12 11:45:26 +04:00
static usec_t opt_timeout = 0 ;
2010-11-12 02:39:17 +03:00
2010-11-14 04:08:31 +03:00
/* Options Debian's crypttab knows we don't:
offset =
skip =
precheck =
check =
checkargs =
noearly =
loud =
keyscript =
*/
2010-11-12 02:39:17 +03:00
static int parse_one_option ( const char * option ) {
assert ( option ) ;
/* Handled outside of this tool */
2012-11-21 15:30:47 +04:00
if ( streq ( option , " noauto " ) | | streq ( option , " nofail " ) )
2010-11-12 02:39:17 +03:00
return 0 ;
if ( startswith ( option , " cipher= " ) ) {
char * t ;
2013-03-26 18:23:54 +04:00
t = strdup ( option + 7 ) ;
if ( ! t )
2010-11-12 02:39:17 +03:00
return - ENOMEM ;
free ( opt_cipher ) ;
opt_cipher = t ;
} else if ( startswith ( option , " size= " ) ) {
2010-11-14 03:53:46 +03:00
if ( safe_atou ( option + 5 , & opt_key_size ) < 0 ) {
2010-11-12 02:39:17 +03:00
log_error ( " size= parse failure, ignoring. " ) ;
return 0 ;
}
2012-08-03 14:47:24 +04:00
} else if ( startswith ( option , " keyfile-size= " ) ) {
if ( safe_atou ( option + 13 , & opt_keyfile_size ) < 0 ) {
log_error ( " keyfile-size= parse failure, ignoring. " ) ;
return 0 ;
}
2012-06-29 16:36:37 +04:00
} else if ( startswith ( option , " keyfile-offset= " ) ) {
if ( safe_atou ( option + 15 , & opt_keyfile_offset ) < 0 ) {
log_error ( " keyfile-offset= parse failure, ignoring. " ) ;
return 0 ;
}
2010-11-12 02:39:17 +03:00
} else if ( startswith ( option , " hash= " ) ) {
char * t ;
2013-03-26 18:23:54 +04:00
t = strdup ( option + 5 ) ;
if ( ! t )
2010-11-12 02:39:17 +03:00
return - ENOMEM ;
free ( opt_hash ) ;
opt_hash = t ;
} else if ( startswith ( option , " tries= " ) ) {
if ( safe_atou ( option + 6 , & opt_tries ) < 0 ) {
log_error ( " tries= parse failure, ignoring. " ) ;
return 0 ;
}
2013-01-31 14:03:09 +04:00
} else if ( streq ( option , " readonly " ) | | streq ( option , " read-only " ) )
2010-11-12 02:39:17 +03:00
opt_readonly = true ;
else if ( streq ( option , " verify " ) )
opt_verify = true ;
2012-05-19 19:05:50 +04:00
else if ( streq ( option , " allow-discards " ) )
opt_discards = true ;
2010-11-14 03:53:46 +03:00
else if ( streq ( option , " luks " ) )
opt_type = CRYPT_LUKS1 ;
else if ( streq ( option , " plain " ) | |
streq ( option , " swap " ) | |
streq ( option , " tmp " ) )
opt_type = CRYPT_PLAIN ;
2010-11-12 02:39:17 +03:00
else if ( startswith ( option , " timeout= " ) ) {
2013-04-02 22:38:16 +04:00
if ( parse_sec ( option + 8 , & opt_timeout ) < 0 ) {
2010-11-12 02:39:17 +03:00
log_error ( " timeout= parse failure, ignoring. " ) ;
return 0 ;
}
2011-08-04 18:04:43 +04:00
} else if ( ! streq ( option , " none " ) )
2010-11-12 02:39:17 +03:00
log_error ( " Encountered unknown /etc/crypttab option '%s', ignoring. " , option ) ;
return 0 ;
}
static int parse_options ( const char * options ) {
2013-03-26 18:23:54 +04:00
char * state , * w ;
2010-11-12 02:39:17 +03:00
size_t l ;
2013-03-26 18:23:54 +04:00
int r ;
2010-11-12 02:39:17 +03:00
assert ( options ) ;
FOREACH_WORD_SEPARATOR ( w , l , options , " , " , state ) {
2013-03-26 18:23:54 +04:00
_cleanup_free_ char * o ;
2010-11-12 02:39:17 +03:00
2013-03-26 18:23:54 +04:00
o = strndup ( w , l ) ;
if ( ! o )
2010-11-12 02:39:17 +03:00
return - ENOMEM ;
r = parse_one_option ( o ) ;
if ( r < 0 )
return r ;
}
return 0 ;
}
static void log_glue ( int level , const char * msg , void * usrptr ) {
2010-11-14 03:53:46 +03:00
log_debug ( " %s " , msg ) ;
2010-11-12 02:39:17 +03:00
}
2010-11-08 07:02:45 +03:00
2010-11-19 01:34:42 +03:00
static char * disk_description ( const char * path ) {
2013-03-26 18:23:54 +04:00
static const char name_fields [ ] = {
" ID_PART_ENTRY_NAME \0 "
" DM_NAME \0 "
" ID_MODEL_FROM_DATABASE \0 "
" ID_MODEL \0 "
} ;
2010-11-19 01:34:42 +03:00
struct udev * udev = NULL ;
struct udev_device * device = NULL ;
struct stat st ;
char * description = NULL ;
2013-03-26 18:23:54 +04:00
const char * i ;
2010-11-19 01:34:42 +03:00
assert ( path ) ;
if ( stat ( path , & st ) < 0 )
return NULL ;
if ( ! S_ISBLK ( st . st_mode ) )
return NULL ;
2013-03-26 18:23:54 +04:00
udev = udev_new ( ) ;
if ( ! udev )
2010-11-19 01:34:42 +03:00
return NULL ;
2013-03-26 18:23:54 +04:00
device = udev_device_new_from_devnum ( udev , ' b ' , st . st_rdev ) ;
if ( ! device )
2010-11-19 01:34:42 +03:00
goto finish ;
2013-03-26 18:23:54 +04:00
NULSTR_FOREACH ( i , name_fields ) {
const char * name ;
name = udev_device_get_property_value ( device , i ) ;
if ( ! isempty ( name ) ) {
description = strdup ( name ) ;
break ;
}
}
2010-11-19 01:34:42 +03:00
finish :
if ( device )
udev_device_unref ( device ) ;
if ( udev )
udev_unref ( udev ) ;
return description ;
}
2011-02-23 20:46:27 +03:00
static char * disk_mount_point ( const char * label ) {
2013-07-13 15:19:36 +04:00
char * mp = NULL ;
_cleanup_free_ char * device = NULL ;
2011-02-23 20:46:27 +03:00
FILE * f = NULL ;
struct mntent * m ;
/* Yeah, we don't support native systemd unit files here for now */
if ( asprintf ( & device , " /dev/mapper/%s " , label ) < 0 )
goto finish ;
2012-04-22 17:33:43 +04:00
f = setmntent ( " /etc/fstab " , " r " ) ;
if ( ! f )
2011-02-23 20:46:27 +03:00
goto finish ;
while ( ( m = getmntent ( f ) ) )
if ( path_equal ( m - > mnt_fsname , device ) ) {
mp = strdup ( m - > mnt_dir ) ;
break ;
}
finish :
if ( f )
endmntent ( f ) ;
return mp ;
}
2013-07-13 15:19:36 +04:00
static int get_password ( const char * name , usec_t until , bool accept_cached , char * * * passwords ) {
int r ;
char * * p ;
_cleanup_free_ char * text = NULL ;
assert ( name ) ;
assert ( passwords ) ;
if ( asprintf ( & text , " Please enter passphrase for disk %s! " , name ) < 0 )
return log_oom ( ) ;
r = ask_password_auto ( text , " drive-harddisk " , until , accept_cached , passwords ) ;
if ( r < 0 ) {
log_error ( " Failed to query password: %s " , strerror ( - r ) ) ;
return r ;
}
if ( opt_verify ) {
_cleanup_strv_free_ char * * passwords2 = NULL ;
assert ( strv_length ( * passwords ) = = 1 ) ;
if ( asprintf ( & text , " Please enter passphrase for disk %s! (verification) " , name ) < 0 )
return log_oom ( ) ;
r = ask_password_auto ( text , " drive-harddisk " , until , false , & passwords2 ) ;
if ( r < 0 ) {
log_error ( " Failed to query verification password: %s " , strerror ( - r ) ) ;
return r ;
}
assert ( strv_length ( passwords2 ) = = 1 ) ;
if ( ! streq ( * passwords [ 0 ] , passwords2 [ 0 ] ) ) {
log_warning ( " Passwords did not match, retrying. " ) ;
return - EAGAIN ;
}
}
strv_uniq ( * passwords ) ;
STRV_FOREACH ( p , * passwords ) {
char * c ;
if ( strlen ( * p ) + 1 > = opt_key_size )
continue ;
/* Pad password if necessary */
if ( ! ( c = new ( char , opt_key_size ) ) )
return log_oom ( ) ;
strncpy ( c , * p , opt_key_size ) ;
free ( * p ) ;
* p = c ;
}
return 0 ;
}
2011-02-25 04:56:27 +03:00
static int help ( void ) {
printf ( " %s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS] \n "
" %s detach VOLUME \n \n "
" Attaches or detaches an encrypted block device. \n " ,
program_invocation_short_name ,
program_invocation_short_name ) ;
return 0 ;
}
2010-11-08 07:02:45 +03:00
int main ( int argc , char * argv [ ] ) {
2010-11-12 02:39:17 +03:00
int r = EXIT_FAILURE ;
struct crypt_device * cd = NULL ;
2010-11-08 07:02:45 +03:00
2011-02-25 04:56:27 +03:00
if ( argc < = 1 ) {
help ( ) ;
return EXIT_SUCCESS ;
}
2010-11-08 07:02:45 +03:00
if ( argc < 3 ) {
log_error ( " This program requires at least two arguments. " ) ;
return EXIT_FAILURE ;
}
2010-11-12 03:01:04 +03:00
log_set_target ( LOG_TARGET_AUTO ) ;
2010-11-08 07:02:45 +03:00
log_parse_environment ( ) ;
log_open ( ) ;
2011-08-01 22:52:18 +04:00
umask ( 0022 ) ;
2010-11-14 03:53:46 +03:00
if ( streq ( argv [ 1 ] , " attach " ) ) {
2010-11-12 02:39:17 +03:00
uint32_t flags = 0 ;
int k ;
2010-11-14 03:53:46 +03:00
unsigned try ;
usec_t until ;
2010-11-14 04:01:29 +03:00
crypt_status_info status ;
2013-07-13 15:19:36 +04:00
const char * key_file = NULL , * cipher = NULL , * cipher_mode = NULL ,
* hash = NULL , * name = NULL ;
_cleanup_free_ char * description = NULL , * name_buffer = NULL ,
* mount_point = NULL , * truncated_cipher = NULL ;
2010-11-12 02:39:17 +03:00
2011-02-23 20:46:27 +03:00
/* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */
2010-11-12 02:39:17 +03:00
if ( argc < 4 ) {
log_error ( " attach requires at least two arguments. " ) ;
goto finish ;
}
2010-11-14 04:08:31 +03:00
if ( argc > = 5 & &
argv [ 4 ] [ 0 ] & &
! streq ( argv [ 4 ] , " - " ) & &
! streq ( argv [ 4 ] , " none " ) ) {
2010-11-12 02:39:17 +03:00
if ( ! path_is_absolute ( argv [ 4 ] ) )
log_error ( " Password file path %s is not absolute. Ignoring. " , argv [ 4 ] ) ;
else
key_file = argv [ 4 ] ;
}
2013-03-26 18:23:54 +04:00
if ( argc > = 6 & & argv [ 5 ] [ 0 ] & & ! streq ( argv [ 5 ] , " - " ) ) {
if ( parse_options ( argv [ 5 ] ) < 0 )
goto finish ;
}
2010-11-08 07:02:45 +03:00
2010-11-16 05:23:52 +03:00
/* A delicious drop of snake oil */
mlockall ( MCL_FUTURE ) ;
2010-11-19 01:34:42 +03:00
description = disk_description ( argv [ 3 ] ) ;
2011-02-23 20:46:27 +03:00
mount_point = disk_mount_point ( argv [ 2 ] ) ;
if ( description & & streq ( argv [ 2 ] , description ) ) {
/* If the description string is simply the
* volume name , then let ' s not show this
* twice */
free ( description ) ;
description = NULL ;
}
if ( mount_point & & description )
asprintf ( & name_buffer , " %s (%s) on %s " , description , argv [ 2 ] , mount_point ) ;
else if ( mount_point )
asprintf ( & name_buffer , " %s on %s " , argv [ 2 ] , mount_point ) ;
else if ( description )
asprintf ( & name_buffer , " %s (%s) " , description , argv [ 2 ] ) ;
name = name_buffer ? name_buffer : argv [ 2 ] ;
2010-11-19 01:34:42 +03:00
2013-03-26 18:23:54 +04:00
k = crypt_init ( & cd , argv [ 3 ] ) ;
if ( k ) {
2010-11-12 02:39:17 +03:00
log_error ( " crypt_init() failed: %s " , strerror ( - k ) ) ;
goto finish ;
}
2010-11-08 07:02:45 +03:00
2010-11-12 02:39:17 +03:00
crypt_set_log_callback ( cd , log_glue , NULL ) ;
2010-11-14 03:53:46 +03:00
status = crypt_status ( cd , argv [ 2 ] ) ;
if ( status = = CRYPT_ACTIVE | | status = = CRYPT_BUSY ) {
log_info ( " Volume %s already active. " , argv [ 2 ] ) ;
r = EXIT_SUCCESS ;
2010-11-12 02:39:17 +03:00
goto finish ;
}
if ( opt_readonly )
flags | = CRYPT_ACTIVATE_READONLY ;
2012-05-19 19:05:50 +04:00
if ( opt_discards )
flags | = CRYPT_ACTIVATE_ALLOW_DISCARDS ;
2011-04-13 23:42:46 +04:00
if ( opt_timeout > 0 )
until = now ( CLOCK_MONOTONIC ) + opt_timeout ;
else
until = 0 ;
2010-11-14 03:53:46 +03:00
opt_tries = opt_tries > 0 ? opt_tries : 3 ;
opt_key_size = ( opt_key_size > 0 ? opt_key_size : 256 ) ;
2012-11-06 18:49:27 +04:00
if ( opt_hash ) {
/* plain isn't a real hash type. it just means "use no hash" */
if ( ! streq ( opt_hash , " plain " ) )
hash = opt_hash ;
} else
hash = " ripemd160 " ;
2010-11-14 03:53:46 +03:00
2010-11-14 04:01:29 +03:00
if ( opt_cipher ) {
size_t l ;
l = strcspn ( opt_cipher , " - " ) ;
2013-03-26 18:23:54 +04:00
truncated_cipher = strndup ( opt_cipher , l ) ;
2010-11-14 04:01:29 +03:00
2013-03-26 18:23:54 +04:00
if ( ! truncated_cipher ) {
2012-07-26 01:55:59 +04:00
log_oom ( ) ;
2010-11-14 04:01:29 +03:00
goto finish ;
}
cipher = truncated_cipher ;
cipher_mode = opt_cipher [ l ] ? opt_cipher + l + 1 : " plain " ;
} else {
cipher = " aes " ;
cipher_mode = " cbc-essiv:sha256 " ;
}
2010-11-14 03:53:46 +03:00
for ( try = 0 ; try < opt_tries ; try + + ) {
bool pass_volume_key = false ;
2013-07-13 15:19:36 +04:00
_cleanup_strv_free_ char * * passwords = NULL ;
2010-11-14 03:53:46 +03:00
if ( ! key_file ) {
2013-07-13 15:19:36 +04:00
k = get_password ( name , until , try = = 0 & & ! opt_verify , & passwords ) ;
if ( k = = - EAGAIN )
continue ;
else if ( k < 0 )
2010-11-14 03:53:46 +03:00
goto finish ;
}
2011-04-13 23:42:46 +04:00
k = 0 ;
2010-11-14 03:53:46 +03:00
if ( ! opt_type | | streq ( opt_type , CRYPT_LUKS1 ) )
k = crypt_load ( cd , CRYPT_LUKS1 , NULL ) ;
if ( ( ! opt_type & & k < 0 ) | | streq_ptr ( opt_type , CRYPT_PLAIN ) ) {
2013-03-25 03:59:00 +04:00
struct crypt_params_plain params = { . hash = hash } ;
2010-11-14 03:53:46 +03:00
2012-08-03 14:47:24 +04:00
/* for CRYPT_PLAIN limit reads
2013-07-13 15:19:36 +04:00
* from keyfile to key length , and
* ignore keyfile - size */
2012-08-03 14:47:24 +04:00
opt_keyfile_size = opt_key_size / 8 ;
2010-11-14 03:53:46 +03:00
/* In contrast to what the name
* crypt_setup ( ) might suggest this
* doesn ' t actually format anything ,
* it just configures encryption
* parameters when used for plain
* mode . */
k = crypt_format ( cd , CRYPT_PLAIN ,
cipher ,
cipher_mode ,
NULL ,
NULL ,
2012-08-03 14:47:24 +04:00
opt_keyfile_size ,
2010-11-14 03:53:46 +03:00
& params ) ;
2012-11-06 19:17:18 +04:00
/* hash == NULL implies the user passed "plain" */
pass_volume_key = ( hash = = NULL ) ;
2010-11-14 03:53:46 +03:00
}
if ( k < 0 ) {
log_error ( " Loading of cryptographic parameters failed: %s " , strerror ( - k ) ) ;
goto finish ;
}
log_info ( " Set cipher %s, mode %s, key size %i bits for device %s. " ,
crypt_get_cipher ( cd ) ,
crypt_get_cipher_mode ( cd ) ,
crypt_get_volume_key_size ( cd ) * 8 ,
argv [ 3 ] ) ;
2013-04-30 02:57:29 +04:00
if ( key_file ) {
struct stat st ;
/* Ideally we'd do this on the open
* fd , but since this is just a
* warning it ' s OK to do this in two
* steps */
if ( stat ( key_file , & st ) > = 0 & & ( st . st_mode & 0005 ) )
log_warning ( " Key file %s is world-readable. That's certainly not a good idea. " , key_file ) ;
k = crypt_activate_by_keyfile_offset (
cd , argv [ 2 ] , CRYPT_ANY_SLOT , key_file , opt_keyfile_size ,
opt_keyfile_offset , flags ) ;
2013-04-18 11:41:23 +04:00
if ( k < 0 ) {
log_error ( " Failed to activate with key file '%s': %s " , key_file , strerror ( - k ) ) ;
key_file = NULL ;
continue ;
}
2013-04-30 02:57:29 +04:00
} else {
2011-02-23 03:12:07 +03:00
char * * p ;
STRV_FOREACH ( p , passwords ) {
if ( pass_volume_key )
k = crypt_activate_by_volume_key ( cd , argv [ 2 ] , * p , opt_key_size , flags ) ;
else
k = crypt_activate_by_passphrase ( cd , argv [ 2 ] , CRYPT_ANY_SLOT , * p , strlen ( * p ) , flags ) ;
if ( k > = 0 )
break ;
}
}
2010-11-14 03:53:46 +03:00
if ( k > = 0 )
break ;
if ( k ! = - EPERM ) {
log_error ( " Failed to activate: %s " , strerror ( - k ) ) ;
goto finish ;
}
log_warning ( " Invalid passphrase. " ) ;
2010-11-12 02:39:17 +03:00
}
2010-11-14 03:53:46 +03:00
if ( try > = opt_tries ) {
log_error ( " Too many attempts. " ) ;
r = EXIT_FAILURE ;
2011-01-22 04:18:59 +03:00
goto finish ;
2010-11-12 02:39:17 +03:00
}
} else if ( streq ( argv [ 1 ] , " detach " ) ) {
int k ;
2013-03-26 18:23:54 +04:00
k = crypt_init_by_name ( & cd , argv [ 2 ] ) ;
if ( k ) {
2010-11-12 02:39:17 +03:00
log_error ( " crypt_init() failed: %s " , strerror ( - k ) ) ;
goto finish ;
}
crypt_set_log_callback ( cd , log_glue , NULL ) ;
2013-03-26 18:23:54 +04:00
k = crypt_deactivate ( cd , argv [ 2 ] ) ;
if ( k < 0 ) {
2010-11-12 02:39:17 +03:00
log_error ( " Failed to deactivate: %s " , strerror ( - k ) ) ;
goto finish ;
}
2010-11-08 07:02:45 +03:00
} else {
log_error ( " Unknown verb %s. " , argv [ 1 ] ) ;
goto finish ;
}
2010-11-12 02:39:17 +03:00
r = EXIT_SUCCESS ;
2010-11-08 07:02:45 +03:00
finish :
2010-11-12 02:39:17 +03:00
if ( cd )
crypt_free ( cd ) ;
2010-11-14 03:53:46 +03:00
free ( opt_cipher ) ;
free ( opt_hash ) ;
2010-11-08 07:02:45 +03:00
return r ;
}