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
76#include "private/svn_auth_private.h"
77
78#include "svn_private_config.h"
79
80#ifdef SVN_HAVE_GPG_AGENT
81
82#define BUFFER_SIZE 1024
83
84/* Modify STR in-place such that blanks are escaped as required by the
85 * gpg-agent protocol. Return a pointer to STR. */
86static char *
87escape_blanks(char *str)
88{
89  char *s = str;
90
91  while (*s)
92    {
93      if (*s == ' ')
94        *s = '+';
95      s++;
96    }
97
98  return str;
99}
100
101/* Attempt to read a gpg-agent response message from the socket SD into
102 * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
103 * message could be read that fits into the buffer. Else return FALSE.
104 * If a message could be read it will always be NUL-terminated and the
105 * trailing newline is retained. */
106static svn_boolean_t
107receive_from_gpg_agent(int sd, char *buf, size_t n)
108{
109  int i = 0;
110  size_t recvd;
111  char c;
112
113  /* Clear existing buffer content before reading response. */
114  if (n > 0)
115    *buf = '\0';
116
117  /* Require the message to fit into the buffer and be terminated
118   * with a newline. */
119  while (i < n)
120    {
121      recvd = read(sd, &c, 1);
122      if (recvd == -1)
123        return FALSE;
124      buf[i] = c;
125      i++;
126      if (i < n && c == '\n')
127        {
128          buf[i] = '\0';
129          return TRUE;
130        }
131    }
132
133    return FALSE;
134}
135
136/* Using socket SD, send the option OPTION with the specified VALUE
137 * to the gpg agent. Store the response in BUF, assumed to be N bytes
138 * in size, and evaluate the response. Return TRUE if the agent liked
139 * the smell of the option, if there is such a thing, and doesn't feel
140 * saturated by it. Else return FALSE.
141 * Do temporary allocations in scratch_pool. */
142static svn_boolean_t
143send_option(int sd, char *buf, size_t n, const char *option, const char *value,
144            apr_pool_t *scratch_pool)
145{
146  const char *request;
147
148  request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
149
150  if (write(sd, request, strlen(request)) == -1)
151    return FALSE;
152
153  if (!receive_from_gpg_agent(sd, buf, n))
154    return FALSE;
155
156  return (strncmp(buf, "OK", 2) == 0);
157}
158
159
160/* Locate a running GPG Agent, and return an open file descriptor
161 * for communication with the agent in *NEW_SD. If no running agent
162 * can be found, set *NEW_SD to -1. */
163static svn_error_t *
164find_running_gpg_agent(int *new_sd, apr_pool_t *pool)
165{
166  char *buffer;
167  char *gpg_agent_info = NULL;
168  const char *socket_name = NULL;
169  const char *request = NULL;
170  const char *p = NULL;
171  char *ep = NULL;
172  int sd;
173
174  *new_sd = -1;
175
176  gpg_agent_info = getenv("GPG_AGENT_INFO");
177  if (gpg_agent_info != NULL)
178    {
179      apr_array_header_t *socket_details;
180
181      socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
182                                         pool);
183      socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
184    }
185  else
186    return SVN_NO_ERROR;
187
188  if (socket_name != NULL)
189    {
190      struct sockaddr_un addr;
191
192      addr.sun_family = AF_UNIX;
193      strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1);
194      addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
195
196      sd = socket(AF_UNIX, SOCK_STREAM, 0);
197      if (sd == -1)
198        return SVN_NO_ERROR;
199
200      if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
201        {
202          close(sd);
203          return SVN_NO_ERROR;
204        }
205    }
206  else
207    return SVN_NO_ERROR;
208
209  /* Receive the connection status from the gpg-agent daemon. */
210  buffer = apr_palloc(pool, BUFFER_SIZE);
211  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
212    {
213      close(sd);
214      return SVN_NO_ERROR;
215    }
216
217  if (strncmp(buffer, "OK", 2) != 0)
218    {
219      close(sd);
220      return SVN_NO_ERROR;
221    }
222
223  /* The GPG-Agent documentation says:
224   *  "Clients should deny to access an agent with a socket name which does
225   *   not match its own configuration". */
226  request = "GETINFO socket_name\n";
227  if (write(sd, request, strlen(request)) == -1)
228    {
229      close(sd);
230      return SVN_NO_ERROR;
231    }
232  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
233    {
234      close(sd);
235      return SVN_NO_ERROR;
236    }
237  if (strncmp(buffer, "D", 1) == 0)
238    p = &buffer[2];
239  if (!p)
240    {
241      close(sd);
242      return SVN_NO_ERROR;
243    }
244  ep = strchr(p, '\n');
245  if (ep != NULL)
246    *ep = '\0';
247  if (strcmp(socket_name, p) != 0)
248    {
249      close(sd);
250      return SVN_NO_ERROR;
251    }
252  /* The agent will terminate its response with "OK". */
253  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
254    {
255      close(sd);
256      return SVN_NO_ERROR;
257    }
258  if (strncmp(buffer, "OK", 2) != 0)
259    {
260      close(sd);
261      return SVN_NO_ERROR;
262    }
263
264  *new_sd = sd;
265  return SVN_NO_ERROR;
266}
267
268/* Implementation of svn_auth__password_get_t that retrieves the password
269   from gpg-agent */
270static svn_error_t *
271password_get_gpg_agent(svn_boolean_t *done,
272                       const char **password,
273                       apr_hash_t *creds,
274                       const char *realmstring,
275                       const char *username,
276                       apr_hash_t *parameters,
277                       svn_boolean_t non_interactive,
278                       apr_pool_t *pool)
279{
280  int sd;
281  const char *p = NULL;
282  char *ep = NULL;
283  char *buffer;
284  const char *request = NULL;
285  const char *cache_id = NULL;
286  const char *tty_name;
287  const char *tty_type;
288  const char *lc_ctype;
289  const char *display;
290  svn_checksum_t *digest = NULL;
291  char *password_prompt;
292  char *realm_prompt;
293
294  *done = FALSE;
295
296  SVN_ERR(find_running_gpg_agent(&sd, pool));
297  if (sd == -1)
298    return SVN_NO_ERROR;
299
300  buffer = apr_palloc(pool, BUFFER_SIZE);
301
302  /* Send TTY_NAME to the gpg-agent daemon. */
303  tty_name = getenv("GPG_TTY");
304  if (tty_name != NULL)
305    {
306      if (!send_option(sd, buffer, BUFFER_SIZE, "ttyname", tty_name, pool))
307        {
308          close(sd);
309          return SVN_NO_ERROR;
310        }
311    }
312
313  /* Send TTY_TYPE to the gpg-agent daemon. */
314  tty_type = getenv("TERM");
315  if (tty_type != NULL)
316    {
317      if (!send_option(sd, buffer, BUFFER_SIZE, "ttytype", tty_type, pool))
318        {
319          close(sd);
320          return SVN_NO_ERROR;
321        }
322    }
323
324  /* Compute LC_CTYPE. */
325  lc_ctype = getenv("LC_ALL");
326  if (lc_ctype == NULL)
327    lc_ctype = getenv("LC_CTYPE");
328  if (lc_ctype == NULL)
329    lc_ctype = getenv("LANG");
330
331  /* Send LC_CTYPE to the gpg-agent daemon. */
332  if (lc_ctype != NULL)
333    {
334      if (!send_option(sd, buffer, BUFFER_SIZE, "lc-ctype", lc_ctype, pool))
335        {
336          close(sd);
337          return SVN_NO_ERROR;
338        }
339    }
340
341  /* Send DISPLAY to the gpg-agent daemon. */
342  display = getenv("DISPLAY");
343  if (display != NULL)
344    {
345      if (!send_option(sd, buffer, BUFFER_SIZE, "display", display, pool))
346        {
347          close(sd);
348          return SVN_NO_ERROR;
349        }
350    }
351
352  /* Create the CACHE_ID which will be generated based on REALMSTRING similar
353     to other password caching mechanisms. */
354  SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
355                       strlen(realmstring), pool));
356  cache_id = svn_checksum_to_cstring(digest, pool);
357
358  password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
359  realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
360                              realmstring);
361  request = apr_psprintf(pool,
362                         "GET_PASSPHRASE --data %s--repeat=1 "
363                         "%s X %s %s\n",
364                         non_interactive ? "--no-ask " : "",
365                         cache_id,
366                         escape_blanks(password_prompt),
367                         escape_blanks(realm_prompt));
368
369  if (write(sd, request, strlen(request)) == -1)
370    {
371      close(sd);
372      return SVN_NO_ERROR;
373    }
374  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
375    {
376      close(sd);
377      return SVN_NO_ERROR;
378    }
379
380  close(sd);
381
382  if (strncmp(buffer, "ERR", 3) == 0)
383    return SVN_NO_ERROR;
384
385  p = NULL;
386  if (strncmp(buffer, "D", 1) == 0)
387    p = &buffer[2];
388
389  if (!p)
390    return SVN_NO_ERROR;
391
392  ep = strchr(p, '\n');
393  if (ep != NULL)
394    *ep = '\0';
395
396  *password = p;
397
398  *done = TRUE;
399  return SVN_NO_ERROR;
400}
401
402
403/* Implementation of svn_auth__password_set_t that would store the
404   password in GPG Agent if that's how this particular integration
405   worked.  But it isn't.  GPG Agent stores the password provided by
406   the user via the pinentry program immediately upon its provision
407   (and regardless of its accuracy as passwords go), so we just need
408   to check if a running GPG Agent exists. */
409static svn_error_t *
410password_set_gpg_agent(svn_boolean_t *done,
411                       apr_hash_t *creds,
412                       const char *realmstring,
413                       const char *username,
414                       const char *password,
415                       apr_hash_t *parameters,
416                       svn_boolean_t non_interactive,
417                       apr_pool_t *pool)
418{
419  int sd;
420
421  *done = FALSE;
422
423  SVN_ERR(find_running_gpg_agent(&sd, pool));
424  if (sd == -1)
425    return SVN_NO_ERROR;
426
427  close(sd);
428  *done = TRUE;
429
430  return SVN_NO_ERROR;
431}
432
433
434/* An implementation of svn_auth_provider_t::first_credentials() */
435static svn_error_t *
436simple_gpg_agent_first_creds(void **credentials,
437                             void **iter_baton,
438                             void *provider_baton,
439                             apr_hash_t *parameters,
440                             const char *realmstring,
441                             apr_pool_t *pool)
442{
443  return svn_auth__simple_creds_cache_get(credentials, iter_baton,
444                                          provider_baton, parameters,
445                                          realmstring, password_get_gpg_agent,
446                                          SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
447                                          pool);
448}
449
450
451/* An implementation of svn_auth_provider_t::save_credentials() */
452static svn_error_t *
453simple_gpg_agent_save_creds(svn_boolean_t *saved,
454                            void *credentials,
455                            void *provider_baton,
456                            apr_hash_t *parameters,
457                            const char *realmstring,
458                            apr_pool_t *pool)
459{
460  return svn_auth__simple_creds_cache_set(saved, credentials,
461                                          provider_baton, parameters,
462                                          realmstring, password_set_gpg_agent,
463                                          SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
464                                          pool);
465}
466
467
468static const svn_auth_provider_t gpg_agent_simple_provider = {
469  SVN_AUTH_CRED_SIMPLE,
470  simple_gpg_agent_first_creds,
471  NULL,
472  simple_gpg_agent_save_creds
473};
474
475
476/* Public API */
477void
478svn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
479                                       apr_pool_t *pool)
480{
481  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
482
483  po->vtable = &gpg_agent_simple_provider;
484  *provider = po;
485}
486
487#endif /* SVN_HAVE_GPG_AGENT */
488#endif /* !WIN32 */
489