dirmngr: Fix memory leak.
[gnupg.git] / dirmngr / server.c
index 584cae7..6094bc9 100644 (file)
@@ -1,15 +1,16 @@
-/* dirmngr.c - LDAP access
- *     Copyright (C) 2002 Klarälvdalens Datakonsult AB
- *      Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009 g10 Code GmbH
+/* server.c - LDAP and Keyserver access server
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011 g10 Code GmbH
+ * Copyright (C) 2014 Werner Koch
  *
- * This file is part of DirMngr.
+ * This file is part of GnuPG.
  *
- * DirMngr is free software; you can redistribute it and/or modify
+ * GnuPG 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
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
- * DirMngr is distributed in the hope that it will be useful,
+ * GnuPG 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.
 
 #include "crlcache.h"
 #include "crlfetch.h"
-#include "ldapserver.h"
+#if USE_LDAP
+# include "ldapserver.h"
+#endif
 #include "ocsp.h"
 #include "certcache.h"
 #include "validate.h"
 #include "misc.h"
-#include "ldap-wrapper.h"
+#if USE_LDAP
+# include "ldap-wrapper.h"
+#endif
+#include "ks-action.h"
+#include "ks-engine.h"  /* (ks_hkp_print_hosttable) */
 
 /* To avoid DoS attacks we limit the size of a certificate to
    something reasonable. */
 #define MAX_CERT_LENGTH (8*1024)
 
+/* The same goes for OpenPGP keyblocks, but here we need to allow for
+   much longer blocks; a 200k keyblock is not too unusual for keys
+   with a lot of signatures (e.g. 0x5b0358a2).  */
+#define MAX_KEYBLOCK_LENGTH (512*1024)
+
+
 #define PARM_ERROR(t) assuan_set_error (ctx, \
                                         gpg_error (GPG_ERR_ASS_PARAMETER), (t))
 #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
 
 
 /* Control structure per connection. */
-struct server_local_s 
+struct server_local_s
 {
   /* Data used to associate an Assuan context with local server data */
   assuan_context_t assuan_ctx;
 
-  /* Per-session LDAP serfver.  */
+  /* Per-session LDAP servers.  */
   ldap_server_t ldapservers;
+
+  /* If this flag is set to true this dirmngr process will be
+     terminated after the end of this session.  */
+  int stopme;
 };
 
 
@@ -90,9 +107,25 @@ get_ldapservers_from_ctrl (ctrl_t ctrl)
 }
 
 
+/* Release all configured keyserver info from CTRL.  */
+void
+release_ctrl_keyservers (ctrl_t ctrl)
+{
+  while (ctrl->keyservers)
+    {
+      uri_item_t tmp = ctrl->keyservers->next;
+      http_release_parsed_uri (ctrl->keyservers->parsed_uri);
+      xfree (ctrl->keyservers);
+      ctrl->keyservers = tmp;
+    }
+}
+
+
+
 /* Helper to print a message while leaving a command.  */
 static gpg_error_t
 leave_cmd (assuan_context_t ctx, gpg_error_t err)
