12

I'm struggling with some issues while scripting gpg with bash on a Debian 6.0.6 box. I have a script that does a batch of operations and wants to make sure that a gpg-agent is available before it attempts to proceed.

Since gpg-agent will take no action and return success if launched when already running, ensuring the agent is present is as simple as:

eval $(gpg-agent --daemon)

gpg-agent start, or will report:

gpg-agent[21927]: a gpg-agent is already running - not starting a new one

and return 0 (success) if already running.

The problem arises when an agent is already running in another session. gpg-agent says that it's already running ... but gpg its self then claims that it's unavailable.

$ gpg-agent --version
gpg-agent (GnuPG) 2.0.19
libgcrypt 1.5.0
$ gpg --version
gpg (GnuPG) 1.4.13

$ eval $(gpg-agent --daemon)
gpg-agent[21927]: a gpg-agent is already running - not starting a new one
$ gpg -d demo-file.asc
gpg: gpg-agent is not available in this session

This leaves me frustrated and confused. It appears that gpg-agent is detecting the agent a different way to gpg its self. Worse, gpg offers no way to ask if the agent is available in a scriptable way, much as it likes to silently ignore recipients with unusable keys and still return success, so it's very hard to detect this problem before beginning the batch. I don't want to get into parsing gpg's output for i18n reasons among others.

You can reproduce this by ensuring you don't have a gpg-agent running or have GPG_AGENT_INFO set, then in one terminal running eval $(gpg-agent --daemon) and in another terminal running the above. You'll note that gpg-agent says it's already running, but gpg fails to connect to the agent.

Ideas?

UPDATE: gpg-agent detects another agent by looking for a socket file in a well-known location and writing to it to test for aliveness, a per this strace:

socket(PF_FILE, SOCK_STREAM, 0)         = 5
connect(5, {sa_family=AF_FILE, sun_path="/home/craig/.gnupg/S.gpg-agent"}, 32) = 0
fcntl(5, F_GETFL)                       = 0x2 (flags O_RDWR)
fcntl(5, F_GETFL)                       = 0x2 (flags O_RDWR)
select(6, [5], NULL, NULL, {0, 0})      = 1 (in [5], left {0, 0})
read(5, "OK Pleased to meet you, process "..., 1002) = 38
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f41a3e61000
write(2, "gpg-agent: gpg-agent running and"..., 43gpg-agent: gpg-agent running and available
) = 43

while GnuPG seems to look only at the environment, ignoring the well-known socket location. In common/simple-pwquery.c:

/* Try to open a connection to the agent, send all options and return
   the file descriptor for the connection.  Return -1 in case of
   error. */
static int
agent_open (int *rfd)
{
  int rc;
  int fd;
  char *infostr, *p;
  struct sockaddr_un client_addr;
  size_t len;
  int prot;
  char line[200];
  int nread;

  *rfd = -1;
  infostr = getenv ( "GPG_AGENT_INFO" );
  if ( !infostr || !*infostr )
    infostr = default_gpg_agent_info;
  if ( !infostr || !*infostr )
    {
#ifdef SPWQ_USE_LOGGING
      log_error (_("gpg-agent is not available in this session\n"));
#endif
      return SPWQ_NO_AGENT;
    }
    /* blah blah blah truncated blah */
}

I don't really want to kill the agent just to make sure I can start it again, and there's no standard place where the user's agent might write an environment file. Worse, I can't even test for the presence of GPG_AGENT_INFO in the environment since that could refer to a stale (dead) agent that's since been replaced ... and neither gpg nor gpg-agent provide a command line option to ping the agent and return true if it's ok.

