gpg_agent.c revision 286506
118334Speter/*
218334Speter * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_*
318334Speter *
418334Speter * ====================================================================
518334Speter *    Licensed to the Apache Software Foundation (ASF) under one
618334Speter *    or more contributor license agreements.  See the NOTICE file
718334Speter *    distributed with this work for additional information
818334Speter *    regarding copyright ownership.  The ASF licenses this file
918334Speter *    to you under the Apache License, Version 2.0 (the
1018334Speter *    "License"); you may not use this file except in compliance
1118334Speter *    with the License.  You may obtain a copy of the License at
1218334Speter *
1318334Speter *      http://www.apache.org/licenses/LICENSE-2.0
1418334Speter *
1518334Speter *    Unless required by applicable law or agreed to in writing,
1618334Speter *    software distributed under the License is distributed on an
1718334Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
1818334Speter *    KIND, either express or implied.  See the License for the
1918334Speter *    specific language governing permissions and limitations
2018334Speter *    under the License.
2118334Speter * ====================================================================
2218334Speter */
2318334Speter
2418334Speter/* ==================================================================== */
2518334Speter
2618334Speter/* This auth provider stores a plaintext password in memory managed by
2718334Speter * a running gpg-agent. In contrast to other password store providers
2818334Speter * it does not save the password to disk.
2918334Speter *
3018334Speter * Prompting is performed by the gpg-agent using a "pinentry" program
3118334Speter * which needs to be installed separately. There are several pinentry
3218334Speter * implementations with different front-ends (e.g. qt, gtk, ncurses).
3318334Speter *
3418334Speter * The gpg-agent will let the password time out after a while,
3518334Speter * or immediately when it receives the SIGHUP signal.
3618334Speter * When the password has timed out it will automatically prompt the
3718334Speter * user for the password again. This is transparent to Subversion.
3818334Speter *
3918334Speter * SECURITY CONSIDERATIONS:
4018334Speter *
4118334Speter * Communication to the agent happens over a UNIX socket, which is located
4218334Speter * in a directory which only the user running Subversion can access.
4318334Speter * However, any program the user runs could access this socket and get
4418334Speter * the Subversion password if the program knows the "cache ID" Subversion
4518334Speter * uses for the password.
4618334Speter * The cache ID is very easy to obtain for programs running as the same user.
4718334Speter * Subversion uses the MD5 of the realmstring as cache ID, and these checksums
4818334Speter * are also used as filenames within ~/.subversion/auth/svn.simple.
4918334Speter * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for
5018334Speter * permission if another program attempts to access the password.
5118334Speter *
5218334Speter * Therefore, while the gpg-agent is running and has the password cached,
5318334Speter * this provider is no more secure than a file storing the password in
5418334Speter * plaintext.
5518334Speter */
5618334Speter
5718334Speter
5818334Speter/*** Includes. ***/
5918334Speter
6018334Speter#ifndef WIN32
6118334Speter
6218334Speter#include <unistd.h>
6318334Speter
6418334Speter#include <sys/socket.h>
6518334Speter#include <sys/un.h>
6618334Speter
6718334Speter#include <apr_pools.h>
6818334Speter#include "svn_auth.h"
6918334Speter#include "svn_config.h"
7018334Speter#include "svn_error.h"
7118334Speter#include "svn_pools.h"
7218334Speter#include "svn_cmdline.h"
7318334Speter#include "svn_checksum.h"
7418334Speter#include "svn_string.h"
7518334Speter#include "svn_hash.h"
7618334Speter#include "svn_user.h"
7718334Speter#include "svn_dirent_uri.h"
7818334Speter
7918334Speter#include "private/svn_auth_private.h"
8018334Speter
8118334Speter#include "svn_private_config.h"
8218334Speter
8318334Speter#ifdef SVN_HAVE_GPG_AGENT
8418334Speter
8518334Speter#define BUFFER_SIZE 1024
8618334Speter#define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt"
8718334Speter
8818334Speter/* Modify STR in-place such that blanks are escaped as required by the
8918334Speter * gpg-agent protocol. Return a pointer to STR. */
9018334Speterstatic char *
9118334Speterescape_blanks(char *str)
9218334Speter{
9318334Speter  char *s = str;
9418334Speter
9518334Speter  while (*s)
9618334Speter    {
9718334Speter      if (*s == ' ')
9818334Speter        *s = '+';
9918334Speter      s++;
10018334Speter    }
10118334Speter
10218334Speter  return str;
10318334Speter}
10418334Speter
10518334Speter/* Generate the string CACHE_ID_P based on the REALMSTRING allocated in
10618334Speter * RESULT_POOL using SCRATCH_POOL for temporary allocations.  This is similar
10718334Speter * to other password caching mechanisms. */
10818334Speterstatic svn_error_t *
10918334Speterget_cache_id(const char **cache_id_p, const char *realmstring,
11018334Speter             apr_pool_t *scratch_pool, apr_pool_t *result_pool)
11118334Speter{
11218334Speter  const char *cache_id = NULL;
11318334Speter  svn_checksum_t *digest = NULL;
11418334Speter
11518334Speter  SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
11618334Speter                       strlen(realmstring), scratch_pool));
11718334Speter  cache_id = svn_checksum_to_cstring(digest, result_pool);
11818334Speter  *cache_id_p = cache_id;
11918334Speter
12018334Speter  return SVN_NO_ERROR;
12118334Speter}
12218334Speter
12318334Speter/* Attempt to read a gpg-agent response message from the socket SD into
12418334Speter * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
12518334Speter * message could be read that fits into the buffer. Else return FALSE.
12618334Speter * If a message could be read it will always be NUL-terminated and the
12718334Speter * trailing newline is retained. */
12818334Speterstatic svn_boolean_t
12918334Speterreceive_from_gpg_agent(int sd, char *buf, size_t n)
13018334Speter{
13118334Speter  int i = 0;
13218334Speter  size_t recvd;
13318334Speter  char c;
13418334Speter
13518334Speter  /* Clear existing buffer content before reading response. */
13618334Speter  if (n > 0)
13718334Speter    *buf = '\0';
13818334Speter
13918334Speter  /* Require the message to fit into the buffer and be terminated
14018334Speter   * with a newline. */
14118334Speter  while (i < n)
14218334Speter    {
14318334Speter      recvd = read(sd, &c, 1);
14418334Speter      if (recvd == -1)
14518334Speter        return FALSE;
14618334Speter      buf[i] = c;
14718334Speter      i++;
14818334Speter      if (i < n && c == '\n')
14918334Speter        {
15018334Speter          buf[i] = '\0';
15118334Speter          return TRUE;
15218334Speter        }
15318334Speter    }
15418334Speter
15518334Speter    return FALSE;
15618334Speter}
15718334Speter
15818334Speter/* Using socket SD, send the option OPTION with the specified VALUE
15918334Speter * to the gpg agent. Store the response in BUF, assumed to be N bytes
16018334Speter * in size, and evaluate the response. Return TRUE if the agent liked
16118334Speter * the smell of the option, if there is such a thing, and doesn't feel
16218334Speter * saturated by it. Else return FALSE.
16318334Speter * Do temporary allocations in scratch_pool. */
16418334Speterstatic svn_boolean_t
16518334Spetersend_option(int sd, char *buf, size_t n, const char *option, const char *value,
16618334Speter            apr_pool_t *scratch_pool)
16718334Speter{
16818334Speter  const char *request;
16918334Speter
17018334Speter  request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
17118334Speter
17218334Speter  if (write(sd, request, strlen(request)) == -1)
17318334Speter    return FALSE;
17418334Speter
17518334Speter  if (!receive_from_gpg_agent(sd, buf, n))
17618334Speter    return FALSE;
17718334Speter
17818334Speter  return (strncmp(buf, "OK", 2) == 0);
17918334Speter}
18018334Speter
18118334Speter/* Send the BYE command and disconnect from the gpg-agent.  Doing this avoids
18218334Speter * gpg-agent emitting a "Connection reset by peer" log message with some
18318334Speter * versions of gpg-agent. */
18418334Speterstatic void
18518334Speterbye_gpg_agent(int sd)
18618334Speter{
18718334Speter  /* don't bother to check the result of the write, it either worked or it
18818334Speter   * didn't, but either way we're closing. */
18918334Speter  write(sd, "BYE\n", 4);
19018334Speter  close(sd);
19118334Speter}
19218334Speter
19318334Speter/* Locate a running GPG Agent, and return an open file descriptor
19418334Speter * for communication with the agent in *NEW_SD. If no running agent
19518334Speter * can be found, set *NEW_SD to -1. */
19618334Speterstatic svn_error_t *
19718334Speterfind_running_gpg_agent(int *new_sd, apr_pool_t *pool)
19818334Speter{
19918334Speter  char *buffer;
20018334Speter  char *gpg_agent_info = NULL;
20118334Speter  const char *socket_name = NULL;
20218334Speter  const char *request = NULL;
20318334Speter  const char *p = NULL;
20418334Speter  char *ep = NULL;
20518334Speter  int sd;
20618334Speter
20718334Speter  *new_sd = -1;
20818334Speter
20918334Speter  /* This implements the method of finding the socket as described in
21018334Speter   * the gpg-agent man page under the --use-standard-socket option.
21118334Speter   * The manage page misleadingly says the standard socket is
21218334Speter   * "named 'S.gpg-agent' located in the home directory."  The standard
21318334Speter   * socket path is actually in the .gnupg directory in the home directory,
21418334Speter   * i.e. ~/.gnupg/S.gpg-agent */
21518334Speter  gpg_agent_info = getenv("GPG_AGENT_INFO");
21618334Speter  if (gpg_agent_info != NULL)
21718334Speter    {
21818334Speter      apr_array_header_t *socket_details;
21918334Speter
22018334Speter      /* For reference GPG_AGENT_INFO consists of 3 : separated fields.
22118334Speter       * The path to the socket, the pid of the gpg-agent process and
22218334Speter       * finally the version of the protocol the agent talks. */
22318334Speter      socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
22418334Speter                                         pool);
22518334Speter      socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
22618334Speter    }
22718334Speter  else
22818334Speter    {
22918334Speter      const char *homedir = svn_user_get_homedir(pool);
23018334Speter
23118334Speter      if (!homedir)
23218334Speter        return SVN_NO_ERROR;
23318334Speter
23418334Speter      socket_name = svn_dirent_join_many(pool, homedir, ".gnupg",
23518334Speter                                         "S.gpg-agent", NULL);
23618334Speter    }
23718334Speter
23818334Speter  if (socket_name != NULL)
23918334Speter    {
24018334Speter      struct sockaddr_un addr;
24118334Speter
24218334Speter      addr.sun_family = AF_UNIX;
24318334Speter      strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1);
24418334Speter      addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
24518334Speter
24618334Speter      sd = socket(AF_UNIX, SOCK_STREAM, 0);
24718334Speter      if (sd == -1)
24818334Speter        return SVN_NO_ERROR;
24918334Speter
25018334Speter      if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
25118334Speter        {
25218334Speter          close(sd);
25318334Speter          return SVN_NO_ERROR;
25418334Speter        }
25518334Speter    }
25618334Speter  else
25718334Speter    return SVN_NO_ERROR;
25818334Speter
25918334Speter  /* Receive the connection status from the gpg-agent daemon. */
26018334Speter  buffer = apr_palloc(pool, BUFFER_SIZE);
26118334Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
26218334Speter    {
26318334Speter      bye_gpg_agent(sd);
26418334Speter      return SVN_NO_ERROR;
26518334Speter    }
26618334Speter
26718334Speter  if (strncmp(buffer, "OK", 2) != 0)
26818334Speter    {
26918334Speter      bye_gpg_agent(sd);
27018334Speter      return SVN_NO_ERROR;
27118334Speter    }
27218334Speter
27318334Speter  /* The GPG-Agent documentation says:
27418334Speter   *  "Clients should deny to access an agent with a socket name which does
27518334Speter   *   not match its own configuration". */
27618334Speter  request = "GETINFO socket_name\n";
27718334Speter  if (write(sd, request, strlen(request)) == -1)
27818334Speter    {
27918334Speter      bye_gpg_agent(sd);
28018334Speter      return SVN_NO_ERROR;
28118334Speter    }
28218334Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
28318334Speter    {
28418334Speter      bye_gpg_agent(sd);
28518334Speter      return SVN_NO_ERROR;
28618334Speter    }
28718334Speter  if (strncmp(buffer, "D", 1) == 0)
28818334Speter    p = &buffer[2];
28918334Speter  if (!p)
29018334Speter    {
29118334Speter      bye_gpg_agent(sd);
29218334Speter      return SVN_NO_ERROR;
29318334Speter    }
29418334Speter  ep = strchr(p, '\n');
29518334Speter  if (ep != NULL)
29618334Speter    *ep = '\0';
29718334Speter  if (strcmp(socket_name, p) != 0)
29818334Speter    {
29918334Speter      bye_gpg_agent(sd);
30018334Speter      return SVN_NO_ERROR;
30118334Speter    }
30218334Speter  /* The agent will terminate its response with "OK". */
30318334Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
30418334Speter    {
30518334Speter      bye_gpg_agent(sd);
30618334Speter      return SVN_NO_ERROR;
30718334Speter    }
30818334Speter  if (strncmp(buffer, "OK", 2) != 0)
30918334Speter    {
31018334Speter      bye_gpg_agent(sd);
31118334Speter      return SVN_NO_ERROR;
31218334Speter    }
31318334Speter
31418334Speter  *new_sd = sd;
31518334Speter  return SVN_NO_ERROR;
31618334Speter}
31718334Speter
31818334Speterstatic svn_boolean_t
31918334Spetersend_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool)
32018334Speter{
32118334Speter  const char *tty_name;
32218334Speter  const char *tty_type;
32318334Speter  const char *lc_ctype;
32418334Speter  const char *display;
32518334Speter
32618334Speter  /* Send TTY_NAME to the gpg-agent daemon. */
32718334Speter  tty_name = getenv("GPG_TTY");
32818334Speter  if (tty_name != NULL)
32918334Speter    {
33018334Speter      if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool))
33118334Speter        return FALSE;
33218334Speter    }
33318334Speter
33418334Speter  /* Send TTY_TYPE to the gpg-agent daemon. */
33518334Speter  tty_type = getenv("TERM");
33618334Speter  if (tty_type != NULL)
33718334Speter    {
33818334Speter      if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool))
33918334Speter        return FALSE;
34018334Speter    }
34118334Speter
34218334Speter  /* Compute LC_CTYPE. */
34318334Speter  lc_ctype = getenv("LC_ALL");
34418334Speter  if (lc_ctype == NULL)
34518334Speter    lc_ctype = getenv("LC_CTYPE");
34618334Speter  if (lc_ctype == NULL)
34718334Speter    lc_ctype = getenv("LANG");
34818334Speter
34918334Speter  /* Send LC_CTYPE to the gpg-agent daemon. */
35018334Speter  if (lc_ctype != NULL)
35118334Speter    {
35218334Speter      if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool))
35318334Speter        return FALSE;
35418334Speter    }
35518334Speter
35618334Speter  /* Send DISPLAY to the gpg-agent daemon. */
35718334Speter  display = getenv("DISPLAY");
35818334Speter  if (display != NULL)
35918334Speter    {
36018334Speter      if (!send_option(sd, buf, n, "display", display, scratch_pool))
36118334Speter        return FALSE;
36218334Speter    }
36318334Speter
36418334Speter  return TRUE;
36518334Speter}
36618334Speter
36718334Speter/* Implementation of svn_auth__password_get_t that retrieves the password
36818334Speter   from gpg-agent */
36918334Speterstatic svn_error_t *
37018334Speterpassword_get_gpg_agent(svn_boolean_t *done,
37118334Speter                       const char **password,
37218334Speter                       apr_hash_t *creds,
37318334Speter                       const char *realmstring,
37418334Speter                       const char *username,
37518334Speter                       apr_hash_t *parameters,
37618334Speter                       svn_boolean_t non_interactive,
37718334Speter                       apr_pool_t *pool)
37818334Speter{
37918334Speter  int sd;
38018334Speter  const char *p = NULL;
38118334Speter  char *ep = NULL;
38218334Speter  char *buffer;
38318334Speter  const char *request = NULL;
38418334Speter  const char *cache_id = NULL;
38518334Speter  char *password_prompt;
38618334Speter  char *realm_prompt;
38718334Speter  char *error_prompt;
38818334Speter  int *attempt;
38918334Speter
39018334Speter  *done = FALSE;
39118334Speter
39218334Speter  attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER);
39318334Speter
39418334Speter  SVN_ERR(find_running_gpg_agent(&sd, pool));
39518334Speter  if (sd == -1)
39618334Speter    return SVN_NO_ERROR;
39718334Speter
39818334Speter  buffer = apr_palloc(pool, BUFFER_SIZE);
39918334Speter
40018334Speter  if (!send_options(sd, buffer, BUFFER_SIZE, pool))
40118334Speter    {
40218334Speter      bye_gpg_agent(sd);
40318334Speter      return SVN_NO_ERROR;
40418334Speter    }
40518334Speter
40618334Speter  SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
40718334Speter
40818334Speter  password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
40918334Speter  realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
41018334Speter                              realmstring);
41118334Speter  if (*attempt == 1)
41218334Speter    /* X means no error to the gpg-agent protocol */
41318334Speter    error_prompt = apr_pstrdup(pool, "X");
41418334Speter  else
41518334Speter    error_prompt = apr_pstrdup(pool, _("Authentication failed"));
41618334Speter
41718334Speter  request = apr_psprintf(pool,
41818334Speter                         "GET_PASSPHRASE --data %s"
41918334Speter                         "%s %s %s %s\n",
42018334Speter                         non_interactive ? "--no-ask " : "",
42118334Speter                         cache_id,
42218334Speter                         escape_blanks(error_prompt),
42318334Speter                         escape_blanks(password_prompt),
42418334Speter                         escape_blanks(realm_prompt));
42518334Speter
42618334Speter  if (write(sd, request, strlen(request)) == -1)
42718334Speter    {
42818334Speter      bye_gpg_agent(sd);
42918334Speter      return SVN_NO_ERROR;
43018334Speter    }
43118334Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
43218334Speter    {
43318334Speter      bye_gpg_agent(sd);
43418334Speter      return SVN_NO_ERROR;
43518334Speter    }
43618334Speter
43718334Speter  bye_gpg_agent(sd);
43818334Speter
43918334Speter  if (strncmp(buffer, "ERR", 3) == 0)
44018334Speter    return SVN_NO_ERROR;
44118334Speter
44218334Speter  p = NULL;
44318334Speter  if (strncmp(buffer, "D", 1) == 0)
44418334Speter    p = &buffer[2];
44518334Speter
44618334Speter  if (!p)
44718334Speter    return SVN_NO_ERROR;
44818334Speter
44918334Speter  ep = strchr(p, '\n');
45018334Speter  if (ep != NULL)
45118334Speter    *ep = '\0';
45218334Speter
45318334Speter  *password = p;
45418334Speter
45518334Speter  *done = TRUE;
45618334Speter  return SVN_NO_ERROR;
45718334Speter}
45818334Speter
45918334Speter
46018334Speter/* Implementation of svn_auth__password_set_t that would store the
46118334Speter   password in GPG Agent if that's how this particular integration
46218334Speter   worked.  But it isn't.  GPG Agent stores the password provided by
46318334Speter   the user via the pinentry program immediately upon its provision
46418334Speter   (and regardless of its accuracy as passwords go), so we just need
46518334Speter   to check if a running GPG Agent exists. */
46618334Speterstatic svn_error_t *
46718334Speterpassword_set_gpg_agent(svn_boolean_t *done,
46818334Speter                       apr_hash_t *creds,
46918334Speter                       const char *realmstring,
47018334Speter                       const char *username,
47118334Speter                       const char *password,
47218334Speter                       apr_hash_t *parameters,
47318334Speter                       svn_boolean_t non_interactive,
47418334Speter                       apr_pool_t *pool)
47518334Speter{
47618334Speter  int sd;
47718334Speter
47818334Speter  *done = FALSE;
47918334Speter
48018334Speter  SVN_ERR(find_running_gpg_agent(&sd, pool));
48118334Speter  if (sd == -1)
48218334Speter    return SVN_NO_ERROR;
48318334Speter
48418334Speter  bye_gpg_agent(sd);
48518334Speter  *done = TRUE;
48618334Speter
48718334Speter  return SVN_NO_ERROR;
48818334Speter}
48918334Speter
49018334Speter
49118334Speter/* An implementation of svn_auth_provider_t::first_credentials() */
49218334Speterstatic svn_error_t *
49318334Spetersimple_gpg_agent_first_creds(void **credentials,
49418334Speter                             void **iter_baton,
49518334Speter                             void *provider_baton,
49618334Speter                             apr_hash_t *parameters,
49718334Speter                             const char *realmstring,
49818334Speter                             apr_pool_t *pool)
49918334Speter{
50018334Speter  svn_error_t *err;
50118334Speter  int *attempt = apr_palloc(pool, sizeof(*attempt));
50218334Speter
50318334Speter  *attempt = 1;
50418334Speter  svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt);
50518334Speter  err = svn_auth__simple_creds_cache_get(credentials, iter_baton,
50618334Speter                                         provider_baton, parameters,
50718334Speter                                         realmstring, password_get_gpg_agent,
50818334Speter                                         SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
50918334Speter                                         pool);
51018334Speter  *iter_baton = attempt;
51118334Speter
51218334Speter  return err;
51318334Speter}
51418334Speter
51518334Speter/* An implementation of svn_auth_provider_t::next_credentials() */
51618334Speterstatic svn_error_t *
51718334Spetersimple_gpg_agent_next_creds(void **credentials,
51818334Speter                            void *iter_baton,
51918334Speter                            void *provider_baton,
52018334Speter                            apr_hash_t *parameters,
52118334Speter                            const char *realmstring,
52218334Speter                            apr_pool_t *pool)
52318334Speter{
52418334Speter  int *attempt = (int *)iter_baton;
52518334Speter  int sd;
52618334Speter  char *buffer;
52718334Speter  const char *cache_id = NULL;
52818334Speter  const char *request = NULL;
52918334Speter
53018334Speter  *credentials = NULL;
53118334Speter
53218334Speter  /* The users previous credentials failed so first remove the cached entry,
53318334Speter   * before trying to retrieve them again.  Because gpg-agent stores cached
53418334Speter   * credentials immediately upon retrieving them, this gives us the
53518334Speter   * opportunity to remove the invalid credentials and prompt the
53618334Speter   * user again.  While it's possible that server side issues could trigger
53718334Speter   * this, this cache is ephemeral so at worst we're just speeding up
53818334Speter   * when the user would need to re-enter their password. */
53918334Speter
54018334Speter  if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE))
54118334Speter    {
54218334Speter      /* In this case since we're running non-interactively we do not
54318334Speter       * want to clear the cache since the user was never prompted by
54418334Speter       * gpg-agent to set a password. */
54518334Speter      return SVN_NO_ERROR;
54618334Speter    }
54718334Speter
54818334Speter  *attempt = *attempt + 1;
54918334Speter
55018334Speter  SVN_ERR(find_running_gpg_agent(&sd, pool));
55118334Speter  if (sd == -1)
55218334Speter    return SVN_NO_ERROR;
55318334Speter
55418334Speter  buffer = apr_palloc(pool, BUFFER_SIZE);
55518334Speter
55618334Speter  if (!send_options(sd, buffer, BUFFER_SIZE, pool))
55718334Speter    {
55818334Speter      bye_gpg_agent(sd);
55918334Speter      return SVN_NO_ERROR;
56018334Speter    }
56118334Speter
56218334Speter  SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
56318334Speter
56418334Speter  request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id);
56518334Speter
56618334Speter  if (write(sd, request, strlen(request)) == -1)
56718334Speter    {
56818334Speter      bye_gpg_agent(sd);
56918334Speter      return SVN_NO_ERROR;
57018334Speter    }
57118334Speter
57218334Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
57318334Speter    {
57418334Speter      bye_gpg_agent(sd);
57518334Speter      return SVN_NO_ERROR;
57618334Speter    }
57718334Speter
57818334Speter  if (strncmp(buffer, "OK\n", 3) != 0)
57918334Speter    {
58018334Speter      bye_gpg_agent(sd);
58118334Speter      return SVN_NO_ERROR;
58218334Speter    }
58318334Speter
58418334Speter  /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries)
58518334Speter   * which matches svn command line client's retry_limit as set in
58618334Speter   * svn_cmdline_create_auth_baton().  It would be nice to have that
58718334Speter   * limit reflected here but that violates the boundry between the
58818334Speter   * prompt provider and the cache provider.  gpg-agent is acting as
58918334Speter   * both here due to the peculiarties of their design so we'll have to
59018334Speter   * live with this for now.  Note that when these failures get exceeded
59118334Speter   * it'll eventually fall back on the retry limits of whatever prompt
59218334Speter   * provider is in effect, so this effectively doubles the limit. */
59318334Speter  if (*attempt < 4)
59418334Speter    return svn_auth__simple_creds_cache_get(credentials, &iter_baton,
59518334Speter                                            provider_baton, parameters,
59618334Speter                                            realmstring,
59718334Speter                                            password_get_gpg_agent,
59818334Speter                                            SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
59918334Speter                                            pool);
60018334Speter
60118334Speter  return SVN_NO_ERROR;
60218334Speter}
60318334Speter
60418334Speter
60518334Speter/* An implementation of svn_auth_provider_t::save_credentials() */
60618334Speterstatic svn_error_t *
60718334Spetersimple_gpg_agent_save_creds(svn_boolean_t *saved,
60818334Speter                            void *credentials,
60918334Speter                            void *provider_baton,
61018334Speter                            apr_hash_t *parameters,
61118334Speter                            const char *realmstring,
61218334Speter                            apr_pool_t *pool)
61318334Speter{
61418334Speter  return svn_auth__simple_creds_cache_set(saved, credentials,
61518334Speter                                          provider_baton, parameters,
61618334Speter                                          realmstring, password_set_gpg_agent,
61718334Speter                                          SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
61818334Speter                                          pool);
61918334Speter}
62018334Speter
62118334Speter
62218334Speterstatic const svn_auth_provider_t gpg_agent_simple_provider = {
62318334Speter  SVN_AUTH_CRED_SIMPLE,
62418334Speter  simple_gpg_agent_first_creds,
62518334Speter  simple_gpg_agent_next_creds,
62618334Speter  simple_gpg_agent_save_creds
62718334Speter};
62818334Speter
62918334Speter
63018334Speter/* Public API */
63118334Spetervoid
63218334Spetersvn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
63318334Speter                                       apr_pool_t *pool)
63418334Speter{
63518334Speter  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
63618334Speter
63718334Speter  po->vtable = &gpg_agent_simple_provider;
63818334Speter  *provider = po;
63918334Speter}
64018334Speter
64118334Speter#endif /* SVN_HAVE_GPG_AGENT */
64218334Speter#endif /* !WIN32 */
64318334Speter