1251881Speter/*
2251881Speter * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_*
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter/* This auth provider stores a plaintext password in memory managed by
27251881Speter * a running gpg-agent. In contrast to other password store providers
28251881Speter * it does not save the password to disk.
29251881Speter *
30251881Speter * Prompting is performed by the gpg-agent using a "pinentry" program
31251881Speter * which needs to be installed separately. There are several pinentry
32251881Speter * implementations with different front-ends (e.g. qt, gtk, ncurses).
33251881Speter *
34251881Speter * The gpg-agent will let the password time out after a while,
35251881Speter * or immediately when it receives the SIGHUP signal.
36251881Speter * When the password has timed out it will automatically prompt the
37251881Speter * user for the password again. This is transparent to Subversion.
38251881Speter *
39251881Speter * SECURITY CONSIDERATIONS:
40251881Speter *
41251881Speter * Communication to the agent happens over a UNIX socket, which is located
42251881Speter * in a directory which only the user running Subversion can access.
43251881Speter * However, any program the user runs could access this socket and get
44251881Speter * the Subversion password if the program knows the "cache ID" Subversion
45251881Speter * uses for the password.
46251881Speter * The cache ID is very easy to obtain for programs running as the same user.
47251881Speter * Subversion uses the MD5 of the realmstring as cache ID, and these checksums
48251881Speter * are also used as filenames within ~/.subversion/auth/svn.simple.
49251881Speter * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for
50251881Speter * permission if another program attempts to access the password.
51251881Speter *
52251881Speter * Therefore, while the gpg-agent is running and has the password cached,
53251881Speter * this provider is no more secure than a file storing the password in
54251881Speter * plaintext.
55251881Speter */
56251881Speter
57251881Speter
58251881Speter/*** Includes. ***/
59251881Speter
60251881Speter#ifndef WIN32
61251881Speter
62251881Speter#include <unistd.h>
63251881Speter
64251881Speter#include <sys/socket.h>
65251881Speter#include <sys/un.h>
66251881Speter
67251881Speter#include <apr_pools.h>
68251881Speter#include "svn_auth.h"
69251881Speter#include "svn_config.h"
70251881Speter#include "svn_error.h"
71251881Speter#include "svn_pools.h"
72251881Speter#include "svn_cmdline.h"
73251881Speter#include "svn_checksum.h"
74251881Speter#include "svn_string.h"
75251881Speter
76251881Speter#include "private/svn_auth_private.h"
77251881Speter
78251881Speter#include "svn_private_config.h"
79251881Speter
80251881Speter#ifdef SVN_HAVE_GPG_AGENT
81251881Speter
82251881Speter#define BUFFER_SIZE 1024
83251881Speter
84251881Speter/* Modify STR in-place such that blanks are escaped as required by the
85251881Speter * gpg-agent protocol. Return a pointer to STR. */
86251881Speterstatic char *
87251881Speterescape_blanks(char *str)
88251881Speter{
89251881Speter  char *s = str;
90251881Speter
91251881Speter  while (*s)
92251881Speter    {
93251881Speter      if (*s == ' ')
94251881Speter        *s = '+';
95251881Speter      s++;
96251881Speter    }
97251881Speter
98251881Speter  return str;
99251881Speter}
100251881Speter
101251881Speter/* Attempt to read a gpg-agent response message from the socket SD into
102251881Speter * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
103251881Speter * message could be read that fits into the buffer. Else return FALSE.
104251881Speter * If a message could be read it will always be NUL-terminated and the
105251881Speter * trailing newline is retained. */
106251881Speterstatic svn_boolean_t
107251881Speterreceive_from_gpg_agent(int sd, char *buf, size_t n)
108251881Speter{
109251881Speter  int i = 0;
110251881Speter  size_t recvd;
111251881Speter  char c;
112251881Speter
113251881Speter  /* Clear existing buffer content before reading response. */
114251881Speter  if (n > 0)
115251881Speter    *buf = '\0';
116251881Speter
117251881Speter  /* Require the message to fit into the buffer and be terminated
118251881Speter   * with a newline. */
119251881Speter  while (i < n)
120251881Speter    {
121251881Speter      recvd = read(sd, &c, 1);
122251881Speter      if (recvd == -1)
123251881Speter        return FALSE;
124251881Speter      buf[i] = c;
125251881Speter      i++;
126251881Speter      if (i < n && c == '\n')
127251881Speter        {
128251881Speter          buf[i] = '\0';
129251881Speter          return TRUE;
130251881Speter        }
131251881Speter    }
132251881Speter
133251881Speter    return FALSE;
134251881Speter}
135251881Speter
136251881Speter/* Using socket SD, send the option OPTION with the specified VALUE
137251881Speter * to the gpg agent. Store the response in BUF, assumed to be N bytes
138251881Speter * in size, and evaluate the response. Return TRUE if the agent liked
139251881Speter * the smell of the option, if there is such a thing, and doesn't feel
140251881Speter * saturated by it. Else return FALSE.
141251881Speter * Do temporary allocations in scratch_pool. */
142251881Speterstatic svn_boolean_t
143251881Spetersend_option(int sd, char *buf, size_t n, const char *option, const char *value,
144251881Speter            apr_pool_t *scratch_pool)
145251881Speter{
146251881Speter  const char *request;
147251881Speter
148251881Speter  request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
149251881Speter
150251881Speter  if (write(sd, request, strlen(request)) == -1)
151251881Speter    return FALSE;
152251881Speter
153251881Speter  if (!receive_from_gpg_agent(sd, buf, n))
154251881Speter    return FALSE;
155251881Speter
156251881Speter  return (strncmp(buf, "OK", 2) == 0);
157251881Speter}
158251881Speter
159253734Speter
160253734Speter/* Locate a running GPG Agent, and return an open file descriptor
161253734Speter * for communication with the agent in *NEW_SD. If no running agent
162253734Speter * can be found, set *NEW_SD to -1. */
163251881Speterstatic svn_error_t *
164253734Speterfind_running_gpg_agent(int *new_sd, apr_pool_t *pool)
165251881Speter{
166253734Speter  char *buffer;
167251881Speter  char *gpg_agent_info = NULL;
168253734Speter  const char *socket_name = NULL;
169253734Speter  const char *request = NULL;
170251881Speter  const char *p = NULL;
171251881Speter  char *ep = NULL;
172253734Speter  int sd;
173251881Speter
174253734Speter  *new_sd = -1;
175251881Speter
176251881Speter  gpg_agent_info = getenv("GPG_AGENT_INFO");
177251881Speter  if (gpg_agent_info != NULL)
178251881Speter    {
179253734Speter      apr_array_header_t *socket_details;
180253734Speter
181251881Speter      socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
182251881Speter                                         pool);
183251881Speter      socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
184251881Speter    }
185251881Speter  else
186251881Speter    return SVN_NO_ERROR;
187251881Speter
188251881Speter  if (socket_name != NULL)
189251881Speter    {
190253734Speter      struct sockaddr_un addr;
191253734Speter
192251881Speter      addr.sun_family = AF_UNIX;
193251881Speter      strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1);
194251881Speter      addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
195251881Speter
196251881Speter      sd = socket(AF_UNIX, SOCK_STREAM, 0);
197251881Speter      if (sd == -1)
198251881Speter        return SVN_NO_ERROR;
199251881Speter
200251881Speter      if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
201251881Speter        {
202251881Speter          close(sd);
203251881Speter          return SVN_NO_ERROR;
204251881Speter        }
205251881Speter    }
206251881Speter  else
207251881Speter    return SVN_NO_ERROR;
208251881Speter
209251881Speter  /* Receive the connection status from the gpg-agent daemon. */
210251881Speter  buffer = apr_palloc(pool, BUFFER_SIZE);
211251881Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
212251881Speter    {
213251881Speter      close(sd);
214251881Speter      return SVN_NO_ERROR;
215251881Speter    }
216251881Speter
217251881Speter  if (strncmp(buffer, "OK", 2) != 0)
218251881Speter    {
219251881Speter      close(sd);
220251881Speter      return SVN_NO_ERROR;
221251881Speter    }
222251881Speter
223251881Speter  /* The GPG-Agent documentation says:
224251881Speter   *  "Clients should deny to access an agent with a socket name which does
225251881Speter   *   not match its own configuration". */
226251881Speter  request = "GETINFO socket_name\n";
227251881Speter  if (write(sd, request, strlen(request)) == -1)
228251881Speter    {
229251881Speter      close(sd);
230251881Speter      return SVN_NO_ERROR;
231251881Speter    }
232251881Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
233251881Speter    {
234251881Speter      close(sd);
235251881Speter      return SVN_NO_ERROR;
236251881Speter    }
237251881Speter  if (strncmp(buffer, "D", 1) == 0)
238251881Speter    p = &buffer[2];
239251881Speter  if (!p)
240251881Speter    {
241251881Speter      close(sd);
242251881Speter      return SVN_NO_ERROR;
243251881Speter    }
244251881Speter  ep = strchr(p, '\n');
245251881Speter  if (ep != NULL)
246251881Speter    *ep = '\0';
247251881Speter  if (strcmp(socket_name, p) != 0)
248251881Speter    {
249251881Speter      close(sd);
250251881Speter      return SVN_NO_ERROR;
251251881Speter    }
252251881Speter  /* The agent will terminate its response with "OK". */
253251881Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
254251881Speter    {
255251881Speter      close(sd);
256251881Speter      return SVN_NO_ERROR;
257251881Speter    }
258251881Speter  if (strncmp(buffer, "OK", 2) != 0)
259251881Speter    {
260251881Speter      close(sd);
261251881Speter      return SVN_NO_ERROR;
262251881Speter    }
263251881Speter
264253734Speter  *new_sd = sd;
265253734Speter  return SVN_NO_ERROR;
266253734Speter}
267253734Speter
268253734Speter/* Implementation of svn_auth__password_get_t that retrieves the password
269253734Speter   from gpg-agent */
270253734Speterstatic svn_error_t *
271253734Speterpassword_get_gpg_agent(svn_boolean_t *done,
272253734Speter                       const char **password,
273253734Speter                       apr_hash_t *creds,
274253734Speter                       const char *realmstring,
275253734Speter                       const char *username,
276253734Speter                       apr_hash_t *parameters,
277253734Speter                       svn_boolean_t non_interactive,
278253734Speter                       apr_pool_t *pool)
279253734Speter{
280253734Speter  int sd;
281253734Speter  const char *p = NULL;
282253734Speter  char *ep = NULL;
283253734Speter  char *buffer;
284253734Speter  const char *request = NULL;
285253734Speter  const char *cache_id = NULL;
286253734Speter  const char *tty_name;
287253734Speter  const char *tty_type;
288253734Speter  const char *lc_ctype;
289253734Speter  const char *display;
290253734Speter  svn_checksum_t *digest = NULL;
291253734Speter  char *password_prompt;
292253734Speter  char *realm_prompt;
293253734Speter
294253734Speter  *done = FALSE;
295253734Speter
296253734Speter  SVN_ERR(find_running_gpg_agent(&sd, pool));
297253734Speter  if (sd == -1)
298253734Speter    return SVN_NO_ERROR;
299253734Speter
300253734Speter  buffer = apr_palloc(pool, BUFFER_SIZE);
301253734Speter
302251881Speter  /* Send TTY_NAME to the gpg-agent daemon. */
303251881Speter  tty_name = getenv("GPG_TTY");
304251881Speter  if (tty_name != NULL)
305251881Speter    {
306251881Speter      if (!send_option(sd, buffer, BUFFER_SIZE, "ttyname", tty_name, pool))
307251881Speter        {
308251881Speter          close(sd);
309251881Speter          return SVN_NO_ERROR;
310251881Speter        }
311251881Speter    }
312251881Speter
313251881Speter  /* Send TTY_TYPE to the gpg-agent daemon. */
314251881Speter  tty_type = getenv("TERM");
315251881Speter  if (tty_type != NULL)
316251881Speter    {
317251881Speter      if (!send_option(sd, buffer, BUFFER_SIZE, "ttytype", tty_type, pool))
318251881Speter        {
319251881Speter          close(sd);
320251881Speter          return SVN_NO_ERROR;
321251881Speter        }
322251881Speter    }
323251881Speter
324251881Speter  /* Compute LC_CTYPE. */
325251881Speter  lc_ctype = getenv("LC_ALL");
326251881Speter  if (lc_ctype == NULL)
327251881Speter    lc_ctype = getenv("LC_CTYPE");
328251881Speter  if (lc_ctype == NULL)
329251881Speter    lc_ctype = getenv("LANG");
330251881Speter
331251881Speter  /* Send LC_CTYPE to the gpg-agent daemon. */
332251881Speter  if (lc_ctype != NULL)
333251881Speter    {
334251881Speter      if (!send_option(sd, buffer, BUFFER_SIZE, "lc-ctype", lc_ctype, pool))
335251881Speter        {
336251881Speter          close(sd);
337251881Speter          return SVN_NO_ERROR;
338251881Speter        }
339251881Speter    }
340251881Speter
341251881Speter  /* Send DISPLAY to the gpg-agent daemon. */
342251881Speter  display = getenv("DISPLAY");
343251881Speter  if (display != NULL)
344251881Speter    {
345251881Speter      if (!send_option(sd, buffer, BUFFER_SIZE, "display", display, pool))
346251881Speter        {
347251881Speter          close(sd);
348251881Speter          return SVN_NO_ERROR;
349251881Speter        }
350251881Speter    }
351251881Speter
352251881Speter  /* Create the CACHE_ID which will be generated based on REALMSTRING similar
353251881Speter     to other password caching mechanisms. */
354251881Speter  SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
355251881Speter                       strlen(realmstring), pool));
356251881Speter  cache_id = svn_checksum_to_cstring(digest, pool);
357251881Speter
358251881Speter  password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
359251881Speter  realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
360251881Speter                              realmstring);
361251881Speter  request = apr_psprintf(pool,
362251881Speter                         "GET_PASSPHRASE --data %s--repeat=1 "
363251881Speter                         "%s X %s %s\n",
364251881Speter                         non_interactive ? "--no-ask " : "",
365251881Speter                         cache_id,
366251881Speter                         escape_blanks(password_prompt),
367251881Speter                         escape_blanks(realm_prompt));
368251881Speter
369251881Speter  if (write(sd, request, strlen(request)) == -1)
370251881Speter    {
371251881Speter      close(sd);
372251881Speter      return SVN_NO_ERROR;
373251881Speter    }
374251881Speter  if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
375251881Speter    {
376251881Speter      close(sd);
377251881Speter      return SVN_NO_ERROR;
378251881Speter    }
379251881Speter
380251881Speter  close(sd);
381251881Speter
382251881Speter  if (strncmp(buffer, "ERR", 3) == 0)
383251881Speter    return SVN_NO_ERROR;
384251881Speter
385251881Speter  p = NULL;
386251881Speter  if (strncmp(buffer, "D", 1) == 0)
387251881Speter    p = &buffer[2];
388251881Speter
389251881Speter  if (!p)
390251881Speter    return SVN_NO_ERROR;
391251881Speter
392251881Speter  ep = strchr(p, '\n');
393251881Speter  if (ep != NULL)
394251881Speter    *ep = '\0';
395251881Speter
396251881Speter  *password = p;
397251881Speter
398251881Speter  *done = TRUE;
399251881Speter  return SVN_NO_ERROR;
400251881Speter}
401251881Speter
402251881Speter
403251881Speter/* Implementation of svn_auth__password_set_t that would store the
404251881Speter   password in GPG Agent if that's how this particular integration
405251881Speter   worked.  But it isn't.  GPG Agent stores the password provided by
406251881Speter   the user via the pinentry program immediately upon its provision
407253734Speter   (and regardless of its accuracy as passwords go), so we just need
408253734Speter   to check if a running GPG Agent exists. */
409251881Speterstatic svn_error_t *
410251881Speterpassword_set_gpg_agent(svn_boolean_t *done,
411251881Speter                       apr_hash_t *creds,
412251881Speter                       const char *realmstring,
413251881Speter                       const char *username,
414251881Speter                       const char *password,
415251881Speter                       apr_hash_t *parameters,
416251881Speter                       svn_boolean_t non_interactive,
417251881Speter                       apr_pool_t *pool)
418251881Speter{
419253734Speter  int sd;
420253734Speter
421253734Speter  *done = FALSE;
422253734Speter
423253734Speter  SVN_ERR(find_running_gpg_agent(&sd, pool));
424253734Speter  if (sd == -1)
425253734Speter    return SVN_NO_ERROR;
426253734Speter
427253734Speter  close(sd);
428251881Speter  *done = TRUE;
429251881Speter
430251881Speter  return SVN_NO_ERROR;
431251881Speter}
432251881Speter
433251881Speter
434251881Speter/* An implementation of svn_auth_provider_t::first_credentials() */
435251881Speterstatic svn_error_t *
436251881Spetersimple_gpg_agent_first_creds(void **credentials,
437251881Speter                             void **iter_baton,
438251881Speter                             void *provider_baton,
439251881Speter                             apr_hash_t *parameters,
440251881Speter                             const char *realmstring,
441251881Speter                             apr_pool_t *pool)
442251881Speter{
443251881Speter  return svn_auth__simple_creds_cache_get(credentials, iter_baton,
444251881Speter                                          provider_baton, parameters,
445251881Speter                                          realmstring, password_get_gpg_agent,
446251881Speter                                          SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
447251881Speter                                          pool);
448251881Speter}
449251881Speter
450251881Speter
451251881Speter/* An implementation of svn_auth_provider_t::save_credentials() */
452251881Speterstatic svn_error_t *
453251881Spetersimple_gpg_agent_save_creds(svn_boolean_t *saved,
454251881Speter                            void *credentials,
455251881Speter                            void *provider_baton,
456251881Speter                            apr_hash_t *parameters,
457251881Speter                            const char *realmstring,
458251881Speter                            apr_pool_t *pool)
459251881Speter{
460251881Speter  return svn_auth__simple_creds_cache_set(saved, credentials,
461251881Speter                                          provider_baton, parameters,
462251881Speter                                          realmstring, password_set_gpg_agent,
463251881Speter                                          SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
464251881Speter                                          pool);
465251881Speter}
466251881Speter
467251881Speter
468251881Speterstatic const svn_auth_provider_t gpg_agent_simple_provider = {
469251881Speter  SVN_AUTH_CRED_SIMPLE,
470251881Speter  simple_gpg_agent_first_creds,
471251881Speter  NULL,
472251881Speter  simple_gpg_agent_save_creds
473251881Speter};
474251881Speter
475251881Speter
476251881Speter/* Public API */
477251881Spetervoid
478251881Spetersvn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
479251881Speter                                       apr_pool_t *pool)
480251881Speter{
481251881Speter  svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
482251881Speter
483251881Speter  po->vtable = &gpg_agent_simple_provider;
484251881Speter  *provider = po;
485251881Speter}
486251881Speter
487251881Speter#endif /* SVN_HAVE_GPG_AGENT */
488251881Speter#endif /* !WIN32 */
489