summaryrefslogtreecommitdiffstats
path: root/dirmngr
diff options
context:
space:
mode:
authorWerner Koch <wk@gnupg.org>2010-06-09 18:53:51 +0200
committerWerner Koch <wk@gnupg.org>2010-06-09 18:53:51 +0200
commitc3f08dcb7266efeac84f5f720ec0a353a45e950d (patch)
tree51501aa7d0e6dadb80576a1e982fdfde871bd2ad /dirmngr
parent2010-06-08 Marcus Brinkmann <marcus@g10code.de> (diff)
downloadgnupg2-c3f08dcb7266efeac84f5f720ec0a353a45e950d.tar.xz
gnupg2-c3f08dcb7266efeac84f5f720ec0a353a45e950d.zip
Merged Dirmngr with GnuPG.
A few code changes to support dirmngr.
Diffstat (limited to 'dirmngr')
-rw-r--r--dirmngr/ChangeLog1345
-rw-r--r--dirmngr/ChangeLog.1802
-rw-r--r--dirmngr/Makefile.am65
-rw-r--r--dirmngr/OAUTHORS40
-rw-r--r--dirmngr/ONEWS240
-rw-r--r--dirmngr/b64dec.c217
-rw-r--r--dirmngr/b64enc.c213
-rw-r--r--dirmngr/cdb.h91
-rw-r--r--dirmngr/cdblib.c925
-rw-r--r--dirmngr/certcache.c1384
-rw-r--r--dirmngr/certcache.h103
-rw-r--r--dirmngr/crlcache.c2544
-rw-r--r--dirmngr/crlcache.h70
-rw-r--r--dirmngr/crlfetch.c479
-rw-r--r--dirmngr/crlfetch.h93
-rw-r--r--dirmngr/dirmngr-client.c1042
-rw-r--r--dirmngr/dirmngr.c1829
-rw-r--r--dirmngr/dirmngr.h189
-rw-r--r--dirmngr/dirmngr_ldap.c646
-rw-r--r--dirmngr/get-path.c620
-rw-r--r--dirmngr/http.c1861
-rw-r--r--dirmngr/http.h109
-rw-r--r--dirmngr/ldap-url.c932
-rw-r--r--dirmngr/ldap-url.h50
-rw-r--r--dirmngr/ldap.c1499
-rw-r--r--dirmngr/ldapserver.c133
-rw-r--r--dirmngr/ldapserver.h90
-rw-r--r--dirmngr/misc.c486
-rw-r--r--dirmngr/misc.h87
-rw-r--r--dirmngr/no-libgcrypt.c154
-rw-r--r--dirmngr/ocsp.c799
-rw-r--r--dirmngr/ocsp.h31
-rw-r--r--dirmngr/server.c1539
-rw-r--r--dirmngr/validate.c1160
-rw-r--r--dirmngr/validate.h55
35 files changed, 21922 insertions, 0 deletions
diff --git a/dirmngr/ChangeLog b/dirmngr/ChangeLog
new file mode 100644
index 000000000..b5294642f
--- /dev/null
+++ b/dirmngr/ChangeLog
@@ -0,0 +1,1345 @@
+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 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/ChangeLog.1 b/dirmngr/ChangeLog.1
new file mode 100644
index 000000000..6d7a513e2
--- /dev/null
+++ b/dirmngr/ChangeLog.1
@@ -0,0 +1,802 @@
+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
diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am
new file mode 100644
index 000000000..b4ea8c66a
--- /dev/null
+++ b/dirmngr/Makefile.am
@@ -0,0 +1,65 @@
+# 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
+
+bin_PROGRAMS = dirmngr dirmngr-client
+
+libexec_PROGRAMS = dirmngr_ldap
+
+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) $(PTH_CFLAGS)
+
+BUILT_SOURCES = no-libgcrypt.c
+
+noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h
+
+dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \
+ ldapserver.h ldapserver.c certcache.c certcache.h \
+ b64dec.c cdb.h cdblib.c ldap.c http.c http.h misc.c \
+ ocsp.c ocsp.h validate.c validate.h
+
+dirmngr_LDADD = $(libcommonpth) $(DNSLIBS) $(LIBASSUAN_LIBS) \
+ $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(PTH_LIBS) $(LIBINTL) $(LIBICONV)
+
+if HAVE_W32_SYSTEM
+ldap_url = ldap-url.h ldap-url.c
+else
+ldap_url =
+endif
+
+dirmngr_ldap_SOURCES = dirmngr_ldap.c $(ldap_url) no-libgcrypt.c
+dirmngr_ldap_CFLAGS = $(GPG_ERROR_CFLAGS)
+dirmngr_ldap_LDFLAGS =
+dirmngr_ldap_LDADD = $(libcommon) $(DNSLIBS) \
+ $(GPG_ERROR_LIBS) $(LDAPLIBS) $(LIBINTL) $(LIBICONV)
+
+dirmngr_client_SOURCES = dirmngr-client.c b64enc.c no-libgcrypt.c
+dirmngr_client_LDADD = $(libcommon) $(LIBASSUAN_LIBS) \
+ $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV)
+
+
+no-libgcrypt.c : $(top_srcdir)/tools/no-libgcrypt.c
+ cat $(top_srcdir)/tools/no-libgcrypt.c > no-libgcrypt.c
diff --git a/dirmngr/OAUTHORS b/dirmngr/OAUTHORS
new file mode 100644
index 000000000..f9adc324c
--- /dev/null
+++ b/dirmngr/OAUTHORS
@@ -0,0 +1,40 @@
+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 000000000..a9ec4d77c
--- /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/b64dec.c b/dirmngr/b64dec.c
new file mode 100644
index 000000000..af223aef2
--- /dev/null
+++ b/dirmngr/b64dec.c
@@ -0,0 +1,217 @@
+/* b64dec.c - Simple Base64 decoder.
+ * Copyright (C) 2008 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 <errno.h>
+#include <assert.h>
+
+#include "i18n.h"
+#include "util.h"
+
+
+/* The reverse base-64 list used for base-64 decoding. */
+static unsigned char const asctobin[128] =
+ {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f,
+ 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
+ 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+ 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+ 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+ 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff
+ };
+
+enum decoder_states
+ {
+ s_init, s_idle, s_lfseen, s_begin,
+ s_b64_0, s_b64_1, s_b64_2, s_b64_3,
+ s_waitendtitle, s_waitend
+ };
+
+
+
+/* Initialize the context for the base64 decoder. If TITLE is NULL a
+ plain base64 decoding is done. If it is the empty string the
+ decoder will skip everything until a "-----BEGIN " line has been
+ seen, decoding ends at a "----END " line.
+
+ Not yet implemented: If TITLE is either "PGP" or begins with "PGP "
+ the PGP armor lines are skipped as well. */
+gpg_error_t
+b64dec_start (struct b64state *state, const char *title)
+{
+ memset (state, 0, sizeof *state);
+ if (title)
+ {
+ if (!strncmp (title, "PGP", 3) && (!title[3] || title[3] == ' '))
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ state->title = xtrystrdup (title);
+ if (!state->title)
+ return gpg_error_from_syserror ();
+ state->idx = s_init;
+ }
+ else
+ state->idx = s_b64_0;
+ return 0;
+}
+
+
+/* Do in-place decoding of base-64 data of LENGTH in BUFFER. Stores the
+ new length of the buffer at R_NBYTES. */
+gpg_error_t
+b64dec_proc (struct b64state *state, void *buffer, size_t length,
+ size_t *r_nbytes)
+{
+ enum decoder_states ds = state->idx;
+ unsigned char val = state->radbuf[0];
+ int pos = state->quad_count;
+ char *d, *s;
+
+ if (state->stop_seen)
+ {
+ *r_nbytes = 0;
+ return gpg_error (GPG_ERR_EOF);
+ }
+
+ for (s=d=buffer; length && !state->stop_seen; length--, s++)
+ {
+ switch (ds)
+ {
+ case s_idle:
+ if (*s == '\n')
+ {
+ ds = s_lfseen;
+ pos = 0;
+ }
+ break;
+ case s_init:
+ ds = s_lfseen;
+ case s_lfseen:
+ if (*s != "-----BEGIN "[pos])
+ ds = s_idle;
+ else if (pos == 10)
+ ds = s_begin;
+ else
+ pos++;
+ break;
+ case s_begin:
+ if (*s == '\n')
+ ds = s_b64_0;
+ break;
+ case s_b64_0:
+ case s_b64_1:
+ case s_b64_2:
+ case s_b64_3:
+ {
+ int c;
+
+ if (*s == '-' && state->title)
+ {
+ /* Not a valid Base64 character: assume end
+ header. */
+ ds = s_waitend;
+ }
+ else if (*s == '=')
+ {
+ /* Pad character: stop */
+ if (ds == s_b64_1)
+ *d++ = val;
+ ds = state->title? s_waitendtitle : s_waitend;
+ }
+ else if (*s == '\n' || *s == ' ' || *s == '\r' || *s == '\t')
+ ; /* Skip white spaces. */
+ else if ( (*s & 0x80)
+ || (c = asctobin[*(unsigned char *)s]) == 255)
+ {
+ /* Skip invalid encodings. */
+ state->invalid_encoding = 1;
+ }
+ else if (ds == s_b64_0)
+ {
+ val = c << 2;
+ ds = s_b64_1;
+ }
+ else if (ds == s_b64_1)
+ {
+ val |= (c>>4)&3;
+ *d++ = val;
+ val = (c<<4)&0xf0;
+ ds = s_b64_2;
+ }
+ else if (ds == s_b64_2)
+ {
+ val |= (c>>2)&15;
+ *d++ = val;
+ val = (c<<6)&0xc0;
+ ds = s_b64_3;
+ }
+ else
+ {
+ val |= c&0x3f;
+ *d++ = val;
+ ds = s_b64_0;
+ }
+ }
+ break;
+ case s_waitendtitle:
+ if (*s == '-')
+ ds = s_waitend;
+ break;
+ case s_waitend:
+ if ( *s == '\n')
+ state->stop_seen = 1;
+ break;
+ default:
+ BUG();
+ }
+ }
+
+
+ state->idx = ds;
+ state->radbuf[0] = val;
+ state->quad_count = pos;
+ *r_nbytes = (d -(char*) buffer);
+ return 0;
+}
+
+
+/* This function needs to be called before releasing the decoder
+ state. It may return an error code in case an encoding error has
+ been found during decoding. */
+gpg_error_t
+b64dec_finish (struct b64state *state)
+{
+ xfree (state->title);
+ state->title = NULL;
+ return state->invalid_encoding? gpg_error(GPG_ERR_BAD_DATA): 0;
+}
+
diff --git a/dirmngr/b64enc.c b/dirmngr/b64enc.c
new file mode 100644
index 000000000..4429a8e75
--- /dev/null
+++ b/dirmngr/b64enc.c
@@ -0,0 +1,213 @@
+/* b64enc.c - Simple Base64 encoder.
+ * Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "i18n.h"
+#include "util.h"
+
+#define B64ENC_DID_HEADER 1
+#define B64ENC_DID_TRAILER 2
+#define B64ENC_NO_LINEFEEDS 16
+
+
+/* The base-64 character list */
+static unsigned char bintoasc[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+
+/* Prepare for base-64 writing to the stream FP. If TITLE is not NULL
+ and not an empty string, this string will be used as the title for
+ the armor lines, with TITLE being an empty string, we don't write
+ the header lines and furthermore even don't write any linefeeds.
+ With TITLE beeing NULL, we merely don't write header but make sure
+ that lines are not too long. Note, that we don't write any output
+ unless at least one byte get written using b64enc_write. */
+gpg_error_t
+b64enc_start (struct b64state *state, FILE *fp, const char *title)
+{
+ memset (state, 0, sizeof *state);
+ state->fp = fp;
+ if (title && !*title)
+ state->flags |= B64ENC_NO_LINEFEEDS;
+ else if (title)
+ {
+ state->title = strdup (title);
+ if (!state->title)
+ return gpg_error_from_errno (errno);
+ }
+ return 0;
+}
+
+
+/* Write NBYTES from BUFFER to the Base 64 stream identified by
+ STATE. With BUFFER and NBYTES being 0, merely do a fflush on the
+ stream. */
+gpg_error_t
+b64enc_write (struct b64state *state, const void *buffer, size_t nbytes)
+{
+ unsigned char radbuf[4];
+ int idx, quad_count;
+ const unsigned char *p;
+ FILE *fp = state->fp;
+
+
+ if (!nbytes)
+ {
+ if (buffer && fflush (fp))
+ goto write_error;
+ return 0;
+ }
+
+ if (!(state->flags & B64ENC_DID_HEADER))
+ {
+ if (state->title)
+ {
+ if ( fputs ("-----BEGIN ", fp) == EOF
+ || fputs (state->title, fp) == EOF
+ || fputs ("-----\n", fp) == EOF)
+ goto write_error;
+ }
+ state->flags |= B64ENC_DID_HEADER;
+ }
+
+ idx = state->idx;
+ quad_count = state->quad_count;
+ assert (idx < 4);
+ memcpy (radbuf, state->radbuf, idx);
+
+ for (p=buffer; nbytes; p++, nbytes--)
+ {
+ radbuf[idx++] = *p;
+ if (idx > 2)
+ {
+ char tmp[4];
+
+ tmp[0] = bintoasc[(*radbuf >> 2) & 077];
+ tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077];
+ tmp[2] = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077];
+ tmp[3] = bintoasc[radbuf[2]&077];
+ for (idx=0; idx < 4; idx++)
+ putc (tmp[idx], fp);
+ idx = 0;
+ if (ferror (fp))
+ goto write_error;
+ if (++quad_count >= (64/4))
+ {
+ quad_count = 0;
+ if (!(state->flags & B64ENC_NO_LINEFEEDS)
+ && fputs ("\n", fp) == EOF)
+ goto write_error;
+ }
+ }
+ }
+ memcpy (state->radbuf, radbuf, idx);
+ state->idx = idx;
+ state->quad_count = quad_count;
+ return 0;
+
+ write_error:
+ return gpg_error_from_errno (errno);
+}
+
+gpg_error_t
+b64enc_finish (struct b64state *state)
+{
+ gpg_error_t err = 0;
+ unsigned char radbuf[4];
+ int idx, quad_count;
+ FILE *fp;
+
+ if (!(state->flags & B64ENC_DID_HEADER))
+ goto cleanup;
+
+ /* Flush the base64 encoding */
+ fp = state->fp;
+ idx = state->idx;
+ quad_count = state->quad_count;
+ assert (idx < 4);
+ memcpy (radbuf, state->radbuf, idx);
+
+ if (idx)
+ {
+ char tmp[4];
+
+ tmp[0] = bintoasc[(*radbuf>>2)&077];
+ if (idx == 1)
+ {
+ tmp[1] = bintoasc[((*radbuf << 4) & 060) & 077];
+ tmp[2] = '=';
+ tmp[3] = '=';
+ }
+ else
+ {
+ tmp[1] = bintoasc[(((*radbuf<<4)&060)|((radbuf[1]>>4)&017))&077];
+ tmp[2] = bintoasc[((radbuf[1] << 2) & 074) & 077];
+ tmp[3] = '=';
+ }
+ for (idx=0; idx < 4; idx++)
+ putc (tmp[idx], fp);
+ idx = 0;
+ if (ferror (fp))
+ goto write_error;
+
+ if (++quad_count >= (64/4))
+ {
+ quad_count = 0;
+ if (!(state->flags & B64ENC_NO_LINEFEEDS)
+ && fputs ("\n", fp) == EOF)
+ goto write_error;
+ }
+ }
+
+ /* Finish the last line and write the trailer. */
+ if (quad_count
+ && !(state->flags & B64ENC_NO_LINEFEEDS)
+ && fputs ("\n", fp) == EOF)
+ goto write_error;
+
+ if (state->title)
+ {
+ if ( fputs ("-----END ", fp) == EOF
+ || fputs (state->title, fp) == EOF
+ || fputs ("-----\n", fp) == EOF)
+ goto write_error;
+ }
+
+ goto cleanup;
+
+ write_error:
+ err = gpg_error_from_errno (errno);
+
+ cleanup:
+ if (state->title)
+ {
+ free (state->title);
+ state->title = NULL;
+ }
+ state->fp = NULL;
+ return err;
+}
+
diff --git a/dirmngr/cdb.h b/dirmngr/cdb.h
new file mode 100644
index 000000000..73cc9952e
--- /dev/null
+++ b/dirmngr/cdb.h
@@ -0,0 +1,91 @@
+/* $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 */
+ 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 000000000..de60fe926
--- /dev/null
+++ b/dirmngr/cdblib.c
@@ -0,0 +1,925 @@
+/* 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 "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;
+ unsigned fsize;
+#ifdef _WIN32
+ HANDLE hFile, hMapping;
+#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) {
+ errno = EPROTO;
+ return -1;
+ }
+ fsize = (unsigned)(st.st_size & 0xffffffffu);
+ /* memory-map file */
+#ifdef _WIN32
+ hFile = (HANDLE) _get_osfhandle(fd);
+ 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;
+#else
+ 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
+ HANDLE hFile, hMapping;
+#endif
+#ifdef _WIN32
+ hFile = (HANDLE) _get_osfhandle(cdbp->cdb_fd);
+ hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
+ UnmapViewOfFile((void*) cdbp->cdb_mem);
+ CloseHandle(hMapping);
+#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) {
+ 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 ? */
+ {
+ 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 */
+ errno = EPROTO;
+ return -1;
+ }
+ if (cdb_unpack(cdbp->cdb_mem + pos) == klen) {
+ if (cdbp->cdb_fsize - klen < pos + 8) {
+ 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) {
+ 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)
+ {
+ 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) {
+ errno = EPROTO;
+ return -1;
+ }
+ if (cdb_unpack(cdbp->cdb_mem + pos) == cdbfp->cdb_klen) {
+ if (cdbp->cdb_fsize - cdbfp->cdb_klen < pos + 8) {
+ 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) {
+ 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)
+ {
+ 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)
+ {
+ 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)
+ {
+ 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)
+ 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)) {
+ 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) {
+ 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) {
+ 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) {
+ 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:
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) ||
+ vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) {
+ 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) {
+ 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) {
+ 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 000000000..c40bb17d0
--- /dev/null
+++ b/dirmngr/certcache.c
@@ -0,0 +1,1384 @@
+/* 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 <pth.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 pth 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 pth_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)
+{
+ if (!pth_rwlock_init (&cert_cache_lock))
+ log_fatal (_("can't initialize certificate cache lock: %s\n"),
+ strerror (errno));
+}
+
+static void
+acquire_cache_read_lock (void)
+{
+ if (!pth_rwlock_acquire (&cert_cache_lock, PTH_RWLOCK_RD, FALSE, NULL))
+ log_fatal (_("can't acquire read lock on the certificate cache: %s\n"),
+ strerror (errno));
+}
+
+static void
+acquire_cache_write_lock (void)
+{
+ if (!pth_rwlock_acquire (&cert_cache_lock, PTH_RWLOCK_RW, FALSE, NULL))
+ log_fatal (_("can't acquire write lock on the certificate cache: %s\n"),
+ strerror (errno));
+}
+
+static void
+release_cache_lock (void)
+{
+ if (!pth_rwlock_release (&cert_cache_lock))
+ log_fatal (_("can't release lock on the certificate cache: %s\n"),
+ strerror (errno));
+}
+
+
+/* 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;
+ FILE *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 = fopen (fname, "rb");
+ if (!fp)
+ {
+ log_error (_("can't open `%s': %s\n"),
+ fname, strerror (errno));
+ continue;
+ }
+ err = ksba_reader_new (&reader);
+ if (!err)
+ err = ksba_reader_set_file (reader, fp);
+ if (err)
+ {
+ log_error (_("can't setup KSBA reader: %s\n"), gpg_strerror (err));
+ ksba_reader_release (reader);
+ fclose (fp);
+ continue;
+ }
+
+ err = ksba_cert_new (&cert);
+ if (!err)
+ err = ksba_cert_read_der (cert, reader);
+ ksba_reader_release (reader);
+ 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;
+
+ 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 = PATTERN_UNKNOWN;
+ const char *s;
+ int hexprefix = 0;
+ int hexlength;
+ int mode = 0;
+
+ *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. */
+ mode = 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)
+ {
+ 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 000000000..2b7aeaf74
--- /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 000000000..9ec5414fa
--- /dev/null
+++ b/dirmngr/crlcache.c
@@ -0,0 +1,2544 @@
+/* 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"
+#include "estream-printf.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 isi usually initialized
+ right at startup. */
+static crl_cache_t current_cache;
+
+
+
+
+
+/* Return the current cache object or bail out if it is has not yet
+ been initialized. */
+static crl_cache_t
+get_current_cache (void)
+{
+ if (!current_cache)
+ log_fatal ("CRL cache has not yet been initialized\n");
+ return current_cache;
+}
+
+
+/*
+ Create ae directory if it does not yet exists. Returns on
+ success, or -1 on error.
+ */
+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);
+ 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 (unlink (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 (FILE *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=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 FILE *
+open_dir_file (const char *fname)
+{
+ FILE *fp;
+
+ fp = 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 = fopen (fname, "w");
+ if (!fp)
+ {
+ log_error (_("error creating new cache dir file `%s': %s\n"),
+ fname, strerror (errno));
+ return NULL;
+ }
+ fprintf (fp, "v:%d:\n", DBDIRVERSION);
+ if (ferror (fp))
+ {
+ log_error (_("error writing new cache dir file `%s': %s\n"),
+ fname, strerror (errno));
+ fclose (fp);
+ return NULL;
+ }
+ if (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 = 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 (FILE **fpadr, const char *fname,
+ unsigned int *lineno,
+ int cleanup_on_mismatch)
+{
+ char *line;
+ gpg_error_t lineerr = 0;
+ FILE *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"));
+ 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;
+ FILE *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); break;
+ case 6: strncpy (entry->next_update, p, 15); 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 (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:
+ if (fp)
+ 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, FILE *fp)
+{
+ for (; *s; s++)
+ if (*s == ':')
+ fputs ("%3A", fp);
+ else if (*s == '\n')
+ fputs ("%0A", fp);
+ else if (*s == '\r')
+ fputs ("%0D", fp);
+ else
+ putc (*s, fp);
+}
+
+
+static void
+write_dir_line_crl (FILE *fp, crl_cache_entry_t e)
+{
+ if (e->invalid)
+ fprintf (fp, "i%d", e->invalid);
+ else if (e->user_trust_req)
+ putc ('u', fp);
+ else
+ putc ('c', fp);
+ putc (':', fp);
+ fputs (e->issuer_hash, fp);
+ putc (':', fp);
+ write_percented_string (e->issuer, fp);
+ putc (':', fp);
+ write_percented_string (e->url, fp);
+ putc (':', fp);
+ fwrite (e->this_update, 15, 1, fp);
+ putc (':', fp);
+ fwrite (e->next_update, 15, 1, fp);
+ putc (':', fp);
+ fputs (e->dbfile_hash, fp);
+ putc (':', fp);
+ if (e->crl_number)
+ fputs (e->crl_number, fp);
+ putc (':', fp);
+ if (e->authority_issuer)
+ write_percented_string (e->authority_issuer, fp);
+ putc (':', fp);
+ if (e->authority_serialno)
+ fputs (e->authority_serialno, fp);
+ putc (':', fp);
+ if (e->check_trust_anchor && e->user_trust_req)
+ fputs (e->check_trust_anchor, fp);
+ 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;
+ FILE *fp, *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 = 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;
+ 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
+
+ estream_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 = 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. */
+ fprintf (fpout, "# Next line added by "
+ "another process; our pid is %lu\n",
+ (unsigned long)getpid ());
+ fputs (line, fpout);
+ putc ('\n', fpout);
+ }
+ }
+ else
+ {
+ fputs ("# Invalid line detected: ", fpout);
+ fputs (line, fpout);
+ putc ('\n', fpout);
+ }
+ }
+ else
+ {
+ /* Write out all non CRL lines as they are. */
+ fputs (line, fpout);
+ putc ('\n', fpout);
+ }
+
+ xfree (line);
+ }
+ if (!ferror (fp) && !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 (ferror (fp))
+ {
+ err = gpg_error_from_errno (errno);
+ log_error (_("error reading `%s': %s\n"), fname, strerror (errno));
+ }
+ if (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. */
+ fclose (fp);
+ fp = NULL;
+ if (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. */
+ unlink (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);
+ if (fp)
+ fclose (fp);
+ xfree (fname);
+ if (fpout)
+ {
+ fclose (fpout);
+ if (err && tmpfname)
+ 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)
+{
+ FILE *fp;
+ char *buffer;
+ size_t n;
+ gcry_md_hd_t md5;
+ gpg_err_code_t err;
+
+ buffer = xtrymalloc (65536);
+ fp = buffer? 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);
+ 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 = fread (buffer, 1, 65536, fp);
+ if (n < 65536 && ferror (fp))
+ {
+ log_error (_("error hashing `%s': %s\n"), fname, strerror (errno));
+ xfree (buffer);
+ fclose (fp);
+ gcry_md_close (md5);
+ return -1;
+ }
+ if (!n)
+ break;
+ gcry_md_write (md5, buffer, n);
+ }
+ 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;
+ }
+}
+
+
+/* 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. */
+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
+
+ estream_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 (!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);
+#ifdef HAVE_W32_SYSTEM
+ unlink (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)
+ {
+ 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, FILE *fp)
+{
+ struct cdb_find cdbfp;
+ struct cdb *cdb;
+ int rc;
+ int warn = 0;
+ const unsigned char *s;
+
+ fputs ("--------------------------------------------------------\n", fp );
+ fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url );
+ fprintf (fp, " Issuer:\t%s\n", e->issuer );
+ fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash );
+ fprintf (fp, " This Update:\t%s\n", e->this_update );
+ fprintf (fp, " Next Update:\t%s\n", e->next_update );
+ fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none");
+ fprintf (fp, " AuthKeyId :\t%s\n",
+ e->authority_serialno? e->authority_serialno:"none");
+ if (e->authority_serialno && e->authority_issuer)
+ {
+ fputs (" \t", fp);
+ for (s=e->authority_issuer; *s; s++)
+ if (*s == '\x01')
+ fputs ("\n \t", fp);
+ else
+ putc (*s, fp);
+ putc ('\n', fp);
+ }
+ 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))
+ fprintf (fp, _(" ERROR: The CRL will not be used because it was still too old after an update!\n"));
+ if ((e->invalid & 2))
+ fprintf (fp, _(" ERROR: The CRL will not be used due to an unknown critical extension!\n"));
+ if ((e->invalid & ~3))
+ 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)
+ fprintf (fp, _(" ERROR: This cached CRL may has been tampered with!\n"));
+
+ 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;
+ fputs (" ", fp);
+ for (i = 0; i < n; i++)
+ fprintf (fp, "%02X", keyrecord[i]);
+ fputs (":\t reasons( ", fp);
+
+ if (reason & KSBA_CRLREASON_UNSPECIFIED)
+ fputs( "unspecified ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_KEY_COMPROMISE )
+ fputs( "key_compromise ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CA_COMPROMISE )
+ fputs( "ca_compromise ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED )
+ fputs( "affiliation_changed ", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_SUPERSEDED )
+ fputs( "superseeded", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION )
+ fputs( "cessation_of_operation", fp ), any = 1;
+ if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD )
+ fputs( "certificate_hold", fp ), any = 1;
+ if (reason && !any)
+ fputs( "other", fp );
+
+ 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);
+ fprintf (fp, _("End CRL dump\n") );
+ 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 (FILE *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;
+ FILE *fp;
+ ksba_reader_t reader;
+
+ fp = 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 = ksba_reader_new (&reader);
+ if (!err)
+ err = ksba_reader_set_file (reader, fp);
+ if (err)
+ {
+ log_error (_("error initializing reader object: %s\n"),
+ gpg_strerror (err));
+ ksba_reader_release (reader);
+ return err;
+ }
+ err = crl_cache_insert (ctrl, filename, reader);
+ ksba_reader_release (reader);
+ 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 000000000..b9e487436
--- /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 (FILE* 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 000000000..ca6c77a84
--- /dev/null
+++ b/dirmngr/crlfetch.c
@@ -0,0 +1,479 @@
+/* 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 <pth.h>
+
+#include "crlfetch.h"
+#include "dirmngr.h"
+#include "misc.h"
+#include "http.h"
+
+#include "estream.h"
+
+
+/* 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"));
+ pth_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. */
+
+ *reader = NULL;
+
+ once_more:
+ err = http_parse_uri (&uri, url);
+ 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);
+ 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)
+ |HTTP_FLAG_NEED_HEADER
+ |(DBG_LOOKUP? HTTP_FLAG_LOG_RESP:0),
+ opt.http_proxy, 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. */
+ 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 foer 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
+ err = url_fetch_ldap (ctrl, url, NULL, 0, reader);
+ }
+
+ 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);
+ }
+ return attr_fetch_ldap (ctrl, issuer, "certificateRevocationList",
+ reader);
+}
+
+
+/* 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);
+ }
+ return start_default_fetch_ldap (ctrl, context, dn, "cACertificate");
+}
+
+
+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);
+ }
+ return start_cert_fetch_ldap (ctrl, context, patterns, server);
+}
+
+
+gpg_error_t
+fetch_next_cert (cert_fetch_context_t context,
+ unsigned char **value, size_t * valuelen)
+{
+ return fetch_next_cert_ldap (context, value, valuelen);
+}
+
+
+/* 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;
+
+ err = fetch_next_cert_ldap (context, &value, &valuelen);
+ if (!err && !value)
+ err = gpg_error (GPG_ERR_BUG);
+ 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)
+{
+ return end_cert_fetch_ldap (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)
+{
+ 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;
+
+ err = url_fetch_ldap (ctrl, url, NULL, 0, &reader);
+ 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);
+ ldap_wrapper_release_context (reader);
+
+ 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). */
+ ldap_wrapper_release_context (reader);
+
+ /* 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 000000000..e42196dc7
--- /dev/null
+++ b/dirmngr/crlfetch.h
@@ -0,0 +1,93 @@
+/* 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 --*/
+void *ldap_wrapper_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 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 000000000..1e388408d
--- /dev/null
+++ b/dirmngr/dirmngr-client.c
@@ -0,0 +1,1042 @@
+/* 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_("certificates are expected 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;
+}
+
+
+static void
+my_i18n_init (void)
+{
+#warning Better use common init functions
+#ifdef USE_SIMPLE_GETTEXT
+ set_gettext_file (PACKAGE);
+#else
+# ifdef ENABLE_NLS
+ setlocale (LC_ALL, "" );
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+# endif
+#endif
+}
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ assuan_context_t ctx;
+ gpg_error_t err;
+ unsigned char *certbuf;
+ size_t certbuflen = 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. */
+ my_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");
+ if (only_daemon && (!infostr || !*infostr))
+ {
+ infostr = xstrdup (dirmngr_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];
+ int no_close_list[3];
+ int i;
+
+ if (only_daemon)
+ {
+ log_error (_("apparently no running dirmngr\n"));
+ return NULL;
+ }
+
+ if (opt.verbose)
+ log_info (_("no running dirmngr - starting one\n"));
+
+ if (!opt.dirmngr_program || !*opt.dirmngr_program)
+ opt.dirmngr_program = "./dirmngr";
+ if ( !(pgmname = strrchr (opt.dirmngr_program, '/')))
+ pgmname = opt.dirmngr_program;
+ else
+ pgmname++;
+
+ argv[0] = pgmname;
+ argv[1] = "--server";
+ argv[2] = NULL;
+
+ i=0;
+ if (log_get_fd () != -1)
+ no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ());
+ no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr));
+ no_close_list[i] = -1;
+
+ /* 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 DIRMNGR_INFO environment variable\n"));
+ xfree (infostr);
+ if (only_daemon)
+ return NULL;
+ /* Try again by starting a new instance. */
+ opt.force_pipe_server = 1;
+ return start_dirmngr (0);
+ }
+ *p++ = 0;
+ pid = atoi (p);
+ while (*p && *p != ':')
+ p++;
+ prot = *p? atoi (p+1) : 0;
+ if (prot != 1)
+ {
+ log_error (_("dirmngr protocol version %d is not supported\n"),
+ prot);
+ xfree (infostr);
+ if (only_daemon)
+ return NULL;
+ opt.force_pipe_server = 1;
+ return start_dirmngr (0);
+ }
+ }
+ else
+ pid = -1;
+
+ rc = assuan_socket_connect (ctx, infostr, pid, 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.c b/dirmngr/dirmngr.c
new file mode 100644
index 000000000..12b74bd00
--- /dev/null
+++ b/dirmngr/dirmngr.c
@@ -0,0 +1,1829 @@
+/* dirmngr.c - LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2006, 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., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <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>
+#include <signal.h>
+#include <pth.h>
+
+
+#define JNLIB_NEED_LOG_LOGV
+#include "dirmngr.h"
+
+#include <assuan.h>
+
+#include "certcache.h"
+#include "crlcache.h"
+#include "crlfetch.h"
+#include "misc.h"
+#include "ldapserver.h"
+#include "asshelp.h"
+
+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,
+ oNoGreeting,
+ oNoOptions,
+ oHomedir,
+ oNoDetach,
+ oLogFile,
+ oBatch,
+ oDisableHTTP,
+ oDisableLDAP,
+ oIgnoreLDAPDP,
+ oIgnoreHTTPDP,
+ oIgnoreOCSPSvcUrl,
+ oHonorHTTPProxy,
+ oHTTPProxy,
+ oLDAPProxy,
+ oOnlyLDAPProxy,
+ oLDAPFile,
+ oLDAPTimeout,
+ oLDAPAddServers,
+ oOCSPResponder,
+ oOCSPSigner,
+ oOCSPMaxClockSkew,
+ oOCSPMaxPeriod,
+ oOCSPCurrentPeriod,
+ oMaxReplies,
+ 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 HAVE_W32_SYSTEM
+ 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 (oSocketName, "socket-name", N_("|FILE|listen on socket FILE")),
+
+ ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/
+ ARGPARSE_p_u (oDebug, "debug", "@"),
+ ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
+ 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;
+
+/* 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. */
+#ifdef HAVE_W32_SYSTEM
+#define TIMERTICK_INTERVAL (4)
+#else
+#define TIMERTICK_INTERVAL (2) /* Seconds. */
+#endif
+
+/* 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 (pth_key_getdata 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);
+static ldap_server_t parse_ldapserver_file (const char* filename);
+static fingerprint_list_t parse_ocsp_signer (const char *string);
+static void handle_connections (assuan_fd_t listen_fd);
+
+/* Pth wrapper function definitions. */
+ASSUAN_SYSTEM_PTH_IMPL;
+
+GCRY_THREAD_OPTION_PTH_IMPL;
+static int fixed_gcry_pth_init (void)
+{
+ return pth_self ()? 0 : (pth_init () == FALSE) ? errno : 0;
+}
+
+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"
+ "LDAP 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;
+}
+
+
+/* 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);
+}
+
+
+static void
+wrong_args (const char *text)
+{
+ fputs (_("usage: dirmngr [options] "), stderr);
+ fputs (text, stderr);
+ putc ('\n', stderr);
+ dirmngr_exit (2);
+}
+
+
+/* Helper to start the reaper thread for the ldap wrapper. */
+static void
+launch_reaper_thread (void)
+{
+ static int done;
+ pth_attr_t tattr;
+
+ if (done)
+ return;
+ done = 1;
+
+ tattr = pth_attr_new();
+ pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+ pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024);
+ pth_attr_set (tattr, PTH_ATTR_NAME, "ldap-reaper");
+
+ if (!pth_spawn (tattr, ldap_wrapper_thread, NULL))
+ {
+ log_error (_("error spawning ldap wrapper reaper thread: %s\n"),
+ strerror (errno) );
+ dirmngr_exit (1);
+ }
+ pth_attr_destroy (tattr);
+}
+
+
+/* Helper to stop the reaper thread for the ldap wrapper. */
+static void
+shutdown_reaper (void)
+{
+ ldap_wrapper_wait_connections ();
+}
+
+
+/* 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);
+ return 1;
+ }
+
+ switch (pargs->r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+ case oDebug: opt.debug |= pargs->r.ret_ulong; break;
+ case oDebugAll: opt.debug = ~0; break;
+ case oDebugLevel: debug_level = pargs->r.ret_str; break;
+
+ case oLogFile:
+ if (!reread)
+ return 0; /* Not handled. */
+ if (!current_logfile || !pargs->r.ret_str
+ || strcmp (current_logfile, pargs->r.ret_str))
+ {
+ log_set_file (pargs->r.ret_str);
+ xfree (current_logfile);
+ current_logfile = xtrystrdup (pargs->r.ret_str);
+ }
+ break;
+
+ 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 oIgnoreCertExtension:
+ add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str);
+ break;
+
+ default:
+ return 0; /* Not handled. */
+ }
+
+ return 1; /* Handled. */
+}
+
+
+#ifdef HAVE_W32_SYSTEM
+/* 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)
+{
+ /* 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 /*HAVE_W32_SYSTEM*/
+
+#ifndef HAVE_W32_SYSTEM
+static int
+pid_suffix_callback (unsigned long *r_suffix)
+{
+ union int_and_ptr_u value;
+
+ value.aptr = pth_key_getdata (my_tlskey_current_fd);
+ *r_suffix = value.aint;
+ return (*r_suffix != -1); /* Use decimal representation. */
+}
+#endif /*!HAVE_W32_SYSTEM*/
+
+
+#ifdef HAVE_W32_SYSTEM
+#define main real_main
+#endif
+int
+main (int argc, char **argv)
+{
+#ifdef HAVE_W32_SYSTEM
+#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;
+ char *ldapfile = NULL;
+ int debug_wait = 0;
+ int rc;
+ int homedir_seen = 0;
+ struct assuan_malloc_hooks malloc_hooks;
+
+#ifdef HAVE_W32_SYSTEM
+ /* 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 /*HAVE_W32_SYSTEM*/
+
+ set_strusage (my_strusage);
+ log_set_prefix ("dirmngr", 1|4);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init ();
+ init_common_subsystems (&argc, &argv);
+
+ /* Libgcrypt requires us to register the threading model first.
+ Note that this will also do the pth_init. */
+ gcry_threads_pth.init = fixed_gcry_pth_init;
+ rc = gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pth);
+ if (rc)
+ {
+ log_fatal ("can't register GNU Pth with Libgcrypt: %s\n",
+ gpg_strerror (rc));
+ }
+ gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
+
+ /* 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 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_PTH);
+ 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 Pth running we can set the logging callback. Our
+ windows implementation does not yet feature the Pth TLS
+ functions. */
+#ifndef HAVE_W32_SYSTEM
+ if (pth_key_create (&my_tlskey_current_fd, NULL))
+ if (pth_key_setdata (my_tlskey_current_fd, NULL))
+ 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. */
+ socket_name = dirmngr_socket_name ();
+
+ /* 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/dirmngr as default home directory. Note, that
+ this also overrides the GNUPGHOME environment variable. */
+ if (opt.system_daemon && !homedir_seen)
+ {
+ opt.homedir = gnupg_sysconfdir ();
+ opt.homedir_data = gnupg_datadir ();
+ opt.homedir_cache = gnupg_cachedir ();
+ }
+
+ if (default_config)
+ configname = make_filename (opt.homedir, "dirmngr.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: ldapfile = pargs.r.ret_str; 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)
+ {
+ fprintf (stderr, "%s %s; %s\n",
+ strusage(11), strusage(13), strusage(14) );
+ fprintf (stderr, "%s\n", strusage(15) );
+ }
+
+#ifdef IS_DEVELOPMENT_VERSION
+ log_info ("NOTE: this is a development version!\n");
+#endif
+
+ if (gnupg_faked_time_p ())
+ {
+ gnupg_isotime_t tbuf;
+ gnupg_get_isotime (tbuf);
+ log_info (_("WARNING: running with faked system time %s\n"), tbuf);
+ }
+
+ set_debug ();
+
+ /* Get LDAP server list from file. */
+ 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);
+
+#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)
+ {
+ 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");
+ }
+
+ launch_reaper_thread ();
+ 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 = (offsetof (struct sockaddr_un, sun_path)
+ + strlen (serv_addr.sun_path) + 1);
+
+ rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
+ if (rc == -1 && errno == EADDRINUSE)
+ {
+ 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 );
+
+ fflush (NULL);
+
+#ifdef HAVE_W32_SYSTEM
+ pid = getpid ();
+ printf ("set DIRMNGR_INFO=%s;%lu;1\n", socket_name, (ulong) pid);
+#else
+ pid = pth_fork ();
+ if (pid == (pid_t)-1)
+ {
+ log_fatal (_("fork failed: %s\n"), strerror (errno) );
+ dirmngr_exit (1);
+ }
+
+ if (pid)
+ { /* We are the parent */
+ char *infostr;
+
+ /* Don't let cleanup() remove the socket - the child is
+ responsible for doing that. */
+ cleanup_socket = 0;
+
+ close (fd);
+
+ /* Create the info string: <name>:<pid>:<protocol_version> */
+ if (asprintf (&infostr, "DIRMNGR_INFO=%s:%lu:1",
+ socket_name, (ulong)pid ) < 0)
+ {
+ log_error (_("out of core\n"));
+ kill (pid, SIGTERM);
+ dirmngr_exit (1);
+ }
+ /* Print the environment string, so that the caller can use
+ shell's eval to set it */
+ if (csh_style)
+ {
+ *strchr (infostr, '=') = ' ';
+ printf ( "setenv %s\n", infostr);
+ }
+ else
+ {
+ printf ( "%s; export DIRMNGR_INFO;\n", infostr);
+ }
+ free (infostr);
+ exit (0);
+ /*NEVER REACHED*/
+ } /* end parent */
+
+
+ /*
+ This is the child
+ */
+
+ /* Detach from tty and put process into a new session */
+ if (!nodetach )
+ {
+ int i;
+ unsigned int oldflags;
+
+ /* Close stdin, stdout and stderr unless it is the log stream */
+ for (i=0; i <= 2; i++)
+ {
+ if (!log_test_fd (i) && i != fd )
+ close (i);
+ }
+ if (setsid() == -1)
+ {
+ log_error (_("setsid() failed: %s\n"), strerror(errno) );
+ dirmngr_exit (1);
+ }
+
+ log_get_prefix (&oldflags);
+ log_set_prefix (NULL, oldflags | JNLIB_LOG_RUN_DETACHED);
+ opt.running_detached = 1;
+
+ if (chdir("/"))
+ {
+ log_error (_("chdir to / failed: %s\n"), strerror (errno));
+ dirmngr_exit (1);
+ }
+ }
+#endif
+
+ launch_reaper_thread ();
+ cert_cache_init ();
+ crl_cache_init ();
+#ifdef HAVE_W32_SYSTEM
+ 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 HAVE_W32_SYSTEM
+ 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");
+ launch_reaper_thread ();
+ crl_cache_init ();
+ crl_cache_list (stdout);
+ }
+ else if (cmd == aLoadCRL)
+ {
+ struct server_control_s ctrlbuf;
+
+ memset (&ctrlbuf, 0, sizeof ctrlbuf);
+ dirmngr_init_default_ctrl (&ctrlbuf);
+
+ launch_reaper_thread ();
+ 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);
+
+ launch_reaper_thread ();
+ 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;
+
+ /* List options and default values in the GPG Conf format. */
+
+/* The following list is taken from gnupg/tools/gpgconf-comp.c. */
+/* Option flags. YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING
+ FLAGS, AS THEY ARE PART OF THE EXTERNAL INTERFACE. */
+#define GC_OPT_FLAG_NONE 0UL
+/* The DEFAULT flag for an option indicates that the option has a
+ default value. */
+#define GC_OPT_FLAG_DEFAULT (1UL << 4)
+/* The DEF_DESC flag for an option indicates that the option has a
+ default, which is described by the value of the default field. */
+#define GC_OPT_FLAG_DEF_DESC (1UL << 5)
+/* The NO_ARG_DESC flag for an option indicates that the argument has
+ a default, which is described by the value of the ARGDEF field. */
+#define GC_OPT_FLAG_NO_ARG_DESC (1UL << 6)
+#define GC_OPT_FLAG_NO_CHANGE (1UL <<7)
+
+#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);
+ printf ("gpgconf-dirmngr.conf:%lu:\"%s\n",
+ GC_OPT_FLAG_DEFAULT, filename);
+ xfree (filename);
+
+ printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT);
+ printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ 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);
+ printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT,
+ filename_esc);
+ xfree (filename_esc);
+ xfree (filename);
+
+ printf ("ldaptimeout:%lu:%u\n",
+ flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT);
+ printf ("max-replies:%lu:%u\n",
+ flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES);
+ printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE);
+
+ printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE);
+
+ printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE);
+ printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ 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. */
+ printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE);
+ }
+ cleanup ();
+ return !!rc;
+}
+
+
+#ifdef HAVE_W32_SYSTEM
+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 [] =
+ {
+ /* Ignore warning. */
+ { "DirMngr", &real_main },
+ { NULL, NULL }
+ };
+
+ if (!StartServiceCtrlDispatcher (DispatchTable))
+ return 1;
+ return 0;
+ }
+}
+#endif
+
+
+static void
+cleanup (void)
+{
+ crl_cache_deinit ();
+ cert_cache_deinit (1);
+
+ ldapserver_list_free (opt.ldapservers);
+ opt.ldapservers = NULL;
+
+ if (cleanup_socket)
+ {
+ cleanup_socket = 0;
+ if (socket_name && *socket_name)
+ 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
+
+*/
+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;
+ FILE *fp;
+
+ fp = fopen (filename, "r");
+ if (!fp)
+ {
+ log_error (_("error opening `%s': %s\n"), filename, strerror (errno));
+ return NULL;
+ }
+
+ serverstart = NULL;
+ serverend = &serverstart;
+ while (fgets (buffer, sizeof buffer, fp))
+ {
+ lineno++;
+ if (!*buffer || buffer[strlen(buffer)-1] != '\n')
+ {
+ if (*buffer && feof (fp))
+ ; /* Last line not terminated - continue. */
+ else
+ {
+ log_error (_("%s:%u: line too long - skipped\n"),
+ filename, lineno);
+ while ( (c=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 (ferror (fp))
+ log_error (_("error reading `%s': %s\n"), filename, strerror (errno));
+ fclose (fp);
+
+ return serverstart;
+}
+
+
+static fingerprint_list_t
+parse_ocsp_signer (const char *string)
+{
+ gpg_error_t err;
+ char *fname;
+ FILE *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 = 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 (!fgets (line, DIM(line)-1, fp) )
+ {
+ if (!feof (fp))
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("%s:%u: read error: %s\n"),
+ fname, lnr, gpg_strerror (err));
+ errflag = 1;
+ }
+ 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=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 PTH
+ 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 ();
+}
+
+
+
+/* The signal handler. */
+static void
+handle_signal (int signo)
+{
+ switch (signo)
+ {
+#ifndef HAVE_W32_SYSTEM
+ case SIGHUP:
+ 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 ();
+ 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;
+#endif
+ default:
+ log_info (_("signal %d received - no action defined\n"), signo);
+ }
+}
+
+
+/* This is the worker for the ticker. It is called every few seconds
+ and may only do fast operations. */
+static void
+handle_tick (void)
+{
+ /* Nothing real to do right now. Actually we need the timeout only
+ for W32 where 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*/
+}
+
+
+/* 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;
+ assuan_fd_t fd;
+
+ argval.aptr = arg;
+ fd = argval.afd;
+
+ if (check_nonce (fd, &socket_nonce))
+ return NULL;
+
+#ifndef HAVE_W32_SYSTEM
+ pth_key_setdata (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;
+ pth_key_setdata (my_tlskey_current_fd, argval.aptr);
+#endif
+
+ return NULL;
+}
+
+
+/* Main loop in daemon mode. */
+static void
+handle_connections (assuan_fd_t listen_fd)
+{
+ pth_attr_t tattr;
+ pth_event_t ev, time_ev;
+ sigset_t sigs, oldsigs;
+ int signo;
+ struct sockaddr_un paddr;
+ socklen_t plen = sizeof( paddr );
+ assuan_fd_t fd;
+
+ tattr = pth_attr_new();
+ pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0);
+ pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 1024*1024);
+ pth_attr_set (tattr, PTH_ATTR_NAME, "dirmngr");
+
+#ifndef HAVE_W32_SYSTEM /* FIXME */
+ sigemptyset (&sigs );
+ sigaddset (&sigs, SIGHUP);
+ sigaddset (&sigs, SIGUSR1);
+ sigaddset (&sigs, SIGUSR2);
+ sigaddset (&sigs, SIGINT);
+ sigaddset (&sigs, SIGTERM);
+ ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+#else
+ sigs = 0;
+ ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo);
+#endif
+ time_ev = NULL;
+
+ for (;;)
+ {
+ if (shutdown_pending)
+ {
+ if (!active_connections)
+ break; /* ready */
+
+ /* Do not accept anymore connections but wait for existing
+ connections to terminate. */
+ signo = 0;
+ pth_wait (ev);
+ if (pth_event_occurred (ev) && signo)
+ handle_signal (signo);
+ continue;
+ }
+
+ if (!time_ev)
+ time_ev = pth_event (PTH_EVENT_TIME,
+ pth_timeout (TIMERTICK_INTERVAL, 0));
+
+ if (time_ev)
+ pth_event_concat (ev, time_ev, NULL);
+ fd = (assuan_fd_t) pth_accept_ev (FD2INT (listen_fd), (struct sockaddr *)&paddr, &plen, ev);
+ if (time_ev)
+ pth_event_isolate (time_ev);
+
+ if (fd == ASSUAN_INVALID_FD)
+ {
+ if (pth_event_occurred (ev)
+ || (time_ev && pth_event_occurred (time_ev)) )
+ {
+ if (pth_event_occurred (ev))
+ handle_signal (signo);
+ if (time_ev && pth_event_occurred (time_ev))
+ {
+ pth_event_free (time_ev, PTH_FREE_ALL);
+ time_ev = NULL;
+ handle_tick ();
+ }
+ continue;
+ }
+ log_error (_("accept failed: %s - waiting 1s\n"), strerror (errno));
+ pth_sleep (1);
+ continue;
+ }
+
+ if (pth_event_occurred (ev))
+ {
+ handle_signal (signo);
+ }
+
+ if (time_ev && pth_event_occurred (time_ev))
+ {
+ pth_event_free (time_ev, PTH_FREE_ALL);
+ time_ev = NULL;
+ handle_tick ();
+ }
+
+
+ /* We now might create a new thread and because we don't want
+ any signals (as we are handling them here) to be delivered to
+ a new thread we need to block those signals. */
+ pth_sigmask (SIG_BLOCK, &sigs, &oldsigs);
+
+ /* Create thread to handle this connection. */
+ {
+ union int_and_ptr_u argval;
+
+ argval.afd = fd;
+ if (!pth_spawn (tattr, start_connection_thread, argval.aptr))
+ {
+ log_error (_("error spawning connection handler: %s\n"),
+ strerror (errno) );
+ assuan_sock_close (fd);
+ }
+ }
+
+ /* Restore the signal mask. */
+ pth_sigmask (SIG_SETMASK, &oldsigs, NULL);
+ }
+
+ pth_event_free (ev, PTH_FREE_ALL);
+ if (time_ev)
+ pth_event_free (time_ev, PTH_FREE_ALL);
+ pth_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 000000000..e6fa0d318
--- /dev/null
+++ b/dirmngr/dirmngr.h
@@ -0,0 +1,189 @@
+/* dirmngr.h - Common definitions for the dirmngr
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DIRMNGR_H
+#define DIRMNGR_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>
+#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"
+
+
+/* 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;
+
+
+/* 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. */
+};
+
+
+/*-- dirmngr.c --*/
+void dirmngr_exit( int ); /* Wrapper for exit() */
+void dirmngr_init_default_ctrl (ctrl_t ctrl);
+
+/*-- 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_tick (ctrl_t ctrl);
+
+
+#endif /*DIRMNGR_H*/
diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c
new file mode 100644
index 000000000..b73cc7da6
--- /dev/null
+++ b/dirmngr/dirmngr_ldap.c
@@ -0,0 +1,646 @@
+/* dirmngr-ldap.c - The LDAP helper for dirmngr.
+ * Copyright (C) 2004 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#ifdef HAVE_W32_SYSTEM
+#include <winsock2.h>
+#include <winldap.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 "../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"
+
+#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 }
+};
+
+
+/* The usual structure for the program flags. */
+static struct
+{
+ int quiet;
+ int verbose;
+ struct timeval timeout; /* Timeout for the LDAP search functions. */
+ unsigned int alarm_timeout; /* And for the alarm based timeout. */
+ int multi;
+
+ /* 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. */
+} opt;
+
+
+/* Prototypes. */
+static void catch_alarm (int dummy);
+static int process_url (const char *url);
+
+
+
+/* Function called by argparse.c to display information. */
+static const char *
+my_strusage (int level)
+{
+ const char *p;
+
+ switch(level)
+ {
+ case 11: p = "dirmngr_ldap (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;
+}
+
+
+static void
+my_i18n_init (void)
+{
+#warning Better use common init functions
+#ifdef USE_SIMPLE_GETTEXT
+ set_gettext_file (PACKAGE);
+#else
+# ifdef ENABLE_NLS
+ setlocale (LC_ALL, "" );
+ bindtextdomain (PACKAGE, LOCALEDIR);
+ textdomain (PACKAGE);
+# endif
+#endif
+}
+
+
+int
+main (int argc, char **argv )
+{
+ ARGPARSE_ARGS pargs;
+ int any_err = 0;
+ char *p;
+ int only_search_timeout = 0;
+
+#ifdef HAVE_W32_SYSTEM
+ /* Yeah, right. Sigh. */
+ _setmode (_fileno (stdout), _O_BINARY);
+#endif
+
+ set_strusage (my_strusage);
+ log_set_prefix ("dirmngr_ldap", JNLIB_LOG_WITH_PREFIX);
+
+ /* Setup I18N. */
+ my_i18n_init();
+
+ /* LDAP defaults */
+ opt.timeout.tv_sec = DEFAULT_LDAP_TIMEOUT;
+ opt.timeout.tv_usec = 0;
+ opt.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: opt.verbose++; break;
+ case oQuiet: opt.quiet++; break;
+ case oTimeout:
+ opt.timeout.tv_sec = pargs.r.ret_int;
+ opt.timeout.tv_usec = 0;
+ opt.alarm_timeout = pargs.r.ret_int;
+ break;
+ case oOnlySearchTimeout: only_search_timeout = 1; break;
+ case oMulti: opt.multi = 1; break;
+ case oUser: opt.user = pargs.r.ret_str; break;
+ case oPass: opt.pass = pargs.r.ret_str; break;
+ case oEnvPass:
+ opt.pass = getenv ("DIRMNGR_LDAP_PASS");
+ break;
+ case oProxy: opt.proxy = pargs.r.ret_str; break;
+ case oHost: opt.host = pargs.r.ret_str; break;
+ case oPort: opt.port = pargs.r.ret_int; break;
+ case oDN: opt.dn = pargs.r.ret_str; break;
+ case oFilter: opt.filter = pargs.r.ret_str; break;
+ case oAttr: opt.attr = pargs.r.ret_str; break;
+ case oLogWithPID:
+ {
+ unsigned int oldflags;
+ log_get_prefix (&oldflags);
+ log_set_prefix (NULL, oldflags | JNLIB_LOG_WITH_PID);
+ }
+ break;
+
+ default : pargs.err = 2; break;
+ }
+ }
+
+ if (only_search_timeout)
+ opt.alarm_timeout = 0;
+
+ if (opt.proxy)
+ {
+ opt.host = xstrdup (opt.proxy);
+ p = strchr (opt.host, ':');
+ if (p)
+ {
+ *p++ = 0;
+ opt.port = atoi (p);
+ }
+ if (!opt.port)
+ opt.port = 389; /* make sure ports gets overridden. */
+ }
+
+ if (opt.port < 0 || opt.port > 65535)
+ log_error (_("invalid port number %d\n"), opt.port);
+
+ if (log_get_errorcount (0))
+ exit (2);
+
+ if (argc < 1)
+ usage (1);
+
+ if (opt.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
+ }
+
+ for (; argc; argc--, argv++)
+ if (process_url (*argv))
+ any_err = 1;
+
+ return any_err;
+}
+
+
+static void
+catch_alarm (int dummy)
+{
+ (void)dummy;
+ _exit (10);
+}
+
+
+static void
+set_timeout (void)
+{
+#ifndef HAVE_W32_SYSTEM
+ /* FIXME for W32. */
+ if (opt.alarm_timeout)
+ alarm (opt.alarm_timeout);
+#endif
+}
+
+
+/* Helper for fetch_ldap(). */
+static int
+print_ldap_entries (LDAP *ld, LDAPMessage *msg, char *want_attr)
+{
+ LDAPMessage *item;
+ int any = 0;
+
+ for (item = ldap_first_entry (ld, msg); item;
+ item = ldap_next_entry (ld, item))
+ {
+ BerElement *berctx;
+ char *attr;
+
+ if (opt.verbose > 1)
+ log_info (_("scanning result for attribute `%s'\n"),
+ want_attr? want_attr : "[all]");
+
+ if (opt.multi)
+ { /* Write item marker. */
+ if (fwrite ("I\0\0\0\0", 5, 1, stdout) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ return -1;
+ }
+ }
+
+
+ for (attr = ldap_first_attribute (ld, item, &berctx); attr;
+ attr = ldap_next_attribute (ld, item, berctx))
+ {
+ struct berval **values;
+ int idx;
+
+ if (opt.verbose > 1)
+ log_info (_(" available attribute `%s'\n"), attr);
+
+ set_timeout ();
+
+ /* 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)
+ {
+ ldap_memfree (attr);
+ continue; /* Not found: Try next attribute. */
+ }
+ }
+
+ values = ldap_get_values_len (ld, item, attr);
+
+ if (!values)
+ {
+ if (opt.verbose)
+ log_info (_("attribute `%s' not found\n"), attr);
+ ldap_memfree (attr);
+ continue;
+ }
+
+ if (opt.verbose)
+ {
+ log_info (_("found attribute `%s'\n"), attr);
+ if (opt.verbose > 1)
+ for (idx=0; values[idx]; idx++)
+ log_info (" length[%d]=%d\n",
+ idx, (int)values[0]->bv_len);
+
+ }
+
+ if (opt.multi)
+ { /* Write attribute marker. */
+ unsigned char tmp[5];
+ size_t n = strlen (attr);
+
+ tmp[0] = 'A';
+ tmp[1] = (n >> 24);
+ tmp[2] = (n >> 16);
+ tmp[3] = (n >> 8);
+ tmp[4] = (n);
+ if (fwrite (tmp, 5, 1, stdout) != 1
+ || fwrite (attr, n, 1, stdout) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ ldap_value_free_len (values);
+ ldap_memfree (attr);
+ ber_free (berctx, 0);
+ return -1;
+ }
+ }
+
+ for (idx=0; values[idx]; idx++)
+ {
+ if (opt.multi)
+ { /* Write value marker. */
+ unsigned char tmp[5];
+ size_t n = values[0]->bv_len;
+
+ tmp[0] = 'V';
+ tmp[1] = (n >> 24);
+ tmp[2] = (n >> 16);
+ tmp[3] = (n >> 8);
+ tmp[4] = (n);
+
+ if (fwrite (tmp, 5, 1, stdout) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ ldap_value_free_len (values);
+ ldap_memfree (attr);
+ ber_free (berctx, 0);
+ return -1;
+ }
+ }
+#if 1
+ /* Note: this does not work for STDOUT on a Windows
+ console, where it fails with "Not enough space" for
+ CRLs which are 52 KB or larger. */
+ if (fwrite (values[0]->bv_val, values[0]->bv_len,
+ 1, stdout) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ ldap_value_free_len (values);
+ ldap_memfree (attr);
+ ber_free (berctx, 0);
+ return -1;
+ }
+#else
+ /* On Windows console STDOUT, we have to break up the
+ writes into small parts. */
+ {
+ int n = 0;
+ while (n < values[0]->bv_len)
+ {
+ int cnt = values[0]->bv_len - n;
+ /* The actual limit is (52 * 1024 - 1) on Windows XP SP2. */
+#define MAX_CNT (32*1024)
+ if (cnt > MAX_CNT)
+ cnt = MAX_CNT;
+
+ if (fwrite (((char *) values[0]->bv_val) + n, cnt, 1,
+ stdout) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ ldap_value_free_len (values);
+ ldap_memfree (attr);
+ ber_free (berctx, 0);
+ return -1;
+ }
+ n += cnt;
+ }
+ }
+#endif
+ any = 1;
+ if (!opt.multi)
+ break; /* Print only the first value. */
+ }
+ ldap_value_free_len (values);
+ ldap_memfree (attr);
+ if (want_attr || !opt.multi)
+ break; /* We only want to return the first attribute. */
+ }
+ ber_free (berctx, 0);
+ }
+
+ if (opt.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 (const char *url, const LDAPURLDesc *ludp)
+{
+ LDAP *ld;
+ LDAPMessage *msg;
+ int rc = 0;
+ char *host, *dn, *filter, *attrs[2], *attr;
+ int port;
+
+ host = opt.host? opt.host : ludp->lud_host;
+ port = opt.port? opt.port : ludp->lud_port;
+ dn = opt.dn? opt.dn : ludp->lud_dn;
+ filter = opt.filter? opt.filter : ludp->lud_filter;
+ attrs[0] = opt.attr? opt.attr : ludp->lud_attrs? ludp->lud_attrs[0]:NULL;
+ attrs[1] = NULL;
+ attr = attrs[0];
+
+ if (!port)
+ port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389;
+
+ if (opt.verbose)
+ {
+ log_info (_("processing url `%s'\n"), url);
+ if (opt.user)
+ log_info (_(" user `%s'\n"), opt.user);
+ if (opt.pass)
+ log_info (_(" pass `%s'\n"), *opt.pass?"*****":"");
+ if (host)
+ log_info (_(" host `%s'\n"), host);
+ log_info (_(" port %d\n"), port);
+ if (dn)
+ log_info (_(" DN `%s'\n"), dn);
+ if (filter)
+ log_info (_(" filter `%s'\n"), filter);
+ if (opt.multi && !opt.attr && ludp->lud_attrs)
+ {
+ int i;
+ for (i=0; ludp->lud_attrs[i]; i++)
+ log_info (_(" attr `%s'\n"), ludp->lud_attrs[i]);
+ }
+ else if (attr)
+ log_info (_(" attr `%s'\n"), attr);
+ }
+
+
+ if (!host || !*host)
+ {
+ log_error (_("no host name in `%s'\n"), url);
+ return -1;
+ }
+ if (!opt.multi && !attr)
+ {
+ log_error (_("no attribute given for query `%s'\n"), url);
+ return -1;
+ }
+
+ if (!opt.multi && !opt.attr
+ && ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1])
+ log_info (_("WARNING: using first attribute only\n"));
+
+
+ set_timeout ();
+ ld = ldap_init (host, port);
+ if (!ld)
+ {
+ log_error (_("LDAP init to `%s:%d' failed: %s\n"),
+ host, port, strerror (errno));
+ return -1;
+ }
+ if (ldap_simple_bind_s (ld, opt.user, opt.pass))
+ {
+ log_error (_("binding to `%s:%d' failed: %s\n"),
+ host, port, strerror (errno));
+ /* FIXME: Need deinit (ld)? */
+ return -1;
+ }
+
+ set_timeout ();
+ rc = ldap_search_st (ld, dn, ludp->lud_scope, filter,
+ opt.multi && !opt.attr && ludp->lud_attrs?
+ ludp->lud_attrs:attrs,
+ 0,
+ &opt.timeout, &msg);
+ if (rc == LDAP_SIZELIMIT_EXCEEDED && opt.multi)
+ {
+ if (fwrite ("E\0\0\0\x09truncated", 14, 1, stdout) != 1)
+ {
+ log_error (_("error writing to stdout: %s\n"),
+ strerror (errno));
+ return -1;
+ }
+ }
+ else if (rc)
+ {
+ log_error (_("searching `%s' failed: %s\n"),
+ url, ldap_err2string (rc));
+ 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 (ld, msg, opt.multi? NULL:attr);
+
+ ldap_msgfree (msg);
+ /* FIXME: Need deinit (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 (const char *url)
+{
+ int rc;
+ LDAPURLDesc *ludp = NULL;
+
+
+ if (!ldap_is_ldap_url (url))
+ {
+ log_error (_("`%s' is not an LDAP URL\n"), url);
+ return -1;
+ }
+
+ if (ldap_url_parse (url, &ludp))
+ {
+ log_error (_("`%s' is an invalid LDAP URL\n"), url);
+ return -1;
+ }
+
+ rc = fetch_ldap (url, ludp);
+
+ ldap_free_urldesc (ludp);
+ return rc;
+}
+
diff --git a/dirmngr/get-path.c b/dirmngr/get-path.c
new file mode 100644
index 000000000..c944ec1dd
--- /dev/null
+++ b/dirmngr/get-path.c
@@ -0,0 +1,620 @@
+/* get-path.c - Utility functions for the W32 API
+ Copyright (C) 1999 Free Software Foundation, Inc
+ Copyright (C) 2001 Werner Koch (dd9jn)
+ Copyright (C) 2001, 2002, 2003, 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 */
+
+#error Code has been replaced by common/homedir.c
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <fcntl.h>
+#ifdef HAVE_W32_SYSTEM
+#include <windows.h>
+#include <shlobj.h>
+#include <io.h>
+#endif
+
+#include "util.h"
+
+#ifdef HAVE_W32_SYSTEM
+#define GNUPG_DEFAULT_HOMEDIR "c:/gnupg"
+#elif defined(__VMS)
+#define GNUPG_DEFAULT_HOMEDIR "/SYS\$LOGIN/gnupg"
+#else
+#define GNUPG_DEFAULT_HOMEDIR "~/.gnupg"
+#endif
+
+#ifdef HAVE_DOSISH_SYSTEM
+#define DIRSEP_C '\\'
+#define DIRSEP_S "\\"
+#else
+#define DIRSEP_C '/'
+#define DIRSEP_S "/"
+#endif
+
+
+#ifdef HAVE_W32_SYSTEM
+#define RTLD_LAZY 0
+
+static __inline__ void *
+dlopen (const char * name, int flag)
+{
+ void * hd = LoadLibrary (name);
+ return hd;
+}
+
+static __inline__ void *
+dlsym (void * hd, const char * sym)
+{
+ if (hd && sym)
+ {
+ void * fnc = GetProcAddress (hd, sym);
+ if (!fnc)
+ return NULL;
+ return fnc;
+ }
+ return NULL;
+}
+
+static __inline__ int
+dlclose (void * hd)
+{
+ if (hd)
+ {
+ FreeLibrary (hd);
+ return 0;
+ }
+ return -1;
+}
+
+
+/* Return a string from the W32 Registry or NULL in case of error.
+ Caller must release the return value. A NULL for root is an alias
+ for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. */
+static char *
+read_w32_registry_string (const char *root, const char *dir, const char *name)
+{
+ HKEY root_key, key_handle;
+ DWORD n1, nbytes, type;
+ char *result = NULL;
+
+ if ( !root )
+ root_key = HKEY_CURRENT_USER;
+ else if ( !strcmp( root, "HKEY_CLASSES_ROOT" ) )
+ root_key = HKEY_CLASSES_ROOT;
+ else if ( !strcmp( root, "HKEY_CURRENT_USER" ) )
+ root_key = HKEY_CURRENT_USER;
+ else if ( !strcmp( root, "HKEY_LOCAL_MACHINE" ) )
+ root_key = HKEY_LOCAL_MACHINE;
+ else if ( !strcmp( root, "HKEY_USERS" ) )
+ root_key = HKEY_USERS;
+ else if ( !strcmp( root, "HKEY_PERFORMANCE_DATA" ) )
+ root_key = HKEY_PERFORMANCE_DATA;
+ else if ( !strcmp( root, "HKEY_CURRENT_CONFIG" ) )
+ root_key = HKEY_CURRENT_CONFIG;
+ else
+ return NULL;
+
+ if ( RegOpenKeyEx ( root_key, dir, 0, KEY_READ, &key_handle ) )
+ {
+ if (root)
+ return NULL; /* no need for a RegClose, so return direct */
+ /* It seems to be common practise to fall back to HKLM. */
+ if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) )
+ return NULL; /* still no need for a RegClose, so return direct */
+ }
+
+ nbytes = 1;
+ if ( RegQueryValueEx( key_handle, name, 0, NULL, NULL, &nbytes ) )
+ {
+ if (root)
+ goto leave;
+ /* Try to fallback to HKLM also vor a missing value. */
+ RegCloseKey (key_handle);
+ if (RegOpenKeyEx (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle) )
+ return NULL; /* Nope. */
+ if (RegQueryValueEx ( key_handle, name, 0, NULL, NULL, &nbytes))
+ goto leave;
+ }
+ result = malloc ( (n1=nbytes+1) );
+ if ( !result )
+ goto leave;
+ if ( RegQueryValueEx ( key_handle, name, 0, &type, result, &n1 ) )
+ {
+ free(result); result = NULL;
+ goto leave;
+ }
+ result[nbytes] = 0; /* Make sure it is really a string. */
+ if (type == REG_EXPAND_SZ && strchr (result, '%'))
+ {
+ char *tmp;
+
+ n1 += 1000;
+ tmp = malloc (n1+1);
+ if (!tmp)
+ goto leave;
+ nbytes = ExpandEnvironmentStrings (result, tmp, n1);
+ if (nbytes && nbytes > n1)
+ {
+ free (tmp);
+ n1 = nbytes;
+ tmp = malloc (n1 + 1);
+ if (!tmp)
+ goto leave;
+ nbytes = ExpandEnvironmentStrings (result, tmp, n1);
+ if (nbytes && nbytes > n1) {
+ free (tmp); /* Oops - truncated, better don't expand at all. */
+ goto leave;
+ }
+ tmp[nbytes] = 0;
+ free (result);
+ result = tmp;
+ }
+ else if (nbytes) /* Okay, reduce the length. */
+ {
+ tmp[nbytes] = 0;
+ free (result);
+ result = malloc (strlen (tmp)+1);
+ if (!result)
+ result = tmp;
+ else
+ {
+ strcpy (result, tmp);
+ free (tmp);
+ }
+ }
+ else /* Error - don't expand. */
+ {
+ free (tmp);
+ }
+ }
+
+ leave:
+ RegCloseKey( key_handle );
+ return result;
+}
+
+
+/* This is a helper function to load and run a Windows function from
+ either of one DLLs. */
+static HRESULT
+w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
+{
+ static int initialized;
+ static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
+
+ if (!initialized)
+ {
+ static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
+ void *handle;
+ int i;
+
+ initialized = 1;
+
+ for (i=0, handle = NULL; !handle && dllnames[i]; i++)
+ {
+ handle = dlopen (dllnames[i], RTLD_LAZY);
+ if (handle)
+ {
+ func = dlsym (handle, "SHGetFolderPathA");
+ if (!func)
+ {
+ dlclose (handle);
+ handle = NULL;
+ }
+ }
+ }
+ }
+
+ if (func)
+ return func (a,b,c,d,e);
+ else
+ return -1;
+}
+
+
+#if 0
+static char *
+find_program_in_inst_dir (const char *name)
+{
+ char *result = NULL;
+ char *tmp;
+
+ tmp = read_w32_registry_string ("HKEY_LOCAL_MACHINE",
+ "Software\\GNU\\GnuPG",
+ "Install Directory");
+ if (!tmp)
+ return NULL;
+
+ result = malloc (strlen (tmp) + 1 + strlen (name) + 1);
+ if (!result)
+ {
+ free (tmp);
+ return NULL;
+ }
+
+ strcpy (stpcpy (stpcpy (result, tmp), "\\"), name);
+ free (tmp);
+ if (access (result, F_OK))
+ {
+ free (result);
+ return NULL;
+ }
+
+ return result;
+}
+
+
+static char *
+find_program_at_standard_place (const char *name)
+{
+ char path[MAX_PATH];
+ char *result = NULL;
+
+ if (w32_shgetfolderpath (NULL, CSIDL_PROGRAM_FILES, NULL, 0, path) >= 0)
+ {
+ result = malloc (strlen (path) + 1 + strlen (name) + 1);
+ if (result)
+ {
+ strcpy (stpcpy (stpcpy (result, path), "\\"), name);
+ if (access (result, F_OK))
+ {
+ free (result);
+ result = NULL;
+ }
+ }
+ }
+ return result;
+}
+#endif
+#endif
+
+
+const char *
+get_dirmngr_ldap_path (void)
+{
+ static char *pgmname;
+
+#ifdef HAVE_W32_SYSTEM
+ if (! pgmname)
+ {
+ const char *dir = dirmngr_libexecdir ();
+ const char *exe = "\\dirmngr_ldap.exe";
+ pgmname = malloc (strlen (dir) + strlen (exe) + 1);
+ if (pgmname)
+ strcpy (stpcpy (pgmname, dir), exe);
+ }
+#endif
+ if (!pgmname)
+ pgmname = DIRMNGR_LIBEXECDIR "/dirmngr_ldap";
+ return pgmname;
+}
+
+
+
+/* Home directory. */
+
+#ifdef HAVE_W32_SYSTEM
+#ifndef CSIDL_APPDATA
+#define CSIDL_APPDATA 0x001a
+#endif
+#ifndef CSIDL_LOCAL_APPDATA
+#define CSIDL_LOCAL_APPDATA 0x001c
+#endif
+#ifndef CSIDL_COMMON_APPDATA
+#define CSIDL_COMMON_APPDATA 0x0023
+#endif
+#ifndef CSIDL_FLAG_CREATE
+#define CSIDL_FLAG_CREATE 0x8000
+#endif
+#endif /*HAVE_W32_SYSTEM*/
+
+/* Get the standard home directory. In general this function should
+ not be used as it does not consider a registry value (under W32) or
+ the GNUPGHOME environment variable. It is better to use
+ default_homedir(). */
+const char *
+standard_homedir (void)
+{
+#ifdef HAVE_W32_SYSTEM
+ static const char *dir;
+
+ if (!dir)
+ {
+ char path[MAX_PATH];
+
+ /* It might be better to use LOCAL_APPDATA because this is
+ defined as "non roaming" and thus more likely to be kept
+ locally. For private keys this is desired. However, given
+ that many users copy private keys anyway forth and back,
+ using a system roaming services might be better than to let
+ them do it manually. A security conscious user will anyway
+ use the registry entry to have better control. */
+ if (w32_shgetfolderpath (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
+ NULL, 0, path) >= 0)
+ {
+ char *tmp = xmalloc (strlen (path) + 6 +1);
+ strcpy (stpcpy (tmp, path), "\\gnupg");
+ dir = tmp;
+
+ /* Try to create the directory if it does not yet exists. */
+ if (access (dir, F_OK))
+ CreateDirectory (dir, NULL);
+ }
+ else
+ dir = GNUPG_DEFAULT_HOMEDIR;
+ }
+ return dir;
+#else/*!HAVE_W32_SYSTEM*/
+ return GNUPG_DEFAULT_HOMEDIR;
+#endif /*!HAVE_W32_SYSTEM*/
+}
+
+/* Set up the default home directory. The usual --homedir option
+ should be parsed later. */
+const char *
+default_homedir (void)
+{
+ const char *dir;
+
+ dir = getenv ("GNUPGHOME");
+#ifdef HAVE_W32_SYSTEM
+ if (!dir || !*dir)
+ {
+ static const char *saved_dir;
+
+ if (!saved_dir)
+ {
+ if (!dir || !*dir)
+ {
+ char *tmp;
+
+ tmp = read_w32_registry_string (NULL, "Software\\GNU\\GnuPG",
+ "HomeDir");
+ if (tmp && *tmp)
+ {
+ xfree (tmp);
+ tmp = NULL;
+ }
+ if (tmp)
+ saved_dir = tmp;
+ }
+
+ if (!saved_dir)
+ saved_dir = standard_homedir ();
+ }
+ dir = saved_dir;
+ }
+#endif /*HAVE_W32_SYSTEM*/
+ if (!dir || !*dir)
+ dir = GNUPG_DEFAULT_HOMEDIR;
+
+ return dir;
+}
+
+
+#ifdef HAVE_W32_SYSTEM
+static const char *
+w32_rootdir (void)
+{
+ static int got_dir;
+ static char dir[MAX_PATH+5];
+
+ if (!got_dir)
+ {
+ char *p;
+
+ if ( !GetModuleFileName ( NULL, dir, MAX_PATH) )
+ {
+ log_debug ("GetModuleFileName failed: %s\n", w32_strerror (0));
+ *dir = 0;
+ }
+ got_dir = 1;
+ p = strrchr (dir, DIRSEP_C);
+ if (p)
+ *p = 0;
+ else
+ {
+ log_debug ("bad filename `%s' returned for this process\n", dir);
+ *dir = 0;
+ }
+ }
+
+ if (*dir)
+ return dir;
+ /* Fallback to the hardwired value. */
+ return DIRMNGR_LIBEXECDIR;
+}
+
+static const char *
+w32_commondir (void)
+{
+ static char *dir;
+
+ if (!dir)
+ {
+ char path[MAX_PATH];
+
+ if (w32_shgetfolderpath (NULL, CSIDL_COMMON_APPDATA,
+ NULL, 0, path) >= 0)
+ {
+ char *tmp = xmalloc (strlen (path) + 4 +1);
+ strcpy (stpcpy (tmp, path), "\\GNU");
+ dir = tmp;
+ /* No auto create of the directory. Either the installer or
+ the admin has to create these directories. */
+ }
+ else
+ {
+ /* Ooops: Not defined - probably an old Windows version.
+ Use the installation directory instead. */
+ dir = xstrdup (w32_rootdir ());
+ }
+ }
+
+ return dir;
+}
+#endif /*HAVE_W32_SYSTEM*/
+
+
+
+
+/* Return the name of the sysconfdir. This is a static string. This
+ function is required because under Windows we can't simply compile
+ it in. */
+const char *
+dirmngr_sysconfdir (void)
+{
+#ifdef HAVE_W32_SYSTEM
+ static char *name;
+
+ if (!name)
+ {
+ const char *s1, *s2;
+ s1 = w32_commondir ();
+ s2 = DIRSEP_S "etc" DIRSEP_S "dirmngr";
+ name = xmalloc (strlen (s1) + strlen (s2) + 1);
+ strcpy (stpcpy (name, s1), s2);
+ }
+ return name;
+#else /*!HAVE_W32_SYSTEM*/
+ return DIRMNGR_SYSCONFDIR;
+#endif /*!HAVE_W32_SYSTEM*/
+}
+
+
+/* Return the name of the libexec directory. The name is allocated in
+ a static area on the first use. This function won't fail. */
+const char *
+dirmngr_libexecdir (void)
+{
+#ifdef HAVE_W32_SYSTEM
+ return w32_rootdir ();
+#else /*!HAVE_W32_SYSTEM*/
+ return DIRMNGR_LIBEXECDIR;
+#endif /*!HAVE_W32_SYSTEM*/
+}
+
+
+const char *
+dirmngr_datadir (void)
+{
+#ifdef HAVE_W32_SYSTEM
+ static char *name;
+
+ if (!name)
+ {
+ const char *s1, *s2;
+ s1 = w32_commondir ();
+ s2 = DIRSEP_S "lib" DIRSEP_S "dirmngr";
+ name = xmalloc (strlen (s1) + strlen (s2) + 1);
+ strcpy (stpcpy (name, s1), s2);
+ }
+ return name;
+#else /*!HAVE_W32_SYSTEM*/
+ return DIRMNGR_DATADIR;
+#endif /*!HAVE_W32_SYSTEM*/
+}
+
+
+const char *
+dirmngr_cachedir (void)
+{
+#ifdef HAVE_W32_SYSTEM
+ static const char *dir;
+
+ if (!dir)
+ {
+ char path[MAX_PATH];
+ const char *s1[] = { "GNU", "cache", "dirmngr", NULL };
+ int s1_len;
+ const char **comp;
+
+ s1_len = 0;
+ for (comp = s1; *comp; comp++)
+ {
+ /* Take account for the separator. */
+ s1_len += 1 + strlen (*comp);
+ }
+
+ if (w32_shgetfolderpath (NULL, CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE,
+ NULL, 0, path) >= 0)
+ {
+ char *tmp = xmalloc (strlen (path) + s1_len + 1);
+ char *p;
+
+ p = stpcpy (tmp, path);
+ for (comp = s1; *comp; comp++)
+ {
+ p = stpcpy (p, "\\");
+ p = stpcpy (p, *comp);
+
+ if (access (tmp, F_OK))
+ CreateDirectory (tmp, NULL);
+ }
+
+ dir = tmp;
+ }
+ else
+ dir = "c:\\temp\\cache\\dirmngr";
+ }
+ return dir;
+#else /*!HAVE_W32_SYSTEM*/
+ return DIRMNGR_CACHEDIR;
+#endif /*!HAVE_W32_SYSTEM*/
+}
+
+
+const char *
+default_socket_name (void)
+{
+#ifdef HAVE_W32_SYSTEM
+ static char *name;
+
+ if (!name)
+ {
+ char s1[MAX_PATH];
+ const char *s2;
+
+ /* We need something akin CSIDL_COMMON_PROGRAMS, but local
+ (non-roaming). This is becuase the file needs to be on the
+ local machine and makes only sense on that machine.
+ CSIDL_WINDOWS seems to be the only location which guarantees
+ that. */
+ if (w32_shgetfolderpath (NULL, CSIDL_WINDOWS, NULL, 0, s1) < 0)
+ strcpy (s1, "C:\\WINDOWS");
+ s2 = DIRSEP_S "S.dirmngr";
+ name = xmalloc (strlen (s1) + strlen (s2) + 1);
+ strcpy (stpcpy (name, s1), s2);
+ }
+ return name;
+#else /*!HAVE_W32_SYSTEM*/
+ return DIRMNGR_SOCKETDIR "/socket";
+#endif /*!HAVE_W32_SYSTEM*/
+}
diff --git a/dirmngr/http.c b/dirmngr/http.c
new file mode 100644
index 000000000..b10ba254e
--- /dev/null
+++ b/dirmngr/http.c
@@ -0,0 +1,1861 @@
+/* http.c - HTTP protocol handler
+ * Copyright (C) 1999, 2001, 2002, 2003, 2004,
+ * 2006, 2009 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+/* Simple HTTP client implementation. We try to keep the code as
+ self-contained as possible. There are some contraints however:
+
+ - estream is required. We now require estream because it provides a
+ very useful and portable asprintf implementation and the fopencookie
+ function.
+ - stpcpy is required
+ - fixme: list other requirements.
+
+
+ - With HTTP_USE_GNUTLS support for https is provided (this also
+ requires estream).
+ - With HTTP_NO_WSASTARTUP the socket initialization is not done
+ under Windows. This is useful if the socket layer has already
+ been initialized elsewhere. This also avoids the installation of
+ an exit handler to cleanup the socket layer.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+
+#ifdef HAVE_W32_SYSTEM
+# include <windows.h>
+#else /*!HAVE_W32_SYSTEM*/
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <sys/time.h>
+# include <time.h>
+# include <netinet/in.h>
+# include <arpa/inet.h>
+# include <netdb.h>
+#endif /*!HAVE_W32_SYSTEM*/
+
+#include <pth.h>
+
+#ifdef HTTP_USE_GNUTLS
+# include <gnutls/gnutls.h>
+/* For non-understandable reasons GNUTLS dropped the _t suffix from
+ all types. yes, ISO-C might be read as this but there are still
+ other name space conflicts and using _t is actually a Good
+ Thing. */
+typedef gnutls_session gnutls_session_t;
+typedef gnutls_transport_ptr gnutls_transport_ptr_t;
+#endif /*HTTP_USE_GNUTLS*/
+
+#ifdef TEST
+#undef USE_DNS_SRV
+#endif
+
+#include "util.h"
+#include "i18n.h"
+#include "http.h"
+#ifdef USE_DNS_SRV
+#include "srv.h"
+#else /*!USE_DNS_SRV*/
+/* If we are not compiling with SRV record support we provide stub
+ data structures. */
+#ifndef MAXDNAME
+#define MAXDNAME 1025
+#endif
+struct srventry
+{
+ unsigned short priority;
+ unsigned short weight;
+ unsigned short port;
+ int run_count;
+ char target[MAXDNAME];
+};
+#endif/*!USE_DNS_SRV*/
+
+
+#ifdef HAVE_W32_SYSTEM
+#define sock_close(a) closesocket(a)
+#else
+#define sock_close(a) close(a)
+#endif
+
+#ifndef EAGAIN
+#define EAGAIN EWOULDBLOCK
+#endif
+
+#define HTTP_PROXY_ENV "http_proxy"
+#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */
+#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "01234567890@" \
+ "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
+
+/* A long counter type. */
+#ifdef HAVE_STRTOULL
+typedef unsigned long long longcounter_t;
+#define counter_strtoul(a) strtoull ((a), NULL, 10)
+#else
+typedef unsigned long longcounter_t;
+#define counter_strtoul(a) strtoul ((a), NULL, 10)
+#endif
+
+#ifndef HTTP_USE_GNUTLS
+typedef void * gnutls_session_t;
+#endif
+
+static gpg_error_t do_parse_uri (parsed_uri_t uri, int only_local_part);
+static int remove_escapes (char *string);
+static int insert_escapes (char *buffer, const char *string,
+ const char *special);
+static uri_tuple_t parse_tuple (char *string);
+static gpg_error_t send_request (http_t hd,
+ const char *auth, const char *proxy);
+static char *build_rel_path (parsed_uri_t uri);
+static gpg_error_t parse_response (http_t hd);
+
+static int connect_server (const char *server, unsigned short port,
+ unsigned int flags, const char *srvtag);
+
+static ssize_t cookie_read (void *cookie, void *buffer, size_t size);
+static ssize_t cookie_write (void *cookie, const void *buffer, size_t size);
+static int cookie_close (void *cookie);
+
+static es_cookie_io_functions_t cookie_functions =
+ {
+ cookie_read,
+ cookie_write,
+ NULL,
+ cookie_close
+ };
+
+struct cookie_s
+{
+ int fd; /* File descriptor or -1 if already closed. */
+ gnutls_session_t tls_session; /* TLS session context or NULL if not used. */
+
+ /* The remaining content length and a flag telling whether to use
+ the content length. */
+ longcounter_t content_length;
+ unsigned int content_length_valid:1;
+
+ /* Flag to communicate with the close handler. */
+ unsigned int keep_socket:1;
+};
+typedef struct cookie_s *cookie_t;
+
+
+#ifdef HTTP_USE_GNUTLS
+static gpg_error_t (*tls_callback) (http_t, gnutls_session_t, int);
+#endif /*HTTP_USE_GNUTLS*/
+
+
+/* An object to save header lines. */
+struct header_s
+{
+ struct header_s *next;
+ char *value; /* The value of the header (malloced). */
+ char name[1]; /* The name of the header (canonicalized). */
+};
+typedef struct header_s *header_t;
+
+
+/* Our handle context. */
+struct http_context_s
+{
+ unsigned int status_code;
+ int sock;
+ unsigned int in_data:1;
+ unsigned int is_http_0_9:1;
+ estream_t fp_read;
+ estream_t fp_write;
+ void *write_cookie;
+ void *read_cookie;
+ void *tls_context;
+ parsed_uri_t uri;
+ http_req_t req_type;
+ char *buffer; /* Line buffer. */
+ size_t buffer_size;
+ unsigned int flags;
+ header_t headers; /* Received headers. */
+};
+
+
+
+
+#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
+
+#if GNUPG_MAJOR_VERSION == 1
+#define REQ_WINSOCK_MAJOR 1
+#define REQ_WINSOCK_MINOR 1
+#else
+#define REQ_WINSOCK_MAJOR 2
+#define REQ_WINSOCK_MINOR 2
+#endif
+
+
+static void
+deinit_sockets (void)
+{
+ WSACleanup();
+}
+
+static void
+init_sockets (void)
+{
+ static int initialized;
+ static WSADATA wsdata;
+
+ if (initialized)
+ return;
+
+ if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) )
+ {
+ log_error ("error initializing socket library: ec=%d\n",
+ (int)WSAGetLastError () );
+ return;
+ }
+ if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR
+ || HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR )
+ {
+ log_error ("socket library version is %x.%x - but %d.%d needed\n",
+ LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion),
+ REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR);
+ WSACleanup();
+ return;
+ }
+ atexit ( deinit_sockets );
+ initialized = 1;
+}
+#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
+
+
+
+/*
+ * Helper function to create an HTTP header with hex encoded data. A
+ * new buffer is returned. This buffer is the concatenation of the
+ * string PREFIX, the hex-encoded DATA of length LEN and the string
+ * SUFFIX. On error NULL is returned and ERRNO set.
+ */
+static char *
+make_header_line (const char *prefix, const char *suffix,
+ const void *data, size_t len )
+{
+ static unsigned char bintoasc[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789+/";
+ const unsigned int *s = data;
+ char *buffer, *p;
+
+ buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1);
+ if (!buffer)
+ return NULL;
+ p = stpcpy (buffer, prefix);
+ for ( ; len >= 3 ; len -= 3, s += 3 )
+ {
+ *p++ = bintoasc[(s[0] >> 2) & 077];
+ *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
+ *p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077];
+ *p++ = bintoasc[s[2]&077];
+ }
+ if ( len == 2 )
+ {
+ *p++ = bintoasc[(s[0] >> 2) & 077];
+ *p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
+ *p++ = bintoasc[((s[1]<<2)&074)];
+ *p++ = '=';
+ }
+ else if ( len == 1 )
+ {
+ *p++ = bintoasc[(s[0] >> 2) & 077];
+ *p++ = bintoasc[(s[0] <<4)&060];
+ *p++ = '=';
+ *p++ = '=';
+ }
+ strcpy (p, suffix);
+ return buffer;
+}
+
+
+
+
+void
+http_register_tls_callback ( gpg_error_t (*cb) (http_t, void *, int) )
+{
+#ifdef HTTP_USE_GNUTLS
+ tls_callback = (gpg_error_t (*) (http_t, gnutls_session_t, int))cb;
+#else
+ (void)cb;
+#endif
+}
+
+
+
+/* Start a HTTP retrieval and return on success in R_HD a context
+ pointer for completing the the request and to wait for the
+ response. */
+gpg_error_t
+http_open (http_t *r_hd, http_req_t reqtype, const char *url,
+ const char *auth, unsigned int flags, const char *proxy,
+ void *tls_context)
+{
+ gpg_error_t err;
+ http_t hd;
+
+ *r_hd = NULL;
+
+ if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST))
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ /* Make need_header default unless ignore_cl is set. We might want
+ to drop the need_header entirely. */
+ if (!(flags & HTTP_FLAG_IGNORE_CL))
+ flags |= HTTP_FLAG_NEED_HEADER;
+
+ /* Create the handle. */
+ hd = xtrycalloc (1, sizeof *hd);
+ if (!hd)
+ return gpg_error_from_syserror ();
+ hd->sock = -1;
+ hd->req_type = reqtype;
+ hd->flags = flags;
+ hd->tls_context = tls_context;
+
+ err = http_parse_uri (&hd->uri, url);
+ if (!err)
+ err = send_request (hd, auth, proxy);
+
+ if (err)
+ {
+ if (!hd->fp_read && !hd->fp_write && hd->sock != -1)
+ sock_close (hd->sock);
+ if (hd->fp_read)
+ es_fclose (hd->fp_read);
+ if (hd->fp_write)
+ es_fclose (hd->fp_write);
+ http_release_parsed_uri (hd->uri);
+ xfree (hd);
+ }
+ else
+ *r_hd = hd;
+ return err;
+}
+
+
+void
+http_start_data (http_t hd)
+{
+ if (!hd->in_data)
+ {
+ es_fputs ("\r\n", hd->fp_write);
+ es_fflush (hd->fp_write);
+ hd->in_data = 1;
+ }
+ else
+ es_fflush (hd->fp_write);
+}
+
+
+gpg_error_t
+http_wait_response (http_t hd)
+{
+ gpg_error_t err;
+ cookie_t cookie;
+
+ /* Make sure that we are in the data. */
+ http_start_data (hd);
+
+ cookie = hd->write_cookie;
+ if (!cookie)
+ return gpg_error (GPG_ERR_INTERNAL);
+
+ /* Close the write stream but keep the socket open. */
+ cookie->keep_socket = 1;
+ es_fclose (hd->fp_write);
+ hd->fp_write = NULL;
+ hd->write_cookie = NULL;
+
+ /* Shutdown one end of the socket is desired. As per HTTP/1.0 this
+ is not required but some very old servers (e.g. the original pksd
+ key server didn't worked without it. */
+ if ((hd->flags & HTTP_FLAG_SHUTDOWN))
+ shutdown (hd->sock, 1);
+ hd->in_data = 0;
+
+ /* Create a new cookie and a stream for reading. */
+ cookie = xtrycalloc (1, sizeof *cookie);
+ if (!cookie)
+ return gpg_error_from_syserror ();
+ cookie->fd = hd->sock;
+ if (hd->uri->use_tls)
+ cookie->tls_session = hd->tls_context;
+
+ hd->read_cookie = cookie;
+ hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
+ if (!hd->fp_read)
+ {
+ xfree (cookie);
+ hd->read_cookie = NULL;
+ return gpg_error_from_syserror ();
+ }
+
+ err = parse_response (hd);
+ return err;
+}
+
+
+/* Convenience function to send a request and wait for the response.
+ Closes the handle on error. If PROXY is not NULL, this value will
+ be used as an HTTP proxy and any enabled $http_proxy gets
+ ignored. */
+gpg_error_t
+http_open_document (http_t *r_hd, const char *document,
+ const char *auth, unsigned int flags, const char *proxy,
+ void *tls_context)
+{
+ gpg_error_t err;
+
+ err = http_open (r_hd, HTTP_REQ_GET, document, auth, flags,
+ proxy, tls_context);
+ if (err)
+ return err;
+
+ err = http_wait_response (*r_hd);
+ if (err)
+ http_close (*r_hd, 0);
+
+ return err;
+}
+
+
+void
+http_close (http_t hd, int keep_read_stream)
+{
+ if (!hd)
+ return;
+ if (!hd->fp_read && !hd->fp_write && hd->sock != -1)
+ sock_close (hd->sock);
+ if (hd->fp_read && !keep_read_stream)
+ es_fclose (hd->fp_read);
+ if (hd->fp_write)
+ es_fclose (hd->fp_write);
+ http_release_parsed_uri (hd->uri);
+ while (hd->headers)
+ {
+ header_t tmp = hd->headers->next;
+ xfree (hd->headers->value);
+ xfree (hd->headers);
+ hd->headers = tmp;
+ }
+ xfree (hd->buffer);
+ xfree (hd);
+}
+
+
+estream_t
+http_get_read_ptr (http_t hd)
+{
+ return hd?hd->fp_read:NULL;
+}
+
+estream_t
+http_get_write_ptr (http_t hd)
+{
+ return hd?hd->fp_write:NULL;
+}
+
+unsigned int
+http_get_status_code (http_t hd)
+{
+ return hd?hd->status_code:0;
+}
+
+
+
+/*
+ * Parse an URI and put the result into the newly allocated RET_URI.
+ * The caller must always use release_parsed_uri() to releases the
+ * resources (even on error).
+ */
+gpg_error_t
+http_parse_uri (parsed_uri_t * ret_uri, const char *uri)
+{
+ *ret_uri = xcalloc (1, sizeof **ret_uri + strlen (uri));
+ strcpy ((*ret_uri)->buffer, uri);
+ return do_parse_uri (*ret_uri, 0);
+}
+
+void
+http_release_parsed_uri (parsed_uri_t uri)
+{
+ if (uri)
+ {
+ uri_tuple_t r, r2;
+
+ for (r = uri->query; r; r = r2)
+ {
+ r2 = r->next;
+ xfree (r);
+ }
+ xfree (uri);
+ }
+}
+
+
+static gpg_error_t
+do_parse_uri (parsed_uri_t uri, int only_local_part)
+{
+ uri_tuple_t *tail;
+ char *p, *p2, *p3, *pp;
+ int n;
+
+ p = uri->buffer;
+ n = strlen (uri->buffer);
+
+ /* Initialize all fields to an empty string or an empty list. */
+ uri->scheme = uri->host = uri->path = p + n;
+ uri->port = 0;
+ uri->params = uri->query = NULL;
+ uri->use_tls = 0;
+
+ /* A quick validity check. */
+ if (strspn (p, VALID_URI_CHARS) != n)
+ return gpg_error (GPG_ERR_BAD_URI); /* Invalid characters found. */
+
+ if (!only_local_part)
+ {
+ /* Find the scheme. */
+ if (!(p2 = strchr (p, ':')) || p2 == p)
+ return gpg_error (GPG_ERR_BAD_URI); /* No scheme. */
+ *p2++ = 0;
+ for (pp=p; *pp; pp++)
+ *pp = tolower (*(unsigned char*)pp);
+ uri->scheme = p;
+ if (!strcmp (uri->scheme, "http"))
+ uri->port = 80;
+#ifdef HTTP_USE_GNUTLS
+ else if (!strcmp (uri->scheme, "https"))
+ {
+ uri->port = 443;
+ uri->use_tls = 1;
+ }
+#endif
+ else
+ return gpg_error (GPG_ERR_INV_URI); /* Unsupported scheme */
+
+ p = p2;
+
+ /* Find the hostname */
+ if (*p != '/')
+ return gpg_error (GPG_ERR_INV_URI); /* Does not start with a slash. */
+
+ p++;
+ if (*p == '/') /* There seems to be a hostname. */
+ {
+ p++;
+ if ((p2 = strchr (p, '/')))
+ *p2++ = 0;
+
+ /* Check for username/password encoding */
+ if ((p3 = strchr (p, '@')))
+ {
+ uri->auth = p;
+ *p3++ = '\0';
+ p = p3;
+ }
+
+ for (pp=p; *pp; pp++)
+ *pp = tolower (*(unsigned char*)pp);
+ uri->host = p;
+ if ((p3 = strchr (p, ':')))
+ {
+ *p3++ = 0;
+ uri->port = atoi (p3);
+ }
+
+ uri->host = p;
+ if ((n = remove_escapes (uri->host)) < 0)
+ return gpg_error (GPG_ERR_BAD_URI);
+ if (n != strlen (p))
+ return gpg_error (GPG_ERR_BAD_URI); /* Hostname incudes a Nul. */
+ p = p2 ? p2 : NULL;
+ }
+ } /* End global URI part. */
+
+ /* Parse the pathname part */
+ if (!p || !*p)
+ return 0; /* We don't have a path. Okay. */
+
+ /* TODO: Here we have to check params. */
+
+ /* Do we have a query part? */
+ if ((p2 = strchr (p, '?')))
+ *p2++ = 0;
+
+ uri->path = p;
+ if ((n = remove_escapes (p)) < 0)
+ return gpg_error (GPG_ERR_BAD_URI);
+ if (n != strlen (p))
+ return gpg_error (GPG_ERR_BAD_URI); /* Path includes a Nul. */
+ p = p2 ? p2 : NULL;
+
+ if (!p || !*p)
+ return 0; /* We don't have a query string. Okay. */
+
+ /* Now parse the query string. */
+ tail = &uri->query;
+ for (;;)
+ {
+ uri_tuple_t elem;
+
+ if ((p2 = strchr (p, '&')))
+ *p2++ = 0;
+ if (!(elem = parse_tuple (p)))
+ return gpg_error (GPG_ERR_BAD_URI);
+ *tail = elem;
+ tail = &elem->next;
+
+ if (!p2)
+ break; /* Ready. */
+ p = p2;
+ }
+
+ return 0;
+}
+
+
+/*
+ * Remove all %xx escapes; this is done in-place. Returns: New length
+ * of the string.
+ */
+static int
+remove_escapes (char *string)
+{
+ int n = 0;
+ unsigned char *p, *s;
+
+ for (p = s = (unsigned char*)string; *s; s++)
+ {
+ if (*s == '%')
+ {
+ if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2]))
+ {
+ s++;
+ *p = *s >= '0' && *s <= '9' ? *s - '0' :
+ *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
+ *p <<= 4;
+ s++;
+ *p |= *s >= '0' && *s <= '9' ? *s - '0' :
+ *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
+ 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; /* Make sure to keep a string terminator. */
+ return n;
+}
+
+
+static int
+insert_escapes (char *buffer, const char *string,
+ const char *special)
+{
+ const unsigned char *s = (const unsigned char*)string;
+ int n = 0;
+
+ for (; *s; s++)
+ {
+ if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
+ {
+ if (buffer)
+ *(unsigned char*)buffer++ = *s;
+ n++;
+ }
+ else
+ {
+ if (buffer)
+ {
+ sprintf (buffer, "%%%02X", *s);
+ buffer += 3;
+ }
+ n += 3;
+ }
+ }
+ return n;
+}
+
+
+/* Allocate a new string from STRING using standard HTTP escaping as
+ well as escaping of characters given in SPECIALS. A common pattern
+ for SPECIALS is "%;?&=". However it depends on the needs, for
+ example "+" and "/: often needs to be escaped too. Returns NULL on
+ failure and sets ERRNO. */
+char *
+http_escape_string (const char *string, const char *specials)
+{
+ int n;
+ char *buf;
+
+ n = insert_escapes (NULL, string, specials);
+ buf = xtrymalloc (n+1);
+ if (buf)
+ {
+ insert_escapes (buf, string, specials);
+ buf[n] = 0;
+ }
+ return buf;
+}
+
+
+
+static uri_tuple_t
+parse_tuple (char *string)
+{
+ char *p = string;
+ char *p2;
+ int n;
+ uri_tuple_t tuple;
+
+ if ((p2 = strchr (p, '=')))
+ *p2++ = 0;
+ if ((n = remove_escapes (p)) < 0)
+ return NULL; /* Bad URI. */
+ if (n != strlen (p))
+ return NULL; /* Name with a Nul in it. */
+ tuple = xtrycalloc (1, sizeof *tuple);
+ if (!tuple)
+ return NULL; /* Out of core. */
+ tuple->name = p;
+ if (!p2) /* We have only the name, so we assume an empty value string. */
+ {
+ tuple->value = p + strlen (p);
+ tuple->valuelen = 0;
+ tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */
+ }
+ else /* Name and value. */
+ {
+ if ((n = remove_escapes (p2)) < 0)
+ {
+ xfree (tuple);
+ return NULL; /* Bad URI. */
+ }
+ tuple->value = p2;
+ tuple->valuelen = n;
+ }
+ return tuple;
+}
+
+
+/*
+ * Send a HTTP request to the server
+ * Returns 0 if the request was successful
+ */
+static gpg_error_t
+send_request (http_t hd, const char *auth, const char *proxy)
+{
+ gnutls_session_t tls_session;
+ gpg_error_t err;
+ const char *server;
+ char *request, *p;
+ unsigned short port;
+ const char *http_proxy = NULL;
+ char *proxy_authstr = NULL;
+ char *authstr = NULL;
+ int save_errno;
+ cookie_t cookie;
+
+
+ tls_session = hd->tls_context;
+ if (hd->uri->use_tls && !tls_session)
+ {
+ log_error ("TLS requested but no GNUTLS context provided\n");
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+
+ server = *hd->uri->host ? hd->uri->host : "localhost";
+ port = hd->uri->port ? hd->uri->port : 80;
+
+ if ( (proxy && *proxy)
+ || ( (hd->flags & HTTP_FLAG_TRY_PROXY)
+ && (http_proxy = getenv (HTTP_PROXY_ENV))
+ && *http_proxy ))
+ {
+ parsed_uri_t uri;
+
+ if (proxy)
+ http_proxy = proxy;
+
+ err = http_parse_uri (&uri, http_proxy);
+ if (err)
+ {
+ log_error ("invalid HTTP proxy (%s): %s\n",
+ http_proxy, gpg_strerror (err));
+ http_release_parsed_uri (uri);
+ return gpg_error (GPG_ERR_CONFIGURATION);
+
+ }
+
+ if (uri->auth)
+ {
+ remove_escapes (uri->auth);
+ proxy_authstr = make_header_line ("Proxy-Authorization: Basic ",
+ "\r\n",
+ uri->auth, strlen(uri->auth));
+ if (!proxy_authstr)
+ {
+ err = gpg_error_from_syserror ();
+ http_release_parsed_uri (uri);
+ return err;
+ }
+ }
+
+ hd->sock = connect_server (*uri->host ? uri->host : "localhost",
+ uri->port ? uri->port : 80,
+ hd->flags, hd->uri->scheme);
+ save_errno = errno;
+ http_release_parsed_uri (uri);
+ }
+ else
+ {
+ hd->sock = connect_server (server, port, hd->flags, hd->uri->scheme);
+ save_errno = errno;
+ }
+
+ if (hd->sock == -1)
+ {
+ xfree (proxy_authstr);
+ return (save_errno
+ ? gpg_error_from_errno (save_errno)
+ : gpg_error (GPG_ERR_NOT_FOUND));
+ }
+
+#ifdef HTTP_USE_GNUTLS
+ if (hd->uri->use_tls)
+ {
+ int rc;
+
+ gnutls_transport_set_ptr (tls_session, (gnutls_transport_ptr_t)hd->sock);
+ do
+ {
+ rc = gnutls_handshake (tls_session);
+ }
+ while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
+ if (rc < 0)
+ {
+ log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
+ xfree (proxy_authstr);
+ return gpg_error (GPG_ERR_NETWORK);
+ }
+
+ if (tls_callback)
+ {
+ err = tls_callback (hd, tls_session, 0);
+ if (err)
+ {
+ log_info ("TLS connection authentication failed: %s\n",
+ gpg_strerror (err));
+ xfree (proxy_authstr);
+ return err;
+ }
+ }
+ }
+#endif /*HTTP_USE_GNUTLS*/
+
+ if (auth || hd->uri->auth)
+ {
+ char *myauth;
+
+ if (auth)
+ {
+ myauth = xtrystrdup (auth);
+ if (!myauth)
+ {
+ xfree (proxy_authstr);
+ return gpg_error_from_syserror ();
+ }
+ remove_escapes (myauth);
+ }
+ else
+ {
+ remove_escapes (hd->uri->auth);
+ myauth = hd->uri->auth;
+ }
+
+ authstr = make_header_line ("Authorization: Basic %s", "\r\n",
+ myauth, strlen (myauth));
+ if (auth)
+ xfree (myauth);
+
+ if (!authstr)
+ {
+ xfree (proxy_authstr);
+ return gpg_error_from_syserror ();
+ }
+ }
+
+ p = build_rel_path (hd->uri);
+ if (!p)
+ return gpg_error_from_syserror ();
+
+ if (http_proxy && *http_proxy)
+ {
+ request = es_asprintf
+ ("%s http://%s:%hu%s%s HTTP/1.0\r\n%s%s",
+ hd->req_type == HTTP_REQ_GET ? "GET" :
+ hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
+ hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
+ server, port, *p == '/' ? "" : "/", p,
+ authstr ? authstr : "",
+ proxy_authstr ? proxy_authstr : "");
+ }
+ else
+ {
+ char portstr[35];
+
+ if (port == 80)
+ *portstr = 0;
+ else
+ snprintf (portstr, sizeof portstr, ":%u", port);
+
+ request = es_asprintf
+ ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
+ hd->req_type == HTTP_REQ_GET ? "GET" :
+ hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
+ hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
+ *p == '/' ? "" : "/", p, server, portstr,
+ authstr? authstr:"");
+ }
+ xfree (p);
+ if (!request)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (authstr);
+ xfree (proxy_authstr);
+ return err;
+ }
+
+ /* First setup estream so that we can write even the first line
+ using estream. This is also required for the sake of gnutls. */
+ cookie = xtrycalloc (1, sizeof *cookie);
+ if (!cookie)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ cookie->fd = hd->sock;
+ hd->write_cookie = cookie;
+ if (hd->uri->use_tls)
+ cookie->tls_session = tls_session;
+ hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
+ if (!hd->fp_write)
+ {
+ xfree (cookie);
+ hd->write_cookie = NULL;
+ err = gpg_error_from_syserror ();
+ }
+ else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
+ err = gpg_error_from_syserror ();
+ else
+ err = 0;
+
+ leave:
+ es_free (request);
+ xfree (authstr);
+ xfree (proxy_authstr);
+
+ return err;
+}
+
+
+/*
+ * Build the relative path from the parsed URI. Minimal
+ * implementation. May return NULL in case of memory failure; errno
+ * is then set accordingly.
+ */
+static char *
+build_rel_path (parsed_uri_t uri)
+{
+ uri_tuple_t r;
+ char *rel_path, *p;
+ int n;
+
+ /* Count the needed space. */
+ n = insert_escapes (NULL, uri->path, "%;?&");
+ /* TODO: build params. */
+ for (r = uri->query; r; r = r->next)
+ {
+ n++; /* '?'/'&' */
+ n += insert_escapes (NULL, r->name, "%;?&=");
+ if (!r->no_value)
+ {
+ n++; /* '=' */
+ n += insert_escapes (NULL, r->value, "%;?&=");
+ }
+ }
+ n++;
+
+ /* Now allocate and copy. */
+ p = rel_path = xtrymalloc (n);
+ if (!p)
+ return NULL;
+ n = insert_escapes (p, uri->path, "%;?&");
+ p += n;
+ /* TODO: add params. */
+ for (r = uri->query; r; r = r->next)
+ {
+ *p++ = r == uri->query ? '?' : '&';
+ n = insert_escapes (p, r->name, "%;?&=");
+ p += n;
+ if (!r->no_value)
+ {
+ *p++ = '=';
+ /* TODO: Use valuelen. */
+ n = insert_escapes (p, r->value, "%;?&=");
+ p += n;
+ }
+ }
+ *p = 0;
+ return rel_path;
+}
+
+
+/* Transform a header name into a standard capitalized format; e.g.
+ "Content-Type". Conversion stops at the colon. As usual we don't
+ use the localized versions of ctype.h. */
+static void
+capitalize_header_name (char *name)
+{
+ int first = 1;
+
+ for (; *name && *name != ':'; name++)
+ {
+ if (*name == '-')
+ first = 1;
+ else if (first)
+ {
+ if (*name >= 'a' && *name <= 'z')
+ *name = *name - 'a' + 'A';
+ first = 0;
+ }
+ else if (*name >= 'A' && *name <= 'Z')
+ *name = *name - 'A' + 'a';
+ }
+}
+
+
+/* Store an HTTP header line in LINE away. Line continuation is
+ supported as well as merging of headers with the same name. This
+ function may modify LINE. */
+static gpg_error_t
+store_header (http_t hd, char *line)
+{
+ size_t n;
+ char *p, *value;
+ header_t h;
+
+ n = strlen (line);
+ if (n && line[n-1] == '\n')
+ {
+ line[--n] = 0;
+ if (n && line[n-1] == '\r')
+ line[--n] = 0;
+ }
+ if (!n) /* we are never called to hit this. */
+ return gpg_error (GPG_ERR_BUG);
+ if (*line == ' ' || *line == '\t')
+ {
+ /* Continuation. This won't happen too often as it is not
+ recommended. We use a straightforward implementaion. */
+ if (!hd->headers)
+ return gpg_error (GPG_ERR_PROTOCOL_VIOLATION);
+ n += strlen (hd->headers->value);
+ p = xtrymalloc (n+1);
+ if (!p)
+ return gpg_error_from_syserror ();
+ strcpy (stpcpy (p, hd->headers->value), line);
+ xfree (hd->headers->value);
+ hd->headers->value = p;
+ return 0;
+ }
+
+ capitalize_header_name (line);
+ p = strchr (line, ':');
+ if (!p)
+ return gpg_error (GPG_ERR_PROTOCOL_VIOLATION);
+ *p++ = 0;
+ while (*p == ' ' || *p == '\t')
+ p++;
+ value = p;
+
+ for (h=hd->headers; h; h = h->next)
+ if ( !strcmp (h->name, line) )
+ break;
+ if (h)
+ {
+ /* We have already seen a line with that name. Thus we assume
+ it is a comma separated list and merge them. */
+ p = xtrymalloc (strlen (h->value) + 1 + strlen (value)+ 1);
+ if (!p)
+ return gpg_error_from_syserror ();
+ strcpy (stpcpy (stpcpy (p, h->value), ","), value);
+ xfree (h->value);
+ h->value = p;
+ return 0;
+ }
+
+ /* Append a new header. */
+ h = xtrymalloc (sizeof *h + strlen (line));
+ if (!h)
+ return gpg_error_from_syserror ();
+ strcpy (h->name, line);
+ h->value = xtrymalloc (strlen (value)+1);
+ if (!h->value)
+ {
+ xfree (h);
+ return gpg_error_from_syserror ();
+ }
+ strcpy (h->value, value);
+ h->next = hd->headers;
+ hd->headers = h;
+
+ return 0;
+}
+
+
+/* Return the header NAME from the last response. The returned value
+ is valid as along as HD has not been closed and no othe request has
+ been send. If the header was not found, NULL is returned. Name
+ must be canonicalized, that is the first letter of each dash
+ delimited part must be uppercase and all other letters lowercase.
+ Note that the context must have been opened with the
+ HTTP_FLAG_NEED_HEADER. */
+const char *
+http_get_header (http_t hd, const char *name)
+{
+ header_t h;
+
+ for (h=hd->headers; h; h = h->next)
+ if ( !strcmp (h->name, name) )
+ return h->value;
+ return NULL;
+}
+
+
+
+/*
+ * Parse the response from a server.
+ * Returns: Errorcode and sets some files in the handle
+ */
+static gpg_error_t
+parse_response (http_t hd)
+{
+ char *line, *p, *p2;
+ size_t maxlen, len;
+ cookie_t cookie = hd->read_cookie;
+ const char *s;
+
+ /* Delete old header lines. */
+ while (hd->headers)
+ {
+ header_t tmp = hd->headers->next;
+ xfree (hd->headers->value);
+ xfree (hd->headers);
+ hd->headers = tmp;
+ }
+
+ /* Wait for the status line. */
+ do
+ {
+ maxlen = MAX_LINELEN;
+ len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
+ line = hd->buffer;
+ if (!line)
+ return gpg_error_from_syserror (); /* Out of core. */
+ if (!maxlen)
+ return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */
+ if (!len)
+ return gpg_error (GPG_ERR_EOF);
+ if ( (hd->flags & HTTP_FLAG_LOG_RESP) )
+ log_info ("RESP: `%.*s'\n",
+ (int)strlen(line)-(*line&&line[1]?2:0),line);
+ }
+ while (!*line);
+
+ if ((p = strchr (line, '/')))
+ *p++ = 0;
+ if (!p || strcmp (line, "HTTP"))
+ return 0; /* Assume http 0.9. */
+
+ if ((p2 = strpbrk (p, " \t")))
+ {
+ *p2++ = 0;
+ p2 += strspn (p2, " \t");
+ }
+ if (!p2)
+ return 0; /* Also assume http 0.9. */
+ p = p2;
+ /* TODO: Add HTTP version number check. */
+ if ((p2 = strpbrk (p, " \t")))
+ *p2++ = 0;
+ if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1])
+ || !isdigit ((unsigned int)p[2]) || p[3])
+ {
+ /* Malformed HTTP status code - assume http 0.9. */
+ hd->is_http_0_9 = 1;
+ hd->status_code = 200;
+ return 0;
+ }
+ hd->status_code = atoi (p);
+
+ /* Skip all the header lines and wait for the empty line. */
+ do
+ {
+ maxlen = MAX_LINELEN;
+ len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
+ line = hd->buffer;
+ if (!line)
+ return gpg_error_from_syserror (); /* Out of core. */
+ /* Note, that we can silently ignore truncated lines. */
+ if (!len)
+ return gpg_error (GPG_ERR_EOF);
+ /* Trim line endings of empty lines. */
+ if ((*line == '\r' && line[1] == '\n') || *line == '\n')
+ *line = 0;
+ if ( (hd->flags & HTTP_FLAG_LOG_RESP) )
+ log_info ("RESP: `%.*s'\n",
+ (int)strlen(line)-(*line&&line[1]?2:0),line);
+ if ( (hd->flags & HTTP_FLAG_NEED_HEADER) && *line )
+ {
+ gpg_error_t err = store_header (hd, line);
+ if (err)
+ return err;
+ }
+ }
+ while (len && *line);
+
+ cookie->content_length_valid = 0;
+ if (!(hd->flags & HTTP_FLAG_IGNORE_CL))
+ {
+ s = http_get_header (hd, "Content-Length");
+ if (s)
+ {
+ cookie->content_length_valid = 1;
+ cookie->content_length = counter_strtoul (s);
+ }
+ }
+
+ return 0;
+}
+
+#if 0
+static int
+start_server ()
+{
+ struct sockaddr_in mya;
+ struct sockaddr_in peer;
+ int fd, client;
+ fd_set rfds;
+ int addrlen;
+ int i;
+
+ if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
+ {
+ log_error ("socket() failed: %s\n", strerror (errno));
+ return -1;
+ }
+ i = 1;
+ if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i)))
+ log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno));
+
+ mya.sin_family = AF_INET;
+ memset (&mya.sin_addr, 0, sizeof (mya.sin_addr));
+ mya.sin_port = htons (11371);
+
+ if (bind (fd, (struct sockaddr *) &mya, sizeof (mya)))
+ {
+ log_error ("bind to port 11371 failed: %s\n", strerror (errno));
+ sock_close (fd);
+ return -1;
+ }
+
+ if (listen (fd, 5))
+ {
+ log_error ("listen failed: %s\n", strerror (errno));
+ sock_close (fd);
+ return -1;
+ }
+
+ for (;;)
+ {
+ FD_ZERO (&rfds);
+ FD_SET (fd, &rfds);
+
+ if (select (fd + 1, &rfds, NULL, NULL, NULL) <= 0)
+ continue; /* ignore any errors */
+
+ if (!FD_ISSET (fd, &rfds))
+ continue;
+
+ addrlen = sizeof peer;
+ client = accept (fd, (struct sockaddr *) &peer, &addrlen);
+ if (client == -1)
+ continue; /* oops */
+
+ log_info ("connect from %s\n", inet_ntoa (peer.sin_addr));
+
+ fflush (stdout);
+ fflush (stderr);
+ if (!fork ())
+ {
+ int c;
+ FILE *fp;
+
+ fp = fdopen (client, "r");
+ while ((c = getc (fp)) != EOF)
+ putchar (c);
+ fclose (fp);
+ exit (0);
+ }
+ sock_close (client);
+ }
+
+
+ return 0;
+}
+#endif
+
+/* Actually connect to a server. Returns the file descripto or -1 on
+ error. ERRNO is set on error. */
+static int
+connect_server (const char *server, unsigned short port,
+ unsigned int flags, const char *srvtag)
+{
+ int sock = -1;
+ int srvcount = 0;
+ int hostfound = 0;
+ int srv, connected;
+ int last_errno = 0;
+ struct srventry *serverlist = NULL;
+
+#ifdef HAVE_W32_SYSTEM
+ unsigned long inaddr;
+
+#ifndef HTTP_NO_WSASTARTUP
+ init_sockets ();
+#endif
+ /* Win32 gethostbyname doesn't handle IP addresses internally, so we
+ try inet_addr first on that platform only. */
+ inaddr = inet_addr(server);
+ if ( inaddr != INADDR_NONE )
+ {
+ struct sockaddr_in addr;
+
+ memset(&addr,0,sizeof(addr));
+
+ sock = socket(AF_INET,SOCK_STREAM,0);
+ if ( sock==INVALID_SOCKET )
+ {
+ log_error("error creating socket: ec=%d\n",(int)WSAGetLastError());
+ return -1;
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ memcpy (&addr.sin_addr,&inaddr,sizeof(inaddr));
+
+ if (!connect (sock,(struct sockaddr *)&addr,sizeof(addr)) )
+ return sock;
+ sock_close(sock);
+ return -1;
+ }
+#endif /*HAVE_W32_SYSTEM*/
+
+#ifdef USE_DNS_SRV
+ /* Do the SRV thing */
+ if ((flags & HTTP_FLAG_TRY_SRV) && srvtag)
+ {
+ /* We're using SRV, so append the tags. */
+ if (1+strlen (srvtag) + 6 + strlen (server) + 1 <= MAXDNAME)
+ {
+ char srvname[MAXDNAME];
+
+ stpcpy (stpcpy (stpcpy (stpcpy (srvname,"_"), srvtag),
+ "._tcp."), server);
+ srvcount = getsrv (srvname, &serverlist);
+ }
+ }
+#else /*!USE_DNS_SRV*/
+ (void)flags;
+ (void)srvtag;
+#endif /*!USE_DNS_SRV*/
+
+ if (!serverlist)
+ {
+ /* Either we're not using SRV, or the SRV lookup failed. Make
+ up a fake SRV record. */
+ serverlist = xtrycalloc (1, sizeof *serverlist);
+ if (!serverlist)
+ return -1; /* Out of core. */
+ serverlist->port = port;
+ strncpy (serverlist->target, server, MAXDNAME);
+ serverlist->target[MAXDNAME-1] = '\0';
+ srvcount = 1;
+ }
+
+#ifdef HAVE_GETADDRINFO
+ connected = 0;
+ for (srv=0; srv < srvcount && !connected; srv++)
+ {
+ struct addrinfo hints, *res, *ai;
+ char portstr[35];
+
+ sprintf (portstr, "%hu", port);
+ memset (&hints, 0, sizeof (hints));
+ hints.ai_socktype = SOCK_STREAM;
+ if (getaddrinfo (serverlist[srv].target, portstr, &hints, &res))
+ continue; /* Not found - try next one. */
+ hostfound = 1;
+
+ for (ai = res; ai && !connected; ai = ai->ai_next)
+ {
+ if (sock != -1)
+ sock_close (sock);
+ sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+ if (sock == -1)
+ {
+ int save_errno = errno;
+ log_error ("error creating socket: %s\n", strerror (errno));
+ freeaddrinfo (res);
+ xfree (serverlist);
+ errno = save_errno;
+ return -1;
+ }
+
+ if (connect (sock, ai->ai_addr, ai->ai_addrlen))
+ last_errno = errno;
+ else
+ connected = 1;
+ }
+ freeaddrinfo (res);
+ }
+#else /* !HAVE_GETADDRINFO */
+ connected = 0;
+ for (srv=0; srv < srvcount && !connected; srv++)
+ {
+ int i;
+ struct hostent *host = NULL;
+ struct sockaddr_in addr;
+
+ /* Note: This code is not thread-safe. */
+
+ memset (&addr, 0, sizeof (addr));
+ host = gethostbyname (serverlist[srv].target);
+ if (!host)
+ continue;
+ hostfound = 1;
+
+ if (sock != -1)
+ sock_close (sock);
+ sock = socket (host->h_addrtype, SOCK_STREAM, 0);
+ if (sock == -1)
+ {
+ log_error (_("error creating socket: %s\n"), strerror (errno));
+ xfree (serverlist);
+ return -1;
+ }
+
+ addr.sin_family = host->h_addrtype;
+ if (addr.sin_family != AF_INET)
+ {
+ log_error ("unknown address family for `%s'\n",
+ serverlist[srv].target);
+ xfree (serverlist);
+ return -1;
+ }
+ addr.sin_port = htons (serverlist[srv].port);
+ if (host->h_length != 4)
+ {
+ log_error ("illegal address length for `%s'\n",
+ serverlist[srv].target);
+ xfree (serverlist);
+ return -1;
+ }
+
+ /* Try all A records until one responds. */
+ for (i = 0; host->h_addr_list[i] && !connected; i++)
+ {
+ memcpy (&addr.sin_addr, host->h_addr_list[i], host->h_length);
+ if (connect (sock, (struct sockaddr *) &addr, sizeof (addr)))
+ last_errno = errno;
+ else
+ {
+ connected = 1;
+ break;
+ }
+ }
+ }
+#endif /* !HAVE_GETADDRINFO */
+
+ xfree (serverlist);
+
+ if (!connected)
+ {
+#ifdef HAVE_W32_SYSTEM
+ log_error ("can't connect to `%s': %s%sec=%d\n",
+ server,
+ hostfound? "":_("host not found"),
+ hostfound? "":" - ", (int)WSAGetLastError());
+#else
+ log_error ("can't connect to `%s': %s\n",
+ server,
+ hostfound? strerror (last_errno):"host not found");
+#endif
+ if (sock != -1)
+ sock_close (sock);
+ errno = last_errno;
+ return -1;
+ }
+ return sock;
+}
+
+
+
+
+/* Read handler for estream. */
+static ssize_t
+cookie_read (void *cookie, void *buffer, size_t size)
+{
+ cookie_t c = cookie;
+ int nread;
+
+ if (c->content_length_valid)
+ {
+ if (!c->content_length)
+ return 0; /* EOF */
+ if (c->content_length < size)
+ size = c->content_length;
+ }
+
+#ifdef HTTP_USE_GNUTLS
+ if (c->tls_session)
+ {
+ again:
+ nread = gnutls_record_recv (c->tls_session, buffer, size);
+ if (nread < 0)
+ {
+ if (nread == GNUTLS_E_INTERRUPTED)
+ goto again;
+ if (nread == GNUTLS_E_AGAIN)
+ {
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 50000;
+ select (0, NULL, NULL, NULL, &tv);
+ goto again;
+ }
+ if (nread == GNUTLS_E_REHANDSHAKE)
+ goto again; /* A client is allowed to just ignore this request. */
+ log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
+ errno = EIO;
+ return -1;
+ }
+ }
+ else
+#endif /*HTTP_USE_GNUTLS*/
+ {
+ do
+ {
+ nread = pth_read (c->fd, buffer, size);
+ }
+ while (nread == -1 && errno == EINTR);
+ }
+
+ if (c->content_length_valid && nread > 0)
+ {
+ if (nread < c->content_length)
+ c->content_length -= nread;
+ else
+ c->content_length = 0;
+ }
+
+ return nread;
+}
+
+/* Write handler for estream. */
+static ssize_t
+cookie_write (void *cookie, const void *buffer, size_t size)
+{
+ cookie_t c = cookie;
+ int nwritten = 0;
+
+#ifdef HTTP_USE_GNUTLS
+ if (c->tls_session)
+ {
+ int nleft = size;
+ while (nleft > 0)
+ {
+ nwritten = gnutls_record_send (c->tls_session, buffer, nleft);
+ if (nwritten <= 0)
+ {
+ if (nwritten == GNUTLS_E_INTERRUPTED)
+ continue;
+ if (nwritten == GNUTLS_E_AGAIN)
+ {
+ struct timeval tv;
+
+ tv.tv_sec = 0;
+ tv.tv_usec = 50000;
+ select (0, NULL, NULL, NULL, &tv);
+ continue;
+ }
+ log_info ("TLS network write failed: %s\n",
+ gnutls_strerror (nwritten));
+ errno = EIO;
+ return -1;
+ }
+ nleft -= nwritten;
+ buffer += nwritten;
+ }
+ }
+ else
+#endif /*HTTP_USE_GNUTLS*/
+ {
+ do
+ {
+ nwritten = pth_write (c->fd, buffer, size);
+ }
+ while (nwritten == -1 && errno == EINTR);
+ }
+
+ return nwritten;
+}
+
+/* Close handler for estream. */
+static int
+cookie_close (void *cookie)
+{
+ cookie_t c = cookie;
+
+ if (!c)
+ return 0;
+
+#ifdef HTTP_USE_GNUTLS
+ if (c->tls_session && !c->keep_socket)
+ {
+ /* This indicates that the read end has been closed. */
+ gnutls_bye (c->tls_session, GNUTLS_SHUT_RDWR);
+ }
+#endif /*HTTP_USE_GNUTLS*/
+ if (c->fd != -1 && !c->keep_socket)
+ sock_close (c->fd);
+
+ xfree (c);
+ return 0;
+}
+
+
+
+
+/**** Test code ****/
+#ifdef TEST
+
+static gpg_error_t
+verify_callback (http_t hd, void *tls_context, int reserved)
+{
+ log_info ("verification of certificates skipped\n");
+ return 0;
+}
+
+
+
+/* static void */
+/* my_gnutls_log (int level, const char *text) */
+/* { */
+/* fprintf (stderr, "gnutls:L%d: %s", level, text); */
+/* } */
+
+int
+main (int argc, char **argv)
+{
+ int rc;
+ parsed_uri_t uri;
+ uri_tuple_t r;
+ http_t hd;
+ int c;
+ gnutls_session_t tls_session = NULL;
+#ifdef HTTP_USE_GNUTLS
+ gnutls_certificate_credentials certcred;
+ const int certprio[] = { GNUTLS_CRT_X509, 0 };
+#endif /*HTTP_USE_GNUTLS*/
+ header_t hdr;
+
+ es_init ();
+ log_set_prefix ("http-test", 1 | 4);
+ if (argc == 1)
+ {
+ /*start_server (); */
+ return 0;
+ }
+
+ if (argc != 2)
+ {
+ fprintf (stderr, "usage: http-test uri\n");
+ return 1;
+ }
+ argc--;
+ argv++;
+
+#ifdef HTTP_USE_GNUTLS
+ rc = gnutls_global_init ();
+ if (rc)
+ log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
+ rc = gnutls_certificate_allocate_credentials (&certcred);
+ if (rc)
+ log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
+ gnutls_strerror (rc));
+/* rc = gnutls_certificate_set_x509_trust_file */
+/* (certcred, "ca.pem", GNUTLS_X509_FMT_PEM); */
+/* if (rc) */
+/* log_error ("gnutls_certificate_set_x509_trust_file failed: %s\n", */
+/* gnutls_strerror (rc)); */
+ rc = gnutls_init (&tls_session, GNUTLS_CLIENT);
+ if (rc)
+ log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
+ rc = gnutls_set_default_priority (tls_session);
+ if (rc)
+ log_error ("gnutls_set_default_priority failed: %s\n",
+ gnutls_strerror (rc));
+ rc = gnutls_certificate_type_set_priority (tls_session, certprio);
+ if (rc)
+ log_error ("gnutls_certificate_type_set_priority failed: %s\n",
+ gnutls_strerror (rc));
+ rc = gnutls_credentials_set (tls_session, GNUTLS_CRD_CERTIFICATE, certcred);
+ if (rc)
+ log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
+/* gnutls_global_set_log_function (my_gnutls_log); */
+/* gnutls_global_set_log_level (4); */
+
+ http_register_tls_callback (verify_callback);
+#endif /*HTTP_USE_GNUTLS*/
+
+ rc = http_parse_uri (&uri, *argv);
+ if (rc)
+ {
+ log_error ("`%s': %s\n", *argv, gpg_strerror (rc));
+ http_release_parsed_uri (uri);
+ return 1;
+ }
+
+ printf ("Scheme: %s\n", uri->scheme);
+ printf ("Host : %s\n", uri->host);
+ printf ("Port : %u\n", uri->port);
+ printf ("Path : %s\n", uri->path);
+ for (r = uri->params; r; r = r->next)
+ {
+ printf ("Params: %s", r->name);
+ if (!r->no_value)
+ {
+ printf ("=%s", r->value);
+ if (strlen (r->value) != r->valuelen)
+ printf (" [real length=%d]", (int) r->valuelen);
+ }
+ putchar ('\n');
+ }
+ for (r = uri->query; r; r = r->next)
+ {
+ printf ("Query : %s", r->name);
+ if (!r->no_value)
+ {
+ printf ("=%s", r->value);
+ if (strlen (r->value) != r->valuelen)
+ printf (" [real length=%d]", (int) r->valuelen);
+ }
+ putchar ('\n');
+ }
+ http_release_parsed_uri (uri);
+ uri = NULL;
+
+ rc = http_open_document (&hd, *argv, NULL,
+ HTTP_FLAG_NEED_HEADER,
+ NULL, tls_session);
+ if (rc)
+ {
+ log_error ("can't get `%s': %s\n", *argv, gpg_strerror (rc));
+ return 1;
+ }
+ log_info ("open_http_document succeeded; status=%u\n",
+ http_get_status_code (hd));
+ for (hdr = hd->headers; hdr; hdr = hdr->next)
+ printf ("HDR: %s: %s\n", hdr->name, hdr->value);
+ switch (http_get_status_code (hd))
+ {
+ case 200:
+ while ((c = es_getc (http_get_read_ptr (hd))) != EOF)
+ putchar (c);
+ break;
+ case 301:
+ case 302:
+ printf ("Redirected to `%s'\n", http_get_header (hd, "Location"));
+ break;
+ }
+ http_close (hd, 0);
+
+#ifdef HTTP_USE_GNUTLS
+ gnutls_deinit (tls_session);
+ gnutls_certificate_free_credentials (certcred);
+ gnutls_global_deinit ();
+#endif /*HTTP_USE_GNUTLS*/
+
+ return 0;
+}
+#endif /*TEST*/
+
+/*
+Local Variables:
+compile-command: "gcc -I.. -I../gl -DTEST -DHAVE_CONFIG_H -Wall -O2 -g -o http-test http.c -L. -lcommon -L../jnlib -ljnlib -lgcrypt -lpth -lgnutls"
+End:
+*/
diff --git a/dirmngr/http.h b/dirmngr/http.h
new file mode 100644
index 000000000..6e688b8d1
--- /dev/null
+++ b/dirmngr/http.h
@@ -0,0 +1,109 @@
+/* http.h - HTTP protocol handler
+ * Copyright (C) 1999, 2000, 2001, 2003,
+ * 2006 Free Software Foundation, Inc.
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+#ifndef GNUPG_COMMON_HTTP_H
+#define GNUPG_COMMON_HTTP_H
+
+#include <gpg-error.h>
+#include "estream.h"
+
+struct uri_tuple_s {
+ struct uri_tuple_s *next;
+ const char *name; /* A pointer into name. */
+ char *value; /* A pointer to value (a Nul is always appended). */
+ size_t valuelen; /* The real length of the value; we need it
+ because the value may contain embedded Nuls. */
+ int no_value; /* True if no value has been given in the URL. */
+};
+typedef struct uri_tuple_s *uri_tuple_t;
+
+struct parsed_uri_s
+{
+ /* All these pointers point into BUFFER; most stuff is not escaped. */
+ char *scheme; /* Pointer to the scheme string (lowercase). */
+ int use_tls; /* Whether TLS should be used. */
+ char *auth; /* username/password for basic auth */
+ char *host; /* Host (converted to lowercase). */
+ unsigned short port; /* Port (always set if the host is set). */
+ char *path; /* Path. */
+ uri_tuple_t params; /* ";xxxxx" */
+ uri_tuple_t query; /* "?xxx=yyy" */
+ char buffer[1]; /* Buffer which holds a (modified) copy of the URI. */
+};
+typedef struct parsed_uri_s *parsed_uri_t;
+
+typedef enum
+ {
+ HTTP_REQ_GET = 1,
+ HTTP_REQ_HEAD = 2,
+ HTTP_REQ_POST = 3
+ }
+http_req_t;
+
+/* We put the flag values into an enum, so that gdb can display them. */
+enum
+ {
+ HTTP_FLAG_TRY_PROXY = 1,
+ HTTP_FLAG_SHUTDOWN = 2,
+ HTTP_FLAG_TRY_SRV = 4,
+ HTTP_FLAG_LOG_RESP = 8,
+ HTTP_FLAG_NEED_HEADER = 16,
+ HTTP_FLAG_IGNORE_CL = 32
+ };
+
+struct http_context_s;
+typedef struct http_context_s *http_t;
+
+void http_register_tls_callback (gpg_error_t (*cb) (http_t, void *, int));
+
+gpg_error_t http_parse_uri (parsed_uri_t *ret_uri, const char *uri);
+
+void http_release_parsed_uri (parsed_uri_t uri);
+
+gpg_error_t http_open (http_t *r_hd, http_req_t reqtype,
+ const char *url,
+ const char *auth,
+ unsigned int flags,
+ const char *proxy,
+ void *tls_context);
+
+void http_start_data (http_t hd);
+
+gpg_error_t http_wait_response (http_t hd);
+
+void http_close (http_t hd, int keep_read_stream);
+
+gpg_error_t http_open_document (http_t *r_hd,
+ const char *document,
+ const char *auth,
+ unsigned int flags,
+ const char *proxy,
+ void *tls_context);
+
+estream_t http_get_read_ptr (http_t hd);
+estream_t http_get_write_ptr (http_t hd);
+unsigned int http_get_status_code (http_t hd);
+const char *http_get_header (http_t hd, const char *name);
+
+char *http_escape_string (const char *string, const char *specials);
+
+
+#endif /*GNUPG_COMMON_HTTP_H*/
diff --git a/dirmngr/ldap-url.c b/dirmngr/ldap-url.c
new file mode 100644
index 000000000..eedcc6fd2
--- /dev/null
+++ b/dirmngr/ldap-url.c
@@ -0,0 +1,932 @@
+/* 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,s) strtok (n,d)
+#define Debug(a,b,c,d,e)
+void ldap_pvt_hex_unescape( char *s );
+
+
+
+/* $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;
+ char *lasts;
+ 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, &lasts );
+ s != NULL;
+ s = ldap_utf8_strtok( NULL, brkstr, &lasts ) )
+ {
+ 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 000000000..f3104d818
--- /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.c b/dirmngr/ldap.c
new file mode 100644
index 000000000..fd3c3f510
--- /dev/null
+++ b/dirmngr/ldap.c
@@ -0,0 +1,1499 @@
+/* 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 <pth.h>
+
+#include "dirmngr.h"
+#include "exechelp.h"
+#include "crlfetch.h"
+#include "ldapserver.h"
+#include "misc.h"
+
+#ifdef HAVE_W32_SYSTEM
+#define setenv(a,b,c) SetEnvironmentVariable ((a),(b))
+#else
+#define pth_close(fd) close(fd)
+#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 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. */
+};
+
+
+/* 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. */
+ pth_event_t log_ev;
+ 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_PTH_CLOSE(fd) \
+ do { int _fd = fd; if (_fd != -1) { pth_close (_fd); fd = -1;} } while (0)
+
+
+/* Prototypes. */
+static gpg_error_t read_buffer (ksba_reader_t reader,
+ unsigned char *buffer, size_t count);
+
+
+
+
+/* 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;
+ }
+}
+
+
+/* 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_PTH_CLOSE (ctx->fd);
+ SAFE_PTH_CLOSE (ctx->log_fd);
+ if (ctx->log_ev)
+ pth_event_free (ctx->log_ev, PTH_FREE_THIS);
+ 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 pth_read function for pipes, always. */
+ do
+ n = pth_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_PTH_CLOSE (ctx->log_fd);
+ pth_event_free (ctx->log_ev, PTH_FREE_THIS);
+ ctx->log_ev = NULL;
+ 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;
+ time_t current_time;
+
+ (void)dummy;
+
+ for (;;)
+ {
+ pth_event_t timeout_ev;
+ int any_action = 0;
+
+ timeout_ev = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0));
+ if (! timeout_ev)
+ {
+ log_error (_("pth_event failed: %s\n"), strerror (errno));
+ pth_sleep (10);
+ continue;
+ }
+
+ for (ctx = wrapper_list; ctx; ctx = ctx->next)
+ {
+ if (ctx->log_fd != -1)
+ {
+ pth_event_isolate (ctx->log_ev);
+ pth_event_concat (timeout_ev, ctx->log_ev, NULL);
+ }
+ }
+
+ /* Note that the read FDs are actually handles. Thus, we can
+ not use pth_select, but have to use pth_wait. */
+ nfds = pth_wait (timeout_ev);
+ if (nfds < 0)
+ {
+ pth_event_free (timeout_ev, PTH_FREE_THIS);
+ log_error (_("pth_wait failed: %s\n"), strerror (errno));
+ pth_sleep (10);
+ continue;
+ }
+ if (pth_event_status (timeout_ev) == PTH_STATUS_OCCURRED)
+ nfds--;
+ pth_event_free (timeout_ev, PTH_FREE_THIS);
+
+ current_time = time (NULL);
+ if (current_time > INACTIVITY_TIMEOUT)
+ current_time -= 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
+ && pth_event_status (ctx->log_ev) == PTH_STATUS_OCCURRED)
+ {
+ 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 < current_time)
+ {
+ 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_PTH_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. */
+}
+
+
+
+/* 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;
+ while (wrapper_list)
+ pth_yield (NULL);
+}
+
+
+/* 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_PTH_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;
+
+ /* 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;
+ }
+
+ while (nleft > 0)
+ {
+ int n;
+ pth_event_t evt;
+ gpg_error_t err;
+
+ evt = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0));
+ n = pth_read_ev (ctx->fd, buffer, nleft, evt);
+ if (n < 0 && evt && pth_event_occurred (evt))
+ {
+ n = 0;
+ err = dirmngr_tick (ctx->ctrl);
+ if (err)
+ {
+ ctx->fd_error = err;
+ SAFE_PTH_CLOSE (ctx->fd);
+ if (evt)
+ pth_event_free (evt, PTH_FREE_THIS);
+ return -1;
+ }
+
+ }
+ else if (n < 0)
+ {
+ ctx->fd_error = gpg_error_from_errno (errno);
+ SAFE_PTH_CLOSE (ctx->fd);
+ if (evt)
+ pth_event_free (evt, PTH_FREE_THIS);
+ return -1;
+ }
+ else if (!n)
+ {
+ if (nleft == count)
+ {
+ if (evt)
+ pth_event_free (evt, PTH_FREE_THIS);
+ return -1; /* EOF. */
+ }
+ break;
+ }
+ nleft -= n;
+ buffer += n;
+ if (evt)
+ pth_event_free (evt, PTH_FREE_THIS);
+ 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.
+
+ 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.
+
+ 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. */
+static 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 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->log_ev = pth_event (PTH_EVENT_FD | PTH_UNTIL_FD_READABLE, ctx->log_fd);
+ if (! ctx->log_ev)
+ {
+ xfree (ctx);
+ return gpg_error_from_syserror ();
+ }
+ 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;
+}
+
+
+
+/* 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.)*/
+ 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 000000000..da702ec52
--- /dev/null
+++ b/dirmngr/ldapserver.c
@@ -0,0 +1,133 @@
+/* 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 000000000..eb1ab226d
--- /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 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);
+
+
+/* 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 000000000..040d4434a
--- /dev/null
+++ b/dirmngr/misc.c
@@ -0,0 +1,486 @@
+/* misc.c - miscellaneous
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2002, 2003, 2004 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");
+}
+
+
+/****************
+ * 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;
+}
+
diff --git a/dirmngr/misc.h b/dirmngr/misc.h
new file mode 100644
index 000000000..b721549ec
--- /dev/null
+++ b/dirmngr/misc.h
@@ -0,0 +1,87 @@
+/* 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);
+
+/* 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);
+
+
+#ifdef HAVE_FOPENCOOKIE
+/* We have to implement funopen in terms of glibc's fopencookie. */
+FILE *funopen(void *cookie,
+ int (*readfn)(void *, char *, int),
+ int (*writefn)(void *, const char *, int),
+ fpos_t (*seekfn)(void *, fpos_t, int),
+ int (*closefn)(void *));
+#endif /*HAVE_FOPENCOOKIE*/
+
+
+#endif /* MISC_H */
diff --git a/dirmngr/no-libgcrypt.c b/dirmngr/no-libgcrypt.c
new file mode 100644
index 000000000..fbbfd40ed
--- /dev/null
+++ b/dirmngr/no-libgcrypt.c
@@ -0,0 +1,154 @@
+/* no-libgcrypt.c - Replacement functions for libgcrypt.
+ * Copyright (C) 2003 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.
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "../common/util.h"
+#include "i18n.h"
+
+
+/* Replace libgcrypt's malloc functions which are used by
+ ../jnlib/libjnlib.a . ../common/util.h defines macros to map them
+ to xmalloc etc. */
+static void
+out_of_memory (void)
+{
+ log_fatal (_("error allocating enough memory: %s\n"), strerror (errno));
+}
+
+
+void *
+gcry_malloc (size_t n)
+{
+ return malloc (n);
+}
+
+void *
+gcry_malloc_secure (size_t n)
+{
+ return malloc (n);
+}
+
+void *
+gcry_xmalloc (size_t n)
+{
+ void *p = malloc (n);
+ if (!p)
+ out_of_memory ();
+ return p;
+}
+
+char *
+gcry_strdup (const char *string)
+{
+ char *p = malloc (strlen (string)+1);
+ if (p)
+ strcpy (p, string);
+ return p;
+}
+
+
+void *
+gcry_realloc (void *a, size_t n)
+{
+ return realloc (a, n);
+}
+
+void *
+gcry_xrealloc (void *a, size_t n)
+{
+ void *p = realloc (a, n);
+ if (!p)
+ out_of_memory ();
+ return p;
+}
+
+
+
+void *
+gcry_calloc (size_t n, size_t m)
+{
+ return calloc (n, m);
+}
+
+void *
+gcry_xcalloc (size_t n, size_t m)
+{
+ void *p = calloc (n, m);
+ if (!p)
+ out_of_memory ();
+ return p;
+}
+
+
+char *
+gcry_xstrdup (const char *string)
+{
+ void *p = malloc (strlen (string)+1);
+ if (!p)
+ out_of_memory ();
+ strcpy( p, string );
+ return p;
+}
+
+void
+gcry_free (void *a)
+{
+ if (a)
+ free (a);
+}
+
+
+/* We need this dummy because exechelp.c uses gcry_control to
+ terminate the secure memeory. */
+gcry_error_t
+gcry_control (enum gcry_ctl_cmds cmd, ...)
+{
+ (void)cmd;
+ return 0;
+}
+
+void
+gcry_set_outofcore_handler (gcry_handler_no_mem_t h, void *opaque)
+{
+ (void)h;
+ (void)opaque;
+}
+
+void
+gcry_set_fatalerror_handler (gcry_handler_error_t fnc, void *opaque)
+{
+ (void)fnc;
+ (void)opaque;
+}
+
+void
+gcry_set_log_handler (gcry_handler_log_t f, void *opaque)
+{
+ (void)f;
+ (void)opaque;
+}
+
+
+void
+gcry_create_nonce (void *buffer, size_t length)
+{
+ (void)buffer;
+ (void)length;
+
+ log_fatal ("unexpected call to gcry_create_nonce\n");
+}
diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c
new file mode 100644
index 000000000..a8db51d17
--- /dev/null
+++ b/dirmngr/ocsp.c
@@ -0,0 +1,799 @@
+/* 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"
+#include "estream.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,
+ (opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
+ |HTTP_FLAG_NEED_HEADER,
+ opt.http_proxy,
+ 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 000000000..cfab7dd6f
--- /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 000000000..a7b623cb1
--- /dev/null
+++ b/dirmngr/server.c
@@ -0,0 +1,1539 @@
+/* dirmngr.c - LDAP access
+ * Copyright (C) 2002 Klarälvdalens Datakonsult AB
+ * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009 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 <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"
+#include "ldapserver.h"
+#include "ocsp.h"
+#include "certcache.h"
+#include "validate.h"
+#include "misc.h"
+
+/* To avoid DoS attacks we limit the size of a certificate to
+ something reasonable. */
+#define MAX_CERT_LENGTH (8*1024)
+
+#define PARM_ERROR(t) assuan_set_error (ctx, \
+ gpg_error (GPG_ERR_ASS_PARAMETER), (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 serfver. */
+ ldap_server_t ldapservers;
+};
+
+
+
+
+/* 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;
+}
+
+
+
+/* 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;
+}
+
+
+/* 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)
+{
+ 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 PARM_ERROR (_("ldapserver missing"));
+
+ server = ldapserver_parse_one (line, "", 0);
+ if (! server)
+ return 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 0;
+}
+
+
+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 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");
+ }
+ }
+
+ if (err)
+ log_error (_("command %s failed: %s\n"), "ISVALID", gpg_strerror (err));
+ xfree (issuerhash);
+ return 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:
+ if (err)
+ log_error (_("command %s failed: %s\n"), "CHECKCRL", gpg_strerror (err));
+ ksba_cert_release (cert);
+ return 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:
+ if (err)
+ log_error (_("command %s failed: %s\n"), "CHECKOCSP", gpg_strerror (err));
+ ksba_cert_release (cert);
+ return 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)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ char *p;
+ strlist_t sl, list = NULL;
+ int truncated = 0, truncation_forced = 0;
+ int count = 0;
+ int local_count = 0;
+ unsigned char *value = NULL;
+ size_t valuelen;
+ struct ldapserver_iter ldapserver_iter;
+ cert_fetch_context_t fetch_context;
+ 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. */
+ 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);
+ }
+
+ 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);
+
+ if (err)
+ log_error (_("command %s failed: %s\n"), "LOOKUP", gpg_strerror (err));
+
+ return 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);
+ }
+ }
+
+ if (err)
+ log_error (_("command %s failed: %s\n"), "LOADCRL", gpg_strerror (err));
+ return 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;
+ FILE *fp = assuan_get_data_fp (ctx);
+
+ (void)line;
+
+ if (!fp)
+ return PARM_ERROR (_("no data stream"));
+
+ err = crl_cache_list (fp);
+ if (err)
+ log_error (_("command %s failed: %s\n"), "LISTCRLS", gpg_strerror (err));
+ return 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 wnats 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:
+ if (err)
+ log_error (_("command %s failed: %s\n"), "CACHECERT", gpg_strerror (err));
+ ksba_cert_release (cert);
+ return 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:
+ if (err)
+ log_error (_("command %s failed: %s\n"), "VALIDATE", gpg_strerror (err));
+ ksba_cert_release (cert);
+ return err;
+}
+
+
+
+/* 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 },
+ { "INPUT", NULL },
+ { "OUTPUT", NULL },
+ { 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;
+}
+
+
+static gpg_error_t
+reset_notify (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ (void)line;
+
+ ldapserver_list_free (ctrl->server_local->ldapservers);
+ 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;
+ }
+ }
+
+ ldap_wrapper_connection_cleanup (ctrl);
+
+ ldapserver_list_free (ctrl->server_local->ldapservers);
+ ctrl->server_local->ldapservers = NULL;
+
+ ctrl->server_local->assuan_ctx = NULL;
+ assuan_release (ctx);
+
+ 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 optioal string argumenst 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;
+}
+
+
+/* Note, that we ignore CTRL for now but use the first connection to
+ send the progress info back. */
+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/validate.c b/dirmngr/validate.c
new file mode 100644
index 000000000..538779842
--- /dev/null
+++ b/dirmngr/validate.c
@@ -0,0 +1,1160 @@
+/* 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_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 have not "
+ "been used for certification\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+ }
+
+ if (mode == 5)
+ {
+ if (use != ~0
+ && (have_ocsp_signing
+ || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN
+ |KSBA_KEYUSAGE_CRL_SIGN))))
+ return 0;
+ log_info (_("certificate should have not "
+ "been used for OCSP response signing\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+ }
+
+ if (mode == 6)
+ {
+ if ((use & (KSBA_KEYUSAGE_CRL_SIGN)))
+ return 0;
+ log_info (_("certificate should have not "
+ "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 have not been used "
+ "for encryption\n"):
+ mode==2? _("certificate should have not been used for signing\n"):
+ mode==1? _("certificate is not usable for encryption\n"):
+ _("certificate is not usable for signing\n"));
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+}
+
+/* Return 0 if the certificate CERT is usable for certification. */
+gpg_error_t
+cert_use_cert_p (ksba_cert_t cert)
+{
+ return cert_usage_p (cert, 4);
+}
+
+/* Return 0 if the certificate CERT is usable for signing OCSP
+ responses. */
+gpg_error_t
+cert_use_ocsp_p (ksba_cert_t cert)
+{
+ return cert_usage_p (cert, 5);
+}
+
+/* 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 000000000..0d9283c04
--- /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*/