]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/subversion/subversion/libsvn_subr/gpg_agent.c
Update svn-1.9.7 to 1.10.0.
[FreeBSD/FreeBSD.git] / contrib / subversion / subversion / libsvn_subr / gpg_agent.c
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 \f
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 *
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 *
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 *
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
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
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
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 *
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 *
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
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 *
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 *
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 *
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 *
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 *
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
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 */