[git] Pinentry - branch, master, updated. pinentry-0.9.4-8-g3ef5d07

by Daiki Ueno cvs at cvs.gnupg.org
Wed Jun 17 12:24:19 CEST 2015


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "The standard pinentry collection".

The branch, master has been updated
       via  3ef5d07873ab917e66c299521e85cff12fbbf40c (commit)
      from  34f3cdbf5aa0b7b8647a3bcedef7e1a51746b5ed (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 3ef5d07873ab917e66c299521e85cff12fbbf40c
Author: Daiki Ueno <ueno at gnu.org>
Date:   Wed Jun 17 10:32:22 2015 +0900

    Add inside-Emacs mode to GUI pinentry programs
    
    * configure.ac: Add --enable-pinentry-emacs and
    --enable-inside-emacs option.
    (BUILD_LIBPINENTRY_EMACS): New conditional.
    (BUILD_PINENTRY_EMACS): New conditional.
    (INSIDE_EMACS): New conditional.
    * Makefile.am (pinentry_emacs): New.
    (SUBDIRS): Add "emacs" subdir if PINENTRY_EMACS is set.
    
    * pinentry/pinentry-emacs.h: New file.
    * pinentry/pinentry-emacs.c: New file.
    * pinentry/Makefile.am: New file.
    * pinentry/pinentry.c (option_handler): Handle the allow-emacs-prompt
    Assuan option.
    
    * emacs/pinentry-emacs.c: New file.
    * emacs/Makefile.am: New file.

diff --git a/Makefile.am b/Makefile.am
index 71f8541..999f82d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -40,6 +40,12 @@ else
 pinentry_tty =
 endif
 
+if BUILD_PINENTRY_EMACS
+pinentry_emacs = emacs
+else
+pinentry_emacs =
+endif
+
 if BUILD_PINENTRY_GTK_2
 pinentry_gtk_2 = gtk+-2
 else
@@ -65,8 +71,8 @@ pinentry_w32 =
 endif
 
 SUBDIRS = secmem pinentry ${pinentry_curses} ${pinentry_tty} \
-	${pinentry_gtk_2} ${pinentry_gnome_3} ${pinentry_qt4} \
-	${pinentry_w32} doc
+	${pinentry_emacs} ${pinentry_gtk_2} ${pinentry_gnome_3} \
+	${pinentry_qt4} ${pinentry_w32} doc
 
 
 install-exec-local:
diff --git a/configure.ac b/configure.ac
index e5343c0..48316bf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -223,6 +223,9 @@ dnl Checks for library functions.
 AC_CHECK_FUNCS(seteuid stpcpy mmap)
 GNUPG_CHECK_MLOCK
 
+dnl Checks for standard types.
+AC_TYPE_UINT32_T
+
 # Common libraries and cflags.
 COMMON_CFLAGS=
 COMMON_LIBS=
@@ -361,6 +364,57 @@ if test "$pinentry_curses" = "yes" \
   fi
 fi
 
+dnl
+dnl Check for emacs pinentry program.
+dnl
+AC_ARG_ENABLE(pinentry-emacs,
+            AC_HELP_STRING([--enable-pinentry-emacs], [build emacs pinentry]),
+            pinentry_emacs=$enableval, pinentry_emacs=maybe)
+AC_ARG_ENABLE(inside-emacs,
+            AC_HELP_STRING([--enable-inside-emacs], [include emacs hack]),
+            inside_emacs=$enableval, inside_emacs=maybe)
+
+if test "$pinentry_emacs" != "no" -o "$inside_emacs" != "no"; then
+  AC_MSG_CHECKING([if Unix domain socket is supported])
+  AC_TRY_COMPILE([
+#include <sys/socket.h>
+#include <sys/un.h>
+],
+	         [int s = socket (AF_UNIX, SOCK_STREAM, 0);],
+		 [_unixsock_works=yes],
+		 [_unixsock_works=no])
+  AC_MSG_RESULT($_unixsock_works)
+  if test "$_unixsock_works" = "yes"; then
+    if test "$pinentry_emacs" != "no"; then
+      pinentry_emacs=yes
+    fi
+    if test "$inside_emacs" != "no"; then
+      inside_emacs=yes
+      AC_DEFINE(INSIDE_EMACS, 1,
+                [The GUI pinentries should respect INSIDE_EMACS envvar.])
+    fi
+  else
+    if test "$pinentry_emacs" = "yes" -o "$inside_emacs" = "yes"; then
+      AC_MSG_ERROR([[
+***
+*** Support for Unix domain sockets is required.
+***]])
+    fi
+    pinentry_emacs=no
+    inside_emacs=no
+  fi
+fi
+
+AM_CONDITIONAL(BUILD_LIBPINENTRY_EMACS,
+              test "$pinentry_emacs" = "yes" -o "$inside_emacs" = "yes")
+AM_CONDITIONAL(BUILD_PINENTRY_EMACS, test "$pinentry_emacs" = "yes")
+AM_CONDITIONAL(INSIDE_EMACS, test "$inside_emacs" = "yes")
+
+if test "$pinentry_emacs" = "yes"; then
+  AC_DEFINE(PINENTRY_EMACS, 1,
+            [The Emacs version of Pinentry is to be build])
+fi
+
 
 
 dnl
@@ -659,6 +713,7 @@ secmem/Makefile
 pinentry/Makefile
 curses/Makefile
 tty/Makefile
+emacs/Makefile
 gtk+-2/Makefile
 gnome3/Makefile
 qt4/Makefile
@@ -678,12 +733,14 @@ AC_MSG_NOTICE([
 
 	Curses Pinentry ..: $pinentry_curses
 	TTY Pinentry .....: $pinentry_tty
+	Emacs Pinentry ...: $pinentry_emacs
 	GTK+-2 Pinentry ..: $pinentry_gtk_2
 	GNOME 3 Pinentry .: $pinentry_gnome_3
 	Qt4 Pinentry .....: $pinentry_qt4 $pinentry_qt4_clip_msg
 	W32 Pinentry .....: $pinentry_w32
 
 	Fallback to Curses: $fallback_curses
+	Emacs integration : $inside_emacs
 
 	libsecret ........: $libsecret
 
diff --git a/pinentry/Makefile.am b/emacs/Makefile.am
similarity index 65%
copy from pinentry/Makefile.am
copy to emacs/Makefile.am
index d24581b..e2102bc 100644
--- a/pinentry/Makefile.am
+++ b/emacs/Makefile.am
@@ -1,4 +1,4 @@
-# Pinentry support library Makefile
+# Makefile.am - PIN entry emacs frontend.
 # Copyright (C) 2002, 2015 g10 Code GmbH
 #
 # This file is part of PINENTRY.
@@ -19,19 +19,11 @@
 
 ## Process this file with automake to produce Makefile.in
 
-EXTRA_DIST =
+bin_PROGRAMS = pinentry-emacs
 
-if BUILD_LIBPINENTRY_CURSES
-pinentry_curses = libpinentry-curses.a
-else
-pinentry_curses =
-endif
+AM_CPPFLAGS = $(COMMON_CFLAGS) $(NEMACS_INCLUDE) -I$(top_srcdir)/pinentry
+LDADD = ../pinentry/libpinentry.a \
+	../assuan/libassuan.a ../secmem/libsecmem.a \
+	$(COMMON_LIBS) $(LIBCAP) $(LIBEMACS) $(LIBICONV)
 
-noinst_LIBRARIES = libpinentry.a $(pinentry_curses)
-
-LDADD = $(COMMON_LIBS)
-AM_CPPFLAGS = $(COMMON_CFLAGS) -I$(top_srcdir)/secmem
-
-libpinentry_a_SOURCES = pinentry.h pinentry.c argparse.c argparse.h \
-	password-cache.h password-cache.c
-libpinentry_curses_a_SOURCES = pinentry-curses.h pinentry-curses.c
+pinentry_emacs_SOURCES = pinentry-emacs.c
diff --git a/emacs/pinentry-emacs.c b/emacs/pinentry-emacs.c
new file mode 100644
index 0000000..de4ca05
--- /dev/null
+++ b/emacs/pinentry-emacs.c
@@ -0,0 +1,47 @@
+/* pinentry-emacs.c - A secure emacs dialog for PIN entry, library version
+   Copyright (C) 2015 Daiki Ueno
+
+   This file is part of PINENTRY.
+
+   PINENTRY is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PINENTRY is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "pinentry.h"
+#include "pinentry-emacs.h"
+
+pinentry_cmd_handler_t pinentry_cmd_handler = emacs_cmd_handler;
+
+

+int
+main (int argc, char *argv[])
+{
+  pinentry_init ("pinentry-emacs");
+
+  if (!pinentry_emacs_init ())
+    return 1;
+
+  pinentry_parse_opts (argc, argv);
+
+  if (pinentry_loop ())
+    return 1;
+
+  return 0;
+}
diff --git a/pinentry/Makefile.am b/pinentry/Makefile.am
index d24581b..be99822 100644
--- a/pinentry/Makefile.am
+++ b/pinentry/Makefile.am
@@ -27,11 +27,17 @@ else
 pinentry_curses =
 endif
 
+if BUILD_LIBPINENTRY_EMACS
+pinentry_emacs_sources = pinentry-emacs.h pinentry-emacs.c
+else
+pinentry_emacs_sources =
+endif
+
 noinst_LIBRARIES = libpinentry.a $(pinentry_curses)
 
 LDADD = $(COMMON_LIBS)
 AM_CPPFLAGS = $(COMMON_CFLAGS) -I$(top_srcdir)/secmem
 
 libpinentry_a_SOURCES = pinentry.h pinentry.c argparse.c argparse.h \
-	password-cache.h password-cache.c
+	password-cache.h password-cache.c $(pinentry_emacs_sources)
 libpinentry_curses_a_SOURCES = pinentry-curses.h pinentry-curses.c
diff --git a/pinentry/pinentry-emacs.c b/pinentry/pinentry-emacs.c
new file mode 100644
index 0000000..9ced8da
--- /dev/null
+++ b/pinentry/pinentry-emacs.c
@@ -0,0 +1,695 @@
+/* pinentry-emacs.c - A secure emacs dialog for PIN entry, library version
+   Copyright (C) 2015 Daiki Ueno
+
+   This file is part of PINENTRY.
+
+   PINENTRY is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PINENTRY is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+#include <assert.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef HAVE_UTIME_H
+#include <utime.h>
+#endif /*HAVE_UTIME_H*/
+
+#include <assuan.h>
+
+#include "pinentry-emacs.h"
+#include "memory.h"
+#include "secmem-util.h"
+
+/* The communication mechanism is similar to emacsclient, but there
+   are a few differences:
+
+   - To avoid unnecessary character escaping and encoding conversion,
+     we use a subset of the Pinentry Assuan protocol, instead of the
+     emacsclient protocol.
+
+   - We only use a Unix domain socket, while emacsclient has an
+     ability to use a TCP socket.  The socket file is located at
+     ${TMPDIR-/tmp}/emacs$(id -u)/pinentry (i.e., under the same
+     directory as the socket file used by emacsclient, so the same
+     permission and file owner settings apply).
+
+   - The server implementation can be found in pinentry.el, which is
+     available in Emacs 25+ or from ELPA.  */
+
+#define LINELENGTH ASSUAN_LINELENGTH
+#define SEND_BUFFER_SIZE 4096
+#define INITIAL_TIMEOUT 60
+
+static int initial_timeout = INITIAL_TIMEOUT;
+
+#undef MIN
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+
+#undef MAX
+#define MAX(x, y) ((x) < (y) ? (y) : (x))
+
+#ifndef SUN_LEN
+# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
+                       + strlen ((ptr)->sun_path))
+#endif
+
+/* FIXME: We could use the I/O functions in Assuan directly, once
+   Pinentry links to libassuan.  */
+static int emacs_socket = -1;
+static char send_buffer[SEND_BUFFER_SIZE + 1];
+static int send_buffer_length; /* Fill pointer for the send buffer.  */
+
+static pinentry_cmd_handler_t fallback_cmd_handler;
+
+#ifndef HAVE_DOSISH_SYSTEM
+static int timed_out;
+#endif
+
+static int
+set_socket (const char *socket_name)
+{
+  struct sockaddr_un unaddr;
+  struct stat statbuf;
+  const char *tmpdir;
+  char *tmpdir_storage = NULL;
+  char *socket_name_storage = NULL;
+  uid_t uid;
+
+  unaddr.sun_family = AF_UNIX;
+
+  /* We assume 32-bit UIDs, which can be represented with 10 decimal
+     digits.  */
+  uid = getuid ();
+  if (uid != (uint32_t) uid)
+    {
+      fprintf (stderr, "UID is too large\n");
+      return 0;
+    }
+
+  tmpdir = getenv ("TMPDIR");
+  if (!tmpdir)
+    {
+#ifdef _CS_DARWIN_USER_TEMP_DIR
+      size_t n = confstr (_CS_DARWIN_USER_TEMP_DIR, NULL, (size_t) 0);
+      if (n > 0)
+	{
+	  tmpdir = tmpdir_storage = malloc (n);
+	  if (!tmpdir)
+	    {
+	      fprintf (stderr, "out of core\n");
+	      return 0;
+	    }
+	  confstr (_CS_DARWIN_USER_TEMP_DIR, tmpdir_storage, n);
+	}
+      else
+#endif
+	tmpdir = "/tmp";
+    }
+
+  socket_name_storage = malloc (strlen (tmpdir)
+				+ strlen ("/emacs") + 10 + strlen ("/")
+				+ strlen (socket_name)
+				+ 1);
+  if (!socket_name_storage)
+    {
+      fprintf (stderr, "out of core\n");
+      free (tmpdir_storage);
+      return 0;
+    }
+
+  sprintf (socket_name_storage, "%s/emacs%u/%s", tmpdir,
+	   (uint32_t) uid, socket_name);
+  free (tmpdir_storage);
+
+  if (strlen (socket_name_storage) >= sizeof (unaddr.sun_path))
+    {
+      fprintf (stderr, "socket name is too long\n");
+      free (socket_name_storage);
+      return 0;
+    }
+
+  strcpy (unaddr.sun_path, socket_name_storage);
+  free (socket_name_storage);
+
+  /* See if the socket exists, and if it's owned by us. */
+  if (stat (unaddr.sun_path, &statbuf) == -1)
+    {
+      perror ("stat");
+      return 0;
+    }
+
+  if (statbuf.st_uid != geteuid ())
+    {
+      fprintf (stderr, "socket is not owned by the same user\n");
+      return 0;
+    }
+
+  emacs_socket = socket (AF_UNIX, SOCK_STREAM, 0);
+  if (emacs_socket < 0)
+    {
+      perror ("socket");
+      return 0;
+    }
+
+  if (connect (emacs_socket, (struct sockaddr *) &unaddr,
+	       SUN_LEN (&unaddr)) < 0)
+    {
+      perror ("connect");
+      close (emacs_socket);
+      emacs_socket = -1;
+      return 0;
+    }
+
+  return 1;
+}
+
+/* Percent-escape control characters in DATA.  Return a newly
+   allocated string.  */
+static char *
+escape (const char *data)
+{
+  char *buffer, *out_p;
+  size_t length, buffer_length;
+  size_t offset;
+  size_t count = 0;
+
+  length = strlen (data);
+  for (offset = 0; offset < length; offset++)
+    {
+      switch (data[offset])
+	{
+	case '%': case '\n': case '\r':
+	  count++;
+	  break;
+	default:
+	  break;
+	}
+    }
+
+  buffer_length = length + count * 2;
+  buffer = malloc (buffer_length + 1);
+  if (!buffer)
+    return NULL;
+
+  out_p = buffer;
+  for (offset = 0; offset < length; offset++)
+    {
+      int c = data[offset];
+      switch (c)
+	{
+	case '%': case '\n': case '\r':
+	  sprintf (out_p, "%%%02X", c);
+	  out_p += 3;
+	  break;
+	default:
+	  *out_p++ = c;
+	  break;
+	}
+    }
+  *out_p = '\0';
+
+  return buffer;
+}
+
+/* The inverse of escape.  Unlike escape, it removes quoting in string
+   DATA by modifying the string in place, to avoid copying of secret
+   data sent from Emacs.  */
+static char *
+unescape (char *data)
+{
+  char *p = data, *q = data;
+
+  while (*p)
+    {
+      if (*p == '%' && p[1] && p[2])
+        {
+          p++;
+          *q++ = xtoi_2 (p);
+	  p += 2;
+        }
+      else
+	*q++ = *p++;
+    }
+  *q = 0;
+  return data;
+}
+
+/* Let's send the data to Emacs when either
+   - the data ends in "\n", or
+   - the buffer is full (but this shouldn't happen)
+   Otherwise, we just accumulate it.  */
+static int
+send_to_emacs (int s, const char *buffer)
+{
+  size_t length;
+
+  length = strlen (buffer);
+  while (*buffer)
+    {
+      size_t part = MIN (length, SEND_BUFFER_SIZE - send_buffer_length);
+      memcpy (&send_buffer[send_buffer_length], buffer, part);
+      buffer += part;
+      send_buffer_length += part;
+
+      if (send_buffer_length == SEND_BUFFER_SIZE
+	  || (send_buffer_length > 0
+	      && send_buffer[send_buffer_length-1] == '\n'))
+	{
+	  int sent = send (s, send_buffer, send_buffer_length, 0);
+	  if (sent < 0)
+	    {
+	      fprintf (stderr, "failed to send %d bytes to socket: %s\n",
+		       send_buffer_length, strerror (errno));
+	      send_buffer_length = 0;
+	      return 0;
+	    }
+	  if (sent != send_buffer_length)
+	    memmove (send_buffer, &send_buffer[sent],
+		     send_buffer_length - sent);
+	  send_buffer_length -= sent;
+	}
+
+      length -= part;
+    }
+
+  return 1;
+}
+
+/* Read a server response.  If the response contains data, it will be
+   stored in BUFFER with a terminating NUL byte.  BUFFER must be
+   at least as large as CAPACITY.  */
+static gpg_error_t
+read_from_emacs (int s, int timeout, char *buffer, size_t capacity)
+{
+  struct timeval tv;
+  fd_set rfds;
+  int retval;
+  /* Offset in BUFFER.  */
+  size_t offset = 0;
+  int got_response = 0;
+  char read_buffer[LINELENGTH + 1];
+  /* Offset in READ_BUFFER.  */
+  size_t read_offset = 0;
+  gpg_error_t result = 0;
+
+  tv.tv_sec = timeout;
+  tv.tv_usec = 0;
+
+  FD_ZERO (&rfds);
+  FD_SET (s, &rfds);
+  retval = select (s + 1, &rfds, NULL, NULL, &tv);
+  if (retval == -1)
+    {
+      perror ("select");
+      return gpg_error (GPG_ERR_ASS_GENERAL);
+    }
+  else if (retval == 0)
+    {
+      timed_out = 1;
+      return gpg_error (GPG_ERR_TIMEOUT);
+    }
+
+  /* Loop until we get either OK or ERR.  */
+  while (!got_response)
+    {
+      int rl = 0;
+      char *p, *end_p;
+      do
+	{
+	  errno = 0;
+	  rl = recv (s, read_buffer + read_offset, LINELENGTH - read_offset, 0);
+	}
+      /* If we receive a signal (e.g. SIGWINCH, which we pass
+	 through to Emacs), on some OSes we get EINTR and must retry. */
+      while (rl < 0 && errno == EINTR);
+
+      if (rl < 0)
+	{
+	  perror ("recv");
+	  return gpg_error (GPG_ERR_ASS_GENERAL);;
+	}
+      if (rl == 0)
+	break;
+
+      read_offset += rl;
+      read_buffer[read_offset] = '\0';
+
+      end_p = strchr (read_buffer, '\n');
+
+      /* If the buffer is filled without NL, throw away the content
+	 and start over the buffering.
+
+	 FIXME: We could return ASSUAN_Line_Too_Long or
+	 ASSUAN_Line_Not_Terminated here.  */
+      if (!end_p && read_offset == sizeof (read_buffer) - 1)
+	{
+	  read_offset = 0;
+	  continue;
+	}
+
+      /* Loop over all NL-terminated messages.  */
+      for (p = read_buffer; end_p; p = end_p + 1, end_p = strchr (p, '\n'))
+	{
+	  *end_p = '\0';
+	  if (!strncmp ("D ", p, 2))
+	    {
+	      char *data;
+	      size_t data_length;
+	      size_t needed_capacity;
+
+	      data = p + 2;
+	      data_length = end_p - data;
+	      if (data_length > 0)
+		{
+		  needed_capacity = offset + data_length + 1;
+
+		  /* Check overflow.  This is unrealistic but can
+		     happen since OFFSET is cumulative.  */
+		  if (needed_capacity < offset)
+		    return gpg_error (GPG_ERR_ASS_GENERAL);;
+
+		  if (needed_capacity > capacity)
+		    return gpg_error (GPG_ERR_ASS_GENERAL);;
+
+		  memcpy (&buffer[offset], data, data_length);
+		  offset += data_length;
+		  buffer[offset] = 0;
+		}
+	    }
+          else if (!strcmp ("OK", p) || !strncmp ("OK ", p, 3))
+            {
+	      got_response = 1;
+	      break;
+            }
+          else if (!strncmp ("ERR ", p, 4))
+            {
+	      unsigned long code = strtoul (p + 4, NULL, 10);
+	      if (code == ULONG_MAX && errno == ERANGE)
+		return gpg_error (GPG_ERR_ASS_GENERAL);
+	      else
+		result = code;
+	      got_response = 1;
+	      break;
+            }
+	  else if (*p == '#')
+	    ;
+	  else
+	    fprintf (stderr, "invalid response: %s\n", p);
+	}
+
+      if (!got_response)
+	{
+	  size_t length = &read_buffer[read_offset] - p;
+	  memmove (read_buffer, p, length);
+	  read_offset = length;
+	}
+    }
+
+  return result;
+}
+
+int
+set_label (pinentry_t pe, const char *name, const char *value)
+{
+  char buffer[16], *escaped;
+  gpg_error_t error;
+  int retval;
+
+  if (!send_to_emacs (emacs_socket, name)
+      || !send_to_emacs (emacs_socket, " "))
+    return 0;
+
+  escaped = escape (value);
+  if (!escaped)
+    return 0;
+
+  retval = send_to_emacs (emacs_socket, escaped)
+    && send_to_emacs (emacs_socket, "\n");
+
+  free (escaped);
+  if (!retval)
+    return 0;
+
+  error = read_from_emacs (emacs_socket, pe->timeout, buffer, sizeof (buffer));
+  return error == 0;
+}
+
+static void
+set_labels (pinentry_t pe)
+{
+  if (pe->title)
+    set_label (pe, "SETTITLE", pe->title);
+  if (pe->description)
+    set_label (pe, "SETDESC", pe->description);
+  if (pe->error)
+    set_label (pe, "SETERROR", pe->error);
+  if (pe->prompt)
+    set_label (pe, "SETPROMPT", pe->prompt);
+  else if (pe->default_prompt)
+    set_label (pe, "SETPROMPT", pe->default_prompt);
+  if (pe->repeat_passphrase)
+    set_label (pe, "SETREPEAT", pe->repeat_passphrase);
+  if (pe->repeat_error_string)
+    set_label (pe, "SETREPEATERROR", pe->repeat_error_string);
+
+  /* XXX: pe->quality_bar and pe->quality_bar_tt are not supported.  */
+
+  /* Buttons.  */
+  if (pe->ok)
+    set_label (pe, "SETOK", pe->ok);
+  else if (pe->default_ok)
+    set_label (pe, "SETOK", pe->default_ok);
+  if (pe->cancel)
+    set_label (pe, "SETCANCEL", pe->cancel);
+  else if (pe->default_ok)
+    set_label (pe, "SETCANCEL", pe->default_cancel);
+  if (pe->notok)
+    set_label (pe, "SETNOTOK", pe->notok);
+}
+
+static int
+do_password (pinentry_t pe)
+{
+  char *buffer, *password;
+  size_t length = LINELENGTH;
+  gpg_error_t error;
+
+  set_labels (pe);
+
+  if (!send_to_emacs (emacs_socket, "GETPIN\n"))
+    return -1;
+
+  buffer = secmem_malloc (length);
+  if (!buffer)
+    {
+      pe->specific_err = gpg_error (GPG_ERR_ENOMEM);
+      return -1;
+    }
+
+  error = read_from_emacs (emacs_socket, pe->timeout, buffer, length);
+  if (error != 0)
+    {
+      if (gpg_err_code (error) == GPG_ERR_CANCELED)
+	pe->canceled = 1;
+
+      secmem_free (buffer);
+      pe->specific_err = error;
+      return -1;
+    }
+
+  password = unescape (buffer);
+  pinentry_setbufferlen (pe, strlen (password) + 1);
+  if (pe->pin)
+    strcpy (pe->pin, password);
+  secmem_free (buffer);
+
+  if (pe->repeat_passphrase)
+    pe->repeat_okay = 1;
+
+  /* XXX: we don't support external password cache (yet).  */
+
+  return 1;
+}
+
+static int
+do_confirm (pinentry_t pe)
+{
+  char buffer[16];
+  gpg_error_t error;
+
+  set_labels (pe);
+
+  if (!send_to_emacs (emacs_socket, "CONFIRM\n"))
+    return 0;
+
+  error = read_from_emacs (emacs_socket, pe->timeout, buffer, sizeof (buffer));
+  if (error != 0)
+    {
+      if (gpg_err_code (error) == GPG_ERR_CANCELED)
+	pe->canceled = 1;
+
+      pe->specific_err = error;
+      return 0;
+    }
+
+  return 1;
+}
+
+/* If a touch has been registered, touch that file.  */
+static void
+do_touch_file (pinentry_t pinentry)
+{
+#ifdef HAVE_UTIME_H
+  struct stat st;
+  time_t tim;
+
+  if (!pinentry->touch_file || !*pinentry->touch_file)
+    return;
+
+  if (stat (pinentry->touch_file, &st))
+    return; /* Oops.  */
+
+  /* Make sure that we actually update the mtime.  */
+  while ( (tim = time (NULL)) == st.st_mtime )
+    sleep (1);
+
+  /* Update but ignore errors as we can't do anything in that case.
+     Printing error messages may even clubber the display further. */
+  utime (pinentry->touch_file, NULL);
+#endif /*HAVE_UTIME_H*/
+}
+
+#ifndef HAVE_DOSISH_SYSTEM
+static void
+catchsig (int sig)
+{
+  if (sig == SIGALRM)
+    timed_out = 1;
+}
+#endif
+
+int
+emacs_cmd_handler (pinentry_t pe)
+{
+  int rc;
+
+#ifndef HAVE_DOSISH_SYSTEM
+  timed_out = 0;
+
+  if (pe->timeout)
+    {
+      struct sigaction sa;
+
+      memset (&sa, 0, sizeof(sa));
+      sa.sa_handler = catchsig;
+      sigaction (SIGALRM, &sa, NULL);
+      alarm (pe->timeout);
+    }
+#endif
+
+  if (pe->pin)
+    rc = do_password (pe);
+  else
+    rc = do_confirm (pe);
+
+  do_touch_file (pe);
+  return rc;
+}
+
+static int
+initial_emacs_cmd_handler (pinentry_t pe)
+{
+  /* Let the select() call in pinentry_emacs_init honor the timeout
+     value set through an Assuan option.  */
+  initial_timeout = pe->timeout;
+
+  if (emacs_socket < 0)
+    pinentry_emacs_init ();
+
+  /* If we have successfully connected to Emacs, swap
+     pinentry_cmd_handler to emacs_cmd_handler, so further
+     interactions will be forwarded to Emacs.  Otherwise, set it back
+     to the original command handler saved as
+     fallback_cmd_handler.  */
+  if (emacs_socket < 0)
+    pinentry_cmd_handler = fallback_cmd_handler;
+  else
+    pinentry_cmd_handler = emacs_cmd_handler;
+
+  return (* pinentry_cmd_handler) (pe);
+}
+
+void
+pinentry_enable_emacs_cmd_handler (void)
+{
+  const char *envvar;
+
+  /* Check if pinentry_cmd_handler is already prepared for Emacs.  */
+  if (pinentry_cmd_handler == initial_emacs_cmd_handler
+      || pinentry_cmd_handler == emacs_cmd_handler)
+    return;
+
+  /* Check if INSIDE_EMACS envvar is set.  */
+  envvar = getenv ("INSIDE_EMACS");
+  if (!envvar || !*envvar)
+    return;
+
+  /* Save the original command handler as fallback_cmd_handler, and
+     swap pinentry_cmd_handler to initial_emacs_cmd_handler.  */
+  fallback_cmd_handler = pinentry_cmd_handler;
+  pinentry_cmd_handler = initial_emacs_cmd_handler;
+}
+
+int
+pinentry_emacs_init (void)
+{
+  char buffer[256];
+  gpg_error_t error;
+
+  assert (emacs_socket < 0);
+
+  /* Check if we can connect to the Emacs server socket.  */
+  if (!set_socket ("pinentry"))
+    return 0;
+
+  /* Check if the server responds.  */
+  error = read_from_emacs (emacs_socket, initial_timeout,
+			   buffer, sizeof (buffer));
+  if (error != 0)
+    {
+      close (emacs_socket);
+      emacs_socket = -1;
+      return 0;
+    }
+  return 1;
+}
diff --git a/pinentry/pinentry-emacs.h b/pinentry/pinentry-emacs.h
new file mode 100644
index 0000000..61d04cc
--- /dev/null
+++ b/pinentry/pinentry-emacs.h
@@ -0,0 +1,43 @@
+/* pinentry-emacs.c - A secure emacs dialog for PIN entry, library version
+   Copyright (C) 2015 Daiki Ueno
+
+   This file is part of PINENTRY.
+
+   PINENTRY is free software; you can redistribute it and/or modify it
+   under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   PINENTRY is distributed in the hope that it will be useful, but
+   WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PINENTRY_EMACS_H
+#define PINENTRY_EMACS_H
+
+#include "pinentry.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Enable pinentry command handler which interacts with Emacs, if
+   INSIDE_EMACS envvar is set.  This function shall be called upon
+   receiving an Assuan request "OPTION allow-emacs-prompt".  */
+void pinentry_enable_emacs_cmd_handler (void);
+
+/* Initialize the Emacs interface, return true if success.  */
+int pinentry_emacs_init (void);
+
+int emacs_cmd_handler (pinentry_t pinentry);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif	/* PINENTRY_EMACS_H */
diff --git a/pinentry/pinentry.c b/pinentry/pinentry.c
index 47d9b06..e682de7 100644
--- a/pinentry/pinentry.c
+++ b/pinentry/pinentry.c
@@ -51,6 +51,10 @@
 #include "pinentry.h"
 #include "password-cache.h"
 
+#ifdef INSIDE_EMACS
+#include "pinentry-emacs.h"
+#endif
+
 #ifdef HAVE_W32CE_SYSTEM
 #define getpid() GetCurrentProcessId ()
 #endif
@@ -866,6 +870,14 @@ option_handler (assuan_context_t ctx, const char *key, const char *value)
       pinentry.allow_external_password_cache = 1;
       pinentry.tried_password_cache = 0;
     }
+  else if (!strcmp (key, "allow-emacs-prompt") && !*value)
+    {
+#ifdef INSIDE_EMACS
+      pinentry_enable_emacs_cmd_handler ();
+#else
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+#endif
+    }
   else
     return gpg_error (GPG_ERR_UNKNOWN_OPTION);
   return 0;

-----------------------------------------------------------------------

Summary of changes:
 Makefile.am                                        |  10 +-
 configure.ac                                       |  57 ++
 {curses => emacs}/Makefile.am                      |  13 +-
 curses/pinentry-curses.c => emacs/pinentry-emacs.c |  18 +-
 pinentry/Makefile.am                               |   8 +-
 pinentry/pinentry-emacs.c                          | 695 +++++++++++++++++++++
 pinentry/{pinentry-curses.h => pinentry-emacs.h}   |  33 +-
 pinentry/pinentry.c                                |  12 +
 8 files changed, 816 insertions(+), 30 deletions(-)
 copy {curses => emacs}/Makefile.am (71%)
 copy curses/pinentry-curses.c => emacs/pinentry-emacs.c (69%)
 create mode 100644 pinentry/pinentry-emacs.c
 copy pinentry/{pinentry-curses.h => pinentry-emacs.h} (51%)


hooks/post-receive
-- 
The standard pinentry collection
http://git.gnupg.org




More information about the Gnupg-commits mailing list