#!/bin/bash

# mkinitcpio install hook for the Mandos client
# (https://www.recompile.se/mandos). It stages the mandos client
# plugin-runner, plugins, plugin helpers, keys (from /etc/keys/mandos),
# cryptsetup, the GnuPG binaries gpgme needs, and the network and IPv6
# modules into the initramfs image, and generates an in-image
# /etc/mandos/plugin-runner.conf.

# It is meant to be used together with the matching runtime hook (also
# called "mandos"), which replaces the "encrypt" hook.

build() {
    local libdir=/usr/lib/mandos
    local keydir=/etc/keys/mandos
    local base
    local mod
    local conffile=/etc/mandos/plugin-runner.conf

    # module requirements copied from the stock "encrypt" install hook
    map add_module 'dm-crypt' 'dm-integrity' 'hid-generic?'
    if [[ -n "$CRYPTO_MODULES" ]]; then
        for mod in $CRYPTO_MODULES; do
            add_module "$mod"
        done
    else
        add_all_modules '/crypto/'
    fi

    # The Mandos network client uses the network
    add_all_modules '/drivers/net/'
    # The Mandos network client uses IPv6 for zeroconf
    add_module 'ipv6'

    add_binary 'cryptsetup'
    add_binary '/usr/lib/libgcc_s.so.1'
    add_binary '/usr/lib/ossl-modules/legacy.so'
    map add_udev_rule \
        '10-dm.rules' \
        '13-dm-disk.rules' \
        '95-dm-notify.rules'

    if [ ! -x "${libdir}/plugin-runner" ]; then
        error "Mandos client not found (${libdir}/plugin-runner missing)"
        return 1
    fi

    add_dir '/etc/mandos'
    add_dir '/usr/lib/mandos/plugins.d'
    add_dir '/usr/lib/mandos/plugin-helpers'

    # plugin-runner itself
    add_binary "${libdir}/plugin-runner"

    # packaged plugins, overridden by user-supplied ones from
    # /etc/mandos/plugins.d
    for base in "${libdir}"/plugins.d/*; do
        [ -e "$base" ] || continue
        [ -e "/etc/mandos/plugins.d/${base##*/}" ] && continue
        case "${base##*/}" in
            *~|*.bak|*.old|.*|\#*\#) ;;
            *) add_binary "$base" ;;
        esac
    done
    # user-supplied plugins
    for base in /etc/mandos/plugins.d/*; do
        [ -e "$base" ] || continue
        case "${base##*/}" in
            *~|*.bak|*.old|.*|\#*\#) ;;
            *) add_binary "$base" ;;
        esac
    done

    # packaged plugin helpers, overridden by user-supplied ones from
    # /etc/mandos/plugin-helpers
    for base in "${libdir}"/plugin-helpers/*; do
        [ -e "$base" ] || continue
        [ -e "/etc/mandos/plugin-helpers/${base##*/}" ] && continue
        add_binary "$base"
    done
    # user-supplied plugin helpers
    for base in /etc/mandos/plugin-helpers/*; do
        [ -e "$base" ] || continue
        add_binary "$base"
    done

    for base in "$keydir"/pubkey.txt "$keydir"/seckey.txt \
        "$keydir"/tls-pubkey.pem "$keydir"/tls-privkey.pem \
        "$keydir"/dhparams.pem; do
        [ -f "$base" ] || continue
        add_file "$base" "$base"
    done

    # GPGME needs GnuPG. Find the actual gpg binary and its agent via
    # gpgconf and put them into the image as well.
    if [ -x /usr/bin/gpgconf ]; then
        add_binary /usr/bin/gpgconf
        _gpg=$(gpgconf 2>/dev/null | sed -n 's/^gpg:[^:]*://p')
        _gpgagent=$(gpgconf 2>/dev/null | sed -n 's/^gpg-agent:[^:]*://p')
        [ -n "$_gpg" ] && add_binary "$_gpg"
        [ -n "$_gpgagent" ] && add_binary "$_gpgagent"
        unset _gpg _gpgagent
    else
        warn "No /usr/bin/gpgconf found; the mandos-client plugin might not be able to decrypt the key."
    fi

    # generate the in-image plugin-runner configuration
    {
        echo '# This file was generated by the mandos mkinitcpio hook.'
        echo --userid=0
        echo --groupid=0
        for base in plymouth splashy usplash askpass-fifo; do
            [ -e "/etc/mandos/plugins.d/$base" ] || echo "--disable=$base"
        done
        printf '%s' --options-for=mandos-client:
        local first=yes
        for base in pubkey.txt seckey.txt tls-pubkey.pem tls-privkey.pem; do
            [ -f "${keydir}/$base" ] || continue
            [ "${first}" = "yes" ] || printf ','
            printf -- '--%s=%s/%s' \
                "${base%%.*}" "${keydir}" "$base"
            first=no
        done
        if [ -f "${keydir}/dhparams.pem" ]; then
            [ "${first}" = "yes" ] || printf ','
            printf -- '--dh-params=%s/dhparams.pem' "${keydir}"
        fi
        echo ''
    } > "${BUILDROOT}${conffile}"

    # make everything readable for plugin-runner (which we run as root,
    # but the image is nevertheless made consistent here)
    chmod a+rX \
        "${BUILDROOT}/etc/mandos" \
        "${BUILDROOT}/usr/lib/mandos" \
        "${BUILDROOT}/usr/lib/mandos/plugins.d" \
        "${BUILDROOT}/usr/lib/mandos/plugin-helpers"

    if [ ! -f "${keydir}/pubkey.txt" ]; then
        warn "No Mandos client key found in ${keydir}."
        warn "Run 'mandos-keygen' (as root) to create it, then re-run 'mkinitcpio -P'."
        warn "Without a key, only the interactive console passphrase prompt will work."
    fi

    # stage (as /hooks/mandos in the image) and activate the runtime hook
    add_runscript
}

help() {
    cat <<HELPEOF
This hook adds the Mandos client to the initramfs, so that the LUKS
passphrase is fetched from a Mandos server over the network at boot.
Use the "mandos" runtime hook (from this package) instead of "encrypt"
in the HOOKS array of /etc/mkinitcpio.conf.

Before it is useful, you need to
  * run, as root, "mandos-keygen" to create the client keys in
    /etc/keys/mandos (then re-run "mkinitcpio -P"),
  * configure the Mandos server to hand out the disk password to this
    client (see the manual), and
  * have an Avahi-enabled network connecting the client's initramfs
    and the server.
HELPEOF
}

# vim: set ft=sh ts=4 sw=4 et:
