Imported Upstream version 2.1.9
[platform/upstream/gpg2.git] / agent / gpg-agent.c
index 720f5f4..f81a2fb 100644 (file)
 #endif
 #include <npth.h>
 
-#define JNLIB_NEED_LOG_LOGV
-#define JNLIB_NEED_AFLOCAL
+#define GNUPG_COMMON_NEED_AFLOCAL
 #include "agent.h"
 #include <assuan.h> /* Malloc hooks  and socket wrappers. */
 
 #include "i18n.h"
-#include "mkdtemp.h" /* Gnulib replacement. */
 #include "sysutils.h"
 #include "gc-opt-flags.h"
 #include "exechelp.h"
@@ -81,6 +79,7 @@ enum cmd_and_opt_values
   oDebugLevel,
   oDebugWait,
   oDebugQuickRandom,
+  oDebugPinentry,
   oNoGreeting,
   oNoOptions,
   oHomedir,
@@ -93,6 +92,7 @@ enum cmd_and_opt_values
 
   oPinentryProgram,
   oPinentryTouchFile,
+  oPinentryInvisibleChar,
   oDisplay,
   oTTYname,
   oTTYtype,
@@ -112,6 +112,8 @@ enum cmd_and_opt_values
   oEnablePassphraseHistory,
   oUseStandardSocket,
   oNoUseStandardSocket,
+  oExtraSocket,
+  oBrowserSocket,
   oFakedSystemTime,
 
   oIgnoreCacheForSigning,
@@ -119,6 +121,8 @@ enum cmd_and_opt_values
   oNoAllowMarkTrusted,
   oAllowPresetPassphrase,
   oAllowLoopbackPinentry,
+  oNoAllowExternalCache,
+  oAllowEmacsPinentry,
   oKeepTTY,
   oKeepDISPLAY,
   oSSHSupport,
@@ -129,89 +133,130 @@ enum cmd_and_opt_values
 };
 
 
