common: Rework the simple password query module.
authorJustus Winter <justus@g10code.com>
Thu, 11 Aug 2016 10:26:09 +0000 (12:26 +0200)
committerJustus Winter <justus@g10code.com>
Thu, 11 Aug 2016 10:49:30 +0000 (12:49 +0200)
* common/simple-pwquery.c (writen, readline): Drop.
(agent_send_option, agent_send_all_options, agent_open): Just use
libassuan.
(simple_pw_set_socket): Simplify.
(default_inq_cb): New function.
(simple_pwquery, simple_query): Just use libassuan.
* agent/Makefile.am (gpg_preset_passphrase_LDADD): Add libassuan.
* tools/Makefile.am (symcryptrun_LDADD): Likewise.

Signed-off-by: Justus Winter <justus@g10code.com>
agent/Makefile.am
common/simple-pwquery.c
tools/Makefile.am

index 4be9090..1970088 100644 (file)
@@ -85,7 +85,7 @@ gpg_preset_passphrase_SOURCES = \
 
 # Needs $(NETLIBS) for libsimple-pwquery.la.
 gpg_preset_passphrase_LDADD = \
-         $(pwquery_libs) $(common_libs) \
+         $(pwquery_libs) $(common_libs) $(LIBASSUAN_LIBS) \
         $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV)
 
 
index bd40fdf..240451b 100644 (file)
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
-/* This module is intended as a standalone client implementation to
-   gpg-agent's GET_PASSPHRASE command.  In particular it does not use
-   the Assuan library and can only cope with an already running
-   gpg-agent.  Some stuff is configurable in the header file. */
+/* This module is intended as a simple client implementation to
+   gpg-agent's GET_PASSPHRASE command.  It can only cope with an
+   already running gpg-agent.  Some stuff is configurable in the
+   header file. */
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
@@ -30,6 +30,7 @@
 #include <string.h>
 #include <errno.h>
 #include <unistd.h>
+#include <assuan.h>
 #ifdef HAVE_W32_SYSTEM
 #include <winsock2.h>
 #else
@@ -42,9 +43,8 @@
 
 #define GNUPG_COMMON_NEED_AFLOCAL
 #include "../common/mischelp.h"
-#ifdef HAVE_W32_SYSTEM
-#include "../common/w32-afunix.h"
-#endif
+#include "sysutils.h"
+#include "membuf.h"
 
 
 #define SIMPLE_PWQUERY_IMPLEMENTATION 1
@@ -96,88 +96,11 @@ my_stpcpy(char *a,const char *b)
 #endif
 
 
-
-/* Write NBYTES of BUF to file descriptor FD. */
-static int
-writen (int fd, const void *buf, size_t nbytes)
-{
-  size_t nleft = nbytes;
-  int nwritten;
-
-  while (nleft > 0)
-    {
-#ifdef HAVE_W32_SYSTEM
-      nwritten = send (fd, buf, nleft, 0);
-#else
-      nwritten = write (fd, buf, nleft);
-#endif
-      if (nwritten < 0)
-        {
-          if (errno == EINTR)
-            nwritten = 0;
-          else {
-#ifdef SPWQ_USE_LOGGING
-            log_error ("write failed: %s\n", strerror (errno));
-#endif
-            return SPWQ_IO_ERROR;
-          }
-        }
-      nleft -= nwritten;
-      buf = (const char*)buf + nwritten;
-    }
-
-  return 0;
-}
-
-
-/* Read an entire line and return number of bytes read. */
-static int
-readline (int fd, char *buf, size_t buflen)
-{
-  size_t nleft = buflen;
-  char *p;
-  int nread = 0;
-
-  while (nleft > 0)
-    {
-#ifdef HAVE_W32_SYSTEM
-      int n = recv (fd, buf, nleft, 0);
-#else
-      int n = read (fd, buf, nleft);
-#endif
-      if (n < 0)
-        {
-          if (errno == EINTR)
-            continue;
-          return -(SPWQ_IO_ERROR);
-        }
-      else if (!n)
-        {
-          return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */
-        }
-      p = buf;
-      nleft -= n;
-      buf += n;
-      nread += n;
-
-      for (; n && *p != '\n'; n--, p++)
-        ;
-      if (n)
-        {
-          break; /* At least one full line available - that's enough.
-                    This function is just a simple implementation, so
-                    it is okay to forget about pending bytes.  */
-        }
-    }
-
-  return nread;
-}
-
-
 /* Send an option to the agent */
 static int
-agent_send_option (int fd, const char *name, const char *value)
+agent_send_option (assuan_context_t ctx, const char *name, const char *value)
 {
+  int err;
   char buf[200];
   int nread;
   char *line;
@@ -188,28 +111,17 @@ agent_send_option (int fd, const char *name, const char *value)
     return SPWQ_OUT_OF_CORE;
   strcpy (stpcpy (stpcpy (stpcpy (
                      stpcpy (line, "OPTION "), name), "="), value), "\n");
-  i = writen (fd, line, strlen (line));
-  spwq_free (line);
-  if (i)
-    return i;
-
-  /* get response */
-  nread = readline (fd, buf, DIM(buf)-1);
-  if (nread < 0)
-    return -nread;
-  if (nread < 3)
-    return SPWQ_PROTOCOL_ERROR;
 
-  if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n'))
-    return 0; /* okay */
+  err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
 
-  return SPWQ_ERR_RESPONSE;
+  spwq_free (line);
+  return err;
 }
 
 
 /* Send all available options to the agent. */
 static int
-agent_send_all_options (int fd)
+agent_send_all_options (assuan_context_t ctx)
 {
   char *dft_display = NULL;
   char *dft_ttyname = NULL;
@@ -221,7 +133,7 @@ agent_send_all_options (int fd)
   dft_display = getenv ("DISPLAY");
   if (dft_display)
     {
-      if ((rc = agent_send_option (fd, "display", dft_display)))
+      if ((rc = agent_send_option (ctx, "display", dft_display)))
         return rc;
     }
 
@@ -232,14 +144,14 @@ agent_send_all_options (int fd)
 #endif
   if (dft_ttyname && *dft_ttyname)
     {
-      if ((rc=agent_send_option (fd, "ttyname", dft_ttyname)))
+      if ((rc=agent_send_option (ctx, "ttyname", dft_ttyname)))
         return rc;
     }
 
   dft_ttytype = getenv ("TERM");
   if (dft_ttyname && dft_ttytype)
     {
-      if ((rc = agent_send_option (fd, "ttytype", dft_ttytype)))
+      if ((rc = agent_send_option (ctx, "ttytype", dft_ttytype)))
         return rc;
     }
 
@@ -260,7 +172,7 @@ agent_send_all_options (int fd)
       }
     dft_lc = setlocale (LC_CTYPE, "");
     if (dft_ttyname && dft_lc)
-      rc = agent_send_option (fd, "lc-ctype", dft_lc);
+      rc = agent_send_option (ctx, "lc-ctype", dft_lc);
     if (old_lc)
       {
         setlocale (LC_CTYPE, old_lc);
@@ -282,7 +194,7 @@ agent_send_all_options (int fd)
       }
     dft_lc = setlocale (LC_MESSAGES, "");
     if (dft_ttyname && dft_lc)
-      rc = agent_send_option (fd, "lc-messages", dft_lc);
+      rc = agent_send_option (ctx, "lc-messages", dft_lc);
     if (old_lc)
       {
         setlocale (LC_MESSAGES, old_lc);
@@ -300,7 +212,7 @@ agent_send_all_options (int fd)
     {
       /* We ignore errors here because older gpg-agents don't support
          this option.  */
-      agent_send_option (fd, "xauthority", dft_xauthority);
+      agent_send_option (ctx, "xauthority", dft_xauthority);
     }
 
   /* Send the PINENTRY_USER_DATA variable.  */
@@ -309,9 +221,14 @@ agent_send_all_options (int fd)
     {
       /* We ignore errors here because older gpg-agents don't support
          this option.  */
-      agent_send_option (fd, "pinentry-user-data", dft_pinentry_user_data);
+      agent_send_option (ctx, "pinentry-user-data", dft_pinentry_user_data);
     }
 
+  /* Tell the agent that we support Pinentry notifications.  No
+     error checking so that it will work with older agents.  */
+  assuan_transact (ctx, "OPTION allow-pinentry-notify",
+                   NULL, NULL, NULL, NULL, NULL, NULL);
+
   return 0;
 }
 
