effect scp -O (use legacy SCP procotol) per host in .ssh/config?
Phil Pennock
phil.pennock at globnix.org
Thu Jun 23 10:07:01 AEST 2022
On 2022-06-22 at 10:48 +0200, Jochen Bern wrote:
> Now if only we could get the hostname(s) without entirely duplicating scp's
> command line parsing ....... ?
I have this, because I wrap both ssh and scp to be able to easily
redirect the agent socket based on a profile, to separate work from
personal keys and "code" from "prod". Some stuff is configured via
direnv setting of the right env var, and some stuff by a sourced config
file which can set override variables. It's handy but very
personal-setup-specific.
On the other hand, when trying to strip this down to the core parts
right now I decided I was too likely to introduce bugs. So you know
what? Have two scripts which grew a bit, will make people scream, and
let you keep all the broken fragments when it all goes horribly wrong.
For the "set the -O on some hosts" purpose you should ignore the
SSH_PDP_ROLE bits; that's for integration into my ssh-role script which
manages the systemd launch from a template ssh-agent-pdp at .service to
coordinate things. It shouldn't be needed. That stuff ... grew
organically. I'm not including that script or the .service file.
I don't expect anyone to use these as-is, but ... it's all been debugged
over a few years and has the worst rough edges worn smooth with only a
little bit of my blood on them, so it's not a _horrible_ starting point.
There's probably quite a few places left where there are bugs but this
has worked enough to let me work day-to-day without usually noticing
that there's a wrapper in the way.
-Phil
-------------- next part --------------
#!/usr/bin/env -S zsh -feu
set -eu
# Our override env vars:
# * SSH_PDP_COMMAND: ssh command or pathname to use
# * SSH_PDP_BYPASS: if set and non-empty, then ignore all other logic except SSH_PDP_COMMAND
# * SSH_PDP_ROLE: per-role SSH agent support; identifier
# * SSH_PDP_ROLEFILE: location of non-default rolefile of keys
# * SSH_PDP_GOTO: used by my goto system, disables escape char
# * SSH_PDP_TRACE: used for some extra debug info
# * SSH_PDP_SKIP_TITLE: set non-empty to inhibit terminal manipulations
# Our extra config files:
# * ~/.ssh/role.<agentname> (or $SSH_PDP_ROLEFILE): ssh-keys to load
# * ~/.ssh/agent-keyuse-confirm: existence prompts -c for ssh-add, same as ssh-add-today
# * ~/etc/site/ssh.hosts-mapping: sourced, can change terminal profile names etc; also set the Role
progname="${0:A:t}"
warn() { printf >&2 '%s: %s\n' "$progname" "$*"; }
die() { warn "$@"; exit 1; }
have_cmd() { (( $+commands[$1] )) }
readonly DEFAULT_KEY_DURATION='20h'
# Finding the actual comand to call.
#
case "${SSH_PDP_COMMAND:-ssh}" in
/*) command="${SSH_PDP_COMMAND:-ssh}" ;;
*) # minimize diff, leave this block unindented
saved_PATH="$PATH"
typeset -gU path
# We bias the result, for platforms where we need to keep system bins in path
# first but we want a newer SSH
path=( /opt/openssh/bin /opt/local/bin /usr/local/opt/openssh/bin /usr/local/bin "${path[@]}" )
# We drop our own directory from path; -U makes it unique, only need to drop once
path[(r)${0:h}]=()
#
command="$commands[${SSH_PDP_COMMAND:-ssh}]"
# We might use other utilities of our own below
PATH="$saved_PATH"
;;
esac
if [[ -n "${SSH_PDP_BYPASS:-}" ]]; then
exec "$command" "$@"
fi
# Per-role dedicated SSH Agent support, so can have keys per role
# We define the functions here, but we only call them after sourcing the user
# mapping file, so that roles can be set per-hostname.
#
set_agent_socket_systemd() {
if [[ "$SSH_PDP_ROLE" == "default" ]]; then
want_socket="$XDG_RUNTIME_DIR/pdp.ssh/agent.systemd"
export SSH_AUTH_SOCK="$want_socket"
return
fi
want_socket="$XDG_RUNTIME_DIR/pdp.ssh/agent-${SSH_PDP_ROLE}.systemd"
if [[ ! -S "$want_socket" ]]; then
warn "Asking systemd to start ${(q-)SSH_PDP_ROLE} socket"
systemctl --user start "ssh-agent-pdp@$SSH_PDP_ROLE"
[[ -S "$want_socket" ]] || die "missing ${(q-)want_socket} after systemd user start"
fi
export SSH_AUTH_SOCK="$want_socket"
}
set_agent_socket() {
[[ -n "${SSH_PDP_ROLE:-}" ]] || return 0
case "$SSH_PDP_ROLE" in
(*/* | *$'\0'* ) die "malformed SSH_PDP_ROLE" ;;
esac
if [[ -n "${XDG_RUNTIME_DIR:-}" ]]; then
if have_cmd systemctl; then
set_agent_socket_systemd
return $?
fi
warn "XDG-using platform, no systemctl"
else
warn "non-XDG platform"
fi
warn "unable to setup per-role ssh-agent for ${(q-)SSH_PDP_ROLE} on this platform"
return 1
}
load_per_agent_keys() {
[[ -n "${SSH_PDP_ROLE:-}" ]] || return 0
[[ "$SSH_PDP_ROLE" != "default" ]] || return 0
if ssh-add -l >/dev/null 2>&1; then return 0; fi
local rolefile="${SSH_PDP_ROLEFILE:-$HOME/.ssh/role.$SSH_PDP_ROLE}"
if [[ ! -f "$rolefile" ]]; then
warn "Missing ${(q-)rolefile}, not auto-loading keys for ${(q-)SSH_PDP_ROLE}"
return 1
fi
local -a key_files load_params
load_params=(-t "$DEFAULT_KEY_DURATION")
if [[ -e "$HOME/.ssh/agent-keyuse-confirm" ]]; then
load_params+=(-c)
fi
local spec keydir
keydir="${rolefile:A:h}"
while read -r line; do
case "${line:-empty}" in
(empty) ;;
(\#*) ;;
(*)
spec="${line%%\#*}"
spec="${spec%[[:space:]]##}"
spec="${spec#[[:space:]]##}"
# match relevant entries from ssh_config(5) TOKENS
spec="${spec//%d/$HOME}"
spec="${spec//%u/$USERNAME}"
spec="${spec//%%/%}"
if [[ "$spec" == */* ]]; then
spec="${spec/#~\//$HOME/}"
spec="${spec/#.\//$keydir/}"
fi
key_files+=( "$spec" )
;;
esac
done < "$rolefile"
(( ${#key_files} )) || die "parsed no keys from ${(q-)rolefile}"
ssh-add "${load_params[@]}" "${key_files[@]}" || die "ssh-add failed"
}
# We want $color_variant to be available as a Light/Dark switch for other stuff to auto-use
case "${COLORFGBG:-}" in
( 7\;0 | white\;black )
reset_color_scheme='Dark Background'
color_variant=Dark
;;
( 0\;7 | black\;white )
reset_color_scheme='Light Background'
color_variant=Light
;;
(*)
reset_color_scheme='Dark Background'
color_variant=Dark
;;
esac
# tab_coloring is for window tabs; color_schemes is for preset color schemes
typeset -A tab_coloring color_schemes
tab_coloring[example]=0:0:255
color_schemes[example]=Molokai
typeset -a ssh_invoke
ssh_invoke=( "$command" )
declare -i invoke_ssh_command_index=1
if [[ -n "${SSH_PDP_GOTO:-}" ]]; then
ssh_invoke+=( -e none )
fi
if [[ $OSTYPE == darwin* ]]
then
if (( ${${OSTYPE#darwin}%.*} >= 12.0 ))
then
ssh_invoke=(caffeinate -s "${ssh_invoke[@]}")
invoke_ssh_command_index+=2
fi
# Keep this here, commented out, because the need _will_ return: {{{
# Although: now should probably do this in the hosts-mapping by setting ssh_want_keepalive?
#case $(wifi_ssid) in
#(a|b|c-*|d*)
# ssh_invoke+=(-o ServerAliveInterval=120)
# ;;
#esac
# }}}
# Locking screen, too often no keys in agent
if ! ssh-add -l > /dev/null 2>&1
then
print -u 2 "No SSH keys loaded into agent ..."
ssh-add-today || exit 1
fi
fi
if [[ -z "${TZ:-}" && -L /etc/localtime ]]
then
zmodload -F zsh/stat b:zstat
local h
zstat -H h -L /etc/localtime
export TZ=${h[link]##*/zoneinfo/}
fi
if [[ -n "${ITERM_SESSION_ID:-}" ]]; then
# Beware: `Disable potentially insecure escape sequences` advanced setting (On by default) inhibits this
terminal_profile() { printf >/dev/tty '\e]1337;SetProfile=%s\a' "${1:-Default}" ; }
# Ripped from my iterm_tabcolor_rgb shell function
tab_color() {
case ${1:-.} in
(off|disable|reset|clear) printf '\e]6;1;bg;*;default\a' > /dev/tty
return ;;
esac
(( red = ${1:?need a red 0-255 decimal} ))
(( green = ${2:?need a green 0-255 decimal} ))
(( blue = ${3:?need a blue 0-255 decimal} ))
printf '\e]6;1;bg;red;brightness;%d\a\e]6;1;bg;green;brightness;%d\a\e]6;1;bg;blue;brightness;%d\a' $red $green $blue > /dev/tty
}
terminal_color_scheme() {
printf '\e]1337;SetColors=preset=%s\a' "$1" > /dev/tty
}
set_tab_title() { print >/dev/tty -n -- "\e]1;$*\a"; }
else
terminal_profile() { : ; }
tab_color() { : ; }
terminal_color_scheme() { : ; }
set_tab_title() { print >/dev/tty -n -- "\e]2;$*\a"; }
fi
# Saving the current window title is not portable, especially when OSC
# retrieval injects into keyboard stream so gets disabled as a security
# measure.
# So we just have set_tab_title().
# But while iTerm uses the icon title for the bit I want set, gnome-terminal
# ignores that and only displays the window title in tabs. I could use 0 for
# both, but I have a vague recollection that on macOS, I didn't like the
# result, so instead just switch based on iTerm above.
# This is the variable set by lookup logic to say we're in an Interesting
# environment and the display should be mutated.
remote_env=''
# and this one for just "set the title but no other mutation"
remote_set_title=false
# this allows the title to be overridden, if desired
remote_title=''
# this/these add to ssh_invoke
ssh_want_keepalive='' # number for interval, or just 'true'
# used to not set title when "ssh host <something>" by default, but perhaps we
# want that anyway, in which case override this.
ssh_set_title_even_if_remote_cmd=false
# This is used to let the config file override the ssh command.
# Should probably _not_ do so if SSH_PDP_COMMAND is set.
# This supports the idea of dispatching to an insecure ssh which still speaks
# some bad crypto variant, or a bug-compatible client, or a special binary with
# PQ support if testing that out with a particular host.
force_ssh_command=''
# We don't consume getopts, so don't shift by OPTIND, we just want to find the remote hostname
# We also find the remote_user, so ssh mapping can augment decisions about which role might be needed
# Last confirmed up-to-date: OpenSSH 8.6p1
remote_user=''
remote_hostname=''
remote_port=''
hostname_at_argc=-1
hostname_argv_prefix=''
hostname_argv_suffix=''
verbose=false
# This getopts string comes from the getopt() invocation in ssh.c, last updated 2022-04-17
while getopts '1246ab:c:e:fgi:kl:m:no:p:qstvxAB:CD:E:F:GI:J:KL:MNO:PQ:R:S:TVw:W:XYy' arg; do
case $arg in
(l)
remote_user="$OPTARG"
;;
(o)
case ${(L)OPTARG} in
(hostname=*)
hostname_at_argc=$((OPTIND-1))
remote_hostname="${OPTARG#*=}"
hostname_argv_prefix="${OPTARG%%=*}="
;;
(user=*)
remote_user="${OPTARG#*=}"
;;
esac
;;
(p)
remote_port="$OPTARG"
;;
(v)
verbose=true
;;
esac
done
if [[ -n "${SSH_PDP_TRACE:-}" ]] || $verbose; then
msg() { warn "$@"; }
else
msg() { true; }
fi
# We might need to mutate the hostname, whether because of our punycode
# normalization or because of the mapping file switching things. So we
# record any user@ prefix needed, together with the index at which to mutate.
if [[ -z "$remote_hostname" && $# -ge $OPTIND ]]; then
hostname_at_argc="$OPTIND"
d="${argv[$OPTIND]}"
OPTIND=$((OPTIND+1))
case "$d" in
ssh://*)
if [[ "$d" =~ '^ssh://([^@]+@)?([^:]+)(:[0-9]+)?$' ]]; then
remote_hostname="${match[2]}"
[[ -n "$remote_user" ]] || remote_user="${match[1]%@}"
[[ -n "$remote_port" ]] || remote_port="${match[3]#:}"
hostname_argv_prefix="ssh://${match[1]}"
hostname_argv_suffix="${match[3]}"
else
warn "unable to parse SSH URL to extract information"
fi
;;
*@*)
remote_hostname="${d#*@}"
hostname_argv_prefix="${d%%@*}@"
if [[ -z "$remote_user" ]]; then
remote_user="${d%%@*}"
fi
;;
*)
remote_hostname="$d"
;;
esac
msg "remote_hostname=${(q-)remote_hostname} remote_user=${(q-)remote_user} remote_port=${(q-)remote_port}"
unset d
fi
supplied_remote_hostname="$remote_hostname"
if (( OPTIND > $# )); then
# no arguments left, we're probably interactive
have_remote_cmd=false
first_remote_argc='-1'
else
have_remote_cmd=true
first_remote_argc="$OPTIND"
fi
if [[ "$remote_hostname" == *[^[:ascii:]]* ]]; then
if (( ${+commands[character]} )); then
remote_hostname="$(character x-puny -o "$remote_hostname")"
else
warn "non-ASCII hostname ${(q)remote_hostname} seen, need a punycode converter"
fi
fi
# Does case switching to set remote_env and possibly change ssh_invoke.
# SHOULD NOT change ssh_invoke, and if it does and prepends, then
# invoke_ssh_command_index had better be incremented too or chaos will result.
# *STRONGLY* prefer to add new intent vars to communicate intended behavioral
# change instead of directly manipulating.
#
# Might change remote_hostname. Can set/override SSH_PDP_ROLE.
# Likely augments tab_coloring/color_schemes too.
# Have $color_variant available to help with Light/Dark
if [[ -f "$HOME/etc/site/ssh.hosts-mapping" ]]; then
source "$HOME/etc/site/ssh.hosts-mapping"
fi
if [[ -n "${ssh_want_keepalive:-}" ]]; then
if [[ "$ssh_want_keepalive" =~ '^[0-9]+$' ]]; then # beware, not PCRE; POSIX ERE
ssh_invoke+=(-o "ServerAliveInterval=$ssh_want_keepalive")
else
ssh_invoke+=(-o ServerAliveInterval=120)
fi
fi
if [[ -n "${force_ssh_command:-}" ]]; then
ssh_invoke[$invoke_ssh_command_index]="$force_ssh_command"
fi
# We keep this after sourcing ssh.hosts-mapping so that SSH_PDP_ROLE can be set.
if set_agent_socket; then
# false means no keyfile; I think best to continue on, for ad-hoc roles
load_per_agent_keys || true
else
warn "continuing with unmodified ssh-agent"
fi
reset_all() {
terminal_profile ITERM_PROFILE
[[ -z ${tab_coloring[$remote_env]:-} ]] || tab_color reset
[[ -z ${color_schemes[$remote_env]:-} ]] || terminal_color_scheme "$reset_color_scheme"
set_tab_title "$HOST"
}
reset_title() { set_tab_title "$HOST"; }
if [[ "$remote_hostname" != "$supplied_remote_hostname" ]]; then
if (( $hostname_at_argc < 0 )); then
warn "need to massage hostname, don't know where, skipping"
else
repl="${hostname_argv_prefix}${remote_hostname}${hostname_argv_suffix}"
argv[hostname_at_argc]=("$repl")
fi
fi
if [[ -n "${SSH_PDP_TRACE:-}" ]] || $verbose; then
warn "hostname=${(q-)remote_hostname} original=${(q-)supplied_remote_hostname} env=${(q-)remote_env} role=${(q-)SSH_PDP_ROLE:-} title=${(q-)remote_title} SSH_AUTH_SOCK=${(q-)SSH_AUTH_SOCK:-}"
warn "${(@q+)ssh_invoke[*]} ${(@q+)*}"
if [[ "$remote_hostname" == "does.not.exist" ]]; then warn "not invoking real ssh"; exit 0; fi
fi
just_exec=false
if [[ -n "${SSH_PDP_SKIP_TITLE:-}" ]]; then
just_exec=true
elif $have_remote_cmd && ! $ssh_set_title_even_if_remote_cmd; then
just_exec=true
elif [[ -n "${remote_env:-}" ]]; then
set +e
terminal_profile "$remote_env"
[[ -z ${tab_coloring[$remote_env]:-} ]] || tab_color ${(s,:,)tab_coloring[$remote_env]}
[[ -z ${color_schemes[$remote_env]:-} ]] || terminal_color_scheme "${color_schemes[$remote_env]}"
if [[ -n "${remote_title:-}" ]]; then
set_tab_title "${remote_title}"
else
set_tab_title "${remote_env}: ${remote_hostname}"
fi
resetter=reset_all
elif $remote_set_title; then
case "$TERM" in
xterm*)
set_tab_title "${remote_title:-$remote_hostname}"
resetter=reset_title
;;
*)
just_exec=true
;;
esac
else
just_exec=true
fi
if $just_exec; then
exec "${ssh_invoke[@]}" "$@"
fi
trap "$resetter" INT
"${ssh_invoke[@]}" "$@"
ev=$?
"$resetter"
exit $ev
# vim: set ft=zsh :
-------------- next part --------------
#!/usr/bin/env -S zsh -feu
set -eu
# Our override env vars:
# * SSH_PDP_COMMAND: ssh command or pathname to use (NOT scp)
# * SSH_PDP_SCP_COMMAND: scp command or pathname to use
# * SSH_PDP_BYPASS: if set and non-empty, then ignore all other logic except SSH_PDP_SCP_COMMAND
# * SSH_PDP_ROLE: per-role SSH agent support; identifier
# * SSH_PDP_ROLEFILE: location of non-default rolefile of keys
# * SSH_PDP_TRACE: used for some extra debug info
# Our extra config files:
# * ~/.ssh/role.<agentname> (or $SSH_PDP_ROLEFILE): ssh-keys to load
# * ~/.ssh/agent-keyuse-confirm: existence prompts -c for ssh-add, same as ssh-add-today
# * ~/etc/site/ssh.hosts-mapping: sourced, can change terminal profile names etc; also set the Role
progname="${0:A:t}"
progsuffix=".wrap"
warn() { printf >&2 '%s%s: %s\n' "$progname" "$progsuffix" "$*"; }
die() { warn "$@"; exit 1; }
have_cmd() { (( $+commands[$1] )) }
readonly DEFAULT_KEY_DURATION='20h'
# Finding the actual comand to call.
#
### case "${SSH_PDP_COMMAND:-ssh}" in
### /*) command="${SSH_PDP_COMMAND:-ssh}" ;;
case "${SSH_PDP_SCP_COMMAND:-scp}" in
/*)
command="${SSH_PDP_SCP_COMMAND:-scp}"
if [[ -z "${SSH_PDP_COMMAND:-}" ]]; then
next_ssh_command="${command%/scp}/ssh"
else
next_ssh_command="${SSH_PDP_COMMAND:?}"
if [[ "$next_ssh_command" != /* ]]; then
die "if SSH_PDP_SCP_COMMAND starts with / then SSH_PDP_COMMAND, if defined, must too"
# laziness, don't want to rework the flow for the next section to handle resolution past us
fi
fi
;;
*)
saved_PATH="$PATH"
typeset -gU path
# We bias the result, for platforms where we need to keep system bins in path
# first but we want a newer SSH
path=( /opt/openssh/bin /opt/local/bin /usr/local/opt/openssh/bin /usr/local/bin "${path[@]}" )
# We drop our own directory from path; -U makes it unique, only need to drop once
path[(r)${0:h}]=()
#
command="$commands[${SSH_PDP_SCP_COMMAND:-scp}]"
next_ssh_command="$commands[${SSH_PDP_COMMAND:-ssh}]"
# We might use other utilities of our own below
PATH="$saved_PATH"
;;
esac
if [[ -n "${SSH_PDP_BYPASS:-}" ]]; then
exec "$command" "$@"
fi
# Per-role dedicated SSH Agent support, so can have keys per role
# We define the functions here, but we only call them after sourcing the user
# mapping file, so that roles can be set per-hostname.
#
set_agent_socket_systemd() {
if [[ "$SSH_PDP_ROLE" == "default" ]]; then
want_socket="$XDG_RUNTIME_DIR/pdp.ssh/agent.systemd"
export SSH_AUTH_SOCK="$want_socket"
return
fi
want_socket="$XDG_RUNTIME_DIR/pdp.ssh/agent-${SSH_PDP_ROLE}.systemd"
if [[ ! -S "$want_socket" ]]; then
warn "Asking systemd to start ${(q-)SSH_PDP_ROLE} socket"
systemctl --user start "ssh-agent-pdp@$SSH_PDP_ROLE"
[[ -S "$want_socket" ]] || die "missing ${(q-)want_socket} after systemd user start"
fi
export SSH_AUTH_SOCK="$want_socket"
}
set_agent_socket() {
[[ -n "${SSH_PDP_ROLE:-}" ]] || return 0
case "$SSH_PDP_ROLE" in
(*/* | *$'\0'* ) die "malformed SSH_PDP_ROLE" ;;
esac
if [[ -n "${XDG_RUNTIME_DIR:-}" ]]; then
if have_cmd systemctl; then
set_agent_socket_systemd
return $?
fi
warn "XDG-using platform, no systemctl"
else
warn "non-XDG platform"
fi
warn "unable to setup per-role ssh-agent for ${(q-)SSH_PDP_ROLE} on this platform"
return 1
}
check_have_per_agent_keys() {
ssh-add -l >/dev/null 2>&1
}
typeset -a scp_invoke
scp_invoke=( "$command" )
if [[ $OSTYPE == darwin* ]]
then
if (( ${${OSTYPE#darwin}%.*} >= 12.0 ))
then
scp_invoke=(caffeinate -s "${scp_invoke[@]}")
fi
fi
# Preserve API for loaded config file, even though not used for scp, only ssh
terminal_profile() { : ; }
tab_color() { : ; }
terminal_color_scheme() { : ; }
set_tab_title() { : ; }
# This is the variable set by lookup logic to say we're in an Interesting
# environment and the display should be mutated.
remote_env=''
# and this one for just "set the title but no other mutation"
remote_set_title=false
# this/these add to scp_invoke
ssh_want_keepalive='' # number for interval, or just 'true'
# used to not set title when "ssh host <something>" by default, but perhaps we
# want that anyway, in which case override this.
ssh_set_title_even_if_remote_cmd=false
# For setting -O on a host-by-host basis
scp_want_old_rcp=false
# We don't consume getopts, so don't shift by OPTIND, we just want to find the remote hostname
# We also find the remote_user, so ssh mapping can augment decisions about which role might be needed
# Last confirmed up-to-date: OpenSSH 8.6p1
remote_user=''
remote_hostname=''
remote_port=''
ssh_at_argc=-1
ssh_command=''
hostname_at_argc=-1
hostname_argv_prefix=''
hostname_argv_suffix=''
port_at_argc=-1
verbose=false
cmdline_requested_old_scp=false
server_mode=false
# NB: scp invokes on the _remote_ side with options not in manual-page;
# we might be on the server side too, so need to support them.
# These server options are: -d -f -t (targetshouldbedirectory, from, to)
# Also existing but undocumented (so pass through): -1, -2 (protocol version)
#
# There's also M: but that appears to be a cruft bug:
# added: 197e29f1cca190d767c4b2b63a662f9a9e5da0b3 2021-08-02
# removed: 391ca67fb978252c48d20c910553f803f988bd37 2021-08-10
# (was replaced with -O/-s); do not support that.
# Updated: 2022-04-17 for OpenSSH 9.0 (and scp/sftp changes)
# nb: this is copy/paste from scp source, for completeness, not ordered by me
if [[ "$#" -eq 1 && "$1" == "--version" ]]; then
printf >&2 'scp.wrap: '
"$next_ssh_command" -V
exit 0
fi
while getopts '12346ABCTdfOpqRrstvD:F:J:M:P:S:c:i:l:o:' arg; do
case $arg in
(S)
ssh_at_argc=$((OPTIND-1))
ssh_command="$OPTARG"
;;
(o)
case ${(L)OPTARG} in
(hostname=*)
hostname_at_argc=$((OPTIND-1))
remote_hostname="${OPTARG#*=}"
hostname_argv_prefix="${OPTARG%%=*}="
;;
(user=*)
remote_user="${OPTARG#*=}"
;;
esac
;;
(O)
cmdline_requested_old_scp=true
;;
(P)
remote_port="$OPTARG"
port_at_argc=$((OPTIND-1))
;;
(v)
verbose=true
# scp takes -v too, this is pass-through.
;;
(f)
progsuffix+="/svr-from"
server_mode=true
;;
(t)
progsuffix+="/svr-to"
server_mode=true
;;
esac
done
if [[ -n "${SSH_PDP_TRACE:-}" ]] || $verbose; then
msg() { warn "$@"; }
else
msg() { true; }
fi
# We might need to mutate the hostname, whether because of our punycode
# normalization or because of the mapping file switching things. So we
# record any user@ prefix needed, together with the index at which to mutate.
# For two-remotes invocations, the mutated hostname will always be the first
# (we're not really designed to handle that, this logic was written for ssh
# not scp).
if [[ -z "$remote_hostname" && $# -ge $((OPTIND + 1)) ]]; then
first_host_index="$OPTIND"
if [[ ! "${argv[$first_host_index]}" == *:* ]]; then
# Not "the second after the options", but "the last"
# eg: scp local1 local2 local3 remote:/target/dir/
first_host_index=$#
if [[ ! "${argv[$first_host_index]}" == *:* ]]; then
warn "no colon found at argv $OPTIND or $#, scp will copy locally?"
first_host_index=''
fi
fi
fi
if [[ -n "${first_host_index:-}" ]]; then
hostname_at_argc="$first_host_index"
d="${argv[$first_host_index]}"
case "$d" in
scp://*)
if [[ "$d" =~ '^scp://([^@]+@)?([^:/]+)(:[0-9]+)?(/.*)?$' ]]; then
remote_hostname="${match[2]}"
[[ -n "$remote_user" ]] || remote_user="${match[1]%@}"
[[ -n "$remote_port" ]] || remote_port="${match[3]#:}"
hostname_argv_prefix="ssh://${match[1]}"
hostname_argv_suffix="${match[3]}${match[4]}"
else
warn "unable to parse SCP URL to extract information"
fi
;;
*@*:*)
remote_hostname="${d#*@}"
hostname_argv_prefix="${d%%@*}@"
hostname_argv_suffix=":${remote_hostname#*:}"
remote_hostname="${remote_hostname%%:*}"
if [[ -z "$remote_user" ]]; then
remote_user="${d%%@*}"
fi
;;
*:*)
remote_hostname="${d%%:*}"
hostname_argv_suffix=":${d#*:}"
;;
*)
msg "unhandled remote hostname candidate: ${(q-)d}"
;;
esac
msg "remote_hostname=${(q-)remote_hostname} remote_user=${(q-)remote_user} remote_port=${(q-)remote_port}"
unset d
fi
supplied_remote_hostname="$remote_hostname"
supplied_remote_port="$remote_port"
if [[ "$remote_hostname" == *[^[:ascii:]]* ]]; then
if (( ${+commands[character]} )); then
remote_hostname="$(character x-puny -o "$remote_hostname")"
else
warn "non-ASCII hostname ${(q)remote_hostname} seen, need a punycode converter"
fi
fi
supplied_ssh_command="$ssh_command"
force_ssh_command=''
# Does case switching to set remote_env and possibly change scp_invoke.
# Might change remote_hostname. Can set/override SSH_PDP_ROLE.
# Can change ssh_command to force use of a historical-cryptosuite-supporting variant.
# force_ssh_command can be set, to override our default of bypassing our
# wrappers for the scp invocation of ssh.
if $server_mode; then
: # we do not source a config file as a server
elif [[ -f "$HOME/etc/site/ssh.hosts-mapping" ]]; then
source "$HOME/etc/site/ssh.hosts-mapping"
fi
if [[ -n "${ssh_want_keepalive:-}" ]]; then
if [[ "$ssh_want_keepalive" =~ '^[0-9]+$' ]]; then # beware, not PCRE; POSIX ERE
scp_invoke+=(-o "ServerAliveInterval=$ssh_want_keepalive")
else
scp_invoke+=(-o ServerAliveInterval=120)
fi
fi
# We keep this after sourcing ssh.hosts-mapping so that SSH_PDP_ROLE can be set.
if $server_mode; then
: # no keys to load as an scp server
elif set_agent_socket; then
check_have_per_agent_keys || warn "no keys loaded for target SSH role"
else
warn "continuing with unmodified ssh-agent"
fi
if [[ "$remote_hostname" != "$supplied_remote_hostname" ]]; then
if (( $hostname_at_argc < 0 )); then
warn "need to massage hostname, don't know where, skipping"
else
repl="${hostname_argv_prefix}${remote_hostname}${hostname_argv_suffix}"
argv[hostname_at_argc]=("$repl")
fi
fi
# This doesn't handle port being derived from anything other than -P.
if [[ "$remote_port" != "$supplied_remote_port" ]]; then
msg "port remapped ${(q-)supplied_remote_port} -> ${(q-)remote_port}"
if (( $port_at_argc < 0 )); then
scp_invoke+=( -P "$remote_port" )
else
argc[port_at_argc]=("$remote_port")
fi
fi
if $scp_want_old_rcp && ! $cmdline_requested_old_scp; then
scp_invoke+=(-O)
fi
if [[ -n "$force_ssh_command" ]]; then
ssh_command="$force_ssh_command"
elif [[ "$ssh_command" == "$supplied_ssh_command" ]]; then
# This should be the normal case.
# The scp layer will, in this self script, have overridden the
# environment variables as appropriate to talk to the correct socket.
# There's no need to redo all the logic again in the ssh layer.
#
# Except, scp by default will look at argv[0] to figure out the path
# for ssh, so we don't need to do this _unless_ there's an explicit
# instance in argv and it's unqualified.
# But if we explicitly set 'ssh.pdp' then ... don't?
#
# Aargh, this is a chaotic mess, I'd rip out the ssh-command tracking
# as a bad idea except that I just know that I will need the ability to
# override for some ancient crap client only speaking an insecure
# ciphersuite, sooner or later.
# So ... only do it if it's "ssh".
#
# After all, if it were only for optimization, we could get 90% of the
# way there by exporting SSH_PDP_BYPASS=from-scp into environ, so that
# ssh.pdp would early-exec.
if (( $ssh_at_argc > 0 )) && [[ "$ssh_command" == ssh ]]; then
ssh_command="$next_ssh_command"
fi
fi
if [[ "$ssh_command" != "$supplied_ssh_command" ]]; then
if (( $ssh_at_argc < 0 )); then
scp_invoke+=(-S "$ssh_command")
else
argv[ssh_at_argc]="$ssh_command"
fi
fi
if [[ -n "${SSH_PDP_TRACE:-}" ]] || $verbose; then
warn "hostname=${(q-)remote_hostname} original=${(q-)supplied_remote_hostname} env=${(q-)remote_env} role=${(q-)SSH_PDP_ROLE:-} SSH_AUTH_SOCK=${(q-)SSH_AUTH_SOCK:-}"
warn "${(@q+)scp_invoke[*]} ${(@q+)*}"
if [[ "$remote_hostname" == "does.not.exist" ]]; then warn "not invoking real scp"; exit 0; fi
fi
# If we do call back into ssh.pdp or ourselves at some point, then as long as
# SSH_PDP_BYPASS is not passed to a remote system (it shouldn't be), this will
# short-circuit the already-done work.
export SSH_PDP_BYPASS='from-scp'
exec "${scp_invoke[@]}" "$@"
# vim: set ft=zsh :
-------------- next part --------------
if ! [ -n "${remote_hostname:-}" ]; then
# ssh without a remote probably means "ssh -V" or other query.
# we should avoid interfering; there's nothing else for us to do here though.
return 0
fi
prior_role="${SSH_PDP_ROLE:-}"
case "${remote_hostname%.lan}" in
forge-*) SSH_PDP_ROLE=foo-prod ;;
bastion1) SSH_PDP_ROLE=foo-prod ;;
# For now, assume that anything AWS is for Foo
*.amazonaws.com) SSH_PDP_ROLE=foo-prod ;;
oldhost) scp_want_old_rcp=true ;;
debug-local) remote_hostname='localhost'; remote_port=26; scp_want_old_rcp=true ;;
esac
if [ "$remote_port" = '2222' ] && [ "$remote_user" = 'special' ]; then
msg "foo matched"
SSH_PDP_ROLE=foo-prod
fi
if [ -n "${GIT_PROTOCOL:-}" ] && [ -n "${GIT_EXEC_PATH:-}" ]; then
# We are being called as ssh inside git
remote_set_title=false
remote_env=''
fi
# Other stuff I might check here in a non-trimmed version:
# [ -n "${PACKER_RUN_UUID:-}" ]
# [ "${GIT_REFLOG_ACTION:-}" = "" ]
# sourced by zsh but try to keep it sh-friendly
# vim: set ft=sh sw=2 et :
More information about the openssh-unix-dev
mailing list