+#ifndef ENAMETOOLONG
+# define ENAMETOOLONG EINVAL
+#endif
+
 
 static ARGPARSE_OPTS opts[] = {
 
-  { aGPGConfList, "gpgconf-list", 256, "@" },
-  { aGPGConfTest, "gpgconf-test", 256, "@" },
-  { aUseStandardSocketP, "use-standard-socket-p", 256, "@" },
-
-  { 301, NULL, 0, N_("@Options:\n ") },
-
-  { oDaemon,   "daemon",     0, N_("run in daemon mode (background)") },
-  { oServer,   "server",     0, N_("run in server mode (foreground)") },
-  { oVerbose, "verbose",     0, N_("verbose") },
-  { oQuiet,    "quiet",     0, N_("be somewhat more quiet") },
-  { oSh,       "sh",        0, N_("sh-style command output") },
-  { oCsh,      "csh",       0, N_("csh-style command output") },
-  { oOptions, "options"  , 2, N_("|FILE|read options from FILE")},
-  { oDebug,    "debug"     ,4|16, "@"},
-  { oDebugAll, "debug-all"     ,0, "@"},
-  { oDebugLevel, "debug-level" ,2, "@"},
-  { oDebugWait,"debug-wait",1, "@"},
+  ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
+  ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
+  ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"),
+
+  ARGPARSE_group (301, N_("@Options:\n ")),
+
+  ARGPARSE_s_n (oDaemon,  "daemon", N_("run in daemon mode (background)")),
+  ARGPARSE_s_n (oServer,  "server", N_("run in server mode (foreground)")),
+  ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
+  ARGPARSE_s_n (oQuiet,          "quiet",     N_("be somewhat more quiet")),
+  ARGPARSE_s_n (oSh,     "sh",        N_("sh-style command output")),
+  ARGPARSE_s_n (oCsh,    "csh",       N_("csh-style command output")),
+  ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
+
+  ARGPARSE_s_s (oDebug,             "debug",       "@"),
+  ARGPARSE_s_n (oDebugAll,   "debug-all",   "@"),
+  ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
+  ARGPARSE_s_i (oDebugWait,"  debug-wait",  "@"),
   ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"),
-  { oNoDetach, "no-detach" ,0, N_("do not detach from the console")},
-  { oNoGrab, "no-grab"     ,0, N_("do not grab keyboard and mouse")},
-  { oLogFile, "log-file"   ,2, N_("use a log file for the server")},
-  { oUseStandardSocket, "use-standard-socket", 0, "@"},      /* dummy */
-  { oNoUseStandardSocket, "no-use-standard-socket", 0, "@"}, /* dummy */
-  { oPinentryProgram, "pinentry-program", 2 ,
-                               N_("|PGM|use PGM as the PIN-Entry program") },
-  { oPinentryTouchFile, "pinentry-touch-file", 2 , "@" },
-  { oScdaemonProgram, "scdaemon-program", 2 ,
-                               N_("|PGM|use PGM as the SCdaemon program") },
-  { oDisableScdaemon, "disable-scdaemon", 0, N_("do not use the SCdaemon") },
-  { oDisableCheckOwnSocket, "disable-check-own-socket", 0, "@" },
-  { oFakedSystemTime, "faked-system-time", 2, "@" }, /* (epoch time) */
-
-  { oBatch,      "batch",       0, "@" },
-  { oHomedir,    "homedir",     2, "@"},
-
-  { oDisplay,    "display",     2, "@" },
-  { oTTYname,    "ttyname",     2, "@" },
-  { oTTYtype,    "ttytype",     2, "@" },
-  { oLCctype,    "lc-ctype",    2, "@" },
-  { oLCmessages, "lc-messages", 2, "@" },
-  { oXauthority, "xauthority",  2, "@" },
-  { oKeepTTY,    "keep-tty",    0,  N_("ignore requests to change the TTY")},
-  { oKeepDISPLAY, "keep-display",
-                          0, N_("ignore requests to change the X display")},
-
-  { oDefCacheTTL, "default-cache-ttl", 4,
-                               N_("|N|expire cached PINs after N seconds")},
-  { oDefCacheTTLSSH, "default-cache-ttl-ssh", 4, "@" },
-  { oMaxCacheTTL, "max-cache-ttl", 4, "@" },
-  { oMaxCacheTTLSSH, "max-cache-ttl-ssh", 4, "@" },
-
-  { oEnforcePassphraseConstraints, "enforce-passphrase-constraints", 0, "@"},
-  { oMinPassphraseLen, "min-passphrase-len", 4, "@" },
-  { oMinPassphraseNonalpha, "min-passphrase-nonalpha", 4, "@" },
-  { oCheckPassphrasePattern, "check-passphrase-pattern", 2, "@" },
-  { oMaxPassphraseDays, "max-passphrase-days", 4, "@" },
-  { oEnablePassphraseHistory, "enable-passphrase-history", 0, "@" },
-
-  { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0,
-                               N_("do not use the PIN cache when signing")},
-  { oNoAllowMarkTrusted, "no-allow-mark-trusted", 0,
-                            N_("disallow clients to mark keys as \"trusted\"")},
-  { oAllowMarkTrusted, "allow-mark-trusted", 0, "@"},
-  { oAllowPresetPassphrase, "allow-preset-passphrase", 0,
-                             N_("allow presetting passphrase")},
-  { oAllowLoopbackPinentry, "allow-loopback-pinentry", 0,
-                             N_("allow presetting passphrase")},
-  { oSSHSupport, "enable-ssh-support", 0, N_("enable ssh support") },
-  { oPuttySupport, "enable-putty-support", 0,
+  ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"),
+
+  ARGPARSE_s_n (oNoDetach,  "no-detach", N_("do not detach from the console")),
+  ARGPARSE_s_n (oNoGrab,    "no-grab",   N_("do not grab keyboard and mouse")),
+  ARGPARSE_s_s (oLogFile,   "log-file",  N_("use a log file for the server")),
+  ARGPARSE_s_s (oPinentryProgram, "pinentry-program",
+                /* */             N_("|PGM|use PGM as the PIN-Entry program")),
+  ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"),
+  ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"),
+  ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program",
+                /* */             N_("|PGM|use PGM as the SCdaemon program") ),
+  ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon",
+                /* */             N_("do not use the SCdaemon") ),
+  ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
+
+  ARGPARSE_s_s (oExtraSocket, "extra-socket",
+                /* */       N_("|NAME|accept some commands via NAME")),
+
+  ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"),
+
+  ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
+
+  ARGPARSE_s_n (oBatch,      "batch",        "@"),
+  ARGPARSE_s_s (oHomedir,    "homedir",      "@"),
+
+  ARGPARSE_s_s (oDisplay,    "display",     "@"),
+  ARGPARSE_s_s (oTTYname,    "ttyname",     "@"),
+  ARGPARSE_s_s (oTTYtype,    "ttytype",     "@"),
+  ARGPARSE_s_s (oLCctype,    "lc-ctype",    "@"),
+  ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
+  ARGPARSE_s_s (oXauthority, "xauthority",  "@"),
+  ARGPARSE_s_n (oKeepTTY,    "keep-tty",
+                /* */        N_("ignore requests to change the TTY")),
+  ARGPARSE_s_n (oKeepDISPLAY, "keep-display",
+                /* */        N_("ignore requests to change the X display")),
+
+  ARGPARSE_s_u (oDefCacheTTL,    "default-cache-ttl",
+                                 N_("|N|expire cached PINs after N seconds")),
+  ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh", "@" ),
+  ARGPARSE_s_u (oMaxCacheTTL,    "max-cache-ttl",         "@" ),
+  ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh",     "@" ),
+
+  ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints",
+                /* */                          "@"),
+  ARGPARSE_s_u (oMinPassphraseLen,        "min-passphrase-len", "@"),
+  ARGPARSE_s_u (oMinPassphraseNonalpha,   "min-passphrase-nonalpha", "@"),
+  ARGPARSE_s_s (oCheckPassphrasePattern,  "check-passphrase-pattern", "@"),
+  ARGPARSE_s_u (oMaxPassphraseDays,       "max-passphrase-days", "@"),
+  ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history", "@"),
+
+  ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing",
+                /* */    N_("do not use the PIN cache when signing")),
+  ARGPARSE_s_n (oNoAllowExternalCache,  "no-allow-external-cache",
+                /* */    N_("disallow the use of an external password cache")),
+  ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted",
+                /* */    N_("disallow clients to mark keys as \"trusted\"")),
+  ARGPARSE_s_n (oAllowMarkTrusted,   "allow-mark-trusted", "@"),
+  ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase",
+                /* */                    N_("allow presetting passphrase")),
+  ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry",
+                                   N_("allow caller to override the pinentry")),
+  ARGPARSE_s_n (oAllowEmacsPinentry,  "allow-emacs-pinentry",
+                /* */    N_("allow passphrase to be prompted through Emacs")),
+
+  ARGPARSE_s_n (oSSHSupport,   "enable-ssh-support", N_("enable ssh support")),
+  ARGPARSE_s_n (oPuttySupport, "enable-putty-support",
 #ifdef HAVE_W32_SYSTEM
-      N_("enable putty support")
+                /* */           N_("enable putty support")
 #else
-      "@"
+                /* */           "@"
 #endif
-  },
-  { oWriteEnvFile, "write-env-file", 2|8, "@" }, /* dummy */
-  {0}
+                ),
+
+  /* Dummy options for backward compatibility.  */
+  ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"),
+  ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"),
+  ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"),
+
+  {0} /* End of list */
 };
 
 
+/* The list of supported debug flags.  */
+static struct debug_flags_s debug_flags [] =
+  {
+    { DBG_COMMAND_VALUE, "command"  },
+    { DBG_MPI_VALUE    , "mpi"     },
+    { DBG_CRYPTO_VALUE , "crypto"  },
+    { DBG_MEMORY_VALUE , "memory"  },
+    { DBG_CACHE_VALUE  , "cache"   },
+    { DBG_MEMSTAT_VALUE, "memstat" },
+    { DBG_HASHING_VALUE, "hashing" },
+    { DBG_IPC_VALUE    , "ipc"     },
+    { 77, NULL } /* 77 := Do not exit on "help" or "?".  */
+  };
+
+
+
 #define DEFAULT_CACHE_TTL     (10*60)  /* 10 minutes */
 #define DEFAULT_CACHE_TTL_SSH (30*60)  /* 30 minutes */
 #define MAX_CACHE_TTL         (120*60) /* 2 hours */
