1251881Speter/*
2251881Speter * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_*
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter/* This auth provider stores a plaintext password in memory managed by
27251881Speter * a running gpg-agent. In contrast to other password store providers
28251881Speter * it does not save the password to disk.
29251881Speter *
30251881Speter * Prompting is performed by the gpg-agent using a "pinentry" program
31251881Speter * which needs to be installed separately. There are several pinentry
32251881Speter * implementations with different front-ends (e.g. qt, gtk, ncurses).
33251881Speter *
34251881Speter * The gpg-agent will let the password time out after a while,
35251881Speter * or immediately when it receives the SIGHUP signal.
36251881Speter * When the password has timed out it will automatically prompt the
37251881Speter * user for the password again. This is transparent to Subversion.
38251881Speter *
39251881Speter * SECURITY CONSIDERATIONS:
40251881Speter *
41251881Speter * Communication to the agent happens over a UNIX socket, which is located
42251881Speter * in a directory which only the user running Subversion can access.
43251881Speter * However, any program the user runs could access this socket and get
44251881Speter * the Subversion password if the program knows the "cache ID" Subversion
45251881Speter * uses for the password.
46251881Speter * The cache ID is very easy to obtain for programs running as the same user.
47251881Speter * Subversion uses the MD5 of the realmstring as cache ID, and these checksums
48251881Speter * are also used as filenames within ~/.subversion/auth/svn.simple.
49251881Speter * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for
50251881Speter * permission if another program attempts to access the password.
51251881Speter *
52251881Speter * Therefore, while the gpg-agent is running and has the password cached,
53251881Speter * this provider is no more secure than a file storing the password in
54251881Speter * plaintext.
55251881Speter */
56251881Speter
57251881Speter
58251881Speter/*** Includes. ***/
59251881Speter
60251881Speter#ifndef WIN32
61251881Speter
62251881Speter#include <unistd.h>
63251881Speter
64251881Speter#include <sys/socket.h>
65251881Speter#include <sys/un.h>
66251881Speter
67251881Speter#include <apr_pools.h>
68251881Speter#include "svn_auth.h"
69251881Speter#include "svn_config.h"
70251881Speter#include "svn_error.h"
71251881Speter#include "svn_pools.h"
72251881Speter#include "svn_cmdline.h"
73251881Speter#include "svn_checksum.h"
74251881Speter#include "svn_string.h"
75289166Speter#include "svn_hash.h"
76289166Speter#include "svn_user.h"
77289166Speter#include "svn_dirent_uri.h"
78251881Speter
79251881Speter#include "private/svn_auth_private.h"
80251881Speter
81251881Speter#include "svn_private_config.h"
82251881Speter
83251881Speter#ifdef SVN_HAVE_GPG_AGENT
84251881Speter
85251881Speter#define BUFFER_SIZE 1024
86289166Speter#define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt"
87251881Speter
88251881Speter/* Modify STR in-place such that blanks are escaped as required by the
89251881Speter * gpg-agent protocol. Return a pointer to STR. */
90251881Speterstatic char *
91251881Speterescape_blanks(char *str)
92251881Speter{
93251881Speter  char *s = str;
94251881Speter
95251881Speter  while (*s)
96251881Speter    {
97251881Speter      if (*s == ' ')
98251881Speter        *s = '+';
99251881Speter      s++;
100251881Speter    }
101251881Speter
102251881Speter  return str;
103251881Speter}
104251881Speter
105289166Speter/* Generate the string CACHE_ID_P based on the REALMSTRING allocated in
106289166Speter * RESULT_POOL using SCRATCH_POOL for temporary allocations.  This is similar
107289166Speter * to other password caching mechanisms. */
108289166Speterstatic svn_error_t *
109289166Speterget_cache_id(const char **cache_id_p, const char *realmstring,
110289166Speter             apr_pool_t *scratch_pool, apr_pool_t *result_pool)
111289166Speter{
112289166Speter  const char *cache_id = NULL;
113289166Speter  svn_checksum_t *digest = NULL;
114289166Speter
115289166Speter  SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
116289166Speter                       strlen(realmstring), scratch_pool));
117289166Speter  cache_id = svn_checksum_to_cstring(digest, result_pool);
118289166Speter  *cache_id_p = cache_id;
119289166Speter
120289166Speter  return SVN_NO_ERROR;
121289166Speter}
122289166Speter
123251881Speter/* Attempt to read a gpg-agent response message from the socket SD into
124251881Speter * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
125251881Speter * message could be read that fits into the buffer. Else return FALSE.
126251881Speter * If a message could be read it will always be NUL-terminated and the
127251881Speter * trailing newline is retained. */
128251881Speterstatic svn_boolean_t
129251881Speterreceive_from_gpg_agent(int sd, char *buf, size_t n)
130251881Speter{
131251881Speter  int i = 0;
132251881Speter  size_t recvd;
133251881Speter  char c;
134251881Speter
135251881Speter  /* Clear existing buffer content before reading response. */
136251881Speter  if (n > 0)
137251881Speter    *buf = '\0';
138251881Speter
139251881Speter  /* Require the message to fit into the buffer and be terminated
140251881Speter   * with a newline. */
141251881Speter  while (i < n)
142251881Speter    {
143251881Speter      recvd = read(sd, &c, 1);
144251881Speter      if (recvd == -1)
145251881Speter        return FALSE;
146251881Speter      buf[i] = c;
147251881Speter      i++;
148251881Speter      if (i < n && c == '\n')
149251881Speter        {
150251881Speter          buf[i] = '\0';
151251881Speter          return TRUE;
152251881Speter        }
153251881Speter    }
154251881Speter
155251881Speter    return FALSE;
156251881Speter}
157251881Speter
158251881Speter/* Using socket SD, send the option OPTION with the specified VALUE
159251881Speter * to the gpg agent. Store the response in BUF, assumed to be N bytes
160251881Speter * in size, and evaluate the response. Return TRUE if the agent liked
161251881Speter * the smell of the option, if there is such a thing, and doesn't feel
162251881Speter * saturated by it. Else return FALSE.
163251881Speter * Do temporary allocations in scratch_pool. */
164251881Speterstatic svn_boolean_t
165251881Spetersend_option(int sd, char *buf, size_t n, const char *option, const char *value,
166251881Speter            apr_pool_t *scratch_pool)
167251881Speter{
168251881Speter  const char *request;
169251881Speter
170251881Speter  request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
171251881Speter
172251881Speter  if (write(sd, request, strlen(request)) == -1)
173251881Speter    return FALSE;
174251881Speter
175251881Speter  if (!receive_from_gpg_agent(sd, buf, n))
176251881Speter    return FALSE;
177251881Speter
178251881Speter  return (strncmp(buf, "OK", 2) == 0);
179251881Speter}
180251881Speter
181289166Speter/* Send the BYE command and disconnect from the gpg-agent.  Doing this avoids
182289166Speter * gpg-agent emitting a "Connection reset by peer" log message with some
183289166Speter * versions of gpg-agent. */
184289166Speterstatic void
185289166Speterbye_gpg_agent(int sd)
186289166Speter{
187289166Speter  /* don't bother to check the result of the write, it either worked or it
188289166Speter   * didn't, but either way we're closing. */
189289166Speter  write(sd, "BYE\n", 4);
190289166Speter  close(sd);
191289166Speter}
192253734Speter
193253734Speter/* Locate a running GPG Agent, and return an open file descriptor
194253734Speter * for communication with the agent in *NEW_SD. If no running agent
195253734Speter * can be found, set *NEW_SD to -1. */
196251881Speterstatic svn_error_t *
197253734Speterfind_running_gpg_agent(int *new_sd, apr_pool_t *pool)
198251881Speter{
199253734Speter  char *buffer;
200251881Speter  char *gpg_agent_info = NULL;
201253734Speter  const char *socket_name = NULL;
202253734Speter  const char *request = NULL;
203251881Speter  const char *p = NULL;
204251881Speter  char *ep = NULL;
205253734Speter  int sd;
206251881Speter
207253734Speter  *new_sd = -1;
208251881Speter
209289166Speter  /* This implements the method of finding the socket as described in
210289166Speter   * the gpg-agent man page under the --use-standard-socket option.
211289166Speter   * The manage page misleadingly says the standard socket is
212289166Speter   * "named 'S.gpg-agent' located in the home directory."  The standard
213289166Speter   * socket path is actually in the .gnupg directory in the home directory,
214289166Speter   * i.e. ~/.gnupg/S.gpg-agent */
215251881Speter  gpg_agent_info = getenv("GPG_AGENT_INFO");
216251881Speter  if (gpg_agent_info != NULL)
217251881Speter    {
218253734Speter      apr_array_header_t *socket_details;
219253734Speter
220289166Speter      /* For reference GPG_AGENT_INFO consists of 3 : separated fields.
221289166Speter       * The path to the socket, the pid of the gpg-agent process and
222289166Speter       * finally the version of the protocol the agent talks. */
223251881Speter      socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
224251881Speter                                         pool);
225251881Speter      socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
226251881Speter    }
227251881Speter  else
228289166Speter    {
229289166Speter      const char *homedir = svn_user_get_homedir(pool);
230251881Speter
231289166Speter      if (!homedir)
232289166Speter        return SVN_NO_ERROR;
233289166Speter
234289166Speter      socket_name = svn_dirent_join_many(pool, homedir, ".gnupg",
235289166Speter                                         "S.gpg-agent", NULL);
236289166Speter    }
237289166Speter
238251881Speter  if (socket_name != NULL)
239251881Speter    {
240253734Speter      struct sockaddr_un addr;
241253734Speter
242251881Speter      addr.sun_family = AF_UNIX;
243251881Speter      strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1);
244251881Speter      addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
245251881Speter
246251881Speter      sd = socket(AF_UNIX, SOCK_STREAM, 0);
247251881Speter      if (sd == -1)
248251881Speter        return SVN_NO_ERROR;
249251881Speter
250251881Speter      if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
251251881Speter        {
252251881Speter          close(sd);
253251881Speter          return SVN_NO_ERROR;
254251881Speter        }
255251881Speter    }
256251881Speter  else
257251881Speter    return SVN_NO_ERROR;
258251881Speter
259251881Speter  /* Receive the connection status from the gpg-agent daemon. */
260251881Speter  buffer = apr_palloc(pool, BUFFER_SIZE);
261251881Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
262251881Speter    {
263289166Speter      bye_gpg_agent(sd);
264251881Speter      return SVN_NO_ERROR;
265251881Speter    }
266251881Speter
267251881Speter  if (strncmp(buffer, "OK", 2) != 0)
268251881Speter    {
269289166Speter      bye_gpg_agent(sd);
270251881Speter      return SVN_NO_ERROR;
271251881Speter    }
272251881Speter
273251881Speter  /* The GPG-Agent documentation says:
274251881Speter   *  "Clients should deny to access an agent with a socket name which does
275251881Speter   *   not match its own configuration". */
276251881Speter  request = "GETINFO socket_name\n";
277251881Speter  if (write(sd, request, strlen(request)) == -1)
278251881Speter    {
279289166Speter      bye_gpg_agent(sd);
280251881Speter      return SVN_NO_ERROR;
281251881Speter    }
282251881Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
283251881Speter    {
284289166Speter      bye_gpg_agent(sd);
285251881Speter      return SVN_NO_ERROR;
286251881Speter    }
287251881Speter  if (strncmp(buffer, "D", 1) == 0)
288251881Speter    p = &buffer[2];
289251881Speter  if (!p)
290251881Speter    {
291289166Speter      bye_gpg_agent(sd);
292251881Speter      return SVN_NO_ERROR;
293251881Speter    }
294251881Speter  ep = strchr(p, '\n');
295251881Speter  if (ep != NULL)
296251881Speter    *ep = '\0';
297251881Speter  if (strcmp(socket_name, p) != 0)
298251881Speter    {
299289166Speter      bye_gpg_agent(sd);
300251881Speter      return SVN_NO_ERROR;
301251881Speter    }
302251881Speter  /* The agent will terminate its response with "OK". */
303251881Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
304251881Speter    {
305289166Speter      bye_gpg_agent(sd);
306251881Speter      return SVN_NO_ERROR;
307251881Speter    }
308251881Speter  if (strncmp(buffer, "OK", 2) != 0)
309251881Speter    {
310289166Speter      bye_gpg_agent(sd);
311251881Speter      return SVN_NO_ERROR;
312251881Speter    }
313251881Speter
314253734Speter  *new_sd = sd;
315253734Speter  return SVN_NO_ERROR;
316253734Speter}
317253734Speter
318289166Speterstatic svn_boolean_t
319289166Spetersend_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool)
320253734Speter{
321253734Speter  const char *tty_name;
322253734Speter  const char *tty_type;
323253734Speter  const char *lc_ctype;
324253734Speter  const char *display;
325253734Speter
326251881Speter  /* Send TTY_NAME to the gpg-agent daemon. */
327251881Speter  tty_name = getenv("GPG_TTY");
328251881Speter  if (tty_name != NULL)
329251881Speter    {
330289166Speter      if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool))
331289166Speter        return FALSE;
332251881Speter    }
333251881Speter
334251881Speter  /* Send TTY_TYPE to the gpg-agent daemon. */
335251881Speter  tty_type = getenv("TERM");
336251881Speter  if (tty_type != NULL)
337251881Speter    {
338289166Speter      if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool))
339289166Speter        return FALSE;
340251881Speter    }
341251881Speter
342251881Speter  /* Compute LC_CTYPE. */
343251881Speter  lc_ctype = getenv("LC_ALL");
344251881Speter  if (lc_ctype == NULL)
345251881Speter    lc_ctype = getenv("LC_CTYPE");
346251881Speter  if (lc_ctype == NULL)
347251881Speter    lc_ctype = getenv("LANG");
348251881Speter
349251881Speter  /* Send LC_CTYPE to the gpg-agent daemon. */
350251881Speter  if (lc_ctype != NULL)
351251881Speter    {
352289166Speter      if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool))
353289166Speter        return FALSE;
354251881Speter    }
355251881Speter
356251881Speter  /* Send DISPLAY to the gpg-agent daemon. */
357251881Speter  display = getenv("DISPLAY");
358251881Speter  if (display != NULL)
359251881Speter    {
360289166Speter      if (!send_option(sd, buf, n, "display", display, scratch_pool))
361289166Speter        return FALSE;
362251881Speter    }
363251881Speter
364289166Speter  return TRUE;
365289166Speter}
366251881Speter
367289166Speter/* Implementation of svn_auth__password_get_t that retrieves the password
368289166Speter   from gpg-agent */
369289166Speterstatic svn_error_t *
370289166Speterpassword_get_gpg_agent(svn_boolean_t *done,
371289166Speter                       const char **password,
372289166Speter                       apr_hash_t *creds,
373289166Speter                       const char *realmstring,
374289166Speter                       const char *username,
375289166Speter                       apr_hash_t *parameters,
376289166Speter                       svn_boolean_t non_interactive,
377289166Speter                       apr_pool_t *pool)
378289166Speter{
379289166Speter  int sd;
380289166Speter  const char *p = NULL;
381289166Speter  char *ep = NULL;
382289166Speter  char *buffer;
383289166Speter  const char *request = NULL;
384289166Speter  const char *cache_id = NULL;
385289166Speter  char *password_prompt;
386289166Speter  char *realm_prompt;
387289166Speter  char *error_prompt;
388289166Speter  int *attempt;
389289166Speter
390289166Speter  *done = FALSE;
391289166Speter
392289166Speter  attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER);
393289166Speter
394289166Speter  SVN_ERR(find_running_gpg_agent(&sd, pool));
395289166Speter  if (sd == -1)
396289166Speter    return SVN_NO_ERROR;
397289166Speter
398289166Speter  buffer = apr_palloc(pool, BUFFER_SIZE);
399289166Speter
400289166Speter  if (!send_options(sd, buffer, BUFFER_SIZE, pool))
401289166Speter    {
402289166Speter      bye_gpg_agent(sd);
403289166Speter      return SVN_NO_ERROR;
404289166Speter    }
405289166Speter
406289166Speter  SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
407289166Speter
408251881Speter  password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
409251881Speter  realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
410251881Speter                              realmstring);
411289166Speter  if (*attempt == 1)
412289166Speter    /* X means no error to the gpg-agent protocol */
413289166Speter    error_prompt = apr_pstrdup(pool, "X");
414289166Speter  else
415289166Speter    error_prompt = apr_pstrdup(pool, _("Authentication failed"));
416289166Speter
417251881Speter  request = apr_psprintf(pool,
418289166Speter                         "GET_PASSPHRASE --data %s"
419289166Speter                         "%s %s %s %s\n",
420251881Speter                         non_interactive ? "--no-ask " : "",
421251881Speter                         cache_id,
422289166Speter                         escape_blanks(error_prompt),
423251881Speter                         escape_blanks(password_prompt),
424251881Speter                         escape_blanks(realm_prompt));
425251881Speter
426251881Speter  if (write(sd, request, strlen(request)) == -1)
427251881Speter    {
428289166Speter      bye_gpg_agent(sd);
429251881Speter      return SVN_NO_ERROR;
430251881Speter    }
431251881Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
432251881Speter    {
433289166Speter      bye_gpg_agent(sd);
434251881Speter      return SVN_NO_ERROR;
435251881Speter    }
436251881Speter
437289166Speter  bye_gpg_agent(sd);
438251881Speter
439251881Speter  if (strncmp(buffer, "ERR", 3) == 0)
440251881Speter    return SVN_NO_ERROR;
441251881Speter
442251881Speter  p = NULL;
443251881Speter  if (strncmp(buffer, "D", 1) == 0)
444251881Speter    p = &buffer[2];
445251881Speter
446251881Speter  if (!p)
447251881Speter    return SVN_NO_ERROR;
448251881Speter
449251881Speter  ep = strchr(p, '\n');
450251881Speter  if (ep != NULL)
451251881Speter    *ep = '\0';
452251881Speter
453251881Speter  *password = p;
454251881Speter
455251881Speter  *done = TRUE;
456251881Speter  return SVN_NO_ERROR;
457251881Speter}
458251881Speter
459251881Speter
460251881Speter/* Implementation of svn_auth__password_set_t that would store the
461251881Speter   password in GPG Agent if that's how this particular integration
462251881Speter   worked.  But it isn't.  GPG Agent stores the password provided by
463251881Speter   the user via the pinentry program immediately upon its provision
464253734Speter   (and regardless of its accuracy as passwords go), so we just need
465253734Speter   to check if a running GPG Agent exists. */
466251881Speterstatic svn_error_t *
467251881Speterpassword_set_gpg_agent(svn_boolean_t *done,
468251881Speter                       apr_hash_t *creds,
469251881Speter                       const char *realmstring,
470251881Speter                       const char *username,
471251881Speter                       const char *password,
472251881Speter                       apr_hash_t *parameters,
473251881Speter                       svn_boolean_t non_interactive,
474251881Speter                       apr_pool_t *pool)
475251881Speter{
476253734Speter  int sd;
477253734Speter
478253734Speter  *done = FALSE;
479253734Speter
480253734Speter  SVN_ERR(find_running_gpg_agent(&sd, pool));
481253734Speter  if (sd == -1)
482253734Speter    return SVN_NO_ERROR;
483253734Speter
484289166Speter  bye_gpg_agent(sd);
485251881Speter  *done = TRUE;
486251881Speter
487251881Speter  return SVN_NO_ERROR;
488251881Speter}
489251881Speter
490251881Speter
491251881Speter/* An implementation of svn_auth_provider_t::first_credentials() */
492251881Speterstatic svn_error_t *
493251881Spetersimple_gpg_agent_first_creds(void **credentials,
494251881Speter                             void **iter_baton,
495251881Speter                             void *provider_baton,
496251881Speter                             apr_hash_t *parameters,
497251881Speter                             const char *realmstring,
498251881Speter                             apr_pool_t *pool)
499251881Speter{
500289166Speter  svn_error_t *err;
501289166Speter  int *attempt = apr_palloc(pool, sizeof(*attempt));
502289166Speter
503289166Speter  *attempt = 1;
504289166Speter  svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt);
505289166Speter  err = svn_auth__simple_creds_cache_get(credentials, iter_baton,
506289166Speter                                         provider_baton, parameters,
507289166Speter                                         realmstring, password_get_gpg_agent,
508289166Speter                                         SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
509289166Speter                                         pool);
510289166Speter  *iter_baton = attempt;
511289166Speter
512289166Speter  return err;
513251881Speter}
514251881Speter
515289166Speter/* An implementation of svn_auth_provider_t::next_credentials() */
516289166Speterstatic svn_error_t *
517289166Spetersimple_gpg_agent_next_creds(void **credentials,
518289166Speter                            void *iter_baton,
519289166Speter                            void *provider_baton,
520289166Speter                            apr_hash_t *parameters,
521289166Speter                            const char *realmstring,
522289166Speter                            apr_pool_t *pool)
523289166Speter{
524289166Speter  int *attempt = (int *)iter_baton;
525289166Speter  int sd;
526289166Speter  char *buffer;
527289166Speter  const char *cache_id = NULL;
528289166Speter  const char *request = NULL;
529251881Speter
530289166Speter  *credentials = NULL;
531289166Speter
532289166Speter  /* The users previous credentials failed so first remove the cached entry,
533289166Speter   * before trying to retrieve them again.  Because gpg-agent stores cached
534289166Speter   * credentials immediately upon retrieving them, this gives us the
535289166Speter   * opportunity to remove the invalid credentials and prompt the
536289166Speter   * user again.  While it's possible that server side issues could trigger
537289166Speter   * this, this cache is ephemeral so at worst we're just speeding up
538289166Speter   * when the user would need to re-enter their password. */
539289166Speter
540289166Speter  if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE))
541289166Speter    {
542289166Speter      /* In this case since we're running non-interactively we do not
543289166Speter       * want to clear the cache since the user was never prompted by
544289166Speter       * gpg-agent to set a password. */
545289166Speter      return SVN_NO_ERROR;
546289166Speter    }
547289166Speter
548289166Speter  *attempt = *attempt + 1;
549289166Speter
550289166Speter  SVN_ERR(find_running_gpg_agent(&sd, pool));
551289166Speter  if (sd == -1)
552289166Speter    return SVN_NO_ERROR;
553289166Speter
554289166Speter  buffer = apr_palloc(pool, BUFFER_SIZE);
555289166Speter
556289166Speter  if (!send_options(sd, buffer, BUFFER_SIZE, pool))
557289166Speter    {
558289166Speter      bye_gpg_agent(sd);
559289166Speter      return SVN_NO_ERROR;
560289166Speter    }
561289166Speter
562289166Speter  SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
563289166Speter
564289166Speter  request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id);
565289166Speter
566289166Speter  if (write(sd, request, strlen(request)) == -1)
567289166Speter    {
568289166Speter      bye_gpg_agent(sd);
569289166Speter      return SVN_NO_ERROR;
570289166Speter    }
571289166Speter
572289166Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
573289166Speter    {
574289166Speter      bye_gpg_agent(sd);
575289166Speter      return SVN_NO_ERROR;
576289166Speter    }
577289166Speter
578289166Speter  if (strncmp(buffer, "OK\n", 3) != 0)
579289166Speter    {
580289166Speter      bye_gpg_agent(sd);
581289166Speter      return SVN_NO_ERROR;
582289166Speter    }
583289166Speter
584289166Speter  /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries)
585289166Speter   * which matches svn command line client's retry_limit as set in
586289166Speter   * svn_cmdline_create_auth_baton().  It would be nice to have that
587289166Speter   * limit reflected here but that violates the boundry between the
588289166Speter   * prompt provider and the cache provider.  gpg-agent is acting as
589289166Speter   * both here due to the peculiarties of their design so we'll have to
590289166Speter   * live with this for now.  Note that when these failures get exceeded
591289166Speter   * it'll eventually fall back on the retry limits of whatever prompt
592289166Speter   * provider is in effect, so this effectively doubles the limit. */
593289166Speter  if (*attempt < 4)
594289166Speter    return svn_auth__simple_creds_cache_get(credentials, &iter_baton,
595289166Speter                                            provider_baton, parameters,
596289166Speter                                            realmstring,
597289166Speter                                            password_get_gpg_agent,
598289166Speter                                            SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
599289166Speter                                            pool);
600289166Speter
601289166Speter  return SVN_NO_ERROR;
602289166Speter}
603289166Speter
604289166Speter
605251881Speter/* An implementation of svn_auth_provider_t::save_credentials() */
606251881Speterstatic svn_error_t *
607251881Spetersimple_gpg_agent_save_creds(svn_boolean_t *saved,
608251881Speter                            void *credentials,
609251881Speter                            void *provider_baton,
610251881Speter                            apr_hash_t *parameters,
611251881Speter                            const char *realmstring,
612251881Speter                            apr_pool_t *pool)
613251881Speter{
614251881Speter  return svn_auth__simple_creds_cache_set(saved, credentials,
615251881Speter                                          provider_baton, parameters,
616251881Speter                                          realmstring, password_set_gpg_agent,
617251881Speter                                          SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
618251881Speter                                          pool);
619251881Speter}
620251881Speter
621251881Speter
622251881Speterstatic const svn_auth_provider_t gpg_agent_simple_provider = {
623251881Speter  SVN_AUTH_CRED_SIMPLE,
624251881Speter  simple_gpg_agent_first_creds,
625289166Speter  simple_gpg_agent_next_creds,
626251881Speter  simple_gpg_agent_save_creds
627251881Speter};
628251881Speter
629251881Speter
630251881Speter/* Public API */
631251881Spetervoid
632251881Spetersvn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
633251881Speter                                       apr_pool_t *pool)
634251881Speter{
635251881Speter  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
636251881Speter
637251881Speter  po->vtable = &gpg_agent_simple_provider;
638251881Speter  *provider = po;
639251881Speter}
640251881Speter
641251881Speter#endif /* SVN_HAVE_GPG_AGENT */
642251881Speter#endif /* !WIN32 */
643