summaryrefslogtreecommitdiffstats
path: root/dirmngr
diff options
context:
space:
mode:
Diffstat (limited to 'dirmngr')
-rw-r--r--dirmngr/ChangeLog-20112407
-rw-r--r--dirmngr/Makefile.am193
-rw-r--r--dirmngr/Makefile.in1837
-rw-r--r--dirmngr/OAUTHORS38
-rw-r--r--dirmngr/ONEWS240
-rw-r--r--dirmngr/cdb.h94
-rw-r--r--dirmngr/cdblib.c928
-rw-r--r--dirmngr/certcache.c1861
-rw-r--r--dirmngr/certcache.h128
-rw-r--r--dirmngr/crlcache.c2757
-rw-r--r--dirmngr/crlcache.h70
-rw-r--r--dirmngr/crlfetch.c589
-rw-r--r--dirmngr/crlfetch.h88
-rw-r--r--dirmngr/dirmngr-client.c942
-rw-r--r--dirmngr/dirmngr-err.h12
-rw-r--r--dirmngr/dirmngr-status.h39
-rw-r--r--dirmngr/dirmngr-w32info.rc52
-rw-r--r--dirmngr/dirmngr.c2401
-rw-r--r--dirmngr/dirmngr.h271
-rw-r--r--dirmngr/dirmngr.w32-manifest.in18
-rw-r--r--dirmngr/dirmngr_ldap.c775
-rw-r--r--dirmngr/dns-stuff.c2478
-rw-r--r--dirmngr/dns-stuff.h176
-rw-r--r--dirmngr/dns.c11570
-rw-r--r--dirmngr/dns.h1321
-rw-r--r--dirmngr/domaininfo.c378
-rw-r--r--dirmngr/http-common.c50
-rw-r--r--dirmngr/http-common.h25
-rw-r--r--dirmngr/http-ntbtls.c134
-rw-r--r--dirmngr/http.c3780
-rw-r--r--dirmngr/http.h206
-rw-r--r--dirmngr/ks-action.c412
-rw-r--r--dirmngr/ks-action.h36
-rw-r--r--dirmngr/ks-engine-finger.c127
-rw-r--r--dirmngr/ks-engine-hkp.c1809
-rw-r--r--dirmngr/ks-engine-http.c225
-rw-r--r--dirmngr/ks-engine-kdns.c79
-rw-r--r--dirmngr/ks-engine-ldap.c2248
-rw-r--r--dirmngr/ks-engine.h75
-rw-r--r--dirmngr/ldap-parse-uri.c258
-rw-r--r--dirmngr/ldap-parse-uri.h33
-rw-r--r--dirmngr/ldap-url.c935
-rw-r--r--dirmngr/ldap-url.h50
-rw-r--r--dirmngr/ldap-wrapper-ce.c575
-rw-r--r--dirmngr/ldap-wrapper.c931
-rw-r--r--dirmngr/ldap-wrapper.h40
-rw-r--r--dirmngr/ldap.c874
-rw-r--r--dirmngr/ldapserver.c132
-rw-r--r--dirmngr/ldapserver.h90
-rw-r--r--dirmngr/loadswdb.c405
-rw-r--r--dirmngr/misc.c656
-rw-r--r--dirmngr/misc.h91
-rw-r--r--dirmngr/ocsp.c889
-rw-r--r--dirmngr/ocsp.h31
-rw-r--r--dirmngr/server.c3103
-rw-r--r--dirmngr/sks-keyservers.netCA.pem32
-rw-r--r--dirmngr/t-dns-stuff.c313
-rw-r--r--dirmngr/t-http-basic.c199
-rw-r--r--dirmngr/t-http.c497
-rw-r--r--dirmngr/t-ldap-parse-uri.c257
-rw-r--r--dirmngr/t-support.c43
-rw-r--r--dirmngr/t-support.h42
-rw-r--r--dirmngr/tls-ca.pem30
-rw-r--r--dirmngr/validate.c1305
-rw-r--r--dirmngr/validate.h68
-rw-r--r--dirmngr/w32-ldap-help.h169
-rw-r--r--dirmngr/workqueue.c214
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=''; \
+ grn=''; \
+ lgn=''; \
+ blu=''; \
+ mgn=''; \
+ brg=''; \
+ std=''; \
+ 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);
+}