diff --git a/.config/ssh-find-agent.sh b/.config/ssh-find-agent.sh new file mode 100755 index 0000000..7959493 --- /dev/null +++ b/.config/ssh-find-agent.sh @@ -0,0 +1,258 @@ +#!/bin/bash + +# Copyright (C) 2011 by Wayne Walker +# +# Released under one of the versions of the MIT License. +# +# Copyright (C) 2011 by Wayne Walker +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +sfa_init() { + _ssh_agent_sockets=() + _live_agent_list=() + _live_agent_sock_list=() + _sorted_live_agent_list=() + + # Set $sfa_path array to the dirs to search for ssh-agent sockets + sfa_set_path + + if ! command -v 'timeout' &>/dev/null; then + printf "ssh-find-agent.sh: 'timeout' command could not be found.\n" + printf " Please install 'coreutils' via your system's package manager.\n" + fi +} + +# Allow users to override the default path to search for ssh-agent sockets +# The first of the variable found is used to set the path: +# SSH_FIND_AGENT_PATH (colon separated dir list) +# _TMPDIR_OVERRIDE for legacy compatibility +# TMPDIR (if set) (plus /tmp due to ssh bug) +sfa_set_path() { + sfa_path=() + if [[ -n "$SSH_FIND_AGENT_PATH" ]]; then + IFS=':' read -r -a sfa_path <<<"$SSH_FIND_AGENT_PATH" + else + # Maintain backwards compatibility with the old _TMPDIR_OVERRIDE variable + if [[ -n "$_TMPDIR_OVERRIDE" ]]; then + sfa_path=("$_TMPDIR_OVERRIDE") + else + if [[ -n "$TMPDIR" ]]; then + sfa_path=("/tmp" "$TMPDIR") + else + sfa_path=("/tmp") + fi + fi + fi +} + +sfa_err() { + # shellcheck disable=SC2059 + printf "$@" 1>&2 +} + +sfa_debug() { + if ((_DEBUG > 0)); then + sfa_err "$@" 1>&2 + fi +} + +sfa_find_all_agent_sockets() { + _ssh_agent_sockets=$( + find "${sfa_path[@]}" -maxdepth 2 -type s -name agent.\* 2>/dev/null | grep '/ssh-.*/agent.*' + find "${sfa_path[@]}" -maxdepth 2 -type s -name S.gpg-agent.ssh 2>/dev/null | grep '/gpg-.*/S.gpg-agent.ssh' + find "${sfa_path[@]}" -maxdepth 2 -type s -name ssh 2>/dev/null | grep '/keyring-.*/ssh$' + find "${sfa_path[@]}" -maxdepth 2 -type s -regex '.*/ssh-.*/agent..*$' 2>/dev/null + ) + sfa_debug "$_ssh_agent_sockets" +} + +sfa_test_agent_socket() { + local socket=$1 + local output + output=$(SSH_AUTH_SOCK=$socket timeout 0.4 ssh-add -l 2>&1) + result=$? + + [[ "$output" == "error fetching identities: communication with agent failed" ]] && result=2 + sfa_debug $result + + case $result in + 0 | 1 | 141) + # contactible and has keys loaded + { + OIFS="$IFS" + IFS=$'\n' + # shellcheck disable=SC2207 + _keys=($(SSH_AUTH_SOCK=$socket ssh-add -l 2>/dev/null)) + IFS="$OIFS" + } + _live_agent_list+=("${#_keys[@]}:$socket") + return 0 + ;; + 2 | 124) + # socket is dead, delete it + sfa_err 'socket (%s) is dead, removing it.\n' "$socket" + sfa_debug "rm -rf ${socket%/*}" + rm -rf "${socket%/*}" + ;; + 125 | 126 | 127) + sfa_err 'timeout returned <%s>\n' "$result" 1>&2 + ;; + *) + sfa_err 'Unknown failure timeout returned <%s>\n' "$result" 1>&2 + ;; + esac + + case $result in + 0 | 1) + _live_agent_list+=("$_key_count:$socket") + return 0 + ;; + esac + + return 1 +} + +sfa_verify_sockets() { + for i in $_ssh_agent_sockets; do + sfa_test_agent_socket "$i" + done +} + +sfa_fingerprints() { + local file="$1" + while read -r l; do + [[ -n "$l" && ${l##\#} = "$l" ]] && ssh-keygen -l -f /dev/stdin <<<"$l" + done <"$file" +} + +sfa_print_choose_menu() { + # find all the apparent socket files + # the sockets go into $_ssh_agent_sockets[] + sfa_find_all_agent_sockets + + # verify each socket, discarding if dead + # the live sockets go into $_live_agent_list[] + sfa_verify_sockets + sfa_debug '<%s>\n' "${_live_agent_list[@]}" + + # shellcheck disable=SC2207 + IFS=$'\n' _sorted_live_agent_list=($(sort -u <<<"${_live_agent_list[*]}")) + unset IFS + + sfa_debug "SORTED:\n" + sfa_debug ' <%s>\n' "${_sorted_live_agent_list[@]}" + + local i=0 + local sock + + for agent in "${_sorted_live_agent_list[@]}"; do + i=$((i + 1)) + sock=${agent/*:/} + if [[ "$1" = "-i" ]]; then + _live_agent_sock_list[$i]=$sock + + printf '#%i)\n' "$i" + printf ' export SSH_AUTH_SOCK=%s\n' "$sock" + # Get all the forwarded keys for this agent, parse them and print them + SSH_AUTH_SOCK=$sock ssh-add -l 2>&1 | + grep -v 'error fetching identities for protocol 1: agent refused operation' | + while IFS= read -r key; do + parts=("$key") + key_size="${parts[0]}" + fingerprint="${parts[1]}" + remote_name="${parts[2]}" + key_type="${parts[3]}" + printf ' %s %s\t%s\t%s\n' "$key_size" "$key_type" "$remote_name" "$fingerprint" + done + else + printf '%s\n' "$sock" + fi + done +} + +sfa_set_ssh_agent_socket() { + case $1 in + -c | --choose) + sfa_print_choose_menu -i + + ((0 == ${#_live_agent_list[@]})) && { + sfa_err 'No agents found.\n' + return 1 + } + + read -p "Choose (1-${#_live_agent_sock_list[@]})? " -r choice + if [ "$choice" -eq "$choice" ]; then + [[ -z "${_live_agent_sock_list[$choice]}" ]] && { + sfa_err 'Invalid choice.\n' + return 1 + } + printf 'Setting export SSH_AUTH_SOCK=%s\n' "${_live_agent_sock_list[$choice]}" + export SSH_AUTH_SOCK=${_live_agent_sock_list[$choice]} + fi + ;; + -a | --auto) + # Choose the last one, as they are sorted numerically by how many keys they have + sock=$(sfa_print_choose_menu | tail -n -1) + [[ -z "$sock" ]] && return 1 + sfa_debug 'export SSH_AUTH_SOCK=%s\n' "$sock" + export SSH_AUTH_SOCK=$sock + ;; + *) + sfa_usage + ;; + esac + + # set agent pid - this is unreliable as the pid may be of the child rather than the agent + if [ -n "$SSH_AUTH_SOCK" ]; then + export SSH_AGENT_PID=$(($(basename "$SSH_AUTH_SOCK" | cut -d. -f2) + 1)) + fi + + return 0 +} + +sfa_usage() { + sfa_err 'ssh-find-agent <[-c|--choose|-a|--auto|-h|--help]>\n' + return 1 +} + +# Renamed for https://github.com/wwalker/ssh-find-agent/issues/12 +ssh_find_agent() { + sfa_init + + case $1 in + -c | --choose | -a | --auto) + sfa_set_ssh_agent_socket "$1" + return $? + ;; + -l | --list) + sfa_print_choose_menu -i + ;; + *) + sfa_usage + ;; + esac +} + +# Original function name is still supported. +# https://github.com/wwalker/ssh-find-agent/issues/12 points out that I +# should use ssh_find_agent() for best compatibility. +ssh-find-agent() { + ssh_find_agent "$@" +} diff --git a/home.nix b/home.nix index e127dae..de93fa4 100755 --- a/home.nix +++ b/home.nix @@ -4,6 +4,16 @@ home.username = "felix"; home.homeDirectory = "/home/felix"; + home.file.".config/ssh-find-agent.sh".source = ./.config/ssh-find-agent.sh; + # link the configuration file in current directory to the specified location in home directory + + # link all files in `./scripts` to `~/.config/i3/scripts` + # home.file.".config/i3/scripts" = { + # source = ./scripts; + # recursive = true; # link recursively + # executable = true; # make all files executable + # }; + # encode the file content in nix configuration file directly # home.file.".xxx".text = '' # xxx