[PATCH] Add inside-Emacs mode to GUI pinentry programs

Neal H. Walfield neal at walfield.org
Wed Jun 3 14:49:27 CEST 2015


Hi Daiki,

I just took a quick look and it seems good.  I'll have time to do a
thorough review tomorrow (Thursday).

:) Neal

At Tue, 02 Jun 2015 17:56:44 +0900,
Daiki Ueno wrote:
> 
> [1  <text/plain (7bit)>]
> Hello Neal,
> 
> Thanks for the thorough review, responses are inlined below.
> 
> By the way, on second thoughts, I ended up with a specialized protocol
> for this, to avoid unnecessary character escaping and encoding
> conversion (and also reconnecting to a socket every time).  The protocol
> is actually a subset of the Pinentry Assuan protocol, and implemented in
> pinentry.el, which shall be included in Emacs, maybe under lisp/net/ or
> ELPA.
> 
> I'm attaching the new patch and pinentry.el.  To test, use "M-x
> pinentry-start" instead of "M-x server-start".
> 
> "Neal H. Walfield" <neal at walfield.org> writes:
> 
> >> +if test "$pinentry_emacs" = "maybe"; then
> >> +  AC_MSG_CHECKING([if Unix domain socket is supported])
> >
> > I think we should check for != "no" here.  If pinentry_emacs is yes,
> > but we don't support unix domain sockets, then it would be better to
> > error out here than when compiling the code.  Do you agree?
> 
> Yes, exactly.  Fixed.
> 
> >> +#include <assert.h>
> >
> > As far as I can tell, this include is gratuitious.
> 
> Removed.
> 
> >> +  if (strlen (tmpdir) + strlen (socket_name) + 10 + 2
> >> +      >= sizeof (emacs_server_address.sun_path))
> [...]
> >> +  sprintf (emacs_server_address.sun_path, "%s/emacs%lu/%s",
> >> +	   tmpdir, (unsigned long) uid, socket_name);
> >
> > I think the check above is wrong.  It should be something like:
> >
> >   if (strlen (tmpdir) + strlen("/emacs") + 10 + strlen("/")
> >       + strlen (socket_name) + 1
> >       >= sizeof (emacs_server_address.sun_path))
> 
> Right, thanks for pointing that out.  Fixed using the correct formula.
> 
> >> +  if (connect (s, (struct sockaddr *) &emacs_server_address,
> >> +	       strlen (emacs_server_address.sun_path) + 2) < 0)
> >
> > Why don't you just use 'sizeof (emacs_server_address)' here?  I really
> > don't like the magic number 2 here.
> 
> Fixed using SUN_LEN from GnuPG.
> 
> >> +	  if (strprefix ("-print ", p))
> 
> > Does it make sense to emit a warning if there is a line with an
> > unrecognized prefix?  As far as I can see, lines with unknown commands
> > are currently ignored.
> 
> Yes, added warnings.
> 
> >> +  sprintf (command, "(" CALLBACK_NAME " \"%s\" %s)",
> >> +	   name, quoted_value);
> >> +  if (quoted_value != nil)
> >> +    free (quoted_value);
> >
> > Here's another instance where I'd strongly prefer something like
> > asprintf.
> 
> Yes.  Those allocation and copying are no longer necessary with the new
> protocol.
> 
> >> +  quoted_command = build_command (name, local_value);
> >> +  if (local_value != value)
> >> +    free (local_value);
> >
> > I think this comparison is useless.  If value is not NULL, then we
> > call pinentry_utf8_to_local, which always allocates a new buffer.
> 
> Good point.  Likewise to the above, the extra allocation is not needed
> anymore.
> 
> > Does emacs not support setting custom button labels?
> 
> Yes, it could.  However, for a password query, labels would be too
> verbose to be shown in the minibuffer.  So, in pinentry.el, they are
> only used for a confirmation query.
> 
> > If pe->prompt is NULL, then you should fallback to pe->default_prompt.
> 
> Fixed.
> 
> > If you have time, it would be nice to add support for the external
> > password manager checkbox.  In the very least, please note the lack of
> > this support with an XXX comment in the source code.
> 
> Sure, added a comment.
> 
> >> +	  if (pe->repeat_passphrase)
> >> +	    pe->repeat_okay = 1;
> >
> > Why are you setting this if the user didn't actually enter the
> > password twice?
> 
> I implemented the repeat feature in the new patch.
> 
> >> +int
> >> +do_confirm (pinentry_t pe)
> [...]
> >> +  set_value (pe, "SETPROMPT", pe->prompt);
> >
> > Again, please fallback to pe->default_prompt.
> 
> Fixed.
> 
> > Any reason, there is no support for custom button labels?  Also, what
> > about support for the other button?  If this is not yet possible,
> > please note it with an XXX comment.
> 
> Fixed.
> 
> >> -#if defined FALLBACK_CURSES || defined PINENTRY_CURSES || defined
> >> PINENTRY_GTK
> >> +#if defined FALLBACK_CURSES || defined INSIDE_EMACS || defined
> >> PINENTRY_CURSES || defined PINENTRY_GTK
> >>  char *
> >>  pinentry_utf8_to_local (const char *lc_ctype, const char *text)
> >>  {
> >
> > I think we should check if PINENTRY_EMACS is defined, not INSIDE_EMACS
> > here.
> 
> I reverted this change since the patch no longer uses
> pinentry_utf8_to_local.
> 
> Regards,
> -- 
> Daiki Ueno
> [2 0001-Add-inside-Emacs-mode-to-GUI-pinentry-programs.patch <text/x-patch (7bit)>]
> From 14789d2bfa4fd7931a0191a1379d3fcd449e12aa Mon Sep 17 00:00:00 2001
> From: Daiki Ueno <ueno at gnu.org>
> Date: Wed, 27 May 2015 17:06:08 +0900
> Subject: [PATCH] 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.
> 
> * emacs/pinentry-emacs.c: New file.
> * emacs/Makefile.am: New file.
> 
> * qt4/Makefile.am (libemacs): New variable, set if the
> INSIDE_EMACS conditional is true.
> (pinentry_qt4_LDADD): Add $(libemacs).
> * qt4/main.cpp (main): Set pinentry_cmd_handler if
> the INSIDE_EMACS envvar is set and the socket is usable.
> 
> * gnome3/Makefile.am (libemacs): New variable, set if the
> INSIDE_EMACS conditional is true.
> (LDADD): Add $(libemacs).
> * gnome3/pinentry-gnome3.c (main): Set pinentry_cmd_handler if
> the INSIDE_EMACS envvar is set and the socket is usable.
> 
> * gtk+-2/Makefile.am (libemacs): New variable, set if the INSIDE_EMACS
> conditional is true.
> (LDADD): Add $(libemacs).
> * gtk+-2/pinentry-gtk-2.c (main): Set pinentry_cmd_handler if
> the INSIDE_EMACS envvar is set and the socket is usable.
> ---
>  Makefile.am               |  10 +-
>  configure.ac              |  60 +++++
>  emacs/Makefile.am         |  29 +++
>  emacs/pinentry-emacs.c    |  47 ++++
>  gnome3/Makefile.am        |   8 +-
>  gnome3/pinentry-gnome3.c  |  21 +-
>  gtk+-2/Makefile.am        |   8 +-
>  gtk+-2/pinentry-gtk-2.c   |  25 +-
>  pinentry/Makefile.am      |   9 +-
>  pinentry/pinentry-emacs.c | 640 ++++++++++++++++++++++++++++++++++++++++++++++
>  pinentry/pinentry-emacs.h |  41 +++
>  qt4/Makefile.am           |   9 +-
>  qt4/main.cpp              |  93 ++++---
>  13 files changed, 941 insertions(+), 59 deletions(-)
>  create mode 100644 emacs/Makefile.am
>  create mode 100644 emacs/pinentry-emacs.c
>  create mode 100644 pinentry/pinentry-emacs.c
>  create mode 100644 pinentry/pinentry-emacs.h
> 
> diff --git a/Makefile.am b/Makefile.am
> index 177f37e..388464e 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 = assuan 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 9948d1f..abc248e 100644
> --- a/configure.ac
> +++ b/configure.ac
> @@ -271,6 +271,63 @@ 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"; 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
> +    pinentry_emacs=yes
> +  else
> +    if test "$pinentry_emacs" = "yes"; then
> +      AC_MSG_ERROR([[
> +***
> +*** Support for Unix domain sockets is required.
> +***]])
> +    fi
> +    pinentry_emacs=no
> +  fi
> +fi
> +
> +if test "$inside_emacs" = "maybe"; then
> +  if test "$pinentry_emacs" = "yes"; then
> +    inside_emacs=yes
> +  else
> +    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
> +
> +if test "$inside_emacs" = "yes"; then
> +  inside_emacs=yes
> +  AC_DEFINE(INSIDE_EMACS, 1,
> +            [The GUI pinentries should respect INSIDE_EMACS envvar.])
> +fi
> +
>  
>  
>  dnl
> @@ -512,6 +569,7 @@ secmem/Makefile
>  pinentry/Makefile
>  curses/Makefile
>  tty/Makefile
> +emacs/Makefile
>  gtk+-2/Makefile
>  gnome3/Makefile
>  qt4/Makefile
> @@ -531,12 +589,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
> +	Inside-EMACS mode : $inside_emacs
>  
>  	libsecret ........: $libsecret
>  
> diff --git a/emacs/Makefile.am b/emacs/Makefile.am
> new file mode 100644
> index 0000000..a0bfe6c
> --- /dev/null
> +++ b/emacs/Makefile.am
> @@ -0,0 +1,29 @@
> +# Makefile.am - PIN entry emacs frontend.
> +# Copyright (C) 2002, 2015 g10 Code GmbH
> +#
> +# 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, write to the Free Software
> +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
> +
> +## Process this file with automake to produce Makefile.in
> +
> +bin_PROGRAMS = pinentry-emacs
> +
> +AM_CPPFLAGS = $(COMMON_CFLAGS) $(NEMACS_INCLUDE) -I$(top_srcdir)/pinentry
> +LDADD = ../pinentry/libpinentry.a ../pinentry/libpinentry-emacs.a \
> +	../assuan/libassuan.a ../secmem/libsecmem.a \
> +	$(COMMON_LIBS) $(LIBCAP) $(LIBEMACS) $(LIBICONV)
> +
> +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/gnome3/Makefile.am b/gnome3/Makefile.am
> index 46639de..c8767df 100644
> --- a/gnome3/Makefile.am
> +++ b/gnome3/Makefile.am
> @@ -29,10 +29,16 @@ ncurses_include =
>  libcurses =
>  endif
>  
> +if INSIDE_EMACS
> +libemacs = ../pinentry/libpinentry-emacs.a
> +else
> +libemacs =
> +endif
> +
>  AM_CPPFLAGS = $(COMMON_CFLAGS) $(GNOME3CFLAGS) \
>  	$(ncurses_include) -I$(top_srcdir)/assuan \
>  	-I$(top_srcdir)/secmem -I$(top_srcdir)/pinentry
>  LDADD = ../pinentry/libpinentry.a ../assuan/libassuan.a ../secmem/libsecmem.a \
> -	$(COMMON_LIBS) $(LIBCAP) $(GNOME3LIBS) $(libcurses)
> +	$(COMMON_LIBS) $(LIBCAP) $(GNOME3LIBS) $(libcurses) $(libemacs)
>  
>  pinentry_gnome3_SOURCES = pinentry-gnome3.c
> diff --git a/gnome3/pinentry-gnome3.c b/gnome3/pinentry-gnome3.c
> index 74ec89c..113bde3 100644
> --- a/gnome3/pinentry-gnome3.c
> +++ b/gnome3/pinentry-gnome3.c
> @@ -37,6 +37,10 @@
>  #include "pinentry-curses.h"
>  #endif
>  
> +#ifdef INSIDE_EMACS
> +#include "pinentry-emacs.h"
> +#endif
> +
>  
>  #define PGMNAME "pinentry-gnome3"
>  
> @@ -253,14 +257,21 @@ main (int argc, char *argv[])
>  {
>    pinentry_init (PGMNAME);
>  
> -#ifdef FALLBACK_CURSES
> -  if (pinentry_have_display (argc, argv))
> -    gtk_init (&argc, &argv);
> +#ifdef INSIDE_EMACS
> +  if (pinentry_inside_emacs () && pinentry_emacs_init ())
> +    pinentry_cmd_handler = emacs_cmd_handler;
>    else
> -    pinentry_cmd_handler = curses_cmd_handler;
> +#endif
> +    {
> +#ifdef FALLBACK_CURSES
> +      if (pinentry_have_display (argc, argv))
> +	gtk_init (&argc, &argv);
> +      else
> +	pinentry_cmd_handler = curses_cmd_handler;
>  #else
> -  gtk_init (&argc, &argv);
> +      gtk_init (&argc, &argv);
>  #endif
> +    }
>  
>    pinentry_parse_opts (argc, argv);
>  
> diff --git a/gtk+-2/Makefile.am b/gtk+-2/Makefile.am
> index 7e37469..8e7717e 100644
> --- a/gtk+-2/Makefile.am
> +++ b/gtk+-2/Makefile.am
> @@ -29,10 +29,16 @@ ncurses_include =
>  libcurses =
>  endif
>  
> +if INSIDE_EMACS
> +libemacs = ../pinentry/libpinentry-emacs.a $(LIBEMACS) $(LIBICONV)
> +else
> +libemacs =
> +endif
> +
>  AM_CPPFLAGS = $(COMMON_CFLAGS) $(GTK2CFLAGS) $(ncurses_include) \
>  	-I$(top_srcdir)/secmem -I$(top_srcdir)/pinentry
>  LDADD = ../pinentry/libpinentry.a ../assuan/libassuan.a ../secmem/libsecmem.a \
> -	$(COMMON_LIBS) $(LIBCAP) $(GTK2LIBS) $(libcurses)
> +	$(COMMON_LIBS) $(LIBCAP) $(GTK2LIBS) $(libcurses) $(libemacs)
>  
>  pinentry_gtk_2_SOURCES = pinentry-gtk-2.c \
>  	gtksecentry.c gtksecentry.h gseal-gtk-compat.h
> diff --git a/gtk+-2/pinentry-gtk-2.c b/gtk+-2/pinentry-gtk-2.c
> index 1a88e5a..7792046 100644
> --- a/gtk+-2/pinentry-gtk-2.c
> +++ b/gtk+-2/pinentry-gtk-2.c
> @@ -52,6 +52,10 @@
>  #include "pinentry-curses.h"
>  #endif
>  
> +#ifdef INSIDE_EMACS
> +#include "pinentry-emacs.h"
> +#endif
> +
>  
>  #define PGMNAME "pinentry-gtk2"
>  
> @@ -716,17 +720,24 @@ main (int argc, char *argv[])
>  
>    pinentry_init (PGMNAME);
>  
> -#ifdef FALLBACK_CURSES
> -  if (pinentry_have_display (argc, argv))
> +#ifdef INSIDE_EMACS
> +  if (pinentry_inside_emacs () && pinentry_emacs_init ())
> +    pinentry_cmd_handler = emacs_cmd_handler;
> +  else
> +#endif
>      {
> -      if (! gtk_init_check (&argc, &argv))
> +#ifdef FALLBACK_CURSES
> +      if (pinentry_have_display (argc, argv))
> +	{
> +	  if (! gtk_init_check (&argc, &argv))
> +	    pinentry_cmd_handler = curses_cmd_handler;
> +	}
> +      else
>  	pinentry_cmd_handler = curses_cmd_handler;
> -    }
> -  else
> -    pinentry_cmd_handler = curses_cmd_handler;
>  #else
> -  gtk_init (&argc, &argv);
> +      gtk_init (&argc, &argv);
>  #endif
> +    }
>  
>    pinentry_parse_opts (argc, argv);
>  
> diff --git a/pinentry/Makefile.am b/pinentry/Makefile.am
> index 7fbbab6..e6e4ba6 100644
> --- a/pinentry/Makefile.am
> +++ b/pinentry/Makefile.am
> @@ -27,7 +27,13 @@ else
>  pinentry_curses =
>  endif
>  
> -noinst_LIBRARIES = libpinentry.a $(pinentry_curses)
> +if BUILD_LIBPINENTRY_EMACS
> +pinentry_emacs = libpinentry-emacs.a
> +else
> +pinentry_emacs =
> +endif
> +
> +noinst_LIBRARIES = libpinentry.a $(pinentry_curses) $(pinentry_emacs)
>  
>  LDADD = $(COMMON_LIBS)
>  AM_CPPFLAGS = $(COMMON_CFLAGS) -I$(top_srcdir)/assuan -I$(top_srcdir)/secmem
> @@ -35,3 +41,4 @@ AM_CPPFLAGS = $(COMMON_CFLAGS) -I$(top_srcdir)/assuan -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
> +libpinentry_emacs_a_SOURCES = pinentry-emacs.h pinentry-emacs.c
> diff --git a/pinentry/pinentry-emacs.c b/pinentry/pinentry-emacs.c
> new file mode 100644
> index 0000000..280546d
> --- /dev/null
> +++ b/pinentry/pinentry-emacs.c
> @@ -0,0 +1,640 @@
> +/* 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 <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 "pinentry.h"
> +#include "assuan.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$UID/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
> +
> +#define MIN(x, y) ((x) < (y) ? (x) : (y))
> +
> +#ifndef SUN_LEN
> +# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
> +                       + strlen ((ptr)->sun_path))
> +#endif
> +
> +static int emacs_socket = -1;
> +static char send_buffer[SEND_BUFFER_SIZE + 1];
> +static int send_buffer_length; /* Fill pointer for the send buffer.  */
> +
> +#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;
> +  uid_t uid;
> +
> +  unaddr.sun_family = AF_UNIX;
> +
> +  /* The socket address contains a UID, but POSIX doesn't define the
> +     maximum of uid_t:
> +     http://pubs.opengroup.org/onlinepubs/9699919799/xrat/V4_xsh_chap02.html#tag_22_02_12_01
> +     We only support 32-bit UIDs, which can be represented with 10
> +     decimal digits.
> +  */
> +  uid = getuid ();
> +  if (uid > 0x100000000)
> +    {
> +      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";
> +    }
> +
> +  if (strlen (tmpdir) + strlen ("/emacs") + 10 + strlen ("/")
> +      + strlen (socket_name) + 1 >= sizeof (unaddr.sun_path))
> +    {
> +      fprintf (stderr, "socket name is too long\n");
> +      return 0;
> +    }
> +  sprintf (unaddr.sun_path, "%s/emacs%lu/%s", tmpdir,
> +	   (unsigned long) uid, socket_name);
> +  free (tmpdir_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);
> +      return 0;
> +    }
> +
> +  return 1;
> +}
> +
> +/* Percent-escape control characters in DATA.  Return a newly
> +   allocated string, or DATA itself if there is no need to escape any
> +   character.  Store the length of the string to LENGTHP.  */
> +static char *
> +escape (const char *data, size_t *lengthp)
> +{
> +  char *buffer, *out_p;
> +  size_t length = *lengthp, buffer_length;
> +  size_t offset = 0;
> +  size_t count = 0;
> +
> +  while (offset < length)
> +    {
> +      switch (data[offset])
> +	{
> +	case '%': case '\n': case '\r':
> +	  count++;
> +	  break;
> +	default:
> +	  break;
> +	}
> +      offset++;
> +    }
> +
> +  if (count == 0)
> +    return (char *) data;
> +
> +  buffer_length = length + count * 2;
> +  buffer = malloc (buffer_length);
> +  if (!buffer)
> +    return NULL;
> +
> +  out_p = buffer;
> +  while (offset < length)
> +    {
> +      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;
> +	}
> +      offset++;
> +    }
> +
> +  *lengthp = buffer_length;
> +  return buffer;
> +}
> +
> +/* The inverse of escape.  Removes quoting in string STR by modifying
> +   the string in place.  */
> +static char *
> +unescape (char *data, size_t *lengthp)
> +{
> +  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;
> +  *lengthp = q - data;
> +  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)
> +{
> +  int retval = 1;
> +
> +  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));
> +	      retval = 0;
> +	      break;
> +	    }
> +	  if (sent != send_buffer_length)
> +	    memmove (send_buffer, &send_buffer[sent],
> +		     send_buffer_length - sent);
> +	  send_buffer_length -= sent;
> +	}
> +
> +      length -= part;
> +    }
> +
> +  return retval;
> +}
> +
> +/* Read a server response.  If the response contains any data, this
> +   function returns it as a string.  If BUFFER is not NULL, the data
> +   will be stored there.  Otherwise, it allocates memory.  */
> +static char *
> +read_from_emacs (int s, int timeout, char *buffer, size_t *lengthp,
> +		 assuan_error_t *error)
> +{
> +  struct timeval tv;
> +  fd_set rfds;
> +  int retval;
> +  size_t offset = 0;
> +  int allocated = buffer == NULL;
> +  size_t allocated_capacity = 0;
> +  int got_response = 0;
> +  char read_buffer[BUFSIZ + 1];
> +  size_t read_offset = 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 0;
> +    }
> +  else if (retval == 0)
> +    {
> +      timed_out = 1;
> +      return 0;
> +    }
> +
> +  while (!got_response)
> +    {
> +      int rl = 0;
> +      char *p, *end_p;
> +      do
> +	{
> +	  errno = 0;
> +	  rl = recv (s, read_buffer + read_offset,
> +		     sizeof (read_buffer) - 1 - 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");
> +	  *error = ASSUAN_General_Error;
> +	  goto error;
> +	}
> +      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 the contents away.
> +	 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;
> +
> +      /* 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;
> +
> +	      data = p + 2;
> +	      data_length = end_p - data;
> +
> +	      if (allocated && offset + data_length > allocated_capacity)
> +		{
> +		  allocated_capacity = allocated_capacity * 2 + 10;
> +		  buffer = realloc (buffer, allocated_capacity);
> +		  if (!buffer)
> +		    {
> +		      *error = ASSUAN_Out_Of_Core;
> +		      goto error;
> +		    }
> +		}
> +	      else if (!allocated && offset + data_length > *lengthp)
> +		{
> +		  *error = ASSUAN_General_Error;
> +		  goto error;
> +		}
> +
> +	      memcpy (&buffer[offset], data, data_length);
> +	      offset += data_length;
> +	    }
> +          else if (!strcmp ("OK", p) || !strncmp ("OK ", p, 3))
> +            {
> +	      *error = ASSUAN_No_Error;
> +	      got_response = 1;
> +	      break;
> +            }
> +          else if (!strncmp ("ERR ", p, 4))
> +            {
> +	      unsigned long code = strtoul (p + 4, NULL, 10);
> +	      if (code == ULONG_MAX && errno == ERANGE)
> +		*error = ASSUAN_General_Error;
> +	      else
> +		*error = code;
> +	      got_response = 1;
> +	      break;
> +            }
> +	  else if (!strncmp ("# ", p, 2))
> +	    ;
> +	  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;
> +	}
> +    }
> +
> +  if (*error == ASSUAN_No_Error)
> +    {
> +      *lengthp = offset;
> +      return buffer;
> +    }
> +
> + error:
> +  if (allocated)
> +    free (buffer);
> +
> +  return NULL;
> +}
> +
> +int
> +set_label (pinentry_t pe, const char *name, const char *value)
> +{
> +  char buffer[16], *escaped;
> +  size_t length;
> +  assuan_error_t error;
> +  int retval;
> +
> +  if (!send_to_emacs (emacs_socket, name, strlen (name))
> +      || !send_to_emacs (emacs_socket, " ", 1))
> +    return 0;
> +
> +  length = strlen (value);
> +  escaped = escape (value, &length);
> +  if (!escaped)
> +    return 0;
> +
> +  retval = send_to_emacs (emacs_socket, escaped, length)
> +    && send_to_emacs (emacs_socket, "\n", 1);
> +
> +  if (escaped != value)
> +    free (escaped);
> +  if (!retval)
> +    return 0;
> +
> +  length = sizeof (buffer);
> +  read_from_emacs (emacs_socket, pe->timeout, buffer, &length, &error);
> +  return error == ASSUAN_No_Error;
> +}
> +
> +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 *escaped, *password;
> +  size_t length;
> +  assuan_error_t error;
> +
> +  set_labels (pe);
> +
> +  if (!send_to_emacs (emacs_socket, "GETPIN\n", 7))
> +    return -1;
> +
> +  escaped = read_from_emacs (emacs_socket, pe->timeout, NULL, &length, &error);
> +  if (error != ASSUAN_No_Error)
> +    {
> +      pe->specific_err = error;
> +      return -1;
> +    }
> +  /* No data sent from the server - maybe cancelled.  */
> +  if (!escaped)
> +    {
> +      pe->specific_err = ASSUAN_General_Error;
> +      return -1;
> +    }
> +
> +  password = unescape (escaped, &length);
> +  pinentry_setbufferlen (pe, length);
> +  if (pe->pin)
> +    memcpy (pe->pin, password, length);
> +
> +  if (pe->repeat_passphrase)
> +    pe->repeat_okay = 1;
> +
> +  if (password != escaped)
> +    free (password);
> +  free (escaped);
> +
> +  /* XXX: we don't support external password cache (yet).  */
> +
> +  return 1;
> +}
> +
> +static int
> +do_confirm (pinentry_t pe)
> +{
> +  char buffer[16];
> +  size_t length;
> +  assuan_error_t error;
> +
> +  set_labels (pe);
> +
> +  if (!send_to_emacs (emacs_socket, "CONFIRM\n", 8))
> +    return 0;
> +
> +  length = sizeof (buffer);
> +  read_from_emacs (emacs_socket, pe->timeout, buffer, &length, &error);
> +  if (error != ASSUAN_No_Error)
> +    {
> +      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;
> +}
> +
> +int
> +pinentry_inside_emacs (void)
> +{
> +  const char *envvar;
> +
> +  /* Check if INSIDE_EMACS envvar is set.  */
> +  envvar = getenv ("INSIDE_EMACS");
> +  if (!envvar || !*envvar)
> +    return 0;
> +
> +  /* FIXME: Additional checks for the value.  */
> +  return 1;
> +}
> +
> +int
> +pinentry_emacs_init (void)
> +{
> +  char buffer[256];
> +  size_t length = sizeof (buffer);
> +  assuan_error_t error;
> +
> +  /* Check if we can connect to the Emacs server socket.  */
> +  if (!set_socket ("pinentry"))
> +    return 0;
> +
> +  /* Check if the server responds.  */
> +  read_from_emacs (emacs_socket, INITIAL_TIMEOUT, buffer, &length, &error);
> +  return error == ASSUAN_No_Error;
> +}
> diff --git a/pinentry/pinentry-emacs.h b/pinentry/pinentry-emacs.h
> new file mode 100644
> index 0000000..732c0ac
> --- /dev/null
> +++ b/pinentry/pinentry-emacs.h
> @@ -0,0 +1,41 @@
> +/* 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
> +
> +/* Return true if INSIDE_EMACS is set. */
> +int pinentry_inside_emacs (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/qt4/Makefile.am b/qt4/Makefile.am
> index 816aade..c2ee3c6 100644
> --- a/qt4/Makefile.am
> +++ b/qt4/Makefile.am
> @@ -33,6 +33,12 @@ ncurses_include =
>  libcurses =
>  endif
>  
> +if INSIDE_EMACS
> +libemacs = ../pinentry/libpinentry-emacs.a $(LIBEMACS) $(LIBICONV)
> +else
> +libemacs =
> +endif
> +
>  
>  AM_CPPFLAGS = $(COMMON_CFLAGS) \
>  	-I$(top_srcdir) -I$(top_srcdir)/assuan -I$(top_srcdir)/secmem \
> @@ -41,7 +47,8 @@ AM_CXXFLAGS = $(QT4_CORE_CFLAGS) $(QT4_GUI_CFLAGS)
>  pinentry_qt4_LDADD = \
>  	../pinentry/libpinentry.a $(top_builddir)/assuan/libassuan.a \
>  	$(top_builddir)/secmem/libsecmem.a \
> -	$(COMMON_LIBS) $(QT4_CORE_LIBS) $(QT4_GUI_LIBS) $(libcurses) $(LIBCAP)
> +	$(COMMON_LIBS) $(QT4_CORE_LIBS) $(QT4_GUI_LIBS) \
> +	$(libcurses) $(libemacs) $(LIBCAP)
>  
>  BUILT_SOURCES = \
>  	pinentryconfirm.moc qsecurelineedit.moc pinentrydialog.moc
> diff --git a/qt4/main.cpp b/qt4/main.cpp
> index 37b6e7b..bb682b3 100644
> --- a/qt4/main.cpp
> +++ b/qt4/main.cpp
> @@ -50,6 +50,10 @@
>  #include <pinentry-curses.h>
>  #endif
>  
> +#ifdef INSIDE_EMACS
> +#include "pinentry-emacs.h"
> +#endif
> +
>  static QString escape_accel( const QString & s ) {
>  
>    QString result;
> @@ -263,51 +267,58 @@ main (int argc, char *argv[])
>  
>    std::auto_ptr<QApplication> app;
>  
> -#ifdef FALLBACK_CURSES
> -  if (!pinentry_have_display (argc, argv))
> -    pinentry_cmd_handler = curses_cmd_handler;
> +#ifdef INSIDE_EMACS
> +  if (pinentry_inside_emacs () && pinentry_emacs_init ())
> +    pinentry_cmd_handler = emacs_cmd_handler;
>    else
>  #endif
>      {
> -      /* Qt does only understand -display but not --display; thus we
> -         are fixing that here.  The code is pretty simply and may get
> -         confused if an argument is called "--display". */
> -      char **new_argv, *p;
> -      size_t n;
> -      int i, done;
> -
> -      for (n=0,i=0; i < argc; i++)
> -        n += strlen (argv[i])+1;
> -      n++;
> -      new_argv = (char**)calloc (argc+1, sizeof *new_argv);
> -      if (new_argv)
> -        *new_argv = (char*)malloc (n);
> -      if (!new_argv || !*new_argv)
> -        {
> -          fprintf (stderr, "pinentry-qt4: can't fixup argument list: %s\n",
> -                   strerror (errno));
> -          exit (EXIT_FAILURE);
> -
> -        }
> -      for (done=0,p=*new_argv,i=0; i < argc; i++)
> -        if (!done && !strcmp (argv[i], "--display"))
> -          {
> -            new_argv[i] = strcpy (p, argv[i]+1);
> -            p += strlen (argv[i]+1) + 1;
> -            done = 1;
> -          }
> -        else
> -          {
> -            new_argv[i] = strcpy (p, argv[i]);
> -            p += strlen (argv[i]) + 1;
> -          }
> +#ifdef FALLBACK_CURSES
> +      if (!pinentry_have_display (argc, argv))
> +	pinentry_cmd_handler = curses_cmd_handler;
> +      else
> +#endif
> +	{
> +	  /* Qt does only understand -display but not --display; thus we
> +	     are fixing that here.  The code is pretty simply and may get
> +	     confused if an argument is called "--display". */
> +	  char **new_argv, *p;
> +	  size_t n;
> +	  int i, done;
> +
> +	  for (n=0,i=0; i < argc; i++)
> +	    n += strlen (argv[i])+1;
> +	  n++;
> +	  new_argv = (char**)calloc (argc+1, sizeof *new_argv);
> +	  if (new_argv)
> +	    *new_argv = (char*)malloc (n);
> +	  if (!new_argv || !*new_argv)
> +	    {
> +	      fprintf (stderr, "pinentry-qt4: can't fixup argument list: %s\n",
> +		       strerror (errno));
> +	      exit (EXIT_FAILURE);
>  
> -      /* We use a modal dialog window, so we don't need the application
> -         window anymore.  */
> -      i = argc;
> -      app.reset (new QApplication (i, new_argv));
> -      const QIcon icon( QLatin1String( ":/document-encrypt.png" ) );
> -      app->setWindowIcon( icon );
> +	    }
> +	  for (done=0,p=*new_argv,i=0; i < argc; i++)
> +	    if (!done && !strcmp (argv[i], "--display"))
> +	      {
> +		new_argv[i] = strcpy (p, argv[i]+1);
> +		p += strlen (argv[i]+1) + 1;
> +		done = 1;
> +	      }
> +	    else
> +	      {
> +		new_argv[i] = strcpy (p, argv[i]);
> +		p += strlen (argv[i]) + 1;
> +	      }
> +
> +	  /* We use a modal dialog window, so we don't need the application
> +	     window anymore.  */
> +	  i = argc;
> +	  app.reset (new QApplication (i, new_argv));
> +	  const QIcon icon( QLatin1String( ":/document-encrypt.png" ) );
> +	  app->setWindowIcon( icon );
> +	}
>      }
>  
>  
> -- 
> 2.1.0
> 
> [3 pinentry.el <text/plain (7bit)>]
> ;;; pinentry.el --- GnuPG Pinentry server implementation -*- lexical-binding: t -*-
> 
> ;; Copyright (C) 2015 Free Software Foundation, Inc.
> 
> ;; Author: Daiki Ueno <ueno at gnu.org>
> ;; Keywords: GnuPG
> 
> ;; This file is part of GNU Emacs.
> 
> ;; GNU Emacs 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 3 of the License, or
> ;; (at your option) any later version.
> 
> ;; GNU Emacs 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 GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
> 
> ;;; Code:
> 
> (defvar pinentry--server-process nil)
> (defvar pinentry--connection-process-list nil)
> 
> (defvar pinentry--labels nil)
> (put 'pinentry-read-point 'permanent-local t)
> (defvar pinentry--read-point nil)
> (put 'pinentry--read-point 'permanent-local t)
> 
> ;; We use the same location as `server-socket-dir', when local sockets
> ;; are supported.
> (defvar pinentry--socket-dir
>   (format "%s/emacs%d" (or (getenv "TMPDIR") "/tmp") (user-uid))
>   "The directory in which to place the server socket.
> If local sockets are not supported, this is nil.")
> 
> (defconst pinentry--set-label-commands
>   '("SETPROMPT" "SETTITLE" "SETDESC"
>     "SETREPEAT" "SETREPEATERROR"
>     "SETOK" "SETCANCEL" "SETNOTOK"))
> 
> (defmacro pinentry--error-code (code)
>   (logior (lsh 5 24) code))
> (defconst pinentry--error-not-implemented
>   (cons (pinentry--error-code 69) "not implemented"))
> (defconst pinentry--error-cancelled
>   (cons (pinentry--error-code 99) "cancelled"))
> (defconst pinentry--error-not-confirmed
>   (cons (pinentry--error-code 114) "not confirmed"))
> 
> (autoload 'server-ensure-safe-dir "server")
> 
> ;;;###autoload
> (defun pinentry-start ()
>   "Start a Pinentry service."
>   (interactive)
>   (unless (featurep 'make-network-process '(:family local))
>     (error "local sockets are not supported"))
>   (if (process-live-p pinentry--server-process)
>       (message "Pinentry service is already running")
>     (let* ((server-file (expand-file-name "pinentry" pinentry--socket-dir)))
>       (server-ensure-safe-dir pinentry--socket-dir)
>       ;; Delete the socket files made by previous server invocations.
>       (ignore-errors
>         (let (delete-by-moving-to-trash)
>           (delete-file server-file)))
>       (setq pinentry--server-process
>             (make-network-process
>              :name "pinentry"
>              :server t
>              :noquery t
>              :sentinel #'pinentry--process-sentinel
>              :filter #'pinentry--process-filter
>              :coding 'no-conversion
>              :family 'local
>              :service server-file))
>       (process-put pinentry--server-process :server-file server-file))))
> 
> (defun pinentry-stop ()
>   "Stop a Pinentry service."
>   (interactive)
>   (when (process-live-p pinentry--server-process)
>     (delete-process pinentry--server-process))
>   (setq pinentry--server-process nil)
>   (dolist (process pinentry--connection-process-list)
>     (when (buffer-live-p (process-buffer process))
>       (kill-buffer (process-buffer process))))
>   (setq pinentry--connection-process-list nil))
> 
> (defun pinentry--labels-to-shortcuts (labels)
>   (mapcar (lambda (label)
>             (when label
>               (if (string-match "_\\([[:alnum:]]\\)" label)
>                   (let* ((key (match-string 1 label))
>                          (c (downcase (aref key 0))))
>                     (setq label (replace-match
>                                  (propertize key 'face 'underline)
>                                  t t label))
>                     (cons c label))
>                 (cons (if (= (length label) 0)
>                           ??
>                         (downcase (aref 0 label)))
>                       label))))
>           labels))
> 
> (defun pinentry--escape-string (string)
>   "Escape STRING in the Assuan percent escape."
>   (let ((length (length string))
>         (index 0)
>         (count 0))
>     (while (< index length)
>       (if (memq (aref string index) '(?\n ?\r ?%))
>           (setq count (1+ count)))
>       (setq index (1+ index)))
>     (setq index 0)
>     (let ((result (make-string (+ length (* count 2)) ?\0))
>           (result-index 0)
>           c)
>       (while (< index length)
>         (setq c (aref string index))
>         (if (memq c '(?\n ?\r ?%))
>             (let ((hex (format "%02X" c)))
>               (aset result result-index ?%)
>               (setq result-index (1+ result-index))
>               (aset result result-index (aref hex 0))
>               (setq result-index (1+ result-index))
>               (aset result result-index (aref hex 1))
>               (setq result-index (1+ result-index)))
>           (aset result result-index c)
>           (setq result-index (1+ result-index)))
>         (setq index (1+ index)))
>       result)))
> 
> (defun pinentry--send-data (process escaped)
>   "Send a string ESCAPED to a process PROCESS.
> ESCAPED will be split if it exceeds the line length limit of the
> Assuan protocol."
>   (let ((length (length escaped))
>         (index 0))
>     (if (= length 0)
>         (process-send-string process "D \n")
>       (while (< index length)
>         ;; 997 = ASSUAN_LINELENGTH (= 1000) - strlen ("D \n")
>         (let* ((sub-length (min (- length index) 997))
>                (sub (substring escaped index (+ index sub-length))))
>           (unwind-protect
>               (progn
>                 (process-send-string process "D ")
>                 (process-send-string process sub)
>                 (process-send-string process "\n"))
>             (clear-string sub))
>           (setq index (+ index sub-length)))))))
> 
> (defun pinentry--send-error (process error)
>   (process-send-string process (format "ERR %d %s\n" (car error) (cdr error))))
> 
> (defun pinentry--process-filter (process input)
>   (unless (buffer-live-p (process-buffer process))
>     (let ((buffer (generate-new-buffer " *pinentry*")))
>       (set-process-buffer process buffer)
>       (with-current-buffer buffer
>         (if (fboundp 'set-buffer-multibyte)
>             (set-buffer-multibyte nil))
>         (make-local-variable 'pinentry--read-point)
>         (setq pinentry--read-point (point-min))
>         (make-local-variable 'pinentry--labels))))
>   (with-current-buffer (process-buffer process)
>     (save-excursion
>       (goto-char (point-max))
>       (insert input)
>       (goto-char pinentry--read-point)
>       (beginning-of-line)
>       (while (looking-at ".*\n")        ;the input line finished
>         (if (looking-at "\\([A-Z_]+\\) ?\\(.*\\)")
>             (let ((command (match-string 1))
>                   (string (match-string 2)))
>               (pcase command
>                 ((and set (guard (member set pinentry--set-label-commands)))
> 		 (when (> (length string) 0)
> 		   (let* ((symbol (intern (downcase (substring set 3))))
> 			  (entry (assq symbol pinentry--labels))
> 			  (label (decode-coding-string string 'utf-8)))
> 		     (if entry
> 			 (setcdr entry label)
> 		       (push (cons symbol label) pinentry--labels))))
>                  (process-send-string process "OK\n"))
>                 ("NOP"
>                  (process-send-string process "OK\n"))
>                 ("GETPIN"
>                  (let ((prompt
>                         (or (cdr (assq 'desc pinentry--labels))
>                             (cdr (assq 'prompt pinentry--labels))
>                             ""))
> 		       (confirm (not (null (assq 'repeat pinentry--labels))))
>                        entry)
>                    (if (setq entry (assq 'error pinentry--labels))
>                        (setq prompt (concat "Error: "
>                                             (propertize
>                                              (copy-sequence (cdr entry))
>                                              'face 'error)
>                                             "\n"
>                                             prompt)))
>                    (if (setq entry (assq 'title pinentry--labels))
>                        (setq prompt (format "[%s] %s"
>                                             (cdr entry) prompt)))
>                    (if (string-match ":?[ \n]*\\'" prompt)
>                        (setq prompt (concat
>                                      (substring
>                                       prompt 0 (match-beginning 0)) ": ")))
>                    (let (passphrase escaped-passphrase encoded-passphrase)
>                      (unwind-protect
>                          (condition-case nil
>                              (progn
>                                (setq passphrase
> 				     (read-passwd prompt confirm))
>                                (setq escaped-passphrase
>                                      (pinentry--escape-string
>                                       passphrase))
>                                (setq encoded-passphrase (encode-coding-string
>                                                          escaped-passphrase
>                                                          'utf-8))
>                                (pinentry--send-data
>                                 process encoded-passphrase)
>                                (process-send-string process "OK\n"))
>                            (error
>                             (pinentry--send-error
> 			     process
> 			     pinentry--error-cancelled)))
>                        (if passphrase
>                            (clear-string passphrase))
>                        (if escaped-passphrase
>                            (clear-string escaped-passphrase))
>                        (if encoded-passphrase
>                            (clear-string encoded-passphrase))))
>                    (setq pinentry--labels nil)))
>                 ("CONFIRM"
>                  (let ((prompt
>                         (or (cdr (assq 'desc pinentry--labels))
>                             ""))
>                        (buttons
>                         (pinentry--labels-to-shortcuts
>                          (list (cdr (assq 'ok pinentry--labels))
>                                (cdr (assq 'notok pinentry--labels))
> 			       (cdr (assq 'cancel pinentry--labels)))))
>                        entry)
>                    (if (setq entry (assq 'error pinentry--labels))
>                        (setq prompt (concat "Error: "
>                                             (propertize
>                                              (copy-sequence (cdr entry))
>                                              'face 'error)
>                                             "\n"
>                                             prompt)))
>                    (if (setq entry (assq 'title pinentry--labels))
>                        (setq prompt (format "[%s] %s"
>                                             (cdr entry) prompt)))
>                    (if (remq nil buttons)
>                        (progn
>                          (setq prompt
>                                (concat prompt " ("
>                                        (mapconcat #'cdr (remq nil buttons)
>                                                   ", ")
>                                        ") "))
>                          (condition-case nil
>                              (let ((result (read-char prompt)))
>                                (if (eq result (caar buttons))
>                                    (process-send-string process "OK\n")
>                                  (if (eq result (car (nth 1 buttons)))
>                                      (pinentry--send-error
> 				      process
> 				      pinentry--error-not-confirmed)
> 				   (pinentry--send-error
> 				    process
> 				    pinentry--error-cancelled))))
>                            (error
>                             (pinentry--send-error
> 			     process
> 			     pinentry--error-cancelled))))
>                      (if (string-match "[ \n]*\\'" prompt)
>                          (setq prompt (concat
>                                        (substring
>                                         prompt 0 (match-beginning 0)) " ")))
>                      (if (condition-case nil
>                              (y-or-n-p prompt)
>                            (quit))
>                          (process-send-string process "OK\n")
>                        (pinentry--send-error
> 			process
> 			pinentry--error-not-confirmed)))
>                    (setq pinentry--labels nil)))
>                 (_ (pinentry--send-error
> 		    process
> 		    pinentry--error-not-implemented)))
>               (forward-line)
>               (setq pinentry--read-point (point))))))))
> 
> (defun pinentry--process-sentinel (process _status)
>   "The process sentinel for Emacs server connections."
>   ;; If this is a new client process, set the query-on-exit flag to nil
>   ;; for this process (it isn't inherited from the server process).
>   (when (and (eq (process-status process) 'open)
> 	     (process-query-on-exit-flag process))
>     (push process pinentry--connection-process-list)
>     (set-process-query-on-exit-flag process nil)
>     (process-send-string process "OK Your orders please\n"))
>   ;; Kill the process buffer of the connection process.
>   (when (and (not (process-contact process :server))
> 	     (eq (process-status process) 'closed))
>     (when (buffer-live-p (process-buffer process))
>       (kill-buffer (process-buffer process)))
>     (setq pinentry--connection-process-list
> 	  (delq process pinentry--connection-process-list)))
>   ;; Delete the associated connection file, if applicable.
>   ;; Although there's no 100% guarantee that the file is owned by the
>   ;; running Emacs instance, server-start uses server-running-p to check
>   ;; for possible servers before doing anything, so it *should* be ours.
>   (and (process-contact process :server)
>        (eq (process-status process) 'closed)
>        (ignore-errors
> 	 (delete-file (process-get process :server-file)))))
> 
> (provide 'pinentry)
> 
> ;;; pinentry.el ends here



More information about the Gnupg-devel mailing list