diff --git a/ChangeLog b/ChangeLog index 80429f849f5..d69fd350c11 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +20120322 + - (djm) [contrib/ssh-copy-id contrib/ssh-copy-id.1] Updated to Phil + Hands' greatly revised version. + 20120318 - (djm) [configure.ac log.c scp.c sshconnect2.c openbsd-compat/vis.c] [openbsd-compat/vis.h] FreeBSD's strnvis isn't compatible with OpenBSD's diff --git a/contrib/ssh-copy-id b/contrib/ssh-copy-id index 9451aceec83..af18a192997 100644 --- a/contrib/ssh-copy-id +++ b/contrib/ssh-copy-id @@ -1,54 +1,293 @@ #!/bin/sh -# Shell script to install your public key on a remote machine -# Takes the remote machine name as an argument. -# Obviously, the remote machine must accept password authentication, -# or one of the other keys in your ssh-agent, for this to work. - -ID_FILE="${HOME}/.ssh/id_rsa.pub" - -if [ "-i" = "$1" ]; then - shift - # check if we have 2 parameters left, if so the first is the new ID file - if [ -n "$2" ]; then - if expr "$1" : ".*\.pub" > /dev/null ; then - ID_FILE="$1" - else - ID_FILE="$1.pub" - fi - shift # and this should leave $1 as the target name +# Copyright (c) 1999-2013 Philip Hands +# 2013 Martin Kletzander +# 2010 Adeodato =?iso-8859-1?Q?Sim=F3?= +# 2010 Eric Moret +# 2009 Xr +# 2007 Justin Pryzby +# 2004 Reini Urban +# 2003 Colin Watson +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Shell script to install your public key(s) on a remote machine +# See the ssh-copy-id(1) man page for details + +# check that we have something mildly sane as our shell, or try to find something better +if false ^ printf "%s: WARNING: ancient shell, hunting for a more modern one... " "$0" +then + SANE_SH=${SANE_SH:-/usr/bin/ksh} + if printf 'true ^ false\n' | "$SANE_SH" + then + printf "'%s' seems viable.\n" "$SANE_SH" + exec "$SANE_SH" "$0" "$@" + else + cat <<-EOF + oh dear. + + If you have a more recent shell available, that supports \$(...) etc. + please try setting the environment variable SANE_SH to the path of that + shell, and then retry running this script. If that works, please report + a bug describing your setup, and the shell you used to make it work. + + EOF + printf "%s: ERROR: Less dimwitted shell required.\n" "$0" + exit 1 fi -else - if [ x$SSH_AUTH_SOCK != x ] && ssh-add -L >/dev/null 2>&1; then - GET_ID="$GET_ID ssh-add -L" +fi + +DEFAULT_PUB_ID_FILE=$(ls -t ${HOME}/.ssh/id*.pub 2>/dev/null | grep -v -- '-cert.pub$' | head -n 1) + +usage () { + printf 'Usage: %s [-h|-?|-n] [-i [identity_file]] [-p port] [[-o ] ...] [user@]hostname\n' "$0" >&2 + exit 1 +} + +# escape any single quotes in an argument +quote() { + printf "%s\n" "$1" | sed -e "s/'/'\\\\''/g" +} + +use_id_file() { + local L_ID_FILE="$1" + + if expr "$L_ID_FILE" : ".*\.pub$" >/dev/null ; then + PUB_ID_FILE="$L_ID_FILE" + else + PUB_ID_FILE="$L_ID_FILE.pub" fi + + PRIV_ID_FILE=$(dirname "$PUB_ID_FILE")/$(basename "$PUB_ID_FILE" .pub) + + # check that the files are readable + for f in $PUB_ID_FILE $PRIV_ID_FILE ; do + ErrMSG=$( { : < $f ; } 2>&1 ) || { + printf "\n%s: ERROR: failed to open ID file '%s': %s\n\n" "$0" "$f" "$(printf "%s\n" "$ErrMSG" | sed -e 's/.*: *//')" + exit 1 + } + done + GET_ID="cat \"$PUB_ID_FILE\"" +} + +if [ -n "$SSH_AUTH_SOCK" ] && ssh-add -L >/dev/null 2>&1 ; then + GET_ID="ssh-add -L" fi -if [ -z "`eval $GET_ID`" ] && [ -r "${ID_FILE}" ] ; then - GET_ID="cat \"${ID_FILE}\"" +while test "$#" -gt 0 +do + [ "${SEEN_OPT_I}" ] && expr "$1" : "[-]i" >/dev/null && { + printf "\n%s: ERROR: -i option must not be specified more than once\n\n" "$0" + usage + } + + OPT= OPTARG= + # implement something like getopt to avoid Solaris pain + case "$1" in + -i?*|-o?*|-p?*) + OPT="$(printf -- "$1"|cut -c1-2)" + OPTARG="$(printf -- "$1"|cut -c3-)" + shift + ;; + -o|-p) + OPT="$1" + OPTARG="$2" + shift 2 + ;; + -i) + OPT="$1" + test "$#" -le 2 || expr "$2" : "[-]" >/dev/null || { + OPTARG="$2" + shift + } + shift + ;; + -n|-h|-\?) + OPT="$1" + OPTARG= + shift + ;; + --) + shift + while test "$#" -gt 0 + do + SAVEARGS="${SAVEARGS:+$SAVEARGS }'$(quote "$1")'" + shift + done + break + ;; + -*) + printf "\n%s: ERROR: invalid option (%s)\n\n" "$0" "$1" + usage + ;; + *) + SAVEARGS="${SAVEARGS:+$SAVEARGS }'$(quote "$1")'" + shift + continue + ;; + esac + + case "$OPT" in + -i) + SEEN_OPT_I="yes" + use_id_file "${OPTARG:-$DEFAULT_PUB_ID_FILE}" + ;; + -o|-p) + SSH_OPTS="${SSH_OPTS:+$SSH_OPTS }$OPT '$(quote "$OPTARG")'" + ;; + -n) + DRY_RUN=1 + ;; + -h|-\?) + usage + ;; + esac +done + +eval set -- "$SAVEARGS" + +if [ $# != 1 ] ; then + printf '%s: ERROR: Too many arguments. Expecting a target hostname, got: %s\n\n' "$0" "$SAVEARGS" >&2 + usage fi -if [ -z "`eval $GET_ID`" ]; then - echo "$0: ERROR: No identities found" >&2 - exit 1 +# drop trailing colon +USER_HOST=$(printf "%s\n" "$1" | sed 's/:$//') +# tack the hostname onto SSH_OPTS +SSH_OPTS="${SSH_OPTS:+$SSH_OPTS }'$(quote "$USER_HOST")'" +# and populate "$@" for later use (only way to get proper quoting of options) +eval set -- "$SSH_OPTS" + +if [ -z "$(eval $GET_ID)" ] && [ -r "${PUB_ID_FILE:=$DEFAULT_PUB_ID_FILE}" ] ; then + use_id_file "$PUB_ID_FILE" fi -if [ "$#" -lt 1 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then - echo "Usage: $0 [-i [identity_file]] [user@]machine" >&2 +if [ -z "$(eval $GET_ID)" ] ; then + printf '%s: ERROR: No identities found\n' "$0" >&2 exit 1 fi -# strip any trailing colon -host=`echo $1 | sed 's/:$//'` +# populate_new_ids() uses several global variables ($USER_HOST, $SSH_OPTS ...) +# and has the side effect of setting $NEW_IDS +populate_new_ids() { + local L_SUCCESS="$1" -{ eval "$GET_ID" ; } | ssh $host "umask 077; test -d ~/.ssh || mkdir ~/.ssh ; cat >> ~/.ssh/authorized_keys" || exit 1 + # repopulate "$@" inside this function + eval set -- "$SSH_OPTS" -cat <&2 + NEW_IDS=$( + eval $GET_ID | { + while read ID ; do + printf '%s\n' "$ID" > $L_TMP_ID_FILE - ~/.ssh/authorized_keys + # the next line assumes $PRIV_ID_FILE only set if using a single id file - this + # 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}" \ + -o PreferredAuthentications=publickey \ + -o IdentitiesOnly=yes "$@" exit 2>$L_TMP_ID_FILE.stderr $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 + cat >/dev/null #consume the other keys, causing loop to end + } + fi + + cat $L_TMP_ID_FILE + done + } + ) + rm -f $L_TMP_ID_FILE* && trap - EXIT TERM INT QUIT + + if expr "$NEW_IDS" : "^ERROR: " >/dev/null ; then + printf '\n%s: %s\n\n' "$0" "$NEW_IDS" >&2 + exit 1 + fi + if [ -z "$NEW_IDS" ] ; then + printf '\n%s: WARNING: All keys were skipped because they already exist on the remote system.\n\n' "$0" >&2 + exit 0 + fi + 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 +} -to make sure we haven't added extra keys that you weren't expecting. +REMOTE_VERSION=$(ssh -v -o PreferredAuthentications=',' "$@" 2>&1 | + sed -ne 's/.*remote software version //p') -EOF +case "$REMOTE_VERSION" in + NetScreen*) + populate_new_ids 1 + for KEY in $(printf "%s" "$NEW_IDS" | cut -d' ' -f2) ; do + KEY_NO=$(($KEY_NO + 1)) + printf "%s\n" "$KEY" | grep ssh-dss >/dev/null || { + 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 + 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 + ADDED=$(($ADDED + 1)) + fi + done + if [ -z "$ADDED" ] ; then + exit 1 + fi + ;; + *) + # 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 "$@" " + umask 077 ; + mkdir -p .ssh && cat >> .ssh/authorized_keys || exit 1 ; + if type restorecon >/dev/null 2>&1 ; then restorecon -F .ssh .ssh/authorized_keys ; fi" \ + || exit 1 + ADDED=$(printf '%s\n' "$NEW_IDS" | wc -l) + ;; +esac + +if [ "$DRY_RUN" ] ; then + cat <<-EOF + =-=-=-=-=-=-=-= + Would have added the following key(s): + + $NEW_IDS + =-=-=-=-=-=-=-= + EOF +else + cat <<-EOF + + Number of key(s) added: $ADDED + + Now try logging into the machine, with: "ssh $SSH_OPTS" + and check to make sure that only the key(s) you wanted were added. + + EOF +fi +# =-=-=-= diff --git a/contrib/ssh-copy-id.1 b/contrib/ssh-copy-id.1 index cb15ab24dbc..67a59e492a9 100644 --- a/contrib/ssh-copy-id.1 +++ b/contrib/ssh-copy-id.1 @@ -1,75 +1,186 @@ .ig \" -*- nroff -*- -Copyright (c) 1999 Philip Hands Computing +Copyright (c) 1999-2013 hands.com Ltd. -Permission is granted to make and distribute verbatim copies of -this manual provided the copyright notice and this permission notice -are preserved on all copies. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. -Permission is granted to copy and distribute modified versions of this -manual under the conditions for verbatim copying, provided that the -entire resulting derived work is distributed under the terms of a -permission notice identical to this one. - -Permission is granted to copy and distribute translations of this -manual into another language, under the above conditions for modified -versions, except that this permission notice may be included in -translations approved by the Free Software Foundation instead of in -the original English. +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .. -.TH SSH-COPY-ID 1 "14 November 1999" "OpenSSH" -.SH NAME -ssh-copy-id \- install your public key in a remote machine's authorized_keys -.SH SYNOPSIS -.B ssh-copy-id [-i [identity_file]] -.I "[user@]machine" +.Dd $Mdocdate: June 17 2010 $ +.Dt SSH-COPY-ID 1 +.Os +.Sh NAME +.Nm ssh-copy-id +.Nd use locally available keys to authorise logins on a remote machine +.Sh SYNOPSIS +.Nm +.Op Fl n +.Op Fl i Op Ar identity_file +.Op Fl p Ar port +.Op Fl o Ar ssh_option +.Op Ar user Ns @ Ns +.Ar hostname +.Nm +.Fl h | Fl ? .br -.SH DESCRIPTION -.BR ssh-copy-id -is a script that uses ssh to log into a remote machine and -append the indicated identity file to that machine's -.B ~/.ssh/authorized_keys -file. -.PP -If the -.B -i -option is given then the identity file (defaults to -.BR ~/.ssh/id_rsa.pub ) -is used, regardless of whether there are any keys in your -.BR ssh-agent . -Otherwise, if this: -.PP -.B " ssh-add -L" -.PP -provides any output, it uses that in preference to the identity file. -.PP -If the -.B -i -option is used, or the -.B ssh-add -produced no output, then it uses the contents of the identity -file. Once it has one or more fingerprints (by whatever means) it -uses ssh to append them to -.B ~/.ssh/authorized_keys -on the remote machine (creating the file, and directory, if necessary.) - -.SH NOTES -This program does not modify the permissions of any -pre-existing files or directories. Therefore, if the remote -.B sshd -has -.B StrictModes -set in its -configuration, then the user's home, -.B ~/.ssh -folder, and -.B ~/.ssh/authorized_keys -file may need to have group writability disabled manually, e.g. via - -.B " chmod go-w ~ ~/.ssh ~/.ssh/authorized_keys" - -on the remote machine. - -.SH "SEE ALSO" -.BR ssh (1), -.BR ssh-agent (1), -.BR sshd (8) +.Sh DESCRIPTION +.Nm +is a script that uses +.Xr ssh 1 +to log into a remote machine (presumably using a login password, +so password authentication should be enabled, unless you've done some +clever use of multiple identities). It assembles a list of one or more +fingerprints (as described below) and tries to log in with each key, to +see if any of them are already installed (of course, if you are not using +.Xr ssh-agent 1 +this may result in you being repeatedly prompted for pass-phrases). +It then assembles a list of those that failed to log in, and using ssh, +enables logins with those keys on the remote server. By default it adds +the keys by appending them to the remote user's +.Pa ~/.ssh/authorized_keys +(creating the file, and directory, if necessary). It is also capable +of detecting if the remote system is a NetScreen, and using its +.Ql set ssh pka-dsa key ... +command instead. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl i Ar identity_file +Use only the key(s) contained in +.Ar identity_file +(rather than looking for identities via +.Xr ssh-add 1 +or in the +.Ic default_ID_file ) . +If the filename does not end in +.Pa .pub +this is added. If the filename is omitted, the +.Ic default_ID_file +is used. +.Pp +Note that this can be used to ensure that the keys copied have the +comment one prefers and/or extra options applied, by ensuring that the +key file has these set as preferred before the copy is attempted. +.It Fl n +do a dry-run. Instead of installing keys on the remote system simply +prints the key(s) that would have been installed. +.It Fl h , Fl ? +Print Usage summary +.It Fl p Ar port , Fl o Ar ssh_option +These two options are simply passed through untouched, along with their +argument, to allow one to set the port or other +.Xr ssh 1 +options, respectively. +.Pp +Rather than specifying these as command line options, it is often better to use (per-host) settings in +.Xr ssh 1 Ns 's +configuration file: +.Xr ssh_config 5 . +.El +.Pp +Default behaviour without +.Fl i , +is to check if +.Ql ssh-add -L +provides any output, and if so those keys are used. Note that this results in +the comment on the key being the filename that was given to +.Xr ssh-add 1 +when the key was loaded into your +.Xr ssh-agent 1 +rather than the comment contained in that file, which is a bit of a shame. +Otherwise, if +.Xr ssh-add 1 +provides no keys contents of the +.Ic default_ID_file +will be used. +.Pp +The +.Ic default_ID_file +is the most recent file that matches: +.Pa ~/.ssh/id*.pub , +(excluding those that match +.Pa ~/.ssh/*-cert.pub ) +so if you create a key that is not the one you want +.Nm +to use, just use +.Xr touch 1 +on your preferred key's +.Pa .pub +file to reinstate it as the most recent. +.Pp +.Sh EXAMPLES +If you have already installed keys from one system on a lot of remote +hosts, and you then create a new key, on a new client machine, say, +it can be difficult to keep track of which systems on which you've +installed the new key. One way of dealing with this is to load both +the new key and old key(s) into your +.Xr ssh-agent 1 . +Load the new key first, without the +.Fl c +option, then load one or more old keys into the agent, possibly by +ssh-ing to the client machine that has that old key, using the +.Fl A +option to allow agent forwarding: +.Pp +.D1 user@newclient$ ssh-add +.D1 user@newclient$ ssh -A old.client +.D1 user@oldl$ ssh-add -c +.D1 No ... prompt for pass-phrase ... +.D1 user@old$ logoff +.D1 user@newclient$ ssh someserver +.Pp +now, if the new key is installed on the server, you'll be allowed in +unprompted, whereas if you only have the old key(s) enabled, you'll be +asked for confirmation, which is your cue to log back out and run +.Pp +.D1 user@newclient$ ssh-copy-id -i someserver +.Pp +The reason you might want to specify the -i option in this case is to +ensure that the comment on the installed key is the one from the +.Pa .pub +file, rather than just the filename that was loaded into you agent. +It also ensures that only the id you intended is installed, rather than +all the keys that you have in your +.Xr ssh-agent 1 . +Of course, you can specify another id, or use the contents of the +.Xr ssh-agent 1 +as you prefer. +.Pp +Having mentioned +.Xr ssh-add 1 Ns 's +.Fl c +option, you might consider using this whenever using agent forwarding +to avoid your key being hijacked, but it is much better to instead use +.Xr ssh 1 Ns 's +.Ar ProxyCommand +and +.Fl W +option, +to bounce through remote servers while always doing direct end-to-end +authentication. This way the middle hop(s) don't get access to your +.Xr ssh-agent 1 . +A web search for +.Ql ssh proxycommand nc +should prove enlightening (N.B. the modern approach is to use the +.Fl W +option, rather than +.Xr nc 1 ) . +.Sh "SEE ALSO" +.Xr ssh 1 , +.Xr ssh-agent 1 , +.Xr sshd 8