Command Not Found Handler to Quickly SSH to Hosts

I believe that part of being a good Systems Administrator is being lazy - that lazier you are, the more you’ve automated.

Out with the old

In the spirit of laziness, many years ago I wrote a little bash function to enable me to SSH to hosts simply by typing or pasting their hostname/IP address as though they were commands. Here’s the original function:

function updatehostsandaliases() {
unset   HOSTFILE
HOSTFILE="~/.hostfile"
rotate='|/-\'
for host in `sed -e 's/[, ].*//' ~/.ssh/known_hosts ; cat ~/.hostfile`; do
  # check if it already exists and skip
  type ${host} > /dev/null 2>&1 && continue
  alias ${host}="ssh ${host}"

  # Remove domainnames
  domain=${host%%.*}
  if [ "${domain}" != "${host}" ]; then
    if ! type ${domain} > /dev/null 2>&1; then
      alias ${domain}="ssh ${host}"
    fi
    if ! type scp${domain} > /dev/null 2>&1; then
      eval "function scp${host}() { scp \${1} ${host}:\${2:-/tmp}; }"
    fi
  fi
  type scp${host} > /dev/null 2>&1 && continue
  eval "function scp${host}() { scp \${1} ${host}:/tmp; }"
done && echo aliases rebuilt
}

The function defined aliases in the form of alias <hostname>="ssh <hostname>" and this worked quite well save for one annoying shortcoming - it only worked for hosts I’d been to before (taken from ~/.ssh.known_hosts) or which I had manually added to ~/.hostfile. Any new hosts would simply give command not found.

In with the new

In search of a more elegant solution, I stumbled across a Gist by Wijnand Modderman-Lenstra which makes use of bash’s and zsh’s command not found handlers.

Since bash version 4, if a function named command_not_found_handle is defined, this function gets called for any instances of command not found. The same goes for zsh except it’s suffixed handler instead of handle.

Wijnand’s solution is much nicer than my clunky old function as it works for any host, whether you’ve been to it before or not.

I’ve taken his idea and made some modifications, namely

  • to only attempt SSH if there was only 1 argument (if there was more than one argument, you probably fat-fingered a command rather than wanted to SSH somewhere.)
  • to check if the host is already in ~/.ssh/known_hosts (that’s a good indication it’s safe to SSH to.)
  • if it isn’t an IP address but looks like a valid domain name, prompt for confirmation before performing a DNS lookup (in order to avoid leaking any potentially sensitive data in the lookup.)
  • test if SSH is reachable with ssh-keygen first
  • if all SSH tests fail, switch to using thefuck to work out what I meant
Important

If you use thefuck in a command not found handler, you ==must== enable confirmation or you may end up running something destructive like rm without being prompted first! Set require_confirmation = True in ~/.thefuck/settings.pyto turn confirmation on.

# Modifications based on https://gist.github.com/tehmaze/1080039
# Original license follows:
#                         _______
#   ____________ _______ _\__   /_________       ___  _____
#  |    _   _   \   _   |   ____\   _    /      |   |/  _  \
#  |    /   /   /   /   |  |     |  /___/   _   |   |   /  /
#  |___/___/   /___/____|________|___   |  |_|  |___|_____/
#          \__/                     |___|
#
#
# Put this in your ~/.zshrc or ~/.bashrc:
#   source ~/.zsh/ssh
#
# (c) 2011 Wijnand Modderman-Lenstra <[email protected]>
# MIT License
#

zmodload zsh/pcre

is_valid_ip() {
    IP="$1"
    python - "${IP}" <<EOF
import socket
import sys
# Check IPv6
if socket.has_ipv6:
    try:
        socket.inet_aton(sys.argv[1])
        sys.exit(0)
    except socket.error:
        pass
# Check IPv4
try:
    socket.inet_aton(sys.argv[1])
    sys.exit(0)
except socket.error:
    sys.exit(1)
EOF
    return $?
}

try_connect_ssh() {
    # If only one argument is supplied, try to
    # connect there using ssh
    if [[ $# -eq 1 ]]; then
        # Have we SSHd here before or does it look like an IP
        if (grep -q ${1} ~/.ssh/known_hosts) || (is_valid_ip "$1") ; then
            SAFE=true
        fi
	# Haven't seen it before and it doesn't look like an IP
	# Does it look like a valid domain name?
        if [ -z "${SAFE}" -a "${1}" -pcre-match "(?=^.{4,253}$)(^(?:[a-zA-Z](?:(?:[a-zA-Z0-9\-]){0,61}[a-zA-Z])?\.)+[a-
zA-Z]{2,}$)" ]; then
            # Prompt for confirmation so we don't leak data in DNS queries
            read -k1 -q "CONFIRM?Do you want to try to SSH to ${1}?"
            if [ ${CONFIRM} = "y" ] ; then
                # If it ain't an IP, is it a hostname?
                if (host -W 2 $1 2>/dev/null | grep -v NXDOMAIN >/dev/null) ; then
                    RESOLVES="$1"
                fi
            fi
        fi
        # It was either already known, an IP or a hostname, test for SSH and connect if present
        if [ $? -eq 0 -a -n "${SAFE}" -o -n "${RESOLVES}" ]; then
            ssh-keyscan -T 1 "$@" 2>/dev/null | grep "$@" >/dev/null 2>&1 && ssh "$@" && return 0 || return 1
        fi
    fi
    # Bork out, we couldn't handle the command
    return 1
}

case "${SHELL}" in
    */zsh)
        command_not_found_handler() {
            try_connect_ssh "$@" || TF_CMD=$(TF_ALIAS=fuck PYTHONIOENCODING=utf-8 TF_SHELL_ALIASES=$(alias) thefuck "$@") && eval $TF_CMD && print -s $TF_CMD
            return $?
        } 
    ;;
    */bash)
        command_not_found_handle() {
            try_connect_ssh "$@"
            return $?
        }
    ;;
esac

Seeing it in action