diff options
author | Werner Koch <wk@gnupg.org> | 2010-06-09 18:53:51 +0200 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2010-06-09 18:53:51 +0200 |
commit | c3f08dcb7266efeac84f5f720ec0a353a45e950d (patch) | |
tree | 51501aa7d0e6dadb80576a1e982fdfde871bd2ad /dirmngr | |
parent | 2010-06-08 Marcus Brinkmann <marcus@g10code.de> (diff) | |
download | gnupg2-c3f08dcb7266efeac84f5f720ec0a353a45e950d.tar.xz gnupg2-c3f08dcb7266efeac84f5f720ec0a353a45e950d.zip |
Merged Dirmngr with GnuPG.
A few code changes to support dirmngr.
Diffstat (limited to 'dirmngr')
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*/ |