gpg_agent.c revision 286506
118334Speter/* 218334Speter * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_* 318334Speter * 418334Speter * ==================================================================== 518334Speter * Licensed to the Apache Software Foundation (ASF) under one 618334Speter * or more contributor license agreements. See the NOTICE file 718334Speter * distributed with this work for additional information 818334Speter * regarding copyright ownership. The ASF licenses this file 918334Speter * to you under the Apache License, Version 2.0 (the 1018334Speter * "License"); you may not use this file except in compliance 1118334Speter * with the License. You may obtain a copy of the License at 1218334Speter * 1318334Speter * http://www.apache.org/licenses/LICENSE-2.0 1418334Speter * 1518334Speter * Unless required by applicable law or agreed to in writing, 1618334Speter * software distributed under the License is distributed on an 1718334Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 1818334Speter * KIND, either express or implied. See the License for the 1918334Speter * specific language governing permissions and limitations 2018334Speter * under the License. 2118334Speter * ==================================================================== 2218334Speter */ 2318334Speter 2418334Speter/* ==================================================================== */ 2518334Speter 2618334Speter/* This auth provider stores a plaintext password in memory managed by 2718334Speter * a running gpg-agent. In contrast to other password store providers 2818334Speter * it does not save the password to disk. 2918334Speter * 3018334Speter * Prompting is performed by the gpg-agent using a "pinentry" program 3118334Speter * which needs to be installed separately. There are several pinentry 3218334Speter * implementations with different front-ends (e.g. qt, gtk, ncurses). 3318334Speter * 3418334Speter * The gpg-agent will let the password time out after a while, 3518334Speter * or immediately when it receives the SIGHUP signal. 3618334Speter * When the password has timed out it will automatically prompt the 3718334Speter * user for the password again. This is transparent to Subversion. 3818334Speter * 3918334Speter * SECURITY CONSIDERATIONS: 4018334Speter * 4118334Speter * Communication to the agent happens over a UNIX socket, which is located 4218334Speter * in a directory which only the user running Subversion can access. 4318334Speter * However, any program the user runs could access this socket and get 4418334Speter * the Subversion password if the program knows the "cache ID" Subversion 4518334Speter * uses for the password. 4618334Speter * The cache ID is very easy to obtain for programs running as the same user. 4718334Speter * Subversion uses the MD5 of the realmstring as cache ID, and these checksums 4818334Speter * are also used as filenames within ~/.subversion/auth/svn.simple. 4918334Speter * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for 5018334Speter * permission if another program attempts to access the password. 5118334Speter * 5218334Speter * Therefore, while the gpg-agent is running and has the password cached, 5318334Speter * this provider is no more secure than a file storing the password in 5418334Speter * plaintext. 5518334Speter */ 5618334Speter 5718334Speter 5818334Speter/*** Includes. ***/ 5918334Speter 6018334Speter#ifndef WIN32 6118334Speter 6218334Speter#include <unistd.h> 6318334Speter 6418334Speter#include <sys/socket.h> 6518334Speter#include <sys/un.h> 6618334Speter 6718334Speter#include <apr_pools.h> 6818334Speter#include "svn_auth.h" 6918334Speter#include "svn_config.h" 7018334Speter#include "svn_error.h" 7118334Speter#include "svn_pools.h" 7218334Speter#include "svn_cmdline.h" 7318334Speter#include "svn_checksum.h" 7418334Speter#include "svn_string.h" 7518334Speter#include "svn_hash.h" 7618334Speter#include "svn_user.h" 7718334Speter#include "svn_dirent_uri.h" 7818334Speter 7918334Speter#include "private/svn_auth_private.h" 8018334Speter 8118334Speter#include "svn_private_config.h" 8218334Speter 8318334Speter#ifdef SVN_HAVE_GPG_AGENT 8418334Speter 8518334Speter#define BUFFER_SIZE 1024 8618334Speter#define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt" 8718334Speter 8818334Speter/* Modify STR in-place such that blanks are escaped as required by the 8918334Speter * gpg-agent protocol. Return a pointer to STR. */ 9018334Speterstatic char * 9118334Speterescape_blanks(char *str) 9218334Speter{ 9318334Speter char *s = str; 9418334Speter 9518334Speter while (*s) 9618334Speter { 9718334Speter if (*s == ' ') 9818334Speter *s = '+'; 9918334Speter s++; 10018334Speter } 10118334Speter 10218334Speter return str; 10318334Speter} 10418334Speter 10518334Speter/* Generate the string CACHE_ID_P based on the REALMSTRING allocated in 10618334Speter * RESULT_POOL using SCRATCH_POOL for temporary allocations. This is similar 10718334Speter * to other password caching mechanisms. */ 10818334Speterstatic svn_error_t * 10918334Speterget_cache_id(const char **cache_id_p, const char *realmstring, 11018334Speter apr_pool_t *scratch_pool, apr_pool_t *result_pool) 11118334Speter{ 11218334Speter const char *cache_id = NULL; 11318334Speter svn_checksum_t *digest = NULL; 11418334Speter 11518334Speter SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring, 11618334Speter strlen(realmstring), scratch_pool)); 11718334Speter cache_id = svn_checksum_to_cstring(digest, result_pool); 11818334Speter *cache_id_p = cache_id; 11918334Speter 12018334Speter return SVN_NO_ERROR; 12118334Speter} 12218334Speter 12318334Speter/* Attempt to read a gpg-agent response message from the socket SD into 12418334Speter * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response 12518334Speter * message could be read that fits into the buffer. Else return FALSE. 12618334Speter * If a message could be read it will always be NUL-terminated and the 12718334Speter * trailing newline is retained. */ 12818334Speterstatic svn_boolean_t 12918334Speterreceive_from_gpg_agent(int sd, char *buf, size_t n) 13018334Speter{ 13118334Speter int i = 0; 13218334Speter size_t recvd; 13318334Speter char c; 13418334Speter 13518334Speter /* Clear existing buffer content before reading response. */ 13618334Speter if (n > 0) 13718334Speter *buf = '\0'; 13818334Speter 13918334Speter /* Require the message to fit into the buffer and be terminated 14018334Speter * with a newline. */ 14118334Speter while (i < n) 14218334Speter { 14318334Speter recvd = read(sd, &c, 1); 14418334Speter if (recvd == -1) 14518334Speter return FALSE; 14618334Speter buf[i] = c; 14718334Speter i++; 14818334Speter if (i < n && c == '\n') 14918334Speter { 15018334Speter buf[i] = '\0'; 15118334Speter return TRUE; 15218334Speter } 15318334Speter } 15418334Speter 15518334Speter return FALSE; 15618334Speter} 15718334Speter 15818334Speter/* Using socket SD, send the option OPTION with the specified VALUE 15918334Speter * to the gpg agent. Store the response in BUF, assumed to be N bytes 16018334Speter * in size, and evaluate the response. Return TRUE if the agent liked 16118334Speter * the smell of the option, if there is such a thing, and doesn't feel 16218334Speter * saturated by it. Else return FALSE. 16318334Speter * Do temporary allocations in scratch_pool. */ 16418334Speterstatic svn_boolean_t 16518334Spetersend_option(int sd, char *buf, size_t n, const char *option, const char *value, 16618334Speter apr_pool_t *scratch_pool) 16718334Speter{ 16818334Speter const char *request; 16918334Speter 17018334Speter request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value); 17118334Speter 17218334Speter if (write(sd, request, strlen(request)) == -1) 17318334Speter return FALSE; 17418334Speter 17518334Speter if (!receive_from_gpg_agent(sd, buf, n)) 17618334Speter return FALSE; 17718334Speter 17818334Speter return (strncmp(buf, "OK", 2) == 0); 17918334Speter} 18018334Speter 18118334Speter/* Send the BYE command and disconnect from the gpg-agent. Doing this avoids 18218334Speter * gpg-agent emitting a "Connection reset by peer" log message with some 18318334Speter * versions of gpg-agent. */ 18418334Speterstatic void 18518334Speterbye_gpg_agent(int sd) 18618334Speter{ 18718334Speter /* don't bother to check the result of the write, it either worked or it 18818334Speter * didn't, but either way we're closing. */ 18918334Speter write(sd, "BYE\n", 4); 19018334Speter close(sd); 19118334Speter} 19218334Speter 19318334Speter/* Locate a running GPG Agent, and return an open file descriptor 19418334Speter * for communication with the agent in *NEW_SD. If no running agent 19518334Speter * can be found, set *NEW_SD to -1. */ 19618334Speterstatic svn_error_t * 19718334Speterfind_running_gpg_agent(int *new_sd, apr_pool_t *pool) 19818334Speter{ 19918334Speter char *buffer; 20018334Speter char *gpg_agent_info = NULL; 20118334Speter const char *socket_name = NULL; 20218334Speter const char *request = NULL; 20318334Speter const char *p = NULL; 20418334Speter char *ep = NULL; 20518334Speter int sd; 20618334Speter 20718334Speter *new_sd = -1; 20818334Speter 20918334Speter /* This implements the method of finding the socket as described in 21018334Speter * the gpg-agent man page under the --use-standard-socket option. 21118334Speter * The manage page misleadingly says the standard socket is 21218334Speter * "named 'S.gpg-agent' located in the home directory." The standard 21318334Speter * socket path is actually in the .gnupg directory in the home directory, 21418334Speter * i.e. ~/.gnupg/S.gpg-agent */ 21518334Speter gpg_agent_info = getenv("GPG_AGENT_INFO"); 21618334Speter if (gpg_agent_info != NULL) 21718334Speter { 21818334Speter apr_array_header_t *socket_details; 21918334Speter 22018334Speter /* For reference GPG_AGENT_INFO consists of 3 : separated fields. 22118334Speter * The path to the socket, the pid of the gpg-agent process and 22218334Speter * finally the version of the protocol the agent talks. */ 22318334Speter socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE, 22418334Speter pool); 22518334Speter socket_name = APR_ARRAY_IDX(socket_details, 0, const char *); 22618334Speter } 22718334Speter else 22818334Speter { 22918334Speter const char *homedir = svn_user_get_homedir(pool); 23018334Speter 23118334Speter if (!homedir) 23218334Speter return SVN_NO_ERROR; 23318334Speter 23418334Speter socket_name = svn_dirent_join_many(pool, homedir, ".gnupg", 23518334Speter "S.gpg-agent", NULL); 23618334Speter } 23718334Speter 23818334Speter if (socket_name != NULL) 23918334Speter { 24018334Speter struct sockaddr_un addr; 24118334Speter 24218334Speter addr.sun_family = AF_UNIX; 24318334Speter strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1); 24418334Speter addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; 24518334Speter 24618334Speter sd = socket(AF_UNIX, SOCK_STREAM, 0); 24718334Speter if (sd == -1) 24818334Speter return SVN_NO_ERROR; 24918334Speter 25018334Speter if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) 25118334Speter { 25218334Speter close(sd); 25318334Speter return SVN_NO_ERROR; 25418334Speter } 25518334Speter } 25618334Speter else 25718334Speter return SVN_NO_ERROR; 25818334Speter 25918334Speter /* Receive the connection status from the gpg-agent daemon. */ 26018334Speter buffer = apr_palloc(pool, BUFFER_SIZE); 26118334Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 26218334Speter { 26318334Speter bye_gpg_agent(sd); 26418334Speter return SVN_NO_ERROR; 26518334Speter } 26618334Speter 26718334Speter if (strncmp(buffer, "OK", 2) != 0) 26818334Speter { 26918334Speter bye_gpg_agent(sd); 27018334Speter return SVN_NO_ERROR; 27118334Speter } 27218334Speter 27318334Speter /* The GPG-Agent documentation says: 27418334Speter * "Clients should deny to access an agent with a socket name which does 27518334Speter * not match its own configuration". */ 27618334Speter request = "GETINFO socket_name\n"; 27718334Speter if (write(sd, request, strlen(request)) == -1) 27818334Speter { 27918334Speter bye_gpg_agent(sd); 28018334Speter return SVN_NO_ERROR; 28118334Speter } 28218334Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 28318334Speter { 28418334Speter bye_gpg_agent(sd); 28518334Speter return SVN_NO_ERROR; 28618334Speter } 28718334Speter if (strncmp(buffer, "D", 1) == 0) 28818334Speter p = &buffer[2]; 28918334Speter if (!p) 29018334Speter { 29118334Speter bye_gpg_agent(sd); 29218334Speter return SVN_NO_ERROR; 29318334Speter } 29418334Speter ep = strchr(p, '\n'); 29518334Speter if (ep != NULL) 29618334Speter *ep = '\0'; 29718334Speter if (strcmp(socket_name, p) != 0) 29818334Speter { 29918334Speter bye_gpg_agent(sd); 30018334Speter return SVN_NO_ERROR; 30118334Speter } 30218334Speter /* The agent will terminate its response with "OK". */ 30318334Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 30418334Speter { 30518334Speter bye_gpg_agent(sd); 30618334Speter return SVN_NO_ERROR; 30718334Speter } 30818334Speter if (strncmp(buffer, "OK", 2) != 0) 30918334Speter { 31018334Speter bye_gpg_agent(sd); 31118334Speter return SVN_NO_ERROR; 31218334Speter } 31318334Speter 31418334Speter *new_sd = sd; 31518334Speter return SVN_NO_ERROR; 31618334Speter} 31718334Speter 31818334Speterstatic svn_boolean_t 31918334Spetersend_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool) 32018334Speter{ 32118334Speter const char *tty_name; 32218334Speter const char *tty_type; 32318334Speter const char *lc_ctype; 32418334Speter const char *display; 32518334Speter 32618334Speter /* Send TTY_NAME to the gpg-agent daemon. */ 32718334Speter tty_name = getenv("GPG_TTY"); 32818334Speter if (tty_name != NULL) 32918334Speter { 33018334Speter if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool)) 33118334Speter return FALSE; 33218334Speter } 33318334Speter 33418334Speter /* Send TTY_TYPE to the gpg-agent daemon. */ 33518334Speter tty_type = getenv("TERM"); 33618334Speter if (tty_type != NULL) 33718334Speter { 33818334Speter if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool)) 33918334Speter return FALSE; 34018334Speter } 34118334Speter 34218334Speter /* Compute LC_CTYPE. */ 34318334Speter lc_ctype = getenv("LC_ALL"); 34418334Speter if (lc_ctype == NULL) 34518334Speter lc_ctype = getenv("LC_CTYPE"); 34618334Speter if (lc_ctype == NULL) 34718334Speter lc_ctype = getenv("LANG"); 34818334Speter 34918334Speter /* Send LC_CTYPE to the gpg-agent daemon. */ 35018334Speter if (lc_ctype != NULL) 35118334Speter { 35218334Speter if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool)) 35318334Speter return FALSE; 35418334Speter } 35518334Speter 35618334Speter /* Send DISPLAY to the gpg-agent daemon. */ 35718334Speter display = getenv("DISPLAY"); 35818334Speter if (display != NULL) 35918334Speter { 36018334Speter if (!send_option(sd, buf, n, "display", display, scratch_pool)) 36118334Speter return FALSE; 36218334Speter } 36318334Speter 36418334Speter return TRUE; 36518334Speter} 36618334Speter 36718334Speter/* Implementation of svn_auth__password_get_t that retrieves the password 36818334Speter from gpg-agent */ 36918334Speterstatic svn_error_t * 37018334Speterpassword_get_gpg_agent(svn_boolean_t *done, 37118334Speter const char **password, 37218334Speter apr_hash_t *creds, 37318334Speter const char *realmstring, 37418334Speter const char *username, 37518334Speter apr_hash_t *parameters, 37618334Speter svn_boolean_t non_interactive, 37718334Speter apr_pool_t *pool) 37818334Speter{ 37918334Speter int sd; 38018334Speter const char *p = NULL; 38118334Speter char *ep = NULL; 38218334Speter char *buffer; 38318334Speter const char *request = NULL; 38418334Speter const char *cache_id = NULL; 38518334Speter char *password_prompt; 38618334Speter char *realm_prompt; 38718334Speter char *error_prompt; 38818334Speter int *attempt; 38918334Speter 39018334Speter *done = FALSE; 39118334Speter 39218334Speter attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER); 39318334Speter 39418334Speter SVN_ERR(find_running_gpg_agent(&sd, pool)); 39518334Speter if (sd == -1) 39618334Speter return SVN_NO_ERROR; 39718334Speter 39818334Speter buffer = apr_palloc(pool, BUFFER_SIZE); 39918334Speter 40018334Speter if (!send_options(sd, buffer, BUFFER_SIZE, pool)) 40118334Speter { 40218334Speter bye_gpg_agent(sd); 40318334Speter return SVN_NO_ERROR; 40418334Speter } 40518334Speter 40618334Speter SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool)); 40718334Speter 40818334Speter password_prompt = apr_psprintf(pool, _("Password for '%s': "), username); 40918334Speter realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"), 41018334Speter realmstring); 41118334Speter if (*attempt == 1) 41218334Speter /* X means no error to the gpg-agent protocol */ 41318334Speter error_prompt = apr_pstrdup(pool, "X"); 41418334Speter else 41518334Speter error_prompt = apr_pstrdup(pool, _("Authentication failed")); 41618334Speter 41718334Speter request = apr_psprintf(pool, 41818334Speter "GET_PASSPHRASE --data %s" 41918334Speter "%s %s %s %s\n", 42018334Speter non_interactive ? "--no-ask " : "", 42118334Speter cache_id, 42218334Speter escape_blanks(error_prompt), 42318334Speter escape_blanks(password_prompt), 42418334Speter escape_blanks(realm_prompt)); 42518334Speter 42618334Speter if (write(sd, request, strlen(request)) == -1) 42718334Speter { 42818334Speter bye_gpg_agent(sd); 42918334Speter return SVN_NO_ERROR; 43018334Speter } 43118334Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 43218334Speter { 43318334Speter bye_gpg_agent(sd); 43418334Speter return SVN_NO_ERROR; 43518334Speter } 43618334Speter 43718334Speter bye_gpg_agent(sd); 43818334Speter 43918334Speter if (strncmp(buffer, "ERR", 3) == 0) 44018334Speter return SVN_NO_ERROR; 44118334Speter 44218334Speter p = NULL; 44318334Speter if (strncmp(buffer, "D", 1) == 0) 44418334Speter p = &buffer[2]; 44518334Speter 44618334Speter if (!p) 44718334Speter return SVN_NO_ERROR; 44818334Speter 44918334Speter ep = strchr(p, '\n'); 45018334Speter if (ep != NULL) 45118334Speter *ep = '\0'; 45218334Speter 45318334Speter *password = p; 45418334Speter 45518334Speter *done = TRUE; 45618334Speter return SVN_NO_ERROR; 45718334Speter} 45818334Speter 45918334Speter 46018334Speter/* Implementation of svn_auth__password_set_t that would store the 46118334Speter password in GPG Agent if that's how this particular integration 46218334Speter worked. But it isn't. GPG Agent stores the password provided by 46318334Speter the user via the pinentry program immediately upon its provision 46418334Speter (and regardless of its accuracy as passwords go), so we just need 46518334Speter to check if a running GPG Agent exists. */ 46618334Speterstatic svn_error_t * 46718334Speterpassword_set_gpg_agent(svn_boolean_t *done, 46818334Speter apr_hash_t *creds, 46918334Speter const char *realmstring, 47018334Speter const char *username, 47118334Speter const char *password, 47218334Speter apr_hash_t *parameters, 47318334Speter svn_boolean_t non_interactive, 47418334Speter apr_pool_t *pool) 47518334Speter{ 47618334Speter int sd; 47718334Speter 47818334Speter *done = FALSE; 47918334Speter 48018334Speter SVN_ERR(find_running_gpg_agent(&sd, pool)); 48118334Speter if (sd == -1) 48218334Speter return SVN_NO_ERROR; 48318334Speter 48418334Speter bye_gpg_agent(sd); 48518334Speter *done = TRUE; 48618334Speter 48718334Speter return SVN_NO_ERROR; 48818334Speter} 48918334Speter 49018334Speter 49118334Speter/* An implementation of svn_auth_provider_t::first_credentials() */ 49218334Speterstatic svn_error_t * 49318334Spetersimple_gpg_agent_first_creds(void **credentials, 49418334Speter void **iter_baton, 49518334Speter void *provider_baton, 49618334Speter apr_hash_t *parameters, 49718334Speter const char *realmstring, 49818334Speter apr_pool_t *pool) 49918334Speter{ 50018334Speter svn_error_t *err; 50118334Speter int *attempt = apr_palloc(pool, sizeof(*attempt)); 50218334Speter 50318334Speter *attempt = 1; 50418334Speter svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt); 50518334Speter err = svn_auth__simple_creds_cache_get(credentials, iter_baton, 50618334Speter provider_baton, parameters, 50718334Speter realmstring, password_get_gpg_agent, 50818334Speter SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 50918334Speter pool); 51018334Speter *iter_baton = attempt; 51118334Speter 51218334Speter return err; 51318334Speter} 51418334Speter 51518334Speter/* An implementation of svn_auth_provider_t::next_credentials() */ 51618334Speterstatic svn_error_t * 51718334Spetersimple_gpg_agent_next_creds(void **credentials, 51818334Speter void *iter_baton, 51918334Speter void *provider_baton, 52018334Speter apr_hash_t *parameters, 52118334Speter const char *realmstring, 52218334Speter apr_pool_t *pool) 52318334Speter{ 52418334Speter int *attempt = (int *)iter_baton; 52518334Speter int sd; 52618334Speter char *buffer; 52718334Speter const char *cache_id = NULL; 52818334Speter const char *request = NULL; 52918334Speter 53018334Speter *credentials = NULL; 53118334Speter 53218334Speter /* The users previous credentials failed so first remove the cached entry, 53318334Speter * before trying to retrieve them again. Because gpg-agent stores cached 53418334Speter * credentials immediately upon retrieving them, this gives us the 53518334Speter * opportunity to remove the invalid credentials and prompt the 53618334Speter * user again. While it's possible that server side issues could trigger 53718334Speter * this, this cache is ephemeral so at worst we're just speeding up 53818334Speter * when the user would need to re-enter their password. */ 53918334Speter 54018334Speter if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE)) 54118334Speter { 54218334Speter /* In this case since we're running non-interactively we do not 54318334Speter * want to clear the cache since the user was never prompted by 54418334Speter * gpg-agent to set a password. */ 54518334Speter return SVN_NO_ERROR; 54618334Speter } 54718334Speter 54818334Speter *attempt = *attempt + 1; 54918334Speter 55018334Speter SVN_ERR(find_running_gpg_agent(&sd, pool)); 55118334Speter if (sd == -1) 55218334Speter return SVN_NO_ERROR; 55318334Speter 55418334Speter buffer = apr_palloc(pool, BUFFER_SIZE); 55518334Speter 55618334Speter if (!send_options(sd, buffer, BUFFER_SIZE, pool)) 55718334Speter { 55818334Speter bye_gpg_agent(sd); 55918334Speter return SVN_NO_ERROR; 56018334Speter } 56118334Speter 56218334Speter SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool)); 56318334Speter 56418334Speter request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id); 56518334Speter 56618334Speter if (write(sd, request, strlen(request)) == -1) 56718334Speter { 56818334Speter bye_gpg_agent(sd); 56918334Speter return SVN_NO_ERROR; 57018334Speter } 57118334Speter 57218334Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 57318334Speter { 57418334Speter bye_gpg_agent(sd); 57518334Speter return SVN_NO_ERROR; 57618334Speter } 57718334Speter 57818334Speter if (strncmp(buffer, "OK\n", 3) != 0) 57918334Speter { 58018334Speter bye_gpg_agent(sd); 58118334Speter return SVN_NO_ERROR; 58218334Speter } 58318334Speter 58418334Speter /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries) 58518334Speter * which matches svn command line client's retry_limit as set in 58618334Speter * svn_cmdline_create_auth_baton(). It would be nice to have that 58718334Speter * limit reflected here but that violates the boundry between the 58818334Speter * prompt provider and the cache provider. gpg-agent is acting as 58918334Speter * both here due to the peculiarties of their design so we'll have to 59018334Speter * live with this for now. Note that when these failures get exceeded 59118334Speter * it'll eventually fall back on the retry limits of whatever prompt 59218334Speter * provider is in effect, so this effectively doubles the limit. */ 59318334Speter if (*attempt < 4) 59418334Speter return svn_auth__simple_creds_cache_get(credentials, &iter_baton, 59518334Speter provider_baton, parameters, 59618334Speter realmstring, 59718334Speter password_get_gpg_agent, 59818334Speter SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 59918334Speter pool); 60018334Speter 60118334Speter return SVN_NO_ERROR; 60218334Speter} 60318334Speter 60418334Speter 60518334Speter/* An implementation of svn_auth_provider_t::save_credentials() */ 60618334Speterstatic svn_error_t * 60718334Spetersimple_gpg_agent_save_creds(svn_boolean_t *saved, 60818334Speter void *credentials, 60918334Speter void *provider_baton, 61018334Speter apr_hash_t *parameters, 61118334Speter const char *realmstring, 61218334Speter apr_pool_t *pool) 61318334Speter{ 61418334Speter return svn_auth__simple_creds_cache_set(saved, credentials, 61518334Speter provider_baton, parameters, 61618334Speter realmstring, password_set_gpg_agent, 61718334Speter SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 61818334Speter pool); 61918334Speter} 62018334Speter 62118334Speter 62218334Speterstatic const svn_auth_provider_t gpg_agent_simple_provider = { 62318334Speter SVN_AUTH_CRED_SIMPLE, 62418334Speter simple_gpg_agent_first_creds, 62518334Speter simple_gpg_agent_next_creds, 62618334Speter simple_gpg_agent_save_creds 62718334Speter}; 62818334Speter 62918334Speter 63018334Speter/* Public API */ 63118334Spetervoid 63218334Spetersvn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider, 63318334Speter apr_pool_t *pool) 63418334Speter{ 63518334Speter svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); 63618334Speter 63718334Speter po->vtable = &gpg_agent_simple_provider; 63818334Speter *provider = po; 63918334Speter} 64018334Speter 64118334Speter#endif /* SVN_HAVE_GPG_AGENT */ 64218334Speter#endif /* !WIN32 */ 64318334Speter