dirmngr (36 files)

cvs user wk cvs at cvs.gnupg.org
Tue Nov 16 19:18:40 CET 2004


    Date: Tuesday, November 16, 2004 @ 19:24:36
  Author: wk
    Path: /cvs/dirmngr/dirmngr

   Added: compile src/certcache.c src/certcache.h src/dirmngr-client.c
          src/dirmngr_ldap.c src/i18n.h src/maperror.c src/no-libgcrypt.c
          src/no-libgcrypt.h src/util.h src/validate.c src/validate.h
          tests/show-multi.c
Modified: ChangeLog NEWS TODO acinclude.m4 configure.ac doc/dirmngr.texi
          jnlib/ChangeLog jnlib/logging.c po/POTFILES.in src/ChangeLog
          src/Makefile.am src/crlcache.c src/crlcache.h src/crlfetch.c
          src/crlfetch.h src/dirmngr.c src/dirmngr.h src/http.c src/ldap.c
          src/misc.c src/misc.h src/server.c tests/Makefile.am

Started a partial rewrite with the goal to optionally run it as a
system daemon.  

Currently builds but is broken in some important parts.


----------------------+
 ChangeLog            |   13 
 NEWS                 |    9 
 TODO                 |    9 
 acinclude.m4         |   41 +
 compile              |  107 +++
 configure.ac         |   49 +
 doc/dirmngr.texi     |  185 ++++++
 jnlib/ChangeLog      |    8 
 jnlib/logging.c      |  306 ++++++----
 po/POTFILES.in       |    2 
 src/ChangeLog        |  122 ++++
 src/Makefile.am      |   35 -
 src/certcache.c      |  313 ++++++++++
 src/certcache.h      |   45 +
 src/crlcache.c       |  112 ++-
 src/crlcache.h       |   19 
 src/crlfetch.c       |  192 ++----
 src/crlfetch.h       |   27 
 src/dirmngr-client.c |  474 +++++++++++++++
 src/dirmngr.c        |  510 +++++++++++++++--
 src/dirmngr.h        |   62 --
 src/dirmngr_ldap.c   |  497 ++++++++++++++++
 src/http.c           |    2 
 src/i18n.h           |   48 +
 src/ldap.c           | 1456 ++++++++++++++++++++++++++-----------------------
 src/maperror.c       |   74 ++
 src/misc.c           |  103 +++
 src/misc.h           |   19 
 src/no-libgcrypt.c   |  107 +++
 src/no-libgcrypt.h   |   33 +
 src/server.c         |  496 ++++++++++++----
 src/util.h           |   57 +
 src/validate.c       |  781 ++++++++++++++++++++++++++
 src/validate.h       |   37 +
 tests/Makefile.am    |    5 
 tests/show-multi.c   |  170 +++++
 36 files changed, 5313 insertions(+), 1212 deletions(-)


Index: dirmngr/ChangeLog
diff -u dirmngr/ChangeLog:1.75 dirmngr/ChangeLog:1.76
--- dirmngr/ChangeLog:1.75	Mon Oct  4 13:50:51 2004
+++ dirmngr/ChangeLog	Tue Nov 16 19:24:36 2004
@@ -1,3 +1,14 @@
+2004-11-15  Werner Koch  <wk at g10code.com>
+
+	* tests/show-multi.c: New.
+	* tests/Makefile.am (noinst_PROGRAMS): Added.
+
+2004-11-08  Werner Koch  <wk at g10code.com>
+
+	* acinclude.m4 (GNUPG_PTH_VERSION_CHECK): New.  Taken from gnupg.
+	* configure.ac: Check for GNU Pth.
+	* doc/dirmngr.texi (Dirmngr Client): Added.
+
 2004-10-04  Werner Koch  <wk at g10code.com>
 
 	* src/dirmngr.c: Changed an help entry description.
@@ -564,7 +575,7 @@
 	* src/ldap.c (url_fetch_ldap()):
 	  try other default servers when an url with hostname failed
 	* AUTHORS:  added Steffen and Werner
-	* THANKS: Thanked people in the ChangeLog and the Ägypten-Team
+	* THANKS: Thanked people in the ChangeLog and the Ägypten-Team
 
 
 2003-06-16  Steffen Hansen  <steffen at klaralvdalens-datakonsult.se>
Index: dirmngr/NEWS
diff -u dirmngr/NEWS:1.26 dirmngr/NEWS:1.27
--- dirmngr/NEWS:1.26	Tue Sep 28 15:17:24 2004
+++ dirmngr/NEWS	Tue Nov 16 19:24:36 2004
@@ -1,6 +1,11 @@
-Noteworthy changes in version 0.5.7
+Noteworthy changes in version 0.9.0
 ------------------------------------------------
 
+  WARNING: CURRENTLY UNDER HEAVY RECONSTRUCTION.  Many things might
+  not yet work again. 
+
+ * New tool dirmngr-client.
+
 
 Noteworthy changes in version 0.5.6 (2004-09-28)
 ------------------------------------------------
@@ -79,7 +84,7 @@
 * The default home directory is now .gnupg
 
 
- Copyright 2003 g10 Code GmbH
+ Copyright 2003, 2004 g10 Code GmbH
 
  This file is free software; as a special exception the author gives
  unlimited permission to copy and/or distribute it, with or without
Index: dirmngr/TODO
diff -u dirmngr/TODO:1.13 dirmngr/TODO:1.14
--- dirmngr/TODO:1.13	Wed Nov  3 18:48:03 2004
+++ dirmngr/TODO	Tue Nov 16 19:24:36 2004
@@ -15,12 +15,15 @@
    This is the server where a baseDN has been given and that baseDN is
    contained in the search pattern.
 
-* Properly parse LDAP attributes from the URL
-  and remove the bad hack in crl_fetch.
+* Properly parse LDAP attributes from the URL and remove the bad hack in crl_fetch.
 
 * Add --honor-http-proxy
 
-* Write a utility to use Dirmngr from a script or by other programs.
+* Properly use locks in crlcache.c
 
+* truncated
+  Need to updated the code in ldap.c
+
+* In daemon mode we should put the cache files into /var/cache/dirmngr
 
 
Index: dirmngr/acinclude.m4
diff -u dirmngr/acinclude.m4:1.3 dirmngr/acinclude.m4:1.4
--- dirmngr/acinclude.m4:1.3	Thu Nov 13 17:19:57 2003
+++ dirmngr/acinclude.m4	Tue Nov 16 19:24:36 2004
@@ -35,4 +35,45 @@
     fi
   ])
 
+# GNUPG_PTH_VERSION_CHECK(REQUIRED)
+# 
+# If the version is sufficient, HAVE_PTH will be set to yes.
+#
+# Taken form the m4 macros which come with Pth
+AC_DEFUN(GNUPG_PTH_VERSION_CHECK,
+  [
+    _pth_version=`$PTH_CONFIG --version | awk 'NR==1 {print [$]3}'`
+    _req_version="ifelse([$1],,1.2.0,$1)"
+    for _var in _pth_version _req_version; do
+        eval "_val=\"\$${_var}\""
+        _major=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[ab.]]\)\([[0-9]]*\)/\1/'`
+        _minor=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[ab.]]\)\([[0-9]]*\)/\2/'`
+        _rtype=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[ab.]]\)\([[0-9]]*\)/\3/'`
+        _micro=`echo $_val | sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\([[ab.]]\)\([[0-9]]*\)/\4/'`
+        case $_rtype in
+            "a" ) _rtype=0 ;;
+            "b" ) _rtype=1 ;;
+            "." ) _rtype=2 ;;
+        esac
+        _hex=`echo dummy | awk '{ printf("%d%02d%1d%02d", major, minor, rtype, micro); }' \
+              "major=$_major" "minor=$_minor" "rtype=$_rtype" "micro=$_micro"`
+        eval "${_var}_hex=\"\$_hex\""
+    done
+    have_pth=no
+    if test ".$_pth_version_hex" != .; then
+        if test ".$_req_version_hex" != .; then
+            if test $_pth_version_hex -ge $_req_version_hex; then
+                have_pth=yes
+            fi
+        fi
+    fi
+    if test $have_pth = no; then
+       AC_MSG_WARN([[
+***
+*** Found Pth version $_pth_version, but require at least
+*** version $_req_version.  Please upgrade Pth first.
+***]])
+    fi    
+  ])
+
 
Index: dirmngr/compile
diff -u /dev/null dirmngr/compile:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/compile	Tue Nov 16 19:24:36 2004
@@ -0,0 +1,107 @@
+#! /bin/sh
+
+# Wrapper for compilers which do not understand `-c -o'.
+
+# Copyright 1999, 2000 Free Software Foundation, Inc.
+# Written by Tom Tromey <tromey at cygnus.com>.
+#
+# This program 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, or (at your option)
+# any later version.
+#
+# This program 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.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# Usage:
+# compile PROGRAM [ARGS]...
+# `-o FOO.o' is removed from the args passed to the actual compile.
+
+# Usage statement added by Billy Biggs <vektor at dumbterm.net>.
+if [ -z $1 ]; then
+    echo "Wrapper for compilers which do not understand '-c -o'."
+    echo "usage: compile PROGRAM [ARGS]..."
+    echo "'-o FOO.o' is removed from the args passed to the actual compile."
+    exit 1
+fi
+
+prog=$1
+shift
+
+ofile=
+cfile=
+args=
+while test $# -gt 0; do
+   case "$1" in
+    -o)
+       # configure might choose to run compile as `compile cc -o foo foo.c'.
+       # So we do something ugly here.
+       ofile=$2
+       shift
+       case "$ofile" in
+	*.o | *.obj)
+	   ;;
+	*)
+	   args="$args -o $ofile"
+	   ofile=
+	   ;;
+       esac
+       ;;
+    *.c)
+       cfile=$1
+       args="$args $1"
+       ;;
+    *)
+       args="$args $1"
+       ;;
+   esac
+   shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+   # If no `-o' option was seen then we might have been invoked from a
+   # pattern rule where we don't need one.  That is ok -- this is a
+   # normal compilation that the losing compiler can handle.  If no
+   # `.c' file was seen then we are probably linking.  That is also
+   # ok.
+   exec "$prog" $args
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo $cfile | sed -e 's|^.*/||' -e 's/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use `[/.-]' here to ensure that we don't use the same name
+# that we are using for the .o file.  Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo $cofile | sed -e 's|[/.-]|_|g'`.d
+while true; do
+   if mkdir $lockdir > /dev/null 2>&1; then
+      break
+   fi
+   sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir $lockdir; exit 1" 1 2 15
+
+# Run the compile.
+"$prog" $args
+status=$?
+
+if test -f "$cofile"; then
+   mv "$cofile" "$ofile"
+fi
+
+rmdir $lockdir
+exit $status
Index: dirmngr/configure.ac
diff -u dirmngr/configure.ac:1.60 dirmngr/configure.ac:1.61
--- dirmngr/configure.ac:1.60	Tue Sep 28 15:17:24 2004
+++ dirmngr/configure.ac	Tue Nov 16 19:24:36 2004
@@ -1,5 +1,5 @@
 # configure.ac - for DirMngr
-#       Copyright (C) 2002 Klarälvdalens Datakonsult AB
+#       Copyright (C) 2002 Klarälvdalens Datakonsult AB
 #       Copyright (C) 2003, 2004 g10 Code GmbH
 # 
 # This file is part of DirMngr.
@@ -19,10 +19,10 @@
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 
 # Process this file with autoconf to produce a configure script.
-AC_PREREQ(2.57)
-min_automake_version="1.7.6"
+AC_PREREQ(2.59)
+min_automake_version="1.7.9"
 
-AC_INIT(dirmngr, 0.5.7-cvs, gpa-dev at gnupg.org)
+AC_INIT(dirmngr, 0.9.0-cvs, gpa-dev at gnupg.org)
 
 NEED_GPG_ERROR_VERSION=0.7
 
@@ -164,12 +164,49 @@
 AM_PATH_KSBA("$NEED_KSBA_VERSION",have_ksba=yes,have_ksba=no)
 
 
+#
+# Check whether the GNU Pth library is available
+#
+AC_ARG_WITH(pth-prefix,
+            AC_HELP_STRING([--with-pth-prefix=PFX],
+                           [prefix where GNU Pth is installed (optional)]),
+     pth_config_prefix="$withval", pth_config_prefix="")
+if test x$pth_config_prefix != x ; then
+   PTH_CONFIG="$pth_config_prefix/bin/pth-config"
+fi
+AC_PATH_PROG(PTH_CONFIG, pth-config, no)
+if test "$PTH_CONFIG" = "no"; then
+    AC_MSG_WARN([[
+***
+*** GNU Portable Threads Library not found.  
+*** The --daemon mode won't work.
+*** Download GNU Pth from ftp://ftp.gnu.org/gnu/pth/
+*** On a Debian GNU/Linux system you might want to try 
+***   apt-get install libpth-dev
+***]])
+else
+  GNUPG_PTH_VERSION_CHECK(1.3.7)
+  if test $have_pth = yes; then      
+     PTH_CFLAGS=`$PTH_CONFIG --cflags`
+     PTH_LIBS=`$PTH_CONFIG --ldflags`
+     PTH_LIBS="$PTH_LIBS `$PTH_CONFIG --libs`"
+     AC_DEFINE(USE_GNU_PTH, 1,
+              [Defined if the GNU Portable Thread Library should be used])
+  fi
+fi
+AC_SUBST(PTH_CFLAGS)
+AC_SUBST(PTH_LIBS)
 
 
-
+#
+# Do we need the resolver lib
+#
 AC_HAVE_LIBRARY(resolv)
 
-### OpenLDAP libraries/includes
+
+#
+# OpenLDAP libraries/includes
+#
 AC_DEFUN(AC_FIND_FILE,
 [
 $3=NO
Index: dirmngr/doc/dirmngr.texi
diff -u dirmngr/doc/dirmngr.texi:1.13 dirmngr/doc/dirmngr.texi:1.14
--- dirmngr/doc/dirmngr.texi:1.13	Wed Apr 28 11:00:54 2004
+++ dirmngr/doc/dirmngr.texi	Tue Nov 16 19:24:35 2004
@@ -65,7 +65,8 @@
 @ifnottex
 @dircategory GNU Utilities
 @direntry
-* dirmngr: (gnupg). X.509 CRL and OCSP server.
+* dirmngr: (gnupg).        X.509 CRL and OCSP server.
+* dirmngr-client: (gnupg). X.509 CRL and OCSP client.
 @end direntry
 This file documents the use of dirmngr.
 
@@ -116,13 +117,14 @@
 * Dirmngr Options::     List of all options.
 * Dirmngr Examples::    Some usage examples.
 * Dirmngr Protocol::    The protocol dirmngr uses.
+* Dirmngr Client::      How to use the Dirmngr client tool.
 
 * Copying::             GNU General Public License says
                         how you can copy and share 'Dirmngr'.
 
 * Option Index::        Index to command line options.
 * Index::	        Index of concepts and symbol names.
-* History::             Change hostory of this document.
+* History::             Change history of this document.
 @end menu
 
 @c man begin COMMANDS
@@ -149,6 +151,11 @@
 Run in server mode and wait for commands on the @code{stdin}.  The
 default mode is to create a socket and listen for commands there.
 
+ at item --daemon
+ at opindex daemon
+Run in background daemon mode and listen for commands on a socket.
+Note that this also changes the default home directory.
+
 @item --list-crls
 @opindex list-crls
 List the contents of the CRL cache on @code{stdout}. This is probably
@@ -191,7 +198,25 @@
 @item --options @var{file}
 @opindex options
 Reads configuration from @var{file} instead of from the default
-per-user configuration file.
+per-user configuration file.  The default configuration file is named
+ at file{gpgsm.conf} and expected in the home directory.
+
+ at item --homedir @var{dir}
+ at opindex options
+Set the name of the home directory to @var{dir}.  This option is only
+effective when used on the command line.  The default depends on the
+running mode:
+
+ @table @asis
+ @item With @code{--daemon} given on the commandline
+ the directory named @file{/etc/dirmngr}. 
+ @item Without @code{--daemon} given on the commandline
+the directory named @file{.gnupg} directly below the home directory
+of the user unless the environment variable @code{GNUPGHOME} has been set
+in which case its value will be used.
+ @end table
+
+
 
 @item -v
 @item --verbose
@@ -201,6 +226,8 @@
 You can increase the verbosity by giving several
 verbose commands to @sc{dirmngr}, such as @samp{-vv}.
 
+
+
 @item --log-file @var{file}
 @opindex log-file
 Append all logging output to @var{file}.  This is very helpful in
@@ -350,7 +377,9 @@
 
 @menu
 * Dirmngr LOOKUP::      Look up a certificate via LDAP
-* Dirmngr ISVALID::     Validate a certificate against a CRL.
+* Dirmngr ISVALID::     Validate a certificate using a CRL or OCSP.
+* Dirmngr CHECKCRL::    Validate a certificate using a CRL.
+* Dirmngr CHECKOCSP::   Validate a certificate using OCSP.
 @end menu
 
 @node Dirmngr LOOKUP
@@ -383,7 +412,7 @@
 
 
 @node Dirmngr ISVALID
- at section Validate a certificate against the CRL
+ at section Validate a certificate using a CRL or OCSP
 
 Check whether the certificate described by the certificate_ID (which
 is a fingerprint) has been revoked. Due to caching, the Dirmngr is
@@ -423,6 +452,152 @@
 whatever reason.
 
 
+ at node Dirmngr CHECKCRL
+ at section Validate a certificate using a CRL
+
+Check whether the certificate with FINGERPRINT (SHA-1 hash of the
+entire X.509 certificate blob) is valid or not by consulting the CRL
+responsible for this certificate.  If the fingerprint has not been
+given or the certificate is not know, the function inquires the
+certificate using:
+
+ at example
+  S: INQUIRE TARGETCERT 
+  C: D <DER encoded certificate>
+  C: END
+ at end example
+
+Thus the caller is expected to return the certificate for the request
+(which should match FINGERPRINT) as a binary blob.  Processing then
+takes place without further interaction; in particular dirmngr tries
+to locate other required certificate by its own mechanism which
+includes a local certificate store as well as a list of trusted root
+certificates.
+
+ at noindent
+The return code is 0 for success; i.e. the certificate has not been
+revoked or one of the usual error codes from libgpg-error.
+
+ at node Dirmngr CHECKOCSP
+ at section Validate a certificate using OCSP
+
+   Check whether the certificate with FINGERPRINT (SHA-1 hash of the
+   entire X.509 certificate blob) is valid or not by consulting the
+   appropiateOCSP responder.  If the fingerprint has not
+   been given or the certificate is not know, the function 
+   inquires the certificate using:
+
+ at example
+  S: INQUIRE TARGETCERT 
+  C: D <DER encoded certificate>
+  C: END
+ at end example
+
+   Thus the caller is expected to return the certificate for the
+   request (which should match FINGERPRINT) as a binary blob.
+   Processing then takes place without further interaction; in
+   particular dirmngr tries to locate other required certificate by
+   its own mechanism which includes a local certificate store as well
+   as a list of trusted root certificates.
+
+ at noindent
+The return code is 0 for success; i.e. the certificate has not been
+revoked or one of the usual error codes from libgpg-error.
+
+
+ at c -------------------------------------------
+ at c Dirmngr Client
+ at c ------------------------------------------
+
+ at node Dirmngr Client
+ at chapter The Client Tool
+
+The @command{dirmngr-client} is a simple tool to contact a running
+dirmngr and test whether a certificate has been revoked --- either by
+being listed in the corresponding CRL or by running the OCSP protocol.
+If no dirmngr is running, a new instances will be started but this is
+in general not a good idea due tom the huge performace overhead.
+
+ at noindent
+The usual way to run this tool is either:
+
+ at example
+dirmngr-client @var{acert}
+ at end example
+
+ at noindent
+or
+
+ at example
+dirmngr-client <@var{acert}
+ at end example
+
+Where @var{acert} is one DER encoded (binary) X.509 certificates to be
+tested.  The return value of this command is
+
+ at table @code
+
+ at item 0 
+The certificate under question is valid; i.e. there is a valid CRL
+available and it is not listed tehre or teh OCSP request returned that
+that certificate is valid.
+
+ at item 1
+The certificate has been revoked
+
+ at item 2 (and other values)
+There was a problem checking the revocation state of the certificate.
+A message to stderr has given more detailed information.  Most likely
+this is due to a missing or tool old CRL or a network problem.
+
+ at end table
+
+ at noindent
+ at command{dirmngr-client} may be called with the following options:
+
+
+ at table @gnupgtabopt
+ at item --version
+ at opindex version
+Print the program version and licensing information.  Note that you cannot
+abbreviate this command.
+
+ at item --help, -h
+ at opindex help
+Print a usage message summarizing the most useful command-line options.
+Note that you cannot abbreviate this command.
+
+ at item --quiet, -q
+ at opindex quiet
+Make the output extra brief by suppressing any informational messages.
+
+ at item -v
+ at item --verbose
+ at opindex v
+ at opindex verbose
+Outputs additional information while running.
+You can increase the verbosity by giving several
+verbose commands to @sc{dirmngr}, such as @samp{-vv}.
+
+ at item --ocsp
+ at opindex ocsp
+Do the check using the COSP protocol and ignore any CRLs.
+
+ at item --ping
+ at opindex ping
+Check whether the dirmngr daemon is up and running.
+
+ at item --use-daemon
+ at opindex use-daemon
+Always try to use a running background daemon.  The default is to try
+to start a dirmngr if a connecting a daemon fails.  This option is
+also implicitly use with @code{--ping}.
+
+
+ at end table
+
+
+
 @include gpl.texi
 
 @c ---------------------------------------------------------------------
Index: dirmngr/jnlib/ChangeLog
diff -u dirmngr/jnlib/ChangeLog:1.8 dirmngr/jnlib/ChangeLog:1.9
--- dirmngr/jnlib/ChangeLog:1.8	Tue Sep 28 14:27:26 2004
+++ dirmngr/jnlib/ChangeLog	Tue Nov 16 19:24:35 2004
@@ -1,3 +1,11 @@
+2004-10-21  Werner Koch  <wk at g10code.com>
+
+	* logging.c (do_logv): Use set_log_stream to setup a default.
+	(log_set_file): Factored code out to ..
+	(set_file_fd): .. New function to allow using a file descriptor.
+	(log_set_fd): Make use of new fucntion.
+	(fun_writer): Reworked.
+
 2004-09-28  Werner Koch  <wk at g10code.com>
 
 	* stringhelp.c (print_sanitized_utf8_string): Actually implement
Index: dirmngr/jnlib/logging.c
diff -u dirmngr/jnlib/logging.c:1.7 dirmngr/jnlib/logging.c:1.8
--- dirmngr/jnlib/logging.c:1.7	Tue Sep 28 14:27:26 2004
+++ dirmngr/jnlib/logging.c	Tue Nov 16 19:24:35 2004
@@ -37,14 +37,20 @@
 #include <sys/socket.h>
 #include <sys/un.h>
 #include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
 #ifdef __MINGW32__
 #  include <io.h>
 #endif
 
+
 #define JNLIB_NEED_LOG_LOGV 1
 #include "libjnlib-config.h"
 #include "logging.h"
 
+#if defined (HAVE_FOPENCOOKIE) ||  defined (HAVE_FUNOPEN)
+#define USE_FUNWRITER 1
+#endif
 
 static FILE *logstream;
 static int log_socket = -1;
@@ -94,10 +100,12 @@
 
 /* The follwing 3 functions are used by funopen to write logs to a
    socket. */
-#if defined (HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN)
+#ifdef USE_FUNWRITER
 struct fun_cookie_s {
   int fd;
   int quiet;
+  int want_socket;
+  int is_socket;
   char name[1];
 };
 
@@ -135,64 +143,78 @@
      processes often close stderr and by writing to file descriptor 2
      we might send the log message to a file not intended for logging
      (e.g. a pipe or network connection). */
-  if (cookie->fd == -1)
+  if (cookie->want_socket && cookie->fd == -1)
     {
       /* Not yet open or meanwhile closed due to an error. */
-      struct sockaddr_un addr;
-      size_t addrlen;
-
+      cookie->is_socket = 0;
       cookie->fd = socket (PF_LOCAL, SOCK_STREAM, 0);
       if (cookie->fd == -1)
         {
           if (!cookie->quiet && !running_detached)
             fprintf (stderr, "failed to create socket for logging: %s\n",
                      strerror(errno));
-          goto failure;
         }
-      log_socket = cookie->fd;
+      else
+        {
+          struct sockaddr_un addr;
+          size_t addrlen;
+          
+          memset (&addr, 0, sizeof addr);
+          addr.sun_family = PF_LOCAL;
+          strncpy (addr.sun_path, cookie->name, sizeof (addr.sun_path)-1);
+          addr.sun_path[sizeof (addr.sun_path)-1] = 0;
+          addrlen = (offsetof (struct sockaddr_un, sun_path)
+                     + strlen (addr.sun_path) + 1);
       
-      memset (&addr, 0, sizeof addr);
-      addr.sun_family = PF_LOCAL;
-      strncpy (addr.sun_path, cookie->name, sizeof (addr.sun_path)-1);
-      addr.sun_path[sizeof (addr.sun_path)-1] = 0;
-      addrlen = (offsetof (struct sockaddr_un, sun_path)
-                 + strlen (addr.sun_path) + 1);
+          if (connect (cookie->fd, (struct sockaddr *) &addr, addrlen) == -1)
+            {
+              if (!cookie->quiet && !running_detached)
+                fprintf (stderr, "can't connect to `%s': %s\n",
+                         cookie->name, strerror(errno));
+              close (cookie->fd);
+              cookie->fd = -1;
+            }
+        }
       
-      if (connect (cookie->fd, (struct sockaddr *) &addr, addrlen) == -1)
+      if (cookie->fd == -1)
         {
-          log_socket = -1;
-          if (!cookie->quiet && !running_detached)
-            fprintf (stderr, "can't connect to `%s': %s\n",
-                     cookie->name, strerror(errno));
-          close (cookie->fd);
-          cookie->fd = -1;
-          goto failure;
+          if (!running_detached)
+            {
+              if (!cookie->quiet)
+                {
+                  fputs ("switching logging to stderr\n", stderr);
+                  cookie->quiet = 1;
+                }
+              cookie->fd = fileno (stderr);
+            }
+        }
+      else /* Connection has been established. */
+        {
+          cookie->quiet = 0;
+          cookie->is_socket = 1;
         }
-      /* Connection established. */
-      cookie->quiet = 0;
     }
-  
-  if (!writen (cookie->fd, buffer, size))
-    return size; /* Okay. */ 
 
-  log_socket = -1;
-  if (!running_detached)
-    fprintf (stderr, "error writing to `%s': %s\n",
-             cookie->name, strerror(errno));
-  close (cookie->fd);
-  cookie->fd = -1;
+  log_socket = cookie->fd;
+  if (cookie->fd != -1 && !writen (cookie->fd, buffer, size))
+    return size; /* Okay. */ 
 
- failure: 
-  if (!running_detached)
+  if (!running_detached && cookie->fd != -1)
     {
-      if (!cookie->quiet)
-        {
-          fputs ("switching logging to stderr\n", stderr);
-          cookie->quiet = 1;
-        }
-      
-      fwrite (buffer, size, 1, stderr);
+      if (*cookie->name)
+        fprintf (stderr, "error writing to `%s': %s\n",
+                 cookie->name, strerror(errno));
+      else
+        fprintf (stderr, "error writing to file descriptor %d: %s\n",
+                 cookie->fd, strerror(errno));
+    }
+  if (cookie->is_socket && cookie->fd != -1)
+    {
+      close (cookie->fd);
+      cookie->fd = -1;
+      log_socket = -1;
     }
+
   return size;
 }
 
@@ -204,113 +226,151 @@
   if (cookie->fd != -1)
     close (cookie->fd);
   jnlib_free (cookie);
+  log_socket = -1;
   return 0;
 }
-#endif /* HAVE_FOPENCOOKIE || HAVE_FUNOPEN */
+#endif /*USE_FUNWRITER*/
 
 
 
-
-/* Set the file to write log to.  The special names NULL and "-" may
-   be used to select stderr and names formatted like
-   "socket:///home/foo/mylogs" may be used to write the logging to the
-   socket "/home/foo/mylogs".  If the connection to the socket fails
-   or a write error is detected, the function writes to stderr and
-   tries the next time again to connect the socket.
-  */
-void
-log_set_file (const char *name) 
+/* Common function to either set the logging to a file or a file
+   descriptor. */
+static void
+set_file_fd (const char *name, int fd) 
 {
   FILE *fp;
+  int want_socket;
+#ifdef USE_FUNWRITER
+  struct fun_cookie_s *cookie;
+#endif
 
-  force_prefixes = 0;
-  if (name && !strncmp (name, "socket://", 9) && name[9])
+  if (name && !strcmp (name, "-"))
     {
-#if defined (HAVE_FOPENCOOKIE)||  defined (HAVE_FUNOPEN)
-      struct fun_cookie_s *cookie;
-
-      cookie = jnlib_xmalloc (sizeof *cookie + strlen (name+9));
-      cookie->fd = -1;
-      cookie->quiet = 0;
-      strcpy (cookie->name, name+9);
-
-#ifdef HAVE_FOPENCOOKIE
-      {
-        cookie_io_functions_t io = { NULL };
-        io.write = fun_writer;
-        io.close = fun_closer;
+      name = NULL;
+      fd = fileno (stderr);
+    }
 
-        fp = fopencookie (cookie, "w", io);
-      }
-#else /*!HAVE_FOPENCOOKIE*/
-      {
-        fp = funopen (cookie, NULL, fun_writer, NULL, fun_closer);
-      }
-#endif /*!HAVE_FOPENCOOKIE*/
-#else /* Neither fopencookie nor funopen. */
-      {
-        fprintf (stderr, "system does not support logging to a socket - "
-                 "using stderr\n");
-        fp = stderr;
-      }
-#endif /* Neither fopencookie nor funopen. */
-
-      /* We always need to print the prefix and the pid, so that the
-         server reading the socket can do something meaningful. */
-      force_prefixes = 1;
-      /* On success close the old logstream right now, so that we are
-         really sure it has been closed. */
-      if (fp && logstream)
-        {
-          if (logstream != stderr && logstream != stdout)
-            fclose (logstream);
-          logstream = NULL;
-        }
+  if (name)
+    {
+      want_socket = (!strncmp (name, "socket://", 9) && name[9]);
+      if (want_socket)
+        name += 9;
     }
   else
-    fp = (name && strcmp(name,"-"))? fopen (name, "a") : stderr;
-  if (!fp)
     {
-      fprintf (stderr, "failed to open log file `%s': %s\n",
-               name? name:"[stderr]", strerror(errno));
-      return;
+      want_socket = 0;
     }
-  setvbuf (fp, NULL, _IOLBF, 0);
-  
-  if (logstream && logstream != stderr && logstream != stdout)
-    fclose (logstream);
-  logstream = fp;
-  missing_lf = 0;
-}
 
