summaryrefslogtreecommitdiff
path: root/dirmngr
diff options
context:
space:
mode:
authorDongHun Kwak <dh0128.kwak@samsung.com>2021-02-09 15:59:53 +0900
committerDongHun Kwak <dh0128.kwak@samsung.com>2021-02-09 15:59:53 +0900
commit929e941c10903b80c933e483a1610240bd52286d (patch)
tree60316c32d86e29c55a03306f7b44eb0c18ca2921 /dirmngr
parentf65267827a88ffd96a41c708e90f4a38b2bc15d3 (diff)
downloadgpg2-929e941c10903b80c933e483a1610240bd52286d.tar.gz
gpg2-929e941c10903b80c933e483a1610240bd52286d.tar.bz2
gpg2-929e941c10903b80c933e483a1610240bd52286d.zip
Imported Upstream version 2.1.0upstream/2.1.0
Diffstat (limited to 'dirmngr')
-rw-r--r--dirmngr/ChangeLog-20111602
-rw-r--r--dirmngr/ChangeLog.1806
-rw-r--r--dirmngr/Makefile.am100
-rw-r--r--dirmngr/OAUTHORS38
-rw-r--r--dirmngr/ONEWS240
-rw-r--r--dirmngr/cdb.h94
-rw-r--r--dirmngr/cdblib.c929
-rw-r--r--dirmngr/certcache.c1394
-rw-r--r--dirmngr/certcache.h103
-rw-r--r--dirmngr/crlcache.c2580
-rw-r--r--dirmngr/crlcache.h70
-rw-r--r--dirmngr/crlfetch.c540
-rw-r--r--dirmngr/crlfetch.h88
-rw-r--r--dirmngr/dirmngr-client.c1031
-rw-r--r--dirmngr/dirmngr-err.h12
-rw-r--r--dirmngr/dirmngr.c2032
-rw-r--r--dirmngr/dirmngr.h206
-rw-r--r--dirmngr/dirmngr_ldap.c718
-rw-r--r--dirmngr/ks-action.c332
-rw-r--r--dirmngr/ks-action.h31
-rw-r--r--dirmngr/ks-engine-finger.c123
-rw-r--r--dirmngr/ks-engine-hkp.c1477
-rw-r--r--dirmngr/ks-engine-http.c172
-rw-r--r--dirmngr/ks-engine-kdns.c79
-rw-r--r--dirmngr/ks-engine.h57
-rw-r--r--dirmngr/ldap-url.c935
-rw-r--r--dirmngr/ldap-url.h50
-rw-r--r--dirmngr/ldap-wrapper-ce.c571
-rw-r--r--dirmngr/ldap-wrapper.c788
-rw-r--r--dirmngr/ldap-wrapper.h40
-rw-r--r--dirmngr/ldap.c843
-rw-r--r--dirmngr/ldapserver.c131
-rw-r--r--dirmngr/ldapserver.h90
-rw-r--r--dirmngr/misc.c564
-rw-r--r--dirmngr/misc.h85
-rw-r--r--dirmngr/ocsp.c796
-rw-r--r--dirmngr/ocsp.h31
-rw-r--r--dirmngr/server.c2183
-rw-r--r--dirmngr/sks-keyservers.netCA.pem32
-rw-r--r--dirmngr/validate.c1159
-rw-r--r--dirmngr/validate.h55
-rw-r--r--dirmngr/w32-ldap-help.h169
42 files changed, 23376 insertions, 0 deletions
diff --git a/dirmngr/ChangeLog-2011 b/dirmngr/ChangeLog-2011
new file mode 100644
index 0000000..84cf552
--- /dev/null
+++ b/dirmngr/ChangeLog-2011
@@ -0,0 +1,1602 @@
+2011-12-01 Werner Koch <wk@g10code.com>
+
+ NB: ChangeLog files are no longer manually maintained. Starting
+ on December 1st, 2011 we put change information only in the GIT
+ commit log, and generate a top-level ChangeLog file from logs at
+ "make dist". See doc/HACKING for details.
+
+2011-11-24 Werner Koch <wk@g10code.com>
+
+ * ks-engine-http.c (ks_http_help): Do not print help for hkp.
+ * ks-engine-hkp.c (ks_hkp_help): Print help only for hkp.
+ (send_request): Remove test code.
+ (map_host): Use xtrymalloc.
+
+ * certcache.c (classify_pattern): Remove unused variable and make
+ explicit substring search work.
+
+2011-06-01 Marcus Brinkmann <mb@g10code.com>
+
+ * Makefile.am (dirmngr_ldap_CFLAGS): Add $(LIBGCRYPT_CFLAGS),
+ which is needed by common/util.h.
+
+2011-04-25 Werner Koch <wk@g10code.com>
+
+ * ks-engine-hkp.c (ks_hkp_search): Mark classify_user_id for use
+ with OpenPGP.
+ (ks_hkp_get): Ditto.
+
+2011-04-12 Werner Koch <wk@g10code.com>
+
+ * ks-engine-hkp.c (ks_hkp_search, ks_hkp_get, ks_hkp_put): Factor
+ code out to ..
+ (make_host_part): new.
+ (hostinfo_s): New.
+ (create_new_hostinfo, find_hostinfo, sort_hostpool)
+ (select_random_host, map_host, mark_host_dead)
+ (ks_hkp_print_hosttable): New.
+
+2011-02-23 Werner Koch <wk@g10code.com>
+
+ * certcache.c (get_cert_bysubject): Take care of a NULL argument.
+ (find_cert_bysubject): Ditto. Fixes bug#1300.
+
+2011-02-09 Werner Koch <wk@g10code.com>
+
+ * ks-engine-kdns.c: New but only the framework.
+
+ * server.c (cmd_keyserver): Add option --help.
+ (dirmngr_status_help): New.
+ * ks-action.c (ks_print_help): New.
+ (ks_action_help): New.
+ * ks-engine-finger.c (ks_finger_help): New.
+ * ks-engine-http.c (ks_http_help): New.
+ * ks-engine-hkp.c (ks_hkp_help): New.
+
+ * ks-action.c (ks_action_fetch): Support http URLs.
+ * ks-engine-http.c: New.
+
+ * ks-engine-finger.c (ks_finger_get): Rename to ks_finger_fetch.
+ Change caller.
+
+2011-02-08 Werner Koch <wk@g10code.com>
+
+ * server.c (cmd_ks_fetch): New.
+ * ks-action.c (ks_action_fetch): New.
+ * ks-engine-finger.c: New.
+
+2011-02-03 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (dirmngr_LDADD): Remove -llber.
+
+2011-01-25 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (handle_connections): Rewrite loop to use pth-select
+ so to sync timeouts to the full second.
+ (pth_thread_id): New.
+ (main) [W32CE]: Fix setting of default homedir.
+
+ * ldap-wrapper.c (ldap_wrapper_thread): Sync to the full second.
+ Increate pth_wait timeout from 1 to 2 seconds.
+
+2011-01-20 Werner Koch <wk@g10code.com>
+
+ * server.c (release_ctrl_keyservers): New.
+ (cmd_keyserver, cmd_ks_seach, cmd_ks_get, cmd_ks_put): New.
+ * dirmngr.h (uri_item_t): New.
+ (struct server_control_s): Add field KEYSERVERS.
+ * ks-engine-hkp.c: New.
+ * ks-engine.h: New.
+ * ks-action.c, ks-action.h: New.
+ * server.c: Include ks-action.h.
+ (cmd_ks_search): New.
+ * Makefile.am (dirmngr_SOURCES): Add new files.
+
+2011-01-19 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (main): Use es_printf for --gpgconf-list.
+
+2010-12-14 Werner Koch <wk@g10code.com>
+
+ * cdb.h (struct cdb) [W32]: Add field CDB_MAPPING.
+ * cdblib.c (cdb_init) [W32]: Save mapping handle.
+ (cdb_free) [W32]: Don't leak the mapping handle from cdb_init by
+ using the saved one.
+
+ * crlcache.c (crl_cache_insert): Close unused matching files.
+
+ * dirmngr.c (main) [W32CE]: Change homedir in daemon mode to /gnupg.
+
+2010-12-07 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (TIMERTICK_INTERVAL) [W32CE]: Change to 60s.
+
+2010-11-23 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (dirmngr_LDFLAGS): Add extra_bin_ldflags.
+ (dirmngr_client_LDFLAGS): Ditto.
+
+2010-10-21 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (main): Changed faked system time warning
+
+2010-10-15 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (CLEANFILES): Add no-libgcrypt.c.
+
+2010-09-16 Werner Koch <wk@g10code.com>
+
+ * validate.c (validate_cert_chain): Use GPG_ERR_MISSING_ISSUER_CERT.
+
+2010-08-13 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (dirmngr_SOURCES): Add w32-ldap-help.h.
+
+ * dirmngr_ldap.c (fetch_ldap): Call ldap_unbind.
+
+ * w32-ldap-help.h: New.
+ * dirmngr_ldap.c [W32CE]: Include w32-ldap-help.h and use the
+ mapped ldap functions.
+
+2010-08-12 Werner Koch <wk@g10code.com>
+
+ * crlcache.c (update_dir, crl_cache_insert): s/unlink/gnupg_remove/.
+
+ * dirmngr.c (dirmngr_sighup_action): New.
+
+ * server.c (cmd_killdirmngr, cmd_reloaddirmngr): New.
+ (struct server_local_s): Add field STOPME.
+ (start_command_handler): Act on STOPME.
+
+2010-08-06 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (JNLIB_NEED_AFLOCAL): Define macro.
+ (main): Use SUN_LEN macro.
+ (main) [W32]: Allow EEXIST in addition to EADDRINUSE.
+
+2010-08-05 Werner Koch <wk@g10code.com>
+
+ * server.c (set_error, leave_cmd): New.
+ (cmd_validate, cmd_ldapserver, cmd_isvalid, cmd_checkcrl)
+ (cmd_checkocsp, cmd_lookup, cmd_listcrls, cmd_cachecert): Use
+ leave_cmd.
+ (cmd_getinfo): New.
+ (data_line_cookie_write, data_line_cookie_close): New.
+ (cmd_listcrls): Replace assuan_get_data_fp by es_fopencookie.
+
+ * misc.c (create_estream_ksba_reader, my_estream_ksba_reader_cb): New.
+ * certcache.c (load_certs_from_dir): Use create_estream_ksba_reader.
+ * crlcache.c (crl_cache_load): Ditto.
+
+2010-08-03 Werner Koch <wk@g10code.com>
+
+ * dirmngr_ldap.c (pth_enter, pth_leave) [USE_LDAPWRAPPER]: Turn
+ into functions for use in a 'for' control stmt.
+
+2010-07-26 Werner Koch <wk@g10code.com>
+
+ * dirmngr_ldap.c (print_ldap_entries): Remove special fwrite case
+ for W32 because that is now handles by estream.
+
+2010-07-25 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (dirmngr_SOURCES) [!USE_LDAPWRAPPER]: Build
+ ldap-wrapper-ce.
+ * ldap-wrapper-ce.c: New.
+
+ * dirmngr_ldap.c (opt): Remove global variable ...
+ (my_opt_t): ... and declare a type instead.
+ (main): Define a MY_OPT variable and change all references to OPT
+ to this.
+ (set_timeout, print_ldap_entries, fetch_ldap, process_url): Pass
+ MYOPT arg.
+
+2010-07-24 Werner Koch <wk@g10code.com>
+
+ * dirmngr_ldap.c (main): Init common subsystems. Call
+ es_set_binary.
+
+2010-07-19 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c: Include ldap-wrapper.h.
+ (launch_reaper_thread): Move code to ...
+ * ldap-wrapper.c (ldap_wrapper_launch_thread): .. here. Change
+ callers.
+ (ldap_wrapper_thread): Rename to ...
+ (wrapper_thread): this and make local.
+
+ * ldap.c (destroy_wrapper, print_log_line)
+ (read_log_data, ldap_wrapper_thread)
+ (ldap_wrapper_wait_connections, ldap_wrapper_release_context)
+ (ldap_wrapper_connection_cleanup, reader_callback, ldap_wrapper):
+ Factor code out to ...
+ * ldap-wrapper.c: new.
+ (ldap_wrapper): Make public.
+ (read_buffer): Copy from ldap.c.
+ * ldap-wrapper.h: New.
+ * Makefile.am (dirmngr_SOURCES): Add new files.
+
+2010-07-16 Werner Koch <wk@g10code.com>
+
+ * http.c, http.h: Remove.
+
+ * dirmngr-err.h: New.
+ * dirmngr.h: Include dirmngr-err.h instead of gpg-error.h
+
+ * cdblib.c: Replace assignments to ERRNO by a call to
+ gpg_err_set_errno. Include dirmngr-err.h.
+ (cdb_free) [__MINGW32CE__]: Do not use get_osfhandle.
+
+ * dirmngr.c [!HAVE_SIGNAL_H]: Don't include signal.h.
+ (USE_W32_SERVICE): New. Use this to control the use of the W32
+ service system.
+
+2010-07-06 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (main): Print note on directory name changes.
+
+ Replace almost all uses of stdio by estream.
+
+ * b64dec.c, b64enc.c: Remove. They are duplicated in ../common/.
+
+2010-06-28 Werner Koch <wk@g10code.com>
+
+ * dirmngr_ldap.c (my_i18n_init): Remove.
+ (main): Call i18n_init instead of above function.
+
+ * dirmngr-client.c (my_i18n_init): Remove.
+ (main): Call i18n_init instead of above function.
+
+ * Makefile.am (dirmngr_LDADD): Add ../gl/libgnu.
+ (dirmngr_ldap_LDADD, dirmngr_client_LDADD): Ditto.
+
+2010-06-09 Werner Koch <wk@g10code.com>
+
+ * i18n.h: Remove.
+
+ * Makefile.am (no-libgcrypt.c): New rule.
+
+ * exechelp.h: Remove.
+ * exechelp.c: Remove.
+ (dirmngr_release_process): Change callers to use the gnupg func.
+ (dirmngr_wait_process): Likewise.
+ (dirmngr_kill_process): Likewise. This actually implements it for
+ W32.
+ * ldap.c (ldap_wrapper): s/get_dirmngr_ldap_path/gnupg_module_name/.
+ (ldap_wrapper_thread): Use gnupg_wait_process and adjust for
+ changed semantics.
+ (ldap_wrapper): Replace xcalloc by xtrycalloc. Replace spawn
+ mechanism.
+
+ * server.c (start_command_handler): Remove assuan_set_log_stream.
+
+ * validate.c: Remove gcrypt.h and ksba.h.
+
+ * ldapserver.c: s/util.h/dirmngr.h/.
+
+ * dirmngr.c (sleep) [W32]: Remove macro.
+ (main): s/sleep/gnupg_sleep/.
+ (pid_suffix_callback): Change arg type.
+ (my_gcry_logger): Remove.
+ (fixed_gcry_pth_init): New.
+ (main): Use it.
+ (FD2INT): Remove.
+
+2010-06-08 Werner Koch <wk@g10code.com>
+
+ * misc.h (copy_time): Remove and replace by gnupg_copy_time which
+ allows to set a null date.
+ * misc.c (dump_isotime, get_time, get_isotime, set_time)
+ (check_isotime, add_isotime): Remove and replace all calls by the
+ versions from common/gettime.c.
+
+ * crlcache.c, misc.c, misc.h: s/dirmngr_isotime_t/gnupg_isotime_t/.
+ * server.c, ldap.c: Reorder include directives.
+ * crlcache.h, misc.h: Remove all include directives.
+
+ * certcache.c (cmp_simple_canon_sexp): Remove.
+ (compare_serialno): Rewrite using cmp_simple_canon_sexp from
+ common/sexputil.c
+
+ * error.h: Remove.
+
+ * dirmngr.c: Remove transitional option "--ignore-ocsp-servic-url".
+ (opts): Use ARGPARSE macros.
+ (i18n_init): Remove.
+ (main): Use GnuPG init functions.
+
+ * dirmngr.h: Remove duplicated stuff now taken from ../common.
+
+ * get-path.c, util.h: Remove.
+
+ * Makefile.am: Adjust to GnuPG system.
+ * estream.c, estream.h, estream-printf.c, estream-printf.h: Remove.
+
+2010-06-07 Werner Koch <wk@g10code.com>
+
+ * OAUTHORS, ONEWS, ChangeLog.1: New.
+
+ * ChangeLog, Makefile.am, b64dec.c, b64enc.c, cdb.h, cdblib.c
+ * certcache.c, certcache.h, crlcache.c, crlcache.h, crlfetch.c
+ * crlfetch.h, dirmngr-client.c, dirmngr.c, dirmngr.h
+ * dirmngr_ldap.c, error.h, estream-printf.c, estream-printf.h
+ * estream.c, estream.h, exechelp.c, exechelp.h, get-path.c, http.c
+ * http.h, i18n.h, ldap-url.c, ldap-url.h, ldap.c, ldapserver.c
+ * ldapserver.h, misc.c, misc.h, ocsp.c, ocsp.h, server.c, util.h
+ * validate.c, validate.h: Imported from the current SVN of the
+ dirmngr package (only src/).
+
+2010-03-13 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (int_and_ptr_u): New.
+ (pid_suffix_callback): Trick out compiler.
+ (start_connection_thread): Ditto.
+ (handle_connections): Ditto.
+
+2010-03-09 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (set_debug): Allow numerical values.
+
+2009-12-15 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c: Add option --ignore-cert-extension.
+ (parse_rereadable_options): Implement.
+ * dirmngr.h (opt): Add IGNORED_CERT_EXTENSIONS.
+ * validate.c (unknown_criticals): Handle ignored extensions.
+
+2009-12-08 Marcus Brinkmann <marcus@g10code.de>
+
+ * dirmngr-client.c (start_dirmngr): Convert posix FDs to assuan fds.
+
+2009-11-25 Marcus Brinkmann <marcus@g10code.de>
+
+ * server.c (start_command_handler): Use assuan_fd_t and
+ assuan_fdopen on fds.
+
+2009-11-05 Marcus Brinkmann <marcus@g10code.de>
+
+ * server.c (start_command_handler): Update use of
+ assuan_init_socket_server.
+ * dirmngr-client.c (start_dirmngr): Update use of
+ assuan_pipe_connect and assuan_socket_connect.
+
+2009-11-04 Werner Koch <wk@g10code.com>
+
+ * server.c (register_commands): Add help arg to
+ assuan_register_command. Change all command comments to strings.
+
+2009-11-02 Marcus Brinkmann <marcus@g10code.de>
+
+ * server.c (reset_notify): Take LINE argument, return gpg_error_t.
+
+2009-10-16 Marcus Brinkmann <marcus@g10code.com>
+
+ * Makefile.am: (dirmngr_LDADD): Link to $(LIBASSUAN_LIBS) instead
+ of $(LIBASSUAN_PTH_LIBS).
+ * dirmngr.c: Invoke ASSUAN_SYSTEM_PTH_IMPL.
+ (main): Call assuan_set_system_hooks and assuan_sock_init.
+
+2009-09-22 Marcus Brinkmann <marcus@g10code.de>
+
+ * dirmngr.c (main): Update to new Assuan interface.
+ * server.c (option_handler, cmd_ldapserver, cmd_isvalid)
+ (cmd_checkcrl, cmd_checkocsp, cmd_lookup, cmd_loadcrl)
+ (cmd_listcrls, cmd_cachecert, cmd_validate): Return gpg_error_t
+ instead int.
+ (register_commands): Likewise for member HANDLER.
+ (start_command_handler): Allocate context with assuan_new before
+ starting server. Release on error.
+ * dirmngr-client.c (main): Update to new Assuan interface.
+ (start_dirmngr): Allocate context with assuan_new before
+ connecting to server. Release on error.
+
+2009-08-12 Werner Koch <wk@g10code.com>
+
+ * dirmngr-client.c (squid_loop_body): Flush stdout. Suggested by
+ Philip Shin.
+
+2009-08-07 Werner Koch <wk@g10code.com>
+
+ * crlfetch.c (my_es_read): Add explicit check for EOF.
+
+ * http.c (struct http_context_s): Turn IN_DATA and IS_HTTP_0_9 to
+ bit fields.
+ (struct cookie_s): Add CONTENT_LENGTH_VALID and CONTENT_LENGTH.
+ (parse_response): Parse the Content-Length header.
+ (cookie_read): Handle content length.
+ (http_open): Make NEED_HEADER the semi-default.
+
+ * http.h (HTTP_FLAG_IGNORE_CL): New.
+
+2009-08-04 Werner Koch <wk@g10code.com>
+
+ * ldap.c (ldap_wrapper_thread): Factor some code out to ...
+ (read_log_data): ... new. Close the log fd on error.
+ (ldap_wrapper_thread): Delay cleanup until the log fd is closed.
+ (SAFE_PTH_CLOSE): New. Use it instead of pth_close.
+
+2009-07-31 Werner Koch <wk@g10code.com>
+
+ * server.c (cmd_loadcrl): Add option --url.
+ * dirmngr-client.c (do_loadcrl): Make use of --url.
+
+ * crlfetch.c (crl_fetch): Remove HTTP_FLAG_NO_SHUTDOWN. Add
+ flag HTTP_FLAG_LOG_RESP with active DBG_LOOKUP.
+
+ * http.c: Require estream. Remove P_ES macro.
+ (write_server): Remove.
+ (my_read_line): Remove. Replace all callers by es_read_line.
+ (send_request): Use es_asprintf. Always store the cookie.
+ (http_wait_response): Remove the need to dup the socket. USe new
+ shutdown flag.
+ * http.h (HTTP_FLAG_NO_SHUTDOWN): Rename to HTTP_FLAG_SHUTDOWN.
+
+ * estream.c, estream.h, estream-printf.c, estream-printf.h: Update
+ from current libestream. This is provide es_asprintf.
+
+2009-07-20 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (pid_suffix_callback): New.
+ (main): Use log_set_pid_suffix_cb.
+ (start_connection_thread): Put the fd into the tls.
+
+ * ldap.c (ldap_wrapper_thread): Print ldap worker stati.
+ (ldap_wrapper_release_context): Print a debug info.
+ (end_cert_fetch_ldap): Release the reader. Might fix bug#999.
+
+2009-06-17 Werner Koch <wk@g10code.com>
+
+ * util.h: Remove unused dotlock.h.
+
+2009-05-26 Werner Koch <wk@g10code.com>
+
+ * ldap.c (ldap_wrapper): Show reader object in diagnostics.
+ * crlcache.c (crl_cache_reload_crl): Ditto. Change debug messages
+ to regular diagnostics.
+ * dirmngr_ldap.c (print_ldap_entries): Add extra diagnostics.
+
+2009-04-03 Werner Koch <wk@g10code.com>
+
+ * dirmngr.h (struct server_local_s): Move back to ...
+ * server.c (struct server_local_s): ... here.
+ (get_ldapservers_from_ctrl): New.
+ * ldapserver.h (ldapserver_iter_begin): Use it.
+
+2008-10-29 Marcus Brinkmann <marcus@g10code.de>
+
+ * estream.c (es_getline): Add explicit cast to silence gcc -W
+ warning.
+ * crlcache.c (finish_sig_check): Likewise.
+
+ * dirmngr.c (opts): Add missing initializer to silence gcc
+ -W warning.
+ * server.c (register_commands): Likewise.
+ * dirmngr-client.c (opts): Likewise.
+ * dirmngr_ldap.c (opts): Likewise.
+
+ * dirmngr-client.c (status_cb, inq_cert, data_cb): Change return
+ type to gpg_error_t to silence gcc warning.
+
+2008-10-21 Werner Koch <wk@g10code.com>
+
+ * certcache.c (load_certs_from_dir): Accept ".der" files.
+
+ * server.c (get_istrusted_from_client): New.
+ * validate.c (validate_cert_chain): Add new optional arg
+ R_TRUST_ANCHOR. Adjust all callers
+ * crlcache.c (crl_cache_entry_s): Add fields USER_TRUST_REQ
+ and CHECK_TRUST_ANCHOR.
+ (release_one_cache_entry): Release CHECK_TRUST_ANCHOR.
+ (list_one_crl_entry): Print info about the new fields.
+ (open_dir, write_dir_line_crl): Support the new U-flag.
+ (crl_parse_insert): Add arg R_TRUST_ANCHOR and set it accordingly.
+ (crl_cache_insert): Store trust anchor in entry object.
+ (cache_isvalid): Ask client for trust is needed.
+
+ * crlcache.c (open_dir): Replace xcalloc by xtrycalloc.
+ (next_line_from_file): Ditt. Add arg to return the gpg error.
+ Change all callers.
+ (update_dir): Replace sprintf and malloc by estream_asprintf.
+ (crl_cache_insert): Ditto.
+ (crl_cache_isvalid): Replace xmalloc by xtrymalloc.
+ (get_auth_key_id): Ditto.
+ (crl_cache_insert): Ditto.
+
+ * crlcache.c (start_sig_check): Remove HAVE_GCRY_MD_DEBUG test.
+ * validate.c (check_cert_sig): Ditto. Remove workaround for bug
+ in libgcrypt 1.2.
+
+ * estream.c, estream.h, estream-printf.c, estream-printf.h: Update
+ from current libestream (svn rev 61).
+
+2008-09-30 Marcus Brinkmann <marcus@g10code.com>
+
+ * get-path.c (get_dirmngr_ldap_path): Revert last change.
+ Instead, use dirmngr_libexecdir().
+ (find_program_at_standard_place): Don't define for now.
+
+2008-09-30 Marcus Brinkmann <marcus@g10code.com>
+
+ * get-path.c (dirmngr_cachedir): Make COMP a pointer to const to
+ silence gcc warning.
+ (get_dirmngr_ldap_path): Look for dirmngr_ldap in the installation
+ directory.
+
+2008-08-06 Marcus Brinkmann <marcus@g10code.com>
+
+ * dirmngr.c (main): Mark the ldapserverlist-file option as
+ read-only.
+
+2008-07-31 Werner Koch <wk@g10code.com>
+
+ * crlcache.c (start_sig_check) [!HAVE_GCRY_MD_DEBUG]: Use
+ gcry_md_start_debug
+
+2008-06-16 Werner Koch <wk@g10code.com>
+
+ * get-path.c (w32_commondir): New.
+ (dirmngr_sysconfdir): Use it here.
+ (dirmngr_datadir): Ditto.
+
+2008-06-12 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (dirmngr_SOURCES): Add ldapserver.h and ldapserver.c.
+ * ldapserver.h, ldapserver.c: New files.
+ * ldap.c: Include "ldapserver.h".
+ (url_fetch_ldap): Use iterator to get session servers as well.
+ (attr_fetch_ldap, start_default_fetch_ldap): Likewise.
+ * dirmngr.c: Include "ldapserver.h".
+ (free_ldapservers_list): Removed. Change callers to
+ ldapserver_list_free.
+ (parse_ldapserver_file): Use ldapserver_parse_one.
+ * server.c: Include "ldapserver.h".
+ (cmd_ldapserver): New command.
+ (register_commands): Add new command LDAPSERVER.
+ (reset_notify): New function.
+ (start_command_handler): Register reset notify handler.
+ Deallocate session server list.
+ (lookup_cert_by_pattern): Use iterator to get session servers as well.
+ (struct server_local_s): Move to ...
+ * dirmngr.h (struct server_local_s): ... here. Add new member
+ ldapservers.
+
+2008-06-10 Werner Koch <wk@g10code.com>
+
+ Support PEM encoded CRLs. Fixes bug#927.
+
+ * crlfetch.c (struct reader_cb_context_s): New.
+ (struct file_reader_map_s): Replace FP by new context.
+ (register_file_reader, get_file_reader): Adjust accordingly.
+ (my_es_read): Detect Base64 encoded CRL and decode if needed.
+ (crl_fetch): Pass new context to the callback.
+ (crl_close_reader): Cleanup the new context.
+ * b64dec.c: New. Taken from GnuPG.
+ * util.h (struct b64state): Add new fields STOP_SEEN and
+ INVALID_ENCODING.
+
+2008-05-26 Marcus Brinkmann <marcus@g10code.com>
+
+ * dirmngr.c (main) [HAVE_W32_SYSTEM]: Switch to system
+ configuration on gpgconf related commands, and make all options
+ unchangeable.
+
+2008-03-25 Marcus Brinkmann <marcus@g10code.de>
+
+ * dirmngr_ldap.c (print_ldap_entries): Add code alternative for
+ W32 console stdout (unused at this point).
+
+2008-03-21 Marcus Brinkmann <marcus@g10code.de>
+
+ * estream.c (ESTREAM_MUTEX_DESTROY): New macro.
+ (es_create, es_destroy): Use it.
+
+2008-02-21 Werner Koch <wk@g10code.com>
+
+ * validate.c (check_cert_sig) [HAVE_GCRY_MD_DEBUG]: Use new debug
+ function if available.
+
+ * crlcache.c (abort_sig_check): Mark unused arg.
+
+ * exechelp.c (dirmngr_release_process) [!W32]: Mark unsed arg.
+
+ * validate.c (is_root_cert): New. Taken from GnuPG.
+ (validate_cert_chain): Use it in place of the simple DN compare.
+
+2008-02-15 Marcus Brinkmann <marcus@g10code.de>
+
+ * dirmngr.c (main): Reinitialize assuan log stream if necessary.
+
+ * crlcache.c (update_dir) [HAVE_W32_SYSTEM]: Remove destination
+ file before rename.
+ (crl_cache_insert) [HAVE_W32_SYSTEM]: Remove destination file
+ before rename.
+
+2008-02-14 Marcus Brinkmann <marcus@g10code.de>
+
+ * validate.c (check_cert_policy): Use ksba_free instead of xfree.
+ (validate_cert_chain): Likewise. Free SUBJECT on error.
+ (cert_usage_p): Likewise.
+
+ * crlcache.c (finish_sig_check): Undo last change.
+ (finish_sig_check): Close md.
+ (abort_sig_check): New function.
+ (crl_parse_insert): Use abort_sig_check to clean up.
+
+ * crlcache.c (crl_cache_insert): Clean up CDB on error.
+
+2008-02-13 Marcus Brinkmann <marcus@g10code.de>
+
+ * crlcache.c (finish_sig_check): Call gcry_md_stop_debug.
+ * exechelp.h (dirmngr_release_process): New prototype.
+ * exechelp.c (dirmngr_release_process): New function.
+ * ldap.c (ldap_wrapper_thread): Release pid.
+ (destroy_wrapper): Likewise.
+
+ * dirmngr.c (launch_reaper_thread): Destroy tattr.
+ (handle_connections): Likewise.
+
+2008-02-12 Marcus Brinkmann <marcus@g10code.de>
+
+ * ldap.c (pth_close) [! HAVE_W32_SYSTEM]: New macro.
+ (struct wrapper_context_s): New member log_ev.
+ (destroy_wrapper): Check FDs for != -1 rather than != 0. Use
+ pth_close instead of close. Free CTX->log_ev.
+ (ldap_wrapper_thread): Rewritten to use pth_wait instead of
+ select. Also use pth_read instead of read and pth_close instead
+ of close.
+ (ldap_wrapper): Initialize CTX->log_ev.
+ (reader_callback): Use pth_close instead of close.
+ * exechelp.c (create_inheritable_pipe) [HAVE_W32_SYSTEM]: Removed.
+ (dirmngr_spawn_process) [HAVE_W32_SYSTEM]: Use pth_pipe instead.
+ * dirmngr_ldap.c [HAVE_W32_SYSTEM]: Include <fcntl.h>.
+ (main) [HAVE_W32_SYSTEM]: Set mode of stdout to binary.
+
+2008-02-01 Werner Koch <wk@g10code.com>
+
+ * ldap.c: Remove all ldap headers as they are unused.
+
+ * dirmngr_ldap.c (LDAP_DEPRECATED): New, to have OpenLDAP use the
+ old standard API.
+
+2008-01-10 Werner Koch <wk@g10code.com>
+
+ * dirmngr-client.c: New option --local.
+ (do_lookup): Use it.
+
+ * server.c (lookup_cert_by_pattern): Implement local lookup.
+ (return_one_cert): New.
+ * certcache.c (hexsn_to_sexp): New.
+ (classify_pattern, get_certs_bypattern): New.
+
+ * misc.c (unhexify): Allow passing NULL for RESULT.
+ (cert_log_subject): Do not call ksba_free on an unused variable.
+
+2008-01-02 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (dirmngr_LDADD, dirmngr_ldap_LDADD)
+ (dirmngr_client_LDADD): Add $(LIBICONV). Reported by Michael
+ Nottebrock.
+
+2007-12-11 Werner Koch <wk@g10code.com>
+
+ * server.c (option_handler): New option audit-events.
+ * dirmngr.h (struct server_control_s): Add member AUDIT_EVENTS.
+
+2007-11-26 Marcus Brinkmann <marcus@g10code.de>
+
+ * get-path.c (dirmngr_cachedir): Create intermediate directories.
+ (default_socket_name): Use CSIDL_WINDOWS.
+
+2007-11-21 Werner Koch <wk@g10code.com>
+
+ * server.c (lookup_cert_by_pattern): Add args SINGLE and CACHE_ONLY.
+ (cmd_lookup): Add options --single and --cache-only.
+
+2007-11-16 Werner Koch <wk@g10code.com>
+
+ * certcache.c (load_certs_from_dir): Also log the subject DN.
+ * misc.c (cert_log_subject): New.
+
+2007-11-14 Werner Koch <wk@g10code.com>
+
+ * dirmngr-client.c: Replace --lookup-url by --url.
+ (main): Remove extra code for --lookup-url.
+ (do_lookup): Remove LOOKUP_URL arg and use the
+ global option OPT.URL.
+
+ * server.c (has_leading_option): New.
+ (cmd_lookup): Use it.
+
+ * crlfetch.c (fetch_cert_by_url): Use GPG_ERR_INV_CERT_OBJ.
+ (fetch_cert_by_url): Use gpg_error_from_syserror.
+
+2007-11-14 Moritz <moritz@gnu.org> (wk)
+
+ * dirmngr-client.c: New command: --lookup-url <URL>.
+ (do_lookup): New parameter: lookup_url. If TRUE, include "--url"
+ switch in LOOKUP transaction.
+ (enum): New entry: oLookupUrl.
+ (opts): Likewise.
+ (main): Handle oLookupUrl. New variable: cmd_lookup_url, set
+ during option parsing, pass to do_lookup() and substitute some
+ occurences of "cmd_lookup" with "cmd_lookup OR cmd_lookup_url".
+ * crlfetch.c (fetch_cert_by_url): New function, uses
+ url_fetch_ldap() to create a reader object and libksba functions
+ to read a single cert from that reader.
+ * server.c (lookup_cert_by_url, lookup_cert_by_pattern): New
+ functions.
+ (cmd_lookup): Moved almost complete code ...
+ (lookup_cert_by_pattern): ... here.
+ (cmd_lookup): Support new optional argument: --url. Depending on
+ the presence of that switch, call lookup_cert_by_url() or
+ lookup_cert_by_pattern().
+ (lookup_cert_by_url): Heavily stripped down version of
+ lookup_cert_by_pattern(), using fetch_cert_by_url.
+
+2007-10-24 Marcus Brinkmann <marcus@g10code.de>
+
+ * exechelp.c (dirmngr_spawn_process): Fix child handles.
+
+2007-10-05 Marcus Brinkmann <marcus@g10code.de>
+
+ * dirmngr.h: Include assuan.h.
+ (start_command_handler): Change type of FD to assuan_fd_t.
+ * dirmngr.c: Do not include w32-afunix.h.
+ (socket_nonce): New global variable.
+ (create_server_socket): Use assuan socket wrappers. Remove W32
+ specific stuff. Save the server nonce.
+ (check_nonce): New function.
+ (start_connection_thread): Call it.
+ (handle_connections): Change args to assuan_fd_t.
+ * server.c (start_command_handler): Change type of FD to assuan_fd_t.
+
+2007-09-12 Marcus Brinkmann <marcus@g10code.de>
+
+ * dirmngr.c (main): Percent escape pathnames in --gpgconf-list output.
+
+2007-08-27 Moritz Schulte <moritz@g10code.com>
+
+ * src/Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SOCKETDIR based on
+ $(localstatedir).
+ * src/get-path.c (default_socket_name): Use DIRMNGR_SOCKETDIR
+ instead of hard-coded "/var/run/dirmngr".
+
+2007-08-16 Werner Koch <wk@g10code.com>
+
+ * get-path.c (get_dirmngr_ldap_path): Make PATHNAME const.
+
+ * dirmngr.c (my_ksba_hash_buffer): Mark unused arg.
+ (dirmngr_init_default_ctrl): Ditto.
+ (my_gcry_logger): Ditto.
+ * dirmngr-client.c (status_cb): Ditto.
+ * dirmngr_ldap.c (catch_alarm): Ditto.
+ * estream-printf.c (pr_bytes_so_far): Ditto.
+ * estream.c (es_func_fd_create): Ditto.
+ (es_func_fp_create): Ditto.
+ (es_write_hexstring): Ditto.
+ * server.c (cmd_listcrls): Ditto.
+ (cmd_cachecert): Ditto.
+ * crlcache.c (cache_isvalid): Ditto.
+ * ocsp.c (do_ocsp_request): Ditto.
+ * ldap.c (ldap_wrapper_thread): Ditto.
+ * http.c (http_register_tls_callback): Ditto.
+ (connect_server): Ditto.
+ (write_server) [!HTTP_USE_ESTREAM]: Don't build.
+
+2007-08-14 Werner Koch <wk@g10code.com>
+
+ * get-path.c (dirmngr_cachedir) [W32]: Use CSIDL_LOCAL_APPDATA.
+
+2007-08-13 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (handle_connections): Use a timeout in the accept
+ function. Block signals while creating a new thread.
+ (shutdown_pending): Needs to be volatile as also accessed bt the
+ service function.
+ (w32_service_control): Do not use the regular log fucntions here.
+ (handle_tick): New.
+ (main): With system_service in effect use aDaemon as default
+ command.
+ (main) [W32]: Only temporary redefine main for the sake of Emacs's
+ "C-x 4 a".
+
+ * dirmngr-client.c (main) [W32]: Initialize sockets.
+ (start_dirmngr): Use default_socket_name instead of a constant.
+ * Makefile.am (dirmngr_client_SOURCES): Add get-path.c
+
+2007-08-09 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (parse_ocsp_signer): New.
+ (parse_rereadable_options): Set opt.ocsp_signer to this.
+ * dirmngr.h (fingerprint_list_t): New.
+ * ocsp.c (ocsp_isvalid, check_signature, validate_responder_cert):
+ Allow for several default ocscp signers.
+ (ocsp_isvalid): Return GPG_ERR_NO_DATA for an unknwon status.
+
+ * dirmngr-client.c: New option --force-default-responder.
+
+ * server.c (has_option, skip_options): New.
+ (cmd_checkocsp): Add option --force-default-responder.
+ (cmd_isvalid): Ditto. Also add option --only-ocsp.
+
+ * ocsp.c (ocsp_isvalid): New arg FORCE_DEFAULT_RESPONDER.
+
+ * dirmngr.c: New option --ocsp-max-period.
+ * ocsp.c (ocsp_isvalid): Implement it and take care that a missing
+ next_update is to be ignored.
+
+ * crlfetch.c (my_es_read): New. Use it instead of es_read.
+
+ * estream.h, estream.c, estream-printf.c: Updated from current
+ libestream SVN.
+
+2007-08-08 Werner Koch <wk@g10code.com>
+
+ * crlcache.c (crl_parse_insert): Hack to allow for a missing
+ nextUpdate.
+
+ * dirmngr_ldap.c (print_ldap_entries): Strip the extension from
+ the want_attr.
+
+ * exechelp.c (dirmngr_wait_process): Reworked for clear error
+ semantics.
+ * ldap.c (ldap_wrapper_thread): Adjust for new
+ dirmngr_wait_process semantics.
+
+2007-08-07 Werner Koch <wk@g10code.com>
+
+ * get-path.c (default_socket_name) [!W32]: Fixed syntax error.
+
+ * ldap.c (X509CACERT, make_url, fetch_next_cert_ldap): Support
+ x509caCert as used by the Bundesnetzagentur.
+ (ldap_wrapper): Do not pass the prgtram name as the first
+ argument. dirmngr_spawn_process takes care of that.
+
+2007-08-04 Marcus Brinkmann <marcus@g10code.de>
+
+ * dirmngr.h (opt): Add member system_service.
+ * dirmngr.c (opts) [HAVE_W32_SYSTEM]: New entry for option
+ --service.
+ (DEFAULT_SOCKET_NAME): Removed.
+ (service_handle, service_status,
+ w32_service_control) [HAVE_W32_SYSTEM]: New symbols.
+ (main) [HAVE_W32_SYSTEM]: New entry point for --service. Rename
+ old function to ...
+ (real_main) [HAVE_W32_SYSTEM]: ... this. Use default_socket_name
+ instead of DEFAULT_SOCKET_NAME, and similar for other paths.
+ Allow colons in Windows socket path name, and implement --service
+ option.
+ * util.h (dirmngr_sysconfdir, dirmngr_libexecdir, dirmngr_datadir,
+ dirmngr_cachedir, default_socket_name): New prototypes.
+ * get-path.c (dirmngr_sysconfdir, dirmngr_libexecdir)
+ (dirmngr_datadir, dirmngr_cachedir, default_socket_name): New
+ functions.
+ (DIRSEP_C, DIRSEP_S): New macros.
+
+2007-08-03 Marcus Brinkmann <marcus@g10code.de>
+
+ * get-path.c: Really add the file this time.
+
+2007-07-31 Marcus Brinkmann <marcus@g10code.de>
+
+ * crlfetch.c: Include "estream.h".
+ (crl_fetch): Use es_read callback instead a file handle.
+ (crl_close_reader): Use es_fclose instead of fclose.
+ (struct file_reader_map_s): Change type of FP to estream_t.
+ (register_file_reader, crl_fetch, crl_close_reader): Likewise.
+ * ocsp.c: Include "estream.h".
+ (read_response): Change type of FP to estream_t.
+ (read_response, do_ocsp_request): Use es_* variants of I/O
+ functions.
+
+ * http.c: Include <pth.h>.
+ (http_wait_response) [HAVE_W32_SYSTEM]: Use DuplicateHandle.
+ (cookie_read): Use pth_read instead read.
+ (cookie_write): Use pth_write instead write.
+
+2007-07-30 Marcus Brinkmann <marcus@g10code.de>
+
+ * ldap-url.c (ldap_str2charray): Fix buglet in ldap_utf8_strchr
+ invocation.
+
+2007-07-27 Marcus Brinkmann <marcus@g10code.de>
+
+ * estream.h, estream.c: Update from recent GnuPG.
+
+ * get-path.c: New file.
+ * Makefile.am (dirmngr_SOURCES): Add get-path.c.
+ * util.h (default_homedir, get_dirmngr_ldap_path): New prototypes.
+ * dirmngr.c (main): Use default_homedir().
+ * ldap-url.h: Remove japanese white space (sorry!).
+
+2007-07-26 Marcus Brinkmann <marcus@g10code.de>
+
+ * ldap.c (pth_yield): Remove macro.
+
+ * ldap.c (pth_yield) [HAVE_W32_SYSTEM]: Define to Sleep(0).
+
+ * dirmngr_ldap.c [HAVE_W32_SYSTEM]: Do not include <ldap.h>, but
+ <winsock2.h>, <winldap.h> and "ldap-url.h".
+ * ldap.c [HAVE_W32_SYSTEM]: Do not include <ldap.h>, but
+ <winsock2.h> and <winldap.h>.
+
+ * ldap-url.c: Do not include <ldap.h>, but <winsock2.h>,
+ <winldap.h> and "ldap-url.h".
+ (LDAP_P): New macro.
+ * ldap-url.h: New file.
+ * Makefile.am (ldap_url): Add ldap-url.h.
+
+ * Makefile.am (ldap_url): New variable.
+ (dirmngr_ldap_SOURCES): Add $(ldap_url).
+ (dirmngr_ldap_LDADD): Add $(LIBOBJS).
+ * ldap-url.c: New file, excerpted from OpenLDAP.
+ * dirmngr.c (main) [HAVE_W32_SYSTEM]: Avoid the daemonization.
+ * dirmngr_ldap.c: Include "util.h".
+ (main) [HAVE_W32_SYSTEM]: Don't set up alarm.
+ (set_timeout) [HAVE_W32_SYSTEM]: Likewise.
+ * ldap.c [HAVE_W32_SYSTEM]: Add macros for setenv and pth_yield.
+ * no-libgcrypt.h (NO_LIBGCRYPT): Define.
+ * util.h [NO_LIBGCRYPT]: Don't include <gcrypt.h>.
+
+2007-07-23 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (dirmngr_SOURCES): Add exechelp.h and exechelp.c.
+ * exechelp.h, exechelp.c: New files.
+ * ldap.c: Don't include <sys/wait.h> but "exechelp.h".
+ (destroy_wrapper, ldap_wrapper_thread,
+ ldap_wrapper_connection_cleanup): Use dirmngr_kill_process instead
+ of kill.
+ (ldap_wrapper_thread): Use dirmngr_wait_process instead of
+ waitpid.
+ (ldap_wrapper): Use dirmngr_spawn_process.
+
+2007-07-20 Marcus Brinkmann <marcus@g10code.de>
+
+ * certcache.c (cert_cache_lock): Do not initialize statically.
+ (init_cache_lock): New function.
+ (cert_cache_init): Call init_cache_lock.
+
+ * estream.h, estream.c, estream-printf.h, estream-printf.c: New
+ files.
+ * Makefile.am (dirmngr_SOURCES): Add estream.c, estream.h,
+ estream-printf.c, estream-printf.h.
+
+ * http.c: Update to latest version from GnuPG.
+
+ * Makefile.am (cdb_sources)
+ * cdblib.c: Port to windows (backport from tinycdb 0.76).
+
+ * crlcache.c [HAVE_W32_SYSTEM]: Don't include sys/utsname.h.
+ [MKDIR_TAKES_ONE_ARG]: Define mkdir as a macro for such systems.
+ (update_dir, crl_cache_insert) [HAVE_W32_SYSTEM]: Don't get uname.
+ * server.c (start_command_handler) [HAVE_W32_SYSTEM]: Don't log
+ peer credentials.
+
+ * dirmngr.c [HAVE_W32_SYSTEM]: Do not include sys/socket.h or
+ sys/un.h, but ../jnlib/w32-afunix.h.
+ (sleep) [HAVE_W32_SYSTEM]: New macro.
+ (main) [HAVE_W32_SYSTEM]: Don't mess with SIGPIPE. Use W32 socket
+ API.
+ (handle_signal) [HAVE_W32_SYSTEM]: Deactivate the bunch of the
+ code.
+ (handle_connections) [HAVE_W32_SYSTEM]: don't handle signals.
+
+2006-11-29 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (my_strusage): Use macro for the bug report address
+ and the copyright line.
+ * dirmngr-client.c (my_strusage): Ditto.
+ * dirmngr_ldap.c (my_strusage): Ditto.
+
+ * Makefile.am: Do not link against LIBICONV.
+
+2006-11-19 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c: Include i18n.h.
+
+2006-11-17 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (dirmngr_LDADD): Use LIBASSUAN_PTH_LIBS.
+
+2006-11-16 Werner Koch <wk@g10code.com>
+
+ * server.c (start_command_handler): Replaced
+ assuan_init_connected_socket_server by assuan_init_socket_server_ext.
+
+ * crlcache.c (update_dir): Put a diagnostic into DIR.txt.
+ (open_dir): Detect invalid and duplicate entries.
+ (update_dir): Fixed search for second field.
+
+2006-10-23 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (main): New command --gpgconf-test.
+
+2006-09-14 Werner Koch <wk@g10code.com>
+
+ * server.c (start_command_handler): In vebose mode print
+ information about the peer. This may later be used to restrict
+ certain commands.
+
+2006-09-12 Werner Koch <wk@g10code.com>
+
+ * server.c (start_command_handler): Print a more informative hello
+ line.
+ * dirmngr.c: Moved config_filename into the opt struct.
+
+2006-09-11 Werner Koch <wk@g10code.com>
+
+ Changed everything to use Assuan with gpg-error codes.
+ * maperror.c: Removed.
+ * server.c (map_to_assuan_status): Removed.
+ * dirmngr.c (main): Set assuan error source.
+ * dirmngr-client.c (main): Ditto.
+
+2006-09-04 Werner Koch <wk@g10code.com>
+
+ * crlfetch.c (crl_fetch): Implement HTTP redirection.
+ * ocsp.c (do_ocsp_request): Ditto.
+
+ New HTTP code version taken from gnupg svn release 4236.
+ * http.c (http_get_header): New.
+ (capitalize_header_name, store_header): New.
+ (parse_response): Store headers away.
+ (send_request): Return GPG_ERR_NOT_FOUND if connect_server failed.
+ * http.h: New flag HTTP_FLAG_NEED_HEADER.
+
+2006-09-01 Werner Koch <wk@g10code.com>
+
+ * crlfetch.c (register_file_reader, get_file_reader): New.
+ (crl_fetch): Register the file pointer for HTTP.
+ (crl_close_reader): And release it.
+
+ * http.c, http.h: Updated from GnuPG SVN trunk. Changed all users
+ to adopt the new API.
+ * dirmngr.h: Moved inclusion of jnlib header to ...
+ * util.h: .. here. This is required becuase http.c includes only
+ a file util.h but makes use of log_foo. Include gcrypt.h so that
+ gcry_malloc et al are declared.
+
+2006-08-31 Werner Koch <wk@g10code.com>
+
+ * ocsp.c (check_signature): Make use of the responder id.
+
+2006-08-30 Werner Koch <wk@g10code.com>
+
+ * validate.c (check_cert_sig): Workaround for rimemd160.
+ (allowed_ca): Always allow trusted CAs.
+
+ * dirmngr.h (cert_ref_t): New.
+ (struct server_control_s): Add field OCSP_CERTS.
+ * server.c (start_command_handler): Release new field
+ * ocsp.c (release_ctrl_ocsp_certs): New.
+ (check_signature): Store certificates in OCSP_CERTS.
+
+ * certcache.c (find_issuing_cert): Reset error if cert was found
+ by subject.
+ (put_cert): Add new arg FPR_BUFFER. Changed callers.
+ (cache_cert_silent): New.
+
+ * dirmngr.c (parse_rereadable_options): New options
+ --ocsp-max-clock-skew and --ocsp-current-period.
+ * ocsp.c (ocsp_isvalid): Use them here.
+
+ * ocsp.c (validate_responder_cert): New optional arg signer_cert.
+ (check_signature_core): Ditto.
+ (check_signature): Use the default signer certificate here.
+
+2006-06-27 Werner Koch <wk@g10code.com>
+
+ * dirmngr-client.c (inq_cert): Take care of SENDCERT_SKI.
+
+2006-06-26 Werner Koch <wk@g10code.com>
+
+ * crlcache.c (lock_db_file): Count open files when needed.
+ (find_entry): Fixed deleted case.
+
+2006-06-23 Werner Koch <wk@g10code.com>
+
+ * misc.c (cert_log_name): New.
+
+ * certcache.c (load_certs_from_dir): Also print certificate name.
+ (find_cert_bysn): Release ISSDN.
+
+ * validate.h: New VALIDATE_MODE_CERT.
+ * server.c (cmd_validate): Use it here so that no policy checks
+ are done. Try to validated a cached copy of the target.
+
+ * validate.c (validate_cert_chain): Implement a validation cache.
+ (check_revocations): Print more diagnostics. Actually use the
+ loop variable and not the head of the list.
+ (validate_cert_chain): Do not check revocations of CRL issuer
+ certificates in plain CRL check mode.
+ * ocsp.c (ocsp_isvalid): Make sure it is reset for a status of
+ revoked.
+
+2006-06-22 Werner Koch <wk@g10code.com>
+
+ * validate.c (cert_use_crl_p): New.
+ (cert_usage_p): Add a mode 6 for CRL signing.
+ (validate_cert_chain): Check that the certificate may be used for
+ CRL signing. Print a note when not running as system daemon.
+ (validate_cert_chain): Reduce the maximum depth from 50 to 10.
+
+ * certcache.c (find_cert_bysn): Minor restructuring
+ (find_cert_bysubject): Ditto. Use get_cert_local when called
+ without KEYID.
+ * crlcache.c (get_crlissuer_cert_bysn): Removed.
+ (get_crlissuer_cert): Removed.
+ (crl_parse_insert): Use find_cert_bysubject and find_cert_bysn
+ instead of the removed functions.
+
+2006-06-19 Werner Koch <wk@g10code.com>
+
+ * certcache.c (compare_serialno): Silly me. Using 0 as true is
+ that hard; tsss. Fixed call cases except for the only working one
+ which are both numbers of the same length.
+
+2006-05-15 Werner Koch <wk@g10code.com>
+
+ * crlfetch.c (crl_fetch): Use no-shutdown flag for HTTP. This
+ seems to be required for "IBM_HTTP_Server/2.0.47.1 Apache/2.0.47
+ (Unix)".
+
+ * http.c (parse_tuple): Set flag to to indicate no value.
+ (build_rel_path): Take care of it.
+
+ * crlcache.c (crl_cache_reload_crl): Also iterate over all names
+ within a DP.
+
+2005-09-28 Marcus Brinkmann <marcus@g10code.de>
+
+ * Makefile.am (dirmngr_LDADD): Add @LIBINTL@ and @LIBICONV@.
+ (dirmngr_ldap_LDADD): Likewise.
+ (dirmngr_client_LDADD): Likewise.
+
+2005-09-12 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c: Fixed description to match the one in gpgconf.
+
+2005-06-15 Werner Koch <wk@g10code.com>
+
+ * server.c (cmd_lookup): Take care of NO_DATA which might get
+ returned also by start_cert_fetch().
+
+2005-04-20 Werner Koch <wk@g10code.com>
+
+ * ldap.c (ldap_wrapper_wait_connections): Set a shutdown flag.
+ (ldap_wrapper_thread): Handle shutdown in a special way.
+
+2005-04-19 Werner Koch <wk@g10code.com>
+
+ * server.c (get_cert_local, get_issuing_cert_local)
+ (get_cert_local_ski): Bail out if called without a local context.
+
+2005-04-18 Werner Koch <wk@g10code.com>
+
+ * certcache.c (find_issuing_cert): Fixed last resort method which
+ should be finding by subject and not by issuer. Try to locate it
+ also using the keyIdentifier method. Improve error reporting.
+ (cmp_simple_canon_sexp): New.
+ (find_cert_bysubject): New.
+ (find_cert_bysn): Ask back to the caller before trying an extarnl
+ lookup.
+ * server.c (get_cert_local_ski): New.
+ * crlcache.c (crl_parse_insert): Also try to locate issuer
+ certificate using the keyIdentifier. Improved error reporting.
+
+2005-04-14 Werner Koch <wk@g10code.com>
+
+ * ldap.c (start_cert_fetch_ldap): Really return ERR.
+
+2005-03-17 Werner Koch <wk@g10code.com>
+
+ * http.c (parse_response): Changed MAXLEN and LEN to size_t to
+ match the requirement of read_line.
+ * http.h (http_context_s): Ditto for BUFFER_SIZE.
+
+2005-03-15 Werner Koch <wk@g10code.com>
+
+ * ldap.c: Included time.h. Reported by Bernhard Herzog.
+
+2005-03-09 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c: Add a note to the help listing check the man page for
+ other options.
+
+2005-02-01 Werner Koch <wk@g10code.com>
+
+ * crlcache.c (crl_parse_insert): Renamed a few variables and
+ changed diagnostic strings for clarity.
+ (get_issuer_cert): Renamed to get_crlissuer_cert. Try to locate
+ the certificate from the cache using the subject name. Use new
+ fetch function.
+ (get_crlissuer_cert_bysn): New.
+ (crl_parse_insert): Use it here.
+ * crlfetch.c (ca_cert_fetch): Changed interface.
+ (fetch_next_ksba_cert): New.
+ * ldap.c (run_ldap_wrapper): Add arg MULTI_MODE. Changed all
+ callers.
+ (start_default_fetch_ldap): New
+ * certcache.c (get_cert_bysubject): New.
+ (clean_cache_slot, put_cert): Store the subject DN if available.
+ (MAX_EXTRA_CACHED_CERTS): Increase limit of cachable certificates
+ to 1000.
+ (find_cert_bysn): Loop until a certificate with a matching S/N has
+ been found.
+
+ * dirmngr.c (main): Add honor-http-proxy to the gpgconf list.
+
+2005-01-31 Werner Koch <wk@g10code.com>
+
+ * ldap.c: Started to work on support for userSMIMECertificates.
+
+ * dirmngr.c (main): Make sure to always pass a server control
+ structure to the caching functions. Reported by Neil Dunbar.
+
+2005-01-05 Werner Koch <wk@g10code.com>
+
+ * dirmngr-client.c (read_pem_certificate): Skip trailing percent
+ escaped linefeeds.
+
+2005-01-03 Werner Koch <wk@g10code.com>
+
+ * dirmngr-client.c (read_pem_certificate): New.
+ (read_certificate): Divert to it depending on pem option.
+ (squid_loop_body): New.
+ (main): New options --pem and --squid-mode.
+
+2004-12-17 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (launch_ripper_thread): Renamed to launch_reaper_thread.
+ (shutdown_reaper): New. Use it for --server and --daemon.
+ * ldap.c (ldap_wrapper_wait_connections): New.
+
+2004-12-17 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (dirmngr_ldap_LDADD): Adjusted for new LDAP checks.
+
+2004-12-16 Werner Koch <wk@g10code.com>
+
+ * ldap.c (ldap_wrapper): Peek on the output to detect empty output
+ early.
+
+2004-12-15 Werner Koch <wk@g10code.com>
+
+ * ldap.c (ldap_wrapper): Print a diagnostic after forking for the
+ ldap wrapper.
+ * certcache.h (find_cert_bysn): Add this prototype.
+ * crlcache.c (start_sig_check): Write CRL hash debug file.
+ (finish_sig_check): Dump the signer's certificate.
+ (crl_parse_insert): Try to get the issuing cert by authKeyId.
+ Moved certificate retrieval after item processing.
+
+2004-12-13 Werner Koch <wk@g10code.com>
+
+ * dirmngr_ldap.c (catch_alarm, set_timeout): new.
+ (main): Install alarm handler. Add new option --only-search-timeout.
+ (print_ldap_entries, fetch_ldap): Use set_timeout ();
+ * dirmngr.h: Make LDAPTIMEOUT a simple unsigned int. Change all
+ initializations.
+ * ldap.c (start_cert_fetch_ldap, run_ldap_wrapper): Pass timeout
+ option to the wrapper.
+ (INACTIVITY_TIMEOUT): Depend on LDAPTIMEOUT.
+ (run_ldap_wrapper): Add arg IGNORE_TIMEOUT.
+ (ldap_wrapper_thread): Check for special timeout exit code.
+
+ * dirmngr.c: Workaround a typo in gpgconf for
+ ignore-ocsp-service-url.
+
+2004-12-10 Werner Koch <wk@g10code.com>
+
+ * ldap.c (url_fetch_ldap): Use TMP and not a HOST which is always
+ NULL.
+ * misc.c (host_and_port_from_url): Fixed bad encoding detection.
+
+2004-12-03 Werner Koch <wk@g10code.com>
+
+ * crlcache.c (crl_cache_load): Re-implement it.
+
+ * dirmngr-client.c: New command --load-crl
+ (do_loadcrl): New.
+
+ * dirmngr.c (parse_rereadable_options, main): Make --allow-ocsp,
+ --ocsp-responder, --ocsp-signer and --max-replies re-readable.
+
+ * ocsp.c (check_signature): try to get the cert from the cache
+ first.
+ (ocsp_isvalid): Print the next and this update times on time
+ conflict.
+
+ * certcache.c (load_certs_from_dir): Print the fingerprint for
+ trusted certificates.
+ (get_cert_byhexfpr): New.
+ * misc.c (get_fingerprint_hexstring_colon): New.
+
+2004-12-01 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (dirmngr_LDADD): Don't use LDAP_LIBS.
+
+ * validate.c (validate_cert_chain): Fixed test; as written in the
+ comment we want to do this only in daemon mode. For clarity
+ reworked by using a linked list of certificates and include root
+ and tragte certificate.
+ (check_revocations): Likewise. Introduced a recursion sentinel.
+
+2004-11-30 Werner Koch <wk@g10code.com>
+
+ * crlfetch.c (ca_cert_fetch, crl_fetch_default): Do not use the
+ binary prefix as this will be handled in the driver.
+
+ * dirmngr_ldap.c: New option --log-with-pid.
+ (fetch_ldap): Handle LDAP_NO_SUCH_OBJECT.
+ * ldap.c (run_ldap_wrapper, start_cert_fetch_ldap): Use new log
+ option.
+
+
+2004-11-25 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (dirmngr_ldap_CFLAGS): Added GPG_ERROR_CFLAGS.
+ Noted by Bernhard Herzog.
+
+2004-11-24 Werner Koch <wk@g10code.com>
+
+ * ldap.c (ldap_wrapper): Fixed default name of the ldap wrapper.
+
+ * b64enc.c (b64enc_start, b64enc_finish): Use standard strdup/free
+ to manage memory.
+
+ * dirmngr.c: New options --ignore-http-dp, --ignore-ldap-dp and
+ --ignore-ocsp-service-url.
+ * crlcache.c (crl_cache_reload_crl): Implement them.
+ * ocsp.c (ocsp_isvalid): Ditto.
+
+2004-11-23 Werner Koch <wk@g10code.com>
+
+ * ldap.c (ldap_wrapper_thread, reader_callback, ldap_wrapper):
+ Keep a timestamp and terminate the wrapper after some time of
+ inactivity.
+
+ * dirmngr-client.c (do_lookup): New.
+ (main): New option --lookup.
+ (data_cb): New.
+ * b64enc.c: New. Taken from GnuPG 1.9.
+ * no-libgcrypt.c (gcry_strdup): Added.
+
+ * ocsp.c (ocsp_isvalid): New arg CERT and lookup the issuer
+ certificate using the standard methods.
+
+ * server.c (cmd_lookup): Truncation is now also an indication for
+ error.
+ (cmd_checkocsp): Implemented.
+
+ * dirmngr_ldap.c (fetch_ldap): Write an error marker for a
+ truncated search.
+ * ldap.c (add_server_to_servers): Reactivated.
+ (url_fetch_ldap): Call it here and try all configured servers in
+ case of a a failed lookup.
+ (fetch_next_cert_ldap): Detect the truncation error flag.
+ * misc.c (host_and_port_from_url, remove_percent_escapes): New.
+
+2004-11-22 Werner Koch <wk@g10code.com>
+
+ * dirmngr_ldap.c (main): New option --proxy.
+ * ocsp.c (do_ocsp_request): Take care of opt.disable_http.
+ * crlfetch.c (crl_fetch): Honor the --honor-http-proxy variable.
+ (crl_fetch): Take care of opt.disable_http and disable_ldap.
+ (crl_fetch_default, ca_cert_fetch, start_cert_fetch):
+ * ldap.c (run_ldap_wrapper): New arg PROXY.
+ (url_fetch_ldap, attr_fetch_ldap, start_cert_fetch_ldap): Pass it.
+
+ * http.c (http_open_document): Add arg PROXY.
+ (http_open): Ditto.
+ (send_request): Ditto and implement it as an override.
+
+ * ocsp.c (validate_responder_cert): Use validate_cert_chain.
+
+ * Makefile.am (AM_CPPFLAGS): Add macros for a few system
+ directories.
+ * dirmngr.h (opt): New members homedir_data, homedir_cache,
+ ldap_wrapper_program, system_daemon, honor_http_proxy, http_proxy,
+ ldap_proxy, only_ldap_proxy, disable_ldap, disable_http.
+ * dirmngr.c (main): Initialize new opt members HOMEDIR_DATA and
+ HOMEDIR_CACHE.
+ (parse_rereadable_options): New options --ldap-wrapper-program,
+ --http-wrapper-program, --disable-ldap, --disable-http,
+ --honor-http-proxy, --http-proxy, --ldap-proxy, --only-ldap-proxy.
+ (reread_configuration): New.
+
+ * ldap.c (ldap_wrapper): Use the correct name for the wrapper.
+
+ * crlcache.c (DBDIR_D): Make it depend on opt.SYSTEM_DAEMON.
+ (cleanup_cache_dir, open_dir, update_dir, make_db_file_name)
+ (crl_cache_insert, create_directory_if_needed): Use opt.HOMEDIR_CACHE
+
+ * validate.c (check_revocations): New.
+ * crlcache.c (crl_cache_isvalid): Factored most code out to
+ (cache_isvalid): .. new.
+ (crl_cache_cert_isvalid): New.
+ * server.c (cmd_checkcrl): Cleaned up by using this new function.
+ (reload_crl): Moved to ..
+ * crlcache.c (crl_cache_reload_crl): .. here and made global.
+
+ * certcache.c (cert_compute_fpr): Renamed from computer_fpr and
+ made global.
+ (find_cert_bysn): Try to lookup missing certs.
+ (cert_cache_init): Intialize using opt.HOMEDIR_DATA.
+
+
+2004-11-19 Werner Koch <wk@g10code.com>
+
+ * dirmngr-client.c (status_cb): New. Use it in very verbose mode.
+
+ * server.c (start_command_handler): Malloc the control structure
+ and properly release it. Removed the primary_connection
+ hack. Cleanup running wrappers.
+ (dirmngr_status): Return an error code.
+ (dirmngr_tick): Return an error code and detect a
+ cancellation. Use wall time and not CPU time.
+ * validate.c (validate_cert_chain): Add CTRL arg and changed callers.
+ * crlcache.c (crl_cache_isvalid):
+ * crlfetch.c (ca_cert_fetch, start_cert_fetch, crl_fetch_default)
+ (crl_fetch): Ditto.
+ * ldap.c (ldap_wrapper, run_ldap_wrapper, url_fetch_ldap)
+ (attr_fetch_ldap, start_cert_fetch_ldap): Ditto.
+ (ldap_wrapper_release_context): Reset the stored CTRL.
+ (reader_callback): Periodically call dirmngr_tick.
+ (ldap_wrapper_release_context): Print an error message for read
+ errors.
+ (ldap_wrapper_connection_cleanup): New.
+
+2004-11-18 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (main): Do not cd / if not running detached.
+
+ * dirmngr-client.c: New options --cache-cert and --validate.
+ (do_cache, do_validate): New.
+ * server.c (cmd_cachecert, cmd_validate): New.
+
+ * crlcache.c (get_issuer_cert): Make use of the certificate cache.
+ (crl_parse_insert): Validate the issuer certificate.
+
+ * dirmngr.c (handle_signal): Reinitialize the certificate cache on
+ a HUP.
+ (struct opts): Add --homedir to enable the already implemented code.
+ (handle_signal): Print stats on SIGUSR1.
+
+ * certcache.c (clean_cache_slot, cert_cache_init)
+ (cert_cache_deinit): New.
+ (acquire_cache_read_lock, acquire_cache_write_lock)
+ (release_cache_lock): New. Use them where needed.
+ (put_cert): Renamed from put_loaded_cert.
+ (cache_cert): New.
+ (cert_cache_print_stats): New.
+ (compare_serialno): Fixed.
+
+2004-11-16 Werner Koch <wk@g10code.com>
+
+ * Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SYSCONFDIR and
+ DIRMNGR_LIBEXECDIR.
+
+ * misc.c (dump_isotime, dump_string, dump_cert): New. Taken from
+ gnupg 1.9.
+ (dump_serial): New.
+
+2004-11-15 Werner Koch <wk@g10code.com>
+
+ * validate.c: New. Based on gnupg's certchain.c
+
+ * ldap.c (get_cert_ldap): Removed.
+ (read_buffer): New.
+ (start_cert_fetch_ldap, fetch_next_cert_ldap)
+ (end_cert_fetch_ldap): Rewritten to make use of the ldap wrapper.
+
+2004-11-12 Werner Koch <wk@g10code.com>
+
+ * http.c (insert_escapes): Print the percent sign too.
+
+ * dirmngr-client.c (inq_cert): Ignore "SENDCERT" and
+ "SENDISSUERCERT".
+
+ * server.c (do_get_cert_local): Limit the length of a retruned
+ certificate. Return NULL without an error if an empry value has
+ been received.
+
+ * crlfetch.c (ca_cert_fetch): Use the ksba_reader_object.
+ (setup_funopen, fun_reader, fun_closer): Removed.
+
+ * crlcache.c (get_issuer_cert): Adjust accordingly.
+
+ * ldap.c (attr_fetch_ldap_internal, attr_fetch_fun_closer)
+ (attr_fetch_fun_reader, url_fetch_ldap_internal)
+ (get_attr_from_result_ldap): Removed.
+ (destroy_wrapper, print_log_line, ldap_wrapper_thread)
+ (ldap_wrapper_release_context, reader_callback, ldap_wrapper)
+ (run_ldap_wrapper): New.
+ (url_fetch_ldap): Make use of the new ldap wrapper and return a
+ ksba reader object instead of a stdio stream.
+ (attr_fetch_ldap): Ditto.
+ (make_url, escape4url): New.
+
+2004-11-11 Werner Koch <wk@g10code.com>
+
+ * dirmngr.c (launch_ripper_thread): New.
+ (main): Start it wheere appropriate. Always ignore SIGPIPE.
+ (start_connection_thread): Maintain a connection count.
+ (handle_signal, handle_connections): Use it here instead of the
+ thread count.
+
+ * crlcache.c (crl_cache_insert): Changed to use ksba reader
+ object. Changed all callers to pass this argument.
+
+2004-11-08 Werner Koch <wk@g10code.com>
+
+ * dirmngr_ldap.c: New.
+
+ * crlcache.c (crl_cache_init): Don't return a cache object but
+ keep it module local. We only need one.
+ (crl_cache_deinit): Don't take cache object but work on existing
+ one.
+ (get_current_cache): New.
+ (crl_cache_insert, crl_cache_list, crl_cache_load): Use the global
+ cache object and removed the cache arg. Changed all callers.
+
+ * dirmngr-client.c: New option --ping.
+
+ * dirmngr.c (main): New option --daemon. Initialize PTH.
+ (handle_connections, start_connection_thread): New.
+ (handle_signal): New.
+ (parse_rereadable_options): New. Changed main to make use of it.
+ (set_debug): Don't bail out on invalid debug levels.
+ (main): Init the crl_chache for server and daemon mode.
+
+ * server.c (start_command_handler): New arg FD. Changed callers.
+
+2004-11-06 Werner Koch <wk@g10code.com>
+
+ * server.c (map_assuan_err): Factored out to ..
+ * maperror.c: .. new file.
+ * util.h: Add prototype
+
+2004-11-05 Werner Koch <wk@g10code.com>
+
+ * no-libgcrypt.c: New, used as helper for dirmngr-client which
+ does not need libgcrypt proper but jnlib references the memory
+ functions. Taken from gnupg 1.9.12.
+
+ * dirmngr.h: Factored i18n and xmalloc code out to ..
+ * i18n.h, util.h: .. New.
+
+ * dirmngr-client.c: New. Some code taken from gnupg 1.9.12.
+ * Makefile.am (bin_PROGRAMS) Add dirmngr-client.
+
+2004-11-04 Werner Koch <wk@g10code.com>
+
+ * src/server.c (get_fingerprint_from_line, cmd_checkcrl)
+ (cmd_checkocsp): New.
+ (register_commands): Register new commands.
+ (inquire_cert_and_load_crl): Factored most code out to ..
+ (reload_crl): .. new function.
+ * src/certcache.h, src/certcache.c: New.
+ * src/Makefile.am (dirmngr_SOURCES): Add new files.
+
+2004-11-04 Werner Koch <wk@g10code.com>
+
+ Please note that earlier entries are found in the top level
+ ChangeLog.
+ [Update after merge with GnuPG: see ./ChangeLog.1]
+
+
+ Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ 2011 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.
+
+Local Variables:
+buffer-read-only: t
+End:
diff --git a/dirmngr/ChangeLog.1 b/dirmngr/ChangeLog.1
new file mode 100644
index 0000000..f7b50c7
--- /dev/null
+++ b/dirmngr/ChangeLog.1
@@ -0,0 +1,806 @@
+There are old Dirmngr ChangeLog entries.
+
+2004-10-04 Werner Koch <wk@g10code.com>
+
+ * src/dirmngr.c: Changed an help entry description.
+
+2004-09-30 Werner Koch <wk@g10code.com>
+
+ * src/dirmngr.c (i18n_init): Always use LC_ALL.
+
+2004-09-28 Werner Koch <wk@g10code.com>
+
+ Released 0.5.6.
+
+ * config.guess, config.sub: Updated.
+
+2004-06-21 Werner Koch <wk@g10code.com>
+
+ * src/crlfetch.c (crl_fetch): Bad hack to use the right attribute.
+
+2004-05-13 Werner Koch <wk@gnupg.org>
+
+ Released 0.5.5.
+
+ * src/ldap.c (start_cert_fetch_ldap, start_cert_fetch_ldap): More
+ detailed error messages.
+
+ * src/crlcache.c (update_dir): Handle i-records properly.
+
+2004-04-29 Werner Koch <wk@gnupg.org>
+
+ Released 0.5.4.
+
+ * src/crlcache.h (crl_cache_result_t): Add CRL_CACHE_CANTUSE.
+ * src/server.c (cmd_isvalid): Handle it here.
+ * src/crlcache.c (crl_cache_isvalid): Issue this code if the CRL
+ cant be used.
+ (open_dir): Parse new fields 8,9 and 10 as well as the invalid flag.
+ (write_dir_line_crl): Write new fields.
+ (get_crl_number, get_auth_key_id): New.
+ (crl_cache_insert): Fill new fields. Mark the entry invalid if
+ the CRL is too old after an update or an unknown critical
+ extension was seen.
+ (list_one_crl_entry): Print the new fields.
+
+2004-04-28 Werner Koch <wk@gnupg.org>
+
+ * configure.ac: Requires libksba 0.9.6.
+
+ * src/dirmngr.c: New option --ocsp-signer.
+ * src/dirmngr.h (opt): Renamed member OCSP_REPONDERS to
+ OCSP_RESPONDER and made ist a simple string. Add OCSP_SIGNER.
+ * src/ocsp.c (ocsp_isvalid): Changed it accordingly.
+ (ocsp_isvalid): Pass the ocsp_signer to check_signature.
+ (check_signature): New arg SIGNER_FPR. Use it to retrieve the
+ certificate. Factored out common code to ..
+ (check_signature_core): .. New.
+
+2004-04-27 Werner Koch <wk@gnupg.org>
+
+ * src/server.c (start_command_handler): Keep track of the first
+ connection.
+ (dirmngr_tick): New.
+ * src/ldap.c (attr_fetch_fun_reader): Call it from time to time.
+
+2004-04-23 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c (main): Removed the add-servers option from the
+ gpgconf list. It is not really useful.
+
+2004-04-02 Thomas Schwinge <schwinge@nic-nac-project.de>
+
+ * autogen.sh: Added ACLOCAL_FLAGS.
+
+2004-04-13 Werner Koch <wk@gnupg.org>
+
+ * src/crlcache.c (update_dir): Do not double close FPOUT.
+
+2004-04-09 Werner Koch <wk@gnupg.org>
+
+ * src/cdblib.c (cdb_make_start): Wipeout the entire buffer to
+ shutup valgrind.
+ (ewrite): Fixed writing bad data on EINTR.
+
+ * src/ldap.c (get_attr_from_result_ldap): Fixed bad copy and
+ terminate of a string.
+
+ * src/crlfetch.c (crl_fetch): Fixed freeing of VALUE on error.
+
+2004-04-07 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.h (server_control_s): Add member force_crl_refresh.
+ * src/server.c (option_handler): New.
+ (start_command_handler): Register option handler
+ * src/crlcache.c (crl_cache_isvalid): Add arg FORCE_REFRESH.
+ (crl_cache_insert): Record last refresh in memory.
+
+ * src/server.c (inquire_cert_and_load_crl): Renamed from
+ inquire_cert.
+
+2004-04-06 Werner Koch <wk@gnupg.org>
+
+ Released 0.5.3
+
+ * doc/dirmngr.texi: Updated.
+ * doc/texinfo.tex: Updated.
+
+2004-04-05 Werner Koch <wk@gnupg.org>
+
+ * src/ocsp.c (ocsp_isvalid): Check THIS_UPDATE.
+
+ * src/misc.c (add_isotime): New.
+ (date2jd, jd2date, days_per_month, days_per_year): New. Taken from
+ my ancient (1988) code used in Wedit (time2.c).
+
+2004-04-02 Werner Koch <wk@gnupg.org>
+
+ * autogen.sh: Check gettext version.
+ * configure.ac: Add AM_GNU_GETTEXT.
+
+2004-04-02 gettextize <bug-gnu-gettext@gnu.org>
+
+ * Makefile.am (SUBDIRS): Add intl.
+ (EXTRA_DIST): Add config.rpath.
+ * configure.ac (AC_CONFIG_FILES): Add intl/Makefile,
+
+2004-04-02 Werner Koch <wk@gnupg.org>
+
+ Add i18n at most places.
+
+ * src/dirmngr.c (i18n_init): New.
+ (main): Call it.
+ * src/dirmngr.h: Add i18n stuff.
+
+2004-04-01 Werner Koch <wk@gnupg.org>
+
+ * src/misc.c (get_fingerprint_hexstring): New.
+
+ * src/server.c (dirmngr_status): New.
+
+2004-03-26 Werner Koch <wk@gnupg.org>
+
+ * configure.ac: Add AC_SYS_LARGEFILE.
+
+ * doc/dirmngr.texi: Changed the license to the GPL as per message
+ by Mathhias Kalle Dalheimer of Klaralvdalens-Datakonsult dated
+ Jan 7, 2004.
+ * doc/fdl.texi: Removed.
+
+2004-03-25 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c (main): New command --fetch-crl.
+
+2004-03-23 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c: New option --allow-ocsp.
+ * src/server.c (cmd_isvalid): Make use of allow_ocsp.
+
+2004-03-17 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c (main) <gpgconf>: Fixed default value quoting.
+
+2004-03-16 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c (main): Add ocsp-responder to the gpgconf list.
+ Add option --debug-level.
+ (set_debug): New.
+
+2004-03-15 Werner Koch <wk@gnupg.org>
+
+ * src/misc.c (canon_sexp_to_grcy): New.
+
+2004-03-12 Werner Koch <wk@gnupg.org>
+
+ * src/crlfetch.c (crl_fetch): Hack to substitute http for https.
+
+2004-03-10 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c (parse_ldapserver_file): Don't skip the entire
+ file on errors.
+
+2004-03-09 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c (my_ksba_hash_buffer): New.
+ (main): Initialize the internal libksba hashing.
+
+ * src/server.c (get_issuer_cert_local): Renamed to ...
+ (get_cert_local): ... this. Changed all callers. Allow NULL for
+ ISSUER to return the current target cert.
+ (get_issuing_cert_local): New.
+ (do_get_cert_local): Moved common code to here.
+
+2004-03-06 Werner Koch <wk@gnupg.org>
+
+ Released 0.5.2.
+
+ * configure.ac: Fixed last change to check the API version of
+ libgcrypt.
+
+2004-03-05 Werner Koch <wk@gnupg.org>
+
+ * configure.ac: Also check the SONAME of libgcrypt.
+
+2004-03-03 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c: New option --ocsp-responder.
+ * src/dirmngr.h (opt): Add member OCSP_RESPONDERS.
+
+2004-02-26 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * src/server.c (start_command_handler): Corrected typo and made
+ dirmngr output it's version in the greeting message.
+
+2004-02-24 Marcus Brinkmann <marcus@g10code.de>
+
+ * src/dirmngr.c (DEFAULT_ADD_SERVERS): Removed. If this were
+ true, there'd be no way to disable it.
+ (main): Dump options in new gpgconf format.
+
+2004-02-11 Werner Koch <wk@gnupg.org>
+
+ * autogen.sh (check_version): Removed bashism and simplified.
+
+2004-02-06 Moritz Schulte <mo@g10code.com>
+
+ * src/crlfetch.c (crl_fetch_default): Do not dereference VALUE,
+ when checking for non-zero.
+
+2004-02-01 Marcus Brinkmann <marcus@g10code.de>
+
+ * src/dirmngr.c (DEFAULT_ADD_SERVERS, DEFAULT_MAX_REPLIES)
+ (DEFAULT_LDAP_TIMEOUT): New macros.
+ (main): Use them.
+ (enum cmd_and_opt_values): New command aGPGConfList.
+ (main): Add handler here.
+
+2004-01-17 Werner Koch <wk@gnupg.org>
+
+ * configure.ac: Added AC_CHECK_FUNCS tests again, because the
+ other test occurrences belong to the jnlib tests block.
+
+2004-01-15 Moritz Schulte <mo@g10code.com>
+
+ * configure.ac: Fixed funopen replacement mechanism; removed
+ unnecessary AC_CHECK_FUNCS calls.
+
+2004-01-14 Werner Koch <wk@gnupg.org>
+
+ * src/crlcache.c (list_one_crl_entry): Don't use putchar.
+
+ * src/server.c (cmd_listcrls): New.
+
+2003-12-23 Werner Koch <wk@gnupg.org>
+
+ Released 0.5.1.
+
+2003-12-17 Werner Koch <wk@gnupg.org>
+
+ * configure.ac (CFLAGS): Add -Wformat-noliteral in gcc +
+ maintainer mode.
+ (NEED_LIBASSUAN_VERSION): Bump up to 0.6.2.
+
+2003-12-16 Werner Koch <wk@gnupg.org>
+
+ * configure.ac: Update the tests for jnlib.
+ * src/dirmngr.c (main): Ignore SIGPIPE in server mode.
+
+2003-12-12 Werner Koch <wk@gnupg.org>
+
+ * src/crlcache.c (hash_dbfile): Also hash version info of the
+ cache file format.
+
+ * src/Makefile.am (dirmngr_SOURCES): Add http.h.
+
+ * configure.ac: Removed checking for DB2. Add checking for mmap.
+ * src/cdb.h, src/cdblib.h: New. Add a few comments from the
+ original man page and fixed typos.
+ * src/cdblib.c (cdb_findinit, cdb_findnext): Modified to allow
+ walking over all entries.
+ * src/crlcache.h: Removed DB2/4 cruft.
+ (release_one_cache_entry, lock_db_file, crl_parse_insert)
+ (crl_cache_insert, crl_cache_isvalid, list_one_crl_entry): Use the
+ new CDB interface.
+
+ * src/dirmngr.c: Beautified the help messages.
+ (wrong_args): New.
+ (main): new option --force. Revamped the command handling code.
+ Allow to pass multiple CRLS as well as stdin to --local-crl.
+ * src/crlcache.c (crl_cache_insert): Make --force work.
+
+2003-12-11 Werner Koch <wk@gnupg.org>
+
+ * src/crlfetch.c (crl_fetch): Enhanced to allow fetching binary
+ data using HTTP.
+ * src/http.c, src/http.h: Replaced by the code from gnupg 1.3 and
+ modified acording to our needs.
+ (read_line): New. Based on the code from GnuPG's iobuf_read_line.
+ * configure.ac: Check for getaddrinfo.
+
+ * src/dirmngr.c (parse_ldapserver_file): Close the stream.
+ (main): Free ldapfile.
+
+ * src/ocsp.c, src/ocsp.h: New. Albeit not functionality.
+
+ * src/server.c (inquire_cert): Catch EOF when reading dist points.
+
+ * src/crlcache.c (hash_dbfile, check_dbfile): New.
+ (lock_db_file, crl_cache_insert): Use them here to detect
+ corrupted CRL files.
+ (open_dir): Read the new dbfile hash field.
+
+ * src/crlfetch.c (crl_fetch, crl_fetch_default): Changed to retrun
+ a stream.
+ (fun_reader, fun_closer, setup_funopen): New.
+ * src/server.c (inquire_cert): Changed to use the new stream interface
+ of crlfetch.c.
+
+2003-12-10 Werner Koch <wk@gnupg.org>
+
+ * src/funopen.c: New.
+ * configure.ac (funopen): Add test.
+ * src/Makefile.am (dirmngr_LDADD): Add LIBOBJS.
+
+ * src/crlcache.c (next_line_from_file): Remove the limit on the
+ line length.
+ (crl_cache_new): Removed.
+ (open_dbcontent): New.
+ (crl_cache_init): Use it here.
+ (crl_cache_flush): The DB content fie is now in the cache
+ directory, so we can simplify it.
+ (make_db_file_name, lock_db_file, unlock_db_file): New.
+ (release_cache): Close the cached DB files.
+ (crl_cache_isvalid): Make use of the new lock_db_file.
+ (crl_cache_insert): Changed to take a stream as argument.
+ (crl_parse_insert): Rewritten to use a temporary DB and to avoid
+ using up large amounts of memory.
+ (db_entry_new): Removed.
+ (release_cache,release_one_cache_entry): Splitted up.
+ (find_entry): Take care of the new deleted flag.
+ (crl_cache_load): Simplified becuase we can now pass a FP to the
+ insert code.
+ (save_contents): Removed.
+ (update_dir): New.
+ (open_dbcontent_file): Renamed to open_dir_file.
+ (check_dbcontent_version): Renamed to check_dir_version.
+ (open_dbcontent): Renamed to open_dir.
+
+ * src/dirmngr.c: New option --faked-system-time.
+ * src/misc.c (faked_time_p, set_time, get_time): New. Taken from GnuPG.
+ (check_isotime): New.
+ (unpercent_string): New.
+
+2003-12-09 Werner Koch <wk@gnupg.org>
+
+ * src/crlcache.h (DBDIR,DBCONTENTFILE): Changed value.
+
+ * autogen.sh: Reworked.
+ * README.CVS: New.
+ * configure.ac: Added min_automake_version.
+
+2003-12-03 Werner Koch <wk@gnupg.org>
+
+ * src/server.c (cmd_lookup): Send an END line after each
+ certificate.
+
+2003-11-28 Werner Koch <wk@gnupg.org>
+
+ * src/Makefile.am (dirmngr_LDADD): Remove DB_LIBS
+ because it never got defined and -ldb{2,4} is implictly set
+ by the AC_CHECK_LIB test in configure.
+
+ * src/crlcache.c (mydbopen): DB4 needs an extra parameter; I
+ wonder who ever tested DB4 support. Add an error statement in
+ case no DB support is configured.
+
+ * tests/Makefile.am: Don't use AM_CPPFLAGS but AM_CFLAGS, replaced
+ variables by configure templates.
+ * src/Makefile.am: Ditto.
+
+2003-11-19 Werner Koch <wk@gnupg.org>
+
+ * src/crlcache.c (list_one_crl_entry): Define X to nothing for non
+ DB4 systems. Thanks to Luca M. G. Centamore.
+
+2003-11-17 Werner Koch <wk@gnupg.org>
+
+ Released 0.5.0
+
+ * src/crlcache.c (crl_cache_new): Fixed eof detection.
+
+ * src/server.c (cmd_loadcrl): Do the unescaping.
+
+ * doc/dirmngr.texi: Added a history section for this modified
+ version.
+
+2003-11-14 Werner Koch <wk@gnupg.org>
+
+ * tests/asschk.c: New. Taken from GnuPG.
+ * tests/Makefile.am: Added asschk.
+
+2003-11-13 Werner Koch <wk@gnupg.org>
+
+ * src/ldap.c (fetch_next_cert_ldap): Get the pattern switching
+ right.
+
+ * tests/test-dirmngr.c: Replaced a couple of deprecated types.
+
+ * configure.ac (GPG_ERR_SOURCE_DEFAULT): Added.
+ (fopencookie, asprintf): Removed unneeded test.
+ (PRINTABLE_OS_NAME): Updated the test from gnupg.
+ (CFLAGS): Do full warnings only in maintainer mode. Add flag
+ --enable gcc-warnings to override it and to enable even more
+ warnings.
+ * acinclude.m4: Removed the libgcrypt test.
+
+ * src/ldap.c (get_attr_from_result_ldap): Simplified the binary
+ hack and return a proper gpg error.
+ (attr_fetch_ldap_internal): Changed error handling.
+ (attr_fetch_ldap): Reworked. Return configuration error if no
+ servers are configured.
+ (url_fetch_ldap, add_server_to_servers)
+ (url_fetch_ldap_internal): Reworked.
+ (struct cert_fetch_context_s): New to get rid of a global state.
+ (start_cert_fetch_ldap): Allocate context and do a bind with a
+ timeout. Parse pattern.
+ (end_cert_fetch_ldap): Take context and don't return anything.
+ (find_next_pattern): Removed.
+ (parse_one_pattern): Redone.
+ (get_cert_ldap): Redone.
+ * src/server.c (cmd_lookup): Changed for changed fetch functions.
+
+ * doc/dirmngr.texi: Reworked a bit to get rid of tex errors.
+
+ * configure.ac: Enable makeinfo test.
+
+ * src/crlcache.c (crl_cache_insert): Fixed for latest KSBA API
+ changes.
+ * tests/test-dirmngr.c (main): Ditto. Also added some more error
+ checking.
+
+2003-11-11 Werner Koch <wk@gnupg.org>
+
+ * src/cert.c (hashify_data, hexify_data, serial_hex)
+ (serial_to_buffer): Moved all to ...
+ * src/misc.c: .. here.
+ * src/Makefile.am (cert.c, cert.h): Removed.
+ * cert.c, cert.h: Removed.
+
+ * m4/: New.
+ * configure.ac, Makefile.am: Include m4 directory support, updated
+ required library versions.
+
+ * src/cert.c (make_cert): Removed.
+
+ * src/ldap.c (fetch_next_cert_ldap): Return a gpg style error.
+
+ * src/misc.h (copy_time): New.
+ * src/misc.c (get_isotime): New.
+ (iso_string2time, iso_time2string): Removed.
+ (unhexify): New.
+
+ * src/crlcache.h (DBCONTENTSVERSION): Bumbed to 0.6.
+ * src/crlcache.c (finish_sig_check): New. Factored out from
+ crl_parse_insert and entirely redone.
+ (do_encode_md): Removed.
+ (print_time): Removed
+ (crl_cache_isvalid): Reworked.
+
+2003-11-10 Werner Koch <wk@gnupg.org>
+
+ * src/crlcache.c (make_db_val, parse_db_val): Removed.
+
+ * src/cert.c (serial_to_buffer): New.
+
+ * src/server.c (get_issuer_cert_local): Rewritten.
+
+ * src/crlcache.c (crl_parse_insert): Rewritten. Takes now a CTRL
+ instead of the Assuan context. Changed caller accordingly.
+ (get_issuer_cert): Cleaned up.
+
+ * src/crlfetch.c (crl_fetch): Changed VALUE to unsigned char* for
+ documentation reasons. Make sure that VALUE is released on error.
+ (crl_fetch_default, ca_cert_fetch): Ditto.
+
+ * src/crlcache.c (release_cache): New.
+ (crl_cache_deinit): Use it here.
+ (crl_cache_flush): Redone.
+ (save_contents): Redone.
+ (crl_cache_list, list_one_crl_entry): Print error messages.
+
+2003-11-06 Werner Koch <wk@gnupg.org>
+
+ * src/crlcache.c (create_directory_if_needed, cleanup_cache_dir):
+ New. Factored out from crl_cache_new and mostly rewritten.
+ (crl_cache_new): Rewritten.
+ (next_line_from_file): New.
+ (find_entry): Cleaned up.
+ (crl_cache_deinit): Cleaned up.
+
+ * src/dirmngr.c (dirmngr_init_default_ctrl): New stub.
+ * src/dirmngr.h (ctrl_t): New.
+ (DBG_ASSUAN,...): Added the usual debug test macros.
+ * src/server.c: Removed the GET_PTR cruft, replaced it by ctrl_t.
+ Removed the recursion flag.
+ (get_issuer_cert_local): Allow for arbitary large
+ certificates. 4096 is definitely too small.
+ (inquire_cert): Ditto.
+ (start_command_handler): Set a hello line and call the default
+ init function.
+ (cmd_isvalid): Rewritten.
+ (inquire_cert): Removed unused arg LINE. General cleanup.
+ (map_assuan_err,map_to_assuan_status): New. Taken from gnupg 1.9.
+ (cmd_lookup): Rewritten.
+ (cmd_loadcrl): Started to rewrite it.
+
+2003-10-29 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.c (parse_ldapserver_file): Entirely rewritten.
+ (cleanup): New.
+ (main): Cleaned up.
+
+2003-10-28 Werner Koch <wk@gnupg.org>
+
+ * src/dirmngr.h: Renamed dirmngr_opt to opt.
+
+ * src/dirmngr.c (parse_ldapserver_file, free_ldapservers_list):
+ Moved with this file. Cleaned up. Replaced too deep recursion in
+ the free function.
+
+2003-10-21 Werner Koch <wk@gnupg.org>
+
+ Changed all occurrences of assuan.h to use use the system provided
+ one.
+ * src/server.c (register_commands): Adjusted for Assuan API change.
+
+2003-08-14 Werner Koch <wk@gnupg.org>
+
+ * src/Makefile.am: s/LIBKSBA_/KSBA_/. Changed for external Assuan lib.
+ * tests/Makefile.am: Ditto.
+
+ * configure.ac: Partly restructured, add standard checks for
+ required libraries, removed included libassuan.
+ * Makefile.am (SUBDIRS): Removed assuan becuase we now use the
+ libassuan package.
+
+ * src/dirmngr.c (main): Properly initialize Libgcrypt and libksba.
+
+2003-08-13 Werner Koch <wk@gnupg.org>
+
+ * src/server.c (get_issuer_cert_local): Print error using
+ assuan_strerror.
+
+ * src/crlcache.c (do_encode_md, start_sig_check): Adjust for
+ changed Libgcrypt API.
+
+2003-06-19 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * configure.ac: Upped version to 0.4.7-cvs.
+
+2003-06-19 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * configure.ac: Release 0.4.6.
+
+2003-06-17 Bernhard Reiter <bernhard@intevation.de>
+
+ * src/ldap.c (url_fetch_ldap()):
+ try other default servers when an url with hostname failed
+ * AUTHORS: added Steffen and Werner
+ * THANKS: Thanked people in the ChangeLog and the Ägypten-Team
+
+
+2003-06-16 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * configure.ac, src/crlcache.h, src/crlcache.c: Added db4 support.
+ * src/Makefile.am, tests/Makefile.am: Removed automake warning.
+ * tests/test-dirmngr.c: Removed a warning.
+
+2003-05-12 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * doc/Makefile.am: Added dirmngr.ops to DISTCLEANFILES.
+ * ChangeLog, doc/ChangeLog, src/ChangeLog: Merged dirmngr ChangeLogs
+ into one toplevel file.
+ * acinclude.m4, configure.ac: Renamed PFX to PATH for consistency.
+
+2003-05-12 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * src/ldap.c: Fixed end-of-certificates-list indication.
+
+2003-05-08 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * src/server.c: Fixed iteration over server list
+
+2003-02-23 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * src/crlcache.h, src/crlcache.c, src/dirmngr.c: Implemented --flush command.
+
+2003-02-07 Marcus Brinkmann <marcus@g10code.de>
+
+ * configure.ac: Release 0.4.4.
+
+2003-02-05 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * src/ldap.c: Try harder with and without ";binary" in the
+ attribute name when fetching certificates.
+ * src/ldap.c, src/server.c: Support multiple userCertificate attributes
+ per entry.
+
+2003-02-04 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * src/ldap.c: Include the sn attribute in the search filter.
+ Better log messages.
+
+2002-11-20 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Doc updates (fixes #1373)
+ * Fix for #1419 (crash in free_ldapservers_list())
+ * Fix for #1375. Dirmngr now asks back with an INQUIRE SENDCERT before
+ querying the LDAP servers for an issuer certificate to validate a CRL
+
+2002-11-12 Werner Koch <wk@gnupg.org>
+
+ * config.sub, config.guess: Updated from ftp.gnu.org/gnu/config
+ to version 2002-11-08.
+
+2002-11-12 Werner Koch <wk@gnupg.org>
+
+ * dirmngr.c (main) <load_crl_filename>: Better pass NULL instead
+ of an unitialized Assuan context. Let's hope that the other
+ functions can cope with this.
+
+2002-10-25 Bernhard Reiter <bernhard@intevation.de>
+
+ * src/ldap.c (get_attr_from_result_ldap()):
+ added value extraction retry for CRLs and Certs without ";binary"
+ * changed version number to reflect cvs status to "0.4.3-cvs"
+
+2002-08-21 Werner Koch <wk@gnupg.org>
+
+ * dirmngr.c (main): Changed default homedir to .gnupg.
+
+2002-08-07 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Added configure check to examine whether db2 cursor() uses 3 or
+ 4 parameters.
+
+2002-07-31 Werner Koch <wk@gnupg.org>
+
+ * doc/dirmngr.texi: Fixed the structure and added menu entries
+ for the other nodes.
+
+2002-07-30 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Added doc dir and first steps towards manual.
+
+2002-07-29 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Got rid of the default server for CRL lookup. We now use the
+ same list of servers that we use for cert. lookup.
+
+2002-07-29 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * New option --add-servers to allow dirmngr to add LDAP servers
+ found in CRL distribution points to the list of servers it
+ searches. NOTE: The added servers are only active in the currently
+ running dirmngr -- the info isn't written to persistens storage.
+
+2002-07-26 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Default LDAP timeout is 100 seconds now.
+
+ * Use DB2 instead of DB1. Check for libresolv, fixed bug when
+ libldap was found in the default search path.
+
+2002-07-22 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Implemented --load-crl <filename> option. Also available as
+ LOADCRL assuan command when in server mode.
+
+2002-07-22 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Implemented new option --ldaptimeout to specify the number of seconds to
+ wait for an LDAP request before timeout.
+
+ * Added --list-crls option to print the contents of the CRL cache
+ * Added some items to the dbcontents file to make printout nicer
+ and updated it's version number
+
+2002-07-02 Werner Koch <wk@gnupg.org>
+
+ * crlcache.c (crl_parse_insert): Fixed log_debug format string.
+
+2002-07-02 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * configure.ac: Use DB->get() return value correctly.
+
+2002-06-28 Werner Koch <wk@gnupg.org>
+
+ * crlcache.c (crl_parse_insert): Keep track of newly allocated
+ ENTRY so that we don't free existing errors after a bad signature.
+
+ * dirmngr.h: Include prototype for start_command_handler.
+
+ * crlfetch.c, crlcache.c, http.c, cert.c, ldap.c: Include
+ config.h.
+
+ * crlcache.c (crl_parse_insert): Fixed format type specifiers for
+ time_t variables in log_debug.
+
+ * error.h: Use log_debug instead of dirmngr_debug. Changed all
+ callers.
+ * Makefile.am (dirmngr_SOURCES): Removed error.c
+
+ * dirmngr.c (main): Register gcrypt malloc functions with ksba so
+ that we don't run into problems by using the wrong free function.
+ The gcrypt malloc function have the additional benefit of a
+ providing allocation sanity checks when compiled with that
+ feature.
+
+ * crlcache.c (get_issuer_cert): Use xfree instead of ksba_free.
+
+
+2002-06-27 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * ldap.c: Look for both userCertificate and caCertificate
+
+2002-06-26 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * configure.ac: Upped version number to 0.3.1
+
+2002-06-25 Werner Koch <wk@gnupg.org>
+
+ * server.c (cmd_lookup): Use assuan_write_status which ensures a
+ correct syntax.
+
+2002-06-20 Werner Koch <wk@gnupg.org>
+
+ * crlcache.c (crl_cache_isvalid): Started with some nicer logging.
+ However, this will need a lot more work.
+ (get_issuer_cert): Ditto.
+
+ * dirmngr.c (main): Changed required libgcrypt version and don't
+ print the prefix when using a logfile.
+
+2002-06-20 Werner Koch <wk@gnupg.org>
+
+ * tests/Makefile.am (TESTS): Removed test-dirmngr because it
+ is not a proper test program.
+ (EXTRA_DIST): Removed the non-existent test certificate.
+
+2002-05-21 Werner Koch <wk@gnupg.org>
+
+ * server.c (start_command_handler): Enable assuan debugging.
+
+2002-05-08 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Replaced gdbm check with db1 check
+
+2002-05-08 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Replaced gdbm with db1, updated file format version
+
+2002-03-01 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Added gdbm configure check
+
+2002-01-23 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Return ASSUAN_CRL_Too_Old if the CRL is too old
+
+
+2002-01-17 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ Added commandline options --ldapserver <host> --ldapport <port>
+ --ldapuser <user> --ldappassword <passwd>.
+
+ Cleaned up CRL parsing, signature evaluation a bit, changed
+ datetime format in config file to ISO, added version string to
+ contents format and cache file clean up code in case of mismatch.
+
+2002-01-14 Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+
+ * Use dirmngr_opt.homedir for storing the db. Added Makefile.am to
+ tests, bugfixes.
+
+ * First code.
+ Things that work:
+ Loading/saving database (paths hardcoded)
+ Fetching CRL from hardcoded server, parsing and inserting in database
+ Answer ISVALID xxx.yyy requests
+
+ Things that are missing:
+ Some error-checking/handling
+ Proper autoconf handling of gdbm and OpenLDAP
+ Signature checking downloaded CRLs
+ Answer LOOKUP requests
+ ...
+
+ How to test:
+ cd tests
+ ldapsearch -v -x -h www.trustcenter.de -b '<some-users-DN>' userCertificate -t
+ cp /tmp/<cert-file> testcert.der
+ ./test-dirmngr
+
+Local Variables:
+buffer-read-only: t
+End:
diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am
new file mode 100644
index 0000000..0e9a7c7
--- /dev/null
+++ b/dirmngr/Makefile.am
@@ -0,0 +1,100 @@
+# Makefile.am - dirmngr
+# Copyright (C) 2002 Klarälvdalens Datakonsult AB
+# Copyright (C) 2004, 2007, 2010 g10 Code GmbH
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+
+## Process this file with automake to produce Makefile.in
+
+EXTRA_DIST = OAUTHORS ONEWS ChangeLog.1 ChangeLog-2011
+
+bin_PROGRAMS = dirmngr dirmngr-client
+
+if USE_LDAPWRAPPER
+libexec_PROGRAMS = dirmngr_ldap
+endif
+
+AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common
+
+include $(top_srcdir)/am/cmacros.am
+
+AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) \
+ $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS)
+
+BUILT_SOURCES = no-libgcrypt.c
+
+CLEANFILES = no-libgcrypt.c
+
+if HAVE_W32_SYSTEM
+ldap_url = ldap-url.h ldap-url.c
+else
+ldap_url =
+endif
+
+if USE_LDAPWRAPPER
+extraldap_src = ldap-wrapper.c
+else
+extraldap_src = ldap-wrapper-ce.c dirmngr_ldap.c
+endif
+
+noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h
+
+dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \
+ certcache.c certcache.h \
+ cdb.h cdblib.c misc.c dirmngr-err.h \
+ ocsp.c ocsp.h validate.c validate.h \
+ ks-action.c ks-action.h ks-engine.h \
+ ks-engine-hkp.c ks-engine-http.c ks-engine-finger.c ks-engine-kdns.c
+
+if USE_LDAP
+dirmngr_SOURCES += ldapserver.h ldapserver.c ldap.c w32-ldap-help.h \
+ ldap-wrapper.h $(ldap_url) $(extraldap_src)
+ldaplibs = $(LDAPLIBS)
+else
+ldaplibs =
+endif
+
+
+dirmngr_LDADD = $(libcommontlsnpth) $(libcommonpth) \
+ ../gl/libgnu.a $(DNSLIBS) $(LIBASSUAN_LIBS) \
+ $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(NPTH_LIBS) \
+ $(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) $(LIBINTL) $(LIBICONV)
+if !USE_LDAPWRAPPER
+dirmngr_LDADD += $(ldaplibs)
+endif
+dirmngr_LDFLAGS = $(extra_bin_ldflags)
+
+if USE_LDAPWRAPPER
+dirmngr_ldap_SOURCES = dirmngr_ldap.c $(ldap_url)
+dirmngr_ldap_CFLAGS = $(GPG_ERROR_CFLAGS) $(LIBGCRYPT_CFLAGS)
+dirmngr_ldap_LDFLAGS =
+dirmngr_ldap_LDADD = $(libcommon) no-libgcrypt.o ../gl/libgnu.a \
+ $(GPG_ERROR_LIBS) $(LDAPLIBS) $(LBER_LIBS) $(LIBINTL) \
+ $(LIBICONV)
+endif
+
+dirmngr_client_SOURCES = dirmngr-client.c
+dirmngr_client_LDADD = $(libcommon) no-libgcrypt.o \
+ ../gl/libgnu.a $(LIBASSUAN_LIBS) \
+ $(GPG_ERROR_LIBS) $(NETLIBS) $(LIBINTL) $(LIBICONV)
+dirmngr_client_LDFLAGS = $(extra_bin_ldflags)
+
+
+no-libgcrypt.c : $(top_srcdir)/tools/no-libgcrypt.c
+ cat $(top_srcdir)/tools/no-libgcrypt.c > no-libgcrypt.c
+
+
+$(PROGRAMS) : $(libcommon) $(libcommonpth) $(libcommontls) $(libcommontlsnpth)
diff --git a/dirmngr/OAUTHORS b/dirmngr/OAUTHORS
new file mode 100644
index 0000000..7832449
--- /dev/null
+++ b/dirmngr/OAUTHORS
@@ -0,0 +1,38 @@
+The old AUTHORS file from the separate dirmngr package.
+
+ Package: dirmngr
+ Maintainer: Werner Koch <wk@gnupg.org>
+ Bug reports: bug-dirmngr@gnupg.org
+ Security related bug reports: security@gnupg.org
+ License: GPLv2+
+
+
+Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
+ - Initial code
+
+g10 Code GmbH <code@g10code.com>
+ - All stuff written since October 2003.
+
+Werner Koch <wk@gnupg.org>, <wk@g10code.com>
+ - Help with initial code.
+
+Free Software Foundation <gnu@gnu.org>
+ - Code taken from GnuPG.
+
+Michael Tokarev <mjt@corpit.ru>
+ - src/cdb.h and src/cdblib.c from the public domain tinycdb 0.73.
+
+
+The actual code is under the GNU GPL, except for src/cdb.h and
+src/cdblib.h which are in the public domain.
+
+
+ Copyright 2003, 2004, 2006, 2007, 2008, 2010 g10 Code GmbH
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/dirmngr/ONEWS b/dirmngr/ONEWS
new file mode 100644
index 0000000..cb20507
--- /dev/null
+++ b/dirmngr/ONEWS
@@ -0,0 +1,240 @@
+These are NEWS entries from the old separate dirmngr package
+
+Noteworthy changes in version 1.1.0 (unreleased)
+------------------------------------------------
+
+ * Fixed a resource problem with LDAP CRLs.
+
+ * Fixed a bad EOF detection with HTTP CRLs.
+
+ * Made "dirmngr-client --url --load-crl URL" work.
+
+ * New option --ignore-cert-extension.
+
+ * Make use of libassuan 2.0 which is available as a DSO.
+
+
+Noteworthy changes in version 1.0.3 (2009-06-17)
+------------------------------------------------
+
+ * Client based trust anchors are now supported.
+
+ * Configured certificates with the suffix ".der" are now also used.
+
+ * Libgcrypt 1.4 is now required.
+
+
+Noteworthy changes in version 1.0.2 (2008-07-31)
+------------------------------------------------
+
+ * New option --url for the LOOKUP command and dirmngr-client.
+
+ * The LOOKUP command does now also consults the local cache. New
+ option --cache-only for it and --local for dirmngr-client.
+
+ * Port to Windows completed.
+
+ * Improved certificate chain construction.
+
+ * Support loading of PEM encoded CRLs via HTTP.
+
+
+Noteworthy changes in version 1.0.1 (2007-08-16)
+------------------------------------------------
+
+ * The option --ocsp-signer may now take a filename to allow several
+ certificates to be valid signers for the default responder.
+
+ * New option --ocsp-max-period and improved the OCSP time checks.
+
+ * New option --force-default-signer for dirmngr-client.
+
+ * Ported to Windows.
+
+
+Noteworthy changes in version 1.0.0 (2006-11-29)
+------------------------------------------------
+
+ * Bumbed the version number.
+
+ * Removed included gettext. We now require the system to provide a
+ suitable installation.
+
+
+Noteworthy changes in version 0.9.7 (2006-11-17)
+------------------------------------------------
+
+ * Internal cleanups.
+
+ * Fixed updating of DIR.txt. Add additional diagnostics.
+
+ * Updated gettext package.
+
+
+Noteworthy changes in version 0.9.6 (2006-09-04)
+------------------------------------------------
+
+ * A couple of bug fixes for OCSP.
+
+ * OCSP does now make use of the responder ID and optionally included
+ certificates in the response to locate certificates.
+
+ * No more lost file descriptors when loading CRLs via HTTP.
+
+ * HTTP redirection for CRL and OCSP has been implemented.
+
+ * Man pages are now build and installed from the texinfo source.
+
+
+Noteworthy changes in version 0.9.5 (2006-06-27)
+------------------------------------------------
+
+ * Fixed a problems with the CRL caching and CRL certificate
+ validation.
+
+ * Improved diagnostics.
+
+
+Noteworthy changes in version 0.9.4 (2006-05-16)
+------------------------------------------------
+
+ * Try all names of each crlDP.
+
+ * Don't shutdown the socket after sending the HTTP request.
+
+
+Noteworthy changes in version 0.9.3 (2005-10-26)
+------------------------------------------------
+
+ * Minor bug fixes.
+
+
+Noteworthy changes in version 0.9.2 (2005-04-21)
+------------------------------------------------
+
+ * Make use of authorityKeyidentifier.keyIdentifier.
+
+ * Fixed a possible hang on exit.
+
+
+Noteworthy changes in version 0.9.1 (2005-02-08)
+------------------------------------------------
+
+ * New option --pem for dirmngr-client to allow requesting service
+ using a PEM encoded certificate.
+
+ * New option --squid-mode to allow using dirmngr-client directly as a
+ Squid helper.
+
+ * Bug fixes.
+
+
+Noteworthy changes in version 0.9.0 (2004-12-17)
+------------------------------------------------
+
+ * New option --daemon to start dirmngr as a system daemon. This
+ switches to the use of different directories and also does
+ CRL signing certificate validation on its own.
+
+ * New tool dirmngr-client.
+
+ * New options: --ldap-wrapper-program, --http-wrapper-program,
+ --disable-ldap, --disable-http, --honor-http-proxy, --http-proxy,
+ --ldap-proxy, --only-ldap-proxy, --ignore-ldap-dp and
+ --ignore-http-dp.
+
+ * Uses an external ldap wrapper to cope with timeouts and general
+ LDAP problems.
+
+ * SIGHUP may be used to reread the configuration and to flush the
+ certificate cache.
+
+ * An authorithyKeyIdentifier in a CRL is now handled correctly.
+
+
+Noteworthy changes in version 0.5.6 (2004-09-28)
+------------------------------------------------
+
+ * LDAP fix.
+
+ * Logging fixes.
+
+ * Updated some configuration files.
+
+
+Noteworthy changes in version 0.5.5 (2004-05-13)
+------------------------------------------------
+
+ * Fixed the growing-dir.txt bug.
+
+ * Better LDAP error logging.
+
+
+Noteworthy changes in version 0.5.4 (2004-04-29)
+------------------------------------------------
+
+ * New commands --ocsp-responder and --ocsp-signer to define a default
+ OCSP reponder if a certificate does not contain an assigned OCSP
+ responder.
+
+
+Noteworthy changes in version 0.5.3 (2004-04-06)
+------------------------------------------------
+
+ * Basic OCSP support.
+
+
+Noteworthy changes in version 0.5.2 (2004-03-06)
+------------------------------------------------
+
+ * New Assuan command LISTCRLS.
+
+ * A couple of minor bug fixes.
+
+
+Noteworthy changes in version 0.5.1 (2003-12-23)
+------------------------------------------------
+
+* New options --faked-system-time and --force.
+
+* Changed the name of the cache directory to $HOMEDIR/dirmngr-cache.d
+ and renamed the dbcontents file. You may delete the now obsolete
+ cache/ directory and the dbcontents file.
+
+* Dropped DB2 or DB4 use. There is no need for it because a constant
+ database fits our needs far better.
+
+* Experimental support for retrieving CRLs via http.
+
+* The --log-file option may now be used to print logs to a socket.
+ Prefix the socket name with "socket://" to enable this. This does
+ not work on all systems and falls back to stderr if there is a
+ problem with the socket.
+
+
+Noteworthy changes in version 0.5.0 (2003-11-17)
+------------------------------------------------
+
+* Revamped the entire thing.
+
+* Does now require Libgcrypt 1.1.90 or higher, as well as the latest
+ libksba and libassuan.
+
+* Fixed a bug in the assuan inquire processing.
+
+
+Noteworthy changes as of 2002-08-21
+------------------------------------
+
+* The default home directory is now .gnupg
+
+
+ Copyright 2003, 2004, 2005 g10 Code GmbH
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
+ implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/dirmngr/cdb.h b/dirmngr/cdb.h
new file mode 100644
index 0000000..0c0d270
--- /dev/null
+++ b/dirmngr/cdb.h
@@ -0,0 +1,94 @@
+/* $Id: cdb.h 106 2003-12-12 17:36:49Z werner $
+ * public cdb include file
+ *
+ * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+ * Public domain.
+ *
+ * Taken from tinycdb-0.73. By Werner Koch <wk@gnupg.org> 2003-12-12.
+ */
+
+#ifndef TINYCDB_VERSION
+#define TINYCDB_VERSION 0.73
+
+typedef unsigned int cdbi_t; /*XXX should be at least 32 bits long */
+
+/* common routines */
+cdbi_t cdb_hash(const void *buf, cdbi_t len);
+cdbi_t cdb_unpack(const unsigned char buf[4]);
+void cdb_pack(cdbi_t num, unsigned char buf[4]);
+
+struct cdb {
+ int cdb_fd; /* file descriptor */
+ /* private members */
+#ifdef HAVE_W32_SYSTEM
+ void *cdb_mapping; /* Mapping handle. */
+#endif
+ cdbi_t cdb_fsize; /* datafile size */
+ const unsigned char *cdb_mem; /* mmap'ed file memory */
+ cdbi_t cdb_vpos, cdb_vlen; /* found data */
+ cdbi_t cdb_kpos, cdb_klen; /* found key (only set if cdb_findinit
+ was called with KEY set to NULL). */
+};
+
+#define cdb_datapos(c) ((c)->cdb_vpos)
+#define cdb_datalen(c) ((c)->cdb_vlen)
+#define cdb_keypos(c) ((c)->cdb_kpos)
+#define cdb_keylen(c) ((c)->cdb_klen)
+#define cdb_fileno(c) ((c)->cdb_fd)
+
+int cdb_init(struct cdb *cdbp, int fd);
+void cdb_free(struct cdb *cdbp);
+
+int cdb_read(const struct cdb *cdbp,
+ void *buf, unsigned len, cdbi_t pos);
+int cdb_find(struct cdb *cdbp, const void *key, unsigned klen);
+
+struct cdb_find {
+ struct cdb *cdb_cdbp;
+ cdbi_t cdb_hval;
+ const unsigned char *cdb_htp, *cdb_htab, *cdb_htend;
+ cdbi_t cdb_httodo;
+ const void *cdb_key;
+ cdbi_t cdb_klen;
+};
+
+int cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp,
+ const void *key, cdbi_t klen);
+int cdb_findnext(struct cdb_find *cdbfp);
+
+/* old simple interface */
+/* open file using standard routine, then: */
+int cdb_seek(int fd, const void *key, unsigned klen, cdbi_t *dlenp);
+int cdb_bread(int fd, void *buf, int len);
+
+/* cdb_make */
+
+struct cdb_make {
+ int cdb_fd; /* file descriptor */
+ /* private */
+ cdbi_t cdb_dpos; /* data position so far */
+ cdbi_t cdb_rcnt; /* record count so far */
+ char cdb_buf[4096]; /* write buffer */
+ char *cdb_bpos; /* current buf position */
+ struct cdb_rl *cdb_rec[256]; /* list of arrays of record infos */
+};
+
+
+
+int cdb_make_start(struct cdb_make *cdbmp, int fd);
+int cdb_make_add(struct cdb_make *cdbmp,
+ const void *key, cdbi_t klen,
+ const void *val, cdbi_t vlen);
+int cdb_make_exists(struct cdb_make *cdbmp,
+ const void *key, cdbi_t klen);
+int cdb_make_put(struct cdb_make *cdbmp,
+ const void *key, cdbi_t klen,
+ const void *val, cdbi_t vlen,
+ int flag);
+#define CDB_PUT_ADD 0 /* add unconditionnaly, like cdb_make_add() */
+#define CDB_PUT_REPLACE 1 /* replace: do not place to index OLD record */
+#define CDB_PUT_INSERT 2 /* add only if not already exists */
+#define CDB_PUT_WARN 3 /* add unconditionally but ret. 1 if exists */
+int cdb_make_finish(struct cdb_make *cdbmp);
+
+#endif /* include guard */
diff --git a/dirmngr/cdblib.c b/dirmngr/cdblib.c
new file mode 100644
index 0000000..9636b6c
--- /dev/null
+++ b/dirmngr/cdblib.c
@@ -0,0 +1,929 @@
+/* cdblib.c - all CDB library functions.
+ *
+ * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru.
+ * Public domain.
+ *
+ * Taken from tinycdb-0.73 and merged into one file for easier
+ * inclusion into Dirmngr. By Werner Koch <wk@gnupg.org> 2003-12-12.
+ */
+
+/* A cdb database is a single file used to map 'keys' to 'values',
+ having records of (key,value) pairs. File consists of 3 parts: toc
+ (table of contents), data and index (hash tables).
+
+ Toc has fixed length of 2048 bytes, containing 256 pointers to hash
+ tables inside index sections. Every pointer consists of position
+ of a hash table in bytes from the beginning of a file, and a size
+ of a hash table in entries, both are 4-bytes (32 bits) unsigned
+ integers in little-endian form. Hash table length may have zero
+ length, meaning that corresponding hash table is empty.
+
+ Right after toc section, data section follows without any
+ alingment. It consists of series of records, each is a key length,
+ value (data) length, key and value. Again, key and value length
+ are 4-byte unsigned integers. Each next record follows previous
+ without any special alignment.
+
+ After data section, index (hash tables) section follows. It should
+ be looked to in conjunction with toc section, where each of max 256
+ hash tables are defined. Index section consists of series of hash
+ tables, with starting position and length defined in toc section.
+ Every hash table is a sequence of records each holds two numbers:
+ key's hash value and record position inside data section (bytes
+ from the beginning of a file to first byte of key length starting
+ data record). If record position is zero, then this is an empty
+ hash table slot, pointed to nowhere.
+
+ CDB hash function is
+ hv = ((hv << 5) + hv) ^ c
+ for every single c byte of a key, starting with hv = 5381.
+
+ Toc section indexed by (hv % 256), i.e. hash value modulo 256
+ (number of entries in toc section).
+
+ In order to find a record, one should: first, compute the hash
+ value (hv) of a key. Second, look to hash table number hv modulo
+ 256. If it is empty, then there is no such key exists. If it is
+ not empty, then third, loop by slots inside that hash table,
+ starting from slot with number hv divided by 256 modulo length of
+ that table, or ((hv / 256) % htlen), searching for this hv in hash
+ table. Stop search on empty slot (if record position is zero) or
+ when all slots was probed (note cyclic search, jumping from end to
+ beginning of a table). When hash value in question is found in
+ hash table, look to key of corresponding record, comparing it with
+ key in question. If them of the same length and equals to each
+ other, then record is found, overwise, repeat with next hash table
+ slot. Note that there may be several records with the same key.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#ifdef _WIN32
+# include <windows.h>
+#else
+# include <sys/mman.h>
+# ifndef MAP_FAILED
+# define MAP_FAILED ((void*)-1)
+# endif
+#endif
+#include <sys/stat.h>
+
+#include "dirmngr-err.h"
+#include "cdb.h"
+
+#ifndef EPROTO
+# define EPROTO EINVAL
+#endif
+#ifndef SEEK_SET
+# define SEEK_SET 0
+#endif
+
+
+struct cdb_rec {
+ cdbi_t hval;
+ cdbi_t rpos;
+};
+
+struct cdb_rl {
+ struct cdb_rl *next;
+ cdbi_t cnt;
+ struct cdb_rec rec[254];
+};
+
+static int make_find(struct cdb_make *cdbmp,
+ const void *key, cdbi_t klen, cdbi_t hval,
+ struct cdb_rl **rlp);
+static int make_write(struct cdb_make *cdbmp,
+ const char *ptr, cdbi_t len);
+
+
+
+/* Initializes structure given by CDBP pointer and associates it with
+ the open file descriptor FD. Allocate memory for the structure
+ itself if needed and file open operation should be done by
+ application. File FD should be opened at least read-only, and
+ should be seekable. Routine returns 0 on success or negative value
+ on error. */
+int
+cdb_init(struct cdb *cdbp, int fd)
+{
+ struct stat st;
+ unsigned char *mem;
+#ifdef _WIN32
+ HANDLE hFile, hMapping;
+#else
+ unsigned int fsize;
+#endif
+
+ /* get file size */
+ if (fstat(fd, &st) < 0)
+ return -1;
+ /* trivial sanity check: at least toc should be here */
+ if (st.st_size < 2048) {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ /* memory-map file */
+#ifdef _WIN32
+# ifdef __MINGW32CE__
+ hFile = fd;
+# else
+ hFile = (HANDLE) _get_osfhandle(fd);
+# endif
+ if (hFile == (HANDLE) -1)
+ return -1;
+ hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+ if (!hMapping)
+ return -1;
+ mem = (unsigned char *)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
+ if (!mem)
+ return -1;
+ cdbp->cdb_mapping = hMapping;
+#else /*!_WIN32*/
+ fsize = (unsigned int)(st.st_size & 0xffffffffu);
+ mem = (unsigned char*)mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0);
+ if (mem == MAP_FAILED)
+ return -1;
+#endif /*!_WIN32*/
+
+ cdbp->cdb_fd = fd;
+ cdbp->cdb_fsize = st.st_size;
+ cdbp->cdb_mem = mem;
+
+#if 0
+ /* XXX don't know well about madvise syscall -- is it legal
+ to set different options for parts of one mmap() region?
+ There is also posix_madvise() exist, with POSIX_MADV_RANDOM etc...
+ */
+#ifdef MADV_RANDOM
+ /* set madvise() parameters. Ignore errors for now if system
+ doesn't support it */
+ madvise(mem, 2048, MADV_WILLNEED);
+ madvise(mem + 2048, cdbp->cdb_fsize - 2048, MADV_RANDOM);
+#endif
+#endif
+
+ cdbp->cdb_vpos = cdbp->cdb_vlen = 0;
+
+ return 0;
+}
+
+
+/* Frees the internal resources held by structure. Note that this
+ routine does not close the file. */
+void
+cdb_free(struct cdb *cdbp)
+{
+ if (cdbp->cdb_mem) {
+#ifdef _WIN32
+ UnmapViewOfFile ((void*) cdbp->cdb_mem);
+ CloseHandle (cdbp->cdb_mapping);
+ cdbp->cdb_mapping = NULL;
+#else
+ munmap((void*)cdbp->cdb_mem, cdbp->cdb_fsize);
+#endif /* _WIN32 */
+ cdbp->cdb_mem = NULL;
+ }
+ cdbp->cdb_fsize = 0;
+}
+
+
+/* Read data from cdb file, starting at position pos of length len,
+ placing result to buf. This routine may be used to get actual
+ value found by cdb_find() or other routines that returns position
+ and length of a data. Returns 0 on success or negative value on
+ error. */
+int
+cdb_read(const struct cdb *cdbp, void *buf, unsigned len, cdbi_t pos)
+{
+ if (pos > cdbp->cdb_fsize || cdbp->cdb_fsize - pos < len) {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ memcpy(buf, cdbp->cdb_mem + pos, len);
+ return 0;
+}
+
+
+/* Attempts to find a key given by (key,klen) parameters. If key
+ exists in database, routine returns 1 and places position and
+ length of value associated with this key to internal fields inside
+ cdbp structure, to be accessible by cdb_datapos() and
+ cdb_datalen(). If key is not in database, routines returns 0. On
+ error, negative value is returned. Note that using cdb_find() it
+ is possible to lookup only first record with a given key. */
+int
+cdb_find(struct cdb *cdbp, const void *key, cdbi_t klen)
+{
+ const unsigned char *htp; /* hash table pointer */
+ const unsigned char *htab; /* hash table */
+ const unsigned char *htend; /* end of hash table */
+ cdbi_t httodo; /* ht bytes left to look */
+ cdbi_t pos, n;
+
+ cdbi_t hval;
+
+ if (klen > cdbp->cdb_fsize) /* if key size is larger than file */
+ return 0;
+
+ hval = cdb_hash(key, klen);
+
+ /* find (pos,n) hash table to use */
+ /* first 2048 bytes (toc) are always available */
+ /* (hval % 256) * 8 */
+ htp = cdbp->cdb_mem + ((hval << 3) & 2047); /* index in toc (256x8) */
+ n = cdb_unpack(htp + 4); /* table size */
+ if (!n) /* empty table */
+ return 0; /* not found */
+ httodo = n << 3; /* bytes of htab to lookup */
+ pos = cdb_unpack(htp); /* htab position */
+ if (n > (cdbp->cdb_fsize >> 3) /* overflow of httodo ? */
+ || pos > cdbp->cdb_fsize /* htab start within file ? */
+ || httodo > cdbp->cdb_fsize - pos) /* entrie htab within file ? */
+ {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+
+ htab = cdbp->cdb_mem + pos; /* htab pointer */
+ htend = htab + httodo; /* after end of htab */
+ /* htab starting position: rest of hval modulo htsize, 8bytes per elt */
+ htp = htab + (((hval >> 8) % n) << 3);
+
+ for(;;) {
+ pos = cdb_unpack(htp + 4); /* record position */
+ if (!pos)
+ return 0;
+ if (cdb_unpack(htp) == hval) {
+ if (pos > cdbp->cdb_fsize - 8) { /* key+val lengths */
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ if (cdb_unpack(cdbp->cdb_mem + pos) == klen) {
+ if (cdbp->cdb_fsize - klen < pos + 8) {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ if (memcmp(key, cdbp->cdb_mem + pos + 8, klen) == 0) {
+ n = cdb_unpack(cdbp->cdb_mem + pos + 4);
+ pos += 8 + klen;
+ if (cdbp->cdb_fsize < n || cdbp->cdb_fsize - n < pos) {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ cdbp->cdb_vpos = pos;
+ cdbp->cdb_vlen = n;
+ return 1;
+ }
+ }
+ }
+ httodo -= 8;
+ if (!httodo)
+ return 0;
+ if ((htp += 8) >= htend)
+ htp = htab;
+ }
+
+}
+
+
+
+/* Sequential-find routines that used separate structure. It is
+ possible to have many than one record with the same key in a
+ database, and these routines allows to enumerate all them.
+ cdb_findinit() initializes search structure pointed to by cdbfp.
+ It will return negative value on error or 0 on success. cdb_find­
+ next() attempts to find next matching key, setting value position
+ and length in cdbfp structure. It will return positive value if
+ given key was found, 0 if there is no more such key(s), or negative
+ value on error. To access value position and length after
+ successeful call to cdb_findnext() (when it returned positive
+ result), use cdb_datapos() and cdb_datalen() macros with cdbp
+ pointer. It is error to use cdb_findnext() after it returned 0 or
+ error condition. These routines is a bit slower than
+ cdb_find().
+
+ Setting KEY to NULL will start a sequential search through the
+ entire DB.
+*/
+int
+cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp,
+ const void *key, cdbi_t klen)
+{
+ cdbi_t n, pos;
+
+ cdbfp->cdb_cdbp = cdbp;
+ cdbfp->cdb_key = key;
+ cdbfp->cdb_klen = klen;
+ cdbfp->cdb_hval = key? cdb_hash(key, klen) : 0;
+
+ if (key)
+ {
+ cdbfp->cdb_htp = cdbp->cdb_mem + ((cdbfp->cdb_hval << 3) & 2047);
+ n = cdb_unpack(cdbfp->cdb_htp + 4);
+ cdbfp->cdb_httodo = n << 3; /* Set to size of hash table. */
+ if (!n)
+ return 0; /* The hash table is empry. */
+ pos = cdb_unpack(cdbfp->cdb_htp);
+ if (n > (cdbp->cdb_fsize >> 3)
+ || pos > cdbp->cdb_fsize
+ || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos)
+ {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+
+ cdbfp->cdb_htab = cdbp->cdb_mem + pos;
+ cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo;
+ cdbfp->cdb_htp = cdbfp->cdb_htab + (((cdbfp->cdb_hval >> 8) % n) << 3);
+ }
+ else /* Walk over all entries. */
+ {
+ cdbfp->cdb_hval = 0;
+ /* Force stepping in findnext. */
+ cdbfp->cdb_htp = cdbfp->cdb_htend = cdbp->cdb_mem;
+ }
+ return 0;
+}
+
+
+/* See cdb_findinit. */
+int
+cdb_findnext(struct cdb_find *cdbfp)
+{
+ cdbi_t pos, n;
+ struct cdb *cdbp = cdbfp->cdb_cdbp;
+
+ if (cdbfp->cdb_key)
+ {
+ while(cdbfp->cdb_httodo) {
+ pos = cdb_unpack(cdbfp->cdb_htp + 4);
+ if (!pos)
+ return 0;
+ n = cdb_unpack(cdbfp->cdb_htp) == cdbfp->cdb_hval;
+ if ((cdbfp->cdb_htp += 8) >= cdbfp->cdb_htend)
+ cdbfp->cdb_htp = cdbfp->cdb_htab;
+ cdbfp->cdb_httodo -= 8;
+ if (n) {
+ if (pos > cdbp->cdb_fsize - 8) {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ if (cdb_unpack(cdbp->cdb_mem + pos) == cdbfp->cdb_klen) {
+ if (cdbp->cdb_fsize - cdbfp->cdb_klen < pos + 8) {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ if (memcmp(cdbfp->cdb_key,
+ cdbp->cdb_mem + pos + 8, cdbfp->cdb_klen) == 0) {
+ n = cdb_unpack(cdbp->cdb_mem + pos + 4);
+ pos += 8 + cdbfp->cdb_klen;
+ if (cdbp->cdb_fsize < n || cdbp->cdb_fsize - n < pos) {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ cdbp->cdb_vpos = pos;
+ cdbp->cdb_vlen = n;
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ else /* Walk over all entries. */
+ {
+ do
+ {
+ while (cdbfp->cdb_htp >= cdbfp->cdb_htend)
+ {
+ if (cdbfp->cdb_hval > 255)
+ return 0; /* No more items. */
+
+ cdbfp->cdb_htp = cdbp->cdb_mem + cdbfp->cdb_hval * 8;
+ cdbfp->cdb_hval++; /* Advance for next round. */
+ pos = cdb_unpack (cdbfp->cdb_htp); /* Offset of table. */
+ n = cdb_unpack (cdbfp->cdb_htp + 4); /* Number of entries. */
+ cdbfp->cdb_httodo = n * 8; /* Size of table. */
+ if (n > (cdbp->cdb_fsize / 8)
+ || pos > cdbp->cdb_fsize
+ || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos)
+ {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+
+ cdbfp->cdb_htab = cdbp->cdb_mem + pos;
+ cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo;
+ cdbfp->cdb_htp = cdbfp->cdb_htab;
+ }
+
+ pos = cdb_unpack (cdbfp->cdb_htp + 4); /* Offset of record. */
+ cdbfp->cdb_htp += 8;
+ }
+ while (!pos);
+ if (pos > cdbp->cdb_fsize - 8)
+ {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+
+ cdbp->cdb_kpos = pos + 8;
+ cdbp->cdb_klen = cdb_unpack(cdbp->cdb_mem + pos);
+ cdbp->cdb_vpos = pos + 8 + cdbp->cdb_klen;
+ cdbp->cdb_vlen = cdb_unpack(cdbp->cdb_mem + pos + 4);
+ n = 8 + cdbp->cdb_klen + cdbp->cdb_vlen;
+ if ( pos > cdbp->cdb_fsize || pos > cdbp->cdb_fsize - n)
+ {
+ gpg_err_set_errno (EPROTO);
+ return -1;
+ }
+ return 1; /* Found. */
+ }
+ return 0;
+}
+
+/* Read a chunk from file, ignoring interrupts (EINTR) */
+int
+cdb_bread(int fd, void *buf, int len)
+{
+ int l;
+ while(len > 0) {
+ do l = read(fd, buf, len);
+ while(l < 0 && errno == EINTR);
+ if (l <= 0) {
+ if (!l)
+ gpg_err_set_errno (EIO);
+ return -1;
+ }
+ buf = (char*)buf + l;
+ len -= l;
+ }
+ return 0;
+}
+
+/* Find a given key in cdb file, seek a file pointer to it's value and
+ place data length to *dlenp. */
+int
+cdb_seek(int fd, const void *key, unsigned klen, cdbi_t *dlenp)
+{
+ cdbi_t htstart; /* hash table start position */
+ cdbi_t htsize; /* number of elements in a hash table */
+ cdbi_t httodo; /* hash table elements left to look */
+ cdbi_t hti; /* hash table index */
+ cdbi_t pos; /* position in a file */
+ cdbi_t hval; /* key's hash value */
+ unsigned char rbuf[64]; /* read buffer */
+ int needseek = 1; /* if we should seek to a hash slot */
+
+ hval = cdb_hash(key, klen);
+ pos = (hval & 0xff) << 3; /* position in TOC */
+ /* read the hash table parameters */
+ if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0)
+ return -1;
+ if ((htsize = cdb_unpack(rbuf + 4)) == 0)
+ return 0;
+ hti = (hval >> 8) % htsize; /* start position in hash table */
+ httodo = htsize;
+ htstart = cdb_unpack(rbuf);
+
+ for(;;) {
+ if (needseek && lseek(fd, htstart + (hti << 3), SEEK_SET) < 0)
+ return -1;
+ if (cdb_bread(fd, rbuf, 8) < 0)
+ return -1;
+ if ((pos = cdb_unpack(rbuf + 4)) == 0) /* not found */
+ return 0;
+
+ if (cdb_unpack(rbuf) != hval) /* hash value not matched */
+ needseek = 0;
+ else { /* hash value matched */
+ if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0)
+ return -1;
+ if (cdb_unpack(rbuf) == klen) { /* key length matches */
+ /* read the key from file and compare with wanted */
+ cdbi_t l = klen, c;
+ const char *k = (const char*)key;
+ if (*dlenp)
+ *dlenp = cdb_unpack(rbuf + 4); /* save value length */
+ for(;;) {
+ if (!l) /* the whole key read and matches, return */
+ return 1;
+ c = l > sizeof(rbuf) ? sizeof(rbuf) : l;
+ if (cdb_bread(fd, rbuf, c) < 0)
+ return -1;
+ if (memcmp(rbuf, k, c) != 0) /* no, it differs, stop here */
+ break;
+ k += c; l -= c;
+ }
+ }
+ needseek = 1; /* we're looked to other place, should seek back */
+ }
+ if (!--httodo)
+ return 0;
+ if (++hti == htsize) {
+ hti = htstart;
+ needseek = 1;
+ }
+ }
+}
+
+cdbi_t
+cdb_unpack(const unsigned char buf[4])
+{
+ cdbi_t n = buf[3];
+ n <<= 8; n |= buf[2];
+ n <<= 8; n |= buf[1];
+ n <<= 8; n |= buf[0];
+ return n;
+}
+
+/* Add record with key (KEY,KLEN) and value (VAL,VLEN) to a database.
+ Returns 0 on success or negative value on error. Note that this
+ routine does not checks if given key already exists, but cdb_find()
+ will not see second record with the same key. It is not possible
+ to continue building a database if cdb_make_add() returned an error
+ indicator. */
+int
+cdb_make_add(struct cdb_make *cdbmp,
+ const void *key, cdbi_t klen,
+ const void *val, cdbi_t vlen)
+{
+ unsigned char rlen[8];
+ cdbi_t hval;
+ struct cdb_rl *rl;
+ if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) ||
+ vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) {
+ gpg_err_set_errno (ENOMEM);
+ return -1;
+ }
+ hval = cdb_hash(key, klen);
+ rl = cdbmp->cdb_rec[hval&255];
+ if (!rl || rl->cnt >= sizeof(rl->rec)/sizeof(rl->rec[0])) {
+ rl = (struct cdb_rl*)malloc(sizeof(struct cdb_rl));
+ if (!rl) {
+ gpg_err_set_errno (ENOMEM);
+ return -1;
+ }
+ rl->cnt = 0;
+ rl->next = cdbmp->cdb_rec[hval&255];
+ cdbmp->cdb_rec[hval&255] = rl;
+ }
+ rl->rec[rl->cnt].hval = hval;
+ rl->rec[rl->cnt].rpos = cdbmp->cdb_dpos;
+ ++rl->cnt;
+ ++cdbmp->cdb_rcnt;
+ cdb_pack(klen, rlen);
+ cdb_pack(vlen, rlen + 4);
+ if (make_write(cdbmp, rlen, 8) < 0 ||
+ make_write(cdbmp, key, klen) < 0 ||
+ make_write(cdbmp, val, vlen) < 0)
+ return -1;
+ return 0;
+}
+
+int
+cdb_make_put(struct cdb_make *cdbmp,
+ const void *key, cdbi_t klen,
+ const void *val, cdbi_t vlen,
+ int flags)
+{
+ unsigned char rlen[8];
+ cdbi_t hval = cdb_hash(key, klen);
+ struct cdb_rl *rl;
+ int c, r;
+
+ switch(flags) {
+ case CDB_PUT_REPLACE:
+ case CDB_PUT_INSERT:
+ case CDB_PUT_WARN:
+ c = make_find(cdbmp, key, klen, hval, &rl);
+ if (c < 0)
+ return -1;
+ if (c) {
+ if (flags == CDB_PUT_INSERT) {
+ gpg_err_set_errno (EEXIST);
+ return 1;
+ }
+ else if (flags == CDB_PUT_REPLACE) {
+ --c;
+ r = 1;
+ break;
+ }
+ else
+ r = 1;
+ }
+ /* fall */
+
+ case CDB_PUT_ADD:
+ rl = cdbmp->cdb_rec[hval&255];
+ if (!rl || rl->cnt >= sizeof(rl->rec)/sizeof(rl->rec[0])) {
+ rl = (struct cdb_rl*)malloc(sizeof(struct cdb_rl));
+ if (!rl) {
+ gpg_err_set_errno (ENOMEM);
+ return -1;
+ }
+ rl->cnt = 0;
+ rl->next = cdbmp->cdb_rec[hval&255];
+ cdbmp->cdb_rec[hval&255] = rl;
+ }
+ c = rl->cnt;
+ r = 0;
+ break;
+
+ default:
+ gpg_err_set_errno (EINVAL);
+ return -1;
+ }
+
+ if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) ||
+ vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) {
+ gpg_err_set_errno (ENOMEM);
+ return -1;
+ }
+ rl->rec[c].hval = hval;
+ rl->rec[c].rpos = cdbmp->cdb_dpos;
+ if (c == rl->cnt) {
+ ++rl->cnt;
+ ++cdbmp->cdb_rcnt;
+ }
+ cdb_pack(klen, rlen);
+ cdb_pack(vlen, rlen + 4);
+ if (make_write(cdbmp, rlen, 8) < 0 ||
+ make_write(cdbmp, key, klen) < 0 ||
+ make_write(cdbmp, val, vlen) < 0)
+ return -1;
+ return r;
+}
+
+
+static int
+match(int fd, cdbi_t pos, const char *key, cdbi_t klen)
+{
+ unsigned char buf[64]; /*XXX cdb_buf may be used here instead */
+ if (lseek(fd, pos, SEEK_SET) < 0 || read(fd, buf, 8) != 8)
+ return -1;
+ if (cdb_unpack(buf) != klen)
+ return 0;
+
+ while(klen > sizeof(buf)) {
+ if (read(fd, buf, sizeof(buf)) != sizeof(buf))
+ return -1;
+ if (memcmp(buf, key, sizeof(buf)) != 0)
+ return 0;
+ key += sizeof(buf);
+ klen -= sizeof(buf);
+ }
+ if (klen) {
+ if (read(fd, buf, klen) != klen)
+ return -1;
+ if (memcmp(buf, key, klen) != 0)
+ return 0;
+ }
+ return 1;
+}
+
+
+static int
+make_find (struct cdb_make *cdbmp,
+ const void *key, cdbi_t klen, cdbi_t hval,
+ struct cdb_rl **rlp)
+{
+ struct cdb_rl *rl = cdbmp->cdb_rec[hval&255];
+ int r, i;
+ int seeked = 0;
+ while(rl) {
+ for(i = rl->cnt - 1; i >= 0; --i) { /* search backward */
+ if (rl->rec[i].hval != hval)
+ continue;
+ /*XXX this explicit flush may be unnecessary having
+ * smarter match() that looks to cdb_buf too, but
+ * most of a time here spent in finding hash values
+ * (above), not keys */
+ if (cdbmp->cdb_bpos != cdbmp->cdb_buf) {
+ if (write(cdbmp->cdb_fd, cdbmp->cdb_buf,
+ cdbmp->cdb_bpos - cdbmp->cdb_buf) < 0)
+ return -1;
+ cdbmp->cdb_bpos = cdbmp->cdb_buf;
+ }
+ seeked = 1;
+ r = match(cdbmp->cdb_fd, rl->rec[i].rpos, key, klen);
+ if (!r)
+ continue;
+ if (r < 0)
+ return -1;
+ if (lseek(cdbmp->cdb_fd, cdbmp->cdb_dpos, SEEK_SET) < 0)
+ return -1;
+ if (rlp)
+ *rlp = rl;
+ return i + 1;
+ }
+ rl = rl->next;
+ }
+ if (seeked && lseek(cdbmp->cdb_fd, cdbmp->cdb_dpos, SEEK_SET) < 0)
+ return -1;
+ return 0;
+}
+
+int
+cdb_make_exists(struct cdb_make *cdbmp,
+ const void *key, cdbi_t klen)
+{
+ return make_find(cdbmp, key, klen, cdb_hash(key, klen), NULL);
+}
+
+
+void
+cdb_pack(cdbi_t num, unsigned char buf[4])
+{
+ buf[0] = num & 255; num >>= 8;
+ buf[1] = num & 255; num >>= 8;
+ buf[2] = num & 255;
+ buf[3] = num >> 8;
+}
+
+
+/* Initializes structure to create a database. File FD should be
+ opened read-write and should be seekable. Returns 0 on success or
+ negative value on error. */
+int
+cdb_make_start(struct cdb_make *cdbmp, int fd)
+{
+ memset (cdbmp, 0, sizeof *cdbmp);
+ cdbmp->cdb_fd = fd;
+ cdbmp->cdb_dpos = 2048;
+ cdbmp->cdb_bpos = cdbmp->cdb_buf + 2048;
+ return 0;
+}
+
+
+static int
+ewrite(int fd, const char *buf, int len)
+{
+ while(len) {
+ int l = write(fd, buf, len);
+ if (l < 0 && errno != EINTR)
+ return -1;
+ if (l > 0)
+ {
+ len -= l;
+ buf += l;
+ }
+ }
+ return 0;
+}
+
+static int
+make_write(struct cdb_make *cdbmp, const char *ptr, cdbi_t len)
+{
+ cdbi_t l = sizeof(cdbmp->cdb_buf) - (cdbmp->cdb_bpos - cdbmp->cdb_buf);
+ cdbmp->cdb_dpos += len;
+ if (len > l) {
+ memcpy(cdbmp->cdb_bpos, ptr, l);
+ if (ewrite(cdbmp->cdb_fd, cdbmp->cdb_buf, sizeof(cdbmp->cdb_buf)) < 0)
+ return -1;
+ ptr += l; len -= l;
+ l = len / sizeof(cdbmp->cdb_buf);
+ if (l) {
+ l *= sizeof(cdbmp->cdb_buf);
+ if (ewrite(cdbmp->cdb_fd, ptr, l) < 0)
+ return -1;
+ ptr += l; len -= l;
+ }
+ cdbmp->cdb_bpos = cdbmp->cdb_buf;
+ }
+ if (len) {
+ memcpy(cdbmp->cdb_bpos, ptr, len);
+ cdbmp->cdb_bpos += len;
+ }
+ return 0;
+}
+
+static int
+cdb_make_finish_internal(struct cdb_make *cdbmp)
+{
+ cdbi_t hcnt[256]; /* hash table counts */
+ cdbi_t hpos[256]; /* hash table positions */
+ struct cdb_rec *htab;
+ unsigned char *p;
+ struct cdb_rl *rl;
+ cdbi_t hsize;
+ unsigned t, i;
+
+ if (((0xffffffff - cdbmp->cdb_dpos) >> 3) < cdbmp->cdb_rcnt) {
+ gpg_err_set_errno (ENOMEM);
+ return -1;
+ }
+
+ /* count htab sizes and reorder reclists */
+ hsize = 0;
+ for (t = 0; t < 256; ++t) {
+ struct cdb_rl *rlt = NULL;
+ i = 0;
+ rl = cdbmp->cdb_rec[t];
+ while(rl) {
+ struct cdb_rl *rln = rl->next;
+ rl->next = rlt;
+ rlt = rl;
+ i += rl->cnt;
+ rl = rln;
+ }
+ cdbmp->cdb_rec[t] = rlt;
+ if (hsize < (hcnt[t] = i << 1))
+ hsize = hcnt[t];
+ }
+
+ /* allocate memory to hold max htable */
+ htab = (struct cdb_rec*)malloc((hsize + 2) * sizeof(struct cdb_rec));
+ if (!htab) {
+ gpg_err_set_errno (ENOENT);
+ return -1;
+ }
+ p = (unsigned char *)htab;
+ htab += 2;
+
+ /* build hash tables */
+ for (t = 0; t < 256; ++t) {
+ cdbi_t len, hi;
+ hpos[t] = cdbmp->cdb_dpos;
+ if ((len = hcnt[t]) == 0)
+ continue;
+ for (i = 0; i < len; ++i)
+ htab[i].hval = htab[i].rpos = 0;
+ for (rl = cdbmp->cdb_rec[t]; rl; rl = rl->next)
+ for (i = 0; i < rl->cnt; ++i) {
+ hi = (rl->rec[i].hval >> 8) % len;
+ while(htab[hi].rpos)
+ if (++hi == len)
+ hi = 0;
+ htab[hi] = rl->rec[i];
+ }
+ for (i = 0; i < len; ++i) {
+ cdb_pack(htab[i].hval, p + (i << 3));
+ cdb_pack(htab[i].rpos, p + (i << 3) + 4);
+ }
+ if (make_write(cdbmp, p, len << 3) < 0) {
+ free(p);
+ return -1;
+ }
+ }
+ free(p);
+ if (cdbmp->cdb_bpos != cdbmp->cdb_buf &&
+ ewrite(cdbmp->cdb_fd, cdbmp->cdb_buf,
+ cdbmp->cdb_bpos - cdbmp->cdb_buf) != 0)
+ return -1;
+ p = cdbmp->cdb_buf;
+ for (t = 0; t < 256; ++t) {
+ cdb_pack(hpos[t], p + (t << 3));
+ cdb_pack(hcnt[t], p + (t << 3) + 4);
+ }
+ if (lseek(cdbmp->cdb_fd, 0, 0) != 0 ||
+ ewrite(cdbmp->cdb_fd, p, 2048) != 0)
+ return -1;
+
+ return 0;
+}
+
+static void
+cdb_make_free(struct cdb_make *cdbmp)
+{
+ unsigned t;
+ for(t = 0; t < 256; ++t) {
+ struct cdb_rl *rl = cdbmp->cdb_rec[t];
+ while(rl) {
+ struct cdb_rl *tm = rl;
+ rl = rl->next;
+ free(tm);
+ }
+ }
+}
+
+
+
+/* Finalizes database file, constructing all needed indexes, and frees
+ memory structures. It does not close the file descriptor. Returns
+ 0 on success or a negative value on error. */
+int
+cdb_make_finish(struct cdb_make *cdbmp)
+{
+ int r = cdb_make_finish_internal(cdbmp);
+ cdb_make_free(cdbmp);
+ return r;
+}
+
+
+cdbi_t
+cdb_hash(const void *buf, cdbi_t len)
+{
+ register const unsigned char *p = (const unsigned char *)buf;
+ register const unsigned char *end = p + len;
+ register cdbi_t hash = 5381; /* start value */
+ while (p < end)
+ hash = (hash + (hash << 5)) ^ *p++;
+ return hash;
+}
diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c
new file mode 100644
index 0000000..969b3ec
--- /dev/null
+++ b/dirmngr/certcache.c
@@ -0,0 +1,1394 @@
+/* certcache.c - Certificate caching
+ * Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <npth.h>
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "crlfetch.h"
+#include "certcache.h"
+
+
+#define MAX_EXTRA_CACHED_CERTS 1000
+
+/* Constants used to classify search patterns. */
+enum pattern_class
+ {
+ PATTERN_UNKNOWN = 0,
+ PATTERN_EMAIL,
+ PATTERN_EMAIL_SUBSTR,
+ PATTERN_FINGERPRINT16,
+ PATTERN_FINGERPRINT20,
+ PATTERN_SHORT_KEYID,
+ PATTERN_LONG_KEYID,
+ PATTERN_SUBJECT,
+ PATTERN_SERIALNO,
+ PATTERN_SERIALNO_ISSUER,
+ PATTERN_ISSUER,
+ PATTERN_SUBSTR
+ };
+
+
+/* A certificate cache item. This consists of a the KSBA cert object
+ and some meta data for easier lookup. We use a hash table to keep
+ track of all items and use the (randomly distributed) first byte of
+ the fingerprint directly as the hash which makes it pretty easy. */
+struct cert_item_s
+{
+ struct cert_item_s *next; /* Next item with the same hash value. */
+ ksba_cert_t cert; /* The KSBA cert object or NULL is this is
+ not a valid item. */
+ unsigned char fpr[20]; /* The fingerprint of this object. */
+ char *issuer_dn; /* The malloced issuer DN. */
+ ksba_sexp_t sn; /* The malloced serial number */
+ char *subject_dn; /* The malloced subject DN - maybe NULL. */
+ struct
+ {
+ unsigned int loaded:1; /* It has been explicitly loaded. */
+ unsigned int trusted:1; /* This is a trusted root certificate. */
+ } flags;
+};
+typedef struct cert_item_s *cert_item_t;
+
+/* The actual cert cache consisting of 256 slots for items indexed by
+ the first byte of the fingerprint. */
+static cert_item_t cert_cache[256];
+
+/* This is the global cache_lock variable. In general looking is not
+ needed but it would take extra efforts to make sure that no
+ indirect use of npth functions is done, so we simply lock it
+ always. Note: We can't use static initialization, as that is not
+ available through w32-pth. */
+static npth_rwlock_t cert_cache_lock;
+
+/* Flag to track whether the cache has been initialized. */
+static int initialization_done;
+
+/* Total number of certificates loaded during initialization and
+ cached during operation. */
+static unsigned int total_loaded_certificates;
+static unsigned int total_extra_certificates;
+
+
+
+/* Helper to do the cache locking. */
+static void
+init_cache_lock (void)
+{
+ int err;
+
+ err = npth_rwlock_init (&cert_cache_lock, NULL);
+ if (err)
+ log_fatal (_("can't initialize certificate cache lock: %s\n"),
+ strerror (err));
+}
+
+static void
+acquire_cache_read_lock (void)
+{
+ int err;
+
+ err = npth_rwlock_rdlock (&cert_cache_lock);
+ if (err)
+ log_fatal (_("can't acquire read lock on the certificate cache: %s\n"),
+ strerror (err));
+}
+
+static void
+acquire_cache_write_lock (void)
+{
+ int err;
+
+ err = npth_rwlock_wrlock (&cert_cache_lock);
+ if (err)
+ log_fatal (_("can't acquire write lock on the certificate cache: %s\n"),
+ strerror (err));
+}
+
+static void
+release_cache_lock (void)
+{
+ int err;
+
+ err = npth_rwlock_unlock (&cert_cache_lock);
+ if (err)
+ log_fatal (_("can't release lock on the certificate cache: %s\n"),
+ strerror (err));
+}
+
+
+/* Return false if both serial numbers match. Can't be used for
+ sorting. */
+static int
+compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 )
+{
+ unsigned char *a = serial1;
+ unsigned char *b = serial2;
+ return cmp_simple_canon_sexp (a, b);
+}
+
+
+
+/* Return a malloced canonical S-Expression with the serialnumber
+ converted from the hex string HEXSN. Return NULL on memory
+ error. */
+ksba_sexp_t
+hexsn_to_sexp (const char *hexsn)
+{
+ char *buffer, *p;
+ size_t len;
+ char numbuf[40];
+
+ len = unhexify (NULL, hexsn);
+ snprintf (numbuf, sizeof numbuf, "(%u:", (unsigned int)len);
+ buffer = xtrymalloc (strlen (numbuf) + len + 2 );
+ if (!buffer)
+ return NULL;
+ p = stpcpy (buffer, numbuf);
+ len = unhexify (p, hexsn);
+ p[len] = ')';
+ p[len+1] = 0;
+
+ return buffer;
+}
+
+
+/* Compute the fingerprint of the certificate CERT and put it into
+ the 20 bytes large buffer DIGEST. Return address of this buffer. */
+unsigned char *
+cert_compute_fpr (ksba_cert_t cert, unsigned char *digest)
+{
+ gpg_error_t err;
+ gcry_md_hd_t md;
+
+ err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+ if (err)
+ log_fatal ("gcry_md_open failed: %s\n", gpg_strerror (err));
+
+ err = ksba_cert_hash (cert, 0, HASH_FNC, md);
+ if (err)
+ {
+ log_error ("oops: ksba_cert_hash failed: %s\n", gpg_strerror (err));
+ memset (digest, 0xff, 20); /* Use a dummy value. */
+ }
+ else
+ {
+ gcry_md_final (md);
+ memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20);
+ }
+ gcry_md_close (md);
+ return digest;
+}
+
+
+/* Cleanup one slot. This releases all resourses but keeps the actual
+ slot in the cache marked for reuse. */
+static void
+clean_cache_slot (cert_item_t ci)
+{
+ ksba_cert_t cert;
+
+ if (!ci->cert)
+ return; /* Already cleaned. */
+
+ ksba_free (ci->sn);
+ ci->sn = NULL;
+ ksba_free (ci->issuer_dn);
+ ci->issuer_dn = NULL;
+ ksba_free (ci->subject_dn);
+ ci->subject_dn = NULL;
+ cert = ci->cert;
+ ci->cert = NULL;
+
+ ksba_cert_release (cert);
+}
+
+
+/* Put the certificate CERT into the cache. It is assumed that the
+ cache is locked while this function is called. If FPR_BUFFER is not
+ NULL the fingerprint of the certificate will be stored there.
+ FPR_BUFFER neds to point to a buffer of at least 20 bytes. The
+ fingerprint will be stored on success or when the function returns
+ gpg_err_code(GPG_ERR_DUP_VALUE). */
+static gpg_error_t
+put_cert (ksba_cert_t cert, int is_loaded, int is_trusted, void *fpr_buffer)
+{
+ unsigned char help_fpr_buffer[20], *fpr;
+ cert_item_t ci;
+
+ fpr = fpr_buffer? fpr_buffer : &help_fpr_buffer;
+
+ /* If we already reached the caching limit, drop a couple of certs
+ from the cache. Our dropping strategy is simple: We keep a
+ static index counter and use this to start looking for
+ certificates, then we drop 5 percent of the oldest certificates
+ starting at that index. For a large cache this is a fair way of
+ removing items. An LRU strategy would be better of course.
+ Because we append new entries to the head of the list and we want
+ to remove old ones first, we need to do this from the tail. The
+ implementation is not very efficient but compared to the long
+ time it takes to retrieve a certifciate from an external resource
+ it seems to be reasonable. */
+ if (!is_loaded && total_extra_certificates >= MAX_EXTRA_CACHED_CERTS)
+ {
+ static int idx;
+ cert_item_t ci_mark;
+ int i;
+ unsigned int drop_count;
+
+ drop_count = MAX_EXTRA_CACHED_CERTS / 20;
+ if (drop_count < 2)
+ drop_count = 2;
+
+ log_info (_("dropping %u certificates from the cache\n"), drop_count);
+ assert (idx < 256);
+ for (i=idx; drop_count; i = ((i+1)%256))
+ {
+ ci_mark = NULL;
+ for (ci = cert_cache[i]; ci; ci = ci->next)
+ if (ci->cert && !ci->flags.loaded)
+ ci_mark = ci;
+ if (ci_mark)
+ {
+ clean_cache_slot (ci_mark);
+ drop_count--;
+ total_extra_certificates--;
+ }
+ }
+ if (i==idx)
+ idx++;
+ else
+ idx = i;
+ idx %= 256;
+ }
+
+ cert_compute_fpr (cert, fpr);
+ for (ci=cert_cache[*fpr]; ci; ci = ci->next)
+ if (ci->cert && !memcmp (ci->fpr, fpr, 20))
+ return gpg_error (GPG_ERR_DUP_VALUE);
+ /* Try to reuse an existing entry. */
+ for (ci=cert_cache[*fpr]; ci; ci = ci->next)
+ if (!ci->cert)
+ break;
+ if (!ci)
+ { /* No: Create a new entry. */
+ ci = xtrycalloc (1, sizeof *ci);
+ if (!ci)
+ return gpg_error_from_errno (errno);
+ ci->next = cert_cache[*fpr];
+ cert_cache[*fpr] = ci;
+ }
+ else
+ memset (&ci->flags, 0, sizeof ci->flags);
+
+ ksba_cert_ref (cert);
+ ci->cert = cert;
+ memcpy (ci->fpr, fpr, 20);
+ ci->sn = ksba_cert_get_serial (cert);
+ ci->issuer_dn = ksba_cert_get_issuer (cert, 0);
+ if (!ci->issuer_dn || !ci->sn)
+ {
+ clean_cache_slot (ci);
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ ci->subject_dn = ksba_cert_get_subject (cert, 0);
+ ci->flags.loaded = !!is_loaded;
+ ci->flags.trusted = !!is_trusted;
+
+ if (is_loaded)
+ total_loaded_certificates++;
+ else
+ total_extra_certificates++;
+
+ return 0;
+}
+
+
+/* Load certificates from the directory DIRNAME. All certificates
+ matching the pattern "*.crt" or "*.der" are loaded. We assume that
+ certificates are DER encoded and not PEM encapsulated. The cache
+ should be in a locked state when calling this fucntion. */
+static gpg_error_t
+load_certs_from_dir (const char *dirname, int are_trusted)
+{
+ gpg_error_t err;
+ DIR *dir;
+ struct dirent *ep;
+ char *p;
+ size_t n;
+ estream_t fp;
+ ksba_reader_t reader;
+ ksba_cert_t cert;
+ char *fname = NULL;
+
+ dir = opendir (dirname);
+ if (!dir)
+ {
+ if (opt.system_daemon)
+ log_info (_("can't access directory '%s': %s\n"),
+ dirname, strerror (errno));
+ return 0; /* We do not consider this a severe error. */
+ }
+
+ while ( (ep=readdir (dir)) )
+ {
+ p = ep->d_name;
+ if (*p == '.' || !*p)
+ continue; /* Skip any hidden files and invalid entries. */
+ n = strlen (p);
+ if ( n < 5 || (strcmp (p+n-4,".crt") && strcmp (p+n-4,".der")))
+ continue; /* Not the desired "*.crt" or "*.der" pattern. */
+
+ xfree (fname);
+ fname = make_filename (dirname, p, NULL);
+ fp = es_fopen (fname, "rb");
+ if (!fp)
+ {
+ log_error (_("can't open '%s': %s\n"),
+ fname, strerror (errno));
+ continue;
+ }
+
+ err = create_estream_ksba_reader (&reader, fp);
+ if (err)
+ {
+ es_fclose (fp);
+ continue;
+ }
+
+ err = ksba_cert_new (&cert);
+ if (!err)
+ err = ksba_cert_read_der (cert, reader);
+ ksba_reader_release (reader);
+ es_fclose (fp);
+ if (err)
+ {
+ log_error (_("can't parse certificate '%s': %s\n"),
+ fname, gpg_strerror (err));
+ ksba_cert_release (cert);
+ continue;
+ }
+
+ err = put_cert (cert, 1, are_trusted, NULL);
+ if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
+ log_info (_("certificate '%s' already cached\n"), fname);
+ else if (!err)
+ {
+ if (are_trusted)
+ log_info (_("trusted certificate '%s' loaded\n"), fname);
+ else
+ log_info (_("certificate '%s' loaded\n"), fname);
+ if (opt.verbose)
+ {
+ p = get_fingerprint_hexstring_colon (cert);
+ log_info (_(" SHA1 fingerprint = %s\n"), p);
+ xfree (p);
+
+ cert_log_name (_(" issuer ="), cert);
+ cert_log_subject (_(" subject ="), cert);
+ }
+ }
+ else
+ log_error (_("error loading certificate '%s': %s\n"),
+ fname, gpg_strerror (err));
+ ksba_cert_release (cert);
+ }
+
+ xfree (fname);
+ closedir (dir);
+ return 0;
+}
+
+
+/* Initialize the certificate cache if not yet done. */
+void
+cert_cache_init (void)
+{
+ char *dname;
+
+ if (initialization_done)
+ return;
+ init_cache_lock ();
+ acquire_cache_write_lock ();
+
+ dname = make_filename (opt.homedir, "trusted-certs", NULL);
+ load_certs_from_dir (dname, 1);
+ xfree (dname);
+
+ dname = make_filename (opt.homedir_data, "extra-certs", NULL);
+ load_certs_from_dir (dname, 0);
+ xfree (dname);
+
+ initialization_done = 1;
+ release_cache_lock ();
+
+ cert_cache_print_stats ();
+}
+
+/* Deinitialize the certificate cache. With FULL set to true even the
+ unused certificate slots are released. */
+void
+cert_cache_deinit (int full)
+{
+ cert_item_t ci, ci2;
+ int i;
+
+ if (!initialization_done)
+ return;
+
+ acquire_cache_write_lock ();
+
+ for (i=0; i < 256; i++)
+ for (ci=cert_cache[i]; ci; ci = ci->next)
+ clean_cache_slot (ci);
+
+ if (full)
+ {
+ for (i=0; i < 256; i++)
+ {
+ for (ci=cert_cache[i]; ci; ci = ci2)
+ {
+ ci2 = ci->next;
+ xfree (ci);
+ }
+ cert_cache[i] = NULL;
+ }
+ }
+
+ total_loaded_certificates = 0;
+ total_extra_certificates = 0;
+ initialization_done = 0;
+ release_cache_lock ();
+}
+
+/* Print some statistics to the log file. */
+void
+cert_cache_print_stats (void)
+{
+ log_info (_("permanently loaded certificates: %u\n"),
+ total_loaded_certificates);
+ log_info (_(" runtime cached certificates: %u\n"),
+ total_extra_certificates);
+}
+
+
+/* Put CERT into the certificate cache. */
+gpg_error_t
+cache_cert (ksba_cert_t cert)
+{
+ gpg_error_t err;
+
+ acquire_cache_write_lock ();
+ err = put_cert (cert, 0, 0, NULL);
+ release_cache_lock ();
+ if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
+ log_info (_("certificate already cached\n"));
+ else if (!err)
+ log_info (_("certificate cached\n"));
+ else
+ log_error (_("error caching certificate: %s\n"), gpg_strerror (err));
+ return err;
+}
+
+
+/* Put CERT into the certificate cache and store the fingerprint of
+ the certificate into FPR_BUFFER. If the certificate is already in
+ the cache do not print a warning; just store the
+ fingerprint. FPR_BUFFER needs to be at least 20 bytes. */
+gpg_error_t
+cache_cert_silent (ksba_cert_t cert, void *fpr_buffer)
+{
+ gpg_error_t err;
+
+ acquire_cache_write_lock ();
+ err = put_cert (cert, 0, 0, fpr_buffer);
+ release_cache_lock ();
+ if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
+ err = 0;
+ if (err)
+ log_error (_("error caching certificate: %s\n"), gpg_strerror (err));
+ return err;
+}
+
+
+
+/* Return a certificate object for the given fingerprint. FPR is
+ expected to be a 20 byte binary SHA-1 fingerprint. If no matching
+ certificate is available in the cache NULL is returned. The caller
+ must release a returned certificate. Note that although we are
+ using reference counting the caller should not just compare the
+ pointers to check for identical certificates. */
+ksba_cert_t
+get_cert_byfpr (const unsigned char *fpr)
+{
+ cert_item_t ci;
+
+ acquire_cache_read_lock ();
+ for (ci=cert_cache[*fpr]; ci; ci = ci->next)
+ if (ci->cert && !memcmp (ci->fpr, fpr, 20))
+ {
+ ksba_cert_ref (ci->cert);
+ release_cache_lock ();
+ return ci->cert;
+ }
+
+ release_cache_lock ();
+ return NULL;
+}
+
+/* Return a certificate object for the given fingerprint. STRING is
+ expected to be a SHA-1 fingerprint in standard hex notation with or
+ without colons. If no matching certificate is available in the
+ cache NULL is returned. The caller must release a returned
+ certificate. Note that although we are using reference counting
+ the caller should not just compare the pointers to check for
+ identical certificates. */
+ksba_cert_t
+get_cert_byhexfpr (const char *string)
+{
+ unsigned char fpr[20];
+ const char *s;
+ int i;
+
+ if (strchr (string, ':'))
+ {
+ for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1);)
+ {
+ if (s[2] && s[2] != ':')
+ break; /* Invalid string. */
+ fpr[i++] = xtoi_2 (s);
+ s += 2;
+ if (i!= 20 && *s == ':')
+ s++;
+ }
+ }
+ else
+ {
+ for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1); s+=2 )
+ fpr[i++] = xtoi_2 (s);
+ }
+ if (i!=20 || *s)
+ {
+ log_error (_("invalid SHA1 fingerprint string '%s'\n"), string);
+ return NULL;
+ }
+
+ return get_cert_byfpr (fpr);
+}
+
+
+
+/* Return the certificate matching ISSUER_DN and SERIALNO. */
+ksba_cert_t
+get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno)
+{
+ /* Simple and inefficient implementation. fixme! */
+ cert_item_t ci;
+ int i;
+
+ acquire_cache_read_lock ();
+ for (i=0; i < 256; i++)
+ {
+ for (ci=cert_cache[i]; ci; ci = ci->next)
+ if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn)
+ && !compare_serialno (ci->sn, serialno))
+ {
+ ksba_cert_ref (ci->cert);
+ release_cache_lock ();
+ return ci->cert;
+ }
+ }
+
+ release_cache_lock ();
+ return NULL;
+}
+
+
+/* Return the certificate matching ISSUER_DN. SEQ should initially be
+ set to 0 and bumped up to get the next issuer with that DN. */
+ksba_cert_t
+get_cert_byissuer (const char *issuer_dn, unsigned int seq)
+{
+ /* Simple and very inefficient implementation and API. fixme! */
+ cert_item_t ci;
+ int i;
+
+ acquire_cache_read_lock ();
+ for (i=0; i < 256; i++)
+ {
+ for (ci=cert_cache[i]; ci; ci = ci->next)
+ if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn))
+ if (!seq--)
+ {
+ ksba_cert_ref (ci->cert);
+ release_cache_lock ();
+ return ci->cert;
+ }
+ }
+
+ release_cache_lock ();
+ return NULL;
+}
+
+
+/* Return the certificate matching SUBJECT_DN. SEQ should initially be
+ set to 0 and bumped up to get the next subject with that DN. */
+ksba_cert_t
+get_cert_bysubject (const char *subject_dn, unsigned int seq)
+{
+ /* Simple and very inefficient implementation and API. fixme! */
+ cert_item_t ci;
+ int i;
+
+ if (!subject_dn)
+ return NULL;
+
+ acquire_cache_read_lock ();
+ for (i=0; i < 256; i++)
+ {
+ for (ci=cert_cache[i]; ci; ci = ci->next)
+ if (ci->cert && ci->subject_dn
+ && !strcmp (ci->subject_dn, subject_dn))
+ if (!seq--)
+ {
+ ksba_cert_ref (ci->cert);
+ release_cache_lock ();
+ return ci->cert;
+ }
+ }
+
+ release_cache_lock ();
+ return NULL;
+}
+
+
+
+/* Return a value decribing the the class of PATTERN. The offset of
+ the actual string to be used for the comparison is stored at
+ R_OFFSET. The offset of the serialnumer is stored at R_SN_OFFSET. */
+static enum pattern_class
+classify_pattern (const char *pattern, size_t *r_offset, size_t *r_sn_offset)
+{
+ enum pattern_class result;
+ const char *s;
+ int hexprefix = 0;
+ int hexlength;
+
+ *r_offset = *r_sn_offset = 0;
+
+ /* Skip leading spaces. */
+ for(s = pattern; *s && spacep (s); s++ )
+ ;
+
+ switch (*s)
+ {
+ case 0: /* Empty string is an error. */
+ result = PATTERN_UNKNOWN;
+ break;
+
+ case '.': /* An email address, compare from end. */
+ result = PATTERN_UNKNOWN; /* Not implemented. */
+ break;
+
+ case '<': /* An email address. */
+ result = PATTERN_EMAIL;
+ s++;
+ break;
+
+ case '@': /* Part of an email address. */
+ result = PATTERN_EMAIL_SUBSTR;
+ s++;
+ break;
+
+ case '=': /* Exact compare. */
+ result = PATTERN_UNKNOWN; /* Does not make sense for X.509. */
+ break;
+
+ case '*': /* Case insensitive substring search. */
+ result = PATTERN_SUBSTR;
+ s++;
+ break;
+
+ case '+': /* Compare individual words. */
+ result = PATTERN_UNKNOWN; /* Not implemented. */
+ break;
+
+ case '/': /* Subject's DN. */
+ s++;
+ if (!*s || spacep (s))
+ result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */
+ else
+ result = PATTERN_SUBJECT;
+ break;
+
+ case '#': /* Serial number or issuer DN. */
+ {
+ const char *si;
+
+ s++;
+ if ( *s == '/')
+ {
+ /* An issuer's DN is indicated by "#/" */
+ s++;
+ if (!*s || spacep (s))
+ result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */
+ else
+ result = PATTERN_ISSUER;
+ }
+ else
+ { /* Serialnumber + optional issuer ID. */
+ for (si=s; *si && *si != '/'; si++)
+ if (!strchr("01234567890abcdefABCDEF", *si))
+ break;
+ if (*si && *si != '/')
+ result = PATTERN_UNKNOWN; /* Invalid digit in serial number. */
+ else
+ {
+ *r_sn_offset = s - pattern;
+ if (!*si)
+ result = PATTERN_SERIALNO;
+ else
+ {
+ s = si+1;
+ if (!*s || spacep (s))
+ result = PATTERN_UNKNOWN; /* No DN or prefixed
+ with a space. */
+ else
+ result = PATTERN_SERIALNO_ISSUER;
+ }
+ }
+ }
+ }
+ break;
+
+ case ':': /* Unified fingerprint. */
+ {
+ const char *se, *si;
+ int i;
+
+ se = strchr (++s, ':');
+ if (!se)
+ result = PATTERN_UNKNOWN;
+ else
+ {
+ for (i=0, si=s; si < se; si++, i++ )
+ if (!strchr("01234567890abcdefABCDEF", *si))
+ break;
+ if ( si < se )
+ result = PATTERN_UNKNOWN; /* Invalid digit. */
+ else if (i == 32)
+ result = PATTERN_FINGERPRINT16;
+ else if (i == 40)
+ result = PATTERN_FINGERPRINT20;
+ else
+ result = PATTERN_UNKNOWN; /* Invalid length for a fingerprint. */
+ }
+ }
+ break;
+
+ case '&': /* Keygrip. */
+ result = PATTERN_UNKNOWN; /* Not implemented. */
+ break;
+
+ default:
+ if (s[0] == '0' && s[1] == 'x')
+ {
+ hexprefix = 1;
+ s += 2;
+ }
+
+ hexlength = strspn(s, "0123456789abcdefABCDEF");
+
+ /* Check if a hexadecimal number is terminated by EOS or blank. */
+ if (hexlength && s[hexlength] && !spacep (s+hexlength))
+ {
+ /* If the "0x" prefix is used a correct termination is required. */
+ if (hexprefix)
+ {
+ result = PATTERN_UNKNOWN;
+ break; /* switch */
+ }
+ hexlength = 0; /* Not a hex number. */
+ }
+
+ if (hexlength == 8 || (!hexprefix && hexlength == 9 && *s == '0'))
+ {
+ if (hexlength == 9)
+ s++;
+ result = PATTERN_SHORT_KEYID;
+ }
+ else if (hexlength == 16 || (!hexprefix && hexlength == 17 && *s == '0'))
+ {
+ if (hexlength == 17)
+ s++;
+ result = PATTERN_LONG_KEYID;
+ }
+ else if (hexlength == 32 || (!hexprefix && hexlength == 33 && *s == '0'))
+ {
+ if (hexlength == 33)
+ s++;
+ result = PATTERN_FINGERPRINT16;
+ }
+ else if (hexlength == 40 || (!hexprefix && hexlength == 41 && *s == '0'))
+ {
+ if (hexlength == 41)
+ s++;
+ result = PATTERN_FINGERPRINT20;
+ }
+ else if (!hexprefix)
+ {
+ /* The fingerprints used with X.509 are often delimited by
+ colons, so we try to single this case out. */
+ result = PATTERN_UNKNOWN;
+ hexlength = strspn (s, ":0123456789abcdefABCDEF");
+ if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength)))
+ {
+ int i, c;
+
+ for (i=0; i < 20; i++, s += 3)
+ {
+ c = hextobyte(s);
+ if (c == -1 || (i < 19 && s[2] != ':'))
+ break;
+ }
+ if (i == 20)
+ result = PATTERN_FINGERPRINT20;
+ }
+ if (result == PATTERN_UNKNOWN) /* Default to substring match. */
+ {
+ result = PATTERN_SUBSTR;
+ }
+ }
+ else /* A hex number with a prefix but with a wrong length. */
+ result = PATTERN_UNKNOWN;
+ }
+
+ if (result != PATTERN_UNKNOWN)
+ *r_offset = s - pattern;
+ return result;
+}
+
+
+
+/* Given PATTERN, which is a string as used by GnuPG to specify a
+ certificate, return all matching certificates by calling the
+ supplied function RETFNC. */
+gpg_error_t
+get_certs_bypattern (const char *pattern,
+ gpg_error_t (*retfnc)(void*,ksba_cert_t),
+ void *retfnc_data)
+{
+ gpg_error_t err = GPG_ERR_BUG;
+ enum pattern_class class;
+ size_t offset, sn_offset;
+ const char *hexserialno;
+ ksba_sexp_t serialno = NULL;
+ ksba_cert_t cert = NULL;
+ unsigned int seq;
+
+ if (!pattern || !retfnc)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ class = classify_pattern (pattern, &offset, &sn_offset);
+ hexserialno = pattern + sn_offset;
+ pattern += offset;
+ switch (class)
+ {
+ case PATTERN_UNKNOWN:
+ err = gpg_error (GPG_ERR_INV_NAME);
+ break;
+
+ case PATTERN_FINGERPRINT20:
+ cert = get_cert_byhexfpr (pattern);
+ err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND);
+ break;
+
+ case PATTERN_SERIALNO_ISSUER:
+ serialno = hexsn_to_sexp (hexserialno);
+ if (!serialno)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ cert = get_cert_bysn (pattern, serialno);
+ err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND);
+ }
+ break;
+
+ case PATTERN_ISSUER:
+ for (seq=0,err=0; !err && (cert = get_cert_byissuer (pattern, seq)); seq++)
+ {
+ err = retfnc (retfnc_data, cert);
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ if (!err && !seq)
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ break;
+
+ case PATTERN_SUBJECT:
+ for (seq=0,err=0; !err && (cert = get_cert_bysubject (pattern, seq));seq++)
+ {
+ err = retfnc (retfnc_data, cert);
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ if (!err && !seq)
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ break;
+
+ case PATTERN_EMAIL:
+ case PATTERN_EMAIL_SUBSTR:
+ case PATTERN_FINGERPRINT16:
+ case PATTERN_SHORT_KEYID:
+ case PATTERN_LONG_KEYID:
+ case PATTERN_SUBSTR:
+ case PATTERN_SERIALNO:
+ /* Not supported. */
+ err = gpg_error (GPG_ERR_INV_NAME);
+ }
+
+
+ if (!err && cert)
+ err = retfnc (retfnc_data, cert);
+ ksba_cert_release (cert);
+ xfree (serialno);
+ return err;
+}
+
+
+
+
+
+/* Return the certificate matching ISSUER_DN and SERIALNO; if it is
+ not already in the cache, try to find it from other resources. */
+ksba_cert_t
+find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno)
+{
+ gpg_error_t err;
+ ksba_cert_t cert;
+ cert_fetch_context_t context = NULL;
+ char *hexsn, *buf;
+
+ /* First check whether it has already been cached. */
+ cert = get_cert_bysn (issuer_dn, serialno);
+ if (cert)
+ return cert;
+
+ /* Ask back to the service requester to return the certificate.
+ This is because we can assume that he already used the
+ certificate while checking for the CRL. */
+ hexsn = serial_hex (serialno);
+ if (!hexsn)
+ {
+ log_error ("serial_hex() failed\n");
+ return NULL;
+ }
+ buf = xtrymalloc (1 + strlen (hexsn) + 1 + strlen (issuer_dn) + 1);
+ if (!buf)
+ {
+ log_error ("can't allocate enough memory: %s\n", strerror (errno));
+ xfree (hexsn);
+ return NULL;
+ }
+ strcpy (stpcpy (stpcpy (stpcpy (buf, "#"), hexsn),"/"), issuer_dn);
+ xfree (hexsn);
+ cert = get_cert_local (ctrl, buf);
+ xfree (buf);
+ if (cert)
+ {
+ cache_cert (cert);
+ return cert; /* Done. */
+ }
+
+ if (DBG_LOOKUP)
+ log_debug ("find_cert_bysn: certificate not returned by caller"
+ " - doing lookup\n");
+
+ /* Retrieve the certificate from external resources. */
+ while (!cert)
+ {
+ ksba_sexp_t sn;
+ char *issdn;
+
+ if (!context)
+ {
+ err = ca_cert_fetch (ctrl, &context, issuer_dn);
+ if (err)
+ {
+ log_error (_("error fetching certificate by S/N: %s\n"),
+ gpg_strerror (err));
+ break;
+ }
+ }
+
+ err = fetch_next_ksba_cert (context, &cert);
+ if (err)
+ {
+ log_error (_("error fetching certificate by S/N: %s\n"),
+ gpg_strerror (err) );
+ break;
+ }
+
+ issdn = ksba_cert_get_issuer (cert, 0);
+ if (strcmp (issuer_dn, issdn))
+ {
+ log_debug ("find_cert_bysn: Ooops: issuer DN does not match\n");
+ ksba_cert_release (cert);
+ cert = NULL;
+ ksba_free (issdn);
+ break;
+ }
+
+ sn = ksba_cert_get_serial (cert);
+
+ if (DBG_LOOKUP)
+ {
+ log_debug (" considering certificate (#");
+ dump_serial (sn);
+ log_printf ("/");
+ dump_string (issdn);
+ log_printf (")\n");
+ }
+
+ if (!compare_serialno (serialno, sn))
+ {
+ ksba_free (sn);
+ ksba_free (issdn);
+ cache_cert (cert);
+ if (DBG_LOOKUP)
+ log_debug (" found\n");
+ break; /* Ready. */
+ }
+
+ ksba_free (sn);
+ ksba_free (issdn);
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+
+ end_cert_fetch (context);
+ return cert;
+}
+
+
+/* Return the certificate matching SUBJECT_DN and (if not NULL)
+ KEYID. If it is not already in the cache, try to find it from other
+ resources. Note, that the external search does not work for user
+ certificates because the LDAP lookup is on the caCertificate
+ attribute. For our purposes this is just fine. */
+ksba_cert_t
+find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid)
+{
+ gpg_error_t err;
+ int seq;
+ ksba_cert_t cert = NULL;
+ cert_fetch_context_t context = NULL;
+ ksba_sexp_t subj;
+
+ /* If we have certificates from an OCSP request we first try to use
+ them. This is because these certificates will really be the
+ required ones and thus even in the case that they can't be
+ uniquely located by the following code we can use them. This is
+ for example required by Telesec certificates where a keyId is
+ used but the issuer certificate comes without a subject keyId! */
+ if (ctrl->ocsp_certs && subject_dn)
+ {
+ cert_item_t ci;
+ cert_ref_t cr;
+ int i;
+
+ /* For efficiency reasons we won't use get_cert_bysubject here. */
+ acquire_cache_read_lock ();
+ for (i=0; i < 256; i++)
+ for (ci=cert_cache[i]; ci; ci = ci->next)
+ if (ci->cert && ci->subject_dn
+ && !strcmp (ci->subject_dn, subject_dn))
+ for (cr=ctrl->ocsp_certs; cr; cr = cr->next)
+ if (!memcmp (ci->fpr, cr->fpr, 20))
+ {
+ ksba_cert_ref (ci->cert);
+ release_cache_lock ();
+ return ci->cert; /* We use this certificate. */
+ }
+ release_cache_lock ();
+ if (DBG_LOOKUP)
+ log_debug ("find_cert_bysubject: certificate not in ocsp_certs\n");
+ }
+
+
+ /* First we check whether the certificate is cached. */
+ for (seq=0; (cert = get_cert_bysubject (subject_dn, seq)); seq++)
+ {
+ if (!keyid)
+ break; /* No keyid requested, so return the first one found. */
+ if (!ksba_cert_get_subj_key_id (cert, NULL, &subj)
+ && !cmp_simple_canon_sexp (keyid, subj))
+ {
+ xfree (subj);
+ break; /* Found matching cert. */
+ }
+ xfree (subj);
+ ksba_cert_release (cert);
+ }
+ if (cert)
+ return cert; /* Done. */
+
+ if (DBG_LOOKUP)
+ log_debug ("find_cert_bysubject: certificate not in cache\n");
+
+ /* Ask back to the service requester to return the certificate.
+ This is because we can assume that he already used the
+ certificate while checking for the CRL. */
+ if (keyid)
+ cert = get_cert_local_ski (ctrl, subject_dn, keyid);
+ else
+ {
+ /* In contrast to get_cert_local_ski, get_cert_local uses any
+ passed pattern, so we need to make sure that an exact subject
+ search is done. */
+ char *buf;
+
+ buf = xtrymalloc (1 + strlen (subject_dn) + 1);
+ if (!buf)
+ {
+ log_error ("can't allocate enough memory: %s\n", strerror (errno));
+ return NULL;
+ }
+ strcpy (stpcpy (buf, "/"), subject_dn);
+ cert = get_cert_local (ctrl, buf);
+ xfree (buf);
+ }
+ if (cert)
+ {
+ cache_cert (cert);
+ return cert; /* Done. */
+ }
+
+ if (DBG_LOOKUP)
+ log_debug ("find_cert_bysubject: certificate not returned by caller"
+ " - doing lookup\n");
+
+ /* Locate the certificate using external resources. */
+ while (!cert)
+ {
+ char *subjdn;
+
+ if (!context)
+ {
+ err = ca_cert_fetch (ctrl, &context, subject_dn);
+ if (err)
+ {
+ log_error (_("error fetching certificate by subject: %s\n"),
+ gpg_strerror (err));
+ break;
+ }
+ }
+
+ err = fetch_next_ksba_cert (context, &cert);
+ if (err)
+ {
+ log_error (_("error fetching certificate by subject: %s\n"),
+ gpg_strerror (err) );
+ break;
+ }
+
+ subjdn = ksba_cert_get_subject (cert, 0);
+ if (strcmp (subject_dn, subjdn))
+ {
+ log_info ("find_cert_bysubject: subject DN does not match\n");
+ ksba_cert_release (cert);
+ cert = NULL;
+ ksba_free (subjdn);
+ continue;
+ }
+
+
+ if (DBG_LOOKUP)
+ {
+ log_debug (" considering certificate (/");
+ dump_string (subjdn);
+ log_printf (")\n");
+ }
+ ksba_free (subjdn);
+
+ /* If no key ID has been provided, we return the first match. */
+ if (!keyid)
+ {
+ cache_cert (cert);
+ if (DBG_LOOKUP)
+ log_debug (" found\n");
+ break; /* Ready. */
+ }
+
+ /* With the key ID given we need to compare it. */
+ if (!ksba_cert_get_subj_key_id (cert, NULL, &subj))
+ {
+ if (!cmp_simple_canon_sexp (keyid, subj))
+ {
+ ksba_free (subj);
+ cache_cert (cert);
+ if (DBG_LOOKUP)
+ log_debug (" found\n");
+ break; /* Ready. */
+ }
+ }
+
+ ksba_free (subj);
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+
+ end_cert_fetch (context);
+ return cert;
+}
+
+
+
+/* Return 0 if the certificate is a trusted certificate. Returns
+ GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in
+ case of systems errors. */
+gpg_error_t
+is_trusted_cert (ksba_cert_t cert)
+{
+ unsigned char fpr[20];
+ cert_item_t ci;
+
+ cert_compute_fpr (cert, fpr);
+
+ acquire_cache_read_lock ();
+ for (ci=cert_cache[*fpr]; ci; ci = ci->next)
+ if (ci->cert && !memcmp (ci->fpr, fpr, 20))
+ {
+ if (ci->flags.trusted)
+ {
+ release_cache_lock ();
+ return 0; /* Yes, it is trusted. */
+ }
+ break;
+ }
+
+ release_cache_lock ();
+ return gpg_error (GPG_ERR_NOT_TRUSTED);
+}
+
+
+
+/* Given the certificate CERT locate the issuer for this certificate
+ and return it at R_CERT. Returns 0 on success or
+ GPG_ERR_NOT_FOUND. */
+gpg_error_t
+find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert)
+{
+ gpg_error_t err;
+ char *issuer_dn;
+ ksba_cert_t issuer_cert = NULL;
+ ksba_name_t authid;
+ ksba_sexp_t authidno;
+ ksba_sexp_t keyid;
+
+ *r_cert = NULL;
+
+ issuer_dn = ksba_cert_get_issuer (cert, 0);
+ if (!issuer_dn)
+ {
+ log_error (_("no issuer found in certificate\n"));
+ err = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ /* First we need to check whether we can return that certificate
+ using the authorithyKeyIdentifier. */
+ err = ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno);
+ if (err)
+ {
+ log_info (_("error getting authorityKeyIdentifier: %s\n"),
+ gpg_strerror (err));
+ }
+ else
+ {
+ const char *s = ksba_name_enum (authid, 0);
+ if (s && *authidno)
+ {
+ issuer_cert = find_cert_bysn (ctrl, s, authidno);
+ }
+ if (!issuer_cert && keyid)
+ {
+ /* Not found by issuer+s/n. Now that we have an AKI
+ keyIdentifier look for a certificate with a matching
+ SKI. */
+ issuer_cert = find_cert_bysubject (ctrl, issuer_dn, keyid);
+ }
+ /* Print a note so that the user does not feel too helpless when
+ an issuer certificate was found and gpgsm prints BAD
+ signature because it is not the correct one. */
+ if (!issuer_cert)
+ {
+ log_info ("issuer certificate ");
+ if (keyid)
+ {
+ log_printf ("{");
+ dump_serial (keyid);
+ log_printf ("} ");
+ }
+ if (authidno)
+ {
+ log_printf ("(#");
+ dump_serial (authidno);
+ log_printf ("/");
+ dump_string (s);
+ log_printf (") ");
+ }
+ log_printf ("not found using authorityKeyIdentifier\n");
+ }
+ ksba_name_release (authid);
+ xfree (authidno);
+ xfree (keyid);
+ }
+
+ /* If this did not work, try just with the issuer's name and assume
+ that there is only one such certificate. We only look into our
+ cache then. */
+ if (err || !issuer_cert)
+ {
+ issuer_cert = get_cert_bysubject (issuer_dn, 0);
+ if (issuer_cert)
+ err = 0;
+ }
+
+ leave:
+ if (!err && !issuer_cert)
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+
+ xfree (issuer_dn);
+
+ if (err)
+ ksba_cert_release (issuer_cert);
+ else
+ *r_cert = issuer_cert;
+
+ return err;
+}
diff --git a/dirmngr/certcache.h b/dirmngr/certcache.h
new file mode 100644
index 0000000..9986f15
--- /dev/null
+++ b/dirmngr/certcache.h
@@ -0,0 +1,103 @@
+/* certcache.h - Certificate caching
+ * Copyright (C) 2004, 2008 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef CERTCACHE_H
+#define CERTCACHE_H
+
+/* First time initialization of the certificate cache. */
+void cert_cache_init (void);
+
+/* Deinitialize the certificate cache. */
+void cert_cache_deinit (int full);
+
+/* Print some statistics to the log file. */
+void cert_cache_print_stats (void);
+
+/* Compute the fingerprint of the certificate CERT and put it into
+ the 20 bytes large buffer DIGEST. Return address of this buffer. */
+unsigned char *cert_compute_fpr (ksba_cert_t cert, unsigned char *digest);
+
+/* Put CERT into the certificate cache. */
+gpg_error_t cache_cert (ksba_cert_t cert);
+
+/* Put CERT into the certificate cache and return the fingerprint. */
+gpg_error_t cache_cert_silent (ksba_cert_t cert, void *fpr_buffer);
+
+/* Return 0 if the certificate is a trusted certificate. Returns
+ GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in
+ case of systems errors. */
+gpg_error_t is_trusted_cert (ksba_cert_t cert);
+
+
+/* Return a certificate object for the given fingerprint. FPR is
+ expected to be a 20 byte binary SHA-1 fingerprint. If no matching
+ certificate is available in the cache NULL is returned. The caller
+ must release a returned certificate. */
+ksba_cert_t get_cert_byfpr (const unsigned char *fpr);
+
+/* Return a certificate object for the given fingerprint. STRING is
+ expected to be a SHA-1 fingerprint in standard hex notation with or
+ without colons. If no matching certificate is available in the
+ cache NULL is returned. The caller must release a returned
+ certificate. */
+ksba_cert_t get_cert_byhexfpr (const char *string);
+
+/* Return the certificate matching ISSUER_DN and SERIALNO. */
+ksba_cert_t get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno);
+
+/* Return the certificate matching ISSUER_DN. SEQ should initially be
+ set to 0 and bumped up to get the next issuer with that DN. */
+ksba_cert_t get_cert_byissuer (const char *issuer_dn, unsigned int seq);
+
+/* Return the certificate matching SUBJECT_DN. SEQ should initially be
+ set to 0 and bumped up to get the next issuer with that DN. */
+ksba_cert_t get_cert_bysubject (const char *subject_dn, unsigned int seq);
+
+/* Given PATTERN, which is a string as used by GnuPG to specify a
+ certificate, return all matching certificates by calling the
+ supplied function RETFNC. */
+gpg_error_t get_certs_bypattern (const char *pattern,
+ gpg_error_t (*retfnc)(void*,ksba_cert_t),
+ void *retfnc_data);
+
+/* Return the certificate matching ISSUER_DN and SERIALNO; if it is
+ not already in the cache, try to find it from other resources. */
+ksba_cert_t find_cert_bysn (ctrl_t ctrl,
+ const char *issuer_dn, ksba_sexp_t serialno);
+
+
+/* Return the certificate matching SUBJECT_DN and (if not NULL) KEYID. If
+ it is not already in the cache, try to find it from other
+ resources. Note, that the external search does not work for user
+ certificates because the LDAP lookup is on the caCertificate
+ attribute. For our purposes this is just fine. */
+ksba_cert_t find_cert_bysubject (ctrl_t ctrl,
+ const char *subject_dn, ksba_sexp_t keyid);
+
+/* Given the certificate CERT locate the issuer for this certificate
+ and return it at R_CERT. Returns 0 on success or
+ GPG_ERR_NOT_FOUND. */
+gpg_error_t find_issuing_cert (ctrl_t ctrl,
+ ksba_cert_t cert, ksba_cert_t *r_cert);
+
+
+
+
+#endif /*CERTCACHE_H*/
diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c
new file mode 100644
index 0000000..d10e3ca
--- /dev/null
+++ b/dirmngr/crlcache.c
@@ -0,0 +1,2580 @@
+/* crlcache.c - LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2005, 2008 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+
+ 1. To keep track of the CRLs actually cached and to store the meta
+ information of the CRLs a simple record oriented text file is
+ used. Fields in the file are colon (':') separated and values
+ containing colons or linefeeds are percent escaped (e.g. a colon
+ itself is represented as "%3A").
+
+ The first field is a record type identifier, so that the file is
+ useful to keep track of other meta data too.
+
+ The name of the file is "DIR.txt".
+
+
+ 1.1. Comment record
+
+ Field 1: Constant beginning with "#".
+
+ Other fields are not defined and such a record is simply
+ skipped during processing.
+
+ 1.2. Version record
+
+ Field 1: Constant "v"
+ Field 2: Version number of this file. Must be 1.
+
+ This record must be the first non-comment record record and
+ there shall only exist one record of this type.
+
+ 1.3. CRL cache record
+
+ Field 1: Constant "c", "u" or "i".
+ A "c" or "u" indicate a valid cache entry, however
+ "u" requires that a user root certificate check needs
+ to be done.
+ An "i" indicates an invalid cache entry which should
+ not be used but still exists so that it can be
+ updated at NEXT_UPDATE.
+ Field 2: Hexadecimal encoded SHA-1 hash of the issuer DN using
+ uppercase letters.
+ Field 3: Issuer DN in RFC-2253 notation.
+ Field 4: URL used to retrieve the corresponding CRL.
+ Field 5: 15 character ISO timestamp with THIS_UPDATE.
+ Field 6: 15 character ISO timestamp with NEXT_UPDATE.
+ Field 7: Hexadecimal encoded MD-5 hash of the DB file to detect
+ accidental modified (i.e. deleted and created) cache files.
+ Field 8: optional CRL number as a hex string.
+ Field 9: AuthorityKeyID.issuer, each Name separated by 0x01
+ Field 10: AuthorityKeyID.serial
+ Field 11: Hex fingerprint of trust anchor if field 1 is 'u'.
+
+ 2. Layout of the standard CRL Cache DB file:
+
+ We use records of variable length with this structure
+
+ n bytes Serialnumber (binary) used as key
+ thus there is no need to store the length explicitly with DB2.
+ 1 byte Reason for revocation
+ (currently the KSBA reason flags are used)
+ 15 bytes ISO date of revocation (e.g. 19980815T142000)
+ Note that there is no terminating 0 stored.
+
+ The filename used is the hexadecimal (using uppercase letters)
+ SHA-1 hash value of the issuer DN prefixed with a "crl-" and
+ suffixed with a ".db". Thus the length of the filename is 47.
+
+
+*/
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <unistd.h>
+#ifndef HAVE_W32_SYSTEM
+#include <sys/utsname.h>
+#endif
+#ifdef MKDIR_TAKES_ONE_ARG
+#undef mkdir
+#define mkdir(a,b) mkdir(a)
+#endif
+
+#include "dirmngr.h"
+#include "validate.h"
+#include "certcache.h"
+#include "crlcache.h"
+#include "crlfetch.h"
+#include "misc.h"
+#include "cdb.h"
+
+/* Change this whenever the format changes */
+#define DBDIR_D (opt.system_daemon? "crls.d" : "dirmngr-cache.d")
+#define DBDIRFILE "DIR.txt"
+#define DBDIRVERSION 1
+
+/* The number of DB files we may have open at one time. We need to
+ limit this because there is no guarantee that the number of issuers
+ has a upper limit. We are currently using mmap, so it is a good
+ idea anyway to limit the number of opened cache files. */
+#define MAX_OPEN_DB_FILES 5
+
+
+static const char oidstr_crlNumber[] = "2.5.29.20";
+static const char oidstr_issuingDistributionPoint[] = "2.5.29.28";
+static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35";
+
+
+/* Definition of one cached item. */
+struct crl_cache_entry_s
+{
+ struct crl_cache_entry_s *next;
+ int deleted; /* True if marked for deletion. */
+ int mark; /* Internally used by update_dir. */
+ unsigned int lineno;/* A 0 indicates a new entry. */
+ char *release_ptr; /* The actual allocated memory. */
+ char *url; /* Points into RELEASE_PTR. */
+ char *issuer; /* Ditto. */
+ char *issuer_hash; /* Ditto. */
+ char *dbfile_hash; /* MD5 sum of the cache file, points into RELEASE_PTR.*/
+ int invalid; /* Can't use this CRL. */
+ int user_trust_req; /* User supplied root certificate required. */
+ char *check_trust_anchor; /* Malloced fingerprint. */
+ ksba_isotime_t this_update;
+ ksba_isotime_t next_update;
+ ksba_isotime_t last_refresh; /* Use for the force_crl_refresh feature. */
+ char *crl_number;
+ char *authority_issuer;
+ char *authority_serialno;
+
+ struct cdb *cdb; /* The cache file handle or NULL if not open. */
+
+ unsigned int cdb_use_count; /* Current use count. */
+ unsigned int cdb_lru_count; /* Used for LRU purposes. */
+ int dbfile_checked; /* Set to true if the dbfile_hash value has
+ been checked one. */
+};
+
+
+/* Definition of the entire cache object. */
+struct crl_cache_s
+{
+ crl_cache_entry_t entries;
+};
+
+typedef struct crl_cache_s *crl_cache_t;
+
+
+/* Prototypes. */
+static crl_cache_entry_t find_entry (crl_cache_entry_t first,
+ const char *issuer_hash);
+
+
+
+/* The currently loaded cache object. This is usually initialized
+ right at startup. */
+static crl_cache_t current_cache;
+
+
+
+
+
+/* Return the current cache object or bail out if it is has not yet
+ been initialized. */
+static crl_cache_t
+get_current_cache (void)
+{
+ if (!current_cache)
+ log_fatal ("CRL cache has not yet been initialized\n");
+ return current_cache;
+}
+
+
+/*
+ Create ae directory if it does not yet exists. Returns on
+ success, or -1 on error.
+ */
+static int
+create_directory_if_needed (const char *name)
+{
+ DIR *dir;
+ char *fname;
+
+ fname = make_filename (opt.homedir_cache, name, NULL);
+ dir = opendir (fname);
+ if (!dir)
+ {
+ log_info (_("creating directory '%s'\n"), fname);
+ if (mkdir (fname, S_IRUSR|S_IWUSR|S_IXUSR) )
+ {
+ int save_errno = errno;
+ log_error (_("error creating directory '%s': %s\n"),
+ fname, strerror (errno));
+ xfree (fname);
+ gpg_err_set_errno (save_errno);
+ return -1;
+ }
+ }
+ else
+ closedir (dir);
+ xfree (fname);
+ return 0;
+}
+
+/* Remove all files from the cache directory. If FORCE is not true,
+ some sanity checks on the filenames are done. Return 0 if
+ everything went fine. */
+static int
+cleanup_cache_dir (int force)
+{
+ char *dname = make_filename (opt.homedir_cache, DBDIR_D, NULL);
+ DIR *dir;
+ struct dirent *de;
+ int problem = 0;
+
+ if (!force)
+ { /* Very minor sanity checks. */
+ if (!strcmp (dname, "~/") || !strcmp (dname, "/" ))
+ {
+ log_error (_("ignoring database dir '%s'\n"), dname);
+ xfree (dname);
+ return -1;
+ }
+ }
+
+ dir = opendir (dname);
+ if (!dir)
+ {
+ log_error (_("error reading directory '%s': %s\n"),
+ dname, strerror (errno));
+ xfree (dname);
+ return -1;
+ }
+
+ while ((de = readdir (dir)))
+ {
+ if (strcmp (de->d_name, "." ) && strcmp (de->d_name, ".."))
+ {
+ char *cdbname = make_filename (dname, de->d_name, NULL);
+ int okay;
+ struct stat sbuf;
+
+ if (force)
+ okay = 1;
+ else
+ okay = (!stat (cdbname, &sbuf) && S_ISREG (sbuf.st_mode));
+
+ if (okay)
+ {
+ log_info (_("removing cache file '%s'\n"), cdbname);
+ if (gnupg_remove (cdbname))
+ {
+ log_error ("failed to remove '%s': %s\n",
+ cdbname, strerror (errno));
+ problem = -1;
+ }
+ }
+ else
+ log_info (_("not removing file '%s'\n"), cdbname);
+ xfree (cdbname);
+ }
+ }
+ xfree (dname);
+ closedir (dir);
+ return problem;
+}
+
+
+/* Read the next line from the file FP and return the line in an
+ malloced buffer. Return NULL on error or EOF. There is no
+ limitation os the line length. The trailing linefeed has been
+ removed, the function will read the last line of a file, even if
+ that is not terminated by a LF. */
+static char *
+next_line_from_file (estream_t fp, gpg_error_t *r_err)
+{
+ char buf[300];
+ char *largebuf = NULL;
+ size_t buflen;
+ size_t len = 0;
+ unsigned char *p;
+ int c;
+ char *tmpbuf;
+
+ *r_err = 0;
+ p = buf;
+ buflen = sizeof buf - 1;
+ while ((c=es_getc (fp)) != EOF && c != '\n')
+ {
+ if (len >= buflen)
+ {
+ if (!largebuf)
+ {
+ buflen += 1024;
+ largebuf = xtrymalloc ( buflen + 1 );
+ if (!largebuf)
+ {
+ *r_err = gpg_error_from_syserror ();
+ return NULL;
+ }
+ memcpy (largebuf, buf, len);
+ }
+ else
+ {
+ buflen += 1024;
+ tmpbuf = xtryrealloc (largebuf, buflen + 1);
+ if (!tmpbuf)
+ {
+ *r_err = gpg_error_from_syserror ();
+ xfree (largebuf);
+ return NULL;
+ }
+ largebuf = tmpbuf;
+ }
+ p = largebuf;
+ }
+ p[len++] = c;
+ }
+ if (c == EOF && !len)
+ return NULL;
+ p[len] = 0;
+
+ if (largebuf)
+ tmpbuf = xtryrealloc (largebuf, len+1);
+ else
+ tmpbuf = xtrystrdup (buf);
+ if (!tmpbuf)
+ {
+ *r_err = gpg_error_from_syserror ();
+ xfree (largebuf);
+ }
+ return tmpbuf;
+}
+
+
+/* Release one cache entry. */
+static void
+release_one_cache_entry (crl_cache_entry_t entry)
+{
+ if (entry)
+ {
+ if (entry->cdb)
+ {
+ int fd = cdb_fileno (entry->cdb);
+ cdb_free (entry->cdb);
+ xfree (entry->cdb);
+ if (close (fd))
+ log_error (_("error closing cache file: %s\n"), strerror(errno));
+ }
+ xfree (entry->release_ptr);
+ xfree (entry->check_trust_anchor);
+ xfree (entry);
+ }
+}
+
+
+/* Release the CACHE object. */
+static void
+release_cache (crl_cache_t cache)
+{
+ crl_cache_entry_t entry, entry2;
+
+ if (!cache)
+ return;
+
+ for (entry = cache->entries; entry; entry = entry2)
+ {
+ entry2 = entry->next;
+ release_one_cache_entry (entry);
+ }
+ cache->entries = NULL;
+ xfree (cache);
+}
+
+
+/* Open the dir file FNAME or create a new one if it does not yet
+ exist. */
+static estream_t
+open_dir_file (const char *fname)
+{
+ estream_t fp;
+
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ log_error (_("failed to open cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+
+ /* Make sure that the directory exists, try to create if otherwise. */
+ if (create_directory_if_needed (NULL)
+ || create_directory_if_needed (DBDIR_D))
+ return NULL;
+ fp = es_fopen (fname, "w");
+ if (!fp)
+ {
+ log_error (_("error creating new cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ return NULL;
+ }
+ es_fprintf (fp, "v:%d:\n", DBDIRVERSION);
+ if (es_ferror (fp))
+ {
+ log_error (_("error writing new cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ es_fclose (fp);
+ return NULL;
+ }
+ if (es_fclose (fp))
+ {
+ log_error (_("error closing new cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ return NULL;
+ }
+
+ log_info (_("new cache dir file '%s' created\n"), fname);
+
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ log_error (_("failed to re-open cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ return NULL;
+ }
+ }
+
+ return fp;
+}
+
+/* Helper for open_dir. */
+static gpg_error_t
+check_dir_version (estream_t *fpadr, const char *fname,
+ unsigned int *lineno,
+ int cleanup_on_mismatch)
+{
+ char *line;
+ gpg_error_t lineerr = 0;
+ estream_t fp = *fpadr;
+ int created = 0;
+
+ retry:
+ while ((line = next_line_from_file (fp, &lineerr)))
+ {
+ ++*lineno;
+ if (*line == 'v' && line[1] == ':')
+ break;
+ else if (*line != '#')
+ {
+ log_error (_("first record of '%s' is not the version\n"), fname);
+ xfree (line);
+ return gpg_error (GPG_ERR_CONFIGURATION);
+ }
+ xfree (line);
+ }
+ if (lineerr)
+ return lineerr;
+
+ if (strtol (line+2, NULL, 10) != DBDIRVERSION)
+ {
+ if (!created && cleanup_on_mismatch)
+ {
+ log_error (_("old version of cache directory - cleaning up\n"));
+ es_fclose (fp);
+ *fpadr = NULL;
+ if (!cleanup_cache_dir (1))
+ {
+ *lineno = 0;
+ fp = *fpadr = open_dir_file (fname);
+ if (!fp)
+ {
+ xfree (line);
+ return gpg_error (GPG_ERR_CONFIGURATION);
+ }
+ created = 1;
+ goto retry;
+ }
+ }
+ log_error (_("old version of cache directory - giving up\n"));
+ xfree (line);
+ return gpg_error (GPG_ERR_CONFIGURATION);
+ }
+ xfree (line);
+ return 0;
+}
+
+
+/* Open the dir file and read in all available information. Store
+ that in a newly allocated cache object and return that if
+ everything worked out fine. Create the cache directory and the dir
+ if it does not yet exist. Remove all files in that directory if
+ the version does not match. */
+static gpg_error_t
+open_dir (crl_cache_t *r_cache)
+{
+ crl_cache_t cache;
+ char *fname;
+ char *line = NULL;
+ gpg_error_t lineerr = 0;
+ estream_t fp;
+ crl_cache_entry_t entry, *entrytail;
+ unsigned int lineno;
+ gpg_error_t err = 0;
+ int anyerr = 0;
+
+ cache = xtrycalloc (1, sizeof *cache);
+ if (!cache)
+ return gpg_error_from_syserror ();
+
+ fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL);
+
+ lineno = 0;
+ fp = open_dir_file (fname);
+ if (!fp)
+ {
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+ goto leave;
+ }
+
+ err = check_dir_version (&fp, fname, &lineno, 1);
+ if (err)
+ goto leave;
+
+
+ /* Read in all supported entries from the dir file. */
+ cache->entries = NULL;
+ entrytail = &cache->entries;
+ xfree (line);
+ while ((line = next_line_from_file (fp, &lineerr)))
+ {
+ int fieldno;
+ char *p, *endp;
+
+ lineno++;
+ if ( *line == 'c' || *line == 'u' || *line == 'i' )
+ {
+ entry = xtrycalloc (1, sizeof *entry);
+ if (!entry)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ entry->lineno = lineno;
+ entry->release_ptr = line;
+ if (*line == 'i')
+ {
+ entry->invalid = atoi (line+1);
+ if (entry->invalid < 1)
+ entry->invalid = 1;
+ }
+ else if (*line == 'u')
+ entry->user_trust_req = 1;
+
+ for (fieldno=1, p = line; p; p = endp, fieldno++)
+ {
+ endp = strchr (p, ':');
+ if (endp)
+ *endp++ = '\0';
+
+ switch (fieldno)
+ {
+ case 1: /* record type */ break;
+ case 2: entry->issuer_hash = p; break;
+ case 3: entry->issuer = unpercent_string (p); break;
+ case 4: entry->url = unpercent_string (p); break;
+ case 5:
+ strncpy (entry->this_update, p, 15);
+ entry->this_update[15] = 0;
+ break;
+ case 6:
+ strncpy (entry->next_update, p, 15);
+ entry->next_update[15] = 0;
+ break;
+ case 7: entry->dbfile_hash = p; break;
+ case 8: if (*p) entry->crl_number = p; break;
+ case 9:
+ if (*p)
+ entry->authority_issuer = unpercent_string (p);
+ break;
+ case 10:
+ if (*p)
+ entry->authority_serialno = unpercent_string (p);
+ break;
+ case 11:
+ if (*p)
+ entry->check_trust_anchor = xtrystrdup (p);
+ break;
+ default:
+ if (*p)
+ log_info (_("extra field detected in crl record of "
+ "'%s' line %u\n"), fname, lineno);
+ break;
+ }
+ }
+
+ if (!entry->issuer_hash)
+ {
+ log_info (_("invalid line detected in '%s' line %u\n"),
+ fname, lineno);
+ xfree (entry);
+ entry = NULL;
+ }
+ else if (find_entry (cache->entries, entry->issuer_hash))
+ {
+ /* Fixme: The duplicate checking used is not very
+ effective for large numbers of issuers. */
+ log_info (_("duplicate entry detected in '%s' line %u\n"),
+ fname, lineno);
+ xfree (entry);
+ entry = NULL;
+ }
+ else
+ {
+ line = NULL;
+ *entrytail = entry;
+ entrytail = &entry->next;
+ }
+ }
+ else if (*line == '#')
+ ;
+ else
+ log_info (_("unsupported record type in '%s' line %u skipped\n"),
+ fname, lineno);
+
+ if (line)
+ xfree (line);
+ }
+ if (lineerr)
+ {
+ err = lineerr;
+ log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
+ goto leave;
+ }
+ if (es_ferror (fp))
+ {
+ log_error (_("error reading '%s': %s\n"), fname, strerror (errno));
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+ goto leave;
+ }
+
+ /* Now do some basic checks on the data. */
+ for (entry = cache->entries; entry; entry = entry->next)
+ {
+ assert (entry->lineno);
+ if (strlen (entry->issuer_hash) != 40)
+ {
+ anyerr++;
+ log_error (_("invalid issuer hash in '%s' line %u\n"),
+ fname, entry->lineno);
+ }
+ else if ( !*entry->issuer )
+ {
+ anyerr++;
+ log_error (_("no issuer DN in '%s' line %u\n"),
+ fname, entry->lineno);
+ }
+ else if ( check_isotime (entry->this_update)
+ || check_isotime (entry->next_update))
+ {
+ anyerr++;
+ log_error (_("invalid timestamp in '%s' line %u\n"),
+ fname, entry->lineno);
+ }
+
+ /* Checks not leading to an immediate fail. */
+ if (strlen (entry->dbfile_hash) != 32)
+ log_info (_("WARNING: invalid cache file hash in '%s' line %u\n"),
+ fname, entry->lineno);
+ }
+
+ if (anyerr)
+ {
+ log_error (_("detected errors in cache dir file\n"));
+ log_info (_("please check the reason and manually delete that file\n"));
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+ }
+
+
+ leave:
+ es_fclose (fp);
+ xfree (line);
+ xfree (fname);
+ if (err)
+ {
+ release_cache (cache);
+ cache = NULL;
+ }
+ *r_cache = cache;
+ return err;
+}
+
+static void
+write_percented_string (const char *s, estream_t fp)
+{
+ for (; *s; s++)
+ if (*s == ':')
+ es_fputs ("%3A", fp);
+ else if (*s == '\n')
+ es_fputs ("%0A", fp);
+ else if (*s == '\r')
+ es_fputs ("%0D", fp);
+ else
+ es_putc (*s, fp);
+}
+
+
+static void
+write_dir_line_crl (estream_t fp, crl_cache_entry_t e)
+{
+ if (e->invalid)
+ es_fprintf (fp, "i%d", e->invalid);
+ else if (e->user_trust_req)
+ es_putc ('u', fp);
+ else
+ es_putc ('c', fp);
+ es_putc (':', fp);
+ es_fputs (e->issuer_hash, fp);
+ es_putc (':', fp);
+ write_percented_string (e->issuer, fp);
+ es_putc (':', fp);
+ write_percented_string (e->url, fp);
+ es_putc (':', fp);
+ es_fwrite (e->this_update, 15, 1, fp);
+ es_putc (':', fp);
+ es_fwrite (e->next_update, 15, 1, fp);
+ es_putc (':', fp);
+ es_fputs (e->dbfile_hash, fp);
+ es_putc (':', fp);
+ if (e->crl_number)
+ es_fputs (e->crl_number, fp);
+ es_putc (':', fp);
+ if (e->authority_issuer)
+ write_percented_string (e->authority_issuer, fp);
+ es_putc (':', fp);
+ if (e->authority_serialno)
+ es_fputs (e->authority_serialno, fp);
+ es_putc (':', fp);
+ if (e->check_trust_anchor && e->user_trust_req)
+ es_fputs (e->check_trust_anchor, fp);
+ es_putc ('\n', fp);
+}
+
+
+/* Update the current dir file using the cache. */
+static gpg_error_t
+update_dir (crl_cache_t cache)
+{
+ char *fname = NULL;
+ char *tmpfname = NULL;
+ char *line = NULL;
+ gpg_error_t lineerr = 0;
+ estream_t fp;
+ estream_t fpout = NULL;
+ crl_cache_entry_t e;
+ unsigned int lineno;
+ gpg_error_t err = 0;
+
+ fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL);
+
+ /* Fixme: Take an update file lock here. */
+
+ for (e= cache->entries; e; e = e->next)
+ e->mark = 1;
+
+ lineno = 0;
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("failed to open cache dir file '%s': %s\n"),
+ fname, strerror (errno));
+ goto leave;
+ }
+ err = check_dir_version (&fp, fname, &lineno, 0);
+ if (err)
+ goto leave;
+ es_rewind (fp);
+ lineno = 0;
+
+ /* Create a temporary DIR file. */
+ {
+ char *tmpbuf, *p;
+ const char *nodename;
+#ifndef HAVE_W32_SYSTEM
+ struct utsname utsbuf;
+#endif
+
+#ifdef HAVE_W32_SYSTEM
+ nodename = "unknown";
+#else
+ if (uname (&utsbuf))
+ nodename = "unknown";
+ else
+ nodename = utsbuf.nodename;
+#endif
+
+ gpgrt_asprintf (&tmpbuf, "DIR-tmp-%s-%u-%p.txt.tmp",
+ nodename, (unsigned int)getpid (), &tmpbuf);
+ if (!tmpbuf)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("failed to create temporary cache dir file '%s': %s\n"),
+ tmpfname, strerror (errno));
+ goto leave;
+ }
+ for (p=tmpbuf; *p; p++)
+ if (*p == '/')
+ *p = '.';
+ tmpfname = make_filename (opt.homedir_cache, DBDIR_D, tmpbuf, NULL);
+ xfree (tmpbuf);
+ }
+ fpout = es_fopen (tmpfname, "w");
+ if (!fpout)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("failed to create temporary cache dir file '%s': %s\n"),
+ tmpfname, strerror (errno));
+ goto leave;
+ }
+
+ while ((line = next_line_from_file (fp, &lineerr)))
+ {
+ lineno++;
+ if (*line == 'c' || *line == 'u' || *line == 'i')
+ {
+ /* Extract the issuer hash field. */
+ char *fieldp, *endp;
+
+ fieldp = strchr (line, ':');
+ endp = fieldp? strchr (++fieldp, ':') : NULL;
+ if (endp)
+ {
+ /* There should be no percent within the issuer hash
+ field, thus we can compare it pretty easily. */
+ *endp = 0;
+ e = find_entry ( cache->entries, fieldp);
+ *endp = ':'; /* Restore orginal line. */
+ if (e && e->deleted)
+ {
+ /* Marked for deletion, so don't write it. */
+ e->mark = 0;
+ }
+ else if (e)
+ {
+ /* Yep, this is valid entry we know about; write it out */
+ write_dir_line_crl (fpout, e);
+ e->mark = 0;
+ }
+ else
+ { /* We ignore entries we don't have in our cache
+ because they may have been added in the meantime
+ by other instances of dirmngr. */
+ es_fprintf (fpout, "# Next line added by "
+ "another process; our pid is %lu\n",
+ (unsigned long)getpid ());
+ es_fputs (line, fpout);
+ es_putc ('\n', fpout);
+ }
+ }
+ else
+ {
+ es_fputs ("# Invalid line detected: ", fpout);
+ es_fputs (line, fpout);
+ es_putc ('\n', fpout);
+ }
+ }
+ else
+ {
+ /* Write out all non CRL lines as they are. */
+ es_fputs (line, fpout);
+ es_putc ('\n', fpout);
+ }
+
+ xfree (line);
+ }
+ if (!es_ferror (fp) && !es_ferror (fpout) && !lineerr)
+ {
+ /* Write out the remaining entries. */
+ for (e= cache->entries; e; e = e->next)
+ if (e->mark)
+ {
+ if (!e->deleted)
+ write_dir_line_crl (fpout, e);
+ e->mark = 0;
+ }
+ }
+ if (lineerr)
+ {
+ err = lineerr;
+ log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
+ goto leave;
+ }
+ if (es_ferror (fp))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error reading '%s': %s\n"), fname, strerror (errno));
+ }
+ if (es_ferror (fpout))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error writing '%s': %s\n"), tmpfname, strerror (errno));
+ }
+ if (err)
+ goto leave;
+
+ /* Rename the files. */
+ es_fclose (fp);
+ fp = NULL;
+ if (es_fclose (fpout))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error closing '%s': %s\n"), tmpfname, strerror (errno));
+ goto leave;
+ }
+ fpout = NULL;
+
+#ifdef HAVE_W32_SYSTEM
+ /* No atomic mv on W32 systems. */
+ gnupg_remove (fname);
+#endif
+ if (rename (tmpfname, fname))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error renaming '%s' to '%s': %s\n"),
+ tmpfname, fname, strerror (errno));
+ goto leave;
+ }
+
+ leave:
+ /* Fixme: Relinquish update lock. */
+ xfree (line);
+ es_fclose (fp);
+ xfree (fname);
+ if (fpout)
+ {
+ es_fclose (fpout);
+ if (err && tmpfname)
+ gnupg_remove (tmpfname);
+ }
+ xfree (tmpfname);
+ return err;
+}
+
+
+
+
+/* Create the filename for the cache file from the 40 byte ISSUER_HASH
+ string. Caller must release the return string. */
+static char *
+make_db_file_name (const char *issuer_hash)
+{
+ char bname[50];
+
+ assert (strlen (issuer_hash) == 40);
+ memcpy (bname, "crl-", 4);
+ memcpy (bname + 4, issuer_hash, 40);
+ strcpy (bname + 44, ".db");
+ return make_filename (opt.homedir_cache, DBDIR_D, bname, NULL);
+}
+
+
+/* Hash the file FNAME and return the MD5 digest in MD5BUFFER. The
+ caller must allocate MD%buffer wityh at least 16 bytes. Returns 0
+ on success. */
+static int
+hash_dbfile (const char *fname, unsigned char *md5buffer)
+{
+ estream_t fp;
+ char *buffer;
+ size_t n;
+ gcry_md_hd_t md5;
+ gpg_err_code_t err;
+
+ buffer = xtrymalloc (65536);
+ fp = buffer? es_fopen (fname, "rb") : NULL;
+ if (!fp)
+ {
+ log_error (_("can't hash '%s': %s\n"), fname, strerror (errno));
+ xfree (buffer);
+ return -1;
+ }
+
+ err = gcry_md_open (&md5, GCRY_MD_MD5, 0);
+ if (err)
+ {
+ log_error (_("error setting up MD5 hash context: %s\n"),
+ gpg_strerror (err));
+ xfree (buffer);
+ es_fclose (fp);
+ return -1;
+ }
+
+ /* We better hash some information about the cache file layout in. */
+ sprintf (buffer, "%.100s/%.100s:%d", DBDIR_D, DBDIRFILE, DBDIRVERSION);
+ gcry_md_write (md5, buffer, strlen (buffer));
+
+ for (;;)
+ {
+ n = es_fread (buffer, 1, 65536, fp);
+ if (n < 65536 && es_ferror (fp))
+ {
+ log_error (_("error hashing '%s': %s\n"), fname, strerror (errno));
+ xfree (buffer);
+ es_fclose (fp);
+ gcry_md_close (md5);
+ return -1;
+ }
+ if (!n)
+ break;
+ gcry_md_write (md5, buffer, n);
+ }
+ es_fclose (fp);
+ xfree (buffer);
+ gcry_md_final (md5);
+
+ memcpy (md5buffer, gcry_md_read (md5, GCRY_MD_MD5), 16);
+ gcry_md_close (md5);
+ return 0;
+}
+
+/* Compare the file FNAME against the dexified MD5 hash MD5HASH and
+ return 0 if they match. */
+static int
+check_dbfile (const char *fname, const char *md5hexvalue)
+{
+ unsigned char buffer1[16], buffer2[16];
+
+ if (strlen (md5hexvalue) != 32)
+ {
+ log_error (_("invalid formatted checksum for '%s'\n"), fname);
+ return -1;
+ }
+ unhexify (buffer1, md5hexvalue);
+
+ if (hash_dbfile (fname, buffer2))
+ return -1;
+
+ return memcmp (buffer1, buffer2, 16);
+}
+
+
+/* Open the cache file for ENTRY. This function implements a caching
+ strategy and might close unused cache files. It is required to use
+ unlock_db_file after using the file. */
+static struct cdb *
+lock_db_file (crl_cache_t cache, crl_cache_entry_t entry)
+{
+ char *fname;
+ int fd;
+ int open_count;
+ crl_cache_entry_t e;
+
+ if (entry->cdb)
+ {
+ entry->cdb_use_count++;
+ return entry->cdb;
+ }
+
+ for (open_count = 0, e = cache->entries; e; e = e->next)
+ {
+ if (e->cdb)
+ open_count++;
+/* log_debug ("CACHE: cdb=%p use_count=%u lru_count=%u\n", */
+/* e->cdb,e->cdb_use_count,e->cdb_lru_count); */
+ }
+
+ /* If there are too many file open, find the least recent used DB
+ file and close it. Note that for Pth thread safeness we need to
+ use a loop here. */
+ while (open_count >= MAX_OPEN_DB_FILES )
+ {
+ crl_cache_entry_t last_e = NULL;
+ unsigned int last_lru = (unsigned int)(-1);
+
+ for (e = cache->entries; e; e = e->next)
+ if (e->cdb && !e->cdb_use_count && e->cdb_lru_count < last_lru)
+ {
+ last_lru = e->cdb_lru_count;
+ last_e = e;
+ }
+ if (!last_e)
+ {
+ log_error (_("too many open cache files; can't open anymore\n"));
+ return NULL;
+ }
+
+/* log_debug ("CACHE: closing file at cdb=%p\n", last_e->cdb); */
+
+ fd = cdb_fileno (last_e->cdb);
+ cdb_free (last_e->cdb);
+ xfree (last_e->cdb);
+ last_e->cdb = NULL;
+ if (close (fd))
+ log_error (_("error closing cache file: %s\n"), strerror(errno));
+ open_count--;
+ }
+
+
+ fname = make_db_file_name (entry->issuer_hash);
+ if (opt.verbose)
+ log_info (_("opening cache file '%s'\n"), fname );
+
+ if (!entry->dbfile_checked)
+ {
+ if (!check_dbfile (fname, entry->dbfile_hash))
+ entry->dbfile_checked = 1;
+ /* Note, in case of an error we don't print an error here but
+ let require the caller to do that check. */
+ }
+
+ entry->cdb = xtrycalloc (1, sizeof *entry->cdb);
+ if (!entry->cdb)
+ {
+ xfree (fname);
+ return NULL;
+ }
+ fd = open (fname, O_RDONLY);
+ if (fd == -1)
+ {
+ log_error (_("error opening cache file '%s': %s\n"),
+ fname, strerror (errno));
+ xfree (entry->cdb);
+ entry->cdb = NULL;
+ xfree (fname);
+ return NULL;
+ }
+ if (cdb_init (entry->cdb, fd))
+ {
+ log_error (_("error initializing cache file '%s' for reading: %s\n"),
+ fname, strerror (errno));
+ xfree (entry->cdb);
+ entry->cdb = NULL;
+ close (fd);
+ xfree (fname);
+ return NULL;
+ }
+ xfree (fname);
+
+ entry->cdb_use_count = 1;
+ entry->cdb_lru_count = 0;
+
+ return entry->cdb;
+}
+
+/* Unlock a cache file, so that it can be reused. */
+static void
+unlock_db_file (crl_cache_t cache, crl_cache_entry_t entry)
+{
+ if (!entry->cdb)
+ log_error (_("calling unlock_db_file on a closed file\n"));
+ else if (!entry->cdb_use_count)
+ log_error (_("calling unlock_db_file on an unlocked file\n"));
+ else
+ {
+ entry->cdb_use_count--;
+ entry->cdb_lru_count++;
+ }
+
+ /* If the entry was marked for deletion in the meantime do it now.
+ We do this for the sake of Pth thread safeness. */
+ if (!entry->cdb_use_count && entry->deleted)
+ {
+ crl_cache_entry_t eprev, enext;
+
+ enext = entry->next;
+ for (eprev = cache->entries;
+ eprev && eprev->next != entry; eprev = eprev->next)
+ ;
+ assert (eprev);
+ if (eprev == cache->entries)
+ cache->entries = enext;
+ else
+ eprev->next = enext;
+ /* FIXME: Do we leak ENTRY? */
+ }
+}
+
+
+/* Find ISSUER_HASH in our cache FIRST. This may be used to enumerate
+ the linked list we use to keep the CRLs of an issuer. */
+static crl_cache_entry_t
+find_entry (crl_cache_entry_t first, const char *issuer_hash)
+{
+ while (first && (first->deleted || strcmp (issuer_hash, first->issuer_hash)))
+ first = first->next;
+ return first;
+}
+
+
+/* Create a new CRL cache. This fucntion is usually called only once.
+ never fail. */
+void
+crl_cache_init(void)
+{
+ crl_cache_t cache = NULL;
+ gpg_error_t err;
+
+ if (current_cache)
+ {
+ log_error ("crl cache has already been initialized - not doing twice\n");
+ return;
+ }
+
+ err = open_dir (&cache);
+ if (err)
+ log_fatal (_("failed to create a new cache object: %s\n"),
+ gpg_strerror (err));
+ current_cache = cache;
+}
+
+
+/* Remove the cache information and all its resources. Note that we
+ still keep the cache on disk. */
+void
+crl_cache_deinit (void)
+{
+ if (current_cache)
+ {
+ release_cache (current_cache);
+ current_cache = NULL;
+ }
+}
+
+
+/* Delete the cache from disk. Return 0 on success.*/
+int
+crl_cache_flush (void)
+{
+ int rc;
+
+ rc = cleanup_cache_dir (0)? -1 : 0;
+
+ return rc;
+}
+
+
+/* Check whether the certificate identified by ISSUER_HASH and
+ SN/SNLEN is valid; i.e. not listed in our cache. With
+ FORCE_REFRESH set to true, a new CRL will be retrieved even if the
+ cache has not yet expired. We use a 30 minutes threshold here so
+ that invoking this function several times won't load the CRL over
+ and over. */
+static crl_cache_result_t
+cache_isvalid (ctrl_t ctrl, const char *issuer_hash,
+ const unsigned char *sn, size_t snlen,
+ int force_refresh)
+{
+ crl_cache_t cache = get_current_cache ();
+ crl_cache_result_t retval;
+ struct cdb *cdb;
+ int rc;
+ crl_cache_entry_t entry;
+ gnupg_isotime_t current_time;
+ size_t n;
+
+ (void)ctrl;
+
+ entry = find_entry (cache->entries, issuer_hash);
+ if (!entry)
+ {
+ log_info (_("no CRL available for issuer id %s\n"), issuer_hash );
+ return CRL_CACHE_DONTKNOW;
+ }
+
+ gnupg_get_isotime (current_time);
+ if (strcmp (entry->next_update, current_time) < 0 )
+ {
+ log_info (_("cached CRL for issuer id %s too old; update required\n"),
+ issuer_hash);
+ return CRL_CACHE_DONTKNOW;
+ }
+ if (force_refresh)
+ {
+ gnupg_isotime_t tmptime;
+
+ if (*entry->last_refresh)
+ {
+ gnupg_copy_time (tmptime, entry->last_refresh);
+ add_seconds_to_isotime (tmptime, 30 * 60);
+ if (strcmp (tmptime, current_time) < 0 )
+ {
+ log_info (_("force-crl-refresh active and %d minutes passed for"
+ " issuer id %s; update required\n"),
+ 30, issuer_hash);
+ return CRL_CACHE_DONTKNOW;
+ }
+ }
+ else
+ {
+ log_info (_("force-crl-refresh active for"
+ " issuer id %s; update required\n"),
+ issuer_hash);
+ return CRL_CACHE_DONTKNOW;
+ }
+ }
+
+ if (entry->invalid)
+ {
+ log_info (_("available CRL for issuer ID %s can't be used\n"),
+ issuer_hash);
+ return CRL_CACHE_CANTUSE;
+ }
+
+ cdb = lock_db_file (cache, entry);
+ if (!cdb)
+ return CRL_CACHE_DONTKNOW; /* Hmmm, not the best error code. */
+
+ if (!entry->dbfile_checked)
+ {
+ log_error (_("cached CRL for issuer id %s tampered; we need to update\n")
+ , issuer_hash);
+ unlock_db_file (cache, entry);
+ return CRL_CACHE_DONTKNOW;
+ }
+
+ rc = cdb_find (cdb, sn, snlen);
+ if (rc == 1)
+ {
+ n = cdb_datalen (cdb);
+ if (n != 16)
+ {
+ log_error (_("WARNING: invalid cache record length for S/N "));
+ log_printhex ("", sn, snlen);
+ }
+ else if (opt.verbose)
+ {
+ unsigned char record[16];
+ char *tmp = hexify_data (sn, snlen);
+
+ if (cdb_read (cdb, record, n, cdb_datapos (cdb)))
+ log_error (_("problem reading cache record for S/N %s: %s\n"),
+ tmp, strerror (errno));
+ else
+ log_info (_("S/N %s is not valid; reason=%02X date=%.15s\n"),
+ tmp, *record, record+1);
+ xfree (tmp);
+ }
+ retval = CRL_CACHE_INVALID;
+ }
+ else if (!rc)
+ {
+ if (opt.verbose)
+ {
+ char *serialno = hexify_data (sn, snlen);
+ log_info (_("S/N %s is valid, it is not listed in the CRL\n"),
+ serialno );
+ xfree (serialno);
+ }
+ retval = CRL_CACHE_VALID;
+ }
+ else
+ {
+ log_error (_("error getting data from cache file: %s\n"),
+ strerror (errno));
+ retval = CRL_CACHE_DONTKNOW;
+ }
+
+
+ if (entry->user_trust_req
+ && (retval == CRL_CACHE_VALID || retval == CRL_CACHE_INVALID))
+ {
+ if (!entry->check_trust_anchor)
+ {
+ log_error ("inconsistent data on user trust check\n");
+ retval = CRL_CACHE_CANTUSE;
+ }
+ else if (get_istrusted_from_client (ctrl, entry->check_trust_anchor))
+ {
+ if (opt.verbose)
+ log_info ("no system trust and client does not trust either\n");
+ retval = CRL_CACHE_CANTUSE;
+ }
+ else
+ {
+ /* Okay, the CRL is considered valid by the client and thus
+ we can return the result as is. */
+ }
+ }
+
+ unlock_db_file (cache, entry);
+
+ return retval;
+}
+
+
+/* Check whether the certificate identified by ISSUER_HASH and
+ SERIALNO is valid; i.e. not listed in our cache. With
+ FORCE_REFRESH set to true, a new CRL will be retrieved even if the
+ cache has not yet expired. We use a 30 minutes threshold here so
+ that invoking this function several times won't load the CRL over
+ and over. */
+crl_cache_result_t
+crl_cache_isvalid (ctrl_t ctrl, const char *issuer_hash, const char *serialno,
+ int force_refresh)
+{
+ crl_cache_result_t result;
+ unsigned char snbuf_buffer[50];
+ unsigned char *snbuf;
+ size_t n;
+
+ n = strlen (serialno)/2+1;
+ if (n < sizeof snbuf_buffer - 1)
+ snbuf = snbuf_buffer;
+ else
+ {
+ snbuf = xtrymalloc (n);
+ if (!snbuf)
+ return CRL_CACHE_DONTKNOW;
+ }
+
+ n = unhexify (snbuf, serialno);
+
+ result = cache_isvalid (ctrl, issuer_hash, snbuf, n, force_refresh);
+
+ if (snbuf != snbuf_buffer)
+ xfree (snbuf);
+
+ return result;
+}
+
+
+/* Check whether the certificate CERT is valid; i.e. not listed in our
+ cache. With FORCE_REFRESH set to true, a new CRL will be retrieved
+ even if the cache has not yet expired. We use a 30 minutes
+ threshold here so that invoking this function several times won't
+ load the CRL over and over. */
+gpg_error_t
+crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert,
+ int force_refresh)
+{
+ gpg_error_t err;
+ crl_cache_result_t result;
+ unsigned char issuerhash[20];
+ char issuerhash_hex[41];
+ ksba_sexp_t serial;
+ unsigned char *sn;
+ size_t snlen;
+ char *endp, *tmp;
+ int i;
+
+ /* Compute the hash value of the issuer name. */
+ tmp = ksba_cert_get_issuer (cert, 0);
+ if (!tmp)
+ {
+ log_error ("oops: issuer missing in certificate\n");
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ gcry_md_hash_buffer (GCRY_MD_SHA1, issuerhash, tmp, strlen (tmp));
+ xfree (tmp);
+ for (i=0,tmp=issuerhash_hex; i < 20; i++, tmp += 2)
+ sprintf (tmp, "%02X", issuerhash[i]);
+
+ /* Get the serial number. */
+ serial = ksba_cert_get_serial (cert);
+ if (!serial)
+ {
+ log_error ("oops: S/N missing in certificate\n");
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ sn = serial;
+ if (*sn != '(')
+ {
+ log_error ("oops: invalid S/N\n");
+ xfree (serial);
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ sn++;
+ snlen = strtoul (sn, &endp, 10);
+ sn = endp;
+ if (*sn != ':')
+ {
+ log_error ("oops: invalid S/N\n");
+ xfree (serial);
+ return gpg_error (GPG_ERR_INV_CERT_OBJ);
+ }
+ sn++;
+
+ /* Check the cache. */
+ result = cache_isvalid (ctrl, issuerhash_hex, sn, snlen, force_refresh);
+ switch (result)
+ {
+ case CRL_CACHE_VALID:
+ err = 0;
+ break;
+ case CRL_CACHE_INVALID:
+ err = gpg_error (GPG_ERR_CERT_REVOKED);
+ break;
+ case CRL_CACHE_DONTKNOW:
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ case CRL_CACHE_CANTUSE:
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ break;
+ default:
+ log_fatal ("cache_isvalid returned invalid status code %d\n", result);
+ }
+
+ xfree (serial);
+ return err;
+}
+
+
+/* Prepare a hash context for the signature verification. Input is
+ the CRL and the output is the hash context MD as well as the uses
+ algorithm identifier ALGO. */
+static gpg_error_t
+start_sig_check (ksba_crl_t crl, gcry_md_hd_t *md, int *algo)
+{
+ gpg_error_t err;
+ const char *algoid;
+
+ algoid = ksba_crl_get_digest_algo (crl);
+ *algo = gcry_md_map_name (algoid);
+ if (!*algo)
+ {
+ log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?");
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+
+ err = gcry_md_open (md, *algo, 0);
+ if (err)
+ {
+ log_error (_("gcry_md_open for algorithm %d failed: %s\n"),
+ *algo, gcry_strerror (err));
+ return err;
+ }
+ if (DBG_HASHING)
+ gcry_md_debug (*md, "hash.cert");
+
+ ksba_crl_set_hash_function (crl, HASH_FNC, *md);
+ return 0;
+}
+
+
+/* Finish a hash context and verify the signature. This function
+ should return 0 on a good signature, GPG_ERR_BAD_SIGNATURE if the
+ signature does not verify or any other error code. CRL is the CRL
+ object we are working on, MD the hash context and ISSUER_CERT the
+ certificate of the CRL issuer. This function closes MD. */
+static gpg_error_t
+finish_sig_check (ksba_crl_t crl, gcry_md_hd_t md, int algo,
+ ksba_cert_t issuer_cert)
+{
+ gpg_error_t err;
+ ksba_sexp_t sigval = NULL, pubkey = NULL;
+ const char *s;
+ char algoname[50];
+ size_t n;
+ gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL;
+ unsigned int i;
+
+ /* This also stops debugging on the MD. */
+ gcry_md_final (md);
+
+ /* Get and convert the signature value. */
+ sigval = ksba_crl_get_sig_val (crl);
+ n = gcry_sexp_canon_len (sigval, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error (_("got an invalid S-expression from libksba\n"));
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto leave;
+ }
+ err = gcry_sexp_sscan (&s_sig, NULL, sigval, n);
+ if (err)
+ {
+ log_error (_("converting S-expression failed: %s\n"),
+ gcry_strerror (err));
+ goto leave;
+ }
+
+ /* Get and convert the public key for the issuer certificate. */
+ if (DBG_X509)
+ dump_cert ("crl_issuer_cert", issuer_cert);
+ pubkey = ksba_cert_get_public_key (issuer_cert);
+ n = gcry_sexp_canon_len (pubkey, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error (_("got an invalid S-expression from libksba\n"));
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ goto leave;
+ }
+ err = gcry_sexp_sscan (&s_pkey, NULL, pubkey, n);
+ if (err)
+ {
+ log_error (_("converting S-expression failed: %s\n"),
+ gcry_strerror (err));
+ goto leave;
+ }
+
+ /* Create an S-expression with the actual hash value. */
+ s = gcry_md_algo_name (algo);
+ for (i = 0; *s && i < sizeof(algoname) - 1; s++, i++)
+ algoname[i] = ascii_tolower (*s);
+ algoname[i] = 0;
+ err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
+ algoname,
+ gcry_md_get_algo_dlen (algo), gcry_md_read (md, algo));
+ if (err)
+ {
+ log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err));
+ goto leave;
+ }
+
+ /* Pass this on to the signature verification. */
+ err = gcry_pk_verify (s_sig, s_hash, s_pkey);
+ if (DBG_X509)
+ log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err));
+
+ leave:
+ xfree (sigval);
+ xfree (pubkey);
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_hash);
+ gcry_sexp_release (s_pkey);
+ gcry_md_close (md);
+
+ return err;
+}
+
+
+/* Call this to match a start_sig_check that can not be completed
+ normally. */
+static void
+abort_sig_check (ksba_crl_t crl, gcry_md_hd_t md)
+{
+ (void)crl;
+ gcry_md_close (md);
+}
+
+
+/* Workhorse of the CRL loading machinery. The CRL is read using the
+ CRL object and stored in the data base file DB with the name FNAME
+ (only used for printing error messages). That DB should be a
+ temporary one and not the actual one. If the function fails the
+ caller should delete this temporary database file. CTRL is
+ required to retrieve certificates using the general dirmngr
+ callback service. R_CRLISSUER returns an allocated string with the
+ crl-issuer DN, THIS_UPDATE and NEXT_UPDATE are filled with the
+ corresponding data from the CRL. Note that these values might get
+ set even if the CRL processing fails at a later step; thus the
+ caller should free *R_ISSUER even if the function returns with an
+ error. R_TRUST_ANCHOR is set on exit to NULL or a string with the
+ hexified fingerprint of the root certificate, if checking this
+ certificate for trustiness is required.
+*/
+static int
+crl_parse_insert (ctrl_t ctrl, ksba_crl_t crl,
+ struct cdb_make *cdb, const char *fname,
+ char **r_crlissuer,
+ ksba_isotime_t thisupdate, ksba_isotime_t nextupdate,
+ char **r_trust_anchor)
+{
+ gpg_error_t err;
+ ksba_stop_reason_t stopreason;
+ ksba_cert_t crlissuer_cert = NULL;
+ gcry_md_hd_t md = NULL;
+ int algo = 0;
+ size_t n;
+
+ (void)fname;
+
+ *r_crlissuer = NULL;
+ *thisupdate = *nextupdate = 0;
+ *r_trust_anchor = NULL;
+
+ /* Start of the KSBA parser loop. */
+ do
+ {
+ err = ksba_crl_parse (crl, &stopreason);
+ if (err)
+ {
+ log_error (_("ksba_crl_parse failed: %s\n"), gpg_strerror (err) );
+ goto failure;
+ }
+
+ switch (stopreason)
+ {
+ case KSBA_SR_BEGIN_ITEMS:
+ {
+ if (start_sig_check (crl, &md, &algo ))
+ goto failure;
+
+ err = ksba_crl_get_update_times (crl, thisupdate, nextupdate);
+ if (err)
+ {
+ log_error (_("error getting update times of CRL: %s\n"),
+ gpg_strerror (err));
+ err = gpg_error (GPG_ERR_INV_CRL);
+ goto failure;
+ }
+
+ if (opt.verbose || !*nextupdate)
+ log_info (_("update times of this CRL: this=%s next=%s\n"),
+ thisupdate, nextupdate);
+ if (!*nextupdate)
+ {
+ log_info (_("nextUpdate not given; "
+ "assuming a validity period of one day\n"));
+ gnupg_copy_time (nextupdate, thisupdate);
+ add_seconds_to_isotime (nextupdate, 86400);
+ }
+ }
+ break;
+
+ case KSBA_SR_GOT_ITEM:
+ {
+ ksba_sexp_t serial;
+ const unsigned char *p;
+ ksba_isotime_t rdate;
+ ksba_crl_reason_t reason;
+ int rc;
+ unsigned char record[1+15];
+
+ err = ksba_crl_get_item (crl, &serial, rdate, &reason);
+ if (err)
+ {
+ log_error (_("error getting CRL item: %s\n"),
+ gpg_strerror (err));
+ err = gpg_error (GPG_ERR_INV_CRL);
+ ksba_free (serial);
+ goto failure;
+ }
+ p = serial_to_buffer (serial, &n);
+ if (!p)
+ BUG ();
+ record[0] = (reason & 0xff);
+ memcpy (record+1, rdate, 15);
+ rc = cdb_make_add (cdb, p, n, record, 1+15);
+ if (rc)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error inserting item into "
+ "temporary cache file: %s\n"),
+ strerror (errno));
+ goto failure;
+ }
+
+ ksba_free (serial);
+ }
+ break;
+
+ case KSBA_SR_END_ITEMS:
+ break;
+
+ case KSBA_SR_READY:
+ {
+ char *crlissuer;
+ ksba_name_t authid;
+ ksba_sexp_t authidsn;
+ ksba_sexp_t keyid;
+
+ /* We need to look for the issuer only after having read
+ all items. The issuer itselfs comes before the items
+ but the optional authorityKeyIdentifier comes after the
+ items. */
+ err = ksba_crl_get_issuer (crl, &crlissuer);
+ if( err )
+ {
+ log_error (_("no CRL issuer found in CRL: %s\n"),
+ gpg_strerror (err) );
+ err = gpg_error (GPG_ERR_INV_CRL);
+ goto failure;
+ }
+ /* Note: This should be released by ksba_free, not xfree.
+ May need a memory reallocation dance. */
+ *r_crlissuer = crlissuer; /* (Do it here so we don't need
+ to free it later) */
+
+ if (!ksba_crl_get_auth_key_id (crl, &keyid, &authid, &authidsn))
+ {
+ const char *s;
+
+ if (opt.verbose)
+ log_info (_("locating CRL issuer certificate by "
+ "authorityKeyIdentifier\n"));
+
+ s = ksba_name_enum (authid, 0);
+ if (s && *authidsn)
+ crlissuer_cert = find_cert_bysn (ctrl, s, authidsn);
+ if (!crlissuer_cert && keyid)
+ crlissuer_cert = find_cert_bysubject (ctrl,
+ crlissuer, keyid);
+
+ if (!crlissuer_cert)
+ {
+ log_info ("CRL issuer certificate ");
+ if (keyid)
+ {
+ log_printf ("{");
+ dump_serial (keyid);
+ log_printf ("} ");
+ }
+ if (authidsn)
+ {
+ log_printf ("(#");
+ dump_serial (authidsn);
+ log_printf ("/");
+ dump_string (s);
+ log_printf (") ");
+ }
+ log_printf ("not found\n");
+ }
+ ksba_name_release (authid);
+ xfree (authidsn);
+ xfree (keyid);
+ }
+ else
+ crlissuer_cert = find_cert_bysubject (ctrl, crlissuer, NULL);
+ err = 0;
+ if (!crlissuer_cert)
+ {
+ err = gpg_error (GPG_ERR_MISSING_CERT);
+ goto failure;
+ }
+
+ err = finish_sig_check (crl, md, algo, crlissuer_cert);
+ if (err)
+ {
+ log_error (_("CRL signature verification failed: %s\n"),
+ gpg_strerror (err));
+ goto failure;
+ }
+ md = NULL;
+
+ err = validate_cert_chain (ctrl, crlissuer_cert, NULL,
+ VALIDATE_MODE_CRL_RECURSIVE,
+ r_trust_anchor);
+ if (err)
+ {
+ log_error (_("error checking validity of CRL "
+ "issuer certificate: %s\n"),
+ gpg_strerror (err));
+ goto failure;
+ }
+
+ }
+ break;
+
+ default:
+ log_debug ("crl_parse_insert: unknown stop reason\n");
+ err = gpg_error (GPG_ERR_BUG);
+ goto failure;
+ }
+ }
+ while (stopreason != KSBA_SR_READY);
+ assert (!err);
+
+
+ failure:
+ if (md)
+ abort_sig_check (crl, md);
+ ksba_cert_release (crlissuer_cert);
+ return err;
+}
+
+
+
+/* Return the crlNumber extension as an allocated hex string or NULL
+ if there is none. */
+static char *
+get_crl_number (ksba_crl_t crl)
+{
+ gpg_error_t err;
+ ksba_sexp_t number;
+ char *string;
+
+ err = ksba_crl_get_crl_number (crl, &number);
+ if (err)
+ return NULL;
+ string = serial_hex (number);
+ ksba_free (number);
+ return string;
+}
+
+
+/* Return the authorityKeyIdentifier or NULL if it is not available.
+ The issuer name may consists of several parts - they are delimted by
+ 0x01. */
+static char *
+get_auth_key_id (ksba_crl_t crl, char **serialno)
+{
+ gpg_error_t err;
+ ksba_name_t name;
+ ksba_sexp_t sn;
+ int idx;
+ const char *s;
+ char *string;
+ size_t length;
+
+ *serialno = NULL;
+ err = ksba_crl_get_auth_key_id (crl, NULL, &name, &sn);
+ if (err)
+ return NULL;
+ *serialno = serial_hex (sn);
+ ksba_free (sn);
+
+ if (!name)
+ return xstrdup ("");
+
+ length = 0;
+ for (idx=0; (s = ksba_name_enum (name, idx)); idx++)
+ {
+ char *p = ksba_name_get_uri (name, idx);
+ length += strlen (p?p:s) + 1;
+ xfree (p);
+ }
+ string = xtrymalloc (length+1);
+ if (string)
+ {
+ *string = 0;
+ for (idx=0; (s = ksba_name_enum (name, idx)); idx++)
+ {
+ char *p = ksba_name_get_uri (name, idx);
+ if (*string)
+ strcat (string, "\x01");
+ strcat (string, p?p:s);
+ xfree (p);
+ }
+ }
+ ksba_name_release (name);
+ return string;
+}
+
+
+
+/* Insert the CRL retrieved using URL into the cache specified by
+ CACHE. The CRL itself will be read from the stream FP and is
+ expected in binary format.
+
+ Called by:
+ crl_cache_load
+ cmd_loadcrl
+ --load-crl
+ crl_cache_reload_crl
+ cmd_isvalid
+ cmd_checkcrl
+ cmd_loadcrl
+ --fetch-crl
+
+ */
+gpg_error_t
+crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader)
+{
+ crl_cache_t cache = get_current_cache ();
+ gpg_error_t err, err2;
+ ksba_crl_t crl;
+ char *fname = NULL;
+ char *newfname = NULL;
+ struct cdb_make cdb;
+ int fd_cdb = -1;
+ char *issuer = NULL;
+ char *issuer_hash = NULL;
+ ksba_isotime_t thisupdate, nextupdate;
+ crl_cache_entry_t entry = NULL;
+ crl_cache_entry_t e;
+ gnupg_isotime_t current_time;
+ char *checksum = NULL;
+ int invalidate_crl = 0;
+ int idx;
+ const char *oid;
+ int critical;
+ char *trust_anchor = NULL;
+
+ /* FIXME: We should acquire a mutex for the URL, so that we don't
+ simultaneously enter the same CRL twice. However this needs to be
+ interweaved with the checking function.*/
+
+ err2 = 0;
+
+ err = ksba_crl_new (&crl);
+ if (err)
+ {
+ log_error (_("ksba_crl_new failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ err = ksba_crl_set_reader (crl, reader);
+ if ( err )
+ {
+ log_error (_("ksba_crl_set_reader failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Create a temporary cache file to load the CRL into. */
+ {
+ char *tmpfname, *p;
+ const char *nodename;
+#ifndef HAVE_W32_SYSTEM
+ struct utsname utsbuf;
+#endif
+
+#ifdef HAVE_W32_SYSTEM
+ nodename = "unknown";
+#else
+ if (uname (&utsbuf))
+ nodename = "unknown";
+ else
+ nodename = utsbuf.nodename;
+#endif
+
+ gpgrt_asprintf (&tmpfname, "crl-tmp-%s-%u-%p.db.tmp",
+ nodename, (unsigned int)getpid (), &tmpfname);
+ if (!tmpfname)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ for (p=tmpfname; *p; p++)
+ if (*p == '/')
+ *p = '.';
+ fname = make_filename (opt.homedir_cache, DBDIR_D, tmpfname, NULL);
+ xfree (tmpfname);
+ if (!gnupg_remove (fname))
+ log_info (_("removed stale temporary cache file '%s'\n"), fname);
+ else if (errno != ENOENT)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("problem removing stale temporary cache file '%s': %s\n"),
+ fname, gpg_strerror (err));
+ goto leave;
+ }
+ }
+
+ fd_cdb = open (fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd_cdb == -1)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error creating temporary cache file '%s': %s\n"),
+ fname, strerror (errno));
+ goto leave;
+ }
+ cdb_make_start(&cdb, fd_cdb);
+
+ err = crl_parse_insert (ctrl, crl, &cdb, fname,
+ &issuer, thisupdate, nextupdate, &trust_anchor);
+ if (err)
+ {
+ log_error (_("crl_parse_insert failed: %s\n"), gpg_strerror (err));
+ /* Error in cleanup ignored. */
+ cdb_make_finish (&cdb);
+ goto leave;
+ }
+
+ /* Finish the database. */
+ if (cdb_make_finish (&cdb))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error finishing temporary cache file '%s': %s\n"),
+ fname, strerror (errno));
+ goto leave;
+ }
+ if (close (fd_cdb))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error closing temporary cache file '%s': %s\n"),
+ fname, strerror (errno));
+ goto leave;
+ }
+ fd_cdb = -1;
+
+
+ /* Create a checksum. */
+ {
+ unsigned char md5buf[16];
+
+ if (hash_dbfile (fname, md5buf))
+ {
+ err = gpg_error (GPG_ERR_CHECKSUM);
+ goto leave;
+ }
+ checksum = hexify_data (md5buf, 16);
+ }
+
+
+ /* Check whether that new CRL is still not expired. */
+ gnupg_get_isotime (current_time);
+ if (strcmp (nextupdate, current_time) < 0 )
+ {
+ if (opt.force)
+ log_info (_("WARNING: new CRL still too old; it expired on %s "
+ "- loading anyway\n"), nextupdate);
+ else
+ {
+ log_error (_("new CRL still too old; it expired on %s\n"),
+ nextupdate);
+ if (!err2)
+ err2 = gpg_error (GPG_ERR_CRL_TOO_OLD);
+ invalidate_crl |= 1;
+ }
+ }
+
+ /* Check for unknown critical extensions. */
+ for (idx=0; !(err=ksba_crl_get_extension (crl, idx, &oid, &critical,
+ NULL, NULL)); idx++)
+ {
+ if (!critical
+ || !strcmp (oid, oidstr_authorityKeyIdentifier)
+ || !strcmp (oid, oidstr_crlNumber) )
+ continue;
+ log_error (_("unknown critical CRL extension %s\n"), oid);
+ if (!err2)
+ err2 = gpg_error (GPG_ERR_INV_CRL);
+ invalidate_crl |= 2;
+ }
+ if (gpg_err_code (err) == GPG_ERR_EOF
+ || gpg_err_code (err) == GPG_ERR_NO_DATA )
+ err = 0;
+ if (err)
+ {
+ log_error (_("error reading CRL extensions: %s\n"), gpg_strerror (err));
+ err = gpg_error (GPG_ERR_INV_CRL);
+ }
+
+
+ /* Create an hex encoded SHA-1 hash of the issuer DN to be
+ used as the key for the cache. */
+ issuer_hash = hashify_data (issuer, strlen (issuer));
+
+ /* Create an ENTRY. */
+ entry = xtrycalloc (1, sizeof *entry);
+ if (!entry)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ entry->release_ptr = xtrymalloc (strlen (issuer_hash) + 1
+ + strlen (issuer) + 1
+ + strlen (url) + 1
+ + strlen (checksum) + 1);
+ if (!entry->release_ptr)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (entry);
+ entry = NULL;
+ goto leave;
+ }
+ entry->issuer_hash = entry->release_ptr;
+ entry->issuer = stpcpy (entry->issuer_hash, issuer_hash) + 1;
+ entry->url = stpcpy (entry->issuer, issuer) + 1;
+ entry->dbfile_hash = stpcpy (entry->url, url) + 1;
+ strcpy (entry->dbfile_hash, checksum);
+ gnupg_copy_time (entry->this_update, thisupdate);
+ gnupg_copy_time (entry->next_update, nextupdate);
+ gnupg_copy_time (entry->last_refresh, current_time);
+ entry->crl_number = get_crl_number (crl);
+ entry->authority_issuer = get_auth_key_id (crl, &entry->authority_serialno);
+ entry->invalid = invalidate_crl;
+ entry->user_trust_req = !!trust_anchor;
+ entry->check_trust_anchor = trust_anchor;
+ trust_anchor = NULL;
+
+ /* Check whether we already have an entry for this issuer and mark
+ it as deleted. We better use a loop, just in case duplicates got
+ somehow into the list. */
+ for (e = cache->entries; (e=find_entry (e, entry->issuer_hash)); e = e->next)
+ e->deleted = 1;
+
+ /* Rename the temporary DB to the real name. */
+ newfname = make_db_file_name (entry->issuer_hash);
+ if (opt.verbose)
+ log_info (_("creating cache file '%s'\n"), newfname);
+
+ /* Just in case close unused matching files. Actually we need this
+ only under Windows but saving file descriptors is never bad. */
+ {
+ int any;
+ do
+ {
+ any = 0;
+ for (e = cache->entries; e; e = e->next)
+ if (!e->cdb_use_count && e->cdb
+ && !strcmp (e->issuer_hash, entry->issuer_hash))
+ {
+ int fd = cdb_fileno (e->cdb);
+ cdb_free (e->cdb);
+ xfree (e->cdb);
+ e->cdb = NULL;
+ if (close (fd))
+ log_error (_("error closing cache file: %s\n"),
+ strerror(errno));
+ any = 1;
+ break;
+ }
+ }
+ while (any);
+ }
+#ifdef HAVE_W32_SYSTEM
+ gnupg_remove (newfname);
+#endif
+ if (rename (fname, newfname))
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("problem renaming '%s' to '%s': %s\n"),
+ fname, newfname, gpg_strerror (err));
+ goto leave;
+ }
+ xfree (fname); fname = NULL; /*(let the cleanup code not try to remove it)*/
+
+ /* Link the new entry in. */
+ entry->next = cache->entries;
+ cache->entries = entry;
+ entry = NULL;
+
+ err = update_dir (cache);
+ if (err)
+ {
+ log_error (_("updating the DIR file failed - "
+ "cache entry will get lost with the next program start\n"));
+ err = 0; /* Keep on running. */
+ }
+
+
+ leave:
+ release_one_cache_entry (entry);
+ if (fd_cdb != -1)
+ close (fd_cdb);
+ if (fname)
+ {
+ gnupg_remove (fname);
+ xfree (fname);
+ }
+ xfree (newfname);
+ ksba_crl_release (crl);
+ xfree (issuer);
+ xfree (issuer_hash);
+ xfree (checksum);
+ xfree (trust_anchor);
+ return err ? err : err2;
+}
+
+
+/* Print one cached entry E in a human readable format to stream
+ FP. Return 0 on success. */
+static gpg_error_t
+list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp)
+{
+ struct cdb_find cdbfp;
+ struct cdb *cdb;
+ int rc;
+ int warn = 0;
+ const unsigned char *s;
+
+ es_fputs ("--------------------------------------------------------\n", fp );
+ es_fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url );
+ es_fprintf (fp, " Issuer:\t%s\n", e->issuer );
+ es_fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash );
+ es_fprintf (fp, " This Update:\t%s\n", e->this_update );
+ es_fprintf (fp, " Next Update:\t%s\n", e->next_update );
+ es_fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none");
+ es_fprintf (fp, " AuthKeyId :\t%s\n",
+ e->authority_serialno? e->authority_serialno:"none");
+ if (e->authority_serialno && e->authority_issuer)
+ {
+ es_fputs (" \t", fp);
+ for (s=e->authority_issuer; *s; s++)
+ if (*s == '\x01')
+ es_fputs ("\n \t", fp);
+ else
+ es_putc (*s, fp);
+ es_putc ('\n', fp);
+ }
+ es_fprintf (fp, " Trust Check:\t%s\n",
+ !e->user_trust_req? "[system]" :
+ e->check_trust_anchor? e->check_trust_anchor:"[missing]");
+
+ if ((e->invalid & 1))
+ es_fprintf (fp, _(" ERROR: The CRL will not be used "
+ "because it was still too old after an update!\n"));
+ if ((e->invalid & 2))
+ es_fprintf (fp, _(" ERROR: The CRL will not be used "
+ "due to an unknown critical extension!\n"));
+ if ((e->invalid & ~3))
+ es_fprintf (fp, _(" ERROR: The CRL will not be used\n"));
+
+ cdb = lock_db_file (cache, e);
+ if (!cdb)
+ return gpg_error (GPG_ERR_GENERAL);
+
+ if (!e->dbfile_checked)
+ es_fprintf (fp, _(" ERROR: This cached CRL may have been tampered with!\n"));
+
+ es_putc ('\n', fp);
+
+ rc = cdb_findinit (&cdbfp, cdb, NULL, 0);
+ while (!rc && (rc=cdb_findnext (&cdbfp)) > 0 )
+ {
+ unsigned char keyrecord[256];
+ unsigned char record[16];
+ int reason;
+ int any = 0;
+ cdbi_t n;
+ cdbi_t i;
+
+ rc = 0;
+ n = cdb_datalen (cdb);
+ if (n != 16)
+ {
+ log_error (_(" WARNING: invalid cache record length\n"));
+ warn = 1;
+ continue;
+ }
+
+ if (cdb_read (cdb, record, n, cdb_datapos (cdb)))
+ {
+ log_error (_("problem reading cache record: %s\n"),
+ strerror (errno));
+ warn = 1;
+ continue;
+ }
+
+ n = cdb_keylen (cdb);
+ if (n > sizeof keyrecord)
+ n = sizeof keyrecord;
+ if (cdb_read (cdb, keyrecord, n, cdb_keypos (cdb)))
+ {
+ log_error (_("problem reading cache key: %s\n"), strerror (errno));
+ warn = 1;
+ continue;
+ }
+
+ reason = *record;
+ es_fputs (" ", fp);
+ for (i = 0; i < n; i++)
+ es_fprintf (fp, "%02X", keyrecord[i]);
+ es_fputs (":\t reasons( ", fp);
+
+ if (reason & KSBA_CRLREASON_UNSPECIFIED)
+ es_fputs( "unspecified ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_KEY_COMPROMISE )
+ es_fputs( "key_compromise ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CA_COMPROMISE )
+ es_fputs( "ca_compromise ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED )
+ es_fputs( "affiliation_changed ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_SUPERSEDED )
+ es_fputs( "superseeded", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION )
+ es_fputs( "cessation_of_operation", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD )
+ es_fputs( "certificate_hold", fp ), any = 1;
+ if (reason && !any)
+ es_fputs( "other", fp );
+
+ es_fprintf (fp, ") rdate: %.15s\n", record+1);
+ }
+ if (rc)
+ log_error (_("error reading cache entry from db: %s\n"), strerror (rc));
+
+ unlock_db_file (cache, e);
+ es_fprintf (fp, _("End CRL dump\n") );
+ es_putc ('\n', fp);
+
+ return (rc||warn)? gpg_error (GPG_ERR_GENERAL) : 0;
+}
+
+
+/* Print the contents of the CRL CACHE in a human readable format to
+ stream FP. */
+gpg_error_t
+crl_cache_list (estream_t fp)
+{
+ crl_cache_t cache = get_current_cache ();
+ crl_cache_entry_t entry;
+ gpg_error_t err = 0;
+
+ for (entry = cache->entries;
+ entry && !entry->deleted && !err;
+ entry = entry->next )
+ err = list_one_crl_entry (cache, entry, fp);
+
+ return err;
+}
+
+
+/* Load the CRL containing the file named FILENAME into our CRL cache. */
+gpg_error_t
+crl_cache_load (ctrl_t ctrl, const char *filename)
+{
+ gpg_error_t err;
+ estream_t fp;
+ ksba_reader_t reader;
+
+ fp = es_fopen (filename, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
+ return err;
+ }
+
+ err = create_estream_ksba_reader (&reader, fp);
+ if (!err)
+ {
+ err = crl_cache_insert (ctrl, filename, reader);
+ ksba_reader_release (reader);
+ }
+ es_fclose (fp);
+ return err;
+}
+
+
+/* Locate the corresponding CRL for the certificate CERT, read and
+ verify the CRL and store it in the cache. */
+gpg_error_t
+crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert)
+{
+ gpg_error_t err;
+ ksba_reader_t reader = NULL;
+ char *issuer = NULL;
+ ksba_name_t distpoint = NULL;
+ ksba_name_t issuername = NULL;
+ char *distpoint_uri = NULL;
+ char *issuername_uri = NULL;
+ int any_dist_point = 0;
+ int seq;
+
+ /* Loop over all distribution points, get the CRLs and put them into
+ the cache. */
+ if (opt.verbose)
+ log_info ("checking distribution points\n");
+ seq = 0;
+ while ( !(err = ksba_cert_get_crl_dist_point (cert, seq++,
+ &distpoint,
+ &issuername, NULL )))
+ {
+ int name_seq;
+ gpg_error_t last_err = 0;
+
+ if (!distpoint && !issuername)
+ {
+ if (opt.verbose)
+ log_info ("no issuer name and no distribution point\n");
+ break; /* Not allowed; i.e. an invalid certificate. We give
+ up here and hope that the default method returns a
+ suitable CRL. */
+ }
+
+ xfree (issuername_uri); issuername_uri = NULL;
+
+ /* Get the URIs. We do this in a loop to iterate over all names
+ in the crlDP. */
+ for (name_seq=0; ksba_name_enum (distpoint, name_seq); name_seq++)
+ {
+ xfree (distpoint_uri); distpoint_uri = NULL;
+ distpoint_uri = ksba_name_get_uri (distpoint, name_seq);
+ if (!distpoint_uri)
+ continue;
+
+ if (!strncmp (distpoint_uri, "ldap:", 5)
+ || !strncmp (distpoint_uri, "ldaps:", 6))
+ {
+ if (opt.ignore_ldap_dp)
+ continue;
+ }
+ else if (!strncmp (distpoint_uri, "http:", 5)
+ || !strncmp (distpoint_uri, "https:", 6))
+ {
+ if (opt.ignore_http_dp)
+ continue;
+ }
+ else
+ continue; /* Skip unknown schemes. */
+
+ any_dist_point = 1;
+
+ if (opt.verbose)
+ log_info ("fetching CRL from '%s'\n", distpoint_uri);
+ err = crl_fetch (ctrl, distpoint_uri, &reader);
+ if (err)
+ {
+ log_error (_("crl_fetch via DP failed: %s\n"),
+ gpg_strerror (err));
+ last_err = err;
+ continue; /* with the next name. */
+ }
+
+ if (opt.verbose)
+ log_info ("inserting CRL (reader %p)\n", reader);
+ err = crl_cache_insert (ctrl, distpoint_uri, reader);
+ if (err)
+ {
+ log_error (_("crl_cache_insert via DP failed: %s\n"),
+ gpg_strerror (err));
+ last_err = err;
+ continue; /* with the next name. */
+ }
+ last_err = 0;
+ break; /* Ready. */
+ }
+ if (last_err)
+ {
+ err = last_err;
+ goto leave;
+ }
+
+ ksba_name_release (distpoint); distpoint = NULL;
+
+ /* We don't do anything with issuername_uri yet but we keep the
+ code for documentation. */
+ issuername_uri = ksba_name_get_uri (issuername, 0);
+ ksba_name_release (issuername); issuername = NULL;
+
+ }
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ err = 0;
+
+ /* If we did not found any distpoint, try something reasonable. */
+ if (!any_dist_point )
+ {
+ if (opt.verbose)
+ log_info ("no distribution point - trying issuer name\n");
+
+ if (reader)
+ {
+ crl_close_reader (reader);
+ reader = NULL;
+ }
+
+ issuer = ksba_cert_get_issuer (cert, 0);
+ if (!issuer)
+ {
+ log_error ("oops: issuer missing in certificate\n");
+ err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("fetching CRL from default location\n");
+ err = crl_fetch_default (ctrl, issuer, &reader);
+ if (err)
+ {
+ log_error ("crl_fetch via issuer failed: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+
+ if (opt.verbose)
+ log_info ("inserting CRL (reader %p)\n", reader);
+ err = crl_cache_insert (ctrl, "default location(s)", reader);
+ if (err)
+ {
+ log_error (_("crl_cache_insert via issuer failed: %s\n"),
+ gpg_strerror (err));
+ goto leave;
+ }
+ }
+
+ leave:
+ if (reader)
+ crl_close_reader (reader);
+ xfree (distpoint_uri);
+ xfree (issuername_uri);
+ ksba_name_release (distpoint);
+ ksba_name_release (issuername);
+ ksba_free (issuer);
+ return err;
+}
diff --git a/dirmngr/crlcache.h b/dirmngr/crlcache.h
new file mode 100644
index 0000000..6e9dc28
--- /dev/null
+++ b/dirmngr/crlcache.h
@@ -0,0 +1,70 @@
+/* crlcache.h - LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef CRLCACHE_H
+#define CRLCACHE_H
+
+
+typedef enum
+ {
+ CRL_CACHE_VALID = 0,
+ CRL_CACHE_INVALID,
+ CRL_CACHE_DONTKNOW,
+ CRL_CACHE_CANTUSE
+ }
+crl_cache_result_t;
+
+typedef enum foo
+ {
+ CRL_SIG_OK = 0,
+ CRL_SIG_NOT_OK,
+ CRL_TOO_OLD,
+ CRL_SIG_ERROR,
+ CRL_GENERAL_ERROR
+ }
+crl_sig_result_t;
+
+struct crl_cache_entry_s;
+typedef struct crl_cache_entry_s *crl_cache_entry_t;
+
+
+void crl_cache_init (void);
+void crl_cache_deinit (void);
+int crl_cache_flush(void);
+
+crl_cache_result_t crl_cache_isvalid (ctrl_t ctrl,
+ const char *issuer_hash,
+ const char *cert_id,
+ int force_refresh);
+
+gpg_error_t crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert,
+ int force_refresh);
+
+gpg_error_t crl_cache_insert (ctrl_t ctrl, const char *url,
+ ksba_reader_t reader);
+
+gpg_error_t crl_cache_list (estream_t fp);
+
+gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename);
+
+gpg_error_t crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert);
+
+
+#endif /* CRLCACHE_H */
diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c
new file mode 100644
index 0000000..2471ca2
--- /dev/null
+++ b/dirmngr/crlfetch.c
@@ -0,0 +1,540 @@
+/* crlfetch.c - LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2005, 2006, 2007 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <errno.h>
+#include <npth.h>
+
+#include "crlfetch.h"
+#include "dirmngr.h"
+#include "misc.h"
+#include "http.h"
+
+#if USE_LDAP
+# include "ldap-wrapper.h"
+#endif
+
+/* For detecting armored CRLs received via HTTP (yes, such CRLS really
+ exits, e.g. http://grid.fzk.de/ca/gridka-crl.pem at least in June
+ 2008) we need a context in the reader callback. */
+struct reader_cb_context_s
+{
+ estream_t fp; /* The stream used with the ksba reader. */
+ int checked:1; /* PEM/binary detection ahs been done. */
+ int is_pem:1; /* The file stream is PEM encoded. */
+ struct b64state b64state; /* The state used for Base64 decoding. */
+};
+
+
+/* We need to associate a reader object with the reader callback
+ context. This table is used for it. */
+struct file_reader_map_s
+{
+ ksba_reader_t reader;
+ struct reader_cb_context_s *cb_ctx;
+};
+#define MAX_FILE_READER 50
+static struct file_reader_map_s file_reader_map[MAX_FILE_READER];
+
+/* Associate FP with READER. If the table is full wait until another
+ thread has removed an entry. */
+static void
+register_file_reader (ksba_reader_t reader, struct reader_cb_context_s *cb_ctx)
+{
+ int i;
+
+ for (;;)
+ {
+ for (i=0; i < MAX_FILE_READER; i++)
+ if (!file_reader_map[i].reader)
+ {
+ file_reader_map[i].reader = reader;
+ file_reader_map[i].cb_ctx = cb_ctx;
+ return;
+ }
+ log_info (_("reader to file mapping table full - waiting\n"));
+ npth_sleep (2);
+ }
+}
+
+/* Scan the table for an entry matching READER, remove that entry and
+ return the associated file pointer. */
+static struct reader_cb_context_s *
+get_file_reader (ksba_reader_t reader)
+{
+ struct reader_cb_context_s *cb_ctx = NULL;
+ int i;
+
+ for (i=0; i < MAX_FILE_READER; i++)
+ if (file_reader_map[i].reader == reader)
+ {
+ cb_ctx = file_reader_map[i].cb_ctx;
+ file_reader_map[i].reader = NULL;
+ file_reader_map[i].cb_ctx = NULL;
+ break;
+ }
+ return cb_ctx;
+}
+
+
+
+static int
+my_es_read (void *opaque, char *buffer, size_t nbytes, size_t *nread)
+{
+ struct reader_cb_context_s *cb_ctx = opaque;
+ int result;
+
+ result = es_read (cb_ctx->fp, buffer, nbytes, nread);
+ if (result)
+ return result;
+ /* Fixme we should check whether the semantics of es_read are okay
+ and well defined. I have some doubts. */
+ if (nbytes && !*nread && es_feof (cb_ctx->fp))
+ return gpg_error (GPG_ERR_EOF);
+ if (!nread && es_ferror (cb_ctx->fp))
+ return gpg_error (GPG_ERR_EIO);
+
+ if (!cb_ctx->checked && *nread)
+ {
+ int c = *(unsigned char *)buffer;
+
+ cb_ctx->checked = 1;
+ if ( ((c & 0xc0) >> 6) == 0 /* class: universal */
+ && (c & 0x1f) == 16 /* sequence */
+ && (c & 0x20) /* is constructed */ )
+ ; /* Binary data. */
+ else
+ {
+ cb_ctx->is_pem = 1;
+ b64dec_start (&cb_ctx->b64state, "");
+ }
+ }
+ if (cb_ctx->is_pem && *nread)
+ {
+ size_t nread2;
+
+ if (b64dec_proc (&cb_ctx->b64state, buffer, *nread, &nread2))
+ {
+ /* EOF from decoder. */
+ *nread = 0;
+ result = gpg_error (GPG_ERR_EOF);
+ }
+ else
+ *nread = nread2;
+ }
+
+ return result;
+}
+
+
+/* Fetch CRL from URL and return the entire CRL using new ksba reader
+ object in READER. Note that this reader object should be closed
+ only using ldap_close_reader. */
+gpg_error_t
+crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader)
+{
+ gpg_error_t err;
+ parsed_uri_t uri;
+ char *free_this = NULL;
+ int redirects_left = 2; /* We allow for 2 redirect levels. */
+
+#ifndef USE_LDAP
+ (void)ctrl;
+#endif
+
+ *reader = NULL;
+
+ once_more:
+ err = http_parse_uri (&uri, url, 0);
+ http_release_parsed_uri (uri);
+ if (err && url && !strncmp (url, "https:", 6))
+ {
+ /* Our HTTP code does not support TLS, thus we can't use this
+ scheme and it is frankly not useful for CRL retrieval anyway.
+ We resort to using http, assuming that the server also
+ provides plain http access. */
+ free_this = xtrymalloc (strlen (url) + 1);
+ if (free_this)
+ {
+ strcpy (stpcpy (free_this,"http:"), url+6);
+ err = http_parse_uri (&uri, free_this, 0);
+ http_release_parsed_uri (uri);
+ if (!err)
+ {
+ log_info (_("using \"http\" instead of \"https\"\n"));
+ url = free_this;
+ }
+ }
+ }
+ if (!err) /* Yes, our HTTP code groks that. */
+ {
+ http_t hd;
+
+ if (opt.disable_http)
+ {
+ log_error (_("CRL access not possible due to disabled %s\n"),
+ "HTTP");
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+ else
+ err = http_open_document (&hd, url, NULL,
+ (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
+ |(DBG_LOOKUP? HTTP_FLAG_LOG_RESP:0),
+ opt.http_proxy, NULL, NULL, NULL);
+
+ switch ( err? 99999 : http_get_status_code (hd) )
+ {
+ case 200:
+ {
+ estream_t fp = http_get_read_ptr (hd);
+ struct reader_cb_context_s *cb_ctx;
+
+ cb_ctx = xtrycalloc (1, sizeof *cb_ctx);
+ if (!cb_ctx)
+ err = gpg_error_from_syserror ();
+ if (!err)
+ err = ksba_reader_new (reader);
+ if (!err)
+ {
+ cb_ctx->fp = fp;
+ err = ksba_reader_set_cb (*reader, &my_es_read, cb_ctx);
+ }
+ if (err)
+ {
+ log_error (_("error initializing reader object: %s\n"),
+ gpg_strerror (err));
+ ksba_reader_release (*reader);
+ *reader = NULL;
+ http_close (hd, 0);
+ }
+ else
+ {
+ /* The ksba reader misses a user pointer thus we need
+ to come up with our own way of associating a file
+ pointer (or well the callback context) with the
+ reader. It is only required when closing the
+ reader thus there is no performance issue doing it
+ this way. FIXME: We now have a close notification
+ which might be used here. */
+ register_file_reader (*reader, cb_ctx);
+ http_close (hd, 1);
+ }
+ }
+ break;
+
+ case 301: /* Redirection (perm.). */
+ case 302: /* Redirection (temp.). */
+ {
+ const char *s = http_get_header (hd, "Location");
+
+ log_info (_("URL '%s' redirected to '%s' (%u)\n"),
+ url, s?s:"[none]", http_get_status_code (hd));
+ if (s && *s && redirects_left-- )
+ {
+ xfree (free_this); url = NULL;
+ free_this = xtrystrdup (s);
+ if (!free_this)
+ err = gpg_error_from_errno (errno);
+ else
+ {
+ url = free_this;
+ http_close (hd, 0);
+ /* Note, that our implementation of redirection
+ actually handles a redirect to LDAP. */
+ goto once_more;
+ }
+ }
+ else
+ err = gpg_error (GPG_ERR_NO_DATA);
+ log_error (_("too many redirections\n")); /* Or no "Location". */
+ http_close (hd, 0);
+ }
+ break;
+
+ case 99999: /* Made up status code for error reporting. */
+ log_error (_("error retrieving '%s': %s\n"),
+ url, gpg_strerror (err));
+ break;
+
+ default:
+ log_error (_("error retrieving '%s': http status %u\n"),
+ url, http_get_status_code (hd));
+ err = gpg_error (GPG_ERR_NO_DATA);
+ http_close (hd, 0);
+ }
+ }
+ else /* Let the LDAP code try other schemes. */
+ {
+ if (opt.disable_ldap)
+ {
+ log_error (_("CRL access not possible due to disabled %s\n"),
+ "LDAP");
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+ else
+ {
+# if USE_LDAP
+ err = url_fetch_ldap (ctrl, url, NULL, 0, reader);
+# else /*!USE_LDAP*/
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+# endif /*!USE_LDAP*/
+ }
+ }
+
+ xfree (free_this);
+ return err;
+}
+
+
+/* Fetch CRL for ISSUER using a default server. Return the entire CRL
+ as a newly opened stream returned in R_FP. */
+gpg_error_t
+crl_fetch_default (ctrl_t ctrl, const char *issuer, ksba_reader_t *reader)
+{
+ if (opt.disable_ldap)
+ {
+ log_error (_("CRL access not possible due to disabled %s\n"),
+ "LDAP");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+#if USE_LDAP
+ return attr_fetch_ldap (ctrl, issuer, "certificateRevocationList",
+ reader);
+#else
+ (void)ctrl;
+ (void)issuer;
+ (void)reader;
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif
+}
+
+
+/* Fetch a CA certificate for DN using the default server. This
+ function only initiates the fetch; fetch_next_cert must be used to
+ actually read the certificate; end_cert_fetch to end the
+ operation. */
+gpg_error_t
+ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, const char *dn)
+{
+ if (opt.disable_ldap)
+ {
+ log_error (_("CRL access not possible due to disabled %s\n"),
+ "LDAP");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+#if USE_LDAP
+ return start_default_fetch_ldap (ctrl, context, dn, "cACertificate");
+#else
+ (void)ctrl;
+ (void)context;
+ (void)dn;
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif
+}
+
+
+gpg_error_t
+start_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context,
+ strlist_t patterns, const ldap_server_t server)
+{
+ if (opt.disable_ldap)
+ {
+ log_error (_("certificate search not possible due to disabled %s\n"),
+ "LDAP");
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+#if USE_LDAP
+ return start_cert_fetch_ldap (ctrl, context, patterns, server);
+#else
+ (void)ctrl;
+ (void)context;
+ (void)patterns;
+ (void)server;
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif
+}
+
+
+gpg_error_t
+fetch_next_cert (cert_fetch_context_t context,
+ unsigned char **value, size_t * valuelen)
+{
+#if USE_LDAP
+ return fetch_next_cert_ldap (context, value, valuelen);
+#else
+ (void)context;
+ (void)value;
+ (void)valuelen;
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif
+}
+
+
+/* Fetch the next data from CONTEXT, assuming it is a certificate and return
+ it as a cert object in R_CERT. */
+gpg_error_t
+fetch_next_ksba_cert (cert_fetch_context_t context, ksba_cert_t *r_cert)
+{
+ gpg_error_t err;
+ unsigned char *value;
+ size_t valuelen;
+ ksba_cert_t cert;
+
+ *r_cert = NULL;
+
+#if USE_LDAP
+ err = fetch_next_cert_ldap (context, &value, &valuelen);
+ if (!err && !value)
+ err = gpg_error (GPG_ERR_BUG);
+#else
+ (void)context;
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif
+ if (err)
+ return err;
+
+ err = ksba_cert_new (&cert);
+ if (err)
+ {
+ xfree (value);
+ return err;
+ }
+
+ err = ksba_cert_init_from_mem (cert, value, valuelen);
+ xfree (value);
+ if (err)
+ {
+ ksba_cert_release (cert);
+ return err;
+ }
+ *r_cert = cert;
+ return 0;
+}
+
+
+void
+end_cert_fetch (cert_fetch_context_t context)
+{
+#if USE_LDAP
+ end_cert_fetch_ldap (context);
+#else
+ (void)context;
+#endif
+}
+
+
+/* Lookup a cert by it's URL. */
+gpg_error_t
+fetch_cert_by_url (ctrl_t ctrl, const char *url,
+ unsigned char **value, size_t *valuelen)
+{
+ const unsigned char *cert_image;
+ size_t cert_image_n;
+ ksba_reader_t reader;
+ ksba_cert_t cert;
+ gpg_error_t err;
+
+ *value = NULL;
+ *valuelen = 0;
+ cert_image = NULL;
+ reader = NULL;
+ cert = NULL;
+
+#if USE_LDAP
+ err = url_fetch_ldap (ctrl, url, NULL, 0, &reader);
+#else
+ (void)ctrl;
+ (void)url;
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+#endif /*USE_LDAP*/
+ if (err)
+ goto leave;
+
+ err = ksba_cert_new (&cert);
+ if (err)
+ goto leave;
+
+ err = ksba_cert_read_der (cert, reader);
+ if (err)
+ goto leave;
+
+ cert_image = ksba_cert_get_image (cert, &cert_image_n);
+ if (!cert_image || !cert_image_n)
+ {
+ err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+ goto leave;
+ }
+
+ *value = xtrymalloc (cert_image_n);
+ if (!*value)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ memcpy (*value, cert_image, cert_image_n);
+ *valuelen = cert_image_n;
+
+ leave:
+
+ ksba_cert_release (cert);
+#if USE_LDAP
+ ldap_wrapper_release_context (reader);
+#endif /*USE_LDAP*/
+
+ return err;
+}
+
+/* This function is to be used to close the reader object. In
+ addition to running ksba_reader_release it also releases the LDAP
+ or HTTP contexts associated with that reader. */
+void
+crl_close_reader (ksba_reader_t reader)
+{
+ struct reader_cb_context_s *cb_ctx;
+
+ if (!reader)
+ return;
+
+ /* Check whether this is a HTTP one. */
+ cb_ctx = get_file_reader (reader);
+ if (cb_ctx)
+ {
+ /* This is an HTTP context. */
+ if (cb_ctx->fp)
+ es_fclose (cb_ctx->fp);
+ /* Release the base64 decoder state. */
+ if (cb_ctx->is_pem)
+ b64dec_finish (&cb_ctx->b64state);
+ /* Release the callback context. */
+ xfree (cb_ctx);
+ }
+ else /* This is an ldap wrapper context (Currently not used). */
+ {
+#if USE_LDAP
+ ldap_wrapper_release_context (reader);
+#endif /*USE_LDAP*/
+ }
+
+ /* Now get rid of the reader object. */
+ ksba_reader_release (reader);
+}
diff --git a/dirmngr/crlfetch.h b/dirmngr/crlfetch.h
new file mode 100644
index 0000000..dd28238
--- /dev/null
+++ b/dirmngr/crlfetch.h
@@ -0,0 +1,88 @@
+/* crlfetch.h - LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef CRLFETCH_H
+#define CRLFETCH_H
+
+#include "dirmngr.h"
+
+
+struct cert_fetch_context_s;
+typedef struct cert_fetch_context_s *cert_fetch_context_t;
+
+
+/* Fetch CRL from URL. */
+gpg_error_t crl_fetch (ctrl_t ctrl, const char* url, ksba_reader_t *reader);
+
+/* Fetch CRL for ISSUER using default server. */
+gpg_error_t crl_fetch_default (ctrl_t ctrl,
+ const char* issuer, ksba_reader_t *reader);
+
+
+/* Fetch cert for DN. */
+gpg_error_t ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context,
+ const char *dn);
+
+
+/* Query the server for certs matching patterns. */
+gpg_error_t start_cert_fetch (ctrl_t ctrl,
+ cert_fetch_context_t *context,
+ strlist_t patterns,
+ const ldap_server_t server);
+gpg_error_t fetch_next_cert(cert_fetch_context_t context,
+ unsigned char **value, size_t *valuelen);
+gpg_error_t fetch_next_ksba_cert (cert_fetch_context_t context,
+ ksba_cert_t *r_cert);
+void end_cert_fetch (cert_fetch_context_t context);
+
+/* Lookup a cert by it's URL. */
+gpg_error_t fetch_cert_by_url (ctrl_t ctrl, const char *url,
+ unsigned char **value, size_t *valuelen);
+
+/* Close a reader object. */
+void crl_close_reader (ksba_reader_t reader);
+
+
+
+/*-- ldap.c --*/
+gpg_error_t url_fetch_ldap (ctrl_t ctrl,
+ const char *url, const char *host, int port,
+ ksba_reader_t *reader);
+gpg_error_t attr_fetch_ldap (ctrl_t ctrl,
+ const char *dn, const char *attr,
+ ksba_reader_t *reader);
+
+
+gpg_error_t start_default_fetch_ldap (ctrl_t ctrl,
+ cert_fetch_context_t *context,
+ const char *dn, const char *attr);
+gpg_error_t start_cert_fetch_ldap( ctrl_t ctrl,
+ cert_fetch_context_t *context,
+ strlist_t patterns,
+ const ldap_server_t server );
+gpg_error_t fetch_next_cert_ldap (cert_fetch_context_t context,
+ unsigned char **value, size_t *valuelen );
+void end_cert_fetch_ldap (cert_fetch_context_t context);
+
+
+
+
+
+
+#endif /* CRLFETCH_H */
diff --git a/dirmngr/dirmngr-client.c b/dirmngr/dirmngr-client.c
new file mode 100644
index 0000000..0e62764
--- /dev/null
+++ b/dirmngr/dirmngr-client.c
@@ -0,0 +1,1031 @@
+/* dirmngr-client.c - A client for the dirmngr daemon
+ * Copyright (C) 2004, 2007 g10 Code GmbH
+ * Copyright (C) 2002, 2003 Free Software Foundation, Inc.
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include <gpg-error.h>
+#include <assuan.h>
+
+#define JNLIB_NEED_LOG_LOGV
+#include "../common/logging.h"
+#include "../common/argparse.h"
+#include "../common/stringhelp.h"
+#include "../common/mischelp.h"
+#include "../common/strlist.h"
+
+#include "i18n.h"
+#include "util.h"
+
+
+/* Constants for the options. */
+enum
+ {
+ oQuiet = 'q',
+ oVerbose = 'v',
+ oLocal = 'l',
+ oUrl = 'u',
+
+ oOCSP = 500,
+ oPing,
+ oCacheCert,
+ oValidate,
+ oLookup,
+ oLoadCRL,
+ oSquidMode,
+ oPEM,
+ oEscapedPEM,
+ oForceDefaultResponder
+ };
+
+
+/* The list of options as used by the argparse.c code. */
+static ARGPARSE_OPTS opts[] = {
+ { oVerbose, "verbose", 0, N_("verbose") },
+ { oQuiet, "quiet", 0, N_("be somewhat more quiet") },
+ { oOCSP, "ocsp", 0, N_("use OCSP instead of CRLs") },
+ { oPing, "ping", 0, N_("check whether a dirmngr is running")},
+ { oCacheCert,"cache-cert",0, N_("add a certificate to the cache")},
+ { oValidate, "validate", 0, N_("validate a certificate")},
+ { oLookup, "lookup", 0, N_("lookup a certificate")},
+ { oLocal, "local", 0, N_("lookup only locally stored certificates")},
+ { oUrl, "url", 0, N_("expect an URL for --lookup")},
+ { oLoadCRL, "load-crl", 0, N_("load a CRL into the dirmngr")},
+ { oSquidMode,"squid-mode",0, N_("special mode for use by Squid")},
+ { oPEM, "pem", 0, N_("expect certificates in PEM format")},
+ { oForceDefaultResponder, "force-default-responder", 0,
+ N_("force the use of the default OCSP responder")},
+ { 0, NULL, 0, NULL }
+};
+
+
+/* The usual structure for the program flags. */
+static struct
+{
+ int quiet;
+ int verbose;
+ const char *dirmngr_program;
+ int force_pipe_server;
+ int force_default_responder;
+ int pem;
+ int escaped_pem; /* PEM is additional percent encoded. */
+ int url; /* Expect an URL. */
+ int local; /* Lookup up only local certificates. */
+
+ int use_ocsp;
+} opt;
+
+
+/* Communication structure for the certificate inquire callback. */
+struct inq_cert_parm_s
+{
+ assuan_context_t ctx;
+ const unsigned char *cert;
+ size_t certlen;
+};
+
+
+/* Base64 conversion tables. */
+static unsigned char bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+static unsigned char asctobin[256]; /* runtime initialized */
+
+
+/* Prototypes. */
+static assuan_context_t start_dirmngr (int only_daemon);
+static gpg_error_t read_certificate (const char *fname,
+ unsigned char **rbuf, size_t *rbuflen);
+static gpg_error_t do_check (assuan_context_t ctx,
+ const unsigned char *cert, size_t certlen);
+static gpg_error_t do_cache (assuan_context_t ctx,
+ const unsigned char *cert, size_t certlen);
+static gpg_error_t do_validate (assuan_context_t ctx,
+ const unsigned char *cert, size_t certlen);
+static gpg_error_t do_loadcrl (assuan_context_t ctx, const char *filename);
+static gpg_error_t do_lookup (assuan_context_t ctx, const char *pattern);
+static gpg_error_t squid_loop_body (assuan_context_t ctx);
+
+
+
+/* Function called by argparse.c to display information. */
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+
+ switch(level)
+ {
+ case 11: p = "dirmngr-client (@GNUPG@)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
+ case 49: p = PACKAGE_BUGREPORT; break;
+ case 1:
+ case 40: p =
+ _("Usage: dirmngr-client [options] "
+ "[certfile|pattern] (-h for help)\n");
+ break;
+ case 41: p =
+ _("Syntax: dirmngr-client [options] [certfile|pattern]\n"
+ "Test an X.509 certificate against a CRL or do an OCSP check\n"
+ "The process returns 0 if the certificate is valid, 1 if it is\n"
+ "not valid and other error codes for general failures\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ assuan_context_t ctx;
+ gpg_error_t err;
+ unsigned char *certbuf;
+ size_t certbuflen = 0;
+ int cmd_ping = 0;
+ int cmd_cache_cert = 0;
+ int cmd_validate = 0;
+ int cmd_lookup = 0;
+ int cmd_loadcrl = 0;
+ int cmd_squid_mode = 0;
+
+ set_strusage (my_strusage);
+ log_set_prefix ("dirmngr-client",
+ JNLIB_LOG_WITH_PREFIX);
+
+ /* For W32 we need to initialize the socket subsystem. Becuase we
+ don't use Pth we need to do this explicit. */
+#ifdef HAVE_W32_SYSTEM
+ {
+ WSADATA wsadat;
+
+ WSAStartup (0x202, &wsadat);
+ }
+#endif /*HAVE_W32_SYSTEM*/
+
+ /* Init Assuan. */
+ assuan_set_assuan_log_prefix (log_get_prefix (NULL));
+ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
+
+ /* Setup I18N. */
+ i18n_init();
+
+ /* Parse the command line. */
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* Do not remove the args. */
+ while (arg_parse (&pargs, opts) )
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose: opt.verbose++; break;
+ case oQuiet: opt.quiet++; break;
+
+ case oOCSP: opt.use_ocsp++; break;
+ case oPing: cmd_ping = 1; break;
+ case oCacheCert: cmd_cache_cert = 1; break;
+ case oValidate: cmd_validate = 1; break;
+ case oLookup: cmd_lookup = 1; break;
+ case oUrl: opt.url = 1; break;
+ case oLocal: opt.local = 1; break;
+ case oLoadCRL: cmd_loadcrl = 1; break;
+ case oPEM: opt.pem = 1; break;
+ case oSquidMode:
+ opt.pem = 1;
+ opt.escaped_pem = 1;
+ cmd_squid_mode = 1;
+ break;
+ case oForceDefaultResponder: opt.force_default_responder = 1; break;
+
+ default : pargs.err = 2; break;
+ }
+ }
+ if (log_get_errorcount (0))
+ exit (2);
+
+ /* Build the helptable for radix64 to bin conversion. */
+ if (opt.pem)
+ {
+ int i;
+ unsigned char *s;
+
+ for (i=0; i < 256; i++ )
+ asctobin[i] = 255; /* Used to detect invalid characters. */
+ for (s=bintoasc, i=0; *s; s++, i++)
+ asctobin[*s] = i;
+ }
+
+
+ if (cmd_ping)
+ err = 0;
+ else if (cmd_lookup || cmd_loadcrl)
+ {
+ if (!argc)
+ usage (1);
+ err = 0;
+ }
+ else if (cmd_squid_mode)
+ {
+ err = 0;
+ if (argc)
+ usage (1);
+ }
+ else if (!argc)
+ {
+ err = read_certificate (NULL, &certbuf, &certbuflen);
+ if (err)
+ log_error (_("error reading certificate from stdin: %s\n"),
+ gpg_strerror (err));
+ }
+ else if (argc == 1)
+ {
+ err = read_certificate (*argv, &certbuf, &certbuflen);
+ if (err)
+ log_error (_("error reading certificate from '%s': %s\n"),
+ *argv, gpg_strerror (err));
+ }
+ else
+ {
+ err = 0;
+ usage (1);
+ }
+
+ if (log_get_errorcount (0))
+ exit (2);
+
+ if (certbuflen > 20000)
+ {
+ log_error (_("certificate too large to make any sense\n"));
+ exit (2);
+ }
+
+ ctx = start_dirmngr (1);
+ if (!ctx)
+ exit (2);
+
+ if (cmd_ping)
+ ;
+ else if (cmd_squid_mode)
+ {
+ while (!(err = squid_loop_body (ctx)))
+ ;
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ err = 0;
+ }
+ else if (cmd_lookup)
+ {
+ int last_err = 0;
+
+ for (; argc; argc--, argv++)
+ {
+ err = do_lookup (ctx, *argv);
+ if (err)
+ {
+ log_error (_("lookup failed: %s\n"), gpg_strerror (err));
+ last_err = err;
+ }
+ }
+ err = last_err;
+ }
+ else if (cmd_loadcrl)
+ {
+ int last_err = 0;
+
+ for (; argc; argc--, argv++)
+ {
+ err = do_loadcrl (ctx, *argv);
+ if (err)
+ {
+ log_error (_("loading CRL '%s' failed: %s\n"),
+ *argv, gpg_strerror (err));
+ last_err = err;
+ }
+ }
+ err = last_err;
+ }
+ else if (cmd_cache_cert)
+ {
+ err = do_cache (ctx, certbuf, certbuflen);
+ xfree (certbuf);
+ }
+ else if (cmd_validate)
+ {
+ err = do_validate (ctx, certbuf, certbuflen);
+ xfree (certbuf);
+ }
+ else
+ {
+ err = do_check (ctx, certbuf, certbuflen);
+ xfree (certbuf);
+ }
+
+ assuan_release (ctx);
+
+ if (cmd_ping)
+ {
+ if (!opt.quiet)
+ log_info (_("a dirmngr daemon is up and running\n"));
+ return 0;
+ }
+ else if (cmd_lookup|| cmd_loadcrl || cmd_squid_mode)
+ return err? 1:0;
+ else if (cmd_cache_cert)
+ {
+ if (err && gpg_err_code (err) == GPG_ERR_DUP_VALUE )
+ {
+ if (!opt.quiet)
+ log_info (_("certificate already cached\n"));
+ }
+ else if (err)
+ {
+ log_error (_("error caching certificate: %s\n"),
+ gpg_strerror (err));
+ return 1;
+ }
+ return 0;
+ }
+ else if (cmd_validate && err)
+ {
+ log_error (_("validation of certificate failed: %s\n"),
+ gpg_strerror (err));
+ return 1;
+ }
+ else if (!err)
+ {
+ if (!opt.quiet)
+ log_info (_("certificate is valid\n"));
+ return 0;
+ }
+ else if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED )
+ {
+ if (!opt.quiet)
+ log_info (_("certificate has been revoked\n"));
+ return 1;
+ }
+ else
+ {
+ log_error (_("certificate check failed: %s\n"), gpg_strerror (err));
+ return 2;
+ }
+}
+
+
+/* Print status line from the assuan protocol. */
+static gpg_error_t
+status_cb (void *opaque, const char *line)
+{
+ (void)opaque;
+
+ if (opt.verbose > 2)
+ log_info (_("got status: '%s'\n"), line);
+ return 0;
+}
+
+/* Print data as retrieved by the lookup function. */
+static gpg_error_t
+data_cb (void *opaque, const void *buffer, size_t length)
+{
+ gpg_error_t err;
+ struct b64state *state = opaque;
+
+ if (buffer)
+ {
+ err = b64enc_write (state, buffer, length);
+ if (err)
+ log_error (_("error writing base64 encoding: %s\n"),
+ gpg_strerror (err));
+ }
+ return 0;
+}
+
+
+/* Try to connect to the dirmngr via socket or fork it off and work by
+ pipes. Handle the server's initial greeting */
+static assuan_context_t
+start_dirmngr (int only_daemon)
+{
+ int rc;
+ char *infostr, *p;
+ assuan_context_t ctx;
+ int try_default = 0;
+
+ infostr = opt.force_pipe_server? NULL : getenv (DIRMNGR_INFO_NAME);
+ if (only_daemon && (!infostr || !*infostr))
+ {
+ if (dirmngr_user_socket_name ())
+ infostr = xstrdup (dirmngr_user_socket_name ());
+ else
+ infostr = xstrdup (dirmngr_sys_socket_name ());
+ try_default = 1;
+ }
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error (_("failed to allocate assuan context: %s\n"),
+ gpg_strerror (rc));
+ return NULL;
+ }
+
+ if (!infostr || !*infostr)
+ {
+ const char *pgmname;
+ const char *argv[3];
+ assuan_fd_t no_close_list[3];
+ int i;
+
+ if (only_daemon)
+ {
+ log_error (_("apparently no running dirmngr\n"));
+ return NULL;
+ }
+
+ if (opt.verbose)
+ log_info (_("no running dirmngr - starting one\n"));
+
+ if (!opt.dirmngr_program || !*opt.dirmngr_program)
+ opt.dirmngr_program = "./dirmngr";
+ if ( !(pgmname = strrchr (opt.dirmngr_program, '/')))
+ pgmname = opt.dirmngr_program;
+ else
+ pgmname++;
+
+ argv[0] = pgmname;
+ argv[1] = "--server";
+ argv[2] = NULL;
+
+ i=0;
+ if (log_get_fd () != -1)
+ no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
+ no_close_list[i++] = assuan_fd_from_posix_fd (es_fileno (es_stderr));
+ no_close_list[i] = ASSUAN_INVALID_FD;
+
+ /* Connect to the agent and perform initial handshaking. */
+ rc = assuan_pipe_connect (ctx, opt.dirmngr_program, argv,
+ no_close_list, NULL, NULL, 0);
+ }
+ else /* Connect to a daemon. */
+ {
+ int prot;
+ int pid;
+
+ infostr = xstrdup (infostr);
+ if (!try_default && *infostr)
+ {
+ if ( !(p = strchr (infostr, ':')) || p == infostr)
+ {
+ log_error (_("malformed %s environment variable\n"),
+ DIRMNGR_INFO_NAME);
+ xfree (infostr);
+ if (only_daemon)
+ return NULL;
+ /* Try again by starting a new instance. */
+ opt.force_pipe_server = 1;
+ return start_dirmngr (0);
+ }
+ *p++ = 0;
+ pid = atoi (p);
+ while (*p && *p != ':')
+ p++;
+ prot = *p? atoi (p+1) : 0;
+ if (prot != 1)
+ {
+ log_error (_("dirmngr protocol version %d is not supported\n"),
+ prot);
+ xfree (infostr);
+ if (only_daemon)
+ return NULL;
+ opt.force_pipe_server = 1;
+ return start_dirmngr (0);
+ }
+ }
+ else
+ pid = -1;
+
+ rc = assuan_socket_connect (ctx, infostr, pid, 0);
+ xfree (infostr);
+ if (gpg_err_code(rc) == GPG_ERR_ASS_CONNECT_FAILED && !only_daemon)
+ {
+ log_error (_("can't connect to the dirmngr - trying fall back\n"));
+ opt.force_pipe_server = 1;
+ return start_dirmngr (0);
+ }
+ }
+
+ if (rc)
+ {
+ assuan_release (ctx);
+ log_error (_("can't connect to the dirmngr: %s\n"),
+ gpg_strerror (rc));
+ return NULL;
+ }
+
+ return ctx;
+}
+
+
+/* Read the first PEM certificate from the file FNAME. If fname is
+ NULL the next certificate is read from stdin. The certificate is
+ returned in an alloced buffer whose address will be returned in
+ RBUF and its length in RBUFLEN. */
+static gpg_error_t
+read_pem_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen)
+{
+ FILE *fp;
+ int c;
+ int pos;
+ int value;
+ unsigned char *buf;
+ size_t bufsize, buflen;
+ enum {
+ s_init, s_idle, s_lfseen, s_begin,
+ s_b64_0, s_b64_1, s_b64_2, s_b64_3,
+ s_waitend
+ } state = s_init;
+
+ fp = fname? fopen (fname, "r") : stdin;
+ if (!fp)
+ return gpg_error_from_errno (errno);
+
+ pos = 0;
+ value = 0;
+ bufsize = 8192;
+ buf = xmalloc (bufsize);
+ buflen = 0;
+ while ((c=getc (fp)) != EOF)
+ {
+ int escaped_c = 0;
+
+ if (opt.escaped_pem)
+ {
+ if (c == '%')
+ {
+ char tmp[2];
+ if ((c = getc(fp)) == EOF)
+ break;
+ tmp[0] = c;
+ if ((c = getc(fp)) == EOF)
+ break;
+ tmp[1] = c;
+ if (!hexdigitp (tmp) || !hexdigitp (tmp+1))
+ {
+ log_error ("invalid percent escape sequence\n");
+ state = s_idle; /* Force an error. */
+ /* Skip to end of line. */
+ while ( (c=getc (fp)) != EOF && c != '\n')
+ ;
+ goto ready;
+ }
+ c = xtoi_2 (tmp);
+ escaped_c = 1;
+ }
+ else if (c == '\n')
+ goto ready; /* Ready. */
+ }
+ switch (state)
+ {
+ case s_idle:
+ if (c == '\n')
+ {
+ state = s_lfseen;
+ pos = 0;
+ }
+ break;
+ case s_init:
+ state = s_lfseen;
+ case s_lfseen:
+ if (c != "-----BEGIN "[pos])
+ state = s_idle;
+ else if (pos == 10)
+ state = s_begin;
+ else
+ pos++;
+ break;
+ case s_begin:
+ if (c == '\n')
+ state = s_b64_0;
+ break;
+ case s_b64_0:
+ case s_b64_1:
+ case s_b64_2:
+ case s_b64_3:
+ {
+ if (buflen >= bufsize)
+ {
+ bufsize += 8192;
+ buf = xrealloc (buf, bufsize);
+ }
+
+ if (c == '-')
+ state = s_waitend;
+ else if ((c = asctobin[c & 0xff]) == 255 )
+ ; /* Just skip invalid base64 characters. */
+ else if (state == s_b64_0)
+ {
+ value = c << 2;
+ state = s_b64_1;
+ }
+ else if (state == s_b64_1)
+ {
+ value |= (c>>4)&3;
+ buf[buflen++] = value;
+ value = (c<<4)&0xf0;
+ state = s_b64_2;
+ }
+ else if (state == s_b64_2)
+ {
+ value |= (c>>2)&15;
+ buf[buflen++] = value;
+ value = (c<<6)&0xc0;
+ state = s_b64_3;
+ }
+ else
+ {
+ value |= c&0x3f;
+ buf[buflen++] = value;
+ state = s_b64_0;
+ }
+ }
+ break;
+ case s_waitend:
+ /* Note that we do not check that the base64 decoder has
+ been left in the expected state. We assume that the PEM
+ header is just fine. However we need to wait for the
+ real LF and not a trailing percent escaped one. */
+ if (c== '\n' && !escaped_c)
+ goto ready;
+ break;
+ default:
+ BUG();
+ }
+ }
+ ready:
+ if (fname)
+ fclose (fp);
+
+ if (state == s_init && c == EOF)
+ {
+ xfree (buf);
+ return gpg_error (GPG_ERR_EOF);
+ }
+ else if (state != s_waitend)
+ {
+ log_error ("no certificate or invalid encoded\n");
+ xfree (buf);
+ return gpg_error (GPG_ERR_INV_ARMOR);
+ }
+
+ *rbuf = buf;
+ *rbuflen = buflen;
+ return 0;
+}
+
+/* Read a binary certificate from the file FNAME. If fname is NULL the
+ file is read from stdin. The certificate is returned in an alloced
+ buffer whose address will be returned in RBUF and its length in
+ RBUFLEN. */
+static gpg_error_t
+read_certificate (const char *fname, unsigned char **rbuf, size_t *rbuflen)
+{
+ gpg_error_t err;
+ FILE *fp;
+ unsigned char *buf;
+ size_t nread, bufsize, buflen;
+
+ if (opt.pem)
+ return read_pem_certificate (fname, rbuf, rbuflen);
+
+ fp = fname? fopen (fname, "rb") : stdin;
+ if (!fp)
+ return gpg_error_from_errno (errno);
+
+ buf = NULL;
+ bufsize = buflen = 0;
+#define NCHUNK 8192
+ do
+ {
+ bufsize += NCHUNK;
+ if (!buf)
+ buf = xmalloc (bufsize);
+ else
+ buf = xrealloc (buf, bufsize);
+
+ nread = fread (buf+buflen, 1, NCHUNK, fp);
+ if (nread < NCHUNK && ferror (fp))
+ {
+ err = gpg_error_from_errno (errno);
+ xfree (buf);
+ if (fname)
+ fclose (fp);
+ return err;
+ }
+ buflen += nread;
+ }
+ while (nread == NCHUNK);
+#undef NCHUNK
+ if (fname)
+ fclose (fp);
+ *rbuf = buf;
+ *rbuflen = buflen;
+ return 0;
+}
+
+
+/* Callback for the inquire fiunction to send back the certificate. */
+static gpg_error_t
+inq_cert (void *opaque, const char *line)
+{
+ struct inq_cert_parm_s *parm = opaque;
+ gpg_error_t err;
+
+ if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10]))
+ {
+ err = assuan_send_data (parm->ctx, parm->cert, parm->certlen);
+ }
+ else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8]))
+ {
+ /* We don't support this but dirmngr might ask for it. So
+ simply ignore it by sending back and empty value. */
+ err = assuan_send_data (parm->ctx, NULL, 0);
+ }
+ else if (!strncmp (line, "SENDCERT_SKI", 12)
+ && (line[12]==' ' || !line[12]))
+ {
+ /* We don't support this but dirmngr might ask for it. So
+ simply ignore it by sending back an empty value. */
+ err = assuan_send_data (parm->ctx, NULL, 0);
+ }
+ else if (!strncmp (line, "SENDISSUERCERT", 14)
+ && (line[14] == ' ' || !line[14]))
+ {
+ /* We don't support this but dirmngr might ask for it. So
+ simply ignore it by sending back an empty value. */
+ err = assuan_send_data (parm->ctx, NULL, 0);
+ }
+ else
+ {
+ log_info (_("unsupported inquiry '%s'\n"), line);
+ err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
+ /* Note that this error will let assuan_transact terminate
+ immediately instead of return the error to the caller. It is
+ not clear whether this is the desired behaviour - it may
+ change in future. */
+ }
+
+ return err;
+}
+
+
+/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP.
+ Return a proper error code. */
+static gpg_error_t
+do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen)
+{
+ gpg_error_t err;
+ struct inq_cert_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+ parm.ctx = ctx;
+ parm.cert = cert;
+ parm.certlen = certlen;
+
+ err = assuan_transact (ctx,
+ (opt.use_ocsp && opt.force_default_responder
+ ? "CHECKOCSP --force-default-responder"
+ : opt.use_ocsp? "CHECKOCSP" : "CHECKCRL"),
+ NULL, NULL, inq_cert, &parm, status_cb, NULL);
+ if (opt.verbose > 1)
+ log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
+ return err;
+}
+
+/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP.
+ Return a proper error code. */
+static gpg_error_t
+do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen)
+{
+ gpg_error_t err;
+ struct inq_cert_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+ parm.ctx = ctx;
+ parm.cert = cert;
+ parm.certlen = certlen;
+
+ err = assuan_transact (ctx, "CACHECERT", NULL, NULL,
+ inq_cert, &parm,
+ status_cb, NULL);
+ if (opt.verbose > 1)
+ log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
+ return err;
+}
+
+/* Check the certificate CERT,CERTLEN for validity using dirmngrs
+ internal validate feature. Return a proper error code. */
+static gpg_error_t
+do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen)
+{
+ gpg_error_t err;
+ struct inq_cert_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+ parm.ctx = ctx;
+ parm.cert = cert;
+ parm.certlen = certlen;
+
+ err = assuan_transact (ctx, "VALIDATE", NULL, NULL,
+ inq_cert, &parm,
+ status_cb, NULL);
+ if (opt.verbose > 1)
+ log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
+ return err;
+}
+
+/* Load a CRL into the dirmngr. */
+static gpg_error_t
+do_loadcrl (assuan_context_t ctx, const char *filename)
+{
+ gpg_error_t err;
+ const char *s;
+ char *fname, *line, *p;
+
+ if (opt.url)
+ fname = xstrdup (filename);
+ else
+ {
+#ifdef HAVE_CANONICALIZE_FILE_NAME
+ fname = canonicalize_file_name (filename);
+ if (!fname)
+ {
+ log_error ("error canonicalizing '%s': %s\n",
+ filename, strerror (errno));
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+#else
+ fname = xstrdup (filename);
+#endif
+ if (*fname != '/')
+ {
+ log_error (_("absolute file name expected\n"));
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ }
+
+ line = xmalloc (8 + 6 + strlen (fname) * 3 + 1);
+ p = stpcpy (line, "LOADCRL ");
+ if (opt.url)
+ p = stpcpy (p, "--url ");
+ for (s = fname; *s; s++)
+ {
+ if (*s < ' ' || *s == '+')
+ {
+ sprintf (p, "%%%02X", *s);
+ p += 3;
+ }
+ else if (*s == ' ')
+ *p++ = '+';
+ else
+ *p++ = *s;
+ }
+ *p = 0;
+
+ err = assuan_transact (ctx, line, NULL, NULL,
+ NULL, NULL,
+ status_cb, NULL);
+ if (opt.verbose > 1)
+ log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
+ xfree (line);
+ xfree (fname);
+ return err;
+}
+
+
+/* Do a LDAP lookup using PATTERN and print the result in a base-64
+ encoded format. */
+static gpg_error_t
+do_lookup (assuan_context_t ctx, const char *pattern)
+{
+ gpg_error_t err;
+ const unsigned char *s;
+ char *line, *p;
+ struct b64state state;
+
+ if (opt.verbose)
+ log_info (_("looking up '%s'\n"), pattern);
+
+ err = b64enc_start (&state, stdout, NULL);
+ if (err)
+ return err;
+
+ line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1);
+
+ p = stpcpy (line, "LOOKUP ");
+ if (opt.url)
+ p = stpcpy (p, "--url ");
+ if (opt.local)
+ p = stpcpy (p, "--cache-only ");
+ for (s=pattern; *s; s++)
+ {
+ if (*s < ' ' || *s == '+')
+ {
+ sprintf (p, "%%%02X", *s);
+ p += 3;
+ }
+ else if (*s == ' ')
+ *p++ = '+';
+ else
+ *p++ = *s;
+ }
+ *p = 0;
+
+
+ err = assuan_transact (ctx, line,
+ data_cb, &state,
+ NULL, NULL,
+ status_cb, NULL);
+ if (opt.verbose > 1)
+ log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay");
+
+ err = b64enc_finish (&state);
+
+ xfree (line);
+ return err;
+}
+
+/* The body of an endless loop: Read a line from stdin, retrieve the
+ certificate from it, validate it and print "ERR" or "OK" to stdout.
+ Continue. */
+static gpg_error_t
+squid_loop_body (assuan_context_t ctx)
+{
+ gpg_error_t err;
+ unsigned char *certbuf;
+ size_t certbuflen = 0;
+
+ err = read_pem_certificate (NULL, &certbuf, &certbuflen);
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ return err;
+ if (err)
+ {
+ log_error (_("error reading certificate from stdin: %s\n"),
+ gpg_strerror (err));
+ puts ("ERROR");
+ return 0;
+ }
+
+ err = do_check (ctx, certbuf, certbuflen);
+ xfree (certbuf);
+ if (!err)
+ {
+ if (opt.verbose)
+ log_info (_("certificate is valid\n"));
+ puts ("OK");
+ }
+ else
+ {
+ if (!opt.quiet)
+ {
+ if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED )
+ log_info (_("certificate has been revoked\n"));
+ else
+ log_error (_("certificate check failed: %s\n"),
+ gpg_strerror (err));
+ }
+ puts ("ERROR");
+ }
+
+ fflush (stdout);
+
+ return 0;
+}
diff --git a/dirmngr/dirmngr-err.h b/dirmngr/dirmngr-err.h
new file mode 100644
index 0000000..17e8255
--- /dev/null
+++ b/dirmngr/dirmngr-err.h
@@ -0,0 +1,12 @@
+/* Definition of the gpg-error source. */
+
+#ifndef DIRMNGR_ERR_H
+#define DIRMNGR_ERR_H
+
+#ifdef GPG_ERR_SOURCE_DEFAULT
+#error GPG_ERR_SOURCE_DEFAULT already defined
+#endif
+#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_DIRMNGR
+#include <gpg-error.h>
+
+#endif /*DIRMNGR_ERR_H*/
diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c
new file mode 100644
index 0000000..95f9058
--- /dev/null
+++ b/dirmngr/dirmngr.c
@@ -0,0 +1,2032 @@
+/* dirmngr.c - Keyserver and X.509 LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010, 2011 g10 Code GmbH
+ * Copyright (C) 2014 Werner Koch
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <time.h>
+#include <fcntl.h>
+#ifndef HAVE_W32_SYSTEM
+#include <sys/socket.h>
+#include <sys/un.h>
+#endif
+#include <sys/stat.h>
+#include <unistd.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#include <npth.h>
+
+#include "dirmngr-err.h"
+
+#if HTTP_USE_NTBTLS
+# include <ntbtls.h>
+#elif HTTP_USE_GNUTLS
+# include <gnutls/gnutls.h>
+#endif /*HTTP_USE_GNUTLS*/
+
+
+#define JNLIB_NEED_LOG_LOGV
+#define JNLIB_NEED_AFLOCAL
+#include "dirmngr.h"
+
+#include <assuan.h>
+
+#include "certcache.h"
+#include "crlcache.h"
+#include "crlfetch.h"
+#include "misc.h"
+#if USE_LDAP
+# include "ldapserver.h"
+#endif
+#include "asshelp.h"
+#if USE_LDAP
+# include "ldap-wrapper.h"
+#endif
+#include "../common/init.h"
+#include "gc-opt-flags.h"
+
+/* The plain Windows version uses the windows service system. For
+ example to start the service you may use "sc start dirmngr".
+ WindowsCE does not support this; the service system over there is
+ based on a single process with all services being DLLs - we can't
+ support this easily. */
+#if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM)
+# define USE_W32_SERVICE 1
+#endif
+
+
+enum cmd_and_opt_values {
+ aNull = 0,
+ oCsh = 'c',
+ oQuiet = 'q',
+ oSh = 's',
+ oVerbose = 'v',
+ oNoVerbose = 500,
+
+ aServer,
+ aDaemon,
+ aService,
+ aListCRLs,
+ aLoadCRL,
+ aFetchCRL,
+ aShutdown,
+ aFlush,
+ aGPGConfList,
+ aGPGConfTest,
+
+ oOptions,
+ oDebug,
+ oDebugAll,
+ oDebugWait,
+ oDebugLevel,
+ oGnutlsDebug,
+ oNoGreeting,
+ oNoOptions,
+ oHomedir,
+ oNoDetach,
+ oLogFile,
+ oBatch,
+ oDisableHTTP,
+ oDisableLDAP,
+ oIgnoreLDAPDP,
+ oIgnoreHTTPDP,
+ oIgnoreOCSPSvcUrl,
+ oHonorHTTPProxy,
+ oHTTPProxy,
+ oLDAPProxy,
+ oOnlyLDAPProxy,
+ oLDAPFile,
+ oLDAPTimeout,
+ oLDAPAddServers,
+ oOCSPResponder,
+ oOCSPSigner,
+ oOCSPMaxClockSkew,
+ oOCSPMaxPeriod,
+ oOCSPCurrentPeriod,
+ oMaxReplies,
+ oHkpCaCert,
+ oFakedSystemTime,
+ oForce,
+ oAllowOCSP,
+ oSocketName,
+ oLDAPWrapperProgram,
+ oHTTPWrapperProgram,
+ oIgnoreCertExtension,
+ aTest
+};
+
+
+
+static ARGPARSE_OPTS opts[] = {
+
+ ARGPARSE_group (300, N_("@Commands:\n ")),
+
+ ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ),
+ ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ),
+#ifdef USE_W32_SERVICE
+ ARGPARSE_c (aService, "service", N_("run as windows service (background)")),
+#endif
+ ARGPARSE_c (aListCRLs, "list-crls", N_("list the contents of the CRL cache")),
+ ARGPARSE_c (aLoadCRL, "load-crl", N_("|FILE|load CRL from FILE into cache")),
+ ARGPARSE_c (aFetchCRL, "fetch-crl", N_("|URL|fetch a CRL from URL")),
+ ARGPARSE_c (aShutdown, "shutdown", N_("shutdown the dirmngr")),
+ ARGPARSE_c (aFlush, "flush", N_("flush the cache")),
+ ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
+ ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
+
+ ARGPARSE_group (301, N_("@\nOptions:\n ")),
+
+ ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
+ ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
+ ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
+ ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
+ ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
+ ARGPARSE_s_s (oDebugLevel, "debug-level",
+ N_("|LEVEL|set the debugging level to LEVEL")),
+ ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
+ ARGPARSE_s_s (oLogFile, "log-file",
+ N_("|FILE|write server mode logs to FILE")),
+ ARGPARSE_s_n (oBatch, "batch", N_("run without asking a user")),
+ ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")),
+ ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")),
+ ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")),
+ ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")),
+ ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp",
+ N_("ignore HTTP CRL distribution points")),
+ ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp",
+ N_("ignore LDAP CRL distribution points")),
+ ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url",
+ N_("ignore certificate contained OCSP service URLs")),
+
+ ARGPARSE_s_s (oHTTPProxy, "http-proxy",
+ N_("|URL|redirect all HTTP requests to URL")),
+ ARGPARSE_s_s (oLDAPProxy, "ldap-proxy",
+ N_("|HOST|use HOST for LDAP queries")),
+ ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy",
+ N_("do not use fallback hosts with --ldap-proxy")),
+
+ ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file",
+ N_("|FILE|read LDAP server list from FILE")),
+ ARGPARSE_s_n (oLDAPAddServers, "add-servers",
+ N_("add new servers discovered in CRL distribution"
+ " points to serverlist")),
+ ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout",
+ N_("|N|set LDAP timeout to N seconds")),
+
+ ARGPARSE_s_s (oOCSPResponder, "ocsp-responder",
+ N_("|URL|use OCSP responder at URL")),
+ ARGPARSE_s_s (oOCSPSigner, "ocsp-signer",
+ N_("|FPR|OCSP response signed by FPR")),
+ ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"),
+ ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"),
+ ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"),
+
+ ARGPARSE_s_i (oMaxReplies, "max-replies",
+ N_("|N|do not return more than N items in one query")),
+
+ ARGPARSE_s_s (oHkpCaCert, "hkp-cacert",
+ N_("|FILE|use the CA certificates in FILE for HKP over TLS")),
+
+
+ ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */
+
+ ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/
+ ARGPARSE_p_u (oDebug, "debug", "@"),
+ ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
+ ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"),
+ ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"),
+ ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
+ ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
+ ARGPARSE_s_s (oHomedir, "homedir", "@"),
+ ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"),
+ ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"),
+ ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy", "@"),
+ ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"),
+
+ ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing "
+ "of all commands and options)\n")),
+
+ ARGPARSE_end ()
+};
+
+#define DEFAULT_MAX_REPLIES 10
+#define DEFAULT_LDAP_TIMEOUT 100 /* arbitrary large timeout */
+
+/* For the cleanup handler we need to keep track of the socket's name. */
+static const char *socket_name;
+
+/* We need to keep track of the server's nonces (these are dummies for
+ POSIX systems). */
+static assuan_sock_nonce_t socket_nonce;
+
+/* Only if this flag has been set we will remove the socket file. */
+static int cleanup_socket;
+
+/* Keep track of the current log file so that we can avoid updating
+ the log file after a SIGHUP if it didn't changed. Malloced. */
+static char *current_logfile;
+
+/* Helper to implement --debug-level. */
+static const char *debug_level;
+
+/* Helper to set the NTBTLS or GNUTLS log level. */
+static int opt_gnutls_debug = -1;
+
+/* Flag indicating that a shutdown has been requested. */
+static volatile int shutdown_pending;
+
+/* Counter for the active connections. */
+static int active_connections;
+
+/* The timer tick used for housekeeping stuff. For Windows we use a
+ longer period as the SetWaitableTimer seems to signal earlier than
+ the 2 seconds. All values are in seconds. */
+#if defined(HAVE_W32CE_SYSTEM)
+# define TIMERTICK_INTERVAL (60)
+#elif defined(HAVE_W32_SYSTEM)
+# define TIMERTICK_INTERVAL (4)
+#else
+# define TIMERTICK_INTERVAL (2)
+#endif
+
+#define HOUSEKEEPING_INTERVAL (600)
+
+
+/* This union is used to avoid compiler warnings in case a pointer is
+ 64 bit and an int 32 bit. We store an integer in a pointer and get
+ it back later (npth_getspecific et al.). */
+union int_and_ptr_u
+{
+ int aint;
+ assuan_fd_t afd;
+ void *aptr;
+};
+
+
+
+/* The key used to store the current file descriptor in the thread
+ local storage. We use this in conjunction with the
+ log_set_pid_suffix_cb feature.. */
+#ifndef HAVE_W32_SYSTEM
+static int my_tlskey_current_fd;
+#endif
+
+/* Prototypes. */
+static void cleanup (void);
+#if USE_LDAP
+static ldap_server_t parse_ldapserver_file (const char* filename);
+#endif /*USE_LDAP*/
+static fingerprint_list_t parse_ocsp_signer (const char *string);
+static void handle_connections (assuan_fd_t listen_fd);
+
+/* NPth wrapper function definitions. */
+ASSUAN_SYSTEM_NPTH_IMPL;
+
+static const char *
+my_strusage( int level )
+{
+ const char *p;
+ switch ( level )
+ {
+ case 11: p = "@DIRMNGR@ (@GNUPG@)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
+ reporting address. This is so that we can change the
+ reporting address without breaking the translations. */
+ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
+ case 49: p = PACKAGE_BUGREPORT; break;
+ case 1:
+ case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)");
+ break;
+ case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n"
+ "Keyserver, CRL, and OCSP access for @GNUPG@\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+
+
+/* Callback from libksba to hash a provided buffer. Our current
+ implementation does only allow SHA-1 for hashing. This may be
+ extended by mapping the name, testing for algorithm availibility
+ and adjust the length checks accordingly. */
+static gpg_error_t
+my_ksba_hash_buffer (void *arg, const char *oid,
+ const void *buffer, size_t length, size_t resultsize,
+ unsigned char *result, size_t *resultlen)
+{
+ (void)arg;
+
+ if (oid && strcmp (oid, "1.3.14.3.2.26"))
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ if (resultsize < 20)
+ return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+ gcry_md_hash_buffer (2, result, buffer, length);
+ *resultlen = 20;
+ return 0;
+}
+
+
+/* GNUTLS log function callback. */
+static void
+my_gnutls_log (int level, const char *text)
+{
+ int n;
+
+ n = strlen (text);
+ while (n && text[n-1] == '\n')
+ n--;
+
+ log_debug ("gnutls:L%d: %.*s\n", level, n, text);
+}
+
+
+/* Setup the debugging. With a LEVEL of NULL only the active debug
+ flags are propagated to the subsystems. With LEVEL set, a specific
+ set of debug flags is set; thus overriding all flags already
+ set. */
+static void
+set_debug (void)
+{
+ int numok = (debug_level && digitp (debug_level));
+ int numlvl = numok? atoi (debug_level) : 0;
+
+ if (!debug_level)
+ ;
+ else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
+ opt.debug = 0;
+ else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
+ opt.debug = DBG_ASSUAN_VALUE;
+ else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
+ opt.debug = (DBG_ASSUAN_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE);
+ else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
+ opt.debug = (DBG_ASSUAN_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE
+ |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
+ else if (!strcmp (debug_level, "guru") || numok)
+ {
+ opt.debug = ~0;
+ /* Unless the "guru" string has been used we don't want to allow
+ hashing debugging. The rationale is that people tend to
+ select the highest debug value and would then clutter their
+ disk with debug files which may reveal confidential data. */
+ if (numok)
+ opt.debug &= ~(DBG_HASHING_VALUE);
+ }
+ else
+ {
+ log_error (_("invalid debug-level '%s' given\n"), debug_level);
+ log_info (_("valid debug levels are: %s\n"),
+ "none, basic, advanced, expert, guru");
+ opt.debug = 0; /* Reset debugging, so that prior debug
+ statements won't have an undesired effect. */
+ }
+
+
+ if (opt.debug && !opt.verbose)
+ {
+ opt.verbose = 1;
+ gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
+ }
+ if (opt.debug && opt.quiet)
+ opt.quiet = 0;
+
+ if (opt.debug & DBG_CRYPTO_VALUE )
+ gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
+
+#if HTTP_USE_NTBTLS
+ if (opt_gnutls_debug >= 0)
+ {
+ ntbtls_set_debug (opt_gnutls_debug, NULL, NULL);
+ }
+#elif HTTP_USE_GNUTLS
+ if (opt_gnutls_debug >= 0)
+ {
+ gnutls_global_set_log_function (my_gnutls_log);
+ gnutls_global_set_log_level (opt_gnutls_debug);
+ }
+#endif /*HTTP_USE_GNUTLS*/
+}
+
+
+static void
+wrong_args (const char *text)
+{
+ es_fprintf (es_stderr, _("usage: %s [options] "), DIRMNGR_NAME);
+ es_fputs (text, es_stderr);
+ es_putc ('\n', es_stderr);
+ dirmngr_exit (2);
+}
+
+
+/* Helper to stop the reaper thread for the ldap wrapper. */
+static void
+shutdown_reaper (void)
+{
+#if USE_LDAP
+ ldap_wrapper_wait_connections ();
+#endif
+}
+
+
+/* Handle options which are allowed to be reset after program start.
+ Return true if the current option in PARGS could be handled and
+ false if not. As a special feature, passing a value of NULL for
+ PARGS, resets the options to the default. REREAD should be set
+ true if it is not the initial option parsing. */
+static int
+parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
+{
+ if (!pargs)
+ { /* Reset mode. */
+ opt.quiet = 0;
+ opt.verbose = 0;
+ opt.debug = 0;
+ opt.ldap_wrapper_program = NULL;
+ opt.disable_http = 0;
+ opt.disable_ldap = 0;
+ opt.honor_http_proxy = 0;
+ opt.http_proxy = NULL;
+ opt.ldap_proxy = NULL;
+ opt.only_ldap_proxy = 0;
+ opt.ignore_http_dp = 0;
+ opt.ignore_ldap_dp = 0;
+ opt.ignore_ocsp_service_url = 0;
+ opt.allow_ocsp = 0;
+ opt.ocsp_responder = NULL;
+ opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */
+ opt.ocsp_max_period = 90 * 86400; /* 90 days. */
+ opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */
+ opt.max_replies = DEFAULT_MAX_REPLIES;
+ while (opt.ocsp_signer)
+ {
+ fingerprint_list_t tmp = opt.ocsp_signer->next;
+ xfree (opt.ocsp_signer);
+ opt.ocsp_signer = tmp;
+ }
+ FREE_STRLIST (opt.ignored_cert_extensions);
+ http_register_tls_ca (NULL);
+ return 1;
+ }
+
+ switch (pargs->r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+ case oDebug: opt.debug |= pargs->r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+ case oDebugLevel: debug_level = pargs->r.ret_str; break;
+ case oGnutlsDebug: opt_gnutls_debug = pargs->r.ret_int; break;
+
+ case oLogFile:
+ if (!reread)
+ return 0; /* Not handled. */
+ if (!current_logfile || !pargs->r.ret_str
+ || strcmp (current_logfile, pargs->r.ret_str))
+ {
+ log_set_file (pargs->r.ret_str);
+ xfree (current_logfile);
+ current_logfile = xtrystrdup (pargs->r.ret_str);
+ }
+ break;
+
+ case oLDAPWrapperProgram:
+ opt.ldap_wrapper_program = pargs->r.ret_str;
+ break;
+ case oHTTPWrapperProgram:
+ opt.http_wrapper_program = pargs->r.ret_str;
+ break;
+
+ case oDisableHTTP: opt.disable_http = 1; break;
+ case oDisableLDAP: opt.disable_ldap = 1; break;
+ case oHonorHTTPProxy: opt.honor_http_proxy = 1; break;
+ case oHTTPProxy: opt.http_proxy = pargs->r.ret_str; break;
+ case oLDAPProxy: opt.ldap_proxy = pargs->r.ret_str; break;
+ case oOnlyLDAPProxy: opt.only_ldap_proxy = 1; break;
+ case oIgnoreHTTPDP: opt.ignore_http_dp = 1; break;
+ case oIgnoreLDAPDP: opt.ignore_ldap_dp = 1; break;
+ case oIgnoreOCSPSvcUrl: opt.ignore_ocsp_service_url = 1; break;
+
+ case oAllowOCSP: opt.allow_ocsp = 1; break;
+ case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break;
+ case oOCSPSigner:
+ opt.ocsp_signer = parse_ocsp_signer (pargs->r.ret_str);
+ break;
+ case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break;
+ case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break;
+ case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break;
+
+ case oMaxReplies: opt.max_replies = pargs->r.ret_int; break;
+
+ case oHkpCaCert:
+ http_register_tls_ca (pargs->r.ret_str);
+ break;
+
+ case oIgnoreCertExtension:
+ add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str);
+ break;
+
+ default:
+ return 0; /* Not handled. */
+ }
+
+ return 1; /* Handled. */
+}
+
+
+#ifdef USE_W32_SERVICE
+/* The global status of our service. */
+SERVICE_STATUS_HANDLE service_handle;
+SERVICE_STATUS service_status;
+
+DWORD WINAPI
+w32_service_control (DWORD control, DWORD event_type, LPVOID event_data,
+ LPVOID context)
+{
+ (void)event_type;
+ (void)event_data;
+ (void)context;
+
+ /* event_type and event_data are not used here. */
+ switch (control)
+ {
+ case SERVICE_CONTROL_SHUTDOWN:
+ /* For shutdown we will try to force termination. */
+ service_status.dwCurrentState = SERVICE_STOP_PENDING;
+ SetServiceStatus (service_handle, &service_status);
+ shutdown_pending = 3;
+ break;
+
+ case SERVICE_CONTROL_STOP:
+ service_status.dwCurrentState = SERVICE_STOP_PENDING;
+ SetServiceStatus (service_handle, &service_status);
+ shutdown_pending = 1;
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}
+#endif /*USE_W32_SERVICE*/
+
+#ifndef HAVE_W32_SYSTEM
+static int
+pid_suffix_callback (unsigned long *r_suffix)
+{
+ union int_and_ptr_u value;
+
+ value.aptr = npth_getspecific (my_tlskey_current_fd);
+ *r_suffix = value.aint;
+ return (*r_suffix != -1); /* Use decimal representation. */
+}
+#endif /*!HAVE_W32_SYSTEM*/
+
+
+#ifdef USE_W32_SERVICE
+# define main real_main
+#endif
+int
+main (int argc, char **argv)
+{
+#ifdef USE_W32_SERVICE
+# undef main
+#endif
+ enum cmd_and_opt_values cmd = 0;
+ ARGPARSE_ARGS pargs;
+ int orig_argc;
+ char **orig_argv;
+ FILE *configfp = NULL;
+ char *configname = NULL;
+ const char *shell;
+ unsigned configlineno;
+ int parse_debug = 0;
+ int default_config =1;
+ int greeting = 0;
+ int nogreeting = 0;
+ int nodetach = 0;
+ int csh_style = 0;
+ char *logfile = NULL;
+#if USE_LDAP
+ char *ldapfile = NULL;
+#endif /*USE_LDAP*/
+ int debug_wait = 0;
+ int rc;
+ int homedir_seen = 0;
+ struct assuan_malloc_hooks malloc_hooks;
+
+#ifdef USE_W32_SERVICE
+ /* The option will be set by main() below if we should run as a
+ system daemon. */
+ if (opt.system_service)
+ {
+ service_handle
+ = RegisterServiceCtrlHandlerEx ("DirMngr",
+ &w32_service_control, NULL /*FIXME*/);
+ if (service_handle == 0)
+ log_error ("failed to register service control handler: ec=%d",
+ (int) GetLastError ());
+ service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ service_status.dwCurrentState = SERVICE_START_PENDING;
+ service_status.dwControlsAccepted
+ = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
+ service_status.dwWin32ExitCode = NO_ERROR;
+ service_status.dwServiceSpecificExitCode = NO_ERROR;
+ service_status.dwCheckPoint = 0;
+ service_status.dwWaitHint = 10000; /* 10 seconds timeout. */
+ SetServiceStatus (service_handle, &service_status);
+ }
+#endif /*USE_W32_SERVICE*/
+
+ set_strusage (my_strusage);
+ log_set_prefix (DIRMNGR_NAME, 1|4);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems (&argc, &argv);
+
+ npth_init ();
+
+ gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
+
+ /* Check that the libraries are suitable. Do it here because
+ the option parsing may need services of the libraries. */
+
+ if (!gcry_check_version (NEED_LIBGCRYPT_VERSION) )
+ log_fatal (_("%s is too old (need %s, have %s)\n"), "libgcrypt",
+ NEED_LIBGCRYPT_VERSION, gcry_check_version (NULL) );
+ if (!ksba_check_version (NEED_KSBA_VERSION) )
+ log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba",
+ NEED_KSBA_VERSION, ksba_check_version (NULL) );
+
+ ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
+ ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL);
+
+ /* Init TLS library. */
+#if HTTP_USE_NTBTLS
+ if (!ntbtls_check_version (NEED_NTBTLS_VERSION) )
+ log_fatal( _("%s is too old (need %s, have %s)\n"), "ntbtls",
+ NEED_NTBTLS_VERSION, ntbtls_check_version (NULL) );
+#elif HTTP_USE_GNUTLS
+ rc = gnutls_global_init ();
+ if (rc)
+ log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
+#endif /*HTTP_USE_GNUTLS*/
+
+ /* Init Assuan. */
+ malloc_hooks.malloc = gcry_malloc;
+ malloc_hooks.realloc = gcry_realloc;
+ malloc_hooks.free = gcry_free;
+ assuan_set_malloc_hooks (&malloc_hooks);
+ assuan_set_assuan_log_prefix (log_get_prefix (NULL));
+ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
+ assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
+ assuan_sock_init ();
+ setup_libassuan_logging (&opt.debug);
+
+ setup_libgcrypt_logging ();
+
+ /* Setup defaults. */
+ shell = getenv ("SHELL");
+ if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
+ csh_style = 1;
+
+ opt.homedir = default_homedir ();
+
+ /* Now with NPth running we can set the logging callback. Our
+ windows implementation does not yet feature the NPth TLS
+ functions. */
+#ifndef HAVE_W32_SYSTEM
+ if (npth_key_create (&my_tlskey_current_fd, NULL) == 0)
+ if (npth_setspecific (my_tlskey_current_fd, NULL) == 0)
+ log_set_pid_suffix_cb (pid_suffix_callback);
+#endif /*!HAVE_W32_SYSTEM*/
+
+ /* Reset rereadable options to default values. */
+ parse_rereadable_options (NULL, 0);
+
+ /* LDAP defaults. */
+ opt.add_new_ldapservers = 0;
+ opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT;
+
+ /* Other defaults. */
+
+ /* Check whether we have a config file given on the commandline */
+ orig_argc = argc;
+ orig_argv = argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1|(1<<6); /* do not remove the args, ignore version */
+ while (arg_parse( &pargs, opts))
+ {
+ if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll)
+ parse_debug++;
+ else if (pargs.r_opt == oOptions)
+ { /* Yes there is one, so we do not try the default one, but
+ read the option file when it is encountered at the
+ commandline */
+ default_config = 0;
+ }
+ else if (pargs.r_opt == oNoOptions)
+ default_config = 0; /* --no-options */
+ else if (pargs.r_opt == oHomedir)
+ {
+ opt.homedir = pargs.r.ret_str;
+ homedir_seen = 1;
+ }
+ else if (pargs.r_opt == aDaemon)
+ opt.system_daemon = 1;
+ else if (pargs.r_opt == aService)
+ {
+ /* Redundant. The main function takes care of it. */
+ opt.system_service = 1;
+ opt.system_daemon = 1;
+ }
+#ifdef HAVE_W32_SYSTEM
+ else if (pargs.r_opt == aGPGConfList || pargs.r_opt == aGPGConfTest)
+ /* We set this so we switch to the system configuration
+ directory below. This is a crutch to solve the problem
+ that the user configuration is never used on Windows. Also
+ see below at aGPGConfList. */
+ opt.system_daemon = 1;
+#endif
+ }
+
+ /* If --daemon has been given on the command line but not --homedir,
+ we switch to /etc/gnupg as default home directory. Note, that
+ this also overrides the GNUPGHOME environment variable. */
+ if (opt.system_daemon && !homedir_seen)
+ {
+#ifdef HAVE_W32CE_SYSTEM
+ opt.homedir = DIRSEP_S "gnupg";
+#else
+ opt.homedir = gnupg_sysconfdir ();
+#endif
+ opt.homedir_data = gnupg_datadir ();
+ opt.homedir_cache = gnupg_cachedir ();
+ socket_name = dirmngr_sys_socket_name ();
+ }
+ else if (dirmngr_user_socket_name ())
+ socket_name = dirmngr_user_socket_name ();
+ else
+ socket_name = dirmngr_sys_socket_name ();
+
+ if (default_config)
+ configname = make_filename (opt.homedir, DIRMNGR_NAME".conf", NULL );
+
+ argc = orig_argc;
+ argv = orig_argv;
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* do not remove the args */
+ next_pass:
+ if (configname)
+ {
+ configlineno = 0;
+ configfp = fopen (configname, "r");
+ if (!configfp)
+ {
+ if (default_config)
+ {
+ if( parse_debug )
+ log_info (_("Note: no default option file '%s'\n"),
+ configname );
+ }
+ else
+ {
+ log_error (_("option file '%s': %s\n"),
+ configname, strerror(errno) );
+ exit(2);
+ }
+ xfree (configname);
+ configname = NULL;
+ }
+ if (parse_debug && configname )
+ log_info (_("reading options from '%s'\n"), configname );
+ default_config = 0;
+ }
+
+ while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) )
+ {
+ if (parse_rereadable_options (&pargs, 0))
+ continue; /* Already handled */
+ switch (pargs.r_opt)
+ {
+ case aServer:
+ case aDaemon:
+ case aService:
+ case aShutdown:
+ case aFlush:
+ case aListCRLs:
+ case aLoadCRL:
+ case aFetchCRL:
+ case aGPGConfList:
+ case aGPGConfTest:
+ cmd = pargs.r_opt;
+ break;
+
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+ case oBatch: opt.batch=1; break;
+
+ case oDebug: opt.debug |= pargs.r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+ case oDebugLevel: debug_level = pargs.r.ret_str; break;
+ case oDebugWait: debug_wait = pargs.r.ret_int; break;
+
+ case oOptions:
+ /* Config files may not be nested (silently ignore them) */
+ if (!configfp)
+ {
+ xfree(configname);
+ configname = xstrdup(pargs.r.ret_str);
+ goto next_pass;
+ }
+ break;
+ case oNoGreeting: nogreeting = 1; break;
+ case oNoVerbose: opt.verbose = 0; break;
+ case oNoOptions: break; /* no-options */
+ case oHomedir: /* Ignore this option here. */; break;
+ case oNoDetach: nodetach = 1; break;
+ case oLogFile: logfile = pargs.r.ret_str; break;
+ case oCsh: csh_style = 1; break;
+ case oSh: csh_style = 0; break;
+ case oLDAPFile:
+# if USE_LDAP
+ ldapfile = pargs.r.ret_str;
+# endif /*USE_LDAP*/
+ break;
+ case oLDAPAddServers: opt.add_new_ldapservers = 1; break;
+ case oLDAPTimeout:
+ opt.ldaptimeout = pargs.r.ret_int;
+ break;
+
+ case oFakedSystemTime:
+ gnupg_set_time ((time_t)pargs.r.ret_ulong, 0);
+ break;
+
+ case oForce: opt.force = 1; break;
+
+ case oSocketName: socket_name = pargs.r.ret_str; break;
+
+ default : pargs.err = configfp? 1:2; break;
+ }
+ }
+ if (configfp)
+ {
+ fclose (configfp);
+ configfp = NULL;
+ /* Keep a copy of the name so that it can be read on SIGHUP. */
+ opt.config_filename = configname;
+ configname = NULL;
+ goto next_pass;
+ }
+ xfree (configname);
+ configname = NULL;
+ if (log_get_errorcount(0))
+ exit(2);
+ if (nogreeting )
+ greeting = 0;
+
+ if (!opt.homedir_data)
+ opt.homedir_data = opt.homedir;
+ if (!opt.homedir_cache)
+ opt.homedir_cache = opt.homedir;
+
+ if (greeting)
+ {
+ es_fprintf (es_stderr, "%s %s; %s\n",
+ strusage(11), strusage(13), strusage(14) );
+ es_fprintf (es_stderr, "%s\n", strusage(15) );
+ }
+
+#ifdef IS_DEVELOPMENT_VERSION
+ log_info ("NOTE: this is a development version!\n");
+#endif
+
+ /* Print a warning if an argument looks like an option. */
+ if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
+ {
+ int i;
+
+ for (i=0; i < argc; i++)
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
+ }
+
+ if (!access ("/etc/"DIRMNGR_NAME, F_OK) && !strncmp (opt.homedir, "/etc/", 5))
+ log_info
+ ("NOTE: DirMngr is now a proper part of %s. The configuration and"
+ " other directory names changed. Please check that no other version"
+ " of dirmngr is still installed. To disable this warning, remove the"
+ " directory '/etc/dirmngr'.\n", GNUPG_NAME);
+
+ if (gnupg_faked_time_p ())
+ {
+ gnupg_isotime_t tbuf;
+
+ log_info (_("WARNING: running with faked system time: "));
+ gnupg_get_isotime (tbuf);
+ dump_isotime (tbuf);
+ log_printf ("\n");
+ }
+
+ set_debug ();
+
+ /* Get LDAP server list from file. */
+#if USE_LDAP
+ if (!ldapfile)
+ {
+ ldapfile = make_filename (opt.homedir,
+ opt.system_daemon?
+ "ldapservers.conf":"dirmngr_ldapservers.conf",
+ NULL);
+ opt.ldapservers = parse_ldapserver_file (ldapfile);
+ xfree (ldapfile);
+ }
+ else
+ opt.ldapservers = parse_ldapserver_file (ldapfile);
+#endif /*USE_LDAP*/
+
+#ifndef HAVE_W32_SYSTEM
+ /* We need to ignore the PIPE signal because the we might log to a
+ socket and that code handles EPIPE properly. The ldap wrapper
+ also requires us to ignore this silly signal. Assuan would set
+ this signal to ignore anyway.*/
+ signal (SIGPIPE, SIG_IGN);
+#endif
+
+ /* Ready. Now to our duties. */
+ if (!cmd && opt.system_service)
+ cmd = aDaemon;
+ else if (!cmd)
+ cmd = aServer;
+ rc = 0;
+
+ if (cmd == aServer)
+ {
+ /* Note that this server mode is mainly useful for debugging. */
+ if (argc)
+ wrong_args ("--server");
+
+ if (logfile)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, 2|4);
+ }
+
+ if (debug_wait)
+ {
+ log_debug ("waiting for debugger - my pid is %u .....\n",
+ (unsigned int)getpid());
+ gnupg_sleep (debug_wait);
+ log_debug ("... okay\n");
+ }
+
+#if USE_LDAP
+ ldap_wrapper_launch_thread ();
+#endif /*USE_LDAP*/
+
+ cert_cache_init ();
+ crl_cache_init ();
+ start_command_handler (ASSUAN_INVALID_FD);
+ shutdown_reaper ();
+ }
+ else if (cmd == aDaemon)
+ {
+ assuan_fd_t fd;
+ pid_t pid;
+ int len;
+ struct sockaddr_un serv_addr;
+
+ if (argc)
+ wrong_args ("--daemon");
+
+ /* Now start with logging to a file if this is desired. */
+ if (logfile)
+ {
+ log_set_file (logfile);
+ log_set_prefix (NULL, (JNLIB_LOG_WITH_PREFIX
+ |JNLIB_LOG_WITH_TIME
+ |JNLIB_LOG_WITH_PID));
+ current_logfile = xstrdup (logfile);
+ }
+
+#ifndef HAVE_W32_SYSTEM
+ if (strchr (socket_name, ':'))
+ {
+ log_error (_("colons are not allowed in the socket name\n"));
+ dirmngr_exit (1);
+ }
+#endif
+ if (strlen (socket_name)+1 >= sizeof serv_addr.sun_path )
+ {
+ log_error (_("name of socket too long\n"));
+ dirmngr_exit (1);
+ }
+
+ fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
+ if (fd == ASSUAN_INVALID_FD)
+ {
+ log_error (_("can't create socket: %s\n"), strerror (errno));
+ cleanup ();
+ dirmngr_exit (1);
+ }
+
+ memset (&serv_addr, 0, sizeof serv_addr);
+ serv_addr.sun_family = AF_UNIX;
+ strcpy (serv_addr.sun_path, socket_name);
+ len = SUN_LEN (&serv_addr);
+
+ rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
+ if (rc == -1
+ && (errno == EADDRINUSE
+#ifdef HAVE_W32_SYSTEM
+ || errno == EEXIST
+#endif
+ ))
+ {
+ /* Fixme: We should test whether a dirmngr is already running. */
+ gnupg_remove (socket_name);
+ rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
+ }
+ if (rc != -1
+ && (rc = assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, len, &socket_nonce)))
+ log_error (_("error getting nonce for the socket\n"));
+ if (rc == -1)
+ {
+ log_error (_("error binding socket to '%s': %s\n"),
+ serv_addr.sun_path, gpg_strerror (gpg_error_from_errno (errno)));
+ assuan_sock_close (fd);
+ dirmngr_exit (1);
+ }
+ cleanup_socket = 1;
+
+ if (listen (FD2INT (fd), 5) == -1)
+ {
+ log_error (_("listen() failed: %s\n"), strerror (errno));
+ assuan_sock_close (fd);
+ dirmngr_exit (1);
+ }
+
+ if (opt.verbose)
+ log_info (_("listening on socket '%s'\n"), socket_name );
+
+ es_fflush (NULL);
+
+ /* Note: We keep the dirmngr_info output only for the sake of
+ existing scripts which might use this to detect a successful
+ start of the dirmngr. */
+#ifdef HAVE_W32_SYSTEM
+ (void)csh_style;
+ (void)nodetach;
+
+ pid = getpid ();
+ es_printf ("set %s=%s;%lu;1\n",
+ DIRMNGR_INFO_NAME, socket_name, (ulong) pid);
+#else
+ pid = fork();
+ if (pid == (pid_t)-1)
+ {
+ log_fatal (_("error forking process: %s\n"), strerror (errno));
+ dirmngr_exit (1);
+ }
+
+ if (pid)
+ { /* We are the parent */
+ char *infostr;
+
+ /* Don't let cleanup() remove the socket - the child is
+ responsible for doing that. */
+ cleanup_socket = 0;
+
+ close (fd);
+
+ /* Create the info string: <name>:<pid>:<protocol_version> */
+ if (asprintf (&infostr, "%s=%s:%lu:1",
+ DIRMNGR_INFO_NAME, socket_name, (ulong)pid ) < 0)
+ {
+ log_error (_("out of core\n"));
+ kill (pid, SIGTERM);
+ dirmngr_exit (1);
+ }
+ /* Print the environment string, so that the caller can use
+ shell's eval to set it. But see above. */
+ if (csh_style)
+ {
+ *strchr (infostr, '=') = ' ';
+ es_printf ( "setenv %s;\n", infostr);
+ }
+ else
+ {
+ es_printf ( "%s; export %s;\n", infostr, DIRMNGR_INFO_NAME);
+ }
+ free (infostr);
+ exit (0);
+ /*NEVER REACHED*/
+ } /* end parent */
+
+
+ /*
+ This is the child
+ */
+
+ /* Detach from tty and put process into a new session */
+ if (!nodetach )
+ {
+ int i;
+ unsigned int oldflags;
+
+ /* Close stdin, stdout and stderr unless it is the log stream */
+ for (i=0; i <= 2; i++)
+ {
+ if (!log_test_fd (i) && i != fd )
+ close (i);
+ }
+ if (setsid() == -1)
+ {
+ log_error ("setsid() failed: %s\n", strerror(errno) );
+ dirmngr_exit (1);
+ }
+
+ log_get_prefix (&oldflags);
+ log_set_prefix (NULL, oldflags | JNLIB_LOG_RUN_DETACHED);
+ opt.running_detached = 1;
+
+ if (chdir("/"))
+ {
+ log_error ("chdir to / failed: %s\n", strerror (errno));
+ dirmngr_exit (1);
+ }
+ }
+#endif
+
+#if USE_LDAP
+ ldap_wrapper_launch_thread ();
+#endif /*USE_LDAP*/
+
+ cert_cache_init ();
+ crl_cache_init ();
+#ifdef USE_W32_SERVICE
+ if (opt.system_service)
+ {
+ service_status.dwCurrentState = SERVICE_RUNNING;
+ SetServiceStatus (service_handle, &service_status);
+ }
+#endif
+ handle_connections (fd);
+ assuan_sock_close (fd);
+ shutdown_reaper ();
+#ifdef USE_W32_SERVICE
+ if (opt.system_service)
+ {
+ service_status.dwCurrentState = SERVICE_STOPPED;
+ SetServiceStatus (service_handle, &service_status);
+ }
+#endif
+ }
+ else if (cmd == aListCRLs)
+ {
+ /* Just list the CRL cache and exit. */
+ if (argc)
+ wrong_args ("--list-crls");
+#if USE_LDAP
+ ldap_wrapper_launch_thread ();
+#endif /*USE_LDAP*/
+ crl_cache_init ();
+ crl_cache_list (es_stdout);
+ }
+ else if (cmd == aLoadCRL)
+ {
+ struct server_control_s ctrlbuf;
+
+ memset (&ctrlbuf, 0, sizeof ctrlbuf);
+ dirmngr_init_default_ctrl (&ctrlbuf);
+
+#if USE_LDAP
+ ldap_wrapper_launch_thread ();
+#endif /*USE_LDAP*/
+ cert_cache_init ();
+ crl_cache_init ();
+ if (!argc)
+ rc = crl_cache_load (&ctrlbuf, NULL);
+ else
+ {
+ for (; !rc && argc; argc--, argv++)
+ rc = crl_cache_load (&ctrlbuf, *argv);
+ }
+ }
+ else if (cmd == aFetchCRL)
+ {
+ ksba_reader_t reader;
+ struct server_control_s ctrlbuf;
+
+ if (argc != 1)
+ wrong_args ("--fetch-crl URL");
+
+ memset (&ctrlbuf, 0, sizeof ctrlbuf);
+ dirmngr_init_default_ctrl (&ctrlbuf);
+
+#if USE_LDAP
+ ldap_wrapper_launch_thread ();
+#endif /*USE_LDAP*/
+ cert_cache_init ();
+ crl_cache_init ();
+ rc = crl_fetch (&ctrlbuf, argv[0], &reader);
+ if (rc)
+ log_error (_("fetching CRL from '%s' failed: %s\n"),
+ argv[0], gpg_strerror (rc));
+ else
+ {
+ rc = crl_cache_insert (&ctrlbuf, argv[0], reader);
+ if (rc)
+ log_error (_("processing CRL from '%s' failed: %s\n"),
+ argv[0], gpg_strerror (rc));
+ crl_close_reader (reader);
+ }
+ }
+ else if (cmd == aFlush)
+ {
+ /* Delete cache and exit. */
+ if (argc)
+ wrong_args ("--flush");
+ rc = crl_cache_flush();
+ }
+ else if (cmd == aGPGConfTest)
+ dirmngr_exit (0);
+ else if (cmd == aGPGConfList)
+ {
+ unsigned long flags = 0;
+ char *filename;
+ char *filename_esc;
+
+#ifdef HAVE_W32_SYSTEM
+ /* On Windows systems, dirmngr always runs as system daemon, and
+ the per-user configuration is never used. So we short-cut
+ everything to use the global system configuration of dirmngr
+ above, and here we set the no change flag to make these
+ read-only. */
+ flags |= GC_OPT_FLAG_NO_CHANGE;
+#endif
+
+ /* First the configuration file. This is not an option, but it
+ is vital information for GPG Conf. */
+ if (!opt.config_filename)
+ opt.config_filename = make_filename (opt.homedir,
+ "dirmngr.conf", NULL );
+
+ filename = percent_escape (opt.config_filename, NULL);
+ es_printf ("gpgconf-dirmngr.conf:%lu:\"%s\n",
+ GC_OPT_FLAG_DEFAULT, filename);
+ xfree (filename);
+
+ es_printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT);
+ es_printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE);
+
+ /* --csh and --sh are mutually exclusive, something we can not
+ express in GPG Conf. --options is only usable from the
+ command line, really. --debug-all interacts with --debug,
+ and having both of them is thus problematic. --no-detach is
+ also only usable on the command line. --batch is unused. */
+
+ filename = make_filename (opt.homedir,
+ opt.system_daemon?
+ "ldapservers.conf":"dirmngr_ldapservers.conf",
+ NULL);
+ filename_esc = percent_escape (filename, NULL);
+ es_printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT,
+ filename_esc);
+ xfree (filename_esc);
+ xfree (filename);
+
+ es_printf ("ldaptimeout:%lu:%u\n",
+ flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT);
+ es_printf ("max-replies:%lu:%u\n",
+ flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES);
+ es_printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE);
+
+ es_printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE);
+
+ es_printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ es_printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ /* Note: The next one is to fix a typo in gpgconf - should be
+ removed eventually. */
+ es_printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ }
+ cleanup ();
+ return !!rc;
+}
+
+
+#ifdef USE_W32_SERVICE
+static void WINAPI
+call_real_main (DWORD argc, LPSTR *argv)
+{
+ real_main (argc, argv);
+}
+
+int
+main (int argc, char *argv[])
+{
+ int i;
+
+ /* Find out if we run in daemon mode or on the command line. */
+ for (i = 1; i < argc; i++)
+ if (!strcmp (argv[i], "--service"))
+ {
+ opt.system_service = 1;
+ opt.system_daemon = 1;
+ break;
+ }
+
+ if (!opt.system_service)
+ return real_main (argc, argv);
+ else
+ {
+ SERVICE_TABLE_ENTRY DispatchTable [] =
+ {
+ { "DirMngr", &call_real_main },
+ { NULL, NULL }
+ };
+
+ if (!StartServiceCtrlDispatcher (DispatchTable))
+ return 1;
+ return 0;
+ }
+}
+#endif /*USE_W32_SERVICE*/
+
+
+static void
+cleanup (void)
+{
+ crl_cache_deinit ();
+ cert_cache_deinit (1);
+
+#if USE_LDAP
+ ldapserver_list_free (opt.ldapservers);
+#endif /*USE_LDAP*/
+ opt.ldapservers = NULL;
+
+ if (cleanup_socket)
+ {
+ cleanup_socket = 0;
+ if (socket_name && *socket_name)
+ gnupg_remove (socket_name);
+ }
+}
+
+
+void
+dirmngr_exit (int rc)
+{
+ cleanup ();
+ exit (rc);
+}
+
+
+void
+dirmngr_init_default_ctrl (ctrl_t ctrl)
+{
+ (void)ctrl;
+
+ /* Nothing for now. */
+}
+
+
+/* Create a list of LDAP servers from the file FILENAME. Returns the
+ list or NULL in case of errors.
+
+ The format fo such a file is line oriented where empty lines and
+ lines starting with a hash mark are ignored. All other lines are
+ assumed to be colon seprated with these fields:
+
+ 1. field: Hostname
+ 2. field: Portnumber
+ 3. field: Username
+ 4. field: Password
+ 5. field: Base DN
+
+*/
+#if USE_LDAP
+static ldap_server_t
+parse_ldapserver_file (const char* filename)
+{
+ char buffer[1024];
+ char *p;
+ ldap_server_t server, serverstart, *serverend;
+ int c;
+ unsigned int lineno = 0;
+ estream_t fp;
+
+ fp = es_fopen (filename, "r");
+ if (!fp)
+ {
+ log_error (_("error opening '%s': %s\n"), filename, strerror (errno));
+ return NULL;
+ }
+
+ serverstart = NULL;
+ serverend = &serverstart;
+ while (es_fgets (buffer, sizeof buffer, fp))
+ {
+ lineno++;
+ if (!*buffer || buffer[strlen(buffer)-1] != '\n')
+ {
+ if (*buffer && es_feof (fp))
+ ; /* Last line not terminated - continue. */
+ else
+ {
+ log_error (_("%s:%u: line too long - skipped\n"),
+ filename, lineno);
+ while ( (c=es_fgetc (fp)) != EOF && c != '\n')
+ ; /* Skip until end of line. */
+ continue;
+ }
+ }
+ /* Skip empty and comment lines.*/
+ for (p=buffer; spacep (p); p++)
+ ;
+ if (!*p || *p == '\n' || *p == '#')
+ continue;
+
+ /* Parse the colon separated fields. */
+ server = ldapserver_parse_one (buffer, filename, lineno);
+ if (server)
+ {
+ *serverend = server;
+ serverend = &server->next;
+ }
+ }
+
+ if (es_ferror (fp))
+ log_error (_("error reading '%s': %s\n"), filename, strerror (errno));
+ es_fclose (fp);
+
+ return serverstart;
+}
+#endif /*USE_LDAP*/
+
+static fingerprint_list_t
+parse_ocsp_signer (const char *string)
+{
+ gpg_error_t err;
+ char *fname;
+ estream_t fp;
+ char line[256];
+ char *p;
+ fingerprint_list_t list, *list_tail, item;
+ unsigned int lnr = 0;
+ int c, i, j;
+ int errflag = 0;
+
+
+ /* Check whether this is not a filename and treat it as a direct
+ fingerprint specification. */
+ if (!strpbrk (string, "/.~\\"))
+ {
+ item = xcalloc (1, sizeof *item);
+ for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++)
+ if ( string[i] != ':' )
+ item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i];
+ item->hexfpr[j] = 0;
+ if (j != 40 || !(spacep (string+i) || !string[i]))
+ {
+ log_error (_("%s:%u: invalid fingerprint detected\n"),
+ "--ocsp-signer", 0);
+ xfree (item);
+ return NULL;
+ }
+ return item;
+ }
+
+ /* Well, it is a filename. */
+ if (*string == '/' || (*string == '~' && string[1] == '/'))
+ fname = make_filename (string, NULL);
+ else
+ {
+ if (string[0] == '.' && string[1] == '/' )
+ string += 2;
+ fname = make_filename (opt.homedir, string, NULL);
+ }
+
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
+ xfree (fname);
+ return NULL;
+ }
+
+ list = NULL;
+ list_tail = &list;
+ for (;;)
+ {
+ if (!es_fgets (line, DIM(line)-1, fp) )
+ {
+ if (!es_feof (fp))
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("%s:%u: read error: %s\n"),
+ fname, lnr, gpg_strerror (err));
+ errflag = 1;
+ }
+ es_fclose (fp);
+ if (errflag)
+ {
+ while (list)
+ {
+ fingerprint_list_t tmp = list->next;
+ xfree (list);
+ list = tmp;
+ }
+ }
+ xfree (fname);
+ return list; /* Ready. */
+ }
+
+ lnr++;
+ if (!*line || line[strlen(line)-1] != '\n')
+ {
+ /* Eat until end of line. */
+ while ( (c=es_getc (fp)) != EOF && c != '\n')
+ ;
+ err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
+ /* */: GPG_ERR_INCOMPLETE_LINE);
+ log_error (_("%s:%u: read error: %s\n"),
+ fname, lnr, gpg_strerror (err));
+ errflag = 1;
+ continue;
+ }
+
+ /* Allow for empty lines and spaces */
+ for (p=line; spacep (p); p++)
+ ;
+ if (!*p || *p == '\n' || *p == '#')
+ continue;
+
+ item = xcalloc (1, sizeof *item);
+ *list_tail = item;
+ list_tail = &item->next;
+
+ for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
+ if ( p[i] != ':' )
+ item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
+ item->hexfpr[j] = 0;
+ if (j != 40 || !(spacep (p+i) || p[i] == '\n'))
+ {
+ log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr);
+ errflag = 1;
+ }
+ i++;
+ while (spacep (p+i))
+ i++;
+ if (p[i] && p[i] != '\n')
+ log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr);
+ }
+ /*NOTREACHED*/
+}
+
+
+
+
+/*
+ Stuff used in daemon mode.
+ */
+
+
+
+/* Reread parts of the configuration. Note, that this function is
+ obviously not thread-safe and should only be called from the NPTH
+ signal handler.
+
+ Fixme: Due to the way the argument parsing works, we create a
+ memory leak here for all string type arguments. There is currently
+ no clean way to tell whether the memory for the argument has been
+ allocated or points into the process' original arguments. Unless
+ we have a mechanism to tell this, we need to live on with this. */
+static void
+reread_configuration (void)
+{
+ ARGPARSE_ARGS pargs;
+ FILE *fp;
+ unsigned int configlineno = 0;
+ int dummy;
+
+ if (!opt.config_filename)
+ return; /* No config file. */
+
+ fp = fopen (opt.config_filename, "r");
+ if (!fp)
+ {
+ log_error (_("option file '%s': %s\n"),
+ opt.config_filename, strerror(errno) );
+ return;
+ }
+
+ parse_rereadable_options (NULL, 1); /* Start from the default values. */
+
+ memset (&pargs, 0, sizeof pargs);
+ dummy = 0;
+ pargs.argc = &dummy;
+ pargs.flags = 1; /* do not remove the args */
+ while (optfile_parse (fp, opt.config_filename, &configlineno, &pargs, opts) )
+ {
+ if (pargs.r_opt < -1)
+ pargs.err = 1; /* Print a warning. */
+ else /* Try to parse this option - ignore unchangeable ones. */
+ parse_rereadable_options (&pargs, 1);
+ }
+ fclose (fp);
+
+ set_debug ();
+}
+
+
+/* A global function which allows us to trigger the reload stuff from
+ other places. */
+void
+dirmngr_sighup_action (void)
+{
+ log_info (_("SIGHUP received - "
+ "re-reading configuration and flushing caches\n"));
+ reread_configuration ();
+ cert_cache_deinit (0);
+ crl_cache_deinit ();
+ cert_cache_init ();
+ crl_cache_init ();
+}
+
+
+
+/* The signal handler. */
+#ifndef HAVE_W32_SYSTEM
+static void
+handle_signal (int signo)
+{
+ switch (signo)
+ {
+ case SIGHUP:
+ dirmngr_sighup_action ();
+ break;
+
+ case SIGUSR1:
+ cert_cache_print_stats ();
+ break;
+
+ case SIGUSR2:
+ log_info (_("SIGUSR2 received - no action defined\n"));
+ break;
+
+ case SIGTERM:
+ if (!shutdown_pending)
+ log_info (_("SIGTERM received - shutting down ...\n"));
+ else
+ log_info (_("SIGTERM received - still %d active connections\n"),
+ active_connections);
+ shutdown_pending++;
+ if (shutdown_pending > 2)
+ {
+ log_info (_("shutdown forced\n"));
+ log_info ("%s %s stopped\n", strusage(11), strusage(13) );
+ cleanup ();
+ dirmngr_exit (0);
+ }
+ break;
+
+ case SIGINT:
+ log_info (_("SIGINT received - immediate shutdown\n"));
+ log_info( "%s %s stopped\n", strusage(11), strusage(13));
+ cleanup ();
+ dirmngr_exit (0);
+ break;
+
+ default:
+ log_info (_("signal %d received - no action defined\n"), signo);
+ }
+}
+#endif /*!HAVE_W32_SYSTEM*/
+
+
+/* Thread to do the housekeeping. */
+static void *
+housekeeping_thread (void *arg)
+{
+ static int sentinel;
+ time_t curtime;
+
+ (void)arg;
+
+ curtime = gnupg_get_time ();
+ if (sentinel)
+ {
+ log_info ("housekeeping is already going on\n");
+ return NULL;
+ }
+ sentinel++;
+ if (opt.verbose)
+ log_info ("starting housekeeping\n");
+
+ ks_hkp_housekeeping (curtime);
+
+ if (opt.verbose)
+ log_info ("ready with housekeeping\n");
+ sentinel--;
+ return NULL;
+
+}
+
+
+#if JNLIB_GCC_HAVE_PUSH_PRAGMA
+# pragma GCC push_options
+# pragma GCC optimize ("no-strict-overflow")
+#endif
+static int
+time_for_housekeeping_p (time_t curtime)
+{
+ static time_t last_housekeeping;
+
+ if (!last_housekeeping)
+ last_housekeeping = curtime;
+
+ if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime
+ || last_housekeeping > curtime /*(be prepared for y2038)*/)
+ {
+ last_housekeeping = curtime;
+ return 1;
+ }
+ return 0;
+}
+#if JNLIB_GCC_HAVE_PUSH_PRAGMA
+# pragma GCC pop_options
+#endif
+
+
+/* This is the worker for the ticker. It is called every few seconds
+ and may only do fast operations. */
+static void
+handle_tick (void)
+{
+ /* Under Windows we don't use signals and need a way for the loop to
+ check for the shutdown flag. */
+#ifdef HAVE_W32_SYSTEM
+ if (shutdown_pending)
+ log_info (_("SIGTERM received - shutting down ...\n"));
+ if (shutdown_pending > 2)
+ {
+ log_info (_("shutdown forced\n"));
+ log_info ("%s %s stopped\n", strusage(11), strusage(13) );
+ cleanup ();
+ dirmngr_exit (0);
+ }
+#endif /*HAVE_W32_SYSTEM*/
+
+ if (time_for_housekeeping_p (gnupg_get_time ()))
+ {
+ npth_t thread;
+ npth_attr_t tattr;
+ int err;
+
+ err = npth_attr_init (&tattr);
+ if (err)
+ log_error ("error preparing housekeeping thread: %s\n", strerror (err));
+ else
+ {
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+ err = npth_create (&thread, &tattr, housekeeping_thread, NULL);
+ if (err)
+ log_error ("error spawning housekeeping thread: %s\n",
+ strerror (err));
+ npth_attr_destroy (&tattr);
+ }
+ }
+}
+
+
+/* Check the nonce on a new connection. This is a NOP unless we we
+ are using our Unix domain socket emulation under Windows. */
+static int
+check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce)
+{
+ if (assuan_sock_check_nonce (fd, nonce))
+ {
+ log_info (_("error reading nonce on fd %d: %s\n"),
+ FD2INT (fd), strerror (errno));
+ assuan_sock_close (fd);
+ return -1;
+ }
+ else
+ return 0;
+}
+
+
+/* Helper to call a connection's main fucntion. */
+static void *
+start_connection_thread (void *arg)
+{
+ union int_and_ptr_u argval;
+ gnupg_fd_t fd;
+
+ argval.aptr = arg;
+ fd = argval.afd;
+
+ if (check_nonce (fd, &socket_nonce))
+ {
+ log_error ("handler nonce check FAILED\n");
+ return NULL;
+ }
+
+#ifndef HAVE_W32_SYSTEM
+ npth_setspecific (my_tlskey_current_fd, argval.aptr);
+#endif
+
+ active_connections++;
+ if (opt.verbose)
+ log_info (_("handler for fd %d started\n"), FD2INT (fd));
+
+ start_command_handler (fd);
+
+ if (opt.verbose)
+ log_info (_("handler for fd %d terminated\n"), FD2INT (fd));
+ active_connections--;
+
+#ifndef HAVE_W32_SYSTEM
+ argval.afd = ASSUAN_INVALID_FD;
+ npth_setspecific (my_tlskey_current_fd, argval.aptr);
+#endif
+
+ return NULL;
+}
+
+
+/* Main loop in daemon mode. */
+static void
+handle_connections (assuan_fd_t listen_fd)
+{
+ npth_attr_t tattr;
+#ifndef HAVE_W32_SYSTEM
+ int signo;
+#endif
+ struct sockaddr_un paddr;
+ socklen_t plen = sizeof( paddr );
+ gnupg_fd_t fd;
+ int nfd, ret;
+ fd_set fdset, read_fdset;
+ struct timespec abstime;
+ struct timespec curtime;
+ struct timespec timeout;
+ int saved_errno;
+
+ npth_attr_init (&tattr);
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+
+#ifndef HAVE_W32_SYSTEM /* FIXME */
+ npth_sigev_init ();
+ npth_sigev_add (SIGHUP);
+ npth_sigev_add (SIGUSR1);
+ npth_sigev_add (SIGUSR2);
+ npth_sigev_add (SIGINT);
+ npth_sigev_add (SIGTERM);
+ npth_sigev_fini ();
+#endif
+
+ /* Setup the fdset. It has only one member. This is because we use
+ pth_select instead of pth_accept to properly sync timeouts with
+ to full second. */
+ FD_ZERO (&fdset);
+ FD_SET (FD2INT (listen_fd), &fdset);
+ nfd = FD2INT (listen_fd);
+
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+
+ /* Main loop. */
+ for (;;)
+ {
+ /* Shutdown test. */
+ if (shutdown_pending)
+ {
+ if (!active_connections)
+ break; /* ready */
+
+ /* Do not accept new connections but keep on running the
+ loop to cope with the timer events. */
+ FD_ZERO (&fdset);
+ }
+
+ /* Take a copy of the fdset. */
+ read_fdset = fdset;
+
+ npth_clock_gettime (&curtime);
+ if (!(npth_timercmp (&curtime, &abstime, <)))
+ {
+ /* Timeout. */
+ handle_tick ();
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+ }
+ npth_timersub (&abstime, &curtime, &timeout);
+
+#ifndef HAVE_W32_SYSTEM
+ ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, npth_sigev_sigmask());
+ saved_errno = errno;
+
+ while (npth_sigev_get_pending(&signo))
+ handle_signal (signo);
+#else
+ ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, NULL, NULL);
+ saved_errno = errno;
+#endif
+
+ if (ret == -1 && saved_errno != EINTR)
+ {
+ log_error (_("npth_pselect failed: %s - waiting 1s\n"),
+ strerror (saved_errno));
+ npth_sleep (1);
+ continue;
+ }
+
+ if (ret <= 0)
+ /* Interrupt or timeout. Will be handled when calculating the
+ next timeout. */
+ continue;
+
+ if (!shutdown_pending && FD_ISSET (FD2INT (listen_fd), &read_fdset))
+ {
+ plen = sizeof paddr;
+ fd = INT2FD (npth_accept (FD2INT(listen_fd),
+ (struct sockaddr *)&paddr, &plen));
+ if (fd == GNUPG_INVALID_FD)
+ {
+ log_error ("accept failed: %s\n", strerror (errno));
+ }
+ else
+ {
+ char threadname[50];
+ union int_and_ptr_u argval;
+ npth_t thread;
+
+ argval.afd = fd;
+ snprintf (threadname, sizeof threadname-1,
+ "conn fd=%d", FD2INT(fd));
+ threadname[sizeof threadname -1] = 0;
+
+ ret = npth_create (&thread, &tattr, start_connection_thread, argval.aptr);
+ if (ret)
+ {
+ log_error ("error spawning connection handler: %s\n",
+ strerror (ret) );
+ assuan_sock_close (fd);
+ }
+ npth_setname_np (thread, threadname);
+ }
+ fd = GNUPG_INVALID_FD;
+ }
+ }
+
+ npth_attr_destroy (&tattr);
+ cleanup ();
+ log_info ("%s %s stopped\n", strusage(11), strusage(13));
+}
diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h
new file mode 100644
index 0000000..bb368f2
--- /dev/null
+++ b/dirmngr/dirmngr.h
@@ -0,0 +1,206 @@
+/* dirmngr.h - Common definitions for the dirmngr
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2004 g10 Code GmbH
+ * Copyright (C) 2014 Werner Koch
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DIRMNGR_H
+#define DIRMNGR_H
+
+#include "./dirmngr-err.h"
+#define map_assuan_err(a) \
+ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a))
+#include <errno.h>
+#include <gcrypt.h>
+#include <ksba.h>
+
+#include "../common/util.h"
+#include "../common/membuf.h"
+#include "../common/sysutils.h" /* (gnupg_fd_t) */
+#include "../common/i18n.h"
+#include "../common/http.h" /* (parsed_uri_t) */
+
+/* This objects keeps information about a particular LDAP server and
+ is used as item of a single linked list of servers. */
+struct ldap_server_s
+{
+ struct ldap_server_s* next;
+
+ char *host;
+ int port;
+ char *user;
+ char *pass;
+ char *base;
+};
+typedef struct ldap_server_s *ldap_server_t;
+
+
+/* This objects is used to build a list of URI consisting of the
+ original and the parsed URI. */
+struct uri_item_s
+{
+ struct uri_item_s *next;
+ parsed_uri_t parsed_uri; /* The broken down URI. */
+ char uri[1]; /* The original URI. */
+};
+typedef struct uri_item_s *uri_item_t;
+
+
+/* A list of fingerprints. */
+struct fingerprint_list_s;
+typedef struct fingerprint_list_s *fingerprint_list_t;
+struct fingerprint_list_s
+{
+ fingerprint_list_t next;
+ char hexfpr[20+20+1];
+};
+
+
+/* A large struct named "opt" to keep global flags. */
+struct
+{
+ unsigned int debug; /* debug flags (DBG_foo_VALUE) */
+ int verbose; /* verbosity level */
+ int quiet; /* be as quiet as possible */
+ int dry_run; /* don't change any persistent data */
+ int batch; /* batch mode */
+ const char *homedir; /* Configuration directory name */
+ const char *homedir_data; /* Ditto for data files (/usr/share/dirmngr). */
+ const char *homedir_cache; /* Ditto for cache files (/var/cache/dirmngr). */
+
+ char *config_filename; /* Name of a config file, which will be
+ reread on a HUP if it is not NULL. */
+
+ char *ldap_wrapper_program; /* Override value for the LDAP wrapper
+ program. */
+ char *http_wrapper_program; /* Override value for the HTTP wrapper
+ program. */
+
+ int system_service; /* We are running as W32 service (implies daemon). */
+ int system_daemon; /* We are running in system daemon mode. */
+ int running_detached; /* We are running in detached mode. */
+
+ int force; /* Force loading outdated CRLs. */
+
+ int disable_http; /* Do not use HTTP at all. */
+ int disable_ldap; /* Do not use LDAP at all. */
+ int honor_http_proxy; /* Honor the http_proxy env variable. */
+ const char *http_proxy; /* Use given HTTP proxy. */
+ const char *ldap_proxy; /* Use given LDAP proxy. */
+ int only_ldap_proxy; /* Only use the LDAP proxy; no fallback. */
+ int ignore_http_dp; /* Ignore HTTP CRL distribution points. */
+ int ignore_ldap_dp; /* Ignore LDAP CRL distribution points. */
+ int ignore_ocsp_service_url; /* Ignore OCSP service URLs as given in
+ the certificate. */
+
+ /* A list of certificate extension OIDs which are ignored so that
+ one can claim that a critical extension has been handled. One
+ OID per string. */
+ strlist_t ignored_cert_extensions;
+
+ int allow_ocsp; /* Allow using OCSP. */
+
+ int max_replies;
+ unsigned int ldaptimeout;
+
+ ldap_server_t ldapservers;
+ int add_new_ldapservers;
+
+ const char *ocsp_responder; /* Standard OCSP responder's URL. */
+ fingerprint_list_t ocsp_signer; /* The list of fingerprints with allowed
+ standard OCSP signer certificates. */
+
+ unsigned int ocsp_max_clock_skew; /* Allowed seconds of clocks skew. */
+ unsigned int ocsp_max_period; /* Seconds a response is at maximum
+ considered valid after thisUpdate. */
+ unsigned int ocsp_current_period; /* Seconds a response is considered
+ current after nextUpdate. */
+} opt;
+
+
+#define DBG_X509_VALUE 1 /* debug x.509 parsing */
+#define DBG_LOOKUP_VALUE 2 /* debug lookup details */
+#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
+#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
+#define DBG_CACHE_VALUE 64 /* debug the caching */
+#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
+#define DBG_HASHING_VALUE 512 /* debug hashing operations */
+#define DBG_ASSUAN_VALUE 1024 /* debug assuan communication */
+
+#define DBG_X509 (opt.debug & DBG_X509_VALUE)
+#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
+#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
+#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
+#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
+#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
+#define DBG_ASSUAN (opt.debug & DBG_ASSUAN_VALUE)
+
+/* A simple list of certificate references. */
+struct cert_ref_s
+{
+ struct cert_ref_s *next;
+ unsigned char fpr[20];
+};
+typedef struct cert_ref_s *cert_ref_t;
+
+/* Forward references; access only through server.c. */
+struct server_local_s;
+
+/* Connection control structure. */
+struct server_control_s
+{
+ int refcount; /* Count additional references to this object. */
+ int no_server; /* We are not running under server control. */
+ int status_fd; /* Only for non-server mode. */
+ struct server_local_s *server_local;
+ int force_crl_refresh; /* Always load a fresh CRL. */
+
+ int check_revocations_nest_level; /* Internal to check_revovations. */
+ cert_ref_t ocsp_certs; /* Certificates from the current OCSP
+ response. */
+
+ int audit_events; /* Send audit events to client. */
+ uri_item_t keyservers; /* List of keyservers. */
+};
+
+
+/*-- dirmngr.c --*/
+void dirmngr_exit( int ); /* Wrapper for exit() */
+void dirmngr_init_default_ctrl (ctrl_t ctrl);
+void dirmngr_sighup_action (void);
+
+
+/*-- Various housekeeping functions. --*/
+void ks_hkp_housekeeping (time_t curtime);
+
+
+/*-- server.c --*/
+ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl);
+ksba_cert_t get_cert_local (ctrl_t ctrl, const char *issuer);
+ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *issuer);
+ksba_cert_t get_cert_local_ski (ctrl_t ctrl,
+ const char *name, ksba_sexp_t keyid);
+gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr);
+void start_command_handler (gnupg_fd_t fd);
+gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...);
+gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text);
+gpg_error_t dirmngr_tick (ctrl_t ctrl);
+
+
+
+#endif /*DIRMNGR_H*/
diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c
new file mode 100644
index 0000000..daa2d1b
--- /dev/null
+++ b/dirmngr/dirmngr_ldap.c
@@ -0,0 +1,718 @@
+/* dirmngr-ldap.c - The LDAP helper for dirmngr.
+ * Copyright (C) 2004 g10 Code GmbH
+ * Copyright (C) 2010 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#include <errno.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <unistd.h>
+#ifndef USE_LDAPWRAPPER
+# include <npth.h>
+#endif
+
+#ifdef HAVE_W32_SYSTEM
+# include <winsock2.h>
+# include <winldap.h>
+# include <winber.h>
+# include <fcntl.h>
+# include "ldap-url.h"
+#else
+ /* For OpenLDAP, to enable the API that we're using. */
+# define LDAP_DEPRECATED 1
+# include <ldap.h>
+#endif
+
+
+#define JNLIB_NEED_LOG_LOGV
+#include <gpg-error.h>
+#include "../common/logging.h"
+#include "../common/argparse.h"
+#include "../common/stringhelp.h"
+#include "../common/mischelp.h"
+#include "../common/strlist.h"
+
+#include "i18n.h"
+#include "util.h"
+#include "../common/init.h"
+
+/* With the ldap wrapper, there is no need for the npth_unprotect and leave
+ functions; thus we redefine them to nops. If we are not using the
+ ldap wrapper process we need to include the prototype for our
+ module's main function. */
+#ifdef USE_LDAPWRAPPER
+static void npth_unprotect (void) { }
+static void npth_protect (void) { }
+#else
+# include "./ldap-wrapper.h"
+#endif
+
+#ifdef HAVE_W32CE_SYSTEM
+# include "w32-ldap-help.h"
+# define my_ldap_init(a,b) \
+ _dirmngr_ldap_init ((a), (b))
+# define my_ldap_simple_bind_s(a,b,c) \
+ _dirmngr_ldap_simple_bind_s ((a),(b),(c))
+# define my_ldap_search_st(a,b,c,d,e,f,g,h) \
+ _dirmngr_ldap_search_st ((a), (b), (c), (d), (e), (f), (g), (h))
+# define my_ldap_first_attribute(a,b,c) \
+ _dirmngr_ldap_first_attribute ((a),(b),(c))
+# define my_ldap_next_attribute(a,b,c) \
+ _dirmngr_ldap_next_attribute ((a),(b),(c))
+# define my_ldap_get_values_len(a,b,c) \
+ _dirmngr_ldap_get_values_len ((a),(b),(c))
+# define my_ldap_free_attr(a) \
+ xfree ((a))
+#else
+# define my_ldap_init(a,b) ldap_init ((a), (b))
+# define my_ldap_simple_bind_s(a,b,c) ldap_simple_bind_s ((a), (b), (c))
+# define my_ldap_search_st(a,b,c,d,e,f,g,h) \
+ ldap_search_st ((a), (b), (c), (d), (e), (f), (g), (h))
+# define my_ldap_first_attribute(a,b,c) ldap_first_attribute ((a),(b),(c))
+# define my_ldap_next_attribute(a,b,c) ldap_next_attribute ((a),(b),(c))
+# define my_ldap_get_values_len(a,b,c) ldap_get_values_len ((a),(b),(c))
+# define my_ldap_free_attr(a) ldap_memfree ((a))
+#endif
+
+#ifdef HAVE_W32_SYSTEM
+ typedef LDAP_TIMEVAL my_ldap_timeval_t;
+#else
+ typedef struct timeval my_ldap_timeval_t;
+#endif
+
+#define DEFAULT_LDAP_TIMEOUT 100 /* Arbitrary long timeout. */
+
+
+/* Constants for the options. */
+enum
+ {
+ oQuiet = 'q',
+ oVerbose = 'v',
+
+ oTimeout = 500,
+ oMulti,
+ oProxy,
+ oHost,
+ oPort,
+ oUser,
+ oPass,
+ oEnvPass,
+ oDN,
+ oFilter,
+ oAttr,
+
+ oOnlySearchTimeout,
+ oLogWithPID
+ };
+
+
+/* The list of options as used by the argparse.c code. */
+static ARGPARSE_OPTS opts[] = {
+ { oVerbose, "verbose", 0, N_("verbose") },
+ { oQuiet, "quiet", 0, N_("be somewhat more quiet") },
+ { oTimeout, "timeout", 1, N_("|N|set LDAP timeout to N seconds")},
+ { oMulti, "multi", 0, N_("return all values in"
+ " a record oriented format")},
+ { oProxy, "proxy", 2,
+ N_("|NAME|ignore host part and connect through NAME")},
+ { oHost, "host", 2, N_("|NAME|connect to host NAME")},
+ { oPort, "port", 1, N_("|N|connect to port N")},
+ { oUser, "user", 2, N_("|NAME|use user NAME for authentication")},
+ { oPass, "pass", 2, N_("|PASS|use password PASS"
+ " for authentication")},
+ { oEnvPass, "env-pass", 0, N_("take password from $DIRMNGR_LDAP_PASS")},
+ { oDN, "dn", 2, N_("|STRING|query DN STRING")},
+ { oFilter, "filter", 2, N_("|STRING|use STRING as filter expression")},
+ { oAttr, "attr", 2, N_("|STRING|return the attribute STRING")},
+ { oOnlySearchTimeout, "only-search-timeout", 0, "@"},
+ { oLogWithPID,"log-with-pid", 0, "@"},
+ { 0, NULL, 0, NULL }
+};
+
+
+/* A structure with module options. This is not a static variable
+ because if we are not build as a standalone binary, each thread
+ using this module needs to handle its own values. */
+struct my_opt_s
+{
+ int quiet;
+ int verbose;
+ my_ldap_timeval_t timeout;/* Timeout for the LDAP search functions. */
+ unsigned int alarm_timeout; /* And for the alarm based timeout. */
+ int multi;
+
+ estream_t outstream; /* Send output to thsi stream. */
+
+ /* Note that we can't use const for the strings because ldap_* are
+ not defined that way. */
+ char *proxy; /* Host and Port override. */
+ char *user; /* Authentication user. */
+ char *pass; /* Authentication password. */
+ char *host; /* Override host. */
+ int port; /* Override port. */
+ char *dn; /* Override DN. */
+ char *filter;/* Override filter. */
+ char *attr; /* Override attribute. */
+};
+typedef struct my_opt_s *my_opt_t;
+
+
+/* Prototypes. */
+#ifndef HAVE_W32_SYSTEM
+static void catch_alarm (int dummy);
+#endif
+static int process_url (my_opt_t myopt, const char *url);
+
+
+
+/* Function called by argparse.c to display information. */
+#ifdef USE_LDAPWRAPPER
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+
+ switch(level)
+ {
+ case 11: p = "dirmngr_ldap (@GNUPG@)";
+ break;
+ case 13: p = VERSION; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
+ case 49: p = PACKAGE_BUGREPORT; break;
+ case 1:
+ case 40: p =
+ _("Usage: dirmngr_ldap [options] [URL] (-h for help)\n");
+ break;
+ case 41: p =
+ _("Syntax: dirmngr_ldap [options] [URL]\n"
+ "Internal LDAP helper for Dirmngr\n"
+ "Interface and options may change without notice\n");
+ break;
+
+ default: p = NULL;
+ }
+ return p;
+}
+#endif /*!USE_LDAPWRAPPER*/
+
+
+int
+#ifdef USE_LDAPWRAPPER
+main (int argc, char **argv)
+#else
+ldap_wrapper_main (char **argv, estream_t outstream)
+#endif
+{
+#ifndef USE_LDAPWRAPPER
+ int argc;
+#endif
+ ARGPARSE_ARGS pargs;
+ int any_err = 0;
+ char *p;
+ int only_search_timeout = 0;
+ struct my_opt_s my_opt_buffer;
+ my_opt_t myopt = &my_opt_buffer;
+ char *malloced_buffer1 = NULL;
+
+ memset (&my_opt_buffer, 0, sizeof my_opt_buffer);
+
+#ifdef USE_LDAPWRAPPER
+ set_strusage (my_strusage);
+ log_set_prefix ("dirmngr_ldap", JNLIB_LOG_WITH_PREFIX);
+
+ /* Setup I18N and common subsystems. */
+ i18n_init();
+
+ init_common_subsystems (&argc, &argv);
+
+ es_set_binary (es_stdout);
+ myopt->outstream = es_stdout;
+#else /*!USE_LDAPWRAPPER*/
+ myopt->outstream = outstream;
+ for (argc=0; argv[argc]; argc++)
+ ;
+#endif /*!USE_LDAPWRAPPER*/
+
+ /* LDAP defaults */
+ myopt->timeout.tv_sec = DEFAULT_LDAP_TIMEOUT;
+ myopt->timeout.tv_usec = 0;
+ myopt->alarm_timeout = 0;
+
+ /* Parse the command line. */
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags= 1; /* Do not remove the args. */
+ while (arg_parse (&pargs, opts) )
+ {
+ switch (pargs.r_opt)
+ {
+ case oVerbose: myopt->verbose++; break;
+ case oQuiet: myopt->quiet++; break;
+ case oTimeout:
+ myopt->timeout.tv_sec = pargs.r.ret_int;
+ myopt->timeout.tv_usec = 0;
+ myopt->alarm_timeout = pargs.r.ret_int;
+ break;
+ case oOnlySearchTimeout: only_search_timeout = 1; break;
+ case oMulti: myopt->multi = 1; break;
+ case oUser: myopt->user = pargs.r.ret_str; break;
+ case oPass: myopt->pass = pargs.r.ret_str; break;
+ case oEnvPass:
+ myopt->pass = getenv ("DIRMNGR_LDAP_PASS");
+ break;
+ case oProxy: myopt->proxy = pargs.r.ret_str; break;
+ case oHost: myopt->host = pargs.r.ret_str; break;
+ case oPort: myopt->port = pargs.r.ret_int; break;
+ case oDN: myopt->dn = pargs.r.ret_str; break;
+ case oFilter: myopt->filter = pargs.r.ret_str; break;
+ case oAttr: myopt->attr = pargs.r.ret_str; break;
+ case oLogWithPID:
+ {
+ unsigned int oldflags;
+ log_get_prefix (&oldflags);
+ log_set_prefix (NULL, oldflags | JNLIB_LOG_WITH_PID);
+ }
+ break;
+
+ default :
+#ifdef USE_LDAPWRAPPER
+ pargs.err = ARGPARSE_PRINT_ERROR;
+#else
+ pargs.err = ARGPARSE_PRINT_WARNING; /* No exit() please. */
+#endif
+ break;
+ }
+ }
+
+ if (only_search_timeout)
+ myopt->alarm_timeout = 0;
+
+ if (myopt->proxy)
+ {
+ malloced_buffer1 = xtrystrdup (myopt->proxy);
+ if (!malloced_buffer1)
+ {
+ log_error ("error copying string: %s\n", strerror (errno));
+ return 1;
+ }
+ myopt->host = malloced_buffer1;
+ p = strchr (myopt->host, ':');
+ if (p)
+ {
+ *p++ = 0;
+ myopt->port = atoi (p);
+ }
+ if (!myopt->port)
+ myopt->port = 389; /* make sure ports gets overridden. */
+ }
+
+ if (myopt->port < 0 || myopt->port > 65535)
+ log_error (_("invalid port number %d\n"), myopt->port);
+
+#ifdef USE_LDAPWRAPPER
+ if (log_get_errorcount (0))
+ exit (2);
+ if (argc < 1)
+ usage (1);
+#else
+ /* All passed arguments should be fine in this case. */
+ assert (argc);
+#endif
+
+#ifdef USE_LDAPWRAPPER
+ if (myopt->alarm_timeout)
+ {
+#ifndef HAVE_W32_SYSTEM
+# if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION)
+ struct sigaction act;
+
+ act.sa_handler = catch_alarm;
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction (SIGALRM,&act,NULL))
+# else
+ if (signal (SIGALRM, catch_alarm) == SIG_ERR)
+# endif
+ log_fatal ("unable to register timeout handler\n");
+#endif
+ }
+#endif /*USE_LDAPWRAPPER*/
+
+ for (; argc; argc--, argv++)
+ if (process_url (myopt, *argv))
+ any_err = 1;
+
+ xfree (malloced_buffer1);
+ return any_err;
+}
+
+#ifndef HAVE_W32_SYSTEM
+static void
+catch_alarm (int dummy)
+{
+ (void)dummy;
+ _exit (10);
+}
+#endif
+
+static void
+set_timeout (my_opt_t myopt)
+{
+#ifdef HAVE_W32_SYSTEM
+ /* FIXME for W32. */
+ (void)myopt;
+#else
+ if (myopt->alarm_timeout)
+ alarm (myopt->alarm_timeout);
+#endif
+}
+
+
+/* Helper for fetch_ldap(). */
+static int
+print_ldap_entries (my_opt_t myopt, LDAP *ld, LDAPMessage *msg, char *want_attr)
+{
+ LDAPMessage *item;
+ int any = 0;
+
+ for (npth_unprotect (), item = ldap_first_entry (ld, msg), npth_protect ();
+ item;
+ npth_unprotect (), item = ldap_next_entry (ld, item), npth_protect ())
+ {
+ BerElement *berctx;
+ char *attr;
+
+ if (myopt->verbose > 1)
+ log_info (_("scanning result for attribute '%s'\n"),
+ want_attr? want_attr : "[all]");
+
+ if (myopt->multi)
+ { /* Write item marker. */
+ if (es_fwrite ("I\0\0\0\0", 5, 1, myopt->outstream) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ return -1;
+ }
+ }
+
+
+ for (npth_unprotect (), attr = my_ldap_first_attribute (ld, item, &berctx),
+ npth_protect ();
+ attr;
+ npth_unprotect (), attr = my_ldap_next_attribute (ld, item, berctx),
+ npth_protect ())
+ {
+ struct berval **values;
+ int idx;
+
+ if (myopt->verbose > 1)
+ log_info (_(" available attribute '%s'\n"), attr);
+
+ set_timeout (myopt);
+
+ /* I case we want only one attribute we do a case
+ insensitive compare without the optional extension
+ (i.e. ";binary"). Case insensitive is not really correct
+ but the best we can do. */
+ if (want_attr)
+ {
+ char *cp1, *cp2;
+ int cmpres;
+
+ cp1 = strchr (want_attr, ';');
+ if (cp1)
+ *cp1 = 0;
+ cp2 = strchr (attr, ';');
+ if (cp2)
+ *cp2 = 0;
+ cmpres = ascii_strcasecmp (want_attr, attr);
+ if (cp1)
+ *cp1 = ';';
+ if (cp2)
+ *cp2 = ';';
+ if (cmpres)
+ {
+ my_ldap_free_attr (attr);
+ continue; /* Not found: Try next attribute. */
+ }
+ }
+
+ npth_unprotect ();
+ values = my_ldap_get_values_len (ld, item, attr);
+ npth_protect ();
+
+ if (!values)
+ {
+ if (myopt->verbose)
+ log_info (_("attribute '%s' not found\n"), attr);
+ my_ldap_free_attr (attr);
+ continue;
+ }
+
+ if (myopt->verbose)
+ {
+ log_info (_("found attribute '%s'\n"), attr);
+ if (myopt->verbose > 1)
+ for (idx=0; values[idx]; idx++)
+ log_info (" length[%d]=%d\n",
+ idx, (int)values[0]->bv_len);
+
+ }
+
+ if (myopt->multi)
+ { /* Write attribute marker. */
+ unsigned char tmp[5];
+ size_t n = strlen (attr);
+
+ tmp[0] = 'A';
+ tmp[1] = (n >> 24);
+ tmp[2] = (n >> 16);
+ tmp[3] = (n >> 8);
+ tmp[4] = (n);
+ if (es_fwrite (tmp, 5, 1, myopt->outstream) != 1
+ || es_fwrite (attr, n, 1, myopt->outstream) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ ldap_value_free_len (values);
+ my_ldap_free_attr (attr);
+ ber_free (berctx, 0);
+ return -1;
+ }
+ }
+
+ for (idx=0; values[idx]; idx++)
+ {
+ if (myopt->multi)
+ { /* Write value marker. */
+ unsigned char tmp[5];
+ size_t n = values[0]->bv_len;
+
+ tmp[0] = 'V';
+ tmp[1] = (n >> 24);
+ tmp[2] = (n >> 16);
+ tmp[3] = (n >> 8);
+ tmp[4] = (n);
+
+ if (es_fwrite (tmp, 5, 1, myopt->outstream) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ ldap_value_free_len (values);
+ my_ldap_free_attr (attr);
+ ber_free (berctx, 0);
+ return -1;
+ }
+ }
+
+ if (es_fwrite (values[0]->bv_val, values[0]->bv_len,
+ 1, myopt->outstream) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ ldap_value_free_len (values);
+ my_ldap_free_attr (attr);
+ ber_free (berctx, 0);
+ return -1;
+ }
+
+ any = 1;
+ if (!myopt->multi)
+ break; /* Print only the first value. */
+ }
+ ldap_value_free_len (values);
+ my_ldap_free_attr (attr);
+ if (want_attr || !myopt->multi)
+ break; /* We only want to return the first attribute. */
+ }
+ ber_free (berctx, 0);
+ }
+
+ if (myopt->verbose > 1 && any)
+ log_info ("result has been printed\n");
+
+ return any?0:-1;
+}
+
+
+
+/* Helper for the URL based LDAP query. */
+static int
+fetch_ldap (my_opt_t myopt, const char *url, const LDAPURLDesc *ludp)
+{
+ LDAP *ld;
+ LDAPMessage *msg;
+ int rc = 0;
+ char *host, *dn, *filter, *attrs[2], *attr;
+ int port;
+ int ret;
+
+ host = myopt->host? myopt->host : ludp->lud_host;
+ port = myopt->port? myopt->port : ludp->lud_port;
+ dn = myopt->dn? myopt->dn : ludp->lud_dn;
+ filter = myopt->filter? myopt->filter : ludp->lud_filter;
+ attrs[0] = myopt->attr? myopt->attr : ludp->lud_attrs? ludp->lud_attrs[0]:NULL;
+ attrs[1] = NULL;
+ attr = attrs[0];
+
+ if (!port)
+ port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389;
+
+ if (myopt->verbose)
+ {
+ log_info (_("processing url '%s'\n"), url);
+ if (myopt->user)
+ log_info (_(" user '%s'\n"), myopt->user);
+ if (myopt->pass)
+ log_info (_(" pass '%s'\n"), *myopt->pass?"*****":"");
+ if (host)
+ log_info (_(" host '%s'\n"), host);
+ log_info (_(" port %d\n"), port);
+ if (dn)
+ log_info (_(" DN '%s'\n"), dn);
+ if (filter)
+ log_info (_(" filter '%s'\n"), filter);
+ if (myopt->multi && !myopt->attr && ludp->lud_attrs)
+ {
+ int i;
+ for (i=0; ludp->lud_attrs[i]; i++)
+ log_info (_(" attr '%s'\n"), ludp->lud_attrs[i]);
+ }
+ else if (attr)
+ log_info (_(" attr '%s'\n"), attr);
+ }
+
+
+ if (!host || !*host)
+ {
+ log_error (_("no host name in '%s'\n"), url);
+ return -1;
+ }
+ if (!myopt->multi && !attr)
+ {
+ log_error (_("no attribute given for query '%s'\n"), url);
+ return -1;
+ }
+
+ if (!myopt->multi && !myopt->attr
+ && ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1])
+ log_info (_("WARNING: using first attribute only\n"));
+
+
+ set_timeout (myopt);
+ npth_unprotect ();
+ ld = my_ldap_init (host, port);
+ npth_protect ();
+ if (!ld)
+ {
+ log_error (_("LDAP init to '%s:%d' failed: %s\n"),
+ host, port, strerror (errno));
+ return -1;
+ }
+ npth_unprotect ();
+ /* Fixme: Can we use MYOPT->user or is it shared with other theeads?. */
+ ret = my_ldap_simple_bind_s (ld, myopt->user, myopt->pass);
+ npth_protect ();
+ if (ret)
+ {
+ log_error (_("binding to '%s:%d' failed: %s\n"),
+ host, port, strerror (errno));
+ ldap_unbind (ld);
+ return -1;
+ }
+
+ set_timeout (myopt);
+ npth_unprotect ();
+ rc = my_ldap_search_st (ld, dn, ludp->lud_scope, filter,
+ myopt->multi && !myopt->attr && ludp->lud_attrs?
+ ludp->lud_attrs:attrs,
+ 0,
+ &myopt->timeout, &msg);
+ npth_protect ();
+ if (rc == LDAP_SIZELIMIT_EXCEEDED && myopt->multi)
+ {
+ if (es_fwrite ("E\0\0\0\x09truncated", 14, 1, myopt->outstream) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"), strerror (errno));
+ return -1;
+ }
+ }
+ else if (rc)
+ {
+#ifdef HAVE_W32CE_SYSTEM
+ log_error ("searching '%s' failed: %d\n", url, rc);
+#else
+ log_error (_("searching '%s' failed: %s\n"),
+ url, ldap_err2string (rc));
+#endif
+ if (rc != LDAP_NO_SUCH_OBJECT)
+ {
+ /* FIXME: Need deinit (ld)? */
+ /* Hmmm: Do we need to released MSG in case of an error? */
+ return -1;
+ }
+ }
+
+ rc = print_ldap_entries (myopt, ld, msg, myopt->multi? NULL:attr);
+
+ ldap_msgfree (msg);
+ ldap_unbind (ld);
+ return rc;
+}
+
+
+
+
+/* Main processing. Take the URL and run the LDAP query. The result
+ is printed to stdout, errors are logged to the log stream. */
+static int
+process_url (my_opt_t myopt, const char *url)
+{
+ int rc;
+ LDAPURLDesc *ludp = NULL;
+
+
+ if (!ldap_is_ldap_url (url))
+ {
+ log_error (_("'%s' is not an LDAP URL\n"), url);
+ return -1;
+ }
+
+ if (ldap_url_parse (url, &ludp))
+ {
+ log_error (_("'%s' is an invalid LDAP URL\n"), url);
+ return -1;
+ }
+
+ rc = fetch_ldap (myopt, url, ludp);
+
+ ldap_free_urldesc (ludp);
+ return rc;
+}
diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c
new file mode 100644
index 0000000..e4cd8f1
--- /dev/null
+++ b/dirmngr/ks-action.c
@@ -0,0 +1,332 @@
+/* ks-action.c - OpenPGP keyserver actions
+ * Copyright (C) 2011 Free Software Foundation, Inc.
+ * Copyright (C) 2011, 2014 Werner Koch
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "ks-engine.h"
+#include "ks-action.h"
+
+
+/* Copy all data from IN to OUT. */
+static gpg_error_t
+copy_stream (estream_t in, estream_t out)
+{
+ char buffer[512];
+ size_t nread;
+
+ while (!es_read (in, buffer, sizeof buffer, &nread))
+ {
+ if (!nread)
+ return 0; /* EOF */
+ if (es_write (out, buffer, nread, NULL))
+ break;
+
+ }
+ return gpg_error_from_syserror ();
+}
+
+
+/* Called by the engine's help functions to print the actual help. */
+gpg_error_t
+ks_print_help (ctrl_t ctrl, const char *text)
+{
+ return dirmngr_status_help (ctrl, text);
+}
+
+
+/* Called by the engine's help functions to print the actual help. */
+gpg_error_t
+ks_printf_help (ctrl_t ctrl, const char *format, ...)
+{
+ va_list arg_ptr;
+ gpg_error_t err;
+ char *buf;
+
+ va_start (arg_ptr, format);
+ buf = es_vbsprintf (format, arg_ptr);
+ err = buf? 0 : gpg_error_from_syserror ();
+ va_end (arg_ptr);
+ if (!err)
+ err = dirmngr_status_help (ctrl, buf);
+ es_free (buf);
+ return err;
+}
+
+
+/* Run the help command for the engine responsible for URI. */
+gpg_error_t
+ks_action_help (ctrl_t ctrl, const char *url)
+{
+ gpg_error_t err;
+ parsed_uri_t parsed_uri; /* The broken down URI. */
+
+ if (!url || !*url)
+ {
+ ks_print_help (ctrl, "Known schemata:\n");
+ parsed_uri = NULL;
+ }
+ else
+ {
+ err = http_parse_uri (&parsed_uri, url, 1);
+ if (err)
+ return err;
+ }
+
+ /* Call all engines to give them a chance to print a help sting. */
+ err = ks_hkp_help (ctrl, parsed_uri);
+ if (!err)
+ err = ks_http_help (ctrl, parsed_uri);
+ if (!err)
+ err = ks_finger_help (ctrl, parsed_uri);
+ if (!err)
+ err = ks_kdns_help (ctrl, parsed_uri);
+
+ if (!parsed_uri)
+ ks_print_help (ctrl,
+ "(Use an URL for engine specific help.)");
+ else
+ http_release_parsed_uri (parsed_uri);
+ return err;
+}
+
+
+/* Resolve all host names. This is useful for looking at the status
+ of configured keyservers. */
+gpg_error_t
+ks_action_resolve (ctrl_t ctrl)
+{
+ gpg_error_t err = 0;
+ int any_server = 0;
+ uri_item_t uri;
+
+ for (uri = ctrl->keyservers; !err && uri; uri = uri->next)
+ {
+ if (uri->parsed_uri->is_http)
+ {
+ any_server = 1;
+ err = ks_hkp_resolve (ctrl, uri->parsed_uri);
+ if (err)
+ break;
+ }
+ }
+
+ if (!any_server)
+ err = gpg_error (GPG_ERR_NO_KEYSERVER);
+ return err;
+}
+
+
+/* Search all configured keyservers for keys matching PATTERNS and
+ write the result to the provided output stream. */
+gpg_error_t
+ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
+{
+ gpg_error_t err = 0;
+ int any_server = 0;
+ uri_item_t uri;
+ estream_t infp;
+
+ if (!patterns)
+ return gpg_error (GPG_ERR_NO_USER_ID);
+
+ /* FIXME: We only take care of the first pattern. To fully support
+ multiple patterns we might either want to run several queries in
+ parallel and merge them. We also need to decide what to do with
+ errors - it might not be the best idea to ignore an error from
+ one server and silently continue with another server. For now we
+ stop at the first error. */
+ for (uri = ctrl->keyservers; !err && uri; uri = uri->next)
+ {
+ if (uri->parsed_uri->is_http)
+ {
+ any_server = 1;
+ err = ks_hkp_search (ctrl, uri->parsed_uri, patterns->d, &infp);
+ if (!err)
+ {
+ err = copy_stream (infp, outfp);
+ es_fclose (infp);
+ break;
+ }
+ }
+ }
+
+ if (!any_server)
+ err = gpg_error (GPG_ERR_NO_KEYSERVER);
+ return err;
+}
+
+
+/* Get the requested keys (matching PATTERNS) using all configured
+ keyservers and write the result to the provided output stream. */
+gpg_error_t
+ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp)
+{
+ gpg_error_t err = 0;
+ gpg_error_t first_err = 0;
+ int any_server = 0;
+ int any_data = 0;
+ strlist_t sl;
+ uri_item_t uri;
+ estream_t infp;
+
+ if (!patterns)
+ return gpg_error (GPG_ERR_NO_USER_ID);
+
+ /* FIXME: We only take care of the first keyserver. To fully
+ support multiple keyservers we need to track the result for each
+ pattern and use the next keyserver if one key was not found. The
+ keyservers might not all be fully synced thus it is not clear
+ whether the first keyserver has the freshest copy of the key.
+ Need to think about a better strategy. */
+ for (uri = ctrl->keyservers; !err && uri; uri = uri->next)
+ {
+ if (uri->parsed_uri->is_http)
+ {
+ any_server = 1;
+ for (sl = patterns; !err && sl; sl = sl->next)
+ {
+ err = ks_hkp_get (ctrl, uri->parsed_uri, sl->d, &infp);
+ if (err)
+ {
+ /* It is possible that a server does not carry a
+ key, thus we only save the error and continue
+ with the next pattern. FIXME: It is an open
+ question how to return such an error condition to
+ the caller. */
+ first_err = err;
+ err = 0;
+ }
+ else
+ {
+ err = copy_stream (infp, outfp);
+ /* Reading from the keyserver should never fail, thus
+ return this error. */
+ if (!err)
+ any_data = 1;
+ es_fclose (infp);
+ infp = NULL;
+ }
+ }
+ }
+ if (any_data)
+ break; /* Stop loop after a keyserver returned something. */
+ }
+
+ if (!any_server)
+ err = gpg_error (GPG_ERR_NO_KEYSERVER);
+ else if (!err && first_err && !any_data)
+ err = first_err;
+ return err;
+}
+
+
+/* Retrieve keys from URL and write the result to the provided output
+ stream OUTFP. */
+gpg_error_t
+ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp)
+{
+ gpg_error_t err = 0;
+ estream_t infp;
+ parsed_uri_t parsed_uri; /* The broken down URI. */
+
+ if (!url)
+ return gpg_error (GPG_ERR_INV_URI);
+
+ err = http_parse_uri (&parsed_uri, url, 1);
+ if (err)
+ return err;
+
+ if (parsed_uri->is_http)
+ {
+ err = ks_http_fetch (ctrl, url, &infp);
+ if (!err)
+ {
+ err = copy_stream (infp, outfp);
+ es_fclose (infp);
+ }
+ }
+ else if (!parsed_uri->opaque)
+ {
+ err = gpg_error (GPG_ERR_INV_URI);
+ }
+ else if (!strcmp (parsed_uri->scheme, "finger"))
+ {
+ err = ks_finger_fetch (ctrl, parsed_uri, &infp);
+ if (!err)
+ {
+ err = copy_stream (infp, outfp);
+ es_fclose (infp);
+ }
+ }
+ else if (!strcmp (parsed_uri->scheme, "kdns"))
+ {
+ err = ks_kdns_fetch (ctrl, parsed_uri, &infp);
+ if (!err)
+ {
+ err = copy_stream (infp, outfp);
+ es_fclose (infp);
+ }
+ }
+ else
+ err = gpg_error (GPG_ERR_INV_URI);
+
+ http_release_parsed_uri (parsed_uri);
+ return err;
+}
+
+
+
+/* Send an OpenPGP key to all keyservers. The key in {DATA,DATALEN}
+ is expected in OpenPGP binary transport format. */
+gpg_error_t
+ks_action_put (ctrl_t ctrl, const void *data, size_t datalen)
+{
+ gpg_error_t err = 0;
+ gpg_error_t first_err = 0;
+ int any_server = 0;
+ uri_item_t uri;
+
+ for (uri = ctrl->keyservers; !err && uri; uri = uri->next)
+ {
+ if (uri->parsed_uri->is_http)
+ {
+ any_server = 1;
+ err = ks_hkp_put (ctrl, uri->parsed_uri, data, datalen);
+ if (err)
+ {
+ first_err = err;
+ err = 0;
+ }
+ }
+ }
+
+ if (!any_server)
+ err = gpg_error (GPG_ERR_NO_KEYSERVER);
+ else if (!err && first_err)
+ err = first_err;
+ return err;
+}
diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h
new file mode 100644
index 0000000..5c8a5cd
--- /dev/null
+++ b/dirmngr/ks-action.h
@@ -0,0 +1,31 @@
+/* ks-action.h - OpenPGP keyserver actions definitions
+ * Copyright (C) 2011 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DIRMNGR_KS_ACTION_H
+#define DIRMNGR_KS_ACTION_H 1
+
+gpg_error_t ks_action_help (ctrl_t ctrl, const char *url);
+gpg_error_t ks_action_resolve (ctrl_t ctrl);
+gpg_error_t ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp);
+gpg_error_t ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp);
+gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp);
+gpg_error_t ks_action_put (ctrl_t ctrl, const void *data, size_t datalen);
+
+
+#endif /*DIRMNGR_KS_ACTION_H*/
diff --git a/dirmngr/ks-engine-finger.c b/dirmngr/ks-engine-finger.c
new file mode 100644
index 0000000..57dd340
--- /dev/null
+++ b/dirmngr/ks-engine-finger.c
@@ -0,0 +1,123 @@
+/* ks-engine-finger.c - Finger OpenPGP key access
+ * Copyright (C) 2011 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "userids.h"
+#include "ks-engine.h"
+
+/* Print a help output for the schemata supported by this module. */
+gpg_error_t
+ks_finger_help (ctrl_t ctrl, parsed_uri_t uri)
+{
+ char const data[] =
+ "Handler for FINGER:\n"
+ " finger:<user>@<host>\n"
+ "Supported methods: fetch\n"
+ "Example:\n"
+ " finger:joe@example.org\n";
+ gpg_error_t err;
+
+ if (!uri)
+ err = ks_print_help (ctrl, " finger");
+ else if (!strcmp (uri->scheme, "finger"))
+ err = ks_print_help (ctrl, data);
+ else
+ err = 0;
+
+ return err;
+}
+
+
+/* Get the key from URI which is expected to specify a finger scheme.
+ On success R_FP has an open stream to read the data. */
+gpg_error_t
+ks_finger_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp)
+{
+ gpg_error_t err;
+ estream_t fp;
+ char *server;
+ char *name;
+ http_t http;
+
+ (void)ctrl;
+ *r_fp = NULL;
+
+ if (strcmp (uri->scheme, "finger") || !uri->opaque || !uri->path)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ name = xtrystrdup (uri->path);
+ if (!name)
+ return gpg_error_from_syserror ();
+
+ server = strchr (name, '@');
+ if (!server)
+ {
+ err = gpg_error (GPG_ERR_INV_URI);
+ xfree (name);
+ return err;
+ }
+ *server++ = 0;
+
+ err = http_raw_connect (&http, server, 79, 0, NULL);
+ if (err)
+ {
+ xfree (name);
+ return err;
+ }
+
+ fp = http_get_write_ptr (http);
+ if (!fp)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL);
+ http_close (http, 0);
+ xfree (name);
+ return err;
+ }
+
+ if (es_fputs (name, fp) || es_fputs ("\r\n", fp) || es_fflush (fp))
+ {
+ err = gpg_error_from_syserror ();
+ http_close (http, 0);
+ xfree (name);
+ return err;
+ }
+ xfree (name);
+ es_fclose (fp);
+
+ fp = http_get_read_ptr (http);
+ if (!fp)
+ {
+ err = gpg_error (GPG_ERR_INTERNAL);
+ http_close (http, 0);
+ return err;
+ }
+
+ http_close (http, 1 /* Keep read ptr. */);
+
+ *r_fp = fp;
+ return 0;
+}
diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c
new file mode 100644
index 0000000..12b1778
--- /dev/null
+++ b/dirmngr/ks-engine-hkp.c
@@ -0,0 +1,1477 @@
+/* ks-engine-hkp.c - HKP keyserver engine
+ * Copyright (C) 2011, 2012 Free Software Foundation, Inc.
+ * Copyright (C) 2011, 2012, 2014 Werner Koch
+ *
+ * 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+# endif
+# include <windows.h>
+#else /*!HAVE_W32_SYSTEM*/
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <netdb.h>
+#endif /*!HAVE_W32_SYSTEM*/
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "userids.h"
+#include "ks-engine.h"
+
+/* Substitutes for missing Mingw macro. The EAI_SYSTEM mechanism
+ seems not to be available (probably because there is only one set
+ of error codes anyway). For now we use WSAEINVAL. */
+#ifndef EAI_OVERFLOW
+# define EAI_OVERFLOW EAI_FAIL
+#endif
+#ifdef HAVE_W32_SYSTEM
+# ifndef EAI_SYSTEM
+# define EAI_SYSTEM WSAEINVAL
+# endif
+#endif
+
+
+/* Number of seconds after a host is marked as resurrected. */
+#define RESURRECT_INTERVAL (3600*3) /* 3 hours */
+
+/* To match the behaviour of our old gpgkeys helper code we escape
+ more characters than actually needed. */
+#define EXTRA_ESCAPE_CHARS "@!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
+
+/* How many redirections do we allow. */
+#define MAX_REDIRECTS 2
+
+/* Number of retries done for a dead host etc. */
+#define SEND_REQUEST_RETRIES 3
+
+/* Objects used to maintain information about hosts. */
+struct hostinfo_s;
+typedef struct hostinfo_s *hostinfo_t;
+struct hostinfo_s
+{
+ time_t lastfail; /* Time we tried to connect and failed. */
+ time_t lastused; /* Time of last use. */
+ int *pool; /* A -1 terminated array with indices into
+ HOSTTABLE or NULL if NAME is not a pool
+ name. */
+ int poolidx; /* Index into POOL with the used host. -1 if not set. */
+ unsigned int v4:1; /* Host supports AF_INET. */
+ unsigned int v6:1; /* Host supports AF_INET6. */
+ unsigned int dead:1; /* Host is currently unresponsive. */
+ time_t died_at; /* The time the host was marked dead. If this is
+ 0 the host has been manually marked dead. */
+ char *cname; /* Canonical name of the host. Only set if this
+ is a pool. */
+ char *v4addr; /* A string with the v4 IP address of the host.
+ NULL if NAME has a numeric IP address or no v4
+ address is available. */
+ char *v6addr; /* A string with the v6 IP address of the host.
+ NULL if NAME has a numeric IP address or no v4
+ address is available. */
+ char name[1]; /* The hostname. */
+};
+
+
+/* An array of hostinfo_t for all hosts requested by the caller or
+ resolved from a pool name and its allocated size.*/
+static hostinfo_t *hosttable;
+static int hosttable_size;
+
+/* The number of host slots we initally allocate for HOSTTABLE. */
+#define INITIAL_HOSTTABLE_SIZE 10
+
+
+/* Create a new hostinfo object, fill in NAME and put it into
+ HOSTTABLE. Return the index into hosttable on success or -1 on
+ error. */
+static int
+create_new_hostinfo (const char *name)
+{
+ hostinfo_t hi, *newtable;
+ int newsize;
+ int idx, rc;
+
+ hi = xtrymalloc (sizeof *hi + strlen (name));
+ if (!hi)
+ return -1;
+ strcpy (hi->name, name);
+ hi->pool = NULL;
+ hi->poolidx = -1;
+ hi->lastused = (time_t)(-1);
+ hi->lastfail = (time_t)(-1);
+ hi->v4 = 0;
+ hi->v6 = 0;
+ hi->dead = 0;
+ hi->died_at = 0;
+ hi->cname = NULL;
+ hi->v4addr = NULL;
+ hi->v6addr = NULL;
+
+ /* Add it to the hosttable. */
+ for (idx=0; idx < hosttable_size; idx++)
+ if (!hosttable[idx])
+ {
+ hosttable[idx] = hi;
+ return idx;
+ }
+ /* Need to extend the hosttable. */
+ newsize = hosttable_size + INITIAL_HOSTTABLE_SIZE;
+ newtable = xtryrealloc (hosttable, newsize * sizeof *hosttable);
+ if (!newtable)
+ {
+ xfree (hi);
+ return -1;
+ }
+ hosttable = newtable;
+ idx = hosttable_size;
+ hosttable_size = newsize;
+ rc = idx;
+ hosttable[idx++] = hi;
+ while (idx < hosttable_size)
+ hosttable[idx++] = NULL;
+
+ return rc;
+}
+
+
+/* Find the host NAME in our table. Return the index into the
+ hosttable or -1 if not found. */
+static int
+find_hostinfo (const char *name)
+{
+ int idx;
+
+ for (idx=0; idx < hosttable_size; idx++)
+ if (hosttable[idx] && !ascii_strcasecmp (hosttable[idx]->name, name))
+ return idx;
+ return -1;
+}
+
+
+static int
+sort_hostpool (const void *xa, const void *xb)
+{
+ int a = *(int *)xa;
+ int b = *(int *)xb;
+
+ assert (a >= 0 && a < hosttable_size);
+ assert (b >= 0 && b < hosttable_size);
+ assert (hosttable[a]);
+ assert (hosttable[b]);
+
+ return ascii_strcasecmp (hosttable[a]->name, hosttable[b]->name);
+}
+
+
+/* Return true if the host with the hosttable index TBLIDX is in POOL. */
+static int
+host_in_pool_p (int *pool, int tblidx)
+{
+ int i, pidx;
+
+ for (i=0; (pidx = pool[i]) != -1; i++)
+ if (pidx == tblidx && hosttable[pidx])
+ return 1;
+ return 0;
+}
+
+
+/* Select a random host. Consult TABLE which indices into the global
+ hosttable. Returns index into TABLE or -1 if no host could be
+ selected. */
+static int
+select_random_host (int *table)
+{
+ int *tbl;
+ size_t tblsize;
+ int pidx, idx;
+
+ /* We create a new table so that we randomly select only from
+ currently alive hosts. */
+ for (idx=0, tblsize=0; (pidx = table[idx]) != -1; idx++)
+ if (hosttable[pidx] && !hosttable[pidx]->dead)
+ tblsize++;
+ if (!tblsize)
+ return -1; /* No hosts. */
+
+ tbl = xtrymalloc (tblsize * sizeof *tbl);
+ if (!tbl)
+ return -1;
+ for (idx=0, tblsize=0; (pidx = table[idx]) != -1; idx++)
+ if (hosttable[pidx] && !hosttable[pidx]->dead)
+ tbl[tblsize++] = pidx;
+
+ if (tblsize == 1) /* Save a get_uint_nonce. */
+ pidx = tbl[0];
+ else
+ pidx = tbl[get_uint_nonce () % tblsize];
+
+ xfree (tbl);
+ return pidx;
+}
+
+
+/* Simplified version of getnameinfo which also returns a numeric
+ hostname inside of brackets. The caller should provide a buffer
+ for HOST which is 2 bytes larger than the largest hostname. If
+ NUMERIC is true the returned value is numeric IP address. Returns
+ 0 on success or an EAI error code. True is stored at R_ISNUMERIC
+ if HOST has a numeric IP address. */
+static int
+my_getnameinfo (struct addrinfo *ai, char *host, size_t hostlen,
+ int numeric, int *r_isnumeric)
+{
+ int ec;
+ char *p;
+
+ *r_isnumeric = 0;
+
+ if (hostlen < 5)
+ return EAI_OVERFLOW;
+
+ if (numeric)
+ ec = EAI_NONAME;
+ else
+ ec = getnameinfo (ai->ai_addr, ai->ai_addrlen,
+ host, hostlen, NULL, 0, NI_NAMEREQD);
+
+ if (!ec && *host == '[')
+ ec = EAI_FAIL; /* A name may never start with a bracket. */
+ else if (ec == EAI_NONAME)
+ {
+ p = host;
+ if (ai->ai_family == AF_INET6)
+ {
+ *p++ = '[';
+ hostlen -= 2;
+ }
+ ec = getnameinfo (ai->ai_addr, ai->ai_addrlen,
+ p, hostlen, NULL, 0, NI_NUMERICHOST);
+ if (!ec && ai->ai_family == AF_INET6)
+ strcat (host, "]");
+
+ *r_isnumeric = 1;
+ }
+
+ return ec;
+}
+
+
+/* Check whether NAME is an IP address. */
+static int
+is_ip_address (const char *name)
+{
+ int ndots, n;
+
+ if (*name == '[')
+ return 1;
+ /* Check whether it is legacy IP address. */
+ if (*name == '.')
+ return 0; /* No. */
+ ndots = n = 0;
+ for (; *name; name++)
+ {
+ if (*name == '.')
+ {
+ if (name[1] == '.')
+ return 0; /* No. */
+ if (atoi (name+1) > 255)
+ return 0; /* Value too large. */
+ ndots++;
+ n = 0;
+ }
+ else if (!strchr ("012345678", *name))
+ return 0; /* Not a digit. */
+ else if (++n > 3)
+ return 0; /* More than 3 digits. */
+ }
+ return !!(ndots == 3);
+}
+
+
+/* Map the host name NAME to the actual to be used host name. This
+ allows us to manage round robin DNS names. We use our own strategy
+ to choose one of the hosts. For example we skip those hosts which
+ failed for some time and we stick to one host for a time
+ independent of DNS retry times. If FORCE_RESELECT is true a new
+ host is always selected. If R_HTTPFLAGS is not NULL if will
+ receive flags which are to be passed to http_open. If R_HOST is
+ not NULL a malloced name of the pool is stored or NULL if it is not
+ a pool. */
+static char *
+map_host (ctrl_t ctrl, const char *name, int force_reselect,
+ unsigned int *r_httpflags, char **r_host)
+{
+ hostinfo_t hi;
+ int idx;
+
+ if (r_httpflags)
+ *r_httpflags = 0;
+ if (r_host)
+ *r_host = NULL;
+
+ /* No hostname means localhost. */
+ if (!name || !*name)
+ return xtrystrdup ("localhost");
+
+ /* See whether the host is in our table. */
+ idx = find_hostinfo (name);
+ if (idx == -1)
+ {
+ /* We never saw this host. Allocate a new entry. */
+ struct addrinfo hints, *aibuf, *ai;
+ int *reftbl;
+ size_t reftblsize;
+ int refidx;
+ int is_pool = 0;
+
+ reftblsize = 100;
+ reftbl = xtrymalloc (reftblsize * sizeof *reftbl);
+ if (!reftbl)
+ return NULL;
+ refidx = 0;
+
+ idx = create_new_hostinfo (name);
+ if (idx == -1)
+ {
+ xfree (reftbl);
+ return NULL;
+ }
+ hi = hosttable[idx];
+
+ /* Find all A records for this entry and put them into the pool
+ list - if any. */
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_CANONNAME;
+ /* We can't use the the AI_IDN flag because that does the
+ conversion using the current locale. However, GnuPG always
+ used UTF-8. To support IDN we would need to make use of the
+ libidn API. */
+ if (!getaddrinfo (name, NULL, &hints, &aibuf))
+ {
+ int n_v6, n_v4;
+
+ /* First figure out whether this is a pool. For a pool we
+ use a different strategy than for a plains erver: We use
+ the canonical name of the pool as the virtual host along
+ with the IP addresses. If it is not a pool, we use the
+ specified name. */
+ n_v6 = n_v4 = 0;
+ for (ai = aibuf; ai; ai = ai->ai_next)
+ {
+ if (ai->ai_family != AF_INET6)
+ n_v6++;
+ else if (ai->ai_family != AF_INET)
+ n_v4++;
+ }
+ if (n_v6 > 1 || n_v4 > 1)
+ is_pool = 1;
+ if (is_pool && aibuf->ai_canonname)
+ hi->cname = xtrystrdup (aibuf->ai_canonname);
+
+ for (ai = aibuf; ai; ai = ai->ai_next)
+ {
+ char tmphost[NI_MAXHOST + 2];
+ int tmpidx;
+ int is_numeric;
+ int ec;
+ int i;
+
+ if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
+ continue;
+
+ dirmngr_tick (ctrl);
+
+ if (!is_pool && !is_ip_address (name))
+ {
+ /* This is a hostname but not a pool. Use the name
+ as given without going through getnameinfo. */
+ if (strlen (name)+1 > sizeof tmphost)
+ {
+ ec = EAI_SYSTEM;
+ gpg_err_set_errno (EINVAL);
+ }
+ else
+ {
+ ec = 0;
+ strcpy (tmphost, name);
+ }
+ is_numeric = 0;
+ }
+ else
+ ec = my_getnameinfo (ai, tmphost, sizeof tmphost,
+ 0, &is_numeric);
+
+ if (ec)
+ {
+ log_info ("getnameinfo failed while checking '%s': %s\n",
+ name, gai_strerror (ec));
+ }
+ else if (refidx+1 >= reftblsize)
+ {
+ log_error ("getnameinfo returned for '%s': '%s'"
+ " [index table full - ignored]\n", name, tmphost);
+ }
+ else
+ {
+ tmpidx = find_hostinfo (tmphost);
+ log_info ("getnameinfo returned for '%s': '%s'%s\n",
+ name, tmphost,
+ tmpidx == -1? "" : " [already known]");
+
+ if (tmpidx == -1) /* Create a new entry. */
+ tmpidx = create_new_hostinfo (tmphost);
+
+ if (tmpidx == -1)
+ {
+ log_error ("map_host for '%s' problem: %s - '%s'"
+ " [ignored]\n",
+ name, strerror (errno), tmphost);
+ }
+ else /* Set or update the entry. */
+ {
+ char *ipaddr = NULL;
+
+ if (!is_numeric)
+ {
+ ec = my_getnameinfo (ai, tmphost, sizeof tmphost,
+ 1, &is_numeric);
+ if (!ec && !(ipaddr = xtrystrdup (tmphost)))
+ ec = EAI_SYSTEM;
+ if (ec)
+ log_info ("getnameinfo failed: %s\n",
+ gai_strerror (ec));
+ }
+
+ if (ai->ai_family == AF_INET6)
+ {
+ hosttable[tmpidx]->v6 = 1;
+ xfree (hosttable[tmpidx]->v6addr);
+ hosttable[tmpidx]->v6addr = ipaddr;
+ }
+ else if (ai->ai_family == AF_INET)
+ {
+ hosttable[tmpidx]->v4 = 1;
+ xfree (hosttable[tmpidx]->v4addr);
+ hosttable[tmpidx]->v4addr = ipaddr;
+ }
+ else
+ BUG ();
+
+ for (i=0; i < refidx; i++)
+ if (reftbl[i] == tmpidx)
+ break;
+ if (!(i < refidx) && tmpidx != idx)
+ reftbl[refidx++] = tmpidx;
+ }
+ }
+ }
+ freeaddrinfo (aibuf);
+ }
+ reftbl[refidx] = -1;
+ if (refidx && is_pool)
+ {
+ assert (!hi->pool);
+ hi->pool = xtryrealloc (reftbl, (refidx+1) * sizeof *reftbl);
+ if (!hi->pool)
+ {
+ log_error ("shrinking index table in map_host failed: %s\n",
+ strerror (errno));
+ xfree (reftbl);
+ }
+ qsort (reftbl, refidx, sizeof *reftbl, sort_hostpool);
+ }
+ else
+ xfree (reftbl);
+ }
+
+ hi = hosttable[idx];
+ if (hi->pool)
+ {
+ /* If the currently selected host is now marked dead, force a
+ re-selection . */
+ if (force_reselect)
+ hi->poolidx = -1;
+ else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size
+ && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead)
+ hi->poolidx = -1;
+
+ /* Select a host if needed. */
+ if (hi->poolidx == -1)
+ {
+ hi->poolidx = select_random_host (hi->pool);
+ if (hi->poolidx == -1)
+ {
+ log_error ("no alive host found in pool '%s'\n", name);
+ return NULL;
+ }
+ }
+
+ assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size);
+ hi = hosttable[hi->poolidx];
+ assert (hi);
+ }
+
+ if (hi->dead)
+ {
+ log_error ("host '%s' marked as dead\n", hi->name);
+ return NULL;
+ }
+
+ if (r_httpflags)
+ {
+ /* If the hosttable does not indicate that a certain host
+ supports IPv<N>, we explicit set the corresponding http
+ flags. The reason for this is that a host might be listed in
+ a pool as not v6 only but actually support v6 when later
+ the name is resolved by our http layer. */
+ if (!hi->v4)
+ *r_httpflags |= HTTP_FLAG_IGNORE_IPv4;
+ if (!hi->v6)
+ *r_httpflags |= HTTP_FLAG_IGNORE_IPv6;
+ }
+
+ if (r_host && hi->pool && hi->cname)
+ *r_host = xtrystrdup (hi->cname);
+
+ return xtrystrdup (hi->name);
+}
+
+
+/* Mark the host NAME as dead. NAME may be given as an URL. Returns
+ true if a host was really marked as dead or was already marked dead
+ (e.g. by a concurrent session). */
+static int
+mark_host_dead (const char *name)
+{
+ const char *host;
+ char *host_buffer = NULL;
+ parsed_uri_t parsed_uri = NULL;
+ int done = 0;
+
+ if (name && *name && !http_parse_uri (&parsed_uri, name, 1))
+ {
+ if (parsed_uri->v6lit)
+ {
+ host_buffer = strconcat ("[", parsed_uri->host, "]", NULL);
+ if (!host_buffer)
+ log_error ("out of core in mark_host_dead");
+ host = host_buffer;
+ }
+ else
+ host = parsed_uri->host;
+ }
+ else
+ host = name;
+
+ if (host && *host && strcmp (host, "localhost"))
+ {
+ hostinfo_t hi;
+ int idx;
+
+ idx = find_hostinfo (host);
+ if (idx != -1)
+ {
+ hi = hosttable[idx];
+ log_info ("marking host '%s' as dead%s\n",
+ hi->name, hi->dead? " (again)":"");
+ hi->dead = 1;
+ hi->died_at = gnupg_get_time ();
+ if (!hi->died_at)
+ hi->died_at = 1;
+ done = 1;
+ }
+ }
+
+ http_release_parsed_uri (parsed_uri);
+ xfree (host_buffer);
+ return done;
+}
+
+
+/* Mark a host in the hosttable as dead or - if ALIVE is true - as
+ alive. */
+gpg_error_t
+ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive)
+{
+ gpg_error_t err = 0;
+ hostinfo_t hi, hi2;
+ int idx, idx2, idx3, n;
+
+ if (!name || !*name || !strcmp (name, "localhost"))
+ return 0;
+
+ idx = find_hostinfo (name);
+ if (idx == -1)
+ return gpg_error (GPG_ERR_NOT_FOUND);
+
+ hi = hosttable[idx];
+ if (alive && hi->dead)
+ {
+ hi->dead = 0;
+ err = ks_printf_help (ctrl, "marking '%s' as alive", name);
+ }
+ else if (!alive && !hi->dead)
+ {
+ hi->dead = 1;
+ hi->died_at = 0; /* Manually set dead. */
+ err = ks_printf_help (ctrl, "marking '%s' as dead", name);
+ }
+
+ /* If the host is a pool mark all member hosts. */
+ if (!err && hi->pool)
+ {
+ for (idx2=0; !err && (n=hi->pool[idx2]) != -1; idx2++)
+ {
+ assert (n >= 0 && n < hosttable_size);
+
+ if (!alive)
+ {
+ /* Do not mark a host from a pool dead if it is also a
+ member in another pool. */
+ for (idx3=0; idx3 < hosttable_size; idx3++)
+ {
+ if (hosttable[idx3] && hosttable[idx3]
+ && hosttable[idx3]->pool
+ && idx3 != idx
+ && host_in_pool_p (hosttable[idx3]->pool, n))
+ break;
+ }
+ if (idx3 < hosttable_size)
+ continue; /* Host is also a member of another pool. */
+ }
+
+ hi2 = hosttable[n];
+ if (!hi2)
+ ;
+ else if (alive && hi2->dead)
+ {
+ hi2->dead = 0;
+ err = ks_printf_help (ctrl, "marking '%s' as alive",
+ hi2->name);
+ }
+ else if (!alive && !hi2->dead)
+ {
+ hi2->dead = 1;
+ hi2->died_at = 0; /* Manually set dead. */
+ err = ks_printf_help (ctrl, "marking '%s' as dead",
+ hi2->name);
+ }
+ }
+ }
+
+ return err;
+}
+
+
+/* Debug function to print the entire hosttable. */
+gpg_error_t
+ks_hkp_print_hosttable (ctrl_t ctrl)
+{
+ gpg_error_t err;
+ int idx, idx2;
+ hostinfo_t hi;
+ membuf_t mb;
+ time_t curtime;
+ char *p, *died;
+ const char *diedstr;
+
+ err = ks_print_help (ctrl, "hosttable (idx, ipv6, ipv4, dead, name, time):");
+ if (err)
+ return err;
+
+ curtime = gnupg_get_time ();
+ for (idx=0; idx < hosttable_size; idx++)
+ if ((hi=hosttable[idx]))
+ {
+ if (hi->dead && hi->died_at)
+ {
+ died = elapsed_time_string (hi->died_at, curtime);
+ diedstr = died? died : "error";
+ }
+ else
+ diedstr = died = NULL;
+ err = ks_printf_help (ctrl, "%3d %s %s %s %s%s%s%s%s%s%s%s\n",
+ idx, hi->v6? "6":" ", hi->v4? "4":" ",
+ hi->dead? "d":" ",
+ hi->name,
+ hi->v6addr? " v6=":"",
+ hi->v6addr? hi->v6addr:"",
+ hi->v4addr? " v4=":"",
+ hi->v4addr? hi->v4addr:"",
+ diedstr? " (":"",
+ diedstr? diedstr:"",
+ diedstr? ")":"" );
+ xfree (died);
+ if (err)
+ return err;
+
+ if (hi->cname)
+ err = ks_printf_help (ctrl, " . %s", hi->cname);
+ if (err)
+ return err;
+
+ if (hi->pool)
+ {
+ init_membuf (&mb, 256);
+ put_membuf_printf (&mb, " . -->");
+ for (idx2=0; hi->pool[idx2] != -1; idx2++)
+ {
+ put_membuf_printf (&mb, " %d", hi->pool[idx2]);
+ if (hi->poolidx == hi->pool[idx2])
+ put_membuf_printf (&mb, "*");
+ }
+ put_membuf( &mb, "", 1);
+ p = get_membuf (&mb, NULL);
+ if (!p)
+ return gpg_error_from_syserror ();
+ err = ks_print_help (ctrl, p);
+ xfree (p);
+ if (err)
+ return err;
+ }
+ }
+ return 0;
+}
+
+
+
+/* Print a help output for the schemata supported by this module. */
+gpg_error_t
+ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri)
+{
+ const char const data[] =
+ "Handler for HKP URLs:\n"
+ " hkp://\n"
+ " hkps://\n"
+ "Supported methods: search, get, put\n";
+ gpg_error_t err;
+
+ if (!uri)
+ err = ks_print_help (ctrl, " hkp\n hkps");
+ else if (uri->is_http && (!strcmp (uri->scheme, "hkp")
+ || !strcmp (uri->scheme, "hkps")))
+ err = ks_print_help (ctrl, data);
+ else
+ err = 0;
+
+ return err;
+}
+
+
+/* Build the remote part of the URL from SCHEME, HOST and an optional
+ PORT. Returns an allocated string or NULL on failure and sets
+ ERRNO. If R_HTTPHOST is not NULL it receive a mallcoed string with
+ the poolname. */
+static char *
+make_host_part (ctrl_t ctrl,
+ const char *scheme, const char *host, unsigned short port,
+ int force_reselect,
+ unsigned int *r_httpflags, char **r_httphost)
+{
+ char portstr[10];
+ char *hostname;
+ char *hostport;
+
+ /* Map scheme and port. */
+ if (!strcmp (scheme, "hkps") || !strcmp (scheme,"https"))
+ {
+ scheme = "https";
+ strcpy (portstr, "443");
+ }
+ else /* HKP or HTTP. */
+ {
+ scheme = "http";
+ strcpy (portstr, "11371");
+ }
+ if (port)
+ snprintf (portstr, sizeof portstr, "%hu", port);
+ else
+ {
+ /*fixme_do_srv_lookup ()*/
+ }
+
+ hostname = map_host (ctrl, host, force_reselect, r_httpflags, r_httphost);
+ if (!hostname)
+ return NULL;
+
+ hostport = strconcat (scheme, "://", hostname, ":", portstr, NULL);
+ xfree (hostname);
+ return hostport;
+}
+
+
+/* Resolve all known keyserver names and update the hosttable. This
+ is mainly useful for debugging because the resolving is anyway done
+ on demand. */
+gpg_error_t
+ks_hkp_resolve (ctrl_t ctrl, parsed_uri_t uri)
+{
+ gpg_error_t err;
+ char *hostport = NULL;
+
+ hostport = make_host_part (ctrl, uri->scheme, uri->host, uri->port, 1,
+ NULL, NULL);
+ if (!hostport)
+ {
+ err = gpg_error_from_syserror ();
+ err = ks_printf_help (ctrl, "%s://%s:%hu: resolve failed: %s",
+ uri->scheme, uri->host, uri->port,
+ gpg_strerror (err));
+ }
+ else
+ {
+ err = ks_printf_help (ctrl, "%s", hostport);
+ xfree (hostport);
+ }
+ return err;
+}
+
+
+/* Housekeeping function called from the housekeeping thread. It is
+ used to mark dead hosts alive so that they may be tried again after
+ some time. */
+void
+ks_hkp_housekeeping (time_t curtime)
+{
+ int idx;
+ hostinfo_t hi;
+
+ for (idx=0; idx < hosttable_size; idx++)
+ {
+ hi = hosttable[idx];
+ if (!hi)
+ continue;
+ if (!hi->dead)
+ continue;
+ if (!hi->died_at)
+ continue; /* Do not resurrect manually shot hosts. */
+ if (hi->died_at + RESURRECT_INTERVAL <= curtime
+ || hi->died_at > curtime)
+ {
+ hi->dead = 0;
+ log_info ("resurrected host '%s'", hi->name);
+ }
+ }
+}
+
+
+/* Send an HTTP request. On success returns an estream object at
+ R_FP. HOSTPORTSTR is only used for diagnostics. If HTTPHOST is
+ not NULL it will be used as HTTP "Host" header. If POST_CB is not
+ NULL a post request is used and that callback is called to allow
+ writing the post data. */
+static gpg_error_t
+send_request (ctrl_t ctrl, const char *request, const char *hostportstr,
+ const char *httphost, unsigned int httpflags,
+ gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value,
+ estream_t *r_fp)
+{
+ gpg_error_t err;
+ http_session_t session = NULL;
+ http_t http = NULL;
+ int redirects_left = MAX_REDIRECTS;
+ estream_t fp = NULL;
+ char *request_buffer = NULL;
+
+ *r_fp = NULL;
+
+ err = http_session_new (&session, NULL);
+ if (err)
+ goto leave;
+ http_session_set_log_cb (session, cert_log_cb);
+
+ once_more:
+ err = http_open (&http,
+ post_cb? HTTP_REQ_POST : HTTP_REQ_GET,
+ request,
+ httphost,
+ /* fixme: AUTH */ NULL,
+ httpflags,
+ /* fixme: proxy*/ NULL,
+ session,
+ NULL,
+ /*FIXME curl->srvtag*/NULL);
+ if (!err)
+ {
+ fp = http_get_write_ptr (http);
+ /* Avoid caches to get the most recent copy of the key. We set
+ both the Pragma and Cache-Control versions of the header, so
+ we're good with both HTTP 1.0 and 1.1. */
+ es_fputs ("Pragma: no-cache\r\n"
+ "Cache-Control: no-cache\r\n", fp);
+ if (post_cb)
+ err = post_cb (post_cb_value, http);
+ if (!err)
+ {
+ http_start_data (http);
+ if (es_ferror (fp))
+ err = gpg_error_from_syserror ();
+ }
+ }
+ if (err)
+ {
+ /* Fixme: After a redirection we show the old host name. */
+ log_error (_("error connecting to '%s': %s\n"),
+ hostportstr, gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Wait for the response. */
+ dirmngr_tick (ctrl);
+ err = http_wait_response (http);
+ if (err)
+ {
+ log_error (_("error reading HTTP response for '%s': %s\n"),
+ hostportstr, gpg_strerror (err));
+ goto leave;
+ }
+
+ if (http_get_tls_info (http, NULL))
+ {
+ /* Update the httpflags so that a redirect won't fallback to an
+ unencrypted connection. */
+ httpflags |= HTTP_FLAG_FORCE_TLS;
+ }
+
+ switch (http_get_status_code (http))
+ {
+ case 200:
+ err = 0;
+ break; /* Success. */
+
+ case 301:
+ case 302:
+ case 307:
+ {
+ const char *s = http_get_header (http, "Location");
+
+ log_info (_("URL '%s' redirected to '%s' (%u)\n"),
+ request, s?s:"[none]", http_get_status_code (http));
+ if (s && *s && redirects_left-- )
+ {
+ xfree (request_buffer);
+ request_buffer = xtrystrdup (s);
+ if (request_buffer)
+ {
+ request = request_buffer;
+ http_close (http, 0);
+ http = NULL;
+ goto once_more;
+ }
+ err = gpg_error_from_syserror ();
+ }
+ else
+ err = gpg_error (GPG_ERR_NO_DATA);
+ log_error (_("too many redirections\n"));
+ }
+ goto leave;
+
+ default:
+ log_error (_("error accessing '%s': http status %u\n"),
+ request, http_get_status_code (http));
+ err = gpg_error (GPG_ERR_NO_DATA);
+ goto leave;
+ }
+
+ /* FIXME: We should register a permanent redirection and whether a
+ host has ever used TLS so that future calls will always use
+ TLS. */
+
+ fp = http_get_read_ptr (http);
+ if (!fp)
+ {
+ err = gpg_error (GPG_ERR_BUG);
+ goto leave;
+ }
+
+ /* Return the read stream and close the HTTP context. */
+ *r_fp = fp;
+ http_close (http, 1);
+ http = NULL;
+
+ leave:
+ http_close (http, 0);
+ http_session_release (session);
+ xfree (request_buffer);
+ return err;
+}
+
+
+/* Helper to evaluate the error code ERR form a send_request() call
+ with REQUEST. The function returns true if the caller shall try
+ again. TRIES_LEFT points to a variable to track the number of
+ retries; this function decrements it and won't return true if it is
+ down to zero. */
+static int
+handle_send_request_error (gpg_error_t err, const char *request,
+ unsigned int *tries_left)
+{
+ int retry = 0;
+
+ switch (gpg_err_code (err))
+ {
+ case GPG_ERR_ECONNREFUSED:
+ case GPG_ERR_ENETUNREACH:
+ if (mark_host_dead (request) && *tries_left)
+ retry = 1;
+ break;
+
+ case GPG_ERR_ETIMEDOUT:
+ if (*tries_left)
+ {
+ log_info ("selecting a different host due to a timeout\n");
+ retry = 1;
+ }
+
+ default:
+ break;
+ }
+
+ if (*tries_left)
+ --*tries_left;
+
+ return retry;
+}
+
+static gpg_error_t
+armor_data (char **r_string, const void *data, size_t datalen)
+{
+ gpg_error_t err;
+ struct b64state b64state;
+ estream_t fp;
+ long length;
+ char *buffer;
+ size_t nread;
+
+ *r_string = NULL;
+
+ fp = es_fopenmem (0, "rw,samethread");
+ if (!fp)
+ return gpg_error_from_syserror ();
+
+ if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK"))
+ || (err=b64enc_write (&b64state, data, datalen))
+ || (err = b64enc_finish (&b64state)))
+ {
+ es_fclose (fp);
+ return err;
+ }
+
+ /* FIXME: To avoid the extra buffer allocation estream should
+ provide a function to snatch the internal allocated memory from
+ such a memory stream. */
+ length = es_ftell (fp);
+ if (length < 0)
+ {
+ err = gpg_error_from_syserror ();
+ es_fclose (fp);
+ return err;
+ }
+
+ buffer = xtrymalloc (length+1);
+ if (!buffer)
+ {
+ err = gpg_error_from_syserror ();
+ es_fclose (fp);
+ return err;
+ }
+
+ es_rewind (fp);
+ if (es_read (fp, buffer, length, &nread))
+ {
+ err = gpg_error_from_syserror ();
+ es_fclose (fp);
+ return err;
+ }
+ buffer[nread] = 0;
+ es_fclose (fp);
+
+ *r_string = buffer;
+ return 0;
+}
+
+
+
+/* Search the keyserver identified by URI for keys matching PATTERN.
+ On success R_FP has an open stream to read the data. */
+gpg_error_t
+ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
+ estream_t *r_fp)
+{
+ gpg_error_t err;
+ KEYDB_SEARCH_DESC desc;
+ char fprbuf[2+40+1];
+ char *hostport = NULL;
+ char *request = NULL;
+ estream_t fp = NULL;
+ int reselect;
+ unsigned int httpflags;
+ char *httphost = NULL;
+ unsigned int tries = SEND_REQUEST_RETRIES;
+
+ *r_fp = NULL;
+
+ /* Remove search type indicator and adjust PATTERN accordingly.
+ Note that HKP keyservers like the 0x to be present when searching
+ by keyid. We need to re-format the fingerprint and keyids so to
+ remove the gpg specific force-use-of-this-key flag ("!"). */
+ err = classify_user_id (pattern, &desc, 1);
+ if (err)
+ return err;
+ switch (desc.mode)
+ {
+ case KEYDB_SEARCH_MODE_EXACT:
+ case KEYDB_SEARCH_MODE_SUBSTR:
+ case KEYDB_SEARCH_MODE_MAIL:
+ case KEYDB_SEARCH_MODE_MAILSUB:
+ pattern = desc.u.name;
+ break;
+ case KEYDB_SEARCH_MODE_SHORT_KID:
+ snprintf (fprbuf, sizeof fprbuf, "0x%08lX", (ulong)desc.u.kid[1]);
+ pattern = fprbuf;
+ break;
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ snprintf (fprbuf, sizeof fprbuf, "0x%08lX%08lX",
+ (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
+ pattern = fprbuf;
+ break;
+ case KEYDB_SEARCH_MODE_FPR16:
+ bin2hex (desc.u.fpr, 16, fprbuf);
+ pattern = fprbuf;
+ break;
+ case KEYDB_SEARCH_MODE_FPR20:
+ case KEYDB_SEARCH_MODE_FPR:
+ bin2hex (desc.u.fpr, 20, fprbuf);
+ pattern = fprbuf;
+ break;
+ default:
+ return gpg_error (GPG_ERR_INV_USER_ID);
+ }
+
+ /* Build the request string. */
+ reselect = 0;
+ again:
+ {
+ char *searchkey;
+
+ xfree (hostport);
+ xfree (httphost); httphost = NULL;
+ hostport = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
+ reselect, &httpflags, &httphost);
+ if (!hostport)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ searchkey = http_escape_string (pattern, EXTRA_ESCAPE_CHARS);
+ if (!searchkey)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ xfree (request);
+ request = strconcat (hostport,
+ "/pks/lookup?op=index&options=mr&search=",
+ searchkey,
+ NULL);
+ xfree (searchkey);
+ if (!request)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ }
+
+ /* Send the request. */
+ err = send_request (ctrl, request, hostport, httphost, httpflags,
+ NULL, NULL, &fp);
+ if (handle_send_request_error (err, request, &tries))
+ {
+ reselect = 1;
+ goto again;
+ }
+ if (err)
+ goto leave;
+
+ err = dirmngr_status (ctrl, "SOURCE", hostport, NULL);
+ if (err)
+ goto leave;
+
+ /* Peek at the response. */
+ {
+ int c = es_getc (fp);
+ if (c == -1)
+ {
+ err = es_ferror (fp)?gpg_error_from_syserror ():gpg_error (GPG_ERR_EOF);
+ log_error ("error reading response: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+ if (c == '<')
+ {
+ /* The document begins with a '<': Assume a HTML response,
+ which we don't support. */
+ err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
+ goto leave;
+ }
+ es_ungetc (c, fp);
+ }
+
+ /* Return the read stream. */
+ *r_fp = fp;
+ fp = NULL;
+
+ leave:
+ es_fclose (fp);
+ xfree (request);
+ xfree (hostport);
+ xfree (httphost);
+ return err;
+}
+
+
+/* Get the key described key the KEYSPEC string from the keyserver
+ identified by URI. On success R_FP has an open stream to read the
+ data. */
+gpg_error_t
+ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp)
+{
+ gpg_error_t err;
+ KEYDB_SEARCH_DESC desc;
+ char kidbuf[2+40+1];
+ const char *exactname = NULL;
+ char *searchkey = NULL;
+ char *hostport = NULL;
+ char *request = NULL;
+ estream_t fp = NULL;
+ int reselect;
+ char *httphost = NULL;
+ unsigned int httpflags;
+ unsigned int tries = SEND_REQUEST_RETRIES;
+
+ *r_fp = NULL;
+
+ /* Remove search type indicator and adjust PATTERN accordingly.
+ Note that HKP keyservers like the 0x to be present when searching
+ by keyid. We need to re-format the fingerprint and keyids so to
+ remove the gpg specific force-use-of-this-key flag ("!"). */
+ err = classify_user_id (keyspec, &desc, 1);
+ if (err)
+ return err;
+ switch (desc.mode)
+ {
+ case KEYDB_SEARCH_MODE_SHORT_KID:
+ snprintf (kidbuf, sizeof kidbuf, "0x%08lX", (ulong)desc.u.kid[1]);
+ break;
+ case KEYDB_SEARCH_MODE_LONG_KID:
+ snprintf (kidbuf, sizeof kidbuf, "0x%08lX%08lX",
+ (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]);
+ break;
+ case KEYDB_SEARCH_MODE_FPR20:
+ case KEYDB_SEARCH_MODE_FPR:
+ /* This is a v4 fingerprint. */
+ kidbuf[0] = '0';
+ kidbuf[1] = 'x';
+ bin2hex (desc.u.fpr, 20, kidbuf+2);
+ break;
+
+ case KEYDB_SEARCH_MODE_EXACT:
+ exactname = desc.u.name;
+ break;
+
+ case KEYDB_SEARCH_MODE_FPR16:
+ log_error ("HKP keyservers do not support v3 fingerprints\n");
+ default:
+ return gpg_error (GPG_ERR_INV_USER_ID);
+ }
+
+ searchkey = http_escape_string (exactname? exactname : kidbuf,
+ EXTRA_ESCAPE_CHARS);
+ if (!searchkey)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ reselect = 0;
+ again:
+ /* Build the request string. */
+ xfree (hostport);
+ xfree (httphost); httphost = NULL;
+ hostport = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
+ reselect, &httpflags, &httphost);
+ if (!hostport)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ xfree (request);
+ request = strconcat (hostport,
+ "/pks/lookup?op=get&options=mr&search=",
+ searchkey,
+ exactname? "&exact=on":"",
+ NULL);
+ if (!request)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* Send the request. */
+ err = send_request (ctrl, request, hostport, httphost, httpflags,
+ NULL, NULL, &fp);
+ if (handle_send_request_error (err, request, &tries))
+ {
+ reselect = 1;
+ goto again;
+ }
+ if (err)
+ goto leave;
+
+ err = dirmngr_status (ctrl, "SOURCE", hostport, NULL);
+ if (err)
+ goto leave;
+
+ /* Return the read stream and close the HTTP context. */
+ *r_fp = fp;
+ fp = NULL;
+
+ leave:
+ es_fclose (fp);
+ xfree (request);
+ xfree (hostport);
+ xfree (httphost);
+ xfree (searchkey);
+ return err;
+}
+
+
+
+
+/* Callback parameters for put_post_cb. */
+struct put_post_parm_s
+{
+ char *datastring;
+};
+
+
+/* Helper for ks_hkp_put. */
+static gpg_error_t
+put_post_cb (void *opaque, http_t http)
+{
+ struct put_post_parm_s *parm = opaque;
+ gpg_error_t err = 0;
+ estream_t fp;
+ size_t len;
+
+ fp = http_get_write_ptr (http);
+ len = strlen (parm->datastring);
+
+ es_fprintf (fp,
+ "Content-Type: application/x-www-form-urlencoded\r\n"
+ "Content-Length: %zu\r\n", len+8 /* 8 is for "keytext" */);
+ http_start_data (http);
+ if (es_fputs ("keytext=", fp) || es_write (fp, parm->datastring, len, NULL))
+ err = gpg_error_from_syserror ();
+ return err;
+}
+
+
+/* Send the key in {DATA,DATALEN} to the keyserver identified by URI. */
+gpg_error_t
+ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen)
+{
+ gpg_error_t err;
+ char *hostport = NULL;
+ char *request = NULL;
+ estream_t fp = NULL;
+ struct put_post_parm_s parm;
+ char *armored = NULL;
+ int reselect;
+ char *httphost = NULL;
+ unsigned int httpflags;
+ unsigned int tries = SEND_REQUEST_RETRIES;
+
+ parm.datastring = NULL;
+
+ err = armor_data (&armored, data, datalen);
+ if (err)
+ goto leave;
+
+ parm.datastring = http_escape_string (armored, EXTRA_ESCAPE_CHARS);
+ if (!parm.datastring)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ xfree (armored);
+ armored = NULL;
+
+ /* Build the request string. */
+ reselect = 0;
+ again:
+ xfree (hostport);
+ xfree (httphost); httphost = NULL;
+ hostport = make_host_part (ctrl, uri->scheme, uri->host, uri->port,
+ reselect, &httpflags, &httphost);
+ if (!hostport)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ xfree (request);
+ request = strconcat (hostport, "/pks/add", NULL);
+ if (!request)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* Send the request. */
+ err = send_request (ctrl, request, hostport, httphost, 0,
+ put_post_cb, &parm, &fp);
+ if (handle_send_request_error (err, request, &tries))
+ {
+ reselect = 1;
+ goto again;
+ }
+ if (err)
+ goto leave;
+
+ leave:
+ es_fclose (fp);
+ xfree (parm.datastring);
+ xfree (armored);
+ xfree (request);
+ xfree (hostport);
+ xfree (httphost);
+ return err;
+}
diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c
new file mode 100644
index 0000000..e4c2b78
--- /dev/null
+++ b/dirmngr/ks-engine-http.c
@@ -0,0 +1,172 @@
+/* ks-engine-http.c - HTTP OpenPGP key access
+ * Copyright (C) 2011 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "ks-engine.h"
+
+/* How many redirections do we allow. */
+#define MAX_REDIRECTS 2
+
+/* Print a help output for the schemata supported by this module. */
+gpg_error_t
+ks_http_help (ctrl_t ctrl, parsed_uri_t uri)
+{
+ const char const data[] =
+ "Handler for HTTP URLs:\n"
+ " http://\n"
+ " https://\n"
+ "Supported methods: fetch\n";
+ gpg_error_t err;
+
+ if (!uri)
+ err = ks_print_help (ctrl, " http");
+ else if (uri->is_http && strcmp (uri->scheme, "hkp"))
+ err = ks_print_help (ctrl, data);
+ else
+ err = 0;
+
+ return err;
+}
+
+
+/* Get the key from URL which is expected to specify a http style
+ scheme. On success R_FP has an open stream to read the data. */
+gpg_error_t
+ks_http_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp)
+{
+ gpg_error_t err;
+ http_session_t session = NULL;
+ http_t http = NULL;
+ int redirects_left = MAX_REDIRECTS;
+ estream_t fp = NULL;
+ char *request_buffer = NULL;
+
+ err = http_session_new (&session, NULL);
+ if (err)
+ goto leave;
+ http_session_set_log_cb (session, cert_log_cb);
+
+ *r_fp = NULL;
+ once_more:
+ err = http_open (&http,
+ HTTP_REQ_GET,
+ url,
+ /* httphost */ NULL,
+ /* fixme: AUTH */ NULL,
+ 0,
+ /* fixme: proxy*/ NULL,
+ session,
+ NULL,
+ /*FIXME curl->srvtag*/NULL);
+ if (!err)
+ {
+ fp = http_get_write_ptr (http);
+ /* Avoid caches to get the most recent copy of the key. We set
+ both the Pragma and Cache-Control versions of the header, so
+ we're good with both HTTP 1.0 and 1.1. */
+ es_fputs ("Pragma: no-cache\r\n"
+ "Cache-Control: no-cache\r\n", fp);
+ http_start_data (http);
+ if (es_ferror (fp))
+ err = gpg_error_from_syserror ();
+ }
+ if (err)
+ {
+ /* Fixme: After a redirection we show the old host name. */
+ log_error (_("error connecting to '%s': %s\n"),
+ url, gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Wait for the response. */
+ dirmngr_tick (ctrl);
+ err = http_wait_response (http);
+ if (err)
+ {
+ log_error (_("error reading HTTP response for '%s': %s\n"),
+ url, gpg_strerror (err));
+ goto leave;
+ }
+
+ switch (http_get_status_code (http))
+ {
+ case 200:
+ err = 0;
+ break; /* Success. */
+
+ case 301:
+ case 302:
+ case 307:
+ {
+ const char *s = http_get_header (http, "Location");
+
+ log_info (_("URL '%s' redirected to '%s' (%u)\n"),
+ url, s?s:"[none]", http_get_status_code (http));
+ if (s && *s && redirects_left-- )
+ {
+ xfree (request_buffer);
+ request_buffer = xtrystrdup (s);
+ if (request_buffer)
+ {
+ url = request_buffer;
+ http_close (http, 0);
+ http = NULL;
+ goto once_more;
+ }
+ err = gpg_error_from_syserror ();
+ }
+ else
+ err = gpg_error (GPG_ERR_NO_DATA);
+ log_error (_("too many redirections\n"));
+ }
+ goto leave;
+
+ default:
+ log_error (_("error accessing '%s': http status %u\n"),
+ url, http_get_status_code (http));
+ err = gpg_error (GPG_ERR_NO_DATA);
+ goto leave;
+ }
+
+ fp = http_get_read_ptr (http);
+ if (!fp)
+ {
+ err = gpg_error (GPG_ERR_BUG);
+ goto leave;
+ }
+
+ /* Return the read stream and close the HTTP context. */
+ *r_fp = fp;
+ http_close (http, 1);
+ http = NULL;
+
+ leave:
+ http_close (http, 0);
+ http_session_release (session);
+ xfree (request_buffer);
+ return err;
+}
diff --git a/dirmngr/ks-engine-kdns.c b/dirmngr/ks-engine-kdns.c
new file mode 100644
index 0000000..748274d
--- /dev/null
+++ b/dirmngr/ks-engine-kdns.c
@@ -0,0 +1,79 @@
+/* ks-engine-kdns.c - KDNS OpenPGP key access
+ * Copyright (C) 2011 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "userids.h"
+#include "ks-engine.h"
+
+/* Print a help output for the schemata supported by this module. */
+gpg_error_t
+ks_kdns_help (ctrl_t ctrl, parsed_uri_t uri)
+{
+ const char const data[] =
+ "This keyserver engine accepts URLs of the form:\n"
+ " kdns://[NAMESERVER]/[ROOT][?at=STRING]\n"
+ "with\n"
+ " NAMESERVER used for queries (default: system standard)\n"
+ " ROOT a DNS name appended to the query (default: none)\n"
+ " STRING a string to replace the '@' (default: \".\")\n"
+ "If a long answer is expected add the parameter \"usevc=1\".\n"
+ "Supported methods: fetch\n"
+ "Example:\n"
+ "A query for \"hacker@gnupg.org\" with\n"
+ " kdns://10.0.0.1/example.net?at=_key_&usevc=1\n"
+ "setup as --auto-key-lookup in gpg does a CERT record query\n"
+ "with type PGP on the nameserver 10.0.0.1 for\n"
+ " hacker._key_.gnupg.org.example.net";
+ gpg_error_t err;
+
+ if (!uri)
+ err = ks_print_help (ctrl, " kdns");
+ else if (!strcmp (uri->scheme, "kdns"))
+ err = ks_print_help (ctrl, data);
+ else
+ err = 0;
+
+ return err;
+}
+
+
+/* Get the key from URI which is expected to specify a kdns scheme.
+ On success R_FP has an open stream to read the data. */
+gpg_error_t
+ks_kdns_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp)
+{
+ gpg_error_t err;
+
+ (void)ctrl;
+ *r_fp = NULL;
+
+ if (strcmp (uri->scheme, "kdns"))
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ return err;
+}
diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h
new file mode 100644
index 0000000..dc950cf
--- /dev/null
+++ b/dirmngr/ks-engine.h
@@ -0,0 +1,57 @@
+/* ks-engine.h - Keyserver engines definitions
+ * Copyright (C) 2011 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DIRMNGR_KS_ENGINE_H
+#define DIRMNGR_KS_ENGINE_H 1
+
+#include "../common/http.h"
+
+/*-- ks-action.c --*/
+gpg_error_t ks_print_help (ctrl_t ctrl, const char *text);
+gpg_error_t ks_printf_help (ctrl_t ctrl, const char *format,
+ ...) JNLIB_GCC_A_PRINTF(2,3);
+
+/*-- ks-engine-hkp.c --*/
+gpg_error_t ks_hkp_resolve (ctrl_t ctrl, parsed_uri_t uri);
+gpg_error_t ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive);
+gpg_error_t ks_hkp_print_hosttable (ctrl_t ctrl);
+gpg_error_t ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri);
+gpg_error_t ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
+ estream_t *r_fp);
+gpg_error_t ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri,
+ const char *keyspec, estream_t *r_fp);
+gpg_error_t ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri,
+ const void *data, size_t datalen);
+
+/*-- ks-engine-http.c --*/
+gpg_error_t ks_http_help (ctrl_t ctrl, parsed_uri_t uri);
+gpg_error_t ks_http_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp);
+
+
+/*-- ks-engine-finger.c --*/
+gpg_error_t ks_finger_help (ctrl_t ctrl, parsed_uri_t uri);
+gpg_error_t ks_finger_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp);
+
+/*-- ks-engine-kdns.c --*/
+gpg_error_t ks_kdns_help (ctrl_t ctrl, parsed_uri_t uri);
+gpg_error_t ks_kdns_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp);
+
+
+
+#endif /*DIRMNGR_KS_ENGINE_H*/
diff --git a/dirmngr/ldap-url.c b/dirmngr/ldap-url.c
new file mode 100644
index 0000000..8308514
--- /dev/null
+++ b/dirmngr/ldap-url.c
@@ -0,0 +1,935 @@
+/* The following code comes from the OpenLDAP project. The references
+ to the COPYRIGHT file below refer to the corresponding file in the
+ OpenLDAP distribution, which is reproduced here in full:
+
+Copyright 1998-2004 The OpenLDAP Foundation
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted only as authorized by the OpenLDAP
+Public License.
+
+A copy of this license is available in the file LICENSE in the
+top-level directory of the distribution or, alternatively, at
+<http://www.OpenLDAP.org/license.html>.
+
+OpenLDAP is a registered trademark of the OpenLDAP Foundation.
+
+Individual files and/or contributed packages may be copyright by
+other parties and subject to additional restrictions.
+
+This work is derived from the University of Michigan LDAP v3.3
+distribution. Information concerning this software is available
+at <http://www.umich.edu/~dirsvcs/ldap/>.
+
+This work also contains materials derived from public sources.
+
+Additional information about OpenLDAP can be obtained at
+<http://www.openldap.org/>.
+
+---
+
+Portions Copyright 1998-2004 Kurt D. Zeilenga.
+Portions Copyright 1998-2004 Net Boolean Incorporated.
+Portions Copyright 2001-2004 IBM Corporation.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted only as authorized by the OpenLDAP
+Public License.
+
+---
+
+Portions Copyright 1999-2003 Howard Y.H. Chu.
+Portions Copyright 1999-2003 Symas Corporation.
+Portions Copyright 1998-2003 Hallvard B. Furuseth.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that this notice is preserved.
+The names of the copyright holders may not be used to endorse or
+promote products derived from this software without their specific
+prior written permission. This software is provided `'as is''
+without express or implied warranty.
+
+---
+
+Portions Copyright (c) 1992-1996 Regents of the University of Michigan.
+All rights reserved.
+
+Redistribution and use in source and binary forms are permitted
+provided that this notice is preserved and that due credit is given
+to the University of Michigan at Ann Arbor. The name of the
+University may not be used to endorse or promote products derived
+from this software without specific prior written permission. This
+software is provided `'as is'' without express or implied warranty. */
+
+
+#include <config.h>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <winsock2.h>
+#include <winldap.h>
+#include "ldap-url.h"
+#define LDAP_P(protos) protos
+#define LDAP_URL_URLCOLON "URL:"
+#define LDAP_URL_URLCOLON_LEN (sizeof(LDAP_URL_URLCOLON)-1)
+#define LDAP_URL_PREFIX "ldap://"
+#define LDAP_URL_PREFIX_LEN (sizeof(LDAP_URL_PREFIX)-1)
+#define LDAPS_URL_PREFIX "ldaps://"
+#define LDAPS_URL_PREFIX_LEN (sizeof(LDAPS_URL_PREFIX)-1)
+#define LDAPI_URL_PREFIX "ldapi://"
+#define LDAPI_URL_PREFIX_LEN (sizeof(LDAPI_URL_PREFIX)-1)
+#define LDAP_VFREE(v) { int _i; for (_i = 0; (v)[_i]; _i++) free((v)[_i]); }
+#define LDAP_FREE free
+#define LDAP_STRDUP strdup
+#define LDAP_CALLOC calloc
+#define LDAP_MALLOC malloc
+#define LDAP_REALLOC realloc
+#define ldap_utf8_strchr strchr
+#define ldap_utf8_strtok(n,d) strtok (n,d)
+#define Debug(a,b,c,d,e)
+void ldap_pvt_hex_unescape( char *s );
+
+
+#ifndef LDAP_SCOPE_DEFAULT
+# define LDAP_SCOPE_DEFAULT -1
+#endif
+
+
+
+/* $OpenLDAP: pkg/ldap/libraries/libldap/charray.c,v 1.9.2.2 2003/03/03 17:10:04 kurt Exp $ */
+/*
+ * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved.
+ * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+ */
+/* charray.c - routines for dealing with char * arrays */
+
+int
+ldap_charray_add(
+ char ***a,
+ char *s
+)
+{
+ int n;
+
+ if ( *a == NULL ) {
+ *a = (char **) LDAP_MALLOC( 2 * sizeof(char *) );
+ n = 0;
+
+ if( *a == NULL ) {
+ return -1;
+ }
+
+ } else {
+ char **new;
+
+ for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) {
+ ; /* NULL */
+ }
+
+ new = (char **) LDAP_REALLOC( (char *) *a,
+ (n + 2) * sizeof(char *) );
+
+ if( new == NULL ) {
+ /* caller is required to call ldap_charray_free(*a) */
+ return -1;
+ }
+
+ *a = new;
+ }
+
+ (*a)[n] = LDAP_STRDUP(s);
+
+ if( (*a)[n] == NULL ) {
+ return 1;
+ }
+
+ (*a)[++n] = NULL;
+
+ return 0;
+}
+
+int
+ldap_charray_merge(
+ char ***a,
+ char **s
+)
+{
+ int i, n, nn;
+ char **aa;
+
+ for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) {
+ ; /* NULL */
+ }
+ for ( nn = 0; s[nn] != NULL; nn++ ) {
+ ; /* NULL */
+ }
+
+ aa = (char **) LDAP_REALLOC( (char *) *a, (n + nn + 1) * sizeof(char *) );
+
+ if( aa == NULL ) {
+ return -1;
+ }
+
+ *a = aa;
+
+ for ( i = 0; i < nn; i++ ) {
+ (*a)[n + i] = LDAP_STRDUP(s[i]);
+
+ if( (*a)[n + i] == NULL ) {
+ for( --i ; i >= 0 ; i-- ) {
+ LDAP_FREE( (*a)[n + i] );
+ (*a)[n + i] = NULL;
+ }
+ return -1;
+ }
+ }
+
+ (*a)[n + nn] = NULL;
+ return 0;
+}
+
+void
+ldap_charray_free( char **a )
+{
+ char **p;
+
+ if ( a == NULL ) {
+ return;
+ }
+
+ for ( p = a; *p != NULL; p++ ) {
+ if ( *p != NULL ) {
+ LDAP_FREE( *p );
+ }
+ }
+
+ LDAP_FREE( (char *) a );
+}
+
+int
+ldap_charray_inlist(
+ char **a,
+ char *s
+)
+{
+ int i;
+
+ if( a == NULL ) return 0;
+
+ for ( i=0; a[i] != NULL; i++ ) {
+ if ( strcasecmp( s, a[i] ) == 0 ) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+char **
+ldap_charray_dup( char **a )
+{
+ int i;
+ char **new;
+
+ for ( i = 0; a[i] != NULL; i++ )
+ ; /* NULL */
+
+ new = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) );
+
+ if( new == NULL ) {
+ return NULL;
+ }
+
+ for ( i = 0; a[i] != NULL; i++ ) {
+ new[i] = LDAP_STRDUP( a[i] );
+
+ if( new[i] == NULL ) {
+ for( --i ; i >= 0 ; i-- ) {
+ LDAP_FREE( new[i] );
+ }
+ LDAP_FREE( new );
+ return NULL;
+ }
+ }
+ new[i] = NULL;
+
+ return( new );
+}
+
+char **
+ldap_str2charray( const char *str_in, const char *brkstr )
+{
+ char **res;
+ char *str, *s;
+ int i;
+
+ /* protect the input string from strtok */
+ str = LDAP_STRDUP( str_in );
+ if( str == NULL ) {
+ return NULL;
+ }
+
+ i = 1;
+ for ( s = str; *s; s++ ) {
+ if ( ldap_utf8_strchr( brkstr, *s ) != NULL ) {
+ i++;
+ }
+ }
+
+ res = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) );
+
+ if( res == NULL ) {
+ LDAP_FREE( str );
+ return NULL;
+ }
+
+ i = 0;
+
+ for ( s = ldap_utf8_strtok( str, brkstr);
+ s != NULL;
+ s = ldap_utf8_strtok( NULL, brkstr) )
+ {
+ res[i] = LDAP_STRDUP( s );
+
+ if(res[i] == NULL) {
+ for( --i ; i >= 0 ; i-- ) {
+ LDAP_FREE( res[i] );
+ }
+ LDAP_FREE( res );
+ LDAP_FREE( str );
+ return NULL;
+ }
+
+ i++;
+ }
+
+ res[i] = NULL;
+
+ LDAP_FREE( str );
+ return( res );
+}
+
+char * ldap_charray2str( char **a, const char *sep )
+{
+ char *s, **v, *p;
+ int len;
+ int slen;
+
+ if( sep == NULL ) sep = " ";
+
+ slen = strlen( sep );
+ len = 0;
+
+ for ( v = a; *v != NULL; v++ ) {
+ len += strlen( *v ) + slen;
+ }
+
+ if ( len == 0 ) {
+ return NULL;
+ }
+
+ /* trim extra sep len */
+ len -= slen;
+
+ s = LDAP_MALLOC ( len + 1 );
+
+ if ( s == NULL ) {
+ return NULL;
+ }
+
+ p = s;
+ for ( v = a; *v != NULL; v++ ) {
+ if ( v != a ) {
+ strncpy( p, sep, slen );
+ p += slen;
+ }
+
+ len = strlen( *v );
+ strncpy( p, *v, len );
+ p += len;
+ }
+
+ *p = '\0';
+ return s;
+}
+
+
+
+/* $OpenLDAP: pkg/ldap/libraries/libldap/url.c,v 1.64.2.5 2003/03/03 17:10:05 kurt Exp $ */
+/*
+ * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved.
+ * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
+ */
+/* Portions
+ * Copyright (c) 1996 Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * LIBLDAP url.c -- LDAP URL (RFC 2255) related routines
+ *
+ * LDAP URLs look like this:
+ * ldap[is]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
+ *
+ * where:
+ * attributes is a comma separated list
+ * scope is one of these three strings: base one sub (default=base)
+ * filter is an string-represented filter as in RFC 2254
+ *
+ * e.g., ldap://host:port/dc=com?o,cn?base?(o=openldap)?extension
+ *
+ * We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl>
+ */
+
+/* local functions */
+static const char* skip_url_prefix LDAP_P((
+ const char *url,
+ int *enclosedp,
+ const char **scheme ));
+
+int
+ldap_is_ldap_url( LDAP_CONST char *url )
+{
+ int enclosed;
+ const char * scheme;
+
+ if( url == NULL ) {
+ return 0;
+ }
+
+ if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
+ return 0;
+ }
+
+ return 1;
+}
+
+
+static const char*
+skip_url_prefix(
+ const char *url,
+ int *enclosedp,
+ const char **scheme )
+{
+ /*
+ * return non-zero if this looks like a LDAP URL; zero if not
+ * if non-zero returned, *urlp will be moved past "ldap://" part of URL
+ */
+ const char *p;
+
+ if ( url == NULL ) {
+ return( NULL );
+ }
+
+ p = url;
+
+ /* skip leading '<' (if any) */
+ if ( *p == '<' ) {
+ *enclosedp = 1;
+ ++p;
+ } else {
+ *enclosedp = 0;
+ }
+
+ /* skip leading "URL:" (if any) */
+ if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) {
+ p += LDAP_URL_URLCOLON_LEN;
+ }
+
+ /* check for "ldap://" prefix */
+ if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) {
+ /* skip over "ldap://" prefix and return success */
+ p += LDAP_URL_PREFIX_LEN;
+ *scheme = "ldap";
+ return( p );
+ }
+
+ /* check for "ldaps://" prefix */
+ if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
+ /* skip over "ldaps://" prefix and return success */
+ p += LDAPS_URL_PREFIX_LEN;
+ *scheme = "ldaps";
+ return( p );
+ }
+
+ /* check for "ldapi://" prefix */
+ if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) {
+ /* skip over "ldapi://" prefix and return success */
+ p += LDAPI_URL_PREFIX_LEN;
+ *scheme = "ldapi";
+ return( p );
+ }
+
+#ifdef LDAP_CONNECTIONLESS
+ /* check for "cldap://" prefix */
+ if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) {
+ /* skip over "cldap://" prefix and return success */
+ p += LDAPC_URL_PREFIX_LEN;
+ *scheme = "cldap";
+ return( p );
+ }
+#endif
+
+ return( NULL );
+}
+
+
+static int str2scope( const char *p )
+{
+ if ( strcasecmp( p, "one" ) == 0 ) {
+ return LDAP_SCOPE_ONELEVEL;
+
+ } else if ( strcasecmp( p, "onetree" ) == 0 ) {
+ return LDAP_SCOPE_ONELEVEL;
+
+ } else if ( strcasecmp( p, "base" ) == 0 ) {
+ return LDAP_SCOPE_BASE;
+
+ } else if ( strcasecmp( p, "sub" ) == 0 ) {
+ return LDAP_SCOPE_SUBTREE;
+
+ } else if ( strcasecmp( p, "subtree" ) == 0 ) {
+ return LDAP_SCOPE_SUBTREE;
+ }
+
+ return( -1 );
+}
+
+
+int
+ldap_url_parse_ext( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
+{
+/*
+ * Pick apart the pieces of an LDAP URL.
+ */
+
+ LDAPURLDesc *ludp;
+ char *p, *q, *r;
+ int i, enclosed;
+ const char *scheme = NULL;
+ const char *url_tmp;
+ char *url;
+
+ if( url_in == NULL || ludpp == NULL ) {
+ return LDAP_URL_ERR_PARAM;
+ }
+
+#ifndef LDAP_INT_IN_KERNEL
+ /* Global options may not be created yet
+ * We can't test if the global options are initialized
+ * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate
+ * the options and cause infinite recursion
+ */
+#ifdef NEW_LOGGING
+ LDAP_LOG ( OPERATION, ENTRY, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 );
+#else
+ Debug( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 );
+#endif
+#endif
+
+ *ludpp = NULL; /* pessimistic */
+
+ url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
+
+ if ( url_tmp == NULL ) {
+ return LDAP_URL_ERR_BADSCHEME;
+ }
+
+ assert( scheme );
+
+ /* make working copy of the remainder of the URL */
+ url = LDAP_STRDUP( url_tmp );
+ if ( url == NULL ) {
+ return LDAP_URL_ERR_MEM;
+ }
+
+ if ( enclosed ) {
+ p = &url[strlen(url)-1];
+
+ if( *p != '>' ) {
+ LDAP_FREE( url );
+ return LDAP_URL_ERR_BADENCLOSURE;
+ }
+
+ *p = '\0';
+ }
+
+ /* allocate return struct */
+ ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc ));
+
+ if ( ludp == NULL ) {
+ LDAP_FREE( url );
+ return LDAP_URL_ERR_MEM;
+ }
+
+ ludp->lud_next = NULL;
+ ludp->lud_host = NULL;
+ ludp->lud_port = 0;
+ ludp->lud_dn = NULL;
+ ludp->lud_attrs = NULL;
+ ludp->lud_filter = NULL;
+ ludp->lud_scope = LDAP_SCOPE_DEFAULT;
+ ludp->lud_filter = NULL;
+ ludp->lud_exts = NULL;
+
+ ludp->lud_scheme = LDAP_STRDUP( scheme );
+
+ if ( ludp->lud_scheme == NULL ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_MEM;
+ }
+
+ /* scan forward for '/' that marks end of hostport and begin. of dn */
+ p = strchr( url, '/' );
+
+ if( p != NULL ) {
+ /* terminate hostport; point to start of dn */
+ *p++ = '\0';
+ }
+
+ /* IPv6 syntax with [ip address]:port */
+ if ( *url == '[' ) {
+ r = strchr( url, ']' );
+ if ( r == NULL ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_BADURL;
+ }
+ *r++ = '\0';
+ q = strchr( r, ':' );
+ } else {
+ q = strchr( url, ':' );
+ }
+
+ if ( q != NULL ) {
+ *q++ = '\0';
+ ldap_pvt_hex_unescape( q );
+
+ if( *q == '\0' ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_BADURL;
+ }
+
+ ludp->lud_port = atoi( q );
+ }
+
+ ldap_pvt_hex_unescape( url );
+
+ /* If [ip address]:port syntax, url is [ip and we skip the [ */
+ ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) );
+
+ if( ludp->lud_host == NULL ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_MEM;
+ }
+
+ /*
+ * Kludge. ldap://111.222.333.444:389??cn=abc,o=company
+ *
+ * On early Novell releases, search references/referrals were returned
+ * in this format, i.e., the dn was kind of in the scope position,
+ * but the required slash is missing. The whole thing is illegal syntax,
+ * but we need to account for it. Fortunately it can't be confused with
+ * anything real.
+ */
+ if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) {
+ q++;
+ /* ? immediately followed by question */
+ if( *q == '?') {
+ q++;
+ if( *q != '\0' ) {
+ /* parse dn part */
+ ldap_pvt_hex_unescape( q );
+ ludp->lud_dn = LDAP_STRDUP( q );
+ } else {
+ ludp->lud_dn = LDAP_STRDUP( "" );
+ }
+
+ if( ludp->lud_dn == NULL ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_MEM;
+ }
+ }
+ }
+
+ if( p == NULL ) {
+ LDAP_FREE( url );
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of dn */
+ q = strchr( p, '?' );
+
+ if( q != NULL ) {
+ /* terminate dn part */
+ *q++ = '\0';
+ }
+
+ if( *p != '\0' ) {
+ /* parse dn part */
+ ldap_pvt_hex_unescape( p );
+ ludp->lud_dn = LDAP_STRDUP( p );
+ } else {
+ ludp->lud_dn = LDAP_STRDUP( "" );
+ }
+
+ if( ludp->lud_dn == NULL ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_MEM;
+ }
+
+ if( q == NULL ) {
+ /* no more */
+ LDAP_FREE( url );
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of attributes */
+ p = q;
+ q = strchr( p, '?' );
+
+ if( q != NULL ) {
+ /* terminate attributes part */
+ *q++ = '\0';
+ }
+
+ if( *p != '\0' ) {
+ /* parse attributes */
+ ldap_pvt_hex_unescape( p );
+ ludp->lud_attrs = ldap_str2charray( p, "," );
+
+ if( ludp->lud_attrs == NULL ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_BADATTRS;
+ }
+ }
+
+ if ( q == NULL ) {
+ /* no more */
+ LDAP_FREE( url );
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of scope */
+ p = q;
+ q = strchr( p, '?' );
+
+ if( q != NULL ) {
+ /* terminate the scope part */
+ *q++ = '\0';
+ }
+
+ if( *p != '\0' ) {
+ /* parse the scope */
+ ldap_pvt_hex_unescape( p );
+ ludp->lud_scope = str2scope( p );
+
+ if( ludp->lud_scope == -1 ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_BADSCOPE;
+ }
+ }
+
+ if ( q == NULL ) {
+ /* no more */
+ LDAP_FREE( url );
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of filter */
+ p = q;
+ q = strchr( p, '?' );
+
+ if( q != NULL ) {
+ /* terminate the filter part */
+ *q++ = '\0';
+ }
+
+ if( *p != '\0' ) {
+ /* parse the filter */
+ ldap_pvt_hex_unescape( p );
+
+ if( ! *p ) {
+ /* missing filter */
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_BADFILTER;
+ }
+
+ LDAP_FREE( ludp->lud_filter );
+ ludp->lud_filter = LDAP_STRDUP( p );
+
+ if( ludp->lud_filter == NULL ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_MEM;
+ }
+ }
+
+ if ( q == NULL ) {
+ /* no more */
+ LDAP_FREE( url );
+ *ludpp = ludp;
+ return LDAP_URL_SUCCESS;
+ }
+
+ /* scan forward for '?' that may marks end of extensions */
+ p = q;
+ q = strchr( p, '?' );
+
+ if( q != NULL ) {
+ /* extra '?' */
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_BADURL;
+ }
+
+ /* parse the extensions */
+ ludp->lud_exts = ldap_str2charray( p, "," );
+
+ if( ludp->lud_exts == NULL ) {
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_BADEXTS;
+ }
+
+ for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
+ ldap_pvt_hex_unescape( ludp->lud_exts[i] );
+
+ if( *ludp->lud_exts[i] == '!' ) {
+ /* count the number of critical extensions */
+ ludp->lud_crit_exts++;
+ }
+ }
+
+ if( i == 0 ) {
+ /* must have 1 or more */
+ LDAP_FREE( url );
+ ldap_free_urldesc( ludp );
+ return LDAP_URL_ERR_BADEXTS;
+ }
+
+ /* no more */
+ *ludpp = ludp;
+ LDAP_FREE( url );
+ return LDAP_URL_SUCCESS;
+}
+
+int
+ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
+{
+ int rc = ldap_url_parse_ext( url_in, ludpp );
+
+ if( rc != LDAP_URL_SUCCESS ) {
+ return rc;
+ }
+
+ if ((*ludpp)->lud_scope == LDAP_SCOPE_DEFAULT) {
+ (*ludpp)->lud_scope = LDAP_SCOPE_BASE;
+ }
+
+ if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') {
+ LDAP_FREE( (*ludpp)->lud_host );
+ (*ludpp)->lud_host = NULL;
+ }
+
+ if ((*ludpp)->lud_port == 0) {
+ if( strcmp((*ludpp)->lud_scheme, "ldap") == 0 ) {
+ (*ludpp)->lud_port = LDAP_PORT;
+#ifdef LDAP_CONNECTIONLESS
+ } else if( strcmp((*ludpp)->lud_scheme, "cldap") == 0 ) {
+ (*ludpp)->lud_port = LDAP_PORT;
+#endif
+ } else if( strcmp((*ludpp)->lud_scheme, "ldaps") == 0 ) {
+ (*ludpp)->lud_port = LDAPS_PORT;
+ }
+ }
+
+ return rc;
+}
+
+
+void
+ldap_free_urldesc( LDAPURLDesc *ludp )
+{
+ if ( ludp == NULL ) {
+ return;
+ }
+
+ if ( ludp->lud_scheme != NULL ) {
+ LDAP_FREE( ludp->lud_scheme );
+ }
+
+ if ( ludp->lud_host != NULL ) {
+ LDAP_FREE( ludp->lud_host );
+ }
+
+ if ( ludp->lud_dn != NULL ) {
+ LDAP_FREE( ludp->lud_dn );
+ }
+
+ if ( ludp->lud_filter != NULL ) {
+ LDAP_FREE( ludp->lud_filter);
+ }
+
+ if ( ludp->lud_attrs != NULL ) {
+ LDAP_VFREE( ludp->lud_attrs );
+ }
+
+ if ( ludp->lud_exts != NULL ) {
+ LDAP_VFREE( ludp->lud_exts );
+ }
+
+ LDAP_FREE( ludp );
+}
+
+
+static int
+ldap_int_unhex( int c )
+{
+ return( c >= '0' && c <= '9' ? c - '0'
+ : c >= 'A' && c <= 'F' ? c - 'A' + 10
+ : c - 'a' + 10 );
+}
+
+void
+ldap_pvt_hex_unescape( char *s )
+{
+ /*
+ * Remove URL hex escapes from s... done in place. The basic concept for
+ * this routine is borrowed from the WWW library HTUnEscape() routine.
+ */
+ char *p;
+
+ for ( p = s; *s != '\0'; ++s ) {
+ if ( *s == '%' ) {
+ if ( *++s == '\0' ) {
+ break;
+ }
+ *p = ldap_int_unhex( *s ) << 4;
+ if ( *++s == '\0' ) {
+ break;
+ }
+ *p++ += ldap_int_unhex( *s );
+ } else {
+ *p++ = *s;
+ }
+ }
+
+ *p = '\0';
+}
diff --git a/dirmngr/ldap-url.h b/dirmngr/ldap-url.h
new file mode 100644
index 0000000..f3104d8
--- /dev/null
+++ b/dirmngr/ldap-url.h
@@ -0,0 +1,50 @@
+/* Copyright 2007 g10 Code GmbH
+
+ This file is free software; as a special exception the author gives
+ unlimited permission to copy and/or distribute it, with or without
+ modifications, as long as this notice is preserved.
+
+ This file is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY, to the extent permitted by law; without even
+ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ PURPOSE. */
+
+#ifndef LDAP_URL_H
+#define LDAP_URL_H 1
+
+#define LDAP_CONST const
+
+typedef struct ldap_url_desc
+{
+ struct ldap_url_desc *lud_next;
+ char *lud_scheme;
+ char *lud_host;
+ int lud_port;
+ char *lud_dn;
+ char **lud_attrs;
+ int lud_scope;
+ char *lud_filter;
+ char **lud_exts;
+ int lud_crit_exts;
+} LDAPURLDesc;
+
+#define LDAP_URL_SUCCESS 0x00
+#define LDAP_URL_ERR_MEM 0x01
+#define LDAP_URL_ERR_PARAM 0x02
+
+#define LDAP_URL_ERR_BADSCHEME 0x03
+#define LDAP_URL_ERR_BADENCLOSURE 0x04
+#define LDAP_URL_ERR_BADURL 0x05
+#define LDAP_URL_ERR_BADHOST 0x06
+#define LDAP_URL_ERR_BADATTRS 0x07
+#define LDAP_URL_ERR_BADSCOPE 0x08
+#define LDAP_URL_ERR_BADFILTER 0x09
+#define LDAP_URL_ERR_BADEXTS 0x0a
+
+#define LDAPS_PORT 636
+
+int ldap_is_ldap_url (LDAP_CONST char *url);
+int ldap_url_parse (LDAP_CONST char *url_in, LDAPURLDesc **ludpp);
+void ldap_free_urldesc (LDAPURLDesc *ludp);
+
+#endif /* !LDAP_URL_H */
diff --git a/dirmngr/ldap-wrapper-ce.c b/dirmngr/ldap-wrapper-ce.c
new file mode 100644
index 0000000..ce63ea6
--- /dev/null
+++ b/dirmngr/ldap-wrapper-ce.c
@@ -0,0 +1,571 @@
+/* ldap-wrapper-ce.c - LDAP access via W32 threads
+ * Copyright (C) 2010 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ Alternative wrapper for use with WindowsCE. Under WindowsCE the
+ number of processes is strongly limited (32 processes including the
+ kernel processes) and thus we don't use the process approach but
+ implement a wrapper based on native threads.
+
+ See ldap-wrapper.c for the standard wrapper interface.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <npth.h>
+#include <assert.h>
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "ldap-wrapper.h"
+
+#ifdef USE_LDAPWRAPPER
+# error This module is not expected to be build.
+#endif
+
+
+
+/* Read a fixed amount of data from READER into BUFFER. */
+static gpg_error_t
+read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
+{
+ gpg_error_t err;
+ size_t nread;
+
+ while (count)
+ {
+ err = ksba_reader_read (reader, buffer, count, &nread);
+ if (err)
+ return err;
+ buffer += nread;
+ count -= nread;
+ }
+ return 0;
+}
+
+
+
+
+/* Start the reaper thread for this wrapper. */
+void
+ldap_wrapper_launch_thread (void)
+{
+ /* Not required. */
+}
+
+
+
+
+
+/* Wait until all ldap wrappers have terminated. We assume that the
+ kill has already been sent to all of them. */
+void
+ldap_wrapper_wait_connections ()
+{
+ /* Not required. */
+}
+
+
+/* Cleanup all resources held by the connection associated with
+ CTRL. This is used after a cancel to kill running wrappers. */
+void
+ldap_wrapper_connection_cleanup (ctrl_t ctrl)
+{
+ (void)ctrl;
+
+ /* Not required. */
+}
+
+
+
+/* The cookie we use to implement the outstream of the wrapper thread. */
+struct outstream_cookie_s
+{
+ int refcount; /* Reference counter - possible values are 1 and 2. */
+
+ /* We don't need a mutex for the conditions, as npth provides a
+ simpler condition interface that relies on the global lock. This
+ can be used if we never yield between testing the condition and
+ waiting on it. */
+ npth_cond_t wait_data; /* Condition that data is available. */
+ npth_cond_t wait_space; /* Condition that space is available. */
+
+ int eof_seen; /* EOF indicator. */
+ char buffer[4000]; /* Data ring buffer. */
+ size_t buffer_len; /* The amount of data in the BUFFER. */
+ size_t buffer_pos; /* The next read position of the BUFFER. */
+};
+
+#define BUFFER_EMPTY(c) ((c)->buffer_len == 0)
+#define BUFFER_FULL(c) ((c)->buffer_len == DIM((c)->buffer))
+#define BUFFER_DATA_AVAILABLE(c) ((c)->buffer_len)
+#define BUFFER_SPACE_AVAILABLE(c) (DIM((c)->buffer) - (c)->buffer_len)
+#define BUFFER_INC_POS(c,n) (c)->buffer_pos = ((c)->buffer_pos + (n)) % DIM((c)->buffer)
+#define BUFFER_CUR_POS(c) (&(c)->buffer[(c)->buffer_pos])
+
+static int
+buffer_get_data (struct outstream_cookie_s *cookie, char *dst, int cnt)
+{
+ int amount;
+ int left;
+ int chunk;
+
+ amount = cnt;
+ if (BUFFER_DATA_AVAILABLE (cookie) < amount)
+ amount = BUFFER_DATA_AVAILABLE (cookie);
+ left = amount;
+
+ /* How large is the part up to the end of the buffer array? */
+ chunk = DIM(cookie->buffer) - cookie->buffer_pos;
+ if (chunk > left)
+ chunk = left;
+
+ memcpy (dst, BUFFER_CUR_POS (cookie), chunk);
+ BUFFER_INC_POS (cookie, chunk);
+ left -= chunk;
+ dst += chunk;
+
+ if (left)
+ {
+ memcpy (dst, BUFFER_CUR_POS (cookie), left);
+ BUFFER_INC_POS (cookie, left);
+ }
+
+ return amount;
+}
+
+
+static int
+buffer_put_data (struct outstream_cookie_s *cookie, const char *src, int cnt)
+{
+ int amount;
+ int remain;
+ int left;
+ int chunk;
+
+ remain = DIM(cookie->buffer) - cookie->buffer_len;
+
+ amount = cnt;
+ if (remain < amount)
+ amount = remain;
+ left = amount;
+
+ /* How large is the part up to the end of the buffer array? */
+ chunk = DIM(cookie->buffer) - cookie->buffer_pos;
+ if (chunk > left)
+ chunk = left;
+
+ memcpy (BUFFER_CUR_POS (cookie), src, chunk);
+ BUFFER_INC_POS (cookie, chunk);
+ left -= chunk;
+ src += chunk;
+
+ if (left)
+ {
+ memcpy (BUFFER_CUR_POS (cookie), src, left);
+ BUFFER_INC_POS (cookie, left);
+ }
+
+ cookie->buffer_len -= amount;
+ return amount;
+}
+
+
+/* The writer function for the outstream. This is used to transfer
+ the output of the ldap wrapper thread to the ksba reader object. */
+static ssize_t
+outstream_cookie_writer (void *cookie_arg, const void *buffer, size_t size)
+{
+ struct outstream_cookie_s *cookie = cookie_arg;
+ const char *src;
+ ssize_t nwritten = 0;
+ int res;
+ ssize_t amount = 0;
+
+ src = buffer;
+ do
+ {
+ int was_empty = 0;
+
+ /* Wait for free space. */
+ while (BUFFER_FULL(cookie))
+ {
+ /* Buffer is full: Wait for space. */
+ res = npth_cond_wait (&cookie->wait_space, NULL);
+ if (res)
+ {
+ gpg_err_set_errno (res);
+ return -1;
+ }
+ }
+
+ if (BUFFER_EMPTY(cookie))
+ was_empty = 1;
+
+ /* Copy data. */
+ nwritten = buffer_put_data (cookie, buffer, size);
+ size -= nwritten;
+ src += nwritten;
+ amount += nwritten;
+
+ if (was_empty)
+ npth_cond_signal (&cookie->wait_data);
+ }
+ while (size); /* Until done. */
+
+ return amount;
+}
+
+
+static void
+outstream_release_cookie (struct outstream_cookie_s *cookie)
+{
+ cookie->refcount--;
+ if (!cookie->refcount)
+ {
+ npth_cond_destroy (&cookie->wait_data);
+ npth_cond_destroy (&cookie->wait_space);
+ xfree (cookie);
+ }
+}
+
+
+/* Closer function for the outstream. This deallocates the cookie if
+ it won't be used anymore. */
+static int
+outstream_cookie_closer (void *cookie_arg)
+{
+ struct outstream_cookie_s *cookie = cookie_arg;
+
+ if (!cookie)
+ return 0; /* Nothing to do. */
+
+ cookie->eof_seen = 1; /* (only useful if refcount > 1) */
+
+ assert (cookie->refcount > 0);
+ outstream_release_cookie (cookie);
+ return 0;
+}
+
+
+/* The KSBA reader callback which takes the output of the ldap thread
+ form the outstream_cookie_writer and make it available to the ksba
+ reader. */
+static int
+outstream_reader_cb (void *cb_value, char *buffer, size_t count,
+ size_t *r_nread)
+{
+ struct outstream_cookie_s *cookie = cb_value;
+ size_t nread = 0;
+ int was_full = 0;
+
+ if (!buffer && !count && !r_nread)
+ return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Rewind is not supported. */
+
+ *r_nread = 0;
+
+ while (BUFFER_EMPTY(cookie))
+ {
+ if (cookie->eof_seen)
+ return gpg_error (GPG_ERR_EOF);
+
+ /* Wait for data to become available. */
+ npth_cond_wait (&cookie->wait_data, NULL);
+ }
+
+ if (BUFFER_FULL(cookie))
+ was_full = 1;
+
+ nread = buffer_get_data (cookie, buffer, count);
+
+ if (was_full)
+ {
+ npth_cond_signal (&cookie->wait_space);
+ }
+
+ *r_nread = nread;
+ return 0; /* Success. */
+}
+
+
+/* This function is called by ksba_reader_release. */
+static void
+outstream_reader_released (void *cb_value, ksba_reader_t r)
+{
+ struct outstream_cookie_s *cookie = cb_value;
+
+ (void)r;
+
+ assert (cookie->refcount > 0);
+ outstream_release_cookie (cookie);
+}
+
+
+
+/* This function is to be used to release a context associated with the
+ given reader object. This does not release the reader object, though. */
+void
+ldap_wrapper_release_context (ksba_reader_t reader)
+{
+ (void)reader;
+ /* Nothing to do. */
+}
+
+
+
+/* Free a NULL terminated array of malloced strings and the array
+ itself. */
+static void
+free_arg_list (char **arg_list)
+{
+ int i;
+
+ if (arg_list)
+ {
+ for (i=0; arg_list[i]; i++)
+ xfree (arg_list[i]);
+ xfree (arg_list);
+ }
+}
+
+
+/* Copy ARGV into a new array and prepend one element as name of the
+ program (which is more or less a stub). We need to allocate all
+ the strings to get ownership of them. */
+static gpg_error_t
+create_arg_list (const char *argv[], char ***r_arg_list)
+{
+ gpg_error_t err;
+ char **arg_list;
+ int i, j;
+
+ for (i = 0; argv[i]; i++)
+ ;
+ arg_list = xtrycalloc (i + 2, sizeof *arg_list);
+ if (!arg_list)
+ goto outofcore;
+
+ i = 0;
+ arg_list[i] = xtrystrdup ("<ldap-wrapper-thread>");
+ if (!arg_list[i])
+ goto outofcore;
+ i++;
+ for (j=0; argv[j]; j++)
+ {
+ arg_list[i] = xtrystrdup (argv[j]);
+ if (!arg_list[i])
+ goto outofcore;
+ i++;
+ }
+ arg_list[i] = NULL;
+ *r_arg_list = arg_list;
+ return 0;
+
+ outofcore:
+ err = gpg_error_from_syserror ();
+ log_error (_("error allocating memory: %s\n"), strerror (errno));
+ free_arg_list (arg_list);
+ *r_arg_list = NULL;
+ return err;
+
+}
+
+
+/* Parameters passed to the wrapper thread. */
+struct ldap_wrapper_thread_parms
+{
+ char **arg_list;
+ estream_t outstream;
+};
+
+/* The thread which runs the LDAP wrapper. */
+static void *
+ldap_wrapper_thread (void *opaque)
+{
+ struct ldap_wrapper_thread_parms *parms = opaque;
+
+ /*err =*/ ldap_wrapper_main (parms->arg_list, parms->outstream);
+
+ /* FIXME: Do we need to return ERR? */
+
+ free_arg_list (parms->arg_list);
+ es_fclose (parms->outstream);
+ xfree (parms);
+ return NULL;
+}
+
+
+
+/* Start a new LDAP thread and returns a new libksba reader
+ object at READER. ARGV is a NULL terminated list of arguments for
+ the wrapper. The function returns 0 on success or an error code. */
+gpg_error_t
+ldap_wrapper (ctrl_t ctrl, ksba_reader_t *r_reader, const char *argv[])
+{
+ gpg_error_t err;
+ struct ldap_wrapper_thread_parms *parms;
+ npth_attr_t tattr;
+ es_cookie_io_functions_t outstream_func = { NULL };
+ struct outstream_cookie_s *outstream_cookie;
+ ksba_reader_t reader;
+ int res;
+ npth_t thread;
+
+ (void)ctrl;
+
+ *r_reader = NULL;
+
+ parms = xtrycalloc (1, sizeof *parms);
+ if (!parms)
+ return gpg_error_from_syserror ();
+
+ err = create_arg_list (argv, &parms->arg_list);
+ if (err)
+ {
+ xfree (parms);
+ return err;
+ }
+
+ outstream_cookie = xtrycalloc (1, sizeof *outstream_cookie);
+ if (!outstream_cookie)
+ {
+ err = gpg_error_from_syserror ();
+ free_arg_list (parms->arg_list);
+ xfree (parms);
+ return err;
+ }
+ outstream_cookie->refcount++;
+
+ res = npth_cond_init (&outstream_cookie->wait_data, NULL);
+ if (res)
+ {
+ free_arg_list (parms->arg_list);
+ xfree (parms);
+ return gpg_error_from_errno (res);
+ }
+ res = npth_cond_init (&outstream_cookie->wait_space, NULL);
+ if (res)
+ {
+ npth_cond_destroy (&outstream_cookie->wait_data);
+ free_arg_list (parms->arg_list);
+ xfree (parms);
+ return gpg_error_from_errno (res);
+ }
+
+ err = ksba_reader_new (&reader);
+ if (!err)
+ err = ksba_reader_set_release_notify (reader,
+ outstream_reader_released,
+ outstream_cookie);
+ if (!err)
+ err = ksba_reader_set_cb (reader,
+ outstream_reader_cb, outstream_cookie);
+ if (err)
+ {
+ log_error (_("error initializing reader object: %s\n"),
+ gpg_strerror (err));
+ ksba_reader_release (reader);
+ outstream_release_cookie (outstream_cookie);
+ free_arg_list (parms->arg_list);
+ xfree (parms);
+ return err;
+ }
+
+
+ outstream_func.func_write = outstream_cookie_writer;
+ outstream_func.func_close = outstream_cookie_closer;
+ parms->outstream = es_fopencookie (outstream_cookie, "wb", outstream_func);
+ if (!parms->outstream)
+ {
+ err = gpg_error_from_syserror ();
+ ksba_reader_release (reader);
+ outstream_release_cookie (outstream_cookie);
+ free_arg_list (parms->arg_list);
+ xfree (parms);
+ return err;
+ }
+ outstream_cookie->refcount++;
+
+ res = npth_attr_init(&tattr);
+ if (res)
+ {
+ err = gpg_error_from_errno (res);
+ ksba_reader_release (reader);
+ free_arg_list (parms->arg_list);
+ es_fclose (parms->outstream);
+ xfree (parms);
+ return err;
+ }
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+
+ res = npth_create (&thread, &tattr, ldap_wrapper_thread, parms);
+ npth_attr_destroy (&tattr);
+ if (res)
+ {
+ err = gpg_error_from_errno (res);
+ log_error ("error spawning ldap wrapper thread: %s\n",
+ strerror (res) );
+ }
+ else
+ parms = NULL; /* Now owned by the thread. */
+
+ if (parms)
+ {
+ free_arg_list (parms->arg_list);
+ es_fclose (parms->outstream);
+ xfree (parms);
+ }
+ if (err)
+ {
+ ksba_reader_release (reader);
+ return err;
+ }
+
+ /* Need to wait for the first byte so we are able to detect an empty
+ output and not let the consumer see an EOF without further error
+ indications. The CRL loading logic assumes that after return
+ from this function, a failed search (e.g. host not found ) is
+ indicated right away. */
+ {
+ unsigned char c;
+
+ err = read_buffer (reader, &c, 1);
+ if (err)
+ {
+ ksba_reader_release (reader);
+ reader = NULL;
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ return gpg_error (GPG_ERR_NO_DATA);
+ else
+ return err;
+ }
+ ksba_reader_unread (reader, &c, 1);
+ }
+
+ *r_reader = reader;
+
+ return 0;
+}
diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c
new file mode 100644
index 0000000..f2aaf59
--- /dev/null
+++ b/dirmngr/ldap-wrapper.c
@@ -0,0 +1,788 @@
+/* ldap-wrapper.c - LDAP access via a wrapper process
+ * Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH
+ * Copyright (C) 2010 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ We can't use LDAP directly for these reasons:
+
+ 1. On some systems the LDAP library uses (indirectly) pthreads and
+ that is not compatible with PTh.
+
+ 2. It is huge library in particular if TLS comes into play. So
+ problems with unfreed memory might turn up and we don't want
+ this in a long running daemon.
+
+ 3. There is no easy way for timeouts. In particular the timeout
+ value does not work for DNS lookups (well, this is usual) and it
+ seems not to work while loading a large attribute like a
+ CRL. Having a separate process allows us to either tell the
+ process to commit suicide or have our own housekepping function
+ kill it after some time. The latter also allows proper
+ cancellation of a query at any point of time.
+
+ 4. Given that we are going out to the network and usually get back
+ a long response, the fork/exec overhead is acceptable.
+
+ Note that under WindowsCE the number of processes is strongly
+ limited (32 processes including the kernel processes) and thus we
+ don't use the process approach but implement a different wrapper in
+ ldap-wrapper-ce.c.
+*/
+
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <npth.h>
+
+#include "dirmngr.h"
+#include "exechelp.h"
+#include "misc.h"
+#include "ldap-wrapper.h"
+
+
+#ifdef HAVE_W32_SYSTEM
+#define setenv(a,b,c) SetEnvironmentVariable ((a),(b))
+#else
+#define pth_close(fd) close(fd)
+#endif
+
+#ifndef USE_LDAPWRAPPER
+# error This module is not expected to be build.
+#endif
+
+/* In case sysconf does not return a value we need to have a limit. */
+#ifdef _POSIX_OPEN_MAX
+#define MAX_OPEN_FDS _POSIX_OPEN_MAX
+#else
+#define MAX_OPEN_FDS 20
+#endif
+
+#define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */
+
+#define TIMERTICK_INTERVAL 2
+
+/* To keep track of the LDAP wrapper state we use this structure. */
+struct wrapper_context_s
+{
+ struct wrapper_context_s *next;
+
+ pid_t pid; /* The pid of the wrapper process. */
+ int printable_pid; /* Helper to print diagnostics after the process has
+ been cleaned up. */
+ int fd; /* Connected with stdout of the ldap wrapper. */
+ gpg_error_t fd_error; /* Set to the gpg_error of the last read error
+ if any. */
+ int log_fd; /* Connected with stderr of the ldap wrapper. */
+ ctrl_t ctrl; /* Connection data. */
+ int ready; /* Internally used to mark to be removed contexts. */
+ ksba_reader_t reader; /* The ksba reader object or NULL. */
+ char *line; /* Used to print the log lines (malloced). */
+ size_t linesize;/* Allocated size of LINE. */
+ size_t linelen; /* Use size of LINE. */
+ time_t stamp; /* The last time we noticed ativity. */
+};
+
+
+
+/* We keep a global list of spawed wrapper process. A separate thread
+ makes use of this list to log error messages and to watch out for
+ finished processes. */
+static struct wrapper_context_s *wrapper_list;
+
+/* We need to know whether we are shutting down the process. */
+static int shutting_down;
+
+/* Close the pth file descriptor FD and set it to -1. */
+#define SAFE_CLOSE(fd) \
+ do { int _fd = fd; if (_fd != -1) { close (_fd); fd = -1;} } while (0)
+
+
+
+
+/* Read a fixed amount of data from READER into BUFFER. */
+static gpg_error_t
+read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
+{
+ gpg_error_t err;
+ size_t nread;
+
+ while (count)
+ {
+ err = ksba_reader_read (reader, buffer, count, &nread);
+ if (err)
+ return err;
+ buffer += nread;
+ count -= nread;
+ }
+ return 0;
+}
+
+
+/* Release the wrapper context and kill a running wrapper process. */
+static void
+destroy_wrapper (struct wrapper_context_s *ctx)
+{
+ if (ctx->pid != (pid_t)(-1))
+ {
+ gnupg_kill_process (ctx->pid);
+ gnupg_release_process (ctx->pid);
+ }
+ ksba_reader_release (ctx->reader);
+ SAFE_CLOSE (ctx->fd);
+ SAFE_CLOSE (ctx->log_fd);
+ xfree (ctx->line);
+ xfree (ctx);
+}
+
+
+/* Print the content of LINE to thye log stream but make sure to only
+ print complete lines. Using NULL for LINE will flush any pending
+ output. LINE may be modified by this fucntion. */
+static void
+print_log_line (struct wrapper_context_s *ctx, char *line)
+{
+ char *s;
+ size_t n;
+
+ if (!line)
+ {
+ if (ctx->line && ctx->linelen)
+ {
+
+ log_info ("%s\n", ctx->line);
+ ctx->linelen = 0;
+ }
+ return;
+ }
+
+ while ((s = strchr (line, '\n')))
+ {
+ *s = 0;
+ if (ctx->line && ctx->linelen)
+ {
+ log_info ("%s", ctx->line);
+ ctx->linelen = 0;
+ log_printf ("%s\n", line);
+ }
+ else
+ log_info ("%s\n", line);
+ line = s + 1;
+ }
+ n = strlen (line);
+ if (n)
+ {
+ if (ctx->linelen + n + 1 >= ctx->linesize)
+ {
+ char *tmp;
+ size_t newsize;
+
+ newsize = ctx->linesize + ((n + 255) & ~255) + 1;
+ tmp = (ctx->line ? xtryrealloc (ctx->line, newsize)
+ : xtrymalloc (newsize));
+ if (!tmp)
+ {
+ log_error (_("error printing log line: %s\n"), strerror (errno));
+ return;
+ }
+ ctx->line = tmp;
+ ctx->linesize = newsize;
+ }
+ memcpy (ctx->line + ctx->linelen, line, n);
+ ctx->linelen += n;
+ ctx->line[ctx->linelen] = 0;
+ }
+}
+
+
+/* Read data from the log stream. Returns true if the log stream
+ indicated EOF or error. */
+static int
+read_log_data (struct wrapper_context_s *ctx)
+{
+ int n;
+ char line[256];
+
+ /* We must use the npth_read function for pipes, always. */
+ do
+ n = npth_read (ctx->log_fd, line, sizeof line - 1);
+ while (n < 0 && errno == EINTR);
+
+ if (n <= 0) /* EOF or error. */
+ {
+ if (n < 0)
+ log_error (_("error reading log from ldap wrapper %d: %s\n"),
+ ctx->pid, strerror (errno));
+ print_log_line (ctx, NULL);
+ SAFE_CLOSE (ctx->log_fd);
+ return 1;
+ }
+
+ line[n] = 0;
+ print_log_line (ctx, line);
+ if (ctx->stamp != (time_t)(-1))
+ ctx->stamp = time (NULL);
+ return 0;
+}
+
+
+/* This function is run by a separate thread to maintain the list of
+ wrappers and to log error messages from these wrappers. */
+void *
+ldap_wrapper_thread (void *dummy)
+{
+ int nfds;
+ struct wrapper_context_s *ctx;
+ struct wrapper_context_s *ctx_prev;
+ struct timespec abstime;
+ struct timespec curtime;
+ struct timespec timeout;
+ int saved_errno;
+ fd_set fdset, read_fdset;
+ int ret;
+ time_t exptime;
+
+ (void)dummy;
+
+ FD_ZERO (&fdset);
+ nfds = -1;
+ for (ctx = wrapper_list; ctx; ctx = ctx->next)
+ {
+ if (ctx->log_fd != -1)
+ {
+ FD_SET (ctx->log_fd, &fdset);
+ if (ctx->log_fd > nfds)
+ nfds = ctx->log_fd;
+ }
+ }
+ nfds++;
+
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+
+ for (;;)
+ {
+ int any_action = 0;
+
+ /* POSIX says that fd_set should be implemented as a structure,
+ thus a simple assignment is fine to copy the entire set. */
+ read_fdset = fdset;
+
+ npth_clock_gettime (&curtime);
+ if (!(npth_timercmp (&curtime, &abstime, <)))
+ {
+ /* Inactivity is checked below. Nothing else to do. */
+ // handle_tick ();
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+ }
+ npth_timersub (&abstime, &curtime, &timeout);
+
+ /* FIXME: For Windows, we have to use a reader thread on the
+ pipe that signals an event (and a npth_select_ev variant). */
+ ret = npth_pselect (nfds + 1, &read_fdset, NULL, NULL, &timeout, NULL);
+ saved_errno = errno;
+
+ if (ret == -1 && saved_errno != EINTR)
+ {
+ log_error (_("npth_select failed: %s - waiting 1s\n"),
+ strerror (saved_errno));
+ npth_sleep (1);
+ continue;
+ }
+
+ if (ret <= 0)
+ /* Interrupt or timeout. Will be handled when calculating the
+ next timeout. */
+ continue;
+
+ /* All timestamps before exptime should be considered expired. */
+ exptime = time (NULL);
+ if (exptime > INACTIVITY_TIMEOUT)
+ exptime -= INACTIVITY_TIMEOUT;
+
+ /* Note that there is no need to lock the list because we always
+ add entries at the head (with a pending event status) and
+ thus traversing the list will even work if we have a context
+ switch in waitpid (which should anyway only happen with Pth's
+ hard system call mapping). */
+ for (ctx = wrapper_list; ctx; ctx = ctx->next)
+ {
+ /* Check whether there is any logging to be done. */
+ if (nfds && ctx->log_fd != -1 && FD_ISSET (ctx->log_fd, &read_fdset))
+ {
+ if (read_log_data (ctx))
+ any_action = 1;
+ }
+
+ /* Check whether the process is still running. */
+ if (ctx->pid != (pid_t)(-1))
+ {
+ gpg_error_t err;
+ int status;
+
+ err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0,
+ &status);
+ if (!err)
+ {
+ log_info (_("ldap wrapper %d ready"), (int)ctx->pid);
+ ctx->ready = 1;
+ gnupg_release_process (ctx->pid);
+ ctx->pid = (pid_t)(-1);
+ any_action = 1;
+ }
+ else if (gpg_err_code (err) == GPG_ERR_GENERAL)
+ {
+ if (status == 10)
+ log_info (_("ldap wrapper %d ready: timeout\n"),
+ (int)ctx->pid);
+ else
+ log_info (_("ldap wrapper %d ready: exitcode=%d\n"),
+ (int)ctx->pid, status);
+ ctx->ready = 1;
+ gnupg_release_process (ctx->pid);
+ ctx->pid = (pid_t)(-1);
+ any_action = 1;
+ }
+ else if (gpg_err_code (err) != GPG_ERR_TIMEOUT)
+ {
+ log_error (_("waiting for ldap wrapper %d failed: %s\n"),
+ (int)ctx->pid, gpg_strerror (err));
+ any_action = 1;
+ }
+ }
+
+ /* Check whether we should terminate the process. */
+ if (ctx->pid != (pid_t)(-1)
+ && ctx->stamp != (time_t)(-1) && ctx->stamp < exptime)
+ {
+ gnupg_kill_process (ctx->pid);
+ ctx->stamp = (time_t)(-1);
+ log_info (_("ldap wrapper %d stalled - killing\n"),
+ (int)ctx->pid);
+ /* We need to close the log fd because the cleanup loop
+ waits for it. */
+ SAFE_CLOSE (ctx->log_fd);
+ any_action = 1;
+ }
+ }
+
+ /* If something has been printed to the log file or we got an
+ EOF from a wrapper, we now print the list of active
+ wrappers. */
+ if (any_action && DBG_LOOKUP)
+ {
+ log_info ("ldap worker stati:\n");
+ for (ctx = wrapper_list; ctx; ctx = ctx->next)
+ log_info (" c=%p pid=%d/%d rdr=%p ctrl=%p/%d la=%lu rdy=%d\n",
+ ctx,
+ (int)ctx->pid, (int)ctx->printable_pid,
+ ctx->reader,
+ ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0,
+ (unsigned long)ctx->stamp, ctx->ready);
+ }
+
+
+ /* Use a separate loop to check whether ready marked wrappers
+ may be removed. We may only do so if the ksba reader object
+ is not anymore in use or we are in shutdown state. */
+ again:
+ for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next)
+ if (ctx->ready
+ && ((ctx->log_fd == -1 && !ctx->reader) || shutting_down))
+ {
+ if (ctx_prev)
+ ctx_prev->next = ctx->next;
+ else
+ wrapper_list = ctx->next;
+ destroy_wrapper (ctx);
+ /* We need to restart because destroy_wrapper might have
+ done a context switch. */
+ goto again;
+ }
+ }
+ /*NOTREACHED*/
+ return NULL; /* Make the compiler happy. */
+}
+
+
+
+/* Start the reaper thread for the ldap wrapper. */
+void
+ldap_wrapper_launch_thread (void)
+{
+ static int done;
+ npth_attr_t tattr;
+ npth_t thread;
+ int err;
+
+ if (done)
+ return;
+ done = 1;
+
+ npth_attr_init (&tattr);
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+
+ err = npth_create (&thread, &tattr, ldap_wrapper_thread, NULL);
+ if (err)
+ {
+ log_error (_("error spawning ldap wrapper reaper thread: %s\n"),
+ strerror (err) );
+ dirmngr_exit (1);
+ }
+ npth_setname_np (thread, "ldap-reaper");
+ npth_attr_destroy (&tattr);
+}
+
+
+
+
+
+/* Wait until all ldap wrappers have terminated. We assume that the
+ kill has already been sent to all of them. */
+void
+ldap_wrapper_wait_connections ()
+{
+ shutting_down = 1;
+ /* FIXME: This is a busy wait. */
+ while (wrapper_list)
+ npth_usleep (200);
+}
+
+
+/* This function is to be used to release a context associated with the
+ given reader object. */
+void
+ldap_wrapper_release_context (ksba_reader_t reader)
+{
+ struct wrapper_context_s *ctx;
+
+ if (!reader )
+ return;
+
+ for (ctx=wrapper_list; ctx; ctx=ctx->next)
+ if (ctx->reader == reader)
+ {
+ if (DBG_LOOKUP)
+ log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n",
+ ctx,
+ (int)ctx->pid, (int)ctx->printable_pid,
+ ctx->reader,
+ ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0);
+
+ ctx->reader = NULL;
+ SAFE_CLOSE (ctx->fd);
+ if (ctx->ctrl)
+ {
+ ctx->ctrl->refcount--;
+ ctx->ctrl = NULL;
+ }
+ if (ctx->fd_error)
+ log_info (_("reading from ldap wrapper %d failed: %s\n"),
+ ctx->printable_pid, gpg_strerror (ctx->fd_error));
+ break;
+ }
+}
+
+/* Cleanup all resources held by the connection associated with
+ CTRL. This is used after a cancel to kill running wrappers. */
+void
+ldap_wrapper_connection_cleanup (ctrl_t ctrl)
+{
+ struct wrapper_context_s *ctx;
+
+ for (ctx=wrapper_list; ctx; ctx=ctx->next)
+ if (ctx->ctrl && ctx->ctrl == ctrl)
+ {
+ ctx->ctrl->refcount--;
+ ctx->ctrl = NULL;
+ if (ctx->pid != (pid_t)(-1))
+ gnupg_kill_process (ctx->pid);
+ if (ctx->fd_error)
+ log_info (_("reading from ldap wrapper %d failed: %s\n"),
+ ctx->printable_pid, gpg_strerror (ctx->fd_error));
+ }
+}
+
+
+/* This is the callback used by the ldap wrapper to feed the ksba
+ reader with the wrappers stdout. See the description of
+ ksba_reader_set_cb for details. */
+static int
+reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread)
+{
+ struct wrapper_context_s *ctx = cb_value;
+ size_t nleft = count;
+ int nfds;
+ struct timespec abstime;
+ struct timespec curtime;
+ struct timespec timeout;
+ int saved_errno;
+ fd_set fdset, read_fdset;
+ int ret;
+
+ /* FIXME: We might want to add some internal buffering because the
+ ksba code does not do any buffering for itself (because a ksba
+ reader may be detached from another stream to read other data and
+ the it would be cumbersome to get back already buffered
+ stuff). */
+
+ if (!buffer && !count && !nread)
+ return -1; /* Rewind is not supported. */
+
+ /* If we ever encountered a read error don't allow to continue and
+ possible overwrite the last error cause. Bail out also if the
+ file descriptor has been closed. */
+ if (ctx->fd_error || ctx->fd == -1)
+ {
+ *nread = 0;
+ return -1;
+ }
+
+ FD_ZERO (&fdset);
+ FD_SET (ctx->fd, &fdset);
+ nfds = ctx->fd + 1;
+
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+
+ while (nleft > 0)
+ {
+ int n;
+ gpg_error_t err;
+
+ npth_clock_gettime (&curtime);
+ if (!(npth_timercmp (&curtime, &abstime, <)))
+ {
+ err = dirmngr_tick (ctx->ctrl);
+ if (err)
+ {
+ ctx->fd_error = err;
+ SAFE_CLOSE (ctx->fd);
+ return -1;
+ }
+ npth_clock_gettime (&abstime);
+ abstime.tv_sec += TIMERTICK_INTERVAL;
+ }
+ npth_timersub (&abstime, &curtime, &timeout);
+
+ read_fdset = fdset;
+ ret = npth_pselect (nfds, &read_fdset, NULL, NULL, &timeout, NULL);
+ saved_errno = errno;
+
+ if (ret == -1 && saved_errno != EINTR)
+ {
+ ctx->fd_error = gpg_error_from_errno (errno);
+ SAFE_CLOSE (ctx->fd);
+ return -1;
+ }
+ if (ret <= 0)
+ /* Timeout. Will be handled when calculating the next timeout. */
+ continue;
+
+ /* This should not block now that select returned with a file
+ descriptor. So it shouldn't be necessary to use npth_read
+ (and it is slightly dangerous in the sense that a concurrent
+ thread might (accidentially?) change the status of ctx->fd
+ before we read. FIXME: Set ctx->fd to nonblocking? */
+ n = read (ctx->fd, buffer, nleft);
+ if (n < 0)
+ {
+ ctx->fd_error = gpg_error_from_errno (errno);
+ SAFE_CLOSE (ctx->fd);
+ return -1;
+ }
+ else if (!n)
+ {
+ if (nleft == count)
+ return -1; /* EOF. */
+ break;
+ }
+ nleft -= n;
+ buffer += n;
+ if (n > 0 && ctx->stamp != (time_t)(-1))
+ ctx->stamp = time (NULL);
+ }
+ *nread = count - nleft;
+
+ return 0;
+}
+
+/* Fork and exec the LDAP wrapper and returns a new libksba reader
+ object at READER. ARGV is a NULL terminated list of arguments for
+ the wrapper. The function returns 0 on success or an error code.
+
+ Special hack to avoid passing a password through the command line
+ which is globally visible: If the first element of ARGV is "--pass"
+ it will be removed and instead the environment variable
+ DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern
+ OSes the environment is not visible to other users. For those old
+ systems where it can't be avoided, we don't want to go into the
+ hassle of passing the password via stdin; it's just too complicated
+ and an LDAP password used for public directory lookups should not
+ be that confidential. */
+gpg_error_t
+ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
+{
+ gpg_error_t err;
+ pid_t pid;
+ struct wrapper_context_s *ctx;
+ int i;
+ int j;
+ const char **arg_list;
+ const char *pgmname;
+ int outpipe[2], errpipe[2];
+
+ /* It would be too simple to connect stderr just to our logging
+ stream. The problem is that if we are running multi-threaded
+ everything gets intermixed. Clearly we don't want this. So the
+ only viable solutions are either to have another thread
+ responsible for logging the messages or to add an option to the
+ wrapper module to do the logging on its own. Given that we anyway
+ need a way to rip the child process and this is best done using a
+ general ripping thread, that thread can do the logging too. */
+
+ *reader = NULL;
+
+ /* Files: We need to prepare stdin and stdout. We get stderr from
+ the function. */
+ if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program)
+ pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP);
+ else
+ pgmname = opt.ldap_wrapper_program;
+
+ /* Create command line argument array. */
+ for (i = 0; argv[i]; i++)
+ ;
+ arg_list = xtrycalloc (i + 2, sizeof *arg_list);
+ if (!arg_list)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error allocating memory: %s\n"), strerror (errno));
+ return err;
+ }
+ for (i = j = 0; argv[i]; i++, j++)
+ if (!i && argv[i + 1] && !strcmp (*argv, "--pass"))
+ {
+ arg_list[j] = "--env-pass";
+ setenv ("DIRMNGR_LDAP_PASS", argv[1], 1);
+ i++;
+ }
+ else
+ arg_list[j] = (char*) argv[i];
+
+ ctx = xtrycalloc (1, sizeof *ctx);
+ if (!ctx)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error allocating memory: %s\n"), strerror (errno));
+ xfree (arg_list);
+ return err;
+ }
+
+ err = gnupg_create_inbound_pipe (outpipe);
+ if (!err)
+ {
+ err = gnupg_create_inbound_pipe (errpipe);
+ if (err)
+ {
+ close (outpipe[0]);
+ close (outpipe[1]);
+ }
+ }
+ if (err)
+ {
+ log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
+ xfree (arg_list);
+ xfree (ctx);
+ return err;
+ }
+
+ err = gnupg_spawn_process_fd (pgmname, arg_list,
+ -1, outpipe[1], errpipe[1], &pid);
+ xfree (arg_list);
+ close (outpipe[1]);
+ close (errpipe[1]);
+ if (err)
+ {
+ close (outpipe[0]);
+ close (errpipe[0]);
+ xfree (ctx);
+ return err;
+ }
+
+ ctx->pid = pid;
+ ctx->printable_pid = (int) pid;
+ ctx->fd = outpipe[0];
+ ctx->log_fd = errpipe[0];
+ ctx->ctrl = ctrl;
+ ctrl->refcount++;
+ ctx->stamp = time (NULL);
+
+ err = ksba_reader_new (reader);
+ if (!err)
+ err = ksba_reader_set_cb (*reader, reader_callback, ctx);
+ if (err)
+ {
+ log_error (_("error initializing reader object: %s\n"),
+ gpg_strerror (err));
+ destroy_wrapper (ctx);
+ ksba_reader_release (*reader);
+ *reader = NULL;
+ return err;
+ }
+
+ /* Hook the context into our list of running wrappers. */
+ ctx->reader = *reader;
+ ctx->next = wrapper_list;
+ wrapper_list = ctx;
+ if (opt.verbose)
+ log_info ("ldap wrapper %d started (reader %p)\n",
+ (int)ctx->pid, ctx->reader);
+
+ /* Need to wait for the first byte so we are able to detect an empty
+ output and not let the consumer see an EOF without further error
+ indications. The CRL loading logic assumes that after return
+ from this function, a failed search (e.g. host not found ) is
+ indicated right away. */
+ {
+ unsigned char c;
+
+ err = read_buffer (*reader, &c, 1);
+ if (err)
+ {
+ ldap_wrapper_release_context (*reader);
+ ksba_reader_release (*reader);
+ *reader = NULL;
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ return gpg_error (GPG_ERR_NO_DATA);
+ else
+ return err;
+ }
+ ksba_reader_unread (*reader, &c, 1);
+ }
+
+ return 0;
+}
diff --git a/dirmngr/ldap-wrapper.h b/dirmngr/ldap-wrapper.h
new file mode 100644
index 0000000..f7f5680
--- /dev/null
+++ b/dirmngr/ldap-wrapper.h
@@ -0,0 +1,40 @@
+/* ldap-wrapper.h - Interface to an LDAP access wrapper.
+ * Copyright (C) 2010 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LDAP_WRAPPER_H
+#define LDAP_WRAPPER_H
+
+#include <ksba.h>
+
+/* ldap-wrapper.c or ldap-wrapper-ce.c */
+void ldap_wrapper_launch_thread (void);
+void ldap_wrapper_wait_connections (void);
+void ldap_wrapper_release_context (ksba_reader_t reader);
+void ldap_wrapper_connection_cleanup (ctrl_t);
+gpg_error_t ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader,
+ const char *argv[]);
+
+
+/* dirmngr_ldap.c */
+#ifndef USE_LDAPWRAPPER
+int ldap_wrapper_main (char **argv, estream_t outstream);
+#endif
+
+
+#endif /*LDAP_WRAPPER_H*/
diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c
new file mode 100644
index 0000000..478fdfd
--- /dev/null
+++ b/dirmngr/ldap.c
@@ -0,0 +1,843 @@
+/* ldap.c - LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2010 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <npth.h>
+
+#include "dirmngr.h"
+#include "exechelp.h"
+#include "crlfetch.h"
+#include "ldapserver.h"
+#include "misc.h"
+#include "ldap-wrapper.h"
+
+
+#define UNENCODED_URL_CHARS "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "01234567890" \
+ "$-_.+!*'(),"
+#define USERCERTIFICATE "userCertificate"
+#define CACERTIFICATE "caCertificate"
+#define X509CACERT "x509caCert"
+#define USERSMIMECERTIFICATE "userSMIMECertificate"
+
+
+/* Definition for the context of the cert fetch functions. */
+struct cert_fetch_context_s
+{
+ ksba_reader_t reader; /* The reader used (shallow copy). */
+ unsigned char *tmpbuf; /* Helper buffer. */
+ size_t tmpbufsize; /* Allocated size of tmpbuf. */
+ int truncated; /* Flag to indicate a truncated output. */
+};
+
+
+
+
+/* Add HOST and PORT to our list of LDAP servers. Fixme: We should
+ better use an extra list of servers. */
+static void
+add_server_to_servers (const char *host, int port)
+{
+ ldap_server_t server;
+ ldap_server_t last = NULL;
+ const char *s;
+
+ if (!port)
+ port = 389;
+
+ for (server=opt.ldapservers; server; server = server->next)
+ {
+ if (!strcmp (server->host, host) && server->port == port)
+ return; /* already in list... */
+ last = server;
+ }
+
+ /* We assume that the host names are all supplied by our
+ configuration files and thus are sane. To keep this assumption
+ we must reject all invalid host names. */
+ for (s=host; *s; s++)
+ if (!strchr ("abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "01234567890.-", *s))
+ {
+ log_error (_("invalid char 0x%02x in host name - not added\n"), *s);
+ return;
+ }
+
+ log_info (_("adding '%s:%d' to the ldap server list\n"), host, port);
+ server = xtrycalloc (1, sizeof *s);
+ if (!server)
+ log_error (_("malloc failed: %s\n"), strerror (errno));
+ else
+ {
+ server->host = xstrdup (host);
+ server->port = port;
+ if (last)
+ last->next = server;
+ else
+ opt.ldapservers = server;
+ }
+}
+
+
+
+
+/* Perform an LDAP query. Returns an gpg error code or 0 on success.
+ The function returns a new reader object at READER. */
+static gpg_error_t
+run_ldap_wrapper (ctrl_t ctrl,
+ int ignore_timeout,
+ int multi_mode,
+ const char *proxy,
+ const char *host, int port,
+ const char *user, const char *pass,
+ const char *dn, const char *filter, const char *attr,
+ const char *url,
+ ksba_reader_t *reader)
+{
+ const char *argv[40];
+ int argc;
+ char portbuf[30], timeoutbuf[30];
+
+
+ *reader = NULL;
+
+ argc = 0;
+ if (pass) /* Note, that the password most be the first item. */
+ {
+ argv[argc++] = "--pass";
+ argv[argc++] = pass;
+ }
+ if (opt.verbose)
+ argv[argc++] = "-vv";
+ argv[argc++] = "--log-with-pid";
+ if (multi_mode)
+ argv[argc++] = "--multi";
+ if (opt.ldaptimeout)
+ {
+ sprintf (timeoutbuf, "%u", opt.ldaptimeout);
+ argv[argc++] = "--timeout";
+ argv[argc++] = timeoutbuf;
+ if (ignore_timeout)
+ argv[argc++] = "--only-search-timeout";
+ }
+ if (proxy)
+ {
+ argv[argc++] = "--proxy";
+ argv[argc++] = proxy;
+ }
+ if (host)
+ {
+ argv[argc++] = "--host";
+ argv[argc++] = host;
+ }
+ if (port)
+ {
+ sprintf (portbuf, "%d", port);
+ argv[argc++] = "--port";
+ argv[argc++] = portbuf;
+ }
+ if (user)
+ {
+ argv[argc++] = "--user";
+ argv[argc++] = user;
+ }
+ if (dn)
+ {
+ argv[argc++] = "--dn";
+ argv[argc++] = dn;
+ }
+ if (filter)
+ {
+ argv[argc++] = "--filter";
+ argv[argc++] = filter;
+ }
+ if (attr)
+ {
+ argv[argc++] = "--attr";
+ argv[argc++] = attr;
+ }
+ argv[argc++] = url? url : "ldap://";
+ argv[argc] = NULL;
+
+ return ldap_wrapper (ctrl, reader, argv);
+}
+
+
+
+
+/* Perform a LDAP query using a given URL. On success a new ksba
+ reader is returned. If HOST or PORT are not 0, they are used to
+ override the values from the URL. */
+gpg_error_t
+url_fetch_ldap (ctrl_t ctrl, const char *url, const char *host, int port,
+ ksba_reader_t *reader)
+{
+ gpg_error_t err;
+
+ err = run_ldap_wrapper (ctrl,
+ 1, /* Ignore explicit timeout because CRLs
+ might be very large. */
+ 0,
+ opt.ldap_proxy,
+ host, port,
+ NULL, NULL,
+ NULL, NULL, NULL, url,
+ reader);
+
+ /* FIXME: This option might be used for DoS attacks. Because it
+ will enlarge the list of servers to consult without a limit and
+ all LDAP queries w/o a host are will then try each host in
+ turn. */
+ if (!err && opt.add_new_ldapservers && !opt.ldap_proxy)
+ {
+ if (host)
+ add_server_to_servers (host, port);
+ else if (url)
+ {
+ char *tmp = host_and_port_from_url (url, &port);
+ if (tmp)
+ {
+ add_server_to_servers (tmp, port);
+ xfree (tmp);
+ }
+ }
+ }
+
+ /* If the lookup failed and we are not only using the proxy, we try
+ again using our default list of servers. */
+ if (err && !(opt.ldap_proxy && opt.only_ldap_proxy))
+ {
+ struct ldapserver_iter iter;
+
+ if (DBG_LOOKUP)
+ log_debug ("no hostname in URL or query failed; "
+ "trying all default hostnames\n");
+
+ for (ldapserver_iter_begin (&iter, ctrl);
+ err && ! ldapserver_iter_end_p (&iter);
+ ldapserver_iter_next (&iter))
+ {
+ ldap_server_t server = iter.server;
+
+ err = run_ldap_wrapper (ctrl,
+ 0,
+ 0,
+ NULL,
+ server->host, server->port,
+ NULL, NULL,
+ NULL, NULL, NULL, url,
+ reader);
+ if (!err)
+ break;
+ }
+ }
+
+ return err;
+}
+
+
+
+/* Perform an LDAP query on all configured servers. On error the
+ error code of the last try is returned. */
+gpg_error_t
+attr_fetch_ldap (ctrl_t ctrl,
+ const char *dn, const char *attr, ksba_reader_t *reader)
+{
+ gpg_error_t err = gpg_error (GPG_ERR_CONFIGURATION);
+ struct ldapserver_iter iter;
+
+ *reader = NULL;
+
+ /* FIXME; we might want to look at the Base SN to try matching
+ servers first. */
+ for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter);
+ ldapserver_iter_next (&iter))
+ {
+ ldap_server_t server = iter.server;
+
+ err = run_ldap_wrapper (ctrl,
+ 0,
+ 0,
+ opt.ldap_proxy,
+ server->host, server->port,
+ server->user, server->pass,
+ dn, "objectClass=*", attr, NULL,
+ reader);
+ if (!err)
+ break; /* Probably found a result. Ready. */
+ }
+ return err;
+}
+
+
+/* Parse PATTERN and return a new strlist to be used for the actual
+ LDAP query. Bit 0 of the flags field is set if that pattern is
+ actually a base specification. Caller must release the returned
+ strlist. NULL is returned on error.
+
+ * Possible patterns:
+ *
+ * KeyID
+ * Fingerprint
+ * OpenPGP userid
+ * x Email address Indicated by a left angle bracket.
+ * Exact word match in user id or subj. name
+ * x Subj. DN indicated bu a leading slash
+ * Issuer DN
+ * Serial number + subj. DN
+ * x Substring match indicated by a leading '*; is also the default.
+ */
+
+strlist_t
+parse_one_pattern (const char *pattern)
+{
+ strlist_t result = NULL;
+ char *p;
+
+ switch (*pattern)
+ {
+ case '<': /* Email. */
+ {
+ pattern++;
+ result = xmalloc (sizeof *result + 5 + strlen (pattern));
+ result->next = NULL;
+ result->flags = 0;
+ p = stpcpy (stpcpy (result->d, "mail="), pattern);
+ if (p[-1] == '>')
+ *--p = 0;
+ if (!*result->d) /* Error. */
+ {
+ xfree (result);
+ result = NULL;
+ }
+ break;
+ }
+ case '/': /* Subject DN. */
+ pattern++;
+ if (*pattern)
+ {
+ result = xmalloc (sizeof *result + strlen (pattern));
+ result->next = NULL;
+ result->flags = 1; /* Base spec. */
+ strcpy (result->d, pattern);
+ }
+ break;
+ case '#': /* Issuer DN. */
+ pattern++;
+ if (*pattern == '/') /* Just issuer DN. */
+ {
+ pattern++;
+ }
+ else /* Serial number + issuer DN */
+ {
+ }
+ break;
+ case '*':
+ pattern++;
+ default: /* Take as substring match. */
+ {
+ const char format[] = "(|(sn=*%s*)(|(cn=*%s*)(mail=*%s*)))";
+
+ if (*pattern)
+ {
+ result = xmalloc (sizeof *result
+ + strlen (format) + 3 * strlen (pattern));
+ result->next = NULL;
+ result->flags = 0;
+ sprintf (result->d, format, pattern, pattern, pattern);
+ }
+ }
+ break;
+ }
+
+ return result;
+}
+
+/* Take the string STRING and escape it accoring to the URL rules.
+ Retun a newly allocated string. */
+static char *
+escape4url (const char *string)
+{
+ const char *s;
+ char *buf, *p;
+ size_t n;
+
+ if (!string)
+ string = "";
+
+ for (s=string,n=0; *s; s++)
+ if (strchr (UNENCODED_URL_CHARS, *s))
+ n++;
+ else
+ n += 3;
+
+ buf = malloc (n+1);
+ if (!buf)
+ return NULL;
+
+ for (s=string,p=buf; *s; s++)
+ if (strchr (UNENCODED_URL_CHARS, *s))
+ *p++ = *s;
+ else
+ {
+ sprintf (p, "%%%02X", *(const unsigned char *)s);
+ p += 3;
+ }
+ *p = 0;
+
+ return buf;
+}
+
+
+
+/* Create a LDAP URL from DN and FILTER and return it in URL. We don't
+ need the host and port because this will be specified using the
+ override options. */
+static gpg_error_t
+make_url (char **url, const char *dn, const char *filter)
+{
+ gpg_error_t err;
+ char *u_dn, *u_filter;
+ char const attrs[] = (USERCERTIFICATE ","
+/* USERSMIMECERTIFICATE "," */
+ CACERTIFICATE ","
+ X509CACERT );
+
+ *url = NULL;
+
+ u_dn = escape4url (dn);
+ if (!u_dn)
+ return gpg_error_from_errno (errno);
+
+ u_filter = escape4url (filter);
+ if (!u_filter)
+ {
+ err = gpg_error_from_errno (errno);
+ xfree (u_dn);
+ return err;
+ }
+ *url = malloc ( 8 + strlen (u_dn)
+ + 1 + strlen (attrs)
+ + 5 + strlen (u_filter) + 1 );
+ if (!*url)
+ {
+ err = gpg_error_from_errno (errno);
+ xfree (u_dn);
+ xfree (u_filter);
+ return err;
+ }
+
+ stpcpy (stpcpy (stpcpy (stpcpy (stpcpy (stpcpy (*url, "ldap:///"),
+ u_dn),
+ "?"),
+ attrs),
+ "?sub?"),
+ u_filter);
+ xfree (u_dn);
+ xfree (u_filter);
+ return 0;
+}
+
+
+/* Prepare an LDAP query to return the attribute ATTR for the DN. All
+ configured default servers are queried until one responds. This
+ function returns an error code or 0 and a CONTEXT on success. */
+gpg_error_t
+start_default_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context,
+ const char *dn, const char *attr)
+{
+ gpg_error_t err;
+ struct ldapserver_iter iter;
+
+ *context = xtrycalloc (1, sizeof **context);
+ if (!*context)
+ return gpg_error_from_errno (errno);
+
+ /* FIXME; we might want to look at the Base SN to try matching
+ servers first. */
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+
+ for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter);
+ ldapserver_iter_next (&iter))
+ {
+ ldap_server_t server = iter.server;
+
+ err = run_ldap_wrapper (ctrl,
+ 0,
+ 1,
+ opt.ldap_proxy,
+ server->host, server->port,
+ server->user, server->pass,
+ dn, "objectClass=*", attr, NULL,
+ &(*context)->reader);
+ if (!err)
+ break; /* Probably found a result. */
+ }
+
+ if (err)
+ {
+ xfree (*context);
+ *context = NULL;
+ }
+ return err;
+}
+
+
+/* Prepare an LDAP query to return certificates maching PATTERNS using
+ the SERVER. This function returns an error code or 0 and a CONTEXT
+ on success. */
+gpg_error_t
+start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context,
+ strlist_t patterns, const ldap_server_t server)
+{
+ gpg_error_t err;
+ const char *host;
+ int port;
+ const char *user;
+ const char *pass;
+ const char *base;
+ const char *argv[50];
+ int argc;
+ char portbuf[30], timeoutbuf[30];
+
+
+ *context = NULL;
+ if (server)
+ {
+ host = server->host;
+ port = server->port;
+ user = server->user;
+ pass = server->pass;
+ base = server->base;
+ }
+ else /* Use a default server. */
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ if (!base)
+ base = "";
+
+ argc = 0;
+ if (pass) /* Note: Must be the first item. */
+ {
+ argv[argc++] = "--pass";
+ argv[argc++] = pass;
+ }
+ if (opt.verbose)
+ argv[argc++] = "-vv";
+ argv[argc++] = "--log-with-pid";
+ argv[argc++] = "--multi";
+ if (opt.ldaptimeout)
+ {
+ sprintf (timeoutbuf, "%u", opt.ldaptimeout);
+ argv[argc++] = "--timeout";
+ argv[argc++] = timeoutbuf;
+ }
+ if (opt.ldap_proxy)
+ {
+ argv[argc++] = "--proxy";
+ argv[argc++] = opt.ldap_proxy;
+ }
+ if (host)
+ {
+ argv[argc++] = "--host";
+ argv[argc++] = host;
+ }
+ if (port)
+ {
+ sprintf (portbuf, "%d", port);
+ argv[argc++] = "--port";
+ argv[argc++] = portbuf;
+ }
+ if (user)
+ {
+ argv[argc++] = "--user";
+ argv[argc++] = user;
+ }
+
+
+ for (; patterns; patterns = patterns->next)
+ {
+ strlist_t sl;
+ char *url;
+
+ if (argc >= sizeof argv -1)
+ {
+ /* Too many patterns. It does not make sense to allow an
+ arbitrary number of patters because the length of the
+ command line is limited anyway. */
+ /* fixme: cleanup. */
+ return gpg_error (GPG_ERR_RESOURCE_LIMIT);
+ }
+ sl = parse_one_pattern (patterns->d);
+ if (!sl)
+ {
+ log_error (_("start_cert_fetch: invalid pattern '%s'\n"),
+ patterns->d);
+ /* fixme: cleanup argv. */
+ return gpg_error (GPG_ERR_INV_USER_ID);
+ }
+ if ((sl->flags & 1))
+ err = make_url (&url, sl->d, "objectClass=*");
+ else
+ err = make_url (&url, base, sl->d);
+ free_strlist (sl);
+ if (err)
+ {
+ /* fixme: cleanup argv. */
+ return err;
+ }
+ argv[argc++] = url;
+ }
+ argv[argc] = NULL;
+
+ *context = xtrycalloc (1, sizeof **context);
+ if (!*context)
+ return gpg_error_from_errno (errno);
+
+ err = ldap_wrapper (ctrl, &(*context)->reader, argv);
+
+ if (err)
+ {
+ xfree (*context);
+ *context = NULL;
+ }
+
+ return err;
+}
+
+
+/* Read a fixed amount of data from READER into BUFFER. */
+static gpg_error_t
+read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
+{
+ gpg_error_t err;
+ size_t nread;
+
+ while (count)
+ {
+ err = ksba_reader_read (reader, buffer, count, &nread);
+ if (err)
+ return err;
+ buffer += nread;
+ count -= nread;
+ }
+ return 0;
+}
+
+
+/* Fetch the next certificate. Return 0 on success, GPG_ERR_EOF if no
+ (more) certificates are available or any other error
+ code. GPG_ERR_TRUNCATED may be returned to indicate that the result
+ has been truncated. */
+gpg_error_t
+fetch_next_cert_ldap (cert_fetch_context_t context,
+ unsigned char **value, size_t *valuelen)
+{
+ gpg_error_t err;
+ unsigned char hdr[5];
+ char *p, *pend;
+ int n;
+ int okay = 0;
+ /* int is_cms = 0; */
+
+ *value = NULL;
+ *valuelen = 0;
+
+ err = 0;
+ while (!err)
+ {
+ err = read_buffer (context->reader, hdr, 5);
+ if (err)
+ break;
+ n = (hdr[1] << 24)|(hdr[2]<<16)|(hdr[3]<<8)|hdr[4];
+ if (*hdr == 'V' && okay)
+ {
+#if 0 /* That code is not yet ready. */
+
+ if (is_cms)
+ {
+ /* The certificate needs to be parsed from CMS data. */
+ ksba_cms_t cms;
+ ksba_stop_reason_t stopreason;
+ int i;
+
+ err = ksba_cms_new (&cms);
+ if (err)
+ goto leave;
+ err = ksba_cms_set_reader_writer (cms, context->reader, NULL);
+ if (err)
+ {
+ log_error ("ksba_cms_set_reader_writer failed: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+
+ do
+ {
+ err = ksba_cms_parse (cms, &stopreason);
+ if (err)
+ {
+ log_error ("ksba_cms_parse failed: %s\n",
+ gpg_strerror (err));
+ goto leave;
+ }
+
+ if (stopreason == KSBA_SR_BEGIN_DATA)
+ log_error ("userSMIMECertificate is not "
+ "a certs-only message\n");
+ }
+ while (stopreason != KSBA_SR_READY);
+
+ for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++)
+ {
+ check_and_store (ctrl, stats, cert, 0);
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ if (!i)
+ log_error ("no certificate found\n");
+ else
+ any = 1;
+ }
+ else
+#endif
+ {
+ *value = xtrymalloc (n);
+ if (!*value)
+ return gpg_error_from_errno (errno);
+ *valuelen = n;
+ err = read_buffer (context->reader, *value, n);
+ break; /* Ready or error. */
+ }
+ }
+ else if (!n && *hdr == 'A')
+ okay = 0;
+ else if (n)
+ {
+ if (n > context->tmpbufsize)
+ {
+ xfree (context->tmpbuf);
+ context->tmpbufsize = 0;
+ context->tmpbuf = xtrymalloc (n+1);
+ if (!context->tmpbuf)
+ return gpg_error_from_errno (errno);
+ context->tmpbufsize = n;
+ }
+ err = read_buffer (context->reader, context->tmpbuf, n);
+ if (err)
+ break;
+ if (*hdr == 'A')
+ {
+ p = context->tmpbuf;
+ p[n] = 0; /*(we allocated one extra byte for this.)*/
+ /* fixme: is_cms = 0; */
+ if ( (pend = strchr (p, ';')) )
+ *pend = 0; /* Strip off the extension. */
+ if (!ascii_strcasecmp (p, USERCERTIFICATE))
+ {
+ if (DBG_LOOKUP)
+ log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
+ USERCERTIFICATE);
+ okay = 1;
+ }
+ else if (!ascii_strcasecmp (p, CACERTIFICATE))
+ {
+ if (DBG_LOOKUP)
+ log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
+ CACERTIFICATE);
+ okay = 1;
+ }
+ else if (!ascii_strcasecmp (p, X509CACERT))
+ {
+ if (DBG_LOOKUP)
+ log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
+ CACERTIFICATE);
+ okay = 1;
+ }
+/* else if (!ascii_strcasecmp (p, USERSMIMECERTIFICATE)) */
+/* { */
+/* if (DBG_LOOKUP) */
+/* log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", */
+/* USERSMIMECERTIFICATE); */
+/* okay = 1; */
+/* is_cms = 1; */
+/* } */
+ else
+ {
+ if (DBG_LOOKUP)
+ log_debug ("fetch_next_cert_ldap: got attribute '%s'"
+ " - ignored\n", p);
+ okay = 0;
+ }
+ }
+ else if (*hdr == 'E')
+ {
+ p = context->tmpbuf;
+ p[n] = 0; /*(we allocated one extra byte for this.)*/
+ if (!strcmp (p, "truncated"))
+ {
+ context->truncated = 1;
+ log_info (_("ldap_search hit the size limit of"
+ " the server\n"));
+ }
+ }
+ }
+ }
+
+ if (err)
+ {
+ xfree (*value);
+ *value = NULL;
+ *valuelen = 0;
+ if (gpg_err_code (err) == GPG_ERR_EOF && context->truncated)
+ {
+ context->truncated = 0; /* So that the next call would return EOF. */
+ err = gpg_error (GPG_ERR_TRUNCATED);
+ }
+ }
+
+ return err;
+}
+
+
+void
+end_cert_fetch_ldap (cert_fetch_context_t context)
+{
+ if (context)
+ {
+ ksba_reader_t reader = context->reader;
+
+ xfree (context->tmpbuf);
+ xfree (context);
+ ldap_wrapper_release_context (reader);
+ ksba_reader_release (reader);
+ }
+}
diff --git a/dirmngr/ldapserver.c b/dirmngr/ldapserver.c
new file mode 100644
index 0000000..20a574c
--- /dev/null
+++ b/dirmngr/ldapserver.c
@@ -0,0 +1,131 @@
+/* dirmngr.c - LDAP access
+ Copyright (C) 2008 g10 Code GmbH
+
+ This file is part of DirMngr.
+
+ DirMngr is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DirMngr is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ 02110-1301, USA. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "dirmngr.h"
+#include "ldapserver.h"
+
+
+/* Release the list of SERVERS. As usual it is okay to call this
+ function with SERVERS passed as NULL. */
+void
+ldapserver_list_free (ldap_server_t servers)
+{
+ while (servers)
+ {
+ ldap_server_t tmp = servers->next;
+ xfree (servers->host);
+ xfree (servers->user);
+ if (servers->pass)
+ memset (servers->pass, 0, strlen (servers->pass));
+ xfree (servers->pass);
+ xfree (servers->base);
+ xfree (servers);
+ servers = tmp;
+ }
+}
+
+
+/* Parse a single LDAP server configuration line. Returns the server
+ or NULL in case of errors. The configuration lineis assumed to be
+ colon seprated with these fields:
+
+ 1. field: Hostname
+ 2. field: Portnumber
+ 3. field: Username
+ 4. field: Password
+ 5. field: Base DN
+
+ FILENAME and LINENO are used for diagnostic purposes only.
+*/
+ldap_server_t
+ldapserver_parse_one (char *line,
+ const char *filename, unsigned int lineno)
+{
+ char *p;
+ char *endp;
+ ldap_server_t server;
+ int fieldno;
+ int fail = 0;
+
+ /* Parse the colon separated fields. */
+ server = xcalloc (1, sizeof *server);
+ for (fieldno = 1, p = line; p; p = endp, fieldno++ )
+ {
+ endp = strchr (p, ':');
+ if (endp)
+ *endp++ = '\0';
+ trim_spaces (p);
+ switch (fieldno)
+ {
+ case 1:
+ if (*p)
+ server->host = xstrdup (p);
+ else
+ {
+ log_error (_("%s:%u: no hostname given\n"),
+ filename, lineno);
+ fail = 1;
+ }
+ break;
+
+ case 2:
+ if (*p)
+ server->port = atoi (p);
+ break;
+
+ case 3:
+ if (*p)
+ server->user = xstrdup (p);
+ break;
+
+ case 4:
+ if (*p && !server->user)
+ {
+ log_error (_("%s:%u: password given without user\n"),
+ filename, lineno);
+ fail = 1;
+ }
+ else if (*p)
+ server->pass = xstrdup (p);
+ break;
+
+ case 5:
+ if (*p)
+ server->base = xstrdup (p);
+ break;
+
+ default:
+ /* (We silently ignore extra fields.) */
+ break;
+ }
+ }
+
+ if (fail)
+ {
+ log_info (_("%s:%u: skipping this line\n"), filename, lineno);
+ ldapserver_list_free (server);
+ }
+
+ return server;
+}
diff --git a/dirmngr/ldapserver.h b/dirmngr/ldapserver.h
new file mode 100644
index 0000000..8056e67
--- /dev/null
+++ b/dirmngr/ldapserver.h
@@ -0,0 +1,90 @@
+/* ldapserver.h
+ Copyright (C) 2008 g10 Code GmbH
+
+ This file is part of DirMngr.
+
+ DirMngr is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ DirMngr is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef LDAPSERVER_H
+#define LDAPSERVER_H
+
+#include "dirmngr.h"
+
+/* Release the list of SERVERS. As usual it is okay to call this
+ function with SERVERS passed as NULL. */
+void ldapserver_list_free (ldap_server_t servers);
+
+
+/* Parse a single LDAP server configuration line. Returns the server
+ or NULL in case of errors. The configuration line is assumed to be
+ colon separated with these fields:
+
+ 1. field: Hostname
+ 2. field: Portnumber
+ 3. field: Username
+ 4. field: Password
+ 5. field: Base DN
+
+ FILENAME and LINENO are used for diagnostic purposes only.
+*/
+ldap_server_t ldapserver_parse_one (char *line,
+ const char *filename, unsigned int lineno);
+
+
+/* Iterate over all servers. */
+
+struct ldapserver_iter
+{
+ ctrl_t ctrl;
+ enum { LDAPSERVER_SESSION, LDAPSERVER_OPT } group;
+ ldap_server_t server;
+};
+
+
+static inline void
+ldapserver_iter_next (struct ldapserver_iter *iter)
+{
+ if (iter->server)
+ iter->server = iter->server->next;
+
+ if (! iter->server)
+ {
+ if (iter->group == LDAPSERVER_SESSION)
+ {
+ iter->group = LDAPSERVER_OPT;
+ iter->server = opt.ldapservers;
+ }
+ }
+}
+
+
+static inline int
+ldapserver_iter_end_p (struct ldapserver_iter *iter)
+{
+ return (iter->group == LDAPSERVER_OPT && iter->server == NULL);
+}
+
+
+static inline void
+ldapserver_iter_begin (struct ldapserver_iter *iter, ctrl_t ctrl)
+{
+ iter->ctrl = ctrl;
+ iter->group = LDAPSERVER_SESSION;
+ iter->server = get_ldapservers_from_ctrl (ctrl);
+
+ while (iter->server == NULL && ! ldapserver_iter_end_p (iter))
+ ldapserver_iter_next (iter);
+}
+
+#endif /* LDAPSERVER_H */
diff --git a/dirmngr/misc.c b/dirmngr/misc.c
new file mode 100644
index 0000000..25652a2
--- /dev/null
+++ b/dirmngr/misc.c
@@ -0,0 +1,564 @@
+/* misc.c - miscellaneous
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2002, 2003, 2004, 2010 Free Software Foundation, Inc.
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+
+#include "dirmngr.h"
+#include "util.h"
+#include "misc.h"
+
+
+/* Convert the hex encoded STRING back into binary and store the
+ result into the provided buffer RESULT. The actual size of that
+ buffer will be returned. The caller should provide RESULT of at
+ least strlen(STRING)/2 bytes. There is no error detection, the
+ parsing stops at the first non hex character. With RESULT given as
+ NULL, the fucntion does only return the size of the buffer which
+ would be needed. */
+size_t
+unhexify (unsigned char *result, const char *string)
+{
+ const char *s;
+ size_t n;
+
+ for (s=string,n=0; hexdigitp (s) && hexdigitp(s+1); s += 2)
+ {
+ if (result)
+ result[n] = xtoi_2 (s);
+ n++;
+ }
+ return n;
+}
+
+
+char*
+hashify_data( const char* data, size_t len )
+{
+ unsigned char buf[20];
+ gcry_md_hash_buffer (GCRY_MD_SHA1, buf, data, len);
+ return hexify_data( buf, 20 );
+}
+
+char*
+hexify_data( const unsigned char* data, size_t len )
+{
+ int i;
+ char* result = xmalloc( sizeof( char ) * (2*len+1));
+
+ for( i = 0; i < 2*len; i+=2 )
+ sprintf( result+i, "%02X", *data++);
+ return result;
+}
+
+char *
+serial_hex (ksba_sexp_t serial )
+{
+ unsigned char* p = serial;
+ char *endp;
+ unsigned long n;
+ char *certid;
+
+ if (!p)
+ return NULL;
+ else {
+ p++; /* ignore initial '(' */
+ n = strtoul (p, (char**)&endp, 10);
+ p = endp;
+ if (*p!=':')
+ return NULL;
+ else {
+ int i = 0;
+ certid = xmalloc( sizeof( char )*(2*n + 1 ) );
+ for (p++; n; n--, p++) {
+ sprintf ( certid+i , "%02X", *p);
+ i += 2;
+ }
+ }
+ }
+ return certid;
+}
+
+
+/* Take an S-Expression encoded blob and return a pointer to the
+ actual data as well as its length. Return NULL for an invalid
+ S-Expression.*/
+const unsigned char *
+serial_to_buffer (const ksba_sexp_t serial, size_t *length)
+{
+ unsigned char *p = serial;
+ char *endp;
+ unsigned long n;
+
+ if (!p || *p != '(')
+ return NULL;
+ p++;
+ n = strtoul (p, &endp, 10);
+ p = endp;
+ if (*p != ':')
+ return NULL;
+ p++;
+ *length = n;
+ return p;
+}
+
+
+/* Do an in-place percent unescaping of STRING. Returns STRING. Noet
+ that this function does not do a '+'-to-space unescaping.*/
+char *
+unpercent_string (char *string)
+{
+ char *s = string;
+ char *d = string;
+
+ while (*s)
+ {
+ if (*s == '%' && s[1] && s[2])
+ {
+ s++;
+ *d++ = xtoi_2 ( s);
+ s += 2;
+ }
+ else
+ *d++ = *s++;
+ }
+ *d = 0;
+ return string;
+}
+
+/* Convert a canonical encoded S-expression in CANON into the GCRY
+ type. */
+gpg_error_t
+canon_sexp_to_gcry (const unsigned char *canon, gcry_sexp_t *r_sexp)
+{
+ gpg_error_t err;
+ size_t n;
+ gcry_sexp_t sexp;
+
+ *r_sexp = NULL;
+ n = gcry_sexp_canon_len (canon, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error (_("invalid canonical S-expression found\n"));
+ err = gpg_error (GPG_ERR_INV_SEXP);
+ }
+ else if ((err = gcry_sexp_sscan (&sexp, NULL, canon, n)))
+ log_error (_("converting S-expression failed: %s\n"), gcry_strerror (err));
+ else
+ *r_sexp = sexp;
+ return err;
+}
+
+
+/* Return an allocated buffer with the formatted fingerprint as one
+ large hexnumber */
+char *
+get_fingerprint_hexstring (ksba_cert_t cert)
+{
+ unsigned char digest[20];
+ gcry_md_hd_t md;
+ int rc;
+ char *buf;
+ int i;
+
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+ if (rc)
+ log_fatal (_("gcry_md_open failed: %s\n"), gpg_strerror (rc));
+
+ rc = ksba_cert_hash (cert, 0, HASH_FNC, md);
+ if (rc)
+ {
+ log_error (_("oops: ksba_cert_hash failed: %s\n"), gpg_strerror (rc));
+ memset (digest, 0xff, 20); /* Use a dummy value. */
+ }
+ else
+ {
+ gcry_md_final (md);
+ memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20);
+ }
+ gcry_md_close (md);
+ buf = xmalloc (41);
+ *buf = 0;
+ for (i=0; i < 20; i++ )
+ sprintf (buf+strlen(buf), "%02X", digest[i]);
+ return buf;
+}
+
+/* Return an allocated buffer with the formatted fingerprint as one
+ large hexnumber. This version inserts the usual colons. */
+char *
+get_fingerprint_hexstring_colon (ksba_cert_t cert)
+{
+ unsigned char digest[20];
+ gcry_md_hd_t md;
+ int rc;
+ char *buf;
+ int i;
+
+ rc = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+ if (rc)
+ log_fatal (_("gcry_md_open failed: %s\n"), gpg_strerror (rc));
+
+ rc = ksba_cert_hash (cert, 0, HASH_FNC, md);
+ if (rc)
+ {
+ log_error (_("oops: ksba_cert_hash failed: %s\n"), gpg_strerror (rc));
+ memset (digest, 0xff, 20); /* Use a dummy value. */
+ }
+ else
+ {
+ gcry_md_final (md);
+ memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20);
+ }
+ gcry_md_close (md);
+ buf = xmalloc (61);
+ *buf = 0;
+ for (i=0; i < 20; i++ )
+ sprintf (buf+strlen(buf), "%02X:", digest[i]);
+ buf[strlen(buf)-1] = 0; /* Remove railing colon. */
+ return buf;
+}
+
+
+/* Dump the serial number SERIALNO to the log stream. */
+void
+dump_serial (ksba_sexp_t serialno)
+{
+ char *p;
+
+ p = serial_hex (serialno);
+ log_printf ("%s", p?p:"?");
+ xfree (p);
+}
+
+
+/* Dump STRING to the log file but choose the best readable
+ format. */
+void
+dump_string (const char *string)
+{
+
+ if (!string)
+ log_printf ("[error]");
+ else
+ {
+ const unsigned char *s;
+
+ for (s=string; *s; s++)
+ {
+ if (*s < ' ' || (*s >= 0x7f && *s <= 0xa0))
+ break;
+ }
+ if (!*s && *string != '[')
+ log_printf ("%s", string);
+ else
+ {
+ log_printf ( "[ ");
+ log_printhex (NULL, string, strlen (string));
+ log_printf ( " ]");
+ }
+ }
+}
+
+/* Dump an KSBA cert object to the log stream. Prefix the output with
+ TEXT. This is used for debugging. */
+void
+dump_cert (const char *text, ksba_cert_t cert)
+{
+ ksba_sexp_t sexp;
+ char *p;
+ ksba_isotime_t t;
+
+ log_debug ("BEGIN Certificate '%s':\n", text? text:"");
+ if (cert)
+ {
+ sexp = ksba_cert_get_serial (cert);
+ p = serial_hex (sexp);
+ log_debug (" serial: %s\n", p?p:"?");
+ xfree (p);
+ ksba_free (sexp);
+
+ ksba_cert_get_validity (cert, 0, t);
+ log_debug (" notBefore: ");
+ dump_isotime (t);
+ log_printf ("\n");
+ ksba_cert_get_validity (cert, 1, t);
+ log_debug (" notAfter: ");
+ dump_isotime (t);
+ log_printf ("\n");
+
+ p = ksba_cert_get_issuer (cert, 0);
+ log_debug (" issuer: ");
+ dump_string (p);
+ ksba_free (p);
+ log_printf ("\n");
+
+ p = ksba_cert_get_subject (cert, 0);
+ log_debug (" subject: ");
+ dump_string (p);
+ ksba_free (p);
+ log_printf ("\n");
+
+ log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert));
+
+ p = get_fingerprint_hexstring (cert);
+ log_debug (" SHA1 fingerprint: %s\n", p);
+ xfree (p);
+ }
+ log_debug ("END Certificate\n");
+}
+
+
+
+/* Log the certificate's name in "#SN/ISSUERDN" format along with
+ TEXT. */
+void
+cert_log_name (const char *text, ksba_cert_t cert)
+{
+ log_info ("%s", text? text:"certificate" );
+ if (cert)
+ {
+ ksba_sexp_t sn;
+ char *p;
+
+ p = ksba_cert_get_issuer (cert, 0);
+ sn = ksba_cert_get_serial (cert);
+ if (p && sn)
+ {
+ log_printf (" #");
+ dump_serial (sn);
+ log_printf ("/");
+ dump_string (p);
+ }
+ else
+ log_printf (" [invalid]");
+ ksba_free (sn);
+ xfree (p);
+ }
+ log_printf ("\n");
+}
+
+
+/* Log the certificate's subject DN along with TEXT. */
+void
+cert_log_subject (const char *text, ksba_cert_t cert)
+{
+ log_info ("%s", text? text:"subject" );
+ if (cert)
+ {
+ char *p;
+
+ p = ksba_cert_get_subject (cert, 0);
+ if (p)
+ {
+ log_printf (" /");
+ dump_string (p);
+ xfree (p);
+ }
+ else
+ log_printf (" [invalid]");
+ }
+ log_printf ("\n");
+}
+
+
+/* Callback to print infos about the TLS certificates. */
+void
+cert_log_cb (http_session_t sess, gpg_error_t err,
+ const char *hostname, const void **certs, size_t *certlens)
+{
+ ksba_cert_t cert;
+ size_t n;
+
+ (void)sess;
+
+ if (!err)
+ return; /* No error - no need to log anything */
+
+ log_debug ("expected hostname: %s\n", hostname);
+ for (n=0; certs[n]; n++)
+ {
+ err = ksba_cert_new (&cert);
+ if (!err)
+ err = ksba_cert_init_from_mem (cert, certs[n], certlens[n]);
+ if (err)
+ log_error ("error parsing cert for logging: %s\n", gpg_strerror (err));
+ else
+ {
+ char textbuf[20];
+ snprintf (textbuf, sizeof textbuf, "server[%u]", (unsigned int)n);
+ dump_cert (textbuf, cert);
+ }
+
+ ksba_cert_release (cert);
+ }
+}
+
+
+/****************
+ * Remove all %xx escapes; this is done inplace.
+ * Returns: New length of the string.
+ */
+static int
+remove_percent_escapes (unsigned char *string)
+{
+ int n = 0;
+ unsigned char *p, *s;
+
+ for (p = s = string; *s; s++)
+ {
+ if (*s == '%')
+ {
+ if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2))
+ {
+ s++;
+ *p = xtoi_2 (s);
+ s++;
+ p++;
+ n++;
+ }
+ else
+ {
+ *p++ = *s++;
+ if (*s)
+ *p++ = *s++;
+ if (*s)
+ *p++ = *s++;
+ if (*s)
+ *p = 0;
+ return -1; /* Bad URI. */
+ }
+ }
+ else
+ {
+ *p++ = *s;
+ n++;
+ }
+ }
+ *p = 0; /* Always keep a string terminator. */
+ return n;
+}
+
+
+/* Return the host name and the port (0 if none was given) from the
+ URL. Return NULL on error or if host is not included in the
+ URL. */
+char *
+host_and_port_from_url (const char *url, int *port)
+{
+ const char *s, *s2;
+ char *buf, *p;
+ int n;
+
+ s = url;
+
+ *port = 0;
+
+ /* Find the scheme */
+ if ( !(s2 = strchr (s, ':')) || s2 == s )
+ return NULL; /* No scheme given. */
+ s = s2+1;
+
+ /* Find the hostname */
+ if (*s != '/')
+ return NULL; /* Does not start with a slash. */
+
+ s++;
+ if (*s != '/')
+ return NULL; /* No host name. */
+ s++;
+
+ buf = xtrystrdup (s);
+ if (!buf)
+ {
+ log_error (_("malloc failed: %s\n"), strerror (errno));
+ return NULL;
+ }
+ if ((p = strchr (buf, '/')))
+ *p++ = 0;
+ strlwr (buf);
+ if ((p = strchr (p, ':')))
+ {
+ *p++ = 0;
+ *port = atoi (p);
+ }
+
+ /* Remove quotes and make sure that no Nul has been encoded. */
+ if ((n = remove_percent_escapes (buf)) < 0
+ || n != strlen (buf) )
+ {
+ log_error (_("bad URL encoding detected\n"));
+ xfree (buf);
+ return NULL;
+ }
+
+ return buf;
+}
+
+
+/* A KSBA reader callback to read from an estream. */
+static int
+my_estream_ksba_reader_cb (void *cb_value, char *buffer, size_t count,
+ size_t *r_nread)
+{
+ estream_t fp = cb_value;
+
+ if (!fp)
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+ if (!buffer && !count && !r_nread)
+ {
+ es_rewind (fp);
+ return 0;
+ }
+
+ *r_nread = es_fread (buffer, 1, count, fp);
+ if (!*r_nread)
+ return -1; /* EOF or error. */
+ return 0; /* Success. */
+}
+
+
+/* Create a KSBA reader object and connect it to the estream FP. */
+gpg_error_t
+create_estream_ksba_reader (ksba_reader_t *r_reader, estream_t fp)
+{
+ gpg_error_t err;
+ ksba_reader_t reader;
+
+ *r_reader = NULL;
+ err = ksba_reader_new (&reader);
+ if (!err)
+ err = ksba_reader_set_cb (reader, my_estream_ksba_reader_cb, fp);
+ if (err)
+ {
+ log_error (_("error initializing reader object: %s\n"),
+ gpg_strerror (err));
+ ksba_reader_release (reader);
+ return err;
+ }
+ *r_reader = reader;
+ return 0;
+}
diff --git a/dirmngr/misc.h b/dirmngr/misc.h
new file mode 100644
index 0000000..2dc2985
--- /dev/null
+++ b/dirmngr/misc.h
@@ -0,0 +1,85 @@
+/* misc.h - miscellaneous
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef MISC_H
+#define MISC_H
+
+/* Convert hex encoded string back to binary. */
+size_t unhexify (unsigned char *result, const char *string);
+
+/* Returns SHA1 hash of the data. */
+char* hashify_data( const char* data, size_t len );
+
+/* Returns data as a hex string. */
+char* hexify_data( const unsigned char* data, size_t len );
+
+/* Returns the serial number as a hex string. */
+char* serial_hex ( ksba_sexp_t serial );
+
+/* Take an S-Expression encoded blob and return a pointer to the
+ actual data as well as its length. */
+const unsigned char *serial_to_buffer (const ksba_sexp_t serial,
+ size_t *length);
+
+/* Do an in-place percent unescaping of STRING. Returns STRING. */
+char *unpercent_string (char *string);
+
+gpg_error_t canon_sexp_to_gcry (const unsigned char *canon,
+ gcry_sexp_t *r_sexp);
+
+/* Return an allocated hex-string with the SHA-1 fingerprint of
+ CERT. */
+char *get_fingerprint_hexstring (ksba_cert_t cert);
+/* Return an allocated hex-string with the SHA-1 fingerprint of
+ CERT. This version inserts the usual colons. */
+char *get_fingerprint_hexstring_colon (ksba_cert_t cert);
+
+/* Log CERT in short format with s/n and issuer DN prefixed by TEXT. */
+void cert_log_name (const char *text, ksba_cert_t cert);
+
+/* Log CERT in short format with the subject DN prefixed by TEXT. */
+void cert_log_subject (const char *text, ksba_cert_t cert);
+
+/* Dump the serial number SERIALNO to the log stream. */
+void dump_serial (ksba_sexp_t serialno);
+
+/* Dump STRING to the log file but choose the best readable
+ format. */
+void dump_string (const char *string);
+
+/* Dump an KSBA cert object to the log stream. Prefix the output with
+ TEXT. This is used for debugging. */
+void dump_cert (const char *text, ksba_cert_t cert);
+
+/* Callback to print infos about the TLS certificates. */
+void cert_log_cb (http_session_t sess, gpg_error_t err,
+ const char *hostname, const void **certs, size_t *certlens);
+
+/* Return the host name and the port (0 if none was given) from the
+ URL. Return NULL on error or if host is not included in the
+ URL. */
+char *host_and_port_from_url (const char *url, int *port);
+
+/* Create a KSBA reader object and connect it to the estream FP. */
+gpg_error_t create_estream_ksba_reader (ksba_reader_t *r_reader, estream_t fp);
+
+
+
+#endif /* MISC_H */
diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c
new file mode 100644
index 0000000..f8c437d
--- /dev/null
+++ b/dirmngr/ocsp.c
@@ -0,0 +1,796 @@
+/* ocsp.c - OCSP management
+ * Copyright (C) 2004, 2007 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "dirmngr.h"
+#include "misc.h"
+#include "http.h"
+#include "validate.h"
+#include "certcache.h"
+#include "ocsp.h"
+
+/* The maximum size we allow as a response from an OCSP reponder. */
+#define MAX_RESPONSE_SIZE 65536
+
+
+static const char oidstr_ocsp[] = "1.3.6.1.5.5.7.48.1";
+
+
+/* Telesec attribute used to implement a positive confirmation.
+
+ CertHash ::= SEQUENCE {
+ HashAlgorithm AlgorithmIdentifier,
+ certificateHash OCTET STRING }
+ */
+static const char oidstr_certHash[] = "1.3.36.8.3.13";
+
+
+
+
+/* Read from FP and return a newly allocated buffer in R_BUFFER with the
+ entire data read from FP. */
+static gpg_error_t
+read_response (estream_t fp, unsigned char **r_buffer, size_t *r_buflen)
+{
+ gpg_error_t err;
+ unsigned char *buffer;
+ size_t bufsize, nbytes;
+
+ *r_buffer = NULL;
+ *r_buflen = 0;
+
+ bufsize = 4096;
+ buffer = xtrymalloc (bufsize);
+ if (!buffer)
+ return gpg_error_from_errno (errno);
+
+ nbytes = 0;
+ for (;;)
+ {
+ unsigned char *tmp;
+ size_t nread = 0;
+
+ assert (nbytes < bufsize);
+ nread = es_fread (buffer+nbytes, 1, bufsize-nbytes, fp);
+ if (nread < bufsize-nbytes && es_ferror (fp))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error reading from responder: %s\n"),
+ strerror (errno));
+ xfree (buffer);
+ return err;
+ }
+ if ( !(nread == bufsize-nbytes && !es_feof (fp)))
+ { /* Response succesfully received. */
+ nbytes += nread;
+ *r_buffer = buffer;
+ *r_buflen = nbytes;
+ return 0;
+ }
+
+ nbytes += nread;
+
+ /* Need to enlarge the buffer. */
+ if (bufsize >= MAX_RESPONSE_SIZE)
+ {
+ log_error (_("response from server too large; limit is %d bytes\n"),
+ MAX_RESPONSE_SIZE);
+ xfree (buffer);
+ return gpg_error (GPG_ERR_TOO_LARGE);
+ }
+
+ bufsize += 4096;
+ tmp = xtryrealloc (buffer, bufsize);
+ if (!tmp)
+ {
+ err = gpg_error_from_errno (errno);
+ xfree (buffer);
+ return err;
+ }
+ buffer = tmp;
+ }
+}
+
+
+/* Construct an OCSP request, send it to the configured OCSP responder
+ and parse the response. On success the OCSP context may be used to
+ further process the reponse. */
+static gpg_error_t
+do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, gcry_md_hd_t md,
+ const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert)
+{
+ gpg_error_t err;
+ unsigned char *request, *response;
+ size_t requestlen, responselen;
+ http_t http;
+ ksba_ocsp_response_status_t response_status;
+ const char *t;
+ int redirects_left = 2;
+ char *free_this = NULL;
+
+ (void)ctrl;
+
+ if (opt.disable_http)
+ {
+ log_error (_("OCSP request not possible due to disabled HTTP\n"));
+ return gpg_error (GPG_ERR_NOT_SUPPORTED);
+ }
+
+ err = ksba_ocsp_add_target (ocsp, cert, issuer_cert);
+ if (err)
+ {
+ log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err));
+ return err;
+ }
+
+ {
+ size_t n;
+ unsigned char nonce[32];
+
+ n = ksba_ocsp_set_nonce (ocsp, NULL, 0);
+ if (n > sizeof nonce)
+ n = sizeof nonce;
+ gcry_create_nonce (nonce, n);
+ ksba_ocsp_set_nonce (ocsp, nonce, n);
+ }
+
+ err = ksba_ocsp_build_request (ocsp, &request, &requestlen);
+ if (err)
+ {
+ log_error (_("error building OCSP request: %s\n"), gpg_strerror (err));
+ return err;
+ }
+
+ once_more:
+ err = http_open (&http, HTTP_REQ_POST, url, NULL, NULL,
+ (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0),
+ opt.http_proxy, NULL, NULL, NULL);
+ if (err)
+ {
+ log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err));
+ xfree (free_this);
+ return err;
+ }
+
+ es_fprintf (http_get_write_ptr (http),
+ "Content-Type: application/ocsp-request\r\n"
+ "Content-Length: %lu\r\n",
+ (unsigned long)requestlen );
+ http_start_data (http);
+ if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1)
+ {
+ err = gpg_error_from_errno (errno);
+ log_error ("error sending request to '%s': %s\n", url, strerror (errno));
+ http_close (http, 0);
+ xfree (request);
+ xfree (free_this);
+ return err;
+ }
+ xfree (request);
+ request = NULL;
+
+ err = http_wait_response (http);
+ if (err || http_get_status_code (http) != 200)
+ {
+ if (err)
+ log_error (_("error reading HTTP response for '%s': %s\n"),
+ url, gpg_strerror (err));
+ else
+ {
+ switch (http_get_status_code (http))
+ {
+ case 301:
+ case 302:
+ {
+ const char *s = http_get_header (http, "Location");
+
+ log_info (_("URL '%s' redirected to '%s' (%u)\n"),
+ url, s?s:"[none]", http_get_status_code (http));
+ if (s && *s && redirects_left-- )
+ {
+ xfree (free_this); url = NULL;
+ free_this = xtrystrdup (s);
+ if (!free_this)
+ err = gpg_error_from_errno (errno);
+ else
+ {
+ url = free_this;
+ http_close (http, 0);
+ goto once_more;
+ }
+ }
+ else
+ err = gpg_error (GPG_ERR_NO_DATA);
+ log_error (_("too many redirections\n"));
+ }
+ break;
+
+ default:
+ log_error (_("error accessing '%s': http status %u\n"),
+ url, http_get_status_code (http));
+ err = gpg_error (GPG_ERR_NO_DATA);
+ break;
+ }
+ }
+ http_close (http, 0);
+ xfree (free_this);
+ return err;
+ }
+
+ err = read_response (http_get_read_ptr (http), &response, &responselen);
+ http_close (http, 0);
+ if (err)
+ {
+ log_error (_("error reading HTTP response for '%s': %s\n"),
+ url, gpg_strerror (err));
+ xfree (free_this);
+ return err;
+ }
+
+ err = ksba_ocsp_parse_response (ocsp, response, responselen,
+ &response_status);
+ if (err)
+ {
+ log_error (_("error parsing OCSP response for '%s': %s\n"),
+ url, gpg_strerror (err));
+ xfree (response);
+ xfree (free_this);
+ return err;
+ }
+
+ switch (response_status)
+ {
+ case KSBA_OCSP_RSPSTATUS_SUCCESS: t = "success"; break;
+ case KSBA_OCSP_RSPSTATUS_MALFORMED: t = "malformed"; break;
+ case KSBA_OCSP_RSPSTATUS_INTERNAL: t = "internal error"; break;
+ case KSBA_OCSP_RSPSTATUS_TRYLATER: t = "try later"; break;
+ case KSBA_OCSP_RSPSTATUS_SIGREQUIRED: t = "must sign request"; break;
+ case KSBA_OCSP_RSPSTATUS_UNAUTHORIZED: t = "unauthorized"; break;
+ case KSBA_OCSP_RSPSTATUS_REPLAYED: t = "replay detected"; break;
+ case KSBA_OCSP_RSPSTATUS_OTHER: t = "other (unknown)"; break;
+ case KSBA_OCSP_RSPSTATUS_NONE: t = "no status"; break;
+ default: t = "[unknown status]"; break;
+ }
+ if (response_status == KSBA_OCSP_RSPSTATUS_SUCCESS)
+ {
+ if (opt.verbose)
+ log_info (_("OCSP responder at '%s' status: %s\n"), url, t);
+
+ err = ksba_ocsp_hash_response (ocsp, response, responselen,
+ HASH_FNC, md);
+ if (err)
+ log_error (_("hashing the OCSP response for '%s' failed: %s\n"),
+ url, gpg_strerror (err));
+ }
+ else
+ {
+ log_error (_("OCSP responder at '%s' status: %s\n"), url, t);
+ err = gpg_error (GPG_ERR_GENERAL);
+ }
+
+ xfree (response);
+ xfree (free_this);
+ return err;
+}
+
+
+/* Validate that CERT is indeed valid to sign an OCSP response. If
+ SIGNER_FPR_LIST is not NULL we simply check that CERT matches one
+ of the fingerprints in this list. */
+static gpg_error_t
+validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert,
+ fingerprint_list_t signer_fpr_list)
+{
+ gpg_error_t err;
+ char *fpr;
+
+ if (signer_fpr_list)
+ {
+ fpr = get_fingerprint_hexstring (cert);
+ for (; signer_fpr_list && strcmp (signer_fpr_list->hexfpr, fpr);
+ signer_fpr_list = signer_fpr_list->next)
+ ;
+ if (signer_fpr_list)
+ err = 0;
+ else
+ {
+ log_error (_("not signed by a default OCSP signer's certificate"));
+ err = gpg_error (GPG_ERR_BAD_CA_CERT);
+ }
+ xfree (fpr);
+ }
+ else if (opt.system_daemon)
+ {
+ err = validate_cert_chain (ctrl, cert, NULL, VALIDATE_MODE_OCSP, NULL);
+ }
+ else
+ {
+ /* We avoid duplicating the entire certificate validation code
+ from gpgsm here. Because we have no way calling back to the
+ client and letting it compute the validity, we use the ugly
+ hack of telling the client that the response will only be
+ valid if the certificate given in this status message is
+ valid.
+
+ Note, that in theory we could simply ask the client via an
+ inquire to validate a certificate but this might involve
+ calling DirMngr again recursivly - we can't do that as of now
+ (neither DirMngr nor gpgsm have the ability for concurrent
+ access to DirMngr. */
+
+ /* FIXME: We should cache this certificate locally, so that the next
+ call to dirmngr won't need to look it up - if this works at
+ all. */
+ fpr = get_fingerprint_hexstring (cert);
+ dirmngr_status (ctrl, "ONLY_VALID_IF_CERT_VALID", fpr, NULL);
+ xfree (fpr);
+ err = 0;
+ }
+
+ return err;
+}
+
+
+/* Helper for check_signature. */
+static int
+check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig,
+ gcry_sexp_t s_hash, fingerprint_list_t signer_fpr_list)
+{
+ gpg_error_t err;
+ ksba_sexp_t pubkey;
+ gcry_sexp_t s_pkey = NULL;
+
+ pubkey = ksba_cert_get_public_key (cert);
+ if (!pubkey)
+ err = gpg_error (GPG_ERR_INV_OBJ);
+ else
+ err = canon_sexp_to_gcry (pubkey, &s_pkey);
+ xfree (pubkey);
+ if (!err)
+ err = gcry_pk_verify (s_sig, s_hash, s_pkey);
+ if (!err)
+ err = validate_responder_cert (ctrl, cert, signer_fpr_list);
+ if (!err)
+ {
+ gcry_sexp_release (s_pkey);
+ return 0; /* Successfully verified the signature. */
+ }
+
+ /* We simply ignore all errors. */
+ gcry_sexp_release (s_pkey);
+ return -1;
+}
+
+
+/* Check the signature of an OCSP repsonse. OCSP is the context,
+ S_SIG the signature value and MD the handle of the hash we used for
+ the response. This function automagically finds the correct public
+ key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been
+ used and thus the certificate is one of those identified by
+ the fingerprints. */
+static gpg_error_t
+check_signature (ctrl_t ctrl,
+ ksba_ocsp_t ocsp, gcry_sexp_t s_sig, gcry_md_hd_t md,
+ fingerprint_list_t signer_fpr_list)
+{
+ gpg_error_t err;
+ int algo, cert_idx;
+ gcry_sexp_t s_hash;
+ ksba_cert_t cert;
+
+ /* Create a suitable S-expression with the hash value of our response. */
+ gcry_md_final (md);
+ algo = gcry_md_get_algo (md);
+ if (algo != GCRY_MD_SHA1 )
+ {
+ log_error (_("only SHA-1 is supported for OCSP responses\n"));
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+ err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash sha1 %b))",
+ gcry_md_get_algo_dlen (algo),
+ gcry_md_read (md, algo));
+ if (err)
+ {
+ log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err));
+ return err;
+ }
+
+ /* Get rid of old OCSP specific certificate references. */
+ release_ctrl_ocsp_certs (ctrl);
+
+ if (signer_fpr_list && !signer_fpr_list->next)
+ {
+ /* There is exactly one signer fingerprint given. Thus we use
+ the default OCSP responder's certificate and instantly know
+ the certificate to use. */
+ cert = get_cert_byhexfpr (signer_fpr_list->hexfpr);
+ if (!cert)
+ cert = get_cert_local (ctrl, signer_fpr_list->hexfpr);
+ if (cert)
+ {
+ err = check_signature_core (ctrl, cert, s_sig, s_hash,
+ signer_fpr_list);
+ ksba_cert_release (cert);
+ cert = NULL;
+ if (!err)
+ {
+ gcry_sexp_release (s_hash);
+ return 0; /* Successfully verified the signature. */
+ }
+ }
+ }
+ else
+ {
+ char *name;
+ ksba_sexp_t keyid;
+
+ /* Put all certificates included in the response into the cache
+ and setup a list of those certificate which will later be
+ preferred used when locating certificates. */
+ for (cert_idx=0; (cert = ksba_ocsp_get_cert (ocsp, cert_idx));
+ cert_idx++)
+ {
+ cert_ref_t cref;
+
+ cref = xtrymalloc (sizeof *cref);
+ if (!cref)
+ log_error (_("allocating list item failed: %s\n"),
+ gcry_strerror (err));
+ else if (!cache_cert_silent (cert, &cref->fpr))
+ {
+ cref->next = ctrl->ocsp_certs;
+ ctrl->ocsp_certs = cref;
+ }
+ else
+ xfree (cref);
+ }
+
+ /* Get the certificate by means of the responder ID. */
+ err = ksba_ocsp_get_responder_id (ocsp, &name, &keyid);
+ if (err)
+ {
+ log_error (_("error getting responder ID: %s\n"),
+ gcry_strerror (err));
+ return err;
+ }
+ cert = find_cert_bysubject (ctrl, name, keyid);
+ if (!cert)
+ {
+ log_error ("responder certificate ");
+ if (name)
+ log_printf ("'/%s' ", name);
+ if (keyid)
+ {
+ log_printf ("{");
+ dump_serial (keyid);
+ log_printf ("} ");
+ }
+ log_printf ("not found\n");
+ }
+ ksba_free (name);
+ ksba_free (keyid);
+
+ if (cert)
+ {
+ err = check_signature_core (ctrl, cert, s_sig, s_hash,
+ signer_fpr_list);
+ ksba_cert_release (cert);
+ if (!err)
+ {
+ gcry_sexp_release (s_hash);
+ return 0; /* Successfully verified the signature. */
+ }
+ }
+ }
+
+ gcry_sexp_release (s_hash);
+ log_error (_("no suitable certificate found to verify the OCSP response\n"));
+ return gpg_error (GPG_ERR_NO_PUBKEY);
+}
+
+
+/* Check whether the certificate either given by fingerprint CERT_FPR
+ or directly through the CERT object is valid by running an OCSP
+ transaction. With FORCE_DEFAULT_RESPONDER set only the configured
+ default responder is used. */
+gpg_error_t
+ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr,
+ int force_default_responder)
+{
+ gpg_error_t err;
+ ksba_ocsp_t ocsp = NULL;
+ ksba_cert_t issuer_cert = NULL;
+ ksba_sexp_t sigval = NULL;
+ gcry_sexp_t s_sig = NULL;
+ ksba_isotime_t current_time;
+ ksba_isotime_t this_update, next_update, revocation_time, produced_at;
+ ksba_isotime_t tmp_time;
+ ksba_status_t status;
+ ksba_crl_reason_t reason;
+ char *url_buffer = NULL;
+ const char *url;
+ gcry_md_hd_t md = NULL;
+ int i, idx;
+ char *oid;
+ ksba_name_t name;
+ fingerprint_list_t default_signer = NULL;
+
+ /* Get the certificate. */
+ if (cert)
+ {
+ ksba_cert_ref (cert);
+
+ err = find_issuing_cert (ctrl, cert, &issuer_cert);
+ if (err)
+ {
+ log_error (_("issuer certificate not found: %s\n"),
+ gpg_strerror (err));
+ goto leave;
+ }
+ }
+ else
+ {
+ cert = get_cert_local (ctrl, cert_fpr);
+ if (!cert)
+ {
+ log_error (_("caller did not return the target certificate\n"));
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+ issuer_cert = get_issuing_cert_local (ctrl, NULL);
+ if (!issuer_cert)
+ {
+ log_error (_("caller did not return the issuing certificate\n"));
+ err = gpg_error (GPG_ERR_GENERAL);
+ goto leave;
+ }
+ }
+
+ /* Create an OCSP instance. */
+ err = ksba_ocsp_new (&ocsp);
+ if (err)
+ {
+ log_error (_("failed to allocate OCSP context: %s\n"),
+ gpg_strerror (err));
+ goto leave;
+ }
+
+
+
+ /* Figure out the OCSP responder to use.
+ 1. Try to get the reponder from the certificate.
+ We do only take http and https style URIs into account.
+ 2. If this fails use the default responder, if any.
+ */
+ url = NULL;
+ for (idx=0; !url && !opt.ignore_ocsp_service_url && !force_default_responder
+ && !(err=ksba_cert_get_authority_info_access (cert, idx,
+ &oid, &name)); idx++)
+ {
+ if ( !strcmp (oid, oidstr_ocsp) )
+ {
+ for (i=0; !url && ksba_name_enum (name, i); i++)
+ {
+ char *p = ksba_name_get_uri (name, i);
+ if (p && (!ascii_strncasecmp (p, "http:", 5)
+ || !ascii_strncasecmp (p, "https:", 6)))
+ url = url_buffer = p;
+ else
+ xfree (p);
+ }
+ }
+ ksba_name_release (name);
+ ksba_free (oid);
+ }
+ if (err && gpg_err_code (err) != GPG_ERR_EOF)
+ {
+ log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+ if (!url)
+ {
+ if (!opt.ocsp_responder || !*opt.ocsp_responder)
+ {
+ log_info (_("no default OCSP responder defined\n"));
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+ goto leave;
+ }
+ if (!opt.ocsp_signer)
+ {
+ log_info (_("no default OCSP signer defined\n"));
+ err = gpg_error (GPG_ERR_CONFIGURATION);
+ goto leave;
+ }
+ url = opt.ocsp_responder;
+ default_signer = opt.ocsp_signer;
+ if (opt.verbose)
+ log_info (_("using default OCSP responder '%s'\n"), url);
+ }
+ else
+ {
+ if (opt.verbose)
+ log_info (_("using OCSP responder '%s'\n"), url);
+ }
+
+ /* Ask the OCSP responder. */
+ err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
+ if (err)
+ {
+ log_error (_("failed to establish a hashing context for OCSP: %s\n"),
+ gpg_strerror (err));
+ goto leave;
+ }
+ err = do_ocsp_request (ctrl, ocsp, md, url, cert, issuer_cert);
+ if (err)
+ goto leave;
+
+ /* We got a useful answer, check that the answer has a valid signature. */
+ sigval = ksba_ocsp_get_sig_val (ocsp, produced_at);
+ if (!sigval || !*produced_at)
+ {
+ err = gpg_error (GPG_ERR_INV_OBJ);
+ goto leave;
+ }
+ if ( (err = canon_sexp_to_gcry (sigval, &s_sig)) )
+ goto leave;
+ xfree (sigval);
+ sigval = NULL;
+ err = check_signature (ctrl, ocsp, s_sig, md, default_signer);
+ if (err)
+ goto leave;
+
+ /* We only support one certificate per request. Check that the
+ answer matches the right certificate. */
+ err = ksba_ocsp_get_status (ocsp, cert,
+ &status, this_update, next_update,
+ revocation_time, &reason);
+ if (err)
+ {
+ log_error (_("error getting OCSP status for target certificate: %s\n"),
+ gpg_strerror (err));
+ goto leave;
+ }
+
+ /* In case the certificate has been revoked, we better invalidate
+ our cached validation status. */
+ if (status == KSBA_STATUS_REVOKED)
+ {
+ time_t validated_at = 0; /* That is: No cached validation available. */
+ err = ksba_cert_set_user_data (cert, "validated_at",
+ &validated_at, sizeof (validated_at));
+ if (err)
+ {
+ log_error ("set_user_data(validated_at) failed: %s\n",
+ gpg_strerror (err));
+ err = 0; /* The certificate is anyway revoked, and that is a
+ more important message than the failure of our
+ cache. */
+ }
+ }
+
+
+ if (opt.verbose)
+ {
+ log_info (_("certificate status is: %s (this=%s next=%s)\n"),
+ status == KSBA_STATUS_GOOD? _("good"):
+ status == KSBA_STATUS_REVOKED? _("revoked"):
+ status == KSBA_STATUS_UNKNOWN? _("unknown"):
+ status == KSBA_STATUS_NONE? _("none"): "?",
+ this_update, next_update);
+ if (status == KSBA_STATUS_REVOKED)
+ log_info (_("certificate has been revoked at: %s due to: %s\n"),
+ revocation_time,
+ reason == KSBA_CRLREASON_UNSPECIFIED? "unspecified":
+ reason == KSBA_CRLREASON_KEY_COMPROMISE? "key compromise":
+ reason == KSBA_CRLREASON_CA_COMPROMISE? "CA compromise":
+ reason == KSBA_CRLREASON_AFFILIATION_CHANGED?
+ "affiliation changed":
+ reason == KSBA_CRLREASON_SUPERSEDED? "superseeded":
+ reason == KSBA_CRLREASON_CESSATION_OF_OPERATION?
+ "cessation of operation":
+ reason == KSBA_CRLREASON_CERTIFICATE_HOLD?
+ "certificate on hold":
+ reason == KSBA_CRLREASON_REMOVE_FROM_CRL?
+ "removed from CRL":
+ reason == KSBA_CRLREASON_PRIVILEGE_WITHDRAWN?
+ "privilege withdrawn":
+ reason == KSBA_CRLREASON_AA_COMPROMISE? "AA compromise":
+ reason == KSBA_CRLREASON_OTHER? "other":"?");
+
+ }
+
+
+ if (status == KSBA_STATUS_REVOKED)
+ err = gpg_error (GPG_ERR_CERT_REVOKED);
+ else if (status == KSBA_STATUS_UNKNOWN)
+ err = gpg_error (GPG_ERR_NO_DATA);
+ else if (status != KSBA_STATUS_GOOD)
+ err = gpg_error (GPG_ERR_GENERAL);
+
+ /* Allow for some clock skew. */
+ gnupg_get_isotime (current_time);
+ add_seconds_to_isotime (current_time, opt.ocsp_max_clock_skew);
+
+ if (strcmp (this_update, current_time) > 0 )
+ {
+ log_error (_("OCSP responder returned a status in the future\n"));
+ log_info ("used now: %s this_update: %s\n", current_time, this_update);
+ if (!err)
+ err = gpg_error (GPG_ERR_TIME_CONFLICT);
+ }
+
+ /* Check that THIS_UPDATE is not too far back in the past. */
+ gnupg_copy_time (tmp_time, this_update);
+ add_seconds_to_isotime (tmp_time,
+ opt.ocsp_max_period+opt.ocsp_max_clock_skew);
+ if (!*tmp_time || strcmp (tmp_time, current_time) < 0 )
+ {
+ log_error (_("OCSP responder returned a non-current status\n"));
+ log_info ("used now: %s this_update: %s\n",
+ current_time, this_update);
+ if (!err)
+ err = gpg_error (GPG_ERR_TIME_CONFLICT);
+ }
+
+ /* Check that we are not beyound NEXT_UPDATE (plus some extra time). */
+ if (*next_update)
+ {
+ gnupg_copy_time (tmp_time, next_update);
+ add_seconds_to_isotime (tmp_time,
+ opt.ocsp_current_period+opt.ocsp_max_clock_skew);
+ if (!*tmp_time && strcmp (tmp_time, current_time) < 0 )
+ {
+ log_error (_("OCSP responder returned an too old status\n"));
+ log_info ("used now: %s next_update: %s\n",
+ current_time, next_update);
+ if (!err)
+ err = gpg_error (GPG_ERR_TIME_CONFLICT);
+ }
+ }
+
+
+ leave:
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ xfree (sigval);
+ ksba_cert_release (issuer_cert);
+ ksba_cert_release (cert);
+ ksba_ocsp_release (ocsp);
+ xfree (url_buffer);
+ return err;
+}
+
+
+/* Release the list of OCSP certificates hold in the CTRL object. */
+void
+release_ctrl_ocsp_certs (ctrl_t ctrl)
+{
+ while (ctrl->ocsp_certs)
+ {
+ cert_ref_t tmp = ctrl->ocsp_certs->next;
+ xfree (ctrl->ocsp_certs);
+ ctrl->ocsp_certs = tmp;
+ }
+}
diff --git a/dirmngr/ocsp.h b/dirmngr/ocsp.h
new file mode 100644
index 0000000..cfab7dd
--- /dev/null
+++ b/dirmngr/ocsp.h
@@ -0,0 +1,31 @@
+/* ocsp.h - OCSP management
+ * Copyright (C) 2003 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#ifndef OCSP_H
+#define OCSP_H
+
+gpg_error_t ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr,
+ int force_default_responder);
+
+/* Release the list of OCSP certificates hold in the CTRL object. */
+void release_ctrl_ocsp_certs (ctrl_t ctrl);
+
+#endif /*OCSP_H*/
diff --git a/dirmngr/server.c b/dirmngr/server.c
new file mode 100644
index 0000000..9b4cdb2
--- /dev/null
+++ b/dirmngr/server.c
@@ -0,0 +1,2183 @@
+/* server.c - LDAP and Keyserver access server
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011 g10 Code GmbH
+ * Copyright (C) 2014 Werner Koch
+ *
+ * This file is part of 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define JNLIB_NEED_LOG_LOGV
+#include "dirmngr.h"
+#include <assuan.h>
+
+#include "crlcache.h"
+#include "crlfetch.h"
+#if USE_LDAP
+# include "ldapserver.h"
+#endif
+#include "ocsp.h"
+#include "certcache.h"
+#include "validate.h"
+#include "misc.h"
+#if USE_LDAP
+# include "ldap-wrapper.h"
+#endif
+#include "ks-action.h"
+#include "ks-engine.h" /* (ks_hkp_print_hosttable) */
+
+/* To avoid DoS attacks we limit the size of a certificate to
+ something reasonable. */
+#define MAX_CERT_LENGTH (8*1024)
+
+/* The same goes for OpenPGP keyblocks, but here we need to allow for
+ much longer blocks; a 200k keyblock is not too unusual for keys
+ with a lot of signatures (e.g. 0x5b0358a2). */
+#define MAX_KEYBLOCK_LENGTH (512*1024)
+
+
+#define PARM_ERROR(t) assuan_set_error (ctx, \
+ gpg_error (GPG_ERR_ASS_PARAMETER), (t))
+#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
+
+
+
+/* Control structure per connection. */
+struct server_local_s
+{
+ /* Data used to associate an Assuan context with local server data */
+ assuan_context_t assuan_ctx;
+
+ /* Per-session LDAP servers. */
+ ldap_server_t ldapservers;
+
+ /* If this flag is set to true this dirmngr process will be
+ terminated after the end of this session. */
+ int stopme;
+};
+
+
+/* Cookie definition for assuan data line output. */
+static ssize_t data_line_cookie_write (void *cookie,
+ const void *buffer, size_t size);
+static int data_line_cookie_close (void *cookie);
+static es_cookie_io_functions_t data_line_cookie_functions =
+ {
+ NULL,
+ data_line_cookie_write,
+ NULL,
+ data_line_cookie_close
+ };
+
+
+
+
+
+/* Accessor for the local ldapservers variable. */
+ldap_server_t
+get_ldapservers_from_ctrl (ctrl_t ctrl)
+{
+ if (ctrl && ctrl->server_local)
+ return ctrl->server_local->ldapservers;
+ else
+ return NULL;
+}
+
+
+/* Release all configured keyserver info from CTRL. */
+void
+release_ctrl_keyservers (ctrl_t ctrl)
+{
+ while (ctrl->keyservers)
+ {
+ uri_item_t tmp = ctrl->keyservers->next;
+ http_release_parsed_uri (ctrl->keyservers->parsed_uri);
+ xfree (ctrl->keyservers);
+ ctrl->keyservers = tmp;
+ }
+}
+
+
+
+/* Helper to print a message while leaving a command. */
+static gpg_error_t
+leave_cmd (assuan_context_t ctx, gpg_error_t err)
+
+{
+ if (err)
+ {
+ const char *name = assuan_get_command_name (ctx);
+ if (!name)
+ name = "?";
+ if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
+ log_error ("command '%s' failed: %s\n", name,
+ gpg_strerror (err));
+ else
+ log_error ("command '%s' failed: %s <%s>\n", name,
+ gpg_strerror (err), gpg_strsource (err));
+ }
+ return err;
+}
+
+/* A write handler used by es_fopencookie to write assuan data
+ lines. */
+static ssize_t
+data_line_cookie_write (void *cookie, const void *buffer_arg, size_t size)
+{
+ assuan_context_t ctx = cookie;
+ const char *buffer = buffer_arg;
+
+ if (opt.verbose && buffer && size)
+ {
+ /* Ease reading of output by sending a physical line at each LF. */
+ const char *p;
+ size_t n, nbytes;
+
+ nbytes = size;
+ do
+ {
+ p = memchr (buffer, '\n', nbytes);
+ n = p ? (p - buffer) + 1 : nbytes;
+ if (assuan_send_data (ctx, buffer, n))
+ {
+ gpg_err_set_errno (EIO);
+ return -1;
+ }
+ buffer += n;
+ nbytes -= n;
+ if (nbytes && assuan_send_data (ctx, NULL, 0)) /* Flush line. */
+ {
+ gpg_err_set_errno (EIO);
+ return -1;
+ }
+ }
+ while (nbytes);
+ }
+ else
+ {
+ if (assuan_send_data (ctx, buffer, size))
+ {
+ gpg_err_set_errno (EIO);
+ return -1;
+ }
+ }
+
+ return size;
+}
+
+static int
+data_line_cookie_close (void *cookie)
+{
+ assuan_context_t ctx = cookie;
+
+ if (assuan_send_data (ctx, NULL, 0))
+ {
+ gpg_err_set_errno (EIO);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* Copy the % and + escaped string S into the buffer D and replace the
+ escape sequences. Note, that it is sufficient to allocate the
+ target string D as long as the source string S, i.e.: strlen(s)+1.
+ Note further that if S contains an escaped binary Nul the resulting
+ string D will contain the 0 as well as all other characters but it
+ will be impossible to know whether this is the original EOS or a
+ copied Nul. */
+static void
+strcpy_escaped_plus (char *d, const unsigned char *s)
+{
+ while (*s)
+ {
+ if (*s == '%' && s[1] && s[2])
+ {
+ s++;
+ *d++ = xtoi_2 ( s);
+ s += 2;
+ }
+ else if (*s == '+')
+ *d++ = ' ', s++;
+ else
+ *d++ = *s++;
+ }
+ *d = 0;
+}
+
+
+/* Check whether the option NAME appears in LINE */
+static int
+has_option (const char *line, const char *name)
+{
+ const char *s;
+ int n = strlen (name);
+
+ s = strstr (line, name);
+ return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
+}
+
+/* Same as has_option but only considers options at the begin of the
+ line. This is useful for commands which allow arbitrary strings on
+ the line. */
+static int
+has_leading_option (const char *line, const char *name)
+{
+ const char *s;
+ int n;
+
+ if (name[0] != '-' || name[1] != '-' || !name[2] || spacep (name+2))
+ return 0;
+ n = strlen (name);
+ while ( *line == '-' && line[1] == '-' )
+ {
+ s = line;
+ while (*line && !spacep (line))
+ line++;
+ if (n == (line - s) && !strncmp (s, name, n))
+ return 1;
+ while (spacep (line))
+ line++;
+ }
+ return 0;
+}
+
+
+/* Same as has_option but does only test for the name of the option
+ and ignores an argument, i.e. with NAME being "--hash" it would
+ return a pointer for "--hash" as well as for "--hash=foo". If
+ thhere is no such option NULL is returned. The pointer returned
+ points right behind the option name, this may be an equal sign, Nul
+ or a space. */
+/* static const char * */
+/* has_option_name (const char *line, const char *name) */
+/* { */
+/* const char *s; */
+/* int n = strlen (name); */
+
+/* s = strstr (line, name); */
+/* return (s && (s == line || spacep (s-1)) */
+/* && (!s[n] || spacep (s+n) || s[n] == '=')) ? (s+n) : NULL; */
+/* } */
+
+
+/* Skip over options. It is assumed that leading spaces have been
+ removed (this is the case for lines passed to a handler from
+ assuan). Blanks after the options are also removed. */
+static char *
+skip_options (char *line)
+{
+ while ( *line == '-' && line[1] == '-' )
+ {
+ while (*line && !spacep (line))
+ line++;
+ while (spacep (line))
+ line++;
+ }
+ return line;
+}
+
+
+/* Return an error if the assuan context does not belong to the owner
+ of the process or to root. On error FAILTEXT is set as Assuan
+ error string. */
+static gpg_error_t
+check_owner_permission (assuan_context_t ctx, const char *failtext)
+{
+#ifdef HAVE_W32_SYSTEM
+ /* Under Windows the dirmngr is always run under the control of the
+ user. */
+ (void)ctx;
+ (void)failtext;
+#else
+ gpg_err_code_t ec;
+ assuan_peercred_t cred;
+
+ ec = gpg_err_code (assuan_get_peercred (ctx, &cred));
+ if (!ec && cred->uid && cred->uid != getuid ())
+ ec = GPG_ERR_EPERM;
+ if (ec)
+ return set_error (ec, failtext);
+#endif
+ return 0;
+}
+
+
+
+/* Common code for get_cert_local and get_issuer_cert_local. */
+static ksba_cert_t
+do_get_cert_local (ctrl_t ctrl, const char *name, const char *command)
+{
+ unsigned char *value;
+ size_t valuelen;
+ int rc;
+ char *buf;
+ ksba_cert_t cert;
+
+ if (name)
+ {
+ buf = xmalloc ( strlen (command) + 1 + strlen(name) + 1);
+ strcpy (stpcpy (stpcpy (buf, command), " "), name);
+ }
+ else
+ buf = xstrdup (command);
+
+ rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
+ &value, &valuelen, MAX_CERT_LENGTH);
+ xfree (buf);
+ if (rc)
+ {
+ log_error (_("assuan_inquire(%s) failed: %s\n"),
+ command, gpg_strerror (rc));
+ return NULL;
+ }
+
+ if (!valuelen)
+ {
+ xfree (value);
+ return NULL;
+ }
+
+ rc = ksba_cert_new (&cert);
+ if (!rc)
+ {
+ rc = ksba_cert_init_from_mem (cert, value, valuelen);
+ if (rc)
+ {
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ }
+ xfree (value);
+ return cert;
+}
+
+
+
+/* Ask back to return a certificate for name, given as a regular
+ gpgsm certificate indentificates (e.g. fingerprint or one of the
+ other methods). Alternatively, NULL may be used for NAME to
+ return the current target certificate. Either return the certificate
+ in a KSBA object or NULL if it is not available.
+*/
+ksba_cert_t
+get_cert_local (ctrl_t ctrl, const char *name)
+{
+ if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
+ {
+ if (opt.debug)
+ log_debug ("get_cert_local called w/o context\n");
+ return NULL;
+ }
+ return do_get_cert_local (ctrl, name, "SENDCERT");
+
+}
+
+/* Ask back to return the issuing certificate for name, given as a
+ regular gpgsm certificate indentificates (e.g. fingerprint or one
+ of the other methods). Alternatively, NULL may be used for NAME to
+ return thecurrent target certificate. Either return the certificate
+ in a KSBA object or NULL if it is not available.
+
+*/
+ksba_cert_t
+get_issuing_cert_local (ctrl_t ctrl, const char *name)
+{
+ if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
+ {
+ if (opt.debug)
+ log_debug ("get_issuing_cert_local called w/o context\n");
+ return NULL;
+ }
+ return do_get_cert_local (ctrl, name, "SENDISSUERCERT");
+}
+
+/* Ask back to return a certificate with subject NAME and a
+ subjectKeyIdentifier of KEYID. */
+ksba_cert_t
+get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid)
+{
+ unsigned char *value;
+ size_t valuelen;
+ int rc;
+ char *buf;
+ ksba_cert_t cert;
+ char *hexkeyid;
+
+ if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
+ {
+ if (opt.debug)
+ log_debug ("get_cert_local_ski called w/o context\n");
+ return NULL;
+ }
+ if (!name || !keyid)
+ {
+ log_debug ("get_cert_local_ski called with insufficient arguments\n");
+ return NULL;
+ }
+
+ hexkeyid = serial_hex (keyid);
+ if (!hexkeyid)
+ {
+ log_debug ("serial_hex() failed\n");
+ return NULL;
+ }
+
+ buf = xtrymalloc (15 + strlen (hexkeyid) + 2 + strlen(name) + 1);
+ if (!buf)
+ {
+
+ log_error ("can't allocate enough memory: %s\n", strerror (errno));
+ xfree (hexkeyid);
+ return NULL;
+ }
+ strcpy (stpcpy (stpcpy (stpcpy (buf, "SENDCERT_SKI "), hexkeyid)," /"),name);
+ xfree (hexkeyid);
+
+ rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
+ &value, &valuelen, MAX_CERT_LENGTH);
+ xfree (buf);
+ if (rc)
+ {
+ log_error (_("assuan_inquire(%s) failed: %s\n"), "SENDCERT_SKI",
+ gpg_strerror (rc));
+ return NULL;
+ }
+
+ if (!valuelen)
+ {
+ xfree (value);
+ return NULL;
+ }
+
+ rc = ksba_cert_new (&cert);
+ if (!rc)
+ {
+ rc = ksba_cert_init_from_mem (cert, value, valuelen);
+ if (rc)
+ {
+ ksba_cert_release (cert);
+ cert = NULL;
+ }
+ }
+ xfree (value);
+ return cert;
+}
+
+
+/* Ask the client via an inquiry to check the istrusted status of the
+ certificate specified by the hexified fingerprint HEXFPR. Returns
+ 0 if the certificate is trusted by the client or an error code. */
+gpg_error_t
+get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr)
+{
+ unsigned char *value;
+ size_t valuelen;
+ int rc;
+ char request[100];
+
+ if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx
+ || !hexfpr)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr);
+ rc = assuan_inquire (ctrl->server_local->assuan_ctx, request,
+ &value, &valuelen, 100);
+ if (rc)
+ {
+ log_error (_("assuan_inquire(%s) failed: %s\n"),
+ request, gpg_strerror (rc));
+ return rc;
+ }
+ /* The expected data is: "1" or "1 cruft" (not a C-string). */
+ if (valuelen && *value == '1' && (valuelen == 1 || spacep (value+1)))
+ rc = 0;
+ else
+ rc = gpg_error (GPG_ERR_NOT_TRUSTED);
+ xfree (value);
+ return rc;
+}
+
+
+
+
+/* Ask the client to return the certificate associated with the
+ current command. This is sometimes needed because the client usually
+ sends us just the cert ID, assuming that the request can be
+ satisfied from the cache, where the cert ID is used as key. */
+static int
+inquire_cert_and_load_crl (assuan_context_t ctx)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ unsigned char *value = NULL;
+ size_t valuelen;
+ ksba_cert_t cert = NULL;
+
+ err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0);
+ if (err)
+ return err;
+
+/* { */
+/* FILE *fp = fopen ("foo.der", "r"); */
+/* value = xmalloc (2000); */
+/* valuelen = fread (value, 1, 2000, fp); */
+/* fclose (fp); */
+/* } */
+
+ if (!valuelen) /* No data returned; return a comprehensible error. */
+ return gpg_error (GPG_ERR_MISSING_CERT);
+
+ err = ksba_cert_new (&cert);
+ if (err)
+ goto leave;
+ err = ksba_cert_init_from_mem (cert, value, valuelen);
+ if(err)
+ goto leave;
+ xfree (value); value = NULL;
+
+ err = crl_cache_reload_crl (ctrl, cert);
+
+ leave:
+ ksba_cert_release (cert);
+ xfree (value);
+ return err;
+}
+
+
+/* Handle OPTION commands. */
+static gpg_error_t
+option_handler (assuan_context_t ctx, const char *key, const char *value)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+
+ if (!strcmp (key, "force-crl-refresh"))
+ {
+ int i = *value? atoi (value) : 0;
+ ctrl->force_crl_refresh = i;
+ }
+ else if (!strcmp (key, "audit-events"))
+ {
+ int i = *value? atoi (value) : 0;
+ ctrl->audit_events = i;
+ }
+ else
+ return gpg_error (GPG_ERR_UNKNOWN_OPTION);
+
+ return 0;
+}
+
+static const char hlp_ldapserver[] =
+ "LDAPSERVER <data>\n"
+ "\n"
+ "Add a new LDAP server to the list of configured LDAP servers.\n"
+ "DATA is in the same format as expected in the configure file.";
+static gpg_error_t
+cmd_ldapserver (assuan_context_t ctx, char *line)
+{
+#if USE_LDAP
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ ldap_server_t server;
+ ldap_server_t *last_next_p;
+
+ while (spacep (line))
+ line++;
+ if (*line == '\0')
+ return leave_cmd (ctx, PARM_ERROR (_("ldapserver missing")));
+
+ server = ldapserver_parse_one (line, "", 0);
+ if (! server)
+ return leave_cmd (ctx, gpg_error (GPG_ERR_INV_ARG));
+
+ last_next_p = &ctrl->server_local->ldapservers;
+ while (*last_next_p)
+ last_next_p = &(*last_next_p)->next;
+ *last_next_p = server;
+ return leave_cmd (ctx, 0);
+#else
+ (void)line;
+ return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED));
+#endif
+}
+
+
+static const char hlp_isvalid[] =
+ "ISVALID [--only-ocsp] [--force-default-responder]"
+ " <certificate_id>|<certificate_fpr>\n"
+ "\n"
+ "This command checks whether the certificate identified by the\n"
+ "certificate_id is valid. This is done by consulting CRLs or\n"
+ "whatever has been configured. Note, that the returned error codes\n"
+ "are from gpg-error.h. The command may callback using the inquire\n"
+ "function. See the manual for details.\n"
+ "\n"
+ "The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n"
+ "delimited by a single dot. The first part is the SHA-1 hash of the\n"
+ "issuer name and the second part the serial number.\n"
+ "\n"
+ "Alternatively the certificate's fingerprint may be given in which\n"
+ "case an OCSP request is done before consulting the CRL.\n"
+ "\n"
+ "If the option --only-ocsp is given, no fallback to a CRL check will\n"
+ "be used.\n"
+ "\n"
+ "If the option --force-default-responder is given, only the default\n"
+ "OCSP responder will be used and any other methods of obtaining an\n"
+ "OCSP responder URL won't be used.";
+static gpg_error_t
+cmd_isvalid (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ char *issuerhash, *serialno;
+ gpg_error_t err;
+ int did_inquire = 0;
+ int ocsp_mode = 0;
+ int only_ocsp;
+ int force_default_responder;
+
+ only_ocsp = has_option (line, "--only-ocsp");
+ force_default_responder = has_option (line, "--force-default-responder");
+ line = skip_options (line);
+
+ issuerhash = xstrdup (line); /* We need to work on a copy of the
+ line because that same Assuan
+ context may be used for an inquiry.
+ That is because Assuan reuses its
+ line buffer.
+ */
+
+ serialno = strchr (issuerhash, '.');
+ if (serialno)
+ *serialno++ = 0;
+ else
+ {
+ char *endp = strchr (issuerhash, ' ');
+ if (endp)
+ *endp = 0;
+ if (strlen (issuerhash) != 40)
+ {
+ xfree (issuerhash);
+ return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID")));
+ }
+ ocsp_mode = 1;
+ }
+
+
+ again:
+ if (ocsp_mode)
+ {
+ /* Note, that we ignore the given issuer hash and instead rely
+ on the current certificate semantics used with this
+ command. */
+ if (!opt.allow_ocsp)
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ else
+ err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder);
+ /* Fixme: If we got no ocsp response and --only-ocsp is not used
+ we should fall back to CRL mode. Thus we need to clear
+ OCSP_MODE, get the issuerhash and the serialno from the
+ current certificate and jump to again. */
+ }
+ else if (only_ocsp)
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ else
+ {
+ switch (crl_cache_isvalid (ctrl,
+ issuerhash, serialno,
+ ctrl->force_crl_refresh))
+ {
+ case CRL_CACHE_VALID:
+ err = 0;
+ break;
+ case CRL_CACHE_INVALID:
+ err = gpg_error (GPG_ERR_CERT_REVOKED);
+ break;
+ case CRL_CACHE_DONTKNOW:
+ if (did_inquire)
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ else if (!(err = inquire_cert_and_load_crl (ctx)))
+ {
+ did_inquire = 1;
+ goto again;
+ }
+ break;
+ case CRL_CACHE_CANTUSE:
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ break;
+ default:
+ log_fatal ("crl_cache_isvalid returned invalid code\n");
+ }
+ }
+
+ xfree (issuerhash);
+ return leave_cmd (ctx, err);
+}
+
+
+/* If the line contains a SHA-1 fingerprint as the first argument,
+ return the FPR vuffer on success. The function checks that the
+ fingerprint consists of valid characters and prints and error
+ message if it does not and returns NULL. Fingerprints are
+ considered optional and thus no explicit error is returned. NULL is
+ also returned if there is no fingerprint at all available.
+ FPR must be a caller provided buffer of at least 20 bytes.
+
+ Note that colons within the fingerprint are allowed to separate 2
+ hex digits; this allows for easier cutting and pasting using the
+ usual fingerprint rendering.
+*/
+static unsigned char *
+get_fingerprint_from_line (const char *line, unsigned char *fpr)
+{
+ const char *s;
+ int i;
+
+ for (s=line, i=0; *s && *s != ' '; s++ )
+ {
+ if ( hexdigitp (s) && hexdigitp (s+1) )
+ {
+ if ( i >= 20 )
+ return NULL; /* Fingerprint too long. */
+ fpr[i++] = xtoi_2 (s);
+ s++;
+ }
+ else if ( *s != ':' )
+ return NULL; /* Invalid. */
+ }
+ if ( i != 20 )
+ return NULL; /* Fingerprint to short. */
+ return fpr;
+}
+
+
+
+static const char hlp_checkcrl[] =
+ "CHECKCRL [<fingerprint>]\n"
+ "\n"
+ "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
+ "entire X.509 certificate blob) is valid or not by consulting the\n"
+ "CRL responsible for this certificate. If the fingerprint has not\n"
+ "been given or the certificate is not known, the function \n"
+ "inquires the certificate using an\n"
+ "\n"
+ " INQUIRE TARGETCERT\n"
+ "\n"
+ "and the caller is expected to return the certificate for the\n"
+ "request (which should match FINGERPRINT) as a binary blob.\n"
+ "Processing then takes place without further interaction; in\n"
+ "particular dirmngr tries to locate other required certificate by\n"
+ "its own mechanism which includes a local certificate store as well\n"
+ "as a list of trusted root certificates.\n"
+ "\n"
+ "The return value is the usual gpg-error code or 0 for ducesss;\n"
+ "i.e. the certificate validity has been confirmed by a valid CRL.";
+static gpg_error_t
+cmd_checkcrl (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ unsigned char fprbuffer[20], *fpr;
+ ksba_cert_t cert;
+
+ fpr = get_fingerprint_from_line (line, fprbuffer);
+ cert = fpr? get_cert_byfpr (fpr) : NULL;
+
+ if (!cert)
+ {
+ /* We do not have this certificate yet or the fingerprint has
+ not been given. Inquire it from the client. */
+ unsigned char *value = NULL;
+ size_t valuelen;
+
+ err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
+ &value, &valuelen, MAX_CERT_LENGTH);
+ if (err)
+ {
+ log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ if (!valuelen) /* No data returned; return a comprehensible error. */
+ err = gpg_error (GPG_ERR_MISSING_CERT);
+ else
+ {
+ err = ksba_cert_new (&cert);
+ if (!err)
+ err = ksba_cert_init_from_mem (cert, value, valuelen);
+ }
+ xfree (value);
+ if(err)
+ goto leave;
+ }
+
+ assert (cert);
+
+ err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh);
+ if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN)
+ {
+ err = crl_cache_reload_crl (ctrl, cert);
+ if (!err)
+ err = crl_cache_cert_isvalid (ctrl, cert, 0);
+ }
+
+ leave:
+ ksba_cert_release (cert);
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_checkocsp[] =
+ "CHECKOCSP [--force-default-responder] [<fingerprint>]\n"
+ "\n"
+ "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
+ "entire X.509 certificate blob) is valid or not by asking an OCSP\n"
+ "responder responsible for this certificate. The optional\n"
+ "fingerprint may be used for a quick check in case an OCSP check has\n"
+ "been done for this certificate recently (we always cache OCSP\n"
+ "responses for a couple of minutes). If the fingerprint has not been\n"
+ "given or there is no cached result, the function inquires the\n"
+ "certificate using an\n"
+ "\n"
+ " INQUIRE TARGETCERT\n"
+ "\n"
+ "and the caller is expected to return the certificate for the\n"
+ "request (which should match FINGERPRINT) as a binary blob.\n"
+ "Processing then takes place without further interaction; in\n"
+ "particular dirmngr tries to locate other required certificates by\n"
+ "its own mechanism which includes a local certificate store as well\n"
+ "as a list of trusted root certifciates.\n"
+ "\n"
+ "If the option --force-default-responder is given, only the default\n"
+ "OCSP responder will be used and any other methods of obtaining an\n"
+ "OCSP responder URL won't be used.\n"
+ "\n"
+ "The return value is the usual gpg-error code or 0 for ducesss;\n"
+ "i.e. the certificate validity has been confirmed by a valid CRL.";
+static gpg_error_t
+cmd_checkocsp (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ unsigned char fprbuffer[20], *fpr;
+ ksba_cert_t cert;
+ int force_default_responder;
+
+ force_default_responder = has_option (line, "--force-default-responder");
+ line = skip_options (line);
+
+ fpr = get_fingerprint_from_line (line, fprbuffer);
+ cert = fpr? get_cert_byfpr (fpr) : NULL;
+
+ if (!cert)
+ {
+ /* We do not have this certificate yet or the fingerprint has
+ not been given. Inquire it from the client. */
+ unsigned char *value = NULL;
+ size_t valuelen;
+
+ err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
+ &value, &valuelen, MAX_CERT_LENGTH);
+ if (err)
+ {
+ log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ if (!valuelen) /* No data returned; return a comprehensible error. */
+ err = gpg_error (GPG_ERR_MISSING_CERT);
+ else
+ {
+ err = ksba_cert_new (&cert);
+ if (!err)
+ err = ksba_cert_init_from_mem (cert, value, valuelen);
+ }
+ xfree (value);
+ if(err)
+ goto leave;
+ }
+
+ assert (cert);
+
+ if (!opt.allow_ocsp)
+ err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+ else
+ err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder);
+
+ leave:
+ ksba_cert_release (cert);
+ return leave_cmd (ctx, err);
+}
+
+
+
+static int
+lookup_cert_by_url (assuan_context_t ctx, const char *url)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ unsigned char *value = NULL;
+ size_t valuelen;
+
+ /* Fetch single certificate given it's URL. */
+ err = fetch_cert_by_url (ctrl, url, &value, &valuelen);
+ if (err)
+ {
+ log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Send the data, flush the buffer and then send an END. */
+ err = assuan_send_data (ctx, value, valuelen);
+ if (!err)
+ err = assuan_send_data (ctx, NULL, 0);
+ if (!err)
+ err = assuan_write_line (ctx, "END");
+ if (err)
+ {
+ log_error (_("error sending data: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ leave:
+
+ return err;
+}
+
+
+/* Send the certificate, flush the buffer and then send an END. */
+static gpg_error_t
+return_one_cert (void *opaque, ksba_cert_t cert)
+{
+ assuan_context_t ctx = opaque;
+ gpg_error_t err;
+ const unsigned char *der;
+ size_t derlen;
+
+ der = ksba_cert_get_image (cert, &derlen);
+ if (!der)
+ err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+ else
+ {
+ err = assuan_send_data (ctx, der, derlen);
+ if (!err)
+ err = assuan_send_data (ctx, NULL, 0);
+ if (!err)
+ err = assuan_write_line (ctx, "END");
+ }
+ if (err)
+ log_error (_("error sending data: %s\n"), gpg_strerror (err));
+ return err;
+}
+
+
+/* Lookup certificates from the internal cache or using the ldap
+ servers. */
+static int
+lookup_cert_by_pattern (assuan_context_t ctx, char *line,
+ int single, int cache_only)
+{
+ gpg_error_t err = 0;
+ char *p;
+ strlist_t sl, list = NULL;
+ int truncated = 0, truncation_forced = 0;
+ int count = 0;
+ int local_count = 0;
+#if USE_LDAP
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ unsigned char *value = NULL;
+ size_t valuelen;
+ struct ldapserver_iter ldapserver_iter;
+ cert_fetch_context_t fetch_context;
+#endif /*USE_LDAP*/
+ int any_no_data = 0;
+
+ /* Break the line down into an STRLIST */
+ for (p=line; *p; line = p)
+ {
+ while (*p && *p != ' ')
+ p++;
+ if (*p)
+ *p++ = 0;
+
+ if (*line)
+ {
+ sl = xtrymalloc (sizeof *sl + strlen (line));
+ if (!sl)
+ {
+ err = gpg_error_from_errno (errno);
+ goto leave;
+ }
+ memset (sl, 0, sizeof *sl);
+ strcpy_escaped_plus (sl->d, line);
+ sl->next = list;
+ list = sl;
+ }
+ }
+
+ /* First look through the internal cache. The certifcates retruned
+ here are not counted towards the truncation limit. */
+ if (single && !cache_only)
+ ; /* Do not read from the local cache in this case. */
+ else
+ {
+ for (sl=list; sl; sl = sl->next)
+ {
+ err = get_certs_bypattern (sl->d, return_one_cert, ctx);
+ if (!err)
+ local_count++;
+ if (!err && single)
+ goto ready;
+
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ {
+ err = 0;
+ if (cache_only)
+ any_no_data = 1;
+ }
+ else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only)
+ {
+ /* No real fault because the internal pattern lookup
+ can't yet cope with all types of pattern. */
+ err = 0;
+ }
+ if (err)
+ goto ready;
+ }
+ }
+
+ /* Loop over all configured servers unless we want only the
+ certificates from the cache. */
+#if USE_LDAP
+ for (ldapserver_iter_begin (&ldapserver_iter, ctrl);
+ !cache_only && !ldapserver_iter_end_p (&ldapserver_iter)
+ && ldapserver_iter.server->host && !truncation_forced;
+ ldapserver_iter_next (&ldapserver_iter))
+ {
+ ldap_server_t ldapserver = ldapserver_iter.server;
+
+ if (DBG_LOOKUP)
+ log_debug ("cmd_lookup: trying %s:%d base=%s\n",
+ ldapserver->host, ldapserver->port,
+ ldapserver->base?ldapserver->base : "[default]");
+
+ /* Fetch certificates matching pattern */
+ err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver);
+ if ( gpg_err_code (err) == GPG_ERR_NO_DATA )
+ {
+ if (DBG_LOOKUP)
+ log_debug ("cmd_lookup: no data\n");
+ err = 0;
+ any_no_data = 1;
+ continue;
+ }
+ if (err)
+ {
+ log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Fetch the certificates for this query. */
+ while (!truncation_forced)
+ {
+ xfree (value); value = NULL;
+ err = fetch_next_cert (fetch_context, &value, &valuelen);
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA )
+ {
+ err = 0;
+ any_no_data = 1;
+ break; /* Ready. */
+ }
+ if (gpg_err_code (err) == GPG_ERR_TRUNCATED)
+ {
+ truncated = 1;
+ err = 0;
+ break; /* Ready. */
+ }
+ if (gpg_err_code (err) == GPG_ERR_EOF)
+ {
+ err = 0;
+ break; /* Ready. */
+ }
+ if (!err && !value)
+ {
+ err = gpg_error (GPG_ERR_BUG);
+ goto leave;
+ }
+ if (err)
+ {
+ log_error (_("fetch_next_cert failed: %s\n"),
+ gpg_strerror (err));
+ end_cert_fetch (fetch_context);
+ goto leave;
+ }
+
+ if (DBG_LOOKUP)
+ log_debug ("cmd_lookup: returning one cert%s\n",
+ truncated? " (truncated)":"");
+
+ /* Send the data, flush the buffer and then send an END line
+ as a certificate delimiter. */
+ err = assuan_send_data (ctx, value, valuelen);
+ if (!err)
+ err = assuan_send_data (ctx, NULL, 0);
+ if (!err)
+ err = assuan_write_line (ctx, "END");
+ if (err)
+ {
+ log_error (_("error sending data: %s\n"), gpg_strerror (err));
+ end_cert_fetch (fetch_context);
+ goto leave;
+ }
+
+ if (++count >= opt.max_replies )
+ {
+ truncation_forced = 1;
+ log_info (_("max_replies %d exceeded\n"), opt.max_replies );
+ }
+ if (single)
+ break;
+ }
+
+ end_cert_fetch (fetch_context);
+ }
+#endif /*USE_LDAP*/
+
+ ready:
+ if (truncated || truncation_forced)
+ {
+ char str[50];
+
+ sprintf (str, "%d", count);
+ assuan_write_status (ctx, "TRUNCATED", str);
+ }
+
+ if (!err && !count && !local_count && any_no_data)
+ err = gpg_error (GPG_ERR_NO_DATA);
+
+ leave:
+ free_strlist (list);
+ return err;
+}
+
+
+static const char hlp_lookup[] =
+ "LOOKUP [--url] [--single] [--cache-only] <pattern>\n"
+ "\n"
+ "Lookup certificates matching PATTERN. With --url the pattern is\n"
+ "expected to be one URL.\n"
+ "\n"
+ "If --url is not given: To allow for multiple patterns (which are ORed)\n"
+ "quoting is required: Spaces are translated to \"+\" or \"%20\";\n"
+ "obviously this requires that the usual escape quoting rules are applied.\n"
+ "\n"
+ "If --url is given no special escaping is required because URLs are\n"
+ "already escaped this way.\n"
+ "\n"
+ "If --single is given the first and only the first match will be\n"
+ "returned. If --cache-only is _not_ given, no local query will be\n"
+ "done.\n"
+ "\n"
+ "If --cache-only is given no external lookup is done so that only\n"
+ "certificates from the cache may get returned.";
+static gpg_error_t
+cmd_lookup (assuan_context_t ctx, char *line)
+{
+ gpg_error_t err;
+ int lookup_url, single, cache_only;
+
+ lookup_url = has_leading_option (line, "--url");
+ single = has_leading_option (line, "--single");
+ cache_only = has_leading_option (line, "--cache-only");
+ line = skip_options (line);
+
+ if (lookup_url && cache_only)
+ err = gpg_error (GPG_ERR_NOT_FOUND);
+ else if (lookup_url && single)
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+ else if (lookup_url)
+ err = lookup_cert_by_url (ctx, line);
+ else
+ err = lookup_cert_by_pattern (ctx, line, single, cache_only);
+
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_loadcrl[] =
+ "LOADCRL [--url] <filename|url>\n"
+ "\n"
+ "Load the CRL in the file with name FILENAME into our cache. Note\n"
+ "that FILENAME should be given with an absolute path because\n"
+ "Dirmngrs cwd is not known. With --url the CRL is directly loaded\n"
+ "from the given URL.\n"
+ "\n"
+ "This command is usually used by gpgsm using the invocation \"gpgsm\n"
+ "--call-dirmngr loadcrl <filename>\". A direct invocation of Dirmngr\n"
+ "is not useful because gpgsm might need to callback gpgsm to ask for\n"
+ "the CA's certificate.";
+static gpg_error_t
+cmd_loadcrl (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ int use_url = has_leading_option (line, "--url");
+
+ line = skip_options (line);
+
+ if (use_url)
+ {
+ ksba_reader_t reader;
+
+ err = crl_fetch (ctrl, line, &reader);
+ if (err)
+ log_error (_("fetching CRL from '%s' failed: %s\n"),
+ line, gpg_strerror (err));
+ else
+ {
+ err = crl_cache_insert (ctrl, line, reader);
+ if (err)
+ log_error (_("processing CRL from '%s' failed: %s\n"),
+ line, gpg_strerror (err));
+ crl_close_reader (reader);
+ }
+ }
+ else
+ {
+ char *buf;
+
+ buf = xtrymalloc (strlen (line)+1);
+ if (!buf)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ strcpy_escaped_plus (buf, line);
+ err = crl_cache_load (ctrl, buf);
+ xfree (buf);
+ }
+ }
+
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_listcrls[] =
+ "LISTCRLS\n"
+ "\n"
+ "List the content of all CRLs in a readable format. This command is\n"
+ "usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n"
+ "listcrls\". It may also be used directly using \"dirmngr\n"
+ "--list-crls\".";
+static gpg_error_t
+cmd_listcrls (assuan_context_t ctx, char *line)
+{
+ gpg_error_t err;
+ estream_t fp;
+
+ (void)line;
+
+ fp = es_fopencookie (ctx, "w", data_line_cookie_functions);
+ if (!fp)
+ err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
+ else
+ {
+ err = crl_cache_list (fp);
+ es_fclose (fp);
+ }
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_cachecert[] =
+ "CACHECERT\n"
+ "\n"
+ "Put a certificate into the internal cache. This command might be\n"
+ "useful if a client knows in advance certificates required for a\n"
+ "test and wants to make sure they get added to the internal cache.\n"
+ "It is also helpful for debugging. To get the actual certificate,\n"
+ "this command immediately inquires it using\n"
+ "\n"
+ " INQUIRE TARGETCERT\n"
+ "\n"
+ "and the caller is expected to return the certificate for the\n"
+ "request as a binary blob.";
+static gpg_error_t
+cmd_cachecert (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ ksba_cert_t cert = NULL;
+ unsigned char *value = NULL;
+ size_t valuelen;
+
+ (void)line;
+
+ err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
+ &value, &valuelen, MAX_CERT_LENGTH);
+ if (err)
+ {
+ log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ if (!valuelen) /* No data returned; return a comprehensible error. */
+ err = gpg_error (GPG_ERR_MISSING_CERT);
+ else
+ {
+ err = ksba_cert_new (&cert);
+ if (!err)
+ err = ksba_cert_init_from_mem (cert, value, valuelen);
+ }
+ xfree (value);
+ if(err)
+ goto leave;
+
+ err = cache_cert (cert);
+
+ leave:
+ ksba_cert_release (cert);
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_validate[] =
+ "VALIDATE\n"
+ "\n"
+ "Validate a certificate using the certificate validation function\n"
+ "used internally by dirmngr. This command is only useful for\n"
+ "debugging. To get the actual certificate, this command immediately\n"
+ "inquires it using\n"
+ "\n"
+ " INQUIRE TARGETCERT\n"
+ "\n"
+ "and the caller is expected to return the certificate for the\n"
+ "request as a binary blob.";
+static gpg_error_t
+cmd_validate (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ ksba_cert_t cert = NULL;
+ unsigned char *value = NULL;
+ size_t valuelen;
+
+ (void)line;
+
+ err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
+ &value, &valuelen, MAX_CERT_LENGTH);
+ if (err)
+ {
+ log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ if (!valuelen) /* No data returned; return a comprehensible error. */
+ err = gpg_error (GPG_ERR_MISSING_CERT);
+ else
+ {
+ err = ksba_cert_new (&cert);
+ if (!err)
+ err = ksba_cert_init_from_mem (cert, value, valuelen);
+ }
+ xfree (value);
+ if(err)
+ goto leave;
+
+ /* If we have this certificate already in our cache, use the cached
+ version for validation because this will take care of any cached
+ results. */
+ {
+ unsigned char fpr[20];
+ ksba_cert_t tmpcert;
+
+ cert_compute_fpr (cert, fpr);
+ tmpcert = get_cert_byfpr (fpr);
+ if (tmpcert)
+ {
+ ksba_cert_release (cert);
+ cert = tmpcert;
+ }
+ }
+
+ err = validate_cert_chain (ctrl, cert, NULL, VALIDATE_MODE_CERT, NULL);
+
+ leave:
+ ksba_cert_release (cert);
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_keyserver[] =
+ "KEYSERVER [<options>] [<uri>|<host>]\n"
+ "Options are:\n"
+ " --help\n"
+ " --clear Remove all configured keyservers\n"
+ " --resolve Resolve HKP host names and rotate\n"
+ " --hosttable Print table of known hosts and pools\n"
+ " --dead Mark <host> as dead\n"
+ " --alive Mark <host> as alive\n"
+ "\n"
+ "If called without arguments list all configured keyserver URLs.\n"
+ "If called with an URI add this as keyserver. Note that keyservers\n"
+ "are configured on a per-session base. A default keyserver may already be\n"
+ "present, thus the \"--clear\" option must be used to get full control.\n"
+ "If \"--clear\" and an URI are used together the clear command is\n"
+ "obviously executed first. A RESET command does not change the list\n"
+ "of configured keyservers.";
+static gpg_error_t
+cmd_keyserver (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ int clear_flag, add_flag, help_flag, host_flag, resolve_flag;
+ int dead_flag, alive_flag;
+ uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it
+ is always initialized. */
+
+ clear_flag = has_option (line, "--clear");
+ help_flag = has_option (line, "--help");
+ resolve_flag = has_option (line, "--resolve");
+ host_flag = has_option (line, "--hosttable");
+ dead_flag = has_option (line, "--dead");
+ alive_flag = has_option (line, "--alive");
+ line = skip_options (line);
+ add_flag = !!*line;
+
+ if (help_flag)
+ {
+ err = ks_action_help (ctrl, line);
+ goto leave;
+ }
+
+ if (resolve_flag)
+ {
+ err = ks_action_resolve (ctrl);
+ if (err)
+ goto leave;
+ }
+
+ if (alive_flag && dead_flag)
+ {
+ err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies");
+ goto leave;
+ }
+ if (dead_flag)
+ {
+ err = check_owner_permission (ctx, "no permission to use --dead");
+ if (err)
+ goto leave;
+ }
+ if (alive_flag || dead_flag)
+ {
+ if (!*line)
+ {
+ err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing");
+ goto leave;
+ }
+
+ err = ks_hkp_mark_host (ctrl, line, alive_flag);
+ if (err)
+ goto leave;
+ }
+
+ if (host_flag)
+ {
+ err = ks_hkp_print_hosttable (ctrl);
+ if (err)
+ goto leave;
+ }
+ if (resolve_flag || host_flag || alive_flag || dead_flag)
+ goto leave;
+
+ if (add_flag)
+ {
+ item = xtrymalloc (sizeof *item + strlen (line));
+ if (!item)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ item->next = NULL;
+ item->parsed_uri = NULL;
+ strcpy (item->uri, line);
+
+ err = http_parse_uri (&item->parsed_uri, line, 1);
+ if (err)
+ {
+ xfree (item);
+ goto leave;
+ }
+ }
+ if (clear_flag)
+ release_ctrl_keyservers (ctrl);
+ if (add_flag)
+ {
+ item->next = ctrl->keyservers;
+ ctrl->keyservers = item;
+ }
+
+ if (!add_flag && !clear_flag && !help_flag) /* List configured keyservers. */
+ {
+ uri_item_t u;
+
+ for (u=ctrl->keyservers; u; u = u->next)
+ dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL);
+ }
+ err = 0;
+
+ leave:
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_ks_search[] =
+ "KS_SEARCH {<pattern>}\n"
+ "\n"
+ "Search the configured OpenPGP keyservers (see command KEYSERVER)\n"
+ "for keys matching PATTERN";
+static gpg_error_t
+cmd_ks_search (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ strlist_t list, sl;
+ char *p;
+ estream_t outfp;
+
+ /* No options for now. */
+ line = skip_options (line);
+
+ /* Break the line down into an strlist. Each pattern is
+ percent-plus escaped. */
+ list = NULL;
+ for (p=line; *p; line = p)
+ {
+ while (*p && *p != ' ')
+ p++;
+ if (*p)
+ *p++ = 0;
+ if (*line)
+ {
+ sl = xtrymalloc (sizeof *sl + strlen (line));
+ if (!sl)
+ {
+ err = gpg_error_from_syserror ();
+ free_strlist (list);
+ goto leave;
+ }
+ sl->flags = 0;
+ strcpy_escaped_plus (sl->d, line);
+ sl->next = list;
+ list = sl;
+ }
+ }
+
+ /* Setup an output stream and perform the search. */
+ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
+ if (!outfp)
+ err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
+ else
+ {
+ err = ks_action_search (ctrl, list, outfp);
+ es_fclose (outfp);
+ }
+
+ leave:
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_ks_get[] =
+ "KS_GET {<pattern>}\n"
+ "\n"
+ "Get the keys matching PATTERN from the configured OpenPGP keyservers\n"
+ "(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n"
+ "or an exact name indicastes by the '=' prefix.";
+static gpg_error_t
+cmd_ks_get (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ strlist_t list, sl;
+ char *p;
+ estream_t outfp;
+
+ /* No options for now. */
+ line = skip_options (line);
+
+ /* Break the line down into an strlist. Each pattern is by
+ definition percent-plus escaped. However we only support keyids
+ and fingerprints and thus the client has no need to apply the
+ escaping. */
+ list = NULL;
+ for (p=line; *p; line = p)
+ {
+ while (*p && *p != ' ')
+ p++;
+ if (*p)
+ *p++ = 0;
+ if (*line)
+ {
+ sl = xtrymalloc (sizeof *sl + strlen (line));
+ if (!sl)
+ {
+ err = gpg_error_from_syserror ();
+ free_strlist (list);
+ goto leave;
+ }
+ sl->flags = 0;
+ strcpy_escaped_plus (sl->d, line);
+ sl->next = list;
+ list = sl;
+ }
+ }
+
+ /* Setup an output stream and perform the get. */
+ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
+ if (!outfp)
+ err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
+ else
+ {
+ err = ks_action_get (ctrl, list, outfp);
+ es_fclose (outfp);
+ }
+
+ leave:
+ return leave_cmd (ctx, err);
+}
+
+
+static const char hlp_ks_fetch[] =
+ "KS_FETCH <URL>\n"
+ "\n"
+ "Get the key(s) from URL.";
+static gpg_error_t
+cmd_ks_fetch (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ estream_t outfp;
+
+ /* No options for now. */
+ line = skip_options (line);
+
+ /* Setup an output stream and perform the get. */
+ outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
+ if (!outfp)
+ err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
+ else
+ {
+ err = ks_action_fetch (ctrl, line, outfp);
+ es_fclose (outfp);
+ }
+
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_ks_put[] =
+ "KS_PUT\n"
+ "\n"
+ "Send a key to the configured OpenPGP keyservers. The actual key material\n"
+ "is then requested by Dirmngr using\n"
+ "\n"
+ " INQUIRE KEYBLOCK\n"
+ "\n"
+ "The client shall respond with a binary version of the keyblock. For LDAP\n"
+ "keyservers Dirmngr may ask for meta information of the provided keyblock\n"
+ "using:\n"
+ "\n"
+ " INQUIRE KEYBLOCK_INFO\n"
+ "\n"
+ "The client shall respond with a colon delimited info lines";
+static gpg_error_t
+cmd_ks_put (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+ unsigned char *value = NULL;
+ size_t valuelen;
+ unsigned char *info = NULL;
+ size_t infolen;
+
+ /* No options for now. */
+ line = skip_options (line);
+
+ /* Ask for the key material. */
+ err = assuan_inquire (ctx, "KEYBLOCK",
+ &value, &valuelen, MAX_KEYBLOCK_LENGTH);
+ if (err)
+ {
+ log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ if (!valuelen) /* No data returned; return a comprehensible error. */
+ {
+ err = gpg_error (GPG_ERR_MISSING_CERT);
+ goto leave;
+ }
+
+ /* Ask for the key meta data. Not actually needed for HKP servers
+ but we do it anyway test the client implementaion. */
+ err = assuan_inquire (ctx, "KEYBLOCK_INFO",
+ &info, &infolen, MAX_KEYBLOCK_LENGTH);
+ if (err)
+ {
+ log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Send the key. */
+ err = ks_action_put (ctrl, value, valuelen);
+
+ leave:
+ xfree (info);
+ xfree (value);
+ return leave_cmd (ctx, err);
+}
+
+
+
+
+static const char hlp_getinfo[] =
+ "GETINFO <what>\n"
+ "\n"
+ "Multi purpose command to return certain information. \n"
+ "Supported values of WHAT are:\n"
+ "\n"
+ "version - Return the version of the program.\n"
+ "pid - Return the process id of the server.\n"
+ "\n"
+ "socket_name - Return the name of the socket.\n";
+static gpg_error_t
+cmd_getinfo (assuan_context_t ctx, char *line)
+{
+ gpg_error_t err;
+
+ if (!strcmp (line, "version"))
+ {
+ const char *s = VERSION;
+ err = assuan_send_data (ctx, s, strlen (s));
+ }
+ else if (!strcmp (line, "pid"))
+ {
+ char numbuf[50];
+
+ snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
+ err = assuan_send_data (ctx, numbuf, strlen (numbuf));
+ }
+ else if (!strcmp (line, "socket_name"))
+ {
+ const char *s = dirmngr_user_socket_name ();
+
+ if (!s)
+ s = dirmngr_sys_socket_name ();
+
+ if (s)
+ err = assuan_send_data (ctx, s, strlen (s));
+ else
+ err = gpg_error (GPG_ERR_NO_DATA);
+ }
+ else
+ err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
+
+ return leave_cmd (ctx, err);
+}
+
+
+
+static const char hlp_killdirmngr[] =
+ "KILLDIRMNGR\n"
+ "\n"
+ "This command allows a user - given sufficient permissions -\n"
+ "to kill this dirmngr process.\n";
+static gpg_error_t
+cmd_killdirmngr (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err;
+
+ (void)line;
+
+ if (opt.system_daemon)
+ {
+ if (opt.system_service)
+ err = set_error (GPG_ERR_NOT_SUPPORTED,
+ "can't do that whilst running as system service");
+ else
+ err = check_owner_permission (ctx,
+ "no permission to kill this process");
+ }
+ else
+ err = 0;
+
+ if (!err)
+ {
+ ctrl->server_local->stopme = 1;
+ err = gpg_error (GPG_ERR_EOF);
+ }
+ return err;
+}
+
+
+static const char hlp_reloaddirmngr[] =
+ "RELOADDIRMNGR\n"
+ "\n"
+ "This command is an alternative to SIGHUP\n"
+ "to reload the configuration.";
+static gpg_error_t
+cmd_reloaddirmngr (assuan_context_t ctx, char *line)
+{
+ (void)ctx;
+ (void)line;
+
+ if (opt.system_daemon)
+ {
+#ifndef HAVE_W32_SYSTEM
+ {
+ gpg_err_code_t ec;
+ assuan_peercred_t cred;
+
+ ec = gpg_err_code (assuan_get_peercred (ctx, &cred));
+ if (!ec && cred->uid)
+ ec = GPG_ERR_EPERM; /* Only root may terminate. */
+ if (ec)
+ return set_error (ec, "no permission to reload this process");
+ }
+#endif
+ }
+
+ dirmngr_sighup_action ();
+ return 0;
+}
+
+
+
+
+/* Tell the assuan library about our commands. */
+static int
+register_commands (assuan_context_t ctx)
+{
+ static struct {
+ const char *name;
+ assuan_handler_t handler;
+ const char * const help;
+ } table[] = {
+ { "LDAPSERVER", cmd_ldapserver, hlp_ldapserver },
+ { "ISVALID", cmd_isvalid, hlp_isvalid },
+ { "CHECKCRL", cmd_checkcrl, hlp_checkcrl },
+ { "CHECKOCSP", cmd_checkocsp, hlp_checkocsp },
+ { "LOOKUP", cmd_lookup, hlp_lookup },
+ { "LOADCRL", cmd_loadcrl, hlp_loadcrl },
+ { "LISTCRLS", cmd_listcrls, hlp_listcrls },
+ { "CACHECERT", cmd_cachecert, hlp_cachecert },
+ { "VALIDATE", cmd_validate, hlp_validate },
+ { "KEYSERVER", cmd_keyserver, hlp_keyserver },
+ { "KS_SEARCH", cmd_ks_search, hlp_ks_search },
+ { "KS_GET", cmd_ks_get, hlp_ks_get },
+ { "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch },
+ { "KS_PUT", cmd_ks_put, hlp_ks_put },
+ { "GETINFO", cmd_getinfo, hlp_getinfo },
+ { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr },
+ { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr },
+ { NULL, NULL }
+ };
+ int i, j, rc;
+
+ for (i=j=0; table[i].name; i++)
+ {
+ rc = assuan_register_command (ctx, table[i].name, table[i].handler,
+ table[i].help);
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+
+/* Note that we do not reset the list of configured keyservers. */
+static gpg_error_t
+reset_notify (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ (void)line;
+
+#if USE_LDAP
+ ldapserver_list_free (ctrl->server_local->ldapservers);
+#endif /*USE_LDAP*/
+ ctrl->server_local->ldapservers = NULL;
+ return 0;
+}
+
+
+/* Startup the server and run the main command loop. With FD = -1
+ used stdin/stdout. */
+void
+start_command_handler (assuan_fd_t fd)
+{
+ static const char hello[] = "Dirmngr " VERSION " at your service";
+ static char *hello_line;
+ int rc;
+ assuan_context_t ctx;
+ ctrl_t ctrl;
+
+ ctrl = xtrycalloc (1, sizeof *ctrl);
+ if (ctrl)
+ ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
+ if (!ctrl || !ctrl->server_local)
+ {
+ log_error (_("can't allocate control structure: %s\n"),
+ strerror (errno));
+ xfree (ctrl);
+ return;
+ }
+
+ dirmngr_init_default_ctrl (ctrl);
+
+ rc = assuan_new (&ctx);
+ if (rc)
+ {
+ log_error (_("failed to allocate assuan context: %s\n"),
+ gpg_strerror (rc));
+ dirmngr_exit (2);
+ }
+
+ if (fd == ASSUAN_INVALID_FD)
+ {
+ assuan_fd_t filedes[2];
+
+ filedes[0] = assuan_fdopen (0);
+ filedes[1] = assuan_fdopen (1);
+ rc = assuan_init_pipe_server (ctx, filedes);
+ }
+ else
+ {
+ rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
+ }
+
+ if (rc)
+ {
+ assuan_release (ctx);
+ log_error (_("failed to initialize the server: %s\n"),
+ gpg_strerror(rc));
+ dirmngr_exit (2);
+ }
+
+ rc = register_commands (ctx);
+ if (rc)
+ {
+ log_error (_("failed to the register commands with Assuan: %s\n"),
+ gpg_strerror(rc));
+ dirmngr_exit (2);
+ }
+
+
+ if (!hello_line)
+ {
+ size_t n;
+ const char *cfgname;
+
+ cfgname = opt.config_filename? opt.config_filename : "[none]";
+
+ n = (30 + strlen (opt.homedir) + strlen (cfgname)
+ + strlen (hello) + 1);
+ hello_line = xmalloc (n+1);
+ snprintf (hello_line, n,
+ "Home: %s\n"
+ "Config: %s\n"
+ "%s",
+ opt.homedir,
+ cfgname,
+ hello);
+ hello_line[n] = 0;
+ }
+
+ ctrl->server_local->assuan_ctx = ctx;
+ assuan_set_pointer (ctx, ctrl);
+
+ assuan_set_hello_line (ctx, hello_line);
+ assuan_register_option_handler (ctx, option_handler);
+ assuan_register_reset_notify (ctx, reset_notify);
+
+ for (;;)
+ {
+ rc = assuan_accept (ctx);
+ if (rc == -1)
+ break;
+ if (rc)
+ {
+ log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc));
+ break;
+ }
+
+#ifndef HAVE_W32_SYSTEM
+ if (opt.verbose)
+ {
+ assuan_peercred_t peercred;
+
+ if (!assuan_get_peercred (ctx, &peercred))
+ log_info ("connection from process %ld (%ld:%ld)\n",
+ (long)peercred->pid, (long)peercred->uid,
+ (long)peercred->gid);
+ }
+#endif
+
+ rc = assuan_process (ctx);
+ if (rc)
+ {
+ log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
+ continue;
+ }
+ }
+
+#if USE_LDAP
+ ldap_wrapper_connection_cleanup (ctrl);
+
+ ldapserver_list_free (ctrl->server_local->ldapservers);
+#endif /*USE_LDAP*/
+ ctrl->server_local->ldapservers = NULL;
+
+ ctrl->server_local->assuan_ctx = NULL;
+ assuan_release (ctx);
+
+ if (ctrl->server_local->stopme)
+ dirmngr_exit (0);
+
+ if (ctrl->refcount)
+ log_error ("oops: connection control structure still referenced (%d)\n",
+ ctrl->refcount);
+ else
+ {
+ release_ctrl_ocsp_certs (ctrl);
+ xfree (ctrl->server_local);
+ xfree (ctrl);
+ }
+}
+
+
+/* Send a status line back to the client. KEYWORD is the status
+ keyword, the optional string arguments are blank separated added to
+ the line, the last argument must be a NULL. */
+gpg_error_t
+dirmngr_status (ctrl_t ctrl, const char *keyword, ...)
+{
+ gpg_error_t err = 0;
+ va_list arg_ptr;
+ const char *text;
+
+ va_start (arg_ptr, keyword);
+
+ if (ctrl->server_local)
+ {
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+ char buf[950], *p;
+ size_t n;
+
+ p = buf;
+ n = 0;
+ while ( (text = va_arg (arg_ptr, const char *)) )
+ {
+ if (n)
+ {
+ *p++ = ' ';
+ n++;
+ }
+ for ( ; *text && n < DIM (buf)-2; n++)
+ *p++ = *text++;
+ }
+ *p = 0;
+ err = assuan_write_status (ctx, keyword, buf);
+ }
+
+ va_end (arg_ptr);
+ return err;
+}
+
+
+/* Print a help status line. TEXTLEN gives the length of the text
+ from TEXT to be printed. The function splits text at LFs. */
+gpg_error_t
+dirmngr_status_help (ctrl_t ctrl, const char *text)
+{
+ gpg_error_t err = 0;
+
+ if (ctrl->server_local)
+ {
+ assuan_context_t ctx = ctrl->server_local->assuan_ctx;
+ char buf[950], *p;
+ size_t n;
+
+ do
+ {
+ p = buf;
+ n = 0;
+ for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++)
+ *p++ = *text++;
+ if (*text == '\n')
+ text++;
+ *p = 0;
+ err = assuan_write_status (ctx, "#", buf);
+ }
+ while (!err && *text);
+ }
+
+ return err;
+}
+
+/* Send a tick progress indicator back. Fixme: This is only done for
+ the currently active channel. */
+gpg_error_t
+dirmngr_tick (ctrl_t ctrl)
+{
+ static time_t next_tick = 0;
+ gpg_error_t err = 0;
+ time_t now = time (NULL);
+
+ if (!next_tick)
+ {
+ next_tick = now + 1;
+ }
+ else if ( now > next_tick )
+ {
+ if (ctrl)
+ {
+ err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL);
+ if (err)
+ {
+ /* Take this as in indication for a cancel request. */
+ err = gpg_error (GPG_ERR_CANCELED);
+ }
+ now = time (NULL);
+ }
+
+ next_tick = now + 1;
+ }
+ return err;
+}
diff --git a/dirmngr/sks-keyservers.netCA.pem b/dirmngr/sks-keyservers.netCA.pem
new file mode 100644
index 0000000..24a2ad2
--- /dev/null
+++ b/dirmngr/sks-keyservers.netCA.pem
@@ -0,0 +1,32 @@
+-----BEGIN CERTIFICATE-----
+MIIFizCCA3OgAwIBAgIJAK9zyLTPn4CPMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNV
+BAYTAk5PMQ0wCwYDVQQIDARPc2xvMR4wHAYDVQQKDBVza3Mta2V5c2VydmVycy5u
+ZXQgQ0ExHjAcBgNVBAMMFXNrcy1rZXlzZXJ2ZXJzLm5ldCBDQTAeFw0xMjEwMDkw
+MDMzMzdaFw0yMjEwMDcwMDMzMzdaMFwxCzAJBgNVBAYTAk5PMQ0wCwYDVQQIDARP
+c2xvMR4wHAYDVQQKDBVza3Mta2V5c2VydmVycy5uZXQgQ0ExHjAcBgNVBAMMFXNr
+cy1rZXlzZXJ2ZXJzLm5ldCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
+ggIBANdsWy4PXWNUCkS3L//nrd0GqN3dVwoBGZ6w94Tw2jPDPifegwxQozFXkG6I
+6A4TK1CJLXPvfz0UP0aBYyPmTNadDinaB9T4jIwd4rnxl+59GiEmqkN3IfPsv5Jj
+MkKUmJnvOT0DEVlEaO1UZIwx5WpfprB3mR81/qm4XkAgmYrmgnLXd/pJDAMk7y1F
+45b5zWofiD5l677lplcIPRbFhpJ6kDTODXh/XEdtF71EAeaOdEGOvyGDmCO0GWqS
+FDkMMPTlieLA/0rgFTcz4xwUYj/cD5e0ZBuSkYsYFAU3hd1cGfBue0cPZaQH2HYx
+Qk4zXD8S3F4690fRhr+tki5gyG6JDR67aKp3BIGLqm7f45WkX1hYp+YXywmEziM4
+aSbGYhx8hoFGfq9UcfPEvp2aoc8u5sdqjDslhyUzM1v3m3ZGbhwEOnVjljY6JJLx
+MxagxnZZSAY424ZZ3t71E/Mn27dm2w+xFRuoy8JEjv1d+BT3eChM5KaNwrj0IO/y
+u8kFIgWYA1vZ/15qMT+tyJTfyrNVV/7Df7TNeWyNqjJ5rBmt0M6NpHG7CrUSkBy9
+p8JhimgjP5r0FlEkgg+lyD+V79H98gQfVgP3pbJICz0SpBQf2F/2tyS4rLm+49rP
+fcOajiXEuyhpcmzgusAj/1FjrtlynH1r9mnNaX4e+rLWzvU5AgMBAAGjUDBOMB0G
+A1UdDgQWBBTkwyoJFGfYTVISTpM8E+igjdq28zAfBgNVHSMEGDAWgBTkwyoJFGfY
+TVISTpM8E+igjdq28zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAR
+OXnYwu3g1ZjHyley3fZI5aLPsaE17cOImVTehC8DcIphm2HOMR/hYTTL+V0G4P+u
+gH+6xeRLKSHMHZTtSBIa6GDL03434y9CBuwGvAFCMU2GV8w92/Z7apkAhdLToZA/
+X/iWP2jeaVJhxgEcH8uPrnSlqoPBcKC9PrgUzQYfSZJkLmB+3jEa3HKruy1abJP5
+gAdQvwvcPpvYRnIzUc9fZODsVmlHVFBCl2dlu/iHh2h4GmL4Da2rRkUMlbVTdioB
+UYIvMycdOkpH5wJftzw7cpjsudGas0PARDXCFfGyKhwBRFY7Xp7lbjtU5Rz0Gc04
+lPrhDf0pFE98Aw4jJRpFeWMjpXUEaG1cq7D641RpgcMfPFvOHY47rvDTS7XJOaUT
+BwRjmDt896s6vMDcaG/uXJbQjuzmmx3W2Idyh3s5SI0GTHb0IwMKYb4eBUIpQOnB
+cE77VnCYqKvN1NVYAqhWjXbY7XasZvszCRcOG+W3FqNaHOK/n/0ueb0uijdLan+U
+f4p1bjbAox8eAOQS/8a3bzkJzdyBNUKGx1BIK2IBL9bn/HravSDOiNRSnZ/R3l9G
+ZauX0tu7IIDlRCILXSyeazu0aj/vdT3YFQXPcvt5Fkf5wiNTo53f72/jYEJd6qph
+WrpoKqrwGwTpRUCMhYIUt65hsTxCiJJ5nKe39h46sg==
+-----END CERTIFICATE-----
diff --git a/dirmngr/validate.c b/dirmngr/validate.c
new file mode 100644
index 0000000..574eca6
--- /dev/null
+++ b/dirmngr/validate.c
@@ -0,0 +1,1159 @@
+/* validate.c - Validate a certificate chain.
+ * Copyright (C) 2001, 2003, 2004, 2008 Free Software Foundation, Inc.
+ * Copyright (C) 2004, 2006, 2008 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "dirmngr.h"
+#include "certcache.h"
+#include "crlcache.h"
+#include "validate.h"
+#include "misc.h"
+
+/* While running the validation function we need to keep track of the
+ certificates and the validation outcome of each. We use this type
+ for it. */
+struct chain_item_s
+{
+ struct chain_item_s *next;
+ ksba_cert_t cert; /* The certificate. */
+ unsigned char fpr[20]; /* Fingerprint of the certificate. */
+ int is_self_signed; /* This certificate is self-signed. */
+ int is_valid; /* The certifiate is valid except for revocations. */
+};
+typedef struct chain_item_s *chain_item_t;
+
+
+/* A couple of constants with Object Identifiers. */
+static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1";
+static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2";
+static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3";
+static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4";
+static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8";
+static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9";
+
+
+/* Prototypes. */
+static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert);
+
+
+
+
+/* Check whether CERT contains critical extensions we don't know
+ about. */
+static gpg_error_t
+unknown_criticals (ksba_cert_t cert)
+{
+ static const char *known[] = {
+ "2.5.29.15", /* keyUsage */
+ "2.5.29.19", /* basic Constraints */
+ "2.5.29.32", /* certificatePolicies */
+ "2.5.29.37", /* extendedKeyUsage */
+ NULL
+ };
+ int i, idx, crit;
+ const char *oid;
+ int unsupported;
+ strlist_t sl;
+ gpg_error_t err, rc;
+
+ rc = 0;
+ for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
+ &oid, &crit, NULL, NULL));idx++)
+ {
+ if (!crit)
+ continue;
+ for (i=0; known[i] && strcmp (known[i],oid); i++)
+ ;
+ unsupported = !known[i];
+
+ /* If this critical extension is not supported, check the list
+ of to be ignored extensions to see whether we claim that it
+ is supported. */
+ if (unsupported && opt.ignored_cert_extensions)
+ {
+ for (sl=opt.ignored_cert_extensions;
+ sl && strcmp (sl->d, oid); sl = sl->next)
+ ;
+ if (sl)
+ unsupported = 0;
+ }
+
+ if (unsupported)
+ {
+ log_error (_("critical certificate extension %s is not supported"),
+ oid);
+ rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
+ }
+ }
+ if (err && gpg_err_code (err) != GPG_ERR_EOF)
+ rc = err; /* Such an error takes precendence. */
+
+ return rc;
+}
+
+
+/* Basic check for supported policies. */
+static gpg_error_t
+check_cert_policy (ksba_cert_t cert)
+{
+ static const char *allowed[] = {
+ "2.289.9.9",
+ NULL
+ };
+ gpg_error_t err;
+ int idx;
+ char *p, *haystack;
+ char *policies;
+ int any_critical;
+
+ err = ksba_cert_get_cert_policies (cert, &policies);
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ return 0; /* No policy given. */
+ if (err)
+ return err;
+
+ /* STRING is a line delimited list of certifiate policies as stored
+ in the certificate. The line itself is colon delimited where the
+ first field is the OID of the policy and the second field either
+ N or C for normal or critical extension */
+ if (opt.verbose > 1)
+ log_info ("certificate's policy list: %s\n", policies);
+
+ /* The check is very minimal but won't give false positives */
+ any_critical = !!strstr (policies, ":C");
+
+ /* See whether we find ALLOWED (which is an OID) in POLICIES */
+ for (idx=0; allowed[idx]; idx++)
+ {
+ for (haystack=policies; (p=strstr (haystack, allowed[idx]));
+ haystack = p+1)
+ {
+ if ( !(p == policies || p[-1] == '\n') )
+ continue; /* Does not match the begin of a line. */
+ if (p[strlen (allowed[idx])] != ':')
+ continue; /* The length does not match. */
+ /* Yep - it does match: Return okay. */
+ ksba_free (policies);
+ return 0;
+ }
+ }
+
+ if (!any_critical)
+ {
+ log_info (_("Note: non-critical certificate policy not allowed"));
+ err = 0;
+ }
+ else
+ {
+ log_info (_("certificate policy not allowed"));
+ err = gpg_error (GPG_ERR_NO_POLICY_MATCH);
+ }
+
+ ksba_free (policies);
+ return err;
+}
+
+
+static gpg_error_t
+allowed_ca (ksba_cert_t cert, int *chainlen)
+{
+ gpg_error_t err;
+ int flag;
+
+ err = ksba_cert_is_ca (cert, &flag, chainlen);
+ if (err)
+ return err;
+ if (!flag)
+ {
+ if (!is_trusted_cert (cert))
+ {
+ /* The German SigG Root CA's certificate does not flag
+ itself as a CA; thus we relax this requirement if we
+ trust a root CA. I think this is reasonable. Note, that
+ gpgsm implements a far stricter scheme here. */
+ if (chainlen)
+ *chainlen = 3; /* That is what the SigG implements. */
+ if (opt.verbose)
+ log_info (_("accepting root CA not marked as a CA"));
+ }
+ else
+ {
+ log_error (_("issuer certificate is not marked as a CA"));
+ return gpg_error (GPG_ERR_BAD_CA_CERT);
+ }
+ }
+ return 0;
+}
+
+/* Helper for validate_cert_chain. */
+static gpg_error_t
+check_revocations (ctrl_t ctrl, chain_item_t chain)
+{
+ gpg_error_t err = 0;
+ int any_revoked = 0;
+ int any_no_crl = 0;
+ int any_crl_too_old = 0;
+ chain_item_t ci;
+
+ assert (ctrl->check_revocations_nest_level >= 0);
+ assert (chain);
+
+ if (ctrl->check_revocations_nest_level > 10)
+ {
+ log_error (_("CRL checking too deeply nested\n"));
+ return gpg_error(GPG_ERR_BAD_CERT_CHAIN);
+ }
+ ctrl->check_revocations_nest_level++;
+
+
+ for (ci=chain; ci; ci = ci->next)
+ {
+ assert (ci->cert);
+ if (ci == chain)
+ {
+ /* It does not make sense to check the root certificate for
+ revocations. In almost all cases this will lead to a
+ catch-22 as the root certificate is the final trust
+ anchor for the certificates and the CRLs. We expect the
+ user to remove root certificates from the list of trusted
+ certificates in case they have been revoked. */
+ if (opt.verbose)
+ cert_log_name (_("not checking CRL for"), ci->cert);
+ continue;
+ }
+
+ if (opt.verbose)
+ cert_log_name (_("checking CRL for"), ci->cert);
+ err = crl_cache_cert_isvalid (ctrl, ci->cert, 0);
+ if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN)
+ {
+ err = crl_cache_reload_crl (ctrl, ci->cert);
+ if (!err)
+ err = crl_cache_cert_isvalid (ctrl, ci->cert, 0);
+ }
+ switch (gpg_err_code (err))
+ {
+ case 0: err = 0; break;
+ case GPG_ERR_CERT_REVOKED: any_revoked = 1; err = 0; break;
+ case GPG_ERR_NO_CRL_KNOWN: any_no_crl = 1; err = 0; break;
+ case GPG_ERR_CRL_TOO_OLD: any_crl_too_old = 1; err = 0; break;
+ default: break;
+ }
+ }
+ ctrl->check_revocations_nest_level--;
+
+
+ if (err)
+ ;
+ else if (any_revoked)
+ err = gpg_error (GPG_ERR_CERT_REVOKED);
+ else if (any_no_crl)
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ else if (any_crl_too_old)
+ err = gpg_error (GPG_ERR_CRL_TOO_OLD);
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Check whether CERT is a root certificate. ISSUERDN and SUBJECTDN
+ are the DNs already extracted by the caller from CERT. Returns
+ True if this is the case. */
+static int
+is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn)
+{
+ gpg_error_t err;
+ int result = 0;
+ ksba_sexp_t serialno;
+ ksba_sexp_t ak_keyid;
+ ksba_name_t ak_name;
+ ksba_sexp_t ak_sn;
+ const char *ak_name_str;
+ ksba_sexp_t subj_keyid = NULL;
+
+ if (!issuerdn || !subjectdn)
+ return 0; /* No. */
+
+ if (strcmp (issuerdn, subjectdn))
+ return 0; /* No. */
+
+ err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn);
+ if (err)
+ {
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ return 1; /* Yes. Without a authorityKeyIdentifier this needs
+ to be the Root certifcate (our trust anchor). */
+ log_error ("error getting authorityKeyIdentifier: %s\n",
+ gpg_strerror (err));
+ return 0; /* Well, it is broken anyway. Return No. */
+ }
+
+ serialno = ksba_cert_get_serial (cert);
+ if (!serialno)
+ {
+ log_error ("error getting serialno: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Check whether the auth name's matches the issuer name+sn. If
+ that is the case this is a root certificate. */
+ ak_name_str = ksba_name_enum (ak_name, 0);
+ if (ak_name_str
+ && !strcmp (ak_name_str, issuerdn)
+ && !cmp_simple_canon_sexp (ak_sn, serialno))
+ {
+ result = 1; /* Right, CERT is self-signed. */
+ goto leave;
+ }
+
+ /* Similar for the ak_keyid. */
+ if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid)
+ && !cmp_simple_canon_sexp (ak_keyid, subj_keyid))
+ {
+ result = 1; /* Right, CERT is self-signed. */
+ goto leave;
+ }
+
+
+ leave:
+ ksba_free (subj_keyid);
+ ksba_free (ak_keyid);
+ ksba_name_release (ak_name);
+ ksba_free (ak_sn);
+ ksba_free (serialno);
+ return result;
+}
+
+
+/* Validate the certificate CHAIN up to the trust anchor. Optionally
+ return the closest expiration time in R_EXPTIME (this is useful for
+ caching issues). MODE is one of the VALIDATE_MODE_* constants.
+
+ If R_TRUST_ANCHOR is not NULL and the validation would fail only
+ because the root certificate is not trusted, the hexified
+ fingerprint of that root certificate is stored at R_TRUST_ANCHOR
+ and success is returned. The caller needs to free the value at
+ R_TRUST_ANCHOR; in all other cases NULL is stored there. */
+gpg_error_t
+validate_cert_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime,
+ int mode, char **r_trust_anchor)
+{
+ gpg_error_t err = 0;
+ int depth, maxdepth;
+ char *issuer = NULL;
+ char *subject = NULL;
+ ksba_cert_t subject_cert = NULL, issuer_cert = NULL;
+ ksba_isotime_t current_time;
+ ksba_isotime_t exptime;
+ int any_expired = 0;
+ int any_no_policy_match = 0;
+ chain_item_t chain;
+
+
+ if (r_exptime)
+ *r_exptime = 0;
+ *exptime = 0;
+
+ if (r_trust_anchor)
+ *r_trust_anchor = NULL;
+
+ if (!opt.system_daemon)
+ {
+ /* For backward compatibility we only do this in daemon mode. */
+ log_info (_("running in compatibility mode - "
+ "certificate chain not checked!\n"));
+ return 0; /* Okay. */
+ }
+
+ if (DBG_X509)
+ dump_cert ("subject", cert);
+
+ /* May the target certificate be used for this purpose? */
+ switch (mode)
+ {
+ case VALIDATE_MODE_OCSP:
+ err = cert_use_ocsp_p (cert);
+ break;
+ case VALIDATE_MODE_CRL:
+ case VALIDATE_MODE_CRL_RECURSIVE:
+ err = cert_use_crl_p (cert);
+ break;
+ default:
+ err = 0;
+ break;
+ }
+ if (err)
+ return err;
+
+ /* If we already validated the certificate not too long ago, we can
+ avoid the excessive computations and lookups unless the caller
+ asked for the expiration time. */
+ if (!r_exptime)
+ {
+ size_t buflen;
+ time_t validated_at;
+
+ err = ksba_cert_get_user_data (cert, "validated_at",
+ &validated_at, sizeof (validated_at),
+ &buflen);
+ if (err || buflen != sizeof (validated_at) || !validated_at)
+ err = 0; /* Not available or other error. */
+ else
+ {
+ /* If the validation is not older than 30 minutes we are ready. */
+ if (validated_at < gnupg_get_time () + (30*60))
+ {
+ if (opt.verbose)
+ log_info ("certificate is good (cached)\n");
+ /* Note, that we can't jump to leave here as this would
+ falsely updated the validation timestamp. */
+ return 0;
+ }
+ }
+ }
+
+ /* Get the current time. */
+ gnupg_get_isotime (current_time);
+
+ /* We walk up the chain until we find a trust anchor. */
+ subject_cert = cert;
+ maxdepth = 10;
+ chain = NULL;
+ depth = 0;
+ for (;;)
+ {
+ /* Get the subject and issuer name from the current
+ certificate. */
+ ksba_free (issuer);
+ ksba_free (subject);
+ issuer = ksba_cert_get_issuer (subject_cert, 0);
+ subject = ksba_cert_get_subject (subject_cert, 0);
+
+ if (!issuer)
+ {
+ log_error (_("no issuer found in certificate\n"));
+ err = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ /* Handle the notBefore and notAfter timestamps. */
+ {
+ ksba_isotime_t not_before, not_after;
+
+ err = ksba_cert_get_validity (subject_cert, 0, not_before);
+ if (!err)
+ err = ksba_cert_get_validity (subject_cert, 1, not_after);
+ if (err)
+ {
+ log_error (_("certificate with invalid validity: %s"),
+ gpg_strerror (err));
+ err = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ /* Keep track of the nearest expiration time in EXPTIME. */
+ if (*not_after)
+ {
+ if (!*exptime)
+ gnupg_copy_time (exptime, not_after);
+ else if (strcmp (not_after, exptime) < 0 )
+ gnupg_copy_time (exptime, not_after);
+ }
+
+ /* Check whether the certificate is already valid. */
+ if (*not_before && strcmp (current_time, not_before) < 0 )
+ {
+ log_error (_("certificate not yet valid"));
+ log_info ("(valid from ");
+ dump_isotime (not_before);
+ log_printf (")\n");
+ err = gpg_error (GPG_ERR_CERT_TOO_YOUNG);
+ goto leave;
+ }
+
+ /* Now check whether the certificate has expired. */
+ if (*not_after && strcmp (current_time, not_after) > 0 )
+ {
+ log_error (_("certificate has expired"));
+ log_info ("(expired at ");
+ dump_isotime (not_after);
+ log_printf (")\n");
+ any_expired = 1;
+ }
+ }
+
+ /* Do we have any critical extensions in the certificate we
+ can't handle? */
+ err = unknown_criticals (subject_cert);
+ if (err)
+ goto leave; /* yes. */
+
+ /* Check that given policies are allowed. */
+ err = check_cert_policy (subject_cert);
+ if (gpg_err_code (err) == GPG_ERR_NO_POLICY_MATCH)
+ {
+ any_no_policy_match = 1;
+ err = 0;
+ }
+ else if (err)
+ goto leave;
+
+ /* Is this a self-signed certificate? */
+ if (is_root_cert ( subject_cert, issuer, subject))
+ {
+ /* Yes, this is our trust anchor. */
+ if (check_cert_sig (subject_cert, subject_cert) )
+ {
+ log_error (_("selfsigned certificate has a BAD signature"));
+ err = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
+ : GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ /* Is this certificate allowed to act as a CA. */
+ err = allowed_ca (subject_cert, NULL);
+ if (err)
+ goto leave; /* No. */
+
+ err = is_trusted_cert (subject_cert);
+ if (!err)
+ ; /* Yes we trust this cert. */
+ else if (gpg_err_code (err) == GPG_ERR_NOT_TRUSTED)
+ {
+ char *fpr;
+
+ log_error (_("root certificate is not marked trusted"));
+ fpr = get_fingerprint_hexstring (subject_cert);
+ log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
+ dump_cert ("issuer", subject_cert);
+ if (r_trust_anchor)
+ {
+ /* Caller wants to do another trustiness check. */
+ *r_trust_anchor = fpr;
+ err = 0;
+ }
+ else
+ xfree (fpr);
+ }
+ else
+ {
+ log_error (_("checking trustworthiness of "
+ "root certificate failed: %s\n"),
+ gpg_strerror (err));
+ }
+ if (err)
+ goto leave;
+
+ /* Prepend the certificate to our list. */
+ {
+ chain_item_t ci;
+
+ ci = xtrycalloc (1, sizeof *ci);
+ if (!ci)
+ {
+ err = gpg_error_from_errno (errno);
+ goto leave;
+ }
+ ksba_cert_ref (subject_cert);
+ ci->cert = subject_cert;
+ cert_compute_fpr (subject_cert, ci->fpr);
+ ci->next = chain;
+ chain = ci;
+ }
+
+ if (opt.verbose)
+ {
+ if (r_trust_anchor && *r_trust_anchor)
+ log_info ("root certificate is good but not trusted\n");
+ else
+ log_info ("root certificate is good and trusted\n");
+ }
+
+ break; /* Okay: a self-signed certicate is an end-point. */
+ }
+
+ /* To avoid loops, we use an arbitary limit on the length of
+ the chain. */
+ depth++;
+ if (depth > maxdepth)
+ {
+ log_error (_("certificate chain too long\n"));
+ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+ goto leave;
+ }
+
+ /* Find the next cert up the tree. */
+ ksba_cert_release (issuer_cert); issuer_cert = NULL;
+ err = find_issuing_cert (ctrl, subject_cert, &issuer_cert);
+ if (err)
+ {
+ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+ {
+ log_error (_("issuer certificate not found"));
+ log_info ("issuer certificate: #/");
+ dump_string (issuer);
+ log_printf ("\n");
+ }
+ else
+ log_error (_("issuer certificate not found: %s\n"),
+ gpg_strerror (err));
+ /* Use a better understandable error code. */
+ err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
+ goto leave;
+ }
+
+/* try_another_cert: */
+ if (DBG_X509)
+ {
+ log_debug ("got issuer's certificate:\n");
+ dump_cert ("issuer", issuer_cert);
+ }
+
+ /* Now check the signature of the certificate. Well, we
+ should delay this until later so that faked certificates
+ can't be turned into a DoS easily. */
+ err = check_cert_sig (issuer_cert, subject_cert);
+ if (err)
+ {
+ log_error (_("certificate has a BAD signature"));
+#if 0
+ if (gpg_err_code (err) == GPG_ERR_BAD_SIGNATURE)
+ {
+ /* We now try to find other issuer certificates which
+ might have been used. This is required because some
+ CAs are reusing the issuer and subject DN for new
+ root certificates without using a authorityKeyIdentifier. */
+ rc = find_up (kh, subject_cert, issuer, 1);
+ if (!rc)
+ {
+ ksba_cert_t tmp_cert;
+
+ rc = keydb_get_cert (kh, &tmp_cert);
+ if (rc || !compare_certs (issuer_cert, tmp_cert))
+ {
+ /* The find next did not work or returned an
+ identical certificate. We better stop here
+ to avoid infinite checks. */
+ rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
+ ksba_cert_release (tmp_cert);
+ }
+ else
+ {
+ do_list (0, lm, fp, _("found another possible matching "
+ "CA certificate - trying again"));
+ ksba_cert_release (issuer_cert);
+ issuer_cert = tmp_cert;
+ goto try_another_cert;
+ }
+ }
+ }
+#endif
+ /* We give a more descriptive error code than the one
+ returned from the signature checking. */
+ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+ goto leave;
+ }
+
+ /* Check that the length of the chain is not longer than allowed
+ by the CA. */
+ {
+ int chainlen;
+
+ err = allowed_ca (issuer_cert, &chainlen);
+ if (err)
+ goto leave;
+ if (chainlen >= 0 && (depth - 1) > chainlen)
+ {
+ log_error (_("certificate chain longer than allowed by CA (%d)"),
+ chainlen);
+ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+ goto leave;
+ }
+ }
+
+ /* May that certificate be used for certification? */
+ err = cert_use_cert_p (issuer_cert);
+ if (err)
+ goto leave; /* No. */
+
+ /* Prepend the certificate to our list. */
+ {
+ chain_item_t ci;
+
+ ci = xtrycalloc (1, sizeof *ci);
+ if (!ci)
+ {
+ err = gpg_error_from_errno (errno);
+ goto leave;
+ }
+ ksba_cert_ref (subject_cert);
+ ci->cert = subject_cert;
+ cert_compute_fpr (subject_cert, ci->fpr);
+ ci->next = chain;
+ chain = ci;
+ }
+
+ if (opt.verbose)
+ log_info (_("certificate is good\n"));
+
+ /* Now to the next level up. */
+ subject_cert = issuer_cert;
+ issuer_cert = NULL;
+ }
+
+ if (!err)
+ { /* If we encountered an error somewhere during the checks, set
+ the error code to the most critical one */
+ if (any_expired)
+ err = gpg_error (GPG_ERR_CERT_EXPIRED);
+ else if (any_no_policy_match)
+ err = gpg_error (GPG_ERR_NO_POLICY_MATCH);
+ }
+
+ if (!err && opt.verbose)
+ {
+ chain_item_t citem;
+
+ log_info (_("certificate chain is good\n"));
+ for (citem = chain; citem; citem = citem->next)
+ cert_log_name (" certificate", citem->cert);
+ }
+
+ if (!err && mode != VALIDATE_MODE_CRL)
+ { /* Now that everything is fine, walk the chain and check each
+ certificate for revocations.
+
+ 1. item in the chain - The root certificate.
+ 2. item - the CA below the root
+ last item - the target certificate.
+
+ Now for each certificate in the chain check whether it has
+ been included in a CRL and thus be revoked. We don't do OCSP
+ here because this does not seem to make much sense. This
+ might become a recursive process and we should better cache
+ our validity results to avoid double work. Far worse a
+ catch-22 may happen for an improper setup hierachy and we
+ need a way to break up such a deadlock. */
+ err = check_revocations (ctrl, chain);
+ }
+
+ if (!err && opt.verbose)
+ {
+ if (r_trust_anchor && *r_trust_anchor)
+ log_info ("target certificate may be valid\n");
+ else
+ log_info ("target certificate is valid\n");
+ }
+ else if (err && opt.verbose)
+ log_info ("target certificate is NOT valid\n");
+
+
+ leave:
+ if (!err && !(r_trust_anchor && *r_trust_anchor))
+ {
+ /* With no error we can update the validation cache. We do this
+ for all certificates in the chain. Note that we can't use
+ the cache if the caller requested to check the trustiness of
+ the root certificate himself. Adding such a feature would
+ require us to also store the fingerprint of root
+ certificate. */
+ chain_item_t citem;
+ time_t validated_at = gnupg_get_time ();
+
+ for (citem = chain; citem; citem = citem->next)
+ {
+ err = ksba_cert_set_user_data (citem->cert, "validated_at",
+ &validated_at, sizeof (validated_at));
+ if (err)
+ {
+ log_error ("set_user_data(validated_at) failed: %s\n",
+ gpg_strerror (err));
+ err = 0;
+ }
+ }
+ }
+
+ if (r_exptime)
+ gnupg_copy_time (r_exptime, exptime);
+ ksba_free (issuer);
+ ksba_free (subject);
+ ksba_cert_release (issuer_cert);
+ if (subject_cert != cert)
+ ksba_cert_release (subject_cert);
+ while (chain)
+ {
+ chain_item_t ci_next = chain->next;
+ if (chain->cert)
+ ksba_cert_release (chain->cert);
+ xfree (chain);
+ chain = ci_next;
+ }
+ if (err && r_trust_anchor && *r_trust_anchor)
+ {
+ xfree (*r_trust_anchor);
+ *r_trust_anchor = NULL;
+ }
+ return err;
+}
+
+
+
+/* Return the public key algorithm id from the S-expression PKEY.
+ FIXME: libgcrypt should provide such a function. Note that this
+ implementation uses the names as used by libksba. */
+static int
+pk_algo_from_sexp (gcry_sexp_t pkey)
+{
+ gcry_sexp_t l1, l2;
+ const char *name;
+ size_t n;
+ int algo;
+
+ l1 = gcry_sexp_find_token (pkey, "public-key", 0);
+ if (!l1)
+ return 0; /* Not found. */
+ l2 = gcry_sexp_cadr (l1);
+ gcry_sexp_release (l1);
+
+ name = gcry_sexp_nth_data (l2, 0, &n);
+ if (!name)
+ algo = 0; /* Not found. */
+ else if (n==3 && !memcmp (name, "rsa", 3))
+ algo = GCRY_PK_RSA;
+ else if (n==3 && !memcmp (name, "dsa", 3))
+ algo = GCRY_PK_DSA;
+ else if (n==13 && !memcmp (name, "ambiguous-rsa", 13))
+ algo = GCRY_PK_RSA;
+ else
+ algo = 0;
+ gcry_sexp_release (l2);
+ return algo;
+}
+
+
+/* Check the signature on CERT using the ISSUER_CERT. This function
+ does only test the cryptographic signature and nothing else. It is
+ assumed that the ISSUER_CERT is valid. */
+static gpg_error_t
+check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert)
+{
+ gpg_error_t err;
+ const char *algoid;
+ gcry_md_hd_t md;
+ int i, algo;
+ ksba_sexp_t p;
+ size_t n;
+ gcry_sexp_t s_sig, s_hash, s_pkey;
+ const char *s;
+ char algo_name[16+1]; /* hash algorithm name converted to lower case. */
+ int digestlen;
+ unsigned char *digest;
+
+ /* Hash the target certificate using the algorithm from that certificate. */
+ algoid = ksba_cert_get_digest_algo (cert);
+ algo = gcry_md_map_name (algoid);
+ if (!algo)
+ {
+ log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+ s = gcry_md_algo_name (algo);
+ for (i=0; *s && i < sizeof algo_name - 1; s++, i++)
+ algo_name[i] = tolower (*s);
+ algo_name[i] = 0;
+
+ err = gcry_md_open (&md, algo, 0);
+ if (err)
+ {
+ log_error ("md_open failed: %s\n", gpg_strerror (err));
+ return err;
+ }
+ if (DBG_HASHING)
+ gcry_md_debug (md, "hash.cert");
+
+ err = ksba_cert_hash (cert, 1, HASH_FNC, md);
+ if (err)
+ {
+ log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (err));
+ gcry_md_close (md);
+ return err;
+ }
+ gcry_md_final (md);
+
+ /* Get the signature value out of the target certificate. */
+ p = ksba_cert_get_sig_val (cert);
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ gcry_md_close (md);
+ ksba_free (p);
+ return gpg_error (GPG_ERR_BUG);
+ }
+ if (DBG_CRYPTO)
+ {
+ int j;
+ log_debug ("signature value:");
+ for (j=0; j < n; j++)
+ log_printf (" %02X", p[j]);
+ log_printf ("\n");
+ }
+
+ err = gcry_sexp_sscan ( &s_sig, NULL, p, n);
+ ksba_free (p);
+ if (err)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err));
+ gcry_md_close (md);
+ return err;
+ }
+
+ /* Get the public key from the issuer certificate. */
+ p = ksba_cert_get_public_key (issuer_cert);
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ gcry_md_close (md);
+ ksba_free (p);
+ gcry_sexp_release (s_sig);
+ return gpg_error (GPG_ERR_BUG);
+ }
+ err = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
+ ksba_free (p);
+ if (err)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err));
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ return err;
+ }
+
+
+ /* Prepare the values for signature verification. At this point we
+ have these values:
+
+ S_PKEY - S-expression with the issuer's public key.
+ S_SIG - Signature value as given in the certrificate.
+ MD - Finalized hash context with hash of the certificate.
+ ALGO_NAME - Lowercase hash algorithm name
+ */
+ digestlen = gcry_md_get_algo_dlen (algo);
+ digest = gcry_md_read (md, algo);
+ if (pk_algo_from_sexp (s_pkey) == GCRY_PK_DSA)
+ {
+ if (digestlen != 20)
+ {
+ log_error (_("DSA requires the use of a 160 bit hash algorithm\n"));
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_pkey);
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+ if ( gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))",
+ (int)digestlen, digest) )
+ BUG ();
+ }
+ else /* Not DSA. */
+ {
+ if ( gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
+ algo_name, (int)digestlen, digest) )
+ BUG ();
+
+ }
+
+ err = gcry_pk_verify (s_sig, s_hash, s_pkey);
+ if (DBG_X509)
+ log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err));
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_hash);
+ gcry_sexp_release (s_pkey);
+ return err;
+}
+
+
+
+/* Return 0 if the cert is usable for encryption. A MODE of 0 checks
+ for signing, a MODE of 1 checks for encryption, a MODE of 2 checks
+ for verification and a MODE of 3 for decryption (just for
+ debugging). MODE 4 is for certificate signing, MODE 5 for OCSP
+ response signing, MODE 6 is for CRL signing. */
+static int
+cert_usage_p (ksba_cert_t cert, int mode)
+{
+ gpg_error_t err;
+ unsigned int use;
+ char *extkeyusages;
+ int have_ocsp_signing = 0;
+
+ err = ksba_cert_get_ext_key_usages (cert, &extkeyusages);
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ err = 0; /* No policy given. */
+ if (!err)
+ {
+ unsigned int extusemask = ~0; /* Allow all. */
+
+ if (extkeyusages)
+ {
+ char *p, *pend;
+ int any_critical = 0;
+
+ extusemask = 0;
+
+ p = extkeyusages;
+ while (p && (pend=strchr (p, ':')))
+ {
+ *pend++ = 0;
+ /* Only care about critical flagged usages. */
+ if ( *pend == 'C' )
+ {
+ any_critical = 1;
+ if ( !strcmp (p, oid_kp_serverAuth))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_KEY_ENCIPHERMENT
+ | KSBA_KEYUSAGE_KEY_AGREEMENT);
+ else if ( !strcmp (p, oid_kp_clientAuth))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_KEY_AGREEMENT);
+ else if ( !strcmp (p, oid_kp_codeSigning))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE);
+ else if ( !strcmp (p, oid_kp_emailProtection))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_NON_REPUDIATION
+ | KSBA_KEYUSAGE_KEY_ENCIPHERMENT
+ | KSBA_KEYUSAGE_KEY_AGREEMENT);
+ else if ( !strcmp (p, oid_kp_timeStamping))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_NON_REPUDIATION);
+ }
+
+ /* This is a hack to cope with OCSP. Note that we do
+ not yet fully comply with the requirements and that
+ the entire CRL/OCSP checking thing should undergo a
+ thorough review and probably redesign. */
+ if ( !strcmp (p, oid_kp_ocspSigning))
+ have_ocsp_signing = 1;
+
+ if ((p = strchr (pend, '\n')))
+ p++;
+ }
+ ksba_free (extkeyusages);
+ extkeyusages = NULL;
+
+ if (!any_critical)
+ extusemask = ~0; /* Reset to the don't care mask. */
+ }
+
+
+ err = ksba_cert_get_key_usage (cert, &use);
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ {
+ err = 0;
+ if (opt.verbose && mode < 2)
+ log_info (_("no key usage specified - assuming all usages\n"));
+ use = ~0;
+ }
+
+ /* Apply extKeyUsage. */
+ use &= extusemask;
+
+ }
+ if (err)
+ {
+ log_error (_("error getting key usage information: %s\n"),
+ gpg_strerror (err));
+ ksba_free (extkeyusages);
+ return err;
+ }
+
+ if (mode == 4)
+ {
+ if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN)))
+ return 0;
+ log_info (_("certificate should not have "
+ "been used for certification\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+ }
+
+ if (mode == 5)
+ {
+ if (use != ~0
+ && (have_ocsp_signing
+ || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN
+ |KSBA_KEYUSAGE_CRL_SIGN))))
+ return 0;
+ log_info (_("certificate should not have "
+ "been used for OCSP response signing\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+ }
+
+ if (mode == 6)
+ {
+ if ((use & (KSBA_KEYUSAGE_CRL_SIGN)))
+ return 0;
+ log_info (_("certificate should not have "
+ "been used for CRL signing\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+ }
+
+ if ((use & ((mode&1)?
+ (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT):
+ (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
+ )
+ return 0;
+
+ log_info (mode==3? _("certificate should not have been used "
+ "for encryption\n"):
+ mode==2? _("certificate should not have been used for signing\n"):
+ mode==1? _("certificate is not usable for encryption\n"):
+ _("certificate is not usable for signing\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+}
+
+/* Return 0 if the certificate CERT is usable for certification. */
+gpg_error_t
+cert_use_cert_p (ksba_cert_t cert)
+{
+ return cert_usage_p (cert, 4);
+}
+
+/* Return 0 if the certificate CERT is usable for signing OCSP
+ responses. */
+gpg_error_t
+cert_use_ocsp_p (ksba_cert_t cert)
+{
+ return cert_usage_p (cert, 5);
+}
+
+/* Return 0 if the certificate CERT is usable for signing CRLs. */
+gpg_error_t
+cert_use_crl_p (ksba_cert_t cert)
+{
+ return cert_usage_p (cert, 6);
+}
diff --git a/dirmngr/validate.h b/dirmngr/validate.h
new file mode 100644
index 0000000..0d9283c
--- /dev/null
+++ b/dirmngr/validate.h
@@ -0,0 +1,55 @@
+/* validate.h - Certificate validation
+ * Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#ifndef VALIDATE_H
+#define VALIDATE_H
+
+
+enum {
+ /* Simple certificate validation mode. */
+ VALIDATE_MODE_CERT = 0,
+ /* Standard CRL issuer certificate validation; i.e. CRLs are not
+ considered for CRL issuer certificates. */
+ VALIDATE_MODE_CRL = 1,
+ /* Full CRL validation. */
+ VALIDATE_MODE_CRL_RECURSIVE = 2,
+ /* Validation as used for OCSP. */
+ VALIDATE_MODE_OCSP = 3
+};
+
+
+/* Validate the certificate CHAIN up to the trust anchor. Optionally
+ return the closest expiration time in R_EXPTIME. */
+gpg_error_t validate_cert_chain (ctrl_t ctrl,
+ ksba_cert_t cert, ksba_isotime_t r_exptime,
+ int mode, char **r_trust_anchor);
+
+/* Return 0 if the certificate CERT is usable for certification. */
+gpg_error_t cert_use_cert_p (ksba_cert_t cert);
+
+/* Return 0 if the certificate CERT is usable for signing OCSP
+ responses. */
+gpg_error_t cert_use_ocsp_p (ksba_cert_t cert);
+
+/* Return 0 if the certificate CERT is usable for signing CRLs. */
+gpg_error_t cert_use_crl_p (ksba_cert_t cert);
+
+
+#endif /*VALIDATE_H*/
diff --git a/dirmngr/w32-ldap-help.h b/dirmngr/w32-ldap-help.h
new file mode 100644
index 0000000..80668d9
--- /dev/null
+++ b/dirmngr/w32-ldap-help.h
@@ -0,0 +1,169 @@
+/* w32-ldap-help.h - Map utf8 based API into a wchar_t API.
+ * Copyright (C) 2010 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef W32_LDAP_HELP_H
+#define W32_LDAP_HELP_H
+
+#ifndef HAVE_W32CE_SYSTEM
+# error This is only required for W32CE.
+#endif
+
+
+static inline LDAP *
+_dirmngr_ldap_init (const char *host, unsigned short port)
+{
+ LDAP *ld;
+ wchar_t *whost = NULL;
+
+ if (host)
+ {
+ whost = utf8_to_wchar (host);
+ if (!whost)
+ return NULL;
+ }
+ ld = ldap_init (whost, port);
+ xfree (whost);
+ return ld;
+}
+
+
+static inline ULONG
+_dirmngr_ldap_simple_bind_s (LDAP *ld, const char *user, const char *pass)
+{
+ ULONG ret;
+ wchar_t *wuser, *wpass;
+
+ wuser = user? utf8_to_wchar (user) : NULL;
+ wpass = pass? utf8_to_wchar (pass) : NULL;
+ /* We can't easily map errnos to ldap_errno, thus we pass a NULL to
+ the function in the hope that the server will throw an error. */
+ ret = ldap_simple_bind_s (ld, wuser, wpass);
+ xfree (wpass);
+ xfree (wuser);
+ return ret;
+}
+
+
+static inline ULONG
+_dirmngr_ldap_search_st (LDAP *ld, const char *base, ULONG scope,
+ const char *filter, char **attrs,
+ ULONG attrsonly, struct timeval *timeout,
+ LDAPMessage **res)
+{
+ ULONG ret = LDAP_NO_MEMORY;
+ wchar_t *wbase = NULL;
+ wchar_t *wfilter = NULL;
+ wchar_t **wattrs = NULL;
+ int i;
+
+ if (base)
+ {
+ wbase = utf8_to_wchar (base);
+ if (!wbase)
+ goto leave;
+ }
+ if (filter)
+ {
+ wfilter = utf8_to_wchar (filter);
+ if (!wfilter)
+ goto leave;
+ }
+ if (attrs)
+ {
+ for (i=0; attrs[i]; i++)
+ ;
+ wattrs = xtrycalloc (i+1, sizeof *wattrs);
+ if (!wattrs)
+ goto leave;
+ for (i=0; attrs[i]; i++)
+ {
+ wattrs[i] = utf8_to_wchar (attrs[i]);
+ if (!wattrs[i])
+ goto leave;
+ }
+ }
+
+ ret = ldap_search_st (ld, wbase, scope, wfilter, wattrs, attrsonly,
+ (struct l_timeval *)timeout, res);
+
+ leave:
+ if (wattrs)
+ {
+ for (i=0; wattrs[i]; i++)
+ xfree (wattrs[i]);
+ xfree (wattrs);
+ }
+ xfree (wfilter);
+ xfree (wbase);
+ return ret;
+}
+
+
+static inline char *
+_dirmngr_ldap_first_attribute (LDAP *ld, LDAPMessage *msg, BerElement **elem)
+{
+ wchar_t *wattr;
+ char *attr;
+
+ wattr = ldap_first_attribute (ld, msg, elem);
+ if (!wattr)
+ return NULL;
+ attr = wchar_to_utf8 (wattr);
+ ldap_memfree (wattr);
+ return attr;
+}
+
+
+static inline char *
+_dirmngr_ldap_next_attribute (LDAP *ld, LDAPMessage *msg, BerElement *elem)
+{
+ wchar_t *wattr;
+ char *attr;
+
+ wattr = ldap_next_attribute (ld, msg, elem);
+ if (!wattr)
+ return NULL;
+ attr = wchar_to_utf8 (wattr);
+ ldap_memfree (wattr);
+ return attr;
+}
+
+static inline BerValue **
+_dirmngr_ldap_get_values_len (LDAP *ld, LDAPMessage *msg, const char *attr)
+{
+ BerValue **ret;
+ wchar_t *wattr;
+
+ if (attr)
+ {
+ wattr = utf8_to_wchar (attr);
+ if (!wattr)
+ return NULL;
+ }
+ else
+ wattr = NULL;
+
+ ret = ldap_get_values_len (ld, msg, wattr);
+ xfree (wattr);
+
+ return ret;
+}
+
+
+#endif /*W32_LDAP_HELP_H*/