From 53081b6917dd6b8bdc22b1404218d9eff66e8792 Mon Sep 17 00:00:00 2001 From: Darren 'Tadgy' Austin Date: Mon, 12 Sep 2022 07:57:10 +0100 Subject: [PATCH] Add ssh-agent handling. Update things for shellcheck. Reorganise. --- .bashrc | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 228 insertions(+), 23 deletions(-) diff --git a/.bashrc b/.bashrc index 8cd6db1..84f3f54 100644 --- a/.bashrc +++ b/.bashrc @@ -1,3 +1,197 @@ +#!/bin/bash - not strictly necessary, but helps nano with syntax highlighting. + +__find_ssh_agent_sock() { + # Returns: 0 = Found an alternative socket. + # 1 = Did not find a viable socket. + local ERR I + + # Search the SSH_AUTH_SOCKS array for a viable socket. + for ((I = 0; I < ${#SSH_AUTH_SOCKS[@]}; I++)); do + SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}" ssh-add -l >/dev/null 2>&1 + (( $? < 2 )) && { export SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}"; break; } + unset SSH_AUTH_SOCK + done + + [[ -z "$SSH_AUTH_SOCK" ]] && return 1 + return 0 +} + +__read_ssh_agents() { + # Returns: 0 = Processed and read the agents file without issue. + # 1 = Error processing/read the agents file. + local ERR FD I SOCK + + [[ ! -e ~/.ssh/agents ]] && touch ~/.ssh/agents + + # Lock the ~/.ssh/agents file. + if [[ "$(uname -s)" == "Linux" ]]; then + # Linux has 'flock', thankfully. + { exec {FD}<~/.ssh/agents && flock -E 10 -e -w 0.5 "$FD"; } || { + ERR=$? + if (( ERR == 10 )); then + printf "\\033[1;31;40m%s\\033[0;39m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 + else + printf "\\033[1;31;40m%s\\033[0;39m\\n" "Flock usage error." >&2 + fi + return 1 + } + + # Make note of the mtime for use in write_ssh_agents. + SSH_AGENTS_MTIME="$(stat --format=%.9Y ~/.ssh/agents)" + elif [[ "$(uname -s)" == "Darwin" ]]; then + # Do locking the sucky way on macOS. + for ((I = 0; I < 6; I++)); do + if shlock -p "$$" -f ~/.ssh/agents.lock; then + exec {FD}<~/.ssh/agents + # Make note of the mtime for use in write_ssh_agents. + SSH_AGENTS_MTIME="$(stat -f %Fm ~/.ssh/agents)" + ERR=0 + break + else + ERR=1 + sleep 0.1 + fi + done + (( ERR != 0 )) && { printf "\\033[1;31;40m%s\\033[0;39m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2; return 1; } + else + printf "\\033[1;31;40m%s\\033[0;39m\\n" "File locking unsupported on this platform." >&2 + return 1 + fi + + # Read the socket list (bash v3+ compliant) + while read -u "$FD" -r SOCK; do + [[ -n "$SOCK" ]] && SSH_AUTH_SOCKS+=("$SOCK") + done + ERR=$? + + # Close the file descriptor (which on Linux releases the lock too). + exec {FD}<&- + + # On Darwin, release the lock on the file. + rm -f ~/.ssh/agents.lock + + # Remove the . in the mtime. + SSH_AGENTS_MTIME="${SSH_AGENTS_MTIME/\.}" + + # Error out if the data couldn't be read. + (( ERR != 0 )) && { printf "\\033[1;31;40m%s\\033[0;39m\\n" "Failed to read ssh agent socket list." >&2; unset SSH_AUTH_SOCKS; return 1; } + return 0 +} + +__write_ssh_agents() { + # Returns: 0 = Processed and wrote the agents file without issue. + # 1 = Error processing/writing the agents file. + # 2 = The SSH_AUTH_SOCKS array may be out of date as the agents file'a mtime has changed. + local FD ERR I J MTIME SOCKS + + # Add the current agent socket to the sockets array. + SSH_AUTH_SOCKS=("$SSH_AUTH_SOCK" "${SSH_AUTH_SOCKS[@]}") + + # Remove any duplicates from SSH_AUTH_SOCKS. + for ((I = 0; I < ${#SSH_AUTH_SOCKS[@]}; I++)); do + for ((J = 0; J < ${#SOCKS[@]}; J++)); do + [[ "${SSH_AUTH_SOCKS[$I]}" == "${SOCKS[$J]}" ]] && continue 2 + done + # Only add the socket if it's still viable. + [[ -n "${SSH_AUTH_SOCKS[$I]}" ]] && { + SSH_AUTH_SOCK="${SSH_AUTH_SOCKS[$I]}" ssh-add -l >/dev/null 2>&1 + (( $? < 2 )) && SOCKS+=("${SSH_AUTH_SOCKS[$I]}") + } + done + + # Lock the ~/.ssh/agents file. + if [[ "$(uname -s)" == "Linux" ]]; then + # Make sure SSH_AUTH_SOCKS has the most up to date data. + MTIME="$(stat --format=%.9Y ~/.ssh/agents)" + (( ${MTIME/\.} > SSH_AGENTS_MTIME )) && return 2 + + # Lock the agents file. + { exec {FD}>~/.ssh/agents && flock -E 10 -e -w 0.5 "$FD"; } || { + if (( $? == 10 )); then + printf "\\033[1;31;40m%s\\033[0;39m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2 + else + printf "\\033[1;31;40m%s\\033[0;39m\\n" "Flock usage error" >&2 + fi + return 1 + } + elif [[ "$(uname -s)" == "Darwin" ]]; then + # Make sure SSH_AUTH_SOCKS has the most up to date data. + MTIME="$(stat --format=%.9Y ~/.ssh/agents)" + (( ${MTIME/\.} > SSH_AGENTS_MTIME )) && return 2 + + # Do locking the sucky way on OSX. + for ((I = 0; I < 6; I++)); do + if shlock -p "$$" -f ~/.ssh/agents.lock; then + exec {FD}>~/.ssh/agents + ERR=0 + break + else + ERR=1 + sleep 0.1 + fi + done + (( ERR != 0 )) && { printf "\\033[1;31;40m%s\\033[0;39m\\n" "Failed to obtain lock on ~/.ssh/agents." >&2; return 1; } + else + printf "\\033[1;31;40m%s\\033[0;39m\\n" "File locking unsupported on this platform." >&2 + return 1 + fi + + # Write the cleaned array to disk. + printf "%s\\n" "${SOCKS[@]}" >~/.ssh/agents 2>/dev/null + ERR=$? + + # Release locks. + exec {FD}>&- + rm -f ~/.ssh/agents.lock + + # Error out if the data couldn't be written. + (( ERR != 0 )) && { printf "\\033[1;31;40m%s\\033[0;39m\\n" "Failed to write ssh agent socket list." >&2; return 1; } + return 0 +} + +__ssh_agent_prompt_command() { + local ERR SSH_AUTH_SOCKS=() + + if [[ -z "$SSH_AUTH_SOCK" ]]; then + ERR=2 + else + SSH_AUTH_SOCK="$SSH_AUTH_SOCK" ssh-add -l >/dev/null 2>&1 + ERR=$? + fi + (( ERR == 2 )) && { + # Read previous sockets from ~/.ssh/agents. + __read_ssh_agents || { + unset SSH_AUTH_SOCK + return 1 + } + + # Find a new socket to use. + if __find_ssh_agent_sock; then + printf "\\033[1;33;40m%s\\033[0;39m\\n" "Connected to existing agent socket." + else + # Start a new agent. + eval "$(ssh-agent -s 2>/dev/null | grep -v 'echo'; printf "%s" "ERR=${PIPESTATUS[0]}")" + (( ERR > 0 )) && { printf "\\033[1;31;40m%s\\033[0;39m\\n" "Failed to start ssh-agent - continuing with no agent." >&2; return 1; } + printf "\\033[1;32;40m%s\\033[0;39m\\n" "Started new ssh-agent." + fi + + # Update the agents file. + while :; do + __write_ssh_agents + ERR=$? + (( ERR == 2 )) && { __read_ssh_agents || return 1; } && continue + (( ERR == 1 )) && return 1 + break + done + } + return 0 +} + +imagebin() { + [[ -z "$1" ]] && { printf "%s: %s\\n" "Usage" "${FUNCNAME[0]} " >&2; return 1; } + curl -F file="@${1:-}" https://imagebin.ca/upload.php | grep ^url: | cut -d: -f2- +} + # Make bash a little more pleasent - these are valid for all versions. shopt -s cdspell checkhash checkwinsize cmdhist histappend no_empty_cmd_completion @@ -21,7 +215,7 @@ else fi # Version specific set up. -if (( ${BASH_VERSINFO[0]} >= 4 )); then +if (( BASH_VERSINFO[0] >= 4 )); then # Add to the shopts. shopt -s checkjobs dirspell @@ -49,19 +243,46 @@ fi unset COLOUR # Set the debugger prompt. -# PS4="+(\\\$? = \$?) \${BASH_SOURCE##*/}\${FUNCNAME:+(\$FUNCNAME)}:\$LINENO: " -# PS4="+(\[\e[1;33;40m\]\\\$? = \$?\[$(tput sgr0)\]) \[$(tput bold)$(tput setaf 4)\]\${BASH_SOURCE##*/}\[$(tput sgr0)\]\${FUNCNAME:+(\[$(tput bold)$(tput setaf 2)\]\$FUNCNAME\[$(tput sgr0)\])}:\[$(tput bold)$(tput setaf 1)\]\$LINENO\[$(tput sgr0)\]: " -export PS4="+(\[\e[1;33;40m\]\$?\[$(tput sgr0)\]) \[$(tput bold)$(tput setaf 4)\]\${BASH_SOURCE##*/}\[$(tput sgr0)\]\${FUNCNAME:+(\[$(tput bold)$(tput setaf 2)\]\$FUNCNAME\[$(tput sgr0)\])}:\[$(tput bold)$(tput setaf 1)\]\$LINENO\[$(tput sgr0)\]: " +# PS4="+(\\\$? = \$?) \${BASH_SOURCE##*/}\${FUNCNAME[0]:+(\${FUNCNAME[0]})}:\$LINENO: " +# PS4="+(\[\e[1;33;40m\]\\\$? = \$?\[$(tput sgr0)\]) \[$(tput bold)$(tput setaf 4)\]\${BASH_SOURCE##*/}\[$(tput sgr0)\]\${FUNCNAME[0]:+(\[$(tput bold)$(tput setaf 2)\]\${FUNCNAME[0]}\[$(tput sgr0)\])}:\[$(tput bold)$(tput setaf 1)\]\$LINENO\[$(tput sgr0)\]: " +export PS4="+(\[\e[1;33;40m\]\$?\[$(tput sgr0)\]) \[$(tput bold)$(tput setaf 4)\]\${BASH_SOURCE##*/}\[$(tput sgr0)\]\${FUNCNAME[0]:+(\[$(tput bold)$(tput setaf 2)\]\${FUNCNAME[0]}\[$(tput sgr0)\])}:\[$(tput bold)$(tput setaf 1)\]\$LINENO\[$(tput sgr0)\]: " # The commands to execute before the prompt is displayed. -# PROMPT_COMMAND="" +PROMPT_COMMAND="__ssh_agent_prompt_command" + +# Common aliases. +hash bc >/dev/null 2>&1 && alias bc='bc -lq' +hash diff >/dev/null 2>&1 && alias diff='diff --color=auto -u' +hash grep >/dev/null 2>&1 && alias egrep='grep -E --color=auto' +hash grep >/dev/null 2>&1 && alias fgrep='grep -E --color=auto' +hash grep >/dev/null 2>&1 && alias grep='grep --color=auto' +hash nc >/dev/null 2>&1 && alias pastebin='nc termbin.com 9999' +hash screen >/dev/null 2>&1 && alias screen='screen -Ua' +hash shellcheck >/dev/null 2>&1 && alias shellcheck='shellcheck -x' +hash scp ssh ssh-add >/dev/null 2>&1 && alias scp='_EXEC=scp ssh' +hash sftp ssh ssh-add >/dev/null 2>&1 && alias sftp='_EXEC=scp ssh' +hash ssh ssh-add >/dev/null 2>&1 && ssh() { + local ERR + + if [[ -z "$SSH_AUTH_SOCK" ]]; then + ERR=2 + else + SSH_AUTH_SOCK="$SSH_AUTH_SOCK" ssh-add -l >/dev/null 2>&1 + ERR=$? + fi + (( ERR == 2 )) && __ssh_agent_prompt_command && ssh-add + (( ERR == 1 )) && ssh-add + command "${_EXEC:-${FUNCNAME[0]}}" "$@" +} + +# Determine the platform being logged into. +PLATFORM="$(uname -s)" # Platform specific set up. -PLATFORM="$(uname -s)" if [[ "$PLATFORM" = "Linux" ]]; then # Linux specific functions. psgrep() { - [[ -z "$1" ]] && { printf "%s: %s\\n" "Usage" "$FUNCNAME " >&2; return 1; } + [[ -z "$1" ]] && { printf "%s: %s\\n" "Usage" "${FUNCNAME[0]} " >&2; return 1; } ps -auwwx | command grep -E --color=always -- "(.*RSS.*|$1)" | grep -F -v "(.*RSS.*|" } @@ -85,19 +306,3 @@ else echo "${BASH_SOURCE##*/}: unsupported platform: $PLATFORM" >&2 fi unset PLATFORM - -# Common functions. -imagebin() { - [[ -z "$1" ]] && { printf "%s: %s\\n" "Usage" "$FUNCNAME " >&2; return 1; } - curl -F file="@${1:-}" https://imagebin.ca/upload.php | grep ^url: | cut -d: -f2- -} - -# Common aliases. -hash bc >/dev/null 2>&1 && alias bc='bc -lq' -hash diff >/dev/null 2>&1 && alias diff='diff --color=auto -u' -hash grep >/dev/null 2>&1 && alias egrep='grep -E --color=auto' -hash grep >/dev/null 2>&1 && alias fgrep='grep -E --color=auto' -hash grep >/dev/null 2>&1 && alias grep='grep --color=auto' -hash nc >/dev/null 2>&1 && alias pastebin='nc termbin.com 9999' -hash screen >/dev/null 2>&1 && alias screen='screen -Ua' -hash shellcheck >/dev/null 2>&1 && alias shellcheck='shellcheck -x'