# Functions commonly used by fripost-partman shell scripts # Copyright © 2013,2014 Guilhem Moulin # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . true ${DEBIAN_HAS_FRONTEND:=} ${DEBCONF_REDIR:=} . /usr/share/debconf/confmodule # TODO: # * encryption without SSH daemon (eg when /home is on a separate device) # * encryption of multiple disks with the same key (eg / on a software raid) # Log a message. log() { logger -t fripost -p user.info -- "$@" } # Log a fatal error and quit. fatal() { logger -t fripost -p user.err -- "$@" exit 1 } # Ensure stdout is opened with line buffering. If some day stdbuf(1) is # available in busybox, we should replace the LD_PRELOAD by 'stdbuf -oL -eL'. # XXX: workaround for #751394 stdbuf() { LD_PRELOAD=/lib/fripost-partman/stdbuf.so "$@" } # Wait for a device to be created wait_for_device() { local device="$1" timeout=40 until [ -b "$device" -o $timeout -le 0 ]; do log "Waiting for ${device}..." sleep 0.25 timeout=$(( $timeout - 1 )) done [ -b "$device" ] || fatal "Error: $device not found!" } # The non-blocking PRNG /dev/urandom doesn't block if it hasn't been # seeded enough, sadly (e.g., if the entropy pool is empty). Reading a # few bytes from the *blocking* PRNG should ensure that "the kernel RNG # has [...] reach full entropy at least once, which guarantees # cryptographic quality of the rest of the /dev/urandom stream." -- Tor # bug #10676. See also urandom(4). (XXX It's possible that the debian # installer does that already.) seed_urandom() { local seed=/var/run/random-seed fifo n records dir local poolfile=/proc/sys/kernel/random/poolsize bits=4096 if [ -f "$seed" ]; then log "/dev/urandom has already been seeded since start-up" return 0 fi [ -r $poolfile ] && bits=$(cat $poolfile) local bytes=$(( $bits / 8 )) log "Seeding /dev/urandom with $bytes bytes from /dev/random" db_subst fripost/seed_urandom_progress_title BYTES $bytes db_progress START 0 $bytes fripost/seed_urandom_progress_title db_progress INFO fripost/seed_urandom_progress_info fifo=$(mktemp -u) /bin/mknod "$fifo" p || exit 1 trap 'kill $pid' EXIT /bin/dd if=/dev/random bs=1 count=$bytes of=/dev/null 2> "$fifo" & pid=$! heartbeat $pid USR1 & local n records dir while read -u 7 n records dir; do [ "$records" = records -a "$dir" = out ] && db_progress SET ${n%+*} done 7< "$fifo" db_progress SET $bytes; sleep 0.25 rm -f "$fifo" trap - EXIT db_progress STOP db_unregister fripost/seed_urandom_progress_title db_unregister fripost/seed_urandom_progress_info touch "$seed" } ############################################################################## # Wipe the disk (unless d-i's 'fripost/wipe-device' is 'none') # # Usage: fripost_wipe fripost_wipe() { local device="$1" source bs=8192 blockdir size wipe fifo pid blockdir="/sys/block/${device#/dev/}" db_get fripost/wipe-device if [ "$RET" = none ]; then log "Not wiping $device" return 0 fi source="/dev/$RET" [ "$source" != /dev/urandom ] || seed_urandom log "Want to wipe $device using source $source" [ -b "$device" -a -d "$blockdir" -a -c "$source" ] || \ fatal "Invalid device $device or source $source" size=$(( `cat $blockdir/size` * `cat $blockdir/queue/physical_block_size` )) wipe="dd if=$source of=$device bs=$bs" db_subst fripost/wipe-device_progress_title DISK "$device" db_subst fripost/wipe-device_progress_title SIZE "$size" if [ "$source" = /dev/zero ]; then db_subst fripost/wipe-device_progress_title WHAT "zeroes" elif [ "$source" = /dev/random -o "$source" = /dev/urandom ]; then db_subst fripost/wipe-device_progress_title WHAT "bytes of random data" else db_subst fripost/wipe-device_progress_title WHAT "bytes" fi db_progress START 0 $(( $size / $bs )) fripost/wipe-device_progress_title db_subst fripost/wipe-device_progress_info COMMAND "$wipe" db_progress INFO fripost/wipe-device_progress_info fifo=$(mktemp -u) /bin/mknod "$fifo" p || exit 1 trap 'kill $pid' EXIT $wipe 2> "$fifo" & pid=$! heartbeat $pid USR1 & local n records dir while read -u 7 n records dir; do [ "$records" = records -a "$dir" = out ] && db_progress SET ${n%+*} done 7< "$fifo" db_progress SET $(( $size / $bs )); sleep 0.25 rm -f "$fifo" trap - EXIT db_progress STOP db_unregister fripost/wipe-device_progress_title db_unregister fripost/wipe-device_progress_info } heartbeat () { local pid=$1 sig=${2:-SIGHUP} sleep=${3:-1} until false; do sleep $sleep /bin/kill -$sig $pid 2>/dev/null || break done } ############################################################################## # Partitioning # # Usage: fripost_mkpart [flag | +flag | -flag | ...] # # Prints the bloc device for the new partition. can be given # using SI prefixes T,G,M, or k. If is a percentage, it is # relative to the free capacity. fripost_mkpart() { local device="$1" name="$2" size="$3" grain=$(( 256*32 )) offset bs local start stop part flag free # TODO: names are supported on GPT only. What is the second argument # of mkpart on msdos? bs=$(cat /sys/block/${device#/dev/}/queue/physical_block_size) if /sbin/parted -sm "$device" p | grep -q '^[1-9][0-9]*:'; then # The last offset, in sector; it is *always* supposed to be aligned. start=$( /sbin/parted -sm "$device" u s p \ | grep '^[1-9][0-9]*:' \ | sort -t: -nk2,2 \ | tail -1 \ | sed -r 's/^[1-9][0-9]*:[^:]*:([1-9][0-9]*)s:.*/\1/' ) start=$(( 1 + $start )) else # There are no partitions yet offset=$(cat /sys/block/${device#/dev/}/alignment_offset) if [ $offset -eq 0 ]; then # 64 is arbitrary here; $grain may be better since aligned, # but the 1st partition is typically /boot or something # without heavy IO, so choosing a smaller value means we get # more space without significant performance penalty start=64 else start=$(( $offset / $bs )) fi fi # Remove the SI prefix, or compute the size relative to a percentage case "$size" in *T) size=$(( ${size%T} * 1024**4 ));; *G) size=$(( ${size%G} * 1024**3 ));; *M) size=$(( ${size%M} * 1024**2 ));; *K) size=$(( ${size%K} * 1024 ));; *%) free=$(( `cat /sys/block/${device#/dev/}/size` - 1 - $start )) size=$(( $bs * $free * ${size%\%} / 100 )) ;; *) fatal "Unknown size $size" ;; esac # Round toward 0 to preserve the alignement stop=$(( $start + $size / $bs )) stop=$(( $stop - $stop % $grain - 1 )) log "Creating partition '$name' on $device, between sectors $start and $stop" [ $start -ge $stop ] && fatal "Cannot create partition: $start >= $stop" /sbin/parted -a minimal -s $device mkpart "$name" ${start}s ${stop}s # The partition index that is to be created part=$( /sbin/parted -sm $device u s p \ | grep -m1 "^[0-9]:${start}s:" \ | sed 's/:.*//' ) wait_for_device "${device}${part}" while [ $# -gt 3 ]; do flag="$4" log "Partition flag for ${device}${part} ($name): $flag" case "$flag" in +*) /sbin/parted -s $device set $part "${flag#+}" on ;; -*) /sbin/parted -s $device set $part "${flag#-}" off ;; *) /sbin/parted -s $device toggle $part "$flag" ;; esac shift done echo ${device}${part} } ############################################################################## # Encryption # # Usage: fripost_encrypt [] fripost_encrypt() { local device="$1" name="$2" keyfile sshHostKey import=/cdrom/include local fifo pid shift; shift keyfile=$(mktemp) || exit 1 log "Encryting device $device and sets up a mapping $name" seed_urandom db_input high fripost/encryption-password || true db_go db_get fripost/encryption-password if [ "$RET" ]; then log "The password can be found from debconf (hey, that's insecure!)" # Since we're using the builtin, the password shouldn't be # visible in ps(1). echo -n "$RET" > "$keyfile" db_set fripost/encryption-password '' else log "Starting a SSH daemon to slurp the volume key..." local type=rsa db_subst fripost/ssh-keypair-generation_progress_title TYPE $type db_progress START 0 1 fripost/ssh-keypair-generation_progress_title mkdir -pm0755 /etc/ssh/ sshHostKey=/etc/ssh/ssh_host_rsa_key /usr/bin/ssh-keygen -b 4096 -t $type -N '' -C $sshHostKey -f $sshHostKey db_progress SET 1; sleep 0.25 db_progress STOP db_unregister fripost/ssh-keypair-generation_progress_title cat > /etc/ssh/sshd_config <<- EOF Port 22 Protocol 2 HostKey $sshHostKey UsePrivilegeSeparation no PasswordAuthentication no ChallengeResponseAuthentication no HostbasedAuthentication no PubkeyAuthentication yes PermitRootLogin yes AllowUsers root StrictModes yes AllowAgentForwarding no AllowTcpForwarding no PermitOpen none PermitTTY no PermitUserRC no ForceCommand /bin/cat >$keyfile EOF # Populate the authorized keys. [ -d ~root/.ssh ] || mkdir -m0700 ~root/.ssh copy_authorized_keys $import/authorized_keys ~root/.ssh/authorized_keys # Start the SSH daemon touch /var/log/lastlog /usr/sbin/sshd # Tell the user we're ready db_subst fripost/encryption-slurpkey_text IPv4 "$(getIPv4)" db_subst fripost/encryption-slurpkey_text SSHFPR_SERVER \ "$(sshfprs ${sshHostKey}.pub)" db_subst fripost/encryption-slurpkey_text SSHFPR_AUTHORIZED \ "$(sshfprs ~root/.ssh/authorized_keys ' - ')" # Anything sent to the SSH is stored into $keyfile, which is our LUKS key. until test -s "$keyfile"; do db_settitle fripost/encryption-slurpkey_title db_input critical fripost/encryption-slurpkey_text db_go done # Kill the daemon, we're done log "Killing the SSH daemon..." kill `cat /var/run/sshd.pid` || true fi # Load dm-crypt /sbin/dmsetup targets | grep -q '^crypt\s' || /sbin/modprobe -v dm_crypt log "cryptsetup options: $@" db_progress START 0 100 fripost/cryptsetup-genkey_progress_title db_progress INFO fripost/cryptsetup-genkey_progress_info fifo=$(mktemp -u) /bin/mknod "$fifo" p || exit 1 trap 'kill $pid' EXIT stdbuf /sbin/cryptsetup -q "$@" --key-file="$keyfile" luksFormat "$device" \ | stdbuf sed -nr 's/^Generating key \(([0-9]+)% done\)\.$/\1/p' > "$fifo" & pid=$! local n while read -u 7 n; do db_progress SET $n done 7< "$fifo" db_progress SET 100; sleep 0.25 rm -f "$fifo" trap - EXIT db_progress STOP db_unregister fripost/cryptsetup-genkey_progress_title db_unregister fripost/cryptsetup-genkey_progress_info # Open the LUKS device and set up the mapping /sbin/cryptsetup --key-file="$keyfile" luksOpen "$device" "$name" log "Removing the key file" rm -f "$keyfile" # We are on a ramdisk, so it's good enough to unlink(2) # Add an entry to the crypttab fripost_crypttab_addentry "$name" "$device" none luks local m _ [ -d /var/lib/fripost ] || mkdir /var/lib/fripost # The modules required to fire up dropbear and start cryptsetup in the ramdisk. echo dm_crypt >> /var/lib/fripost/initrd-modules while read m _; do /sbin/modinfo -F filename "$m"; done < /proc/modules \ | sed -nr "s@^/lib/modules/`uname -r`/kernel/((arch/[^/]+/)?crypto|drivers/(ata|scsi|net))(/.*)?/([^/]+)\.ko\$@\5@p" \ >> /var/lib/fripost/initrd-modules /bin/apt-install busybox cryptsetup || true wait_for_device "/dev/mapper/$name" } fripost_crypttab_addentry() { local name="$1" uuid=$(/bin/block-attr --uuid "$2") keyfile="$3" options="$4" printf "%-15s %-41s %-15s %s\n" "$name" "UUID=$uuid" "$keyfile" "$options" >>/tmp/crypttab } # Like ssh-keygen -lf, but for a file such as authorized_keys, which # may contain multiple keys. Also, use the comment associated with the # key rather than the filename. # # Usage: sshfprs file [prefix] sshfprs() { local file="$1" prefix="${2:-}" type pk comment pkf=$(mktemp) sed -nr "s#^([^#]+\s)?(ssh-(dss|rsa|ed25519)|ecdsa-sha2-nistp(256|384|521))\s#\2 #p" "$file" | \ while read type pk comment; do # /usr/bin/ssh-keygen can't read from STDIN, and the '<<<' is # not POSIX, so we save each pubkey in a temporary file echo "$type $pk $comment" > "$pkf" echo "${prefix}$(/usr/bin/ssh-keygen -lf $pkf | sed "s#$pkf#$comment#")" done rm -f "$pkf" } # Copy an authorized_keys file, possibly adding some options. The input # format is sshd(8)'s; see the section called "AUTHORIZED_KEYS FILE # FORMAT" in the manpage. The character '#' cannot occur in the options. copy_authorized_keys() { local from="$1" to="$2" if [ $# -gt 2 ]; then sed -r "s#^([^#]+\s)?(ssh-(dss|rsa|ed25519)|ecdsa-sha2-nistp(256|384|521))\s#$3 \2 #" \ "$from" > "$to" else cp "$from" "$to" fi chmod 0600 "$to" } ############################################################################## # Add a progress bar to mkfs(8). Exit gracefully if $device is empty # # Usage: fripost_mkfs [] fripost_mkfs() { local type="$1" device="$2" fifo pid line stage step total= [ "$device" ] || return 0 shift; shift log "Formatting $device as $type; options $@" wait_for_device "$device" if ! [ "$type" = ext2 -o "$type" = ext3 -o "$type" = ext4 ]; then # XXX: The parsing has been tested with mkfs.ext{2,3,4} only. /sbin/mkfs."$type" "$@" "$device" return 0 fi db_subst fripost/mkfs_progress_title DEVICE "$device" db_subst fripost/mkfs_progress_title TYPE "$type" fifo=$(mktemp -u) /bin/mknod "$fifo" p || exit 1 trap 'kill $pid' EXIT /sbin/mkfs."$type" "$@" "$device" | tr '\b' '\n' > "$fifo" & pid=$! while read -u 7 line; do case "$line" in *:*/*) [ "$total" ] && db_progress STOP stage="${line%: *}" step="${line##*: }" log "mkfs(8) stage '$stage', $step steps" total="${step#*/}" db_progress START 0 $total fripost/mkfs_progress_title db_progress SET ${step%/*} db_subst fripost/mkfs_progress_info STAGE "$stage" db_progress INFO fripost/mkfs_progress_info ;; */$total) [ "$total" ] && db_progress SET ${line%/*} ;; done) [ "$total" ] && db_progress SET $total ;; esac done 7< "$fifo" rm -f "$fifo" trap - EXIT [ "$total" ] && db_progress STOP db_unregister fripost/mkfs_progress_title db_unregister fripost/mkfs_progress_info } ############################################################################## # Set up the fstab. Exit gracefully if $device is empty # # Usage: fripost_fstab [] # # Note: Don't forget to run 'fripost_mount_partitions' after the last # call to 'fripost_fstab'. fripost_fstab() { local device="$1" mp="$2" type="$3" options="${4:-defaults}" pass [ "$device" ] || return 0 if [ "$mp" = / ]; then log "Got root device, $device" pass=1 elif [ "$mp" = /boot/efi ]; then log "Got EFI device, $device" pass=1 elif [ "$type" = swap ]; then log "Got swap device, $device" pass=0 else log "Got other device, $device -> $mp" pass=2 fi # Add an entry to the fstab fripost_fstab_addentry $device $mp $type $options 0 $pass } fripost_fstab_addentry() { local device=$1 mp=$2 type=$3 options=$4 dump=$5 pass=$6 local fstab=/tmp/fstab local entry="%-41s %-15s %-7s %-31s %-7s %s\n" log "Adding fstab entry $device -> $mp" if ! [ -s $fstab -a -f $fstab ]; then # No fstab has bee created yet. We copy the header from # /lib/partman/finish.d/39create_fstab_header cat > $fstab <<-EOF # /etc/fstab: static file system information. # # Use 'blkid' to print the universally unique identifier for a device; # this may be used with UUID= as a more robust way to name devices that # works even if disks are added and removed. See fstab(5). # EOF printf "$entry" '# ' '' '' \ '' '' '' \ >> $fstab fi # Always use UUIDs local uuid=$(/bin/block-attr --uuid "$device") if [ ! "$uuid" ]; then log "Warning: Block device $device has no UUID!" printf "$entry" "$device" $mp $type $options $dump $pass >> $fstab else log "Block device $device has UUID $uuid" printf "$entry" "UUID=$uuid" $mp $type $options $dump $pass >> $fstab # Fix udev's UUID mapping if ! [ -h /dev/disk/by-uuid/"$uuid" ]; then device=$(/bin/mapdevfs "$device") log "Adding a symlink UUID $uuid -> $device" ln -fs ../../"${device#/dev/}" /dev/disk/by-uuid/"$uuid" fi fi } ############################################################################## # Mount all entries in the fstab # # Usage: fripost_mount_partitions fripost_mount_partitions() { local cdrom uuid mp type options _ # sort(1), to ensure that roots are mounted earlier, regardless of # where they appear in the file (eg, /boot can appear before /). sed -e 's/#.*//' -e '/^\s*$/d' -e 's/\s\s*/ /g' /tmp/fstab \ | grep -v '\s0$' \ | sort -t' ' -k2,2 \ | while read uuid mp type options _; do local dev=$(/sbin/blkid -U "${uuid#UUID=}") log "Found fstab entry: $dev -> $mp" wait_for_device "$dev" if [ "$type" = swap ]; then /sbin/swapon "$dev" elif [ / = $(expr substr "$mp" 1 1) ]; then mkdir -p "/target$mp" /bin/mount -t "$type" -o "$options" "$dev" "/target$mp" fi done # Post-installation scripts may use the install CD as a local mirror # for APT packages. cdrom=$( sed -rn '\#^(\S+) /cdrom\s.*# {s//\1/p; q}' /proc/mounts ) [ "$cdrom" ] || fatal "Error: Is /cdrom a mountpoint?" fripost_fstab_addentry "$cdrom" /media/cdrom0 udf,iso9660 user,noauto 0 0 mkdir -p /target/media/cdrom0 ln -s cdrom0 /target/media/cdrom # Move the fstab and crypttab where they belong [ -d /target/etc ] || mkdir /target/etc mv /tmp/fstab /target/etc/fstab [ \! -f /tmp/crypttab ] || mv /tmp/crypttab /target/etc/crypttab } getIPv4() { local ip=/sbin/ip [ -x $ip ] || ip=/bin/ip [ -x $ip ] || fatal "Error: couldn't find ip(8)" local if=$( $ip -4 route show to default scope global \ | sed -nr '/^default via \S+ dev (\S+).*/ {s//\1/p;q}' ) $ip -4 address show dev "$if" scope global \ | sed -nr '/^\s+inet\s([[:xdigit:].:]{3,39}).*/ {s//\1/p;q}' } ############################################################################## # List all block devices that are not mounted on /cdrom # fripost_list_devices() { local cdrom=$( sed -rn '\#^(\S+) /cdrom\s.*# {s//\1/p; q}' /proc/mounts ) [ "$cdrom" ] || fatal "Error: Is /cdrom a mountpoint?" find /dev -type b | grep -v '[0-9]$' | grep -vFx "$cdrom" } ############################################################################## # Remove a partition from a device # # Usage: fripost_rmpart device partition fripost_rmpart () { local device="$1" part="$2" n=$( /sbin/parted -sm "$device" p \ | sed -nr "/^[0-9].*:$part:[^:]*;$/ s/:.*//p" ) [ "$n" ] || return 0 log "Removing partition $n ($part) from $device" /sbin/parted -sm "$device" rm "$n" } ############################################################################## # Create a RAID aray; add a progress bar to mdadm(8) --create # # Usage: fripost_mdadm_create device mdadm_options # (equivalent to mdadm --create device mdadm_options) fripost_mdadm_create() { local md="$1"; shift local device level devices size info n= /sbin/modprobe -v md_mod [ -e /proc/mdstat ] || fail "/proc/mdstat missing" log "Creating RAID device $md with $@" /sbin/mdadm --create "$md" "$@" wait_for_device "$md" #device=$(/usr/bin/readlink -f "$md") device="${md#/dev/}" level=$(sed -nr "/^$device\s*:/ s/.*:\s*active (\S+) .*/\1/p" /proc/mdstat) devices=$(sed -nr "/^$device\s*:/ s/.*:\s*active \S+ //p" /proc/mdstat) size=$(sed -nr "/^$device\s*:/ {n; s/^\s*([0-9]+)\s+blocks\s.*/\1/p}" /proc/mdstat) info=$(sed -nr "/^$device\s*:/ {n; s/^.*\s(super\s.*)/\1/p}" /proc/mdstat) if ! [ "$level" -a "$devices" -a "$size" ] && [ $size -gt 0 ]; then log < /proc/mdstat echo "Couldn't find entry $device ($md) in /proc/mdstat" >&2 exit 1 fi db_subst fripost/mdadm_create_progress_title MD "$md" db_subst fripost/mdadm_create_progress_title LEVEL $level db_subst fripost/mdadm_create_progress_title DEVICES "$devices" db_progress START 0 $size fripost/mdadm_create_progress_title db_subst fripost/mdadm_create_progress_info CURRENT 0 db_subst fripost/mdadm_create_progress_info SIZE $size db_subst fripost/mdadm_create_progress_info INFO "$info" db_progress INFO fripost/mdadm_create_progress_info while sed -nr "/^$device\s*:/ { p; :l; n; /^\s+\S/ {p; b l;} q }" /proc/mdstat \ | grep -q '\bresync\b'; do n=$(sed -nr "/^$device\s*:/ { :l; n s#^\s.*\sresync\s*=\s*[0-9.]+%\s*\(([0-9]+)/$size\).*#\1#p /^\s+\S/ b l; q }" /proc/mdstat) if [ "$n" ]; then db_subst fripost/mdadm_create_progress_info CURRENT $n db_progress INFO fripost/mdadm_create_progress_info db_progress SET $n fi sleep 1 done db_progress SET $size; sleep 0.25 db_progress STOP db_unregister fripost/mdadm_create_progress_title db_unregister fripost/mdadm_create_progress_info }