Craig Ringer
  • 10,553
  • 9
  • 38
  • 59
  • I've also asked on the gpg-users mailing list; I'll link to the post once it appears in the archives. – Craig Ringer Feb 22 '13 at 00:33
  • Unix.SE [How to configure gpg to enter passphrase only once per session](http://unix.stackexchange.com/questions/46960/how-to-configure-gpg-to-enter-passphrase-only-once-per-session) solved some of my problems - on a single-user system though. – Joel Purra Jan 10 '14 at 10:02

4 Answers4

8
  1. You can check the exit code of gpg-connect-agent /bye
  2. You can check whether the socket given in $GPG_AGENT_INFO exists. That should be enough but you can also check with fuser or lsof whether the process given in $GPG_AGENT_INFO is the one that has opened the socket. And if you want to be really exhaustive you can also check whether /proc/$PID/exe is a link to /usr/bin/gpg-agent (or whatever).
Hauke Laging
  • 5,157
  • 2
  • 23
  • 40
  • Unfortunately, neither of these resolve the issue. (1) correctly determines if gpg-agent is running, but it doesn't test the same way `gpg` its self does, so it may succeed when gpg subsequently fails to connect to the agent. The same is true of (2) in that the agent may be running, but GPG_AGENT_INFO not set in the current session, and there's no apparent way to ask the `gpg-agent` command for the `GPG_AGENT_INFO` of an already-running agent. – Craig Ringer Feb 21 '13 at 23:33
4

The running gpg agent's major version is 2. You should invoke gpg2 rather than gpg as answered here: https://unix.stackexchange.com/questions/231386/how-to-make-gpg-find-gpg-agent

Hermann
  • 176
  • 5
3

So far the best workaround I have is the following hideous mess:

if ! test -v GPG_AGENT_INFO; then
    if gpg-agent 2>/dev/null; then
        if test -e /tmp/.gpg-agent-$USER/env; then
            . /tmp/.gpg-agent-$USER/env
        elif test -e ~/.gpg-agent-info; then
            . ~/.gpg-agent-info
        else
            echo 'A gpg agent is running, but we cannot find its socket info because'
            echo 'the GPG_AGENT_INFO env var is not set and gpg agent info has not been'
            echo 'written to any expected location. Cannot continue. Please report this'
            echo 'issue for investigation.'
            exit 5
        fi
    else
        mkdir /tmp/.gpg-agent-$USER
        chmod 700 /tmp/.gpg-agent-$USER
        gpg-agent --daemon --write-env-file /tmp/.gpg-agent-$USER/env
        . /tmp/.gpg-agent-$USER/env
    fi
    # The env file doesn't include an export statement
    export GPG_AGENT_INFO
else
    if ! gpg-agent 2>/dev/null; then
        echo 'GPG_AGENT_INFO is set, but cannot connect to the agent.'
        echo 'Unsure how to proceed, so aborting execution. Please report this'
        echo 'issue for investigation.'
        exit 5
    fi
fi

This will check for GPG_AGENT_INFO in the environment and if it's set, make sure gpg-agent is actually running. (I'm not yet sure how this interacts with other gpg-agent implementations like GNOME's agent). If the agent info is set but the agent is not running it doesn't know how to cope and gives up.

If the agent info isn't set it checks to see if the agent is running. If it is, it looks for the env info in a couple of well known locations and if it fails to find it, gives up.

If the agent isn't running and the agent info is unset, it starts an agent, writes the env file to a private location, and proceeds.

To say that I'm unhappy with this horrible, user-hostile and unreliable hack is an understatement.

It's very surprising that gpg, a security/crypto tool, will ignore arguments and proceed. --use-agent should be a fatal error if an agent is not running, at least optionally, much as specifying -r with an invalid recipient should be an error rather than ignored. The fact that gpg finds its agent a different way to the gpg-agent command is bewildering.

Craig Ringer
  • 10,553
  • 9
  • 38
  • 59
  • As usual it can be made even messier... :-) If GPG_AGENT_INFO is not (or wrongly) set and you know the PID (e.g. by `pgrep gpg-agent`) then ypu may do this to find the socket: `lsof -n -p $PID | grep S.gpg-agent$ | awk '{print $NF}'` – Hauke Laging Feb 22 '13 at 01:34
  • 1
    @HaukeLaging ... if it's really `gpg-agent` not say `gnome-keyring-daemon` at work. 'cos it wasn't already horrible enough :S . I'm amazed that it's all such an inconsistent mess. – Craig Ringer Feb 22 '13 at 02:34
  • `! test -v GPG_AGENT_INFO ` doesn't work on Mac OS X. You'll need to use something like `[ -z ${GPG_AGENT_INFO+x} ]` instead. – dwlz Jul 27 '16 at 22:14
2

On my Ubuntu system gpg-agent is configured to write its environment file to ~/.gnupg/gpg-agent-info-$(hostname) (which is done by /etc/X11/Xsession.d/90gpg-agent). If your system doesn't do this you could modify the way the agent is started to write an environment file in a well known location which can later be sourced. For example:

$ gpg-agent --daemon --write-env-file="$HOME/.gnupg/gpg-agent-info"
$ source ~/.gnupg/gpg-agent-info
kasperd
  • 29,894
  • 16
  • 72
  • 122
mgorven
  • 30,036
  • 7
  • 76
  • 121
  • Yeah, the trouble is that I'm scripting tools that need to be portable; I can't really rely on distro-specific details. gpg-agent won't write an env file if an agent is already running and I can't determine where any env file might already be. If gpg-agent --write-env-file would query the currently running agent and write an env file, that'd be just fine, but it doesn't. – Craig Ringer Feb 21 '13 at 23:30
  • 2
    NB: `gpg-agent[2333]: WARNING: "--write-env-file" is an obsolete option - it has no effect` – Kent Fredric Apr 04 '16 at 12:33