1ce3adf43SDag-Erling Smørgrav#!/bin/sh 2ce3adf43SDag-Erling Smørgrav 3*3d9fd9fcSEd Maste# Copyright (c) 1999-2024 Philip Hands <phil@hands.com> 4535af610SEd Maste# 2021 Carlos Rodríguez Gili <carlos.rodriguez-gili@upc.edu> 519261079SEd Maste# 2020 Matthias Blümel <blaimi@blaimi.de> 619261079SEd Maste# 2017 Sebastien Boyron <seb@boyron.eu> 7ce3adf43SDag-Erling Smørgrav# 2013 Martin Kletzander <mkletzan@redhat.com> 8ce3adf43SDag-Erling Smørgrav# 2010 Adeodato =?iso-8859-1?Q?Sim=F3?= <asp16@alu.ua.es> 9ce3adf43SDag-Erling Smørgrav# 2010 Eric Moret <eric.moret@gmail.com> 10ce3adf43SDag-Erling Smørgrav# 2009 Xr <xr@i-jeuxvideo.com> 11ce3adf43SDag-Erling Smørgrav# 2007 Justin Pryzby <justinpryzby@users.sourceforge.net> 12ce3adf43SDag-Erling Smørgrav# 2004 Reini Urban <rurban@x-ray.at> 13ce3adf43SDag-Erling Smørgrav# 2003 Colin Watson <cjwatson@debian.org> 14ce3adf43SDag-Erling Smørgrav# All rights reserved. 15ce3adf43SDag-Erling Smørgrav# 16ce3adf43SDag-Erling Smørgrav# Redistribution and use in source and binary forms, with or without 17ce3adf43SDag-Erling Smørgrav# modification, are permitted provided that the following conditions 18ce3adf43SDag-Erling Smørgrav# are met: 19ce3adf43SDag-Erling Smørgrav# 1. Redistributions of source code must retain the above copyright 20ce3adf43SDag-Erling Smørgrav# notice, this list of conditions and the following disclaimer. 21ce3adf43SDag-Erling Smørgrav# 2. Redistributions in binary form must reproduce the above copyright 22ce3adf43SDag-Erling Smørgrav# notice, this list of conditions and the following disclaimer in the 23ce3adf43SDag-Erling Smørgrav# documentation and/or other materials provided with the distribution. 24ce3adf43SDag-Erling Smørgrav# 25ce3adf43SDag-Erling Smørgrav# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 26ce3adf43SDag-Erling Smørgrav# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 27ce3adf43SDag-Erling Smørgrav# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 28ce3adf43SDag-Erling Smørgrav# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 29ce3adf43SDag-Erling Smørgrav# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 30ce3adf43SDag-Erling Smørgrav# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 31ce3adf43SDag-Erling Smørgrav# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 32ce3adf43SDag-Erling Smørgrav# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 33ce3adf43SDag-Erling Smørgrav# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 34ce3adf43SDag-Erling Smørgrav# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 35ce3adf43SDag-Erling Smørgrav 36ce3adf43SDag-Erling Smørgrav# Shell script to install your public key(s) on a remote machine 37ce3adf43SDag-Erling Smørgrav# See the ssh-copy-id(1) man page for details 38ce3adf43SDag-Erling Smørgrav 3919261079SEd Maste# shellcheck shell=dash 4019261079SEd Maste 41ce3adf43SDag-Erling Smørgrav# check that we have something mildly sane as our shell, or try to find something better 42ce3adf43SDag-Erling Smørgravif false ^ printf "%s: WARNING: ancient shell, hunting for a more modern one... " "$0" 43ce3adf43SDag-Erling Smørgravthen 44ce3adf43SDag-Erling Smørgrav SANE_SH=${SANE_SH:-/usr/bin/ksh} 45ce3adf43SDag-Erling Smørgrav if printf 'true ^ false\n' | "$SANE_SH" 46ce3adf43SDag-Erling Smørgrav then 4719261079SEd Maste printf "'%s' seems viable.\\n" "$SANE_SH" 48ce3adf43SDag-Erling Smørgrav exec "$SANE_SH" "$0" "$@" 49ce3adf43SDag-Erling Smørgrav else 50ce3adf43SDag-Erling Smørgrav cat <<-EOF 51ce3adf43SDag-Erling Smørgrav oh dear. 52ce3adf43SDag-Erling Smørgrav 53ce3adf43SDag-Erling Smørgrav If you have a more recent shell available, that supports \$(...) etc. 54ce3adf43SDag-Erling Smørgrav please try setting the environment variable SANE_SH to the path of that 55ce3adf43SDag-Erling Smørgrav shell, and then retry running this script. If that works, please report 56ce3adf43SDag-Erling Smørgrav a bug describing your setup, and the shell you used to make it work. 57ce3adf43SDag-Erling Smørgrav 58ce3adf43SDag-Erling Smørgrav EOF 59*3d9fd9fcSEd Maste printf '%s: ERROR: Less dimwitted shell required.\n' "$0" >&2 60ce3adf43SDag-Erling Smørgrav exit 1 61ce3adf43SDag-Erling Smørgrav fi 62ce3adf43SDag-Erling Smørgravfi 63ce3adf43SDag-Erling Smørgrav 6419261079SEd Maste# shellcheck disable=SC2010 65*3d9fd9fcSEd MasteDEFAULT_PUB_ID_FILE=$(ls -dt "${HOME}"/.ssh/id*.pub 2>/dev/null | grep -v -- '-cert.pub$' | head -n 1) 6619261079SEd MasteSSH="ssh -a -x" 67535af610SEd MasteTARGET_PATH=".ssh/authorized_keys" 6819261079SEd Masteumask 0177 69ce3adf43SDag-Erling Smørgrav 70ce3adf43SDag-Erling Smørgravusage () { 71*3d9fd9fcSEd Maste printf 'Usage: %s [-h|-?|-f|-n|-s|-x] [-i [identity_file]] [-t target_path] [-F ssh_config] [[-o ssh_option] ...] [-p port] [user@]hostname\n' "$0" >&2 72acc1a9efSDag-Erling Smørgrav printf '\t-f: force mode -- copy keys without trying to check if they are already installed\n' >&2 73acc1a9efSDag-Erling Smørgrav printf '\t-n: dry run -- no keys are actually copied\n' >&2 7419261079SEd Maste printf '\t-s: use sftp -- use sftp instead of executing remote-commands. Can be useful if the remote only allows sftp\n' >&2 75535af610SEd Maste printf '\t-x: debug -- enables -x in this shell, for debugging\n' >&2 76acc1a9efSDag-Erling Smørgrav printf '\t-h|-?: print this help\n' >&2 77ce3adf43SDag-Erling Smørgrav exit 1 78ce3adf43SDag-Erling Smørgrav} 79ce3adf43SDag-Erling Smørgrav 80ce3adf43SDag-Erling Smørgrav# escape any single quotes in an argument 81ce3adf43SDag-Erling Smørgravquote() { 8219261079SEd Maste printf '%s\n' "$1" | sed -e "s/'/'\\\\''/g" 83ce3adf43SDag-Erling Smørgrav} 84ce3adf43SDag-Erling Smørgrav 85ce3adf43SDag-Erling Smørgravuse_id_file() { 8619261079SEd Maste L_ID_FILE="$1" 87ce3adf43SDag-Erling Smørgrav 884f52dfbbSDag-Erling Smørgrav if [ -z "$L_ID_FILE" ] ; then 89*3d9fd9fcSEd Maste printf '%s: ERROR: no ID file found\n' "$0" >&2 904f52dfbbSDag-Erling Smørgrav exit 1 914f52dfbbSDag-Erling Smørgrav fi 924f52dfbbSDag-Erling Smørgrav 9319261079SEd Maste if expr "$L_ID_FILE" : '.*\.pub$' >/dev/null ; then 94ce3adf43SDag-Erling Smørgrav PUB_ID_FILE="$L_ID_FILE" 95ce3adf43SDag-Erling Smørgrav else 96ce3adf43SDag-Erling Smørgrav PUB_ID_FILE="$L_ID_FILE.pub" 97ce3adf43SDag-Erling Smørgrav fi 98ce3adf43SDag-Erling Smørgrav 99acc1a9efSDag-Erling Smørgrav [ "$FORCED" ] || PRIV_ID_FILE=$(dirname "$PUB_ID_FILE")/$(basename "$PUB_ID_FILE" .pub) 100ce3adf43SDag-Erling Smørgrav 101ce3adf43SDag-Erling Smørgrav # check that the files are readable 102acc1a9efSDag-Erling Smørgrav for f in "$PUB_ID_FILE" ${PRIV_ID_FILE:+"$PRIV_ID_FILE"} ; do 103acc1a9efSDag-Erling Smørgrav ErrMSG=$( { : < "$f" ; } 2>&1 ) || { 10419261079SEd Maste L_PRIVMSG="" 105acc1a9efSDag-Erling Smørgrav [ "$f" = "$PRIV_ID_FILE" ] && L_PRIVMSG=" (to install the contents of '$PUB_ID_FILE' anyway, look at the -f option)" 106*3d9fd9fcSEd Maste printf "\\n%s: ERROR: failed to open ID file '%s': %s\\n" "$0" "$f" "$(printf '%s\n%s\n' "$ErrMSG" "$L_PRIVMSG" | sed -e 's/.*: *//')" >&2 107ce3adf43SDag-Erling Smørgrav exit 1 108ce3adf43SDag-Erling Smørgrav } 109ce3adf43SDag-Erling Smørgrav done 110ce3adf43SDag-Erling Smørgrav GET_ID="cat \"$PUB_ID_FILE\"" 111ce3adf43SDag-Erling Smørgrav} 112ce3adf43SDag-Erling Smørgrav 113ce3adf43SDag-Erling Smørgravif [ -n "$SSH_AUTH_SOCK" ] && ssh-add -L >/dev/null 2>&1 ; then 114ce3adf43SDag-Erling Smørgrav GET_ID="ssh-add -L" 115ce3adf43SDag-Erling Smørgravfi 116ce3adf43SDag-Erling Smørgrav 117*3d9fd9fcSEd MasteOPTS="io:p:F:t:fnsxh?" 118*3d9fd9fcSEd Maste 119*3d9fd9fcSEd Mastewhile getopts "$OPTS" OPT 120ce3adf43SDag-Erling Smørgravdo 121ce3adf43SDag-Erling Smørgrav case "$OPT" in 12219261079SEd Maste i) 12319261079SEd Maste [ "${SEEN_OPT_I}" ] && { 124*3d9fd9fcSEd Maste printf '\n%s: ERROR: -i option must not be specified more than once\n\n' "$0" >&2 12519261079SEd Maste usage 12619261079SEd Maste } 127ce3adf43SDag-Erling Smørgrav SEEN_OPT_I="yes" 128*3d9fd9fcSEd Maste 129*3d9fd9fcSEd Maste # Check for -i's optional parameter 130*3d9fd9fcSEd Maste eval "nextarg=\${$OPTIND}" 131*3d9fd9fcSEd Maste # shellcheck disable=SC2154 132*3d9fd9fcSEd Maste if [ $OPTIND = $# ]; then 133*3d9fd9fcSEd Maste if [ -r "$nextarg" ] && grep -iq ssh "$nextarg"; then 134*3d9fd9fcSEd Maste printf '\n%s: ERROR: Missing hostname. Use "-i -- %s" if you really mean to use this as the hostname\n\n' "$0" "$nextarg" >&2 135*3d9fd9fcSEd Maste usage 136*3d9fd9fcSEd Maste fi 137*3d9fd9fcSEd Maste elif ! expr -- "$nextarg" : "-[$(echo "$OPTS" | tr -d :)-]" >/dev/null ; then 138*3d9fd9fcSEd Maste # when not at the last arg, and not followed by an option, -i has an argument 139*3d9fd9fcSEd Maste OPTARG="$nextarg" 140*3d9fd9fcSEd Maste OPTIND=$((OPTIND + 1)) 141*3d9fd9fcSEd Maste fi 142ce3adf43SDag-Erling Smørgrav use_id_file "${OPTARG:-$DEFAULT_PUB_ID_FILE}" 143ce3adf43SDag-Erling Smørgrav ;; 144535af610SEd Maste o|F) 145535af610SEd Maste OPTS_oF="${OPTS_oF:+$OPTS_oF }-$OPT '$(quote "${OPTARG}")'" 146ce3adf43SDag-Erling Smørgrav ;; 14719261079SEd Maste f) 148acc1a9efSDag-Erling Smørgrav FORCED=1 149acc1a9efSDag-Erling Smørgrav ;; 15019261079SEd Maste n) 151ce3adf43SDag-Erling Smørgrav DRY_RUN=1 152ce3adf43SDag-Erling Smørgrav ;; 153535af610SEd Maste p) 154535af610SEd Maste SSH_PORT=${OPTARG} 155535af610SEd Maste ;; 15619261079SEd Maste s) 15719261079SEd Maste SFTP=sftp 15819261079SEd Maste ;; 159535af610SEd Maste t) 160535af610SEd Maste TARGET_PATH="${OPTARG}" 161535af610SEd Maste ;; 162535af610SEd Maste x) 163535af610SEd Maste SET_X="set -x;" 164535af610SEd Maste set -x 165535af610SEd Maste ;; 16619261079SEd Maste h|\?) 167ce3adf43SDag-Erling Smørgrav usage 168ce3adf43SDag-Erling Smørgrav ;; 169ce3adf43SDag-Erling Smørgrav esac 170ce3adf43SDag-Erling Smørgravdone 17119261079SEd Maste#shift all args to keep only USER_HOST 17219261079SEd Masteshift $((OPTIND-1)) 173ce3adf43SDag-Erling Smørgrav 174ce3adf43SDag-Erling Smørgravif [ $# = 0 ] ; then 175ce3adf43SDag-Erling Smørgrav usage 176ce3adf43SDag-Erling Smørgravfi 177ce3adf43SDag-Erling Smørgravif [ $# != 1 ] ; then 178ce3adf43SDag-Erling Smørgrav printf '%s: ERROR: Too many arguments. Expecting a target hostname, got: %s\n\n' "$0" "$SAVEARGS" >&2 179ce3adf43SDag-Erling Smørgrav usage 180ce3adf43SDag-Erling Smørgravfi 181ce3adf43SDag-Erling Smørgrav 18219261079SEd MasteUSER_HOST="$*" 183ce3adf43SDag-Erling Smørgrav# tack the hostname onto SSH_OPTS 184535af610SEd MasteOPTS_USER_HOST="${OPTS_oF:+$OPTS_oF }'$(quote "$USER_HOST")'" 185535af610SEd MasteSSH_OPTS="${SSH_PORT:+-p $SSH_PORT }$OPTS_USER_HOST" 186ce3adf43SDag-Erling Smørgrav# and populate "$@" for later use (only way to get proper quoting of options) 187ce3adf43SDag-Erling Smørgraveval set -- "$SSH_OPTS" 188ce3adf43SDag-Erling Smørgrav 18919261079SEd Maste# shellcheck disable=SC2086 190ce3adf43SDag-Erling Smørgravif [ -z "$(eval $GET_ID)" ] && [ -r "${PUB_ID_FILE:=$DEFAULT_PUB_ID_FILE}" ] ; then 191ce3adf43SDag-Erling Smørgrav use_id_file "$PUB_ID_FILE" 192ce3adf43SDag-Erling Smørgravfi 193ce3adf43SDag-Erling Smørgrav 194*3d9fd9fcSEd Masteprintf '%s: INFO: Source of key(s) to be installed: %s\n' "$0" "${GET_ID#cat }" >&2 195*3d9fd9fcSEd Maste 19619261079SEd Maste# shellcheck disable=SC2086 197ce3adf43SDag-Erling Smørgravif [ -z "$(eval $GET_ID)" ] ; then 198ce3adf43SDag-Erling Smørgrav printf '%s: ERROR: No identities found\n' "$0" >&2 199ce3adf43SDag-Erling Smørgrav exit 1 200ce3adf43SDag-Erling Smørgravfi 201ce3adf43SDag-Erling Smørgrav 202*3d9fd9fcSEd Maste# assert_scratch_ok() 203*3d9fd9fcSEd Maste# ensures that $SCRATCH_DIR is setup. 204*3d9fd9fcSEd Masteassert_scratch_ok() { 205*3d9fd9fcSEd Maste [ "$SCRATCH_DIR" ] && [ -d "$SCRATCH_DIR" ] && [ -w "$SCRATCH_DIR" ] && return 0 206*3d9fd9fcSEd Maste 207*3d9fd9fcSEd Maste printf 'ERROR: Assertion failure: in %s(): scratch_dir was not correctly set up (SCRATCH_DIR = "%s")\n' "$1" "$SCRATCH_DIR" >&2 208*3d9fd9fcSEd Maste return 1 209*3d9fd9fcSEd Maste} 210*3d9fd9fcSEd Maste 21119261079SEd Maste# filter_ids() 21219261079SEd Maste# tries to log in using the keys piped to it, and filters out any that work 21319261079SEd Mastefilter_ids() { 21419261079SEd Maste L_SUCCESS="$1" 215*3d9fd9fcSEd Maste assert_scratch_ok filter_ids || return 21619261079SEd Maste L_TMP_ID_FILE="$SCRATCH_DIR"/popids_tmp_id 21719261079SEd Maste L_OUTPUT_FILE="$SCRATCH_DIR"/popids_output 218acc1a9efSDag-Erling Smørgrav 219ce3adf43SDag-Erling Smørgrav # repopulate "$@" inside this function 220ce3adf43SDag-Erling Smørgrav eval set -- "$SSH_OPTS" 221ce3adf43SDag-Erling Smørgrav 22219261079SEd Maste while read -r ID || [ "$ID" ] ; do 223acc1a9efSDag-Erling Smørgrav printf '%s\n' "$ID" > "$L_TMP_ID_FILE" 224ce3adf43SDag-Erling Smørgrav 225ce3adf43SDag-Erling Smørgrav # the next line assumes $PRIV_ID_FILE only set if using a single id file - this 226ce3adf43SDag-Erling Smørgrav # assumption will break if we implement the possibility of multiple -i options. 227ce3adf43SDag-Erling Smørgrav # The point being that if file based, ssh needs the private key, which it cannot 228ce3adf43SDag-Erling Smørgrav # find if only given the contents of the .pub file in an unrelated tmpfile 22919261079SEd Maste $SSH -i "${PRIV_ID_FILE:-$L_TMP_ID_FILE}" \ 230acc1a9efSDag-Erling Smørgrav -o ControlPath=none \ 231acc1a9efSDag-Erling Smørgrav -o LogLevel=INFO \ 232ce3adf43SDag-Erling Smørgrav -o PreferredAuthentications=publickey \ 23319261079SEd Maste -o IdentitiesOnly=yes "$@" exit >"$L_OUTPUT_FILE" 2>&1 </dev/null 23419261079SEd Maste if [ "$?" = "$L_SUCCESS" ] || { 23519261079SEd Maste [ "$SFTP" ] && grep 'allows sftp connections only' "$L_OUTPUT_FILE" >/dev/null 23619261079SEd Maste # this error counts as a success if we're setting up an sftp connection 23719261079SEd Maste } 23819261079SEd Maste then 239076ad2f8SDag-Erling Smørgrav : > "$L_TMP_ID_FILE" 240ce3adf43SDag-Erling Smørgrav else 24119261079SEd Maste grep 'Permission denied' "$L_OUTPUT_FILE" >/dev/null || { 24219261079SEd Maste sed -e 's/^/ERROR: /' <"$L_OUTPUT_FILE" >"$L_TMP_ID_FILE" 243ce3adf43SDag-Erling Smørgrav cat >/dev/null #consume the other keys, causing loop to end 244ce3adf43SDag-Erling Smørgrav } 245ce3adf43SDag-Erling Smørgrav fi 246ce3adf43SDag-Erling Smørgrav 247076ad2f8SDag-Erling Smørgrav cat "$L_TMP_ID_FILE" 248ce3adf43SDag-Erling Smørgrav done 249ce3adf43SDag-Erling Smørgrav} 25019261079SEd Maste 25119261079SEd Maste# populate_new_ids() uses several global variables ($USER_HOST, $SSH_OPTS ...) 25219261079SEd Maste# and has the side effect of setting $NEW_IDS 25319261079SEd Mastepopulate_new_ids() { 25419261079SEd Maste if [ "$FORCED" ] ; then 25519261079SEd Maste # shellcheck disable=SC2086 25619261079SEd Maste NEW_IDS=$(eval $GET_ID) 25719261079SEd Maste return 25819261079SEd Maste fi 25919261079SEd Maste 26019261079SEd Maste printf '%s: INFO: attempting to log in with the new key(s), to filter out any that are already installed\n' "$0" >&2 26119261079SEd Maste # shellcheck disable=SC2086 26219261079SEd Maste NEW_IDS=$(eval $GET_ID | filter_ids $1) 263ce3adf43SDag-Erling Smørgrav 264ce3adf43SDag-Erling Smørgrav if expr "$NEW_IDS" : "^ERROR: " >/dev/null ; then 265ce3adf43SDag-Erling Smørgrav printf '\n%s: %s\n\n' "$0" "$NEW_IDS" >&2 266ce3adf43SDag-Erling Smørgrav exit 1 267ce3adf43SDag-Erling Smørgrav fi 268ce3adf43SDag-Erling Smørgrav if [ -z "$NEW_IDS" ] ; then 269acc1a9efSDag-Erling Smørgrav printf '\n%s: WARNING: All keys were skipped because they already exist on the remote system.\n' "$0" >&2 27019261079SEd Maste printf '\t\t(if you think this is a mistake, you may want to use -f option)\n\n' >&2 271ce3adf43SDag-Erling Smørgrav exit 0 272ce3adf43SDag-Erling Smørgrav fi 273ce3adf43SDag-Erling Smørgrav printf '%s: INFO: %d key(s) remain to be installed -- if you are prompted now it is to install the new keys\n' "$0" "$(printf '%s\n' "$NEW_IDS" | wc -l)" >&2 274ce3adf43SDag-Erling Smørgrav} 275ce3adf43SDag-Erling Smørgrav 27619261079SEd Maste# installkey_sh [target_path] 277535af610SEd Maste# produce a one-liner to add the keys to remote $TARGET_PATH 27819261079SEd Masteinstallkeys_sh() { 27919261079SEd Maste # In setting INSTALLKEYS_SH: 28019261079SEd Maste # the tr puts it all on one line (to placate tcsh) 28119261079SEd Maste # (hence the excessive use of semi-colons (;) ) 28219261079SEd Maste # then in the command: 28319261079SEd Maste # cd to be at $HOME, just in case; 28419261079SEd Maste # the -z `tail ...` checks for a trailing newline. The echo adds one if was missing 28519261079SEd Maste # the cat adds the keys we're getting via STDIN 28619261079SEd Maste # and if available restorecon is used to restore the SELinux context 287535af610SEd Maste # OpenWrt has a special case for root only. 28819261079SEd Maste INSTALLKEYS_SH=$(tr '\t\n' ' ' <<-EOF 289535af610SEd Maste $SET_X 29019261079SEd Maste cd; 29119261079SEd Maste umask 077; 292535af610SEd Maste AUTH_KEY_FILE="${TARGET_PATH}"; 293535af610SEd Maste [ -f /etc/openwrt_release ] && [ "\$LOGNAME" = "root" ] && 294535af610SEd Maste AUTH_KEY_FILE=/etc/dropbear/authorized_keys; 295535af610SEd Maste AUTH_KEY_DIR=\`dirname "\${AUTH_KEY_FILE}"\`; 296535af610SEd Maste mkdir -p "\${AUTH_KEY_DIR}" && 297535af610SEd Maste { [ -z "\`tail -1c "\${AUTH_KEY_FILE}" 2>/dev/null\`" ] || 298535af610SEd Maste echo >> "\${AUTH_KEY_FILE}" || exit 1; } && 299535af610SEd Maste cat >> "\${AUTH_KEY_FILE}" || exit 1; 30019261079SEd Maste if type restorecon >/dev/null 2>&1; then 301535af610SEd Maste restorecon -F "\${AUTH_KEY_DIR}" "\${AUTH_KEY_FILE}"; 30219261079SEd Maste fi 30319261079SEd Maste EOF 30419261079SEd Maste ) 30519261079SEd Maste 30619261079SEd Maste # to defend against quirky remote shells: use 'exec sh -c' to get POSIX; 30719261079SEd Maste printf "exec sh -c '%s'" "${INSTALLKEYS_SH}" 30819261079SEd Maste} 30919261079SEd Maste 31019261079SEd Maste#shellcheck disable=SC2120 # the 'eval set' confuses this 31119261079SEd Masteinstallkeys_via_sftp() { 312535af610SEd Maste AUTH_KEY_FILE=${TARGET_PATH} 313535af610SEd Maste AUTH_KEY_DIR=$(dirname "${AUTH_KEY_FILE}") 31419261079SEd Maste 31519261079SEd Maste # repopulate "$@" inside this function 31619261079SEd Maste eval set -- "$SSH_OPTS" 31719261079SEd Maste 318*3d9fd9fcSEd Maste assert_scratch_ok installkeys_via_sftp || return 1 319*3d9fd9fcSEd Maste L_KEYS="$SCRATCH_DIR"/authorized_keys 320*3d9fd9fcSEd Maste L_SHARED_CON="$SCRATCH_DIR"/master-conn 32119261079SEd Maste $SSH -f -N -M -S "$L_SHARED_CON" "$@" 322*3d9fd9fcSEd Maste L_CLEANUP="$SSH -S '$L_SHARED_CON' -O exit 'ignored' >/dev/null 2>&1 ; $SCRATCH_CLEANUP" 32319261079SEd Maste #shellcheck disable=SC2064 32419261079SEd Maste trap "$L_CLEANUP" EXIT TERM INT QUIT 325*3d9fd9fcSEd Maste sftp -b - -o "ControlPath='$L_SHARED_CON'" "ignored" <<-EOF || return 1 326535af610SEd Maste -get "$AUTH_KEY_FILE" "$L_KEYS" 32719261079SEd Maste EOF 32819261079SEd Maste # add a newline or create file if it's missing, same like above 32919261079SEd Maste [ -z "$(tail -1c "$L_KEYS" 2>/dev/null)" ] || echo >> "$L_KEYS" 33019261079SEd Maste # append the keys being piped in here 33119261079SEd Maste cat >> "$L_KEYS" 332*3d9fd9fcSEd Maste sftp -b - -o "ControlPath='$L_SHARED_CON'" "ignored" <<-EOF || return 1 333535af610SEd Maste -mkdir "$AUTH_KEY_DIR" 334535af610SEd Maste chmod 700 "$AUTH_KEY_DIR" 335*3d9fd9fcSEd Maste put "$L_KEYS" "$AUTH_KEY_FILE" 336535af610SEd Maste chmod 600 "$AUTH_KEY_FILE" 33719261079SEd Maste EOF 33819261079SEd Maste #shellcheck disable=SC2064 33919261079SEd Maste eval "$L_CLEANUP" && trap "$SCRATCH_CLEANUP" EXIT TERM INT QUIT 34019261079SEd Maste} 34119261079SEd Maste 34219261079SEd Maste 34319261079SEd Maste# create a scratch dir for any temporary files needed 34419261079SEd Masteif SCRATCH_DIR=$(mktemp -d ~/.ssh/ssh-copy-id.XXXXXXXXXX) && 34519261079SEd Maste [ "$SCRATCH_DIR" ] && [ -d "$SCRATCH_DIR" ] 34619261079SEd Mastethen 34719261079SEd Maste chmod 0700 "$SCRATCH_DIR" 34819261079SEd Maste SCRATCH_CLEANUP="rm -rf \"$SCRATCH_DIR\"" 34919261079SEd Maste #shellcheck disable=SC2064 35019261079SEd Maste trap "$SCRATCH_CLEANUP" EXIT TERM INT QUIT 35119261079SEd Masteelse 352*3d9fd9fcSEd Maste printf '%s: ERROR: failed to create required temporary directory under ~/.ssh (HOME="%s")\n' "$0" "$HOME" >&2 35319261079SEd Maste exit 1 35419261079SEd Mastefi 35519261079SEd Maste 35619261079SEd MasteREMOTE_VERSION=$($SSH -v -o PreferredAuthentications=',' -o ControlPath=none "$@" 2>&1 | 357ce3adf43SDag-Erling Smørgrav sed -ne 's/.*remote software version //p') 358ce3adf43SDag-Erling Smørgrav 35919261079SEd Maste# shellcheck disable=SC2029 360ce3adf43SDag-Erling Smørgravcase "$REMOTE_VERSION" in 361ce3adf43SDag-Erling Smørgrav NetScreen*) 362ce3adf43SDag-Erling Smørgrav populate_new_ids 1 363ce3adf43SDag-Erling Smørgrav for KEY in $(printf "%s" "$NEW_IDS" | cut -d' ' -f2) ; do 36419261079SEd Maste KEY_NO=$((KEY_NO + 1)) 36519261079SEd Maste printf '%s\n' "$KEY" | grep ssh-dss >/dev/null || { 366ce3adf43SDag-Erling Smørgrav printf '%s: WARNING: Non-dsa key (#%d) skipped (NetScreen only supports DSA keys)\n' "$0" "$KEY_NO" >&2 367ce3adf43SDag-Erling Smørgrav continue 368ce3adf43SDag-Erling Smørgrav } 36919261079SEd Maste [ "$DRY_RUN" ] || printf 'set ssh pka-dsa key %s\nsave\nexit\n' "$KEY" | $SSH -T "$@" >/dev/null 2>&1 370ce3adf43SDag-Erling Smørgrav if [ $? = 255 ] ; then 371ce3adf43SDag-Erling Smørgrav printf '%s: ERROR: installation of key #%d failed (please report a bug describing what caused this, so that we can make this message useful)\n' "$0" "$KEY_NO" >&2 372ce3adf43SDag-Erling Smørgrav else 37319261079SEd Maste ADDED=$((ADDED + 1)) 374ce3adf43SDag-Erling Smørgrav fi 375ce3adf43SDag-Erling Smørgrav done 376ce3adf43SDag-Erling Smørgrav if [ -z "$ADDED" ] ; then 377ce3adf43SDag-Erling Smørgrav exit 1 378ce3adf43SDag-Erling Smørgrav fi 379ce3adf43SDag-Erling Smørgrav ;; 380ce3adf43SDag-Erling Smørgrav *) 381535af610SEd Maste # Assuming that the remote host treats $TARGET_PATH as one might expect 382ce3adf43SDag-Erling Smørgrav populate_new_ids 0 38319261079SEd Maste if ! [ "$DRY_RUN" ] ; then 38419261079SEd Maste printf '%s\n' "$NEW_IDS" | \ 38519261079SEd Maste if [ "$SFTP" ] ; then 38619261079SEd Maste #shellcheck disable=SC2119 38719261079SEd Maste installkeys_via_sftp 38819261079SEd Maste else 38919261079SEd Maste $SSH "$@" "$(installkeys_sh)" 39019261079SEd Maste fi || exit 1 39119261079SEd Maste fi 392ce3adf43SDag-Erling Smørgrav ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l) 393ce3adf43SDag-Erling Smørgrav ;; 394ce3adf43SDag-Erling Smørgravesac 395ce3adf43SDag-Erling Smørgrav 396ce3adf43SDag-Erling Smørgravif [ "$DRY_RUN" ] ; then 397ce3adf43SDag-Erling Smørgrav cat <<-EOF 398ce3adf43SDag-Erling Smørgrav =-=-=-=-=-=-=-= 399ce3adf43SDag-Erling Smørgrav Would have added the following key(s): 400ce3adf43SDag-Erling Smørgrav 401ce3adf43SDag-Erling Smørgrav $NEW_IDS 402ce3adf43SDag-Erling Smørgrav =-=-=-=-=-=-=-= 403ce3adf43SDag-Erling Smørgrav EOF 404ce3adf43SDag-Erling Smørgravelse 405535af610SEd Maste [ -z "$SFTP" ] || PORT_OPT=P 406ce3adf43SDag-Erling Smørgrav cat <<-EOF 407ce3adf43SDag-Erling Smørgrav 408ce3adf43SDag-Erling Smørgrav Number of key(s) added: $ADDED 409ce3adf43SDag-Erling Smørgrav 410*3d9fd9fcSEd Maste Now try logging into the machine, with: "${SFTP:-ssh} ${SEEN_OPT_I:+-i${PRIV_ID_FILE:+ $PRIV_ID_FILE} }${SSH_PORT:+-${PORT_OPT:-p} $SSH_PORT }${OPTS_USER_HOST}" 411ce3adf43SDag-Erling Smørgrav and check to make sure that only the key(s) you wanted were added. 412ce3adf43SDag-Erling Smørgrav 413ce3adf43SDag-Erling Smørgrav EOF 414ce3adf43SDag-Erling Smørgravfi 415ce3adf43SDag-Erling Smørgrav 416ce3adf43SDag-Erling Smørgrav# =-=-=-= 417