diff options
Diffstat (limited to 'contrib')
-rw-r--r-- | contrib/ssh-copy-id | 158 | ||||
-rw-r--r-- | contrib/ssh-copy-id.1 | 2 |
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 |
37 | if false ^ printf "%s: WARNING: ancient shell, hunting for a more modern one... " "$0" | 40 | if false ^ printf "%s: WARNING: ancient shell, hunting for a more modern one... " "$0" |
38 | then | 41 | then |
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 |
57 | fi | 60 | fi |
58 | 61 | ||
59 | most_recent_id="$(cd "$HOME" ; ls -t .ssh/id*.pub 2>/dev/null | grep -v -- '-cert.pub$' | head -n 1)" | 62 | # shellcheck disable=SC2010 |
60 | DEFAULT_PUB_ID_FILE="${most_recent_id:+$HOME/}$most_recent_id" | 63 | DEFAULT_PUB_ID_FILE=$(ls -t "${HOME}"/.ssh/id*.pub 2>/dev/null | grep -v -- '-cert.pub$' | head -n 1) |
61 | 64 | ||
62 | usage () { | 65 | usage () { |
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 |
71 | quote() { | 74 | quote() { |
72 | printf "%s\n" "$1" | sed -e "s/'/'\\\\''/g" | 75 | printf '%s\n' "$1" | sed -e "s/'/'\\\\''/g" |
73 | } | 76 | } |
74 | 77 | ||
75 | use_id_file() { | 78 | use_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" |
106 | fi | 109 | fi |
107 | 110 | ||
108 | while test "$#" -gt 0 | 111 | while getopts "i:o:p:F:fnh?" OPT |
109 | do | 112 | do |
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 |
179 | done | 136 | done |
137 | #shift all args to keep only USER_HOST | ||
138 | shift $((OPTIND-1)) | ||
139 | |||
140 | |||
180 | 141 | ||
181 | eval set -- "$SAVEARGS" | ||
182 | 142 | ||
183 | if [ $# = 0 ] ; then | 143 | if [ $# = 0 ] ; then |
184 | usage | 144 | usage |
@@ -189,16 +149,18 @@ if [ $# != 1 ] ; then | |||
189 | fi | 149 | fi |
190 | 150 | ||
191 | # drop trailing colon | 151 | # drop trailing colon |
192 | USER_HOST=$(printf "%s\n" "$1" | sed 's/:$//') | 152 | USER_HOST="$*" |
193 | # tack the hostname onto SSH_OPTS | 153 | # tack the hostname onto SSH_OPTS |
194 | SSH_OPTS="${SSH_OPTS:+$SSH_OPTS }'$(quote "$USER_HOST")'" | 154 | SSH_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) |
196 | eval set -- "$SSH_OPTS" | 156 | eval set -- "$SSH_OPTS" |
197 | 157 | ||
158 | # shellcheck disable=SC2086 | ||
198 | if [ -z "$(eval $GET_ID)" ] && [ -r "${PUB_ID_FILE:=$DEFAULT_PUB_ID_FILE}" ] ; then | 159 | if [ -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" |
200 | fi | 161 | fi |
201 | 162 | ||
163 | # shellcheck disable=SC2086 | ||
202 | if [ -z "$(eval $GET_ID)" ] ; then | 164 | if [ -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 | |||
209 | populate_new_ids() { | 171 | populate_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 | ||
239 | installkeys_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 | ||
260 | EOF | ||
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 | |||
270 | REMOTE_VERSION=$(ssh -v -o PreferredAuthentications=',' -o ControlPath=none "$@" 2>&1 | | 266 | REMOTE_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 | ||
273 | case "$REMOTE_VERSION" in | 270 | case "$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 -*- |
2 | Copyright (c) 1999-2013 hands.com Ltd. <http://hands.com/> | 2 | Copyright (c) 1999-2016 hands.com Ltd. <http://hands.com/> |
3 | 3 | ||
4 | Redistribution and use in source and binary forms, with or without | 4 | Redistribution and use in source and binary forms, with or without |
5 | modification, are permitted provided that the following conditions | 5 | modification, are permitted provided that the following conditions |