diff --git a/subversion/libsvn_subr/gpg_agent.c b/subversion/libsvn_subr/gpg_agent.c index 36fe30ddb8fe..4108a684e129 100644 --- a/subversion/libsvn_subr/gpg_agent.c +++ b/subversion/libsvn_subr/gpg_agent.c @@ -64,10 +64,13 @@ #include #include +#include #include +#include #include "svn_auth.h" #include "svn_config.h" #include "svn_error.h" +#include "svn_io.h" #include "svn_pools.h" #include "svn_cmdline.h" #include "svn_checksum.h" @@ -225,6 +228,65 @@ bye_gpg_agent(int sd) close(sd); } +/* Find gpg-agent socket location using gpgconf. Returns the path to socket, or + * NULL if the socket path cannot be determined using gpgconf. + */ +static const char * +find_gpgconf_agent_socket(apr_pool_t *pool) +{ + apr_proc_t proc; + svn_stringbuf_t *line; + svn_error_t *err; + svn_boolean_t eof; + const char* agent_socket; + const char* const gpgargv[] = { "gpgconf", "--list-dir", NULL }; + + /* execute "gpgconf --list-dir" */ + err = svn_io_start_cmd3(&proc, NULL, "gpgconf", gpgargv, + NULL /* env */, TRUE /* inherit */, + FALSE /* infile_pipe */, NULL /* infile */, + TRUE /* outfile_pipe */, NULL /* outfile */, + FALSE /* errfile_pipe */, NULL /* errfile */, + pool); + if (err != SVN_NO_ERROR) + { + svn_error_clear(err); + return NULL; + } + + /* Parse the gpgconf output. + * The format of output is a list of directories/sockets with each + * directory/socket listed on a separate line in format "field:/some/path" */ + eof = FALSE; + while (((err = svn_io_file_readline(proc.out, &line, NULL, &eof, APR_SIZE_MAX, + pool, pool)) == SVN_NO_ERROR) + && !eof) + { + if (strncmp(line->data, "agent-socket:", strlen("agent-socket:")) == 0) + { + apr_array_header_t *dir_details; + dir_details = svn_cstring_split(line->data, ":", TRUE, pool); + /* note: unescape_assuan modifies dir_details in place */ + agent_socket = unescape_assuan(APR_ARRAY_IDX(dir_details, 1, char*)); + break; + } + } + if (err != SVN_NO_ERROR) + { + svn_error_clear(err); + return NULL; + } + apr_file_close(proc.out); + err = svn_io_wait_for_cmd(&proc, "gpgconf", NULL, NULL, pool); + if (err != SVN_NO_ERROR) + { + svn_error_clear(err); + return NULL; + } + + return agent_socket; +} + /* Locate a running GPG Agent, and return an open file descriptor * for communication with the agent in *NEW_SD. If no running agent * can be found, set *NEW_SD to -1. */ @@ -242,38 +304,44 @@ find_running_gpg_agent(int *new_sd, apr_pool_t *pool) *new_sd = -1; - /* This implements the method of finding the socket as described in - * the gpg-agent man page under the --use-standard-socket option. - * The manage page says the standard socket is "named 'S.gpg-agent' located - * in the home directory." GPG's home directory is either the directory - * specified by $GNUPGHOME or ~/.gnupg. */ - gpg_agent_info = getenv("GPG_AGENT_INFO"); - if (gpg_agent_info != NULL) - { - apr_array_header_t *socket_details; + /* Query socket location using gpgconf if possible */ + socket_name = find_gpgconf_agent_socket(pool); - /* For reference GPG_AGENT_INFO consists of 3 : separated fields. - * The path to the socket, the pid of the gpg-agent process and - * finally the version of the protocol the agent talks. */ - socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE, - pool); - socket_name = APR_ARRAY_IDX(socket_details, 0, const char *); - } - else if ((gnupghome = getenv("GNUPGHOME")) != NULL) - { - const char *homedir = svn_dirent_canonicalize(gnupghome, pool); - socket_name = svn_dirent_join(homedir, "S.gpg-agent", pool); - } - else + /* fallback to the old method used with Gnupg 1.x */ + if (socket_name == NULL) { - const char *homedir = svn_user_get_homedir(pool); + /* This implements the method of finding the socket as described in + * the gpg-agent man page under the --use-standard-socket option. + * The manage page says the standard socket is "named 'S.gpg-agent' located + * in the home directory." GPG's home directory is either the directory + * specified by $GNUPGHOME or ~/.gnupg. */ + if ((gpg_agent_info = getenv("GPG_AGENT_INFO")) != NULL) + { + apr_array_header_t *socket_details; + + /* For reference GPG_AGENT_INFO consists of 3 : separated fields. + * The path to the socket, the pid of the gpg-agent process and + * finally the version of the protocol the agent talks. */ + socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE, + pool); + socket_name = APR_ARRAY_IDX(socket_details, 0, const char *); + } + else if ((gnupghome = getenv("GNUPGHOME")) != NULL) + { + const char *homedir = svn_dirent_canonicalize(gnupghome, pool); + socket_name = svn_dirent_join(homedir, "S.gpg-agent", pool); + } + else + { + const char *homedir = svn_user_get_homedir(pool); - if (!homedir) - return SVN_NO_ERROR; + if (!homedir) + return SVN_NO_ERROR; - homedir = svn_dirent_canonicalize(homedir, pool); - socket_name = svn_dirent_join_many(pool, homedir, ".gnupg", - "S.gpg-agent", SVN_VA_NULL); + homedir = svn_dirent_canonicalize(homedir, pool); + socket_name = svn_dirent_join_many(pool, homedir, ".gnupg", + "S.gpg-agent", SVN_VA_NULL); + } } if (socket_name != NULL)