@@ -237,6 +282,9 @@ static ARGPARSE_OPTS opts[] = {
 #endif
 
 
+/* Flag indicating that the ssh-agent subsystem has been enabled.  */
+static int ssh_support;
+
 #ifdef HAVE_W32_SYSTEM
 /* Flag indicating that support for Putty has been enabled.  */
 static int putty_support;
@@ -270,15 +318,29 @@ static int disable_check_own_socket;
 /* It is possible that we are currently running under setuid permissions */
 static int maybe_setuid = 1;
 
-/* Name of the communication socket used for native gpg-agent requests.  */
+/* Name of the communication socket used for native gpg-agent
+   requests. The second variable is either NULL or a malloced string
+   with the real socket name in case it has been redirected.  */
 static char *socket_name;
+static char *redir_socket_name;
+
+/* Name of the optional extra socket used for native gpg-agent requests.  */
+static char *socket_name_extra;
+static char *redir_socket_name_extra;
+
+/* Name of the optional browser socket used for native gpg-agent requests.  */
+static char *socket_name_browser;
+static char *redir_socket_name_browser;
 
 /* Name of the communication socket used for ssh-agent-emulation.  */
 static char *socket_name_ssh;
+static char *redir_socket_name_ssh;
 
 /* We need to keep track of the server's nonces (these are dummies for
    POSIX systems). */
 static assuan_sock_nonce_t socket_nonce;
+static assuan_sock_nonce_t socket_nonce_extra;
+static assuan_sock_nonce_t socket_nonce_browser;
 static assuan_sock_nonce_t socket_nonce_ssh;
 
 
@@ -313,8 +375,9 @@ static int active_connections;
    Local prototypes.
  */
 
-static char *create_socket_name (char *standard_name);
-static gnupg_fd_t create_server_socket (char *name, int is_ssh,
+static char *create_socket_name (char *standard_name, int with_homedir);
+static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin,
+                                        char **r_redir_name,
                                         assuan_sock_nonce_t *nonce);
 static void create_directories (void);
 
@@ -322,6 +385,8 @@ static void agent_init_default_ctrl (ctrl_t ctrl);
 static void agent_deinit_default_ctrl (ctrl_t ctrl);
 
 static void handle_connections (gnupg_fd_t listen_fd,
+                                gnupg_fd_t listen_fd_extra,
+                                gnupg_fd_t listen_fd_browser,
                                 gnupg_fd_t listen_fd_ssh);
 static void check_own_socket (void);
 static int check_for_running_agent (int silent);
@@ -411,11 +476,11 @@ set_debug (void)
   else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
     opt.debug = 0;
   else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
-    opt.debug = DBG_ASSUAN_VALUE;
+    opt.debug = DBG_IPC_VALUE;
   else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
-    opt.debug = DBG_ASSUAN_VALUE|DBG_COMMAND_VALUE;
+    opt.debug = DBG_IPC_VALUE|DBG_COMMAND_VALUE;
   else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
-    opt.debug = (DBG_ASSUAN_VALUE|DBG_COMMAND_VALUE
+    opt.debug = (DBG_IPC_VALUE|DBG_COMMAND_VALUE
                  |DBG_CACHE_VALUE);
   else if (!strcmp (debug_level, "guru") || numok)
     {
@@ -446,26 +511,22 @@ set_debug (void)
   gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
 
   if (opt.debug)
-    log_info ("enabled debug flags:%s%s%s%s%s%s%s%s\n",
-              (opt.debug & DBG_COMMAND_VALUE)? " command":"",
-              (opt.debug & DBG_MPI_VALUE    )? " mpi":"",
-              (opt.debug & DBG_CRYPTO_VALUE )? " crypto":"",
-              (opt.debug & DBG_MEMORY_VALUE )? " memory":"",
-              (opt.debug & DBG_CACHE_VALUE  )? " cache":"",
-              (opt.debug & DBG_MEMSTAT_VALUE)? " memstat":"",
-              (opt.debug & DBG_HASHING_VALUE)? " hashing":"",
-              (opt.debug & DBG_ASSUAN_VALUE )? " assuan":"");
+    parse_debug_flag (NULL, &opt.debug, debug_flags);
 }
 
 
-/* Helper for cleanup to remove one socket with NAME.  */
+/* Helper for cleanup to remove one socket with NAME.  REDIR_NAME is
+   the corresponding real name if the socket has been redirected.  */
 static void
-remove_socket (char *name)
+remove_socket (char *name, char *redir_name)
 {
   if (name && *name)
     {
       char *p;
 
+      if (redir_name)
+        name = redir_name;
+
       gnupg_remove (name);
       p = strrchr (name, '/');
       if (p)
@@ -490,8 +551,12 @@ cleanup (void)
     return;
   done = 1;
   deinitialize_module_cache ();
-  remove_socket (socket_name);
-  remove_socket (socket_name_ssh);
+  remove_socket (socket_name, redir_socket_name);
+  if (opt.extra_socket > 1)
+    remove_socket (socket_name_extra, redir_socket_name_extra);
+  if (opt.browser_socket > 1)
+    remove_socket (socket_name_browser, redir_socket_name_browser);
+  remove_socket (socket_name_ssh, redir_socket_name_ssh);
 }
 
 
@@ -510,8 +575,11 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
       opt.verbose = 0;
       opt.debug = 0;
       opt.no_grab = 0;
+      opt.debug_pinentry = 0;
       opt.pinentry_program = NULL;
       opt.pinentry_touch_file = NULL;
+      xfree (opt.pinentry_invisible_char);
+      opt.pinentry_invisible_char = NULL;
       opt.scdaemon_program = NULL;
       opt.def_cache_ttl = DEFAULT_CACHE_TTL;
       opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
@@ -525,6 +593,8 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
       opt.enable_passhrase_history = 0;
       opt.ignore_cache_for_signing = 0;
       opt.allow_mark_trusted = 1;
+      opt.allow_external_cache = 1;
+      opt.allow_emacs_pinentry = 0;
       opt.disable_scdaemon = 0;
       disable_check_own_socket = 0;
       return 1;
@@ -535,9 +605,12 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
     case oQuiet: opt.quiet = 1; break;
     case oVerbose: opt.verbose++; break;
 
-    case oDebug: opt.debug |= pargs->r.ret_ulong; break;
+    case oDebug:
+      parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
+      break;
     case oDebugAll: opt.debug = ~0; break;
     case oDebugLevel: debug_level = pargs->r.ret_str; break;
+    case oDebugPinentry: opt.debug_pinentry = 1; break;
 
     case oLogFile:
       if (!reread)
@@ -555,6 +628,10 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
 
     case oPinentryProgram: opt.pinentry_program = pargs->r.ret_str; break;
     case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break;
+    case oPinentryInvisibleChar:
+      xfree (opt.pinentry_invisible_char);
+      opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break;
+      break;
     case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break;
     case oDisableScdaemon: opt.disable_scdaemon = 1; break;
     case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
@@ -590,6 +667,12 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
 
     case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break;
 
+    case oNoAllowExternalCache: opt.allow_external_cache = 0;
+      break;
+
+    case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1;
+      break;
+
     default:
       return 0; /* not handled */
     }
@@ -598,6 +681,14 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
 }
 
 
+/* Fixup some options after all have been processed.  */
+static void
+finalize_rereadable_options (void)
+{
+}
+
+
+
 /* The main entry point.  */
 int
 main (int argc, char **argv )
@@ -621,6 +712,8 @@ main (int argc, char **argv )
   gpg_error_t err;
   struct assuan_malloc_hooks malloc_hooks;
 
+  early_system_init ();
+
   /* Before we do anything else we save the list of currently open
      file descriptors and the signal mask.  This info is required to
      do the exec call properly. */
@@ -636,7 +729,7 @@ main (int argc, char **argv )
   /* Please note that we may running SUID(ROOT), so be very CAREFUL
      when adding any stuff between here and the call to INIT_SECMEM()
      somewhere after the option parsing */
-  log_set_prefix (GPG_AGENT_NAME, JNLIB_LOG_WITH_PREFIX|JNLIB_LOG_WITH_PID);
+  log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
 
   /* Make sure that our subsystems are ready.  */
   i18n_init ();
@@ -830,8 +923,10 @@ main (int argc, char **argv )
         case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str);
           break;
 