@@ -321,7 +238,7 @@ agent_send_all_options (int fd)
    the file descriptor for the connection.  Return -1 in case of
    error. */
 static int
-agent_open (int *rfd)
+agent_open (assuan_context_t *ctx)
 {
   int rc;
   int fd;
@@ -331,7 +248,6 @@ agent_open (int *rfd)
   char line[200];
   int nread;
 
-  *rfd = -1;
   infostr = default_gpg_agent_info;
   if ( !infostr || !*infostr )
     {
@@ -340,81 +256,35 @@ agent_open (int *rfd)
 #endif
       return SPWQ_NO_AGENT;
     }
-  p = spwq_malloc (strlen (infostr)+1);
-  if (!p)
-    return SPWQ_OUT_OF_CORE;
-  strcpy (p, infostr);
-  infostr = p;
-
-  if ( !(p = strchr ( infostr, PATHSEP_C)) || p == infostr
-       || (p-infostr)+1 >= sizeof client_addr.sun_path )
-    {
-      spwq_free (infostr);
-      return SPWQ_NO_AGENT;
-    }
-  *p++ = 0;
-
-  while (*p && *p != PATHSEP_C)
-    p++;
 
-#ifdef HAVE_W32_SYSTEM
-  fd = _w32_sock_new (AF_UNIX, SOCK_STREAM, 0);
-#else
-  fd = socket (AF_UNIX, SOCK_STREAM, 0);
-#endif
-  if (fd == -1)
-    {
-#ifdef SPWQ_USE_LOGGING
-      log_error ("can't create socket: %s\n", strerror(errno) );
-#endif
-      spwq_free (infostr);
-      return SPWQ_SYS_ERROR;
-    }
-
-  memset (&client_addr, 0, sizeof client_addr);
-  client_addr.sun_family = AF_UNIX;
-  strcpy (client_addr.sun_path, infostr);
-  spwq_free (infostr);
-  len = SUN_LEN (&client_addr);
+  rc = assuan_new (ctx);
+  if (rc)
+    return rc;
 
-#ifdef HAVE_W32_SYSTEM
-  rc = _w32_sock_connect (fd, (struct sockaddr*)&client_addr, len );
-#else
-  rc = connect (fd, (struct sockaddr*)&client_addr, len );
-#endif
-  if (rc == -1)
+  rc = assuan_socket_connect (*ctx, infostr, 0, 0);
+  if (rc)
     {
 #ifdef SPWQ_USE_LOGGING
       log_error (_("can't connect to '%s': %s\n"),
-                 client_addr.sun_path, strerror (errno));
+                 infostr, gpg_strerror (rc));
 #endif
-      close (fd );
-      return SPWQ_IO_ERROR;
+      goto errout;
     }
 
-  nread = readline (fd, line, DIM(line));
-  if (nread < 3 || !(line[0] == 'O' && line[1] == 'K'
-                     && (line[2] == '\n' || line[2] == ' ')) )
-    {
-#ifdef SPWQ_USE_LOGGING
-      log_error ( _("communication problem with gpg-agent\n"));
-#endif
-      close (fd );
-      return SPWQ_PROTOCOL_ERROR;
-    }
-
-  rc = agent_send_all_options (fd);
+  rc = agent_send_all_options (*ctx);
   if (rc)
     {
 #ifdef SPWQ_USE_LOGGING
       log_error (_("problem setting the gpg-agent options\n"));
 #endif
-      close (fd);
-      return rc;
+      goto errout;
     }
 
-  *rfd = fd;
   return 0;
+
+ errout:
+  assuan_release (*ctx);
+  *ctx = NULL;
 }
 
 
