Added keyserver directory from trunk
authorWerner Koch <wk@gnupg.org>
Fri, 30 Jun 2006 13:19:49 +0000 (13:19 +0000)
committerWerner Koch <wk@gnupg.org>
Fri, 30 Jun 2006 13:19:49 +0000 (13:19 +0000)
18 files changed:
ChangeLog
Makefile.am
configure.ac
keyserver/ChangeLog [new file with mode: 0644]
keyserver/Makefile.am [new file with mode: 0644]
keyserver/curl-shim.c [new file with mode: 0644]
keyserver/curl-shim.h [new file with mode: 0644]
keyserver/gpgkeys_curl.c [new file with mode: 0644]
keyserver/gpgkeys_finger.c [new file with mode: 0644]
keyserver/gpgkeys_hkp.c [new file with mode: 0644]
keyserver/gpgkeys_ldap.c [new file with mode: 0644]
keyserver/gpgkeys_mailto.in [new file with mode: 0755]
keyserver/gpgkeys_test.in [new file with mode: 0755]
keyserver/ksutil.c [new file with mode: 0644]
keyserver/ksutil.h [new file with mode: 0644]
m4/ChangeLog
m4/Makefile.am
m4/ldap.m4 [new file with mode: 0644]

index 745a6a1..eeb91bc 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2006-06-30  Werner Koch  <wk@g10code.com>
+
+       * keyserver/: New.  Taken from 1.4.4
+       * Makefile.am (SUBDIRS): Include keyserver/.
+       * configure.ac: Include keyserver/.
+       (FAKE_CURL, GPGKEYS_CURL): New.
+       
 2006-06-20  Werner Koch  <wk@g10code.com>
 
        Released 1.9.21.
index 0c5fbe4..6f5f165 100644 (file)
@@ -35,8 +35,11 @@ endif
 
 if BUILD_GPG
 gpg = g10
+# fixme: Noy yet ready for a build
+keyserver = 
 else
 gpg =
+keyserver = 
 endif
 if BUILD_GPGSM
 sm = sm
@@ -61,7 +64,7 @@ tests = tests
 endif
 
 SUBDIRS = m4 intl gl jnlib common ${kbx} \
- ${gpg} ${sm} ${agent} ${scd} tools po doc ${tests}
+ ${gpg} ${keyserver} ${sm} ${agent} ${scd} tools po doc ${tests}
 
 dist-hook:
        @set -e; \
index 3f536e4..03d8809 100644 (file)
@@ -716,6 +716,20 @@ fi
 AC_SUBST(GPGKEYS_LDAP)
 AC_SUBST(LDAPLIBS)
 
+
+# Check for curl.  We fake the curl API if libcurl isn't installed.
+
+# fixme: need to add this
+#LIBCURL_CHECK_CONFIG([yes],,,[fake_curl=yes])
+#AM_CONDITIONAL(FAKE_CURL,test x"$fake_curl" = xyes)
+AM_CONDITIONAL(FAKE_CURL,1)
+
+# Generic, for us, means curl
+
+if test x"$try_generic" = xyes ; then
+   AC_SUBST(GPGKEYS_CURL,"gpgkeys_curl$EXEEXT")
+fi
+
 dnl This isn't necessarily sendmail itself, but anything that gives a
 dnl sendmail-ish interface to the outside world.  That includes qmail,
 dnl postfix, etc.  Basically, anything that can handle "sendmail -t".
@@ -1231,6 +1245,7 @@ jnlib/Makefile
 common/Makefile
 kbx/Makefile
 g10/Makefile
+keyserver/Makefile
 sm/Makefile
 agent/Makefile
 scd/Makefile
