#!/bin/sh
set -e

# Mise à jour des clefs gpg publiques détenues par Schleuder.
# Lors de son éxécution (par cron) ce script ne rafraichit qu'une seule clef,
# choisie aléatoirement dans l'ensemble des trousseaux: en effet, puisque
# Schleuder a plusieurs trousseaux (un par liste) et qu'une même clef peut se
# trouver dans plusieurs trousseaux, il semble plus profitable de considérer
# les clefs dans leur ensemble plutôt que par trousseau. Ainsi, si une clef
# est mise à jour dans un trousseau, elle sera tout simplement exportée vers
# les autres trousseaux où elle se trouve déjà.
#
# Usages:
#
# 1. Dans cron:
# 0 */2 * * * schleuder schleuder-refresh-keys
#
# 2. En ligne de commande:
# sudo -u schleuder -H schleuder-refresh-keys KEY_ID
#
# La commande accepte un argument (un identifiant de clef au sens large:
# 0x0123456789ABCDEF ou adresse email par exemple; en fait toute chaîne de
# caractères non ambiguë reconnue par gpg comme identifiant de clef) pour
# une utilisation manuelle.

# Le répertoire de base qui contient les sous-répertoires utilisés comme
# "homedir" par gpg.
BASE_HOMEDIR="${HOME}/$(hostname -d)"

# Les options de la commande gpg qui seront utilisées. Le certificat de
# sks-keyservers.net est autosigné; cependant il est possible d'en vérifier
# l'authenticité dans la mesure où il est aussi signé par gpg, avec la clef
# 0x0B7F8B60E3EDFAE3; tout dépend maintenant de la confiance qu'on accorde
# à cette clef.
# wget --no-check-certificate https://sks-keyservers.net/sks-keyservers.netCA.pem
# wget --no-check-certificate https://sks-keyservers.net/sks-keyservers.netCA.pem.asc
# gpg --keyserver hkp://keys.gnupg.net --recv-keys 0x0B7F8B60E3EDFAE3
# gpg --verify sks-keyservers.netCA.pem.asc sks-keyservers.netCA.pem
KEYSERVER="--keyserver hkps://hkps.pool.sks-keyservers.net"
KEYSERVER_OPTIONS="--keyserver-options ca-cert-file=/etc/ssl/certs/sks-keyservers.netCA.pem"
KEYSERVER_OPTIONS="${KEYSERVER_OPTIONS} --keyserver-options no-honor-keyserver-url"
KEYID_FORMAT="--keyid-format 0xlong"

# Les options d'aléas de la commande sort. Comme le serveur (bulbe) n'a pas
# vocation à être redémarré souvent, on ne peut pas compter sur /dev/urandom
# comme source d'entropie dans la durée (on risquerait de rafraichir souvent
# les mêmes clefs, et certaines pourraient être oubliées pendant plusieurs
# semaines)
RANDOM_SORT="--random-sort --random-source=/dev/random"

# Extraction des identifiants numériques de clefs; options:
# <--homedir DIR> <--list-public-keys|--list-secret-keys>
key_id() {
    gpg ${KEYID_FORMAT} "$@" |
    awk 'BEGIN {FS=" +|/"} $1 ~ /^(pub|sec)$/ {print $3}'
}

# Ce script peut accepter un argument (pour la mise à jour d'une clef
# spécifique)
if [ -n "${1}" ]; then
    for d in ${BASE_HOMEDIR}/*; do
        KEY_ID="$(key_id --homedir ${d} --list-public-keys ${1})" || true
        [ -n "${KEY_ID}" ] && break
    done
    # Mais si l'argument n'est pas valide et qu'on n'a pas trouvé d'identifiant
    # de clef correspondant, on sort:
    [ -n "${KEY_ID}" ] || exit 3
else
    # Sinon, on choisit une clef aléatoirement dans l'ensemble des trousseaux:
    KEY_ID="$(for d in ${BASE_HOMEDIR}/*; do key_id --homedir ${d} --list-public-keys; done | sort --uniq ${RANDOM_SORT} | head -n1)"
fi

# ...on initialise 2 variables:
HOMEDIR=    # répertoire du trousseau en cours de traitement (--homedir DIR)
FROMDIR=    # répertoire du trousseau dans lequel la clef a été mise à jour

# On va rediriger toutes les sorties de gpg (indésirables en cas de succès,
# mais qu'on veut garder en cas d'échec) vers un fichier:
GPG_OUTPUT="/tmp/gpg_output.$(date +%s)"
trap "rm -f ${GPG_OUTPUT}" EXIT

# Pour chaque trousseau...
for d in ${BASE_HOMEDIR}/*; do
    HOMEDIR="--homedir ${d}"

    # ...si la clef en question en fait partie...
    if key_id ${HOMEDIR} --list-public-keys | grep -q "^${KEY_ID}$"; then
        # ...et si elle n'a pas encore été mise à jour depuis le serveur...
        if [ -z "${FROMDIR}" ]; then
            # ...alors on tente de la mettre à jour:
            if gpg ${KEYID_FORMAT} ${HOMEDIR} ${KEYSERVER} ${KEYSERVER_OPTIONS} \
                --verbose --refresh-keys ${KEY_ID} >${GPG_OUTPUT} 2>&1 ; then
                # et on actualise la variable pour signifier que la clef
                # a bien été mise à jour, dans ce trousseau-là:
                FROMDIR="${HOMEDIR}"
                # et on passe au trousseau suivant, pour la même clef:
                continue
            else
                # sinon ben tant pis, ça attendra la prochaine fois:
                cat ${GPG_OUTPUT} >&2
                echo "==================================================" >&2
                echo "Pour information:" >&2
                gpg ${KEYID_FORMAT} ${HOMEDIR} --list-public-keys ${KEY_ID} >&2
                break
            fi
        else
            # La clef existe dans ce trousseau, et a déjà été mise à jour dans
            # un autre...
            # ...donc on a juste à importer la clef depuis le trousseau dans
            # lequel elle a été mise à jour:
            if ! gpg ${FROMDIR} --export ${KEY_ID} 2>/dev/null |
                gpg ${HOMEDIR} --verbose --import >${GPG_OUTPUT} 2>&1; then
                cat ${GPG_OUTPUT} >&2
                echo >&2
            fi
        fi
    fi
done

# Maintenant qu'on a fait le tour des trousseaux pour une même clef...
if [ -n "${FROMDIR}" ]; then
    # ...on s'arrête là si la mise à jour a bien eu lieu...
    exit 0
else
    # On réserve le code de sortie 1 aux erreurs internes du programme
    # ou celles qui ne sont pas gérées correctement (set -e); les erreurs
    # de gpg ont le code de sortie 2.
    exit 3
fi

# vim: et sts=4 sw=4 ts=4
