Author: breser
Date: Thu Jun 5 22:15:55 2014
New Revision: 1600781
URL: http://svn.apache.org/r1600781
Log:
Make the gpg-agent pinentry not ask for confirmation of password entries and
make it re-prompt if the password is incorrect.
* subversion/libsvn_subr/gpg_agent.c:
(ATTEMPT_PARAMETER): New macro.
(send_options, get_cache_id): New functions with code taken out of
password_get_gpg_agent() so it can be reused.
(password_get_gpg_agent): Use send_options() and get_cache_id(),
retrieve the attempt from the parameters and use it to determine
if we should set an error message that will be displayed in pinentry.
(simple_gpg_agent_first_creds): Set an iter_baton so we can limit the
retries, put the iter_baton in the parameters so password_get_gpg_agent()
can access it.
(simple_gpg_agent_next_creds): New function, removes the cached password
and prompts the user again.
(gpg_agent_simple_provider): Add simple_gpg_agent_next_creds callback.
Modified:
subversion/trunk/subversion/libsvn_subr/gpg_agent.c
Modified: subversion/trunk/subversion/libsvn_subr/gpg_agent.c
URL:
http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/gpg_agent.c?rev=1600781&r1=1600780&r2=1600781&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/gpg_agent.c (original)
+++ subversion/trunk/subversion/libsvn_subr/gpg_agent.c Thu Jun 5 22:15:55 2014
@@ -72,6 +72,7 @@
#include "svn_cmdline.h"
#include "svn_checksum.h"
#include "svn_string.h"
+#include "svn_hash.h"
#include "svn_user.h"
#include "svn_dirent_uri.h"
@@ -83,6 +84,7 @@
#ifdef SVN_HAVE_GPG_AGENT
#define BUFFER_SIZE 1024
+#define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt"
/* Modify STR in-place such that blanks are escaped as required by the
* gpg-agent protocol. Return a pointer to STR. */
@@ -101,6 +103,24 @@ escape_blanks(char *str)
return str;
}
+/* Generate the string CACHE_ID_P based on the REALMSTRING allocated in
+ * RESULT_POOL using SCRATCH_POOL for temporary allocations. This is similar
+ * to other password caching mechanisms. */
+static svn_error_t *
+get_cache_id(const char **cache_id_p, const char *realmstring,
+ apr_pool_t *scratch_pool, apr_pool_t *result_pool)
+{
+ const char *cache_id = NULL;
+ svn_checksum_t *digest = NULL;
+
+ SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
+ strlen(realmstring), scratch_pool));
+ cache_id = svn_checksum_to_cstring(digest, result_pool);
+ *cache_id_p = cache_id;
+
+ return SVN_NO_ERROR;
+}
+
/* Attempt to read a gpg-agent response message from the socket SD into
* buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
* message could be read that fits into the buffer. Else return FALSE.
@@ -296,60 +316,28 @@ find_running_gpg_agent(int *new_sd, apr_
return SVN_NO_ERROR;
}
-/* Implementation of svn_auth__password_get_t that retrieves the password
- from gpg-agent */
-static svn_error_t *
-password_get_gpg_agent(svn_boolean_t *done,
- const char **password,
- apr_hash_t *creds,
- const char *realmstring,
- const char *username,
- apr_hash_t *parameters,
- svn_boolean_t non_interactive,
- apr_pool_t *pool)
+static svn_boolean_t
+send_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool)
{
- int sd;
- const char *p = NULL;
- char *ep = NULL;
- char *buffer;
- const char *request = NULL;
- const char *cache_id = NULL;
const char *tty_name;
const char *tty_type;
const char *lc_ctype;
const char *display;
- svn_checksum_t *digest = NULL;
- char *password_prompt;
- char *realm_prompt;
-
- *done = FALSE;
-
- SVN_ERR(find_running_gpg_agent(&sd, pool));
- if (sd == -1)
- return SVN_NO_ERROR;
-
- buffer = apr_palloc(pool, BUFFER_SIZE);
/* Send TTY_NAME to the gpg-agent daemon. */
tty_name = getenv("GPG_TTY");
if (tty_name != NULL)
{
- if (!send_option(sd, buffer, BUFFER_SIZE, "ttyname", tty_name, pool))
- {
- bye_gpg_agent(sd);
- return SVN_NO_ERROR;
- }
+ if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool))
+ return FALSE;
}
/* Send TTY_TYPE to the gpg-agent daemon. */
tty_type = getenv("TERM");
if (tty_type != NULL)
{
- if (!send_option(sd, buffer, BUFFER_SIZE, "ttytype", tty_type, pool))
- {
- bye_gpg_agent(sd);
- return SVN_NO_ERROR;
- }
+ if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool))
+ return FALSE;
}
/* Compute LC_CTYPE. */
@@ -362,38 +350,77 @@ password_get_gpg_agent(svn_boolean_t *do
/* Send LC_CTYPE to the gpg-agent daemon. */
if (lc_ctype != NULL)
{
- if (!send_option(sd, buffer, BUFFER_SIZE, "lc-ctype", lc_ctype, pool))
- {
- bye_gpg_agent(sd);
- return SVN_NO_ERROR;
- }
+ if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool))
+ return FALSE;
}
/* Send DISPLAY to the gpg-agent daemon. */
display = getenv("DISPLAY");
if (display != NULL)
{
- if (!send_option(sd, buffer, BUFFER_SIZE, "display", display, pool))
- {
- bye_gpg_agent(sd);
- return SVN_NO_ERROR;
- }
+ if (!send_option(sd, buf, n, "display", display, scratch_pool))
+ return FALSE;
}
- /* Create the CACHE_ID which will be generated based on REALMSTRING similar
- to other password caching mechanisms. */
- SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
- strlen(realmstring), pool));
- cache_id = svn_checksum_to_cstring(digest, pool);
+ return TRUE;
+}
+
+/* Implementation of svn_auth__password_get_t that retrieves the password
+ from gpg-agent */
+static svn_error_t *
+password_get_gpg_agent(svn_boolean_t *done,
+ const char **password,
+ apr_hash_t *creds,
+ const char *realmstring,
+ const char *username,
+ apr_hash_t *parameters,
+ svn_boolean_t non_interactive,
+ apr_pool_t *pool)
+{
+ int sd;
+ const char *p = NULL;
+ char *ep = NULL;
+ char *buffer;
+ const char *request = NULL;
+ const char *cache_id = NULL;
+ char *password_prompt;
+ char *realm_prompt;
+ char *error_prompt;
+ int *attempt;
+
+ *done = FALSE;
+
+ attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER);
+
+ SVN_ERR(find_running_gpg_agent(&sd, pool));
+ if (sd == -1)
+ return SVN_NO_ERROR;
+
+ buffer = apr_palloc(pool, BUFFER_SIZE);
+
+ if (!send_options(sd, buffer, BUFFER_SIZE, pool))
+ {
+ bye_gpg_agent(sd);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
realmstring);
+ if (*attempt == 1)
+ /* X means no error to the gpg-agent protocol */
+ error_prompt = apr_pstrdup(pool, "X");
+ else
+ error_prompt = apr_pstrdup(pool, _("Authentication failed"));
+
request = apr_psprintf(pool,
- "GET_PASSPHRASE --data %s--repeat=1 "
- "%s X %s %s\n",
+ "GET_PASSPHRASE --data %s"
+ "%s %s %s %s\n",
non_interactive ? "--no-ask " : "",
cache_id,
+ escape_blanks(error_prompt),
escape_blanks(password_prompt),
escape_blanks(realm_prompt));
@@ -471,11 +498,108 @@ simple_gpg_agent_first_creds(void **cred
const char *realmstring,
apr_pool_t *pool)
{
- return svn_auth__simple_creds_cache_get(credentials, iter_baton,
- provider_baton, parameters,
- realmstring, password_get_gpg_agent,
- SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
- pool);
+ svn_error_t *err;
+ int *attempt = apr_palloc(pool, sizeof(*attempt));
+
+ *attempt = 1;
+ svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt);
+ err = svn_auth__simple_creds_cache_get(credentials, iter_baton,
+ provider_baton, parameters,
+ realmstring, password_get_gpg_agent,
+ SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
+ pool);
+ *iter_baton = attempt;
+
+ return err;
+}
+
+/* An implementation of svn_auth_provider_t::next_credentials() */
+static svn_error_t *
+simple_gpg_agent_next_creds(void **credentials,
+ void *iter_baton,
+ void *provider_baton,
+ apr_hash_t *parameters,
+ const char *realmstring,
+ apr_pool_t *pool)
+{
+ int *attempt = (int *)iter_baton;
+ int sd;
+ char *buffer;
+ const char *cache_id = NULL;
+ const char *request = NULL;
+
+ *credentials = NULL;
+
+ /* The users previous credentials failed so first remove the cached entry,
+ * before trying to retrieve them again. Because gpg-agent stores cached
+ * credentials immediately upon retrieving them, this gives us the
+ * opportunity to remove the invalid credentials and prompt the
+ * user again. While it's possible that server side issues could trigger
+ * this, this cache is ephemeral so at worst we're just speeding up
+ * when the user would need to re-enter their password. */
+
+ if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE))
+ {
+ /* In this case since we're running non-interactively we do not
+ * want to clear the cache since the user was never prompted by
+ * gpg-agent to set a password. */
+ return SVN_NO_ERROR;
+ }
+
+ *attempt = *attempt + 1;
+
+ SVN_ERR(find_running_gpg_agent(&sd, pool));
+ if (sd == -1)
+ return SVN_NO_ERROR;
+
+ buffer = apr_palloc(pool, BUFFER_SIZE);
+
+ if (!send_options(sd, buffer, BUFFER_SIZE, pool))
+ {
+ bye_gpg_agent(sd);
+ return SVN_NO_ERROR;
+ }
+
+ SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
+
+ request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id);
+
+ if (write(sd, request, strlen(request)) == -1)
+ {
+ bye_gpg_agent(sd);
+ return SVN_NO_ERROR;
+ }
+
+ if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
+ {
+ bye_gpg_agent(sd);
+ return SVN_NO_ERROR;
+ }
+
+ if (strncmp(buffer, "OK\n", 3) != 0)
+ {
+ bye_gpg_agent(sd);
+ return SVN_NO_ERROR;
+ }
+
+ /* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries)
+ * which matches svn command line client's retry_limit as set in
+ * svn_cmdline_create_auth_baton(). It would be nice to have that
+ * limit reflected here but that violates the boundry between the
+ * prompt provider and the cache provider. gpg-agent is acting as
+ * both here due to the peculiarties of their design so we'll have to
+ * live with this for now. Note that when these failures get exceeded
+ * it'll eventually fall back on the retry limits of whatever prompt
+ * provider is in effect, so this effectively doubles the limit. */
+ if (*attempt < 4)
+ return svn_auth__simple_creds_cache_get(credentials, &iter_baton,
+ provider_baton, parameters,
+ realmstring,
+ password_get_gpg_agent,
+ SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
+ pool);
+
+ return SVN_NO_ERROR;
}
@@ -499,7 +623,7 @@ simple_gpg_agent_save_creds(svn_boolean_
static const svn_auth_provider_t gpg_agent_simple_provider = {
SVN_AUTH_CRED_SIMPLE,
simple_gpg_agent_first_creds,
- NULL,
+ simple_gpg_agent_next_creds,
simple_gpg_agent_save_creds
};