summaryrefslogtreecommitdiff
path: root/contrib/ssh-copy-id
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/ssh-copy-id')
-rw-r--r--contrib/ssh-copy-id158
1 files changed, 80 insertions, 78 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 ;;