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