-        case oUseStandardSocket:   /* dummy */ break;
-        case oNoUseStandardSocket: /* dummy */ break;
+        case oUseStandardSocket:
+        case oNoUseStandardSocket:
+          obsolete_option (configname, configlineno, "use-standard-socket");
+          break;
 
         case oFakedSystemTime:
           {
@@ -845,19 +940,32 @@ main (int argc, char **argv )
         case oKeepTTY: opt.keep_tty = 1; break;
         case oKeepDISPLAY: opt.keep_display = 1; break;
 
-       case oSSHSupport:  opt.ssh_support = 1; break;
+       case oSSHSupport:
+          ssh_support = 1;
+          break;
         case oPuttySupport:
 #        ifdef HAVE_W32_SYSTEM
           putty_support = 1;
-          opt.ssh_support = 1;
 #        endif
           break;
 
+        case oExtraSocket:
+          opt.extra_socket = 1;  /* (1 = points into argv)  */
+          socket_name_extra = pargs.r.ret_str;
+          break;
+
+        case oBrowserSocket:
+          opt.browser_socket = 1;  /* (1 = points into argv)  */
+          socket_name_browser = pargs.r.ret_str;
+          break;
+
         case oDebugQuickRandom:
           /* Only used by the first stage command line parser.  */
           break;
 
-        case oWriteEnvFile: /* dummy */ break;
+        case oWriteEnvFile:
+          obsolete_option (configname, configlineno, "write-env-file");
+          break;
 
         default : pargs.err = configfp? 1:2; break;
        }
@@ -881,6 +989,8 @@ main (int argc, char **argv )
   if (log_get_errorcount(0))
     exit(2);
 
+  finalize_rereadable_options ();
+
   /* Turn the homedir into an absolute one. */
   opt.homedir = make_absfilename (opt.homedir, NULL);
 