+#ifdef USE_FUNWRITER
+  cookie = jnlib_xmalloc (sizeof *cookie + (name? strlen (name):0));
+  strcpy (cookie->name, name? name:"");
+  cookie->quiet = 0;
+  cookie->is_socket = 0;
+  cookie->want_socket = want_socket;
+  if (!name)
+    cookie->fd = fd;
+  else if (want_socket)
+    cookie->fd = -1;
+  else
+    {
+      do
+        cookie->fd = open (name, O_WRONLY|O_APPEND|O_CREAT,
+                           (S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH));
+      while (cookie->fd == -1 && errno == EINTR);
+    }
+  log_socket = cookie->fd;
 
-void
-log_set_fd (int fd)
-{
-  FILE *fp;
+#ifdef HAVE_FOPENCOOKIE
+  {
+    cookie_io_functions_t io = { NULL };
+    io.write = fun_writer;
+    io.close = fun_closer;
+    
+    fp = fopencookie (cookie, "w", io);
+  }
+#else /*!HAVE_FOPENCOOKIE*/
+  fp = funopen (cookie, NULL, fun_writer, NULL, fun_closer);
+#endif /*!HAVE_FOPENCOOKIE*/
 
-  force_prefixes = 0;
-  if (fd == 1)
+#else /*!USE_FUNWRITER*/
+
+  /* The system does not feature custom streams.  Thus fallback to
+     plain stdio. */
+  if (want_socket)
+    {
+      fprintf (stderr, "system does not support logging to a socket - "
+               "using stderr\n");
+      fp = stderr;
+    }
+  else if (name)
+    fp = fopen (name, "a");
+  else if (fd == 1)
     fp = stdout;
   else if (fd == 2)
     fp = stderr;
   else
     fp = fdopen (fd, "a");
+
+  log_socket = -1; 
+
+#endif /*!USE_FUNWRITER*/
+
+  /* On success close the old logstream right now, so that we are
+     really sure it has been closed. */
+  if (fp && logstream)
+    {
+      if (logstream != stderr && logstream != stdout)
+        fclose (logstream);
+      logstream = NULL;
+    }
+      
   if (!fp)
     {
-      fprintf (stderr, "failed to fdopen log fd %d: %s\n",
-               fd, strerror(errno));
-      return;
+      if (name)
+        fprintf (stderr, "failed to open log file `%s': %s\n",
+                 name, strerror(errno));
+      else
+        fprintf (stderr, "failed to fdopen file descriptor %d: %s\n",
+                 fd, strerror(errno));
+      /* We need to make sure that there is a log stream.  We use stderr. */
+      fp = stderr;
     }
-  setvbuf (fp, NULL, _IOLBF, 0);
+  else
+    setvbuf (fp, NULL, _IOLBF, 0);
   
   if (logstream && logstream != stderr && logstream != stdout)
-    fclose( logstream);
+    fclose (logstream);
   logstream = fp;
+
+  /* We always need to print the prefix and the pid for socket mode,
+     so that the server reading the socket can do something
+     meaningful. */
+  force_prefixes = want_socket;
+
   missing_lf = 0;
 }
 
 
+/* Set the file to write log to.  The special names NULL and "-" may
+   be used to select stderr and names formatted like
+   "socket:///home/foo/mylogs" may be used to write the logging to the
+   socket "/home/foo/mylogs".  If the connection to the socket fails
+   or a write error is detected, the function writes to stderr and
+   tries the next time again to connect the socket.
+  */
+void
+log_set_file (const char *name) 
+{
+  set_file_fd (name? name: "-", -1);
+}
+
+void
+log_set_fd (int fd)
+{
+  set_file_fd (NULL, fd);
+}
+
+
 void
 log_set_prefix (const char *text, unsigned int flags)
 {
@@ -351,9 +411,10 @@
 int
 log_test_fd (int fd)
 {
-  if (fileno (logstream?logstream:stderr) == fd)
+  int tmp = fileno (logstream);
+  if ( tmp != -1 && tmp == fd)
     return 1;
-  if (log_socket == fd)
+  if (log_socket != -1 && log_socket == fd)
     return 1;
   return 0;
 }
@@ -367,15 +428,20 @@
 FILE *
 log_get_stream ()
 {
+  /* FIXME: We should not return stderr here but initialize the log
+     stream properly.  This might break more things than using stderr,
+     though */
   return logstream?logstream:stderr;
 }
 
-
 static void
 do_logv (int level, const char *fmt, va_list arg_ptr)
 {
   if (!logstream)
-    logstream = stderr;
+    {
+      log_set_file (NULL); /* Make sure a log stream has been set.  */
+      assert (logstream);
+    }
 
   if (missing_lf && level != JNLIB_LOG_CONT)
     putc('\n', logstream );
Index: dirmngr/po/POTFILES.in
diff -u dirmngr/po/POTFILES.in:1.1 dirmngr/po/POTFILES.in:1.2
--- dirmngr/po/POTFILES.in:1.1	Fri Apr  2 20:05:20 2004
+++ dirmngr/po/POTFILES.in	Tue Nov 16 19:24:35 2004
@@ -10,4 +10,6 @@
 src/misc.c
 src/ocsp.c
 src/server.c
+src/dirmngr-client.c
+src/no-libgcrypt.c
 
Index: dirmngr/src/ChangeLog
diff -u /dev/null dirmngr/src/ChangeLog:1.21
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/ChangeLog	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,122 @@
+2004-11-16  Werner Koch  <wk at g10code.com>
+
+	* Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SYSCONFDIR and
+	DIRMNGR_LIBEXECDIR.
+
+	* misc.c (dump_isotime, dump_string, dump_cert): New.  Taken from
+	gnupg 1.9.
+	(dump_serial): New.
+
+2004-11-15  Werner Koch  <wk at g10code.com>
+
+	* validate.c: New. Based on gnupg's certchain.c
+
+	* ldap.c (get_cert_ldap): Removed.
+	(read_buffer): New.
+	(start_cert_fetch_ldap, fetch_next_cert_ldap)
+	(end_cert_fetch_ldap): Rewritten to make use of the ldap wrapper.
+
+2004-11-12  Werner Koch  <wk at g10code.com>
+
+	* http.c (insert_escapes): Print the percent sign too.
+
+	* dirmngr-client.c (inq_cert): Ignore "SENDCERT" and
+	"SENDISSUERCERT".
+
+	* server.c (do_get_cert_local): Limit the length of a retruned
+	certificate.  Return NULL without an error if an empry value has
+	been received.
+
+	* crlfetch.c (ca_cert_fetch): Use the ksba_reader_object.
+	(setup_funopen, fun_reader, fun_closer): Removed.
+
+	* crlcache.c (get_issuer_cert): Adjust accordingly.
+
+	* ldap.c (attr_fetch_ldap_internal, attr_fetch_fun_closer) 
+	(attr_fetch_fun_reader, url_fetch_ldap_internal)
+	(get_attr_from_result_ldap): Removed.
+	(destroy_wrapper, print_log_line, ldap_wrapper_thread) 
+	(ldap_wrapper_release_context, reader_callback, ldap_wrapper) 
+	(run_ldap_wrapper): New.
+	(url_fetch_ldap): Make use of the new ldap wrapper and return a
+	ksba reader object instead of a stdio stream.
+	(attr_fetch_ldap): Ditto.
+	(make_url, escape4url): New.
+
+2004-11-11  Werner Koch  <wk at g10code.com>
+
+	* dirmngr.c (launch_ripper_thread): New.
+	(main): Start it wheere appropriate.  Always ignore SIGPIPE.
+	(start_connection_thread): Maintain a connection count.
+	(handle_signal, handle_connections): Use it here instead of the
+	thread count.
+
+	* crlcache.c (crl_cache_insert): Changed to use ksba reader
+	object.  Changed all callers to pass this argument.
+
+2004-11-08  Werner Koch  <wk at g10code.com>
+
+	* dirmngr_ldap.c: New.
+
+	* crlcache.c (crl_cache_init): Don't return a cache object but
+	keep it module local.  We only need one.
+	(crl_cache_deinit): Don't take cache object but work on existing
+	one.
+	(get_current_cache): New.
+	(crl_cache_insert, crl_cache_list, crl_cache_load): Use the global
+	cache object and removed the cache arg.  Changed all callers.
+	
+	* dirmngr-client.c: New option --ping.
+
+	* dirmngr.c (main): New option --daemon. Initialize PTH.
+	(handle_connections, start_connection_thread): New.
+	(handle_signal): New.
+	(parse_rereadable_options): New. Changed main to make use of it.
+	(set_debug): Don't bail out on invalid debug levels.
+	(main): Init the crl_chache for server and daemon mode.
+
+	* server.c (start_command_handler): New arg FD.  Changed callers.
+
+2004-11-06  Werner Koch  <wk at g10code.com>
+
+	* server.c (map_assuan_err): Factored out to ..
+	* maperror.c: .. new file.
+	* util.h: Add prototype
+
+2004-11-05  Werner Koch  <wk at g10code.com>
+
+	* no-libgcrypt.c: New, used as helper for dirmngr-client which
+	does not need libgcrypt proper but jnlib references the memory
+	functions.  Taken from gnupg 1.9.12.
+
+	* dirmngr.h: Factored i18n and xmalloc code out to ..
+	* i18n.h, util.h: .. New.
+
+	* dirmngr-client.c: New.  Some code taken from gnupg 1.9.12.
+	* Makefile.am (bin_PROGRAMS) Add dirmngr-client.
+
+2004-11-04  Werner Koch  <wk at g10code.com>
+
+	* src/server.c (get_fingerprint_from_line, cmd_checkcrl) 
+	(cmd_checkocsp): New.
+	(register_commands): Register new commands.
+	(inquire_cert_and_load_crl): Factored most code out to ..
+	(reload_crl): .. new function.
+	* src/certcache.h, src/certcache.c: New.
+	* src/Makefile.am (dirmngr_SOURCES): Add new files.
+
+2004-11-04  Werner Koch  <wk at g10code.com>
+
+	Please note that earlier entries are found in the top level
+	ChangeLog.
+	
+	
+ Copyright 2004 g10 Code GmbH
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Index: dirmngr/src/Makefile.am
diff -u dirmngr/src/Makefile.am:1.17 dirmngr/src/Makefile.am:1.18
--- dirmngr/src/Makefile.am:1.17	Fri Apr  2 20:05:19 2004
+++ dirmngr/src/Makefile.am	Tue Nov 16 19:24:35 2004
@@ -1,4 +1,6 @@
-# Copyright (C) 2002 Klarälvdalens Datakonsult AB
+# Makefile.am - dirmngr
+# Copyright (C) 2002 Klarälvdalens Datakonsult AB
+# Copyright (C) 2004 g10 Code GmbH
 #
 # This file is part of DirMngr.
 #
@@ -22,21 +24,38 @@
 
 EXTRA_DIST = Manifest
 
-bin_PROGRAMS = dirmngr
+bin_PROGRAMS = dirmngr dirmngr-client
+libexec_PROGRAMS = dirmngr_ldap
 
 AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" 
-AM_CFLAGS = -I$(top_srcdir)/jnlib @LIBGCRYPT_CFLAGS@ @LIBASSUAN_CFLAGS@ \
-              @KSBA_CFLAGS@ @LDAP_INCS@
+AM_CPPFLAGS += -DDIRMNGR_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" 
+AM_CPPFLAGS += -DDIRMNGR_LIBEXECDIR="\"$(libexecdir)\""
 
-AM_LDFLAGS = @LDFLAGS@ 
+AM_CFLAGS = -I$(top_srcdir)/jnlib @LIBGCRYPT_CFLAGS@ @LIBASSUAN_CFLAGS@ \
+              @KSBA_CFLAGS@ @GPG_ERROR_CFLAGS@  $(PTH_CFLAGS)
 
 noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h error.h misc.h
 
 dirmngr_SOURCES = \
 	dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \
-	cdb.h cdblib.c ldap.c http.c http.h misc.c ocsp.c ocsp.h
+	certcache.c certcache.h i18n.h util.h maperror.c \
+	cdb.h cdblib.c ldap.c http.c http.h misc.c ocsp.c ocsp.h \
+	validate.c validate.h
 
-dirmngr_LDFLAGS = @LDAP_RPATH@
 
 dirmngr_LDADD = ../jnlib/libjnlib.a @LIBOBJS@ @LIBASSUAN_LIBS@  \
-		@LIBGCRYPT_LIBS@  @KSBA_LIBS@ @LDAP_LIBS@ 
+		@LIBGCRYPT_LIBS@  @KSBA_LIBS@ @LDAP_LIBS@ $(PTH_LIBS)
+
+dirmngr_ldap_SOURCES = dirmngr_ldap.c i18n.h util.h \
+                       no-libgcrypt.c no-libgcrypt.h 
+dirmngr_ldap_CFLAGS = $(LDAP_INCS)
+dirmngr_ldap_LDFLAGS = $(LDAP_RPATH)
+dirmngr_ldap_LDADD = ../jnlib/libjnlib.a $(LIBOBJS) $(GPG_ERROR_LIBS) \
+		    $(LDAP_LIBS)
+
+
+dirmngr_client_SOURCES = dirmngr-client.c i18n.h util.h maperror.c \
+                         no-libgcrypt.c no-libgcrypt.h 
+dirmngr_client_LDADD = ../jnlib/libjnlib.a $(LIBOBJS) $(LIBASSUAN_LIBS) \
+	               $(GPG_ERROR_LIBS)
+
Index: dirmngr/src/certcache.c
diff -u /dev/null dirmngr/src/certcache.c:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/certcache.c	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,313 @@
+/* certcache.c - Certificate caching
+ *      Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <ksba.h>
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "certcache.h"
+
+
+/* A certificate cache item.  This consists of a the ksba cert object
+   and some meta data for easier lookup.  We use a hash table to keep
+   track of all items and use sue the randomly distributed first byte
+   of the fingerprint directly as the hash which makes it pretty easy. */
+struct cert_item_s
+{
+  struct cert_item_s *next; /* Next item with the same hash value. */
+  ksba_cert_t cert;         /* The KSBA cert object or NULL is this is
+                               not a valid item.  */
+  unsigned char fpr[20];    /* The fingerprint of this object. */
+  char *issuer_dn;          /* The malloced issuer DN.  */
+  ksba_sexp_t sn;           /* The malloced serial number  */
+  struct 
+  {
+    int trusted;            /* This is a trusted root certificate.  */
+    
+
+
+
+  } flags;
+};
+typedef struct cert_item_s *cert_item_t;
+
+/* The actual cert cache consisting of 256 slots for items idnex by
+   the first byte of the fingerprint.  */
+static cert_item_t cert_cache[256];
+
+
+
+/* Return false if both serial numbers match.  Can't be used for
+   sorting. */
+static int
+compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 )
+{
+  unsigned char *a = serial1;
+  unsigned char *b = serial2;
+  char *endp;
+  unsigned long n_a, n_b;
+
+  if (!a || !b)
+    return (!a && !b);
+
+  n_a = strtoul (a+1, (char**)&endp, 10);
+  a = endp;
+  n_b = strtoul (b+1, (char**)&endp, 10);
+  b = endp;
+  if (*a != ':' || *b != ':')
+    {
+      log_debug ("invalid S-exp in compare_serialno\n");
+      return 0;
+    }
+  if (n_a != n_b)    
+    return 0;
+  
+  for (; n_a && *a == *b; n_a--, a++, b++)
+    ;
+  return !!n_a;
+}
+
+
+/* Computer the fingerprint of the certificate CERT and put it into
+   the 20 bytes large buffer DIGEST.  Return address of this buffer.  */
+static unsigned char *
+compute_fpr (ksba_cert_t cert, unsigned char *digest)
+{
+  gpg_error_t err;
+  gcry_md_hd_t md;
+
+  err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+  if (err)
+    log_fatal ("gcry_md_open failed: %s\n", gpg_strerror (err));
+
+  err = ksba_cert_hash (cert, 0, HASH_FNC, md);
+  if (err)
+    {
+      log_error ("oops: ksba_cert_hash failed: %s\n", gpg_strerror (err));
+      memset (digest, 0xff, 20); /* Use a dummy value. */
+    }
+  else
+    {
+      gcry_md_final (md);
+      memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20);
+    }
+  gcry_md_close (md);
+  return digest;
+}
+
+
+
+/* Return a certificate object for the given fingerprint.  FPR is
+   expected to be a 20 byte binary SHA-1 fingerprint.  If no matching
+   certificate is available in the cache NULL is returned.  The caller
+   must release a returned certificate.  Note that although we are
+   using reference counting the caller should not just compare the
+   pointers to check for identical certificates. */
+ksba_cert_t
+get_cert_byfpr (const unsigned char *fpr)
+{
+  cert_item_t ci;
+
+  for (ci=cert_cache[*fpr]; ci; ci = ci->next)
+    if (ci->cert && !memcmp (ci->fpr, fpr, 20))
+      {
+        ksba_cert_ref (ci->cert);
+        return ci->cert;
+      }
+
+  return NULL;
+}
+
+
+/* Return the certificate matching ISSUER_DN and SERIALNO.  */
+ksba_cert_t
+get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno)
+{
+  /* Simple and inefficient implementation.   fixme! */
+  cert_item_t ci;
+  int i;
+
+  for (i=0; i < 256; i++)
+    {
+      for (ci=cert_cache[i]; ci; ci = ci->next)
+        if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn)
+            && !compare_serialno (ci->sn, serialno))
+          {
+            ksba_cert_ref (ci->cert);
+            return ci->cert;
+          }
+    }
+
+  return NULL;
+}
+
+
+/* Return the certificate matching ISSUER_DN.  SEQ should initially be
+   set to 0 and bumped up to get the next issuer with that DN. */
+ksba_cert_t
+get_cert_byissuer (const char *issuer_dn, unsigned int seq)
+{
+  /* Simple and very inefficient implementation and API.  fixme! */
+  cert_item_t ci;
+  int i;
+
+  for (i=0; i < 256; i++)
+    {
+      for (ci=cert_cache[i]; ci; ci = ci->next)
+        if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn))
+          if (!seq--)
+            {
+              ksba_cert_ref (ci->cert);
+              return ci->cert;
+            }
+    }
+
+  return NULL;
+}
+
+
+
+
+/* Return the certificate matching ISSUER_DN and SERIALNO; if it is
+   not already in the cache, try to find it from other resources.  */
+ksba_cert_t
+find_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno)
+{
+  ksba_cert_t cert;
+
+  cert = get_cert_bysn (issuer_dn, serialno);
+  if (cert)
+    return cert;
+
+  return NULL;
+}
+
+
+
+/* Return 0 if the certificate is a trusted certificate. Returns
+   GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in
+   case of systems errors. */
+gpg_error_t 
+is_trusted_cert (ksba_cert_t cert)
+{
+  unsigned char fpr[20];
+  cert_item_t ci;
+
+  compute_fpr (cert, fpr);
+
+  for (ci=cert_cache[*fpr]; ci; ci = ci->next)
+    if (ci->cert && !memcmp (ci->fpr, fpr, 20))
+      {
+        if (ci->flags.trusted)
+          return 0; /* Yes it is trusted. */
+        break;
+      }
+
+  return gpg_error (GPG_ERR_NOT_TRUSTED);
+}
+
+
+/* Given the certificate CERT locate the issuer for this certificate
+   and return it at R_CERT.  Returns 0 on success or
+   GPG_ERR_NOT_FOUND.  */
+gpg_error_t
+find_issuing_cert (ksba_cert_t cert, ksba_cert_t *r_cert)
+{
+  gpg_error_t err;
+  char *issuer_dn;
+  ksba_cert_t issuer_cert = NULL;
+  ksba_name_t authid;
+  ksba_sexp_t authidno;
+
+  *r_cert = NULL;
+
+  issuer_dn = ksba_cert_get_issuer (cert, 0);
+  if (!issuer_dn)
+    {
+      log_error (_("no issuer found in certificate"));
+      err = gpg_error (GPG_ERR_BAD_CERT);
+      goto leave;
+    }
+
+  /* First we need to check whether we can return that certificate
+     using the authorithyKeyIdentifier.  */
+  err = ksba_cert_get_auth_key_id (cert, NULL, &authid, &authidno);
+  if (err)
+    {
+      log_info (_("error getting authorityKeyIdentifier: %s\n"),
+                gpg_strerror (err));
+    }
+  else
+    {
+      const char *s = ksba_name_enum (authid, 0);
+      if (s && *authidno)
+        {
+          issuer_cert = find_cert_bysn (s, authidno);
+        }
+      /* Print a note so that the user does not feel too helpless when
+         an issuer certificate was found and gpgsm prints BAD
+         signature because it is not the correct one. */
+      if (!issuer_cert)
+        {
+          log_info ("issuer certificate (#");
+          dump_serial (authidno);
+          log_printf ("/");
+          dump_string (s);
+          log_printf (") not found\n");
+        }
+      ksba_name_release (authid);
+      xfree (authidno);
+    }
+
+  /* If this did not work, try just with the issuer's name.  */
+  if (err || !issuer_cert)
+    {
+      /* FIXME: we need to save a sequence number here for later
+         tries.  */
+      issuer_cert = get_cert_byissuer (issuer_dn, 0);
+    }
+
+ leave:  
+  if (!err && !issuer_cert)
+    err = gpg_error (GPG_ERR_NOT_FOUND);
+
+  xfree (issuer_dn);
+
+  if (err)
+    ksba_cert_release (issuer_cert);
+  else
+    *r_cert = issuer_cert;
+
+  return err;
+}
+
+
+
+
+
+
+
Index: dirmngr/src/certcache.h
diff -u /dev/null dirmngr/src/certcache.h:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/certcache.h	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,45 @@
+/* certcache.h - Certificate caching
+ *      Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#ifndef CERTCACHE_H
+#define CERTCACHE_H
+
+
+/* Return 0 if the certificate is a trusted certificate. Returns
+   GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in
+   case of systems errors. */
+gpg_error_t is_trusted_cert (ksba_cert_t cert);
+
+
+/* Return a certificate object for the given fingerprint.  FPR is
+   expected to be a 20 byte binary SHA-1 fingerprint.  If no matching
+   certificate is available in the cache NULL is returned.  The caller
+   must release a returned certificate.  */
+ksba_cert_t get_cert_byfpr (const unsigned char *fpr);
+
+
+/* Given the certificate CERT locate the issuer for this certificate
+   and return it at R_CERT.  Returns 0 on success or
+   GPG_ERR_NOT_FOUND.  */
+gpg_error_t find_issuing_cert (ksba_cert_t cert, ksba_cert_t *r_cert);
+
+
+
+#endif /*CERTCACHE_H*/
Index: dirmngr/src/crlcache.c
diff -u dirmngr/src/crlcache.c:1.45 dirmngr/src/crlcache.c:1.46
--- dirmngr/src/crlcache.c:1.45	Wed Aug 18 18:41:42 2004
+++ dirmngr/src/crlcache.c	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,5 @@
 /* crlcache.c - LDAP access
- *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
  *      Copyright (C) 2003, 2004 g10 Code GmbH
  *
  * This file is part of DirMngr.
@@ -110,7 +110,7 @@
 /* The number of DB files we may have open at one time.  We need to
    limit this because there is no guarantee that the number of issuers
    has a upper limit.  We are currently using mmap, so it is a good
-   idea anyway to limit the number of opend cache files. */
+   idea anyway to limit the number of opened cache files. */
 #define MAX_OPEN_DB_FILES 5
 
 
@@ -154,12 +154,34 @@
   unsigned int cdb_open_count; /* Currently open cache files. */
 };
 
+typedef struct crl_cache_s *crl_cache_t;
 
+
+/* Prototypes.  */
 static crl_cache_entry_t find_entry (crl_cache_entry_t first,
                                      const char *issuer_hash);
 
 
 
+/* The currently loaded cache object.  This isi usually initialized
+   right at startup.  */
+static crl_cache_t current_cache;
+
+
+
+
+
+/* Return the current cache object or bail out if it is has not yet
+   been initialized.  */
+static crl_cache_t
+get_current_cache (void)
+{
+  if (!current_cache)
+    log_fatal ("CRL cache has not yet been initialized\n");
+  return current_cache;
+}
+
+
 /* 
    Create ae directory if it does not yet exists.  Returns on
    success, or -1 on error.
@@ -449,7 +471,7 @@
 
 
 /* Open the dir file and read in all available information.  Store
-   that in a newly allocated cahce object and return that if
+   that in a newly allocated cache object and return that if
    everything worked out fine.  Create the cache directory and the dir
    if it does not yet exist.  Remove all files in that directory if
    the version does not match. */
@@ -1057,31 +1079,38 @@
 
 
 
-/* Create a new CRL cache. Returns the handle to this cache. Will
+/* Create a new CRL cache. This fucntion is usually called only once.
    never fail. */
-crl_cache_t
+void
 crl_cache_init(void)
 {
   crl_cache_t cache;
   gpg_error_t err;
 
+  if (current_cache)
+    {
+      log_error ("crl cache has already been initialized - not doing twice\n");
+      return;
+    }
+
   err = open_dir (&cache);
-  if (err)
+  if (err) 
     log_fatal (_("failed to create a new cache object: %s\n"),
                gpg_strerror (err));
-  return cache;
+  current_cache = cache;
 }
 
 
 /* Remove the cache information and all its resources.  Note that we
    still keep the cache on disk. */
 void 
-crl_cache_deinit (crl_cache_t cache)
+crl_cache_deinit (void)
 {
-  if (!cache)
-    return;
-
-  release_cache (cache);
+  if (current_cache)
+    {
+      release_cache (current_cache);
+      current_cache = NULL;
+    }
 }
 
 
@@ -1089,25 +1118,25 @@
 int 
 crl_cache_flush (void)
 {
-  int problem = 0;
+  int rc;
 
-  if (cleanup_cache_dir (0))
-    problem = -1;
-  return problem;
+  rc = cleanup_cache_dir (0)? -1 : 0;
+
+  return rc;
 }
 
 
 /* Check whether the certificate identified by ISSUER_HASH and
    SERIALNO is valid; i.e. not listed in our cache.  With
-   FORCE_REFRESH set to true, a new CRL will be retrieved eben if the
-   cached has not yet expired.  We use a 30 minutes threshold here so
+   FORCE_REFRESH set to true, a new CRL will be retrieved even if the
+   cache has not yet expired.  We use a 30 minutes threshold here so
    that invoking this function several times won't load the CRL over
    and over.  */
 crl_cache_result_t 
