1 /*
2  * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_*
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 /* ==================================================================== */
25 
26 /* This auth provider stores a plaintext password in memory managed by
27  * a running gpg-agent. In contrast to other password store providers
28  * it does not save the password to disk.
29  *
30  * Prompting is performed by the gpg-agent using a "pinentry" program
31  * which needs to be installed separately. There are several pinentry
32  * implementations with different front-ends (e.g. qt, gtk, ncurses).
33  *
34  * The gpg-agent will let the password time out after a while,
35  * or immediately when it receives the SIGHUP signal.
36  * When the password has timed out it will automatically prompt the
37  * user for the password again. This is transparent to Subversion.
38  *
39  * SECURITY CONSIDERATIONS:
40  *
41  * Communication to the agent happens over a UNIX socket, which is located
42  * in a directory which only the user running Subversion can access.
43  * However, any program the user runs could access this socket and get
44  * the Subversion password if the program knows the "cache ID" Subversion
45  * uses for the password.
46  * The cache ID is very easy to obtain for programs running as the same user.
47  * Subversion uses the MD5 of the realmstring as cache ID, and these checksums
48  * are also used as filenames within ~/.subversion/auth/svn.simple.
49  * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for
50  * permission if another program attempts to access the password.
51  *
52  * Therefore, while the gpg-agent is running and has the password cached,
53  * this provider is no more secure than a file storing the password in
54  * plaintext.
55  */
56 
57 
58 /*** Includes. ***/
59 
60 #ifndef WIN32
61 
62 #include <unistd.h>
63 
64 #include <sys/socket.h>
65 #include <sys/un.h>
66 
67 #include <apr_pools.h>
68 #include <apr_strings.h>
69 #include <apr_user.h>
70 #include "svn_auth.h"
71 #include "svn_config.h"
72 #include "svn_error.h"
73 #include "svn_io.h"
74 #include "svn_pools.h"
75 #include "svn_cmdline.h"
76 #include "svn_checksum.h"
77 #include "svn_string.h"
78 #include "svn_hash.h"
79 #include "svn_user.h"
80 #include "svn_dirent_uri.h"
81 
82 #include "auth.h"
83 #include "private/svn_auth_private.h"
84 
85 #include "svn_private_config.h"
86 
87 #ifdef SVN_HAVE_GPG_AGENT
88 
89 #define BUFFER_SIZE 1024
90 #define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt"
91 
92 /* Modify STR in-place such that blanks are escaped as required by the
93  * gpg-agent protocol. Return a pointer to STR. */
94 static char *
escape_blanks(char * str)95 escape_blanks(char *str)
96 {
97   char *s = str;
98 
99   while (*s)
100     {
101       if (*s == ' ')
102         *s = '+';
103       s++;
104     }
105 
106   return str;
107 }
108 
109 #define is_hex(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F'))
110 #define hex_to_int(c) ((c) < '9' ? (c) - '0' : (c) - 'A' + 10)
111 
112 /* Modify STR in-place.  '%', CR and LF are always percent escaped,
113    other characters may be percent escaped, always using uppercase
114    hex, see https://www.gnupg.org/documentation/manuals/assuan.pdf */
115 static char *
unescape_assuan(char * str)116 unescape_assuan(char *str)
117 {
118   char *s = str;
119 
120   while (s[0])
121     {
122       if (s[0] == '%' && is_hex(s[1]) && is_hex(s[2]))
123         {
124           char *s2 = s;
125           char val = hex_to_int(s[1]) * 16 + hex_to_int(s[2]);
126 
127           s2[0] = val;
128           ++s2;
129 
130           while (s2[2])
131             {
132               s2[0] = s2[2];
133               ++s2;
134             }
135           s2[0] = '\0';
136         }
137       ++s;
138     }
139 
140   return str;
141 }
142 
143 /* Generate the string CACHE_ID_P based on the REALMSTRING allocated in
144  * RESULT_POOL using SCRATCH_POOL for temporary allocations.  This is similar
145  * to other password caching mechanisms. */
146 static svn_error_t *
get_cache_id(const char ** cache_id_p,const char * realmstring,apr_pool_t * result_pool,apr_pool_t * scratch_pool)147 get_cache_id(const char **cache_id_p, const char *realmstring,
148              apr_pool_t *result_pool, apr_pool_t *scratch_pool)
149 {
150   const char *cache_id = NULL;
151   svn_checksum_t *digest = NULL;
152 
153   SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
154                        strlen(realmstring), scratch_pool));
155   cache_id = svn_checksum_to_cstring(digest, result_pool);
156   *cache_id_p = cache_id;
157 
158   return SVN_NO_ERROR;
159 }
160 
161 /* Attempt to read a gpg-agent response message from the socket SD into
162  * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
163  * message could be read that fits into the buffer. Else return FALSE.
164  * If a message could be read it will always be NUL-terminated and the
165  * trailing newline is retained. */
166 static svn_boolean_t
receive_from_gpg_agent(int sd,char * buf,size_t n)167 receive_from_gpg_agent(int sd, char *buf, size_t n)
168 {
169   int i = 0;
170   size_t recvd;
171   char c;
172 
173   /* Clear existing buffer content before reading response. */
174   if (n > 0)
175     *buf = '\0';
176 
177   /* Require the message to fit into the buffer and be terminated
178    * with a newline. */
179   while (i < n)
180     {
181       recvd = read(sd, &c, 1);
182       if (recvd == -1)
183         return FALSE;
184       buf[i] = c;
185       i++;
186       if (i < n && c == '\n')
187         {
188           buf[i] = '\0';
189           return TRUE;
190         }
191     }
192 
193     return FALSE;
194 }
195 
196 /* Using socket SD, send the option OPTION with the specified VALUE
197  * to the gpg agent. Store the response in BUF, assumed to be N bytes
198  * in size, and evaluate the response. Return TRUE if the agent liked
199  * the smell of the option, if there is such a thing, and doesn't feel
200  * saturated by it. Else return FALSE.
201  * Do temporary allocations in scratch_pool. */
202 static svn_boolean_t
send_option(int sd,char * buf,size_t n,const char * option,const char * value,apr_pool_t * scratch_pool)203 send_option(int sd, char *buf, size_t n, const char *option, const char *value,
204             apr_pool_t *scratch_pool)
205 {
206   const char *request;
207 
208   request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
209 
210   if (write(sd, request, strlen(request)) == -1)
211     return FALSE;
212 
213   if (!receive_from_gpg_agent(sd, buf, n))
214     return FALSE;
215 
216   return (strncmp(buf, "OK", 2) == 0);
217 }
218 
219 /* Send the BYE command and disconnect from the gpg-agent.  Doing this avoids
220  * gpg-agent emitting a "Connection reset by peer" log message with some
221  * versions of gpg-agent. */
222 static void
bye_gpg_agent(int sd)223 bye_gpg_agent(int sd)
224 {
225   /* don't bother to check the result of the write, it either worked or it
226    * didn't, but either way we're closing. */
227   write(sd, "BYE\n", 4);
228   close(sd);
229 }
230 
231 /* This implements a method of finding the socket which is a mix of the
232  * description from GPG 1.x's gpg-agent man page under the
233  * --use-standard-socket option and the logic from GPG 2.x's socket discovery
234  * code in common/homedir.c.
235  *
236  * The man page says the standard socket is "named 'S.gpg-agent' located
237  * in the home directory."  GPG's home directory is either the directory
238  * specified by $GNUPGHOME or ~/.gnupg.  GPG >= 2.1.13 will check for a
239  * socket under (/var)/run/UID/gnupg before ~/.gnupg if no environment
240  * variables are set.
241  *
242  * $GPG_AGENT_INFO takes precedence, if set, otherwise $GNUPGHOME will be
243  * used.  For GPG >= 2.1.13, $GNUPGHOME will be used directly only if it
244  * refers to the canonical home -- ~/.gnupg.  Otherwise, the path specified
245  * by $GNUPGHOME is hashed (SHA1 + z-base-32) and the socket is expected to
246  * be present under (/var)/run/UID/gnupg/d.HASH. This last mechanism is not
247  * yet supported here. */
248 static const char *
find_gpg_agent_socket(apr_pool_t * result_pool,apr_pool_t * scratch_pool)249 find_gpg_agent_socket(apr_pool_t *result_pool, apr_pool_t *scratch_pool)
250 {
251   char *gpg_agent_info = NULL;
252   char *gnupghome = NULL;
253   const char *socket_name = NULL;
254 
255   if ((gpg_agent_info = getenv("GPG_AGENT_INFO")) != NULL)
256     {
257       apr_array_header_t *socket_details;
258 
259       /* For reference GPG_AGENT_INFO consists of 3 : separated fields.
260        * The path to the socket, the pid of the gpg-agent process and
261        * finally the version of the protocol the agent talks. */
262       socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
263                                          scratch_pool);
264       socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
265     }
266   else if ((gnupghome = getenv("GNUPGHOME")) != NULL)
267     {
268       const char *homedir = svn_dirent_canonicalize(gnupghome, scratch_pool);
269       socket_name = svn_dirent_join(homedir, "S.gpg-agent", scratch_pool);
270     }
271   else
272     {
273       int i = 0;
274       const char *maybe_socket[] = {NULL, NULL, NULL, NULL};
275       const char *homedir;
276 
277 #ifdef APR_HAS_USER
278       apr_uid_t uid;
279       apr_gid_t gid;
280 
281       if (apr_uid_current(&uid, &gid, scratch_pool) == APR_SUCCESS)
282         {
283           const char *uidbuf = apr_psprintf(scratch_pool, "%lu",
284                                             (unsigned long)uid);
285           maybe_socket[i++] = svn_dirent_join_many(scratch_pool, "/run/user",
286                                                    uidbuf, "gnupg",
287                                                    "S.gpg-agent",
288                                                    SVN_VA_NULL);
289           maybe_socket[i++] = svn_dirent_join_many(scratch_pool,
290                                                    "/var/run/user",
291                                                    uidbuf, "gnupg",
292                                                    "S.gpg-agent",
293                                                    SVN_VA_NULL);
294         }
295 #endif
296 
297       homedir = svn_user_get_homedir(scratch_pool);
298       if (homedir)
299         maybe_socket[i++] = svn_dirent_join_many(scratch_pool, homedir,
300                                                  ".gnupg", "S.gpg-agent",
301                                                  SVN_VA_NULL);
302 
303       for (i = 0; !socket_name && maybe_socket[i]; i++)
304         {
305           apr_finfo_t finfo;
306           svn_error_t *err = svn_io_stat(&finfo, maybe_socket[i],
307                                          APR_FINFO_TYPE, scratch_pool);
308           if (!err && finfo.filetype == APR_SOCK)
309             socket_name = maybe_socket[i];
310           svn_error_clear(err);
311         }
312     }
313 
314   if (socket_name)
315     socket_name = apr_pstrdup(result_pool, socket_name);
316 
317   return socket_name;
318 }
319 
320 /* Locate a running GPG Agent, and return an open file descriptor
321  * for communication with the agent in *NEW_SD. If no running agent
322  * can be found, set *NEW_SD to -1. */
323 static svn_error_t *
find_running_gpg_agent(int * new_sd,apr_pool_t * pool)324 find_running_gpg_agent(int *new_sd, apr_pool_t *pool)
325 {
326   char *buffer;
327   const char *socket_name = find_gpg_agent_socket(pool, pool);
328   const char *request = NULL;
329   const char *p = NULL;
330   char *ep = NULL;
331   int sd;
332 
333   *new_sd = -1;
334 
335   if (socket_name != NULL)
336     {
337       struct sockaddr_un addr;
338 
339       addr.sun_family = AF_UNIX;
340       strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1);
341       addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
342 
343       sd = socket(AF_UNIX, SOCK_STREAM, 0);
344       if (sd == -1)
345         return SVN_NO_ERROR;
346 
347       if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
348         {
349           close(sd);
350           return SVN_NO_ERROR;
351         }
352     }
353   else
354     return SVN_NO_ERROR;
355 
356   /* Receive the connection status from the gpg-agent daemon. */
357   buffer = apr_palloc(pool, BUFFER_SIZE);
358   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
359     {
360       bye_gpg_agent(sd);
361       return SVN_NO_ERROR;
362     }
363 
364   if (strncmp(buffer, "OK", 2) != 0)
365     {
366       bye_gpg_agent(sd);
367       return SVN_NO_ERROR;
368     }
369 
370   /* The GPG-Agent documentation says:
371    *  "Clients should deny to access an agent with a socket name which does
372    *   not match its own configuration". */
373   request = "GETINFO socket_name\n";
374   if (write(sd, request, strlen(request)) == -1)
375     {
376       bye_gpg_agent(sd);
377       return SVN_NO_ERROR;
378     }
379   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
380     {
381       bye_gpg_agent(sd);
382       return SVN_NO_ERROR;
383     }
384   if (strncmp(buffer, "D", 1) == 0)
385     p = &buffer[2];
386   if (!p)
387     {
388       bye_gpg_agent(sd);
389       return SVN_NO_ERROR;
390     }
391   ep = strchr(p, '\n');
392   if (ep != NULL)
393     *ep = '\0';
394   if (strcmp(socket_name, p) != 0)
395     {
396       bye_gpg_agent(sd);
397       return SVN_NO_ERROR;
398     }
399   /* The agent will terminate its response with "OK". */
400   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
401     {
402       bye_gpg_agent(sd);
403       return SVN_NO_ERROR;
404     }
405   if (strncmp(buffer, "OK", 2) != 0)
406     {
407       bye_gpg_agent(sd);
408       return SVN_NO_ERROR;
409     }
410 
411   *new_sd = sd;
412   return SVN_NO_ERROR;
413 }
414 
415 static svn_boolean_t
send_options(int sd,char * buf,size_t n,apr_pool_t * scratch_pool)416 send_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool)
417 {
418   const char *tty_name;
419   const char *tty_type;
420   const char *lc_ctype;
421   const char *display;
422 
423   /* Send TTY_NAME to the gpg-agent daemon. */
424   tty_name = getenv("GPG_TTY");
425   if (tty_name != NULL)
426     {
427       if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool))
428         return FALSE;
429     }
430 
431   /* Send TTY_TYPE to the gpg-agent daemon. */
432   tty_type = getenv("TERM");
433   if (tty_type != NULL)
434     {
435       if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool))
436         return FALSE;
437     }
438 
439   /* Compute LC_CTYPE. */
440   lc_ctype = getenv("LC_ALL");
441   if (lc_ctype == NULL)
442     lc_ctype = getenv("LC_CTYPE");
443   if (lc_ctype == NULL)
444     lc_ctype = getenv("LANG");
445 
446   /* Send LC_CTYPE to the gpg-agent daemon. */
447   if (lc_ctype != NULL)
448     {
449       if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool))
450         return FALSE;
451     }
452 
453   /* Send DISPLAY to the gpg-agent daemon. */
454   display = getenv("DISPLAY");
455   if (display != NULL)
456     {
457       if (!send_option(sd, buf, n, "display", display, scratch_pool))
458         return FALSE;
459     }
460 
461   return TRUE;
462 }
463 
464 /* Implementation of svn_auth__password_get_t that retrieves the password
465    from gpg-agent */
466 static svn_error_t *
password_get_gpg_agent(svn_boolean_t * done,const char ** password,apr_hash_t * creds,const char * realmstring,const char * username,apr_hash_t * parameters,svn_boolean_t non_interactive,apr_pool_t * pool)467 password_get_gpg_agent(svn_boolean_t *done,
468                        const char **password,
469                        apr_hash_t *creds,
470                        const char *realmstring,
471                        const char *username,
472                        apr_hash_t *parameters,
473                        svn_boolean_t non_interactive,
474                        apr_pool_t *pool)
475 {
476   int sd;
477   char *p = NULL;
478   char *ep = NULL;
479   char *buffer;
480   const char *request = NULL;
481   const char *cache_id = NULL;
482   char *password_prompt;
483   char *realm_prompt;
484   char *error_prompt;
485   int *attempt;
486 
487   *done = FALSE;
488 
489   attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER);
490 
491   SVN_ERR(find_running_gpg_agent(&sd, pool));
492   if (sd == -1)
493     return SVN_NO_ERROR;
494 
495   buffer = apr_palloc(pool, BUFFER_SIZE);
496 
497   if (!send_options(sd, buffer, BUFFER_SIZE, pool))
498     {
499       bye_gpg_agent(sd);
500       return SVN_NO_ERROR;
501     }
502 
503   SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
504 
505   password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
506   realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
507                               realmstring);
508   if (*attempt == 1)
509     /* X means no error to the gpg-agent protocol */
510     error_prompt = apr_pstrdup(pool, "X");
511   else
512     error_prompt = apr_pstrdup(pool, _("Authentication failed"));
513 
514   request = apr_psprintf(pool,
515                          "GET_PASSPHRASE --data %s"
516                          "%s %s %s %s\n",
517                          non_interactive ? "--no-ask " : "",
518                          cache_id,
519                          escape_blanks(error_prompt),
520                          escape_blanks(password_prompt),
521                          escape_blanks(realm_prompt));
522 
523   if (write(sd, request, strlen(request)) == -1)
524     {
525       bye_gpg_agent(sd);
526       return SVN_NO_ERROR;
527     }
528   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
529     {
530       bye_gpg_agent(sd);
531       return SVN_NO_ERROR;
532     }
533 
534   bye_gpg_agent(sd);
535 
536   if (strncmp(buffer, "ERR", 3) == 0)
537     return SVN_NO_ERROR;
538 
539   p = NULL;
540   if (strncmp(buffer, "D", 1) == 0)
541     p = &buffer[2];
542 
543   if (!p)
544     return SVN_NO_ERROR;
545 
546   ep = strchr(p, '\n');
547   if (ep != NULL)
548     *ep = '\0';
549 
550   *password = unescape_assuan(p);
551 
552   *done = TRUE;
553   return SVN_NO_ERROR;
554 }
555 
556 
557 /* Implementation of svn_auth__password_set_t that would store the
558    password in GPG Agent if that's how this particular integration
559    worked.  But it isn't.  GPG Agent stores the password provided by
560    the user via the pinentry program immediately upon its provision
561    (and regardless of its accuracy as passwords go), so we just need
562    to check if a running GPG Agent exists. */
563 static svn_error_t *
password_set_gpg_agent(svn_boolean_t * done,apr_hash_t * creds,const char * realmstring,const char * username,const char * password,apr_hash_t * parameters,svn_boolean_t non_interactive,apr_pool_t * pool)564 password_set_gpg_agent(svn_boolean_t *done,
565                        apr_hash_t *creds,
566                        const char *realmstring,
567                        const char *username,
568                        const char *password,
569                        apr_hash_t *parameters,
570                        svn_boolean_t non_interactive,
571                        apr_pool_t *pool)
572 {
573   int sd;
574 
575   *done = FALSE;
576 
577   SVN_ERR(find_running_gpg_agent(&sd, pool));
578   if (sd == -1)
579     return SVN_NO_ERROR;
580 
581   bye_gpg_agent(sd);
582   *done = TRUE;
583 
584   return SVN_NO_ERROR;
585 }
586 
587 
588 /* An implementation of svn_auth_provider_t::first_credentials() */
589 static svn_error_t *
simple_gpg_agent_first_creds(void ** credentials,void ** iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)590 simple_gpg_agent_first_creds(void **credentials,
591                              void **iter_baton,
592                              void *provider_baton,
593                              apr_hash_t *parameters,
594                              const char *realmstring,
595                              apr_pool_t *pool)
596 {
597   svn_error_t *err;
598   int *attempt = apr_palloc(pool, sizeof(*attempt));
599 
600   *attempt = 1;
601   svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt);
602   err = svn_auth__simple_creds_cache_get(credentials, iter_baton,
603                                          provider_baton, parameters,
604                                          realmstring, password_get_gpg_agent,
605                                          SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
606                                          pool);
607   *iter_baton = attempt;
608 
609   return err;
610 }
611 
612 /* An implementation of svn_auth_provider_t::next_credentials() */
613 static svn_error_t *
simple_gpg_agent_next_creds(void ** credentials,void * iter_baton,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)614 simple_gpg_agent_next_creds(void **credentials,
615                             void *iter_baton,
616                             void *provider_baton,
617                             apr_hash_t *parameters,
618                             const char *realmstring,
619                             apr_pool_t *pool)
620 {
621   int *attempt = (int *)iter_baton;
622   int sd;
623   char *buffer;
624   const char *cache_id = NULL;
625   const char *request = NULL;
626 
627   *credentials = NULL;
628 
629   /* The users previous credentials failed so first remove the cached entry,
630    * before trying to retrieve them again.  Because gpg-agent stores cached
631    * credentials immediately upon retrieving them, this gives us the
632    * opportunity to remove the invalid credentials and prompt the
633    * user again.  While it's possible that server side issues could trigger
634    * this, this cache is ephemeral so at worst we're just speeding up
635    * when the user would need to re-enter their password. */
636 
637   if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE))
638     {
639       /* In this case since we're running non-interactively we do not
640        * want to clear the cache since the user was never prompted by
641        * gpg-agent to set a password. */
642       return SVN_NO_ERROR;
643     }
644 
645   *attempt = *attempt + 1;
646 
647   SVN_ERR(find_running_gpg_agent(&sd, pool));
648   if (sd == -1)
649     return SVN_NO_ERROR;
650 
651   buffer = apr_palloc(pool, BUFFER_SIZE);
652 
653   if (!send_options(sd, buffer, BUFFER_SIZE, pool))
654     {
655       bye_gpg_agent(sd);
656       return SVN_NO_ERROR;
657     }
658 
659   SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
660 
661   request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id);
662 
663   if (write(sd, request, strlen(request)) == -1)
664     {
665       bye_gpg_agent(sd);
666       return SVN_NO_ERROR;
667     }
668 
669   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
670     {
671       bye_gpg_agent(sd);
672       return SVN_NO_ERROR;
673     }
674 
675   bye_gpg_agent(sd);
676 
677   if (strncmp(buffer, "OK\n", 3) != 0)
678     return SVN_NO_ERROR;
679 
680   /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries)
681    * which matches svn command line client's retry_limit as set in
682    * svn_cmdline_create_auth_baton().  It would be nice to have that
683    * limit reflected here but that violates the boundry between the
684    * prompt provider and the cache provider.  gpg-agent is acting as
685    * both here due to the peculiarties of their design so we'll have to
686    * live with this for now.  Note that when these failures get exceeded
687    * it'll eventually fall back on the retry limits of whatever prompt
688    * provider is in effect, so this effectively doubles the limit. */
689   if (*attempt < 4)
690     return svn_auth__simple_creds_cache_get(credentials, &iter_baton,
691                                             provider_baton, parameters,
692                                             realmstring,
693                                             password_get_gpg_agent,
694                                             SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
695                                             pool);
696 
697   return SVN_NO_ERROR;
698 }
699 
700 
701 /* An implementation of svn_auth_provider_t::save_credentials() */
702 static svn_error_t *
simple_gpg_agent_save_creds(svn_boolean_t * saved,void * credentials,void * provider_baton,apr_hash_t * parameters,const char * realmstring,apr_pool_t * pool)703 simple_gpg_agent_save_creds(svn_boolean_t *saved,
704                             void *credentials,
705                             void *provider_baton,
706                             apr_hash_t *parameters,
707                             const char *realmstring,
708                             apr_pool_t *pool)
709 {
710   return svn_auth__simple_creds_cache_set(saved, credentials,
711                                           provider_baton, parameters,
712                                           realmstring, password_set_gpg_agent,
713                                           SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
714                                           pool);
715 }
716 
717 
718 static const svn_auth_provider_t gpg_agent_simple_provider = {
719   SVN_AUTH_CRED_SIMPLE,
720   simple_gpg_agent_first_creds,
721   simple_gpg_agent_next_creds,
722   simple_gpg_agent_save_creds
723 };
724 
725 
726 /* Public API */
727 void
svn_auth__get_gpg_agent_simple_provider(svn_auth_provider_object_t ** provider,apr_pool_t * pool)728 svn_auth__get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
729                                        apr_pool_t *pool)
730 {
731   svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
732 
733   po->vtable = &gpg_agent_simple_provider;
734   *provider = po;
735 }
736 
737 #endif /* SVN_HAVE_GPG_AGENT */
738 #endif /* !WIN32 */
739