diff --git a/keyserver/ChangeLog b/keyserver/ChangeLog
new file mode 100644 (file)
index 0000000..c171f6a
--- /dev/null
@@ -0,0 +1,1067 @@
+2006-04-26  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_http.c, gpgkeys_oldhkp.c: Removed.
+
+       * Makefile.am: Don't build gpgkeys_http or gpgkeys_(old)hkp any
+       longer as this is done via curl or fake-curl.
+
+       * ksutil.h, ksutil.c, gpgkeys_hkp.c, gpgkeys_curl.c: Minor
+       #include tweaks as FAKE_CURL is no longer meaningful.
+
+2006-04-10  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (ldap_quote, get_name, search_key): LDAP-quote
+       directly into place rather than mallocing temporary buffers.
+
+       * gpgkeys_ldap.c (get_name): Build strings with strcat rather than
+       using sprintf which is harder to read and modify.
+
+       * ksutil.h, ksutil.c (classify_ks_search): Add
+       KS_SEARCH_KEYID_SHORT and KS_SEARCH_KEYID_LONG to search for a key
+       ID.
+
+       * gpgkeys_ldap.c (search_key): Use it here to flip from pgpUserID
+       searches to pgpKeyID or pgpCertID.
+
+2006-03-27  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c: #define LDAP_DEPRECATED for newer OpenLDAPs so
+       they use the regular old API that is compatible with other LDAP
+       libraries.
+
+2006-03-03  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (main): Fix build problem with non-OpenLDAP LDAP
+       libraries that have TLS.
+
+2006-02-23  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.c (init_ks_options): Default include-revoked and
+       include-subkeys to on, as gpg isn't doing this any longer.
+
+2006-02-22  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (get_name): A GETNAME query turns exact=on to cut
+       down on odd matches.
+
+2006-02-21  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (make_one_attr, build_attrs, send_key): Don't
+       allow duplicate attributes as OpenLDAP is now enforcing this.
+
+       * gpgkeys_ldap.c (main): Add binddn and bindpw so users can pass
+       credentials to a remote LDAP server.
+
+       * curl-shim.h, curl-shim.c (curl_easy_init, curl_easy_setopt,
+       curl_easy_perform): Mingw has 'stderr' as a macro?
+
+       * curl-shim.h, curl-shim.c (curl_easy_init, curl_easy_setopt,
+       curl_easy_perform): Add CURLOPT_VERBOSE and CURLOPT_STDERR for
+       easier debugging.
+
+2006-01-16  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (send_key): Do not escape the '=' in the HTTP POST
+       when uploading a key.
+
+2005-12-23  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.h, ksutil.c (parse_ks_options): New keyserver command
+       "getname".
+
+       * gpgkeys_hkp.c (main, get_name), gpgkeys_ldap.c (main, get_name):
+       Use it here to do direct name (rather than key ID) fetches.
+
+2005-12-19  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.h, ksutil.c (curl_armor_writer, curl_writer,
+       curl_writer_finalize): New functionality to handle binary format
+       keys by armoring them for input to GPG.
+
+       * gpgkeys_curl.c (get_key), gpgkeys_hkp.c (get_key): Call it here.
+
+2005-12-07  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_finger.c (get_key), gpgkeys_curl.c (get_key): Better
+       language for the key-not-found error.
+
+       * ksutil.c (curl_err_to_gpg_err): Add CURLE_OK and
+       CURLE_COULDNT_CONNECT.
+
+       * gpgkeys_curl.c (get_key): Give key-not-found error if no data is
+       found (or file itself is not found) during a fetch.
+
+2005-12-06  David Shaw  <dshaw@jabberwocky.com>
+
+       * curl-shim.c (curl_easy_perform): Fix build warning (code before
+       declaration).
+
+2005-11-02  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (search_key): Fix warning with typecast (though
+       curl should really have defined that char * as const).
+
+2005-08-25  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.h, ksutil.c (parse_ks_options): Remove exact-name and
+       exact-email.
+       (classify_ks_search): Mimic the gpg search modes instead with *,
+       =, <, and @.
+
+       * gpgkeys_ldap.c (search_key), gpgkeys_hkp.c (search_key): Call
+       them here.  Suggested by Jason Harris.
+
+2005-08-18  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.h, ksutil.c (parse_ks_options): New keyserver-option
+       exact-name.  The last of exact-name and exact-email overrides the
+       earlier.
+
+       * gpgkeys_ldap.c (search_key), gpgkeys_hkp.c (search_key): Use it
+       here to do a name-only search.
+
+       * gpgkeys_ldap.c (ldap_quote): \-quote a string for LDAP.
+
+       * gpgkeys_ldap.c (search_key): Use it here to escape reserved
+       characters in searches.
+
+2005-08-17  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.h, ksutil.c (parse_ks_options): New keyserver-option
+       exact-email.
+
+       * gpgkeys_ldap.c (search_key), gpgkeys_hkp.c (search_key): Use it
+       here to do an email-only search.
+
+2005-08-08  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: Include LDAP_CPPFLAGS when building LDAP.
+
+2005-08-03  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (main), gpgkeys_curl.c (main), curl-shim.h: Show
+       version of curl (or curl-shim) when debug is set.
+
+2005-07-20  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_curl.c (get_key, main): Don't try and be smart about
+       what protocols we handle.  Directly pass them to curl or fake-curl
+       and see if an error comes back.
+
+       * curl-shim.h, curl-shim.c (handle_error), ksutil.c
+       (curl_err_to_gpg_err): Add support for CURLE_UNSUPPORTED_PROTOCOL
+       in fake curl.
+
+       * Makefile.am: Don't need -DFAKE_CURL any longer since it's in
+       config.h.
+
+2005-06-23  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_mailto.in, gpgkeys_test.in: Use @VERSION@ so version
+       string stays up to date.
+
+       * gpgkeys_http.c: Don't need to define HTTP_PROXY_ENV here since
+       it's in ksutil.h.
+
+       * gpgkeys_curl.c (get_key, main), gpgkeys_hkp.c (main): Pass AUTH
+       values to curl or curl-shim.
+
+       * curl-shim.c (curl_easy_perform), gpgkeys_curl.c (main),
+       gpgkeys_hkp.c (main): Use curl-style proxy semantics.
+
+       * curl-shim.h, curl-shim.c (curl_easy_setopt, curl_easy_perform):
+       Add CURLOPT_USERPWD option for HTTP auth.
+
+       * gpgkeys_http.c (get_key), gpgkeys_oldhkp (send_key, get_key,
+       search_key): No longer need to pass a proxyauth.
+
+       * gpgkeys_http.c (get_key): Pass auth outside of the URL.
+
+2005-06-21  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_http.c (get_key), gpgkeys_oldhkp.c (send_key, get_key,
+       search_key): Fix http_open/http_open_document calls to pass NULL
+       for auth and proxyauth since these programs pass them in the URL.
+
+2005-06-20  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (append_path, send_key, get_key, search_key,
+       main), gpgkeys_oldhkp.c (main): Properly handle double slashes in
+       paths.
+
+2005-06-05  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.c (init_ks_options, parse_ks_options): Provide a default
+       "/" path unless overridden by the config.  Allow config to specify
+       items multiple times and take the last specified item.
+
+2005-06-04  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c, gpgkeys_oldhkp.c: Add support for HKP servers
+       that aren't at the root path.  Suggested by Jack Bates.
+
+2005-06-01  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.c [HAVE_DOSISH_SYSTEM]: Fix warnings on mingw32.  Noted
+       by Joe Vender.
+
+2005-05-04  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.h, ksutil.c: #ifdef so we can build without libcurl or
+       fake-curl.
+
+2005-05-03  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_http.c: Need GET defined.
+
+2005-05-01  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c, gpgkeys_oldhkp.c, ksutil.h: Some minor cleanup
+       and comments as to the size of MAX_LINE and MAX_URL.
+
+2005-04-16  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c: New hkp handler that uses curl or curl-shim.
+
+       * Makefile.am: Build new gpgkeys_hkp.
+
+       * curl-shim.c (curl_easy_perform): Cleanup.
+
+       * ksutil.h, ksutil.c (curl_writer), gpgkeys_curl.c (get_key): Pass
+       a context to curl_writer so we can support multiple fetches in a
+       single session.
+
+       * curl-shim.h, curl-shim.c (handle_error, curl_easy_setopt,
+       curl_easy_perform): Add POST functionality to the curl shim.
+
+       * curl-shim.h, curl-shim.c (curl_escape, curl_free): Emulate
+       curl_escape and curl_free.
+
+       * gpgkeys_curl.c (main): If the http-proxy option is given without
+       any arguments, try to get the proxy from the environment.
+
+       * ksutil.h, ksutil.c (curl_err_to_gpg_err, curl_writer): Copy from
+       gpgkeys_curl.c.
+
+       * gpgkeys_oldhkp.c: Copy from gpgkeys_hkp.c.
+
+2005-03-22  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c, ksutil.h, ksutil.c (print_nocr): Moved from
+       gpgkeys_ldap.c.  Print a string, but strip out any CRs.
+
+       * gpgkeys_finger.c (get_key), gpgkeys_hkp.c (get_key),
+       gpgkeys_http.c (get_key): Use it here when outputting key material
+       to canonicalize line endings.
+
+2005-03-19  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (main): Fix three wrong calls to fail_all().
+       Noted by Stefan Bellon.
+
+2005-03-17  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.c (parse_ks_options): Handle verbose=nnn.
+
+       * Makefile.am: Calculate GNUPG_LIBEXECDIR directly.  Do not
+       redefine $libexecdir.
+
+       * gpgkeys_curl.c, gpgkeys_finger.c, gpgkeys_ldap.c: Start using
+       parse_ks_options and remove a lot of common code.
+
+       * ksutil.h, ksutil.c (parse_ks_options): Parse OPAQUE, and default
+       debug with no arguments to 1.
+
+2005-03-16  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c: Include lber.h if configure determines we need
+       it.
+
+       * ksutil.h, ksutil.c (ks_action_to_string): New.
+       (free_ks_options): Only free if options exist.
+
+       * ksutil.h, ksutil.c (init_ks_options, free_ks_options,
+       parse_ks_options): Pull a lot of duplicated code into a single
+       options parser for all keyserver helpers.
+
+2005-02-11  David Shaw  <dshaw@jabberwocky.com>
+
+       * curl-shim.c (curl_easy_perform): Fix compile warning.
+
+       * curl-shim.h, gpgkeys_curl.c (main), gpgkeys_ldap.c (main): Add
+       ca-cert-file option, to pass in the SSL cert.
+
+       * curl-shim.h, curl-shim.c: New.  This is code to fake the curl
+       API in terms of the current HTTP iobuf API.
+
+       * gpgkeys_curl.c [FAKE_CURL], Makefile.am: If FAKE_CURL is set,
+       link with the iobuf code rather than libcurl.
+
+2005-02-05  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_finger.c (main), gpgkeys_hkp.c (main): Fix --version
+       output.
+
+       * gpgkeys_curl.c (main): Make sure the curl handle is cleaned up
+       on failure.
+
+2005-02-01  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (get_key), gpgkeys_http.c (get_key): Fix missing
+       http_close() calls.  Noted by Phil Pennock.
+
+       * ksutil.h: Up the default timeout to two minutes.
+
+2005-01-24  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (print_nocr): New.
+       (get_key): Call it here to canonicalize line endings.
+
+       * gpgkeys_curl.c (writer): Discard everything outside the BEGIN
+       and END lines when retrieving keys.  Canonicalize line endings.
+       (main): Accept FTPS.
+
+2005-01-21  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (main): Add "check-cert" option to disable SSL
+       certificate checking (which is on by default).
+
+       * gpgkeys_curl.c (main): Add "debug" option to match the LDAP
+       helper.  Add "check-cert" option to disable SSL certificate
+       checking (which is on by default).
+
+2005-01-18  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_curl.c: Fix typo.
+
+2005-01-18  Werner Koch  <wk@g10code.com>
+
+       * gpgkeys_curl.c: s/MAX_PATH/URLMAX_PATH/g to avoid a clash with
+       the W32 defined macro.  Removed unneeded initialization of static
+       variables.
+       * gpgkeys_http.c: Ditto.
+       * ksutil.h: s/MAX_PATH/URLMAX_PATH/.
+
+2005-01-17  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_curl.c (main): Only allow specified protocols to use the
+       curl handler.
+
+       * Makefile.am: Use LIBCURL_CPPFLAGS instead of LIBCURL_INCLUDES.
+
+2005-01-13  David Shaw  <dshaw@jabberwocky.com>
+
+       * ksutil.h, gpgkeys_curl.c, gpgkeys_hkp.c, gpgkeys_ldap.c,
+       gpgkeys_finger.c, gpgkeys_http.c: Part 2 of the cleanup.  Move all
+       the various defines to ksutil.h.
+
+       * gpgkeys_finger.c, gpgkeys_hkp.c, gpgkeys_http.c, gpgkeys_ldap.c:
+       Part 1 of a minor cleanup to use #defines instead of hard-coded
+       sizes.
+
+       * gpgkeys_finger.c (connect_server): Use INADDR_NONE instead of
+       SOCKET_ERROR.  Noted by Timo.
+
+2005-01-09  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_curl.c (get_key): Newer versions of libcurl don't define
+       TRUE.
+
+2004-12-24  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_curl.c (main): Use new defines for opting out of certain
+       transfer protocols.  Allow setting HTTP proxy via "http-proxy=foo"
+       option (there is natural support in libcurl for the http_proxy
+       environment variable).
+
+       * Makefile.am: Remove the conditional since this is all handled in
+       autoconf now.
+
+2004-12-22  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_curl.c (main): New "follow-redirects" option.  Takes an
+       optional numeric value for the maximum number of redirects to
+       allow.  Defaults to 5.
+
+       * gpgkeys_curl.c (main), gpgkeys_finger.c (main), gpgkeys_hkp.c
+       (main), gpgkeys_http.c (main), gpgkeys_ldap.c (main): Make sure
+       that a "timeout" option passed with no arguments is properly
+       handled.
+
+       * gpgkeys_curl.c (get_key, writer): New function to wrap around
+       fwrite to avoid DLL access problem on win32.
+
+       * gpgkeys_http.c (main, get_key): Properly pass authentication
+       info through to the http library.
+
+       * Makefile.am: Build gpgkeys_http or gpgkeys_curl as needed.
+
+       * gpgkeys_curl.c (main, get_key): Minor tweaks to work with either
+       FTP or HTTP.
+
+       * gpgkeys_ftp.c: renamed to gpgkeys_curl.c.
+
+       * gpgkeys_ftp.c (main, get_key): Use auth data as passed by gpg.
+       Use CURLOPT_FILE instead of CURLOPT_WRITEDATA (same option, but
+       backwards compatible).
+
+2004-12-21  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ftp.c: New.
+
+       * Makefile.am: Build it if requested.
+
+2004-12-14  Werner Koch  <wk@g10code.com>
+
+       * Makefile.am (install-exec-hook, uninstall-hook): Removed.  For
+       Windows reasons we can't use the symlink trick.
+
+2004-12-03  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: The harmless "ignored error" on gpgkeys_ldap
+       install on top of an existing install is bound to confuse people.
+       Use ln -s -f to force the overwrite.
+
+2004-10-28  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_finger.c [_WIN32] (connect_server): Fix typo.
+
+2004-10-28  Werner Koch  <wk@g10code.com>
+
+       * Makefile.am (other_libs): New.  Also include LIBICONV.  Noted by
+       Tim Mooney.
+
+2004-10-28  Werner Koch  <wk@g10code.com>
+
+       * Makefile.am (other_libs): 
+
+2004-10-18  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (send_key, get_key, search_key): Use "hkp" instead
+       of "x-hkp" so it can be used as a SRV tag.
+
+2004-10-16  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_finger.c [_WIN32] (connect_server): Fix typo.
+
+2004-10-15  Werner Koch  <wk@g10code.com>
+
+       * gpgkeys_ldap.c (main, show_help): Kludge to implement standard
+       GNU options. Factored help printing out.
+       * gpgkeys_finger.c (main, show_help): Ditto.
+       * gpgkeys_hkp.c (main, show_help): Ditto.
+       * gpgkeys_http.c (main, show_help): Ditto.
+       * gpgkeys_test.in, gpgkeys_mailto.in: Implement --version and --help.
+
+       * Makefile.am: Add ksutil.h.
+
+2004-10-14  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_finger.c (main): We do not support relay fingering
+       (i.e. "finger://relayhost/user@example.com"), but finger URLs are
+       occasionally miswritten that way.  Give an error in this case.
+
+2004-10-14  Werner Koch  <wk@g10code.com>
+
+       * gpgkeys_finger.c (get_key): s/unsigned char/byte/ due
+       to a strange typedef for RISC OS.  Noted by Stefan.
+
+2004-10-13  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (main), gpgkeys_hkp.c (main), gpgkeys_http.c
+       (main), gpgkeys_finger.c (main): Call timeout functions before
+       performing an action that could block for a long time.
+
+       * ksutil.h, ksutil.c: New.  Right now just contains timeout
+       functions.
+
+2004-10-11  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_finger.c, gpgkeys_hkp.c, gpgkeys_http.c, gpgkeys_ldap.c:
+       Fix a few occurances of "filename" to `filename'.
+
+2004-10-11  Werner Koch  <wk@g10code.com>
+
+       * gpgkeys_finger.c: New.
+
+2004-08-27  Stefan Bellon  <sbellon@sbellon.de>
+
+       * gpgkeys_hkp.c (search_key): Fix the prior faulty fix by
+       introducing a cast but leaving skey unsigned.
+
+       * gpgkeys_hkp.c (search_key): Change type of variable skey from
+       unsigned char* to char* to fix type incompatibility.
+
+2004-08-23  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (get_key, search_key), gpgkeys_hkp.c (get_key,
+       search_key), gpgkeys_http.c (get_key): Do not give informational
+       logs since this is now done inside gpg.
+
+       * gpgkeys_hkp.c (dehtmlize): Understand the quote character
+       (i.e. "&quot;") in HTML responses.
+       (search_key): Search key must be unsigned for url encoder to work
+       properly for 8-bit values.
+
+       * gpgkeys_ldap.c (get_key): Factor out informational display into
+       new function build_info().
+
+       * gpgkeys_ldap.c (build_attrs): Properly terminate user ID strings
+       that got shrunk due to encoding.
+
+2004-08-22  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (find_basekeyspacedn): Use LDAP_SCOPE_BASE along
+       with a full DN rather than LDAP_SCOPE_ONELEVEL plus a filter to
+       find the pgpServerInfo object.  Some LDAP setups don't like the
+       search.
+       (main): Stop binding to the server since it seems no server really
+       requires it, and some require it not be there.
+
+2004-07-29  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (main): Add "debug" option.  This is only really
+       useful with OpenLDAP, but it's practically vital to debug SSL and
+       TLS setups.  Add "basedn" option.  This allows users to override
+       the autodetection for base DN.  SSL overrides TLS, so TLS will not
+       be started on SSL connections (starting an already started car).
+
+2004-07-28  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (build_attrs): Add "pgpKeySize" and "pgpSubKeyID"
+       attributes so we can do subkey searches.
+
+       * gpgkeys_ldap.c (main): Under certain error conditions, we might
+       try and unbind twice.  Don't.
+
+       * gpgkeys_ldap.c (join_two_modlists): New.
+       (send_key): Use new function so we can try a modify operation
+       first, and fail over to an add if that fails.  Add cannot cope
+       with the NULLs at the head of the modify request, so we jump into
+       the list in the middle.
+
+2004-07-27  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (main): Don't try and error out before making a
+       ldaps connection to the NAI keyserver since we cannot tell if it
+       is a NAI keyserver until we connect.  Fail if we cannot find a
+       base keyspace DN.  Fix a false success message for TLS being
+       enabled.
+
+2004-07-20  Werner Koch  <wk@gnupg.org>
+
+       * gpgkeys_ldap.c [_WIN32]: Include Windows specific header files.
+       Suggested by Brian Gladman.
+
+2004-05-26  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_http.c: General polish and removal of leftover stuff
+       from gpgkeys_hkp.c.
+
+2004-05-21  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_http.c (get_key): Cosmetic fix - make sure that URLs
+       with no path use a path of "/".
+
+       * gpgkeys_ldap.c (ldap2epochtime): We can always rely on timegm()
+       being available now, since it's a replacement function.
+
+2004-05-20  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_http.c: New program to do a simple HTTP file fetch using
+       the keyserver interface.
+
+       * Makefile.am: Build it.
+
+2004-02-28  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: Don't split LDADD across two lines since some make
+       programs can't handle blank lines after a \ continuation.  Noted
+       by Christoph Moench-Tegeder.
+
+2004-02-25  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (send_key): List pgpCertID as one of the deleted
+       attributes.  This guarantees that if something goes wrong, we
+       won't be able to complete the transaction, thus leaving any key
+       already existing on the server intact.
+
+2004-02-23  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (delete_one_attr): Removed.
+       (make_one_attr): Delete functionality added.  Optional deduping
+       functionality added (currently only used for pgpSignerID).
+       (build_attrs): Translate sig entries into pgpSignerID.  Properly
+       build the timestamp for pgpKeyCreateTime and pgpKeyExpireTime.
+
+2004-02-22  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (delete_one_attr): New function to replace
+       attributes with NULL (a "delete" that works even for nonexistant
+       attributes).
+       (send_key): Use it here to remove attributes so a modify operation
+       starts with a clean playing field.  Bias sends to modify before
+       add, since (I suspect) people update their existing keys more
+       often than they make and send new keys to the server.
+
+2004-02-21  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (epoch2ldaptime): New.  Converse of
+       ldap2epochtime.
+       (make_one_attr): New. Build a modification list in memory to send
+       to the LDAP server.
+       (build_attrs): New. Parse INFO lines sent over by gpg.
+       (free_mod_values): New.  Unwinds a modification list.
+       (send_key_keyserver): Renamed from old send_key().
+       (send_key): New function to send a key to a LDAP server.
+       (main): Use send_key() for real LDAP servers, send_key_keyserver()
+       otherwise.
+
+2004-02-20  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c: Replacement prototypes for setenv and unsetenv.
+       (search_key): Catch a SIZELIMIT_EXCEEDED error and show the user
+       whatever the server did give us.
+       (find_basekeyspacedn): There is no guarantee that namingContexts
+       will be readable.
+
+       * Makefile.am: Link gpgkeys_ldap with libutil.a to get the
+       replacement functions (and eventually translations, etc).
+
+2004-02-19  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (ldap2epochtime): LDAP timestamps are UTC, so do
+       not correct for timezones.
+       (main): Find the basekeyspacedn before we try to start TLS, so we
+       can give a better error message when a user tries to use TLS with
+       a LDAP keyserver.
+
+       * Makefile.am: Add automake conditionals to symlink gpgkeys_ldaps
+       to gpgkeys_ldap when needed.
+
+       * gpgkeys_ldap.c (main): Add support for LDAPS and TLS
+       connections.  These are only useful and usable when talking to
+       real LDAP keyservers.  Add new "tls" option to tune TLS use from
+       off, to try quietly, to try loudly, or to require TLS.
+
+       * gpgkeys_ldap.c (find_basekeyspacedn): New function to figure out
+       what kind of LDAP server we're talking to (either real LDAP or the
+       LDAP keyserver), and return the baseKeySpaceDN to find keys under.
+       (main): Call it from here, and remove the old code that only
+       handled the LDAP keyserver.
+
+2004-02-18  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (ldap_to_gpg_err): Make sure that
+       LDAP_OPT_ERROR_NUMBER is defined before we use it.
+
+       * gpgkeys_mailto.in: Fix VERSION number.
+
+2004-01-13  Werner Koch  <wk@gnupg.org>
+
+       * gpgkeys_hkp.c (send_key): Add a content type.
+
+2004-01-11  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (search_key): Catch a mangled input file (useful
+       if something other than GnuPG is calling the program).
+       (main): Avoid possible pre-string write.  Noted by Christian
+       Biere.
+
+       * gpgkeys_ldap.c (main): Avoid possible pre-string write.
+
+2003-12-28  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (send_key, get_key, main): Work with new HTTP code
+       that passes the proxy in from the outside.  If the command file
+       sends a proxy, use it.  If it sends "http-proxy" with no
+       arguments, use $http_proxy from the environment.  Suggested by
+       Christian Biere.
+
+2003-12-28  Stefan Bellon  <sbellon@sbellon.de>
+
+       * gpgkeys_hkp.c, gpgkeys_ldap.c [__riscos__]: Removal of
+       unnecessary #ifdef __riscos__ sections.
+
+2003-11-27  Werner Koch  <wk@gnupg.org>
+
+       * gpgkeys_hkp.c (get_key): Fixed invalid use of fprintf without
+       format string.
+
+2003-10-25  Werner Koch  <wk@gnupg.org>
+
+       * Makefile.am (gpgkeys_hkp_LDADD): Replaced INTLLIBS by LIBINTL.
+
+2003-07-10  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: Use W32LIBS where appropriate.
+
+2003-05-30  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c, gpgkeys_ldap.c: #include <getopt.h> if it is
+       available.  Also include extern references for optarg and optind
+       since there is no guarantee that any header file will include
+       them.  Standards?  We don't need no stinkin' standards.
+
+       * Makefile.am: Use @GETOPT@ to pull in libiberty on those
+       platforms that need it.
+
+2003-04-08  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (dehtmlize, parse_hkp_index): Fix memory
+       corruption bug on some platforms.
+
+2003-03-11  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (get_key): Properly handle CRLF line endings in
+       the armored key.
+       (main): Accept "try-dns-srv" option.
+
+       * Makefile.am: Use @CAPLIBS@ to link in -lcap if we are using
+       capabilities.  Use @SRVLIBS@ to link in the resolver if we are
+       using DNS SRV.
+
+2003-02-11  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: Use a local copy of libexecdir along with @PACKAGE@
+       so it can be easily overridden at make time.
+
+2003-01-29  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_mailto.in: Fix regexp to work properly if the "keyid" is
+       not a keyid, but rather a text string from the user ID.
+
+2003-01-06  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (get_key): Use options=mr when getting a key so
+       keyserver doesn't attach the HTML header which we will just have
+       to discard.
+
+2002-11-17  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (main), gpgkeys_hkp.c (main): Use new keyserver
+       protocol version.
+
+2002-11-14  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (get_key): The deduping code requires
+       "pgpcertid", but that was not available when running without
+       verbose on.  Noted by Stefan.
+
+2002-11-10  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (get_key): Fix typo in deduping code.
+
+2002-11-05  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (key_in_keylist, add_key_to_keylist,
+       free_keylist, get_key, search_key): The LDAP keyserver doesn't
+       remove duplicates, so remove them locally.  Do not include the key
+       modification time in the search response.
+
+2002-11-04  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (send_key), gpgkeys_ldap.c (send_key): Properly
+       handle an input file that does not include any key data at all.
+
+2002-10-24  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (main), gpgkeys_ldap.c (main): Add -V flag to
+       output protocol and program version.
+
+2002-10-21  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: Anything linking with libutil.a needs INTLLIBS as
+       well on platforms where INTLLIBS is set.
+
+2002-10-14  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (write_quoted): Use %-encoding instead of
+       \-encoding.
+       (parse_hkp_index): Use new keyserver key listing format, and add
+       support for disabled keys via include-disabled.
+
+       * gpgkeys_ldap.c (get_key): Don't print keysize unless it's >0.
+       (printquoted): Use %-encoding instead of \-encoding.
+       (search_key): Use new keyserver key listing format.
+
+2002-10-08  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (search_key, main): Make sure LDAP values are
+       freed in case of error.
+
+       * gpgkeys_ldap.c (fail_all): New function to unwind a keylist and
+       error each item.
+       (main): Call fail_all from here, as needed.  Also add a NO_MEMORY
+       error in an appropriate place and fix error return code.
+       (ldap_err_to_gpg_err): Add KEYSERVER_UNREACHABLE.
+
+       * gpgkeys_hkp.c (fail_all): New function to unwind a keylist and
+       error each item.
+       (main): Call fail_all from here.  Also add a NO_MEMORY error in an
+       appropriate place.
+       (get_key): Use new UNREACHABLE error for network errors.
+
+2002-09-26  Werner Koch  <wk@gnupg.org>
+
+       * gpgkeys_ldap.c (send_key): Removed non-constant initializers.
+
+2002-09-24  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (ldap_err_to_gpg_err, ldap_to_gpg_err, send_key,
+       get_key, search_key, main): Some minor error reporting
+       enhancements for use with GPA (show reasons for KEY FAILED).
+
+       * gpgkeys_hkp.c (send_key, get_key, search_key, main): Some minor
+       error reporting enhancements for use with GPA (show reasons for
+       KEY FAILED).
+
+2002-09-20  Werner Koch  <wk@gnupg.org>
+
+       * gpgkeys_hkp.c (handle_old_hkp_index): s/input/inp/ to avoid
+       shadowing warning.
+
+2002-09-19  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (get_key, handle_old_hkp_index, search_key):
+       Properly handle line truncation.
+
+2002-09-16  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_mailto.in: Add quasi-RFC-2368 mailto:email@addr?from=
+       syntax so people can set their own email address to respond to.
+
+       * gpgkeys_hkp.c (get_key): Properly respond with KEY FAILED (to
+       gpg) and "key not found" (to user) on failure.
+
+2002-09-13  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c: (search_key, handle_old_hkp_index): Try and
+       request a machine-readable key index.  If the server supports
+       this, pass it through.  If the server does not support it, parse
+       the "index" page.
+
+2002-09-12  Stefan Bellon  <sbellon@sbellon.de>
+
+       * gpgkeys_hkp.c: Tidied up RISC OS initializations.
+
+2002-09-12  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (main): Remove warning - this is no longer
+       experimental code.
+
+2002-09-09  Werner Koch  <wk@gnupg.org>
+
+       * gpgkeys_hkp.c (send_key, get_key, search_key): Check return
+       value of malloc.
+       (dehtmlize): Use ascii_tolower to protect against weird locales.
+       Cast the argument for isspace for the sake of broken HP/UXes.
+       (search_key): Check return value of realloc.
+
+2002-09-09  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (get_key): Some compilers (RISC OS, HPUX c89)
+       don't like using variables as array initializers.
+
+       * gpgkeys_hkp.c (send_key): Use CRLF in headers.
+
+2002-08-28  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (parse_hkp_index): Use same types on all
+       platforms.  This was probably leftover from earlier code where the
+       typing mattered.
+
+       * gpgkeys_hkp.c: Overall cleanup from iobuf conversion.  Be
+       consistent in m_alloc and malloc usage.  Remove include-disabled
+       (meaningless on HKP).  RISC OS tweak.
+
+2002-08-27  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c, Makefile.am: Convert over to using iobufs.
+
+       * gpgkeys_hkp.c (http_get, http_post): Use CRLF for line endings.
+
+       * gpgkeys_hkp.c: Include util.h on RISC OS as per Stefan.  Include
+       a replacement for hstrerror() for those platforms (such as RISC
+       OS) that don't have it.
+
+2002-08-26  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: May as well include gpgkeys_hkp.c in the
+       distribution now.  It works well enough without proxies, and isn't
+       built by default.  It would be good to get some test experience
+       with it.
+
+       * gpgkeys_hkp.c (main): Don't warn about include-subkeys - it
+       isn't unsupported, it's actually non-meaningful in the context of
+       HKP (yet).
+
+       * gpgkeys_hkp.c (parse_hkp_index, dehtmlize): Move HTML
+       functionality into new "dehtmlize" function.  Remove HTML before
+       trying to parse each line from the keyserver.  If the keyserver
+       provides key type information in the listing, use it.  (Copy over
+       from g10/hkp.c).
+
+2002-08-19  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (get_key, parse_hkp_index): Bring over latest code
+       from g10/hkp.c.
+
+       * gpgkeys_ldap.c (get_key): Fix cosmetic URL display problem
+       (extra ":" at the end).
+
+2002-08-03  Stefan Bellon  <sbellon@sbellon.de>
+
+       * gpgkeys_ldap.c: Tidied up RISC OS initializations.
+
+2002-07-25  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c: "Warning" -> "WARNING"
+
+2002-07-24  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: Install keyserver helpers in @GNUPG_LIBEXECDIR@
+
+2002-07-15  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (send_key, get_key, main): Consult the server
+       version string to determine whether to use pgpKey or pgpKeyV2.
+
+2002-07-09  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_mailto.in: Use new OPAQUE tag for non net-path URIs.
+       Fail more elegantly if there is no email address to send to.  Show
+       the GnuPG version in the message body.
+
+2002-07-04  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (get_key), gpgkeys_hkp.c (get_key): Display
+       keyserver URI as a URI, but only if verbose.
+
+2002-07-01  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (parse_hkp_index): Error if the keyserver returns
+       an unparseable HKP response.
+
+       * gpgkeys_hkp.c (main): Warn on honor-http-proxy,
+       broken-http-proxy, and include-subkeys (not supported yet).
+
+       * gpgkeys_ldap.c (main), gpgkeys_hkp.c (http_connect, main): Fix
+       some shadowing warnings.
+
+2002-06-11  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am: Don't hard-code the LDAP libraries - get them from
+       LDAPLIBS via configure.  Also, gpgkeys_hkp is a program, not a
+       script.
+
+2002-06-10  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (include_subkeys): Default "include-subkeys" to
+       off, since GnuPG now defaults it to on.
+
+2002-06-06  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_hkp.c (parse_hkp_index): Type tweaks.
+
+       * gpgkeys_hkp.c (main): Add experimental code warning.
+
+2002-06-05  David Shaw  <dshaw@jabberwocky.com>
+
+       * Makefile.am, gpgkeys_hkp.c (new): Experimental HKP keyserver
+       interface.
+
+2002-05-08  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c: Include <lber.h> if we absolutely must.  This
+       helps when compiling against a very old OpenLDAP.
+
+2002-04-29  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_mailto.in: Properly handle key requests in full
+       fingerprint form.
+
+2002-03-29  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (printquoted): Quote backslashes within keyserver
+       search responses.
+
+2002-02-25  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap (get_key): LDAP keyservers do not support v3
+       fingerprints, so error out if someone tries.  Actually, they don't
+       support any fingerprints, but at least we can calculate a keyid
+       from a v4 fingerprint.
+
+2002-02-23  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap: Clarify the notion of a partial failure.  This is
+       possible if more than one key is being handled in a batch, and one
+       fails while the other succeeds.  Note that a search that comes up
+       with no results is not a failure - that is a valid response of "no
+       answer".
+
+       * gpgkeys_ldap.c (get_key): Allow GnuPG to send us full v4
+       fingerprints, long key ids, or short key ids while fetching.
+       Since the LDAP server doesn't actually handle fingerprints, chop
+       them down to long key ids for actual use.
+
+       * gpgkeys_ldap.c (main, get_key): When searching for a keyid,
+       search for subkeys as well as primary keys.  This is mostly
+       significant when automatically fetching the key based on the id in
+       a header (i.e. "signature made by....").  "no-include-subkeys"
+       disables.
+
+2002-02-14  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c: Fix compiler warning.
+
+       * gpgkeys_ldap.c: Be much more robust with mangled input files.
+
+2001-12-28  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_mailto.in: Use the new OUTOFBAND indicator so gpg knows
+       not to try and import anything.  Also turn on perl -w for
+       warnings.
+
+       * gpgkeys_ldap.c (main): If we're using temp files (rather than
+       stdin/stdout), make sure the file is closed when we're done.
+
+2001-12-20  David Shaw  <dshaw@jabberwocky.com>
+
+       * Properly free the LDAP response when we're done with it.
+
+       * Now that we handle multiple keys, we must remove duplicates as
+       the LDAP keyserver returns keys with multiple user IDs multiple
+       times.
+
+       * Properly handle multiple keys with the same key ID (it's really
+       rare, so fetch "0xDEADBEEF" to test this).
+
+2001-12-17  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c, gpgkeys_mailto.in: Fix GNU capitalization
+       issues.  Prefix log messages with "gpgkeys" to clarify which
+       program is generating them.
+
+2001-12-14  David Shaw  <dshaw@jabberwocky.com>
+
+       * gpgkeys_ldap.c (search_key): Use unsigned int rather than uint
+       for portability.
+
+2001-12-04  David Shaw  <dshaw@jabberwocky.com>
+
+       * Initial version of gpgkeys_ldap (LDAP keyserver helper) and
+       gpgkeys_mailto (email keyserver helper)
+
+
+ Copyright 1998, 1999, 2000, 2001, 2002, 2003,
+          2004 Free Software Foundation, Inc.
+
+ 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.
diff --git a/keyserver/Makefile.am b/keyserver/Makefile.am
new file mode 100644 (file)
index 0000000..72572de
--- /dev/null
@@ -0,0 +1,53 @@
+# Copyright (C) 2001, 2002, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+## Process this file with automake to produce Makefile.in
+
+INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)/intl
+EXTRA_PROGRAMS = gpgkeys_ldap gpgkeys_hkp gpgkeys_finger gpgkeys_curl
+EXTRA_SCRIPTS = gpgkeys_mailto
+
+gpglibexecdir = $(libexecdir)/@PACKAGE@
+
+gpglibexec_PROGRAMS = @GPGKEYS_LDAP@ @GPGKEYS_HKP@ @GPGKEYS_FINGER@ @GPGKEYS_CURL@
+gpglibexec_SCRIPTS = @GPGKEYS_MAILTO@
+noinst_SCRIPTS = gpgkeys_test
+
+gpgkeys_ldap_SOURCES = gpgkeys_ldap.c ksutil.c ksutil.h
+gpgkeys_hkp_SOURCES = gpgkeys_hkp.c ksutil.c ksutil.h
+gpgkeys_finger_SOURCES = gpgkeys_finger.c ksutil.c ksutil.h
+gpgkeys_curl_SOURCES = gpgkeys_curl.c ksutil.c ksutil.h
+
+other_libs = $(LIBICONV) $(LIBINTL) $(CAPLIBS)
+
+gpgkeys_ldap_CPPFLAGS = @LDAP_CPPFLAGS@
+gpgkeys_ldap_LDADD = ../util/libutil.a @LDAPLIBS@ @NETLIBS@ $(other_libs) @GETOPT@ @W32LIBS@
+
+gpgkeys_finger_LDADD = ../util/libutil.a @NETLIBS@ $(other_libs) @GETOPT@ @W32LIBS@
+
+if FAKE_CURL
+gpgkeys_curl_SOURCES += curl-shim.c curl-shim.h
+gpgkeys_curl_LDADD = ../util/libutil.a @NETLIBS@ @SRVLIBS@ $(other_libs) @GETOPT@ @W32LIBS@
+gpgkeys_hkp_SOURCES += curl-shim.c curl-shim.h
+gpgkeys_hkp_LDADD = ../util/libutil.a @NETLIBS@ @SRVLIBS@ $(other_libs) @GETOPT@ @W32LIBS@
+else
+gpgkeys_curl_CPPFLAGS = @LIBCURL_CPPFLAGS@
+gpgkeys_curl_LDADD = @LIBCURL@ @GETOPT@
+gpgkeys_hkp_CPPFLAGS = @LIBCURL_CPPFLAGS@
+gpgkeys_hkp_LDADD = @LIBCURL@ @GETOPT@
+endif
diff --git a/keyserver/curl-shim.c b/keyserver/curl-shim.c
new file mode 100644 (file)
index 0000000..4a4d18f
--- /dev/null
@@ -0,0 +1,324 @@
+/* curl-shim.c - Implement a small subset of the curl API in terms of
+ * the iobuf HTTP API
+ *
+ * Copyright (C) 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include "http.h"
+#include "util.h"
+#include "ksutil.h"
+#include "curl-shim.h"
+
+static CURLcode
+handle_error(CURL *curl,CURLcode err,const char *str)
+{
+  if(curl->errorbuffer)
+    {
+      /* Make sure you never exceed CURL_ERROR_SIZE, currently set to
+        256 in curl-shim.h */
+      switch(err)
+       {
+       case CURLE_OK:
+         strcpy(curl->errorbuffer,"okay");
+         break;
+
+       case CURLE_UNSUPPORTED_PROTOCOL:
+         strcpy(curl->errorbuffer,"unsupported protocol");
+         break;
+
+       case CURLE_COULDNT_CONNECT:
+         strcpy(curl->errorbuffer,"couldn't connect");
+         break;
+
+       case CURLE_WRITE_ERROR:
+         strcpy(curl->errorbuffer,"write error");
+         break;
+
+       case CURLE_HTTP_RETURNED_ERROR:
+         sprintf(curl->errorbuffer,"url returned error %u",curl->status);
+         break;
+
+       default:
+         strcpy(curl->errorbuffer,"generic error");
+         break;
+       }
+
+      if(str && (strlen(curl->errorbuffer)+2+strlen(str)+1)<=CURL_ERROR_SIZE)
+       {
+         strcat(curl->errorbuffer,": ");
+         strcat(curl->errorbuffer,str);
+       }
+    }
+
+  return err;
+}
+
+CURLcode
+curl_global_init(long flags)
+{
+  return CURLE_OK;
+}
+
+void
+curl_global_cleanup(void) {}
+
+CURL *
+curl_easy_init(void)
+{
+  CURL *handle;
+
+  handle=calloc(1,sizeof(CURL));
+  if(handle)
+    handle->errors=stderr;
+
+  return handle;
+}
+
+void
+curl_easy_cleanup(CURL *curl)
+{
+  free(curl);
+}
+
+CURLcode
+curl_easy_setopt(CURL *curl,CURLoption option,...)
+{
+  va_list ap;
+
+  va_start(ap,option);
+
+  switch(option)
+    {
+    case CURLOPT_URL:
+      curl->url=va_arg(ap,char *);
+      break;
+    case CURLOPT_USERPWD:
+      curl->auth=va_arg(ap,char *);
+      break;
+    case CURLOPT_WRITEFUNCTION:
+      curl->writer=va_arg(ap,write_func);
+      break;
+    case CURLOPT_FILE:
+      curl->file=va_arg(ap,void *);
+      break;
+    case CURLOPT_ERRORBUFFER:
+      curl->errorbuffer=va_arg(ap,char *);
+      break;
+    case CURLOPT_PROXY:
+      curl->proxy=va_arg(ap,char *);
+      break;
+    case CURLOPT_POST:
+      curl->flags.post=va_arg(ap,unsigned int);
+      break;
+    case CURLOPT_POSTFIELDS:
+      curl->postfields=va_arg(ap,char *);
+      break;
+    case CURLOPT_FAILONERROR:
+      curl->flags.failonerror=va_arg(ap,unsigned int);
+      break;
+    case CURLOPT_VERBOSE:
+      curl->flags.verbose=va_arg(ap,unsigned int);
+      break;
+    case CURLOPT_STDERR:
+      curl->errors=va_arg(ap,FILE *);
+      break;
+    default:
+      /* We ignore the huge majority of curl options */
+      break;
+    }
+
+  return handle_error(curl,CURLE_OK,NULL);
+}
+
+CURLcode
+curl_easy_perform(CURL *curl)
+{
+  int rc;
+  CURLcode err=CURLE_OK;
+  const char *errstr=NULL;
+  char *proxy=NULL;
+
+  /* Emulate the libcurl proxy behavior.  If the calling program set a
+     proxy, use it.  If it didn't set a proxy or set it to NULL, check
+     for one in the environment.  If the calling program explicitly
+     set a null-string proxy, don't set a proxy at all. */
+
+  if(curl->proxy)
+    {
+      if(*curl->proxy)
+       proxy=curl->proxy;
+    }
+  else
+    proxy=getenv(HTTP_PROXY_ENV);
+
+  if(curl->flags.verbose)
+    fprintf(curl->errors,"* HTTP proxy is \"%s\"\n",proxy?proxy:"null");
+
+  if(curl->flags.post)
+    {
+      rc=http_open(&curl->hd,HTTP_REQ_POST,curl->url,curl->auth,0,proxy);
+      if(rc==0)
+       {
+         char content_len[50];
+         unsigned int post_len=strlen(curl->postfields);
+
+         iobuf_writestr(curl->hd.fp_write,
+                        "Content-Type: application/x-www-form-urlencoded\r\n");
+         sprintf(content_len,"Content-Length: %u\r\n",post_len);
+
+         iobuf_writestr(curl->hd.fp_write,content_len);
+
+         http_start_data(&curl->hd);
+         iobuf_write(curl->hd.fp_write,curl->postfields,post_len);
+         rc=http_wait_response(&curl->hd,&curl->status);
+         if(rc==0 && curl->flags.failonerror && curl->status>=300)
+           err=CURLE_HTTP_RETURNED_ERROR;
+       }
+    }
+  else
+    {
+      rc=http_open(&curl->hd,HTTP_REQ_GET,curl->url,curl->auth,0,proxy);
+      if(rc==0)
+       {
+         rc=http_wait_response(&curl->hd,&curl->status);
+         if(rc==0)
+           {
+             if(curl->flags.failonerror && curl->status>=300)
+               err=CURLE_HTTP_RETURNED_ERROR;
+             else
+               {
+                 unsigned int maxlen=1024,buflen,len;
+                 byte *line=NULL;
+
+                 while((len=iobuf_read_line(curl->hd.fp_read,
+                                            &line,&buflen,&maxlen)))
+                   {
+                     size_t ret;
+
+                     maxlen=1024;
+
+                     ret=(curl->writer)(line,len,1,curl->file);
+                     if(ret!=len)
+                       {
+                         err=CURLE_WRITE_ERROR;
+                         break;
+                       }
+                   }
+
+                 xfree(line);
+                 http_close(&curl->hd);
+               }
+           }
+         else
+           http_close(&curl->hd);
+       }
+    }
+
+  switch(rc)
+    {
+    case 0:
+      break;
+
+    case G10ERR_INVALID_URI:
+      err=CURLE_UNSUPPORTED_PROTOCOL;
+      break;
+
+    case G10ERR_NETWORK:
+      errstr=strerror(errno);
+      err=CURLE_COULDNT_CONNECT;
+      break;
+
+    default:
+      errstr=g10_errstr(rc);
+      err=CURLE_COULDNT_CONNECT;
+      break;
+    }
+      
+  return handle_error(curl,err,errstr);
+}
+
+/* This is not the same exact set that is allowed according to
+   RFC-2396, but it is what the real curl uses. */
+#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
+                        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+                        "0123456789"
+
+char *
+curl_escape(char *str,int length)
+{
+  int len,max,idx,enc_idx=0;
+  char *enc;
+
+  if(length)
+    len=length;
+  else
+    len=strlen(str);
+
+  enc=malloc(len+1);
+  if(!enc)
+    return enc;
+
+  max=len;
+
+  for(idx=0;idx<len;idx++)
+    {
+      if(enc_idx+3>max)
+       {
+         char *tmp;
+
+         max+=100;
+
+         tmp=realloc(enc,max+1);
+         if(!tmp)
+           {
+             free(enc);
+             return NULL;
+           }
+
+         enc=tmp;
+       }
+
+      if(strchr(VALID_URI_CHARS,str[idx]))
+       enc[enc_idx++]=str[idx];
+      else
+       {
+         char numbuf[5];
+         sprintf(numbuf,"%%%02X",str[idx]);
+         strcpy(&enc[enc_idx],numbuf);
+         enc_idx+=3;
+       }
+    }
+
+  enc[enc_idx]='\0';
+
+  return enc;
+}
+
+void
+curl_free(char *ptr)
+{
+  free(ptr);
+}
diff --git a/keyserver/curl-shim.h b/keyserver/curl-shim.h
new file mode 100644 (file)
index 0000000..91eac9d
--- /dev/null
@@ -0,0 +1,92 @@
+/* curl-shim.h
+ * Copyright (C) 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#ifndef _CURL_SHIM_H_
+#define _CURL_SHIM_H_
+
+#include "http.h"
+
+typedef enum
+  {
+    CURLE_OK=0,
+    CURLE_UNSUPPORTED_PROTOCOL=1,
+    CURLE_COULDNT_CONNECT=7,
+    CURLE_FTP_COULDNT_RETR_FILE=19,
+    CURLE_HTTP_RETURNED_ERROR=22,
+    CURLE_WRITE_ERROR=23
+  } CURLcode;
+
+typedef enum
+  {
+    CURLOPT_URL,
+    CURLOPT_USERPWD,
+    CURLOPT_WRITEFUNCTION,
+    CURLOPT_FILE,
+    CURLOPT_ERRORBUFFER,
+    CURLOPT_FOLLOWLOCATION,
+    CURLOPT_MAXREDIRS,
+    CURLOPT_STDERR,
+    CURLOPT_VERBOSE,
+    CURLOPT_SSL_VERIFYPEER,
+    CURLOPT_PROXY,
+    CURLOPT_CAINFO,
+    CURLOPT_POST,
+    CURLOPT_POSTFIELDS,
+    CURLOPT_FAILONERROR
+  } CURLoption;
+
+typedef size_t (*write_func)(char *buffer,size_t size,
+                            size_t nitems,void *outstream);
+
+typedef struct
+{
+  char *url;
+  char *auth;
+  char *errorbuffer;
+  char *proxy;
+  write_func writer;
+  void *file;
+  char *postfields;
+  unsigned int status;
+  FILE *errors;
+  struct
+  {
+    unsigned int post:1;
+    unsigned int failonerror:1;
+    unsigned int verbose:1;
+  } flags;
+  struct http_context hd;
+} CURL;
+
+#define CURL_ERROR_SIZE 256
+#define CURL_GLOBAL_DEFAULT 0
+
+CURLcode curl_global_init(long flags);
+void curl_global_cleanup(void);
+CURL *curl_easy_init(void);
+CURLcode curl_easy_setopt(CURL *curl,CURLoption option,...);
+CURLcode curl_easy_perform(CURL *curl);
+void curl_easy_cleanup(CURL *curl);
+char *curl_escape(char *str,int len);
+void curl_free(char *ptr);
+#define curl_version() "GnuPG curl-shim "VERSION
+
+#endif /* !_CURL_SHIM_H_ */
diff --git a/keyserver/gpgkeys_curl.c b/keyserver/gpgkeys_curl.c
new file mode 100644 (file)
index 0000000..9e4b567
--- /dev/null
@@ -0,0 +1,369 @@
+/* gpgkeys_curl.c - fetch a key via libcurl
+ * Copyright (C) 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifdef HAVE_LIBCURL
+#include <curl/curl.h>
+#else
+#include "curl-shim.h"
+#endif
+#include "keyserver.h"
+#include "ksutil.h"
+
+extern char *optarg;
+extern int optind;
+
+static FILE *input,*output,*console;
+static CURL *curl;
+static struct ks_options *opt;
+
+static int
+get_key(char *getkey)
+{
+  CURLcode res;
+  char errorbuffer[CURL_ERROR_SIZE];
+  char request[MAX_URL];
+  struct curl_writer_ctx ctx;
+
+  memset(&ctx,0,sizeof(ctx));
+
+  if(strncmp(getkey,"0x",2)==0)
+    getkey+=2;
+
+  fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+
+  sprintf(request,"%s://%s%s%s%s",opt->scheme,opt->host,
+         opt->port?":":"",opt->port?opt->port:"",opt->path?opt->path:"/");
+
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_writer);
+  ctx.stream=output;
+  curl_easy_setopt(curl,CURLOPT_FILE,&ctx);
+  curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
+
+  res=curl_easy_perform(curl);
+  if(res!=CURLE_OK)
+    {
+      fprintf(console,"gpgkeys: %s fetch error %d: %s\n",opt->scheme,
+             res,errorbuffer);
+      fprintf(output,"\nKEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
+    }
+  else
+    {
+      curl_writer_finalize(&ctx);
+      if(!ctx.flags.done)
+       {
+         fprintf(console,"gpgkeys: no key data found for %s\n",request);
+         fprintf(output,"\nKEY 0x%s FAILED %d\n",
+                 getkey,KEYSERVER_KEY_NOT_FOUND);
+       }
+      else
+       fprintf(output,"\nKEY 0x%s END\n",getkey);
+    }
+
+  return curl_err_to_gpg_err(res);
+}
+
+static void 
+show_help (FILE *fp)
+{
+  fprintf (fp,"-h\thelp\n");
+  fprintf (fp,"-V\tversion\n");
+  fprintf (fp,"-o\toutput to this file\n");
+}
+
+int
+main(int argc,char *argv[])
+{
+  int arg,ret=KEYSERVER_INTERNAL_ERROR;
+  char line[MAX_LINE];
+  char *thekey=NULL;
+  long follow_redirects=5;
+  char *proxy=NULL;
+
+  console=stderr;
+
+  /* Kludge to implement standard GNU options.  */
+  if (argc > 1 && !strcmp (argv[1], "--version"))
+    {
+      fputs ("gpgkeys_curl (GnuPG) " VERSION"\n", stdout);
+      return 0;
+    }
+  else if (argc > 1 && !strcmp (argv[1], "--help"))
+    {
+      show_help (stdout);
+      return 0;
+    }
+
+  while((arg=getopt(argc,argv,"hVo:"))!=-1)
+    switch(arg)
+      {
+      default:
+      case 'h':
+        show_help (console);
+       return KEYSERVER_OK;
+
+      case 'V':
+       fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
+       return KEYSERVER_OK;
+
+      case 'o':
+       output=fopen(optarg,"wb");
+       if(output==NULL)
+         {
+           fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
+                   optarg,strerror(errno));
+           return KEYSERVER_INTERNAL_ERROR;
+         }
+
+       break;
+      }
+
+  if(argc>optind)
+    {
+      input=fopen(argv[optind],"r");
+      if(input==NULL)
+       {
+         fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
+                 argv[optind],strerror(errno));
+         return KEYSERVER_INTERNAL_ERROR;
+       }
+    }
+
+  if(input==NULL)
+    input=stdin;
+
+  if(output==NULL)
+    output=stdout;
+
+  opt=init_ks_options();
+  if(!opt)
+    return KEYSERVER_NO_MEMORY;
+
+  /* Get the command and info block */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    {
+      int err;
+      char option[MAX_OPTION+1];
+
+      if(line[0]=='\n')
+       break;
+
+      err=parse_ks_options(line,opt);
+      if(err>0)
+       {
+         ret=err;
+         goto fail;
+       }
+      else if(err==0)
+       continue;
+
+      if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "s\n",option)==1)
+       {
+         int no=0;
+         char *start=&option[0];
+
+         option[MAX_OPTION]='\0';
+
+         if(strncasecmp(option,"no-",3)==0)
+           {
+             no=1;
+             start=&option[3];
+           }
+
+         if(strncasecmp(start,"http-proxy",10)==0)
+           {
+             /* Safe to not check the return code of strdup() here.
+                If it fails, we simply won't use a proxy. */
+             if(no)
+               {
+                 free(proxy);
+                 proxy=strdup("");
+               }
+             else if(start[10]=='=')
+               {
+                 if(strlen(&start[11])<MAX_PROXY)
+                   {
+                     free(proxy);
+                     proxy=strdup(&start[11]);
+                   }
+               }
+           }
+         else if(strncasecmp(start,"follow-redirects",16)==0)
+           {
+             if(no)
+               follow_redirects=0;
+             else if(start[16]=='=')
+               follow_redirects=atoi(&start[17]);
+             else if(start[16]=='\0')
+               follow_redirects=-1;
+           }
+
+         continue;
+       }
+    }
+
+  if(!opt->scheme)
+    {
+      fprintf(console,"gpgkeys: no scheme supplied!\n");
+      ret=KEYSERVER_SCHEME_NOT_FOUND;
+      goto fail;
+    }
+
+  if(!opt->host)
+    {
+      fprintf(console,"gpgkeys: no keyserver host provided\n");
+      goto fail;
+    }
+
+  if(opt->timeout && register_timeout()==-1)
+    {
+      fprintf(console,"gpgkeys: unable to register timeout handler\n");
+      return KEYSERVER_INTERNAL_ERROR;
+    }
+
+  curl_global_init(CURL_GLOBAL_DEFAULT);
+  curl=curl_easy_init();
+  if(!curl)
+    {
+      fprintf(console,"gpgkeys: unable to initialize curl\n");
+      ret=KEYSERVER_INTERNAL_ERROR;
+      goto fail;
+    }
+
+  if(follow_redirects)
+    {
+      curl_easy_setopt(curl,CURLOPT_FOLLOWLOCATION,1);
+      if(follow_redirects>0)
+       curl_easy_setopt(curl,CURLOPT_MAXREDIRS,follow_redirects);
+    }
+
+  if(opt->auth)
+    curl_easy_setopt(curl,CURLOPT_USERPWD,opt->auth);
+
+  if(opt->debug)
+    {
+      fprintf(console,"gpgkeys: curl version = %s\n",curl_version());
+      curl_easy_setopt(curl,CURLOPT_STDERR,console);
+      curl_easy_setopt(curl,CURLOPT_VERBOSE,1);
+    }
+
+  curl_easy_setopt(curl,CURLOPT_SSL_VERIFYPEER,opt->flags.check_cert);
+  curl_easy_setopt(curl,CURLOPT_CAINFO,opt->ca_cert_file);
+
+  if(proxy)
+    curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
+
+  /* If it's a GET or a SEARCH, the next thing to come in is the
+     keyids.  If it's a SEND, then there are no keyids. */
+
+  if(opt->action==KS_GET)
+    {
+      /* Eat the rest of the file */
+      for(;;)
+       {
+         if(fgets(line,MAX_LINE,input)==NULL)
+           break;
+         else
+           {
+             if(line[0]=='\n' || line[0]=='\0')
+               break;
+
+             if(!thekey)
+               {
+                 thekey=strdup(line);
+                 if(!thekey)
+                   {
+                     fprintf(console,"gpgkeys: out of memory while "
+                             "building key list\n");
+                     ret=KEYSERVER_NO_MEMORY;
+                     goto fail;
+                   }
+
+                 /* Trim the trailing \n */
+                 thekey[strlen(line)-1]='\0';
+               }
+           }
+       }
+    }
+  else
+    {
+      fprintf(console,
+             "gpgkeys: this keyserver type only supports key retrieval\n");
+      goto fail;
+    }
+
+  if(!thekey)
+    {
+      fprintf(console,"gpgkeys: invalid keyserver instructions\n");
+      goto fail;
+    }
+
+  /* Send the response */
+
+  fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
+  fprintf(output,"PROGRAM %s\n\n",VERSION);
+
+  if(opt->verbose)
+    {
+      fprintf(console,"Scheme:\t\t%s\n",opt->scheme);
+      fprintf(console,"Host:\t\t%s\n",opt->host);
+      if(opt->port)
+       fprintf(console,"Port:\t\t%s\n",opt->port);
+      if(opt->path)
+       fprintf(console,"Path:\t\t%s\n",opt->path);
+      fprintf(console,"Command:\tGET\n");
+    }
+
+  set_timeout(opt->timeout);
+
+  ret=get_key(thekey);
+
+ fail:
+
+  free(thekey);
+
+  if(input!=stdin)
+    fclose(input);
+
+  if(output!=stdout)
+    fclose(output);
+
+  free_ks_options(opt);
+
+  if(curl)
+    curl_easy_cleanup(curl);
+
+  free(proxy);
+
+  curl_global_cleanup();
+
+  return ret;
+}
diff --git a/keyserver/gpgkeys_finger.c b/keyserver/gpgkeys_finger.c
new file mode 100644 (file)
index 0000000..4124ebc
--- /dev/null
@@ -0,0 +1,534 @@
+/* gpgkeys_finger.c - fetch a key via finger
+ * Copyright (C) 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#endif
+
+#define INCLUDED_BY_MAIN_MODULE 1
+#include "util.h"
+#include "keyserver.h"
+#include "ksutil.h"
+
+#ifdef _WIN32
+#define sock_close(a)  closesocket(a)
+#else
+#define sock_close(a)  close(a)
+#endif
+
+extern char *optarg;
+extern int optind;
+
+static FILE *input,*output,*console;
+static struct ks_options *opt;
+
+#ifdef _WIN32
+static void
+deinit_sockets (void)
+{
+  WSACleanup();
+}
+
+static void
+init_sockets (void)
+{
+  static int initialized;
+  static WSADATA wsdata;
+
+  if (initialized)
+    return;
+
+  if (WSAStartup (0x0101, &wsdata) )
+    {
+      fprintf (console, "error initializing socket library: ec=%d\n", 
+               (int)WSAGetLastError () );
+      return;
+    }
+  if (wsdata.wVersion < 0x0001)
+    {
+      fprintf (console, "socket library version is %x.%x - but 1.1 needed\n",
+               LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion));
+      WSACleanup();
+      return;
+    }
+  atexit  (deinit_sockets);
+  initialized = 1;
+}
+#endif /*_WIN32*/
+
+
+/* Connect to SERVER at PORT and return a file descriptor or -1 on
+   error. */
+static int
+connect_server (const char *server, unsigned short port)
+{
+  int sock = -1;
+
+#ifdef _WIN32
+  struct hostent *hp;
+  struct sockaddr_in addr;
+  unsigned long l;
+
+  init_sockets ();
+
+  memset (&addr, 0, sizeof addr);
+  addr.sin_family = AF_INET;
+  addr.sin_port = htons (port);
+
+  /* Win32 gethostbyname doesn't handle IP addresses internally, so we
+     try inet_addr first on that platform only. */
+  if ((l = inet_addr (server)) != INADDR_NONE) 
+    memcpy (&addr.sin_addr, &l, sizeof l);
+  else if ((hp = gethostbyname (server))) 
+    {
+      if (hp->h_addrtype != AF_INET)
+        {
+          fprintf (console, "gpgkeys: unknown address family for `%s'\n",
+                   server);
+          return -1;
+        }
+      if (hp->h_length != 4)
+        {
+          fprintf (console, "gpgkeys: illegal address length for `%s'\n",
+                   server);
+          return -1;
+        }
+      memcpy (&addr.sin_addr, hp->h_addr, hp->h_length);
+    }
+  else
+    {
+      fprintf (console, "gpgkeys: host `%s' not found: ec=%d\n",
+               server, (int)WSAGetLastError ());
+      return -1;
+    }
+
+  sock = socket (AF_INET, SOCK_STREAM, 0);
+  if (sock == INVALID_SOCKET)
+    {
+      fprintf (console, "gpgkeys: error creating socket: ec=%d\n", 
+               (int)WSAGetLastError ());
+      return -1;
+    }
+
+  if (connect (sock, (struct sockaddr *)&addr, sizeof addr))
+    {
+      fprintf (console, "gpgkeys: error connecting `%s': ec=%d\n", 
+               server, (int)WSAGetLastError ());
+      sock_close (sock);
+      return -1;
+    }
+
+#else
+
+  struct sockaddr_in addr;
+  struct hostent *host;
+
+  addr.sin_family = AF_INET;
+  addr.sin_port = htons (port);
+  host = gethostbyname ((char*)server);
+  if (!host)
+    {
+      fprintf (console, "gpgkeys: host `%s' not found: %s\n",
+               server, strerror (errno));
+      return -1;
+    }
+  
+  addr.sin_addr = *(struct in_addr*)host->h_addr;
+
+  sock = socket (AF_INET, SOCK_STREAM, 0);
+  if (sock == -1)
+    {
+      fprintf (console, "gpgkeys: error creating socket: %s\n", 
+               strerror (errno));
+      return -1;
+    }
+  
+  if (connect (sock, (struct sockaddr *)&addr, sizeof addr) == -1)
+    {
+      fprintf (console, "gpgkeys: error connecting `%s': %s\n", 
+               server, strerror (errno));
+      close (sock);
+      return -1;
+    }
+#endif
+    
+  return sock;
+}
+
+static int
+write_server (int sock, const char *data, size_t length)
+{
+  int nleft;
+
+  nleft = length;
+  while (nleft > 0) 
+    {
+      int nwritten;
+      
+#ifdef _WIN32  
+      nwritten = send (sock, data, nleft, 0);
+      if ( nwritten == SOCKET_ERROR )
+        {
+          fprintf (console, "gpgkeys: write failed: ec=%d\n",
+                   (int)WSAGetLastError ());
+          return -1;
+        }
+#else
+      nwritten = write (sock, data, nleft);
+      if (nwritten == -1)
+        {
+          if (errno == EINTR)
+            continue;
+          if (errno == EAGAIN)
+            {
+              struct timeval tv;
+              
+              tv.tv_sec =  0;
+              tv.tv_usec = 50000;
+              select(0, NULL, NULL, NULL, &tv);
+              continue;
+           }
+          fprintf (console, "gpgkeys: write failed: %s\n", strerror(errno));
+          return -1;
+       }
+#endif
+      nleft -=nwritten;
+      data += nwritten;
+    }
+  
+  return 0;
+}
+
+
+/* Send the finger REQUEST to the server.  Returns 0 and a file descriptor
+   in R_SOCK if the request was sucessful. */
+static int
+send_request (const char *request, int *r_sock)
+{
+  char *server;
+  char *name;
+  int sock;
+
+  *r_sock = -1;
+  name = strdup (request);
+  if (!name)
+    {
+      fprintf(console,"gpgkeys: out of memory\n");
+      return KEYSERVER_NO_MEMORY;
+    }
+
+  server = strchr (name, '@');
+  if (!server)
+    {
+      fprintf (console, "gpgkeys: no name included in request\n");
+      free (name);
+      return KEYSERVER_GENERAL_ERROR;
+    }
+  *server++ = 0;
+  
+  sock = connect_server (server, 79);
+  if (sock == -1)
+    {
+      free (name);
+      return KEYSERVER_UNREACHABLE;
+    }
+
+  if (write_server (sock, name, strlen (name))
+      || write_server (sock, "\r\n", 2))
+    {
+      free (name);
+      sock_close (sock);
+      return KEYSERVER_GENERAL_ERROR;
+    }
+  free (name);
+  *r_sock = sock;
+  return 0;
+}
+
+
+
+static int
+get_key (char *getkey)
+{
+  int rc;
+  int sock;
+  IOBUF fp_read;
+  unsigned int maxlen, buflen, gotit=0;
+  byte *line = NULL;
+
+  if (strncmp (getkey,"0x",2)==0)
+    getkey+=2;
+
+  /* Frankly we don't know what keys the server will return; we
+     indicated the requested key anyway. */
+  fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+
+  rc=send_request(opt->opaque,&sock);
+  if(rc)
+    {
+      fprintf(output,"KEY 0x%s FAILED %d\n",getkey, rc);
+      sock_close (sock);
+      return KEYSERVER_OK;
+    }
+  
+  /* Hmmm, we use iobuf here only to cope with Windows socket
+     peculiarities (we can't used fdopen).  */
+  fp_read = iobuf_sockopen (sock , "r");
+  if (!fp_read)
+    {
+      fprintf(output,"KEY 0x%s FAILED %d\n",getkey, KEYSERVER_INTERNAL_ERROR);
+      sock_close (sock);
+      return KEYSERVER_OK;
+    }
+
+  while ( iobuf_read_line ( fp_read, &line, &buflen, &maxlen))
+    {
+      maxlen=1024;
+      
+      if(gotit)
+        {
+         print_nocr(output,line);
+          if (!strncmp(line,END,strlen(END)))
+            break;
+        }
+      else if(!strncmp(line,BEGIN,strlen(BEGIN)))
+        {
+         print_nocr(output,line);
+          gotit=1;
+        }
+    }
+  
+  if(gotit)
+    fprintf (output,"KEY 0x%s END\n", getkey);
+  else
+    {
+      fprintf(console,"gpgkeys: no key data found for finger:%s\n",
+             opt->opaque);
+      fprintf(output,"KEY 0x%s FAILED %d\n",getkey,KEYSERVER_KEY_NOT_FOUND);
+    }
+
+  xfree(line);
+  iobuf_close (fp_read);
+
+  return KEYSERVER_OK;
+}
+
+
+static void 
+show_help (FILE *fp)
+{
+  fprintf (fp,"-h\thelp\n");
+  fprintf (fp,"-V\tversion\n");
+  fprintf (fp,"-o\toutput to this file\n");
+}
+
+int
+main(int argc,char *argv[])
+{
+  int arg,ret=KEYSERVER_INTERNAL_ERROR;
+  char line[MAX_LINE];
+  char *thekey=NULL;
+
+  console=stderr;
+
+  /* Kludge to implement standard GNU options.  */
+  if (argc > 1 && !strcmp (argv[1], "--version"))
+    {
+      fputs ("gpgkeys_finger (GnuPG) " VERSION"\n", stdout);
+      return 0;
+    }
+  else if (argc > 1 && !strcmp (argv[1], "--help"))
+    {
+      show_help (stdout);
+      return 0;
+    }
+
+  while((arg=getopt(argc,argv,"hVo:"))!=-1)
+    switch(arg)
+      {
+      default:
+      case 'h':
+        show_help (console);
+       return KEYSERVER_OK;
+
+      case 'V':
+       fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
+       return KEYSERVER_OK;
+
+      case 'o':
+       output=fopen(optarg,"w");
+       if(output==NULL)
+         {
+           fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
+                   optarg,strerror(errno));
+           return KEYSERVER_INTERNAL_ERROR;
+         }
+
+       break;
+      }
+
+  if(argc>optind)
+    {
+      input=fopen(argv[optind],"r");
+      if(input==NULL)
+       {
+         fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
+                 argv[optind],strerror(errno));
+         return KEYSERVER_INTERNAL_ERROR;
+       }
+    }
+
+  if(input==NULL)
+    input=stdin;
+
+  if(output==NULL)
+    output=stdout;
+
+  opt=init_ks_options();
+  if(!opt)
+    return KEYSERVER_NO_MEMORY;
+
+  /* Get the command and info block */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    {
+      int err;
+
+      if(line[0]=='\n')
+       break;
+
+      err=parse_ks_options(line,opt);
+      if(err>0)
+       {
+         ret=err;
+         goto fail;
+       }
+      else if(err==0)
+       continue;
+    }
+
+  if(opt->host)
+    {
+      fprintf(console,"gpgkeys: finger://relay/user syntax is not"
+             " supported.  Use finger:user instead.\n");
+      ret=KEYSERVER_NOT_SUPPORTED;
+      goto fail;
+    }
+
+  if(opt->timeout && register_timeout()==-1)
+    {
+      fprintf(console,"gpgkeys: unable to register timeout handler\n");
+      return KEYSERVER_INTERNAL_ERROR;
+    }
+
+  /* If it's a GET or a SEARCH, the next thing to come in is the
+     keyids.  If it's a SEND, then there are no keyids. */
+
+  if(opt->action==KS_GET)
+    {
+      /* Eat the rest of the file */
+      for(;;)
+       {
+         if(fgets(line,MAX_LINE,input)==NULL)
+           break;
+         else
+           {
+             if(line[0]=='\n' || line[0]=='\0')
+               break;
+
+             if(!thekey)
+               {
+                 thekey=strdup(line);
+                 if(!thekey)
+                   {
+                     fprintf(console,"gpgkeys: out of memory while "
+                             "building key list\n");
+                     ret=KEYSERVER_NO_MEMORY;
+                     goto fail;
+                   }
+
+                 /* Trim the trailing \n */
+                 thekey[strlen(line)-1]='\0';
+               }
+           }
+       }
+    }
+  else
+    {
+      fprintf(console,
+             "gpgkeys: this keyserver type only supports key retrieval\n");
+      goto fail;
+    }
+
+  if(!thekey || !opt->opaque)
+    {
+      fprintf(console,"gpgkeys: invalid keyserver instructions\n");
+      goto fail;
+    }
+
+  /* Send the response */
+
+  fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
+  fprintf(output,"PROGRAM %s\n\n",VERSION);
+
+  if(opt->verbose>1)
+    {
+      fprintf(console,"User:\t\t%s\n",opt->opaque);
+      fprintf(console,"Command:\tGET\n");
+    }
+
+  set_timeout(opt->timeout);
+
+  ret=get_key(thekey);
+
+ fail:
+
+  free(thekey);
+
+  if(input!=stdin)
+    fclose(input);
+
+  if(output!=stdout)
+    fclose(output);
+
+  free_ks_options(opt);
+
+  return ret;
+}
diff --git a/keyserver/gpgkeys_hkp.c b/keyserver/gpgkeys_hkp.c
new file mode 100644 (file)
index 0000000..b3ebcf1
--- /dev/null
@@ -0,0 +1,833 @@
+/* gpgkeys_hkp.c - talk to an HKP keyserver
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#ifdef HAVE_LIBCURL
+#include <curl/curl.h>
+#else
+#include "curl-shim.h"
+#endif
+#include "keyserver.h"
+#include "ksutil.h"
+
+extern char *optarg;
+extern int optind;
+
+static FILE *input,*output,*console;
+static CURL *curl;
+static struct ks_options *opt;
+static char errorbuffer[CURL_ERROR_SIZE];
+
+static size_t
+curl_mrindex_writer(const void *ptr,size_t size,size_t nmemb,void *stream)
+{
+  static int checked=0,swallow=0;
+
+  if(!checked)
+    {
+      /* If the document begins with a '<', assume it's a HTML
+        response, which we don't support.  Discard the whole message
+        body.  GPG can handle it, but this is an optimization to deal
+        with it on this side of the pipe.  */
+      const char *buf=ptr;
+      if(buf[0]=='<')
+       swallow=1;
+
+      checked=1;
+    }
+
+  if(swallow || fwrite(ptr,size,nmemb,stream)==nmemb)
+    return size*nmemb;
+  else
+    return 0;
+}
+
+/* Append but avoid creating a double slash // in the path. */
+static char *
+append_path(char *dest,const char *src)
+{
+  size_t n=strlen(dest);
+
+  if(src[0]=='/' && n>0 && dest[n-1]=='/')
+    dest[n-1]='\0';
+
+  return strcat(dest,src);
+}
+
+int
+send_key(int *eof)
+{
+  CURLcode res;
+  char request[MAX_URL+15];
+  int begin=0,end=0,ret=KEYSERVER_INTERNAL_ERROR;
+  char keyid[17];
+  char line[MAX_LINE];
+  char *key=NULL,*encoded_key=NULL;
+  size_t keylen=0,keymax=0;
+
+  /* Read and throw away input until we see the BEGIN */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s BEGIN\n",keyid)==1)
+      {
+       begin=1;
+       break;
+      }
+
+  if(!begin)
+    {
+      /* i.e. eof before the KEY BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
+      goto fail;
+    }
+
+  /* Now slurp up everything until we see the END */
+
+  while(fgets(line,MAX_LINE,input))
+    if(sscanf(line,"KEY %16s END\n",keyid)==1)
+      {
+       end=1;
+       break;
+      }
+    else
+      {
+       if(strlen(line)+keylen>keymax)
+         {
+           char *tmp;
+
+           keymax+=200;
+           tmp=realloc(key,keymax+1);
+           if(!tmp)
+             {
+               free(key);
+               fprintf(console,"gpgkeys: out of memory\n");
+               ret=KEYSERVER_NO_MEMORY;
+               goto fail;
+             }
+
+           key=tmp;
+         }
+
+       strcpy(&key[keylen],line);
+       keylen+=strlen(line);
+      }
+
+  if(!end)
+    {
+      fprintf(console,"gpgkeys: no KEY %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  encoded_key=curl_escape(key,keylen);
+  if(!encoded_key)
+    {
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  free(key);
+
+  key=malloc(8+strlen(encoded_key)+1);
+  if(!key)
+    {
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  strcpy(key,"keytext=");
+  strcat(key,encoded_key);
+
+  strcpy(request,"http://");
+  strcat(request,opt->host);
+  strcat(request,":");
+  if(opt->port)
+    strcat(request,opt->port);
+  else
+    strcat(request,"11371");
+  strcat(request,opt->path);
+  /* request is MAX_URL+15 bytes long - MAX_URL covers the whole URL,
+     including any supplied path.  The 15 covers /pks/add. */
+  append_path(request,"/pks/add");
+
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: HTTP URL is `%s'\n",request);
+
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_POST,1);
+  curl_easy_setopt(curl,CURLOPT_POSTFIELDS,key);
+  curl_easy_setopt(curl,CURLOPT_FAILONERROR,1);
+
+  res=curl_easy_perform(curl);
+  if(res!=0)
+    {
+      fprintf(console,"gpgkeys: HTTP post error %d: %s\n",res,errorbuffer);
+      ret=curl_err_to_gpg_err(res);
+    }
+  else
+    fprintf(output,"\nKEY %s SENT\n",keyid);
+
+  ret=KEYSERVER_OK;
+
+ fail:
+  free(key);
+  curl_free(encoded_key);
+
+  if(ret!=0 && begin)
+    fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
+
+  return ret;
+}
+
+static int
+get_key(char *getkey)
+{
+  CURLcode res;
+  char request[MAX_URL+60];
+  char *offset;
+  struct curl_writer_ctx ctx;
+
+  memset(&ctx,0,sizeof(ctx));
+
+  /* Build the search string.  HKP only uses the short key IDs. */
+
+  if(strncmp(getkey,"0x",2)==0)
+    getkey+=2;
+
+  fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+
+  if(strlen(getkey)==32)
+    {
+      fprintf(console,
+             "gpgkeys: HKP keyservers do not support v3 fingerprints\n");
+      fprintf(output,"KEY 0x%s FAILED %d\n",getkey,KEYSERVER_NOT_SUPPORTED);
+      return KEYSERVER_NOT_SUPPORTED;
+    }
+
+  strcpy(request,"http://");
+  strcat(request,opt->host);
+  strcat(request,":");
+  if(opt->port)
+    strcat(request,opt->port);
+  else
+    strcat(request,"11371");
+  strcat(request,opt->path);
+  /* request is MAX_URL+55 bytes long - MAX_URL covers the whole URL,
+     including any supplied path.  The 60 overcovers this /pks/... etc
+     string plus the 8 bytes of key id */
+  append_path(request,"/pks/lookup?op=get&options=mr&search=0x");
+
+  /* fingerprint or long key id.  Take the last 8 characters and treat
+     it like a short key id */
+  if(strlen(getkey)>8)
+    offset=&getkey[strlen(getkey)-8];
+  else
+    offset=getkey;
+
+  strcat(request,offset);
+
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: HTTP URL is `%s'\n",request);
+
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_writer);
+  ctx.stream=output;
+  curl_easy_setopt(curl,CURLOPT_FILE,&ctx);
+
+  res=curl_easy_perform(curl);
+  if(res!=CURLE_OK)
+    {
+      fprintf(console,"gpgkeys: HTTP fetch error %d: %s\n",res,errorbuffer);
+      fprintf(output,"\nKEY 0x%s FAILED %d\n",getkey,curl_err_to_gpg_err(res));
+    }
+  else
+    {
+      curl_writer_finalize(&ctx);
+      if(!ctx.flags.done)
+       {
+         fprintf(console,"gpgkeys: key %s not found on keyserver\n",getkey);
+         fprintf(output,"\nKEY 0x%s FAILED %d\n",
+                 getkey,KEYSERVER_KEY_NOT_FOUND);
+       }
+      else
+       fprintf(output,"\nKEY 0x%s END\n",getkey);
+    }
+
+  return KEYSERVER_OK;
+}
+
+static int
+get_name(const char *getkey)
+{
+  CURLcode res;
+  char *request=NULL;
+  char *searchkey_encoded;
+  int ret=KEYSERVER_INTERNAL_ERROR;
+  struct curl_writer_ctx ctx;
+
+  memset(&ctx,0,sizeof(ctx));
+
+  searchkey_encoded=curl_escape((char *)getkey,0);
+  if(!searchkey_encoded)
+    {
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  request=malloc(MAX_URL+60+strlen(searchkey_encoded));
+  if(!request)
+    {
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  fprintf(output,"NAME %s BEGIN\n",getkey);
+
+  strcpy(request,"http://");
+  strcat(request,opt->host);
+  strcat(request,":");
+  if(opt->port)
+    strcat(request,opt->port);
+  else
+    strcat(request,"11371");
+  strcat(request,opt->path);
+  append_path(request,"/pks/lookup?op=get&options=mr&search=");
+  strcat(request,searchkey_encoded);
+
+  if(opt->action==KS_GETNAME)
+    strcat(request,"&exact=on");
+
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: HTTP URL is `%s'\n",request);
+
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_writer);
+  ctx.stream=output;
+  curl_easy_setopt(curl,CURLOPT_FILE,&ctx);
+
+  res=curl_easy_perform(curl);
+  if(res!=CURLE_OK)
+    {
+      fprintf(console,"gpgkeys: HTTP fetch error %d: %s\n",res,errorbuffer);
+      ret=curl_err_to_gpg_err(res);
+    }
+  else
+    {
+      curl_writer_finalize(&ctx);
+      if(!ctx.flags.done)
+       {
+         fprintf(console,"gpgkeys: key %s not found on keyserver\n",getkey);
+         ret=KEYSERVER_KEY_NOT_FOUND;
+       }
+      else
+       {
+         fprintf(output,"\nNAME %s END\n",getkey);
+         ret=KEYSERVER_OK;
+       }
+    }
+
+ fail:
+  curl_free(searchkey_encoded);
+  free(request);
+
+  if(ret!=KEYSERVER_OK)
+    fprintf(output,"\nNAME %s FAILED %d\n",getkey,ret);
+
+  return ret;
+}
+
+static int
+search_key(const char *searchkey)
+{
+  CURLcode res;
+  char *request=NULL;
+  char *searchkey_encoded;
+  int ret=KEYSERVER_INTERNAL_ERROR;
+  enum ks_search_type search_type;
+
+  search_type=classify_ks_search(&searchkey);
+
+  if(opt->debug)
+    fprintf(console,"gpgkeys: search type is %d, and key is \"%s\"\n",
+           search_type,searchkey);
+
+  searchkey_encoded=curl_escape((char *)searchkey,0);
+  if(!searchkey_encoded)
+    {
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  request=malloc(MAX_URL+60+strlen(searchkey_encoded));
+  if(!request)
+    {
+      fprintf(console,"gpgkeys: out of memory\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  fprintf(output,"SEARCH %s BEGIN\n",searchkey);
+
+  strcpy(request,"http://");
+  strcat(request,opt->host);
+  strcat(request,":");
+  if(opt->port)
+    strcat(request,opt->port);
+  else
+    strcat(request,"11371");
+  strcat(request,opt->path);
+  append_path(request,"/pks/lookup?op=index&options=mr&search=");
+  strcat(request,searchkey_encoded);
+
+  if(search_type!=KS_SEARCH_SUBSTR)
+    strcat(request,"&exact=on");
+
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: HTTP URL is `%s'\n",request);
+
+  curl_easy_setopt(curl,CURLOPT_URL,request);
+  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,curl_mrindex_writer);
+  curl_easy_setopt(curl,CURLOPT_FILE,output);
+
+  res=curl_easy_perform(curl);
+  if(res!=0)
+    {
+      fprintf(console,"gpgkeys: HTTP search error %d: %s\n",res,errorbuffer);
+      ret=curl_err_to_gpg_err(res);
+    }
+  else
+    {
+      fprintf(output,"\nSEARCH %s END\n",searchkey);
+      ret=KEYSERVER_OK;
+    }
+
+ fail:
+
+  curl_free(searchkey_encoded);
+  free(request);
+
+  if(ret!=KEYSERVER_OK)
+    fprintf(output,"\nSEARCH %s FAILED %d\n",searchkey,ret);
+
+  return ret;
+}
+
+void
+fail_all(struct keylist *keylist,int err)
+{
+  if(!keylist)
+    return;
+
+  if(opt->action==KS_SEARCH)
+    {
+      fprintf(output,"SEARCH ");
+      while(keylist)
+       {
+         fprintf(output,"%s ",keylist->str);
+         keylist=keylist->next;
+       }
+      fprintf(output,"FAILED %d\n",err);
+    }
+  else
+    while(keylist)
+      {
+       fprintf(output,"KEY %s FAILED %d\n",keylist->str,err);
+       keylist=keylist->next;
+      }
+}
+
+static void 
+show_help (FILE *fp)
+{
+  fprintf (fp,"-h\thelp\n");
+  fprintf (fp,"-V\tversion\n");
+  fprintf (fp,"-o\toutput to this file\n");
+}
+
+int
+main(int argc,char *argv[])
+{
+  int arg,ret=KEYSERVER_INTERNAL_ERROR;
+  char line[MAX_LINE];
+  int failed=0;
+  struct keylist *keylist=NULL,*keyptr=NULL;
+  char *proxy=NULL;
+
+  console=stderr;
+
+  /* Kludge to implement standard GNU options.  */
+  if (argc > 1 && !strcmp (argv[1], "--version"))
+    {
+      fputs ("gpgkeys_hkp (GnuPG) " VERSION"\n", stdout);
+      return 0;
+    }
+  else if (argc > 1 && !strcmp (argv[1], "--help"))
+    {
+      show_help (stdout);
+      return 0;
+    }
+
+  while((arg=getopt(argc,argv,"hVo:"))!=-1)
+    switch(arg)
+      {
+      default:
+      case 'h':
+        show_help (console);
+       return KEYSERVER_OK;
+
+      case 'V':
+       fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
+       return KEYSERVER_OK;
+
+      case 'o':
+       output=fopen(optarg,"w");
+       if(output==NULL)
+         {
+           fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
+                   optarg,strerror(errno));
+           return KEYSERVER_INTERNAL_ERROR;
+         }
+
+       break;
+      }
+
+  if(argc>optind)
+    {
+      input=fopen(argv[optind],"r");
+      if(input==NULL)
+       {
+         fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
+                 argv[optind],strerror(errno));
+         return KEYSERVER_INTERNAL_ERROR;
+       }
+    }
+
+  if(input==NULL)
+    input=stdin;
+
+  if(output==NULL)
+    output=stdout;
+
+  opt=init_ks_options();
+  if(!opt)
+    return KEYSERVER_NO_MEMORY;
+
+  /* Get the command and info block */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    {
+      int err;
+      char option[MAX_OPTION+1];
+
+      if(line[0]=='\n')
+       break;
+
+      err=parse_ks_options(line,opt);
+      if(err>0)
+       {
+         ret=err;
+         goto fail;
+       }
+      else if(err==0)
+       continue;
+
+      if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "s\n",option)==1)
+       {
+         int no=0;
+         char *start=&option[0];
+
+         option[MAX_OPTION]='\0';
+
+         if(strncasecmp(option,"no-",3)==0)
+           {
+             no=1;
+             start=&option[3];
+           }
+
+         if(strncasecmp(start,"http-proxy",10)==0)
+           {
+             if(no)
+               {
+                 free(proxy);
+                 proxy=strdup("");
+               }
+             else if(start[10]=='=')
+               {
+                 if(strlen(&start[11])<MAX_PROXY)
+                   {
+                     free(proxy);
+                     proxy=strdup(&start[11]);
+                   }
+               }
+           }
+#if 0
+         else if(strcasecmp(start,"try-dns-srv")==0)
+           {
+             if(no)
+               http_flags&=~HTTP_FLAG_TRY_SRV;
+             else
+               http_flags|=HTTP_FLAG_TRY_SRV;
+           }
+#endif
+         continue;
+       }
+    }
+
+  if(!opt->host)
+    {
+      fprintf(console,"gpgkeys: no keyserver host provided\n");
+      goto fail;
+    }
+
+  if(opt->timeout && register_timeout()==-1)
+    {
+      fprintf(console,"gpgkeys: unable to register timeout handler\n");
+      return KEYSERVER_INTERNAL_ERROR;
+    }
+
+  curl_global_init(CURL_GLOBAL_DEFAULT);
+  curl=curl_easy_init();
+  if(!curl)
+    {
+      fprintf(console,"gpgkeys: unable to initialize curl\n");
+      ret=KEYSERVER_INTERNAL_ERROR;
+      goto fail;
+    }
+
+  curl_easy_setopt(curl,CURLOPT_ERRORBUFFER,errorbuffer);
+
+  if(opt->auth)
+    curl_easy_setopt(curl,CURLOPT_USERPWD,opt->auth);
+
+  if(opt->debug)
+    {
+      fprintf(console,"gpgkeys: curl version = %s\n",curl_version());
+      curl_easy_setopt(curl,CURLOPT_STDERR,console);
+      curl_easy_setopt(curl,CURLOPT_VERBOSE,1);
+    }
+
+  if(proxy)
+    curl_easy_setopt(curl,CURLOPT_PROXY,proxy);
+
+#if 0
+  /* By suggested convention, if the user gives a :port, then disable
+     SRV. */
+  if(opt->port)
+    http_flags&=~HTTP_FLAG_TRY_SRV;
+#endif
+
+  /* If it's a GET or a SEARCH, the next thing to come in is the
+     keyids.  If it's a SEND, then there are no keyids. */
+
+  if(opt->action==KS_SEND)
+    while(fgets(line,MAX_LINE,input)!=NULL && line[0]!='\n');
+  else if(opt->action==KS_GET
+         || opt->action==KS_GETNAME || opt->action==KS_SEARCH)
+    {
+      for(;;)
+       {
+         struct keylist *work;
+
+         if(fgets(line,MAX_LINE,input)==NULL)
+           break;
+         else
+           {
+             if(line[0]=='\n' || line[0]=='\0')
+               break;
+
+             work=malloc(sizeof(struct keylist));
+             if(work==NULL)
+               {
+                 fprintf(console,"gpgkeys: out of memory while "
+                         "building key list\n");
+                 ret=KEYSERVER_NO_MEMORY;
+                 goto fail;
+               }
+
+             strcpy(work->str,line);
+
+             /* Trim the trailing \n */
+             work->str[strlen(line)-1]='\0';
+
+             work->next=NULL;
+
+             /* Always attach at the end to keep the list in proper
+                 order for searching */
+             if(keylist==NULL)
+               keylist=work;
+             else
+               keyptr->next=work;
+
+             keyptr=work;
+           }
+       }
+    }
+  else
+    {
+      fprintf(console,"gpgkeys: no keyserver command specified\n");
+      goto fail;
+    }
+
+  /* Send the response */
+
+  fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
+  fprintf(output,"PROGRAM %s\n\n",VERSION);
+
+  if(opt->verbose>1)
+    {
+      fprintf(console,"Host:\t\t%s\n",opt->host);
+      if(opt->port)
+       fprintf(console,"Port:\t\t%s\n",opt->port);
+      if(strcmp(opt->path,"/")!=0)
+       fprintf(console,"Path:\t\t%s\n",opt->path);
+      fprintf(console,"Command:\t%s\n",ks_action_to_string(opt->action));
+    }
+
+  if(opt->action==KS_GET)
+    {
+      keyptr=keylist;
+
+      while(keyptr!=NULL)
+       {
+         set_timeout(opt->timeout);
+
+         if(get_key(keyptr->str)!=KEYSERVER_OK)
+           failed++;
+
+         keyptr=keyptr->next;
+       }
+    }
+  else if(opt->action==KS_GETNAME)
+    {
+      keyptr=keylist;
+
+      while(keyptr!=NULL)
+       {
+         set_timeout(opt->timeout);
+
+         if(get_name(keyptr->str)!=KEYSERVER_OK)
+           failed++;
+
+         keyptr=keyptr->next;
+       }
+    }
+  else if(opt->action==KS_SEND)
+    {
+      int eof=0;
+
+      do
+       {
+         set_timeout(opt->timeout);
+
+         if(send_key(&eof)!=KEYSERVER_OK)
+           failed++;
+       }
+      while(!eof);
+    }
+  else if(opt->action==KS_SEARCH)
+    {
+      char *searchkey=NULL;
+      int len=0;
+
+      set_timeout(opt->timeout);
+
+      /* To search, we stick a space in between each key to search
+        for. */
+
+      keyptr=keylist;
+      while(keyptr!=NULL)
+       {
+         len+=strlen(keyptr->str)+1;
+         keyptr=keyptr->next;
+       }
+
+      searchkey=malloc(len+1);
+      if(searchkey==NULL)
+       {
+         ret=KEYSERVER_NO_MEMORY;
+         fail_all(keylist,KEYSERVER_NO_MEMORY);
+         goto fail;
+       }
+
+      searchkey[0]='\0';
+
+      keyptr=keylist;
+      while(keyptr!=NULL)
+       {
+         strcat(searchkey,keyptr->str);
+         strcat(searchkey," ");
+         keyptr=keyptr->next;
+       }
+
+      /* Nail that last space */
+      if(*searchkey)
+       searchkey[strlen(searchkey)-1]='\0';
+
+      if(search_key(searchkey)!=KEYSERVER_OK)
+       failed++;
+
+      free(searchkey);
+    }
+  else
+    abort();
+
+  if(!failed)
+    ret=KEYSERVER_OK;
+
+ fail:
+  while(keylist!=NULL)
+    {
+      struct keylist *current=keylist;
+      keylist=keylist->next;
+      free(current);
+    }
+
+  if(input!=stdin)
+    fclose(input);
+
+  if(output!=stdout)
+    fclose(output);
+
+  free_ks_options(opt);
+
+  if(curl)
+    curl_easy_cleanup(curl);
+
+  free(proxy);
+
+  return ret;
+}
diff --git a/keyserver/gpgkeys_ldap.c b/keyserver/gpgkeys_ldap.c
new file mode 100644 (file)
index 0000000..b4bd00e
--- /dev/null
@@ -0,0 +1,2350 @@
+/* gpgkeys_ldap.c - talk to a LDAP keyserver
+ * Copyright (C) 2001, 2002, 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#include <stdlib.h>
+#include <errno.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#include <winldap.h>
+#else
+#ifdef NEED_LBER_H
+#include <lber.h>
+#endif
+/* For OpenLDAP, to enable the API that we're using. */
+#define LDAP_DEPRECATED 1
+#include <ldap.h>
+#endif
+
+#include "util.h"
+#include "keyserver.h"
+#include "ksutil.h"
+
+#ifdef __riscos__
+#include "util.h"
+#endif
+
+extern char *optarg;
+extern int optind;
+
+static int real_ldap=0;
+static char *basekeyspacedn=NULL;
+static char *pgpkeystr="pgpKey";
+static FILE *input=NULL,*output=NULL,*console=NULL;
+static LDAP *ldap=NULL;
+static struct ks_options *opt;
+
+#ifndef HAVE_TIMEGM
+time_t timegm(struct tm *tm);
+#endif
+
+static int
+ldap_err_to_gpg_err(int err)
+{
+  int ret;
+
+  switch(err)
+    {
+    case LDAP_ALREADY_EXISTS:
+      ret=KEYSERVER_KEY_EXISTS;
+      break;
+
+    case LDAP_SERVER_DOWN:
+      ret=KEYSERVER_UNREACHABLE;
+      break;
+
+    default:
+      ret=KEYSERVER_GENERAL_ERROR;
+      break;
+    }
+
+  return ret;
+}
+
+static int
+ldap_to_gpg_err(LDAP *ld)
+{
+#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
+
+  int err;
+
+  if(ldap_get_option(ld,LDAP_OPT_ERROR_NUMBER,&err)==0)
+    return ldap_err_to_gpg_err(err);
+  else
+    return KEYSERVER_GENERAL_ERROR;
+
+#elif defined(HAVE_LDAP_LD_ERRNO)
+
+  return ldap_err_to_gpg_err(ld->ld_errno);
+
+#else
+
+  /* We should never get here since the LDAP library should always
+     have either ldap_get_option or ld_errno, but just in case... */
+  return KEYSERVER_GENERAL_ERROR;
+
+#endif
+}
+
+static int
+key_in_keylist(const char *key,struct keylist *list)
+{
+  struct keylist *keyptr=list;
+
+  while(keyptr!=NULL)
+    {
+      if(strcasecmp(key,keyptr->str)==0)
+       return 1;
+
+      keyptr=keyptr->next;
+    }
+
+  return 0;
+}
+
+static int
+add_key_to_keylist(const char *key,struct keylist **list)
+{
+  struct keylist *keyptr=malloc(sizeof(struct keylist));
+
+  if(keyptr==NULL)
+    {
+      fprintf(console,"gpgkeys: out of memory when deduping "
+             "key list\n");
+      return KEYSERVER_NO_MEMORY;
+    }
+
+  strncpy(keyptr->str,key,MAX_LINE);
+  keyptr->str[MAX_LINE-1]='\0';
+  keyptr->next=*list;
+  *list=keyptr;
+
+  return 0;
+}
+
+static void
+free_keylist(struct keylist *list)
+{
+  while(list!=NULL)
+    {
+      struct keylist *keyptr=list;
+
+      list=keyptr->next;
+      free(keyptr);
+    }
+}
+
+static time_t
+ldap2epochtime(const char *timestr)
+{
+  struct tm pgptime;
+  time_t answer;
+
+  memset(&pgptime,0,sizeof(pgptime));
+
+  /* YYYYMMDDHHmmssZ */
+
+  sscanf(timestr,"%4d%2d%2d%2d%2d%2d",
+        &pgptime.tm_year,
+        &pgptime.tm_mon,
+        &pgptime.tm_mday,
+        &pgptime.tm_hour,
+        &pgptime.tm_min,
+        &pgptime.tm_sec);
+
+  pgptime.tm_year-=1900;
+  pgptime.tm_isdst=-1;
+  pgptime.tm_mon--;
+
+  /* mktime() takes the timezone into account, so we use timegm() */
+
+  answer=timegm(&pgptime);
+
+  return answer;
+}
+
+/* Caller must free */
+static char *
+epoch2ldaptime(time_t stamp)
+{
+  struct tm *ldaptime;
+  char buf[16];
+
+  ldaptime=gmtime(&stamp);
+
+  ldaptime->tm_year+=1900;
+  ldaptime->tm_mon++;
+
+  /* YYYYMMDDHHmmssZ */
+
+  sprintf(buf,"%04d%02d%02d%02d%02d%02dZ",
+         ldaptime->tm_year,
+         ldaptime->tm_mon,
+         ldaptime->tm_mday,
+         ldaptime->tm_hour,
+         ldaptime->tm_min,
+         ldaptime->tm_sec);
+
+  return strdup(buf);
+}
+
+/* Append two onto the end of one.  Two is not freed, but its pointers
+   are now part of one.  Make sure you don't free them both! */
+static int
+join_two_modlists(LDAPMod ***one,LDAPMod **two)
+{
+  int i,one_count=0,two_count=0;
+  LDAPMod **grow;
+
+  for(grow=*one;*grow;grow++)
+    one_count++;
+
+  for(grow=two;*grow;grow++)
+    two_count++;
+
+  grow=realloc(*one,sizeof(LDAPMod *)*(one_count+two_count+1));
+  if(!grow)
+    return 0;
+
+  for(i=0;i<two_count;i++)
+    grow[one_count+i]=two[i];
+
+  grow[one_count+i]=NULL;
+
+  *one=grow;
+
+  return 1;
+}
+
+/* Passing a NULL for value effectively deletes that attribute.  This
+   doesn't mean "delete" in the sense of removing something from the
+   modlist, but "delete" in the LDAP sense of adding a modlist item
+   that specifies LDAP_MOD_REPLACE and a null attribute for the given
+   attribute.  LDAP_MOD_DELETE doesn't work here as we don't know if
+   the attribute in question exists or not. */
+
+static int
+make_one_attr(LDAPMod ***modlist,char *attr,const char *value)
+{
+  LDAPMod **m;
+  int nummods=0;
+
+  /* Search modlist for the attribute we're playing with. */
+  for(m=*modlist;*m;m++)
+    {
+      if(strcasecmp((*m)->mod_type,attr)==0)
+       {
+         char **ptr=(*m)->mod_values;
+         int numvalues=0;
+
+         /* We have this attribute already, so when the REPLACE
+            happens, the server attributes will be replaced
+            anyway. */
+         if(!value)
+           return 1;
+
+         if(ptr)
+           for(ptr=(*m)->mod_values;*ptr;ptr++)
+             {
+               /* Duplicate value */
+               if(strcmp(*ptr,value)==0)
+                 return 1;
+               numvalues++;
+             }
+
+         ptr=realloc((*m)->mod_values,sizeof(char *)*(numvalues+2));
+         if(!ptr)
+           return 0;
+
+         (*m)->mod_values=ptr;
+         ptr[numvalues]=strdup(value);
+         if(!ptr[numvalues])
+           return 0;
+
+         ptr[numvalues+1]=NULL;
+         break;
+       }
+
+      nummods++;
+    }
+
+  /* We didn't find the attr, so make one and add it to the end */
+  if(!*m)
+    {
+      LDAPMod **grow;
+
+      grow=realloc(*modlist,sizeof(LDAPMod *)*(nummods+2));
+      if(!grow)
+       return 0;
+
+      *modlist=grow;
+      grow[nummods]=malloc(sizeof(LDAPMod));
+      if(!grow[nummods])
+       return 0;
+      grow[nummods]->mod_op=LDAP_MOD_REPLACE;
+      grow[nummods]->mod_type=attr;
+      if(value)
+       {
+         grow[nummods]->mod_values=malloc(sizeof(char *)*2);
+         if(!grow[nummods]->mod_values)
+           {
+             grow[nummods]=NULL;
+             return 0;
+           }
+
+         /* Is this the right thing?  Can a UTF8-encoded user ID have
+            embedded nulls? */
+         grow[nummods]->mod_values[0]=strdup(value);
+         if(!grow[nummods]->mod_values[0])
+           {
+             free(grow[nummods]->mod_values);
+             grow[nummods]=NULL;
+             return 0;
+           }
+
+         grow[nummods]->mod_values[1]=NULL;
+       }
+      else
+       grow[nummods]->mod_values=NULL;
+
+      grow[nummods+1]=NULL;
+    }
+
+  return 1;
+}
+
+static void
+build_attrs(LDAPMod ***modlist,char *line)
+{
+  char *record;
+  int i;
+
+  /* Remove trailing whitespace */
+  for(i=strlen(line);i>0;i--)
+    if(ascii_isspace(line[i-1]))
+      line[i-1]='\0';
+    else
+      break;
+
+  if((record=strsep(&line,":"))==NULL)
+    return;
+
+  if(ascii_strcasecmp("pub",record)==0)
+    {
+      char *tok;
+      int disabled=0,revoked=0;
+
+      /* The long keyid */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==16)
+       {
+         make_one_attr(modlist,"pgpCertID",tok);
+         make_one_attr(modlist,"pgpKeyID",&tok[8]);
+       }
+      else
+       return;
+
+      /* The primary pubkey algo */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      switch(atoi(tok))
+       {
+       case 1:
+         make_one_attr(modlist,"pgpKeyType","RSA");
+         break;
+
+       case 17:
+         make_one_attr(modlist,"pgpKeyType","DSS/DH");
+         break;
+       }
+
+      /* Size of primary key */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(atoi(tok)>0)
+       {
+         char padded[6];
+         int val=atoi(tok);
+
+         /* We zero pad this on the left to make PGP happy. */
+
+         if(val<99999 && val>0)
+           {
+             sprintf(padded,"%05u",atoi(tok));
+             make_one_attr(modlist,"pgpKeySize",padded);
+           }
+       }
+
+      /* pk timestamp */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(atoi(tok)>0)
+       {
+         char *stamp=epoch2ldaptime(atoi(tok));
+         if(stamp)
+           {
+             make_one_attr(modlist,"pgpKeyCreateTime",stamp);
+             free(stamp);
+           }
+       }
+
+      /* pk expire */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(atoi(tok)>0)
+       {
+         char *stamp=epoch2ldaptime(atoi(tok));
+         if(stamp)
+           {
+             make_one_attr(modlist,"pgpKeyExpireTime",stamp);
+             free(stamp);
+           }
+       }
+
+      /* flags */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      while(*tok)
+       switch(*tok++)
+         {
+         case 'r':
+         case 'R':
+           revoked=1;
+           break;
+           
+         case 'd':
+         case 'D':
+           disabled=1;
+           break;
+         }
+
+      /*
+       Note that we always create the pgpDisabled and pgpRevoked
+       attributes, regardless of whether the key is disabled/revoked
+       or not.  This is because a very common search is like
+       "(&(pgpUserID=*isabella*)(pgpDisabled=0))"
+      */
+
+      make_one_attr(modlist,"pgpDisabled",disabled?"1":"0");
+      make_one_attr(modlist,"pgpRevoked",revoked?"1":"0");
+    }
+  else if(ascii_strcasecmp("sub",record)==0)
+    {
+      char *tok;
+
+      /* The long keyid */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==16)
+       make_one_attr(modlist,"pgpSubKeyID",tok);
+      else
+       return;
+
+      /* The subkey algo */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      /* Size of subkey */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(atoi(tok)>0)
+       {
+         char padded[6];
+         int val=atoi(tok);
+
+         /* We zero pad this on the left to make PGP happy. */
+
+         if(val<99999 && val>0)
+           {
+             sprintf(padded,"%05u",atoi(tok));
+             make_one_attr(modlist,"pgpKeySize",padded);
+           }
+       }
+
+      /* Ignore the rest of the items for subkeys since the LDAP
+        schema doesn't store them. */
+    }
+  else if(ascii_strcasecmp("uid",record)==0)
+    {
+      char *userid,*tok;
+
+      /* The user ID string */
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==0)
+       return;
+
+      userid=tok;
+
+      /* By definition, de-%-encoding is always smaller than the
+         original string so we can decode in place. */
+
+      i=0;
+
+      while(*tok)
+       if(tok[0]=='%' && tok[1] && tok[2])
+         {
+           if((userid[i]=hextobyte(&tok[1]))==-1)
+             userid[i]='?';
+
+           i++;
+           tok+=3;
+         }
+       else
+         userid[i++]=*tok++;
+
+      userid[i]='\0';
+
+      /* We don't care about the other info provided in the uid: line
+        since the LDAP schema doesn't need it. */
+
+      make_one_attr(modlist,"pgpUserID",userid);
+    }
+  else if(ascii_strcasecmp("sig",record)==0)
+    {
+      char *tok;
+
+      if((tok=strsep(&line,":"))==NULL)
+       return;
+
+      if(strlen(tok)==16)
+       make_one_attr(modlist,"pgpSignerID",tok);
+    }
+}
+
+static void
+free_mod_values(LDAPMod *mod)
+{
+  char **ptr;
+
+  if(!mod->mod_values)
+    return;
+
+  for(ptr=mod->mod_values;*ptr;ptr++)
+    free(*ptr);
+
+  free(mod->mod_values);
+}
+
+static int
+send_key(int *eof)
+{
+  int err,begin=0,end=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
+  char *dn=NULL,line[MAX_LINE],*key=NULL;
+  char keyid[17];
+  LDAPMod **modlist,**addlist,**ml;
+
+  modlist=malloc(sizeof(LDAPMod *));
+  if(!modlist)
+    {
+      fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  *modlist=NULL;
+
+  addlist=malloc(sizeof(LDAPMod *));
+  if(!addlist)
+    {
+      fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  *addlist=NULL;
+
+  /* Start by nulling out all attributes.  We try and do a modify
+     operation first, so this ensures that we don't leave old
+     attributes lying around. */
+  make_one_attr(&modlist,"pgpDisabled",NULL);
+  make_one_attr(&modlist,"pgpKeyID",NULL);
+  make_one_attr(&modlist,"pgpKeyType",NULL);
+  make_one_attr(&modlist,"pgpUserID",NULL);
+  make_one_attr(&modlist,"pgpKeyCreateTime",NULL);
+  make_one_attr(&modlist,"pgpSignerID",NULL);
+  make_one_attr(&modlist,"pgpRevoked",NULL);
+  make_one_attr(&modlist,"pgpSubKeyID",NULL);
+  make_one_attr(&modlist,"pgpKeySize",NULL);
+  make_one_attr(&modlist,"pgpKeyExpireTime",NULL);
+  make_one_attr(&modlist,"pgpCertID",NULL);
+
+  /* Assemble the INFO stuff into LDAP attributes */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"INFO %16s BEGIN\n",keyid)==1)
+      {
+       begin=1;
+       break;
+      }
+
+  if(!begin)
+    {
+      /* i.e. eof before the INFO BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
+      goto fail;
+    }
+
+  if(strlen(keyid)!=16)
+    {
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  dn=malloc(strlen("pgpCertID=")+16+1+strlen(basekeyspacedn)+1);
+  if(dn==NULL)
+    {
+      fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  sprintf(dn,"pgpCertID=%s,%s",keyid,basekeyspacedn);
+
+  key=malloc(1);
+  if(!key)
+    {
+      fprintf(console,"gpgkeys: unable to allocate memory for key\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  key[0]='\0';
+
+  /* Now parse each line until we see the END */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"INFO %16s END\n",keyid)==1)
+      {
+       end=1;
+       break;
+      }
+    else
+      build_attrs(&addlist,line);
+
+  if(!end)
+    {
+      fprintf(console,"gpgkeys: no INFO %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  begin=end=0;
+
+  /* Read and throw away stdin until we see the BEGIN */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s BEGIN\n",keyid)==1)
+      {
+       begin=1;
+       break;
+      }
+
+  if(!begin)
+    {
+      /* i.e. eof before the KEY BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
+      goto fail;
+    }
+
+  /* Now slurp up everything until we see the END */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s END\n",keyid)==1)
+      {
+       end=1;
+       break;
+      }
+    else
+      {
+       char *tempkey;
+       keysize+=strlen(line);
+       tempkey=realloc(key,keysize);
+       if(tempkey==NULL)
+         {
+           fprintf(console,"gpgkeys: unable to reallocate for key\n");
+           ret=KEYSERVER_NO_MEMORY;
+           goto fail;
+         }
+       else
+         key=tempkey;
+
+       strcat(key,line);
+      }
+
+  if(!end)
+    {
+      fprintf(console,"gpgkeys: no KEY %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  make_one_attr(&addlist,"objectClass","pgpKeyInfo");
+  make_one_attr(&addlist,"pgpKey",key);
+
+  /* Now append addlist onto modlist */
+  if(!join_two_modlists(&modlist,addlist))
+    {
+      fprintf(console,"gpgkeys: unable to merge LDAP modification lists\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  /* Going on the assumption that modify operations are more frequent
+     than adds, we try a modify first.  If it's not there, we just
+     turn around and send an add command for the same key.  Otherwise,
+     the modify brings the server copy into compliance with our copy.
+     Note that unlike the LDAP keyserver (and really, any other
+     keyserver) this does NOT merge signatures, but replaces the whole
+     key.  This should make some people very happy. */
+
+  err=ldap_modify_s(ldap,dn,modlist);
+  if(err==LDAP_NO_SUCH_OBJECT)
+    err=ldap_add_s(ldap,dn,addlist);
+
+  if(err!=LDAP_SUCCESS)
+    {
+      fprintf(console,"gpgkeys: error adding key %s to keyserver: %s\n",
+             keyid,ldap_err2string(err));
+      ret=ldap_err_to_gpg_err(err);
+      goto fail;
+    }
+
+  ret=KEYSERVER_OK;
+
+ fail:
+  /* Unwind and free the whole modlist structure */
+  for(ml=modlist;*ml;ml++)
+    {
+      free_mod_values(*ml);
+      free(*ml);
+    }
+
+  free(modlist);
+  free(addlist);
+  free(dn);
+
+  if(ret!=0 && begin)
+    fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
+
+  return ret;
+}
+
+static int
+send_key_keyserver(int *eof)
+{
+  int err,begin=0,end=0,keysize=1,ret=KEYSERVER_INTERNAL_ERROR;
+  char *dn=NULL,line[MAX_LINE],*key[2]={NULL,NULL};
+  char keyid[17];
+  LDAPMod mod, *attrs[2];
+
+  memset(&mod,0,sizeof(mod));
+  mod.mod_op=LDAP_MOD_ADD;
+  mod.mod_type=pgpkeystr;
+  mod.mod_values=key;
+  attrs[0]=&mod;
+  attrs[1]=NULL;
+
+  dn=malloc(strlen("pgpCertid=virtual,")+strlen(basekeyspacedn)+1);
+  if(dn==NULL)
+    {
+      fprintf(console,"gpgkeys: can't allocate memory for keyserver record\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  strcpy(dn,"pgpCertid=virtual,");
+  strcat(dn,basekeyspacedn);
+
+  key[0]=malloc(1);
+  if(key[0]==NULL)
+    {
+      fprintf(console,"gpgkeys: unable to allocate memory for key\n");
+      ret=KEYSERVER_NO_MEMORY;
+      goto fail;
+    }
+
+  key[0][0]='\0';
+
+  /* Read and throw away stdin until we see the BEGIN */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s BEGIN\n",keyid)==1)
+      {
+       begin=1;
+       break;
+      }
+
+  if(!begin)
+    {
+      /* i.e. eof before the KEY BEGIN was found.  This isn't an
+        error. */
+      *eof=1;
+      ret=KEYSERVER_OK;
+      goto fail;
+    }
+
+  /* Now slurp up everything until we see the END */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    if(sscanf(line,"KEY %16s END\n",keyid)==1)
+      {
+       end=1;
+       break;
+      }
+    else
+      {
+       keysize+=strlen(line);
+       key[0]=realloc(key[0],keysize);
+       if(key[0]==NULL)
+         {
+           fprintf(console,"gpgkeys: unable to reallocate for key\n");
+           ret=KEYSERVER_NO_MEMORY;
+           goto fail;
+         }
+
+       strcat(key[0],line);
+      }
+
+  if(!end)
+    {
+      fprintf(console,"gpgkeys: no KEY %s END found\n",keyid);
+      *eof=1;
+      ret=KEYSERVER_KEY_INCOMPLETE;
+      goto fail;
+    }
+
+  err=ldap_add_s(ldap,dn,attrs);
+  if(err!=LDAP_SUCCESS)
+    {
+      fprintf(console,"gpgkeys: error adding key %s to keyserver: %s\n",
+             keyid,ldap_err2string(err));
+      ret=ldap_err_to_gpg_err(err);
+      goto fail;
+    }
+
+  ret=KEYSERVER_OK;
+
+ fail:
+
+  free(key[0]);
+  free(dn);
+
+  if(ret!=0 && begin)
+    fprintf(output,"KEY %s FAILED %d\n",keyid,ret);
+
+  /* Not a fatal error */
+  if(ret==KEYSERVER_KEY_EXISTS)
+    ret=KEYSERVER_OK;
+
+  return ret;
+}
+
+static void
+build_info(const char *certid,LDAPMessage *each)
+{
+  char **vals;
+
+  fprintf(output,"INFO %s BEGIN\n",certid);
+
+  fprintf(output,"pub:%s:",certid);
+
+  vals=ldap_get_values(ldap,each,"pgpkeytype");
+  if(vals!=NULL)
+    {
+      if(strcmp(vals[0],"RSA")==0)
+       fprintf(output,"1");
+      else if(strcmp(vals[0],"DSS/DH")==0)
+       fprintf(output,"17");
+      ldap_value_free(vals);
+    }
+
+  fprintf(output,":");
+
+  vals=ldap_get_values(ldap,each,"pgpkeysize");
+  if(vals!=NULL)
+    {
+      if(atoi(vals[0])>0)
+       fprintf(output,"%d",atoi(vals[0]));
+      ldap_value_free(vals);
+    }
+
+  fprintf(output,":");
+
+  vals=ldap_get_values(ldap,each,"pgpkeycreatetime");
+  if(vals!=NULL)
+    {
+      if(strlen(vals[0])==15)
+       fprintf(output,"%u",(unsigned int)ldap2epochtime(vals[0]));
+      ldap_value_free(vals);
+    }
+
+  fprintf(output,":");
+
+  vals=ldap_get_values(ldap,each,"pgpkeyexpiretime");
+  if(vals!=NULL)
+    {
+      if(strlen(vals[0])==15)
+       fprintf(output,"%u",(unsigned int)ldap2epochtime(vals[0]));
+      ldap_value_free(vals);
+    }
+
+  fprintf(output,":");
+
+  vals=ldap_get_values(ldap,each,"pgprevoked");
+  if(vals!=NULL)
+    {
+      if(atoi(vals[0])==1)
+       fprintf(output,"r");
+      ldap_value_free(vals);
+    }
+
+  fprintf(output,"\n");
+
+  vals=ldap_get_values(ldap,each,"pgpuserid");
+  if(vals!=NULL)
+    {
+      int i;
+
+      for(i=0;vals[i];i++)
+       fprintf(output,"uid:%s\n",vals[i]);
+      ldap_value_free(vals);
+    }
+
+  fprintf(output,"INFO %s END\n",certid);
+}
+
+/* Note that key-not-found is not a fatal error */
+static int
+get_key(char *getkey)
+{
+  LDAPMessage *res,*each;
+  int ret=KEYSERVER_INTERNAL_ERROR,err,count;
+  struct keylist *dupelist=NULL;
+  char search[62];
+  /* This ordering is significant - specifically, "pgpcertid" needs to
+     be the second item in the list, since everything after it may be
+     discarded if the user isn't in verbose mode. */
+  char *attrs[]={"replaceme","pgpcertid","pgpuserid","pgpkeyid","pgprevoked",
+                "pgpdisabled","pgpkeycreatetime","modifytimestamp",
+                "pgpkeysize","pgpkeytype",NULL};
+  attrs[0]=pgpkeystr; /* Some compilers don't like using variables as
+                         array initializers. */
+
+  /* Build the search string */
+
+  /* GPG can send us a v4 fingerprint, a v3 or v4 long key id, or a v3
+     or v4 short key id */
+
+  if(strncmp(getkey,"0x",2)==0)
+    getkey+=2;
+
+  if(strlen(getkey)==32)
+    {
+      fprintf(console,
+             "gpgkeys: LDAP keyservers do not support v3 fingerprints\n");
+      fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+      fprintf(output,"KEY 0x%s FAILED %d\n",getkey,KEYSERVER_NOT_SUPPORTED);
+      return KEYSERVER_NOT_SUPPORTED;
+    }
+
+  if(strlen(getkey)>16)
+    {
+      char *offset=&getkey[strlen(getkey)-16];
+
+      /* fingerprint.  Take the last 16 characters and treat it like a
+         long key id */
+
+      if(opt->flags.include_subkeys)
+       sprintf(search,"(|(pgpcertid=%.16s)(pgpsubkeyid=%.16s))",
+               offset,offset);
+      else
+       sprintf(search,"(pgpcertid=%.16s)",offset);
+    }
+  else if(strlen(getkey)>8)
+    {
+      /* long key id */
+
+      if(opt->flags.include_subkeys)
+       sprintf(search,"(|(pgpcertid=%.16s)(pgpsubkeyid=%.16s))",
+               getkey,getkey);
+      else
+       sprintf(search,"(pgpcertid=%.16s)",getkey);
+    }
+  else
+    {
+      /* short key id */
+    
+      sprintf(search,"(pgpkeyid=%.8s)",getkey);
+    }
+
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: LDAP fetch for: %s\n",search);
+
+  if(!opt->verbose)
+    attrs[2]=NULL; /* keep only pgpkey(v2) and pgpcertid */
+
+  err=ldap_search_s(ldap,basekeyspacedn,
+                   LDAP_SCOPE_SUBTREE,search,attrs,0,&res);
+  if(err!=0)
+    {
+      int errtag=ldap_err_to_gpg_err(err);
+
+      fprintf(console,"gpgkeys: LDAP search error: %s\n",ldap_err2string(err));
+      fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+      fprintf(output,"KEY 0x%s FAILED %d\n",getkey,errtag);
+      return errtag;
+    }
+
+  count=ldap_count_entries(ldap,res);
+  if(count<1)
+    {
+      fprintf(console,"gpgkeys: key %s not found on keyserver\n",getkey);
+      fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+      fprintf(output,"KEY 0x%s FAILED %d\n",getkey,KEYSERVER_KEY_NOT_FOUND);
+    }
+  else
+    {
+      /* There may be more than one unique result for a given keyID,
+        so we should fetch them all (test this by fetching short key
+        id 0xDEADBEEF). */
+
+      each=ldap_first_entry(ldap,res);
+      while(each!=NULL)
+       {
+         char **vals,**certid;
+
+         /* Use the long keyid to remove duplicates.  The LDAP server
+            returns the same keyid more than once if there are
+            multiple user IDs on the key.  Note that this does NOT
+            mean that a keyid that exists multiple times on the
+            keyserver will not be fetched.  It means that each KEY,
+            no matter how many user IDs share its keyid, will be
+            fetched only once.  If a keyid that belongs to more than
+            one key is fetched, the server quite properly responds
+            with all matching keys. -ds */
+
+         certid=ldap_get_values(ldap,each,"pgpcertid");
+         if(certid!=NULL)
+           {
+             if(!key_in_keylist(certid[0],dupelist))
+               {
+                 /* it's not a duplicate, so add it */
+
+                 int rc=add_key_to_keylist(certid[0],&dupelist);
+                 if(rc)
+                   {
+                     ret=rc;
+                     goto fail;
+                   }
+
+                 build_info(certid[0],each);
+
+                 fprintf(output,"KEY 0x%s BEGIN\n",getkey);
+
+                 vals=ldap_get_values(ldap,each,pgpkeystr);
+                 if(vals==NULL)
+                   {
+                     int errtag=ldap_to_gpg_err(ldap);
+
+                     fprintf(console,"gpgkeys: unable to retrieve key %s "
+                             "from keyserver\n",getkey);
+                     fprintf(output,"KEY 0x%s FAILED %d\n",getkey,errtag);
+                   }
+                 else
+                   {
+                     print_nocr(output,vals[0]);
+                     fprintf(output,"\nKEY 0x%s END\n",getkey);
+
+                     ldap_value_free(vals);
+                   }
+               }
+
+             ldap_value_free(certid);
+           }
+
+         each=ldap_next_entry(ldap,each);
+       }
+    }
+
+  ret=KEYSERVER_OK;
+
+ fail:
+  ldap_msgfree(res);
+  free_keylist(dupelist);
+
+  return ret;
+}
+
+#define LDAP_ESCAPE_CHARS "*()\\"
+
+/* Append string to buffer in a LDAP-quoted way */
+static void
+ldap_quote(char *buffer,const char *string)
+{
+  /* Find the end of buffer */
+  buffer+=strlen(buffer);
+
+  for(;*string;string++)
+    {
+      if(strchr(LDAP_ESCAPE_CHARS,*string))
+       {
+         sprintf(buffer,"\\%02X",*string);
+         buffer+=3;
+       }
+      else
+       *buffer++=*string;
+    }
+
+  *buffer='\0';
+}
+
+/* Note that key-not-found is not a fatal error */
+static int
+get_name(char *getkey)
+{
+  LDAPMessage *res,*each;
+  int ret=KEYSERVER_INTERNAL_ERROR,err,count;
+  /* The maximum size of the search, including the optional stuff and
+     the trailing \0 */
+  char search[2+12+(MAX_LINE*3)+2+15+14+1+1+20];
+  /* This ordering is significant - specifically, "pgpcertid" needs to
+     be the second item in the list, since everything after it may be
+     discarded if the user isn't in verbose mode. */
+  char *attrs[]={"replaceme","pgpcertid","pgpuserid","pgpkeyid","pgprevoked",
+                "pgpdisabled","pgpkeycreatetime","modifytimestamp",
+                "pgpkeysize","pgpkeytype",NULL};
+  attrs[0]=pgpkeystr; /* Some compilers don't like using variables as
+                         array initializers. */
+
+  /* Build the search string */
+
+  search[0]='\0';
+
+  if(!opt->flags.include_disabled || !opt->flags.include_revoked)
+    strcat(search,"(&");
+
+  strcat(search,"(pgpUserID=*");
+  ldap_quote(search,getkey);
+  strcat(search,"*)");
+
+  if(!opt->flags.include_disabled)
+    strcat(search,"(pgpDisabled=0)");
+
+  if(!opt->flags.include_revoked)
+    strcat(search,"(pgpRevoked=0)");
+
+  if(!opt->flags.include_disabled || !opt->flags.include_revoked)
+    strcat(search,")");
+
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: LDAP fetch for: %s\n",search);
+
+  if(!opt->verbose)
+    attrs[2]=NULL; /* keep only pgpkey(v2) and pgpcertid */
+
+  err=ldap_search_s(ldap,basekeyspacedn,
+                   LDAP_SCOPE_SUBTREE,search,attrs,0,&res);
+  if(err!=0)
+    {
+      int errtag=ldap_err_to_gpg_err(err);
+
+      fprintf(console,"gpgkeys: LDAP search error: %s\n",ldap_err2string(err));
+      fprintf(output,"NAME %s BEGIN\n",getkey);
+      fprintf(output,"NAME %s FAILED %d\n",getkey,errtag);
+      return errtag;
+    }
+
+  count=ldap_count_entries(ldap,res);
+  if(count<1)
+    {
+      fprintf(console,"gpgkeys: key %s not found on keyserver\n",getkey);
+      fprintf(output,"NAME %s BEGIN\n",getkey);
+      fprintf(output,"NAME %s FAILED %d\n",getkey,KEYSERVER_KEY_NOT_FOUND);
+    }
+  else
+    {
+      /* There may be more than one result, but we return them all. */
+
+      each=ldap_first_entry(ldap,res);
+      while(each!=NULL)
+       {
+         char **vals,**certid;
+
+         certid=ldap_get_values(ldap,each,"pgpcertid");
+         if(certid!=NULL)
+           {
+             build_info(certid[0],each);
+
+             fprintf(output,"NAME %s BEGIN\n",getkey);
+
+             vals=ldap_get_values(ldap,each,pgpkeystr);
+             if(vals==NULL)
+               {
+                 int errtag=ldap_to_gpg_err(ldap);
+
+                 fprintf(console,"gpgkeys: unable to retrieve key %s "
+                         "from keyserver\n",getkey);
+                 fprintf(output,"NAME %s FAILED %d\n",getkey,errtag);
+               }
+             else
+               {
+                 print_nocr(output,vals[0]);
+                 fprintf(output,"\nNAME %s END\n",getkey);
+
+                 ldap_value_free(vals);
+               }
+
+             ldap_value_free(certid);
+           }
+
+         each=ldap_next_entry(ldap,each);
+       }
+    }
+
+  ret=KEYSERVER_OK;
+
+  ldap_msgfree(res);
+
+  return ret;
+}
+
+static void
+printquoted(FILE *stream,char *string,char delim)
+{
+  while(*string)
+    {
+      if(*string==delim || *string=='%')
+       fprintf(stream,"%%%02x",*string);
+      else
+       fputc(*string,stream);
+
+      string++;
+    }
+}
+
+/* Returns 0 on success and -1 on error.  Note that key-not-found is
+   not an error! */
+static int
+search_key(const char *searchkey)
+{
+  char **vals;
+  LDAPMessage *res,*each;
+  int err,count=0;
+  struct keylist *dupelist=NULL;
+  /* The maximum size of the search, including the optional stuff and
+     the trailing \0 */
+  char search[2+1+9+1+3+(MAX_LINE*3)+3+1+15+14+1+1+20];
+  char *attrs[]={"pgpcertid","pgpuserid","pgprevoked","pgpdisabled",
+                "pgpkeycreatetime","pgpkeyexpiretime","modifytimestamp",
+                "pgpkeysize","pgpkeytype",NULL};
+  enum ks_search_type search_type;
+
+  fprintf(output,"SEARCH %s BEGIN\n",searchkey);
+
+  search_type=classify_ks_search(&searchkey);
+
+  if(opt->debug)
+    fprintf(console,"search type is %d, and key is \"%s\"\n",
+           search_type,searchkey);
+
+  /* Build the search string */
+
+  search[0]='\0';
+
+  if(!opt->flags.include_disabled || !opt->flags.include_revoked)
+    strcat(search,"(&");
+
+  strcat(search,"(");
+
+  switch(search_type)
+    {
+    case KS_SEARCH_KEYID_SHORT:
+      strcat(search,"pgpKeyID");
+      break;
+
+    case KS_SEARCH_KEYID_LONG:
+      strcat(search,"pgpCertID");
+      break;
+
+    default:
+      strcat(search,"pgpUserID");
+      break;
+    }
+
+  strcat(search,"=");
+
+  switch(search_type)
+    {
+    case KS_SEARCH_SUBSTR:
+      strcat(search,"*");
+      break;
+
+    case KS_SEARCH_MAIL:
+      strcat(search,"*<");
+      break;
+
+    case KS_SEARCH_MAILSUB:
+      strcat(search,"*<*");
+      break;
+
+    case KS_SEARCH_EXACT:
+    case KS_SEARCH_KEYID_LONG:
+    case KS_SEARCH_KEYID_SHORT:
+      break;
+    }
+
+  ldap_quote(search,searchkey);
+
+  switch(search_type)
+    {
+    case KS_SEARCH_SUBSTR:
+      strcat(search,"*");
+      break;
+
+    case KS_SEARCH_MAIL:
+      strcat(search,">*");
+      break;
+
+    case KS_SEARCH_MAILSUB:
+      strcat(search,"*>*");
+      break;
+
+    case KS_SEARCH_EXACT:
+    case KS_SEARCH_KEYID_LONG:
+    case KS_SEARCH_KEYID_SHORT:
+      break;
+    }
+
+  strcat(search,")");
+
+  if(!opt->flags.include_disabled)
+    strcat(search,"(pgpDisabled=0)");
+
+  if(!opt->flags.include_revoked)
+    strcat(search,"(pgpRevoked=0)");
+
+  if(!opt->flags.include_disabled || !opt->flags.include_revoked)
+    strcat(search,")");
+
+  if(opt->verbose>2)
+    fprintf(console,"gpgkeys: LDAP search for: %s\n",search);
+
+  err=ldap_search_s(ldap,basekeyspacedn,
+                   LDAP_SCOPE_SUBTREE,search,attrs,0,&res);
+  if(err!=LDAP_SUCCESS && err!=LDAP_SIZELIMIT_EXCEEDED)
+    {
+      int errtag=ldap_err_to_gpg_err(err);
+
+      fprintf(output,"SEARCH %s FAILED %d\n",searchkey,errtag);
+      fprintf(console,"gpgkeys: LDAP search error: %s\n",ldap_err2string(err));
+      return errtag;
+    }
+
+  /* The LDAP server doesn't return a real count of unique keys, so we
+     can't use ldap_count_entries here. */
+  each=ldap_first_entry(ldap,res);
+  while(each!=NULL)
+    {
+      char **certid=ldap_get_values(ldap,each,"pgpcertid");
+
+      if(certid!=NULL)
+       {
+         if(!key_in_keylist(certid[0],dupelist))
+           {
+             int rc=add_key_to_keylist(certid[0],&dupelist);
+             if(rc!=0)
+               {
+                 fprintf(output,"SEARCH %s FAILED %d\n",searchkey,rc);
+                 free_keylist(dupelist);
+                 return rc;
+               }
+
+             count++;
+           }
+       }
+
+      each=ldap_next_entry(ldap,each);
+    }
+
+  if(err==LDAP_SIZELIMIT_EXCEEDED)
+    {
+      if(count==1)
+       fprintf(console,"gpgkeys: search results exceeded server limit."
+               "  First %d result shown.\n",count);
+      else
+       fprintf(console,"gpgkeys: search results exceeded server limit."
+               "  First %d results shown.\n",count);
+    }
+
+  free_keylist(dupelist);
+  dupelist=NULL;
+
+  if(count<1)
+    fprintf(output,"info:1:0\n");
+  else
+    {
+      fprintf(output,"info:1:%d\n",count);
+
+      each=ldap_first_entry(ldap,res);
+      while(each!=NULL)
+       {
+         char **certid;
+
+         certid=ldap_get_values(ldap,each,"pgpcertid");
+         if(certid!=NULL)
+           {
+             LDAPMessage *uids;
+
+             /* Have we seen this certid before? */
+             if(!key_in_keylist(certid[0],dupelist))
+               {
+                 int rc=add_key_to_keylist(certid[0],&dupelist);
+                 if(rc)
+                   {
+                     fprintf(output,"SEARCH %s FAILED %d\n",searchkey,rc);
+                     free_keylist(dupelist);
+                     ldap_value_free(certid);
+                     ldap_msgfree(res);
+                     return rc;
+                   }
+
+                 fprintf(output,"pub:%s:",certid[0]);
+
+                 vals=ldap_get_values(ldap,each,"pgpkeytype");
+                 if(vals!=NULL)
+                   {
+                     /* The LDAP server doesn't exactly handle this
+                        well. */
+                     if(strcasecmp(vals[0],"RSA")==0)
+                       fprintf(output,"1");
+                     else if(strcasecmp(vals[0],"DSS/DH")==0)
+                       fprintf(output,"17");
+                     ldap_value_free(vals);
+                   }
+
+                 fputc(':',output);
+
+                 vals=ldap_get_values(ldap,each,"pgpkeysize");
+                 if(vals!=NULL)
+                   {
+                     /* Not sure why, but some keys are listed with a
+                        key size of 0.  Treat that like an
+                        unknown. */
+                     if(atoi(vals[0])>0)
+                       fprintf(output,"%d",atoi(vals[0]));
+                     ldap_value_free(vals);
+                   }
+
+                 fputc(':',output);
+
+                 /* YYYYMMDDHHmmssZ */
+
+                 vals=ldap_get_values(ldap,each,"pgpkeycreatetime");
+                 if(vals!=NULL && strlen(vals[0])==15)
+                   {
+                     fprintf(output,"%u",
+                             (unsigned int)ldap2epochtime(vals[0]));
+                     ldap_value_free(vals);
+                   }
+
+                 fputc(':',output);
+
+                 vals=ldap_get_values(ldap,each,"pgpkeyexpiretime");
+                 if(vals!=NULL && strlen(vals[0])==15)
+                   {
+                     fprintf(output,"%u",
+                             (unsigned int)ldap2epochtime(vals[0]));
+                     ldap_value_free(vals);
+                   }
+
+                 fputc(':',output);
+
+                 vals=ldap_get_values(ldap,each,"pgprevoked");
+                 if(vals!=NULL)
+                   {
+                     if(atoi(vals[0])==1)
+                       fprintf(output,"r");
+                     ldap_value_free(vals);
+                   }
+
+                 vals=ldap_get_values(ldap,each,"pgpdisabled");
+                 if(vals!=NULL)
+                   {
+                     if(atoi(vals[0])==1)
+                       fprintf(output,"d");
+                     ldap_value_free(vals);
+                   }
+
+#if 0
+                 /* This is not yet specified in the keyserver
+                    protocol, but may be someday. */
+                 fputc(':',output);
+
+                 vals=ldap_get_values(ldap,each,"modifytimestamp");
+                 if(vals!=NULL && strlen(vals[0])==15)
+                   {
+                     fprintf(output,"%u",
+                             (unsigned int)ldap2epochtime(vals[0]));
+                     ldap_value_free(vals);
+                   }
+#endif
+
+                 fprintf(output,"\n");
+
+                 /* Now print all the uids that have this certid */
+                 uids=ldap_first_entry(ldap,res);
+                 while(uids!=NULL)
+                   {
+                     vals=ldap_get_values(ldap,uids,"pgpcertid");
+                     if(vals!=NULL)
+                       {
+                         if(strcasecmp(certid[0],vals[0])==0)
+                           {
+                             char **uidvals;
+
+                             fprintf(output,"uid:");
+
+                             uidvals=ldap_get_values(ldap,uids,"pgpuserid");
+                             if(uidvals!=NULL)
+                               {
+                                 /* Need to escape any colons */
+                                 printquoted(output,uidvals[0],':');
+                                 ldap_value_free(uidvals);
+                               }
+
+                             fprintf(output,"\n");
+                           }
+
+                         ldap_value_free(vals);
+                       }
+
+                     uids=ldap_next_entry(ldap,uids);
+                   }
+               }
+
+             ldap_value_free(certid);
+           }
+
+         each=ldap_next_entry(ldap,each);
+       }
+    }
+
+  ldap_msgfree(res);
+  free_keylist(dupelist);
+
+  fprintf(output,"SEARCH %s END\n",searchkey);
+
+  return KEYSERVER_OK;
+}
+
+static void
+fail_all(struct keylist *keylist,int err)
+{
+  if(!keylist)
+    return;
+
+  if(opt->action==KS_SEARCH)
+    {
+      fprintf(output,"SEARCH ");
+      while(keylist)
+       {
+         fprintf(output,"%s ",keylist->str);
+         keylist=keylist->next;
+       }
+      fprintf(output,"FAILED %d\n",err);
+    }
+  else
+    while(keylist)
+      {
+       fprintf(output,"KEY %s FAILED %d\n",keylist->str,err);
+       keylist=keylist->next;
+      }
+}
+
+static int
+find_basekeyspacedn(void)
+{
+  int err,i;
+  char *attr[]={"namingContexts",NULL,NULL,NULL};
+  LDAPMessage *res;
+  char **context;
+
+  /* Look for namingContexts */
+  err=ldap_search_s(ldap,"",LDAP_SCOPE_BASE,"(objectClass=*)",attr,0,&res);
+  if(err==LDAP_SUCCESS)
+    {
+      context=ldap_get_values(ldap,res,"namingContexts");
+      if(context)
+       {
+         attr[0]="pgpBaseKeySpaceDN";
+         attr[1]="pgpVersion";
+         attr[2]="pgpSoftware";
+
+         real_ldap=1;
+
+         /* We found some, so try each namingContext as the search base
+            and look for pgpBaseKeySpaceDN.  Because we found this, we
+            know we're talking to a regular-ish LDAP server and not a
+            LDAP keyserver. */
+
+         for(i=0;context[i] && !basekeyspacedn;i++)
+           {
+             char **vals;
+             LDAPMessage *si_res;
+             char *object;
+
+             object=malloc(17+strlen(context[i])+1);
+             if(!object)
+               return -1;
+
+             strcpy(object,"cn=pgpServerInfo,");
+             strcat(object,context[i]);
+
+             err=ldap_search_s(ldap,object,LDAP_SCOPE_BASE,
+                               "(objectClass=*)",attr,0,&si_res);
+             free(object);
+
+             if(err==LDAP_NO_SUCH_OBJECT)
+               continue;
+             else if(err!=LDAP_SUCCESS)
+               return err;
+
+             vals=ldap_get_values(ldap,si_res,"pgpBaseKeySpaceDN");
+             if(vals)
+               {
+                 basekeyspacedn=strdup(vals[0]);
+                 ldap_value_free(vals);
+               }
+
+             if(opt->verbose>1)
+               {
+                 vals=ldap_get_values(ldap,si_res,"pgpSoftware");
+                 if(vals)
+                   {
+                     fprintf(console,"Server: \t%s\n",vals[0]);
+                     ldap_value_free(vals);
+                   }
+
+                 vals=ldap_get_values(ldap,si_res,"pgpVersion");
+                 if(vals)
+                   {
+                     fprintf(console,"Version:\t%s\n",vals[0]);
+                     ldap_value_free(vals);
+                   }
+               }
+
+             ldap_msgfree(si_res);
+           }
+
+         ldap_value_free(context);
+       }
+
+      ldap_msgfree(res);
+    }
+  else
+    {
+      /* We don't have an answer yet, which means the server might be
+        a LDAP keyserver. */
+      char **vals;
+      LDAPMessage *si_res;
+
+      attr[0]="pgpBaseKeySpaceDN";
+      attr[1]="version";
+      attr[2]="software";
+
+      err=ldap_search_s(ldap,"cn=pgpServerInfo",LDAP_SCOPE_BASE,
+                       "(objectClass=*)",attr,0,&si_res);
+      if(err!=LDAP_SUCCESS)
+       return err;
+
+      /* For the LDAP keyserver, this is always "OU=ACTIVE,O=PGP
+        KEYSPACE,C=US", but it might not be in the future. */
+
+      vals=ldap_get_values(ldap,si_res,"baseKeySpaceDN");
+      if(vals)
+       {
+         basekeyspacedn=strdup(vals[0]);
+         ldap_value_free(vals);
+       }
+
+      if(opt->verbose>1)
+       {
+         vals=ldap_get_values(ldap,si_res,"software");
+         if(vals)
+           {
+             fprintf(console,"Server: \t%s\n",vals[0]);
+             ldap_value_free(vals);
+           }
+       }
+
+      vals=ldap_get_values(ldap,si_res,"version");
+      if(vals)
+       {
+         if(opt->verbose>1)
+           fprintf(console,"Version:\t%s\n",vals[0]);
+
+         /* If the version is high enough, use the new pgpKeyV2
+            attribute.  This design if iffy at best, but it matches how
+            PGP does it.  I figure the NAI folks assumed that there would
+            never be a LDAP keyserver vendor with a different numbering
+            scheme. */
+         if(atoi(vals[0])>1)
+           pgpkeystr="pgpKeyV2";
+
+         ldap_value_free(vals);
+       }
+
+      ldap_msgfree(si_res);
+    }   
+
+  return LDAP_SUCCESS;
+}
+
+static void 
+show_help (FILE *fp)
+{
+  fprintf (fp,"-h\thelp\n");
+  fprintf (fp,"-V\tversion\n");
+  fprintf (fp,"-o\toutput to this file\n");
+}
+
+int
+main(int argc,char *argv[])
+{
+  int port=0,arg,err,ret=KEYSERVER_INTERNAL_ERROR;
+  char line[MAX_LINE],*binddn=NULL,*bindpw=NULL;
+  int failed=0,use_ssl=0,use_tls=0,bound=0;
+  struct keylist *keylist=NULL,*keyptr=NULL;
+
+  console=stderr;
+
+  /* Kludge to implement standard GNU options.  */
+  if (argc > 1 && !strcmp (argv[1], "--version"))
+    {
+      fputs ("gpgkeys_ldap (GnuPG) " VERSION"\n", stdout);
+      return 0;
+    }
+  else if (argc > 1 && !strcmp (argv[1], "--help"))
+    {
+      show_help (stdout);
+      return 0;
+    }
+
+  while((arg=getopt(argc,argv,"hVo:"))!=-1)
+    switch(arg)
+      {
+      default:
+      case 'h':
+        show_help (console);
+       return KEYSERVER_OK;
+
+      case 'V':
+       fprintf(stdout,"%d\n%s\n",KEYSERVER_PROTO_VERSION,VERSION);
+       return KEYSERVER_OK;
+
+      case 'o':
+       output=fopen(optarg,"w");
+       if(output==NULL)
+         {
+           fprintf(console,"gpgkeys: Cannot open output file `%s': %s\n",
+                   optarg,strerror(errno));
+           return KEYSERVER_INTERNAL_ERROR;
+         }
+
+       break;
+      }
+
+  if(argc>optind)
+    {
+      input=fopen(argv[optind],"r");
+      if(input==NULL)
+       {
+         fprintf(console,"gpgkeys: Cannot open input file `%s': %s\n",
+                 argv[optind],strerror(errno));
+         return KEYSERVER_INTERNAL_ERROR;
+       }
+    }
+
+  if(input==NULL)
+    input=stdin;
+
+  if(output==NULL)
+    output=stdout;
+
+  opt=init_ks_options();
+  if(!opt)
+    return KEYSERVER_NO_MEMORY;
+
+  /* Get the command and info block */
+
+  while(fgets(line,MAX_LINE,input)!=NULL)
+    {
+      char optionstr[MAX_OPTION+1];
+
+      if(line[0]=='\n')
+       break;
+
+      err=parse_ks_options(line,opt);
+      if(err>0)
+       {
+         ret=err;
+         goto fail;
+       }
+      else if(err==0)
+       continue;
+
+      if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "[^\n]\n",optionstr)==1)
+       {
+         int no=0;
+         char *start=&optionstr[0];
+
+         optionstr[MAX_OPTION]='\0';
+
+         if(strncasecmp(optionstr,"no-",3)==0)
+           {
+             no=1;
+             start=&optionstr[3];
+           }
+
+         if(strncasecmp(start,"tls",3)==0)
+           {
+             if(no)
+               use_tls=0;
+             else if(start[3]=='=')
+               {
+                 if(strcasecmp(&start[4],"no")==0)
+                   use_tls=0;
+                 else if(strcasecmp(&start[4],"try")==0)
+                   use_tls=1;
+                 else if(strcasecmp(&start[4],"warn")==0)
+                   use_tls=2;
+                 else if(strcasecmp(&start[4],"require")==0)
+                   use_tls=3;
+                 else
+                   use_tls=1;
+               }
+             else if(start[3]=='\0')
+               use_tls=1;
+           }
+         else if(strncasecmp(start,"basedn",6)==0)
+           {
+             if(no)
+               {
+                 free(basekeyspacedn);
+                 basekeyspacedn=NULL;
+               }
+             else if(start[6]=='=')
+               {
+                 free(basekeyspacedn);
+                 basekeyspacedn=strdup(&start[7]);
+                 if(!basekeyspacedn)
+                   {
+                     fprintf(console,"gpgkeys: out of memory while creating "
+                             "base DN\n");
+                     ret=KEYSERVER_NO_MEMORY;
+                     goto fail;
+                   }
+
+                 real_ldap=1;
+               }
+           }
+         else if(strncasecmp(start,"binddn",6)==0)
+           {
+             if(no)
+               {
+                 free(binddn);
+                 binddn=NULL;
+               }
+             else if(start[6]=='=')
+               {
+                 free(binddn);
+                 binddn=strdup(&start[7]);
+                 if(!binddn)
+                   {
+                     fprintf(console,"gpgkeys: out of memory while creating "
+                             "bind DN\n");
+                     ret=KEYSERVER_NO_MEMORY;
+                     goto fail;
+                   }
+
+                 real_ldap=1;
+               }
+           }
+         else if(strncasecmp(start,"bindpw",6)==0)
+           {
+             if(no)
+               {
+                 free(bindpw);
+                 bindpw=NULL;
+               }
+             else if(start[6]=='=')
+               {
+                 free(bindpw);
+                 bindpw=strdup(&start[7]);
+                 if(!bindpw)
+                   {
+                     fprintf(console,"gpgkeys: out of memory while creating "
+                             "bind password\n");
+                     ret=KEYSERVER_NO_MEMORY;
+                     goto fail;
+                   }
+
+                 real_ldap=1;
+               }
+           }
+
+         continue;
+       }
+    }
+
+  if(!opt->scheme)
+    {
+      fprintf(console,"gpgkeys: no scheme supplied!\n");
+      ret=KEYSERVER_SCHEME_NOT_FOUND;
+      goto fail;
+    }
+
+  if(strcasecmp(opt->scheme,"ldaps")==0)
+    {
+      port=636;
+      use_ssl=1;
+    }
+
+  if(opt->port)
+    port=atoi(opt->port);
+
+  if(!opt->host)
+    {
+      fprintf(console,"gpgkeys: no keyserver host provided\n");
+      goto fail;
+    }
+
+  if(opt->timeout && register_timeout()==-1)
+    {
+      fprintf(console,"gpgkeys: unable to register timeout handler\n");
+      return KEYSERVER_INTERNAL_ERROR;
+    }
+
+#if defined(LDAP_OPT_X_TLS_CACERTFILE) && defined(HAVE_LDAP_SET_OPTION)
+
+  if(opt->ca_cert_file)
+    {
+      err=ldap_set_option(NULL,LDAP_OPT_X_TLS_CACERTFILE,opt->ca_cert_file);
+      if(err!=LDAP_SUCCESS)
+       {
+         fprintf(console,"gpgkeys: unable to set ca-cert-file: %s\n",
+                 ldap_err2string(err));
+         ret=KEYSERVER_INTERNAL_ERROR;
+         goto fail;
+       }
+    }
+#endif /* LDAP_OPT_X_TLS_CACERTFILE && HAVE_LDAP_SET_OPTION */
+
+  /* SSL trumps TLS */
+  if(use_ssl)
+    use_tls=0;
+
+  /* If it's a GET or a SEARCH, the next thing to come in is the
+     keyids.  If it's a SEND, then there are no keyids. */
+
+  if(opt->action==KS_SEND)
+    while(fgets(line,MAX_LINE,input)!=NULL && line[0]!='\n');
+  else if(opt->action==KS_GET
+         || opt->action==KS_GETNAME || opt->action==KS_SEARCH)
+    {
+      for(;;)
+       {
+         struct keylist *work;
+
+         if(fgets(line,MAX_LINE,input)==NULL)
+           break;
+         else
+           {
+             if(line[0]=='\n' || line[0]=='\0')
+               break;
+
+             work=malloc(sizeof(struct keylist));
+             if(work==NULL)
+               {
+                 fprintf(console,"gpgkeys: out of memory while "
+                         "building key list\n");
+                 ret=KEYSERVER_NO_MEMORY;
+                 goto fail;
+               }
+
+             strcpy(work->str,line);
+
+             /* Trim the trailing \n */
+             work->str[strlen(line)-1]='\0';
+
+             work->next=NULL;
+
+             /* Always attach at the end to keep the list in proper
+                 order for searching */
+             if(keylist==NULL)
+               keylist=work;
+             else
+               keyptr->next=work;
+
+             keyptr=work;
+           }
+       }
+    }
+  else
+    {
+      fprintf(console,"gpgkeys: no keyserver command specified\n");
+      goto fail;
+    }
+
+  /* Send the response */
+
+  fprintf(output,"VERSION %d\n",KEYSERVER_PROTO_VERSION);
+  fprintf(output,"PROGRAM %s\n\n",VERSION);
+
+  if(opt->verbose>1)
+    {
+      fprintf(console,"Host:\t\t%s\n",opt->host);
+      if(port)
+       fprintf(console,"Port:\t\t%d\n",port);
+      fprintf(console,"Command:\t%s\n",ks_action_to_string(opt->action));
+    }
+
+  if(opt->debug)
+    {
+#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(HAVE_LDAP_SET_OPTION)
+      err=ldap_set_option(NULL,LDAP_OPT_DEBUG_LEVEL,&opt->debug);
+      if(err!=LDAP_SUCCESS)
+       fprintf(console,"gpgkeys: unable to set debug mode: %s\n",
+               ldap_err2string(err));
+      else
+       fprintf(console,"gpgkeys: debug level %d\n",opt->debug);
+#else
+      fprintf(console,"gpgkeys: not built with debugging support\n");
+#endif
+    }
+
+  /* We have a timeout set for the setup stuff since it could time out
+     as well. */
+  set_timeout(opt->timeout);
+
+  /* Note that this tries all A records on a given host (or at least,
+     OpenLDAP does). */
+  ldap=ldap_init(opt->host,port);
+  if(ldap==NULL)
+    {
+      fprintf(console,"gpgkeys: internal LDAP init error: %s\n",
+             strerror(errno));
+      fail_all(keylist,KEYSERVER_INTERNAL_ERROR);
+      goto fail;
+    }
+
+  if(use_ssl)
+    {
+#if defined(LDAP_OPT_X_TLS) && defined(HAVE_LDAP_SET_OPTION)
+      int ssl=LDAP_OPT_X_TLS_HARD;
+
+      err=ldap_set_option(ldap,LDAP_OPT_X_TLS,&ssl);
+      if(err!=LDAP_SUCCESS)
+       {
+         fprintf(console,"gpgkeys: unable to make SSL connection: %s\n",
+                 ldap_err2string(err));
+         fail_all(keylist,ldap_err_to_gpg_err(err));
+         goto fail;
+       }
+
+      if(!opt->flags.check_cert)
+       ssl=LDAP_OPT_X_TLS_NEVER;
+
+      err=ldap_set_option(NULL,LDAP_OPT_X_TLS_REQUIRE_CERT,&ssl);
+      if(err!=LDAP_SUCCESS)
+       {
+         fprintf(console,
+                 "gpgkeys: unable to set certificate validation: %s\n",
+                 ldap_err2string(err));
+         fail_all(keylist,ldap_err_to_gpg_err(err));
+         goto fail;
+       }
+#else
+      fprintf(console,"gpgkeys: unable to make SSL connection: %s\n",
+             "not built with LDAPS support");
+      fail_all(keylist,KEYSERVER_INTERNAL_ERROR);
+      goto fail;
+#endif
+    }
+
+  if(!basekeyspacedn)
+    if((err=find_basekeyspacedn()) || !basekeyspacedn)
+      {
+       fprintf(console,"gpgkeys: unable to retrieve LDAP base: %s\n",
+               err?ldap_err2string(err):"not found");
+       fail_all(keylist,ldap_err_to_gpg_err(err));
+       goto fail;
+      }
+
+  /* use_tls: 0=don't use, 1=try silently to use, 2=try loudly to use,
+     3=force use. */
+  if(use_tls)
+    {
+      if(!real_ldap)
+       {
+         if(use_tls>=2)
+           fprintf(console,"gpgkeys: unable to start TLS: %s\n",
+                   "not supported by the NAI LDAP keyserver");
+         if(use_tls==3)
+           {
+             fail_all(keylist,KEYSERVER_INTERNAL_ERROR);
+             goto fail;
+           }
+       }
+      else
+       {
+#if defined(HAVE_LDAP_START_TLS_S) && defined(HAVE_LDAP_SET_OPTION)
+         int ver=LDAP_VERSION3;
+
+         err=ldap_set_option(ldap,LDAP_OPT_PROTOCOL_VERSION,&ver);
+
+#ifdef LDAP_OPT_X_TLS
+         if(err==LDAP_SUCCESS)
+           {
+             if(opt->flags.check_cert)
+               ver=LDAP_OPT_X_TLS_HARD;
+             else
+               ver=LDAP_OPT_X_TLS_NEVER;
+
+             err=ldap_set_option(ldap,LDAP_OPT_X_TLS_REQUIRE_CERT,&ver);
+           }
+#endif
+
+         if(err==LDAP_SUCCESS)
+           err=ldap_start_tls_s(ldap,NULL,NULL);
+
+         if(err!=LDAP_SUCCESS)
+           {
+             if(use_tls>=2 || opt->verbose>2)
+               fprintf(console,"gpgkeys: unable to start TLS: %s\n",
+                       ldap_err2string(err));
+             /* Are we forcing it? */
+             if(use_tls==3)
+               {
+                 fail_all(keylist,ldap_err_to_gpg_err(err));
+                 goto fail;
+               }
+           }
+         else if(opt->verbose>1)
+           fprintf(console,"gpgkeys: TLS started successfully.\n");
+#else
+         if(use_tls>=2)
+           fprintf(console,"gpgkeys: unable to start TLS: %s\n",
+                   "not built with TLS support");
+         if(use_tls==3)
+           {
+             fail_all(keylist,KEYSERVER_INTERNAL_ERROR);
+             goto fail;
+           }
+#endif
+       }
+    }
+
+  /* By default we don't bind as there is usually no need to.  For
+     cases where the server needs some authentication, the user can
+     use binddn and bindpw for auth. */
+
+  if(binddn)
+    {
+#ifdef HAVE_LDAP_SET_OPTION
+      int ver=LDAP_VERSION3;
+
+      err=ldap_set_option(ldap,LDAP_OPT_PROTOCOL_VERSION,&ver);
+      if(err!=LDAP_SUCCESS)
+       {
+         fprintf(console,"gpgkeys: unable to go to LDAP 3: %s\n",
+                 ldap_err2string(err));
+         fail_all(keylist,ldap_err_to_gpg_err(err));
+         goto fail;
+       }
+#endif
+
+      if(opt->verbose>2)
+       fprintf(console,"gpgkeys: LDAP bind to %s, pw %s\n",binddn,
+               bindpw?">not shown<":">none<");
+      err=ldap_simple_bind_s(ldap,binddn,bindpw);
+      if(err!=LDAP_SUCCESS)
+       {
+         fprintf(console,"gpgkeys: internal LDAP bind error: %s\n",
+                 ldap_err2string(err));
+         fail_all(keylist,ldap_err_to_gpg_err(err));
+         goto fail;
+       }
+      else
+       bound=1;
+    }
+
+  if(opt->action==KS_GET)
+    {
+      keyptr=keylist;
+
+      while(keyptr!=NULL)
+       {
+         set_timeout(opt->timeout);
+
+         if(get_key(keyptr->str)!=KEYSERVER_OK)
+           failed++;
+
+         keyptr=keyptr->next;
+       }
+    }
+  else if(opt->action==KS_GETNAME)
+    {
+      keyptr=keylist;
+
+      while(keyptr!=NULL)
+       {
+         set_timeout(opt->timeout);
+
+         if(get_name(keyptr->str)!=KEYSERVER_OK)
+           failed++;
+
+         keyptr=keyptr->next;
+       }
+    }
+  else if(opt->action==KS_SEND)
+    {
+      int eof=0;
+
+      do
+       {
+         set_timeout(opt->timeout);
+
+         if(real_ldap)
+           {
+             if(send_key(&eof)!=KEYSERVER_OK)
+               failed++;
+           }
+         else
+           {
+             if(send_key_keyserver(&eof)!=KEYSERVER_OK)
+               failed++;
+           }
+       }
+      while(!eof);
+    }
+  else if(opt->action==KS_SEARCH)
+    {
+      char *searchkey=NULL;
+      int len=0;
+
+      set_timeout(opt->timeout);
+
+      /* To search, we stick a * in between each key to search for.
+        This means that if the user enters words, they'll get
+        "enters*words".  If the user "enters words", they'll get
+        "enters words" */
+
+      keyptr=keylist;
+      while(keyptr!=NULL)
+       {
+         len+=strlen(keyptr->str)+1;
+         keyptr=keyptr->next;
+       }
+
+      searchkey=malloc(len+1);
+      if(searchkey==NULL)
+       {
+         ret=KEYSERVER_NO_MEMORY;
+         fail_all(keylist,KEYSERVER_NO_MEMORY);
+         goto fail;
+       }
+
+      searchkey[0]='\0';
+
+      keyptr=keylist;
+      while(keyptr!=NULL)
+       {
+         strcat(searchkey,keyptr->str);
+         strcat(searchkey,"*");
+         keyptr=keyptr->next;
+       }
+
+      /* Nail that last "*" */
+      if(*searchkey)
+       searchkey[strlen(searchkey)-1]='\0';
+
+      if(search_key(searchkey)!=KEYSERVER_OK)
+       failed++;
+
+      free(searchkey);
+    }
+  else
+    BUG();
+
+  if(!failed)
+    ret=KEYSERVER_OK;
+
+ fail:
+
+  while(keylist!=NULL)
+    {
+      struct keylist *current=keylist;
+      keylist=keylist->next;
+      free(current);
+    }
+
+  if(input!=stdin)
+    fclose(input);
+
+  if(output!=stdout)
+    fclose(output);
+
+  free_ks_options(opt);
+
+  if(ldap!=NULL && bound)
+    ldap_unbind_s(ldap);
+
+  free(basekeyspacedn);
+
+  return ret;
+}
diff --git a/keyserver/gpgkeys_mailto.in b/keyserver/gpgkeys_mailto.in
new file mode 100755 (executable)
index 0000000..e37f5c0
--- /dev/null
@@ -0,0 +1,225 @@
+#!@PERL@ -w
+
+# gpgkeys_mailto - talk to a email keyserver 
+# Copyright (C) 2001, 2002 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+use Getopt::Std;
+$Getopt::Std::STANDARD_HELP_VERSION=1;
+$sendmail="@SENDMAIL@ -t";
+
+###
+
+sub VERSION_MESSAGE ()
+{
+    print STDOUT "gpgkeys_mailto (GnuPG) @VERSION@\n";
+}
+
+sub HELP_MESSAGE ()
+{
+    print STDOUT <<EOT
+
+--help     Print this help
+--version  Print the version
+-o FILE    Write output to FILE
+EOT
+}
+
+
+
+getopts('o:');
+
+if(defined($opt_o))
+{
+    open(STDOUT,">$opt_o") || die "Can't open output file $opt_o\n";
+}
+
+if(@ARGV)
+{
+    open(STDIN,$ARGV[0]) || die "Can't open input file $ARGV[0]\n";
+}
+
+($login,$name)=(getpwuid($<))[0,6];
+
+$from="$name <$login>";
+
+while(<STDIN>)
+{
+    last if($_ eq "\n");
+
+    if(/^COMMAND (\S+)/)
+    {
+       $command=$1;
+    }
+
+    if(/^OPAQUE (\S+)/)
+    {
+       $address=$1;
+    }
+
+    if(/^PROGRAM (\S+)/)
+    {
+       $program=$1;
+    }
+
+    if(/^OPTION (\S+)/)
+    {
+       if($1=~/^verbose$/i)
+       {
+           $verbose++;
+       }
+       elsif($1=~/^no-verbose$/i)
+       {
+           $verbose--;
+       }
+    }
+}
+
+$program="(unknown)" if(!defined($program));
+
+if(!defined($address))
+{
+    print STDERR "gpgkeys: no address provided\n";
+    exit(1);
+}
+
+# decode $address
+
+($address,$args)=split(/\?/,$address);
+
+if(defined($args))
+{
+    @pairs = split(/&/, $args);
+    foreach $pair (@pairs)
+    {
+       ($hdr, $val) = split(/=/, $pair);
+       $hdr =~ tr/+/ /;
+       $hdr =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+       $val =~ tr/+/ /;
+       $val =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
+# we only handle "from" right now
+       if($hdr=~/^from$/i)
+       {
+           $from=$val;
+           last;
+       }
+    }
+}
+
+while(<STDIN>)
+{
+    last if($_ eq "\n");
+
+    chomp;
+
+    push(@keys,$_);
+}
+
+# Send response
+
+print "VERSION 1\n";
+print "OPTION OUTOFBAND\n\n";
+
+# Email keyservers get and search the same way
+
+if($command=~/get/i || $command=~/search/i)
+{
+    if($command=~/search/i)
+    {
+       print "COUNT 0\n";
+    }
+
+    foreach $key (@keys)
+    {
+       open(MAIL,"|$sendmail") || die "ERROR: Can't open $sendmail\n";
+       print MAIL "From: $from\n";
+       print MAIL "To: $address\n";
+       if($command=~/get/i)
+       {
+           # mail keyservers don't like long-form keyids
+
+           if(substr($key,0,2) eq "0x")
+           {
+               $key=substr($key,2);
+           }
+
+           if(length($key)>8)
+           {
+               $key=substr($key,-8);
+           }
+
+           print MAIL "Subject: GET 0x$key\n\n";
+       }
+       else
+       {
+           print MAIL "Subject: GET $key\n\n";
+       }
+       print MAIL "GnuPG $program email keyserver request\n";
+       close(MAIL);
+
+       # Tell GnuPG not to expect a key
+       print "KEY $key OUTOFBAND\n";
+
+       if($verbose)
+       {
+           print STDERR "gpgkeys: key $key requested from $address\n";
+       }
+    }
+}
+
+if($command=~/send/i)
+{
+    while(!eof(STDIN))
+    {
+       open(MAIL,"|$sendmail") || die "ERROR: Can't open $sendmail\n";
+       print MAIL "From: $name <$login>\n";
+       print MAIL "To: $address\n";
+       print MAIL "Subject: ADD\n\n";
+
+       while(<STDIN>)
+       {
+           if(/^KEY (\S+) BEGIN$/)
+           {
+               $key=$1;
+               last;
+           }
+       }
+
+       while(<STDIN>)
+       {
+           if(/^KEY \S+ END$/)
+           {
+               last;
+           }
+
+           print MAIL;
+       }
+
+       close(MAIL);
+
+       if($verbose)
+       {
+           print STDERR "gpgkeys: key $key sent to $address\n";
+       }
+    }
+}
+
+
+# Local Variables: 
+# mode:perl
+# End:
diff --git a/keyserver/gpgkeys_test.in b/keyserver/gpgkeys_test.in
new file mode 100755 (executable)
index 0000000..97748cd
--- /dev/null
@@ -0,0 +1,99 @@
+#!@PERL@
+
+# gpgkeys_test - keyserver code tester
+# Copyright (C) 2001 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+use Getopt::Std;
+$Getopt::Std::STANDARD_HELP_VERSION=1;
+
+$|=1;
+
+sub VERSION_MESSAGE ()
+{
+    print STDOUT "gpgkeys_test (GnuPG) @VERSION@\n";
+}
+
+sub HELP_MESSAGE ()
+{
+    print STDOUT <<EOT
+
+--help     Print this help
+--version  Print the version
+EOT
+}
+
+
+getopts('o:');
+
+print STDERR "gpgkeys_test starting\n";
+
+if(defined($opt_o))
+{
+    print STDERR "Using output file $opt_o\n";
+    open(STDOUT,">$opt_o") || die "Can't open output file $opt_o\n";
+}
+
+if(@ARGV)
+{
+    print STDERR "Using input file $ARGV[0]\n";
+    open(STDIN,$ARGV[0]) || die "Can't open input file $ARGV[0]\n";
+}
+
+# Get the command block
+
+print STDERR "Command block:\n";
+
+while(<STDIN>)
+{
+    last if($_ eq "\n");
+    print STDERR "--command-> $_";
+
+    if(/^COMMAND (\w+)/)
+    {
+       $command=$1;
+    }
+}
+
+# Get the keylist block
+
+print STDERR "Keylist block:\n";
+
+while(<STDIN>)
+{
+    last if($_ eq "\n");
+    print STDERR "--keylist-> $_";
+}
+
+# If it's a SEND, then get the key material
+
+if($command eq "SEND")
+{
+    print STDERR "Key material to send:\n";
+
+    while(<STDIN>)
+    {
+       print STDERR "$_";
+    }
+}
+
+printf STDERR "gpgkeys_test finished\n";
+
+# Local Variables: 
+# mode:perl
+# End:
diff --git a/keyserver/ksutil.c b/keyserver/ksutil.c
new file mode 100644 (file)
index 0000000..64912bb
--- /dev/null
@@ -0,0 +1,540 @@
+/* ksutil.c - general keyserver utility functions
+ * Copyright (C) 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+#include <signal.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#ifdef HAVE_LIBCURL
+#include <curl/curl.h>
+#else
+#include "curl-shim.h"
+#endif
+#include "keyserver.h"
+#include "ksutil.h"
+
+#ifdef HAVE_DOSISH_SYSTEM
+
+unsigned int set_timeout(unsigned int seconds) {return 0;}
+int register_timeout(void) {return 0;}
+
+#else
+
+static void
+catch_alarm(int foo)
+{
+  (void)foo;
+  _exit(KEYSERVER_TIMEOUT);
+}
+
+unsigned int
+set_timeout(unsigned int seconds)
+{
+  return alarm(seconds);
+}
+
+int
+register_timeout(void)
+{
+#if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION)
+  struct sigaction act;
+
+  act.sa_handler=catch_alarm;
+  sigemptyset(&act.sa_mask);
+  act.sa_flags=0;
+  return sigaction(SIGALRM,&act,NULL);
+#else 
+  if(signal(SIGALRM,catch_alarm)==SIG_ERR)
+    return -1;
+  else
+    return 0;
+#endif
+}
+
+#endif /* !HAVE_DOSISH_SYSTEM */
+
+struct ks_options *
+init_ks_options(void)
+{
+  struct ks_options *opt;
+
+  opt=calloc(1,sizeof(struct ks_options));
+
+  if(opt)
+    {
+      opt->action=KS_UNKNOWN;
+      opt->flags.include_revoked=1;
+      opt->flags.include_subkeys=1;
+      opt->flags.check_cert=1;
+      opt->timeout=DEFAULT_KEYSERVER_TIMEOUT;
+      opt->path=strdup("/");
+      if(!opt->path)
+       {
+         free(opt);
+         opt=NULL;
+       }
+    }
+
+  return opt;
+}
+
+void
+free_ks_options(struct ks_options *opt)
+{
+  if(opt)
+    {
+      free(opt->host);
+      free(opt->port);
+      free(opt->scheme);
+      free(opt->auth);
+      free(opt->path);
+      free(opt->opaque);
+      free(opt->ca_cert_file);
+      free(opt);
+    }
+}
+
+/* Returns 0 if we "ate" the line.  Returns >0, a KEYSERVER_ error
+   code if that error applies.  Returns -1 if we did not match the
+   line at all. */
+int
+parse_ks_options(char *line,struct ks_options *opt)
+{
+  int version;
+  char command[MAX_COMMAND+1];
+  char host[MAX_HOST+1];
+  char port[MAX_PORT+1];
+  char scheme[MAX_SCHEME+1];
+  char auth[MAX_AUTH+1];
+  char path[URLMAX_PATH+1];
+  char opaque[MAX_OPAQUE+1];
+  char option[MAX_OPTION+1];
+
+  if(line[0]=='#')
+    return 0;
+
+  if(sscanf(line,"COMMAND %" MKSTRING(MAX_COMMAND) "s\n",command)==1)
+    {
+      command[MAX_COMMAND]='\0';
+
+      if(strcasecmp(command,"get")==0)
+       opt->action=KS_GET;
+      else if(strcasecmp(command,"getname")==0)
+       opt->action=KS_GETNAME;
+      else if(strcasecmp(command,"send")==0)
+       opt->action=KS_SEND;
+      else if(strcasecmp(command,"search")==0)
+       opt->action=KS_SEARCH;
+
+      return 0;
+    }
+
+  if(sscanf(line,"HOST %" MKSTRING(MAX_HOST) "s\n",host)==1)
+    {
+      host[MAX_HOST]='\0';
+      free(opt->host);
+      opt->host=strdup(host);
+      if(!opt->host)
+       return KEYSERVER_NO_MEMORY;
+      return 0;
+    }
+
+  if(sscanf(line,"PORT %" MKSTRING(MAX_PORT) "s\n",port)==1)
+    {
+      port[MAX_PORT]='\0';
+      free(opt->port);
+      opt->port=strdup(port);
+      if(!opt->port)
+       return KEYSERVER_NO_MEMORY;
+      return 0;
+    }
+
+  if(sscanf(line,"SCHEME %" MKSTRING(MAX_SCHEME) "s\n",scheme)==1)
+    {
+      scheme[MAX_SCHEME]='\0';
+      free(opt->scheme);
+      opt->scheme=strdup(scheme);
+      if(!opt->scheme)
+       return KEYSERVER_NO_MEMORY;
+      return 0;
+    }
+
+  if(sscanf(line,"AUTH %" MKSTRING(MAX_AUTH) "s\n",auth)==1)
+    {
+      auth[MAX_AUTH]='\0';
+      free(opt->auth);
+      opt->auth=strdup(auth);
+      if(!opt->auth)
+       return KEYSERVER_NO_MEMORY;
+      return 0;
+    }
+
+  if(sscanf(line,"PATH %" MKSTRING(URLMAX_PATH) "s\n",path)==1)
+    {
+      path[URLMAX_PATH]='\0';
+      free(opt->path);
+      opt->path=strdup(path);
+      if(!opt->path)
+       return KEYSERVER_NO_MEMORY;
+      return 0;
+    }
+
+  if(sscanf(line,"OPAQUE %" MKSTRING(MAX_OPAQUE) "s\n",opaque)==1)
+    {
+      opaque[MAX_OPAQUE]='\0';
+      free(opt->opaque);
+      opt->opaque=strdup(opaque);
+      if(!opt->opaque)
+       return KEYSERVER_NO_MEMORY;
+      return 0;
+    }
+
+  if(sscanf(line,"VERSION %d\n",&version)==1)
+    {
+      if(version!=KEYSERVER_PROTO_VERSION)
+       return KEYSERVER_VERSION_ERROR;
+
+      return 0;
+    }
+
+  if(sscanf(line,"OPTION %" MKSTRING(MAX_OPTION) "[^\n]\n",option)==1)
+    {
+      int no=0;
+      char *start=&option[0];
+
+      option[MAX_OPTION]='\0';
+
+      if(strncasecmp(option,"no-",3)==0)
+       {
+         no=1;
+         start=&option[3];
+       }
+
+      if(strncasecmp(start,"verbose",7)==0)
+       {
+         if(no)
+           opt->verbose=0;
+         else if(start[7]=='=')
+           opt->verbose=atoi(&start[8]);
+         else
+           opt->verbose++;
+       }
+      else if(strcasecmp(start,"include-disabled")==0)
+       {
+         if(no)
+           opt->flags.include_disabled=0;
+         else
+           opt->flags.include_disabled=1;
+       }
+      else if(strcasecmp(start,"include-revoked")==0)
+       {
+         if(no)
+           opt->flags.include_revoked=0;
+         else
+           opt->flags.include_revoked=1;
+       }
+      else if(strcasecmp(start,"include-subkeys")==0)
+       {
+         if(no)
+           opt->flags.include_subkeys=0;
+         else
+           opt->flags.include_subkeys=1;
+       }
+      else if(strcasecmp(start,"check-cert")==0)
+       {
+         if(no)
+           opt->flags.check_cert=0;
+         else
+           opt->flags.check_cert=1;
+       }
+      else if(strncasecmp(start,"debug",5)==0)
+       {
+         if(no)
+           opt->debug=0;
+         else if(start[5]=='=')
+           opt->debug=atoi(&start[6]);
+         else if(start[5]=='\0')
+           opt->debug=1;
+       }
+      else if(strncasecmp(start,"timeout",7)==0)
+       {
+         if(no)
+           opt->timeout=0;
+         else if(start[7]=='=')
+           opt->timeout=atoi(&start[8]);
+         else if(start[7]=='\0')
+           opt->timeout=DEFAULT_KEYSERVER_TIMEOUT;
+       }
+      else if(strncasecmp(start,"ca-cert-file",12)==0)
+       {
+         if(no)
+           {
+             free(opt->ca_cert_file);
+             opt->ca_cert_file=NULL;
+           }
+         else if(start[12]=='=')
+           {
+             free(opt->ca_cert_file);
+             opt->ca_cert_file=strdup(&start[13]);
+             if(!opt->ca_cert_file)
+               return KEYSERVER_NO_MEMORY;
+           }
+       }
+    }
+
+  return -1;
+}
+
+const char *
+ks_action_to_string(enum ks_action action)
+{
+  switch(action)
+    {
+    case KS_UNKNOWN: return "UNKNOWN";
+    case KS_GET:     return "GET";
+    case KS_GETNAME: return "GETNAME";
+    case KS_SEND:    return "SEND";
+    case KS_SEARCH:  return "SEARCH";
+    }
+
+  return "?";
+}
+
+/* Canonicalize CRLF to just LF by stripping CRs.  This actually makes
+   sense, since on Unix-like machines LF is correct, and on win32-like
+   machines, our output buffer is opened in textmode and will
+   re-canonicalize line endings back to CRLF.  Since we only need to
+   handle armored keys, we don't have to worry about odd cases like
+   CRCRCR and the like. */
+
+void
+print_nocr(FILE *stream,const char *str)
+{
+  while(*str)
+    {
+      if(*str!='\r')
+       fputc(*str,stream);
+      str++;
+    }
+}
+
+enum ks_search_type
+classify_ks_search(const char **search)
+{
+  switch(**search)
+    {
+    case '*':
+      (*search)++;
+      return KS_SEARCH_SUBSTR;
+    case '=':
+      (*search)++;
+      return KS_SEARCH_EXACT;
+    case '<':
+      (*search)++;
+      return KS_SEARCH_MAIL;
+    case '@':
+      (*search)++;
+      return KS_SEARCH_MAILSUB;
+    case '0':
+      if((*search)[1]=='x')
+       {
+         if(strlen(*search)==10
+            && strspn(*search,"abcdefABCDEF1234567890x")==10)
+           {
+             (*search)+=2;
+             return KS_SEARCH_KEYID_SHORT;
+           }
+         else if(strlen(*search)==18
+                 && strspn(*search,"abcdefABCDEF1234567890x")==18)
+           {
+             (*search)+=2;
+             return KS_SEARCH_KEYID_LONG;
+           }
+       }
+      /* fall through */
+    default:
+      return KS_SEARCH_SUBSTR;
+    }
+}
+
+int
+curl_err_to_gpg_err(CURLcode error)
+{
+  switch(error)
+    {
+    case CURLE_OK:                    return KEYSERVER_OK;
+    case CURLE_UNSUPPORTED_PROTOCOL:  return KEYSERVER_SCHEME_NOT_FOUND;
+    case CURLE_COULDNT_CONNECT:       return KEYSERVER_UNREACHABLE;
+    case CURLE_FTP_COULDNT_RETR_FILE: return KEYSERVER_KEY_NOT_FOUND;
+    default: return KEYSERVER_INTERNAL_ERROR;
+    }
+}
+
+#define B64 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
+
+static void
+curl_armor_writer(const unsigned char *buf,size_t size,void *cw_ctx)
+{
+  struct curl_writer_ctx *ctx=cw_ctx;
+  size_t idx=0;
+
+  while(idx<size)
+    {
+      for(;ctx->armor_remaining<3 && idx<size;ctx->armor_remaining++,idx++)
+       ctx->armor_ctx[ctx->armor_remaining]=buf[idx];
+
+      if(ctx->armor_remaining==3)
+       {
+         /* Top 6 bytes of ctx->armor_ctx[0] */
+         fputc(B64[(ctx->armor_ctx[0]>>2)&0x3F],ctx->stream);
+         /* Bottom 2 bytes of ctx->armor_ctx[0] and top 4 bytes of
+            ctx->armor_ctx[1] */
+         fputc(B64[(((ctx->armor_ctx[0]<<4)&0x30)
+                    |((ctx->armor_ctx[1]>>4)&0x0F))&0x3F],ctx->stream);
+         /* Bottom 4 bytes of ctx->armor_ctx[1] and top 2 bytes of
+            ctx->armor_ctx[2] */
+         fputc(B64[(((ctx->armor_ctx[1]<<2)&0x3C)
+                    |((ctx->armor_ctx[2]>>6)&0x03))&0x3F],ctx->stream);
+         /* Bottom 6 bytes of ctx->armor_ctx[2] */
+         fputc(B64[(ctx->armor_ctx[2]&0x3F)],ctx->stream);
+
+         ctx->linelen+=4;
+         if(ctx->linelen>=70)
+           {
+             fputc('\n',ctx->stream);
+             ctx->linelen=0;
+           }
+
+         ctx->armor_remaining=0;
+       }
+    }
+
+}
+
+size_t
+curl_writer(const void *ptr,size_t size,size_t nmemb,void *cw_ctx)
+{
+  struct curl_writer_ctx *ctx=cw_ctx;
+  const char *buf=ptr;
+  size_t i;
+
+  if(!ctx->flags.initialized)
+    {
+      if(size*nmemb==0)
+       return 0;
+
+      /* The object we're fetching is in binary form */
+      if(*buf&0x80)
+       {
+         ctx->flags.armor=1;
+         fprintf(ctx->stream,BEGIN"\n\n");
+       }
+      else
+       ctx->marker=BEGIN;
+
+      ctx->flags.initialized=1;
+    }
+
+  if(ctx->flags.armor)
+    curl_armor_writer(ptr,size*nmemb,cw_ctx);
+  else
+    {
+      /* scan the incoming data for our marker */
+      for(i=0;!ctx->flags.done && i<(size*nmemb);i++)
+       {
+         if(buf[i]==ctx->marker[ctx->markeridx])
+           {
+             ctx->markeridx++;
+             if(ctx->marker[ctx->markeridx]=='\0')
+               {
+                 if(ctx->flags.begun)
+                   ctx->flags.done=1;
+                 else
+                   {
+                     /* We've found the BEGIN marker, so now we're
+                        looking for the END marker. */
+                     ctx->flags.begun=1;
+                     ctx->marker=END;
+                     ctx->markeridx=0;
+                     fprintf(ctx->stream,BEGIN);
+                     continue;
+                   }
+               }
+           }
+         else
+           ctx->markeridx=0;
+
+         if(ctx->flags.begun)
+           {
+             /* Canonicalize CRLF to just LF by stripping CRs.  This
+                actually makes sense, since on Unix-like machines LF
+                is correct, and on win32-like machines, our output
+                buffer is opened in textmode and will re-canonicalize
+                line endings back to CRLF.  Since this code is just
+                for handling armored keys, we don't have to worry
+                about odd cases like CRCRCR and the like. */
+
+             if(buf[i]!='\r')
+               fputc(buf[i],ctx->stream);
+           }
+       }
+    }
+
+  return size*nmemb;
+}
+
+void
+curl_writer_finalize(struct curl_writer_ctx *ctx)
+{
+  if(ctx->flags.armor)
+    {
+      if(ctx->armor_remaining==2)
+       {
+         /* Top 6 bytes of ctx->armorctx[0] */
+         fputc(B64[(ctx->armor_ctx[0]>>2)&0x3F],ctx->stream);
+         /* Bottom 2 bytes of ctx->armor_ctx[0] and top 4 bytes of
+            ctx->armor_ctx[1] */
+         fputc(B64[(((ctx->armor_ctx[0]<<4)&0x30)
+                    |((ctx->armor_ctx[1]>>4)&0x0F))&0x3F],ctx->stream);
+         /* Bottom 4 bytes of ctx->armor_ctx[1] */
+         fputc(B64[((ctx->armor_ctx[1]<<2)&0x3C)],ctx->stream);
+         /* Pad */
+         fputc('=',ctx->stream);
+       }
+      else if(ctx->armor_remaining==1)
+       {
+         /* Top 6 bytes of ctx->armor_ctx[0] */
+         fputc(B64[(ctx->armor_ctx[0]>>2)&0x3F],ctx->stream);
+         /* Bottom 2 bytes of ctx->armor_ctx[0] */
+         fputc(B64[((ctx->armor_ctx[0]<<4)&0x30)],ctx->stream);
+         /* Pad */
+         fputc('=',ctx->stream);
+         /* Pad */
+         fputc('=',ctx->stream);
+       }
+
+      fprintf(ctx->stream,"\n"END);
+      ctx->flags.done=1;
+    }
+}
diff --git a/keyserver/ksutil.h b/keyserver/ksutil.h
new file mode 100644 (file)
index 0000000..16c1ebf
--- /dev/null
@@ -0,0 +1,130 @@
+/* ksutil.h
+ * Copyright (C) 2004, 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#ifndef _KSUTIL_H_
+#define _KSUTIL_H_
+
+#ifdef HAVE_LIBCURL
+#include <curl/curl.h>
+#else
+#include "curl-shim.h"
+#endif
+
+/* MAX_LINE must be at least 1 larger than the largest item we expect
+   to receive, including the name tag ("COMMAND", "PORT", etc) and
+   space between.  In practice, that means it should be
+   strlen("OPAQUE")+1+sizeof_opaque+1 */
+#define MAX_LINE       (6+1+1024+1)
+
+#define MAX_COMMAND    7
+#define MAX_OPTION   256
+#define MAX_SCHEME    20
+#define MAX_OPAQUE  1024
+#define MAX_AUTH     128
+#define MAX_HOST      80
+#define MAX_PORT      10
+#define URLMAX_PATH 1024
+#define MAX_PROXY    128
+#define MAX_URL     (MAX_SCHEME+1+3+MAX_AUTH+1+1+MAX_HOST+1+1 \
+                     +MAX_PORT+1+1+URLMAX_PATH+1+50)
+
+#define STRINGIFY(x) #x
+#define MKSTRING(x) STRINGIFY(x)
+
+#define BEGIN "-----BEGIN PGP PUBLIC KEY BLOCK-----"
+#define END   "-----END PGP PUBLIC KEY BLOCK-----"
+
+#ifdef __riscos__
+#define HTTP_PROXY_ENV           "GnuPG$HttpProxy"
+#else
+#define HTTP_PROXY_ENV           "http_proxy"
+#endif
+
+struct keylist
+{
+  char str[MAX_LINE];
+  struct keylist *next;
+};
+
+/* 2 minutes seems reasonable */
+#define DEFAULT_KEYSERVER_TIMEOUT 120
+
+unsigned int set_timeout(unsigned int seconds);
+int register_timeout(void);
+
+enum ks_action {KS_UNKNOWN=0,KS_GET,KS_GETNAME,KS_SEND,KS_SEARCH};
+
+enum ks_search_type {KS_SEARCH_SUBSTR,KS_SEARCH_EXACT,
+                    KS_SEARCH_MAIL,KS_SEARCH_MAILSUB,
+                    KS_SEARCH_KEYID_LONG,KS_SEARCH_KEYID_SHORT};
+
+struct ks_options
+{
+  enum ks_action action;
+  char *host;
+  char *port;
+  char *scheme;
+  char *auth;
+  char *path;
+  char *opaque;
+  struct
+  {
+    unsigned int include_disabled:1;
+    unsigned int include_revoked:1;
+    unsigned int include_subkeys:1;
+    unsigned int check_cert:1;
+  } flags;
+  unsigned int verbose;
+  unsigned int debug;
+  unsigned int timeout;
+  char *ca_cert_file;
+};
+
+struct ks_options *init_ks_options(void);
+void free_ks_options(struct ks_options *opt);
+int parse_ks_options(char *line,struct ks_options *opt);
+const char *ks_action_to_string(enum ks_action action);
+void print_nocr(FILE *stream,const char *str);
+enum ks_search_type classify_ks_search(const char **search);
+
+int curl_err_to_gpg_err(CURLcode error);
+
+struct curl_writer_ctx
+{
+  struct
+  {
+    unsigned int initialized:1;
+    unsigned int begun:1;
+    unsigned int done:1;
+    unsigned int armor:1;
+  } flags;
+
+  int armor_remaining;
+  unsigned char armor_ctx[3];
+  int markeridx,linelen;
+  const char *marker;
+  FILE *stream;
+};
+
+size_t curl_writer(const void *ptr,size_t size,size_t nmemb,void *cw_ctx);
+void curl_writer_finalize(struct curl_writer_ctx *ctx);
+
+#endif /* !_KSUTIL_H_ */
index a7a9d1e..2cc6a30 100644 (file)
@@ -1,3 +1,8 @@
+2006-06-30  Werner Koch  <wk@g10code.com>
+
+       * ldap.m4: New.  Taken from gnupg 1.4.4
+       * Makefile.am (EXTRA_DIST): Add ldap.me
+
 2004-09-30  Werner Koch  <wk@g10code.com>
 
        * gpg-error.m4, libassuan.m4, libgcrypt.m4: Updated.
index a31103f..d218b73 100644 (file)
@@ -1,3 +1,5 @@
 EXTRA_DIST = intmax.m4 longdouble.m4 longlong.m4 printf-posix.m4 signed.m4 size_max.m4 wchar_t.m4 wint_t.m4 xsize.m4  codeset.m4 gettext.m4 glibc21.m4 iconv.m4 intdiv0.m4 inttypes.m4 inttypes_h.m4 inttypes-pri.m4 isc-posix.m4 lcmessage.m4 lib-ld.m4 lib-link.m4 lib-prefix.m4 progtest.m4 stdint_h.m4 uintmax_t.m4 ulonglong.m4
 
+EXTRA_DIST += ldap.m4
+
 EXTRA_DIST += gpg-error.m4 libgcrypt.m4 libassuan.m4 ksba.m4
diff --git a/m4/ldap.m4 b/m4/ldap.m4
new file mode 100644 (file)
index 0000000..95249ac
--- /dev/null
@@ -0,0 +1,97 @@
+dnl Check for LDAP
+dnl Copyright (C) 2005 Free Software Foundation, Inc.
+dnl
+dnl This file is free software, distributed under the terms of the GNU
+dnl General Public License.  As a special exception to the GNU General
+dnl Public License, this file may be distributed as part of a program
+dnl that contains a configuration script generated by Autoconf, under
+dnl the same distribution terms as the rest of that program.
+dnl
+dnl Defines @GPGKEYS_LDAP@ to a executable name if a working ldap
+dnl setup is found, and sets @LDAPLIBS@ to the necessary libraries.
+
+AC_DEFUN([GNUPG_CHECK_LDAP],
+[
+# Try and link a LDAP test program to weed out unusable LDAP
+# libraries.  -lldap [-llber [-lresolv]] is for older OpenLDAPs.
+# OpenLDAP, circa 1999, was terrible with creating weird dependencies.
+# If all else fails, the user can play guess-the-dependency by using
+# something like ./configure LDAPLIBS="-Lfoo -lbar"
+
+AC_ARG_WITH(ldap,
+  AC_HELP_STRING([--with-ldap=DIR],[look for the LDAP library in DIR]),
+  [_ldap_with=$withval])
+
+if test x$_ldap_with != xno ; then
+
+  if test -d "$withval" ; then
+     LDAP_CPPFLAGS="-I$withval/include"
+     LDAP_LDFLAGS="-L$withval/lib"
+  fi
+
+  _ldap_save_cppflags=$CPPFLAGS
+  CPPFLAGS="${LDAP_CPPFLAGS} ${CPPFLAGS}"
+  _ldap_save_ldflags=$LDFLAGS
+  LDFLAGS="${LDAP_LDFLAGS} ${LDFLAGS}"
+
+  for MY_LDAPLIBS in ${LDAPLIBS+"$LDAPLIBS"} "-lldap" "-lldap -llber" "-lldap -llber -lresolv" "-lwldap32"; do
+    _ldap_save_libs=$LIBS
+    LIBS="$MY_LDAPLIBS $1 $LIBS"
+
+    AC_MSG_CHECKING([whether LDAP via \"$MY_LDAPLIBS\" is present and sane])
+    AC_TRY_LINK([
+#ifdef _WIN32
+#include <winsock2.h>
+#include <winldap.h>
+#else
+#include <ldap.h>
+#endif
+],[ldap_open("foobar",1234);],
+                [gnupg_cv_func_ldap_init=yes],[gnupg_cv_func_ldap_init=no])
+    AC_MSG_RESULT([$gnupg_cv_func_ldap_init])
+
+    if test $gnupg_cv_func_ldap_init = no; then
+      AC_MSG_CHECKING([whether I can make LDAP be sane with lber.h])
+      AC_TRY_LINK([#include <lber.h>
+#include <ldap.h>],[ldap_open("foobar",1234);],
+         [gnupg_cv_func_ldaplber_init=yes],[gnupg_cv_func_ldaplber_init=no])
+      AC_MSG_RESULT([$gnupg_cv_func_ldaplber_init])
+    fi
+
+    if test "$gnupg_cv_func_ldaplber_init" = yes ; then
+       AC_DEFINE(NEED_LBER_H,1,[Define if the LDAP library requires including lber.h before ldap.h])
+    fi
+
+    if test "$gnupg_cv_func_ldap_init" = yes || \
+        test "$gnupg_cv_func_ldaplber_init" = yes ; then
+       LDAPLIBS="$LDAP_LDFLAGS $MY_LDAPLIBS"
+       GPGKEYS_LDAP="gpgkeys_ldap$EXEEXT"
+
+       AC_CHECK_FUNCS(ldap_get_option ldap_set_option ldap_start_tls_s)
+
+       if test "$ac_cv_func_ldap_get_option" != yes ; then
+          AC_MSG_CHECKING([whether LDAP supports ld_errno])
+         AC_TRY_LINK([#include <ldap.h>],[LDAP *ldap; ldap->ld_errno;],
+            [gnupg_cv_func_ldap_ld_errno=yes],
+            [gnupg_cv_func_ldap_ld_errno=no])
+          AC_MSG_RESULT([$gnupg_cv_func_ldap_ld_errno])
+
+         if test "$gnupg_cv_func_ldap_ld_errno" = yes ; then
+            AC_DEFINE(HAVE_LDAP_LD_ERRNO,1,[Define if the LDAP library supports ld_errno])
+          fi
+       fi
+    fi
+
+    LIBS=$_ldap_save_libs
+
+    if test "$GPGKEYS_LDAP" != "" ; then break; fi
+  done
+
+  AC_SUBST(GPGKEYS_LDAP)
+  AC_SUBST(LDAPLIBS)
+  AC_SUBST(LDAP_CPPFLAGS)
+
+  CPPFLAGS=$_ldap_save_cppflags
+  LDFLAGS=$_ldap_save_ldflags
+fi
+])dnl