-crl_cache_isvalid (crl_cache_t cache,
-                   const char *issuer_hash, const char *serialno,
+crl_cache_isvalid (const char *issuer_hash, const char *serialno,
                    int force_refresh)
 {
+  crl_cache_t cache = get_current_cache ();
   crl_cache_result_t retval;
   struct cdb *cdb;
   int rc;
@@ -1232,7 +1261,7 @@
 {
   gpg_error_t err;
   ksba_cert_t issuer_cert = NULL;
-  FILE *fp;
+  ksba_reader_t reader;
 
   /* Ask back to the service requester to return the certificate.
      This is because we can assume that he already used the
@@ -1246,7 +1275,7 @@
     log_debug ("certificate for CRL issuer not returned by caller"
                " - doing lookup\n");
 
-  err = ca_cert_fetch (issuer, &fp);
+  err = ca_cert_fetch (issuer, &reader);
   if (err)
     {
       log_error (_("error fetching certificate for issuer: %s\n"),
@@ -1257,23 +1286,16 @@
   err = ksba_cert_new (&issuer_cert);
   if (!err)
     {
-      ksba_reader_t r;
-
-      err = ksba_reader_new (&r);
-      if (!err)
-        err = ksba_reader_set_file (r, fp);
-      if (!err)
-        err = ksba_cert_read_der (issuer_cert, r);
+      err = ksba_cert_read_der (issuer_cert, reader);
       if (err)
         {
           ksba_cert_release (issuer_cert);
           issuer_cert = NULL;
         }
-      ksba_reader_release (r);
     }
   if (err)
     log_error (_("invalid issuer certificate: %s\n"), gpg_strerror (err) );
-  fclose (fp);
+  ksba_reader_release (reader);
 
   return issuer_cert;
 }
@@ -1560,7 +1582,7 @@
 
 
 /* Return the authorityKeyIdentifier or NULL if it is not available.
-   The issuer name may consists of several parts - the are delimted by
+   The issuer name may consists of several parts - they are delimted by
    0x01. */
 static char *
 get_auth_key_id (ksba_crl_t crl, char **serialno)
@@ -1610,11 +1632,11 @@
    CACHE.  The CRL itself will be read from the stream FP and is
    expected in binary format. */
 gpg_error_t
-crl_cache_insert (ctrl_t ctrl, crl_cache_t cache, const char *url, FILE *fp)
+crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader)
 {
+  crl_cache_t cache = get_current_cache ();
   gpg_error_t err, err2;
   ksba_crl_t crl;
-  ksba_reader_t reader;
   char *fname = NULL;
   char *newfname = NULL;
   struct cdb_make cdb;
@@ -1630,6 +1652,11 @@
   int idx;
   const char *oid;
   int critical;
+
+
+  /* FIXME: We should acquire a mutex for the URL, so that we don't
+     simultaneously enter the same CRL twice.  However this needs t be
+     interweaved with the checking function.*/
  
   err2 = 0;
 
@@ -1640,14 +1667,6 @@
       goto leave;
     }
   
-  err = ksba_reader_new (&reader);
-  if (!err)
-    err = ksba_reader_set_file (reader, fp);
-  if (err)
-    {
-      log_error (_("ksba_reader_set_file failed: %s\n"), gpg_strerror (err));
-      goto leave;
-    }
   err = ksba_crl_set_reader (crl, reader);
   if ( err )
     {
@@ -1836,7 +1855,6 @@
       xfree (fname);
     }
   xfree (newfname);
-  ksba_reader_release (reader);
   ksba_crl_release (crl);
   xfree (issuer);
   xfree (issuer_hash);
@@ -1967,8 +1985,9 @@
 /* Print the contents of the CRL CACHE in a human readable format to
    stream FP. */
 gpg_error_t 
-crl_cache_list (crl_cache_t cache, FILE *fp) 
+crl_cache_list (FILE *fp) 
 {
+  crl_cache_t cache = get_current_cache ();
   crl_cache_entry_t entry;
   gpg_error_t err = 0;
 
@@ -1984,7 +2003,7 @@
 /* Load the CRL contain the the file named FILENAME into our cache
    CACHE */
 gpg_error_t
-crl_cache_load (ctrl_t ctrl, crl_cache_t cache, const char *filename)
+crl_cache_load (ctrl_t ctrl, const char *filename)
 {
   gpg_error_t err;
   FILE* fp;
@@ -1997,7 +2016,8 @@
       return err;
     }
 
-  err = crl_cache_insert (ctrl, cache, filename, fp);
+  err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+       /*crl_cache_insert (ctrl, filename, fo);*/
   fclose (fp);
   return err;
 }
Index: dirmngr/src/crlcache.h
diff -u dirmngr/src/crlcache.h:1.21 dirmngr/src/crlcache.h:1.22
--- dirmngr/src/crlcache.h:1.21	Thu Apr 29 10:26:32 2004
+++ dirmngr/src/crlcache.h	Tue Nov 16 19:24:35 2004
@@ -51,26 +51,21 @@
 struct crl_cache_entry_s;
 typedef struct crl_cache_entry_s *crl_cache_entry_t;
 
-struct crl_cache_s;
-typedef struct crl_cache_s *crl_cache_t;
 
-
-crl_cache_t crl_cache_init (void);
-void crl_cache_deinit (crl_cache_t c);
+void crl_cache_init (void);
+void crl_cache_deinit (void);
 int crl_cache_flush(void);
 
-crl_cache_result_t crl_cache_isvalid (crl_cache_t cache,
-                                      const char *issuer_hash,
+crl_cache_result_t crl_cache_isvalid (const char *issuer_hash,
                                       const char *cert_id,
                                       int force_refresh);
 
-gpg_error_t crl_cache_insert (ctrl_t ctrl, crl_cache_t cache,
-                              const char *url, FILE *fp);
+gpg_error_t crl_cache_insert (ctrl_t ctrl, const char *url,
+                              ksba_reader_t reader);
 
-gpg_error_t crl_cache_list (crl_cache_t cache, FILE* fp);
+gpg_error_t crl_cache_list (FILE* fp);
 
-gpg_error_t crl_cache_load (ctrl_t ctrl, crl_cache_t cache,
-                            const char *filename);
+gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename);
 
 
 #endif /* CRLCACHE_H */
Index: dirmngr/src/crlfetch.c
diff -u dirmngr/src/crlfetch.c:1.18 dirmngr/src/crlfetch.c:1.19
--- dirmngr/src/crlfetch.c:1.18	Mon Jun 21 10:13:40 2004
+++ dirmngr/src/crlfetch.c	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,5 @@
 /* crlfetch.c - LDAP access
- *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
  *      Copyright (C) 2003, 2004 g10 Code GmbH
  *
  * This file is part of DirMngr.
@@ -29,87 +29,18 @@
 #include "misc.h"
 #include "http.h"
 
-struct fun_cookie_s {
-  unsigned char *value; /* Malloced buffer with the data to deliver. */
-  size_t valuelen;      /* Size of this buffer. */
-  size_t nread;         /* Bytes already read from value. */
-};
 
-
-static int 
-fun_reader (void *cookie, char *buffer, int size)
-{
-  struct fun_cookie_s *h = cookie;
-
-  if (h->nread >= h->valuelen)
-    return 0; /* EOF */
-  if (h->nread + size > h->valuelen)
-    size = h->valuelen - h->nread;
-  memcpy (buffer, h->value + h->nread, size);
-  h->nread += size;
-  return size;
-}
-
-static int
-fun_closer (void *cookie)
-{
-  struct fun_cookie_s *h = cookie;
-
-  xfree (h->value);
-  xfree (h);
-  return 0;
-}
-
-/* Helper to prepare a stream from VALUE and VALUELEN and return that
-   stream in R_FP.  In the error case VALUE will be freed. */
-static gpg_error_t
-setup_funopen (unsigned char *value, size_t valuelen, FILE **r_fp)
-{
-  gpg_error_t err = 0;
-  struct fun_cookie_s *h;
-  FILE *fp;
-
-  h = xtrymalloc (sizeof *h);
-  if (!h)
-    {
-      err = gpg_error_from_errno (errno);
-      xfree (value);
-    }
-  else 
-    {
-      h->value = value;
-      value = NULL;
-      h->valuelen = valuelen;
-      h->nread = 0;
-      fp = funopen (h, fun_reader, NULL, NULL, fun_closer);
-      if (!fp)
-        {
-          err = gpg_error_from_errno (errno);
-          xfree (h->value);
-          xfree (h);
-        }
-      else
-        *r_fp = fp;
-    }
-  return err;
-}
-
-/* Fetch CRL from URL and return the entire CRL as a newly opened
-   stream returned in R_FP.
-
-   FIXME: we should look for a way to return the stream direct from
-   the ldap core and not use a temporary buffer.
- */
+/* Fetch CRL from URL and return the entire CRL using new ksba reader
+   object in READER.  Note that this reader object should be closed
+   only using ldap_close_reader. */
 gpg_error_t
-crl_fetch (const char *url, FILE **r_fp)
+crl_fetch (const char *url, ksba_reader_t *reader)
 {
   gpg_error_t err;
-  unsigned char *value = NULL;
-  size_t valuelen;
   parsed_uri_t uri;
   char *free_this = NULL;
 
-  *r_fp = NULL;
+  *reader = NULL;
 
   err = http_parse_uri (&uri, url);
   http_release_parsed_uri (uri);
@@ -148,37 +79,59 @@
         }
       else
         {
-          *r_fp = hd.fp_read;
-          http_close (&hd, 1);
+          err = ksba_reader_new (reader);
+          if (!err)
+            err = ksba_reader_set_file (*reader, hd.fp_read);
+          if (err)
+            {
+              log_error (_("error initializing reader object: %s\n"),
+                         gpg_strerror (err));
+              ksba_reader_release (*reader);
+              *reader = NULL;
+              http_close (&hd, 0);
+            }
+          else
+            {
+              /* FIXME: Register the fp somewhere so that we are able
+                 to release the ksba_reader and close the underlying
+                 stream. See also below crl_close_reader(). */
+              http_close (&hd, 1);
+            }
         }
     }
   else /* Let the LDAP code try other schemes. */
     {
       const char *s;
-      const char *attr;
 
-      /* FIXME: This is a bad hack.  I don't know why we at all
-         pass the attribute separately to the LDAP serach code.
-         The attributes are included in the URL and OpenLDAP
-         provides a parse functionality for it.  It has never been
-         used though.  We need to find out why this strange way
-         was used!!!!. */
-      s = strchr (url, '?');
-      if (s && !strcmp (s+1, "certificateRevocationList"))
-        attr = "certificateRevocationList;binary";
-      else if (s && !strcmp (s+1, "authorityRevocationList"))
-        attr = "authorityRevocationList;binary";
-      else 
-        {
-          log_info ("WARNING: unsupported LDAP attribute - using CRL\n");
-          attr = "certificateRevocationList;binary";
-        }
+      err = url_fetch_ldap (url, NULL, 0, reader);
+/*       if (err) */
+/*         { */
+/*           /\* If the function failed we try again using our default */
+/*              host.  *\/ */
+/*           ldap_server_t server; */
+/*           char *savehost; */
+/*           int saveport; */
+      
+/*           if (DBG_LOOKUP) */
+/*             log_debug ("no hostname in URL or query failed; " */
+/*                        "trying all default hostnames\n"); */
+/*           savehost = ludp->lud_host; */
+/*           saveport = ludp->lud_port; */
+/*           for (server = opt.ldapservers; server; server = server->next) */
+/*             { */
+/*               ludp->lud_host = server->host; */
+/*               ludp->lud_port = server->port; */
+/*               err = url_fetch_ldap_internal (ludp, attr, value, valuelen); */
+/*               if (!err) */
+/*                 break; */
+/*             } */
+/*           ludp->lud_host = savehost; */
+/*           ludp->lud_port = saveport; */
+/*                 if (opt.add_new_ldapservers) */
+/* 	add_server_to_servers (ludp->lud_host, ludp->lud_port); */
+
+/*         } */
 
-      err = url_fetch_ldap (url, attr, &value, &valuelen);
-      if (!err)
-        err = setup_funopen (value, valuelen, r_fp);
-      else if (err && value)
-        xfree (value);
     }
   if (free_this)
     xfree (free_this);
@@ -189,28 +142,18 @@
 /* Fetch CRL for ISSUER using a default server. Return the entire CRL
    as a newly opened stream returned in R_FP. */
 gpg_error_t
-crl_fetch_default (const char *issuer, FILE **r_fp)
+crl_fetch_default (const char *issuer, ksba_reader_t *reader)
 {
-  gpg_error_t err;
-/*   unsigned char *value = NULL; */
-/*   size_t valuelen; */
-  
-/*   *r_fp = NULL; */
-  err = attr_fetch_ldap (issuer, "certificateRevocationList;binary", 
-                         r_fp);
-/*   if (!err) */
-/*     err = setup_funopen (value, valuelen, r_fp); */
-/*   else if (err && value) */
-/*     xfree (value); */
-  return err;
+  return  attr_fetch_ldap (issuer, "certificateRevocationList;binary",
+                           reader);
 }
 
 
 /* Fetch a CA certificate for DN using the default server. */
 int
-ca_cert_fetch (const char *dn, FILE **r_fp)
+ca_cert_fetch (const char *dn, ksba_reader_t *reader)
 {
-  return attr_fetch_ldap (dn, "cACertificate;binary", r_fp);
+  return attr_fetch_ldap (dn, "cACertificate;binary", reader);
 }
 
 
@@ -233,3 +176,24 @@
 {
   return end_cert_fetch_ldap (context);
 }
+
+
+/* This function is to be used to close the reader object.  In
+   addition to running ksba_reader_release it also releases the LDAP
+   or HTTP contexts associated with that reader.  */
+void
+crl_close_reader (ksba_reader_t reader)
+{
+  if (!reader)
+    return;
+
+  /* Check whether this is a HTTP one. */
+
+  /* FIXME: TODO */ 
+
+  /* If not a ldap wrapper context might be assocaited.  */
+  ldap_wrapper_release_context (reader);
+
+  /* Now get rid of the reader object. */
+  ksba_reader_release (reader);
+}
Index: dirmngr/src/crlfetch.h
diff -u dirmngr/src/crlfetch.h:1.10 dirmngr/src/crlfetch.h:1.11
--- dirmngr/src/crlfetch.h:1.10	Fri Apr  2 20:05:19 2004
+++ dirmngr/src/crlfetch.h	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,5 @@
 /* crlfetch.h - LDAP access
- *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
  *
  * This file is part of DirMngr.
  *
@@ -31,14 +31,14 @@
 
 
 /* Fetch CRL from URL. */
-gpg_error_t crl_fetch (const char* url, FILE **r_fp);
+gpg_error_t crl_fetch (const char* url, ksba_reader_t *reader);
 
 /* Fetch CRL for ISSUER using default server. */
-gpg_error_t crl_fetch_default (const char* issuer, FILE **r_fp);
+gpg_error_t crl_fetch_default (const char* issuer, ksba_reader_t *reader);
 
 
 /* Fetch cert for DN. */
-int ca_cert_fetch( const char *dn, FILE **r_fp);
+int ca_cert_fetch (const char *dn, ksba_reader_t *reader);
 
 
 /* Query the server for certs matching patterns. */
@@ -49,11 +49,20 @@
                             unsigned char **value, size_t *valuelen);
 void end_cert_fetch (cert_fetch_context_t context);
 
+/* Close a reader object. */
+void crl_close_reader (ksba_reader_t reader);
+
+
 
 /*-- ldap.c --*/
-gpg_error_t attr_fetch_ldap( const char* dn, const char *attr, FILE **r_fp);
-gpg_error_t url_fetch_ldap(const char* url, const char* attr,
-                           unsigned char **value, size_t *valuelen );
+void *ldap_wrapper_thread (void*);
+void ldap_wrapper_release_context (ksba_reader_t reader);
+
+gpg_error_t url_fetch_ldap (const char *url, const char *host, int port,
+                            ksba_reader_t *reader);
+gpg_error_t attr_fetch_ldap (const char *dn, const char *attr,
+                            ksba_reader_t *reader);
+
 
 gpg_error_t start_cert_fetch_ldap(cert_fetch_context_t *context,
                                   strlist_t patterns,
@@ -63,4 +72,8 @@
 void end_cert_fetch_ldap (cert_fetch_context_t context);
 
 
+
+
+
+
 #endif /* CRLFETCH_H */
Index: dirmngr/src/dirmngr-client.c
diff -u /dev/null dirmngr/src/dirmngr-client.c:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/dirmngr-client.c	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,474 @@
+/* dirmngr-client.c  -  A client for the dirmngr daemon
+ *	Copyright (C) 2004 g10 Code GmbH
+ *	Copyright (C) 2002, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <gpg-error.h>
+#include <assuan.h> 
+
+#define JNLIB_NEED_LOG_LOGV
+#include "../jnlib/logging.h"
+#include "../jnlib/argparse.h"
+#include "../jnlib/stringhelp.h"
+#include "../jnlib/mischelp.h"
+#include "../jnlib/strlist.h"
+
+#include "i18n.h"
+#include "util.h"
+#include "no-libgcrypt.h"
+
+
+/* Constants for the options.  */
+enum 
+  {
+    oQuiet	  = 'q',
+    oVerbose	  = 'v',
+
+    oOCSP         = 500,
+    oPing,
+    oUseDaemon,
+    oDummy
+  };
+
+
+/* The list of options as used by the argparse.c code.  */
+static ARGPARSE_OPTS opts[] = {
+  { oVerbose,  "verbose",   0, N_("verbose") },
+  { oQuiet,    "quiet",     0, N_("be somewhat more quiet") },
+  { oOCSP,     "ocsp",      0, N_("use OCSP instead of CRLs") },
+  { oPing,     "ping",      0, N_("check whether a dirmngr is running")},
+  { oUseDaemon,"use-daemon",0, N_("force use of the daemon")},
+  {0}
+};
+ 
+#define DEFAULT_SOCKET_NAME "/var/run/dirmngr/socket"
+
+/* The usual structure for the program flags.  */
+static struct
+{
+  int quiet;
+  int verbose;
+  const char *dirmngr_program;
+  int force_pipe_server;
+
+  int use_ocsp;
+
+} opt;
+
+
+/* Communication structure for the certificate inquire callback. */
+struct inq_cert_parm_s
+{
+  assuan_context_t ctx;
+  const unsigned char *cert;
+  size_t certlen;
+};
+
+
+/* Prototypes.  */
+static assuan_context_t start_dirmngr (int only_daemon);
+static gpg_error_t read_certificate (const char *fname,
+                                     unsigned char **rbuf, size_t *rbuflen);
+static gpg_error_t do_check (assuan_context_t ctx,
+                             const unsigned char *cert, size_t certlen);
+
+
+
+/* Function called by argparse.c to display information.  */
+static const char *
+my_strusage (int level)
+{
+  const char *p;
+    
+  switch(level)
+    {
+    case 11: p = "dirmngr-client";
+      break;
+    case 13: p = VERSION; break;
+    case 14: p = "Copyright (C) 2004 g10 Code GmbH"; break;
+    case 17: p = PRINTABLE_OS_NAME; break;
+    case 19: p =
+               _("Please report bugs to <gpa-dev at gnupg.org>.\n");
+      break;
+    case 1:
+    case 40: p =
+               _("Usage: dirmngr-client [options] [certfile] (-h for help)\n");
+      break;
+    case 41: p =
+          _("Syntax: dirmngr-client [options] [certfile]\n"
+            "Test an X.509 certificate against a CRL or do an OCSP check\n"
+            "The process returns 0 if the certificate is valid, 1 if it is\n"
+            "not valid and other error codes for general failures\n");
+      break;
+
+    default: p = NULL;
+    }
+  return p;
+}
+
+
+static void
+i18n_init (void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+  set_gettext_file (PACKAGE);
+#else
+# ifdef ENABLE_NLS
+  setlocale (LC_ALL, "" );
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+# endif
+#endif
+}
+
+
+int
+main (int argc, char **argv )
+{
+  ARGPARSE_ARGS pargs;
+  assuan_context_t ctx;
+  gpg_error_t err;
+  unsigned char *certbuf;
+  size_t certbuflen;
+  int do_ping = 0;
+  int use_daemon = 0;
+  
+  set_strusage (my_strusage);
+  log_set_prefix ("dirmngr-client",
+                  JNLIB_LOG_WITH_PREFIX); 
+  
+  /* Init Assuan.  */
+  assuan_set_assuan_log_stream (log_get_stream ());
+  assuan_set_assuan_log_prefix (log_get_prefix (NULL));
+
+  /* Setup I18N. */
+  i18n_init();
+
+  /* Parse the command line.  */
+  pargs.argc = &argc;
+  pargs.argv = &argv;
+  pargs.flags= 1;  /* Do not remove the args. */
+  while (arg_parse (&pargs, opts) )
+    {
+      switch (pargs.r_opt)
+        {
+        case oVerbose: opt.verbose++; break;
+        case oQuiet: opt.quiet++; break;
+        case oOCSP: opt.use_ocsp++; break;
+        case oPing: do_ping = 1; break;
+        case oUseDaemon: use_daemon = 1; break;
+
+        default : pargs.err = 2; break;
+	}
+    }
+  if (log_get_errorcount (0))
+    exit (2);
+
+  if (do_ping)
+    err = 0;
+  else if (!argc)
+    {
+      err = read_certificate (NULL, &certbuf, &certbuflen);
+      if (err)
+        log_error (_("error reading certificate from stdin: %s\n"),
+                   gpg_strerror (err));
+    }
+  else if (argc == 1)
+    {
+      err = read_certificate (*argv, &certbuf, &certbuflen);
+      if (err)
+        log_error (_("error reading certificate from `%s': %s\n"),
+                   *argv, gpg_strerror (err));
+    }
+  else
+    {
+      err = 0;
+      usage (1);
+    }
+
+  if (log_get_errorcount (0))
+    exit (2);
+
+  if (certbuflen > 20000)
+    {
+      log_error (_("certificate too large to make any sense\n"));
+      exit (2);
+    }
+
+  ctx = start_dirmngr (do_ping || use_daemon);
+  if (!ctx)
+    exit (2);
+
+  if (!do_ping)
+    {
+      err = do_check (ctx, certbuf, certbuflen);
+      xfree (certbuf);
+    }
+
+  assuan_disconnect (ctx);
+
+  if (do_ping)
+    {
+      if (!opt.quiet)
+        log_info (_("a dirmngr daemon is up and running\n"));
+      return 0;
+    }
+  else if (!err)
+    {
+      if (!opt.quiet)
+        log_info (_("certificate is valid\n"));
+      return 0;
+    }
+  else if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED )
+    {
+      if (!opt.quiet)
+        log_info (_("certificate has been revoked\n"));
+      return 1;
+    }
+  else
+    {
+      log_error (_("certificate check failed: %s\n"), gpg_strerror (err));
+      return 2;
+    }
+}
+
+
+/* Try to connect to the dirmngr via socket or fork it off and work by
+   pipes.  Handle the server's initial greeting */
+static assuan_context_t
+start_dirmngr (int only_daemon)
+{
+  int rc;
+  char *infostr, *p;
+  assuan_context_t ctx;
+  int try_default = 0;
+
+  infostr = opt.force_pipe_server? NULL : getenv ("DIRMNGR_INFO");
+  if (only_daemon && (!infostr || !*infostr))
+    {
+      infostr = DEFAULT_SOCKET_NAME;
+      try_default = 1;
+    }
+  if (!infostr || !*infostr)
+    {
+      const char *pgmname;
+      const char *argv[3];
+      int no_close_list[3];
+      int i;
+
+      if (only_daemon)
+        {
+          log_error (_("apparently no running dirmngr\n"));
+          return NULL;
+        }
+
+      if (opt.verbose)
+        log_info (_("no running dirmngr - starting one\n"));
+      
+      if (!opt.dirmngr_program || !*opt.dirmngr_program)
+        opt.dirmngr_program = "./dirmngr";
+      if ( !(pgmname = strrchr (opt.dirmngr_program, '/')))
+        pgmname = opt.dirmngr_program;
+      else
+        pgmname++;
+
+      argv[0] = pgmname;
+      argv[1] = "--server";
+      argv[2] = NULL;
+
+      i=0;
+      if (log_get_fd () != -1)
+        no_close_list[i++] = log_get_fd ();
+      no_close_list[i++] = fileno (stderr);
+      no_close_list[i] = -1;
+
+      /* Connect to the agent and perform initial handshaking.  */
+      rc = assuan_pipe_connect (&ctx, opt.dirmngr_program, (char**)argv,
+                                no_close_list);
+    }
+  else /* Connect to a daemon.  */
+    {
+      int prot;
+      int pid;
+
+      infostr = xstrdup (infostr);
+      if (!try_default && *infostr)
+        {
+          if ( !(p = strchr (infostr, ':')) || p == infostr)
+            {
+              log_error (_("malformed DIRMNGR_INFO environment variable\n"));
+              xfree (infostr);
+              if (only_daemon)
+                return NULL;
+              /* Try again by starting a new instance.  */
+              opt.force_pipe_server = 1;
+              return start_dirmngr (0);
+            }
+          *p++ = 0;
+          pid = atoi (p);
+          while (*p && *p != ':')
+            p++;
+          prot = *p? atoi (p+1) : 0;
+          if (prot != 1)
+            {
+              log_error (_("dirmngr protocol version %d is not supported\n"),
+                         prot);
+              xfree (infostr);
+              if (only_daemon)
+                return NULL;
+              opt.force_pipe_server = 1;
+              return start_dirmngr (0);
+            }
+        }
+      else
+        pid = -1;
+
+      rc = assuan_socket_connect (&ctx, infostr, pid);
+      xfree (infostr);
+      if (rc == ASSUAN_Connect_Failed && !only_daemon)
+        {
+          log_error (_("can't connect to the dirmngr - trying fall back\n"));
+          opt.force_pipe_server = 1;
+          return start_dirmngr (0);
+        }
+    }
+
+  if (rc)
+    {
+      log_error (_("can't connect to the dirmngr: %s\n"),
+                 assuan_strerror (rc));
+      return NULL;
+    }
+
+  return ctx;
+}
+
+
+/* Red a binary certificate from the file FNAME.  If fname is NULL the
+   file is read from stdin.  The certificate is returned in an alloced
+   buffer whos address will be returned in RBUF and its length in
+   RBUFLEN.  */
+static gpg_error_t
+read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen)
+{
+  gpg_error_t err;
+  FILE *fp;
+  unsigned char *buf;
+  size_t nread, bufsize, buflen;
+
+  fp = fname? fopen (fname, "rb") : stdin;
+  if (!fp)
+    return gpg_error_from_errno (errno);
+
+  buf = NULL;
+  bufsize = buflen = 0;
+#define NCHUNK 8192
+  do 
+    {
+      bufsize += NCHUNK;
+      if (!buf)
+        buf = xmalloc (bufsize);
+      else
+        buf = xrealloc (buf, bufsize);
+
+      nread = fread (buf+buflen, 1, NCHUNK, fp);
+      if (nread < NCHUNK && ferror (fp))
+        {
+          err = gpg_error_from_errno (errno);
+          xfree (buf);
+          if (fname)
+            fclose (fp);
+          return err;
+        }
+      buflen += nread;
+    }
+  while (nread == NCHUNK);
+#undef NCHUNK
+  if (fname)
+    fclose (fp);
+  *rbuf = buf;
+  *rbuflen = buflen;
+  return 0;
+}
+
+
+/* Callback for the inquire fiunction to send back the certificate.  */
+static assuan_error_t
+inq_cert (void *opaque, const char *line)
+{
+  struct inq_cert_parm_s *parm = opaque;
+  assuan_error_t ae;
+
+  if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10]))
+    {
+      ae = assuan_send_data (parm->ctx, parm->cert, parm->certlen);
+    }
+  else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]))
+    {
+      /* We don't support this but dirmngr might ask for it.  So
+         simply ignore it by sending back and empty value. */
+      ae = assuan_send_data (parm->ctx, NULL, 0);
+    }
+  else if (!strncmp (line, "SENDISSUERCERT", 14)
+           && (line[14] == ' ' || !line[14]))
+    {
+      /* We don't support this but dirmngr might ask for it.  So
+         simply ignore it by sending back and empty value. */
+      ae = assuan_send_data (parm->ctx, NULL, 0);
+    }
+  else
+    {
+      log_info (_("unsupported inquiry `%s'\n"), line);
+      ae = ASSUAN_Inquire_Unknown;
+    }
+
+  return ae;
+}
+
+
+/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP.
+   Return a proper error code. */
+static gpg_error_t
+do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen)
+{
+  assuan_error_t ae;
+  struct inq_cert_parm_s parm;
+
+  memset (&parm, 0, sizeof parm);
+  parm.ctx = ctx;
+  parm.cert = cert;
+  parm.certlen = certlen;
+
+  ae = assuan_transact (ctx, opt.use_ocsp? "CHECKOCSP":"CHECKCRL", NULL, NULL,
+                        inq_cert, &parm,
+                        NULL, NULL);
+  if (opt.verbose > 1)
+    log_info ("response of dirmngr: %s\n", ae? assuan_strerror (ae): "okay");
+  return map_assuan_err (ae);
+}
Index: dirmngr/src/dirmngr.c
diff -u dirmngr/src/dirmngr.c:1.40 dirmngr/src/dirmngr.c:1.41
--- dirmngr/src/dirmngr.c:1.40	Mon Oct  4 13:50:50 2004
+++ dirmngr/src/dirmngr.c	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,5 @@
 /* dirmngr.c - LDAP access
- *	Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *	Copyright (C) 2002 Klarälvdalens Datakonsult AB
  *      Copyright (C) 2003, 2004 g10 Code GmbH
  *
  * This file is part of DirMngr.
@@ -30,12 +30,16 @@
 #include <assert.h>
 #include <time.h>
 #include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
 #include <unistd.h>
 #include <signal.h>
+#include <pth.h>
 
 #include <gcrypt.h>
 #include <ksba.h>
-#include <assuan.h> /* needed for the malloc hooks */
+#include <assuan.h> /* Needed for the malloc hooks */
 
 #define JNLIB_NEED_LOG_LOGV
 #include "dirmngr.h"
@@ -52,6 +56,7 @@
   oNoVerbose = 500,
   
   aServer,
+  aDaemon,
   aListCRLs,
   aLoadCRL,
   aFetchCRL,
@@ -79,6 +84,7 @@
   oFakedSystemTime,
   oForce,
   oAllowOCSP,
+  oSocketName,
 aTest };
 
 
@@ -87,7 +93,8 @@
   
   { 300, NULL, 0, N_("@Commands:\n ") },
 
-  { aServer,   "server",    256, N_("run in server mode") },
+  { aServer,   "server",    256, N_("run in server mode (foreground)") },
+  { aDaemon,   "daemon",    256, N_("run in daemon mode (background)") },
   { aListCRLs, "list-crls", 256, N_("list the contents of the CRL cache")},
   { aLoadCRL,  "load-crl",  256, N_("|FILE|load CRL from FILE into cache")},
   { aFetchCRL, "fetch-crl", 256, N_("|URL|fetch a CRL from URL")},
@@ -123,6 +130,8 @@
   { oMaxReplies, "max-replies", 1,
     N_("|N|do not return more than N items in one query")},
 
+  { oSocketName, "socket-name", 2, N_("|FILE|listen on socket FILE") },
+
   { oFakedSystemTime, "faked-system-time", 4, "@" }, /* (epoch time) */
   { oDebug,    "debug"     ,4|16, "@"},
   { oDebugAll, "debug-all" ,0,    "@"},
@@ -133,10 +142,30 @@
 
 #define DEFAULT_MAX_REPLIES 10
 #define DEFAULT_LDAP_TIMEOUT 100 /* arbitrary large timeout */
+#define DEFAULT_SOCKET_NAME "/var/run/dirmngr/socket"
+
+/* For the cleanup handler we need to keep track of the socket's name. */
+static const char *socket_name;
+/* Only if this flag has been set we will remove the socket file.  */
+static int cleanup_socket;
+/* Keep track of the current log file so that we can avoid updating
+   the log file after a SIGHUP if it didn't changed. Malloced. */
+static char *current_logfile;
+/* Helper to implement --debug-level. */
+static const char *debug_level;
+/* Flag indicating that a shutdown has been requested.  */
+static int shutdown_pending;
+/* Counter for the active connections.  */
+static int active_connections;
 
+/* Prototypes. */
 static void cleanup (void);
 static ldap_server_t parse_ldapserver_file (const char* filename);
 static void free_ldapservers_list (ldap_server_t servers);
+static void handle_connections (int listen_fd);
+
+/* Pth wrapper function definitions. */
+GCRY_THREAD_OPTION_PTH_IMPL;
 
 
 static const char *
@@ -211,27 +240,28 @@
    set of debug flags is set; thus overriding all flags already
    set. */
 static void
