diff options
Diffstat (limited to 'dirmngr')
67 files changed, 53131 insertions, 0 deletions
diff --git a/dirmngr/ChangeLog-2011 b/dirmngr/ChangeLog-2011 new file mode 100644 index 0000000..243f2b5 --- /dev/null +++ b/dirmngr/ChangeLog-2011 @@ -0,0 +1,2407 @@ +2011-12-01 Werner Koch <wk@g10code.com> + + NB: ChangeLog files are no longer manually maintained. Starting + on December 1st, 2011 we put change information only in the GIT + commit log, and generate a top-level ChangeLog file from logs at + "make dist". See doc/HACKING for details. + +2011-11-24 Werner Koch <wk@g10code.com> + + * ks-engine-http.c (ks_http_help): Do not print help for hkp. + * ks-engine-hkp.c (ks_hkp_help): Print help only for hkp. + (send_request): Remove test code. + (map_host): Use xtrymalloc. + + * certcache.c (classify_pattern): Remove unused variable and make + explicit substring search work. + +2011-06-01 Marcus Brinkmann <mb@g10code.com> + + * Makefile.am (dirmngr_ldap_CFLAGS): Add $(LIBGCRYPT_CFLAGS), + which is needed by common/util.h. + +2011-04-25 Werner Koch <wk@g10code.com> + + * ks-engine-hkp.c (ks_hkp_search): Mark classify_user_id for use + with OpenPGP. + (ks_hkp_get): Ditto. + +2011-04-12 Werner Koch <wk@g10code.com> + + * ks-engine-hkp.c (ks_hkp_search, ks_hkp_get, ks_hkp_put): Factor + code out to .. + (make_host_part): new. + (hostinfo_s): New. + (create_new_hostinfo, find_hostinfo, sort_hostpool) + (select_random_host, map_host, mark_host_dead) + (ks_hkp_print_hosttable): New. + +2011-02-23 Werner Koch <wk@g10code.com> + + * certcache.c (get_cert_bysubject): Take care of a NULL argument. + (find_cert_bysubject): Ditto. Fixes bug#1300. + +2011-02-09 Werner Koch <wk@g10code.com> + + * ks-engine-kdns.c: New but only the framework. + + * server.c (cmd_keyserver): Add option --help. + (dirmngr_status_help): New. + * ks-action.c (ks_print_help): New. + (ks_action_help): New. + * ks-engine-finger.c (ks_finger_help): New. + * ks-engine-http.c (ks_http_help): New. + * ks-engine-hkp.c (ks_hkp_help): New. + + * ks-action.c (ks_action_fetch): Support http URLs. + * ks-engine-http.c: New. + + * ks-engine-finger.c (ks_finger_get): Rename to ks_finger_fetch. + Change caller. + +2011-02-08 Werner Koch <wk@g10code.com> + + * server.c (cmd_ks_fetch): New. + * ks-action.c (ks_action_fetch): New. + * ks-engine-finger.c: New. + +2011-02-03 Werner Koch <wk@g10code.com> + + * Makefile.am (dirmngr_LDADD): Remove -llber. + +2011-01-25 Werner Koch <wk@g10code.com> + + * dirmngr.c (handle_connections): Rewrite loop to use pth-select + so to sync timeouts to the full second. + (pth_thread_id): New. + (main) [W32CE]: Fix setting of default homedir. + + * ldap-wrapper.c (ldap_wrapper_thread): Sync to the full second. + Increate pth_wait timeout from 1 to 2 seconds. + +2011-01-20 Werner Koch <wk@g10code.com> + + * server.c (release_ctrl_keyservers): New. + (cmd_keyserver, cmd_ks_seach, cmd_ks_get, cmd_ks_put): New. + * dirmngr.h (uri_item_t): New. + (struct server_control_s): Add field KEYSERVERS. + * ks-engine-hkp.c: New. + * ks-engine.h: New. + * ks-action.c, ks-action.h: New. + * server.c: Include ks-action.h. + (cmd_ks_search): New. + * Makefile.am (dirmngr_SOURCES): Add new files. + +2011-01-19 Werner Koch <wk@g10code.com> + + * dirmngr.c (main): Use es_printf for --gpgconf-list. + +2010-12-14 Werner Koch <wk@g10code.com> + + * cdb.h (struct cdb) [W32]: Add field CDB_MAPPING. + * cdblib.c (cdb_init) [W32]: Save mapping handle. + (cdb_free) [W32]: Don't leak the mapping handle from cdb_init by + using the saved one. + + * crlcache.c (crl_cache_insert): Close unused matching files. + + * dirmngr.c (main) [W32CE]: Change homedir in daemon mode to /gnupg. + +2010-12-07 Werner Koch <wk@g10code.com> + + * dirmngr.c (TIMERTICK_INTERVAL) [W32CE]: Change to 60s. + +2010-11-23 Werner Koch <wk@g10code.com> + + * Makefile.am (dirmngr_LDFLAGS): Add extra_bin_ldflags. + (dirmngr_client_LDFLAGS): Ditto. + +2010-10-21 Werner Koch <wk@g10code.com> + + * dirmngr.c (main): Changed faked system time warning + +2010-10-15 Werner Koch <wk@g10code.com> + + * Makefile.am (CLEANFILES): Add no-libgcrypt.c. + +2010-09-16 Werner Koch <wk@g10code.com> + + * validate.c (validate_cert_chain): Use GPG_ERR_MISSING_ISSUER_CERT. + +2010-08-13 Werner Koch <wk@g10code.com> + + * Makefile.am (dirmngr_SOURCES): Add w32-ldap-help.h. + + * dirmngr_ldap.c (fetch_ldap): Call ldap_unbind. + + * w32-ldap-help.h: New. + * dirmngr_ldap.c [W32CE]: Include w32-ldap-help.h and use the + mapped ldap functions. + +2010-08-12 Werner Koch <wk@g10code.com> + + * crlcache.c (update_dir, crl_cache_insert): s/unlink/gnupg_remove/. + + * dirmngr.c (dirmngr_sighup_action): New. + + * server.c (cmd_killdirmngr, cmd_reloaddirmngr): New. + (struct server_local_s): Add field STOPME. + (start_command_handler): Act on STOPME. + +2010-08-06 Werner Koch <wk@g10code.com> + + * dirmngr.c (JNLIB_NEED_AFLOCAL): Define macro. + (main): Use SUN_LEN macro. + (main) [W32]: Allow EEXIST in addition to EADDRINUSE. + +2010-08-05 Werner Koch <wk@g10code.com> + + * server.c (set_error, leave_cmd): New. + (cmd_validate, cmd_ldapserver, cmd_isvalid, cmd_checkcrl) + (cmd_checkocsp, cmd_lookup, cmd_listcrls, cmd_cachecert): Use + leave_cmd. + (cmd_getinfo): New. + (data_line_cookie_write, data_line_cookie_close): New. + (cmd_listcrls): Replace assuan_get_data_fp by es_fopencookie. + + * misc.c (create_estream_ksba_reader, my_estream_ksba_reader_cb): New. + * certcache.c (load_certs_from_dir): Use create_estream_ksba_reader. + * crlcache.c (crl_cache_load): Ditto. + +2010-08-03 Werner Koch <wk@g10code.com> + + * dirmngr_ldap.c (pth_enter, pth_leave) [USE_LDAPWRAPPER]: Turn + into functions for use in a 'for' control stmt. + +2010-07-26 Werner Koch <wk@g10code.com> + + * dirmngr_ldap.c (print_ldap_entries): Remove special fwrite case + for W32 because that is now handles by estream. + +2010-07-25 Werner Koch <wk@g10code.com> + + * Makefile.am (dirmngr_SOURCES) [!USE_LDAPWRAPPER]: Build + ldap-wrapper-ce. + * ldap-wrapper-ce.c: New. + + * dirmngr_ldap.c (opt): Remove global variable ... + (my_opt_t): ... and declare a type instead. + (main): Define a MY_OPT variable and change all references to OPT + to this. + (set_timeout, print_ldap_entries, fetch_ldap, process_url): Pass + MYOPT arg. + +2010-07-24 Werner Koch <wk@g10code.com> + + * dirmngr_ldap.c (main): Init common subsystems. Call + es_set_binary. + +2010-07-19 Werner Koch <wk@g10code.com> + + * dirmngr.c: Include ldap-wrapper.h. + (launch_reaper_thread): Move code to ... + * ldap-wrapper.c (ldap_wrapper_launch_thread): .. here. Change + callers. + (ldap_wrapper_thread): Rename to ... + (wrapper_thread): this and make local. + + * ldap.c (destroy_wrapper, print_log_line) + (read_log_data, ldap_wrapper_thread) + (ldap_wrapper_wait_connections, ldap_wrapper_release_context) + (ldap_wrapper_connection_cleanup, reader_callback, ldap_wrapper): + Factor code out to ... + * ldap-wrapper.c: new. + (ldap_wrapper): Make public. + (read_buffer): Copy from ldap.c. + * ldap-wrapper.h: New. + * Makefile.am (dirmngr_SOURCES): Add new files. + +2010-07-16 Werner Koch <wk@g10code.com> + + * http.c, http.h: Remove. + + * dirmngr-err.h: New. + * dirmngr.h: Include dirmngr-err.h instead of gpg-error.h + + * cdblib.c: Replace assignments to ERRNO by a call to + gpg_err_set_errno. Include dirmngr-err.h. + (cdb_free) [__MINGW32CE__]: Do not use get_osfhandle. + + * dirmngr.c [!HAVE_SIGNAL_H]: Don't include signal.h. + (USE_W32_SERVICE): New. Use this to control the use of the W32 + service system. + +2010-07-06 Werner Koch <wk@g10code.com> + + * dirmngr.c (main): Print note on directory name changes. + + Replace almost all uses of stdio by estream. + + * b64dec.c, b64enc.c: Remove. They are duplicated in ../common/. + +2010-06-28 Werner Koch <wk@g10code.com> + + * dirmngr_ldap.c (my_i18n_init): Remove. + (main): Call i18n_init instead of above function. + + * dirmngr-client.c (my_i18n_init): Remove. + (main): Call i18n_init instead of above function. + + * Makefile.am (dirmngr_LDADD): Add ../gl/libgnu. + (dirmngr_ldap_LDADD, dirmngr_client_LDADD): Ditto. + +2010-06-09 Werner Koch <wk@g10code.com> + + * i18n.h: Remove. + + * Makefile.am (no-libgcrypt.c): New rule. + + * exechelp.h: Remove. + * exechelp.c: Remove. + (dirmngr_release_process): Change callers to use the gnupg func. + (dirmngr_wait_process): Likewise. + (dirmngr_kill_process): Likewise. This actually implements it for + W32. + * ldap.c (ldap_wrapper): s/get_dirmngr_ldap_path/gnupg_module_name/. + (ldap_wrapper_thread): Use gnupg_wait_process and adjust for + changed semantics. + (ldap_wrapper): Replace xcalloc by xtrycalloc. Replace spawn + mechanism. + + * server.c (start_command_handler): Remove assuan_set_log_stream. + + * validate.c: Remove gcrypt.h and ksba.h. + + * ldapserver.c: s/util.h/dirmngr.h/. + + * dirmngr.c (sleep) [W32]: Remove macro. + (main): s/sleep/gnupg_sleep/. + (pid_suffix_callback): Change arg type. + (my_gcry_logger): Remove. + (fixed_gcry_pth_init): New. + (main): Use it. + (FD2INT): Remove. + +2010-06-08 Werner Koch <wk@g10code.com> + + * misc.h (copy_time): Remove and replace by gnupg_copy_time which + allows to set a null date. + * misc.c (dump_isotime, get_time, get_isotime, set_time) + (check_isotime, add_isotime): Remove and replace all calls by the + versions from common/gettime.c. + + * crlcache.c, misc.c, misc.h: s/dirmngr_isotime_t/gnupg_isotime_t/. + * server.c, ldap.c: Reorder include directives. + * crlcache.h, misc.h: Remove all include directives. + + * certcache.c (cmp_simple_canon_sexp): Remove. + (compare_serialno): Rewrite using cmp_simple_canon_sexp from + common/sexputil.c + + * error.h: Remove. + + * dirmngr.c: Remove transitional option "--ignore-ocsp-servic-url". + (opts): Use ARGPARSE macros. + (i18n_init): Remove. + (main): Use GnuPG init functions. + + * dirmngr.h: Remove duplicated stuff now taken from ../common. + + * get-path.c, util.h: Remove. + + * Makefile.am: Adjust to GnuPG system. + * estream.c, estream.h, estream-printf.c, estream-printf.h: Remove. + +2010-06-07 Werner Koch <wk@g10code.com> + + * OAUTHORS, ONEWS, ChangeLog.1: New. + + * ChangeLog, Makefile.am, b64dec.c, b64enc.c, cdb.h, cdblib.c + * certcache.c, certcache.h, crlcache.c, crlcache.h, crlfetch.c + * crlfetch.h, dirmngr-client.c, dirmngr.c, dirmngr.h + * dirmngr_ldap.c, error.h, estream-printf.c, estream-printf.h + * estream.c, estream.h, exechelp.c, exechelp.h, get-path.c, http.c + * http.h, i18n.h, ldap-url.c, ldap-url.h, ldap.c, ldapserver.c + * ldapserver.h, misc.c, misc.h, ocsp.c, ocsp.h, server.c, util.h + * validate.c, validate.h: Imported from the current SVN of the + dirmngr package (only src/). + +2010-03-13 Werner Koch <wk@g10code.com> + + * dirmngr.c (int_and_ptr_u): New. + (pid_suffix_callback): Trick out compiler. + (start_connection_thread): Ditto. + (handle_connections): Ditto. + +2010-03-09 Werner Koch <wk@g10code.com> + + * dirmngr.c (set_debug): Allow numerical values. + +2009-12-15 Werner Koch <wk@g10code.com> + + * dirmngr.c: Add option --ignore-cert-extension. + (parse_rereadable_options): Implement. + * dirmngr.h (opt): Add IGNORED_CERT_EXTENSIONS. + * validate.c (unknown_criticals): Handle ignored extensions. + +2009-12-08 Marcus Brinkmann <marcus@g10code.de> + + * dirmngr-client.c (start_dirmngr): Convert posix FDs to assuan fds. + +2009-11-25 Marcus Brinkmann <marcus@g10code.de> + + * server.c (start_command_handler): Use assuan_fd_t and + assuan_fdopen on fds. + +2009-11-05 Marcus Brinkmann <marcus@g10code.de> + + * server.c (start_command_handler): Update use of + assuan_init_socket_server. + * dirmngr-client.c (start_dirmngr): Update use of + assuan_pipe_connect and assuan_socket_connect. + +2009-11-04 Werner Koch <wk@g10code.com> + + * server.c (register_commands): Add help arg to + assuan_register_command. Change all command comments to strings. + +2009-11-02 Marcus Brinkmann <marcus@g10code.de> + + * server.c (reset_notify): Take LINE argument, return gpg_error_t. + +2009-10-16 Marcus Brinkmann <marcus@g10code.com> + + * Makefile.am: (dirmngr_LDADD): Link to $(LIBASSUAN_LIBS) instead + of $(LIBASSUAN_PTH_LIBS). + * dirmngr.c: Invoke ASSUAN_SYSTEM_PTH_IMPL. + (main): Call assuan_set_system_hooks and assuan_sock_init. + +2009-09-22 Marcus Brinkmann <marcus@g10code.de> + + * dirmngr.c (main): Update to new Assuan interface. + * server.c (option_handler, cmd_ldapserver, cmd_isvalid) + (cmd_checkcrl, cmd_checkocsp, cmd_lookup, cmd_loadcrl) + (cmd_listcrls, cmd_cachecert, cmd_validate): Return gpg_error_t + instead int. + (register_commands): Likewise for member HANDLER. + (start_command_handler): Allocate context with assuan_new before + starting server. Release on error. + * dirmngr-client.c (main): Update to new Assuan interface. + (start_dirmngr): Allocate context with assuan_new before + connecting to server. Release on error. + +2009-08-12 Werner Koch <wk@g10code.com> + + * dirmngr-client.c (squid_loop_body): Flush stdout. Suggested by + Philip Shin. + +2009-08-07 Werner Koch <wk@g10code.com> + + * crlfetch.c (my_es_read): Add explicit check for EOF. + + * http.c (struct http_context_s): Turn IN_DATA and IS_HTTP_0_9 to + bit fields. + (struct cookie_s): Add CONTENT_LENGTH_VALID and CONTENT_LENGTH. + (parse_response): Parse the Content-Length header. + (cookie_read): Handle content length. + (http_open): Make NEED_HEADER the semi-default. + + * http.h (HTTP_FLAG_IGNORE_CL): New. + +2009-08-04 Werner Koch <wk@g10code.com> + + * ldap.c (ldap_wrapper_thread): Factor some code out to ... + (read_log_data): ... new. Close the log fd on error. + (ldap_wrapper_thread): Delay cleanup until the log fd is closed. + (SAFE_PTH_CLOSE): New. Use it instead of pth_close. + +2009-07-31 Werner Koch <wk@g10code.com> + + * server.c (cmd_loadcrl): Add option --url. + * dirmngr-client.c (do_loadcrl): Make use of --url. + + * crlfetch.c (crl_fetch): Remove HTTP_FLAG_NO_SHUTDOWN. Add + flag HTTP_FLAG_LOG_RESP with active DBG_LOOKUP. + + * http.c: Require estream. Remove P_ES macro. + (write_server): Remove. + (my_read_line): Remove. Replace all callers by es_read_line. + (send_request): Use es_asprintf. Always store the cookie. + (http_wait_response): Remove the need to dup the socket. USe new + shutdown flag. + * http.h (HTTP_FLAG_NO_SHUTDOWN): Rename to HTTP_FLAG_SHUTDOWN. + + * estream.c, estream.h, estream-printf.c, estream-printf.h: Update + from current libestream. This is provide es_asprintf. + +2009-07-20 Werner Koch <wk@g10code.com> + + * dirmngr.c (pid_suffix_callback): New. + (main): Use log_set_pid_suffix_cb. + (start_connection_thread): Put the fd into the tls. + + * ldap.c (ldap_wrapper_thread): Print ldap worker stati. + (ldap_wrapper_release_context): Print a debug info. + (end_cert_fetch_ldap): Release the reader. Might fix bug#999. + +2009-06-17 Werner Koch <wk@g10code.com> + + * util.h: Remove unused dotlock.h. + +2009-05-26 Werner Koch <wk@g10code.com> + + * ldap.c (ldap_wrapper): Show reader object in diagnostics. + * crlcache.c (crl_cache_reload_crl): Ditto. Change debug messages + to regular diagnostics. + * dirmngr_ldap.c (print_ldap_entries): Add extra diagnostics. + +2009-04-03 Werner Koch <wk@g10code.com> + + * dirmngr.h (struct server_local_s): Move back to ... + * server.c (struct server_local_s): ... here. + (get_ldapservers_from_ctrl): New. + * ldapserver.h (ldapserver_iter_begin): Use it. + +2008-10-29 Marcus Brinkmann <marcus@g10code.de> + + * estream.c (es_getline): Add explicit cast to silence gcc -W + warning. + * crlcache.c (finish_sig_check): Likewise. + + * dirmngr.c (opts): Add missing initializer to silence gcc + -W warning. + * server.c (register_commands): Likewise. + * dirmngr-client.c (opts): Likewise. + * dirmngr_ldap.c (opts): Likewise. + + * dirmngr-client.c (status_cb, inq_cert, data_cb): Change return + type to gpg_error_t to silence gcc warning. + +2008-10-21 Werner Koch <wk@g10code.com> + + * certcache.c (load_certs_from_dir): Accept ".der" files. + + * server.c (get_istrusted_from_client): New. + * validate.c (validate_cert_chain): Add new optional arg + R_TRUST_ANCHOR. Adjust all callers + * crlcache.c (crl_cache_entry_s): Add fields USER_TRUST_REQ + and CHECK_TRUST_ANCHOR. + (release_one_cache_entry): Release CHECK_TRUST_ANCHOR. + (list_one_crl_entry): Print info about the new fields. + (open_dir, write_dir_line_crl): Support the new U-flag. + (crl_parse_insert): Add arg R_TRUST_ANCHOR and set it accordingly. + (crl_cache_insert): Store trust anchor in entry object. + (cache_isvalid): Ask client for trust is needed. + + * crlcache.c (open_dir): Replace xcalloc by xtrycalloc. + (next_line_from_file): Ditt. Add arg to return the gpg error. + Change all callers. + (update_dir): Replace sprintf and malloc by estream_asprintf. + (crl_cache_insert): Ditto. + (crl_cache_isvalid): Replace xmalloc by xtrymalloc. + (get_auth_key_id): Ditto. + (crl_cache_insert): Ditto. + + * crlcache.c (start_sig_check): Remove HAVE_GCRY_MD_DEBUG test. + * validate.c (check_cert_sig): Ditto. Remove workaround for bug + in libgcrypt 1.2. + + * estream.c, estream.h, estream-printf.c, estream-printf.h: Update + from current libestream (svn rev 61). + +2008-09-30 Marcus Brinkmann <marcus@g10code.com> + + * get-path.c (get_dirmngr_ldap_path): Revert last change. + Instead, use dirmngr_libexecdir(). + (find_program_at_standard_place): Don't define for now. + +2008-09-30 Marcus Brinkmann <marcus@g10code.com> + + * get-path.c (dirmngr_cachedir): Make COMP a pointer to const to + silence gcc warning. + (get_dirmngr_ldap_path): Look for dirmngr_ldap in the installation + directory. + +2008-08-06 Marcus Brinkmann <marcus@g10code.com> + + * dirmngr.c (main): Mark the ldapserverlist-file option as + read-only. + +2008-07-31 Werner Koch <wk@g10code.com> + + * crlcache.c (start_sig_check) [!HAVE_GCRY_MD_DEBUG]: Use + gcry_md_start_debug + +2008-06-16 Werner Koch <wk@g10code.com> + + * get-path.c (w32_commondir): New. + (dirmngr_sysconfdir): Use it here. + (dirmngr_datadir): Ditto. + +2008-06-12 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (dirmngr_SOURCES): Add ldapserver.h and ldapserver.c. + * ldapserver.h, ldapserver.c: New files. + * ldap.c: Include "ldapserver.h". + (url_fetch_ldap): Use iterator to get session servers as well. + (attr_fetch_ldap, start_default_fetch_ldap): Likewise. + * dirmngr.c: Include "ldapserver.h". + (free_ldapservers_list): Removed. Change callers to + ldapserver_list_free. + (parse_ldapserver_file): Use ldapserver_parse_one. + * server.c: Include "ldapserver.h". + (cmd_ldapserver): New command. + (register_commands): Add new command LDAPSERVER. + (reset_notify): New function. + (start_command_handler): Register reset notify handler. + Deallocate session server list. + (lookup_cert_by_pattern): Use iterator to get session servers as well. + (struct server_local_s): Move to ... + * dirmngr.h (struct server_local_s): ... here. Add new member + ldapservers. + +2008-06-10 Werner Koch <wk@g10code.com> + + Support PEM encoded CRLs. Fixes bug#927. + + * crlfetch.c (struct reader_cb_context_s): New. + (struct file_reader_map_s): Replace FP by new context. + (register_file_reader, get_file_reader): Adjust accordingly. + (my_es_read): Detect Base64 encoded CRL and decode if needed. + (crl_fetch): Pass new context to the callback. + (crl_close_reader): Cleanup the new context. + * b64dec.c: New. Taken from GnuPG. + * util.h (struct b64state): Add new fields STOP_SEEN and + INVALID_ENCODING. + +2008-05-26 Marcus Brinkmann <marcus@g10code.com> + + * dirmngr.c (main) [HAVE_W32_SYSTEM]: Switch to system + configuration on gpgconf related commands, and make all options + unchangeable. + +2008-03-25 Marcus Brinkmann <marcus@g10code.de> + + * dirmngr_ldap.c (print_ldap_entries): Add code alternative for + W32 console stdout (unused at this point). + +2008-03-21 Marcus Brinkmann <marcus@g10code.de> + + * estream.c (ESTREAM_MUTEX_DESTROY): New macro. + (es_create, es_destroy): Use it. + +2008-02-21 Werner Koch <wk@g10code.com> + + * validate.c (check_cert_sig) [HAVE_GCRY_MD_DEBUG]: Use new debug + function if available. + + * crlcache.c (abort_sig_check): Mark unused arg. + + * exechelp.c (dirmngr_release_process) [!W32]: Mark unsed arg. + + * validate.c (is_root_cert): New. Taken from GnuPG. + (validate_cert_chain): Use it in place of the simple DN compare. + +2008-02-15 Marcus Brinkmann <marcus@g10code.de> + + * dirmngr.c (main): Reinitialize assuan log stream if necessary. + + * crlcache.c (update_dir) [HAVE_W32_SYSTEM]: Remove destination + file before rename. + (crl_cache_insert) [HAVE_W32_SYSTEM]: Remove destination file + before rename. + +2008-02-14 Marcus Brinkmann <marcus@g10code.de> + + * validate.c (check_cert_policy): Use ksba_free instead of xfree. + (validate_cert_chain): Likewise. Free SUBJECT on error. + (cert_usage_p): Likewise. + + * crlcache.c (finish_sig_check): Undo last change. + (finish_sig_check): Close md. + (abort_sig_check): New function. + (crl_parse_insert): Use abort_sig_check to clean up. + + * crlcache.c (crl_cache_insert): Clean up CDB on error. + +2008-02-13 Marcus Brinkmann <marcus@g10code.de> + + * crlcache.c (finish_sig_check): Call gcry_md_stop_debug. + * exechelp.h (dirmngr_release_process): New prototype. + * exechelp.c (dirmngr_release_process): New function. + * ldap.c (ldap_wrapper_thread): Release pid. + (destroy_wrapper): Likewise. + + * dirmngr.c (launch_reaper_thread): Destroy tattr. + (handle_connections): Likewise. + +2008-02-12 Marcus Brinkmann <marcus@g10code.de> + + * ldap.c (pth_close) [! HAVE_W32_SYSTEM]: New macro. + (struct wrapper_context_s): New member log_ev. + (destroy_wrapper): Check FDs for != -1 rather than != 0. Use + pth_close instead of close. Free CTX->log_ev. + (ldap_wrapper_thread): Rewritten to use pth_wait instead of + select. Also use pth_read instead of read and pth_close instead + of close. + (ldap_wrapper): Initialize CTX->log_ev. + (reader_callback): Use pth_close instead of close. + * exechelp.c (create_inheritable_pipe) [HAVE_W32_SYSTEM]: Removed. + (dirmngr_spawn_process) [HAVE_W32_SYSTEM]: Use pth_pipe instead. + * dirmngr_ldap.c [HAVE_W32_SYSTEM]: Include <fcntl.h>. + (main) [HAVE_W32_SYSTEM]: Set mode of stdout to binary. + +2008-02-01 Werner Koch <wk@g10code.com> + + * ldap.c: Remove all ldap headers as they are unused. + + * dirmngr_ldap.c (LDAP_DEPRECATED): New, to have OpenLDAP use the + old standard API. + +2008-01-10 Werner Koch <wk@g10code.com> + + * dirmngr-client.c: New option --local. + (do_lookup): Use it. + + * server.c (lookup_cert_by_pattern): Implement local lookup. + (return_one_cert): New. + * certcache.c (hexsn_to_sexp): New. + (classify_pattern, get_certs_bypattern): New. + + * misc.c (unhexify): Allow passing NULL for RESULT. + (cert_log_subject): Do not call ksba_free on an unused variable. + +2008-01-02 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (dirmngr_LDADD, dirmngr_ldap_LDADD) + (dirmngr_client_LDADD): Add $(LIBICONV). Reported by Michael + Nottebrock. + +2007-12-11 Werner Koch <wk@g10code.com> + + * server.c (option_handler): New option audit-events. + * dirmngr.h (struct server_control_s): Add member AUDIT_EVENTS. + +2007-11-26 Marcus Brinkmann <marcus@g10code.de> + + * get-path.c (dirmngr_cachedir): Create intermediate directories. + (default_socket_name): Use CSIDL_WINDOWS. + +2007-11-21 Werner Koch <wk@g10code.com> + + * server.c (lookup_cert_by_pattern): Add args SINGLE and CACHE_ONLY. + (cmd_lookup): Add options --single and --cache-only. + +2007-11-16 Werner Koch <wk@g10code.com> + + * certcache.c (load_certs_from_dir): Also log the subject DN. + * misc.c (cert_log_subject): New. + +2007-11-14 Werner Koch <wk@g10code.com> + + * dirmngr-client.c: Replace --lookup-url by --url. + (main): Remove extra code for --lookup-url. + (do_lookup): Remove LOOKUP_URL arg and use the + global option OPT.URL. + + * server.c (has_leading_option): New. + (cmd_lookup): Use it. + + * crlfetch.c (fetch_cert_by_url): Use GPG_ERR_INV_CERT_OBJ. + (fetch_cert_by_url): Use gpg_error_from_syserror. + +2007-11-14 Moritz <moritz@gnu.org> (wk) + + * dirmngr-client.c: New command: --lookup-url <URL>. + (do_lookup): New parameter: lookup_url. If TRUE, include "--url" + switch in LOOKUP transaction. + (enum): New entry: oLookupUrl. + (opts): Likewise. + (main): Handle oLookupUrl. New variable: cmd_lookup_url, set + during option parsing, pass to do_lookup() and substitute some + occurences of "cmd_lookup" with "cmd_lookup OR cmd_lookup_url". + * crlfetch.c (fetch_cert_by_url): New function, uses + url_fetch_ldap() to create a reader object and libksba functions + to read a single cert from that reader. + * server.c (lookup_cert_by_url, lookup_cert_by_pattern): New + functions. + (cmd_lookup): Moved almost complete code ... + (lookup_cert_by_pattern): ... here. + (cmd_lookup): Support new optional argument: --url. Depending on + the presence of that switch, call lookup_cert_by_url() or + lookup_cert_by_pattern(). + (lookup_cert_by_url): Heavily stripped down version of + lookup_cert_by_pattern(), using fetch_cert_by_url. + +2007-10-24 Marcus Brinkmann <marcus@g10code.de> + + * exechelp.c (dirmngr_spawn_process): Fix child handles. + +2007-10-05 Marcus Brinkmann <marcus@g10code.de> + + * dirmngr.h: Include assuan.h. + (start_command_handler): Change type of FD to assuan_fd_t. + * dirmngr.c: Do not include w32-afunix.h. + (socket_nonce): New global variable. + (create_server_socket): Use assuan socket wrappers. Remove W32 + specific stuff. Save the server nonce. + (check_nonce): New function. + (start_connection_thread): Call it. + (handle_connections): Change args to assuan_fd_t. + * server.c (start_command_handler): Change type of FD to assuan_fd_t. + +2007-09-12 Marcus Brinkmann <marcus@g10code.de> + + * dirmngr.c (main): Percent escape pathnames in --gpgconf-list output. + +2007-08-27 Moritz Schulte <moritz@g10code.com> + + * src/Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SOCKETDIR based on + $(localstatedir). + * src/get-path.c (default_socket_name): Use DIRMNGR_SOCKETDIR + instead of hard-coded "/var/run/dirmngr". + +2007-08-16 Werner Koch <wk@g10code.com> + + * get-path.c (get_dirmngr_ldap_path): Make PATHNAME const. + + * dirmngr.c (my_ksba_hash_buffer): Mark unused arg. + (dirmngr_init_default_ctrl): Ditto. + (my_gcry_logger): Ditto. + * dirmngr-client.c (status_cb): Ditto. + * dirmngr_ldap.c (catch_alarm): Ditto. + * estream-printf.c (pr_bytes_so_far): Ditto. + * estream.c (es_func_fd_create): Ditto. + (es_func_fp_create): Ditto. + (es_write_hexstring): Ditto. + * server.c (cmd_listcrls): Ditto. + (cmd_cachecert): Ditto. + * crlcache.c (cache_isvalid): Ditto. + * ocsp.c (do_ocsp_request): Ditto. + * ldap.c (ldap_wrapper_thread): Ditto. + * http.c (http_register_tls_callback): Ditto. + (connect_server): Ditto. + (write_server) [!HTTP_USE_ESTREAM]: Don't build. + +2007-08-14 Werner Koch <wk@g10code.com> + + * get-path.c (dirmngr_cachedir) [W32]: Use CSIDL_LOCAL_APPDATA. + +2007-08-13 Werner Koch <wk@g10code.com> + + * dirmngr.c (handle_connections): Use a timeout in the accept + function. Block signals while creating a new thread. + (shutdown_pending): Needs to be volatile as also accessed bt the + service function. + (w32_service_control): Do not use the regular log fucntions here. + (handle_tick): New. + (main): With system_service in effect use aDaemon as default + command. + (main) [W32]: Only temporary redefine main for the sake of Emacs's + "C-x 4 a". + + * dirmngr-client.c (main) [W32]: Initialize sockets. + (start_dirmngr): Use default_socket_name instead of a constant. + * Makefile.am (dirmngr_client_SOURCES): Add get-path.c + +2007-08-09 Werner Koch <wk@g10code.com> + + * dirmngr.c (parse_ocsp_signer): New. + (parse_rereadable_options): Set opt.ocsp_signer to this. + * dirmngr.h (fingerprint_list_t): New. + * ocsp.c (ocsp_isvalid, check_signature, validate_responder_cert): + Allow for several default ocscp signers. + (ocsp_isvalid): Return GPG_ERR_NO_DATA for an unknwon status. + + * dirmngr-client.c: New option --force-default-responder. + + * server.c (has_option, skip_options): New. + (cmd_checkocsp): Add option --force-default-responder. + (cmd_isvalid): Ditto. Also add option --only-ocsp. + + * ocsp.c (ocsp_isvalid): New arg FORCE_DEFAULT_RESPONDER. + + * dirmngr.c: New option --ocsp-max-period. + * ocsp.c (ocsp_isvalid): Implement it and take care that a missing + next_update is to be ignored. + + * crlfetch.c (my_es_read): New. Use it instead of es_read. + + * estream.h, estream.c, estream-printf.c: Updated from current + libestream SVN. + +2007-08-08 Werner Koch <wk@g10code.com> + + * crlcache.c (crl_parse_insert): Hack to allow for a missing + nextUpdate. + + * dirmngr_ldap.c (print_ldap_entries): Strip the extension from + the want_attr. + + * exechelp.c (dirmngr_wait_process): Reworked for clear error + semantics. + * ldap.c (ldap_wrapper_thread): Adjust for new + dirmngr_wait_process semantics. + +2007-08-07 Werner Koch <wk@g10code.com> + + * get-path.c (default_socket_name) [!W32]: Fixed syntax error. + + * ldap.c (X509CACERT, make_url, fetch_next_cert_ldap): Support + x509caCert as used by the Bundesnetzagentur. + (ldap_wrapper): Do not pass the prgtram name as the first + argument. dirmngr_spawn_process takes care of that. + +2007-08-04 Marcus Brinkmann <marcus@g10code.de> + + * dirmngr.h (opt): Add member system_service. + * dirmngr.c (opts) [HAVE_W32_SYSTEM]: New entry for option + --service. + (DEFAULT_SOCKET_NAME): Removed. + (service_handle, service_status, + w32_service_control) [HAVE_W32_SYSTEM]: New symbols. + (main) [HAVE_W32_SYSTEM]: New entry point for --service. Rename + old function to ... + (real_main) [HAVE_W32_SYSTEM]: ... this. Use default_socket_name + instead of DEFAULT_SOCKET_NAME, and similar for other paths. + Allow colons in Windows socket path name, and implement --service + option. + * util.h (dirmngr_sysconfdir, dirmngr_libexecdir, dirmngr_datadir, + dirmngr_cachedir, default_socket_name): New prototypes. + * get-path.c (dirmngr_sysconfdir, dirmngr_libexecdir) + (dirmngr_datadir, dirmngr_cachedir, default_socket_name): New + functions. + (DIRSEP_C, DIRSEP_S): New macros. + +2007-08-03 Marcus Brinkmann <marcus@g10code.de> + + * get-path.c: Really add the file this time. + +2007-07-31 Marcus Brinkmann <marcus@g10code.de> + + * crlfetch.c: Include "estream.h". + (crl_fetch): Use es_read callback instead a file handle. + (crl_close_reader): Use es_fclose instead of fclose. + (struct file_reader_map_s): Change type of FP to estream_t. + (register_file_reader, crl_fetch, crl_close_reader): Likewise. + * ocsp.c: Include "estream.h". + (read_response): Change type of FP to estream_t. + (read_response, do_ocsp_request): Use es_* variants of I/O + functions. + + * http.c: Include <pth.h>. + (http_wait_response) [HAVE_W32_SYSTEM]: Use DuplicateHandle. + (cookie_read): Use pth_read instead read. + (cookie_write): Use pth_write instead write. + +2007-07-30 Marcus Brinkmann <marcus@g10code.de> + + * ldap-url.c (ldap_str2charray): Fix buglet in ldap_utf8_strchr + invocation. + +2007-07-27 Marcus Brinkmann <marcus@g10code.de> + + * estream.h, estream.c: Update from recent GnuPG. + + * get-path.c: New file. + * Makefile.am (dirmngr_SOURCES): Add get-path.c. + * util.h (default_homedir, get_dirmngr_ldap_path): New prototypes. + * dirmngr.c (main): Use default_homedir(). + * ldap-url.h: Remove japanese white space (sorry!). + +2007-07-26 Marcus Brinkmann <marcus@g10code.de> + + * ldap.c (pth_yield): Remove macro. + + * ldap.c (pth_yield) [HAVE_W32_SYSTEM]: Define to Sleep(0). + + * dirmngr_ldap.c [HAVE_W32_SYSTEM]: Do not include <ldap.h>, but + <winsock2.h>, <winldap.h> and "ldap-url.h". + * ldap.c [HAVE_W32_SYSTEM]: Do not include <ldap.h>, but + <winsock2.h> and <winldap.h>. + + * ldap-url.c: Do not include <ldap.h>, but <winsock2.h>, + <winldap.h> and "ldap-url.h". + (LDAP_P): New macro. + * ldap-url.h: New file. + * Makefile.am (ldap_url): Add ldap-url.h. + + * Makefile.am (ldap_url): New variable. + (dirmngr_ldap_SOURCES): Add $(ldap_url). + (dirmngr_ldap_LDADD): Add $(LIBOBJS). + * ldap-url.c: New file, excerpted from OpenLDAP. + * dirmngr.c (main) [HAVE_W32_SYSTEM]: Avoid the daemonization. + * dirmngr_ldap.c: Include "util.h". + (main) [HAVE_W32_SYSTEM]: Don't set up alarm. + (set_timeout) [HAVE_W32_SYSTEM]: Likewise. + * ldap.c [HAVE_W32_SYSTEM]: Add macros for setenv and pth_yield. + * no-libgcrypt.h (NO_LIBGCRYPT): Define. + * util.h [NO_LIBGCRYPT]: Don't include <gcrypt.h>. + +2007-07-23 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (dirmngr_SOURCES): Add exechelp.h and exechelp.c. + * exechelp.h, exechelp.c: New files. + * ldap.c: Don't include <sys/wait.h> but "exechelp.h". + (destroy_wrapper, ldap_wrapper_thread, + ldap_wrapper_connection_cleanup): Use dirmngr_kill_process instead + of kill. + (ldap_wrapper_thread): Use dirmngr_wait_process instead of + waitpid. + (ldap_wrapper): Use dirmngr_spawn_process. + +2007-07-20 Marcus Brinkmann <marcus@g10code.de> + + * certcache.c (cert_cache_lock): Do not initialize statically. + (init_cache_lock): New function. + (cert_cache_init): Call init_cache_lock. + + * estream.h, estream.c, estream-printf.h, estream-printf.c: New + files. + * Makefile.am (dirmngr_SOURCES): Add estream.c, estream.h, + estream-printf.c, estream-printf.h. + + * http.c: Update to latest version from GnuPG. + + * Makefile.am (cdb_sources) + * cdblib.c: Port to windows (backport from tinycdb 0.76). + + * crlcache.c [HAVE_W32_SYSTEM]: Don't include sys/utsname.h. + [MKDIR_TAKES_ONE_ARG]: Define mkdir as a macro for such systems. + (update_dir, crl_cache_insert) [HAVE_W32_SYSTEM]: Don't get uname. + * server.c (start_command_handler) [HAVE_W32_SYSTEM]: Don't log + peer credentials. + + * dirmngr.c [HAVE_W32_SYSTEM]: Do not include sys/socket.h or + sys/un.h, but ../jnlib/w32-afunix.h. + (sleep) [HAVE_W32_SYSTEM]: New macro. + (main) [HAVE_W32_SYSTEM]: Don't mess with SIGPIPE. Use W32 socket + API. + (handle_signal) [HAVE_W32_SYSTEM]: Deactivate the bunch of the + code. + (handle_connections) [HAVE_W32_SYSTEM]: don't handle signals. + +2006-11-29 Werner Koch <wk@g10code.com> + + * dirmngr.c (my_strusage): Use macro for the bug report address + and the copyright line. + * dirmngr-client.c (my_strusage): Ditto. + * dirmngr_ldap.c (my_strusage): Ditto. + + * Makefile.am: Do not link against LIBICONV. + +2006-11-19 Werner Koch <wk@g10code.com> + + * dirmngr.c: Include i18n.h. + +2006-11-17 Werner Koch <wk@g10code.com> + + * Makefile.am (dirmngr_LDADD): Use LIBASSUAN_PTH_LIBS. + +2006-11-16 Werner Koch <wk@g10code.com> + + * server.c (start_command_handler): Replaced + assuan_init_connected_socket_server by assuan_init_socket_server_ext. + + * crlcache.c (update_dir): Put a diagnostic into DIR.txt. + (open_dir): Detect invalid and duplicate entries. + (update_dir): Fixed search for second field. + +2006-10-23 Werner Koch <wk@g10code.com> + + * dirmngr.c (main): New command --gpgconf-test. + +2006-09-14 Werner Koch <wk@g10code.com> + + * server.c (start_command_handler): In vebose mode print + information about the peer. This may later be used to restrict + certain commands. + +2006-09-12 Werner Koch <wk@g10code.com> + + * server.c (start_command_handler): Print a more informative hello + line. + * dirmngr.c: Moved config_filename into the opt struct. + +2006-09-11 Werner Koch <wk@g10code.com> + + Changed everything to use Assuan with gpg-error codes. + * maperror.c: Removed. + * server.c (map_to_assuan_status): Removed. + * dirmngr.c (main): Set assuan error source. + * dirmngr-client.c (main): Ditto. + +2006-09-04 Werner Koch <wk@g10code.com> + + * crlfetch.c (crl_fetch): Implement HTTP redirection. + * ocsp.c (do_ocsp_request): Ditto. + + New HTTP code version taken from gnupg svn release 4236. + * http.c (http_get_header): New. + (capitalize_header_name, store_header): New. + (parse_response): Store headers away. + (send_request): Return GPG_ERR_NOT_FOUND if connect_server failed. + * http.h: New flag HTTP_FLAG_NEED_HEADER. + +2006-09-01 Werner Koch <wk@g10code.com> + + * crlfetch.c (register_file_reader, get_file_reader): New. + (crl_fetch): Register the file pointer for HTTP. + (crl_close_reader): And release it. + + * http.c, http.h: Updated from GnuPG SVN trunk. Changed all users + to adopt the new API. + * dirmngr.h: Moved inclusion of jnlib header to ... + * util.h: .. here. This is required becuase http.c includes only + a file util.h but makes use of log_foo. Include gcrypt.h so that + gcry_malloc et al are declared. + +2006-08-31 Werner Koch <wk@g10code.com> + + * ocsp.c (check_signature): Make use of the responder id. + +2006-08-30 Werner Koch <wk@g10code.com> + + * validate.c (check_cert_sig): Workaround for rimemd160. + (allowed_ca): Always allow trusted CAs. + + * dirmngr.h (cert_ref_t): New. + (struct server_control_s): Add field OCSP_CERTS. + * server.c (start_command_handler): Release new field + * ocsp.c (release_ctrl_ocsp_certs): New. + (check_signature): Store certificates in OCSP_CERTS. + + * certcache.c (find_issuing_cert): Reset error if cert was found + by subject. + (put_cert): Add new arg FPR_BUFFER. Changed callers. + (cache_cert_silent): New. + + * dirmngr.c (parse_rereadable_options): New options + --ocsp-max-clock-skew and --ocsp-current-period. + * ocsp.c (ocsp_isvalid): Use them here. + + * ocsp.c (validate_responder_cert): New optional arg signer_cert. + (check_signature_core): Ditto. + (check_signature): Use the default signer certificate here. + +2006-06-27 Werner Koch <wk@g10code.com> + + * dirmngr-client.c (inq_cert): Take care of SENDCERT_SKI. + +2006-06-26 Werner Koch <wk@g10code.com> + + * crlcache.c (lock_db_file): Count open files when needed. + (find_entry): Fixed deleted case. + +2006-06-23 Werner Koch <wk@g10code.com> + + * misc.c (cert_log_name): New. + + * certcache.c (load_certs_from_dir): Also print certificate name. + (find_cert_bysn): Release ISSDN. + + * validate.h: New VALIDATE_MODE_CERT. + * server.c (cmd_validate): Use it here so that no policy checks + are done. Try to validated a cached copy of the target. + + * validate.c (validate_cert_chain): Implement a validation cache. + (check_revocations): Print more diagnostics. Actually use the + loop variable and not the head of the list. + (validate_cert_chain): Do not check revocations of CRL issuer + certificates in plain CRL check mode. + * ocsp.c (ocsp_isvalid): Make sure it is reset for a status of + revoked. + +2006-06-22 Werner Koch <wk@g10code.com> + + * validate.c (cert_use_crl_p): New. + (cert_usage_p): Add a mode 6 for CRL signing. + (validate_cert_chain): Check that the certificate may be used for + CRL signing. Print a note when not running as system daemon. + (validate_cert_chain): Reduce the maximum depth from 50 to 10. + + * certcache.c (find_cert_bysn): Minor restructuring + (find_cert_bysubject): Ditto. Use get_cert_local when called + without KEYID. + * crlcache.c (get_crlissuer_cert_bysn): Removed. + (get_crlissuer_cert): Removed. + (crl_parse_insert): Use find_cert_bysubject and find_cert_bysn + instead of the removed functions. + +2006-06-19 Werner Koch <wk@g10code.com> + + * certcache.c (compare_serialno): Silly me. Using 0 as true is + that hard; tsss. Fixed call cases except for the only working one + which are both numbers of the same length. + +2006-05-15 Werner Koch <wk@g10code.com> + + * crlfetch.c (crl_fetch): Use no-shutdown flag for HTTP. This + seems to be required for "IBM_HTTP_Server/2.0.47.1 Apache/2.0.47 + (Unix)". + + * http.c (parse_tuple): Set flag to to indicate no value. + (build_rel_path): Take care of it. + + * crlcache.c (crl_cache_reload_crl): Also iterate over all names + within a DP. + +2005-09-28 Marcus Brinkmann <marcus@g10code.de> + + * Makefile.am (dirmngr_LDADD): Add @LIBINTL@ and @LIBICONV@. + (dirmngr_ldap_LDADD): Likewise. + (dirmngr_client_LDADD): Likewise. + +2005-09-12 Werner Koch <wk@g10code.com> + + * dirmngr.c: Fixed description to match the one in gpgconf. + +2005-06-15 Werner Koch <wk@g10code.com> + + * server.c (cmd_lookup): Take care of NO_DATA which might get + returned also by start_cert_fetch(). + +2005-04-20 Werner Koch <wk@g10code.com> + + * ldap.c (ldap_wrapper_wait_connections): Set a shutdown flag. + (ldap_wrapper_thread): Handle shutdown in a special way. + +2005-04-19 Werner Koch <wk@g10code.com> + + * server.c (get_cert_local, get_issuing_cert_local) + (get_cert_local_ski): Bail out if called without a local context. + +2005-04-18 Werner Koch <wk@g10code.com> + + * certcache.c (find_issuing_cert): Fixed last resort method which + should be finding by subject and not by issuer. Try to locate it + also using the keyIdentifier method. Improve error reporting. + (cmp_simple_canon_sexp): New. + (find_cert_bysubject): New. + (find_cert_bysn): Ask back to the caller before trying an extarnl + lookup. + * server.c (get_cert_local_ski): New. + * crlcache.c (crl_parse_insert): Also try to locate issuer + certificate using the keyIdentifier. Improved error reporting. + +2005-04-14 Werner Koch <wk@g10code.com> + + * ldap.c (start_cert_fetch_ldap): Really return ERR. + +2005-03-17 Werner Koch <wk@g10code.com> + + * http.c (parse_response): Changed MAXLEN and LEN to size_t to + match the requirement of read_line. + * http.h (http_context_s): Ditto for BUFFER_SIZE. + +2005-03-15 Werner Koch <wk@g10code.com> + + * ldap.c: Included time.h. Reported by Bernhard Herzog. + +2005-03-09 Werner Koch <wk@g10code.com> + + * dirmngr.c: Add a note to the help listing check the man page for + other options. + +2005-02-01 Werner Koch <wk@g10code.com> + + * crlcache.c (crl_parse_insert): Renamed a few variables and + changed diagnostic strings for clarity. + (get_issuer_cert): Renamed to get_crlissuer_cert. Try to locate + the certificate from the cache using the subject name. Use new + fetch function. + (get_crlissuer_cert_bysn): New. + (crl_parse_insert): Use it here. + * crlfetch.c (ca_cert_fetch): Changed interface. + (fetch_next_ksba_cert): New. + * ldap.c (run_ldap_wrapper): Add arg MULTI_MODE. Changed all + callers. + (start_default_fetch_ldap): New + * certcache.c (get_cert_bysubject): New. + (clean_cache_slot, put_cert): Store the subject DN if available. + (MAX_EXTRA_CACHED_CERTS): Increase limit of cachable certificates + to 1000. + (find_cert_bysn): Loop until a certificate with a matching S/N has + been found. + + * dirmngr.c (main): Add honor-http-proxy to the gpgconf list. + +2005-01-31 Werner Koch <wk@g10code.com> + + * ldap.c: Started to work on support for userSMIMECertificates. + + * dirmngr.c (main): Make sure to always pass a server control + structure to the caching functions. Reported by Neil Dunbar. + +2005-01-05 Werner Koch <wk@g10code.com> + + * dirmngr-client.c (read_pem_certificate): Skip trailing percent + escaped linefeeds. + +2005-01-03 Werner Koch <wk@g10code.com> + + * dirmngr-client.c (read_pem_certificate): New. + (read_certificate): Divert to it depending on pem option. + (squid_loop_body): New. + (main): New options --pem and --squid-mode. + +2004-12-17 Werner Koch <wk@g10code.com> + + * dirmngr.c (launch_ripper_thread): Renamed to launch_reaper_thread. + (shutdown_reaper): New. Use it for --server and --daemon. + * ldap.c (ldap_wrapper_wait_connections): New. + +2004-12-17 Werner Koch <wk@g10code.com> + + * Makefile.am (dirmngr_ldap_LDADD): Adjusted for new LDAP checks. + +2004-12-16 Werner Koch <wk@g10code.com> + + * ldap.c (ldap_wrapper): Peek on the output to detect empty output + early. + +2004-12-15 Werner Koch <wk@g10code.com> + + * ldap.c (ldap_wrapper): Print a diagnostic after forking for the + ldap wrapper. + * certcache.h (find_cert_bysn): Add this prototype. + * crlcache.c (start_sig_check): Write CRL hash debug file. + (finish_sig_check): Dump the signer's certificate. + (crl_parse_insert): Try to get the issuing cert by authKeyId. + Moved certificate retrieval after item processing. + +2004-12-13 Werner Koch <wk@g10code.com> + + * dirmngr_ldap.c (catch_alarm, set_timeout): new. + (main): Install alarm handler. Add new option --only-search-timeout. + (print_ldap_entries, fetch_ldap): Use set_timeout (); + * dirmngr.h: Make LDAPTIMEOUT a simple unsigned int. Change all + initializations. + * ldap.c (start_cert_fetch_ldap, run_ldap_wrapper): Pass timeout + option to the wrapper. + (INACTIVITY_TIMEOUT): Depend on LDAPTIMEOUT. + (run_ldap_wrapper): Add arg IGNORE_TIMEOUT. + (ldap_wrapper_thread): Check for special timeout exit code. + + * dirmngr.c: Workaround a typo in gpgconf for + ignore-ocsp-service-url. + +2004-12-10 Werner Koch <wk@g10code.com> + + * ldap.c (url_fetch_ldap): Use TMP and not a HOST which is always + NULL. + * misc.c (host_and_port_from_url): Fixed bad encoding detection. + +2004-12-03 Werner Koch <wk@g10code.com> + + * crlcache.c (crl_cache_load): Re-implement it. + + * dirmngr-client.c: New command --load-crl + (do_loadcrl): New. + + * dirmngr.c (parse_rereadable_options, main): Make --allow-ocsp, + --ocsp-responder, --ocsp-signer and --max-replies re-readable. + + * ocsp.c (check_signature): try to get the cert from the cache + first. + (ocsp_isvalid): Print the next and this update times on time + conflict. + + * certcache.c (load_certs_from_dir): Print the fingerprint for + trusted certificates. + (get_cert_byhexfpr): New. + * misc.c (get_fingerprint_hexstring_colon): New. + +2004-12-01 Werner Koch <wk@g10code.com> + + * Makefile.am (dirmngr_LDADD): Don't use LDAP_LIBS. + + * validate.c (validate_cert_chain): Fixed test; as written in the + comment we want to do this only in daemon mode. For clarity + reworked by using a linked list of certificates and include root + and tragte certificate. + (check_revocations): Likewise. Introduced a recursion sentinel. + +2004-11-30 Werner Koch <wk@g10code.com> + + * crlfetch.c (ca_cert_fetch, crl_fetch_default): Do not use the + binary prefix as this will be handled in the driver. + + * dirmngr_ldap.c: New option --log-with-pid. + (fetch_ldap): Handle LDAP_NO_SUCH_OBJECT. + * ldap.c (run_ldap_wrapper, start_cert_fetch_ldap): Use new log + option. + + +2004-11-25 Werner Koch <wk@g10code.com> + + * Makefile.am (dirmngr_ldap_CFLAGS): Added GPG_ERROR_CFLAGS. + Noted by Bernhard Herzog. + +2004-11-24 Werner Koch <wk@g10code.com> + + * ldap.c (ldap_wrapper): Fixed default name of the ldap wrapper. + + * b64enc.c (b64enc_start, b64enc_finish): Use standard strdup/free + to manage memory. + + * dirmngr.c: New options --ignore-http-dp, --ignore-ldap-dp and + --ignore-ocsp-service-url. + * crlcache.c (crl_cache_reload_crl): Implement them. + * ocsp.c (ocsp_isvalid): Ditto. + +2004-11-23 Werner Koch <wk@g10code.com> + + * ldap.c (ldap_wrapper_thread, reader_callback, ldap_wrapper): + Keep a timestamp and terminate the wrapper after some time of + inactivity. + + * dirmngr-client.c (do_lookup): New. + (main): New option --lookup. + (data_cb): New. + * b64enc.c: New. Taken from GnuPG 1.9. + * no-libgcrypt.c (gcry_strdup): Added. + + * ocsp.c (ocsp_isvalid): New arg CERT and lookup the issuer + certificate using the standard methods. + + * server.c (cmd_lookup): Truncation is now also an indication for + error. + (cmd_checkocsp): Implemented. + + * dirmngr_ldap.c (fetch_ldap): Write an error marker for a + truncated search. + * ldap.c (add_server_to_servers): Reactivated. + (url_fetch_ldap): Call it here and try all configured servers in + case of a a failed lookup. + (fetch_next_cert_ldap): Detect the truncation error flag. + * misc.c (host_and_port_from_url, remove_percent_escapes): New. + +2004-11-22 Werner Koch <wk@g10code.com> + + * dirmngr_ldap.c (main): New option --proxy. + * ocsp.c (do_ocsp_request): Take care of opt.disable_http. + * crlfetch.c (crl_fetch): Honor the --honor-http-proxy variable. + (crl_fetch): Take care of opt.disable_http and disable_ldap. + (crl_fetch_default, ca_cert_fetch, start_cert_fetch): + * ldap.c (run_ldap_wrapper): New arg PROXY. + (url_fetch_ldap, attr_fetch_ldap, start_cert_fetch_ldap): Pass it. + + * http.c (http_open_document): Add arg PROXY. + (http_open): Ditto. + (send_request): Ditto and implement it as an override. + + * ocsp.c (validate_responder_cert): Use validate_cert_chain. + + * Makefile.am (AM_CPPFLAGS): Add macros for a few system + directories. + * dirmngr.h (opt): New members homedir_data, homedir_cache, + ldap_wrapper_program, system_daemon, honor_http_proxy, http_proxy, + ldap_proxy, only_ldap_proxy, disable_ldap, disable_http. + * dirmngr.c (main): Initialize new opt members HOMEDIR_DATA and + HOMEDIR_CACHE. + (parse_rereadable_options): New options --ldap-wrapper-program, + --http-wrapper-program, --disable-ldap, --disable-http, + --honor-http-proxy, --http-proxy, --ldap-proxy, --only-ldap-proxy. + (reread_configuration): New. + + * ldap.c (ldap_wrapper): Use the correct name for the wrapper. + + * crlcache.c (DBDIR_D): Make it depend on opt.SYSTEM_DAEMON. + (cleanup_cache_dir, open_dir, update_dir, make_db_file_name) + (crl_cache_insert, create_directory_if_needed): Use opt.HOMEDIR_CACHE + + * validate.c (check_revocations): New. + * crlcache.c (crl_cache_isvalid): Factored most code out to + (cache_isvalid): .. new. + (crl_cache_cert_isvalid): New. + * server.c (cmd_checkcrl): Cleaned up by using this new function. + (reload_crl): Moved to .. + * crlcache.c (crl_cache_reload_crl): .. here and made global. + + * certcache.c (cert_compute_fpr): Renamed from computer_fpr and + made global. + (find_cert_bysn): Try to lookup missing certs. + (cert_cache_init): Intialize using opt.HOMEDIR_DATA. + + +2004-11-19 Werner Koch <wk@g10code.com> + + * dirmngr-client.c (status_cb): New. Use it in very verbose mode. + + * server.c (start_command_handler): Malloc the control structure + and properly release it. Removed the primary_connection + hack. Cleanup running wrappers. + (dirmngr_status): Return an error code. + (dirmngr_tick): Return an error code and detect a + cancellation. Use wall time and not CPU time. + * validate.c (validate_cert_chain): Add CTRL arg and changed callers. + * crlcache.c (crl_cache_isvalid): + * crlfetch.c (ca_cert_fetch, start_cert_fetch, crl_fetch_default) + (crl_fetch): Ditto. + * ldap.c (ldap_wrapper, run_ldap_wrapper, url_fetch_ldap) + (attr_fetch_ldap, start_cert_fetch_ldap): Ditto. + (ldap_wrapper_release_context): Reset the stored CTRL. + (reader_callback): Periodically call dirmngr_tick. + (ldap_wrapper_release_context): Print an error message for read + errors. + (ldap_wrapper_connection_cleanup): New. + +2004-11-18 Werner Koch <wk@g10code.com> + + * dirmngr.c (main): Do not cd / if not running detached. + + * dirmngr-client.c: New options --cache-cert and --validate. + (do_cache, do_validate): New. + * server.c (cmd_cachecert, cmd_validate): New. + + * crlcache.c (get_issuer_cert): Make use of the certificate cache. + (crl_parse_insert): Validate the issuer certificate. + + * dirmngr.c (handle_signal): Reinitialize the certificate cache on + a HUP. + (struct opts): Add --homedir to enable the already implemented code. + (handle_signal): Print stats on SIGUSR1. + + * certcache.c (clean_cache_slot, cert_cache_init) + (cert_cache_deinit): New. + (acquire_cache_read_lock, acquire_cache_write_lock) + (release_cache_lock): New. Use them where needed. + (put_cert): Renamed from put_loaded_cert. + (cache_cert): New. + (cert_cache_print_stats): New. + (compare_serialno): Fixed. + +2004-11-16 Werner Koch <wk@g10code.com> + + * Makefile.am (AM_CPPFLAGS): Define DIRMNGR_SYSCONFDIR and + DIRMNGR_LIBEXECDIR. + + * misc.c (dump_isotime, dump_string, dump_cert): New. Taken from + gnupg 1.9. + (dump_serial): New. + +2004-11-15 Werner Koch <wk@g10code.com> + + * validate.c: New. Based on gnupg's certchain.c + + * ldap.c (get_cert_ldap): Removed. + (read_buffer): New. + (start_cert_fetch_ldap, fetch_next_cert_ldap) + (end_cert_fetch_ldap): Rewritten to make use of the ldap wrapper. + +2004-11-12 Werner Koch <wk@g10code.com> + + * http.c (insert_escapes): Print the percent sign too. + + * dirmngr-client.c (inq_cert): Ignore "SENDCERT" and + "SENDISSUERCERT". + + * server.c (do_get_cert_local): Limit the length of a returned + 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: These old ChangeLog entries are + found below up to ==END OLDEST CHANGELOG==] + +==BEGIN OLDEST CHANGELOG== + +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 return + 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 + +==END OLDEST CHANGELOG== + + Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011 Free Software Foundation, Inc. + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Local Variables: +buffer-read-only: t +End: diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am new file mode 100644 index 0000000..00d3c42 --- /dev/null +++ b/dirmngr/Makefile.am @@ -0,0 +1,193 @@ +# 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 <https://www.gnu.org/licenses/>. +# +# SPDX-License-Identifier: GPL-3.0+ + +## Process this file with automake to produce Makefile.in + +EXTRA_DIST = OAUTHORS ONEWS ChangeLog-2011 tls-ca.pem \ + dirmngr-w32info.rc dirmngr.w32-manifest.in + + +dist_pkgdata_DATA = sks-keyservers.netCA.pem + +bin_PROGRAMS = dirmngr dirmngr-client + +if USE_LDAPWRAPPER +libexec_PROGRAMS = dirmngr_ldap +endif + +noinst_PROGRAMS = $(module_tests) $(module_net_tests) $(module_maint_tests) +if DISABLE_TESTS +TESTS = +else +TESTS = $(module_tests) $(module_net_tests) +endif + +AM_CPPFLAGS = + +include $(top_srcdir)/am/cmacros.am + +AM_CFLAGS = $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) \ + $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(NTBTLS_CFLAGS) \ + $(LIBGNUTLS_CFLAGS) + + +if HAVE_W32_SYSTEM +ldap_url = ldap-url.h ldap-url.c +else +ldap_url = +endif + +if USE_LDAPWRAPPER +extraldap_src = ldap-wrapper.c +else +extraldap_src = ldap-wrapper-ce.c dirmngr_ldap.c +endif + +noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h + +dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \ + certcache.c certcache.h \ + domaininfo.c \ + workqueue.c \ + loadswdb.c \ + cdb.h cdblib.c misc.c dirmngr-err.h dirmngr-status.h \ + ocsp.c ocsp.h validate.c validate.h \ + dns-stuff.c dns-stuff.h \ + http.c http.h http-common.c http-common.h http-ntbtls.c \ + ks-action.c ks-action.h ks-engine.h \ + ks-engine-hkp.c ks-engine-http.c ks-engine-finger.c ks-engine-kdns.c + +if USE_LIBDNS +dirmngr_SOURCES += dns.c dns.h +endif + +if USE_LDAP +dirmngr_SOURCES += ldapserver.h ldapserver.c ldap.c w32-ldap-help.h \ + ldap-wrapper.h ldap-parse-uri.c ldap-parse-uri.h \ + ks-engine-ldap.c $(ldap_url) $(extraldap_src) +ldaplibs = $(LDAPLIBS) +else +ldaplibs = +endif + +if HAVE_W32_SYSTEM +dirmngr_robjs = $(resource_objs) dirmngr-w32info.o +dirmngr-w32info.o : dirmngr.w32-manifest +else +dirmngr_robjs = +endif + + +dirmngr_LDADD = $(libcommonpth) \ + $(DNSLIBS) $(LIBASSUAN_LIBS) \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(NPTH_LIBS) \ + $(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) $(LIBINTL) $(LIBICONV) $(NETLIBS) \ + $(dirmngr_robj) +if USE_LDAP +dirmngr_LDADD += $(ldaplibs) +endif +if !USE_LDAPWRAPPER +dirmngr_LDADD += $(ldaplibs) +endif +dirmngr_LDFLAGS = $(extra_bin_ldflags) + +if USE_LDAPWRAPPER +dirmngr_ldap_SOURCES = dirmngr_ldap.c $(ldap_url) +dirmngr_ldap_CFLAGS = $(GPG_ERROR_CFLAGS) $(LIBGCRYPT_CFLAGS) +dirmngr_ldap_LDFLAGS = +dirmngr_ldap_LDADD = $(libcommon) \ + $(GPG_ERROR_LIBS) $(LIBGCRYPT_LIBS) $(LDAPLIBS) \ + $(LBER_LIBS) $(LIBINTL) $(LIBICONV) $(NETLIBS) +endif + +dirmngr_client_SOURCES = dirmngr-client.c +dirmngr_client_LDADD = $(libcommon) \ + $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBGCRYPT_LIBS) $(NETLIBS) $(LIBINTL) $(LIBICONV) +dirmngr_client_LDFLAGS = $(extra_bin_ldflags) + + +t_common_src = t-support.h t-support.c +if USE_LIBDNS +t_common_src += dns.c dns.h +endif +t_common_ldadd = $(libcommon) $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) $(NETLIBS) \ + $(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) \ + $(DNSLIBS) $(LIBINTL) $(LIBICONV) + +module_tests = t-http-basic + +if USE_LDAP +module_tests += t-ldap-parse-uri +endif + +# Test which need a network connections are only used in maintainer mode. +if MAINTAINER_MODE +module_net_tests = t-dns-stuff +else +module_net_tests = +endif + +# Tests which are only for manually testing are only build in maintainer-mode. +if MAINTAINER_MODE +module_maint_tests = t-http +else +module_maint_tests = +endif + + +# http tests +# We need to add the KSBA flags in case we are building against GNUTLS. +# In that case NTBTLS flags are empty, but we need ksba anyway. +t_http_SOURCES = $(t_common_src) t-http.c http.c dns-stuff.c http-common.c +t_http_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) $(NTBTLS_CFLAGS) $(LIBGNUTLS_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(KSBA_CFLAGS) +t_http_LDADD = $(t_common_ldadd) \ + $(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS) + +t_http_basic_SOURCES = $(t_common_src) t-http-basic.c http.c \ + dns-stuff.c http-common.c +t_http_basic_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) $(NTBTLS_CFLAGS) $(LIBGNUTLS_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(KSBA_CFLAGS) +t_http_basic_LDADD = $(t_common_ldadd) \ + $(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS) + + +t_ldap_parse_uri_SOURCES = \ + t-ldap-parse-uri.c ldap-parse-uri.c ldap-parse-uri.h \ + http.c http-common.c dns-stuff.c \ + $(ldap_url) $(t_common_src) +t_ldap_parse_uri_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) +t_ldap_parse_uri_LDADD = $(ldaplibs) $(t_common_ldadd) $(DNSLIBS) + +t_dns_stuff_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) +t_dns_stuff_SOURCES = $(t_common_src) t-dns-stuff.c dns-stuff.c +t_dns_stuff_LDADD = $(t_common_ldadd) $(DNSLIBS) + +$(PROGRAMS) : $(libcommon) $(libcommonpth) diff --git a/dirmngr/Makefile.in b/dirmngr/Makefile.in new file mode 100644 index 0000000..5412692 --- /dev/null +++ b/dirmngr/Makefile.in @@ -0,0 +1,1837 @@ +# Makefile.in generated by automake 1.16.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2018 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program 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. + +@SET_MAKE@ + +# 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 <https://www.gnu.org/licenses/>. +# +# SPDX-License-Identifier: GPL-3.0+ + +# cmacros.am - C macro definitions +# Copyright (C) 2004 Free Software Foundation, Inc. +# +# This file is part of GnuPG. +# +# GnuPG is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 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 <https://www.gnu.org/licenses/>. + + + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +bin_PROGRAMS = dirmngr$(EXEEXT) dirmngr-client$(EXEEXT) +@USE_LDAPWRAPPER_TRUE@libexec_PROGRAMS = dirmngr_ldap$(EXEEXT) +noinst_PROGRAMS = $(am__EXEEXT_2) $(am__EXEEXT_3) $(am__EXEEXT_4) +@DISABLE_TESTS_FALSE@TESTS = $(am__EXEEXT_2) $(am__EXEEXT_3) +@HAVE_DOSISH_SYSTEM_FALSE@am__append_1 = -DGNUPG_BINDIR="\"$(bindir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBEXECDIR="\"$(libexecdir)\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LIBDIR="\"$(libdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_DATADIR="\"$(datadir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_SYSCONFDIR="\"$(sysconfdir)/@PACKAGE@\"" \ +@HAVE_DOSISH_SYSTEM_FALSE@ -DGNUPG_LOCALSTATEDIR="\"$(localstatedir)\"" + + +# If a specific protect tool program has been defined, pass its name +# to cc. Note that these macros should not be used directly but via +# the gnupg_module_name function. +@GNUPG_AGENT_PGM_TRUE@am__append_2 = -DGNUPG_DEFAULT_AGENT="\"@GNUPG_AGENT_PGM@\"" +@GNUPG_PINENTRY_PGM_TRUE@am__append_3 = -DGNUPG_DEFAULT_PINENTRY="\"@GNUPG_PINENTRY_PGM@\"" +@GNUPG_SCDAEMON_PGM_TRUE@am__append_4 = -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" +@GNUPG_DIRMNGR_PGM_TRUE@am__append_5 = -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" +@GNUPG_PROTECT_TOOL_PGM_TRUE@am__append_6 = -DGNUPG_DEFAULT_PROTECT_TOOL="\"@GNUPG_PROTECT_TOOL_PGM@\"" +@GNUPG_DIRMNGR_LDAP_PGM_TRUE@am__append_7 = -DGNUPG_DEFAULT_DIRMNGR_LDAP="\"@GNUPG_DIRMNGR_LDAP_PGM@\"" +@USE_LIBDNS_TRUE@am__append_8 = dns.c dns.h +@USE_LDAP_TRUE@am__append_9 = ldapserver.h ldapserver.c ldap.c w32-ldap-help.h \ +@USE_LDAP_TRUE@ ldap-wrapper.h ldap-parse-uri.c ldap-parse-uri.h \ +@USE_LDAP_TRUE@ ks-engine-ldap.c $(ldap_url) $(extraldap_src) + +@USE_LDAP_TRUE@am__append_10 = $(ldaplibs) +@USE_LDAPWRAPPER_FALSE@am__append_11 = $(ldaplibs) +@USE_LIBDNS_TRUE@am__append_12 = dns.c dns.h +@USE_LDAP_TRUE@am__append_13 = t-ldap-parse-uri +subdir = dirmngr +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/autobuild.m4 \ + $(top_srcdir)/m4/codeset.m4 $(top_srcdir)/m4/gettext.m4 \ + $(top_srcdir)/m4/gpg-error.m4 $(top_srcdir)/m4/iconv.m4 \ + $(top_srcdir)/m4/isc-posix.m4 $(top_srcdir)/m4/ksba.m4 \ + $(top_srcdir)/m4/lcmessage.m4 $(top_srcdir)/m4/ldap.m4 \ + $(top_srcdir)/m4/lib-ld.m4 $(top_srcdir)/m4/lib-link.m4 \ + $(top_srcdir)/m4/lib-prefix.m4 $(top_srcdir)/m4/libassuan.m4 \ + $(top_srcdir)/m4/libgcrypt.m4 $(top_srcdir)/m4/nls.m4 \ + $(top_srcdir)/m4/npth.m4 $(top_srcdir)/m4/ntbtls.m4 \ + $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/m4/po.m4 \ + $(top_srcdir)/m4/progtest.m4 $(top_srcdir)/m4/readline.m4 \ + $(top_srcdir)/m4/socklen.m4 $(top_srcdir)/m4/sys_socket_h.m4 \ + $(top_srcdir)/m4/tar-ustar.m4 $(top_srcdir)/acinclude.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(dist_pkgdata_DATA) \ + $(noinst_HEADERS) $(am__DIST_COMMON) +mkinstalldirs = $(SHELL) $(top_srcdir)/build-aux/mkinstalldirs +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = dirmngr.w32-manifest +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)" \ + "$(DESTDIR)$(pkgdatadir)" +@USE_LDAP_TRUE@am__EXEEXT_1 = t-ldap-parse-uri$(EXEEXT) +am__EXEEXT_2 = t-http-basic$(EXEEXT) $(am__EXEEXT_1) +@MAINTAINER_MODE_TRUE@am__EXEEXT_3 = t-dns-stuff$(EXEEXT) +@MAINTAINER_MODE_TRUE@am__EXEEXT_4 = t-http$(EXEEXT) +PROGRAMS = $(bin_PROGRAMS) $(libexec_PROGRAMS) $(noinst_PROGRAMS) +am__dirmngr_SOURCES_DIST = dirmngr.c dirmngr.h server.c crlcache.c \ + crlfetch.c certcache.c certcache.h domaininfo.c workqueue.c \ + loadswdb.c cdb.h cdblib.c misc.c dirmngr-err.h \ + dirmngr-status.h ocsp.c ocsp.h validate.c validate.h \ + dns-stuff.c dns-stuff.h http.c http.h http-common.c \ + http-common.h http-ntbtls.c ks-action.c ks-action.h \ + ks-engine.h ks-engine-hkp.c ks-engine-http.c \ + ks-engine-finger.c ks-engine-kdns.c dns.c dns.h ldapserver.h \ + ldapserver.c ldap.c w32-ldap-help.h ldap-wrapper.h \ + ldap-parse-uri.c ldap-parse-uri.h ks-engine-ldap.c ldap-url.h \ + ldap-url.c ldap-wrapper-ce.c dirmngr_ldap.c ldap-wrapper.c +@USE_LIBDNS_TRUE@am__objects_1 = dns.$(OBJEXT) +@HAVE_W32_SYSTEM_TRUE@am__objects_2 = ldap-url.$(OBJEXT) +@USE_LDAPWRAPPER_FALSE@am__objects_3 = ldap-wrapper-ce.$(OBJEXT) \ +@USE_LDAPWRAPPER_FALSE@ dirmngr_ldap.$(OBJEXT) +@USE_LDAPWRAPPER_TRUE@am__objects_3 = ldap-wrapper.$(OBJEXT) +@USE_LDAP_TRUE@am__objects_4 = ldapserver.$(OBJEXT) ldap.$(OBJEXT) \ +@USE_LDAP_TRUE@ ldap-parse-uri.$(OBJEXT) \ +@USE_LDAP_TRUE@ ks-engine-ldap.$(OBJEXT) $(am__objects_2) \ +@USE_LDAP_TRUE@ $(am__objects_3) +am_dirmngr_OBJECTS = dirmngr.$(OBJEXT) server.$(OBJEXT) \ + crlcache.$(OBJEXT) crlfetch.$(OBJEXT) certcache.$(OBJEXT) \ + domaininfo.$(OBJEXT) workqueue.$(OBJEXT) loadswdb.$(OBJEXT) \ + cdblib.$(OBJEXT) misc.$(OBJEXT) ocsp.$(OBJEXT) \ + validate.$(OBJEXT) dns-stuff.$(OBJEXT) http.$(OBJEXT) \ + http-common.$(OBJEXT) http-ntbtls.$(OBJEXT) \ + ks-action.$(OBJEXT) ks-engine-hkp.$(OBJEXT) \ + ks-engine-http.$(OBJEXT) ks-engine-finger.$(OBJEXT) \ + ks-engine-kdns.$(OBJEXT) $(am__objects_1) $(am__objects_4) +dirmngr_OBJECTS = $(am_dirmngr_OBJECTS) +am__DEPENDENCIES_1 = +@USE_LDAP_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) +@USE_LDAP_TRUE@am__DEPENDENCIES_3 = $(am__DEPENDENCIES_2) +@USE_LDAPWRAPPER_FALSE@am__DEPENDENCIES_4 = $(am__DEPENDENCIES_2) +dirmngr_DEPENDENCIES = $(libcommonpth) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_3) \ + $(am__DEPENDENCIES_4) +dirmngr_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(dirmngr_LDFLAGS) \ + $(LDFLAGS) -o $@ +am_dirmngr_client_OBJECTS = dirmngr-client.$(OBJEXT) +dirmngr_client_OBJECTS = $(am_dirmngr_client_OBJECTS) +dirmngr_client_DEPENDENCIES = $(libcommon) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +dirmngr_client_LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(dirmngr_client_LDFLAGS) $(LDFLAGS) -o $@ +am__dirmngr_ldap_SOURCES_DIST = dirmngr_ldap.c ldap-url.h ldap-url.c +@HAVE_W32_SYSTEM_TRUE@am__objects_5 = dirmngr_ldap-ldap-url.$(OBJEXT) +@USE_LDAPWRAPPER_TRUE@am_dirmngr_ldap_OBJECTS = \ +@USE_LDAPWRAPPER_TRUE@ dirmngr_ldap-dirmngr_ldap.$(OBJEXT) \ +@USE_LDAPWRAPPER_TRUE@ $(am__objects_5) +dirmngr_ldap_OBJECTS = $(am_dirmngr_ldap_OBJECTS) +@USE_LDAPWRAPPER_TRUE@dirmngr_ldap_DEPENDENCIES = $(libcommon) \ +@USE_LDAPWRAPPER_TRUE@ $(am__DEPENDENCIES_1) \ +@USE_LDAPWRAPPER_TRUE@ $(am__DEPENDENCIES_1) \ +@USE_LDAPWRAPPER_TRUE@ $(am__DEPENDENCIES_1) \ +@USE_LDAPWRAPPER_TRUE@ $(am__DEPENDENCIES_1) \ +@USE_LDAPWRAPPER_TRUE@ $(am__DEPENDENCIES_1) \ +@USE_LDAPWRAPPER_TRUE@ $(am__DEPENDENCIES_1) \ +@USE_LDAPWRAPPER_TRUE@ $(am__DEPENDENCIES_1) +dirmngr_ldap_LINK = $(CCLD) $(dirmngr_ldap_CFLAGS) $(CFLAGS) \ + $(dirmngr_ldap_LDFLAGS) $(LDFLAGS) -o $@ +am__t_dns_stuff_SOURCES_DIST = t-support.h t-support.c dns.c dns.h \ + t-dns-stuff.c dns-stuff.c +@USE_LIBDNS_TRUE@am__objects_6 = t_dns_stuff-dns.$(OBJEXT) +am__objects_7 = t_dns_stuff-t-support.$(OBJEXT) $(am__objects_6) +am_t_dns_stuff_OBJECTS = $(am__objects_7) \ + t_dns_stuff-t-dns-stuff.$(OBJEXT) \ + t_dns_stuff-dns-stuff.$(OBJEXT) +t_dns_stuff_OBJECTS = $(am_t_dns_stuff_OBJECTS) +am__DEPENDENCIES_5 = $(libcommon) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +t_dns_stuff_DEPENDENCIES = $(am__DEPENDENCIES_5) $(am__DEPENDENCIES_1) +t_dns_stuff_LINK = $(CCLD) $(t_dns_stuff_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am__t_http_SOURCES_DIST = t-support.h t-support.c dns.c dns.h t-http.c \ + http.c dns-stuff.c http-common.c +@USE_LIBDNS_TRUE@am__objects_8 = t_http-dns.$(OBJEXT) +am__objects_9 = t_http-t-support.$(OBJEXT) $(am__objects_8) +am_t_http_OBJECTS = $(am__objects_9) t_http-t-http.$(OBJEXT) \ + t_http-http.$(OBJEXT) t_http-dns-stuff.$(OBJEXT) \ + t_http-http-common.$(OBJEXT) +t_http_OBJECTS = $(am_t_http_OBJECTS) +t_http_DEPENDENCIES = $(am__DEPENDENCIES_5) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) +t_http_LINK = $(CCLD) $(t_http_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ +am__t_http_basic_SOURCES_DIST = t-support.h t-support.c dns.c dns.h \ + t-http-basic.c http.c dns-stuff.c http-common.c +@USE_LIBDNS_TRUE@am__objects_10 = t_http_basic-dns.$(OBJEXT) +am__objects_11 = t_http_basic-t-support.$(OBJEXT) $(am__objects_10) +am_t_http_basic_OBJECTS = $(am__objects_11) \ + t_http_basic-t-http-basic.$(OBJEXT) \ + t_http_basic-http.$(OBJEXT) t_http_basic-dns-stuff.$(OBJEXT) \ + t_http_basic-http-common.$(OBJEXT) +t_http_basic_OBJECTS = $(am_t_http_basic_OBJECTS) +t_http_basic_DEPENDENCIES = $(am__DEPENDENCIES_5) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ + $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) +t_http_basic_LINK = $(CCLD) $(t_http_basic_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +am__t_ldap_parse_uri_SOURCES_DIST = t-ldap-parse-uri.c \ + ldap-parse-uri.c ldap-parse-uri.h http.c http-common.c \ + dns-stuff.c ldap-url.h ldap-url.c t-support.h t-support.c \ + dns.c dns.h +@HAVE_W32_SYSTEM_TRUE@am__objects_12 = \ +@HAVE_W32_SYSTEM_TRUE@ t_ldap_parse_uri-ldap-url.$(OBJEXT) +@USE_LIBDNS_TRUE@am__objects_13 = t_ldap_parse_uri-dns.$(OBJEXT) +am__objects_14 = t_ldap_parse_uri-t-support.$(OBJEXT) \ + $(am__objects_13) +am_t_ldap_parse_uri_OBJECTS = \ + t_ldap_parse_uri-t-ldap-parse-uri.$(OBJEXT) \ + t_ldap_parse_uri-ldap-parse-uri.$(OBJEXT) \ + t_ldap_parse_uri-http.$(OBJEXT) \ + t_ldap_parse_uri-http-common.$(OBJEXT) \ + t_ldap_parse_uri-dns-stuff.$(OBJEXT) $(am__objects_12) \ + $(am__objects_14) +t_ldap_parse_uri_OBJECTS = $(am_t_ldap_parse_uri_OBJECTS) +t_ldap_parse_uri_DEPENDENCIES = $(am__DEPENDENCIES_2) \ + $(am__DEPENDENCIES_5) $(am__DEPENDENCIES_1) +t_ldap_parse_uri_LINK = $(CCLD) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__maybe_remake_depfiles = depfiles +am__depfiles_remade = ./$(DEPDIR)/cdblib.Po ./$(DEPDIR)/certcache.Po \ + ./$(DEPDIR)/crlcache.Po ./$(DEPDIR)/crlfetch.Po \ + ./$(DEPDIR)/dirmngr-client.Po ./$(DEPDIR)/dirmngr.Po \ + ./$(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Po \ + ./$(DEPDIR)/dirmngr_ldap-ldap-url.Po \ + ./$(DEPDIR)/dirmngr_ldap.Po ./$(DEPDIR)/dns-stuff.Po \ + ./$(DEPDIR)/dns.Po ./$(DEPDIR)/domaininfo.Po \ + ./$(DEPDIR)/http-common.Po ./$(DEPDIR)/http-ntbtls.Po \ + ./$(DEPDIR)/http.Po ./$(DEPDIR)/ks-action.Po \ + ./$(DEPDIR)/ks-engine-finger.Po ./$(DEPDIR)/ks-engine-hkp.Po \ + ./$(DEPDIR)/ks-engine-http.Po ./$(DEPDIR)/ks-engine-kdns.Po \ + ./$(DEPDIR)/ks-engine-ldap.Po ./$(DEPDIR)/ldap-parse-uri.Po \ + ./$(DEPDIR)/ldap-url.Po ./$(DEPDIR)/ldap-wrapper-ce.Po \ + ./$(DEPDIR)/ldap-wrapper.Po ./$(DEPDIR)/ldap.Po \ + ./$(DEPDIR)/ldapserver.Po ./$(DEPDIR)/loadswdb.Po \ + ./$(DEPDIR)/misc.Po ./$(DEPDIR)/ocsp.Po ./$(DEPDIR)/server.Po \ + ./$(DEPDIR)/t_dns_stuff-dns-stuff.Po \ + ./$(DEPDIR)/t_dns_stuff-dns.Po \ + ./$(DEPDIR)/t_dns_stuff-t-dns-stuff.Po \ + ./$(DEPDIR)/t_dns_stuff-t-support.Po \ + ./$(DEPDIR)/t_http-dns-stuff.Po ./$(DEPDIR)/t_http-dns.Po \ + ./$(DEPDIR)/t_http-http-common.Po ./$(DEPDIR)/t_http-http.Po \ + ./$(DEPDIR)/t_http-t-http.Po ./$(DEPDIR)/t_http-t-support.Po \ + ./$(DEPDIR)/t_http_basic-dns-stuff.Po \ + ./$(DEPDIR)/t_http_basic-dns.Po \ + ./$(DEPDIR)/t_http_basic-http-common.Po \ + ./$(DEPDIR)/t_http_basic-http.Po \ + ./$(DEPDIR)/t_http_basic-t-http-basic.Po \ + ./$(DEPDIR)/t_http_basic-t-support.Po \ + ./$(DEPDIR)/t_ldap_parse_uri-dns-stuff.Po \ + ./$(DEPDIR)/t_ldap_parse_uri-dns.Po \ + ./$(DEPDIR)/t_ldap_parse_uri-http-common.Po \ + ./$(DEPDIR)/t_ldap_parse_uri-http.Po \ + ./$(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Po \ + ./$(DEPDIR)/t_ldap_parse_uri-ldap-url.Po \ + ./$(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Po \ + ./$(DEPDIR)/t_ldap_parse_uri-t-support.Po \ + ./$(DEPDIR)/validate.Po ./$(DEPDIR)/workqueue.Po +am__mv = mv -f +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(dirmngr_SOURCES) $(dirmngr_client_SOURCES) \ + $(dirmngr_ldap_SOURCES) $(t_dns_stuff_SOURCES) \ + $(t_http_SOURCES) $(t_http_basic_SOURCES) \ + $(t_ldap_parse_uri_SOURCES) +DIST_SOURCES = $(am__dirmngr_SOURCES_DIST) $(dirmngr_client_SOURCES) \ + $(am__dirmngr_ldap_SOURCES_DIST) \ + $(am__t_dns_stuff_SOURCES_DIST) $(am__t_http_SOURCES_DIST) \ + $(am__t_http_basic_SOURCES_DIST) \ + $(am__t_ldap_parse_uri_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; +am__vpath_adj = case $$p in \ + $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ + *) f=$$p;; \ + esac; +am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`; +am__install_max = 40 +am__nobase_strip_setup = \ + srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'` +am__nobase_strip = \ + for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||" +am__nobase_list = $(am__nobase_strip_setup); \ + for p in $$list; do echo "$$p $$p"; done | \ + sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \ + $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \ + if (++n[$$2] == $(am__install_max)) \ + { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \ + END { for (dir in files) print dir, files[dir] }' +am__base_list = \ + sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ + sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } +DATA = $(dist_pkgdata_DATA) +HEADERS = $(noinst_HEADERS) +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__tty_colors_dummy = \ + mgn= red= grn= lgn= blu= brg= std=; \ + am__color_tests=no +am__tty_colors = { \ + $(am__tty_colors_dummy); \ + if test "X$(AM_COLOR_TESTS)" = Xno; then \ + am__color_tests=no; \ + elif test "X$(AM_COLOR_TESTS)" = Xalways; then \ + am__color_tests=yes; \ + elif test "X$$TERM" != Xdumb && { test -t 1; } 2>/dev/null; then \ + am__color_tests=yes; \ + fi; \ + if test $$am__color_tests = yes; then \ + red='[0;31m'; \ + grn='[0;32m'; \ + lgn='[1;32m'; \ + blu='[1;34m'; \ + mgn='[0;35m'; \ + brg='[1m'; \ + std='[m'; \ + fi; \ +} +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(srcdir)/dirmngr.w32-manifest.in $(top_srcdir)/am/cmacros.am \ + $(top_srcdir)/build-aux/depcomp \ + $(top_srcdir)/build-aux/mkinstalldirs +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +AWK_HEX_NUMBER_OPTION = @AWK_HEX_NUMBER_OPTION@ +BUILD_FILEVERSION = @BUILD_FILEVERSION@ +BUILD_HOSTNAME = @BUILD_HOSTNAME@ +BUILD_INCLUDED_LIBINTL = @BUILD_INCLUDED_LIBINTL@ +BUILD_REVISION = @BUILD_REVISION@ +BUILD_TIMESTAMP = @BUILD_TIMESTAMP@ +BUILD_VERSION = @BUILD_VERSION@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CC_FOR_BUILD = @CC_FOR_BUILD@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DL_LIBS = @DL_LIBS@ +DNSLIBS = @DNSLIBS@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +ENCFS = @ENCFS@ +EXEEXT = @EXEEXT@ +FUSERMOUNT = @FUSERMOUNT@ +GETTEXT_MACRO_VERSION = @GETTEXT_MACRO_VERSION@ +GMSGFMT = @GMSGFMT@ +GMSGFMT_015 = @GMSGFMT_015@ +GNUPG_AGENT_PGM = @GNUPG_AGENT_PGM@ +GNUPG_DIRMNGR_LDAP_PGM = @GNUPG_DIRMNGR_LDAP_PGM@ +GNUPG_DIRMNGR_PGM = @GNUPG_DIRMNGR_PGM@ +GNUPG_PINENTRY_PGM = @GNUPG_PINENTRY_PGM@ +GNUPG_PROTECT_TOOL_PGM = @GNUPG_PROTECT_TOOL_PGM@ +GNUPG_SCDAEMON_PGM = @GNUPG_SCDAEMON_PGM@ +GPGKEYS_LDAP = @GPGKEYS_LDAP@ +GPG_ERROR_CFLAGS = @GPG_ERROR_CFLAGS@ +GPG_ERROR_CONFIG = @GPG_ERROR_CONFIG@ +GPG_ERROR_LIBS = @GPG_ERROR_LIBS@ +GPG_ERROR_MT_CFLAGS = @GPG_ERROR_MT_CFLAGS@ +GPG_ERROR_MT_LIBS = @GPG_ERROR_MT_LIBS@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +INTLLIBS = @INTLLIBS@ +INTL_MACOSX_LIBS = @INTL_MACOSX_LIBS@ +KSBA_CFLAGS = @KSBA_CFLAGS@ +KSBA_CONFIG = @KSBA_CONFIG@ +KSBA_LIBS = @KSBA_LIBS@ +LBER_LIBS = @LBER_LIBS@ +LDAPLIBS = @LDAPLIBS@ +LDAP_CPPFLAGS = @LDAP_CPPFLAGS@ +LDFLAGS = @LDFLAGS@ +LIBASSUAN_CFLAGS = @LIBASSUAN_CFLAGS@ +LIBASSUAN_CONFIG = @LIBASSUAN_CONFIG@ +LIBASSUAN_LIBS = @LIBASSUAN_LIBS@ +LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ +LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ +LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ +LIBGNUTLS_CFLAGS = @LIBGNUTLS_CFLAGS@ +LIBGNUTLS_LIBS = @LIBGNUTLS_LIBS@ +LIBICONV = @LIBICONV@ +LIBINTL = @LIBINTL@ +LIBOBJS = @LIBOBJS@ +LIBREADLINE = @LIBREADLINE@ +LIBS = @LIBS@ +LIBUSB_CPPFLAGS = @LIBUSB_CPPFLAGS@ +LIBUSB_LIBS = @LIBUSB_LIBS@ +LIBUTIL_LIBS = @LIBUTIL_LIBS@ +LN_S = @LN_S@ +LTLIBICONV = @LTLIBICONV@ +LTLIBINTL = @LTLIBINTL@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +MSGFMT = @MSGFMT@ +MSGFMT_015 = @MSGFMT_015@ +MSGMERGE = @MSGMERGE@ +NETLIBS = @NETLIBS@ +NPTH_CFLAGS = @NPTH_CFLAGS@ +NPTH_CONFIG = @NPTH_CONFIG@ +NPTH_LIBS = @NPTH_LIBS@ +NTBTLS_CFLAGS = @NTBTLS_CFLAGS@ +NTBTLS_CONFIG = @NTBTLS_CONFIG@ +NTBTLS_LIBS = @NTBTLS_LIBS@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_GT = @PACKAGE_GT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PERL = @PERL@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +POSUB = @POSUB@ +RANLIB = @RANLIB@ +SENDMAIL = @SENDMAIL@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SHRED = @SHRED@ +SQLITE3_CFLAGS = @SQLITE3_CFLAGS@ +SQLITE3_LIBS = @SQLITE3_LIBS@ +STRIP = @STRIP@ +SYSROOT = @SYSROOT@ +SYS_SOCKET_H = @SYS_SOCKET_H@ +TAR = @TAR@ +USE_C99_CFLAGS = @USE_C99_CFLAGS@ +USE_INCLUDED_LIBINTL = @USE_INCLUDED_LIBINTL@ +USE_NLS = @USE_NLS@ +VERSION = @VERSION@ +W32SOCKLIBS = @W32SOCKLIBS@ +WINDRES = @WINDRES@ +XGETTEXT = @XGETTEXT@ +XGETTEXT_015 = @XGETTEXT_015@ +XGETTEXT_EXTRA_OPTIONS = @XGETTEXT_EXTRA_OPTIONS@ +YAT2M = @YAT2M@ +ZLIBS = @ZLIBS@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_CC = @ac_ct_CC@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = $(datadir)/locale +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +EXTRA_DIST = OAUTHORS ONEWS ChangeLog-2011 tls-ca.pem \ + dirmngr-w32info.rc dirmngr.w32-manifest.in + +dist_pkgdata_DATA = sks-keyservers.netCA.pem + +# NB: AM_CFLAGS may also be used by tools running on the build +# platform to create source files. +AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" $(am__append_1) \ + $(am__append_2) $(am__append_3) $(am__append_4) \ + $(am__append_5) $(am__append_6) $(am__append_7) +@HAVE_W32CE_SYSTEM_FALSE@extra_sys_libs = + +# Under Windows we use LockFileEx. WindowsCE provides this only on +# the WindowsMobile 6 platform and thus we need to use the coredll6 +# import library. We also want to use a stacksize of 256k instead of +# the 2MB which is the default with cegcc. 256k is the largest stack +# we use with pth. +@HAVE_W32CE_SYSTEM_TRUE@extra_sys_libs = -lcoredll6 +@HAVE_W32CE_SYSTEM_FALSE@extra_bin_ldflags = +@HAVE_W32CE_SYSTEM_TRUE@extra_bin_ldflags = -Wl,--stack=0x40000 +resource_objs = + +# Convenience macros +libcommon = ../common/libcommon.a +libcommonpth = ../common/libcommonpth.a +libcommontls = ../common/libcommontls.a +libcommontlsnpth = ../common/libcommontlsnpth.a +AM_CFLAGS = $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) \ + $(GPG_ERROR_CFLAGS) $(NPTH_CFLAGS) $(NTBTLS_CFLAGS) \ + $(LIBGNUTLS_CFLAGS) + +@HAVE_W32_SYSTEM_FALSE@ldap_url = +@HAVE_W32_SYSTEM_TRUE@ldap_url = ldap-url.h ldap-url.c +@USE_LDAPWRAPPER_FALSE@extraldap_src = ldap-wrapper-ce.c dirmngr_ldap.c +@USE_LDAPWRAPPER_TRUE@extraldap_src = ldap-wrapper.c +noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h +dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \ + certcache.c certcache.h domaininfo.c workqueue.c loadswdb.c \ + cdb.h cdblib.c misc.c dirmngr-err.h dirmngr-status.h ocsp.c \ + ocsp.h validate.c validate.h dns-stuff.c dns-stuff.h http.c \ + http.h http-common.c http-common.h http-ntbtls.c ks-action.c \ + ks-action.h ks-engine.h ks-engine-hkp.c ks-engine-http.c \ + ks-engine-finger.c ks-engine-kdns.c $(am__append_8) \ + $(am__append_9) +@USE_LDAP_FALSE@ldaplibs = +@USE_LDAP_TRUE@ldaplibs = $(LDAPLIBS) +@HAVE_W32_SYSTEM_FALSE@dirmngr_robjs = +@HAVE_W32_SYSTEM_TRUE@dirmngr_robjs = $(resource_objs) dirmngr-w32info.o +dirmngr_LDADD = $(libcommonpth) $(DNSLIBS) $(LIBASSUAN_LIBS) \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(NPTH_LIBS) $(NTBTLS_LIBS) \ + $(LIBGNUTLS_LIBS) $(LIBINTL) $(LIBICONV) $(NETLIBS) \ + $(dirmngr_robj) $(am__append_10) $(am__append_11) +dirmngr_LDFLAGS = $(extra_bin_ldflags) +@USE_LDAPWRAPPER_TRUE@dirmngr_ldap_SOURCES = dirmngr_ldap.c $(ldap_url) +@USE_LDAPWRAPPER_TRUE@dirmngr_ldap_CFLAGS = $(GPG_ERROR_CFLAGS) $(LIBGCRYPT_CFLAGS) +@USE_LDAPWRAPPER_TRUE@dirmngr_ldap_LDFLAGS = +@USE_LDAPWRAPPER_TRUE@dirmngr_ldap_LDADD = $(libcommon) \ +@USE_LDAPWRAPPER_TRUE@ $(GPG_ERROR_LIBS) $(LIBGCRYPT_LIBS) $(LDAPLIBS) \ +@USE_LDAPWRAPPER_TRUE@ $(LBER_LIBS) $(LIBINTL) $(LIBICONV) $(NETLIBS) + +dirmngr_client_SOURCES = dirmngr-client.c +dirmngr_client_LDADD = $(libcommon) \ + $(LIBASSUAN_LIBS) $(GPG_ERROR_LIBS) \ + $(LIBGCRYPT_LIBS) $(NETLIBS) $(LIBINTL) $(LIBICONV) + +dirmngr_client_LDFLAGS = $(extra_bin_ldflags) +t_common_src = t-support.h t-support.c $(am__append_12) +t_common_ldadd = $(libcommon) $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \ + $(GPG_ERROR_LIBS) $(NETLIBS) \ + $(NTBTLS_LIBS) $(LIBGNUTLS_LIBS) \ + $(DNSLIBS) $(LIBINTL) $(LIBICONV) + +module_tests = t-http-basic $(am__append_13) +@MAINTAINER_MODE_FALSE@module_net_tests = + +# Test which need a network connections are only used in maintainer mode. +@MAINTAINER_MODE_TRUE@module_net_tests = t-dns-stuff +@MAINTAINER_MODE_FALSE@module_maint_tests = + +# Tests which are only for manually testing are only build in maintainer-mode. +@MAINTAINER_MODE_TRUE@module_maint_tests = t-http + +# http tests +# We need to add the KSBA flags in case we are building against GNUTLS. +# In that case NTBTLS flags are empty, but we need ksba anyway. +t_http_SOURCES = $(t_common_src) t-http.c http.c dns-stuff.c http-common.c +t_http_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) $(NTBTLS_CFLAGS) $(LIBGNUTLS_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(KSBA_CFLAGS) + +t_http_LDADD = $(t_common_ldadd) \ + $(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS) + +t_http_basic_SOURCES = $(t_common_src) t-http-basic.c http.c \ + dns-stuff.c http-common.c + +t_http_basic_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) $(NTBTLS_CFLAGS) $(LIBGNUTLS_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) $(KSBA_CFLAGS) + +t_http_basic_LDADD = $(t_common_ldadd) \ + $(NTBTLS_LIBS) $(KSBA_LIBS) $(LIBGNUTLS_LIBS) $(DNSLIBS) + +t_ldap_parse_uri_SOURCES = \ + t-ldap-parse-uri.c ldap-parse-uri.c ldap-parse-uri.h \ + http.c http-common.c dns-stuff.c \ + $(ldap_url) $(t_common_src) + +t_ldap_parse_uri_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) + +t_ldap_parse_uri_LDADD = $(ldaplibs) $(t_common_ldadd) $(DNSLIBS) +t_dns_stuff_CFLAGS = -DWITHOUT_NPTH=1 $(USE_C99_CFLAGS) \ + $(LIBGCRYPT_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(GPG_ERROR_CFLAGS) + +t_dns_stuff_SOURCES = $(t_common_src) t-dns-stuff.c dns-stuff.c +t_dns_stuff_LDADD = $(t_common_ldadd) $(DNSLIBS) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj .rc +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/am/cmacros.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu dirmngr/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --gnu dirmngr/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + esac; +$(top_srcdir)/am/cmacros.am $(am__empty): + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +dirmngr.w32-manifest: $(top_builddir)/config.status $(srcdir)/dirmngr.w32-manifest.in + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) +install-libexecPROGRAMS: $(libexec_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(libexecdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(libexecdir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(libexecdir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(libexecdir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-libexecPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(libexec_PROGRAMS)'; test -n "$(libexecdir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(libexecdir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(libexecdir)" && rm -f $$files + +clean-libexecPROGRAMS: + -test -z "$(libexec_PROGRAMS)" || rm -f $(libexec_PROGRAMS) + +clean-noinstPROGRAMS: + -test -z "$(noinst_PROGRAMS)" || rm -f $(noinst_PROGRAMS) + +dirmngr$(EXEEXT): $(dirmngr_OBJECTS) $(dirmngr_DEPENDENCIES) $(EXTRA_dirmngr_DEPENDENCIES) + @rm -f dirmngr$(EXEEXT) + $(AM_V_CCLD)$(dirmngr_LINK) $(dirmngr_OBJECTS) $(dirmngr_LDADD) $(LIBS) + +dirmngr-client$(EXEEXT): $(dirmngr_client_OBJECTS) $(dirmngr_client_DEPENDENCIES) $(EXTRA_dirmngr_client_DEPENDENCIES) + @rm -f dirmngr-client$(EXEEXT) + $(AM_V_CCLD)$(dirmngr_client_LINK) $(dirmngr_client_OBJECTS) $(dirmngr_client_LDADD) $(LIBS) + +dirmngr_ldap$(EXEEXT): $(dirmngr_ldap_OBJECTS) $(dirmngr_ldap_DEPENDENCIES) $(EXTRA_dirmngr_ldap_DEPENDENCIES) + @rm -f dirmngr_ldap$(EXEEXT) + $(AM_V_CCLD)$(dirmngr_ldap_LINK) $(dirmngr_ldap_OBJECTS) $(dirmngr_ldap_LDADD) $(LIBS) + +t-dns-stuff$(EXEEXT): $(t_dns_stuff_OBJECTS) $(t_dns_stuff_DEPENDENCIES) $(EXTRA_t_dns_stuff_DEPENDENCIES) + @rm -f t-dns-stuff$(EXEEXT) + $(AM_V_CCLD)$(t_dns_stuff_LINK) $(t_dns_stuff_OBJECTS) $(t_dns_stuff_LDADD) $(LIBS) + +t-http$(EXEEXT): $(t_http_OBJECTS) $(t_http_DEPENDENCIES) $(EXTRA_t_http_DEPENDENCIES) + @rm -f t-http$(EXEEXT) + $(AM_V_CCLD)$(t_http_LINK) $(t_http_OBJECTS) $(t_http_LDADD) $(LIBS) + +t-http-basic$(EXEEXT): $(t_http_basic_OBJECTS) $(t_http_basic_DEPENDENCIES) $(EXTRA_t_http_basic_DEPENDENCIES) + @rm -f t-http-basic$(EXEEXT) + $(AM_V_CCLD)$(t_http_basic_LINK) $(t_http_basic_OBJECTS) $(t_http_basic_LDADD) $(LIBS) + +t-ldap-parse-uri$(EXEEXT): $(t_ldap_parse_uri_OBJECTS) $(t_ldap_parse_uri_DEPENDENCIES) $(EXTRA_t_ldap_parse_uri_DEPENDENCIES) + @rm -f t-ldap-parse-uri$(EXEEXT) + $(AM_V_CCLD)$(t_ldap_parse_uri_LINK) $(t_ldap_parse_uri_OBJECTS) $(t_ldap_parse_uri_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/cdblib.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/certcache.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crlcache.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/crlfetch.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dirmngr-client.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dirmngr.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dirmngr_ldap-ldap-url.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dirmngr_ldap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dns-stuff.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dns.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/domaininfo.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http-common.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http-ntbtls.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ks-action.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ks-engine-finger.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ks-engine-hkp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ks-engine-http.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ks-engine-kdns.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ks-engine-ldap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldap-parse-uri.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldap-url.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldap-wrapper-ce.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldap-wrapper.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldap.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ldapserver.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/loadswdb.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ocsp.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/server.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_dns_stuff-dns-stuff.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_dns_stuff-dns.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_dns_stuff-t-dns-stuff.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_dns_stuff-t-support.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http-dns-stuff.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http-dns.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http-http-common.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http-http.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http-t-http.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http-t-support.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http_basic-dns-stuff.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http_basic-dns.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http_basic-http-common.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http_basic-http.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http_basic-t-http-basic.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_http_basic-t-support.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_ldap_parse_uri-dns-stuff.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_ldap_parse_uri-dns.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_ldap_parse_uri-http-common.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_ldap_parse_uri-http.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_ldap_parse_uri-ldap-url.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/t_ldap_parse_uri-t-support.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/validate.Po@am__quote@ # am--include-marker +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/workqueue.Po@am__quote@ # am--include-marker + +$(am__depfiles_remade): + @$(MKDIR_P) $(@D) + @echo '# dummy' >$@-t && $(am__mv) $@-t $@ + +am--depfiles: $(am__depfiles_remade) + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)$(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +dirmngr_ldap-dirmngr_ldap.o: dirmngr_ldap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dirmngr_ldap_CFLAGS) $(CFLAGS) -MT dirmngr_ldap-dirmngr_ldap.o -MD -MP -MF $(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Tpo -c -o dirmngr_ldap-dirmngr_ldap.o `test -f 'dirmngr_ldap.c' || echo '$(srcdir)/'`dirmngr_ldap.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Tpo $(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dirmngr_ldap.c' object='dirmngr_ldap-dirmngr_ldap.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dirmngr_ldap_CFLAGS) $(CFLAGS) -c -o dirmngr_ldap-dirmngr_ldap.o `test -f 'dirmngr_ldap.c' || echo '$(srcdir)/'`dirmngr_ldap.c + +dirmngr_ldap-dirmngr_ldap.obj: dirmngr_ldap.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dirmngr_ldap_CFLAGS) $(CFLAGS) -MT dirmngr_ldap-dirmngr_ldap.obj -MD -MP -MF $(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Tpo -c -o dirmngr_ldap-dirmngr_ldap.obj `if test -f 'dirmngr_ldap.c'; then $(CYGPATH_W) 'dirmngr_ldap.c'; else $(CYGPATH_W) '$(srcdir)/dirmngr_ldap.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Tpo $(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dirmngr_ldap.c' object='dirmngr_ldap-dirmngr_ldap.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dirmngr_ldap_CFLAGS) $(CFLAGS) -c -o dirmngr_ldap-dirmngr_ldap.obj `if test -f 'dirmngr_ldap.c'; then $(CYGPATH_W) 'dirmngr_ldap.c'; else $(CYGPATH_W) '$(srcdir)/dirmngr_ldap.c'; fi` + +dirmngr_ldap-ldap-url.o: ldap-url.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dirmngr_ldap_CFLAGS) $(CFLAGS) -MT dirmngr_ldap-ldap-url.o -MD -MP -MF $(DEPDIR)/dirmngr_ldap-ldap-url.Tpo -c -o dirmngr_ldap-ldap-url.o `test -f 'ldap-url.c' || echo '$(srcdir)/'`ldap-url.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dirmngr_ldap-ldap-url.Tpo $(DEPDIR)/dirmngr_ldap-ldap-url.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap-url.c' object='dirmngr_ldap-ldap-url.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dirmngr_ldap_CFLAGS) $(CFLAGS) -c -o dirmngr_ldap-ldap-url.o `test -f 'ldap-url.c' || echo '$(srcdir)/'`ldap-url.c + +dirmngr_ldap-ldap-url.obj: ldap-url.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dirmngr_ldap_CFLAGS) $(CFLAGS) -MT dirmngr_ldap-ldap-url.obj -MD -MP -MF $(DEPDIR)/dirmngr_ldap-ldap-url.Tpo -c -o dirmngr_ldap-ldap-url.obj `if test -f 'ldap-url.c'; then $(CYGPATH_W) 'ldap-url.c'; else $(CYGPATH_W) '$(srcdir)/ldap-url.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dirmngr_ldap-ldap-url.Tpo $(DEPDIR)/dirmngr_ldap-ldap-url.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap-url.c' object='dirmngr_ldap-ldap-url.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(dirmngr_ldap_CFLAGS) $(CFLAGS) -c -o dirmngr_ldap-ldap-url.obj `if test -f 'ldap-url.c'; then $(CYGPATH_W) 'ldap-url.c'; else $(CYGPATH_W) '$(srcdir)/ldap-url.c'; fi` + +t_dns_stuff-t-support.o: t-support.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -MT t_dns_stuff-t-support.o -MD -MP -MF $(DEPDIR)/t_dns_stuff-t-support.Tpo -c -o t_dns_stuff-t-support.o `test -f 't-support.c' || echo '$(srcdir)/'`t-support.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_dns_stuff-t-support.Tpo $(DEPDIR)/t_dns_stuff-t-support.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-support.c' object='t_dns_stuff-t-support.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -c -o t_dns_stuff-t-support.o `test -f 't-support.c' || echo '$(srcdir)/'`t-support.c + +t_dns_stuff-t-support.obj: t-support.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -MT t_dns_stuff-t-support.obj -MD -MP -MF $(DEPDIR)/t_dns_stuff-t-support.Tpo -c -o t_dns_stuff-t-support.obj `if test -f 't-support.c'; then $(CYGPATH_W) 't-support.c'; else $(CYGPATH_W) '$(srcdir)/t-support.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_dns_stuff-t-support.Tpo $(DEPDIR)/t_dns_stuff-t-support.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-support.c' object='t_dns_stuff-t-support.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -c -o t_dns_stuff-t-support.obj `if test -f 't-support.c'; then $(CYGPATH_W) 't-support.c'; else $(CYGPATH_W) '$(srcdir)/t-support.c'; fi` + +t_dns_stuff-dns.o: dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -MT t_dns_stuff-dns.o -MD -MP -MF $(DEPDIR)/t_dns_stuff-dns.Tpo -c -o t_dns_stuff-dns.o `test -f 'dns.c' || echo '$(srcdir)/'`dns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_dns_stuff-dns.Tpo $(DEPDIR)/t_dns_stuff-dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns.c' object='t_dns_stuff-dns.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -c -o t_dns_stuff-dns.o `test -f 'dns.c' || echo '$(srcdir)/'`dns.c + +t_dns_stuff-dns.obj: dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -MT t_dns_stuff-dns.obj -MD -MP -MF $(DEPDIR)/t_dns_stuff-dns.Tpo -c -o t_dns_stuff-dns.obj `if test -f 'dns.c'; then $(CYGPATH_W) 'dns.c'; else $(CYGPATH_W) '$(srcdir)/dns.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_dns_stuff-dns.Tpo $(DEPDIR)/t_dns_stuff-dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns.c' object='t_dns_stuff-dns.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -c -o t_dns_stuff-dns.obj `if test -f 'dns.c'; then $(CYGPATH_W) 'dns.c'; else $(CYGPATH_W) '$(srcdir)/dns.c'; fi` + +t_dns_stuff-t-dns-stuff.o: t-dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -MT t_dns_stuff-t-dns-stuff.o -MD -MP -MF $(DEPDIR)/t_dns_stuff-t-dns-stuff.Tpo -c -o t_dns_stuff-t-dns-stuff.o `test -f 't-dns-stuff.c' || echo '$(srcdir)/'`t-dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_dns_stuff-t-dns-stuff.Tpo $(DEPDIR)/t_dns_stuff-t-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-dns-stuff.c' object='t_dns_stuff-t-dns-stuff.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -c -o t_dns_stuff-t-dns-stuff.o `test -f 't-dns-stuff.c' || echo '$(srcdir)/'`t-dns-stuff.c + +t_dns_stuff-t-dns-stuff.obj: t-dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -MT t_dns_stuff-t-dns-stuff.obj -MD -MP -MF $(DEPDIR)/t_dns_stuff-t-dns-stuff.Tpo -c -o t_dns_stuff-t-dns-stuff.obj `if test -f 't-dns-stuff.c'; then $(CYGPATH_W) 't-dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/t-dns-stuff.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_dns_stuff-t-dns-stuff.Tpo $(DEPDIR)/t_dns_stuff-t-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-dns-stuff.c' object='t_dns_stuff-t-dns-stuff.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -c -o t_dns_stuff-t-dns-stuff.obj `if test -f 't-dns-stuff.c'; then $(CYGPATH_W) 't-dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/t-dns-stuff.c'; fi` + +t_dns_stuff-dns-stuff.o: dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -MT t_dns_stuff-dns-stuff.o -MD -MP -MF $(DEPDIR)/t_dns_stuff-dns-stuff.Tpo -c -o t_dns_stuff-dns-stuff.o `test -f 'dns-stuff.c' || echo '$(srcdir)/'`dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_dns_stuff-dns-stuff.Tpo $(DEPDIR)/t_dns_stuff-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns-stuff.c' object='t_dns_stuff-dns-stuff.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -c -o t_dns_stuff-dns-stuff.o `test -f 'dns-stuff.c' || echo '$(srcdir)/'`dns-stuff.c + +t_dns_stuff-dns-stuff.obj: dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -MT t_dns_stuff-dns-stuff.obj -MD -MP -MF $(DEPDIR)/t_dns_stuff-dns-stuff.Tpo -c -o t_dns_stuff-dns-stuff.obj `if test -f 'dns-stuff.c'; then $(CYGPATH_W) 'dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/dns-stuff.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_dns_stuff-dns-stuff.Tpo $(DEPDIR)/t_dns_stuff-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns-stuff.c' object='t_dns_stuff-dns-stuff.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_dns_stuff_CFLAGS) $(CFLAGS) -c -o t_dns_stuff-dns-stuff.obj `if test -f 'dns-stuff.c'; then $(CYGPATH_W) 'dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/dns-stuff.c'; fi` + +t_http-t-support.o: t-support.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-t-support.o -MD -MP -MF $(DEPDIR)/t_http-t-support.Tpo -c -o t_http-t-support.o `test -f 't-support.c' || echo '$(srcdir)/'`t-support.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-t-support.Tpo $(DEPDIR)/t_http-t-support.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-support.c' object='t_http-t-support.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-t-support.o `test -f 't-support.c' || echo '$(srcdir)/'`t-support.c + +t_http-t-support.obj: t-support.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-t-support.obj -MD -MP -MF $(DEPDIR)/t_http-t-support.Tpo -c -o t_http-t-support.obj `if test -f 't-support.c'; then $(CYGPATH_W) 't-support.c'; else $(CYGPATH_W) '$(srcdir)/t-support.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-t-support.Tpo $(DEPDIR)/t_http-t-support.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-support.c' object='t_http-t-support.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-t-support.obj `if test -f 't-support.c'; then $(CYGPATH_W) 't-support.c'; else $(CYGPATH_W) '$(srcdir)/t-support.c'; fi` + +t_http-dns.o: dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-dns.o -MD -MP -MF $(DEPDIR)/t_http-dns.Tpo -c -o t_http-dns.o `test -f 'dns.c' || echo '$(srcdir)/'`dns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-dns.Tpo $(DEPDIR)/t_http-dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns.c' object='t_http-dns.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-dns.o `test -f 'dns.c' || echo '$(srcdir)/'`dns.c + +t_http-dns.obj: dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-dns.obj -MD -MP -MF $(DEPDIR)/t_http-dns.Tpo -c -o t_http-dns.obj `if test -f 'dns.c'; then $(CYGPATH_W) 'dns.c'; else $(CYGPATH_W) '$(srcdir)/dns.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-dns.Tpo $(DEPDIR)/t_http-dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns.c' object='t_http-dns.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-dns.obj `if test -f 'dns.c'; then $(CYGPATH_W) 'dns.c'; else $(CYGPATH_W) '$(srcdir)/dns.c'; fi` + +t_http-t-http.o: t-http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-t-http.o -MD -MP -MF $(DEPDIR)/t_http-t-http.Tpo -c -o t_http-t-http.o `test -f 't-http.c' || echo '$(srcdir)/'`t-http.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-t-http.Tpo $(DEPDIR)/t_http-t-http.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-http.c' object='t_http-t-http.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-t-http.o `test -f 't-http.c' || echo '$(srcdir)/'`t-http.c + +t_http-t-http.obj: t-http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-t-http.obj -MD -MP -MF $(DEPDIR)/t_http-t-http.Tpo -c -o t_http-t-http.obj `if test -f 't-http.c'; then $(CYGPATH_W) 't-http.c'; else $(CYGPATH_W) '$(srcdir)/t-http.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-t-http.Tpo $(DEPDIR)/t_http-t-http.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-http.c' object='t_http-t-http.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-t-http.obj `if test -f 't-http.c'; then $(CYGPATH_W) 't-http.c'; else $(CYGPATH_W) '$(srcdir)/t-http.c'; fi` + +t_http-http.o: http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-http.o -MD -MP -MF $(DEPDIR)/t_http-http.Tpo -c -o t_http-http.o `test -f 'http.c' || echo '$(srcdir)/'`http.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-http.Tpo $(DEPDIR)/t_http-http.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http.c' object='t_http-http.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-http.o `test -f 'http.c' || echo '$(srcdir)/'`http.c + +t_http-http.obj: http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-http.obj -MD -MP -MF $(DEPDIR)/t_http-http.Tpo -c -o t_http-http.obj `if test -f 'http.c'; then $(CYGPATH_W) 'http.c'; else $(CYGPATH_W) '$(srcdir)/http.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-http.Tpo $(DEPDIR)/t_http-http.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http.c' object='t_http-http.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-http.obj `if test -f 'http.c'; then $(CYGPATH_W) 'http.c'; else $(CYGPATH_W) '$(srcdir)/http.c'; fi` + +t_http-dns-stuff.o: dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-dns-stuff.o -MD -MP -MF $(DEPDIR)/t_http-dns-stuff.Tpo -c -o t_http-dns-stuff.o `test -f 'dns-stuff.c' || echo '$(srcdir)/'`dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-dns-stuff.Tpo $(DEPDIR)/t_http-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns-stuff.c' object='t_http-dns-stuff.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-dns-stuff.o `test -f 'dns-stuff.c' || echo '$(srcdir)/'`dns-stuff.c + +t_http-dns-stuff.obj: dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-dns-stuff.obj -MD -MP -MF $(DEPDIR)/t_http-dns-stuff.Tpo -c -o t_http-dns-stuff.obj `if test -f 'dns-stuff.c'; then $(CYGPATH_W) 'dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/dns-stuff.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-dns-stuff.Tpo $(DEPDIR)/t_http-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns-stuff.c' object='t_http-dns-stuff.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-dns-stuff.obj `if test -f 'dns-stuff.c'; then $(CYGPATH_W) 'dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/dns-stuff.c'; fi` + +t_http-http-common.o: http-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-http-common.o -MD -MP -MF $(DEPDIR)/t_http-http-common.Tpo -c -o t_http-http-common.o `test -f 'http-common.c' || echo '$(srcdir)/'`http-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-http-common.Tpo $(DEPDIR)/t_http-http-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http-common.c' object='t_http-http-common.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-http-common.o `test -f 'http-common.c' || echo '$(srcdir)/'`http-common.c + +t_http-http-common.obj: http-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -MT t_http-http-common.obj -MD -MP -MF $(DEPDIR)/t_http-http-common.Tpo -c -o t_http-http-common.obj `if test -f 'http-common.c'; then $(CYGPATH_W) 'http-common.c'; else $(CYGPATH_W) '$(srcdir)/http-common.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http-http-common.Tpo $(DEPDIR)/t_http-http-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http-common.c' object='t_http-http-common.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_CFLAGS) $(CFLAGS) -c -o t_http-http-common.obj `if test -f 'http-common.c'; then $(CYGPATH_W) 'http-common.c'; else $(CYGPATH_W) '$(srcdir)/http-common.c'; fi` + +t_http_basic-t-support.o: t-support.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-t-support.o -MD -MP -MF $(DEPDIR)/t_http_basic-t-support.Tpo -c -o t_http_basic-t-support.o `test -f 't-support.c' || echo '$(srcdir)/'`t-support.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-t-support.Tpo $(DEPDIR)/t_http_basic-t-support.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-support.c' object='t_http_basic-t-support.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-t-support.o `test -f 't-support.c' || echo '$(srcdir)/'`t-support.c + +t_http_basic-t-support.obj: t-support.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-t-support.obj -MD -MP -MF $(DEPDIR)/t_http_basic-t-support.Tpo -c -o t_http_basic-t-support.obj `if test -f 't-support.c'; then $(CYGPATH_W) 't-support.c'; else $(CYGPATH_W) '$(srcdir)/t-support.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-t-support.Tpo $(DEPDIR)/t_http_basic-t-support.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-support.c' object='t_http_basic-t-support.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-t-support.obj `if test -f 't-support.c'; then $(CYGPATH_W) 't-support.c'; else $(CYGPATH_W) '$(srcdir)/t-support.c'; fi` + +t_http_basic-dns.o: dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-dns.o -MD -MP -MF $(DEPDIR)/t_http_basic-dns.Tpo -c -o t_http_basic-dns.o `test -f 'dns.c' || echo '$(srcdir)/'`dns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-dns.Tpo $(DEPDIR)/t_http_basic-dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns.c' object='t_http_basic-dns.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-dns.o `test -f 'dns.c' || echo '$(srcdir)/'`dns.c + +t_http_basic-dns.obj: dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-dns.obj -MD -MP -MF $(DEPDIR)/t_http_basic-dns.Tpo -c -o t_http_basic-dns.obj `if test -f 'dns.c'; then $(CYGPATH_W) 'dns.c'; else $(CYGPATH_W) '$(srcdir)/dns.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-dns.Tpo $(DEPDIR)/t_http_basic-dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns.c' object='t_http_basic-dns.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-dns.obj `if test -f 'dns.c'; then $(CYGPATH_W) 'dns.c'; else $(CYGPATH_W) '$(srcdir)/dns.c'; fi` + +t_http_basic-t-http-basic.o: t-http-basic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-t-http-basic.o -MD -MP -MF $(DEPDIR)/t_http_basic-t-http-basic.Tpo -c -o t_http_basic-t-http-basic.o `test -f 't-http-basic.c' || echo '$(srcdir)/'`t-http-basic.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-t-http-basic.Tpo $(DEPDIR)/t_http_basic-t-http-basic.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-http-basic.c' object='t_http_basic-t-http-basic.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-t-http-basic.o `test -f 't-http-basic.c' || echo '$(srcdir)/'`t-http-basic.c + +t_http_basic-t-http-basic.obj: t-http-basic.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-t-http-basic.obj -MD -MP -MF $(DEPDIR)/t_http_basic-t-http-basic.Tpo -c -o t_http_basic-t-http-basic.obj `if test -f 't-http-basic.c'; then $(CYGPATH_W) 't-http-basic.c'; else $(CYGPATH_W) '$(srcdir)/t-http-basic.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-t-http-basic.Tpo $(DEPDIR)/t_http_basic-t-http-basic.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-http-basic.c' object='t_http_basic-t-http-basic.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-t-http-basic.obj `if test -f 't-http-basic.c'; then $(CYGPATH_W) 't-http-basic.c'; else $(CYGPATH_W) '$(srcdir)/t-http-basic.c'; fi` + +t_http_basic-http.o: http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-http.o -MD -MP -MF $(DEPDIR)/t_http_basic-http.Tpo -c -o t_http_basic-http.o `test -f 'http.c' || echo '$(srcdir)/'`http.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-http.Tpo $(DEPDIR)/t_http_basic-http.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http.c' object='t_http_basic-http.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-http.o `test -f 'http.c' || echo '$(srcdir)/'`http.c + +t_http_basic-http.obj: http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-http.obj -MD -MP -MF $(DEPDIR)/t_http_basic-http.Tpo -c -o t_http_basic-http.obj `if test -f 'http.c'; then $(CYGPATH_W) 'http.c'; else $(CYGPATH_W) '$(srcdir)/http.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-http.Tpo $(DEPDIR)/t_http_basic-http.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http.c' object='t_http_basic-http.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-http.obj `if test -f 'http.c'; then $(CYGPATH_W) 'http.c'; else $(CYGPATH_W) '$(srcdir)/http.c'; fi` + +t_http_basic-dns-stuff.o: dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-dns-stuff.o -MD -MP -MF $(DEPDIR)/t_http_basic-dns-stuff.Tpo -c -o t_http_basic-dns-stuff.o `test -f 'dns-stuff.c' || echo '$(srcdir)/'`dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-dns-stuff.Tpo $(DEPDIR)/t_http_basic-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns-stuff.c' object='t_http_basic-dns-stuff.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-dns-stuff.o `test -f 'dns-stuff.c' || echo '$(srcdir)/'`dns-stuff.c + +t_http_basic-dns-stuff.obj: dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-dns-stuff.obj -MD -MP -MF $(DEPDIR)/t_http_basic-dns-stuff.Tpo -c -o t_http_basic-dns-stuff.obj `if test -f 'dns-stuff.c'; then $(CYGPATH_W) 'dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/dns-stuff.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-dns-stuff.Tpo $(DEPDIR)/t_http_basic-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns-stuff.c' object='t_http_basic-dns-stuff.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-dns-stuff.obj `if test -f 'dns-stuff.c'; then $(CYGPATH_W) 'dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/dns-stuff.c'; fi` + +t_http_basic-http-common.o: http-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-http-common.o -MD -MP -MF $(DEPDIR)/t_http_basic-http-common.Tpo -c -o t_http_basic-http-common.o `test -f 'http-common.c' || echo '$(srcdir)/'`http-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-http-common.Tpo $(DEPDIR)/t_http_basic-http-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http-common.c' object='t_http_basic-http-common.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-http-common.o `test -f 'http-common.c' || echo '$(srcdir)/'`http-common.c + +t_http_basic-http-common.obj: http-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -MT t_http_basic-http-common.obj -MD -MP -MF $(DEPDIR)/t_http_basic-http-common.Tpo -c -o t_http_basic-http-common.obj `if test -f 'http-common.c'; then $(CYGPATH_W) 'http-common.c'; else $(CYGPATH_W) '$(srcdir)/http-common.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_http_basic-http-common.Tpo $(DEPDIR)/t_http_basic-http-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http-common.c' object='t_http_basic-http-common.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_http_basic_CFLAGS) $(CFLAGS) -c -o t_http_basic-http-common.obj `if test -f 'http-common.c'; then $(CYGPATH_W) 'http-common.c'; else $(CYGPATH_W) '$(srcdir)/http-common.c'; fi` + +t_ldap_parse_uri-t-ldap-parse-uri.o: t-ldap-parse-uri.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-t-ldap-parse-uri.o -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Tpo -c -o t_ldap_parse_uri-t-ldap-parse-uri.o `test -f 't-ldap-parse-uri.c' || echo '$(srcdir)/'`t-ldap-parse-uri.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Tpo $(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-ldap-parse-uri.c' object='t_ldap_parse_uri-t-ldap-parse-uri.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-t-ldap-parse-uri.o `test -f 't-ldap-parse-uri.c' || echo '$(srcdir)/'`t-ldap-parse-uri.c + +t_ldap_parse_uri-t-ldap-parse-uri.obj: t-ldap-parse-uri.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-t-ldap-parse-uri.obj -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Tpo -c -o t_ldap_parse_uri-t-ldap-parse-uri.obj `if test -f 't-ldap-parse-uri.c'; then $(CYGPATH_W) 't-ldap-parse-uri.c'; else $(CYGPATH_W) '$(srcdir)/t-ldap-parse-uri.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Tpo $(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-ldap-parse-uri.c' object='t_ldap_parse_uri-t-ldap-parse-uri.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-t-ldap-parse-uri.obj `if test -f 't-ldap-parse-uri.c'; then $(CYGPATH_W) 't-ldap-parse-uri.c'; else $(CYGPATH_W) '$(srcdir)/t-ldap-parse-uri.c'; fi` + +t_ldap_parse_uri-ldap-parse-uri.o: ldap-parse-uri.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-ldap-parse-uri.o -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Tpo -c -o t_ldap_parse_uri-ldap-parse-uri.o `test -f 'ldap-parse-uri.c' || echo '$(srcdir)/'`ldap-parse-uri.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Tpo $(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap-parse-uri.c' object='t_ldap_parse_uri-ldap-parse-uri.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-ldap-parse-uri.o `test -f 'ldap-parse-uri.c' || echo '$(srcdir)/'`ldap-parse-uri.c + +t_ldap_parse_uri-ldap-parse-uri.obj: ldap-parse-uri.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-ldap-parse-uri.obj -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Tpo -c -o t_ldap_parse_uri-ldap-parse-uri.obj `if test -f 'ldap-parse-uri.c'; then $(CYGPATH_W) 'ldap-parse-uri.c'; else $(CYGPATH_W) '$(srcdir)/ldap-parse-uri.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Tpo $(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap-parse-uri.c' object='t_ldap_parse_uri-ldap-parse-uri.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-ldap-parse-uri.obj `if test -f 'ldap-parse-uri.c'; then $(CYGPATH_W) 'ldap-parse-uri.c'; else $(CYGPATH_W) '$(srcdir)/ldap-parse-uri.c'; fi` + +t_ldap_parse_uri-http.o: http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-http.o -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-http.Tpo -c -o t_ldap_parse_uri-http.o `test -f 'http.c' || echo '$(srcdir)/'`http.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-http.Tpo $(DEPDIR)/t_ldap_parse_uri-http.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http.c' object='t_ldap_parse_uri-http.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-http.o `test -f 'http.c' || echo '$(srcdir)/'`http.c + +t_ldap_parse_uri-http.obj: http.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-http.obj -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-http.Tpo -c -o t_ldap_parse_uri-http.obj `if test -f 'http.c'; then $(CYGPATH_W) 'http.c'; else $(CYGPATH_W) '$(srcdir)/http.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-http.Tpo $(DEPDIR)/t_ldap_parse_uri-http.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http.c' object='t_ldap_parse_uri-http.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-http.obj `if test -f 'http.c'; then $(CYGPATH_W) 'http.c'; else $(CYGPATH_W) '$(srcdir)/http.c'; fi` + +t_ldap_parse_uri-http-common.o: http-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-http-common.o -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-http-common.Tpo -c -o t_ldap_parse_uri-http-common.o `test -f 'http-common.c' || echo '$(srcdir)/'`http-common.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-http-common.Tpo $(DEPDIR)/t_ldap_parse_uri-http-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http-common.c' object='t_ldap_parse_uri-http-common.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-http-common.o `test -f 'http-common.c' || echo '$(srcdir)/'`http-common.c + +t_ldap_parse_uri-http-common.obj: http-common.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-http-common.obj -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-http-common.Tpo -c -o t_ldap_parse_uri-http-common.obj `if test -f 'http-common.c'; then $(CYGPATH_W) 'http-common.c'; else $(CYGPATH_W) '$(srcdir)/http-common.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-http-common.Tpo $(DEPDIR)/t_ldap_parse_uri-http-common.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='http-common.c' object='t_ldap_parse_uri-http-common.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-http-common.obj `if test -f 'http-common.c'; then $(CYGPATH_W) 'http-common.c'; else $(CYGPATH_W) '$(srcdir)/http-common.c'; fi` + +t_ldap_parse_uri-dns-stuff.o: dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-dns-stuff.o -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-dns-stuff.Tpo -c -o t_ldap_parse_uri-dns-stuff.o `test -f 'dns-stuff.c' || echo '$(srcdir)/'`dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-dns-stuff.Tpo $(DEPDIR)/t_ldap_parse_uri-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns-stuff.c' object='t_ldap_parse_uri-dns-stuff.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-dns-stuff.o `test -f 'dns-stuff.c' || echo '$(srcdir)/'`dns-stuff.c + +t_ldap_parse_uri-dns-stuff.obj: dns-stuff.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-dns-stuff.obj -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-dns-stuff.Tpo -c -o t_ldap_parse_uri-dns-stuff.obj `if test -f 'dns-stuff.c'; then $(CYGPATH_W) 'dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/dns-stuff.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-dns-stuff.Tpo $(DEPDIR)/t_ldap_parse_uri-dns-stuff.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns-stuff.c' object='t_ldap_parse_uri-dns-stuff.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-dns-stuff.obj `if test -f 'dns-stuff.c'; then $(CYGPATH_W) 'dns-stuff.c'; else $(CYGPATH_W) '$(srcdir)/dns-stuff.c'; fi` + +t_ldap_parse_uri-ldap-url.o: ldap-url.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-ldap-url.o -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-ldap-url.Tpo -c -o t_ldap_parse_uri-ldap-url.o `test -f 'ldap-url.c' || echo '$(srcdir)/'`ldap-url.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-ldap-url.Tpo $(DEPDIR)/t_ldap_parse_uri-ldap-url.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap-url.c' object='t_ldap_parse_uri-ldap-url.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-ldap-url.o `test -f 'ldap-url.c' || echo '$(srcdir)/'`ldap-url.c + +t_ldap_parse_uri-ldap-url.obj: ldap-url.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-ldap-url.obj -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-ldap-url.Tpo -c -o t_ldap_parse_uri-ldap-url.obj `if test -f 'ldap-url.c'; then $(CYGPATH_W) 'ldap-url.c'; else $(CYGPATH_W) '$(srcdir)/ldap-url.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-ldap-url.Tpo $(DEPDIR)/t_ldap_parse_uri-ldap-url.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ldap-url.c' object='t_ldap_parse_uri-ldap-url.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-ldap-url.obj `if test -f 'ldap-url.c'; then $(CYGPATH_W) 'ldap-url.c'; else $(CYGPATH_W) '$(srcdir)/ldap-url.c'; fi` + +t_ldap_parse_uri-t-support.o: t-support.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-t-support.o -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-t-support.Tpo -c -o t_ldap_parse_uri-t-support.o `test -f 't-support.c' || echo '$(srcdir)/'`t-support.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-t-support.Tpo $(DEPDIR)/t_ldap_parse_uri-t-support.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-support.c' object='t_ldap_parse_uri-t-support.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-t-support.o `test -f 't-support.c' || echo '$(srcdir)/'`t-support.c + +t_ldap_parse_uri-t-support.obj: t-support.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-t-support.obj -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-t-support.Tpo -c -o t_ldap_parse_uri-t-support.obj `if test -f 't-support.c'; then $(CYGPATH_W) 't-support.c'; else $(CYGPATH_W) '$(srcdir)/t-support.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-t-support.Tpo $(DEPDIR)/t_ldap_parse_uri-t-support.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='t-support.c' object='t_ldap_parse_uri-t-support.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-t-support.obj `if test -f 't-support.c'; then $(CYGPATH_W) 't-support.c'; else $(CYGPATH_W) '$(srcdir)/t-support.c'; fi` + +t_ldap_parse_uri-dns.o: dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-dns.o -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-dns.Tpo -c -o t_ldap_parse_uri-dns.o `test -f 'dns.c' || echo '$(srcdir)/'`dns.c +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-dns.Tpo $(DEPDIR)/t_ldap_parse_uri-dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns.c' object='t_ldap_parse_uri-dns.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-dns.o `test -f 'dns.c' || echo '$(srcdir)/'`dns.c + +t_ldap_parse_uri-dns.obj: dns.c +@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -MT t_ldap_parse_uri-dns.obj -MD -MP -MF $(DEPDIR)/t_ldap_parse_uri-dns.Tpo -c -o t_ldap_parse_uri-dns.obj `if test -f 'dns.c'; then $(CYGPATH_W) 'dns.c'; else $(CYGPATH_W) '$(srcdir)/dns.c'; fi` +@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/t_ldap_parse_uri-dns.Tpo $(DEPDIR)/t_ldap_parse_uri-dns.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dns.c' object='t_ldap_parse_uri-dns.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(t_ldap_parse_uri_CFLAGS) $(CFLAGS) -c -o t_ldap_parse_uri-dns.obj `if test -f 'dns.c'; then $(CYGPATH_W) 'dns.c'; else $(CYGPATH_W) '$(srcdir)/dns.c'; fi` +install-dist_pkgdataDATA: $(dist_pkgdata_DATA) + @$(NORMAL_INSTALL) + @list='$(dist_pkgdata_DATA)'; test -n "$(pkgdatadir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pkgdatadir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pkgdatadir)" || exit 1; \ + fi; \ + for p in $$list; do \ + if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ + echo "$$d$$p"; \ + done | $(am__base_list) | \ + while read files; do \ + echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(pkgdatadir)'"; \ + $(INSTALL_DATA) $$files "$(DESTDIR)$(pkgdatadir)" || exit $$?; \ + done + +uninstall-dist_pkgdataDATA: + @$(NORMAL_UNINSTALL) + @list='$(dist_pkgdata_DATA)'; test -n "$(pkgdatadir)" || list=; \ + files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \ + dir='$(DESTDIR)$(pkgdatadir)'; $(am__uninstall_files_from_dir) + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +check-TESTS: $(TESTS) + @failed=0; all=0; xfail=0; xpass=0; skip=0; \ + srcdir=$(srcdir); export srcdir; \ + list=' $(TESTS) '; \ + $(am__tty_colors); \ + if test -n "$$list"; then \ + for tst in $$list; do \ + if test -f ./$$tst; then dir=./; \ + elif test -f $$tst; then dir=; \ + else dir="$(srcdir)/"; fi; \ + if $(TESTS_ENVIRONMENT) $${dir}$$tst $(AM_TESTS_FD_REDIRECT); then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xpass=`expr $$xpass + 1`; \ + failed=`expr $$failed + 1`; \ + col=$$red; res=XPASS; \ + ;; \ + *) \ + col=$$grn; res=PASS; \ + ;; \ + esac; \ + elif test $$? -ne 77; then \ + all=`expr $$all + 1`; \ + case " $(XFAIL_TESTS) " in \ + *[\ \ ]$$tst[\ \ ]*) \ + xfail=`expr $$xfail + 1`; \ + col=$$lgn; res=XFAIL; \ + ;; \ + *) \ + failed=`expr $$failed + 1`; \ + col=$$red; res=FAIL; \ + ;; \ + esac; \ + else \ + skip=`expr $$skip + 1`; \ + col=$$blu; res=SKIP; \ + fi; \ + echo "$${col}$$res$${std}: $$tst"; \ + done; \ + if test "$$all" -eq 1; then \ + tests="test"; \ + All=""; \ + else \ + tests="tests"; \ + All="All "; \ + fi; \ + if test "$$failed" -eq 0; then \ + if test "$$xfail" -eq 0; then \ + banner="$$All$$all $$tests passed"; \ + else \ + if test "$$xfail" -eq 1; then failures=failure; else failures=failures; fi; \ + banner="$$All$$all $$tests behaved as expected ($$xfail expected $$failures)"; \ + fi; \ + else \ + if test "$$xpass" -eq 0; then \ + banner="$$failed of $$all $$tests failed"; \ + else \ + if test "$$xpass" -eq 1; then passes=pass; else passes=passes; fi; \ + banner="$$failed of $$all $$tests did not behave as expected ($$xpass unexpected $$passes)"; \ + fi; \ + fi; \ + dashes="$$banner"; \ + skipped=""; \ + if test "$$skip" -ne 0; then \ + if test "$$skip" -eq 1; then \ + skipped="($$skip test was not run)"; \ + else \ + skipped="($$skip tests were not run)"; \ + fi; \ + test `echo "$$skipped" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$skipped"; \ + fi; \ + report=""; \ + if test "$$failed" -ne 0 && test -n "$(PACKAGE_BUGREPORT)"; then \ + report="Please report to $(PACKAGE_BUGREPORT)"; \ + test `echo "$$report" | wc -c` -le `echo "$$banner" | wc -c` || \ + dashes="$$report"; \ + fi; \ + dashes=`echo "$$dashes" | sed s/./=/g`; \ + if test "$$failed" -eq 0; then \ + col="$$grn"; \ + else \ + col="$$red"; \ + fi; \ + echo "$${col}$$dashes$${std}"; \ + echo "$${col}$$banner$${std}"; \ + test -z "$$skipped" || echo "$${col}$$skipped$${std}"; \ + test -z "$$report" || echo "$${col}$$report$${std}"; \ + echo "$${col}$$dashes$${std}"; \ + test "$$failed" -eq 0; \ + else :; fi + +distdir: $(BUILT_SOURCES) + $(MAKE) $(AM_MAKEFLAGS) distdir-am + +distdir-am: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am + $(MAKE) $(AM_MAKEFLAGS) check-TESTS +check: check-am +all-am: Makefile $(PROGRAMS) $(DATA) $(HEADERS) +installdirs: + for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(libexecdir)" "$(DESTDIR)$(pkgdatadir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic clean-libexecPROGRAMS \ + clean-noinstPROGRAMS mostlyclean-am + +distclean: distclean-am + -rm -f ./$(DEPDIR)/cdblib.Po + -rm -f ./$(DEPDIR)/certcache.Po + -rm -f ./$(DEPDIR)/crlcache.Po + -rm -f ./$(DEPDIR)/crlfetch.Po + -rm -f ./$(DEPDIR)/dirmngr-client.Po + -rm -f ./$(DEPDIR)/dirmngr.Po + -rm -f ./$(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Po + -rm -f ./$(DEPDIR)/dirmngr_ldap-ldap-url.Po + -rm -f ./$(DEPDIR)/dirmngr_ldap.Po + -rm -f ./$(DEPDIR)/dns-stuff.Po + -rm -f ./$(DEPDIR)/dns.Po + -rm -f ./$(DEPDIR)/domaininfo.Po + -rm -f ./$(DEPDIR)/http-common.Po + -rm -f ./$(DEPDIR)/http-ntbtls.Po + -rm -f ./$(DEPDIR)/http.Po + -rm -f ./$(DEPDIR)/ks-action.Po + -rm -f ./$(DEPDIR)/ks-engine-finger.Po + -rm -f ./$(DEPDIR)/ks-engine-hkp.Po + -rm -f ./$(DEPDIR)/ks-engine-http.Po + -rm -f ./$(DEPDIR)/ks-engine-kdns.Po + -rm -f ./$(DEPDIR)/ks-engine-ldap.Po + -rm -f ./$(DEPDIR)/ldap-parse-uri.Po + -rm -f ./$(DEPDIR)/ldap-url.Po + -rm -f ./$(DEPDIR)/ldap-wrapper-ce.Po + -rm -f ./$(DEPDIR)/ldap-wrapper.Po + -rm -f ./$(DEPDIR)/ldap.Po + -rm -f ./$(DEPDIR)/ldapserver.Po + -rm -f ./$(DEPDIR)/loadswdb.Po + -rm -f ./$(DEPDIR)/misc.Po + -rm -f ./$(DEPDIR)/ocsp.Po + -rm -f ./$(DEPDIR)/server.Po + -rm -f ./$(DEPDIR)/t_dns_stuff-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_dns_stuff-dns.Po + -rm -f ./$(DEPDIR)/t_dns_stuff-t-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_dns_stuff-t-support.Po + -rm -f ./$(DEPDIR)/t_http-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_http-dns.Po + -rm -f ./$(DEPDIR)/t_http-http-common.Po + -rm -f ./$(DEPDIR)/t_http-http.Po + -rm -f ./$(DEPDIR)/t_http-t-http.Po + -rm -f ./$(DEPDIR)/t_http-t-support.Po + -rm -f ./$(DEPDIR)/t_http_basic-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_http_basic-dns.Po + -rm -f ./$(DEPDIR)/t_http_basic-http-common.Po + -rm -f ./$(DEPDIR)/t_http_basic-http.Po + -rm -f ./$(DEPDIR)/t_http_basic-t-http-basic.Po + -rm -f ./$(DEPDIR)/t_http_basic-t-support.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-dns.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-http-common.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-http.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-ldap-url.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-t-support.Po + -rm -f ./$(DEPDIR)/validate.Po + -rm -f ./$(DEPDIR)/workqueue.Po + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: install-dist_pkgdataDATA + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS install-libexecPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -f ./$(DEPDIR)/cdblib.Po + -rm -f ./$(DEPDIR)/certcache.Po + -rm -f ./$(DEPDIR)/crlcache.Po + -rm -f ./$(DEPDIR)/crlfetch.Po + -rm -f ./$(DEPDIR)/dirmngr-client.Po + -rm -f ./$(DEPDIR)/dirmngr.Po + -rm -f ./$(DEPDIR)/dirmngr_ldap-dirmngr_ldap.Po + -rm -f ./$(DEPDIR)/dirmngr_ldap-ldap-url.Po + -rm -f ./$(DEPDIR)/dirmngr_ldap.Po + -rm -f ./$(DEPDIR)/dns-stuff.Po + -rm -f ./$(DEPDIR)/dns.Po + -rm -f ./$(DEPDIR)/domaininfo.Po + -rm -f ./$(DEPDIR)/http-common.Po + -rm -f ./$(DEPDIR)/http-ntbtls.Po + -rm -f ./$(DEPDIR)/http.Po + -rm -f ./$(DEPDIR)/ks-action.Po + -rm -f ./$(DEPDIR)/ks-engine-finger.Po + -rm -f ./$(DEPDIR)/ks-engine-hkp.Po + -rm -f ./$(DEPDIR)/ks-engine-http.Po + -rm -f ./$(DEPDIR)/ks-engine-kdns.Po + -rm -f ./$(DEPDIR)/ks-engine-ldap.Po + -rm -f ./$(DEPDIR)/ldap-parse-uri.Po + -rm -f ./$(DEPDIR)/ldap-url.Po + -rm -f ./$(DEPDIR)/ldap-wrapper-ce.Po + -rm -f ./$(DEPDIR)/ldap-wrapper.Po + -rm -f ./$(DEPDIR)/ldap.Po + -rm -f ./$(DEPDIR)/ldapserver.Po + -rm -f ./$(DEPDIR)/loadswdb.Po + -rm -f ./$(DEPDIR)/misc.Po + -rm -f ./$(DEPDIR)/ocsp.Po + -rm -f ./$(DEPDIR)/server.Po + -rm -f ./$(DEPDIR)/t_dns_stuff-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_dns_stuff-dns.Po + -rm -f ./$(DEPDIR)/t_dns_stuff-t-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_dns_stuff-t-support.Po + -rm -f ./$(DEPDIR)/t_http-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_http-dns.Po + -rm -f ./$(DEPDIR)/t_http-http-common.Po + -rm -f ./$(DEPDIR)/t_http-http.Po + -rm -f ./$(DEPDIR)/t_http-t-http.Po + -rm -f ./$(DEPDIR)/t_http-t-support.Po + -rm -f ./$(DEPDIR)/t_http_basic-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_http_basic-dns.Po + -rm -f ./$(DEPDIR)/t_http_basic-http-common.Po + -rm -f ./$(DEPDIR)/t_http_basic-http.Po + -rm -f ./$(DEPDIR)/t_http_basic-t-http-basic.Po + -rm -f ./$(DEPDIR)/t_http_basic-t-support.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-dns-stuff.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-dns.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-http-common.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-http.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-ldap-parse-uri.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-ldap-url.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-t-ldap-parse-uri.Po + -rm -f ./$(DEPDIR)/t_ldap_parse_uri-t-support.Po + -rm -f ./$(DEPDIR)/validate.Po + -rm -f ./$(DEPDIR)/workqueue.Po + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS uninstall-dist_pkgdataDATA \ + uninstall-libexecPROGRAMS + +.MAKE: check-am install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-TESTS \ + check-am clean clean-binPROGRAMS clean-generic \ + clean-libexecPROGRAMS clean-noinstPROGRAMS cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-binPROGRAMS install-data \ + install-data-am install-dist_pkgdataDATA install-dvi \ + install-dvi-am install-exec install-exec-am install-html \ + install-html-am install-info install-info-am \ + install-libexecPROGRAMS install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic pdf pdf-am ps ps-am tags tags-am uninstall \ + uninstall-am uninstall-binPROGRAMS uninstall-dist_pkgdataDATA \ + uninstall-libexecPROGRAMS + +.PRECIOUS: Makefile + + +@HAVE_W32_SYSTEM_TRUE@.rc.o: +@HAVE_W32_SYSTEM_TRUE@ $(WINDRES) $(DEFAULT_INCLUDES) $(INCLUDES) "$<" "$@" +@HAVE_W32_SYSTEM_TRUE@dirmngr-w32info.o : dirmngr.w32-manifest + +$(PROGRAMS) : $(libcommon) $(libcommonpth) + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/dirmngr/OAUTHORS b/dirmngr/OAUTHORS new file mode 100644 index 0000000..7832449 --- /dev/null +++ b/dirmngr/OAUTHORS @@ -0,0 +1,38 @@ +The old AUTHORS file from the separate dirmngr package. + + Package: dirmngr + Maintainer: Werner Koch <wk@gnupg.org> + Bug reports: bug-dirmngr@gnupg.org + Security related bug reports: security@gnupg.org + License: GPLv2+ + + +Steffen Hansen <steffen@klaralvdalens-datakonsult.se> + - Initial code + +g10 Code GmbH <code@g10code.com> + - All stuff written since October 2003. + +Werner Koch <wk@gnupg.org>, <wk@g10code.com> + - Help with initial code. + +Free Software Foundation <gnu@gnu.org> + - Code taken from GnuPG. + +Michael Tokarev <mjt@corpit.ru> + - src/cdb.h and src/cdblib.c from the public domain tinycdb 0.73. + + +The actual code is under the GNU GPL, except for src/cdb.h and +src/cdblib.h which are in the public domain. + + + Copyright 2003, 2004, 2006, 2007, 2008, 2010 g10 Code GmbH + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/dirmngr/ONEWS b/dirmngr/ONEWS new file mode 100644 index 0000000..cb20507 --- /dev/null +++ b/dirmngr/ONEWS @@ -0,0 +1,240 @@ +These are NEWS entries from the old separate dirmngr package + +Noteworthy changes in version 1.1.0 (unreleased) +------------------------------------------------ + + * Fixed a resource problem with LDAP CRLs. + + * Fixed a bad EOF detection with HTTP CRLs. + + * Made "dirmngr-client --url --load-crl URL" work. + + * New option --ignore-cert-extension. + + * Make use of libassuan 2.0 which is available as a DSO. + + +Noteworthy changes in version 1.0.3 (2009-06-17) +------------------------------------------------ + + * Client based trust anchors are now supported. + + * Configured certificates with the suffix ".der" are now also used. + + * Libgcrypt 1.4 is now required. + + +Noteworthy changes in version 1.0.2 (2008-07-31) +------------------------------------------------ + + * New option --url for the LOOKUP command and dirmngr-client. + + * The LOOKUP command does now also consults the local cache. New + option --cache-only for it and --local for dirmngr-client. + + * Port to Windows completed. + + * Improved certificate chain construction. + + * Support loading of PEM encoded CRLs via HTTP. + + +Noteworthy changes in version 1.0.1 (2007-08-16) +------------------------------------------------ + + * The option --ocsp-signer may now take a filename to allow several + certificates to be valid signers for the default responder. + + * New option --ocsp-max-period and improved the OCSP time checks. + + * New option --force-default-signer for dirmngr-client. + + * Ported to Windows. + + +Noteworthy changes in version 1.0.0 (2006-11-29) +------------------------------------------------ + + * Bumbed the version number. + + * Removed included gettext. We now require the system to provide a + suitable installation. + + +Noteworthy changes in version 0.9.7 (2006-11-17) +------------------------------------------------ + + * Internal cleanups. + + * Fixed updating of DIR.txt. Add additional diagnostics. + + * Updated gettext package. + + +Noteworthy changes in version 0.9.6 (2006-09-04) +------------------------------------------------ + + * A couple of bug fixes for OCSP. + + * OCSP does now make use of the responder ID and optionally included + certificates in the response to locate certificates. + + * No more lost file descriptors when loading CRLs via HTTP. + + * HTTP redirection for CRL and OCSP has been implemented. + + * Man pages are now build and installed from the texinfo source. + + +Noteworthy changes in version 0.9.5 (2006-06-27) +------------------------------------------------ + + * Fixed a problems with the CRL caching and CRL certificate + validation. + + * Improved diagnostics. + + +Noteworthy changes in version 0.9.4 (2006-05-16) +------------------------------------------------ + + * Try all names of each crlDP. + + * Don't shutdown the socket after sending the HTTP request. + + +Noteworthy changes in version 0.9.3 (2005-10-26) +------------------------------------------------ + + * Minor bug fixes. + + +Noteworthy changes in version 0.9.2 (2005-04-21) +------------------------------------------------ + + * Make use of authorityKeyidentifier.keyIdentifier. + + * Fixed a possible hang on exit. + + +Noteworthy changes in version 0.9.1 (2005-02-08) +------------------------------------------------ + + * New option --pem for dirmngr-client to allow requesting service + using a PEM encoded certificate. + + * New option --squid-mode to allow using dirmngr-client directly as a + Squid helper. + + * Bug fixes. + + +Noteworthy changes in version 0.9.0 (2004-12-17) +------------------------------------------------ + + * New option --daemon to start dirmngr as a system daemon. This + switches to the use of different directories and also does + CRL signing certificate validation on its own. + + * New tool dirmngr-client. + + * New options: --ldap-wrapper-program, --http-wrapper-program, + --disable-ldap, --disable-http, --honor-http-proxy, --http-proxy, + --ldap-proxy, --only-ldap-proxy, --ignore-ldap-dp and + --ignore-http-dp. + + * Uses an external ldap wrapper to cope with timeouts and general + LDAP problems. + + * SIGHUP may be used to reread the configuration and to flush the + certificate cache. + + * An authorithyKeyIdentifier in a CRL is now handled correctly. + + +Noteworthy changes in version 0.5.6 (2004-09-28) +------------------------------------------------ + + * LDAP fix. + + * Logging fixes. + + * Updated some configuration files. + + +Noteworthy changes in version 0.5.5 (2004-05-13) +------------------------------------------------ + + * Fixed the growing-dir.txt bug. + + * Better LDAP error logging. + + +Noteworthy changes in version 0.5.4 (2004-04-29) +------------------------------------------------ + + * New commands --ocsp-responder and --ocsp-signer to define a default + OCSP reponder if a certificate does not contain an assigned OCSP + responder. + + +Noteworthy changes in version 0.5.3 (2004-04-06) +------------------------------------------------ + + * Basic OCSP support. + + +Noteworthy changes in version 0.5.2 (2004-03-06) +------------------------------------------------ + + * New Assuan command LISTCRLS. + + * A couple of minor bug fixes. + + +Noteworthy changes in version 0.5.1 (2003-12-23) +------------------------------------------------ + +* New options --faked-system-time and --force. + +* Changed the name of the cache directory to $HOMEDIR/dirmngr-cache.d + and renamed the dbcontents file. You may delete the now obsolete + cache/ directory and the dbcontents file. + +* Dropped DB2 or DB4 use. There is no need for it because a constant + database fits our needs far better. + +* Experimental support for retrieving CRLs via http. + +* The --log-file option may now be used to print logs to a socket. + Prefix the socket name with "socket://" to enable this. This does + not work on all systems and falls back to stderr if there is a + problem with the socket. + + +Noteworthy changes in version 0.5.0 (2003-11-17) +------------------------------------------------ + +* Revamped the entire thing. + +* Does now require Libgcrypt 1.1.90 or higher, as well as the latest + libksba and libassuan. + +* Fixed a bug in the assuan inquire processing. + + +Noteworthy changes as of 2002-08-21 +------------------------------------ + +* The default home directory is now .gnupg + + + Copyright 2003, 2004, 2005 g10 Code GmbH + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even the + implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. diff --git a/dirmngr/cdb.h b/dirmngr/cdb.h new file mode 100644 index 0000000..0c0d270 --- /dev/null +++ b/dirmngr/cdb.h @@ -0,0 +1,94 @@ +/* $Id: cdb.h 106 2003-12-12 17:36:49Z werner $ + * public cdb include file + * + * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru. + * Public domain. + * + * Taken from tinycdb-0.73. By Werner Koch <wk@gnupg.org> 2003-12-12. + */ + +#ifndef TINYCDB_VERSION +#define TINYCDB_VERSION 0.73 + +typedef unsigned int cdbi_t; /*XXX should be at least 32 bits long */ + +/* common routines */ +cdbi_t cdb_hash(const void *buf, cdbi_t len); +cdbi_t cdb_unpack(const unsigned char buf[4]); +void cdb_pack(cdbi_t num, unsigned char buf[4]); + +struct cdb { + int cdb_fd; /* file descriptor */ + /* private members */ +#ifdef HAVE_W32_SYSTEM + void *cdb_mapping; /* Mapping handle. */ +#endif + cdbi_t cdb_fsize; /* datafile size */ + const unsigned char *cdb_mem; /* mmap'ed file memory */ + cdbi_t cdb_vpos, cdb_vlen; /* found data */ + cdbi_t cdb_kpos, cdb_klen; /* found key (only set if cdb_findinit + was called with KEY set to NULL). */ +}; + +#define cdb_datapos(c) ((c)->cdb_vpos) +#define cdb_datalen(c) ((c)->cdb_vlen) +#define cdb_keypos(c) ((c)->cdb_kpos) +#define cdb_keylen(c) ((c)->cdb_klen) +#define cdb_fileno(c) ((c)->cdb_fd) + +int cdb_init(struct cdb *cdbp, int fd); +void cdb_free(struct cdb *cdbp); + +int cdb_read(const struct cdb *cdbp, + void *buf, unsigned len, cdbi_t pos); +int cdb_find(struct cdb *cdbp, const void *key, unsigned klen); + +struct cdb_find { + struct cdb *cdb_cdbp; + cdbi_t cdb_hval; + const unsigned char *cdb_htp, *cdb_htab, *cdb_htend; + cdbi_t cdb_httodo; + const void *cdb_key; + cdbi_t cdb_klen; +}; + +int cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp, + const void *key, cdbi_t klen); +int cdb_findnext(struct cdb_find *cdbfp); + +/* old simple interface */ +/* open file using standard routine, then: */ +int cdb_seek(int fd, const void *key, unsigned klen, cdbi_t *dlenp); +int cdb_bread(int fd, void *buf, int len); + +/* cdb_make */ + +struct cdb_make { + int cdb_fd; /* file descriptor */ + /* private */ + cdbi_t cdb_dpos; /* data position so far */ + cdbi_t cdb_rcnt; /* record count so far */ + char cdb_buf[4096]; /* write buffer */ + char *cdb_bpos; /* current buf position */ + struct cdb_rl *cdb_rec[256]; /* list of arrays of record infos */ +}; + + + +int cdb_make_start(struct cdb_make *cdbmp, int fd); +int cdb_make_add(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, + const void *val, cdbi_t vlen); +int cdb_make_exists(struct cdb_make *cdbmp, + const void *key, cdbi_t klen); +int cdb_make_put(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, + const void *val, cdbi_t vlen, + int flag); +#define CDB_PUT_ADD 0 /* add unconditionnaly, like cdb_make_add() */ +#define CDB_PUT_REPLACE 1 /* replace: do not place to index OLD record */ +#define CDB_PUT_INSERT 2 /* add only if not already exists */ +#define CDB_PUT_WARN 3 /* add unconditionally but ret. 1 if exists */ +int cdb_make_finish(struct cdb_make *cdbmp); + +#endif /* include guard */ diff --git a/dirmngr/cdblib.c b/dirmngr/cdblib.c new file mode 100644 index 0000000..827399f --- /dev/null +++ b/dirmngr/cdblib.c @@ -0,0 +1,928 @@ +/* cdblib.c - all CDB library functions. + * + * This file is a part of tinycdb package by Michael Tokarev, mjt@corpit.ru. + * Public domain. + * + * Taken from tinycdb-0.73 and merged into one file for easier + * inclusion into Dirmngr. By Werner Koch <wk@gnupg.org> 2003-12-12. + */ + +/* A cdb database is a single file used to map 'keys' to 'values', + having records of (key,value) pairs. File consists of 3 parts: toc + (table of contents), data and index (hash tables). + + Toc has fixed length of 2048 bytes, containing 256 pointers to hash + tables inside index sections. Every pointer consists of position + of a hash table in bytes from the beginning of a file, and a size + of a hash table in entries, both are 4-bytes (32 bits) unsigned + integers in little-endian form. Hash table length may have zero + length, meaning that corresponding hash table is empty. + + Right after toc section, data section follows without any + alingment. It consists of series of records, each is a key length, + value (data) length, key and value. Again, key and value length + are 4-byte unsigned integers. Each next record follows previous + without any special alignment. + + After data section, index (hash tables) section follows. It should + be looked to in conjunction with toc section, where each of max 256 + hash tables are defined. Index section consists of series of hash + tables, with starting position and length defined in toc section. + Every hash table is a sequence of records each holds two numbers: + key's hash value and record position inside data section (bytes + from the beginning of a file to first byte of key length starting + data record). If record position is zero, then this is an empty + hash table slot, pointed to nowhere. + + CDB hash function is + hv = ((hv << 5) + hv) ^ c + for every single c byte of a key, starting with hv = 5381. + + Toc section indexed by (hv % 256), i.e. hash value modulo 256 + (number of entries in toc section). + + In order to find a record, one should: first, compute the hash + value (hv) of a key. Second, look to hash table number hv modulo + 256. If it is empty, then there is no such key exists. If it is + not empty, then third, loop by slots inside that hash table, + starting from slot with number hv divided by 256 modulo length of + that table, or ((hv / 256) % htlen), searching for this hv in hash + table. Stop search on empty slot (if record position is zero) or + when all slots was probed (note cyclic search, jumping from end to + beginning of a table). When hash value in question is found in + hash table, look to key of corresponding record, comparing it with + key in question. If them of the same length and equals to each + other, then record is found, overwise, repeat with next hash table + slot. Note that there may be several records with the same key. +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#ifdef _WIN32 +# include <windows.h> +#else +# include <sys/mman.h> +# ifndef MAP_FAILED +# define MAP_FAILED ((void*)-1) +# endif +#endif +#include <sys/stat.h> + +#include "dirmngr-err.h" +#include "cdb.h" + +#ifndef EPROTO +# define EPROTO EINVAL +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 +#endif + + +struct cdb_rec { + cdbi_t hval; + cdbi_t rpos; +}; + +struct cdb_rl { + struct cdb_rl *next; + cdbi_t cnt; + struct cdb_rec rec[254]; +}; + +static int make_find(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, cdbi_t hval, + struct cdb_rl **rlp); +static int make_write(struct cdb_make *cdbmp, + const char *ptr, cdbi_t len); + + + +/* Initializes structure given by CDBP pointer and associates it with + the open file descriptor FD. Allocate memory for the structure + itself if needed and file open operation should be done by + application. File FD should be opened at least read-only, and + should be seekable. Routine returns 0 on success or negative value + on error. */ +int +cdb_init(struct cdb *cdbp, int fd) +{ + struct stat st; + unsigned char *mem; +#ifdef _WIN32 + HANDLE hFile, hMapping; +#else + unsigned int fsize; +#endif + + /* get file size */ + if (fstat(fd, &st) < 0) + return -1; + /* trivial sanity check: at least toc should be here */ + if (st.st_size < 2048) { + gpg_err_set_errno (EPROTO); + return -1; + } + /* memory-map file */ +#ifdef _WIN32 +# ifdef __MINGW32CE__ + hFile = fd; +# else + hFile = (HANDLE) _get_osfhandle(fd); +# endif + if (hFile == (HANDLE) -1) + return -1; + hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (!hMapping) + return -1; + mem = (unsigned char *)MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); + if (!mem) + return -1; + cdbp->cdb_mapping = hMapping; +#else /*!_WIN32*/ + fsize = (unsigned int)(st.st_size & 0xffffffffu); + mem = (unsigned char*)mmap(NULL, fsize, PROT_READ, MAP_SHARED, fd, 0); + if (mem == MAP_FAILED) + return -1; +#endif /*!_WIN32*/ + + cdbp->cdb_fd = fd; + cdbp->cdb_fsize = st.st_size; + cdbp->cdb_mem = mem; + +#if 0 + /* XXX don't know well about madvise syscall -- is it legal + to set different options for parts of one mmap() region? + There is also posix_madvise() exist, with POSIX_MADV_RANDOM etc... + */ +#ifdef MADV_RANDOM + /* set madvise() parameters. Ignore errors for now if system + doesn't support it */ + madvise(mem, 2048, MADV_WILLNEED); + madvise(mem + 2048, cdbp->cdb_fsize - 2048, MADV_RANDOM); +#endif +#endif + + cdbp->cdb_vpos = cdbp->cdb_vlen = 0; + + return 0; +} + + +/* Frees the internal resources held by structure. Note that this + routine does not close the file. */ +void +cdb_free(struct cdb *cdbp) +{ + if (cdbp->cdb_mem) { +#ifdef _WIN32 + UnmapViewOfFile ((void*) cdbp->cdb_mem); + CloseHandle (cdbp->cdb_mapping); + cdbp->cdb_mapping = NULL; +#else + munmap((void*)cdbp->cdb_mem, cdbp->cdb_fsize); +#endif /* _WIN32 */ + cdbp->cdb_mem = NULL; + } + cdbp->cdb_fsize = 0; +} + + +/* Read data from cdb file, starting at position pos of length len, + placing result to buf. This routine may be used to get actual + value found by cdb_find() or other routines that returns position + and length of a data. Returns 0 on success or negative value on + error. */ +int +cdb_read(const struct cdb *cdbp, void *buf, unsigned len, cdbi_t pos) +{ + if (pos > cdbp->cdb_fsize || cdbp->cdb_fsize - pos < len) { + gpg_err_set_errno (EPROTO); + return -1; + } + memcpy(buf, cdbp->cdb_mem + pos, len); + return 0; +} + + +/* Attempts to find a key given by (key,klen) parameters. If key + exists in database, routine returns 1 and places position and + length of value associated with this key to internal fields inside + cdbp structure, to be accessible by cdb_datapos() and + cdb_datalen(). If key is not in database, routines returns 0. On + error, negative value is returned. Note that using cdb_find() it + is possible to lookup only first record with a given key. */ +int +cdb_find(struct cdb *cdbp, const void *key, cdbi_t klen) +{ + const unsigned char *htp; /* hash table pointer */ + const unsigned char *htab; /* hash table */ + const unsigned char *htend; /* end of hash table */ + cdbi_t httodo; /* ht bytes left to look */ + cdbi_t pos, n; + + cdbi_t hval; + + if (klen > cdbp->cdb_fsize) /* if key size is larger than file */ + return 0; + + hval = cdb_hash(key, klen); + + /* find (pos,n) hash table to use */ + /* first 2048 bytes (toc) are always available */ + /* (hval % 256) * 8 */ + htp = cdbp->cdb_mem + ((hval << 3) & 2047); /* index in toc (256x8) */ + n = cdb_unpack(htp + 4); /* table size */ + if (!n) /* empty table */ + return 0; /* not found */ + httodo = n << 3; /* bytes of htab to lookup */ + pos = cdb_unpack(htp); /* htab position */ + if (n > (cdbp->cdb_fsize >> 3) /* overflow of httodo ? */ + || pos > cdbp->cdb_fsize /* htab start within file ? */ + || httodo > cdbp->cdb_fsize - pos) /* entrie htab within file ? */ + { + gpg_err_set_errno (EPROTO); + return -1; + } + + htab = cdbp->cdb_mem + pos; /* htab pointer */ + htend = htab + httodo; /* after end of htab */ + /* htab starting position: rest of hval modulo htsize, 8bytes per elt */ + htp = htab + (((hval >> 8) % n) << 3); + + for(;;) { + pos = cdb_unpack(htp + 4); /* record position */ + if (!pos) + return 0; + if (cdb_unpack(htp) == hval) { + if (pos > cdbp->cdb_fsize - 8) { /* key+val lengths */ + gpg_err_set_errno (EPROTO); + return -1; + } + if (cdb_unpack(cdbp->cdb_mem + pos) == klen) { + if (cdbp->cdb_fsize - klen < pos + 8) { + gpg_err_set_errno (EPROTO); + return -1; + } + if (memcmp(key, cdbp->cdb_mem + pos + 8, klen) == 0) { + n = cdb_unpack(cdbp->cdb_mem + pos + 4); + pos += 8 + klen; + if (cdbp->cdb_fsize < n || cdbp->cdb_fsize - n < pos) { + gpg_err_set_errno (EPROTO); + return -1; + } + cdbp->cdb_vpos = pos; + cdbp->cdb_vlen = n; + return 1; + } + } + } + httodo -= 8; + if (!httodo) + return 0; + if ((htp += 8) >= htend) + htp = htab; + } + +} + + + +/* Sequential-find routines that used separate structure. It is + possible to have many than one record with the same key in a + database, and these routines allow enumeration of all of them. + cdb_findinit() initializes search structure pointed to by cdbfp. + It will return negative value on error or 0 on success. + cdb_findnext() attempts to find next matching key, setting value + position and length in cdbfp structure. It will return positive + value if given key was found, 0 if there is no more such key(s), or + negative value on error. To access value position and length after + successeful call to cdb_findnext() (when it returned positive + result), use cdb_datapos() and cdb_datalen() macros with cdbp + pointer. It is error to use cdb_findnext() after it returned 0 or + error condition. These routines is a bit slower than cdb_find(). + + Setting KEY to NULL will start a sequential search through the + entire DB. +*/ +int +cdb_findinit(struct cdb_find *cdbfp, struct cdb *cdbp, + const void *key, cdbi_t klen) +{ + cdbi_t n, pos; + + cdbfp->cdb_cdbp = cdbp; + cdbfp->cdb_key = key; + cdbfp->cdb_klen = klen; + cdbfp->cdb_hval = key? cdb_hash(key, klen) : 0; + + if (key) + { + cdbfp->cdb_htp = cdbp->cdb_mem + ((cdbfp->cdb_hval << 3) & 2047); + n = cdb_unpack(cdbfp->cdb_htp + 4); + cdbfp->cdb_httodo = n << 3; /* Set to size of hash table. */ + if (!n) + return 0; /* The hash table is empry. */ + pos = cdb_unpack(cdbfp->cdb_htp); + if (n > (cdbp->cdb_fsize >> 3) + || pos > cdbp->cdb_fsize + || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos) + { + gpg_err_set_errno (EPROTO); + return -1; + } + + cdbfp->cdb_htab = cdbp->cdb_mem + pos; + cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo; + cdbfp->cdb_htp = cdbfp->cdb_htab + (((cdbfp->cdb_hval >> 8) % n) << 3); + } + else /* Walk over all entries. */ + { + cdbfp->cdb_hval = 0; + /* Force stepping in findnext. */ + cdbfp->cdb_htp = cdbfp->cdb_htend = cdbp->cdb_mem; + } + return 0; +} + + +/* See cdb_findinit. */ +int +cdb_findnext(struct cdb_find *cdbfp) +{ + cdbi_t pos, n; + struct cdb *cdbp = cdbfp->cdb_cdbp; + + if (cdbfp->cdb_key) + { + while(cdbfp->cdb_httodo) { + pos = cdb_unpack(cdbfp->cdb_htp + 4); + if (!pos) + return 0; + n = cdb_unpack(cdbfp->cdb_htp) == cdbfp->cdb_hval; + if ((cdbfp->cdb_htp += 8) >= cdbfp->cdb_htend) + cdbfp->cdb_htp = cdbfp->cdb_htab; + cdbfp->cdb_httodo -= 8; + if (n) { + if (pos > cdbp->cdb_fsize - 8) { + gpg_err_set_errno (EPROTO); + return -1; + } + if (cdb_unpack(cdbp->cdb_mem + pos) == cdbfp->cdb_klen) { + if (cdbp->cdb_fsize - cdbfp->cdb_klen < pos + 8) { + gpg_err_set_errno (EPROTO); + return -1; + } + if (memcmp(cdbfp->cdb_key, + cdbp->cdb_mem + pos + 8, cdbfp->cdb_klen) == 0) { + n = cdb_unpack(cdbp->cdb_mem + pos + 4); + pos += 8 + cdbfp->cdb_klen; + if (cdbp->cdb_fsize < n || cdbp->cdb_fsize - n < pos) { + gpg_err_set_errno (EPROTO); + return -1; + } + cdbp->cdb_vpos = pos; + cdbp->cdb_vlen = n; + return 1; + } + } + } + } + } + else /* Walk over all entries. */ + { + do + { + while (cdbfp->cdb_htp >= cdbfp->cdb_htend) + { + if (cdbfp->cdb_hval > 255) + return 0; /* No more items. */ + + cdbfp->cdb_htp = cdbp->cdb_mem + cdbfp->cdb_hval * 8; + cdbfp->cdb_hval++; /* Advance for next round. */ + pos = cdb_unpack (cdbfp->cdb_htp); /* Offset of table. */ + n = cdb_unpack (cdbfp->cdb_htp + 4); /* Number of entries. */ + cdbfp->cdb_httodo = n * 8; /* Size of table. */ + if (n > (cdbp->cdb_fsize / 8) + || pos > cdbp->cdb_fsize + || cdbfp->cdb_httodo > cdbp->cdb_fsize - pos) + { + gpg_err_set_errno (EPROTO); + return -1; + } + + cdbfp->cdb_htab = cdbp->cdb_mem + pos; + cdbfp->cdb_htend = cdbfp->cdb_htab + cdbfp->cdb_httodo; + cdbfp->cdb_htp = cdbfp->cdb_htab; + } + + pos = cdb_unpack (cdbfp->cdb_htp + 4); /* Offset of record. */ + cdbfp->cdb_htp += 8; + } + while (!pos); + if (pos > cdbp->cdb_fsize - 8) + { + gpg_err_set_errno (EPROTO); + return -1; + } + + cdbp->cdb_kpos = pos + 8; + cdbp->cdb_klen = cdb_unpack(cdbp->cdb_mem + pos); + cdbp->cdb_vpos = pos + 8 + cdbp->cdb_klen; + cdbp->cdb_vlen = cdb_unpack(cdbp->cdb_mem + pos + 4); + n = 8 + cdbp->cdb_klen + cdbp->cdb_vlen; + if ( pos > cdbp->cdb_fsize || pos > cdbp->cdb_fsize - n) + { + gpg_err_set_errno (EPROTO); + return -1; + } + return 1; /* Found. */ + } + return 0; +} + +/* Read a chunk from file, ignoring interrupts (EINTR) */ +int +cdb_bread(int fd, void *buf, int len) +{ + int l; + while(len > 0) { + do l = read(fd, buf, len); + while(l < 0 && errno == EINTR); + if (l <= 0) { + if (!l) + gpg_err_set_errno (EIO); + return -1; + } + buf = (char*)buf + l; + len -= l; + } + return 0; +} + +/* Find a given key in cdb file, seek a file pointer to it's value and + place data length to *dlenp. */ +int +cdb_seek(int fd, const void *key, unsigned klen, cdbi_t *dlenp) +{ + cdbi_t htstart; /* hash table start position */ + cdbi_t htsize; /* number of elements in a hash table */ + cdbi_t httodo; /* hash table elements left to look */ + cdbi_t hti; /* hash table index */ + cdbi_t pos; /* position in a file */ + cdbi_t hval; /* key's hash value */ + unsigned char rbuf[64]; /* read buffer */ + int needseek = 1; /* if we should seek to a hash slot */ + + hval = cdb_hash(key, klen); + pos = (hval & 0xff) << 3; /* position in TOC */ + /* read the hash table parameters */ + if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0) + return -1; + if ((htsize = cdb_unpack(rbuf + 4)) == 0) + return 0; + hti = (hval >> 8) % htsize; /* start position in hash table */ + httodo = htsize; + htstart = cdb_unpack(rbuf); + + for(;;) { + if (needseek && lseek(fd, htstart + (hti << 3), SEEK_SET) < 0) + return -1; + if (cdb_bread(fd, rbuf, 8) < 0) + return -1; + if ((pos = cdb_unpack(rbuf + 4)) == 0) /* not found */ + return 0; + + if (cdb_unpack(rbuf) != hval) /* hash value not matched */ + needseek = 0; + else { /* hash value matched */ + if (lseek(fd, pos, SEEK_SET) < 0 || cdb_bread(fd, rbuf, 8) < 0) + return -1; + if (cdb_unpack(rbuf) == klen) { /* key length matches */ + /* read the key from file and compare with wanted */ + cdbi_t l = klen, c; + const char *k = (const char*)key; + if (*dlenp) + *dlenp = cdb_unpack(rbuf + 4); /* save value length */ + for(;;) { + if (!l) /* the whole key read and matches, return */ + return 1; + c = l > sizeof(rbuf) ? sizeof(rbuf) : l; + if (cdb_bread(fd, rbuf, c) < 0) + return -1; + if (memcmp(rbuf, k, c) != 0) /* no, it differs, stop here */ + break; + k += c; l -= c; + } + } + needseek = 1; /* we're looked to other place, should seek back */ + } + if (!--httodo) + return 0; + if (++hti == htsize) { + hti = htstart; + needseek = 1; + } + } +} + +cdbi_t +cdb_unpack(const unsigned char buf[4]) +{ + cdbi_t n = buf[3]; + n <<= 8; n |= buf[2]; + n <<= 8; n |= buf[1]; + n <<= 8; n |= buf[0]; + return n; +} + +/* Add record with key (KEY,KLEN) and value (VAL,VLEN) to a database. + Returns 0 on success or negative value on error. Note that this + routine does not checks if given key already exists, but cdb_find() + will not see second record with the same key. It is not possible + to continue building a database if cdb_make_add() returned an error + indicator. */ +int +cdb_make_add(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, + const void *val, cdbi_t vlen) +{ + unsigned char rlen[8]; + cdbi_t hval; + struct cdb_rl *rl; + if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) || + vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) { + gpg_err_set_errno (ENOMEM); + return -1; + } + hval = cdb_hash(key, klen); + rl = cdbmp->cdb_rec[hval&255]; + if (!rl || rl->cnt >= sizeof(rl->rec)/sizeof(rl->rec[0])) { + rl = (struct cdb_rl*)malloc(sizeof(struct cdb_rl)); + if (!rl) { + gpg_err_set_errno (ENOMEM); + return -1; + } + rl->cnt = 0; + rl->next = cdbmp->cdb_rec[hval&255]; + cdbmp->cdb_rec[hval&255] = rl; + } + rl->rec[rl->cnt].hval = hval; + rl->rec[rl->cnt].rpos = cdbmp->cdb_dpos; + ++rl->cnt; + ++cdbmp->cdb_rcnt; + cdb_pack(klen, rlen); + cdb_pack(vlen, rlen + 4); + if (make_write(cdbmp, rlen, 8) < 0 || + make_write(cdbmp, key, klen) < 0 || + make_write(cdbmp, val, vlen) < 0) + return -1; + return 0; +} + +int +cdb_make_put(struct cdb_make *cdbmp, + const void *key, cdbi_t klen, + const void *val, cdbi_t vlen, + int flags) +{ + unsigned char rlen[8]; + cdbi_t hval = cdb_hash(key, klen); + struct cdb_rl *rl; + int c, r; + + switch(flags) { + case CDB_PUT_REPLACE: + case CDB_PUT_INSERT: + case CDB_PUT_WARN: + c = make_find(cdbmp, key, klen, hval, &rl); + if (c < 0) + return -1; + if (c) { + if (flags == CDB_PUT_INSERT) { + gpg_err_set_errno (EEXIST); + return 1; + } + else if (flags == CDB_PUT_REPLACE) { + --c; + r = 1; + break; + } + else + r = 1; + } + /* fall through */ + + case CDB_PUT_ADD: + rl = cdbmp->cdb_rec[hval&255]; + if (!rl || rl->cnt >= sizeof(rl->rec)/sizeof(rl->rec[0])) { + rl = (struct cdb_rl*)malloc(sizeof(struct cdb_rl)); + if (!rl) { + gpg_err_set_errno (ENOMEM); + return -1; + } + rl->cnt = 0; + rl->next = cdbmp->cdb_rec[hval&255]; + cdbmp->cdb_rec[hval&255] = rl; + } + c = rl->cnt; + r = 0; + break; + + default: + gpg_err_set_errno (EINVAL); + return -1; + } + + if (klen > 0xffffffff - (cdbmp->cdb_dpos + 8) || + vlen > 0xffffffff - (cdbmp->cdb_dpos + klen + 8)) { + gpg_err_set_errno (ENOMEM); + return -1; + } + rl->rec[c].hval = hval; + rl->rec[c].rpos = cdbmp->cdb_dpos; + if (c == rl->cnt) { + ++rl->cnt; + ++cdbmp->cdb_rcnt; + } + cdb_pack(klen, rlen); + cdb_pack(vlen, rlen + 4); + if (make_write(cdbmp, rlen, 8) < 0 || + make_write(cdbmp, key, klen) < 0 || + make_write(cdbmp, val, vlen) < 0) + return -1; + return r; +} + + +static int +match(int fd, cdbi_t pos, const char *key, cdbi_t klen) +{ + unsigned char buf[64]; /*XXX cdb_buf may be used here instead */ + if (lseek(fd, pos, SEEK_SET) < 0 || read(fd, buf, 8) != 8) + return -1; + if (cdb_unpack(buf) != klen) + return 0; + + while(klen > sizeof(buf)) { + if (read(fd, buf, sizeof(buf)) != sizeof(buf)) + return -1; + if (memcmp(buf, key, sizeof(buf)) != 0) + return 0; + key += sizeof(buf); + klen -= sizeof(buf); + } + if (klen) { + if (read(fd, buf, klen) != klen) + return -1; + if (memcmp(buf, key, klen) != 0) + return 0; + } + return 1; +} + + +static int +make_find (struct cdb_make *cdbmp, + const void *key, cdbi_t klen, cdbi_t hval, + struct cdb_rl **rlp) +{ + struct cdb_rl *rl = cdbmp->cdb_rec[hval&255]; + int r, i; + int sought = 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; + } + sought = 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 (sought && lseek(cdbmp->cdb_fd, cdbmp->cdb_dpos, SEEK_SET) < 0) + return -1; + return 0; +} + +int +cdb_make_exists(struct cdb_make *cdbmp, + const void *key, cdbi_t klen) +{ + return make_find(cdbmp, key, klen, cdb_hash(key, klen), NULL); +} + + +void +cdb_pack(cdbi_t num, unsigned char buf[4]) +{ + buf[0] = num & 255; num >>= 8; + buf[1] = num & 255; num >>= 8; + buf[2] = num & 255; + buf[3] = num >> 8; +} + + +/* Initializes structure to create a database. File FD should be + opened read-write and should be seekable. Returns 0 on success or + negative value on error. */ +int +cdb_make_start(struct cdb_make *cdbmp, int fd) +{ + memset (cdbmp, 0, sizeof *cdbmp); + cdbmp->cdb_fd = fd; + cdbmp->cdb_dpos = 2048; + cdbmp->cdb_bpos = cdbmp->cdb_buf + 2048; + return 0; +} + + +static int +ewrite(int fd, const char *buf, int len) +{ + while(len) { + int l = write(fd, buf, len); + if (l < 0 && errno != EINTR) + return -1; + if (l > 0) + { + len -= l; + buf += l; + } + } + return 0; +} + +static int +make_write(struct cdb_make *cdbmp, const char *ptr, cdbi_t len) +{ + cdbi_t l = sizeof(cdbmp->cdb_buf) - (cdbmp->cdb_bpos - cdbmp->cdb_buf); + cdbmp->cdb_dpos += len; + if (len > l) { + memcpy(cdbmp->cdb_bpos, ptr, l); + if (ewrite(cdbmp->cdb_fd, cdbmp->cdb_buf, sizeof(cdbmp->cdb_buf)) < 0) + return -1; + ptr += l; len -= l; + l = len / sizeof(cdbmp->cdb_buf); + if (l) { + l *= sizeof(cdbmp->cdb_buf); + if (ewrite(cdbmp->cdb_fd, ptr, l) < 0) + return -1; + ptr += l; len -= l; + } + cdbmp->cdb_bpos = cdbmp->cdb_buf; + } + if (len) { + memcpy(cdbmp->cdb_bpos, ptr, len); + cdbmp->cdb_bpos += len; + } + return 0; +} + +static int +cdb_make_finish_internal(struct cdb_make *cdbmp) +{ + cdbi_t hcnt[256]; /* hash table counts */ + cdbi_t hpos[256]; /* hash table positions */ + struct cdb_rec *htab; + unsigned char *p; + struct cdb_rl *rl; + cdbi_t hsize; + unsigned t, i; + + if (((0xffffffff - cdbmp->cdb_dpos) >> 3) < cdbmp->cdb_rcnt) { + gpg_err_set_errno (ENOMEM); + return -1; + } + + /* count htab sizes and reorder reclists */ + hsize = 0; + for (t = 0; t < 256; ++t) { + struct cdb_rl *rlt = NULL; + i = 0; + rl = cdbmp->cdb_rec[t]; + while(rl) { + struct cdb_rl *rln = rl->next; + rl->next = rlt; + rlt = rl; + i += rl->cnt; + rl = rln; + } + cdbmp->cdb_rec[t] = rlt; + if (hsize < (hcnt[t] = i << 1)) + hsize = hcnt[t]; + } + + /* allocate memory to hold max htable */ + htab = (struct cdb_rec*)malloc((hsize + 2) * sizeof(struct cdb_rec)); + if (!htab) { + gpg_err_set_errno (ENOENT); + return -1; + } + p = (unsigned char *)htab; + htab += 2; + + /* build hash tables */ + for (t = 0; t < 256; ++t) { + cdbi_t len, hi; + hpos[t] = cdbmp->cdb_dpos; + if ((len = hcnt[t]) == 0) + continue; + for (i = 0; i < len; ++i) + htab[i].hval = htab[i].rpos = 0; + for (rl = cdbmp->cdb_rec[t]; rl; rl = rl->next) + for (i = 0; i < rl->cnt; ++i) { + hi = (rl->rec[i].hval >> 8) % len; + while(htab[hi].rpos) + if (++hi == len) + hi = 0; + htab[hi] = rl->rec[i]; + } + for (i = 0; i < len; ++i) { + cdb_pack(htab[i].hval, p + (i << 3)); + cdb_pack(htab[i].rpos, p + (i << 3) + 4); + } + if (make_write(cdbmp, p, len << 3) < 0) { + free(p); + return -1; + } + } + free(p); + if (cdbmp->cdb_bpos != cdbmp->cdb_buf && + ewrite(cdbmp->cdb_fd, cdbmp->cdb_buf, + cdbmp->cdb_bpos - cdbmp->cdb_buf) != 0) + return -1; + p = cdbmp->cdb_buf; + for (t = 0; t < 256; ++t) { + cdb_pack(hpos[t], p + (t << 3)); + cdb_pack(hcnt[t], p + (t << 3) + 4); + } + if (lseek(cdbmp->cdb_fd, 0, 0) != 0 || + ewrite(cdbmp->cdb_fd, p, 2048) != 0) + return -1; + + return 0; +} + +static void +cdb_make_free(struct cdb_make *cdbmp) +{ + unsigned t; + for(t = 0; t < 256; ++t) { + struct cdb_rl *rl = cdbmp->cdb_rec[t]; + while(rl) { + struct cdb_rl *tm = rl; + rl = rl->next; + free(tm); + } + } +} + + + +/* Finalizes database file, constructing all needed indexes, and frees + memory structures. It does not close the file descriptor. Returns + 0 on success or a negative value on error. */ +int +cdb_make_finish(struct cdb_make *cdbmp) +{ + int r = cdb_make_finish_internal(cdbmp); + cdb_make_free(cdbmp); + return r; +} + + +cdbi_t +cdb_hash(const void *buf, cdbi_t len) +{ + register const unsigned char *p = (const unsigned char *)buf; + register const unsigned char *end = p + len; + register cdbi_t hash = 5381; /* start value */ + while (p < end) + hash = (hash + (hash << 5)) ^ *p++; + return hash; +} diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c new file mode 100644 index 0000000..9ca6069 --- /dev/null +++ b/dirmngr/certcache.c @@ -0,0 +1,1861 @@ +/* certcache.c - Certificate caching + * Copyright (C) 2004, 2005, 2007, 2008, 2017 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <assert.h> +#include <sys/types.h> +#include <dirent.h> +#include <npth.h> + +#include "dirmngr.h" +#include "misc.h" +#include "../common/ksba-io-support.h" +#include "crlfetch.h" +#include "certcache.h" + +#define MAX_NONPERM_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. */ + + /* If this field is set the certificate has been taken from some + * configuration and shall not be flushed from the cache. */ + unsigned int permanent:1; + + /* If this field is set the certificate is trusted. The actual + * value is a (possible) combination of CERTTRUST_CLASS values. */ + unsigned int trustclasses:4; +}; +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 locking is not + needed but it would take extra efforts to make sure that no + indirect use of npth functions is done, so we simply lock it + always. Note: We can't use static initialization, as that is not + available through w32-pth. */ +static npth_rwlock_t cert_cache_lock; + +/* Flag to track whether the cache has been initialized. */ +static int initialization_done; + +/* Total number of non-permanent certificates. */ +static unsigned int total_nonperm_certificates; + +/* For each cert class the corresponding bit is set if at least one + * certificate of that class is loaded permanetly. */ +static unsigned int any_cert_of_class; + + +#ifdef HAVE_W32_SYSTEM +/* We load some functions dynamically. Provide typedefs for tehse + * functions. */ +typedef HCERTSTORE (WINAPI *CERTOPENSYSTEMSTORE) + (HCRYPTPROV hProv, LPCSTR szSubsystemProtocol); +typedef PCCERT_CONTEXT (WINAPI *CERTENUMCERTIFICATESINSTORE) + (HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext); +typedef WINBOOL (WINAPI *CERTCLOSESTORE) + (HCERTSTORE hCertStore,DWORD dwFlags); +#endif /*HAVE_W32_SYSTEM*/ + + + + +/* Helper to do the cache locking. */ +static void +init_cache_lock (void) +{ + int err; + + err = npth_rwlock_init (&cert_cache_lock, NULL); + if (err) + log_fatal (_("can't initialize certificate cache lock: %s\n"), + strerror (err)); +} + +static void +acquire_cache_read_lock (void) +{ + int err; + + err = npth_rwlock_rdlock (&cert_cache_lock); + if (err) + log_fatal (_("can't acquire read lock on the certificate cache: %s\n"), + strerror (err)); +} + +static void +acquire_cache_write_lock (void) +{ + int err; + + err = npth_rwlock_wrlock (&cert_cache_lock); + if (err) + log_fatal (_("can't acquire write lock on the certificate cache: %s\n"), + strerror (err)); +} + +static void +release_cache_lock (void) +{ + int err; + + err = npth_rwlock_unlock (&cert_cache_lock); + if (err) + log_fatal (_("can't release lock on the certificate cache: %s\n"), + strerror (err)); +} + + +/* Return false if both serial numbers match. Can't be used for + sorting. */ +static int +compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 ) +{ + unsigned char *a = serial1; + unsigned char *b = serial2; + return cmp_simple_canon_sexp (a, b); +} + + + +/* Return a malloced canonical S-Expression with the serial number + * 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; + + ci->permanent = 0; + ci->trustclasses = 0; + + ksba_cert_release (cert); +} + + +/* Put the certificate CERT into the cache. It is assumed that the + * cache is locked while this function is called. + * + * FROM_CONFIG indicates that CERT is a permanent certificate and + * should stay in the cache. IS_TRUSTED requests that the trusted + * flag is set for the certificate; a value of 1 indicates the + * cert is trusted due to GnuPG mechanisms, a value of 2 indicates + * that it is trusted because it has been taken from the system's + * store of trusted certificates. If FPR_BUFFER is not NULL the + * fingerprint of the certificate will be stored there. FPR_BUFFER + * needs to point to a buffer of at least 20 bytes. The fingerprint + * will be stored on success or when the function returns + * GPG_ERR_DUP_VALUE. */ +static gpg_error_t +put_cert (ksba_cert_t cert, int permanent, unsigned int trustclass, + 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 certificate from an external resource + * it seems to be reasonable. */ + if (!permanent && total_nonperm_certificates >= MAX_NONPERM_CACHED_CERTS) + { + static int idx; + cert_item_t ci_mark; + int i; + unsigned int drop_count; + + drop_count = MAX_NONPERM_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->permanent) + ci_mark = ci; + if (ci_mark) + { + clean_cache_slot (ci_mark); + drop_count--; + total_nonperm_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; + } + + 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->permanent = !!permanent; + ci->trustclasses = trustclass; + + if (permanent) + any_cert_of_class |= trustclass; + else + total_nonperm_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 function. */ +static gpg_error_t +load_certs_from_dir (const char *dirname, unsigned int trustclass) +{ + gpg_error_t err; + gnupg_dir_t dir; + gnupg_dirent_t ep; + char *p; + size_t n; + estream_t fp; + ksba_reader_t reader; + ksba_cert_t cert; + char *fname = NULL; + + dir = gnupg_opendir (dirname); + if (!dir) + { + return 0; /* We do not consider this a severe error. */ + } + + while ( (ep = gnupg_readdir (dir)) ) + { + p = ep->d_name; + if (*p == '.' || !*p) + continue; /* Skip any hidden files and invalid entries. */ + n = strlen (p); + if ( n < 5 || (strcmp (p+n-4,".crt") && strcmp (p+n-4,".der"))) + continue; /* Not the desired "*.crt" or "*.der" pattern. */ + + xfree (fname); + fname = make_filename (dirname, p, NULL); + fp = es_fopen (fname, "rb"); + if (!fp) + { + log_error (_("can't open '%s': %s\n"), + fname, strerror (errno)); + continue; + } + + err = create_estream_ksba_reader (&reader, fp); + if (err) + { + es_fclose (fp); + continue; + } + + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_read_der (cert, reader); + ksba_reader_release (reader); + es_fclose (fp); + if (err) + { + log_error (_("can't parse certificate '%s': %s\n"), + fname, gpg_strerror (err)); + ksba_cert_release (cert); + continue; + } + + err = put_cert (cert, 1, trustclass, NULL); + if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) + log_info (_("certificate '%s' already cached\n"), fname); + else if (!err) + { + if ((trustclass & CERTTRUST_CLASS_CONFIG)) + http_register_cfg_ca (fname); + + if (trustclass) + 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); + gnupg_closedir (dir); + return 0; +} + + +/* Load certificates from FILE. The certificates are expected to be + * PEM encoded so that it is possible to load several certificates. + * TRUSTCLASSES is used to mark the certificates as trusted. The + * cache should be in a locked state when calling this function. + * NO_ERROR repalces an error message when FNAME was not found by an + * information message. */ +static gpg_error_t +load_certs_from_file (const char *fname, unsigned int trustclasses, + int no_error) +{ + gpg_error_t err; + estream_t fp = NULL; + gnupg_ksba_io_t ioctx = NULL; + ksba_reader_t reader; + ksba_cert_t cert = NULL; + + fp = es_fopen (fname, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_ENONET && no_error) + log_info (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); + else + log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + + err = gnupg_ksba_create_reader (&ioctx, + (GNUPG_KSBA_IO_AUTODETECT + | GNUPG_KSBA_IO_MULTIPEM), + fp, &reader); + if (err) + { + log_error ("can't create reader: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Loop to read all certificates from the file. */ + do + { + ksba_cert_release (cert); + cert = NULL; + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_read_der (cert, reader); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + else + log_error (_("can't parse certificate '%s': %s\n"), + fname, gpg_strerror (err)); + goto leave; + } + + err = put_cert (cert, 1, trustclasses, NULL); + if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) + log_info (_("certificate '%s' already cached\n"), fname); + else if (err) + log_error (_("error loading certificate '%s': %s\n"), + fname, gpg_strerror (err)); + else if (opt.verbose > 1) + { + char *p; + + log_info (_("trusted certificate '%s' loaded\n"), fname); + 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); + } + + ksba_reader_clear (reader, NULL, NULL); + } + while (!gnupg_ksba_reader_eof_seen (ioctx)); + + leave: + ksba_cert_release (cert); + gnupg_ksba_destroy_reader (ioctx); + es_fclose (fp); + + return err; +} + + +#ifdef HAVE_W32_SYSTEM +/* Load all certificates from the Windows store named STORENAME. All + * certificates are considered to be system provided trusted + * certificates. The cache should be in a locked state when calling + * this function. */ +static void +load_certs_from_w32_store (const char *storename) +{ + static int init_done; + static CERTOPENSYSTEMSTORE pCertOpenSystemStore; + static CERTENUMCERTIFICATESINSTORE pCertEnumCertificatesInStore; + static CERTCLOSESTORE pCertCloseStore; + gpg_error_t err; + HCERTSTORE w32store; + const CERT_CONTEXT *w32cert; + ksba_cert_t cert = NULL; + unsigned int count = 0; + + /* Initialize on the first use. */ + if (!init_done) + { + static HANDLE hCrypt32; + + init_done = 1; + + hCrypt32 = LoadLibrary ("Crypt32.dll"); + if (!hCrypt32) + { + log_error ("can't load Crypt32.dll: %s\n", w32_strerror (-1)); + return; + } + + pCertOpenSystemStore = (CERTOPENSYSTEMSTORE) + GetProcAddress (hCrypt32, "CertOpenSystemStoreA"); + pCertEnumCertificatesInStore = (CERTENUMCERTIFICATESINSTORE) + GetProcAddress (hCrypt32, "CertEnumCertificatesInStore"); + pCertCloseStore = (CERTCLOSESTORE) + GetProcAddress (hCrypt32, "CertCloseStore"); + if ( !pCertOpenSystemStore + || !pCertEnumCertificatesInStore + || !pCertCloseStore) + { + log_error ("can't load crypt32.dll: %s\n", "missing function"); + pCertOpenSystemStore = NULL; + } + } + + if (!pCertOpenSystemStore) + return; /* Not initialized. */ + + + w32store = pCertOpenSystemStore (0, storename); + if (!w32store) + { + log_error ("can't open certificate store '%s': %s\n", + storename, w32_strerror (-1)); + return; + } + + w32cert = NULL; + while ((w32cert = pCertEnumCertificatesInStore (w32store, w32cert))) + { + if (w32cert->dwCertEncodingType == X509_ASN_ENCODING) + { + ksba_cert_release (cert); + cert = NULL; + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_init_from_mem (cert, + w32cert->pbCertEncoded, + w32cert->cbCertEncoded); + if (err) + { + log_error (_("can't parse certificate '%s': %s\n"), + storename, gpg_strerror (err)); + break; + } + + err = put_cert (cert, 1, CERTTRUST_CLASS_SYSTEM, NULL); + if (!err) + count++; + if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) + { + if (DBG_X509) + log_debug (_("certificate '%s' already cached\n"), storename); + } + else if (err) + log_error (_("error loading certificate '%s': %s\n"), + storename, gpg_strerror (err)); + else if (opt.verbose > 1) + { + char *p; + + log_info (_("trusted certificate '%s' loaded\n"), storename); + 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); + } + } + } + + ksba_cert_release (cert); + pCertCloseStore (w32store, 0); + + if (DBG_X509) + log_debug ("number of certs loaded from store '%s': %u\n", + storename, count); + +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Load the trusted certificates provided by the system. */ +static gpg_error_t +load_certs_from_system (void) +{ +#ifdef HAVE_W32_SYSTEM + + load_certs_from_w32_store ("ROOT"); + load_certs_from_w32_store ("CA"); + + return 0; + +#else /*!HAVE_W32_SYSTEM*/ + + /* A list of certificate bundles to try. */ + static struct { + const char *name; + } table[] = { +#ifdef DEFAULT_TRUST_STORE_FILE + { DEFAULT_TRUST_STORE_FILE } +#else + { "/etc/ssl/ca-bundle.pem" }, + { "/etc/ssl/certs/ca-certificates.crt" }, + { "/etc/pki/tls/cert.pem" }, + { "/usr/local/share/certs/ca-root-nss.crt" }, + { "/etc/ssl/cert.pem" } +#endif /*!DEFAULT_TRUST_STORE_FILE*/ + }; + int idx; + gpg_error_t err = 0; + + for (idx=0; idx < DIM (table); idx++) + if (!gnupg_access (table[idx].name, F_OK)) + { + /* Take the first available bundle. */ + err = load_certs_from_file (table[idx].name, CERTTRUST_CLASS_SYSTEM, 0); + break; + } + + return err; +#endif /*!HAVE_W32_SYSTEM*/ +} + + +/* Initialize the certificate cache if not yet done. */ +void +cert_cache_init (strlist_t hkp_cacerts) +{ + char *fname; + strlist_t sl; + + if (initialization_done) + return; + init_cache_lock (); + acquire_cache_write_lock (); + + load_certs_from_system (); + + fname = make_filename_try (gnupg_sysconfdir (), "trusted-certs", NULL); + if (fname) + load_certs_from_dir (fname, CERTTRUST_CLASS_CONFIG); + xfree (fname); + + fname = make_filename_try (gnupg_sysconfdir (), "extra-certs", NULL); + if (fname) + load_certs_from_dir (fname, 0); + xfree (fname); + + /* Put the special pool certificate into our store. This is + * currently only used with ntbtls. For GnuTLS http_session_new + * unfortunately loads that certificate directly from the file. */ + fname = make_filename_try (gnupg_datadir (), + "sks-keyservers.netCA.pem", NULL); + if (fname) + load_certs_from_file (fname, CERTTRUST_CLASS_HKPSPOOL, 1); + xfree (fname); + + for (sl = hkp_cacerts; sl; sl = sl->next) + load_certs_from_file (sl->d, CERTTRUST_CLASS_HKP, 0); + + 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; + } + } + + http_register_cfg_ca (NULL); + + total_nonperm_certificates = 0; + any_cert_of_class = 0; + initialization_done = 0; + release_cache_lock (); +} + +/* Print some statistics to the log file. */ +void +cert_cache_print_stats (void) +{ + cert_item_t ci; + int idx; + unsigned int n_nonperm = 0; + unsigned int n_permanent = 0; + unsigned int n_trusted = 0; + unsigned int n_trustclass_system = 0; + unsigned int n_trustclass_config = 0; + unsigned int n_trustclass_hkp = 0; + unsigned int n_trustclass_hkpspool = 0; + + acquire_cache_read_lock (); + for (idx = 0; idx < 256; idx++) + for (ci=cert_cache[idx]; ci; ci = ci->next) + if (ci->cert) + { + if (ci->permanent) + n_permanent++; + else + n_nonperm++; + if (ci->trustclasses) + { + n_trusted++; + if ((ci->trustclasses & CERTTRUST_CLASS_SYSTEM)) + n_trustclass_system++; + if ((ci->trustclasses & CERTTRUST_CLASS_CONFIG)) + n_trustclass_config++; + if ((ci->trustclasses & CERTTRUST_CLASS_HKP)) + n_trustclass_hkp++; + if ((ci->trustclasses & CERTTRUST_CLASS_HKPSPOOL)) + n_trustclass_hkpspool++; + } + } + + release_cache_lock (); + + log_info (_("permanently loaded certificates: %u\n"), + n_permanent); + log_info (_(" runtime cached certificates: %u\n"), + n_nonperm); + log_info (_(" trusted certificates: %u (%u,%u,%u,%u)\n"), + n_trusted, + n_trustclass_system, + n_trustclass_config, + n_trustclass_hkp, + n_trustclass_hkpspool); +} + + +/* Return true if any cert of a class in MASK is permanently + * loaded. */ +int +cert_cache_any_in_class (unsigned int mask) +{ + return !!(any_cert_of_class & mask); +} + + +/* Put CERT into the certificate cache. */ +gpg_error_t +cache_cert (ksba_cert_t cert) +{ + gpg_error_t err; + + acquire_cache_write_lock (); + err = put_cert (cert, 0, 0, NULL); + release_cache_lock (); + if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) + log_info (_("certificate already cached\n")); + else if (!err) + log_info (_("certificate cached\n")); + else + log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); + return err; +} + + +/* Put CERT into the certificate cache and store the fingerprint of + the certificate into FPR_BUFFER. If the certificate is already in + the cache do not print a warning; just store the + fingerprint. FPR_BUFFER needs to be at least 20 bytes. */ +gpg_error_t +cache_cert_silent (ksba_cert_t cert, void *fpr_buffer) +{ + gpg_error_t err; + + acquire_cache_write_lock (); + err = put_cert (cert, 0, 0, fpr_buffer); + release_cache_lock (); + if (gpg_err_code (err) == GPG_ERR_DUP_VALUE) + err = 0; + if (err) + log_error (_("error caching certificate: %s\n"), gpg_strerror (err)); + return err; +} + + + +/* Return a certificate object for the given fingerprint. FPR is + expected to be a 20 byte binary SHA-1 fingerprint. If no matching + certificate is available in the cache NULL is returned. The caller + must release a returned certificate. Note that although we are + using reference counting the caller should not just compare the + pointers to check for identical certificates. */ +ksba_cert_t +get_cert_byfpr (const unsigned char *fpr) +{ + cert_item_t ci; + + acquire_cache_read_lock (); + for (ci=cert_cache[*fpr]; ci; ci = ci->next) + if (ci->cert && !memcmp (ci->fpr, fpr, 20)) + { + ksba_cert_ref (ci->cert); + release_cache_lock (); + return ci->cert; + } + + release_cache_lock (); + return NULL; +} + +/* Return a certificate object for the given fingerprint. STRING is + expected to be a SHA-1 fingerprint in standard hex notation with or + without colons. If no matching certificate is available in the + cache NULL is returned. The caller must release a returned + certificate. Note that although we are using reference counting + the caller should not just compare the pointers to check for + identical certificates. */ +ksba_cert_t +get_cert_byhexfpr (const char *string) +{ + unsigned char fpr[20]; + const char *s; + int i; + + if (strchr (string, ':')) + { + for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1);) + { + if (s[2] && s[2] != ':') + break; /* Invalid string. */ + fpr[i++] = xtoi_2 (s); + s += 2; + if (i!= 20 && *s == ':') + s++; + } + } + else + { + for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1); s+=2 ) + fpr[i++] = xtoi_2 (s); + } + if (i!=20 || *s) + { + log_error (_("invalid SHA1 fingerprint string '%s'\n"), string); + return NULL; + } + + return get_cert_byfpr (fpr); +} + + + +/* Return the certificate matching ISSUER_DN and SERIALNO. */ +ksba_cert_t +get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno) +{ + /* Simple and inefficient implementation. fixme! */ + cert_item_t ci; + int i; + + acquire_cache_read_lock (); + for (i=0; i < 256; i++) + { + for (ci=cert_cache[i]; ci; ci = ci->next) + if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn) + && !compare_serialno (ci->sn, serialno)) + { + ksba_cert_ref (ci->cert); + release_cache_lock (); + return ci->cert; + } + } + + release_cache_lock (); + return NULL; +} + + +/* Return the certificate matching ISSUER_DN. SEQ should initially be + set to 0 and bumped up to get the next issuer with that DN. */ +ksba_cert_t +get_cert_byissuer (const char *issuer_dn, unsigned int seq) +{ + /* Simple and very inefficient implementation and API. fixme! */ + cert_item_t ci; + int i; + + acquire_cache_read_lock (); + for (i=0; i < 256; i++) + { + for (ci=cert_cache[i]; ci; ci = ci->next) + if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn)) + if (!seq--) + { + ksba_cert_ref (ci->cert); + release_cache_lock (); + return ci->cert; + } + } + + release_cache_lock (); + return NULL; +} + + +/* Return the certificate matching SUBJECT_DN. SEQ should initially be + set to 0 and bumped up to get the next subject with that DN. */ +ksba_cert_t +get_cert_bysubject (const char *subject_dn, unsigned int seq) +{ + /* Simple and very inefficient implementation and API. fixme! */ + cert_item_t ci; + int i; + + if (!subject_dn) + return NULL; + + acquire_cache_read_lock (); + for (i=0; i < 256; i++) + { + for (ci=cert_cache[i]; ci; ci = ci->next) + if (ci->cert && ci->subject_dn + && !strcmp (ci->subject_dn, subject_dn)) + if (!seq--) + { + ksba_cert_ref (ci->cert); + release_cache_lock (); + return ci->cert; + } + } + + release_cache_lock (); + return NULL; +} + + + +/* Return a value describing the class of PATTERN. The offset of + the actual string to be used for the comparison is stored at + R_OFFSET. The offset of the serialnumer is stored at R_SN_OFFSET. */ +static enum pattern_class +classify_pattern (const char *pattern, size_t *r_offset, size_t *r_sn_offset) +{ + enum pattern_class result; + const char *s; + int hexprefix = 0; + int hexlength; + + *r_offset = *r_sn_offset = 0; + + /* Skip leading spaces. */ + for(s = pattern; *s && spacep (s); s++ ) + ; + + switch (*s) + { + case 0: /* Empty string is an error. */ + result = PATTERN_UNKNOWN; + break; + + case '.': /* An email address, compare from end. */ + result = PATTERN_UNKNOWN; /* Not implemented. */ + break; + + case '<': /* An email address. */ + result = PATTERN_EMAIL; + s++; + break; + + case '@': /* Part of an email address. */ + result = PATTERN_EMAIL_SUBSTR; + s++; + break; + + case '=': /* Exact compare. */ + result = PATTERN_UNKNOWN; /* Does not make sense for X.509. */ + break; + + case '*': /* Case insensitive substring search. */ + result = PATTERN_SUBSTR; + s++; + break; + + case '+': /* Compare individual words. */ + result = PATTERN_UNKNOWN; /* Not implemented. */ + break; + + case '/': /* Subject's DN. */ + s++; + if (!*s || spacep (s)) + result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ + else + result = PATTERN_SUBJECT; + break; + + case '#': /* Serial number or issuer DN. */ + { + const char *si; + + s++; + if ( *s == '/') + { + /* An issuer's DN is indicated by "#/" */ + s++; + if (!*s || spacep (s)) + result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */ + else + result = PATTERN_ISSUER; + } + else + { /* Serialnumber + optional issuer ID. */ + for (si=s; *si && *si != '/'; si++) + if (!strchr("01234567890abcdefABCDEF", *si)) + break; + if (*si && *si != '/') + result = PATTERN_UNKNOWN; /* Invalid digit in serial number. */ + else + { + *r_sn_offset = s - pattern; + if (!*si) + result = PATTERN_SERIALNO; + else + { + s = si+1; + if (!*s || spacep (s)) + result = PATTERN_UNKNOWN; /* No DN or prefixed + with a space. */ + else + result = PATTERN_SERIALNO_ISSUER; + } + } + } + } + break; + + case ':': /* Unified fingerprint. */ + { + const char *se, *si; + int i; + + se = strchr (++s, ':'); + if (!se) + result = PATTERN_UNKNOWN; + else + { + for (i=0, si=s; si < se; si++, i++ ) + if (!strchr("01234567890abcdefABCDEF", *si)) + break; + if ( si < se ) + result = PATTERN_UNKNOWN; /* Invalid digit. */ + else if (i == 32) + result = PATTERN_FINGERPRINT16; + else if (i == 40) + result = PATTERN_FINGERPRINT20; + else + result = PATTERN_UNKNOWN; /* Invalid length for a fingerprint. */ + } + } + break; + + case '&': /* Keygrip. */ + result = PATTERN_UNKNOWN; /* Not implemented. */ + break; + + default: + if (s[0] == '0' && s[1] == 'x') + { + hexprefix = 1; + s += 2; + } + + hexlength = strspn(s, "0123456789abcdefABCDEF"); + + /* Check if a hexadecimal number is terminated by EOS or blank. */ + if (hexlength && s[hexlength] && !spacep (s+hexlength)) + { + /* If the "0x" prefix is used a correct termination is required. */ + if (hexprefix) + { + result = PATTERN_UNKNOWN; + break; /* switch */ + } + hexlength = 0; /* Not a hex number. */ + } + + if (hexlength == 8 || (!hexprefix && hexlength == 9 && *s == '0')) + { + if (hexlength == 9) + s++; + result = PATTERN_SHORT_KEYID; + } + else if (hexlength == 16 || (!hexprefix && hexlength == 17 && *s == '0')) + { + if (hexlength == 17) + s++; + result = PATTERN_LONG_KEYID; + } + else if (hexlength == 32 || (!hexprefix && hexlength == 33 && *s == '0')) + { + if (hexlength == 33) + s++; + result = PATTERN_FINGERPRINT16; + } + else if (hexlength == 40 || (!hexprefix && hexlength == 41 && *s == '0')) + { + if (hexlength == 41) + s++; + result = PATTERN_FINGERPRINT20; + } + else if (!hexprefix) + { + /* The fingerprints used with X.509 are often delimited by + colons, so we try to single this case out. */ + result = PATTERN_UNKNOWN; + hexlength = strspn (s, ":0123456789abcdefABCDEF"); + if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength))) + { + int i, c; + + for (i=0; i < 20; i++, s += 3) + { + c = hextobyte(s); + if (c == -1 || (i < 19 && s[2] != ':')) + break; + } + if (i == 20) + result = PATTERN_FINGERPRINT20; + } + if (result == PATTERN_UNKNOWN) /* Default to substring match. */ + { + result = PATTERN_SUBSTR; + } + } + else /* A hex number with a prefix but with a wrong length. */ + result = PATTERN_UNKNOWN; + } + + if (result != PATTERN_UNKNOWN) + *r_offset = s - pattern; + return result; +} + + + +/* Given PATTERN, which is a string as used by GnuPG to specify a + certificate, return all matching certificates by calling the + supplied function RETFNC. */ +gpg_error_t +get_certs_bypattern (const char *pattern, + gpg_error_t (*retfnc)(void*,ksba_cert_t), + void *retfnc_data) +{ + gpg_error_t err = GPG_ERR_BUG; + enum pattern_class class; + size_t offset, sn_offset; + const char *hexserialno; + ksba_sexp_t serialno = NULL; + ksba_cert_t cert = NULL; + unsigned int seq; + + if (!pattern || !retfnc) + return gpg_error (GPG_ERR_INV_ARG); + + class = classify_pattern (pattern, &offset, &sn_offset); + hexserialno = pattern + sn_offset; + pattern += offset; + switch (class) + { + case PATTERN_UNKNOWN: + err = gpg_error (GPG_ERR_INV_NAME); + break; + + case PATTERN_FINGERPRINT20: + cert = get_cert_byhexfpr (pattern); + err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND); + break; + + case PATTERN_SERIALNO_ISSUER: + serialno = hexsn_to_sexp (hexserialno); + if (!serialno) + err = gpg_error_from_syserror (); + else + { + cert = get_cert_bysn (pattern, serialno); + err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND); + } + break; + + case PATTERN_ISSUER: + for (seq=0,err=0; !err && (cert = get_cert_byissuer (pattern, seq)); seq++) + { + err = retfnc (retfnc_data, cert); + ksba_cert_release (cert); + cert = NULL; + } + if (!err && !seq) + err = gpg_error (GPG_ERR_NOT_FOUND); + break; + + case PATTERN_SUBJECT: + for (seq=0,err=0; !err && (cert = get_cert_bysubject (pattern, seq));seq++) + { + err = retfnc (retfnc_data, cert); + ksba_cert_release (cert); + cert = NULL; + } + if (!err && !seq) + err = gpg_error (GPG_ERR_NOT_FOUND); + break; + + case PATTERN_EMAIL: + case PATTERN_EMAIL_SUBSTR: + case PATTERN_FINGERPRINT16: + case PATTERN_SHORT_KEYID: + case PATTERN_LONG_KEYID: + case PATTERN_SUBSTR: + case PATTERN_SERIALNO: + /* Not supported. */ + err = gpg_error (GPG_ERR_INV_NAME); + } + + + if (!err && cert) + err = retfnc (retfnc_data, cert); + ksba_cert_release (cert); + xfree (serialno); + return err; +} + + + + + +/* Return the certificate matching ISSUER_DN and SERIALNO; if it is + * not already in the cache, try to find it from other resources. */ +ksba_cert_t +find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno) +{ + gpg_error_t err; + ksba_cert_t cert; + cert_fetch_context_t context = NULL; + char *hexsn, *buf; + + /* First check whether it has already been cached. */ + cert = get_cert_bysn (issuer_dn, serialno); + if (cert) + return cert; + + /* Ask back to the service requester to return the certificate. + * This is because we can assume that he already used the + * certificate while checking for the CRL. */ + hexsn = serial_hex (serialno); + if (!hexsn) + { + log_error ("serial_hex() failed\n"); + return NULL; + } + buf = strconcat ("#", hexsn, "/", issuer_dn, NULL); + if (!buf) + { + log_error ("can't allocate enough memory: %s\n", strerror (errno)); + xfree (hexsn); + return NULL; + } + xfree (hexsn); + + cert = get_cert_local (ctrl, buf); + xfree (buf); + if (cert) + { + cache_cert (cert); + return cert; /* Done. */ + } + + if (DBG_LOOKUP) + log_debug ("find_cert_bysn: certificate not returned by caller" + " - doing lookup\n"); + + /* Retrieve the certificate from external resources. */ + while (!cert) + { + ksba_sexp_t sn; + char *issdn; + + if (!context) + { + err = ca_cert_fetch (ctrl, &context, issuer_dn); + if (err) + { + log_error (_("error fetching certificate by S/N: %s\n"), + gpg_strerror (err)); + break; + } + } + + err = fetch_next_ksba_cert (context, &cert); + if (err) + { + log_error (_("error fetching certificate by S/N: %s\n"), + gpg_strerror (err) ); + break; + } + + issdn = ksba_cert_get_issuer (cert, 0); + if (strcmp (issuer_dn, issdn)) + { + log_debug ("find_cert_bysn: Ooops: issuer DN does not match\n"); + ksba_cert_release (cert); + cert = NULL; + ksba_free (issdn); + break; + } + + sn = ksba_cert_get_serial (cert); + + if (DBG_LOOKUP) + { + log_debug (" considering certificate (#"); + dump_serial (sn); + log_printf ("/"); + dump_string (issdn); + log_printf (")\n"); + } + + if (!compare_serialno (serialno, sn)) + { + ksba_free (sn); + ksba_free (issdn); + cache_cert (cert); + if (DBG_LOOKUP) + log_debug (" found\n"); + break; /* Ready. */ + } + + ksba_free (sn); + ksba_free (issdn); + ksba_cert_release (cert); + cert = NULL; + } + + end_cert_fetch (context); + return cert; +} + + +/* Return the certificate matching SUBJECT_DN and (if not NULL) + * KEYID. If it is not already in the cache, try to find it from other + * resources. Note, that the external search does not work for user + * certificates because the LDAP lookup is on the caCertificate + * attribute. For our purposes this is just fine. */ +ksba_cert_t +find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid) +{ + gpg_error_t err; + int seq; + ksba_cert_t cert = NULL; + cert_fetch_context_t context = NULL; + ksba_sexp_t subj; + + /* If we have certificates from an OCSP request we first try to use + * them. This is because these certificates will really be the + * required ones and thus even in the case that they can't be + * uniquely located by the following code we can use them. This is + * for example required by Telesec certificates where a keyId is + * used but the issuer certificate comes without a subject keyId! */ + if (ctrl->ocsp_certs && subject_dn) + { + cert_item_t ci; + cert_ref_t cr; + int i; + + /* For efficiency reasons we won't use get_cert_bysubject here. */ + acquire_cache_read_lock (); + for (i=0; i < 256; i++) + for (ci=cert_cache[i]; ci; ci = ci->next) + if (ci->cert && ci->subject_dn + && !strcmp (ci->subject_dn, subject_dn)) + for (cr=ctrl->ocsp_certs; cr; cr = cr->next) + if (!memcmp (ci->fpr, cr->fpr, 20)) + { + ksba_cert_ref (ci->cert); + release_cache_lock (); + if (DBG_LOOKUP) + log_debug ("%s: certificate found in the cache" + " via ocsp_certs\n", __func__); + return ci->cert; /* We use this certificate. */ + } + release_cache_lock (); + if (DBG_LOOKUP) + log_debug ("find_cert_bysubject: certificate not in ocsp_certs\n"); + } + + /* Now 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); + if (DBG_LOOKUP) + log_debug ("%s: certificate found in the cache" + " via subject DN\n", __func__); + break; /* Found matching cert. */ + } + xfree (subj); + ksba_cert_release (cert); + } + if (cert) + return cert; /* Done. */ + + /* If we do not have a subject DN but have a keyid, try to locate it + * by keyid. */ + if (!subject_dn && keyid) + { + int i; + cert_item_t ci; + ksba_sexp_t ski; + + acquire_cache_read_lock (); + for (i=0; i < 256; i++) + for (ci=cert_cache[i]; ci; ci = ci->next) + if (ci->cert && !ksba_cert_get_subj_key_id (ci->cert, NULL, &ski)) + { + if (!cmp_simple_canon_sexp (keyid, ski)) + { + ksba_free (ski); + ksba_cert_ref (ci->cert); + release_cache_lock (); + if (DBG_LOOKUP) + log_debug ("%s: certificate found in the cache" + " via ski\n", __func__); + return ci->cert; + } + ksba_free (ski); + } + release_cache_lock (); + } + + 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 = strconcat ("/", subject_dn, NULL); + if (!buf) + { + log_error ("can't allocate enough memory: %s\n", strerror (errno)); + return NULL; + } + 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. TRUSTCLASSES are the bitwise ORed + * CERTTRUST_CLASS values to use for the check. */ +gpg_error_t +is_trusted_cert (ksba_cert_t cert, unsigned int trustclasses) +{ + 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->trustclasses & trustclasses)) + { + /* The certificate is trusted in one of the given + * TRUSTCLASSES. */ + 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; +} + + + +/* Read a list of certificates in PEM format from stream FP and store + * them on success at R_CERTLIST. On error NULL is stored at R_CERT + * list and an error code returned. Note that even on success an + * empty list of certificates can be returned (i.e. NULL stored at + * R_CERTLIST) iff the input stream has no certificates. */ +gpg_error_t +read_certlist_from_stream (certlist_t *r_certlist, estream_t fp) +{ + gpg_error_t err; + gnupg_ksba_io_t ioctx = NULL; + ksba_reader_t reader; + ksba_cert_t cert = NULL; + certlist_t certlist = NULL; + certlist_t cl, *cltail; + + *r_certlist = NULL; + + err = gnupg_ksba_create_reader (&ioctx, + (GNUPG_KSBA_IO_PEM | GNUPG_KSBA_IO_MULTIPEM), + fp, &reader); + if (err) + goto leave; + + /* Loop to read all certificates from the stream. */ + cltail = &certlist; + do + { + ksba_cert_release (cert); + cert = NULL; + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_read_der (cert, reader); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_EOF) + err = 0; + goto leave; + } + + /* Append the certificate to the list. We also store the + * fingerprint and check whether we have a cached certificate; + * in that case the cached certificate is put into the list to + * take advantage of a validation result which might be stored + * in the cached certificate. */ + cl = xtrycalloc (1, sizeof *cl); + if (!cl) + { + err = gpg_error_from_syserror (); + goto leave; + } + cert_compute_fpr (cert, cl->fpr); + cl->cert = get_cert_byfpr (cl->fpr); + if (!cl->cert) + { + cl->cert = cert; + cert = NULL; + } + *cltail = cl; + cltail = &cl->next; + ksba_reader_clear (reader, NULL, NULL); + } + while (!gnupg_ksba_reader_eof_seen (ioctx)); + + leave: + ksba_cert_release (cert); + gnupg_ksba_destroy_reader (ioctx); + if (err) + release_certlist (certlist); + else + *r_certlist = certlist; + + return err; +} + + +/* Release the certificate list CL. */ +void +release_certlist (certlist_t cl) +{ + while (cl) + { + certlist_t next = cl->next; + ksba_cert_release (cl->cert); + cl = next; + } +} diff --git a/dirmngr/certcache.h b/dirmngr/certcache.h new file mode 100644 index 0000000..8d64583 --- /dev/null +++ b/dirmngr/certcache.h @@ -0,0 +1,128 @@ +/* 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 + +/* The origin of the trusted root certificates. */ +enum { + CERTTRUST_CLASS_SYSTEM = 1, /* From the system's list of trusted certs. */ + CERTTRUST_CLASS_CONFIG = 2, /* From dirmngr's config files. */ + CERTTRUST_CLASS_HKP = 4, /* From --hkp-cacert */ + CERTTRUST_CLASS_HKPSPOOL= 8, /* The one and only from sks-keyservers */ +}; + + +/* First time initialization of the certificate cache. */ +void cert_cache_init (strlist_t hkp_cacerts); + +/* Deinitialize the certificate cache. */ +void cert_cache_deinit (int full); + +/* Print some statistics to the log file. */ +void cert_cache_print_stats (void); + +/* Return true if any cert of a class in MASK is permanently loaded. */ +int cert_cache_any_in_class (unsigned int mask); + +/* 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. TRUSTCLASSES are the bitwise ORed + * CERTTRUST_CLASS values to use for the check. */ +gpg_error_t is_trusted_cert (ksba_cert_t cert, unsigned trustclasses); + +/* 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); + + + +/* A simple list of certificates. */ +struct certlist_s +{ + struct certlist_s *next; + ksba_cert_t cert; + unsigned char fpr[20]; /* of the certificate. */ +}; +typedef struct certlist_s *certlist_t; + +gpg_error_t read_certlist_from_stream (certlist_t *r_certlist, estream_t fp); +void release_certlist (certlist_t cl); + + + +#endif /*CERTCACHE_H*/ diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c new file mode 100644 index 0000000..eee4b72 --- /dev/null +++ b/dirmngr/crlcache.c @@ -0,0 +1,2757 @@ +/* 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 <https://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 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 + +#include "dirmngr.h" +#include "validate.h" +#include "certcache.h" +#include "crlcache.h" +#include "crlfetch.h" +#include "misc.h" +#include "cdb.h" + +/* Change this whenever the format changes */ +#define DBDIR_D "crls.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 + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +static const char oidstr_crlNumber[] = "2.5.29.20"; +/* static const char oidstr_issuingDistributionPoint[] = "2.5.29.28"; */ +static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35"; + + +/* Definition of one cached item. */ +struct crl_cache_entry_s +{ + struct crl_cache_entry_s *next; + int deleted; /* True if marked for deletion. */ + int mark; /* Internally used by update_dir. */ + unsigned int lineno;/* A 0 indicates a new entry. */ + char *release_ptr; /* The actual allocated memory. */ + char *url; /* Points into RELEASE_PTR. */ + char *issuer; /* Ditto. */ + char *issuer_hash; /* Ditto. */ + char *dbfile_hash; /* MD5 sum of the cache file, points into RELEASE_PTR.*/ + int invalid; /* Can't use this CRL. */ + int user_trust_req; /* User supplied root certificate required. */ + char *check_trust_anchor; /* Malloced fingerprint. */ + ksba_isotime_t this_update; + ksba_isotime_t next_update; + ksba_isotime_t last_refresh; /* Use for the force_crl_refresh feature. */ + char *crl_number; + char *authority_issuer; + char *authority_serialno; + + struct cdb *cdb; /* The cache file handle or NULL if not open. */ + + unsigned int cdb_use_count; /* Current use count. */ + unsigned int cdb_lru_count; /* Used for LRU purposes. */ + int dbfile_checked; /* Set to true if the dbfile_hash value has + been checked one. */ +}; + + +/* Definition of the entire cache object. */ +struct crl_cache_s +{ + crl_cache_entry_t entries; +}; + +typedef struct crl_cache_s *crl_cache_t; + + +/* Prototypes. */ +static crl_cache_entry_t find_entry (crl_cache_entry_t first, + const char *issuer_hash); + + + +/* The currently loaded cache object. This is usually initialized + right at startup. */ +static crl_cache_t current_cache; + + + + + +/* Return the current cache object or bail out if it is has not yet + been initialized. */ +static crl_cache_t +get_current_cache (void) +{ + if (!current_cache) + log_fatal ("CRL cache has not yet been initialized\n"); + return current_cache; +} + + +/* + Create ae directory if it does not yet exists. Returns on + success, or -1 on error. + */ +static int +create_directory_if_needed (const char *name) +{ + gnupg_dir_t dir; + char *fname; + + fname = make_filename (opt.homedir_cache, name, NULL); + dir = gnupg_opendir (fname); + if (!dir) + { + log_info (_("creating directory '%s'\n"), fname); + if (gnupg_mkdir (fname, "-rwx")) + { + int save_errno = errno; + log_error (_("error creating directory '%s': %s\n"), + fname, strerror (errno)); + xfree (fname); + gpg_err_set_errno (save_errno); + return -1; + } + } + else + gnupg_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); + gnupg_dir_t dir; + gnupg_dirent_t 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 = gnupg_opendir (dname); + if (!dir) + { + log_error (_("error reading directory '%s': %s\n"), + dname, strerror (errno)); + xfree (dname); + return -1; + } + + while ((de = gnupg_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 = (!gnupg_stat (cdbname, &sbuf) && S_ISREG (sbuf.st_mode)); + + if (okay) + { + log_info (_("removing cache file '%s'\n"), cdbname); + if (gnupg_remove (cdbname)) + { + log_error ("failed to remove '%s': %s\n", + cdbname, strerror (errno)); + problem = -1; + } + } + else + log_info (_("not removing file '%s'\n"), cdbname); + xfree (cdbname); + } + } + xfree (dname); + gnupg_closedir (dir); + return problem; +} + + +/* Read the next line from the file FP and return the line in an + malloced buffer. Return NULL on error or EOF. There is no + limitation os the line length. The trailing linefeed has been + removed, the function will read the last line of a file, even if + that is not terminated by a LF. */ +static char * +next_line_from_file (estream_t fp, gpg_error_t *r_err) +{ + char buf[300]; + char *largebuf = NULL; + size_t buflen; + size_t len = 0; + unsigned char *p; + int c; + char *tmpbuf; + + *r_err = 0; + p = buf; + buflen = sizeof buf - 1; + while ((c=es_getc (fp)) != EOF && c != '\n') + { + if (len >= buflen) + { + if (!largebuf) + { + buflen += 1024; + largebuf = xtrymalloc ( buflen + 1 ); + if (!largebuf) + { + *r_err = gpg_error_from_syserror (); + return NULL; + } + memcpy (largebuf, buf, len); + } + else + { + buflen += 1024; + tmpbuf = xtryrealloc (largebuf, buflen + 1); + if (!tmpbuf) + { + *r_err = gpg_error_from_syserror (); + xfree (largebuf); + return NULL; + } + largebuf = tmpbuf; + } + p = largebuf; + } + p[len++] = c; + } + if (c == EOF && !len) + return NULL; + p[len] = 0; + + if (largebuf) + tmpbuf = xtryrealloc (largebuf, len+1); + else + tmpbuf = xtrystrdup (buf); + if (!tmpbuf) + { + *r_err = gpg_error_from_syserror (); + xfree (largebuf); + } + return tmpbuf; +} + + +/* Release one cache entry. */ +static void +release_one_cache_entry (crl_cache_entry_t entry) +{ + if (entry) + { + if (entry->cdb) + { + int fd = cdb_fileno (entry->cdb); + cdb_free (entry->cdb); + xfree (entry->cdb); + if (close (fd)) + log_error (_("error closing cache file: %s\n"), strerror(errno)); + } + xfree (entry->release_ptr); + xfree (entry->check_trust_anchor); + xfree (entry); + } +} + + +/* Release the CACHE object. */ +static void +release_cache (crl_cache_t cache) +{ + crl_cache_entry_t entry, entry2; + + if (!cache) + return; + + for (entry = cache->entries; entry; entry = entry2) + { + entry2 = entry->next; + release_one_cache_entry (entry); + } + cache->entries = NULL; + xfree (cache); +} + + +/* Open the dir file FNAME or create a new one if it does not yet + exist. */ +static estream_t +open_dir_file (const char *fname) +{ + estream_t fp; + + fp = es_fopen (fname, "r"); + if (!fp) + { + log_error (_("failed to open cache dir file '%s': %s\n"), + fname, strerror (errno)); + + /* Make sure that the directory exists, try to create if otherwise. */ + if (create_directory_if_needed (NULL) + || create_directory_if_needed (DBDIR_D)) + return NULL; + fp = es_fopen (fname, "w"); + if (!fp) + { + log_error (_("error creating new cache dir file '%s': %s\n"), + fname, strerror (errno)); + return NULL; + } + es_fprintf (fp, "v:%d:\n", DBDIRVERSION); + if (es_ferror (fp)) + { + log_error (_("error writing new cache dir file '%s': %s\n"), + fname, strerror (errno)); + es_fclose (fp); + return NULL; + } + if (es_fclose (fp)) + { + log_error (_("error closing new cache dir file '%s': %s\n"), + fname, strerror (errno)); + return NULL; + } + + log_info (_("new cache dir file '%s' created\n"), fname); + + fp = es_fopen (fname, "r"); + if (!fp) + { + log_error (_("failed to re-open cache dir file '%s': %s\n"), + fname, strerror (errno)); + return NULL; + } + } + + return fp; +} + +/* Helper for open_dir. */ +static gpg_error_t +check_dir_version (estream_t *fpadr, const char *fname, + unsigned int *lineno, + int cleanup_on_mismatch) +{ + char *line; + gpg_error_t lineerr = 0; + estream_t fp = *fpadr; + int created = 0; + + retry: + while ((line = next_line_from_file (fp, &lineerr))) + { + ++*lineno; + if (*line == 'v' && line[1] == ':') + break; + else if (*line != '#') + { + log_error (_("first record of '%s' is not the version\n"), fname); + xfree (line); + return gpg_error (GPG_ERR_CONFIGURATION); + } + xfree (line); + } + if (lineerr) + return lineerr; + + /* The !line catches the case of an empty DIR file. We handle this + the same as a non-matching version. */ + if (!line || strtol (line+2, NULL, 10) != DBDIRVERSION) + { + if (!created && cleanup_on_mismatch) + { + log_error (_("old version of cache directory - cleaning up\n")); + es_fclose (fp); + *fpadr = NULL; + if (!cleanup_cache_dir (1)) + { + *lineno = 0; + fp = *fpadr = open_dir_file (fname); + if (!fp) + { + xfree (line); + return gpg_error (GPG_ERR_CONFIGURATION); + } + created = 1; + goto retry; + } + } + log_error (_("old version of cache directory - giving up\n")); + xfree (line); + return gpg_error (GPG_ERR_CONFIGURATION); + } + xfree (line); + return 0; +} + + +/* Open the dir file and read in all available information. Store + that in a newly allocated cache object and return that if + everything worked out fine. Create the cache directory and the dir + if it does not yet exist. Remove all files in that directory if + the version does not match. */ +static gpg_error_t +open_dir (crl_cache_t *r_cache) +{ + crl_cache_t cache; + char *fname; + char *line = NULL; + gpg_error_t lineerr = 0; + estream_t fp; + crl_cache_entry_t entry, *entrytail; + unsigned int lineno; + gpg_error_t err = 0; + int anyerr = 0; + + cache = xtrycalloc (1, sizeof *cache); + if (!cache) + return gpg_error_from_syserror (); + + fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL); + + lineno = 0; + fp = open_dir_file (fname); + if (!fp) + { + err = gpg_error (GPG_ERR_CONFIGURATION); + goto leave; + } + + err = check_dir_version (&fp, fname, &lineno, 1); + if (err) + goto leave; + + + /* Read in all supported entries from the dir file. */ + cache->entries = NULL; + entrytail = &cache->entries; + xfree (line); + while ((line = next_line_from_file (fp, &lineerr))) + { + int fieldno; + char *p, *endp; + + lineno++; + if ( *line == 'c' || *line == 'u' || *line == 'i' ) + { + entry = xtrycalloc (1, sizeof *entry); + if (!entry) + { + err = gpg_error_from_syserror (); + goto leave; + } + entry->lineno = lineno; + entry->release_ptr = line; + if (*line == 'i') + { + entry->invalid = atoi (line+1); + if (entry->invalid < 1) + entry->invalid = 1; + } + else if (*line == 'u') + entry->user_trust_req = 1; + + for (fieldno=1, p = line; p; p = endp, fieldno++) + { + endp = strchr (p, ':'); + if (endp) + *endp++ = '\0'; + + switch (fieldno) + { + case 1: /* record type */ break; + case 2: entry->issuer_hash = p; break; + case 3: entry->issuer = unpercent_string (p); break; + case 4: entry->url = unpercent_string (p); break; + case 5: + strncpy (entry->this_update, p, 15); + entry->this_update[15] = 0; + break; + case 6: + strncpy (entry->next_update, p, 15); + entry->next_update[15] = 0; + break; + case 7: entry->dbfile_hash = p; break; + case 8: if (*p) entry->crl_number = p; break; + case 9: + if (*p) + entry->authority_issuer = unpercent_string (p); + break; + case 10: + if (*p) + entry->authority_serialno = unpercent_string (p); + break; + case 11: + if (*p) + entry->check_trust_anchor = xtrystrdup (p); + break; + default: + if (*p) + log_info (_("extra field detected in crl record of " + "'%s' line %u\n"), fname, lineno); + break; + } + } + + if (!entry->issuer_hash) + { + log_info (_("invalid line detected in '%s' line %u\n"), + fname, lineno); + xfree (entry); + entry = NULL; + } + else if (find_entry (cache->entries, entry->issuer_hash)) + { + /* Fixme: The duplicate checking used is not very + effective for large numbers of issuers. */ + log_info (_("duplicate entry detected in '%s' line %u\n"), + fname, lineno); + xfree (entry); + entry = NULL; + } + else + { + line = NULL; + *entrytail = entry; + entrytail = &entry->next; + } + } + else if (*line == '#') + ; + else + log_info (_("unsupported record type in '%s' line %u skipped\n"), + fname, lineno); + + if (line) + xfree (line); + } + if (lineerr) + { + err = lineerr; + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + if (es_ferror (fp)) + { + log_error (_("error reading '%s': %s\n"), fname, strerror (errno)); + err = gpg_error (GPG_ERR_CONFIGURATION); + goto leave; + } + + /* Now do some basic checks on the data. */ + for (entry = cache->entries; entry; entry = entry->next) + { + assert (entry->lineno); + if (strlen (entry->issuer_hash) != 40) + { + anyerr++; + log_error (_("invalid issuer hash in '%s' line %u\n"), + fname, entry->lineno); + } + else if ( !*entry->issuer ) + { + anyerr++; + log_error (_("no issuer DN in '%s' line %u\n"), + fname, entry->lineno); + } + else if ( check_isotime (entry->this_update) + || check_isotime (entry->next_update)) + { + anyerr++; + log_error (_("invalid timestamp in '%s' line %u\n"), + fname, entry->lineno); + } + + /* Checks not leading to an immediate fail. */ + if (strlen (entry->dbfile_hash) != 32) + log_info (_("WARNING: invalid cache file hash in '%s' line %u\n"), + fname, entry->lineno); + } + + if (anyerr) + { + log_error (_("detected errors in cache dir file\n")); + log_info (_("please check the reason and manually delete that file\n")); + err = gpg_error (GPG_ERR_CONFIGURATION); + } + + + leave: + es_fclose (fp); + xfree (line); + xfree (fname); + if (err) + { + release_cache (cache); + cache = NULL; + } + *r_cache = cache; + return err; +} + +static void +write_percented_string (const char *s, estream_t fp) +{ + for (; *s; s++) + if (*s == ':') + es_fputs ("%3A", fp); + else if (*s == '\n') + es_fputs ("%0A", fp); + else if (*s == '\r') + es_fputs ("%0D", fp); + else + es_putc (*s, fp); +} + + +static void +write_dir_line_crl (estream_t fp, crl_cache_entry_t e) +{ + if (e->invalid) + es_fprintf (fp, "i%d", e->invalid); + else if (e->user_trust_req) + es_putc ('u', fp); + else + es_putc ('c', fp); + es_putc (':', fp); + es_fputs (e->issuer_hash, fp); + es_putc (':', fp); + write_percented_string (e->issuer, fp); + es_putc (':', fp); + write_percented_string (e->url, fp); + es_putc (':', fp); + es_fwrite (e->this_update, 15, 1, fp); + es_putc (':', fp); + es_fwrite (e->next_update, 15, 1, fp); + es_putc (':', fp); + es_fputs (e->dbfile_hash, fp); + es_putc (':', fp); + if (e->crl_number) + es_fputs (e->crl_number, fp); + es_putc (':', fp); + if (e->authority_issuer) + write_percented_string (e->authority_issuer, fp); + es_putc (':', fp); + if (e->authority_serialno) + es_fputs (e->authority_serialno, fp); + es_putc (':', fp); + if (e->check_trust_anchor && e->user_trust_req) + es_fputs (e->check_trust_anchor, fp); + es_putc ('\n', fp); +} + + +/* Update the current dir file using the cache. */ +static gpg_error_t +update_dir (crl_cache_t cache) +{ + char *fname = NULL; + char *tmpfname = NULL; + char *line = NULL; + gpg_error_t lineerr = 0; + estream_t fp; + estream_t fpout = NULL; + crl_cache_entry_t e; + unsigned int lineno; + gpg_error_t err = 0; + + fname = make_filename (opt.homedir_cache, DBDIR_D, DBDIRFILE, NULL); + + /* Fixme: Take an update file lock here. */ + + for (e= cache->entries; e; e = e->next) + e->mark = 1; + + lineno = 0; + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_errno (errno); + log_error (_("failed to open cache dir file '%s': %s\n"), + fname, strerror (errno)); + goto leave; + } + err = check_dir_version (&fp, fname, &lineno, 0); + if (err) + goto leave; + es_rewind (fp); + lineno = 0; + + /* Create a temporary DIR file. */ + { + char *tmpbuf, *p; + const char *nodename; +#ifndef HAVE_W32_SYSTEM + struct utsname utsbuf; +#endif + +#ifdef HAVE_W32_SYSTEM + nodename = "unknown"; +#else + if (uname (&utsbuf)) + nodename = "unknown"; + else + nodename = utsbuf.nodename; +#endif + + gpgrt_asprintf (&tmpbuf, "DIR-tmp-%s-%u-%p.txt.tmp", + nodename, (unsigned int)getpid (), &tmpbuf); + if (!tmpbuf) + { + err = gpg_error_from_errno (errno); + log_error (_("failed to create temporary cache dir file '%s': %s\n"), + tmpfname, strerror (errno)); + goto leave; + } + for (p=tmpbuf; *p; p++) + if (*p == '/') + *p = '.'; + tmpfname = make_filename (opt.homedir_cache, DBDIR_D, tmpbuf, NULL); + xfree (tmpbuf); + } + fpout = es_fopen (tmpfname, "w"); + if (!fpout) + { + err = gpg_error_from_errno (errno); + log_error (_("failed to create temporary cache dir file '%s': %s\n"), + tmpfname, strerror (errno)); + goto leave; + } + + while ((line = next_line_from_file (fp, &lineerr))) + { + lineno++; + if (*line == 'c' || *line == 'u' || *line == 'i') + { + /* Extract the issuer hash field. */ + char *fieldp, *endp; + + fieldp = strchr (line, ':'); + endp = fieldp? strchr (++fieldp, ':') : NULL; + if (endp) + { + /* There should be no percent within the issuer hash + field, thus we can compare it pretty easily. */ + *endp = 0; + e = find_entry ( cache->entries, fieldp); + *endp = ':'; /* Restore original line. */ + if (e && e->deleted) + { + /* Marked for deletion, so don't write it. */ + e->mark = 0; + } + else if (e) + { + /* Yep, this is valid entry we know about; write it out */ + write_dir_line_crl (fpout, e); + e->mark = 0; + } + else + { /* We ignore entries we don't have in our cache + because they may have been added in the meantime + by other instances of dirmngr. */ + es_fprintf (fpout, "# Next line added by " + "another process; our pid is %lu\n", + (unsigned long)getpid ()); + es_fputs (line, fpout); + es_putc ('\n', fpout); + } + } + else + { + es_fputs ("# Invalid line detected: ", fpout); + es_fputs (line, fpout); + es_putc ('\n', fpout); + } + } + else + { + /* Write out all non CRL lines as they are. */ + es_fputs (line, fpout); + es_putc ('\n', fpout); + } + + xfree (line); + } + if (!es_ferror (fp) && !es_ferror (fpout) && !lineerr) + { + /* Write out the remaining entries. */ + for (e= cache->entries; e; e = e->next) + if (e->mark) + { + if (!e->deleted) + write_dir_line_crl (fpout, e); + e->mark = 0; + } + } + if (lineerr) + { + err = lineerr; + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + goto leave; + } + if (es_ferror (fp)) + { + err = gpg_error_from_errno (errno); + log_error (_("error reading '%s': %s\n"), fname, strerror (errno)); + } + if (es_ferror (fpout)) + { + err = gpg_error_from_errno (errno); + log_error (_("error writing '%s': %s\n"), tmpfname, strerror (errno)); + } + if (err) + goto leave; + + /* Rename the files. */ + es_fclose (fp); + fp = NULL; + if (es_fclose (fpout)) + { + err = gpg_error_from_errno (errno); + log_error (_("error closing '%s': %s\n"), tmpfname, strerror (errno)); + goto leave; + } + fpout = NULL; + +#ifdef HAVE_W32_SYSTEM + /* No atomic mv on W32 systems. */ + gnupg_remove (fname); +#endif + if (rename (tmpfname, fname)) + { + err = gpg_error_from_errno (errno); + log_error (_("error renaming '%s' to '%s': %s\n"), + tmpfname, fname, strerror (errno)); + goto leave; + } + + leave: + /* Fixme: Relinquish update lock. */ + xfree (line); + es_fclose (fp); + xfree (fname); + if (fpout) + { + es_fclose (fpout); + if (err && tmpfname) + gnupg_remove (tmpfname); + } + xfree (tmpfname); + return err; +} + + + + +/* Create the filename for the cache file from the 40 byte ISSUER_HASH + string. Caller must release the return string. */ +static char * +make_db_file_name (const char *issuer_hash) +{ + char bname[50]; + + assert (strlen (issuer_hash) == 40); + memcpy (bname, "crl-", 4); + memcpy (bname + 4, issuer_hash, 40); + strcpy (bname + 44, ".db"); + return make_filename (opt.homedir_cache, DBDIR_D, bname, NULL); +} + + +/* Hash the file FNAME and return the MD5 digest in MD5BUFFER. The + caller must allocate MD%buffer wityh at least 16 bytes. Returns 0 + on success. */ +static int +hash_dbfile (const char *fname, unsigned char *md5buffer) +{ + estream_t fp; + char *buffer; + size_t n; + gcry_md_hd_t md5; + gpg_error_t err; + + buffer = xtrymalloc (65536); + fp = buffer? es_fopen (fname, "rb") : NULL; + if (!fp) + { + log_error (_("can't hash '%s': %s\n"), fname, strerror (errno)); + xfree (buffer); + return -1; + } + + err = gcry_md_open (&md5, GCRY_MD_MD5, 0); + if (err) + { + log_error (_("error setting up MD5 hash context: %s\n"), + gpg_strerror (err)); + xfree (buffer); + es_fclose (fp); + return -1; + } + + /* We better hash some information about the cache file layout in. */ + sprintf (buffer, "%.100s/%.100s:%d", DBDIR_D, DBDIRFILE, DBDIRVERSION); + gcry_md_write (md5, buffer, strlen (buffer)); + + for (;;) + { + n = es_fread (buffer, 1, 65536, fp); + if (n < 65536 && es_ferror (fp)) + { + log_error (_("error hashing '%s': %s\n"), fname, strerror (errno)); + xfree (buffer); + es_fclose (fp); + gcry_md_close (md5); + return -1; + } + if (!n) + break; + gcry_md_write (md5, buffer, n); + } + es_fclose (fp); + xfree (buffer); + gcry_md_final (md5); + + memcpy (md5buffer, gcry_md_read (md5, GCRY_MD_MD5), 16); + gcry_md_close (md5); + return 0; +} + +/* Compare the file FNAME against the dexified MD5 hash MD5HASH and + return 0 if they match. */ +static int +check_dbfile (const char *fname, const char *md5hexvalue) +{ + unsigned char buffer1[16], buffer2[16]; + + if (strlen (md5hexvalue) != 32) + { + log_error (_("invalid formatted checksum for '%s'\n"), fname); + return -1; + } + unhexify (buffer1, md5hexvalue); + + if (hash_dbfile (fname, buffer2)) + return -1; + + return memcmp (buffer1, buffer2, 16); +} + + +/* Open the cache file for ENTRY. This function implements a caching + strategy and might close unused cache files. It is required to use + unlock_db_file after using the file. */ +static struct cdb * +lock_db_file (crl_cache_t cache, crl_cache_entry_t entry) +{ + char *fname; + int fd; + int open_count; + crl_cache_entry_t e; + + if (entry->cdb) + { + entry->cdb_use_count++; + return entry->cdb; + } + + for (open_count = 0, e = cache->entries; e; e = e->next) + { + if (e->cdb) + open_count++; +/* log_debug ("CACHE: cdb=%p use_count=%u lru_count=%u\n", */ +/* e->cdb,e->cdb_use_count,e->cdb_lru_count); */ + } + + /* If there are too many file open, find the least recent used DB + file and close it. Note that for Pth thread safeness we need to + use a loop here. */ + while (open_count >= MAX_OPEN_DB_FILES ) + { + crl_cache_entry_t last_e = NULL; + unsigned int last_lru = (unsigned int)(-1); + + for (e = cache->entries; e; e = e->next) + if (e->cdb && !e->cdb_use_count && e->cdb_lru_count < last_lru) + { + last_lru = e->cdb_lru_count; + last_e = e; + } + if (!last_e) + { + log_error (_("too many open cache files; can't open anymore\n")); + return NULL; + } + +/* log_debug ("CACHE: closing file at cdb=%p\n", last_e->cdb); */ + + fd = cdb_fileno (last_e->cdb); + cdb_free (last_e->cdb); + xfree (last_e->cdb); + last_e->cdb = NULL; + if (close (fd)) + log_error (_("error closing cache file: %s\n"), strerror(errno)); + open_count--; + } + + + fname = make_db_file_name (entry->issuer_hash); + if (opt.verbose) + log_info (_("opening cache file '%s'\n"), fname ); + + if (!entry->dbfile_checked) + { + if (!check_dbfile (fname, entry->dbfile_hash)) + entry->dbfile_checked = 1; + /* Note, in case of an error we don't print an error here but + let require the caller to do that check. */ + } + + entry->cdb = xtrycalloc (1, sizeof *entry->cdb); + if (!entry->cdb) + { + xfree (fname); + return NULL; + } + fd = gnupg_open (fname, O_RDONLY | O_BINARY, 0); + if (fd == -1) + { + log_error (_("error opening cache file '%s': %s\n"), + fname, strerror (errno)); + xfree (entry->cdb); + entry->cdb = NULL; + xfree (fname); + return NULL; + } + if (cdb_init (entry->cdb, fd)) + { + log_error (_("error initializing cache file '%s' for reading: %s\n"), + fname, strerror (errno)); + xfree (entry->cdb); + entry->cdb = NULL; + close (fd); + xfree (fname); + return NULL; + } + xfree (fname); + + entry->cdb_use_count = 1; + entry->cdb_lru_count = 0; + + return entry->cdb; +} + +/* Unlock a cache file, so that it can be reused. */ +static void +unlock_db_file (crl_cache_t cache, crl_cache_entry_t entry) +{ + if (!entry->cdb) + log_error (_("calling unlock_db_file on a closed file\n")); + else if (!entry->cdb_use_count) + log_error (_("calling unlock_db_file on an unlocked file\n")); + else + { + entry->cdb_use_count--; + entry->cdb_lru_count++; + } + + /* If the entry was marked for deletion in the meantime do it now. + We do this for the sake of Pth thread safeness. */ + if (!entry->cdb_use_count && entry->deleted) + { + crl_cache_entry_t eprev, enext; + + enext = entry->next; + for (eprev = cache->entries; + eprev && eprev->next != entry; eprev = eprev->next) + ; + assert (eprev); + if (eprev == cache->entries) + cache->entries = enext; + else + eprev->next = enext; + /* FIXME: Do we leak ENTRY? */ + } +} + + +/* Find ISSUER_HASH in our cache FIRST. This may be used to enumerate + the linked list we use to keep the CRLs of an issuer. */ +static crl_cache_entry_t +find_entry (crl_cache_entry_t first, const char *issuer_hash) +{ + while (first && (first->deleted || strcmp (issuer_hash, first->issuer_hash))) + first = first->next; + return first; +} + + +/* Create a new CRL cache. This function 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 and memory. Return 0 on success.*/ +int +crl_cache_flush (void) +{ + int rc; + + crl_cache_deinit (); + rc = cleanup_cache_dir (0)? -1 : 0; + crl_cache_init (); + + 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_printf ("0x"); + log_printhex (sn, snlen, ""); + } + else if (opt.verbose) + { + unsigned char record[16]; + char *tmp = hexify_data (sn, snlen, 1); + + 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, 1); + 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); + break; + 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; +} + + +/* Return the hash algorithm's algo id from its name given in the + * non-null termnated string in (buffer,buflen). Returns 0 on failure + * or if the algo is not known. */ +static int +hash_algo_from_buffer (const void *buffer, size_t buflen) +{ + char *string; + int algo; + + string = xtrymalloc (buflen + 1); + if (!string) + { + log_error (_("out of core\n")); + return 0; + } + memcpy (string, buffer, buflen); + string[buflen] = 0; + algo = gcry_md_map_name (string); + if (!algo) + log_error ("unknown digest algorithm '%s' used in certificate\n", string); + xfree (string); + return algo; +} + + +/* Return an unsigned integer from the non-null termnated string + * (buffer,buflen). Returns 0 on failure. */ +static unsigned int +uint_from_buffer (const void *buffer, size_t buflen) +{ + char *string; + unsigned int val; + + string = xtrymalloc (buflen + 1); + if (!string) + { + log_error (_("out of core\n")); + return 0; + } + memcpy (string, buffer, buflen); + string[buflen] = 0; + val = strtoul (string, NULL, 10); + xfree (string); + return val; +} + + +/* 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, int *use_pss) +{ + gpg_error_t err; + const char *algoid; + + *use_pss = 0; + algoid = ksba_crl_get_digest_algo (crl); + if (algoid && !strcmp (algoid, "1.2.840.113549.1.1.10")) + { + /* Parse rsaPSS parameter. */ + gcry_buffer_t ioarray[1] = { {0} }; + ksba_sexp_t pssparam; + size_t n; + gcry_sexp_t psssexp; + + pssparam = ksba_crl_get_sig_val (crl); + n = gcry_sexp_canon_len (pssparam, 0, NULL, NULL); + if (!n) + { + ksba_free (pssparam); + log_error (_("got an invalid S-expression from libksba\n")); + return gpg_error (GPG_ERR_INV_SEXP); + } + err = gcry_sexp_sscan (&psssexp, NULL, pssparam, n); + ksba_free (pssparam); + if (err) + { + log_error (_("converting S-expression failed: %s\n"), + gcry_strerror (err)); + return err; + } + + err = gcry_sexp_extract_param (psssexp, "sig-val", + "&'hash-algo'", ioarray, NULL); + gcry_sexp_release (psssexp); + if (err) + { + log_error ("extracting params from PSS failed: %s\n", + gpg_strerror (err)); + return err; + } + *algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len); + xfree (ioarray[0].data); + *use_pss = 1; + } + else + *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 takes ownership of MD. */ +static gpg_error_t +finish_sig_check (ksba_crl_t crl, gcry_md_hd_t md, int algo, + ksba_cert_t issuer_cert, int use_pss) +{ + gpg_error_t err; + ksba_sexp_t sigval = NULL, pubkey = NULL; + size_t n; + gcry_sexp_t s_sig = NULL, s_hash = NULL, s_pkey = NULL; + unsigned int saltlen = 0; /* (used only with use_pss) */ + + /* 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; + } + + if (use_pss) + { + /* Parse rsaPSS parameter which we should find in S_SIG. */ + gcry_buffer_t ioarray[2] = { {0}, {0} }; + ksba_sexp_t pssparam; + gcry_sexp_t psssexp; + int hashalgo; + + pssparam = ksba_crl_get_sig_val (crl); + n = gcry_sexp_canon_len (pssparam, 0, NULL, NULL); + if (!n) + { + ksba_free (pssparam); + log_error (_("got an invalid S-expression from libksba\n")); + err = gpg_error (GPG_ERR_INV_SEXP); + goto leave; + } + err = gcry_sexp_sscan (&psssexp, NULL, pssparam, n); + ksba_free (pssparam); + if (err) + { + log_error (_("converting S-expression failed: %s\n"), + gcry_strerror (err)); + goto leave; + } + + err = gcry_sexp_extract_param (psssexp, "sig-val", + "&'hash-algo''salt-length'", + ioarray+0, ioarray+1, NULL); + gcry_sexp_release (psssexp); + if (err) + { + log_error ("extracting params from PSS failed: %s\n", + gpg_strerror (err)); + goto leave; + } + hashalgo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len); + saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len); + xfree (ioarray[0].data); + xfree (ioarray[1].data); + if (hashalgo != algo) + { + log_error ("hash algo mismatch: %d announced but %d used\n", + algo, hashalgo); + return gpg_error (GPG_ERR_INV_CRL); + } + /* Add some restrictions; see ../sm/certcheck.c for details. */ + switch (algo) + { + case GCRY_MD_SHA1: + case GCRY_MD_SHA256: + case GCRY_MD_SHA384: + case GCRY_MD_SHA512: + case GCRY_MD_SHA3_256: + case GCRY_MD_SHA3_384: + case GCRY_MD_SHA3_512: + break; + default: + log_error ("PSS hash algorithm '%s' rejected\n", + gcry_md_algo_name (algo)); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + + if (gcry_md_get_algo_dlen (algo) != saltlen) + { + log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n", + gcry_md_algo_name (algo), saltlen); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + } + + + /* 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. */ + if (use_pss) + { + err = gcry_sexp_build (&s_hash, NULL, + "(data (flags pss)" + "(hash %s %b)" + "(salt-length %u))", + hash_algo_to_string (algo), + (int)gcry_md_get_algo_dlen (algo), + gcry_md_read (md, algo), + saltlen); + } + else + { + err = gcry_sexp_build (&s_hash, NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_algo_to_string (algo), + (int)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. Takes ownership of MD if MD is not NULL. */ +static void +abort_sig_check (ksba_crl_t crl, gcry_md_hd_t md) +{ + (void)crl; + if (md) + 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; + int use_pss = 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: + { + err = start_sig_check (crl, &md, &algo, &use_pss); + if (err) + 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, use_pss); + md = NULL; /* Closed. */ + if (err) + { + log_error (_("CRL signature verification failed: %s\n"), + gpg_strerror (err)); + goto failure; + } + + err = validate_cert_chain (ctrl, crlissuer_cert, NULL, + (VALIDATE_FLAG_TRUST_CONFIG + | VALIDATE_FLAG_CRL + | VALIDATE_FLAG_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: + abort_sig_check (crl, md); + ksba_cert_release (crlissuer_cert); + return err; +} + + + +/* Return the crlNumber extension as an allocated hex string or NULL + if there is none. */ +static char * +get_crl_number (ksba_crl_t crl) +{ + gpg_error_t err; + ksba_sexp_t number; + char *string; + + err = ksba_crl_get_crl_number (crl, &number); + if (err) + return NULL; + string = serial_hex (number); + ksba_free (number); + return string; +} + + +/* Return the authorityKeyIdentifier or NULL if it is not available. + The issuer name may consists of several parts - they are delimted by + 0x01. */ +static char * +get_auth_key_id (ksba_crl_t crl, char **serialno) +{ + gpg_error_t err; + ksba_name_t name; + ksba_sexp_t sn; + int idx; + const char *s; + char *string; + size_t length; + + *serialno = NULL; + err = ksba_crl_get_auth_key_id (crl, NULL, &name, &sn); + if (err) + return NULL; + *serialno = serial_hex (sn); + ksba_free (sn); + + if (!name) + return xstrdup (""); + + length = 0; + for (idx=0; (s = ksba_name_enum (name, idx)); idx++) + { + char *p = ksba_name_get_uri (name, idx); + length += strlen (p?p:s) + 1; + xfree (p); + } + string = xtrymalloc (length+1); + if (string) + { + *string = 0; + for (idx=0; (s = ksba_name_enum (name, idx)); idx++) + { + char *p = ksba_name_get_uri (name, idx); + if (*string) + strcat (string, "\x01"); + strcat (string, p?p:s); + xfree (p); + } + } + ksba_name_release (name); + return string; +} + + + +/* Insert the CRL retrieved using URL into the cache specified by + CACHE. The CRL itself will be read from the stream FP and is + expected in binary format. + + Called by: + crl_cache_load + cmd_loadcrl + --load-crl + crl_cache_reload_crl + cmd_isvalid + cmd_checkcrl + cmd_loadcrl + --fetch-crl + + */ +gpg_error_t +crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader) +{ + crl_cache_t cache = get_current_cache (); + gpg_error_t err, err2; + ksba_crl_t crl; + char *fname = NULL; + char *newfname = NULL; + struct cdb_make cdb; + int fd_cdb = -1; + char *issuer = NULL; + char *issuer_hash = NULL; + ksba_isotime_t thisupdate, nextupdate; + crl_cache_entry_t entry = NULL; + crl_cache_entry_t e; + gnupg_isotime_t current_time; + char *checksum = NULL; + int invalidate_crl = 0; + int idx; + const char *oid; + int critical; + char *trust_anchor = NULL; + + /* FIXME: We should acquire a mutex for the URL, so that we don't + simultaneously enter the same CRL twice. However this needs to be + interweaved with the checking function.*/ + + err2 = 0; + + err = ksba_crl_new (&crl); + if (err) + { + log_error (_("ksba_crl_new failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + err = ksba_crl_set_reader (crl, reader); + if ( err ) + { + log_error (_("ksba_crl_set_reader failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + /* Create a temporary cache file to load the CRL into. */ + { + char *tmpfname, *p; + const char *nodename; +#ifndef HAVE_W32_SYSTEM + struct utsname utsbuf; +#endif + +#ifdef HAVE_W32_SYSTEM + nodename = "unknown"; +#else + if (uname (&utsbuf)) + nodename = "unknown"; + else + nodename = utsbuf.nodename; +#endif + + gpgrt_asprintf (&tmpfname, "crl-tmp-%s-%u-%p.db.tmp", + nodename, (unsigned int)getpid (), &tmpfname); + if (!tmpfname) + { + err = gpg_error_from_syserror (); + goto leave; + } + for (p=tmpfname; *p; p++) + if (*p == '/') + *p = '.'; + fname = make_filename (opt.homedir_cache, DBDIR_D, tmpfname, NULL); + xfree (tmpfname); + if (!gnupg_remove (fname)) + log_info (_("removed stale temporary cache file '%s'\n"), fname); + else if (errno != ENOENT) + { + err = gpg_error_from_syserror (); + log_error (_("problem removing stale temporary cache file '%s': %s\n"), + fname, gpg_strerror (err)); + goto leave; + } + } + + fd_cdb = gnupg_open (fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 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, 0); + } + + + /* Check whether that new CRL is still not expired. */ + gnupg_get_isotime (current_time); + if (strcmp (nextupdate, current_time) < 0 ) + { + if (opt.force) + log_info (_("WARNING: new CRL still too old; it expired on %s " + "- loading anyway\n"), nextupdate); + else + { + log_error (_("new CRL still too old; it expired on %s\n"), + nextupdate); + if (!err2) + err2 = gpg_error (GPG_ERR_CRL_TOO_OLD); + invalidate_crl |= 1; + } + } + + /* Check for unknown critical extensions. */ + for (idx=0; !(err=ksba_crl_get_extension (crl, idx, &oid, &critical, + NULL, NULL)); idx++) + { + if (!critical + || !strcmp (oid, oidstr_authorityKeyIdentifier) + || !strcmp (oid, oidstr_crlNumber) ) + continue; + log_error (_("unknown critical CRL extension %s\n"), oid); + if (!err2) + err2 = gpg_error (GPG_ERR_INV_CRL); + invalidate_crl |= 2; + } + if (gpg_err_code (err) == GPG_ERR_EOF + || gpg_err_code (err) == GPG_ERR_NO_DATA ) + err = 0; + if (err) + { + log_error (_("error reading CRL extensions: %s\n"), gpg_strerror (err)); + err = gpg_error (GPG_ERR_INV_CRL); + } + + + /* Create an hex encoded SHA-1 hash of the issuer DN to be + used as the key for the cache. */ + issuer_hash = hashify_data (issuer, strlen (issuer)); + + /* Create an ENTRY. */ + entry = xtrycalloc (1, sizeof *entry); + if (!entry) + { + err = gpg_error_from_syserror (); + goto leave; + } + entry->release_ptr = xtrymalloc (strlen (issuer_hash) + 1 + + strlen (issuer) + 1 + + strlen (url) + 1 + + strlen (checksum) + 1); + if (!entry->release_ptr) + { + err = gpg_error_from_syserror (); + xfree (entry); + entry = NULL; + goto leave; + } + entry->issuer_hash = entry->release_ptr; + entry->issuer = stpcpy (entry->issuer_hash, issuer_hash) + 1; + entry->url = stpcpy (entry->issuer, issuer) + 1; + entry->dbfile_hash = stpcpy (entry->url, url) + 1; + strcpy (entry->dbfile_hash, checksum); + gnupg_copy_time (entry->this_update, thisupdate); + gnupg_copy_time (entry->next_update, nextupdate); + gnupg_copy_time (entry->last_refresh, current_time); + entry->crl_number = get_crl_number (crl); + entry->authority_issuer = get_auth_key_id (crl, &entry->authority_serialno); + entry->invalid = invalidate_crl; + entry->user_trust_req = !!trust_anchor; + entry->check_trust_anchor = trust_anchor; + trust_anchor = NULL; + + /* Check whether we already have an entry for this issuer and mark + it as deleted. We better use a loop, just in case duplicates got + somehow into the list. */ + for (e = cache->entries; (e=find_entry (e, entry->issuer_hash)); e = e->next) + e->deleted = 1; + + /* Rename the temporary DB to the real name. */ + newfname = make_db_file_name (entry->issuer_hash); + if (opt.verbose) + log_info (_("creating cache file '%s'\n"), newfname); + + /* Just in case close unused matching files. Actually we need this + only under Windows but saving file descriptors is never bad. */ + { + int any; + do + { + any = 0; + for (e = cache->entries; e; e = e->next) + if (!e->cdb_use_count && e->cdb + && !strcmp (e->issuer_hash, entry->issuer_hash)) + { + int fd = cdb_fileno (e->cdb); + cdb_free (e->cdb); + xfree (e->cdb); + e->cdb = NULL; + if (close (fd)) + log_error (_("error closing cache file: %s\n"), + strerror(errno)); + any = 1; + break; + } + } + while (any); + } +#ifdef HAVE_W32_SYSTEM + gnupg_remove (newfname); +#endif + if (rename (fname, newfname)) + { + err = gpg_error_from_syserror (); + log_error (_("problem renaming '%s' to '%s': %s\n"), + fname, newfname, gpg_strerror (err)); + goto leave; + } + xfree (fname); fname = NULL; /*(let the cleanup code not try to remove it)*/ + + /* Link the new entry in. */ + entry->next = cache->entries; + cache->entries = entry; + entry = NULL; + + err = update_dir (cache); + if (err) + { + log_error (_("updating the DIR file failed - " + "cache entry will get lost with the next program start\n")); + err = 0; /* Keep on running. */ + } + + + leave: + release_one_cache_entry (entry); + if (fd_cdb != -1) + close (fd_cdb); + if (fname) + { + gnupg_remove (fname); + xfree (fname); + } + xfree (newfname); + ksba_crl_release (crl); + xfree (issuer); + xfree (issuer_hash); + xfree (checksum); + xfree (trust_anchor); + return err ? err : err2; +} + + +/* Print one cached entry E in a human readable format to stream + FP. Return 0 on success. */ +static gpg_error_t +list_one_crl_entry (crl_cache_t cache, crl_cache_entry_t e, estream_t fp) +{ + struct cdb_find cdbfp; + struct cdb *cdb; + int rc; + int warn = 0; + const unsigned char *s; + + es_fputs ("--------------------------------------------------------\n", fp ); + es_fprintf (fp, _("Begin CRL dump (retrieved via %s)\n"), e->url ); + es_fprintf (fp, " Issuer:\t%s\n", e->issuer ); + es_fprintf (fp, " Issuer Hash:\t%s\n", e->issuer_hash ); + es_fprintf (fp, " This Update:\t%s\n", e->this_update ); + es_fprintf (fp, " Next Update:\t%s\n", e->next_update ); + es_fprintf (fp, " CRL Number :\t%s\n", e->crl_number? e->crl_number: "none"); + es_fprintf (fp, " AuthKeyId :\t%s\n", + e->authority_serialno? e->authority_serialno:"none"); + if (e->authority_serialno && e->authority_issuer) + { + es_fputs (" \t", fp); + for (s=e->authority_issuer; *s; s++) + if (*s == '\x01') + es_fputs ("\n \t", fp); + else + es_putc (*s, fp); + es_putc ('\n', fp); + } + es_fprintf (fp, " Trust Check:\t%s\n", + !e->user_trust_req? "[system]" : + e->check_trust_anchor? e->check_trust_anchor:"[missing]"); + + if ((e->invalid & 1)) + es_fprintf (fp, _(" ERROR: The CRL will not be used " + "because it was still too old after an update!\n")); + if ((e->invalid & 2)) + es_fprintf (fp, _(" ERROR: The CRL will not be used " + "due to an unknown critical extension!\n")); + if ((e->invalid & ~3)) + es_fprintf (fp, _(" ERROR: The CRL will not be used\n")); + + cdb = lock_db_file (cache, e); + if (!cdb) + return gpg_error (GPG_ERR_GENERAL); + + if (!e->dbfile_checked) + es_fprintf (fp, _(" ERROR: This cached CRL may have been tampered with!\n")); + + es_putc ('\n', fp); + + rc = cdb_findinit (&cdbfp, cdb, NULL, 0); + while (!rc && (rc=cdb_findnext (&cdbfp)) > 0 ) + { + unsigned char keyrecord[256]; + unsigned char record[16]; + int reason; + int any = 0; + cdbi_t n; + cdbi_t i; + + rc = 0; + n = cdb_datalen (cdb); + if (n != 16) + { + log_error (_(" WARNING: invalid cache record length\n")); + warn = 1; + continue; + } + + if (cdb_read (cdb, record, n, cdb_datapos (cdb))) + { + log_error (_("problem reading cache record: %s\n"), + strerror (errno)); + warn = 1; + continue; + } + + n = cdb_keylen (cdb); + if (n > sizeof keyrecord) + n = sizeof keyrecord; + if (cdb_read (cdb, keyrecord, n, cdb_keypos (cdb))) + { + log_error (_("problem reading cache key: %s\n"), strerror (errno)); + warn = 1; + continue; + } + + reason = *record; + es_fputs (" ", fp); + for (i = 0; i < n; i++) + es_fprintf (fp, "%02X", keyrecord[i]); + es_fputs (":\t reasons( ", fp); + + if (reason & KSBA_CRLREASON_UNSPECIFIED) + es_fputs( "unspecified ", fp ), any = 1; + if (reason & KSBA_CRLREASON_KEY_COMPROMISE ) + es_fputs( "key_compromise ", fp ), any = 1; + if (reason & KSBA_CRLREASON_CA_COMPROMISE ) + es_fputs( "ca_compromise ", fp ), any = 1; + if (reason & KSBA_CRLREASON_AFFILIATION_CHANGED ) + es_fputs( "affiliation_changed ", fp ), any = 1; + if (reason & KSBA_CRLREASON_SUPERSEDED ) + es_fputs( "superseded", fp ), any = 1; + if (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION ) + es_fputs( "cessation_of_operation", fp ), any = 1; + if (reason & KSBA_CRLREASON_CERTIFICATE_HOLD ) + es_fputs( "certificate_hold", fp ), any = 1; + if (reason && !any) + es_fputs( "other", fp ); + + es_fprintf (fp, ") rdate: %.15s\n", record+1); + } + if (rc) + log_error (_("error reading cache entry from db: %s\n"), strerror (rc)); + + unlock_db_file (cache, e); + es_fprintf (fp, _("End CRL dump\n") ); + es_putc ('\n', fp); + + return (rc||warn)? gpg_error (GPG_ERR_GENERAL) : 0; +} + + +/* Print the contents of the CRL CACHE in a human readable format to + stream FP. */ +gpg_error_t +crl_cache_list (estream_t fp) +{ + crl_cache_t cache = get_current_cache (); + crl_cache_entry_t entry; + gpg_error_t err = 0; + + for (entry = cache->entries; + entry && !entry->deleted && !err; + entry = entry->next ) + err = list_one_crl_entry (cache, entry, fp); + + return err; +} + + +/* Load the CRL containing the file named FILENAME into our CRL cache. */ +gpg_error_t +crl_cache_load (ctrl_t ctrl, const char *filename) +{ + gpg_error_t err; + estream_t fp; + ksba_reader_t reader; + + fp = es_fopen (filename, "rb"); + if (!fp) + { + err = gpg_error_from_errno (errno); + log_error (_("can't open '%s': %s\n"), filename, strerror (errno)); + return err; + } + + err = create_estream_ksba_reader (&reader, fp); + if (!err) + { + err = crl_cache_insert (ctrl, filename, reader); + ksba_reader_release (reader); + } + es_fclose (fp); + return err; +} + + +/* Locate the corresponding CRL for the certificate CERT, read and + verify the CRL and store it in the cache. */ +gpg_error_t +crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert) +{ + gpg_error_t err; + ksba_reader_t reader = NULL; + char *issuer = NULL; + ksba_name_t distpoint = NULL; + ksba_name_t issuername = NULL; + char *distpoint_uri = NULL; + char *issuername_uri = NULL; + int any_dist_point = 0; + int seq; + + /* Loop over all distribution points, get the CRLs and put them into + the cache. */ + if (opt.verbose) + log_info ("checking distribution points\n"); + seq = 0; + while ( !(err = ksba_cert_get_crl_dist_point (cert, seq++, + &distpoint, + &issuername, NULL ))) + { + int name_seq; + gpg_error_t last_err = 0; + + if (!distpoint && !issuername) + { + if (opt.verbose) + log_info ("no issuer name and no distribution point\n"); + break; /* Not allowed; i.e. an invalid certificate. We give + up here and hope that the default method returns a + suitable CRL. */ + } + + xfree (issuername_uri); issuername_uri = NULL; + + /* Get the URIs. We do this in a loop to iterate over all names + in the crlDP. */ + for (name_seq=0; ksba_name_enum (distpoint, name_seq); name_seq++) + { + xfree (distpoint_uri); distpoint_uri = NULL; + distpoint_uri = ksba_name_get_uri (distpoint, name_seq); + if (!distpoint_uri) + continue; + + if (!strncmp (distpoint_uri, "ldap:", 5) + || !strncmp (distpoint_uri, "ldaps:", 6)) + { + if (opt.ignore_ldap_dp) + continue; + } + else if (!strncmp (distpoint_uri, "http:", 5) + || !strncmp (distpoint_uri, "https:", 6)) + { + if (opt.ignore_http_dp) + continue; + } + else + continue; /* Skip unknown schemes. */ + + any_dist_point = 1; + + if (opt.verbose) + log_info ("fetching CRL from '%s'\n", distpoint_uri); + err = crl_fetch (ctrl, distpoint_uri, &reader); + if (err) + { + log_error (_("crl_fetch via DP failed: %s\n"), + gpg_strerror (err)); + last_err = err; + continue; /* with the next name. */ + } + + if (opt.verbose) + log_info ("inserting CRL (reader %p)\n", reader); + err = crl_cache_insert (ctrl, distpoint_uri, reader); + if (err) + { + log_error (_("crl_cache_insert via DP failed: %s\n"), + gpg_strerror (err)); + last_err = err; + continue; /* with the next name. */ + } + last_err = 0; + break; /* Ready. */ + } + if (last_err) + { + err = last_err; + goto leave; + } + + ksba_name_release (distpoint); distpoint = NULL; + + /* We don't do anything with issuername_uri yet but we keep the + code for documentation. */ + issuername_uri = ksba_name_get_uri (issuername, 0); + ksba_name_release (issuername); issuername = NULL; + + /* Close the reader. */ + crl_close_reader (reader); + reader = 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"); + + 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: + crl_close_reader (reader); + xfree (distpoint_uri); + xfree (issuername_uri); + ksba_name_release (distpoint); + ksba_name_release (issuername); + ksba_free (issuer); + return err; +} diff --git a/dirmngr/crlcache.h b/dirmngr/crlcache.h new file mode 100644 index 0000000..0e60def --- /dev/null +++ b/dirmngr/crlcache.h @@ -0,0 +1,70 @@ +/* crlcache.h - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * This file is part of DirMngr. + * + * DirMngr is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DirMngr is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#ifndef CRLCACHE_H +#define CRLCACHE_H + + +typedef enum + { + CRL_CACHE_VALID = 0, + CRL_CACHE_INVALID, + CRL_CACHE_DONTKNOW, + CRL_CACHE_CANTUSE + } +crl_cache_result_t; + +typedef enum foo + { + CRL_SIG_OK = 0, + CRL_SIG_NOT_OK, + CRL_TOO_OLD, + CRL_SIG_ERROR, + CRL_GENERAL_ERROR + } +crl_sig_result_t; + +struct crl_cache_entry_s; +typedef struct crl_cache_entry_s *crl_cache_entry_t; + + +void crl_cache_init (void); +void crl_cache_deinit (void); +int crl_cache_flush(void); + +crl_cache_result_t crl_cache_isvalid (ctrl_t ctrl, + const char *issuer_hash, + const char *cert_id, + int force_refresh); + +gpg_error_t crl_cache_cert_isvalid (ctrl_t ctrl, ksba_cert_t cert, + int force_refresh); + +gpg_error_t crl_cache_insert (ctrl_t ctrl, const char *url, + ksba_reader_t reader); + +gpg_error_t crl_cache_list (estream_t fp); + +gpg_error_t crl_cache_load (ctrl_t ctrl, const char *filename); + +gpg_error_t crl_cache_reload_crl (ctrl_t ctrl, ksba_cert_t cert); + + +#endif /* CRLCACHE_H */ diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c new file mode 100644 index 0000000..c8091f6 --- /dev/null +++ b/dirmngr/crlfetch.c @@ -0,0 +1,589 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <errno.h> +#include <npth.h> + +#include "crlfetch.h" +#include "dirmngr.h" +#include "misc.h" +#include "http.h" +#include "ks-engine.h" /* For ks_http_fetch. */ + +#if USE_LDAP +# include "ldap-wrapper.h" +#endif + +/* For detecting armored CRLs received via HTTP (yes, such CRLS really + exits, e.g. http://grid.fzk.de/ca/gridka-crl.pem at least in June + 2008) we need a context in the reader callback. */ +struct reader_cb_context_s +{ + estream_t fp; /* The stream used with the ksba reader. */ + int checked:1; /* PEM/binary detection ahs been done. */ + int is_pem:1; /* The file stream is PEM encoded. */ + struct b64state b64state; /* The state used for Base64 decoding. */ +}; + + +/* We need to associate a reader object with the reader callback + context. This table is used for it. */ +struct file_reader_map_s +{ + ksba_reader_t reader; + struct reader_cb_context_s *cb_ctx; +}; +#define MAX_FILE_READER 50 +static struct file_reader_map_s file_reader_map[MAX_FILE_READER]; + +/* Associate FP with READER. If the table is full wait until another + thread has removed an entry. */ +static void +register_file_reader (ksba_reader_t reader, struct reader_cb_context_s *cb_ctx) +{ + int i; + + for (;;) + { + for (i=0; i < MAX_FILE_READER; i++) + if (!file_reader_map[i].reader) + { + file_reader_map[i].reader = reader; + file_reader_map[i].cb_ctx = cb_ctx; + return; + } + log_info (_("reader to file mapping table full - waiting\n")); + npth_sleep (2); + } +} + +/* Scan the table for an entry matching READER, remove that entry and + return the associated file pointer. */ +static struct reader_cb_context_s * +get_file_reader (ksba_reader_t reader) +{ + struct reader_cb_context_s *cb_ctx = NULL; + int i; + + for (i=0; i < MAX_FILE_READER; i++) + if (file_reader_map[i].reader == reader) + { + cb_ctx = file_reader_map[i].cb_ctx; + file_reader_map[i].reader = NULL; + file_reader_map[i].cb_ctx = NULL; + break; + } + return cb_ctx; +} + + + +static int +my_es_read (void *opaque, char *buffer, size_t nbytes, size_t *nread) +{ + struct reader_cb_context_s *cb_ctx = opaque; + int result; + + result = es_read (cb_ctx->fp, buffer, nbytes, nread); + if (result) + return result; + /* Fixme we should check whether the semantics of es_read are okay + and well defined. I have some doubts. */ + if (nbytes && !*nread && es_feof (cb_ctx->fp)) + return gpg_error (GPG_ERR_EOF); + if (!nread && es_ferror (cb_ctx->fp)) + return gpg_error (GPG_ERR_EIO); + + if (!cb_ctx->checked && *nread) + { + int c = *(unsigned char *)buffer; + + cb_ctx->checked = 1; + if ( ((c & 0xc0) >> 6) == 0 /* class: universal */ + && (c & 0x1f) == 16 /* sequence */ + && (c & 0x20) /* is constructed */ ) + ; /* Binary data. */ + else + { + cb_ctx->is_pem = 1; + b64dec_start (&cb_ctx->b64state, ""); + } + } + if (cb_ctx->is_pem && *nread) + { + size_t nread2; + + if (b64dec_proc (&cb_ctx->b64state, buffer, *nread, &nread2)) + { + /* EOF from decoder. */ + *nread = 0; + result = gpg_error (GPG_ERR_EOF); + } + else + *nread = nread2; + } + + return result; +} + + +/* Fetch CRL from URL and return the entire CRL using new ksba reader + object in READER. Note that this reader object should be closed + only using ldap_close_reader. */ +gpg_error_t +crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) +{ + gpg_error_t err; + parsed_uri_t uri; + estream_t httpfp = NULL; + + *reader = NULL; + + if (!url) + return gpg_error (GPG_ERR_INV_ARG); + + err = http_parse_uri (&uri, url, 0); + http_release_parsed_uri (uri); + if (!err) /* Yes, our HTTP code groks that. */ + { + if (opt.disable_http) + { + log_error (_("CRL access not possible due to disabled %s\n"), + "HTTP"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else + { + /* Note that we also allow root certificates loaded from + * "/etc/gnupg/trusted-certs/". We also do not consult the + * CRL for the TLS connection - that may lead to a loop. + * Due to cacert.org redirecting their https URL to http we + * also allow such a downgrade. */ + err = ks_http_fetch (ctrl, url, + (KS_HTTP_FETCH_TRUST_CFG + | KS_HTTP_FETCH_NO_CRL + | KS_HTTP_FETCH_ALLOW_DOWNGRADE ), + &httpfp); + } + + if (err) + log_error (_("error retrieving '%s': %s\n"), url, gpg_strerror (err)); + else + { + struct reader_cb_context_s *cb_ctx; + + cb_ctx = xtrycalloc (1, sizeof *cb_ctx); + if (!cb_ctx) + err = gpg_error_from_syserror (); + else if (!(err = ksba_reader_new (reader))) + { + cb_ctx->fp = httpfp; + err = ksba_reader_set_cb (*reader, &my_es_read, cb_ctx); + if (!err) + { + /* The ksba reader misses a user pointer thus we + * need to come up with our own way of associating a + * file pointer (or well the callback context) with + * the reader. It is only required when closing the + * reader thus there is no performance issue doing + * it this way. FIXME: We now have a close + * notification which might be used here. */ + register_file_reader (*reader, cb_ctx); + httpfp = NULL; + } + } + + if (err) + { + log_error (_("error initializing reader object: %s\n"), + gpg_strerror (err)); + ksba_reader_release (*reader); + *reader = NULL; + xfree (cb_ctx); + } + } + } + else /* Let the LDAP code parse other schemes. */ + { + if (opt.disable_ldap) + { + log_error (_("CRL access not possible due to disabled %s\n"), + "LDAP"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else if (dirmngr_use_tor ()) + { + /* For now we do not support LDAP over Tor. */ + log_error (_("CRL access not possible due to Tor mode\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + } + else + { +# if USE_LDAP + err = url_fetch_ldap (ctrl, url, NULL, 0, reader); +# else /*!USE_LDAP*/ + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); +# endif /*!USE_LDAP*/ + } + } + + es_fclose (httpfp); + 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 (dirmngr_use_tor ()) + { + /* For now we do not support LDAP over Tor. */ + log_error (_("CRL access not possible due to Tor mode\n")); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + if (opt.disable_ldap) + { + log_error (_("CRL access not possible due to disabled %s\n"), + "LDAP"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + +#if USE_LDAP + return attr_fetch_ldap (ctrl, issuer, "certificateRevocationList", + reader); +#else + (void)ctrl; + (void)issuer; + (void)reader; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + +/* Fetch a CA certificate for DN using the default server. This + * function only initiates the fetch; fetch_next_cert must be used to + * actually read the certificate; end_cert_fetch to end the + * operation. */ +gpg_error_t +ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, const char *dn) +{ + if (dirmngr_use_tor ()) + { + /* For now we do not support LDAP over Tor. */ + log_error (_("CRL access not possible due to Tor mode\n")); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + if (opt.disable_ldap) + { + log_error (_("CRL access not possible due to disabled %s\n"), + "LDAP"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } +#if USE_LDAP + return start_cacert_fetch_ldap (ctrl, context, dn); +#else + (void)ctrl; + (void)context; + (void)dn; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + +gpg_error_t +start_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, + strlist_t patterns, const ldap_server_t server) +{ + if (dirmngr_use_tor ()) + { + /* For now we do not support LDAP over Tor. */ + log_error (_("CRL access not possible due to Tor mode\n")); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + if (opt.disable_ldap) + { + log_error (_("certificate search not possible due to disabled %s\n"), + "LDAP"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } +#if USE_LDAP + return start_cert_fetch_ldap (ctrl, context, patterns, server); +#else + (void)ctrl; + (void)context; + (void)patterns; + (void)server; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + +gpg_error_t +fetch_next_cert (cert_fetch_context_t context, + unsigned char **value, size_t * valuelen) +{ +#if USE_LDAP + return fetch_next_cert_ldap (context, value, valuelen); +#else + (void)context; + (void)value; + (void)valuelen; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + + +/* Fetch the next data from CONTEXT, assuming it is a certificate and return + * it as a cert object in R_CERT. */ +gpg_error_t +fetch_next_ksba_cert (cert_fetch_context_t context, ksba_cert_t *r_cert) +{ + gpg_error_t err; + unsigned char *value; + size_t valuelen; + ksba_cert_t cert; + + *r_cert = NULL; + +#if USE_LDAP + err = fetch_next_cert_ldap (context, &value, &valuelen); + if (!err && !value) + err = gpg_error (GPG_ERR_BUG); +#else + (void)context; + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif + if (err) + return err; + + err = ksba_cert_new (&cert); + if (err) + { + xfree (value); + return err; + } + + err = ksba_cert_init_from_mem (cert, value, valuelen); + xfree (value); + if (err) + { + ksba_cert_release (cert); + return err; + } + *r_cert = cert; + return 0; +} + + +void +end_cert_fetch (cert_fetch_context_t context) +{ +#if USE_LDAP + end_cert_fetch_ldap (context); +#else + (void)context; +#endif +} + + +/* Read a certificate from an HTTP URL and return it as an estream + * memory buffer at R_FP. */ +static gpg_error_t +read_cert_via_http (ctrl_t ctrl, const char *url, estream_t *r_fp) +{ + gpg_error_t err; + estream_t fp = NULL; + estream_t httpfp = NULL; + size_t nread, nwritten; + char buffer[1024]; + + if ((err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_TRUST_CFG, &httpfp))) + goto leave; + + /* We now read the data from the web server into a memory buffer. + * To DOS we limit the certificate length to 32k. */ + fp = es_fopenmem (32*1024, "rw"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + goto leave; + } + + for (;;) + { + if (es_read (httpfp, buffer, sizeof buffer, &nread)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", + es_fname_get (httpfp), gpg_strerror (err)); + goto leave; + } + + if (!nread) + break; /* Ready. */ + if (es_write (fp, buffer, nread, &nwritten)) + { + err = gpg_error_from_syserror (); + log_error ("error writing '%s': %s\n", + es_fname_get (fp), gpg_strerror (err)); + goto leave; + } + else if (nread != nwritten) + { + err = gpg_error (GPG_ERR_EIO); + log_error ("error writing '%s': %s\n", + es_fname_get (fp), "short write"); + goto leave; + } + } + + es_rewind (fp); + *r_fp = fp; + fp = NULL; + + leave: + es_fclose (httpfp); + es_fclose (fp); + return err; +} + + +/* 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 = NULL; + size_t cert_image_n; + ksba_reader_t reader = NULL; + ksba_cert_t cert = NULL; + gpg_error_t err; + + *value = NULL; + *valuelen = 0; + + err = ksba_cert_new (&cert); + if (err) + goto leave; + + if (url && (!strncmp (url, "http:", 5) || !strncmp (url, "https:", 6))) + { + estream_t stream; + void *der; + size_t derlen; + + err = read_cert_via_http (ctrl, url, &stream); + if (err) + goto leave; + + if (es_fclose_snatch (stream, &der, &derlen)) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = ksba_cert_init_from_mem (cert, der, derlen); + xfree (der); + if (err) + goto leave; + } + else /* Assume LDAP. */ + { +#if USE_LDAP + err = url_fetch_ldap (ctrl, url, NULL, 0, &reader); +#else + (void)ctrl; + (void)url; + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif /*USE_LDAP*/ + if (err) + goto leave; + + err = ksba_cert_read_der (cert, reader); + if (err) + goto leave; + } + + cert_image = ksba_cert_get_image (cert, &cert_image_n); + if (!cert_image || !cert_image_n) + { + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + goto leave; + } + + *value = xtrymalloc (cert_image_n); + if (!*value) + { + err = gpg_error_from_syserror (); + goto leave; + } + + memcpy (*value, cert_image, cert_image_n); + *valuelen = cert_image_n; + + leave: + ksba_cert_release (cert); +#if USE_LDAP + ldap_wrapper_release_context (reader); +#endif /*USE_LDAP*/ + + return err; +} + +/* This function is to be used to close the reader object. In + addition to running ksba_reader_release it also releases the LDAP + or HTTP contexts associated with that reader. */ +void +crl_close_reader (ksba_reader_t reader) +{ + struct reader_cb_context_s *cb_ctx; + + if (!reader) + return; + + /* Check whether this is a HTTP one. */ + cb_ctx = get_file_reader (reader); + if (cb_ctx) + { + /* This is an HTTP context. */ + if (cb_ctx->fp) + es_fclose (cb_ctx->fp); + /* Release the base64 decoder state. */ + if (cb_ctx->is_pem) + b64dec_finish (&cb_ctx->b64state); + /* Release the callback context. */ + xfree (cb_ctx); + } + else /* This is an ldap wrapper context (Currently not used). */ + { +#if USE_LDAP + ldap_wrapper_release_context (reader); +#endif /*USE_LDAP*/ + } + + /* Now get rid of the reader object. */ + ksba_reader_release (reader); +} diff --git a/dirmngr/crlfetch.h b/dirmngr/crlfetch.h new file mode 100644 index 0000000..3822adb --- /dev/null +++ b/dirmngr/crlfetch.h @@ -0,0 +1,88 @@ +/* crlfetch.h - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * + * This file is part of DirMngr. + * + * DirMngr is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DirMngr is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef CRLFETCH_H +#define CRLFETCH_H + +#include "dirmngr.h" + + +struct cert_fetch_context_s; +typedef struct cert_fetch_context_s *cert_fetch_context_t; + + +/* Fetch CRL from URL. */ +gpg_error_t crl_fetch (ctrl_t ctrl, const char* url, ksba_reader_t *reader); + +/* Fetch CRL for ISSUER using default server. */ +gpg_error_t crl_fetch_default (ctrl_t ctrl, + const char* issuer, ksba_reader_t *reader); + + +/* Fetch cert for DN. */ +gpg_error_t ca_cert_fetch (ctrl_t ctrl, cert_fetch_context_t *context, + const char *dn); + + +/* Query the server for certs matching patterns. */ +gpg_error_t start_cert_fetch (ctrl_t ctrl, + cert_fetch_context_t *context, + strlist_t patterns, + const ldap_server_t server); +gpg_error_t fetch_next_cert(cert_fetch_context_t context, + unsigned char **value, size_t *valuelen); +gpg_error_t fetch_next_ksba_cert (cert_fetch_context_t context, + ksba_cert_t *r_cert); +void end_cert_fetch (cert_fetch_context_t context); + +/* Lookup a cert by it's URL. */ +gpg_error_t fetch_cert_by_url (ctrl_t ctrl, const char *url, + unsigned char **value, size_t *valuelen); + +/* Close a reader object. */ +void crl_close_reader (ksba_reader_t reader); + + + +/*-- ldap.c --*/ +gpg_error_t url_fetch_ldap (ctrl_t ctrl, + const char *url, const char *host, int port, + ksba_reader_t *reader); +gpg_error_t attr_fetch_ldap (ctrl_t ctrl, + const char *dn, const char *attr, + ksba_reader_t *reader); + + +gpg_error_t start_cacert_fetch_ldap (ctrl_t ctrl, + cert_fetch_context_t *context, + const char *dn); +gpg_error_t start_cert_fetch_ldap( ctrl_t ctrl, + cert_fetch_context_t *context, + strlist_t patterns, + const ldap_server_t server ); +gpg_error_t fetch_next_cert_ldap (cert_fetch_context_t context, + unsigned char **value, size_t *valuelen ); +void end_cert_fetch_ldap (cert_fetch_context_t context); + + + + + + +#endif /* CRLFETCH_H */ diff --git a/dirmngr/dirmngr-client.c b/dirmngr/dirmngr-client.c new file mode 100644 index 0000000..e4df476 --- /dev/null +++ b/dirmngr/dirmngr-client.c @@ -0,0 +1,942 @@ +/* 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#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> + +#include "../common/logging.h" +#include "../common/argparse.h" +#include "../common/stringhelp.h" +#include "../common/mischelp.h" +#include "../common/strlist.h" +#include "../common/asshelp.h" + +#include "../common/i18n.h" +#include "../common/util.h" +#include "../common/init.h" + + +/* Constants for the options. */ +enum + { + oQuiet = 'q', + oVerbose = 'v', + oLocal = 'l', + oUrl = 'u', + + oOCSP = 500, + oPing, + oCacheCert, + oValidate, + oLookup, + oLoadCRL, + oSquidMode, + oPEM, + oEscapedPEM, + oForceDefaultResponder + }; + + +/* The list of options as used by the argparse.c code. */ +static ARGPARSE_OPTS opts[] = { + { oVerbose, "verbose", 0, N_("verbose") }, + { oQuiet, "quiet", 0, N_("be somewhat more quiet") }, + { oOCSP, "ocsp", 0, N_("use OCSP instead of CRLs") }, + { oPing, "ping", 0, N_("check whether a dirmngr is running")}, + { oCacheCert,"cache-cert",0, N_("add a certificate to the cache")}, + { oValidate, "validate", 0, N_("validate a certificate")}, + { oLookup, "lookup", 0, N_("lookup a certificate")}, + { oLocal, "local", 0, N_("lookup only locally stored certificates")}, + { oUrl, "url", 0, N_("expect an URL for --lookup")}, + { oLoadCRL, "load-crl", 0, N_("load a CRL into the dirmngr")}, + { oSquidMode,"squid-mode",0, N_("special mode for use by Squid")}, + { oPEM, "pem", 0, N_("expect certificates in PEM format")}, + { oForceDefaultResponder, "force-default-responder", 0, + N_("force the use of the default OCSP responder")}, + ARGPARSE_end () +}; + + +/* The usual structure for the program flags. */ +static struct +{ + int quiet; + int verbose; + const char *dirmngr_program; + 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 */ + + +/* Build the helptable for radix64 to bin conversion. */ +static void +init_asctobin (void) +{ + static int initialized; + int i; + unsigned char *s; + + if (initialized) + return; + initialized = 1; + + 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; +} + + +/* Prototypes. */ +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 9: p = "GPL-3.0-or-later"; break; + case 11: p = "dirmngr-client (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + case 49: p = PACKAGE_BUGREPORT; break; + case 1: + case 40: p = + _("Usage: dirmngr-client [options] " + "[certfile|pattern] (-h for help)\n"); + break; + case 41: p = + _("Syntax: dirmngr-client [options] [certfile|pattern]\n" + "Test an X.509 certificate against a CRL or do an OCSP check\n" + "The process returns 0 if the certificate is valid, 1 if it is\n" + "not valid and other error codes for general failures\n"); + break; + + default: p = NULL; + } + return p; +} + + + +int +main (int argc, char **argv ) +{ + ARGPARSE_ARGS pargs; + assuan_context_t ctx; + gpg_error_t err; + unsigned char *certbuf; + size_t certbuflen = 0; + int cmd_ping = 0; + int cmd_cache_cert = 0; + int cmd_validate = 0; + int cmd_lookup = 0; + int cmd_loadcrl = 0; + int cmd_squid_mode = 0; + + early_system_init (); + set_strusage (my_strusage); + log_set_prefix ("dirmngr-client", + GPGRT_LOG_WITH_PREFIX); + /* Register our string mapper. Usually done in + * init_common_subsystems, but we don't use that here. */ + gnupg_set_fixed_string_mapper (map_static_macro_string); + + /* For W32 we need to initialize the socket subsystem. Because we + don't use Pth we need to do this explicit. */ +#ifdef HAVE_W32_SYSTEM + { + WSADATA wsadat; + + WSAStartup (0x202, &wsadat); + } +#endif /*HAVE_W32_SYSTEM*/ + + /* Init Assuan. */ + assuan_set_assuan_log_prefix (log_get_prefix (NULL)); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + + /* Setup I18N. */ + i18n_init(); + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= ARGPARSE_FLAG_KEEP; + while (gnupg_argparse (NULL, &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 = ARGPARSE_PRINT_ERROR; break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (log_get_errorcount (0)) + exit (2); + + 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); + } + + err = start_new_dirmngr (&ctx, + GPG_ERR_SOURCE_DEFAULT, + opt.dirmngr_program + ? opt.dirmngr_program + : gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR), + ! cmd_ping, + opt.verbose, + 0, + NULL, NULL); + if (err) + { + log_error (_("can't connect to the dirmngr: %s\n"), gpg_strerror (err)); + 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; +} + + +/* 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; + + init_asctobin (); + + fp = fname? gnupg_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; /* fall through */ + 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); + else if (fname) + { + /* A filename has been given. Let's just assume it is in PEM + format and decode it, and fall back to interpreting it as + binary certificate if that fails. */ + err = read_pem_certificate (fname, rbuf, rbuflen); + if (! err) + return 0; + } + + fp = fname? gnupg_fopen (fname, "rb") : stdin; + if (!fp) + return gpg_error_from_errno (errno); + + buf = NULL; + bufsize = buflen = 0; +#define NCHUNK 8192 + do + { + bufsize += NCHUNK; + if (!buf) + buf = xmalloc (bufsize); + else + buf = xrealloc (buf, bufsize); + + nread = fread (buf+buflen, 1, NCHUNK, fp); + if (nread < NCHUNK && ferror (fp)) + { + err = gpg_error_from_errno (errno); + xfree (buf); + if (fname) + fclose (fp); + return err; + } + buflen += nread; + } + while (nread == NCHUNK); +#undef NCHUNK + if (fname) + fclose (fp); + *rbuf = buf; + *rbuflen = buflen; + return 0; +} + + +/* Callback for the inquire fiunction to send back the certificate. */ +static gpg_error_t +inq_cert (void *opaque, const char *line) +{ + struct inq_cert_parm_s *parm = opaque; + gpg_error_t err; + + if (!strncmp (line, "TARGETCERT", 10) && (line[10] == ' ' || !line[10])) + { + err = assuan_send_data (parm->ctx, parm->cert, parm->certlen); + } + else if (!strncmp (line, "SENDCERT", 8) && (line[8] == ' ' || !line[8])) + { + /* We don't support this but dirmngr might ask for it. So + simply ignore it by sending back and empty value. */ + err = assuan_send_data (parm->ctx, NULL, 0); + } + else if (!strncmp (line, "SENDCERT_SKI", 12) + && (line[12]==' ' || !line[12])) + { + /* We don't support this but dirmngr might ask for it. So + simply ignore it by sending back an empty value. */ + err = assuan_send_data (parm->ctx, NULL, 0); + } + else if (!strncmp (line, "SENDISSUERCERT", 14) + && (line[14] == ' ' || !line[14])) + { + /* We don't support this but dirmngr might ask for it. So + simply ignore it by sending back an empty value. */ + err = assuan_send_data (parm->ctx, NULL, 0); + } + else + { + log_info (_("unsupported inquiry '%s'\n"), line); + err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + /* Note that this error will let assuan_transact terminate + immediately instead of return the error to the caller. It is + not clear whether this is the desired behaviour - it may + change in future. */ + } + + return err; +} + + +/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. + Return a proper error code. */ +static gpg_error_t +do_check (assuan_context_t ctx, const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + struct inq_cert_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.ctx = ctx; + parm.cert = cert; + parm.certlen = certlen; + + err = assuan_transact (ctx, + (opt.use_ocsp && opt.force_default_responder + ? "CHECKOCSP --force-default-responder" + : opt.use_ocsp? "CHECKOCSP" : "CHECKCRL"), + NULL, NULL, inq_cert, &parm, status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + return err; +} + +/* Check the certificate CERT,CERTLEN for validity using a CRL or OCSP. + Return a proper error code. */ +static gpg_error_t +do_cache (assuan_context_t ctx, const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + struct inq_cert_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.ctx = ctx; + parm.cert = cert; + parm.certlen = certlen; + + err = assuan_transact (ctx, "CACHECERT", NULL, NULL, + inq_cert, &parm, + status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + return err; +} + +/* Check the certificate CERT,CERTLEN for validity using dirmngrs + internal validate feature. Return a proper error code. */ +static gpg_error_t +do_validate (assuan_context_t ctx, const unsigned char *cert, size_t certlen) +{ + gpg_error_t err; + struct inq_cert_parm_s parm; + + memset (&parm, 0, sizeof parm); + parm.ctx = ctx; + parm.cert = cert; + parm.certlen = certlen; + + err = assuan_transact (ctx, "VALIDATE", NULL, NULL, + inq_cert, &parm, + status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + return err; +} + +/* Load a CRL into the dirmngr. */ +static gpg_error_t +do_loadcrl (assuan_context_t ctx, const char *filename) +{ + gpg_error_t err; + const char *s; + char *fname, *line, *p; + + if (opt.url) + fname = xstrdup (filename); + else + { +#ifdef HAVE_CANONICALIZE_FILE_NAME + fname = canonicalize_file_name (filename); + if (!fname) + { + log_error ("error canonicalizing '%s': %s\n", + filename, strerror (errno)); + return gpg_error (GPG_ERR_GENERAL); + } +#else + fname = xstrdup (filename); +#endif + if (*fname != '/') + { + log_error (_("absolute file name expected\n")); + return gpg_error (GPG_ERR_GENERAL); + } + } + + line = xmalloc (8 + 6 + strlen (fname) * 3 + 1); + p = stpcpy (line, "LOADCRL "); + if (opt.url) + p = stpcpy (p, "--url "); + for (s = fname; *s; s++) + { + if (*s < ' ' || *s == '+') + { + sprintf (p, "%%%02X", *s); + p += 3; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + *p = 0; + + err = assuan_transact (ctx, line, NULL, NULL, + NULL, NULL, + status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + xfree (line); + xfree (fname); + return err; +} + + +/* Do a LDAP lookup using PATTERN and print the result in a base-64 + encoded format. */ +static gpg_error_t +do_lookup (assuan_context_t ctx, const char *pattern) +{ + gpg_error_t err; + const unsigned char *s; + char *line, *p; + struct b64state state; + + if (opt.verbose) + log_info (_("looking up '%s'\n"), pattern); + + err = b64enc_start (&state, stdout, NULL); + if (err) + return err; + + line = xmalloc (10 + 6 + 13 + strlen (pattern)*3 + 1); + + p = stpcpy (line, "LOOKUP "); + if (opt.url) + p = stpcpy (p, "--url "); + if (opt.local) + p = stpcpy (p, "--cache-only "); + for (s=pattern; *s; s++) + { + if (*s < ' ' || *s == '+') + { + sprintf (p, "%%%02X", *s); + p += 3; + } + else if (*s == ' ') + *p++ = '+'; + else + *p++ = *s; + } + *p = 0; + + + err = assuan_transact (ctx, line, + data_cb, &state, + NULL, NULL, + status_cb, NULL); + if (opt.verbose > 1) + log_info ("response of dirmngr: %s\n", err? gpg_strerror (err): "okay"); + + err = b64enc_finish (&state); + + xfree (line); + return err; +} + +/* The body of an endless loop: Read a line from stdin, retrieve the + certificate from it, validate it and print "ERR" or "OK" to stdout. + Continue. */ +static gpg_error_t +squid_loop_body (assuan_context_t ctx) +{ + gpg_error_t err; + unsigned char *certbuf; + size_t certbuflen = 0; + + err = read_pem_certificate (NULL, &certbuf, &certbuflen); + if (gpg_err_code (err) == GPG_ERR_EOF) + return err; + if (err) + { + log_error (_("error reading certificate from stdin: %s\n"), + gpg_strerror (err)); + puts ("ERROR"); + return 0; + } + + err = do_check (ctx, certbuf, certbuflen); + xfree (certbuf); + if (!err) + { + if (opt.verbose) + log_info (_("certificate is valid\n")); + puts ("OK"); + } + else + { + if (!opt.quiet) + { + if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED ) + log_info (_("certificate has been revoked\n")); + else + log_error (_("certificate check failed: %s\n"), + gpg_strerror (err)); + } + puts ("ERROR"); + } + + fflush (stdout); + + return 0; +} diff --git a/dirmngr/dirmngr-err.h b/dirmngr/dirmngr-err.h new file mode 100644 index 0000000..17e8255 --- /dev/null +++ b/dirmngr/dirmngr-err.h @@ -0,0 +1,12 @@ +/* Definition of the gpg-error source. */ + +#ifndef DIRMNGR_ERR_H +#define DIRMNGR_ERR_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_DIRMNGR +#include <gpg-error.h> + +#endif /*DIRMNGR_ERR_H*/ diff --git a/dirmngr/dirmngr-status.h b/dirmngr/dirmngr-status.h new file mode 100644 index 0000000..2c3fd78 --- /dev/null +++ b/dirmngr/dirmngr-status.h @@ -0,0 +1,39 @@ +/* dirmngr-status.h - Status code helper functions for dirmnmgr. + * Copyright (C) 2004, 2014, 2015, 2018 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 <https://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0+ + */ + +/* We keep them separate so that we don't always need to include the + * entire dirmngr.h */ + +#ifndef DIRMNGR_STATUS_H +#define DIRMNGR_STATUS_H + + +/*-- server.c --*/ +gpg_error_t dirmngr_status (ctrl_t ctrl, const char *keyword, ...); +gpg_error_t dirmngr_status_help (ctrl_t ctrl, const char *text); +gpg_error_t dirmngr_status_helpf (ctrl_t ctrl, const char *format, + ...) GPGRT_ATTR_PRINTF(2,3); +gpg_error_t dirmngr_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, + ...) GPGRT_ATTR_PRINTF(3,4); + + +#endif /* DIRMNGR_STATUS_H */ diff --git a/dirmngr/dirmngr-w32info.rc b/dirmngr/dirmngr-w32info.rc new file mode 100644 index 0000000..c8101b3 --- /dev/null +++ b/dirmngr/dirmngr-w32info.rc @@ -0,0 +1,52 @@ +/* dirmngr-w32info.rc -*- c -*- + * Copyright (C) 2020 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 program 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 "afxres.h" +#include "../common/w32info-rc.h" + +1 ICON "../common/gnupg.ico" + +1 VERSIONINFO + FILEVERSION W32INFO_VI_FILEVERSION + PRODUCTVERSION W32INFO_VI_PRODUCTVERSION + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x01L /* VS_FF_DEBUG (0x1)*/ +#else + FILEFLAGS 0x00L +#endif + FILEOS 0x40004L /* VOS_NT (0x40000) | VOS__WINDOWS32 (0x4) */ + FILETYPE 0x1L /* VFT_APP (0x1) */ + FILESUBTYPE 0x0L /* VFT2_UNKNOWN */ + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" /* US English (0409), Unicode (04b0) */ + BEGIN + VALUE "FileDescription", L"GnuPG\x2019s network daemon\0" + VALUE "InternalName", "dirmngr\0" + VALUE "OriginalFilename", "dirmngr.exe\0" + VALUE "ProductName", W32INFO_PRODUCTNAME + VALUE "ProductVersion", W32INFO_PRODUCTVERSION + VALUE "CompanyName", W32INFO_COMPANYNAME + VALUE "FileVersion", W32INFO_FILEVERSION + VALUE "LegalCopyright", W32INFO_LEGALCOPYRIGHT + VALUE "Comments", W32INFO_COMMENTS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 0x4b0 + END + END + +1 RT_MANIFEST "dirmngr.w32-manifest" diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c new file mode 100644 index 0000000..ae967dd --- /dev/null +++ b/dirmngr/dirmngr.c @@ -0,0 +1,2401 @@ +/* dirmngr.c - Keyserver and X.509 LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2003, 2004, 2006, 2007, 2008, 2010, 2011, 2020 g10 Code GmbH + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <time.h> +#include <fcntl.h> +#ifndef HAVE_W32_SYSTEM +#include <sys/socket.h> +#include <sys/un.h> +#endif +#include <sys/stat.h> +#include <unistd.h> +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif +#ifdef HAVE_INOTIFY_INIT +# include <sys/inotify.h> +#endif /*HAVE_INOTIFY_INIT*/ +#include <npth.h> + +#include "dirmngr-err.h" + +#if HTTP_USE_NTBTLS +# include <ntbtls.h> +#elif HTTP_USE_GNUTLS +# include <gnutls/gnutls.h> +#endif /*HTTP_USE_GNUTLS*/ + + +#define INCLUDED_BY_MAIN_MODULE 1 +#define GNUPG_COMMON_NEED_AFLOCAL +#include "dirmngr.h" + +#include <assuan.h> + +#include "certcache.h" +#include "crlcache.h" +#include "crlfetch.h" +#include "misc.h" +#if USE_LDAP +# include "ldapserver.h" +#endif +#include "../common/asshelp.h" +#if USE_LDAP +# include "ldap-wrapper.h" +#endif +#include "../common/init.h" +#include "../common/gc-opt-flags.h" +#include "dns-stuff.h" +#include "http-common.h" + +#ifndef ENAMETOOLONG +# define ENAMETOOLONG EINVAL +#endif + + +enum cmd_and_opt_values { + aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + oNoVerbose = 500, + + aServer, + aDaemon, + aSupervised, + aListCRLs, + aLoadCRL, + aFetchCRL, + aShutdown, + aFlush, + aGPGConfList, + aGPGConfTest, + aGPGConfVersions, + + oOptions, + oDebug, + oDebugAll, + oDebugWait, + oDebugLevel, + oGnutlsDebug, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oLogFile, + oBatch, + oDisableHTTP, + oDisableLDAP, + oDisableIPv4, + oDisableIPv6, + oIgnoreLDAPDP, + oIgnoreHTTPDP, + oIgnoreOCSPSvcUrl, + oHonorHTTPProxy, + oHTTPProxy, + oLDAPProxy, + oOnlyLDAPProxy, + oLDAPFile, + oLDAPTimeout, + oLDAPAddServers, + oOCSPResponder, + oOCSPSigner, + oOCSPMaxClockSkew, + oOCSPMaxPeriod, + oOCSPCurrentPeriod, + oMaxReplies, + oHkpCaCert, + oFakedSystemTime, + oForce, + oAllowOCSP, + oAllowVersionCheck, + oSocketName, + oLDAPWrapperProgram, + oHTTPWrapperProgram, + oIgnoreCertExtension, + oUseTor, + oNoUseTor, + oKeyServer, + oNameServer, + oDisableCheckOwnSocket, + oStandardResolver, + oRecursiveResolver, + oResolverTimeout, + oConnectTimeout, + oConnectQuickTimeout, + oListenBacklog, + 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)") ), +#ifndef HAVE_W32_SYSTEM + ARGPARSE_c (aSupervised, "supervised", N_("run in supervised mode")), +#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_c (aGPGConfVersions, "gpgconf-versions", "@"), + + 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_conffile (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 (oAllowVersionCheck, "allow-version-check", + N_("allow online software version check")), + 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 (oNameServer, "nameserver", "@"), + ARGPARSE_s_s (oKeyServer, "keyserver", "@"), + ARGPARSE_s_s (oHkpCaCert, "hkp-cacert", + N_("|FILE|use the CA certificates in FILE for HKP over TLS")), + + ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via Tor")), + ARGPARSE_s_n (oNoUseTor, "no-use-tor", "@"), + + ARGPARSE_s_n (oDisableIPv4, "disable-ipv4", "@"), + ARGPARSE_s_n (oDisableIPv6, "disable-ipv6", "@"), + + ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */ + + ARGPARSE_s_u (oFakedSystemTime, "faked-system-time", "@"), /*(epoch time)*/ + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"), + ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"), + ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), + ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), + 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_s_n (oStandardResolver, "standard-resolver", "@"), + ARGPARSE_s_n (oRecursiveResolver, "recursive-resolver", "@"), + ARGPARSE_s_i (oResolverTimeout, "resolver-timeout", "@"), + ARGPARSE_s_i (oConnectTimeout, "connect-timeout", "@"), + ARGPARSE_s_i (oConnectQuickTimeout, "connect-quick-timeout", "@"), + ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), + ARGPARSE_noconffile (oNoOptions, "no-options", "@"), + + ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing " + "of all commands and options)\n")), + + ARGPARSE_end () +}; + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_X509_VALUE , "x509" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { DBG_DNS_VALUE , "dns" }, + { DBG_NETWORK_VALUE, "network" }, + { DBG_LOOKUP_VALUE , "lookup" }, + { DBG_EXTPROG_VALUE, "extprog" }, + { 77, NULL } /* 77 := Do not exit on "help" or "?". */ + }; + +#define DEFAULT_MAX_REPLIES 10 +#define DEFAULT_LDAP_TIMEOUT 15 /* seconds */ + +#define DEFAULT_CONNECT_TIMEOUT (15*1000) /* 15 seconds */ +#define DEFAULT_CONNECT_QUICK_TIMEOUT ( 2*1000) /* 2 seconds */ + +/* For the cleanup handler we need to keep track of the socket's name. */ +static const char *socket_name; +/* If the socket has been redirected, this is the name of the + redirected socket.. */ +static const char *redir_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; + +/* Value for the listen() backlog argument. + * Change at runtime with --listen-backlog. */ +static int listen_backlog = 64; + +/* Only if this flag has been set will we remove the socket file. */ +static int cleanup_socket; + +/* Keep track of the current log file so that we can avoid updating + the log file after a SIGHUP if it didn't changed. Malloced. */ +static char *current_logfile; + +/* Helper to implement --debug-level. */ +static const char *debug_level; + +/* Helper to set the NTBTLS or GNUTLS log level. */ +static int opt_gnutls_debug = -1; + +/* Flag indicating that a shutdown has been requested. */ +static volatile int shutdown_pending; + +/* Flags to indicate that we shall not watch our own socket. */ +static int disable_check_own_socket; + +/* Flag to control the Tor mode. */ +static enum + { TOR_MODE_AUTO = 0, /* Switch to NO or YES */ + TOR_MODE_NEVER, /* Never use Tor. */ + TOR_MODE_NO, /* Do not use Tor */ + TOR_MODE_YES, /* Use Tor */ + TOR_MODE_FORCE /* Force using Tor */ + } tor_mode; + + +/* Counter for the active connections. */ +static int active_connections; + +/* This flag is set by any network access and used by the housekeeping + * thread to run background network tasks. */ +static int network_activity_seen; + +/* A list of filenames registred with --hkp-cacert. */ +static strlist_t hkp_cacert_filenames; + + +/* The timer tick used for housekeeping stuff. The second constant is used when a shutdown is pending. */ +#define TIMERTICK_INTERVAL (60) +#define TIMERTICK_INTERVAL_SHUTDOWN (4) + +/* How oft to run the housekeeping. */ +#define HOUSEKEEPING_INTERVAL (600) + + +/* This union is used to avoid compiler warnings in case a pointer is + 64 bit and an int 32 bit. We store an integer in a pointer and get + it back later (npth_getspecific et al.). */ +union int_and_ptr_u +{ + int aint; + assuan_fd_t afd; + void *aptr; +}; + + + +/* The key used to store the current file descriptor in the thread + local storage. We use this in conjunction with the + log_set_pid_suffix_cb feature. */ +#ifndef HAVE_W32_SYSTEM +static npth_key_t my_tlskey_current_fd; +#endif + +/* Prototypes. */ +static void cleanup (void); +#if USE_LDAP +static ldap_server_t parse_ldapserver_file (const char* filename, int ienoent); +#endif /*USE_LDAP*/ +static fingerprint_list_t parse_ocsp_signer (const char *string); +static void netactivity_action (void); +static void handle_connections (assuan_fd_t listen_fd); +static void gpgconf_versions (void); + + +/* NPth wrapper function definitions. */ +ASSUAN_SYSTEM_NPTH_IMPL; + +static const char * +my_strusage( int level ) +{ + const char *p; + switch ( level ) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "@DIRMNGR@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + /* TRANSLATORS: @EMAIL@ will get replaced by the actual bug + reporting address. This is so that we can change the + reporting address without breaking the translations. */ + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + case 49: p = PACKAGE_BUGREPORT; break; + case 1: + case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)"); + break; + case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n" + "Keyserver, CRL, and OCSP access for @GNUPG@\n"); + break; + + default: p = NULL; + } + return p; +} + + +/* Callback from libksba to hash a provided buffer. Our current + implementation does only allow SHA-1 for hashing. This may be + extended by mapping the name, testing for algorithm availibility + and adjust the length checks accordingly. */ +static gpg_error_t +my_ksba_hash_buffer (void *arg, const char *oid, + const void *buffer, size_t length, size_t resultsize, + unsigned char *result, size_t *resultlen) +{ + (void)arg; + + if (oid && strcmp (oid, "1.3.14.3.2.26")) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + if (resultsize < 20) + return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); + gcry_md_hash_buffer (2, result, buffer, length); + *resultlen = 20; + return 0; +} + + +/* GNUTLS log function callback. */ +#ifdef HTTP_USE_GNUTLS +static void +my_gnutls_log (int level, const char *text) +{ + int n; + + n = strlen (text); + while (n && text[n-1] == '\n') + n--; + + log_debug ("gnutls:L%d: %.*s\n", level, n, text); +} +#endif /*HTTP_USE_GNUTLS*/ + +/* 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_IPC_VALUE; + else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5)) + opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE); + else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8)) + opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE + |DBG_CACHE_VALUE|DBG_CRYPTO_VALUE); + else if (!strcmp (debug_level, "guru") || numok) + { + opt.debug = ~0; + /* Unless the "guru" string has been used we don't want to allow + hashing debugging. The rationale is that people tend to + select the highest debug value and would then clutter their + disk with debug files which may reveal confidential data. */ + if (numok) + opt.debug &= ~(DBG_HASHING_VALUE); + } + else + { + log_error (_("invalid debug-level '%s' given\n"), debug_level); + log_info (_("valid debug levels are: %s\n"), + "none, basic, advanced, expert, guru"); + opt.debug = 0; /* Reset debugging, so that prior debug + statements won't have an undesired effect. */ + } + + + if (opt.debug && !opt.verbose) + { + opt.verbose = 1; + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + } + if (opt.debug && opt.quiet) + opt.quiet = 0; + + if (opt.debug & DBG_CRYPTO_VALUE ) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); + +#if HTTP_USE_NTBTLS + if (opt_gnutls_debug >= 0) + { + ntbtls_set_debug (opt_gnutls_debug, NULL, NULL); + } +#elif HTTP_USE_GNUTLS + if (opt_gnutls_debug >= 0) + { + gnutls_global_set_log_function (my_gnutls_log); + gnutls_global_set_log_level (opt_gnutls_debug); + } +#endif /*HTTP_USE_GNUTLS*/ + + if (opt.debug) + parse_debug_flag (NULL, &opt.debug, debug_flags); +} + + +static void +set_tor_mode (void) +{ + if (dirmngr_use_tor ()) + { + /* Enable Tor mode and when called again force a new curcuit + * (e.g. on SIGHUP). */ + enable_dns_tormode (1); + if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1)) + { + log_error ("error enabling Tor mode: %s\n", strerror (errno)); + log_info ("(is your Libassuan recent enough?)\n"); + } + } + else + disable_dns_tormode (); +} + + +/* Return true if Tor shall be used. */ +int +dirmngr_use_tor (void) +{ + if (tor_mode == TOR_MODE_AUTO) + { + /* Figure out whether Tor is running. */ + assuan_fd_t sock; + + sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR); + if (sock == ASSUAN_INVALID_FD) + tor_mode = TOR_MODE_NO; + else + { + tor_mode = TOR_MODE_YES; + assuan_sock_close (sock); + } + } + + if (tor_mode == TOR_MODE_FORCE) + return 2; /* Use Tor (using 2 to indicate force mode) */ + else if (tor_mode == TOR_MODE_YES) + return 1; /* Use Tor */ + else + return 0; /* Do not use Tor. */ +} + + +static void +wrong_args (const char *text) +{ + es_fprintf (es_stderr, _("usage: %s [options] "), DIRMNGR_NAME); + es_fputs (text, es_stderr); + es_putc ('\n', es_stderr); + dirmngr_exit (2); +} + + +/* Helper to stop the reaper thread for the ldap wrapper. */ +static void +shutdown_reaper (void) +{ +#if USE_LDAP + ldap_wrapper_wait_connections (); +#endif +} + + +/* Handle options which are allowed to be reset after program start. + Return true if the current option in PARGS could be handled and + false if not. As a special feature, passing a value of NULL for + PARGS, resets the options to the default. REREAD should be set + true if it is not the initial option parsing. */ +static int +parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) +{ + if (!pargs) + { /* Reset mode. */ + opt.quiet = 0; + opt.verbose = 0; + opt.debug = 0; + opt.ldap_wrapper_program = NULL; + opt.disable_http = 0; + opt.disable_ldap = 0; + opt.honor_http_proxy = 0; + opt.http_proxy = NULL; + opt.ldap_proxy = NULL; + opt.only_ldap_proxy = 0; + opt.ignore_http_dp = 0; + opt.ignore_ldap_dp = 0; + opt.ignore_ocsp_service_url = 0; + opt.allow_ocsp = 0; + opt.allow_version_check = 0; + opt.ocsp_responder = NULL; + opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */ + opt.ocsp_max_period = 90 * 86400; /* 90 days. */ + opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */ + opt.max_replies = DEFAULT_MAX_REPLIES; + while (opt.ocsp_signer) + { + fingerprint_list_t tmp = opt.ocsp_signer->next; + xfree (opt.ocsp_signer); + opt.ocsp_signer = tmp; + } + FREE_STRLIST (opt.ignored_cert_extensions); + http_register_tls_ca (NULL); + FREE_STRLIST (hkp_cacert_filenames); + FREE_STRLIST (opt.keyserver); + /* Note: We do not allow resetting of TOR_MODE_FORCE at runtime. */ + if (tor_mode != TOR_MODE_FORCE) + tor_mode = TOR_MODE_AUTO; + disable_check_own_socket = 0; + enable_standard_resolver (0); + set_dns_timeout (0); + opt.connect_timeout = 0; + opt.connect_quick_timeout = 0; + return 1; + } + + switch (pargs->r_opt) + { + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oDebug: + parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags); + break; + case oDebugAll: opt.debug = ~0; break; + case oDebugLevel: debug_level = pargs->r.ret_str; break; + case oGnutlsDebug: opt_gnutls_debug = pargs->r.ret_int; break; + + case oLogFile: + if (!reread) + return 0; /* Not handled. */ + if (!current_logfile || !pargs->r.ret_str + || strcmp (current_logfile, pargs->r.ret_str)) + { + log_set_file (pargs->r.ret_str); + xfree (current_logfile); + current_logfile = xtrystrdup (pargs->r.ret_str); + } + break; + + case oDisableCheckOwnSocket: disable_check_own_socket = 1; 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 oDisableIPv4: opt.disable_ipv4 = 1; break; + case oDisableIPv6: opt.disable_ipv6 = 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 oAllowVersionCheck: opt.allow_version_check = 1; break; + case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break; + case oOCSPSigner: + opt.ocsp_signer = parse_ocsp_signer (pargs->r.ret_str); + break; + case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break; + case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break; + case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break; + + case oMaxReplies: opt.max_replies = pargs->r.ret_int; break; + + case oHkpCaCert: + { + /* We need to register the filenames with gnutls (http.c) and + * also for our own cert cache. */ + char *tmpname; + + /* Do tilde expansion and make path absolute. */ + tmpname = make_absfilename (pargs->r.ret_str, NULL); + http_register_tls_ca (tmpname); + add_to_strlist (&hkp_cacert_filenames, pargs->r.ret_str); + xfree (tmpname); + } + break; + + case oIgnoreCertExtension: + add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str); + break; + + case oUseTor: + tor_mode = TOR_MODE_FORCE; + break; + case oNoUseTor: + if (tor_mode != TOR_MODE_FORCE) + tor_mode = TOR_MODE_NEVER; + break; + + case oStandardResolver: enable_standard_resolver (1); break; + case oRecursiveResolver: enable_recursive_resolver (1); break; + + case oKeyServer: + if (*pargs->r.ret_str) + add_to_strlist (&opt.keyserver, pargs->r.ret_str); + break; + + case oNameServer: + set_dns_nameserver (pargs->r.ret_str); + break; + + case oResolverTimeout: + set_dns_timeout (pargs->r.ret_int); + break; + + case oConnectTimeout: + opt.connect_timeout = pargs->r.ret_ulong * 1000; + break; + + case oConnectQuickTimeout: + opt.connect_quick_timeout = pargs->r.ret_ulong * 1000; + break; + + default: + return 0; /* Not handled. */ + } + + set_dns_verbose (opt.verbose, !!DBG_DNS); + http_set_verbose (opt.verbose, !!DBG_NETWORK); + set_dns_disable_ipv4 (opt.disable_ipv4); + set_dns_disable_ipv6 (opt.disable_ipv6); + + return 1; /* Handled. */ +} + + +/* This fucntion is called after option parsing to adjust some values + * and call option setup functions. */ +static void +post_option_parsing (void) +{ + /* It would be too surpirsing if the quick timeout is larger than + * the standard value. */ + if (opt.connect_quick_timeout > opt.connect_timeout) + opt.connect_quick_timeout = opt.connect_timeout; + + set_debug (); + set_tor_mode (); +} + + +#ifndef HAVE_W32_SYSTEM +static int +pid_suffix_callback (unsigned long *r_suffix) +{ + union int_and_ptr_u value; + + memset (&value, 0, sizeof value); + value.aptr = npth_getspecific (my_tlskey_current_fd); + *r_suffix = value.aint; + return (*r_suffix != -1); /* Use decimal representation. */ +} +#endif /*!HAVE_W32_SYSTEM*/ + +#if HTTP_USE_NTBTLS +static void +my_ntbtls_log_handler (void *opaque, int level, const char *fmt, va_list argv) +{ + (void)opaque; + + if (level == -1) + log_logv_with_prefix (GPGRT_LOG_INFO, "ntbtls: ", fmt, argv); + else + { + char prefix[10+20]; + snprintf (prefix, sizeof prefix, "ntbtls(%d): ", level); + log_logv_with_prefix (GPGRT_LOG_DEBUG, prefix, fmt, argv); + } +} +#endif + + +static void +thread_init (void) +{ + npth_init (); + assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* Now with NPth running we can set the logging callback. Our + windows implementation does not yet feature the NPth TLS + functions. */ +#ifndef HAVE_W32_SYSTEM + if (npth_key_create (&my_tlskey_current_fd, NULL) == 0) + if (npth_setspecific (my_tlskey_current_fd, NULL) == 0) + log_set_pid_suffix_cb (pid_suffix_callback); +#endif /*!HAVE_W32_SYSTEM*/ +} + + +int +main (int argc, char **argv) +{ + enum cmd_and_opt_values cmd = 0; + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + char *last_configname = NULL; + const char *configname = NULL; + const char *shell; + int debug_argparser = 0; + int greeting = 0; + int nogreeting = 0; + int nodetach = 0; + int csh_style = 0; + char *logfile = NULL; +#if USE_LDAP + char *ldapfile = NULL; +#endif /*USE_LDAP*/ + int debug_wait = 0; + int rc; + struct assuan_malloc_hooks malloc_hooks; + + early_system_init (); + set_strusage (my_strusage); + log_set_prefix (DIRMNGR_NAME, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + 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 (!ksba_check_version (NEED_KSBA_VERSION) ) + log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba", + NEED_KSBA_VERSION, ksba_check_version (NULL) ); + + ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free ); + ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL); + + /* Init TLS library. */ +#if HTTP_USE_NTBTLS + if (!ntbtls_check_version (NEED_NTBTLS_VERSION) ) + log_fatal( _("%s is too old (need %s, have %s)\n"), "ntbtls", + NEED_NTBTLS_VERSION, ntbtls_check_version (NULL) ); +#elif HTTP_USE_GNUTLS + rc = gnutls_global_init (); + if (rc) + log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc)); +#endif /*HTTP_USE_GNUTLS*/ + + /* Init Assuan. */ + malloc_hooks.malloc = gcry_malloc; + malloc_hooks.realloc = gcry_realloc; + malloc_hooks.free = gcry_free; + assuan_set_malloc_hooks (&malloc_hooks); + assuan_set_assuan_log_prefix (log_get_prefix (NULL)); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + assuan_sock_init (); + setup_libassuan_logging (&opt.debug, dirmngr_assuan_log_monitor); + + setup_libgcrypt_logging (); + +#if HTTP_USE_NTBTLS + ntbtls_set_log_handler (my_ntbtls_log_handler, NULL); +#endif + + /* Setup defaults. */ + shell = getenv ("SHELL"); + if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) + csh_style = 1; + + /* Reset rereadable options to default values. */ + parse_rereadable_options (NULL, 0); + + /* Default TCP timeouts. */ + opt.connect_timeout = DEFAULT_CONNECT_TIMEOUT; + opt.connect_quick_timeout = DEFAULT_CONNECT_QUICK_TIMEOUT; + + /* LDAP defaults. */ + opt.add_new_ldapservers = 0; + opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT; + + /* Other defaults. */ + + /* Check whether we have a config file given on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + while (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oDebug: + case oDebugAll: + debug_argparser++; + break; + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + } + } + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + + socket_name = dirmngr_socket_name (); + + /* The configuraton directories for use by gpgrt_argparser. */ + gnupg_set_confdir (GNUPG_CONFDIR_SYS, gnupg_sysconfdir ()); + gnupg_set_confdir (GNUPG_CONFDIR_USER, gnupg_homedir ()); + + /* We are re-using the struct, thus the reset flag. We OR the + * flags so that the internal intialized flag won't be cleared. */ + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER); + while (gnupg_argparser (&pargs, opts, DIRMNGR_NAME EXTSEP_S "conf")) + { + if (pargs.r_opt == ARGPARSE_CONFFILE) + { + if (debug_argparser) + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + if (pargs.r_type) + { + xfree (last_configname); + last_configname = xstrdup (pargs.r.ret_str); + configname = last_configname; + } + else + configname = NULL; + continue; + } + if (parse_rereadable_options (&pargs, 0)) + continue; /* Already handled */ + switch (pargs.r_opt) + { + case aServer: + case aDaemon: + case aSupervised: + case aShutdown: + case aFlush: + case aListCRLs: + case aLoadCRL: + case aFetchCRL: + case aGPGConfList: + case aGPGConfTest: + case aGPGConfVersions: + cmd = pargs.r_opt; + break; + + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oBatch: opt.batch=1; break; + + case oDebugWait: debug_wait = pargs.r.ret_int; break; + + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: opt.verbose = 0; break; + case oHomedir: /* Ignore this option here. */; break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oCsh: csh_style = 1; break; + case oSh: csh_style = 0; break; + case oLDAPFile: +# if USE_LDAP + ldapfile = pargs.r.ret_str; +# endif /*USE_LDAP*/ + break; + case oLDAPAddServers: opt.add_new_ldapservers = 1; break; + case oLDAPTimeout: + opt.ldaptimeout = pargs.r.ret_int; + break; + + case oFakedSystemTime: + gnupg_set_time ((time_t)pargs.r.ret_ulong, 0); + break; + + case oForce: opt.force = 1; break; + + case oSocketName: socket_name = pargs.r.ret_str; break; + + case oListenBacklog: + listen_backlog = pargs.r.ret_int; + break; + + default: + if (configname) + pargs.err = ARGPARSE_PRINT_WARNING; + else + pargs.err = ARGPARSE_PRINT_ERROR; + break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (!last_configname) + opt.config_filename = make_filename (gnupg_homedir (), + DIRMNGR_NAME EXTSEP_S "conf", + NULL); + else + { + opt.config_filename = last_configname; + last_configname = NULL; + } + + if (log_get_errorcount(0)) + exit(2); + if (nogreeting ) + greeting = 0; + + if (!opt.homedir_cache) + opt.homedir_cache = xstrdup (gnupg_homedir ()); + + if (greeting) + { + es_fprintf (es_stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + es_fprintf (es_stderr, "%s\n", strusage(15) ); + } + +#ifdef IS_DEVELOPMENT_VERSION + log_info ("NOTE: this is a development version!\n"); +#endif + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + + if (!gnupg_access ("/etc/"DIRMNGR_NAME, F_OK) + && !strncmp (gnupg_homedir (), "/etc/", 5)) + log_info + ("NOTE: DirMngr is now a proper part of %s. The configuration and" + " other directory names changed. Please check that no other version" + " of dirmngr is still installed. To disable this warning, remove the" + " directory '/etc/dirmngr'.\n", GNUPG_NAME); + + if (gnupg_faked_time_p ()) + { + gnupg_isotime_t tbuf; + + log_info (_("WARNING: running with faked system time: ")); + gnupg_get_isotime (tbuf); + dump_isotime (tbuf); + log_printf ("\n"); + } + + post_option_parsing (); + + /* Get LDAP server list from file. */ +#if USE_LDAP + if (!ldapfile) + { + ldapfile = make_filename (gnupg_homedir (), + "dirmngr_ldapservers.conf", + NULL); + opt.ldapservers = parse_ldapserver_file (ldapfile, 1); + xfree (ldapfile); + } + else + opt.ldapservers = parse_ldapserver_file (ldapfile, 0); +#endif /*USE_LDAP*/ + +#ifndef HAVE_W32_SYSTEM + /* We need to ignore the PIPE signal because the we might log to a + socket and that code handles EPIPE properly. The ldap wrapper + also requires us to ignore this silly signal. Assuan would set + this signal to ignore anyway.*/ + signal (SIGPIPE, SIG_IGN); +#endif + + /* Ready. Now to our duties. */ + if (!cmd) + cmd = aServer; + rc = 0; + + if (cmd == aServer) + { + /* Note that this server mode is mainly useful for debugging. */ + if (argc) + wrong_args ("--server"); + + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); + } + + if (debug_wait) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + + thread_init (); + cert_cache_init (hkp_cacert_filenames); + crl_cache_init (); + http_register_netactivity_cb (netactivity_action); + start_command_handler (ASSUAN_INVALID_FD, 0); + shutdown_reaper (); + } +#ifndef HAVE_W32_SYSTEM + else if (cmd == aSupervised) + { + /* In supervised mode, we expect file descriptor 3 to be an + already opened, listening socket. + + We will also not detach from the controlling process or close + stderr; the supervisor should handle all of that. */ + struct stat statbuf; + if (fstat (3, &statbuf) == -1 && errno == EBADF) + { + log_error ("file descriptor 3 must be validin --supervised mode\n"); + dirmngr_exit (1); + } + socket_name = gnupg_get_socket_name (3); + + /* Now start with logging to a file if this is desired. */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX + |GPGRT_LOG_WITH_TIME + |GPGRT_LOG_WITH_PID)); + current_logfile = xstrdup (logfile); + } + else + log_set_prefix (NULL, 0); + + thread_init (); + cert_cache_init (hkp_cacert_filenames); + crl_cache_init (); + http_register_netactivity_cb (netactivity_action); + handle_connections (3); + shutdown_reaper (); + } +#endif /*HAVE_W32_SYSTEM*/ + 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, (GPGRT_LOG_WITH_PREFIX + |GPGRT_LOG_WITH_TIME + |GPGRT_LOG_WITH_PID)); + current_logfile = xstrdup (logfile); + } + + if (debug_wait) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + +#ifndef HAVE_W32_SYSTEM + if (strchr (socket_name, ':')) + { + log_error (_("colons are not allowed in the socket name\n")); + dirmngr_exit (1); + } +#endif + 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); + } + + { + int redirected; + + if (assuan_sock_set_sockaddr_un (socket_name, + (struct sockaddr*)&serv_addr, + &redirected)) + { + if (errno == ENAMETOOLONG) + log_error (_("socket name '%s' is too long\n"), socket_name); + else + log_error ("error preparing socket '%s': %s\n", + socket_name, + gpg_strerror (gpg_error_from_syserror ())); + dirmngr_exit (1); + } + if (redirected) + { + redir_socket_name = xstrdup (serv_addr.sun_path); + if (opt.verbose) + log_info ("redirecting socket '%s' to '%s'\n", + socket_name, redir_socket_name); + } + } + + len = SUN_LEN (&serv_addr); + + rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len); + if (rc == -1 + && (errno == EADDRINUSE +#ifdef HAVE_W32_SYSTEM + || errno == EEXIST +#endif + )) + { + /* Fixme: We should test whether a dirmngr is already running. */ + gnupg_remove (redir_socket_name? redir_socket_name : 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_syserror ())); + assuan_sock_close (fd); + dirmngr_exit (1); + } + cleanup_socket = 1; + + if (gnupg_chmod (serv_addr.sun_path, "-rwx")) + log_error (_("can't set permissions of '%s': %s\n"), + serv_addr.sun_path, strerror (errno)); + + if (listen (FD2INT (fd), listen_backlog) == -1) + { + log_error ("listen(fd,%d) failed: %s\n", + listen_backlog, strerror (errno)); + assuan_sock_close (fd); + dirmngr_exit (1); + } + + if (opt.verbose) + log_info (_("listening on socket '%s'\n"), serv_addr.sun_path); + + es_fflush (NULL); + + /* Note: We keep the dirmngr_info output only for the sake of + existing scripts which might use this to detect a successful + start of the dirmngr. */ +#ifdef HAVE_W32_SYSTEM + (void)csh_style; + (void)nodetach; + + pid = getpid (); + es_printf ("set %s=%s;%lu;1\n", + DIRMNGR_INFO_NAME, socket_name, (ulong) pid); +#else + pid = fork(); + if (pid == (pid_t)-1) + { + log_fatal (_("error forking process: %s\n"), strerror (errno)); + dirmngr_exit (1); + } + + if (pid) + { /* We are the parent */ + char *infostr; + + /* Don't let cleanup() remove the socket - the child is + responsible for doing that. */ + cleanup_socket = 0; + + close (fd); + + /* Create the info string: <name>:<pid>:<protocol_version> */ + if (asprintf (&infostr, "%s=%s:%lu:1", + DIRMNGR_INFO_NAME, serv_addr.sun_path, (ulong)pid ) < 0) + { + log_error (_("out of core\n")); + kill (pid, SIGTERM); + dirmngr_exit (1); + } + /* Print the environment string, so that the caller can use + shell's eval to set it. But see above. */ + if (csh_style) + { + *strchr (infostr, '=') = ' '; + es_printf ( "setenv %s;\n", infostr); + } + else + { + es_printf ( "%s; export %s;\n", infostr, DIRMNGR_INFO_NAME); + } + free (infostr); + exit (0); + /*NEVER REACHED*/ + } /* end parent */ + + + /* + This is the child + */ + + /* Detach from tty and put process into a new session */ + if (!nodetach ) + { + int i; + unsigned int oldflags; + + /* Close stdin, stdout and stderr unless it is the log stream */ + for (i=0; i <= 2; i++) + { + if (!log_test_fd (i) && i != fd ) + { + if ( !close (i) + && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) + { + log_error ("failed to open '%s': %s\n", + "/dev/null", strerror (errno)); + cleanup (); + dirmngr_exit (1); + } + } + } + + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + dirmngr_exit (1); + } + + log_get_prefix (&oldflags); + log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED); + opt.running_detached = 1; + + } +#endif + + if (!nodetach ) + { + if (gnupg_chdir (gnupg_daemon_rootdir ())) + { + log_error ("chdir to '%s' failed: %s\n", + gnupg_daemon_rootdir (), strerror (errno)); + dirmngr_exit (1); + } + } + + thread_init (); + cert_cache_init (hkp_cacert_filenames); + crl_cache_init (); + http_register_netactivity_cb (netactivity_action); + handle_connections (fd); + shutdown_reaper (); + } + else if (cmd == aListCRLs) + { + /* Just list the CRL cache and exit. */ + if (argc) + wrong_args ("--list-crls"); + crl_cache_init (); + crl_cache_list (es_stdout); + } + else if (cmd == aLoadCRL) + { + struct server_control_s ctrlbuf; + + memset (&ctrlbuf, 0, sizeof ctrlbuf); + dirmngr_init_default_ctrl (&ctrlbuf); + + thread_init (); + cert_cache_init (hkp_cacert_filenames); + crl_cache_init (); + if (!argc) + rc = crl_cache_load (&ctrlbuf, NULL); + else + { + for (; !rc && argc; argc--, argv++) + rc = crl_cache_load (&ctrlbuf, *argv); + } + dirmngr_deinit_default_ctrl (&ctrlbuf); + } + 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); + + thread_init (); + cert_cache_init (hkp_cacert_filenames); + 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); + } + dirmngr_deinit_default_ctrl (&ctrlbuf); + } + 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; + + filename = percent_escape (opt.config_filename, NULL); + es_printf ("gpgconf-dirmngr.conf:%lu:\"%s\n", + GC_OPT_FLAG_DEFAULT, filename); + xfree (filename); + + es_printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT); + es_printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE); + + /* --csh and --sh are mutually exclusive, something we can not + express in GPG Conf. --options is only usable from the + command line, really. --debug-all interacts with --debug, + and having both of them is thus problematic. --no-detach is + also only usable on the command line. --batch is unused. */ + + filename = make_filename (gnupg_homedir (), + "dirmngr_ldapservers.conf", + NULL); + filename_esc = percent_escape (filename, NULL); + es_printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT, + filename_esc); + xfree (filename_esc); + xfree (filename); + + es_printf ("ldaptimeout:%lu:%u\n", + flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT); + es_printf ("max-replies:%lu:%u\n", + flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES); + es_printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("allow-version-check:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE); + + es_printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE); + + es_printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE); + es_printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE); + /* Note: The next one is to fix a typo in gpgconf - should be + removed eventually. */ + es_printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE); + + es_printf ("use-tor:%lu:\n", flags | GC_OPT_FLAG_NONE); + + filename_esc = percent_escape (get_default_keyserver (0), NULL); + es_printf ("keyserver:%lu:\"%s:\n", flags | GC_OPT_FLAG_DEFAULT, + filename_esc); + xfree (filename_esc); + + es_printf ("nameserver:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("resolver-timeout:%lu:%u\n", + flags | GC_OPT_FLAG_DEFAULT, 0); + } + else if (cmd == aGPGConfVersions) + gpgconf_versions (); + + cleanup (); + return !!rc; +} + + +static void +cleanup (void) +{ + crl_cache_deinit (); + cert_cache_deinit (1); + reload_dns_stuff (1); + +#if USE_LDAP + ldapserver_list_free (opt.ldapservers); +#endif /*USE_LDAP*/ + opt.ldapservers = NULL; + + if (cleanup_socket) + { + cleanup_socket = 0; + if (redir_socket_name) + gnupg_remove (redir_socket_name); + else if (socket_name && *socket_name) + gnupg_remove (socket_name); + } +} + + +void +dirmngr_exit (int rc) +{ + cleanup (); + exit (rc); +} + + +void +dirmngr_init_default_ctrl (ctrl_t ctrl) +{ + ctrl->magic = SERVER_CONTROL_MAGIC; + if (opt.http_proxy) + ctrl->http_proxy = xstrdup (opt.http_proxy); + ctrl->http_no_crl = 1; + ctrl->timeout = opt.connect_timeout; +} + + +void +dirmngr_deinit_default_ctrl (ctrl_t ctrl) +{ + if (!ctrl) + return; + ctrl->magic = 0xdeadbeef; + + xfree (ctrl->http_proxy); + ctrl->http_proxy = NULL; +} + + +/* Create a list of LDAP servers from the file FILENAME. Returns the + list or NULL in case of errors. + + The format fo such a file is line oriented where empty lines and + lines starting with a hash mark are ignored. All other lines are + assumed to be colon seprated with these fields: + + 1. field: Hostname + 2. field: Portnumber + 3. field: Username + 4. field: Password + 5. field: Base DN + +*/ +#if USE_LDAP +static ldap_server_t +parse_ldapserver_file (const char* filename, int ignore_enoent) +{ + char buffer[1024]; + char *p; + ldap_server_t server, serverstart, *serverend; + int c; + unsigned int lineno = 0; + estream_t fp; + + fp = es_fopen (filename, "r"); + if (!fp) + { + if (errno == ENOENT) + { + if (!ignore_enoent) + log_info ("No ldapserver file at: '%s'\n", filename); + } + else + log_error (_("error opening '%s': %s\n"), filename, + strerror (errno)); + return NULL; + } + + serverstart = NULL; + serverend = &serverstart; + while (es_fgets (buffer, sizeof buffer, fp)) + { + lineno++; + if (!*buffer || buffer[strlen(buffer)-1] != '\n') + { + if (*buffer && es_feof (fp)) + ; /* Last line not terminated - continue. */ + else + { + log_error (_("%s:%u: line too long - skipped\n"), + filename, lineno); + while ( (c=es_fgetc (fp)) != EOF && c != '\n') + ; /* Skip until end of line. */ + continue; + } + } + /* Skip empty and comment lines.*/ + for (p=buffer; spacep (p); p++) + ; + if (!*p || *p == '\n' || *p == '#') + continue; + + /* Parse the colon separated fields. */ + server = ldapserver_parse_one (buffer, filename, lineno); + if (server) + { + *serverend = server; + serverend = &server->next; + } + } + + if (es_ferror (fp)) + log_error (_("error reading '%s': %s\n"), filename, strerror (errno)); + es_fclose (fp); + + return serverstart; +} +#endif /*USE_LDAP*/ + +static fingerprint_list_t +parse_ocsp_signer (const char *string) +{ + gpg_error_t err; + char *fname; + estream_t fp; + char line[256]; + char *p; + fingerprint_list_t list, *list_tail, item; + unsigned int lnr = 0; + int c, i, j; + int errflag = 0; + + + /* Check whether this is not a filename and treat it as a direct + fingerprint specification. */ + if (!strpbrk (string, "/.~\\")) + { + item = xcalloc (1, sizeof *item); + for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++) + if ( string[i] != ':' ) + item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i]; + item->hexfpr[j] = 0; + if (j != 40 || !(spacep (string+i) || !string[i])) + { + log_error (_("%s:%u: invalid fingerprint detected\n"), + "--ocsp-signer", 0); + xfree (item); + return NULL; + } + return item; + } + + /* Well, it is a filename. */ + if (*string == '/' || (*string == '~' && string[1] == '/')) + fname = make_filename (string, NULL); + else + { + if (string[0] == '.' && string[1] == '/' ) + string += 2; + fname = make_filename (gnupg_homedir (), string, NULL); + } + + fp = es_fopen (fname, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err)); + xfree (fname); + return NULL; + } + + list = NULL; + list_tail = &list; + for (;;) + { + if (!es_fgets (line, DIM(line)-1, fp) ) + { + if (!es_feof (fp)) + { + err = gpg_error_from_syserror (); + log_error (_("%s:%u: read error: %s\n"), + fname, lnr, gpg_strerror (err)); + errflag = 1; + } + es_fclose (fp); + if (errflag) + { + while (list) + { + fingerprint_list_t tmp = list->next; + xfree (list); + list = tmp; + } + } + xfree (fname); + return list; /* Ready. */ + } + + lnr++; + if (!*line || line[strlen(line)-1] != '\n') + { + /* Eat until end of line. */ + while ( (c=es_getc (fp)) != EOF && c != '\n') + ; + err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG + /* */: GPG_ERR_INCOMPLETE_LINE); + log_error (_("%s:%u: read error: %s\n"), + fname, lnr, gpg_strerror (err)); + errflag = 1; + continue; + } + + /* Allow for empty lines and spaces */ + for (p=line; spacep (p); p++) + ; + if (!*p || *p == '\n' || *p == '#') + continue; + + item = xcalloc (1, sizeof *item); + *list_tail = item; + list_tail = &item->next; + + for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++) + if ( p[i] != ':' ) + item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i]; + item->hexfpr[j] = 0; + if (j != 40 || !(spacep (p+i) || p[i] == '\n')) + { + log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr); + errflag = 1; + } + i++; + while (spacep (p+i)) + i++; + if (p[i] && p[i] != '\n') + log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr); + } + /*NOTREACHED*/ +} + + + + +/* + Stuff used in daemon mode. + */ + + + +/* Reread parts of the configuration. Note, that this function is + obviously not thread-safe and should only be called from the NPTH + signal handler. + + Fixme: Due to the way the argument parsing works, we create a + memory leak here for all string type arguments. There is currently + no clean way to tell whether the memory for the argument has been + allocated or points into the process' original arguments. Unless + we have a mechanism to tell this, we need to live on with this. */ +static void +reread_configuration (void) +{ + ARGPARSE_ARGS pargs; + char *twopart; + int dummy; + + if (!opt.config_filename) + return; /* No config file. */ + + twopart = strconcat (DIRMNGR_NAME EXTSEP_S "conf" PATHSEP_S, + opt.config_filename, NULL); + if (!twopart) + return; /* Out of core. */ + + parse_rereadable_options (NULL, 1); /* Start from the default values. */ + + memset (&pargs, 0, sizeof pargs); + dummy = 0; + pargs.argc = &dummy; + pargs.flags = (ARGPARSE_FLAG_KEEP + |ARGPARSE_FLAG_SYS + |ARGPARSE_FLAG_USER); + while (gnupg_argparser (&pargs, opts, twopart)) + { + if (pargs.r_opt == ARGPARSE_CONFFILE) + { + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + } + else if (pargs.r_opt < -1) + pargs.err = ARGPARSE_PRINT_WARNING; + else /* Try to parse this option - ignore unchangeable ones. */ + parse_rereadable_options (&pargs, 1); + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + xfree (twopart); + post_option_parsing (); +} + + +/* A global function which allows us to trigger the reload stuff from + other places. */ +void +dirmngr_sighup_action (void) +{ + log_info (_("SIGHUP received - " + "re-reading configuration and flushing caches\n")); + reread_configuration (); + cert_cache_deinit (0); + crl_cache_deinit (); + cert_cache_init (hkp_cacert_filenames); + crl_cache_init (); + reload_dns_stuff (0); + ks_hkp_reload (); +} + + +/* This function is called if some network activity was done. At this + * point we know the we have a network and we can decide whether to + * run scheduled background tasks soon. The function should return + * quickly and only trigger actions for another thread. */ +static void +netactivity_action (void) +{ + network_activity_seen = 1; +} + + +/* The signal handler. */ +#ifndef HAVE_W32_SYSTEM +static void +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + dirmngr_sighup_action (); + break; + + case SIGUSR1: + cert_cache_print_stats (); + domaininfo_print_stats (); + break; + + case SIGUSR2: + log_info (_("SIGUSR2 received - no action defined\n")); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info (_("SIGTERM received - shutting down ...\n")); + else + log_info (_("SIGTERM received - still %d active connections\n"), + active_connections); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info (_("shutdown forced\n")); + log_info ("%s %s stopped\n", strusage(11), strusage(13) ); + cleanup (); + dirmngr_exit (0); + } + break; + + case SIGINT: + log_info (_("SIGINT received - immediate shutdown\n")); + log_info( "%s %s stopped\n", strusage(11), strusage(13)); + cleanup (); + dirmngr_exit (0); + break; + + default: + log_info (_("signal %d received - no action defined\n"), signo); + } +} +#endif /*!HAVE_W32_SYSTEM*/ + + +/* Thread to do the housekeeping. */ +static void * +housekeeping_thread (void *arg) +{ + static int sentinel; + time_t curtime; + struct server_control_s ctrlbuf; + + (void)arg; + + curtime = gnupg_get_time (); + if (sentinel) + { + log_info ("housekeeping is already going on\n"); + return NULL; + } + sentinel++; + if (opt.verbose > 1) + log_info ("starting housekeeping\n"); + + memset (&ctrlbuf, 0, sizeof ctrlbuf); + dirmngr_init_default_ctrl (&ctrlbuf); + + dns_stuff_housekeeping (); + ks_hkp_housekeeping (curtime); + if (network_activity_seen) + { + network_activity_seen = 0; + if (opt.allow_version_check) + dirmngr_load_swdb (&ctrlbuf, 0); + workqueue_run_global_tasks (&ctrlbuf, 1); + } + else + workqueue_run_global_tasks (&ctrlbuf, 0); + + dirmngr_deinit_default_ctrl (&ctrlbuf); + + if (opt.verbose > 1) + log_info ("ready with housekeeping\n"); + sentinel--; + return NULL; + +} + + +#if GPGRT_GCC_HAVE_PUSH_PRAGMA +# pragma GCC push_options +# pragma GCC optimize ("no-strict-overflow") +#endif +static int +time_for_housekeeping_p (time_t curtime) +{ + static time_t last_housekeeping; + + if (!last_housekeeping) + last_housekeeping = curtime; + + if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime + || last_housekeeping > curtime /*(be prepared for y2038)*/) + { + last_housekeeping = curtime; + return 1; + } + return 0; +} +#if GPGRT_GCC_HAVE_PUSH_PRAGMA +# pragma GCC pop_options +#endif + + +/* This is the worker for the ticker. It is called every few seconds + and may only do fast operations. */ +static void +handle_tick (void) +{ + struct stat statbuf; + + if (time_for_housekeeping_p (gnupg_get_time ())) + { + npth_t thread; + npth_attr_t tattr; + int err; + + err = npth_attr_init (&tattr); + if (err) + log_error ("error preparing housekeeping thread: %s\n", strerror (err)); + else + { + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + err = npth_create (&thread, &tattr, housekeeping_thread, NULL); + if (err) + log_error ("error spawning housekeeping thread: %s\n", + strerror (err)); + npth_attr_destroy (&tattr); + } + } + + /* Check whether the homedir is still available. */ + if (!shutdown_pending + && gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT) + { + shutdown_pending = 1; + log_info ("homedir has been removed - shutting down\n"); + } +} + + +/* Check the nonce on a new connection. This is a NOP unless 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 function. */ +static void * +start_connection_thread (void *arg) +{ + static unsigned int last_session_id; + unsigned int session_id; + union int_and_ptr_u argval; + gnupg_fd_t fd; + + memset (&argval, 0, sizeof argval); + argval.aptr = arg; + fd = argval.afd; + + if (check_nonce (fd, &socket_nonce)) + { + log_error ("handler nonce check FAILED\n"); + return NULL; + } + +#ifndef HAVE_W32_SYSTEM + npth_setspecific (my_tlskey_current_fd, argval.aptr); +#endif + + active_connections++; + if (opt.verbose) + log_info (_("handler for fd %d started\n"), FD2INT (fd)); + + session_id = ++last_session_id; + if (!session_id) + session_id = ++last_session_id; + start_command_handler (fd, session_id); + + if (opt.verbose) + log_info (_("handler for fd %d terminated\n"), FD2INT (fd)); + active_connections--; + + workqueue_run_post_session_tasks (session_id); + +#ifndef HAVE_W32_SYSTEM + argval.afd = ASSUAN_INVALID_FD; + npth_setspecific (my_tlskey_current_fd, argval.aptr); +#endif + + return NULL; +} + + +#ifdef HAVE_INOTIFY_INIT +/* Read an inotify event and return true if it matches NAME. */ +static int +my_inotify_is_name (int fd, const char *name) +{ + union { + struct inotify_event ev; + char _buf[sizeof (struct inotify_event) + 100 + 1]; + } buf; + int n; + const char *s; + + s = strrchr (name, '/'); + if (s && s[1]) + name = s + 1; + + n = npth_read (fd, &buf, sizeof buf); + if (n < sizeof (struct inotify_event)) + return 0; + if (buf.ev.len < strlen (name)+1) + return 0; + if (strcmp (buf.ev.name, name)) + return 0; /* Not the desired file. */ + + return 1; /* Found. */ +} +#endif /*HAVE_INOTIFY_INIT*/ + + +/* Main loop in daemon mode. Note that LISTEN_FD will be owned by + * this function. */ +static void +handle_connections (assuan_fd_t listen_fd) +{ + npth_attr_t tattr; +#ifndef HAVE_W32_SYSTEM + int signo; +#endif + struct sockaddr_un paddr; + socklen_t plen = sizeof( paddr ); + int nfd, ret; + fd_set fdset, read_fdset; + struct timespec abstime; + struct timespec curtime; + struct timespec timeout; + int saved_errno; + int my_inotify_fd = -1; + + npth_attr_init (&tattr); + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + +#ifndef HAVE_W32_SYSTEM /* FIXME */ + npth_sigev_init (); + npth_sigev_add (SIGHUP); + npth_sigev_add (SIGUSR1); + npth_sigev_add (SIGUSR2); + npth_sigev_add (SIGINT); + npth_sigev_add (SIGTERM); + npth_sigev_fini (); +#endif + +#ifdef HAVE_INOTIFY_INIT + if (disable_check_own_socket) + my_inotify_fd = -1; + else if ((my_inotify_fd = inotify_init ()) == -1) + log_info ("error enabling fast daemon termination: %s\n", + strerror (errno)); + else + { + /* We need to watch the directory for the file because there + * won't be an IN_DELETE_SELF for a socket file. */ + char *slash = strrchr (socket_name, '/'); + log_assert (slash && slash[1]); + *slash = 0; + if (inotify_add_watch (my_inotify_fd, socket_name, IN_DELETE) == -1) + { + close (my_inotify_fd); + my_inotify_fd = -1; + } + *slash = '/'; + } +#endif /*HAVE_INOTIFY_INIT*/ + + + /* Setup the fdset. It has only one member. This is because we use + pth_select instead of pth_accept to properly sync timeouts with + to full second. */ + FD_ZERO (&fdset); + FD_SET (FD2INT (listen_fd), &fdset); + nfd = FD2INT (listen_fd); + if (my_inotify_fd != -1) + { + FD_SET (my_inotify_fd, &fdset); + if (my_inotify_fd > nfd) + nfd = my_inotify_fd; + } + + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + + /* Main loop. */ + for (;;) + { + /* Shutdown test. */ + if (shutdown_pending) + { + if (!active_connections) + break; /* ready */ + + /* Do not accept new connections but keep on running the + * loop to cope with the timer events. + * + * Note that we do not close the listening socket because a + * client trying to connect to that socket would instead + * restart a new dirmngr instance - which is unlikely the + * intention of a shutdown. */ + /* assuan_sock_close (listen_fd); */ + /* listen_fd = -1; */ + FD_ZERO (&fdset); + nfd = -1; + if (my_inotify_fd != -1) + { + FD_SET (my_inotify_fd, &fdset); + nfd = my_inotify_fd; + } + } + + /* Take a copy of the fdset. */ + read_fdset = fdset; + + npth_clock_gettime (&curtime); + if (!(npth_timercmp (&curtime, &abstime, <))) + { + /* Timeout. When a shutdown is pending we use a shorter + * interval to handle the shutdown more quickly. */ + handle_tick (); + npth_clock_gettime (&abstime); + abstime.tv_sec += (shutdown_pending + ? TIMERTICK_INTERVAL_SHUTDOWN + : TIMERTICK_INTERVAL); + } + npth_timersub (&abstime, &curtime, &timeout); + +#ifndef HAVE_W32_SYSTEM + ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + npth_sigev_sigmask()); + saved_errno = errno; + + while (npth_sigev_get_pending(&signo)) + handle_signal (signo); +#else + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, NULL, NULL); + saved_errno = errno; +#endif + + if (ret == -1 && saved_errno != EINTR) + { + log_error (_("npth_pselect failed: %s - waiting 1s\n"), + strerror (saved_errno)); + npth_sleep (1); + continue; + } + + if (ret <= 0) + { + /* Interrupt or timeout. Will be handled when calculating the + next timeout. */ + continue; + } + + if (shutdown_pending) + { + /* Do not anymore accept connections. */ + continue; + } + +#ifdef HAVE_INOTIFY_INIT + if (my_inotify_fd != -1 && FD_ISSET (my_inotify_fd, &read_fdset) + && my_inotify_is_name (my_inotify_fd, socket_name)) + { + shutdown_pending = 1; + log_info ("socket file has been removed - shutting down\n"); + } +#endif /*HAVE_INOTIFY_INIT*/ + + if (FD_ISSET (FD2INT (listen_fd), &read_fdset)) + { + gnupg_fd_t fd; + + plen = sizeof paddr; + fd = INT2FD (npth_accept (FD2INT(listen_fd), + (struct sockaddr *)&paddr, &plen)); + if (fd == GNUPG_INVALID_FD) + { + log_error ("accept failed: %s\n", strerror (errno)); + } + else + { + char threadname[50]; + union int_and_ptr_u argval; + npth_t thread; + + memset (&argval, 0, sizeof argval); + argval.afd = fd; + snprintf (threadname, sizeof threadname, + "conn fd=%d", FD2INT(fd)); + + ret = npth_create (&thread, &tattr, + start_connection_thread, argval.aptr); + if (ret) + { + log_error ("error spawning connection handler: %s\n", + strerror (ret) ); + assuan_sock_close (fd); + } + npth_setname_np (thread, threadname); + } + } + } + +#ifdef HAVE_INOTIFY_INIT + if (my_inotify_fd != -1) + close (my_inotify_fd); +#endif /*HAVE_INOTIFY_INIT*/ + npth_attr_destroy (&tattr); + if (listen_fd != GNUPG_INVALID_FD) + assuan_sock_close (listen_fd); + cleanup (); + log_info ("%s %s stopped\n", strusage(11), strusage(13)); +} + +const char* +dirmngr_get_current_socket_name (void) +{ + if (socket_name) + return socket_name; + else + return dirmngr_socket_name (); +} + + + +/* Parse the revision part from the extended version blurb. */ +static const char * +get_revision_from_blurb (const char *blurb, int *r_len) +{ + const char *s = blurb? blurb : ""; + int n; + + for (; *s; s++) + if (*s == '\n' && s[1] == '(') + break; + if (*s) + { + s += 2; + for (n=0; s[n] && s[n] != ' '; n++) + ; + } + else + { + s = "?"; + n = 1; + } + *r_len = n; + return s; +} + + +/* Print versions of dirmngr and used libraries. This is used by + * "gpgconf --show-versions" so that there is no need to link gpgconf + * against all these libraries. This is an internal API and should + * not be relied upon. */ +static void +gpgconf_versions (void) +{ + const char *s; + int n; + + /* Unfortunately Npth has no way to get the version. */ + + s = get_revision_from_blurb (assuan_check_version ("\x01\x01"), &n); + es_fprintf (es_stdout, "* Libassuan %s (%.*s)\n\n", + assuan_check_version (NULL), n, s); + + s = get_revision_from_blurb (ksba_check_version ("\x01\x01"), &n); + es_fprintf (es_stdout, "* KSBA %s (%.*s)\n\n", + ksba_check_version (NULL), n, s); + +#ifdef HTTP_USE_NTBTLS + s = get_revision_from_blurb (ntbtls_check_version ("\x01\x01"), &n); + es_fprintf (es_stdout, "* NTBTLS %s (%.*s)\n\n", + ntbtls_check_version (NULL), n, s); +#elif HTTP_USE_GNUTLS + es_fprintf (es_stdout, "* GNUTLS %s\n\n", + gnutls_check_version (NULL)); +#endif + +} diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h new file mode 100644 index 0000000..1b52a1d --- /dev/null +++ b/dirmngr/dirmngr.h @@ -0,0 +1,271 @@ +/* dirmngr.h - Common definitions for the dirmngr + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2004, 2015 g10 Code GmbH + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0+ + */ + +#ifndef DIRMNGR_H +#define DIRMNGR_H + +#include "./dirmngr-err.h" +#define map_assuan_err(a) \ + map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) +#include <errno.h> +#include <gcrypt.h> +#include <ksba.h> + +#include "../common/util.h" +#include "../common/membuf.h" +#include "../common/sysutils.h" /* (gnupg_fd_t) */ +#include "../common/asshelp.h" /* (assuan_context_t) */ +#include "../common/i18n.h" +#include "dirmngr-status.h" +#include "http.h" /* (parsed_uri_t) */ + +/* This objects keeps information about a particular LDAP server and + is used as item of a single linked list of servers. */ +struct ldap_server_s +{ + struct ldap_server_s* next; + + char *host; + int port; + char *user; + char *pass; + char *base; +}; +typedef struct ldap_server_s *ldap_server_t; + + +/* This objects is used to build a list of URI consisting of the + original and the parsed URI. */ +struct uri_item_s +{ + struct uri_item_s *next; + parsed_uri_t parsed_uri; /* The broken down URI. */ + char uri[1]; /* The original URI. */ +}; +typedef struct uri_item_s *uri_item_t; + + +/* A list of fingerprints. */ +struct fingerprint_list_s; +typedef struct fingerprint_list_s *fingerprint_list_t; +struct fingerprint_list_s +{ + fingerprint_list_t next; + char hexfpr[20+20+1]; +}; + + +/* A large struct named "opt" to keep global flags. */ +EXTERN_UNLESS_MAIN_MODULE +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_cache; /* Dir 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 running_detached; /* We are running in detached mode. */ + int allow_version_check; /* --allow-version-check is active. */ + + int force; /* Force loading outdated CRLs. */ + + + unsigned int connect_timeout; /* Timeout for connect. */ + unsigned int connect_quick_timeout; /* Shorter timeout for connect. */ + + int disable_http; /* Do not use HTTP at all. */ + int disable_ldap; /* Do not use LDAP at all. */ + int disable_ipv4; /* Do not use legacy IP addresses. */ + int disable_ipv6; /* Do not use standard IP addresses. */ + int honor_http_proxy; /* Honor the http_proxy env variable. */ + const char *http_proxy; /* The default 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. */ + + strlist_t keyserver; /* List of default keyservers. */ +} opt; + + +#define DBG_X509_VALUE 1 /* debug x.509 parsing */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_DNS_VALUE 16 /* debug DNS calls. */ +#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_IPC_VALUE 1024 /* debug assuan communication */ +#define DBG_NETWORK_VALUE 2048 /* debug network I/O. */ +#define DBG_LOOKUP_VALUE 8192 /* debug lookup details */ +#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */ + +#define DBG_X509 (opt.debug & DBG_X509_VALUE) +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_DNS (opt.debug & DBG_DNS_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_IPC (opt.debug & DBG_IPC_VALUE) +#define DBG_NETWORK (opt.debug & DBG_NETWORK_VALUE) +#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE) +#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE) + +/* A simple list of certificate references. FIXME: Better use + certlist_t also for references (Store NULL at .cert) */ +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; + +#if SIZEOF_UNSIGNED_LONG == 8 +# define SERVER_CONTROL_MAGIC 0x6469726d6e677220 +#else +# define SERVER_CONTROL_MAGIC 0x6469726d +#endif + +/* Connection control structure. */ +struct server_control_s +{ + unsigned long magic;/* Always has SERVER_CONTROL_MAGIC. */ + 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. */ + char *http_proxy; /* The used http_proxy or NULL. */ + + unsigned int timeout; /* Timeout for connect calls in ms. */ + + unsigned int http_no_crl:1; /* Do not check CRLs for https. */ +}; + + +/*-- dirmngr.c --*/ +void dirmngr_exit( int ); /* Wrapper for exit() */ +void dirmngr_init_default_ctrl (ctrl_t ctrl); +void dirmngr_deinit_default_ctrl (ctrl_t ctrl); +void dirmngr_sighup_action (void); +const char* dirmngr_get_current_socket_name (void); +int dirmngr_use_tor (void); + +/*-- Various housekeeping functions. --*/ +void ks_hkp_housekeeping (time_t curtime); +void ks_hkp_reload (void); + + +/*-- 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); +int dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, + const char *msg); +void start_command_handler (gnupg_fd_t fd, unsigned int session_id); +gpg_error_t dirmngr_tick (ctrl_t ctrl); + +/* (See also dirmngr-status.h) */ + +/*-- http-ntbtls.c --*/ +/* Note that we don't use a callback for gnutls. */ + +gpg_error_t gnupg_http_tls_verify_cb (void *opaque, + http_t http, + http_session_t session, + unsigned int flags, + void *tls_context); + + +/*-- loadswdb.c --*/ +gpg_error_t dirmngr_load_swdb (ctrl_t ctrl, int force); + + +/*-- domaininfo.c --*/ +void domaininfo_print_stats (void); +int domaininfo_is_wkd_not_supported (const char *domain); +void domaininfo_set_no_name (const char *domain); +void domaininfo_set_wkd_supported (const char *domain); +void domaininfo_set_wkd_not_supported (const char *domain); +void domaininfo_set_wkd_not_found (const char *domain); + +/*-- workqueue.c --*/ +typedef const char *(*wqtask_t)(ctrl_t ctrl, const char *args); + +void workqueue_dump_queue (ctrl_t ctrl); +gpg_error_t workqueue_add_task (wqtask_t func, const char *args, + unsigned int session_id, int need_network); +void workqueue_run_global_tasks (ctrl_t ctrl, int with_network); +void workqueue_run_post_session_tasks (unsigned int session_id); + + + +#endif /*DIRMNGR_H*/ diff --git a/dirmngr/dirmngr.w32-manifest.in b/dirmngr/dirmngr.w32-manifest.in new file mode 100644 index 0000000..719ca97 --- /dev/null +++ b/dirmngr/dirmngr.w32-manifest.in @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> +<description>GNU Privacy Guard (Network daemon)</description> +<assemblyIdentity + type="win32" + name="GnuPG.dirmngr" + version="@BUILD_VERSION@" + /> +<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> + <application> + <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/><!-- 10 --> + <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/><!-- 8.1 --> + <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/><!-- 8 --> + <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><!-- 7 --> + <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/><!-- Vista --> + </application> +</compatibility> +</assembly> diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c new file mode 100644 index 0000000..8049590 --- /dev/null +++ b/dirmngr/dirmngr_ldap.c @@ -0,0 +1,775 @@ +/* dirmngr-ldap.c - The LDAP helper for dirmngr. + * Copyright (C) 2004 g10 Code GmbH + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif +#include <errno.h> +#include <sys/time.h> +#include <unistd.h> +#ifndef USE_LDAPWRAPPER +# include <npth.h> +#endif + +#ifdef HAVE_W32_SYSTEM +# include <winsock2.h> +# include <winldap.h> +# include <winber.h> +# include <fcntl.h> +# include "ldap-url.h" +#else + /* For OpenLDAP, to enable the API that we're using. */ +# define LDAP_DEPRECATED 1 +# include <ldap.h> +#endif + + +#include <gpg-error.h> +#include "../common/logging.h" +#include "../common/argparse.h" +#include "../common/stringhelp.h" +#include "../common/mischelp.h" +#include "../common/strlist.h" + +#include "../common/i18n.h" +#include "../common/util.h" +#include "../common/init.h" + +/* With the ldap wrapper, there is no need for the npth_unprotect and leave + functions; thus we redefine them to nops. If we are not using the + ldap wrapper process we need to include the prototype for our + module's main function. */ +#ifdef USE_LDAPWRAPPER +static void npth_unprotect (void) { } +static void npth_protect (void) { } +#else +# include "./ldap-wrapper.h" +#endif + +#ifdef HAVE_W32CE_SYSTEM +# include "w32-ldap-help.h" +# define my_ldap_init(a,b) \ + _dirmngr_ldap_init ((a), (b)) +# define my_ldap_simple_bind_s(a,b,c) \ + _dirmngr_ldap_simple_bind_s ((a),(b),(c)) +# define my_ldap_search_st(a,b,c,d,e,f,g,h) \ + _dirmngr_ldap_search_st ((a), (b), (c), (d), (e), (f), (g), (h)) +# define my_ldap_first_attribute(a,b,c) \ + _dirmngr_ldap_first_attribute ((a),(b),(c)) +# define my_ldap_next_attribute(a,b,c) \ + _dirmngr_ldap_next_attribute ((a),(b),(c)) +# define my_ldap_get_values_len(a,b,c) \ + _dirmngr_ldap_get_values_len ((a),(b),(c)) +# define my_ldap_free_attr(a) \ + xfree ((a)) +#else +# define my_ldap_init(a,b) ldap_init ((a), (b)) +# define my_ldap_simple_bind_s(a,b,c) ldap_simple_bind_s ((a), (b), (c)) +# define my_ldap_search_st(a,b,c,d,e,f,g,h) \ + ldap_search_st ((a), (b), (c), (d), (e), (f), (g), (h)) +# define my_ldap_first_attribute(a,b,c) ldap_first_attribute ((a),(b),(c)) +# define my_ldap_next_attribute(a,b,c) ldap_next_attribute ((a),(b),(c)) +# define my_ldap_get_values_len(a,b,c) ldap_get_values_len ((a),(b),(c)) +# define my_ldap_free_attr(a) ldap_memfree ((a)) +#endif + +#ifdef HAVE_W32_SYSTEM + typedef LDAP_TIMEVAL my_ldap_timeval_t; +#else + typedef struct timeval my_ldap_timeval_t; +#endif + +#define DEFAULT_LDAP_TIMEOUT 15 /* 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, "@"}, + ARGPARSE_end () +}; + + +/* A structure with module options. This is not a static variable + because if we are not build as a standalone binary, each thread + using this module needs to handle its own values. */ +struct my_opt_s +{ + int quiet; + int verbose; + my_ldap_timeval_t timeout;/* Timeout for the LDAP search functions. */ + unsigned int alarm_timeout; /* And for the alarm based timeout. */ + int multi; + + estream_t outstream; /* Send output to this stream. */ + + /* Note that we can't use const for the strings because ldap_* are + not defined that way. */ + char *proxy; /* Host and Port override. */ + char *user; /* Authentication user. */ + char *pass; /* Authentication password. */ + char *host; /* Override host. */ + int port; /* Override port. */ + char *dn; /* Override DN. */ + char *filter;/* Override filter. */ + char *attr; /* Override attribute. */ +}; +typedef struct my_opt_s *my_opt_t; + + +/* Prototypes. */ +#ifndef HAVE_W32_SYSTEM +static void catch_alarm (int dummy); +#endif +static int process_url (my_opt_t myopt, const char *url); + + + +/* Function called by argparse.c to display information. */ +#ifdef USE_LDAPWRAPPER +static const char * +my_strusage (int level) +{ + const char *p; + + switch(level) + { + case 9: p = "GPL-3.0-or-later"; break; + case 11: p = "dirmngr_ldap (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + case 49: p = PACKAGE_BUGREPORT; break; + case 1: + case 40: p = + _("Usage: dirmngr_ldap [options] [URL] (-h for help)\n"); + break; + case 41: p = + _("Syntax: dirmngr_ldap [options] [URL]\n" + "Internal LDAP helper for Dirmngr\n" + "Interface and options may change without notice\n"); + break; + + default: p = NULL; + } + return p; +} +#endif /*!USE_LDAPWRAPPER*/ + + +int +#ifdef USE_LDAPWRAPPER +main (int argc, char **argv) +#else +ldap_wrapper_main (char **argv, estream_t outstream) +#endif +{ +#ifndef USE_LDAPWRAPPER + int argc; +#endif + ARGPARSE_ARGS pargs; + int any_err = 0; + char *p; + int only_search_timeout = 0; + struct my_opt_s my_opt_buffer; + my_opt_t myopt = &my_opt_buffer; + char *malloced_buffer1 = NULL; + + memset (&my_opt_buffer, 0, sizeof my_opt_buffer); + + early_system_init (); + +#ifdef USE_LDAPWRAPPER + set_strusage (my_strusage); + log_set_prefix ("dirmngr_ldap", GPGRT_LOG_WITH_PREFIX); + + /* Setup I18N and common subsystems. */ + i18n_init(); + + init_common_subsystems (&argc, &argv); + + es_set_binary (es_stdout); + myopt->outstream = es_stdout; +#else /*!USE_LDAPWRAPPER*/ + myopt->outstream = outstream; + for (argc=0; argv[argc]; argc++) + ; +#endif /*!USE_LDAPWRAPPER*/ + + /* LDAP defaults */ + myopt->timeout.tv_sec = DEFAULT_LDAP_TIMEOUT; + myopt->timeout.tv_usec = 0; + myopt->alarm_timeout = 0; + + /* Parse the command line. */ + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= ARGPARSE_FLAG_KEEP; + while (gnupg_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oVerbose: myopt->verbose++; break; + case oQuiet: myopt->quiet++; break; + case oTimeout: + myopt->timeout.tv_sec = pargs.r.ret_int; + myopt->timeout.tv_usec = 0; + myopt->alarm_timeout = pargs.r.ret_int; + break; + case oOnlySearchTimeout: only_search_timeout = 1; break; + case oMulti: myopt->multi = 1; break; + case oUser: myopt->user = pargs.r.ret_str; break; + case oPass: myopt->pass = pargs.r.ret_str; break; + case oEnvPass: + myopt->pass = getenv ("DIRMNGR_LDAP_PASS"); + break; + case oProxy: myopt->proxy = pargs.r.ret_str; break; + case oHost: myopt->host = pargs.r.ret_str; break; + case oPort: myopt->port = pargs.r.ret_int; break; + case oDN: myopt->dn = pargs.r.ret_str; break; + case oFilter: myopt->filter = pargs.r.ret_str; break; + case oAttr: myopt->attr = pargs.r.ret_str; break; + case oLogWithPID: + { + unsigned int oldflags; + log_get_prefix (&oldflags); + log_set_prefix (NULL, oldflags | GPGRT_LOG_WITH_PID); + } + break; + + default : +#ifdef USE_LDAPWRAPPER + pargs.err = ARGPARSE_PRINT_ERROR; +#else + pargs.err = ARGPARSE_PRINT_WARNING; /* No exit() please. */ +#endif + break; + } + } + gnupg_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (only_search_timeout) + myopt->alarm_timeout = 0; + + if (myopt->proxy) + { + malloced_buffer1 = xtrystrdup (myopt->proxy); + if (!malloced_buffer1) + { + log_error ("error copying string: %s\n", strerror (errno)); + return 1; + } + myopt->host = malloced_buffer1; + p = strchr (myopt->host, ':'); + if (p) + { + *p++ = 0; + myopt->port = atoi (p); + } + if (!myopt->port) + myopt->port = 389; /* make sure ports gets overridden. */ + } + + if (myopt->port < 0 || myopt->port > 65535) + log_error (_("invalid port number %d\n"), myopt->port); + +#ifdef USE_LDAPWRAPPER + if (log_get_errorcount (0)) + exit (2); + if (argc < 1) + usage (1); +#else + /* All passed arguments should be fine in this case. */ + log_assert (argc); +#endif + +#ifdef USE_LDAPWRAPPER + if (myopt->alarm_timeout) + { +#ifndef HAVE_W32_SYSTEM +# if defined(HAVE_SIGACTION) && defined(HAVE_STRUCT_SIGACTION) + struct sigaction act; + + act.sa_handler = catch_alarm; + sigemptyset (&act.sa_mask); + act.sa_flags = 0; + if (sigaction (SIGALRM,&act,NULL)) +# else + if (signal (SIGALRM, catch_alarm) == SIG_ERR) +# endif + log_fatal ("unable to register timeout handler\n"); +#endif + } +#endif /*USE_LDAPWRAPPER*/ + + for (; argc; argc--, argv++) + if (process_url (myopt, *argv)) + any_err = 1; + + xfree (malloced_buffer1); + return any_err; +} + +#ifndef HAVE_W32_SYSTEM +static void +catch_alarm (int dummy) +{ + (void)dummy; + _exit (10); +} +#endif + + +#ifdef HAVE_W32_SYSTEM +static DWORD CALLBACK +alarm_thread (void *arg) +{ + HANDLE timer = arg; + + WaitForSingleObject (timer, INFINITE); + _exit (10); + + return 0; +} +#endif + + +static void +set_timeout (my_opt_t myopt) +{ + if (myopt->alarm_timeout) + { +#ifdef HAVE_W32_SYSTEM + static HANDLE timer; + LARGE_INTEGER due_time; + + /* A negative value is a relative time. */ + due_time.QuadPart = (unsigned long long)-10000000 * myopt->alarm_timeout; + + if (!timer) + { + SECURITY_ATTRIBUTES sec_attr; + DWORD tid; + + memset (&sec_attr, 0, sizeof sec_attr); + sec_attr.nLength = sizeof sec_attr; + sec_attr.bInheritHandle = FALSE; + + /* Create a manual resetable timer. */ + timer = CreateWaitableTimer (NULL, TRUE, NULL); + /* Intially set the timer. */ + SetWaitableTimer (timer, &due_time, 0, NULL, NULL, 0); + + if (CreateThread (&sec_attr, 0, alarm_thread, timer, 0, &tid)) + log_error ("failed to create alarm thread\n"); + } + else /* Retrigger the timer. */ + SetWaitableTimer (timer, &due_time, 0, NULL, NULL, 0); +#else + alarm (myopt->alarm_timeout); +#endif + } +} + + +/* Helper for fetch_ldap(). */ +static int +print_ldap_entries (my_opt_t myopt, LDAP *ld, LDAPMessage *msg, char *want_attr) +{ + LDAPMessage *item; + int any = 0; + + for (npth_unprotect (), item = ldap_first_entry (ld, msg), npth_protect (); + item; + npth_unprotect (), item = ldap_next_entry (ld, item), npth_protect ()) + { + BerElement *berctx; + char *attr; + + if (myopt->verbose > 1) + log_info (_("scanning result for attribute '%s'\n"), + want_attr? want_attr : "[all]"); + + if (myopt->multi) + { /* Write item marker. */ + if (es_fwrite ("I\0\0\0\0", 5, 1, myopt->outstream) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + return -1; + } + } + + + for (npth_unprotect (), attr = my_ldap_first_attribute (ld, item, &berctx), + npth_protect (); + attr; + npth_unprotect (), attr = my_ldap_next_attribute (ld, item, berctx), + npth_protect ()) + { + struct berval **values; + int idx; + + if (myopt->verbose > 1) + log_info (_(" available attribute '%s'\n"), attr); + + set_timeout (myopt); + + /* I case we want only one attribute we do a case + insensitive compare without the optional extension + (i.e. ";binary"). Case insensitive is not really correct + but the best we can do. */ + if (want_attr) + { + char *cp1, *cp2; + int cmpres; + + cp1 = strchr (want_attr, ';'); + if (cp1) + *cp1 = 0; + cp2 = strchr (attr, ';'); + if (cp2) + *cp2 = 0; + cmpres = ascii_strcasecmp (want_attr, attr); + if (cp1) + *cp1 = ';'; + if (cp2) + *cp2 = ';'; + if (cmpres) + { + my_ldap_free_attr (attr); + continue; /* Not found: Try next attribute. */ + } + } + + npth_unprotect (); + values = my_ldap_get_values_len (ld, item, attr); + npth_protect (); + + if (!values) + { + if (myopt->verbose) + log_info (_("attribute '%s' not found\n"), attr); + my_ldap_free_attr (attr); + continue; + } + + if (myopt->verbose) + { + log_info (_("found attribute '%s'\n"), attr); + if (myopt->verbose > 1) + for (idx=0; values[idx]; idx++) + log_info (" length[%d]=%d\n", + idx, (int)values[0]->bv_len); + + } + + if (myopt->multi) + { /* Write attribute marker. */ + unsigned char tmp[5]; + size_t n = strlen (attr); + + tmp[0] = 'A'; + tmp[1] = (n >> 24); + tmp[2] = (n >> 16); + tmp[3] = (n >> 8); + tmp[4] = (n); + if (es_fwrite (tmp, 5, 1, myopt->outstream) != 1 + || es_fwrite (attr, n, 1, myopt->outstream) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + ldap_value_free_len (values); + my_ldap_free_attr (attr); + ber_free (berctx, 0); + return -1; + } + } + + for (idx=0; values[idx]; idx++) + { + if (myopt->multi) + { /* Write value marker. */ + unsigned char tmp[5]; + size_t n = values[0]->bv_len; + + tmp[0] = 'V'; + tmp[1] = (n >> 24); + tmp[2] = (n >> 16); + tmp[3] = (n >> 8); + tmp[4] = (n); + + if (es_fwrite (tmp, 5, 1, myopt->outstream) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + ldap_value_free_len (values); + my_ldap_free_attr (attr); + ber_free (berctx, 0); + return -1; + } + } + + if (es_fwrite (values[0]->bv_val, values[0]->bv_len, + 1, myopt->outstream) != 1) + { + log_error (_("error writing to stdout: %s\n"), + strerror (errno)); + ldap_value_free_len (values); + my_ldap_free_attr (attr); + ber_free (berctx, 0); + return -1; + } + + any = 1; + if (!myopt->multi) + break; /* Print only the first value. */ + } + ldap_value_free_len (values); + my_ldap_free_attr (attr); + if (want_attr || !myopt->multi) + break; /* We only want to return the first attribute. */ + } + ber_free (berctx, 0); + } + + if (myopt->verbose > 1 && any) + log_info ("result has been printed\n"); + + return any?0:-1; +} + + + +/* Helper for the URL based LDAP query. */ +static int +fetch_ldap (my_opt_t myopt, const char *url, const LDAPURLDesc *ludp) +{ + LDAP *ld; + LDAPMessage *msg; + int rc = 0; + char *host, *dn, *filter, *attrs[2], *attr; + int port; + int ret; + + host = myopt->host? myopt->host : ludp->lud_host; + port = myopt->port? myopt->port : ludp->lud_port; + dn = myopt->dn? myopt->dn : ludp->lud_dn; + filter = myopt->filter? myopt->filter : ludp->lud_filter; + attrs[0] = myopt->attr? myopt->attr : ludp->lud_attrs? ludp->lud_attrs[0]:NULL; + attrs[1] = NULL; + attr = attrs[0]; + + if (!port) + port = (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))? 636:389; + + if (myopt->verbose) + { + log_info (_("processing url '%s'\n"), url); + if (myopt->user) + log_info (_(" user '%s'\n"), myopt->user); + if (myopt->pass) + log_info (_(" pass '%s'\n"), *myopt->pass?"*****":""); + if (host) + log_info (_(" host '%s'\n"), host); + log_info (_(" port %d\n"), port); + if (dn) + log_info (_(" DN '%s'\n"), dn); + if (filter) + log_info (_(" filter '%s'\n"), filter); + if (myopt->multi && !myopt->attr && ludp->lud_attrs) + { + int i; + for (i=0; ludp->lud_attrs[i]; i++) + log_info (_(" attr '%s'\n"), ludp->lud_attrs[i]); + } + else if (attr) + log_info (_(" attr '%s'\n"), attr); + } + + + if (!host || !*host) + { + log_error (_("no host name in '%s'\n"), url); + return -1; + } + if (!myopt->multi && !attr) + { + log_error (_("no attribute given for query '%s'\n"), url); + return -1; + } + + if (!myopt->multi && !myopt->attr + && ludp->lud_attrs && ludp->lud_attrs[0] && ludp->lud_attrs[1]) + log_info (_("WARNING: using first attribute only\n")); + + + set_timeout (myopt); + npth_unprotect (); + ld = my_ldap_init (host, port); + npth_protect (); + if (!ld) + { + log_error (_("LDAP init to '%s:%d' failed: %s\n"), + host, port, strerror (errno)); + return -1; + } + npth_unprotect (); + /* Fixme: Can we use MYOPT->user or is it shared with other theeads?. */ + ret = my_ldap_simple_bind_s (ld, myopt->user, myopt->pass); + npth_protect (); +#ifdef LDAP_VERSION3 + if (ret == LDAP_PROTOCOL_ERROR) + { + /* Protocol error could mean that the server only supports v3. */ + int version = LDAP_VERSION3; + if (myopt->verbose) + log_info ("protocol error; retrying bind with v3 protocol\n"); + npth_unprotect (); + ldap_set_option (ld, LDAP_OPT_PROTOCOL_VERSION, &version); + ret = my_ldap_simple_bind_s (ld, myopt->user, myopt->pass); + npth_protect (); + } +#endif + if (ret) + { + log_error (_("binding to '%s:%d' failed: %s\n"), + host, port, ldap_err2string (ret)); + ldap_unbind (ld); + return -1; + } + + set_timeout (myopt); + npth_unprotect (); + rc = my_ldap_search_st (ld, dn, ludp->lud_scope, filter, + myopt->multi && !myopt->attr && ludp->lud_attrs? + ludp->lud_attrs:attrs, + 0, + &myopt->timeout, &msg); + npth_protect (); + if (rc == LDAP_SIZELIMIT_EXCEEDED && myopt->multi) + { + if (es_fwrite ("E\0\0\0\x09truncated", 14, 1, myopt->outstream) != 1) + { + log_error (_("error writing to stdout: %s\n"), strerror (errno)); + return -1; + } + } + else if (rc) + { +#ifdef HAVE_W32CE_SYSTEM + log_error ("searching '%s' failed: %d\n", url, rc); +#else + log_error (_("searching '%s' failed: %s\n"), + url, ldap_err2string (rc)); +#endif + if (rc != LDAP_NO_SUCH_OBJECT) + { + /* FIXME: Need deinit (ld)? */ + /* Hmmm: Do we need to released MSG in case of an error? */ + return -1; + } + } + + rc = print_ldap_entries (myopt, ld, msg, myopt->multi? NULL:attr); + + ldap_msgfree (msg); + ldap_unbind (ld); + return rc; +} + + + + +/* Main processing. Take the URL and run the LDAP query. The result + is printed to stdout, errors are logged to the log stream. */ +static int +process_url (my_opt_t myopt, const char *url) +{ + int rc; + LDAPURLDesc *ludp = NULL; + + + if (!ldap_is_ldap_url (url)) + { + log_error (_("'%s' is not an LDAP URL\n"), url); + return -1; + } + + if (ldap_url_parse (url, &ludp)) + { + log_error (_("'%s' is an invalid LDAP URL\n"), url); + return -1; + } + + rc = fetch_ldap (myopt, url, ludp); + + ldap_free_urldesc (ludp); + return rc; +} diff --git a/dirmngr/dns-stuff.c b/dirmngr/dns-stuff.c new file mode 100644 index 0000000..4a50d75 --- /dev/null +++ b/dirmngr/dns-stuff.c @@ -0,0 +1,2478 @@ +/* dns-stuff.c - DNS related code including CERT RR (rfc-4398) + * Copyright (C) 2003, 2005, 2006, 2009 Free Software Foundation, Inc. + * Copyright (C) 2005, 2006, 2009, 2015. 2016 Werner Koch + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * This file 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <sys/types.h> +#ifdef HAVE_W32_SYSTEM +# define WIN32_LEAN_AND_MEAN +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> +# include <iphlpapi.h> +#else +# if HAVE_SYSTEM_RESOLVER +# include <netinet/in.h> +# include <arpa/nameser.h> +# include <resolv.h> +# endif +# include <netdb.h> +#endif +#ifdef HAVE_STAT +# include <sys/stat.h> +#endif +#include <string.h> +#include <unistd.h> + + +/* William Ahern's DNS library, included as a source copy. */ +#ifdef USE_LIBDNS +# include "dns.h" +#endif + +/* dns.c has a dns_p_free but it is not exported. We use our own + * wrapper here so that we do not accidentally use xfree which would + * be wrong for dns.c allocated data. */ +#define dns_free(a) free ((a)) + + +#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */ +# undef USE_NPTH +#endif +#ifdef USE_NPTH +# include <npth.h> +#endif + +#include "./dirmngr-err.h" +#include "../common/util.h" +#include "../common/host2net.h" +#include "dns-stuff.h" + +#ifdef USE_NPTH +# define my_unprotect() npth_unprotect () +# define my_protect() npth_protect () +#else +# define my_unprotect() do { } while(0) +# define my_protect() do { } while(0) +#endif + +/* We allow the use of 0 instead of AF_UNSPEC - check this assumption. */ +#if AF_UNSPEC != 0 +# error AF_UNSPEC does not have the value 0 +#endif + +/* Windows does not support the AI_ADDRCONFIG flag - use zero instead. */ +#ifndef AI_ADDRCONFIG +# define AI_ADDRCONFIG 0 +#endif + +/* Not every installation has gotten around to supporting SRVs or + CERTs yet... */ +#ifndef T_SRV +#define T_SRV 33 +#endif +#undef T_CERT +#define T_CERT 37 + +/* The standard SOCKS and TOR ports. */ +#define SOCKS_PORT 1080 +#define TOR_PORT 9050 +#define TOR_PORT2 9150 /* (Used by the Tor browser) */ + + +/* The default nameserver used in Tor mode. */ +#define DEFAULT_NAMESERVER "8.8.8.8" + +/* The default timeout in seconds for libdns requests. */ +#define DEFAULT_TIMEOUT 30 + + +#define RESOLV_CONF_NAME "/etc/resolv.conf" + +/* Two flags to enable verbose and debug mode. */ +static int opt_verbose; +static int opt_debug; + +/* The timeout in seconds for libdns requests. */ +static int opt_timeout; + +/* The flag to disable IPv4 access - right now this only skips + * returned A records. */ +static int opt_disable_ipv4; + +/* The flag to disable IPv6 access - right now this only skips + * returned AAAA records. */ +static int opt_disable_ipv6; + +/* If set force the use of the standard resolver. */ +static int standard_resolver; + +/* If set use recursive resolver when available. */ +static int recursive_resolver; + +/* If set Tor mode shall be used. */ +static int tor_mode; + +/* A string with the nameserver IP address used with Tor. + (40 should be sufficient for v6 but we add some extra for a scope.) */ +static char tor_nameserver[40+20]; + +/* Two strings to hold the credentials presented to Tor. */ +static char tor_socks_user[30]; +static char tor_socks_password[20]; + +/* To avoid checking the interface too often we cache the result. */ +static struct +{ + unsigned int valid:1; + unsigned int v4:1; + unsigned int v6:1; +} cached_inet_support; + + + +#ifdef USE_LIBDNS +/* Libdns gobal data. */ +struct libdns_s +{ + struct dns_resolv_conf *resolv_conf; + struct dns_hosts *hosts; + struct dns_hints *hints; + + struct sockaddr_storage socks_host; +} libdns; + +/* If this flag is set, libdns shall be reinited for the next use. */ +static int libdns_reinit_pending; + +/* The Tor port to be used. */ +static int libdns_tor_port; + +#endif /*USE_LIBDNS*/ + + +/* Calling this function with YES set to True forces the use of the + * standard resolver even if dirmngr has been built with support for + * an alternative resolver. */ +void +enable_standard_resolver (int yes) +{ + standard_resolver = yes; +} + + +/* Return true if the standard resolver is used. */ +int +standard_resolver_p (void) +{ + return standard_resolver; +} + + +/* Calling this function with YES switches libdns into recursive mode. + * It has no effect on the standard resolver. */ +void +enable_recursive_resolver (int yes) +{ + recursive_resolver = yes; +#ifdef USE_LIBDNS + libdns_reinit_pending = 1; +#endif +} + + +/* Return true iff the recursive resolver is used. */ +int +recursive_resolver_p (void) +{ +#if USE_LIBDNS + return !standard_resolver && recursive_resolver; +#else + return 0; +#endif +} + + +/* Puts this module eternally into Tor mode. When called agained with + * NEW_CIRCUIT request a new TOR circuit for the next DNS query. */ +void +enable_dns_tormode (int new_circuit) +{ + if (!*tor_socks_user || new_circuit) + { + static unsigned int counter; + + gpgrt_snprintf (tor_socks_user, sizeof tor_socks_user, + "dirmngr-%lu", (unsigned long)getpid ()); + gpgrt_snprintf (tor_socks_password, sizeof tor_socks_password, + "p%u", counter); + counter++; + } + tor_mode = 1; +} + + +/* Disable tor mode. */ +void +disable_dns_tormode (void) +{ + tor_mode = 0; +} + + +/* Set verbosity and debug mode for this module. */ +void +set_dns_verbose (int verbose, int debug) +{ + opt_verbose = verbose; + opt_debug = debug; +} + + +/* Set the Disable-IPv4 flag so that the name resolver does not return + * A addresses. */ +void +set_dns_disable_ipv4 (int yes) +{ + opt_disable_ipv4 = !!yes; +} + + +/* Set the Disable-IPv6 flag so that the name resolver does not return + * AAAA addresses. */ +void +set_dns_disable_ipv6 (int yes) +{ + opt_disable_ipv6 = !!yes; +} + + +/* Set the timeout for libdns requests to SECONDS. A value of 0 sets + * the default timeout and values are capped at 10 minutes. */ +void +set_dns_timeout (int seconds) +{ + if (!seconds) + seconds = DEFAULT_TIMEOUT; + else if (seconds < 1) + seconds = 1; + else if (seconds > 600) + seconds = 600; + + opt_timeout = seconds; +} + + +/* Change the default IP address of the nameserver to IPADDR. The + address needs to be a numerical IP address and will be used for the + next DNS query. Note that this is only used in Tor mode. */ +void +set_dns_nameserver (const char *ipaddr) +{ + strncpy (tor_nameserver, ipaddr? ipaddr : DEFAULT_NAMESERVER, + sizeof tor_nameserver -1); + tor_nameserver[sizeof tor_nameserver -1] = 0; +#ifdef USE_LIBDNS + libdns_reinit_pending = 1; + libdns_tor_port = 0; /* Start again with the default port. */ +#endif +} + + +/* Free an addressinfo linked list as returned by resolve_dns_name. */ +void +free_dns_addrinfo (dns_addrinfo_t ai) +{ + while (ai) + { + dns_addrinfo_t next = ai->next; + xfree (ai); + ai = next; + } +} + + +#ifndef HAVE_W32_SYSTEM +/* Return H_ERRNO mapped to a gpg-error code. Will never return 0. */ +static gpg_error_t +get_h_errno_as_gpg_error (void) +{ + gpg_err_code_t ec; + + switch (h_errno) + { + case HOST_NOT_FOUND: ec = GPG_ERR_NO_NAME; break; + case TRY_AGAIN: ec = GPG_ERR_TRY_LATER; break; + case NO_RECOVERY: ec = GPG_ERR_SERVER_FAILED; break; + case NO_DATA: ec = GPG_ERR_NO_DATA; break; + default: ec = GPG_ERR_UNKNOWN_ERRNO; break; + } + return gpg_error (ec); +} +#endif /*!HAVE_W32_SYSTEM*/ + +static gpg_error_t +map_eai_to_gpg_error (int ec) +{ + gpg_error_t err; + + switch (ec) + { + case EAI_AGAIN: err = gpg_error (GPG_ERR_EAGAIN); break; + case EAI_BADFLAGS: err = gpg_error (GPG_ERR_INV_FLAG); break; + case EAI_FAIL: err = gpg_error (GPG_ERR_SERVER_FAILED); break; + case EAI_MEMORY: err = gpg_error (GPG_ERR_ENOMEM); break; +#ifdef EAI_NODATA + case EAI_NODATA: err = gpg_error (GPG_ERR_NO_DATA); break; +#endif + case EAI_NONAME: err = gpg_error (GPG_ERR_NO_NAME); break; + case EAI_SERVICE: err = gpg_error (GPG_ERR_NOT_SUPPORTED); break; + case EAI_FAMILY: err = gpg_error (GPG_ERR_EAFNOSUPPORT); break; + case EAI_SOCKTYPE: err = gpg_error (GPG_ERR_ESOCKTNOSUPPORT); break; +#ifndef HAVE_W32_SYSTEM +# ifdef EAI_ADDRFAMILY + case EAI_ADDRFAMILY:err = gpg_error (GPG_ERR_EADDRNOTAVAIL); break; +# endif + case EAI_SYSTEM: err = gpg_error_from_syserror (); break; +#endif + default: err = gpg_error (GPG_ERR_UNKNOWN_ERRNO); break; + } + return err; +} + + +#ifdef USE_LIBDNS +static gpg_error_t +libdns_error_to_gpg_error (int serr) +{ + gpg_err_code_t ec; + + switch (serr) + { + case 0: ec = 0; break; + + case DNS_ENOBUFS: ec = GPG_ERR_BUFFER_TOO_SHORT; break; + case DNS_EILLEGAL: ec = GPG_ERR_INV_OBJ; break; + case DNS_EORDER: ec = GPG_ERR_INV_ORDER; break; + case DNS_ESECTION: ec = GPG_ERR_DNS_SECTION; break; + case DNS_EUNKNOWN: ec = GPG_ERR_DNS_UNKNOWN; break; + case DNS_EADDRESS: ec = GPG_ERR_DNS_ADDRESS; break; + case DNS_ENOQUERY: ec = GPG_ERR_DNS_NO_QUERY; break; + case DNS_ENOANSWER:ec = GPG_ERR_DNS_NO_ANSWER; break; + case DNS_EFETCHED: ec = GPG_ERR_ALREADY_FETCHED; break; + case DNS_ESERVICE: ec = GPG_ERR_NOT_SUPPORTED; break; + case DNS_ENONAME: ec = GPG_ERR_NO_NAME; break; + case DNS_EFAIL: ec = GPG_ERR_SERVER_FAILED; break; + case DNS_ECONNFIN: ec = GPG_ERR_DNS_CLOSED; break; + case DNS_EVERIFY: ec = GPG_ERR_DNS_VERIFY; break; + + default: + if (serr >= 0) + ec = gpg_err_code_from_errno (serr); + else + ec = GPG_ERR_DNS_UNKNOWN; + break; + } + return gpg_error (ec); +} +#endif /*USE_LIBDNS*/ + + +/* Return true if resolve.conf changed since it was last loaded. */ +#ifdef USE_LIBDNS +static int +resolv_conf_changed_p (void) +{ +#if defined(HAVE_W32_SYSTEM) || !defined(HAVE_STAT) + return 0; +#else + static time_t last_mtime; + const char *fname = RESOLV_CONF_NAME; + struct stat statbuf; + int changed = 0; + + if (stat (fname, &statbuf)) + { + log_error ("stat'ing '%s' failed: %s\n", + fname, gpg_strerror (gpg_error_from_syserror ())); + last_mtime = 1; /* Force a "changed" result the next time stat + * works. */ + } + else if (!last_mtime) + last_mtime = statbuf.st_mtime; + else if (last_mtime != statbuf.st_mtime) + { + changed = 1; + last_mtime = statbuf.st_mtime; + } + + return changed; +#endif +} +#endif /*USE_LIBDNS*/ + +#ifdef USE_LIBDNS +/* Initialize libdns. Returns 0 on success; prints a diagnostic and + * returns an error code on failure. */ +static gpg_error_t +libdns_init (void) +{ + gpg_error_t err; + struct libdns_s ld; + int derr; + char *cfgstr = NULL; + + if (libdns.resolv_conf) + return 0; /* Already initialized. */ + + memset (&ld, 0, sizeof ld); + + ld.resolv_conf = dns_resconf_open (&derr); + if (!ld.resolv_conf) + { + err = libdns_error_to_gpg_error (derr); + log_error ("failed to allocate DNS resconf object: %s\n", + gpg_strerror (err)); + goto leave; + } + + if (tor_mode) + { + if (!*tor_nameserver) + set_dns_nameserver (NULL); + + if (!libdns_tor_port) + libdns_tor_port = TOR_PORT; + + cfgstr = xtryasprintf ("[%s]:53", tor_nameserver); + if (!cfgstr) + err = gpg_error_from_syserror (); + else + err = libdns_error_to_gpg_error + (dns_resconf_pton (&ld.resolv_conf->nameserver[0], cfgstr)); + if (err) + log_error ("failed to set nameserver '%s': %s\n", + cfgstr, gpg_strerror (err)); + if (err) + goto leave; + + ld.resolv_conf->options.tcp = DNS_RESCONF_TCP_SOCKS; + + xfree (cfgstr); + cfgstr = xtryasprintf ("[%s]:%d", "127.0.0.1", libdns_tor_port); + if (!cfgstr) + err = gpg_error_from_syserror (); + else + err = libdns_error_to_gpg_error + (dns_resconf_pton (&ld.socks_host, cfgstr)); + if (err) + { + log_error ("failed to set socks server '%s': %s\n", + cfgstr, gpg_strerror (err)); + goto leave; + } + } + else + { +#ifdef HAVE_W32_SYSTEM + ULONG ninfo_len; + PFIXED_INFO ninfo; + PIP_ADDR_STRING pip; + int idx; + + ninfo_len = 2048; + ninfo = xtrymalloc (ninfo_len); + if (!ninfo) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (GetNetworkParams (ninfo, &ninfo_len)) + { + log_error ("GetNetworkParms failed: %s\n", w32_strerror (-1)); + err = gpg_error (GPG_ERR_GENERAL); + xfree (ninfo); + goto leave; + } + + for (idx=0, pip = &(ninfo->DnsServerList); + pip && idx < DIM (ld.resolv_conf->nameserver); + pip = pip->Next) + { + if (opt_debug) + log_debug ("dns: dnsserver[%d] '%s'\n", idx, pip->IpAddress.String); + err = libdns_error_to_gpg_error + (dns_resconf_pton (&ld.resolv_conf->nameserver[idx], + pip->IpAddress.String)); + if (err) + log_error ("failed to set nameserver[%d] '%s': %s\n", + idx, pip->IpAddress.String, gpg_strerror (err)); + else + idx++; + } + xfree (ninfo); + +#else /* Unix */ + const char *fname; + + fname = RESOLV_CONF_NAME; + resolv_conf_changed_p (); /* Reset timestamp. */ + err = libdns_error_to_gpg_error + (dns_resconf_loadpath (ld.resolv_conf, fname)); + if (err) + { + log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err)); + goto leave; + } + + fname = "/etc/nsswitch.conf"; + err = libdns_error_to_gpg_error + (dns_nssconf_loadpath (ld.resolv_conf, fname)); + if (err) + { + /* This is not a fatal error: nsswitch.conf is not used on + * all systems; assume classic behavior instead. */ + if (gpg_err_code (err) != GPG_ERR_ENOENT) + log_error ("failed to load '%s': %s\n", fname, gpg_strerror (err)); + if (opt_debug) + log_debug ("dns: fallback resolution order, files then DNS\n"); + ld.resolv_conf->lookup[0] = 'f'; + ld.resolv_conf->lookup[1] = 'b'; + ld.resolv_conf->lookup[2] = '\0'; + err = GPG_ERR_NO_ERROR; + } + else if (!strchr (ld.resolv_conf->lookup, 'b')) + { + /* No DNS resolution type found in the list. This might be + * due to systemd based systems which allow for custom + * keywords which are not known to us and thus we do not + * know whether DNS is wanted or not. Because DNS is + * important for our infrastructure, we forcefully append + * DNS to the end of the list. */ + if (strlen (ld.resolv_conf->lookup)+2 < sizeof ld.resolv_conf->lookup) + { + if (opt_debug) + log_debug ("dns: appending DNS to resolution order\n"); + strcat (ld.resolv_conf->lookup, "b"); + } + else + log_error ("failed to append DNS to resolution order\n"); + } + +#endif /* Unix */ + } + + ld.hosts = dns_hosts_open (&derr); + if (!ld.hosts) + { + err = libdns_error_to_gpg_error (derr); + log_error ("failed to initialize hosts file: %s\n", gpg_strerror (err)); + goto leave; + } + + { +#if HAVE_W32_SYSTEM + char *hosts_path = xtryasprintf ("%s\\System32\\drivers\\etc\\hosts", + getenv ("SystemRoot")); + if (! hosts_path) + { + err = gpg_error_from_syserror (); + goto leave; + } + + derr = dns_hosts_loadpath (ld.hosts, hosts_path); + xfree (hosts_path); +#else + derr = dns_hosts_loadpath (ld.hosts, "/etc/hosts"); +#endif + if (derr) + { + err = libdns_error_to_gpg_error (derr); + log_error ("failed to load hosts file: %s\n", gpg_strerror (err)); + err = 0; /* Do not bail out - having no /etc/hosts is legal. */ + } + } + + ld.resolv_conf->options.recurse = recursive_resolver_p (); + + /* dns_hints_local for stub mode, dns_hints_root for recursive. */ + ld.hints = (recursive_resolver + ? dns_hints_root (ld.resolv_conf, &derr) + : dns_hints_local (ld.resolv_conf, &derr)); + if (!ld.hints) + { + err = libdns_error_to_gpg_error (derr); + log_error ("failed to load DNS hints: %s\n", gpg_strerror (err)); + goto leave; + } + + /* All fine. Make the data global. */ + libdns = ld; + + if (opt_debug) + log_debug ("dns: libdns initialized%s\n", tor_mode?" (tor mode)":""); + + leave: + xfree (cfgstr); + return err; +} +#endif /*USE_LIBDNS*/ + + +#ifdef USE_LIBDNS +/* Deinitialize libdns. */ +static void +libdns_deinit (void) +{ + struct libdns_s ld; + + if (!libdns.resolv_conf) + return; /* Not initialized. */ + + ld = libdns; + memset (&libdns, 0, sizeof libdns); + dns_hints_close (ld.hints); + dns_hosts_close (ld.hosts); + dns_resconf_close (ld.resolv_conf); +} +#endif /*USE_LIBDNS*/ + + +/* SIGHUP action handler for this module. With FORCE set objects are + * all immediately released. */ +void +reload_dns_stuff (int force) +{ +#ifdef USE_LIBDNS + if (force) + { + libdns_deinit (); + libdns_reinit_pending = 0; + } + else + { + libdns_reinit_pending = 1; + libdns_tor_port = 0; /* Start again with the default port. */ + } +#else + (void)force; +#endif + + /* We also flush the IPv4/v6 support flag cache. */ + cached_inet_support.valid = 0; +} + + +/* Called from time to time from the housekeeping thread. */ +void +dns_stuff_housekeeping (void) +{ + /* With the current housekeeping interval of 10 minutes we flush + * that case so that a new or removed interface will be detected not + * later than 10 minutes after it changed. This way the user does + * not need a reload. */ + cached_inet_support.valid = 0; +} + + +#ifdef USE_LIBDNS +/* + * Initialize libdns if needed and open a dns_resolver context. + * Returns 0 on success and stores the new context at R_RES. On + * failure an error code is returned and NULL stored at R_RES. + */ +static gpg_error_t +libdns_res_open (struct dns_resolver **r_res) +{ + gpg_error_t err; + struct dns_resolver *res; + int derr; + struct dns_options opts = { 0 }; + + opts.socks_host = &libdns.socks_host; + opts.socks_user = tor_socks_user; + opts.socks_password = tor_socks_password; + + *r_res = NULL; + + /* Force a reload if resolv.conf has changed. */ + if (resolv_conf_changed_p ()) + { + if (opt_debug) + log_debug ("dns: resolv.conf changed - forcing reload\n"); + libdns_reinit_pending = 1; + } + + if (libdns_reinit_pending) + { + libdns_reinit_pending = 0; + libdns_deinit (); + } + + err = libdns_init (); + if (err) + return err; + + if (!opt_timeout) + set_dns_timeout (0); + + res = dns_res_open (libdns.resolv_conf, libdns.hosts, libdns.hints, NULL, + &opts, &derr); + if (!res) + return libdns_error_to_gpg_error (derr); + + *r_res = res; + return 0; +} +#endif /*USE_LIBDNS*/ + + +#ifdef USE_LIBDNS +/* Helper to test whether we need to try again after having switched + * the Tor port. */ +static int +libdns_switch_port_p (gpg_error_t err) +{ + if (tor_mode && gpg_err_code (err) == GPG_ERR_ECONNREFUSED + && libdns_tor_port == TOR_PORT) + { + /* Switch port and try again. */ + if (opt_debug) + log_debug ("dns: switching from SOCKS port %d to %d\n", + TOR_PORT, TOR_PORT2); + libdns_tor_port = TOR_PORT2; + libdns_reinit_pending = 1; + return 1; + } + return 0; +} +#endif /*USE_LIBDNS*/ + + +#ifdef USE_LIBDNS +/* Wrapper around dns_res_submit. */ +static gpg_error_t +libdns_res_submit (struct dns_resolver *res, const char *qname, + enum dns_type qtype, enum dns_class qclass) +{ + return libdns_error_to_gpg_error (dns_res_submit (res, qname, qtype, qclass)); +} +#endif /*USE_LIBDNS*/ + + +#ifdef USE_LIBDNS +/* Standard event handling loop. */ +gpg_error_t +libdns_res_wait (struct dns_resolver *res) +{ + gpg_error_t err; + + while ((err = libdns_error_to_gpg_error (dns_res_check (res))) + && gpg_err_code (err) == GPG_ERR_EAGAIN) + { + if (dns_res_elapsed (res) > opt_timeout) + { + err = gpg_error (GPG_ERR_DNS_TIMEOUT); + break; + } + + my_unprotect (); + dns_res_poll (res, 1); + my_protect (); + } + + return err; +} +#endif /*USE_LIBDNS*/ + + +#ifdef USE_LIBDNS +static gpg_error_t +resolve_name_libdns (const char *name, unsigned short port, + int want_family, int want_socktype, + dns_addrinfo_t *r_dai, char **r_canonname) +{ + gpg_error_t err; + dns_addrinfo_t daihead = NULL; + dns_addrinfo_t dai; + struct dns_resolver *res = NULL; + struct dns_addrinfo *ai = NULL; + struct addrinfo hints; + struct addrinfo *ent; + char portstr_[21]; + char *portstr = NULL; + char *namebuf = NULL; + int derr; + + *r_dai = NULL; + if (r_canonname) + *r_canonname = NULL; + + memset (&hints, 0, sizeof hints); + hints.ai_family = want_family; + hints.ai_socktype = want_socktype; + hints.ai_flags = AI_ADDRCONFIG; + if (r_canonname) + hints.ai_flags |= AI_CANONNAME; + + if (port) + { + snprintf (portstr_, sizeof portstr_, "%hu", port); + portstr = portstr_; + } + + err = libdns_res_open (&res); + if (err) + goto leave; + + + if (is_ip_address (name)) + { + hints.ai_flags |= AI_NUMERICHOST; + /* libdns does not grok brackets - remove them. */ + if (*name == '[' && name[strlen(name)-1] == ']') + { + namebuf = xtrymalloc (strlen (name)); + if (!namebuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + strcpy (namebuf, name+1); + namebuf[strlen (namebuf)-1] = 0; + name = namebuf; + } + } + + ai = dns_ai_open (name, portstr, 0, &hints, res, &derr); + if (!ai) + { + err = libdns_error_to_gpg_error (derr); + goto leave; + } + + /* Loop over all records. */ + for (;;) + { + err = libdns_error_to_gpg_error (dns_ai_nextent (&ent, ai)); + if (gpg_err_code (err) == GPG_ERR_ENOENT) + { + if (daihead) + err = 0; /* We got some results, we're good. */ + break; /* Ready. */ + } + if (gpg_err_code (err) == GPG_ERR_EAGAIN) + { + if (dns_ai_elapsed (ai) > opt_timeout) + { + err = gpg_error (GPG_ERR_DNS_TIMEOUT); + goto leave; + } + + my_unprotect (); + dns_ai_poll (ai, 1); + my_protect (); + continue; + } + if (err) + goto leave; + + if (r_canonname && ! *r_canonname && ent && ent->ai_canonname) + { + *r_canonname = xtrystrdup (ent->ai_canonname); + if (!*r_canonname) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Libdns appends the root zone part which is problematic + * for most other functions - strip it. */ + if (**r_canonname && (*r_canonname)[strlen (*r_canonname)-1] == '.') + (*r_canonname)[strlen (*r_canonname)-1] = 0; + } + + dai = xtrymalloc (sizeof *dai); + if (dai == NULL) + { + err = gpg_error_from_syserror (); + goto leave; + } + + dai->family = ent->ai_family; + dai->socktype = ent->ai_socktype; + dai->protocol = ent->ai_protocol; + dai->addrlen = ent->ai_addrlen; + memcpy (dai->addr, ent->ai_addr, ent->ai_addrlen); + dai->next = daihead; + daihead = dai; + + xfree (ent); + } + + leave: + dns_ai_close (ai); + dns_res_close (res); + + if (err) + { + if (r_canonname) + { + xfree (*r_canonname); + *r_canonname = NULL; + } + free_dns_addrinfo (daihead); + } + else + *r_dai = daihead; + + xfree (namebuf); + return err; +} +#endif /*USE_LIBDNS*/ + + +/* Resolve a name using the standard system function. */ +static gpg_error_t +resolve_name_standard (const char *name, unsigned short port, + int want_family, int want_socktype, + dns_addrinfo_t *r_dai, char **r_canonname) +{ + gpg_error_t err = 0; + dns_addrinfo_t daihead = NULL; + dns_addrinfo_t dai; + struct addrinfo *aibuf = NULL; + struct addrinfo hints, *ai; + char portstr[21]; + int ret; + + *r_dai = NULL; + if (r_canonname) + *r_canonname = NULL; + + memset (&hints, 0, sizeof hints); + hints.ai_family = want_family; + hints.ai_socktype = want_socktype; + hints.ai_flags = AI_ADDRCONFIG; + if (r_canonname) + hints.ai_flags |= AI_CANONNAME; + if (is_ip_address (name)) + hints.ai_flags |= AI_NUMERICHOST; + + if (port) + snprintf (portstr, sizeof portstr, "%hu", port); + else + *portstr = 0; + + /* We can't use the AI_IDN flag because that does the conversion + using the current locale. However, GnuPG always used UTF-8. To + support IDN we would need to make use of the libidn API. */ + ret = getaddrinfo (name, *portstr? portstr : NULL, &hints, &aibuf); + if (ret) + { + aibuf = NULL; + err = map_eai_to_gpg_error (ret); + if (gpg_err_code (err) == GPG_ERR_NO_NAME) + { + /* There seems to be a bug in the glibc getaddrinfo function + if the CNAME points to a long list of A and AAAA records + in which case the function return NO_NAME. Let's do the + CNAME redirection again. */ + char *cname; + + if (get_dns_cname (name, &cname)) + goto leave; /* Still no success. */ + + ret = getaddrinfo (cname, *portstr? portstr : NULL, &hints, &aibuf); + xfree (cname); + if (ret) + { + aibuf = NULL; + err = map_eai_to_gpg_error (ret); + goto leave; + } + err = 0; /* Yep, now it worked. */ + } + else + goto leave; + } + + if (r_canonname && aibuf && aibuf->ai_canonname) + { + *r_canonname = xtrystrdup (aibuf->ai_canonname); + if (!*r_canonname) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + for (ai = aibuf; ai; ai = ai->ai_next) + { + if (ai->ai_family != AF_INET6 && ai->ai_family != AF_INET) + continue; + if (opt_disable_ipv4 && ai->ai_family == AF_INET) + continue; + if (opt_disable_ipv6 && ai->ai_family == AF_INET6) + continue; + + dai = xtrymalloc (sizeof *dai); + dai->family = ai->ai_family; + dai->socktype = ai->ai_socktype; + dai->protocol = ai->ai_protocol; + dai->addrlen = ai->ai_addrlen; + memcpy (dai->addr, ai->ai_addr, ai->ai_addrlen); + dai->next = daihead; + daihead = dai; + } + + leave: + if (aibuf) + freeaddrinfo (aibuf); + if (err) + { + if (r_canonname) + { + xfree (*r_canonname); + *r_canonname = NULL; + } + free_dns_addrinfo (daihead); + } + else + *r_dai = daihead; + return err; +} + + +/* This a wrapper around getaddrinfo with slightly different semantics. + NAME is the name to resolve. + PORT is the requested port or 0. + WANT_FAMILY is either 0 (AF_UNSPEC), AF_INET6, or AF_INET4. + WANT_SOCKETTYPE is either SOCK_STREAM or SOCK_DGRAM. + + On success the result is stored in a linked list with the head + stored at the address R_AI; the caller must call gpg_addrinfo_free + on this. If R_CANONNAME is not NULL the official name of the host + is stored there as a malloced string; if that name is not available + NULL is stored. */ +gpg_error_t +resolve_dns_name (const char *name, unsigned short port, + int want_family, int want_socktype, + dns_addrinfo_t *r_ai, char **r_canonname) +{ + gpg_error_t err; + +#ifdef USE_LIBDNS + if (!standard_resolver) + { + err = resolve_name_libdns (name, port, want_family, want_socktype, + r_ai, r_canonname); + if (err && libdns_switch_port_p (err)) + err = resolve_name_libdns (name, port, want_family, want_socktype, + r_ai, r_canonname); + } + else +#endif /*USE_LIBDNS*/ + err = resolve_name_standard (name, port, want_family, want_socktype, + r_ai, r_canonname); + if (opt_debug) + log_debug ("dns: resolve_dns_name(%s): %s\n", name, gpg_strerror (err)); + return err; +} + + +#ifdef USE_LIBDNS +/* Resolve an address using libdns. */ +static gpg_error_t +resolve_addr_libdns (const struct sockaddr_storage *addr, int addrlen, + unsigned int flags, char **r_name) +{ + gpg_error_t err; + char host[DNS_D_MAXNAME + 1]; + struct dns_resolver *res = NULL; + struct dns_packet *ans = NULL; + struct dns_ptr ptr; + int derr; + + *r_name = NULL; + + /* First we turn ADDR into a DNS name (with ".arpa" suffix). */ + err = 0; + if (addr->ss_family == AF_INET6) + { + const struct sockaddr_in6 *a6 = (const struct sockaddr_in6 *)addr; + if (!dns_aaaa_arpa (host, sizeof host, (void*)&a6->sin6_addr)) + err = gpg_error (GPG_ERR_INV_OBJ); + } + else if (addr->ss_family == AF_INET) + { + const struct sockaddr_in *a4 = (const struct sockaddr_in *)addr; + if (!dns_a_arpa (host, sizeof host, (void*)&a4->sin_addr)) + err = gpg_error (GPG_ERR_INV_OBJ); + } + else + err = gpg_error (GPG_ERR_EAFNOSUPPORT); + if (err) + goto leave; + + + err = libdns_res_open (&res); + if (err) + goto leave; + + err = libdns_res_submit (res, host, DNS_T_PTR, DNS_C_IN); + if (err) + goto leave; + + err = libdns_res_wait (res); + if (err) + goto leave; + + ans = dns_res_fetch (res, &derr); + if (!ans) + { + err = libdns_error_to_gpg_error (derr); + goto leave; + } + + /* Check the rcode. */ + switch (dns_p_rcode (ans)) + { + case DNS_RC_NOERROR: + break; + case DNS_RC_NXDOMAIN: + err = gpg_error (GPG_ERR_NO_NAME); + break; + default: + err = GPG_ERR_SERVER_FAILED; + goto leave; + } + + /* Parse the result. */ + if (!err) + { + struct dns_rr rr; + struct dns_rr_i rri; + + memset (&rri, 0, sizeof rri); + dns_rr_i_init (&rri); + rri.section = DNS_S_ALL & ~DNS_S_QD; + rri.name = host; + rri.type = DNS_T_PTR; + + if (!dns_rr_grep (&rr, 1, &rri, ans, &derr)) + { + err = gpg_error (GPG_ERR_NOT_FOUND); + goto leave; + } + + err = libdns_error_to_gpg_error (dns_ptr_parse (&ptr, &rr, ans)); + if (err) + goto leave; + + /* Copy result. */ + *r_name = xtrystrdup (ptr.host); + if (!*r_name) + { + err = gpg_error_from_syserror (); + goto leave; + } + /* Libdns appends the root zone part which is problematic + * for most other functions - strip it. */ + if (**r_name && (*r_name)[strlen (*r_name)-1] == '.') + (*r_name)[strlen (*r_name)-1] = 0; + } + else /* GPG_ERR_NO_NAME */ + { + char *buffer, *p; + int buflen; + int ec; + + buffer = ptr.host; + buflen = sizeof ptr.host; + + p = buffer; + if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) + { + *p++ = '['; + buflen -= 2; + } + ec = getnameinfo ((const struct sockaddr *)addr, + addrlen, p, buflen, NULL, 0, NI_NUMERICHOST); + if (ec) + { + err = map_eai_to_gpg_error (ec); + goto leave; + } + if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) + strcat (buffer, "]"); + } + + leave: + dns_free (ans); + dns_res_close (res); + return err; +} +#endif /*USE_LIBDNS*/ + + +/* Resolve an address using the standard system function. */ +static gpg_error_t +resolve_addr_standard (const struct sockaddr_storage *addr, int addrlen, + unsigned int flags, char **r_name) +{ + gpg_error_t err; + int ec; + char *buffer, *p; + int buflen; + + *r_name = NULL; + + buflen = NI_MAXHOST; + buffer = xtrymalloc (buflen + 2 + 1); + if (!buffer) + return gpg_error_from_syserror (); + + if ((flags & DNS_NUMERICHOST) || tor_mode) + ec = EAI_NONAME; + else + ec = getnameinfo ((const struct sockaddr *)addr, + addrlen, buffer, buflen, NULL, 0, NI_NAMEREQD); + + if (!ec && *buffer == '[') + ec = EAI_FAIL; /* A name may never start with a bracket. */ + else if (ec == EAI_NONAME) + { + p = buffer; + if (addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) + { + *p++ = '['; + buflen -= 2; + } + ec = getnameinfo ((const struct sockaddr *)addr, + addrlen, p, buflen, NULL, 0, NI_NUMERICHOST); + if (!ec && addr->ss_family == AF_INET6 && (flags & DNS_WITHBRACKET)) + strcat (buffer, "]"); + } + + if (ec) + err = map_eai_to_gpg_error (ec); + else + { + p = xtryrealloc (buffer, strlen (buffer)+1); + if (!p) + err = gpg_error_from_syserror (); + else + { + buffer = p; + err = 0; + } + } + + if (err) + xfree (buffer); + else + *r_name = buffer; + + return err; +} + + +/* A wrapper around getnameinfo. */ +gpg_error_t +resolve_dns_addr (const struct sockaddr_storage *addr, int addrlen, + unsigned int flags, char **r_name) +{ + gpg_error_t err; + +#ifdef USE_LIBDNS + /* Note that we divert to the standard resolver for NUMERICHOST. */ + if (!standard_resolver && !(flags & DNS_NUMERICHOST)) + { + err = resolve_addr_libdns (addr, addrlen, flags, r_name); + if (err && libdns_switch_port_p (err)) + err = resolve_addr_libdns (addr, addrlen, flags, r_name); + } + else +#endif /*USE_LIBDNS*/ + err = resolve_addr_standard (addr, addrlen, flags, r_name); + + if (opt_debug) + log_debug ("dns: resolve_dns_addr(): %s\n", gpg_strerror (err)); + return err; +} + + +/* Check whether NAME is an IP address. Returns a true if it is + * either an IPv6 or a IPv4 numerical address. The actual return + * values can also be used to identify whether it is v4 or v6: The + * true value will surprisingly be 4 for IPv4 and 6 for IPv6. */ +int +is_ip_address (const char *name) +{ + const char *s; + int ndots, dblcol, n; + + if (*name == '[') + return 6; /* yes: A legal DNS name may not contain this character; + this must be bracketed v6 address. */ + if (*name == '.') + return 0; /* No. A leading dot is not a valid IP address. */ + + /* Check whether this is a v6 address. */ + ndots = n = dblcol = 0; + for (s=name; *s; s++) + { + if (*s == ':') + { + ndots++; + if (s[1] == ':') + { + ndots++; + if (dblcol) + return 0; /* No: Only one "::" allowed. */ + dblcol++; + if (s[1]) + s++; + } + n = 0; + } + else if (*s == '.') + goto legacy; + else if (!strchr ("0123456789abcdefABCDEF", *s)) + return 0; /* No: Not a hex digit. */ + else if (++n > 4) + return 0; /* To many digits in a group. */ + } + if (ndots > 7) + return 0; /* No: Too many colons. */ + else if (ndots > 1) + return 6; /* Yes: At least 2 colons indicate an v6 address. */ + + legacy: + /* Check whether it is legacy IP address. */ + ndots = n = 0; + for (s=name; *s; s++) + { + if (*s == '.') + { + if (s[1] == '.') + return 0; /* No: Double dot. */ + if (atoi (s+1) > 255) + return 0; /* No: Ipv4 byte value too large. */ + ndots++; + n = 0; + } + else if (!strchr ("0123456789", *s)) + return 0; /* No: Not a digit. */ + else if (++n > 3) + return 0; /* No: More than 3 digits. */ + } + return (ndots == 3)? 4 : 0; +} + + +/* Return true if NAME is an onion address. */ +int +is_onion_address (const char *name) +{ + size_t len; + + len = name? strlen (name) : 0; + if (len < 8 || strcmp (name + len - 6, ".onion")) + return 0; + /* Note that we require at least 2 characters before the suffix. */ + return 1; /* Yes. */ +} + + +/* libdns version of get_dns_cert. */ +#ifdef USE_LIBDNS +static gpg_error_t +get_dns_cert_libdns (const char *name, int want_certtype, + void **r_key, size_t *r_keylen, + unsigned char **r_fpr, size_t *r_fprlen, char **r_url) +{ + gpg_error_t err; + struct dns_resolver *res = NULL; + struct dns_packet *ans = NULL; + struct dns_rr rr; + struct dns_rr_i rri; + char host[DNS_D_MAXNAME + 1]; + int derr; + int qtype; + + /* Get the query type from WANT_CERTTYPE (which in general indicates + * the subtype we want). */ + qtype = (want_certtype < DNS_CERTTYPE_RRBASE + ? T_CERT + : (want_certtype - DNS_CERTTYPE_RRBASE)); + + + err = libdns_res_open (&res); + if (err) + goto leave; + + if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host) + { + err = gpg_error (GPG_ERR_ENAMETOOLONG); + goto leave; + } + + err = libdns_res_submit (res, name, qtype, DNS_C_IN); + if (err) + goto leave; + + err = libdns_res_wait (res); + if (err) + goto leave; + + ans = dns_res_fetch (res, &derr); + if (!ans) + { + err = libdns_error_to_gpg_error (derr); + goto leave; + } + + /* Check the rcode. */ + switch (dns_p_rcode (ans)) + { + case DNS_RC_NOERROR: break; + case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; + default: err = GPG_ERR_SERVER_FAILED; break; + } + if (err) + goto leave; + + memset (&rri, 0, sizeof rri); + dns_rr_i_init (&rri); + rri.section = DNS_S_ALL & ~DNS_S_QD; + rri.name = host; + rri.type = qtype; + + err = gpg_error (GPG_ERR_NOT_FOUND); + while (dns_rr_grep (&rr, 1, &rri, ans, &derr)) + { + unsigned char *rp = ans->data + rr.rd.p; + unsigned short len = rr.rd.len; + u16 subtype; + + if (!len) + { + /* Definitely too short - skip. */ + } + else if (want_certtype >= DNS_CERTTYPE_RRBASE + && rr.type == (want_certtype - DNS_CERTTYPE_RRBASE) + && r_key) + { + *r_key = xtrymalloc (len); + if (!*r_key) + err = gpg_error_from_syserror (); + else + { + memcpy (*r_key, rp, len); + *r_keylen = len; + err = 0; + } + goto leave; + } + else if (want_certtype >= DNS_CERTTYPE_RRBASE) + { + /* We did not found the requested RR - skip. */ + } + else if (rr.type == T_CERT && len > 5) + { + /* We got a CERT type. */ + subtype = buf16_to_u16 (rp); + rp += 2; len -= 2; + + /* Skip the CERT key tag and algo which we don't need. */ + rp += 3; len -= 3; + + if (want_certtype && want_certtype != subtype) + ; /* Not the requested subtype - skip. */ + else if (subtype == DNS_CERTTYPE_PGP && len && r_key && r_keylen) + { + /* PGP subtype */ + *r_key = xtrymalloc (len); + if (!*r_key) + err = gpg_error_from_syserror (); + else + { + memcpy (*r_key, rp, len); + *r_keylen = len; + err = 0; + } + goto leave; + } + else if (subtype == DNS_CERTTYPE_IPGP + && len && len < 1023 && len >= rp[0] + 1) + { + /* IPGP type */ + *r_fprlen = rp[0]; + if (*r_fprlen) + { + *r_fpr = xtrymalloc (*r_fprlen); + if (!*r_fpr) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (*r_fpr, rp+1, *r_fprlen); + } + else + *r_fpr = NULL; + + if (len > *r_fprlen + 1) + { + *r_url = xtrymalloc (len - (*r_fprlen + 1) + 1); + if (!*r_url) + { + err = gpg_error_from_syserror (); + xfree (*r_fpr); + *r_fpr = NULL; + goto leave; + } + memcpy (*r_url, rp + *r_fprlen + 1, len - (*r_fprlen + 1)); + (*r_url)[len - (*r_fprlen + 1)] = 0; + } + else + *r_url = NULL; + + err = 0; + goto leave; + } + else + { + /* Unknown subtype or record too short - skip. */ + } + } + else + { + /* Not a requested type - skip. */ + } + } + + leave: + dns_free (ans); + dns_res_close (res); + return err; +} +#endif /*USE_LIBDNS*/ + + +/* Standard resolver version of get_dns_cert. */ +static gpg_error_t +get_dns_cert_standard (const char *name, int want_certtype, + void **r_key, size_t *r_keylen, + unsigned char **r_fpr, size_t *r_fprlen, char **r_url) +{ +#ifdef HAVE_SYSTEM_RESOLVER + gpg_error_t err; + unsigned char *answer; + int r; + u16 count; + + /* Allocate a 64k buffer which is the limit for an DNS response. */ + answer = xtrymalloc (65536); + if (!answer) + return gpg_error_from_syserror (); + + err = gpg_error (GPG_ERR_NOT_FOUND); + r = res_query (name, C_IN, + (want_certtype < DNS_CERTTYPE_RRBASE + ? T_CERT + : (want_certtype - DNS_CERTTYPE_RRBASE)), + answer, 65536); + /* Not too big, not too small, no errors and at least 1 answer. */ + if (r >= sizeof (HEADER) && r <= 65536 + && (((HEADER *)(void *) answer)->rcode) == NOERROR + && (count = ntohs (((HEADER *)(void *) answer)->ancount))) + { + int rc; + unsigned char *pt, *emsg; + + emsg = &answer[r]; + + pt = &answer[sizeof (HEADER)]; + + /* Skip over the query */ + + rc = dn_skipname (pt, emsg); + if (rc == -1) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + pt += rc + QFIXEDSZ; + + /* There are several possible response types for a CERT request. + We're interested in the PGP (a key) and IPGP (a URI) types. + Skip all others. TODO: A key is better than a URI since + we've gone through all this bother to fetch it, so favor that + if we have both PGP and IPGP? */ + + while (count-- > 0 && pt < emsg) + { + u16 type, class, dlen, ctype; + + rc = dn_skipname (pt, emsg); /* the name we just queried for */ + if (rc == -1) + { + err = gpg_error (GPG_ERR_INV_OBJ); + goto leave; + } + + pt += rc; + + /* Truncated message? 15 bytes takes us to the point where + we start looking at the ctype. */ + if ((emsg - pt) < 15) + break; + + type = buf16_to_u16 (pt); + pt += 2; + + class = buf16_to_u16 (pt); + pt += 2; + + if (class != C_IN) + break; + + /* ttl */ + pt += 4; + + /* data length */ + dlen = buf16_to_u16 (pt); + pt += 2; + + /* Check the type and parse. */ + if (want_certtype >= DNS_CERTTYPE_RRBASE + && type == (want_certtype - DNS_CERTTYPE_RRBASE) + && r_key) + { + *r_key = xtrymalloc (dlen); + if (!*r_key) + err = gpg_error_from_syserror (); + else + { + memcpy (*r_key, pt, dlen); + *r_keylen = dlen; + err = 0; + } + goto leave; + } + else if (want_certtype >= DNS_CERTTYPE_RRBASE) + { + /* We did not found the requested RR. */ + pt += dlen; + } + else if (type == T_CERT) + { + /* We got a CERT type. */ + ctype = buf16_to_u16 (pt); + pt += 2; + + /* Skip the CERT key tag and algo which we don't need. */ + pt += 3; + + dlen -= 5; + + /* 15 bytes takes us to here */ + if (want_certtype && want_certtype != ctype) + ; /* Not of the requested certtype. */ + else if (ctype == DNS_CERTTYPE_PGP && dlen && r_key && r_keylen) + { + /* PGP type */ + *r_key = xtrymalloc (dlen); + if (!*r_key) + err = gpg_error_from_syserror (); + else + { + memcpy (*r_key, pt, dlen); + *r_keylen = dlen; + err = 0; + } + goto leave; + } + else if (ctype == DNS_CERTTYPE_IPGP + && dlen && dlen < 1023 && dlen >= pt[0] + 1) + { + /* IPGP type */ + *r_fprlen = pt[0]; + if (*r_fprlen) + { + *r_fpr = xtrymalloc (*r_fprlen); + if (!*r_fpr) + { + err = gpg_error_from_syserror (); + goto leave; + } + memcpy (*r_fpr, &pt[1], *r_fprlen); + } + else + *r_fpr = NULL; + + if (dlen > *r_fprlen + 1) + { + *r_url = xtrymalloc (dlen - (*r_fprlen + 1) + 1); + if (!*r_url) + { + err = gpg_error_from_syserror (); + xfree (*r_fpr); + *r_fpr = NULL; + goto leave; + } + memcpy (*r_url, &pt[*r_fprlen + 1], + dlen - (*r_fprlen + 1)); + (*r_url)[dlen - (*r_fprlen + 1)] = '\0'; + } + else + *r_url = NULL; + + err = 0; + goto leave; + } + + /* No subtype matches, so continue with the next answer. */ + pt += dlen; + } + else + { + /* Not a requested type - might be a CNAME. Try next item. */ + pt += dlen; + } + } + } + + leave: + xfree (answer); + return err; + +#else /*!HAVE_SYSTEM_RESOLVER*/ + + (void)name; + (void)want_certtype; + (void)r_key; + (void)r_keylen; + (void)r_fpr; + (void)r_fprlen; + (void)r_url; + return gpg_error (GPG_ERR_NOT_SUPPORTED); + +#endif /*!HAVE_SYSTEM_RESOLVER*/ +} + + +/* Returns 0 on success or an error code. If a PGP CERT record was + found, the malloced data is returned at (R_KEY, R_KEYLEN) and + the other return parameters are set to NULL/0. If an IPGP CERT + record was found the fingerprint is stored as an allocated block at + R_FPR and its length at R_FPRLEN; an URL is allocated as a + string and returned at R_URL. If WANT_CERTTYPE is 0 this function + returns the first CERT found with a supported type; it is expected + that only one CERT record is used. If WANT_CERTTYPE is one of the + supported certtypes only records with this certtype are considered + and the first found is returned. (R_KEY,R_KEYLEN) are optional. */ +gpg_error_t +get_dns_cert (const char *name, int want_certtype, + void **r_key, size_t *r_keylen, + unsigned char **r_fpr, size_t *r_fprlen, char **r_url) +{ + gpg_error_t err; + + if (r_key) + *r_key = NULL; + if (r_keylen) + *r_keylen = 0; + *r_fpr = NULL; + *r_fprlen = 0; + *r_url = NULL; + +#ifdef USE_LIBDNS + if (!standard_resolver) + { + err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen, + r_fpr, r_fprlen, r_url); + if (err && libdns_switch_port_p (err)) + err = get_dns_cert_libdns (name, want_certtype, r_key, r_keylen, + r_fpr, r_fprlen, r_url); + } + else +#endif /*USE_LIBDNS*/ + err = get_dns_cert_standard (name, want_certtype, r_key, r_keylen, + r_fpr, r_fprlen, r_url); + + if (opt_debug) + log_debug ("dns: get_dns_cert(%s): %s\n", name, gpg_strerror (err)); + return err; +} + + +static int +priosort(const void *a,const void *b) +{ + const struct srventry *sa=a,*sb=b; + if(sa->priority>sb->priority) + return 1; + else if(sa->priority<sb->priority) + return -1; + else + return 0; +} + + +/* Libdns based helper for getsrv. Note that it is expected that NULL + * is stored at the address of LIST and 0 is stored at the address of + * R_COUNT. */ +#ifdef USE_LIBDNS +static gpg_error_t +getsrv_libdns (const char *name, struct srventry **list, unsigned int *r_count) +{ + gpg_error_t err; + struct dns_resolver *res = NULL; + struct dns_packet *ans = NULL; + struct dns_rr rr; + struct dns_rr_i rri; + char host[DNS_D_MAXNAME + 1]; + int derr; + unsigned int srvcount = 0; + + err = libdns_res_open (&res); + if (err) + goto leave; + + if (dns_d_anchor (host, sizeof host, name, strlen (name)) >= sizeof host) + { + err = gpg_error (GPG_ERR_ENAMETOOLONG); + goto leave; + } + + err = libdns_res_submit (res, name, DNS_T_SRV, DNS_C_IN); + if (err) + goto leave; + + err = libdns_res_wait (res); + if (err) + goto leave; + + ans = dns_res_fetch (res, &derr); + if (!ans) + { + err = libdns_error_to_gpg_error (derr); + goto leave; + } + + /* Check the rcode. */ + switch (dns_p_rcode (ans)) + { + case DNS_RC_NOERROR: break; + case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; + default: err = GPG_ERR_SERVER_FAILED; break; + } + if (err) + goto leave; + + memset (&rri, 0, sizeof rri); + dns_rr_i_init (&rri); + rri.section = DNS_S_ALL & ~DNS_S_QD; + rri.name = host; + rri.type = DNS_T_SRV; + + while (dns_rr_grep (&rr, 1, &rri, ans, &derr)) + { + struct dns_srv dsrv; + struct srventry *srv; + struct srventry *newlist; + + err = libdns_error_to_gpg_error (dns_srv_parse(&dsrv, &rr, ans)); + if (err) + goto leave; + + newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry)); + if (!newlist) + { + err = gpg_error_from_syserror (); + goto leave; + } + *list = newlist; + memset (&(*list)[srvcount], 0, sizeof(struct srventry)); + srv = &(*list)[srvcount]; + srvcount++; + srv->priority = dsrv.priority; + srv->weight = dsrv.weight; + srv->port = dsrv.port; + mem2str (srv->target, dsrv.target, sizeof srv->target); + /* Libdns appends the root zone part which is problematic for + * most other functions - strip it. */ + if (*srv->target && (srv->target)[strlen (srv->target)-1] == '.') + (srv->target)[strlen (srv->target)-1] = 0; + } + + *r_count = srvcount; + + leave: + if (err) + { + xfree (*list); + *list = NULL; + } + dns_free (ans); + dns_res_close (res); + return err; +} +#endif /*USE_LIBDNS*/ + + +/* Standard resolver based helper for getsrv. Note that it is + * expected that NULL is stored at the address of LIST and 0 is stored + * at the address of R_COUNT. */ +static gpg_error_t +getsrv_standard (const char *name, + struct srventry **list, unsigned int *r_count) +{ +#ifdef HAVE_SYSTEM_RESOLVER + union { + unsigned char ans[2048]; + HEADER header[1]; + } res; + unsigned char *answer = res.ans; + HEADER *header = res.header; + unsigned char *pt, *emsg; + int r, rc; + u16 dlen; + unsigned int srvcount = 0; + u16 count; + + /* Do not allow a query using the standard resolver in Tor mode. */ + if (tor_mode) + return gpg_error (GPG_ERR_NOT_ENABLED); + + my_unprotect (); + r = res_query (name, C_IN, T_SRV, answer, sizeof res.ans); + my_protect (); + if (r < 0) + return get_h_errno_as_gpg_error (); + if (r < sizeof (HEADER)) + return gpg_error (GPG_ERR_SERVER_FAILED); + if (r > sizeof res.ans) + return gpg_error (GPG_ERR_SYSTEM_BUG); + if (header->rcode != NOERROR || !(count=ntohs (header->ancount))) + return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found. */ + + emsg = &answer[r]; + pt = &answer[sizeof(HEADER)]; + + /* Skip over the query */ + rc = dn_skipname (pt, emsg); + if (rc == -1) + goto fail; + + pt += rc + QFIXEDSZ; + + while (count-- > 0 && pt < emsg) + { + struct srventry *srv; + u16 type, class; + struct srventry *newlist; + + newlist = xtryrealloc (*list, (srvcount+1)*sizeof(struct srventry)); + if (!newlist) + goto fail; + *list = newlist; + memset (&(*list)[srvcount], 0, sizeof(struct srventry)); + srv = &(*list)[srvcount]; + srvcount++; + + rc = dn_skipname (pt, emsg); /* The name we just queried for. */ + if (rc == -1) + goto fail; + pt += rc; + + /* Truncated message? */ + if ((emsg-pt) < 16) + goto fail; + + type = buf16_to_u16 (pt); + pt += 2; + /* We asked for SRV and got something else !? */ + if (type != T_SRV) + goto fail; + + class = buf16_to_u16 (pt); + pt += 2; + /* We asked for IN and got something else !? */ + if (class != C_IN) + goto fail; + + pt += 4; /* ttl */ + dlen = buf16_to_u16 (pt); + pt += 2; + + srv->priority = buf16_to_ushort (pt); + pt += 2; + srv->weight = buf16_to_ushort (pt); + pt += 2; + srv->port = buf16_to_ushort (pt); + pt += 2; + + /* Get the name. 2782 doesn't allow name compression, but + * dn_expand still works to pull the name out of the packet. */ + rc = dn_expand (answer, emsg, pt, srv->target, sizeof srv->target); + if (rc == 1 && srv->target[0] == 0) /* "." */ + { + xfree(*list); + *list = NULL; + return 0; + } + if (rc == -1) + goto fail; + pt += rc; + /* Corrupt packet? */ + if (dlen != rc+6) + goto fail; + } + + *r_count = srvcount; + return 0; + + fail: + xfree (*list); + *list = NULL; + return gpg_error (GPG_ERR_GENERAL); + +#else /*!HAVE_SYSTEM_RESOLVER*/ + + (void)name; + (void)list; + (void)r_count; + return gpg_error (GPG_ERR_NOT_SUPPORTED); + +#endif /*!HAVE_SYSTEM_RESOLVER*/ +} + + +/* Query a SRV record for SERVICE and PROTO for NAME. If SERVICE is + * NULL, NAME is expected to contain the full query name. Note that + * we do not return NONAME but simply store 0 at R_COUNT. On error an + * error code is returned and 0 stored at R_COUNT. */ +gpg_error_t +get_dns_srv (const char *name, const char *service, const char *proto, + struct srventry **list, unsigned int *r_count) +{ + gpg_error_t err; + char *namebuffer = NULL; + unsigned int srvcount; + int i; + + *list = NULL; + *r_count = 0; + srvcount = 0; + + /* If SERVICE is given construct the query from it and PROTO. */ + if (service) + { + namebuffer = xtryasprintf ("_%s._%s.%s", + service, proto? proto:"tcp", name); + if (!namebuffer) + { + err = gpg_error_from_syserror (); + goto leave; + } + name = namebuffer; + } + + +#ifdef USE_LIBDNS + if (!standard_resolver) + { + err = getsrv_libdns (name, list, &srvcount); + if (err && libdns_switch_port_p (err)) + err = getsrv_libdns (name, list, &srvcount); + } + else +#endif /*USE_LIBDNS*/ + err = getsrv_standard (name, list, &srvcount); + + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NO_NAME) + err = 0; + goto leave; + } + + /* Now we have an array of all the srv records. */ + + /* Order by priority */ + qsort(*list,srvcount,sizeof(struct srventry),priosort); + + /* For each priority, move the zero-weighted items first. */ + for (i=0; i < srvcount; i++) + { + int j; + + for (j=i;j < srvcount && (*list)[i].priority == (*list)[j].priority; j++) + { + if((*list)[j].weight==0) + { + /* Swap j with i */ + if(j!=i) + { + struct srventry temp; + + memcpy (&temp,&(*list)[j],sizeof(struct srventry)); + memcpy (&(*list)[j],&(*list)[i],sizeof(struct srventry)); + memcpy (&(*list)[i],&temp,sizeof(struct srventry)); + } + + break; + } + } + } + + /* Run the RFC-2782 weighting algorithm. We don't need very high + quality randomness for this, so regular libc srand/rand is + sufficient. */ + + { + static int done; + if (!done) + { + done = 1; + srand (time (NULL)*getpid()); + } + } + + for (i=0; i < srvcount; i++) + { + int j; + float prio_count=0,chose; + + for (j=i; j < srvcount && (*list)[i].priority == (*list)[j].priority; j++) + { + prio_count+=(*list)[j].weight; + (*list)[j].run_count=prio_count; + } + + chose=prio_count*rand()/RAND_MAX; + + for (j=i;j<srvcount && (*list)[i].priority==(*list)[j].priority;j++) + { + if (chose<=(*list)[j].run_count) + { + /* Swap j with i */ + if(j!=i) + { + struct srventry temp; + + memcpy(&temp,&(*list)[j],sizeof(struct srventry)); + memcpy(&(*list)[j],&(*list)[i],sizeof(struct srventry)); + memcpy(&(*list)[i],&temp,sizeof(struct srventry)); + } + break; + } + } + } + + leave: + if (opt_debug) + { + if (err) + log_debug ("dns: getsrv(%s): %s\n", name, gpg_strerror (err)); + else + log_debug ("dns: getsrv(%s) -> %u records\n", name, srvcount); + } + if (!err) + *r_count = srvcount; + xfree (namebuffer); + return err; +} + + + +#ifdef USE_LIBDNS +/* libdns version of get_dns_cname. */ +gpg_error_t +get_dns_cname_libdns (const char *name, char **r_cname) +{ + gpg_error_t err; + struct dns_resolver *res; + struct dns_packet *ans = NULL; + struct dns_cname cname; + int derr; + + err = libdns_res_open (&res); + if (err) + goto leave; + + err = libdns_res_submit (res, name, DNS_T_CNAME, DNS_C_IN); + if (err) + goto leave; + + err = libdns_res_wait (res); + if (err) + goto leave; + + ans = dns_res_fetch (res, &derr); + if (!ans) + { + err = libdns_error_to_gpg_error (derr); + goto leave; + } + + /* Check the rcode. */ + switch (dns_p_rcode (ans)) + { + case DNS_RC_NOERROR: break; + case DNS_RC_NXDOMAIN: err = gpg_error (GPG_ERR_NO_NAME); break; + default: err = GPG_ERR_SERVER_FAILED; break; + } + if (err) + goto leave; + + /* Parse the result into CNAME. */ + err = libdns_error_to_gpg_error (dns_p_study (ans)); + if (err) + goto leave; + + if (!dns_d_cname (&cname, sizeof cname, name, strlen (name), ans, &derr)) + { + err = libdns_error_to_gpg_error (derr); + goto leave; + } + + /* Copy result. */ + *r_cname = xtrystrdup (cname.host); + if (!*r_cname) + err = gpg_error_from_syserror (); + else + { + /* Libdns appends the root zone part which is problematic + * for most other functions - strip it. */ + if (**r_cname && (*r_cname)[strlen (*r_cname)-1] == '.') + (*r_cname)[strlen (*r_cname)-1] = 0; + } + + leave: + dns_free (ans); + dns_res_close (res); + return err; +} +#endif /*USE_LIBDNS*/ + + +/* Standard resolver version of get_dns_cname. */ +gpg_error_t +get_dns_cname_standard (const char *name, char **r_cname) +{ +#ifdef HAVE_SYSTEM_RESOLVER + gpg_error_t err; + int rc; + union { + unsigned char ans[2048]; + HEADER header[1]; + } res; + unsigned char *answer = res.ans; + HEADER *header = res.header; + unsigned char *pt, *emsg; + int r; + char *cname; + int cnamesize = 1025; + u16 count; + + /* Do not allow a query using the standard resolver in Tor mode. */ + if (tor_mode) + return -1; + + my_unprotect (); + r = res_query (name, C_IN, T_CERT, answer, sizeof res.ans); + my_protect (); + if (r < 0) + return get_h_errno_as_gpg_error (); + if (r < sizeof (HEADER)) + return gpg_error (GPG_ERR_SERVER_FAILED); + if (r > sizeof res.ans) + return gpg_error (GPG_ERR_SYSTEM_BUG); + if (header->rcode != NOERROR || !(count=ntohs (header->ancount))) + return gpg_error (GPG_ERR_NO_NAME); /* Error or no record found. */ + if (count != 1) + return gpg_error (GPG_ERR_SERVER_FAILED); + + emsg = &answer[r]; + pt = &answer[sizeof(HEADER)]; + rc = dn_skipname (pt, emsg); + if (rc == -1) + return gpg_error (GPG_ERR_SERVER_FAILED); + + pt += rc + QFIXEDSZ; + if (pt >= emsg) + return gpg_error (GPG_ERR_SERVER_FAILED); + + rc = dn_skipname (pt, emsg); + if (rc == -1) + return gpg_error (GPG_ERR_SERVER_FAILED); + pt += rc + 2 + 2 + 4; + if (pt+2 >= emsg) + return gpg_error (GPG_ERR_SERVER_FAILED); + pt += 2; /* Skip rdlen */ + + cname = xtrymalloc (cnamesize); + if (!cname) + return gpg_error_from_syserror (); + + rc = dn_expand (answer, emsg, pt, cname, cnamesize -1); + if (rc == -1) + { + xfree (cname); + return gpg_error (GPG_ERR_SERVER_FAILED); + } + *r_cname = xtryrealloc (cname, strlen (cname)+1); + if (!*r_cname) + { + err = gpg_error_from_syserror (); + xfree (cname); + return err; + } + return 0; + +#else /*!HAVE_SYSTEM_RESOLVER*/ + + (void)name; + (void)r_cname; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + +#endif /*!HAVE_SYSTEM_RESOLVER*/ +} + + +gpg_error_t +get_dns_cname (const char *name, char **r_cname) +{ + gpg_error_t err; + + *r_cname = NULL; + +#ifdef USE_LIBDNS + if (!standard_resolver) + { + err = get_dns_cname_libdns (name, r_cname); + if (err && libdns_switch_port_p (err)) + err = get_dns_cname_libdns (name, r_cname); + return err; + } +#endif /*USE_LIBDNS*/ + + err = get_dns_cname_standard (name, r_cname); + if (opt_debug) + log_debug ("get_dns_cname(%s)%s%s\n", name, + err ? ": " : " -> ", + err ? gpg_strerror (err) : *r_cname); + return err; +} + + +/* Check whether the machine has any usable inet devices up and + * running. We put this into dns because on Windows this is + * implemented using getaddrinfo and thus easiest done here. */ +void +check_inet_support (int *r_v4, int *r_v6) +{ + if (cached_inet_support.valid) + { + *r_v4 = cached_inet_support.v4; + *r_v6 = cached_inet_support.v6; + return; + } + + *r_v4 = *r_v6 = 0; + +#ifdef HAVE_W32_SYSTEM + { + gpg_error_t err; + int ret; + struct addrinfo *aibuf = NULL; + struct addrinfo *ai; + + ret = getaddrinfo ("..localmachine", NULL, NULL, &aibuf); + if (ret) + { + err = map_eai_to_gpg_error (ret); + log_error ("%s: getaddrinfo failed: %s\n",__func__, gpg_strerror (err)); + aibuf = NULL; + } + + for (ai = aibuf; ai; ai = ai->ai_next) + { + if (opt_debug) + { + log_debug ("%s: family: %d\n", __func__, ai->ai_family); + if (ai->ai_family == AF_INET6 || ai->ai_family == AF_INET) + { + char buffer[46]; + DWORD buflen; + buflen = sizeof buffer; + if (WSAAddressToString (ai->ai_addr, (DWORD)ai->ai_addrlen, + NULL, buffer, &buflen)) + log_debug ("%s: WSAAddressToString failed: ec=%u\n", + __func__, (unsigned int)WSAGetLastError ()); + else + log_debug ("%s: addr: %s\n", __func__, buffer); + } + } + if (ai->ai_family == AF_INET6) + { + struct sockaddr_in6 *v6addr = (struct sockaddr_in6 *)ai->ai_addr; + if (!IN6_IS_ADDR_LINKLOCAL (&v6addr->sin6_addr)) + *r_v6 = 1; + } + else if (ai->ai_family == AF_INET) + { + *r_v4 = 1; + } + } + + if (aibuf) + freeaddrinfo (aibuf); + } +#else /*!HAVE_W32_SYSTEM*/ + { + /* For now we assume that we have both protocols. */ + *r_v4 = *r_v6 = 1; + } +#endif /*!HAVE_W32_SYSTEM*/ + + if (opt_verbose) + log_info ("detected interfaces:%s%s\n", + *r_v4? " IPv4":"", *r_v6? " IPv6":""); + + cached_inet_support.valid = 1; + cached_inet_support.v4 = *r_v4; + cached_inet_support.v6 = *r_v6; +} diff --git a/dirmngr/dns-stuff.h b/dirmngr/dns-stuff.h new file mode 100644 index 0000000..a9f9651 --- /dev/null +++ b/dirmngr/dns-stuff.h @@ -0,0 +1,176 @@ +/* dns-stuff.c - DNS related code including CERT RR (rfc-4398) + * Copyright (C) 2006 Free Software Foundation, Inc. + * Copyright (C) 2006, 2015 Werner Koch + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * This file 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 <https://www.gnu.org/licenses/>. + */ +#ifndef GNUPG_DIRMNGR_DNS_STUFF_H +#define GNUPG_DIRMNGR_DNS_STUFF_H + +#ifdef HAVE_W32_SYSTEM +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> +#else +# include <sys/types.h> +# include <sys/socket.h> +#endif + +/* + * Flags used with resolve_dns_addr. + */ +#define DNS_NUMERICHOST 1 /* Force numeric output format. */ +#define DNS_WITHBRACKET 2 /* Put brackets around numeric v6 + addresses. */ + +/* + * Constants for use with get_dns_cert. + */ +#define DNS_CERTTYPE_ANY 0 /* Internal catch all type. */ +/* Certificate types according to RFC-4398: */ +#define DNS_CERTTYPE_PKIX 1 /* X.509 as per PKIX. */ +#define DNS_CERTTYPE_SPKI 2 /* SPKI certificate. */ +#define DNS_CERTTYPE_PGP 3 /* OpenPGP packet. */ +#define DNS_CERTTYPE_IPKIX 4 /* The URL of an X.509 data object. */ +#define DNS_CERTTYPE_ISPKI 5 /* The URL of an SPKI certificate. */ +#define DNS_CERTTYPE_IPGP 6 /* The fingerprint + and URL of an OpenPGP packet. */ +#define DNS_CERTTYPE_ACPKIX 7 /* Attribute Certificate. */ +#define DNS_CERTTYPE_IACPKIX 8 /* The URL of an Attribute Certificate. */ +#define DNS_CERTTYPE_URI 253 /* URI private. */ +#define DNS_CERTTYPE_OID 254 /* OID private. */ +/* Hacks for our implementation. */ +#define DNS_CERTTYPE_RRBASE 1024 /* Base of special constants. */ +#define DNS_CERTTYPE_RR61 (DNS_CERTTYPE_RRBASE + 61) + + + +struct dns_addrinfo_s; +typedef struct dns_addrinfo_s *dns_addrinfo_t; +struct dns_addrinfo_s +{ + dns_addrinfo_t next; + int family; + int socktype; + int protocol; + int addrlen; + struct sockaddr_storage addr[1]; +}; + + +struct srventry +{ + unsigned short priority; + unsigned short weight; + unsigned short port; + int run_count; + char target[1025]; +}; + + +/* Set verbosity and debug mode for this module. */ +void set_dns_verbose (int verbose, int debug); + +/* Set the Disable-IPv4 flag so that the name resolver does not return + * A addresses. */ +void set_dns_disable_ipv4 (int yes); + +/* Set the Disable-IPv6 flag so that the name resolver does not return + * AAAA addresses. */ +void set_dns_disable_ipv6 (int yes); + +/* Set the timeout for libdns requests to SECONDS. */ +void set_dns_timeout (int seconds); + +/* Calling this function with YES set to True forces the use of the + * standard resolver even if dirmngr has been built with support for + * an alternative resolver. */ +void enable_standard_resolver (int yes); + +/* Return true if the standard resolver is used. */ +int standard_resolver_p (void); + +/* Calling this function with YES switches libdns into recursive mode. + * It has no effect on the standard resolver. */ +void enable_recursive_resolver (int yes); + +/* Return true iff the recursive resolver is used. */ +int recursive_resolver_p (void); + +/* Put this module eternally into Tor mode. When called agained with + * NEW_CIRCUIT request a new TOR circuit for the next DNS query. */ +void enable_dns_tormode (int new_circuit); +void disable_dns_tormode (void); + +/* Change the default IP address of the nameserver to IPADDR. The + address needs to be a numerical IP address and will be used for the + next DNS query. Note that this is only used in Tor mode. */ +void set_dns_nameserver (const char *ipaddr); + +/* SIGHUP action handler for this module. */ +void reload_dns_stuff (int force); + +/* Housekeeping for this module. */ +void dns_stuff_housekeeping (void); + +void free_dns_addrinfo (dns_addrinfo_t ai); + +/* Function similar to getaddrinfo. */ +gpg_error_t resolve_dns_name (const char *name, unsigned short port, + int want_family, int want_socktype, + dns_addrinfo_t *r_dai, char **r_canonname); + +/* Function similar to getnameinfo. */ +gpg_error_t resolve_dns_addr (const struct sockaddr_storage *addr, int addrlen, + unsigned int flags, char **r_name); + +/* Return true if NAME is a numerical IP address. */ +int is_ip_address (const char *name); + +/* Return true if NAME is an onion address. */ +int is_onion_address (const char *name); + +/* Get the canonical name for NAME. */ +gpg_error_t get_dns_cname (const char *name, char **r_cname); + +/* Return a CERT record or an arbitrary RR. */ +gpg_error_t get_dns_cert (const char *name, int want_certtype, + void **r_key, size_t *r_keylen, + unsigned char **r_fpr, size_t *r_fprlen, + char **r_url); + +/* Return an array of SRV records. */ +gpg_error_t get_dns_srv (const char *name, + const char *service, const char *proto, + struct srventry **list, unsigned int *r_count); + + +/* Store true in the variable if such an IP interface is usable. */ +void check_inet_support (int *r_v4, int *r_v6); + + +#endif /*GNUPG_DIRMNGR_DNS_STUFF_H*/ diff --git a/dirmngr/dns.c b/dirmngr/dns.c new file mode 100644 index 0000000..43845eb --- /dev/null +++ b/dirmngr/dns.c @@ -0,0 +1,11570 @@ +/* ========================================================================== + * dns.c - Recursive, Reentrant DNS Resolver. + * -------------------------------------------------------------------------- + * Copyright (c) 2008, 2009, 2010, 2012-2016 William Ahern + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * ========================================================================== + */ +#if HAVE_CONFIG_H +#include "config.h" +#elif !defined _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif + +#include <limits.h> /* INT_MAX */ +#include <stdarg.h> /* va_list va_start va_end */ +#include <stddef.h> /* offsetof() */ +#ifdef _WIN32 +/* JW: This breaks our mingw build: #define uint32_t unsigned int */ +#else +#include <stdint.h> /* uint32_t */ +#endif +#include <stdlib.h> /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */ +#include <stdio.h> /* FILE fopen(3) fclose(3) getc(3) rewind(3) vsnprintf(3) */ +#include <string.h> /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */ +#include <strings.h> /* strcasecmp(3) strncasecmp(3) */ +#include <ctype.h> /* isspace(3) isdigit(3) */ +#include <time.h> /* time_t time(2) difftime(3) */ +#include <signal.h> /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */ +#include <errno.h> /* errno EINVAL ENOENT */ +#undef NDEBUG +#include <assert.h> /* assert(3) */ + +#if _WIN32 +#ifndef FD_SETSIZE +#define FD_SETSIZE 1024 +#endif +#include <winsock2.h> +#include <ws2tcpip.h> +typedef SOCKET socket_fd_t; +#define STDCALL __stdcall +#ifdef TIME_WITH_SYS_TIME +#include <sys/time.h> /* gettimeofday(2) */ +#endif +#else +typedef int socket_fd_t; +#define STDCALL +#include <sys/time.h> /* gettimeofday(2) */ +#include <sys/types.h> /* FD_SETSIZE socklen_t */ +#include <sys/select.h> /* FD_ZERO FD_SET fd_set select(2) */ +#include <sys/socket.h> /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */ +#if defined(AF_UNIX) +#include <sys/un.h> /* struct sockaddr_un */ +#endif +#include <fcntl.h> /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */ +#include <unistd.h> /* _POSIX_THREADS gethostname(3) close(2) */ +#include <poll.h> /* POLLIN POLLOUT */ +#include <netinet/in.h> /* struct sockaddr_in struct sockaddr_in6 */ +#include <arpa/inet.h> /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */ +#include <netdb.h> /* struct addrinfo */ +#endif + +#include "gpgrt.h" /* For GGPRT_GCC_VERSION */ +#include "dns.h" + + +/* + * C O M P I L E R V E R S I O N & F E A T U R E D E T E C T I O N + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p)) +#define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p))) + +#define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p)) +#define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p))) + +#define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p) + +#if defined __has_builtin +#define dns_has_builtin(x) __has_builtin(x) +#else +#define dns_has_builtin(x) 0 +#endif + +#if defined __has_extension +#define dns_has_extension(x) __has_extension(x) +#else +#define dns_has_extension(x) 0 +#endif + +#ifndef HAVE___ASSUME +#define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0) +#endif + +#ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P +#define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__) +#endif + +#ifndef HAVE___BUILTIN_UNREACHABLE +#define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable)) +#endif + +#ifndef HAVE_PRAGMA_MESSAGE +#define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER) +#endif + + +/* + * C O M P I L E R A N N O T A T I O N S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if __GNUC__ +#define DNS_NOTUSED __attribute__((unused)) +#define DNS_NORETURN __attribute__((noreturn)) +#else +#define DNS_NOTUSED +#define DNS_NORETURN +#endif + +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#pragma clang diagnostic ignored "-Wmissing-field-initializers" +#elif DNS_GNUC_PREREQ(4,6,0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + +/* + * S T A N D A R D M A C R O S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if HAVE___BUILTIN_TYPES_COMPATIBLE_P +#define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b)) +#else +#define dns_same_type(a, b, def) (def) +#endif +#define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0)) +/* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */ +#define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; }))) + +#if HAVE___ASSUME +#define dns_assume(cond) __assume(cond) +#elif HAVE___BUILTIN_UNREACHABLE +#define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0) +#else +#define dns_assume(cond) do { (void)(cond); } while (0) +#endif + +#ifndef lengthof +#define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0])) +#endif + +#ifndef endof +#define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))]) +#endif + + +/* + * M I S C E L L A N E O U S C O M P A T + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if _WIN32 || _WIN64 +#define PRIuZ "Iu" +#else +#define PRIuZ "zu" +#endif + +#ifndef DNS_THREAD_SAFE +#if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0 +#define DNS_THREAD_SAFE 1 +#else +#define DNS_THREAD_SAFE 0 +#endif +#endif + +#ifndef HAVE__STATIC_ASSERT +#define HAVE__STATIC_ASSERT \ + (dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \ + __C11FEATURES__ || __STDC_VERSION__ >= 201112L) +#endif + +#ifndef HAVE_STATIC_ASSERT +#if DNS_GNUC_PREREQ(0,0,0) && !DNS_GNUC_PREREQ(4,6,0) +#define HAVE_STATIC_ASSERT 0 /* glibc doesn't check GCC version */ +#elif defined(static_assert) +#define HAVE_STATIC_ASSERT 1 +#else +#define HAVE_STATIC_ASSERT 0 +#endif +#endif + +#if HAVE_STATIC_ASSERT +#define dns_static_assert(cond, msg) static_assert(cond, msg) +#elif HAVE__STATIC_ASSERT +#define dns_static_assert(cond, msg) _Static_assert(cond, msg) +#else +#define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])] +#endif + + +/* + * D E B U G M A C R O S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +int *dns_debug_p(void) { + static int debug; + + return &debug; +} /* dns_debug_p() */ + +#if DNS_DEBUG + +#undef DNS_DEBUG +#define DNS_DEBUG dns_debug + +#define DNS_SAY_(fmt, ...) \ + do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0) +#define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n") +#define DNS_HAI DNS_SAY("HAI") + +#define DNS_SHOW_(P, fmt, ...) do { \ + if (DNS_DEBUG > 1) { \ + fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \ + fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \ + dns_p_dump((P), stderr); \ + fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \ + } \ +} while (0) + +#define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "") + +#else /* !DNS_DEBUG */ + +#undef DNS_DEBUG +#define DNS_DEBUG 0 + +#define DNS_SAY(...) +#define DNS_HAI +#define DNS_SHOW(...) + +#endif /* DNS_DEBUG */ + +#define DNS_CARP(...) DNS_SAY(__VA_ARGS__) + + +/* + * V E R S I O N R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +const char *dns_vendor(void) { + return DNS_VENDOR; +} /* dns_vendor() */ + + +int dns_v_rel(void) { + return DNS_V_REL; +} /* dns_v_rel() */ + + +int dns_v_abi(void) { + return DNS_V_ABI; +} /* dns_v_abi() */ + + +int dns_v_api(void) { + return DNS_V_API; +} /* dns_v_api() */ + + +/* + * E R R O R R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef EPROTO +# define EPROTO EPROTONOSUPPORT +#endif + +#if _WIN32 + +#define DNS_EINTR WSAEINTR +#define DNS_EINPROGRESS WSAEINPROGRESS +#define DNS_EISCONN WSAEISCONN +#define DNS_EWOULDBLOCK WSAEWOULDBLOCK +#define DNS_EALREADY WSAEALREADY +#define DNS_EAGAIN EAGAIN +#define DNS_ETIMEDOUT WSAETIMEDOUT + +#define dns_syerr() ((int)GetLastError()) +#define dns_soerr() ((int)WSAGetLastError()) + +#else + +#define DNS_EINTR EINTR +#define DNS_EINPROGRESS EINPROGRESS +#define DNS_EISCONN EISCONN +#define DNS_EWOULDBLOCK EWOULDBLOCK +#define DNS_EALREADY EALREADY +#define DNS_EAGAIN EAGAIN +#define DNS_ETIMEDOUT ETIMEDOUT + +#define dns_syerr() errno +#define dns_soerr() errno + +#endif + + +const char *dns_strerror(int error) { + switch (error) { + case DNS_ENOBUFS: + return "DNS packet buffer too small"; + case DNS_EILLEGAL: + return "Illegal DNS RR name or data"; + case DNS_EORDER: + return "Attempt to push RR out of section order"; + case DNS_ESECTION: + return "Invalid section specified"; + case DNS_EUNKNOWN: + return "Unknown DNS error"; + case DNS_EADDRESS: + return "Invalid textual address form"; + case DNS_ENOQUERY: + return "Bad execution state (missing query packet)"; + case DNS_ENOANSWER: + return "Bad execution state (missing answer packet)"; + case DNS_EFETCHED: + return "Answer already fetched"; + case DNS_ESERVICE: + return "The service passed was not recognized for the specified socket type"; + case DNS_ENONAME: + return "The name does not resolve for the supplied parameters"; + case DNS_EFAIL: + return "A non-recoverable error occurred when attempting to resolve the name"; + case DNS_ECONNFIN: + return "Connection closed"; + case DNS_EVERIFY: + return "Reply failed verification"; + default: + return strerror(error); + } /* switch() */ +} /* dns_strerror() */ + + +/* + * A T O M I C R O U T I N E S + * + * Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we + * can use the preprocessor to detect API and, more importantly, ISA + * support. We want to avoid linking headaches where the API depends on an + * external library if the ISA (e.g. i386) doesn't support lockless + * operation. + * + * TODO: Support C11's atomic API. Although that may require some finesse + * with how we define some public types, such as dns_atomic_t and struct + * dns_resolv_conf. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef HAVE___ATOMIC_FETCH_ADD +#ifdef __ATOMIC_RELAXED +#define HAVE___ATOMIC_FETCH_ADD 1 +#else +#define HAVE___ATOMIC_FETCH_ADD 0 +#endif +#endif + +#ifndef HAVE___ATOMIC_FETCH_SUB +#define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD +#endif + +#ifndef DNS_ATOMIC_FETCH_ADD +#if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2 +#define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED) +#else +#pragma message("no atomic_fetch_add available") +#define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++) +#endif +#endif + +#ifndef DNS_ATOMIC_FETCH_SUB +#if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2 +#define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED) +#else +#pragma message("no atomic_fetch_sub available") +#define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--) +#endif +#endif + +static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) { + return DNS_ATOMIC_FETCH_ADD(i); +} /* dns_atomic_fetch_add() */ + + +static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) { + return DNS_ATOMIC_FETCH_SUB(i); +} /* dns_atomic_fetch_sub() */ + + +/* + * C R Y P T O R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/* + * P R N G + */ + +#ifndef DNS_RANDOM +#if defined(HAVE_ARC4RANDOM) \ + || defined(__OpenBSD__) \ + || defined(__FreeBSD__) \ + || defined(__NetBSD__) \ + || defined(__APPLE__) +#define DNS_RANDOM arc4random +#elif __linux +#define DNS_RANDOM random +#else +#define DNS_RANDOM rand +#endif +#endif + +#define DNS_RANDOM_arc4random 1 +#define DNS_RANDOM_random 2 +#define DNS_RANDOM_rand 3 +#define DNS_RANDOM_RAND_bytes 4 + +#define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM)) + +#if DNS_RANDOM_OPENSSL +#include <openssl/rand.h> +#endif + +static unsigned dns_random_(void) { +#if DNS_RANDOM_OPENSSL + unsigned r; + _Bool ok; + + ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r)); + assert(ok && "1 == RAND_bytes()"); + + return r; +#else + return DNS_RANDOM(); +#endif +} /* dns_random_() */ + +dns_random_f **dns_random_p(void) { + static dns_random_f *random_f = &dns_random_; + + return &random_f; +} /* dns_random_p() */ + + +/* + * P E R M U T A T I O N G E N E R A T O R + */ + +#define DNS_K_TEA_KEY_SIZE 16 +#define DNS_K_TEA_BLOCK_SIZE 8 +#define DNS_K_TEA_CYCLES 32 +#define DNS_K_TEA_MAGIC 0x9E3779B9U + +struct dns_k_tea { + uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; + unsigned cycles; +}; /* struct dns_k_tea */ + + +static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) { + memcpy(tea->key, key, sizeof tea->key); + + tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES; +} /* dns_k_tea_init() */ + + +static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) { + uint32_t y, z, sum, n; + + y = v[0]; + z = v[1]; + sum = 0; + + for (n = 0; n < tea->cycles; n++) { + sum += DNS_K_TEA_MAGIC; + y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]); + z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]); + } + + w[0] = y; + w[1] = z; + + return /* void */; +} /* dns_k_tea_encrypt() */ + + +/* + * Permutation generator, based on a Luby-Rackoff Feistel construction. + * + * Specifically, this is a generic balanced Feistel block cipher using TEA + * (another block cipher) as the pseudo-random function, F. At best it's as + * strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or + * perhaps Bernstein's Salsa20 core; I am naively trying to keep things + * simple. + * + * The generator can create a permutation of any set of numbers, as long as + * the size of the set is an even power of 2. This limitation arises either + * out of an inherent property of balanced Feistel constructions, or by my + * own ignorance. I'll tackle an unbalanced construction after I wrap my + * head around Schneier and Kelsey's paper. + * + * CAVEAT EMPTOR. IANAC. + */ +#define DNS_K_PERMUTOR_ROUNDS 8 + +struct dns_k_permutor { + unsigned stepi, length, limit; + unsigned shift, mask, rounds; + + struct dns_k_tea tea; +}; /* struct dns_k_permutor */ + + +static inline unsigned dns_k_permutor_powof(unsigned n) { + unsigned m, i = 0; + + for (m = 1; m < n; m <<= 1, i++) + ;; + + return i; +} /* dns_k_permutor_powof() */ + +static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) { + uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)]; + unsigned width, i; + + p->stepi = 0; + + p->length = (high - low) + 1; + p->limit = high; + + width = dns_k_permutor_powof(p->length); + width += width % 2; + + p->shift = width / 2; + p->mask = (1U << p->shift) - 1; + p->rounds = DNS_K_PERMUTOR_ROUNDS; + + for (i = 0; i < lengthof(key); i++) + key[i] = dns_random(); + + dns_k_tea_init(&p->tea, key, 0); + + return /* void */; +} /* dns_k_permutor_init() */ + + +static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) { + uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)]; + + memset(in, '\0', sizeof in); + + in[0] = k; + in[1] = x; + + dns_k_tea_encrypt(&p->tea, in, out); + + return p->mask & out[0]; +} /* dns_k_permutor_F() */ + + +static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) { + unsigned l[2], r[2]; + unsigned i; + + i = 0; + l[i] = p->mask & (n >> p->shift); + r[i] = p->mask & (n >> 0); + + do { + l[(i + 1) % 2] = r[i % 2]; + r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]); + + i++; + } while (i < p->rounds - 1); + + return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); +} /* dns_k_permutor_E() */ + + +DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) { + unsigned l[2], r[2]; + unsigned i; + + i = p->rounds - 1; + l[i % 2] = p->mask & (n >> p->shift); + r[i % 2] = p->mask & (n >> 0); + + do { + i--; + + r[i % 2] = l[(i + 1) % 2]; + l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]); + } while (i > 0); + + return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0); +} /* dns_k_permutor_D() */ + + +static unsigned dns_k_permutor_step(struct dns_k_permutor *p) { + unsigned n; + + do { + n = dns_k_permutor_E(p, p->stepi++); + } while (n >= p->length); + + return n + (p->limit + 1 - p->length); +} /* dns_k_permutor_step() */ + + +/* + * Simple permutation box. Useful for shuffling rrsets from an iterator. + * Uses AES s-box to provide good diffusion. + * + * Seems to pass muster under runs test. + * + * $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done + * $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }' + * library(lawstat) + * runs.test(scan(file="/tmp/out")) + * EOF + */ +static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) { + static const unsigned char sbox[256] = + { 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, + 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, + 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, + 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, + 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, + 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, + 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, + 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, + 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, + 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, + 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, + 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, + 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, + 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, + 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, + 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, + 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + unsigned char a, b; + unsigned i; + + a = 0xff & (n >> 0); + b = 0xff & (n >> 8); + + for (i = 0; i < 4; i++) { + a ^= 0xff & s; + a = sbox[a] ^ b; + b = sbox[b] ^ a; + s >>= 8; + } + + return ((0xff00 & (a << 8)) | (0x00ff & (b << 0))); +} /* dns_k_shuffle16() */ + +/* + * S T A T E M A C H I N E R O U T I N E S + * + * Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the + * local variable pc. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_SM_ENTER \ + do { \ + static const int pc0 = __LINE__; \ + DNS_SM_RESTORE; \ + switch (pc0 + pc) { \ + case __LINE__: (void)0 + +#define DNS_SM_SAVE_AND_DO(do_statement) \ + do { \ + pc = __LINE__ - pc0; \ + DNS_SM_SAVE; \ + do_statement; \ + case __LINE__: (void)0; \ + } while (0) + +#define DNS_SM_YIELD(rv) \ + DNS_SM_SAVE_AND_DO(return (rv)) + +#define DNS_SM_EXIT \ + do { goto leave; } while (0) + +#define DNS_SM_LEAVE \ + leave: (void)0; \ + DNS_SM_SAVE_AND_DO(break); \ + } \ + } while (0) + +/* + * U T I L I T Y R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_MAXINTERVAL 300 + +struct dns_clock { + time_t sample, elapsed; +}; /* struct dns_clock */ + +static void dns_begin(struct dns_clock *clk) { + clk->sample = time(0); + clk->elapsed = 0; +} /* dns_begin() */ + +static time_t dns_elapsed(struct dns_clock *clk) { + time_t curtime; + + if ((time_t)-1 == time(&curtime)) + return clk->elapsed; + + if (curtime > clk->sample) + clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL); + + clk->sample = curtime; + + return clk->elapsed; +} /* dns_elapsed() */ + + +DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) { + size_t n = 0; + + while (*src++ && n < m) + ++n; + + return n; +} /* dns_strnlen() */ + + +DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) { + size_t len = dns_strnlen(src, max), n; + + if (lim > 0) { + n = DNS_PP_MIN(lim - 1, len); + memcpy(dst, src, n); + dst[n] = '\0'; + } + + return len; +} /* dns_strnlcpy() */ + + +#if (defined AF_UNIX && !defined _WIN32) +#define DNS_HAVE_SOCKADDR_UN 1 +#else +#define DNS_HAVE_SOCKADDR_UN 0 +#endif + +static size_t dns_af_len(int af) { + static const size_t table[AF_MAX] = { + [AF_INET6] = sizeof (struct sockaddr_in6), + [AF_INET] = sizeof (struct sockaddr_in), +#if DNS_HAVE_SOCKADDR_UN + [AF_UNIX] = sizeof (struct sockaddr_un), +#endif + }; + + return table[af]; +} /* dns_af_len() */ + +#define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family) + +#define dns_sa_len(sa) dns_af_len(dns_sa_family(sa)) + + +#define DNS_SA_NOPORT &dns_sa_noport +static unsigned short dns_sa_noport; + +static unsigned short *dns_sa_port(int af, void *sa) { + switch (af) { + case AF_INET6: + return &((struct sockaddr_in6 *)sa)->sin6_port; + case AF_INET: + return &((struct sockaddr_in *)sa)->sin_port; + default: + return DNS_SA_NOPORT; + } +} /* dns_sa_port() */ + + +static void *dns_sa_addr(int af, const void *sa, socklen_t *size) { + switch (af) { + case AF_INET6: { + struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr; + + if (size) + *size = sizeof *in6; + + return in6; + } + case AF_INET: { + struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr; + + if (size) + *size = sizeof *in; + + return in; + } + default: + if (size) + *size = 0; + + return 0; + } +} /* dns_sa_addr() */ + + +#if DNS_HAVE_SOCKADDR_UN +#define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path) +#endif + +DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) { + switch (dns_sa_family(sa)) { +#if DNS_HAVE_SOCKADDR_UN + case AF_UNIX: { + char *path = ((struct sockaddr_un *)sa)->sun_path; + + if (size) + *size = dns_strnlen(path, DNS_SUNPATHMAX); + + return path; + } +#endif + default: + if (size) + *size = 0; + + return NULL; + } +} /* dns_sa_path() */ + + +static int dns_sa_cmp(void *a, void *b) { + int cmp, af; + + if ((cmp = dns_sa_family(a) - dns_sa_family(b))) + return cmp; + + switch ((af = dns_sa_family(a))) { + case AF_INET: { + struct in_addr *a4, *b4; + + if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) + return cmp; + + a4 = dns_sa_addr(af, a, NULL); + b4 = dns_sa_addr(af, b, NULL); + + if (ntohl(a4->s_addr) < ntohl(b4->s_addr)) + return -1; + if (ntohl(a4->s_addr) > ntohl(b4->s_addr)) + return 1; + + return 0; + } + case AF_INET6: { + struct in6_addr *a6, *b6; + size_t i; + + if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b)))) + return cmp; + + a6 = dns_sa_addr(af, a, NULL); + b6 = dns_sa_addr(af, b, NULL); + + /* XXX: do we need to use in6_clearscope()? */ + for (i = 0; i < sizeof a6->s6_addr; i++) { + if ((cmp = a6->s6_addr[i] - b6->s6_addr[i])) + return cmp; + } + + return 0; + } +#if DNS_HAVE_SOCKADDR_UN + case AF_UNIX: { + char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path]; + + dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX); + dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX); + + return strcmp(a_path, b_path); + } +#endif + default: + return -1; + } +} /* dns_sa_cmp() */ + + +#if _WIN32 +static int dns_inet_pton(int af, const void *src, void *dst) { + union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; + int size_of_u = (int)sizeof u; + + u.sin.sin_family = af; + + if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &size_of_u)) + return -1; + + switch (af) { + case AF_INET6: + *(struct in6_addr *)dst = u.sin6.sin6_addr; + + return 1; + case AF_INET: + *(struct in_addr *)dst = u.sin.sin_addr; + + return 1; + default: + return 0; + } +} /* dns_inet_pton() */ + +static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) { + union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u; + + /* NOTE: WSAAddressToString will print .sin_port unless zeroed. */ + memset(&u, 0, sizeof u); + + u.sin.sin_family = af; + + switch (af) { + case AF_INET6: + u.sin6.sin6_addr = *(struct in6_addr *)src; + break; + case AF_INET: + u.sin.sin_addr = *(struct in_addr *)src; + + break; + default: + return 0; + } + + if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim)) + return 0; + + return dst; +} /* dns_inet_ntop() */ +#else +#define dns_inet_pton(...) inet_pton(__VA_ARGS__) +#define dns_inet_ntop(...) inet_ntop(__VA_ARGS__) +#endif + + +static dns_error_t dns_pton(int af, const void *src, void *dst) { + switch (dns_inet_pton(af, src, dst)) { + case 1: + return 0; + case -1: + return dns_soerr(); + default: + return DNS_EADDRESS; + } +} /* dns_pton() */ + + +static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) { + return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr(); +} /* dns_ntop() */ + + +size_t dns_strlcpy(char *dst, const char *src, size_t lim) { + char *d = dst; + char *e = &dst[lim]; + const char *s = src; + + if (d < e) { + do { + if ('\0' == (*d++ = *s++)) + return s - src - 1; + } while (d < e); + + d[-1] = '\0'; + } + + while (*s++ != '\0') + ;; + + return s - src - 1; +} /* dns_strlcpy() */ + + +size_t dns_strlcat(char *dst, const char *src, size_t lim) { + char *d = memchr(dst, '\0', lim); + char *e = &dst[lim]; + const char *s = src; + const char *p; + + if (d && d < e) { + do { + if ('\0' == (*d++ = *s++)) + return d - dst - 1; + } while (d < e); + + d[-1] = '\0'; + } + + p = s; + + while (*s++ != '\0') + ;; + + return lim + (s - p - 1); +} /* dns_strlcat() */ + + +static void *dns_reallocarray(void *p, size_t nmemb, size_t size, dns_error_t *error) { + void *rp; + + if (nmemb > 0 && SIZE_MAX / nmemb < size) { + *error = EOVERFLOW; + return NULL; + } + + if (!(rp = realloc(p, nmemb * size))) + *error = (errno)? errno : EINVAL; + + return rp; +} /* dns_reallocarray() */ + + +#if _WIN32 + +static char *dns_strsep(char **sp, const char *delim) { + char *p; + + if (!(p = *sp)) + return 0; + + *sp += strcspn(p, delim); + + if (**sp != '\0') { + **sp = '\0'; + ++*sp; + } else + *sp = NULL; + + return p; +} /* dns_strsep() */ + +#else +#define dns_strsep(...) strsep(__VA_ARGS__) +#endif + + +#if _WIN32 +#define strcasecmp(...) _stricmp(__VA_ARGS__) +#define strncasecmp(...) _strnicmp(__VA_ARGS__) +#endif + + +static inline _Bool dns_isalpha(unsigned char c) { + return isalpha(c); +} /* dns_isalpha() */ + +static inline _Bool dns_isdigit(unsigned char c) { + return isdigit(c); +} /* dns_isdigit() */ + +static inline _Bool dns_isalnum(unsigned char c) { + return isalnum(c); +} /* dns_isalnum() */ + +static inline _Bool dns_isspace(unsigned char c) { + return isspace(c); +} /* dns_isspace() */ + +static inline _Bool dns_isgraph(unsigned char c) { + return isgraph(c); +} /* dns_isgraph() */ + + +static int dns_poll(int fd, short events, int timeout) { + fd_set rset, wset; + struct timeval tv = { timeout, 0 }; + + if (!events) + return 0; + + if (fd < 0 || (unsigned)fd >= FD_SETSIZE) + return EINVAL; + + FD_ZERO(&rset); + FD_ZERO(&wset); + + if (events & DNS_POLLIN) + FD_SET(fd, &rset); + + if (events & DNS_POLLOUT) + FD_SET(fd, &wset); + + select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &tv : NULL); + + return 0; +} /* dns_poll() */ + + +#if !_WIN32 +DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) { +#if DNS_THREAD_SAFE + return pthread_sigmask(how, set, oset); +#else + return (0 == sigprocmask(how, set, oset))? 0 : errno; +#endif +} /* dns_sigmask() */ +#endif + + +static size_t dns_send(int fd, const void *src, size_t len, int flags, dns_error_t *error) { + long n = send(fd, src, len, flags); + + if (n < 0) { + *error = dns_soerr(); + return 0; + } else { + *error = 0; + return n; + } +} /* dns_send() */ + +static size_t dns_recv(int fd, void *dst, size_t lim, int flags, dns_error_t *error) { + long n = recv(fd, dst, lim, flags); + + if (n < 0) { + *error = dns_soerr(); + return 0; + } else if (n == 0) { + *error = (lim > 0)? DNS_ECONNFIN : EINVAL; + return 0; + } else { + *error = 0; + return n; + } +} /* dns_recv() */ + +static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dns_error_t *_error) { +#if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE + return dns_send(fd, src, len, flags, _error); +#elif defined MSG_NOSIGNAL + return dns_send(fd, src, len, (flags|MSG_NOSIGNAL), _error); +#elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */ + /* + * SIGPIPE handling similar to the approach described in + * http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html + */ + sigset_t pending, blocked, piped; + size_t count; + int error; + + sigemptyset(&pending); + sigpending(&pending); + + if (!sigismember(&pending, SIGPIPE)) { + sigemptyset(&piped); + sigaddset(&piped, SIGPIPE); + sigemptyset(&blocked); + + if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked))) + goto error; + } + + count = dns_send(fd, src, len, flags, &error); + + if (!sigismember(&pending, SIGPIPE)) { + int saved = error; + const struct timespec ts = { 0, 0 }; + + if (!count && error == EPIPE) { + while (-1 == sigtimedwait(&piped, NULL, &ts) && errno == EINTR) + ;; + } + + if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL))) + goto error; + + error = saved; + } + + *_error = error; + return count; +error: + *_error = error; + return 0; +#else +#error "unable to suppress SIGPIPE" + return dns_send(fd, src, len, flags, _error); +#endif +} /* dns_send_nopipe() */ + + +static dns_error_t dns_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) { + if (0 != connect(fd, addr, addrlen)) + return dns_soerr(); + return 0; +} /* dns_connect() */ + + +#define DNS_FOPEN_STDFLAGS "rwabt+" + +static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) { + char *p = dst, *pe = dst + lim; + + /* copy standard flags */ + while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) { + if (!(p < pe)) + return ENOMEM; + *p++ = *src++; + } + + /* append flag to standard flags */ + if (!(p < pe)) + return ENOMEM; + *p++ = fc; + + /* copy remaining mode string, including '\0' */ + do { + if (!(p < pe)) + return ENOMEM; + } while ((*p++ = *src++)); + + return 0; +} /* dns_fopen_addflag() */ + +static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) { + FILE *fp; + char mode_cloexec[32]; + int error; + + assert(path && mode && *mode); + if (!*path) { + error = EINVAL; + goto error; + } + +#if _WIN32 || _WIN64 + if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N'))) + goto error; + if (!(fp = fopen(path, mode_cloexec))) + goto syerr; +#else + if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e'))) + goto error; + if (!(fp = fopen(path, mode_cloexec))) { + if (errno != EINVAL) + goto syerr; + if (!(fp = fopen(path, mode))) + goto syerr; + } +#endif + + return fp; +syerr: + error = dns_syerr(); +error: + *_error = error; + + return NULL; +} /* dns_fopen() */ + + +struct dns_hxd_lines_i { + int pc; + size_t p; +}; + +#define DNS_SM_RESTORE \ + do { \ + pc = state->pc; \ + sp = src + state->p; \ + se = src + len; \ + } while (0) +#define DNS_SM_SAVE \ + do { \ + state->p = sp - src; \ + state->pc = pc; \ + } while (0) + +static size_t dns_hxd_lines(void *dst, size_t lim, const unsigned char *src, size_t len, struct dns_hxd_lines_i *state) { + static const unsigned char hex[] = "0123456789abcdef"; + static const unsigned char tmpl[] = " | |\n"; + unsigned char ln[sizeof tmpl]; + const unsigned char *sp, *se; + unsigned char *h, *g; + unsigned i, n; + int pc; + + DNS_SM_ENTER; + + while (sp < se) { + memcpy(ln, tmpl, sizeof ln); + + h = &ln[2]; + g = &ln[53]; + + for (n = 0; n < 2; n++) { + for (i = 0; i < 8 && se - sp > 0; i++, sp++) { + h[0] = hex[0x0f & (*sp >> 4)]; + h[1] = hex[0x0f & (*sp >> 0)]; + h += 3; + + *g++ = (dns_isgraph(*sp))? *sp : '.'; + } + + h++; + } + + n = dns_strlcpy(dst, (char *)ln, lim); + DNS_SM_YIELD(n); + } + + DNS_SM_EXIT; + DNS_SM_LEAVE; + + return 0; +} + +#undef DNS_SM_SAVE +#undef DNS_SM_RESTORE + +/* + * A R I T H M E T I C R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_CHECK_OVERFLOW(error, r, f, ...) \ + do { \ + uintmax_t _r; \ + *(error) = f(&_r, __VA_ARGS__); \ + *(r) = _r; \ + } while (0) + +static dns_error_t dns_clamp_overflow(uintmax_t *r, uintmax_t n, uintmax_t clamp) { + if (n > clamp) { + *r = clamp; + return ERANGE; + } else { + *r = n; + return 0; + } +} /* dns_clamp_overflow() */ + +static dns_error_t dns_add_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) { + if (~a < b) { + *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0)); + return ERANGE; + } else { + return dns_clamp_overflow(r, a + b, clamp); + } +} /* dns_add_overflow() */ + +static dns_error_t dns_mul_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) { + if (a > 0 && UINTMAX_MAX / a < b) { + *r = DNS_PP_MIN(clamp, ~UINTMAX_C(0)); + return ERANGE; + } else { + return dns_clamp_overflow(r, a * b, clamp); + } +} /* dns_mul_overflow() */ + +/* + * F I X E D - S I Z E D B U F F E R R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_B_INIT(src, n) { \ + (unsigned char *)(src), \ + (unsigned char *)(src), \ + (unsigned char *)(src) + (n), \ +} + +#define DNS_B_FROM(src, n) DNS_B_INIT((src), (n)) +#define DNS_B_INTO(src, n) DNS_B_INIT((src), (n)) + +struct dns_buf { + const unsigned char *base; + unsigned char *p; + const unsigned char *pe; + dns_error_t error; + size_t overflow; +}; /* struct dns_buf */ + +static inline size_t +dns_b_tell(struct dns_buf *b) +{ + return b->p - b->base; +} + +static inline dns_error_t +dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error) +{ + b->overflow += n; + return b->error = error; +} + +DNS_NOTUSED static struct dns_buf * +dns_b_into(struct dns_buf *b, void *src, size_t n) +{ + *b = (struct dns_buf)DNS_B_INTO(src, n); + + return b; +} + +static dns_error_t +dns_b_putc(struct dns_buf *b, unsigned char uc) +{ + if (!(b->p < b->pe)) + return dns_b_setoverflow(b, 1, DNS_ENOBUFS); + + *b->p++ = uc; + + return 0; +} + +static dns_error_t +dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p) +{ + size_t pe = b->pe - b->base; + if (pe <= p) + return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS); + + *((unsigned char *)b->base + p) = uc; + + return 0; +} + +static inline dns_error_t +dns_b_put16(struct dns_buf *b, uint16_t u) +{ + return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0); +} + +static inline dns_error_t +dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p) +{ + if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1)) + return b->error; + + return 0; +} + +DNS_NOTUSED static inline dns_error_t +dns_b_put32(struct dns_buf *b, uint32_t u) +{ + return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16), + dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0); +} + +static dns_error_t +dns_b_put(struct dns_buf *b, const void *src, size_t len) +{ + size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len); + + memcpy(b->p, src, n); + b->p += n; + + if (n < len) + return dns_b_setoverflow(b, len - n, DNS_ENOBUFS); + + return 0; +} + +static dns_error_t +dns_b_puts(struct dns_buf *b, const void *src) +{ + return dns_b_put(b, src, strlen(src)); +} + +DNS_NOTUSED static inline dns_error_t +dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width) +{ + size_t digits, padding, overflow; + uintmax_t r; + unsigned char *tp, *te, tc; + + digits = 0; + r = u; + do { + digits++; + r /= 10; + } while (r); + + padding = width - DNS_PP_MIN(digits, width); + overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding)); + + while (padding--) { + dns_b_putc(b, '0'); + } + + digits = 0; + tp = b->p; + r = u; + do { + if (overflow < ++digits) + dns_b_putc(b, '0' + (r % 10)); + r /= 10; + } while (r); + + te = b->p; + while (tp < te) { + tc = *--te; + *te = *tp; + *tp++ = tc; + } + + return b->error; +} + +static void +dns_b_popc(struct dns_buf *b) +{ + if (b->overflow && !--b->overflow) + b->error = 0; + if (b->p > b->base) + b->p--; +} + +static inline const char * +dns_b_tolstring(struct dns_buf *b, size_t *n) +{ + if (b->p < b->pe) { + *b->p = '\0'; + *n = b->p - b->base; + + return (const char *)b->base; + } else if (b->p > b->base) { + if (b->p[-1] != '\0') { + dns_b_setoverflow(b, 1, DNS_ENOBUFS); + b->p[-1] = '\0'; + } + *n = &b->p[-1] - b->base; + + return (const char *)b->base; + } else { + *n = 0; + + return ""; + } +} + +static inline const char * +dns_b_tostring(struct dns_buf *b) +{ + size_t n; + return dns_b_tolstring(b, &n); +} + +static inline size_t +dns_b_strlen(struct dns_buf *b) +{ + size_t n; + dns_b_tolstring(b, &n); + return n; +} + +static inline size_t +dns_b_strllen(struct dns_buf *b) +{ + size_t n = dns_b_strlen(b); + return n + b->overflow; +} + +DNS_NOTUSED static const struct dns_buf * +dns_b_from(const struct dns_buf *b, const void *src, size_t n) +{ + *(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n); + + return b; +} + +static inline int +dns_b_getc(const struct dns_buf *_b, const int eof) +{ + struct dns_buf *b = (struct dns_buf *)_b; + + if (!(b->p < b->pe)) + return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof; + + return *b->p++; +} + +static inline intmax_t +dns_b_get16(const struct dns_buf *b, const intmax_t eof) +{ + intmax_t n; + + n = (dns_b_getc(b, 0) << 8); + n |= (dns_b_getc(b, 0) << 0); + + return (!b->overflow)? n : eof; +} + +DNS_NOTUSED static inline intmax_t +dns_b_get32(const struct dns_buf *b, const intmax_t eof) +{ + intmax_t n; + + n = (dns_b_get16(b, 0) << 16); + n |= (dns_b_get16(b, 0) << 0); + + return (!b->overflow)? n : eof; +} + +static inline dns_error_t +dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n) +{ + struct dns_buf *src = (struct dns_buf *)_src; + size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n); + size_t src_r = n - src_n; + + dns_b_put(dst, src->p, src_n); + src->p += src_n; + + if (src_r) + return dns_b_setoverflow(src, src_r, DNS_EILLEGAL); + + return dst->error; +} + +/* + * T I M E R O U T I N E S + * + * Most functions still rely on the older time routines defined in the + * utility routines section, above. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_TIME_C(n) UINT64_C(n) +#define DNS_TIME_INF (~DNS_TIME_C(0)) + +typedef uint64_t dns_time_t; +typedef dns_time_t dns_microseconds_t; + +static dns_error_t dns_time_add(dns_time_t *r, dns_time_t a, dns_time_t b) { + int error; + DNS_CHECK_OVERFLOW(&error, r, dns_add_overflow, a, b, DNS_TIME_INF); + return error; +} + +static dns_error_t dns_time_mul(dns_time_t *r, dns_time_t a, dns_time_t b) { + int error; + DNS_CHECK_OVERFLOW(&error, r, dns_mul_overflow, a, b, DNS_TIME_INF); + return error; +} + +static dns_error_t dns_time_diff(dns_time_t *r, dns_time_t a, dns_time_t b) { + if (a < b) { + *r = DNS_TIME_C(0); + return ERANGE; + } else { + *r = a - b; + return 0; + } +} + +static dns_microseconds_t dns_ts2us(const struct timespec *ts, _Bool rup) { + if (ts) { + dns_time_t sec = DNS_PP_MAX(0, ts->tv_sec); + dns_time_t nsec = DNS_PP_MAX(0, ts->tv_nsec); + dns_time_t usec = nsec / 1000; + dns_microseconds_t r; + + if (rup && nsec % 1000 > 0) + usec++; + dns_time_mul(&r, sec, DNS_TIME_C(1000000)); + dns_time_add(&r, r, usec); + + return r; + } else { + return DNS_TIME_INF; + } +} /* dns_ts2us() */ + +static struct timespec *dns_tv2ts(struct timespec *ts, const struct timeval *tv) { + if (tv) { + ts->tv_sec = tv->tv_sec; + ts->tv_nsec = tv->tv_usec * 1000; + + return ts; + } else { + return NULL; + } +} /* dns_tv2ts() */ + +static size_t dns_utime_print(void *_dst, size_t lim, dns_microseconds_t us) { + struct dns_buf dst = DNS_B_INTO(_dst, lim); + + dns_b_fmtju(&dst, us / 1000000, 1); + dns_b_putc(&dst, '.'); + dns_b_fmtju(&dst, us % 1000000, 6); + + return dns_b_strllen(&dst); +} /* dns_utime_print() */ + +/* + * P A C K E T R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +unsigned dns_p_count(struct dns_packet *P, enum dns_section section) { + unsigned count; + + switch (section) { + case DNS_S_QD: + return ntohs(dns_header(P)->qdcount); + case DNS_S_AN: + return ntohs(dns_header(P)->ancount); + case DNS_S_NS: + return ntohs(dns_header(P)->nscount); + case DNS_S_AR: + return ntohs(dns_header(P)->arcount); + default: + count = 0; + + if (section & DNS_S_QD) + count += ntohs(dns_header(P)->qdcount); + if (section & DNS_S_AN) + count += ntohs(dns_header(P)->ancount); + if (section & DNS_S_NS) + count += ntohs(dns_header(P)->nscount); + if (section & DNS_S_AR) + count += ntohs(dns_header(P)->arcount); + + return count; + } +} /* dns_p_count() */ + + +struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) { + if (!P) + return 0; + + assert(size >= offsetof(struct dns_packet, data) + 12); + + memset(P, 0, sizeof *P); + P->size = size - offsetof(struct dns_packet, data); + P->end = 12; + + memset(P->data, '\0', 12); + + return P; +} /* dns_p_init() */ + + +static struct dns_packet *dns_p_reset(struct dns_packet *P) { + return dns_p_init(P, offsetof(struct dns_packet, data) + P->size); +} /* dns_p_reset() */ + + +static unsigned short dns_p_qend(struct dns_packet *P) { + unsigned short qend = 12; + unsigned i, count = dns_p_count(P, DNS_S_QD); + + for (i = 0; i < count && qend < P->end; i++) { + if (P->end == (qend = dns_d_skip(qend, P))) + goto invalid; + + if (P->end - qend < 4) + goto invalid; + + qend += 4; + } + + return DNS_PP_MIN(qend, P->end); +invalid: + return P->end; +} /* dns_p_qend() */ + + +struct dns_packet *dns_p_make(size_t len, int *error) { + struct dns_packet *P; + size_t size = dns_p_calcsize(len); + + if (!(P = dns_p_init(malloc(size), size))) + *error = dns_syerr(); + + return P; +} /* dns_p_make() */ + + +static void dns_p_free(struct dns_packet *P) { + free(P); +} /* dns_p_free() */ + + +/* convience routine to free any existing packet before storing new packet */ +static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) { + dns_p_free(*dst); + + *dst = src; + + return src; +} /* dns_p_setptr() */ + + +static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) { + dns_p_setptr(dst, *src); + + *src = NULL; + + return *dst; +} /* dns_p_movptr() */ + + +int dns_p_grow(struct dns_packet **P) { + struct dns_packet *tmp; + size_t size; + int error; + + if (!*P) { + if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error))) + return error; + + return 0; + } + + size = dns_p_sizeof(*P); + size |= size >> 1; + size |= size >> 2; + size |= size >> 4; + size |= size >> 8; + size++; + + if (size > 65536) + return DNS_ENOBUFS; + + if (!(tmp = realloc(*P, dns_p_calcsize(size)))) + return dns_syerr(); + + tmp->size = size; + *P = tmp; + + return 0; +} /* dns_p_grow() */ + + +struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) { + if (!P) + return 0; + + P->end = DNS_PP_MIN(P->size, P0->end); + + memcpy(P->data, P0->data, P->end); + + return P; +} /* dns_p_copy() */ + + +struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) { + size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0)); + struct dns_packet *M; + enum dns_section section; + struct dns_rr rr, mr; + int error, copy; + + if (!A && B) { + A = B; + Amask = Bmask; + B = 0; + } + +merge: + if (!(M = dns_p_make(bufsiz, &error))) + goto error; + + for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) { + if (A && (section & Amask)) { + dns_rr_foreach(&rr, A, .section = section) { + if ((error = dns_rr_copy(M, &rr, A))) + goto error; + } + } + + if (B && (section & Bmask)) { + dns_rr_foreach(&rr, B, .section = section) { + copy = 1; + + dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) { + if (!(copy = dns_rr_cmp(&rr, B, &mr, M))) + break; + } + + if (copy && (error = dns_rr_copy(M, &rr, B))) + goto error; + } + } + } + + return M; +error: + dns_p_setptr(&M, NULL); + + if (error == DNS_ENOBUFS && bufsiz < 65535) { + bufsiz = DNS_PP_MIN(65535, bufsiz * 2); + + goto merge; + } + + *error_ = error; + + return 0; +} /* dns_p_merge() */ + + +static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t); + +void dns_p_dictadd(struct dns_packet *P, unsigned short dn) { + unsigned short lp, lptr, i; + + lp = dn; + + while (lp < P->end) { + if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) { + lptr = ((0x3f & P->data[lp + 0]) << 8) + | ((0xff & P->data[lp + 1]) << 0); + + for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { + if (P->dict[i] == lptr) { + P->dict[i] = dn; + + return; + } + } + } + + lp = dns_l_skip(lp, P->data, P->end); + } + + for (i = 0; i < lengthof(P->dict); i++) { + if (!P->dict[i]) { + P->dict[i] = dn; + + break; + } + } +} /* dns_p_dictadd() */ + + +static inline uint16_t +plus1_ns (uint16_t count_net) +{ + uint16_t count = ntohs (count_net); + + count++; + return htons (count); +} + +int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) { + size_t end = P->end; + int error; + + if ((error = dns_d_push(P, dn, dnlen))) + goto error; + + if (P->size - P->end < 4) + goto nobufs; + + P->data[P->end++] = 0xff & (type >> 8); + P->data[P->end++] = 0xff & (type >> 0); + + P->data[P->end++] = 0xff & (class >> 8); + P->data[P->end++] = 0xff & (class >> 0); + + if (section == DNS_S_QD) + goto update; + + if (P->size - P->end < 6) + goto nobufs; + + if (type != DNS_T_OPT) + ttl = DNS_PP_MIN(ttl, 0x7fffffffU); + P->data[P->end++] = ttl >> 24; + P->data[P->end++] = ttl >> 16; + P->data[P->end++] = ttl >> 8; + P->data[P->end++] = ttl >> 0; + + if ((error = dns_any_push(P, (union dns_any *)any, type))) + goto error; + +update: + switch (section) { + case DNS_S_QD: + if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR)) + goto order; + + if (!P->memo.qd.base && (error = dns_p_study(P))) + goto error; + + dns_header(P)->qdcount = plus1_ns (dns_header(P)->qdcount); + + P->memo.qd.end = P->end; + P->memo.an.base = P->end; + P->memo.an.end = P->end; + P->memo.ns.base = P->end; + P->memo.ns.end = P->end; + P->memo.ar.base = P->end; + P->memo.ar.end = P->end; + + break; + case DNS_S_AN: + if (dns_p_count(P, DNS_S_NS|DNS_S_AR)) + goto order; + + if (!P->memo.an.base && (error = dns_p_study(P))) + goto error; + + dns_header(P)->ancount = plus1_ns (dns_header(P)->ancount); + + P->memo.an.end = P->end; + P->memo.ns.base = P->end; + P->memo.ns.end = P->end; + P->memo.ar.base = P->end; + P->memo.ar.end = P->end; + + break; + case DNS_S_NS: + if (dns_p_count(P, DNS_S_AR)) + goto order; + + if (!P->memo.ns.base && (error = dns_p_study(P))) + goto error; + + dns_header(P)->nscount = plus1_ns (dns_header(P)->nscount); + + P->memo.ns.end = P->end; + P->memo.ar.base = P->end; + P->memo.ar.end = P->end; + + break; + case DNS_S_AR: + if (!P->memo.ar.base && (error = dns_p_study(P))) + goto error; + + dns_header(P)->arcount = plus1_ns (dns_header(P)->arcount); + + P->memo.ar.end = P->end; + + if (type == DNS_T_OPT && !P->memo.opt.p) { + P->memo.opt.p = end; + P->memo.opt.maxudp = class; + P->memo.opt.ttl = ttl; + } + + break; + default: + error = DNS_ESECTION; + + goto error; + } /* switch() */ + + return 0; +nobufs: + error = DNS_ENOBUFS; + + goto error; +order: + error = DNS_EORDER; + + goto error; +error: + P->end = end; + + return error; +} /* dns_p_push() */ + +#define DNS_SM_RESTORE do { pc = state->pc; error = state->error; } while (0) +#define DNS_SM_SAVE do { state->error = error; state->pc = pc; } while (0) + +struct dns_p_lines_i { + int pc; + enum dns_section section; + struct dns_rr rr; + int error; +}; + +static size_t dns_p_lines_fmt(void *dst, size_t lim, dns_error_t *_error, const char *fmt, ...) { + va_list ap; + int error = 0, n; + + va_start(ap, fmt); + if ((n = vsnprintf(dst, lim, fmt, ap)) < 0) + error = errno; + va_end(ap); + + *_error = error; + return DNS_PP_MAX(n, 0); +} /* dns_p_lines_fmt() */ + +#define DNS_P_LINE(...) \ + do { \ + len = dns_p_lines_fmt(dst, lim, &error, __VA_ARGS__); \ + if (len == 0 && error) \ + goto error; \ + DNS_SM_YIELD(len); \ + } while (0) + +static size_t dns_p_lines(void *dst, size_t lim, dns_error_t *_error, struct dns_packet *P, struct dns_rr_i *I, struct dns_p_lines_i *state) { + int error, pc; + size_t len; + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + + *_error = 0; + + DNS_SM_ENTER; + + DNS_P_LINE(";; [HEADER]\n"); + DNS_P_LINE(";; qid : %d\n", ntohs(dns_header(P)->qid)); + DNS_P_LINE(";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); + DNS_P_LINE(";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); + DNS_P_LINE(";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); + DNS_P_LINE(";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); + DNS_P_LINE(";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); + DNS_P_LINE(";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); + DNS_P_LINE(";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P)); + + while (dns_rr_grep(&state->rr, 1, I, P, &error)) { + if (state->section != state->rr.section) { + DNS_P_LINE("\n"); + DNS_P_LINE(";; [%s:%d]\n", dns_strsection(state->rr.section, __dst), dns_p_count(P, state->rr.section)); + } + + if (!(len = dns_rr_print(dst, lim, &state->rr, P, &error))) + goto error; + dns_strlcat(dst, "\n", lim); + DNS_SM_YIELD(len + 1); + + state->section = state->rr.section; + } + + if (error) + goto error; + + DNS_SM_EXIT; +error: + for (;;) { + *_error = error; + DNS_SM_YIELD(0); + } + + DNS_SM_LEAVE; + + *_error = 0; + return 0; +} /* dns_p_lines() */ + +#undef DNS_P_LINE +#undef DNS_SM_SAVE +#undef DNS_SM_RESTORE + +static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) { + struct dns_p_lines_i lines = { 0 }; + char line[sizeof (union dns_any) * 2]; + size_t len; + int error; + + while ((len = dns_p_lines(line, sizeof line, &error, P, I, &lines))) { + if (len < sizeof line) { + fwrite(line, 1, len, fp); + } else { + fwrite(line, 1, sizeof line - 1, fp); + fputc('\n', fp); + } + } +} /* dns_p_dump3() */ + + +void dns_p_dump(struct dns_packet *P, FILE *fp) { + struct dns_rr_i I_instance = { 0 }; + dns_p_dump3(P, &I_instance, fp); +} /* dns_p_dump() */ + + +static void dns_s_unstudy(struct dns_s_memo *m) + { m->base = 0; m->end = 0; } + +static void dns_m_unstudy(struct dns_p_memo *m) { + dns_s_unstudy(&m->qd); + dns_s_unstudy(&m->an); + dns_s_unstudy(&m->ns); + dns_s_unstudy(&m->ar); + m->opt.p = 0; + m->opt.maxudp = 0; + m->opt.ttl = 0; +} /* dns_m_unstudy() */ + +static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) { + unsigned short count, rp; + + count = dns_p_count(P, section); + + for (rp = base; count && rp < P->end; count--) + rp = dns_rr_skip(rp, P); + + m->base = base; + m->end = rp; + + return 0; +} /* dns_s_study() */ + +static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) { + struct dns_rr rr; + int error; + + if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P))) + goto error; + if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P))) + goto error; + if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P))) + goto error; + if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P))) + goto error; + + m->opt.p = 0; + m->opt.maxudp = 0; + m->opt.ttl = 0; + dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) { + m->opt.p = rr.dn.p; + m->opt.maxudp = rr.class; + m->opt.ttl = rr.ttl; + break; + } + + return 0; +error: + dns_m_unstudy(m); + + return error; +} /* dns_m_study() */ + +int dns_p_study(struct dns_packet *P) { + return dns_m_study(&P->memo, P); +} /* dns_p_study() */ + + +enum dns_rcode dns_p_rcode(struct dns_packet *P) { + return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode); +} /* dns_p_rcode() */ + + +/* + * Q U E R Y P A C K E T R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_Q_RD 0x1 /* recursion desired */ +#define DNS_Q_EDNS0 0x2 /* include OPT RR */ + +static dns_error_t +dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags) +{ + struct dns_packet *Q = NULL; + int error; + + if (dns_p_movptr(&Q, _Q)) { + dns_p_reset(Q); + } else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) { + goto error; + } + + if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0))) + goto error; + + dns_header(Q)->rd = !!(qflags & DNS_Q_RD); + + if (qflags & DNS_Q_EDNS0) { + struct dns_opt opt = DNS_OPT_INIT(&opt); + + opt.version = 0; /* RFC 6891 version */ + opt.maxudp = 4096; + + if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt))) + goto error; + } + + *_Q = Q; + + return 0; +error: + dns_p_free(Q); + + return error; +} + +static dns_error_t +dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags) +{ + return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags); +} + +static dns_error_t +dns_q_remake(struct dns_packet **Q, int qflags) +{ + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + struct dns_rr rr; + int error; + + assert(Q && *Q); + if ((error = dns_rr_parse(&rr, 12, *Q))) + return error; + if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error))) + return error; + if (qlen >= sizeof qname) + return DNS_EILLEGAL; + return dns_q_make2(Q, qname, qlen, rr.type, rr.class, qflags); +} + +/* + * D O M A I N N A M E R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DNS_D_MAXPTRS +#define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */ +#endif + +static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) { + unsigned short len; + unsigned nptrs = 0; + +retry: + if (src >= end) + goto invalid; + + switch (0x03 & (data[src] >> 6)) { + case 0x00: + len = (0x3f & (data[src++])); + + if (end - src < len) + goto invalid; + + if (lim > 0) { + memcpy(dst, &data[src], DNS_PP_MIN(lim, len)); + + dst[DNS_PP_MIN(lim - 1, len)] = '\0'; + } + + *nxt = src + len; + + return len; + case 0x01: + goto invalid; + case 0x02: + goto invalid; + case 0x03: + if (++nptrs > DNS_D_MAXPTRS) + goto invalid; + + if (end - src < 2) + goto invalid; + + src = ((0x3f & data[src + 0]) << 8) + | ((0xff & data[src + 1]) << 0); + + goto retry; + } /* switch() */ + + /* NOT REACHED */ +invalid: + *nxt = end; + + return 0; +} /* dns_l_expand() */ + + +static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) { + unsigned short len; + + if (src >= end) + goto invalid; + + switch (0x03 & (data[src] >> 6)) { + case 0x00: + len = (0x3f & (data[src++])); + + if (end - src < len) + goto invalid; + + return (len)? src + len : end; + case 0x01: + goto invalid; + case 0x02: + goto invalid; + case 0x03: + return end; + } /* switch() */ + + /* NOT REACHED */ +invalid: + return end; +} /* dns_l_skip() */ + + +static _Bool dns_d_isanchored(const void *_src, size_t len) { + const unsigned char *src = _src; + return len > 0 && src[len - 1] == '.'; +} /* dns_d_isanchored() */ + + +static size_t dns_d_ndots(const void *_src, size_t len) { + const unsigned char *p = _src, *pe = p + len; + size_t ndots = 0; + + while ((p = memchr(p, '.', pe - p))) { + ndots++; + p++; + } + + return ndots; +} /* dns_d_ndots() */ + + +static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) { + unsigned char *dst = dst_; + const unsigned char *src = src_; + size_t dp = 0, sp = 0; + int lc; + + /* trim any leading dot(s) */ + while (sp < len && src[sp] == '.') + sp++; + + for (lc = 0; sp < len; lc = src[sp++]) { + /* trim extra dot(s) */ + if (src[sp] == '.' && lc == '.') + continue; + + if (dp < lim) + dst[dp] = src[sp]; + + dp++; + } + + if ((flags & DNS_D_ANCHOR) && lc != '.') { + if (dp < lim) + dst[dp] = '.'; + + dp++; + } + + if (lim > 0) + dst[DNS_PP_MIN(dp, lim - 1)] = '\0'; + + return dp; +} /* dns_d_trim() */ + + +char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) { + if (flags & DNS_D_TRIM) { + dns_d_trim(dst, lim, src, len, flags); + } if (flags & DNS_D_ANCHOR) { + dns_d_anchor(dst, lim, src, len); + } else { + memmove(dst, src, DNS_PP_MIN(lim, len)); + + if (lim > 0) + ((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0'; + } + + return dst; +} /* dns_d_init() */ + + +size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) { + if (len == 0) + return 0; + + memmove(dst, src, DNS_PP_MIN(lim, len)); + + if (((const char *)src)[len - 1] != '.') { + if (len < lim) + ((char *)dst)[len] = '.'; + len++; + } + + if (lim > 0) + ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; + + return len; +} /* dns_d_anchor() */ + + +size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) { + const char *dot; + + /* XXX: Skip any leading dot. Handles cleaving root ".". */ + if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1))) + return 0; + + len -= dot - (const char *)src; + + /* XXX: Unless root, skip the label's trailing dot. */ + if (len > 1) { + src = ++dot; + len--; + } else + src = dot; + + memmove(dst, src, DNS_PP_MIN(lim, len)); + + if (lim > 0) + ((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0'; + + return len; +} /* dns_d_cleave() */ + + +size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) { + struct { unsigned char *b; size_t p, x; } dst, src; + unsigned char ch = '.'; + + dst.b = dst_; + dst.p = 0; + dst.x = 1; + + src.b = (unsigned char *)src_; + src.p = 0; + src.x = 0; + + while (src.x < len) { + ch = src.b[src.x]; + + if (ch == '.') { + if (dst.p < lim) + dst.b[dst.p] = (0x3f & (src.x - src.p)); + + dst.p = dst.x++; + src.p = ++src.x; + } else { + if (dst.x < lim) + dst.b[dst.x] = ch; + + dst.x++; + src.x++; + } + } /* while() */ + + if (src.x > src.p) { + if (dst.p < lim) + dst.b[dst.p] = (0x3f & (src.x - src.p)); + + dst.p = dst.x; + } + + if (dst.p > 1) { + if (dst.p < lim) + dst.b[dst.p] = 0x00; + + dst.p++; + } + +#if 1 + if (dst.p < lim) { + struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b; + unsigned i; + + a.p = 0; + + while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) { + for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) { + b.p = P->dict[i]; + + while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) { + a.y = a.x; + b.y = b.x; + + while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) { + a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim); + b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end); + } + + if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) { + dst.b[a.p++] = 0xc0 + | (0x3f & (b.p >> 8)); + dst.b[a.p++] = (0xff & (b.p >> 0)); + + /* silence static analyzers */ + dns_assume(a.p > 0); + + return a.p; + } + + b.p = b.x; + } /* while() */ + } /* for() */ + + a.p = a.x; + } /* while() */ + } /* if () */ +#endif + + if (!dst.p) + *error = DNS_EILLEGAL; + + return dst.p; +} /* dns_d_comp() */ + + +unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) { + unsigned short len; + + while (src < P->end) { + switch (0x03 & (P->data[src] >> 6)) { + case 0x00: /* FOLLOWS */ + len = (0x3f & P->data[src++]); + + if (0 == len) { +/* success ==> */ return src; + } else if (P->end - src > len) { + src += len; + + break; + } else + goto invalid; + + /* NOT REACHED */ + case 0x01: /* RESERVED */ + goto invalid; + case 0x02: /* RESERVED */ + goto invalid; + case 0x03: /* POINTER */ + if (P->end - src < 2) + goto invalid; + + src += 2; + +/* success ==> */ return src; + } /* switch() */ + } /* while() */ + +invalid: + return P->end; +} /* dns_d_skip() */ + + +#include <stdio.h> + +size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) { + size_t dstp = 0; + unsigned nptrs = 0; + unsigned char len; + + while (src < P->end) { + switch ((0x03 & (P->data[src] >> 6))) { + case 0x00: /* FOLLOWS */ + len = (0x3f & P->data[src]); + + if (0 == len) { + if (dstp == 0) { + if (dstp < lim) + ((unsigned char *)dst)[dstp] = '.'; + + dstp++; + } + + /* NUL terminate */ + if (lim > 0) + ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; + +/* success ==> */ return dstp; + } + + src++; + + if (P->end - src < len) + goto toolong; + + if (dstp < lim) + memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp)); + + src += len; + dstp += len; + + if (dstp < lim) + ((unsigned char *)dst)[dstp] = '.'; + + dstp++; + + nptrs = 0; + + continue; + case 0x01: /* RESERVED */ + goto reserved; + case 0x02: /* RESERVED */ + goto reserved; + case 0x03: /* POINTER */ + if (++nptrs > DNS_D_MAXPTRS) + goto toolong; + + if (P->end - src < 2) + goto toolong; + + src = ((0x3f & P->data[src + 0]) << 8) + | ((0xff & P->data[src + 1]) << 0); + + continue; + } /* switch() */ + } /* while() */ + +toolong: + *error = DNS_EILLEGAL; + + if (lim > 0) + ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; + + return 0; +reserved: + *error = DNS_EILLEGAL; + + if (lim > 0) + ((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0'; + + return 0; +} /* dns_d_expand() */ + + +int dns_d_push(struct dns_packet *P, const void *dn, size_t len) { + size_t lim = P->size - P->end; + unsigned dp = P->end; + int error = DNS_EILLEGAL; /* silence compiler */ + + len = dns_d_comp(&P->data[dp], lim, dn, len, P, &error); + + if (len == 0) + return error; + if (len > lim) + return DNS_ENOBUFS; + + P->end += len; + + dns_p_dictadd(P, dp); + + return 0; +} /* dns_d_push() */ + + +size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) { + char host[DNS_D_MAXNAME + 1]; + struct dns_rr_i i; + struct dns_rr rr; + unsigned depth; + int error; + + if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len)) + { error = ENAMETOOLONG; goto error; } + + for (depth = 0; depth < 7; depth++) { + memset(&i, 0, sizeof i); + i.section = DNS_S_ALL & ~DNS_S_QD; + i.name = host; + i.type = DNS_T_CNAME; + + if (!dns_rr_grep(&rr, 1, &i, P, &error)) + break; + + if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P))) + goto error; + } + + return dns_strlcpy(dst, host, lim); +error: + *error_ = error; + + return 0; +} /* dns_d_cname() */ + + +/* + * R E S O U R C E R E C O R D R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) { + unsigned char dn[DNS_D_MAXNAME + 1]; + union dns_any any; + size_t len; + int error; + + if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error))) + return error; + else if (len >= sizeof dn) + return DNS_EILLEGAL; + + if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q))) + return error; + + return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any); +} /* dns_rr_copy() */ + + +int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) { + unsigned short p = src; + + if (src >= P->end) + goto invalid; + + rr->dn.p = p; + rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p; + + if (P->end - p < 4) + goto invalid; + + rr->type = ((0xff & P->data[p + 0]) << 8) + | ((0xff & P->data[p + 1]) << 0); + + rr->class = ((0xff & P->data[p + 2]) << 8) + | ((0xff & P->data[p + 3]) << 0); + + p += 4; + + if (src < dns_p_qend(P)) { + rr->section = DNS_S_QUESTION; + + rr->ttl = 0; + rr->rd.p = 0; + rr->rd.len = 0; + + return 0; + } + + if (P->end - p < 4) + goto invalid; + + rr->ttl = ((0xff & P->data[p + 0]) << 24) + | ((0xff & P->data[p + 1]) << 16) + | ((0xff & P->data[p + 2]) << 8) + | ((0xff & P->data[p + 3]) << 0); + if (rr->type != DNS_T_OPT) + rr->ttl = DNS_PP_MIN(rr->ttl, 0x7fffffffU); + + p += 4; + + if (P->end - p < 2) + goto invalid; + + rr->rd.len = ((0xff & P->data[p + 0]) << 8) + | ((0xff & P->data[p + 1]) << 0); + rr->rd.p = p + 2; + + p += 2; + + if (P->end - p < rr->rd.len) + goto invalid; + + return 0; +invalid: + return DNS_EILLEGAL; +} /* dns_rr_parse() */ + + +static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) { + unsigned short rp, rdlen; + + rp = dns_d_skip(src, P); + + if (P->end - rp < 4) + return P->end - src; + + rp += 4; /* TYPE, CLASS */ + + if (rp <= dns_p_qend(P)) + return rp - src; + + if (P->end - rp < 6) + return P->end - src; + + rp += 6; /* TTL, RDLEN */ + + rdlen = ((0xff & P->data[rp - 2]) << 8) + | ((0xff & P->data[rp - 1]) << 0); + + if (P->end - rp < rdlen) + return P->end - src; + + rp += rdlen; + + return rp - src; +} /* dns_rr_len() */ + + +unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) { + return src + dns_rr_len(src, P); +} /* dns_rr_skip() */ + + +static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) { + enum dns_section section; + unsigned count, index; + unsigned short rp; + + if (src >= P->memo.qd.base && src < P->memo.qd.end) + return DNS_S_QD; + if (src >= P->memo.an.base && src < P->memo.an.end) + return DNS_S_AN; + if (src >= P->memo.ns.base && src < P->memo.ns.end) + return DNS_S_NS; + if (src >= P->memo.ar.base && src < P->memo.ar.end) + return DNS_S_AR; + + /* NOTE: Possibly bad memoization. Try it the hard-way. */ + + for (rp = 12, index = 0; rp < src && rp < P->end; index++) + rp = dns_rr_skip(rp, P); + + section = DNS_S_QD; + count = dns_p_count(P, section); + + while (index >= count && section <= DNS_S_AR) { + section <<= 1; + count += dns_p_count(P, section); + } + + return DNS_S_ALL & section; +} /* dns_rr_section() */ + + +static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) { + struct dns_rr rr; + int error; + + if ((error = dns_rr_parse(&rr, src, P))) + return 0; + + return rr.type; +} /* dns_rr_type() */ + + +int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) { + char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1]; + union dns_any any0, any1; + int cmp, error; + size_t len; + + if ((cmp = r0->type - r1->type)) + return cmp; + + if ((cmp = r0->class - r1->class)) + return cmp; + + /* + * FIXME: Do label-by-label comparison to handle illegally long names? + */ + + if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error)) + || len >= sizeof host0) + return -1; + + if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error)) + || len >= sizeof host1) + return 1; + + if ((cmp = strcasecmp(host0, host1))) + return cmp; + + if (DNS_S_QD & (r0->section | r1->section)) { + if (r0->section == r1->section) + return 0; + + return (r0->section == DNS_S_QD)? -1 : 1; + } + + if ((error = dns_any_parse(&any0, r0, P0))) + return -1; + + if ((error = dns_any_parse(&any1, r1, P1))) + return 1; + + return dns_any_cmp(&any0, r0->type, &any1, r1->type); +} /* dns_rr_cmp() */ + + +static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) { + struct dns_rr rr1; + + dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) { + if (0 == dns_rr_cmp(rr0, P0, &rr1, P1)) + return 1; + } + + return 0; +} /* dns_rr_exists() */ + + +static unsigned short dns_rr_offset(struct dns_rr *rr) { + return rr->dn.p; +} /* dns_rr_offset() */ + + +static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) { + if (i->section && !(rr->section & i->section)) + return 0; + + if (i->type && rr->type != i->type && i->type != DNS_T_ALL) + return 0; + + if (i->class && rr->class != i->class && i->class != DNS_C_ANY) + return 0; + + if (i->name) { + char dn[DNS_D_MAXNAME + 1]; + size_t len; + int error; + + if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error)) + || len >= sizeof dn) + return 0; + + if (0 != strcasecmp(dn, i->name)) + return 0; + } + + if (i->data && i->type && rr->section > DNS_S_QD) { + union dns_any rd; + int error; + + if ((error = dns_any_parse(&rd, rr, P))) + return 0; + + if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type)) + return 0; + } + + return 1; +} /* dns_rr_i_match() */ + + +static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) { + unsigned short rp; + struct dns_rr r0, rr; + int error; + + if ((i->section & DNS_S_QD) && P->memo.qd.base) + rp = P->memo.qd.base; + else if ((i->section & DNS_S_AN) && P->memo.an.base) + rp = P->memo.an.base; + else if ((i->section & DNS_S_NS) && P->memo.ns.base) + rp = P->memo.ns.base; + else if ((i->section & DNS_S_AR) && P->memo.ar.base) + rp = P->memo.ar.base; + else + rp = 12; + + for (; rp < P->end; rp = dns_rr_skip(rp, P)) { + if ((error = dns_rr_parse(&rr, rp, P))) + continue; + + rr.section = dns_rr_section(rp, P); + + if (!dns_rr_i_match(&rr, i, P)) + continue; + + r0 = rr; + + goto lower; + } + + return P->end; +lower: + if (i->sort == &dns_rr_i_packet) + return dns_rr_offset(&r0); + + while ((rp = dns_rr_skip(rp, P)) < P->end) { + if ((error = dns_rr_parse(&rr, rp, P))) + continue; + + rr.section = dns_rr_section(rp, P); + + if (!dns_rr_i_match(&rr, i, P)) + continue; + + if (i->sort(&rr, &r0, i, P) < 0) + r0 = rr; + } + + return dns_rr_offset(&r0); +} /* dns_rr_i_start() */ + + +static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) { + struct dns_rr r0, r1, rr; + int error; + + if ((error = dns_rr_parse(&r0, rp, P))) + return P->end; + + r0.section = dns_rr_section(rp, P); + + rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12; + + for (; rp < P->end; rp = dns_rr_skip(rp, P)) { + if ((error = dns_rr_parse(&rr, rp, P))) + continue; + + rr.section = dns_rr_section(rp, P); + + if (!dns_rr_i_match(&rr, i, P)) + continue; + + if (i->sort(&rr, &r0, i, P) <= 0) + continue; + + r1 = rr; + + goto lower; + } + + return P->end; +lower: + if (i->sort == &dns_rr_i_packet) + return dns_rr_offset(&r1); + + while ((rp = dns_rr_skip(rp, P)) < P->end) { + if ((error = dns_rr_parse(&rr, rp, P))) + continue; + + rr.section = dns_rr_section(rp, P); + + if (!dns_rr_i_match(&rr, i, P)) + continue; + + if (i->sort(&rr, &r0, i, P) <= 0) + continue; + + if (i->sort(&rr, &r1, i, P) >= 0) + continue; + + r1 = rr; + } + + return dns_rr_offset(&r1); +} /* dns_rr_i_skip() */ + + +int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { + (void)i; + (void)P; + + return (int)a->dn.p - (int)b->dn.p; +} /* dns_rr_i_packet() */ + + +int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { + int cmp; + + (void)i; + + if ((cmp = a->section - b->section)) + return cmp; + + if (a->type != b->type) + return (int)a->dn.p - (int)b->dn.p; + + return dns_rr_cmp(a, P, b, P); +} /* dns_rr_i_order() */ + + +int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { + int cmp; + + (void)i; + (void)P; + + while (!i->state.regs[0]) + i->state.regs[0] = dns_random(); + + if ((cmp = a->section - b->section)) + return cmp; + + return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]); +} /* dns_rr_i_shuffle() */ + + +void dns_rr_i_init(struct dns_rr_i *i) { + static const struct dns_rr_i i_initializer; + + i->state = i_initializer.state; + i->saved = i->state; +} /* dns_rr_i_init() */ + + +unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) { + unsigned count = 0; + int error; + + switch (i->state.exec) { + case 0: + if (!i->sort) + i->sort = &dns_rr_i_packet; + + i->state.next = dns_rr_i_start(i, P); + i->state.exec++; + + /* FALL THROUGH */ + case 1: + while (count < lim && i->state.next < P->end) { + if ((error = dns_rr_parse(rr, i->state.next, P))) + goto error; + + rr->section = dns_rr_section(i->state.next, P); + + rr++; + count++; + i->state.count++; + + i->state.next = dns_rr_i_skip(i->state.next, i, P); + } /* while() */ + + break; + } /* switch() */ + + return count; +error: + if (error_) + *error_ = error; + + return count; +} /* dns_rr_grep() */ + + +size_t dns_rr_print(void *_dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *_error) { + struct dns_buf dst = DNS_B_INTO(_dst, lim); + union dns_any any; + size_t n; + int error; + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + + if (rr->section == DNS_S_QD) + dns_b_putc(&dst, ';'); + + if (!(n = dns_d_expand(any.ns.host, sizeof any.ns.host, rr->dn.p, P, &error))) + goto error; + dns_b_put(&dst, any.ns.host, DNS_PP_MIN(n, sizeof any.ns.host - 1)); + + if (rr->section != DNS_S_QD) { + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, rr->ttl, 0); + } + + dns_b_putc(&dst, ' '); + dns_b_puts(&dst, dns_strclass(rr->class, __dst)); + dns_b_putc(&dst, ' '); + dns_b_puts(&dst, dns_strtype(rr->type, __dst)); + + if (rr->section == DNS_S_QD) + goto epilog; + + dns_b_putc(&dst, ' '); + + if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P))) + goto error; + + n = dns_any_print(dst.p, dst.pe - dst.p, &any, rr->type); + dst.p += DNS_PP_MIN(n, (size_t)(dst.pe - dst.p)); +epilog: + return dns_b_strllen(&dst); +error: + *_error = error; + + return 0; +} /* dns_rr_print() */ + + +int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) { + unsigned long addr; + + if (rr->rd.len != 4) + return DNS_EILLEGAL; + + addr = ((0xffU & P->data[rr->rd.p + 0]) << 24) + | ((0xffU & P->data[rr->rd.p + 1]) << 16) + | ((0xffU & P->data[rr->rd.p + 2]) << 8) + | ((0xffU & P->data[rr->rd.p + 3]) << 0); + + a->addr.s_addr = htonl(addr); + + return 0; +} /* dns_a_parse() */ + + +int dns_a_push(struct dns_packet *P, struct dns_a *a) { + unsigned long addr; + + if (P->size - P->end < 6) + return DNS_ENOBUFS; + + P->data[P->end++] = 0x00; + P->data[P->end++] = 0x04; + + addr = ntohl(a->addr.s_addr); + + P->data[P->end++] = 0xffU & (addr >> 24); + P->data[P->end++] = 0xffU & (addr >> 16); + P->data[P->end++] = 0xffU & (addr >> 8); + P->data[P->end++] = 0xffU & (addr >> 0); + + return 0; +} /* dns_a_push() */ + + +size_t dns_a_arpa(void *_dst, size_t lim, const struct dns_a *a) { + struct dns_buf dst = DNS_B_INTO(_dst, lim); + unsigned long octets = ntohl(a->addr.s_addr); + unsigned i; + + for (i = 0; i < 4; i++) { + dns_b_fmtju(&dst, 0xff & octets, 0); + dns_b_putc(&dst, '.'); + octets >>= 8; + } + + dns_b_puts(&dst, "in-addr.arpa."); + + return dns_b_strllen(&dst); +} /* dns_a_arpa() */ + + +int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) { + if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr)) + return -1; + if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr)) + return 1; + + return 0; +} /* dns_a_cmp() */ + + +size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) { + char addr[INET_ADDRSTRLEN + 1] = "0.0.0.0"; + + dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr); + + return dns_strlcpy(dst, addr, lim); +} /* dns_a_print() */ + + +int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) { + if (rr->rd.len != sizeof aaaa->addr.s6_addr) + return DNS_EILLEGAL; + + memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr); + + return 0; +} /* dns_aaaa_parse() */ + + +int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) { + if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr) + return DNS_ENOBUFS; + + P->data[P->end++] = 0x00; + P->data[P->end++] = 0x10; + + memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr); + + P->end += sizeof aaaa->addr.s6_addr; + + return 0; +} /* dns_aaaa_push() */ + + +int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) { + unsigned i; + int cmp; + + for (i = 0; i < lengthof(a->addr.s6_addr); i++) { + if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i]))) + return cmp; + } + + return 0; +} /* dns_aaaa_cmp() */ + + +size_t dns_aaaa_arpa(void *_dst, size_t lim, const struct dns_aaaa *aaaa) { + static const unsigned char hex[16] = "0123456789abcdef"; + struct dns_buf dst = DNS_B_INTO(_dst, lim); + unsigned nyble; + int i, j; + + for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) { + nyble = aaaa->addr.s6_addr[i]; + + for (j = 0; j < 2; j++) { + dns_b_putc(&dst, hex[0x0f & nyble]); + dns_b_putc(&dst, '.'); + nyble >>= 4; + } + } + + dns_b_puts(&dst, "ip6.arpa."); + + return dns_b_strllen(&dst); +} /* dns_aaaa_arpa() */ + + +size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) { + char addr[INET6_ADDRSTRLEN + 1] = "::"; + + dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr); + + return dns_strlcpy(dst, addr, lim); +} /* dns_aaaa_print() */ + + +int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) { + size_t len; + int error; + + if (rr->rd.len < 3) + return DNS_EILLEGAL; + + mx->preference = (0xff00 & (P->data[rr->rd.p + 0] << 8)) + | (0x00ff & (P->data[rr->rd.p + 1] << 0)); + + if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error))) + return error; + else if (len >= sizeof mx->host) + return DNS_EILLEGAL; + + return 0; +} /* dns_mx_parse() */ + + +int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) { + size_t end, len; + int error; + + if (P->size - P->end < 5) + return DNS_ENOBUFS; + + end = P->end; + P->end += 2; + + P->data[P->end++] = 0xff & (mx->preference >> 8); + P->data[P->end++] = 0xff & (mx->preference >> 0); + + if ((error = dns_d_push(P, mx->host, strlen(mx->host)))) + goto error; + + len = P->end - end - 2; + + P->data[end + 0] = 0xff & (len >> 8); + P->data[end + 1] = 0xff & (len >> 0); + + return 0; +error: + P->end = end; + + return error; +} /* dns_mx_push() */ + + +int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) { + int cmp; + + if ((cmp = a->preference - b->preference)) + return cmp; + + return strcasecmp(a->host, b->host); +} /* dns_mx_cmp() */ + + +size_t dns_mx_print(void *_dst, size_t lim, struct dns_mx *mx) { + struct dns_buf dst = DNS_B_INTO(_dst, lim); + + dns_b_fmtju(&dst, mx->preference, 0); + dns_b_putc(&dst, ' '); + dns_b_puts(&dst, mx->host); + + return dns_b_strllen(&dst); +} /* dns_mx_print() */ + + +size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) { + return dns_strlcpy(dst, mx->host, lim); +} /* dns_mx_cname() */ + + +int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) { + size_t len; + int error; + + if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error))) + return error; + else if (len >= sizeof ns->host) + return DNS_EILLEGAL; + + return 0; +} /* dns_ns_parse() */ + + +int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) { + size_t end, len; + int error; + + if (P->size - P->end < 3) + return DNS_ENOBUFS; + + end = P->end; + P->end += 2; + + if ((error = dns_d_push(P, ns->host, strlen(ns->host)))) + goto error; + + len = P->end - end - 2; + + P->data[end + 0] = 0xff & (len >> 8); + P->data[end + 1] = 0xff & (len >> 0); + + return 0; +error: + P->end = end; + + return error; +} /* dns_ns_push() */ + + +int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) { + return strcasecmp(a->host, b->host); +} /* dns_ns_cmp() */ + + +size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) { + return dns_strlcpy(dst, ns->host, lim); +} /* dns_ns_print() */ + + +size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) { + return dns_strlcpy(dst, ns->host, lim); +} /* dns_ns_cname() */ + + +int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) { + return dns_ns_parse((struct dns_ns *)cname, rr, P); +} /* dns_cname_parse() */ + + +int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) { + return dns_ns_push(P, (struct dns_ns *)cname); +} /* dns_cname_push() */ + + +int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) { + return strcasecmp(a->host, b->host); +} /* dns_cname_cmp() */ + + +size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) { + return dns_ns_print(dst, lim, (struct dns_ns *)cname); +} /* dns_cname_print() */ + + +size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) { + return dns_strlcpy(dst, cname->host, lim); +} /* dns_cname_cname() */ + + +int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) { + struct { void *dst; size_t lim; } dn[] = + { { soa->mname, sizeof soa->mname }, + { soa->rname, sizeof soa->rname } }; + unsigned *ts[] = + { &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum }; + unsigned short rp; + unsigned i, j, n; + int error; + + /* MNAME / RNAME */ + if ((rp = rr->rd.p) >= P->end) + return DNS_EILLEGAL; + + for (i = 0; i < lengthof(dn); i++) { + if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error))) + return error; + else if (n >= dn[i].lim) + return DNS_EILLEGAL; + + if ((rp = dns_d_skip(rp, P)) >= P->end) + return DNS_EILLEGAL; + } + + /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ + for (i = 0; i < lengthof(ts); i++) { + for (j = 0; j < 4; j++, rp++) { + if (rp >= P->end) + return DNS_EILLEGAL; + + *ts[i] <<= 8; + *ts[i] |= (0xff & P->data[rp]); + } + } + + return 0; +} /* dns_soa_parse() */ + + +int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) { + void *dn[] = { soa->mname, soa->rname }; + unsigned ts[] = { (0xffffffff & soa->serial), + (0x7fffffff & soa->refresh), + (0x7fffffff & soa->retry), + (0x7fffffff & soa->expire), + (0xffffffff & soa->minimum) }; + unsigned i, j; + size_t end, len; + int error; + + end = P->end; + + if ((P->end += 2) >= P->size) + goto toolong; + + /* MNAME / RNAME */ + for (i = 0; i < lengthof(dn); i++) { + if ((error = dns_d_push(P, dn[i], strlen(dn[i])))) + goto error; + } + + /* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */ + for (i = 0; i < lengthof(ts); i++) { + if ((P->end += 4) >= P->size) + goto toolong; + + for (j = 1; j <= 4; j++) { + P->data[P->end - j] = (0xff & ts[i]); + ts[i] >>= 8; + } + } + + len = P->end - end - 2; + P->data[end + 0] = (0xff & (len >> 8)); + P->data[end + 1] = (0xff & (len >> 0)); + + return 0; +toolong: + error = DNS_ENOBUFS; + + /* FALL THROUGH */ +error: + P->end = end; + + return error; +} /* dns_soa_push() */ + + +int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) { + int cmp; + + if ((cmp = strcasecmp(a->mname, b->mname))) + return cmp; + + if ((cmp = strcasecmp(a->rname, b->rname))) + return cmp; + + if (a->serial > b->serial) + return -1; + else if (a->serial < b->serial) + return 1; + + if (a->refresh > b->refresh) + return -1; + else if (a->refresh < b->refresh) + return 1; + + if (a->retry > b->retry) + return -1; + else if (a->retry < b->retry) + return 1; + + if (a->expire > b->expire) + return -1; + else if (a->expire < b->expire) + return 1; + + if (a->minimum > b->minimum) + return -1; + else if (a->minimum < b->minimum) + return 1; + + return 0; +} /* dns_soa_cmp() */ + + +size_t dns_soa_print(void *_dst, size_t lim, struct dns_soa *soa) { + struct dns_buf dst = DNS_B_INTO(_dst, lim); + + dns_b_puts(&dst, soa->mname); + dns_b_putc(&dst, ' '); + dns_b_puts(&dst, soa->rname); + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, soa->serial, 0); + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, soa->refresh, 0); + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, soa->retry, 0); + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, soa->expire, 0); + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, soa->minimum, 0); + + return dns_b_strllen(&dst); +} /* dns_soa_print() */ + + +int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) { + unsigned short rp; + unsigned i; + size_t n; + int error; + + memset(srv, '\0', sizeof *srv); + + rp = rr->rd.p; + + if (rr->rd.len < 7) + return DNS_EILLEGAL; + + for (i = 0; i < 2; i++, rp++) { + srv->priority <<= 8; + srv->priority |= (0xff & P->data[rp]); + } + + for (i = 0; i < 2; i++, rp++) { + srv->weight <<= 8; + srv->weight |= (0xff & P->data[rp]); + } + + for (i = 0; i < 2; i++, rp++) { + srv->port <<= 8; + srv->port |= (0xff & P->data[rp]); + } + + if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error))) + return error; + else if (n >= sizeof srv->target) + return DNS_EILLEGAL; + + return 0; +} /* dns_srv_parse() */ + + +int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) { + size_t end, len; + int error; + + end = P->end; + + if (P->size - P->end < 2) + goto toolong; + + P->end += 2; + + if (P->size - P->end < 6) + goto toolong; + + P->data[P->end++] = 0xff & (srv->priority >> 8); + P->data[P->end++] = 0xff & (srv->priority >> 0); + + P->data[P->end++] = 0xff & (srv->weight >> 8); + P->data[P->end++] = 0xff & (srv->weight >> 0); + + P->data[P->end++] = 0xff & (srv->port >> 8); + P->data[P->end++] = 0xff & (srv->port >> 0); + + if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error))) + goto error; + else if (P->size - P->end < len) + goto toolong; + + P->end += len; + + if (P->end > 65535) + goto toolong; + + len = P->end - end - 2; + + P->data[end + 0] = 0xff & (len >> 8); + P->data[end + 1] = 0xff & (len >> 0); + + return 0; +toolong: + error = DNS_ENOBUFS; + + /* FALL THROUGH */ +error: + P->end = end; + + return error; +} /* dns_srv_push() */ + + +int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) { + int cmp; + + if ((cmp = a->priority - b->priority)) + return cmp; + + /* + * FIXME: We need some sort of random seed to implement the dynamic + * weighting required by RFC 2782. + */ + if ((cmp = a->weight - b->weight)) + return cmp; + + if ((cmp = a->port - b->port)) + return cmp; + + return strcasecmp(a->target, b->target); +} /* dns_srv_cmp() */ + + +size_t dns_srv_print(void *_dst, size_t lim, struct dns_srv *srv) { + struct dns_buf dst = DNS_B_INTO(_dst, lim); + + dns_b_fmtju(&dst, srv->priority, 0); + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, srv->weight, 0); + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, srv->port, 0); + dns_b_putc(&dst, ' '); + dns_b_puts(&dst, srv->target); + + return dns_b_strllen(&dst); +} /* dns_srv_print() */ + + +size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) { + return dns_strlcpy(dst, srv->target, lim); +} /* dns_srv_cname() */ + + +unsigned int dns_opt_ttl(const struct dns_opt *opt) { + unsigned int ttl = 0; + + ttl |= (0xffU & opt->rcode) << 24; + ttl |= (0xffU & opt->version) << 16; + ttl |= (0xffffU & opt->flags) << 0; + + return ttl; +} /* dns_opt_ttl() */ + + +unsigned short dns_opt_class(const struct dns_opt *opt) { + return opt->maxudp; +} /* dns_opt_class() */ + + +struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) { + assert(size >= offsetof(struct dns_opt, data)); + + opt->size = size - offsetof(struct dns_opt, data); + opt->len = 0; + + opt->rcode = 0; + opt->version = 0; + opt->maxudp = 0; + + return opt; +} /* dns_opt_init() */ + + +static union dns_any *dns_opt_initany(union dns_any *any, size_t size) { + return dns_opt_init(&any->opt, size), any; +} /* dns_opt_initany() */ + + +int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) { + const struct dns_buf src = DNS_B_FROM(&P->data[rr->rd.p], rr->rd.len); + struct dns_buf dst = DNS_B_INTO(opt->data, opt->size); + int error; + + opt->rcode = 0xfff & ((rr->ttl >> 20) | dns_header(P)->rcode); + opt->version = 0xff & (rr->ttl >> 16); + opt->flags = 0xffff & rr->ttl; + opt->maxudp = 0xffff & rr->class; + + while (src.p < src.pe) { + int code, len; + + if (-1 == (code = dns_b_get16(&src, -1))) + return src.error; + if (-1 == (len = dns_b_get16(&src, -1))) + return src.error; + + switch (code) { + default: + dns_b_put16(&dst, code); + dns_b_put16(&dst, len); + if ((error = dns_b_move(&dst, &src, len))) + return error; + break; + } + } + + return 0; +} /* dns_opt_parse() */ + + +int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) { + const struct dns_buf src = DNS_B_FROM(opt->data, opt->len); + struct dns_buf dst = DNS_B_INTO(&P->data[P->end], (P->size - P->end)); + int error; + + /* rdata length (see below) */ + if ((error = dns_b_put16(&dst, 0))) + goto error; + + /* ... push known options here */ + + /* push opaque option data */ + if ((error = dns_b_move(&dst, &src, (size_t)(src.pe - src.p)))) + goto error; + + /* rdata length */ + if ((error = dns_b_pput16(&dst, dns_b_tell(&dst) - 2, 0))) + goto error; + +#if !DNS_DEBUG_OPT_FORMERR + P->end += dns_b_tell(&dst); +#endif + + return 0; +error: + return error; +} /* dns_opt_push() */ + + +int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) { + (void)a; + (void)b; + + return -1; +} /* dns_opt_cmp() */ + + +size_t dns_opt_print(void *_dst, size_t lim, struct dns_opt *opt) { + struct dns_buf dst = DNS_B_INTO(_dst, lim); + size_t p; + + dns_b_putc(&dst, '"'); + + for (p = 0; p < opt->len; p++) { + dns_b_putc(&dst, '\\'); + dns_b_fmtju(&dst, opt->data[p], 3); + } + + dns_b_putc(&dst, '"'); + + return dns_b_strllen(&dst); +} /* dns_opt_print() */ + + +int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) { + return dns_ns_parse((struct dns_ns *)ptr, rr, P); +} /* dns_ptr_parse() */ + + +int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) { + return dns_ns_push(P, (struct dns_ns *)ptr); +} /* dns_ptr_push() */ + + +size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) { + switch (af) { + case AF_INET6: + return dns_aaaa_arpa(dst, lim, addr); + case AF_INET: + return dns_a_arpa(dst, lim, addr); + default: { + struct dns_a a; + a.addr.s_addr = INADDR_NONE; + return dns_a_arpa(dst, lim, &a); + } + } +} /* dns_ptr_qname() */ + + +int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) { + return strcasecmp(a->host, b->host); +} /* dns_ptr_cmp() */ + + +size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) { + return dns_ns_print(dst, lim, (struct dns_ns *)ptr); +} /* dns_ptr_print() */ + + +size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) { + return dns_strlcpy(dst, ptr->host, lim); +} /* dns_ptr_cname() */ + + +int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) { + unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len; + + if (pe - p < 2) + return DNS_EILLEGAL; + + fp->algo = P->data[p++]; + fp->type = P->data[p++]; + + switch (fp->type) { + case DNS_SSHFP_SHA1: + if (pe - p < sizeof fp->digest.sha1) + return DNS_EILLEGAL; + + memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1); + + break; + default: + break; + } /* switch() */ + + return 0; +} /* dns_sshfp_parse() */ + + +int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) { + unsigned p = P->end, pe = P->size, n; + + if (pe - p < 4) + return DNS_ENOBUFS; + + p += 2; + P->data[p++] = 0xff & fp->algo; + P->data[p++] = 0xff & fp->type; + + switch (fp->type) { + case DNS_SSHFP_SHA1: + if (pe - p < sizeof fp->digest.sha1) + return DNS_ENOBUFS; + + memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1); + p += sizeof fp->digest.sha1; + + break; + default: + return DNS_EILLEGAL; + } /* switch() */ + + n = p - P->end - 2; + P->data[P->end++] = 0xff & (n >> 8); + P->data[P->end++] = 0xff & (n >> 0); + P->end = p; + + return 0; +} /* dns_sshfp_push() */ + + +int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) { + int cmp; + + if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type)) + return cmp; + + switch (a->type) { + case DNS_SSHFP_SHA1: + return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1); + default: + return 0; + } /* switch() */ + + /* NOT REACHED */ +} /* dns_sshfp_cmp() */ + + +size_t dns_sshfp_print(void *_dst, size_t lim, struct dns_sshfp *fp) { + static const unsigned char hex[16] = "0123456789abcdef"; + struct dns_buf dst = DNS_B_INTO(_dst, lim); + size_t i; + + dns_b_fmtju(&dst, fp->algo, 0); + dns_b_putc(&dst, ' '); + dns_b_fmtju(&dst, fp->type, 0); + dns_b_putc(&dst, ' '); + + switch (fp->type) { + case DNS_SSHFP_SHA1: + for (i = 0; i < sizeof fp->digest.sha1; i++) { + dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 4)]); + dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 0)]); + } + + break; + default: + dns_b_putc(&dst, '0'); + + break; + } /* switch() */ + + return dns_b_strllen(&dst); +} /* dns_sshfp_print() */ + + +struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) { + assert(size > offsetof(struct dns_txt, data)); + + txt->size = size - offsetof(struct dns_txt, data); + txt->len = 0; + + return txt; +} /* dns_txt_init() */ + + +static union dns_any *dns_txt_initany(union dns_any *any, size_t size) { + /* NB: union dns_any is already initialized as struct dns_txt */ + (void)size; + return any; +} /* dns_txt_initany() */ + + +int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) { + struct { unsigned char *b; size_t p, end; } dst, src; + unsigned n; + + dst.b = txt->data; + dst.p = 0; + dst.end = txt->size; + + src.b = P->data; + src.p = rr->rd.p; + src.end = src.p + rr->rd.len; + + while (src.p < src.end) { + n = 0xff & P->data[src.p++]; + + if (src.end - src.p < n || dst.end - dst.p < n) + return DNS_EILLEGAL; + + memcpy(&dst.b[dst.p], &src.b[src.p], n); + + dst.p += n; + src.p += n; + } + + txt->len = dst.p; + + return 0; +} /* dns_txt_parse() */ + + +int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) { + struct { unsigned char *b; size_t p, end; } dst, src; + unsigned n; + + dst.b = P->data; + dst.p = P->end; + dst.end = P->size; + + src.b = txt->data; + src.p = 0; + src.end = txt->len; + + if (dst.end - dst.p < 2) + return DNS_ENOBUFS; + + n = txt->len + ((txt->len + 254) / 255); + + dst.b[dst.p++] = 0xff & (n >> 8); + dst.b[dst.p++] = 0xff & (n >> 0); + + while (src.p < src.end) { + n = DNS_PP_MIN(255, src.end - src.p); + + if (dst.p >= dst.end) + return DNS_ENOBUFS; + + dst.b[dst.p++] = n; + + if (dst.end - dst.p < n) + return DNS_ENOBUFS; + + memcpy(&dst.b[dst.p], &src.b[src.p], n); + + dst.p += n; + src.p += n; + } + + P->end = dst.p; + + return 0; +} /* dns_txt_push() */ + + +int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) { + (void)a; + (void)b; + + return -1; +} /* dns_txt_cmp() */ + + +size_t dns_txt_print(void *_dst, size_t lim, struct dns_txt *txt) { + struct dns_buf src = DNS_B_FROM(txt->data, txt->len); + struct dns_buf dst = DNS_B_INTO(_dst, lim); + unsigned i; + + if (src.p < src.pe) { + do { + dns_b_putc(&dst, '"'); + + for (i = 0; i < 256 && src.p < src.pe; i++, src.p++) { + if (*src.p < 32 || *src.p > 126 || *src.p == '"' || *src.p == '\\') { + dns_b_putc(&dst, '\\'); + dns_b_fmtju(&dst, *src.p, 3); + } else { + dns_b_putc(&dst, *src.p); + } + } + + dns_b_putc(&dst, '"'); + dns_b_putc(&dst, ' '); + } while (src.p < src.pe); + + dns_b_popc(&dst); + } else { + dns_b_putc(&dst, '"'); + dns_b_putc(&dst, '"'); + } + + return dns_b_strllen(&dst); +} /* dns_txt_print() */ + + +/* Some of the function pointers of DNS_RRTYPES are initialized with + * slighlly different functions, thus we can't use prototypes. */ +DNS_PRAGMA_PUSH +#if __clang__ +#pragma clang diagnostic ignored "-Wstrict-prototypes" +#elif DNS_GNUC_PREREQ(4,6,0) +#pragma GCC diagnostic ignored "-Wstrict-prototypes" +#endif + +static const struct dns_rrtype { + enum dns_type type; + const char *name; + union dns_any *(*init)(union dns_any *, size_t); + int (*parse)(); + int (*push)(); + int (*cmp)(); + size_t (*print)(); + size_t (*cname)(); +} dns_rrtypes[] = { + { DNS_T_A, "A", 0, &dns_a_parse, &dns_a_push, &dns_a_cmp, &dns_a_print, 0, }, + { DNS_T_AAAA, "AAAA", 0, &dns_aaaa_parse, &dns_aaaa_push, &dns_aaaa_cmp, &dns_aaaa_print, 0, }, + { DNS_T_MX, "MX", 0, &dns_mx_parse, &dns_mx_push, &dns_mx_cmp, &dns_mx_print, &dns_mx_cname, }, + { DNS_T_NS, "NS", 0, &dns_ns_parse, &dns_ns_push, &dns_ns_cmp, &dns_ns_print, &dns_ns_cname, }, + { DNS_T_CNAME, "CNAME", 0, &dns_cname_parse, &dns_cname_push, &dns_cname_cmp, &dns_cname_print, &dns_cname_cname, }, + { DNS_T_SOA, "SOA", 0, &dns_soa_parse, &dns_soa_push, &dns_soa_cmp, &dns_soa_print, 0, }, + { DNS_T_SRV, "SRV", 0, &dns_srv_parse, &dns_srv_push, &dns_srv_cmp, &dns_srv_print, &dns_srv_cname, }, + { DNS_T_OPT, "OPT", &dns_opt_initany, &dns_opt_parse, &dns_opt_push, &dns_opt_cmp, &dns_opt_print, 0, }, + { DNS_T_PTR, "PTR", 0, &dns_ptr_parse, &dns_ptr_push, &dns_ptr_cmp, &dns_ptr_print, &dns_ptr_cname, }, + { DNS_T_TXT, "TXT", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, }, + { DNS_T_SPF, "SPF", &dns_txt_initany, &dns_txt_parse, &dns_txt_push, &dns_txt_cmp, &dns_txt_print, 0, }, + { DNS_T_SSHFP, "SSHFP", 0, &dns_sshfp_parse, &dns_sshfp_push, &dns_sshfp_cmp, &dns_sshfp_print, 0, }, + { DNS_T_AXFR, "AXFR", 0, 0, 0, 0, 0, 0, }, +}; /* dns_rrtypes[] */ + +DNS_PRAGMA_POP /*(-Wstrict-prototypes)*/ + + + +static const struct dns_rrtype *dns_rrtype(enum dns_type type) { + const struct dns_rrtype *t; + + for (t = dns_rrtypes; t < endof(dns_rrtypes); t++) { + if (t->type == type && t->parse) { + return t; + } + } + + return NULL; +} /* dns_rrtype() */ + + +union dns_any *dns_any_init(union dns_any *any, size_t size) { + dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type"); + return (union dns_any *)dns_txt_init(&any->rdata, size); +} /* dns_any_init() */ + + +static size_t dns_any_sizeof(union dns_any *any) { + dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type"); + return offsetof(struct dns_txt, data) + any->rdata.size; +} /* dns_any_sizeof() */ + +static union dns_any *dns_any_reinit(union dns_any *any, const struct dns_rrtype *t) { + return (t->init)? t->init(any, dns_any_sizeof(any)) : any; +} /* dns_any_reinit() */ + +int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) { + const struct dns_rrtype *t; + + if ((t = dns_rrtype(rr->type))) + return t->parse(dns_any_reinit(any, t), rr, P); + + if (rr->rd.len > any->rdata.size) + return DNS_EILLEGAL; + + memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len); + any->rdata.len = rr->rd.len; + + return 0; +} /* dns_any_parse() */ + + +int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) { + const struct dns_rrtype *t; + + if ((t = dns_rrtype(type))) + return t->push(P, any); + + if (P->size - P->end < any->rdata.len + 2) + return DNS_ENOBUFS; + + P->data[P->end++] = 0xff & (any->rdata.len >> 8); + P->data[P->end++] = 0xff & (any->rdata.len >> 0); + + memcpy(&P->data[P->end], any->rdata.data, any->rdata.len); + P->end += any->rdata.len; + + return 0; +} /* dns_any_push() */ + + +int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) { + const struct dns_rrtype *t; + int cmp; + + if ((cmp = x - y)) + return cmp; + + if ((t = dns_rrtype(x))) + return t->cmp(a, b); + + return -1; +} /* dns_any_cmp() */ + + +size_t dns_any_print(void *_dst, size_t lim, union dns_any *any, enum dns_type type) { + const struct dns_rrtype *t; + struct dns_buf src, dst; + + if ((t = dns_rrtype(type))) + return t->print(_dst, lim, any); + + dns_b_from(&src, any->rdata.data, any->rdata.len); + dns_b_into(&dst, _dst, lim); + + dns_b_putc(&dst, '"'); + + while (src.p < src.pe) { + dns_b_putc(&dst, '\\'); + dns_b_fmtju(&dst, *src.p++, 3); + } + + dns_b_putc(&dst, '"'); + + return dns_b_strllen(&dst); +} /* dns_any_print() */ + + +size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) { + const struct dns_rrtype *t; + + if ((t = dns_rrtype(type)) && t->cname) + return t->cname(dst, lim, any); + + return 0; +} /* dns_any_cname() */ + + +/* + * E V E N T T R A C I N G R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#include <float.h> /* DBL_MANT_DIG */ +#include <inttypes.h> /* PRIu64 */ + +/* for default trace ID generation try to fit in lua_Number, usually double */ +#define DNS_TRACE_ID_BITS DNS_PP_MIN(DBL_MANT_DIG, (sizeof (dns_trace_id_t) * CHAR_BIT)) /* assuming FLT_RADIX == 2 */ +#define DNS_TRACE_ID_MASK (((DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)) - 1) | (DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1))) +#define DNS_TRACE_ID_PRI PRIu64 + +static inline dns_trace_id_t dns_trace_mkid(void) { + dns_trace_id_t id = 0; + unsigned r; /* return type of dns_random() */ + const size_t id_bit = sizeof id * CHAR_BIT; + const size_t r_bit = sizeof r * CHAR_BIT; + + for (size_t n = 0; n < id_bit; n += r_bit) { + r = dns_random(); + id <<= r_bit; + id |= r; + } + + return DNS_TRACE_ID_MASK & id; +} + +struct dns_trace { + dns_atomic_t refcount; + + FILE *fp; + dns_trace_id_t id; + + struct { + struct dns_trace_cname { + char host[DNS_D_MAXNAME + 1]; + struct sockaddr_storage addr; + } base[4]; + size_t p; + } cnames; +}; + +static void dns_te_initname(struct sockaddr_storage *ss, int fd, int (* STDCALL f)(socket_fd_t, struct sockaddr *, socklen_t *)) { + socklen_t n = sizeof *ss; + + if (0 != f(fd, (struct sockaddr *)ss, &n)) + goto unspec; + + if (n > sizeof *ss) + goto unspec; + + return; +unspec: + memset(ss, '\0', sizeof *ss); + ss->ss_family = AF_UNSPEC; +} + +static void dns_te_initnames(struct sockaddr_storage *local, struct sockaddr_storage *remote, int fd) { + dns_te_initname(local, fd, &getsockname); + dns_te_initname(remote, fd, &getpeername); +} + +static struct dns_trace_event *dns_te_init(struct dns_trace_event *te, int type) { + /* NB: silence valgrind */ + memset(te, '\0', offsetof(struct dns_trace_event, data)); + te->type = type; + return te; +} + +int dns_trace_abi(void) { + return DNS_TRACE_ABI; +} + +struct dns_trace *dns_trace_open(FILE *fp, dns_error_t *error) { + static const struct dns_trace trace_initializer = { .refcount = 1 }; + struct dns_trace *trace; + + if (!(trace = malloc(sizeof *trace))) + goto syerr; + + *trace = trace_initializer; + + if (fp) { + trace->fp = fp; + } else if (!(fp = tmpfile())) { + goto syerr; + } + + trace->id = dns_trace_mkid(); + + return trace; +syerr: + *error = dns_syerr(); + + dns_trace_close(trace); + + return NULL; +} /* dns_trace_open() */ + +void dns_trace_close(struct dns_trace *trace) { + if (!trace || 1 != dns_trace_release(trace)) + return; + + if (trace->fp) + fclose(trace->fp); + free(trace); +} /* dns_trace_close() */ + +dns_refcount_t dns_trace_acquire(struct dns_trace *trace) { + return dns_atomic_fetch_add(&trace->refcount); +} /* dns_trace_acquire() */ + +static struct dns_trace *dns_trace_acquire_p(struct dns_trace *trace) { + return (trace)? dns_trace_acquire(trace), trace : NULL; +} /* dns_trace_acquire_p() */ + +dns_refcount_t dns_trace_release(struct dns_trace *trace) { + return dns_atomic_fetch_sub(&trace->refcount); +} /* dns_trace_release() */ + +dns_trace_id_t dns_trace_id(struct dns_trace *trace) { + return trace->id; +} /* dns_trace_id() */ + +dns_trace_id_t dns_trace_setid(struct dns_trace *trace, dns_trace_id_t id) { + trace->id = (id)? id : dns_trace_mkid(); + return trace->id; +} /* dns_trace_setid() */ + +struct dns_trace_event *dns_trace_get(struct dns_trace *trace, struct dns_trace_event **tp, dns_error_t *error) { + return dns_trace_fget(tp, trace->fp, error); +} /* dns_trace_get() */ + +dns_error_t dns_trace_put(struct dns_trace *trace, const struct dns_trace_event *te, const void *data, size_t datasize) { + return dns_trace_fput(te, data, datasize, trace->fp); +} /* dns_trace_put() */ + +struct dns_trace_event *dns_trace_tag(struct dns_trace *trace, struct dns_trace_event *te) { + struct timeval tv; + + te->id = trace->id; + gettimeofday(&tv, NULL); + dns_tv2ts(&te->ts, &tv); + te->abi = DNS_TRACE_ABI; + + return te; +} /* dns_trace_tag() */ + +static dns_error_t dns_trace_tag_and_put(struct dns_trace *trace, struct dns_trace_event *te, const void *data, size_t datasize) { + return dns_trace_put(trace, dns_trace_tag(trace, te), data, datasize); +} /* dns_trace_tag_and_put() */ + +struct dns_trace_event *dns_trace_fget(struct dns_trace_event **tp, FILE *fp, dns_error_t *error) { + const size_t headsize = offsetof(struct dns_trace_event, data); + struct dns_trace_event tmp, *te; + size_t n; + + errno = 0; + if (!(n = fread(&tmp, 1, headsize, fp))) + goto none; + if (n < offsetof(struct dns_trace_event, data)) + goto some; + + if (!(te = realloc(*tp, DNS_PP_MAX(headsize, tmp.size)))) { + *error = errno; + return NULL; + } + + *tp = te; + memcpy(te, &tmp, offsetof(struct dns_trace_event, data)); + + if (dns_te_datasize(te)) { + errno = 0; + if (!(n = fread(te->data, 1, dns_te_datasize(te), fp))) + goto none; + if (n < dns_te_datasize(te)) + goto some; + } + + return te; +none: + *error = (ferror(fp))? errno : 0; + return NULL; +some: + *error = 0; + return NULL; +} + +dns_error_t dns_trace_fput(const struct dns_trace_event *te, const void *data, size_t datasize, FILE *fp) { + size_t headsize = offsetof(struct dns_trace_event, data); + struct dns_trace_event tmp; + + memcpy(&tmp, te, headsize); + tmp.size = headsize + datasize; + + /* NB: ignore seek error as fp might not point to a regular file */ + (void)fseek(fp, 0, SEEK_END); + + if (fwrite(&tmp, 1, headsize, fp) < headsize) + return errno; + if (data) + if (fwrite(data, 1, datasize, fp) < datasize) + return errno; + if (fflush(fp)) + return errno; + + return 0; +} + +static void dns_trace_setcname(struct dns_trace *trace, const char *host, const struct sockaddr *addr) { + struct dns_trace_cname *cname; + if (!trace || !trace->fp) + return; + + cname = &trace->cnames.base[trace->cnames.p]; + dns_strlcpy(cname->host, host, sizeof cname->host); + memcpy(&cname->addr, addr, DNS_PP_MIN(dns_sa_len(addr), sizeof cname->addr)); + + trace->cnames.p = (trace->cnames.p + 1) % lengthof(trace->cnames.base); +} + +static const char *dns_trace_cname(struct dns_trace *trace, const struct sockaddr *addr) { + if (!trace || !trace->fp) + return NULL; + + /* NB: start search from the write cursor to */ + for (const struct dns_trace_cname *cname = trace->cnames.base; cname < endof(trace->cnames.base); cname++) { + if (0 == dns_sa_cmp((struct sockaddr *)addr, (struct sockaddr *)&cname->addr)) + return cname->host; + } + + return NULL; +} + +static void dns_trace_res_submit(struct dns_trace *trace, const char *qname, enum dns_type qtype, enum dns_class qclass, int error) { + struct dns_trace_event te; + if (!trace || !trace->fp) + return; + + dns_te_init(&te, DNS_TE_RES_SUBMIT); + dns_strlcpy(te.res_submit.qname, qname, sizeof te.res_submit.qname); + te.res_submit.qtype = qtype; + te.res_submit.qclass = qclass; + te.res_submit.error = error; + dns_trace_tag_and_put(trace, &te, NULL, 0); +} + +static void dns_trace_res_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) { + struct dns_trace_event te; + const void *data; + size_t datasize; + if (!trace || !trace->fp) + return; + + dns_te_init(&te, DNS_TE_RES_FETCH); + data = (packet)? packet->data : NULL; + datasize = (packet)? packet->end : 0; + te.res_fetch.error = error; + dns_trace_tag_and_put(trace, &te, data, datasize); +} + +static void dns_trace_so_submit(struct dns_trace *trace, const struct dns_packet *packet, const struct sockaddr *haddr, int error) { + struct dns_trace_event te; + const char *cname; + if (!trace || !trace->fp) + return; + + dns_te_init(&te, DNS_TE_SO_SUBMIT); + memcpy(&te.so_submit.haddr, haddr, DNS_PP_MIN(dns_sa_len(haddr), sizeof te.so_submit.haddr)); + if ((cname = dns_trace_cname(trace, haddr))) + dns_strlcpy(te.so_submit.hname, cname, sizeof te.so_submit.hname); + te.so_submit.error = error; + dns_trace_tag_and_put(trace, &te, packet->data, packet->end); +} + +static void dns_trace_so_verify(struct dns_trace *trace, const struct dns_packet *packet, int error) { + struct dns_trace_event te; + if (!trace || !trace->fp) + return; + + dns_te_init(&te, DNS_TE_SO_VERIFY); + te.so_verify.error = error; + dns_trace_tag_and_put(trace, &te, packet->data, packet->end); +} + +static void dns_trace_so_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) { + struct dns_trace_event te; + const void *data; + size_t datasize; + if (!trace || !trace->fp) + return; + + dns_te_init(&te, DNS_TE_SO_FETCH); + data = (packet)? packet->data : NULL; + datasize = (packet)? packet->end : 0; + te.so_fetch.error = error; + dns_trace_tag_and_put(trace, &te, data, datasize); +} + +static void dns_trace_sys_connect(struct dns_trace *trace, int fd, int socktype, const struct sockaddr *dst, int error) { + struct dns_trace_event te; + if (!trace || !trace->fp) + return; + + dns_te_init(&te, DNS_TE_SYS_CONNECT); + dns_te_initname(&te.sys_connect.src, fd, &getsockname); + memcpy(&te.sys_connect.dst, dst, DNS_PP_MIN(dns_sa_len(dst), sizeof te.sys_connect.dst)); + te.sys_connect.socktype = socktype; + te.sys_connect.error = error; + dns_trace_tag_and_put(trace, &te, NULL, 0); +} + +static void dns_trace_sys_send(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) { + struct dns_trace_event te; + if (!trace || !trace->fp) + return; + + dns_te_init(&te, DNS_TE_SYS_SEND); + dns_te_initnames(&te.sys_send.src, &te.sys_send.dst, fd); + te.sys_send.socktype = socktype; + te.sys_send.error = error; + dns_trace_tag_and_put(trace, &te, data, datasize); +} + +static void dns_trace_sys_recv(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) { + struct dns_trace_event te; + if (!trace || !trace->fp) + return; + + dns_te_init(&te, DNS_TE_SYS_RECV); + dns_te_initnames(&te.sys_recv.dst, &te.sys_recv.src, fd); + te.sys_recv.socktype = socktype; + te.sys_recv.error = error; + dns_trace_tag_and_put(trace, &te, data, datasize); +} + +static dns_error_t dns_trace_dump_packet(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) { + struct dns_packet *packet = NULL; + char *line = NULL, *p; + size_t size = 1, skip = 0; + struct dns_rr_i records; + struct dns_p_lines_i lines; + size_t len, count; + int error; + + if (!(packet = dns_p_make(datasize, &error))) + goto error; + + memcpy(packet->data, data, datasize); + packet->end = datasize; + (void)dns_p_study(packet); +resize: + if (!(p = dns_reallocarray(line, size, 2, &error))) + goto error; + line = p; + size *= 2; + + memset(&records, 0, sizeof records); + memset(&lines, 0, sizeof lines); + count = 0; + + while ((len = dns_p_lines(line, size, &error, packet, &records, &lines))) { + if (!(len < size)) { + skip = count; + goto resize; + } else if (skip <= count) { + fputs(prefix, fp); + fwrite(line, 1, len, fp); + } + count++; + } + + if (error) + goto error; + + error = 0; +error: + free(line); + dns_p_free(packet); + + return error; +} + +static dns_error_t dns_trace_dump_data(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) { + struct dns_hxd_lines_i lines = { 0 }; + char line[128]; + size_t len; + + while ((len = dns_hxd_lines(line, sizeof line, data, datasize, &lines))) { + if (len >= sizeof line) + return EOVERFLOW; /* shouldn't be possible */ + fputs(prefix, fp); + fwrite(line, 1, len, fp); + } + + return 0; +} + +static dns_error_t dns_trace_dump_addr(struct dns_trace *trace, const char *prefix, const struct sockaddr_storage *ss, FILE *fp) { + const void *addr; + const char *path; + socklen_t len; + int error; + + if ((addr = dns_sa_addr(ss->ss_family, (struct sockaddr *)ss, NULL))) { + char ip[INET6_ADDRSTRLEN + 1]; + + if ((error = dns_ntop(ss->ss_family, addr, ip, sizeof ip))) + return error; + fprintf(fp, "%s%s\n", prefix, ip); + } else if ((path = dns_sa_path((struct sockaddr *)ss, &len))) { + fprintf(fp, "%sunix:%.*s", prefix, (int)len, path); + } else { + return EINVAL; + } + + return 0; +} + +static dns_error_t dns_trace_dump_meta(struct dns_trace *trace, const char *prefix, const struct dns_trace_event *te, dns_microseconds_t elapsed, FILE *fp) { + char time_s[48], elapsed_s[48]; + + dns_utime_print(time_s, sizeof time_s, dns_ts2us(&te->ts, 0)); + dns_utime_print(elapsed_s, sizeof elapsed_s, elapsed); + + fprintf(fp, "%sid: %"DNS_TRACE_ID_PRI"\n", prefix, te->id); + fprintf(fp, "%sts: %s (%s)\n", prefix, time_s, elapsed_s); + fprintf(fp, "%sabi: 0x%x (0x%x)\n", prefix, te->abi, DNS_TRACE_ABI); + return 0; +} + +static dns_error_t dns_trace_dump_error(struct dns_trace *trace, const char *prefix, int error, FILE *fp) { + fprintf(fp, "%s%d (%s)\n", prefix, error, (error)? dns_strerror(error) : "none"); + return 0; +} + +dns_error_t dns_trace_dump(struct dns_trace *trace, FILE *fp) { + struct dns_trace_event *te = NULL; + struct { + dns_trace_id_t id; + dns_microseconds_t begin, elapsed; + } state = { 0 }; + int error; + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + + if (!trace || !trace->fp) + return EINVAL; + + if (0 != fseek(trace->fp, 0, SEEK_SET)) + goto syerr; + + while (dns_trace_fget(&te, trace->fp, &error)) { + size_t datasize = dns_te_datasize(te); + const unsigned char *data = (datasize)? te->data : NULL; + + if (state.id != te->id) { + state.id = te->id; + state.begin = dns_ts2us(&te->ts, 0); + } + dns_time_diff(&state.elapsed, dns_ts2us(&te->ts, 0), state.begin); + + switch(te->type) { + case DNS_TE_RES_SUBMIT: + fprintf(fp, "dns_res_submit:\n"); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + fprintf(fp, " qname: %s\n", te->res_submit.qname); + fprintf(fp, " qtype: %s\n", dns_strtype(te->res_submit.qtype, __dst)); + fprintf(fp, " qclass: %s\n", dns_strclass(te->res_submit.qclass, __dst)); + dns_trace_dump_error(trace, " error: ", te->res_submit.error, fp); + break; + case DNS_TE_RES_FETCH: + fprintf(fp, "dns_res_fetch:\n"); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + dns_trace_dump_error(trace, " error: ", te->res_fetch.error, fp); + + if (data) { + fprintf(fp, " packet: |\n"); + if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) + goto error; + fprintf(fp, " data: |\n"); + if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) + goto error; + } + + break; + case DNS_TE_SO_SUBMIT: + fprintf(fp, "dns_so_submit:\n"); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + fprintf(fp, " hname: %s\n", te->so_submit.hname); + dns_trace_dump_addr(trace, " haddr: ", &te->so_submit.haddr, fp); + dns_trace_dump_error(trace, " error: ", te->so_submit.error, fp); + + if (data) { + fprintf(fp, " packet: |\n"); + if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) + goto error; + fprintf(fp, " data: |\n"); + if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) + goto error; + } + + break; + case DNS_TE_SO_VERIFY: + fprintf(fp, "dns_so_verify:\n"); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + dns_trace_dump_error(trace, " error: ", te->so_verify.error, fp); + + if (data) { + fprintf(fp, " packet: |\n"); + if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) + goto error; + fprintf(fp, " data: |\n"); + if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) + goto error; + } + + break; + case DNS_TE_SO_FETCH: + fprintf(fp, "dns_so_fetch:\n"); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + dns_trace_dump_error(trace, " error: ", te->so_fetch.error, fp); + + if (data) { + fprintf(fp, " packet: |\n"); + if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp))) + goto error; + fprintf(fp, " data: |\n"); + if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) + goto error; + } + + break; + case DNS_TE_SYS_CONNECT: { + int socktype = te->sys_connect.socktype; + fprintf(fp, "dns_sys_connect:\n"); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + dns_trace_dump_addr(trace, " src: ", &te->sys_connect.src, fp); + dns_trace_dump_addr(trace, " dst: ", &te->sys_connect.dst, fp); + fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); + dns_trace_dump_error(trace, " error: ", te->sys_connect.error, fp); + + break; + } + case DNS_TE_SYS_SEND: { + int socktype = te->sys_send.socktype; + fprintf(fp, "dns_sys_send:\n"); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + dns_trace_dump_addr(trace, " src: ", &te->sys_send.src, fp); + dns_trace_dump_addr(trace, " dst: ", &te->sys_send.dst, fp); + fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); + dns_trace_dump_error(trace, " error: ", te->sys_send.error, fp); + + if (data) { + fprintf(fp, " data: |\n"); + if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) + goto error; + } + + break; + } + case DNS_TE_SYS_RECV: { + int socktype = te->sys_recv.socktype; + fprintf(fp, "dns_sys_recv:\n"); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + dns_trace_dump_addr(trace, " src: ", &te->sys_recv.src, fp); + dns_trace_dump_addr(trace, " dst: ", &te->sys_recv.dst, fp); + fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?")); + dns_trace_dump_error(trace, " error: ", te->sys_recv.error, fp); + + if (data) { + fprintf(fp, " data: |\n"); + if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) + goto error; + } + + break; + } + default: + fprintf(fp, "unknown(0x%.2x):\n", te->type); + dns_trace_dump_meta(trace, " ", te, state.elapsed, fp); + + if (data) { + fprintf(fp, " data: |\n"); + if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp))) + goto error; + } + + break; + } + } + + goto epilog; +syerr: + error = errno; +error: + (void)0; +epilog: + free(te); + + return error; +} + +/* + * H O S T S R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_hosts { + struct dns_hosts_entry { + char host[DNS_D_MAXNAME + 1]; + char arpa[73 + 1]; + + int af; + + union { + struct in_addr a4; + struct in6_addr a6; + } addr; + + _Bool alias; + + struct dns_hosts_entry *next; + } *head, **tail; + + dns_atomic_t refcount; +}; /* struct dns_hosts */ + + +struct dns_hosts *dns_hosts_open(int *error) { + static const struct dns_hosts hosts_initializer = { .refcount = 1 }; + struct dns_hosts *hosts; + + if (!(hosts = malloc(sizeof *hosts))) + goto syerr; + + *hosts = hosts_initializer; + + hosts->tail = &hosts->head; + + return hosts; +syerr: + *error = dns_syerr(); + + free(hosts); + + return 0; +} /* dns_hosts_open() */ + + +void dns_hosts_close(struct dns_hosts *hosts) { + struct dns_hosts_entry *ent, *xnt; + + if (!hosts || 1 != dns_hosts_release(hosts)) + return; + + for (ent = hosts->head; ent; ent = xnt) { + xnt = ent->next; + + free(ent); + } + + free(hosts); + + return; +} /* dns_hosts_close() */ + + +dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) { + return dns_atomic_fetch_add(&hosts->refcount); +} /* dns_hosts_acquire() */ + + +dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) { + return dns_atomic_fetch_sub(&hosts->refcount); +} /* dns_hosts_release() */ + + +struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) { + if (hosts) + dns_hosts_release(hosts); + + return hosts; +} /* dns_hosts_mortal() */ + + +struct dns_hosts *dns_hosts_local(int *error_) { + struct dns_hosts *hosts; + int error; + + if (!(hosts = dns_hosts_open(&error))) + goto error; + + if ((error = dns_hosts_loadpath(hosts, "/etc/hosts"))) + goto error; + + return hosts; +error: + *error_ = error; + + dns_hosts_close(hosts); + + return 0; +} /* dns_hosts_local() */ + + +#define dns_hosts_issep(ch) (dns_isspace(ch)) +#define dns_hosts_iscom(ch) ((ch) == '#' || (ch) == ';') + +int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) { + struct dns_hosts_entry ent; + char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1]; + unsigned wp, wc, skip; + int ch, error; + + rewind(fp); + + do { + memset(&ent, '\0', sizeof ent); + wc = 0; + skip = 0; + + do { + memset(word, '\0', sizeof word); + wp = 0; + + while (EOF != (ch = fgetc(fp)) && ch != '\n') { + skip |= !!dns_hosts_iscom(ch); + + if (skip) + continue; + + if (dns_hosts_issep(ch)) + break; + + if (wp < sizeof word - 1) + word[wp] = ch; + wp++; + } + + if (!wp) + continue; + + wc++; + + switch (wc) { + case 0: + break; + case 1: + ent.af = (strchr(word, ':'))? AF_INET6 : AF_INET; + skip = (1 != dns_inet_pton(ent.af, word, &ent.addr)); + + break; + default: + if (!wp) + break; + + dns_d_anchor(ent.host, sizeof ent.host, word, wp); + + if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2)))) + return error; + + break; + } /* switch() */ + } while (ch != EOF && ch != '\n'); + } while (ch != EOF); + + return 0; +} /* dns_hosts_loadfile() */ + + +int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) { + FILE *fp; + int error; + + if (!(fp = dns_fopen(path, "rt", &error))) + return error; + + error = dns_hosts_loadfile(hosts, fp); + + fclose(fp); + + return error; +} /* dns_hosts_loadpath() */ + + +int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) { + struct dns_hosts_entry *ent, *xnt; + char addr[INET6_ADDRSTRLEN + 1]; + unsigned i; + + for (ent = hosts->head; ent; ent = xnt) { + xnt = ent->next; + + dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr); + + fputs(addr, fp); + + for (i = strlen(addr); i < INET_ADDRSTRLEN; i++) + fputc(' ', fp); + + fputc(' ', fp); + + fputs(ent->host, fp); + fputc('\n', fp); + } + + return 0; +} /* dns_hosts_dump() */ + + +int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) { + struct dns_hosts_entry *ent; + int error; + + if (!(ent = malloc(sizeof *ent))) + goto syerr; + + dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host)); + + switch ((ent->af = af)) { + case AF_INET6: + memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6); + + dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr); + + break; + case AF_INET: + memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4); + + dns_a_arpa(ent->arpa, sizeof ent->arpa, addr); + + break; + default: + error = EINVAL; + + goto error; + } /* switch() */ + + ent->alias = alias; + + ent->next = 0; + *hosts->tail = ent; + hosts->tail = &ent->next; + + return 0; +syerr: + error = dns_syerr(); +error: + free(ent); + + return error; +} /* dns_hosts_insert() */ + + +struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; + struct dns_packet *P = dns_p_init(&P_instance.p, 512); + struct dns_packet *A = 0; + struct dns_rr rr; + struct dns_hosts_entry *ent; + int error, af; + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + + if ((error = dns_rr_parse(&rr, 12, Q))) + goto error; + + if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error))) + goto error; + else if (qlen >= sizeof qname) + goto toolong; + + if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0))) + goto error; + + switch (rr.type) { + case DNS_T_PTR: + for (ent = hosts->head; ent; ent = ent->next) { + if (ent->alias || 0 != strcasecmp(qname, ent->arpa)) + continue; + + if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host))) + goto error; + } + + break; + case DNS_T_AAAA: + af = AF_INET6; + + goto loop; + case DNS_T_A: + af = AF_INET; + +loop: for (ent = hosts->head; ent; ent = ent->next) { + if (ent->af != af || 0 != strcasecmp(qname, ent->host)) + continue; + + if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr))) + goto error; + } + + break; + default: + break; + } /* switch() */ + + + if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) + goto error; + + return A; +toolong: + error = DNS_EILLEGAL; +error: + *error_ = error; + + dns_p_free(A); + + return 0; +} /* dns_hosts_query() */ + + +/* + * R E S O L V . C O N F R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_resolv_conf *dns_resconf_open(int *error) { + static const struct dns_resolv_conf resconf_initializer = { + .lookup = "bf", + .family = { AF_INET, AF_INET6 }, + .options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, }, + .iface = { .ss_family = AF_INET }, + }; + struct dns_resolv_conf *resconf; + struct sockaddr_in *sin; + + if (!(resconf = malloc(sizeof *resconf))) + goto syerr; + + *resconf = resconf_initializer; + + sin = (struct sockaddr_in *)&resconf->nameserver[0]; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = INADDR_ANY; + sin->sin_port = htons(53); +#if defined(SA_LEN) + sin->sin_len = sizeof *sin; +#endif + + if (0 != gethostname(resconf->search[0], sizeof resconf->search[0])) + goto syerr; + + /* + * If gethostname() returned a string without any label + * separator, then search[0][0] should be NUL. + */ + if (strchr (resconf->search[0], '.')) { + dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); + dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0])); + } else { + memset (resconf->search[0], 0, sizeof resconf->search[0]); + } + + dns_resconf_acquire(resconf); + + return resconf; +syerr: + *error = dns_syerr(); + + free(resconf); + + return 0; +} /* dns_resconf_open() */ + + +void dns_resconf_close(struct dns_resolv_conf *resconf) { + if (!resconf || 1 != dns_resconf_release(resconf)) + return /* void */; + + free(resconf); +} /* dns_resconf_close() */ + + +dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) { + return dns_atomic_fetch_add(&resconf->_.refcount); +} /* dns_resconf_acquire() */ + + +dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) { + return dns_atomic_fetch_sub(&resconf->_.refcount); +} /* dns_resconf_release() */ + + +struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) { + if (resconf) + dns_resconf_release(resconf); + + return resconf; +} /* dns_resconf_mortal() */ + + +struct dns_resolv_conf *dns_resconf_local(int *error_) { + struct dns_resolv_conf *resconf; + int error; + + if (!(resconf = dns_resconf_open(&error))) + goto error; + + if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) { + /* + * NOTE: Both the glibc and BIND9 resolvers ignore a missing + * /etc/resolv.conf, defaulting to a nameserver of + * 127.0.0.1. See also dns_hints_insert_resconf, and the + * default initialization of nameserver[0] in + * dns_resconf_open. + */ + if (error != ENOENT) + goto error; + } + + if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) { + if (error != ENOENT) + goto error; + } + + return resconf; +error: + *error_ = error; + + dns_resconf_close(resconf); + + return 0; +} /* dns_resconf_local() */ + + +struct dns_resolv_conf *dns_resconf_root(int *error) { + struct dns_resolv_conf *resconf; + + if ((resconf = dns_resconf_local(error))) + resconf->options.recurse = 1; + + return resconf; +} /* dns_resconf_root() */ + + +static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) { + return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout); +} /* dns_resconf_timeout() */ + + +enum dns_resconf_keyword { + DNS_RESCONF_NAMESERVER, + DNS_RESCONF_DOMAIN, + DNS_RESCONF_SEARCH, + DNS_RESCONF_LOOKUP, + DNS_RESCONF_FILE, + DNS_RESCONF_BIND, + DNS_RESCONF_CACHE, + DNS_RESCONF_FAMILY, + DNS_RESCONF_INET4, + DNS_RESCONF_INET6, + DNS_RESCONF_OPTIONS, + DNS_RESCONF_EDNS0, + DNS_RESCONF_NDOTS, + DNS_RESCONF_TIMEOUT, + DNS_RESCONF_ATTEMPTS, + DNS_RESCONF_ROTATE, + DNS_RESCONF_RECURSE, + DNS_RESCONF_SMART, + DNS_RESCONF_TCP, + DNS_RESCONF_TCPx, + DNS_RESCONF_INTERFACE, + DNS_RESCONF_ZERO, + DNS_RESCONF_ONE, + DNS_RESCONF_ENABLE, + DNS_RESCONF_ONLY, + DNS_RESCONF_DISABLE, +}; /* enum dns_resconf_keyword */ + +static enum dns_resconf_keyword dns_resconf_keyword(const char *word) { + static const char *words[] = { + [DNS_RESCONF_NAMESERVER] = "nameserver", + [DNS_RESCONF_DOMAIN] = "domain", + [DNS_RESCONF_SEARCH] = "search", + [DNS_RESCONF_LOOKUP] = "lookup", + [DNS_RESCONF_FILE] = "file", + [DNS_RESCONF_BIND] = "bind", + [DNS_RESCONF_CACHE] = "cache", + [DNS_RESCONF_FAMILY] = "family", + [DNS_RESCONF_INET4] = "inet4", + [DNS_RESCONF_INET6] = "inet6", + [DNS_RESCONF_OPTIONS] = "options", + [DNS_RESCONF_EDNS0] = "edns0", + [DNS_RESCONF_ROTATE] = "rotate", + [DNS_RESCONF_RECURSE] = "recurse", + [DNS_RESCONF_SMART] = "smart", + [DNS_RESCONF_TCP] = "tcp", + [DNS_RESCONF_INTERFACE] = "interface", + [DNS_RESCONF_ZERO] = "0", + [DNS_RESCONF_ONE] = "1", + [DNS_RESCONF_ENABLE] = "enable", + [DNS_RESCONF_ONLY] = "only", + [DNS_RESCONF_DISABLE] = "disable", + }; + unsigned i; + + for (i = 0; i < lengthof(words); i++) { + if (words[i] && 0 == strcasecmp(words[i], word)) + return i; + } + + if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1)) + return DNS_RESCONF_NDOTS; + + if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1)) + return DNS_RESCONF_TIMEOUT; + + if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1)) + return DNS_RESCONF_ATTEMPTS; + + if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1)) + return DNS_RESCONF_TCPx; + + return -1; +} /* dns_resconf_keyword() */ + + +/** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */ +int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) { + struct { char buf[128], *p; } addr = { "", addr.buf }; + unsigned short port = 0; + int ch, af = AF_INET, error; + + memset(ss, 0, sizeof *ss); + while ((ch = *src++)) { + switch (ch) { + case ' ': + /* FALL THROUGH */ + case '\t': + break; + case '[': + break; + case ']': + while ((ch = *src++)) { + if (dns_isdigit(ch)) { + port *= 10; + port += ch - '0'; + } + } + + goto inet; + case ':': + af = AF_INET6; + + /* FALL THROUGH */ + default: + if (addr.p < endof(addr.buf) - 1) + *addr.p++ = ch; + + break; + } /* switch() */ + } /* while() */ +inet: + + if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL)))) + return error; + + port = (!port)? 53 : port; + *dns_sa_port(af, ss) = htons(port); + dns_sa_family(ss) = af; + + return 0; +} /* dns_resconf_pton() */ + +#define dns_resconf_issep(ch) (dns_isspace(ch) || (ch) == ',') +#define dns_resconf_iscom(ch) ((ch) == '#' || (ch) == ';') + +int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { + unsigned sa_count = 0; + char words[6][DNS_D_MAXNAME + 1]; + unsigned wp, wc, i, j, n; + int ch, error; + + rewind(fp); + + do { + memset(words, '\0', sizeof words); + wp = 0; + wc = 0; + + while (EOF != (ch = getc(fp)) && ch != '\n') { + if (dns_resconf_issep(ch)) { + if (wp > 0) { + wp = 0; + + if (++wc >= lengthof(words)) + goto skip; + } + } else if (dns_resconf_iscom(ch)) { +skip: + do { + ch = getc(fp); + } while (ch != EOF && ch != '\n'); + + break; + } else if (wp < sizeof words[wc] - 1) { + words[wc][wp++] = ch; + } else { + wp = 0; /* drop word */ + goto skip; + } + } + + if (wp > 0) + wc++; + + if (wc < 2) + continue; + + switch (dns_resconf_keyword(words[0])) { + case DNS_RESCONF_NAMESERVER: + if (sa_count >= lengthof(resconf->nameserver)) + continue; + + if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1]))) + continue; + + sa_count++; + + break; + case DNS_RESCONF_DOMAIN: + case DNS_RESCONF_SEARCH: + memset(resconf->search, '\0', sizeof resconf->search); + + for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++) + dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i])); + + break; + case DNS_RESCONF_LOOKUP: + for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) { + switch (dns_resconf_keyword(words[i])) { + case DNS_RESCONF_FILE: + resconf->lookup[j++] = 'f'; + + break; + case DNS_RESCONF_BIND: + resconf->lookup[j++] = 'b'; + + break; + case DNS_RESCONF_CACHE: + resconf->lookup[j++] = 'c'; + + break; + default: + break; + } /* switch() */ + } /* for() */ + + break; + case DNS_RESCONF_FAMILY: + for (i = 1, j = 0; i < wc && j < lengthof(resconf->family); i++) { + switch (dns_resconf_keyword(words[i])) { + case DNS_RESCONF_INET4: + resconf->family[j++] = AF_INET; + + break; + case DNS_RESCONF_INET6: + resconf->family[j++] = AF_INET6; + + break; + default: + break; + } + } + + break; + case DNS_RESCONF_OPTIONS: + for (i = 1; i < wc; i++) { + switch (dns_resconf_keyword(words[i])) { + case DNS_RESCONF_EDNS0: + resconf->options.edns0 = 1; + + break; + case DNS_RESCONF_NDOTS: + for (j = sizeof "ndots:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { + n *= 10; + n += words[i][j] - '0'; + } /* for() */ + + resconf->options.ndots = n; + + break; + case DNS_RESCONF_TIMEOUT: + for (j = sizeof "timeout:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { + n *= 10; + n += words[i][j] - '0'; + } /* for() */ + + resconf->options.timeout = n; + + break; + case DNS_RESCONF_ATTEMPTS: + for (j = sizeof "attempts:" - 1, n = 0; dns_isdigit(words[i][j]); j++) { + n *= 10; + n += words[i][j] - '0'; + } /* for() */ + + resconf->options.attempts = n; + + break; + case DNS_RESCONF_ROTATE: + resconf->options.rotate = 1; + + break; + case DNS_RESCONF_RECURSE: + resconf->options.recurse = 1; + + break; + case DNS_RESCONF_SMART: + resconf->options.smart = 1; + + break; + case DNS_RESCONF_TCP: + resconf->options.tcp = DNS_RESCONF_TCP_ONLY; + + break; + case DNS_RESCONF_TCPx: + switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) { + case DNS_RESCONF_ENABLE: + resconf->options.tcp = DNS_RESCONF_TCP_ENABLE; + + break; + case DNS_RESCONF_ONE: + case DNS_RESCONF_ONLY: + resconf->options.tcp = DNS_RESCONF_TCP_ONLY; + + break; + case DNS_RESCONF_ZERO: + case DNS_RESCONF_DISABLE: + resconf->options.tcp = DNS_RESCONF_TCP_DISABLE; + + break; + default: + break; + } /* switch() */ + + break; + default: + break; + } /* switch() */ + } /* for() */ + + break; + case DNS_RESCONF_INTERFACE: + for (i = 0, n = 0; dns_isdigit(words[2][i]); i++) { + n *= 10; + n += words[2][i] - '0'; + } + + dns_resconf_setiface(resconf, words[1], n); + + break; + default: + break; + } /* switch() */ + } while (ch != EOF); + + return 0; +} /* dns_resconf_loadfile() */ + + +int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { + FILE *fp; + int error; + + if (!(fp = dns_fopen(path, "rt", &error))) + return error; + + error = dns_resconf_loadfile(resconf, fp); + + fclose(fp); + + return error; +} /* dns_resconf_loadpath() */ + + +struct dns_anyconf { + char *token[16]; + unsigned count; + char buffer[1024], *tp, *cp; +}; /* struct dns_anyconf */ + + +static void dns_anyconf_reset(struct dns_anyconf *cf) { + cf->count = 0; + cf->tp = cf->cp = cf->buffer; +} /* dns_anyconf_reset() */ + + +static int dns_anyconf_push(struct dns_anyconf *cf) { + if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token))) + return ENOMEM; + + *cf->cp++ = '\0'; + cf->token[cf->count++] = cf->tp; + cf->tp = cf->cp; + + return 0; +} /* dns_anyconf_push() */ + + +static void dns_anyconf_pop(struct dns_anyconf *cf) { + if (cf->count > 0) { + --cf->count; + cf->tp = cf->cp = cf->token[cf->count]; + cf->token[cf->count] = 0; + } +} /* dns_anyconf_pop() */ + + +static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) { + if (!(cf->cp < endof(cf->buffer))) + return ENOMEM; + + *cf->cp++ = ch; + + return 0; +} /* dns_anyconf_addc() */ + + +static _Bool dns_anyconf_match(const char *pat, int mc) { + _Bool match; + int pc; + + if (*pat == '^') { + match = 0; + ++pat; + } else { + match = 1; + } + + while ((pc = *(const unsigned char *)pat++)) { + switch (pc) { + case '%': + if (!(pc = *(const unsigned char *)pat++)) + return !match; + + switch (pc) { + case 'a': + if (dns_isalpha(mc)) + return match; + break; + case 'd': + if (dns_isdigit(mc)) + return match; + break; + case 'w': + if (dns_isalnum(mc)) + return match; + break; + case 's': + if (dns_isspace(mc)) + return match; + break; + default: + if (mc == pc) + return match; + break; + } /* switch() */ + + break; + default: + if (mc == pc) + return match; + break; + } /* switch() */ + } /* while() */ + + return !match; +} /* dns_anyconf_match() */ + + +static int dns_anyconf_peek(FILE *fp) { + int ch; + ch = getc(fp); + ungetc(ch, fp); + return ch; +} /* dns_anyconf_peek() */ + + +static size_t dns_anyconf_skip(const char *pat, FILE *fp) { + size_t count = 0; + int ch; + + while (EOF != (ch = getc(fp))) { + if (dns_anyconf_match(pat, ch)) { + count++; + continue; + } + + ungetc(ch, fp); + + break; + } + + return count; +} /* dns_anyconf_skip() */ + + +static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) { + size_t len; + int ch; + + while (EOF != (ch = getc(fp))) { + if (dns_anyconf_match(pat, ch)) { + if ((*error = dns_anyconf_addc(cf, ch))) + return 0; + + continue; + } else { + ungetc(ch, fp); + + break; + } + } + + if ((len = cf->cp - cf->tp)) { + if ((*error = dns_anyconf_push(cf))) + return 0; + + return len; + } else { + *error = 0; + + return 0; + } +} /* dns_anyconf_scan() */ + + +DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) { + unsigned i; + + fprintf(fp, "tokens:"); + + for (i = 0; i < cf->count; i++) { + fprintf(fp, " %s", cf->token[i]); + } + + fputc('\n', fp); +} /* dns_anyconf_dump() */ + + +enum dns_nssconf_keyword { + DNS_NSSCONF_INVALID = 0, + DNS_NSSCONF_HOSTS = 1, + DNS_NSSCONF_SUCCESS, + DNS_NSSCONF_NOTFOUND, + DNS_NSSCONF_UNAVAIL, + DNS_NSSCONF_TRYAGAIN, + DNS_NSSCONF_CONTINUE, + DNS_NSSCONF_RETURN, + DNS_NSSCONF_FILES, + DNS_NSSCONF_DNS, + DNS_NSSCONF_MDNS, + + DNS_NSSCONF_LAST, +}; /* enum dns_nssconf_keyword */ + +static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) { + static const char *list[] = { + [DNS_NSSCONF_HOSTS] = "hosts", + [DNS_NSSCONF_SUCCESS] = "success", + [DNS_NSSCONF_NOTFOUND] = "notfound", + [DNS_NSSCONF_UNAVAIL] = "unavail", + [DNS_NSSCONF_TRYAGAIN] = "tryagain", + [DNS_NSSCONF_CONTINUE] = "continue", + [DNS_NSSCONF_RETURN] = "return", + [DNS_NSSCONF_FILES] = "files", + [DNS_NSSCONF_DNS] = "dns", + [DNS_NSSCONF_MDNS] = "mdns", + }; + unsigned i; + + for (i = 1; i < lengthof(list); i++) { + if (list[i] && 0 == strcasecmp(list[i], word)) + return i; + } + + return DNS_NSSCONF_INVALID; +} /* dns_nssconf_keyword() */ + + +static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) { + static const char map[] = { + ['S'] = DNS_NSSCONF_SUCCESS, + ['N'] = DNS_NSSCONF_NOTFOUND, + ['U'] = DNS_NSSCONF_UNAVAIL, + ['T'] = DNS_NSSCONF_TRYAGAIN, + ['C'] = DNS_NSSCONF_CONTINUE, + ['R'] = DNS_NSSCONF_RETURN, + ['f'] = DNS_NSSCONF_FILES, + ['F'] = DNS_NSSCONF_FILES, + ['d'] = DNS_NSSCONF_DNS, + ['D'] = DNS_NSSCONF_DNS, + ['b'] = DNS_NSSCONF_DNS, + ['B'] = DNS_NSSCONF_DNS, + ['m'] = DNS_NSSCONF_MDNS, + ['M'] = DNS_NSSCONF_MDNS, + }; + + return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID; +} /* dns_nssconf_c2k() */ + + +DNS_PRAGMA_PUSH +DNS_PRAGMA_QUIET + +static int dns_nssconf_k2c(int k) { + static const char map[DNS_NSSCONF_LAST] = { + [DNS_NSSCONF_SUCCESS] = 'S', + [DNS_NSSCONF_NOTFOUND] = 'N', + [DNS_NSSCONF_UNAVAIL] = 'U', + [DNS_NSSCONF_TRYAGAIN] = 'T', + [DNS_NSSCONF_CONTINUE] = 'C', + [DNS_NSSCONF_RETURN] = 'R', + [DNS_NSSCONF_FILES] = 'f', + [DNS_NSSCONF_DNS] = 'b', + [DNS_NSSCONF_MDNS] = 'm', + }; + + return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?'; +} /* dns_nssconf_k2c() */ + +static const char *dns_nssconf_k2s(int k) { + static const char *const map[DNS_NSSCONF_LAST] = { + [DNS_NSSCONF_SUCCESS] = "SUCCESS", + [DNS_NSSCONF_NOTFOUND] = "NOTFOUND", + [DNS_NSSCONF_UNAVAIL] = "UNAVAIL", + [DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN", + [DNS_NSSCONF_CONTINUE] = "continue", + [DNS_NSSCONF_RETURN] = "return", + [DNS_NSSCONF_FILES] = "files", + [DNS_NSSCONF_DNS] = "dns", + [DNS_NSSCONF_MDNS] = "mdns", + }; + + return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : ""; +} /* dns_nssconf_k2s() */ + +DNS_PRAGMA_POP + + +int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) { + enum dns_nssconf_keyword source, status, action; + char lookup[sizeof resconf->lookup] = "", *lp; + struct dns_anyconf cf; + size_t i; + int error; + + while (!feof(fp) && !ferror(fp)) { + dns_anyconf_reset(&cf); + + dns_anyconf_skip("%s", fp); + + if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) + goto nextent; + + if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0])) + goto nextent; + + dns_anyconf_pop(&cf); + + if (!dns_anyconf_skip(": \t", fp)) + goto nextent; + + *(lp = lookup) = '\0'; + + while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { + dns_anyconf_skip(" \t", fp); + + if ('[' == dns_anyconf_peek(fp)) { + dns_anyconf_skip("[! \t", fp); + + while (dns_anyconf_scan(&cf, "%w_", fp, &error)) { + dns_anyconf_skip("= \t", fp); + if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) { + dns_anyconf_pop(&cf); /* discard status */ + dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */ + break; + } + dns_anyconf_skip(" \t", fp); + } + + dns_anyconf_skip("] \t", fp); + } + + if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */ + goto nextsrc; + + source = dns_nssconf_keyword(cf.token[0]); + + switch (source) { + case DNS_NSSCONF_DNS: + case DNS_NSSCONF_MDNS: + case DNS_NSSCONF_FILES: + *lp++ = dns_nssconf_k2c(source); + break; + default: + goto nextsrc; + } + + for (i = 1; i + 1 < cf.count; i += 2) { + status = dns_nssconf_keyword(cf.token[i]); + action = dns_nssconf_keyword(cf.token[i + 1]); + + switch (status) { + case DNS_NSSCONF_SUCCESS: + case DNS_NSSCONF_NOTFOUND: + case DNS_NSSCONF_UNAVAIL: + case DNS_NSSCONF_TRYAGAIN: + *lp++ = dns_nssconf_k2c(status); + break; + default: + continue; + } + + switch (action) { + case DNS_NSSCONF_CONTINUE: + case DNS_NSSCONF_RETURN: + break; + default: + action = (status == DNS_NSSCONF_SUCCESS) + ? DNS_NSSCONF_RETURN + : DNS_NSSCONF_CONTINUE; + break; + } + + *lp++ = dns_nssconf_k2c(action); + } +nextsrc: + *lp = '\0'; + dns_anyconf_reset(&cf); + } +nextent: + dns_anyconf_skip("^\n", fp); + } + + if (*lookup) + strncpy(resconf->lookup, lookup, sizeof resconf->lookup); + + return 0; +} /* dns_nssconf_loadfile() */ + + +int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) { + FILE *fp; + int error; + + if (!(fp = dns_fopen(path, "rt", &error))) + return error; + + error = dns_nssconf_loadfile(resconf, fp); + + fclose(fp); + + return error; +} /* dns_nssconf_loadpath() */ + + +struct dns_nssconf_source { + enum dns_nssconf_keyword source, success, notfound, unavail, tryagain; +}; /* struct dns_nssconf_source */ + +typedef unsigned dns_nssconf_i; + +static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) { + return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0; +} /* dns_nssconf_peek() */ + +static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) { + int source, status, action; + + src->source = DNS_NSSCONF_INVALID; + src->success = DNS_NSSCONF_RETURN; + src->notfound = DNS_NSSCONF_CONTINUE; + src->unavail = DNS_NSSCONF_CONTINUE; + src->tryagain = DNS_NSSCONF_CONTINUE; + + while ((source = dns_nssconf_peek(resconf, *state))) { + source = dns_nssconf_c2k(source); + ++*state; + + switch (source) { + case DNS_NSSCONF_FILES: + case DNS_NSSCONF_DNS: + case DNS_NSSCONF_MDNS: + src->source = source; + break; + default: + continue; + } + + while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) { + status = dns_nssconf_c2k(status); + action = dns_nssconf_c2k(action); + + switch (action) { + case DNS_NSSCONF_RETURN: + case DNS_NSSCONF_CONTINUE: + break; + default: + goto done; + } + + switch (status) { + case DNS_NSSCONF_SUCCESS: + src->success = action; + break; + case DNS_NSSCONF_NOTFOUND: + src->notfound = action; + break; + case DNS_NSSCONF_UNAVAIL: + src->unavail = action; + break; + case DNS_NSSCONF_TRYAGAIN: + src->tryagain = action; + break; + default: + goto done; + } + + *state += 2; + } + + break; + } +done: + return src->source != DNS_NSSCONF_INVALID; +} /* dns_nssconf_next() */ + + +static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) { + switch (status) { + case DNS_NSSCONF_SUCCESS: + if (action == DNS_NSSCONF_RETURN) + return 0; + break; + default: + if (action == DNS_NSSCONF_CONTINUE) + return 0; + break; + } + + fputc(' ', fp); + + if (!*count) + fputc('[', fp); + + fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action)); + + ++*count; + + return 0; +} /* dns_nssconf_dump_status() */ + + +int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { + struct dns_nssconf_source src; + dns_nssconf_i i = 0; + + fputs("hosts:", fp); + + while (dns_nssconf_next(&src, resconf, &i)) { + unsigned n = 0; + + fprintf(fp, " %s", dns_nssconf_k2s(src.source)); + + dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp); + dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp); + dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp); + dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp); + + if (n) + fputc(']', fp); + } + + fputc('\n', fp); + + return 0; +} /* dns_nssconf_dump() */ + + +int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) { + int af = (strchr(addr, ':'))? AF_INET6 : AF_INET; + int error; + + memset(&resconf->iface, 0, sizeof (struct sockaddr_storage)); + if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL)))) + return error; + + *dns_sa_port(af, &resconf->iface) = htons(port); + resconf->iface.ss_family = af; + + return 0; +} /* dns_resconf_setiface() */ + + +#define DNS_SM_RESTORE \ + do { \ + pc = 0xff & (*state >> 0); \ + srchi = 0xff & (*state >> 8); \ + ndots = 0xff & (*state >> 16); \ + } while (0) + +#define DNS_SM_SAVE \ + do { \ + *state = ((0xff & pc) << 0) \ + | ((0xff & srchi) << 8) \ + | ((0xff & ndots) << 16); \ + } while (0) + +size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) { + unsigned pc, srchi, ndots, len; + + DNS_SM_ENTER; + + /* if FQDN then return as-is and finish */ + if (dns_d_isanchored(qname, qlen)) { + len = dns_d_anchor(dst, lim, qname, qlen); + DNS_SM_YIELD(len); + DNS_SM_EXIT; + } + + ndots = dns_d_ndots(qname, qlen); + + if (ndots >= resconf->options.ndots) { + len = dns_d_anchor(dst, lim, qname, qlen); + DNS_SM_YIELD(len); + } + + while (srchi < lengthof(resconf->search) && resconf->search[srchi][0]) { + struct dns_buf buf = DNS_B_INTO(dst, lim); + const char *dn = resconf->search[srchi++]; + + dns_b_put(&buf, qname, qlen); + dns_b_putc(&buf, '.'); + dns_b_puts(&buf, dn); + if (!dns_d_isanchored(dn, strlen(dn))) + dns_b_putc(&buf, '.'); + len = dns_b_strllen(&buf); + DNS_SM_YIELD(len); + } + + if (ndots < resconf->options.ndots) { + len = dns_d_anchor(dst, lim, qname, qlen); + DNS_SM_YIELD(len); + } + + DNS_SM_LEAVE; + + return dns_strlcpy(dst, "", lim); +} /* dns_resconf_search() */ + +#undef DNS_SM_SAVE +#undef DNS_SM_RESTORE + + +int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) { + unsigned i; + int af; + + for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) { + char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; + unsigned short port; + + dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr); + port = ntohs(*dns_sa_port(af, &resconf->nameserver[i])); + + if (port == 53) + fprintf(fp, "nameserver %s\n", addr); + else + fprintf(fp, "nameserver [%s]:%hu\n", addr, port); + } + + + fprintf(fp, "search"); + + for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++) + fprintf(fp, " %s", resconf->search[i]); + + fputc('\n', fp); + + + fputs("; ", fp); + dns_nssconf_dump(resconf, fp); + + fprintf(fp, "lookup"); + + for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) { + switch (resconf->lookup[i]) { + case 'b': + fprintf(fp, " bind"); break; + case 'f': + fprintf(fp, " file"); break; + case 'c': + fprintf(fp, " cache"); break; + } + } + + fputc('\n', fp); + + + fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts); + + if (resconf->options.edns0) + fprintf(fp, " edns0"); + if (resconf->options.rotate) + fprintf(fp, " rotate"); + if (resconf->options.recurse) + fprintf(fp, " recurse"); + if (resconf->options.smart) + fprintf(fp, " smart"); + + switch (resconf->options.tcp) { + case DNS_RESCONF_TCP_ENABLE: + break; + case DNS_RESCONF_TCP_ONLY: + fprintf(fp, " tcp"); + break; + case DNS_RESCONF_TCP_SOCKS: + fprintf(fp, " tcp:socks"); + break; + case DNS_RESCONF_TCP_DISABLE: + fprintf(fp, " tcp:disable"); + break; + } + + fputc('\n', fp); + + + if ((af = resconf->iface.ss_family) != AF_UNSPEC) { + char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]"; + + dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr); + + fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface))); + } + + return 0; +} /* dns_resconf_dump() */ + + +/* + * H I N T S E R V E R R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_hints_soa { + unsigned char zone[DNS_D_MAXNAME + 1]; + + struct { + struct sockaddr_storage ss; + unsigned priority; + } addrs[16]; + + unsigned count; + + struct dns_hints_soa *next; +}; /* struct dns_hints_soa */ + + +struct dns_hints { + dns_atomic_t refcount; + + struct dns_hints_soa *head; +}; /* struct dns_hints */ + + +struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) { + static const struct dns_hints H_initializer; + struct dns_hints *H; + + (void)resconf; + + if (!(H = malloc(sizeof *H))) + goto syerr; + + *H = H_initializer; + + dns_hints_acquire(H); + + return H; +syerr: + *error = dns_syerr(); + + free(H); + + return 0; +} /* dns_hints_open() */ + + +void dns_hints_close(struct dns_hints *H) { + struct dns_hints_soa *soa, *nxt; + + if (!H || 1 != dns_hints_release(H)) + return /* void */; + + for (soa = H->head; soa; soa = nxt) { + nxt = soa->next; + + free(soa); + } + + free(H); + + return /* void */; +} /* dns_hints_close() */ + + +dns_refcount_t dns_hints_acquire(struct dns_hints *H) { + return dns_atomic_fetch_add(&H->refcount); +} /* dns_hints_acquire() */ + + +dns_refcount_t dns_hints_release(struct dns_hints *H) { + return dns_atomic_fetch_sub(&H->refcount); +} /* dns_hints_release() */ + + +struct dns_hints *dns_hints_mortal(struct dns_hints *hints) { + if (hints) + dns_hints_release(hints); + + return hints; +} /* dns_hints_mortal() */ + + +struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) { + struct dns_hints *hints = 0; + int error; + + if (resconf) + dns_resconf_acquire(resconf); + else if (!(resconf = dns_resconf_local(&error))) + goto error; + + if (!(hints = dns_hints_open(resconf, &error))) + goto error; + + error = 0; + + if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error) + goto error; + + dns_resconf_close(resconf); + + return hints; +error: + *error_ = error; + + dns_resconf_close(resconf); + dns_hints_close(hints); + + return 0; +} /* dns_hints_local() */ + + +struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) { + static const struct { + int af; + char addr[INET6_ADDRSTRLEN]; + } root_hints[] = { + { AF_INET, "198.41.0.4" }, /* A.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:503:ba3e::2:30" }, /* A.ROOT-SERVERS.NET. */ + { AF_INET, "192.228.79.201" }, /* B.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:84::b" }, /* B.ROOT-SERVERS.NET. */ + { AF_INET, "192.33.4.12" }, /* C.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:2::c" }, /* C.ROOT-SERVERS.NET. */ + { AF_INET, "199.7.91.13" }, /* D.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:2d::d" }, /* D.ROOT-SERVERS.NET. */ + { AF_INET, "192.203.230.10" }, /* E.ROOT-SERVERS.NET. */ + { AF_INET, "192.5.5.241" }, /* F.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:2f::f" }, /* F.ROOT-SERVERS.NET. */ + { AF_INET, "192.112.36.4" }, /* G.ROOT-SERVERS.NET. */ + { AF_INET, "128.63.2.53" }, /* H.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:1::803f:235" }, /* H.ROOT-SERVERS.NET. */ + { AF_INET, "192.36.148.17" }, /* I.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:7FE::53" }, /* I.ROOT-SERVERS.NET. */ + { AF_INET, "192.58.128.30" }, /* J.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:503:c27::2:30" }, /* J.ROOT-SERVERS.NET. */ + { AF_INET, "193.0.14.129" }, /* K.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:7FD::1" }, /* K.ROOT-SERVERS.NET. */ + { AF_INET, "199.7.83.42" }, /* L.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:500:3::42" }, /* L.ROOT-SERVERS.NET. */ + { AF_INET, "202.12.27.33" }, /* M.ROOT-SERVERS.NET. */ + { AF_INET6, "2001:DC3::35" }, /* M.ROOT-SERVERS.NET. */ + }; + struct dns_hints *hints = 0; + struct sockaddr_storage ss; + unsigned i; + int error, af; + + if (!(hints = dns_hints_open(resconf, &error))) + goto error; + + for (i = 0; i < lengthof(root_hints); i++) { + af = root_hints[i].af; + + memset(&ss, 0, sizeof ss); + if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL)))) + goto error; + + *dns_sa_port(af, &ss) = htons(53); + ss.ss_family = af; + + if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1))) + goto error; + } + + return hints; +error: + *error_ = error; + + dns_hints_close(hints); + + return 0; +} /* dns_hints_root() */ + + +static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) { + struct dns_hints_soa *soa; + + for (soa = H->head; soa; soa = soa->next) { + if (0 == strcasecmp(zone, (char *)soa->zone)) + return soa; + } + + return 0; +} /* dns_hints_fetch() */ + + +int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) { + static const struct dns_hints_soa soa_initializer; + struct dns_hints_soa *soa; + unsigned i; + + if (!(soa = dns_hints_fetch(H, zone))) { + if (!(soa = malloc(sizeof *soa))) + return dns_syerr(); + *soa = soa_initializer; + dns_strlcpy((char *)soa->zone, zone, sizeof soa->zone); + + soa->next = H->head; + H->head = soa; + } + + i = soa->count % lengthof(soa->addrs); + + memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa)); + + soa->addrs[i].priority = DNS_PP_MAX(1, priority); + + if (soa->count < lengthof(soa->addrs)) + soa->count++; + + return 0; +} /* dns_hints_insert() */ + + +static _Bool dns_hints_isinaddr_any(const void *sa) { + struct in_addr *addr; + + if (dns_sa_family(sa) != AF_INET) + return 0; + + addr = dns_sa_addr(AF_INET, sa, NULL); + return addr->s_addr == htonl(INADDR_ANY); +} + +unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) { + unsigned i, n, p; + int error; + + for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) { + union { struct sockaddr_in sin; } tmp; + struct sockaddr *ns; + + /* + * dns_resconf_open initializes nameserver[0] to INADDR_ANY. + * + * Traditionally the semantics of 0.0.0.0 meant the default + * interface, which evolved to mean the loopback interface. + * See comment block preceding resolv/res_init.c:res_init in + * glibc 2.23. As of 2.23, glibc no longer translates + * 0.0.0.0 despite the code comment, but it does default to + * 127.0.0.1 when no nameservers are present. + * + * BIND9 as of 9.10.3 still translates 0.0.0.0 to 127.0.0.1. + * See lib/lwres/lwconfig.c:lwres_create_addr and the + * convert_zero flag. 127.0.0.1 is also the default when no + * nameservers are present. + */ + if (dns_hints_isinaddr_any(&resconf->nameserver[i])) { + memcpy(&tmp.sin, &resconf->nameserver[i], sizeof tmp.sin); + tmp.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + ns = (struct sockaddr *)&tmp.sin; + } else { + ns = (struct sockaddr *)&resconf->nameserver[i]; + } + + if ((error = dns_hints_insert(H, zone, ns, p))) + goto error; + + p += !resconf->options.rotate; + } + + return n; +error: + *error_ = error; + + return n; +} /* dns_hints_insert_resconf() */ + + +static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) { + int cmp; + + if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority)) + return cmp; + + return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed); +} /* dns_hints_i_cmp() */ + + +static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) { + unsigned p0, p; + + p0 = 0; + + for (p = 1; p < soa->count; p++) { + if (dns_hints_i_cmp(p, p0, i, soa) < 0) + p0 = p; + } + + return p0; +} /* dns_hints_i_start() */ + + +static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) { + unsigned pZ, p; + + for (pZ = 0; pZ < soa->count; pZ++) { + if (dns_hints_i_cmp(pZ, p0, i, soa) > 0) + goto cont; + } + + return soa->count; +cont: + for (p = pZ + 1; p < soa->count; p++) { + if (dns_hints_i_cmp(p, p0, i, soa) <= 0) + continue; + + if (dns_hints_i_cmp(p, pZ, i, soa) >= 0) + continue; + + pZ = p; + } + + + return pZ; +} /* dns_hints_i_skip() */ + + +static struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) { + static const struct dns_hints_i i_initializer; + struct dns_hints_soa *soa; + + i->state = i_initializer.state; + + do { + i->state.seed = dns_random(); + } while (0 == i->state.seed); + + if ((soa = dns_hints_fetch(hints, i->zone))) { + i->state.next = dns_hints_i_start(i, soa); + } + + return i; +} /* dns_hints_i_init() */ + + +unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) { + struct dns_hints_soa *soa; + unsigned n; + + if (!(soa = dns_hints_fetch(H, i->zone))) + return 0; + + n = 0; + + while (i->state.next < soa->count && n < lim) { + *sa = (struct sockaddr *)&soa->addrs[i->state.next].ss; + *sa_len = dns_sa_len(*sa); + + sa++; + sa_len++; + n++; + + i->state.next = dns_hints_i_skip(i->state.next, i, soa); + } + + return n; +} /* dns_hints_grep() */ + + +struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; + struct dns_packet *A, *P; + struct dns_rr rr; + char zone[DNS_D_MAXNAME + 1]; + size_t zlen; + struct dns_hints_i i; + struct sockaddr *sa; + socklen_t slen; + int error; + struct dns_rr_i I_instance = { 0 }; + + I_instance.section = DNS_S_QUESTION; + + if (!dns_rr_grep(&rr, 1, &I_instance, Q, &error)) + goto error; + + if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error))) + goto error; + else if (zlen >= sizeof zone) + goto toolong; + + P = dns_p_init(&P_instance.p, 512); + dns_header(P)->qr = 1; + + if ((error = dns_rr_copy(P, &rr, Q))) + goto error; + + if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local."))) + goto error; + + do { + i.zone = zone; + + dns_hints_i_init(&i, hints); + + while (dns_hints_grep(&sa, &slen, 1, &i, hints)) { + int af = sa->sa_family; + int rtype = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A; + + if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL)))) + goto error; + } + } while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen))); + + if (!(A = dns_p_copy(dns_p_make(P->end, &error), P))) + goto error; + + return A; +toolong: + error = DNS_EILLEGAL; +error: + *error_ = error; + + return 0; +} /* dns_hints_query() */ + + +/** ugly hack to support specifying ports other than 53 in resolv.conf. */ +static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) { + struct dns_hints_soa *soa; + void *addrsoa; + socklen_t addrlen; + unsigned short port; + unsigned i; + + for (soa = hints->head; soa; soa = soa->next) { + for (i = 0; i < soa->count; i++) { + if (af != soa->addrs[i].ss.ss_family) + continue; + + if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen))) + continue; + + if (memcmp(addr, addrsoa, addrlen)) + continue; + + port = *dns_sa_port(af, &soa->addrs[i].ss); + + return (port)? port : htons(53); + } + } + + return htons(53); +} /* dns_hints_port() */ + + +int dns_hints_dump(struct dns_hints *hints, FILE *fp) { + struct dns_hints_soa *soa; + char addr[INET6_ADDRSTRLEN]; + unsigned i; + int af, error; + + for (soa = hints->head; soa; soa = soa->next) { + fprintf(fp, "ZONE \"%s\"\n", soa->zone); + + for (i = 0; i < soa->count; i++) { + af = soa->addrs[i].ss.ss_family; + + if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr))) + return error; + + fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss))); + } + } + + return 0; +} /* dns_hints_dump() */ + + +/* + * C A C H E R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) { + return dns_atomic_fetch_add(&cache->_.refcount); +} /* dns_cache_acquire() */ + + +static dns_refcount_t dns_cache_release(struct dns_cache *cache) { + return dns_atomic_fetch_sub(&cache->_.refcount); +} /* dns_cache_release() */ + + +static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) { + (void)query; + (void)cache; + (void)error; + + return NULL; +} /* dns_cache_query() */ + + +static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) { + (void)query; + (void)cache; + + return 0; +} /* dns_cache_submit() */ + + +static int dns_cache_check(struct dns_cache *cache) { + (void)cache; + + return 0; +} /* dns_cache_check() */ + + +static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) { + (void)cache; + (void)error; + + return NULL; +} /* dns_cache_fetch() */ + + +static int dns_cache_pollfd(struct dns_cache *cache) { + (void)cache; + + return -1; +} /* dns_cache_pollfd() */ + + +static short dns_cache_events(struct dns_cache *cache) { + (void)cache; + + return 0; +} /* dns_cache_events() */ + + +static void dns_cache_clear(struct dns_cache *cache) { + (void)cache; + + return; +} /* dns_cache_clear() */ + + +struct dns_cache *dns_cache_init(struct dns_cache *cache) { + static const struct dns_cache c_init = { + .acquire = &dns_cache_acquire, + .release = &dns_cache_release, + .query = &dns_cache_query, + .submit = &dns_cache_submit, + .check = &dns_cache_check, + .fetch = &dns_cache_fetch, + .pollfd = &dns_cache_pollfd, + .events = &dns_cache_events, + .clear = &dns_cache_clear, + ._ = { .refcount = 1, }, + }; + + *cache = c_init; + + return cache; +} /* dns_cache_init() */ + + +void dns_cache_close(struct dns_cache *cache) { + if (cache) + cache->release(cache); +} /* dns_cache_close() */ + + +/* + * S O C K E T R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +static void dns_socketclose(int *fd, const struct dns_options *opts) { + if (opts && opts->closefd.cb) + opts->closefd.cb(fd, opts->closefd.arg); + + if (*fd != -1) { +#if _WIN32 + closesocket(*fd); +#else + close(*fd); +#endif + *fd = -1; + } +} /* dns_socketclose() */ + + +#ifndef HAVE_IOCTLSOCKET +#define HAVE_IOCTLSOCKET (_WIN32 || _WIN64) +#endif + +#ifndef HAVE_SOCK_CLOEXEC +#ifdef SOCK_CLOEXEC +#define HAVE_SOCK_CLOEXEC 1 +#else +#define HAVE_SOCK_CLOEXEC 0 +#endif +#endif + +#ifndef HAVE_SOCK_NONBLOCK +#ifdef SOCK_NONBLOCK +#define HAVE_SOCK_NONBLOCK 1 +#else +#define HAVE_SOCK_NONBLOCK 0 +#endif +#endif + +#define DNS_SO_MAXTRY 7 + +static int dns_socket(struct sockaddr *local, int type, int *error_) { + int fd = -1, flags, error; +#if defined FIONBIO + unsigned long opt; +#endif + + flags = 0; +#if HAVE_SOCK_CLOEXEC + flags |= SOCK_CLOEXEC; +#endif +#if HAVE_SOCK_NONBLOCK + flags |= SOCK_NONBLOCK; +#endif + if (-1 == (fd = socket(local->sa_family, type|flags, 0))) + goto soerr; + +#if defined F_SETFD && !HAVE_SOCK_CLOEXEC + if (-1 == fcntl(fd, F_SETFD, 1)) + goto syerr; +#endif + +#if defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK + if (-1 == (flags = fcntl(fd, F_GETFL))) + goto syerr; + if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK)) + goto syerr; +#elif defined FIONBIO && HAVE_IOCTLSOCKET + opt = 1; + if (0 != ioctlsocket(fd, FIONBIO, &opt)) + goto soerr; +#endif + +#if defined SO_NOSIGPIPE + if (type != SOCK_DGRAM) { + const int v = 1; + if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &v, sizeof (int))) + goto soerr; + } +#endif + + if (local->sa_family != AF_INET && local->sa_family != AF_INET6) + return fd; + + if (type != SOCK_DGRAM) + return fd; + +#define LEAVE_SELECTION_OF_PORT_TO_KERNEL +#if !defined(LEAVE_SELECTION_OF_PORT_TO_KERNEL) + /* + * FreeBSD, Linux, OpenBSD, OS X, and Solaris use random ports by + * default. Though the ephemeral range is quite small on OS X + * (49152-65535 on 10.10) and Linux (32768-60999 on 4.4.0, Ubuntu + * Xenial). See also RFC 6056. + * + * TODO: Optionally rely on the kernel to select a random port. + */ + if (*dns_sa_port(local->sa_family, local) == 0) { + struct sockaddr_storage tmp; + unsigned i, port; + + memcpy(&tmp, local, dns_sa_len(local)); + + for (i = 0; i < DNS_SO_MAXTRY; i++) { + port = 1025 + (dns_random() % 64510); + + *dns_sa_port(tmp.ss_family, &tmp) = htons(port); + + if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp))) + return fd; + } + + /* NB: continue to next bind statement */ + } +#endif + + if (0 == bind(fd, local, dns_sa_len(local))) + return fd; + + /* FALL THROUGH */ +soerr: + error = dns_soerr(); + + goto error; +#if (defined F_SETFD && !HAVE_SOCK_CLOEXEC) || (defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK) +syerr: + error = dns_syerr(); + + goto error; +#endif +error: + *error_ = error; + + dns_socketclose(&fd, NULL); + + return -1; +} /* dns_socket() */ + + +enum { + DNS_SO_UDP_INIT = 1, + DNS_SO_UDP_CONN, + DNS_SO_UDP_SEND, + DNS_SO_UDP_RECV, + DNS_SO_UDP_DONE, + + DNS_SO_TCP_INIT, + DNS_SO_TCP_CONN, + DNS_SO_TCP_SEND, + DNS_SO_TCP_RECV, + DNS_SO_TCP_DONE, + + DNS_SO_SOCKS_INIT, + DNS_SO_SOCKS_CONN, + DNS_SO_SOCKS_HELLO_SEND, + DNS_SO_SOCKS_HELLO_RECV, + DNS_SO_SOCKS_AUTH_SEND, + DNS_SO_SOCKS_AUTH_RECV, + DNS_SO_SOCKS_REQUEST_PREPARE, + DNS_SO_SOCKS_REQUEST_SEND, + DNS_SO_SOCKS_REQUEST_RECV, + DNS_SO_SOCKS_REQUEST_RECV_V6, + DNS_SO_SOCKS_HANDSHAKE_DONE, +}; + +struct dns_socket { + struct dns_options opts; + + int udp; + int tcp; + + int *old; + unsigned onum, olim; + + int type; + + struct sockaddr_storage local, remote; + + struct dns_k_permutor qids; + + struct dns_stat stat; + + struct dns_trace *trace; + + /* + * NOTE: dns_so_reset() zeroes everything from here down. + */ + int state; + + unsigned short qid; + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + enum dns_type qtype; + enum dns_class qclass; + + struct dns_packet *query; + size_t qout; + + /* During a SOCKS handshake the query is temporarily stored + * here. */ + struct dns_packet *query_backup; + + struct dns_clock elapsed; + + struct dns_packet *answer; + size_t alen, apos; +}; /* struct dns_socket */ + + +/* + * NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have + * a chance to recognize a state change after installing a persistent event + * and where sequential descriptors with the same integer value returned + * from _pollfd() would be ambiguous. See dns_so_closefds(). + */ +static int dns_so_closefd(struct dns_socket *so, int *fd) { + int error; + + if (*fd == -1) + return 0; + + if (so->opts.closefd.cb) { + if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) { + return error; + } else if (*fd == -1) + return 0; + } + + if (!(so->onum < so->olim)) { + unsigned olim = DNS_PP_MAX(4, so->olim * 2); + void *old; + + if (!(old = realloc(so->old, sizeof so->old[0] * olim))) + return dns_syerr(); + + so->old = old; + so->olim = olim; + } + + so->old[so->onum++] = *fd; + *fd = -1; + + return 0; +} /* dns_so_closefd() */ + + +#define DNS_SO_CLOSE_UDP 0x01 +#define DNS_SO_CLOSE_TCP 0x02 +#define DNS_SO_CLOSE_OLD 0x04 +#define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD) + +static void dns_so_closefds(struct dns_socket *so, int which) { + if (DNS_SO_CLOSE_UDP & which) + dns_socketclose(&so->udp, &so->opts); + if (DNS_SO_CLOSE_TCP & which) + dns_socketclose(&so->tcp, &so->opts); + if (DNS_SO_CLOSE_OLD & which) { + unsigned i; + for (i = 0; i < so->onum; i++) + dns_socketclose(&so->old[i], &so->opts); + so->onum = 0; + free(so->old); + so->old = 0; + so->olim = 0; + } +} /* dns_so_closefds() */ + + +static void dns_so_destroy(struct dns_socket *); + +static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { + static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, }; + + *so = so_initializer; + so->type = type; + + if (opts) + so->opts = *opts; + + if (local) + memcpy(&so->local, local, dns_sa_len(local)); + + if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error))) + goto error; + + dns_k_permutor_init(&so->qids, 1, 65535); + + return so; +error: + dns_so_destroy(so); + + return 0; +} /* dns_so_init() */ + + +struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) { + struct dns_socket *so; + + if (!(so = malloc(sizeof *so))) + goto syerr; + + if (!dns_so_init(so, local, type, opts, error)) + goto error; + + return so; +syerr: + *error = dns_syerr(); +error: + dns_so_close(so); + + return 0; +} /* dns_so_open() */ + + +static void dns_so_destroy(struct dns_socket *so) { + dns_so_reset(so); + dns_so_closefds(so, DNS_SO_CLOSE_ALL); + dns_trace_close(so->trace); +} /* dns_so_destroy() */ + + +void dns_so_close(struct dns_socket *so) { + if (!so) + return; + + dns_so_destroy(so); + + free(so); +} /* dns_so_close() */ + + +void dns_so_reset(struct dns_socket *so) { + dns_p_setptr(&so->answer, NULL); + + memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state)); +} /* dns_so_reset() */ + + +unsigned short dns_so_mkqid(struct dns_socket *so) { + return dns_k_permutor_step(&so->qids); +} /* dns_so_mkqid() */ + + +#define DNS_SO_MINBUF 768 + +static int dns_so_newanswer(struct dns_socket *so, size_t len) { + size_t size = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF); + void *p; + + if (!(p = realloc(so->answer, size))) + return dns_syerr(); + + so->answer = dns_p_init(p, size); + + return 0; +} /* dns_so_newanswer() */ + + +int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) { + struct dns_rr rr; + int error = DNS_EUNKNOWN; + + dns_so_reset(so); + + if ((error = dns_rr_parse(&rr, 12, Q))) + goto error; + + if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error))) + goto error; + /* + * NOTE: Don't bail if expansion is too long; caller may be + * intentionally sending long names. However, we won't be able to + * verify it on return. + */ + + so->qtype = rr.type; + so->qclass = rr.class; + + if ((error = dns_so_newanswer(so, (Q->memo.opt.maxudp)? Q->memo.opt.maxudp : DNS_SO_MINBUF))) + goto syerr; + + memcpy(&so->remote, host, dns_sa_len(host)); + + so->query = Q; + so->qout = 0; + + dns_begin(&so->elapsed); + + if (dns_header(so->query)->qid == 0) + dns_header(so->query)->qid = dns_so_mkqid(so); + + so->qid = dns_header(so->query)->qid; + so->state = (so->opts.socks_host && so->opts.socks_host->ss_family) ? DNS_SO_SOCKS_INIT : + (so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT; + + so->stat.queries++; + dns_trace_so_submit(so->trace, Q, host, 0); + + return 0; +syerr: + error = dns_syerr(); +error: + dns_so_reset(so); + dns_trace_so_submit(so->trace, Q, host, error); + return error; +} /* dns_so_submit() */ + + +static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) { + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + struct dns_rr rr; + int error = -1; + + if (P->end < 12) + goto reject; + + if (so->qid != dns_header(P)->qid) + goto reject; + + if (!dns_p_count(P, DNS_S_QD)) + goto reject; + + if (0 != dns_rr_parse(&rr, 12, P)) + goto reject; + + if (rr.type != so->qtype || rr.class != so->qclass) + goto reject; + + if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error))) + goto error; + else if (qlen >= sizeof qname || qlen != so->qlen) + goto reject; + + if (0 != strcasecmp(so->qname, qname)) + goto reject; + + dns_trace_so_verify(so->trace, P, 0); + + return 0; +reject: + error = DNS_EVERIFY; +error: + DNS_SHOW(P, "rejecting packet (%s)", dns_strerror(error)); + dns_trace_so_verify(so->trace, P, error); + + return error; +} /* dns_so_verify() */ + + +static _Bool dns_so_tcp_keep(struct dns_socket *so) { + struct sockaddr_storage remote; + socklen_t l = sizeof remote; + + if (so->tcp == -1) + return 0; + + if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &l)) + return 0; + + return 0 == dns_sa_cmp(&remote, &so->remote); +} /* dns_so_tcp_keep() */ + + +/* Convenience functions for sending non-DNS data. */ + +/* Set up everything for sending LENGTH octets. Returns the buffer + for the data. */ +static unsigned char *dns_so_tcp_send_buffer(struct dns_socket *so, size_t length) { + /* Skip the length octets, we are not doing DNS. */ + so->qout = 2; + so->query->end = length; + return so->query->data; +} + +/* Set up everything for receiving LENGTH octets. */ +static void dns_so_tcp_recv_expect(struct dns_socket *so, size_t length) { + /* Skip the length octets, we are not doing DNS. */ + so->apos = 2; + so->alen = length; +} + +/* Returns the buffer containing the received data. */ +static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) { + return so->answer->data; +} + + + +#if GPGRT_GCC_VERSION >= 80000 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#elif defined __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Warray-bounds" +#endif + +static int dns_so_tcp_send(struct dns_socket *so) { + unsigned char *qsrc; + size_t qend; + int error; + size_t n; + + so->query->data[-2] = 0xff & (so->query->end >> 8); + so->query->data[-1] = 0xff & (so->query->end >> 0); + + qend = so->query->end + 2; + + while (so->qout < qend) { + qsrc = &so->query->data[-2] + so->qout; + n = dns_send_nopipe(so->tcp, (void *)qsrc, qend - so->qout, 0, &error); + dns_trace_sys_send(so->trace, so->tcp, SOCK_STREAM, qsrc, n, error); + if (error) + return error; + so->qout += n; + so->stat.tcp.sent.bytes += n; + } + + so->stat.tcp.sent.count++; + + return 0; +} /* dns_so_tcp_send() */ + + +static int dns_so_tcp_recv(struct dns_socket *so) { + unsigned char *asrc; + size_t aend, alen, n; + int error; + + aend = so->alen + 2; + + while (so->apos < aend) { + asrc = &so->answer->data[-2]; + + n = dns_recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0, &error); + dns_trace_sys_recv(so->trace, so->tcp, SOCK_STREAM, &asrc[so->apos], n, error); + if (error) + return error; + + so->apos += n; + so->stat.tcp.rcvd.bytes += n; + + if (so->alen == 0 && so->apos >= 2) { + alen = ((0xff & so->answer->data[-2]) << 8) + | ((0xff & so->answer->data[-1]) << 0); + + if ((error = dns_so_newanswer(so, alen))) + return error; + + so->alen = alen; + aend = alen + 2; + } + } + + so->answer->end = so->alen; + so->stat.tcp.rcvd.count++; + + return 0; +} /* dns_so_tcp_recv() */ + +#if GPGRT_GCC_VERSION >= 80000 +# pragma GCC diagnostic pop +#elif __clang__ +# pragma clang diagnostic pop +#endif + + +int dns_so_check(struct dns_socket *so) { + int error; + size_t n; + unsigned char *buffer; + +retry: + switch (so->state) { + case DNS_SO_UDP_INIT: + if (so->remote.ss_family != so->local.ss_family) { + /* Family mismatch. Reinitialize. */ + if ((error = dns_so_closefd(so, &so->udp))) + goto error; + if ((error = dns_so_closefd(so, &so->tcp))) + goto error; + + /* If the user supplied an interface + statement, that is gone now. Sorry. */ + memset(&so->local, 0, sizeof so->local); + so->local.ss_family = so->remote.ss_family; + + if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, &error))) + goto error; + } + + so->state++; /* FALL THROUGH */ + case DNS_SO_UDP_CONN: + udp_connect_retry: + error = dns_connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote)); + dns_trace_sys_connect(so->trace, so->udp, SOCK_DGRAM, (struct sockaddr *)&so->remote, error); + + /* Linux returns EINVAL when address was bound to + localhost and it's external IP address now. */ + if (error == EINVAL) { + struct sockaddr unspec_addr; + memset (&unspec_addr, 0, sizeof unspec_addr); + unspec_addr.sa_family = AF_UNSPEC; + connect(so->udp, &unspec_addr, sizeof unspec_addr); + goto udp_connect_retry; + } else if (error == ECONNREFUSED) + /* Error for previous socket operation may + be reserverd asynchronously. */ + goto udp_connect_retry; + + if (error) + goto error; + + so->state++; /* FALL THROUGH */ + case DNS_SO_UDP_SEND: + n = dns_send(so->udp, (void *)so->query->data, so->query->end, 0, &error); + dns_trace_sys_send(so->trace, so->udp, SOCK_DGRAM, so->query->data, n, error); + if (error) + goto error; + + so->stat.udp.sent.bytes += n; + so->stat.udp.sent.count++; + + so->state++; /* FALL THROUGH */ + case DNS_SO_UDP_RECV: + n = dns_recv(so->udp, (void *)so->answer->data, so->answer->size, 0, &error); + dns_trace_sys_recv(so->trace, so->udp, SOCK_DGRAM, so->answer->data, n, error); + if (error) + goto error; + + so->answer->end = n; + so->stat.udp.rcvd.bytes += n; + so->stat.udp.rcvd.count++; + + if ((error = dns_so_verify(so, so->answer))) + goto trash; + + so->state++; /* FALL THROUGH */ + case DNS_SO_UDP_DONE: + if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM) + return 0; + + so->state++; /* FALL THROUGH */ + case DNS_SO_TCP_INIT: + if (so->remote.ss_family != so->local.ss_family) { + /* Family mismatch. Reinitialize. */ + if ((error = dns_so_closefd(so, &so->udp))) + goto error; + if ((error = dns_so_closefd(so, &so->tcp))) + goto error; + + /* If the user supplied an interface + statement, that is gone now. Sorry. */ + memset(&so->local, 0, sizeof so->local); + so->local.ss_family = so->remote.ss_family; + } + + if (dns_so_tcp_keep(so)) { + so->state = DNS_SO_TCP_SEND; + + goto retry; + } + + if ((error = dns_so_closefd(so, &so->tcp))) + goto error; + + if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) + goto error; + + so->state++; /* FALL THROUGH */ + case DNS_SO_TCP_CONN: + error = dns_connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote)); + dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)&so->remote, error); + if (error && error != DNS_EISCONN) + goto error; + + so->state++; /* FALL THROUGH */ + case DNS_SO_TCP_SEND: + if ((error = dns_so_tcp_send(so))) + goto error; + + so->state++; /* FALL THROUGH */ + case DNS_SO_TCP_RECV: + if ((error = dns_so_tcp_recv(so))) + goto error; + + so->state++; /* FALL THROUGH */ + case DNS_SO_TCP_DONE: + /* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */ + if (so->type != SOCK_STREAM) { + if ((error = dns_so_closefd(so, &so->tcp))) + goto error; + } + + if ((error = dns_so_verify(so, so->answer))) + goto error; + + return 0; + case DNS_SO_SOCKS_INIT: + if ((error = dns_so_closefd(so, &so->tcp))) + goto error; + + if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error))) + goto error; + + so->state++; /* FALL THROUGH */ + case DNS_SO_SOCKS_CONN: { + unsigned char method; + + error = dns_connect(so->tcp, (struct sockaddr *)so->opts.socks_host, dns_sa_len(so->opts.socks_host)); + dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)so->opts.socks_host, error); + if (error && error != DNS_EISCONN) + goto error; + + /* We need to do a handshake with the SOCKS server, + * but the query is already in the buffer. Move it + * out of the way. */ + dns_p_movptr(&so->query_backup, &so->query); + + /* Create a new buffer for the handshake. */ + dns_p_grow(&so->query); + + /* Negotiate method. */ + buffer = dns_so_tcp_send_buffer(so, 3); + buffer[0] = 5; /* RFC-1928 VER field. */ + buffer[1] = 1; /* NMETHODS */ + if (so->opts.socks_user) + method = 2; /* Method: username/password authentication. */ + else + method = 0; /* Method: No authentication required. */ + buffer[2] = method; + + so->state++; + } /* FALL THROUGH */ + case DNS_SO_SOCKS_HELLO_SEND: + if ((error = dns_so_tcp_send(so))) + goto error; + + dns_so_tcp_recv_expect(so, 2); + so->state++; /* FALL THROUGH */ + case DNS_SO_SOCKS_HELLO_RECV: { + unsigned char method; + + if ((error = dns_so_tcp_recv(so))) + goto error; + + buffer = dns_so_tcp_recv_buffer(so); + method = so->opts.socks_user ? 2 : 0; + if (buffer[0] != 5 || buffer[1] != method) { + /* Socks server returned wrong version or does + not support our requested method. */ + error = ENOTSUP; /* Fixme: Is there a better errno? */ + goto error; + } + + if (method == 0) { + /* No authentication, go ahead and send the + request. */ + so->state = DNS_SO_SOCKS_REQUEST_PREPARE; + goto retry; + } + + /* Prepare username/password sub-negotiation. */ + if (! so->opts.socks_password) { + error = EINVAL; /* No password given. */ + goto error; + } else { + size_t buflen, ulen, plen; + + ulen = strlen(so->opts.socks_user); + plen = strlen(so->opts.socks_password); + if (!ulen || ulen > 255 || !plen || plen > 255) { + error = EINVAL; /* Credentials too long or too short. */ + goto error; + } + + buffer = dns_so_tcp_send_buffer(so, 3 + ulen + plen); + buffer[0] = 1; /* VER of the sub-negotiation. */ + buffer[1] = (unsigned char) ulen; + buflen = 2; + memcpy (buffer+buflen, so->opts.socks_user, ulen); + buflen += ulen; + buffer[buflen++] = (unsigned char) plen; + memcpy (buffer+buflen, so->opts.socks_password, plen); + } + + so->state++; + } /* FALL THROUGH */ + case DNS_SO_SOCKS_AUTH_SEND: + if ((error = dns_so_tcp_send(so))) + goto error; + + /* Skip the two length octets, and receive two octets. */ + dns_so_tcp_recv_expect(so, 2); + + so->state++; /* FALL THROUGH */ + case DNS_SO_SOCKS_AUTH_RECV: + if ((error = dns_so_tcp_recv(so))) + goto error; + + buffer = dns_so_tcp_recv_buffer(so); + if (buffer[0] != 1) { + /* SOCKS server returned wrong version. */ + error = EPROTO; + goto error; + } + if (buffer[1]) { + /* SOCKS server denied access. */ + error = EACCES; + goto error; + } + + so->state++; /* FALL THROUGH */ + case DNS_SO_SOCKS_REQUEST_PREPARE: + /* Send request details (rfc-1928, 4). */ + buffer = dns_so_tcp_send_buffer(so, so->remote.ss_family == AF_INET6 ? 22 : 10); + buffer[0] = 5; /* VER */ + buffer[1] = 1; /* CMD = CONNECT */ + buffer[2] = 0; /* RSV */ + if (so->remote.ss_family == AF_INET6) { + struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&so->remote; + + buffer[3] = 4; /* ATYP = IPv6 */ + memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */ + memcpy (buffer+20, &addr_in6->sin6_port, 2); /* DST.PORT */ + } else { + struct sockaddr_in *addr_in = (struct sockaddr_in *)&so->remote; + + buffer[3] = 1; /* ATYP = IPv4 */ + memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */ + memcpy (buffer+8, &addr_in->sin_port, 2); /* DST.PORT */ + } + + so->state++; /* FALL THROUGH */ + case DNS_SO_SOCKS_REQUEST_SEND: + if ((error = dns_so_tcp_send(so))) + goto error; + + /* Expect ten octets. This is the length of the + * response assuming a IPv4 address is used. */ + dns_so_tcp_recv_expect(so, 10); + so->state++; /* FALL THROUGH */ + case DNS_SO_SOCKS_REQUEST_RECV: + if ((error = dns_so_tcp_recv(so))) + goto error; + + buffer = dns_so_tcp_recv_buffer(so); + if (buffer[0] != 5 || buffer[2] != 0) { + /* Socks server returned wrong version or the + reserved field is not zero. */ + error = EPROTO; + goto error; + } + if (buffer[1]) { + switch (buffer[1]) { + case 0x01: /* general SOCKS server failure. */ + error = ENETDOWN; + break; + case 0x02: /* connection not allowed by ruleset. */ + error = EACCES; + break; + case 0x03: /* Network unreachable */ + error = ENETUNREACH; + break; + case 0x04: /* Host unreachable */ + error = EHOSTUNREACH; + break; + case 0x05: /* Connection refused */ + error = ECONNREFUSED; + break; + case 0x06: /* TTL expired */ + error = ETIMEDOUT; + break; + case 0x08: /* Address type not supported */ + error = EPROTONOSUPPORT; + break; + case 0x07: /* Command not supported */ + default: + error = ENOTSUP; /* Fixme: Is there a better error? */ + break; + } + goto error; + } + + if (buffer[3] == 1) { + /* This was indeed an IPv4 address. */ + so->state = DNS_SO_SOCKS_HANDSHAKE_DONE; + goto retry; + } + + if (buffer[3] != 4) { + error = ENOTSUP; + goto error; + } + + /* Expect receive twelve octets. This accounts for + * the remaining bytes assuming an IPv6 address is + * used. */ + dns_so_tcp_recv_expect(so, 12); + so->state++; /* FALL THROUGH */ + case DNS_SO_SOCKS_REQUEST_RECV_V6: + if ((error = dns_so_tcp_recv(so))) + goto error; + + so->state++; /* FALL THROUGH */ + case DNS_SO_SOCKS_HANDSHAKE_DONE: + /* We have not way to store the actual address used by + * the server. Then again, we don't really care. */ + + /* Restore the query. */ + dns_p_movptr(&so->query, &so->query_backup); + + /* Reset cursors. */ + so->qout = 0; + so->apos = 0; + so->alen = 0; + + /* SOCKS handshake is done. Proceed with the + * lookup. */ + so->state = DNS_SO_TCP_SEND; + goto retry; + default: + error = DNS_EUNKNOWN; + + goto error; + } /* switch() */ + +trash: + DNS_CARP("discarding packet"); + goto retry; +error: + switch (error) { + case DNS_EINTR: + goto retry; + case DNS_EINPROGRESS: + /* FALL THROUGH */ + case DNS_EALREADY: + /* FALL THROUGH */ +#if DNS_EWOULDBLOCK != DNS_EAGAIN + case DNS_EWOULDBLOCK: + /* FALL THROUGH */ +#endif + error = DNS_EAGAIN; + + break; + } /* switch() */ + + return error; +} /* dns_so_check() */ + + +struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) { + struct dns_packet *answer; + + switch (so->state) { + case DNS_SO_UDP_DONE: + case DNS_SO_TCP_DONE: + answer = so->answer; + so->answer = 0; + dns_trace_so_fetch(so->trace, answer, 0); + + return answer; + default: + *error = DNS_EUNKNOWN; + dns_trace_so_fetch(so->trace, NULL, *error); + + return 0; + } +} /* dns_so_fetch() */ + + +struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) { + struct dns_packet *A; + int error; + + if (!so->state) { + if ((error = dns_so_submit(so, Q, host))) + goto error; + } + + if ((error = dns_so_check(so))) + goto error; + + if (!(A = dns_so_fetch(so, &error))) + goto error; + + dns_so_reset(so); + + return A; +error: + *error_ = error; + + return 0; +} /* dns_so_query() */ + + +time_t dns_so_elapsed(struct dns_socket *so) { + return dns_elapsed(&so->elapsed); +} /* dns_so_elapsed() */ + + +void dns_so_clear(struct dns_socket *so) { + dns_so_closefds(so, DNS_SO_CLOSE_OLD); +} /* dns_so_clear() */ + + +static int dns_so_events2(struct dns_socket *so, enum dns_events type) { + int events = 0; + + switch (so->state) { + case DNS_SO_UDP_CONN: + case DNS_SO_UDP_SEND: + events |= DNS_POLLOUT; + + break; + case DNS_SO_UDP_RECV: + events |= DNS_POLLIN; + + break; + case DNS_SO_TCP_CONN: + case DNS_SO_TCP_SEND: + events |= DNS_POLLOUT; + + break; + case DNS_SO_TCP_RECV: + events |= DNS_POLLIN; + + break; + } /* switch() */ + + switch (type) { + case DNS_LIBEVENT: + return DNS_POLL2EV(events); + default: + return events; + } /* switch() */ +} /* dns_so_events2() */ + + +int dns_so_events(struct dns_socket *so) { + return dns_so_events2(so, so->opts.events); +} /* dns_so_events() */ + + +int dns_so_pollfd(struct dns_socket *so) { + switch (so->state) { + case DNS_SO_UDP_CONN: + case DNS_SO_UDP_SEND: + case DNS_SO_UDP_RECV: + return so->udp; + case DNS_SO_TCP_CONN: + case DNS_SO_TCP_SEND: + case DNS_SO_TCP_RECV: + return so->tcp; + } /* switch() */ + + return -1; +} /* dns_so_pollfd() */ + + +int dns_so_poll(struct dns_socket *so, int timeout) { + return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout); +} /* dns_so_poll() */ + + +const struct dns_stat *dns_so_stat(struct dns_socket *so) { + return &so->stat; +} /* dns_so_stat() */ + + +struct dns_trace *dns_so_trace(struct dns_socket *so) { + return so->trace; +} /* dns_so_trace() */ + + +void dns_so_settrace(struct dns_socket *so, struct dns_trace *trace) { + struct dns_trace *otrace = so->trace; + so->trace = dns_trace_acquire_p(trace); + dns_trace_close(otrace); +} /* dns_so_settrace() */ + + +/* + * R E S O L V E R R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +enum dns_res_state { + DNS_R_INIT, + DNS_R_GLUE, + DNS_R_SWITCH, /* (B)IND, (F)ILE, (C)ACHE */ + + DNS_R_FILE, /* Lookup in local hosts database */ + + DNS_R_CACHE, /* Lookup in application cache */ + DNS_R_SUBMIT, + DNS_R_CHECK, + DNS_R_FETCH, + + DNS_R_BIND, /* Lookup in the network */ + DNS_R_SEARCH, + DNS_R_HINTS, + DNS_R_ITERATE, + DNS_R_FOREACH_NS, + DNS_R_RESOLV0_NS, /* Prologue: Setup next frame and recurse */ + DNS_R_RESOLV1_NS, /* Epilog: Inspect answer */ + DNS_R_FOREACH_A, + DNS_R_QUERY_A, + DNS_R_FOREACH_AAAA, + DNS_R_QUERY_AAAA, + DNS_R_CNAME0_A, + DNS_R_CNAME1_A, + + DNS_R_FINISH, + DNS_R_SMART0_A, + DNS_R_SMART1_A, + DNS_R_DONE, + DNS_R_SERVFAIL, +}; /* enum dns_res_state */ + + +#define DNS_R_MAXDEPTH 8 +#define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1) + +struct dns_resolver { + struct dns_socket so; + + struct dns_resolv_conf *resconf; + struct dns_hosts *hosts; + struct dns_hints *hints; + struct dns_cache *cache; + struct dns_trace *trace; + + dns_atomic_t refcount; + + /* Reset zeroes everything below here. */ + + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + + enum dns_type qtype; + enum dns_class qclass; + + struct dns_clock elapsed; + + dns_resconf_i_t search; + + struct dns_rr_i smart; + + struct dns_packet *nodata; /* answer if nothing better */ + + unsigned sp; + + struct dns_res_frame { + enum dns_res_state state; + + int error; + int which; /* (B)IND, (F)ILE; index into resconf->lookup */ + int qflags; + + unsigned attempts; + + struct dns_packet *query, *answer, *hints; + + struct dns_rr_i hints_i, hints_j; + struct dns_rr hints_ns, ans_cname; + } stack[DNS_R_MAXDEPTH]; +}; /* struct dns_resolver */ + + +static int dns_res_tcp2type(int tcp) { + switch (tcp) { + case DNS_RESCONF_TCP_ONLY: + case DNS_RESCONF_TCP_SOCKS: + return SOCK_STREAM; + case DNS_RESCONF_TCP_DISABLE: + return SOCK_DGRAM; + default: + return 0; + } +} /* dns_res_tcp2type() */ + +struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) { + static const struct dns_resolver R_initializer + = { .refcount = 1, }; + struct dns_resolver *R = 0; + int type, error; + + /* + * Grab ref count early because the caller may have passed us a mortal + * reference, and we want to do the right thing if we return early + * from an error. + */ + if (resconf) + dns_resconf_acquire(resconf); + if (hosts) + dns_hosts_acquire(hosts); + if (hints) + dns_hints_acquire(hints); + if (cache) + dns_cache_acquire(cache); + + /* + * Don't try to load it ourselves because a NULL object might be an + * error from, say, dns_resconf_root(), and loading + * dns_resconf_local() by default would create undesirable surpises. + */ + if (!resconf || !hosts || !hints) { + if (!*_error) + *_error = EINVAL; + goto _error; + } + + if (!(R = malloc(sizeof *R))) + goto syerr; + + *R = R_initializer; + type = dns_res_tcp2type(resconf->options.tcp); + + if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error)) + goto error; + + R->resconf = resconf; + R->hosts = hosts; + R->hints = hints; + R->cache = cache; + + return R; +syerr: + error = dns_syerr(); +error: + *_error = error; +_error: + dns_res_close(R); + + dns_resconf_close(resconf); + dns_hosts_close(hosts); + dns_hints_close(hints); + dns_cache_close(cache); + + return 0; +} /* dns_res_open() */ + + +struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) { + struct dns_resolv_conf *resconf = 0; + struct dns_hosts *hosts = 0; + struct dns_hints *hints = 0; + struct dns_resolver *res = 0; + + if (!(resconf = dns_resconf_local(error))) + goto epilog; + + if (!(hosts = dns_hosts_local(error))) + goto epilog; + + if (!(hints = dns_hints_local(resconf, error))) + goto epilog; + + if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error))) + goto epilog; + +epilog: + dns_resconf_close(resconf); + dns_hosts_close(hosts); + dns_hints_close(hints); + + return res; +} /* dns_res_stub() */ + + +static void dns_res_frame_destroy(struct dns_resolver *R, struct dns_res_frame *frame) { + (void)R; + + dns_p_setptr(&frame->query, NULL); + dns_p_setptr(&frame->answer, NULL); + dns_p_setptr(&frame->hints, NULL); +} /* dns_res_frame_destroy() */ + + +static void dns_res_frame_init(struct dns_resolver *R, struct dns_res_frame *frame) { + memset(frame, '\0', sizeof *frame); + + /* + * NB: Can be invoked from dns_res_open, before R->resconf has been + * initialized. + */ + if (R->resconf) { + if (!R->resconf->options.recurse) + frame->qflags |= DNS_Q_RD; + if (R->resconf->options.edns0) + frame->qflags |= DNS_Q_EDNS0; + } +} /* dns_res_frame_init() */ + + +static void dns_res_frame_reset(struct dns_resolver *R, struct dns_res_frame *frame) { + dns_res_frame_destroy(R, frame); + dns_res_frame_init(R, frame); +} /* dns_res_frame_reset() */ + + +static dns_error_t dns_res_frame_prepare(struct dns_resolver *R, struct dns_res_frame *F, const char *qname, enum dns_type qtype, enum dns_class qclass) { + struct dns_packet *P = NULL; + + if (!(F < endof(R->stack))) + return DNS_EUNKNOWN; + + dns_p_movptr(&P, &F->query); + dns_res_frame_reset(R, F); + dns_p_movptr(&F->query, &P); + + return dns_q_make(&F->query, qname, qtype, qclass, F->qflags); +} /* dns_res_frame_prepare() */ + + +void dns_res_reset(struct dns_resolver *R) { + unsigned i; + + dns_so_reset(&R->so); + dns_p_setptr(&R->nodata, NULL); + + for (i = 0; i < lengthof(R->stack); i++) + dns_res_frame_destroy(R, &R->stack[i]); + + memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname)); + + for (i = 0; i < lengthof(R->stack); i++) + dns_res_frame_init(R, &R->stack[i]); +} /* dns_res_reset() */ + + +void dns_res_close(struct dns_resolver *R) { + if (!R || 1 < dns_res_release(R)) + return; + + dns_res_reset(R); + + dns_so_destroy(&R->so); + + dns_hints_close(R->hints); + dns_hosts_close(R->hosts); + dns_resconf_close(R->resconf); + dns_cache_close(R->cache); + dns_trace_close(R->trace); + + free(R); +} /* dns_res_close() */ + + +dns_refcount_t dns_res_acquire(struct dns_resolver *R) { + return dns_atomic_fetch_add(&R->refcount); +} /* dns_res_acquire() */ + + +dns_refcount_t dns_res_release(struct dns_resolver *R) { + return dns_atomic_fetch_sub(&R->refcount); +} /* dns_res_release() */ + + +struct dns_resolver *dns_res_mortal(struct dns_resolver *res) { + if (res) + dns_res_release(res); + return res; +} /* dns_res_mortal() */ + + +static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) { + size_t bufsiz = P0->end + P1->end; + struct dns_packet *P[3] = { P0, P1, 0 }; + struct dns_rr rr[3]; + int error, copy, i; + enum dns_section section; + +retry: + if (!(P[2] = dns_p_make(bufsiz, &error))) + goto error; + + dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) { + if ((error = dns_rr_copy(P[2], &rr[0], P[0]))) + goto error; + } + + for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) { + for (i = 0; i < 2; i++) { + dns_rr_foreach(&rr[i], P[i], .section = section) { + copy = 1; + + dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) { + if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) { + copy = 0; + + break; + } + } + + if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) { + if (error == DNS_ENOBUFS && bufsiz < 65535) { + dns_p_setptr(&P[2], NULL); + + bufsiz = DNS_PP_MAX(65535, bufsiz * 2); + + goto retry; + } + + goto error; + } + } /* foreach(rr) */ + } /* foreach(packet) */ + } /* foreach(section) */ + + return P[2]; +error: + *error_ = error; + + dns_p_free(P[2]); + + return 0; +} /* dns_res_merge() */ + + +static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; + struct dns_packet *P = dns_p_init(&P_instance.p, 512); + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + enum dns_type qtype; + struct dns_rr rr; + unsigned sp; + int error; + + if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error)) + || qlen >= sizeof qname) + return 0; + + if (!(qtype = dns_rr_type(12, Q))) + return 0; + + if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0))) + return 0; + + for (sp = 0; sp <= R->sp; sp++) { + if (!R->stack[sp].answer) + continue; + + dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) { + rr.section = DNS_S_AN; + + if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) + return 0; + } + } + + if (dns_p_count(P, DNS_S_AN) > 0) + goto copy; + + /* Otherwise, look for a CNAME */ + for (sp = 0; sp <= R->sp; sp++) { + if (!R->stack[sp].answer) + continue; + + dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) { + rr.section = DNS_S_AN; + + if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer))) + return 0; + } + } + + if (!dns_p_count(P, DNS_S_AN)) + return 0; + +copy: + return dns_p_copy(dns_p_make(P->end, &error), P); +} /* dns_res_glue() */ + + +/* + * Sort NS records by three criteria: + * + * 1) Whether glue is present. + * 2) Whether glue record is original or of recursive lookup. + * 3) Randomly shuffle records which share the above criteria. + * + * NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will + * be added during an iteration. + * + * FIXME: Only groks A glue, not AAAA glue. + */ +static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) { + _Bool glued[2] = { 0 }; + struct dns_rr x = { 0 }, y = { 0 }; + struct dns_ns ns; + int cmp, error; + + if (!(error = dns_ns_parse(&ns, a, P))) { + struct dns_rr_i I_instance = { 0 }; + + I_instance.section = (DNS_S_ALL & ~DNS_S_QD); + I_instance.name = ns.host; + I_instance.type = DNS_T_A; + glued[0] = !!dns_rr_grep(&x, 1, &I_instance, P, &error); + } + if (!(error = dns_ns_parse(&ns, b, P))) { + struct dns_rr_i I_instance = { 0 }; + + I_instance.section = (DNS_S_ALL & ~DNS_S_QD); + I_instance.name = ns.host; + I_instance.type = DNS_T_A; + glued[1] = !!dns_rr_grep(&y, 1, &I_instance, P, &error); + } + if ((cmp = glued[1] - glued[0])) { + return cmp; + } else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) { + return cmp; + } else { + return dns_rr_i_shuffle(a, b, i, P); + } +} /* dns_res_nameserv_cmp() */ + + +#define dgoto(sp, i) \ + do { R->stack[(sp)].state = (i); goto exec; } while (0) + +static int dns_res_exec(struct dns_resolver *R) { + struct dns_res_frame *F; + struct dns_packet *P; + union { + char host[DNS_D_MAXNAME + 1]; + char name[DNS_D_MAXNAME + 1]; + struct dns_ns ns; + struct dns_cname cname; + } u; + size_t len; + struct dns_rr rr; + int error; + +exec: + + F = &R->stack[R->sp]; + + switch (F->state) { + case DNS_R_INIT: + F->state++; /* FALL THROUGH */ + case DNS_R_GLUE: + if (R->sp == 0) + dgoto(R->sp, DNS_R_SWITCH); + + if (!F->query) + goto noquery; + + if (!(F->answer = dns_res_glue(R, F->query))) + dgoto(R->sp, DNS_R_SWITCH); + + if (!(len = dns_d_expand(u.name, sizeof u.name, 12, F->query, &error))) + goto error; + else if (len >= sizeof u.name) + goto toolong; + + dns_rr_foreach(&rr, F->answer, .name = u.name, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) { + dgoto(R->sp, DNS_R_FINISH); + } + + dns_rr_foreach(&rr, F->answer, .name = u.name, .type = DNS_T_CNAME, .section = DNS_S_AN) { + F->ans_cname = rr; + + dgoto(R->sp, DNS_R_CNAME0_A); + } + + F->state++; + case DNS_R_SWITCH: + while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) { + switch (R->resconf->lookup[F->which++]) { + case 'b': case 'B': + dgoto(R->sp, DNS_R_BIND); + case 'f': case 'F': + dgoto(R->sp, DNS_R_FILE); + case 'c': case 'C': + if (R->cache) + dgoto(R->sp, DNS_R_CACHE); + + break; + default: + break; + } + } + + /* + * FIXME: Examine more closely whether our logic is correct + * and DNS_R_SERVFAIL is the correct default response. + * + * Case 1: We got here because we never got an answer on the + * wire. All queries timed-out and we reached maximum + * attempts count. See DNS_R_FOREACH_NS. In that case + * DNS_R_SERVFAIL is the correct state, unless we want to + * return DNS_ETIMEDOUT. + * + * Case 2: We were a stub resolver and got an unsatisfactory + * answer (empty ANSWER section) which caused us to jump + * back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We + * return the answer returned from the wire, which we + * stashed in R->nodata. + * + * Case 3: We reached maximum attempts count as in case #1, + * but never got an authoritative response which caused us + * to short-circuit. See end of DNS_R_QUERY_A case. We + * should probably prepare R->nodata as in case #2. + */ + if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */ + dns_p_movptr(&F->answer, &R->nodata); + dgoto(R->sp, DNS_R_FINISH); + } + + dgoto(R->sp, DNS_R_SERVFAIL); + case DNS_R_FILE: + if (R->sp > 0) { + if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) + goto error; + + if (dns_p_count(F->answer, DNS_S_AN) > 0) + dgoto(R->sp, DNS_R_FINISH); + + dns_p_setptr(&F->answer, NULL); + } else { + R->search = 0; + + while ((len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) { + if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags))) + goto error; + + if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error))) + goto error; + + if (dns_p_count(F->answer, DNS_S_AN) > 0) + dgoto(R->sp, DNS_R_FINISH); + + dns_p_setptr(&F->answer, NULL); + } + } + + dgoto(R->sp, DNS_R_SWITCH); + case DNS_R_CACHE: + error = 0; + + if (!F->query && (error = dns_q_make(&F->query, R->qname, R->qtype, R->qclass, F->qflags))) + goto error; + + if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) { + if (dns_p_count(F->answer, DNS_S_AN) > 0) + dgoto(R->sp, DNS_R_FINISH); + + dns_p_setptr(&F->answer, NULL); + + dgoto(R->sp, DNS_R_SWITCH); + } else if (error) + goto error; + + F->state++; /* FALL THROUGH */ + case DNS_R_SUBMIT: + if ((error = R->cache->submit(F->query, R->cache))) + goto error; + + F->state++; /* FALL THROUGH */ + case DNS_R_CHECK: + if ((error = R->cache->check(R->cache))) + goto error; + + F->state++; /* FALL THROUGH */ + case DNS_R_FETCH: + error = 0; + + if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) { + if (dns_p_count(F->answer, DNS_S_AN) > 0) + dgoto(R->sp, DNS_R_FINISH); + + dns_p_setptr(&F->answer, NULL); + + dgoto(R->sp, DNS_R_SWITCH); + } else if (error) + goto error; + + dgoto(R->sp, DNS_R_SWITCH); + case DNS_R_BIND: + if (R->sp > 0) { + if (!F->query) + goto noquery; + + dgoto(R->sp, DNS_R_HINTS); + } + + R->search = 0; + + F->state++; /* FALL THROUGH */ + case DNS_R_SEARCH: + /* + * XXX: We probably should only apply the domain search + * algorithm if R->sp == 0. + */ + if (!(len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) + dgoto(R->sp, DNS_R_SWITCH); + + if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags))) + goto error; + + F->state++; /* FALL THROUGH */ + case DNS_R_HINTS: + if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error))) + goto error; + + F->state++; /* FALL THROUGH */ + case DNS_R_ITERATE: + dns_rr_i_init(&F->hints_i); + + F->hints_i.section = DNS_S_AUTHORITY; + F->hints_i.type = DNS_T_NS; + F->hints_i.sort = &dns_res_nameserv_cmp; + F->hints_i.args[0] = F->hints->end; + + F->state++; /* FALL THROUGH */ + case DNS_R_FOREACH_NS: + dns_rr_i_save(&F->hints_i); + + /* Load our next nameserver host. */ + if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) { + if (++F->attempts < R->resconf->options.attempts) + dgoto(R->sp, DNS_R_ITERATE); + + dgoto(R->sp, DNS_R_SWITCH); + } + + dns_rr_i_init(&F->hints_j); + + /* Assume there are glue records */ + dgoto(R->sp, DNS_R_FOREACH_A); + case DNS_R_RESOLV0_NS: + /* Have we reached our max depth? */ + if (&F[1] >= endof(R->stack)) + dgoto(R->sp, DNS_R_FOREACH_NS); + + if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) + goto error; + if ((error = dns_res_frame_prepare(R, &F[1], u.ns.host, DNS_T_A, DNS_C_IN))) + goto error; + + F->state++; + + dgoto(++R->sp, DNS_R_INIT); + case DNS_R_RESOLV1_NS: + if (!(len = dns_d_expand(u.host, sizeof u.host, 12, F[1].query, &error))) + goto error; + else if (len >= sizeof u.host) + goto toolong; + + dns_rr_foreach(&rr, F[1].answer, .name = u.host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) { + rr.section = DNS_S_AR; + + if ((error = dns_rr_copy(F->hints, &rr, F[1].answer))) + goto error; + + dns_rr_i_rewind(&F->hints_i); /* Now there's glue. */ + } + + dgoto(R->sp, DNS_R_FOREACH_NS); + case DNS_R_FOREACH_A: { + struct dns_a a; + struct sockaddr_in sin; + + /* + * NOTE: Iterator initialized in DNS_R_FOREACH_NS because + * this state is re-entrant, but we need to reset + * .name to a valid pointer each time. + */ + if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) + goto error; + + F->hints_j.name = u.ns.host; + F->hints_j.type = DNS_T_A; + F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; + + if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { + if (!dns_rr_i_count(&F->hints_j)) { + /* Check if we have in fact servers + with an IPv6 address. */ + dns_rr_i_init(&F->hints_j); + F->hints_j.name = u.ns.host; + F->hints_j.type = DNS_T_AAAA; + F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; + if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { + /* We do. Reinitialize + iterator and handle it. */ + dns_rr_i_init(&F->hints_j); + dgoto(R->sp, DNS_R_FOREACH_AAAA); + } + + dgoto(R->sp, DNS_R_RESOLV0_NS); + } + + dgoto(R->sp, DNS_R_FOREACH_NS); + } + + if ((error = dns_a_parse(&a, &rr, F->hints))) + goto error; + + memset(&sin, '\0', sizeof sin); /* NB: silence valgrind */ + sin.sin_family = AF_INET; + sin.sin_addr = a.addr; + if (R->sp == 0) + sin.sin_port = dns_hints_port(R->hints, AF_INET, &sin.sin_addr); + else + sin.sin_port = htons(53); + + if (DNS_DEBUG) { + char addr[INET_ADDRSTRLEN + 1]; + dns_a_print(addr, sizeof addr, &a); + dns_header(F->query)->qid = dns_so_mkqid(&R->so); + DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp); + } + + dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin); + + if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin))) + goto error; + + F->state++; + } /* FALL THROUGH */ + case DNS_R_QUERY_A: + if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf)) + dgoto(R->sp, DNS_R_FOREACH_A); + + error = dns_so_check(&R->so); + if (R->so.state != DNS_SO_SOCKS_CONN && error == ECONNREFUSED) + dgoto(R->sp, DNS_R_FOREACH_A); + else if (error) + goto error; + + if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error))) + goto error; + + if (DNS_DEBUG) { + DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp); + } + + if (dns_p_rcode(F->answer) == DNS_RC_FORMERR || + dns_p_rcode(F->answer) == DNS_RC_NOTIMP || + dns_p_rcode(F->answer) == DNS_RC_BADVERS) { + /* Temporarily disable EDNS0 and try again. */ + if (F->qflags & DNS_Q_EDNS0) { + F->qflags &= ~DNS_Q_EDNS0; + if ((error = dns_q_remake(&F->query, F->qflags))) + goto error; + + dgoto(R->sp, DNS_R_FOREACH_A); + } + } + + if ((error = dns_rr_parse(&rr, 12, F->query))) + goto error; + + if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error))) + goto error; + else if (len >= sizeof u.name) + goto toolong; + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) { + dgoto(R->sp, DNS_R_FINISH); /* Found */ + } + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) { + F->ans_cname = rr; + + dgoto(R->sp, DNS_R_CNAME0_A); + } + + /* + * XXX: The condition here should probably check whether + * R->sp == 0, because DNS_R_SEARCH runs regardless of + * options.recurse. See DNS_R_BIND. + */ + if (!R->resconf->options.recurse) { + /* Make first answer our tentative answer */ + if (!R->nodata) + dns_p_movptr(&R->nodata, &F->answer); + + dgoto(R->sp, DNS_R_SEARCH); + } + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) { + dns_p_movptr(&F->hints, &F->answer); + + dgoto(R->sp, DNS_R_ITERATE); + } + + /* XXX: Should this go further up? */ + if (dns_header(F->answer)->aa) + dgoto(R->sp, DNS_R_FINISH); + + /* XXX: Should we copy F->answer to R->nodata? */ + + dgoto(R->sp, DNS_R_FOREACH_A); + case DNS_R_FOREACH_AAAA: { + struct dns_aaaa aaaa; + struct sockaddr_in6 sin6; + + /* + * NOTE: Iterator initialized in DNS_R_FOREACH_NS because + * this state is re-entrant, but we need to reset + * .name to a valid pointer each time. + */ + if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints))) + goto error; + + F->hints_j.name = u.ns.host; + F->hints_j.type = DNS_T_AAAA; + F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; + + if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { + if (!dns_rr_i_count(&F->hints_j)) { + /* Check if we have in fact servers + with an IPv4 address. */ + dns_rr_i_init(&F->hints_j); + F->hints_j.name = u.ns.host; + F->hints_j.type = DNS_T_A; + F->hints_j.section = DNS_S_ALL & ~DNS_S_QD; + if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) { + /* We do. Reinitialize + iterator and handle it. */ + dns_rr_i_init(&F->hints_j); + dgoto(R->sp, DNS_R_FOREACH_A); + } + + dgoto(R->sp, DNS_R_RESOLV0_NS); + } + + dgoto(R->sp, DNS_R_FOREACH_NS); + } + + if ((error = dns_aaaa_parse(&aaaa, &rr, F->hints))) + goto error; + + memset(&sin6, '\0', sizeof sin6); /* NB: silence valgrind */ + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = aaaa.addr; + if (R->sp == 0) + sin6.sin6_port = dns_hints_port(R->hints, AF_INET, &sin6.sin6_addr); + else + sin6.sin6_port = htons(53); + + if (DNS_DEBUG) { + char addr[INET6_ADDRSTRLEN + 1]; + dns_aaaa_print(addr, sizeof addr, &aaaa); + dns_header(F->query)->qid = dns_so_mkqid(&R->so); + DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp); + } + + dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin6); + + if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin6))) + goto error; + + F->state++; + } /* FALL THROUGH */ + case DNS_R_QUERY_AAAA: + if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf)) + dgoto(R->sp, DNS_R_FOREACH_AAAA); + + error = dns_so_check(&R->so); + if (error == ECONNREFUSED) + dgoto(R->sp, DNS_R_FOREACH_AAAA); + else if (error) + goto error; + + if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error))) + goto error; + + if (DNS_DEBUG) { + DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp); + } + + if (dns_p_rcode(F->answer) == DNS_RC_FORMERR || + dns_p_rcode(F->answer) == DNS_RC_NOTIMP || + dns_p_rcode(F->answer) == DNS_RC_BADVERS) { + /* Temporarily disable EDNS0 and try again. */ + if (F->qflags & DNS_Q_EDNS0) { + F->qflags &= ~DNS_Q_EDNS0; + if ((error = dns_q_remake(&F->query, F->qflags))) + goto error; + + dgoto(R->sp, DNS_R_FOREACH_AAAA); + } + } + + if ((error = dns_rr_parse(&rr, 12, F->query))) + goto error; + + if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error))) + goto error; + else if (len >= sizeof u.name) + goto toolong; + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) { + dgoto(R->sp, DNS_R_FINISH); /* Found */ + } + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) { + F->ans_cname = rr; + + dgoto(R->sp, DNS_R_CNAME0_A); + } + + /* + * XXX: The condition here should probably check whether + * R->sp == 0, because DNS_R_SEARCH runs regardless of + * options.recurse. See DNS_R_BIND. + */ + if (!R->resconf->options.recurse) { + /* Make first answer our tentative answer */ + if (!R->nodata) + dns_p_movptr(&R->nodata, &F->answer); + + dgoto(R->sp, DNS_R_SEARCH); + } + + dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) { + dns_p_movptr(&F->hints, &F->answer); + + dgoto(R->sp, DNS_R_ITERATE); + } + + /* XXX: Should this go further up? */ + if (dns_header(F->answer)->aa) + dgoto(R->sp, DNS_R_FINISH); + + /* XXX: Should we copy F->answer to R->nodata? */ + + dgoto(R->sp, DNS_R_FOREACH_AAAA); + case DNS_R_CNAME0_A: + if (&F[1] >= endof(R->stack)) + dgoto(R->sp, DNS_R_FINISH); + + if ((error = dns_cname_parse(&u.cname, &F->ans_cname, F->answer))) + goto error; + if ((error = dns_res_frame_prepare(R, &F[1], u.cname.host, dns_rr_type(12, F->query), DNS_C_IN))) + goto error; + + F->state++; + + dgoto(++R->sp, DNS_R_INIT); + case DNS_R_CNAME1_A: + if (!(P = dns_res_merge(F->answer, F[1].answer, &error))) + goto error; + + dns_p_setptr(&F->answer, P); + + dgoto(R->sp, DNS_R_FINISH); + case DNS_R_FINISH: + if (!F->answer) + goto noanswer; + + if (!R->resconf->options.smart || R->sp > 0) + dgoto(R->sp, DNS_R_DONE); + + R->smart.section = DNS_S_AN; + R->smart.type = R->qtype; + + dns_rr_i_init(&R->smart); + + F->state++; /* FALL THROUGH */ + case DNS_R_SMART0_A: + if (&F[1] >= endof(R->stack)) + dgoto(R->sp, DNS_R_DONE); + + while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) { + union { + struct dns_ns ns; + struct dns_mx mx; + struct dns_srv srv; + } rd; + const char *qname; + enum dns_type qtype; + enum dns_class qclass; + + switch (rr.type) { + case DNS_T_NS: + if ((error = dns_ns_parse(&rd.ns, &rr, F->answer))) + goto error; + + qname = rd.ns.host; + qtype = DNS_T_A; + qclass = DNS_C_IN; + + break; + case DNS_T_MX: + if ((error = dns_mx_parse(&rd.mx, &rr, F->answer))) + goto error; + + qname = rd.mx.host; + qtype = DNS_T_A; + qclass = DNS_C_IN; + + break; + case DNS_T_SRV: + if ((error = dns_srv_parse(&rd.srv, &rr, F->answer))) + goto error; + + qname = rd.srv.target; + qtype = DNS_T_A; + qclass = DNS_C_IN; + + break; + default: + continue; + } /* switch() */ + + if ((error = dns_res_frame_prepare(R, &F[1], qname, qtype, qclass))) + goto error; + + F->state++; + + dgoto(++R->sp, DNS_R_INIT); + } /* while() */ + + /* + * NOTE: SMTP specification says to fallback to A record. + * + * XXX: Should we add a mock MX answer? + */ + if (R->qtype == DNS_T_MX && R->smart.state.count == 0) { + if ((error = dns_res_frame_prepare(R, &F[1], R->qname, DNS_T_A, DNS_C_IN))) + goto error; + + R->smart.state.count++; + F->state++; + + dgoto(++R->sp, DNS_R_INIT); + } + + dgoto(R->sp, DNS_R_DONE); + case DNS_R_SMART1_A: + if (!F[1].answer) + goto noanswer; + + /* + * FIXME: For CNAME chains (which are typically illegal in + * this context), we should rewrite the record host name + * to the original smart qname. All the user cares about + * is locating that A/AAAA record. + */ + dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) { + rr.section = DNS_S_AR; + + if (dns_rr_exists(&rr, F[1].answer, F->answer)) + continue; + + while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) { + if (error != DNS_ENOBUFS) + goto error; + if ((error = dns_p_grow(&F->answer))) + goto error; + } + } + + dgoto(R->sp, DNS_R_SMART0_A); + case DNS_R_DONE: + if (!F->answer) + goto noanswer; + + if (R->sp > 0) + dgoto(--R->sp, F[-1].state); + + break; + case DNS_R_SERVFAIL: + if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error))) + goto error; + + dns_header(F->answer)->qr = 1; + dns_header(F->answer)->rcode = DNS_RC_SERVFAIL; + + if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0))) + goto error; + + dgoto(R->sp, DNS_R_DONE); + default: + error = EINVAL; + + goto error; + } /* switch () */ + + return 0; +noquery: + error = DNS_ENOQUERY; + + goto error; +noanswer: + error = DNS_ENOANSWER; + + goto error; +toolong: + error = DNS_EILLEGAL; + + /* FALL THROUGH */ +error: + return error; +} /* dns_res_exec() */ + +#undef goto + + +void dns_res_clear(struct dns_resolver *R) { + switch (R->stack[R->sp].state) { + case DNS_R_CHECK: + R->cache->clear(R->cache); + break; + default: + dns_so_clear(&R->so); + break; + } +} /* dns_res_clear() */ + + +static int dns_res_events2(struct dns_resolver *R, enum dns_events type) { + int events; + + switch (R->stack[R->sp].state) { + case DNS_R_CHECK: + events = R->cache->events(R->cache); + + return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events; + default: + return dns_so_events2(&R->so, type); + } +} /* dns_res_events2() */ + + +int dns_res_events(struct dns_resolver *R) { + return dns_res_events2(R, R->so.opts.events); +} /* dns_res_events() */ + + +int dns_res_pollfd(struct dns_resolver *R) { + switch (R->stack[R->sp].state) { + case DNS_R_CHECK: + return R->cache->pollfd(R->cache); + default: + return dns_so_pollfd(&R->so); + } +} /* dns_res_pollfd() */ + + +time_t dns_res_timeout(struct dns_resolver *R) { + time_t elapsed; + + switch (R->stack[R->sp].state) { +#if 0 + case DNS_R_QUERY_AAAA: +#endif + case DNS_R_QUERY_A: + elapsed = dns_so_elapsed(&R->so); + + if (elapsed <= dns_resconf_timeout(R->resconf)) + return R->resconf->options.timeout - elapsed; + + break; + default: + break; + } /* switch() */ + + /* + * NOTE: We're not in a pollable state, or the user code hasn't + * called dns_res_check properly. The calling code is probably + * broken. Put them into a slow-burn pattern. + */ + return 1; +} /* dns_res_timeout() */ + + +time_t dns_res_elapsed(struct dns_resolver *R) { + return dns_elapsed(&R->elapsed); +} /* dns_res_elapsed() */ + + +int dns_res_poll(struct dns_resolver *R, int timeout) { + return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout); +} /* dns_res_poll() */ + + +int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) { + dns_res_reset(R); + + /* Don't anchor; that can conflict with searchlist generation. */ + dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0); + + R->qtype = qtype; + R->qclass = qclass; + + dns_begin(&R->elapsed); + + dns_trace_res_submit(R->trace, R->qname, R->qtype, R->qclass, 0); + + return 0; +} /* dns_res_submit2() */ + + +int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) { + return dns_res_submit2(R, qname, strlen(qname), qtype, qclass); +} /* dns_res_submit() */ + + +int dns_res_check(struct dns_resolver *R) { + int error; + + if (R->stack[0].state != DNS_R_DONE) { + if ((error = dns_res_exec(R))) + return error; + } + + return 0; +} /* dns_res_check() */ + + +struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *_error) { + struct dns_packet *P = NULL; + int error; + + if (R->stack[0].state != DNS_R_DONE) { + error = DNS_EUNKNOWN; + goto error; + } + + if (!dns_p_movptr(&P, &R->stack[0].answer)) { + error = DNS_EFETCHED; + goto error; + } + + dns_trace_res_fetch(R->trace, P, 0); + + return P; +error: + *_error = error; + dns_trace_res_fetch(R->trace, NULL, error); + return NULL; +} /* dns_res_fetch() */ + + +static struct dns_packet *dns_res_fetch_and_study(struct dns_resolver *R, int *_error) { + struct dns_packet *P = NULL; + int error; + + if (!(P = dns_res_fetch(R, &error))) + goto error; + if ((error = dns_p_study(P))) + goto error; + + return P; +error: + *_error = error; + + dns_p_free(P); + + return NULL; +} /* dns_res_fetch_and_study() */ + + +struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) { + int error; + + if ((error = dns_res_submit(res, qname, qtype, qclass))) + goto error; + + while ((error = dns_res_check(res))) { + if (dns_res_elapsed(res) > timeout) + error = DNS_ETIMEDOUT; + + if (error != DNS_EAGAIN) + goto error; + + if ((error = dns_res_poll(res, 1))) + goto error; + } + + return dns_res_fetch(res, error_); +error: + *error_ = error; + + return 0; +} /* dns_res_query() */ + + +const struct dns_stat *dns_res_stat(struct dns_resolver *res) { + return dns_so_stat(&res->so); +} /* dns_res_stat() */ + + +void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) { + dns_hints_acquire(hints); /* acquire first in case same hints object */ + dns_hints_close(res->hints); + res->hints = hints; +} /* dns_res_sethints() */ + + +struct dns_trace *dns_res_trace(struct dns_resolver *res) { + return res->trace; +} /* dns_res_trace() */ + + +void dns_res_settrace(struct dns_resolver *res, struct dns_trace *trace) { + struct dns_trace *otrace = res->trace; + res->trace = dns_trace_acquire_p(trace); + dns_trace_close(otrace); + dns_so_settrace(&res->so, trace); +} /* dns_res_settrace() */ + + +/* + * A D D R I N F O R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_addrinfo { + struct addrinfo hints; + struct dns_resolver *res; + struct dns_trace *trace; + + char qname[DNS_D_MAXNAME + 1]; + enum dns_type qtype; + unsigned short qport, port; + + struct { + unsigned long todo; + int state; + int atype; + enum dns_type qtype; + } af; + + struct dns_packet *answer; + struct dns_packet *glue; + + struct dns_rr_i i, g; + struct dns_rr rr; + + char cname[DNS_D_MAXNAME + 1]; + char i_cname[DNS_D_MAXNAME + 1], g_cname[DNS_D_MAXNAME + 1]; + + int g_depth; + + int state; + int found; + + struct dns_stat st; +}; /* struct dns_addrinfo */ + + +#define DNS_AI_AFMAX 32 +#define DNS_AI_AF2INDEX(af) (1UL << ((af) - 1)) + +static inline unsigned long dns_ai_af2index(int af) { + dns_static_assert(dns_same_type(unsigned long, DNS_AI_AF2INDEX(1), 1), "internal type mismatch"); + dns_static_assert(dns_same_type(unsigned long, ((struct dns_addrinfo *)0)->af.todo, 1), "internal type mismatch"); + + return (af > 0 && af <= DNS_AI_AFMAX)? DNS_AI_AF2INDEX(af) : 0; +} + +static int dns_ai_setaf(struct dns_addrinfo *ai, int af, int qtype) { + ai->af.atype = af; + ai->af.qtype = qtype; + + ai->af.todo &= ~dns_ai_af2index(af); + + return af; +} /* dns_ai_setaf() */ + +#define DNS_SM_RESTORE \ + do { pc = 0xff & (ai->af.state >> 0); i = 0xff & (ai->af.state >> 8); } while (0) +#define DNS_SM_SAVE \ + do { ai->af.state = ((0xff & pc) << 0) | ((0xff & i) << 8); } while (0) + +static int dns_ai_nextaf(struct dns_addrinfo *ai) { + int i, pc; + + dns_static_assert(AF_UNSPEC == 0, "AF_UNSPEC constant not 0"); + dns_static_assert(AF_INET <= DNS_AI_AFMAX, "AF_INET constant too large"); + dns_static_assert(AF_INET6 <= DNS_AI_AFMAX, "AF_INET6 constant too large"); + + DNS_SM_ENTER; + + if (ai->res) { + /* + * NB: On OpenBSD, at least, the types of entries resolved + * is the intersection of the /etc/resolv.conf families and + * the families permitted by the .ai_type hint. So if + * /etc/resolv.conf has "family inet4" and .ai_type + * is AF_INET6, then the address ::1 will return 0 entries + * even if AI_NUMERICHOST is specified in .ai_flags. + */ + while (i < (int)lengthof(ai->res->resconf->family)) { + int af = ai->res->resconf->family[i++]; + + if (af == AF_UNSPEC) { + DNS_SM_EXIT; + } else if (af < 0 || af > DNS_AI_AFMAX) { + continue; + } else if (!(DNS_AI_AF2INDEX(af) & ai->af.todo)) { + continue; + } else if (af == AF_INET) { + DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A)); + } else if (af == AF_INET6) { + DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA)); + } + } + } else { + /* + * NB: If we get here than AI_NUMERICFLAGS should be set and + * order shouldn't matter. + */ + if (DNS_AI_AF2INDEX(AF_INET) & ai->af.todo) + DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A)); + if (DNS_AI_AF2INDEX(AF_INET6) & ai->af.todo) + DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA)); + } + + DNS_SM_LEAVE; + + return dns_ai_setaf(ai, AF_UNSPEC, 0); +} /* dns_ai_nextaf() */ + +#undef DNS_SM_RESTORE +#undef DNS_SM_SAVE + +static enum dns_type dns_ai_qtype(struct dns_addrinfo *ai) { + return (ai->qtype)? ai->qtype : ai->af.qtype; +} /* dns_ai_qtype() */ + +/* JW: This is not defined on mingw. */ +#ifndef AI_NUMERICSERV +#define AI_NUMERICSERV 0 +#endif + +static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) { + const char *cp = serv; + unsigned long n = 0; + + while (*cp >= '0' && *cp <= '9' && n < 65536) { + n *= 10; + n += *cp++ - '0'; + } + + if (*cp == '\0') { + if (cp == serv || n >= 65536) + return DNS_ESERVICE; + + *port = n; + + return 0; + } + + if (hints->ai_flags & AI_NUMERICSERV) + return DNS_ESERVICE; + + /* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */ + + return DNS_ESERVICE; +} /* dns_ai_parseport() */ + + +struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *_error) { + static const struct dns_addrinfo ai_initializer; + struct dns_addrinfo *ai; + int error; + + if (res) { + dns_res_acquire(res); + } else if (!(hints->ai_flags & AI_NUMERICHOST)) { + /* + * NOTE: it's assumed that *_error is set from a previous + * API function call, such as dns_res_stub(). Should change + * this semantic, but it's applied elsewhere, too. + */ + if (!*_error) + *_error = EINVAL; + return NULL; + } + + if (!(ai = malloc(sizeof *ai))) + goto syerr; + + *ai = ai_initializer; + ai->hints = *hints; + + ai->res = res; + res = NULL; + + if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname)) + { error = ENAMETOOLONG; goto error; } + + ai->qtype = qtype; + ai->qport = 0; + + if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints))) + goto error; + ai->port = ai->qport; + + /* + * FIXME: If an explicit A or AAAA record type conflicts with + * .ai_family or with resconf.family (i.e. AAAA specified but + * AF_INET6 not in interection of .ai_family and resconf.family), + * then what? + */ + switch (ai->qtype) { + case DNS_T_A: + ai->af.todo = DNS_AI_AF2INDEX(AF_INET); + break; + case DNS_T_AAAA: + ai->af.todo = DNS_AI_AF2INDEX(AF_INET6); + break; + default: /* 0, MX, SRV, etc */ + switch (ai->hints.ai_family) { + case AF_UNSPEC: + ai->af.todo = DNS_AI_AF2INDEX(AF_INET) | DNS_AI_AF2INDEX(AF_INET6); + break; + case AF_INET: + ai->af.todo = DNS_AI_AF2INDEX(AF_INET); + break; + case AF_INET6: + ai->af.todo = DNS_AI_AF2INDEX(AF_INET6); + break; + default: + break; + } + } + + return ai; +syerr: + error = dns_syerr(); +error: + *_error = error; + + dns_ai_close(ai); + dns_res_close(res); + + return NULL; +} /* dns_ai_open() */ + + +void dns_ai_close(struct dns_addrinfo *ai) { + if (!ai) + return; + + dns_res_close(ai->res); + dns_trace_close(ai->trace); + + if (ai->answer != ai->glue) + dns_p_free(ai->glue); + + dns_p_free(ai->answer); + free(ai); +} /* dns_ai_close() */ + + +static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) { + union u { + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + struct sockaddr_storage ss; + } addr; + const char *cname; + size_t clen; + + switch (type) { + case DNS_T_A: + memset(&addr.sin, '\0', sizeof addr.sin); + + addr.sin.sin_family = AF_INET; + addr.sin.sin_port = htons(ai->port); + + memcpy(&addr.sin.sin_addr, any, sizeof addr.sin.sin_addr); + + break; + case DNS_T_AAAA: + memset(&addr.sin6, '\0', sizeof addr.sin6); + + addr.sin6.sin6_family = AF_INET6; + addr.sin6.sin6_port = htons(ai->port); + + memcpy(&addr.sin6.sin6_addr, any, sizeof addr.sin6.sin6_addr); + + break; + default: + return EINVAL; + } /* switch() */ + + if (ai->hints.ai_flags & AI_CANONNAME) { + cname = (*ai->cname)? ai->cname : ai->qname; + clen = strlen(cname); + } else { + cname = NULL; + clen = 0; + } + + if (!(*ent = malloc(sizeof **ent + dns_sa_len(&addr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0)))) + return dns_syerr(); + + memset(*ent, '\0', sizeof **ent); + + (*ent)->ai_family = addr.ss.ss_family; + (*ent)->ai_socktype = ai->hints.ai_socktype; + (*ent)->ai_protocol = ai->hints.ai_protocol; + + (*ent)->ai_addr = memcpy((unsigned char *)*ent + sizeof **ent, &addr, dns_sa_len(&addr)); + (*ent)->ai_addrlen = dns_sa_len(&addr); + + if (ai->hints.ai_flags & AI_CANONNAME) + (*ent)->ai_canonname = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(&addr), cname, clen + 1); + + ai->found++; + + return 0; +} /* dns_ai_setent() */ + + +enum dns_ai_state { + DNS_AI_S_INIT, + DNS_AI_S_NEXTAF, + DNS_AI_S_NUMERIC, + DNS_AI_S_SUBMIT, + DNS_AI_S_CHECK, + DNS_AI_S_FETCH, + DNS_AI_S_FOREACH_I, + DNS_AI_S_INIT_G, + DNS_AI_S_ITERATE_G, + DNS_AI_S_FOREACH_G, + DNS_AI_S_SUBMIT_G, + DNS_AI_S_CHECK_G, + DNS_AI_S_FETCH_G, + DNS_AI_S_DONE, +}; /* enum dns_ai_state */ + +#define dns_ai_goto(which) do { ai->state = (which); goto exec; } while (0) + +int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) { + struct dns_packet *ans, *glue; + struct dns_rr rr; + char qname[DNS_D_MAXNAME + 1]; + union dns_any any; + size_t qlen, clen; + int error; + + *ent = 0; + +exec: + + switch (ai->state) { + case DNS_AI_S_INIT: + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_NEXTAF: + if (!dns_ai_nextaf(ai)) + dns_ai_goto(DNS_AI_S_DONE); + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_NUMERIC: + if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) { + if (ai->af.atype == AF_INET) { + ai->state = DNS_AI_S_NEXTAF; + return dns_ai_setent(ent, &any, DNS_T_A, ai); + } else { + dns_ai_goto(DNS_AI_S_NEXTAF); + } + } + + if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) { + if (ai->af.atype == AF_INET6) { + ai->state = DNS_AI_S_NEXTAF; + return dns_ai_setent(ent, &any, DNS_T_AAAA, ai); + } else { + dns_ai_goto(DNS_AI_S_NEXTAF); + } + } + + if (ai->hints.ai_flags & AI_NUMERICHOST) + dns_ai_goto(DNS_AI_S_NEXTAF); + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_SUBMIT: + assert(ai->res); + + if ((error = dns_res_submit(ai->res, ai->qname, dns_ai_qtype(ai), DNS_C_IN))) + return error; + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_CHECK: + if ((error = dns_res_check(ai->res))) + return error; + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_FETCH: + if (!(ans = dns_res_fetch_and_study(ai->res, &error))) + return error; + if (ai->glue != ai->answer) + dns_p_free(ai->glue); + ai->glue = dns_p_movptr(&ai->answer, &ans); + + /* Search generator may have changed the qname. */ + if (!(qlen = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error))) + return error; + else if (qlen >= sizeof qname) + return DNS_EILLEGAL; + if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, qlen, ai->answer, &error)) + return error; + + dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname); + dns_rr_i_init(&ai->i); + ai->i.section = DNS_S_AN; + ai->i.name = ai->i_cname; + ai->i.type = dns_ai_qtype(ai); + ai->i.sort = &dns_rr_i_order; + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_FOREACH_I: + if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error)) + dns_ai_goto(DNS_AI_S_NEXTAF); + + if ((error = dns_any_parse(&any, &rr, ai->answer))) + return error; + + ai->port = ai->qport; + + switch (rr.type) { + case DNS_T_A: + case DNS_T_AAAA: + return dns_ai_setent(ent, &any, rr.type, ai); + default: + if (!(clen = dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type))) + dns_ai_goto(DNS_AI_S_FOREACH_I); + + /* + * Find the "real" canonical name. Some authorities + * publish aliases where an RFC defines a canonical + * name. We trust that the resolver followed any + * CNAME chains on it's own, regardless of whether + * the "smart" option is enabled. + */ + if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, clen, ai->answer, &error)) + return error; + + if (rr.type == DNS_T_SRV) + ai->port = any.srv.port; + + break; + } /* switch() */ + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_INIT_G: + ai->g_depth = 0; + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_ITERATE_G: + dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname); + dns_rr_i_init(&ai->g); + ai->g.section = DNS_S_ALL & ~DNS_S_QD; + ai->g.name = ai->g_cname; + ai->g.type = ai->af.qtype; + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_FOREACH_G: + if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) { + if (dns_rr_i_count(&ai->g) > 0) + dns_ai_goto(DNS_AI_S_FOREACH_I); + else + dns_ai_goto(DNS_AI_S_SUBMIT_G); + } + + if ((error = dns_any_parse(&any, &rr, ai->glue))) + return error; + + return dns_ai_setent(ent, &any, rr.type, ai); + case DNS_AI_S_SUBMIT_G: + { + struct dns_rr_i I_instance = { 0 }; + + I_instance.section = DNS_S_QD; + I_instance.name = ai->g.name; + I_instance.type = ai->g.type; + /* skip if already queried */ + if (dns_rr_grep(&rr, 1, &I_instance, ai->glue, &error)) + dns_ai_goto(DNS_AI_S_FOREACH_I); + /* skip if we recursed (CNAME chains should have been handled in the resolver) */ + if (++ai->g_depth > 1) + dns_ai_goto(DNS_AI_S_FOREACH_I); + + if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN))) + return error; + + ai->state++; + } /* FALL THROUGH */ + case DNS_AI_S_CHECK_G: + if ((error = dns_res_check(ai->res))) + return error; + + ai->state++; /* FALL THROUGH */ + case DNS_AI_S_FETCH_G: + if (!(ans = dns_res_fetch_and_study(ai->res, &error))) + return error; + + glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error); + dns_p_setptr(&ans, NULL); + if (!glue) + return error; + + if (ai->glue != ai->answer) + dns_p_free(ai->glue); + ai->glue = glue; + + if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->g.name, strlen(ai->g.name), ai->glue, &error)) + dns_ai_goto(DNS_AI_S_FOREACH_I); + + dns_ai_goto(DNS_AI_S_ITERATE_G); + case DNS_AI_S_DONE: + if (ai->found) { + return ENOENT; /* TODO: Just return 0 */ + } else if (ai->answer) { + switch (dns_p_rcode(ai->answer)) { + case DNS_RC_NOERROR: + /* FALL THROUGH */ + case DNS_RC_NXDOMAIN: + return DNS_ENONAME; + default: + return DNS_EFAIL; + } + } else { + return DNS_EFAIL; + } + default: + return EINVAL; + } /* switch() */ +} /* dns_ai_nextent() */ + + +time_t dns_ai_elapsed(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_elapsed(ai->res) : 0; +} /* dns_ai_elapsed() */ + + +void dns_ai_clear(struct dns_addrinfo *ai) { + if (ai->res) + dns_res_clear(ai->res); +} /* dns_ai_clear() */ + + +int dns_ai_events(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_events(ai->res) : 0; +} /* dns_ai_events() */ + + +int dns_ai_pollfd(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_pollfd(ai->res) : -1; +} /* dns_ai_pollfd() */ + + +time_t dns_ai_timeout(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_timeout(ai->res) : 0; +} /* dns_ai_timeout() */ + + +int dns_ai_poll(struct dns_addrinfo *ai, int timeout) { + return (ai->res)? dns_res_poll(ai->res, timeout) : 0; +} /* dns_ai_poll() */ + + +size_t dns_ai_print(void *_dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) { + struct dns_buf dst = DNS_B_INTO(_dst, lim); + char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1]; + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + + dns_b_puts(&dst, "[ "); + dns_b_puts(&dst, ai->qname); + dns_b_puts(&dst, " IN "); + if (ai->qtype) { + dns_b_puts(&dst, dns_strtype(ai->qtype, __dst)); + } else if (ent->ai_family == AF_INET) { + dns_b_puts(&dst, dns_strtype(DNS_T_A, __dst)); + } else if (ent->ai_family == AF_INET6) { + dns_b_puts(&dst, dns_strtype(DNS_T_AAAA, __dst)); + } else { + dns_b_puts(&dst, "0"); + } + dns_b_puts(&dst, " ]\n"); + + dns_b_puts(&dst, ".ai_family = "); + switch (ent->ai_family) { + case AF_INET: + dns_b_puts(&dst, "AF_INET"); + break; + case AF_INET6: + dns_b_puts(&dst, "AF_INET6"); + break; + default: + dns_b_fmtju(&dst, ent->ai_family, 0); + break; + } + dns_b_putc(&dst, '\n'); + + dns_b_puts(&dst, ".ai_socktype = "); + switch (ent->ai_socktype) { + case SOCK_STREAM: + dns_b_puts(&dst, "SOCK_STREAM"); + break; + case SOCK_DGRAM: + dns_b_puts(&dst, "SOCK_DGRAM"); + break; + default: + dns_b_fmtju(&dst, ent->ai_socktype, 0); + break; + } + dns_b_putc(&dst, '\n'); + + dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr); + dns_b_puts(&dst, ".ai_addr = ["); + dns_b_puts(&dst, addr); + dns_b_puts(&dst, "]:"); + dns_b_fmtju(&dst, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0); + dns_b_putc(&dst, '\n'); + + dns_b_puts(&dst, ".ai_canonname = "); + dns_b_puts(&dst, (ent->ai_canonname)? ent->ai_canonname : "[NULL]"); + dns_b_putc(&dst, '\n'); + + return dns_b_strllen(&dst); +} /* dns_ai_print() */ + + +const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) { + return (ai->res)? dns_res_stat(ai->res) : &ai->st; +} /* dns_ai_stat() */ + + +struct dns_trace *dns_ai_trace(struct dns_addrinfo *ai) { + return ai->trace; +} /* dns_ai_trace() */ + + +void dns_ai_settrace(struct dns_addrinfo *ai, struct dns_trace *trace) { + struct dns_trace *otrace = ai->trace; + ai->trace = dns_trace_acquire_p(trace); + dns_trace_close(otrace); + if (ai->res) + dns_res_settrace(ai->res, trace); +} /* dns_ai_settrace() */ + + +/* + * M I S C E L L A N E O U S R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +static const struct { + char name[16]; + enum dns_section type; +} dns_sections[] = { + { "QUESTION", DNS_S_QUESTION }, + { "QD", DNS_S_QUESTION }, + { "ANSWER", DNS_S_ANSWER }, + { "AN", DNS_S_ANSWER }, + { "AUTHORITY", DNS_S_AUTHORITY }, + { "NS", DNS_S_AUTHORITY }, + { "ADDITIONAL", DNS_S_ADDITIONAL }, + { "AR", DNS_S_ADDITIONAL }, +}; + +const char *(dns_strsection)(enum dns_section section, char *_dst) { + struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1); + unsigned i; + + for (i = 0; i < lengthof(dns_sections); i++) { + if (dns_sections[i].type & section) { + dns_b_puts(&dst, dns_sections[i].name); + section &= ~dns_sections[i].type; + if (section) + dns_b_putc(&dst, '|'); + } + } + + if (section || dst.p == dst.base) + dns_b_fmtju(&dst, (0xffff & section), 0); + + return dns_b_tostring(&dst); +} /* dns_strsection() */ + + +enum dns_section dns_isection(const char *src) { + enum dns_section section = 0; + char sbuf[128]; + char *name, *next; + unsigned i; + + dns_strlcpy(sbuf, src, sizeof sbuf); + next = sbuf; + + while ((name = dns_strsep(&next, "|+, \t"))) { + for (i = 0; i < lengthof(dns_sections); i++) { + if (!strcasecmp(dns_sections[i].name, name)) { + section |= dns_sections[i].type; + break; + } + } + } + + return section; +} /* dns_isection() */ + + +static const struct { + char name[8]; + enum dns_class type; +} dns_classes[] = { + { "IN", DNS_C_IN }, +}; + +const char *(dns_strclass)(enum dns_class type, char *_dst) { + struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1); + unsigned i; + + for (i = 0; i < lengthof(dns_classes); i++) { + if (dns_classes[i].type == type) { + dns_b_puts(&dst, dns_classes[i].name); + break; + } + } + + if (dst.p == dst.base) + dns_b_fmtju(&dst, (0xffff & type), 0); + + return dns_b_tostring(&dst); +} /* dns_strclass() */ + + +enum dns_class dns_iclass(const char *name) { + unsigned i, class; + + for (i = 0; i < lengthof(dns_classes); i++) { + if (!strcasecmp(dns_classes[i].name, name)) + return dns_classes[i].type; + } + + class = 0; + while (dns_isdigit(*name)) { + class *= 10; + class += *name++ - '0'; + } + + return DNS_PP_MIN(class, 0xffff); +} /* dns_iclass() */ + + +const char *(dns_strtype)(enum dns_type type, char *_dst) { + struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1); + unsigned i; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (dns_rrtypes[i].type == type) { + dns_b_puts(&dst, dns_rrtypes[i].name); + break; + } + } + + if (dst.p == dst.base) + dns_b_fmtju(&dst, (0xffff & type), 0); + + return dns_b_tostring(&dst); +} /* dns_strtype() */ + + +enum dns_type dns_itype(const char *name) { + unsigned i, type; + + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (!strcasecmp(dns_rrtypes[i].name, name)) + return dns_rrtypes[i].type; + } + + type = 0; + while (dns_isdigit(*name)) { + type *= 10; + type += *name++ - '0'; + } + + return DNS_PP_MIN(type, 0xffff); +} /* dns_itype() */ + + +static char dns_opcodes[16][16] = { + [DNS_OP_QUERY] = "QUERY", + [DNS_OP_IQUERY] = "IQUERY", + [DNS_OP_STATUS] = "STATUS", + [DNS_OP_NOTIFY] = "NOTIFY", + [DNS_OP_UPDATE] = "UPDATE", +}; + +static const char *dns__strcode(int code, volatile char *dst, size_t lim) { + char _tmp[48] = ""; + struct dns_buf tmp; + size_t p; + + assert(lim > 0); + dns_b_fmtju(dns_b_into(&tmp, _tmp, DNS_PP_MIN(sizeof _tmp, lim - 1)), code, 0); + + /* copy downwards so first byte is copied last (see below) */ + p = (size_t)(tmp.p - tmp.base); + dst[p] = '\0'; + while (p--) + dst[p] = _tmp[p]; + + return (const char *)dst; +} + +const char *dns_stropcode(enum dns_opcode opcode) { + opcode = (unsigned)opcode % lengthof(dns_opcodes); + + if ('\0' == dns_opcodes[opcode][0]) + return dns__strcode(opcode, dns_opcodes[opcode], sizeof dns_opcodes[opcode]); + + return dns_opcodes[opcode]; +} /* dns_stropcode() */ + + +enum dns_opcode dns_iopcode(const char *name) { + unsigned opcode; + + for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) { + if (!strcasecmp(name, dns_opcodes[opcode])) + return opcode; + } + + opcode = 0; + while (dns_isdigit(*name)) { + opcode *= 10; + opcode += *name++ - '0'; + } + + return DNS_PP_MIN(opcode, 0x0f); +} /* dns_iopcode() */ + + +static char dns_rcodes[32][16] = { + [DNS_RC_NOERROR] = "NOERROR", + [DNS_RC_FORMERR] = "FORMERR", + [DNS_RC_SERVFAIL] = "SERVFAIL", + [DNS_RC_NXDOMAIN] = "NXDOMAIN", + [DNS_RC_NOTIMP] = "NOTIMP", + [DNS_RC_REFUSED] = "REFUSED", + [DNS_RC_YXDOMAIN] = "YXDOMAIN", + [DNS_RC_YXRRSET] = "YXRRSET", + [DNS_RC_NXRRSET] = "NXRRSET", + [DNS_RC_NOTAUTH] = "NOTAUTH", + [DNS_RC_NOTZONE] = "NOTZONE", + /* EDNS(0) extended RCODEs ... */ + [DNS_RC_BADVERS] = "BADVERS", +}; + +const char *dns_strrcode(enum dns_rcode rcode) { + rcode = (unsigned)rcode % lengthof(dns_rcodes); + + if ('\0' == dns_rcodes[rcode][0]) + return dns__strcode(rcode, dns_rcodes[rcode], sizeof dns_rcodes[rcode]); + + return dns_rcodes[rcode]; +} /* dns_strrcode() */ + + +enum dns_rcode dns_ircode(const char *name) { + unsigned rcode; + + for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) { + if (!strcasecmp(name, dns_rcodes[rcode])) + return rcode; + } + + rcode = 0; + while (dns_isdigit(*name)) { + rcode *= 10; + rcode += *name++ - '0'; + } + + return DNS_PP_MIN(rcode, 0xfff); +} /* dns_ircode() */ + + + +/* + * C O M M A N D - L I N E / R E G R E S S I O N R O U T I N E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +#if DNS_MAIN + +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + +#include <ctype.h> + +#if _WIN32 +#include <getopt.h> +#endif + +#if !_WIN32 +#include <err.h> +#endif + + +struct { + struct { + const char *path[8]; + unsigned count; + } resconf, nssconf, hosts, cache; + + const char *qname; + enum dns_type qtype; + + int (*sort)(); + + const char *trace; + + int verbose; + + struct { + struct dns_resolv_conf *resconf; + struct dns_hosts *hosts; + struct dns_trace *trace; + } memo; + + struct sockaddr_storage socks_host; + const char *socks_user; + const char *socks_password; +} MAIN = { + .sort = &dns_rr_i_packet, +}; + + +static void hexdump(const unsigned char *src, size_t len, FILE *fp) { + struct dns_hxd_lines_i lines = { 0 }; + char line[128]; + + while (dns_hxd_lines(line, sizeof line, src, len, &lines)) { + fputs(line, fp); + } +} /* hexdump() */ + + +DNS_NORETURN static void panic(const char *fmt, ...) { + va_list ap; + + va_start(ap, fmt); + +#if _WIN32 + vfprintf(stderr, fmt, ap); + + exit(EXIT_FAILURE); +#else + verrx(EXIT_FAILURE, fmt, ap); +#endif +} /* panic() */ + +#define panic_(fn, ln, fmt, ...) \ + panic(fmt "%0s", (fn), (ln), __VA_ARGS__) +#define panic(...) \ + panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "") + + +static void *grow(unsigned char *p, size_t size) { + void *tmp; + + if (!(tmp = realloc(p, size))) + panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno)); + + return tmp; +} /* grow() */ + + +static size_t add(size_t a, size_t b) { + if (~a < b) + panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b); + + return a + b; +} /* add() */ + + +static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) { + size_t size = add(osize, len); + + *dst = grow(*dst, size); + memcpy(*dst + osize, src, len); + + return size; +} /* append() */ + + +static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) { + size_t size = osize; + unsigned char buf[1024]; + size_t count; + + while ((count = fread(buf, 1, sizeof buf, fp))) + size = append(dst, size, buf, count); + + if (ferror(fp)) + panic("%s: %s", path, dns_strerror(errno)); + + return size; +} /* slurp() */ + + +static struct dns_resolv_conf *resconf(void) { + struct dns_resolv_conf **resconf = &MAIN.memo.resconf; + const char *path; + unsigned i; + int error; + + if (*resconf) + return *resconf; + + if (!(*resconf = dns_resconf_open(&error))) + panic("dns_resconf_open: %s", dns_strerror(error)); + + if (!MAIN.resconf.count) + MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf"; + + for (i = 0; i < MAIN.resconf.count; i++) { + path = MAIN.resconf.path[i]; + + if (0 == strcmp(path, "-")) + error = dns_resconf_loadfile(*resconf, stdin); + else + error = dns_resconf_loadpath(*resconf, path); + + if (error) + panic("%s: %s", path, dns_strerror(error)); + } + + for (i = 0; i < MAIN.nssconf.count; i++) { + path = MAIN.nssconf.path[i]; + + if (0 == strcmp(path, "-")) + error = dns_nssconf_loadfile(*resconf, stdin); + else + error = dns_nssconf_loadpath(*resconf, path); + + if (error) + panic("%s: %s", path, dns_strerror(error)); + } + + if (!MAIN.nssconf.count) { + path = "/etc/nsswitch.conf"; + + if (!(error = dns_nssconf_loadpath(*resconf, path))) + MAIN.nssconf.path[MAIN.nssconf.count++] = path; + else if (error != ENOENT) + panic("%s: %s", path, dns_strerror(error)); + } + + return *resconf; +} /* resconf() */ + + +static struct dns_hosts *hosts(void) { + struct dns_hosts **hosts = &MAIN.memo.hosts; + const char *path; + unsigned i; + int error; + + if (*hosts) + return *hosts; + + if (!MAIN.hosts.count) { + MAIN.hosts.path[MAIN.hosts.count++] = "/etc/hosts"; + + /* Explicitly test dns_hosts_local() */ + if (!(*hosts = dns_hosts_local(&error))) + panic("%s: %s", "/etc/hosts", dns_strerror(error)); + + return *hosts; + } + + if (!(*hosts = dns_hosts_open(&error))) + panic("dns_hosts_open: %s", dns_strerror(error)); + + for (i = 0; i < MAIN.hosts.count; i++) { + path = MAIN.hosts.path[i]; + + if (0 == strcmp(path, "-")) + error = dns_hosts_loadfile(*hosts, stdin); + else + error = dns_hosts_loadpath(*hosts, path); + + if (error) + panic("%s: %s", path, dns_strerror(error)); + } + + return *hosts; +} /* hosts() */ + + +#if DNS_CACHE +#include "cache.h" + +static struct dns_cache *cache(void) { + static struct cache *cache; + const char *path; + unsigned i; + int error; + + if (cache) + return cache_resi(cache); + if (!MAIN.cache.count) + return NULL; + + if (!(cache = cache_open(&error))) + panic("%s: %s", MAIN.cache.path[0], dns_strerror(error)); + + for (i = 0; i < MAIN.cache.count; i++) { + path = MAIN.cache.path[i]; + + if (!strcmp(path, "-")) { + if ((error = cache_loadfile(cache, stdin, NULL, 0))) + panic("%s: %s", path, dns_strerror(error)); + } else if ((error = cache_loadpath(cache, path, NULL, 0))) + panic("%s: %s", path, dns_strerror(error)); + } + + return cache_resi(cache); +} /* cache() */ +#else +static struct dns_cache *cache(void) { return NULL; } +#endif + + +static struct dns_trace *trace(const char *mode) { + static char omode[64] = ""; + struct dns_trace **trace = &MAIN.memo.trace; + FILE *fp; + int error; + + if (*trace && 0 == strcmp(omode, mode)) + return *trace; + if (!MAIN.trace) + return NULL; + + if (!(fp = fopen(MAIN.trace, mode))) + panic("%s: %s", MAIN.trace, strerror(errno)); + dns_trace_close(*trace); + if (!(*trace = dns_trace_open(fp, &error))) + panic("%s: %s", MAIN.trace, dns_strerror(error)); + dns_strlcpy(omode, mode, sizeof omode); + + return *trace; +} + + +static void print_packet(struct dns_packet *P, FILE *fp) { + struct dns_rr_i I_instance = { 0 }; + I.sort = MAIN.sort; + dns_p_dump3(P, &I, fp); + + if (MAIN.verbose > 2) + hexdump(P->data, P->end, fp); +} /* print_packet() */ + + +static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 }; + struct dns_packet *P = dns_p_init(&P_instance.p, 512); + struct dns_packet *Q = dns_p_init(&Q_instance.p, 512); + enum dns_section section; + struct dns_rr rr; + int error; + union dns_any any; + char pretty[sizeof any * 2]; + size_t len; + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + + P->end = fread(P->data, 1, P->size, stdin); + + fputs(";; [HEADER]\n", stdout); + fprintf(stdout, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr); + fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode); + fprintf(stdout, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa); + fprintf(stdout, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc); + fprintf(stdout, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd); + fprintf(stdout, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra); + fprintf(stdout, ";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P)); + + section = 0; + + dns_rr_foreach(&rr, P, .sort = MAIN.sort) { + if (section != rr.section) + fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section, __dst), dns_p_count(P, rr.section)); + + if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error))) + fprintf(stdout, "%s\n", pretty); + + dns_rr_copy(Q, &rr, P); + + section = rr.section; + } + + fputs("; ; ; ; ; ; ; ;\n\n", stdout); + + section = 0; + +#if 0 + dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") { +#else + char _p[DNS_D_MAXNAME + 1] = { 0 }; + const char *dn = "ns8.yahoo.com"; + char *_name = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); + struct dns_rr rrset[32]; + struct dns_rr_i I_instance = { 0 }; + struct dns_rr_i *rri = &I; + unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error); + + I.name = _name; + I.sort = MAIN.sort; + for (unsigned i = 0; i < rrcount; i++) { + rr = rrset[i]; +#endif + if (section != rr.section) + fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section, __dst), dns_p_count(Q, rr.section)); + + if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error))) + fprintf(stdout, "%s\n", pretty); + + section = rr.section; + } + + if (MAIN.verbose > 1) { + fprintf(stderr, "orig:%"PRIuZ"\n", P->end); + hexdump(P->data, P->end, stdout); + + fprintf(stderr, "copy:%"PRIuZ"\n", Q->end); + hexdump(Q->data, Q->end, stdout); + } + + return 0; +} /* parse_packet() */ + + +static int parse_domain(int argc, char *argv[]) { + char _p[DNS_D_MAXNAME + 1] = { 0 }; + char *dn; + + dn = (argc > 1)? argv[1] : "f.l.google.com"; + + printf("[%s]\n", dn); + + dn = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR); + + do { + puts(dn); + } while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn))); + + return 0; +} /* parse_domain() */ + + +static int trim_domain(int argc, char **argv) { + for (argc--, argv++; argc > 0; argc--, argv++) { + char name[DNS_D_MAXNAME + 1]; + + dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR); + + puts(name); + } + + return 0; +} /* trim_domain() */ + + +static int expand_domain(int argc, char *argv[]) { + unsigned short rp = 0; + unsigned char *src = NULL; + unsigned char *dst; + struct dns_packet *pkt; + size_t lim = 0, len; + int error; + + if (argc > 1) + rp = atoi(argv[1]); + + len = slurp(&src, 0, stdin, "-"); + + if (!(pkt = dns_p_make(len, &error))) + panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error)); + + memcpy(pkt->data, src, len); + pkt->end = len; + + lim = 1; + dst = grow(NULL, lim); + + while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) { + lim = add(len, 1); + dst = grow(dst, lim); + } + + if (!len) + panic("expand: %s", dns_strerror(error)); + + fwrite(dst, 1, len, stdout); + fflush(stdout); + + free(src); + free(dst); + free(pkt); + + return 0; +} /* expand_domain() */ + + +static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + unsigned i; + + resconf(); /* load it */ + + fputs("; SOURCES\n", stdout); + + for (i = 0; i < MAIN.resconf.count; i++) + fprintf(stdout, "; %s\n", MAIN.resconf.path[i]); + + for (i = 0; i < MAIN.nssconf.count; i++) + fprintf(stdout, "; %s\n", MAIN.nssconf.path[i]); + + fputs(";\n", stdout); + + dns_resconf_dump(resconf(), stdout); + + return 0; +} /* show_resconf() */ + + +static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + unsigned i; + + resconf(); + + fputs("# SOURCES\n", stdout); + + for (i = 0; i < MAIN.resconf.count; i++) + fprintf(stdout, "# %s\n", MAIN.resconf.path[i]); + + for (i = 0; i < MAIN.nssconf.count; i++) + fprintf(stdout, "# %s\n", MAIN.nssconf.path[i]); + + fputs("#\n", stdout); + + dns_nssconf_dump(resconf(), stdout); + + return 0; +} /* show_nssconf() */ + + +static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + unsigned i; + + hosts(); + + fputs("# SOURCES\n", stdout); + + for (i = 0; i < MAIN.hosts.count; i++) + fprintf(stdout, "# %s\n", MAIN.hosts.path[i]); + + fputs("#\n", stdout); + + dns_hosts_dump(hosts(), stdout); + + return 0; +} /* show_hosts() */ + + +static int query_hosts(int argc, char *argv[]) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 }; + struct dns_packet *Q = dns_p_init(&Q_instance.p, 512); + struct dns_packet *A; + char qname[DNS_D_MAXNAME + 1]; + size_t qlen; + int error; + + if (!MAIN.qname) + MAIN.qname = (argc > 1)? argv[1] : "localhost"; + if (!MAIN.qtype) + MAIN.qtype = DNS_T_A; + + hosts(); + + if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) { + union { struct in_addr a; struct in6_addr a6; } addr; + int af = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET; + + if ((error = dns_pton(af, MAIN.qname, &addr))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + qlen = dns_ptr_qname(qname, sizeof qname, af, &addr); + } else + qlen = dns_strlcpy(qname, MAIN.qname, sizeof qname); + + if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0))) + panic("%s: %s", qname, dns_strerror(error)); + + if (!(A = dns_hosts_query(hosts(), Q, &error))) + panic("%s: %s", qname, dns_strerror(error)); + + print_packet(A, stdout); + + free(A); + + return 0; +} /* query_hosts() */ + + +static int search_list(int argc, char *argv[]) { + const char *qname = (argc > 1)? argv[1] : "f.l.google.com"; + unsigned long i = 0; + char name[DNS_D_MAXNAME + 1]; + + printf("[%s]\n", qname); + + while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i)) + puts(name); + + return 0; +} /* search_list() */ + + +static int permute_set(int argc, char *argv[]) { + unsigned lo, hi, i; + struct dns_k_permutor p; + + hi = (--argc > 0)? atoi(argv[argc]) : 8; + lo = (--argc > 0)? atoi(argv[argc]) : 0; + + fprintf(stderr, "[%u .. %u]\n", lo, hi); + + dns_k_permutor_init(&p, lo, hi); + + for (i = lo; i <= hi; i++) + fprintf(stdout, "%u\n", dns_k_permutor_step(&p)); +// printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i))); + + return 0; +} /* permute_set() */ + + +static int shuffle_16(int argc, char *argv[]) { + unsigned n, r; + + if (--argc > 0) { + n = 0xffff & atoi(argv[argc]); + r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random(); + + fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); + } else { + r = dns_random(); + + for (n = 0; n < 65536; n++) + fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r)); + } + + return 0; +} /* shuffle_16() */ + + +static int dump_random(int argc, char *argv[]) { + unsigned char b[32]; + unsigned i, j, n, r; + + n = (argc > 1)? atoi(argv[1]) : 32; + + while (n) { + i = 0; + + do { + r = dns_random(); + + for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) { + b[i] = 0xff & r; + r >>= 8; + } + } while (i < n && i < sizeof b); + + hexdump(b, i, stdout); + + n -= i; + } + + return 0; +} /* dump_random() */ + + +static int send_query(int argc, char *argv[]) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 }; + struct dns_packet *A, *Q = dns_p_init(&Q_instance.p, 512); + char host[INET6_ADDRSTRLEN + 1]; + struct sockaddr_storage ss; + struct dns_socket *so; + int error, type; + struct dns_options opts = { 0 }; + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + + memset(&ss, 0, sizeof ss); + if (argc > 1) { + ss.ss_family = (strchr(argv[1], ':'))? AF_INET6 : AF_INET; + + if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL)))) + panic("%s: %s", argv[1], dns_strerror(error)); + + *dns_sa_port(ss.ss_family, &ss) = htons(53); + } else + memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0])); + + if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host)) + panic("bad host address, or none provided"); + + if (!MAIN.qname) + MAIN.qname = "ipv6.google.com"; + if (!MAIN.qtype) + MAIN.qtype = DNS_T_AAAA; + + if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0))) + panic("dns_p_push: %s", dns_strerror(error)); + + dns_header(Q)->rd = 1; + + if (strstr(argv[0], "udp")) + type = SOCK_DGRAM; + else if (strstr(argv[0], "tcp")) + type = SOCK_STREAM; + else + type = dns_res_tcp2type(resconf()->options.tcp); + + fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype, __dst)); + + if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, &opts, &error))) + panic("dns_so_open: %s", dns_strerror(error)); + + while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) { + if (error != DNS_EAGAIN) + panic("dns_so_query: %s (%d)", dns_strerror(error), error); + if (dns_so_elapsed(so) > 10) + panic("query timed-out"); + + dns_so_poll(so, 1); + } + + print_packet(A, stdout); + + dns_so_close(so); + + return 0; +} /* send_query() */ + + +static int print_arpa(int argc, char *argv[]) { + const char *ip = (argc > 1)? argv[1] : "::1"; + int af = (strchr(ip, ':'))? AF_INET6 : AF_INET; + union { struct in_addr a4; struct in6_addr a6; } addr; + char host[DNS_D_MAXNAME + 1]; + + if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr)) + panic("%s: invalid address", ip); + + fprintf(stdout, "%s\n", host); + + return 0; +} /* print_arpa() */ + + +static int show_hints(int argc, char *argv[]) { + struct dns_hints *(*load)(struct dns_resolv_conf *, int *); + const char *which, *how, *who; + struct dns_hints *hints; + int error; + + which = (argc > 1)? argv[1] : "local"; + how = (argc > 2)? argv[2] : "plain"; + who = (argc > 3)? argv[3] : "google.com"; + + load = (0 == strcmp(which, "local")) + ? &dns_hints_local + : &dns_hints_root; + + if (!(hints = load(resconf(), &error))) + panic("%s: %s", argv[0], dns_strerror(error)); + + if (0 == strcmp(how, "plain")) { + dns_hints_dump(hints, stdout); + } else { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; + struct dns_packet *query, *answer; + + query = dns_p_init(&P_instance.p, 512); + + if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0))) + panic("%s: %s", who, dns_strerror(error)); + + if (!(answer = dns_hints_query(hints, query, &error))) + panic("%s: %s", who, dns_strerror(error)); + + print_packet(answer, stdout); + + free(answer); + } + + dns_hints_close(hints); + + return 0; +} /* show_hints() */ + + +static int resolve_query(int argc DNS_NOTUSED, char *argv[]) { + _Bool recurse = !!strstr(argv[0], "recurse"); + struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; + struct dns_resolver *R; + struct dns_packet *ans; + const struct dns_stat *st; + int error; + struct dns_options opts = { 0 }; + + opts.socks_host = &MAIN.socks_host; + opts.socks_user = MAIN.socks_user; + opts.socks_password = MAIN.socks_password; + + if (!MAIN.qname) + MAIN.qname = "www.google.com"; + if (!MAIN.qtype) + MAIN.qtype = DNS_T_A; + + resconf()->options.recurse = recurse; + + if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), + &opts, &error))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + dns_res_settrace(R, trace("w+b")); + + if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + while ((error = dns_res_check(R))) { + if (error != DNS_EAGAIN) + panic("dns_res_check: %s (%d)", dns_strerror(error), error); + if (dns_res_elapsed(R) > 30) + panic("query timed-out"); + + dns_res_poll(R, 1); + } + + ans = dns_res_fetch(R, &error); + print_packet(ans, stdout); + free(ans); + + st = dns_res_stat(R); + putchar('\n'); + printf(";; queries: %"PRIuZ"\n", st->queries); + printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes); + printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes); + printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes); + printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes); + + dns_res_close(R); + + return 0; +} /* resolve_query() */ + + +static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) { + _Bool recurse = !!strstr(argv[0], "recurse"); + struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local; + struct dns_resolver *res = NULL; + struct dns_addrinfo *ai = NULL; + struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME }; + struct addrinfo *ent; + char pretty[512]; + int error; + struct dns_options opts = { 0 }; + + if (!MAIN.qname) + MAIN.qname = "www.google.com"; + /* NB: MAIN.qtype of 0 means obey hints.ai_family */ + + resconf()->options.recurse = recurse; + + if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), &opts, &error))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error))) + panic("%s: %s", MAIN.qname, dns_strerror(error)); + + dns_ai_settrace(ai, trace("w+b")); + + do { + switch (error = dns_ai_nextent(&ent, ai)) { + case 0: + dns_ai_print(pretty, sizeof pretty, ent, ai); + + fputs(pretty, stdout); + + free(ent); + + break; + case ENOENT: + break; + case DNS_EAGAIN: + if (dns_ai_elapsed(ai) > 30) + panic("query timed-out"); + + dns_ai_poll(ai, 1); + + break; + default: + panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error); + } + } while (error != ENOENT); + + dns_res_close(res); + dns_ai_close(ai); + + return 0; +} /* resolve_addrinfo() */ + + +static int dump_trace(int argc DNS_NOTUSED, char *argv[]) { + int error; + + if (!MAIN.trace) + panic("no trace file specified"); + + if ((error = dns_trace_dump(trace("r"), stdout))) + panic("dump_trace: %s", dns_strerror(error)); + + return 0; +} /* dump_trace() */ + + +static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + union { + struct sockaddr sa; + struct sockaddr_in sin; + } port; + int fd; + + memset(&port, 0, sizeof port); + port.sin.sin_family = AF_INET; + port.sin.sin_port = htons(5354); + port.sin.sin_addr.s_addr = inet_addr("127.0.0.1"); + + if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0))) + panic("socket: %s", strerror(errno)); + + if (0 != bind(fd, &port.sa, sizeof port.sa)) + panic("127.0.0.1:5353: %s", dns_strerror(errno)); + + for (;;) { + union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 }; + struct dns_packet *pkt = dns_p_init(&P_instance.p, 512); + struct sockaddr_storage ss; + socklen_t slen = sizeof ss; + ssize_t count; +#if defined(MSG_WAITALL) /* MinGW issue */ + int rflags = MSG_WAITALL; +#else + int rflags = 0; +#endif + + count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen); + + if (!count || count < 0) + panic("recvfrom: %s", strerror(errno)); + + pkt->end = count; + + dns_p_dump(pkt, stdout); + + (void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen); + } + + return 0; +} /* echo_port() */ + + +static int isection(int argc, char *argv[]) { + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_isection(name); + name = dns_strsection(type, __dst); + + printf("%s (%d)\n", name, type); + + return 0; +} /* isection() */ + + +static int iclass(int argc, char *argv[]) { + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_iclass(name); + name = dns_strclass(type, __dst); + + printf("%s (%d)\n", name, type); + + return 0; +} /* iclass() */ + + +static int itype(int argc, char *argv[]) { + char __dst[DNS_STRMAXLEN + 1] = { 0 }; + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_itype(name); + name = dns_strtype(type, __dst); + + printf("%s (%d)\n", name, type); + + return 0; +} /* itype() */ + + +static int iopcode(int argc, char *argv[]) { + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_iopcode(name); + name = dns_stropcode(type); + + printf("%s (%d)\n", name, type); + + return 0; +} /* iopcode() */ + + +static int ircode(int argc, char *argv[]) { + const char *name = (argc > 1)? argv[1] : ""; + int type; + + type = dns_ircode(name); + name = dns_strrcode(type); + + printf("%s (%d)\n", name, type); + + return 0; +} /* ircode() */ + + +#define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) } +#define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__) +#define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__) +#define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__) +#define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__) + +static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) { + static const struct { const char *name; size_t size; } type[] = { + SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i), + SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns), + SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv), + SIZE(struct dns_sshfp, struct dns_txt, union dns_any), + SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i), + SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo), + SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long) + }; + unsigned i, max; + + for (i = 0, max = 0; i < lengthof(type); i++) + max = DNS_PP_MAX(max, strlen(type[i].name)); + + for (i = 0; i < lengthof(type); i++) + printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size); + + return 0; +} /* sizes() */ + + +static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = { + { "parse-packet", &parse_packet, "parse binary packet from stdin" }, + { "parse-domain", &parse_domain, "anchor and iteratively cleave domain" }, + { "trim-domain", &trim_domain, "trim and anchor domain name" }, + { "expand-domain", &expand_domain, "expand domain at offset NN in packet from stdin" }, + { "show-resconf", &show_resconf, "show resolv.conf data" }, + { "show-hosts", &show_hosts, "show hosts data" }, + { "show-nssconf", &show_nssconf, "show nsswitch.conf data" }, + { "query-hosts", &query_hosts, "query A, AAAA or PTR in hosts data" }, + { "search-list", &search_list, "generate query search list from domain" }, + { "permute-set", &permute_set, "generate random permutation -> (0 .. N or N .. M)" }, + { "shuffle-16", &shuffle_16, "simple 16-bit permutation" }, + { "dump-random", &dump_random, "generate random bytes" }, + { "send-query", &send_query, "send query to host" }, + { "send-query-udp", &send_query, "send udp query to host" }, + { "send-query-tcp", &send_query, "send tcp query to host" }, + { "print-arpa", &print_arpa, "print arpa. zone name of address" }, + { "show-hints", &show_hints, "print hints: show-hints [local|root] [plain|packet]" }, + { "resolve-stub", &resolve_query, "resolve as stub resolver" }, + { "resolve-recurse", &resolve_query, "resolve as recursive resolver" }, + { "addrinfo-stub", &resolve_addrinfo, "resolve through getaddrinfo clone" }, + { "addrinfo-recurse", &resolve_addrinfo, "resolve through getaddrinfo clone" }, +/* { "resolve-nameinfo", &resolve_query, "resolve as recursive resolver" }, */ + { "dump-trace", &dump_trace, "dump the contents of a trace file" }, + { "echo", &echo_port, "server echo mode, for nmap fuzzing" }, + { "isection", &isection, "parse section string" }, + { "iclass", &iclass, "parse class string" }, + { "itype", &itype, "parse type string" }, + { "iopcode", &iopcode, "parse opcode string" }, + { "ircode", &ircode, "parse rcode string" }, + { "sizes", &sizes, "print data structure sizes" }, +}; + + +static void print_usage(const char *progname, FILE *fp) { + static const char *usage = + " [OPTIONS] COMMAND [ARGS]\n" + " -c PATH Path to resolv.conf\n" + " -n PATH Path to nsswitch.conf\n" + " -l PATH Path to local hosts\n" + " -z PATH Path to zone cache\n" + " -q QNAME Query name\n" + " -t QTYPE Query type\n" + " -s HOW Sort records\n" + " -S ADDR Address of SOCKS server to use\n" + " -P PORT Port of SOCKS server to use\n" + " -A USER:PASSWORD Credentials for the SOCKS server\n" + " -f PATH Path to trace file\n" + " -v Be more verbose (-vv show packets; -vvv hexdump packets)\n" + " -V Print version info\n" + " -h Print this usage message\n" + "\n"; + unsigned i, n, m; + + fputs(progname, fp); + fputs(usage, fp); + + for (i = 0, m = 0; i < lengthof(cmds); i++) { + if (strlen(cmds[i].cmd) > m) + m = strlen(cmds[i].cmd); + } + + for (i = 0; i < lengthof(cmds); i++) { + fprintf(fp, " %s ", cmds[i].cmd); + + for (n = strlen(cmds[i].cmd); n < m; n++) + putc(' ', fp); + + fputs(cmds[i].help, fp); + putc('\n', fp); + } + + fputs("\nReport bugs to William Ahern <william@25thandClement.com>\n", fp); +} /* print_usage() */ + + +static void print_version(const char *progname, FILE *fp) { + fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel()); + fprintf(fp, "vendor %s\n", dns_vendor()); + fprintf(fp, "release %.8X\n", dns_v_rel()); + fprintf(fp, "abi %.8X\n", dns_v_abi()); + fprintf(fp, "api %.8X\n", dns_v_api()); +} /* print_version() */ + + +static void main_exit(void) { + dns_trace_close(MAIN.memo.trace); + MAIN.memo.trace = NULL; + dns_hosts_close(MAIN.memo.hosts); + MAIN.memo.hosts = NULL; + dns_resconf_close(MAIN.memo.resconf); + MAIN.memo.resconf = NULL; +} /* main_exit() */ + +int main(int argc, char **argv) { + extern int optind; + extern char *optarg; + const char *progname = argv[0]; + unsigned i; + int ch; + + atexit(&main_exit); + + while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:S:P:A:f:vVh"))) { + switch (ch) { + case 'c': + assert(MAIN.resconf.count < lengthof(MAIN.resconf.path)); + + MAIN.resconf.path[MAIN.resconf.count++] = optarg; + + break; + case 'n': + assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path)); + + MAIN.nssconf.path[MAIN.nssconf.count++] = optarg; + + break; + case 'l': + assert(MAIN.hosts.count < lengthof(MAIN.hosts.path)); + + MAIN.hosts.path[MAIN.hosts.count++] = optarg; + + break; + case 'z': + assert(MAIN.cache.count < lengthof(MAIN.cache.path)); + + MAIN.cache.path[MAIN.cache.count++] = optarg; + + break; + case 'q': + MAIN.qname = optarg; + + break; + case 't': + for (i = 0; i < lengthof(dns_rrtypes); i++) { + if (0 == strcasecmp(dns_rrtypes[i].name, optarg)) + { MAIN.qtype = dns_rrtypes[i].type; break; } + } + + if (MAIN.qtype) + break; + + for (i = 0; dns_isdigit(optarg[i]); i++) { + MAIN.qtype *= 10; + MAIN.qtype += optarg[i] - '0'; + } + + if (!MAIN.qtype) + panic("%s: invalid query type", optarg); + + break; + case 's': + if (0 == strcasecmp(optarg, "packet")) + MAIN.sort = &dns_rr_i_packet; + else if (0 == strcasecmp(optarg, "shuffle")) + MAIN.sort = &dns_rr_i_shuffle; + else if (0 == strcasecmp(optarg, "order")) + MAIN.sort = &dns_rr_i_order; + else + panic("%s: invalid sort method", optarg); + + break; + case 'S': { + dns_error_t error; + struct dns_resolv_conf *conf = resconf(); + conf->options.tcp = DNS_RESCONF_TCP_SOCKS; + + MAIN.socks_host.ss_family = (strchr(optarg, ':')) ? AF_INET6 : AF_INET; + if ((error = dns_pton(MAIN.socks_host.ss_family, optarg, + dns_sa_addr(MAIN.socks_host.ss_family, + &MAIN.socks_host, NULL)))) + panic("%s: %s", optarg, dns_strerror(error)); + + *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(1080); + + break; + } + case 'P': + if (! MAIN.socks_host.ss_family) + panic("-P without prior -S"); + *dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(atoi(optarg)); + + break; + case 'A': { + char *password; + if (! MAIN.socks_host.ss_family) + panic("-A without prior -S"); + if (! (password = strchr(optarg, ':'))) + panic("Usage: -A USER:PASSWORD"); + *password = 0; + password += 1; + MAIN.socks_user = optarg; + MAIN.socks_password = password; + break; + } + case 'f': + MAIN.trace = optarg; + + break; + case 'v': + dns_debug = ++MAIN.verbose; + + break; + case 'V': + print_version(progname, stdout); + + return 0; + case 'h': + print_usage(progname, stdout); + + return 0; + default: + print_usage(progname, stderr); + + return EXIT_FAILURE; + } /* switch() */ + } /* while() */ + + argc -= optind; + argv += optind; + + for (i = 0; i < lengthof(cmds) && argv[0]; i++) { + if (0 == strcmp(cmds[i].cmd, argv[0])) + return cmds[i].run(argc, argv); + } + + print_usage(progname, stderr); + + return EXIT_FAILURE; +} /* main() */ + + +#endif /* DNS_MAIN */ + + +/* + * pop file-scoped compiler annotations + */ +#if __clang__ +#pragma clang diagnostic pop +#elif DNS_GNUC_PREREQ(4,6,0) +#pragma GCC diagnostic pop +#endif diff --git a/dirmngr/dns.h b/dirmngr/dns.h new file mode 100644 index 0000000..1f647e1 --- /dev/null +++ b/dirmngr/dns.h @@ -0,0 +1,1321 @@ +/* ========================================================================== + * dns.h - Recursive, Reentrant DNS Resolver. + * -------------------------------------------------------------------------- + * Copyright (c) 2009, 2010, 2012-2015 William Ahern + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * ========================================================================== + */ +#ifndef DNS_H +#define DNS_H + +#include <stddef.h> /* size_t offsetof() */ +#include <stdint.h> /* uint64_t */ +#include <stdio.h> /* FILE */ +#include <string.h> /* strlen(3) */ +#include <time.h> /* struct timespec time_t */ + +#if _WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#else +#include <sys/param.h> /* BYTE_ORDER BIG_ENDIAN _BIG_ENDIAN */ +#include <sys/types.h> /* socklen_t */ +#include <sys/socket.h> /* struct socket */ +#include <poll.h> /* POLLIN POLLOUT */ +#include <netinet/in.h> /* struct in_addr struct in6_addr */ +#include <netdb.h> /* struct addrinfo */ +#endif + + +/* + * V I S I B I L I T Y + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef DNS_PUBLIC +#define DNS_PUBLIC +#endif + + +/* + * V E R S I O N + * + * Vendor: Entity for which versions numbers are relevant. (If forking + * change DNS_VENDOR to avoid confusion.) + * + * Three versions: + * + * REL Official "release"--bug fixes, new features, etc. + * ABI Changes to existing object sizes or parameter types + * API Changes that might effect application source. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_VENDOR "william@25thandClement.com" + +#define DNS_V_REL 0x20160809 +#define DNS_V_ABI 0x20160608 +#define DNS_V_API 0x20160809 + + +DNS_PUBLIC const char *dns_vendor(void); + +DNS_PUBLIC int dns_v_rel(void); +DNS_PUBLIC int dns_v_abi(void); +DNS_PUBLIC int dns_v_api(void); + + +/* + * E R R O R S + * + * Errors and exceptions are always returned through an int. This should + * hopefully make integration easier in the majority of circumstances, and + * also cut down on useless compiler warnings. + * + * System and library errors are returned together. POSIX guarantees that + * all system errors are positive integers. Library errors are always + * negative integers in the range DNS_EBASE to DNS_ELAST, with the high bits + * set to the three magic ASCII characters "dns". + * + * dns_strerror() returns static English string descriptions of all known + * errors, and punts the remainder to strerror(3). + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_EBASE -(('d' << 24) | ('n' << 16) | ('s' << 8) | 64) + +#define dns_error_t int /* for documentation only */ + +enum dns_errno { + DNS_ENOBUFS = DNS_EBASE, + DNS_EILLEGAL, + DNS_EORDER, + DNS_ESECTION, + DNS_EUNKNOWN, + DNS_EADDRESS, + DNS_ENOQUERY, + DNS_ENOANSWER, + DNS_EFETCHED, + DNS_ESERVICE, /* EAI_SERVICE */ + DNS_ENONAME, /* EAI_NONAME */ + DNS_EFAIL, /* EAI_FAIL */ + DNS_ECONNFIN, + DNS_EVERIFY, + DNS_ELAST, +}; /* dns_errno */ + +DNS_PUBLIC const char *dns_strerror(dns_error_t); + +DNS_PUBLIC int *dns_debug_p(void); + +#define dns_debug (*dns_debug_p()) /* was extern int dns_debug before 20160523 API */ + + +/* + * C O M P I L E R A N N O T A T I O N S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if defined __clang__ +#define DNS_PRAGMA_PUSH _Pragma("clang diagnostic push") +#define DNS_PRAGMA_QUIET _Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") +#define DNS_PRAGMA_POP _Pragma("clang diagnostic pop") + +#elif (__GNUC__ == 4 && __GNUC_MINOR__ >= 6) || __GNUC__ > 4 +#define DNS_PRAGMA_PUSH _Pragma("GCC diagnostic push") +#define DNS_PRAGMA_QUIET _Pragma("GCC diagnostic ignored \"-Woverride-init\"") +#define DNS_PRAGMA_POP _Pragma("GCC diagnostic pop") + +#else +#define DNS_PRAGMA_PUSH +#define DNS_PRAGMA_QUIET +#define DNS_PRAGMA_POP +#endif + +#if defined __GNUC__ +#define DNS_PRAGMA_EXTENSION __extension__ +#else +#define DNS_PRAGMA_EXTENSION +#endif + + +/* + * E V E N T S I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#if defined(POLLIN) +#define DNS_POLLIN POLLIN +#else +#define DNS_POLLIN 1 +#endif + +#if defined(POLLOUT) +#define DNS_POLLOUT POLLOUT +#else +#define DNS_POLLOUT 2 +#endif + + +/* + * See Application Interface below for configuring libevent bitmasks instead + * of poll(2) bitmasks. + */ +#define DNS_EVREAD 2 +#define DNS_EVWRITE 4 + + +#define DNS_POLL2EV(set) \ + (((set) & DNS_POLLIN)? DNS_EVREAD : 0) | (((set) & DNS_POLLOUT)? DNS_EVWRITE : 0) + +#define DNS_EV2POLL(set) \ + (((set) & DNS_EVREAD)? DNS_POLLIN : 0) | (((set) & DNS_EVWRITE)? DNS_POLLOUT : 0) + + +/* + * E N U M E R A T I O N I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +enum dns_section { + DNS_S_QD = 0x01, +#define DNS_S_QUESTION DNS_S_QD + + DNS_S_AN = 0x02, +#define DNS_S_ANSWER DNS_S_AN + + DNS_S_NS = 0x04, +#define DNS_S_AUTHORITY DNS_S_NS + + DNS_S_AR = 0x08, +#define DNS_S_ADDITIONAL DNS_S_AR + + DNS_S_ALL = 0x0f +}; /* enum dns_section */ + + +enum dns_class { + DNS_C_IN = 1, + + DNS_C_ANY = 255 +}; /* enum dns_class */ + + +enum dns_type { + DNS_T_A = 1, + DNS_T_NS = 2, + DNS_T_CNAME = 5, + DNS_T_SOA = 6, + DNS_T_PTR = 12, + DNS_T_MX = 15, + DNS_T_TXT = 16, + DNS_T_AAAA = 28, + DNS_T_SRV = 33, + DNS_T_OPT = 41, + DNS_T_SSHFP = 44, + DNS_T_SPF = 99, + DNS_T_AXFR = 252, + + DNS_T_ALL = 255 +}; /* enum dns_type */ + + +enum dns_opcode { + DNS_OP_QUERY = 0, + DNS_OP_IQUERY = 1, + DNS_OP_STATUS = 2, + DNS_OP_NOTIFY = 4, + DNS_OP_UPDATE = 5, +}; /* dns_opcode */ + + +enum dns_rcode { + DNS_RC_NOERROR = 0, + DNS_RC_FORMERR = 1, + DNS_RC_SERVFAIL = 2, + DNS_RC_NXDOMAIN = 3, + DNS_RC_NOTIMP = 4, + DNS_RC_REFUSED = 5, + DNS_RC_YXDOMAIN = 6, + DNS_RC_YXRRSET = 7, + DNS_RC_NXRRSET = 8, + DNS_RC_NOTAUTH = 9, + DNS_RC_NOTZONE = 10, + + /* EDNS(0) extended RCODEs */ + DNS_RC_BADVERS = 16, +}; /* dns_rcode */ + + +/* + * NOTE: These string functions need a small buffer in case the literal + * integer value needs to be printed and returned. UNLESS this buffer is + * SPECIFIED, the returned string has ONLY BLOCK SCOPE. + */ +#define DNS_STRMAXLEN 47 /* "QUESTION|ANSWER|AUTHORITY|ADDITIONAL" */ + +DNS_PUBLIC const char *dns_strsection(enum dns_section, char *); + +DNS_PUBLIC enum dns_section dns_isection(const char *); + +DNS_PUBLIC const char *dns_strclass(enum dns_class, char *); + +DNS_PUBLIC enum dns_class dns_iclass(const char *); + +DNS_PUBLIC const char *dns_strtype(enum dns_type, char *); + +DNS_PUBLIC enum dns_type dns_itype(const char *); + +DNS_PUBLIC const char *dns_stropcode(enum dns_opcode); + +DNS_PUBLIC enum dns_opcode dns_iopcode(const char *); + +DNS_PUBLIC const char *dns_strrcode(enum dns_rcode); + +DNS_PUBLIC enum dns_rcode dns_ircode(const char *); + + +/* + * A T O M I C I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typedef unsigned long dns_atomic_t; + +typedef unsigned long dns_refcount_t; /* must be same value type as dns_atomic_t */ + + +/* + * C R Y P T O I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +typedef unsigned dns_random_f(void); + +DNS_PUBLIC dns_random_f **dns_random_p(void); + +#define dns_random (*dns_random_p()) /* was extern unsigned (*dns_random)(void) before 20160523 API */ + + +/* + * P A C K E T I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_header { + unsigned qid:16; + +#if (defined BYTE_ORDER && BYTE_ORDER == BIG_ENDIAN) || (defined __sun && defined _BIG_ENDIAN) + unsigned qr:1; + unsigned opcode:4; + unsigned aa:1; + unsigned tc:1; + unsigned rd:1; + + unsigned ra:1; + unsigned unused:3; + unsigned rcode:4; +#else + unsigned rd:1; + unsigned tc:1; + unsigned aa:1; + unsigned opcode:4; + unsigned qr:1; + + unsigned rcode:4; + unsigned unused:3; + unsigned ra:1; +#endif + + unsigned qdcount:16; + unsigned ancount:16; + unsigned nscount:16; + unsigned arcount:16; +}; /* struct dns_header */ + +#define dns_header(p) (&(p)->header) + + +#ifndef DNS_P_QBUFSIZ +#define DNS_P_QBUFSIZ dns_p_calcsize(256 + 4) +#endif + +#ifndef DNS_P_DICTSIZE +#define DNS_P_DICTSIZE 16 +#endif + +struct dns_packet { + unsigned short dict[DNS_P_DICTSIZE]; + + struct dns_p_memo { + struct dns_s_memo { + unsigned short base, end; + } qd, an, ns, ar; + + struct { + unsigned short p; + unsigned short maxudp; + unsigned ttl; + } opt; + } memo; + + struct { struct dns_packet *cqe_next, *cqe_prev; } cqe; + + size_t size, end; + + int:16; /* tcp padding */ + + DNS_PRAGMA_EXTENSION union { + struct dns_header header; + unsigned char data[1]; + }; +}; /* struct dns_packet */ + +#define dns_p_calcsize(n) (offsetof(struct dns_packet, data) + DNS_PP_MAX(12, (n))) + +#define dns_p_sizeof(P) dns_p_calcsize((P)->end) + +/** takes size of entire packet structure as allocated */ +DNS_PUBLIC struct dns_packet *dns_p_init(struct dns_packet *, size_t); + +/** takes size of maximum desired payload */ +DNS_PUBLIC struct dns_packet *dns_p_make(size_t, int *); + +DNS_PUBLIC int dns_p_grow(struct dns_packet **); + +DNS_PUBLIC struct dns_packet *dns_p_copy(struct dns_packet *, const struct dns_packet *); + +#define dns_p_opcode(P) (dns_header(P)->opcode) + +DNS_PUBLIC enum dns_rcode dns_p_rcode(struct dns_packet *); + +DNS_PUBLIC unsigned dns_p_count(struct dns_packet *, enum dns_section); + +DNS_PUBLIC int dns_p_push(struct dns_packet *, enum dns_section, const void *, size_t, enum dns_type, enum dns_class, unsigned, const void *); + +DNS_PUBLIC void dns_p_dictadd(struct dns_packet *, unsigned short); + +DNS_PUBLIC struct dns_packet *dns_p_merge(struct dns_packet *, enum dns_section, struct dns_packet *, enum dns_section, int *); + +DNS_PUBLIC void dns_p_dump(struct dns_packet *, FILE *); + +DNS_PUBLIC int dns_p_study(struct dns_packet *); + + +/* + * D O M A I N N A M E I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_D_MAXLABEL 63 /* + 1 '\0' */ +#define DNS_D_MAXNAME 255 /* + 1 '\0' */ + +#define DNS_D_ANCHOR 1 /* anchor domain w/ root "." */ +#define DNS_D_CLEAVE 2 /* cleave sub-domain */ +#define DNS_D_TRIM 4 /* remove superfluous dots */ + +DNS_PUBLIC char *dns_d_init(void *, size_t, const void *, size_t, int); + +DNS_PUBLIC size_t dns_d_anchor(void *, size_t, const void *, size_t); + +DNS_PUBLIC size_t dns_d_cleave(void *, size_t, const void *, size_t); + +DNS_PUBLIC size_t dns_d_comp(void *, size_t, const void *, size_t, struct dns_packet *, int *); + +DNS_PUBLIC size_t dns_d_expand(void *, size_t, unsigned short, struct dns_packet *, int *); + +DNS_PUBLIC unsigned short dns_d_skip(unsigned short, struct dns_packet *); + +DNS_PUBLIC int dns_d_push(struct dns_packet *, const void *, size_t); + +DNS_PUBLIC size_t dns_d_cname(void *, size_t, const void *, size_t, struct dns_packet *, int *error); + + +/* + * R E S O U R C E R E C O R D I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_rr { + enum dns_section section; + + struct { + unsigned short p; + unsigned short len; + } dn; + + enum dns_type type; + enum dns_class class; + unsigned ttl; + + struct { + unsigned short p; + unsigned short len; + } rd; +}; /* struct dns_rr */ + + +DNS_PUBLIC int dns_rr_copy(struct dns_packet *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_rr_parse(struct dns_rr *, unsigned short, struct dns_packet *); + +DNS_PUBLIC unsigned short dns_rr_skip(unsigned short, struct dns_packet *); + +DNS_PUBLIC int dns_rr_cmp(struct dns_rr *, struct dns_packet *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC size_t dns_rr_print(void *, size_t, struct dns_rr *, struct dns_packet *, int *); + + +struct dns_rr_i { + enum dns_section section; + const void *name; + enum dns_type type; + enum dns_class class; + const void *data; + + int follow; + + int (*sort)(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); + unsigned args[2]; + + struct { + unsigned short next; + unsigned short count; + + unsigned exec; + unsigned regs[2]; + } state, saved; +}; /* struct dns_rr_i */ + +DNS_PUBLIC int dns_rr_i_packet(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); + +DNS_PUBLIC int dns_rr_i_order(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); + +DNS_PUBLIC int dns_rr_i_shuffle(struct dns_rr *, struct dns_rr *, struct dns_rr_i *, struct dns_packet *); + +DNS_PUBLIC void dns_rr_i_init(struct dns_rr_i *); + +#define dns_rr_i_save(i) ((i)->saved = (i)->state) +#define dns_rr_i_rewind(i) ((i)->state = (i)->saved) +#define dns_rr_i_count(i) ((i)->state.count) + +DNS_PUBLIC unsigned dns_rr_grep(struct dns_rr *, unsigned, struct dns_rr_i *, struct dns_packet *, int *); + +#define dns_rr_foreach_(rr, P, ...) \ + for (struct dns_rr_i DNS_PP_XPASTE(i, __LINE__) = { __VA_ARGS__ }; dns_rr_grep((rr), 1, &DNS_PP_XPASTE(i, __LINE__), (P), NULL); ) + +#define dns_rr_foreach(...) dns_rr_foreach_(__VA_ARGS__) + + +/* + * A R E S O U R C E R E C O R D + */ + +struct dns_a { + struct in_addr addr; +}; /* struct dns_a */ + +DNS_PUBLIC int dns_a_parse(struct dns_a *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_a_push(struct dns_packet *, struct dns_a *); + +DNS_PUBLIC int dns_a_cmp(const struct dns_a *, const struct dns_a *); + +DNS_PUBLIC size_t dns_a_print(void *, size_t, struct dns_a *); + +DNS_PUBLIC size_t dns_a_arpa(void *, size_t, const struct dns_a *); + + +/* + * AAAA R E S O U R C E R E C O R D + */ + +struct dns_aaaa { + struct in6_addr addr; +}; /* struct dns_aaaa */ + +DNS_PUBLIC int dns_aaaa_parse(struct dns_aaaa *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_aaaa_push(struct dns_packet *, struct dns_aaaa *); + +DNS_PUBLIC int dns_aaaa_cmp(const struct dns_aaaa *, const struct dns_aaaa *); + +DNS_PUBLIC size_t dns_aaaa_print(void *, size_t, struct dns_aaaa *); + +DNS_PUBLIC size_t dns_aaaa_arpa(void *, size_t, const struct dns_aaaa *); + + +/* + * MX R E S O U R C E R E C O R D + */ + +struct dns_mx { + unsigned short preference; + char host[DNS_D_MAXNAME + 1]; +}; /* struct dns_mx */ + +DNS_PUBLIC int dns_mx_parse(struct dns_mx *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_mx_push(struct dns_packet *, struct dns_mx *); + +DNS_PUBLIC int dns_mx_cmp(const struct dns_mx *, const struct dns_mx *); + +DNS_PUBLIC size_t dns_mx_print(void *, size_t, struct dns_mx *); + +DNS_PUBLIC size_t dns_mx_cname(void *, size_t, struct dns_mx *); + + +/* + * NS R E S O U R C E R E C O R D + */ + +struct dns_ns { + char host[DNS_D_MAXNAME + 1]; +}; /* struct dns_ns */ + +DNS_PUBLIC int dns_ns_parse(struct dns_ns *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_ns_push(struct dns_packet *, struct dns_ns *); + +DNS_PUBLIC int dns_ns_cmp(const struct dns_ns *, const struct dns_ns *); + +DNS_PUBLIC size_t dns_ns_print(void *, size_t, struct dns_ns *); + +DNS_PUBLIC size_t dns_ns_cname(void *, size_t, struct dns_ns *); + + +/* + * CNAME R E S O U R C E R E C O R D + */ + +struct dns_cname { + char host[DNS_D_MAXNAME + 1]; +}; /* struct dns_cname */ + +DNS_PUBLIC int dns_cname_parse(struct dns_cname *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_cname_push(struct dns_packet *, struct dns_cname *); + +DNS_PUBLIC int dns_cname_cmp(const struct dns_cname *, const struct dns_cname *); + +DNS_PUBLIC size_t dns_cname_print(void *, size_t, struct dns_cname *); + +DNS_PUBLIC size_t dns_cname_cname(void *, size_t, struct dns_cname *); + + +/* + * SOA R E S O U R C E R E C O R D + */ + +struct dns_soa { + char mname[DNS_D_MAXNAME + 1]; + char rname[DNS_D_MAXNAME + 1]; + unsigned serial, refresh, retry, expire, minimum; +}; /* struct dns_soa */ + +DNS_PUBLIC int dns_soa_parse(struct dns_soa *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_soa_push(struct dns_packet *, struct dns_soa *); + +DNS_PUBLIC int dns_soa_cmp(const struct dns_soa *, const struct dns_soa *); + +DNS_PUBLIC size_t dns_soa_print(void *, size_t, struct dns_soa *); + + +/* + * PTR R E S O U R C E R E C O R D + */ + +struct dns_ptr { + char host[DNS_D_MAXNAME + 1]; +}; /* struct dns_ptr */ + +DNS_PUBLIC int dns_ptr_parse(struct dns_ptr *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_ptr_push(struct dns_packet *, struct dns_ptr *); + +DNS_PUBLIC int dns_ptr_cmp(const struct dns_ptr *, const struct dns_ptr *); + +DNS_PUBLIC size_t dns_ptr_print(void *, size_t, struct dns_ptr *); + +DNS_PUBLIC size_t dns_ptr_cname(void *, size_t, struct dns_ptr *); + +DNS_PUBLIC size_t dns_ptr_qname(void *, size_t, int, void *); + + +/* + * SRV R E S O U R C E R E C O R D + */ + +struct dns_srv { + unsigned short priority; + unsigned short weight; + unsigned short port; + char target[DNS_D_MAXNAME + 1]; +}; /* struct dns_srv */ + +DNS_PUBLIC int dns_srv_parse(struct dns_srv *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_srv_push(struct dns_packet *, struct dns_srv *); + +DNS_PUBLIC int dns_srv_cmp(const struct dns_srv *, const struct dns_srv *); + +DNS_PUBLIC size_t dns_srv_print(void *, size_t, struct dns_srv *); + +DNS_PUBLIC size_t dns_srv_cname(void *, size_t, struct dns_srv *); + + +/* + * OPT R E S O U R C E R E C O R D + */ + +#ifndef DNS_OPT_MINDATA +#define DNS_OPT_MINDATA 256 +#endif + +#define DNS_OPT_DNSSEC 0x8000 + +struct dns_opt { + enum dns_rcode rcode; + unsigned char version; + unsigned short flags; + + union { + unsigned short maxsize; /* deprecated as confusing */ + unsigned short maxudp; /* maximum UDP payload size */ + }; + + size_t size, len; + unsigned char data[DNS_OPT_MINDATA]; +}; /* struct dns_opt */ + +#define DNS_OPT_INIT(opt) { .size = sizeof (*opt) - offsetof(struct dns_opt, data) } + +DNS_PUBLIC struct dns_opt *dns_opt_init(struct dns_opt *, size_t); + +DNS_PUBLIC int dns_opt_parse(struct dns_opt *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_opt_push(struct dns_packet *, struct dns_opt *); + +DNS_PUBLIC int dns_opt_cmp(const struct dns_opt *, const struct dns_opt *); + +DNS_PUBLIC size_t dns_opt_print(void *, size_t, struct dns_opt *); + +DNS_PUBLIC unsigned int dns_opt_ttl(const struct dns_opt *); + +DNS_PUBLIC unsigned short dns_opt_class(const struct dns_opt *); + +DNS_PUBLIC dns_error_t dns_opt_data_push(struct dns_opt *, unsigned char, unsigned short, const void *); + + +/* + * SSHFP R E S O U R C E R E C O R D + */ + +struct dns_sshfp { + enum dns_sshfp_key { + DNS_SSHFP_RSA = 1, + DNS_SSHFP_DSA = 2, + } algo; + + enum dns_sshfp_digest { + DNS_SSHFP_SHA1 = 1, + } type; + + union { + unsigned char sha1[20]; + } digest; +}; /* struct dns_sshfp */ + +DNS_PUBLIC int dns_sshfp_parse(struct dns_sshfp *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_sshfp_push(struct dns_packet *, struct dns_sshfp *); + +DNS_PUBLIC int dns_sshfp_cmp(const struct dns_sshfp *, const struct dns_sshfp *); + +DNS_PUBLIC size_t dns_sshfp_print(void *, size_t, struct dns_sshfp *); + + +/* + * TXT R E S O U R C E R E C O R D + */ + +#ifndef DNS_TXT_MINDATA +#define DNS_TXT_MINDATA 1024 +#endif + +struct dns_txt { + size_t size, len; + unsigned char data[DNS_TXT_MINDATA]; +}; /* struct dns_txt */ + +DNS_PUBLIC struct dns_txt *dns_txt_init(struct dns_txt *, size_t); + +DNS_PUBLIC int dns_txt_parse(struct dns_txt *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_txt_push(struct dns_packet *, struct dns_txt *); + +DNS_PUBLIC int dns_txt_cmp(const struct dns_txt *, const struct dns_txt *); + +DNS_PUBLIC size_t dns_txt_print(void *, size_t, struct dns_txt *); + + +/* + * ANY R E S O U R C E R E C O R D + */ + +union dns_any { + struct dns_a a; + struct dns_aaaa aaaa; + struct dns_mx mx; + struct dns_ns ns; + struct dns_cname cname; + struct dns_soa soa; + struct dns_ptr ptr; + struct dns_srv srv; + struct dns_opt opt; + struct dns_sshfp sshfp; + struct dns_txt txt, spf, rdata; +}; /* union dns_any */ + +#define DNS_ANY_INIT(any) { .rdata = { .size = sizeof *(any) - offsetof(struct dns_txt, data) } } + +DNS_PUBLIC union dns_any *dns_any_init(union dns_any *, size_t); + +DNS_PUBLIC int dns_any_parse(union dns_any *, struct dns_rr *, struct dns_packet *); + +DNS_PUBLIC int dns_any_push(struct dns_packet *, union dns_any *, enum dns_type); + +DNS_PUBLIC int dns_any_cmp(const union dns_any *, enum dns_type, const union dns_any *, enum dns_type); + +DNS_PUBLIC size_t dns_any_print(void *, size_t, union dns_any *, enum dns_type); + +DNS_PUBLIC size_t dns_any_cname(void *, size_t, union dns_any *, enum dns_type); + + +/* + * H O S T S I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_hosts; + +DNS_PUBLIC struct dns_hosts *dns_hosts_open(int *); + +DNS_PUBLIC void dns_hosts_close(struct dns_hosts *); + +DNS_PUBLIC dns_refcount_t dns_hosts_acquire(struct dns_hosts *); + +DNS_PUBLIC dns_refcount_t dns_hosts_release(struct dns_hosts *); + +DNS_PUBLIC struct dns_hosts *dns_hosts_mortal(struct dns_hosts *); + +DNS_PUBLIC struct dns_hosts *dns_hosts_local(int *); + +DNS_PUBLIC int dns_hosts_loadfile(struct dns_hosts *, FILE *); + +DNS_PUBLIC int dns_hosts_loadpath(struct dns_hosts *, const char *); + +DNS_PUBLIC int dns_hosts_dump(struct dns_hosts *, FILE *); + +DNS_PUBLIC int dns_hosts_insert(struct dns_hosts *, int, const void *, const void *, _Bool); + +DNS_PUBLIC struct dns_packet *dns_hosts_query(struct dns_hosts *, struct dns_packet *, int *); + + +/* + * R E S O L V . C O N F I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_resolv_conf { + struct sockaddr_storage nameserver[3]; + + char search[4][DNS_D_MAXNAME + 1]; + + /* (f)ile, (b)ind, (c)ache */ + char lookup[4 * (1 + (4 * 2))]; + + /* getaddrinfo family by preference order ("inet4", "inet6") */ + int family[3]; + + struct { + _Bool edns0; + + unsigned ndots; + + unsigned timeout; + + unsigned attempts; + + _Bool rotate; + + _Bool recurse; + + _Bool smart; + + enum { + DNS_RESCONF_TCP_ENABLE, + DNS_RESCONF_TCP_ONLY, + DNS_RESCONF_TCP_SOCKS, + DNS_RESCONF_TCP_DISABLE, + } tcp; + } options; + + struct sockaddr_storage iface; + + struct { /* PRIVATE */ + dns_atomic_t refcount; + } _; +}; /* struct dns_resolv_conf */ + +DNS_PUBLIC struct dns_resolv_conf *dns_resconf_open(int *); + +DNS_PUBLIC void dns_resconf_close(struct dns_resolv_conf *); + +DNS_PUBLIC dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *); + +DNS_PUBLIC dns_refcount_t dns_resconf_release(struct dns_resolv_conf *); + +DNS_PUBLIC struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *); + +DNS_PUBLIC struct dns_resolv_conf *dns_resconf_local(int *); + +DNS_PUBLIC struct dns_resolv_conf *dns_resconf_root(int *); + +DNS_PUBLIC int dns_resconf_pton(struct sockaddr_storage *, const char *); + +DNS_PUBLIC int dns_resconf_loadfile(struct dns_resolv_conf *, FILE *); + +DNS_PUBLIC int dns_resconf_loadpath(struct dns_resolv_conf *, const char *); + +DNS_PUBLIC int dns_nssconf_loadfile(struct dns_resolv_conf *, FILE *); + +DNS_PUBLIC int dns_nssconf_loadpath(struct dns_resolv_conf *, const char *); + +DNS_PUBLIC int dns_resconf_dump(struct dns_resolv_conf *, FILE *); + +DNS_PUBLIC int dns_nssconf_dump(struct dns_resolv_conf *, FILE *); + +DNS_PUBLIC int dns_resconf_setiface(struct dns_resolv_conf *, const char *, unsigned short); + +typedef unsigned long dns_resconf_i_t; + +DNS_PUBLIC size_t dns_resconf_search(void *, size_t, const void *, size_t, struct dns_resolv_conf *, dns_resconf_i_t *); + + +/* + * H I N T S E R V E R I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_hints; + +DNS_PUBLIC struct dns_hints *dns_hints_open(struct dns_resolv_conf *, int *); + +DNS_PUBLIC void dns_hints_close(struct dns_hints *); + +DNS_PUBLIC dns_refcount_t dns_hints_acquire(struct dns_hints *); + +DNS_PUBLIC dns_refcount_t dns_hints_release(struct dns_hints *); + +DNS_PUBLIC struct dns_hints *dns_hints_mortal(struct dns_hints *); + +DNS_PUBLIC int dns_hints_insert(struct dns_hints *, const char *, const struct sockaddr *, unsigned); + +DNS_PUBLIC unsigned dns_hints_insert_resconf(struct dns_hints *, const char *, const struct dns_resolv_conf *, int *); + +DNS_PUBLIC struct dns_hints *dns_hints_local(struct dns_resolv_conf *, int *); + +DNS_PUBLIC struct dns_hints *dns_hints_root(struct dns_resolv_conf *, int *); + +DNS_PUBLIC struct dns_packet *dns_hints_query(struct dns_hints *, struct dns_packet *, int *); + +DNS_PUBLIC int dns_hints_dump(struct dns_hints *, FILE *); + + +struct dns_hints_i { + const char *zone; + + struct { + unsigned next; + unsigned seed; + } state; +}; /* struct dns_hints_i */ + + +DNS_PUBLIC unsigned dns_hints_grep(struct sockaddr **, socklen_t *, unsigned, struct dns_hints_i *, struct dns_hints *); + + +/* + * C A C H E I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_cache { + void *state; + + dns_refcount_t (*acquire)(struct dns_cache *); + dns_refcount_t (*release)(struct dns_cache *); + + struct dns_packet *(*query)(struct dns_packet *, struct dns_cache *, int *); + + int (*submit)(struct dns_packet *, struct dns_cache *); + int (*check)(struct dns_cache *); + struct dns_packet *(*fetch)(struct dns_cache *, int *); + + int (*pollfd)(struct dns_cache *); + short (*events)(struct dns_cache *); + void (*clear)(struct dns_cache *); + + union { + long i; + void *p; + } arg[3]; + + struct { /* PRIVATE */ + dns_atomic_t refcount; + } _; +}; /* struct dns_cache */ + + +DNS_PUBLIC struct dns_cache *dns_cache_init(struct dns_cache *); + +DNS_PUBLIC void dns_cache_close(struct dns_cache *); + + +/* + * A P P L I C A T I O N I N T E R F A C E + * + * Options to change the behavior of the API. Applies across all the + * different components. + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_OPTS_INITIALIZER_ { 0, 0 }, 0, 0 +#define DNS_OPTS_INITIALIZER { DNS_OPTS_INITIALIZER_ } + +struct dns_options { + /* + * If the callback closes *fd, it must set it to -1. Otherwise, the + * descriptor is queued and lazily closed at object destruction or + * by an explicit call to _clear(). This allows safe use of + * kqueue(2), epoll(2), et al -style persistent events. + */ + struct { + void *arg; + int (*cb)(int *fd, void *arg); + } closefd; + + /* bitmask for _events() routines */ + enum dns_events { + DNS_SYSPOLL, + DNS_LIBEVENT, + } events; + + /* Use this SOCKS server. */ + const struct sockaddr_storage *socks_host; + + /* Credentials for the SOCKS server (optional). */ + const char *socks_user; + const char *socks_password; +}; /* struct dns_options */ + + +/* + * S T A T S I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_stat { + size_t queries; + + struct { + struct { + size_t count, bytes; + } sent, rcvd; + } udp, tcp; +}; /* struct dns_stat */ + + +/* + * S O C K E T I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_socket; + +DNS_PUBLIC struct dns_socket *dns_so_open(const struct sockaddr *, int, const struct dns_options *, int *error); + +DNS_PUBLIC void dns_so_close(struct dns_socket *); + +DNS_PUBLIC void dns_so_reset(struct dns_socket *); + +DNS_PUBLIC unsigned short dns_so_mkqid(struct dns_socket *so); + +DNS_PUBLIC struct dns_packet *dns_so_query(struct dns_socket *, struct dns_packet *, struct sockaddr *, int *); + +DNS_PUBLIC int dns_so_submit(struct dns_socket *, struct dns_packet *, struct sockaddr *); + +DNS_PUBLIC int dns_so_check(struct dns_socket *); + +DNS_PUBLIC struct dns_packet *dns_so_fetch(struct dns_socket *, int *); + +DNS_PUBLIC time_t dns_so_elapsed(struct dns_socket *); + +DNS_PUBLIC void dns_so_clear(struct dns_socket *); + +DNS_PUBLIC int dns_so_events(struct dns_socket *); + +DNS_PUBLIC int dns_so_pollfd(struct dns_socket *); + +DNS_PUBLIC int dns_so_poll(struct dns_socket *, int); + +DNS_PUBLIC const struct dns_stat *dns_so_stat(struct dns_socket *); + +DNS_PUBLIC struct dns_trace *dns_so_trace(struct dns_socket *); + +DNS_PUBLIC void dns_so_settrace(struct dns_socket *, struct dns_trace *); + + +/* + * R E S O L V E R I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_resolver; + +DNS_PUBLIC struct dns_resolver *dns_res_open(struct dns_resolv_conf *, struct dns_hosts *hosts, struct dns_hints *, struct dns_cache *, const struct dns_options *, int *); + +DNS_PUBLIC struct dns_resolver *dns_res_stub(const struct dns_options *, int *); + +DNS_PUBLIC void dns_res_reset(struct dns_resolver *); + +DNS_PUBLIC void dns_res_close(struct dns_resolver *); + +DNS_PUBLIC dns_refcount_t dns_res_acquire(struct dns_resolver *); + +DNS_PUBLIC dns_refcount_t dns_res_release(struct dns_resolver *); + +DNS_PUBLIC struct dns_resolver *dns_res_mortal(struct dns_resolver *); + +DNS_PUBLIC int dns_res_submit(struct dns_resolver *, const char *, enum dns_type, enum dns_class); + +DNS_PUBLIC int dns_res_submit2(struct dns_resolver *, const char *, size_t, enum dns_type, enum dns_class); + +DNS_PUBLIC int dns_res_check(struct dns_resolver *); + +DNS_PUBLIC struct dns_packet *dns_res_fetch(struct dns_resolver *, int *); + +DNS_PUBLIC time_t dns_res_elapsed(struct dns_resolver *); + +DNS_PUBLIC void dns_res_clear(struct dns_resolver *); + +DNS_PUBLIC int dns_res_events(struct dns_resolver *); + +DNS_PUBLIC int dns_res_pollfd(struct dns_resolver *); + +DNS_PUBLIC time_t dns_res_timeout(struct dns_resolver *); + +DNS_PUBLIC int dns_res_poll(struct dns_resolver *, int); + +DNS_PUBLIC struct dns_packet *dns_res_query(struct dns_resolver *, const char *, enum dns_type, enum dns_class, int, int *); + +DNS_PUBLIC const struct dns_stat *dns_res_stat(struct dns_resolver *); + +DNS_PUBLIC void dns_res_sethints(struct dns_resolver *, struct dns_hints *); + +DNS_PUBLIC struct dns_trace *dns_res_trace(struct dns_resolver *); + +DNS_PUBLIC void dns_res_settrace(struct dns_resolver *, struct dns_trace *); + + +/* + * A D D R I N F O I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +struct dns_addrinfo; + +DNS_PUBLIC struct dns_addrinfo *dns_ai_open(const char *, const char *, enum dns_type, const struct addrinfo *, struct dns_resolver *, int *); + +DNS_PUBLIC void dns_ai_close(struct dns_addrinfo *); + +DNS_PUBLIC int dns_ai_nextent(struct addrinfo **, struct dns_addrinfo *); + +DNS_PUBLIC size_t dns_ai_print(void *, size_t, struct addrinfo *, struct dns_addrinfo *); + +DNS_PUBLIC time_t dns_ai_elapsed(struct dns_addrinfo *); + +DNS_PUBLIC void dns_ai_clear(struct dns_addrinfo *); + +DNS_PUBLIC int dns_ai_events(struct dns_addrinfo *); + +DNS_PUBLIC int dns_ai_pollfd(struct dns_addrinfo *); + +DNS_PUBLIC time_t dns_ai_timeout(struct dns_addrinfo *); + +DNS_PUBLIC int dns_ai_poll(struct dns_addrinfo *, int); + +DNS_PUBLIC const struct dns_stat *dns_ai_stat(struct dns_addrinfo *); + +DNS_PUBLIC struct dns_trace *dns_ai_trace(struct dns_addrinfo *); + +DNS_PUBLIC void dns_ai_settrace(struct dns_addrinfo *, struct dns_trace *); + + +/* + * Q U E R Y T R A C I N G I N T E R F A C E + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_TRACE_ID_C(n) UINT64_C(n) +typedef uint64_t dns_trace_id_t; + +#define DNS_TRACE_ABI 0x20160803 + +struct dns_trace_event { + enum { + DNS_TE_RES_SUBMIT = 1, + DNS_TE_RES_FETCH = 99, + + DNS_TE_SO_SUBMIT = 100, + DNS_TE_SO_VERIFY, + DNS_TE_SO_FETCH = 199, + + DNS_TE_SYS_CONNECT = 200, + DNS_TE_SYS_SEND, + DNS_TE_SYS_RECV, + } type; + + size_t size; + dns_trace_id_t id; + struct timespec ts; + int abi; + + union { + struct { + char qname[DNS_D_MAXNAME + 1]; + enum dns_type qtype; + enum dns_class qclass; + int error; + } res_submit; + + struct { + int error; + } res_fetch; + + struct { + struct sockaddr_storage haddr; + char hname[DNS_D_MAXNAME + 1]; + int error; + } so_submit; + + struct { + int error; + } so_verify; + + struct { + int error; + } so_fetch; + + struct { + struct sockaddr_storage src, dst; + int socktype; + dns_error_t error; + } sys_connect, sys_send, sys_recv; + }; + + unsigned char data[]; +}; + +static inline size_t dns_te_datasize(const struct dns_trace_event *te) { + size_t n = offsetof(struct dns_trace_event, data); + return (n <= te->size)? te->size - n : 0; +} + +struct dns_trace; + +DNS_PUBLIC int dns_trace_abi(void); + +DNS_PUBLIC struct dns_trace *dns_trace_open(FILE *, dns_error_t *); + +DNS_PUBLIC void dns_trace_close(struct dns_trace *); + +DNS_PUBLIC dns_refcount_t dns_trace_acquire(struct dns_trace *); + +DNS_PUBLIC dns_refcount_t dns_trace_release(struct dns_trace *); + +DNS_PUBLIC dns_trace_id_t dns_trace_id(struct dns_trace *); + +DNS_PUBLIC dns_trace_id_t dns_trace_setid(struct dns_trace *, dns_trace_id_t); + +DNS_PUBLIC struct dns_trace_event *dns_trace_get(struct dns_trace *, struct dns_trace_event **, dns_error_t *); + +DNS_PUBLIC struct dns_trace_event *dns_trace_tag(struct dns_trace *, struct dns_trace_event *); + +DNS_PUBLIC dns_error_t dns_trace_put(struct dns_trace *, const struct dns_trace_event *, const void *, size_t); + +DNS_PUBLIC dns_error_t dns_trace_dump(struct dns_trace *, FILE *); + +DNS_PUBLIC struct dns_trace_event *dns_trace_fget(struct dns_trace_event **, FILE *, dns_error_t *); + +DNS_PUBLIC dns_error_t dns_trace_fput(const struct dns_trace_event *, const void *, size_t, FILE *); + + +/* + * U T I L I T Y I N T E R F A C E S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +DNS_PUBLIC size_t dns_strlcpy(char *, const char *, size_t); + +DNS_PUBLIC size_t dns_strlcat(char *, const char *, size_t); + + +/* + * M A C R O M A G I C S + * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#define DNS_PP_MIN(a, b) (((a) < (b))? (a) : (b)) +#define DNS_PP_MAX(a, b) (((a) > (b))? (a) : (b)) +#define DNS_PP_NARG_(a, b, c, d, e, f, g, h, i, j, k, N,...) N +#define DNS_PP_NARG(...) DNS_PP_NARG_(__VA_ARGS__, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define DNS_PP_CALL(F, ...) F(__VA_ARGS__) +#define DNS_PP_PASTE(x, y) x##y +#define DNS_PP_XPASTE(x, y) DNS_PP_PASTE(x, y) +#define DNS_PP_STRINGIFY_(s) #s +#define DNS_PP_STRINGIFY(s) DNS_PP_STRINGIFY_(s) +#define DNS_PP_D1 0 +#define DNS_PP_D2 1 +#define DNS_PP_D3 2 +#define DNS_PP_D4 3 +#define DNS_PP_D5 4 +#define DNS_PP_D6 5 +#define DNS_PP_D7 6 +#define DNS_PP_D8 7 +#define DNS_PP_D9 8 +#define DNS_PP_D10 9 +#define DNS_PP_D11 10 +#define DNS_PP_DEC(N) DNS_PP_XPASTE(DNS_PP_D, N) + +#endif /* DNS_H */ diff --git a/dirmngr/domaininfo.c b/dirmngr/domaininfo.c new file mode 100644 index 0000000..2e55842 --- /dev/null +++ b/dirmngr/domaininfo.c @@ -0,0 +1,378 @@ +/* domaininfo.c - Gather statistics about accessed domains + * Copyright (C) 2017 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include <config.h> +#include <stdlib.h> +#include <string.h> + +#include "dirmngr.h" + + +/* Number of bucket for the hash array and limit for the length of a + * bucket chain. For debugging values of 13 and 10 are more suitable + * and a command like + * for j in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \ + * for i in a b c d e f g h i j k l m n o p q r s t u v w z y z; do \ + * gpg-connect-agent --dirmngr "wkd_get foo@$i.$j.gnupg.net" /bye \ + * >/dev/null ; done; done + * will quickly add a couple of domains. + */ +#define NO_OF_DOMAINBUCKETS 103 +#define MAX_DOMAINBUCKET_LEN 20 + + +/* Object to keep track of a domain name. */ +struct domaininfo_s +{ + struct domaininfo_s *next; + unsigned int no_name:1; /* Domain name not found. */ + unsigned int wkd_not_found:1; /* A WKD query failed. */ + unsigned int wkd_supported:1; /* One WKD entry was found. */ + unsigned int wkd_not_supported:1; /* Definitely does not support WKD. */ + unsigned int keepmark:1; /* Private to insert_or_update(). */ + char name[1]; +}; +typedef struct domaininfo_s *domaininfo_t; + +/* And the hashed array. */ +static domaininfo_t domainbuckets[NO_OF_DOMAINBUCKETS]; + + +/* The hash function we use. Must not call a system function. */ +static inline u32 +hash_domain (const char *domain) +{ + const unsigned char *s = (const unsigned char*)domain; + u32 hashval = 0; + u32 carry; + + for (; *s; s++) + { + if (*s == '.') + continue; + hashval = (hashval << 4) + *s; + if ((carry = (hashval & 0xf0000000))) + { + hashval ^= (carry >> 24); + hashval ^= carry; + } + } + + return hashval % NO_OF_DOMAINBUCKETS; +} + + +void +domaininfo_print_stats (void) +{ + int bidx; + domaininfo_t di; + int count, no_name, wkd_not_found, wkd_supported, wkd_not_supported; + int len, minlen, maxlen; + + count = no_name = wkd_not_found = wkd_supported = wkd_not_supported = 0; + maxlen = 0; + minlen = -1; + for (bidx = 0; bidx < NO_OF_DOMAINBUCKETS; bidx++) + { + len = 0; + for (di = domainbuckets[bidx]; di; di = di->next) + { + count++; + len++; + if (di->no_name) + no_name++; + if (di->wkd_not_found) + wkd_not_found++; + if (di->wkd_supported) + wkd_supported++; + if (di->wkd_not_supported) + wkd_not_supported++; + } + if (len > maxlen) + maxlen = len; + if (minlen == -1 || len < minlen) + minlen = len; + } + log_info ("domaininfo: items=%d chainlen=%d..%d nn=%d nf=%d ns=%d s=%d\n", + count, + minlen > 0? minlen : 0, + maxlen, + no_name, wkd_not_found, wkd_not_supported, wkd_supported); +} + + +/* Return true if DOMAIN definitely does not support WKD. Noet that + * DOMAIN is expected to be lowercase. */ +int +domaininfo_is_wkd_not_supported (const char *domain) +{ + domaininfo_t di; + + for (di = domainbuckets[hash_domain (domain)]; di; di = di->next) + if (!strcmp (di->name, domain)) + return !!di->wkd_not_supported; + + return 0; /* We don't know. */ +} + + +/* Core update function. DOMAIN is expected to be lowercase. + * CALLBACK is called to update the existing or the newly inserted + * item. */ +static void +insert_or_update (const char *domain, + void (*callback)(domaininfo_t di, int insert_mode)) +{ + domaininfo_t di; + domaininfo_t di_new; + domaininfo_t drop = NULL; + domaininfo_t drop_extra = NULL; + int nkept = 0; + int ndropped = 0; + u32 hash; + int count; + + hash = hash_domain (domain); + for (di = domainbuckets[hash]; di; di = di->next) + if (!strcmp (di->name, domain)) + { + callback (di, 0); /* Update */ + return; + } + + di_new = xtrycalloc (1, sizeof *di + strlen (domain)); + if (!di_new) + return; /* Out of core - we ignore this. */ + strcpy (di_new->name, domain); + + /* Need to do another lookup because the malloc is a system call and + * thus the hash array may have been changed by another thread. */ + for (count=0, di = domainbuckets[hash]; di; di = di->next, count++) + if (!strcmp (di->name, domain)) + { + callback (di, 0); /* Update */ + xfree (di_new); + return; + } + + /* Before we insert we need to check whether the chain gets too long. */ + if (count >= MAX_DOMAINBUCKET_LEN) + { + domaininfo_t bucket; + domaininfo_t *array; + int narray, idx; + domaininfo_t keep = NULL; + + /* Unlink from the global list before doing a syscall. */ + bucket = domainbuckets[hash]; + domainbuckets[hash] = NULL; + + array = xtrycalloc (count, sizeof *array); + if (!array) + { + /* That's bad; give up the entire bucket. */ + log_error ("domaininfo: error allocating helper array: %s\n", + gpg_strerror (gpg_err_code_from_syserror ())); + drop_extra = bucket; + goto leave; + } + narray = 0; + + /* Move all items into an array for easier processing. */ + for (di = bucket; di; di = di->next) + array[narray++] = di; + log_assert (narray == count); + + /* Mark all item in the array which are flagged to support wkd + * but not more than half of the maximum. This way we will at + * the end drop half of the items. */ + count = 0; + for (idx=0; idx < narray; idx++) + { + di = array[idx]; + di->keepmark = 0; /* Clear flag here on the first pass. */ + if (di->wkd_supported && count < MAX_DOMAINBUCKET_LEN/2) + { + di->keepmark = 1; + count++; + } + } + /* Now mark those which are marked as not found. */ + /* FIXME: we should use an LRU algorithm here. */ + for (idx=0; idx < narray; idx++) + { + di = array[idx]; + if (!di->keepmark + && di->wkd_not_supported && count < MAX_DOMAINBUCKET_LEN/2) + { + di->keepmark = 1; + count++; + } + } + + /* Build a bucket list and a second list for later freeing the + * items (we can't do it directly because a free is a system + * call and we want to avoid locks in this module. Note that + * the kept items will be reversed order which does not matter. */ + for (idx=0; idx < narray; idx++) + { + di = array[idx]; + if (di->keepmark) + { + di->next = keep; + keep = di; + nkept++; + } + else + { + di->next = drop; + drop = di; + ndropped++; + } + } + + /* In case another thread added new stuff to the domain list we + * simply drop them instead all. It would also be possible to + * append them to our list but then we can't guarantee that a + * bucket list is almost all of the time limited to + * MAX_DOMAINBUCKET_LEN. Not sure whether this is really a + * sensible strategy. */ + drop_extra = domainbuckets[hash]; + domainbuckets[hash] = keep; + } + + /* Insert */ + callback (di_new, 1); + di = di_new; + di->next = domainbuckets[hash]; + domainbuckets[hash] = di; + + if (opt.verbose && (nkept || ndropped)) + log_info ("domaininfo: bucket=%lu kept=%d purged=%d\n", + (unsigned long)hash, nkept, ndropped); + + leave: + /* Remove the dropped items. */ + while (drop) + { + di = drop->next; + xfree (drop); + drop = di; + } + while (drop_extra) + { + di = drop_extra->next; + xfree (drop_extra); + drop_extra = di; + } +} + + +/* Helper for domaininfo_set_no_name. May not do any syscalls. */ +static void +set_no_name_cb (domaininfo_t di, int insert_mode) +{ + (void)insert_mode; + + di->no_name = 1; + /* Obviously the domain is in this case also not supported. */ + di->wkd_not_supported = 1; + + /* The next should already be 0 but we clear it anyway in the case + * of a temporary DNS failure. */ + di->wkd_supported = 0; +} + + +/* Mark DOMAIN as not existent. */ +void +domaininfo_set_no_name (const char *domain) +{ + insert_or_update (domain, set_no_name_cb); +} + + +/* Helper for domaininfo_set_wkd_supported. May not do any syscalls. */ +static void +set_wkd_supported_cb (domaininfo_t di, int insert_mode) +{ + (void)insert_mode; + + di->wkd_supported = 1; + /* The next will already be set unless the domain enabled WKD in the + * meantime. Thus we need to clear it. */ + di->wkd_not_supported = 0; +} + + +/* Mark DOMAIN as supporting WKD. */ +void +domaininfo_set_wkd_supported (const char *domain) +{ + insert_or_update (domain, set_wkd_supported_cb); +} + + +/* Helper for domaininfo_set_wkd_not_supported. May not do any syscalls. */ +static void +set_wkd_not_supported_cb (domaininfo_t di, int insert_mode) +{ + (void)insert_mode; + + di->wkd_not_supported = 1; + di->wkd_supported = 0; +} + + +/* Mark DOMAIN as not supporting WKD queries (e.g. no policy file). */ +void +domaininfo_set_wkd_not_supported (const char *domain) +{ + insert_or_update (domain, set_wkd_not_supported_cb); +} + + + +/* Helper for domaininfo_set_wkd_not_found. May not do any syscalls. */ +static void +set_wkd_not_found_cb (domaininfo_t di, int insert_mode) +{ + /* Set the not found flag but there is no need to do this if we + * already know that the domain either does not support WKD or we + * know that it supports WKD. */ + if (insert_mode) + di->wkd_not_found = 1; + else if (!di->wkd_not_supported && !di->wkd_supported) + di->wkd_not_found = 1; + + /* Better clear this flag in case we had a DNS failure in the + * past. */ + di->no_name = 0; +} + + +/* Update a counter for DOMAIN to keep track of failed WKD queries. */ +void +domaininfo_set_wkd_not_found (const char *domain) +{ + insert_or_update (domain, set_wkd_not_found_cb); +} diff --git a/dirmngr/http-common.c b/dirmngr/http-common.c new file mode 100644 index 0000000..3b6cd44 --- /dev/null +++ b/dirmngr/http-common.c @@ -0,0 +1,50 @@ +/* http-common.c - Common support for TLS implementations. + * Copyright (C) 2017 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "dirmngr.h" +#include "http-common.h" + + +/* Return a static string with the default keyserver. If NAME_ONLY is + * given only the name part is returned. */ +const char * +get_default_keyserver (int name_only) +{ + static const char *result; + + if (!name_only) + return DIRMNGR_DEFAULT_KEYSERVER; + + if (!result) + { + /* Strip the scheme from the constant. */ + result = strstr (DIRMNGR_DEFAULT_KEYSERVER, "://"); + log_assert (result && strlen (result) > 3); + result += 3; + /* Assert that there is no port given. */ + log_assert (!strchr (result, ':')); + } + return result; +} diff --git a/dirmngr/http-common.h b/dirmngr/http-common.h new file mode 100644 index 0000000..5e6657b --- /dev/null +++ b/dirmngr/http-common.h @@ -0,0 +1,25 @@ +/* http-common.h - Defs for common support for TLS implementations. + * Copyright (C) 2017 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef HTTP_COMMON_H +#define HTTP_COMMON_H + +const char *get_default_keyserver (int name_only); + +#endif /* HTTP_COMMON_H */ diff --git a/dirmngr/http-ntbtls.c b/dirmngr/http-ntbtls.c new file mode 100644 index 0000000..58388b2 --- /dev/null +++ b/dirmngr/http-ntbtls.c @@ -0,0 +1,134 @@ +/* http-ntbtls.c - Support for using NTBTLS with http.c + * Copyright (C) 2017 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "dirmngr.h" +#include "certcache.h" +#include "validate.h" +#include "http-common.h" + +#ifdef HTTP_USE_NTBTLS +# include <ntbtls.h> + + +/* The callback used to verify the peer's certificate. */ +gpg_error_t +gnupg_http_tls_verify_cb (void *opaque, + http_t http, + http_session_t session, + unsigned int http_flags, + void *tls_context) +{ + ctrl_t ctrl = opaque; + ntbtls_t tls = tls_context; + gpg_error_t err; + int idx; + ksba_cert_t cert; + ksba_cert_t hostcert = NULL; + unsigned int validate_flags; + const char *hostname; + + (void)http; + (void)session; + + log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC); + log_assert (!ntbtls_check_context (tls)); + + /* Get the peer's certs fron ntbtls. */ + for (idx = 0; + (cert = ntbtls_x509_get_peer_cert (tls, idx)); idx++) + { + if (!idx) + hostcert = cert; + else + { + /* Quick hack to make verification work by inserting the supplied + * certs into the cache. FIXME! */ + cache_cert (cert); + ksba_cert_release (cert); + } + } + if (!idx) + { + err = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + validate_flags = VALIDATE_FLAG_TLS; + + /* If we are using the standard hkps:// pool use the dedicated root + * certificate. Note that this differes from the GnuTLS + * implementation which uses this special certificate only if no + * other certificates are configured. */ + hostname = ntbtls_get_hostname (tls); + if (hostname + && !ascii_strcasecmp (hostname, get_default_keyserver (1))) + { + validate_flags |= VALIDATE_FLAG_TRUST_HKPSPOOL; + } + else /* Use the certificates as requested from the HTTP module. */ + { + if ((http_flags & HTTP_FLAG_TRUST_CFG)) + validate_flags |= VALIDATE_FLAG_TRUST_CONFIG; + if ((http_flags & HTTP_FLAG_TRUST_DEF)) + validate_flags |= VALIDATE_FLAG_TRUST_HKP; + if ((http_flags & HTTP_FLAG_TRUST_SYS)) + validate_flags |= VALIDATE_FLAG_TRUST_SYSTEM; + + /* If HKP trust is requested and there are no HKP certificates + * configured, also try the standard system certificates. */ + if ((validate_flags & VALIDATE_FLAG_TRUST_HKP) + && !cert_cache_any_in_class (CERTTRUST_CLASS_HKP)) + validate_flags |= VALIDATE_FLAG_TRUST_SYSTEM; + } + + if ((http_flags & HTTP_FLAG_NO_CRL)) + validate_flags |= VALIDATE_FLAG_NOCRLCHECK; + + err = validate_cert_chain (ctrl, hostcert, NULL, validate_flags, NULL); + + leave: + ksba_cert_release (hostcert); + return err; +} + + +#else /*!HTTP_USE_NTBTLS*/ + +/* Dummy function used when not build without ntbtls support. */ +gpg_error_t +gnupg_http_tls_verify_cb (void *opaque, + http_t http, + http_session_t session, + unsigned int flags, + void *tls_context) +{ + (void)opaque; + (void)http; + (void)session; + (void)flags; + (void)tls_context; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +} +#endif /*!HTTP_USE_NTBTLS*/ diff --git a/dirmngr/http.c b/dirmngr/http.c new file mode 100644 index 0000000..5e3f17c --- /dev/null +++ b/dirmngr/http.c @@ -0,0 +1,3780 @@ +/* http.c - HTTP protocol handler + * Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010, + * 2011 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * Copyright (C) 2015-2017 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * This file 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 <https://www.gnu.org/licenses/>. + */ + +/* Simple HTTP client implementation. We try to keep the code as + self-contained as possible. There are some constraints 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_NTBTLS or 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 +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> +#else /*!HAVE_W32_SYSTEM*/ +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/time.h> +# include <time.h> +# include <fcntl.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# include <netdb.h> +#endif /*!HAVE_W32_SYSTEM*/ + +#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */ +# undef USE_NPTH +#endif + +#ifdef USE_NPTH +# include <npth.h> +#endif + +#if defined (HTTP_USE_GNUTLS) && defined (HTTP_USE_NTBTLS) +# error Both, HTTP_USE_GNUTLS and HTTP_USE_NTBTLS, are defined. +#endif + +#ifdef HTTP_USE_NTBTLS +# include <ntbtls.h> +#elif HTTP_USE_GNUTLS +# include <gnutls/gnutls.h> +# include <gnutls/x509.h> +#endif /*HTTP_USE_GNUTLS*/ + +#include <assuan.h> /* We need the socket wrapper. */ + +#include "../common/util.h" +#include "../common/i18n.h" +#include "../common/sysutils.h" /* (gnupg_fd_t) */ +#include "dns-stuff.h" +#include "dirmngr-status.h" /* (dirmngr_status_printf) */ +#include "http.h" +#include "http-common.h" + + +#ifdef USE_NPTH +# define my_select(a,b,c,d,e) npth_select ((a), (b), (c), (d), (e)) +# define my_accept(a,b,c) npth_accept ((a), (b), (c)) +#else +# define my_select(a,b,c,d,e) select ((a), (b), (c), (d), (e)) +# define my_accept(a,b,c) accept ((a), (b), (c)) +#endif + +#ifdef HAVE_W32_SYSTEM +#define sock_close(a) closesocket(a) +#else +#define sock_close(a) close(a) +#endif + +#ifndef EAGAIN +#define EAGAIN EWOULDBLOCK +#endif +#ifndef INADDR_NONE /* Slowaris is missing that. */ +#define INADDR_NONE ((unsigned long)(-1)) +#endif /*INADDR_NONE*/ + +#define HTTP_PROXY_ENV "http_proxy" +#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */ +#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "01234567890@" \ + "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" + +#if HTTP_USE_NTBTLS +typedef ntbtls_t tls_session_t; +# define USE_TLS 1 +#elif HTTP_USE_GNUTLS +typedef gnutls_session_t tls_session_t; +# define USE_TLS 1 +#else +typedef void *tls_session_t; +# undef USE_TLS +#endif + +static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part, + int no_scheme_check, int force_tls); +static gpg_error_t parse_uri (parsed_uri_t *ret_uri, const char *uri, + int no_scheme_check, int force_tls); +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 *httphost, + const char *auth,const char *proxy, + const char *srvtag, unsigned int timeout, + strlist_t headers); +static char *build_rel_path (parsed_uri_t uri); +static gpg_error_t parse_response (http_t hd); + +static gpg_error_t connect_server (const char *server, unsigned short port, + unsigned int flags, const char *srvtag, + unsigned int timeout, assuan_fd_t *r_sock); +static gpgrt_ssize_t read_server (assuan_fd_t sock, void *buffer, size_t size); +static gpg_error_t write_server (assuan_fd_t sock, const char *data, size_t length); + +static gpgrt_ssize_t cookie_read (void *cookie, void *buffer, size_t size); +static gpgrt_ssize_t cookie_write (void *cookie, + const void *buffer, size_t size); +static int cookie_close (void *cookie); +#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS) +static gpgrt_ssize_t simple_cookie_read (void *cookie, + void *buffer, size_t size); +static gpgrt_ssize_t simple_cookie_write (void *cookie, + const void *buffer, size_t size); +#endif + +/* A socket object used to a allow ref counting of sockets. */ +struct my_socket_s +{ + assuan_fd_t fd; /* The actual socket - shall never be ASSUAN_INVALID_FD. */ + int refcount; /* Number of references to this socket. */ +}; +typedef struct my_socket_s *my_socket_t; + + +/* Cookie function structure and cookie object. */ +static es_cookie_io_functions_t cookie_functions = + { + cookie_read, + cookie_write, + NULL, + cookie_close + }; + + +struct cookie_s +{ + /* Socket object or NULL if already closed. */ + my_socket_t sock; + + /* The session object or NULL if not used. */ + http_session_t session; + + /* True if TLS is to be used. */ + int use_tls; + + /* The remaining content length and a flag telling whether to use + the content length. */ + uint64_t content_length; + unsigned int content_length_valid:1; +}; +typedef struct cookie_s *cookie_t; + + +/* Simple cookie functions. Here the cookie is an int with the + * socket. */ +#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS) +static es_cookie_io_functions_t simple_cookie_functions = + { + simple_cookie_read, + simple_cookie_write, + NULL, + NULL + }; +#endif + + +#if SIZEOF_UNSIGNED_LONG == 8 +# define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */ +#else +# define HTTP_SESSION_MAGIC 0x68547365 /* "hTse" */ +#endif + +/* The session object. */ +struct http_session_s +{ + unsigned long magic; + + int refcount; /* Number of references to this object. */ +#ifdef HTTP_USE_GNUTLS + gnutls_certificate_credentials_t certcred; +#endif /*HTTP_USE_GNUTLS*/ +#ifdef USE_TLS + tls_session_t tls_session; + struct { + int done; /* Verifciation has been done. */ + int rc; /* TLS verification return code. */ + unsigned int status; /* Verification status. */ + } verify; + char *servername; /* Malloced server name. */ +#endif /*USE_TLS*/ + /* A callback function to log details of TLS certifciates. */ + void (*cert_log_cb) (http_session_t, gpg_error_t, const char *, + const void **, size_t *); + + /* The flags passed to the session object. */ + unsigned int flags; + + /* A per-session TLS verification callback. */ + http_verify_cb_t verify_cb; + void *verify_cb_value; + + /* The connect timeout */ + unsigned int connect_timeout; +}; + + +/* 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; + + +#if SIZEOF_UNSIGNED_LONG == 8 +# define HTTP_CONTEXT_MAGIC 0x0068545470435458 /* "hTTpCTX" */ +#else +# define HTTP_CONTEXT_MAGIC 0x68546378 /* "hTcx" */ +#endif + + +/* Our handle context. */ +struct http_context_s +{ + unsigned long magic; + unsigned int status_code; + my_socket_t 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; + http_session_t session; + 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. */ +}; + + +/* Two flags to enable verbose and debug mode. Although currently not + * set-able a value > 1 for OPT_DEBUG enables debugging of the session + * reference counting. */ +static int opt_verbose; +static int opt_debug; + +/* The global callback for the verification function. */ +static gpg_error_t (*tls_callback) (http_t, http_session_t, int); + +/* The list of files with trusted CA certificates. */ +static strlist_t tls_ca_certlist; + +/* The list of files with extra trusted CA certificates. */ +static strlist_t cfg_ca_certlist; + +/* The global callback for net activity. */ +static void (*netactivity_cb)(void); + + + +#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*/ + + +/* Create a new socket object. Returns NULL and closes FD if not + enough memory is available. */ +static my_socket_t +_my_socket_new (int lnr, assuan_fd_t fd) +{ + my_socket_t so; + + so = xtrymalloc (sizeof *so); + if (!so) + { + int save_errno = errno; + assuan_sock_close (fd); + gpg_err_set_errno (save_errno); + return NULL; + } + so->fd = fd; + so->refcount = 1; + if (opt_debug) + log_debug ("http.c:%d:socket_new: object %p for fd %d created\n", + lnr, so, (int)so->fd); + return so; +} +#define my_socket_new(a) _my_socket_new (__LINE__, (a)) + +/* Bump up the reference counter for the socket object SO. */ +static my_socket_t +_my_socket_ref (int lnr, my_socket_t so) +{ + so->refcount++; + if (opt_debug > 1) + log_debug ("http.c:%d:socket_ref: object %p for fd %d refcount now %d\n", + lnr, so, (int)so->fd, so->refcount); + return so; +} +#define my_socket_ref(a) _my_socket_ref (__LINE__,(a)) + + +/* Bump down the reference counter for the socket object SO. If SO + has no more references, close the socket and release the + object. */ +static void +_my_socket_unref (int lnr, my_socket_t so, + void (*preclose)(void*), void *preclosearg) +{ + if (so) + { + so->refcount--; + if (opt_debug > 1) + log_debug ("http.c:%d:socket_unref: object %p for fd %d ref now %d\n", + lnr, so, (int)so->fd, so->refcount); + + if (!so->refcount) + { + if (preclose) + preclose (preclosearg); + assuan_sock_close (so->fd); + xfree (so); + } + } +} +#define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c)) + + +#ifdef HTTP_USE_GNUTLS +static ssize_t +my_gnutls_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size) +{ + my_socket_t sock = ptr; +#if USE_NPTH + return npth_read (sock->fd, buffer, size); +#else + return read (sock->fd, buffer, size); +#endif +} +static ssize_t +my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size) +{ + my_socket_t sock = ptr; +#if USE_NPTH + return npth_write (sock->fd, buffer, size); +#else + return write (sock->fd, buffer, size); +#endif +} +#endif /*HTTP_USE_GNUTLS*/ + + +#ifdef HTTP_USE_NTBTLS +/* Connect the ntbls callback to our generic callback. */ +static gpg_error_t +my_ntbtls_verify_cb (void *opaque, ntbtls_t tls, unsigned int verify_flags) +{ + http_t hd = opaque; + + (void)verify_flags; + + log_assert (hd && hd->session && hd->session->verify_cb); + log_assert (hd->magic == HTTP_CONTEXT_MAGIC); + log_assert (hd->session->magic == HTTP_SESSION_MAGIC); + + return hd->session->verify_cb (hd->session->verify_cb_value, + hd, hd->session, + (hd->flags | hd->session->flags), + tls); +} +#endif /*HTTP_USE_NTBTLS*/ + + + + +/* This notification function is called by estream whenever stream is + closed. Its purpose is to mark the closing in the handle so + that a http_close won't accidentally close the estream. The function + http_close removes this notification so that it won't be called if + http_close was used before an es_fclose. */ +static void +fp_onclose_notification (estream_t stream, void *opaque) +{ + http_t hd = opaque; + + log_assert (hd->magic == HTTP_CONTEXT_MAGIC); + if (hd->fp_read && hd->fp_read == stream) + hd->fp_read = NULL; + else if (hd->fp_write && hd->fp_write == stream) + hd->fp_write = NULL; +} + + +/* + * 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 char *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]; + *p = 0; + } + 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++ = '='; + } + *p = 0; + strcpy (p, suffix); + return buffer; +} + + + + +/* Set verbosity and debug mode for this module. */ +void +http_set_verbose (int verbose, int debug) +{ + opt_verbose = verbose; + opt_debug = debug; +} + + +/* Register a non-standard global TLS callback function. If no + verification is desired a callback needs to be registered which + always returns NULL. */ +void +http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int)) +{ + tls_callback = cb; +} + + +/* Register a CA certificate for future use. The certificate is + expected to be in FNAME. PEM format is assume if FNAME has a + suffix of ".pem". If FNAME is NULL the list of CA files is + removed. */ +void +http_register_tls_ca (const char *fname) +{ + gpg_err_code_t ec; + strlist_t sl; + + if (!fname) + { + free_strlist (tls_ca_certlist); + tls_ca_certlist = NULL; + } + else + { + /* Warn if we can't access right now, but register it anyway in + case it becomes accessible later */ + if ((ec = gnupg_access (fname, F_OK))) + log_info (_("can't access '%s': %s\n"), fname, gpg_strerror (ec)); + sl = add_to_strlist (&tls_ca_certlist, fname); + if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem")) + sl->flags = 1; + } +} + + +/* Register a CA certificate for future use. The certificate is + * expected to be in FNAME. PEM format is assume if FNAME has a + * suffix of ".pem". If FNAME is NULL the list of CA files is + * removed. This is a variant of http_register_tls_ca which puts the + * certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG. */ +void +http_register_cfg_ca (const char *fname) +{ + gpg_err_code_t ec; + strlist_t sl; + + if (!fname) + { + free_strlist (cfg_ca_certlist); + cfg_ca_certlist = NULL; + } + else + { + /* Warn if we can't access right now, but register it anyway in + case it becomes accessible later */ + if ((ec = gnupg_access (fname, F_OK))) + log_info (_("can't access '%s': %s\n"), fname, gpg_strerror (ec)); + sl = add_to_strlist (&cfg_ca_certlist, fname); + if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem")) + sl->flags = 1; + } +} + + +/* Register a callback which is called every time the HTTP mode has + * made a successful connection to some server. */ +void +http_register_netactivity_cb (void (*cb)(void)) +{ + netactivity_cb = cb; +} + + +/* Call the netactivity callback if any. */ +static void +notify_netactivity (void) +{ + if (netactivity_cb) + netactivity_cb (); +} + + + +#ifdef USE_TLS +/* Free the TLS session associated with SESS, if any. */ +static void +close_tls_session (http_session_t sess) +{ + if (sess->tls_session) + { +# if HTTP_USE_NTBTLS + /* FIXME!! + Possibly, ntbtls_get_transport and close those streams. + Somehow get SOCK to call my_socket_unref. + */ + ntbtls_release (sess->tls_session); +# elif HTTP_USE_GNUTLS + my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session); + my_socket_unref (sock, NULL, NULL); + gnutls_deinit (sess->tls_session); + if (sess->certcred) + gnutls_certificate_free_credentials (sess->certcred); +# endif /*HTTP_USE_GNUTLS*/ + xfree (sess->servername); + sess->tls_session = NULL; + } +} +#endif /*USE_TLS*/ + + +/* Release a session. Take care not to release it while it is being + used by a http context object. */ +static void +session_unref (int lnr, http_session_t sess) +{ + if (!sess) + return; + + log_assert (sess->magic == HTTP_SESSION_MAGIC); + + sess->refcount--; + if (opt_debug > 1) + log_debug ("http.c:%d:session_unref: sess %p ref now %d\n", + lnr, sess, sess->refcount); + if (sess->refcount) + return; + +#ifdef USE_TLS + close_tls_session (sess); +#endif /*USE_TLS*/ + + sess->magic = 0xdeadbeef; + xfree (sess); +} +#define http_session_unref(a) session_unref (__LINE__, (a)) + +void +http_session_release (http_session_t sess) +{ + http_session_unref (sess); +} + + +/* Create a new session object which is currently used to enable TLS + * support. It may eventually allow reusing existing connections. + * Valid values for FLAGS are: + * HTTP_FLAG_TRUST_DEF - Use the CAs set with http_register_tls_ca + * HTTP_FLAG_TRUST_SYS - Also use the CAs defined by the system + * HTTP_FLAG_TRUST_CFG - Also use CAs set with http_register_cfg_ca + * HTTP_FLAG_NO_CRL - Do not consult CRLs for https. + */ +gpg_error_t +http_session_new (http_session_t *r_session, + const char *intended_hostname, unsigned int flags, + http_verify_cb_t verify_cb, void *verify_cb_value) +{ + gpg_error_t err; + http_session_t sess; + + *r_session = NULL; + + sess = xtrycalloc (1, sizeof *sess); + if (!sess) + return gpg_error_from_syserror (); + sess->magic = HTTP_SESSION_MAGIC; + sess->refcount = 1; + sess->flags = flags; + sess->verify_cb = verify_cb; + sess->verify_cb_value = verify_cb_value; + sess->connect_timeout = 0; + +#if HTTP_USE_NTBTLS + { + (void)intended_hostname; /* Not needed because we do not preload + * certificates. */ + + err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT); + if (err) + { + log_error ("ntbtls_new failed: %s\n", gpg_strerror (err)); + goto leave; + } + + } +#elif HTTP_USE_GNUTLS + { + const char *errpos; + int rc; + strlist_t sl; + int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS); + int is_hkps_pool; + + rc = gnutls_certificate_allocate_credentials (&sess->certcred); + if (rc < 0) + { + log_error ("gnutls_certificate_allocate_credentials failed: %s\n", + gnutls_strerror (rc)); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + is_hkps_pool = (intended_hostname + && !ascii_strcasecmp (intended_hostname, + get_default_keyserver (1))); + + /* If we are looking for the hkps pool from sks-keyservers.net, + * then forcefully use its dedicated certificate authority. */ + if (is_hkps_pool) + { + char *pemname = make_filename_try (gnupg_datadir (), + "sks-keyservers.netCA.pem", NULL); + if (!pemname) + { + err = gpg_error_from_syserror (); + log_error ("setting CA from file '%s' failed: %s\n", + pemname, gpg_strerror (err)); + } + else + { + rc = gnutls_certificate_set_x509_trust_file + (sess->certcred, pemname, GNUTLS_X509_FMT_PEM); + if (rc < 0) + log_info ("setting CA from file '%s' failed: %s\n", + pemname, gnutls_strerror (rc)); + xfree (pemname); + } + + if (is_hkps_pool) + add_system_cas = 0; + } + + /* Add configured certificates to the session. */ + if ((flags & HTTP_FLAG_TRUST_DEF) && !is_hkps_pool) + { + for (sl = tls_ca_certlist; sl; sl = sl->next) + { + rc = gnutls_certificate_set_x509_trust_file + (sess->certcred, sl->d, + (sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER); + if (rc < 0) + log_info ("setting CA from file '%s' failed: %s\n", + sl->d, gnutls_strerror (rc)); + } + + /* If HKP trust is requested and there are no HKP certificates + * configured, also try the standard system certificates. */ + if (!tls_ca_certlist) + add_system_cas = 1; + } + + /* Add system certificates to the session. */ + if (add_system_cas) + { +#if GNUTLS_VERSION_NUMBER >= 0x030014 + static int shown; + + rc = gnutls_certificate_set_x509_system_trust (sess->certcred); + if (rc < 0) + log_info ("setting system CAs failed: %s\n", gnutls_strerror (rc)); + else if (!shown) + { + shown = 1; + log_info ("number of system provided CAs: %d\n", rc); + } +#endif /* gnutls >= 3.0.20 */ + } + + /* Add other configured certificates to the session. */ + if ((flags & HTTP_FLAG_TRUST_CFG) && !is_hkps_pool) + { + for (sl = cfg_ca_certlist; sl; sl = sl->next) + { + rc = gnutls_certificate_set_x509_trust_file + (sess->certcred, sl->d, + (sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER); + if (rc < 0) + log_info ("setting extra CA from file '%s' failed: %s\n", + sl->d, gnutls_strerror (rc)); + } + } + + + rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT); + if (rc < 0) + { + log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc)); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + /* A new session has the transport ptr set to (void*(-1), we need + it to be NULL. */ + gnutls_transport_set_ptr (sess->tls_session, NULL); + + rc = gnutls_priority_set_direct (sess->tls_session, + "NORMAL", + &errpos); + if (rc < 0) + { + log_error ("gnutls_priority_set_direct failed at '%s': %s\n", + errpos, gnutls_strerror (rc)); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + + rc = gnutls_credentials_set (sess->tls_session, + GNUTLS_CRD_CERTIFICATE, sess->certcred); + if (rc < 0) + { + log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc)); + err = gpg_error (GPG_ERR_GENERAL); + goto leave; + } + } +#else /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/ + { + (void)intended_hostname; + (void)flags; + } +#endif /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/ + + if (opt_debug > 1) + log_debug ("http.c:session_new: sess %p created\n", sess); + err = 0; + +#if USE_TLS + leave: +#endif /*USE_TLS*/ + if (err) + http_session_unref (sess); + else + *r_session = sess; + + return err; +} + + +/* Increment the reference count for session SESS. Passing NULL for + SESS is allowed. */ +http_session_t +http_session_ref (http_session_t sess) +{ + if (sess) + { + sess->refcount++; + if (opt_debug > 1) + log_debug ("http.c:session_ref: sess %p ref now %d\n", + sess, sess->refcount); + } + return sess; +} + + +void +http_session_set_log_cb (http_session_t sess, + void (*cb)(http_session_t, gpg_error_t, + const char *hostname, + const void **certs, size_t *certlens)) +{ + sess->cert_log_cb = cb; +} + + +/* Set the TIMEOUT in milliseconds for the connection's connect + * calls. Using 0 disables the timeout. */ +void +http_session_set_timeout (http_session_t sess, unsigned int timeout) +{ + sess->connect_timeout = timeout; +} + + + + +/* Start a HTTP retrieval and on success store at R_HD a context + pointer for completing the request and to wait for the response. + If HTTPHOST is not NULL it is used for the Host header instead of a + Host header derived from the URL. */ +gpg_error_t +http_open (http_t *r_hd, http_req_t reqtype, const char *url, + const char *httphost, + const char *auth, unsigned int flags, const char *proxy, + http_session_t session, const char *srvtag, strlist_t headers) +{ + gpg_error_t err; + http_t hd; + + *r_hd = NULL; + + if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST)) + return gpg_err_make (default_errsource, GPG_ERR_INV_ARG); + + /* Create the handle. */ + hd = xtrycalloc (1, sizeof *hd); + if (!hd) + return gpg_error_from_syserror (); + hd->magic = HTTP_CONTEXT_MAGIC; + hd->req_type = reqtype; + hd->flags = flags; + hd->session = http_session_ref (session); + + err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS)); + if (!err) + err = send_request (hd, httphost, auth, proxy, srvtag, + hd->session? hd->session->connect_timeout : 0, + headers); + + if (err) + { + my_socket_unref (hd->sock, NULL, NULL); + if (hd->fp_read) + es_fclose (hd->fp_read); + if (hd->fp_write) + es_fclose (hd->fp_write); + http_session_unref (hd->session); + xfree (hd); + } + else + *r_hd = hd; + return err; +} + + +/* This function is useful to connect to a generic TCP service using + this http abstraction layer. This has the advantage of providing + service tags and an estream interface. TIMEOUT is in milliseconds. */ +gpg_error_t +http_raw_connect (http_t *r_hd, const char *server, unsigned short port, + unsigned int flags, const char *srvtag, unsigned int timeout) +{ + gpg_error_t err = 0; + http_t hd; + cookie_t cookie; + + *r_hd = NULL; + + if ((flags & HTTP_FLAG_FORCE_TOR)) + { + int mode; + + if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode) + { + log_error ("Tor support is not available\n"); + return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED); + } + /* Non-blocking connects do not work with our Tor proxy because + * we can't continue the Socks protocol after the EINPROGRESS. + * Disable the timeout to use a blocking connect. */ + timeout = 0; + } + + /* Create the handle. */ + hd = xtrycalloc (1, sizeof *hd); + if (!hd) + return gpg_error_from_syserror (); + hd->magic = HTTP_CONTEXT_MAGIC; + hd->req_type = HTTP_REQ_OPAQUE; + hd->flags = flags; + + /* Connect. */ + { + assuan_fd_t sock; + + err = connect_server (server, port, hd->flags, srvtag, timeout, &sock); + if (err) + { + xfree (hd); + return err; + } + hd->sock = my_socket_new (sock); + if (!hd->sock) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + xfree (hd); + return err; + } + } + + /* Setup estreams for reading and writing. */ + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + goto leave; + } + cookie->sock = my_socket_ref (hd->sock); + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (!hd->fp_write) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock, NULL, NULL); + xfree (cookie); + goto leave; + } + hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */ + + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + goto leave; + } + cookie->sock = my_socket_ref (hd->sock); + hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); + if (!hd->fp_read) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock, NULL, NULL); + xfree (cookie); + goto leave; + } + hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */ + + /* Register close notification to interlock the use of es_fclose in + http_close and in user code. */ + err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd); + if (!err) + err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd); + + leave: + if (err) + { + if (hd->fp_read) + es_fclose (hd->fp_read); + if (hd->fp_write) + es_fclose (hd->fp_write); + my_socket_unref (hd->sock, NULL, NULL); + xfree (hd); + } + else + *r_hd = hd; + return err; +} + + + + +void +http_start_data (http_t hd) +{ + if (!hd->in_data) + { + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_with_string ("\r\n", "http.c:request-header:"); + 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; + int use_tls; + + /* Make sure that we are in the data. */ + http_start_data (hd); + + /* Close the write stream. Note that the reference counted socket + object keeps the actual system socket open. */ + cookie = hd->write_cookie; + if (!cookie) + return gpg_err_make (default_errsource, GPG_ERR_INTERNAL); + + use_tls = cookie->use_tls; + es_fclose (hd->fp_write); + hd->fp_write = NULL; + /* The close has released the cookie and thus we better set it to 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 + keyserver didn't worked without it. */ + if ((hd->flags & HTTP_FLAG_SHUTDOWN)) + shutdown (FD2INT (hd->sock->fd), 1); + hd->in_data = 0; + + /* Create a new cookie and a stream for reading. */ + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + cookie->sock = my_socket_ref (hd->sock); + cookie->session = http_session_ref (hd->session); + cookie->use_tls = use_tls; + + hd->read_cookie = cookie; + hd->fp_read = es_fopencookie (cookie, "r", cookie_functions); + if (!hd->fp_read) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock, NULL, NULL); + http_session_unref (cookie->session); + xfree (cookie); + hd->read_cookie = NULL; + return err; + } + + err = parse_response (hd); + + if (!err) + err = es_onclose (hd->fp_read, 1, fp_onclose_notification, 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, + http_session_t session, + const char *srvtag, strlist_t headers) +{ + gpg_error_t err; + + err = http_open (r_hd, HTTP_REQ_GET, document, NULL, auth, flags, + proxy, session, srvtag, headers); + 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; + + log_assert (hd->magic == HTTP_CONTEXT_MAGIC); + + /* First remove the close notifications for the streams. */ + if (hd->fp_read) + es_onclose (hd->fp_read, 0, fp_onclose_notification, hd); + if (hd->fp_write) + es_onclose (hd->fp_write, 0, fp_onclose_notification, hd); + + /* Now we can close the streams. */ + my_socket_unref (hd->sock, NULL, NULL); + if (hd->fp_read && !keep_read_stream) + es_fclose (hd->fp_read); + if (hd->fp_write) + es_fclose (hd->fp_write); + http_session_unref (hd->session); + hd->magic = 0xdeadbeef; + 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; +} + +/* Return information pertaining to TLS. If TLS is not in use for HD, + NULL is returned. WHAT is used ask for specific information: + + (NULL) := Only check whether TLS is in use. Returns an + unspecified string if TLS is in use. That string may + even be the empty string. + */ +const char * +http_get_tls_info (http_t hd, const char *what) +{ + (void)what; + + if (!hd) + return NULL; + + return hd->uri->use_tls? "":NULL; +} + + + +static gpg_error_t +parse_uri (parsed_uri_t *ret_uri, const char *uri, + int no_scheme_check, int force_tls) +{ + gpg_err_code_t ec; + + *ret_uri = xtrycalloc (1, sizeof **ret_uri + 2 * strlen (uri) + 1); + if (!*ret_uri) + return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + strcpy ((*ret_uri)->buffer, uri); + strcpy ((*ret_uri)->buffer + strlen (uri) + 1, uri); + (*ret_uri)->original = (*ret_uri)->buffer + strlen (uri) + 1; + ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls); + if (ec) + { + http_release_parsed_uri (*ret_uri); + *ret_uri = NULL; + } + return gpg_err_make (default_errsource, ec); +} + + +/* + * Parse an URI and put the result into the newly allocated RET_URI. + * On success the caller must use http_release_parsed_uri() to + * releases the resources. If NO_SCHEME_CHECK is set, the function + * tries to parse the URL in the same way it would do for an HTTP + * style URI. + */ +gpg_error_t +http_parse_uri (parsed_uri_t *ret_uri, const char *uri, + int no_scheme_check) +{ + return parse_uri (ret_uri, uri, no_scheme_check, 0); +} + + +void +http_release_parsed_uri (parsed_uri_t uri) +{ + if (uri) + { + uri_tuple_t r, r2; + + for (r = uri->params; r; r = r2) + { + r2 = r->next; + xfree (r); + } + for (r = uri->query; r; r = r2) + { + r2 = r->next; + xfree (r); + } + xfree (uri); + } +} + + +static gpg_err_code_t +do_parse_uri (parsed_uri_t uri, int only_local_part, + int no_scheme_check, int force_tls) +{ + 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; + uri->is_http = 0; + uri->opaque = 0; + uri->v6lit = 0; + uri->onion = 0; + uri->explicit_port = 0; + uri->off_host = 0; + uri->off_path = 0; + + /* A quick validity check. */ + if (strspn (p, VALID_URI_CHARS) != n) + return GPG_ERR_BAD_URI; /* Invalid characters found. */ + + if (!only_local_part) + { + /* Find the scheme. */ + if (!(p2 = strchr (p, ':')) || p2 == p) + return 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") && !force_tls) + { + uri->port = 80; + uri->is_http = 1; + } + else if (!strcmp (uri->scheme, "hkp") && !force_tls) + { + uri->port = 11371; + uri->is_http = 1; + } +#ifdef USE_TLS + else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps") + || (force_tls && (!strcmp (uri->scheme, "http") + || !strcmp (uri->scheme,"hkp")))) + { + uri->port = 443; + uri->is_http = 1; + uri->use_tls = 1; + } +#endif /*USE_TLS*/ + else if (!no_scheme_check) + return GPG_ERR_INV_URI; /* Unsupported scheme */ + + p = p2; + + if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */ + { + p += 2; + if ((p2 = strchr (p, '/'))) + { + if (p2 - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_path = p2 - uri->buffer; + *p2++ = 0; + } + else + { + n = (p - uri->buffer) + strlen (p); + if (n > 10000) + return GPG_ERR_BAD_URI; + uri->off_path = n; + } + + /* 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); + + /* Handle an IPv6 literal */ + if( *p == '[' && (p3=strchr( p, ']' )) ) + { + *p3++ = '\0'; + /* worst case, uri->host should have length 0, points to \0 */ + uri->host = p + 1; + if (p - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_host = (p + 1) - uri->buffer; + uri->v6lit = 1; + p = p3; + } + else + { + uri->host = p; + if (p - uri->buffer > 10000) + return GPG_ERR_BAD_URI; + uri->off_host = p - uri->buffer; + } + + if ((p3 = strchr (p, ':'))) + { + *p3++ = '\0'; + uri->port = atoi (p3); + uri->explicit_port = 1; + } + + if ((n = remove_escapes (uri->host)) < 0) + return GPG_ERR_BAD_URI; + if (n != strlen (uri->host)) + return GPG_ERR_BAD_URI; /* Hostname includes a Nul. */ + p = p2 ? p2 : NULL; + } + else if (uri->is_http) + return GPG_ERR_INV_URI; /* No Leading double slash for HTTP. */ + else + { + uri->opaque = 1; + uri->path = p; + if (is_onion_address (uri->path)) + uri->onion = 1; + return 0; + } + + } /* End global URI part. */ + + /* Parse the pathname part if any. */ + if (p && *p) + { + /* 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_ERR_BAD_URI; + if (n != strlen (p)) + return GPG_ERR_BAD_URI; /* Path includes a Nul. */ + p = p2 ? p2 : NULL; + + /* Parse a query string if any. */ + if (p && *p) + { + tail = &uri->query; + for (;;) + { + uri_tuple_t elem; + + if ((p2 = strchr (p, '&'))) + *p2++ = 0; + if (!(elem = parse_tuple (p))) + return GPG_ERR_BAD_URI; + *tail = elem; + tail = &elem->next; + + if (!p2) + break; /* Ready. */ + p = p2; + } + } + } + + if (is_onion_address (uri->host)) + uri->onion = 1; + + 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; +} + + +/* If SPECIAL is NULL this function escapes in forms mode. */ +static size_t +escape_data (char *buffer, const void *data, size_t datalen, + const char *special) +{ + int forms = !special; + const unsigned char *s; + size_t n = 0; + + if (forms) + special = "%;?&="; + + for (s = data; datalen; s++, datalen--) + { + if (forms && *s == ' ') + { + if (buffer) + *buffer++ = '+'; + n++; + } + else if (forms && *s == '\n') + { + if (buffer) + memcpy (buffer, "%0D%0A", 6); + n += 6; + } + else if (forms && *s == '\r' && datalen > 1 && s[1] == '\n') + { + if (buffer) + memcpy (buffer, "%0D%0A", 6); + n += 6; + s++; + datalen--; + } + else if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s)) + { + if (buffer) + *(unsigned char*)buffer++ = *s; + n++; + } + else + { + if (buffer) + { + snprintf (buffer, 4, "%%%02X", *s); + buffer += 3; + } + n += 3; + } + } + return n; +} + + +static int +insert_escapes (char *buffer, const char *string, + const char *special) +{ + return escape_data (buffer, string, strlen (string), special); +} + + +/* 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. If SPECIAL is NULL a dedicated forms + encoding mode is used. */ +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; +} + +/* Allocate a new string from {DATA,DATALEN} 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. If SPECIAL is NULL a + dedicated forms encoding mode is used. */ +char * +http_escape_data (const void *data, size_t datalen, const char *specials) +{ + int n; + char *buf; + + n = escape_data (NULL, data, datalen, specials); + buf = xtrymalloc (n+1); + if (buf) + { + escape_data (buf, data, datalen, 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; +} + + +/* Return true if STRING is likely "hostname:port" or only "hostname". */ +static int +is_hostname_port (const char *string) +{ + int colons = 0; + + if (!string || !*string) + return 0; + for (; *string; string++) + { + if (*string == ':') + { + if (colons) + return 0; + if (!string[1]) + return 0; + colons++; + } + else if (!colons && strchr (" \t\f\n\v_@[]/", *string)) + return 0; /* Invalid characters in hostname. */ + else if (colons && !digitp (string)) + return 0; /* Not a digit in the port. */ + } + return 1; +} + + +/* + * 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 *httphost, const char *auth, + const char *proxy, const char *srvtag, unsigned int timeout, + strlist_t headers) +{ + 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; + assuan_fd_t sock; +#ifdef USE_TLS + int have_http_proxy = 0; +#endif + + if (hd->uri->use_tls && !hd->session) + { + log_error ("TLS requested but no session object provided\n"); + return gpg_err_make (default_errsource, GPG_ERR_INTERNAL); + } +#ifdef USE_TLS + if (hd->uri->use_tls && !hd->session->tls_session) + { + log_error ("TLS requested but no TLS context available\n"); + return gpg_err_make (default_errsource, GPG_ERR_INTERNAL); + } + if (opt_debug) + log_debug ("Using TLS library: %s %s\n", +# if HTTP_USE_NTBTLS + "NTBTLS", ntbtls_check_version (NULL) +# elif HTTP_USE_GNUTLS + "GNUTLS", gnutls_check_version (NULL) +# else + "?", "?" +# endif /*HTTP_USE_*TLS*/ + ); +#endif /*USE_TLS*/ + + if ((hd->flags & HTTP_FLAG_FORCE_TOR)) + { + int mode; + + if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode) + { + log_error ("Tor support is not available\n"); + return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED); + } + /* Non-blocking connects do not work with our Tor proxy because + * we can't continue the Socks protocol after the EINPROGRESS. + * Disable the timeout to use a blocking connect. */ + timeout = 0; + } + + server = *hd->uri->host ? hd->uri->host : "localhost"; + port = hd->uri->port ? hd->uri->port : 80; + + /* Try to use SNI. */ +#ifdef USE_TLS + if (hd->uri->use_tls) + { +# if HTTP_USE_GNUTLS + int rc; +# endif + + xfree (hd->session->servername); + hd->session->servername = xtrystrdup (httphost? httphost : server); + if (!hd->session->servername) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + return err; + } + +# if HTTP_USE_NTBTLS + err = ntbtls_set_hostname (hd->session->tls_session, + hd->session->servername); + if (err) + { + log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err)); + return err; + } +# elif HTTP_USE_GNUTLS + rc = gnutls_server_name_set (hd->session->tls_session, + GNUTLS_NAME_DNS, + hd->session->servername, + strlen (hd->session->servername)); + if (rc < 0) + log_info ("gnutls_server_name_set failed: %s\n", gnutls_strerror (rc)); +# endif /*HTTP_USE_GNUTLS*/ + } +#endif /*USE_TLS*/ + + 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 = parse_uri (&uri, http_proxy, 0, 0); + if (gpg_err_code (err) == GPG_ERR_INV_URI + && is_hostname_port (http_proxy)) + { + /* Retry assuming a "hostname:port" string. */ + char *tmpname = strconcat ("http://", http_proxy, NULL); + if (tmpname && !parse_uri (&uri, tmpname, 0, 0)) + err = 0; + xfree (tmpname); + } + + if (err) + ; +#ifdef USE_TLS + else if (!strcmp (uri->scheme, "http")) + have_http_proxy = 1; +#endif + else if (!strcmp (uri->scheme, "socks4") + || !strcmp (uri->scheme, "socks5h")) + err = gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED); + else + err = gpg_err_make (default_errsource, GPG_ERR_INV_URI); + + if (err) + { + log_error ("invalid HTTP proxy (%s): %s\n", + http_proxy, gpg_strerror (err)); + return gpg_err_make (default_errsource, 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_err_make (default_errsource, + gpg_err_code_from_syserror ()); + http_release_parsed_uri (uri); + return err; + } + } + + err = connect_server (*uri->host ? uri->host : "localhost", + uri->port ? uri->port : 80, + hd->flags, NULL, timeout, &sock); + http_release_parsed_uri (uri); + } + else + { + err = connect_server (server, port, hd->flags, srvtag, timeout, &sock); + } + + if (err) + { + xfree (proxy_authstr); + return err; + } + hd->sock = my_socket_new (sock); + if (!hd->sock) + { + xfree (proxy_authstr); + return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + } + +#if USE_TLS + if (have_http_proxy && hd->uri->use_tls) + { + int saved_flags; + cookie_t cookie; + + /* Try to use the CONNECT method to proxy our TLS stream. */ + request = es_bsprintf + ("CONNECT %s:%hu HTTP/1.0\r\nHost: %s:%hu\r\n%s", + httphost ? httphost : server, + port, + httphost ? httphost : server, + port, + proxy_authstr ? proxy_authstr : ""); + xfree (proxy_authstr); + proxy_authstr = NULL; + + if (! request) + return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_with_string (request, "http.c:request:"); + + cookie = xtrycalloc (1, sizeof *cookie); + if (! cookie) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + xfree (request); + return err; + } + cookie->sock = my_socket_ref (hd->sock); + hd->write_cookie = cookie; + + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (! hd->fp_write) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock, NULL, NULL); + xfree (cookie); + xfree (request); + hd->write_cookie = NULL; + return err; + } + else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + + xfree (request); + request = NULL; + + /* Make sure http_wait_response doesn't close the stream. */ + saved_flags = hd->flags; + hd->flags &= ~HTTP_FLAG_SHUTDOWN; + + /* Get the response. */ + err = http_wait_response (hd); + + /* Restore flags, destroy stream. */ + hd->flags = saved_flags; + es_fclose (hd->fp_read); + hd->fp_read = NULL; + hd->read_cookie = NULL; + + /* Reset state. */ + hd->in_data = 0; + + if (err) + return err; + + if (hd->status_code != 200) + { + request = es_bsprintf + ("CONNECT %s:%hu", + httphost ? httphost : server, + port); + + log_error (_("error accessing '%s': http status %u\n"), + request ? request : "out of core", + http_get_status_code (hd)); + + xfree (request); + return gpg_error (GPG_ERR_NO_DATA); + } + + /* We are done with the proxy, the code below will establish a + * TLS session and talk directly to the target server. */ + http_proxy = NULL; + } +#endif /* USE_TLS */ + +#if HTTP_USE_NTBTLS + if (hd->uri->use_tls) + { + estream_t in, out; + + my_socket_ref (hd->sock); + + /* Until we support send/recv in estream under Windows we need + * to use es_fopencookie. */ +#ifdef HAVE_W32_SYSTEM + in = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "rb", + simple_cookie_functions); +#else + in = es_fdopen_nc (hd->sock->fd, "rb"); +#endif + if (!in) + { + err = gpg_error_from_syserror (); + xfree (proxy_authstr); + return err; + } + +#ifdef HAVE_W32_SYSTEM + out = es_fopencookie ((void*)(unsigned int)hd->sock->fd, "wb", + simple_cookie_functions); +#else + out = es_fdopen_nc (hd->sock->fd, "wb"); +#endif + if (!out) + { + err = gpg_error_from_syserror (); + es_fclose (in); + xfree (proxy_authstr); + return err; + } + + err = ntbtls_set_transport (hd->session->tls_session, in, out); + if (err) + { + log_info ("TLS set_transport failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + xfree (proxy_authstr); + return err; + } + +#ifdef HTTP_USE_NTBTLS + if (hd->session->verify_cb) + { + err = ntbtls_set_verify_cb (hd->session->tls_session, + my_ntbtls_verify_cb, hd); + if (err) + { + log_error ("ntbtls_set_verify_cb failed: %s\n", + gpg_strerror (err)); + xfree (proxy_authstr); + return err; + } + } +#endif /*HTTP_USE_NTBTLS*/ + + while ((err = ntbtls_handshake (hd->session->tls_session))) + { +#if NTBTLS_VERSION_NUMBER >= 0x000200 + unsigned int tlevel, ttype; + const char *s = ntbtls_get_last_alert (hd->session->tls_session, + &tlevel, &ttype); + if (s) + log_info ("TLS alert: %s (%u.%u)\n", s, tlevel, ttype); +#endif + + switch (err) + { + default: + log_info ("TLS handshake failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + xfree (proxy_authstr); + return err; + } + } + + hd->session->verify.done = 0; + + /* Try the available verify callbacks until one returns success + * or a real error. Note that NTBTLS does the verification + * during the handshake via */ +#ifdef HTTP_USE_NTBTLS + err = 0; /* Fixme check that the CB has been called. */ +#else + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif + + if (hd->session->verify_cb + && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR + && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED) + err = hd->session->verify_cb (hd->session->verify_cb_value, + hd, hd->session, + (hd->flags | hd->session->flags), + hd->session->tls_session); + + if (tls_callback + && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR + && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED) + err = tls_callback (hd, hd->session, 0); + + if (gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR + && gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED) + err = http_verify_server_credentials (hd->session); + + if (err) + { + log_info ("TLS connection authentication failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + xfree (proxy_authstr); + return err; + } + + } +#elif HTTP_USE_GNUTLS + if (hd->uri->use_tls) + { + int rc; + + my_socket_ref (hd->sock); + gnutls_transport_set_ptr (hd->session->tls_session, hd->sock); + gnutls_transport_set_pull_function (hd->session->tls_session, + my_gnutls_read); + gnutls_transport_set_push_function (hd->session->tls_session, + my_gnutls_write); + + handshake_again: + do + { + rc = gnutls_handshake (hd->session->tls_session); + } + while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN); + if (rc < 0) + { + if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED + || rc == GNUTLS_E_FATAL_ALERT_RECEIVED) + { + gnutls_alert_description_t alertno; + const char *alertstr; + + alertno = gnutls_alert_get (hd->session->tls_session); + alertstr = gnutls_alert_get_name (alertno); + log_info ("TLS handshake %s: %s (alert %d)\n", + rc == GNUTLS_E_WARNING_ALERT_RECEIVED + ? "warning" : "failed", + alertstr, (int)alertno); + if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server) + log_info (" (sent server name '%s')\n", server); + + if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED) + goto handshake_again; + } + else + log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc)); + xfree (proxy_authstr); + return gpg_err_make (default_errsource, GPG_ERR_NETWORK); + } + + hd->session->verify.done = 0; + if (tls_callback) + err = tls_callback (hd, hd->session, 0); + else + err = http_verify_server_credentials (hd->session); + 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_err_make (default_errsource, gpg_err_code_from_syserror ()); + } + remove_escapes (myauth); + } + else + { + remove_escapes (hd->uri->auth); + myauth = hd->uri->auth; + } + + authstr = make_header_line ("Authorization: Basic ", "\r\n", + myauth, strlen (myauth)); + if (auth) + xfree (myauth); + + if (!authstr) + { + xfree (proxy_authstr); + return gpg_err_make (default_errsource, + gpg_err_code_from_syserror ()); + } + } + + p = build_rel_path (hd->uri); + if (!p) + return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + + if (http_proxy && *http_proxy) + { + request = es_bsprintf + ("%s %s://%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", + hd->uri->use_tls? "https" : "http", + httphost? httphost : server, + port, *p == '/' ? "" : "/", p, + authstr ? authstr : "", + proxy_authstr ? proxy_authstr : ""); + } + else + { + char portstr[35]; + + if (port == (hd->uri->use_tls? 443 : 80)) + *portstr = 0; + else + snprintf (portstr, sizeof portstr, ":%u", port); + + request = es_bsprintf + ("%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, + httphost? httphost : server, + portstr, + authstr? authstr:""); + } + xfree (p); + if (!request) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + xfree (authstr); + xfree (proxy_authstr); + return err; + } + + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_with_string (request, "http.c:request:"); + + /* First setup estream so that we can write even the first line + using estream. This is also required for the sake of gnutls. */ + { + cookie_t cookie; + + cookie = xtrycalloc (1, sizeof *cookie); + if (!cookie) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + goto leave; + } + cookie->sock = my_socket_ref (hd->sock); + hd->write_cookie = cookie; + cookie->use_tls = hd->uri->use_tls; + cookie->session = http_session_ref (hd->session); + + hd->fp_write = es_fopencookie (cookie, "w", cookie_functions); + if (!hd->fp_write) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + my_socket_unref (cookie->sock, NULL, NULL); + xfree (cookie); + hd->write_cookie = NULL; + } + else if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write)) + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + else + err = 0; + + if (!err) + { + for (;headers; headers=headers->next) + { + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_with_string (headers->d, "http.c:request-header:"); + if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write)) + || (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write))) + { + err = gpg_err_make (default_errsource, + gpg_err_code_from_syserror ()); + break; + } + } + } + } + + 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_err_code_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_ERR_BUG; + if (*line == ' ' || *line == '\t') + { + /* Continuation. This won't happen too often as it is not + recommended. We use a straightforward implementation. */ + if (!hd->headers) + return GPG_ERR_PROTOCOL_VIOLATION; + n += strlen (hd->headers->value); + p = xtrymalloc (n+1); + if (!p) + return gpg_err_code_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_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 = strconcat (h->value, ",", value, NULL); + if (!p) + return gpg_err_code_from_syserror (); + xfree (h->value); + h->value = p; + return 0; + } + + /* Append a new header. */ + h = xtrymalloc (sizeof *h + strlen (line)); + if (!h) + return gpg_err_code_from_syserror (); + strcpy (h->name, line); + h->value = xtrymalloc (strlen (value)+1); + if (!h->value) + { + xfree (h); + return gpg_err_code_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 other 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. */ +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; +} + + +/* Return a newly allocated and NULL terminated array with pointers to + header names. The array must be released with xfree() and its + content is only values as long as no other request has been + send. */ +const char ** +http_get_header_names (http_t hd) +{ + const char **array; + size_t n; + header_t h; + + for (n=0, h = hd->headers; h; h = h->next) + n++; + array = xtrycalloc (n+1, sizeof *array); + if (array) + { + for (n=0, h = hd->headers; h; h = h->next) + array[n++] = h->name; + } + + return array; +} + + +/* + * Parse the response from a server. + * Returns: Errorcode and sets some files in the handle + */ +static gpg_err_code_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_err_code_from_syserror (); /* Out of core. */ + if (!maxlen) + return GPG_ERR_TRUNCATED; /* Line has been truncated. */ + if (!len) + return GPG_ERR_EOF; + + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_debug_with_string (line, "http.c:response:\n"); + } + 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_err_code_from_syserror (); /* Out of core. */ + /* Note, that we can silently ignore truncated lines. */ + if (!len) + return GPG_ERR_EOF; + /* Trim line endings of empty lines. */ + if ((*line == '\r' && line[1] == '\n') || *line == '\n') + *line = 0; + if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP)) + log_info ("http.c:RESP: '%.*s'\n", + (int)strlen(line)-(*line&&line[1]?2:0),line); + if (*line) + { + gpg_err_code_t ec = store_header (hd, line); + if (ec) + return ec; + } + } + 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 = string_to_u64 (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 (my_select (fd + 1, &rfds, NULL, NULL, NULL) <= 0) + continue; /* ignore any errors */ + + if (!FD_ISSET (fd, &rfds)) + continue; + + addrlen = sizeof peer; + client = my_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 + + + +/* Return true if SOCKS shall be used. This is the case if tor_mode + * is enabled and the desired address is not the loopback address. + * This function is basically a copy of the same internal function in + * Libassuan. */ +static int +use_socks (struct sockaddr_storage *addr) +{ + int mode; + + if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode) + return 0; /* Not in Tor mode. */ + else if (addr->ss_family == AF_INET6) + { + struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr; + const unsigned char *s; + int i; + + s = (unsigned char *)&addr_in6->sin6_addr.s6_addr; + if (s[15] != 1) + return 1; /* Last octet is not 1 - not the loopback address. */ + for (i=0; i < 15; i++, s++) + if (*s) + return 1; /* Non-zero octet found - not the loopback address. */ + + return 0; /* This is the loopback address. */ + } + else if (addr->ss_family == AF_INET) + { + struct sockaddr_in *addr_in = (struct sockaddr_in *)addr; + + if (*(unsigned char*)&addr_in->sin_addr.s_addr == 127) + return 0; /* Loopback (127.0.0.0/8) */ + + return 1; + } + else + return 0; +} + + +/* Wrapper around assuan_sock_new which takes the domain from an + * address parameter. */ +static assuan_fd_t +my_sock_new_for_addr (struct sockaddr_storage *addr, int type, int proto) +{ + int domain; + + if (use_socks (addr)) + { + /* Libassaun always uses 127.0.0.1 to connect to the socks + * server (i.e. the Tor daemon). */ + domain = AF_INET; + } + else + domain = addr->ss_family; + + return assuan_sock_new (domain, type, proto); +} + + +/* Call WSAGetLastError and map it to a libgpg-error. */ +#ifdef HAVE_W32_SYSTEM +static gpg_error_t +my_wsagetlasterror (void) +{ + int wsaerr; + gpg_err_code_t ec; + + wsaerr = WSAGetLastError (); + switch (wsaerr) + { + case WSAENOTSOCK: ec = GPG_ERR_EINVAL; break; + case WSAEWOULDBLOCK: ec = GPG_ERR_EAGAIN; break; + case ERROR_BROKEN_PIPE: ec = GPG_ERR_EPIPE; break; + case WSANOTINITIALISED: ec = GPG_ERR_ENOSYS; break; + case WSAENOBUFS: ec = GPG_ERR_ENOBUFS; break; + case WSAEMSGSIZE: ec = GPG_ERR_EMSGSIZE; break; + case WSAECONNREFUSED: ec = GPG_ERR_ECONNREFUSED; break; + case WSAEISCONN: ec = GPG_ERR_EISCONN; break; + case WSAEALREADY: ec = GPG_ERR_EALREADY; break; + case WSAETIMEDOUT: ec = GPG_ERR_ETIMEDOUT; break; + default: ec = GPG_ERR_EIO; break; + } + + return gpg_err_make (default_errsource, ec); +} +#endif /*HAVE_W32_SYSTEM*/ + + +/* Connect SOCK and return GPG_ERR_ETIMEOUT if a connection could not + * be established within TIMEOUT milliseconds. 0 indicates the + * system's default timeout. The other args are the usual connect + * args. On success 0 is returned, on timeout GPG_ERR_ETIMEDOUT, and + * another error code for other errors. On timeout the caller needs + * to close the socket as soon as possible to stop an ongoing + * handshake. + * + * This implementation is for well-behaving systems; see Stevens, + * Network Programming, 2nd edition, Vol 1, 15.4. */ +static gpg_error_t +connect_with_timeout (assuan_fd_t sock, + struct sockaddr *addr, int addrlen, + unsigned int timeout) +{ + gpg_error_t err; + int syserr; + socklen_t slen; + fd_set rset, wset; + struct timeval tval; + int n; + +#ifndef HAVE_W32_SYSTEM + int oflags; +# define RESTORE_BLOCKING() do { \ + fcntl (sock, F_SETFL, oflags); \ + } while (0) +#else /*HAVE_W32_SYSTEM*/ +# define RESTORE_BLOCKING() do { \ + unsigned long along = 0; \ + ioctlsocket (FD2INT (sock), FIONBIO, &along); \ + } while (0) +#endif /*HAVE_W32_SYSTEM*/ + + + if (!timeout) + { + /* Shortcut. */ + if (assuan_sock_connect (sock, addr, addrlen)) + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + else + err = 0; + return err; + } + + /* Switch the socket into non-blocking mode. */ +#ifdef HAVE_W32_SYSTEM + { + unsigned long along = 1; + if (ioctlsocket (FD2INT (sock), FIONBIO, &along)) + return my_wsagetlasterror (); + } +#else + oflags = fcntl (sock, F_GETFL, 0); + if (fcntl (sock, F_SETFL, oflags | O_NONBLOCK)) + return gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); +#endif + + /* Do the connect. */ + if (!assuan_sock_connect (sock, addr, addrlen)) + { + /* Immediate connect. Restore flags. */ + RESTORE_BLOCKING (); + return 0; /* Success. */ + } + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + if (gpg_err_code (err) != GPG_ERR_EINPROGRESS +#ifdef HAVE_W32_SYSTEM + && gpg_err_code (err) != GPG_ERR_EAGAIN +#endif + ) + { + RESTORE_BLOCKING (); + return err; + } + + FD_ZERO (&rset); + FD_SET (FD2INT (sock), &rset); + wset = rset; + tval.tv_sec = timeout / 1000; + tval.tv_usec = (timeout % 1000) * 1000; + + n = my_select (FD2INT(sock)+1, &rset, &wset, NULL, &tval); + if (n < 0) + { + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + RESTORE_BLOCKING (); + return err; + } + if (!n) + { + /* Timeout: We do not restore the socket flags on timeout + * because the caller is expected to close the socket. */ + return gpg_err_make (default_errsource, GPG_ERR_ETIMEDOUT); + } + if (!FD_ISSET (sock, &rset) && !FD_ISSET (sock, &wset)) + { + /* select misbehaved. */ + return gpg_err_make (default_errsource, GPG_ERR_SYSTEM_BUG); + } + + slen = sizeof (syserr); + if (getsockopt (FD2INT(sock), SOL_SOCKET, SO_ERROR, + (void*)&syserr, &slen) < 0) + { + /* Assume that this is Solaris which returns the error in ERRNO. */ + err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + } + else if (syserr) + err = gpg_err_make (default_errsource, gpg_err_code_from_errno (syserr)); + else + err = 0; /* Connected. */ + + RESTORE_BLOCKING (); + + return err; + +#undef RESTORE_BLOCKING +} + + +/* Actually connect to a server. On success 0 is returned and the + * file descriptor for the socket is stored at R_SOCK; on error an + * error code is returned and ASSUAN_INVALID_FD is stored at R_SOCK. + * TIMEOUT is the connect timeout in milliseconds. Note that the + * function tries to connect to all known addresses and the timeout is + * for each one. */ +static gpg_error_t +connect_server (const char *server, unsigned short port, + unsigned int flags, const char *srvtag, unsigned int timeout, + assuan_fd_t *r_sock) +{ + gpg_error_t err; + assuan_fd_t sock = ASSUAN_INVALID_FD; + unsigned int srvcount = 0; + int hostfound = 0; + int anyhostaddr = 0; + int srv, connected, v4_valid, v6_valid; + gpg_error_t last_err = 0; + struct srventry *serverlist = NULL; + + *r_sock = ASSUAN_INVALID_FD; + +#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP) + init_sockets (); +#endif /*Windows*/ + + check_inet_support (&v4_valid, &v6_valid); + + /* Onion addresses require special treatment. */ + if (is_onion_address (server)) + { +#ifdef ASSUAN_SOCK_TOR + + if (opt_debug) + log_debug ("http.c:connect_server:onion: name='%s' port=%hu\n", + server, port); + sock = assuan_sock_connect_byname (server, port, 0, NULL, + ASSUAN_SOCK_TOR); + if (sock == ASSUAN_INVALID_FD) + { + err = gpg_err_make (default_errsource, + (errno == EHOSTUNREACH)? GPG_ERR_UNKNOWN_HOST + : gpg_err_code_from_syserror ()); + log_error ("can't connect to '%s': %s\n", server, gpg_strerror (err)); + return err; + } + + notify_netactivity (); + *r_sock = sock; + return 0; + +#else /*!ASSUAN_SOCK_TOR*/ + + err = gpg_err_make (default_errsource, GPG_ERR_ENETUNREACH); + return ASSUAN_INVALID_FD; + +#endif /*!HASSUAN_SOCK_TOR*/ + } + + /* Do the SRV thing */ + if (srvtag) + { + err = get_dns_srv (server, srvtag, NULL, &serverlist, &srvcount); + if (err) + log_info ("getting '%s' SRV for '%s' failed: %s\n", + srvtag, server, gpg_strerror (err)); + /* Note that on error SRVCOUNT is zero. */ + err = 0; + } + + 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 gpg_err_make (default_errsource, gpg_err_code_from_syserror ()); + + serverlist->port = port; + strncpy (serverlist->target, server, DIMof (struct srventry, target)); + serverlist->target[DIMof (struct srventry, target)-1] = '\0'; + srvcount = 1; + } + + connected = 0; + for (srv=0; srv < srvcount && !connected; srv++) + { + dns_addrinfo_t aibuf, ai; + + if (opt_debug) + log_debug ("http.c:connect_server: trying name='%s' port=%hu\n", + serverlist[srv].target, port); + err = resolve_dns_name (serverlist[srv].target, port, 0, SOCK_STREAM, + &aibuf, NULL); + if (err) + { + log_info ("resolving '%s' failed: %s\n", + serverlist[srv].target, gpg_strerror (err)); + last_err = err; + continue; /* Not found - try next one. */ + } + hostfound = 1; + + for (ai = aibuf; ai && !connected; ai = ai->next) + { + if (ai->family == AF_INET + && ((flags & HTTP_FLAG_IGNORE_IPv4) || !v4_valid)) + continue; + if (ai->family == AF_INET6 + && ((flags & HTTP_FLAG_IGNORE_IPv6) || !v6_valid)) + continue; + + if (sock != ASSUAN_INVALID_FD) + assuan_sock_close (sock); + sock = my_sock_new_for_addr (ai->addr, ai->socktype, ai->protocol); + if (sock == ASSUAN_INVALID_FD) + { + if (errno == EAFNOSUPPORT) + { + if (ai->family == AF_INET) + v4_valid = 0; + if (ai->family == AF_INET6) + v6_valid = 0; + continue; + } + + err = gpg_err_make (default_errsource, + gpg_err_code_from_syserror ()); + log_error ("error creating socket: %s\n", gpg_strerror (err)); + free_dns_addrinfo (aibuf); + xfree (serverlist); + return err; + } + + anyhostaddr = 1; + err = connect_with_timeout (sock, (struct sockaddr *)ai->addr, + ai->addrlen, timeout); + if (err) + { + last_err = err; + } + else + { + connected = 1; + notify_netactivity (); + } + } + free_dns_addrinfo (aibuf); + } + + xfree (serverlist); + + if (!connected) + { + if (!hostfound) + log_error ("can't connect to '%s': %s\n", + server, "host not found"); + else if (!anyhostaddr) + log_error ("can't connect to '%s': %s\n", + server, "no IP address for host"); + else + { +#ifdef HAVE_W32_SYSTEM + log_error ("can't connect to '%s': ec=%d\n", + server, (int)WSAGetLastError()); +#else + log_error ("can't connect to '%s': %s\n", + server, gpg_strerror (last_err)); +#endif + } + err = last_err? last_err : gpg_err_make (default_errsource, + GPG_ERR_UNKNOWN_HOST); + if (sock != ASSUAN_INVALID_FD) + assuan_sock_close (sock); + return err; + } + + *r_sock = sock; + return 0; +} + + +/* Helper to read from a socket. This handles npth things and + * EINTR. */ +static gpgrt_ssize_t +read_server (assuan_fd_t sock, void *buffer, size_t size) +{ + int nread; + + do + { +#ifdef HAVE_W32_SYSTEM + /* Under Windows we need to use recv for a socket. */ +# if defined(USE_NPTH) + npth_unprotect (); +# endif + nread = recv (FD2INT (sock), buffer, size, 0); +# if defined(USE_NPTH) + npth_protect (); +# endif + +#else /*!HAVE_W32_SYSTEM*/ + +# ifdef USE_NPTH + nread = npth_read (sock, buffer, size); +# else + nread = read (sock, buffer, size); +# endif + +#endif /*!HAVE_W32_SYSTEM*/ + } + while (nread == -1 && errno == EINTR); + + return nread; +} + + +static gpg_error_t +write_server (assuan_fd_t sock, const char *data, size_t length) +{ + int nleft; + int nwritten; + + nleft = length; + while (nleft > 0) + { +#if defined(HAVE_W32_SYSTEM) +# if defined(USE_NPTH) + npth_unprotect (); +# endif + nwritten = send (FD2INT (sock), data, nleft, 0); +# if defined(USE_NPTH) + npth_protect (); +# endif + if ( nwritten == SOCKET_ERROR ) + { + log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ()); + return gpg_error (GPG_ERR_NETWORK); + } +#else /*!HAVE_W32_SYSTEM*/ +# ifdef USE_NPTH + nwritten = npth_write (sock, data, nleft); +# else + nwritten = write (sock, data, nleft); +# endif + if (nwritten == -1) + { + if (errno == EINTR) + continue; + if (errno == EAGAIN) + { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 50000; + my_select (0, NULL, NULL, NULL, &tv); + continue; + } + log_info ("network write failed: %s\n", strerror (errno)); + return gpg_error_from_syserror (); + } +#endif /*!HAVE_W32_SYSTEM*/ + nleft -= nwritten; + data += nwritten; + } + + return 0; +} + + + +/* Read handler for estream. */ +static gpgrt_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; + } + +#if HTTP_USE_NTBTLS + if (c->use_tls && c->session && c->session->tls_session) + { + estream_t in, out; + + ntbtls_get_stream (c->session->tls_session, &in, &out); + nread = es_fread (buffer, 1, size, in); + if (opt_debug) + log_debug ("TLS network read: %d/%zu\n", nread, size); + } + else +#elif HTTP_USE_GNUTLS + if (c->use_tls && c->session && c->session->tls_session) + { + again: + nread = gnutls_record_recv (c->session->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; + my_select (0, NULL, NULL, NULL, &tv); + goto again; + } + if (nread == GNUTLS_E_REHANDSHAKE) + goto again; /* A client is allowed to just ignore this request. */ + if (nread == GNUTLS_E_PREMATURE_TERMINATION) + { + /* The server terminated the connection. Close the TLS + session, and indicate EOF using a short read. */ + close_tls_session (c->session); + return 0; + } + log_info ("TLS network read failed: %s\n", gnutls_strerror (nread)); + gpg_err_set_errno (EIO); + return -1; + } + } + else +#endif /*HTTP_USE_GNUTLS*/ + { + nread = read_server (c->sock->fd, buffer, size); + } + + if (c->content_length_valid && nread > 0) + { + if (nread < c->content_length) + c->content_length -= nread; + else + c->content_length = 0; + } + + return (gpgrt_ssize_t)nread; +} + +/* Write handler for estream. */ +static gpgrt_ssize_t +cookie_write (void *cookie, const void *buffer_arg, size_t size) +{ + const char *buffer = buffer_arg; + cookie_t c = cookie; + int nwritten = 0; + +#if HTTP_USE_NTBTLS + if (c->use_tls && c->session && c->session->tls_session) + { + estream_t in, out; + + ntbtls_get_stream (c->session->tls_session, &in, &out); + if (size == 0) + es_fflush (out); + else + nwritten = es_fwrite (buffer, 1, size, out); + if (opt_debug) + log_debug ("TLS network write: %d/%zu\n", nwritten, size); + } + else +#elif HTTP_USE_GNUTLS + if (c->use_tls && c->session && c->session->tls_session) + { + int nleft = size; + while (nleft > 0) + { + nwritten = gnutls_record_send (c->session->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; + my_select (0, NULL, NULL, NULL, &tv); + continue; + } + log_info ("TLS network write failed: %s\n", + gnutls_strerror (nwritten)); + gpg_err_set_errno (EIO); + return -1; + } + nleft -= nwritten; + buffer += nwritten; + } + } + else +#endif /*HTTP_USE_GNUTLS*/ + { + if ( write_server (c->sock->fd, buffer, size) ) + { + gpg_err_set_errno (EIO); + nwritten = -1; + } + else + nwritten = size; + } + + return (gpgrt_ssize_t)nwritten; +} + + +#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS) +static gpgrt_ssize_t +simple_cookie_read (void *cookie, void *buffer, size_t size) +{ + assuan_fd_t sock = (assuan_fd_t)cookie; + return read_server (sock, buffer, size); +} + +static gpgrt_ssize_t +simple_cookie_write (void *cookie, const void *buffer_arg, size_t size) +{ + assuan_fd_t sock = (assuan_fd_t)cookie; + const char *buffer = buffer_arg; + int nwritten; + + if (write_server (sock, buffer, size)) + { + gpg_err_set_errno (EIO); + nwritten = -1; + } + else + nwritten = size; + + return (gpgrt_ssize_t)nwritten; +} +#endif /*HAVE_W32_SYSTEM*/ + + +#ifdef HTTP_USE_GNUTLS +/* Wrapper for gnutls_bye used by my_socket_unref. */ +static void +send_gnutls_bye (void *opaque) +{ + tls_session_t tls_session = opaque; + int ret; + + again: + do + ret = gnutls_bye (tls_session, GNUTLS_SHUT_RDWR); + while (ret == GNUTLS_E_INTERRUPTED); + if (ret == GNUTLS_E_AGAIN) + { + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 50000; + my_select (0, NULL, NULL, NULL, &tv); + goto again; + } +} +#endif /*HTTP_USE_GNUTLS*/ + +/* Close handler for estream. */ +static int +cookie_close (void *cookie) +{ + cookie_t c = cookie; + + if (!c) + return 0; + +#if HTTP_USE_NTBTLS + if (c->use_tls && c->session && c->session->tls_session) + { + /* FIXME!! Possibly call ntbtls_close_notify for close + of write stream. */ + my_socket_unref (c->sock, NULL, NULL); + } + else +#elif HTTP_USE_GNUTLS + if (c->use_tls && c->session && c->session->tls_session) + my_socket_unref (c->sock, send_gnutls_bye, c->session->tls_session); + else +#endif /*HTTP_USE_GNUTLS*/ + if (c->sock) + my_socket_unref (c->sock, NULL, NULL); + + if (c->session) + http_session_unref (c->session); + xfree (c); + return 0; +} + + + + +/* Verify the credentials of the server. Returns 0 on success and + store the result in the session object. */ +gpg_error_t +http_verify_server_credentials (http_session_t sess) +{ +#if HTTP_USE_GNUTLS + static const char errprefix[] = "TLS verification of peer failed"; + int rc; + unsigned int status; + const char *hostname; + const gnutls_datum_t *certlist; + unsigned int certlistlen; + gnutls_x509_crt_t cert; + gpg_error_t err = 0; + + sess->verify.done = 1; + sess->verify.status = 0; + sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR; + + if (gnutls_certificate_type_get (sess->tls_session) != GNUTLS_CRT_X509) + { + log_error ("%s: %s\n", errprefix, "not an X.509 certificate"); + sess->verify.rc = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE; + return gpg_error (GPG_ERR_GENERAL); + } + + rc = gnutls_certificate_verify_peers2 (sess->tls_session, &status); + if (rc) + { + log_error ("%s: %s\n", errprefix, gnutls_strerror (rc)); + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + } + else if (status) + { + log_error ("%s: status=0x%04x\n", errprefix, status); +#if GNUTLS_VERSION_NUMBER >= 0x030104 + { + gnutls_datum_t statusdat; + + if (!gnutls_certificate_verification_status_print + (status, GNUTLS_CRT_X509, &statusdat, 0)) + { + log_info ("%s: %s\n", errprefix, statusdat.data); + gnutls_free (statusdat.data); + } + } +#endif /*gnutls >= 3.1.4*/ + + sess->verify.status = status; + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + } + + hostname = sess->servername; + if (!hostname || !strchr (hostname, '.')) + { + log_error ("%s: %s\n", errprefix, "hostname missing"); + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + } + + certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen); + if (!certlistlen) + { + log_error ("%s: %s\n", errprefix, "server did not send a certificate"); + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + + /* Need to stop here. */ + if (err) + return err; + } + + rc = gnutls_x509_crt_init (&cert); + if (rc < 0) + { + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + if (err) + return err; + } + + rc = gnutls_x509_crt_import (cert, &certlist[0], GNUTLS_X509_FMT_DER); + if (rc < 0) + { + log_error ("%s: %s: %s\n", errprefix, "error importing certificate", + gnutls_strerror (rc)); + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + } + + if (!gnutls_x509_crt_check_hostname (cert, hostname)) + { + log_error ("%s: %s\n", errprefix, "hostname does not match"); + if (!err) + err = gpg_error (GPG_ERR_GENERAL); + } + + gnutls_x509_crt_deinit (cert); + + if (!err) + sess->verify.rc = 0; + + if (sess->cert_log_cb) + { + const void *bufarr[10]; + size_t buflenarr[10]; + size_t n; + + for (n = 0; n < certlistlen && n < DIM (bufarr)-1; n++) + { + bufarr[n] = certlist[n].data; + buflenarr[n] = certlist[n].size; + } + bufarr[n] = NULL; + buflenarr[n] = 0; + sess->cert_log_cb (sess, err, hostname, bufarr, buflenarr); + } + + return err; +#else /*!HTTP_USE_GNUTLS*/ + (void)sess; + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#endif +} + +/* Return the first query variable with the specified key. If there + is no such variable, return NULL. */ +struct uri_tuple_s * +uri_query_lookup (parsed_uri_t uri, const char *key) +{ + struct uri_tuple_s *t; + + for (t = uri->query; t; t = t->next) + if (strcmp (t->name, key) == 0) + return t; + + return NULL; +} + + +/* Return true if both URI point to the same host for the purpose of + * redirection check. A is the original host and B the host given in + * the Location header. As a temporary workaround a fixed list of + * exceptions is also consulted. */ +static int +same_host_p (parsed_uri_t a, parsed_uri_t b) +{ + static struct + { + const char *from; /* NULL uses the last entry from the table. */ + const char *to; + } allow[] = + { + { "protonmail.com", "api.protonmail.com" }, + { NULL, "api.protonmail.ch" }, + { "protonmail.ch", "api.protonmail.com" }, + { NULL, "api.protonmail.ch" }, + { "pm.me", "api.protonmail.ch" } + }; + static const char *subdomains[] = + { + "openpgpkey." + }; + int i; + const char *from; + + if (!a->host || !b->host) + return 0; + + if (!ascii_strcasecmp (a->host, b->host)) + return 1; + + from = NULL; + for (i=0; i < DIM (allow); i++) + { + if (allow[i].from) + from = allow[i].from; + if (!from) + continue; + if (!ascii_strcasecmp (from, a->host) + && !ascii_strcasecmp (allow[i].to, b->host)) + return 1; + } + + /* Also consider hosts the same if they differ only in a subdomain; + * in both direction. This allows to have redirection between the + * WKD advanced and direct lookup methods. */ + for (i=0; i < DIM (subdomains); i++) + { + const char *subdom = subdomains[i]; + size_t subdomlen = strlen (subdom); + + if (!ascii_strncasecmp (a->host, subdom, subdomlen) + && !ascii_strcasecmp (a->host + subdomlen, b->host)) + return 1; + if (!ascii_strncasecmp (b->host, subdom, subdomlen) + && !ascii_strcasecmp (b->host + subdomlen, a->host)) + return 1; + } + + return 0; +} + + +/* Prepare a new URL for a HTTP redirect. INFO has flags controlling + * the operation, STATUS_CODE is used for diagnostics, LOCATION is the + * value of the "Location" header, and R_URL reveives the new URL on + * success or NULL or error. Note that INFO->ORIG_URL is + * required. */ +gpg_error_t +http_prepare_redirect (http_redir_info_t *info, unsigned int status_code, + const char *location, char **r_url) +{ + gpg_error_t err; + parsed_uri_t locuri; + parsed_uri_t origuri; + char *newurl; + char *p; + + *r_url = NULL; + + if (!info || !info->orig_url) + return gpg_error (GPG_ERR_INV_ARG); + + if (!info->silent) + log_info (_("URL '%s' redirected to '%s' (%u)\n"), + info->orig_url, location? location:"[none]", status_code); + + if (!info->redirects_left) + { + if (!info->silent) + log_error (_("too many redirections\n")); + return gpg_error (GPG_ERR_NO_DATA); + } + info->redirects_left--; + + if (!location || !*location) + return gpg_error (GPG_ERR_NO_DATA); + + err = http_parse_uri (&locuri, location, 0); + if (err) + return err; + + /* Make sure that an onion address only redirects to another + * onion address, or that a https address only redirects to a + * https address. */ + if (info->orig_onion && !locuri->onion) + { + dirmngr_status_printf (info->ctrl, "WARNING", + "http_redirect %u" + " redirect from onion to non-onion address" + " rejected", + err); + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_FORBIDDEN); + } + if (!info->allow_downgrade && info->orig_https && !locuri->use_tls) + { + err = gpg_error (GPG_ERR_FORBIDDEN); + dirmngr_status_printf (info->ctrl, "WARNING", + "http_redirect %u" + " redirect '%s' to '%s' rejected", + err, info->orig_url, location); + http_release_parsed_uri (locuri); + return err; + } + + if (info->trust_location) + { + /* We trust the Location - return it verbatim. */ + http_release_parsed_uri (locuri); + newurl = xtrystrdup (location); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (locuri); + return err; + } + } + else if ((err = http_parse_uri (&origuri, info->orig_url, 0))) + { + http_release_parsed_uri (locuri); + return err; + } + else if (same_host_p (origuri, locuri)) + { + /* The host is the same or on an exception list and thus we can + * take the location verbatim. */ + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + newurl = xtrystrdup (location); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (locuri); + return err; + } + } + else + { + /* We take only the host and port from the URL given in the + * Location. This limits the effects of redirection attacks by + * rogue hosts returning an URL to servers in the client's own + * network. We don't even include the userinfo because they + * should be considered similar to the path and query parts. + */ + if (!(locuri->off_path - locuri->off_host)) + { + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_BAD_URI); + } + if (!(origuri->off_path - origuri->off_host)) + { + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return gpg_error (GPG_ERR_BAD_URI); + } + + newurl = xtrymalloc (strlen (origuri->original) + + (locuri->off_path - locuri->off_host) + 1); + if (!newurl) + { + err = gpg_error_from_syserror (); + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + return err; + } + /* Build new URL from + * uriguri: scheme userinfo ---- ---- path rest + * locuri: ------ -------- host port ---- ---- + */ + p = newurl; + memcpy (p, origuri->original, origuri->off_host); + p += origuri->off_host; + memcpy (p, locuri->original + locuri->off_host, + (locuri->off_path - locuri->off_host)); + p += locuri->off_path - locuri->off_host; + strcpy (p, origuri->original + origuri->off_path); + + http_release_parsed_uri (origuri); + http_release_parsed_uri (locuri); + if (!info->silent) + log_info (_("redirection changed to '%s'\n"), newurl); + dirmngr_status_printf (info->ctrl, "WARNING", + "http_redirect_cleanup %u" + " changed from '%s' to '%s'", + 0, info->orig_url, newurl); + } + + *r_url = newurl; + return 0; +} + + +/* Return string describing the http STATUS. Returns an empty string + * for an unknown status. */ +const char * +http_status2string (unsigned int status) +{ + switch (status) + { + case 500: return "Internal Server Error"; + case 501: return "Not Implemented"; + case 502: return "Bad Gateway"; + case 503: return "Service Unavailable"; + case 504: return "Gateway Timeout"; + case 505: return "HTTP version Not Supported"; + case 506: return "Variant Also Negation"; + case 507: return "Insufficient Storage"; + case 508: return "Loop Detected"; + case 510: return "Not Extended"; + case 511: return "Network Authentication Required"; + } + + return ""; +} diff --git a/dirmngr/http.h b/dirmngr/http.h new file mode 100644 index 0000000..8b9c5b5 --- /dev/null +++ b/dirmngr/http.h @@ -0,0 +1,206 @@ +/* http.h - HTTP protocol handler + * Copyright (C) 1999, 2000, 2001, 2003, 2006, + * 2010 Free Software Foundation, Inc. + * Copyright (C) 2015 g10 Code GmbH + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * This file 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 <https://www.gnu.org/licenses/>. + */ +#ifndef GNUPG_COMMON_HTTP_H +#define GNUPG_COMMON_HTTP_H + +#include <gpg-error.h> +#include "../common/fwddecl.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 *original; /* Unmodified copy of the parsed URI. */ + char *scheme; /* Pointer to the scheme string (always lowercase). */ + unsigned int is_http:1; /* This is a HTTP style URI. */ + unsigned int use_tls:1; /* Whether TLS should be used. */ + unsigned int opaque:1;/* Unknown scheme; PATH has the rest. */ + unsigned int v6lit:1; /* Host was given as a literal v6 address. */ + unsigned int onion:1; /* .onion address given. */ + unsigned int explicit_port:1; /* The port was explicitly specified. */ + unsigned int ad_current:1; /* Use Active Directory's current user. */ + char *auth; /* username/password for basic auth. */ + char *host; /* Host (converted to lowercase). */ + unsigned short port; /* Port (always set if the host is set). */ + unsigned short off_host; /* Offset to the HOST respective PATH parts */ + unsigned short off_path; /* in the original URI buffer. */ + 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; + +struct uri_tuple_s *uri_query_lookup (parsed_uri_t uri, const char *key); + +typedef enum + { + HTTP_REQ_GET = 1, + HTTP_REQ_HEAD = 2, + HTTP_REQ_POST = 3, + HTTP_REQ_OPAQUE = 4 /* Internal use. */ + } +http_req_t; + +/* We put the flag values into an enum, so that gdb can display them. */ +enum + { + HTTP_FLAG_TRY_PROXY = 1, /* Try to use a proxy. */ + HTTP_FLAG_SHUTDOWN = 2, /* Close sending end after the request. */ + HTTP_FLAG_FORCE_TOR = 4, /* Force a TOR connection. */ + HTTP_FLAG_LOG_RESP = 8, /* Log the server response. */ + HTTP_FLAG_FORCE_TLS = 16, /* Force the use of TLS. */ + HTTP_FLAG_IGNORE_CL = 32, /* Ignore content-length. */ + HTTP_FLAG_IGNORE_IPv4 = 64, /* Do not use IPv4. */ + HTTP_FLAG_IGNORE_IPv6 = 128, /* Do not use IPv6. */ + HTTP_FLAG_TRUST_DEF = 256, /* Use the CAs configured for HKP. */ + HTTP_FLAG_TRUST_SYS = 512, /* Also use the system defined CAs. */ + HTTP_FLAG_TRUST_CFG = 1024, /* Also use configured CAs. */ + HTTP_FLAG_NO_CRL = 2048 /* Do not consult CRLs for https. */ + }; + + +struct http_session_s; +typedef struct http_session_s *http_session_t; + +struct http_context_s; +typedef struct http_context_s *http_t; + +/* An object used to track redirection infos. */ +struct http_redir_info_s +{ + unsigned int redirects_left; /* Number of still possible redirects. */ + ctrl_t ctrl; /* The usual connection info or NULL. */ + const char *orig_url; /* The original requested URL. */ + unsigned int orig_onion:1; /* Original request was an onion address. */ + unsigned int orig_https:1; /* Original request was a http address. */ + unsigned int silent:1; /* No diagnostics. */ + unsigned int allow_downgrade:1;/* Allow a downgrade from https to http. */ + unsigned int trust_location:1; /* Trust the received Location header. */ +}; +typedef struct http_redir_info_s http_redir_info_t; + + + +/* A TLS verify callback function. */ +typedef gpg_error_t (*http_verify_cb_t) (void *opaque, + http_t http, + http_session_t session, + unsigned int flags, + void *tls_context); + +void http_set_verbose (int verbose, int debug); + +void http_register_tls_callback (gpg_error_t (*cb)(http_t,http_session_t,int)); +void http_register_tls_ca (const char *fname); +void http_register_cfg_ca (const char *fname); +void http_register_netactivity_cb (void (*cb)(void)); + + +gpg_error_t http_session_new (http_session_t *r_session, + const char *intended_hostname, + unsigned int flags, + http_verify_cb_t cb, + void *cb_value); +http_session_t http_session_ref (http_session_t sess); +void http_session_release (http_session_t sess); + +void http_session_set_log_cb (http_session_t sess, + void (*cb)(http_session_t, gpg_error_t, + const char *, + const void **, size_t *)); +void http_session_set_timeout (http_session_t sess, unsigned int timeout); + + +gpg_error_t http_parse_uri (parsed_uri_t *ret_uri, const char *uri, + int no_scheme_check); + +void http_release_parsed_uri (parsed_uri_t uri); + +gpg_error_t http_raw_connect (http_t *r_hd, + const char *server, unsigned short port, + unsigned int flags, const char *srvtag, + unsigned int timeout); + +gpg_error_t http_open (http_t *r_hd, http_req_t reqtype, + const char *url, + const char *httphost, + const char *auth, + unsigned int flags, + const char *proxy, + http_session_t session, + const char *srvtag, + strlist_t headers); + +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, + http_session_t session, + const char *srvtag, + strlist_t headers); + +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_tls_info (http_t hd, const char *what); +const char *http_get_header (http_t hd, const char *name); +const char **http_get_header_names (http_t hd); +gpg_error_t http_verify_server_credentials (http_session_t sess); + +char *http_escape_string (const char *string, const char *specials); +char *http_escape_data (const void *data, size_t datalen, const char *specials); + +gpg_error_t http_prepare_redirect (http_redir_info_t *info, + unsigned int status_code, + const char *location, char **r_url); + +const char *http_status2string (unsigned int status); + + +#endif /*GNUPG_COMMON_HTTP_H*/ diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c new file mode 100644 index 0000000..4883cf8 --- /dev/null +++ b/dirmngr/ks-action.c @@ -0,0 +1,412 @@ +/* ks-action.c - OpenPGP keyserver actions + * Copyright (C) 2011 Free Software Foundation, Inc. + * Copyright (C) 2011, 2014 Werner Koch + * Copyright (C) 2015 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "dirmngr.h" +#include "misc.h" +#include "ks-engine.h" +#include "ks-action.h" +#if USE_LDAP +# include "ldap-parse-uri.h" +#endif + +/* Called by the engine's help functions to print the actual help. */ +gpg_error_t +ks_print_help (ctrl_t ctrl, const char *text) +{ + return dirmngr_status_help (ctrl, text); +} + + +/* Called by the engine's help functions to print the actual help. */ +gpg_error_t +ks_printf_help (ctrl_t ctrl, const char *format, ...) +{ + va_list arg_ptr; + gpg_error_t err; + char *buf; + + va_start (arg_ptr, format); + buf = es_vbsprintf (format, arg_ptr); + err = buf? 0 : gpg_error_from_syserror (); + va_end (arg_ptr); + if (!err) + err = dirmngr_status_help (ctrl, buf); + es_free (buf); + return err; +} + + +/* Run the help command for the engine responsible for URI. */ +gpg_error_t +ks_action_help (ctrl_t ctrl, const char *url) +{ + gpg_error_t err; + parsed_uri_t parsed_uri; /* The broken down URI. */ + + if (!url || !*url) + { + ks_print_help (ctrl, "Known schemata:\n"); + parsed_uri = NULL; + } + else + { +#if USE_LDAP + if (ldap_uri_p (url)) + err = ldap_parse_uri (&parsed_uri, url); + else +#endif + { + err = http_parse_uri (&parsed_uri, url, 1); + } + + if (err) + return err; + } + + /* Call all engines to give them a chance to print a help sting. */ + err = ks_hkp_help (ctrl, parsed_uri); + if (!err) + err = ks_http_help (ctrl, parsed_uri); + if (!err) + err = ks_finger_help (ctrl, parsed_uri); + if (!err) + err = ks_kdns_help (ctrl, parsed_uri); +#if USE_LDAP + if (!err) + err = ks_ldap_help (ctrl, parsed_uri); +#endif + + if (!parsed_uri) + ks_print_help (ctrl, + "(Use an URL for engine specific help.)"); + else + http_release_parsed_uri (parsed_uri); + return err; +} + + +/* Resolve all host names. This is useful for looking at the status + of configured keyservers. */ +gpg_error_t +ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers) +{ + gpg_error_t err = 0; + int any_server = 0; + uri_item_t uri; + + for (uri = keyservers; !err && uri; uri = uri->next) + { + if (uri->parsed_uri->is_http) + { + any_server = 1; + err = ks_hkp_resolve (ctrl, uri->parsed_uri); + if (err) + break; + } + } + + if (!any_server) + err = gpg_error (GPG_ERR_NO_KEYSERVER); + return err; +} + + +/* Search all configured keyservers for keys matching PATTERNS and + write the result to the provided output stream. */ +gpg_error_t +ks_action_search (ctrl_t ctrl, uri_item_t keyservers, + strlist_t patterns, estream_t outfp) +{ + gpg_error_t err = 0; + int any_server = 0; + int any_results = 0; + uri_item_t uri; + estream_t infp; + + if (!patterns) + return gpg_error (GPG_ERR_NO_USER_ID); + + /* FIXME: We only take care of the first pattern. To fully support + multiple patterns we might either want to run several queries in + parallel and merge them. We also need to decide what to do with + errors - it might not be the best idea to ignore an error from + one server and silently continue with another server. For now we + stop at the first error, unless the server responds with '404 Not + Found', in which case we try the next server. */ + for (uri = keyservers; !err && uri; uri = uri->next) + { + int is_http = uri->parsed_uri->is_http; + int is_ldap = 0; + unsigned int http_status = 0; +#if USE_LDAP + is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0 + || strcmp (uri->parsed_uri->scheme, "ldaps") == 0 + || strcmp (uri->parsed_uri->scheme, "ldapi") == 0); +#endif + if (is_http || is_ldap) + { + any_server = 1; +#if USE_LDAP + if (is_ldap) + err = ks_ldap_search (ctrl, uri->parsed_uri, patterns->d, &infp); + else +#endif + { + err = ks_hkp_search (ctrl, uri->parsed_uri, patterns->d, + &infp, &http_status); + } + + if (err == gpg_error (GPG_ERR_NO_DATA) + && http_status == 404 /* not found */) + { + /* No record found. Clear error and try next server. */ + err = 0; + continue; + } + + if (!err) + { + err = copy_stream (infp, outfp); + es_fclose (infp); + any_results = 1; + break; + } + } + } + + if (!any_server) + err = gpg_error (GPG_ERR_NO_KEYSERVER); + else if (err == 0 && !any_results) + err = gpg_error (GPG_ERR_NO_DATA); + return err; +} + + +/* Get the requested keys (matching PATTERNS) using all configured + keyservers and write the result to the provided output stream. */ +gpg_error_t +ks_action_get (ctrl_t ctrl, uri_item_t keyservers, + strlist_t patterns, estream_t outfp) +{ + gpg_error_t err = 0; + gpg_error_t first_err = 0; + int any_server = 0; + int any_data = 0; + strlist_t sl; + uri_item_t uri; + estream_t infp; + + if (!patterns) + return gpg_error (GPG_ERR_NO_USER_ID); + + /* FIXME: We only take care of the first keyserver. To fully + support multiple keyservers we need to track the result for each + pattern and use the next keyserver if one key was not found. The + keyservers might not all be fully synced thus it is not clear + whether the first keyserver has the freshest copy of the key. + Need to think about a better strategy. */ + for (uri = keyservers; !err && uri; uri = uri->next) + { + int is_hkp_s = (strcmp (uri->parsed_uri->scheme, "hkp") == 0 + || strcmp (uri->parsed_uri->scheme, "hkps") == 0); + int is_http_s = (strcmp (uri->parsed_uri->scheme, "http") == 0 + || strcmp (uri->parsed_uri->scheme, "https") == 0); + int is_ldap = 0; + +#if USE_LDAP + is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0 + || strcmp (uri->parsed_uri->scheme, "ldaps") == 0 + || strcmp (uri->parsed_uri->scheme, "ldapi") == 0); +#endif + + if (is_hkp_s || is_http_s || is_ldap) + { + any_server = 1; + for (sl = patterns; !err && sl; sl = sl->next) + { +#if USE_LDAP + if (is_ldap) + err = ks_ldap_get (ctrl, uri->parsed_uri, sl->d, &infp); + else +#endif + if (is_hkp_s) + err = ks_hkp_get (ctrl, uri->parsed_uri, sl->d, &infp); + else if (is_http_s) + err = ks_http_fetch (ctrl, uri->parsed_uri->original, + KS_HTTP_FETCH_NOCACHE, + &infp); + else + BUG (); + + if (err) + { + /* It is possible that a server does not carry a + key, thus we only save the error and continue + with the next pattern. FIXME: It is an open + question how to return such an error condition to + the caller. */ + first_err = err; + err = 0; + } + else + { + err = copy_stream (infp, outfp); + /* Reading from the keyserver should never fail, thus + return this error. */ + if (!err) + any_data = 1; + es_fclose (infp); + infp = NULL; + } + } + } + if (any_data) + break; /* Stop loop after a keyserver returned something. */ + } + + if (!any_server) + err = gpg_error (GPG_ERR_NO_KEYSERVER); + else if (!err && first_err && !any_data) + err = first_err; + return err; +} + + +/* Retrieve keys from URL and write the result to the provided output + * stream OUTFP. If OUTFP is NULL the data is written to the bit + * bucket. */ +gpg_error_t +ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp) +{ + gpg_error_t err = 0; + estream_t infp; + parsed_uri_t parsed_uri; /* The broken down URI. */ + + if (!url) + return gpg_error (GPG_ERR_INV_URI); + + err = http_parse_uri (&parsed_uri, url, 1); + if (err) + return err; + + if (parsed_uri->is_http) + { + err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_NOCACHE, &infp); + if (!err) + { + err = copy_stream (infp, outfp); + es_fclose (infp); + } + } + else if (!parsed_uri->opaque) + { + err = gpg_error (GPG_ERR_INV_URI); + } + else if (!strcmp (parsed_uri->scheme, "finger")) + { + err = ks_finger_fetch (ctrl, parsed_uri, &infp); + if (!err) + { + err = copy_stream (infp, outfp); + es_fclose (infp); + } + } + else if (!strcmp (parsed_uri->scheme, "kdns")) + { + err = ks_kdns_fetch (ctrl, parsed_uri, &infp); + if (!err) + { + err = copy_stream (infp, outfp); + es_fclose (infp); + } + } + else + err = gpg_error (GPG_ERR_INV_URI); + + http_release_parsed_uri (parsed_uri); + return err; +} + + + +/* Send an OpenPGP key to all keyservers. The key in {DATA,DATALEN} + is expected to be in OpenPGP binary transport format. The metadata + in {INFO,INFOLEN} is in colon-separated format (concretely, it is + the output of 'gpg --list-keys --with-colons KEYID'). This function + may modify DATA and INFO. If this is a problem, then the caller + should create a copy. */ +gpg_error_t +ks_action_put (ctrl_t ctrl, uri_item_t keyservers, + void *data, size_t datalen, + void *info, size_t infolen) +{ + gpg_error_t err = 0; + gpg_error_t first_err = 0; + int any_server = 0; + uri_item_t uri; + + (void) info; + (void) infolen; + + for (uri = keyservers; !err && uri; uri = uri->next) + { + int is_http = uri->parsed_uri->is_http; + int is_ldap = 0; + +#if USE_LDAP + is_ldap = (strcmp (uri->parsed_uri->scheme, "ldap") == 0 + || strcmp (uri->parsed_uri->scheme, "ldaps") == 0 + || strcmp (uri->parsed_uri->scheme, "ldapi") == 0); +#endif + + if (is_http || is_ldap) + { + any_server = 1; +#if USE_LDAP + if (is_ldap) + err = ks_ldap_put (ctrl, uri->parsed_uri, data, datalen, + info, infolen); + else +#endif + { + err = ks_hkp_put (ctrl, uri->parsed_uri, data, datalen); + } + if (err) + { + first_err = err; + err = 0; + } + } + } + + if (!any_server) + err = gpg_error (GPG_ERR_NO_KEYSERVER); + else if (!err && first_err) + err = first_err; + return err; +} diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h new file mode 100644 index 0000000..d576ef0 --- /dev/null +++ b/dirmngr/ks-action.h @@ -0,0 +1,36 @@ +/* ks-action.h - OpenPGP keyserver actions definitions + * Copyright (C) 2011 Free Software Foundation, Inc. + * 2015 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef DIRMNGR_KS_ACTION_H +#define DIRMNGR_KS_ACTION_H 1 + +gpg_error_t ks_action_help (ctrl_t ctrl, const char *url); +gpg_error_t ks_action_resolve (ctrl_t ctrl, uri_item_t keyservers); +gpg_error_t ks_action_search (ctrl_t ctrl, uri_item_t keyservers, + strlist_t patterns, estream_t outfp); +gpg_error_t ks_action_get (ctrl_t ctrl, uri_item_t keyservers, + strlist_t patterns, estream_t outfp); +gpg_error_t ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp); +gpg_error_t ks_action_put (ctrl_t ctrl, uri_item_t keyservers, + void *data, size_t datalen, + void *info, size_t infolen); + + +#endif /*DIRMNGR_KS_ACTION_H*/ diff --git a/dirmngr/ks-engine-finger.c b/dirmngr/ks-engine-finger.c new file mode 100644 index 0000000..e53a0ee --- /dev/null +++ b/dirmngr/ks-engine-finger.c @@ -0,0 +1,127 @@ +/* ks-engine-finger.c - Finger OpenPGP key access + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "dirmngr.h" +#include "misc.h" +#include "../common/userids.h" +#include "ks-engine.h" + +/* Print a help output for the schemata supported by this module. */ +gpg_error_t +ks_finger_help (ctrl_t ctrl, parsed_uri_t uri) +{ + char const data[] = + "Handler for FINGER:\n" + " finger:<user>@<host>\n" + "Supported methods: fetch\n" + "Example:\n" + " finger:joe@example.org\n"; + gpg_error_t err; + + if (!uri) + err = ks_print_help (ctrl, " finger"); + else if (!strcmp (uri->scheme, "finger")) + err = ks_print_help (ctrl, data); + else + err = 0; + + return err; +} + + +/* Get the key from URI which is expected to specify a finger scheme. + On success R_FP has an open stream to read the data. */ +gpg_error_t +ks_finger_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp) +{ + gpg_error_t err; + estream_t fp; + char *server; + char *name; + http_t http; + + (void)ctrl; + *r_fp = NULL; + + if (strcmp (uri->scheme, "finger") || !uri->opaque || !uri->path) + return gpg_error (GPG_ERR_INV_ARG); + + name = xtrystrdup (uri->path); + if (!name) + return gpg_error_from_syserror (); + + server = strchr (name, '@'); + if (!server) + { + err = gpg_error (GPG_ERR_INV_URI); + xfree (name); + return err; + } + *server++ = 0; + + err = http_raw_connect (&http, server, 79, + ((dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR : 0) + | (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0) + | (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)), + NULL, ctrl->timeout); + if (err) + { + xfree (name); + return err; + } + + fp = http_get_write_ptr (http); + if (!fp) + { + err = gpg_error (GPG_ERR_INTERNAL); + http_close (http, 0); + xfree (name); + return err; + } + + if (es_fputs (name, fp) || es_fputs ("\r\n", fp) || es_fflush (fp)) + { + err = gpg_error_from_syserror (); + http_close (http, 0); + xfree (name); + return err; + } + xfree (name); + es_fclose (fp); + + fp = http_get_read_ptr (http); + if (!fp) + { + err = gpg_error (GPG_ERR_INTERNAL); + http_close (http, 0); + return err; + } + + http_close (http, 1 /* Keep read ptr. */); + + *r_fp = fp; + return 0; +} diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c new file mode 100644 index 0000000..14859c7 --- /dev/null +++ b/dirmngr/ks-engine-hkp.c @@ -0,0 +1,1809 @@ +/* ks-engine-hkp.c - HKP keyserver engine + * Copyright (C) 2011, 2012 Free Software Foundation, Inc. + * Copyright (C) 2011, 2012, 2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#ifdef HAVE_W32_SYSTEM +# ifdef HAVE_WINSOCK2_H +# include <winsock2.h> +# endif +# include <windows.h> +#else /*!HAVE_W32_SYSTEM*/ +# include <sys/types.h> +# include <sys/socket.h> +# include <netdb.h> +#endif /*!HAVE_W32_SYSTEM*/ + +#include "dirmngr.h" +#include "misc.h" +#include "../common/userids.h" +#include "dns-stuff.h" +#include "ks-engine.h" + +/* Substitutes for missing Mingw macro. The EAI_SYSTEM mechanism + seems not to be available (probably because there is only one set + of error codes anyway). For now we use WSAEINVAL. */ +#ifndef EAI_OVERFLOW +# define EAI_OVERFLOW EAI_FAIL +#endif +#ifdef HAVE_W32_SYSTEM +# ifndef EAI_SYSTEM +# define EAI_SYSTEM WSAEINVAL +# endif +#endif + + +/* Number of seconds after a host is marked as resurrected. */ +#define RESURRECT_INTERVAL (3600+1800) /* 1.5 hours */ + +/* To match the behaviour of our old gpgkeys helper code we escape + more characters than actually needed. */ +#define EXTRA_ESCAPE_CHARS "@!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" + +/* How many redirections do we allow. */ +#define MAX_REDIRECTS 2 + +/* Number of retries done for a dead host etc. */ +#define SEND_REQUEST_RETRIES 3 + +/* Number of retries done in case of transient errors. */ +#define SEND_REQUEST_EXTRA_RETRIES 5 + + +enum ks_protocol { KS_PROTOCOL_HKP, KS_PROTOCOL_HKPS, KS_PROTOCOL_MAX }; + +/* Objects used to maintain information about hosts. */ +struct hostinfo_s; +typedef struct hostinfo_s *hostinfo_t; +struct hostinfo_s +{ + time_t lastfail; /* Time we tried to connect and failed. */ + time_t lastused; /* Time of last use. */ + int *pool; /* An array with indices into HOSTTABLE or NULL + if NAME is not a pool name. */ + size_t pool_len; /* Length of POOL. */ + size_t pool_size; /* Allocated size of POOL. */ +#define MAX_POOL_SIZE 128 + int poolidx; /* Index into POOL with the used host. -1 if not set. */ + unsigned int v4:1; /* Host supports AF_INET. */ + unsigned int v6:1; /* Host supports AF_INET6. */ + unsigned int onion:1;/* NAME is an onion (Tor HS) address. */ + unsigned int dead:1; /* Host is currently unresponsive. */ + unsigned int iporname_valid:1; /* The field IPORNAME below is valid */ + /* (but may be NULL) */ + unsigned int did_a_lookup:1; /* Have we done an A lookup yet? */ + unsigned int did_srv_lookup:2; /* One bit per protocol indicating + whether we already did a SRV + lookup. */ + time_t died_at; /* The time the host was marked dead. If this is + 0 the host has been manually marked dead. */ + char *cname; /* Canonical name of the host. Only set if this + is a pool or NAME has a numerical IP address. */ + char *iporname; /* Numeric IP address or name for printing. */ + unsigned short port[KS_PROTOCOL_MAX]; + /* The port used by the host for all protocols, 0 + if unknown. */ + char name[1]; /* The hostname. */ +}; + + +/* An array of hostinfo_t for all hosts requested by the caller or + resolved from a pool name and its allocated size.*/ +static hostinfo_t *hosttable; +static int hosttable_size; + +/* The number of host slots we initially allocate for HOSTTABLE. */ +#define INITIAL_HOSTTABLE_SIZE 50 + + +/* Create a new hostinfo object, fill in NAME and put it into + HOSTTABLE. Return the index into hosttable on success or -1 on + error. */ +static int +create_new_hostinfo (const char *name) +{ + hostinfo_t hi, *newtable; + int newsize; + int idx, rc; + + hi = xtrymalloc (sizeof *hi + strlen (name)); + if (!hi) + return -1; + strcpy (hi->name, name); + hi->pool = NULL; + hi->pool_len = 0; + hi->pool_size = 0; + hi->poolidx = -1; + hi->lastused = (time_t)(-1); + hi->lastfail = (time_t)(-1); + hi->v4 = 0; + hi->v6 = 0; + hi->onion = 0; + hi->dead = 0; + hi->did_a_lookup = 0; + hi->did_srv_lookup = 0; + hi->iporname_valid = 0; + hi->died_at = 0; + hi->cname = NULL; + hi->iporname = NULL; + hi->port[KS_PROTOCOL_HKP] = 0; + hi->port[KS_PROTOCOL_HKPS] = 0; + + /* Add it to the hosttable. */ + for (idx=0; idx < hosttable_size; idx++) + if (!hosttable[idx]) + { + hosttable[idx] = hi; + return idx; + } + /* Need to extend the hosttable. */ + newsize = hosttable_size + INITIAL_HOSTTABLE_SIZE; + newtable = xtryrealloc (hosttable, newsize * sizeof *hosttable); + if (!newtable) + { + xfree (hi); + return -1; + } + hosttable = newtable; + idx = hosttable_size; + hosttable_size = newsize; + rc = idx; + hosttable[idx++] = hi; + while (idx < hosttable_size) + hosttable[idx++] = NULL; + + return rc; +} + + +/* Find the host NAME in our table. Return the index into the + hosttable or -1 if not found. */ +static int +find_hostinfo (const char *name) +{ + int idx; + + for (idx=0; idx < hosttable_size; idx++) + if (hosttable[idx] && !ascii_strcasecmp (hosttable[idx]->name, name)) + return idx; + return -1; +} + + +static int +sort_hostpool (const void *xa, const void *xb) +{ + int a = *(int *)xa; + int b = *(int *)xb; + + assert (a >= 0 && a < hosttable_size); + assert (b >= 0 && b < hosttable_size); + assert (hosttable[a]); + assert (hosttable[b]); + + return ascii_strcasecmp (hosttable[a]->name, hosttable[b]->name); +} + + +/* Return true if the host with the hosttable index TBLIDX is in HI->pool. */ +static int +host_in_pool_p (hostinfo_t hi, int tblidx) +{ + int i, pidx; + + for (i = 0; i < hi->pool_len && (pidx = hi->pool[i]) != -1; i++) + if (pidx == tblidx && hosttable[pidx]) + return 1; + return 0; +} + + +/* Select a random host. Consult HI->pool which indices into the global + hosttable. Returns index into HI->pool or -1 if no host could be + selected. */ +static int +select_random_host (hostinfo_t hi) +{ + int *tbl; + size_t tblsize; + int pidx, idx; + + /* We create a new table so that we randomly select only from + currently alive hosts. */ + for (idx = 0, tblsize = 0; + idx < hi->pool_len && (pidx = hi->pool[idx]) != -1; + idx++) + if (hosttable[pidx] && !hosttable[pidx]->dead) + tblsize++; + if (!tblsize) + return -1; /* No hosts. */ + + tbl = xtrymalloc (tblsize * sizeof *tbl); + if (!tbl) + return -1; + for (idx = 0, tblsize = 0; + idx < hi->pool_len && (pidx = hi->pool[idx]) != -1; + idx++) + if (hosttable[pidx] && !hosttable[pidx]->dead) + tbl[tblsize++] = pidx; + + if (tblsize == 1) /* Save a get_uint_nonce. */ + pidx = tbl[0]; + else + pidx = tbl[get_uint_nonce () % tblsize]; + + xfree (tbl); + return pidx; +} + + +/* Figure out if a set of DNS records looks like a pool. */ +static int +arecords_is_pool (dns_addrinfo_t aibuf) +{ + dns_addrinfo_t ai; + int n_v6, n_v4; + + n_v6 = n_v4 = 0; + for (ai = aibuf; ai; ai = ai->next) + { + if (ai->family == AF_INET6) + n_v6++; + else if (ai->family == AF_INET) + n_v4++; + } + + return n_v6 > 1 || n_v4 > 1; +} + + +/* Print a warning iff Tor is not running but Tor has been requested. + * Also return true if it is not running. */ +static int +tor_not_running_p (ctrl_t ctrl) +{ + assuan_fd_t sock; + + if (!dirmngr_use_tor ()) + return 0; + + sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR); + if (sock != ASSUAN_INVALID_FD) + { + assuan_sock_close (sock); + return 0; + } + + log_info ("(it seems Tor is not running)\n"); + dirmngr_status (ctrl, "WARNING", "tor_not_running 0", + "Tor is enabled but the local Tor daemon" + " seems to be down", NULL); + return 1; +} + + +/* Add the host AI under the NAME into the HOSTTABLE. If PORT is not + zero, it specifies which port to use to talk to the host for + PROTOCOL. If NAME specifies a pool (as indicated by IS_POOL), + update the given reference table accordingly. */ +static void +add_host (const char *name, int is_pool, + const dns_addrinfo_t ai, + enum ks_protocol protocol, unsigned short port) +{ + gpg_error_t tmperr; + char *tmphost; + int idx, tmpidx; + hostinfo_t host; + int i; + + idx = find_hostinfo (name); + host = hosttable[idx]; + + if (is_pool) + { + /* For a pool immediately convert the address to a string. */ + tmperr = resolve_dns_addr (ai->addr, ai->addrlen, + (DNS_NUMERICHOST | DNS_WITHBRACKET), &tmphost); + } + else if (!is_ip_address (name)) + { + /* This is a hostname. Use the name as given without going + * through resolve_dns_addr. */ + tmphost = xtrystrdup (name); + if (!tmphost) + tmperr = gpg_error_from_syserror (); + else + tmperr = 0; + } + else + { + /* Do a PTR lookup on AI. If a name was not found the function + * returns the numeric address (with brackets). */ + tmperr = resolve_dns_addr (ai->addr, ai->addrlen, + DNS_WITHBRACKET, &tmphost); + } + + if (tmperr) + { + log_info ("resolve_dns_addr failed while checking '%s': %s\n", + name, gpg_strerror (tmperr)); + } + else if (host->pool_len + 1 >= MAX_POOL_SIZE) + { + log_error ("resolve_dns_addr for '%s': '%s'" + " [index table full - ignored]\n", name, tmphost); + } + else + { + if (!is_pool && is_ip_address (name)) + /* Update the original entry. */ + tmpidx = idx; + else + tmpidx = find_hostinfo (tmphost); + log_info ("resolve_dns_addr for '%s': '%s'%s\n", + name, tmphost, + tmpidx == -1? "" : " [already known]"); + + if (tmpidx == -1) /* Create a new entry. */ + tmpidx = create_new_hostinfo (tmphost); + + if (tmpidx == -1) + { + log_error ("map_host for '%s' problem: %s - '%s' [ignored]\n", + name, strerror (errno), tmphost); + } + else /* Set or update the entry. */ + { + if (port) + hosttable[tmpidx]->port[protocol] = port; + + if (ai->family == AF_INET6) + { + hosttable[tmpidx]->v6 = 1; + } + else if (ai->family == AF_INET) + { + hosttable[tmpidx]->v4 = 1; + } + else + BUG (); + + /* If we updated the main entry, we're done. */ + if (idx == tmpidx) + goto leave; + + /* If we updated an existing entry, we're done. */ + for (i = 0; i < host->pool_len; i++) + if (host->pool[i] == tmpidx) + goto leave; + + /* Otherwise, we need to add it to the pool. Check if there + is space. */ + if (host->pool_len + 1 > host->pool_size) + { + int *new_pool; + size_t new_size; + + if (host->pool_size == 0) + new_size = 4; + else + new_size = host->pool_size * 2; + + new_pool = xtryrealloc (host->pool, + new_size * sizeof *new_pool); + + if (new_pool == NULL) + goto leave; + + host->pool = new_pool; + host->pool_size = new_size; + } + + /* Finally, add it. */ + log_assert (host->pool_len < host->pool_size); + host->pool[host->pool_len++] = tmpidx; + } + } + leave: + xfree (tmphost); +} + + +/* Sort the pool of the given hostinfo HI. */ +static void +hostinfo_sort_pool (hostinfo_t hi) +{ + qsort (hi->pool, hi->pool_len, sizeof *hi->pool, sort_hostpool); +} + +/* Map the host name NAME to the actual to be used host name. This + * allows us to manage round robin DNS names. We use our own strategy + * to choose one of the hosts. For example we skip those hosts which + * failed for some time and we stick to one host for a time + * independent of DNS retry times. If FORCE_RESELECT is true a new + * host is always selected. If SRVTAG is NULL no service record + * lookup will be done, if it is set that service name is used. The + * selected host is stored as a malloced string at R_HOST; on error + * NULL is stored. If we know the port used by the selected host from + * a service record, a string representation is written to R_PORTSTR, + * otherwise it is left untouched. If R_HTTPFLAGS is not NULL it will + * receive flags which are to be passed to http_open. If R_HTTPHOST + * is not NULL a malloced name of the host is stored there; this might + * be different from R_HOST in case it has been selected from a + * pool. */ +static gpg_error_t +map_host (ctrl_t ctrl, const char *name, const char *srvtag, int force_reselect, + enum ks_protocol protocol, char **r_host, char *r_portstr, + unsigned int *r_httpflags, char **r_httphost) +{ + gpg_error_t err = 0; + hostinfo_t hi; + int idx; + dns_addrinfo_t aibuf, ai; + int is_pool; + int new_hosts = 0; + char *cname; + + *r_host = NULL; + if (r_httpflags) + *r_httpflags = 0; + if (r_httphost) + *r_httphost = NULL; + + /* No hostname means localhost. */ + if (!name || !*name) + { + *r_host = xtrystrdup ("localhost"); + if (!*r_host) + return gpg_error_from_syserror (); + if (r_httphost) + { + *r_httphost = xtrystrdup (*r_host); + if (!*r_httphost) + { + err = gpg_error_from_syserror (); + xfree (*r_host); + *r_host = NULL; + return err; + } + } + return 0; + } + + /* See whether the host is in our table. */ + idx = find_hostinfo (name); + if (idx == -1) + { + idx = create_new_hostinfo (name); + if (idx == -1) + return gpg_error_from_syserror (); + hi = hosttable[idx]; + hi->onion = is_onion_address (name); + } + else + hi = hosttable[idx]; + + is_pool = hi->pool != NULL; + + if (srvtag && !is_ip_address (name) + && ! hi->onion + && ! (hi->did_srv_lookup & 1 << protocol)) + { + struct srventry *srvs; + unsigned int srvscount; + + /* Check for SRV records. */ + err = get_dns_srv (name, srvtag, NULL, &srvs, &srvscount); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_ECONNREFUSED) + tor_not_running_p (ctrl); + return err; + } + + if (srvscount > 0) + { + int i; + if (! is_pool) + is_pool = srvscount > 1; + + for (i = 0; i < srvscount; i++) + { + err = resolve_dns_name (srvs[i].target, 0, + AF_UNSPEC, SOCK_STREAM, + &ai, &cname); + if (err) + continue; + dirmngr_tick (ctrl); + add_host (name, is_pool, ai, protocol, srvs[i].port); + new_hosts = 1; + } + + xfree (srvs); + } + + hi->did_srv_lookup |= 1 << protocol; + } + + if (! hi->did_a_lookup + && ! hi->onion) + { + /* Find all A records for this entry and put them into the pool + list - if any. */ + err = resolve_dns_name (name, 0, 0, SOCK_STREAM, &aibuf, &cname); + if (err) + { + log_error ("resolving '%s' failed: %s\n", name, gpg_strerror (err)); + err = 0; + } + else + { + /* First figure out whether this is a pool. For a pool we + use a different strategy than for a plain server: We use + the canonical name of the pool as the virtual host along + with the IP addresses. If it is not a pool, we use the + specified name. */ + if (! is_pool) + is_pool = arecords_is_pool (aibuf); + if (is_pool && cname) + { + hi->cname = cname; + cname = NULL; + } + + for (ai = aibuf; ai; ai = ai->next) + { + if (ai->family != AF_INET && ai->family != AF_INET6) + continue; + if (opt.disable_ipv4 && ai->family == AF_INET) + continue; + if (opt.disable_ipv6 && ai->family == AF_INET6) + continue; + dirmngr_tick (ctrl); + + add_host (name, is_pool, ai, 0, 0); + new_hosts = 1; + } + + hi->did_a_lookup = 1; + } + xfree (cname); + free_dns_addrinfo (aibuf); + } + if (new_hosts) + hostinfo_sort_pool (hi); + + if (hi->pool) + { + /* Deal with the pool name before selecting a host. */ + if (r_httphost) + { + *r_httphost = xtrystrdup (hi->name); + if (!*r_httphost) + return gpg_error_from_syserror (); + } + + /* If the currently selected host is now marked dead, force a + re-selection . */ + if (force_reselect) + hi->poolidx = -1; + else if (hi->poolidx >= 0 && hi->poolidx < hosttable_size + && hosttable[hi->poolidx] && hosttable[hi->poolidx]->dead) + hi->poolidx = -1; + + /* Select a host if needed. */ + if (hi->poolidx == -1) + { + hi->poolidx = select_random_host (hi); + if (hi->poolidx == -1) + { + log_error ("no alive host found in pool '%s'\n", name); + if (r_httphost) + { + xfree (*r_httphost); + *r_httphost = NULL; + } + return gpg_error (GPG_ERR_NO_KEYSERVER); + } + } + + assert (hi->poolidx >= 0 && hi->poolidx < hosttable_size); + hi = hosttable[hi->poolidx]; + assert (hi); + } + else if (r_httphost && is_ip_address (hi->name)) + { + /* This is a numerical IP address and not a pool. We want to + * find the canonical name so that it can be used in the HTTP + * Host header. Fixme: We should store that name in the + * hosttable. */ + char *host; + + err = resolve_dns_name (hi->name, 0, 0, SOCK_STREAM, &aibuf, NULL); + if (!err) + { + for (ai = aibuf; ai; ai = ai->next) + { + if ((!opt.disable_ipv6 && ai->family == AF_INET6) + || (!opt.disable_ipv4 && ai->family == AF_INET)) + { + err = resolve_dns_addr (ai->addr, ai->addrlen, 0, &host); + if (!err) + { + /* Okay, we return the first found name. */ + *r_httphost = host; + break; + } + } + } + } + free_dns_addrinfo (aibuf); + } + else if (r_httphost) + { + *r_httphost = xtrystrdup (hi->name); + if (!*r_httphost) + return gpg_error_from_syserror (); + } + + if (hi->dead) + { + log_error ("host '%s' marked as dead\n", hi->name); + if (r_httphost) + { + xfree (*r_httphost); + *r_httphost = NULL; + } + return gpg_error (GPG_ERR_NO_KEYSERVER); + } + + if (r_httpflags) + { + /* If the hosttable does not indicate that a certain host + supports IPv<N>, we explicit set the corresponding http + flags. The reason for this is that a host might be listed in + a pool as not v6 only but actually support v6 when later + the name is resolved by our http layer. */ + if (!hi->v4) + *r_httpflags |= HTTP_FLAG_IGNORE_IPv4; + if (!hi->v6) + *r_httpflags |= HTTP_FLAG_IGNORE_IPv6; + + /* Note that we do not set the HTTP_FLAG_FORCE_TOR for onion + addresses because the http module detects this itself. This + also allows us to use an onion address without Tor mode being + enabled. */ + } + + *r_host = xtrystrdup (hi->name); + if (!*r_host) + { + err = gpg_error_from_syserror (); + if (r_httphost) + { + xfree (*r_httphost); + *r_httphost = NULL; + } + return err; + } + if (hi->port[protocol]) + snprintf (r_portstr, 6 /* five digits and the sentinel */, + "%hu", hi->port[protocol]); + return 0; +} + + +/* Mark the host NAME as dead. NAME may be given as an URL. Returns + true if a host was really marked as dead or was already marked dead + (e.g. by a concurrent session). */ +static int +mark_host_dead (const char *name) +{ + const char *host; + char *host_buffer = NULL; + parsed_uri_t parsed_uri = NULL; + int done = 0; + + if (name && *name && !http_parse_uri (&parsed_uri, name, 1)) + { + if (parsed_uri->v6lit) + { + host_buffer = strconcat ("[", parsed_uri->host, "]", NULL); + if (!host_buffer) + log_error ("out of core in mark_host_dead"); + host = host_buffer; + } + else + host = parsed_uri->host; + } + else + host = name; + + if (host && *host && strcmp (host, "localhost")) + { + hostinfo_t hi; + int idx; + + idx = find_hostinfo (host); + if (idx != -1) + { + hi = hosttable[idx]; + log_info ("marking host '%s' as dead%s\n", + hi->name, hi->dead? " (again)":""); + hi->dead = 1; + hi->died_at = gnupg_get_time (); + if (!hi->died_at) + hi->died_at = 1; + done = 1; + } + } + + http_release_parsed_uri (parsed_uri); + xfree (host_buffer); + return done; +} + + +/* Mark a host in the hosttable as dead or - if ALIVE is true - as + alive. */ +gpg_error_t +ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive) +{ + gpg_error_t err = 0; + hostinfo_t hi, hi2; + int idx, idx2, idx3, n; + + if (!name || !*name || !strcmp (name, "localhost")) + return 0; + + idx = find_hostinfo (name); + if (idx == -1) + return gpg_error (GPG_ERR_NOT_FOUND); + + hi = hosttable[idx]; + if (alive && hi->dead) + { + hi->dead = 0; + err = ks_printf_help (ctrl, "marking '%s' as alive", name); + } + else if (!alive && !hi->dead) + { + hi->dead = 1; + hi->died_at = 0; /* Manually set dead. */ + err = ks_printf_help (ctrl, "marking '%s' as dead", name); + } + + /* If the host is a pool mark all member hosts. */ + if (!err && hi->pool) + { + for (idx2 = 0; + !err && idx2 < hi->pool_len && (n = hi->pool[idx2]) != -1; + idx2++) + { + assert (n >= 0 && n < hosttable_size); + + if (!alive) + { + /* Do not mark a host from a pool dead if it is also a + member in another pool. */ + for (idx3=0; idx3 < hosttable_size; idx3++) + { + if (hosttable[idx3] + && hosttable[idx3]->pool + && idx3 != idx + && host_in_pool_p (hosttable[idx3], n)) + break; + } + if (idx3 < hosttable_size) + continue; /* Host is also a member of another pool. */ + } + + hi2 = hosttable[n]; + if (!hi2) + ; + else if (alive && hi2->dead) + { + hi2->dead = 0; + err = ks_printf_help (ctrl, "marking '%s' as alive", + hi2->name); + } + else if (!alive && !hi2->dead) + { + hi2->dead = 1; + hi2->died_at = 0; /* Manually set dead. */ + err = ks_printf_help (ctrl, "marking '%s' as dead", + hi2->name); + } + } + } + + return err; +} + + +/* Debug function to print the entire hosttable. */ +gpg_error_t +ks_hkp_print_hosttable (ctrl_t ctrl) +{ + gpg_error_t err; + int idx, idx2; + hostinfo_t hi; + membuf_t mb; + time_t curtime; + char *p, *died; + const char *diedstr; + + err = ks_print_help (ctrl, "hosttable (idx, ipv6, ipv4, dead, name, time):"); + if (err) + return err; + + /* FIXME: We need a lock for the hosttable. */ + curtime = gnupg_get_time (); + for (idx=0; idx < hosttable_size; idx++) + if ((hi=hosttable[idx])) + { + if (hi->dead && hi->died_at) + { + died = elapsed_time_string (hi->died_at, curtime); + diedstr = died? died : "error"; + } + else + diedstr = died = NULL; + + if (!hi->iporname_valid) + { + char *canon = NULL; + + xfree (hi->iporname); + hi->iporname = NULL; + + /* Do a lookup just for the display purpose. */ + if (hi->onion || hi->pool) + ; + else if (is_ip_address (hi->name)) + { + dns_addrinfo_t aibuf, ai; + + /* Turn the numerical IP address string into an AI and + * then do a DNS PTR lookup. */ + if (!resolve_dns_name (hi->name, 0, 0, + SOCK_STREAM, + &aibuf, &canon)) + { + if (canon && is_ip_address (canon)) + { + xfree (canon); + canon = NULL; + } + for (ai = aibuf; !canon && ai; ai = ai->next) + { + resolve_dns_addr (ai->addr, ai->addrlen, + DNS_WITHBRACKET, &canon); + if (canon && is_ip_address (canon)) + { + /* We already have the numeric IP - no need to + * display it a second time. */ + xfree (canon); + canon = NULL; + } + } + } + free_dns_addrinfo (aibuf); + } + else + { + dns_addrinfo_t aibuf, ai; + + /* Get the IP address as a string from a name. Note + * that resolve_dns_addr allocates CANON on success + * and thus terminates the loop. */ + if (!resolve_dns_name (hi->name, 0, + hi->v6? AF_INET6 : AF_INET, + SOCK_STREAM, + &aibuf, NULL)) + { + for (ai = aibuf; !canon && ai; ai = ai->next) + { + resolve_dns_addr (ai->addr, ai->addrlen, + DNS_NUMERICHOST|DNS_WITHBRACKET, + &canon); + } + } + free_dns_addrinfo (aibuf); + } + + hi->iporname = canon; + hi->iporname_valid = 1; + } + + err = ks_printf_help (ctrl, "%3d %s %s %s %s%s%s%s%s%s%s\n", + idx, + hi->onion? "O" : hi->v6? "6":" ", + hi->v4? "4":" ", + hi->dead? "d":" ", + hi->name, + hi->iporname? " (":"", + hi->iporname? hi->iporname : "", + hi->iporname? ")":"", + diedstr? " (":"", + diedstr? diedstr:"", + diedstr? ")":"" ); + xfree (died); + if (err) + return err; + + if (hi->cname) + err = ks_printf_help (ctrl, " . %s", hi->cname); + if (err) + return err; + + if (hi->pool) + { + init_membuf (&mb, 256); + put_membuf_printf (&mb, " . -->"); + for (idx2 = 0; idx2 < hi->pool_len && hi->pool[idx2] != -1; idx2++) + { + put_membuf_printf (&mb, " %d", hi->pool[idx2]); + if (hi->poolidx == hi->pool[idx2]) + put_membuf_printf (&mb, "*"); + } + put_membuf( &mb, "", 1); + p = get_membuf (&mb, NULL); + if (!p) + return gpg_error_from_syserror (); + err = ks_print_help (ctrl, p); + xfree (p); + if (err) + return err; + } + } + return 0; +} + + + +/* Print a help output for the schemata supported by this module. */ +gpg_error_t +ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri) +{ + const char data[] = + "Handler for HKP URLs:\n" + " hkp://\n" +#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS + " hkps://\n" +#endif + "Supported methods: search, get, put\n"; + gpg_error_t err; + +#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS + const char data2[] = " hkp\n hkps"; +#else + const char data2[] = " hkp"; +#endif + + if (!uri) + err = ks_print_help (ctrl, data2); + else if (uri->is_http && (!strcmp (uri->scheme, "hkp") + || !strcmp (uri->scheme, "hkps"))) + err = ks_print_help (ctrl, data); + else + err = 0; + + return err; +} + + +/* Build the remote part of the URL from SCHEME, HOST and an optional + * PORT. If NO_SRV is set no SRV record lookup will be done. Returns + * an allocated string at R_HOSTPORT or NULL on failure. If + * R_HTTPHOST is not NULL it receives a malloced string with the + * hostname; this may be different from HOST if HOST is selected from + * a pool. */ +static gpg_error_t +make_host_part (ctrl_t ctrl, + const char *scheme, const char *host, unsigned short port, + int force_reselect, int no_srv, + char **r_hostport, unsigned int *r_httpflags, char **r_httphost) +{ + gpg_error_t err; + const char *srvtag; + char portstr[10]; + char *hostname; + enum ks_protocol protocol; + + *r_hostport = NULL; + + if (!strcmp (scheme, "hkps") || !strcmp (scheme,"https")) + { + scheme = "https"; + srvtag = no_srv? NULL : "pgpkey-https"; + protocol = KS_PROTOCOL_HKPS; + } + else /* HKP or HTTP. */ + { + scheme = "http"; + srvtag = no_srv? NULL : "pgpkey-http"; + protocol = KS_PROTOCOL_HKP; + } + + portstr[0] = 0; + err = map_host (ctrl, host, srvtag, force_reselect, protocol, + &hostname, portstr, r_httpflags, r_httphost); + if (err) + return err; + + /* If map_host did not return a port (from a SRV record) but a port + * has been specified (implicitly or explicitly) then use that port. + * In the case that a port was not specified (which is probably a + * bug in https.c) we will set up defaults. */ + if (*portstr) + ; + else if (!*portstr && port) + snprintf (portstr, sizeof portstr, "%hu", port); + else if (!strcmp (scheme,"https")) + strcpy (portstr, "443"); + else + strcpy (portstr, "11371"); + + if (*hostname != '[' && is_ip_address (hostname) == 6) + *r_hostport = strconcat (scheme, "://[", hostname, "]:", portstr, NULL); + else + *r_hostport = strconcat (scheme, "://", hostname, ":", portstr, NULL); + xfree (hostname); + if (!*r_hostport) + { + if (r_httphost) + { + xfree (*r_httphost); + *r_httphost = NULL; + } + return gpg_error_from_syserror (); + } + return 0; +} + + +/* Resolve all known keyserver names and update the hosttable. This + is mainly useful for debugging because the resolving is anyway done + on demand. */ +gpg_error_t +ks_hkp_resolve (ctrl_t ctrl, parsed_uri_t uri) +{ + gpg_error_t err; + char *hostport = NULL; + + /* NB: With an explicitly given port we do not want to consult a + * service record because that might be in conflict with the port + * from such a service record. */ + err = make_host_part (ctrl, uri->scheme, uri->host, uri->port, + 1, uri->explicit_port, + &hostport, NULL, NULL); + if (err) + { + err = ks_printf_help (ctrl, "%s://%s:%hu: resolve failed: %s", + uri->scheme, uri->host, uri->port, + gpg_strerror (err)); + } + else + { + err = ks_printf_help (ctrl, "%s", hostport); + xfree (hostport); + } + return err; +} + + +/* Housekeeping function called from the housekeeping thread. It is + used to mark dead hosts alive so that they may be tried again after + some time. */ +void +ks_hkp_housekeeping (time_t curtime) +{ + int idx; + hostinfo_t hi; + + for (idx=0; idx < hosttable_size; idx++) + { + hi = hosttable[idx]; + if (!hi) + continue; + if (!hi->dead) + continue; + if (!hi->died_at) + continue; /* Do not resurrect manually shot hosts. */ + if (hi->died_at + RESURRECT_INTERVAL <= curtime + || hi->died_at > curtime) + { + hi->dead = 0; + log_info ("resurrected host '%s'", hi->name); + } + } +} + + +/* Reload (SIGHUP) action for this module. We mark all host alive + * even those which have been manually shot. */ +void +ks_hkp_reload (void) +{ + int idx, count; + hostinfo_t hi; + + for (idx=count=0; idx < hosttable_size; idx++) + { + hi = hosttable[idx]; + if (!hi) + continue; + hi->iporname_valid = 0; + if (!hi->dead) + continue; + hi->dead = 0; + count++; + } + if (count) + log_info ("number of resurrected hosts: %d", count); +} + + +/* Send an HTTP request. On success returns an estream object at + R_FP. HOSTPORTSTR is only used for diagnostics. If HTTPHOST is + not NULL it will be used as HTTP "Host" header. If POST_CB is not + NULL a post request is used and that callback is called to allow + writing the post data. If R_HTTP_STATUS is not NULL, the http + status code will be stored there. */ +static gpg_error_t +send_request (ctrl_t ctrl, const char *request, const char *hostportstr, + const char *httphost, unsigned int httpflags, + gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value, + estream_t *r_fp, unsigned int *r_http_status) +{ + gpg_error_t err; + http_session_t session = NULL; + http_t http = NULL; + http_redir_info_t redirinfo = { MAX_REDIRECTS }; + estream_t fp = NULL; + char *request_buffer = NULL; + parsed_uri_t uri = NULL; + + *r_fp = NULL; + + err = http_parse_uri (&uri, request, 0); + if (err) + goto leave; + redirinfo.ctrl = ctrl; + redirinfo.orig_url = request; + redirinfo.orig_onion = uri->onion; + redirinfo.allow_downgrade = 1; + /* FIXME: I am not sure whey we allow a downgrade for hkp requests. + * Needs at least an explanation here.. */ + + once_more: + err = http_session_new (&session, httphost, + ((ctrl->http_no_crl? HTTP_FLAG_NO_CRL : 0) + | HTTP_FLAG_TRUST_DEF), + gnupg_http_tls_verify_cb, ctrl); + if (err) + goto leave; + http_session_set_log_cb (session, cert_log_cb); + http_session_set_timeout (session, ctrl->timeout); + + err = http_open (&http, + post_cb? HTTP_REQ_POST : HTTP_REQ_GET, + request, + httphost, + /* fixme: AUTH */ NULL, + (httpflags + |(opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) + |(dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0) + |(opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0) + |(opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)), + ctrl->http_proxy, + session, + NULL, + /*FIXME curl->srvtag*/NULL); + if (!err) + { + fp = http_get_write_ptr (http); + /* Avoid caches to get the most recent copy of the key. We set + both the Pragma and Cache-Control versions of the header, so + we're good with both HTTP 1.0 and 1.1. */ + es_fputs ("Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n", fp); + if (post_cb) + err = post_cb (post_cb_value, http); + if (!err) + { + http_start_data (http); + if (es_ferror (fp)) + err = gpg_error_from_syserror (); + } + } + if (err) + { + /* Fixme: After a redirection we show the old host name. */ + log_error (_("error connecting to '%s': %s\n"), + hostportstr, gpg_strerror (err)); + goto leave; + } + + /* Wait for the response. */ + dirmngr_tick (ctrl); + err = http_wait_response (http); + if (err) + { + log_error (_("error reading HTTP response for '%s': %s\n"), + hostportstr, gpg_strerror (err)); + goto leave; + } + + if (http_get_tls_info (http, NULL)) + { + /* Update the httpflags so that a redirect won't fallback to an + unencrypted connection. */ + httpflags |= HTTP_FLAG_FORCE_TLS; + } + + if (r_http_status) + *r_http_status = http_get_status_code (http); + + switch (http_get_status_code (http)) + { + case 200: + err = 0; + break; /* Success. */ + + case 301: + case 302: + case 307: + { + xfree (request_buffer); + err = http_prepare_redirect (&redirinfo, http_get_status_code (http), + http_get_header (http, "Location"), + &request_buffer); + if (err) + goto leave; + + request = request_buffer; + http_close (http, 0); + http = NULL; + http_session_release (session); + session = NULL; + } + goto once_more; + + case 501: + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + goto leave; + + case 413: /* Payload too large */ + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + + default: + log_error (_("error accessing '%s': http status %u\n"), + request, http_get_status_code (http)); + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + /* FIXME: We should register a permanent redirection and whether a + host has ever used TLS so that future calls will always use + TLS. */ + + fp = http_get_read_ptr (http); + if (!fp) + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + + /* Return the read stream and close the HTTP context. */ + *r_fp = fp; + http_close (http, 1); + http = NULL; + + leave: + http_close (http, 0); + http_session_release (session); + xfree (request_buffer); + http_release_parsed_uri (uri); + return err; +} + + +/* Helper to evaluate the error code ERR from a send_request() call + with REQUEST. The function returns true if the caller shall try + again. TRIES_LEFT points to a variable to track the number of + retries; this function decrements it and won't return true if it is + down to zero. EXTRA_TRIES_LEFT does the same but only for + transient http status codes. */ +static int +handle_send_request_error (ctrl_t ctrl, gpg_error_t err, const char *request, + unsigned int http_status, unsigned int *tries_left, + unsigned int *extra_tries_left) +{ + int retry = 0; + + /* Fixme: Should we disable all hosts of a protocol family if a + * request for an address of that familiy returned ENETDOWN? */ + + switch (gpg_err_code (err)) + { + case GPG_ERR_ECONNREFUSED: + if (tor_not_running_p (ctrl)) + break; /* A retry does not make sense. */ + /* Okay: Tor is up or --use-tor is not used. */ + /*FALLTHRU*/ + case GPG_ERR_ENETUNREACH: + case GPG_ERR_ENETDOWN: + case GPG_ERR_UNKNOWN_HOST: + case GPG_ERR_NETWORK: + case GPG_ERR_EIO: /* Sometimes used by estream cookie functions. */ + case GPG_ERR_EADDRNOTAVAIL: /* e.g. when IPv6 is disabled */ + case GPG_ERR_EAFNOSUPPORT: /* e.g. when IPv6 is not compiled in */ + if (mark_host_dead (request) && *tries_left) + retry = 1; + break; + + case GPG_ERR_ETIMEDOUT: + if (*tries_left) + { + log_info ("selecting a different host due to a timeout\n"); + retry = 1; + } + break; + + case GPG_ERR_EACCES: + if (dirmngr_use_tor ()) + { + log_info ("(Tor configuration problem)\n"); + dirmngr_status (ctrl, "WARNING", "tor_config_problem 0", + "Please check that the \"SocksPort\" flag " + "\"IPv6Traffic\" is set in torrc", NULL); + } + break; + + case GPG_ERR_NO_DATA: + { + switch (http_status) + { + case 502: /* Bad Gateway */ + log_info ("marking host dead due to a %u (%s)\n", + http_status, http_status2string (http_status)); + if (mark_host_dead (request) && *tries_left) + retry = 1; + break; + + case 503: /* Service Unavailable */ + case 504: /* Gateway Timeout */ + if (*extra_tries_left) + { + log_info ("selecting a different host due to a %u (%s)", + http_status, http_status2string (http_status)); + retry = 2; + } + break; + } + } + break; + + default: + break; + } + + if (retry == 2) + { + if (*extra_tries_left) + --*extra_tries_left; + } + else + { + if (*tries_left) + --*tries_left; + } + + return retry; +} + + +/* Search the keyserver identified by URI for keys matching PATTERN. + On success R_FP has an open stream to read the data. If + R_HTTP_STATUS is not NULL, the http status code will be stored + there. */ +gpg_error_t +ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, + estream_t *r_fp, unsigned int *r_http_status) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + char fprbuf[2+40+1]; + char *hostport = NULL; + char *request = NULL; + estream_t fp = NULL; + int reselect; + unsigned int httpflags; + char *httphost = NULL; + unsigned int http_status; + unsigned int tries = SEND_REQUEST_RETRIES; + unsigned int extra_tries = SEND_REQUEST_EXTRA_RETRIES; + + *r_fp = NULL; + + /* Remove search type indicator and adjust PATTERN accordingly. + Note that HKP keyservers like the 0x to be present when searching + by keyid. We need to re-format the fingerprint and keyids so to + remove the gpg specific force-use-of-this-key flag ("!"). */ + err = classify_user_id (pattern, &desc, 1); + if (err) + return err; + switch (desc.mode) + { + case KEYDB_SEARCH_MODE_EXACT: + case KEYDB_SEARCH_MODE_SUBSTR: + case KEYDB_SEARCH_MODE_MAIL: + case KEYDB_SEARCH_MODE_MAILSUB: + pattern = desc.u.name; + break; + case KEYDB_SEARCH_MODE_SHORT_KID: + snprintf (fprbuf, sizeof fprbuf, "0x%08lX", (ulong)desc.u.kid[1]); + pattern = fprbuf; + break; + case KEYDB_SEARCH_MODE_LONG_KID: + snprintf (fprbuf, sizeof fprbuf, "0x%08lX%08lX", + (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]); + pattern = fprbuf; + break; + case KEYDB_SEARCH_MODE_FPR16: + fprbuf[0] = '0'; + fprbuf[1] = 'x'; + bin2hex (desc.u.fpr, 16, fprbuf+2); + pattern = fprbuf; + break; + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + fprbuf[0] = '0'; + fprbuf[1] = 'x'; + bin2hex (desc.u.fpr, 20, fprbuf+2); + pattern = fprbuf; + break; + default: + return gpg_error (GPG_ERR_INV_USER_ID); + } + + /* Build the request string. */ + reselect = 0; + again: + { + char *searchkey; + + xfree (hostport); hostport = NULL; + xfree (httphost); httphost = NULL; + err = make_host_part (ctrl, uri->scheme, uri->host, uri->port, + reselect, uri->explicit_port, + &hostport, &httpflags, &httphost); + if (err) + goto leave; + + searchkey = http_escape_string (pattern, EXTRA_ESCAPE_CHARS); + if (!searchkey) + { + err = gpg_error_from_syserror (); + goto leave; + } + + xfree (request); + request = strconcat (hostport, + "/pks/lookup?op=index&options=mr&search=", + searchkey, + NULL); + xfree (searchkey); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + /* Send the request. */ + err = send_request (ctrl, request, hostport, httphost, httpflags, + NULL, NULL, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, + &tries, &extra_tries)) + { + reselect = 1; + goto again; + } + if (r_http_status) + *r_http_status = http_status; + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + dirmngr_status (ctrl, "SOURCE", hostport, NULL); + goto leave; + } + + err = dirmngr_status (ctrl, "SOURCE", hostport, NULL); + if (err) + goto leave; + + /* Peek at the response. */ + { + int c = es_getc (fp); + if (c == -1) + { + err = es_ferror (fp)?gpg_error_from_syserror ():gpg_error (GPG_ERR_EOF); + log_error ("error reading response: %s\n", gpg_strerror (err)); + goto leave; + } + if (c == '<') + { + /* The document begins with a '<': Assume a HTML response, + which we don't support. */ + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + goto leave; + } + es_ungetc (c, fp); + } + + /* Return the read stream. */ + *r_fp = fp; + fp = NULL; + + leave: + es_fclose (fp); + xfree (request); + xfree (hostport); + xfree (httphost); + return err; +} + + +/* Get the key described key the KEYSPEC string from the keyserver + identified by URI. On success R_FP has an open stream to read the + data. The data will be provided in a format GnuPG can import + (either a binary OpenPGP message or an armored one). */ +gpg_error_t +ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + char kidbuf[2+40+1]; + const char *exactname = NULL; + char *searchkey = NULL; + char *hostport = NULL; + char *request = NULL; + estream_t fp = NULL; + int reselect; + char *httphost = NULL; + unsigned int httpflags; + unsigned int http_status; + unsigned int tries = SEND_REQUEST_RETRIES; + unsigned int extra_tries = SEND_REQUEST_EXTRA_RETRIES; + + *r_fp = NULL; + + /* Remove search type indicator and adjust PATTERN accordingly. + Note that HKP keyservers like the 0x to be present when searching + by keyid. We need to re-format the fingerprint and keyids so to + remove the gpg specific force-use-of-this-key flag ("!"). */ + err = classify_user_id (keyspec, &desc, 1); + if (err) + return err; + switch (desc.mode) + { + case KEYDB_SEARCH_MODE_SHORT_KID: + snprintf (kidbuf, sizeof kidbuf, "0x%08lX", (ulong)desc.u.kid[1]); + break; + case KEYDB_SEARCH_MODE_LONG_KID: + snprintf (kidbuf, sizeof kidbuf, "0x%08lX%08lX", + (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]); + break; + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + /* This is a v4 fingerprint. */ + kidbuf[0] = '0'; + kidbuf[1] = 'x'; + bin2hex (desc.u.fpr, 20, kidbuf+2); + break; + + case KEYDB_SEARCH_MODE_EXACT: + exactname = desc.u.name; + break; + + case KEYDB_SEARCH_MODE_FPR16: + log_error ("HKP keyservers do not support v3 fingerprints\n"); + /* fall through */ + default: + return gpg_error (GPG_ERR_INV_USER_ID); + } + + searchkey = http_escape_string (exactname? exactname : kidbuf, + EXTRA_ESCAPE_CHARS); + if (!searchkey) + { + err = gpg_error_from_syserror (); + goto leave; + } + + reselect = 0; + again: + /* Build the request string. */ + xfree (hostport); hostport = NULL; + xfree (httphost); httphost = NULL; + err = make_host_part (ctrl, uri->scheme, uri->host, uri->port, + reselect, uri->explicit_port, + &hostport, &httpflags, &httphost); + if (err) + goto leave; + + xfree (request); + request = strconcat (hostport, + "/pks/lookup?op=get&options=mr&search=", + searchkey, + exactname? "&exact=on":"", + NULL); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Send the request. */ + err = send_request (ctrl, request, hostport, httphost, httpflags, + NULL, NULL, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, + &tries, &extra_tries)) + { + reselect = 1; + goto again; + } + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + dirmngr_status (ctrl, "SOURCE", hostport, NULL); + goto leave; + } + + err = dirmngr_status (ctrl, "SOURCE", hostport, NULL); + if (err) + goto leave; + + /* Return the read stream and close the HTTP context. */ + *r_fp = fp; + fp = NULL; + + leave: + es_fclose (fp); + xfree (request); + xfree (hostport); + xfree (httphost); + xfree (searchkey); + return err; +} + + + + +/* Callback parameters for put_post_cb. */ +struct put_post_parm_s +{ + char *datastring; +}; + + +/* Helper for ks_hkp_put. */ +static gpg_error_t +put_post_cb (void *opaque, http_t http) +{ + struct put_post_parm_s *parm = opaque; + gpg_error_t err = 0; + estream_t fp; + size_t len; + + fp = http_get_write_ptr (http); + len = strlen (parm->datastring); + + es_fprintf (fp, + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %zu\r\n", len+8 /* 8 is for "keytext" */); + http_start_data (http); + if (es_fputs ("keytext=", fp) || es_write (fp, parm->datastring, len, NULL)) + err = gpg_error_from_syserror (); + return err; +} + + +/* Send the key in {DATA,DATALEN} to the keyserver identified by URI. */ +gpg_error_t +ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) +{ + gpg_error_t err; + char *hostport = NULL; + char *request = NULL; + estream_t fp = NULL; + struct put_post_parm_s parm; + char *armored = NULL; + int reselect; + char *httphost = NULL; + unsigned int httpflags; + unsigned int http_status; + unsigned int tries = SEND_REQUEST_RETRIES; + unsigned int extra_tries = SEND_REQUEST_EXTRA_RETRIES; + + parm.datastring = NULL; + + err = armor_data (&armored, data, datalen); + if (err) + goto leave; + + parm.datastring = http_escape_string (armored, EXTRA_ESCAPE_CHARS); + if (!parm.datastring) + { + err = gpg_error_from_syserror (); + goto leave; + } + xfree (armored); + armored = NULL; + + /* Build the request string. */ + reselect = 0; + again: + xfree (hostport); hostport = NULL; + xfree (httphost); httphost = NULL; + err = make_host_part (ctrl, uri->scheme, uri->host, uri->port, + reselect, uri->explicit_port, + &hostport, &httpflags, &httphost); + if (err) + goto leave; + + xfree (request); + request = strconcat (hostport, "/pks/add", NULL); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Send the request. */ + err = send_request (ctrl, request, hostport, httphost, 0, + put_post_cb, &parm, &fp, &http_status); + if (handle_send_request_error (ctrl, err, request, http_status, + &tries, &extra_tries)) + { + reselect = 1; + goto again; + } + if (err) + goto leave; + + leave: + es_fclose (fp); + xfree (parm.datastring); + xfree (armored); + xfree (request); + xfree (hostport); + xfree (httphost); + return err; +} diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c new file mode 100644 index 0000000..c96625d --- /dev/null +++ b/dirmngr/ks-engine-http.c @@ -0,0 +1,225 @@ +/* ks-engine-http.c - HTTP OpenPGP key access + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "dirmngr.h" +#include "misc.h" +#include "ks-engine.h" + +/* How many redirections do we allow. */ +#define MAX_REDIRECTS 2 + +/* Print a help output for the schemata supported by this module. */ +gpg_error_t +ks_http_help (ctrl_t ctrl, parsed_uri_t uri) +{ + const char data[] = + "Handler for HTTP URLs:\n" + " http://\n" +#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS + " https://\n" +#endif + "Supported methods: fetch\n"; + gpg_error_t err; + +#if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS + const char data2[] = " http\n https"; +#else + const char data2[] = " http"; +#endif + + if (!uri) + err = ks_print_help (ctrl, data2); + else if (uri->is_http && strcmp (uri->scheme, "hkp")) + err = ks_print_help (ctrl, data); + else + err = 0; + + return err; +} + + +/* Get the key from URL which is expected to specify a http style + * scheme. On success R_FP has an open stream to read the data. + * Despite its name this function is also used to retrieve arbitrary + * data via https or http. + */ +gpg_error_t +ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, + estream_t *r_fp) +{ + gpg_error_t err; + http_session_t session = NULL; + unsigned int session_flags; + http_t http = NULL; + http_redir_info_t redirinfo = { MAX_REDIRECTS }; + estream_t fp = NULL; + char *request_buffer = NULL; + parsed_uri_t uri = NULL; + parsed_uri_t helpuri = NULL; + + err = http_parse_uri (&uri, url, 0); + if (err) + goto leave; + redirinfo.ctrl = ctrl; + redirinfo.orig_url = url; + redirinfo.orig_onion = uri->onion; + redirinfo.orig_https = uri->use_tls; + redirinfo.allow_downgrade = !!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE); + + /* By default we only use the system provided certificates with this + * fetch command. */ + session_flags = HTTP_FLAG_TRUST_SYS; + if ((flags & KS_HTTP_FETCH_NO_CRL) || ctrl->http_no_crl) + session_flags |= HTTP_FLAG_NO_CRL; + if ((flags & KS_HTTP_FETCH_TRUST_CFG)) + session_flags |= HTTP_FLAG_TRUST_CFG; + + once_more: + err = http_session_new (&session, NULL, session_flags, + gnupg_http_tls_verify_cb, ctrl); + if (err) + goto leave; + http_session_set_log_cb (session, cert_log_cb); + http_session_set_timeout (session, ctrl->timeout); + + *r_fp = NULL; + err = http_open (&http, + HTTP_REQ_GET, + url, + /* httphost */ NULL, + /* fixme: AUTH */ NULL, + ((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) + | (DBG_LOOKUP? HTTP_FLAG_LOG_RESP:0) + | (dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0) + | (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0) + | (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)), + ctrl->http_proxy, + session, + NULL, + /*FIXME curl->srvtag*/NULL); + if (!err) + { + fp = http_get_write_ptr (http); + /* Avoid caches to get the most recent copy of the key. We set + * both the Pragma and Cache-Control versions of the header, so + * we're good with both HTTP 1.0 and 1.1. */ + if ((flags & KS_HTTP_FETCH_NOCACHE)) + es_fputs ("Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n", fp); + http_start_data (http); + if (es_ferror (fp)) + err = gpg_error_from_syserror (); + } + if (err) + { + log_error (_("error connecting to '%s': %s\n"), + url, gpg_strerror (err)); + if (gpg_err_code (err) == GPG_ERR_WRONG_NAME + && gpg_err_source (err) == GPG_ERR_SOURCE_TLS) + { + const char *errhostname; + + http_release_parsed_uri (helpuri); + if (http_parse_uri (&helpuri, url, 0)) + errhostname = url; /* On parse error we use the full URL. */ + else + errhostname = helpuri->host? helpuri->host : "?"; + + dirmngr_status_printf (ctrl, "NOTE", + "tls_cert_error %u" + " bad cert for '%s': %s", + err, errhostname, + "Hostname does not match the certificate"); + } + goto leave; + } + + /* Wait for the response. */ + dirmngr_tick (ctrl); + err = http_wait_response (http); + if (err) + { + log_error (_("error reading HTTP response for '%s': %s\n"), + url, gpg_strerror (err)); + goto leave; + } + + switch (http_get_status_code (http)) + { + case 200: + err = 0; + break; /* Success. */ + + case 301: + case 302: + case 307: + { + xfree (request_buffer); + err = http_prepare_redirect (&redirinfo, http_get_status_code (http), + http_get_header (http, "Location"), + &request_buffer); + if (err) + goto leave; + + url = request_buffer; + http_close (http, 0); + http = NULL; + http_session_release (session); + session = NULL; + } + goto once_more; + + case 413: /* Payload too large */ + err = gpg_error (GPG_ERR_TOO_LARGE); + goto leave; + + default: + log_error (_("error accessing '%s': http status %u\n"), + url, http_get_status_code (http)); + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + fp = http_get_read_ptr (http); + if (!fp) + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + + /* Return the read stream and close the HTTP context. */ + *r_fp = fp; + http_close (http, 1); + http = NULL; + + leave: + http_close (http, 0); + http_session_release (session); + xfree (request_buffer); + http_release_parsed_uri (uri); + http_release_parsed_uri (helpuri); + return err; +} diff --git a/dirmngr/ks-engine-kdns.c b/dirmngr/ks-engine-kdns.c new file mode 100644 index 0000000..71463fe --- /dev/null +++ b/dirmngr/ks-engine-kdns.c @@ -0,0 +1,79 @@ +/* ks-engine-kdns.c - KDNS OpenPGP key access + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "dirmngr.h" +#include "misc.h" +#include "../common/userids.h" +#include "ks-engine.h" + +/* Print a help output for the schemata supported by this module. */ +gpg_error_t +ks_kdns_help (ctrl_t ctrl, parsed_uri_t uri) +{ + const char data[] = + "This keyserver engine accepts URLs of the form:\n" + " kdns://[NAMESERVER]/[ROOT][?at=STRING]\n" + "with\n" + " NAMESERVER used for queries (default: system standard)\n" + " ROOT a DNS name appended to the query (default: none)\n" + " STRING a string to replace the '@' (default: \".\")\n" + "If a long answer is expected add the parameter \"usevc=1\".\n" + "Supported methods: fetch\n" + "Example:\n" + "A query for \"hacker@gnupg.org\" with\n" + " kdns://10.0.0.1/example.net?at=_key_&usevc=1\n" + "setup as --auto-key-lookup in gpg does a CERT record query\n" + "with type PGP on the nameserver 10.0.0.1 for\n" + " hacker._key_.gnupg.org.example.net"; + gpg_error_t err; + + if (!uri) + err = ks_print_help (ctrl, " kdns"); + else if (!strcmp (uri->scheme, "kdns")) + err = ks_print_help (ctrl, data); + else + err = 0; + + return err; +} + + +/* Get the key from URI which is expected to specify a kdns scheme. + On success R_FP has an open stream to read the data. */ +gpg_error_t +ks_kdns_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp) +{ + gpg_error_t err; + + (void)ctrl; + *r_fp = NULL; + + if (strcmp (uri->scheme, "kdns")) + return gpg_error (GPG_ERR_INV_ARG); + + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + return err; +} diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c new file mode 100644 index 0000000..bd6f8d5 --- /dev/null +++ b/dirmngr/ks-engine-ldap.c @@ -0,0 +1,2248 @@ +/* ks-engine-ldap.c - talk to a LDAP keyserver + * Copyright (C) 2001, 2002, 2004, 2005, 2006 + * 2007 Free Software Foundation, Inc. + * Copyright (C) 2015, 2020 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#ifdef HAVE_GETOPT_H +# include <getopt.h> +#endif +#include <stdlib.h> +#include <errno.h> +#include <assert.h> + +#ifdef _WIN32 +# include <winsock2.h> +# include <winldap.h> +#else +# ifdef NEED_LBER_H +# include <lber.h> +# endif +/* For OpenLDAP, to enable the API that we're using. */ +# define LDAP_DEPRECATED 1 +# include <ldap.h> +#endif +#include <npth.h> + +#include "dirmngr.h" +#include "misc.h" +#include "../common/userids.h" +#include "../common/mbox-util.h" +#include "ks-engine.h" +#include "ldap-parse-uri.h" + + +/* Flags with infos from the connected server. */ +#define SERVERINFO_REALLDAP 1 /* This is not the PGP keyserver. */ +#define SERVERINFO_PGPKEYV2 2 /* Needs "pgpeyV2" instead of "pgpKey" */ +#define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */ +#define SERVERINFO_NTDS 8 /* Server is an Active Directory. */ + + + +#ifndef HAVE_TIMEGM +time_t timegm(struct tm *tm); +#endif + +/* Convert an LDAP error to a GPG error. */ +static int +ldap_err_to_gpg_err (int code) +{ + gpg_err_code_t ec; + + switch (code) + { +#ifdef LDAP_X_CONNECTING + case LDAP_X_CONNECTING: ec = GPG_ERR_LDAP_X_CONNECTING; break; +#endif + + case LDAP_REFERRAL_LIMIT_EXCEEDED: ec = GPG_ERR_LDAP_REFERRAL_LIMIT; break; + case LDAP_CLIENT_LOOP: ec = GPG_ERR_LDAP_CLIENT_LOOP; break; + case LDAP_NO_RESULTS_RETURNED: ec = GPG_ERR_LDAP_NO_RESULTS; break; + case LDAP_CONTROL_NOT_FOUND: ec = GPG_ERR_LDAP_CONTROL_NOT_FOUND; break; + case LDAP_NOT_SUPPORTED: ec = GPG_ERR_LDAP_NOT_SUPPORTED; break; + case LDAP_CONNECT_ERROR: ec = GPG_ERR_LDAP_CONNECT; break; + case LDAP_NO_MEMORY: ec = GPG_ERR_LDAP_NO_MEMORY; break; + case LDAP_PARAM_ERROR: ec = GPG_ERR_LDAP_PARAM; break; + case LDAP_USER_CANCELLED: ec = GPG_ERR_LDAP_USER_CANCELLED; break; + case LDAP_FILTER_ERROR: ec = GPG_ERR_LDAP_FILTER; break; + case LDAP_AUTH_UNKNOWN: ec = GPG_ERR_LDAP_AUTH_UNKNOWN; break; + case LDAP_TIMEOUT: ec = GPG_ERR_LDAP_TIMEOUT; break; + case LDAP_DECODING_ERROR: ec = GPG_ERR_LDAP_DECODING; break; + case LDAP_ENCODING_ERROR: ec = GPG_ERR_LDAP_ENCODING; break; + case LDAP_LOCAL_ERROR: ec = GPG_ERR_LDAP_LOCAL; break; + case LDAP_SERVER_DOWN: ec = GPG_ERR_LDAP_SERVER_DOWN; break; + + case LDAP_SUCCESS: ec = GPG_ERR_LDAP_SUCCESS; break; + + case LDAP_OPERATIONS_ERROR: ec = GPG_ERR_LDAP_OPERATIONS; break; + case LDAP_PROTOCOL_ERROR: ec = GPG_ERR_LDAP_PROTOCOL; break; + case LDAP_TIMELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_TIMELIMIT; break; + case LDAP_SIZELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_SIZELIMIT; break; + case LDAP_COMPARE_FALSE: ec = GPG_ERR_LDAP_COMPARE_FALSE; break; + case LDAP_COMPARE_TRUE: ec = GPG_ERR_LDAP_COMPARE_TRUE; break; + case LDAP_AUTH_METHOD_NOT_SUPPORTED: ec=GPG_ERR_LDAP_UNSUPPORTED_AUTH;break; + case LDAP_STRONG_AUTH_REQUIRED: ec = GPG_ERR_LDAP_STRONG_AUTH_RQRD; break; + case LDAP_PARTIAL_RESULTS: ec = GPG_ERR_LDAP_PARTIAL_RESULTS; break; + case LDAP_REFERRAL: ec = GPG_ERR_LDAP_REFERRAL; break; + +#ifdef LDAP_ADMINLIMIT_EXCEEDED + case LDAP_ADMINLIMIT_EXCEEDED: ec = GPG_ERR_LDAP_ADMINLIMIT; break; +#endif + +#ifdef LDAP_UNAVAILABLE_CRITICAL_EXTENSION + case LDAP_UNAVAILABLE_CRITICAL_EXTENSION: + ec = GPG_ERR_LDAP_UNAVAIL_CRIT_EXTN; break; +#endif + + case LDAP_CONFIDENTIALITY_REQUIRED: ec = GPG_ERR_LDAP_CONFIDENT_RQRD; break; + case LDAP_SASL_BIND_IN_PROGRESS: ec = GPG_ERR_LDAP_SASL_BIND_INPROG; break; + case LDAP_NO_SUCH_ATTRIBUTE: ec = GPG_ERR_LDAP_NO_SUCH_ATTRIBUTE; break; + case LDAP_UNDEFINED_TYPE: ec = GPG_ERR_LDAP_UNDEFINED_TYPE; break; + case LDAP_INAPPROPRIATE_MATCHING: ec = GPG_ERR_LDAP_BAD_MATCHING; break; + case LDAP_CONSTRAINT_VIOLATION: ec = GPG_ERR_LDAP_CONST_VIOLATION; break; + +#ifdef LDAP_TYPE_OR_VALUE_EXISTS + case LDAP_TYPE_OR_VALUE_EXISTS: ec = GPG_ERR_LDAP_TYPE_VALUE_EXISTS; break; +#endif + + case LDAP_INVALID_SYNTAX: ec = GPG_ERR_LDAP_INV_SYNTAX; break; + case LDAP_NO_SUCH_OBJECT: ec = GPG_ERR_LDAP_NO_SUCH_OBJ; break; + case LDAP_ALIAS_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_PROBLEM; break; + case LDAP_INVALID_DN_SYNTAX: ec = GPG_ERR_LDAP_INV_DN_SYNTAX; break; + case LDAP_IS_LEAF: ec = GPG_ERR_LDAP_IS_LEAF; break; + case LDAP_ALIAS_DEREF_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_DEREF; break; + +#ifdef LDAP_X_PROXY_AUTHZ_FAILURE + case LDAP_X_PROXY_AUTHZ_FAILURE: ec = GPG_ERR_LDAP_X_PROXY_AUTH_FAIL; break; +#endif + + case LDAP_INAPPROPRIATE_AUTH: ec = GPG_ERR_LDAP_BAD_AUTH; break; + case LDAP_INVALID_CREDENTIALS: ec = GPG_ERR_LDAP_INV_CREDENTIALS; break; + +#ifdef LDAP_INSUFFICIENT_ACCESS + case LDAP_INSUFFICIENT_ACCESS: ec = GPG_ERR_LDAP_INSUFFICIENT_ACC; break; +#endif + + case LDAP_BUSY: ec = GPG_ERR_LDAP_BUSY; break; + case LDAP_UNAVAILABLE: ec = GPG_ERR_LDAP_UNAVAILABLE; break; + case LDAP_UNWILLING_TO_PERFORM: ec = GPG_ERR_LDAP_UNWILL_TO_PERFORM; break; + case LDAP_LOOP_DETECT: ec = GPG_ERR_LDAP_LOOP_DETECT; break; + case LDAP_NAMING_VIOLATION: ec = GPG_ERR_LDAP_NAMING_VIOLATION; break; + case LDAP_OBJECT_CLASS_VIOLATION: ec = GPG_ERR_LDAP_OBJ_CLS_VIOLATION; break; + case LDAP_NOT_ALLOWED_ON_NONLEAF: ec=GPG_ERR_LDAP_NOT_ALLOW_NONLEAF;break; + case LDAP_NOT_ALLOWED_ON_RDN: ec = GPG_ERR_LDAP_NOT_ALLOW_ON_RDN; break; + case LDAP_ALREADY_EXISTS: ec = GPG_ERR_LDAP_ALREADY_EXISTS; break; + case LDAP_NO_OBJECT_CLASS_MODS: ec = GPG_ERR_LDAP_NO_OBJ_CLASS_MODS; break; + case LDAP_RESULTS_TOO_LARGE: ec = GPG_ERR_LDAP_RESULTS_TOO_LARGE; break; + case LDAP_AFFECTS_MULTIPLE_DSAS: ec = GPG_ERR_LDAP_AFFECTS_MULT_DSAS; break; + +#ifdef LDAP_VLV_ERROR + case LDAP_VLV_ERROR: ec = GPG_ERR_LDAP_VLV; break; +#endif + + case LDAP_OTHER: ec = GPG_ERR_LDAP_OTHER; break; + +#ifdef LDAP_CUP_RESOURCES_EXHAUSTED + case LDAP_CUP_RESOURCES_EXHAUSTED: ec=GPG_ERR_LDAP_CUP_RESOURCE_LIMIT;break; + case LDAP_CUP_SECURITY_VIOLATION: ec=GPG_ERR_LDAP_CUP_SEC_VIOLATION; break; + case LDAP_CUP_INVALID_DATA: ec = GPG_ERR_LDAP_CUP_INV_DATA; break; + case LDAP_CUP_UNSUPPORTED_SCHEME: ec = GPG_ERR_LDAP_CUP_UNSUP_SCHEME; break; + case LDAP_CUP_RELOAD_REQUIRED: ec = GPG_ERR_LDAP_CUP_RELOAD; break; +#endif + +#ifdef LDAP_CANCELLED + case LDAP_CANCELLED: ec = GPG_ERR_LDAP_CANCELLED; break; +#endif + +#ifdef LDAP_NO_SUCH_OPERATION + case LDAP_NO_SUCH_OPERATION: ec = GPG_ERR_LDAP_NO_SUCH_OPERATION; break; +#endif + +#ifdef LDAP_TOO_LATE + case LDAP_TOO_LATE: ec = GPG_ERR_LDAP_TOO_LATE; break; +#endif + +#ifdef LDAP_CANNOT_CANCEL + case LDAP_CANNOT_CANCEL: ec = GPG_ERR_LDAP_CANNOT_CANCEL; break; +#endif + +#ifdef LDAP_ASSERTION_FAILED + case LDAP_ASSERTION_FAILED: ec = GPG_ERR_LDAP_ASSERTION_FAILED; break; +#endif + +#ifdef LDAP_PROXIED_AUTHORIZATION_DENIED + case LDAP_PROXIED_AUTHORIZATION_DENIED: + ec = GPG_ERR_LDAP_PROX_AUTH_DENIED; break; +#endif + + default: +#if defined(LDAP_E_ERROR) && defined(LDAP_X_ERROR) + if (LDAP_E_ERROR (code)) + ec = GPG_ERR_LDAP_E_GENERAL; + else if (LDAP_X_ERROR (code)) + ec = GPG_ERR_LDAP_X_GENERAL; + else +#endif + ec = GPG_ERR_LDAP_GENERAL; + break; + } + + return ec; +} + +/* Retrieve an LDAP error and return it's GPG equivalent. */ +static int +ldap_to_gpg_err (LDAP *ld) +{ +#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER) + int err; + + if (ldap_get_option (ld, LDAP_OPT_ERROR_NUMBER, &err) == 0) + return ldap_err_to_gpg_err (err); + else + return GPG_ERR_GENERAL; +#elif defined(HAVE_LDAP_LD_ERRNO) + return ldap_err_to_gpg_err (ld->ld_errno); +#else + /* We should never get here since the LDAP library should always + have either ldap_get_option or ld_errno, but just in case... */ + return GPG_ERR_INTERNAL; +#endif +} + +static time_t +ldap2epochtime (const char *timestr) +{ + struct tm pgptime; + time_t answer; + + memset (&pgptime, 0, sizeof(pgptime)); + + /* YYYYMMDDHHmmssZ */ + + sscanf (timestr, "%4d%2d%2d%2d%2d%2d", + &pgptime.tm_year, + &pgptime.tm_mon, + &pgptime.tm_mday, + &pgptime.tm_hour, + &pgptime.tm_min, + &pgptime.tm_sec); + + pgptime.tm_year -= 1900; + pgptime.tm_isdst = -1; + pgptime.tm_mon--; + + /* mktime() takes the timezone into account, so we use timegm() */ + + answer = timegm (&pgptime); + + return answer; +} + +/* Caller must free the result. */ +static char * +tm2ldaptime (struct tm *tm) +{ + struct tm tmp = *tm; + char buf[16]; + + /* YYYYMMDDHHmmssZ */ + + tmp.tm_year += 1900; + tmp.tm_mon ++; + + snprintf (buf, sizeof buf, "%04d%02d%02d%02d%02d%02dZ", + tmp.tm_year, + tmp.tm_mon, + tmp.tm_mday, + tmp.tm_hour, + tmp.tm_min, + tmp.tm_sec); + + return xstrdup (buf); +} + +#if 0 +/* Caller must free */ +static char * +epoch2ldaptime (time_t stamp) +{ + struct tm tm; + if (gmtime_r (&stamp, &tm)) + return tm2ldaptime (&tm); + else + return xstrdup ("INVALID TIME"); +} +#endif + +/* Print a help output for the schemata supported by this module. */ +gpg_error_t +ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri) +{ + const char data[] = + "Handler for LDAP URLs:\n" + " ldap://host:port/[BASEDN]???[bindname=BINDNAME,password=PASSWORD]\n" + "\n" + "Note: basedn, bindname and password need to be percent escaped. In\n" + "particular, spaces need to be replaced with %20 and commas with %2c.\n" + "bindname will typically be of the form:\n" + "\n" + " uid=user%2cou=PGP%20Users%2cdc=EXAMPLE%2cdc=ORG\n" + "\n" + "The ldaps:// and ldapi:// schemes are also supported. If ldaps is used\n" + "then the server's certificate will be checked. If it is not valid, any\n" + "operation will be aborted.\n" + "\n" + "Supported methods: search, get, put\n"; + gpg_error_t err; + + if(!uri) + err = ks_print_help (ctrl, " ldap"); + else if (strcmp (uri->scheme, "ldap") == 0 + || strcmp (uri->scheme, "ldaps") == 0 + || strcmp (uri->scheme, "ldapi") == 0) + err = ks_print_help (ctrl, data); + else + err = 0; + + return err; +} + +/* Convert a keyspec to a filter. Return an error if the keyspec is + bad or is not supported. The filter is escaped and returned in + *filter. It is the caller's responsibility to free *filter. + *filter is only set if this function returns success (i.e., 0). */ +static gpg_error_t +keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact, + unsigned int serverinfo) +{ + /* Remove search type indicator and adjust PATTERN accordingly. + Note: don't include a preceding 0x when searching by keyid. */ + + /* XXX: Should we include disabled / revoke options? */ + KEYDB_SEARCH_DESC desc; + char *f = NULL; + char *freeme = NULL; + char *p; + + gpg_error_t err = classify_user_id (keyspec, &desc, 1); + if (err) + return err; + + switch (desc.mode) + { + case KEYDB_SEARCH_MODE_EXACT: + f = xasprintf ("(pgpUserID=%s)", + (freeme = ldap_escape_filter (desc.u.name))); + break; + + case KEYDB_SEARCH_MODE_SUBSTR: + if (! only_exact) + f = xasprintf ("(pgpUserID=*%s*)", + (freeme = ldap_escape_filter (desc.u.name))); + break; + + case KEYDB_SEARCH_MODE_MAIL: + freeme = ldap_escape_filter (desc.u.name); + if (!freeme) + break; + if (*freeme == '<' && freeme[1] && freeme[2]) + { + /* Strip angle brackets. Note that it is does not + * matter whether we work on the plan or LDAP escaped + * version of the mailbox. */ + p = freeme + 1; + if (p[strlen(p)-1] == '>') + p[strlen(p)-1] = 0; + } + else + p = freeme; + if ((serverinfo & SERVERINFO_SCHEMAV2)) + f = xasprintf ("(gpgMailbox=%s)", p); + else if (!only_exact) + f = xasprintf ("(pgpUserID=*<%s>*)", p); + break; + + case KEYDB_SEARCH_MODE_MAILSUB: + if (! only_exact) + f = xasprintf ("(pgpUserID=*<*%s*>*)", + (freeme = ldap_escape_filter (desc.u.name))); + break; + + case KEYDB_SEARCH_MODE_MAILEND: + if (! only_exact) + f = xasprintf ("(pgpUserID=*<*%s>*)", + (freeme = ldap_escape_filter (desc.u.name))); + break; + + case KEYDB_SEARCH_MODE_SHORT_KID: + f = xasprintf ("(pgpKeyID=%08lX)", (ulong) desc.u.kid[1]); + break; + case KEYDB_SEARCH_MODE_LONG_KID: + f = xasprintf ("(pgpCertID=%08lX%08lX)", + (ulong) desc.u.kid[0], (ulong) desc.u.kid[1]); + break; + + case KEYDB_SEARCH_MODE_FPR16: + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + if ((serverinfo & SERVERINFO_SCHEMAV2)) + { + freeme = bin2hex (desc.u.fpr, 20, NULL); + if (!freeme) + return gpg_error_from_syserror (); + f = xasprintf ("(|(gpgFingerprint=%s)(gpgSubFingerprint=%s))", + freeme, freeme); + /* FIXME: For an exact search and in case of a match on + * gpgSubFingerprint we need to check that there is only one + * matching value. */ + } + break; + + case KEYDB_SEARCH_MODE_ISSUER: + case KEYDB_SEARCH_MODE_ISSUER_SN: + case KEYDB_SEARCH_MODE_SN: + case KEYDB_SEARCH_MODE_SUBJECT: + case KEYDB_SEARCH_MODE_KEYGRIP: + case KEYDB_SEARCH_MODE_WORDS: + case KEYDB_SEARCH_MODE_FIRST: + case KEYDB_SEARCH_MODE_NEXT: + default: + break; + } + + xfree (freeme); + + if (! f) + { + log_error ("Unsupported search mode.\n"); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + *filter = f; + + return 0; +} + + + +/* Connect to an LDAP server and interrogate it. + + - uri describes the server to connect to and various options + including whether to use TLS and the username and password (see + ldap_parse_uri for a description of the various fields). + + This function returns: + + - The ldap connection handle in *LDAP_CONNP. + + - The base DN for the PGP key space by querying the + pgpBaseKeySpaceDN attribute (This is normally + 'ou=PGP Keys,dc=EXAMPLE,dc=ORG'). + + - The attribute to lookup to find the pgp key. This is either + 'pgpKey' or 'pgpKeyV2'. + + - Whether this is a real ldap server. (It's unclear what this + exactly means.) + + The values are returned in the passed variables. If you pass NULL, + then the value won't be returned. It is the caller's + responsibility to release *LDAP_CONNP with ldap_unbind and xfree + *BASEDNP. + + If this function successfully interrogated the server, it returns + 0. If there was an LDAP error, it returns the LDAP error code. If + an error occurred, *basednp, etc., are undefined (and don't need to + be freed.) + + R_SERVERINFO receives information about the server. + + If no LDAP error occurred, you still need to check that *basednp is + valid. If it is NULL, then the server does not appear to be an + OpenPGP Keyserver. */ +static int +my_ldap_connect (parsed_uri_t uri, LDAP **ldap_connp, + char **basednp, unsigned int *r_serverinfo) +{ + int err = 0; + LDAP *ldap_conn = NULL; + char *user = uri->auth; + struct uri_tuple_s *password_param; + char *password; + char *basedn = NULL; + + *r_serverinfo = 0; + + password_param = uri_query_lookup (uri, "password"); + password = password_param ? password_param->value : NULL; + + if (opt.debug) + log_debug ("my_ldap_connect(%s:%d/%s????%s%s%s%s%s%s)\n", + uri->host, uri->port, + uri->path ? uri->path : "", + uri->auth ? "bindname=" : "", + uri->auth ? uri->auth : "", + uri->auth && password ? "," : "", + password ? "password=" : "", + password ? ">not shown<": "", + uri->ad_current? " auth=>current_user<":""); + + /* If the uri specifies a secure connection and we don't support + TLS, then fail; don't silently revert to an insecure + connection. */ + if (uri->use_tls) + { +#ifndef HAVE_LDAP_START_TLS_S + log_error ("Can't use LDAP to connect to the server: no TLS support."); + err = GPG_ERR_LDAP_NOT_SUPPORTED; + goto out; +#endif + } + + if (uri->ad_current) + ldap_conn = ldap_init (NULL, uri->port); + else + ldap_conn = ldap_init (uri->host, uri->port); + if (!ldap_conn) + { + err = gpg_err_code_from_syserror (); + if (uri->ad_current) + log_error ("error initializing LDAP for current user\n"); + else + log_error ("error initializing LDAP for (%s://%s:%d)\n", + uri->scheme, uri->host, uri->port); + goto out; + } + +#ifdef HAVE_LDAP_SET_OPTION + { + int ver = LDAP_VERSION3; + + err = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver); + if (err != LDAP_SUCCESS) + { + log_error ("ks-ldap: unable to go to LDAP 3: %s\n", + ldap_err2string (err)); + goto out; + } + } +#endif + + /* XXX: It would be nice to have an option to provide the server's + certificate. */ +#if 0 +#if defined(LDAP_OPT_X_TLS_CACERTFILE) && defined(HAVE_LDAP_SET_OPTION) + err = ldap_set_option (NULL, LDAP_OPT_X_TLS_CACERTFILE, ca_cert_file); + if (err) + { + log_error ("unable to set ca-cert-file to '%s': %s\n", + ca_cert_file, ldap_err2string (err)); + goto out; + } +#endif /* LDAP_OPT_X_TLS_CACERTFILE && HAVE_LDAP_SET_OPTION */ +#endif + +#ifdef HAVE_LDAP_START_TLS_S + if (uri->use_tls) + { + /* XXX: We need an option to determine whether to abort if the + certificate is bad or not. Right now we conservatively + default to checking the certificate and aborting. */ +#ifndef HAVE_W32_SYSTEM + int check_cert = LDAP_OPT_X_TLS_HARD; /* LDAP_OPT_X_TLS_NEVER */ + + err = ldap_set_option (ldap_conn, + LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert); + if (err) + { + log_error ("error setting TLS option on LDAP connection\n"); + goto out; + } +#else + /* On Windows, the certificates are checked by default. If the + option to disable checking mentioned above is ever + implemented, the way to do that on Windows is to install a + callback routine using ldap_set_option (.., + LDAP_OPT_SERVER_CERTIFICATE, ..); */ +#endif + + npth_unprotect (); + err = ldap_start_tls_s (ldap_conn, +#ifdef HAVE_W32_SYSTEM + /* ServerReturnValue, result */ + NULL, NULL, +#endif + /* ServerControls, ClientControls */ + NULL, NULL); + npth_protect (); + if (err) + { + log_error ("error connecting to LDAP server with TLS\n"); + goto out; + } + } +#endif + + if (uri->ad_current) + { + if (opt.debug) + log_debug ("LDAP bind to current user via AD\n"); +#ifdef HAVE_W32_SYSTEM + npth_unprotect (); + err = ldap_bind_s (ldap_conn, NULL, NULL, LDAP_AUTH_NEGOTIATE); + npth_protect (); +#else + err = gpg_error (GPG_ERR_NOT_SUPPORTED); +#endif + if (err != LDAP_SUCCESS) + { + log_error ("error binding to LDAP via AD: %s\n", + ldap_err2string (err)); + goto out; + } + } + else if (uri->auth) + { + if (opt.debug) + log_debug ("LDAP bind to %s, password %s\n", + user, password ? ">not shown<" : ">none<"); + + npth_unprotect (); + err = ldap_simple_bind_s (ldap_conn, user, password); + npth_protect (); + if (err != LDAP_SUCCESS) + { + log_error ("error binding to LDAP: %s\n", ldap_err2string (err)); + goto out; + } + } + else + { + /* By default we don't bind as there is usually no need to. */ + } + + if (uri->path && *uri->path) + { + /* User specified base DN. */ + basedn = xstrdup (uri->path); + + /* If the user specifies a base DN, then we know the server is a + * real LDAP server. */ + *r_serverinfo |= SERVERINFO_REALLDAP; + } + else + { /* Look for namingContexts. */ + LDAPMessage *res = NULL; + char *attr[] = { "namingContexts", NULL }; + + npth_unprotect (); + err = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE, + "(objectClass=*)", attr, 0, &res); + npth_protect (); + + if (err == LDAP_SUCCESS) + { + char **context; + + npth_unprotect (); + context = ldap_get_values (ldap_conn, res, "namingContexts"); + npth_protect (); + if (context) + { + /* We found some, so try each namingContext as the + * search base and look for pgpBaseKeySpaceDN. Because + * we found this, we know we're talking to a regular-ish + * LDAP server and not an LDAP keyserver. */ + int i; + char *attr2[] = + { "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL }; + + *r_serverinfo |= SERVERINFO_REALLDAP; + + for (i = 0; context[i] && ! basedn; i++) + { + char **vals; + LDAPMessage *si_res; + int is_gnupg = 0; + + { + char *object = xasprintf ("cn=pgpServerInfo,%s", + context[i]); + npth_unprotect (); + err = ldap_search_s (ldap_conn, object, LDAP_SCOPE_BASE, + "(objectClass=*)", attr2, 0, &si_res); + npth_protect (); + xfree (object); + } + + if (err == LDAP_SUCCESS) + { + vals = ldap_get_values (ldap_conn, si_res, + "pgpBaseKeySpaceDN"); + if (vals) + { + basedn = xtrystrdup (vals[0]); + ldap_value_free (vals); + } + + vals = ldap_get_values (ldap_conn, si_res, + "pgpSoftware"); + if (vals) + { + if (opt.debug) + log_debug ("Server: \t%s\n", vals[0]); + if (!ascii_strcasecmp (vals[0], "GnuPG")) + is_gnupg = 1; + ldap_value_free (vals); + } + + vals = ldap_get_values (ldap_conn, si_res, + "pgpVersion"); + if (vals) + { + if (opt.debug) + log_debug ("Version:\t%s\n", vals[0]); + if (is_gnupg) + { + char *fields[2]; + int nfields; + nfields = split_fields (vals[0], + fields, DIM(fields)); + if (nfields > 0 && atoi(fields[0]) > 1) + *r_serverinfo |= SERVERINFO_SCHEMAV2; + if (nfields > 1 + && !ascii_strcasecmp (fields[1], "ntds")) + *r_serverinfo |= SERVERINFO_NTDS; + } + ldap_value_free (vals); + } + } + + /* From man ldap_search_s: "res parameter of + ldap_search_ext_s() and ldap_search_s() should be + freed with ldap_msgfree() regardless of return + value of these functions. */ + ldap_msgfree (si_res); + } + + ldap_value_free (context); + } + } + else + { + /* We don't have an answer yet, which means the server might + be a PGP.com keyserver. */ + char **vals; + LDAPMessage *si_res = NULL; + + char *attr2[] = { "pgpBaseKeySpaceDN", "version", "software", NULL }; + + npth_unprotect (); + err = ldap_search_s (ldap_conn, "cn=pgpServerInfo", LDAP_SCOPE_BASE, + "(objectClass=*)", attr2, 0, &si_res); + npth_protect (); + if (err == LDAP_SUCCESS) + { + /* For the PGP LDAP keyserver, this is always + * "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be + * in the future. */ + + vals = ldap_get_values (ldap_conn, si_res, "baseKeySpaceDN"); + if (vals) + { + basedn = xtrystrdup (vals[0]); + ldap_value_free (vals); + } + + vals = ldap_get_values (ldap_conn, si_res, "software"); + if (vals) + { + if (opt.debug) + log_debug ("ks-ldap: PGP Server: \t%s\n", vals[0]); + ldap_value_free (vals); + } + + vals = ldap_get_values (ldap_conn, si_res, "version"); + if (vals) + { + if (opt.debug) + log_debug ("ks-ldap: PGP Server Version:\t%s\n", vals[0]); + + /* If the version is high enough, use the new + pgpKeyV2 attribute. This design is iffy at best, + but it matches how PGP does it. I figure the NAI + folks assumed that there would never be an LDAP + keyserver vendor with a different numbering + scheme. */ + if (atoi (vals[0]) > 1) + *r_serverinfo |= SERVERINFO_PGPKEYV2; + + ldap_value_free (vals); + } + } + + ldap_msgfree (si_res); + } + + /* From man ldap_search_s: "res parameter of ldap_search_ext_s() + and ldap_search_s() should be freed with ldap_msgfree() + regardless of return value of these functions. */ + ldap_msgfree (res); + } + + out: + if (!err && opt.debug) + { + log_debug ("ldap_conn: %p\n", ldap_conn); + log_debug ("server_type: %s\n", ((*r_serverinfo & SERVERINFO_REALLDAP) + ? "LDAP" : "PGP.com keyserver") ); + log_debug ("basedn: %s\n", basedn); + log_debug ("pgpkeyattr: %s\n", + (*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey"); + } + + if (err) + xfree (basedn); + else + { + if (basednp) + *basednp = basedn; + else + xfree (basedn); + } + + if (err) + { + if (ldap_conn) + ldap_unbind (ldap_conn); + } + else + *ldap_connp = ldap_conn; + + return err; +} + +/* Extract keys from an LDAP reply and write them out to the output + stream OUTPUT in a format GnuPG can import (either the OpenPGP + binary format or armored format). */ +static void +extract_keys (estream_t output, + LDAP *ldap_conn, const char *certid, LDAPMessage *message) +{ + char **vals; + + es_fprintf (output, "INFO %s BEGIN\n", certid); + es_fprintf (output, "pub:%s:", certid); + + /* Note: ldap_get_values returns a NULL terminated array of + strings. */ + vals = ldap_get_values (ldap_conn, message, "pgpkeytype"); + if (vals && vals[0]) + { + if (strcmp (vals[0], "RSA") == 0) + es_fprintf (output, "1"); + else if (strcmp (vals[0],"DSS/DH") == 0) + es_fprintf (output, "17"); + ldap_value_free (vals); + } + + es_fprintf (output, ":"); + + vals = ldap_get_values (ldap_conn, message, "pgpkeysize"); + if (vals && vals[0]) + { + int v = atoi (vals[0]); + if (v > 0) + es_fprintf (output, "%d", v); + ldap_value_free (vals); + } + + es_fprintf (output, ":"); + + vals = ldap_get_values (ldap_conn, message, "pgpkeycreatetime"); + if (vals && vals[0]) + { + if (strlen (vals[0]) == 15) + es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0])); + ldap_value_free (vals); + } + + es_fprintf (output, ":"); + + vals = ldap_get_values (ldap_conn, message, "pgpkeyexpiretime"); + if (vals && vals[0]) + { + if (strlen (vals[0]) == 15) + es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0])); + ldap_value_free (vals); + } + + es_fprintf (output, ":"); + + vals = ldap_get_values (ldap_conn, message, "pgprevoked"); + if (vals && vals[0]) + { + if (atoi (vals[0]) == 1) + es_fprintf (output, "r"); + ldap_value_free (vals); + } + + es_fprintf (output, "\n"); + + vals = ldap_get_values (ldap_conn, message, "pgpuserid"); + if (vals && vals[0]) + { + int i; + for (i = 0; vals[i]; i++) + es_fprintf (output, "uid:%s\n", vals[i]); + ldap_value_free (vals); + } + + es_fprintf (output, "INFO %s END\n", certid); +} + +/* Get the key described key the KEYSPEC string from the keyserver + identified by URI. On success R_FP has an open stream to read the + data. */ +gpg_error_t +ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, + estream_t *r_fp) +{ + gpg_error_t err = 0; + int ldap_err; + unsigned int serverinfo; + char *filter = NULL; + LDAP *ldap_conn = NULL; + char *basedn = NULL; + estream_t fp = NULL; + LDAPMessage *message = NULL; + + (void) ctrl; + + if (dirmngr_use_tor ()) + { + /* For now we do not support LDAP over Tor. */ + log_error (_("LDAP access not possible due to Tor mode\n")); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + /* Make sure we are talking to an OpenPGP LDAP server. */ + ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo); + if (ldap_err || !basedn) + { + if (ldap_err) + err = ldap_err_to_gpg_err (ldap_err); + else + err = gpg_error (GPG_ERR_GENERAL); + goto out; + } + + /* Now that we have information about the server we can construct a + * query best suited for the capabilities of the server. */ + err = keyspec_to_ldap_filter (keyspec, &filter, 1, serverinfo); + if (err) + goto out; + + if (opt.debug) + log_debug ("ks-ldap: using filter: %s\n", filter); + + { + /* The ordering is significant. Specifically, "pgpcertid" needs + to be the second item in the list, since everything after it + may be discarded we aren't in verbose mode. */ + char *attrs[] = + { + "dummy", + "pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled", + "pgpkeycreatetime", "modifytimestamp", "pgpkeysize", "pgpkeytype", + NULL + }; + /* 1 if we want just attribute types; 0 if we want both attribute + * types and values. */ + int attrsonly = 0; + int count; + + /* Replace "dummy". */ + attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey"; + + npth_unprotect (); + ldap_err = ldap_search_s (ldap_conn, basedn, LDAP_SCOPE_SUBTREE, + filter, attrs, attrsonly, &message); + npth_protect (); + if (ldap_err) + { + err = ldap_err_to_gpg_err (ldap_err); + + log_error ("ks-ldap: LDAP search error: %s\n", + ldap_err2string (ldap_err)); + goto out; + } + + count = ldap_count_entries (ldap_conn, message); + if (count < 1) + { + log_info ("ks-ldap: key %s not found on keyserver\n", keyspec); + + if (count == -1) + err = ldap_to_gpg_err (ldap_conn); + else + err = gpg_error (GPG_ERR_NO_DATA); + + goto out; + } + + { + /* There may be more than one unique result for a given keyID, + so we should fetch them all (test this by fetching short key + id 0xDEADBEEF). */ + + /* The set of entries that we've seen. */ + strlist_t seen = NULL; + LDAPMessage *each; + + for (npth_unprotect (), + each = ldap_first_entry (ldap_conn, message), + npth_protect (); + each; + npth_unprotect (), + each = ldap_next_entry (ldap_conn, each), + npth_protect ()) + { + char **vals; + char **certid; + + /* Use the long keyid to remove duplicates. The LDAP + server returns the same keyid more than once if there + are multiple user IDs on the key. Note that this does + NOT mean that a keyid that exists multiple times on the + keyserver will not be fetched. It means that each KEY, + no matter how many user IDs share its keyid, will be + fetched only once. If a keyid that belongs to more + than one key is fetched, the server quite properly + responds with all matching keys. -ds */ + + certid = ldap_get_values (ldap_conn, each, "pgpcertid"); + if (certid && certid[0]) + { + if (! strlist_find (seen, certid[0])) + { + /* It's not a duplicate, add it */ + + add_to_strlist (&seen, certid[0]); + + if (! fp) + fp = es_fopenmem(0, "rw"); + + extract_keys (fp, ldap_conn, certid[0], each); + + vals = ldap_get_values (ldap_conn, each, attrs[0]); + if (! vals) + { + err = ldap_to_gpg_err (ldap_conn); + log_error("ks-ldap: unable to retrieve key %s " + "from keyserver\n", certid[0]); + goto out; + } + else + { + /* We should strip the new lines. */ + es_fprintf (fp, "KEY 0x%s BEGIN\n", certid[0]); + es_fputs (vals[0], fp); + es_fprintf (fp, "\nKEY 0x%s END\n", certid[0]); + + ldap_value_free (vals); + } + } + } + + ldap_value_free (certid); + } + + free_strlist (seen); + + if (! fp) + err = gpg_error (GPG_ERR_NO_DATA); + } + } + + out: + if (message) + ldap_msgfree (message); + + if (err) + { + if (fp) + es_fclose (fp); + } + else + { + if (fp) + es_fseek (fp, 0, SEEK_SET); + + *r_fp = fp; + } + + xfree (basedn); + + if (ldap_conn) + ldap_unbind (ldap_conn); + + xfree (filter); + + return err; +} + + +/* Search the keyserver identified by URI for keys matching PATTERN. + On success R_FP has an open stream to read the data. */ +gpg_error_t +ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, + estream_t *r_fp) +{ + gpg_error_t err; + int ldap_err; + unsigned int serverinfo; + char *filter = NULL; + LDAP *ldap_conn = NULL; + char *basedn = NULL; + estream_t fp = NULL; + + (void) ctrl; + + if (dirmngr_use_tor ()) + { + /* For now we do not support LDAP over Tor. */ + log_error (_("LDAP access not possible due to Tor mode\n")); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + /* Make sure we are talking to an OpenPGP LDAP server. */ + ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo); + if (ldap_err || !basedn) + { + if (ldap_err) + err = ldap_err_to_gpg_err (ldap_err); + else + err = GPG_ERR_GENERAL; + goto out; + } + + /* Now that we have information about the server we can construct a + * query best suited for the capabilities of the server. */ + err = keyspec_to_ldap_filter (pattern, &filter, 0, serverinfo); + if (err) + { + log_error ("Bad search pattern: '%s'\n", pattern); + goto out; + } + + /* Even if we have no results, we want to return a stream. */ + fp = es_fopenmem(0, "rw"); + if (!fp) + { + err = gpg_error_from_syserror (); + goto out; + } + + { + char **vals; + LDAPMessage *res, *each; + int count = 0; + strlist_t dupelist = NULL; + + /* The maximum size of the search, including the optional stuff + and the trailing \0 */ + char *attrs[] = + { + "pgpcertid", "pgpuserid", "pgprevoked", "pgpdisabled", + "pgpkeycreatetime", "pgpkeyexpiretime", "modifytimestamp", + "pgpkeysize", "pgpkeytype", NULL + }; + + if (opt.debug) + log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter); + + npth_unprotect (); + ldap_err = ldap_search_s (ldap_conn, basedn, + LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res); + npth_protect (); + + xfree (filter); + filter = NULL; + + if (ldap_err != LDAP_SUCCESS && ldap_err != LDAP_SIZELIMIT_EXCEEDED) + { + err = ldap_err_to_gpg_err (ldap_err); + + log_error ("SEARCH %s FAILED %d\n", pattern, err); + log_error ("ks-ldap: LDAP search error: %s\n", + ldap_err2string (err)); + goto out; + } + + /* The LDAP server doesn't return a real count of unique keys, so we + can't use ldap_count_entries here. */ + for (npth_unprotect (), + each = ldap_first_entry (ldap_conn, res), + npth_protect (); + each; + npth_unprotect (), + each = ldap_next_entry (ldap_conn, each), + npth_protect ()) + { + char **certid = ldap_get_values (ldap_conn, each, "pgpcertid"); + if (certid && certid[0] && ! strlist_find (dupelist, certid[0])) + { + add_to_strlist (&dupelist, certid[0]); + count++; + } + } + + if (ldap_err == LDAP_SIZELIMIT_EXCEEDED) + { + if (count == 1) + log_error ("ks-ldap: search results exceeded server limit." + " First 1 result shown.\n"); + else + log_error ("ks-ldap: search results exceeded server limit." + " First %d results shown.\n", count); + } + + free_strlist (dupelist); + dupelist = NULL; + + if (count < 1) + es_fputs ("info:1:0\n", fp); + else + { + es_fprintf (fp, "info:1:%d\n", count); + + for (each = ldap_first_entry (ldap_conn, res); + each; + each = ldap_next_entry (ldap_conn, each)) + { + char **certid; + LDAPMessage *uids; + + certid = ldap_get_values (ldap_conn, each, "pgpcertid"); + if (! certid || ! certid[0]) + continue; + + /* Have we seen this certid before? */ + if (! strlist_find (dupelist, certid[0])) + { + add_to_strlist (&dupelist, certid[0]); + + es_fprintf (fp, "pub:%s:",certid[0]); + + vals = ldap_get_values (ldap_conn, each, "pgpkeytype"); + if (vals) + { + /* The LDAP server doesn't exactly handle this + well. */ + if (strcasecmp (vals[0], "RSA") == 0) + es_fputs ("1", fp); + else if (strcasecmp (vals[0], "DSS/DH") == 0) + es_fputs ("17", fp); + ldap_value_free (vals); + } + + es_fputc (':', fp); + + vals = ldap_get_values (ldap_conn, each, "pgpkeysize"); + if (vals) + { + /* Not sure why, but some keys are listed with a + key size of 0. Treat that like an unknown. */ + if (atoi (vals[0]) > 0) + es_fprintf (fp, "%d", atoi (vals[0])); + ldap_value_free (vals); + } + + es_fputc (':', fp); + + /* YYYYMMDDHHmmssZ */ + + vals = ldap_get_values (ldap_conn, each, "pgpkeycreatetime"); + if(vals && strlen (vals[0]) == 15) + { + es_fprintf (fp, "%u", + (unsigned int) ldap2epochtime(vals[0])); + ldap_value_free (vals); + } + + es_fputc (':', fp); + + vals = ldap_get_values (ldap_conn, each, "pgpkeyexpiretime"); + if (vals && strlen (vals[0]) == 15) + { + es_fprintf (fp, "%u", + (unsigned int) ldap2epochtime (vals[0])); + ldap_value_free (vals); + } + + es_fputc (':', fp); + + vals = ldap_get_values (ldap_conn, each, "pgprevoked"); + if (vals) + { + if (atoi (vals[0]) == 1) + es_fprintf (fp, "r"); + ldap_value_free (vals); + } + + vals = ldap_get_values (ldap_conn, each, "pgpdisabled"); + if (vals) + { + if (atoi (vals[0]) ==1) + es_fprintf (fp, "d"); + ldap_value_free (vals); + } + +#if 0 + /* This is not yet specified in the keyserver + protocol, but may be someday. */ + es_fputc (':', fp); + + vals = ldap_get_values (ldap_conn, each, "modifytimestamp"); + if(vals && strlen (vals[0]) == 15) + { + es_fprintf (fp, "%u", + (unsigned int) ldap2epochtime (vals[0])); + ldap_value_free (vals); + } +#endif + + es_fprintf (fp, "\n"); + + /* Now print all the uids that have this certid */ + for (uids = ldap_first_entry (ldap_conn, res); + uids; + uids = ldap_next_entry (ldap_conn, uids)) + { + vals = ldap_get_values (ldap_conn, uids, "pgpcertid"); + if (! vals) + continue; + + if (strcasecmp (certid[0], vals[0]) == 0) + { + char **uidvals; + + es_fprintf (fp, "uid:"); + + uidvals = ldap_get_values (ldap_conn, + uids, "pgpuserid"); + if (uidvals) + { + /* Need to escape any colons */ + char *quoted = percent_escape (uidvals[0], NULL); + es_fputs (quoted, fp); + xfree (quoted); + ldap_value_free (uidvals); + } + + es_fprintf (fp, "\n"); + } + + ldap_value_free(vals); + } + } + + ldap_value_free (certid); + } + } + + ldap_msgfree (res); + free_strlist (dupelist); + } + + if (opt.debug) + log_debug ("SEARCH %s END\n", pattern); + + out: + if (err) + { + if (fp) + es_fclose (fp); + } + else + { + /* Return the read stream. */ + if (fp) + es_fseek (fp, 0, SEEK_SET); + + *r_fp = fp; + } + + xfree (basedn); + + if (ldap_conn) + ldap_unbind (ldap_conn); + + xfree (filter); + + return err; +} + + + +/* A modlist describes a set of changes to an LDAP entry. (An entry + consists of 1 or more attributes. Attributes are <name, value> + pairs. Note: an attribute may be multi-valued in which case + multiple values are associated with a single name.) + + A modlist is a NULL terminated array of struct LDAPMod's. + + Thus, if we have: + + LDAPMod **modlist; + + Then: + + modlist[i] + + Is the ith modification. + + Each LDAPMod describes a change to a single attribute. Further, + there is one modification for each attribute that we want to + change. The attribute's new value is stored in LDAPMod.mod_values. + If the attribute is multi-valued, we still only use a single + LDAPMod structure: mod_values is a NULL-terminated array of + strings. To delete an attribute from an entry, we set mod_values + to NULL. + + Thus, if: + + modlist[i]->mod_values == NULL + + then we remove the attribute. + + (Using LDAP_MOD_DELETE doesn't work here as we don't know if the + attribute in question exists or not.) + + Note: this function does NOT copy or free ATTR. It does copy + VALUE. */ +static void +modlist_add (LDAPMod ***modlistp, char *attr, const char *value) +{ + LDAPMod **modlist = *modlistp; + + LDAPMod **m; + int nummods = 0; + + /* Search modlist for the attribute we're playing with. If modlist + is NULL, then the list is empty. Recall: modlist is a NULL + terminated array. */ + for (m = modlist; m && *m; m++, nummods ++) + { + /* The attribute is already on the list. */ + char **ptr; + int numvalues = 0; + + if (strcasecmp ((*m)->mod_type, attr) != 0) + continue; + + /* We have this attribute already, so when the REPLACE happens, + the server attributes will be replaced anyway. */ + if (! value) + return; + + /* Attributes can be multi-valued. See if the value is already + present. mod_values is a NULL terminated array of pointers. + Note: mod_values can be NULL. */ + for (ptr = (*m)->mod_values; ptr && *ptr; ptr++) + { + if (strcmp (*ptr, value) == 0) + /* Duplicate value, we're done. */ + return; + numvalues ++; + } + + /* Append the value. */ + ptr = xrealloc ((*m)->mod_values, sizeof (char *) * (numvalues + 2)); + + (*m)->mod_values = ptr; + ptr[numvalues] = xstrdup (value); + + ptr[numvalues + 1] = NULL; + + return; + } + + /* We didn't find the attr, so make one and add it to the end */ + + /* Like attribute values, the list of attributes is NULL terminated + array of pointers. */ + modlist = xrealloc (modlist, sizeof (LDAPMod *) * (nummods + 2)); + + *modlistp = modlist; + modlist[nummods] = xmalloc (sizeof (LDAPMod)); + + modlist[nummods]->mod_op = LDAP_MOD_REPLACE; + modlist[nummods]->mod_type = attr; + if (value) + { + modlist[nummods]->mod_values = xmalloc (sizeof(char *) * 2); + + modlist[nummods]->mod_values[0] = xstrdup (value); + modlist[nummods]->mod_values[1] = NULL; + } + else + modlist[nummods]->mod_values = NULL; + + modlist[nummods + 1] = NULL; + + return; +} + +/* Look up the value of an attribute in the specified modlist. If the + attribute is not on the mod list, returns NULL. The result is a + NULL-terminated array of strings. Don't change it. */ +static char ** +modlist_lookup (LDAPMod **modlist, const char *attr) +{ + LDAPMod **m; + for (m = modlist; m && *m; m++) + { + if (strcasecmp ((*m)->mod_type, attr) != 0) + continue; + + return (*m)->mod_values; + } + + return NULL; +} + +/* Dump a modlist to a file. This is useful for debugging. */ +static estream_t modlist_dump (LDAPMod **modlist, estream_t output) + GPGRT_ATTR_USED; + +static estream_t +modlist_dump (LDAPMod **modlist, estream_t output) +{ + LDAPMod **m; + + int opened = 0; + + if (! output) + { + output = es_fopenmem (0, "rw"); + if (!output) + return NULL; + opened = 1; + } + + for (m = modlist; m && *m; m++) + { + es_fprintf (output, " %s:", (*m)->mod_type); + + if (! (*m)->mod_values) + es_fprintf(output, " delete.\n"); + else + { + char **ptr; + int i; + + int multi = 0; + if ((*m)->mod_values[0] && (*m)->mod_values[1]) + /* Have at least 2. */ + multi = 1; + + if (multi) + es_fprintf (output, "\n"); + + for ((ptr = (*m)->mod_values), (i = 1); ptr && *ptr; ptr++, i ++) + { + /* Assuming terminals are about 80 characters wide, + display at most about 10 lines of debugging + output. If we do trim the buffer, append '...' to + the end. */ + const int max_len = 10 * 70; + size_t value_len = strlen (*ptr); + int elide = value_len > max_len; + + if (multi) + es_fprintf (output, " %d. ", i); + es_fprintf (output, "`%.*s", max_len, *ptr); + if (elide) + es_fprintf (output, "...' (%zd bytes elided)", + value_len - max_len); + else + es_fprintf (output, "'"); + es_fprintf (output, "\n"); + } + } + } + + if (opened) + es_fseek (output, 0, SEEK_SET); + + return output; +} + +/* Free all of the memory allocated by the mod list. This assumes + that the attribute names don't have to be freed, but the attributes + values do. (Which is what modlist_add does.) */ +static void +modlist_free (LDAPMod **modlist) +{ + LDAPMod **ml; + + if (! modlist) + return; + + /* Unwind and free the whole modlist structure */ + + /* The modlist is a NULL terminated array of pointers. */ + for (ml = modlist; *ml; ml++) + { + LDAPMod *mod = *ml; + char **ptr; + + /* The list of values is a NULL termianted array of pointers. + If the list is NULL, there are no values. */ + + if (mod->mod_values) + { + for (ptr = mod->mod_values; *ptr; ptr++) + xfree (*ptr); + + xfree (mod->mod_values); + } + + xfree (mod); + } + xfree (modlist); +} + +/* Append two onto the end of one. Two is not freed, but its pointers + are now part of one. Make sure you don't free them both! + + As long as you don't add anything to ONE, TWO is still valid. + After that all bets are off. */ +static void +modlists_join (LDAPMod ***one, LDAPMod **two) +{ + int i, one_count = 0, two_count = 0; + LDAPMod **grow; + + if (!*two) + /* two is empty. Nothing to do. */ + return; + + if (!*one) + /* one is empty. Just set it equal to *two. */ + { + *one = two; + return; + } + + for (grow = *one; *grow; grow++) + one_count ++; + + for (grow = two; *grow; grow++) + two_count ++; + + grow = xrealloc (*one, sizeof(LDAPMod *) * (one_count + two_count + 1)); + + for (i = 0; i < two_count; i++) + grow[one_count + i] = two[i]; + + grow[one_count + i] = NULL; + + *one = grow; +} + +/* Given a string, unescape C escapes. In particular, \xXX. This + modifies the string in place. */ +static void +uncescape (char *str) +{ + size_t r = 0; + size_t w = 0; + + char *first = strchr (str, '\\'); + if (! first) + /* No backslashes => no escaping. We're done. */ + return; + + /* Start at the first '\\'. */ + r = w = (uintptr_t) first - (uintptr_t) str; + + while (str[r]) + { + /* XXX: What to do about bad escapes? + XXX: hextobyte already checks the string thus the hexdigitp + could be removed. */ + if (str[r] == '\\' && str[r + 1] == 'x' + && str[r+2] && str[r+3] + && hexdigitp (str + r + 2) + && hexdigitp (str + r + 3)) + { + int x = hextobyte (&str[r + 2]); + assert (0 <= x && x <= 0xff); + + str[w] = x; + + /* We consumed 4 characters and wrote 1. */ + r += 4; + w ++; + } + else + str[w ++] = str[r ++]; + } + + str[w] = '\0'; +} + +/* Given one line from an info block (`gpg --list-{keys,sigs} + --with-colons KEYID'), pull it apart and fill in the modlist with + the relevant (for the LDAP schema) attributes. EXTRACT_STATE + should initally be set to 0 by the caller. SCHEMAV2 is set if the + server supports the version 2 schema. */ +static void +extract_attributes (LDAPMod ***modlist, int *extract_state, + char *line, int schemav2) +{ + int field_count; + char **fields; + char *keyid; + int is_pub, is_sub, is_uid, is_sig; + + /* Remove trailing whitespace */ + trim_trailing_spaces (line); + + fields = strsplit (line, ':', '\0', &field_count); + if (field_count == 1) + /* We only have a single field. There is definitely nothing to + do. */ + goto out; + + if (field_count < 7) + goto out; + + is_pub = !ascii_strcasecmp ("pub", fields[0]); + is_sub = !ascii_strcasecmp ("sub", fields[0]); + is_uid = !ascii_strcasecmp ("uid", fields[0]); + is_sig = !ascii_strcasecmp ("sig", fields[0]); + if (!ascii_strcasecmp ("fpr", fields[0])) + { + /* Special treatment for a fingerprint. */ + if (!(*extract_state & 1)) + goto out; /* Stray fingerprint line - ignore. */ + *extract_state &= ~1; + if (field_count >= 10 && schemav2) + { + if ((*extract_state & 2)) + modlist_add (modlist, "gpgFingerprint", fields[9]); + else + modlist_add (modlist, "gpgSubFingerprint", fields[9]); + } + goto out; + } + + *extract_state &= ~(1|2); + if (is_pub) + *extract_state |= (1|2); + else if (is_sub) + *extract_state |= 1; + + if (!is_pub && !is_sub && !is_uid && !is_sig) + goto out; /* Not a relevant line. */ + + keyid = fields[4]; + + if (is_uid && strlen (keyid) == 0) + ; /* The uid record type can have an empty keyid. */ + else if (strlen (keyid) == 16 + && strspn (keyid, "0123456789aAbBcCdDeEfF") == 16) + ; /* Otherwise, we expect exactly 16 hex characters. */ + else + { + log_error ("malformed record!\n"); + goto out; + } + + if (is_pub) + { + int disabled = 0; + int revoked = 0; + char *flags; + for (flags = fields[1]; *flags; flags ++) + switch (*flags) + { + case 'r': + case 'R': + revoked = 1; + break; + + case 'd': + case 'D': + disabled = 1; + break; + } + + /* Note: we always create the pgpDisabled and pgpRevoked + attributes, regardless of whether the key is disabled/revoked + or not. This is because a very common search is like + "(&(pgpUserID=*isabella*)(pgpDisabled=0))" */ + + if (is_pub) + { + modlist_add (modlist,"pgpDisabled", disabled ? "1" : "0"); + modlist_add (modlist,"pgpRevoked", revoked ? "1" : "0"); + } + } + + if (is_pub || is_sub) + { + char padded[6]; + int val; + + val = atoi (fields[2]); + if (val < 99999 && val > 0) + { + /* We zero pad this on the left to make PGP happy. */ + snprintf (padded, sizeof padded, "%05u", val); + modlist_add (modlist, "pgpKeySize", padded); + } + } + + if (is_pub) + { + char *algo = fields[3]; + int val = atoi (algo); + switch (val) + { + case 1: + algo = "RSA"; + break; + + case 17: + algo = "DSS/DH"; + break; + + default: + algo = NULL; + break; + } + + if (algo) + modlist_add (modlist, "pgpKeyType", algo); + } + + if (is_pub || is_sub || is_sig) + { + if (is_pub) + { + modlist_add (modlist, "pgpCertID", keyid); /* Long keyid(!) */ + modlist_add (modlist, "pgpKeyID", &keyid[8]); /* Short keyid */ + } + + if (is_sub) + modlist_add (modlist, "pgpSubKeyID", keyid); /* Long keyid(!) */ + } + + if (is_pub) + { + char *create_time = fields[5]; + + if (strlen (create_time) == 0) + create_time = NULL; + else + { + char *create_time_orig = create_time; + struct tm tm; + time_t t; + char *end; + + memset (&tm, 0, sizeof (tm)); + + /* parse_timestamp handles both seconds fromt he epoch and + ISO 8601 format. We also need to handle YYYY-MM-DD + format (as generated by gpg1 --with-colons --list-key). + Check that first and then if it fails, then try + parse_timestamp. */ + + if (!isodate_human_to_tm (create_time, &tm)) + create_time = tm2ldaptime (&tm); + else if ((t = parse_timestamp (create_time, &end)) != (time_t) -1 + && *end == '\0') + { + + if (!gnupg_gmtime (&t, &tm)) + create_time = NULL; + else + create_time = tm2ldaptime (&tm); + } + else + create_time = NULL; + + if (! create_time) + /* Failed to parse string. */ + log_error ("Failed to parse creation time ('%s')", + create_time_orig); + } + + if (create_time) + { + modlist_add (modlist, "pgpKeyCreateTime", create_time); + xfree (create_time); + } + } + + if (is_pub) + { + char *expire_time = fields[6]; + + if (strlen (expire_time) == 0) + expire_time = NULL; + else + { + char *expire_time_orig = expire_time; + struct tm tm; + time_t t; + char *end; + + memset (&tm, 0, sizeof (tm)); + + /* parse_timestamp handles both seconds fromt he epoch and + ISO 8601 format. We also need to handle YYYY-MM-DD + format (as generated by gpg1 --with-colons --list-key). + Check that first and then if it fails, then try + parse_timestamp. */ + + if (!isodate_human_to_tm (expire_time, &tm)) + expire_time = tm2ldaptime (&tm); + else if ((t = parse_timestamp (expire_time, &end)) != (time_t) -1 + && *end == '\0') + { + if (!gnupg_gmtime (&t, &tm)) + expire_time = NULL; + else + expire_time = tm2ldaptime (&tm); + } + else + expire_time = NULL; + + if (! expire_time) + /* Failed to parse string. */ + log_error ("Failed to parse creation time ('%s')", + expire_time_orig); + } + + if (expire_time) + { + modlist_add (modlist, "pgpKeyExpireTime", expire_time); + xfree (expire_time); + } + } + + if (is_uid && field_count >= 10) + { + char *uid = fields[9]; + char *mbox; + + uncescape (uid); + modlist_add (modlist, "pgpUserID", uid); + if (schemav2 && (mbox = mailbox_from_userid (uid))) + { + modlist_add (modlist, "gpgMailbox", mbox); + xfree (mbox); + } + } + + out: + xfree (fields); +} + +/* Send the key in {KEY,KEYLEN} with the metadata {INFO,INFOLEN} to + the keyserver identified by URI. See server.c:cmd_ks_put for the + format of the data and metadata. */ +gpg_error_t +ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, + void *data, size_t datalen, + void *info, size_t infolen) +{ + gpg_error_t err = 0; + int ldap_err; + unsigned int serverinfo; + LDAP *ldap_conn = NULL; + char *basedn = NULL; + LDAPMod **modlist = NULL; + LDAPMod **addlist = NULL; + char *data_armored = NULL; + int extract_state; + + /* The last byte of the info block. */ + const char *infoend = (const char *) info + infolen - 1; + + /* Enable this code to dump the modlist to /tmp/modlist.txt. */ +#if 0 +# warning Disable debug code before checking in. + const int dump_modlist = 1; +#else + const int dump_modlist = 0; +#endif + estream_t dump = NULL; + + /* Elide a warning. */ + (void) ctrl; + + if (dirmngr_use_tor ()) + { + /* For now we do not support LDAP over Tor. */ + log_error (_("LDAP access not possible due to Tor mode\n")); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + ldap_err = my_ldap_connect (uri, &ldap_conn, &basedn, &serverinfo); + if (ldap_err || !basedn) + { + if (ldap_err) + err = ldap_err_to_gpg_err (ldap_err); + else + err = GPG_ERR_GENERAL; + goto out; + } + + if (!(serverinfo & SERVERINFO_REALLDAP)) + { + /* We appear to have a PGP.com Keyserver, which can unpack the + * key on its own (not just a dump LDAP server). This will + * rarely be the case these days. */ + LDAPMod mod; + LDAPMod *attrs[2]; + char *key[2]; + char *dn; + + key[0] = data; + key[1] = NULL; + memset (&mod, 0, sizeof (mod)); + mod.mod_op = LDAP_MOD_ADD; + mod.mod_type = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey"; + mod.mod_values = key; + attrs[0] = &mod; + attrs[1] = NULL; + + dn = xtryasprintf ("pgpCertid=virtual,%s", basedn); + if (!dn) + { + err = gpg_error_from_syserror (); + goto out; + } + ldap_err = ldap_add_s (ldap_conn, dn, attrs); + xfree (dn); + + if (ldap_err != LDAP_SUCCESS) + { + err = ldap_err_to_gpg_err (err); + goto out; + } + + goto out; + } + + modlist = xtrymalloc (sizeof (LDAPMod *)); + if (!modlist) + { + err = gpg_error_from_syserror (); + goto out; + } + *modlist = NULL; + + if (dump_modlist) + { + dump = es_fopen("/tmp/modlist.txt", "w"); + if (! dump) + log_error ("Failed to open /tmp/modlist.txt: %s\n", + strerror (errno)); + + if (dump) + { + es_fprintf(dump, "data (%zd bytes)\n", datalen); + es_fprintf(dump, "info (%zd bytes): '\n", infolen); + es_fwrite(info, infolen, 1, dump); + es_fprintf(dump, "'\n"); + } + } + + /* Start by nulling out all attributes. We try and do a modify + operation first, so this ensures that we don't leave old + attributes lying around. */ + modlist_add (&modlist, "pgpDisabled", NULL); + modlist_add (&modlist, "pgpKeyID", NULL); + modlist_add (&modlist, "pgpKeyType", NULL); + modlist_add (&modlist, "pgpUserID", NULL); + modlist_add (&modlist, "pgpKeyCreateTime", NULL); + modlist_add (&modlist, "pgpRevoked", NULL); + modlist_add (&modlist, "pgpSubKeyID", NULL); + modlist_add (&modlist, "pgpKeySize", NULL); + modlist_add (&modlist, "pgpKeyExpireTime", NULL); + modlist_add (&modlist, "pgpCertID", NULL); + if ((serverinfo & SERVERINFO_SCHEMAV2)) + { + modlist_add (&modlist, "gpgFingerprint", NULL); + modlist_add (&modlist, "gpgSubFingerprint", NULL); + modlist_add (&modlist, "gpgMailbox", NULL); + } + + /* Assemble the INFO stuff into LDAP attributes */ + extract_state = 0; + while (infolen > 0) + { + char *temp = NULL; + + char *newline = memchr (info, '\n', infolen); + if (! newline) + /* The last line is not \n terminated! Make a copy so we can + add a NUL terminator. */ + { + temp = xmalloc (infolen + 1); + memcpy (temp, info, infolen); + info = temp; + newline = (char *) info + infolen; + } + + *newline = '\0'; + + extract_attributes (&addlist, &extract_state, info, + (serverinfo & SERVERINFO_SCHEMAV2)); + + infolen = infolen - ((uintptr_t) newline - (uintptr_t) info + 1); + info = newline + 1; + + /* Sanity check. */ + if (! temp) + log_assert ((char *) info + infolen - 1 == infoend); + else + { + log_assert (infolen == -1); + xfree (temp); + } + } + + modlist_add (&addlist, "objectClass", "pgpKeyInfo"); + + err = armor_data (&data_armored, data, datalen); + if (err) + goto out; + + modlist_add (&addlist, + (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey", + data_armored); + + /* Now append addlist onto modlist. */ + modlists_join (&modlist, addlist); + + if (dump) + { + estream_t input = modlist_dump (modlist, NULL); + if (input) + { + copy_stream (input, dump); + es_fclose (input); + } + } + + /* Going on the assumption that modify operations are more frequent + than adds, we try a modify first. If it's not there, we just + turn around and send an add command for the same key. Otherwise, + the modify brings the server copy into compliance with our copy. + Note that unlike the LDAP keyserver (and really, any other + keyserver) this does NOT merge signatures, but replaces the whole + key. This should make some people very happy. */ + { + char **attrval; + char *dn; + + if ((serverinfo & SERVERINFO_NTDS)) + { + /* The modern way using a CN RDN with the fingerprint. This + * has the advantage that we won't have duplicate 64 bit + * keyids in the store. In particular NTDS requires the + * DN to be unique. */ + attrval = modlist_lookup (addlist, "gpgFingerprint"); + /* We should have exactly one value. */ + if (!attrval || !(attrval[0] && !attrval[1])) + { + log_error ("ks-ldap: bad gpgFingerprint provided\n"); + err = GPG_ERR_GENERAL; + goto out; + } + dn = xtryasprintf ("CN=%s,%s", attrval[0], basedn); + } + else /* The old style way. */ + { + attrval = modlist_lookup (addlist, "pgpCertID"); + /* We should have exactly one value. */ + if (!attrval || !(attrval[0] && !attrval[1])) + { + log_error ("ks-ldap: bad pgpCertID provided\n"); + err = GPG_ERR_GENERAL; + goto out; + } + dn = xtryasprintf ("pgpCertID=%s,%s", attrval[0], basedn); + } + if (!dn) + { + err = gpg_error_from_syserror (); + goto out; + } + if (opt.debug) + log_debug ("ks-ldap: using DN: %s\n", dn); + + npth_unprotect (); + err = ldap_modify_s (ldap_conn, dn, modlist); + if (err == LDAP_NO_SUCH_OBJECT) + err = ldap_add_s (ldap_conn, dn, addlist); + npth_protect (); + + xfree (dn); + + if (err != LDAP_SUCCESS) + { + log_error ("ks-ldap: error adding key to keyserver: %s\n", + ldap_err2string (err)); + err = ldap_err_to_gpg_err (err); + } + } + + out: + if (dump) + es_fclose (dump); + + if (ldap_conn) + ldap_unbind (ldap_conn); + + xfree (basedn); + + modlist_free (modlist); + xfree (addlist); + + xfree (data_armored); + + return err; +} diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h new file mode 100644 index 0000000..d28c6ab --- /dev/null +++ b/dirmngr/ks-engine.h @@ -0,0 +1,75 @@ +/* ks-engine.h - Keyserver engines definitions + * Copyright (C) 2011 Free Software Foundation, Inc. + * Copyright (C) 2015 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef DIRMNGR_KS_ENGINE_H +#define DIRMNGR_KS_ENGINE_H 1 + +#include "http.h" + +/*-- ks-action.c --*/ +gpg_error_t ks_print_help (ctrl_t ctrl, const char *text); +gpg_error_t ks_printf_help (ctrl_t ctrl, const char *format, + ...) GPGRT_ATTR_PRINTF(2,3); + +/*-- ks-engine-hkp.c --*/ +gpg_error_t ks_hkp_resolve (ctrl_t ctrl, parsed_uri_t uri); +gpg_error_t ks_hkp_mark_host (ctrl_t ctrl, const char *name, int alive); +gpg_error_t ks_hkp_print_hosttable (ctrl_t ctrl); +gpg_error_t ks_hkp_help (ctrl_t ctrl, parsed_uri_t uri); +gpg_error_t ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, + estream_t *r_fp, unsigned int *r_http_status); +gpg_error_t ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, + const char *keyspec, estream_t *r_fp); +gpg_error_t ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, + const void *data, size_t datalen); + +/*-- ks-engine-http.c --*/ + +/* Flags for the ks_http_fetch. */ +#define KS_HTTP_FETCH_NOCACHE 1 /* Request no caching. */ +#define KS_HTTP_FETCH_TRUST_CFG 2 /* Requests HTTP_FLAG_TRUST_CFG. */ +#define KS_HTTP_FETCH_NO_CRL 4 /* Requests HTTP_FLAG_NO_CRL. */ +#define KS_HTTP_FETCH_ALLOW_DOWNGRADE 8 /* Allow redirect https -> http. */ + +gpg_error_t ks_http_help (ctrl_t ctrl, parsed_uri_t uri); +gpg_error_t ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, + estream_t *r_fp); + + +/*-- ks-engine-finger.c --*/ +gpg_error_t ks_finger_help (ctrl_t ctrl, parsed_uri_t uri); +gpg_error_t ks_finger_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp); + +/*-- ks-engine-kdns.c --*/ +gpg_error_t ks_kdns_help (ctrl_t ctrl, parsed_uri_t uri); +gpg_error_t ks_kdns_fetch (ctrl_t ctrl, parsed_uri_t uri, estream_t *r_fp); + +/*-- ks-engine-ldap.c --*/ +gpg_error_t ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri); +gpg_error_t ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, + estream_t *r_fp); +gpg_error_t ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, + const char *keyspec, estream_t *r_fp); +gpg_error_t ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri, + void *data, size_t datalen, + void *info, size_t infolen); + + +#endif /*DIRMNGR_KS_ENGINE_H*/ diff --git a/dirmngr/ldap-parse-uri.c b/dirmngr/ldap-parse-uri.c new file mode 100644 index 0000000..4d9272c --- /dev/null +++ b/dirmngr/ldap-parse-uri.c @@ -0,0 +1,258 @@ +/* ldap-parse-uri.c - Parse an LDAP URI. + * Copyright (C) 2015 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <gpg-error.h> + +#ifdef HAVE_W32_SYSTEM +# include "ldap-url.h" +#else +# include <ldap.h> +#endif + +#include "../common/util.h" +#include "http.h" + +/* Returns 1 if the string is an LDAP URL (begins with ldap:, ldaps: + or ldapi:). */ +int +ldap_uri_p (const char *url) +{ + char *colon = strchr (url, ':'); + if (! colon) + return 0; + else + { + int offset = (uintptr_t) colon - (uintptr_t) url; + + if (/* All lower case. */ + (offset == 4 && memcmp (url, "ldap", 4) == 0) + || (offset == 5 + && (memcmp (url, "ldaps", 5) == 0 + && memcmp (url, "ldapi", 5) == 0)) + /* Mixed case. */ + || ((url[0] == 'l' || url[0] == 'L') + && (url[1] == 'd' || url[1] == 'D') + && (url[2] == 'a' || url[2] == 'A') + && (url[3] == 'p' || url[3] == 'P') + && (url[4] == ':' + || ((url[4] == 's' || url[4] == 'S' + || url[4] == 'i' || url[4] == 'I') + && url[5] == ':')))) + return 1; + return 0; + } +} + +/* Parse a URI and put the result into *purip. On success the + caller must use http_release_parsed_uri() to releases the resources. + + uri->path is the base DN (or NULL for the default). + uri->auth is the bindname (or NULL for none). + The uri->query variable "password" is the password. + + Note: any specified scope, any attributes, any filter and any + unknown extensions are simply ignored. */ +gpg_error_t +ldap_parse_uri (parsed_uri_t *purip, const char *uri) +{ + gpg_err_code_t err = 0; + parsed_uri_t puri = NULL; + + int result; + LDAPURLDesc *lud = NULL; + + char *scheme = NULL; + char *host = NULL; + char *dn = NULL; + char *bindname = NULL; + char *password = NULL; + + char **s; + + char *buffer; + int len; + + result = ldap_url_parse (uri, &lud); + if (result != 0) + { + log_error ("Unable to parse LDAP uri '%s'\n", uri); + err = GPG_ERR_GENERAL; + goto out; + } + + scheme = lud->lud_scheme; + host = lud->lud_host; + dn = lud->lud_dn; + + for (s = lud->lud_exts; s && *s; s ++) + { + if (strncmp (*s, "bindname=", 9) == 0) + { + if (bindname) + log_error ("bindname given multiple times in URL '%s', ignoring.\n", + uri); + else + bindname = *s + 9; + } + else if (strncmp (*s, "password=", 9) == 0) + { + if (password) + log_error ("password given multiple times in URL '%s', ignoring.\n", + uri); + else + password = *s + 9; + } + else + log_error ("Unhandled extension (%s) in URL '%s', ignoring.", + *s, uri); + } + + len = 0; + +#define add(s) do { if (s) len += strlen (s) + 1; } while (0) + + add (scheme); + add (host); + add (dn); + add (bindname); + add (password); + + puri = xtrycalloc (1, sizeof *puri + len); + if (! puri) + { + err = gpg_err_code_from_syserror (); + goto out; + } + + buffer = puri->buffer; + +#define copy(to, s) \ + do \ + { \ + if (s) \ + { \ + to = buffer; \ + buffer = stpcpy (buffer, s) + 1; \ + } \ + } \ + while (0) + + copy (puri->scheme, scheme); + /* Make sure the scheme is lower case. */ + ascii_strlwr (puri->scheme); + + copy (puri->host, host); + copy (puri->path, dn); + copy (puri->auth, bindname); + + if (password) + { + puri->query = calloc (sizeof (*puri->query), 1); + if (!puri->query) + { + err = gpg_err_code_from_syserror (); + goto out; + } + puri->query->name = "password"; + copy (puri->query->value, password); + puri->query->valuelen = strlen (password) + 1; + } + + puri->use_tls = !strcmp (puri->scheme, "ldaps"); + puri->port = lud->lud_port; + + /* On Windows detect whether this is ldap:// or ldaps:// to indicate + * that authentication via AD and the current user is requested. */ + puri->ad_current = 0; +#ifdef HAVE_W32_SYSTEM + if ((!puri->host || !*puri->host) + && (!puri->path || !*puri->path) + && (!puri->auth || !*puri->auth) + && !password + ) + puri->ad_current = 1; +#endif + + out: + if (lud) + ldap_free_urldesc (lud); + + if (err) + { + if (puri) + http_release_parsed_uri (puri); + } + else + *purip = puri; + + return gpg_err_make (default_errsource, err); +} + +/* The following characters need to be escaped to be part of an LDAP + filter: *, (, ), \, NUL and /. Note: we don't handle NUL, since a + NUL can't be part of a C string. + + This function always allocates a new string on success. It is the + caller's responsibility to free it. +*/ +char * +ldap_escape_filter (const char *filter) +{ + int l = strcspn (filter, "*()\\/"); + if (l == strlen (filter)) + /* Nothing to escape. */ + return xstrdup (filter); + + { + /* In the worst case we need to escape every letter. */ + char *escaped = xmalloc (1 + 3 * strlen (filter)); + + /* Indices into filter and escaped. */ + int filter_i = 0; + int escaped_i = 0; + + for (filter_i = 0; filter_i < strlen (filter); filter_i ++) + { + switch (filter[filter_i]) + { + case '*': + case '(': + case ')': + case '\\': + case '/': + snprintf (&escaped[escaped_i], 4, "%%%02x", + ((const unsigned char *)filter)[filter_i]); + escaped_i += 3; + break; + + default: + escaped[escaped_i ++] = filter[filter_i]; + break; + } + } + /* NUL terminate it. */ + escaped[escaped_i] = 0; + + /* We could shrink escaped to be just escaped_i bytes, but the + result will probably be freed very quickly anyways. */ + return escaped; + } +} diff --git a/dirmngr/ldap-parse-uri.h b/dirmngr/ldap-parse-uri.h new file mode 100644 index 0000000..e9a3f95 --- /dev/null +++ b/dirmngr/ldap-parse-uri.h @@ -0,0 +1,33 @@ +/* ldap-parse-uri.h - Parse an LDAP URI. + * Copyright (C) 2015 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 <https://www.gnu.org/licenses/>. + */ + +#ifndef DIRMNGR_LDAP_PARSE_URI_H +#define DIRMNGR_LDAP_PARSE_URI_H + +#include "../common/util.h" +#include "http.h" + +extern int ldap_uri_p (const char *url); + +extern gpg_error_t ldap_parse_uri (parsed_uri_t *ret_uri, const char *uri); + +extern char *ldap_escape_filter (const char *filter); + + +#endif diff --git a/dirmngr/ldap-url.c b/dirmngr/ldap-url.c new file mode 100644 index 0000000..8308514 --- /dev/null +++ b/dirmngr/ldap-url.c @@ -0,0 +1,935 @@ +/* The following code comes from the OpenLDAP project. The references + to the COPYRIGHT file below refer to the corresponding file in the + OpenLDAP distribution, which is reproduced here in full: + +Copyright 1998-2004 The OpenLDAP Foundation +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted only as authorized by the OpenLDAP +Public License. + +A copy of this license is available in the file LICENSE in the +top-level directory of the distribution or, alternatively, at +<http://www.OpenLDAP.org/license.html>. + +OpenLDAP is a registered trademark of the OpenLDAP Foundation. + +Individual files and/or contributed packages may be copyright by +other parties and subject to additional restrictions. + +This work is derived from the University of Michigan LDAP v3.3 +distribution. Information concerning this software is available +at <http://www.umich.edu/~dirsvcs/ldap/>. + +This work also contains materials derived from public sources. + +Additional information about OpenLDAP can be obtained at +<http://www.openldap.org/>. + +--- + +Portions Copyright 1998-2004 Kurt D. Zeilenga. +Portions Copyright 1998-2004 Net Boolean Incorporated. +Portions Copyright 2001-2004 IBM Corporation. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted only as authorized by the OpenLDAP +Public License. + +--- + +Portions Copyright 1999-2003 Howard Y.H. Chu. +Portions Copyright 1999-2003 Symas Corporation. +Portions Copyright 1998-2003 Hallvard B. Furuseth. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that this notice is preserved. +The names of the copyright holders may not be used to endorse or +promote products derived from this software without their specific +prior written permission. This software is provided `'as is'' +without express or implied warranty. + +--- + +Portions Copyright (c) 1992-1996 Regents of the University of Michigan. +All rights reserved. + +Redistribution and use in source and binary forms are permitted +provided that this notice is preserved and that due credit is given +to the University of Michigan at Ann Arbor. The name of the +University may not be used to endorse or promote products derived +from this software without specific prior written permission. This +software is provided `'as is'' without express or implied warranty. */ + + +#include <config.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <winsock2.h> +#include <winldap.h> +#include "ldap-url.h" +#define LDAP_P(protos) protos +#define LDAP_URL_URLCOLON "URL:" +#define LDAP_URL_URLCOLON_LEN (sizeof(LDAP_URL_URLCOLON)-1) +#define LDAP_URL_PREFIX "ldap://" +#define LDAP_URL_PREFIX_LEN (sizeof(LDAP_URL_PREFIX)-1) +#define LDAPS_URL_PREFIX "ldaps://" +#define LDAPS_URL_PREFIX_LEN (sizeof(LDAPS_URL_PREFIX)-1) +#define LDAPI_URL_PREFIX "ldapi://" +#define LDAPI_URL_PREFIX_LEN (sizeof(LDAPI_URL_PREFIX)-1) +#define LDAP_VFREE(v) { int _i; for (_i = 0; (v)[_i]; _i++) free((v)[_i]); } +#define LDAP_FREE free +#define LDAP_STRDUP strdup +#define LDAP_CALLOC calloc +#define LDAP_MALLOC malloc +#define LDAP_REALLOC realloc +#define ldap_utf8_strchr strchr +#define ldap_utf8_strtok(n,d) strtok (n,d) +#define Debug(a,b,c,d,e) +void ldap_pvt_hex_unescape( char *s ); + + +#ifndef LDAP_SCOPE_DEFAULT +# define LDAP_SCOPE_DEFAULT -1 +#endif + + + +/* $OpenLDAP: pkg/ldap/libraries/libldap/charray.c,v 1.9.2.2 2003/03/03 17:10:04 kurt Exp $ */ +/* + * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ +/* charray.c - routines for dealing with char * arrays */ + +int +ldap_charray_add( + char ***a, + char *s +) +{ + int n; + + if ( *a == NULL ) { + *a = (char **) LDAP_MALLOC( 2 * sizeof(char *) ); + n = 0; + + if( *a == NULL ) { + return -1; + } + + } else { + char **new; + + for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) { + ; /* NULL */ + } + + new = (char **) LDAP_REALLOC( (char *) *a, + (n + 2) * sizeof(char *) ); + + if( new == NULL ) { + /* caller is required to call ldap_charray_free(*a) */ + return -1; + } + + *a = new; + } + + (*a)[n] = LDAP_STRDUP(s); + + if( (*a)[n] == NULL ) { + return 1; + } + + (*a)[++n] = NULL; + + return 0; +} + +int +ldap_charray_merge( + char ***a, + char **s +) +{ + int i, n, nn; + char **aa; + + for ( n = 0; *a != NULL && (*a)[n] != NULL; n++ ) { + ; /* NULL */ + } + for ( nn = 0; s[nn] != NULL; nn++ ) { + ; /* NULL */ + } + + aa = (char **) LDAP_REALLOC( (char *) *a, (n + nn + 1) * sizeof(char *) ); + + if( aa == NULL ) { + return -1; + } + + *a = aa; + + for ( i = 0; i < nn; i++ ) { + (*a)[n + i] = LDAP_STRDUP(s[i]); + + if( (*a)[n + i] == NULL ) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( (*a)[n + i] ); + (*a)[n + i] = NULL; + } + return -1; + } + } + + (*a)[n + nn] = NULL; + return 0; +} + +void +ldap_charray_free( char **a ) +{ + char **p; + + if ( a == NULL ) { + return; + } + + for ( p = a; *p != NULL; p++ ) { + if ( *p != NULL ) { + LDAP_FREE( *p ); + } + } + + LDAP_FREE( (char *) a ); +} + +int +ldap_charray_inlist( + char **a, + char *s +) +{ + int i; + + if( a == NULL ) return 0; + + for ( i=0; a[i] != NULL; i++ ) { + if ( strcasecmp( s, a[i] ) == 0 ) { + return 1; + } + } + + return 0; +} + +char ** +ldap_charray_dup( char **a ) +{ + int i; + char **new; + + for ( i = 0; a[i] != NULL; i++ ) + ; /* NULL */ + + new = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) ); + + if( new == NULL ) { + return NULL; + } + + for ( i = 0; a[i] != NULL; i++ ) { + new[i] = LDAP_STRDUP( a[i] ); + + if( new[i] == NULL ) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( new[i] ); + } + LDAP_FREE( new ); + return NULL; + } + } + new[i] = NULL; + + return( new ); +} + +char ** +ldap_str2charray( const char *str_in, const char *brkstr ) +{ + char **res; + char *str, *s; + int i; + + /* protect the input string from strtok */ + str = LDAP_STRDUP( str_in ); + if( str == NULL ) { + return NULL; + } + + i = 1; + for ( s = str; *s; s++ ) { + if ( ldap_utf8_strchr( brkstr, *s ) != NULL ) { + i++; + } + } + + res = (char **) LDAP_MALLOC( (i + 1) * sizeof(char *) ); + + if( res == NULL ) { + LDAP_FREE( str ); + return NULL; + } + + i = 0; + + for ( s = ldap_utf8_strtok( str, brkstr); + s != NULL; + s = ldap_utf8_strtok( NULL, brkstr) ) + { + res[i] = LDAP_STRDUP( s ); + + if(res[i] == NULL) { + for( --i ; i >= 0 ; i-- ) { + LDAP_FREE( res[i] ); + } + LDAP_FREE( res ); + LDAP_FREE( str ); + return NULL; + } + + i++; + } + + res[i] = NULL; + + LDAP_FREE( str ); + return( res ); +} + +char * ldap_charray2str( char **a, const char *sep ) +{ + char *s, **v, *p; + int len; + int slen; + + if( sep == NULL ) sep = " "; + + slen = strlen( sep ); + len = 0; + + for ( v = a; *v != NULL; v++ ) { + len += strlen( *v ) + slen; + } + + if ( len == 0 ) { + return NULL; + } + + /* trim extra sep len */ + len -= slen; + + s = LDAP_MALLOC ( len + 1 ); + + if ( s == NULL ) { + return NULL; + } + + p = s; + for ( v = a; *v != NULL; v++ ) { + if ( v != a ) { + strncpy( p, sep, slen ); + p += slen; + } + + len = strlen( *v ); + strncpy( p, *v, len ); + p += len; + } + + *p = '\0'; + return s; +} + + + +/* $OpenLDAP: pkg/ldap/libraries/libldap/url.c,v 1.64.2.5 2003/03/03 17:10:05 kurt Exp $ */ +/* + * Copyright 1998-2003 The OpenLDAP Foundation, All Rights Reserved. + * COPYING RESTRICTIONS APPLY, see COPYRIGHT file + */ +/* Portions + * Copyright (c) 1996 Regents of the University of Michigan. + * All rights reserved. + * + * LIBLDAP url.c -- LDAP URL (RFC 2255) related routines + * + * LDAP URLs look like this: + * ldap[is]://host:port[/[dn[?[attributes][?[scope][?[filter][?exts]]]]]] + * + * where: + * attributes is a comma separated list + * scope is one of these three strings: base one sub (default=base) + * filter is an string-represented filter as in RFC 2254 + * + * e.g., ldap://host:port/dc=com?o,cn?base?(o=openldap)?extension + * + * We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl> + */ + +/* local functions */ +static const char* skip_url_prefix LDAP_P(( + const char *url, + int *enclosedp, + const char **scheme )); + +int +ldap_is_ldap_url( LDAP_CONST char *url ) +{ + int enclosed; + const char * scheme; + + if( url == NULL ) { + return 0; + } + + if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) { + return 0; + } + + return 1; +} + + +static const char* +skip_url_prefix( + const char *url, + int *enclosedp, + const char **scheme ) +{ + /* + * return non-zero if this looks like a LDAP URL; zero if not + * if non-zero returned, *urlp will be moved past "ldap://" part of URL + */ + const char *p; + + if ( url == NULL ) { + return( NULL ); + } + + p = url; + + /* skip leading '<' (if any) */ + if ( *p == '<' ) { + *enclosedp = 1; + ++p; + } else { + *enclosedp = 0; + } + + /* skip leading "URL:" (if any) */ + if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) { + p += LDAP_URL_URLCOLON_LEN; + } + + /* check for "ldap://" prefix */ + if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldap://" prefix and return success */ + p += LDAP_URL_PREFIX_LEN; + *scheme = "ldap"; + return( p ); + } + + /* check for "ldaps://" prefix */ + if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldaps://" prefix and return success */ + p += LDAPS_URL_PREFIX_LEN; + *scheme = "ldaps"; + return( p ); + } + + /* check for "ldapi://" prefix */ + if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) { + /* skip over "ldapi://" prefix and return success */ + p += LDAPI_URL_PREFIX_LEN; + *scheme = "ldapi"; + return( p ); + } + +#ifdef LDAP_CONNECTIONLESS + /* check for "cldap://" prefix */ + if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) { + /* skip over "cldap://" prefix and return success */ + p += LDAPC_URL_PREFIX_LEN; + *scheme = "cldap"; + return( p ); + } +#endif + + return( NULL ); +} + + +static int str2scope( const char *p ) +{ + if ( strcasecmp( p, "one" ) == 0 ) { + return LDAP_SCOPE_ONELEVEL; + + } else if ( strcasecmp( p, "onetree" ) == 0 ) { + return LDAP_SCOPE_ONELEVEL; + + } else if ( strcasecmp( p, "base" ) == 0 ) { + return LDAP_SCOPE_BASE; + + } else if ( strcasecmp( p, "sub" ) == 0 ) { + return LDAP_SCOPE_SUBTREE; + + } else if ( strcasecmp( p, "subtree" ) == 0 ) { + return LDAP_SCOPE_SUBTREE; + } + + return( -1 ); +} + + +int +ldap_url_parse_ext( LDAP_CONST char *url_in, LDAPURLDesc **ludpp ) +{ +/* + * Pick apart the pieces of an LDAP URL. + */ + + LDAPURLDesc *ludp; + char *p, *q, *r; + int i, enclosed; + const char *scheme = NULL; + const char *url_tmp; + char *url; + + if( url_in == NULL || ludpp == NULL ) { + return LDAP_URL_ERR_PARAM; + } + +#ifndef LDAP_INT_IN_KERNEL + /* Global options may not be created yet + * We can't test if the global options are initialized + * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate + * the options and cause infinite recursion + */ +#ifdef NEW_LOGGING + LDAP_LOG ( OPERATION, ENTRY, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 ); +#else + Debug( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 ); +#endif +#endif + + *ludpp = NULL; /* pessimistic */ + + url_tmp = skip_url_prefix( url_in, &enclosed, &scheme ); + + if ( url_tmp == NULL ) { + return LDAP_URL_ERR_BADSCHEME; + } + + assert( scheme ); + + /* make working copy of the remainder of the URL */ + url = LDAP_STRDUP( url_tmp ); + if ( url == NULL ) { + return LDAP_URL_ERR_MEM; + } + + if ( enclosed ) { + p = &url[strlen(url)-1]; + + if( *p != '>' ) { + LDAP_FREE( url ); + return LDAP_URL_ERR_BADENCLOSURE; + } + + *p = '\0'; + } + + /* allocate return struct */ + ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc )); + + if ( ludp == NULL ) { + LDAP_FREE( url ); + return LDAP_URL_ERR_MEM; + } + + ludp->lud_next = NULL; + ludp->lud_host = NULL; + ludp->lud_port = 0; + ludp->lud_dn = NULL; + ludp->lud_attrs = NULL; + ludp->lud_filter = NULL; + ludp->lud_scope = LDAP_SCOPE_DEFAULT; + ludp->lud_filter = NULL; + ludp->lud_exts = NULL; + + ludp->lud_scheme = LDAP_STRDUP( scheme ); + + if ( ludp->lud_scheme == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + /* scan forward for '/' that marks end of hostport and begin. of dn */ + p = strchr( url, '/' ); + + if( p != NULL ) { + /* terminate hostport; point to start of dn */ + *p++ = '\0'; + } + + /* IPv6 syntax with [ip address]:port */ + if ( *url == '[' ) { + r = strchr( url, ']' ); + if ( r == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + *r++ = '\0'; + q = strchr( r, ':' ); + } else { + q = strchr( url, ':' ); + } + + if ( q != NULL ) { + *q++ = '\0'; + ldap_pvt_hex_unescape( q ); + + if( *q == '\0' ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + + ludp->lud_port = atoi( q ); + } + + ldap_pvt_hex_unescape( url ); + + /* If [ip address]:port syntax, url is [ip and we skip the [ */ + ludp->lud_host = LDAP_STRDUP( url + ( *url == '[' ) ); + + if( ludp->lud_host == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + /* + * Kludge. ldap://111.222.333.444:389??cn=abc,o=company + * + * On early Novell releases, search references/referrals were returned + * in this format, i.e., the dn was kind of in the scope position, + * but the required slash is missing. The whole thing is illegal syntax, + * but we need to account for it. Fortunately it can't be confused with + * anything real. + */ + if( (p == NULL) && (q != NULL) && ((q = strchr( q, '?')) != NULL)) { + q++; + /* ? immediately followed by question */ + if( *q == '?') { + q++; + if( *q != '\0' ) { + /* parse dn part */ + ldap_pvt_hex_unescape( q ); + ludp->lud_dn = LDAP_STRDUP( q ); + } else { + ludp->lud_dn = LDAP_STRDUP( "" ); + } + + if( ludp->lud_dn == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + } + } + + if( p == NULL ) { + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of dn */ + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate dn part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse dn part */ + ldap_pvt_hex_unescape( p ); + ludp->lud_dn = LDAP_STRDUP( p ); + } else { + ludp->lud_dn = LDAP_STRDUP( "" ); + } + + if( ludp->lud_dn == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + + if( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of attributes */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate attributes part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse attributes */ + ldap_pvt_hex_unescape( p ); + ludp->lud_attrs = ldap_str2charray( p, "," ); + + if( ludp->lud_attrs == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADATTRS; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of scope */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate the scope part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse the scope */ + ldap_pvt_hex_unescape( p ); + ludp->lud_scope = str2scope( p ); + + if( ludp->lud_scope == -1 ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADSCOPE; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of filter */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* terminate the filter part */ + *q++ = '\0'; + } + + if( *p != '\0' ) { + /* parse the filter */ + ldap_pvt_hex_unescape( p ); + + if( ! *p ) { + /* missing filter */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADFILTER; + } + + LDAP_FREE( ludp->lud_filter ); + ludp->lud_filter = LDAP_STRDUP( p ); + + if( ludp->lud_filter == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_MEM; + } + } + + if ( q == NULL ) { + /* no more */ + LDAP_FREE( url ); + *ludpp = ludp; + return LDAP_URL_SUCCESS; + } + + /* scan forward for '?' that may marks end of extensions */ + p = q; + q = strchr( p, '?' ); + + if( q != NULL ) { + /* extra '?' */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADURL; + } + + /* parse the extensions */ + ludp->lud_exts = ldap_str2charray( p, "," ); + + if( ludp->lud_exts == NULL ) { + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADEXTS; + } + + for( i=0; ludp->lud_exts[i] != NULL; i++ ) { + ldap_pvt_hex_unescape( ludp->lud_exts[i] ); + + if( *ludp->lud_exts[i] == '!' ) { + /* count the number of critical extensions */ + ludp->lud_crit_exts++; + } + } + + if( i == 0 ) { + /* must have 1 or more */ + LDAP_FREE( url ); + ldap_free_urldesc( ludp ); + return LDAP_URL_ERR_BADEXTS; + } + + /* no more */ + *ludpp = ludp; + LDAP_FREE( url ); + return LDAP_URL_SUCCESS; +} + +int +ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp ) +{ + int rc = ldap_url_parse_ext( url_in, ludpp ); + + if( rc != LDAP_URL_SUCCESS ) { + return rc; + } + + if ((*ludpp)->lud_scope == LDAP_SCOPE_DEFAULT) { + (*ludpp)->lud_scope = LDAP_SCOPE_BASE; + } + + if ((*ludpp)->lud_host != NULL && *(*ludpp)->lud_host == '\0') { + LDAP_FREE( (*ludpp)->lud_host ); + (*ludpp)->lud_host = NULL; + } + + if ((*ludpp)->lud_port == 0) { + if( strcmp((*ludpp)->lud_scheme, "ldap") == 0 ) { + (*ludpp)->lud_port = LDAP_PORT; +#ifdef LDAP_CONNECTIONLESS + } else if( strcmp((*ludpp)->lud_scheme, "cldap") == 0 ) { + (*ludpp)->lud_port = LDAP_PORT; +#endif + } else if( strcmp((*ludpp)->lud_scheme, "ldaps") == 0 ) { + (*ludpp)->lud_port = LDAPS_PORT; + } + } + + return rc; +} + + +void +ldap_free_urldesc( LDAPURLDesc *ludp ) +{ + if ( ludp == NULL ) { + return; + } + + if ( ludp->lud_scheme != NULL ) { + LDAP_FREE( ludp->lud_scheme ); + } + + if ( ludp->lud_host != NULL ) { + LDAP_FREE( ludp->lud_host ); + } + + if ( ludp->lud_dn != NULL ) { + LDAP_FREE( ludp->lud_dn ); + } + + if ( ludp->lud_filter != NULL ) { + LDAP_FREE( ludp->lud_filter); + } + + if ( ludp->lud_attrs != NULL ) { + LDAP_VFREE( ludp->lud_attrs ); + } + + if ( ludp->lud_exts != NULL ) { + LDAP_VFREE( ludp->lud_exts ); + } + + LDAP_FREE( ludp ); +} + + +static int +ldap_int_unhex( int c ) +{ + return( c >= '0' && c <= '9' ? c - '0' + : c >= 'A' && c <= 'F' ? c - 'A' + 10 + : c - 'a' + 10 ); +} + +void +ldap_pvt_hex_unescape( char *s ) +{ + /* + * Remove URL hex escapes from s... done in place. The basic concept for + * this routine is borrowed from the WWW library HTUnEscape() routine. + */ + char *p; + + for ( p = s; *s != '\0'; ++s ) { + if ( *s == '%' ) { + if ( *++s == '\0' ) { + break; + } + *p = ldap_int_unhex( *s ) << 4; + if ( *++s == '\0' ) { + break; + } + *p++ += ldap_int_unhex( *s ); + } else { + *p++ = *s; + } + } + + *p = '\0'; +} diff --git a/dirmngr/ldap-url.h b/dirmngr/ldap-url.h new file mode 100644 index 0000000..f3104d8 --- /dev/null +++ b/dirmngr/ldap-url.h @@ -0,0 +1,50 @@ +/* Copyright 2007 g10 Code GmbH + + This file is free software; as a special exception the author gives + unlimited permission to copy and/or distribute it, with or without + modifications, as long as this notice is preserved. + + This file is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY, to the extent permitted by law; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. */ + +#ifndef LDAP_URL_H +#define LDAP_URL_H 1 + +#define LDAP_CONST const + +typedef struct ldap_url_desc +{ + struct ldap_url_desc *lud_next; + char *lud_scheme; + char *lud_host; + int lud_port; + char *lud_dn; + char **lud_attrs; + int lud_scope; + char *lud_filter; + char **lud_exts; + int lud_crit_exts; +} LDAPURLDesc; + +#define LDAP_URL_SUCCESS 0x00 +#define LDAP_URL_ERR_MEM 0x01 +#define LDAP_URL_ERR_PARAM 0x02 + +#define LDAP_URL_ERR_BADSCHEME 0x03 +#define LDAP_URL_ERR_BADENCLOSURE 0x04 +#define LDAP_URL_ERR_BADURL 0x05 +#define LDAP_URL_ERR_BADHOST 0x06 +#define LDAP_URL_ERR_BADATTRS 0x07 +#define LDAP_URL_ERR_BADSCOPE 0x08 +#define LDAP_URL_ERR_BADFILTER 0x09 +#define LDAP_URL_ERR_BADEXTS 0x0a + +#define LDAPS_PORT 636 + +int ldap_is_ldap_url (LDAP_CONST char *url); +int ldap_url_parse (LDAP_CONST char *url_in, LDAPURLDesc **ludpp); +void ldap_free_urldesc (LDAPURLDesc *ludp); + +#endif /* !LDAP_URL_H */ diff --git a/dirmngr/ldap-wrapper-ce.c b/dirmngr/ldap-wrapper-ce.c new file mode 100644 index 0000000..884bb32 --- /dev/null +++ b/dirmngr/ldap-wrapper-ce.c @@ -0,0 +1,575 @@ +/* ldap-wrapper-ce.c - LDAP access via W32 threads + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* + Alternative wrapper for use with WindowsCE. Under WindowsCE the + number of processes is strongly limited (32 processes including the + kernel processes) and thus we don't use the process approach but + implement a wrapper based on native threads. + + See ldap-wrapper.c for the standard wrapper interface. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <npth.h> +#include <assert.h> + +#include "dirmngr.h" +#include "misc.h" +#include "ldap-wrapper.h" + +#ifdef USE_LDAPWRAPPER +# error This module is not expected to be build. +#endif +#error This module might not anymore work. + + + +/* Read a fixed amount of data from READER into BUFFER. */ +static gpg_error_t +read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) +{ + gpg_error_t err; + size_t nread; + + while (count) + { + err = ksba_reader_read (reader, buffer, count, &nread); + if (err) + return err; + buffer += nread; + count -= nread; + } + return 0; +} + + + + +/* Start the reaper thread for this wrapper. */ +void +ldap_wrapper_launch_thread (void) +{ + /* Not required. */ +} + + + + + +/* Wait until all ldap wrappers have terminated. We assume that the + kill has already been sent to all of them. */ +void +ldap_wrapper_wait_connections () +{ + /* Not required. */ +} + + +/* Cleanup all resources held by the connection associated with + CTRL. This is used after a cancel to kill running wrappers. */ +void +ldap_wrapper_connection_cleanup (ctrl_t ctrl) +{ + (void)ctrl; + + /* Not required. */ +} + + + +/* The cookie we use to implement the outstream of the wrapper thread. */ +struct outstream_cookie_s +{ + int refcount; /* Reference counter - possible values are 1 and 2. */ + + /* We don't need a mutex for the conditions, as npth provides a + simpler condition interface that relies on the global lock. This + can be used if we never yield between testing the condition and + waiting on it. */ + npth_cond_t wait_data; /* Condition that data is available. */ + npth_cond_t wait_space; /* Condition that space is available. */ + + int eof_seen; /* EOF indicator. */ + char buffer[4000]; /* Data ring buffer. */ + size_t buffer_len; /* The amount of data in the BUFFER. */ + size_t buffer_pos; /* The next read position of the BUFFER. */ + size_t buffer_read_pos; /* The next read position of the BUFFER. */ +}; + +#define BUFFER_EMPTY(c) ((c)->buffer_len == 0) +#define BUFFER_FULL(c) ((c)->buffer_len == DIM((c)->buffer)) +#define BUFFER_DATA_AVAILABLE(c) ((c)->buffer_len) +#define BUFFER_SPACE_AVAILABLE(c) (DIM((c)->buffer) - (c)->buffer_len) +#define BUFFER_INC_POS(c,n) (c)->buffer_pos = ((c)->buffer_pos + (n)) % DIM((c)->buffer) +#define BUFFER_CUR_POS(c) (&(c)->buffer[(c)->buffer_pos]) +#define BUFFER_INC_READ_POS(c,n) (c)->buffer_read_pos = ((c)->buffer_read_pos + (n)) % DIM((c)->buffer) +#define BUFFER_CUR_READ_POS(c) (&(c)->buffer[(c)->buffer_read_pos]) + +static int +buffer_get_data (struct outstream_cookie_s *cookie, char *dst, int cnt) +{ + int amount; + int left; + int chunk; + + amount = cnt; + if (BUFFER_DATA_AVAILABLE (cookie) < amount) + amount = BUFFER_DATA_AVAILABLE (cookie); + left = amount; + + /* How large is the part up to the end of the buffer array? */ + chunk = DIM(cookie->buffer) - cookie->buffer_pos; + if (chunk > left) + chunk = left; + + memcpy (dst, BUFFER_CUR_READ_POS (cookie), chunk); + BUFFER_INC_READ_POS (cookie, chunk); + left -= chunk; + dst += chunk; + + if (left) + { + memcpy (dst, BUFFER_CUR_READ_POS (cookie), left); + BUFFER_INC_READ_POS (cookie, left); + } + + return amount; +} + + +static int +buffer_put_data (struct outstream_cookie_s *cookie, const char *src, int cnt) +{ + int amount; + int remain; + int left; + int chunk; + + remain = DIM(cookie->buffer) - cookie->buffer_len; + + amount = cnt; + if (remain < amount) + amount = remain; + left = amount; + + /* How large is the part up to the end of the buffer array? */ + chunk = DIM(cookie->buffer) - cookie->buffer_pos; + if (chunk > left) + chunk = left; + + memcpy (BUFFER_CUR_POS (cookie), src, chunk); + BUFFER_INC_POS (cookie, chunk); + left -= chunk; + src += chunk; + + if (left) + { + memcpy (BUFFER_CUR_POS (cookie), src, left); + BUFFER_INC_POS (cookie, left); + } + + cookie->buffer_len -= amount; + return amount; +} + + +/* The writer function for the outstream. This is used to transfer + the output of the ldap wrapper thread to the ksba reader object. */ +static gpgrt_ssize_t +outstream_cookie_writer (void *cookie_arg, const void *buffer, size_t size) +{ + struct outstream_cookie_s *cookie = cookie_arg; + const char *src; + ssize_t nwritten = 0; + int res; + ssize_t amount = 0; + + src = buffer; + do + { + int was_empty = 0; + + /* Wait for free space. */ + while (BUFFER_FULL(cookie)) + { + /* Buffer is full: Wait for space. */ + res = npth_cond_wait (&cookie->wait_space, NULL); + if (res) + { + gpg_err_set_errno (res); + return -1; + } + } + + if (BUFFER_EMPTY(cookie)) + was_empty = 1; + + /* Copy data. */ + nwritten = buffer_put_data (cookie, buffer, size); + size -= nwritten; + src += nwritten; + amount += nwritten; + + if (was_empty) + npth_cond_signal (&cookie->wait_data); + } + while (size); /* Until done. */ + + return amount; +} + + +static void +outstream_release_cookie (struct outstream_cookie_s *cookie) +{ + cookie->refcount--; + if (!cookie->refcount) + { + npth_cond_destroy (&cookie->wait_data); + npth_cond_destroy (&cookie->wait_space); + xfree (cookie); + } +} + + +/* Closer function for the outstream. This deallocates the cookie if + it won't be used anymore. */ +static int +outstream_cookie_closer (void *cookie_arg) +{ + struct outstream_cookie_s *cookie = cookie_arg; + + if (!cookie) + return 0; /* Nothing to do. */ + + cookie->eof_seen = 1; /* (only useful if refcount > 1) */ + + assert (cookie->refcount > 0); + outstream_release_cookie (cookie); + return 0; +} + + +/* The KSBA reader callback which takes the output of the ldap thread + form the outstream_cookie_writer and make it available to the ksba + reader. */ +static int +outstream_reader_cb (void *cb_value, char *buffer, size_t count, + size_t *r_nread) +{ + struct outstream_cookie_s *cookie = cb_value; + size_t nread = 0; + int was_full = 0; + + if (!buffer && !count && !r_nread) + return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Rewind is not supported. */ + + *r_nread = 0; + + while (BUFFER_EMPTY(cookie)) + { + if (cookie->eof_seen) + return gpg_error (GPG_ERR_EOF); + + /* Wait for data to become available. */ + npth_cond_wait (&cookie->wait_data, NULL); + } + + if (BUFFER_FULL(cookie)) + was_full = 1; + + nread = buffer_get_data (cookie, buffer, count); + + if (was_full) + { + npth_cond_signal (&cookie->wait_space); + } + + *r_nread = nread; + return 0; /* Success. */ +} + + +/* This function is called by ksba_reader_release. */ +static void +outstream_reader_released (void *cb_value, ksba_reader_t r) +{ + struct outstream_cookie_s *cookie = cb_value; + + (void)r; + + assert (cookie->refcount > 0); + outstream_release_cookie (cookie); +} + + + +/* This function is to be used to release a context associated with the + given reader object. This does not release the reader object, though. */ +void +ldap_wrapper_release_context (ksba_reader_t reader) +{ + (void)reader; + /* Nothing to do. */ +} + + + +/* Free a NULL terminated array of malloced strings and the array + itself. */ +static void +free_arg_list (char **arg_list) +{ + int i; + + if (arg_list) + { + for (i=0; arg_list[i]; i++) + xfree (arg_list[i]); + xfree (arg_list); + } +} + + +/* Copy ARGV into a new array and prepend one element as name of the + program (which is more or less a stub). We need to allocate all + the strings to get ownership of them. */ +static gpg_error_t +create_arg_list (const char *argv[], char ***r_arg_list) +{ + gpg_error_t err; + char **arg_list; + int i, j; + + for (i = 0; argv[i]; i++) + ; + arg_list = xtrycalloc (i + 2, sizeof *arg_list); + if (!arg_list) + goto outofcore; + + i = 0; + arg_list[i] = xtrystrdup ("<ldap-wrapper-thread>"); + if (!arg_list[i]) + goto outofcore; + i++; + for (j=0; argv[j]; j++) + { + arg_list[i] = xtrystrdup (argv[j]); + if (!arg_list[i]) + goto outofcore; + i++; + } + arg_list[i] = NULL; + *r_arg_list = arg_list; + return 0; + + outofcore: + err = gpg_error_from_syserror (); + log_error (_("error allocating memory: %s\n"), strerror (errno)); + free_arg_list (arg_list); + *r_arg_list = NULL; + return err; + +} + + +/* Parameters passed to the wrapper thread. */ +struct ldap_wrapper_thread_parms +{ + char **arg_list; + estream_t outstream; +}; + +/* The thread which runs the LDAP wrapper. */ +static void * +ldap_wrapper_thread (void *opaque) +{ + struct ldap_wrapper_thread_parms *parms = opaque; + + /*err =*/ ldap_wrapper_main (parms->arg_list, parms->outstream); + + /* FIXME: Do we need to return ERR? */ + + free_arg_list (parms->arg_list); + es_fclose (parms->outstream); + xfree (parms); + return NULL; +} + + + +/* Start a new LDAP thread and returns a new libksba reader + object at READER. ARGV is a NULL terminated list of arguments for + the wrapper. The function returns 0 on success or an error code. */ +gpg_error_t +ldap_wrapper (ctrl_t ctrl, ksba_reader_t *r_reader, const char *argv[]) +{ + gpg_error_t err; + struct ldap_wrapper_thread_parms *parms; + npth_attr_t tattr; + es_cookie_io_functions_t outstream_func = { NULL }; + struct outstream_cookie_s *outstream_cookie; + ksba_reader_t reader; + int res; + npth_t thread; + + (void)ctrl; + + *r_reader = NULL; + + parms = xtrycalloc (1, sizeof *parms); + if (!parms) + return gpg_error_from_syserror (); + + err = create_arg_list (argv, &parms->arg_list); + if (err) + { + xfree (parms); + return err; + } + + outstream_cookie = xtrycalloc (1, sizeof *outstream_cookie); + if (!outstream_cookie) + { + err = gpg_error_from_syserror (); + free_arg_list (parms->arg_list); + xfree (parms); + return err; + } + outstream_cookie->refcount++; + + res = npth_cond_init (&outstream_cookie->wait_data, NULL); + if (res) + { + free_arg_list (parms->arg_list); + xfree (parms); + return gpg_error_from_errno (res); + } + res = npth_cond_init (&outstream_cookie->wait_space, NULL); + if (res) + { + npth_cond_destroy (&outstream_cookie->wait_data); + free_arg_list (parms->arg_list); + xfree (parms); + return gpg_error_from_errno (res); + } + + err = ksba_reader_new (&reader); + if (!err) + err = ksba_reader_set_release_notify (reader, + outstream_reader_released, + outstream_cookie); + if (!err) + err = ksba_reader_set_cb (reader, + outstream_reader_cb, outstream_cookie); + if (err) + { + log_error (_("error initializing reader object: %s\n"), + gpg_strerror (err)); + ksba_reader_release (reader); + outstream_release_cookie (outstream_cookie); + free_arg_list (parms->arg_list); + xfree (parms); + return err; + } + + + outstream_func.func_write = outstream_cookie_writer; + outstream_func.func_close = outstream_cookie_closer; + parms->outstream = es_fopencookie (outstream_cookie, "wb", outstream_func); + if (!parms->outstream) + { + err = gpg_error_from_syserror (); + ksba_reader_release (reader); + outstream_release_cookie (outstream_cookie); + free_arg_list (parms->arg_list); + xfree (parms); + return err; + } + outstream_cookie->refcount++; + + res = npth_attr_init(&tattr); + if (res) + { + err = gpg_error_from_errno (res); + ksba_reader_release (reader); + free_arg_list (parms->arg_list); + es_fclose (parms->outstream); + xfree (parms); + return err; + } + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + + res = npth_create (&thread, &tattr, ldap_wrapper_thread, parms); + npth_attr_destroy (&tattr); + if (res) + { + err = gpg_error_from_errno (res); + log_error ("error spawning ldap wrapper thread: %s\n", + strerror (res) ); + } + else + parms = NULL; /* Now owned by the thread. */ + + if (parms) + { + free_arg_list (parms->arg_list); + es_fclose (parms->outstream); + xfree (parms); + } + if (err) + { + ksba_reader_release (reader); + return err; + } + + /* Need to wait for the first byte so we are able to detect an empty + output and not let the consumer see an EOF without further error + indications. The CRL loading logic assumes that after return + from this function, a failed search (e.g. host not found ) is + indicated right away. */ + { + unsigned char c; + + err = read_buffer (reader, &c, 1); + if (err) + { + ksba_reader_release (reader); + reader = NULL; + if (gpg_err_code (err) == GPG_ERR_EOF) + return gpg_error (GPG_ERR_NO_DATA); + else + return err; + } + ksba_reader_unread (reader, &c, 1); + } + + *r_reader = reader; + + return 0; +} diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c new file mode 100644 index 0000000..d01c480 --- /dev/null +++ b/dirmngr/ldap-wrapper.c @@ -0,0 +1,931 @@ +/* ldap-wrapper.c - LDAP access via a wrapper process + * Copyright (C) 2004, 2005, 2007, 2008, 2018 g10 Code GmbH + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +/* + * We can't use LDAP directly for these reasons: + * + * 1. On some systems the LDAP library uses (indirectly) pthreads and + * that is not compatible with GNU Pth. Since 2.1 we use nPth + * instead of GNU Pth which does not have this problem anymore + * because it will use pthreads if the platform supports it. Thus + * this was a historical reasons. + * + * 2. It is huge library in particular if TLS comes into play. So + * problems with unfreed memory might turn up and we don't want + * this in a long running daemon. + * + * 3. There is no easy way for timeouts. In particular the timeout + * value does not work for DNS lookups (well, this is usual) and it + * seems not to work while loading a large attribute like a + * CRL. Having a separate process allows us to either tell the + * process to commit suicide or have our own housekepping function + * kill it after some time. The latter also allows proper + * cancellation of a query at any point of time. + * + * 4. Given that we are going out to the network and usually get back + * a long response, the fork/exec overhead is acceptable. + * + * Note that under WindowsCE the number of processes is strongly + * limited (32 processes including the kernel processes) and thus we + * don't use the process approach but implement a different wrapper in + * ldap-wrapper-ce.c. + */ + + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <npth.h> + +#include "dirmngr.h" +#include "../common/exechelp.h" +#include "misc.h" +#include "ldap-wrapper.h" + + +#ifdef HAVE_W32_SYSTEM +#define setenv(a,b,c) SetEnvironmentVariable ((a),(b)) +#else +#define pth_close(fd) close(fd) +#endif + +#ifndef USE_LDAPWRAPPER +# error This module is not expected to be build. +#endif + +/* In case sysconf does not return a value we need to have a limit. */ +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + +#define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */ + +#define TIMERTICK_INTERVAL 2 + +/* To keep track of the LDAP wrapper state we use this structure. */ +struct wrapper_context_s +{ + struct wrapper_context_s *next; + + pid_t pid; /* The pid of the wrapper process. */ + int printable_pid; /* Helper to print diagnostics after the process has + * been cleaned up. */ + estream_t fp; /* Connected with stdout of the ldap wrapper. */ + gpg_error_t fp_err; /* Set to the gpg_error of the last read error + * if any. */ + estream_t log_fp; /* Connected with stderr of the ldap wrapper. */ + ctrl_t ctrl; /* Connection data. */ + int ready; /* Internally used to mark to be removed contexts. */ + ksba_reader_t reader;/* The ksba reader object or NULL. */ + char *line; /* Used to print the log lines (malloced). */ + size_t linesize; /* Allocated size of LINE. */ + size_t linelen; /* Use size of LINE. */ + time_t stamp; /* The last time we noticed ativity. */ + int reaper_idx; /* Private to ldap_wrapper_thread. */ +}; + + + +/* We keep a global list of spawned wrapper process. A separate + * thread makes use of this list to log error messages and to watch + * out for finished processes. Access to list is protected by a + * mutex. The condition variable is used to wakeup the reaper + * thread. */ +static struct wrapper_context_s *reaper_list; +static npth_mutex_t reaper_list_mutex = NPTH_MUTEX_INITIALIZER; +static npth_cond_t reaper_run_cond = NPTH_COND_INITIALIZER; + +/* We need to know whether we are shutting down the process. */ +static int shutting_down; + + + +/* Close the estream fp and set it to NULL. */ +#define SAFE_CLOSE(fp) \ + do { estream_t _fp = fp; es_fclose (_fp); fp = NULL; } while (0) + + + + + +static void +lock_reaper_list (void) +{ + if (npth_mutex_lock (&reaper_list_mutex)) + log_fatal ("%s: failed to acquire mutex: %s\n", __func__, + gpg_strerror (gpg_error_from_syserror ())); +} + + +static void +unlock_reaper_list (void) +{ + if (npth_mutex_unlock (&reaper_list_mutex)) + log_fatal ("%s: failed to release mutex: %s\n", __func__, + gpg_strerror (gpg_error_from_syserror ())); +} + + + +/* Read a fixed amount of data from READER into BUFFER. */ +static gpg_error_t +read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) +{ + gpg_error_t err; + size_t nread; + + while (count) + { + err = ksba_reader_read (reader, buffer, count, &nread); + if (err) + return err; + buffer += nread; + count -= nread; + } + return 0; +} + + +/* Release the wrapper context and kill a running wrapper process. */ +static void +destroy_wrapper (struct wrapper_context_s *ctx) +{ + if (ctx->pid != (pid_t)(-1)) + { + gnupg_kill_process (ctx->pid); + gnupg_release_process (ctx->pid); + } + ksba_reader_release (ctx->reader); + SAFE_CLOSE (ctx->fp); + SAFE_CLOSE (ctx->log_fp); + 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 function. */ +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 rc; + size_t n; + char line[256]; + + rc = es_read (ctx->log_fp, line, sizeof line - 1, &n); + if (rc || !n) /* Error or EOF. */ + { + if (rc) + { + gpg_error_t err = gpg_error_from_syserror (); + if (gpg_err_code (err) == GPG_ERR_EAGAIN) + return 0; + log_error (_("error reading log from ldap wrapper %d: %s\n"), + (int)ctx->pid, gpg_strerror (err)); + } + print_log_line (ctx, NULL); /* Flush. */ + SAFE_CLOSE (ctx->log_fp); + 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_reaper_thread (void *dummy) +{ + gpg_error_t err; + struct wrapper_context_s *ctx; + struct wrapper_context_s *ctx_prev; + struct timespec abstime; + struct timespec curtime; + struct timespec timeout; + int millisecs; + gpgrt_poll_t *fparray = NULL; + int fparraysize = 0; + int count, i; + int ret; + time_t exptime; + + (void)dummy; + + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + + for (;;) + { + int any_action = 0; + + /* Wait until we are needed and then setup the FPARRAY. */ + /* Note: There is one unlock inside the block! */ + lock_reaper_list (); + { + while (!reaper_list && !shutting_down) + { + if (npth_cond_wait (&reaper_run_cond, &reaper_list_mutex)) + log_error ("ldap-reaper: waiting on condition failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + } + + for (count = 0, ctx = reaper_list; ctx; ctx = ctx->next) + if (ctx->log_fp) + count++; + if (count > fparraysize || !fparray) + { + /* Need to realloc the array. We simply discard it and + * replace it by a new one. */ + xfree (fparray); + fparray = xtrycalloc (count? count : 1, sizeof *fparray); + if (!fparray) + { + err = gpg_error_from_syserror (); + log_error ("ldap-reaper can't allocate poll array: %s" + " - waiting 1s\n", gpg_strerror (err)); + /* Note: Here we unlock and continue! */ + unlock_reaper_list (); + npth_sleep (1); + continue; + } + fparraysize = count; + } + for (count = 0, ctx = reaper_list; ctx; ctx = ctx->next) + { + if (ctx->log_fp) + { + log_assert (count < fparraysize); + fparray[count].stream = ctx->log_fp; + fparray[count].want_read = 1; + fparray[count].ignore = 0; + ctx->reaper_idx = count; + count++; + } + else + { + ctx->reaper_idx = -1; + fparray[count].ignore = 1; + } + } + for (i=count; i < fparraysize; i++) + fparray[i].ignore = 1; + } + unlock_reaper_list (); /* Note the one unlock inside the block. */ + + /* Compute the next timeout. */ + npth_clock_gettime (&curtime); + if (!(npth_timercmp (&curtime, &abstime, <))) + { + /* Inactivity is checked below. Nothing else to do. */ + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + } + npth_timersub (&abstime, &curtime, &timeout); + millisecs = timeout.tv_sec * 1000; + millisecs += timeout.tv_nsec / 1000000; + if (millisecs < 0) + millisecs = 1; + + if (DBG_EXTPROG) + { + log_debug ("ldap-reaper: next run (count=%d size=%d, timeout=%d)\n", + count, fparraysize, millisecs); + for (count=0; count < fparraysize; count++) + if (!fparray[count].ignore) + log_debug ("ldap-reaper: fp[%d] stream=%p want=%d\n", + count, fparray[count].stream,fparray[count].want_read); + } + + ret = es_poll (fparray, fparraysize, millisecs); + if (ret < 0) + { + err = gpg_error_from_syserror (); + log_error ("ldap-reaper failed to poll: %s" + " - waiting 1s\n", gpg_strerror (err)); + /* In case the reason for the error is a too large array, we + * release it so that it will be allocated smaller in the + * next round. */ + xfree (fparray); + fparray = NULL; + fparraysize = 0; + npth_sleep (1); + continue; + } + + if (DBG_EXTPROG) + { + for (count=0; count < fparraysize; count++) + if (!fparray[count].ignore) + log_debug ("ldap-reaper: fp[%d] stream=%p r=%d %c%c%c%c%c%c%c\n", + count, fparray[count].stream, ret, + fparray[count].got_read? 'r':'-', + fparray[count].got_write?'w':'-', + fparray[count].got_oob? 'o':'-', + fparray[count].got_rdhup?'H':'-', + fparray[count].got_err? 'e':'-', + fparray[count].got_hup? 'h':'-', + fparray[count].got_nval? 'n':'-'); + } + + /* All timestamps before exptime should be considered expired. */ + exptime = time (NULL); + if (exptime > INACTIVITY_TIMEOUT) + exptime -= INACTIVITY_TIMEOUT; + + lock_reaper_list (); + { + for (ctx = reaper_list; ctx; ctx = ctx->next) + { + /* Check whether there is any logging to be done. We need + * to check FPARRAYSIZE because it can be 0 in case + * es_poll returned a timeout. */ + if (fparraysize && ctx->log_fp && ctx->reaper_idx >= 0) + { + log_assert (ctx->reaper_idx < fparraysize); + if (fparray[ctx->reaper_idx].got_read) + { + if (read_log_data (ctx)) + { + SAFE_CLOSE (ctx->log_fp); + any_action = 1; + } + } + } + + /* Check whether the process is still running. */ + if (ctx->pid != (pid_t)(-1)) + { + int status; + + err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0, + &status); + if (!err) + { + if (DBG_EXTPROG) + log_info (_("ldap wrapper %d ready"), (int)ctx->pid); + ctx->ready = 1; + gnupg_release_process (ctx->pid); + ctx->pid = (pid_t)(-1); + any_action = 1; + } + else if (gpg_err_code (err) == GPG_ERR_GENERAL) + { + if (status == 10) + log_info (_("ldap wrapper %d ready: timeout\n"), + (int)ctx->pid); + else + log_info (_("ldap wrapper %d ready: exitcode=%d\n"), + (int)ctx->pid, status); + ctx->ready = 1; + gnupg_release_process (ctx->pid); + ctx->pid = (pid_t)(-1); + any_action = 1; + } + else if (gpg_err_code (err) != GPG_ERR_TIMEOUT) + { + log_error (_("waiting for ldap wrapper %d failed: %s\n"), + (int)ctx->pid, gpg_strerror (err)); + any_action = 1; + } + } + + /* Check whether we should terminate the process. */ + if (ctx->pid != (pid_t)(-1) + && ctx->stamp != (time_t)(-1) && ctx->stamp < exptime) + { + gnupg_kill_process (ctx->pid); + ctx->stamp = (time_t)(-1); + log_info (_("ldap wrapper %d stalled - killing\n"), + (int)ctx->pid); + /* We need to close the log stream because the cleanup + * loop waits for it. */ + SAFE_CLOSE (ctx->log_fp); + 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_EXTPROG) + { + log_debug ("ldap worker stati:\n"); + for (ctx = reaper_list; ctx; ctx = ctx->next) + log_debug (" c=%p pid=%d/%d rdr=%p logfp=%p" + " ctrl=%p/%d la=%lu rdy=%d\n", + ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, ctx->log_fp, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, + (unsigned long)ctx->stamp, ctx->ready); + } + + /* An extra 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=reaper_list; ctx; ctx_prev=ctx, ctx=ctx->next) + { + if (ctx->ready + && ((!ctx->log_fp && !ctx->reader) || shutting_down)) + { + if (ctx_prev) + ctx_prev->next = ctx->next; + else + reaper_list = ctx->next; + destroy_wrapper (ctx); + goto again; + } + } + } + unlock_reaper_list (); + } + + /*NOTREACHED*/ + return NULL; /* Make the compiler happy. */ +} + + + +/* Start the reaper thread for the ldap wrapper. */ +void +ldap_reaper_launch_thread (void) +{ + static int done; + npth_attr_t tattr; + npth_t thread; + int err; + + if (done) + return; + done = 1; + +#ifdef HAVE_W32_SYSTEM + /* Static init does not yet work in W32 nPth. */ + if (npth_cond_init (&reaper_run_cond, NULL)) + log_fatal ("%s: failed to init condition variabale: %s\n", + __func__, gpg_strerror (gpg_error_from_syserror ())); +#endif + + npth_attr_init (&tattr); + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + + if (npth_create (&thread, &tattr, ldap_reaper_thread, NULL)) + { + err = gpg_error_from_syserror (); + log_error ("error spawning ldap reaper reaper thread: %s\n", + gpg_strerror (err) ); + dirmngr_exit (1); + } + npth_setname_np (thread, "ldap-reaper"); + npth_attr_destroy (&tattr); +} + + + +/* Wait until all ldap wrappers have terminated. We assume that the + kill has already been sent to all of them. */ +void +ldap_wrapper_wait_connections () +{ + lock_reaper_list (); + { + shutting_down = 1; + if (npth_cond_signal (&reaper_run_cond)) + log_error ("%s: Ooops: signaling condition failed: %s\n", + __func__, gpg_strerror (gpg_error_from_syserror ())); + } + unlock_reaper_list (); + while (reaper_list) + npth_usleep (200); +} + + +/* This function is to be used to release a context associated with the + given reader object. */ +void +ldap_wrapper_release_context (ksba_reader_t reader) +{ + struct wrapper_context_s *ctx; + + if (!reader ) + return; + + lock_reaper_list (); + { + for (ctx=reaper_list; ctx; ctx=ctx->next) + if (ctx->reader == reader) + { + if (DBG_EXTPROG) + log_debug ("releasing ldap worker c=%p pid=%d/%d rdr=%p" + " ctrl=%p/%d\n", ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); + + ctx->reader = NULL; + SAFE_CLOSE (ctx->fp); + if (ctx->ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + } + if (ctx->fp_err) + log_info ("%s: reading from ldap wrapper %d failed: %s\n", + __func__, ctx->printable_pid, gpg_strerror (ctx->fp_err)); + break; + } + } + unlock_reaper_list (); +} + + +/* 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; + + lock_reaper_list (); + { + for (ctx=reaper_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->fp_err) + log_info ("%s: reading from ldap wrapper %d failed: %s\n", + __func__, ctx->printable_pid, gpg_strerror (ctx->fp_err)); + } + } + unlock_reaper_list (); +} + + +/* This is the callback used by the ldap wrapper to feed the ksba + * reader with the wrapper's 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; + struct timespec abstime; + struct timespec curtime; + struct timespec timeout; + int millisecs; + gpgrt_poll_t fparray[1]; + int ret; + gpg_error_t err; + + + /* 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 + then 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 continue (we don't want to + possibly overwrite the last error cause). Bail out also if the + file descriptor has been closed. */ + if (ctx->fp_err || !ctx->fp) + { + *nread = 0; + return -1; + } + + memset (fparray, 0, sizeof fparray); + fparray[0].stream = ctx->fp; + fparray[0].want_read = 1; + + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + + while (nleft > 0) + { + npth_clock_gettime (&curtime); + if (!(npth_timercmp (&curtime, &abstime, <))) + { + err = dirmngr_tick (ctx->ctrl); + if (err) + { + ctx->fp_err = err; + SAFE_CLOSE (ctx->fp); + return -1; + } + npth_clock_gettime (&abstime); + abstime.tv_sec += TIMERTICK_INTERVAL; + } + npth_timersub (&abstime, &curtime, &timeout); + millisecs = timeout.tv_sec * 1000; + millisecs += timeout.tv_nsec / 1000000; + if (millisecs < 0) + millisecs = 1; + + if (DBG_EXTPROG) + { + log_debug ("%s: fp[0] stream=%p want=%d\n", + __func__, fparray[0].stream,fparray[0].want_read); + } + + ret = es_poll (fparray, DIM (fparray), millisecs); + if (ret < 0) + { + ctx->fp_err = gpg_error_from_syserror (); + log_error ("error polling stdout of ldap wrapper %d: %s\n", + ctx->printable_pid, gpg_strerror (ctx->fp_err)); + SAFE_CLOSE (ctx->fp); + return -1; + } + if (DBG_EXTPROG) + { + log_debug ("%s: fp[0] stream=%p r=%d %c%c%c%c%c%c%c\n", + __func__, fparray[0].stream, ret, + fparray[0].got_read? 'r':'-', + fparray[0].got_write?'w':'-', + fparray[0].got_oob? 'o':'-', + fparray[0].got_rdhup?'H':'-', + fparray[0].got_err? 'e':'-', + fparray[0].got_hup? 'h':'-', + fparray[0].got_nval? 'n':'-'); + } + if (!ret) + { + /* Timeout. Will be handled when calculating the next timeout. */ + continue; + } + + if (fparray[0].got_read) + { + size_t n; + + if (es_read (ctx->fp, buffer, nleft, &n)) + { + ctx->fp_err = gpg_error_from_syserror (); + if (gpg_err_code (ctx->fp_err) == GPG_ERR_EAGAIN) + ctx->fp_err = 0; + else + { + log_error ("%s: error reading: %s (%d)\n", + __func__, gpg_strerror (ctx->fp_err), ctx->fp_err); + SAFE_CLOSE (ctx->fp); + return -1; + } + } + else if (!n) /* EOF */ + { + if (nleft == count) + return -1; /* EOF. */ + break; + } + nleft -= n; + buffer += n; + if (n > 0 && ctx->stamp != (time_t)(-1)) + ctx->stamp = time (NULL); + } + } + *nread = count - nleft; + + return 0; +} + + +/* Fork and exec the LDAP wrapper and return a new libksba reader + object at READER. ARGV is a NULL terminated list of arguments for + the wrapper. The function returns 0 on success or an error code. + + Special hack to avoid passing a password through the command line + which is globally visible: If the first element of ARGV is "--pass" + it will be removed and instead the environment variable + DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern + OSes the environment is not visible to other users. For those old + systems where it can't be avoided, we don't want to go into the + hassle of passing the password via stdin; it's just too complicated + and an LDAP password used for public directory lookups should not + be that confidential. */ +gpg_error_t +ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) +{ + gpg_error_t err; + pid_t pid; + struct wrapper_context_s *ctx; + int i; + int j; + const char **arg_list; + const char *pgmname; + estream_t outfp, errfp; + + /* 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 reap the child process and this is best done using a + general reaping thread, that thread can do the logging too. */ + ldap_reaper_launch_thread (); + + *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_spawn_process (pgmname, arg_list, + NULL, NULL, GNUPG_SPAWN_NONBLOCK, + NULL, &outfp, &errfp, &pid); + xfree (arg_list); + if (err) + { + xfree (ctx); + log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err)); + return err; + } + + ctx->pid = pid; + ctx->printable_pid = (int) pid; + ctx->fp = outfp; + ctx->log_fp = errfp; + 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. */ + lock_reaper_list (); + { + ctx->reader = *reader; + ctx->next = reaper_list; + reaper_list = ctx; + if (npth_cond_signal (&reaper_run_cond)) + log_error ("ldap-wrapper: Ooops: signaling condition failed: %s (%d)\n", + gpg_strerror (gpg_error_from_syserror ()), errno); + } + unlock_reaper_list (); + + if (DBG_EXTPROG) + log_debug ("ldap wrapper %d started (%p, %s)\n", + (int)ctx->pid, ctx->reader, pgmname); + + /* Need to wait for the first byte so we are able to detect an empty + output and not let the consumer see an EOF without further error + indications. The CRL loading logic assumes that after return + from this function, a failed search (e.g. host not found ) is + indicated right away. */ + { + unsigned char c; + + err = read_buffer (*reader, &c, 1); + if (err) + { + ldap_wrapper_release_context (*reader); + ksba_reader_release (*reader); + *reader = NULL; + if (gpg_err_code (err) == GPG_ERR_EOF) + return gpg_error (GPG_ERR_NO_DATA); + else + return err; + } + ksba_reader_unread (*reader, &c, 1); + } + + return 0; +} diff --git a/dirmngr/ldap-wrapper.h b/dirmngr/ldap-wrapper.h new file mode 100644 index 0000000..a015efa --- /dev/null +++ b/dirmngr/ldap-wrapper.h @@ -0,0 +1,40 @@ +/* ldap-wrapper.h - Interface to an LDAP access wrapper. + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef LDAP_WRAPPER_H +#define LDAP_WRAPPER_H + +#include <ksba.h> + +/* ldap-wrapper.c or ldap-wrapper-ce.c */ +void ldap_wrapper_launch_thread (void); +void ldap_wrapper_wait_connections (void); +void ldap_wrapper_release_context (ksba_reader_t reader); +void ldap_wrapper_connection_cleanup (ctrl_t); +gpg_error_t ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, + const char *argv[]); + + +/* dirmngr_ldap.c */ +#ifndef USE_LDAPWRAPPER +int ldap_wrapper_main (char **argv, estream_t outstream); +#endif + + +#endif /*LDAP_WRAPPER_H*/ diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c new file mode 100644 index 0000000..e016f64 --- /dev/null +++ b/dirmngr/ldap.c @@ -0,0 +1,874 @@ +/* ldap.c - LDAP access + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2010 g10 Code GmbH + * + * This file is part of DirMngr. + * + * DirMngr is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DirMngr is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <npth.h> + +#include "dirmngr.h" +#include "../common/exechelp.h" +#include "crlfetch.h" +#include "ldapserver.h" +#include "misc.h" +#include "ldap-wrapper.h" +#include "../common/host2net.h" + + +#define UNENCODED_URL_CHARS "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "01234567890" \ + "$-_.+!*'()," +#define USERCERTIFICATE "userCertificate" +#define CACERTIFICATE "caCertificate" +#define X509CACERT "x509caCert" +#define USERSMIMECERTIFICATE "userSMIMECertificate" + + +/* Definition for the context of the cert fetch functions. */ +struct cert_fetch_context_s +{ + ksba_reader_t reader; /* The reader used (shallow copy). */ + unsigned char *tmpbuf; /* Helper buffer. */ + size_t tmpbufsize; /* Allocated size of tmpbuf. */ + int truncated; /* Flag to indicate a truncated output. */ +}; + + + + +/* Add HOST and PORT to our list of LDAP servers. Fixme: We should + better use an extra list of servers. */ +static void +add_server_to_servers (const char *host, int port) +{ + ldap_server_t server; + ldap_server_t last = NULL; + const char *s; + + if (!port) + port = 389; + + for (server=opt.ldapservers; server; server = server->next) + { + if (!strcmp (server->host, host) && server->port == port) + return; /* already in list... */ + last = server; + } + + /* We assume that the host names are all supplied by our + configuration files and thus are sane. To keep this assumption + we must reject all invalid host names. */ + for (s=host; *s; s++) + if (!strchr ("abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "01234567890.-", *s)) + { + log_error (_("invalid char 0x%02x in host name - not added\n"), *s); + return; + } + + log_info (_("adding '%s:%d' to the ldap server list\n"), host, port); + server = xtrycalloc (1, sizeof *s); + if (!server) + log_error (_("malloc failed: %s\n"), strerror (errno)); + else + { + server->host = xstrdup (host); + server->port = port; + if (last) + last->next = server; + else + opt.ldapservers = server; + } +} + + + + +/* Perform an LDAP query. Returns an gpg error code or 0 on success. + The function returns a new reader object at READER. */ +static gpg_error_t +run_ldap_wrapper (ctrl_t ctrl, + int ignore_timeout, + int multi_mode, + const char *proxy, + const char *host, int port, + const char *user, const char *pass, + const char *dn, const char *filter, const char *attr, + const char *url, + ksba_reader_t *reader) +{ + const char *argv[40]; + int argc; + char portbuf[30], timeoutbuf[30]; + + + *reader = NULL; + + argc = 0; + if (pass) /* Note, that the password must be the first item. */ + { + argv[argc++] = "--pass"; + argv[argc++] = pass; + } + + if (DBG_LOOKUP) + argv[argc++] = "-vv"; + else if (DBG_EXTPROG) + argv[argc++] = "-v"; + + 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++; + /* fall through */ + 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 according 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 = strconcat ("ldap:///", u_dn, "?", attrs, "?sub?", u_filter, NULL); + if (!*url) + err = gpg_error_from_syserror (); + else + err = 0; + + xfree (u_dn); + xfree (u_filter); + return err; +} + + +/* Prepare an LDAP query to return the cACertificate attribute for DN. + * All configured default servers are queried until one responds. + * This function returns an error code or 0 and stored a newly + * allocated contect object at CONTEXT on success. */ +gpg_error_t +start_cacert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context, + const char *dn) +{ + gpg_error_t err; + struct ldapserver_iter iter; + + *r_context = xtrycalloc (1, sizeof **r_context); + if (!*r_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, /* --multi (record format) */ + opt.ldap_proxy, + server->host, server->port, + server->user, server->pass, + dn, "objectClass=*", "cACertificate", NULL, + &(*r_context)->reader); + if (!err) + break; /* Probably found a result. */ + } + + if (err) + { + xfree (*r_context); + *r_context = NULL; + } + return err; +} + + +/* Prepare an LDAP query to return certificates matching PATTERNS + * using the SERVER. This function returns an error code or 0 and + * stores a newly allocated object at R_CONTEXT on success. */ +gpg_error_t +start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context, + strlist_t patterns, const ldap_server_t server) +{ + gpg_error_t err; + char *proxy = NULL; + char *host = NULL; + int port; + char *user = NULL; + char *pass = NULL; + const char *base; + char *argv[50]; + int argc = 0; + int argc_malloced = 0; + char portbuf[30], timeoutbuf[30]; + + + *r_context = NULL; + + if (opt.ldap_proxy && !(proxy = xtrystrdup (opt.ldap_proxy))) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (server) + { + if (server->host && !(host = xtrystrdup (server->host))) + { + err = gpg_error_from_syserror (); + goto leave; + } + port = server->port; + if (server->user && !(user = xtrystrdup (server->user))) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (server->pass && !(pass = xtrystrdup (server->pass))) + { + err = gpg_error_from_syserror (); + goto leave; + } + base = server->base; + + } + else /* Use a default server. */ + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + + + if (!base) + base = ""; + + if (pass) /* Note: Must be the first item. */ + { + argv[argc++] = "--pass"; + argv[argc++] = pass; + } + + if (DBG_LOOKUP) + argv[argc++] = "-vv"; + else if (DBG_EXTPROG) + argv[argc++] = "-v"; + + argv[argc++] = "--log-with-pid"; + argv[argc++] = "--multi"; + if (opt.ldaptimeout) + { + snprintf (timeoutbuf, sizeof timeoutbuf, "%u", opt.ldaptimeout); + argv[argc++] = "--timeout"; + argv[argc++] = timeoutbuf; + } + if (opt.ldap_proxy) + { + argv[argc++] = "--proxy"; + argv[argc++] = proxy; + } + if (host) + { + argv[argc++] = "--host"; + argv[argc++] = host; + } + if (port) + { + snprintf (portbuf, sizeof portbuf, "%d", port); + argv[argc++] = "--port"; + argv[argc++] = portbuf; + } + if (user) + { + argv[argc++] = "--user"; + argv[argc++] = user; + } + + /* All entries in argv from this index on are malloc'ed. */ + argc_malloced = argc; + + for (; patterns; patterns = patterns->next) + { + strlist_t sl; + char *url; + + if (argc >= DIM (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); + err = gpg_error (GPG_ERR_INV_USER_ID); + goto leave; + } + if ((sl->flags & 1)) + err = make_url (&url, sl->d, "objectClass=*"); + else + err = make_url (&url, base, sl->d); + free_strlist (sl); + if (err) + goto leave; + argv[argc++] = url; + } + argv[argc] = NULL; + + *r_context = xtrycalloc (1, sizeof **r_context); + if (!*r_context) + { + err = gpg_error_from_errno (errno); + goto leave; + } + + err = ldap_wrapper (ctrl, &(*r_context)->reader, (const char**)argv); + + if (err) + { + xfree (*r_context); + *r_context = NULL; + } + + leave: + for (; argc_malloced < argc; argc_malloced++) + xfree (argv[argc_malloced]); + xfree (proxy); + xfree (host); + xfree (user); + xfree (pass); + 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; + unsigned long 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 = buf32_to_ulong (hdr+1); + if (*hdr == 'V' && okay) + { +#if 0 /* That code to extra a cert from a CMS object 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 /* End unfinished code to extract from a CMS object. */ + { + *value = xtrymalloc (n); + if (!*value) + return gpg_error_from_errno (errno); + *valuelen = n; + err = read_buffer (context->reader, *value, n); + break; /* Ready or error. */ + } + } + else if (!n && *hdr == 'A') + okay = 0; + else if (n) + { + if (n > context->tmpbufsize) + { + xfree (context->tmpbuf); + context->tmpbufsize = 0; + context->tmpbuf = xtrymalloc (n+1); + if (!context->tmpbuf) + return gpg_error_from_errno (errno); + context->tmpbufsize = n; + } + err = read_buffer (context->reader, context->tmpbuf, n); + if (err) + break; + if (*hdr == 'A') + { + p = context->tmpbuf; + p[n] = 0; /*(we allocated one extra byte for this.)*/ + /* fixme: is_cms = 0; */ + if ( (pend = strchr (p, ';')) ) + *pend = 0; /* Strip off the extension. */ + if (!ascii_strcasecmp (p, USERCERTIFICATE)) + { + if (DBG_LOOKUP) + log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", + USERCERTIFICATE); + okay = 1; + } + else if (!ascii_strcasecmp (p, CACERTIFICATE)) + { + if (DBG_LOOKUP) + log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", + CACERTIFICATE); + okay = 1; + } + else if (!ascii_strcasecmp (p, X509CACERT)) + { + if (DBG_LOOKUP) + log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", + CACERTIFICATE); + okay = 1; + } +/* else if (!ascii_strcasecmp (p, USERSMIMECERTIFICATE)) */ +/* { */ +/* if (DBG_LOOKUP) */ +/* log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", */ +/* USERSMIMECERTIFICATE); */ +/* okay = 1; */ +/* is_cms = 1; */ +/* } */ + else + { + if (DBG_LOOKUP) + log_debug ("fetch_next_cert_ldap: got attribute '%s'" + " - ignored\n", p); + okay = 0; + } + } + else if (*hdr == 'E') + { + p = context->tmpbuf; + p[n] = 0; /*(we allocated one extra byte for this.)*/ + if (!strcmp (p, "truncated")) + { + context->truncated = 1; + log_info (_("ldap_search hit the size limit of" + " the server\n")); + } + } + } + } + + if (err) + { + xfree (*value); + *value = NULL; + *valuelen = 0; + if (gpg_err_code (err) == GPG_ERR_EOF && context->truncated) + { + context->truncated = 0; /* So that the next call would return EOF. */ + err = gpg_error (GPG_ERR_TRUNCATED); + } + } + + return err; +} + + +void +end_cert_fetch_ldap (cert_fetch_context_t context) +{ + if (context) + { + ksba_reader_t reader = context->reader; + + xfree (context->tmpbuf); + xfree (context); + ldap_wrapper_release_context (reader); + ksba_reader_release (reader); + } +} diff --git a/dirmngr/ldapserver.c b/dirmngr/ldapserver.c new file mode 100644 index 0000000..16e13e2 --- /dev/null +++ b/dirmngr/ldapserver.c @@ -0,0 +1,132 @@ +/* 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 line is 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); + server = NULL; + } + + return server; +} diff --git a/dirmngr/ldapserver.h b/dirmngr/ldapserver.h new file mode 100644 index 0000000..b6eb452 --- /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 <https://www.gnu.org/licenses/>. */ + +#ifndef LDAPSERVER_H +#define LDAPSERVER_H + +#include "dirmngr.h" + +/* Release the list of SERVERS. As usual it is okay to call this + function with SERVERS passed as NULL. */ +void ldapserver_list_free (ldap_server_t servers); + + +/* Parse a single LDAP server configuration line. Returns the server + or NULL in case of errors. The configuration line is assumed to be + colon separated with these fields: + + 1. field: Hostname + 2. field: Portnumber + 3. field: Username + 4. field: Password + 5. field: Base DN + + FILENAME and LINENO are used for diagnostic purposes only. +*/ +ldap_server_t ldapserver_parse_one (char *line, + const char *filename, unsigned int lineno); + + +/* Iterate over all servers. */ + +struct ldapserver_iter +{ + ctrl_t ctrl; + enum { LDAPSERVER_SESSION, LDAPSERVER_OPT } group; + ldap_server_t server; +}; + + +static inline void +ldapserver_iter_next (struct ldapserver_iter *iter) +{ + if (iter->server) + iter->server = iter->server->next; + + if (! iter->server) + { + if (iter->group == LDAPSERVER_SESSION) + { + iter->group = LDAPSERVER_OPT; + iter->server = opt.ldapservers; + } + } +} + + +static inline int +ldapserver_iter_end_p (struct ldapserver_iter *iter) +{ + return (iter->group == LDAPSERVER_OPT && iter->server == NULL); +} + + +static inline void +ldapserver_iter_begin (struct ldapserver_iter *iter, ctrl_t ctrl) +{ + iter->ctrl = ctrl; + iter->group = LDAPSERVER_SESSION; + iter->server = get_ldapservers_from_ctrl (ctrl); + + while (iter->server == NULL && ! ldapserver_iter_end_p (iter)) + ldapserver_iter_next (iter); +} + +#endif /* LDAPSERVER_H */ diff --git a/dirmngr/loadswdb.c b/dirmngr/loadswdb.c new file mode 100644 index 0000000..fb88372 --- /dev/null +++ b/dirmngr/loadswdb.c @@ -0,0 +1,405 @@ +/* loadswdb.c - Load the swdb file from versions.gnupg.org + * Copyright (C) 2016 g10 Code GmbH + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "dirmngr.h" +#include "../common/ccparray.h" +#include "../common/exectool.h" +#include "misc.h" +#include "ks-engine.h" + + +/* Get the time from the current swdb file and store it at R_FILEDATE + * and R_VERIFIED. If the file does not exist 0 is stored at there. + * The function returns 0 on success or an error code. */ +static gpg_error_t +time_of_saved_swdb (const char *fname, time_t *r_filedate, time_t *r_verified) +{ + gpg_error_t err; + estream_t fp = NULL; + char *line = NULL; + size_t length_of_line = 0; + size_t maxlen; + ssize_t len; + char *fields[2]; + gnupg_isotime_t isot; + time_t filedate = (time_t)(-1); + time_t verified = (time_t)(-1); + + *r_filedate = 0; + *r_verified = 0; + + fp = es_fopen (fname, "r"); + err = fp? 0 : gpg_error_from_syserror (); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_ENOENT) + err = 0; /* No file - assume time is the year of Unix. */ + goto leave; + } + + /* Note that the parser uses the first occurrence of a matching + * values and ignores possible duplicated values. */ + maxlen = 2048; /* Set limit. */ + while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0) + { + if (!maxlen) + { + err = gpg_error (GPG_ERR_LINE_TOO_LONG); + goto leave; + } + /* Strip newline and carriage return, if present. */ + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) + line[--len] = '\0'; + + if (split_fields (line, fields, DIM (fields)) < DIM(fields)) + continue; /* Skip empty lines and names w/o a value. */ + if (*fields[0] == '#') + continue; /* Skip comments. */ + + /* Record the meta data. */ + if (filedate == (time_t)(-1) && !strcmp (fields[0], ".filedate")) + { + if (string2isotime (isot, fields[1])) + filedate = isotime2epoch (isot); + } + else if (verified == (time_t)(-1) && !strcmp (fields[0], ".verified")) + { + if (string2isotime (isot, fields[1])) + verified = isotime2epoch (isot); + } + } + if (len < 0 || es_ferror (fp)) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (filedate == (time_t)(-1) || verified == (time_t)(-1)) + { + err = gpg_error (GPG_ERR_INV_TIME); + goto leave; + } + + *r_filedate = filedate; + *r_verified = verified; + + leave: + if (err) + log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err)); + xfree (line); + es_fclose (fp); + return err; +} + + + +/* Read a file from URL and return it as an estream memory buffer at + * R_FP. */ +static gpg_error_t +fetch_file (ctrl_t ctrl, const char *url, estream_t *r_fp) +{ + gpg_error_t err; + estream_t fp = NULL; + estream_t httpfp = NULL; + size_t nread, nwritten; + char buffer[1024]; + + if ((err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_NOCACHE, &httpfp))) + goto leave; + + /* We now read the data from the web server into a memory buffer. + * To avoid excessive memory use in case of a ill behaving server we + * put a 64 k size limit on the buffer. As of today the actual size + * of the swdb.lst file is 3k. */ + fp = es_fopenmem (64*1024, "rw"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error allocating memory buffer: %s\n", gpg_strerror (err)); + goto leave; + } + + for (;;) + { + if (es_read (httpfp, buffer, sizeof buffer, &nread)) + { + err = gpg_error_from_syserror (); + log_error ("error reading '%s': %s\n", + es_fname_get (httpfp), gpg_strerror (err)); + goto leave; + } + + if (!nread) + break; /* Ready. */ + if (es_write (fp, buffer, nread, &nwritten)) + { + err = gpg_error_from_syserror (); + log_error ("error writing '%s': %s\n", + es_fname_get (fp), gpg_strerror (err)); + goto leave; + } + else if (nread != nwritten) + { + err = gpg_error (GPG_ERR_EIO); + log_error ("error writing '%s': %s\n", + es_fname_get (fp), "short write"); + goto leave; + } + } + + es_rewind (fp); + *r_fp = fp; + fp = NULL; + + leave: + es_fclose (httpfp); + es_fclose (fp); + return err; +} + + +/* Communication object for verify_status_cb. */ +struct verify_status_parm_s +{ + time_t sigtime; + int anyvalid; +}; + +static void +verify_status_cb (void *opaque, const char *keyword, char *args) +{ + struct verify_status_parm_s *parm = opaque; + + if (DBG_EXTPROG) + log_debug ("gpgv status: %s %s\n", keyword, args); + + /* We care only about the first valid signature. */ + if (!strcmp (keyword, "VALIDSIG") && !parm->anyvalid) + { + char *fields[3]; + + parm->anyvalid = 1; + if (split_fields (args, fields, DIM (fields)) >= 3) + parm->sigtime = parse_timestamp (fields[2], NULL); + } +} + + + +/* Load the swdb file into the current home directory. Do this onlky + * when needed unless FORCE is set which will always get a new + * copy. */ +gpg_error_t +dirmngr_load_swdb (ctrl_t ctrl, int force) +{ + gpg_error_t err; + char *fname = NULL; /* The swdb.lst file. */ + char *tmp_fname = NULL; /* The temporary swdb.lst file. */ + char *keyfile_fname = NULL; + estream_t swdb = NULL; + estream_t swdb_sig = NULL; + ccparray_t ccp; + const char **argv = NULL; + struct verify_status_parm_s verify_status_parm = { (time_t)(-1), 0 }; + estream_t outfp = NULL; + time_t now = gnupg_get_time (); + time_t filedate = 0; /* ".filedate" from our swdb. */ + time_t verified = 0; /* ".verified" from our swdb. */ + gnupg_isotime_t isotime; + + + fname = make_filename_try (gnupg_homedir (), "swdb.lst", NULL); + if (!fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Check whether there is a need to get an update. */ + if (!force) + { + static int not_older_than; + static time_t lastcheck; + + if (!not_older_than) + { + /* To balance access to the server we use a random time from + * 5 to 7 days for update checks. */ + not_older_than = 5 * 86400; + not_older_than += (get_uint_nonce () % (2*86400)); + } + + if (now - lastcheck < 3600) + { + /* We checked our swdb file in the last hour - don't check + * again to avoid unnecessary disk access. */ + err = 0; + goto leave; + } + lastcheck = now; + + err = time_of_saved_swdb (fname, &filedate, &verified); + if (gpg_err_code (err) == GPG_ERR_INV_TIME) + err = 0; /* Force reading. */ + if (err) + goto leave; + if (filedate >= now) + goto leave; /* Current or newer. */ + if (now - filedate < not_older_than) + goto leave; /* Our copy is pretty new (not older than 7 days). */ + if (verified > now && now - verified < 3*3600) + goto leave; /* We downloaded and verified in the last 3 hours. */ + } + + /* Create the filename of the file with the keys. */ + keyfile_fname = make_filename_try (gnupg_datadir (), "distsigkey.gpg", NULL); + if (!keyfile_fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Fetch the swdb from the web. */ + err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst", &swdb); + if (err) + goto leave; + err = fetch_file (ctrl, "https://versions.gnupg.org/swdb.lst.sig", &swdb_sig); + if (err) + goto leave; + + /* Run gpgv. */ + ccparray_init (&ccp, 0); + ccparray_put (&ccp, "--enable-special-filenames"); + ccparray_put (&ccp, "--status-fd=2"); + ccparray_put (&ccp, "--keyring"); + ccparray_put (&ccp, keyfile_fname); + ccparray_put (&ccp, "--"); + ccparray_put (&ccp, "-&@INEXTRA@"); + ccparray_put (&ccp, "-"); + ccparray_put (&ccp, NULL); + argv = ccparray_get (&ccp, NULL); + if (!argv) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (DBG_EXTPROG) + log_debug ("starting gpgv\n"); + err = gnupg_exec_tool_stream (gnupg_module_name (GNUPG_MODULE_NAME_GPGV), + argv, swdb, swdb_sig, NULL, + verify_status_cb, &verify_status_parm); + if (!err && verify_status_parm.sigtime == (time_t)(-1)) + err = gpg_error (verify_status_parm.anyvalid? GPG_ERR_BAD_SIGNATURE + /**/ : GPG_ERR_INV_TIME ); + if (DBG_EXTPROG) + log_debug ("gpgv finished: err=%d\n", err); + if (err) + goto leave; + + /* If our swdb is not older than the downloaded one. We don't + * bother to update. */ + if (!force && filedate >= verify_status_parm.sigtime) + goto leave; + + /* Create a file name for a temporary file in the home directory. + * We will later rename that file to the real name. */ + { + char *tmpstr; + +#ifdef HAVE_W32_SYSTEM + tmpstr = es_bsprintf ("tmp-%u-swdb", (unsigned int)getpid ()); +#else + tmpstr = es_bsprintf (".#%u.swdb", (unsigned int)getpid ()); +#endif + if (!tmpstr) + { + err = gpg_error_from_syserror (); + goto leave; + } + tmp_fname = make_filename_try (gnupg_homedir (), tmpstr, NULL); + xfree (tmpstr); + if (!tmp_fname) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + outfp = es_fopen (tmp_fname, "w"); + if (!outfp) + { + err = gpg_error_from_syserror (); + log_error (_("error creating '%s': %s\n"), tmp_fname, gpg_strerror (err)); + goto leave; + } + + epoch2isotime (isotime, verify_status_parm.sigtime); + es_fprintf (outfp, ".filedate %s\n", isotime); + epoch2isotime (isotime, now); + es_fprintf (outfp, ".verified %s\n", isotime); + + if (es_fseek (swdb, 0, SEEK_SET)) + { + err = gpg_error_from_syserror (); + goto leave; + } + + err = copy_stream (swdb, outfp); + if (err) + { + /* Well, it might also be a reading error, but that is pretty + * unlikely for a memory stream. */ + log_error (_("error writing '%s': %s\n"), tmp_fname, gpg_strerror (err)); + goto leave; + } + + if (es_fclose (outfp)) + { + err = gpg_error_from_syserror (); + log_error (_("error writing '%s': %s\n"), tmp_fname, gpg_strerror (err)); + goto leave; + } + outfp = NULL; + + err = gnupg_rename_file (tmp_fname, fname, NULL); + if (err) + goto leave; + xfree (tmp_fname); + tmp_fname = NULL; + + + leave: + es_fclose (outfp); + if (tmp_fname) + gnupg_remove (tmp_fname); /* This is a temporary file. */ + xfree (argv); + es_fclose (swdb_sig); + es_fclose (swdb); + xfree (keyfile_fname); + xfree (tmp_fname); + xfree (fname); + return err; +} diff --git a/dirmngr/misc.c b/dirmngr/misc.c new file mode 100644 index 0000000..ba47c99 --- /dev/null +++ b/dirmngr/misc.c @@ -0,0 +1,656 @@ +/* misc.c - miscellaneous + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2002, 2003, 2004, 2010 Free Software Foundation, Inc. + * + * This file is part of DirMngr. + * + * DirMngr is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DirMngr is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <errno.h> + +#include "dirmngr.h" +#include "../common/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 function 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, 0); +} + + +/* FIXME: Replace this by hextobin. */ +char* +hexify_data (const unsigned char* data, size_t len, int with_prefix) +{ + int i; + char *result = xmalloc (2*len + (with_prefix?2:0) + 1); + char *p; + + if (with_prefix) + p = stpcpy (result, "0x"); + else + p = result; + + for (i = 0; i < 2*len; i+=2 ) + snprintf (p+i, 3, "%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. Note + 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 (string, strlen (string), NULL); + 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; + int idx; + + 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"); + for (idx=1; (p = ksba_cert_get_subject (cert, idx)); idx++) + { + log_debug (" aka: "); + dump_string (p); + ksba_free (p); + log_printf ("\n"); + } + + log_debug (" hash algo: %s\n", ksba_cert_get_digest_algo (cert)); + + p = get_fingerprint_hexstring (cert); + log_debug (" SHA1 fingerprint: %s\n", p); + xfree (p); + } + log_debug ("END Certificate\n"); +} + + + +/* Log the certificate's name in "#SN/ISSUERDN" format along with + TEXT. */ +void +cert_log_name (const char *text, ksba_cert_t cert) +{ + log_info ("%s", text? text:"certificate" ); + if (cert) + { + ksba_sexp_t sn; + char *p; + + p = ksba_cert_get_issuer (cert, 0); + sn = ksba_cert_get_serial (cert); + if (p && sn) + { + log_printf (" #"); + dump_serial (sn); + log_printf ("/"); + dump_string (p); + } + else + log_printf (" [invalid]"); + ksba_free (sn); + xfree (p); + } + log_printf ("\n"); +} + + +/* Log the certificate's subject DN along with TEXT. */ +void +cert_log_subject (const char *text, ksba_cert_t cert) +{ + log_info ("%s", text? text:"subject" ); + if (cert) + { + char *p; + + p = ksba_cert_get_subject (cert, 0); + if (p) + { + log_printf (" /"); + dump_string (p); + xfree (p); + } + else + log_printf (" [invalid]"); + } + log_printf ("\n"); +} + + +/* Callback to print infos about the TLS certificates. */ +void +cert_log_cb (http_session_t sess, gpg_error_t err, + const char *hostname, const void **certs, size_t *certlens) +{ + ksba_cert_t cert; + size_t n; + + (void)sess; + + if (!err) + return; /* No error - no need to log anything */ + + log_debug ("expected hostname: %s\n", hostname); + for (n=0; certs[n]; n++) + { + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_init_from_mem (cert, certs[n], certlens[n]); + if (err) + log_error ("error parsing cert for logging: %s\n", gpg_strerror (err)); + else + { + char textbuf[20]; + snprintf (textbuf, sizeof textbuf, "server[%u]", (unsigned int)n); + dump_cert (textbuf, cert); + } + + ksba_cert_release (cert); + } +} + + +/**************** + * Remove all %xx escapes; this is done inplace. + * Returns: New length of the string. + */ +static int +remove_percent_escapes (unsigned char *string) +{ + int n = 0; + unsigned char *p, *s; + + for (p = s = string; *s; s++) + { + if (*s == '%') + { + if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2)) + { + s++; + *p = xtoi_2 (s); + s++; + p++; + n++; + } + else + { + *p++ = *s++; + if (*s) + *p++ = *s++; + if (*s) + *p++ = *s++; + if (*s) + *p = 0; + return -1; /* Bad URI. */ + } + } + else + { + *p++ = *s; + n++; + } + } + *p = 0; /* Always keep a string terminator. */ + return n; +} + + +/* Return the host name and the port (0 if none was given) from the + URL. Return NULL on error or if host is not included in the + URL. */ +char * +host_and_port_from_url (const char *url, int *port) +{ + const char *s, *s2; + char *buf, *p; + int n; + + s = url; + + *port = 0; + + /* Find the scheme */ + if ( !(s2 = strchr (s, ':')) || s2 == s ) + return NULL; /* No scheme given. */ + s = s2+1; + + /* Find the hostname */ + if (*s != '/') + return NULL; /* Does not start with a slash. */ + + s++; + if (*s != '/') + return NULL; /* No host name. */ + s++; + + buf = xtrystrdup (s); + if (!buf) + { + log_error (_("malloc failed: %s\n"), strerror (errno)); + return NULL; + } + if ((p = strchr (buf, '/'))) + *p++ = 0; + strlwr (buf); + if ((p = strchr (buf, ':'))) + { + *p++ = 0; + *port = atoi (p); + } + + /* Remove quotes and make sure that no Nul has been encoded. */ + if ((n = remove_percent_escapes (buf)) < 0 + || n != strlen (buf) ) + { + log_error (_("bad URL encoding detected\n")); + xfree (buf); + return NULL; + } + + return buf; +} + + +/* A KSBA reader callback to read from an estream. */ +static int +my_estream_ksba_reader_cb (void *cb_value, char *buffer, size_t count, + size_t *r_nread) +{ + estream_t fp = cb_value; + + if (!fp) + return gpg_error (GPG_ERR_INV_VALUE); + + if (!buffer && !count && !r_nread) + { + es_rewind (fp); + return 0; + } + + *r_nread = es_fread (buffer, 1, count, fp); + if (!*r_nread) + return -1; /* EOF or error. */ + return 0; /* Success. */ +} + + +/* Create a KSBA reader object and connect it to the estream FP. */ +gpg_error_t +create_estream_ksba_reader (ksba_reader_t *r_reader, estream_t fp) +{ + gpg_error_t err; + ksba_reader_t reader; + + *r_reader = NULL; + err = ksba_reader_new (&reader); + if (!err) + err = ksba_reader_set_cb (reader, my_estream_ksba_reader_cb, fp); + if (err) + { + log_error (_("error initializing reader object: %s\n"), + gpg_strerror (err)); + ksba_reader_release (reader); + return err; + } + *r_reader = reader; + return 0; +} + +gpg_error_t +armor_data (char **r_string, const void *data, size_t datalen) +{ + gpg_error_t err; + struct b64state b64state; + estream_t fp; + long length; + char *buffer; + size_t nread; + + *r_string = NULL; + + fp = es_fopenmem (0, "rw,samethread"); + if (!fp) + return gpg_error_from_syserror (); + + if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK")) + || (err=b64enc_write (&b64state, data, datalen)) + || (err = b64enc_finish (&b64state))) + { + es_fclose (fp); + return err; + } + + /* FIXME: To avoid the extra buffer allocation estream should + provide a function to snatch the internal allocated memory from + such a memory stream. */ + length = es_ftell (fp); + if (length < 0) + { + err = gpg_error_from_syserror (); + es_fclose (fp); + return err; + } + + buffer = xtrymalloc (length+1); + if (!buffer) + { + err = gpg_error_from_syserror (); + es_fclose (fp); + return err; + } + + es_rewind (fp); + if (es_read (fp, buffer, length, &nread)) + { + err = gpg_error_from_syserror (); + es_fclose (fp); + return err; + } + buffer[nread] = 0; + es_fclose (fp); + + *r_string = buffer; + return 0; +} + + +/* Copy all data from IN to OUT. OUT may be NULL to use this fucntion + * as a dummy reader. */ +gpg_error_t +copy_stream (estream_t in, estream_t out) +{ + char buffer[512]; + size_t nread; + + while (!es_read (in, buffer, sizeof buffer, &nread)) + { + if (!nread) + return 0; /* EOF */ + if (out && es_write (out, buffer, nread, NULL)) + break; + } + return gpg_error_from_syserror (); +} diff --git a/dirmngr/misc.h b/dirmngr/misc.h new file mode 100644 index 0000000..be4049e --- /dev/null +++ b/dirmngr/misc.h @@ -0,0 +1,91 @@ +/* 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, int with_prefix); + +/* Returns the serial number as a hex string. */ +char* serial_hex ( ksba_sexp_t serial ); + +/* Take an S-Expression encoded blob and return a pointer to the + actual data as well as its length. */ +const unsigned char *serial_to_buffer (const ksba_sexp_t serial, + size_t *length); + +/* Do an in-place percent unescaping of STRING. Returns STRING. */ +char *unpercent_string (char *string); + +gpg_error_t canon_sexp_to_gcry (const unsigned char *canon, + gcry_sexp_t *r_sexp); + +/* Return an allocated hex-string with the SHA-1 fingerprint of + CERT. */ +char *get_fingerprint_hexstring (ksba_cert_t cert); +/* Return an allocated hex-string with the SHA-1 fingerprint of + CERT. This version inserts the usual colons. */ +char *get_fingerprint_hexstring_colon (ksba_cert_t cert); + +/* Log CERT in short format with s/n and issuer DN prefixed by TEXT. */ +void cert_log_name (const char *text, ksba_cert_t cert); + +/* Log CERT in short format with the subject DN prefixed by TEXT. */ +void cert_log_subject (const char *text, ksba_cert_t cert); + +/* Dump the serial number SERIALNO to the log stream. */ +void dump_serial (ksba_sexp_t serialno); + +/* Dump STRING to the log file but choose the best readable + format. */ +void dump_string (const char *string); + +/* Dump an KSBA cert object to the log stream. Prefix the output with + TEXT. This is used for debugging. */ +void dump_cert (const char *text, ksba_cert_t cert); + +/* Callback to print infos about the TLS certificates. */ +void cert_log_cb (http_session_t sess, gpg_error_t err, + const char *hostname, const void **certs, size_t *certlens); + +/* Return the host name and the port (0 if none was given) from the + URL. Return NULL on error or if host is not included in the + URL. */ +char *host_and_port_from_url (const char *url, int *port); + +/* Create a KSBA reader object and connect it to the estream FP. */ +gpg_error_t create_estream_ksba_reader (ksba_reader_t *r_reader, estream_t fp); + +/* Encode the binary data in {DATA,DATALEN} as ASCII-armored data and + stored it as a NUL-terminated string in *R_STRING. The caller is + responsible for freeing *R_STRING. */ +gpg_error_t armor_data (char **r_string, const void *data, size_t datalen); + +/* Copy all data from IN to OUT. */ +gpg_error_t copy_stream (estream_t in, estream_t out); + +#endif /* MISC_H */ diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c new file mode 100644 index 0000000..eaa6ca2 --- /dev/null +++ b/dirmngr/ocsp.c @@ -0,0 +1,889 @@ +/* ocsp.c - OCSP management + * Copyright (C) 2004, 2007 g10 Code GmbH + * + * This file is part of DirMngr. + * + * DirMngr is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DirMngr is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <assert.h> + +#include "dirmngr.h" +#include "misc.h" +#include "http.h" +#include "validate.h" +#include "certcache.h" +#include "ocsp.h" + +/* The maximum size we allow as a response from an OCSP reponder. */ +#define MAX_RESPONSE_SIZE 65536 + + +static const char oidstr_ocsp[] = "1.3.6.1.5.5.7.48.1"; + + +/* Telesec attribute used to implement a positive confirmation. + + CertHash ::= SEQUENCE { + HashAlgorithm AlgorithmIdentifier, + certificateHash OCTET STRING } + */ +/* static const char oidstr_certHash[] = "1.3.36.8.3.13"; */ + + + + +/* Read from FP and return a newly allocated buffer in R_BUFFER with the + entire data read from FP. */ +static gpg_error_t +read_response (estream_t fp, unsigned char **r_buffer, size_t *r_buflen) +{ + gpg_error_t err; + unsigned char *buffer; + size_t bufsize, nbytes; + + *r_buffer = NULL; + *r_buflen = 0; + + bufsize = 4096; + buffer = xtrymalloc (bufsize); + if (!buffer) + return gpg_error_from_errno (errno); + + nbytes = 0; + for (;;) + { + unsigned char *tmp; + size_t nread = 0; + + assert (nbytes < bufsize); + nread = es_fread (buffer+nbytes, 1, bufsize-nbytes, fp); + if (nread < bufsize-nbytes && es_ferror (fp)) + { + err = gpg_error_from_errno (errno); + log_error (_("error reading from responder: %s\n"), + strerror (errno)); + xfree (buffer); + return err; + } + if ( !(nread == bufsize-nbytes && !es_feof (fp))) + { /* Response successfully 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 response. The signature value and the + production date are returned at R_SIGVAL and R_PRODUCED_AT; they + may be NULL or an empty string if not available. A new hash + context is returned at R_MD. */ +static gpg_error_t +do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp, + const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert, + ksba_sexp_t *r_sigval, ksba_isotime_t r_produced_at, + gcry_md_hd_t *r_md) +{ + 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; + + *r_sigval = NULL; + *r_produced_at = 0; + *r_md = NULL; + + if (dirmngr_use_tor ()) + { + /* For now we do not allow OCSP via Tor due to possible privacy + concerns. Needs further research. */ + log_error (_("OCSP request not possible due to Tor mode\n")); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + if (opt.disable_http) + { + log_error (_("OCSP request not possible due to disabled HTTP\n")); + return gpg_error (GPG_ERR_NOT_SUPPORTED); + } + + err = ksba_ocsp_add_target (ocsp, cert, issuer_cert); + if (err) + { + log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err)); + return err; + } + + { + size_t n; + unsigned char nonce[32]; + + n = ksba_ocsp_set_nonce (ocsp, NULL, 0); + if (n > sizeof nonce) + n = sizeof nonce; + gcry_create_nonce (nonce, n); + ksba_ocsp_set_nonce (ocsp, nonce, n); + } + + err = ksba_ocsp_build_request (ocsp, &request, &requestlen); + if (err) + { + log_error (_("error building OCSP request: %s\n"), gpg_strerror (err)); + return err; + } + + once_more: + err = http_open (&http, HTTP_REQ_POST, url, NULL, NULL, + ((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) + | (dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0) + | (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0) + | (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)), + ctrl->http_proxy, NULL, NULL, NULL); + if (err) + { + log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err)); + xfree (free_this); + return err; + } + + es_fprintf (http_get_write_ptr (http), + "Content-Type: application/ocsp-request\r\n" + "Content-Length: %lu\r\n", + (unsigned long)requestlen ); + http_start_data (http); + if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1) + { + err = gpg_error_from_errno (errno); + log_error ("error sending request to '%s': %s\n", url, strerror (errno)); + http_close (http, 0); + xfree (request); + xfree (free_this); + return err; + } + xfree (request); + request = NULL; + + err = http_wait_response (http); + if (err || http_get_status_code (http) != 200) + { + if (err) + log_error (_("error reading HTTP response for '%s': %s\n"), + url, gpg_strerror (err)); + else + { + switch (http_get_status_code (http)) + { + case 301: + case 302: + { + const char *s = http_get_header (http, "Location"); + + log_info (_("URL '%s' redirected to '%s' (%u)\n"), + url, s?s:"[none]", http_get_status_code (http)); + if (s && *s && redirects_left-- ) + { + xfree (free_this); url = NULL; + free_this = xtrystrdup (s); + if (!free_this) + err = gpg_error_from_errno (errno); + else + { + url = free_this; + http_close (http, 0); + goto once_more; + } + } + else + err = gpg_error (GPG_ERR_NO_DATA); + log_error (_("too many redirections\n")); + } + break; + + case 413: /* Payload too large */ + err = gpg_error (GPG_ERR_TOO_LARGE); + 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; + } + /* log_printhex (response, responselen, "ocsp response"); */ + + 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) + { + int hash_algo; + + if (opt.verbose) + log_info (_("OCSP responder at '%s' status: %s\n"), url, t); + + /* Get the signature value now because we can call this function + * only once. */ + *r_sigval = ksba_ocsp_get_sig_val (ocsp, r_produced_at); + + hash_algo = hash_algo_from_sigval (*r_sigval); + if (!hash_algo) + { + if (opt.verbose) + log_info ("ocsp: using SHA-256 as fallback hash algo.\n"); + hash_algo = GCRY_MD_SHA256; + } + err = gcry_md_open (r_md, hash_algo, 0); + if (err) + { + log_error (_("failed to establish a hashing context for OCSP: %s\n"), + gpg_strerror (err)); + goto leave; + } + if (DBG_HASHING) + gcry_md_debug (*r_md, "ocsp"); + + err = ksba_ocsp_hash_response (ocsp, response, responselen, + HASH_FNC, *r_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); + } + + leave: + xfree (response); + xfree (free_this); + if (err) + { + xfree (*r_sigval); + *r_sigval = NULL; + *r_produced_at = 0; + gcry_md_close (*r_md); + *r_md = NULL; + } + 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 + { + /* 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 err; +} + + +/* 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; + const char *s; + + /* Create a suitable S-expression with the hash value of our response. */ + gcry_md_final (md); + algo = gcry_md_get_algo (md); + s = gcry_md_algo_name (algo); + if (algo && s && strlen (s) < 16) + { + char hashalgostr[16+1]; + int i; + + for (i=0; s[i]; i++) + hashalgostr[i] = ascii_tolower (s[i]); + hashalgostr[i] = 0; + err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", + hashalgostr, + (int)gcry_md_get_algo_dlen (algo), + gcry_md_read (md, algo)); + } + else + err = gpg_error (GPG_ERR_DIGEST_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; + + /* dump_cert ("from ocsp response", cert); */ + 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"); + } + + if (cert) + { + err = check_signature_core (ctrl, cert, s_sig, s_hash, + signer_fpr_list); + ksba_cert_release (cert); + if (!err) + { + ksba_free (name); + ksba_free (keyid); + gcry_sexp_release (s_hash); + return 0; /* Successfully verified the signature. */ + } + log_error ("responder certificate "); + if (name) + log_printf ("'/%s' ", name); + if (keyid) + { + log_printf ("{"); + dump_serial (keyid); + log_printf ("} "); + } + log_printf ("did not verify: %s\n", gpg_strerror (err)); + } + ksba_free (name); + ksba_free (keyid); + } + + 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 = do_ocsp_request (ctrl, ocsp, url, cert, issuer_cert, + &sigval, produced_at, &md); + if (err) + goto leave; + + /* It is sometimes useful to know the responder ID. */ + if (opt.verbose) + { + char *resp_name; + ksba_sexp_t resp_keyid; + + err = ksba_ocsp_get_responder_id (ocsp, &resp_name, &resp_keyid); + if (err) + log_info (_("error getting responder ID: %s\n"), gpg_strerror (err)); + else + { + log_info ("responder id: "); + if (resp_name) + log_printf ("'/%s' ", resp_name); + if (resp_keyid) + { + log_printf ("{"); + dump_serial (resp_keyid); + log_printf ("} "); + } + log_printf ("\n"); + } + ksba_free (resp_name); + ksba_free (resp_keyid); + err = 0; + } + + /* We got a useful answer, check that the answer has a valid signature. */ + if (!sigval || !*produced_at || !md) + { + 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? "superseded": + reason == KSBA_CRLREASON_CESSATION_OF_OPERATION? + "cessation of operation": + reason == KSBA_CRLREASON_CERTIFICATE_HOLD? + "certificate on hold": + reason == KSBA_CRLREASON_REMOVE_FROM_CRL? + "removed from CRL": + reason == KSBA_CRLREASON_PRIVILEGE_WITHDRAWN? + "privilege withdrawn": + reason == KSBA_CRLREASON_AA_COMPROMISE? "AA compromise": + reason == KSBA_CRLREASON_OTHER? "other":"?"); + + } + + + if (status == KSBA_STATUS_REVOKED) + err = gpg_error (GPG_ERR_CERT_REVOKED); + else if (status == KSBA_STATUS_UNKNOWN) + err = gpg_error (GPG_ERR_NO_DATA); + else if (status != KSBA_STATUS_GOOD) + err = gpg_error (GPG_ERR_GENERAL); + + /* Allow for some clock skew. */ + gnupg_get_isotime (current_time); + add_seconds_to_isotime (current_time, opt.ocsp_max_clock_skew); + + if (strcmp (this_update, current_time) > 0 ) + { + log_error (_("OCSP responder returned a status in the future\n")); + log_info ("used now: %s this_update: %s\n", current_time, this_update); + if (!err) + err = gpg_error (GPG_ERR_TIME_CONFLICT); + } + + /* Check that THIS_UPDATE is not too far back in the past. */ + gnupg_copy_time (tmp_time, this_update); + add_seconds_to_isotime (tmp_time, + opt.ocsp_max_period+opt.ocsp_max_clock_skew); + if (!*tmp_time || strcmp (tmp_time, current_time) < 0 ) + { + log_error (_("OCSP responder returned a non-current status\n")); + log_info ("used now: %s this_update: %s\n", + current_time, this_update); + if (!err) + err = gpg_error (GPG_ERR_TIME_CONFLICT); + } + + /* Check that we are not beyound NEXT_UPDATE (plus some extra time). */ + if (*next_update) + { + gnupg_copy_time (tmp_time, next_update); + add_seconds_to_isotime (tmp_time, + opt.ocsp_current_period+opt.ocsp_max_clock_skew); + if (!*tmp_time && strcmp (tmp_time, current_time) < 0 ) + { + log_error (_("OCSP responder returned an too old status\n")); + log_info ("used now: %s next_update: %s\n", + current_time, next_update); + if (!err) + err = gpg_error (GPG_ERR_TIME_CONFLICT); + } + } + + + leave: + gcry_md_close (md); + gcry_sexp_release (s_sig); + xfree (sigval); + ksba_cert_release (issuer_cert); + ksba_cert_release (cert); + ksba_ocsp_release (ocsp); + xfree (url_buffer); + return err; +} + + +/* Release the list of OCSP certificates hold in the CTRL object. */ +void +release_ctrl_ocsp_certs (ctrl_t ctrl) +{ + while (ctrl->ocsp_certs) + { + cert_ref_t tmp = ctrl->ocsp_certs->next; + xfree (ctrl->ocsp_certs); + ctrl->ocsp_certs = tmp; + } +} diff --git a/dirmngr/ocsp.h b/dirmngr/ocsp.h new file mode 100644 index 0000000..cfab7dd --- /dev/null +++ b/dirmngr/ocsp.h @@ -0,0 +1,31 @@ +/* ocsp.h - OCSP management + * Copyright (C) 2003 g10 Code GmbH + * + * This file is part of DirMngr. + * + * DirMngr is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * DirMngr is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifndef OCSP_H +#define OCSP_H + +gpg_error_t ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr, + int force_default_responder); + +/* Release the list of OCSP certificates hold in the CTRL object. */ +void release_ctrl_ocsp_certs (ctrl_t ctrl); + +#endif /*OCSP_H*/ diff --git a/dirmngr/server.c b/dirmngr/server.c new file mode 100644 index 0000000..6c72e5c --- /dev/null +++ b/dirmngr/server.c @@ -0,0 +1,3103 @@ +/* server.c - LDAP and Keyserver access server + * Copyright (C) 2002 Klarälvdalens Datakonsult AB + * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011, 2015 g10 Code GmbH + * Copyright (C) 2014, 2015, 2016 Werner Koch + * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik + * + * 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 <https://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0+ + */ + +#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> + +#include "dirmngr.h" +#include <assuan.h> + +#include "crlcache.h" +#include "crlfetch.h" +#if USE_LDAP +# include "ldapserver.h" +#endif +#include "ocsp.h" +#include "certcache.h" +#include "validate.h" +#include "misc.h" +#if USE_LDAP +# include "ldap-wrapper.h" +#endif +#include "ks-action.h" +#include "ks-engine.h" /* (ks_hkp_print_hosttable) */ +#if USE_LDAP +# include "ldap-parse-uri.h" +#endif +#include "dns-stuff.h" +#include "../common/mbox-util.h" +#include "../common/zb32.h" +#include "../common/server-help.h" + +/* To avoid DoS attacks we limit the size of a certificate to + something reasonable. The DoS was actually only an issue back when + Dirmngr was a system service and not a user service. */ +#define MAX_CERT_LENGTH (16*1024) + +/* The limit for the CERTLIST inquiry. We allow for up to 20 + * certificates but also take PEM encoding into account. */ +#define MAX_CERTLIST_LENGTH ((MAX_CERT_LENGTH * 20 * 4)/3) + +/* The same goes for OpenPGP keyblocks, but here we need to allow for + much longer blocks; a 200k keyblock is not too unusual for keys + with a lot of signatures (e.g. 0x5b0358a2). 9C31503C6D866396 even + has 770 KiB as of 2015-08-23. To avoid adding a runtime option we + now use 20MiB which should really be enough. Well, a key with + several pictures could be larger (the parser as a 18MiB limit for + attribute packets) but it won't be nice to the keyservers to send + them such large blobs. */ +#define MAX_KEYBLOCK_LENGTH (20*1024*1024) + + +#define PARM_ERROR(t) assuan_set_error (ctx, \ + gpg_error (GPG_ERR_ASS_PARAMETER), (t)) +#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \ + /**/: gpg_error (e)) + + + +/* Control structure per connection. */ +struct server_local_s +{ + /* Data used to associate an Assuan context with local server data */ + assuan_context_t assuan_ctx; + + /* The session id (a counter). */ + unsigned int session_id; + + /* Per-session LDAP servers. */ + ldap_server_t ldapservers; + + /* Per-session list of keyservers. */ + uri_item_t keyservers; + + /* If this flag is set to true this dirmngr process will be + terminated after the end of this session. */ + int stopme; + + /* State variable private to is_tor_running. */ + int tor_state; + + /* If the first both flags are set the assuan logging of data lines + * is suppressed. The count variable is used to show the number of + * non-logged bytes. */ + size_t inhibit_data_logging_count; + unsigned int inhibit_data_logging : 1; + unsigned int inhibit_data_logging_now : 1; +}; + + +/* Cookie definition for assuan data line output. */ +static gpgrt_ssize_t data_line_cookie_write (void *cookie, + const void *buffer, size_t size); +static int data_line_cookie_close (void *cookie); +static es_cookie_io_functions_t data_line_cookie_functions = + { + NULL, + data_line_cookie_write, + NULL, + data_line_cookie_close + }; + + +/* Local prototypes */ +static const char *task_check_wkd_support (ctrl_t ctrl, const char *domain); + + + + +/* Accessor for the local ldapservers variable. */ +ldap_server_t +get_ldapservers_from_ctrl (ctrl_t ctrl) +{ + if (ctrl && ctrl->server_local) + return ctrl->server_local->ldapservers; + else + return NULL; +} + +/* Release an uri_item_t list. */ +static void +release_uri_item_list (uri_item_t list) +{ + while (list) + { + uri_item_t tmp = list->next; + http_release_parsed_uri (list->parsed_uri); + xfree (list); + list = tmp; + } +} + +/* Release all configured keyserver info from CTRL. */ +void +release_ctrl_keyservers (ctrl_t ctrl) +{ + if (! ctrl->server_local) + return; + + release_uri_item_list (ctrl->server_local->keyservers); + ctrl->server_local->keyservers = NULL; +} + + + +/* Helper to print a message while leaving a command. */ +static gpg_error_t +leave_cmd (assuan_context_t ctx, gpg_error_t err) +{ + if (err) + { + const char *name = assuan_get_command_name (ctx); + if (!name) + name = "?"; + if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) + log_error ("command '%s' failed: %s\n", name, + gpg_strerror (err)); + else + log_error ("command '%s' failed: %s <%s>\n", name, + gpg_strerror (err), gpg_strsource (err)); + } + return err; +} + + +/* This is a wrapper around assuan_send_data which makes debugging the + output in verbose mode easier. */ +static gpg_error_t +data_line_write (assuan_context_t ctx, const void *buffer_arg, size_t size) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + const char *buffer = buffer_arg; + gpg_error_t err; + + /* If we do not want logging, enable it here. */ + if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) + ctrl->server_local->inhibit_data_logging_now = 1; + + if (opt.verbose && buffer && size) + { + /* Ease reading of output by sending a physical line at each LF. */ + const char *p; + size_t n, nbytes; + + nbytes = size; + do + { + p = memchr (buffer, '\n', nbytes); + n = p ? (p - buffer) + 1 : nbytes; + err = assuan_send_data (ctx, buffer, n); + if (err) + { + gpg_err_set_errno (EIO); + goto leave; + } + buffer += n; + nbytes -= n; + if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ + { + gpg_err_set_errno (EIO); + goto leave; + } + } + while (nbytes); + } + else + { + err = assuan_send_data (ctx, buffer, size); + if (err) + { + gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */ + goto leave; + } + } + + leave: + if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) + { + ctrl->server_local->inhibit_data_logging_now = 0; + ctrl->server_local->inhibit_data_logging_count += size; + } + + return err; +} + + +/* A write handler used by es_fopencookie to write assuan data + lines. */ +static gpgrt_ssize_t +data_line_cookie_write (void *cookie, const void *buffer, size_t size) +{ + assuan_context_t ctx = cookie; + + if (data_line_write (ctx, buffer, size)) + return -1; + return (gpgrt_ssize_t)size; +} + + +static int +data_line_cookie_close (void *cookie) +{ + assuan_context_t ctx = cookie; + + if (DBG_IPC) + { + ctrl_t ctrl = assuan_get_pointer (ctx); + + if (ctrl && ctrl->server_local + && ctrl->server_local->inhibit_data_logging + && ctrl->server_local->inhibit_data_logging_count) + log_debug ("(%zu bytes sent via D lines not shown)\n", + ctrl->server_local->inhibit_data_logging_count); + } + if (assuan_send_data (ctx, NULL, 0)) + { + gpg_err_set_errno (EIO); + return -1; + } + + return 0; +} + + +/* Copy the % and + escaped string S into the buffer D and replace the + escape sequences. Note, that it is sufficient to allocate the + target string D as long as the source string S, i.e.: strlen(s)+1. + Note further that if S contains an escaped binary Nul the resulting + string D will contain the 0 as well as all other characters but it + will be impossible to know whether this is the original EOS or a + copied Nul. */ +static void +strcpy_escaped_plus (char *d, const unsigned char *s) +{ + while (*s) + { + if (*s == '%' && s[1] && s[2]) + { + s++; + *d++ = xtoi_2 ( s); + s += 2; + } + else if (*s == '+') + *d++ = ' ', s++; + else + *d++ = *s++; + } + *d = 0; +} + + +/* This function returns true if a Tor server is running. The status + * is cached for the current connection. */ +static int +is_tor_running (ctrl_t ctrl) +{ + /* Check whether we can connect to the proxy. */ + + if (!ctrl || !ctrl->server_local) + return 0; /* Ooops. */ + + if (!ctrl->server_local->tor_state) + { + assuan_fd_t sock; + + sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR); + if (sock == ASSUAN_INVALID_FD) + ctrl->server_local->tor_state = -1; /* Not running. */ + else + { + assuan_sock_close (sock); + ctrl->server_local->tor_state = 1; /* Running. */ + } + } + return (ctrl->server_local->tor_state > 0); +} + + +/* Return an error if the assuan context does not belong to the owner + of the process or to root. On error FAILTEXT is set as Assuan + error string. */ +static gpg_error_t +check_owner_permission (assuan_context_t ctx, const char *failtext) +{ +#ifdef HAVE_W32_SYSTEM + /* Under Windows the dirmngr is always run under the control of the + user. */ + (void)ctx; + (void)failtext; +#else + gpg_err_code_t ec; + assuan_peercred_t cred; + + ec = gpg_err_code (assuan_get_peercred (ctx, &cred)); + if (!ec && cred->uid && cred->uid != getuid ()) + ec = GPG_ERR_EPERM; + if (ec) + return set_error (ec, failtext); +#endif + return 0; +} + + + +/* Common code for get_cert_local and get_issuer_cert_local. */ +static ksba_cert_t +do_get_cert_local (ctrl_t ctrl, const char *name, const char *command) +{ + unsigned char *value; + size_t valuelen; + int rc; + char *buf; + ksba_cert_t cert; + + buf = name? strconcat (command, " ", name, NULL) : xtrystrdup (command); + if (!buf) + rc = gpg_error_from_syserror (); + else + { + 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 identifier (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 identifier (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_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 = strconcat ("SENDCERT_SKI ", hexkeyid, " /", name, NULL); + if (!buf) + { + log_error ("can't allocate enough memory: %s\n", strerror (errno)); + xfree (hexkeyid); + return NULL; + } + 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); + gpg_error_t err = 0; + + 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 if (!strcmp (key, "http-proxy")) + { + xfree (ctrl->http_proxy); + if (!*value || !strcmp (value, "none")) + ctrl->http_proxy = NULL; + else if (!(ctrl->http_proxy = xtrystrdup (value))) + err = gpg_error_from_syserror (); + } + else if (!strcmp (key, "honor-keyserver-url-used")) + { + /* Return an error if we are running in Tor mode. */ + if (dirmngr_use_tor ()) + err = gpg_error (GPG_ERR_FORBIDDEN); + } + else if (!strcmp (key, "http-crl")) + { + int i = *value? atoi (value) : 0; + ctrl->http_no_crl = !i; + } + else + err = gpg_error (GPG_ERR_UNKNOWN_OPTION); + + return err; +} + + + +static const char hlp_dns_cert[] = + "DNS_CERT <subtype> <name>\n" + "DNS_CERT --pka <user_id>\n" + "DNS_CERT --dane <user_id>\n" + "\n" + "Return the CERT record for <name>. <subtype> is one of\n" + " * Return the first record of any supported subtype\n" + " PGP Return the first record of subtype PGP (3)\n" + " IPGP Return the first record of subtype IPGP (6)\n" + "If the content of a certificate is available (PGP) it is returned\n" + "by data lines. Fingerprints and URLs are returned via status lines.\n" + "In --pka mode the fingerprint and if available an URL is returned.\n" + "In --dane mode the key is returned from RR type 61"; +static gpg_error_t +cmd_dns_cert (assuan_context_t ctx, char *line) +{ + /* ctrl_t ctrl = assuan_get_pointer (ctx); */ + gpg_error_t err = 0; + int pka_mode, dane_mode; + char *mbox = NULL; + char *namebuf = NULL; + char *encodedhash = NULL; + const char *name; + int certtype; + char *p; + void *key = NULL; + size_t keylen; + unsigned char *fpr = NULL; + size_t fprlen; + char *url = NULL; + + pka_mode = has_option (line, "--pka"); + dane_mode = has_option (line, "--dane"); + line = skip_options (line); + + if (pka_mode && dane_mode) + { + err = PARM_ERROR ("either --pka or --dane may be given"); + goto leave; + } + + if (pka_mode || dane_mode) + ; /* No need to parse here - we do this later. */ + else + { + p = strchr (line, ' '); + if (!p) + { + err = PARM_ERROR ("missing arguments"); + goto leave; + } + *p++ = 0; + if (!strcmp (line, "*")) + certtype = DNS_CERTTYPE_ANY; + else if (!strcmp (line, "IPGP")) + certtype = DNS_CERTTYPE_IPGP; + else if (!strcmp (line, "PGP")) + certtype = DNS_CERTTYPE_PGP; + else + { + err = PARM_ERROR ("unknown subtype"); + goto leave; + } + while (spacep (p)) + p++; + line = p; + if (!*line) + { + err = PARM_ERROR ("name missing"); + goto leave; + } + } + + if (pka_mode || dane_mode) + { + char *domain; /* Points to mbox. */ + char hashbuf[32]; /* For SHA-1 and SHA-256. */ + + /* We lowercase ascii characters but the DANE I-D does not allow + this. FIXME: Check after the release of the RFC whether to + change this. */ + mbox = mailbox_from_userid (line); + if (!mbox || !(domain = strchr (mbox, '@'))) + { + err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); + goto leave; + } + *domain++ = 0; + + if (pka_mode) + { + gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox)); + encodedhash = zb32_encode (hashbuf, 8*20); + if (!encodedhash) + { + err = gpg_error_from_syserror (); + goto leave; + } + namebuf = strconcat (encodedhash, "._pka.", domain, NULL); + if (!namebuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + name = namebuf; + certtype = DNS_CERTTYPE_IPGP; + } + else + { + /* Note: The hash is truncated to 28 bytes and we lowercase + the result only for aesthetic reasons. */ + gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox)); + encodedhash = bin2hex (hashbuf, 28, NULL); + if (!encodedhash) + { + err = gpg_error_from_syserror (); + goto leave; + } + ascii_strlwr (encodedhash); + namebuf = strconcat (encodedhash, "._openpgpkey.", domain, NULL); + if (!namebuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + name = namebuf; + certtype = DNS_CERTTYPE_RR61; + } + } + else + name = line; + + err = get_dns_cert (name, certtype, &key, &keylen, &fpr, &fprlen, &url); + if (err) + goto leave; + + if (key) + { + err = data_line_write (ctx, key, keylen); + if (err) + goto leave; + } + + if (fpr) + { + char *tmpstr; + + tmpstr = bin2hex (fpr, fprlen, NULL); + if (!tmpstr) + err = gpg_error_from_syserror (); + else + { + err = assuan_write_status (ctx, "FPR", tmpstr); + xfree (tmpstr); + } + if (err) + goto leave; + } + + if (url) + { + err = assuan_write_status (ctx, "URL", url); + if (err) + goto leave; + } + + + leave: + xfree (key); + xfree (fpr); + xfree (url); + xfree (mbox); + xfree (namebuf); + xfree (encodedhash); + return leave_cmd (ctx, err); +} + + + +/* Core of cmd_wkd_get and task_check_wkd_support. If CTX is NULL + * this function will not write anything to the assuan output. */ +static gpg_error_t +proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line) +{ + gpg_error_t err = 0; + char *mbox = NULL; + char *domainbuf = NULL; + char *domain; /* Points to mbox or domainbuf. This is used to + * connect to the host. */ + char *domain_orig;/* Points to mbox. This is the used for the + * query; i.e. the domain part of the + * addrspec. */ + char sha1buf[20]; + char *uri = NULL; + char *encodedhash = NULL; + int opt_submission_addr; + int opt_policy_flags; + int is_wkd_query; /* True if this is a real WKD query. */ + int no_log = 0; + char portstr[20] = { 0 }; + int subdomain_mode = 0; + + opt_submission_addr = has_option (line, "--submission-address"); + opt_policy_flags = has_option (line, "--policy-flags"); + if (has_option (line, "--quick")) + ctrl->timeout = opt.connect_quick_timeout; + line = skip_options (line); + is_wkd_query = !(opt_policy_flags || opt_submission_addr); + + mbox = mailbox_from_userid (line); + if (!mbox || !(domain = strchr (mbox, '@'))) + { + err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id"); + goto leave; + } + *domain++ = 0; + domain_orig = domain; + + + /* Let's check whether we already know that the domain does not + * support WKD. */ + if (is_wkd_query) + { + if (domaininfo_is_wkd_not_supported (domain_orig)) + { + err = gpg_error (GPG_ERR_NO_DATA); + dirmngr_status_printf (ctrl, "NOTE", "wkd_cached_result %u", err); + goto leave; + } + } + + + /* First try the new "openpgp" subdomain. We check that the domain + * is valid because it is later used as an unescaped filename part + * of the URI. */ + if (is_valid_domain_name (domain_orig)) + { + dns_addrinfo_t aibuf; + + domainbuf = strconcat ( "openpgpkey.", domain_orig, NULL); + if (!domainbuf) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* FIXME: We should put a cache into dns-stuff because the same + * query (with a different port and socket type, though) will be + * done later by http function. */ + err = resolve_dns_name (domainbuf, 0, 0, 0, &aibuf, NULL); + if (err) + { + err = 0; + xfree (domainbuf); + domainbuf = NULL; + } + else /* Got a subdomain. */ + { + free_dns_addrinfo (aibuf); + subdomain_mode = 1; + domain = domainbuf; + } + } + + /* Check for SRV records unless we have a subdomain. */ + if (!subdomain_mode) + { + struct srventry *srvs; + unsigned int srvscount; + size_t domainlen, targetlen; + int i; + + err = get_dns_srv (domain, "openpgpkey", NULL, &srvs, &srvscount); + if (err) + goto leave; + + /* Check for rogue DNS names. */ + for (i = 0; i < srvscount; i++) + { + if (!is_valid_domain_name (srvs[i].target)) + { + err = gpg_error (GPG_ERR_DNS_ADDRESS); + log_error ("rogue openpgpkey SRV record for '%s'\n", domain); + xfree (srvs); + goto leave; + } + } + + /* Find the first target which also ends in DOMAIN or is equal + * to DOMAIN. */ + domainlen = strlen (domain); + for (i = 0; i < srvscount; i++) + { + if (DBG_DNS) + log_debug ("srv: trying '%s:%hu'\n", srvs[i].target, srvs[i].port); + targetlen = strlen (srvs[i].target); + if ((targetlen > domainlen + 1 + && srvs[i].target[targetlen - domainlen - 1] == '.' + && !ascii_strcasecmp (srvs[i].target + targetlen - domainlen, + domain)) + || (targetlen == domainlen + && !ascii_strcasecmp (srvs[i].target, domain))) + { + /* found. */ + domainbuf = xtrystrdup (srvs[i].target); + if (!domainbuf) + { + err = gpg_error_from_syserror (); + xfree (srvs); + goto leave; + } + domain = domainbuf; + if (srvs[i].port) + snprintf (portstr, sizeof portstr, ":%hu", srvs[i].port); + break; + } + } + xfree (srvs); + } + + /* Prepare the hash of the local part. */ + gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox)); + encodedhash = zb32_encode (sha1buf, 8*20); + if (!encodedhash) + { + err = gpg_error_from_syserror (); + goto leave; + } + + if (opt_submission_addr) + { + uri = strconcat ("https://", + domain, + portstr, + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "submission-address", + NULL); + } + else if (opt_policy_flags) + { + uri = strconcat ("https://", + domain, + portstr, + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "policy", + NULL); + } + else + { + char *escapedmbox; + + escapedmbox = http_escape_string (mbox, "%;?&="); + if (escapedmbox) + { + uri = strconcat ("https://", + domain, + portstr, + "/.well-known/openpgpkey/", + subdomain_mode? domain_orig : "", + subdomain_mode? "/" : "", + "hu/", + encodedhash, + "?l=", + escapedmbox, + NULL); + xfree (escapedmbox); + no_log = 1; + if (uri) + { + err = dirmngr_status_printf (ctrl, "SOURCE", "https://%s%s", + domain, portstr); + if (err) + goto leave; + } + } + } + if (!uri) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Setup an output stream and perform the get. */ + { + estream_t outfp; + + outfp = ctx? es_fopencookie (ctx, "w", data_line_cookie_functions) : NULL; + if (!outfp && ctx) + err = set_error (GPG_ERR_ASS_GENERAL, + "error setting up a data stream"); + else + { + if (ctrl->server_local) + { + if (no_log) + ctrl->server_local->inhibit_data_logging = 1; + ctrl->server_local->inhibit_data_logging_now = 0; + ctrl->server_local->inhibit_data_logging_count = 0; + } + err = ks_action_fetch (ctrl, uri, outfp); + es_fclose (outfp); + if (ctrl->server_local) + ctrl->server_local->inhibit_data_logging = 0; + + /* Register the result under the domain name of MBOX. */ + switch (gpg_err_code (err)) + { + case 0: + domaininfo_set_wkd_supported (domain_orig); + break; + + case GPG_ERR_NO_NAME: + /* There is no such domain. */ + domaininfo_set_no_name (domain_orig); + break; + + case GPG_ERR_NO_DATA: + if (is_wkd_query && ctrl->server_local) + { + /* Mark that and schedule a check. */ + domaininfo_set_wkd_not_found (domain_orig); + workqueue_add_task (task_check_wkd_support, domain_orig, + ctrl->server_local->session_id, 1); + } + else if (opt_policy_flags) /* No policy file - no support. */ + domaininfo_set_wkd_not_supported (domain_orig); + break; + + default: + /* Don't register other errors. */ + break; + } + } + } + + leave: + xfree (uri); + xfree (encodedhash); + xfree (mbox); + xfree (domainbuf); + return err; +} + + +static const char hlp_wkd_get[] = + "WKD_GET [--submission-address|--policy-flags] <user_id>\n" + "\n" + "Return the key or other info for <user_id>\n" + "from the Web Key Directory."; +static gpg_error_t +cmd_wkd_get (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + + err = proc_wkd_get (ctrl, ctx, line); + + return leave_cmd (ctx, err); +} + + +/* A task to check whether DOMAIN supports WKD. This is done by + * checking whether the policy flags file can be read. */ +static const char * +task_check_wkd_support (ctrl_t ctrl, const char *domain) +{ + char *string; + + if (!ctrl || !domain) + return "check_wkd_support"; + + string = strconcat ("--policy-flags foo@", domain, NULL); + if (!string) + log_error ("%s: %s\n", __func__, gpg_strerror (gpg_error_from_syserror ())); + else + { + proc_wkd_get (ctrl, NULL, string); + xfree (string); + } + + return NULL; +} + + + +static const char hlp_ldapserver[] = + "LDAPSERVER <data>\n" + "\n" + "Add a new LDAP server to the list of configured LDAP servers.\n" + "DATA is in the same format as expected in the configure file."; +static gpg_error_t +cmd_ldapserver (assuan_context_t ctx, char *line) +{ +#if USE_LDAP + ctrl_t ctrl = assuan_get_pointer (ctx); + ldap_server_t server; + ldap_server_t *last_next_p; + + while (spacep (line)) + line++; + if (*line == '\0') + return leave_cmd (ctx, PARM_ERROR (_("ldapserver missing"))); + + server = ldapserver_parse_one (line, "", 0); + if (! server) + return leave_cmd (ctx, gpg_error (GPG_ERR_INV_ARG)); + + last_next_p = &ctrl->server_local->ldapservers; + while (*last_next_p) + last_next_p = &(*last_next_p)->next; + *last_next_p = server; + return leave_cmd (ctx, 0); +#else + (void)line; + return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED)); +#endif +} + + +static const char hlp_isvalid[] = + "ISVALID [--only-ocsp] [--force-default-responder]" + " <certificate_id> [<certificate_fpr>]\n" + "\n" + "This command checks whether the certificate identified by the\n" + "certificate_id is valid. This is done by consulting CRLs or\n" + "whatever has been configured. Note, that the returned error codes\n" + "are from gpg-error.h. The command may callback using the inquire\n" + "function. See the manual for details.\n" + "\n" + "The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n" + "delimited by a single dot. The first part is the SHA-1 hash of the\n" + "issuer name and the second part the serial number.\n" + "\n" + "If an OCSP check is desired CERTIFICATE_FPR with the hex encoded\n" + "fingerprint of the certificate is required. In this case an OCSP\n" + "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, *fpr; + 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); + + /* 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. */ + issuerhash = xstrdup (line); + + serialno = strchr (issuerhash, '.'); + if (!serialno) + { + xfree (issuerhash); + return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID"))); + } + *serialno++ = 0; + if (strlen (issuerhash) != 40) + { + xfree (issuerhash); + return leave_cmd (ctx, PARM_ERROR ("cert ID is too short")); + } + + fpr = strchr (serialno, ' '); + while (fpr && spacep (fpr)) + fpr++; + if (fpr && *fpr) + { + char *endp = strchr (fpr, ' '); + if (endp) + *endp = 0; + if (strlen (fpr) != 40) + { + xfree (issuerhash); + return leave_cmd (ctx, PARM_ERROR ("fingerprint too short")); + } + ocsp_mode = 1; + } + + + again: + if (ocsp_mode) + { + /* Note, that we currently ignore the supplied fingerprint FPR; + * instead ocsp_isvalid does an inquire to ask for the cert. + * The fingerprint may eventually be used to lookup the + * certificate in a local cache. */ + if (!opt.allow_ocsp) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + else + err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder); + + if (gpg_err_code (err) == GPG_ERR_CONFIGURATION + && gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR) + { + /* No default responder configured - fallback to CRL. */ + if (!only_ocsp) + log_info ("falling back to CRL check\n"); + ocsp_mode = 0; + goto again; + } + } + else if (only_ocsp) + err = gpg_error (GPG_ERR_NO_CRL_KNOWN); + else + { + switch (crl_cache_isvalid (ctrl, + issuerhash, serialno, + ctrl->force_crl_refresh)) + { + case CRL_CACHE_VALID: + err = 0; + break; + case CRL_CACHE_INVALID: + err = gpg_error (GPG_ERR_CERT_REVOKED); + break; + case CRL_CACHE_DONTKNOW: + if (did_inquire) + err = gpg_error (GPG_ERR_NO_CRL_KNOWN); + else if (!(err = inquire_cert_and_load_crl (ctx))) + { + did_inquire = 1; + goto again; + } + break; + case CRL_CACHE_CANTUSE: + err = gpg_error (GPG_ERR_NO_CRL_KNOWN); + break; + default: + log_fatal ("crl_cache_isvalid returned invalid code\n"); + } + } + + xfree (issuerhash); + return leave_cmd (ctx, err); +} + + +/* If the line contains a SHA-1 fingerprint as the first argument, + return the FPR vuffer on success. The function checks that the + fingerprint consists of valid characters and prints and error + message if it does not and returns NULL. Fingerprints are + considered optional and thus no explicit error is returned. NULL is + also returned if there is no fingerprint at all available. + FPR must be a caller provided buffer of at least 20 bytes. + + Note that colons within the fingerprint are allowed to separate 2 + hex digits; this allows for easier cutting and pasting using the + usual fingerprint rendering. +*/ +static unsigned char * +get_fingerprint_from_line (const char *line, unsigned char *fpr) +{ + const char *s; + int i; + + for (s=line, i=0; *s && *s != ' '; s++ ) + { + if ( hexdigitp (s) && hexdigitp (s+1) ) + { + if ( i >= 20 ) + return NULL; /* Fingerprint too long. */ + fpr[i++] = xtoi_2 (s); + s++; + } + else if ( *s != ':' ) + return NULL; /* Invalid. */ + } + if ( i != 20 ) + return NULL; /* Fingerprint to short. */ + return fpr; +} + + + +static const char hlp_checkcrl[] = + "CHECKCRL [<fingerprint>]\n" + "\n" + "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" + "entire X.509 certificate blob) is valid or not by consulting the\n" + "CRL responsible for this certificate. If the fingerprint has not\n" + "been given or the certificate is not known, the function \n" + "inquires the certificate using an\n" + "\n" + " INQUIRE TARGETCERT\n" + "\n" + "and the caller is expected to return the certificate for the\n" + "request (which should match FINGERPRINT) as a binary blob.\n" + "Processing then takes place without further interaction; in\n" + "particular dirmngr tries to locate other required certificate by\n" + "its own mechanism which includes a local certificate store as well\n" + "as a list of trusted root certificates.\n" + "\n" + "The return value is the usual gpg-error code or 0 for ducesss;\n" + "i.e. the certificate validity has been confirmed by a valid CRL."; +static gpg_error_t +cmd_checkcrl (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + unsigned char fprbuffer[20], *fpr; + ksba_cert_t cert; + + fpr = get_fingerprint_from_line (line, fprbuffer); + cert = fpr? get_cert_byfpr (fpr) : NULL; + + if (!cert) + { + /* We do not have this certificate yet or the fingerprint has + not been given. Inquire it from the client. */ + unsigned char *value = NULL; + size_t valuelen; + + err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", + &value, &valuelen, MAX_CERT_LENGTH); + if (err) + { + log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (!valuelen) /* No data returned; return a comprehensible error. */ + err = gpg_error (GPG_ERR_MISSING_CERT); + else + { + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_init_from_mem (cert, value, valuelen); + } + xfree (value); + if(err) + goto leave; + } + + assert (cert); + + err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh); + if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN) + { + err = crl_cache_reload_crl (ctrl, cert); + if (!err) + err = crl_cache_cert_isvalid (ctrl, cert, 0); + } + + leave: + ksba_cert_release (cert); + return leave_cmd (ctx, err); +} + + +static const char hlp_checkocsp[] = + "CHECKOCSP [--force-default-responder] [<fingerprint>]\n" + "\n" + "Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n" + "entire X.509 certificate blob) is valid or not by asking an OCSP\n" + "responder responsible for this certificate. The optional\n" + "fingerprint may be used for a quick check in case an OCSP check has\n" + "been done for this certificate recently (we always cache OCSP\n" + "responses for a couple of minutes). If the fingerprint has not been\n" + "given or there is no cached result, the function inquires the\n" + "certificate using an\n" + "\n" + " INQUIRE TARGETCERT\n" + "\n" + "and the caller is expected to return the certificate for the\n" + "request (which should match FINGERPRINT) as a binary blob.\n" + "Processing then takes place without further interaction; in\n" + "particular dirmngr tries to locate other required certificates by\n" + "its own mechanism which includes a local certificate store as well\n" + "as a list of trusted root certificates.\n" + "\n" + "If the option --force-default-responder is given, only the default\n" + "OCSP responder will be used and any other methods of obtaining an\n" + "OCSP responder URL won't be used.\n" + "\n" + "The return value is the usual gpg-error code or 0 for ducesss;\n" + "i.e. the certificate validity has been confirmed by a valid CRL."; +static gpg_error_t +cmd_checkocsp (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + unsigned char fprbuffer[20], *fpr; + ksba_cert_t cert; + int force_default_responder; + + force_default_responder = has_option (line, "--force-default-responder"); + line = skip_options (line); + + fpr = get_fingerprint_from_line (line, fprbuffer); + cert = fpr? get_cert_byfpr (fpr) : NULL; + + if (!cert) + { + /* We do not have this certificate yet or the fingerprint has + not been given. Inquire it from the client. */ + unsigned char *value = NULL; + size_t valuelen; + + err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", + &value, &valuelen, MAX_CERT_LENGTH); + if (err) + { + log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (!valuelen) /* No data returned; return a comprehensible error. */ + err = gpg_error (GPG_ERR_MISSING_CERT); + else + { + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_init_from_mem (cert, value, valuelen); + } + xfree (value); + if(err) + goto leave; + } + + assert (cert); + + if (!opt.allow_ocsp) + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + else + err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder); + + leave: + ksba_cert_release (cert); + return leave_cmd (ctx, err); +} + + + +static int +lookup_cert_by_url (assuan_context_t ctx, const char *url) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + unsigned char *value = NULL; + size_t valuelen; + + /* Fetch single certificate given it's URL. */ + err = fetch_cert_by_url (ctrl, url, &value, &valuelen); + if (err) + { + log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + /* Send the data, flush the buffer and then send an END. */ + err = assuan_send_data (ctx, value, valuelen); + if (!err) + err = assuan_send_data (ctx, NULL, 0); + if (!err) + err = assuan_write_line (ctx, "END"); + if (err) + { + log_error (_("error sending data: %s\n"), gpg_strerror (err)); + goto leave; + } + + leave: + + return err; +} + + +/* Send the certificate, flush the buffer and then send an END. */ +static gpg_error_t +return_one_cert (void *opaque, ksba_cert_t cert) +{ + assuan_context_t ctx = opaque; + gpg_error_t err; + const unsigned char *der; + size_t derlen; + + der = ksba_cert_get_image (cert, &derlen); + if (!der) + err = gpg_error (GPG_ERR_INV_CERT_OBJ); + else + { + err = assuan_send_data (ctx, der, derlen); + if (!err) + err = assuan_send_data (ctx, NULL, 0); + if (!err) + err = assuan_write_line (ctx, "END"); + } + if (err) + log_error (_("error sending data: %s\n"), gpg_strerror (err)); + return err; +} + + +/* Lookup certificates from the internal cache or using the ldap + servers. */ +static int +lookup_cert_by_pattern (assuan_context_t ctx, char *line, + int single, int cache_only) +{ + gpg_error_t err = 0; + char *p; + strlist_t sl, list = NULL; + int truncated = 0, truncation_forced = 0; + int count = 0; + int local_count = 0; +#if USE_LDAP + ctrl_t ctrl = assuan_get_pointer (ctx); + unsigned char *value = NULL; + size_t valuelen; + struct ldapserver_iter ldapserver_iter; + cert_fetch_context_t fetch_context; +#endif /*USE_LDAP*/ + int any_no_data = 0; + + /* Break the line down into an STRLIST */ + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + err = gpg_error_from_errno (errno); + goto leave; + } + memset (sl, 0, sizeof *sl); + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + /* First look through the internal cache. The certificates returned + here are not counted towards the truncation limit. */ + if (single && !cache_only) + ; /* Do not read from the local cache in this case. */ + else + { + for (sl=list; sl; sl = sl->next) + { + err = get_certs_bypattern (sl->d, return_one_cert, ctx); + if (!err) + local_count++; + if (!err && single) + goto ready; + + if (gpg_err_code (err) == GPG_ERR_NO_DATA) + { + err = 0; + if (cache_only) + any_no_data = 1; + } + else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only) + { + /* No real fault because the internal pattern lookup + can't yet cope with all types of pattern. */ + err = 0; + } + if (err) + goto ready; + } + } + + /* Loop over all configured servers unless we want only the + certificates from the cache. */ +#if USE_LDAP + for (ldapserver_iter_begin (&ldapserver_iter, ctrl); + !cache_only && !ldapserver_iter_end_p (&ldapserver_iter) + && ldapserver_iter.server->host && !truncation_forced; + ldapserver_iter_next (&ldapserver_iter)) + { + ldap_server_t ldapserver = ldapserver_iter.server; + + if (DBG_LOOKUP) + log_debug ("cmd_lookup: trying %s:%d base=%s\n", + ldapserver->host, ldapserver->port, + ldapserver->base?ldapserver->base : "[default]"); + + /* Fetch certificates matching pattern */ + err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver); + if ( gpg_err_code (err) == GPG_ERR_NO_DATA ) + { + if (DBG_LOOKUP) + log_debug ("cmd_lookup: no data\n"); + err = 0; + any_no_data = 1; + continue; + } + if (err) + { + log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + /* Fetch the certificates for this query. */ + while (!truncation_forced) + { + xfree (value); value = NULL; + err = fetch_next_cert (fetch_context, &value, &valuelen); + if (gpg_err_code (err) == GPG_ERR_NO_DATA ) + { + err = 0; + any_no_data = 1; + break; /* Ready. */ + } + if (gpg_err_code (err) == GPG_ERR_TRUNCATED) + { + truncated = 1; + err = 0; + break; /* Ready. */ + } + if (gpg_err_code (err) == GPG_ERR_EOF) + { + err = 0; + break; /* Ready. */ + } + if (!err && !value) + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + if (err) + { + log_error (_("fetch_next_cert failed: %s\n"), + gpg_strerror (err)); + end_cert_fetch (fetch_context); + goto leave; + } + + if (DBG_LOOKUP) + log_debug ("cmd_lookup: returning one cert%s\n", + truncated? " (truncated)":""); + + /* Send the data, flush the buffer and then send an END line + as a certificate delimiter. */ + err = assuan_send_data (ctx, value, valuelen); + if (!err) + err = assuan_send_data (ctx, NULL, 0); + if (!err) + err = assuan_write_line (ctx, "END"); + if (err) + { + log_error (_("error sending data: %s\n"), gpg_strerror (err)); + end_cert_fetch (fetch_context); + goto leave; + } + + if (++count >= opt.max_replies ) + { + truncation_forced = 1; + log_info (_("max_replies %d exceeded\n"), opt.max_replies ); + } + if (single) + break; + } + + end_cert_fetch (fetch_context); + } +#endif /*USE_LDAP*/ + + ready: + if (truncated || truncation_forced) + { + char str[50]; + + sprintf (str, "%d", count); + assuan_write_status (ctx, "TRUNCATED", str); + } + + if (!err && !count && !local_count && any_no_data) + err = gpg_error (GPG_ERR_NO_DATA); + + leave: + free_strlist (list); + return err; +} + + +static const char hlp_lookup[] = + "LOOKUP [--url] [--single] [--cache-only] <pattern>\n" + "\n" + "Lookup certificates matching PATTERN. With --url the pattern is\n" + "expected to be one URL.\n" + "\n" + "If --url is not given: To allow for multiple patterns (which are ORed)\n" + "quoting is required: Spaces are translated to \"+\" or \"%20\";\n" + "obviously this requires that the usual escape quoting rules are applied.\n" + "\n" + "If --url is given no special escaping is required because URLs are\n" + "already escaped this way.\n" + "\n" + "If --single is given the first and only the first match will be\n" + "returned. If --cache-only is _not_ given, no local query will be\n" + "done.\n" + "\n" + "If --cache-only is given no external lookup is done so that only\n" + "certificates from the cache may get returned."; +static gpg_error_t +cmd_lookup (assuan_context_t ctx, char *line) +{ + gpg_error_t err; + int lookup_url, single, cache_only; + + lookup_url = has_leading_option (line, "--url"); + single = has_leading_option (line, "--single"); + cache_only = has_leading_option (line, "--cache-only"); + line = skip_options (line); + + if (lookup_url && cache_only) + err = gpg_error (GPG_ERR_NOT_FOUND); + else if (lookup_url && single) + err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); + else if (lookup_url) + err = lookup_cert_by_url (ctx, line); + else + err = lookup_cert_by_pattern (ctx, line, single, cache_only); + + return leave_cmd (ctx, err); +} + + +static const char hlp_loadcrl[] = + "LOADCRL [--url] <filename|url>\n" + "\n" + "Load the CRL in the file with name FILENAME into our cache. Note\n" + "that FILENAME should be given with an absolute path because\n" + "Dirmngrs cwd is not known. With --url the CRL is directly loaded\n" + "from the given URL.\n" + "\n" + "This command is usually used by gpgsm using the invocation \"gpgsm\n" + "--call-dirmngr loadcrl <filename>\". A direct invocation of Dirmngr\n" + "is not useful because gpgsm might need to callback gpgsm to ask for\n" + "the CA's certificate."; +static gpg_error_t +cmd_loadcrl (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + int use_url = has_leading_option (line, "--url"); + + line = skip_options (line); + + if (use_url) + { + ksba_reader_t reader; + + err = crl_fetch (ctrl, line, &reader); + if (err) + log_error (_("fetching CRL from '%s' failed: %s\n"), + line, gpg_strerror (err)); + else + { + err = crl_cache_insert (ctrl, line, reader); + if (err) + log_error (_("processing CRL from '%s' failed: %s\n"), + line, gpg_strerror (err)); + crl_close_reader (reader); + } + } + else + { + char *buf; + + buf = xtrymalloc (strlen (line)+1); + if (!buf) + err = gpg_error_from_syserror (); + else + { + strcpy_escaped_plus (buf, line); + err = crl_cache_load (ctrl, buf); + xfree (buf); + } + } + + return leave_cmd (ctx, err); +} + + +static const char hlp_listcrls[] = + "LISTCRLS\n" + "\n" + "List the content of all CRLs in a readable format. This command is\n" + "usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n" + "listcrls\". It may also be used directly using \"dirmngr\n" + "--list-crls\"."; +static gpg_error_t +cmd_listcrls (assuan_context_t ctx, char *line) +{ + gpg_error_t err; + estream_t fp; + + (void)line; + + fp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!fp) + err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); + else + { + err = crl_cache_list (fp); + es_fclose (fp); + } + return leave_cmd (ctx, err); +} + + +static const char hlp_cachecert[] = + "CACHECERT\n" + "\n" + "Put a certificate into the internal cache. This command might be\n" + "useful if a client knows in advance certificates required for a\n" + "test and wants to make sure they get added to the internal cache.\n" + "It is also helpful for debugging. To get the actual certificate,\n" + "this command immediately inquires it using\n" + "\n" + " INQUIRE TARGETCERT\n" + "\n" + "and the caller is expected to return the certificate for the\n" + "request as a binary blob."; +static gpg_error_t +cmd_cachecert (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + ksba_cert_t cert = NULL; + unsigned char *value = NULL; + size_t valuelen; + + (void)line; + + err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT", + &value, &valuelen, MAX_CERT_LENGTH); + if (err) + { + log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (!valuelen) /* No data returned; return a comprehensible error. */ + err = gpg_error (GPG_ERR_MISSING_CERT); + else + { + err = ksba_cert_new (&cert); + if (!err) + err = ksba_cert_init_from_mem (cert, value, valuelen); + } + xfree (value); + if(err) + goto leave; + + err = cache_cert (cert); + + leave: + ksba_cert_release (cert); + return leave_cmd (ctx, err); +} + + +static const char hlp_validate[] = + "VALIDATE [--systrust] [--tls] [--no-crl]\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. The option --tls modifies this by asking\n" + "for list of certificates with\n" + "\n" + " INQUIRE CERTLIST\n" + "\n" + "Here the first certificate is the target certificate, the remaining\n" + "certificates are suggested intermediary certificates. All certificates\n" + "need to be PEM encoded.\n" + "\n" + "The option --systrust changes the behaviour to include the system\n" + "provided root certificates as trust anchors. The option --no-crl\n" + "skips CRL checks"; +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; + certlist_t certlist = NULL; + unsigned char *value = NULL; + size_t valuelen; + int systrust_mode, tls_mode, no_crl; + + systrust_mode = has_option (line, "--systrust"); + tls_mode = has_option (line, "--tls"); + no_crl = has_option (line, "--no-crl"); + line = skip_options (line); + + if (tls_mode) + err = assuan_inquire (ctrl->server_local->assuan_ctx, "CERTLIST", + &value, &valuelen, MAX_CERTLIST_LENGTH); + else + 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 if (tls_mode) + { + estream_t fp; + + fp = es_fopenmem_init (0, "rb", value, valuelen); + if (!fp) + err = gpg_error_from_syserror (); + else + { + err = read_certlist_from_stream (&certlist, fp); + es_fclose (fp); + if (!err && !certlist) + err = gpg_error (GPG_ERR_MISSING_CERT); + if (!err) + { + /* Extract the first certificate from the list. */ + cert = certlist->cert; + ksba_cert_ref (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 (!tls_mode) + { + /* If we have this certificate already in our cache, use the + * cached version for validation because this will take care of + * any cached results. We don't need to do this in tls mode + * because this has already been done for certificate in a + * certlist_t. */ + 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; + } + } + + /* Quick hack to make verification work by inserting the supplied + * certs into the cache. */ + if (tls_mode && certlist) + { + certlist_t cl; + + for (cl = certlist->next; cl; cl = cl->next) + cache_cert (cl->cert); + } + + err = validate_cert_chain (ctrl, cert, NULL, + (VALIDATE_FLAG_TRUST_CONFIG + | (tls_mode ? VALIDATE_FLAG_TLS : 0) + | (systrust_mode ? VALIDATE_FLAG_TRUST_SYSTEM : 0) + | (no_crl ? VALIDATE_FLAG_NOCRLCHECK : 0)), + NULL); + + leave: + ksba_cert_release (cert); + release_certlist (certlist); + return leave_cmd (ctx, err); +} + + + +/* Parse an keyserver URI and store it in a new uri item which is + returned at R_ITEM. On error return an error code. */ +static gpg_error_t +make_keyserver_item (const char *uri, uri_item_t *r_item) +{ + gpg_error_t err; + uri_item_t item; + + *r_item = NULL; + + /* We used to have DNS CNAME redirection from the URLs below to + * sks-keyserver. pools. The idea was to allow for a quick way to + * switch to a different set of pools. The problem with that + * approach is that TLS needs to verify the hostname and - because + * DNS is not secured - it can only check the user supplied hostname + * and not a hostname from a CNAME RR. Thus the final server all + * need to have certificates with the actual pool name as well as + * for keys.gnupg.net - that would render the advantage of + * keys.gnupg.net useless and so we better give up on this. Because + * the keys.gnupg.net URL are still in widespread use we do a static + * mapping here. + */ + if (!strcmp (uri, "hkps://keys.gnupg.net") + || !strcmp (uri, "keys.gnupg.net")) + uri = "hkps://hkps.pool.sks-keyservers.net"; + else if (!strcmp (uri, "https://keys.gnupg.net")) + uri = "https://hkps.pool.sks-keyservers.net"; + else if (!strcmp (uri, "hkp://keys.gnupg.net")) + uri = "hkp://hkps.pool.sks-keyservers.net"; + else if (!strcmp (uri, "http://keys.gnupg.net")) + uri = "http://hkps.pool.sks-keyservers.net"; + else if (!strcmp (uri, "hkps://http-keys.gnupg.net") + || !strcmp (uri, "http-keys.gnupg.net")) + uri = "hkps://ha.pool.sks-keyservers.net"; + else if (!strcmp (uri, "https://http-keys.gnupg.net")) + uri = "https://ha.pool.sks-keyservers.net"; + else if (!strcmp (uri, "hkp://http-keys.gnupg.net")) + uri = "hkp://ha.pool.sks-keyservers.net"; + else if (!strcmp (uri, "http://http-keys.gnupg.net")) + uri = "http://ha.pool.sks-keyservers.net"; + + item = xtrymalloc (sizeof *item + strlen (uri)); + if (!item) + return gpg_error_from_syserror (); + + item->next = NULL; + item->parsed_uri = NULL; + strcpy (item->uri, uri); + +#if USE_LDAP + if (ldap_uri_p (item->uri)) + err = ldap_parse_uri (&item->parsed_uri, uri); + else +#endif + { + err = http_parse_uri (&item->parsed_uri, uri, 1); + } + + if (err) + xfree (item); + else + *r_item = item; + return err; +} + + +/* If no keyserver is stored in CTRL but a global keyserver has been + set, put that global keyserver into CTRL. We need use this + function to help migrate from the old gpg based keyserver + configuration to the new dirmngr based configuration. */ +static gpg_error_t +ensure_keyserver (ctrl_t ctrl) +{ + gpg_error_t err; + uri_item_t item; + uri_item_t onion_items = NULL; + uri_item_t plain_items = NULL; + uri_item_t ui; + strlist_t sl; + + if (ctrl->server_local->keyservers) + return 0; /* Already set for this session. */ + if (!opt.keyserver) + { + /* No global option set. Fall back to default: */ + return make_keyserver_item (DIRMNGR_DEFAULT_KEYSERVER, + &ctrl->server_local->keyservers); + } + + for (sl = opt.keyserver; sl; sl = sl->next) + { + err = make_keyserver_item (sl->d, &item); + if (err) + goto leave; + if (item->parsed_uri->onion) + { + item->next = onion_items; + onion_items = item; + } + else + { + item->next = plain_items; + plain_items = item; + } + } + + /* Decide which to use. Note that the session has no keyservers + yet set. */ + if (onion_items && !onion_items->next && plain_items && !plain_items->next) + { + /* If there is just one onion and one plain keyserver given, we take + only one depending on whether Tor is running or not. */ + if (is_tor_running (ctrl)) + { + ctrl->server_local->keyservers = onion_items; + onion_items = NULL; + } + else + { + ctrl->server_local->keyservers = plain_items; + plain_items = NULL; + } + } + else if (!is_tor_running (ctrl)) + { + /* Tor is not running. It does not make sense to add Onion + addresses. */ + ctrl->server_local->keyservers = plain_items; + plain_items = NULL; + } + else + { + /* In all other cases add all keyservers. */ + ctrl->server_local->keyservers = onion_items; + onion_items = NULL; + for (ui = ctrl->server_local->keyservers; ui && ui->next; ui = ui->next) + ; + if (ui) + ui->next = plain_items; + else + ctrl->server_local->keyservers = plain_items; + plain_items = NULL; + } + + leave: + release_uri_item_list (onion_items); + release_uri_item_list (plain_items); + + return err; +} + + +static const char hlp_keyserver[] = + "KEYSERVER [<options>] [<uri>|<host>]\n" + "Options are:\n" + " --help\n" + " --clear Remove all configured keyservers\n" + " --resolve Resolve HKP host names and rotate\n" + " --hosttable Print table of known hosts and pools\n" + " --dead Mark <host> as dead\n" + " --alive Mark <host> as alive\n" + "\n" + "If called without arguments list all configured keyserver URLs.\n" + "If called with an URI add this as keyserver. Note that keyservers\n" + "are configured on a per-session base. A default keyserver may already be\n" + "present, thus the \"--clear\" option must be used to get full control.\n" + "If \"--clear\" and an URI are used together the clear command is\n" + "obviously executed first. A RESET command does not change the list\n" + "of configured keyservers."; +static gpg_error_t +cmd_keyserver (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + int clear_flag, add_flag, help_flag, host_flag, resolve_flag; + int dead_flag, alive_flag; + uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it + is always initialized. */ + + clear_flag = has_option (line, "--clear"); + help_flag = has_option (line, "--help"); + resolve_flag = has_option (line, "--resolve"); + host_flag = has_option (line, "--hosttable"); + dead_flag = has_option (line, "--dead"); + alive_flag = has_option (line, "--alive"); + line = skip_options (line); + add_flag = !!*line; + + if (help_flag) + { + err = ks_action_help (ctrl, line); + goto leave; + } + + if (resolve_flag) + { + err = ensure_keyserver (ctrl); + if (err) + { + assuan_set_error (ctx, err, + "Bad keyserver configuration in dirmngr.conf"); + goto leave; + } + err = ks_action_resolve (ctrl, ctrl->server_local->keyservers); + if (err) + goto leave; + } + + if (alive_flag && dead_flag) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies"); + goto leave; + } + if (dead_flag) + { + err = check_owner_permission (ctx, "no permission to use --dead"); + if (err) + goto leave; + } + if (alive_flag || dead_flag) + { + if (!*line) + { + err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing"); + goto leave; + } + + err = ks_hkp_mark_host (ctrl, line, alive_flag); + if (err) + goto leave; + } + + if (host_flag) + { + err = ks_hkp_print_hosttable (ctrl); + if (err) + goto leave; + } + if (resolve_flag || host_flag || alive_flag || dead_flag) + goto leave; + + if (add_flag) + { + err = make_keyserver_item (line, &item); + if (err) + goto leave; + } + if (clear_flag) + release_ctrl_keyservers (ctrl); + if (add_flag) + { + item->next = ctrl->server_local->keyservers; + ctrl->server_local->keyservers = item; + } + + if (!add_flag && !clear_flag && !help_flag) + { + /* List configured keyservers. However, we first add a global + keyserver. */ + uri_item_t u; + + err = ensure_keyserver (ctrl); + if (err) + { + assuan_set_error (ctx, err, + "Bad keyserver configuration in dirmngr.conf"); + goto leave; + } + + for (u=ctrl->server_local->keyservers; u; u = u->next) + dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL); + } + err = 0; + + leave: + return leave_cmd (ctx, err); +} + + + +static const char hlp_ks_search[] = + "KS_SEARCH {<pattern>}\n" + "\n" + "Search the configured OpenPGP keyservers (see command KEYSERVER)\n" + "for keys matching PATTERN"; +static gpg_error_t +cmd_ks_search (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + strlist_t list, sl; + char *p; + estream_t outfp; + + if (has_option (line, "--quick")) + ctrl->timeout = opt.connect_quick_timeout; + line = skip_options (line); + + /* Break the line down into an strlist. Each pattern is + percent-plus escaped. */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + err = gpg_error_from_syserror (); + goto leave; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + err = ensure_keyserver (ctrl); + if (err) + goto leave; + + /* Setup an output stream and perform the search. */ + outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!outfp) + err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); + else + { + err = ks_action_search (ctrl, ctrl->server_local->keyservers, + list, outfp); + es_fclose (outfp); + } + + leave: + free_strlist (list); + return leave_cmd (ctx, err); +} + + + +static const char hlp_ks_get[] = + "KS_GET {<pattern>}\n" + "\n" + "Get the keys matching PATTERN from the configured OpenPGP keyservers\n" + "(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n" + "or an exact name indicated by the '=' prefix."; +static gpg_error_t +cmd_ks_get (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + strlist_t list, sl; + char *p; + estream_t outfp; + + if (has_option (line, "--quick")) + ctrl->timeout = opt.connect_quick_timeout; + line = skip_options (line); + + /* Break the line into a strlist. Each pattern is by + definition percent-plus escaped. However we only support keyids + and fingerprints and thus the client has no need to apply the + escaping. */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + err = gpg_error_from_syserror (); + goto leave; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + err = ensure_keyserver (ctrl); + if (err) + goto leave; + + /* Setup an output stream and perform the get. */ + outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!outfp) + err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); + else + { + ctrl->server_local->inhibit_data_logging = 1; + ctrl->server_local->inhibit_data_logging_now = 0; + ctrl->server_local->inhibit_data_logging_count = 0; + err = ks_action_get (ctrl, ctrl->server_local->keyservers, list, outfp); + es_fclose (outfp); + ctrl->server_local->inhibit_data_logging = 0; + } + + leave: + free_strlist (list); + return leave_cmd (ctx, err); +} + + +static const char hlp_ks_fetch[] = + "KS_FETCH <URL>\n" + "\n" + "Get the key(s) from URL."; +static gpg_error_t +cmd_ks_fetch (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + estream_t outfp; + + if (has_option (line, "--quick")) + ctrl->timeout = opt.connect_quick_timeout; + line = skip_options (line); + + err = ensure_keyserver (ctrl); /* FIXME: Why do we needs this here? */ + if (err) + goto leave; + + /* Setup an output stream and perform the get. */ + outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!outfp) + err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); + else + { + ctrl->server_local->inhibit_data_logging = 1; + ctrl->server_local->inhibit_data_logging_now = 0; + ctrl->server_local->inhibit_data_logging_count = 0; + err = ks_action_fetch (ctrl, line, outfp); + es_fclose (outfp); + ctrl->server_local->inhibit_data_logging = 0; + } + + leave: + return leave_cmd (ctx, err); +} + + + +static const char hlp_ks_put[] = + "KS_PUT\n" + "\n" + "Send a key to the configured OpenPGP keyservers. The actual key material\n" + "is then requested by Dirmngr using\n" + "\n" + " INQUIRE KEYBLOCK\n" + "\n" + "The client shall respond with a binary version of the keyblock (e.g.,\n" + "the output of `gpg --export KEYID'). For LDAP\n" + "keyservers Dirmngr may ask for meta information of the provided keyblock\n" + "using:\n" + "\n" + " INQUIRE KEYBLOCK_INFO\n" + "\n" + "The client shall respond with a colon delimited info lines (the output\n" + "of 'gpg --list-keys --with-colons KEYID').\n"; +static gpg_error_t +cmd_ks_put (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + unsigned char *value = NULL; + size_t valuelen; + unsigned char *info = NULL; + size_t infolen; + + /* No options for now. */ + line = skip_options (line); + + err = ensure_keyserver (ctrl); + if (err) + goto leave; + + /* Ask for the key material. */ + err = assuan_inquire (ctx, "KEYBLOCK", + &value, &valuelen, MAX_KEYBLOCK_LENGTH); + if (err) + { + log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (!valuelen) /* No data returned; return a comprehensible error. */ + { + err = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + /* Ask for the key meta data. */ + err = assuan_inquire (ctx, "KEYBLOCK_INFO", + &info, &infolen, MAX_KEYBLOCK_LENGTH); + if (err) + { + log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + /* Send the key. */ + err = ks_action_put (ctrl, ctrl->server_local->keyservers, + value, valuelen, info, infolen); + + leave: + xfree (info); + xfree (value); + return leave_cmd (ctx, err); +} + + + +static const char hlp_loadswdb[] = + "LOADSWDB [--force]\n" + "\n" + "Load and verify the swdb.lst from the Net."; +static gpg_error_t +cmd_loadswdb (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + + err = dirmngr_load_swdb (ctrl, has_option (line, "--force")); + + return leave_cmd (ctx, err); +} + + + +static const char hlp_getinfo[] = + "GETINFO <what>\n" + "\n" + "Multi purpose command to return certain information. \n" + "Supported values of WHAT are:\n" + "\n" + "version - Return the version of the program.\n" + "pid - Return the process id of the server.\n" + "tor - Return OK if running in Tor mode\n" + "dnsinfo - Return info about the DNS resolver\n" + "socket_name - Return the name of the socket.\n" + "session_id - Return the current session_id.\n" + "workqueue - Inspect the work queue\n" + "getenv NAME - Return value of envvar NAME\n"; +static gpg_error_t +cmd_getinfo (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + char numbuf[50]; + + if (!strcmp (line, "version")) + { + const char *s = VERSION; + err = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "pid")) + { + snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); + err = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else if (!strcmp (line, "socket_name")) + { + const char *s = dirmngr_get_current_socket_name (); + err = assuan_send_data (ctx, s, strlen (s)); + } + else if (!strcmp (line, "session_id")) + { + snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id); + err = assuan_send_data (ctx, numbuf, strlen (numbuf)); + } + else if (!strcmp (line, "tor")) + { + int use_tor; + + use_tor = dirmngr_use_tor (); + if (use_tor) + { + if (!is_tor_running (ctrl)) + err = assuan_write_status (ctx, "NO_TOR", "Tor not running"); + else + err = 0; + if (!err) + assuan_set_okay_line (ctx, use_tor == 1 ? "- Tor mode is enabled" + /**/ : "- Tor mode is enforced"); + } + else + err = set_error (GPG_ERR_FALSE, "Tor mode is NOT enabled"); + } + else if (!strcmp (line, "dnsinfo")) + { + if (standard_resolver_p ()) + assuan_set_okay_line + (ctx, "- Forced use of System resolver (w/o Tor support)"); + else + { +#ifdef USE_LIBDNS + assuan_set_okay_line (ctx, (recursive_resolver_p () + ? "- Libdns recursive resolver" + : "- Libdns stub resolver")); +#else + assuan_set_okay_line (ctx, "- System resolver (w/o Tor support)"); +#endif + } + err = 0; + } + else if (!strcmp (line, "workqueue")) + { + workqueue_dump_queue (ctrl); + err = 0; + } + else if (!strncmp (line, "getenv", 6) + && (line[6] == ' ' || line[6] == '\t' || !line[6])) + { + line += 6; + while (*line == ' ' || *line == '\t') + line++; + if (!*line) + err = gpg_error (GPG_ERR_MISSING_VALUE); + else + { + const char *s = getenv (line); + if (!s) + err = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); + else + err = assuan_send_data (ctx, s, strlen (s)); + } + } + else + err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); + + return leave_cmd (ctx, err); +} + + + +static const char hlp_killdirmngr[] = + "KILLDIRMNGR\n" + "\n" + "This command allows a user - given sufficient permissions -\n" + "to kill this dirmngr process.\n"; +static gpg_error_t +cmd_killdirmngr (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)line; + + ctrl->server_local->stopme = 1; + assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); + return 0; +} + + +static const char hlp_reloaddirmngr[] = + "RELOADDIRMNGR\n" + "\n" + "This command is an alternative to SIGHUP\n" + "to reload the configuration."; +static gpg_error_t +cmd_reloaddirmngr (assuan_context_t ctx, char *line) +{ + (void)ctx; + (void)line; + + dirmngr_sighup_action (); + return 0; +} + + +static const char hlp_flushcrls[] = + "FLUSHCRLS\n" + "\n" + "Remove all cached CRLs from memory and\n" + "the file system."; +static gpg_error_t +cmd_flushcrls (assuan_context_t ctx, char *line) +{ + (void)line; + + return leave_cmd (ctx, crl_cache_flush () ? GPG_ERR_GENERAL : 0); +} + + + +/* Tell the assuan library about our commands. */ +static int +register_commands (assuan_context_t ctx) +{ + static struct { + const char *name; + assuan_handler_t handler; + const char * const help; + } table[] = { + { "DNS_CERT", cmd_dns_cert, hlp_dns_cert }, + { "WKD_GET", cmd_wkd_get, hlp_wkd_get }, + { "LDAPSERVER", cmd_ldapserver, hlp_ldapserver }, + { "ISVALID", cmd_isvalid, hlp_isvalid }, + { "CHECKCRL", cmd_checkcrl, hlp_checkcrl }, + { "CHECKOCSP", cmd_checkocsp, hlp_checkocsp }, + { "LOOKUP", cmd_lookup, hlp_lookup }, + { "LOADCRL", cmd_loadcrl, hlp_loadcrl }, + { "LISTCRLS", cmd_listcrls, hlp_listcrls }, + { "CACHECERT", cmd_cachecert, hlp_cachecert }, + { "VALIDATE", cmd_validate, hlp_validate }, + { "KEYSERVER", cmd_keyserver, hlp_keyserver }, + { "KS_SEARCH", cmd_ks_search, hlp_ks_search }, + { "KS_GET", cmd_ks_get, hlp_ks_get }, + { "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch }, + { "KS_PUT", cmd_ks_put, hlp_ks_put }, + { "GETINFO", cmd_getinfo, hlp_getinfo }, + { "LOADSWDB", cmd_loadswdb, hlp_loadswdb }, + { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, + { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr }, + { "FLUSHCRLS", cmd_flushcrls, hlp_flushcrls }, + { NULL, NULL } + }; + int i, j, rc; + + for (i=j=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler, + table[i].help); + if (rc) + return rc; + } + return 0; +} + + +/* Note that we do not reset the list of configured keyservers. */ +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + (void)line; + +#if USE_LDAP + ldapserver_list_free (ctrl->server_local->ldapservers); +#endif /*USE_LDAP*/ + ctrl->server_local->ldapservers = NULL; + return 0; +} + + +/* This function is called by our assuan log handler to test whether a + * log message shall really be printed. The function must return + * false to inhibit the logging of MSG. CAT gives the requested log + * category. MSG might be NULL. */ +int +dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, + const char *msg) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)cat; + (void)msg; + + if (!ctrl || !ctrl->server_local) + return 1; /* Can't decide - allow logging. */ + + if (!ctrl->server_local->inhibit_data_logging) + return 1; /* Not requested - allow logging. */ + + /* Disallow logging if *_now is true. */ + return !ctrl->server_local->inhibit_data_logging_now; +} + + +/* Startup the server and run the main command loop. With FD = -1, + * use stdin/stdout. SESSION_ID is either 0 or a unique number + * identifying a session. */ +void +start_command_handler (assuan_fd_t fd, unsigned int session_id) +{ + 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) + { + hello_line = xtryasprintf + ("Home: %s\n" + "Config: %s\n" + "%s", + gnupg_homedir (), + opt.config_filename? opt.config_filename : "[none]", + hello); + } + + 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); + + ctrl->server_local->session_id = session_id; + + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + break; + if (rc) + { + log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); + break; + } + +#ifndef HAVE_W32_SYSTEM + if (opt.verbose) + { + assuan_peercred_t peercred; + + if (!assuan_get_peercred (ctx, &peercred)) + log_info ("connection from process %ld (%ld:%ld)\n", + (long)peercred->pid, (long)peercred->uid, + (long)peercred->gid); + } +#endif + + rc = assuan_process (ctx); + if (rc) + { + log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); + continue; + } + } + + +#if USE_LDAP + ldap_wrapper_connection_cleanup (ctrl); + + ldapserver_list_free (ctrl->server_local->ldapservers); +#endif /*USE_LDAP*/ + ctrl->server_local->ldapservers = NULL; + + release_ctrl_keyservers (ctrl); + + ctrl->server_local->assuan_ctx = NULL; + assuan_release (ctx); + + if (ctrl->server_local->stopme) + dirmngr_exit (0); + + if (ctrl->refcount) + log_error ("oops: connection control structure still referenced (%d)\n", + ctrl->refcount); + else + { + release_ctrl_ocsp_certs (ctrl); + xfree (ctrl->server_local); + dirmngr_deinit_default_ctrl (ctrl); + xfree (ctrl); + } +} + + +/* Send a status line back to the client. KEYWORD is the status + keyword, the optional string arguments are blank separated added to + the line, the last argument must be a NULL. */ +gpg_error_t +dirmngr_status (ctrl_t ctrl, const char *keyword, ...) +{ + gpg_error_t err = 0; + va_list arg_ptr; + assuan_context_t ctx; + + va_start (arg_ptr, keyword); + + if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx)) + { + err = vprint_assuan_status_strings (ctx, keyword, arg_ptr); + } + + va_end (arg_ptr); + return err; +} + + +/* Print a help status line. The function splits text at LFs. */ +gpg_error_t +dirmngr_status_help (ctrl_t ctrl, const char *text) +{ + gpg_error_t err = 0; + assuan_context_t ctx; + + if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx)) + { + char buf[950], *p; + size_t n; + + do + { + p = buf; + n = 0; + for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++) + *p++ = *text++; + if (*text == '\n') + text++; + *p = 0; + err = assuan_write_status (ctx, "#", buf); + } + while (!err && *text); + } + + return err; +} + + +/* Print a help status line using a printf like format. The function + * splits text at LFs. */ +gpg_error_t +dirmngr_status_helpf (ctrl_t ctrl, const char *format, ...) +{ + va_list arg_ptr; + gpg_error_t err; + char *buf; + + va_start (arg_ptr, format); + buf = es_vbsprintf (format, arg_ptr); + err = buf? 0 : gpg_error_from_syserror (); + va_end (arg_ptr); + if (!err) + err = dirmngr_status_help (ctrl, buf); + es_free (buf); + return err; +} + + +/* This function is similar to print_assuan_status but takes a CTRL + * arg instead of an assuan context as first argument. */ +gpg_error_t +dirmngr_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, ...) +{ + gpg_error_t err; + va_list arg_ptr; + assuan_context_t ctx; + + if (!ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx)) + return 0; + + va_start (arg_ptr, format); + err = vprint_assuan_status (ctx, keyword, format, arg_ptr); + va_end (arg_ptr); + return err; +} + + +/* Send a tick progress indicator back. Fixme: This is only done for + the currently active channel. */ +gpg_error_t +dirmngr_tick (ctrl_t ctrl) +{ + static time_t next_tick = 0; + gpg_error_t err = 0; + time_t now = time (NULL); + + if (!next_tick) + { + next_tick = now + 1; + } + else if ( now > next_tick ) + { + if (ctrl) + { + err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL); + if (err) + { + /* Take this as in indication for a cancel request. */ + err = gpg_error (GPG_ERR_CANCELED); + } + now = time (NULL); + } + + next_tick = now + 1; + } + return err; +} diff --git a/dirmngr/sks-keyservers.netCA.pem b/dirmngr/sks-keyservers.netCA.pem new file mode 100644 index 0000000..24a2ad2 --- /dev/null +++ b/dirmngr/sks-keyservers.netCA.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFizCCA3OgAwIBAgIJAK9zyLTPn4CPMA0GCSqGSIb3DQEBBQUAMFwxCzAJBgNV +BAYTAk5PMQ0wCwYDVQQIDARPc2xvMR4wHAYDVQQKDBVza3Mta2V5c2VydmVycy5u +ZXQgQ0ExHjAcBgNVBAMMFXNrcy1rZXlzZXJ2ZXJzLm5ldCBDQTAeFw0xMjEwMDkw +MDMzMzdaFw0yMjEwMDcwMDMzMzdaMFwxCzAJBgNVBAYTAk5PMQ0wCwYDVQQIDARP +c2xvMR4wHAYDVQQKDBVza3Mta2V5c2VydmVycy5uZXQgQ0ExHjAcBgNVBAMMFXNr +cy1rZXlzZXJ2ZXJzLm5ldCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +ggIBANdsWy4PXWNUCkS3L//nrd0GqN3dVwoBGZ6w94Tw2jPDPifegwxQozFXkG6I +6A4TK1CJLXPvfz0UP0aBYyPmTNadDinaB9T4jIwd4rnxl+59GiEmqkN3IfPsv5Jj +MkKUmJnvOT0DEVlEaO1UZIwx5WpfprB3mR81/qm4XkAgmYrmgnLXd/pJDAMk7y1F +45b5zWofiD5l677lplcIPRbFhpJ6kDTODXh/XEdtF71EAeaOdEGOvyGDmCO0GWqS +FDkMMPTlieLA/0rgFTcz4xwUYj/cD5e0ZBuSkYsYFAU3hd1cGfBue0cPZaQH2HYx +Qk4zXD8S3F4690fRhr+tki5gyG6JDR67aKp3BIGLqm7f45WkX1hYp+YXywmEziM4 +aSbGYhx8hoFGfq9UcfPEvp2aoc8u5sdqjDslhyUzM1v3m3ZGbhwEOnVjljY6JJLx +MxagxnZZSAY424ZZ3t71E/Mn27dm2w+xFRuoy8JEjv1d+BT3eChM5KaNwrj0IO/y +u8kFIgWYA1vZ/15qMT+tyJTfyrNVV/7Df7TNeWyNqjJ5rBmt0M6NpHG7CrUSkBy9 +p8JhimgjP5r0FlEkgg+lyD+V79H98gQfVgP3pbJICz0SpBQf2F/2tyS4rLm+49rP +fcOajiXEuyhpcmzgusAj/1FjrtlynH1r9mnNaX4e+rLWzvU5AgMBAAGjUDBOMB0G +A1UdDgQWBBTkwyoJFGfYTVISTpM8E+igjdq28zAfBgNVHSMEGDAWgBTkwyoJFGfY +TVISTpM8E+igjdq28zAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAR +OXnYwu3g1ZjHyley3fZI5aLPsaE17cOImVTehC8DcIphm2HOMR/hYTTL+V0G4P+u +gH+6xeRLKSHMHZTtSBIa6GDL03434y9CBuwGvAFCMU2GV8w92/Z7apkAhdLToZA/ +X/iWP2jeaVJhxgEcH8uPrnSlqoPBcKC9PrgUzQYfSZJkLmB+3jEa3HKruy1abJP5 +gAdQvwvcPpvYRnIzUc9fZODsVmlHVFBCl2dlu/iHh2h4GmL4Da2rRkUMlbVTdioB +UYIvMycdOkpH5wJftzw7cpjsudGas0PARDXCFfGyKhwBRFY7Xp7lbjtU5Rz0Gc04 +lPrhDf0pFE98Aw4jJRpFeWMjpXUEaG1cq7D641RpgcMfPFvOHY47rvDTS7XJOaUT +BwRjmDt896s6vMDcaG/uXJbQjuzmmx3W2Idyh3s5SI0GTHb0IwMKYb4eBUIpQOnB +cE77VnCYqKvN1NVYAqhWjXbY7XasZvszCRcOG+W3FqNaHOK/n/0ueb0uijdLan+U +f4p1bjbAox8eAOQS/8a3bzkJzdyBNUKGx1BIK2IBL9bn/HravSDOiNRSnZ/R3l9G +ZauX0tu7IIDlRCILXSyeazu0aj/vdT3YFQXPcvt5Fkf5wiNTo53f72/jYEJd6qph +WrpoKqrwGwTpRUCMhYIUt65hsTxCiJJ5nKe39h46sg== +-----END CERTIFICATE----- diff --git a/dirmngr/t-dns-stuff.c b/dirmngr/t-dns-stuff.c new file mode 100644 index 0000000..5a3ede1 --- /dev/null +++ b/dirmngr/t-dns-stuff.c @@ -0,0 +1,313 @@ +/* t-dns-cert.c - Module test for dns-stuff.c + * Copyright (C) 2011 Free Software Foundation, Inc. + * Copyright (C) 2011, 2015 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + + +#include "../common/util.h" +#include "dns-stuff.h" + +#define PGM "t-dns-stuff" + +static int verbose; +static int debug; + + +static void +init_sockets (void) +{ +#ifdef HAVE_W32_SYSTEM + WSADATA wsadat; + + WSAStartup (0x202, &wsadat); +#endif +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + gpg_error_t err; + int any_options = 0; + int opt_tor = 0; + int opt_cert = 0; + int opt_srv = 0; + int opt_bracket = 0; + int opt_cname = 0; + char const *name = NULL; + + gpgrt_init (); + log_set_prefix (PGM, GPGRT_LOG_WITH_PREFIX); + if (argc) + { argc--; argv++; } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--help")) + { + fputs ("usage: " PGM " [HOST]\n" + "Options:\n" + " --verbose print timings etc.\n" + " --debug flyswatter\n" + " --standard-resolver use the system's resolver\n" + " --use-tor use Tor\n" + " --new-circuit use a new Tor circuit\n" + " --bracket enclose v6 addresses in brackets\n" + " --cert lookup a CERT RR\n" + " --srv lookup a SRV RR\n" + " --cname lookup a CNAME RR\n" + " --timeout SECONDS timeout after SECONDS\n" + , stdout); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose++; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose += 2; + debug++; + argc--; argv++; + } + else if (!strcmp (*argv, "--use-tor")) + { + opt_tor = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--standard-resolver")) + { + enable_standard_resolver (1); + argc--; argv++; + } + else if (!strcmp (*argv, "--recursive-resolver")) + { + enable_recursive_resolver (1); + argc--; argv++; + } + else if (!strcmp (*argv, "--bracket")) + { + opt_bracket = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--cert")) + { + any_options = opt_cert = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--srv")) + { + any_options = opt_srv = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--cname")) + { + any_options = opt_cname = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--timeout")) + { + argc--; argv++; + if (argc) + { + set_dns_timeout (atoi (*argv)); + argc--; argv++; + } + } + else if (!strncmp (*argv, "--", 2)) + { + fprintf (stderr, PGM ": unknown option '%s'\n", *argv); + exit (1); + } + } + + if (!argc && !any_options) + { + opt_cert = 1; + name = "simon.josefsson.org"; + } + else if (argc == 1) + name = *argv; + else + { + fprintf (stderr, PGM ": none or too many host names given\n"); + exit (1); + } + + set_dns_verbose (verbose, debug); + init_sockets (); + + if (opt_tor) + enable_dns_tormode (0); + + if (opt_cert) + { + unsigned char *fpr; + size_t fpr_len; + char *url; + void *key; + size_t keylen; + + if (verbose || any_options) + printf ("CERT lookup on '%s'\n", name); + + err = get_dns_cert (name, DNS_CERTTYPE_ANY, &key, &keylen, + &fpr, &fpr_len, &url); + if (err) + printf ("get_dns_cert failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + else if (key) + { + if (verbose || any_options) + printf ("Key found (%u bytes)\n", (unsigned int)keylen); + } + else + { + if (fpr) + { + int i; + + printf ("Fingerprint found (%d bytes): ", (int)fpr_len); + for (i = 0; i < fpr_len; i++) + printf ("%02X", fpr[i]); + putchar ('\n'); + } + else + printf ("No fingerprint found\n"); + + if (url) + printf ("URL found: %s\n", url); + else + printf ("No URL found\n"); + + } + + xfree (key); + xfree (fpr); + xfree (url); + } + else if (opt_cname) + { + char *cname; + + printf ("CNAME lookup on '%s'\n", name); + err = get_dns_cname (name, &cname); + if (err) + printf ("get_dns_cname failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + else + { + printf ("CNAME found: '%s'\n", cname); + } + xfree (cname); + } + else if (opt_srv) + { + struct srventry *srv; + unsigned int count; + int i; + + err = get_dns_srv (name? name : "_hkp._tcp.wwwkeys.pgp.net", + NULL, NULL, &srv, &count); + if (err) + printf ("get_dns_srv failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + else + { + printf ("count=%u\n",count); + for (i=0; i < count; i++) + { + printf("priority=%-8hu ",srv[i].priority); + printf("weight=%-8hu ",srv[i].weight); + printf("port=%-5hu ",srv[i].port); + printf("target=%s\n",srv[i].target); + } + + xfree(srv); + } + } + else /* Standard lookup. */ + { + char *cname; + dns_addrinfo_t aibuf, ai; + char *host; + + printf ("Lookup on '%s'\n", name); + + err = resolve_dns_name (name, 0, 0, SOCK_STREAM, &aibuf, &cname); + if (err) + { + fprintf (stderr, PGM": resolving '%s' failed: %s\n", + name, gpg_strerror (err)); + exit (1); + } + + if (cname) + printf ("cname: %s\n", cname); + for (ai = aibuf; ai; ai = ai->next) + { + printf ("%s %3d %3d ", + ai->family == AF_INET6? "inet6" : + ai->family == AF_INET? "inet4" : "? ", + ai->socktype, ai->protocol); + + err = resolve_dns_addr (ai->addr, ai->addrlen, + (DNS_NUMERICHOST + | (opt_bracket? DNS_WITHBRACKET:0)), + &host); + if (err) + printf ("[resolve_dns_addr failed: %s]", gpg_strerror (err)); + else + { + printf ("%s", host); + xfree (host); + } + + err = resolve_dns_addr (ai->addr, ai->addrlen, + (opt_bracket? DNS_WITHBRACKET:0), + &host); + if (err) + printf (" [resolve_dns_addr failed (2): %s]", gpg_strerror (err)); + else + { + if (!is_ip_address (host)) + printf (" (%s)", host); + xfree (host); + } + putchar ('\n'); + } + xfree (cname); + free_dns_addrinfo (aibuf); + } + + reload_dns_stuff (1); /* Release objects. */ + + return 0; +} diff --git a/dirmngr/t-http-basic.c b/dirmngr/t-http-basic.c new file mode 100644 index 0000000..edf82ef --- /dev/null +++ b/dirmngr/t-http-basic.c @@ -0,0 +1,199 @@ +/* t-http-basic.c - Basic regression tests for http.c + * Copyright (C) 2018 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 <https://gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <stdlib.h> + +#include "../common/util.h" +#include "t-support.h" +#include "http.h" + +#define PGM "t-http-basic" + + +static void +test_http_prepare_redirect (void) +{ + static struct { + const char *url; + const char *location; + const char *expect_url; + gpg_error_t expect_err; + } tests[] = { + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + NULL, + "", + GPG_ERR_NO_DATA + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "", + "", + GPG_ERR_NO_DATA + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "foo//bla", + "", + GPG_ERR_BAD_URI + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://foo.gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + "http://foo.gnupg.org:8080/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http:///.no-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + GPG_ERR_BAD_URI + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8080/.not-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8/.not-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:8/.not-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org:/.no-so-well-known/openpgpkey/hu/12345678", + "http://gnupg.org:/.no-so-well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/", + "http://gnupg.org/", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.net", + "http://gnupg.net/.well-known/openpgpkey/hu/12345678", + 0 + }, + { + "http://gnupg.org", + "http://gnupg.org", + "http://gnupg.org", + 0 + }, + { + "http://gnupg.org", + "http://foo.gnupg.org", + "http://foo.gnupg.org", + 0 + }, + { + "http://gnupg.org/", + "http://foo.gnupg.org", + "http://foo.gnupg.org/", + 0 + }, + { + "http://gnupg.org", + "http://foo.gnupg.org/", + "http://foo.gnupg.org", + 0 + }, + { + "http://gnupg.org/.well-known/openpgpkey/hu/12345678", + "http://gnupg.org/something-else", + "http://gnupg.org/something-else", + 0 + }, + }; + int tidx; + http_redir_info_t ri; + gpg_error_t err; + char *newurl; + + err = http_prepare_redirect (NULL, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_INV_ARG) + fail (0); + memset (&ri, 0, sizeof ri); + err = http_prepare_redirect (&ri, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_INV_ARG) + fail (0); + memset (&ri, 0, sizeof ri); + ri.silent = 1; + ri.orig_url = "http://example.org"; + err = http_prepare_redirect (&ri, 301, tests[0].location, &newurl); + if (gpg_err_code (err) != GPG_ERR_NO_DATA) + fail (0); + + for (tidx = 0; tidx < DIM (tests); tidx++) + { + memset (&ri, 0, sizeof ri); + ri.silent = 1; + ri.redirects_left = 1; + ri.orig_url = tests[tidx].url; + + err = http_prepare_redirect (&ri, 301, tests[tidx].location, &newurl); + if (err && newurl) + fail (tidx); + if (err && gpg_err_code (err) != tests[tidx].expect_err) + fail (tidx); + if (err) + continue; + if (!newurl) + fail (tidx); + if (strcmp (tests[tidx].expect_url, newurl)) + { + fprintf (stderr, "want: '%s'\n", tests[tidx].expect_url); + fprintf (stderr, "got : '%s'\n", newurl); + fail (tidx); + } + + xfree (newurl); + } +} + + +int +main (int argc, char **argv) +{ + (void)argc; + (void)argv; + + test_http_prepare_redirect (); + + return 0; +} diff --git a/dirmngr/t-http.c b/dirmngr/t-http.c new file mode 100644 index 0000000..3cf08ad --- /dev/null +++ b/dirmngr/t-http.c @@ -0,0 +1,497 @@ +/* t-http.c + * Copyright (C) 1999, 2001, 2002, 2003, 2004, 2006, 2009, 2010, + * 2011 Free Software Foundation, Inc. + * Copyright (C) 2014 Werner Koch + * + * This file is part of GnuPG. + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * This file 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <assuan.h> + +#include "../common/util.h" +#include "../common/logging.h" +#include "dns-stuff.h" +#include "http.h" + +#include <ksba.h> +#if HTTP_USE_NTBTLS +# include <ntbtls.h> +#elif HTTP_USE_GNUTLS +# include <gnutls/gnutls.h> /* For init, logging, and deinit. */ +#endif /*HTTP_USE_GNUTLS*/ + +#define PGM "t-http" + +static int verbose; +static int debug; +static int no_verify; + +/* static void */ +/* read_dh_params (const char *fname) */ +/* { */ +/* gpg_error_t err; */ +/* int rc; */ +/* FILE *fp; */ +/* struct stat st; */ +/* char *buf; */ +/* size_t buflen; */ +/* gnutls_datum_t datum; */ + +/* fp = fopen (fname, "rb"); */ +/* if (!fp) */ +/* { */ +/* err = gpg_error_from_syserror (); */ +/* log_fatal ("can't open '%s': %s\n", fname, gpg_strerror (err)); */ +/* } */ + +/* if (fstat (fileno(fp), &st)) */ +/* { */ +/* err = gpg_error_from_syserror (); */ +/* log_fatal ("can't stat '%s': %s\n", fname, gpg_strerror (err)); */ +/* } */ + +/* buflen = st.st_size; */ +/* buf = xmalloc (buflen+1); */ +/* if (fread (buf, buflen, 1, fp) != 1) */ +/* { */ +/* err = gpg_error_from_syserror (); */ +/* log_fatal ("error reading '%s': %s\n", fname, gpg_strerror (err)); */ +/* } */ +/* fclose (fp); */ + +/* datum.size = buflen; */ +/* datum.data = buf; */ + +/* rc = gnutls_dh_params_import_pkcs3 (dh_params, &datum, GNUTLS_X509_FMT_PEM); */ +/* if (rc < 0) */ +/* log_fatal ("gnutls_dh_param_import failed: %s\n", gnutls_strerror (rc)); */ + +/* xfree (buf); */ +/* } */ + + + +#if HTTP_USE_GNUTLS +static gpg_error_t +verify_callback (http_t hd, http_session_t session, int reserved) +{ + (void)hd; + (void)reserved; + return no_verify? 0 : http_verify_server_credentials (session); +} +#endif + +#if HTTP_USE_GNUTLS +static void +my_gnutls_log (int level, const char *text) +{ + fprintf (stderr, "gnutls:L%d: %s", level, text); +} +#endif + +#if HTTP_USE_NTBTLS +static gpg_error_t +my_http_tls_verify_cb (void *opaque, + http_t http, + http_session_t session, + unsigned int http_flags, + void *tls_context) +{ + gpg_error_t err; + int idx; + ksba_cert_t cert; + ksba_cert_t hostcert = NULL; + + (void)opaque; + (void)http; + (void)session; + (void)http_flags; + + /* Get the peer's certs fron ntbtls. */ + for (idx = 0; + (cert = ntbtls_x509_get_peer_cert (tls_context, idx)); idx++) + { + if (!idx) + { + log_info ("Received host certificate\n"); + hostcert = cert; + } + else + { + + log_info ("Received additional certificate\n"); + ksba_cert_release (cert); + } + } + if (!idx) + { + err = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + err = 0; + + leave: + ksba_cert_release (hostcert); + log_info ("my_http_tls_verify_cb returns: %s\n", gpg_strerror (err)); + return err; +} +#endif /*HTTP_USE_NTBTLS*/ + + + +/* Prepend FNAME with the srcdir environment variable's value and + return an allocated filename. */ +static char * +prepend_srcdir (const char *fname) +{ + static const char *srcdir; + char *result; + + if (!srcdir && !(srcdir = getenv ("srcdir"))) + srcdir = "."; + + result = xmalloc (strlen (srcdir) + 1 + strlen (fname) + 1); + strcpy (result, srcdir); + strcat (result, "/"); + strcat (result, fname); + return result; +} + + +int +main (int argc, char **argv) +{ + int last_argc = -1; + gpg_error_t err; + int rc; parsed_uri_t uri; + uri_tuple_t r; + http_t hd; + int c; + unsigned int my_http_flags = 0; + int no_out = 0; + int tls_dbg = 0; + int no_crl = 0; + const char *cafile = NULL; + http_session_t session = NULL; + unsigned int timeout = 0; + + gpgrt_init (); + log_set_prefix (PGM, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID); + if (argc) + { argc--; argv++; } + while (argc && last_argc != argc ) + { + last_argc = argc; + if (!strcmp (*argv, "--")) + { + argc--; argv++; + break; + } + else if (!strcmp (*argv, "--help")) + { + fputs ("usage: " PGM " URL\n" + "Options:\n" + " --verbose print timings etc.\n" + " --debug flyswatter\n" + " --tls-debug N use TLS debug level N\n" + " --cacert FNAME expect CA certificate in file FNAME\n" + " --timeout MS timeout for connect in MS\n" + " --no-verify do not verify the certificate\n" + " --force-tls use HTTP_FLAG_FORCE_TLS\n" + " --force-tor use HTTP_FLAG_FORCE_TOR\n" + " --no-out do not print the content\n" + " --no-crl do not consuilt a CRL\n", + stdout); + exit (0); + } + else if (!strcmp (*argv, "--verbose")) + { + verbose++; + argc--; argv++; + } + else if (!strcmp (*argv, "--debug")) + { + verbose += 2; + debug++; + argc--; argv++; + } + else if (!strcmp (*argv, "--tls-debug")) + { + argc--; argv++; + if (argc) + { + tls_dbg = atoi (*argv); + argc--; argv++; + } + } + else if (!strcmp (*argv, "--cacert")) + { + argc--; argv++; + if (argc) + { + cafile = *argv; + argc--; argv++; + } + } + else if (!strcmp (*argv, "--timeout")) + { + argc--; argv++; + if (argc) + { + timeout = strtoul (*argv, NULL, 10); + argc--; argv++; + } + } + else if (!strcmp (*argv, "--no-verify")) + { + no_verify = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--force-tls")) + { + my_http_flags |= HTTP_FLAG_FORCE_TLS; + argc--; argv++; + } + else if (!strcmp (*argv, "--force-tor")) + { + my_http_flags |= HTTP_FLAG_FORCE_TOR; + argc--; argv++; + } + else if (!strcmp (*argv, "--no-out")) + { + no_out = 1; + argc--; argv++; + } + else if (!strcmp (*argv, "--no-crl")) + { + no_crl = 1; + argc--; argv++; + } + else if (!strncmp (*argv, "--", 2)) + { + fprintf (stderr, PGM ": unknown option '%s'\n", *argv); + exit (1); + } + } + if (argc != 1) + { + fprintf (stderr, PGM ": no or too many URLS given\n"); + exit (1); + } + + if (!cafile) + cafile = prepend_srcdir ("tls-ca.pem"); + + if (verbose) + my_http_flags |= HTTP_FLAG_LOG_RESP; + + if (verbose || debug) + http_set_verbose (verbose, debug); + + /* http.c makes use of the assuan socket wrapper. */ + assuan_sock_init (); + + if ((my_http_flags & HTTP_FLAG_FORCE_TOR)) + { + enable_dns_tormode (1); + if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1)) + { + log_error ("error enabling Tor mode: %s\n", strerror (errno)); + log_info ("(is your Libassuan recent enough?)\n"); + } + } + +#if HTTP_USE_NTBTLS + log_info ("new session.\n"); + err = http_session_new (&session, NULL, + ((no_crl? HTTP_FLAG_NO_CRL : 0) + | HTTP_FLAG_TRUST_DEF), + my_http_tls_verify_cb, NULL); + if (err) + log_error ("http_session_new failed: %s\n", gpg_strerror (err)); + ntbtls_set_debug (tls_dbg, NULL, NULL); + +#elif HTTP_USE_GNUTLS + + rc = gnutls_global_init (); + if (rc) + log_error ("gnutls_global_init failed: %s\n", gnutls_strerror (rc)); + + http_register_tls_callback (verify_callback); + http_register_tls_ca (cafile); + + err = http_session_new (&session, NULL, + ((no_crl? HTTP_FLAG_NO_CRL : 0) + | HTTP_FLAG_TRUST_DEF), + NULL, NULL); + if (err) + log_error ("http_session_new failed: %s\n", gpg_strerror (err)); + + /* rc = gnutls_dh_params_init(&dh_params); */ + /* if (rc) */ + /* log_error ("gnutls_dh_params_init failed: %s\n", gnutls_strerror (rc)); */ + /* read_dh_params ("dh_param.pem"); */ + + /* 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)); */ + + /* gnutls_certificate_set_dh_params (certcred, dh_params); */ + + gnutls_global_set_log_function (my_gnutls_log); + if (tls_dbg) + gnutls_global_set_log_level (tls_dbg); + +#else + (void)err; + (void)tls_dbg; + (void)no_crl; +#endif /*HTTP_USE_GNUTLS*/ + + rc = http_parse_uri (&uri, *argv, 1); + if (rc) + { + log_error ("'%s': %s\n", *argv, gpg_strerror (rc)); + return 1; + } + + printf ("Scheme: %s\n", uri->scheme); + if (uri->opaque) + printf ("Value : %s\n", uri->path); + else + { + printf ("Auth : %s\n", uri->auth? uri->auth:"[none]"); + printf ("Host : %s (off=%hu)\n", uri->host, uri->off_host); + printf ("Port : %u\n", uri->port); + printf ("Path : %s (off=%hu)\n", uri->path, uri->off_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'); + } + printf ("Flags :%s%s%s%s\n", + uri->is_http? " http":"", + uri->opaque? " opaque":"", + uri->v6lit? " v6lit":"", + uri->onion? " onion":""); + printf ("TLS : %s\n", + uri->use_tls? "yes": + (my_http_flags&HTTP_FLAG_FORCE_TLS)? "forced" : "no"); + printf ("Tor : %s\n", + (my_http_flags&HTTP_FLAG_FORCE_TOR)? "yes" : "no"); + + } + fflush (stdout); + http_release_parsed_uri (uri); + uri = NULL; + + if (session) + http_session_set_timeout (session, timeout); + + rc = http_open_document (&hd, *argv, NULL, my_http_flags, + NULL, session, NULL, NULL); + 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)); + + { + const char **names; + int i; + + names = http_get_header_names (hd); + if (!names) + log_fatal ("http_get_header_names failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + for (i = 0; names[i]; i++) + printf ("HDR: %s: %s\n", names[i], http_get_header (hd, names[i])); + xfree (names); + } + fflush (stdout); + + switch (http_get_status_code (hd)) + { + case 200: + case 400: + case 401: + case 403: + case 404: + { + unsigned long count = 0; + while ((c = es_getc (http_get_read_ptr (hd))) != EOF) + { + count++; + if (!no_out) + putchar (c); + } + log_info ("Received bytes: %lu\n", count); + } + break; + case 301: + case 302: + case 307: + log_info ("Redirected to: %s\n", http_get_header (hd, "Location")); + break; + } + http_close (hd, 0); + + http_session_release (session); +#ifdef HTTP_USE_GNUTLS + gnutls_global_deinit (); +#endif /*HTTP_USE_GNUTLS*/ + + return 0; +} diff --git a/dirmngr/t-ldap-parse-uri.c b/dirmngr/t-ldap-parse-uri.c new file mode 100644 index 0000000..932ca7d --- /dev/null +++ b/dirmngr/t-ldap-parse-uri.c @@ -0,0 +1,257 @@ +/* t-ldap-parse-uri.c - Regression tests for ldap-parse-uri.c. + * Copyright (C) 2015 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "ldap-parse-uri.h" + +#include "t-support.h" + +struct test_ldap_uri_p +{ + const char *uri; + int result; +}; + +void +check_ldap_uri_p (int test_count, struct test_ldap_uri_p *test) +{ + int result = ldap_uri_p (test->uri); + if (result != test->result) + { + printf ("'%s' is %san LDAP schema, but ldap_uri_p says opposite.\n", + test->uri, test->result ? "" : "not "); + fail(1000 * test_count); + } +} + +static void +test_ldap_uri_p (void) +{ + struct test_ldap_uri_p tests[] = { + { "ldap://foo", 1 }, + { "ldap://", 1 }, + { "ldap:", 1 }, + { "ldap", 0 }, + { "ldapfoobar", 0 }, + + { "ldaps://foo", 1 }, + { "ldaps://", 1 }, + { "ldaps:", 1 }, + { "ldaps", 0 }, + { "ldapsfoobar", 0 }, + + { "ldapi://foo", 1 }, + { "ldapi://", 1 }, + { "ldapi:", 1 }, + { "ldapi", 0 }, + { "ldapifoobar", 0 }, + + { "LDAP://FOO", 1 }, + { "LDAP://", 1 }, + { "LDAP:", 1 }, + { "LDAP", 0 }, + { "LDAPFOOBAR", 0 } + }; + + int test_count; + for (test_count = 1; + test_count <= sizeof (tests) / sizeof (tests[0]); + test_count ++) + check_ldap_uri_p (test_count, &tests[test_count - 1]); +} + +struct test_ldap_parse_uri +{ + const char *uri; + const char *scheme; + const char *host; + const int port; + const int use_tls; + const char *path; /* basedn. */ + const char *auth; /* binddn. */ + const char *password; /* query[1]. */ +}; + +static int +cmp (const char *a, const char *b) +{ + if (! a) + a = ""; + if (! b) + b = ""; + + return strcmp (a, b) == 0; +} + +void +check_ldap_parse_uri (int test_count, struct test_ldap_parse_uri *test) +{ + gpg_error_t err; + parsed_uri_t puri; + + err = ldap_parse_uri (&puri, test->uri); + if (err) + { + printf ("Parsing '%s' failed (%d).\n", test->uri, err); + fail (test_count * 1000 + 0); + } + + if (! cmp(test->scheme, puri->scheme)) + { + printf ("scheme mismatch: got '%s', expected '%s'.\n", + puri->scheme, test->scheme); + fail (test_count * 1000 + 1); + } + + if (! cmp(test->host, puri->host)) + { + printf ("host mismatch: got '%s', expected '%s'.\n", + puri->host, test->host); + fail (test_count * 1000 + 2); + } + + if (test->port != puri->port) + { + printf ("port mismatch: got '%d', expected '%d'.\n", + puri->port, test->port); + fail (test_count * 1000 + 3); + } + + if (test->use_tls != puri->use_tls) + { + printf ("use_tls mismatch: got '%d', expected '%d'.\n", + puri->use_tls, test->use_tls); + fail (test_count * 1000 + 4); + } + + if (! cmp(test->path, puri->path)) + { + printf ("path mismatch: got '%s', expected '%s'.\n", + puri->path, test->path); + fail (test_count * 1000 + 5); + } + + if (! cmp(test->auth, puri->auth)) + { + printf ("auth mismatch: got '%s', expected '%s'.\n", + puri->auth, test->auth); + fail (test_count * 1000 + 6); + } + + if (! test->password && ! puri->query) + /* Ok. */ + ; + else if (test->password && ! puri->query) + { + printf ("password mismatch: got NULL, expected '%s'.\n", + test->auth); + fail (test_count * 1000 + 7); + } + else if (! test->password && puri->query) + { + printf ("password mismatch: got something, expected NULL.\n"); + fail (test_count * 1000 + 8); + } + else if (! (test->password && puri->query + && puri->query->name && puri->query->value + && strcmp (puri->query->name, "password") == 0 + && cmp (puri->query->value, test->password))) + { + printf ("password mismatch: got '%s:%s', expected 'password:%s'.\n", + puri->query->name, puri->query->value, + test->password); + fail (test_count * 1000 + 9); + } + + http_release_parsed_uri (puri); +} + +static void +test_ldap_parse_uri (void) +{ + struct test_ldap_parse_uri tests[] = { + { "ldap://", "ldap", NULL, 389, 0, NULL, NULL, NULL }, + { "ldap://host", "ldap", "host", 389, 0, NULL, NULL, NULL }, + { "ldap://host:100", "ldap", "host", 100, 0, NULL, NULL, NULL }, + { "ldaps://host", "ldaps", "host", 636, 1, NULL, NULL, NULL }, + { "ldap://host/ou%3DPGP%20Keys%2Cdc%3DEXAMPLE%2Cdc%3DORG", + "ldap", "host", 389, 0, "ou=PGP Keys,dc=EXAMPLE,dc=ORG" }, + { "ldap://host/????bindname=uid%3Duser%2Cou%3DPGP%20Users%2Cdc%3DEXAMPLE%2Cdc%3DORG,password=foobar", + "ldap", "host", 389, 0, "", + "uid=user,ou=PGP Users,dc=EXAMPLE,dc=ORG", "foobar" } + }; + + int test_count; + for (test_count = 1; + test_count <= sizeof (tests) / sizeof (tests[0]); + test_count ++) + check_ldap_parse_uri (test_count, &tests[test_count - 1]); +} + +struct test_ldap_escape_filter +{ + const char *filter; + const char *result; +}; + +static void +check_ldap_escape_filter (int test_count, struct test_ldap_escape_filter *test) +{ + char *result = ldap_escape_filter (test->filter); + + if (strcmp (result, test->result) != 0) + { + printf ("Filter: '%s'. Escaped: '%s'. Expected: '%s'.\n", + test->filter, result, test->result); + fail (test_count * 1000); + } + + xfree (result); +} + +static void +test_ldap_escape_filter (void) +{ + struct test_ldap_escape_filter tests[] = { + { "foobar", "foobar" }, + { "", "" }, + { "(foo)", "%28foo%29" }, + { "* ( ) \\ /", "%2a %28 %29 %5c %2f" } + }; + + int test_count; + for (test_count = 1; + test_count <= sizeof (tests) / sizeof (tests[0]); + test_count ++) + check_ldap_escape_filter (test_count, &tests[test_count - 1]); +} + +int +main (int argc, char **argv) +{ + (void)argc; + (void)argv; + + test_ldap_uri_p (); + test_ldap_parse_uri (); + test_ldap_escape_filter (); + + return 0; +} diff --git a/dirmngr/t-support.c b/dirmngr/t-support.c new file mode 100644 index 0000000..fc9546a --- /dev/null +++ b/dirmngr/t-support.c @@ -0,0 +1,43 @@ +/* t-support.c - Module test support (stubs etc). + * Copyright (C) 2018 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 <https://www.gnu.org/licenses/>. + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + + +#include "../common/util.h" +#include "dirmngr-status.h" +#include "t-support.h" + + + +/* Stub for testing. See server.c for the real implementation. */ +gpg_error_t +dirmngr_status_printf (ctrl_t ctrl, const char *keyword, + const char *format, ...) +{ + (void)ctrl; + (void)keyword; + (void)format; + + return 0; +} diff --git a/dirmngr/t-support.h b/dirmngr/t-support.h new file mode 100644 index 0000000..f773f1e --- /dev/null +++ b/dirmngr/t-support.h @@ -0,0 +1,42 @@ +/* t-support.h - Helper for the regression tests + * Copyright (C) 2007 Free Software Foundation, Inc. + * + * This file is part of JNLIB, which is a subsystem of GnuPG. + * + * JNLIB is free software; you can redistribute it and/or modify it + * under the terms of either + * + * - the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at + * your option) any later version. + * + * or + * + * - 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. + * + * or both in parallel, as here. + * + * JNLIB 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 copies of the GNU General Public License + * and the GNU Lesser General Public License along with this program; + * if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef DIRMNGR_T_SUPPORT_H +#define DIRMNGR_T_SUPPORT_H 1 + +/* Macros to print the result of a test. */ +#define pass() do { ; } while(0) +#define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\ + __FILE__,__LINE__, (a)); \ + exit (1); \ + } while(0) + + +#endif /* DIRMNGR_T_SUPPORT_H */ diff --git a/dirmngr/tls-ca.pem b/dirmngr/tls-ca.pem new file mode 100644 index 0000000..c296466 --- /dev/null +++ b/dirmngr/tls-ca.pem @@ -0,0 +1,30 @@ +Issuer ...: /CN=UTN-USERFirst-Hardware/OU=http:\x2f\x2fwww.usertrust.com/O=The USERTRUST Network/L=Salt Lake City/ST=UT/C=US +Serial ...: 44BE0C8B500024B411D3362AFE650AFD +Subject ..: /CN=UTN-USERFirst-Hardware/OU=http:\x2f\x2fwww.usertrust.com/O=The USERTRUST Network/L=Salt Lake City/ST=UT/C=US + +-----BEGIN CERTIFICATE----- +MIIEdDCCA1ygAwIBAgIQRL4Mi1AAJLQR0zYq/mUK/TANBgkqhkiG9w0BAQUFADCB +lzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2Ug +Q2l0eTEeMBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExho +dHRwOi8vd3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3Qt +SGFyZHdhcmUwHhcNOTkwNzA5MTgxMDQyWhcNMTkwNzA5MTgxOTIyWjCBlzELMAkG +A1UEBhMCVVMxCzAJBgNVBAgTAlVUMRcwFQYDVQQHEw5TYWx0IExha2UgQ2l0eTEe +MBwGA1UEChMVVGhlIFVTRVJUUlVTVCBOZXR3b3JrMSEwHwYDVQQLExhodHRwOi8v +d3d3LnVzZXJ0cnVzdC5jb20xHzAdBgNVBAMTFlVUTi1VU0VSRmlyc3QtSGFyZHdh +cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCx98M4P7Sof885glFn +0G2f0v9Y8+efK+wNiVSZuTiZFvfgIXlIwrthdBKWHTxqctU8EGc6Oe0rE81m65UJ +M6Rsl7HoxuzBdXmcRl6Nq9Bq/bkqVRcQVLMZ8Jr28bFdtqdt++BxF2uiiPsA3/4a +MXcMmgF6sTLjKwEHOG7DpV4jvEWbe1DByTCP2+UretNb+zNAHqDVmBe8i4fDidNd +oI6yqqr2jmmIBsX6iSHzCJ1pLgkzmykNRg+MzEk0sGlRvfkGzWitZky8PqxhvQqI +DsjfPe58BEydCl5rkdbux+0ojatNh4lz0G6k0B4WixThdkQDf2Os5M1JnMWS9Ksy +oUhbAgMBAAGjgbkwgbYwCwYDVR0PBAQDAgHGMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKFyXyYbKJhDlV0HN9WFlp1L0sNFMEQGA1UdHwQ9MDswOaA3oDWGM2h0 +dHA6Ly9jcmwudXNlcnRydXN0LmNvbS9VVE4tVVNFUkZpcnN0LUhhcmR3YXJlLmNy +bDAxBgNVHSUEKjAoBggrBgEFBQcDAQYIKwYBBQUHAwUGCCsGAQUFBwMGBggrBgEF +BQcDBzANBgkqhkiG9w0BAQUFAAOCAQEARxkP3nTGmZev/K0oXnWO6y1n7k57K9cM +//bey1WiCuFMVGWTYGufEpytXoMs61quwOQt9ABjHbjAbPLPSbtNk28Gpgoiskli +CE7/yMgUsogWXecB5BKV5UU0s4tpvc+0hY91UZ59Ojg6FEgSxvunOxqNDYJAB+gE +CJChicsZUN/KHAG8HQQZexB2lzvukJDKxA4fFm517zP4029bHpbj4HR3dHuKom4t +3XbWOTCC8KucUvIqx69JXn7HaOWCgchqJ/kniCrVWFCVH/A7HFe7fRQ5YiuayZSS +KqMiDP+JJn1fIytH1xUdqWqeUQ0qUZ6B+dQ7XnASfxAynB67nfhmqA== +-----END CERTIFICATE----- diff --git a/dirmngr/validate.c b/dirmngr/validate.c new file mode 100644 index 0000000..901c165 --- /dev/null +++ b/dirmngr/validate.c @@ -0,0 +1,1305 @@ +/* validate.c - Validate a certificate chain. + * Copyright (C) 2001, 2003, 2004, 2008 Free Software Foundation, Inc. + * Copyright (C) 2004, 2006, 2008, 2017 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" + + +/* Mode parameters for cert_check_usage(). */ +enum cert_usage_modes + { + CERT_USAGE_MODE_SIGN, /* Usable for encryption. */ + CERT_USAGE_MODE_ENCR, /* Usable for signing. */ + CERT_USAGE_MODE_VRFY, /* Usable for verification. */ + CERT_USAGE_MODE_DECR, /* Usable for decryption. */ + CERT_USAGE_MODE_CERT, /* Usable for cert signing. */ + CERT_USAGE_MODE_OCSP, /* Usable for OCSP respone signing. */ + CERT_USAGE_MODE_CRL /* Usable for CRL signing. */ + }; + + +/* 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); + + +/* Make sure that the values defined in the headers are correct. We + * can't use the preprocessor due to the use of enums. */ +static void +check_header_constants (void) +{ + log_assert (CERTTRUST_CLASS_SYSTEM == VALIDATE_FLAG_TRUST_SYSTEM); + log_assert (CERTTRUST_CLASS_CONFIG == VALIDATE_FLAG_TRUST_CONFIG); + log_assert (CERTTRUST_CLASS_HKP == VALIDATE_FLAG_TRUST_HKP); + log_assert (CERTTRUST_CLASS_HKPSPOOL == VALIDATE_FLAG_TRUST_HKPSPOOL); + +#undef X +#define X (VALIDATE_FLAG_TRUST_SYSTEM | VALIDATE_FLAG_TRUST_CONFIG \ + | VALIDATE_FLAG_TRUST_HKP | VALIDATE_FLAG_TRUST_HKPSPOOL) + +#if ( X & VALIDATE_FLAG_MASK_TRUST ) != X +# error VALIDATE_FLAG_MASK_TRUST is bad +#endif +#if ( ~X & VALIDATE_FLAG_MASK_TRUST ) +# error VALIDATE_FLAG_MASK_TRUST is bad +#endif + +#undef X +} + + +/* 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 precedence. */ + + 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, CERTTRUST_CLASS_CONFIG)) + { + /* 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; + + log_assert (ctrl->check_revocations_nest_level >= 0); + log_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 certificate (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. + + Note that VALIDATE_MODE_OCSP is not used due to the removal of the + system service in 2.1.15. Instead only the callback to gpgsm to + validate a certificate is used. + + 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, + unsigned int flags, char **r_trust_anchor) +{ + gpg_error_t err = 0; + int depth, maxdepth; + char *issuer = NULL; + char *subject = NULL; + ksba_cert_t subject_cert = NULL; + ksba_cert_t 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; + + check_header_constants (); + + if (r_exptime) + *r_exptime = 0; + *exptime = 0; + + if (r_trust_anchor) + *r_trust_anchor = NULL; + + if (DBG_X509) + dump_cert ("subject", cert); + + /* May the target certificate be used for this purpose? */ + if ((flags & VALIDATE_FLAG_OCSP) && (err = check_cert_use_ocsp (cert))) + return err; + if ((flags & VALIDATE_FLAG_CRL) && (err = check_cert_use_crl (cert))) + 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; /* Sensible limit on the length of the chain. */ + 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, + (flags & VALIDATE_FLAG_MASK_TRUST)); + 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 certificate is an end-point. */ + } + + /* To avoid loops, we use an arbitrary limit on the length of + the chain. */ + depth++; + if (depth > maxdepth) + { + log_error (_("certificate chain too long\n")); + err = gpg_error (GPG_ERR_BAD_CERT_CHAIN); + goto leave; + } + + /* Find the next cert up the tree. */ + ksba_cert_release (issuer_cert); issuer_cert = NULL; + err = find_issuing_cert (ctrl, subject_cert, &issuer_cert); + if (err) + { + if (gpg_err_code (err) == GPG_ERR_NOT_FOUND) + { + log_error (_("issuer certificate not found")); + log_info ("issuer certificate: #/"); + dump_string (issuer); + log_printf ("\n"); + } + else + log_error (_("issuer certificate not found: %s\n"), + gpg_strerror (err)); + /* Use a better understandable error code. */ + err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT); + goto leave; + } + +/* try_another_cert: */ + if (DBG_X509) + { + log_debug ("got issuer's certificate:\n"); + dump_cert ("issuer", issuer_cert); + } + + /* Now check the signature of the certificate. FIXME: 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 + /* Return 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 = check_cert_use_cert (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; + } + + /* Even if we have no error here we need to check whether we + * encountered an error somewhere during the checks. Set the error + * code to the most critical one. */ + if (!err) + { + 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); + } + + /* Now check for revocations unless CRL checks are disabled or we + * are non-recursive CRL mode. */ + if (!err + && !(flags & VALIDATE_FLAG_NOCRLCHECK) + && !((flags & VALIDATE_FLAG_CRL) + && !(flags & VALIDATE_FLAG_RECURSIVE))) + { /* 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 hierarchy 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; +} + + +/* Return the hash algorithm's algo id from its name given in the + * non-null termnated string in (buffer,buflen). Returns 0 on failure + * or if the algo is not known. */ +static int +hash_algo_from_buffer (const void *buffer, size_t buflen) +{ + char *string; + int algo; + + string = xtrymalloc (buflen + 1); + if (!string) + { + log_error (_("out of core\n")); + return 0; + } + memcpy (string, buffer, buflen); + string[buflen] = 0; + algo = gcry_md_map_name (string); + if (!algo) + log_error ("unknown digest algorithm '%s' used in certificate\n", string); + xfree (string); + return algo; +} + + +/* Return an unsigned integer from the non-null termnated string + * (buffer,buflen). Returns 0 on failure. */ +static unsigned int +uint_from_buffer (const void *buffer, size_t buflen) +{ + char *string; + unsigned int val; + + string = xtrymalloc (buflen + 1); + if (!string) + { + log_error (_("out of core\n")); + return 0; + } + memcpy (string, buffer, buflen); + string[buflen] = 0; + val = strtoul (string, NULL, 10); + xfree (string); + return val; +} + + +/* 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 algo; + ksba_sexp_t p; + size_t n; + gcry_sexp_t s_sig, s_hash, s_pkey; + const char *algo_name; /* hash algorithm name converted to lower case. */ + int digestlen; + unsigned char *digest; + int use_pss = 0; + unsigned int saltlen; + + /* 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 && algoid && !strcmp (algoid, "1.2.840.113549.1.1.10")) + use_pss = 1; + else if (!algo) + { + log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?"); + return gpg_error (GPG_ERR_GENERAL); + } + + /* 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"); + ksba_free (p); + return gpg_error (GPG_ERR_BUG); + } + 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)); + return err; + } + if (DBG_CRYPTO) + gcry_log_debugsxp ("sigval", s_sig); + + if (use_pss) + { + /* Extract the hash algorithm and the salt length from the sigval. */ + gcry_buffer_t ioarray[2] = { {0}, {0} }; + + err = gcry_sexp_extract_param (s_sig, "sig-val", + "&'hash-algo''salt-length'", + ioarray+0, ioarray+1, NULL); + if (err) + { + gcry_sexp_release (s_sig); + log_error ("extracting params from PSS failed: %s\n", + gpg_strerror (err)); + return err; + } + algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len); + saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len); + xfree (ioarray[0].data); + xfree (ioarray[1].data); + if (saltlen < 20) + { + log_error ("length of PSS salt too short\n"); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + if (!algo) + { + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + /* Add some restrictions; see ../sm/certcheck.c for details. */ + switch (algo) + { + case GCRY_MD_SHA1: + case GCRY_MD_SHA256: + case GCRY_MD_SHA384: + case GCRY_MD_SHA512: + case GCRY_MD_SHA3_256: + case GCRY_MD_SHA3_384: + case GCRY_MD_SHA3_512: + break; + default: + log_error ("PSS hash algorithm '%s' rejected\n", + gcry_md_algo_name (algo)); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + + if (gcry_md_get_algo_dlen (algo) != saltlen) + { + log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n", + gcry_md_algo_name (algo), saltlen); + gcry_sexp_release (s_sig); + return gpg_error (GPG_ERR_DIGEST_ALGO); + } + } + + algo_name = hash_algo_to_string (algo); + err = gcry_md_open (&md, algo, 0); + if (err) + { + log_error ("md_open failed: %s\n", gpg_strerror (err)); + gcry_sexp_release (s_sig); + 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); + gcry_sexp_release (s_sig); + return err; + } + gcry_md_final (md); + + /* 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 certificate. + * MD - Finalized hash context with hash of the certificate. + * ALGO_NAME - Lowercase hash algorithm name + * SALTLEN - Salt length for rsaPSS. + */ + digestlen = gcry_md_get_algo_dlen (algo); + digest = gcry_md_read (md, algo); + if (use_pss) + { + err = gcry_sexp_build (&s_hash, NULL, + "(data (flags pss)" + "(hash %s %b)" + "(salt-length %u))", + algo_name, + (int)digestlen, + digest, + saltlen); + } + else if (pk_algo_from_sexp (s_pkey) == GCRY_PK_DSA) + { + /* NB.: We support only SHA-1 here because we had problems back + * then to get test data for DSA-2. Meanwhile DSA has been + * replaced by ECDSA which we do not yet support. */ + 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); + } + err = gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))", + (int)digestlen, digest); + } + else /* Not DSA - we assume RSA */ + { + err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))", + algo_name, (int)digestlen, digest); + } + + if (!err) + 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 CERT is usable for MODE. */ +static gpg_error_t +check_cert_usage (ksba_cert_t cert, enum cert_usage_modes 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 == CERT_USAGE_MODE_SIGN + || mode == CERT_USAGE_MODE_ENCR)) + 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; + } + + switch (mode) + { + case CERT_USAGE_MODE_SIGN: + case CERT_USAGE_MODE_VRFY: + if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE + | KSBA_KEYUSAGE_NON_REPUDIATION))) + return 0; + log_info (mode == CERT_USAGE_MODE_VRFY + ? _("certificate should not have been used for signing\n") + : _("certificate is not usable for signing\n")); + break; + + case CERT_USAGE_MODE_ENCR: + case CERT_USAGE_MODE_DECR: + if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT + | KSBA_KEYUSAGE_DATA_ENCIPHERMENT))) + return 0; + log_info (mode == CERT_USAGE_MODE_DECR + ? _("certificate should not have been used for encryption\n") + : _("certificate is not usable for encryption\n")); + break; + + case CERT_USAGE_MODE_CERT: + if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN))) + return 0; + log_info (_("certificate should not have " + "been used for certification\n")); + break; + + case CERT_USAGE_MODE_OCSP: + if (use != ~0 + && (have_ocsp_signing + || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN + |KSBA_KEYUSAGE_CRL_SIGN)))) + return 0; + log_info (_("certificate should not have " + "been used for OCSP response signing\n")); + break; + + case CERT_USAGE_MODE_CRL: + if ((use & (KSBA_KEYUSAGE_CRL_SIGN))) + return 0; + log_info (_("certificate should not have " + "been used for CRL signing\n")); + break; + } + + return gpg_error (GPG_ERR_WRONG_KEY_USAGE); +} + + +/* Return 0 if the certificate CERT is usable for certification. */ +gpg_error_t +check_cert_use_cert (ksba_cert_t cert) +{ + return check_cert_usage (cert, CERT_USAGE_MODE_CERT); +} + +/* Return 0 if the certificate CERT is usable for signing OCSP + responses. */ +gpg_error_t +check_cert_use_ocsp (ksba_cert_t cert) +{ + return check_cert_usage (cert, CERT_USAGE_MODE_OCSP); +} + +/* Return 0 if the certificate CERT is usable for signing CRLs. */ +gpg_error_t +check_cert_use_crl (ksba_cert_t cert) +{ + return check_cert_usage (cert, CERT_USAGE_MODE_CRL); +} diff --git a/dirmngr/validate.h b/dirmngr/validate.h new file mode 100644 index 0000000..c7082e3 --- /dev/null +++ b/dirmngr/validate.h @@ -0,0 +1,68 @@ +/* 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 + + +/* Flag values matching the CERTTRUST_CLASS values and a MASK for + * them. check_header_constants() checks their consistency. */ +#define VALIDATE_FLAG_TRUST_SYSTEM 1 +#define VALIDATE_FLAG_TRUST_CONFIG 2 +#define VALIDATE_FLAG_TRUST_HKP 4 +#define VALIDATE_FLAG_TRUST_HKPSPOOL 8 +#define VALIDATE_FLAG_MASK_TRUST 0x0f + +/* Standard CRL issuer certificate validation; i.e. CRLs are not + * considered for CRL issuer certificates. */ +#define VALIDATE_FLAG_CRL 64 + +/* If this flag is set along with VALIDATE_FLAG_CRL a full CRL + * verification is done. */ +#define VALIDATE_FLAG_RECURSIVE 128 + +/* Validation mode as used for OCSP. */ +#define VALIDATE_FLAG_OCSP 256 + +/* Validation mode as used with TLS. */ +#define VALIDATE_FLAG_TLS 512 + +/* Don't do CRL checks. */ +#define VALIDATE_FLAG_NOCRLCHECK 1024 + + +/* 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, + unsigned int flags, char **r_trust_anchor); + +/* Return 0 if the certificate CERT is usable for certification. */ +gpg_error_t check_cert_use_cert (ksba_cert_t cert); + +/* Return 0 if the certificate CERT is usable for signing OCSP + responses. */ +gpg_error_t check_cert_use_ocsp (ksba_cert_t cert); + +/* Return 0 if the certificate CERT is usable for signing CRLs. */ +gpg_error_t check_cert_use_crl (ksba_cert_t cert); + + +#endif /*VALIDATE_H*/ diff --git a/dirmngr/w32-ldap-help.h b/dirmngr/w32-ldap-help.h new file mode 100644 index 0000000..566a346 --- /dev/null +++ b/dirmngr/w32-ldap-help.h @@ -0,0 +1,169 @@ +/* w32-ldap-help.h - Map utf8 based API into a wchar_t API. + * Copyright (C) 2010 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef W32_LDAP_HELP_H +#define W32_LDAP_HELP_H + +#ifndef HAVE_W32CE_SYSTEM +# error This is only required for W32CE. +#endif + + +static inline LDAP * +_dirmngr_ldap_init (const char *host, unsigned short port) +{ + LDAP *ld; + wchar_t *whost = NULL; + + if (host) + { + whost = utf8_to_wchar (host); + if (!whost) + return NULL; + } + ld = ldap_init (whost, port); + xfree (whost); + return ld; +} + + +static inline ULONG +_dirmngr_ldap_simple_bind_s (LDAP *ld, const char *user, const char *pass) +{ + ULONG ret; + wchar_t *wuser, *wpass; + + wuser = user? utf8_to_wchar (user) : NULL; + wpass = pass? utf8_to_wchar (pass) : NULL; + /* We can't easily map errnos to ldap_errno, thus we pass a NULL to + the function in the hope that the server will throw an error. */ + ret = ldap_simple_bind_s (ld, wuser, wpass); + xfree (wpass); + xfree (wuser); + return ret; +} + + +static inline ULONG +_dirmngr_ldap_search_st (LDAP *ld, const char *base, ULONG scope, + const char *filter, char **attrs, + ULONG attrsonly, struct timeval *timeout, + LDAPMessage **res) +{ + ULONG ret = LDAP_NO_MEMORY; + wchar_t *wbase = NULL; + wchar_t *wfilter = NULL; + wchar_t **wattrs = NULL; + int i; + + if (base) + { + wbase = utf8_to_wchar (base); + if (!wbase) + goto leave; + } + if (filter) + { + wfilter = utf8_to_wchar (filter); + if (!wfilter) + goto leave; + } + if (attrs) + { + for (i=0; attrs[i]; i++) + ; + wattrs = xtrycalloc (i+1, sizeof *wattrs); + if (!wattrs) + goto leave; + for (i=0; attrs[i]; i++) + { + wattrs[i] = utf8_to_wchar (attrs[i]); + if (!wattrs[i]) + goto leave; + } + } + + ret = ldap_search_st (ld, wbase, scope, wfilter, wattrs, attrsonly, + (struct l_timeval *)timeout, res); + + leave: + if (wattrs) + { + for (i=0; wattrs[i]; i++) + xfree (wattrs[i]); + xfree (wattrs); + } + xfree (wfilter); + xfree (wbase); + return ret; +} + + +static inline char * +_dirmngr_ldap_first_attribute (LDAP *ld, LDAPMessage *msg, BerElement **elem) +{ + wchar_t *wattr; + char *attr; + + wattr = ldap_first_attribute (ld, msg, elem); + if (!wattr) + return NULL; + attr = wchar_to_utf8 (wattr); + ldap_memfree (wattr); + return attr; +} + + +static inline char * +_dirmngr_ldap_next_attribute (LDAP *ld, LDAPMessage *msg, BerElement *elem) +{ + wchar_t *wattr; + char *attr; + + wattr = ldap_next_attribute (ld, msg, elem); + if (!wattr) + return NULL; + attr = wchar_to_utf8 (wattr); + ldap_memfree (wattr); + return attr; +} + +static inline BerValue ** +_dirmngr_ldap_get_values_len (LDAP *ld, LDAPMessage *msg, const char *attr) +{ + BerValue **ret; + wchar_t *wattr; + + if (attr) + { + wattr = utf8_to_wchar (attr); + if (!wattr) + return NULL; + } + else + wattr = NULL; + + ret = ldap_get_values_len (ld, msg, wattr); + xfree (wattr); + + return ret; +} + + +#endif /*W32_LDAP_HELP_H*/ diff --git a/dirmngr/workqueue.c b/dirmngr/workqueue.c new file mode 100644 index 0000000..2cb8573 --- /dev/null +++ b/dirmngr/workqueue.c @@ -0,0 +1,214 @@ +/* workqueue.c - Maintain a queue of background tasks + * Copyright (C) 2017 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0+ + */ + +#include <config.h> +#include <stdlib.h> +#include <string.h> + +#include "dirmngr.h" + + +/* An object for one item in the workqueue. */ +struct wqitem_s +{ + struct wqitem_s *next; + + /* This flag is set if the task requires network access. */ + unsigned int need_network:1; + + /* The id of the session which created this task. If this is 0 the + * task is not associated with a specific session. */ + unsigned int session_id; + + /* The function to perform the backgrount task. */ + wqtask_t func; + + /* A string with the string argument for that task. */ + char args[1]; +}; +typedef struct wqitem_s *wqitem_t; + + +/* The workque is a simple linked list. */ +static wqitem_t workqueue; + + +/* Dump the queue using Assuan status comments. */ +void +workqueue_dump_queue (ctrl_t ctrl) +{ + wqitem_t saved_workqueue; + wqitem_t item; + unsigned int count; + + /* Temporay detach the entiere workqueue so that other threads don't + * get into our way. */ + saved_workqueue = workqueue; + workqueue = NULL; + + for (count=0, item = saved_workqueue; item; item = item->next) + count++; + + dirmngr_status_helpf (ctrl, "wq: number of entries: %u", count); + for (item = saved_workqueue; item; item = item->next) + dirmngr_status_helpf (ctrl, "wq: sess=%u net=%d %s(\"%.100s%s\")", + item->session_id, item->need_network, + item->func? item->func (NULL, NULL): "nop", + item->args, strlen (item->args) > 100? "[...]":""); + + /* Restore then workqueue. Actually we append the saved queue do a + * possibly updated workqueue. */ + if (!(item=workqueue)) + workqueue = saved_workqueue; + else + { + while (item->next) + item = item->next; + item->next = saved_workqueue; + } +} + + +/* Append the task (FUNC,ARGS) to the work queue. FUNC shall return + * its name when called with (NULL, NULL). */ +gpg_error_t +workqueue_add_task (wqtask_t func, const char *args, unsigned int session_id, + int need_network) +{ + wqitem_t item, wi; + + item = xtrycalloc (1, sizeof *item + strlen (args)); + if (!item) + return gpg_error_from_syserror (); + strcpy (item->args, args); + item->func = func; + item->session_id = session_id; + item->need_network = !!need_network; + + if (!(wi=workqueue)) + workqueue = item; + else + { + while (wi->next) + wi = wi->next; + wi->next = item; + } + return 0; +} + + +/* Run the task described by ITEM. ITEM must have been detached from + * the workqueue; its ownership is transferred to this fucntion. */ +static void +run_a_task (ctrl_t ctrl, wqitem_t item) +{ + log_assert (!item->next); + + if (opt.verbose) + log_info ("session %u: running %s(\"%s%s\")\n", + item->session_id, + item->func? item->func (NULL, NULL): "nop", + item->args, strlen (item->args) > 100? "[...]":""); + if (item->func) + item->func (ctrl, item->args); + + xfree (item); +} + + +/* Run tasks not associated with a session. This is called from the + * ticker every few minutes. If WITH_NETWORK is not set tasks which + * require the network are not run. */ +void +workqueue_run_global_tasks (ctrl_t ctrl, int with_network) +{ + wqitem_t item, prev; + + with_network = !!with_network; + + if (opt.verbose) + log_info ("running scheduled tasks%s\n", with_network?" (with network)":""); + + for (;;) + { + prev = NULL; + for (item = workqueue; item; prev = item, item = item->next) + if (!item->session_id + && (!item->need_network || (item->need_network && with_network))) + break; + if (!item) + break; /* No more tasks to run. */ + + /* Detach that item from the workqueue. */ + if (!prev) + workqueue = item->next; + else + prev->next = item->next; + item->next = NULL; + + /* Run the task. */ + run_a_task (ctrl, item); + } +} + + +/* Run tasks scheduled for running after a session. Those tasks are + * identified by the SESSION_ID. */ +void +workqueue_run_post_session_tasks (unsigned int session_id) +{ + struct server_control_s ctrlbuf; + ctrl_t ctrl = NULL; + wqitem_t item, prev; + + if (!session_id) + return; + + for (;;) + { + prev = NULL; + for (item = workqueue; item; prev = item, item = item->next) + if (item->session_id == session_id) + break; + if (!item) + break; /* No more tasks for this session. */ + + /* Detach that item from the workqueue. */ + if (!prev) + workqueue = item->next; + else + prev->next = item->next; + item->next = NULL; + + /* Create a CTRL object the first time we need it. */ + if (!ctrl) + { + memset (&ctrlbuf, 0, sizeof ctrlbuf); + ctrl = &ctrlbuf; + dirmngr_init_default_ctrl (ctrl); + } + + /* Run the task. */ + run_a_task (ctrl, item); + } + + dirmngr_deinit_default_ctrl (ctrl); +} |