2 * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_*
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
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.
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).
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.
39 * SECURITY CONSIDERATIONS:
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.
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
64 #include <sys/socket.h>
67 #include <apr_pools.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"
76 #include "private/svn_auth_private.h"
78 #include "svn_private_config.h"
80 #ifdef SVN_HAVE_GPG_AGENT
82 #define BUFFER_SIZE 1024
84 /* Modify STR in-place such that blanks are escaped as required by the
85 * gpg-agent protocol. Return a pointer to STR. */
87 escape_blanks(char *str)
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. */
107 receive_from_gpg_agent(int sd, char *buf, size_t n)
113 /* Clear existing buffer content before reading response. */
117 /* Require the message to fit into the buffer and be terminated
121 recvd = read(sd, &c, 1);
126 if (i < n && c == '\n')
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. */
143 send_option(int sd, char *buf, size_t n, const char *option, const char *value,
144 apr_pool_t *scratch_pool)
148 request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
150 if (write(sd, request, strlen(request)) == -1)
153 if (!receive_from_gpg_agent(sd, buf, n))
156 return (strncmp(buf, "OK", 2) == 0);
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. */
164 find_running_gpg_agent(int *new_sd, apr_pool_t *pool)
167 char *gpg_agent_info = NULL;
168 const char *socket_name = NULL;
169 const char *request = NULL;
170 const char *p = NULL;
176 gpg_agent_info = getenv("GPG_AGENT_INFO");
177 if (gpg_agent_info != NULL)
179 apr_array_header_t *socket_details;
181 socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
183 socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
188 if (socket_name != NULL)
190 struct sockaddr_un addr;
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';
196 sd = socket(AF_UNIX, SOCK_STREAM, 0);
200 if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
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))
217 if (strncmp(buffer, "OK", 2) != 0)
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)
232 if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
237 if (strncmp(buffer, "D", 1) == 0)
244 ep = strchr(p, '\n');
247 if (strcmp(socket_name, p) != 0)
252 /* The agent will terminate its response with "OK". */
253 if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
258 if (strncmp(buffer, "OK", 2) != 0)
268 /* Implementation of svn_auth__password_get_t that retrieves the password
271 password_get_gpg_agent(svn_boolean_t *done,
272 const char **password,
274 const char *realmstring,
275 const char *username,
276 apr_hash_t *parameters,
277 svn_boolean_t non_interactive,
281 const char *p = NULL;
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;
290 svn_checksum_t *digest = NULL;
291 char *password_prompt;
296 SVN_ERR(find_running_gpg_agent(&sd, pool));
300 buffer = apr_palloc(pool, BUFFER_SIZE);
302 /* Send TTY_NAME to the gpg-agent daemon. */
303 tty_name = getenv("GPG_TTY");
304 if (tty_name != NULL)
306 if (!send_option(sd, buffer, BUFFER_SIZE, "ttyname", tty_name, pool))
313 /* Send TTY_TYPE to the gpg-agent daemon. */
314 tty_type = getenv("TERM");
315 if (tty_type != NULL)
317 if (!send_option(sd, buffer, BUFFER_SIZE, "ttytype", tty_type, pool))
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");
331 /* Send LC_CTYPE to the gpg-agent daemon. */
332 if (lc_ctype != NULL)
334 if (!send_option(sd, buffer, BUFFER_SIZE, "lc-ctype", lc_ctype, pool))
341 /* Send DISPLAY to the gpg-agent daemon. */
342 display = getenv("DISPLAY");
345 if (!send_option(sd, buffer, BUFFER_SIZE, "display", display, pool))
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);
358 password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
359 realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
361 request = apr_psprintf(pool,
362 "GET_PASSPHRASE --data %s--repeat=1 "
364 non_interactive ? "--no-ask " : "",
366 escape_blanks(password_prompt),
367 escape_blanks(realm_prompt));
369 if (write(sd, request, strlen(request)) == -1)
374 if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
382 if (strncmp(buffer, "ERR", 3) == 0)
386 if (strncmp(buffer, "D", 1) == 0)
392 ep = strchr(p, '\n');
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. */
410 password_set_gpg_agent(svn_boolean_t *done,
412 const char *realmstring,
413 const char *username,
414 const char *password,
415 apr_hash_t *parameters,
416 svn_boolean_t non_interactive,
423 SVN_ERR(find_running_gpg_agent(&sd, pool));
434 /* An implementation of svn_auth_provider_t::first_credentials() */
436 simple_gpg_agent_first_creds(void **credentials,
438 void *provider_baton,
439 apr_hash_t *parameters,
440 const char *realmstring,
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,
451 /* An implementation of svn_auth_provider_t::save_credentials() */
453 simple_gpg_agent_save_creds(svn_boolean_t *saved,
455 void *provider_baton,
456 apr_hash_t *parameters,
457 const char *realmstring,
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,
468 static const svn_auth_provider_t gpg_agent_simple_provider = {
469 SVN_AUTH_CRED_SIMPLE,
470 simple_gpg_agent_first_creds,
472 simple_gpg_agent_save_creds
478 svn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
481 svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
483 po->vtable = &gpg_agent_simple_provider;
487 #endif /* SVN_HAVE_GPG_AGENT */