-set_debug (const char *level)
+set_debug (void)
 {
-  if (!level)
+  if (!debug_level)
     ;
-  else if (!strcmp (level, "none"))
+  else if (!strcmp (debug_level, "none"))
     opt.debug = 0;
-  else if (!strcmp (level, "basic"))
+  else if (!strcmp (debug_level, "basic"))
     opt.debug = DBG_ASSUAN_VALUE;
-  else if (!strcmp (level, "advanced"))
+  else if (!strcmp (debug_level, "advanced"))
     opt.debug = DBG_ASSUAN_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE;
-  else if (!strcmp (level, "expert"))
+  else if (!strcmp (debug_level, "expert"))
     opt.debug = (DBG_ASSUAN_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE
                  |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
-  else if (!strcmp (level, "guru"))
+  else if (!strcmp (debug_level, "guru"))
     opt.debug = ~0;
   else
     {
-      log_error (_("invalid debug-level `%s' given\n"), level);
+      log_error (_("invalid debug-level `%s' given\n"), debug_level);
       log_info (_("valid debug levels are: %s\n"),
                 "none, basic, advanced, expert, guru");
-      dirmngr_exit (2);
+      opt.debug = 0; /* Reset debugging, so that prior debug
+                        statements won't have an undesired effect. */
     }
 
 
@@ -273,6 +303,74 @@
 }
 
 
+/* Helper to start the ripper thread for the ldap wrapper.  */
+static void
+launch_ripper_thread (void)
+{
+  static int done;
+  pth_attr_t tattr;
+
+  if (done)
+    return;
+  done = 1;
+
+  tattr = pth_attr_new();
+  pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+  pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
+  pth_attr_set (tattr, PTH_ATTR_NAME, "ldap-ripper");
+
+  if (!pth_spawn (tattr, ldap_wrapper_thread, NULL))
+    {
+      log_error (_("error spawning ldap wrapper ripper thread: %s\n"),
+                 strerror (errno) );
+      dirmngr_exit (1);
+    }
+}
+
+
+
+/* Handle options which are allowed to be reset after program start.
+   Return true if the current option in PARGS could be handled and
+   false if not.  As a special feature, passing a value of NULL for
+   PARGS, resets the options to the default.  REREAD should be set
+   true if it is not the initial option parsing. */
+static int
+parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
+{
+  if (!pargs)
+    { /* Reset mode. */
+      opt.quiet = 0;
+      opt.verbose = 0;
+      opt.debug = 0;
+      return 1;
+    }
+
+  switch (pargs->r_opt)
+    {
+    case oQuiet:   opt.quiet = 1; break;
+    case oVerbose: opt.verbose++; break;
+    case oDebug:   opt.debug |= pargs->r.ret_ulong; break;
+    case oDebugAll: opt.debug = ~0; break;
+    case oDebugLevel: debug_level = pargs->r.ret_str; break;
+
+    case oLogFile:
+      if (!reread)
+        return 0; /* Not handled. */
+      if (!current_logfile || !pargs->r.ret_str
+          || strcmp (current_logfile, pargs->r.ret_str))
+        {
+          log_set_file (pargs->r.ret_str);
+          xfree (current_logfile);
+          current_logfile = xtrystrdup (pargs->r.ret_str);
+        }
+      break;
+
+    default:
+      return 0; /* Not handled. */
+    }
+  set_debug ();
+  return 1; /* Handled. */
+}
 
 int
 main (int argc, char **argv )
@@ -289,13 +387,14 @@
   int default_config =1;
   int greeting = 0;
   int nogreeting = 0;
-  const char *debug_level = NULL;
   int nodetach = 0;
   int csh_style = 0;
   char *logfile = NULL;
   char *ldapfile = NULL;
   int debug_wait = 0;
   int rc;
+  int homedir_seen = 0;
+  int daemon_seen = 0;
 
   set_strusage (my_strusage);
   log_set_prefix ("dirmngr", 1|4); 
@@ -303,7 +402,16 @@
   /* Check that the libraries are suitable.  Do it here because
      the option parsing may need services of the libraries. */
 
+  /* Libgcrypt requires us to register the threading model first.
+     Note that this will also do the pth_init. */
+
   /* Init Libgcrypt. */
+  rc = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth);
+  if (rc)
+    {
+      log_fatal ("can't register GNU Pth with Libgcrypt: %s\n",
+                 gpg_strerror (rc));
+    }
   gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
   if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
     {
@@ -356,6 +464,10 @@
   /* Default max replies */
   opt.max_replies = DEFAULT_MAX_REPLIES;
 
+  /* Other defaults.  */
+  socket_name = DEFAULT_SOCKET_NAME;
+
+
   /* Check whether we have a config file given on the commandline */
   orig_argc = argc;
   orig_argv = argv;
@@ -372,12 +484,23 @@
 	     commandline */
           default_config = 0;
 	}
-	else if (pargs.r_opt == oNoOptions)
-          default_config = 0; /* --no-options */
-	else if (pargs.r_opt == oHomedir)
+      else if (pargs.r_opt == oNoOptions)
+        default_config = 0; /* --no-options */
+      else if (pargs.r_opt == oHomedir)
+        {
           opt.homedir = pargs.r.ret_str;
+          homedir_seen = 1;
+        }
+      else if (pargs.r_opt == aDaemon)
+        daemon_seen = 1;
     }
 
+  /* If --daemon has been given on the command line but not --homedir,
+     we switch to /etc/dirmngr as default home directory.  Note, that
+     this also overrides the GNUPGHOME environment variable.  */
+  if (daemon_seen && !homedir_seen)
+    opt.homedir = DIRMNGR_SYSCONFDIR;
+
   if (default_config)
     configname = make_filename (opt.homedir, "dirmngr.conf", NULL );
   
@@ -415,9 +538,12 @@
 
   while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
     {
+      if (parse_rereadable_options (&pargs, 0))
+        continue; /* Already handled */
       switch (pargs.r_opt)
         {
         case aServer: 
+        case aDaemon:
         case aShutdown: 
         case aFlush: 
 	case aListCRLs: 
@@ -471,6 +597,8 @@
 
         case oForce: opt.force = 1; break;
 
+        case oSocketName: socket_name = pargs.r.ret_str; break;
+
         default : pargs.err = configfp? 1:2; break;
 	}
     }
@@ -507,7 +635,7 @@
       log_info (_("WARNING: running with faked system time %s\n"), tbuf);
     }
 
-  set_debug (debug_level);
+  set_debug ();
 
   /* Get LDAP server list from file. */
   if (!ldapfile) 
@@ -519,20 +647,23 @@
   else
       opt.ldapservers = parse_ldapserver_file (ldapfile);
 
+  /* We need to ignore the PIPE signal because the we might log to a
+     socket and that code handles EPIPE properly.  The ldap wrapper
+     also requires us to ignore this silly signal. Assuan would set
+     this signal to ignore anyway.*/
+  signal (SIGPIPE, SIG_IGN);
+
+
   /* Ready.  Now to our duties. */
   if (!cmd)
     cmd = aServer;
   rc = 0;
+
   if (cmd == aServer)
     {
       if (argc)
         wrong_args ("--server");
 
-      /* We need to ignore the PIPE signal because the we might log to
-         a socket and that code handles EPIPE properly.  Assuan would
-         set this signal to ignore anyway.*/
-      signal (SIGPIPE, SIG_IGN);
-
       if (logfile)
         {
           log_set_file (logfile);
@@ -546,52 +677,204 @@
           sleep (debug_wait);
           log_debug ("... okay\n");
         }
+
+      launch_ripper_thread ();
+      crl_cache_init ();
+      start_command_handler (-1);
+    }
+  else if (cmd == aDaemon)
+    {
+      int fd;
+      pid_t pid;
+      int len;
+      struct sockaddr_un serv_addr;
+
+      if (argc)
+        wrong_args ("--daemon");
       
-      start_command_handler();
+      /* Now start with logging to a file if this is desired. */
+      if (logfile)
+        {
+          log_set_file (logfile);
+          log_set_prefix (NULL, (JNLIB_LOG_WITH_PREFIX
+                                 |JNLIB_LOG_WITH_TIME
+                                 |JNLIB_LOG_WITH_PID));
+          current_logfile = xstrdup (logfile);
+        }
+
+      if (strchr (socket_name, ':') )
+        {
+          log_error (_("colons are not allowed in the socket name\n"));
+          dirmngr_exit (1);
+        }
+      if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path ) 
+        {
+          log_error (_("name of socket too long\n"));
+          dirmngr_exit (1);
+        }
+   
+      fd = socket (AF_UNIX, SOCK_STREAM, 0);
+      if (fd == -1)
+        {
+          log_error (_("can't create socket: %s\n"), strerror(errno) );
+          cleanup ();
+          dirmngr_exit (1);
+        }
+
+      memset (&serv_addr, 0, sizeof serv_addr);
+      serv_addr.sun_family = AF_UNIX;
+      strcpy (serv_addr.sun_path, socket_name);
+      len = (offsetof (struct sockaddr_un, sun_path)
+             + strlen(serv_addr.sun_path) + 1);
+
+      rc = bind (fd, (struct sockaddr*)&serv_addr, len);
+      if (rc == -1 && errno == EADDRINUSE)
+        {
+          remove (socket_name);
+          rc = bind (fd, (struct sockaddr*)&serv_addr, len);
+        }
+      if (rc == -1)
+        {
+          log_error (_("error binding socket to `%s': %s\n"),
+                     serv_addr.sun_path, strerror (errno) );
+          close (fd);
+          dirmngr_exit (1);
+        }
+      cleanup_socket = 1;
+  
+      if (listen (fd, 5 ) == -1)
+        {
+          log_error (_("listen() failed: %s\n"), strerror (errno));
+          close (fd);
+          dirmngr_exit (1);
+        }
+
+      if (opt.verbose)
+        log_info (_("listening on socket `%s'\n"), socket_name );
+
+      fflush (NULL);
+      pid = pth_fork ();
+      if (pid == (pid_t)-1) 
+        {
+          log_fatal (_("fork failed: %s\n"), strerror (errno) );
+          dirmngr_exit (1);
+        }
+
+      if (pid) 
+        { /* We are the parent */
+          char *infostr;
+          
+          /* Don't let cleanup() remove the socket - the child is
+             responsible for doing that.  */
+          cleanup_socket = 0;
+
+          close (fd);
+          
+          /* Create the info string: <name>:<pid>:<protocol_version> */
+          if (asprintf (&infostr, "DIRMNGR_INFO=%s:%lu:1",
+                        socket_name, (ulong)pid ) < 0)
+            {
+              log_error (_("out of core\n"));
+              kill (pid, SIGTERM);
+              dirmngr_exit (1);
+            }
+          /* Print the environment string, so that the caller can use
+             shell's eval to set it */
+          if (csh_style)
+            {
+              *strchr (infostr, '=') = ' ';
+              printf ( "setenv %s\n", infostr);
+            }
+          else
+            {
+              printf ( "%s; export DIRMNGR_INFO;\n", infostr);
+            }
+          free (infostr);
+          exit (0); 
+          /*NEVER REACHED*/
+        } /* end parent */
+      
+      
+      /* 
+         This is the child
+       */
+
+      /* Detach from tty and put process into a new session */
+      if (!nodetach )
+        { 
+          int i;
+          unsigned int oldflags;
+
+          /* Close stdin, stdout and stderr unless it is the log stream */
+          for (i=0; i <= 2; i++)
+            {
+              if (!log_test_fd (i) && i != fd )
+                close (i);
+            }
+          if (setsid() == -1)
+            {
+              log_error (_("setsid() failed: %s\n"), strerror(errno) );
+              dirmngr_exit (1);
+            }
+
+          log_get_prefix (&oldflags);
+          log_set_prefix (NULL, oldflags | JNLIB_LOG_RUN_DETACHED);
+          opt.running_detached = 1;
+        }
+
+      if (chdir("/"))
+        {
+          log_error (_("chdir to / failed: %s\n"), strerror (errno));
+          dirmngr_exit (1);
+        }
+      
+
+      launch_ripper_thread ();
+      crl_cache_init ();
+      handle_connections (fd);
+      close (fd);
     }
   else if (cmd == aListCRLs)
     {
       /* Just list the CRL cache and exit. */
-      crl_cache_t cache;
-
       if (argc)
         wrong_args ("--list-crls");
-      cache = crl_cache_init ();
-      crl_cache_list (cache, stdout);
+      launch_ripper_thread ();
+      crl_cache_init ();
+      crl_cache_list (stdout);
     }
   else if (cmd == aLoadCRL)
     {
-      crl_cache_t c;
-
-      c = crl_cache_init ();
+      launch_ripper_thread ();
+      crl_cache_init ();
       if (!argc)
-        rc = crl_cache_load (NULL, c, NULL);
+        rc = crl_cache_load (NULL, NULL);
       else
         {
           for (; !rc && argc; argc--, argv++)
-            rc = crl_cache_load (NULL, c, *argv);
+            rc = crl_cache_load (NULL, *argv);
         }
     }
   else if (cmd == aFetchCRL)
     {
-      crl_cache_t c;
-      FILE *fp;
+      ksba_reader_t reader;
 
       if (argc != 1)
         wrong_args ("--fetch-crl URL");
 
-      c = crl_cache_init ();
-      rc = crl_fetch (argv[0], &fp);
+      launch_ripper_thread ();
+      crl_cache_init ();
+      rc = crl_fetch (argv[0], &reader);
       if (rc)
         log_error (_("fetching CRL from `%s' failed: %s\n"),
                      argv[0], gpg_strerror (rc));
       else
         {
-          rc = crl_cache_insert (NULL, c,  argv[0], fp); 
+          rc = crl_cache_insert (NULL, argv[0], reader); 
           if (rc)
             log_error (_("processing CRL from `%s' failed: %s\n"),
                        argv[0], gpg_strerror (rc));
-          fclose (fp);
+          crl_close_reader (reader);
         }
     }
   else if (cmd == aFlush)
@@ -663,8 +946,17 @@
 static void
 cleanup (void)
 {
+  crl_cache_deinit ();
+
   free_ldapservers_list (opt.ldapservers);
   opt.ldapservers = NULL;
+
+  if (cleanup_socket)
+    {
+      cleanup_socket = 0;
+      if (socket_name && *socket_name)
+        remove (socket_name);
+    }
 }
 
 
@@ -832,3 +1124,145 @@
       servers = tmp;
     }
 }
+
+
+
+/*
+   Stuff used in daemon mode.  
+ */
+/* The signal handler. */
+static void
+handle_signal (int signo)
+{
+  switch (signo)
+    {
+    case SIGHUP:
+      log_info (_("SIGHUP received - "
+                  "re-reading configuration and flushing caches\n"));
+/*       reread_configuration (); */
+      break;
+      
+    case SIGUSR1:
+      log_info (_("SIGUSR1 received - no action defined\n"));
+      break;
+      
+    case SIGUSR2:
+      log_info (_("SIGUSR2 received - no action defined\n"));
+      break;
+
+    case SIGTERM:
+      if (!shutdown_pending)
+        log_info (_("SIGTERM received - shutting down ...\n"));
+      else
+        log_info (_("SIGTERM received - still %d active connections\n"),
+                  active_connections);
+      shutdown_pending++;
+      if (shutdown_pending > 2)
+        {
+          log_info (_("shutdown forced\n"));
+          log_info ("%s %s stopped\n", strusage(11), strusage(13) );
+          cleanup ();
+          dirmngr_exit (0);
+	}
+      break;
+        
+    case SIGINT:
+      log_info (_("SIGINT received - immediate shutdown\n"));
+      log_info( "%s %s stopped\n", strusage(11), strusage(13));
+      cleanup ();
+      dirmngr_exit (0);
+      break;
+
+    default:
+      log_info (_("signal %d received - no action defined\n"), signo);
+    }
+}
+
+
+
+/* Helper to call a connection's main fucntion. */
+static void *
+start_connection_thread (void *arg)
+{
+  int fd = (int)arg;
+
+  active_connections++;
+  if (opt.verbose)
+    log_info (_("handler for fd %d started\n"), fd);
+
+  start_command_handler (fd);
+
+  if (opt.verbose)
+    log_info (_("handler for fd %d terminated\n"), fd);
+  active_connections--;
+  
+  return NULL;
+}
+
+
+/* Main loop in daemon mode. */
+static void
+handle_connections (int listen_fd)
+{
+  pth_attr_t tattr;
+  pth_event_t ev;
+  sigset_t sigs;
+  int signo;
+  struct sockaddr_un paddr;
+  socklen_t plen = sizeof( paddr );
+  int fd;
+
+  tattr = pth_attr_new();
+  pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+  pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
+  pth_attr_set (tattr, PTH_ATTR_NAME, "dirmngr");
+
+  sigemptyset (&sigs );
+  sigaddset (&sigs, SIGHUP);
+  sigaddset (&sigs, SIGUSR1);
+  sigaddset (&sigs, SIGUSR2);
+  sigaddset (&sigs, SIGINT);
+  sigaddset (&sigs, SIGTERM);
+  ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+
+  for (;;)
+    {
+      if (shutdown_pending)
+        {
+          if (!active_connections)
+            break; /* ready */
+
+          /* Do not accept anymore connections but wait for existing
+             connections to terminate.  */
+          signo = 0;
+          pth_wait (ev);
+          if (pth_event_occurred (ev) && signo)
+            handle_signal (signo);
+          continue;
+	}
+
+      fd = pth_accept_ev (listen_fd, (struct sockaddr *)&paddr, &plen, ev);
+      if (fd == -1)
+        {
+          if (pth_event_occurred (ev))
+            {
+              handle_signal (signo);
+              continue;
+	    }
+          log_error (_("accept failed: %s - waiting 1s\n"), strerror (errno));
+          pth_sleep (1);
+          continue;
+	}
+
+      if (!pth_spawn (tattr, start_connection_thread, (void*)fd))
+        {
+          log_error (_("error spawning connection handler: %s\n"),
+                     strerror (errno) );
+          close (fd);
+	}
+    }
+  
+  pth_event_free (ev, PTH_FREE_ALL);
+  cleanup ();
+  log_info ("%s %s stopped\n", strusage(11), strusage(13));
+}
Index: dirmngr/src/dirmngr.h
diff -u dirmngr/src/dirmngr.h:1.18 dirmngr/src/dirmngr.h:1.19
--- dirmngr/src/dirmngr.h:1.18	Wed Apr 28 11:00:53 2004
+++ dirmngr/src/dirmngr.h	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,6 @@
-/* dirmngr.c - LDAP access 
- *	Copyright (C) 2002 Klarälvdalens Datakonsult AB
+/* dirmngr.c - Common definitions for the dirmngr
+ *	Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *	Copyright (C) 2004 g10 Code GmbH
  *
  * This file is part of DirMngr.
  *
@@ -38,59 +39,11 @@
 #include "../jnlib/strlist.h"
 #include "../jnlib/dotlock.h"
 
-
-#ifdef USE_SIMPLE_GETTEXT
-  int set_gettext_file( const char *filename );
-  const char *gettext( const char *msgid );
-# define _(a) gettext (a)
-# define N_(a) (a)
-#else
-# ifdef HAVE_LOCALE_H
-#  include <locale.h>	
-# endif
-# ifdef ENABLE_NLS
-#  include <libintl.h>
-#  define _(a) gettext (a)
-#  ifdef gettext_noop
-#   define N_(a) gettext_noop (a)
-#  else
-#   define N_(a) (a)
-#  endif
-# else
-#  define _(a) (a)
-#  define N_(a) (a)
-# endif
-#endif /*!USE_SIMPLE_GETTEXT*/
-
-
-/* Handy malloc macros  - use only them */
-#define xtrymalloc(a)    gcry_malloc ((a))
-#define xtrycalloc(a,b)  gcry_calloc ((a),(b))
-#define xtryrealloc(a,b) gcry_realloc ((a),(b))
-#define xtrystrdup(a)    gcry_strdup ((a))
-#define xfree(a)         gcry_free ((a))
-
-#define xmalloc(a)       gcry_xmalloc ((a))
-#define xcalloc(a,b)     gcry_xcalloc ((a),(b))
-#define xrealloc(a,b)    gcry_xrealloc ((a),(b))
-#define xstrdup(a)       gcry_xstrdup ((a))
+#include "i18n.h"
+#include "util.h"
 
 #define seterr(a)  (GNUPG_ ## a)
 
-/* some macros to replace ctype ones and avoid locale problems */
-#define spacep(p)   (*(p) == ' ' || *(p) == '\t')
-#define digitp(p)   (*(p) >= '0' && *(p) <= '9')
-#define hexdigitp(a) (digitp (a)                     \
-                      || (*(a) >= 'A' && *(a) <= 'F')  \
-                      || (*(a) >= 'a' && *(a) <= 'f'))
-/* the atoi macros assume that the buffer has only valid digits */
-#define atoi_1(p)   (*(p) - '0' )
-#define atoi_2(p)   ((atoi_1(p) * 10) + atoi_1((p)+1))
-#define atoi_4(p)   ((atoi_2(p) * 100) + atoi_2((p)+2))
-#define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
-                     *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
-#define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
-
 
 /* This objects keeps information about a particular LDAP server and
    is used as item of a single linked list of servers. */
@@ -116,6 +69,8 @@
   int batch;          /* batch mode */
   const char *homedir;/* configuration directory name */
 
+  int running_detached; /* We are running in detached mode.  */
+
   int force;          /* Force loading outdated CRLs. */
 
   int allow_ocsp;     /* Allow using OCSP. */
@@ -171,10 +126,11 @@
 /*-- server.c --*/
 ksba_cert_t get_cert_local (ctrl_t ctrl, const char *issuer);
 ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *issuer);
-void start_command_handler (void);
+void start_command_handler (int fd);
 void dirmngr_status (ctrl_t ctrl, const char *keyword, ...);
 void dirmngr_tick (ctrl_t ctrl);
 
+
 #endif /*DIRMNGR_H*/
 
 
