Author: breser
Date: Thu Jun  5 23:18:16 2014
New Revision: 1600790

URL: http://svn.apache.org/r1600790
Log:
On 1.8.x-gpg-agent branch: Merge r1600331, r1600348, 1600368, r1600563, 
r1600781 from trunk.

SVN_VA_NULL changed to just NULL since that macro is not available in 1.8.x

Modified:
    subversion/branches/1.8.x-gpg-agent/   (props changed)
    subversion/branches/1.8.x-gpg-agent/subversion/libsvn_subr/gpg_agent.c

Propchange: subversion/branches/1.8.x-gpg-agent/
------------------------------------------------------------------------------
  Merged /subversion/trunk:r1600331,1600348,1600368,1600563,1600781

Modified: subversion/branches/1.8.x-gpg-agent/subversion/libsvn_subr/gpg_agent.c
URL: 
http://svn.apache.org/viewvc/subversion/branches/1.8.x-gpg-agent/subversion/libsvn_subr/gpg_agent.c?rev=1600790&r1=1600789&r2=1600790&view=diff
==============================================================================
--- subversion/branches/1.8.x-gpg-agent/subversion/libsvn_subr/gpg_agent.c 
(original)
+++ subversion/branches/1.8.x-gpg-agent/subversion/libsvn_subr/gpg_agent.c Thu 
Jun  5 23:18:16 2014
@@ -72,6 +72,9 @@
 #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"
 
 #include "private/svn_auth_private.h"
 
@@ -80,6 +83,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. */
@@ -98,6 +102,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.
@@ -156,6 +178,17 @@ send_option(int sd, char *buf, size_t n,
   return (strncmp(buf, "OK", 2) == 0);
 }
 
+/* Send the BYE command and disconnect from the gpg-agent.  Doing this avoids
+ * gpg-agent emitting a "Connection reset by peer" log message with some
+ * versions of gpg-agent. */
+static void
+bye_gpg_agent(int sd)
+{
+  /* don't bother to check the result of the write, it either worked or it
+   * didn't, but either way we're closing. */
+  write(sd, "BYE\n", 4);
+  close(sd);
+}
 
 /* Locate a running GPG Agent, and return an open file descriptor
  * for communication with the agent in *NEW_SD. If no running agent
@@ -173,17 +206,34 @@ find_running_gpg_agent(int *new_sd, apr_
 
   *new_sd = -1;
 
+  /* This implements the method of finding the socket as described in
+   * the gpg-agent man page under the --use-standard-socket option.
+   * The manage page misleadingly says the standard socket is 
+   * "named 'S.gpg-agent' located in the home directory."  The standard
+   * socket path is actually in the .gnupg directory in the home directory,
+   * i.e. ~/.gnupg/S.gpg-agent */
   gpg_agent_info = getenv("GPG_AGENT_INFO");
   if (gpg_agent_info != NULL)
     {
       apr_array_header_t *socket_details;
 
+      /* For reference GPG_AGENT_INFO consists of 3 : separated fields.
+       * The path to the socket, the pid of the gpg-agent process and 
+       * finally the version of the protocol the agent talks. */
       socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
                                          pool);
       socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
     }
   else
-    return SVN_NO_ERROR;
+    {
+      const char *homedir = svn_user_get_homedir(pool);
+
+      if (!homedir)
+        return SVN_NO_ERROR;
+
+      socket_name = svn_dirent_join_many(pool, homedir, ".gnupg",
+                                         "S.gpg-agent", NULL);
+    }
 
   if (socket_name != NULL)
     {
@@ -210,13 +260,13 @@ find_running_gpg_agent(int *new_sd, apr_
   buffer = apr_palloc(pool, BUFFER_SIZE);
   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
 
   if (strncmp(buffer, "OK", 2) != 0)
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
 
@@ -226,19 +276,19 @@ find_running_gpg_agent(int *new_sd, apr_
   request = "GETINFO socket_name\n";
   if (write(sd, request, strlen(request)) == -1)
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
   if (strncmp(buffer, "D", 1) == 0)
     p = &buffer[2];
   if (!p)
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
   ep = strchr(p, '\n');
@@ -246,18 +296,18 @@ find_running_gpg_agent(int *new_sd, apr_
     *ep = '\0';
   if (strcmp(socket_name, p) != 0)
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
   /* The agent will terminate its response with "OK". */
   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
   if (strncmp(buffer, "OK", 2) != 0)
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
 
@@ -265,60 +315,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))
-        {
-          close(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))
-        {
-          close(sd);
-          return SVN_NO_ERROR;
-        }
+      if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool))
+        return FALSE;
     }
 
   /* Compute LC_CTYPE. */
@@ -331,53 +349,92 @@ 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))
-        {
-          close(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))
-        {
-          close(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));
 
   if (write(sd, request, strlen(request)) == -1)
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
   if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
     {
-      close(sd);
+      bye_gpg_agent(sd);
       return SVN_NO_ERROR;
     }
 
-  close(sd);
+  bye_gpg_agent(sd);
 
   if (strncmp(buffer, "ERR", 3) == 0)
     return SVN_NO_ERROR;
@@ -424,7 +481,7 @@ password_set_gpg_agent(svn_boolean_t *do
   if (sd == -1)
     return SVN_NO_ERROR;
 
-  close(sd);
+  bye_gpg_agent(sd);
   *done = TRUE;
 
   return SVN_NO_ERROR;
@@ -440,11 +497,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;
 }
 
 
@@ -468,7 +622,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
 };
 


Reply via email to