# ssh(1) completion

have ssh &&
{

_ssh_bindaddress()
{
    COMPREPLY=( "${COMPREPLY[@]}" $( compgen -W \
        "$( PATH="$PATH:/sbin" ifconfig -a | \
        sed -ne 's/.*addr:\([^[:space:]]*\).*/\1/p' \
            -ne 's/.*inet[[:space:]]\{1,\}\([^[:space:]]*\).*/\1/p' )" \
        -- "$cur" ) )
}

_ssh_ciphers()
{
    COMPREPLY=( "${COMPREPLY[@]}" $( compgen -W '3des-cbc aes128-cbc \
        aes192-cbc aes256-cbc aes128-ctr aes192-ctr aes256-ctr arcfour128 \
        arcfour256 arcfour blowfish-cbc cast128-cbc' -- "$cur" ) )
}

_ssh_macs()
{
    COMPREPLY=( "${COMPREPLY[@]}" $( compgen -W 'hmac-md5 hmac-sha1 \
        umac-64@openssh.com hmac-ripemd160 hmac-sha1-96 hmac-md5-96' \
        -- "$cur" ) )
}

_ssh_options()
{
    type compopt &>/dev/null && compopt -o nospace
    COMPREPLY=( $( compgen -S = -W 'AddressFamily BatchMode BindAddress \
        ChallengeResponseAuthentication CheckHostIP Cipher Ciphers \
        ClearAllForwardings Compression CompressionLevel ConnectionAttempts \
        ConnectTimeout ControlMaster ControlPath DynamicForward EscapeChar \
        ExitOnForwardFailure ForwardAgent ForwardX11 ForwardX11Trusted \
        GatewayPorts GlobalKnownHostsFile GSSAPIAuthentication \
        GSSAPIDelegateCredentials HashKnownHosts Host HostbasedAuthentication \
        HostKeyAlgorithms HostKeyAlias HostName IdentityFile IdentitiesOnly \
        KbdInteractiveDevices LocalCommand LocalForward LogLevel MACs \
        NoHostAuthenticationForLocalhost NumberOfPasswordPrompts \
        PasswordAuthentication PermitLocalCommand Port \
        PreferredAuthentications Protocol ProxyCommand PubkeyAuthentication \
        RekeyLimit RemoteForward RhostsRSAAuthentication RSAAuthentication \
        SendEnv ServerAliveInterval ServerAliveCountMax SmartcardDevice \
        StrictHostKeyChecking TCPKeepAlive Tunnel TunnelDevice \
        UsePrivilegedPort User UserKnownHostsFile VerifyHostKeyDNS \
        VisualHostKey XAuthLocation' -- "$cur" ) )
}

# Complete a ssh suboption (like ForwardAgent=y<tab>)
# Only one parameter: the string to complete including the equal sign.
# Not all suboptions are completed.
# Doesn't handle comma-separated lists.
_ssh_suboption()
{
    # Split into subopt and subval
    local prev=${1%%=*} cur=${1#*=}

    case $prev in
        BatchMode|ChallengeResponseAuthentication|CheckHostIP|\
        ClearAllForwardings|Compression|ExitOnForwardFailure|ForwardAgent|\
        ForwardX11|ForwardX11Trusted|GatewayPorts|GSSAPIAuthentication|\
        GSSAPIKeyExchange|GSSAPIDelegateCredentials|GSSAPITrustDns|\
        HashKnownHosts|HostbasedAuthentication|IdentitiesOnly|\
        KbdInteractiveAuthentication|KbdInteractiveDevices|\
        NoHostAuthenticationForLocalhost|PasswordAuthentication|\
        PubkeyAuthentication|RhostsRSAAuthentication|RSAAuthentication|\
        StrictHostKeyChecking|TCPKeepAlive|UsePrivilegedPort|\
        VerifyHostKeyDNS|VisualHostKey)
            COMPREPLY=( $( compgen -W 'yes no' -- "$cur") )
            ;;
        AddressFamily)
            COMPREPLY=( $( compgen -W 'any inet inet6' -- "$cur" ) )
            ;;
        BindAddress)
            _ssh_bindaddress
            ;;
        Cipher)
            COMPREPLY=( $( compgen -W 'blowfish des 3des' -- "$cur" ) )
            ;;
        Protocol)
            COMPREPLY=( $( compgen -W '1 2 1,2 2,1' -- "$cur" ) )
            ;;
        Tunnel)
            COMPREPLY=( $( compgen -W 'yes no point-to-point ethernet' \
                    -- "$cur" ) )
            ;;
        PreferredAuthentications)
            COMPREPLY=( $( compgen -W 'gssapi-with-mic host-based \
                    publickey keyboard-interactive password' -- "$cur" ) )
            ;;
        MACs)
            _ssh_macs
            ;;
        Ciphers)
            _ssh_ciphers
            ;;
    esac
    return 0
}