Index: dirmngr/src/dirmngr_ldap.c
diff -u /dev/null dirmngr/src/dirmngr_ldap.c:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/dirmngr_ldap.c	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,497 @@
+/* dirmngr-ldap.c  -  The LDAP helper for dirmngr.
+ *	Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <ldap.h>
+
+
+
+#define JNLIB_NEED_LOG_LOGV
+#include "../jnlib/logging.h"
+#include "../jnlib/argparse.h"
+#include "../jnlib/stringhelp.h"
+#include "../jnlib/mischelp.h"
+#include "../jnlib/strlist.h"
+
+#include "i18n.h"
+#include "util.h"
+#include "no-libgcrypt.h"
+
+#define DEFAULT_LDAP_TIMEOUT 100 /* Arbitrary long timeout. */
+
+
+/* Constants for the options.  */
+enum 
+  {
+    oQuiet	  = 'q',
+    oVerbose	  = 'v',
+
+    oTimeout      = 500,
+    oMulti,
+    oHost,
+    oPort,
+    oUser,
+    oPass,
+    oEnvPass,
+    oDN,
+    oFilter,
+    oAttr,
+
+    oDummy
+  };
+
+
+/* The list of options as used by the argparse.c code.  */
+static ARGPARSE_OPTS opts[] = {
+  { oVerbose,  "verbose",   0, N_("verbose") },
+  { oQuiet,    "quiet",     0, N_("be somewhat more quiet") },
+  { oTimeout,  "timeout",   1, N_("|N|set LDAP timeout to N seconds")},
+  { oMulti,    "multi",     0, N_("return all values in"
+                                  " a record oriented format")},
+  { oHost,     "host",      2, N_("|NAME|connect to host NAME")},
+  { oPort,     "port",      1, N_("|N|connect to port N")},
+  { oUser,     "user",      2, N_("|NAME|use user NAME for authentication")},
+  { oPass,     "pass",      2, N_("|PASS|use password PASS"
+                                  " for authentication")},
+  { oEnvPass,  "env-pass",  0, N_("take password from $DIRMNGR_LDAP_PASS")},
+  { oDN,       "dn",        2, N_("|STRING|query DN STRING")},
+  { oFilter,   "filter",    2, N_("|STRING|use STRING as filter expression")},
+  { oAttr,     "attr",      2, N_("|STRING|return the attribute STRING")},
+  {0}
+};
+
+
+/* The usual structure for the program flags.  */
+static struct
+{
+  int quiet;
+  int verbose;
+  struct timeval timeout;
+  int multi;
+
+  /* Note that we can't use const for the strings because ldap_* are
+     not defined that way.  */
+  char *user;  /* Authentication user.  */
+  char *pass;  /* Authentication password.  */
+  char *host;  /* Override host.  */
+  int port;    /* Override port.  */
+  char *dn;    /* Override DN.  */
+  char *filter;/* Override filter.  */
+  char *attr;  /* Override attribute.  */
+} opt;
+
+
+
+/* Prototypes.  */
+static int process_url (const char *url);
+
+
+
+/* Function called by argparse.c to display information.  */
+static const char *
+my_strusage (int level)
+{
+  const char *p;
+    
+  switch(level)
+    {
+    case 11: p = "dirmngr_ldap";
+      break;
+    case 13: p = VERSION; break;
+    case 14: p = "Copyright (C) 2004 g10 Code GmbH"; break;
+    case 17: p = PRINTABLE_OS_NAME; break;
+    case 19: p =
+               _("Please report bugs to " PACKAGE_BUGREPORT ".\n");
+      break;
+    case 1:
+    case 40: p =
+               _("Usage: dirmngr_ldap [options] [URL] (-h for help)\n");
+      break;
+    case 41: p =
+          _("Syntax: dirmngr_ldap [options] [URL]\n"
+            "Internal LDAP helper for Dirmngr.\n"
+            "Interface and options may change without notice.\n");
+      break;
+
+    default: p = NULL;
+    }
+  return p;
+}
+
+
+static void
+i18n_init (void)
+{
+#ifdef USE_SIMPLE_GETTEXT
+  set_gettext_file (PACKAGE);
+#else
+# ifdef ENABLE_NLS
+  setlocale (LC_ALL, "" );
+  bindtextdomain (PACKAGE, LOCALEDIR);
+  textdomain (PACKAGE);
+# endif
+#endif
+}
+
+
+int
+main (int argc, char **argv )
+{
+  ARGPARSE_ARGS pargs;
+  int any_err = 0;
+
+  set_strusage (my_strusage);
+  log_set_prefix ("dirmngr_ldap", JNLIB_LOG_WITH_PREFIX); 
+  
+  /* Setup I18N. */
+  i18n_init();
+
+  /* LDAP defaults */
+  opt.timeout.tv_sec = DEFAULT_LDAP_TIMEOUT;
+  opt.timeout.tv_usec = 0;
+
+
+  /* Parse the command line.  */
+  pargs.argc = &argc;
+  pargs.argv = &argv;
+  pargs.flags= 1;  /* Do not remove the args. */
+  while (arg_parse (&pargs, opts) )
+    {
+      switch (pargs.r_opt)
+        {
+        case oVerbose: opt.verbose++; break;
+        case oQuiet: opt.quiet++; break;
+	case oTimeout: 
+	  opt.timeout.tv_sec = pargs.r.ret_int; 
+	  opt.timeout.tv_usec = 0;
+	  break;
+        case oMulti: opt.multi = 1; break;
+        case oUser: opt.user = pargs.r.ret_str; break;
+        case oPass: opt.pass = pargs.r.ret_str; break;
+        case oEnvPass:
+          opt.pass = getenv ("DIRMNGR_LDAP_PASS");
+          break;
+        case oHost: opt.host = pargs.r.ret_str; break;
+        case oPort: opt.port = pargs.r.ret_int; break;
+        case oDN:   opt.dn = pargs.r.ret_str; break;
+        case oFilter: opt.filter = pargs.r.ret_str; break;
+        case oAttr: opt.attr = pargs.r.ret_str; break;
+
+        default : pargs.err = 2; break;
+	}
+    }
+  if (opt.port < 0 || opt.port > 65535)
+    log_error (_("invalid port number %d\n"), opt.port);
+
+  sleep (5);
+
+  if (log_get_errorcount (0))
+    exit (2);
+
+  if (argc < 1)
+    usage (1);
+
+  for (; argc; argc--, argv++)
+    if (process_url (*argv))
+      any_err = 1;
+
+  return any_err;
+}
+
+
+/* Helper for fetch_ldap().  */
+static int
+print_ldap_entries (LDAP *ld, LDAPMessage *msg, char *want_attr)
+{
+  LDAPMessage *item;
+  int any = 0;
+
+  for (item = ldap_first_entry (ld, msg); item;
+       item = ldap_next_entry (ld, item))
+    {
+      BerElement *berctx;
+      char *attr;
+
+      if (opt.verbose > 1)
+        log_info (_("scanning result for attribute `%s'\n"),
+                  want_attr? want_attr : "[all]");
+
+      if (opt.multi)
+        { /*  Write item marker. */
+          if (fwrite ("I\0\0\0\0", 5, 1, stdout) != 1)
+            {
+              log_error (_("error writing to stdout: %s\n"),
+                         strerror (errno));
+              return -1;
+            }
+        }
+
+          
+      for (attr = ldap_first_attribute (ld, item, &berctx); attr;
+           attr = ldap_next_attribute (ld, item, berctx))
+        {
+          struct berval **values;
+          int idx;
+
+          if (opt.verbose > 1)
+            log_info (_("          available attribute `%s'\n"), attr);
+          
+          /* I case we want only one attribute we do a case
+             insensitive compare without the optional extension
+             (i.e. ";binary").  case insensive is not really correct
+             but the best we can do. */
+          if (want_attr)
+            {
+              char *cp = strchr (attr, ';');
+              if (cp)
+                *cp = 0;
+              if ( ascii_strcasecmp (want_attr, attr) )
+                {
+                  ldap_memfree (attr);
+                  continue; /* Not found:  Try next attribute.  */
+                }
+              if (cp)
+                *cp = ';';
+            }
+          
+          values = ldap_get_values_len (ld, item, attr);
+  
+          if (!values)
+            {
+              if (opt.verbose)
+                log_info (_("attribute `%s' not found\n"), attr);
+              ldap_memfree (attr);
+              continue;
+            }
+
+          if (opt.verbose)
+            log_info (_("found attribute `%s'\n"), attr);
+
+          if (opt.multi)
+            { /*  Write attribute marker. */
+              unsigned char tmp[5];
+              size_t n = strlen (attr);
+
+              tmp[0] = 'A';
+              tmp[1] = (n >> 24);
+              tmp[2] = (n >> 16);
+              tmp[3] = (n >> 8);
+              tmp[4] = (n);
+              if (fwrite (tmp, 5, 1, stdout) != 1 
+                  || fwrite (attr, n, 1, stdout) != 1)
+                {
+                  log_error (_("error writing to stdout: %s\n"),
+                             strerror (errno));
+                  ldap_value_free_len (values);
+                  ldap_memfree (attr);
+                  ber_free (berctx, 0);
+                  return -1;
+                }
+            }
+
+          for (idx=0; values[idx]; idx++)
+            {
+              if (opt.multi)
+                { /* Write value marker.  */
+                  unsigned char tmp[5];
+                  size_t n = values[0]->bv_len;
+
+                  tmp[0] = 'V';
+                  tmp[1] = (n >> 24);
+                  tmp[2] = (n >> 16);
+                  tmp[3] = (n >> 8);
+                  tmp[4] = (n);
+
+                  if (fwrite (tmp, 5, 1, stdout) != 1)
+                    {
+                      log_error (_("error writing to stdout: %s\n"),
+                                 strerror (errno));
+                      ldap_value_free_len (values);
+                      ldap_memfree (attr);
+                      ber_free (berctx, 0);
+                      return -1;
+                    }
+                }
+              if (fwrite (values[0]->bv_val, values[0]->bv_len,
+                          1, stdout) != 1)
+                {
+                  log_error (_("error writing to stdout: %s\n"),
+                             strerror (errno));
+                  ldap_value_free_len (values);
+                  ldap_memfree (attr);
+                  ber_free (berctx, 0);
+                  return -1;
+                }
+              any = 1;
+              if (!opt.multi)
+                break; /* Print only the first value.  */
+            }
+          ldap_value_free_len (values);
+          ldap_memfree (attr);
+          if (want_attr || !opt.multi)
+            break; /* We only want to return the first attribute.  */
+        }
+      ber_free (berctx, 0);
+    } 
+
+  return any?0:-1;
+}
+
+
+
+/* Helper for the URL based LDAP query. */
+static int
+fetch_ldap (const char *url, const LDAPURLDesc *ludp)
+{
+  LDAP *ld;
+  LDAPMessage *msg;
+  int rc = 0;
+  char *host, *dn, *filter, *attrs[2], *attr;
+  int port;
+
+  host     = opt.host?   opt.host   : ludp->lud_host;
+  port     = opt.port?   opt.port   : ludp->lud_port;
+  dn       = opt.dn?     opt.dn     : ludp->lud_dn;
+  filter   = opt.filter? opt.filter : ludp->lud_filter;
+  attrs[0] = opt.attr?   opt.attr   : ludp->lud_attrs? ludp->lud_attrs[0]:NULL;
+  attrs[1] = NULL;
+  attr = attrs[0];
+
+  if (!port)
+    port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389;
+
+  if (opt.verbose)
+    {
+      log_info (_("processing url `%s'\n"), url);
+      if (opt.user)
+        log_info (_("          user `%s'\n"), opt.user);
+      if (opt.pass)
+        log_info (_("          pass `%s'\n"), *opt.pass?"*****":"");
+      if (host)
+        log_info (_("          host `%s'\n"), host);
+      log_info (_("          port %d\n"), port);
+      if (dn)
+        log_info (_("            DN `%s'\n"), dn);
+      if (filter)
+        log_info (_("        filter `%s'\n"), filter);
+      if (opt.multi && !opt.attr && ludp->lud_attrs)
+        {
+          int i;
+          for (i=0; ludp->lud_attrs[i]; i++)
+            log_info (_("          attr `%s'\n"), ludp->lud_attrs[i]);
+        }
+      else if (attr)
+        log_info (_("          attr `%s'\n"), attr);
+    }
+
+
+  if (!host || !*host)
+    {
+      log_error (_("no host name in `%s'\n"), url);
+      return -1;
+    }
+  if (!opt.multi && !attr)
+    {
+      log_error (_("no attribute given for query `%s'\n"), url);
+      return -1;
+    }
+
+  if (!opt.multi && !opt.attr
+      && ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1])
+    log_info (_("WARNING: using first attribute only\n"));
+
+
+  ld = ldap_init (host, port);
+  if (!ld)
+    {
+      log_error (_("LDAP init to `%s:%d' failed: %s\n"), 
+                 host, port, strerror (errno));
+      return -1;
+    }
+  if (ldap_simple_bind_s (ld, opt.user, opt.pass))
+    {
+      log_error (_("binding to `%s:%d' failed: %s\n"), 
+                 host, port, strerror (errno));
+      /* FIXME: Need deinit (ld)?  */
+      return -1;
+    }
+
+  rc = ldap_search_st (ld, dn, ludp->lud_scope, filter,
+                       opt.multi && !opt.attr && ludp->lud_attrs?
+                       ludp->lud_attrs:attrs,
+                       0,
+                       &opt.timeout, &msg);
+  if (rc)
+    {
+      log_error (_("searching `%s' failed: %s\n"), 
+                 url, ldap_err2string (rc));
+      /* FIXME: Need deinit (ld)?  */
+      /* Hmmm: Do we need to released MSG in case of an error? */
+      return -1;
+    }
+
+
+  rc = print_ldap_entries (ld, msg, opt.multi? NULL:attr);
+
+  ldap_msgfree (msg);
+  /* FIXME: Need deinit (ld)?  */
+  return rc;
+}
+
+
+
+
+/* Main processing.  Take the URL and run the DLAP query. The result
+   is printed to stdout, errors are logged to the log stream. */
+static int
+process_url (const char *url)
+{
+  int rc;
+  LDAPURLDesc *ludp = NULL;
+
+
+  if (!ldap_is_ldap_url (url))
+    {
+      log_error (_("`%s' is not an LDAP URL\n"), url);
+      return -1;
+    }
+
+  if (ldap_url_parse (url, &ludp))
+    {
+      log_error (_("`%s' is an invalid LDAP URL\n"), url);
+      return -1;
+    }
+
+
+  rc = fetch_ldap (url, ludp);
+
+  ldap_free_urldesc (ludp);
+  return rc;
+}
+
Index: dirmngr/src/http.c
diff -u dirmngr/src/http.c:1.5 dirmngr/src/http.c:1.6
--- dirmngr/src/http.c:1.5	Fri Apr  2 20:05:19 2004
+++ dirmngr/src/http.c	Tue Nov 16 19:24:35 2004
@@ -392,7 +392,7 @@
         {
           if (buffer)
             {
-              sprintf (buffer, "%02X", *string);
+              sprintf (buffer, "%%%02X", *string);
               buffer += 3;
             }
           n += 3;
Index: dirmngr/src/i18n.h
diff -u /dev/null dirmngr/src/i18n.h:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/i18n.h	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,48 @@
+/* i18n.h
+ *	Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#ifndef I18N_H
+#define I18N_H
+
+
+#ifdef USE_SIMPLE_GETTEXT
+  int set_gettext_file( const char *filename );
+  const char *gettext( const char *msgid );
+# define _(a) gettext (a)
+# define N_(a) (a)
+#else
+# ifdef HAVE_LOCALE_H
+#  include <locale.h>	
+# endif
+# ifdef ENABLE_NLS
+#  include <libintl.h>
+#  define _(a) gettext (a)
+#  ifdef gettext_noop
+#   define N_(a) gettext_noop (a)
+#  else
+#   define N_(a) (a)
+#  endif
+# else
+#  define _(a) (a)
+#  define N_(a) (a)
+# endif
+#endif /*!USE_SIMPLE_GETTEXT*/
+
+#endif /*I18N_H*/
Index: dirmngr/src/ldap.c
diff -u dirmngr/src/ldap.c:1.32 dirmngr/src/ldap.c:1.33
--- dirmngr/src/ldap.c:1.32	Thu May 13 08:55:44 2004
+++ dirmngr/src/ldap.c	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,5 @@
 /* ldap.c - LDAP access
- *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
  *      Copyright (C) 2003 g10 Code GmbH
  *
  * This file is part of DirMngr.
@@ -24,578 +24,686 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <ldap.h>
 #include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/wait.h>
+#include <ldap.h> /* fixme: remove it */
+#include <pth.h>
+#include <ksba.h> 
 
 #include "crlfetch.h"
 #include "dirmngr.h"
 #include "misc.h"
 
+/* In case sysconf does not return a value we need to have a limit. */
+#ifdef _POSIX_OPEN_MAX
+#define MAX_OPEN_FDS _POSIX_OPEN_MAX
+#else
+#define MAX_OPEN_FDS 20
+#endif
+
+
+#define UNENCODED_URL_CHARS "abcdefghijklmnopqrstuvwxyz"   \
+                            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"   \
+                            "01234567890"                  \
+                            "$-_.+!*'(),"
+#define USERCERTIFICATE "userCertificate"
+#define CACERTIFICATE   "caCertificate"
 
-#define CERTATTR "userCertificate"
-#define CERTATTRBIN CERTATTR ";binary"
-#define CACERTATTR "caCertificate"
-#define CACERTATTRBIN CACERTATTR ";binary"
-
-struct cert_fetch_context_s {
-  LDAP *ld;
-  strlist_t query_list; /* List of all prepared queries. */
-  strlist_t query;      /* Pointer into this list. */
-  LDAPMessage *result;
-  int bercount;
-  struct berval **bervalues;
-  char *base;
-};
 
+/* Definition for the context of the cert fetch functions. */
+struct cert_fetch_context_s
+{
+  ksba_reader_t reader;  /* The reader used (shallow copy). */
+  unsigned char *tmpbuf; /* Helper buffer.  */
+  size_t tmpbufsize;     /* Allocated size of tmpbuf.  */
+};
 
 
-/* Fetch the value attr, put result in value, length in valuelen.
-   Returns 0 on success or an gpg error code.
- */
-static gpg_error_t
-get_attr_from_result_ldap (LDAP * ld, LDAPMessage * msg, const char *attr,
-			   unsigned char **value, size_t * valuelen)
+/* To keep track of the LDAp wrapper state we use this structure.  */
+struct wrapper_context_s
 {
-  LDAPMessage *item;
-  struct berval **values;
+  struct wrapper_context_s *next;
 
-  item = ldap_first_entry (ld, msg);
-  if (!item)
-    return gpg_error (GPG_ERR_NO_CRL_KNOWN);
+  pid_t pid;    /* The pid of the wrapper process. */
+  int fd;       /* Connected with stdout of the ldap wrapper.  */
+  int fd_error; /* Set to the errno of the last read error if any.  */
+  int log_fd;   /* Connected with stdoerr of the ldap wrapper.  */
+  int ready;    /* Internally used to mark to be removed contexts. */
+  ksba_reader_t reader; /* The ksba reader object or NULL. */
+  char *line;     /* Used to print the log lines (malloced). */
+  size_t linesize;/* Allocated size of LINE.  */
+  size_t linelen; /* Use size of LINE.  */
+};
 
-  if (DBG_LOOKUP)
-     log_info ("get_attr_from_result_ldap `%s'\n", attr);
 
-  values = ldap_get_values_len (ld, item, attr);
 
-  if (values && opt.verbose)
-    log_info (_("found attribute `%s'\n"), attr);
 
-  if (!values)
-    {
-      /* Try again without or with ";binary" .. which worked with some
-         strange LDAP servers.  */
-      const char *s;
-      char *newattr;
 
-      s = memistr (attr, strlen (attr), ";binary");
-      if (s)
-        { 
-          newattr = xstrdup (attr);
-          newattr[s - attr] = 0;
-        }
-      else
-        {
-          newattr = xmalloc (strlen (attr) + 7 + 1);
-          strcpy (stpcpy (newattr, attr), ";binary");
-        }
+/* We keep a global list of spawed wrapper process.  A separate thread
+   makes use of this list to log error messages and to watch out for
+   finished processes. */
+static struct wrapper_context_s *wrapper_list;
 
-      values = ldap_get_values_len (ld, item, newattr);
-      if (values && opt.verbose)
-	    log_info (_("found attributes `%s' at second try\n"), newattr);
-      xfree (newattr);
-    }
 
-  if (values)
+#if 0
+/* Add HOST and PORT to our list of LDAP servers.  Fixme: We should
+   better use an extra list of servers. */
+static void
+add_server_to_servers (const char *host, int port)
+{
+  ldap_server_t server;
+  ldap_server_t last = NULL;
+  const char *s;
+
+  for (server=opt.ldapservers; server; server = server->next)
     {
-      *valuelen = values[0]->bv_len;
-      *value = xmalloc (*valuelen * sizeof (char));
-      memcpy (*value, values[0]->bv_val, *valuelen);
-      ldap_value_free_len (values);
-      return 0;
+      if (!strcmp (server->host, host) && server->port == port)
+	  return; /* already in list... */
+      last = server;
     }
+
+  /* We assume that the host names are all supplied by our
+     configuration files and thus are sane.  To keep this assumption
+     we must reject all invalid host names. */
+  for (s=host; *s; s++)
+    if (!strchr ("abcdefghijklmnopqrstuvwxyz"
+                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                 "01234567890.-", *s))
+      {
+        log_error (_("invalid char 0x%02x in host name"
+                   " - not added\n"), *s);
+        return;
+      }
+
+  log_info (_("adding `%s:%d' to the ldap server list\n"), host, port);
+  server = xcalloc (1, sizeof *s);
+  server->host = xstrdup (host);
+  server->port = port;
+  if (last)
+    last->next = server;
   else
-    return gpg_error (GPG_ERR_NO_CRL_KNOWN);
+    opt.ldapservers = server;
 }
+#endif
 
-/* The cookie structure used for returning the results from a
-   attr_fetch_ldap query. */
-struct attr_fetch_fun_cookie_s {
-  int eof_reached;   /* True if we already encountered EOF. */
-  gpg_error_t err;   /* The error code from the last operation. */
-  int sys_err;       /* The system error number. */
-  char *attr;        /* The attribute used for the search. */
-  LDAP *ld;
-  int msgid;
-  LDAPMessage *result;
-  LDAPMessage *item;
-  struct berval **values;
-  int nread;
-};
+/* Release the wrapper context and kill a running wrapper process. */
+static void
+destroy_wrapper (struct wrapper_context_s *ctx) 
+{
+  if (ctx->pid != (pid_t)(-1))
+    kill (ctx->pid, SIGTERM);
+  ksba_reader_release (ctx->reader);
+  if (ctx->fd)
+    close (ctx->fd);
+  if (ctx->log_fd)
+    close (ctx->log_fd);
+  xfree (ctx->line);
+  xfree (ctx);
+}
 
 
-/* The reader function used for returning the results from a
-   attr_fetch_ldap query. */
-static int 
-attr_fetch_fun_reader (void *cookie, char *buffer, int size)
+/* Print the content of LINE to thye log stream but make sure to only
+   print complete lines.  Using NULL for LINE will flush any pending
+   output.  LINE may be modified by this fucntion. */
+static void
+print_log_line (struct wrapper_context_s *ctx, char *line)
 {
-  struct attr_fetch_fun_cookie_s *h = cookie;
-  int rc;
-  struct timeval timeout, tout;
+  char *s;
+  size_t n;
 
-  if (h->eof_reached || !size)
-    return 0;
+  if (!line)
+    {
+      if (ctx->line && ctx->linelen)
+        {
 
-  dirmngr_tick (NULL);
-  if (h->err)
+          log_info ("%s\n", ctx->line); 
+          ctx->linelen = 0;
+        }
+      return;
+    }
+  
+  while ((s = strchr (line, '\n')))
     {
-      errno = h->sys_err;
-      return -1;
+      *s = 0;
+      if (ctx->line && ctx->linelen)
+        {
+          log_info ("%s", ctx->line); 
+          ctx->linelen = 0;
+          log_printf ("%s\n", line);
+        }
+      else
+        log_info ("%s\n", line);
+      line = s + 1;
     }
-
-  for (;;)
+  n = strlen (line);
+  if (n)
     {
-      /* Any pending stuff we need to return first? */
-      if (h->values && h->values[0] && h->values[0]->bv_len)
+      if (ctx->linelen + n + 1 >= ctx->linesize)
         {
-          if (h->nread < h->values[0]->bv_len)
+          char *tmp;
+          size_t newsize;
+
+          newsize = ctx->linesize + ((n + 255) & ~255) + 1;
+          tmp = (ctx->line ? xtryrealloc (ctx->line, newsize)
+                           : xtrymalloc (newsize));
+          if (!tmp)
             {
-              if (h->nread + size > h->values[0]->bv_len)
-                size = h->values[0]->bv_len - h->nread;
-              memcpy (buffer, h->values[0]->bv_val + h->nread, size);
-              h->nread += size;
-              return size;
+              log_error (_("error printing log line: %s\n"), strerror (errno));
+              return;
             }
-          if (h->values)
+          ctx->line = tmp;
+          ctx->linesize = newsize;
+        }
+      memcpy (ctx->line + ctx->linelen, line, n);
+      ctx->linelen += n;
+      ctx->line[ctx->linelen] = 0;
+    }
+}
+
+
+
+/* This function is run by a separate thread to maintain the list of
+   wrappers and to log error messages from these wrappers. */
+void *
+ldap_wrapper_thread (void *dummy)
+{
+  fd_set read_fds;
+  int nfds, n;
+  struct timeval tv;
+  struct wrapper_context_s *ctx, *ctx_prev;
+  char line[256];
+
+  for (;;)
+    {
+      FD_ZERO (&read_fds);
+      
+      for (nfds = -1, ctx = wrapper_list; ctx; ctx = ctx->next)
+        {
+          if (ctx->log_fd != -1)
             {
-              ldap_value_free_len (h->values);
-              h->values = NULL;
+              FD_SET (ctx->log_fd, &read_fds);
+              if (ctx->log_fd > nfds)
+                nfds = ctx->log_fd;
             }
         }
+      nfds++;
 
-      /* If we still have a result from the last round, process that one. */
-      if (h->result)
+      tv.tv_sec = 1;
+      tv.tv_usec = 0;
+      nfds = pth_select (nfds, &read_fds, NULL, NULL, &tv);
+      if ( nfds < 0 )
         {
-          if (!h->item)
-            h->item = ldap_first_entry (h->ld, h->result);
-          else
-            h->item = ldap_next_entry (h->ld, h->item);
+          log_error (_("select failed: %s\n"), strerror (errno));
+          pth_sleep (10);
+          continue;
         }
-      
-      /* Continue if we still hold an item. */
-      if (h->result && h->item)
+      /* Note that there is no need to lock the list because we always
+         add entries at the head and thus traversing the list will
+         even work if we have a context switch in waitpid (which should
+         anyway only happen with Pth's hard system call mapping).   */
+      for (ctx = wrapper_list; ctx; ctx = ctx->next)
         {
-          if (h->values)
-            ldap_value_free_len (h->values);
-          h->values = ldap_get_values_len (h->ld, h->item, h->attr);
-
-          if (h->values && opt.verbose)
-            log_info (_("found attribute `%s'\n"), h->attr);
-
-          if (!h->values)
+          /* Check whether there is any logging to be done. */
+          if (nfds && ctx->log_fd != -1 && FD_ISSET (ctx->log_fd, &read_fds))
             {
-              /* Try again without or with ";binary" .. which worked with some
-                 strange LDAP servers.  */
-              const char *s;
-              char *newattr;
-
-              s = memistr (h->attr, strlen (h->attr), ";binary");
-              if (s)
-                { 
-                  newattr = xstrdup (h->attr);
-                  newattr[s - newattr] = 0;
+              /* Note that we do not need to use pth_read here because
+                 we already know that the read won't block. */
+              do 
+                n = read (ctx->log_fd, line, sizeof line - 1);
+              while (n < 0 && errno == EINTR);
+              if (n < 0)
+                {
+                  print_log_line (ctx, NULL);
+                  log_error (_("error reading log from ldap wrapper %d: %s\n"),
+                             ctx->pid, strerror (errno));
                 }
-              else
+              else if (!n) /* EOF */
                 {
-                  newattr = xmalloc (strlen (h->attr) + 7 + 1);
-                  strcpy (stpcpy (newattr, h->attr), ";binary");
+                  print_log_line (ctx, NULL);
+                  close (ctx->log_fd);
+                  ctx->log_fd = -1;
+                }
+              else 
+                {
+                  line[n] = 0;
+                  print_log_line (ctx, line);
                 }
-
-              h->values = ldap_get_values_len (h->ld, h->item, newattr);
-              if (h->values && opt.verbose)
-                log_info (_("found attributes `%s' at second try\n"), newattr);
-              xfree (newattr);
             }
-        }
 
-      /* If there is stuff to return, jump to the top.  Note, that we
-         only handle the first item in the list of values. */
-      if (h->values && h->values[0] && h->values[0]->bv_len)
-        continue;
-
-      /* Wait for the next result. */
-      timeout.tv_sec = 0;
-      timeout.tv_usec = 0;
-    retry:
-      tout.tv_sec = 0;
-      tout.tv_usec = 100000; /* Temporary timeout after 100 ms. */
-      if (h->result)
-        {
-          ldap_msgfree (h->result);
-          h->result = NULL;
-        }
-      rc = ldap_result (h->ld, h->msgid, 0, &tout, &h->result);
-      if (!rc)
-        {
-          /* We don't take an updated timeout into account, not all
-             systems do this properly in select and we don't know whether
-             ldap_result fixes for that. We also ignore the usec which is
-             not a problem as we set opt.ldaptimeout only to full seconds.
-             The timeout is restarted with every bit of data we get. */
-          timeout.tv_usec += 100000;
-          while (timeout.tv_usec >= 1000000)
+          /* Check whether the process is still running.  */
+          if (ctx->pid != (pid_t)(-1))
             {
-              timeout.tv_sec++;
-              timeout.tv_usec -= 1000000;
-            }
-          if (timeout.tv_sec < opt.ldaptimeout.tv_sec)
-            {
-              dirmngr_tick (NULL);
-              goto retry;
+              int i, status;
+              
+              while ( (i=waitpid (ctx->pid, &status, WNOHANG)) == -1
+                      && errno == EINTR)
+                ;
+              if (i == -1)
+                log_error (_("waiting for ldap wrapper %d failed: %s\n"),
+                           (int)ctx->pid, strerror (errno));
+              else if (i)
+                {
+                  if (!WIFEXITED (status))
+                    log_info (_("ldap wrapper %d ready: terminated\n"),
+                              (int)ctx->pid);
+                  else 
+                    log_info (_("ldap wrapper %d ready: exit status %d\n"),
+                              (int)ctx->pid, WEXITSTATUS (status));
+                  ctx->ready = 1;
+                  ctx->pid = (pid_t)(-1);
+                }
             }
 
-          log_error (_("timeout while waiting for ldap_search result\n"));
-          h->err = gpg_error (GPG_ERR_TIMEOUT);
-          h->sys_err = errno = ETIMEDOUT;
-          return -1;
-        }
-      if (rc == -1)
-        {
-          log_error (_("ldap_result for an ldap_search failed: %s\n"),
-                     strerror (errno));
-          h->sys_err = errno;
-          h->err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
-          return -1;    
-        }
-      else if (rc == LDAP_RES_SEARCH_ENTRY)
-        ; /* Yep, we got a result. */
-      else if (rc == LDAP_RES_SEARCH_RESULT)
-        {
-          int errcode;
-
-          rc = ldap_parse_result (h->ld, h->result, &errcode, NULL, NULL,
-                                  NULL, NULL, 0);
-          if (rc)
-            log_error (_("ldap_parse_result failed\n"));
-          else 
-            {
-              if (errcode)
-                log_info (_("ldap search result is: %s\n"),
-                          ldap_err2string (errcode));
-            }
-          h->eof_reached = 1;
-          return 0;
-        }
-      else
-        {
-          log_error (_("unexpected message type %d "
-                       "returned for ldap_search\n"), rc);
-          h->sys_err = errno = 0;
-          h->err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
-          return -1;    
+          /* Check whether we should terminate the process. */
         }
+
+      /* Use a separate loop to check whether ready marked wrappers
+         may be removed.  We may only do so if the ksba reader object
+         is not anymore in use. */
+     again:
+      for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next)
+        if (ctx->ready && !ctx->reader)
+          {
+            if (ctx_prev)
+              ctx_prev->next = ctx->next;
+            else
+              wrapper_list = ctx->next;
+            destroy_wrapper (ctx);
+            /* We need to restart because destroy_wrapper might have
+               done a context switch. */
+            goto again;
+          }
     }
-  /*NOT REACHED*/
+  /*NOTREACHED*/
 }
 
-/* The closer function used for returning the results from a
-   attr_fetch_ldap query. */
-static int
-attr_fetch_fun_closer (void *cookie)
+
+/* This function is to be used to release a context associated with the
+   given reader object. */
+void
+ldap_wrapper_release_context (ksba_reader_t reader)
 {
-  struct attr_fetch_fun_cookie_s *h = cookie;
-  
-  if (h->values)
-    ldap_value_free_len (h->values);
-  if (h->result)
-    {
-      ldap_msgfree (h->result);
-      h->result = NULL;
-    }
-  xfree (h->attr);
-  ldap_unbind (h->ld);
-  xfree (h);
-  return 0;
+  struct wrapper_context_s *ctx;
+  int fd;
+
+  if (!reader )
+    return;
+    
+  for (ctx=wrapper_list; ctx; ctx=ctx->next)
+    if (ctx->reader == reader)
+      {
+        ctx->reader = NULL;
+        if (ctx->fd != -1)
+          {
+            fd = ctx->fd;
+            ctx->fd = -1;
+            close (fd);
+          }
+        break;
+      }
 }
 
 
 
-/* Perform an LDAP query.  Returns an gpg error code or 0 on success.
-   The function returns a new stream at R_FP. */
-static gpg_error_t
-attr_fetch_ldap_internal (const char *host, int port,
-			  const char *user, const char *pass,
-			  const char *dn, const char *attr,
-			  FILE **r_fp)
+/* This is the callback used by the ldap wrapper to feed the ksab
+   reader with the wrappers stdout.  See the description of
+   ksba_reader_set_cb for details.  */
+static int 
+reader_callback (void *cb_value, char *buffer, size_t count,  size_t *nread)
 {
-  gpg_error_t err;
-  struct timeval timeout;
-  char *attrs[2];
-  int rc;
-  int msgid;
-  LDAPMessage *result;
-  LDAP *ld;
-  struct attr_fetch_fun_cookie_s *cookie;
-
-  *r_fp = NULL;
-
-  attrs[0] = xstrdup (attr); 
-  attrs[1] = NULL;
-
-  if (DBG_LOOKUP)
-    log_debug ("fetching attribute `%s' from '%s:%d' for DN '%s'\n",
-               attr, host, port, dn);
-
-  ld = ldap_init (host, port);
-  if (!ld)
-    {
-      log_error (_("ldap_init failed for '%s:%d': %s\n"),
-                 host, port, strerror (errno));
-      /* We do not return the system error because the host might
-         simply not be available and the user is not intersted inthese
-         details. */
-      return gpg_error (GPG_ERR_NO_CRL_KNOWN);
-    }
-  msgid = ldap_simple_bind (ld, user, pass);
-  if (msgid == -1)
-    {
-      log_error (_("ldap_simple_bind failed for user '%s': %s\n"),
-                 user? user : _("[none]"), strerror (errno));
-      /* According to the documentation a variable ld->ld_errno will
-         be set.  However, that structure is private and thus we can't
-         access.  So we assume that the standard errno is used. */
-      xfree (attrs[0]);
-      ldap_unbind (ld);
-      return gpg_error (GPG_ERR_NO_CRL_KNOWN);
-    }
-
-  dirmngr_tick (NULL);
-  timeout = opt.ldaptimeout;
-  rc = ldap_result (ld, msgid, 0, &timeout, &result);
-  if (rc != LDAP_RES_BIND)
-    {
-      if (rc == -1)
-        log_error (_("ldap_result for bind failed: %s\n"), strerror (errno));
-      else if (!rc)
-        log_error (_("timeout while waiting for ldap_bind to complete\n"));
-      else
-        {
-          log_error (_("unexpected message type %d returned for ldap_bind\n"),
-                     rc);
-          ldap_msgfree (result);
-        }
-      xfree (attrs[0]);
-      ldap_unbind (ld);
-      return gpg_error (GPG_ERR_NO_CRL_KNOWN);
-    }
-  ldap_msgfree (result);
-  result = NULL;
-
-  /* Start the search. */
-  dirmngr_tick (NULL);
-  msgid = ldap_search (ld, dn, LDAP_SCOPE_BASE, "objectclass=*", attrs, 0);
-  if (msgid == -1)
-    {
-      log_error (_("ldap_search failed: %s\n"), strerror (errno));
-      xfree (attrs[0]);
-      ldap_unbind (ld);
-      return gpg_error (GPG_ERR_NO_CRL_KNOWN);
-    }
-
-  /* Setup the stream. */
-  cookie = xtrycalloc (1, sizeof *cookie);
-  if (!cookie)
+  struct wrapper_context_s *ctx = cb_value;
+  size_t nleft = count;
+
+  /* FIXME: We might want to add some internal buffering becuase the
+     ksba code does not not any buffering for itself (because a ksba
+     reader may be detached from another stream to read other data and
+     the it would be cumbersome to get back already buffered
+     stuff).  */
+
+  if (!buffer && !count && !nread)
+    return -1; /* Rewind is not supported. */
+
+  /* If we ever encountered a read error don't allow to continue and
+     possible overwrite the last error cause.  Bail out alos if the
+     file descriptor has been closed. */
+  if (ctx->fd_error || ctx->fd == -1)
     {
-      err = gpg_error_from_errno (errno);
-      xfree (attrs[0]);
-      ldap_unbind (ld);
-      return err;
+      *nread = 0;
+      return -1;
     }
 
-  cookie->ld = ld;
-  cookie->msgid = msgid;
-  cookie->attr  = attrs[0];
-  *r_fp = funopen (cookie, attr_fetch_fun_reader, NULL, NULL, 
-                   attr_fetch_fun_closer);
-  if (!*r_fp)
+  while (nleft > 0)
     {
-      err = gpg_error_from_errno (errno);
-      attr_fetch_fun_closer (cookie);
-      return err;
+      int n = pth_read ( ctx->fd, buffer, nleft );
+      if (n < 0 && errno == EINTR)
+        n = 0;
+      else if (n < 0)
+        {
+          ctx->fd_error = errno;
+          close (ctx->fd);
+          ctx->fd = -1;
+          return -1;
+        }
+      else if (!n)
+        {
+          if (nleft == count)
+            {
+              return -1; /* EOF. */
+            }
+          break; 
+        }
+      nleft -= n;
+      buffer += n;
     }
+  *nread = count - nleft;
 
   return 0;
+
 }
 
+/* Fork and exec the LDAP wrapper and returns a new libksba reader
+   object at READER.  ARGV is a NULL terminated list or argumenst for
+   the wrapper; however the function adds the program's name as the
+   first arg.  The function returns 0 on success or an error code.
 
-/* Perform an LDAP query on all configured servers.  On error the
-   error code of the last try is returned.  */
-gpg_error_t
-attr_fetch_ldap (const char *dn, const char *attr, FILE **r_fp)
-{
-  struct ldap_server_s *server;
-  gpg_error_t err = gpg_error (GPG_ERR_CONFIGURATION);
-
-  for (server = opt.ldapservers; server; server = server->next)
-    {
-      err = attr_fetch_ldap_internal (server->host, server->port,
-				     server->user, server->pass,
-				     dn, attr, r_fp);
-      if (!err)
-        break; /* Probably found a result. Ready. */
-    }
-  return err;
-}
+   We can't use LDAP directly for these reasons:
 
+   1. On some systems the LDAP library uses (indirectly) pthreads and
+      that is not compatible with PTh.
+  
+   2. It is huge library in particular if TLS comes into play.  So
+      problems with unfreed memory might turn up and we don't want
+      this in a long running daemon.
+
+   3. There is no easy way for timeouts. In particular the timeout
+      value does not work for DNS lookups (well, this is usual) and it
+      seems not to work while loading a large attribute like a
+      CRL. Having a separate process allows us to either tell than
+      process to commit suicide or have our own housekepping function
+      kill it after some time.  The latter also allows proper
+      cancellation of a query at any point.
+      
+   4. Given that we are going out to the network and usually get back
+      a long response, the frok/exec overhead is acceptable.
 
-/* Helper for the URL based LDAP query. */
+   Special hack to avoid passing a password through the command line
+   which is gloabbally visible: If the first element of ARGV is
+   "--pass" it will be removed and instead the environment variable
+   DIRMNGR_LDAP_PASS will be set to the next value of ARGV.  On modern
+   OSes the environment is not visible to other other user.  For those
+   old systems where it can't be avoided, we don't want to go into the
+   hassle of passing the password via stdin; it's just too complicated
+   and an LDAP password used for public directory lookups should be
+   that confidential.
+  */
 static gpg_error_t
-url_fetch_ldap_internal (const LDAPURLDesc *ludp, const char *attr,
-			 unsigned char **value, size_t * valuelen)
+ldap_wrapper (ksba_reader_t *reader, const char *argv[])
 {
   gpg_error_t err;
-  LDAPMessage *result;
-  LDAP *ld;
-  int rc;
+  pid_t pid;
+  int rp[2], rp2[2];
+  struct wrapper_context_s *ctx;
+
+  /* It would be too simple to connect stderr just to our logging
+     stream.  The problem is that if we are running multi-threaded
+     everything gets intermixed.  Clearly we don't want this.  So the
+     only viable solutions are either to have another thread
+     responsible for logging the messages or to add an option to the
+     wrapper modules to the logging on its own.  Given that we anyway
+     need a way to rip the child process and is best done using a
+     general ripping thread, that thread can do the logging too. */
 
-  ld = ldap_init (ludp->lud_host, ludp->lud_port);
-  if (!ld)
+  *reader = NULL;
+
+  if (pipe (rp) == -1)
     {
-      log_error (_("%s failed for `"), "ldap_init");
-      print_sanitized_string (log_get_stream (), ludp->lud_host, 0);
-      log_printf (":%d': %s\n", ludp->lud_port, strerror (errno));
-      return gpg_error (GPG_ERR_NO_CRL_KNOWN);
+      err = gpg_error_from_errno (errno);
+      log_error (_("error creating a pipe: %s\n"), strerror (errno));
+      return err;
     }
-  if (ldap_simple_bind_s (ld, 0, 0))
+  if (pipe (rp2) == -1)
     {
-      log_error (_("%s failed for `"), "ldap_simple_bind");
-      print_sanitized_string (log_get_stream (), ludp->lud_host, 0);
-      log_printf (":%d': %s\n", ludp->lud_port, strerror (errno));
-      return gpg_error (GPG_ERR_NO_CRL_KNOWN);
+      err = gpg_error_from_errno (errno);
+      log_error (_("error creating a pipe: %s\n"), strerror (errno));
+      close (rp[0]);
+      close (rp[1]);
+      return err;
     }
-
-  rc = ldap_search_st (ld, ludp->lud_dn, ludp->lud_scope,
-                       ludp->lud_filter, ludp->lud_attrs, 0,
-                       &opt.ldaptimeout, &result);
-  if (rc)
+      
+  pid = fork ();
+  if (pid == -1)
     {
-      log_error (_("%s failed for `"), "ldap_search");
-      print_sanitized_string (log_get_stream (), ludp->lud_host, 0);
-      log_printf (":%d': %s\n", ludp->lud_port, strerror (errno));
-      return gpg_error (GPG_ERR_NO_CRL_KNOWN);
+      err = gpg_error_from_errno (errno);
+      log_error (_("error forking process: %s\n"), strerror (errno));
+      close (rp[0]);
+      close (rp[1]);
+      close (rp2[0]);
+      close (rp2[1]);
+      return err;
     }
 
-  err = get_attr_from_result_ldap (ld, result,
-                                   ludp->lud_attrs? ludp->lud_attrs[0] : attr,
-                                   value, valuelen);
-  ldap_msgfree (result);
-  return err;
-}
+  if (!pid)
+    { /* Child. */
+      char *pgmname = "/home/wk/w/dirmngr/src/dirmngr_ldap"; /*FIXME*/
+      char **arg_list;
+      int n, i, j;
+      int fd;
+
+      /* Create command line argument array.  */
+      for (i=0; argv[i]; i++)
+        ;
+      arg_list = xcalloc (i+2, sizeof *arg_list);
+      arg_list[0] = strrchr (pgmname, '/');
+      if (arg_list[0])
+        arg_list[0]++;
+      else
+        arg_list[0] = pgmname;
+      for (i=0,j=1; argv[i]; i++, j++)
+        if (!i && argv[i+1] && !strcmp (*argv, "--pass"))
+          {
+            arg_list[j] = "--env-pass";
+            setenv ("DIRMNGR_LDAP_PASS", argv[1], 1 );
+            i++;
+          }
+        else
+          arg_list[j] = (char*)argv[i];
 
+      /* Connect stderr to the second pipe.  */
+      if (rp2[1] != STDERR_FILENO && dup2 (rp2[1], STDERR_FILENO) == -1)
+        {
+          log_error (_("dup2 failed in child: %s\n"), strerror (errno));
+          _exit (4);
+        }
 
-/* Add HOST and PORT to our list of LDAP servers.  Fixme: We should
-   better use an extra list of servers. */
-static void
-add_server_to_servers (const char *host, int port)
-{
-  ldap_server_t server;
-  ldap_server_t last = NULL;
-  const char *s;
+      /* Connect stdin to /dev/null.  */
+      fd = open ("/dev/null", O_RDONLY);
+      if (fd == -1)
+        {
+          log_error (_("can't open `/dev/null': %s\n"), strerror (errno));
+          _exit (4);
+        }
+      if (fd != STDIN_FILENO && dup2 (fd, STDIN_FILENO) == -1)
+        {
+          log_error (_("dup2 failed in child: %s\n"), strerror (errno));
+          _exit (4);
+        }
 
-  for (server=opt.ldapservers; server; server = server->next)
+      /* Connect stdout to the first pipe.  */
+      if (rp[1] != STDOUT_FILENO && dup2 (rp[1], STDOUT_FILENO) == -1)
+        {
+          log_error (_("dup2 failed in child: %s\n"), strerror (errno));
+          _exit (4);
+        }
+  
+      /* Close all files which will not be duped. */
+      n = sysconf (_SC_OPEN_MAX);
+      if (n < 0)
+        n = MAX_OPEN_FDS;
+      for (i=0; i < n; i++)
+        {
+          if ( i == STDIN_FILENO || i == STDOUT_FILENO || i == STDERR_FILENO)
+            continue;
+          close(i);
+        }
+      errno = 0;
+
+      execv (pgmname, arg_list);
+      log_error (_("error running `%s': %s\n"), pgmname, strerror (errno));
+      _exit (31);
+    }
+
+  /* Parent. */
+  close (rp[1]);
+  close (rp2[1]);
+
+  ctx = xtrycalloc (1, sizeof *ctx);
+  if (!ctx)
     {
-      if (!strcmp (server->host, host) && server->port == port)
-	  return; /* already in list... */
-      last = server;
+      err = gpg_error_from_errno (errno);
+      log_error (_("error allocating memory: %s\n"), strerror (errno));
+      kill (pid, SIGTERM);
+      close (rp[0]);
+      close (rp2[0]);
+      return err;
+    }
+  ctx->pid = pid;
+  ctx->fd = rp[0];
+  ctx->log_fd = rp2[0];
+
+  err = ksba_reader_new (reader);
+  if (!err)
+    err = ksba_reader_set_cb (*reader, reader_callback, ctx);
+  if (err)
+    {
+      log_error (_("error initializing reader object: %s\n"),
+                 gpg_strerror (err));
+      destroy_wrapper (ctx);
+      ksba_reader_release (*reader);
+      *reader = NULL;
+      return err;
     }
 
-  /* We assume that the host names are all supplied by our
-     configuration files and thus are sane.  To keep this assumption
-     we must reject all invalid host names. */
-  for (s=host; *s; s++)
-    if (!strchr ("abcdefghijklmnopqrstuvwxyz"
-                 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-                 "01234567890.-", *s))
-      {
-        log_error (_("invalid char 0x%02x in host name"
-                   " - not added\n"), *s);
-        return;
-      }
+  /* Hook the context into our list of running wrappers.  */
+  ctx->reader = *reader;
+  ctx->next = wrapper_list;
+  wrapper_list = ctx;
 
-  log_info (_("adding `%s:%d' to the ldap server list\n"), host, port);
-  server = xcalloc (1, sizeof *s);
-  server->host = xstrdup (host);
-  server->port = port;
-  if (last)
-    last->next = server;
-  else
-    opt.ldapservers = server;
+  return 0;
 }
 
 
-/* Perform a LDAP query using a given URL. */
-gpg_error_t
-url_fetch_ldap (const char *url, const char *attr, unsigned char **value,
-		size_t *valuelen)
+
+/* Perform an LDAP query.  Returns an gpg error code or 0 on success.
+   The function returns a new stream at R_FP. */
+static gpg_error_t
+run_ldap_wrapper (const char *host, int port, 
+                  const char *user, const char *pass,
+                  const char *dn, const char *filter, const char *attr,
+                  const char *url,
+                  ksba_reader_t *reader)
 {
-  gpg_error_t err;
-  LDAPURLDesc *ludp = NULL;
+  const char *argv[20];
+  int argc;
+  char portbuf[30];
 
-  *value = NULL;
-  *valuelen = 0;
-  if (DBG_LOOKUP)
+  *reader = NULL;
+
+  argc = 0;
+  if (pass)  /* Note, that the password most be the first item.  */
     {
-      log_debug ("url_fetch_ldap: url=`");
-      print_sanitized_string (log_get_stream (), url, 0);
-      log_printf ("' attr=`%s'\n", attr);
+      argv[argc++] = "--pass";
+      argv[argc++] = pass;
     }
-
-  if (!ldap_is_ldap_url (url))
+  if (opt.verbose)
+    argv[argc++] = "-vv";
+  if (host)
     {
-      log_error ("'");
-      print_sanitized_string (log_get_stream (), url, 0);
-      log_printf (_("' is not an LDAP URL\n"));
-      return gpg_error (GPG_ERR_INV_URI);
+      argv[argc++] = "--host";
+      argv[argc++] = host;
     }
-
-  if (ldap_url_parse (url, &ludp))
+  if (port)
     {
-      log_error ("'");
-      print_sanitized_string (log_get_stream (), url, 0);
-      log_printf (_("' is an invalid LDAP URL\n"));
-      return gpg_error (GPG_ERR_BAD_URI);
+      sprintf (portbuf, "%d", port);
+      argv[argc++] = "--port";
+      argv[argc++] = portbuf;
     }
-
-  if (ludp->lud_host)
+  if (user)
     {
-      /* URL has host part: try a simple query. */
-      if (opt.add_new_ldapservers)
-	add_server_to_servers (ludp->lud_host, ludp->lud_port);
-      err = url_fetch_ldap_internal (ludp, attr, value, valuelen);
+      argv[argc++] = "--user";
+      argv[argc++] = user;
     }
-  else
-    err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+  if (dn)
+    {
+      argv[argc++] = "--dn";
+      argv[argc++] = dn;
+    }
+  if (filter)
+    {
+      argv[argc++] = "--filter";
+      argv[argc++] = filter;
+    }
+  if (attr)
+    {
+      argv[argc++] = "--attr";
+      argv[argc++] = attr;
+    }
+  argv[argc++] = url? url : "ldap://";
+  argv[argc] = NULL;
+    
+  return ldap_wrapper (reader, argv);
+}
+
 
 
-  if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN)
+
+/* Perform a LDAP query using a given URL. On success a new ksba
+   reader is returned.  If HOST or PORT ar not 0, they are used to
+   override the values from the URL. */
+gpg_error_t
+url_fetch_ldap (const char *url, const char *host, int port,
+                ksba_reader_t *reader)
+{
+  return run_ldap_wrapper (host, port,
+                           NULL, NULL,
+                           NULL, NULL, NULL, url,
+                           reader);
+
+  /* FIXME: This option might be used for DoS attacks.  Becuase it
+     will enlarge the list of servers to consult without a limit and
+     all LDAP queries w/o a host are will then try each host in
+     turn.  */
+/*   if (opt.add_new_ldapservers) */
+/*     add_server_to_servers (ludp->lud_host, ludp->lud_port); */
+}
+
+
+
+/* Perform an LDAP query on all configured servers.  On error the
+   error code of the last try is returned.  */
+gpg_error_t
+attr_fetch_ldap (const char *dn, const char *attr, ksba_reader_t *reader)
+{
+  struct ldap_server_s *server;
+  gpg_error_t err = gpg_error (GPG_ERR_CONFIGURATION);
+
+  *reader = NULL;
+  for (server = opt.ldapservers; server; server = server->next)
     {
-      /* CRL not found so far. */
-      ldap_server_t server;
-      char *savehost;
-      int saveport;
-      
-      if (DBG_LOOKUP)
-        log_debug ("no hostname in URL or query failed; "
-                   "trying default hostnames\n");
-      savehost = ludp->lud_host;
-      saveport = ludp->lud_port;
-      for (server = opt.ldapservers; server; server = server->next)
-	{
-	  ludp->lud_host = server->host;
-	  ludp->lud_port = server->port;
-	  err = url_fetch_ldap_internal (ludp, attr, value, valuelen);
-	  if (!err)
-	    break;
-	}
-      ludp->lud_host = savehost;
-      ludp->lud_port = saveport;
+      err = run_ldap_wrapper (server->host, server->port,
+                              server->user, server->pass,
+                              dn, "objectClass=*", attr, NULL,
+                              reader);
+      if (!err)
+        break; /* Probably found a result. Ready. */
     }
-
-  ldap_free_urldesc (ludp);
   return err;
 }
 
 
