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" 75289166Speter#include "svn_hash.h" 76289166Speter#include "svn_user.h" 77289166Speter#include "svn_dirent_uri.h" 78251881Speter 79251881Speter#include "private/svn_auth_private.h" 80251881Speter 81251881Speter#include "svn_private_config.h" 82251881Speter 83251881Speter#ifdef SVN_HAVE_GPG_AGENT 84251881Speter 85251881Speter#define BUFFER_SIZE 1024 86289166Speter#define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt" 87251881Speter 88251881Speter/* Modify STR in-place such that blanks are escaped as required by the 89251881Speter * gpg-agent protocol. Return a pointer to STR. */ 90251881Speterstatic char * 91251881Speterescape_blanks(char *str) 92251881Speter{ 93251881Speter char *s = str; 94251881Speter 95251881Speter while (*s) 96251881Speter { 97251881Speter if (*s == ' ') 98251881Speter *s = '+'; 99251881Speter s++; 100251881Speter } 101251881Speter 102251881Speter return str; 103251881Speter} 104251881Speter 105289166Speter/* Generate the string CACHE_ID_P based on the REALMSTRING allocated in 106289166Speter * RESULT_POOL using SCRATCH_POOL for temporary allocations. This is similar 107289166Speter * to other password caching mechanisms. */ 108289166Speterstatic svn_error_t * 109289166Speterget_cache_id(const char **cache_id_p, const char *realmstring, 110289166Speter apr_pool_t *scratch_pool, apr_pool_t *result_pool) 111289166Speter{ 112289166Speter const char *cache_id = NULL; 113289166Speter svn_checksum_t *digest = NULL; 114289166Speter 115289166Speter SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring, 116289166Speter strlen(realmstring), scratch_pool)); 117289166Speter cache_id = svn_checksum_to_cstring(digest, result_pool); 118289166Speter *cache_id_p = cache_id; 119289166Speter 120289166Speter return SVN_NO_ERROR; 121289166Speter} 122289166Speter 123251881Speter/* Attempt to read a gpg-agent response message from the socket SD into 124251881Speter * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response 125251881Speter * message could be read that fits into the buffer. Else return FALSE. 126251881Speter * If a message could be read it will always be NUL-terminated and the 127251881Speter * trailing newline is retained. */ 128251881Speterstatic svn_boolean_t 129251881Speterreceive_from_gpg_agent(int sd, char *buf, size_t n) 130251881Speter{ 131251881Speter int i = 0; 132251881Speter size_t recvd; 133251881Speter char c; 134251881Speter 135251881Speter /* Clear existing buffer content before reading response. */ 136251881Speter if (n > 0) 137251881Speter *buf = '\0'; 138251881Speter 139251881Speter /* Require the message to fit into the buffer and be terminated 140251881Speter * with a newline. */ 141251881Speter while (i < n) 142251881Speter { 143251881Speter recvd = read(sd, &c, 1); 144251881Speter if (recvd == -1) 145251881Speter return FALSE; 146251881Speter buf[i] = c; 147251881Speter i++; 148251881Speter if (i < n && c == '\n') 149251881Speter { 150251881Speter buf[i] = '\0'; 151251881Speter return TRUE; 152251881Speter } 153251881Speter } 154251881Speter 155251881Speter return FALSE; 156251881Speter} 157251881Speter 158251881Speter/* Using socket SD, send the option OPTION with the specified VALUE 159251881Speter * to the gpg agent. Store the response in BUF, assumed to be N bytes 160251881Speter * in size, and evaluate the response. Return TRUE if the agent liked 161251881Speter * the smell of the option, if there is such a thing, and doesn't feel 162251881Speter * saturated by it. Else return FALSE. 163251881Speter * Do temporary allocations in scratch_pool. */ 164251881Speterstatic svn_boolean_t 165251881Spetersend_option(int sd, char *buf, size_t n, const char *option, const char *value, 166251881Speter apr_pool_t *scratch_pool) 167251881Speter{ 168251881Speter const char *request; 169251881Speter 170251881Speter request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value); 171251881Speter 172251881Speter if (write(sd, request, strlen(request)) == -1) 173251881Speter return FALSE; 174251881Speter 175251881Speter if (!receive_from_gpg_agent(sd, buf, n)) 176251881Speter return FALSE; 177251881Speter 178251881Speter return (strncmp(buf, "OK", 2) == 0); 179251881Speter} 180251881Speter 181289166Speter/* Send the BYE command and disconnect from the gpg-agent. Doing this avoids 182289166Speter * gpg-agent emitting a "Connection reset by peer" log message with some 183289166Speter * versions of gpg-agent. */ 184289166Speterstatic void 185289166Speterbye_gpg_agent(int sd) 186289166Speter{ 187289166Speter /* don't bother to check the result of the write, it either worked or it 188289166Speter * didn't, but either way we're closing. */ 189289166Speter write(sd, "BYE\n", 4); 190289166Speter close(sd); 191289166Speter} 192253734Speter 193253734Speter/* Locate a running GPG Agent, and return an open file descriptor 194253734Speter * for communication with the agent in *NEW_SD. If no running agent 195253734Speter * can be found, set *NEW_SD to -1. */ 196251881Speterstatic svn_error_t * 197253734Speterfind_running_gpg_agent(int *new_sd, apr_pool_t *pool) 198251881Speter{ 199253734Speter char *buffer; 200251881Speter char *gpg_agent_info = NULL; 201253734Speter const char *socket_name = NULL; 202253734Speter const char *request = NULL; 203251881Speter const char *p = NULL; 204251881Speter char *ep = NULL; 205253734Speter int sd; 206251881Speter 207253734Speter *new_sd = -1; 208251881Speter 209289166Speter /* This implements the method of finding the socket as described in 210289166Speter * the gpg-agent man page under the --use-standard-socket option. 211289166Speter * The manage page misleadingly says the standard socket is 212289166Speter * "named 'S.gpg-agent' located in the home directory." The standard 213289166Speter * socket path is actually in the .gnupg directory in the home directory, 214289166Speter * i.e. ~/.gnupg/S.gpg-agent */ 215251881Speter gpg_agent_info = getenv("GPG_AGENT_INFO"); 216251881Speter if (gpg_agent_info != NULL) 217251881Speter { 218253734Speter apr_array_header_t *socket_details; 219253734Speter 220289166Speter /* For reference GPG_AGENT_INFO consists of 3 : separated fields. 221289166Speter * The path to the socket, the pid of the gpg-agent process and 222289166Speter * finally the version of the protocol the agent talks. */ 223251881Speter socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE, 224251881Speter pool); 225251881Speter socket_name = APR_ARRAY_IDX(socket_details, 0, const char *); 226251881Speter } 227251881Speter else 228289166Speter { 229289166Speter const char *homedir = svn_user_get_homedir(pool); 230251881Speter 231289166Speter if (!homedir) 232289166Speter return SVN_NO_ERROR; 233289166Speter 234289166Speter socket_name = svn_dirent_join_many(pool, homedir, ".gnupg", 235289166Speter "S.gpg-agent", NULL); 236289166Speter } 237289166Speter 238251881Speter if (socket_name != NULL) 239251881Speter { 240253734Speter struct sockaddr_un addr; 241253734Speter 242251881Speter addr.sun_family = AF_UNIX; 243251881Speter strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1); 244251881Speter addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; 245251881Speter 246251881Speter sd = socket(AF_UNIX, SOCK_STREAM, 0); 247251881Speter if (sd == -1) 248251881Speter return SVN_NO_ERROR; 249251881Speter 250251881Speter if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) 251251881Speter { 252251881Speter close(sd); 253251881Speter return SVN_NO_ERROR; 254251881Speter } 255251881Speter } 256251881Speter else 257251881Speter return SVN_NO_ERROR; 258251881Speter 259251881Speter /* Receive the connection status from the gpg-agent daemon. */ 260251881Speter buffer = apr_palloc(pool, BUFFER_SIZE); 261251881Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 262251881Speter { 263289166Speter bye_gpg_agent(sd); 264251881Speter return SVN_NO_ERROR; 265251881Speter } 266251881Speter 267251881Speter if (strncmp(buffer, "OK", 2) != 0) 268251881Speter { 269289166Speter bye_gpg_agent(sd); 270251881Speter return SVN_NO_ERROR; 271251881Speter } 272251881Speter 273251881Speter /* The GPG-Agent documentation says: 274251881Speter * "Clients should deny to access an agent with a socket name which does 275251881Speter * not match its own configuration". */ 276251881Speter request = "GETINFO socket_name\n"; 277251881Speter if (write(sd, request, strlen(request)) == -1) 278251881Speter { 279289166Speter bye_gpg_agent(sd); 280251881Speter return SVN_NO_ERROR; 281251881Speter } 282251881Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 283251881Speter { 284289166Speter bye_gpg_agent(sd); 285251881Speter return SVN_NO_ERROR; 286251881Speter } 287251881Speter if (strncmp(buffer, "D", 1) == 0) 288251881Speter p = &buffer[2]; 289251881Speter if (!p) 290251881Speter { 291289166Speter bye_gpg_agent(sd); 292251881Speter return SVN_NO_ERROR; 293251881Speter } 294251881Speter ep = strchr(p, '\n'); 295251881Speter if (ep != NULL) 296251881Speter *ep = '\0'; 297251881Speter if (strcmp(socket_name, p) != 0) 298251881Speter { 299289166Speter bye_gpg_agent(sd); 300251881Speter return SVN_NO_ERROR; 301251881Speter } 302251881Speter /* The agent will terminate its response with "OK". */ 303251881Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 304251881Speter { 305289166Speter bye_gpg_agent(sd); 306251881Speter return SVN_NO_ERROR; 307251881Speter } 308251881Speter if (strncmp(buffer, "OK", 2) != 0) 309251881Speter { 310289166Speter bye_gpg_agent(sd); 311251881Speter return SVN_NO_ERROR; 312251881Speter } 313251881Speter 314253734Speter *new_sd = sd; 315253734Speter return SVN_NO_ERROR; 316253734Speter} 317253734Speter 318289166Speterstatic svn_boolean_t 319289166Spetersend_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool) 320253734Speter{ 321253734Speter const char *tty_name; 322253734Speter const char *tty_type; 323253734Speter const char *lc_ctype; 324253734Speter const char *display; 325253734Speter 326251881Speter /* Send TTY_NAME to the gpg-agent daemon. */ 327251881Speter tty_name = getenv("GPG_TTY"); 328251881Speter if (tty_name != NULL) 329251881Speter { 330289166Speter if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool)) 331289166Speter return FALSE; 332251881Speter } 333251881Speter 334251881Speter /* Send TTY_TYPE to the gpg-agent daemon. */ 335251881Speter tty_type = getenv("TERM"); 336251881Speter if (tty_type != NULL) 337251881Speter { 338289166Speter if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool)) 339289166Speter return FALSE; 340251881Speter } 341251881Speter 342251881Speter /* Compute LC_CTYPE. */ 343251881Speter lc_ctype = getenv("LC_ALL"); 344251881Speter if (lc_ctype == NULL) 345251881Speter lc_ctype = getenv("LC_CTYPE"); 346251881Speter if (lc_ctype == NULL) 347251881Speter lc_ctype = getenv("LANG"); 348251881Speter 349251881Speter /* Send LC_CTYPE to the gpg-agent daemon. */ 350251881Speter if (lc_ctype != NULL) 351251881Speter { 352289166Speter if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool)) 353289166Speter return FALSE; 354251881Speter } 355251881Speter 356251881Speter /* Send DISPLAY to the gpg-agent daemon. */ 357251881Speter display = getenv("DISPLAY"); 358251881Speter if (display != NULL) 359251881Speter { 360289166Speter if (!send_option(sd, buf, n, "display", display, scratch_pool)) 361289166Speter return FALSE; 362251881Speter } 363251881Speter 364289166Speter return TRUE; 365289166Speter} 366251881Speter 367289166Speter/* Implementation of svn_auth__password_get_t that retrieves the password 368289166Speter from gpg-agent */ 369289166Speterstatic svn_error_t * 370289166Speterpassword_get_gpg_agent(svn_boolean_t *done, 371289166Speter const char **password, 372289166Speter apr_hash_t *creds, 373289166Speter const char *realmstring, 374289166Speter const char *username, 375289166Speter apr_hash_t *parameters, 376289166Speter svn_boolean_t non_interactive, 377289166Speter apr_pool_t *pool) 378289166Speter{ 379289166Speter int sd; 380289166Speter const char *p = NULL; 381289166Speter char *ep = NULL; 382289166Speter char *buffer; 383289166Speter const char *request = NULL; 384289166Speter const char *cache_id = NULL; 385289166Speter char *password_prompt; 386289166Speter char *realm_prompt; 387289166Speter char *error_prompt; 388289166Speter int *attempt; 389289166Speter 390289166Speter *done = FALSE; 391289166Speter 392289166Speter attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER); 393289166Speter 394289166Speter SVN_ERR(find_running_gpg_agent(&sd, pool)); 395289166Speter if (sd == -1) 396289166Speter return SVN_NO_ERROR; 397289166Speter 398289166Speter buffer = apr_palloc(pool, BUFFER_SIZE); 399289166Speter 400289166Speter if (!send_options(sd, buffer, BUFFER_SIZE, pool)) 401289166Speter { 402289166Speter bye_gpg_agent(sd); 403289166Speter return SVN_NO_ERROR; 404289166Speter } 405289166Speter 406289166Speter SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool)); 407289166Speter 408251881Speter password_prompt = apr_psprintf(pool, _("Password for '%s': "), username); 409251881Speter realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"), 410251881Speter realmstring); 411289166Speter if (*attempt == 1) 412289166Speter /* X means no error to the gpg-agent protocol */ 413289166Speter error_prompt = apr_pstrdup(pool, "X"); 414289166Speter else 415289166Speter error_prompt = apr_pstrdup(pool, _("Authentication failed")); 416289166Speter 417251881Speter request = apr_psprintf(pool, 418289166Speter "GET_PASSPHRASE --data %s" 419289166Speter "%s %s %s %s\n", 420251881Speter non_interactive ? "--no-ask " : "", 421251881Speter cache_id, 422289166Speter escape_blanks(error_prompt), 423251881Speter escape_blanks(password_prompt), 424251881Speter escape_blanks(realm_prompt)); 425251881Speter 426251881Speter if (write(sd, request, strlen(request)) == -1) 427251881Speter { 428289166Speter bye_gpg_agent(sd); 429251881Speter return SVN_NO_ERROR; 430251881Speter } 431251881Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 432251881Speter { 433289166Speter bye_gpg_agent(sd); 434251881Speter return SVN_NO_ERROR; 435251881Speter } 436251881Speter 437289166Speter bye_gpg_agent(sd); 438251881Speter 439251881Speter if (strncmp(buffer, "ERR", 3) == 0) 440251881Speter return SVN_NO_ERROR; 441251881Speter 442251881Speter p = NULL; 443251881Speter if (strncmp(buffer, "D", 1) == 0) 444251881Speter p = &buffer[2]; 445251881Speter 446251881Speter if (!p) 447251881Speter return SVN_NO_ERROR; 448251881Speter 449251881Speter ep = strchr(p, '\n'); 450251881Speter if (ep != NULL) 451251881Speter *ep = '\0'; 452251881Speter 453251881Speter *password = p; 454251881Speter 455251881Speter *done = TRUE; 456251881Speter return SVN_NO_ERROR; 457251881Speter} 458251881Speter 459251881Speter 460251881Speter/* Implementation of svn_auth__password_set_t that would store the 461251881Speter password in GPG Agent if that's how this particular integration 462251881Speter worked. But it isn't. GPG Agent stores the password provided by 463251881Speter the user via the pinentry program immediately upon its provision 464253734Speter (and regardless of its accuracy as passwords go), so we just need 465253734Speter to check if a running GPG Agent exists. */ 466251881Speterstatic svn_error_t * 467251881Speterpassword_set_gpg_agent(svn_boolean_t *done, 468251881Speter apr_hash_t *creds, 469251881Speter const char *realmstring, 470251881Speter const char *username, 471251881Speter const char *password, 472251881Speter apr_hash_t *parameters, 473251881Speter svn_boolean_t non_interactive, 474251881Speter apr_pool_t *pool) 475251881Speter{ 476253734Speter int sd; 477253734Speter 478253734Speter *done = FALSE; 479253734Speter 480253734Speter SVN_ERR(find_running_gpg_agent(&sd, pool)); 481253734Speter if (sd == -1) 482253734Speter return SVN_NO_ERROR; 483253734Speter 484289166Speter bye_gpg_agent(sd); 485251881Speter *done = TRUE; 486251881Speter 487251881Speter return SVN_NO_ERROR; 488251881Speter} 489251881Speter 490251881Speter 491251881Speter/* An implementation of svn_auth_provider_t::first_credentials() */ 492251881Speterstatic svn_error_t * 493251881Spetersimple_gpg_agent_first_creds(void **credentials, 494251881Speter void **iter_baton, 495251881Speter void *provider_baton, 496251881Speter apr_hash_t *parameters, 497251881Speter const char *realmstring, 498251881Speter apr_pool_t *pool) 499251881Speter{ 500289166Speter svn_error_t *err; 501289166Speter int *attempt = apr_palloc(pool, sizeof(*attempt)); 502289166Speter 503289166Speter *attempt = 1; 504289166Speter svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt); 505289166Speter err = svn_auth__simple_creds_cache_get(credentials, iter_baton, 506289166Speter provider_baton, parameters, 507289166Speter realmstring, password_get_gpg_agent, 508289166Speter SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 509289166Speter pool); 510289166Speter *iter_baton = attempt; 511289166Speter 512289166Speter return err; 513251881Speter} 514251881Speter 515289166Speter/* An implementation of svn_auth_provider_t::next_credentials() */ 516289166Speterstatic svn_error_t * 517289166Spetersimple_gpg_agent_next_creds(void **credentials, 518289166Speter void *iter_baton, 519289166Speter void *provider_baton, 520289166Speter apr_hash_t *parameters, 521289166Speter const char *realmstring, 522289166Speter apr_pool_t *pool) 523289166Speter{ 524289166Speter int *attempt = (int *)iter_baton; 525289166Speter int sd; 526289166Speter char *buffer; 527289166Speter const char *cache_id = NULL; 528289166Speter const char *request = NULL; 529251881Speter 530289166Speter *credentials = NULL; 531289166Speter 532289166Speter /* The users previous credentials failed so first remove the cached entry, 533289166Speter * before trying to retrieve them again. Because gpg-agent stores cached 534289166Speter * credentials immediately upon retrieving them, this gives us the 535289166Speter * opportunity to remove the invalid credentials and prompt the 536289166Speter * user again. While it's possible that server side issues could trigger 537289166Speter * this, this cache is ephemeral so at worst we're just speeding up 538289166Speter * when the user would need to re-enter their password. */ 539289166Speter 540289166Speter if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE)) 541289166Speter { 542289166Speter /* In this case since we're running non-interactively we do not 543289166Speter * want to clear the cache since the user was never prompted by 544289166Speter * gpg-agent to set a password. */ 545289166Speter return SVN_NO_ERROR; 546289166Speter } 547289166Speter 548289166Speter *attempt = *attempt + 1; 549289166Speter 550289166Speter SVN_ERR(find_running_gpg_agent(&sd, pool)); 551289166Speter if (sd == -1) 552289166Speter return SVN_NO_ERROR; 553289166Speter 554289166Speter buffer = apr_palloc(pool, BUFFER_SIZE); 555289166Speter 556289166Speter if (!send_options(sd, buffer, BUFFER_SIZE, pool)) 557289166Speter { 558289166Speter bye_gpg_agent(sd); 559289166Speter return SVN_NO_ERROR; 560289166Speter } 561289166Speter 562289166Speter SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool)); 563289166Speter 564289166Speter request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id); 565289166Speter 566289166Speter if (write(sd, request, strlen(request)) == -1) 567289166Speter { 568289166Speter bye_gpg_agent(sd); 569289166Speter return SVN_NO_ERROR; 570289166Speter } 571289166Speter 572289166Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 573289166Speter { 574289166Speter bye_gpg_agent(sd); 575289166Speter return SVN_NO_ERROR; 576289166Speter } 577289166Speter 578289166Speter if (strncmp(buffer, "OK\n", 3) != 0) 579289166Speter { 580289166Speter bye_gpg_agent(sd); 581289166Speter return SVN_NO_ERROR; 582289166Speter } 583289166Speter 584289166Speter /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries) 585289166Speter * which matches svn command line client's retry_limit as set in 586289166Speter * svn_cmdline_create_auth_baton(). It would be nice to have that 587289166Speter * limit reflected here but that violates the boundry between the 588289166Speter * prompt provider and the cache provider. gpg-agent is acting as 589289166Speter * both here due to the peculiarties of their design so we'll have to 590289166Speter * live with this for now. Note that when these failures get exceeded 591289166Speter * it'll eventually fall back on the retry limits of whatever prompt 592289166Speter * provider is in effect, so this effectively doubles the limit. */ 593289166Speter if (*attempt < 4) 594289166Speter return svn_auth__simple_creds_cache_get(credentials, &iter_baton, 595289166Speter provider_baton, parameters, 596289166Speter realmstring, 597289166Speter password_get_gpg_agent, 598289166Speter SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 599289166Speter pool); 600289166Speter 601289166Speter return SVN_NO_ERROR; 602289166Speter} 603289166Speter 604289166Speter 605251881Speter/* An implementation of svn_auth_provider_t::save_credentials() */ 606251881Speterstatic svn_error_t * 607251881Spetersimple_gpg_agent_save_creds(svn_boolean_t *saved, 608251881Speter void *credentials, 609251881Speter void *provider_baton, 610251881Speter apr_hash_t *parameters, 611251881Speter const char *realmstring, 612251881Speter apr_pool_t *pool) 613251881Speter{ 614251881Speter return svn_auth__simple_creds_cache_set(saved, credentials, 615251881Speter provider_baton, parameters, 616251881Speter realmstring, password_set_gpg_agent, 617251881Speter SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 618251881Speter pool); 619251881Speter} 620251881Speter 621251881Speter 622251881Speterstatic const svn_auth_provider_t gpg_agent_simple_provider = { 623251881Speter SVN_AUTH_CRED_SIMPLE, 624251881Speter simple_gpg_agent_first_creds, 625289166Speter simple_gpg_agent_next_creds, 626251881Speter simple_gpg_agent_save_creds 627251881Speter}; 628251881Speter 629251881Speter 630251881Speter/* Public API */ 631251881Spetervoid 632251881Spetersvn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider, 633251881Speter apr_pool_t *pool) 634251881Speter{ 635251881Speter svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); 636251881Speter 637251881Speter po->vtable = &gpg_agent_simple_provider; 638251881Speter *provider = po; 639251881Speter} 640251881Speter 641251881Speter#endif /* SVN_HAVE_GPG_AGENT */ 642251881Speter#endif /* !WIN32 */ 643