From a9c9e91a82bc1a2cf801b4e3ef27a941dbd27717 Mon Sep 17 00:00:00 2001 From: Philip Hands Date: Wed, 16 Sep 2020 16:13:30 +0200 Subject: add -s flag: to install keys via SFTP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is prompted by: https://bugzilla.mindrot.org/show_bug.cgi?id=3201 Thanks go to Matthias Blümel for the idea, and the helpful patch, from which this patch grew. SSH-Copy-ID-Upstream: f7c76dc64427cd20287a6868f672423b62057614 --- contrib/ssh-copy-id | 99 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 72 insertions(+), 27 deletions(-) (limited to 'contrib/ssh-copy-id') diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id index ee3f6374..6b8aca7d 100644 --- a/contrib/ssh-copy-id +++ b/contrib/ssh-copy-id @@ -1,6 +1,7 @@ #!/bin/sh # Copyright (c) 1999-2020 Philip Hands +# 2020 Matthias Blümel # 2017 Sebastien Boyron # 2013 Martin Kletzander # 2010 Adeodato =?iso-8859-1?Q?Sim=F3?= @@ -61,11 +62,14 @@ fi # shellcheck disable=SC2010 DEFAULT_PUB_ID_FILE=$(ls -t "${HOME}"/.ssh/id*.pub 2>/dev/null | grep -v -- '-cert.pub$' | head -n 1) +SSH="ssh -a -x" +umask 0177 usage () { - printf 'Usage: %s [-h|-?|-f|-n] [-i [identity_file]] [-p port] [-F alternative ssh_config file] [[-o ] ...] [user@]hostname\n' "$0" >&2 + printf 'Usage: %s [-h|-?|-f|-n|-s] [-i [identity_file]] [-p port] [-F alternative ssh_config file] [[-o ] ...] [user@]hostname\n' "$0" >&2 printf '\t-f: force mode -- copy keys without trying to check if they are already installed\n' >&2 printf '\t-n: dry run -- no keys are actually copied\n' >&2 + printf '\t-s: use sftp -- use sftp instead of executing remote-commands. Can be useful if the remote only allows sftp\n' >&2 printf '\t-h|-?: print this help\n' >&2 exit 1 } @@ -108,9 +112,8 @@ if [ -n "$SSH_AUTH_SOCK" ] && ssh-add -L >/dev/null 2>&1 ; then GET_ID="ssh-add -L" fi -while getopts "i:o:p:F:fnh?" OPT +while getopts "i:o:p:F:fnsh?" OPT do - case "$OPT" in i) [ "${SEEN_OPT_I}" ] && { @@ -129,6 +132,9 @@ do n) DRY_RUN=1 ;; + s) + SFTP=sftp + ;; h|\?) usage ;; @@ -137,9 +143,6 @@ done #shift all args to keep only USER_HOST shift $((OPTIND-1)) - - - if [ $# = 0 ] ; then usage fi @@ -170,6 +173,8 @@ fi # and has the side effect of setting $NEW_IDS populate_new_ids() { L_SUCCESS="$1" + L_TMP_ID_FILE="$SCRATCH_DIR"/popids_tmp_id + L_OUTPUT_FILE="$SCRATCH_DIR"/popids_output # shellcheck disable=SC2086 if [ "$FORCED" ] ; then @@ -180,15 +185,6 @@ populate_new_ids() { # repopulate "$@" inside this function eval set -- "$SSH_OPTS" - umask 0177 - L_TMP_ID_FILE=$(mktemp ~/.ssh/ssh-copy-id_id.XXXXXXXXXX) - if test $? -ne 0 || test "x$L_TMP_ID_FILE" = "x" ; then - printf '%s: ERROR: mktemp failed\n' "$0" >&2 - exit 1 - fi - L_CLEANUP="rm -f \"$L_TMP_ID_FILE\" \"${L_TMP_ID_FILE}.stderr\"" - # shellcheck disable=SC2064 - trap "$L_CLEANUP" EXIT TERM INT QUIT printf '%s: INFO: attempting to log in with the new key(s), to filter out any that are already installed\n' "$0" >&2 # shellcheck disable=SC2086 NEW_IDS=$( @@ -200,16 +196,19 @@ populate_new_ids() { # assumption will break if we implement the possibility of multiple -i options. # The point being that if file based, ssh needs the private key, which it cannot # find if only given the contents of the .pub file in an unrelated tmpfile - ssh -i "${PRIV_ID_FILE:-$L_TMP_ID_FILE}" \ + $SSH -i "${PRIV_ID_FILE:-$L_TMP_ID_FILE}" \ -o ControlPath=none \ -o LogLevel=INFO \ -o PreferredAuthentications=publickey \ - -o IdentitiesOnly=yes "$@" exit 2>"$L_TMP_ID_FILE.stderr" "$L_OUTPUT_FILE" 2>&1 "$L_TMP_ID_FILE" + elif [ "$SFTP" ] && grep 'allows sftp connections only' "$L_OUTPUT_FILE" >/dev/null ; then + # this error counts as a success when we're setting up an sftp connection + : > "$L_TMP_ID_FILE" else - grep 'Permission denied' "$L_TMP_ID_FILE.stderr" >/dev/null || { - sed -e 's/^/ERROR: /' <"$L_TMP_ID_FILE.stderr" >"$L_TMP_ID_FILE" + grep 'Permission denied' "$L_OUTPUT_FILE" >/dev/null || { + sed -e 's/^/ERROR: /' <"$L_OUTPUT_FILE" >"$L_TMP_ID_FILE" cat >/dev/null #consume the other keys, causing loop to end } fi @@ -218,7 +217,6 @@ populate_new_ids() { done } ) - eval "$L_CLEANUP" && trap - EXIT TERM INT QUIT if expr "$NEW_IDS" : "^ERROR: " >/dev/null ; then printf '\n%s: %s\n\n' "$0" "$NEW_IDS" >&2 @@ -264,7 +262,49 @@ EOF printf "exec sh -c '%s'" "${INSTALLKEYS_SH}" } -REMOTE_VERSION=$(ssh -v -o PreferredAuthentications=',' -o ControlPath=none "$@" 2>&1 | +installkeys_via_sftp() { + + # repopulate "$@" inside this function + eval set -- "$SSH_OPTS" + + L_KEYS=$SCRATCH_DIR/authorized_keys + L_SHARED_CON=$SCRATCH_DIR/master-conn + $SSH -f -N -M -S $L_SHARED_CON "$@" + L_CLEANUP="$SSH -S $L_SHARED_CON -O exit 'ignored' >/dev/null 2>&1 ; $SCRATCH_CLEANUP" + trap "$L_CLEANUP" EXIT TERM INT QUIT + sftp -b - -o "ControlPath=$L_SHARED_CON" "ignored" <<-EOF || return 1 + -get .ssh/authorized_keys $L_KEYS + EOF + # add a newline or create file if it's missing, same like above + [ -z "$(tail -1c $L_KEYS 2>/dev/null)" ] || echo >> $L_KEYS + # append the keys being piped in here + cat >> $L_KEYS + sftp -b - -o "ControlPath=$L_SHARED_CON" "ignored" <<-EOF || return 1 + -mkdir .ssh + chmod 700 .ssh + put $L_KEYS .ssh/authorized_keys + chmod 600 .ssh/authorized_keys + EOF + eval "$L_CLEANUP" && trap "$SCRATCH_CLEANUP" EXIT TERM INT QUIT +} + + +# create a scratch dir for any temporary files needed +SCRATCH_DIR=$(mktemp -d ~/.ssh/ssh-copy-id.XXXXXXXXXX) +if test $? -ne 0 || test "x$SCRATCH_DIR" = "x" ; then + printf '%s: ERROR: mktemp failed\n' "$0" >&2 + exit 1 +fi +chmod 0700 $SCRATCH_DIR +if [ -d "$SCRATCH_DIR" ] ; then + SCRATCH_CLEANUP="rm -rf \"$SCRATCH_DIR\"" + trap "$SCRATCH_CLEANUP" EXIT TERM INT QUIT +else + printf '%s: ERROR: Required scratch directory (%s) was not created\n' "$0" "$SCRATCH_DIR" >&2 + exit 1 +fi + +REMOTE_VERSION=$($SSH -v -o PreferredAuthentications=',' -o ControlPath=none "$@" 2>&1 | sed -ne 's/.*remote software version //p') # shellcheck disable=SC2029 @@ -277,7 +317,7 @@ case "$REMOTE_VERSION" in printf '%s: WARNING: Non-dsa key (#%d) skipped (NetScreen only supports DSA keys)\n' "$0" "$KEY_NO" >&2 continue } - [ "$DRY_RUN" ] || printf 'set ssh pka-dsa key %s\nsave\nexit\n' "$KEY" | ssh -T "$@" >/dev/null 2>&1 + [ "$DRY_RUN" ] || printf 'set ssh pka-dsa key %s\nsave\nexit\n' "$KEY" | $SSH -T "$@" >/dev/null 2>&1 if [ $? = 255 ] ; then 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 else @@ -291,16 +331,21 @@ case "$REMOTE_VERSION" in dropbear*) populate_new_ids 0 [ "$DRY_RUN" ] || printf '%s\n' "$NEW_IDS" | \ - ssh "$@" "$(installkeys_sh /etc/dropbear/authorized_keys)" \ + $SSH "$@" "$(installkeys_sh /etc/dropbear/authorized_keys)" \ || exit 1 ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l) ;; *) # Assuming that the remote host treats ~/.ssh/authorized_keys as one might expect populate_new_ids 0 - [ "$DRY_RUN" ] || printf '%s\n' "$NEW_IDS" | \ - ssh "$@" "$(installkeys_sh)" \ - || exit 1 + if ! [ "$DRY_RUN" ] ; then + printf '%s\n' "$NEW_IDS" | \ + if [ "$SFTP" ] ; then + installkeys_via_sftp + else + $SSH "$@" "$(installkeys_sh)" + fi || exit 1 + fi ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l) ;; esac @@ -318,7 +363,7 @@ else Number of key(s) added: $ADDED - Now try logging into the machine, with: "ssh $SSH_OPTS" + Now try logging into the machine, with: "${SFTP:-ssh} $SSH_OPTS" and check to make sure that only the key(s) you wanted were added. EOF -- cgit v1.2.3