+
+
 
 
 
@@ -682,6 +790,88 @@
   return result;
 }
 
+/* Take the string STRING and escape it accoring to the URL rules.
+   Retun a newly allocated string. */
+static char *
+escape4url (const char *string)
+{
+  const char *s;
+  char *buf, *p;
+  size_t n;
+
+  if (!string)
+    string = "";
+
+  for (s=string,n=0; *s; s++)
+    if (strchr (UNENCODED_URL_CHARS, *s))
+      n++;
+    else 
+      n += 3;
+  
+  buf = malloc (n+1);
+  if (!buf)
+    return NULL;
+
+  for (s=string,p=buf; *s; s++)
+    if (strchr (UNENCODED_URL_CHARS, *s))
+      *p++ = *s;
+    else 
+      {
+        sprintf (p, "%%%02X", *(const unsigned char *)s);
+        p += 3;
+      }
+  *p = 0;
+
+  return buf;
+}
+
+
+
+/* Create a LDAP URL from DN and FILTER and return it in URL.  We don't
+   need the host and port because this will be specified using the
+   override options. */
+static gpg_error_t
+make_url (char **url, const char *dn, const char *filter)
+{
+  gpg_error_t err;
+  char *u_dn, *u_filter;
+  char const attrs[] = (USERCERTIFICATE "," CACERTIFICATE);
+
+  *url = NULL;
+
+  u_dn = escape4url (dn);
+  if (!u_dn)
+      return gpg_error_from_errno (errno);
+
+  u_filter = escape4url (filter);
+  if (!u_filter)
+    {
+      err = gpg_error_from_errno (errno);
+      xfree (u_dn);
+      return err;
+    }
+  *url = malloc ( 8 + strlen (u_dn)
+                 + 1 + strlen (attrs)
+                 + 5 + strlen (u_filter) + 1 );
+  if (!*url)
+    {
+      err = gpg_error_from_errno (errno);
+      xfree (u_dn);
+      xfree (u_filter);
+      return err;
+    }
+ 
+  stpcpy (stpcpy (stpcpy (stpcpy (stpcpy (stpcpy (*url, "ldap:///"),
+                                          u_dn),
+                                  "?"),
+                          attrs),
+                  "?sub?"),
+          u_filter);
+  xfree (u_dn);
+  xfree (u_filter);
+  return 0;
+}
+
 
 /* Prepare an LDAP query to return certificates maching PATTERNS using
    the SERVER.  This function retruns an error code or 0 and a CONTEXT
@@ -691,16 +881,15 @@
                        strlist_t patterns, const ldap_server_t server)
 {
   gpg_error_t err;
-  int rc;
   const char *host;
   int port;
   const char *user;
   const char *pass;
   const char *base;
-  LDAP *ld;
-  int msgid;
-  LDAPMessage *result;
-  strlist_t query_list = NULL, *query_list_tail, sl;
+  const char *argv[50];
+  int argc;
+  char portbuf[30];
+
   
   *context = NULL;
   if (server)
@@ -717,257 +906,206 @@
   if (!base)
     base = "";
 
-  if (DBG_LOOKUP)
+  argc = 0;
+  if (pass) /* Note: Must be the first item. */
     {
-      log_debug ("start querying LDAP host=`%s:%d' base='", host, port);
-      print_sanitized_string (log_get_stream (), base, 0);
-      log_printf ("'\n");
+      argv[argc++] = "--pass";
+      argv[argc++] = pass;
+    }
+  if (opt.verbose)
+    argv[argc++] = "-vv";
+  argv[argc++] = "--multi";
+  if (host)
+    {
+      argv[argc++] = "--host";
+      argv[argc++] = host;
+    }
+  if (port)
+    {
+      sprintf (portbuf, "%d", port);
+      argv[argc++] = "--port";
+      argv[argc++] = portbuf;
+    }
+  if (user)
+    {
+      argv[argc++] = "--user";
+      argv[argc++] = user;
     }
 
-  query_list_tail = &query_list;
+
   for (; patterns; patterns = patterns->next)
     {
+      strlist_t sl;
+      char *url;
+
+      if (argc >= sizeof argv -1)
+        {
+          /* Too many patterns.  It does not make sense to allow an
+             arbitrary number of patters because the length of the
+             command line is limited anyway.  */
+          /* fixme: cleanup. */
+          return gpg_error (GPG_ERR_RESOURCE_LIMIT);
+        }
       sl = parse_one_pattern (patterns->d);
       if (!sl)
         {
           log_error (_("start_cert_fetch: invalid pattern `%s'\n"),
                      patterns->d);
-          free_strlist (query_list);
+          /* fixme: cleanup argv.  */
           return gpg_error (GPG_ERR_INV_USER_ID);
         }
-      *query_list_tail = sl;
-      query_list_tail = &sl;
+      if ((sl->flags & 1))
+        err = make_url (&url, sl->d, "objectClass=*");
+      else
+        err = make_url (&url, base, sl->d);
+      free_strlist (sl);
+      if (err)
+        {
+          /* fixme: cleanup argv. */
+          return err;
+        }
+      argv[argc++] = url;
     }
+  argv[argc] = NULL;
 
+  *context = xtrycalloc (1, sizeof **context);
+  if (!*context)
+    return gpg_error_from_errno (errno);
 
-  ld = ldap_init (host, port);
-  if (!ld)
-    {
-      err = gpg_error_from_errno (errno);
-      log_error (_("%s failed for `"), "ldap_init");
-      print_sanitized_string (log_get_stream (), host, 0);
-      log_printf (":%d': %s\n", port, strerror (errno));
-      free_strlist (query_list);
-      return err;
-    }
+  err = ldap_wrapper (&(*context)->reader, argv);
 
-  msgid = ldap_simple_bind (ld, user, pass);
-  if (msgid == -1)
+  if (err)
     {
-      err = gpg_error_from_errno (errno);
-      log_error (_("%s failed for `"), "ldap_simple_bind");
-      print_sanitized_string (log_get_stream (), host, 0);
-      log_printf (":%d' user `%s': %s\n",
-                  port, user? user : _("[none]"),strerror (errno));
-      ldap_unbind (ld);
-      free_strlist (query_list);
-      return err;
+      xfree (*context);
+      *context = NULL;
     }
 
-  rc = ldap_result (ld, msgid, 0, &opt.ldaptimeout, &result);
-  if (rc != LDAP_RES_BIND)
-    {
-      if (rc == -1 || !rc)
-        {
-          err = rc? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_TIMEOUT);
-          log_error (_("%s failed for `"), "ldap_result/bind");
-          print_sanitized_string (log_get_stream (), host, 0);
-          log_printf (":%d' user `%s': %s\n",
-                      port, user? user : _("[none]"), gpg_strerror (err));
-        }
-      else
-        {
-          log_error (_("unexpected message type %d returned for ldap_bind\n"),
-                     rc);
-          ldap_msgfree (result);
-          err = gpg_error (GPG_ERR_BUG);
-        }
-      ldap_unbind (ld);
-      free_strlist (query_list);
-      return err;
-    }
-  ldap_msgfree (result);
-
-  *context = xcalloc (1, sizeof **context);
-  (*context)->ld = ld;
-  (*context)->query_list = query_list;
-  (*context)->base = xstrdup (base);
-
   return 0;
 }
 
-/* Fixme: This is a strange helper.  It does for example never return an
-   error code but instead sets a last value to whatever semantics LDAP
-   has. */
-static int
-get_cert_ldap (cert_fetch_context_t context,
-               unsigned char **value, size_t * valuelen, int *last)
-{
-  int try_ca = 0;
-  char *dn;
-
-  *last = 0;
-  if (DBG_LOOKUP)
-    {
-      dn = ldap_get_dn (context->ld, context->result);
-      log_debug ("get_cert_ldap: examining DN=`");
-      print_sanitized_string (log_get_stream (), dn?dn:"[none]", 0);
-      log_printf ("'\n");
-    }
-
- again:
-  if (!context->bervalues)
-    {
-      context->bercount = 0;
-      context->bervalues = ldap_get_values_len (context->ld,
-                                                context->result, 
-                                                try_ca? CACERTATTR
-                                                : CERTATTR);
-      if (!context->bervalues)
-	context->bervalues = ldap_get_values_len (context->ld,
-                                                  context->result,
-                                                  try_ca? CACERTATTRBIN
-                                                        : CERTATTRBIN);
-    }
-
-  if (context->bervalues)
-    {
-      if (DBG_LOOKUP)
-        log_debug ("get_cert_ldap: got a %sCertificate\n", try_ca? "ca":"user");
-
-      *valuelen = context->bervalues[context->bercount]->bv_len;
-      *value = xmalloc (*valuelen);
-      memcpy (*value, context->bervalues[context->bercount]->bv_val, *valuelen);
-      context->bercount++;
-      if (!context->bervalues[context->bercount])
-	{
-	  ldap_value_free_len (context->bervalues);
-	  context->bervalues = 0;
-	  *last = 1;
-	}
-    }
-  else if (!try_ca)
-    {
-      try_ca = 1;
-      goto again;
-    }
-  else
+
+/* Read a fixed amount of data from READER into BUFFER.  */
+static gpg_error_t
+read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
+{
+  gpg_error_t err;
+  size_t nread;
+  
+  while (count)
     {
-      *last = 1;
+      err = ksba_reader_read (reader, buffer, count, &nread);
+      if (err)
+        return err;
+      buffer += nread;
+      count -= nread;
     }
-
   return 0;
 }
 
 
 /* Fetch the next certificate. Return 0 on success, GPG_ERR_EOF
-   when no (more) certifciates are available or any other error
+   if no (more) certificates are available or any other error
    code. */
 gpg_error_t
 fetch_next_cert_ldap (cert_fetch_context_t context,
-                      unsigned char **value, size_t *valuelen)
+  unsigned char **value, size_t *valuelen)
 {
   gpg_error_t err;
-  int last;
-  int rc;
+  unsigned char hdr[5];
+  char *p, *pend;
+  int n;
+  int okay = 0;
   int truncated = 0;
-  char *attrs[5];
 
-  attrs[0] = CERTATTR;
-  attrs[1] = CERTATTRBIN;
-  attrs[2] = CACERTATTR;  
-  attrs[3] = CACERTATTRBIN;
-  attrs[4] = NULL;
-
-
- next:
   *value = NULL;
   *valuelen = 0;
 
-  if (!context->result)
+  err = 0;
+  while (!err)
     {
-      context->query = context->query? context->query->next
-                                     : context->query_list;
-      if (!context->query)
-        return gpg_error (GPG_ERR_EOF); /* No more patterns. */
-
-      if ((context->query->flags & 1))
-        {
-          if (DBG_LOOKUP)
-            log_debug ("ldap_search_st using base `%s'\n", context->query->d);
-          rc = ldap_search_st (context->ld, context->query->d,
-                               LDAP_SCOPE_SUBTREE,
-                               "objectClass=*", attrs, 0,
-                               &opt.ldaptimeout, &context->result);
-        }
-      else
+      err = read_buffer (context->reader, hdr, 5);
+      if (err)
+        break;
+      n = (hdr[1] << 24)|(hdr[2]<<16)|(hdr[3]<<8)|hdr[4];
+      if (*hdr == 'V' && okay)
         {
-          if (DBG_LOOKUP)
-            log_debug ("ldap_search_st using filter `%s', base `%s'\n",
-			context->query->d, context->base);
-          rc = ldap_search_st (context->ld, context->base,
-                               LDAP_SCOPE_SUBTREE,
-                               context->query->d, attrs, 0,
-                               &opt.ldaptimeout, &context->result);
+          *value = xtrymalloc (n);
+          if (!*value)
+            return gpg_error_from_errno (errno);
+          *valuelen = n; 
+          err = read_buffer (context->reader, *value, n);
+          break; /* Ready or error.  */
         }
-
-      if (rc)
+      else if (!n && *hdr == 'A')
+        okay = 0;
+      else if (n)
         {
-          if (rc == LDAP_SIZELIMIT_EXCEEDED)
+          if (n > context->tmpbufsize)
             {
-              truncated = 1;
-              log_info (_("ldap_search hit the size limit of the server\n"));
-              /* Fixme: Shouldn't we bail out here? */
-            }
-          else if (rc == LDAP_NO_SUCH_OBJECT)
-            {
-              /* Ignore this error. */
-              if (DBG_LOOKUP)
-                log_debug ("ldap_search returned with no object\n");
-              /* Fixme: Shouldn't we bail out here? */
-            }
-          else
+              xfree (context->tmpbuf);
+              context->tmpbufsize = 0;
+              context->tmpbuf = xtrymalloc (n+1);
+              if (!context->tmpbuf)
+                return gpg_error_from_errno (errno);
+              context->tmpbufsize = n;
+            }  
+          err = read_buffer (context->reader, context->tmpbuf, n);
+          if (err)
+            break;
+          if (*hdr == 'A')
             {
-              log_error (_("ldap_search failed: %s\n"),
-                         ldap_err2string (ldap_result2error
-                                          (context->ld,
-                                           context->result, 0)));
-              /* FIXME we should write a ldap to gpg error mapper but
-                 before we can do that we need to dig deeper into the
-                 LDAP API definition. */
-              return gpg_error (GPG_ERR_GENERAL);
+              p = context->tmpbuf;
+              p[n] = 0; /*(we allocated one extra byte for this.)*/
+              if ( (pend = strchr (p, ';')) )
+                *pend = 0; /* Strip off the extension. */
+              if (!ascii_strcasecmp (p, USERCERTIFICATE))
+                {
+                  if (DBG_LOOKUP)
+                    log_debug ("fetch_next_cert_ldap: got attribute `%s'\n",
+                               USERCERTIFICATE);
+                  okay = 1;
+                }
+              else if (!ascii_strcasecmp (p, CACERTIFICATE))
+                {
+                  if (DBG_LOOKUP)
+                    log_debug ("fetch_next_cert_ldap: got attribute `%s'\n",
+                               CACERTIFICATE);
+                  okay = 1;
+                }
+              else
+                {
+                  if (DBG_LOOKUP)
+                    log_debug ("fetch_next_cert_ldap: got attribute `%s'"
+                               " -  ignored\n", p);
+                  okay = 0;
+                }
             }
         }
-      context->result = ldap_first_entry (context->ld, context->result);
     }
-
-
-  if (!context->result)
-    goto next;
-
-  err = get_cert_ldap (context, value, valuelen, &last);
+  
   if (err)
-    return err;
-
-  if (last)
-    context->result = ldap_next_entry (context->ld, context->result);
+    {
+      xfree (*value);
+      *value = NULL;
+      *valuelen = 0;
+    }
 
-  if (!*value)
-    goto next;
+  return err;
 
-  return truncated? gpg_error (GPG_ERR_TRUNCATED) : 0;
+  
+/*    if (rc == LDAP_SIZELIMIT_EXCEEDED) */
+/*    { */
+  /*     truncated = 1; */
+/*       log_info (_("ldap_search hit the size limit of the server\n")); */
 }
 
+
 void
 end_cert_fetch_ldap (cert_fetch_context_t context)
 {
   if (context)
     {
-      free_strlist (context->query_list);
-      xfree (context->base);
-      ldap_unbind (context->ld);
+      xfree (context->tmpbuf);
       xfree (context);
-
-      /* fixme: do we need to release the BERVALUES? */
     }
 }
