Permalink
Cannot retrieve contributors at this time
executable file
1500 lines (1348 sloc)
39.1 KB
| #!/bin/sh | |
| # Copyright 1999-2005 Gentoo Foundation | |
| # Copyright 2007 Aron Griffis <agriffis@n01se.net> | |
| # Copyright 2009-2017 Funtoo Solutions, Inc. | |
| # lockfile() Copyright 2009 Parallels, Inc. | |
| # Distributed under the terms of the GNU General Public License v2 | |
| # Originally authored by Daniel Robbins <drobbins@gentoo.org> | |
| # Maintained August 2002 - April 2003 by Seth Chandler <sethbc@gentoo.org> | |
| # Maintained and rewritten April 2004 - July 2007 by Aron Griffis <agriffis@n01se.net> | |
| # Maintained July 2009 - Sept 2017 by Daniel Robbins <drobbins@funtoo.org> | |
| # Maintained September 2017 - present by Ryan Harris <x48rph@gmail.com> | |
| version=##VERSION## | |
| PATH="${PATH:-/usr/bin:/bin:/sbin:/usr/sbin:/usr/ucb}" | |
| maintainer="x48rph@gmail.com" | |
| unset mesglog | |
| unset myaction | |
| unset agentsopt | |
| havelock=false | |
| unset hostopt | |
| ignoreopt=false | |
| noaskopt=false | |
| noguiopt=false | |
| nolockopt=false | |
| lockwait=5 | |
| openssh=unknown | |
| sunssh=unknown | |
| confhost=unknown | |
| sshconfig=false | |
| quickopt=false | |
| quietopt=false | |
| clearopt=false | |
| color=true | |
| inheritwhich=local-once | |
| unset stopwhich | |
| unset timeout | |
| unset ssh_timeout | |
| attempts=1 | |
| unset sshavail | |
| unset sshkeys | |
| unset gpgkeys | |
| unset mykeys | |
| keydir="${HOME}/.keychain" | |
| unset envf | |
| evalopt=false | |
| queryopt=false | |
| confirmopt=false | |
| absoluteopt=false | |
| systemdopt=false | |
| unset ssh_confirm | |
| unset GREP_OPTIONS | |
| gpg_prog_name="gpg" | |
| BLUE="[34;01m" | |
| CYAN="[36;01m" | |
| CYANN="[36m" | |
| GREEN="[32;01m" | |
| RED="[31;01m" | |
| PURP="[35;01m" | |
| OFF="[0m" | |
| # GNU awk and sed have regex issues in a multibyte environment. If any locale | |
| # variables are set, then override by setting LC_ALL | |
| unset pinentry_locale | |
| if [ -n "$LANG$LC_ALL" ] || [ -n "$(locale 2>/dev/null | egrep -v '="?(|POSIX|C)"?$' 2>/dev/null)" ]; then | |
| # save LC_ALL so that pinentry-curses works right. This has always worked | |
| # correctly for me but peper and kloeri had problems with it. | |
| pinentry_lc_all="$LC_ALL" | |
| LC_ALL=C | |
| export LC_ALL | |
| fi | |
| # synopsis: qprint "message" | |
| qprint() { | |
| $quietopt || echo "$*" >&2 | |
| } | |
| # synopsis: mesg "message" | |
| # Prettily print something to stderr, honors quietopt | |
| mesg() { | |
| qprint " ${GREEN}*${OFF} $*" | |
| } | |
| # synopsis: warn "message" | |
| # Prettily print a warning to stderr | |
| warn() { | |
| echo " ${RED}* Warning${OFF}: $*" >&2 | |
| } | |
| # synopsis: error "message" | |
| # Prettily print an error | |
| error() { | |
| echo " ${RED}* Error${OFF}: $*" >&2 | |
| } | |
| # synopsis: die "message" | |
| # Prettily print an error, then abort | |
| die() { | |
| [ -n "$1" ] && error "$*" | |
| qprint | |
| $evalopt && { echo; echo "false;"; } | |
| exit 1 | |
| } | |
| # synopsis: versinfo | |
| # Display the version information | |
| versinfo() { | |
| qprint | |
| qprint " Copyright ${CYANN}2002-2006${OFF} Gentoo Foundation;" | |
| qprint " Copyright ${CYANN}2007${OFF} Aron Griffis;" | |
| qprint " Copyright ${CYANN}2009-2017${OFF} Funtoo Solutions, Inc;" | |
| qprint " lockfile() Copyright ${CYANN}2009${OFF} Parallels, Inc." | |
| qprint | |
| qprint " Keychain is free software: you can redistribute it and/or modify" | |
| qprint " it under the terms of the ${CYANN}GNU General Public License version 2${OFF} as" | |
| qprint " published by the Free Software Foundation." | |
| qprint | |
| } | |
| # synopsis: helpinfo | |
| # Display the help information. There's no really good way to use qprint for | |
| # this... | |
| helpinfo() { | |
| cat >&1 <<EOHELP | |
| INSERT_POD_OUTPUT_HERE | |
| EOHELP | |
| } | |
| # synopsis: testssh | |
| # Figure out which ssh is in use, set the global boolean $openssh and $sunssh | |
| testssh() { | |
| # Query local host for SSH application, presently supporting | |
| # OpenSSH, Sun SSH, and ssh.com | |
| openssh=false | |
| sunssh=false | |
| case "$(ssh -V 2>&1)" in | |
| *OpenSSH*) openssh=true ;; | |
| *Sun?SSH*) sunssh=true ;; | |
| esac | |
| } | |
| # synopsis: getuser | |
| # Set the global string $me | |
| getuser() { | |
| # id -un gives euid, which might be different from USER or LOGNAME | |
| me=$(id -un) || die "Who are you? id -un doesn't know..." | |
| } | |
| # synopsis: getos | |
| # Set the global string $OSTYPE | |
| getos() { | |
| OSTYPE=$(uname) || die 'uname failed' | |
| } | |
| # synopsis: verifykeydir | |
| # Make sure the key dir is set up correctly. Exits on error. | |
| verifykeydir() { | |
| # Create keydir if it doesn't exist already | |
| if [ -f "${keydir}" ]; then | |
| die "${keydir} is a file (it should be a directory)" | |
| # Solaris 9 doesn't have -e; using -d.... | |
| elif [ ! -d "${keydir}" ]; then | |
| ( umask 0077 && mkdir "${keydir}"; ) || die "can't create ${keydir}" | |
| fi | |
| } | |
| lockfile() { | |
| # This function originates from Parallels Inc.'s OpenVZ vpsreboot script | |
| # Description: This function attempts to acquire the lock. If it succeeds, | |
| # it returns 0. If it fails, it returns 1. This function retuns immediately | |
| # and only tries to acquire the lock once. | |
| tmpfile="$lockf.$$" | |
| echo $$ >"$tmpfile" 2>/dev/null || exit | |
| if ln "$tmpfile" "$lockf" 2>/dev/null; then | |
| rm -f "$tmpfile" | |
| havelock=true && return 0 | |
| fi | |
| if kill -0 $(cat $lockf 2>/dev/null) 2>/dev/null; then | |
| rm -f "$tmpfile" | |
| return 1 | |
| fi | |
| if ln "$tmpfile" "$lockf" 2>/dev/null; then | |
| rm -f "$tmpfile" | |
| havelock=true && return 0 | |
| fi | |
| rm -f "$tmpfile" "$lockf" && return 1 | |
| } | |
| takelock() { | |
| # Description: This function calls lockfile() multiple times if necessary | |
| # to try to acquire the lock. It returns 0 on success and 1 on failure. | |
| # Change in behavior: if timeout expires, we will forcefully acquire lock. | |
| [ "$havelock" = "true" ] && return 0 | |
| [ "$nolockopt" = "true" ] && return 0 | |
| # First attempt: | |
| lockfile && return 0 | |
| counter=0 | |
| mesg "Waiting $lockwait seconds for lock..." | |
| while [ "$counter" -lt "$(( $lockwait * 2 ))" ] | |
| do | |
| lockfile && return 0 | |
| sleep 0.5; counter=$(( $counter + 1 )) | |
| done | |
| rm -f "$lockf" && lockfile && return 0 | |
| return 1 | |
| } | |
| # synopsis: droplock | |
| # Drops the lock if we're holding it. | |
| droplock() { | |
| $havelock && [ -n "$lockf" ] && rm -f "$lockf" | |
| } | |
| # synopsis: findpids [prog] | |
| # Returns a space-separated list of agent pids. | |
| # prog can be ssh or gpg, defaults to ssh. Note that if another prog is ever | |
| # added, need to pay attention to the length for Solaris compatibility. | |
| findpids() { | |
| fp_prog=${1-ssh} | |
| unset fp_psout | |
| # Different systems require different invocations of ps. Try to generalize | |
| # the best we can. The only requirement is that the agent command name | |
| # appears in the line, and the PID is the first item on the line. | |
| [ -n "$OSTYPE" ] || getos | |
| # Try systems where we know what to do first | |
| case "$OSTYPE" in | |
| AIX|*bsd*|*BSD*|CYGWIN|darwin*|Linux|linux-gnu|OSF1) | |
| fp_psout=$(ps x 2>/dev/null) ;; # BSD syntax | |
| HP-UX) | |
| fp_psout=$(ps -u $me 2>/dev/null) ;; # SysV syntax | |
| SunOS) | |
| case $(uname -r) in | |
| [56]*) | |
| fp_psout=$(ps -u $me 2>/dev/null) ;; # SysV syntax | |
| *) | |
| fp_psout=$(ps x 2>/dev/null) ;; # BSD syntax | |
| esac ;; | |
| GNU|gnu) | |
| fp_psout=$(ps -g 2>/dev/null) ;; # GNU Hurd syntax | |
| esac | |
| # If we didn't get a match above, try a list of possibilities... | |
| # The first one will probably fail on systems supporting only BSD syntax. | |
| if [ -z "$fp_psout" ]; then | |
| fp_psout=$(UNIX95=1 ps -u $me -o pid,comm 2>/dev/null | grep '^ *[0-9]') | |
| [ -z "$fp_psout" ] && fp_psout=$(ps x 2>/dev/null) | |
| [ -z "$fp_psout" ] && fp_psout=$(ps w 2>/dev/null) # Busybox syntax | |
| fi | |
| # Return the list of pids; ignore case for Cygwin. | |
| # Check only 8 characters since Solaris truncates at that length. | |
| # Ignore defunct ssh-agents (bug 28599) | |
| if [ -n "$fp_psout" ]; then | |
| echo "$fp_psout" | \ | |
| awk "BEGIN{IGNORECASE=1} /defunct/{next} | |
| /$fp_prog-[a]gen/{print \$1}" | xargs | |
| return 0 | |
| fi | |
| # If none worked, we're stuck | |
| error "Unable to use \"ps\" to scan for $fp_prog-agent processes" | |
| error "Please report to $maintainer via http://bugs.gentoo.org" | |
| return 1 | |
| } | |
| # synopsis: stopagent [prog] | |
| # --stop tells keychain to kill the existing agent(s) | |
| # prog can be ssh or gpg, defaults to ssh. | |
| stopagent() { | |
| stop_prog=${1-ssh} | |
| eval stop_except=\$\{${stop_prog}_agent_pid\} | |
| stop_mypids=$(findpids "$stop_prog") | |
| [ $? = 0 ] || die | |
| if [ -z "$stop_mypids" ]; then | |
| mesg "No $stop_prog-agent(s) found running" | |
| return 0 | |
| fi | |
| case "$stopwhich" in | |
| all) | |
| kill $stop_mypids >/dev/null 2>&1 | |
| mesg "All ${CYANN}$me${OFF}'s $stop_prog-agents stopped: ${CYANN}$stop_mypids${OFF}" | |
| ;; | |
| others) | |
| # Try to handle the case where we *will* inherit a pid | |
| kill -0 $stop_except >/dev/null 2>&1 | |
| if [ -z "$stop_except" -o $? != 0 -o \ | |
| "$inheritwhich" = local -o "$inheritwhich" = any ]; then | |
| if [ "$inheritwhich" != none ]; then | |
| eval stop_except=\$\{inherit_${stop_prog}_agent_pid\} | |
| kill -0 $stop_except >/dev/null 2>&1 | |
| if [ -z "$stop_except" -o $? != 0 ]; then | |
| # Handle ssh2 | |
| eval stop_except=\$\{inherit_${stop_prog}2_agent_pid\} | |
| fi | |
| fi | |
| fi | |
| # Filter out the running agent pid | |
| unset stop_mynewpids | |
| for stop_x in $stop_mypids; do | |
| [ $stop_x -eq $stop_except ] 2>/dev/null && continue | |
| stop_mynewpids="${stop_mynewpids+$stop_mynewpids }$stop_x" | |
| done | |
| if [ -n "$stop_mynewpids" ]; then | |
| kill $stop_mynewpids >/dev/null 2>&1 | |
| mesg "Other ${CYANN}$me${OFF}'s $stop_prog-agents stopped: ${CYANN}$stop_mynewpids${OFF}" | |
| else | |
| mesg "No other $stop_prog-agent(s) than keychain's $stop_except found running" | |
| fi | |
| ;; | |
| mine) | |
| if [ $stop_except -gt 0 ] 2>/dev/null; then | |
| kill $stop_except >/dev/null 2>&1 | |
| mesg "Keychain $stop_prog-agents stopped: ${CYANN}$stop_except${OFF}" | |
| else | |
| mesg "No keychain $stop_prog-agent found running" | |
| fi | |
| ;; | |
| esac | |
| # remove pid files if keychain-controlled | |
| if [ "$stopwhich" != others ]; then | |
| if [ "$stop_prog" != ssh ]; then | |
| rm -f "${pidf}-$stop_prog" "${cshpidf}-$stop_prog" "${fishpidf}-$stop_prog" 2>/dev/null | |
| else | |
| rm -f "${pidf}" "${cshpidf}" "${fishpidf}" 2>/dev/null | |
| fi | |
| eval unset ${stop_prog}_agent_pid | |
| fi | |
| } | |
| # synopsis: inheritagents | |
| # Save agent variables from the environment before they get wiped out | |
| inheritagents() { | |
| # Verify these global vars are null | |
| unset inherit_ssh_auth_sock inherit_ssh_agent_pid | |
| unset inherit_ssh2_auth_sock inherit_ssh2_agent_sock | |
| unset inherit_gpg_agent_info inherit_gpg_agent_pid | |
| # Save variables so we can inherit a running agent | |
| if [ "$inheritwhich" != none ]; then | |
| if wantagent ssh; then | |
| if [ -n "$SSH_AUTH_SOCK" ]; then | |
| inherit_ssh_auth_sock="$SSH_AUTH_SOCK" | |
| inherit_ssh_agent_pid="$SSH_AGENT_PID" | |
| fi | |
| if [ -n "$SSH2_AUTH_SOCK" ]; then | |
| inherit_ssh2_auth_sock="$SSH2_AUTH_SOCK" | |
| inherit_ssh2_agent_pid="$SSH2_AGENT_PID" | |
| fi | |
| fi | |
| if wantagent gpg; then | |
| if [ -n "$GPG_AGENT_INFO" ]; then | |
| inherit_gpg_agent_info="$GPG_AGENT_INFO" | |
| inherit_gpg_agent_pid=$(echo "$GPG_AGENT_INFO" | cut -f2 -d:) | |
| # GnuPG v.2.1+ removes $GPG_AGENT_INFO | |
| else | |
| gpg_socket_dir="${GNUPGHOME:=$HOME/.gnupg}" | |
| if [ ! -S "${GNUPGHOME:=$HOME/.gnupg}/S.gpg-agent" ]; then | |
| gpg_socket_dir="${XDG_RUNTIME_DIR}/gnupg" | |
| fi | |
| if [ -S "${gpg_socket_dir}/S.gpg-agent" ]; then | |
| inherit_gpg_agent_pid=$(findpids "${gpg_prog_name}") | |
| inherit_gpg_agent_info="${gpg_socket_dir}/S.gpg-agent:${inherit_gpg_agent_pid}:1" | |
| fi | |
| fi | |
| fi | |
| fi | |
| } | |
| # synopsis: validinherit | |
| # Test inherit_* variables for validity | |
| validinherit() { | |
| vi_agent="$1" | |
| vi_status=0 | |
| if [ "$vi_agent" = ssh ]; then | |
| if [ -n "$inherit_ssh_auth_sock" ]; then | |
| ls "$inherit_ssh_auth_sock" >/dev/null 2>&1 | |
| if [ $? != 0 ]; then | |
| warn "SSH_AUTH_SOCK in environment is invalid; ignoring it" | |
| unset inherit_ssh_auth_sock inherit_ssh_agent_pid | |
| vi_status=1 | |
| fi | |
| fi | |
| if [ -n "$inherit_ssh2_auth_sock" ]; then | |
| ls "$inherit_ssh2_auth_sock" >/dev/null 2>&1 | |
| if [ $? != 0 ]; then | |
| warn "SSH2_AUTH_SOCK in environment is invalid; ignoring it" | |
| unset inherit_ssh2_auth_sock inherit_ssh2_agent_pid | |
| vi_status=1 | |
| fi | |
| fi | |
| elif [ "$vi_agent" = gpg ]; then | |
| if [ -n "$inherit_gpg_agent_pid" ]; then | |
| kill -0 "$inherit_gpg_agent_pid" >/dev/null 2>&1 | |
| if [ $? != 0 ]; then | |
| unset inherit_gpg_agent_pid inherit_gpg_agent_info | |
| warn "GPG_AGENT_INFO in environment is invalid; ignoring it" | |
| vi_status=1 | |
| fi | |
| fi | |
| fi | |
| return $vi_status | |
| } | |
| # synopsis: catpidf_shell shell agents... | |
| # cat the pid files for the given agents. This is used by loadagents and also | |
| # for keychain output when --eval is given. | |
| catpidf_shell() { | |
| case "$1" in | |
| */fish|fish) cp_pidf="$fishpidf" ;; | |
| *csh) cp_pidf="$cshpidf" ;; | |
| *) cp_pidf="$pidf" ;; | |
| esac | |
| shift | |
| for cp_a in "$@"; do | |
| case "${cp_a}" in | |
| ssh) [ -f "$cp_pidf" ] && cat "$cp_pidf" ;; | |
| *) [ -f "${cp_pidf}-$cp_a" ] && cat "${cp_pidf}-$cp_a" ;; | |
| esac | |
| echo | |
| done | |
| return 0 | |
| } | |
| # synopsis: catpidf agents... | |
| # cat the pid files for the given agents, appropriate for the current value of | |
| # $SHELL. This is used for keychain output when --eval is given. | |
| catpidf() { | |
| catpidf_shell "$SHELL" "$@" | |
| } | |
| # synopsis: loadagents agents... | |
| # Load agent variables from $pidf and copy implementation-specific environment | |
| # variables into generic global strings | |
| loadagents() { | |
| for la_a in "$@"; do | |
| case "$la_a" in | |
| ssh) | |
| unset SSH_AUTH_SOCK SSH_AGENT_PID SSH2_AUTH_SOCK SSH2_AGENT_PID | |
| eval "$(catpidf_shell sh $la_a)" | |
| if [ -n "$SSH_AUTH_SOCK" ]; then | |
| ssh_auth_sock=$SSH_AUTH_SOCK | |
| ssh_agent_pid=$SSH_AGENT_PID | |
| elif [ -n "$SSH2_AUTH_SOCK" ]; then | |
| ssh_auth_sock=$SSH2_AUTH_SOCK | |
| ssh_agent_pid=$SSH2_AGENT_PID | |
| else | |
| unset ssh_auth_sock ssh_agent_pid | |
| fi | |
| ;; | |
| gpg) | |
| unset GPG_AGENT_INFO | |
| eval "$(catpidf_shell sh $la_a)" | |
| if [ -n "$GPG_AGENT_INFO" ]; then | |
| la_IFS="$IFS" # save current IFS | |
| IFS=':' # set IFS to colon to separate PATH | |
| set -- $GPG_AGENT_INFO | |
| IFS="$la_IFS" # restore IFS | |
| gpg_agent_pid=$2 | |
| fi | |
| ;; | |
| *) | |
| eval "$(catpidf_shell sh $la_a)" | |
| ;; | |
| esac | |
| done | |
| return 0 | |
| } | |
| # synopsis: startagent [prog] | |
| # Starts an agent if it isn't already running. | |
| # Requires $ssh_agent_pid | |
| startagent() { | |
| start_prog=${1-ssh} | |
| start_proto=${2-${start_prog}} | |
| unset start_pid | |
| start_inherit_pid=none | |
| start_mypids=$(findpids "$start_prog") | |
| [ $? = 0 ] || die | |
| # Unfortunately there isn't much way to genericize this without introducing | |
| # a lot more supporting code/structures. | |
| if [ "$start_prog" = ssh ]; then | |
| start_pidf="$pidf" | |
| start_cshpidf="$cshpidf" | |
| start_fishpidf="$fishpidf" | |
| start_pid="$ssh_agent_pid" | |
| if [ -n "$inherit_ssh_auth_sock" -o -n "$inherit_ssh2_auth_sock" ]; then | |
| if [ -n "$inherit_ssh_agent_pid" ]; then | |
| start_inherit_pid="$inherit_ssh_agent_pid" | |
| elif [ -n "$inherit_ssh2_agent_pid" ]; then | |
| start_inherit_pid="$inherit_ssh2_agent_pid" | |
| else | |
| start_inherit_pid="forwarded" | |
| fi | |
| fi | |
| else | |
| start_pidf="${pidf}-$start_prog" | |
| start_cshpidf="${cshpidf}-$start_prog" | |
| start_fishpidf="${fishpidf}-$start_prog" | |
| if [ "$start_prog" = gpg ]; then | |
| start_pid="$gpg_agent_pid" | |
| if [ -n "$inherit_gpg_agent_pid" ]; then | |
| start_inherit_pid="$inherit_gpg_agent_pid" | |
| fi | |
| else | |
| error "I don't know how to start $start_prog-agent (1)" | |
| return 1 | |
| fi | |
| fi | |
| [ "$start_pid" -gt 0 ] 2>/dev/null || start_pid=none | |
| # This hack makes the case statement easier | |
| if [ "$inheritwhich" = any -o "$inheritwhich" = any-once ]; then | |
| start_fwdflg=forwarded | |
| else | |
| unset start_fwdflg | |
| fi | |
| # Check for an existing agent | |
| start_tester="$inheritwhich: $start_mypids $start_fwdflg " | |
| case "$start_tester" in | |
| none:*" $start_pid "*|*-once:*" $start_pid "*) | |
| mesg "Found existing ${start_prog}-agent: ${CYANN}$start_pid${OFF}" | |
| return 0 | |
| ;; | |
| *:*" $start_inherit_pid "*) | |
| # This test was postponed until now to prevent generating warnings | |
| validinherit "$start_prog" | |
| if [ $? != 0 ]; then | |
| # inherit_* vars have been removed from the environment. Try | |
| # again now | |
| startagent "$start_prog" | |
| return $? | |
| fi | |
| mesg "Inheriting ${start_prog}-agent ($start_inherit_pid)" | |
| ;; | |
| *) | |
| # start_inherit_pid might be "forwarded" which we don't allow with, | |
| # for example, local-once (the default setting) | |
| start_inherit_pid=none | |
| ;; | |
| esac | |
| # Init the bourne-formatted pidfile | |
| ( umask 0177 && :> "$start_pidf"; ) | |
| if [ $? != 0 ]; then | |
| rm -f "$start_pidf" "$start_cshpidf" "$start_fishpidf" 2>/dev/null | |
| error "can't create $start_pidf" | |
| return 1 | |
| fi | |
| # Init the csh-formatted pidfile | |
| ( umask 0177 && :> "$start_cshpidf"; ) | |
| if [ $? != 0 ]; then | |
| rm -f "$start_pidf" "$start_cshpidf" "$start_fishpidf" 2>/dev/null | |
| error "can't create $start_cshpidf" | |
| return 1 | |
| fi | |
| # Init the fish-formatted pidfile | |
| ( umask 0177 && :> "$start_fishpidf"; ) | |
| if [ $? != 0 ]; then | |
| rm -f "$start_pidf" "$start_cshpidf" "$start_fishpidf" 2>/dev/null | |
| error "can't create $start_fishpidf" | |
| return 1 | |
| fi | |
| # Determine content for files | |
| unset start_out | |
| if [ "$start_inherit_pid" = none ]; then | |
| # Start the agent. | |
| # Branch again since the agents start differently | |
| mesg "Starting ${start_prog}-agent..." | |
| if [ "$start_prog" = ssh ]; then | |
| start_out=$(ssh-agent ${ssh_timeout}) | |
| elif [ "$start_prog" = gpg ]; then | |
| if [ -n "${timeout}" ]; then | |
| gpg_cache_ttl="$(expr $timeout \* 60)" | |
| start_gpg_timeout="--default-cache-ttl $gpg_cache_ttl --max-cache-ttl $gpg_cache_ttl" | |
| else | |
| unset start_gpg_timeout | |
| fi | |
| # the 1.9.x series of gpg spews debug on stderr | |
| start_out=$(gpg-agent --daemon --write-env-file $start_gpg_timeout 2>/dev/null) | |
| else | |
| error "I don't know how to start $start_prog-agent (2)" | |
| return 1 | |
| fi | |
| if [ $? != 0 -a $? != 2 ]; then | |
| rm -f "$start_pidf" "$start_cshpidf" "$start_fishpidf" 2>/dev/null | |
| error "Failed to start ${start_prog}-agent" | |
| return 1 | |
| fi | |
| elif [ "$start_prog" = ssh -a -n "$inherit_ssh_auth_sock" ]; then | |
| start_out="SSH_AUTH_SOCK=$inherit_ssh_auth_sock; export SSH_AUTH_SOCK;" | |
| if [ "$inherit_ssh_agent_pid" -gt 0 ] 2>/dev/null; then | |
| start_out="$start_out | |
| SSH_AGENT_PID=$inherit_ssh_agent_pid; export SSH_AGENT_PID;" | |
| fi | |
| elif [ "$start_prog" = ssh -a -n "$inherit_ssh2_auth_sock" ]; then | |
| start_out="SSH2_AUTH_SOCK=$inherit_ssh2_auth_sock; export SSH2_AUTH_SOCK; | |
| SSH2_AGENT_PID=$inherit_ssh2_agent_pid; export SSH2_AGENT_PID;" | |
| if [ "$inherit_ssh2_agent_pid" -gt 0 ] 2>/dev/null; then | |
| start_out="$start_out | |
| SSH2_AGENT_PID=$inherit_ssh2_agent_pid; export SSH2_AGENT_PID;" | |
| fi | |
| elif [ "$start_prog" = "${gpg_prog_name}" -a -n "$inherit_gpg_agent_info" ]; then | |
| start_out="GPG_AGENT_INFO=$inherit_gpg_agent_info; export GPG_AGENT_INFO;" | |
| else | |
| die "something bad happened" # should never be here | |
| fi | |
| # Add content to pidfiles. | |
| # Some versions of ssh-agent don't understand -s, which means to | |
| # generate Bourne shell syntax. It appears they also ignore SHELL, | |
| # according to http://bugs.gentoo.org/show_bug.cgi?id=52874 | |
| # So make no assumptions. | |
| start_out=$(echo "$start_out" | grep -v 'Agent pid') | |
| case "$start_out" in | |
| setenv*) | |
| echo "$start_out" >"$start_cshpidf" | |
| echo "$start_out" | awk '{print $2"="$3" export "$2";"}' >"$start_pidf" | |
| ;; | |
| *) | |
| echo "$start_out" >"$start_pidf" | |
| echo "$start_out" | sed 's/;.*/;/' | sed 's/=/ /' | sed 's/^/setenv /' >"$start_cshpidf" | |
| echo "$start_out" | sed 's/;.*/;/' | sed 's/^\(.*\)=\(.*\);/set -e \1; set -x -U \1 \2;/' >"$start_fishpidf" | |
| ;; | |
| esac | |
| # Hey the agent should be started now... load it up! | |
| loadagents "$start_prog" | |
| } | |
| # synopsis: extract_fingerprints | |
| # Extract the fingerprints from standard input, returns space-separated list. | |
| # Utility routine for ssh_l and ssh_f | |
| extract_fingerprints() { | |
| while read ef_line; do | |
| case "$ef_line" in | |
| *\ *\ [0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:*) | |
| # Sun SSH spits out different things depending on the type of | |
| # key. For example: | |
| # md5 1024 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_dsa(DSA) | |
| # 2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_rsa.pub | |
| echo "$ef_line" | cut -f3 -d' ' | |
| ;; | |
| *\ [0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:*) | |
| # The more consistent OpenSSH format, we hope | |
| # 1024 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_dsa (DSA) | |
| echo "$ef_line" | cut -f2 -d' ' | |
| ;; | |
| *\ [A-Z0-9][A-Z0-9]*:[A-Za-z0-9+/][A-Za-z0-9+/]*) | |
| # The new OpenSSH 6.8+ format, | |
| # 1024 SHA256:mVPwvezndPv/ARoIadVY98vAC0g+P/5633yTC4d/wXE /home/barney/.ssh/id_dsa (DSA) | |
| echo "$ef_line" | cut -f2 -d' ' | |
| ;; | |
| *) | |
| # Fall back to filename. Note that commercial ssh is handled | |
| # explicitly in ssh_l and ssh_f, so hopefully this rule will | |
| # never fire. | |
| warn "Can't determine fingerprint from the following line, falling back to filename" | |
| mesg "$ef_line" | |
| basename "$ef_line" | sed 's/[ (].*//' | |
| ;; | |
| esac | |
| done | xargs | |
| } | |
| # synopsis: ssh_l | |
| # Return space-separated list of known fingerprints | |
| ssh_l() { | |
| sl_mylist=$(ssh-add -l 2>/dev/null) | |
| sl_retval=$? | |
| if $openssh; then | |
| # Error codes: | |
| # 0 success | |
| # 1 OpenSSH_3.8.1p1 on Linux: no identities (not an error) | |
| # OpenSSH_3.0.2p1 on HP-UX: can't connect to auth agent | |
| # 2 can't connect to auth agent | |
| case $sl_retval in | |
| 0) | |
| echo "$sl_mylist" | extract_fingerprints | |
| ;; | |
| 1) | |
| case "$sl_mylist" in | |
| *"open a connection"*) sl_retval=2 ;; | |
| esac | |
| ;; | |
| esac | |
| return $sl_retval | |
| elif $sunssh; then | |
| # Error codes (from http://docs.sun.com/db/doc/817-3936/6mjgdbvio?a=view) | |
| # 0 success (even when there are no keys) | |
| # 1 error | |
| case $sl_retval in | |
| 0) | |
| echo "$sl_mylist" | extract_fingerprints | |
| ;; | |
| 1) | |
| case "$sl_mylist" in | |
| *"open a connection"*) sl_retval=2 ;; | |
| esac | |
| ;; | |
| esac | |
| return $sl_retval | |
| else | |
| # Error codes: | |
| # 0 success - however might say "The authorization agent has no keys." | |
| # 1 can't connect to auth agent | |
| # 2 bad passphrase | |
| # 3 bad identity file | |
| # 4 the agent does not have the requested identity | |
| # 5 unspecified error | |
| if [ $sl_retval = 0 ]; then | |
| # Output of ssh-add -l: | |
| # The authorization agent has one key: | |
| # id_dsa_2048_a: 2048-bit dsa, agriffis@alpha.zk3.dec.com, Fri Jul 25 2003 10:53:49 -0400 | |
| # Since we don't have a fingerprint, just get the filenames *shrug* | |
| echo "$sl_mylist" | sed '2,$s/:.*//' | xargs | |
| fi | |
| return $sl_retval | |
| fi | |
| } | |
| # synopsis: ssh_f filename | |
| # Return fingerprint for a keyfile | |
| # Requires $openssh or $sunssh | |
| ssh_f() { | |
| sf_filename="$1" | |
| if $openssh || $sunssh; then | |
| realpath_bin="$(command -v realpath)" | |
| # if private key is symlink and symlink to *.pub is missing: | |
| if [ -L "$sf_filename" ] && [ ! -z "$realpath_bin" ]; then | |
| sf_filename="$($realpath_bin $sf_filename)" | |
| fi | |
| lsf_filename="$sf_filename.pub" | |
| if [ ! -f "$lsf_filename" ]; then | |
| # try to remove extension from private key, *then* add .pub, and see if we now find it: | |
| if [ -L "$sf_filename" ] && [ ! -z "$realpath_bin" ]; then | |
| sf_filename="$($realpath_bin $sf_filename)" | |
| fi | |
| lsf_filename=$(echo "$sf_filename" | sed 's/\.[^\.]*$//').pub | |
| if [ ! -f "$lsf_filename" ]; then | |
| warn "Cannot find separate public key for $1." | |
| lsf_filename="$sf_filename" | |
| fi | |
| fi | |
| sf_fing=$(ssh-keygen -l -f "$lsf_filename") || return 1 | |
| echo "$sf_fing" | extract_fingerprints | |
| else | |
| # can't get fingerprint for ssh2 so use filename *shrug* | |
| basename "$sf_filename" | |
| fi | |
| return 0 | |
| } | |
| # synopsis: gpg_listmissing | |
| # Uses $gpgkeys | |
| # Returns a newline-separated list of keys found to be missing. | |
| gpg_listmissing() { | |
| unset glm_missing | |
| GPG_TTY=$(tty) | |
| # Parse $gpgkeys into positional params to preserve spaces in filenames | |
| set -f # disable globbing | |
| glm_IFS="$IFS" # save current IFS | |
| IFS=" | |
| " # set IFS to newline | |
| set -- $gpgkeys | |
| IFS="$glm_IFS" # restore IFS | |
| set +f # re-enable globbing | |
| for glm_k in "$@"; do | |
| # Check if this key is known to the agent. Don't know another way... | |
| if echo | env -i GPG_TTY="$GPG_TTY" PATH="$PATH" GPG_AGENT_INFO="$GPG_AGENT_INFO" \ | |
| "${gpg_prog_name}" --no-options --use-agent --no-tty --sign --local-user "$glm_k" -o- >/dev/null 2>&1; then | |
| # already know about this key | |
| mesg "Known gpg key: ${CYANN}${glm_k}${OFF}" | |
| continue | |
| else | |
| # need to add this key | |
| if [ -z "$glm_missing" ]; then | |
| glm_missing="$glm_k" | |
| else | |
| glm_missing="$glm_missing | |
| $glm_k" | |
| fi | |
| fi | |
| done | |
| echo "$glm_missing" | |
| } | |
| # synopsis: ssh_listmissing | |
| # Uses $sshkeys and $sshavail | |
| # Returns a newline-separated list of keys found to be missing. | |
| ssh_listmissing() { | |
| unset slm_missing | |
| # Parse $sshkeys into positional params to preserve spaces in filenames | |
| set -f # disable globbing | |
| slm_IFS="$IFS" # save current IFS | |
| IFS=" | |
| " # set IFS to newline | |
| set -- $sshkeys | |
| IFS="$slm_IFS" # restore IFS | |
| set +f # re-enable globbing | |
| for slm_k in "$@"; do | |
| # Fingerprint current user-specified key | |
| slm_finger=$(ssh_f "$slm_k") || continue | |
| # Check if it needs to be added | |
| case " $sshavail " in | |
| *" $slm_finger "*) | |
| # already know about this key | |
| mesg "Known ssh key: ${CYANN}${slm_k}${OFF}" | |
| ;; | |
| *) | |
| # need to add this key | |
| if [ -z "$slm_missing" ]; then | |
| slm_missing="$slm_k" | |
| else | |
| slm_missing="$slm_missing | |
| $slm_k" | |
| fi | |
| ;; | |
| esac | |
| done | |
| echo "$slm_missing" | |
| } | |
| # synopsis: add_gpgkey | |
| # Adds a key to $gpgkeys | |
| add_gpgkey() { | |
| gpgkeys=${gpgkeys+"$gpgkeys | |
| "}"$1" | |
| } | |
| # synopsis: add_sshkey | |
| # Adds a key to $sshkeys | |
| add_sshkey() { | |
| sshkeys=${sshkeys+"$sshkeys | |
| "}"$1" | |
| } | |
| # synopsis: parse_mykeys | |
| # Sets $sshkeys and $gpgkeys based on $mykeys | |
| parse_mykeys() { | |
| # Possible path to the private key: if --confhost variable used. | |
| pkeypath="$1" | |
| # Parse $mykeys into positional params to preserve spaces in filenames | |
| set -f # disable globbing | |
| pm_IFS="$IFS" # save current IFS | |
| IFS=" | |
| " # set IFS to newline | |
| set -- $mykeys | |
| IFS="$pm_IFS" # restore IFS | |
| set +f # re-enable globbing | |
| for pm_k in "$@"; do | |
| # Check for ssh | |
| if wantagent ssh; then | |
| if [ -f "$pm_k" ]; then | |
| add_sshkey "$pm_k" ; continue | |
| elif [ -f "$HOME/.ssh/$pm_k" ]; then | |
| add_sshkey "$HOME/.ssh/$pm_k" ; continue | |
| elif [ -f "$HOME/.ssh2/$pm_k" ]; then | |
| add_sshkey "$HOME/.ssh2/$pm_k" ; continue | |
| elif [ -f "$pkeypath" ]; then | |
| add_sshkey "$pkeypath"; continue | |
| fi | |
| fi | |
| # Check for gpg | |
| if wantagent gpg; then | |
| "${gpg_prog_name}" --list-secret-keys "$pm_k" >/dev/null 2>&1 | |
| if [ $? -eq 0 ]; then | |
| add_gpgkey "$pm_k" ; continue | |
| fi | |
| fi | |
| $ignoreopt || warn "can't find $pm_k; skipping" | |
| continue | |
| done | |
| return 0 | |
| } | |
| # synopsis: setaction | |
| # Sets $myaction or dies if $myaction is already set | |
| setaction() { | |
| if [ -n "$myaction" ]; then | |
| die "you can't specify --$myaction and $1 at the same time" | |
| else | |
| myaction="$1" | |
| fi | |
| } | |
| # synopsis: setagents | |
| # Check validity of agentsopt | |
| setagents() { | |
| if [ -n "$agentsopt" ]; then | |
| agentsopt=$(echo "$agentsopt" | sed 's/,/ /g') | |
| unset new_agentsopt | |
| for a in $agentsopt; do | |
| if command -v ${a}-agent >/dev/null; then | |
| new_agentsopt="${new_agentsopt+$new_agentsopt }${a}" | |
| else | |
| warn "can't find ${a}-agent, removing from list" | |
| fi | |
| done | |
| agentsopt="${new_agentsopt}" | |
| else | |
| for a in ssh; do | |
| command -v ${a}-agent >/dev/null || continue | |
| agentsopt="${agentsopt+$agentsopt }${a}" | |
| done | |
| fi | |
| if [ -z "$agentsopt" ]; then | |
| die "no agents available to start" | |
| fi | |
| } | |
| # synopsis: confpath | |
| # Return private key path if found in ~/.ssh/config SSH configuration file. | |
| # Input: the name of the host we would like to connect to. | |
| confpath() { | |
| h="" | |
| while IFS= read -r line; do | |
| # get the Host directives | |
| case $line in | |
| *"Host "*) h=$(echo $line | awk '{print $2}') ;; | |
| esac | |
| case $line in | |
| *IdentityFile*) | |
| if [ $h = "$1" ]; then | |
| echo $line | awk '{print $2}' | |
| break | |
| fi | |
| esac | |
| done < ~/.ssh/config | |
| } | |
| # synopsis: wantagent prog | |
| # Return 0 (true) or 1 (false) depending on whether prog is one of the agents in | |
| # agentsopt | |
| wantagent() { | |
| case "$agentsopt" in | |
| "$1"|"$1 "*|*" $1 "*|*" $1") | |
| return 0 ;; | |
| *) | |
| return 1 ;; | |
| esac | |
| } | |
| # | |
| # MAIN PROGRAM | |
| # | |
| # parse the command-line | |
| while [ -n "$1" ]; do | |
| case "$1" in | |
| --help|-h) | |
| setaction help | |
| ;; | |
| --stop|-k) | |
| # As of version 2.5, --stop takes an argument. For the sake of | |
| # backward compatibility, only eat the arg if it's one we recognize. | |
| if [ "$2" = mine ]; then | |
| stopwhich=mine; shift | |
| elif [ "$2" = others ]; then | |
| stopwhich=others; shift | |
| elif [ "$2" = all ]; then | |
| stopwhich=all; shift | |
| else | |
| # backward compat | |
| stopwhich=all-warn | |
| fi | |
| ;; | |
| --version|-V) | |
| setaction version | |
| ;; | |
| --agents) | |
| shift | |
| agentsopt="$1" | |
| ;; | |
| --attempts) | |
| shift | |
| if [ "$1" -gt 0 ] 2>/dev/null; then | |
| attempts=$1 | |
| else | |
| die "--attempts requires a numeric argument greater than zero" | |
| fi | |
| ;; | |
| --clear) | |
| clearopt=true | |
| $quickopt && die "--quick and --clear are not compatible" | |
| ;; | |
| --confirm) | |
| confirmopt=true | |
| ;; | |
| --absolute) | |
| absoluteopt=true | |
| ;; | |
| --dir) | |
| shift | |
| case "$1" in | |
| */.*) keydir="$1" ;; | |
| '') die "--dir requires an argument" ;; | |
| *) | |
| if $absoluteopt; then | |
| keydir="$1" | |
| else | |
| keydir="$1/.keychain" # be backward-compatible | |
| fi | |
| ;; | |
| esac | |
| ;; | |
| --env) | |
| shift | |
| if [ -z "$1" ]; then | |
| die "--env requires an argument" | |
| else | |
| envf="$1" | |
| fi | |
| ;; | |
| --eval) | |
| evalopt=true | |
| ;; | |
| --list|-l) | |
| ssh-add -l | |
| quietopt=true | |
| ;; | |
| --list-fp|-L) | |
| ssh-add -L | |
| quietopt=true | |
| ;; | |
| --query) | |
| queryopt=true | |
| ;; | |
| --host) | |
| shift | |
| hostopt="$1" | |
| ;; | |
| --ignore-missing) | |
| ignoreopt=true | |
| ;; | |
| --inherit) | |
| shift | |
| case "$1" in | |
| local|any|local-once|any-once) | |
| inheritwhich="$1" | |
| ;; | |
| *) | |
| die "--inherit requires an argument (local, any, local-once or any-once)" | |
| ;; | |
| esac | |
| ;; | |
| --noinherit) | |
| inheritwhich=none | |
| ;; | |
| --noask) | |
| noaskopt=true | |
| ;; | |
| --nogui) | |
| noguiopt=true | |
| ;; | |
| --nolock) | |
| nolockopt=true | |
| ;; | |
| --lockwait) | |
| shift | |
| if [ "$1" -ge 0 ] 2>/dev/null; then | |
| lockwait="$1" | |
| else | |
| die "--lockwait requires an argument zero or greater." | |
| fi | |
| ;; | |
| --quick|-Q) | |
| quickopt=true | |
| $clearopt && die "--quick and --clear are not compatible" | |
| ;; | |
| --quiet|-q) | |
| quietopt=true | |
| ;; | |
| --confhost|-c) | |
| if [ -e ~/.ssh/config ]; then | |
| sshconfig=true | |
| confhost="$2" | |
| else | |
| warn "~/.ssh/config not found; --confhost/-c option ignored." | |
| fi | |
| ;; | |
| --nocolor) | |
| color=false | |
| ;; | |
| --timeout) | |
| shift | |
| if [ "$1" -gt 0 ] 2>/dev/null; then | |
| timeout=$1 | |
| else | |
| die "--timeout requires a numeric argument greater than zero" | |
| fi | |
| ;; | |
| --systemd) | |
| systemdopt=true | |
| ;; | |
| --gpg2) | |
| gpg_prog_name="gpg2" | |
| ;; | |
| --) | |
| shift | |
| IFS=" | |
| " | |
| mykeys=${mykeys+"$mykeys | |
| "}"$*" | |
| unset IFS | |
| break | |
| ;; | |
| -*) | |
| zero=$(basename "$0") | |
| echo "$zero: unknown option $1" >&2 | |
| $evalopt && { echo; echo "false;"; } | |
| exit 1 | |
| ;; | |
| *) | |
| mykeys=${mykeys+"$mykeys | |
| "}"$1" | |
| ;; | |
| esac | |
| shift | |
| done | |
| # Set filenames *after* parsing command-line options to allow | |
| # modification of $keydir and/or $hostopt | |
| # | |
| # pidf holds the specific name of the keychain .ssh-agent-myhostname file. | |
| # We use the new hostname extension for NFS compatibility. cshpidf is the | |
| # .ssh-agent file with csh-compatible syntax. fishpidf is the .ssh-agent | |
| # file with fish-compatible syntax. lockf is the lockfile, used | |
| # to serialize the execution of multiple ssh-agent processes started | |
| # simultaneously | |
| [ -z "$hostopt" ] && hostopt="${HOSTNAME}" | |
| [ -z "$hostopt" ] && hostopt=$(uname -n 2>/dev/null || echo unknown) | |
| pidf="${keydir}/${hostopt}-sh" | |
| cshpidf="${keydir}/${hostopt}-csh" | |
| fishpidf="${keydir}/${hostopt}-fish" | |
| olockf="${keydir}/${hostopt}-lock" | |
| lockf="${keydir}/${hostopt}-lockf" | |
| # Read the env snippet (especially for things like PATH, but could modify | |
| # basically anything) | |
| if [ -z "$envf" ]; then | |
| envf="${keydir}/${hostopt}-env" | |
| [ -f "$envf" ] || envf="${keydir}/env" | |
| [ -f "$envf" ] || unset envf | |
| fi | |
| if [ -n "$envf" ]; then | |
| . "$envf" | |
| fi | |
| # Don't use color if there's no terminal on stderr | |
| if [ -n "$OFF" ]; then | |
| tty <&2 >/dev/null 2>&1 || color=false | |
| fi | |
| #disable color if necessary, right before our initial newline | |
| $color || unset BLUE CYAN CYANN GREEN PURP OFF RED | |
| qprint #initial newline | |
| mesg "${PURP}keychain ${OFF}${CYANN}${version}${OFF} ~ ${GREEN}http://www.funtoo.org${OFF}" | |
| [ "$myaction" = version ] && { versinfo; exit 0; } | |
| [ "$myaction" = help ] && { versinfo; helpinfo; exit 0; } | |
| # Set up traps | |
| # Don't use signal names because they don't work on Cygwin. | |
| if $clearopt; then | |
| trap '' 2 # disallow ^C until we've had a chance to --clear | |
| trap 'droplock; exit 1' 1 15 # drop the lock on signal | |
| trap 'droplock; exit 0' 0 # drop the lock on exit | |
| else | |
| # Don't use signal names because they don't work on Cygwin. | |
| trap 'droplock; exit 1' 1 2 15 # drop the lock on signal | |
| trap 'droplock; exit 0' 0 # drop the lock on exit | |
| fi | |
| setagents # verify/set $agentsopt | |
| verifykeydir # sets up $keydir | |
| wantagent ssh && testssh # sets $openssh and $sunssh | |
| getuser # sets $me | |
| # Inherit agent info from the environment before loadagents wipes it out. | |
| # Always call this since it checks $inheritopt and sets variables accordingly. | |
| inheritagents | |
| # --stop: kill the existing ssh-agent(s) and quit | |
| if [ -n "$stopwhich" ]; then | |
| if [ "$stopwhich" = all-warn ]; then | |
| warn "--stop without an argument is deprecated; see --help" | |
| stopwhich=all | |
| fi | |
| takelock || die | |
| if [ "$stopwhich" = mine -o "$stopwhich" = others ]; then | |
| loadagents $agentsopt | |
| fi | |
| for a in $agentsopt; do | |
| stopagent $a | |
| done | |
| if [ "$stopwhich" != others ]; then | |
| qprint | |
| exit 0 # stopagent is always successful | |
| fi | |
| fi | |
| # Note regarding locking: if we're trying to be quick, then don't take the lock. | |
| # It will be taken later if we discover we can't be quick. | |
| if $quickopt; then | |
| loadagents $agentsopt # sets ssh_auth_sock, ssh_agent_pid, etc | |
| unset nagentsopt | |
| for a in $agentsopt; do | |
| needstart=true | |
| # Trying to be quick has a price... If we discover the agent isn't running, | |
| # then we'll have to check things again (in startagent) after taking the | |
| # lock. So don't do the initial check unless --quick was specified. | |
| if [ $a = ssh ]; then | |
| sshavail=$(ssh_l) # try to use existing agent | |
| # 0 = found keys, 1 = no keys, 2 = no agent | |
| if [ $? = 0 -o \( $? = 1 -a -z "$mykeys" \) ]; then | |
| mesg "Found existing ssh-agent: ${CYANN}$ssh_agent_pid${OFF}" | |
| needstart=false | |
| fi | |
| elif [ $a = gpg ]; then | |
| # not much way to be quick on this | |
| if [ -n "$gpg_agent_pid" ]; then | |
| case " $(findpids "${gpg_prog_name}") " in | |
| *" $gpg_agent_pid "*) | |
| mesg "Found existing gpg-agent: ${CYANN}$gpg_agent_pid${OFF}" | |
| needstart=false ;; | |
| esac | |
| fi | |
| fi | |
| if $needstart; then | |
| nagentsopt="$nagentsopt $a" | |
| elif $evalopt; then | |
| catpidf $a | |
| fi | |
| done | |
| agentsopt="$nagentsopt" | |
| fi | |
| # If there are no agents remaining, then bow out now... | |
| [ -n "$agentsopt" ] || { qprint; exit 0; } | |
| # --timeout translates almost directly to ssh-add/ssh-agent -t, but ssh.com uses | |
| # minutes and OpenSSH uses seconds | |
| if [ -n "$timeout" ] && wantagent ssh; then | |
| ssh_timeout=$timeout | |
| if $openssh || $sunssh; then | |
| ssh_timeout=$(expr $ssh_timeout \* 60) | |
| fi | |
| ssh_timeout="-t $ssh_timeout" | |
| fi | |
| # There are agents remaining to start, and we now know we can't be quick. Take | |
| # the lock before continuing | |
| takelock || die | |
| loadagents $agentsopt | |
| unset nagentsopt | |
| for a in $agentsopt; do | |
| if $queryopt; then | |
| catpidf_shell sh $a | cut -d\; -f1 | |
| elif startagent $a; then | |
| nagentsopt="${nagentsopt+$nagentsopt }$a" | |
| $evalopt && catpidf $a | |
| fi | |
| done | |
| agentsopt="$nagentsopt" | |
| # If we are just querying the services, exit. | |
| $queryopt && exit 0 | |
| # If there are no agents remaining, then duck out now... | |
| [ -n "$agentsopt" ] || { qprint; exit 0; } | |
| # --confirm translates to ssh-add -c | |
| if $confirmopt && wantagent ssh; then | |
| if $openssh || $sunssh; then | |
| ssh_confirm=-c | |
| else | |
| warn "--confirm only works with OpenSSH" | |
| fi | |
| fi | |
| # --clear: remove all keys from the agent(s) | |
| if $clearopt; then | |
| for a in ${agentsopt}; do | |
| if [ $a = ssh ]; then | |
| sshout=$(ssh-add -D 2>&1) | |
| if [ $? = 0 ]; then | |
| mesg "ssh-agent: $sshout" | |
| else | |
| warn "ssh-agent: $sshout" | |
| fi | |
| elif [ $a = gpg ]; then | |
| kill -1 $gpg_agent_pid 2>/dev/null | |
| mesg "gpg-agent: All identities removed." | |
| else | |
| warn "--clear not supported for ${a}-agent" | |
| fi | |
| done | |
| trap 'droplock' 2 # done clearing, safe to ctrl-c | |
| fi | |
| if $systemdopt; then | |
| for a in $agentsopt; do | |
| systemctl --user set-environment $( catpidf_shell sh $a | cut -d\; -f1 ) | |
| done | |
| fi | |
| # --noask: "don't ask for keys", so we're all done | |
| $noaskopt && { qprint; exit 0; } | |
| # If the --confhost option used, determine the path to the private key as | |
| # written in the ~/.ssh/config and add it to ssh-add. | |
| if $sshconfig; then | |
| pkeypath=$(confpath "$confhost") | |
| eval pkeypath=$pkeypath | |
| fi | |
| # Parse $mykeys into ssh vs. gpg keys; it may be necessary in the future to | |
| # differentiate on the cmdline | |
| parse_mykeys "$pkeypath" || die | |
| # Load ssh keys | |
| if wantagent ssh; then | |
| sshavail=$(ssh_l) # update sshavail now that we're locked | |
| if [ "$myaction" = "list" ]; then | |
| for key in $sshavail end; do | |
| [ "$key" = "end" ] && continue | |
| echo "$key" | |
| done | |
| else | |
| sshkeys="$(ssh_listmissing)" # cache list of missing keys, newline-separated | |
| sshattempts=$attempts | |
| savedisplay="$DISPLAY" | |
| # Attempt to add the keys | |
| while [ -n "$sshkeys" ]; do | |
| mesg "Adding ${CYANN}"$(echo "$sshkeys" | wc -l)"${OFF} ssh key(s): $(echo $sshkeys)" | |
| # Parse $sshkeys into positional params to preserve spaces in filenames. | |
| # This *must* happen after any calls to subroutines because pure Bourne | |
| # shell doesn't restore "$@" following a call. Eeeeek! | |
| set -f # disable globbing | |
| old_IFS="$IFS" # save current IFS | |
| IFS=" | |
| " # set IFS to newline | |
| set -- $sshkeys | |
| IFS="$old_IFS" # restore IFS | |
| set +f # re-enable globbing | |
| if $noguiopt || [ -z "$SSH_ASKPASS" -o -z "$DISPLAY" ]; then | |
| unset DISPLAY # DISPLAY="" can cause problems | |
| unset SSH_ASKPASS # make sure ssh-add doesn't try SSH_ASKPASS | |
| sshout=$(ssh-add ${ssh_timeout} ${ssh_confirm} "$@" 2>&1) | |
| else | |
| sshout=$(ssh-add ${ssh_timeout} ${ssh_confirm} "$@" 2>&1 </dev/null) | |
| fi | |
| if [ $? = 0 ] | |
| then | |
| blurb="" | |
| [ -n "$timeout" ] && blurb="life=${timeout}m" | |
| [ -n "$timeout" ] && $confirmopt && blurb="${blurb}," | |
| $confirmopt && blurb="${blurb}confirm" | |
| [ -n "$blurb" ] && blurb=" (${blurb})" | |
| mesg "ssh-add: Identities added: $(echo $sshkeys)${blurb}" | |
| break | |
| fi | |
| if [ $sshattempts = 1 ]; then | |
| die "Problem adding; giving up" | |
| else | |
| warn "Problem adding; trying again" | |
| fi | |
| # Update the list of missing keys | |
| sshavail=$(ssh_l) | |
| [ $? = 0 ] || die "problem running ssh-add -l" | |
| sshkeys="$(ssh_listmissing)" # remember, newline-separated | |
| # Decrement the countdown | |
| sshattempts=$(expr $sshattempts - 1) | |
| done | |
| [ -n "$savedisplay" ] && DISPLAY="$savedisplay" | |
| fi | |
| fi | |
| # Load gpg keys | |
| if wantagent gpg; then | |
| gpgkeys="$(gpg_listmissing)" # cache list of missing keys, newline-separated | |
| gpgattempts=$attempts | |
| $noguiopt && unset DISPLAY | |
| [ -n "$DISPLAY" ] || unset DISPLAY # DISPLAY="" can cause problems | |
| GPG_TTY=$(tty) ; export GPG_TTY # fall back to ncurses pinentry | |
| # Attempt to add the keys | |
| while [ -n "$gpgkeys" ]; do | |
| tryagain=false | |
| mesg "Adding ${BLUE}"$(echo "$gpgkeys" | wc -l)"${OFF} gpg key(s): $(echo $gpgkeys)" | |
| # Parse $gpgkeys into positional params to preserve spaces in filenames. | |
| # This *must* happen after any calls to subroutines because pure Bourne | |
| # shell doesn't restore "$@" following a call. Eeeeek! | |
| set -f # disable globbing | |
| old_IFS="$IFS" # save current IFS | |
| IFS=" | |
| " # set IFS to newline | |
| set -- $gpgkeys | |
| IFS="$old_IFS" # restore IFS | |
| set +f # re-enable globbing | |
| for k in "$@"; do | |
| echo | env LC_ALL="$pinentry_lc_all" \ | |
| "${gpg_prog_name}" --no-options --use-agent --no-tty --sign --local-user "$k" -o- >/dev/null 2>&1 | |
| [ $? != 0 ] && tryagain=true | |
| done | |
| $tryagain || break | |
| if [ $gpgattempts = 1 ]; then | |
| die "Problem adding (is pinentry installed?); giving up" | |
| else | |
| warn "Problem adding; trying again" | |
| fi | |
| # Update the list of missing keys | |
| gpgkeys="$(gpg_listmissing)" # remember, newline-separated | |
| # Decrement the countdown | |
| gpgattempts=$(expr $gpgattempts - 1) | |
| done | |
| fi | |
| qprint # trailing newline | |
| # vim:sw=4 noexpandtab tw=120 |