#!/bin/bash

# Copyright © 2025 Jean-Jacques Brucker (u4=sRyUhEbNU5OwyLEjfSwaXAe_42.17-002.76) <jjbrucker@foopgp.org>
# Copyright © 2026 Mnème (u5=001777236237.945e_43.30_005.38) <mneme@foopgp.org>
# SPDX-License-Identifier: GPL-3.0-only

helpmsg="Usage: $BASH_SOURCE [OPTIONS] [SSH_OPTIONS] destination [SSH_OPTIONS] [command [argument ...]]

SSH With GnuPG: ssh wrapper that dynamically forwards
the gpg-agent (gpg + ssh sockets).

This wrapper makes first an extra ssh connection to get server's gpg-agent
socket paths, then opens the real connection with two RemoteForward entries:
  - agent-socket       ← client's agent-extra-socket (restricted, sandbox)
  - agent-ssh-socket   ← client's agent-ssh-socket   (ssh-auth via OpenPGP)

The ssh-socket forwarding lets the server use the client's OpenPGP authentication
key (typically on a smartcard) for outbound ssh — enabling multi-hop chains
between djibian machines without ever copying private keys.

All options passed AFTER the destination (server) are ignored by the first extra
connection.

Options:
      --no-gpg             do not forward the gpg-agent socket (no remote
                           sign/decrypt — useful when the remote is only
                           used as an ssh bastion).
      --no-ssh             do not forward the ssh-agent socket (no outbound
                           ssh from the remote — minimizes exposure when
                           only remote sign/decrypt is needed).
      --dry-run            don't execute ssh, just show what would be done.
      --help               show this help and exit
      --version            show versions and exit

See ssh(1) for SSH_OPTIONS, and sshwgpg(1) for server configuration and examples.
"

set -euo pipefail

version="1.1.0"
args=()
dest=""
dryrun=""
fwd_gpg=1
fwd_ssh=1

# usage: ssh [-46AaCfGgKkMNnqsTtVvXxYy] [-B bind_interface] [-b bind_address]
#            [-c cipher_spec] [-D [bind_address:]port] [-E log_file]
#            [-e escape_char] [-F configfile] [-I pkcs11] [-i identity_file]
#            [-J destination] [-L address] [-l login_name] [-m mac_spec]
#            [-O ctl_cmd] [-o option] [-P tag] [-p port] [-R address]
#            [-S ctl_path] [-W host:port] [-w local_tun[:remote_tun]]
#            destination [command [argument ...]]
#        ssh [-Q query_option]
# Search for destination
for ((;$#;)) ; do
	if [[ "$1" =~ ^-[BbcDEeFIiJLlmOoPpRSWw]$ ]] ; then
		# Option has a value
		args+=("$1")
		shift
		args+=("$1")
	elif [[ "$1" =~ ^-[46AaCfGgKkMNnqsTtVvXxYy]$ ]] ; then
		# Option has no value
		args+=("$1")
	elif [[ "$1" =~ ^-Q$ ]] ; then
		# local query
		printf "%s: Error: For ssh queries (-Q), please use ssh directly.\n" "$BASH_SOURCE" >&2
		exit 1
	elif [[ "$1" =~ ^--$ ]] ; then
		# This marks the end of options, next arg is the destination (which could be start with a '-' (insane !))
		args+=("$1")
		shift
		dest="$1"
		break
	elif [[ "$1" =~ ^--help$ ]] ; then
		echo "$helpmsg"
		exit 0
	elif [[ "$1" =~ ^--version$ ]] ; then
		echo "$BASH_SOURCE $version"
		ssh -V
		exit 0
	elif [[ "$1" =~ ^--dry-run$ ]] ; then
		dryrun="echo"
	elif [[ "$1" =~ ^--no-gpg$ ]] ; then
		fwd_gpg=0
	elif [[ "$1" =~ ^--no-ssh$ ]] ; then
		fwd_ssh=0
	elif [[ "$1" =~ ^- ]] ; then
		printf "%s: Warning: assuming no value is following unknown option -- %s\n" "$BASH_SOURCE" "$1" >&2
		args+=("$1")
	else
		dest="$1"
        break
	fi
	shift
done


# Build the list of `-o RemoteForward …` options to pass to the final ssh.
# Only query the remote for the paths we actually need; skip the preparatory
# connection entirely if both forwardings have been disabled.
ssh_opts=()

if ((fwd_gpg || fwd_ssh)) ; then
	probe_cmd=""
	((fwd_gpg)) && probe_cmd+="gpgconf --list-dir agent-socket ; "
	((fwd_ssh)) && probe_cmd+="gpgconf --list-dir agent-ssh-socket ; "
	remote_paths=$(ssh "${args[@]}" -- "$dest" "$probe_cmd") || {
		printf "%s: Error: preparatory ssh to %s failed.\n" "$BASH_SOURCE" "$dest" >&2
		exit 1
	}
	if ((fwd_gpg && fwd_ssh)) ; then
		remote_gpg_socket=${remote_paths%$'\n'*}
		remote_ssh_socket=${remote_paths#*$'\n'}
	elif ((fwd_gpg)) ; then
		remote_gpg_socket=$remote_paths
	else
		remote_ssh_socket=$remote_paths
	fi

	if ((fwd_gpg)) ; then
		local_gpg_socket=$(gpgconf --list-dir agent-extra-socket)
		ssh_opts+=(-o "RemoteForward $remote_gpg_socket $local_gpg_socket")
	fi
	if ((fwd_ssh)) ; then
		local_ssh_socket=$(gpgconf --list-dir agent-ssh-socket)
		ssh_opts+=(-o "RemoteForward $remote_ssh_socket $local_ssh_socket")
	fi
else
	printf "%s: Warning: both --no-gpg and --no-ssh given; nothing to forward.\n" "$BASH_SOURCE" >&2
fi

if [[ "$dryrun" ]] ; then
	echo ssh "${ssh_opts[@]@Q}" "${args[@]}" "$@"
else
	ssh "${ssh_opts[@]}" "${args[@]}" "$@"
	exit $?
fi