@@ -451,17 +321,37 @@ int
 simple_pw_set_socket (const char *name)
 {
   spwq_free (default_gpg_agent_info);
+  default_gpg_agent_info = NULL;
   if (name)
     {
-      default_gpg_agent_info = spwq_malloc (strlen (name) + 4 + 1);
+      default_gpg_agent_info = spwq_malloc (strlen (name) + 1);
       if (!default_gpg_agent_info)
         return SPWQ_OUT_OF_CORE;
-      /* We don't know the PID thus we use 0.  */
-      strcpy (stpcpy (default_gpg_agent_info, name),
-              PATHSEP_S "0" PATHSEP_S "1");
+      strcpy (default_gpg_agent_info, name);
+    }
+
+  return 0;
+}
+
+
+/* This is the default inquiry callback.  It merely handles the
+   Pinentry notification.  */
+static gpg_error_t
+default_inq_cb (void *opaque, const char *line)
+{
+  (void)opaque;
+
+  if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17]))
+    {
+      gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10));
+      /* We do not return errors to avoid breaking other code.  */
     }
   else
-    default_gpg_agent_info = NULL;
+    {
+#ifdef SPWQ_USE_LOGGING
+      log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
+#endif
+    }
 
   return 0;
 }
@@ -483,14 +373,15 @@ simple_pwquery (const char *cacheid,
                 int opt_check,
                 int *errorcode)
 {
-  int fd = -1;
+  assuan_context_t ctx;
+  membuf_t data;
   int nread;
   char *result = NULL;
   char *pw = NULL;
   char *p;
   int rc, i;
 
-  rc = agent_open (&fd);
+  rc = agent_open (&ctx);
   if (rc)
     goto leave;
 
@@ -530,73 +421,43 @@ simple_pwquery (const char *cacheid,
     *p++ = ' ';
     p = copy_and_escape (p, description);
     *p++ = '\n';
-    rc = writen (fd, line, p - line);
+
+    init_membuf_secure (&data, 64);
+
+    rc = assuan_transact (ctx, line, put_membuf_cb, &data,
+                          default_inq_cb, NULL, NULL, NULL);
     spwq_free (line);
-    if (rc)
-      goto leave;
-  }
 
-  /* get response */
-  pw = spwq_secure_malloc (500);
-  nread = readline (fd, pw, 499);
-  if (nread < 0)
-    {
-      rc = -nread;
-      goto leave;
-    }
-  if (nread < 3)
-    {
-      rc = SPWQ_PROTOCOL_ERROR;
-      goto leave;
-    }
+    /* Older Pinentries return the old assuan error code for canceled
+       which gets translated by libassuan to GPG_ERR_ASS_CANCELED and
+       not to the code for a user cancel.  Fix this here. */
+    if (rc && gpg_err_source (rc)
+        && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
+      rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
 
-  if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ')
-    { /* we got a passphrase - convert it back from hex */
-      size_t pwlen = 0;
+    if (rc)
+      {
+        void *p;
+        size_t n;
 
-      for (i=3; i < nread && hexdigitp (pw+i); i+=2)
-        pw[pwlen++] = xtoi_2 (pw+i);
-      pw[pwlen] = 0; /* make a C String */
-      result = pw;
-      pw = NULL;
-    }
-  else if ((nread > 7 && !memcmp (pw, "ERR 111", 7)
-            && (pw[7] == ' ' || pw[7] == '\n') )
-           || ((nread > 4 && !memcmp (pw, "ERR ", 4)
-                && (strtoul (pw+4, NULL, 0) & 0xffff) == 99)) )
-    {
-      /* 111 is the old Assuan code for canceled which might still
-         be in use by old installations. 99 is GPG_ERR_CANCELED as
-         used by modern gpg-agents; 0xffff is used to mask out the
-         error source.  */
-#ifdef SPWQ_USE_LOGGING
-      log_info (_("canceled by user\n") );
-#endif
-      *errorcode = 0; /* Special error code to indicate Cancel. */
-    }
-  else if (nread > 4 && !memcmp (pw, "ERR ", 4))
-    {
-      switch ( (strtoul (pw+4, NULL, 0) & 0xffff) )
-        {
-        case 85: rc = SPWQ_NO_PIN_ENTRY;  break;
-        default: rc = SPWQ_GENERAL_ERROR; break;
-        }
-    }
-  else
-    {
-#ifdef SPWQ_USE_LOGGING
-      log_error (_("problem with the agent\n"));
-#endif
-      rc = SPWQ_ERR_RESPONSE;
-    }
+        p = get_membuf (&data, &n);
+        if (p)
+          wipememory (p, n);
+        spwq_free (p);
+      }
+    else
+      {
+        put_membuf (&data, "", 1);
+        result = get_membuf (&data, NULL);
+        if (pw == NULL)
+          rc = gpg_error_from_syserror ();
+      }
+  }
 
  leave:
   if (errorcode)
     *errorcode = rc;
-  if (fd != -1)
-    close (fd);
-  if (pw)
-    spwq_secure_free (pw);
+  assuan_release (ctx);
   return result;
 }
 
