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 "svn_auth.h"
69#include "svn_config.h"
70#include "svn_error.h"
71#include "svn_pools.h"
72#include "svn_cmdline.h"
73#include "svn_checksum.h"
74#include "svn_string.h"
75#include "svn_hash.h"
76#include "svn_user.h"
77#include "svn_dirent_uri.h"
78
79#include "auth.h"
80#include "private/svn_auth_private.h"
81
82#include "svn_private_config.h"
83
84#ifdef SVN_HAVE_GPG_AGENT
85
86#define BUFFER_SIZE 1024
87#define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt"
88
89/* Modify STR in-place such that blanks are escaped as required by the
90 * gpg-agent protocol. Return a pointer to STR. */
91static char *
92escape_blanks(char *str)
93{
94  char *s = str;
95
96  while (*s)
97    {
98      if (*s == ' ')
99        *s = '+';
100      s++;
101    }
102
103  return str;
104}
105
106#define is_hex(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F'))
107#define hex_to_int(c) ((c) < '9' ? (c) - '0' : (c) - 'A' + 10)
108
109/* Modify STR in-place.  '%', CR and LF are always percent escaped,
110   other characters may be percent escaped, always using uppercase
111   hex, see https://www.gnupg.org/documentation/manuals/assuan.pdf */
112static char *
113unescape_assuan(char *str)
114{
115  char *s = str;
116
117  while (s[0])
118    {
119      if (s[0] == '%' && is_hex(s[1]) && is_hex(s[2]))
120        {
121          char *s2 = s;
122          char val = hex_to_int(s[1]) * 16 + hex_to_int(s[2]);
123
124          s2[0] = val;
125          ++s2;
126
127          while (s2[2])
128            {
129              s2[0] = s2[2];
130              ++s2;
131            }
132          s2[0] = '\0';
133        }
134      ++s;
135    }
136
137  return str;
138}
139
140/* Generate the string CACHE_ID_P based on the REALMSTRING allocated in
141 * RESULT_POOL using SCRATCH_POOL for temporary allocations.  This is similar
142 * to other password caching mechanisms. */
143static svn_error_t *
144get_cache_id(const char **cache_id_p, const char *realmstring,
145             apr_pool_t *result_pool, apr_pool_t *scratch_pool)
146{
147  const char *cache_id = NULL;
148  svn_checksum_t *digest = NULL;
149
150  SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
151                       strlen(realmstring), scratch_pool));
152  cache_id = svn_checksum_to_cstring(digest, result_pool);
153  *cache_id_p = cache_id;
154
155  return SVN_NO_ERROR;
156}
157
158/* Attempt to read a gpg-agent response message from the socket SD into
159 * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
160 * message could be read that fits into the buffer. Else return FALSE.
161 * If a message could be read it will always be NUL-terminated and the
162 * trailing newline is retained. */
163static svn_boolean_t
164receive_from_gpg_agent(int sd, char *buf, size_t n)
165{
166  int i = 0;
167  size_t recvd;
168  char c;
169
170  /* Clear existing buffer content before reading response. */
171  if (n > 0)
172    *buf = '\0';
173
174  /* Require the message to fit into the buffer and be terminated
175   * with a newline. */
176  while (i < n)
177    {
178      recvd = read(sd, &c, 1);
179      if (recvd == -1)
180        return FALSE;
181      buf[i] = c;
182      i++;
183      if (i < n && c == '\n')
184        {
185          buf[i] = '\0';
186          return TRUE;
187        }
188    }
189
190    return FALSE;
191}
192
193/* Using socket SD, send the option OPTION with the specified VALUE
194 * to the gpg agent. Store the response in BUF, assumed to be N bytes
195 * in size, and evaluate the response. Return TRUE if the agent liked
196 * the smell of the option, if there is such a thing, and doesn't feel
197 * saturated by it. Else return FALSE.
198 * Do temporary allocations in scratch_pool. */
199static svn_boolean_t
200send_option(int sd, char *buf, size_t n, const char *option, const char *value,
201            apr_pool_t *scratch_pool)
202{
203  const char *request;
204
205  request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
206
207  if (write(sd, request, strlen(request)) == -1)
208    return FALSE;
209
210  if (!receive_from_gpg_agent(sd, buf, n))
211    return FALSE;
212
213  return (strncmp(buf, "OK", 2) == 0);
214}
215
216/* Send the BYE command and disconnect from the gpg-agent.  Doing this avoids
217 * gpg-agent emitting a "Connection reset by peer" log message with some
218 * versions of gpg-agent. */
219static void
220bye_gpg_agent(int sd)
221{
222  /* don't bother to check the result of the write, it either worked or it
223   * didn't, but either way we're closing. */
224  write(sd, "BYE\n", 4);
225  close(sd);
226}
227
228/* Locate a running GPG Agent, and return an open file descriptor
229 * for communication with the agent in *NEW_SD. If no running agent
230 * can be found, set *NEW_SD to -1. */
231static svn_error_t *
232find_running_gpg_agent(int *new_sd, apr_pool_t *pool)
233{
234  char *buffer;
235  char *gpg_agent_info = NULL;
236  char *gnupghome = NULL;
237  const char *socket_name = NULL;
238  const char *request = NULL;
239  const char *p = NULL;
240  char *ep = NULL;
241  int sd;
242
243  *new_sd = -1;
244
245  /* This implements the method of finding the socket as described in
246   * the gpg-agent man page under the --use-standard-socket option.
247   * The manage page says the standard socket is "named 'S.gpg-agent' located
248   * in the home directory."  GPG's home directory is either the directory
249   * specified by $GNUPGHOME or ~/.gnupg. */
250  gpg_agent_info = getenv("GPG_AGENT_INFO");
251  if (gpg_agent_info != NULL)
252    {
253      apr_array_header_t *socket_details;
254
255      /* For reference GPG_AGENT_INFO consists of 3 : separated fields.
256       * The path to the socket, the pid of the gpg-agent process and
257       * finally the version of the protocol the agent talks. */
258      socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
259                                         pool);
260      socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
261    }
262  else if ((gnupghome = getenv("GNUPGHOME")) != NULL)
263    {
264      const char *homedir = svn_dirent_canonicalize(gnupghome, pool);
265      socket_name = svn_dirent_join(homedir, "S.gpg-agent", pool);
266    }
267  else
268    {
269      const char *homedir = svn_user_get_homedir(pool);
270
271      if (!homedir)
272        return SVN_NO_ERROR;
273
274      homedir = svn_dirent_canonicalize(homedir, pool);
275      socket_name = svn_dirent_join_many(pool, homedir, ".gnupg",
276                                         "S.gpg-agent", SVN_VA_NULL);
277    }
278
279  if (socket_name != NULL)
280    {
281      struct sockaddr_un addr;
282
283      addr.sun_family = AF_UNIX;
284      strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1);
285      addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
286
287      sd = socket(AF_UNIX, SOCK_STREAM, 0);
288      if (sd == -1)
289        return SVN_NO_ERROR;
290
291      if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
292        {
293          close(sd);
294          return SVN_NO_ERROR;
295        }
296    }
297  else
298    return SVN_NO_ERROR;
299
300  /* Receive the connection status from the gpg-agent daemon. */
301  buffer = apr_palloc(pool, BUFFER_SIZE);
302  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
303    {
304      bye_gpg_agent(sd);
305      return SVN_NO_ERROR;
306    }
307
308  if (strncmp(buffer, "OK", 2) != 0)
309    {
310      bye_gpg_agent(sd);
311      return SVN_NO_ERROR;
312    }
313
314  /* The GPG-Agent documentation says:
315   *  "Clients should deny to access an agent with a socket name which does
316   *   not match its own configuration". */
317  request = "GETINFO socket_name\n";
318  if (write(sd, request, strlen(request)) == -1)
319    {
320      bye_gpg_agent(sd);
321      return SVN_NO_ERROR;
322    }
323  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
324    {
325      bye_gpg_agent(sd);
326      return SVN_NO_ERROR;
327    }
328  if (strncmp(buffer, "D", 1) == 0)
329    p = &buffer[2];
330  if (!p)
331    {
332      bye_gpg_agent(sd);
333      return SVN_NO_ERROR;
334    }
335  ep = strchr(p, '\n');
336  if (ep != NULL)
337    *ep = '\0';
338  if (strcmp(socket_name, p) != 0)
339    {
340      bye_gpg_agent(sd);
341      return SVN_NO_ERROR;
342    }
343  /* The agent will terminate its response with "OK". */
344  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
345    {
346      bye_gpg_agent(sd);
347      return SVN_NO_ERROR;
348    }
349  if (strncmp(buffer, "OK", 2) != 0)
350    {
351      bye_gpg_agent(sd);
352      return SVN_NO_ERROR;
353    }
354
355  *new_sd = sd;
356  return SVN_NO_ERROR;
357}
358
359static svn_boolean_t
360send_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool)
361{
362  const char *tty_name;
363  const char *tty_type;
364  const char *lc_ctype;
365  const char *display;
366
367  /* Send TTY_NAME to the gpg-agent daemon. */
368  tty_name = getenv("GPG_TTY");
369  if (tty_name != NULL)
370    {
371      if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool))
372        return FALSE;
373    }
374
375  /* Send TTY_TYPE to the gpg-agent daemon. */
376  tty_type = getenv("TERM");
377  if (tty_type != NULL)
378    {
379      if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool))
380        return FALSE;
381    }
382
383  /* Compute LC_CTYPE. */
384  lc_ctype = getenv("LC_ALL");
385  if (lc_ctype == NULL)
386    lc_ctype = getenv("LC_CTYPE");
387  if (lc_ctype == NULL)
388    lc_ctype = getenv("LANG");
389
390  /* Send LC_CTYPE to the gpg-agent daemon. */
391  if (lc_ctype != NULL)
392    {
393      if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool))
394        return FALSE;
395    }
396
397  /* Send DISPLAY to the gpg-agent daemon. */
398  display = getenv("DISPLAY");
399  if (display != NULL)
400    {
401      if (!send_option(sd, buf, n, "display", display, scratch_pool))
402        return FALSE;
403    }
404
405  return TRUE;
406}
407
408/* Implementation of svn_auth__password_get_t that retrieves the password
409   from gpg-agent */
410static svn_error_t *
411password_get_gpg_agent(svn_boolean_t *done,
412                       const char **password,
413                       apr_hash_t *creds,
414                       const char *realmstring,
415                       const char *username,
416                       apr_hash_t *parameters,
417                       svn_boolean_t non_interactive,
418                       apr_pool_t *pool)
419{
420  int sd;
421  char *p = NULL;
422  char *ep = NULL;
423  char *buffer;
424  const char *request = NULL;
425  const char *cache_id = NULL;
426  char *password_prompt;
427  char *realm_prompt;
428  char *error_prompt;
429  int *attempt;
430
431  *done = FALSE;
432
433  attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER);
434
435  SVN_ERR(find_running_gpg_agent(&sd, pool));
436  if (sd == -1)
437    return SVN_NO_ERROR;
438
439  buffer = apr_palloc(pool, BUFFER_SIZE);
440
441  if (!send_options(sd, buffer, BUFFER_SIZE, pool))
442    {
443      bye_gpg_agent(sd);
444      return SVN_NO_ERROR;
445    }
446
447  SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
448
449  password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
450  realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
451                              realmstring);
452  if (*attempt == 1)
453    /* X means no error to the gpg-agent protocol */
454    error_prompt = apr_pstrdup(pool, "X");
455  else
456    error_prompt = apr_pstrdup(pool, _("Authentication failed"));
457
458  request = apr_psprintf(pool,
459                         "GET_PASSPHRASE --data %s"
460                         "%s %s %s %s\n",
461                         non_interactive ? "--no-ask " : "",
462                         cache_id,
463                         escape_blanks(error_prompt),
464                         escape_blanks(password_prompt),
465                         escape_blanks(realm_prompt));
466
467  if (write(sd, request, strlen(request)) == -1)
468    {
469      bye_gpg_agent(sd);
470      return SVN_NO_ERROR;
471    }
472  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
473    {
474      bye_gpg_agent(sd);
475      return SVN_NO_ERROR;
476    }
477
478  bye_gpg_agent(sd);
479
480  if (strncmp(buffer, "ERR", 3) == 0)
481    return SVN_NO_ERROR;
482
483  p = NULL;
484  if (strncmp(buffer, "D", 1) == 0)
485    p = &buffer[2];
486
487  if (!p)
488    return SVN_NO_ERROR;
489
490  ep = strchr(p, '\n');
491  if (ep != NULL)
492    *ep = '\0';
493
494  *password = unescape_assuan(p);
495
496  *done = TRUE;
497  return SVN_NO_ERROR;
498}
499
500
501/* Implementation of svn_auth__password_set_t that would store the
502   password in GPG Agent if that's how this particular integration
503   worked.  But it isn't.  GPG Agent stores the password provided by
504   the user via the pinentry program immediately upon its provision
505   (and regardless of its accuracy as passwords go), so we just need
506   to check if a running GPG Agent exists. */
507static svn_error_t *
508password_set_gpg_agent(svn_boolean_t *done,
509                       apr_hash_t *creds,
510                       const char *realmstring,
511                       const char *username,
512                       const char *password,
513                       apr_hash_t *parameters,
514                       svn_boolean_t non_interactive,
515                       apr_pool_t *pool)
516{
517  int sd;
518
519  *done = FALSE;
520
521  SVN_ERR(find_running_gpg_agent(&sd, pool));
522  if (sd == -1)
523    return SVN_NO_ERROR;
524
525  bye_gpg_agent(sd);
526  *done = TRUE;
527
528  return SVN_NO_ERROR;
529}
530
531
532/* An implementation of svn_auth_provider_t::first_credentials() */
533static svn_error_t *
534simple_gpg_agent_first_creds(void **credentials,
535                             void **iter_baton,
536                             void *provider_baton,
537                             apr_hash_t *parameters,
538                             const char *realmstring,
539                             apr_pool_t *pool)
540{
541  svn_error_t *err;
542  int *attempt = apr_palloc(pool, sizeof(*attempt));
543
544  *attempt = 1;
545  svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt);
546  err = svn_auth__simple_creds_cache_get(credentials, iter_baton,
547                                         provider_baton, parameters,
548                                         realmstring, password_get_gpg_agent,
549                                         SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
550                                         pool);
551  *iter_baton = attempt;
552
553  return err;
554}
555
556/* An implementation of svn_auth_provider_t::next_credentials() */
557static svn_error_t *
558simple_gpg_agent_next_creds(void **credentials,
559                            void *iter_baton,
560                            void *provider_baton,
561                            apr_hash_t *parameters,
562                            const char *realmstring,
563                            apr_pool_t *pool)
564{
565  int *attempt = (int *)iter_baton;
566  int sd;
567  char *buffer;
568  const char *cache_id = NULL;
569  const char *request = NULL;
570
571  *credentials = NULL;
572
573  /* The users previous credentials failed so first remove the cached entry,
574   * before trying to retrieve them again.  Because gpg-agent stores cached
575   * credentials immediately upon retrieving them, this gives us the
576   * opportunity to remove the invalid credentials and prompt the
577   * user again.  While it's possible that server side issues could trigger
578   * this, this cache is ephemeral so at worst we're just speeding up
579   * when the user would need to re-enter their password. */
580
581  if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE))
582    {
583      /* In this case since we're running non-interactively we do not
584       * want to clear the cache since the user was never prompted by
585       * gpg-agent to set a password. */
586      return SVN_NO_ERROR;
587    }
588
589  *attempt = *attempt + 1;
590
591  SVN_ERR(find_running_gpg_agent(&sd, pool));
592  if (sd == -1)
593    return SVN_NO_ERROR;
594
595  buffer = apr_palloc(pool, BUFFER_SIZE);
596
597  if (!send_options(sd, buffer, BUFFER_SIZE, pool))
598    {
599      bye_gpg_agent(sd);
600      return SVN_NO_ERROR;
601    }
602
603  SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
604
605  request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id);
606
607  if (write(sd, request, strlen(request)) == -1)
608    {
609      bye_gpg_agent(sd);
610      return SVN_NO_ERROR;
611    }
612
613  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
614    {
615      bye_gpg_agent(sd);
616      return SVN_NO_ERROR;
617    }
618
619  bye_gpg_agent(sd);
620
621  if (strncmp(buffer, "OK\n", 3) != 0)
622    return SVN_NO_ERROR;
623
624  /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries)
625   * which matches svn command line client's retry_limit as set in
626   * svn_cmdline_create_auth_baton().  It would be nice to have that
627   * limit reflected here but that violates the boundry between the
628   * prompt provider and the cache provider.  gpg-agent is acting as
629   * both here due to the peculiarties of their design so we'll have to
630   * live with this for now.  Note that when these failures get exceeded
631   * it'll eventually fall back on the retry limits of whatever prompt
632   * provider is in effect, so this effectively doubles the limit. */
633  if (*attempt < 4)
634    return svn_auth__simple_creds_cache_get(credentials, &iter_baton,
635                                            provider_baton, parameters,
636                                            realmstring,
637                                            password_get_gpg_agent,
638                                            SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
639                                            pool);
640
641  return SVN_NO_ERROR;
642}
643
644
645/* An implementation of svn_auth_provider_t::save_credentials() */
646static svn_error_t *
647simple_gpg_agent_save_creds(svn_boolean_t *saved,
648                            void *credentials,
649                            void *provider_baton,
650                            apr_hash_t *parameters,
651                            const char *realmstring,
652                            apr_pool_t *pool)
653{
654  return svn_auth__simple_creds_cache_set(saved, credentials,
655                                          provider_baton, parameters,
656                                          realmstring, password_set_gpg_agent,
657                                          SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
658                                          pool);
659}
660
661
662static const svn_auth_provider_t gpg_agent_simple_provider = {
663  SVN_AUTH_CRED_SIMPLE,
664  simple_gpg_agent_first_creds,
665  simple_gpg_agent_next_creds,
666  simple_gpg_agent_save_creds
667};
668
669
670/* Public API */
671void
672svn_auth__get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
673                                       apr_pool_t *pool)
674{
675  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
676
677  po->vtable = &gpg_agent_simple_provider;
678  *provider = po;
679}
680
681#endif /* SVN_HAVE_GPG_AGENT */
682#endif /* !WIN32 */
683