+
 {
   if (err)
     {
@@ -112,14 +145,44 @@ leave_cmd (assuan_context_t ctx, gpg_error_t err)
 /* A write handler used by es_fopencookie to write assuan data
    lines.  */
 static ssize_t
-data_line_cookie_write (void *cookie, const void *buffer, size_t size)
+data_line_cookie_write (void *cookie, const void *buffer_arg, size_t size)
 {
   assuan_context_t ctx = cookie;
+  const char *buffer = buffer_arg;
 
-  if (assuan_send_data (ctx, buffer, size))
+  if (opt.verbose && buffer && size)
     {
-      gpg_err_set_errno (EIO);
-      return -1;
+      /* Ease reading of output by sending a physical line at each LF.  */
+      const char *p;
+      size_t n, nbytes;
+
+      nbytes = size;
+      do
+        {
+          p = memchr (buffer, '\n', nbytes);
+          n = p ? (p - buffer) + 1 : nbytes;
+          if (assuan_send_data (ctx, buffer, n))
+            {
+              gpg_err_set_errno (EIO);
+              return -1;
+            }
+          buffer += n;
+          nbytes -= n;
+          if (nbytes && assuan_send_data (ctx, NULL, 0)) /* Flush line. */
+            {
+              gpg_err_set_errno (EIO);
+              return -1;
+            }
+        }
+      while (nbytes);
+    }
+  else
+    {
+      if (assuan_send_data (ctx, buffer, size))
+        {
+          gpg_err_set_errno (EIO);
+          return -1;
+        }
     }
 
   return size;
@@ -143,7 +206,7 @@ data_line_cookie_close (void *cookie)
 /* Copy the % and + escaped string S into the buffer D and replace the
    escape sequences.  Note, that it is sufficient to allocate the
    target string D as long as the source string S, i.e.: strlen(s)+1.
-   NOte further that If S contains an escaped binary nul the resulting
+   Note further that if S contains an escaped binary Nul the resulting
    string D will contain the 0 as well as all other characters but it
    will be impossible to know whether this is the original EOS or a
    copied Nul. */
@@ -239,12 +302,38 @@ skip_options (char *line)
 }
 
 
+/* Return an error if the assuan context does not belong to the owner
+   of the process or to root.  On error FAILTEXT is set as Assuan
+   error string.  */
+static gpg_error_t
+check_owner_permission (assuan_context_t ctx, const char *failtext)
+{
+#ifdef HAVE_W32_SYSTEM
+  /* Under Windows the dirmngr is always run under the control of the
+     user.  */
+  (void)ctx;
+  (void)failtext;
+#else
+  gpg_err_code_t ec;
+  assuan_peercred_t cred;
+
+  ec = gpg_err_code (assuan_get_peercred (ctx, &cred));
+  if (!ec && cred->uid && cred->uid != getuid ())
+    ec = GPG_ERR_EPERM;
+  if (ec)
+    return set_error (ec, failtext);
+#endif
+  return 0;
+}
+
+
+
 /* Common code for get_cert_local and get_issuer_cert_local. */
-static ksba_cert_t 
+static ksba_cert_t
 do_get_cert_local (ctrl_t ctrl, const char *name, const char *command)
 {
   unsigned char *value;
-  size_t valuelen; 
+  size_t valuelen;
   int rc;
   char *buf;
   ksba_cert_t cert;
@@ -266,7 +355,7 @@ do_get_cert_local (ctrl_t ctrl, const char *name, const char *command)
                  command, gpg_strerror (rc));
       return NULL;
     }
-  
+
   if (!valuelen)
     {
       xfree (value);
@@ -295,7 +384,7 @@ do_get_cert_local (ctrl_t ctrl, const char *name, const char *command)
    return the current target certificate. Either return the certificate
    in a KSBA object or NULL if it is not available.
 */
-ksba_cert_t 
+ksba_cert_t
 get_cert_local (ctrl_t ctrl, const char *name)
 {
   if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
@@ -307,15 +396,15 @@ get_cert_local (ctrl_t ctrl, const char *name)
   return do_get_cert_local (ctrl, name, "SENDCERT");
 
 }
-       
+
 /* Ask back to return the issuing certificate for name, given as a
    regular gpgsm certificate indentificates (e.g. fingerprint or one
    of the other methods).  Alternatively, NULL may be used for NAME to
    return thecurrent target certificate. Either return the certificate
    in a KSBA object or NULL if it is not available.
-   
+
 */
-ksba_cert_t 
+ksba_cert_t
 get_issuing_cert_local (ctrl_t ctrl, const char *name)
 {
   if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
@@ -329,11 +418,11 @@ get_issuing_cert_local (ctrl_t ctrl, const char *name)
 
 /* Ask back to return a certificate with subject NAME and a
    subjectKeyIdentifier of KEYID. */
-ksba_cert_t 
+ksba_cert_t
 get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid)
 {
   unsigned char *value;
-  size_t valuelen; 
+  size_t valuelen;
   int rc;
   char *buf;
   ksba_cert_t cert;
@@ -378,7 +467,7 @@ get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid)
                  gpg_strerror (rc));
       return NULL;
     }
-  
+
   if (!valuelen)
     {
       xfree (value);
@@ -407,14 +496,14 @@ gpg_error_t
 get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr)
 {
   unsigned char *value;
-  size_t valuelen; 
+  size_t valuelen;
   int rc;
   char request[100];
 
   if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx
       || !hexfpr)
     return gpg_error (GPG_ERR_INV_ARG);
-  
+
   snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr);
   rc = assuan_inquire (ctrl->server_local->assuan_ctx, request,
                        &value, &valuelen, 100);
@@ -446,7 +535,7 @@ inquire_cert_and_load_crl (assuan_context_t ctx)
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err;
   unsigned char *value = NULL;
-  size_t valuelen; 
+  size_t valuelen;
   ksba_cert_t cert = NULL;
 
   err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0);
@@ -502,7 +591,7 @@ option_handler (assuan_context_t ctx, const char *key, const char *value)
   return 0;
 }
 
-static const char hlp_ldapserver[] = 
+static const char hlp_ldapserver[] =
   "LDAPSERVER <data>\n"
   "\n"
   "Add a new LDAP server to the list of configured LDAP servers.\n"
@@ -510,6 +599,7 @@ static const char hlp_ldapserver[] =
 static gpg_error_t
 cmd_ldapserver (assuan_context_t ctx, char *line)
 {
+#if USE_LDAP
   ctrl_t ctrl = assuan_get_pointer (ctx);
   ldap_server_t server;
   ldap_server_t *last_next_p;
@@ -528,10 +618,14 @@ cmd_ldapserver (assuan_context_t ctx, char *line)
     last_next_p = &(*last_next_p)->next;
   *last_next_p = server;
   return leave_cmd (ctx, 0);
+#else
+  (void)line;
+  return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED));
+#endif
 }
 
 