@@ -1000,15 +1110,20 @@ main (int argc, char **argv )
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
       es_printf ("ignore-cache-for-signing:%lu:\n",
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+      es_printf ("no-allow-external-cache:%lu:\n",
+              GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
       es_printf ("no-allow-mark-trusted:%lu:\n",
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
       es_printf ("disable-scdaemon:%lu:\n",
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+      es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE);
 #ifdef HAVE_W32_SYSTEM
       es_printf ("enable-putty-support:%lu:\n", GC_OPT_FLAG_NONE);
-#else
-      es_printf ("enable-ssh-support:%lu:\n", GC_OPT_FLAG_NONE);
 #endif
+      es_printf ("allow-loopback-pinentry:%lu:\n",
+                 GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+      es_printf ("allow-emacs-pinentry:%lu:\n",
+                 GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
 
       agent_exit (0);
     }
@@ -1017,9 +1132,9 @@ main (int argc, char **argv )
   if (logfile)
     {
       log_set_file (logfile);
-      log_set_prefix (NULL, (JNLIB_LOG_WITH_PREFIX
-                             |JNLIB_LOG_WITH_TIME
-                             |JNLIB_LOG_WITH_PID));
+      log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
+                             | GPGRT_LOG_WITH_TIME
+                             | GPGRT_LOG_WITH_PID));
       current_logfile = xstrdup (logfile);
     }
 
@@ -1060,7 +1175,9 @@ main (int argc, char **argv )
   else
     { /* Regular server mode */
       gnupg_fd_t fd;
-      gnupg_fd_t fd_ssh;
+      gnupg_fd_t fd_extra = GNUPG_INVALID_FD;
+      gnupg_fd_t fd_browser = GNUPG_INVALID_FD;
+      gnupg_fd_t fd_ssh = GNUPG_INVALID_FD;
       pid_t pid;
 
       /* Remove the DISPLAY variable so that a pinentry does not
@@ -1074,17 +1191,41 @@ main (int argc, char **argv )
         gnupg_unsetenv ("DISPLAY");
 #endif
 
+      /* Remove the INSIDE_EMACS variable so that a pinentry does not
+         always try to interact with Emacs.  The variable is set when
+         a client requested this using an OPTION command.  */
+      gnupg_unsetenv ("INSIDE_EMACS");
 
       /* Create the sockets.  */
-      socket_name = create_socket_name (GPG_AGENT_SOCK_NAME);
-      fd = create_server_socket (socket_name, 0, &socket_nonce);
-      if (opt.ssh_support)
+      socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1);
+      fd = create_server_socket (socket_name, 1, 0,
+                                 &redir_socket_name, &socket_nonce);
+
+      if (opt.extra_socket)
         {
-          socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME);
-          fd_ssh = create_server_socket (socket_name_ssh, 1, &socket_nonce_ssh);
+          socket_name_extra = create_socket_name (socket_name_extra, 0);
+          opt.extra_socket = 2; /* Indicate that it has been malloced.  */
+          fd_extra = create_server_socket (socket_name_extra, 0, 0,
+                                           &redir_socket_name_extra,
+                                           &socket_nonce_extra);
+        }
+
+      if (opt.browser_socket)
+        {
+          socket_name_browser = create_socket_name (socket_name_browser, 0);
+          opt.browser_socket = 2; /* Indicate that it has been malloced.  */
+          fd_browser = create_server_socket (socket_name_browser, 0, 0,
+                                             &redir_socket_name_browser,
+                                             &socket_nonce_browser);
+        }
+
+      if (ssh_support)
+        {
+          socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1);
+          fd_ssh = create_server_socket (socket_name_ssh, 0, 1,
+                                         &redir_socket_name_ssh,
+                                         &socket_nonce_ssh);
         }
-      else
-       fd_ssh = GNUPG_INVALID_FD;
 
       /* If we are going to exec a program in the parent, we record
          the PID, so that the child may check whether the program is
@@ -1127,7 +1268,7 @@ main (int argc, char **argv )
 #endif /*HAVE_SIGPROCMASK*/
 
           /* Create the SSH info string if enabled. */
-         if (opt.ssh_support)
+         if (ssh_support)
            {
              if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s",
                            socket_name_ssh) < 0)
@@ -1147,13 +1288,17 @@ main (int argc, char **argv )
 
           *socket_name = 0; /* Don't let cleanup() remove the socket -
                                the child should do this from now on */
