]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_subr/gpg_agent.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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 "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. */
86 static char *
87 escape_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. */
106 static svn_boolean_t
107 receive_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. */
142 static svn_boolean_t
143 send_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. */
163 static svn_error_t *
164 find_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 */
270 static svn_error_t *
271 password_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. */
409 static svn_error_t *
410 password_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() */
435 static svn_error_t *
436 simple_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() */
452 static svn_error_t *
453 simple_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
468 static 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 */
477 void
478 svn_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 */