summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien Miller <djm@mindrot.org>2020-09-22 10:07:43 +1000
committerDamien Miller <djm@mindrot.org>2020-09-22 10:07:43 +1000
commit9bb8a303ce05ff13fb421de991b495930be103c3 (patch)
treef1a8391347358b2ffa313e35ff145a226bc60f78
parent0a4a5571ada76b1b012bec9cf6ad1203fc19ec8d (diff)
sync with upstream ssh-copy-id rev f0da1a1b7
-rw-r--r--contrib/ssh-copy-id158
-rw-r--r--contrib/ssh-copy-id.12
2 files changed, 81 insertions, 79 deletions
diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id
index b83b83619..392f64f94 100644
--- a/contrib/ssh-copy-id
+++ b/contrib/ssh-copy-id
@@ -1,6 +1,7 @@
1#!/bin/sh 1#!/bin/sh
2 2
3# Copyright (c) 1999-2016 Philip Hands <phil@hands.com> 3# Copyright (c) 1999-2020 Philip Hands <phil@hands.com>
4# 2017 Sebastien Boyron <seb@boyron.eu>
4# 2013 Martin Kletzander <mkletzan@redhat.com> 5# 2013 Martin Kletzander <mkletzan@redhat.com>
5# 2010 Adeodato =?iso-8859-1?Q?Sim=F3?= <asp16@alu.ua.es> 6# 2010 Adeodato =?iso-8859-1?Q?Sim=F3?= <asp16@alu.ua.es>
6# 2010 Eric Moret <eric.moret@gmail.com> 7# 2010 Eric Moret <eric.moret@gmail.com>
@@ -33,13 +34,15 @@
33# Shell script to install your public key(s) on a remote machine 34# Shell script to install your public key(s) on a remote machine
34# See the ssh-copy-id(1) man page for details 35# See the ssh-copy-id(1) man page for details
35 36
37# shellcheck shell=dash
38
36# check that we have something mildly sane as our shell, or try to find something better 39# check that we have something mildly sane as our shell, or try to find something better
37if false ^ printf "%s: WARNING: ancient shell, hunting for a more modern one... " "$0" 40if false ^ printf "%s: WARNING: ancient shell, hunting for a more modern one... " "$0"
38then 41then
39 SANE_SH=${SANE_SH:-/usr/bin/ksh} 42 SANE_SH=${SANE_SH:-/usr/bin/ksh}
40 if printf 'true ^ false\n' | "$SANE_SH" 43 if printf 'true ^ false\n' | "$SANE_SH"
41 then 44 then
42 printf "'%s' seems viable.\n" "$SANE_SH" 45 printf "'%s' seems viable.\\n" "$SANE_SH"
43 exec "$SANE_SH" "$0" "$@" 46 exec "$SANE_SH" "$0" "$@"
44 else 47 else
45 cat <<-EOF 48 cat <<-EOF
@@ -51,16 +54,16 @@ then
51 a bug describing your setup, and the shell you used to make it work. 54 a bug describing your setup, and the shell you used to make it work.
52 55
53 EOF 56 EOF
54 printf "%s: ERROR: Less dimwitted shell required.\n" "$0" 57 printf '%s: ERROR: Less dimwitted shell required.\n' "$0"
55 exit 1 58 exit 1
56 fi 59 fi
57fi 60fi
58 61
59most_recent_id="$(cd "$HOME" ; ls -t .ssh/id*.pub 2>/dev/null | grep -v -- '-cert.pub$' | head -n 1)" 62# shellcheck disable=SC2010
60DEFAULT_PUB_ID_FILE="${most_recent_id:+$HOME/}$most_recent_id" 63DEFAULT_PUB_ID_FILE=$(ls -t "${HOME}"/.ssh/id*.pub 2>/dev/null | grep -v -- '-cert.pub$' | head -n 1)
61 64
62usage () { 65usage () {
63 printf 'Usage: %s [-h|-?|-f|-n] [-i [identity_file]] [-p port] [[-o <ssh -o options>] ...] [user@]hostname\n' "$0" >&2 66 printf 'Usage: %s [-h|-?|-f|-n] [-i [identity_file]] [-p port] [-F alternative ssh_config file] [[-o <ssh -o options>] ...] [user@]hostname\n' "$0" >&2
64 printf '\t-f: force mode -- copy keys without trying to check if they are already installed\n' >&2 67 printf '\t-f: force mode -- copy keys without trying to check if they are already installed\n' >&2
65 printf '\t-n: dry run -- no keys are actually copied\n' >&2 68 printf '\t-n: dry run -- no keys are actually copied\n' >&2
66 printf '\t-h|-?: print this help\n' >&2 69 printf '\t-h|-?: print this help\n' >&2
@@ -69,18 +72,18 @@ usage () {
69 72
70# escape any single quotes in an argument 73# escape any single quotes in an argument
71quote() { 74quote() {
72 printf "%s\n" "$1" | sed -e "s/'/'\\\\''/g" 75 printf '%s\n' "$1" | sed -e "s/'/'\\\\''/g"
73} 76}
74 77
75use_id_file() { 78use_id_file() {
76 local L_ID_FILE="$1" 79 local L_ID_FILE="$1"
77 80
78 if [ -z "$L_ID_FILE" ] ; then 81 if [ -z "$L_ID_FILE" ] ; then
79 printf "%s: ERROR: no ID file found\n" "$0" 82 printf '%s: ERROR: no ID file found\n' "$0"
80 exit 1 83 exit 1
81 fi 84 fi
82 85
83 if expr "$L_ID_FILE" : ".*\.pub$" >/dev/null ; then 86 if expr "$L_ID_FILE" : '.*\.pub$' >/dev/null ; then
84 PUB_ID_FILE="$L_ID_FILE" 87 PUB_ID_FILE="$L_ID_FILE"
85 else 88 else
86 PUB_ID_FILE="$L_ID_FILE.pub" 89 PUB_ID_FILE="$L_ID_FILE.pub"
@@ -93,7 +96,7 @@ use_id_file() {
93 ErrMSG=$( { : < "$f" ; } 2>&1 ) || { 96 ErrMSG=$( { : < "$f" ; } 2>&1 ) || {
94 local L_PRIVMSG="" 97 local L_PRIVMSG=""
95 [ "$f" = "$PRIV_ID_FILE" ] && L_PRIVMSG=" (to install the contents of '$PUB_ID_FILE' anyway, look at the -f option)" 98 [ "$f" = "$PRIV_ID_FILE" ] && L_PRIVMSG=" (to install the contents of '$PUB_ID_FILE' anyway, look at the -f option)"
96 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/.*: *//')" 99 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/.*: *//')"
97 exit 1 100 exit 1
98 } 101 }
99 done 102 done
@@ -105,80 +108,37 @@ if [ -n "$SSH_AUTH_SOCK" ] && ssh-add -L >/dev/null 2>&1 ; then
105 GET_ID="ssh-add -L" 108 GET_ID="ssh-add -L"
106fi 109fi
107 110
108while test "$#" -gt 0 111while getopts "i:o:p:F:fnh?" OPT
109do 112do
110 [ "${SEEN_OPT_I}" ] && expr "$1" : "[-]i" >/dev/null && {
111 printf "\n%s: ERROR: -i option must not be specified more than once\n\n" "$0"
112 usage
113 }
114
115 OPT= OPTARG=
116 # implement something like getopt to avoid Solaris pain
117 case "$1" in
118 -i?*|-o?*|-p?*)
119 OPT="$(printf -- "$1"|cut -c1-2)"
120 OPTARG="$(printf -- "$1"|cut -c3-)"
121 shift
122 ;;
123 -o|-p)
124 OPT="$1"
125 OPTARG="$2"
126 shift 2
127 ;;
128 -i)
129 OPT="$1"
130 test "$#" -le 2 || expr "$2" : "[-]" >/dev/null || {
131 OPTARG="$2"
132 shift
133 }
134 shift
135 ;;
136 -f|-n|-h|-\?)
137 OPT="$1"
138 OPTARG=
139 shift
140 ;;
141 --)
142 shift
143 while test "$#" -gt 0
144 do
145 SAVEARGS="${SAVEARGS:+$SAVEARGS }'$(quote "$1")'"
146 shift
147 done
148 break
149 ;;
150 -*)
151 printf "\n%s: ERROR: invalid option (%s)\n\n" "$0" "$1"
152 usage
153 ;;
154 *)
155 SAVEARGS="${SAVEARGS:+$SAVEARGS }'$(quote "$1")'"
156 shift
157 continue
158 ;;
159 esac
160 113
161 case "$OPT" in 114 case "$OPT" in
162 -i) 115 i)
116 [ "${SEEN_OPT_I}" ] && {
117 printf '\n%s: ERROR: -i option must not be specified more than once\n\n' "$0"
118 usage
119 }
163 SEEN_OPT_I="yes" 120 SEEN_OPT_I="yes"
164 use_id_file "${OPTARG:-$DEFAULT_PUB_ID_FILE}" 121 use_id_file "${OPTARG:-$DEFAULT_PUB_ID_FILE}"
165 ;; 122 ;;
166 -o|-p) 123 o|p|F)
167 SSH_OPTS="${SSH_OPTS:+$SSH_OPTS }$OPT '$(quote "$OPTARG")'" 124 SSH_OPTS="${SSH_OPTS:+$SSH_OPTS }-$OPT '$(quote "${OPTARG}")'"
168 ;; 125 ;;
169 -f) 126 f)
170 FORCED=1 127 FORCED=1
171 ;; 128 ;;
172 -n) 129 n)
173 DRY_RUN=1 130 DRY_RUN=1
174 ;; 131 ;;
175 -h|-\?) 132 h|\?)
176 usage 133 usage
177 ;; 134 ;;
178 esac 135 esac
179done 136done
137#shift all args to keep only USER_HOST
138shift $((OPTIND-1))
139
140
180 141
181eval set -- "$SAVEARGS"
182 142
183if [ $# = 0 ] ; then 143if [ $# = 0 ] ; then
184 usage 144 usage
@@ -189,16 +149,18 @@ if [ $# != 1 ] ; then
189fi 149fi
190 150
191# drop trailing colon 151# drop trailing colon
192USER_HOST=$(printf "%s\n" "$1" | sed 's/:$//') 152USER_HOST="$*"
193# tack the hostname onto SSH_OPTS 153# tack the hostname onto SSH_OPTS
194SSH_OPTS="${SSH_OPTS:+$SSH_OPTS }'$(quote "$USER_HOST")'" 154SSH_OPTS="${SSH_OPTS:+$SSH_OPTS }'$(quote "$USER_HOST")'"
195# and populate "$@" for later use (only way to get proper quoting of options) 155# and populate "$@" for later use (only way to get proper quoting of options)
196eval set -- "$SSH_OPTS" 156eval set -- "$SSH_OPTS"
197 157
158# shellcheck disable=SC2086
198if [ -z "$(eval $GET_ID)" ] && [ -r "${PUB_ID_FILE:=$DEFAULT_PUB_ID_FILE}" ] ; then 159if [ -z "$(eval $GET_ID)" ] && [ -r "${PUB_ID_FILE:=$DEFAULT_PUB_ID_FILE}" ] ; then
199 use_id_file "$PUB_ID_FILE" 160 use_id_file "$PUB_ID_FILE"
200fi 161fi
201 162
163# shellcheck disable=SC2086
202if [ -z "$(eval $GET_ID)" ] ; then 164if [ -z "$(eval $GET_ID)" ] ; then
203 printf '%s: ERROR: No identities found\n' "$0" >&2 165 printf '%s: ERROR: No identities found\n' "$0" >&2
204 exit 1 166 exit 1
@@ -209,6 +171,7 @@ fi
209populate_new_ids() { 171populate_new_ids() {
210 local L_SUCCESS="$1" 172 local L_SUCCESS="$1"
211 173
174 # shellcheck disable=SC2086
212 if [ "$FORCED" ] ; then 175 if [ "$FORCED" ] ; then
213 NEW_IDS=$(eval $GET_ID) 176 NEW_IDS=$(eval $GET_ID)
214 return 177 return
@@ -218,17 +181,20 @@ populate_new_ids() {
218 eval set -- "$SSH_OPTS" 181 eval set -- "$SSH_OPTS"
219 182
220 umask 0177 183 umask 0177
221 local L_TMP_ID_FILE=$(mktemp ~/.ssh/ssh-copy-id_id.XXXXXXXXXX) 184 local L_TMP_ID_FILE
185 L_TMP_ID_FILE=$(mktemp ~/.ssh/ssh-copy-id_id.XXXXXXXXXX)
222 if test $? -ne 0 || test "x$L_TMP_ID_FILE" = "x" ; then 186 if test $? -ne 0 || test "x$L_TMP_ID_FILE" = "x" ; then
223 printf '%s: ERROR: mktemp failed\n' "$0" >&2 187 printf '%s: ERROR: mktemp failed\n' "$0" >&2
224 exit 1 188 exit 1
225 fi 189 fi
226 local L_CLEANUP="rm -f \"$L_TMP_ID_FILE\" \"${L_TMP_ID_FILE}.stderr\"" 190 local L_CLEANUP="rm -f \"$L_TMP_ID_FILE\" \"${L_TMP_ID_FILE}.stderr\""
191 # shellcheck disable=SC2064
227 trap "$L_CLEANUP" EXIT TERM INT QUIT 192 trap "$L_CLEANUP" EXIT TERM INT QUIT
228 printf '%s: INFO: attempting to log in with the new key(s), to filter out any that are already installed\n' "$0" >&2 193 printf '%s: INFO: attempting to log in with the new key(s), to filter out any that are already installed\n' "$0" >&2
194 # shellcheck disable=SC2086
229 NEW_IDS=$( 195 NEW_IDS=$(
230 eval $GET_ID | { 196 eval $GET_ID | {
231 while read ID || [ "$ID" ] ; do 197 while read -r ID || [ "$ID" ] ; do
232 printf '%s\n' "$ID" > "$L_TMP_ID_FILE" 198 printf '%s\n' "$ID" > "$L_TMP_ID_FILE"
233 199
234 # the next line assumes $PRIV_ID_FILE only set if using a single id file - this 200 # the next line assumes $PRIV_ID_FILE only set if using a single id file - this
@@ -261,21 +227,52 @@ populate_new_ids() {
261 fi 227 fi
262 if [ -z "$NEW_IDS" ] ; then 228 if [ -z "$NEW_IDS" ] ; then
263 printf '\n%s: WARNING: All keys were skipped because they already exist on the remote system.\n' "$0" >&2 229 printf '\n%s: WARNING: All keys were skipped because they already exist on the remote system.\n' "$0" >&2
264 printf '\t\t(if you think this is a mistake, you may want to use -f option)\n\n' "$0" >&2 230 printf '\t\t(if you think this is a mistake, you may want to use -f option)\n\n' >&2
265 exit 0 231 exit 0
266 fi 232 fi
267 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 233 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
268} 234}
269 235
236# installkey_sh [target_path]
237# produce a one-liner to add the keys to remote authorized_keys file
238# optionally takes an alternative path for authorized_keys
239installkeys_sh() {
240 local AUTH_KEY_FILE=${1:-.ssh/authorized_keys}
241
242 # In setting INSTALLKEYS_SH:
243 # the tr puts it all on one line (to placate tcsh)
244 # (hence the excessive use of semi-colons (;) )
245 # then in the command:
246 # cd to be at $HOME, just in case;
247 # the -z `tail ...` checks for a trailing newline. The echo adds one if was missing
248 # the cat adds the keys we're getting via STDIN
249 # and if available restorecon is used to restore the SELinux context
250 INSTALLKEYS_SH=$(tr '\t\n' ' ' <<-EOF)
251 cd;
252 umask 077;
253 mkdir -p $(dirname "${AUTH_KEY_FILE}") &&
254 { [ -z \`tail -1c ${AUTH_KEY_FILE} 2>/dev/null\` ] || echo >> ${AUTH_KEY_FILE}; } &&
255 cat >> ${AUTH_KEY_FILE} ||
256 exit 1;
257 if type restorecon >/dev/null 2>&1; then
258 restorecon -F .ssh ${AUTH_KEY_FILE};
259 fi
260EOF
261
262 # to defend against quirky remote shells: use 'exec sh -c' to get POSIX;
263 printf "exec sh -c '%s'" "${INSTALLKEYS_SH}"
264}
265
270REMOTE_VERSION=$(ssh -v -o PreferredAuthentications=',' -o ControlPath=none "$@" 2>&1 | 266REMOTE_VERSION=$(ssh -v -o PreferredAuthentications=',' -o ControlPath=none "$@" 2>&1 |
271 sed -ne 's/.*remote software version //p') 267 sed -ne 's/.*remote software version //p')
272 268
269# shellcheck disable=SC2029
273case "$REMOTE_VERSION" in 270case "$REMOTE_VERSION" in
274 NetScreen*) 271 NetScreen*)
275 populate_new_ids 1 272 populate_new_ids 1
276 for KEY in $(printf "%s" "$NEW_IDS" | cut -d' ' -f2) ; do 273 for KEY in $(printf "%s" "$NEW_IDS" | cut -d' ' -f2) ; do
277 KEY_NO=$(($KEY_NO + 1)) 274 KEY_NO=$((KEY_NO + 1))
278 printf "%s\n" "$KEY" | grep ssh-dss >/dev/null || { 275 printf '%s\n' "$KEY" | grep ssh-dss >/dev/null || {
279 printf '%s: WARNING: Non-dsa key (#%d) skipped (NetScreen only supports DSA keys)\n' "$0" "$KEY_NO" >&2 276 printf '%s: WARNING: Non-dsa key (#%d) skipped (NetScreen only supports DSA keys)\n' "$0" "$KEY_NO" >&2
280 continue 277 continue
281 } 278 }
@@ -283,20 +280,25 @@ case "$REMOTE_VERSION" in
283 if [ $? = 255 ] ; then 280 if [ $? = 255 ] ; then
284 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 281 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
285 else 282 else
286 ADDED=$(($ADDED + 1)) 283 ADDED=$((ADDED + 1))
287 fi 284 fi
288 done 285 done
289 if [ -z "$ADDED" ] ; then 286 if [ -z "$ADDED" ] ; then
290 exit 1 287 exit 1
291 fi 288 fi
292 ;; 289 ;;
290 dropbear*)
291 populate_new_ids 0
292 [ "$DRY_RUN" ] || printf '%s\n' "$NEW_IDS" | \
293 ssh "$@" "$(installkeys_sh /etc/dropbear/authorized_keys)" \
294 || exit 1
295 ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l)
296 ;;
293 *) 297 *)
294 # Assuming that the remote host treats ~/.ssh/authorized_keys as one might expect 298 # Assuming that the remote host treats ~/.ssh/authorized_keys as one might expect
295 populate_new_ids 0 299 populate_new_ids 0
296 # in ssh below - to defend against quirky remote shells: use 'exec sh -c' to get POSIX;
297 # 'cd' to be at $HOME; add a newline if it's missing; and all on one line, because tcsh.
298 [ "$DRY_RUN" ] || printf '%s\n' "$NEW_IDS" | \ 300 [ "$DRY_RUN" ] || printf '%s\n' "$NEW_IDS" | \
299 ssh "$@" "exec sh -c 'cd ; umask 077 ; mkdir -p .ssh && { [ -z "'`tail -1c .ssh/authorized_keys 2>/dev/null`'" ] || echo >> .ssh/authorized_keys ; } && cat >> .ssh/authorized_keys || exit 1 ; if type restorecon >/dev/null 2>&1 ; then restorecon -F .ssh .ssh/authorized_keys ; fi'" \ 301 ssh "$@" "$(installkeys_sh)" \
300 || exit 1 302 || exit 1
301 ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l) 303 ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l)
302 ;; 304 ;;
diff --git a/contrib/ssh-copy-id.1 b/contrib/ssh-copy-id.1
index ae75c79a5..b75a88365 100644
--- a/contrib/ssh-copy-id.1
+++ b/contrib/ssh-copy-id.1
@@ -1,5 +1,5 @@
1.ig \" -*- nroff -*- 1.ig \" -*- nroff -*-
2Copyright (c) 1999-2013 hands.com Ltd. <http://hands.com/> 2Copyright (c) 1999-2016 hands.com Ltd. <http://hands.com/>
3 3
4Redistribution and use in source and binary forms, with or without 4Redistribution and use in source and binary forms, with or without
5modification, are permitted provided that the following conditions 5modification, are permitted provided that the following conditions