@@ -628,96 +489,17 @@ simple_pwclear (const char *cacheid)
 int
 simple_query (const char *query)
 {
-  int fd = -1;
-  int nread;
+  assuan_context_t ctx;
   char response[500];
   int have = 0;
   int rc;
 
-  rc = agent_open (&fd);
-  if (rc)
-    goto leave;
-
-  rc = writen (fd, query, strlen (query));
+  rc = agent_open (&ctx);
   if (rc)
-    goto leave;
-
-  while (1)
-    {
-      if (! have || ! strchr (response, '\n'))
-        /* get response */
-        {
-          nread = readline (fd, &response[have],
-                            sizeof (response) - 1 /* NUL */ - have);
-          if (nread < 0)
-            {
-              rc = -nread;
-              goto leave;
-            }
-          have += nread;
-          if (have < 3)
-            {
-              rc = SPWQ_PROTOCOL_ERROR;
-              goto leave;
-            }
-          response[have] = 0;
-        }
-
-      if (response[0] == 'O' && response[1] == 'K')
-        /* OK, do nothing.  */;
-      else if ((nread > 7 && !memcmp (response, "ERR 111", 7)
-                && (response[7] == ' ' || response[7] == '\n') )
-               || ((nread > 4 && !memcmp (response, "ERR ", 4)
-                    && (strtoul (response+4, NULL, 0) & 0xffff) == 99)) )
-        {
-          /* 111 is the old Assuan code for canceled which might still
-             be in use by old installations. 99 is GPG_ERR_CANCELED as
-             used by modern gpg-agents; 0xffff is used to mask out the
-             error source.  */
-#ifdef SPWQ_USE_LOGGING
-          log_info (_("canceled by user\n") );
-#endif
-        }
-      else if (response[0] == 'S' && response[1] == ' ')
-        {
-          char *nextline;
-          int consumed;
-
-          nextline = strchr (response, '\n');
-          if (! nextline)
-            /* Point to the NUL.  */
-            nextline = &response[have];
-          else
-            /* Move past the \n.  */
-            nextline ++;
-
-          consumed = (size_t) nextline - (size_t) response;
+    return rc;
 
-          /* Skip any additional newlines.  */
-          while (consumed < have && response[consumed] == '\n')
-            consumed ++;
+  rc = assuan_transact (ctx, query, NULL, NULL, NULL, NULL, NULL, NULL);
 
-          have -= consumed;
-
-          if (have)
-            memmove (response, &response[consumed], have + 1);
-
-          continue;
-        }
-      else
-        {
-#ifdef SPWQ_USE_LOGGING
-          log_error (_("problem with the agent (unexpected response \"%s\")\n"),
-                     response);
-#endif
-          rc = SPWQ_ERR_RESPONSE;
-        }
-
-      break;
-    }
-
- leave:
-  if (fd != -1)
-    close (fd);
+  assuan_release (ctx);
   return rc;
 }
index bc159d9..12e5815 100644 (file)
@@ -113,7 +113,7 @@ gpgparsemail_LDADD =
 symcryptrun_SOURCES = symcryptrun.c
 symcryptrun_LDADD = $(LIBUTIL_LIBS) $(common_libs) $(pwquery_libs) \
                     $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) $(LIBINTL) \
-                    $(LIBICONV) $(NETLIBS) $(W32SOCKLIBS)
+                    $(LIBICONV) $(NETLIBS) $(W32SOCKLIBS) $(LIBASSUAN_LIBS)
 
 watchgnupg_SOURCES = watchgnupg.c
 watchgnupg_LDADD = $(NETLIBS)