-         if (opt.ssh_support)
+         if (opt.extra_socket)
+           *socket_name_extra = 0;
+         if (opt.browser_socket)
+           *socket_name_browser = 0;
+         if (ssh_support)
            *socket_name_ssh = 0;
 
           if (argc)
             { /* Run the program given on the commandline.  */
-              if (opt.ssh_support && (putenv (infostr_ssh_sock)
-                                      || putenv (infostr_ssh_valid)))
+              if (ssh_support && (putenv (infostr_ssh_sock)
+                                  || putenv (infostr_ssh_valid)))
                 {
                   log_error ("failed to set environment: %s\n",
                              strerror (errno) );
@@ -1179,7 +1324,7 @@ main (int argc, char **argv )
                  shell's eval to set it */
               if (csh_style)
                 {
-                 if (opt.ssh_support)
+                 if (ssh_support)
                    {
                      *strchr (infostr_ssh_sock, '=') = ' ';
                      es_printf ("setenv %s;\n", infostr_ssh_sock);
@@ -1187,13 +1332,13 @@ main (int argc, char **argv )
                 }
               else
                 {
-                 if (opt.ssh_support)
+                 if (ssh_support)
                    {
                      es_printf ("%s; export SSH_AUTH_SOCK;\n",
                                  infostr_ssh_sock);
                    }
                 }
-             if (opt.ssh_support)
+             if (ssh_support)
                {
                  xfree (infostr_ssh_sock);
                  xfree (infostr_ssh_valid);
@@ -1236,7 +1381,7 @@ main (int argc, char **argv )
             }
 
           log_get_prefix (&oldflags);
-          log_set_prefix (NULL, oldflags | JNLIB_LOG_RUN_DETACHED);
+          log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
           opt.running_detached = 1;
         }
 
@@ -1257,7 +1402,7 @@ main (int argc, char **argv )
 #endif /*!HAVE_W32_SYSTEM*/
 
       log_info ("%s %s started\n", strusage(11), strusage(13) );
-      handle_connections (fd, opt.ssh_support ? fd_ssh : GNUPG_INVALID_FD);
+      handle_connections (fd, fd_extra, fd_browser, fd_ssh);
       assuan_sock_close (fd);
     }
 
@@ -1297,7 +1442,7 @@ agent_exit (int rc)
    structure usually identified by an argument named CTRL.  This
    function is called immediately after allocating the control
    structure.  Its purpose is to setup the default values for that
-   structure.  */
+   structure.  Note that some values may have already been set.  */
 static void
 agent_init_default_ctrl (ctrl_t ctrl)
 {
@@ -1338,6 +1483,39 @@ agent_deinit_default_ctrl (ctrl_t ctrl)
 }
 
 
+/* Because the ssh protocol does not send us information about the
+   current TTY setting, we use this function to use those from startup
+   or those explictly set.  This is also used for the restricted mode
+   where we ignore requests to change the environment.  */
+gpg_error_t
+agent_copy_startup_env (ctrl_t ctrl)
+{
+  static const char *names[] =
+    {"GPG_TTY", "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL};
+  gpg_error_t err = 0;
+  int idx;
+  const char *value;
+
+  for (idx=0; !err && names[idx]; idx++)
+      if ((value = session_env_getenv (opt.startup_env, names[idx])))
+      err = session_env_setenv (ctrl->session_env, names[idx], value);
+
+  if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
+    if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
+      err = gpg_error_from_syserror ();
+
+  if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
+    if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
+      err = gpg_error_from_syserror ();
+
+  if (err)
+    log_error ("error setting default session environment: %s\n",
+               gpg_strerror (err));
+
+  return err;
+}
+
+
 /* Reread parts of the configuration.  Note, that this function is
    obviously not thread-safe and should only be called from the PTH
    signal handler.
@@ -1380,6 +1558,7 @@ reread_configuration (void)
         parse_rereadable_options (&pargs, 1);
     }
   fclose (fp);
+  finalize_rereadable_options ();
   set_debug ();
 }
 
@@ -1456,56 +1635,97 @@ get_agent_scd_notify_event (void)
    Pointer to an allocated string with the absolute name of the socket
    used.  */
 static char *
-create_socket_name (char *standard_name)
+create_socket_name (char *standard_name, int with_homedir)
 {
   char *name;
 
-  name = make_filename (opt.homedir, standard_name, NULL);
+  if (with_homedir)
+    name = make_filename (opt.homedir, standard_name, NULL);
+  else
+    name = make_filename (standard_name, NULL);
   if (strchr (name, PATHSEP_C))
     {
       log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
       agent_exit (2);
     }
-  if (strlen (name) + 1 >= DIMof (struct sockaddr_un, sun_path) )
-    {
-      log_error (_("name of socket too long\n"));
-      agent_exit (2);
-    }
   return name;
 }
 
 
 
 /* Create a Unix domain socket with NAME.  Returns the file descriptor
-   or terminates the process in case of an error.  Not that this
-   function needs to be used for the regular socket first and only
-   then for the ssh socket.  */
+   or terminates the process in case of an error.  Note that this
+   function needs to be used for the regular socket first (indicated
+   by PRIMARY) and only then for the extra and the ssh sockets.  If
+   the socket has been redirected the name of the real socket is
+   stored as a malloced string at R_REDIR_NAME.  If CYGWIN is set a
+   Cygwin compatible socket is created (Windows only). */
 static gnupg_fd_t
-create_server_socket (char *name, int is_ssh, assuan_sock_nonce_t *nonce)
+create_server_socket (char *name, int primary, int cygwin,
+                      char **r_redir_name, assuan_sock_nonce_t *nonce)
 {
-  struct sockaddr_un *serv_addr;
+  struct sockaddr *addr;
+  struct sockaddr_un *unaddr;
   socklen_t len;
   gnupg_fd_t fd;
   int rc;
 
+  xfree (*r_redir_name);
+  *r_redir_name = NULL;
+
   fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
   if (fd == ASSUAN_INVALID_FD)
     {
       log_error (_("can't create socket: %s\n"), strerror (errno));
+      *name = 0; /* Inhibit removal of the socket by cleanup(). */
       agent_exit (2);
     }
 
-  serv_addr = xmalloc (sizeof (*serv_addr));
-  memset (serv_addr, 0, sizeof *serv_addr);
-  serv_addr->sun_family = AF_UNIX;
-  if (strlen (name) + 1 >= sizeof (serv_addr->sun_path))
+#if ASSUAN_VERSION_NUMBER >= 0x020300 /* >= 2.3.0 */
+  if (cygwin)
+    assuan_sock_set_flag (fd, "cygwin", 1);
+#else
+  (void)cygwin;
+#endif
+
+  unaddr = xmalloc (sizeof *unaddr);
+  addr = (struct sockaddr*)unaddr;
+
+#if ASSUAN_VERSION_NUMBER >= 0x020104 /* >= 2.1.4 */
+  {
+    int redirected;
+
+    if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
+      {
+        if (errno == ENAMETOOLONG)
+          log_error (_("socket name '%s' is too long\n"), name);
+        else
+          log_error ("error preparing socket '%s': %s\n",
+                     name, gpg_strerror (gpg_error_from_syserror ()));
+        *name = 0; /* Inhibit removal of the socket by cleanup(). */
+        agent_exit (2);
+      }
+    if (redirected)
+      {
+        *r_redir_name = xstrdup (unaddr->sun_path);
+        if (opt.verbose)
+          log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
+      }
+  }
+#else /* Assuan < 2.1.4 */
+  memset (unaddr, 0, sizeof *unaddr);
+  unaddr->sun_family = AF_UNIX;
+  if (strlen (name) + 1 >= sizeof (unaddr->sun_path))
     {
       log_error (_("socket name '%s' is too long\n"), name);
+      *name = 0; /* Inhibit removal of the socket by cleanup(). */
       agent_exit (2);
     }
-  strcpy (serv_addr->sun_path, name);
-  len = SUN_LEN (serv_addr);
-  rc = assuan_sock_bind (fd, (struct sockaddr*) serv_addr, len);
+  strcpy (unaddr->sun_path, name);
+#endif /* Assuan < 2.1.4 */
+
+  len = SUN_LEN (unaddr);
+  rc = assuan_sock_bind (fd, addr, len);
 
   /* Our error code mapping on W32CE returns EEXIST thus we also test
      for this. */
@@ -1516,17 +1736,16 @@ create_server_socket (char *name, int is_ssh, assuan_sock_nonce_t *nonce)
 #endif
           ))
     {
-      /* Check whether a gpg-agent is already running.
-         We do this test only if this is not the ssh socket.
-         For ssh we assume that a test for gpg-agent has already been
-         done and reuse the requested ssh socket.  Testing the
-         ssh-socket is not possible because at this point, though we
-         know the new Assuan socket, the Assuan server and thus the
-         ssh-agent server is not yet operational.  This would lead to
-         a hang.  */
-      if (!is_ssh && !check_for_running_agent (1))
+      /* Check whether a gpg-agent is already running.  We do this
+         test only if this is the primary socket.  For secondary
+         sockets we assume that a test for gpg-agent has already been
+         done and reuse the requested socket.  Testing the ssh-socket
+         is not possible because at this point, though we know the new
+         Assuan socket, the Assuan server and thus the ssh-agent
+         server is not yet operational; this would lead to a hang.  */
+      if (primary && !check_for_running_agent (1))
         {
-          log_set_prefix (NULL, JNLIB_LOG_WITH_PREFIX);
+          log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
           log_set_file (NULL);
           log_error (_("a gpg-agent is already running - "
                        "not starting a new one\n"));
@@ -1534,19 +1753,18 @@ create_server_socket (char *name, int is_ssh, assuan_sock_nonce_t *nonce)
           assuan_sock_close (fd);
           agent_exit (2);
         }
-      gnupg_remove (name);
-      rc = assuan_sock_bind (fd, (struct sockaddr*) serv_addr, len);
+      gnupg_remove (unaddr->sun_path);
+      rc = assuan_sock_bind (fd, addr, len);
     }
-  if (rc != -1
-      && (rc=assuan_sock_get_nonce ((struct sockaddr*)serv_addr, len, nonce)))
+  if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
     log_error (_("error getting nonce for the socket\n"));
   if (rc == -1)
     {
       /* We use gpg_strerror here because it allows us to get strings
          for some W32 socket error codes.  */
       log_error (_("error binding socket to '%s': %s\n"),
-                serv_addr->sun_path,
-                 gpg_strerror (gpg_error_from_errno (errno)));
+                unaddr->sun_path,
+                 gpg_strerror (gpg_error_from_syserror ()));
 
       assuan_sock_close (fd);
       *name = 0; /* Inhibit removal of the socket by cleanup(). */
@@ -1556,12 +1774,13 @@ create_server_socket (char *name, int is_ssh, assuan_sock_nonce_t *nonce)
   if (listen (FD2INT(fd), 5 ) == -1)
     {
       log_error (_("listen() failed: %s\n"), strerror (errno));
+      *name = 0; /* Inhibit removal of the socket by cleanup(). */
       assuan_sock_close (fd);
       agent_exit (2);
     }
 
   if (opt.verbose)
-    log_info (_("listening on socket '%s'\n"), serv_addr->sun_path);
+    log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
 
   return fd;
 }