# Try to complete -o SubOptions=
#
# Returns 0 if the completion was handled or non-zero otherwise.
_ssh_suboption_check()
{
    # Get prev and cur words without splitting on =
    local cureq=`_get_cword :=` preveq=`_get_pword :=`
    if [[ $cureq == *=* && $preveq == -o ]]; then
        _ssh_suboption $cureq
        return $?
    fi
    return 1
}

_ssh()
{
    local cur prev configfile
    local -a config

    COMPREPLY=()
    _get_comp_words_by_ref -n : cur prev
    #cur=`_get_cword :`
    #prev=`_get_pword`

    _ssh_suboption_check && return 0

    case $prev in
        -F|-i|-S)
            _filedir
            return 0
            ;;
        -c)
            _ssh_ciphers
            return 0
            ;;
        -m)
            _ssh_macs
            return 0
            ;;
        -l)
            COMPREPLY=( $( compgen -u -- "$cur" ) )
            return 0
            ;;
        -o)
            _ssh_options
            return 0
            ;;
        -w)
            _available_interfaces
            return 0
            ;;
        -b)
            _ssh_bindaddress
            return 0
            ;;
    esac

    if [[ "$cur" == -F* ]]; then
        cur=${cur#-F}
        _filedir
        # Prefix completions with '-F'
        COMPREPLY=( "${COMPREPLY[@]/#/-F}" )
        cur=-F$cur  # Restore cur
    elif [[ "$cur" == -* ]]; then
        COMPREPLY=( $( compgen -W '-1 -2 -4 -6 -A -a -C -f -g -K -k -M \
            -N -n -q -s -T -t -V -v -X -v -Y -y -b -b -c -D -e -F \
            -i -L -l -m -O -o -p -R -S -w' -- "$cur" ) )
    else
        # Search COMP_WORDS for '-F configfile' or '-Fconfigfile' argument
        set -- "${COMP_WORDS[@]}"
        while [ $# -gt 0 ]; do
            if [ "${1:0:2}" = -F ]; then
                if [ ${#1} -gt 2 ]; then
                    configfile="$(dequote "${1:2}")"
                else
                    shift
                    [ "$1" ] && configfile="$(dequote "$1")"
                fi
                break
            fi
            shift
        done
        _known_hosts_real -a -F "$configfile" "$cur"
        if [ $COMP_CWORD -ne 1 ]; then
            _compopt_o_filenames
            COMPREPLY=( "${COMPREPLY[@]}" $( compgen -c -- "$cur" ) )
        fi
    fi

    return 0
}
shopt -u hostcomplete && complete -F _ssh ssh slogin autossh

# sftp(1) completion
#
_sftp()
{
    local cur prev configfile

    COMPREPLY=()
    _get_comp_words_by_ref cur prev

    _ssh_suboption_check && return 0

    case $prev in
        -b|-F|-P)
            _filedir
            return 0
            ;;
        -o)
            _ssh_options
            return 0
            ;;
    esac

    if [[ "$cur" == -F* ]]; then
        cur=${cur#-F}
        _filedir
        # Prefix completions with '-F'
        COMPREPLY=( "${COMPREPLY[@]/#/-F}" )
        cur=-F$cur  # Restore cur
    elif [[ "$cur" == -* ]]; then
        COMPREPLY=( $( compgen -W '-1 -C -v -B -b -F -o -P -R -S -s' \
            -- "$cur" ) )
    else
        # Search COMP_WORDS for '-F configfile' argument
        set -- "${COMP_WORDS[@]}"
        while [ $# -gt 0 ]; do
            if [ "${1:0:2}" = -F ]; then
                if [ ${#1} -gt 2 ]; then
                    configfile="$(dequote "${1:2}")"
                else
                    shift
                    [ "$1" ] && configfile="$(dequote "$1")"
                fi
                break
            fi
            shift
        done
        _known_hosts_real -a -F "$configfile" "$cur"
    fi

    return 0
}
shopt -u hostcomplete && complete -F _sftp sftp

# things we want to backslash escape in scp paths
_scp_path_esc='[][(){}<>",:;^&!$=?`|\\'"'"'[:space:]]'

# Complete remote files with ssh.  If the first arg is -d, complete on dirs
# only.  Returns paths escaped with three backslashes.
_scp_remote_files()
{
    local IFS=$'\n'

    # remove backslash escape from the first colon
    cur=${cur/\\:/:}

    local userhost=${cur%%?(\\):*}
    local path=${cur#*:}

    # unescape (3 backslashes to 1 for chars we escaped)
    path=$( sed -e 's/\\\\\\\('$_scp_path_esc'\)/\\\1/g' <<<"$path" )

    # default to home dir of specified user on remote host
    if [ -z "$path" ]; then
        path=$(ssh -o 'Batchmode yes' $userhost pwd 2>/dev/null)
    fi

    local files
    if [ "$1" = -d ] ; then
        # escape problematic characters; remove non-dirs
        files=$( ssh -o 'Batchmode yes' $userhost \
            command ls -aF1d "$path*" 2>/dev/null | \
            sed -e 's/'$_scp_path_esc'/\\\\\\&/g' -e '/[^\/]$/d' )
    else
        # escape problematic characters; remove executables, aliases, pipes
        # and sockets; add space at end of file names
        files=$( ssh -o 'Batchmode yes' $userhost \
            command ls -aF1d "$path*" 2>/dev/null | \
            sed -e 's/'$_scp_path_esc'/\\\\\\&/g' -e 's/[*@|=]$//g' \
            -e 's/[^\/]$/& /g' )
    fi
    COMPREPLY=( "${COMPREPLY[@]}" $files )
}

# This approach is used instead of _filedir to get a space appended
# after local file/dir completions, and -o nospace retained for others.
# If first arg is -d, complete on directory names only.  The next arg is
# an optional prefix to add to returned completions.
_scp_local_files()
{
    local IFS=$'\n'

    local dirsonly=false
    if [ "$1" = -d ]; then
        dirsonly=true
        shift
    fi

    if $dirsonly ; then
        COMPREPLY=( "${COMPREPLY[@]}" $( command ls -aF1d $cur* 2>/dev/null | \
            sed -e "s/$_scp_path_esc/\\\\&/g" -e '/[^\/]$/d' -e "s/^/$1/") )
    else
        COMPREPLY=( "${COMPREPLY[@]}" $( command ls -aF1d $cur* 2>/dev/null | \
            sed -e "s/$_scp_path_esc/\\\\&/g" -e 's/[*@|=]$//g' \
            -e 's/[^\/]$/& /g' -e "s/^/$1/") )
    fi
}

# scp(1) completion
#
_scp()
{
    local configfile cur prev prefix

    COMPREPLY=()
    _get_comp_words_by_ref -n : cur prev

    _ssh_suboption_check && {
        COMPREPLY=( "${COMPREPLY[@]/%/ }" )
        return 0
    }

    case $prev in
        -l|-P)
            return 0
            ;;
        -F|-i|-S)
            _filedir
            type compopt &>/dev/null && compopt +o nospace
            return 0
            ;;
        -c)
            _ssh_ciphers
            COMPREPLY=( "${COMPREPLY[@]/%/ }" )
            return 0
            ;;
        -o)
            _ssh_options
            return 0
            ;;
    esac

    _expand || return 0

    if [[ "$cur" == *:* ]]; then
        _scp_remote_files
        return 0
    fi

    if [[ "$cur" == -F* ]]; then
        cur=${cur#-F}
        prefix=-F
    else
        # Search COMP_WORDS for '-F configfile' or '-Fconfigfile' argument
        set -- "${COMP_WORDS[@]}"
        while [ $# -gt 0 ]; do
            if [ "${1:0:2}" = -F ]; then
                if [ ${#1} -gt 2 ]; then
                    configfile="$(dequote "${1:2}")"
                else
                    shift
                    [ "$1" ] && configfile="$(dequote "$1")"
                fi
                break
            fi
            shift
        done

        case $cur in
            -*)
                COMPREPLY=( $( compgen -W '-1 -2 -4 -6 -B -C -c -F -i -l -o \
                    -P -p -q -r -S -v' -- "$cur" ) )
                COMPREPLY=( "${COMPREPLY[@]/%/ }" )
                return 0
                ;;
            */*)
                # pass through
                ;;
            *)
                _known_hosts_real -c -a -F "$configfile" "$cur"
                ;;
        esac
    fi

    _scp_local_files "$prefix"

    return 0
}
complete -F _scp -o nospace scp

# ssh-copy-id(1) completion
#
_ssh_copy_id()
{
    local cur prev

    COMPREPLY=()
    _get_comp_words_by_ref cur prev

    case $prev in
        -i)
            _filedir
            return 0
            ;;
    esac

    if [[ "$cur" == -* ]]; then
        COMPREPLY=( $( compgen -W '-i' -- "$cur" ) )
    else
        _known_hosts_real -a "$cur"
    fi

    return 0
}
complete -F _ssh_copy_id ssh-copy-id
}

# Local variables:
# mode: shell-script
# sh-basic-offset: 4
# sh-indent-comment: t
# indent-tabs-mode: nil
# End:
# ex: ts=4 sw=4 et filetype=sh
