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@.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
#!/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 :
#!/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 :
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 :
_______________________________________________ openssh-unix-dev mailing list openssh-unix-dev@xxxxxxxxxxx https://lists.mindrot.org/mailman/listinfo/openssh-unix-dev