@@ -1693,9 +1912,14 @@ agent_sighup_action (void)
 {
   log_info ("SIGHUP received - "
             "re-reading configuration and flushing cache\n");
+
   agent_flush_cache ();
   reread_configuration ();
   agent_reload_trustlist ();
+  /* We flush the module name cache so that after installing a
+     "pinentry" binary that one can be used in case the
+     "pinentry-basic" fallback was in use.  */
+  gnupg_module_name_flush_some ();
 }
 
 
@@ -1813,7 +2037,7 @@ putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
   if (!cds->cbData || mapfile[cds->cbData - 1])
     return 0;  /* Ignore empty and non-properly terminated strings.  */
 
-  if (DBG_ASSUAN)
+  if (DBG_IPC)
     {
       npth_protect ();
       log_debug ("ssh map file '%s'", mapfile);
@@ -1821,7 +2045,7 @@ putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
     }
 
   maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile);
-  if (DBG_ASSUAN)
+  if (DBG_IPC)
     {
       npth_protect ();
       log_debug ("ssh map handle %p\n", maphd);
@@ -1850,7 +2074,7 @@ putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
       goto leave;
     }
 
-  if (DBG_ASSUAN)
+  if (DBG_IPC)
     {
       char *sidstr;
 
@@ -1871,7 +2095,7 @@ putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
     }
 
   data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
-  if (DBG_ASSUAN)
+  if (DBG_IPC)
     log_debug ("ssh IPC buffer at %p\n", data);
   if (!data)
     goto leave;
@@ -1973,19 +2197,9 @@ putty_message_thread (void *arg)
 #endif /*HAVE_W32_SYSTEM*/
 
 
-/* This is the standard connection thread's main function.  */
 static void *