-static const char hlp_isvalid[] = 
+static const char hlp_isvalid[] =
   "ISVALID [--only-ocsp] [--force-default-responder]"
   " <certificate_id>|<certificate_fpr>\n"
   "\n"
@@ -564,7 +658,7 @@ cmd_isvalid (assuan_context_t ctx, char *line)
   int ocsp_mode = 0;
   int only_ocsp;
   int force_default_responder;
-  
+
   only_ocsp = has_option (line, "--only-ocsp");
   force_default_responder = has_option (line, "--force-default-responder");
   line = skip_options (line);
@@ -610,7 +704,7 @@ cmd_isvalid (assuan_context_t ctx, char *line)
     }
   else if (only_ocsp)
     err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
-  else 
+  else
     {
       switch (crl_cache_isvalid (ctrl,
                                  issuerhash, serialno,
@@ -622,7 +716,7 @@ cmd_isvalid (assuan_context_t ctx, char *line)
         case CRL_CACHE_INVALID:
           err = gpg_error (GPG_ERR_CERT_REVOKED);
           break;
-        case CRL_CACHE_DONTKNOW: 
+        case CRL_CACHE_DONTKNOW:
           if (did_inquire)
             err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
           else if (!(err = inquire_cert_and_load_crl (ctx)))
@@ -631,7 +725,7 @@ cmd_isvalid (assuan_context_t ctx, char *line)
               goto again;
             }
           break;
-        case CRL_CACHE_CANTUSE: 
+        case CRL_CACHE_CANTUSE:
           err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
           break;
         default:
@@ -649,7 +743,7 @@ cmd_isvalid (assuan_context_t ctx, char *line)
    fingerprint consists of valid characters and prints and error
    message if it does not and returns NULL.  Fingerprints are
    considered optional and thus no explicit error is returned. NULL is
-   also returned if there is no fingerprint at all available. 
+   also returned if there is no fingerprint at all available.
    FPR must be a caller provided buffer of at least 20 bytes.
 
    Note that colons within the fingerprint are allowed to separate 2
@@ -681,7 +775,7 @@ get_fingerprint_from_line (const char *line, unsigned char *fpr)
 
 
 
-static const char hlp_checkcrl[] = 
+static const char hlp_checkcrl[] =
   "CHECKCRL [<fingerprint>]\n"
   "\n"
   "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
@@ -711,14 +805,14 @@ cmd_checkcrl (assuan_context_t ctx, char *line)
 
   fpr = get_fingerprint_from_line (line, fprbuffer);
   cert = fpr? get_cert_byfpr (fpr) : NULL;
-  
+
   if (!cert)
     {
       /* We do not have this certificate yet or the fingerprint has
          not been given.  Inquire it from the client.  */
       unsigned char *value = NULL;
-      size_t valuelen; 
-      
+      size_t valuelen;
+
       err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
                            &value, &valuelen, MAX_CERT_LENGTH);
       if (err)
@@ -726,7 +820,7 @@ cmd_checkcrl (assuan_context_t ctx, char *line)
           log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
           goto leave;
         }
-  
+
       if (!valuelen) /* No data returned; return a comprehensible error. */
         err = gpg_error (GPG_ERR_MISSING_CERT);
       else
@@ -756,7 +850,7 @@ cmd_checkcrl (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_checkocsp[] = 
+static const char hlp_checkocsp[] =
   "CHECKOCSP [--force-default-responder] [<fingerprint>]\n"
   "\n"
   "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
@@ -791,20 +885,20 @@ cmd_checkocsp (assuan_context_t ctx, char *line)
   unsigned char fprbuffer[20], *fpr;
   ksba_cert_t cert;
   int force_default_responder;
-  
+
   force_default_responder = has_option (line, "--force-default-responder");
   line = skip_options (line);
 
   fpr = get_fingerprint_from_line (line, fprbuffer);
   cert = fpr? get_cert_byfpr (fpr) : NULL;
-  
+
   if (!cert)
     {
       /* We do not have this certificate yet or the fingerprint has
          not been given.  Inquire it from the client.  */
       unsigned char *value = NULL;
-      size_t valuelen; 
-      
+      size_t valuelen;
+
       err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
                            &value, &valuelen, MAX_CERT_LENGTH);
       if (err)
@@ -812,7 +906,7 @@ cmd_checkocsp (assuan_context_t ctx, char *line)
           log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
           goto leave;
         }
-  
+
       if (!valuelen) /* No data returned; return a comprehensible error. */
         err = gpg_error (GPG_ERR_MISSING_CERT);
       else
@@ -846,7 +940,7 @@ lookup_cert_by_url (assuan_context_t ctx, const char *url)
   ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err = 0;
   unsigned char *value = NULL;
-  size_t valuelen; 
+  size_t valuelen;
 
   /* Fetch single certificate given it's URL.  */
   err = fetch_cert_by_url (ctrl, url, &value, &valuelen);
@@ -857,12 +951,12 @@ lookup_cert_by_url (assuan_context_t ctx, const char *url)
     }
 
   /* Send the data, flush the buffer and then send an END. */
-  err = assuan_send_data (ctx, value, valuelen);      
+  err = assuan_send_data (ctx, value, valuelen);
   if (!err)
     err = assuan_send_data (ctx, NULL, 0);
   if (!err)
     err = assuan_write_line (ctx, "END");
-  if (err) 
+  if (err)
     {
       log_error (_("error sending data: %s\n"), gpg_strerror (err));
       goto leave;
@@ -888,13 +982,13 @@ return_one_cert (void *opaque, ksba_cert_t cert)
     err = gpg_error (GPG_ERR_INV_CERT_OBJ);
   else
     {
-      err = assuan_send_data (ctx, der, derlen);      
+      err = assuan_send_data (ctx, der, derlen);
       if (!err)
         err = assuan_send_data (ctx, NULL, 0);
       if (!err)
         err = assuan_write_line (ctx, "END");
     }
-  if (err) 
+  if (err)
     log_error (_("error sending data: %s\n"), gpg_strerror (err));
   return err;
 }
@@ -903,20 +997,22 @@ return_one_cert (void *opaque, ksba_cert_t cert)
 /* Lookup certificates from the internal cache or using the ldap
    servers. */
 static int
-lookup_cert_by_pattern (assuan_context_t ctx, char *line, 
+lookup_cert_by_pattern (assuan_context_t ctx, char *line,
                         int single, int cache_only)
 {
-  ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err = 0;
   char *p;
   strlist_t sl, list = NULL;
   int truncated = 0, truncation_forced = 0;
   int count = 0;
   int local_count = 0;
+#if USE_LDAP
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   unsigned char *value = NULL;
-  size_t valuelen; 
+  size_t valuelen;
   struct ldapserver_iter ldapserver_iter;
   cert_fetch_context_t fetch_context;
+#endif /*USE_LDAP*/
   int any_no_data = 0;
 
   /* Break the line down into an STRLIST */
@@ -924,7 +1020,7 @@ lookup_cert_by_pattern (assuan_context_t ctx, char *line,
     {
       while (*p && *p != ' ')
         p++;
-      if (*p) 
+      if (*p)
         *p++ = 0;
 
       if (*line)
@@ -954,7 +1050,7 @@ lookup_cert_by_pattern (assuan_context_t ctx, char *line,
           if (!err)
             local_count++;
           if (!err && single)
-            goto ready; 
+            goto ready;
 
           if (gpg_err_code (err) == GPG_ERR_NO_DATA)
             {
@@ -975,15 +1071,16 @@ lookup_cert_by_pattern (assuan_context_t ctx, char *line,
 
   /* Loop over all configured servers unless we want only the
      certificates from the cache.  */
+#if USE_LDAP
   for (ldapserver_iter_begin (&ldapserver_iter, ctrl);
        !cache_only && !ldapserver_iter_end_p (&ldapserver_iter)
         && ldapserver_iter.server->host && !truncation_forced;
        ldapserver_iter_next (&ldapserver_iter))
     {
       ldap_server_t ldapserver = ldapserver_iter.server;
-      
+
       if (DBG_LOOKUP)
-        log_debug ("cmd_lookup: trying %s:%d base=%s\n", 
+        log_debug ("cmd_lookup: trying %s:%d base=%s\n",
                    ldapserver->host, ldapserver->port,
                    ldapserver->base?ldapserver->base : "[default]");
 
@@ -1037,25 +1134,25 @@ lookup_cert_by_pattern (assuan_context_t ctx, char *line,
               end_cert_fetch (fetch_context);
               goto leave;
             }
-          
+
           if (DBG_LOOKUP)
             log_debug ("cmd_lookup: returning one cert%s\n",
                        truncated? " (truncated)":"");
-          
+
           /* Send the data, flush the buffer and then send an END line
              as a certificate delimiter. */
-          err = assuan_send_data (ctx, value, valuelen);      
+          err = assuan_send_data (ctx, value, valuelen);
           if (!err)
             err = assuan_send_data (ctx, NULL, 0);
           if (!err)
             err = assuan_write_line (ctx, "END");
-          if (err) 
+          if (err)
             {
               log_error (_("error sending data: %s\n"), gpg_strerror (err));
               end_cert_fetch (fetch_context);
               goto leave;
             }
-          
+
           if (++count >= opt.max_replies )
             {
               truncation_forced = 1;
@@ -1067,6 +1164,7 @@ lookup_cert_by_pattern (assuan_context_t ctx, char *line,
 
       end_cert_fetch (fetch_context);
     }
+#endif /*USE_LDAP*/
 
  ready:
   if (truncated || truncation_forced)
@@ -1074,7 +1172,7 @@ lookup_cert_by_pattern (assuan_context_t ctx, char *line,
       char str[50];
 
       sprintf (str, "%d", count);
-      assuan_write_status (ctx, "TRUNCATED", str);    
+      assuan_write_status (ctx, "TRUNCATED", str);
     }
 
   if (!err && !count && !local_count && any_no_data)
@@ -1086,7 +1184,7 @@ lookup_cert_by_pattern (assuan_context_t ctx, char *line,
 }
 
 
-static const char hlp_lookup[] = 
+static const char hlp_lookup[] =
   "LOOKUP [--url] [--single] [--cache-only] <pattern>\n"
   "\n"
   "Lookup certificates matching PATTERN. With --url the pattern is\n"
@@ -1156,13 +1254,13 @@ cmd_loadcrl (assuan_context_t ctx, char *line)
 
       err = crl_fetch (ctrl, line, &reader);
       if (err)
-        log_error (_("fetching CRL from `%s' failed: %s\n"),
+        log_error (_("fetching CRL from '%s' failed: %s\n"),
                    line, gpg_strerror (err));
       else
         {
-          err = crl_cache_insert (ctrl, line, reader); 
+          err = crl_cache_insert (ctrl, line, reader);
           if (err)
-            log_error (_("processing CRL from `%s' failed: %s\n"),
+            log_error (_("processing CRL from '%s' failed: %s\n"),
                        line, gpg_strerror (err));
           crl_close_reader (reader);
         }
@@ -1213,12 +1311,12 @@ cmd_listcrls (assuan_context_t ctx, char *line)
 }
 
 
-static const char hlp_cachecert[] = 
+static const char hlp_cachecert[] =
   "CACHECERT\n"
   "\n"
   "Put a certificate into the internal cache.  This command might be\n"
   "useful if a client knows in advance certificates required for a\n"
-  "test and wnats to make sure they get added to the internal cache.\n"
+  "test and wants to make sure they get added to the internal cache.\n"
   "It is also helpful for debugging.  To get the actual certificate,\n"
   "this command immediately inquires it using\n"
   "\n"
@@ -1233,10 +1331,10 @@ cmd_cachecert (assuan_context_t ctx, char *line)
   gpg_error_t err;
   ksba_cert_t cert = NULL;
   unsigned char *value = NULL;
-  size_t valuelen; 
+  size_t valuelen;
 
   (void)line;
-      
+
   err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
                        &value, &valuelen, MAX_CERT_LENGTH);
   if (err)
@@ -1244,7 +1342,7 @@ cmd_cachecert (assuan_context_t ctx, char *line)
       log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
       goto leave;
     }
-  
+
   if (!valuelen) /* No data returned; return a comprehensible error. */
     err = gpg_error (GPG_ERR_MISSING_CERT);
   else
@@ -1284,10 +1382,10 @@ cmd_validate (assuan_context_t ctx, char *line)
   gpg_error_t err;
   ksba_cert_t cert = NULL;
   unsigned char *value = NULL;
-  size_t valuelen; 
+  size_t valuelen;
 
   (void)line;
-    
+
   err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
                        &value, &valuelen, MAX_CERT_LENGTH);
   if (err)
@@ -1295,7 +1393,7 @@ cmd_validate (assuan_context_t ctx, char *line)
       log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
       goto leave;
     }
-  
+
   if (!valuelen) /* No data returned; return a comprehensible error. */
     err = gpg_error (GPG_ERR_MISSING_CERT);
   else
@@ -1311,7 +1409,7 @@ cmd_validate (assuan_context_t ctx, char *line)
   /* If we have this certificate already in our cache, use the cached
      version for validation because this will take care of any cached
      results. */
-  { 
+  {
     unsigned char fpr[20];
     ksba_cert_t tmpcert;
 
@@ -1331,9 +1429,344 @@ cmd_validate (assuan_context_t ctx, char *line)
   return leave_cmd (ctx, err);
 }
 
+\f
+static const char hlp_keyserver[] =
+  "KEYSERVER [<options>] [<uri>|<host>]\n"
+  "Options are:\n"
+  "  --help\n"
+  "  --clear      Remove all configured keyservers\n"
+  "  --resolve    Resolve HKP host names and rotate\n"
+  "  --hosttable  Print table of known hosts and pools\n"
+  "  --dead       Mark <host> as dead\n"
+  "  --alive      Mark <host> as alive\n"
+  "\n"
+  "If called without arguments list all configured keyserver URLs.\n"
+  "If called with an URI add this as keyserver.  Note that keyservers\n"
+  "are configured on a per-session base.  A default keyserver may already be\n"
+  "present, thus the \"--clear\" option must be used to get full control.\n"
+  "If \"--clear\" and an URI are used together the clear command is\n"
+  "obviously executed first.  A RESET command does not change the list\n"
+  "of configured keyservers.";
+static gpg_error_t
+cmd_keyserver (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err = 0;
+  int clear_flag, add_flag, help_flag, host_flag, resolve_flag;
+  int dead_flag, alive_flag;
+  uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it
+                             is always initialized.  */
+
+  clear_flag = has_option (line, "--clear");
+  help_flag = has_option (line, "--help");
+  resolve_flag = has_option (line, "--resolve");
+  host_flag = has_option (line, "--hosttable");
+  dead_flag = has_option (line, "--dead");
+  alive_flag = has_option (line, "--alive");
+  line = skip_options (line);
+  add_flag = !!*line;
+
+  if (help_flag)
+    {
+      err = ks_action_help (ctrl, line);
+      goto leave;
+    }
+
+  if (resolve_flag)
+    {
+      err = ks_action_resolve (ctrl);
+      if (err)
+        goto leave;
+    }
+
+  if (alive_flag && dead_flag)
+    {
+      err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies");
+      goto leave;
+    }
+  if (dead_flag)
+    {
+      err = check_owner_permission (ctx, "no permission to use --dead");
+      if (err)
+        goto leave;
+    }
+  if (alive_flag || dead_flag)
+    {
+      if (!*line)
+        {
+          err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing");
+          goto leave;
+        }
+
+      err = ks_hkp_mark_host (ctrl, line, alive_flag);
+      if (err)
+        goto leave;
+    }
+
+  if (host_flag)
+    {
+      err = ks_hkp_print_hosttable (ctrl);
+      if (err)
+        goto leave;
+    }
+  if (resolve_flag || host_flag || alive_flag || dead_flag)
+    goto leave;
+
+  if (add_flag)
+    {
+      item = xtrymalloc (sizeof *item + strlen (line));
+      if (!item)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      item->next = NULL;
+      item->parsed_uri = NULL;
+      strcpy (item->uri, line);
+
+      err = http_parse_uri (&item->parsed_uri, line, 1);
+      if (err)
+        {
+          xfree (item);
+          goto leave;
+        }
+    }
+  if (clear_flag)
+    release_ctrl_keyservers (ctrl);
+  if (add_flag)
+    {
+      item->next = ctrl->keyservers;
+      ctrl->keyservers = item;
+    }
+
+  if (!add_flag && !clear_flag && !help_flag) /* List configured keyservers.  */
+    {
+      uri_item_t u;
+
+      for (u=ctrl->keyservers; u; u = u->next)
+        dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL);
+    }
+  err = 0;
+
+ leave:
+  return leave_cmd (ctx, err);
+}
+
+
+\f
+static const char hlp_ks_search[] =
+  "KS_SEARCH {<pattern>}\n"
+  "\n"
+  "Search the configured OpenPGP keyservers (see command KEYSERVER)\n"
+  "for keys matching PATTERN";
+static gpg_error_t
+cmd_ks_search (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  strlist_t list, sl;
+  char *p;
+  estream_t outfp;
+
+  /* No options for now.  */
+  line = skip_options (line);
+
+  /* Break the line down into an strlist.  Each pattern is
+     percent-plus escaped. */
+  list = NULL;
+  for (p=line; *p; line = p)
+    {
+      while (*p && *p != ' ')
+        p++;
+      if (*p)
+        *p++ = 0;
+      if (*line)
+        {
+          sl = xtrymalloc (sizeof *sl + strlen (line));
+          if (!sl)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          sl->flags = 0;
+          strcpy_escaped_plus (sl->d, line);
+          sl->next = list;
+          list = sl;
+        }
+    }
+
+  /* Setup an output stream and perform the search.  */
+  outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
+  if (!outfp)
+    err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
+  else
+    {
+      err = ks_action_search (ctrl, list, outfp);
+      es_fclose (outfp);
+    }
+
+ leave:
+  free_strlist (list);
+  return leave_cmd (ctx, err);
+}
+
 
 \f
-static const char hlp_getinfo[] = 
+static const char hlp_ks_get[] =
+  "KS_GET {<pattern>}\n"
+  "\n"
+  "Get the keys matching PATTERN from the configured OpenPGP keyservers\n"
+  "(see command KEYSERVER).  Each pattern should be a keyid, a fingerprint,\n"
+  "or an exact name indicastes by the '=' prefix.";
+static gpg_error_t
+cmd_ks_get (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  strlist_t list, sl;
+  char *p;
+  estream_t outfp;
+
+  /* No options for now.  */
+  line = skip_options (line);
+
+  /* Break the line down into an strlist.  Each pattern is by
+     definition percent-plus escaped.  However we only support keyids
+     and fingerprints and thus the client has no need to apply the
+     escaping.  */
+  list = NULL;
+  for (p=line; *p; line = p)
+    {
+      while (*p && *p != ' ')
+        p++;
+      if (*p)
+        *p++ = 0;
+      if (*line)
+        {
+          sl = xtrymalloc (sizeof *sl + strlen (line));
+          if (!sl)
+            {
+              err = gpg_error_from_syserror ();
+              goto leave;
+            }
+          sl->flags = 0;
+          strcpy_escaped_plus (sl->d, line);
+          sl->next = list;
+          list = sl;
+        }
+    }
+
+  /* Setup an output stream and perform the get.  */
+  outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
+  if (!outfp)
+    err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
+  else
+    {
+      err = ks_action_get (ctrl, list, outfp);
+      es_fclose (outfp);
+    }
+
+ leave:
+  free_strlist (list);
+  return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_ks_fetch[] =
+  "KS_FETCH <URL>\n"
+  "\n"
+  "Get the key(s) from URL.";
+static gpg_error_t
+cmd_ks_fetch (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  estream_t outfp;
+
+  /* No options for now.  */
+  line = skip_options (line);
+
+  /* Setup an output stream and perform the get.  */
+  outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
+  if (!outfp)
+    err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
+  else
+    {
+      err = ks_action_fetch (ctrl, line, outfp);
+      es_fclose (outfp);
+    }
+
+  return leave_cmd (ctx, err);
+}
+
+
+\f
+static const char hlp_ks_put[] =
+  "KS_PUT\n"
+  "\n"
+  "Send a key to the configured OpenPGP keyservers.  The actual key material\n"
+  "is then requested by Dirmngr using\n"
+  "\n"
+  "  INQUIRE KEYBLOCK\n"
+  "\n"
+  "The client shall respond with a binary version of the keyblock.  For LDAP\n"
+  "keyservers Dirmngr may ask for meta information of the provided keyblock\n"
+  "using:\n"
+  "\n"
+  "  INQUIRE KEYBLOCK_INFO\n"
+  "\n"
+  "The client shall respond with a colon delimited info lines";
+static gpg_error_t
+cmd_ks_put (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  unsigned char *value = NULL;
+  size_t valuelen;
+  unsigned char *info = NULL;
+  size_t infolen;
+
+  /* No options for now.  */
+  line = skip_options (line);
+
+  /* Ask for the key material.  */
+  err = assuan_inquire (ctx, "KEYBLOCK",
+                        &value, &valuelen, MAX_KEYBLOCK_LENGTH);
+  if (err)
+    {
+      log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+      goto leave;
+    }
+
+  if (!valuelen) /* No data returned; return a comprehensible error. */
+    {
+      err = gpg_error (GPG_ERR_MISSING_CERT);
+      goto leave;
+    }
+
+  /* Ask for the key meta data. Not actually needed for HKP servers
+     but we do it anyway test the client implementaion.  */
+  err = assuan_inquire (ctx, "KEYBLOCK_INFO",
+                        &info, &infolen, MAX_KEYBLOCK_LENGTH);
+  if (err)
+    {
+      log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Send the key.  */
+  err = ks_action_put (ctrl, value, valuelen);
+
+ leave:
+  xfree (info);
+  xfree (value);
+  return leave_cmd (ctx, err);
+}
+
+
+
+\f
+static const char hlp_getinfo[] =
   "GETINFO <what>\n"
   "\n"
   "Multi purpose command to return certain information.  \n"
@@ -1362,7 +1795,10 @@ cmd_getinfo (assuan_context_t ctx, char *line)
     }
   else if (!strcmp (line, "socket_name"))
     {
-      const char *s = dirmngr_socket_name ();
+      const char *s = dirmngr_user_socket_name ();
+
+      if (!s)
+        s = dirmngr_sys_socket_name ();
 
       if (s)
         err = assuan_send_data (ctx, s, strlen (s));
@@ -1376,6 +1812,73 @@ cmd_getinfo (assuan_context_t ctx, char *line)
 }
 
 
+\f
+static const char hlp_killdirmngr[] =
+  "KILLDIRMNGR\n"
+  "\n"
+  "This command allows a user - given sufficient permissions -\n"
+  "to kill this dirmngr process.\n";
+static gpg_error_t
+cmd_killdirmngr (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+
+  (void)line;
+
+  if (opt.system_daemon)
+    {
+      if (opt.system_service)
+        err = set_error (GPG_ERR_NOT_SUPPORTED,
+                         "can't do that whilst running as system service");
+      else
+        err = check_owner_permission (ctx,
+                                      "no permission to kill this process");
+    }
+  else
+    err = 0;
+
+  if (!err)
+    {
+      ctrl->server_local->stopme = 1;
+      err = gpg_error (GPG_ERR_EOF);
+    }
+  return err;
+}
+
+
+static const char hlp_reloaddirmngr[] =
+  "RELOADDIRMNGR\n"
+  "\n"
+  "This command is an alternative to SIGHUP\n"
+  "to reload the configuration.";
+static gpg_error_t
+cmd_reloaddirmngr (assuan_context_t ctx, char *line)
+{
+  (void)ctx;
+  (void)line;
+
+ if (opt.system_daemon)
+    {
+#ifndef HAVE_W32_SYSTEM
+      {
+        gpg_err_code_t ec;
+        assuan_peercred_t cred;
+
+        ec = gpg_err_code (assuan_get_peercred (ctx, &cred));
+        if (!ec && cred->uid)
+          ec = GPG_ERR_EPERM; /* Only root may terminate.  */
+        if (ec)
+          return set_error (ec, "no permission to reload this process");
+      }
+#endif
+    }
+
+  dirmngr_sighup_action ();
+  return 0;
+}
+
+
 
 \f
 /* Tell the assuan library about our commands. */
@@ -1396,7 +1899,14 @@ register_commands (assuan_context_t ctx)
     { "LISTCRLS",   cmd_listcrls,   hlp_listcrls },
     { "CACHECERT",  cmd_cachecert,  hlp_cachecert },
     { "VALIDATE",   cmd_validate,   hlp_validate },
+    { "KEYSERVER",  cmd_keyserver,  hlp_keyserver },
+    { "KS_SEARCH",  cmd_ks_search,  hlp_ks_search },
+    { "KS_GET",     cmd_ks_get,     hlp_ks_get },
+    { "KS_FETCH",   cmd_ks_fetch,   hlp_ks_fetch },
+    { "KS_PUT",     cmd_ks_put,     hlp_ks_put },
     { "GETINFO",    cmd_getinfo,    hlp_getinfo },
+    { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr },
+    { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr },
     { NULL, NULL }
   };
   int i, j, rc;
@@ -1412,13 +1922,16 @@ register_commands (assuan_context_t ctx)
 }
 
 
+/* Note that we do not reset the list of configured keyservers.  */
 static gpg_error_t
 reset_notify (assuan_context_t ctx, char *line)
 {
   ctrl_t ctrl = assuan_get_pointer (ctx);
   (void)line;
 
+#if USE_LDAP
   ldapserver_list_free (ctrl->server_local->ldapservers);
+#endif /*USE_LDAP*/
   ctrl->server_local->ldapservers = NULL;
   return 0;
 }
@@ -1445,7 +1958,7 @@ start_command_handler (assuan_fd_t fd)
       xfree (ctrl);
       return;
     }
-    
+
   dirmngr_init_default_ctrl (ctrl);
 
   rc = assuan_new (&ctx);
@@ -1459,7 +1972,7 @@ start_command_handler (assuan_fd_t fd)
   if (fd == ASSUAN_INVALID_FD)
     {
       assuan_fd_t filedes[2];
-      
+
       filedes[0] = assuan_fdopen (0);
       filedes[1] = assuan_fdopen (1);
       rc = assuan_init_pipe_server (ctx, filedes);
@@ -1513,7 +2026,7 @@ start_command_handler (assuan_fd_t fd)
   assuan_register_option_handler (ctx, option_handler);
   assuan_register_reset_notify (ctx, reset_notify);
 
-  for (;;) 
+  for (;;)
     {
       rc = assuan_accept (ctx);
       if (rc == -1)
@@ -1543,15 +2056,20 @@ start_command_handler (assuan_fd_t fd)
           continue;
         }
     }
-  
+
+#if USE_LDAP
   ldap_wrapper_connection_cleanup (ctrl);
 
   ldapserver_list_free (ctrl->server_local->ldapservers);
+#endif /*USE_LDAP*/
   ctrl->server_local->ldapservers = NULL;
 
   ctrl->server_local->assuan_ctx = NULL;
   assuan_release (ctx);
 
+  if (ctrl->server_local->stopme)
+    dirmngr_exit (0);
+
   if (ctrl->refcount)
     log_error ("oops: connection control structure still referenced (%d)\n",
                ctrl->refcount);
@@ -1565,7 +2083,7 @@ start_command_handler (assuan_fd_t fd)
 
 
 /* Send a status line back to the client.  KEYWORD is the status
-   keyword, the optioal string argumenst are blank separated added to
+   keyword, the optional string arguments are blank separated added to
    the line, the last argument must be a NULL. */
 gpg_error_t
 dirmngr_status (ctrl_t ctrl, const char *keyword, ...)
@@ -1581,8 +2099,8 @@ dirmngr_status (ctrl_t ctrl, const char *keyword, ...)
       assuan_context_t ctx = ctrl->server_local->assuan_ctx;
       char buf[950], *p;
       size_t n;
-      
-      p = buf; 
+
+      p = buf;
       n = 0;
       while ( (text = va_arg (arg_ptr, const char *)) )
         {
@@ -1603,8 +2121,38 @@ dirmngr_status (ctrl_t ctrl, const char *keyword, ...)
 }
 
 
-/* Note, that we ignore CTRL for now but use the first connection to
-   send the progress info back. */
+/* Print a help status line.  TEXTLEN gives the length of the text
+   from TEXT to be printed.  The function splits text at LFs.  */
+gpg_error_t
+dirmngr_status_help (ctrl_t ctrl, const char *text)
+{
+  gpg_error_t err = 0;
+
+  if (ctrl->server_local)
+    {
+      assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+      char buf[950], *p;
+      size_t n;
+
+      do
+        {
+          p = buf;
+          n = 0;
+          for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++)
+            *p++ = *text++;
+          if (*text == '\n')
+            text++;
+          *p = 0;
+          err = assuan_write_status (ctx, "#", buf);
+        }
+      while (!err && *text);
+    }
+
+  return err;
+}
+
+/* Send a tick progress indicator back.  Fixme: This is only done for
+   the currently active channel.  */
 gpg_error_t
 dirmngr_tick (ctrl_t ctrl)
 {