Index: dirmngr/src/maperror.c
diff -u /dev/null dirmngr/src/maperror.c:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/maperror.c	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,74 @@
+/* maperror.c  -  error code mapping for assuan
+ *	Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#include <config.h>
+
+#include <gpg-error.h>
+#include <assuan.h>
+
+#include "util.h"
+
+/* Map Assuan error code ERR to an GPG_ERR_ code.  We need to
+   distinguish between genuine (and legacy) Assuan error codes and
+   application error codes shared with all GnuPG modules.  The rule is
+   simple: All errors with a gpg_err_source of UNKNOWN are genuine;
+   all other Assuan codes are passed verbatim through. */
+gpg_error_t
+map_assuan_err (int err)
+{
+  gpg_err_code_t ec;
+
+  if (gpg_err_source (err))
+    return err;
+
+  switch (err)
+    {
+    case -1:                     ec = GPG_ERR_EOF; break;
+    case 0:                      ec = 0; break;
+
+    case ASSUAN_Canceled:        ec = GPG_ERR_CANCELED; break;
+    case ASSUAN_Invalid_Index:   ec = GPG_ERR_INV_INDEX; break;
+
+    case ASSUAN_Not_Implemented: ec = GPG_ERR_NOT_IMPLEMENTED; break;
+    case ASSUAN_Server_Fault:    ec = GPG_ERR_ASSUAN_SERVER_FAULT; break;
+    case ASSUAN_No_Public_Key:   ec = GPG_ERR_NO_PUBKEY; break;
+    case ASSUAN_No_Secret_Key:   ec = GPG_ERR_NO_SECKEY; break;
+
+    case ASSUAN_Cert_Revoked:    ec = GPG_ERR_CERT_REVOKED; break;
+    case ASSUAN_No_CRL_For_Cert: ec = GPG_ERR_NO_CRL_KNOWN; break;       
+    case ASSUAN_CRL_Too_Old:     ec = GPG_ERR_CRL_TOO_OLD; break;        
+
+    case ASSUAN_Not_Trusted:     ec = GPG_ERR_NOT_TRUSTED; break;
+
+    case ASSUAN_Card_Error:      ec = GPG_ERR_CARD; break;
+    case ASSUAN_Invalid_Card:    ec = GPG_ERR_INV_CARD; break;
+    case ASSUAN_No_PKCS15_App:   ec = GPG_ERR_NO_PKCS15_APP; break;
+    case ASSUAN_Card_Not_Present: ec= GPG_ERR_CARD_NOT_PRESENT; break;
+    case ASSUAN_Not_Confirmed:   ec = GPG_ERR_NOT_CONFIRMED; break;
+    case ASSUAN_Invalid_Id:      ec = GPG_ERR_INV_ID; break;
+
+    default:
+      ec = err < 100? GPG_ERR_ASSUAN_SERVER_FAULT : GPG_ERR_ASSUAN;
+      break;
+    }
+  return gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, ec);
+}
+
+
Index: dirmngr/src/misc.c
diff -u dirmngr/src/misc.c:1.7 dirmngr/src/misc.c:1.8
--- dirmngr/src/misc.c:1.7	Tue Apr  6 10:24:57 2004
+++ dirmngr/src/misc.c	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,5 @@
 /* misc.c - miscellaneous
- *	Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *	Copyright (C) 2002 Klarälvdalens Datakonsult AB
  *      Copyright (C) 2002, 2003, 2004 Free Software Foundation, Inc.
  *
  * This file is part of DirMngr.
@@ -468,3 +468,104 @@
   return buf;
 }
 
+
+/* Dump the serial number SERIALNO to the log stream.  */
+void
+dump_serial (ksba_sexp_t serialno)
+{
+  char *p;
+
+  p = serial_hex (serialno);
+  log_printf ("%s", p?p:"?");
+  xfree (p);
+}
+
+
+/* Dump the ISO time T to the log stream without a LF.  */
+void
+dump_isotime (dirmngr_isotime_t t)
+{
+  if (!t || !*t)
+    log_printf (_("[none]"));
+  else
+    log_printf ("%.4s-%.2s-%.2s %.2s:%.2s:%s",
+                t, t+4, t+6, t+9, t+11, t+13);
+}
+
+
+/* Dump STRING to the log file but choose the best readable
+   format.  */
+void
+dump_string (const char *string)
+{
+
+  if (!string)
+    log_printf ("[error]");
+  else
+    {
+      const unsigned char *s;
+
+      for (s=string; *s; s++)
+        {
+          if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0))
+            break;
+        }
+      if (!*s && *string != '[')
+        log_printf ("%s", string);
+      else
+        {
+          log_printf ( "[ ");
+          log_printhex (NULL, string, strlen (string));
+          log_printf ( " ]");
+        }
+    }
+}
+
+
+/* Dump an KSBA cert object to the log stream. Prefix the output with
+   TEXT.  This is used for debugging. */
+void 
+dump_cert (const char *text, ksba_cert_t cert)
+{
+  ksba_sexp_t sexp;
+  char *p;
+  ksba_isotime_t t;
+
+  log_debug ("BEGIN Certificate `%s':\n", text? text:"");
+  if (cert)
+    {
+      sexp = ksba_cert_get_serial (cert);
+      p = serial_hex (sexp);
+      log_debug ("     serial: %s\n", p?p:"?");
+      xfree (p);
+      ksba_free (sexp);
+
+      ksba_cert_get_validity (cert, 0, t);
+      log_debug ("  notBefore: ");
+      dump_isotime (t);
+      log_printf ("\n");
+      ksba_cert_get_validity (cert, 1, t);
+      log_debug ("   notAfter: ");
+      dump_isotime (t);
+      log_printf ("\n");
+
+      p = ksba_cert_get_issuer (cert, 0);
+      log_debug ("     issuer: ");
+      dump_string (p);
+      ksba_free (p);
+      log_printf ("\n");
+    
+      p = ksba_cert_get_subject (cert, 0);
+      log_debug ("    subject: ");
+      dump_string (p);
+      ksba_free (p);
+      log_printf ("\n");
+
+      log_debug ("  hash algo: %s\n", ksba_cert_get_digest_algo (cert));
+
+      p = get_fingerprint_hexstring (cert);
+      log_debug ("  SHA1 Fingerprint: %s\n", p);
+      xfree (p);
+    }
+  log_debug ("END Certificate\n");
+}
Index: dirmngr/src/misc.h
diff -u dirmngr/src/misc.h:1.7 dirmngr/src/misc.h:1.8
--- dirmngr/src/misc.h:1.7	Tue Apr  6 10:24:57 2004
+++ dirmngr/src/misc.h	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,5 @@
 /* misc.h - miscellaneous
- *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *      Copyright (C) 2002 Klarälvdalens Datakonsult AB
  *
  * This file is part of DirMngr.
  *
@@ -86,6 +86,23 @@
 char *get_fingerprint_hexstring (ksba_cert_t cert);
 
 
+/* Dump the serial number SERIALNO to the log stream.  */
+void dump_serial (ksba_sexp_t serialno);
+
+/* Dump the ISO time T to the log stream without a LF.  */
+void dump_isotime (dirmngr_isotime_t t);
+
+/* Dump STRING to the log file but choose the best readable
+   format.  */
+void dump_string (const char *string);
+
+/* Dump an KSBA cert object to the log stream. Prefix the output with
+   TEXT.  This is used for debugging. */
+void dump_cert (const char *text, ksba_cert_t cert);
+
+
+
+
 #ifdef HAVE_FOPENCOOKIE
 /* We have to implement funopen in terms of glibc's fopencookie. */
 FILE *funopen(void *cookie,
Index: dirmngr/src/no-libgcrypt.c
diff -u /dev/null dirmngr/src/no-libgcrypt.c:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/no-libgcrypt.c	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,107 @@
+/* no-libgcrypt.c - Replacement functions for libgcrypt.
+ *	Copyright (C) 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "../jnlib/logging.h"
+
+#include "i18n.h"
+#include "no-libgcrypt.h"
+
+
+/* Replace libgcrypt's malloc functions which are used by
+   ../jnlib/libjnlib.a .  util.h defines macros to map them to xmalloc
+   etc. */
+static void
+out_of_core (void)
+{
+  log_fatal (_("error allocating enough memory: %s\n"), strerror (errno));
+}
+
+
+void *
+gcry_malloc (size_t n)
+{
+  return malloc (n);
+}
+
+void *
+gcry_xmalloc (size_t n)
+{
+  void *p = malloc (n);
+  if (!p)
+    out_of_core ();
+  return p;
+}
+
+
+void *
+gcry_realloc (void *a, size_t n)
+{
+  return realloc (a, n);
+}
+
+void *
+gcry_xrealloc (void *a, size_t n)
+{
+  void *p = realloc (a, n);
+  if (!p)
+    out_of_core ();
+  return p;
+}
+
+
+
+void *
+gcry_calloc (size_t n, size_t m)
+{
+  return calloc (n, m);
+}
+
+void *
+gcry_xcalloc (size_t n, size_t m)
+{
+  void *p = calloc (n, m);
+  if (!p)
+    out_of_core ();
+  return p;
+}
+
+
+char *
+gcry_xstrdup (const char *string)
+{
+  void *p = malloc (strlen (string)+1);
+  if (!p)
+    out_of_core ();
+  strcpy( p, string );
+  return p;
+}
+
+void
+gcry_free (void *a)
+{
+  if (a)
+    free (a);
+}
Index: dirmngr/src/no-libgcrypt.h
diff -u /dev/null dirmngr/src/no-libgcrypt.h:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/no-libgcrypt.h	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,33 @@
+/* no-libgcrypt.h - Replacement functions for libgcrypt.
+ *	Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * 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
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * 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
+ */
+
+#ifndef NO_LIBGCRYPT_H
+#define NO_LIBGCRYPT_H
+
+void *gcry_malloc (size_t n);
+void *gcry_xmalloc (size_t n);
+void *gcry_realloc (void *a, size_t n);
+void *gcry_xrealloc (void *a, size_t n);
+void *gcry_calloc (size_t n, size_t m);
+void *gcry_xcalloc (size_t n, size_t m);
+char *gcry_xstrdup (const char *string);
+void gcry_free (void *a);
+
+#endif /*NO_LIBGCRYPT_H*/
Index: dirmngr/src/server.c
diff -u dirmngr/src/server.c:1.42 dirmngr/src/server.c:1.43
--- dirmngr/src/server.c:1.42	Thu Apr 29 19:09:58 2004
+++ dirmngr/src/server.c	Tue Nov 16 19:24:35 2004
@@ -1,5 +1,5 @@
 /* dirmngr.c - LDAP access
- *	Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *	Copyright (C) 2002 Klarälvdalens Datakonsult AB
  *      Copyright (C) 2003, 2004 g10 Code GmbH
  *
  * This file is part of DirMngr.
@@ -37,6 +37,11 @@
 #include "crlfetch.h"
 #include "dirmngr.h"
 #include "ocsp.h"
+#include "certcache.h"
+
+/* To avoid DoS attacks we limit the size of a certificate to
+   something reasonable. */
+#define MAX_CERT_LENGTH (8*1024)
 
 #define PARM_ERROR(t) assuan_set_error (ctx, ASSUAN_Parameter_Error, (t))
 
@@ -48,57 +53,10 @@
 /* Data used to associate an Assuan context with local server data */
 struct server_local_s {
   ASSUAN_CONTEXT assuan_ctx;
-  crl_cache_t     crl_cache;
   int message_fd;
 };
 
 
-/* Map Assuan error code ERR to an GPG_ERR_ code.  We need to
-   distinguish between genuine (and legacy) Assuan error codes and
-   application error codes shared with all GnuPG modules.  The rule is
-   simple: All errors with a gpg_err_source of UNKNOWN are genuine;
-   all other Assuan codes are passed verbatim through. */
-static gpg_error_t
-map_assuan_err (int err)
-{
-  gpg_err_code_t ec;
-
-  if (gpg_err_source (err))
-    return err;
-
-  switch (err)
-    {
-    case -1:                     ec = GPG_ERR_EOF; break;
-    case 0:                      ec = 0; break;
-
-    case ASSUAN_Canceled:        ec = GPG_ERR_CANCELED; break;
-    case ASSUAN_Invalid_Index:   ec = GPG_ERR_INV_INDEX; break;
-
-    case ASSUAN_Not_Implemented: ec = GPG_ERR_NOT_IMPLEMENTED; break;
-    case ASSUAN_Server_Fault:    ec = GPG_ERR_ASSUAN_SERVER_FAULT; break;
-    case ASSUAN_No_Public_Key:   ec = GPG_ERR_NO_PUBKEY; break;
-    case ASSUAN_No_Secret_Key:   ec = GPG_ERR_NO_SECKEY; break;
-
-    case ASSUAN_Cert_Revoked:    ec = GPG_ERR_CERT_REVOKED; break;
-    case ASSUAN_No_CRL_For_Cert: ec = GPG_ERR_NO_CRL_KNOWN; break;       
-    case ASSUAN_CRL_Too_Old:     ec = GPG_ERR_CRL_TOO_OLD; break;        
-
-    case ASSUAN_Not_Trusted:     ec = GPG_ERR_NOT_TRUSTED; break;
-
-    case ASSUAN_Card_Error:      ec = GPG_ERR_CARD; break;
-    case ASSUAN_Invalid_Card:    ec = GPG_ERR_INV_CARD; break;
-    case ASSUAN_No_PKCS15_App:   ec = GPG_ERR_NO_PKCS15_APP; break;
-    case ASSUAN_Card_Not_Present: ec= GPG_ERR_CARD_NOT_PRESENT; break;
-    case ASSUAN_Not_Confirmed:   ec = GPG_ERR_NOT_CONFIRMED; break;
-    case ASSUAN_Invalid_Id:      ec = GPG_ERR_INV_ID; break;
-
-    default:
-      ec = err < 100? GPG_ERR_ASSUAN_SERVER_FAULT : GPG_ERR_ASSUAN;
-      break;
-    }
-  return gpg_err_make (GPG_ERR_SOURCE_UNKNOWN, ec);
-}
-
 /* Map GPG_ERR_xx error codes to Assuan status codes */
 static int
 map_to_assuan_status (int rc)
@@ -169,14 +127,8 @@
   else
     buf = xstrdup (command);
 
-  /* FIXME: When changed to run as a system wide server, we should
-     setup a maxlen limit for the certificate.  The problem is that
-     libassuan allocates a buffer of that size in advance which we
-     usually don't need. As long as dirmngr is used in server mode we
-     don't need to care about it because the user can toast his
-     applications in many other ways.*/
   rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
-                       &value, &valuelen, 0);
+                       &value, &valuelen, MAX_CERT_LENGTH);
   xfree (buf);
   if (rc)
     {
@@ -184,6 +136,12 @@
       return NULL;
     }
   
+  if (!valuelen)
+    {
+      xfree (value);
+      return NULL;
+    }
+
   rc = ksba_cert_new (&cert);
   if (!rc)
     {
@@ -236,70 +194,37 @@
 }
 
 
-/* Ask the client to return the certificate asscociated with the
-   current command. This is sometimes needed because the client usually
-   sends us just the cert ID, assuming that the request can be
-   satisfied from the cache, where the cert ID is used as key. */
+/* Locate the corresponding CRL for the certificate CERT, read and
+   verify the CRL and store it in the cache.  */
 static int
-inquire_cert_and_load_crl (assuan_context_t ctx)
+reload_crl (ctrl_t ctrl, ksba_cert_t cert) 
 {
-  ctrl_t ctrl = assuan_get_pointer (ctx);
   gpg_error_t err;
-  assuan_error_t ae;
-  FILE *crl_fp = NULL;
-  unsigned char *value = NULL;
-  size_t valuelen; 
+  ksba_reader_t reader = NULL;
   char *issuer = NULL;
-  ksba_cert_t issuer_cert = NULL;
-  int seq;
-  int found_dist_point = 0;
   ksba_name_t distpoint = NULL;
   ksba_name_t issuername = NULL;
   char *distpoint_uri = NULL;
   char *issuername_uri = NULL;
+  int any_dist_point = 0;
+  int seq;
 
-  ae = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0);
-  if (ae)
-    return map_assuan_err (ae);
-
-/*   { */
-/*     FILE *fp = fopen ("foo.der", "r"); */
-/*     value = xmalloc (2000); */
-/*     valuelen = fread (value, 1, 2000, fp); */
-/*     fclose (fp); */
-/*   } */
-
-  if (!valuelen) /* No data returned; return a comprehensible error. */
-    return gpg_error (GPG_ERR_MISSING_CERT);
-
-  err = ksba_cert_new (&issuer_cert);
-  if (err)
-    goto leave;
-  err = ksba_cert_init_from_mem (issuer_cert, value, valuelen);
-  if(err)
-    goto leave;
-
-  xfree (value); 
-  value = NULL;
-
-  issuer = ksba_cert_get_issuer (issuer_cert, 0);
-  if (!issuer) 
-    return gpg_error (GPG_ERR_BAD_CERT); /* No issuer in certificate. */
-
-  /* Loop over all distribution points, get the CRLs and put them
-     into the cache. */
+  /* Loop over all distribution points, get the CRLs and put them into
+     the cache. */
   if (DBG_X509)
     log_debug ("checking distribution points\n");
   seq = 0;
