Fix detection of GnuPG Agent socket

This patch modifies slightly the logic of GnuPG Agent's socket
detection. Failure to find a socket at the location specified
by a GPG_AGENT_INFO is now a definitive error (no other locations
are tried), and the socket is not looked for in GnuPG's standard
directories if GNUPGHOME is set.

The patch also modifies the internal behavior of the code to avoid
any memory allocation.

Incenp-bug-id: 7
develop
Damien Goutte-Gattat 6 years ago
parent d3b8dd57fd
commit 4eb505e544
  1. 203
      src/gpg-util.c

@ -1,6 +1,6 @@
/*
* scdtools - Tools for Scdaemon and OpenPGP smartcards
* Copyright (C) 2014,2015,2016 Damien Goutte-Gattat
* Copyright (C) 2014,2015,2016,2017 Damien Goutte-Gattat
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -36,94 +36,137 @@
#include "gpg-util.h"
/*
* Find the socket of GnuPG Agent. First look for the GPG_AGENT_INFO
* environment variable, if unset look for the standard socket in
* GnuPG's home directory, if not found use gpg-connect-agent.
*
* The socket name is stored in a newly allocated buffer that should
* be freed by the caller.
* Check that a socket exists at the specified location.
*
* Return NULL if the socket could not be found.
* Return 0 if the socket does exist, or a gpg_error_t error code.
*/
static char *
get_agent_socket_name(void)
static gpg_error_t
check_socket(const char *socket_name)
{
char *env_info, *socket_name = NULL;
char buf[255] = "";
struct stat st;
gpg_error_t e;
/* First look for GPG_AGENT_INFO in the environment. */
if ( (env_info = getenv("GPG_AGENT_INFO")) ) {
if ( (socket_name = strdup(env_info)) ) {
char *colon_index;
/*
* GPG_AGENT_INFO should be of the form
* "path_to_socket:x:y", where x is the PID
* of the GnuPG Agent process and y is the
* protocol version (currently 1).
*/
if ( (colon_index = strchr(socket_name, ':')) )
*colon_index = '\0';
}
}
if ( stat(socket_name, &st) == -1 )
e = gpg_err_code_from_errno(errno);
else if ( ! S_ISSOCK(st.st_mode) )
e = gpg_err_code_from_errno(ENOTSOCK);
else
e = GPG_ERR_NO_ERROR;
if ( ! socket_name ) {
/* Then look for the standard socket
* in GnuPG's home directory. */
struct passwd *pwd;
return e;
}
/*
* Convenience function: it does the same as check_socket above
* but accept a printf-like format string to construct the path
* before testing it.
*/
static gpg_error_t
check_socket_fmt(char *buffer, size_t len, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vsnprintf(buffer, len, fmt, ap);
va_end(ap);
return check_socket(buffer);
}
/*
* Find the socket of GnuPG Agent. First look for the GPG_AGENT_INFO
* environment variable, if unset look for the socket at some
* standard locations, if not found use gpg-connect-agent.
*
* Return 0 if the socket is found, or a gpg_error_t error code.
*/
static gpg_error_t
get_agent_socket_name(char *buffer, size_t len)
{
char *env_info = NULL;
gpg_error_t e = GPG_ERR_NO_AGENT;
/* First look for GPG_AGENT_INFO in the environment
* (GnuPG < 2.1). */
if ( (env_info = getenv("GPG_AGENT_INFO")) ) {
unsigned proto;
char fmt[32];
if ( (env_info = getenv("GNUPGHOME")) )
snprintf(buf, sizeof(buf), "%s/S.gpg-agent", env_info);
else if ( (env_info = getenv("HOME")) )
snprintf(buf, sizeof(buf), "%s/.gnupg/S.gpg-agent", env_info);
else if ( (pwd = getpwuid(getuid())) )
snprintf(buf, sizeof(buf), "%s/.gnupg/S.gpg-agent", pwd->pw_dir);
/*
* GPG_AGENT_INFO should be of the form
* "path_to_socket:x:y", where x is the PID
* of the GnuPG Agent process and y is the
* protocol version (currently always 1).
*/
snprintf(fmt, sizeof(fmt), "%%%lus:%%*u:%%i", len);
if ( sscanf(env_info, fmt, buffer, &proto) != 2 )
e = gcry_error(GPG_ERR_INV_VALUE);
else if ( proto != 1 )
e = gcry_error(GPG_ERR_UNSUPPORTED_PROTOCOL);
else
e = check_socket(buffer);
/* Check whether the socket does exist. */
if ( *buf && stat(buf, &st) != -1 && S_ISSOCK(st.st_mode) )
socket_name = strdup(buf);
/*
* If the GPG_AGENT_INFO variable exists, it must
* point to a suitable socket; otherwise, abort
* here without looking elsewhere.
*/
if ( e )
return e;
}
if ( ! socket_name ) {
/* Then look for the standard socket outside of GnuPG's
* home directory (for GnuPG >= 2.1.17). */
/* If GNUPGHOME is defined, try looking for a socket in
* the specified directory. */
if ( e && (env_info = getenv("GNUPGHOME")) )
e = check_socket_fmt(buffer, len, "%s/S.gpg-agent", env_info);
/* Without GNUPGHOME, try looking in standard directories. */
if ( e && ! env_info ) {
static const char *prefixes[] = { "/run", "/var/run", NULL };
struct passwd *pwd;
int i;
for ( i = 0; prefixes[i] && ! socket_name ; i++ ) {
snprintf(buf, sizeof(buf), "%s/user/%u/gnupg/S.gpg-agent",
prefixes[i], getuid());
if ( ! (env_info = getenv("HOME")) && (pwd = getpwuid(getuid())) )
env_info = pwd->pw_dir;
if ( stat(buf, &st) != -1 && S_ISSOCK(st.st_mode) )
socket_name = strdup(buf);
}
if ( env_info )
e = check_socket_fmt(buffer, len, "%s/.gnupg/S.gpg-agent",
env_info);
/* If no socket was found in GnuPG's home directory,
* try looking under [/var]/run (for GnuPG >= 2.1.13). */
for ( i = 0; e && prefixes[i]; i++ )
e = check_socket_fmt(buffer, len, "%s/user/%u/gnupg/S.gpg-agent",
prefixes[i], getuid());
}
if ( ! socket_name ) {
/*
* Finally, try to ask the agent directly. This method could
* replace all the methods above, but since it requires spawing
* a 0new process, we use it only as a fallback. We expect that
* most of the time, the agent should already be running when
* our programs are called, and thus the socket would already
* exist and be found by the above code.
*/
/*
* Finally, try asking the agent directly. This methold could
* replace all the methods above, but since it requires spawning
* a new process, we use it only as a fallback. We expect that
* most of the time, the agent should already be running when our
* programs are called, and thus the socket would already exist
* and be found by the above code.
*/
if ( e ) {
FILE *f;
f = popen("gpg-connect-agent 'GETINFO socket_name' /bye", "r");
if ( f ) {
if ( fscanf(f, "D %254s\nOK\n", buf) == 1 )
socket_name = strdup(buf);
char fmt[32];
snprintf(fmt, sizeof(fmt), "D %%%lus\nOK\n", len);
if ( fscanf(f, fmt, buffer) == 1 )
e = check_socket(buffer);
pclose(f);
}
}
return socket_name;
return e;
}
/*
* Establish a connection with a running GnuPG Agent.
*
@ -138,11 +181,11 @@ get_agent_socket_name(void)
gpg_error_t
connect_to_agent(assuan_context_t *ctx, int init_env)
{
char *socket_name;
char socket_name[255];
gpg_error_t e;
if ( ! (socket_name = get_agent_socket_name()) )
return gcry_error(GPG_ERR_NO_AGENT);
if ( (e = get_agent_socket_name(socket_name, sizeof(socket_name))) )
return e;
if ( ! (e = assuan_new(ctx)) ) {
if ( ! (e = assuan_socket_connect(*ctx, socket_name, ASSUAN_INVALID_PID, 0)) ) {
@ -153,23 +196,26 @@ connect_to_agent(assuan_context_t *ctx, int init_env)
assuan_release(*ctx);
}
free(socket_name);
return e;
}
struct pstring {
size_t len;
char *buffer;
};
/*
* Callback for the below function.
*/
static gpg_error_t
socket_name_cb(void *arg, const void *line, size_t len)
{
char **socket_name = (char **)arg;
struct pstring *p = (struct pstring *)arg;
(void)len;
if ( len > p->len - 1 )
return gcry_error(GPG_ERR_BUFFER_TOO_SHORT);
if ( ! (*socket_name = strdup(line)) )
return gcry_error_from_errno(errno);
strncpy(p->buffer, line, p->len);
return 0;
}
@ -179,15 +225,11 @@ socket_name_cb(void *arg, const void *line, size_t len)
* GnuPG Agent and inquire about Scdaemon's running status and
* socket name.
*
* @param socket_name A pointer to a string to store the name of
* Scdaemon's socket. Should be freed by the
* caller after use.
*
* @return 0 if Scdaemon's socket was found, or a gpg_error_t
* error code otherwise.
*/
static gpg_error_t
get_scd_socket_name(char **scd_socket_name)
get_scd_socket_name(char *buffer, size_t len)
{
assuan_context_t ctx;
gpg_error_t e;
@ -196,9 +238,12 @@ get_scd_socket_name(char **scd_socket_name)
if ( ! (e = assuan_transact(ctx, "GETINFO scd_running",
NULL, NULL, NULL, NULL, NULL, NULL)) ) {
struct pstring p;
p.len = len;
p.buffer = buffer;
e = assuan_transact(ctx, "SCD GETINFO socket_name",
socket_name_cb, scd_socket_name, NULL, NULL, NULL, NULL);
socket_name_cb, &p, NULL, NULL, NULL, NULL);
}
assuan_release(ctx);
@ -219,10 +264,10 @@ get_scd_socket_name(char **scd_socket_name)
gpg_error_t
connect_to_scdaemon(assuan_context_t *ctx)
{
char *socket_name;
char socket_name[255];
gpg_error_t e;
if ( (e = get_scd_socket_name(&socket_name)) )
if ( (e = get_scd_socket_name(socket_name, sizeof(socket_name))) )
return e;

Loading…
Cancel
Save