Advertisement
Combreal

keychain

Oct 17th, 2018
360
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 47.52 KB | None | 0 0
  1. #!/bin/sh
  2.  
  3. # Copyright 1999-2005 Gentoo Foundation
  4. # Copyright 2007 Aron Griffis <agriffis@n01se.net>
  5. # Copyright 2009-2015 Funtoo Technologies, LLC
  6. # lockfile() Copyright 2009 Parallels, Inc.
  7.  
  8. # Distributed under the terms of the GNU General Public License v2
  9.  
  10. # Originally authored by Daniel Robbins <drobbins@gentoo.org>
  11. # Maintained August 2002 - April 2003 by Seth Chandler <sethbc@gentoo.org>
  12. # Maintained and rewritten April 2004 - July 2007 by Aron Griffis <agriffis@n01se.net>
  13. # Maintained July 2009 - present by Daniel Robbins <drobbins@funtoo.org>
  14.  
  15. version=2.8.1
  16.  
  17. PATH="${PATH:-/usr/bin:/bin:/sbin:/usr/sbin:/usr/ucb}"
  18.  
  19. maintainer="drobbins@funtoo.org"
  20. zero=`basename "$0"`
  21. unset mesglog
  22. unset myaction
  23. unset agentsopt
  24. havelock=false
  25. unset hostopt
  26. ignoreopt=false
  27. noaskopt=false
  28. noguiopt=false
  29. nolockopt=false
  30. lockwait=5
  31. openssh=unknown
  32. sunssh=unknown
  33. confhost=unknown
  34. sshconfig=false
  35. quickopt=false
  36. quietopt=false
  37. clearopt=false
  38. color=true
  39. inheritwhich=local-once
  40. unset stopwhich
  41. unset timeout
  42. unset ssh_timeout
  43. attempts=1
  44. unset sshavail
  45. unset sshkeys
  46. unset gpgkeys
  47. unset mykeys
  48. keydir="${HOME}/.keychain"
  49. unset envf
  50. evalopt=false
  51. queryopt=false
  52. confirmopt=false
  53. absoluteopt=false
  54. systemdopt=false
  55. unset ssh_confirm
  56. unset GREP_OPTIONS
  57. realpath_bin="`command -v realpath`"
  58.  
  59. BLUE=""
  60. CYAN=""
  61. CYANN=""
  62. GREEN=""
  63. RED=""
  64. PURP=""
  65. OFF=""
  66.  
  67. # GNU awk and sed have regex issues in a multibyte environment.  If any locale
  68. # variables are set, then override by setting LC_ALL
  69. unset pinentry_locale
  70. lvars=`locale 2>/dev/null | egrep -v '="?(|POSIX|C)"?$' 2>/dev/null`
  71. if [ -n "$lvars$LANG$LC_ALL" ]; then
  72.     # save LC_ALL so that pinentry-curses works right.  This has always worked
  73.     # correctly for me but peper and kloeri had problems with it.
  74.     pinentry_lc_all="$LC_ALL"
  75.     LC_ALL=C
  76.     export LC_ALL
  77. fi
  78.  
  79. # synopsis: qprint "message"
  80. qprint() {
  81.     $quietopt || echo "$*" >&2
  82. }
  83.  
  84. # synopsis: mesg "message"
  85. # Prettily print something to stderr, honors quietopt
  86. mesg() {
  87.     qprint " ${GREEN}*${OFF} $*"
  88. }
  89.  
  90. # synopsis: warn "message"
  91. # Prettily print a warning to stderr
  92. warn() {
  93.     echo " ${RED}* Warning${OFF}: $*" >&2
  94. }
  95.  
  96. # synopsis: error "message"
  97. # Prettily print an error
  98. error() {
  99.     echo " ${RED}* Error${OFF}: $*" >&2
  100. }
  101.  
  102. # synopsis: die "message"
  103. # Prettily print an error, then abort
  104. die() {
  105.     [ -n "$1" ] && error "$*"
  106.     qprint
  107.     $evalopt && { echo; echo "false;"; }
  108.     exit 1
  109. }
  110.  
  111. # synopsis: versinfo
  112. # Display the version information
  113. versinfo() {
  114.     qprint
  115.     qprint "   Copyright ${CYANN}2002-2006${OFF} Gentoo Foundation;"
  116.     qprint "   Copyright ${CYANN}2007${OFF} Aron Griffis;"
  117.     qprint "   Copyright ${CYANN}2009-2015${OFF} Funtoo Technologies, LLC;"
  118.     qprint "   lockfile() Copyright ${CYANN}2009${OFF} Parallels, Inc."
  119.     qprint
  120.     qprint " Keychain is free software: you can redistribute it and/or modify"
  121.     qprint " it under the terms of the ${CYANN}GNU General Public License version 2${OFF} as"
  122.     qprint " published by the Free Software Foundation."
  123.     qprint
  124. }
  125.  
  126. # synopsis: helpinfo
  127. # Display the help information. There's no really good way to use qprint for
  128. # this...
  129. helpinfo() {
  130.     cat >&1 <<EOHELP
  131. SYNOPSIS
  132.     keychain [ ${GREEN}-hklQqV${OFF} ] [ ${GREEN}--clear${OFF} ${GREEN}--confhost${OFF} ${GREEN}--help${OFF} ${GREEN}--ignore-missing${OFF} ${GREEN}--list${OFF}
  133.      ${GREEN}--noask${OFF} ${GREEN}--nocolor${OFF} ${GREEN}--nogui${OFF} ${GREEN}--nolock${OFF} ${GREEN}--quick${OFF} ${GREEN}--quiet${OFF} ${GREEN}--version${OFF} ]
  134.     [ ${GREEN}--agents${OFF} ${CYAN}list${OFF} ] [ ${GREEN}--attempts${OFF} ${CYAN}num${OFF} ] [ ${GREEN}--dir${OFF} ${CYAN}dirname${OFF} ]
  135.     [ ${GREEN}--host${OFF} ${CYAN}name${OFF} ] [ ${GREEN}--lockwait${OFF} ${CYAN}seconds${OFF} ]
  136.     [ ${GREEN}--stop${OFF} ${CYAN}which${OFF} ] [ ${GREEN}--timeout${OFF} ${CYAN}minutes${OFF} ] [ keys... ]
  137.  
  138. OPTIONS
  139.     ${GREEN}--agents${OFF} ${CYAN}list${OFF}
  140.         Start the agents listed. By default keychain will start ssh-agent if
  141.         it is found in your path. The list should be comma-separated, for
  142.         example "gpg,ssh"
  143.  
  144.     ${GREEN}--attempts${OFF} ${CYAN}num${OFF}
  145.         Try num times to add keys before giving up. The default is 1.
  146.  
  147.     ${GREEN}--clear${OFF}
  148.         Delete all of ssh-agent's keys. Typically this is used in
  149.         .bash_profile. The theory behind this is that keychain should assume
  150.         that you are an intruder until proven otherwise. However, while this
  151.         option increases security, it still allows your cron jobs to use
  152.         your ssh keys when you're logged out.
  153.  
  154.     ${GREEN}--confhost${OFF}
  155.         By default, keychain will look for key pairs in the ~/.ssh/
  156.         directory. The ${GREEN}--confhost${OFF} option will inform keychain to look in
  157.         ~/.ssh/config for IdentityFile settings defined for particular
  158.         hosts, and use these paths to locate keys.
  159.  
  160.     ${GREEN}--confirm${OFF}
  161.         Keys are subject to interactive confirmation by the SSH_ASKPASS
  162.         program before being used for authentication. See the ${GREEN}-c${OFF} option for
  163.         ssh-add(1).
  164.  
  165.     ${GREEN}--absolute${OFF}
  166.         Any arguments to "--dir" are interpreted to be absolute. The default
  167.         behavior is to append "/.keychain" to the argument for backwards
  168.         compatibility.
  169.  
  170.     ${GREEN}--dir${OFF} ${CYAN}dirname${OFF}
  171.         Keychain will use dirname rather than \$HOME/.keychain
  172.  
  173.     ${GREEN}--query${OFF}
  174.         Keychain will print lines in KEY=value format representing the
  175.         values which are set by the agents.
  176.  
  177.     ${GREEN}--eval${OFF}
  178.         Keychain will print lines to be evaluated in the shell on stdout. It
  179.         respects the SHELL environment variable to determine if Bourne shell
  180.         or C shell output is expected.
  181.  
  182.     ${GREEN}--env${OFF} ${CYAN}filename${OFF}
  183.         After parsing options, keychain will load additional environment
  184.         settings from "filename". By default, if "--env" is not given, then
  185.         keychain will attempt to load from ~/.keychain/[hostname]-env or
  186.         alternatively ~/.keychain/env. The purpose of this file is to
  187.         override settings such as PATH, in case ssh is stored in a
  188.         non-standard place.
  189.  
  190.     ${GREEN}-h${OFF} ${GREEN}--help${OFF}
  191.         Show help that looks remarkably like this man-page. As of 2.6.10,
  192.         help is sent to stdout so it can be easily piped to a pager.
  193.  
  194.     ${GREEN}--host${OFF} ${CYAN}name${OFF}
  195.         Set alternate hostname for creation of pidfiles
  196.  
  197.     ${GREEN}--ignore-missing${OFF}
  198.         Don't warn if some keys on the command-line can't be found. This is
  199.         useful for situations where you have a shared .bash_profile, but
  200.         your keys might not be available on every machine where keychain is
  201.         run.
  202.  
  203.     ${GREEN}--inherit${OFF} ${CYAN}which${OFF}
  204.         Attempt to inherit agent variables from the environment. This can be
  205.         useful in a variety of circumstances, for example when ssh-agent is
  206.         started by gdm. The following values are valid for "which":
  207.  
  208.         local       Inherit when a pid (e.g. SSH_AGENT_PID) is set in the
  209.                     environment. This disallows inheriting a forwarded
  210.                     agent.
  211.  
  212.         any         Inherit when a sock (e.g. SSH_AUTH_SOCK) is set in the
  213.                     environment. This allows inheriting a forwarded agent.
  214.  
  215.         local-once  Same as "local", but only inherit if keychain isn't
  216.                     already providing an agent.
  217.  
  218.         any-once    Same as "any", but only inherit if keychain isn't
  219.                     already providing an agent.
  220.  
  221.         By default, keychain-2.5.0 and later will behave as if "--inherit
  222.         local-once" is specified. You should specify "--noinherit" if you
  223.         want the older behavior.
  224.  
  225.     ${GREEN}-l${OFF} ${GREEN}--list${OFF}
  226.         List signatures of all active SSH keys, and exit, similar to
  227.         "ssh-add ${GREEN}-l${OFF}".
  228.  
  229.     ${GREEN}--lockwait${OFF} ${CYAN}seconds${OFF}
  230.         How long to wait for the lock to become available. Defaults to 5
  231.         seconds. Specify a value of zero or more. If the lock cannot be
  232.         acquired within the specified number of seconds, then this keychain
  233.         process will forcefully acquire the lock.
  234.  
  235.     ${GREEN}--noask${OFF}
  236.         This option tells keychain do everything it normally does (ensure
  237.         ssh-agent is running, set up the ~/.keychain/[hostname]-{c}sh files)
  238.         except that it will not prompt you to add any of the keys you
  239.         specified if they haven't yet been added to ssh-agent.
  240.  
  241.     ${GREEN}--nocolor${OFF}
  242.         Disable color hilighting for non ANSI-compatible terms.
  243.  
  244.     ${GREEN}--nogui${OFF}
  245.         Don't honor SSH_ASKPASS, if it is set. This will cause ssh-add to
  246.         prompt on the terminal instead of using a graphical program.
  247.  
  248.     ${GREEN}--noinherit${OFF}
  249.         Don't inherit any agent processes, overriding the default "--inherit
  250.         local-once"
  251.  
  252.     ${GREEN}--nolock${OFF}
  253.         Don't attempt to use a lockfile while manipulating files, pids and
  254.         keys.
  255.  
  256.     ${GREEN}-k${OFF} ${GREEN}--stop${OFF} ${CYAN}which${OFF}
  257.         Kill currently running agent processes. The following values are
  258.         valid for "which":
  259.  
  260.     ${GREEN}--systemd${OFF}
  261.         Inject environment variables into the systemd ${GREEN}--user${OFF} session.
  262.  
  263.         all      Kill all agent processes and quit keychain immediately.
  264.                  Prior to keychain-2.5.0, this was the behavior of the bare
  265.                  "--stop" option.
  266.  
  267.         others   Kill agent processes other than the one keychain is
  268.                  providing. Prior to keychain-2.5.0, keychain would do this
  269.                  automatically. The new behavior requires that you specify
  270.                  it explicitly if you want it.
  271.  
  272.         mine     Kill keychain's agent processes, leaving other agents
  273.                  alone.
  274.  
  275.     ${GREEN}-Q${OFF} ${GREEN}--quick${OFF}
  276.         If an ssh-agent process is running then use it. Don't verify the
  277.         list of keys, other than making sure it's non-empty. This option
  278.         avoids locking when possible so that multiple terminals can be
  279.         opened simultaneously without waiting on each other.
  280.  
  281.     ${GREEN}-q${OFF} ${GREEN}--quiet${OFF}
  282.         Only print messages in case of warning, error or required
  283.         interactivity. As of version 2.6.10, this also suppresses
  284.         "Identities added" messages for ssh-agent.
  285.  
  286.     ${GREEN}--timeout${OFF} ${CYAN}minutes${OFF}
  287.         Set a timeout in minutes on your keys. This is conveyed to ssh-agent
  288.         which does the actual timing out of keys since keychain doesn't run
  289.         continuously.
  290.  
  291.     ${GREEN}-V${OFF} ${GREEN}--version${OFF}
  292.         Show version information.
  293.  
  294. EOHELP
  295. }
  296.  
  297. # synopsis: testssh
  298. # Figure out which ssh is in use, set the global boolean $openssh and $sunssh
  299. testssh() {
  300.     # Query local host for SSH application, presently supporting
  301.     # OpenSSH, Sun SSH, and ssh.com
  302.     openssh=false
  303.     sunssh=false
  304.     case "`ssh -V 2>&1`" in
  305.         *OpenSSH*) openssh=true ;;
  306.         *Sun?SSH*) sunssh=true ;;
  307.     esac
  308. }
  309.  
  310. # synopsis: getuser
  311. # Set the global string $me
  312. getuser() {
  313.     # whoami gives euid, which might be different from USER or LOGNAME
  314.     me=`whoami` || die "Who are you?  whoami doesn't know..."
  315. }
  316.  
  317. # synopsis: getos
  318. # Set the global string $OSTYPE
  319. getos() {
  320.     OSTYPE=`uname` || die 'uname failed'
  321. }
  322.  
  323. # synopsis: verifykeydir
  324. # Make sure the key dir is set up correctly.  Exits on error.
  325. verifykeydir() {
  326.     # Create keydir if it doesn't exist already
  327.     if [ -f "${keydir}" ]; then
  328.         die "${keydir} is a file (it should be a directory)"
  329.     # Solaris 9 doesn't have -e; using -d....
  330.     elif [ ! -d "${keydir}" ]; then
  331.         ( umask 0077 && mkdir "${keydir}"; ) || die "can't create ${keydir}"
  332.     fi
  333. }
  334.  
  335. lockfile() {
  336.     # This function originates from Parallels Inc.'s OpenVZ vpsreboot script
  337.  
  338.     # Description: This function attempts to acquire the lock. If it succeeds,
  339.     # it returns 0. If it fails, it returns 1. This function retuns immediately
  340.     # and only tries to acquire the lock once.
  341.  
  342.         local tmpfile="$lockf.$$"
  343.  
  344.         echo $$ >"$tmpfile" 2>/dev/null || exit
  345.         if ln "$tmpfile" "$lockf" 2>/dev/null; then
  346.                 rm -f "$tmpfile"
  347.         havelock=true && return 0
  348.         fi
  349.         if kill -0 `cat $lockf 2>/dev/null` 2>/dev/null; then
  350.                 rm -f "$tmpfile"
  351.             return 1
  352.     fi
  353.         if ln "$tmpfile" "$lockf" 2>/dev/null; then
  354.                 rm -f "$tmpfile"
  355.         havelock=true && return 0
  356.         fi
  357.         rm -f "$tmpfile" "$lockf" && return 1
  358. }
  359.  
  360. takelock() {
  361.     # Description: This function calls lockfile() multiple times if necessary
  362.     # to try to acquire the lock. It returns 0 on success and 1 on failure.
  363.     # Change in behavior: if timeout expires, we will forcefully acquire lock.
  364.  
  365.     [ "$havelock" = "true" ] && return 0
  366.     [ "$nolockopt" = "true" ] && return 0
  367.  
  368.     # First attempt:
  369.     lockfile && return 0
  370.  
  371.     local counter=0
  372.     mesg "Waiting $lockwait seconds for lock..."
  373.     while [ "$counter" -lt "$(( $lockwait * 2 ))" ]
  374.     do
  375.         lockfile && return 0
  376.         sleep 0.5; counter=$(( $counter + 1 ))
  377.     done
  378.     rm -f "$lockf" && lockfile && return 0
  379.     return 1
  380. }
  381.  
  382.  
  383. # synopsis: droplock
  384. # Drops the lock if we're holding it.
  385. droplock() {
  386.     $havelock && [ -n "$lockf" ] && rm -f "$lockf"
  387. }
  388.  
  389. # synopsis: findpids [prog]
  390. # Returns a space-separated list of agent pids.
  391. # prog can be ssh or gpg, defaults to ssh.  Note that if another prog is ever
  392. # added, need to pay attention to the length for Solaris compatibility.
  393. findpids() {
  394.     fp_prog=${1-ssh}
  395.     unset fp_psout
  396.  
  397.     # Different systems require different invocations of ps.  Try to generalize
  398.     # the best we can.  The only requirement is that the agent command name
  399.     # appears in the line, and the PID is the first item on the line.
  400.     [ -n "$OSTYPE" ] || getos
  401.  
  402.     # Try systems where we know what to do first
  403.     case "$OSTYPE" in
  404.         AIX|*bsd*|*BSD*|CYGWIN|darwin*|Linux|linux-gnu|OSF1)
  405.             fp_psout=`ps x 2>/dev/null` ;;      # BSD syntax
  406.         HP-UX)
  407.             fp_psout=`ps -u $me 2>/dev/null` ;; # SysV syntax
  408.         SunOS)
  409.             case `uname -r` in
  410.                 [56]*)
  411.                     fp_psout=`ps -u $me 2>/dev/null` ;; # SysV syntax
  412.                 *)
  413.                     fp_psout=`ps x 2>/dev/null` ;;      # BSD syntax
  414.             esac ;;
  415.         GNU|gnu)
  416.             fp_psout=`ps -g 2>/dev/null` ;;     # GNU Hurd syntax
  417.     esac
  418.  
  419.     # If we didn't get a match above, try a list of possibilities...
  420.     # The first one will probably fail on systems supporting only BSD syntax.
  421.     if [ -z "$fp_psout" ]; then
  422.         fp_psout=`UNIX95=1 ps -u $me -o pid,comm 2>/dev/null | grep '^ *[0-9]'`
  423.         [ -z "$fp_psout" ] && fp_psout=`ps x 2>/dev/null`
  424.     fi
  425.  
  426.     # Return the list of pids; ignore case for Cygwin.
  427.     # Check only 8 characters since Solaris truncates at that length.
  428.     # Ignore defunct ssh-agents (bug 28599)
  429.     if [ -n "$fp_psout" ]; then
  430.         echo "$fp_psout" | \
  431.             awk "BEGIN{IGNORECASE=1} /defunct/{next}
  432.                 /$fp_prog-[a]gen/{print \$1}" | xargs
  433.         return 0
  434.     fi
  435.  
  436.     # If none worked, we're stuck
  437.     error "Unable to use \"ps\" to scan for $fp_prog-agent processes"
  438.     error "Please report to $maintainer via http://bugs.gentoo.org"
  439.     return 1
  440. }
  441.  
  442. # synopsis: stopagent [prog]
  443. # --stop tells keychain to kill the existing agent(s)
  444. # prog can be ssh or gpg, defaults to ssh.
  445. stopagent() {
  446.     stop_prog=${1-ssh}
  447.     eval stop_except=\$\{${stop_prog}_agent_pid\}
  448.     stop_mypids=`findpids "$stop_prog"`
  449.     [ $? = 0 ] || die
  450.  
  451.     if [ -z "$stop_mypids" ]; then
  452.         mesg "No $stop_prog-agent(s) found running"
  453.         return 0
  454.     fi
  455.  
  456.     case "$stopwhich" in
  457.         all)
  458.             kill $stop_mypids >/dev/null 2>&1
  459.             mesg "All ${CYANN}$me${OFF}'s $stop_prog-agents stopped: ${CYANN}$stop_mypids${OFF}"
  460.             ;;
  461.  
  462.         others)
  463.             # Try to handle the case where we *will* inherit a pid
  464.             kill -0 $stop_except >/dev/null 2>&1
  465.             if [ -z "$stop_except" -o $? != 0 -o \
  466.                     "$inheritwhich" = local -o "$inheritwhich" = any ]; then
  467.                 if [ "$inheritwhich" != none ]; then
  468.                     eval stop_except=\$\{inherit_${stop_prog}_agent_pid\}
  469.                     kill -0 $stop_except >/dev/null 2>&1
  470.                     if [ -z "$stop_except" -o $? != 0 ]; then
  471.                         # Handle ssh2
  472.                         eval stop_except=\$\{inherit_${stop_prog}2_agent_pid\}
  473.                     fi
  474.                 fi
  475.             fi
  476.  
  477.             # Filter out the running agent pid
  478.             unset stop_mynewpids
  479.             for stop_x in $stop_mypids; do
  480.                 [ $stop_x -eq $stop_except ] 2>/dev/null && continue
  481.                 stop_mynewpids="${stop_mynewpids+$stop_mynewpids }$stop_x"
  482.             done
  483.  
  484.             if [ -n "$stop_mynewpids" ]; then
  485.                 kill $stop_mynewpids >/dev/null 2>&1
  486.                 mesg "Other ${CYANN}$me${OFF}'s $stop_prog-agents stopped: ${CYANN}$stop_mynewpids${OFF}"
  487.             else
  488.                 mesg "No other $stop_prog-agent(s) than keychain's $stop_except found running"
  489.             fi
  490.             ;;
  491.  
  492.         mine)
  493.             if [ $stop_except -gt 0 ] 2>/dev/null; then
  494.                 kill $stop_except >/dev/null 2>&1
  495.                 mesg "Keychain $stop_prog-agents stopped: ${CYANN}$stop_except${OFF}"
  496.             else
  497.                 mesg "No keychain $stop_prog-agent found running"
  498.             fi
  499.             ;;
  500.     esac
  501.  
  502.     # remove pid files if keychain-controlled
  503.     if [ "$stopwhich" != others ]; then
  504.         if [ "$stop_prog" != ssh ]; then
  505.             rm -f "${pidf}-$stop_prog" "${cshpidf}-$stop_prog" "${fishpidf}-$stop_prog" 2>/dev/null
  506.         else
  507.             rm -f "${pidf}" "${cshpidf}" "${fishpidf}" 2>/dev/null
  508.         fi
  509.  
  510.         eval unset ${stop_prog}_agent_pid
  511.     fi
  512. }
  513.  
  514. # synopsis: inheritagents
  515. # Save agent variables from the environment before they get wiped out
  516. inheritagents() {
  517.     # Verify these global vars are null
  518.     unset inherit_ssh_auth_sock inherit_ssh_agent_pid
  519.     unset inherit_ssh2_auth_sock inherit_ssh2_agent_sock
  520.     unset inherit_gpg_agent_info inherit_gpg_agent_pid
  521.  
  522.     # Save variables so we can inherit a running agent
  523.     if [ "$inheritwhich" != none ]; then
  524.         if wantagent ssh; then
  525.             if [ -n "$SSH_AUTH_SOCK" ]; then
  526.                 inherit_ssh_auth_sock="$SSH_AUTH_SOCK"
  527.                 inherit_ssh_agent_pid="$SSH_AGENT_PID"
  528.             fi
  529.  
  530.             if [ -n "$SSH2_AUTH_SOCK" ]; then
  531.                 inherit_ssh2_auth_sock="$SSH2_AUTH_SOCK"
  532.                 inherit_ssh2_agent_pid="$SSH2_AGENT_PID"
  533.             fi
  534.         fi
  535.  
  536.         if wantagent gpg; then
  537.             if [ -n "$GPG_AGENT_INFO" ]; then
  538.                 inherit_gpg_agent_info="$GPG_AGENT_INFO"
  539.                 inherit_gpg_agent_pid=`echo "$GPG_AGENT_INFO" | cut -f2 -d:`
  540.             # GnuPG v.2.1+ removes $GPG_AGENT_INFO
  541.             elif [ -S "${GNUPGHOME:=$HOME/.gnupg}/S.gpg-agent" ]; then
  542.                 inherit_gpg_agent_pid=$(findpids gpg)
  543.                 inherit_gpg_agent_info="$GNUPGHOME/S.gpg-agent:${inherit_gpg_agent_pid}:1"
  544.             fi
  545.         fi
  546.     fi
  547. }
  548.  
  549. # synopsis: validinherit
  550. # Test inherit_* variables for validity
  551. validinherit() {
  552.     vi_agent="$1"
  553.     vi_status=0
  554.  
  555.     if [ "$vi_agent" = ssh ]; then
  556.         if [ -n "$inherit_ssh_auth_sock" ]; then
  557.             ls "$inherit_ssh_auth_sock" >/dev/null 2>&1
  558.             if [ $? != 0 ]; then
  559.                 warn "SSH_AUTH_SOCK in environment is invalid; ignoring it"
  560.                 unset inherit_ssh_auth_sock inherit_ssh_agent_pid
  561.                 vi_status=1
  562.             fi
  563.         fi
  564.  
  565.         if [ -n "$inherit_ssh2_auth_sock" ]; then
  566.             ls "$inherit_ssh2_auth_sock" >/dev/null 2>&1
  567.             if [ $? != 0 ]; then
  568.                 warn "SSH2_AUTH_SOCK in environment is invalid; ignoring it"
  569.                 unset inherit_ssh2_auth_sock inherit_ssh2_agent_pid
  570.                 vi_status=1
  571.             fi
  572.         fi
  573.  
  574.     elif [ "$vi_agent" = gpg ]; then
  575.         if [ -n "$inherit_gpg_agent_pid" ]; then
  576.             kill -0 "$inherit_gpg_agent_pid" >/dev/null 2>&1
  577.             if [ $? != 0 ]; then
  578.                 unset inherit_gpg_agent_pid inherit_gpg_agent_info
  579.                 warn "GPG_AGENT_INFO in environment is invalid; ignoring it"
  580.                 vi_status=1
  581.             fi
  582.         fi
  583.     fi
  584.  
  585.     return $vi_status
  586. }
  587.  
  588. # synopsis: catpidf_shell shell agents...
  589. # cat the pid files for the given agents.  This is used by loadagents and also
  590. # for keychain output when --eval is given.
  591. catpidf_shell() {
  592.     case "$1" in
  593.         */fish|fish) cp_pidf="$fishpidf" ;;
  594.         *csh)        cp_pidf="$cshpidf" ;;
  595.         *)           cp_pidf="$pidf" ;;
  596.     esac
  597.     shift
  598.  
  599.     for cp_a in "$@"; do
  600.         case "${cp_a}" in
  601.             ssh) [ -f "$cp_pidf" ] && cat "$cp_pidf" ;;
  602.             *)   [ -f "${cp_pidf}-$cp_a" ] && cat "${cp_pidf}-$cp_a" ;;
  603.         esac
  604.         echo
  605.     done
  606.  
  607.     return 0
  608. }
  609.  
  610. # synopsis: catpidf agents...
  611. # cat the pid files for the given agents, appropriate for the current value of
  612. # $SHELL.  This is used for keychain output when --eval is given.
  613. catpidf() {
  614.     catpidf_shell "$SHELL" "$@"
  615. }
  616.  
  617. # synopsis: loadagents agents...
  618. # Load agent variables from $pidf and copy implementation-specific environment
  619. # variables into generic global strings
  620. loadagents() {
  621.     for la_a in "$@"; do
  622.         case "$la_a" in
  623.             ssh)
  624.                 unset SSH_AUTH_SOCK SSH_AGENT_PID SSH2_AUTH_SOCK SSH2_AGENT_PID
  625.                 eval "`catpidf_shell sh $la_a`"
  626.                 if [ -n "$SSH_AUTH_SOCK" ]; then
  627.                     ssh_auth_sock=$SSH_AUTH_SOCK
  628.                     ssh_agent_pid=$SSH_AGENT_PID
  629.                 elif [ -n "$SSH2_AUTH_SOCK" ]; then
  630.                     ssh_auth_sock=$SSH2_AUTH_SOCK
  631.                     ssh_agent_pid=$SSH2_AGENT_PID
  632.                 else
  633.                     unset ssh_auth_sock ssh_agent_pid
  634.                 fi
  635.                 ;;
  636.  
  637.             gpg)
  638.                 unset GPG_AGENT_INFO
  639.                 eval "`catpidf_shell sh $la_a`"
  640.                 if [ -n "$GPG_AGENT_INFO" ]; then
  641.                     la_IFS="$IFS"  # save current IFS
  642.                     IFS=':'        # set IFS to colon to separate PATH
  643.                     set -- $GPG_AGENT_INFO
  644.                     IFS="$la_IFS"  # restore IFS
  645.                     gpg_agent_pid=$2
  646.                 fi
  647.                 ;;
  648.  
  649.             *)
  650.                 eval "`catpidf_shell sh $la_a`"
  651.                 ;;
  652.         esac
  653.     done
  654.  
  655.     return 0
  656. }
  657.  
  658. # synopsis: startagent [prog]
  659. # Starts an agent if it isn't already running.
  660. # Requires $ssh_agent_pid
  661. startagent() {
  662.     start_prog=${1-ssh}
  663.     start_proto=${2-${start_prog}}
  664.     unset start_pid
  665.     start_inherit_pid=none
  666.     start_mypids=`findpids "$start_prog"`
  667.     [ $? = 0 ] || die
  668.  
  669.     # Unfortunately there isn't much way to genericize this without introducing
  670.     # a lot more supporting code/structures.
  671.     if [ "$start_prog" = ssh ]; then
  672.         start_pidf="$pidf"
  673.         start_cshpidf="$cshpidf"
  674.         start_fishpidf="$fishpidf"
  675.         start_pid="$ssh_agent_pid"
  676.         if [ -n "$inherit_ssh_auth_sock" -o -n "$inherit_ssh2_auth_sock" ]; then
  677.             if [ -n "$inherit_ssh_agent_pid" ]; then
  678.                 start_inherit_pid="$inherit_ssh_agent_pid"
  679.             elif [ -n "$inherit_ssh2_agent_pid" ]; then
  680.                 start_inherit_pid="$inherit_ssh2_agent_pid"
  681.             else
  682.                 start_inherit_pid="forwarded"
  683.             fi
  684.         fi
  685.     else
  686.         start_pidf="${pidf}-$start_prog"
  687.         start_cshpidf="${cshpidf}-$start_prog"
  688.         start_fishpidf="${fishpidf}-$start_prog"
  689.         if [ "$start_prog" = gpg ]; then
  690.             start_pid="$gpg_agent_pid"
  691.             if [ -n "$inherit_gpg_agent_pid" ]; then
  692.                 start_inherit_pid="$inherit_gpg_agent_pid"
  693.             fi
  694.         else
  695.             error "I don't know how to start $start_prog-agent (1)"
  696.             return 1
  697.         fi
  698.     fi
  699.     [ "$start_pid" -gt 0 ] 2>/dev/null || start_pid=none
  700.  
  701.     # This hack makes the case statement easier
  702.     if [ "$inheritwhich" = any -o "$inheritwhich" = any-once ]; then
  703.         start_fwdflg=forwarded
  704.     else
  705.         unset start_fwdflg
  706.     fi
  707.  
  708.     # Check for an existing agent
  709.     start_tester="$inheritwhich: $start_mypids $start_fwdflg "
  710.     case "$start_tester" in
  711.         none:*" $start_pid "*|*-once:*" $start_pid "*)
  712.             mesg "Found existing ${start_prog}-agent: ${CYANN}$start_pid${OFF}"
  713.             return 0
  714.             ;;
  715.  
  716.         *:*" $start_inherit_pid "*)
  717.             # This test was postponed until now to prevent generating warnings
  718.             validinherit "$start_prog"
  719.             if [ $? != 0 ]; then
  720.                 # inherit_* vars have been removed from the environment.  Try
  721.                 # again now
  722.                 startagent "$start_prog"
  723.                 return $?
  724.             fi
  725.             mesg "Inheriting ${start_prog}-agent ($start_inherit_pid)"
  726.             ;;
  727.  
  728.         *)
  729.             # start_inherit_pid might be "forwarded" which we don't allow with,
  730.             # for example, local-once (the default setting)
  731.             start_inherit_pid=none
  732.             ;;
  733.     esac
  734.  
  735.     # Init the bourne-formatted pidfile
  736.     ( umask 0177 && :> "$start_pidf"; )
  737.     if [ $? != 0 ]; then
  738.         rm -f "$start_pidf" "$start_cshpidf" "$start_fishpidf" 2>/dev/null
  739.         error "can't create $start_pidf"
  740.         return 1
  741.     fi
  742.  
  743.     # Init the csh-formatted pidfile
  744.     ( umask 0177 && :> "$start_cshpidf"; )
  745.     if [ $? != 0 ]; then
  746.         rm -f "$start_pidf" "$start_cshpidf" "$start_fishpidf" 2>/dev/null
  747.         error "can't create $start_cshpidf"
  748.         return 1
  749.     fi
  750.  
  751.     # Init the fish-formatted pidfile
  752.     ( umask 0177 && :> "$start_fishpidf"; )
  753.     if [ $? != 0 ]; then
  754.         rm -f "$start_pidf" "$start_cshpidf" "$start_fishpidf" 2>/dev/null
  755.         error "can't create $start_fishpidf"
  756.         return 1
  757.     fi
  758.  
  759.     # Determine content for files
  760.     unset start_out
  761.     if [ "$start_inherit_pid" = none ]; then
  762.  
  763.         # Start the agent.
  764.         # Branch again since the agents start differently
  765.         mesg "Starting ${start_prog}-agent..."
  766.         if [ "$start_prog" = ssh ]; then
  767.             start_out=`ssh-agent`
  768.         elif [ "$start_prog" = gpg ]; then
  769.             if [ -n "${timeout}" ]; then
  770.                 start_gpg_timeout="--default-cache-ttl `expr $timeout \* 60`"
  771.             else
  772.                 unset start_gpg_timeout
  773.             fi
  774.             # the 1.9.x series of gpg spews debug on stderr
  775.             start_out=`gpg-agent --daemon --write-env-file $start_gpg_timeout 2>/dev/null`
  776.         else
  777.             error "I don't know how to start $start_prog-agent (2)"
  778.             return 1
  779.         fi
  780.         if [ $? != 0 -a $? != 2 ]; then
  781.             rm -f "$start_pidf" "$start_cshpidf" "$start_fishpidf" 2>/dev/null
  782.             error "Failed to start ${start_prog}-agent"
  783.             return 1
  784.         fi
  785.  
  786.     elif [ "$start_prog" = ssh -a -n "$inherit_ssh_auth_sock" ]; then
  787.         start_out="SSH_AUTH_SOCK=$inherit_ssh_auth_sock; export SSH_AUTH_SOCK;"
  788.         if [ "$inherit_ssh_agent_pid" -gt 0 ] 2>/dev/null; then
  789.             start_out="$start_out
  790. SSH_AGENT_PID=$inherit_ssh_agent_pid; export SSH_AGENT_PID;"
  791.         fi
  792.     elif [ "$start_prog" = ssh -a -n "$inherit_ssh2_auth_sock" ]; then
  793.         start_out="SSH2_AUTH_SOCK=$inherit_ssh2_auth_sock; export SSH2_AUTH_SOCK;
  794. SSH2_AGENT_PID=$inherit_ssh2_agent_pid; export SSH2_AGENT_PID;"
  795.         if [ "$inherit_ssh2_agent_pid" -gt 0 ] 2>/dev/null; then
  796.             start_out="$start_out
  797. SSH2_AGENT_PID=$inherit_ssh2_agent_pid; export SSH2_AGENT_PID;"
  798.         fi
  799.    
  800.     elif [ "$start_prog" = gpg -a -n "$inherit_gpg_agent_info" ]; then
  801.         start_out="GPG_AGENT_INFO=$inherit_gpg_agent_info; export GPG_AGENT_INFO;"
  802.  
  803.     else
  804.         die "something bad happened"    # should never be here
  805.     fi
  806.  
  807.     # Add content to pidfiles.
  808.     # Some versions of ssh-agent don't understand -s, which means to
  809.     # generate Bourne shell syntax.  It appears they also ignore SHELL,
  810.     # according to http://bugs.gentoo.org/show_bug.cgi?id=52874
  811.     # So make no assumptions.
  812.     start_out=`echo "$start_out" | grep -v 'Agent pid'`
  813.     case "$start_out" in
  814.         setenv*)
  815.             echo "$start_out" >"$start_cshpidf"
  816.             echo "$start_out" | awk '{print $2"="$3" export "$2";"}' >"$start_pidf"
  817.             ;;
  818.         *)
  819.             echo "$start_out" >"$start_pidf"
  820.             echo "$start_out" | sed 's/;.*/;/' | sed 's/=/ /' | sed 's/^/setenv /' >"$start_cshpidf"
  821.             echo "$start_out" | sed 's/;.*/;/' | sed 's/^\(.*\)=\(.*\);/set -e \1; set -x -U \1 \2;/' >"$start_fishpidf"
  822.             ;;
  823.     esac
  824.  
  825.     # Hey the agent should be started now... load it up!
  826.     loadagents "$start_prog"
  827. }
  828.  
  829. # synopsis: extract_fingerprints
  830. # Extract the fingerprints from standard input, returns space-separated list.
  831. # Utility routine for ssh_l and ssh_f
  832. extract_fingerprints() {
  833.     while read ef_line; do
  834.         case "$ef_line" in
  835.             *\ *\ [0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:*)
  836.                 # Sun SSH spits out different things depending on the type of
  837.                 # key.  For example:
  838.                 #   md5 1024 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_dsa(DSA)
  839.                 #   2048 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_rsa.pub
  840.                 echo "$ef_line" | cut -f3 -d' '
  841.                 ;;
  842.             *\ [0-9a-fA-F][0-9a-fA-F]:[0-9a-fA-F][0-9a-fA-F]:*)
  843.                 # The more consistent OpenSSH format, we hope
  844.                 #   1024 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 /home/barney/.ssh/id_dsa (DSA)
  845.                 echo "$ef_line" | cut -f2 -d' '
  846.                 ;;
  847.             *\ SHA256:[0-9a-zA-Z\+\/=]*|*\ MD5:[0-9a-zA-Z\+\/=]*)
  848.                 # The new OpenSSH 6.8+ format,
  849.                 #   1024 SHA256:mVPwvezndPv/ARoIadVY98vAC0g+P/5633yTC4d/wXE /home/barney/.ssh/id_dsa (DSA)
  850.                 echo "$ef_line" | cut -f2 -d' '
  851.                 ;;
  852.             *)
  853.                 # Fall back to filename.  Note that commercial ssh is handled
  854.                 # explicitly in ssh_l and ssh_f, so hopefully this rule will
  855.                 # never fire.
  856.                 warn "Can't determine fingerprint from the following line, falling back to filename"
  857.                 mesg "$ef_line"
  858.                 basename "$ef_line" | sed 's/[ (].*//'
  859.                 ;;
  860.         esac
  861.     done | xargs
  862. }
  863.  
  864. # synopsis: ssh_l
  865. # Return space-separated list of known fingerprints
  866. ssh_l() {
  867.     sl_mylist=`ssh-add -l 2>/dev/null`
  868.     sl_retval=$?
  869.  
  870.     if $openssh; then
  871.         # Error codes:
  872.         #   0  success
  873.         #   1  OpenSSH_3.8.1p1 on Linux: no identities (not an error)
  874.         #      OpenSSH_3.0.2p1 on HP-UX: can't connect to auth agent
  875.         #   2  can't connect to auth agent
  876.         case $sl_retval in
  877.             0)
  878.                 echo "$sl_mylist" | extract_fingerprints
  879.                 ;;
  880.             1)
  881.                 case "$sl_mylist" in
  882.                     *"open a connection"*) sl_retval=2 ;;
  883.                 esac
  884.                 ;;
  885.         esac
  886.         return $sl_retval
  887.  
  888.     elif $sunssh; then
  889.         # Error codes (from http://docs.sun.com/db/doc/817-3936/6mjgdbvio?a=view)
  890.         #   0  success (even when there are no keys)
  891.         #   1  error
  892.         case $sl_retval in
  893.             0)
  894.                 echo "$sl_mylist" | extract_fingerprints
  895.                 ;;
  896.             1)
  897.                 case "$sl_mylist" in
  898.                     *"open a connection"*) sl_retval=2 ;;
  899.                 esac
  900.                 ;;
  901.         esac
  902.         return $sl_retval
  903.  
  904.     else
  905.         # Error codes:
  906.         #   0  success - however might say "The authorization agent has no keys."
  907.         #   1  can't connect to auth agent
  908.         #   2  bad passphrase
  909.         #   3  bad identity file
  910.         #   4  the agent does not have the requested identity
  911.         #   5  unspecified error
  912.         if [ $sl_retval = 0 ]; then
  913.             # Output of ssh-add -l:
  914.             #   The authorization agent has one key:
  915.             #   id_dsa_2048_a: 2048-bit dsa, agriffis@alpha.zk3.dec.com, Fri Jul 25 2003 10:53:49 -0400
  916.             # Since we don't have a fingerprint, just get the filenames *shrug*
  917.             echo "$sl_mylist" | sed '2,$s/:.*//' | xargs
  918.         fi
  919.         return $sl_retval
  920.     fi
  921. }
  922.  
  923. # synopsis: ssh_f filename
  924. # Return fingerprint for a keyfile
  925. # Requires $openssh and $sunssh
  926. ssh_f() {
  927.     sf_filename="$1"
  928.    
  929.     if $openssh || $sunssh; then
  930.         # if private key is symlink and symlink to *.pub is missing:
  931.         if [ -L "$sf_filename" ] && [ ! -z "$realpath_bin" ]; then
  932.             sf_filename="`$realpath_bin $sf_filename`"
  933.         fi
  934.         lsf_filename="$sf_filename.pub"
  935.         if [ ! -f "$lsf_filename" ]; then
  936.             # try to remove extension from private key, *then* add .pub, and see if we now find it:
  937.             if [ -L "$sf_filename" ] && [ ! -z "$realpath_bin" ]; then
  938.                 sf_filename="`$realpath_bin $sf_filename`"
  939.             fi
  940.             lsf_filename=`echo "$sf_filename" | sed 's/\.[^\.]*$//'`.pub
  941.             if [ ! -f "$lsf_filename" ]; then
  942.                 warn "Cannot find public key for $1."
  943.                 return 1
  944.             fi
  945.         fi
  946.         sf_fing=`ssh-keygen -l -f "$lsf_filename"` || return 1
  947.         echo "$sf_fing" | extract_fingerprints
  948.     else
  949.         # can't get fingerprint for ssh2 so use filename *shrug*
  950.         basename "$sf_filename"
  951.     fi
  952.     return 0
  953. }
  954.  
  955. # synopsis: gpg_listmissing
  956. # Uses $gpgkeys
  957. # Returns a newline-separated list of keys found to be missing.
  958. gpg_listmissing() {
  959.     unset glm_missing
  960.  
  961.     GPG_TTY=`tty`
  962.  
  963.     # Parse $gpgkeys into positional params to preserve spaces in filenames
  964.     set -f          # disable globbing
  965.     glm_IFS="$IFS"  # save current IFS
  966.     IFS="
  967. "                   # set IFS to newline
  968.     set -- $gpgkeys
  969.     IFS="$glm_IFS"  # restore IFS
  970.     set +f          # re-enable globbing
  971.  
  972.     for glm_k in "$@"; do
  973.         # Check if this key is known to the agent.  Don't know another way...
  974.         if echo | env -i GPG_TTY="$GPG_TTY" PATH="$PATH" GPG_AGENT_INFO="$GPG_AGENT_INFO" \
  975.                 gpg --no-options --use-agent --no-tty --sign --local-user "$glm_k" -o- >/dev/null 2>&1; then
  976.             # already know about this key
  977.             mesg "Known gpg key: ${CYANN}${glm_k}${OFF}"
  978.             continue
  979.         else
  980.             # need to add this key
  981.             if [ -z "$glm_missing" ]; then
  982.                 glm_missing="$glm_k"
  983.             else
  984.                 glm_missing="$glm_missing
  985. $glm_k"
  986.             fi
  987.         fi
  988.     done
  989.  
  990.     echo "$glm_missing"
  991. }
  992.  
  993. # synopsis: ssh_listmissing
  994. # Uses $sshkeys and $sshavail
  995. # Returns a newline-separated list of keys found to be missing.
  996. ssh_listmissing() {
  997.     unset slm_missing
  998.  
  999.     # Parse $sshkeys into positional params to preserve spaces in filenames
  1000.     set -f          # disable globbing
  1001.     slm_IFS="$IFS"  # save current IFS
  1002.     IFS="
  1003. "                   # set IFS to newline
  1004.     set -- $sshkeys
  1005.     IFS="$slm_IFS"  # restore IFS
  1006.     set +f          # re-enable globbing
  1007.  
  1008.     for slm_k in "$@"; do
  1009.         # Fingerprint current user-specified key
  1010.         slm_finger=`ssh_f "$slm_k"` || continue
  1011.  
  1012.         # Check if it needs to be added
  1013.         case " $sshavail " in
  1014.             *" $slm_finger "*)
  1015.                 # already know about this key
  1016.                 mesg "Known ssh key: ${CYANN}${slm_k}${OFF}"
  1017.                 ;;
  1018.             *)
  1019.                 # need to add this key
  1020.                 if [ -z "$slm_missing" ]; then
  1021.                     slm_missing="$slm_k"
  1022.                 else
  1023.                     slm_missing="$slm_missing
  1024. $slm_k"
  1025.                 fi
  1026.                 ;;
  1027.         esac
  1028.     done
  1029.  
  1030.     echo "$slm_missing"
  1031. }
  1032.  
  1033. # synopsis: add_gpgkey
  1034. # Adds a key to $gpgkeys
  1035. add_gpgkey() {
  1036.     gpgkeys=${gpgkeys+"$gpgkeys
  1037. "}"$1"
  1038. }
  1039.  
  1040. # synopsis: add_sshkey
  1041. # Adds a key to $sshkeys
  1042. add_sshkey() {
  1043.     sshkeys=${sshkeys+"$sshkeys
  1044. "}"$1"
  1045. }
  1046.  
  1047. # synopsis: parse_mykeys
  1048. # Sets $sshkeys and $gpgkeys based on $mykeys
  1049. parse_mykeys() {
  1050.     # Possible path to the private key: if --confhost variable used.
  1051.     pkeypath="$1"
  1052.  
  1053.     # Parse $mykeys into positional params to preserve spaces in filenames
  1054.     set -f         # disable globbing
  1055.     pm_IFS="$IFS"  # save current IFS
  1056.     IFS="
  1057. "                  # set IFS to newline
  1058.     set -- $mykeys
  1059.     IFS="$pm_IFS"  # restore IFS
  1060.     set +f         # re-enable globbing
  1061.  
  1062.     for pm_k in "$@"; do
  1063.         # Check for ssh
  1064.         if wantagent ssh; then
  1065.             if [ -f "$pm_k" ]; then
  1066.                 add_sshkey "$pm_k" ; continue
  1067.             elif [ -f "$HOME/.ssh/$pm_k" ]; then
  1068.                 add_sshkey "$HOME/.ssh/$pm_k" ; continue
  1069.             elif [ -f "$HOME/.ssh2/$pm_k" ]; then
  1070.                 add_sshkey "$HOME/.ssh2/$pm_k" ; continue
  1071.             elif [ -f "$pkeypath" ]; then
  1072.                 add_sshkey "$pkeypath"; continue
  1073.             fi
  1074.         fi
  1075.  
  1076.         # Check for gpg
  1077.         if wantagent gpg; then
  1078.             if [ -z "$pm_gpgsecrets" ]; then
  1079.                 pm_gpgsecrets="`gpg --list-secret-keys 2>/dev/null | cut -d/ -f2 | cut -d' ' -f1 | xargs`"
  1080.                 [ -z "$pm_gpgsecrets" ] && pm_gpgsecrets='/'    # arbitrary
  1081.             fi
  1082.             case " $pm_gpgsecrets " in *" $pm_k "*)
  1083.                 add_gpgkey "$pm_k" ; continue ;;
  1084.             esac
  1085.         fi
  1086.  
  1087.         $ignoreopt || warn "can't find $pm_k; skipping"
  1088.         continue
  1089.     done
  1090.    
  1091.     return 0
  1092. }
  1093.  
  1094. # synopsis: setaction
  1095. # Sets $myaction or dies if $myaction is already set
  1096. setaction() {
  1097.     if [ -n "$myaction" ]; then
  1098.         die "you can't specify --$myaction and $1 at the same time"
  1099.     else
  1100.         myaction="$1"
  1101.     fi
  1102. }
  1103.  
  1104. # synopsis: setagents
  1105. # Check validity of agentsopt
  1106. setagents() {
  1107.     if [ -n "$agentsopt" ]; then
  1108.         agentsopt=`echo "$agentsopt" | sed 's/,/ /g'`
  1109.         unset new_agentsopt
  1110.         for a in $agentsopt; do
  1111.             if command -v ${a}-agent >/dev/null; then
  1112.                 new_agentsopt="${new_agentsopt+$new_agentsopt }${a}"
  1113.             else
  1114.                 warn "can't find ${a}-agent, removing from list"
  1115.             fi
  1116.         done
  1117.         agentsopt="${new_agentsopt}"
  1118.     else
  1119.         for a in ssh; do
  1120.             command -v ${a}-agent >/dev/null || continue
  1121.             agentsopt="${agentsopt+$agentsopt }${a}"
  1122.         done
  1123.     fi
  1124.  
  1125.     if [ -z "$agentsopt" ]; then
  1126.         die "no agents available to start"
  1127.     fi
  1128. }
  1129.  
  1130. # synopsis: confpath
  1131. # Return private key path if found in ~/.ssh/config SSH configuration file.
  1132. # Input: the name of the host we would like to connect to.
  1133. confpath() {
  1134.     h=""
  1135.     while IFS= read -r line; do
  1136.         # get the Host directives
  1137.         if [[ $line == *"Host "* ]]; then
  1138.             h=$(echo $line | awk '{print $2}')
  1139.         fi
  1140.         if [[ $line == *IdentityFile* ]] && [[ $h == "$1" ]]; then
  1141.             echo $line | awk '{print $2}'
  1142.             break
  1143.         fi
  1144.     done < ~/.ssh/config
  1145. }
  1146.  
  1147. # synopsis: wantagent prog
  1148. # Return 0 (true) or 1 (false) depending on whether prog is one of the agents in
  1149. # agentsopt
  1150. wantagent() {
  1151.     case "$agentsopt" in
  1152.         "$1"|"$1 "*|*" $1 "*|*" $1")
  1153.             return 0 ;;
  1154.         *)
  1155.             return 1 ;;
  1156.     esac
  1157. }
  1158.  
  1159. #
  1160. # MAIN PROGRAM
  1161. #
  1162.  
  1163. # parse the command-line
  1164. while [ -n "$1" ]; do
  1165.     case "$1" in
  1166.         --help|-h)
  1167.             setaction help
  1168.             ;;
  1169.         --stop|-k)
  1170.             # As of version 2.5, --stop takes an argument.  For the sake of
  1171.             # backward compatibility, only eat the arg if it's one we recognize.
  1172.             if [ "$2" = mine ]; then
  1173.                 stopwhich=mine; shift
  1174.             elif [ "$2" = others ]; then
  1175.                 stopwhich=others; shift
  1176.             elif [ "$2" = all ]; then
  1177.                 stopwhich=all; shift
  1178.             else
  1179.                 # backward compat
  1180.                 stopwhich=all-warn
  1181.             fi
  1182.             ;;
  1183.         --version|-V)
  1184.             setaction version
  1185.             ;;
  1186.         --agents)
  1187.             shift
  1188.             agentsopt="$1"
  1189.             ;;
  1190.         --attempts)
  1191.             shift
  1192.             if [ "$1" -gt 0 ] 2>/dev/null; then
  1193.                 attempts=$1
  1194.             else
  1195.                 die "--attempts requires a numeric argument greater than zero"
  1196.             fi
  1197.             ;;
  1198.         --clear)
  1199.             clearopt=true
  1200.             $quickopt && die "--quick and --clear are not compatible"
  1201.             ;;
  1202.         --confirm)
  1203.             confirmopt=true
  1204.             ;;
  1205.         --absolute)
  1206.             absoluteopt=true
  1207.             ;;
  1208.         --dir)
  1209.             shift
  1210.             case "$1" in
  1211.                 */.*) keydir="$1" ;;
  1212.                 '')   die "--dir requires an argument" ;;
  1213.                 *)
  1214.                     if $absoluteopt; then
  1215.                         keydir="$1"
  1216.                     else
  1217.                         keydir="$1/.keychain" # be backward-compatible
  1218.                     fi
  1219.                     ;;
  1220.             esac
  1221.             ;;
  1222.         --env)
  1223.             shift
  1224.             if [ -z "$1" ]; then
  1225.                 die "--env requires an argument"
  1226.             else
  1227.                 envf="$1"
  1228.             fi
  1229.             ;;
  1230.         --eval)
  1231.             evalopt=true
  1232.             ;;
  1233.         --list|-l)
  1234.             setaction list
  1235.             quietopt=true
  1236.             ;;
  1237.         --query)
  1238.             queryopt=true
  1239.             ;;
  1240.         --host)
  1241.             shift
  1242.             hostopt="$1"
  1243.             ;;
  1244.         --ignore-missing)
  1245.             ignoreopt=true
  1246.             ;;
  1247.         --inherit)
  1248.             shift
  1249.             case "$1" in
  1250.                 local|any|local-once|any-once)
  1251.                     inheritwhich="$1"
  1252.                     ;;
  1253.                 *)
  1254.                     die "--inherit requires an argument (local, any, local-once or any-once)"
  1255.                     ;;
  1256.             esac
  1257.             ;;
  1258.         --noinherit)
  1259.             inheritwhich=none
  1260.             ;;
  1261.         --noask)
  1262.             noaskopt=true
  1263.             ;;
  1264.         --nogui)
  1265.             noguiopt=true
  1266.             ;;
  1267.         --nolock)
  1268.             nolockopt=true
  1269.             ;;
  1270.         --lockwait)
  1271.             shift
  1272.             if [ "$1" -ge 0 ] 2>/dev/null; then
  1273.                 lockwait="$1"
  1274.             else
  1275.                 die "--lockwait requires an argument zero or greater."
  1276.             fi
  1277.             ;;
  1278.         --quick|-Q)
  1279.             quickopt=true
  1280.             $clearopt && die "--quick and --clear are not compatible"
  1281.             ;;
  1282.         --quiet|-q)
  1283.             quietopt=true
  1284.             ;;
  1285.         --confhost|-c)
  1286.             if [ -e ~/.ssh/config ]; then
  1287.                 sshconfig=true
  1288.                 confhost="$2"
  1289.             else
  1290.                 warn "~/.ssh/config not found; --confhost/-c option ignored."
  1291.             fi
  1292.             ;;
  1293.         --nocolor)
  1294.             color=false
  1295.             ;;
  1296.         --timeout)
  1297.             shift
  1298.             if [ "$1" -gt 0 ] 2>/dev/null; then
  1299.                 timeout=$1
  1300.             else
  1301.                 die "--timeout requires a numeric argument greater than zero"
  1302.             fi
  1303.             ;;
  1304.         --systemd)
  1305.             systemdopt=true
  1306.             ;;
  1307.         --)
  1308.             shift
  1309.             IFS="
  1310. "
  1311.             mykeys=${mykeys+"$mykeys
  1312. "}"$*"
  1313.             unset IFS
  1314.             break
  1315.             ;;
  1316.         -*)
  1317.             echo "$zero: unknown option $1" >&2
  1318.             $evalopt && { echo; echo "false;"; }
  1319.             exit 1
  1320.             ;;
  1321.         *)
  1322.             mykeys=${mykeys+"$mykeys
  1323. "}"$1"
  1324.             ;;
  1325.     esac
  1326.     shift
  1327. done
  1328.  
  1329. # Set filenames *after* parsing command-line options to allow
  1330. # modification of $keydir and/or $hostopt
  1331. #
  1332. # pidf holds the specific name of the keychain .ssh-agent-myhostname file.
  1333. # We use the new hostname extension for NFS compatibility. cshpidf is the
  1334. # .ssh-agent file with csh-compatible syntax. fishpidf is the .ssh-agent
  1335. # file with fish-compatible syntax. lockf is the lockfile, used
  1336. # to serialize the execution of multiple ssh-agent processes started
  1337. # simultaneously
  1338. [ -z "$hostopt" ] && hostopt="${HOSTNAME}"
  1339. [ -z "$hostopt" ] && hostopt=`uname -n 2>/dev/null || echo unknown`
  1340. pidf="${keydir}/${hostopt}-sh"
  1341. cshpidf="${keydir}/${hostopt}-csh"
  1342. fishpidf="${keydir}/${hostopt}-fish"
  1343. olockf="${keydir}/${hostopt}-lock"
  1344. lockf="${keydir}/${hostopt}-lockf"
  1345.  
  1346. # Read the env snippet (especially for things like PATH, but could modify
  1347. # basically anything)
  1348. if [ -z "$envf" ]; then
  1349.     envf="${keydir}/${hostopt}-env"
  1350.     [ -f "$envf" ] || envf="${keydir}/env"
  1351.     [ -f "$envf" ] || unset envf
  1352. fi
  1353. if [ -n "$envf" ]; then
  1354.     . "$envf"
  1355. fi
  1356.  
  1357. # Don't use color if there's no terminal on stderr
  1358. if [ -n "$OFF" ]; then
  1359.     tty <&2 >/dev/null 2>&1 || color=false
  1360. fi
  1361.  
  1362. #disable color if necessary, right before our initial newline
  1363.  
  1364. $color || unset BLUE CYAN CYANN GREEN PURP OFF RED
  1365.  
  1366. qprint #initial newline
  1367. mesg "${PURP}keychain ${OFF}${CYANN}${version}${OFF} ~ ${GREEN}http://www.funtoo.org${OFF}"
  1368. [ "$myaction" = version ] && { versinfo; exit 0; }
  1369. [ "$myaction" = help ] && { versinfo; helpinfo; exit 0; }
  1370.  
  1371. # Set up traps
  1372. # Don't use signal names because they don't work on Cygwin.
  1373. if $clearopt; then
  1374.     trap '' 2   # disallow ^C until we've had a chance to --clear
  1375.     trap 'droplock; exit 1' 1 15    # drop the lock on signal
  1376.     trap 'droplock; exit 0' 0       # drop the lock on exit
  1377. else
  1378.     # Don't use signal names because they don't work on Cygwin.
  1379.     trap 'droplock; exit 1' 1 2 15  # drop the lock on signal
  1380.     trap 'droplock; exit 0' 0       # drop the lock on exit
  1381. fi
  1382.  
  1383. setagents                       # verify/set $agentsopt
  1384. verifykeydir                    # sets up $keydir
  1385. wantagent ssh && testssh        # sets $openssh and $sunssh
  1386. getuser                         # sets $me
  1387.  
  1388. # Inherit agent info from the environment before loadagents wipes it out.
  1389. # Always call this since it checks $inheritopt and sets variables accordingly.
  1390. inheritagents
  1391.  
  1392. # --stop: kill the existing ssh-agent(s) and quit
  1393. if [ -n "$stopwhich" ]; then
  1394.     if [ "$stopwhich" = all-warn ]; then
  1395.         warn "--stop without an argument is deprecated; see --help"
  1396.         stopwhich=all
  1397.     fi
  1398.     takelock || die
  1399.     if [ "$stopwhich" = mine -o "$stopwhich" = others ]; then
  1400.         loadagents $agentsopt
  1401.     fi
  1402.     for a in $agentsopt; do
  1403.         stopagent $a
  1404.     done
  1405.     if [ "$stopwhich" != others ]; then
  1406.         qprint
  1407.         exit 0                  # stopagent is always successful
  1408.     fi
  1409. fi
  1410.  
  1411. # Note regarding locking: if we're trying to be quick, then don't take the lock.
  1412. # It will be taken later if we discover we can't be quick.
  1413. if $quickopt; then
  1414.     loadagents $agentsopt       # sets ssh_auth_sock, ssh_agent_pid, etc
  1415.     unset nagentsopt
  1416.     for a in $agentsopt; do
  1417.         needstart=true
  1418.  
  1419.         # Trying to be quick has a price... If we discover the agent isn't running,
  1420.         # then we'll have to check things again (in startagent) after taking the
  1421.         # lock.  So don't do the initial check unless --quick was specified.
  1422.         if [ $a = ssh ]; then
  1423.             sshavail=`ssh_l`    # try to use existing agent
  1424.                                 # 0 = found keys, 1 = no keys, 2 = no agent
  1425.             if [ $? = 0 -o \( $? = 1 -a -z "$mykeys" \) ]; then
  1426.                 mesg "Found existing ssh-agent: ${CYANN}$ssh_agent_pid${OFF}"
  1427.                 needstart=false
  1428.             fi
  1429.         elif [ $a = gpg ]; then
  1430.             # not much way to be quick on this
  1431.             if [ -n "$gpg_agent_pid" ]; then
  1432.                 case " `findpids gpg` " in
  1433.                     *" $gpg_agent_pid "*)
  1434.                         mesg "Found existing gpg-agent: ${CYANN}$gpg_agent_pid${OFF}"
  1435.                         needstart=false ;;
  1436.                 esac
  1437.             fi
  1438.         fi
  1439.  
  1440.         if $needstart; then
  1441.             nagentsopt="$nagentsopt $a"
  1442.         elif $evalopt; then
  1443.             catpidf $a
  1444.         fi
  1445.     done
  1446.     agentsopt="$nagentsopt"
  1447. fi
  1448.  
  1449. # If there are no agents remaining, then bow out now...
  1450. [ -n "$agentsopt" ] || { qprint; exit 0; }
  1451.  
  1452. # There are agents remaining to start, and we now know we can't be quick.  Take
  1453. # the lock before continuing
  1454. takelock || die
  1455. loadagents $agentsopt
  1456. unset nagentsopt
  1457. for a in $agentsopt; do
  1458.     if $queryopt; then
  1459.         catpidf_shell sh $a | cut -d\; -f1
  1460.     elif startagent $a; then
  1461.         nagentsopt="${nagentsopt+$nagentsopt }$a"
  1462.         $evalopt && catpidf $a
  1463.     fi
  1464. done
  1465. agentsopt="$nagentsopt"
  1466.  
  1467. # If we are just querying the services, exit.
  1468. $queryopt && exit 0
  1469.  
  1470. # If there are no agents remaining, then duck out now...
  1471. [ -n "$agentsopt" ] || { qprint; exit 0; }
  1472.  
  1473. # --timeout translates almost directly to ssh-add -t, but ssh.com uses
  1474. # minutes and OpenSSH uses seconds
  1475. if [ -n "$timeout" ] && wantagent ssh; then
  1476.     ssh_timeout=$timeout
  1477.     if $openssh || $sunssh; then
  1478.         ssh_timeout=`expr $ssh_timeout \* 60`
  1479.     fi
  1480.     ssh_timeout="-t $ssh_timeout"
  1481. fi
  1482.  
  1483. # --confirm translates to ssh-add -c
  1484. if $confirmopt && wantagent ssh; then
  1485.     if $openssh || $sunssh; then
  1486.         ssh_confirm=-c
  1487.     else
  1488.         warn "--confirm only works with OpenSSH"
  1489.     fi
  1490. fi
  1491.  
  1492. # --clear: remove all keys from the agent(s)
  1493. if $clearopt; then
  1494.     for a in ${agentsopt}; do
  1495.         if [ $a = ssh ]; then
  1496.             sshout=`ssh-add -D 2>&1`
  1497.             if [ $? = 0 ]; then
  1498.                 mesg "ssh-agent: $sshout"
  1499.             else
  1500.                 warn "ssh-agent: $sshout"
  1501.             fi
  1502.         elif [ $a = gpg ]; then
  1503.             kill -1 $gpg_agent_pid 2>/dev/null
  1504.             mesg "gpg-agent: All identities removed."
  1505.         else
  1506.             warn "--clear not supported for ${a}-agent"
  1507.         fi
  1508.     done
  1509.     trap 'droplock' 2               # done clearing, safe to ctrl-c
  1510. fi
  1511.  
  1512. if $systemdopt; then
  1513.     for a in $agentsopt; do
  1514.         systemctl --user set-environment $( catpidf_shell sh $a | cut -d\; -f1 )
  1515.     done
  1516. fi
  1517.  
  1518. # --noask: "don't ask for keys", so we're all done
  1519. $noaskopt && { qprint; exit 0; }
  1520.  
  1521. # If the --confhost option used, determine the path to the private key as
  1522. # written in the ~/.ssh/config and add it to ssh-add.
  1523. if $sshconfig; then
  1524.     pkeypath=$(confpath "$confhost")
  1525.     eval pkeypath=$pkeypath
  1526. fi
  1527.  
  1528. # Parse $mykeys into ssh vs. gpg keys; it may be necessary in the future to
  1529. # differentiate on the cmdline
  1530. parse_mykeys "$pkeypath" || die
  1531.  
  1532. # Load ssh keys
  1533. if wantagent ssh; then
  1534.     sshavail=`ssh_l`                # update sshavail now that we're locked
  1535.     if [ "$myaction" = "list" ]; then
  1536.         for key in $sshavail end; do
  1537.             [ "$key" == "end" ] && continue
  1538.             echo "$key"
  1539.         done
  1540.     else
  1541.         sshkeys="`ssh_listmissing`"     # cache list of missing keys, newline-separated
  1542.         sshattempts=$attempts
  1543.         savedisplay="$DISPLAY"
  1544.  
  1545.         # Attempt to add the keys
  1546.         while [ -n "$sshkeys" ]; do
  1547.  
  1548.             mesg "Adding ${CYANN}"`echo "$sshkeys" | wc -l`"${OFF} ssh key(s): `echo $sshkeys`"
  1549.  
  1550.             # Parse $sshkeys into positional params to preserve spaces in filenames.
  1551.             # This *must* happen after any calls to subroutines because pure Bourne
  1552.             # shell doesn't restore "$@" following a call.  Eeeeek!
  1553.             set -f          # disable globbing
  1554.             old_IFS="$IFS"  # save current IFS
  1555.             IFS="
  1556.     "                       # set IFS to newline
  1557.             set -- $sshkeys
  1558.             IFS="$old_IFS"  # restore IFS
  1559.             set +f          # re-enable globbing
  1560.  
  1561.             if $noguiopt || [ -z "$SSH_ASKPASS" -o -z "$DISPLAY" ]; then
  1562.                 unset DISPLAY       # DISPLAY="" can cause problems
  1563.                 unset SSH_ASKPASS   # make sure ssh-add doesn't try SSH_ASKPASS
  1564.                 sshout=`ssh-add ${ssh_timeout} ${ssh_confirm} "$@" 2>&1`
  1565.             else
  1566.                 sshout=`ssh-add ${ssh_timeout} ${ssh_confirm} "$@" 2>&1 </dev/null`
  1567.             fi
  1568.             if [ $? = 0 ]
  1569.         then
  1570.             blurb=""
  1571.             [ -n "$timeout" ] && blurb="life=${timeout}m"
  1572.             [ -n "$timeout" ] && $confirmopt && blurb="${blurb},"
  1573.             $confirmopt && blurb="${blurb}confirm"
  1574.             [ -n "$blurb" ] && blurb=" (${blurb})"
  1575.             mesg "ssh-add: Identities added: `echo $sshkeys`${blurb}"
  1576.             break
  1577.         fi
  1578.             if [ $sshattempts = 1 ]; then
  1579.                 die "Problem adding; giving up"
  1580.             else
  1581.                 warn "Problem adding; trying again"
  1582.             fi
  1583.  
  1584.             # Update the list of missing keys
  1585.             sshavail=`ssh_l`
  1586.             [ $? = 0 ] || die "problem running ssh-add -l"
  1587.             sshkeys="`ssh_listmissing`"  # remember, newline-separated
  1588.  
  1589.             # Decrement the countdown
  1590.             sshattempts=`expr $sshattempts - 1`
  1591.         done
  1592.  
  1593.         [ -n "$savedisplay" ] && DISPLAY="$savedisplay"
  1594.     fi
  1595. fi
  1596.  
  1597. # Load gpg keys
  1598. if wantagent gpg; then
  1599.     gpgkeys="`gpg_listmissing`"     # cache list of missing keys, newline-separated
  1600.     gpgattempts=$attempts
  1601.  
  1602.     $noguiopt && unset DISPLAY
  1603.     [ -n "$DISPLAY" ] || unset DISPLAY  # DISPLAY="" can cause problems
  1604.     GPG_TTY=`tty` ; export GPG_TTY      # fall back to ncurses pinentry
  1605.  
  1606.     # Attempt to add the keys
  1607.     while [ -n "$gpgkeys" ]; do
  1608.         tryagain=false
  1609.  
  1610.         mesg "Adding ${BLUE}"`echo "$gpgkeys" | wc -l`"${OFF} gpg key(s): `echo $gpgkeys`"
  1611.  
  1612.         # Parse $gpgkeys into positional params to preserve spaces in filenames.
  1613.         # This *must* happen after any calls to subroutines because pure Bourne
  1614.         # shell doesn't restore "$@" following a call.  Eeeeek!
  1615.         set -f          # disable globbing
  1616.         old_IFS="$IFS"  # save current IFS
  1617.         IFS="
  1618. "                       # set IFS to newline
  1619.         set -- $gpgkeys
  1620.         IFS="$old_IFS"  # restore IFS
  1621.         set +f          # re-enable globbing
  1622.  
  1623.         for k in "$@"; do
  1624.             echo | env LC_ALL="$pinentry_lc_all" \
  1625.                 gpg --no-options --use-agent --no-tty --sign --local-user "$k" -o- >/dev/null 2>&1
  1626.             [ $? != 0 ] && tryagain=true
  1627.         done
  1628.         $tryagain || break
  1629.  
  1630.         if [ $gpgattempts = 1 ]; then
  1631.             die "Problem adding (is pinentry installed?); giving up"
  1632.         else
  1633.             warn "Problem adding; trying again"
  1634.         fi
  1635.  
  1636.         # Update the list of missing keys
  1637.         gpgkeys="`gpg_listmissing`"  # remember, newline-separated
  1638.  
  1639.         # Decrement the countdown
  1640.         gpgattempts=`expr $gpgattempts - 1`
  1641.     done
  1642. fi
  1643.  
  1644. qprint  # trailing newline
  1645.  
  1646. # vim:sw=4 noexpandtab tw=120
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement