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