├── .travis.yml ├── LICENSE ├── README.md ├── ssh-find-agent.sh └── test └── test.sh /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | language: bash 3 | script: 4 | - bash test/test.sh 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2011 Wayne Walker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ssh-find-agent 2 | 3 | ssh-find-agent is a tool for locating existing ssh compatible agent processes (e.g., ssh-agent, gpg-agent, gnome-keyring, osx-keychain); and, optionally, setting `SSH_AUTH_SOCK` accordingly. 4 | 5 | ## Build Status 6 | 7 | [![Build Status](https://travis-ci.org/wwalker/ssh-find-agent.svg?branch=master)](https://travis-ci.org/wwalker/ssh-find-agent) 8 | 9 | 10 | ## Usage 11 | 12 | Somewhere in shell initialization (`~/.bashrc` or `~./.zshrc`) 13 | 14 | ```bash 15 | source ssh-find-agent.sh # for bash 16 | emulate ksh -c "source ssh-find-agent.sh" # for zsh 17 | ``` 18 | 19 | Add the following to automatically choose the first agent 20 | ```bash 21 | ssh-add -l >&/dev/null || ssh-find-agent -a || eval $(ssh-agent) > /dev/null 22 | ``` 23 | 24 | To choose the agent manually run 25 | ```bash 26 | ssh-find-agent -c 27 | ``` 28 | 29 | NOTE: The choose option is Useful when you actually want multiple agents forwarded. E.g., while pairing. 30 | 31 | To list the agents run 32 | ```bash 33 | ssh-find-agent -l 34 | ``` 35 | 36 | This will return a list of export commands that can be used to set the socket. 37 | 38 | Should this output be executed it will set the socket to the last agent found. 39 | ```bash 40 | eval $(ssh-find-agent -l) 41 | ``` 42 | 43 | ## Status 44 | 45 | ## Alternatives 46 | 47 | * [keychain](https://github.com/funtoo/keychain) 48 | * [envoy](https://github.com/vodik/envoy) 49 | -------------------------------------------------------------------------------- /ssh-find-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright (C) 2011 by Wayne Walker 4 | # 5 | # Released under one of the versions of the MIT License. 6 | # 7 | # Copyright (C) 2011 by Wayne Walker 8 | # 9 | # Permission is hereby granted, free of charge, to any person obtaining a copy 10 | # of this software and associated documentation files (the "Software"), to deal 11 | # in the Software without restriction, including without limitation the rights 12 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | # copies of the Software, and to permit persons to whom the Software is 14 | # furnished to do so, subject to the following conditions: 15 | # 16 | # The above copyright notice and this permission notice shall be included in 17 | # all copies or substantial portions of the Software. 18 | # 19 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 | # THE SOFTWARE. 26 | 27 | sfa_init() { 28 | _ssh_agent_sockets=() 29 | _live_agent_list=() 30 | _live_agent_sock_list=() 31 | _sorted_live_agent_list=() 32 | _sfa_timeout=1.0 33 | _sfa_no_timeout_command=0 34 | 35 | # Set $sfa_path array to the dirs to search for ssh-agent sockets 36 | sfa_set_path 37 | 38 | if ! command -v 'timeout' &>/dev/null; then 39 | printf "ssh-find-agent.sh: 'timeout' command could not be found.\n" 40 | printf " Please install 'coreutils' via your system's package manager.\n" 41 | printf "Meanwhile, we will run without \`timeout\` support.\n" 42 | printf " This may cause delays or complete hangs if the agent is slow or unresponsive.\n" 43 | _sfa_no_timeout_command=1 44 | fi 45 | } 46 | 47 | # Allow users to override the default path to search for ssh-agent sockets 48 | # The first of the variable found is used to set the path: 49 | # SSH_FIND_AGENT_PATH (colon separated dir list) 50 | # _TMPDIR_OVERRIDE for legacy compatibility 51 | # TMPDIR (if set) (plus /tmp due to ssh bug) 52 | sfa_set_path() { 53 | sfa_path=() 54 | if [[ -n "$SSH_FIND_AGENT_PATH" ]]; then 55 | IFS=':' read -r -a sfa_path <<<"$SSH_FIND_AGENT_PATH" 56 | else 57 | # Maintain backwards compatibility with the old _TMPDIR_OVERRIDE variable 58 | if [[ -n "$_TMPDIR_OVERRIDE" ]]; then 59 | sfa_path=("$_TMPDIR_OVERRIDE") 60 | else 61 | # Add ~/.ssh/agent to support newer OpenSSH versions 62 | # See: https://github.com/openssh/openssh-portable/commit/80162f9d7e7eadca4ffd0bd1c015d38cb1821ab6 63 | local ssh_agent_dir="${HOME}/.ssh/agent" 64 | if [[ -n "$TMPDIR" ]]; then 65 | sfa_path=("/tmp" "$TMPDIR" "$ssh_agent_dir") 66 | else 67 | sfa_path=("/tmp" "$ssh_agent_dir") 68 | fi 69 | fi 70 | fi 71 | } 72 | 73 | sfa_err() { 74 | # shellcheck disable=SC2059 75 | printf "$@" 1>&2 76 | } 77 | 78 | sfa_debug() { 79 | if ((_DEBUG > 0)); then 80 | sfa_err "$@" 1>&2 81 | fi 82 | } 83 | 84 | sfa_find_all_agent_sockets() { 85 | _ssh_agent_sockets=($( 86 | find "${sfa_path[@]}" -maxdepth 2 -type s -name agent.\* \ 87 | -o -name S.gpg-agent.ssh -o -name ssh -o -name 's.*.agent.*' \ 88 | -o -regex '.*/ssh-.*/agent..*$' \ 89 | 2>/dev/null | grep -E \ 90 | '/ssh-.*/agent.*|/gpg- .*/S.gpg-agent.ssh|/keyring-.*/ssh$|.*/ssh-.*/agent..*$|s\..*\.agent\..*' 91 | )) 92 | 93 | sfa_debug "${_ssh_agent_sockets[@]}" 94 | } 95 | 96 | sfa_test_agent_socket() { 97 | local socket=$1 98 | local output 99 | 100 | if [[ _sfa_no_timeout_command -eq 1 ]]; then 101 | output=$(SSH_AUTH_SOCK=$socket sh-add -l 2>&1) 102 | else 103 | output=$(SSH_AUTH_SOCK=$socket timeout "$_sfa_timeout" ssh-add -l 2>&1) 104 | fi 105 | result=$? 106 | 107 | [[ "$output" == "error fetching identities: communication with agent failed" ]] && result=2 108 | sfa_debug $result 109 | 110 | case $result in 111 | 0 | 1 | 141) 112 | # contactible and has keys loaded 113 | { 114 | OIFS="$IFS" 115 | IFS=$'\n' 116 | # shellcheck disable=SC2207 117 | _keys=($(SSH_AUTH_SOCK=$socket ssh-add -l 2>/dev/null)) 118 | IFS="$OIFS" 119 | } 120 | _live_agent_list+=("${#_keys[@]}:$socket") 121 | return 0 122 | ;; 123 | 2 | 124) 124 | # socket is dead, delete it 125 | sfa_err 'socket (%s) is dead, removing it.\n' "$socket" 126 | sfa_debug "rm -rf ${socket%/*}" 127 | rm -rf "${socket%/*}" 128 | ;; 129 | 125 | 126 | 127) 130 | sfa_err 'timeout returned <%s>\n' "$result" 1>&2 131 | ;; 132 | *) 133 | sfa_err 'Unknown failure timeout returned <%s>\n' "$result" 1>&2 134 | ;; 135 | esac 136 | 137 | case $result in 138 | 0 | 1) 139 | _live_agent_list+=("$_key_count:$socket") 140 | return 0 141 | ;; 142 | esac 143 | 144 | return 1 145 | } 146 | 147 | sfa_verify_sockets() { 148 | for i in "${_ssh_agent_sockets[@]}"; do 149 | sfa_test_agent_socket "$i" 150 | done 151 | } 152 | 153 | sfa_fingerprints() { 154 | local file="$1" 155 | while read -r l; do 156 | [[ -n "$l" && ${l##\#} = "$l" ]] && ssh-keygen -l -f /dev/stdin <<<"$l" 157 | done <"$file" 158 | } 159 | 160 | sfa_print_choose_menu() { 161 | # find all the apparent socket files 162 | # the sockets go into $_ssh_agent_sockets[] 163 | sfa_find_all_agent_sockets 164 | 165 | # verify each socket, discarding if dead 166 | # the live sockets go into $_live_agent_list[] 167 | sfa_verify_sockets 168 | sfa_debug '<%s>\n' "${_live_agent_list[@]}" 169 | 170 | # shellcheck disable=SC2207 171 | IFS=$'\n' _sorted_live_agent_list=($(sort -u <<<"${_live_agent_list[*]}")) 172 | unset IFS 173 | 174 | sfa_debug "SORTED:\n" 175 | sfa_debug ' <%s>\n' "${_sorted_live_agent_list[@]}" 176 | 177 | local i=0 178 | local sock 179 | 180 | for agent in "${_sorted_live_agent_list[@]}"; do 181 | i=$((i + 1)) 182 | sock=${agent/*:/} 183 | if [[ "$1" = "-i" ]]; then 184 | _live_agent_sock_list[i]=$sock 185 | 186 | printf '#%i)\n' "$i" 187 | printf ' export SSH_AUTH_SOCK=%s\n' "$sock" 188 | # Get all the forwarded keys for this agent, parse them and print them 189 | SSH_AUTH_SOCK=$sock ssh-add -l 2>&1 | 190 | grep -v 'error fetching identities for protocol 1: agent refused operation' | 191 | while IFS= read -r key; do 192 | parts=("$key") 193 | key_size="${parts[0]}" 194 | fingerprint="${parts[1]}" 195 | remote_name="${parts[2]}" 196 | key_type="${parts[3]}" 197 | printf ' %s %s\t%s\t%s\n' "$key_size" "$key_type" "$remote_name" "$fingerprint" 198 | done 199 | else 200 | printf '%s\n' "$sock" 201 | fi 202 | done 203 | } 204 | 205 | sfa_set_ssh_agent_socket() { 206 | case $1 in 207 | -c | --choose) 208 | sfa_print_choose_menu -i 209 | 210 | ((0 == ${#_live_agent_list[@]})) && { 211 | sfa_err 'No agents found.\n' 212 | return 1 213 | } 214 | 215 | read -p "Choose (1-${#_live_agent_sock_list[@]})? " -r choice 216 | if [ "$choice" -eq "$choice" ]; then 217 | [[ -z "${_live_agent_sock_list[$choice]}" ]] && { 218 | sfa_err 'Invalid choice.\n' 219 | return 1 220 | } 221 | printf 'Setting export SSH_AUTH_SOCK=%s\n' "${_live_agent_sock_list[$choice]}" 222 | export SSH_AUTH_SOCK=${_live_agent_sock_list[$choice]} 223 | fi 224 | ;; 225 | -a | --auto) 226 | # Choose the last one, as they are sorted numerically by how many keys they have 227 | sock=$(sfa_print_choose_menu | tail -n -1) 228 | [[ -z "$sock" ]] && return 1 229 | sfa_debug 'export SSH_AUTH_SOCK=%s\n' "$sock" 230 | export SSH_AUTH_SOCK=$sock 231 | ;; 232 | *) 233 | sfa_usage 234 | ;; 235 | esac 236 | 237 | # set agent pid - this is unreliable as the pid may be of the child rather than the agent 238 | # Note: newer OpenSSH socket names (s.*.agent.*) don't contain the PID 239 | if [ -n "$SSH_AUTH_SOCK" ]; then 240 | local sock_basename 241 | sock_basename=$(basename "$SSH_AUTH_SOCK") 242 | # Only try to extract PID from old-style socket names (agent.*) 243 | if [[ "$sock_basename" =~ ^agent\.([0-9]+)$ ]]; then 244 | local pid_candidate="${BASH_REMATCH[1]}" 245 | if [[ "$pid_candidate" =~ ^[0-9]+$ ]]; then 246 | export SSH_AGENT_PID=$((pid_candidate + 1)) 247 | fi 248 | fi 249 | fi 250 | 251 | return 0 252 | } 253 | 254 | sfa_usage() { 255 | sfa_err 'ssh-find-agent <[-c|--choose|-a|--auto|-h|--help]>\n' 256 | return 1 257 | } 258 | 259 | # Renamed for https://github.com/wwalker/ssh-find-agent/issues/12 260 | ssh_find_agent() { 261 | sfa_init 262 | 263 | case $1 in 264 | -c | --choose | -a | --auto) 265 | sfa_set_ssh_agent_socket "$1" 266 | return $? 267 | ;; 268 | -l | --list) 269 | sfa_print_choose_menu -i 270 | ;; 271 | *) 272 | sfa_usage 273 | ;; 274 | esac 275 | } 276 | 277 | # Original function name is still supported. 278 | # https://github.com/wwalker/ssh-find-agent/issues/12 points out that I 279 | # should use ssh_find_agent() for best compatibility. 280 | ssh-find-agent() { 281 | ssh_find_agent "$@" 282 | } 283 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" 4 | SSH_FIND_AGENT_DIR=${TEST_DIR}/.. 5 | SSH_FIND_AGENT=${SSH_FIND_AGENT_DIR}/ssh-find-agent.sh 6 | 7 | ( 8 | . ${SSH_FIND_AGENT} 9 | 10 | # kill any existing agents 11 | killall ssh-agent 12 | 13 | # verify no agent running 14 | AGENT_PIDS=$(pgrep ssh-agent) 15 | if [ $? -eq 0 ]; then 16 | echo "ERROR: test-setup failed: ssh-agent already running." 17 | echo "PIDs" ${AGENT_PIDS} 18 | exit 1 19 | fi 20 | 21 | # ssh-find-agent -a should return non-zero 22 | if [ $(ssh-find-agent -a) ]; then 23 | echo "ERROR: ${SSH_FIND_AGENT} -a should return non-zero when no agents are running." 24 | exit 1 25 | fi 26 | 27 | # run an ssh-agent but discard the environment 28 | ssh-agent 29 | 30 | # ssh-find-agent -a (auto) 31 | ssh-find-agent -a 32 | 33 | # ssh-add -D returns 0/success if it can contact the agent, even if there is nothing to delete 34 | if ! ssh-add -D; then 35 | echo "ERROR: ssh-add -D failed to locate agent." 36 | exit 1 37 | fi 38 | 39 | # kill the ssh-agent 40 | ssh-agent -k 41 | if [ $? -ne 0 ]; then 42 | echo "ERROR: ssh-agent -k failed - SSH_AGENT_PID might not be set" 43 | #FIXME failing test, requires https://github.com/wwalker/ssh-find-agent/pull/21 to be merged 44 | #exit 1 45 | fi 46 | ) 47 | 48 | RESULT=$? 49 | 50 | # cleanup 51 | killall ssh-agent || true 52 | 53 | if [ $RESULT -eq 0 ]; then 54 | echo "INFO: ssh-find-agent: all tests passed" 55 | exit 0 56 | else 57 | echo "ERROR: ssh-find-agent: TESTS FAILED" 58 | exit $RESULT 59 | fi 60 | --------------------------------------------------------------------------------