-  while ( !(err = ksba_cert_get_crl_dist_point (issuer_cert, seq++,
+  while ( !(err = ksba_cert_get_crl_dist_point (cert, seq++,
                                                 &distpoint,
                                                 &issuername, NULL )))
     {
       if (!distpoint && !issuername)
         {
           if (DBG_X509)
-            log_debug ("no issuername and no distpoint\n");
-          break; /* Not allowed, so don't keep trying. */
+            log_debug ("no issuer name and no distribution point\n");
+          break; /* Not allowed; i.e. an invalid certificate.  We give
+                    up here and hope that the default method returns a
+                    suitable CRL. */
         }
 
       /* Get the URIs. */
@@ -309,18 +234,16 @@
       issuername_uri = ksba_name_get_uri (issuername, 0); 
       ksba_name_release (distpoint); distpoint = NULL;
       ksba_name_release (issuername); issuername = NULL;
-      found_dist_point = 1;
+      any_dist_point = 1;
       
-      xfree (value); value = NULL;
-      err = crl_fetch (distpoint_uri, &crl_fp);
+      err = crl_fetch (distpoint_uri, &reader);
       if (err)
         {
           log_error (_("crl_fetch via DP failed: %s\n"), gpg_strerror (err));
           goto leave;
         }
       
-      err = crl_cache_insert (ctrl, ctrl->server_local->crl_cache,
-                              distpoint_uri, crl_fp); 
+      err = crl_cache_insert (ctrl, distpoint_uri, reader); 
       if (err)
         {
           log_error (_("crl_cache_insert via DP failed: %s\n"),
@@ -332,18 +255,26 @@
     err = 0;
 
   /* If we did not found any distpoint, try something reasonable. */
-  if (!found_dist_point )
+  if (!any_dist_point )
     {
       if (DBG_X509)
         log_debug ("no distribution point - trying issuer name\n");
       
-      if (crl_fp)
+      if (reader)
+        {
+          crl_close_reader (reader);
+          reader = NULL;
+        }
+
+      issuer = ksba_cert_get_issuer (cert, 0);
+      if (!issuer) 
         {
-          fclose (crl_fp);
-          crl_fp = NULL;
+          log_error ("oops: issuer missing in certificate\n");
+          err = gpg_error (GPG_ERR_INV_CERT_OBJ); 
+          goto leave;
         }
-      xfree (value); value = NULL;
-      err = crl_fetch_default (issuer, &crl_fp);
+
+      err = crl_fetch_default (issuer, &reader);
       if (err)
           {
             log_error (_("crl_fetch via issuer failed: %s\n"),
@@ -351,8 +282,7 @@
             goto leave;
           }
 
-      err = crl_cache_insert (ctrl, ctrl->server_local->crl_cache,
-                             "default location(s)", crl_fp);
+      err = crl_cache_insert (ctrl, "default location(s)", reader);
       if (err)
         {
           log_error (_("crl_cache_insert via issuer failed: %s\n"),
@@ -362,14 +292,58 @@
     }
 
  leave:
-  if (crl_fp)
-    fclose (crl_fp);
+  if (reader)
+    crl_close_reader (reader);
   xfree (distpoint_uri);
   xfree (issuername_uri);
   ksba_name_release (distpoint); 
   ksba_name_release (issuername); 
   ksba_free (issuer);
-  ksba_cert_release (issuer_cert);
+  return err;
+}
+
+
+
+/* Ask the client to return the certificate asscociated with the
+   current command. This is sometimes needed because the client usually
+   sends us just the cert ID, assuming that the request can be
+   satisfied from the cache, where the cert ID is used as key. */
+static int
+inquire_cert_and_load_crl (assuan_context_t ctx)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  assuan_error_t ae;
+  unsigned char *value = NULL;
+  size_t valuelen; 
+  ksba_cert_t cert = NULL;
+
+  ae = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0);
+  if (ae)
+    return map_assuan_err (ae);
+
+/*   { */
+/*     FILE *fp = fopen ("foo.der", "r"); */
+/*     value = xmalloc (2000); */
+/*     valuelen = fread (value, 1, 2000, fp); */
+/*     fclose (fp); */
+/*   } */
+
+  if (!valuelen) /* No data returned; return a comprehensible error. */
+    return gpg_error (GPG_ERR_MISSING_CERT);
+
+  err = ksba_cert_new (&cert);
+  if (err)
+    goto leave;
+  err = ksba_cert_init_from_mem (cert, value, valuelen);
+  if(err)
+    goto leave;
+  xfree (value); value = NULL;
+
+  err = reload_crl (ctrl, cert);
+
+ leave:
+  ksba_cert_release (cert);
   xfree (value);
   return err;
 }
@@ -459,8 +433,7 @@
     }
   else
     {
-      switch (crl_cache_isvalid (ctrl->server_local->crl_cache,
-                                 issuerhash, serialno,
+      switch (crl_cache_isvalid (issuerhash, serialno,
                                  ctrl->force_crl_refresh))
         {
         case CRL_CACHE_VALID:
@@ -493,6 +466,256 @@
 }
 
 
+/* If the line contains a SHA-1 fingerprint as the first argument,
+   return the FPR vuffer on success.  The function checks that the
+   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. 
+   FPR must be a caller provided buffer of at least 20 bytes.
+
+   Note that colons within the fingerprint are allowed to separate 2
+   hex digits; this allows for easier cutting and pasting using the
+   usual fingerprint rendering.
+*/
+static unsigned char *
+get_fingerprint_from_line (const char *line, unsigned char *fpr)
+{
+  const char *s;
+  int i;
+
+  for (s=line, i=0; *s && *s != ' '; s++ )
+    {
+      if ( hexdigitp (s) && hexdigitp (s+1) )
+        {
+          if ( i >= 20 )
+            return NULL;  /* Fingerprint too long.  */
+          fpr[i++] = xtoi_2 (s);
+          s++;
+        }
+      else if ( *s != ':' )
+        return NULL; /* Invalid.  */
+    }
+  if ( i != 20 )
+    return NULL; /* Fingerprint to short.  */
+  return fpr;
+}
+
+
+
+/* CHECKCRL [<fingerprint>]
+
+   Check whether the certificate with FINGERPRINT (SHA-1 hash of the
+   entire X.509 certificate blob) is valid or not by consulting the
+   CRL responsible for this certificate.  If the fingerprint has not
+   been given or the certificate is not know, the function 
+   inquires the certficate using the
+
+      INQUIRE TARGETCERT
+
+   and the caller is expected to return the certificate for the
+   request (which should match FINGERPRINT) as a binary blob.
+   Processing then takes place without further interaction; in
+   particular dirmngr tries to locate other required certificate by
+   its own mechanism which includes a local certificate store as well
+   as a list of trusted root certificates.
+
+   The return value is the usual gpg-error code or 0 for ducesss;
+   i.e. the certificate validity has been confirmed by a valid CRL.
+*/
+static int
+cmd_checkcrl (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  gpg_error_t err;
+  unsigned char fprbuffer[20], *fpr;
+  unsigned char issuerhash[20];
+  char issuerhash_hex[41];
+  ksba_cert_t cert;
+  char *serialno = NULL;
+
+  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.  */
+      assuan_error_t ae;
+      unsigned char *value = NULL;
+      size_t valuelen; 
+      
+      ae = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
+                           &value, &valuelen, MAX_CERT_LENGTH);
+      if (ae)
+        {
+          log_error (_("assuan_inquire failed: %s\n"), assuan_strerror (ae));
+          err = map_assuan_err (ae);
+          goto leave;
+        }
+  
+      if (!valuelen) /* No data returned; return a comprehensible error. */
+        err = gpg_error (GPG_ERR_MISSING_CERT);
+      else
+        {
+          err = ksba_cert_new (&cert);
+          if (!err)
+            err = ksba_cert_init_from_mem (cert, value, valuelen);
+        }
+      xfree (value);
+      if(err)
+        goto leave;
+    }
+
+  assert (cert);
+
+  /* Compute the hash value of the issuer name.  */
+  {
+    char *tmp;
+    int i;
+
+    tmp = ksba_cert_get_issuer (cert, 0);
+    if (!tmp)
+      {
+        log_error ("oops: issuer missing in certificate\n");
+        err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+        goto leave;
+      }
+    gcry_md_hash_buffer (GCRY_MD_SHA1, issuerhash, tmp, strlen (tmp));
+    xfree (tmp);
+    for (i=0,tmp=issuerhash_hex; i < 20; i++, tmp += 2)
+      sprintf (tmp, "%02X", issuerhash[i]);
+  }
+
+  /* FIXME: We don't have a suitable check function so we need to format the
+     S/N as string first. */
+  {
+    ksba_sexp_t serial;
+    unsigned char *p;
+    unsigned long n;
+    char *endp, *tmp;
+    int i;
+
+    serial = ksba_cert_get_serial (cert);
+    if (!serial)
+      {
+        log_error ("oops: S/N missing in certificate\n");
+        err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+        goto leave;
+      }
+    p = serial;
+    if (*p != '(')
+      {
+        log_error ("oops: invalid S/N\n");
+        err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+        xfree (serial);
+        goto leave;
+      }
+    p++;
+    n = strtoul (p, &endp, 10);
+    p = endp;
+    if (*p != ':')
+      {
+        log_error ("oops: invalid S/N\n");
+        err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+        xfree (serial);
+        goto leave;
+      }
+    p++;
+
+    serialno = tmp = xtrymalloc (n*2 + 1);
+    if (!serialno)
+      {
+        err = gpg_error_from_errno (errno);
+        xfree (serial);
+        goto leave;
+      }
+    
+    for (i=0; i < n; i++, tmp += 2)
+      sprintf (tmp, "%02X", p[i]);
+    *tmp = 0;
+    xfree (serial);
+  }
+
+  /* Now use our old function to do the actual CRL check.  */
+  {
+    int reloaded = 0;
+
+  again:
+    switch (crl_cache_isvalid (issuerhash_hex, serialno,
+                               ctrl->force_crl_refresh))
+      {
+      case CRL_CACHE_VALID:
+        err = 0;
+        break;
+      case CRL_CACHE_INVALID:
+        err = gpg_error (GPG_ERR_CERT_REVOKED);
+        break;
+      case CRL_CACHE_DONTKNOW: 
+        if (reloaded)
+          err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+        else
+          {
+            reloaded = 1;
+            err = reload_crl (ctrl, cert);
+            if (!err)
+              goto again;
+          }
+        break;
+      case CRL_CACHE_CANTUSE: 
+        err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+        break;
+      default:
+        log_fatal ("crl_cache_isvalid returned invalid status code\n");
+      }
+  }
+
+ leave:
+  if (err)
+    log_error (_("command %s failed: %s\n"), "CHECKCRL", gpg_strerror (err));
+  ksba_cert_release (cert);
+  xfree (serialno);
+  return map_to_assuan_status (err);
+}
+
+
+/* CHECKOCSP [<fingerprint>]
+
+   Check whether the certificate with FINGERPRINT (SHA-1 hash of the
+   entire X.509 certificate blob) is valid or not by asking an OCSP
+   responder responsible for this certificate.  The optional
+   fingerprint may be used for a quick check in case an OCSP check has
+   been down for this certificate recently (we always cache OCSP
+   responses for a couple of minutes). If the fingerprint has not been
+   given or there is no cached result, the function inquires the
+   certificate using the
+
+      INQUIRE TARGETCERT
+
+   and the caller is expected to return the certificate for the
+   request (which should match FINGERPRINT) as a binary blob.
+   Processing then takes place without further interaction; in
+   particular dirmngr tries to locate other required certificates by
+   its own mechanism which includes a local certificate store as well
+   as a list of trusted root certifciates.
+
+   The return value is the usual gpg-error code or 0 for ducesss;
+   i.e. the certificate validity has been confirmed by a valid CRL.
+*/
+static int
+cmd_checkocsp (assuan_context_t ctx, char *line)
+{
+/*   ctrl_t ctrl = assuan_get_pointer (ctx); */
+  gpg_error_t err;
+
+  err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+  if (err)
+    log_error (_("command %s failed: %s\n"), "CHECKOCSP", gpg_strerror (err));
+  return map_to_assuan_status (err);
+}
+
+
 
 /* LOOKUP <pattern>
 
@@ -650,7 +873,7 @@
 
   buf = xmalloc (strlen (line)+1);
   strcpy_escaped_plus (buf, line);
-  err = crl_cache_load (ctrl, ctrl->server_local->crl_cache, buf);
+  err = crl_cache_load (ctrl, buf);
   xfree (buf);
   if (err)
     log_error (_("command %s failed: %s\n"), "LOADCRL", gpg_strerror (err));
@@ -670,13 +893,12 @@
 cmd_listcrls (assuan_context_t ctx, char *line)
 {
   gpg_error_t err;
-  ctrl_t ctrl = assuan_get_pointer (ctx);
   FILE *fp = assuan_get_data_fp (ctx);
 
   if (!fp)
     return PARM_ERROR (_("no data stream"));
 
-  err = crl_cache_list (ctrl->server_local->crl_cache, fp);
+  err = crl_cache_list (fp);
   if (err)
     log_error (_("command %s failed: %s\n"), "LISTCRLS", gpg_strerror (err));
   return map_to_assuan_status (err);
@@ -693,6 +915,8 @@
     int (*handler)(ASSUAN_CONTEXT, char *line);
   } table[] = {
     { "ISVALID",    cmd_isvalid },
+    { "CHECKCRL",   cmd_checkcrl },
+    { "CHECKOCSP",  cmd_checkocsp },
     { "LOOKUP",     cmd_lookup },
     { "LOADCRL",    cmd_loadcrl },
     { "LISTCRLS",   cmd_listcrls },
@@ -712,24 +936,31 @@
 }
 
 
-/* Startup the server and run the main loop. */
+/* Startup the server and run the main command loop.  With FD = -1
+   used stdin/stdout. */
 void
-start_command_handler (void)
+start_command_handler (int fd)
 {
   int rc;
-  int filedes[2];
   assuan_context_t ctx;
   struct server_control_s ctrl;
 
   memset (&ctrl, 0, sizeof ctrl);
   dirmngr_init_default_ctrl (&ctrl);
 
-  /* For now we use a simple pipe based server so that we can work
-     from scripts.  We will later add options to run as a daemon and
-     wait for requests on a Unix domain socket. */
-  filedes[0] = 0;
-  filedes[1] = 1;
-  rc = assuan_init_pipe_server (&ctx, filedes);
+  if (fd == -1)
+    {
+      int filedes[2];
+
+      filedes[0] = 0;
+      filedes[1] = 1;
+      rc = assuan_init_pipe_server (&ctx, filedes);
+    }
+  else
+    {
+      rc = assuan_init_connected_socket_server (&ctx, fd);
+    }
+
   if (rc)
     {
       log_error (_("failed to initialize the server: %s\n"),
@@ -750,7 +981,6 @@
   assuan_set_pointer (ctx, &ctrl);
   ctrl.server_local = xcalloc (1, sizeof *ctrl.server_local);
   ctrl.server_local->assuan_ctx = ctx;
-  ctrl.server_local->crl_cache = crl_cache_init ();
   ctrl.server_local->message_fd = -1;
 
   if (DBG_ASSUAN)
@@ -781,8 +1011,6 @@
   if (primary_connection == &ctrl)
     primary_connection = NULL;
 
-  crl_cache_deinit (ctrl.server_local->crl_cache);
-
 
   assuan_deinit_server (ctx);
 }
Index: dirmngr/src/util.h
diff -u /dev/null dirmngr/src/util.h:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/util.h	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,57 @@
+/* util.h
+ *	Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#ifndef UTIL_H
+#define UTIL_H
+
+#include <gpg-error.h>
+
+/*-- maperror.c --*/
+gpg_error_t map_assuan_err (int err);
+
+
+/* Handy malloc macros - use only them.  */
+#define xtrymalloc(a)    gcry_malloc ((a))
+#define xtrycalloc(a,b)  gcry_calloc ((a),(b))
+#define xtryrealloc(a,b) gcry_realloc ((a),(b))
+#define xtrystrdup(a)    gcry_strdup ((a))
+#define xfree(a)         gcry_free ((a))
+
+#define xmalloc(a)       gcry_xmalloc ((a))
+#define xcalloc(a,b)     gcry_xcalloc ((a),(b))
+#define xrealloc(a,b)    gcry_xrealloc ((a),(b))
+#define xstrdup(a)       gcry_xstrdup ((a))
+
+/* Macros to replace ctype ones and to avoid locale problems.  */
+#define spacep(p)   (*(p) == ' ' || *(p) == '\t')
+#define digitp(p)   (*(p) >= '0' && *(p) <= '9')
+#define hexdigitp(a) (digitp (a)                     \
+                      || (*(a) >= 'A' && *(a) <= 'F')  \
+                      || (*(a) >= 'a' && *(a) <= 'f'))
+/* These atoi macros assume that the buffer has only valid digits.  */
+#define atoi_1(p)   (*(p) - '0' )
+#define atoi_2(p)   ((atoi_1(p) * 10) + atoi_1((p)+1))
+#define atoi_4(p)   ((atoi_2(p) * 100) + atoi_2((p)+2))
+#define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
+                     *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
+#define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
+
+
+#endif /*UTIL_H*/
Index: dirmngr/src/validate.c
diff -u /dev/null dirmngr/src/validate.c:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/validate.c	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,781 @@
+/* validate.c - Validate a certificate chain.
+ *	Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
+ *      Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <ctype.h>
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "dirmngr.h"
+#include "certcache.h"
+#include "validate.h"
+#include "misc.h"
+
+static const char oid_kp_serverAuth[]     = "1.3.6.1.5.5.7.3.1";
+static const char oid_kp_clientAuth[]     = "1.3.6.1.5.5.7.3.2";
+static const char oid_kp_codeSigning[]    = "1.3.6.1.5.5.7.3.3";
+static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4";
+static const char oid_kp_timeStamping[]   = "1.3.6.1.5.5.7.3.8";
+static const char oid_kp_ocspSigning[]    = "1.3.6.1.5.5.7.3.9";
+
+
+
+static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert);
+
+
+
+
+/* Check whether CERT contains critical extensions we don't know
+   about.  */
+static gpg_error_t
+unknown_criticals (ksba_cert_t cert)
+{
+  static const char *known[] = {
+    "2.5.29.15", /* keyUsage */
+    "2.5.29.19", /* basic Constraints */
+    "2.5.29.32", /* certificatePolicies */
+    "2.5.29.37", /* extendedKeyUsage */
+    NULL
+  };
+  int i, idx, crit;
+  const char *oid;
+  gpg_error_t err, rc;
+
+  rc = 0;
+  for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
+                                             &oid, &crit, NULL, NULL));idx++)
+    {
+      if (!crit)
+        continue;
+      for (i=0; known[i] && strcmp (known[i],oid); i++)
+        ;
+      if (!known[i])
+        {
+          log_error (_("critical certificate extension %s is not supported"),
+                     oid);
+          rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
+        }
+    }
+  if (err && gpg_err_code (err) != GPG_ERR_EOF)
+    rc = err; /* Such an error takes precendence.  */
+
+  return rc;
+} 
+
+
+/* Basic check for supported policies.  */
+static gpg_error_t
+check_cert_policy (ksba_cert_t cert)
+{
+  static char *allowed[] = {
+    "2.289.9.9",
+    NULL
+  };
+  gpg_error_t err;
+  int idx;
+  char *p, *haystack;
+  char *policies;
+  int any_critical;
+
+  err = ksba_cert_get_cert_policies (cert, &policies);
+  if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+    return 0; /* No policy given. */
+  if (err)
+    return err;
+
+  /* STRING is a line delimited list of certifiate policies as stored
+     in the certificate.  The line itself is colon delimited where the
+     first field is the OID of the policy and the second field either
+     N or C for normal or critical extension */
+  if (opt.verbose > 1)
+    log_info ("certificate's policy list: %s\n", policies);
+
+  /* The check is very minimal but won't give false positives */
+  any_critical = !!strstr (policies, ":C");
+
+  /* See whether we find ALLOWED (which is an OID) in POLICIES */
+  for (idx=0; allowed[idx]; idx++) 
+    {
+      for (haystack=policies; (p=strstr (haystack, allowed[idx]));
+           haystack = p+1)
+        {
+          if ( !(p == policies || p[-1] == '\n') )
+            continue; /* Does not match the begin of a line. */
+          if (p[strlen (allowed[idx])] != ':')
+            continue; /* The length does not match. */
+          /* Yep - it does match: Return okay. */
+          xfree (policies);
+          return 0;
+        }
+    }
+  
+  if (!any_critical)
+    {
+      log_info (_("note: non-critical certificate policy not allowed"));
+      err = 0;
+    }
+  else
+    {
+      log_info (_("certificate policy not allowed"));
+      err = gpg_error (GPG_ERR_NO_POLICY_MATCH);
+    }
+
+  xfree (policies);
+  return err;
+}
+
+
+static gpg_error_t
+allowed_ca (ksba_cert_t cert, int *chainlen)
+{
+  gpg_error_t err;
+  int flag;
+
+  err = ksba_cert_is_ca (cert, &flag, chainlen);
+  if (err)
+    return err;
+  if (!flag)
+    {
+      log_error (_("issuer certificate is not marked as a CA"));
+      return gpg_error (GPG_ERR_BAD_CA_CERT);
+    }
+  return 0;
+}
+
+
+/* Validate the certificate CHAIN up to the trust anchor. Optionally
+   return the closest expiration time in R_EXPTIME (this is useful for
+   caching issues).  */
+gpg_error_t
+validate_cert_chain (ksba_cert_t cert, ksba_isotime_t r_exptime)
+{
+  gpg_error_t err = 0;
+  int depth = 0, maxdepth;
+  char *issuer = NULL;
+  char *subject = NULL;
+  ksba_cert_t subject_cert = NULL, issuer_cert = NULL;
+  ksba_isotime_t current_time;
+  ksba_isotime_t exptime;
+  int any_expired = 0;
+  int any_revoked = 0;
+  int any_no_crl = 0;
+  int any_crl_too_old = 0;
+  int any_no_policy_match = 0;
+
+  /* Get the current time. */
+  get_isotime (current_time);
+  if (r_exptime)
+    *r_exptime = 0;
+  *exptime = 0;
+
+  if (DBG_X509)
+    dump_cert ("subject", cert);
+
+  /* We walk up the the path until we find a trust anchor. */
+  subject_cert = cert;
+  maxdepth = 50;
+
+  for (;;)
+    {
+      /* Get the subject and issuer name from the current
+         certificate.  */
+      xfree (issuer);
+      xfree (subject);
+      issuer = ksba_cert_get_issuer (subject_cert, 0);
+      subject = ksba_cert_get_subject (subject_cert, 0);
+
+      if (!issuer)
+        {
+          log_error (_("no issuer found in certificate"));
+          err = gpg_error (GPG_ERR_BAD_CERT);
+          goto leave;
+        }
+
+      /* Handle the notBefore and notAfter timestamps.  */
+      { 
+        ksba_isotime_t not_before, not_after;
+
+        err = ksba_cert_get_validity (subject_cert, 0, not_before);
+        if (!err)
+          err = ksba_cert_get_validity (subject_cert, 1, not_after);
+        if (err)
+          {
+            log_error (_("certificate with invalid validity: %s"),
+                       gpg_strerror (err));
+            err = gpg_error (GPG_ERR_BAD_CERT);
+            goto leave;
+          }
+
+        /* Keep track of the nearest expiration time in EXPTIME.  */
+        if (*not_after)
+          {
+            if (!*exptime)
+              copy_time (exptime, not_after);
+            else if (strcmp (not_after, exptime) < 0 )
+              copy_time (exptime, not_after);
+          }
+
+        /* Check whether the certificate is already valid.  */
+        if (*not_before && strcmp (current_time, not_before) < 0 )
+          {
+            log_error (_("certificate not yet valid"));
+            log_info ("(valid from ");
+            dump_isotime (not_before);
+            log_printf (")\n");
+            err = gpg_error (GPG_ERR_CERT_TOO_YOUNG);
+            goto leave;
+          }    
+
+        /* Now check whether the certificate has expired.  */
+        if (*not_after && strcmp (current_time, not_after) > 0 )
+          {
+            log_error (_("certificate has expired"));
+            log_info ("(expired at ");
+            dump_isotime (not_after);
+            log_printf (")\n");
+            any_expired = 1;
+          }            
+      }
+
+      /* Do we have any critical extesnions in the certificate we
+         can't handle? */
+      err = unknown_criticals (subject_cert);
+      if (err)
+        goto leave; /* yes. */
+
+      /* Check that given policies are allowed.  */
+      err = check_cert_policy (subject_cert);
+      if (gpg_err_code (err) == GPG_ERR_NO_POLICY_MATCH)
+        {
+          any_no_policy_match = 1;
+          err = 1; /*???? FIXME*/
+        }
+      else if (err)
+        goto leave;
+
+      /* Is this a self-signed certificate? */
+      if (subject && !strcmp (issuer, subject))
+        {  /* Yes. */
+          if (check_cert_sig (subject_cert, subject_cert) )
+            {
+              log_error (_("selfsigned certificate has a BAD signature"));
+              err = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
+                                    : GPG_ERR_BAD_CERT);
+              goto leave;
+            }
+
+          /* Is this certificate allowed to act as a CA.  */
+          err = allowed_ca (subject_cert, NULL);
+          if (err)
+            goto leave;  /* No. */
+
+          
+          err = is_trusted_cert (subject_cert);
+          if (!err)
+            ; /* Yes we trust this cert.  */
+          else if (gpg_err_code (err) == GPG_ERR_NOT_TRUSTED)
+            {
+              char *fpr;
+
+              log_error (_("root certificate is not marked trusted"));
+              fpr = get_fingerprint_hexstring (subject_cert);
+              log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
+              xfree (fpr);
+              dump_cert ("issuer", subject_cert);
+            }
+          else 
+            {
+              log_error (_("checking trustworthiness of "
+                           "root certificate failed: %s\n"),
+                         gpg_strerror (err));
+            }
+          if (err)
+            goto leave;
+
+          /* FIXME: Now check whether this certificate has been revoked.  */
+
+          break;  /* Okay: a self-signed certicate is an end-point. */
+        }
+
+      /* To avoid loops, we use an arbitary limit on tghe length of
+         the chain. */
+      depth++;
+      if (depth > maxdepth)
+        {
+          log_error (_("certificate chain too long\n"));
+          err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+          goto leave;
+        }
+
+      /* Find the next cert up the tree. */
+      ksba_cert_release (issuer_cert); issuer_cert = NULL;
+      err = find_issuing_cert (subject_cert, &issuer_cert);
+      if (err)
+        {
+          if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+            {
+              log_error (_("issuer certificate not found"));
+              log_info ("issuer certificate: #/");
+              dump_string (issuer);
+              log_printf ("\n");
+            }
+          else
+            log_error (_("failed to find issuer certificate: %s\n"),
+                         gpg_strerror (err));
+          /* Use a better understandable error code.  */
+          err = gpg_error (GPG_ERR_MISSING_CERT);
+          goto leave;
+        }
+
+/*     try_another_cert: */
+      if (DBG_X509)
+        {
+          log_debug ("got issuer's certificate:\n");
+          dump_cert ("issuer", issuer_cert);
+        }
+
+      /* Now check the signature of the certificate.  Well, not
+         really: We delay this until later so that fakedcertificates
+         can't be turned into a DoS easily.  */
+      err = check_cert_sig (issuer_cert, subject_cert);
+      if (err)
+        {
+          log_error (_("certificate has a BAD signature"));
+#if 0
+          if (gpg_err_code (err) == GPG_ERR_BAD_SIGNATURE)
+            {
+              /* We now try to find other issuer certificates which
+                 might have been used.  This is required because some
+                 CAs are reusing the issuer and subject DN for new
+                 root certificates without using a  authorityKeyIdentifier. */
+              rc = find_up (kh, subject_cert, issuer, 1);
+              if (!rc)
+                {
+                  ksba_cert_t tmp_cert;
+
+                  rc = keydb_get_cert (kh, &tmp_cert);
+                  if (rc || !compare_certs (issuer_cert, tmp_cert))
+                    {
+                      /* The find next did not work or returned an
+                         identical certificate.  We better stop here
+                         to avoid infinite checks. */
+                      rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
+                      ksba_cert_release (tmp_cert);
+                    }
+                  else
+                    {
+                      do_list (0, lm, fp, _("found another possible matching "
+                                            "CA certificate - trying again"));
+                      ksba_cert_release (issuer_cert); 
+                      issuer_cert = tmp_cert;
+                      goto try_another_cert;
+                    }
+                }
+            }
+#endif
+          /* We give a more descriptive error code than the one
+             returned from the signature checking. */
+          err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+          goto leave;
+        }
+
+      /* Check that the length of the chain is not longer than allowed
+         by the CA.  */
+      {
+        int chainlen;
+
+        err = allowed_ca (issuer_cert, &chainlen);
+        if (err)
+          goto leave;
+        if (chainlen >= 0 && (depth - 1) > chainlen)
+          {
+            log_error (_("certificate chain longer than allowed by CA (%d)"),
+                       chainlen);
+            err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+            goto leave;
+          }
+      }
+
+      /* May that certificate be used for certification? */
+      err = cert_use_cert_p (issuer_cert);
+      if (err)
+        goto leave;  /* No.  */
+
+      /* FIXME: Now check whether this certificate has been revoked.  */
+
+      if (opt.verbose)
+        log_info ("certificate is good\n");
+
+      /* Now to the next level up.  */
+      subject_cert = issuer_cert;
+      issuer_cert = NULL;
+    }
+
+  if (!err)
+    { /* If we encountered an error somewhere during the checks, set
+         the error code to the most critical one */
+      if (any_revoked)
+        err = gpg_error (GPG_ERR_CERT_REVOKED);
+      else if (any_expired)
+        err = gpg_error (GPG_ERR_CERT_EXPIRED);
+      else if (any_no_crl)
+        err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+      else if (any_crl_too_old)
+        err = gpg_error (GPG_ERR_CRL_TOO_OLD);
+      else if (any_no_policy_match)
+        err = gpg_error (GPG_ERR_NO_POLICY_MATCH);
+    }
+  
+ leave:
+  if (r_exptime)
+    copy_time (r_exptime, exptime);
+  xfree (issuer);
+  ksba_cert_release (issuer_cert);
+  if (subject_cert != cert)
+    ksba_cert_release (subject_cert);
+  return err;
+}
+
+
+
+/* Return the public key algorithm id from the S-expression PKEY.
+   FIXME: libgcrypt should provide such a function.  Note that this
+   implementation uses the names as used by libksba.  */
+static int
+pk_algo_from_sexp (gcry_sexp_t pkey)
+{
+  gcry_sexp_t l1, l2;
+  const char *name;
+  size_t n;
+  int algo;
+
+  l1 = gcry_sexp_find_token (pkey, "public-key", 0);
+  if (!l1)
+    return 0; /* Not found.  */
+  l2 = gcry_sexp_cadr (l1);
+  gcry_sexp_release (l1);
+
+  name = gcry_sexp_nth_data (l2, 0, &n);
+  if (!name)
+    algo = 0; /* Not found. */
+  else if (n==3 && !memcmp (name, "rsa", 3))
+    algo = GCRY_PK_RSA;
+  else if (n==3 && !memcmp (name, "dsa", 3))
+    algo = GCRY_PK_DSA;
+  else if (n==13 && !memcmp (name, "ambiguous-rsa", 13))
+    algo = GCRY_PK_RSA;
+  else
+    algo = 0;
+  gcry_sexp_release (l2);
+  return algo;
+}
+
+
+/* Check the signature on CERT using the ISSUER_CERT.  This function
+   does only test the cryptographic signature and nothing else.  It is
+   assumed that the ISSUER_CERT is valid. */
+static gpg_error_t
+check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert)
+{
+  gpg_error_t err;
+  const char *algoid;
+  gcry_md_hd_t md;
+  int i, algo;
+  ksba_sexp_t p;
+  size_t n;
+  gcry_sexp_t s_sig, s_hash, s_pkey;
+  const char *s;
+  char algo_name[16+1]; /* hash algorithm name converted to lower case. */
+  int digestlen;
+  unsigned char *digest;
+
+  /* Hash the target certificate using the algorithm from that certificate.  */
+  algoid = ksba_cert_get_digest_algo (cert);
+  algo = gcry_md_map_name (algoid);
+  if (!algo)
+    {
+      log_error (_("unknown hash algorithm `%s'\n"), algoid? algoid:"?");
+      return gpg_error (GPG_ERR_GENERAL);
+    }
+  s = gcry_md_algo_name (algo);
+  for (i=0; *s && i < sizeof algo_name - 1; s++, i++)
+    algo_name[i] = tolower (*s);
+  algo_name[i] = 0;   
+
+  err = gcry_md_open (&md, algo, 0);
+  if (err)
+    {
+      log_error ("md_open failed: %s\n", gpg_strerror (err));
+      return err;
+    }
+  if (DBG_HASHING)
+    gcry_md_start_debug (md, "hash.cert");
+  err = ksba_cert_hash (cert, 1, HASH_FNC, md);
+  if (err)
+    {
+      log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (err));
+      gcry_md_close (md);
+      return err;
+    }
+  gcry_md_final (md);
+
+  /* Get the signature value out of the target certificate.  */
+  p = ksba_cert_get_sig_val (cert);
+  n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+  if (!n)
+    {
+      log_error ("libksba did not return a proper S-Exp\n");
+      gcry_md_close (md);
+      ksba_free (p);
+      return gpg_error (GPG_ERR_BUG);
+    }
+  if (DBG_X509)
+    {
+      int j;
+      log_debug ("signature value:");
+      for (j=0; j < n; j++)
+        log_printf (" %02X", p[j]);
+      log_printf ("\n");
+    }
+
+  err = gcry_sexp_sscan ( &s_sig, NULL, p, n);
+  ksba_free (p);
+  if (err)
+    {
+      log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err));
+      gcry_md_close (md);
+      return err;
+    }
+
+  /* Get the public key from the issuer certificate.  */
+  p = ksba_cert_get_public_key (issuer_cert);
+  n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+  if (!n)
+    {
+      log_error ("libksba did not return a proper S-Exp\n");
+      gcry_md_close (md);
+      ksba_free (p);
+      gcry_sexp_release (s_sig);
+      return gpg_error (GPG_ERR_BUG);
+    }
+  err = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
+  ksba_free (p);
+  if (err)
+    {
+      log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err));
+      gcry_md_close (md);
+      gcry_sexp_release (s_sig);
+      return err;
+    }
+
+
+  /* Prepare the values for signature verification. At this point we
+     have these values:
+
+     S_PKEY    - S-expression with the issuer's public key.
+     S_SIG     - Signature value as given in the certrificate.
+     MD        - Finalized hash context with hash of the certificate.
+     ALGO_NAME - Lowercase hash algorithm name
+   */
+  digestlen = gcry_md_get_algo_dlen (algo);
+  digest = gcry_md_read (md, algo);
+  if (pk_algo_from_sexp (s_pkey) == GCRY_PK_DSA)
+    {
+      if (digestlen != 20)
+        {
+          log_error (_("DSA requires the use of a 160 bit hash algorithm\n"));
+          gcry_md_close (md);
+          gcry_sexp_release (s_sig);
+          gcry_sexp_release (s_pkey);
+          return gpg_error (GPG_ERR_INTERNAL);
+        }
+      if ( gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))",
+                            (int)digestlen, digest) )
+        BUG ();
+    }
+  else /* Not DSA.  */
+    {
+      if ( gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
+                            algo_name, (int)digestlen, digest) )
+        BUG ();
+      
+    }
+      
+  err = gcry_pk_verify (s_sig, s_hash, s_pkey);
+  if (DBG_CRYPTO)
+    log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err));
+  gcry_md_close (md);
+  gcry_sexp_release (s_sig);
+  gcry_sexp_release (s_hash);
+  gcry_sexp_release (s_pkey);
+  return err;
+}
+
+
+
+/* Return 0 if the cert is usable for encryption.  A MODE of 0 checks
+   for signing, a MODE of 1 checks for encryption, a MODE of 2 checks
+   for verification and a MODE of 3 for decryption (just for
+   debugging).  MODE 4 is for certificate signing, MODE 5 for OCSP
+   response signing. */
+static int
+cert_usage_p (ksba_cert_t cert, int mode)
+{
+  gpg_error_t err;
+  unsigned int use;
+  char *extkeyusages;
+  int have_ocsp_signing = 0;
+
+  err = ksba_cert_get_ext_key_usages (cert, &extkeyusages);
+  if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+    err = 0; /* No policy given. */
+  if (!err)
+    {
+      unsigned int extusemask = ~0; /* Allow all. */
+
+      if (extkeyusages)
+        {
+          char *p, *pend;
+          int any_critical = 0;
+
+          extusemask = 0;
+
+          p = extkeyusages;
+          while (p && (pend=strchr (p, ':')))
+            {
+              *pend++ = 0;
+              /* Only care about critical flagged usages. */
+              if ( *pend == 'C' )
+                {
+                  any_critical = 1;
+                  if ( !strcmp (p, oid_kp_serverAuth))
+                    extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+                                   | KSBA_KEYUSAGE_KEY_ENCIPHERMENT
+                                   | KSBA_KEYUSAGE_KEY_AGREEMENT);
+                  else if ( !strcmp (p, oid_kp_clientAuth))
+                    extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+                                   | KSBA_KEYUSAGE_KEY_AGREEMENT);
+                  else if ( !strcmp (p, oid_kp_codeSigning))
+                    extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE);
+                  else if ( !strcmp (p, oid_kp_emailProtection))
+                    extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+                                   | KSBA_KEYUSAGE_NON_REPUDIATION
+                                   | KSBA_KEYUSAGE_KEY_ENCIPHERMENT
+                                   | KSBA_KEYUSAGE_KEY_AGREEMENT);
+                  else if ( !strcmp (p, oid_kp_timeStamping))
+                    extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+                                   | KSBA_KEYUSAGE_NON_REPUDIATION);
+                }
+              
+              /* This is a hack to cope with OCSP.  Note that we do
+                 not yet fully comply with the requirements and that
+                 the entire CRL/OCSP checking thing should undergo a
+                 thorough review and probably redesign. */
+              if ( !strcmp (p, oid_kp_ocspSigning))
+                have_ocsp_signing = 1;
+
+              if ((p = strchr (pend, '\n')))
+                p++;
+            }
+          xfree (extkeyusages);
+          extkeyusages = NULL;
+          
+          if (!any_critical)
+            extusemask = ~0; /* Reset to the don't care mask. */
+        }
+
+
+      err = ksba_cert_get_key_usage (cert, &use);
+      if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+        {
+          err = 0;
+          if (opt.verbose && mode < 2)
+            log_info (_("no key usage specified - assuming all usages\n"));
+          use = ~0;
+        }
+
+      /* Apply extKeyUsage. */
+      use &= extusemask;
+
+    }
+  if (err)
+    { 
+      log_error (_("error getting key usage information: %s\n"),
+                 gpg_strerror (err));
+      xfree (extkeyusages);
+      return err;
+    } 
+
+  if (mode == 4)
+    {
+      if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN)))
+        return 0;
+      log_info (_("certificate should have not "
+                  "been used for certification\n"));
+      return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+    }
+
+  if (mode == 5)
+    {
+      if (use != ~0 
+          && (have_ocsp_signing
+              || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN
+                         |KSBA_KEYUSAGE_CRL_SIGN))))
+        return 0;
+      log_info (_("certificate should have not "
+                  "been used for OCSP response signing\n"));
+      return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+    }
+
+  if ((use & ((mode&1)?
+              (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT):
+              (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
+      )
+    return 0;
+
+  log_info (mode==3? _("certificate should have not been used "
+                       "for encryption\n"):
+            mode==2? _("certificate should have not been used for signing\n"):
+            mode==1? _("certificate is not usable for encryption\n"):
+                     _("certificate is not usable for signing\n"));
+  return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+}
+
+/* Return 0 if the certificate CERT is usable for certification.  */
+gpg_error_t
+cert_use_cert_p (ksba_cert_t cert)
+{
+  return cert_usage_p (cert, 4);
+}
+
+/* Return 0 if the certificate CERT is usable for signing OCSP
+   responses.  */
+gpg_error_t
+cert_use_ocsp_p (ksba_cert_t cert)
+{
+  return cert_usage_p (cert, 5);
+}
+
Index: dirmngr/src/validate.h
diff -u /dev/null dirmngr/src/validate.h:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/src/validate.h	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,37 @@
+/* validate.h - Certificate validation
+ *      Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+#ifndef VALIDATE_H
+#define VALIDATE_H
+
+
+/* Validate the certificate CHAIN up to the trust anchor. Optionally
+   return the closest expiration time in R_EXPTIME. */
+gpg_error_t validate_cert_chain (ksba_cert_t cert, ksba_isotime_t r_exptime);
+
+/* Return 0 if the certificate CERT is usable for certification.  */
+gpg_error_t cert_use_cert_p (ksba_cert_t cert);
+
+/* Return 0 if the certificate CERT is usable for signing OCSP
+   responses.  */
+gpg_error_t cert_use_ocsp_p (ksba_cert_t cert);
+
+
+#endif /*VALIDATE_H*/
Index: dirmngr/tests/Makefile.am
diff -u dirmngr/tests/Makefile.am:1.8 dirmngr/tests/Makefile.am:1.9
--- dirmngr/tests/Makefile.am:1.8	Wed Dec  3 22:22:14 2003
+++ dirmngr/tests/Makefile.am	Tue Nov 16 19:24:35 2004
@@ -10,11 +10,14 @@
 
 TESTS = 
 
-noinst_PROGRAMS = test-dirmngr asschk
+noinst_PROGRAMS = test-dirmngr asschk show-multi
 LDADD = ../jnlib/libjnlib.a @LIBASSUAN_LIBS@ @LIBGCRYPT_LIBS@ @KSBA_LIBS@
 
 asschk_SOURCES = asschk.c
 
+show_multi_SOURCERS = show-multi.c
+show_multi_LDADD = 
+
 
 
 
Index: dirmngr/tests/show-multi.c
diff -u /dev/null dirmngr/tests/show-multi.c:1.1
--- /dev/null	Tue Nov 16 19:24:36 2004
+++ dirmngr/tests/show-multi.c	Tue Nov 16 19:24:35 2004
@@ -0,0 +1,170 @@
+/* show-multi.c  -  test tool to show dirmngr_ldap --multi output
+ *	Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr 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.
+ *
+ * DirMngr 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
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+
+#define PGM "show-multi"
+
+/* Print diagnostic message and exit with failure. */
+static void
+die (const char *format, ...)
+{
+  va_list arg_ptr;
+
+  fflush (stdout);
+  fprintf (stderr, "%s: ", PGM);
+
+  va_start (arg_ptr, format);
+  vfprintf (stderr, format, arg_ptr);
+  va_end (arg_ptr);
+  putc ('\n', stderr);
+
+  exit (1);
+}
+
+
+static void
+copy_n_bytes (FILE *fp, size_t n)
+{
+  int c;
+
+  while (n-- && (c=getc(fp))!=EOF)
+    if ( c == '\\' )
+      fputs ("\\\\", stdout);
+    else if ( c == '\n' )
+      fputs ("\\n", stdout);
+    else if ( c == '\r' )
+      fputs ("\\r", stdout);
+    else if ( c == '\t' )
+      fputs ("\\t", stdout);
+    else if ( c == '\f' )
+      fputs ("\\f", stdout);
+    else if ( c == '\v' )
+      fputs ("\\v", stdout);
+    else if ( c == '\b' )
+      fputs ("\\b", stdout);
+    else if ( c == '\a' )
+      fputs ("\\a", stdout);
+    else if (c < 0x20 || c >= 0x7f)
+      printf ("\\x%02X", (unsigned int)c);
+    else
+      putchar (c);
+  
+}
+
+
+
+static void
+show_data (FILE *fp)
+{
+  unsigned char hdr[5];
+  size_t length;
+  int n;
+
+
+  while ((n=fread (hdr, 1, 5, fp)) == 5)
+    {
+      length = (hdr[1] << 24)|(hdr[2]<<16)|(hdr[3]<<8)|hdr[4];
+      if (*hdr == 'I')
+        {
+          printf ("ITEM\n");
+          if (length)
+            die ("header type 'I' with a non-zero length\n");
+        }
+      else if (*hdr == 'A')
+        {
+          printf ("  ATTRIBUTE ");
+          copy_n_bytes (fp, length);
+          putchar ('\n');
+        }
+      else if (*hdr == 'V')    
+        {
+          printf ("      VALUE ");
+          copy_n_bytes (fp, length);
+          putchar ('\n');
+        }
+      else
+        die ("invalid header type 0x%02X\n", *hdr);
+    }
+  if (ferror (fp))
+    die ("error reading input: %s\n", strerror (errno));
+  else if (n)
+    die ("header record too short\n");
+}
+
+
+
+int 
+main (int argc, char **argv)
+{
+  int last_argc = -1;
+ 
+  if (argc)
+    {
+      argc--; argv++;
+    }
+  while (argc && last_argc != argc )
+    {
+      last_argc = argc;
+      if (!strcmp (*argv, "--"))
+        {
+          argc--; argv++;
+          break;
+        }
+      else if (!strcmp (*argv, "--help"))
+        {
+          puts (
+                "Usage: " PGM " [OPTION] [FILE]\n"
+                "Show the output of dirmngr_ldap --multi\n\n"
+                "  --help      display this help and exit\n\n"
+                "With no FILE, or when FILE is -, read standard input.\n");
+          exit (0);
+        }
+    }          
+ 
+  if (argc > 1)
+    die ("usage: " PGM " [OPTION] [FILE] (try --help for more information)\n");
+
+  if (argc && strcmp (*argv, "-"))
+    {
+      FILE *fp = fopen (*argv, "rb");
+      if (!fp)
+        die ("can't open `%s': %s", *argv, strerror (errno));
+      show_data (fp);
+      fclose (fp);
+    }
+  else
+    show_data (stdin);
+
+  return 0;
+}
+
+
+/*
+Local Variables:
+compile-command: "gcc -Wall -g -o show-multi show-multi.c"
+End:
+*/




More information about the Gnupg-commits mailing list