-start_connection_thread (void *arg)
+do_start_connection_thread (ctrl_t ctrl)
 {
-  ctrl_t ctrl = arg;
-
-  if (check_nonce (ctrl, &socket_nonce))
-    {
-      log_error ("handler 0x%lx nonce check FAILED\n",
-                 (unsigned long) npth_self());
-      return NULL;
-    }
-
   agent_init_default_ctrl (ctrl);
   if (opt.verbose)
     log_info (_("handler 0x%lx for fd %d started\n"),
@@ -2002,6 +2216,59 @@ start_connection_thread (void *arg)
 }
 
 
+/* This is the standard connection thread's main function.  */
+static void *
+start_connection_thread_std (void *arg)
+{
+  ctrl_t ctrl = arg;
+
+  if (check_nonce (ctrl, &socket_nonce))
+    {
+      log_error ("handler 0x%lx nonce check FAILED\n",
+                 (unsigned long) npth_self());
+      return NULL;
+    }
+
+  return do_start_connection_thread (ctrl);
+}
+
+
+/* This is the extra socket connection thread's main function.  */
+static void *
+start_connection_thread_extra (void *arg)
+{
+  ctrl_t ctrl = arg;
+
+  if (check_nonce (ctrl, &socket_nonce_extra))
+    {
+      log_error ("handler 0x%lx nonce check FAILED\n",
+                 (unsigned long) npth_self());
+      return NULL;
+    }
+
+  ctrl->restricted = 1;
+  return do_start_connection_thread (ctrl);
+}
+
+
+/* This is the browser socket connection thread's main function.  */
+static void *
+start_connection_thread_browser (void *arg)
+{
+  ctrl_t ctrl = arg;
+
+  if (check_nonce (ctrl, &socket_nonce_browser))
+    {
+      log_error ("handler 0x%lx nonce check FAILED\n",
+                 (unsigned long) npth_self());
+      return NULL;
+    }
+
+  ctrl->restricted = 2;
+  return do_start_connection_thread (ctrl);
+}
+
+
 /* This is the ssh connection thread's main function.  */
 static void *
 start_connection_thread_ssh (void *arg)
@@ -2030,7 +2297,10 @@ start_connection_thread_ssh (void *arg)
 /* Connection handler loop.  Wait for connection requests and spawn a
    thread after accepting a connection.  */
 static void
-handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
+handle_connections (gnupg_fd_t listen_fd,
+                    gnupg_fd_t listen_fd_extra,
+                    gnupg_fd_t listen_fd_browser,
+                    gnupg_fd_t listen_fd_ssh)
 {
   npth_attr_t tattr;
   struct sockaddr_un paddr;
@@ -2047,6 +2317,17 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
   HANDLE events[2];
   unsigned int events_set;
 #endif
+  struct {
+    const char *name;
+    void *(*func) (void *arg);
+    gnupg_fd_t l_fd;
+  } listentbl[] = {
+    { "std",     start_connection_thread_std   },
+    { "extra",   start_connection_thread_extra },
+    { "browser", start_connection_thread_browser },
+    { "ssh",    start_connection_thread_ssh   }
+  };
+
 
   ret = npth_attr_init(&tattr);
   if (ret)
@@ -2096,6 +2377,18 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
   FD_ZERO (&fdset);
   FD_SET (FD2INT (listen_fd), &fdset);
   nfd = FD2INT (listen_fd);
+  if (listen_fd_extra != GNUPG_INVALID_FD)
+    {
+      FD_SET ( FD2INT(listen_fd_extra), &fdset);
+      if (FD2INT (listen_fd_extra) > nfd)
+        nfd = FD2INT (listen_fd_extra);
+    }
+  if (listen_fd_browser != GNUPG_INVALID_FD)
+    {
+      FD_SET ( FD2INT(listen_fd_browser), &fdset);
+      if (FD2INT (listen_fd_browser) > nfd)
+        nfd = FD2INT (listen_fd_browser);
+    }
   if (listen_fd_ssh != GNUPG_INVALID_FD)
     {
       FD_SET ( FD2INT(listen_fd_ssh), &fdset);
@@ -2103,6 +2396,11 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
         nfd = FD2INT (listen_fd_ssh);
     }
 
+  listentbl[0].l_fd = listen_fd;
+  listentbl[1].l_fd = listen_fd_extra;
+  listentbl[2].l_fd = listen_fd_browser;
+  listentbl[3].l_fd = listen_fd_ssh;
+
   npth_clock_gettime (&abstime);
   abstime.tv_sec += TIMERTICK_INTERVAL;
 
@@ -2165,92 +2463,56 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_ssh)
           next timeout.  */
        continue;
 
-      if (!shutdown_pending && FD_ISSET (FD2INT (listen_fd), &read_fdset))
-       {
+      if (!shutdown_pending)
+        {
+          int idx;
           ctrl_t ctrl;
+          npth_t thread;
 
-          plen = sizeof paddr;
-         fd = INT2FD (npth_accept (FD2INT(listen_fd),
-                                   (struct sockaddr *)&paddr, &plen));
-         if (fd == GNUPG_INVALID_FD)
-           {
-             log_error ("accept failed: %s\n", strerror (errno));
-           }
-          else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
-            {
-              log_error ("error allocating connection control data: %s\n",
-                         strerror (errno) );
-              assuan_sock_close (fd);
-            }
-          else if ( !(ctrl->session_env = session_env_new ()) )
-            {
-              log_error ("error allocating session environment block: %s\n",
-                         strerror (errno) );
-              xfree (ctrl);
-              assuan_sock_close (fd);
-            }
-          else
+          for (idx=0; idx < DIM(listentbl); idx++)
             {
-             npth_t thread;
-
-              ctrl->thread_startup.fd = fd;
-             ret = npth_create (&thread, &tattr,
-                                 start_connection_thread, ctrl);
-              if (ret)
+              if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
+                continue;
+              if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
+                continue;
+
+              plen = sizeof paddr;
+              fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd),
+                                        (struct sockaddr *)&paddr, &plen));
+              if (fd == GNUPG_INVALID_FD)
                 {
-                  log_error ("error spawning connection handler: %s\n",
-                            strerror (ret));
-                  assuan_sock_close (fd);
-                  xfree (ctrl);
+                  log_error ("accept failed for %s: %s\n",
+                             listentbl[idx].name, strerror (errno));
                 }
-
-            }
-          fd = GNUPG_INVALID_FD;
-       }
-
-      if (!shutdown_pending && listen_fd_ssh != GNUPG_INVALID_FD
-          && FD_ISSET ( FD2INT (listen_fd_ssh), &read_fdset))
-       {
-          ctrl_t ctrl;
-
-          plen = sizeof paddr;
-         fd = INT2FD(npth_accept (FD2INT(listen_fd_ssh),
-                                  (struct sockaddr *)&paddr, &plen));
-         if (fd == GNUPG_INVALID_FD)
-           {
-             log_error ("accept failed for ssh: %s\n", strerror (errno));
-           }
-          else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
-            {
-              log_error ("error allocating connection control data: %s\n",
-                         strerror (errno) );
-              assuan_sock_close (fd);
-            }
-          else if ( !(ctrl->session_env = session_env_new ()) )
-            {
-              log_error ("error allocating session environment block: %s\n",
-                         strerror (errno) );
-              xfree (ctrl);
-              assuan_sock_close (fd);
-            }
-          else
-            {
-             npth_t thread;
-
-              agent_init_default_ctrl (ctrl);
-              ctrl->thread_startup.fd = fd;
-              ret = npth_create (&thread, &tattr,
-                                 start_connection_thread_ssh, ctrl);
-             if (ret)
+              else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
                 {
-                  log_error ("error spawning ssh connection handler: %s\n",
-                            strerror (ret));
+                  log_error ("error allocating connection data for %s: %s\n",
+                             listentbl[idx].name, strerror (errno) );
                   assuan_sock_close (fd);
+                }
+              else if ( !(ctrl->session_env = session_env_new ()))
+                {
+                  log_error ("error allocating session env block for %s: %s\n",
+                             listentbl[idx].name, strerror (errno) );
                   xfree (ctrl);
+                  assuan_sock_close (fd);
                 }
+              else
+                {
+                  ctrl->thread_startup.fd = fd;
+                  ret = npth_create (&thread, &tattr,
+                                     listentbl[idx].func, ctrl);
+                  if (ret)
+                    {
+                      log_error ("error spawning connection handler for %s:"
+                                 " %s\n", listentbl[idx].name, strerror (ret));
+                      assuan_sock_close (fd);
+                      xfree (ctrl);
+                    }
+                }
+              fd = GNUPG_INVALID_FD;
             }
-          fd = GNUPG_INVALID_FD;
-       }
+        }
     }
 
   cleanup ();