aboutsummaryrefslogtreecommitdiffstats
path: root/dirmngr
diff options
context:
space:
mode:
Diffstat (limited to 'dirmngr')
-rw-r--r--dirmngr/certcache.c5
-rw-r--r--dirmngr/crlcache.c7
-rw-r--r--dirmngr/crlfetch.c163
-rw-r--r--dirmngr/dirmngr.c3
-rw-r--r--dirmngr/dirmngr_ldap.c51
-rw-r--r--dirmngr/http-ntbtls.c4
-rw-r--r--dirmngr/http.c60
-rw-r--r--dirmngr/http.h6
-rw-r--r--dirmngr/ks-action.c6
-rw-r--r--dirmngr/ks-engine-hkp.c6
-rw-r--r--dirmngr/ks-engine-http.c41
-rw-r--r--dirmngr/ks-engine.h10
-rw-r--r--dirmngr/ldap-wrapper-ce.c1
-rw-r--r--dirmngr/ldap-wrapper.c757
-rw-r--r--dirmngr/ldap.c12
-rw-r--r--dirmngr/loadswdb.c2
-rw-r--r--dirmngr/server.c121
17 files changed, 778 insertions, 477 deletions
diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c
index 56629fdda..adb005ec8 100644
--- a/dirmngr/certcache.c
+++ b/dirmngr/certcache.c
@@ -423,6 +423,9 @@ load_certs_from_dir (const char *dirname, unsigned int trustclass)
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
@@ -763,6 +766,8 @@ cert_cache_deinit (int full)
}
}
+ http_register_cfg_ca (NULL);
+
total_nonperm_certificates = 0;
any_cert_of_class = 0;
initialization_done = 0;
diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c
index 8687c7bf4..fbe3beea1 100644
--- a/dirmngr/crlcache.c
+++ b/dirmngr/crlcache.c
@@ -125,6 +125,9 @@
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"; */
@@ -1139,7 +1142,7 @@ lock_db_file (crl_cache_t cache, crl_cache_entry_t entry)
xfree (fname);
return NULL;
}
- fd = open (fname, O_RDONLY);
+ fd = open (fname, O_RDONLY | O_BINARY);
if (fd == -1)
{
log_error (_("error opening cache file '%s': %s\n"),
@@ -2051,7 +2054,7 @@ crl_cache_insert (ctrl_t ctrl, const char *url, ksba_reader_t reader)
}
}
- fd_cdb = open (fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ fd_cdb = open (fname, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644);
if (fd_cdb == -1)
{
err = gpg_error_from_errno (errno);
diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c
index 0892421e9..57ac51b93 100644
--- a/dirmngr/crlfetch.c
+++ b/dirmngr/crlfetch.c
@@ -28,6 +28,7 @@
#include "dirmngr.h"
#include "misc.h"
#include "http.h"
+#include "ks-engine.h" /* For ks_http_fetch. */
#if USE_LDAP
# include "ldap-wrapper.h"
@@ -154,41 +155,17 @@ crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader)
{
gpg_error_t err;
parsed_uri_t uri;
- char *free_this = NULL;
- int redirects_left = 2; /* We allow for 2 redirect levels. */
+ estream_t httpfp = NULL;
*reader = NULL;
if (!url)
return gpg_error (GPG_ERR_INV_ARG);
- once_more:
err = http_parse_uri (&uri, url, 0);
http_release_parsed_uri (uri);
- if (err && !strncmp (url, "https:", 6))
- {
- /* FIXME: We now support https.
- * Our HTTP code does not support TLS, thus we can't use this
- * scheme and it is frankly not useful for CRL retrieval anyway.
- * We resort to using http, assuming that the server also
- * provides plain http access. */
- free_this = xtrymalloc (strlen (url) + 1);
- if (free_this)
- {
- strcpy (stpcpy (free_this,"http:"), url+6);
- err = http_parse_uri (&uri, free_this, 0);
- http_release_parsed_uri (uri);
- if (!err)
- {
- log_info (_("using \"http\" instead of \"https\"\n"));
- url = free_this;
- }
- }
- }
if (!err) /* Yes, our HTTP code groks that. */
{
- http_t hd;
-
if (opt.disable_http)
{
log_error (_("CRL access not possible due to disabled %s\n"),
@@ -196,97 +173,57 @@ crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else
- err = http_open_document (&hd, url, NULL,
- ((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
- |(DBG_LOOKUP? HTTP_FLAG_LOG_RESP:0)
- |(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);
-
- switch ( err? 99999 : http_get_status_code (hd) )
{
- case 200:
- {
- estream_t fp = http_get_read_ptr (hd);
- struct reader_cb_context_s *cb_ctx;
-
- cb_ctx = xtrycalloc (1, sizeof *cb_ctx);
- if (!cb_ctx)
- err = gpg_error_from_syserror ();
- if (!err)
- err = ksba_reader_new (reader);
- if (!err)
- {
- cb_ctx->fp = fp;
- err = ksba_reader_set_cb (*reader, &my_es_read, cb_ctx);
- }
- if (err)
- {
- log_error (_("error initializing reader object: %s\n"),
- gpg_strerror (err));
- ksba_reader_release (*reader);
- *reader = NULL;
- http_close (hd, 0);
- }
- else
- {
- /* The ksba reader misses a user pointer thus we need
- to come up with our own way of associating a file
- pointer (or well the callback context) with the
- reader. It is only required when closing the
- reader thus there is no performance issue doing it
- this way. FIXME: We now have a close notification
- which might be used here. */
- register_file_reader (*reader, cb_ctx);
- http_close (hd, 1);
- }
- }
- break;
+ /* 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);
+ }
- case 301: /* Redirection (perm.). */
- case 302: /* Redirection (temp.). */
- {
- const char *s = http_get_header (hd, "Location");
-
- log_info (_("URL '%s' redirected to '%s' (%u)\n"),
- url, s?s:"[none]", http_get_status_code (hd));
- if (s && *s && redirects_left-- )
- {
- xfree (free_this); url = NULL;
- free_this = xtrystrdup (s);
- if (!free_this)
- err = gpg_error_from_errno (errno);
- else
- {
- url = free_this;
- http_close (hd, 0);
- /* Note, that our implementation of redirection
- actually handles a redirect to LDAP. */
- goto once_more;
- }
- }
- else
- err = gpg_error (GPG_ERR_NO_DATA);
- log_error (_("too many redirections\n")); /* Or no "Location". */
- http_close (hd, 0);
- }
- break;
-
- case 99999: /* Made up status code for error reporting. */
- log_error (_("error retrieving '%s': %s\n"),
- url, gpg_strerror (err));
- break;
-
- default:
- log_error (_("error retrieving '%s': http status %u\n"),
- url, http_get_status_code (hd));
- err = gpg_error (GPG_ERR_NO_DATA);
- http_close (hd, 0);
+ 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 try other schemes. */
+ else /* Let the LDAP code parse other schemes. */
{
if (opt.disable_ldap)
{
@@ -310,7 +247,7 @@ crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader)
}
}
- xfree (free_this);
+ es_fclose (httpfp);
return err;
}
diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c
index 00caf0686..6fdfe36c2 100644
--- a/dirmngr/dirmngr.c
+++ b/dirmngr/dirmngr.c
@@ -2243,7 +2243,8 @@ handle_connections (assuan_fd_t listen_fd)
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
- ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, npth_sigev_sigmask());
+ ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
+ npth_sigev_sigmask());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
diff --git a/dirmngr/dirmngr_ldap.c b/dirmngr/dirmngr_ldap.c
index 5be4e5814..8452c3ba0 100644
--- a/dirmngr/dirmngr_ldap.c
+++ b/dirmngr/dirmngr_ldap.c
@@ -29,7 +29,6 @@
# include <signal.h>
#endif
#include <errno.h>
-#include <assert.h>
#include <sys/time.h>
#include <unistd.h>
#ifndef USE_LDAPWRAPPER
@@ -343,7 +342,7 @@ ldap_wrapper_main (char **argv, estream_t outstream)
usage (1);
#else
/* All passed arguments should be fine in this case. */
- assert (argc);
+ log_assert (argc);
#endif
#ifdef USE_LDAPWRAPPER
@@ -382,16 +381,56 @@ catch_alarm (int dummy)
}
#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
- /* FIXME for W32. */
- (void)myopt;
+ 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
- if (myopt->alarm_timeout)
- alarm (myopt->alarm_timeout);
+ alarm (myopt->alarm_timeout);
#endif
+ }
}
diff --git a/dirmngr/http-ntbtls.c b/dirmngr/http-ntbtls.c
index ea66a4d73..ed4cdd496 100644
--- a/dirmngr/http-ntbtls.c
+++ b/dirmngr/http-ntbtls.c
@@ -87,13 +87,15 @@ gnupg_http_tls_verify_cb (void *opaque,
}
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 thye standard system 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;
diff --git a/dirmngr/http.c b/dirmngr/http.c
index cc7f5a553..049aefc29 100644
--- a/dirmngr/http.c
+++ b/dirmngr/http.c
@@ -318,6 +318,9 @@ 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);
@@ -596,6 +599,35 @@ http_register_tls_ca (const char *fname)
}
+/* 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)
+{
+ 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 (access (fname, F_OK))
+ log_info (_("can't access '%s': %s\n"), fname,
+ gpg_strerror (gpg_error_from_syserror()));
+ 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
@@ -680,6 +712,7 @@ http_session_release (http_session_t sess)
* 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
@@ -793,6 +826,21 @@ http_session_new (http_session_t *r_session,
#endif /* gnutls >= 3.0.20 */
}
+ /* Add other configured certificates to the session. */
+ if ((flags & HTTP_FLAG_TRUST_CFG))
+ {
+ 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)
{
@@ -1688,9 +1736,19 @@ send_request (http_t hd, const char *httphost, const char *auth,
#ifdef USE_TLS
if (hd->uri->use_tls && !hd->session->tls_session)
{
- log_error ("TLS requested but no GNUTLS context available\n");
+ 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))
diff --git a/dirmngr/http.h b/dirmngr/http.h
index 9fa462c05..4cfb4c890 100644
--- a/dirmngr/http.h
+++ b/dirmngr/http.h
@@ -88,8 +88,9 @@ enum
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_NO_CRL = 1024 /* Do not consult CRLs for https. */
+ 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. */
};
@@ -110,6 +111,7 @@ 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));
diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c
index 38cd02feb..c1ecafb58 100644
--- a/dirmngr/ks-action.c
+++ b/dirmngr/ks-action.c
@@ -257,7 +257,9 @@ ks_action_get (ctrl_t ctrl, uri_item_t keyservers,
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, &infp);
+ err = ks_http_fetch (ctrl, uri->parsed_uri->original,
+ KS_HTTP_FETCH_NOCACHE,
+ &infp);
else
BUG ();
@@ -314,7 +316,7 @@ ks_action_fetch (ctrl_t ctrl, const char *url, estream_t outfp)
if (parsed_uri->is_http)
{
- err = ks_http_fetch (ctrl, url, &infp);
+ err = ks_http_fetch (ctrl, url, KS_HTTP_FETCH_NOCACHE, &infp);
if (!err)
{
err = copy_stream (infp, outfp);
diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c
index a9bb93666..32840e68e 100644
--- a/dirmngr/ks-engine-hkp.c
+++ b/dirmngr/ks-engine-hkp.c
@@ -55,7 +55,7 @@
/* Number of seconds after a host is marked as resurrected. */
-#define RESURRECT_INTERVAL (3600*3) /* 3 hours */
+#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. */
@@ -110,7 +110,7 @@ static hostinfo_t *hosttable;
static int hosttable_size;
/* The number of host slots we initially allocate for HOSTTABLE. */
-#define INITIAL_HOSTTABLE_SIZE 10
+#define INITIAL_HOSTTABLE_SIZE 50
/* Create a new hostinfo object, fill in NAME and put it into
@@ -583,7 +583,7 @@ map_host (ctrl_t ctrl, const char *name, const char *srvtag, int force_reselect,
/* Deal with the pool name before selecting a host. */
if (r_httphost)
{
- *r_httphost = xtrystrdup (hi->cname? hi->cname : hi->name);
+ *r_httphost = xtrystrdup (hi->name);
if (!*r_httphost)
return gpg_error_from_syserror ();
}
diff --git a/dirmngr/ks-engine-http.c b/dirmngr/ks-engine-http.c
index 6492dda8a..946c92769 100644
--- a/dirmngr/ks-engine-http.c
+++ b/dirmngr/ks-engine-http.c
@@ -62,12 +62,17 @@ ks_http_help (ctrl_t ctrl, parsed_uri_t uri)
/* 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. */
+ * 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, estream_t *r_fp)
+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;
int redirects_left = MAX_REDIRECTS;
estream_t fp = NULL;
@@ -81,12 +86,16 @@ ks_http_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp)
is_onion = uri->onion;
is_https = uri->use_tls;
- once_more:
- /* Note that we only use the system provided certificates with the
+ /* By default we only use the system provided certificates with this
* fetch command. */
- err = http_session_new (&session, NULL,
- ((ctrl->http_no_crl? HTTP_FLAG_NO_CRL : 0)
- | HTTP_FLAG_TRUST_SYS),
+ 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;
@@ -100,6 +109,7 @@ ks_http_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp)
/* 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)),
@@ -111,10 +121,11 @@ ks_http_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp)
{
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);
+ * 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 ();
@@ -164,7 +175,13 @@ ks_http_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp)
if (err)
goto leave;
- if ((is_onion && ! uri->onion) || (is_https && ! uri->use_tls))
+ if (is_onion && !uri->onion)
+ {
+ err = gpg_error (GPG_ERR_FORBIDDEN);
+ goto leave;
+ }
+ if (!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE)
+ && is_https && !uri->use_tls)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h
index b5b4dd08b..d28c6ab71 100644
--- a/dirmngr/ks-engine.h
+++ b/dirmngr/ks-engine.h
@@ -41,8 +41,16 @@ 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, estream_t *r_fp);
+gpg_error_t ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags,
+ estream_t *r_fp);
/*-- ks-engine-finger.c --*/
diff --git a/dirmngr/ldap-wrapper-ce.c b/dirmngr/ldap-wrapper-ce.c
index 01f8f647e..884bb325d 100644
--- a/dirmngr/ldap-wrapper-ce.c
+++ b/dirmngr/ldap-wrapper-ce.c
@@ -45,6 +45,7 @@
#ifdef USE_LDAPWRAPPER
# error This module is not expected to be build.
#endif
+#error This module might not anymore work.
diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c
index 8b53bd60f..d01c4808e 100644
--- a/dirmngr/ldap-wrapper.c
+++ b/dirmngr/ldap-wrapper.c
@@ -1,5 +1,5 @@
/* ldap-wrapper.c - LDAP access via a wrapper process
- * Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH
+ * Copyright (C) 2004, 2005, 2007, 2008, 2018 g10 Code GmbH
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
@@ -19,31 +19,34 @@
*/
/*
- We can't use LDAP directly for these reasons:
-
- 1. On some systems the LDAP library uses (indirectly) pthreads and
- that is not compatible with PTh.
-
- 2. It is huge library in particular if TLS comes into play. So
- problems with unfreed memory might turn up and we don't want
- this in a long running daemon.
-
- 3. There is no easy way for timeouts. In particular the timeout
- value does not work for DNS lookups (well, this is usual) and it
- seems not to work while loading a large attribute like a
- CRL. Having a separate process allows us to either tell the
- process to commit suicide or have our own housekepping function
- kill it after some time. The latter also allows proper
- cancellation of a query at any point of time.
-
- 4. Given that we are going out to the network and usually get back
- a long response, the fork/exec overhead is acceptable.
-
- Note that under WindowsCE the number of processes is strongly
- limited (32 processes including the kernel processes) and thus we
- don't use the process approach but implement a different wrapper in
- ldap-wrapper-ce.c.
-*/
+ * 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>
@@ -89,39 +92,66 @@ struct wrapper_context_s
{
struct wrapper_context_s *next;
- pid_t pid; /* The pid of the wrapper process. */
- int printable_pid; /* Helper to print diagnostics after the process has
- been cleaned up. */
- int fd; /* Connected with stdout of the ldap wrapper. */
- gpg_error_t fd_error; /* Set to the gpg_error of the last read error
- if any. */
- int log_fd; /* Connected with stderr of the ldap wrapper. */
- ctrl_t ctrl; /* Connection data. */
- int ready; /* Internally used to mark to be removed contexts. */
- ksba_reader_t reader; /* The ksba reader object or NULL. */
- char *line; /* Used to print the log lines (malloced). */
- size_t linesize;/* Allocated size of LINE. */
- size_t linelen; /* Use size of LINE. */
- time_t stamp; /* The last time we noticed ativity. */
+ 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. */
-static struct wrapper_context_s *wrapper_list;
+/* 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 pth file descriptor FD and set it to -1. */
-#define SAFE_CLOSE(fd) \
- do { int _fd = fd; if (_fd != -1) { close (_fd); fd = -1;} } while (0)
+
+
+/* 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)
@@ -151,8 +181,8 @@ destroy_wrapper (struct wrapper_context_s *ctx)
gnupg_release_process (ctx->pid);
}
ksba_reader_release (ctx->reader);
- SAFE_CLOSE (ctx->fd);
- SAFE_CLOSE (ctx->log_fd);
+ SAFE_CLOSE (ctx->fp);
+ SAFE_CLOSE (ctx->log_fp);
xfree (ctx->line);
xfree (ctx);
}
@@ -218,25 +248,27 @@ print_log_line (struct wrapper_context_s *ctx, char *line)
/* Read data from the log stream. Returns true if the log stream
- indicated EOF or error. */
+ * indicated EOF or error. */
static int
read_log_data (struct wrapper_context_s *ctx)
{
- int n;
+ int rc;
+ size_t n;
char line[256];
- /* We must use the npth_read function for pipes, always. */
- do
- n = npth_read (ctx->log_fd, line, sizeof line - 1);
- while (n < 0 && errno == EINTR);
-
- if (n <= 0) /* EOF or error. */
+ rc = es_read (ctx->log_fp, line, sizeof line - 1, &n);
+ if (rc || !n) /* Error or EOF. */
{
- if (n < 0)
- log_error (_("error reading log from ldap wrapper %d: %s\n"),
- (int)ctx->pid, strerror (errno));
- print_log_line (ctx, NULL);
- SAFE_CLOSE (ctx->log_fd);
+ 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;
}
@@ -251,15 +283,18 @@ read_log_data (struct wrapper_context_s *ctx)
/* This function is run by a separate thread to maintain the list of
wrappers and to log error messages from these wrappers. */
void *
-ldap_wrapper_thread (void *dummy)
+ldap_reaper_thread (void *dummy)
{
- int nfds;
+ gpg_error_t err;
struct wrapper_context_s *ctx;
struct wrapper_context_s *ctx_prev;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
- fd_set fdset;
+ int millisecs;
+ gpgrt_poll_t *fparray = NULL;
+ int fparraysize = 0;
+ int count, i;
int ret;
time_t exptime;
@@ -272,6 +307,61 @@ ldap_wrapper_thread (void *dummy)
{
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, <)))
{
@@ -280,142 +370,166 @@ ldap_wrapper_thread (void *dummy)
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;
- FD_ZERO (&fdset);
- nfds = -1;
- for (ctx = wrapper_list; ctx; ctx = ctx->next)
+ if (DBG_EXTPROG)
{
- if (ctx->log_fd != -1)
- {
- FD_SET (ctx->log_fd, &fdset);
- if (ctx->log_fd > nfds)
- nfds = ctx->log_fd;
- }
+ 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);
}
- nfds++;
- /* FIXME: For Windows, we have to use a reader thread on the
- pipe that signals an event (and a npth_select_ev variant). */
- ret = npth_pselect (nfds + 1, &fdset, NULL, NULL, &timeout, NULL);
- if (ret == -1)
+ ret = es_poll (fparray, fparraysize, millisecs);
+ if (ret < 0)
{
- if (errno != EINTR)
- {
- log_error (_("npth_select failed: %s - waiting 1s\n"),
- strerror (errno));
- npth_sleep (1);
- }
+ 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;
- /* Note that there is no need to lock the list because we always
- add entries at the head (with a pending event status) and
- thus traversing the list will even work if we have a context
- switch in waitpid (which should anyway only happen with Pth's
- hard system call mapping). */
- for (ctx = wrapper_list; ctx; ctx = ctx->next)
- {
- /* Check whether there is any logging to be done. */
- if (nfds && ctx->log_fd != -1 && FD_ISSET (ctx->log_fd, &fdset))
- {
- if (read_log_data (ctx))
- {
- SAFE_CLOSE (ctx->log_fd);
- any_action = 1;
- }
- }
-
- /* Check whether the process is still running. */
- if (ctx->pid != (pid_t)(-1))
- {
- gpg_error_t err;
- int status;
-
- err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0,
- &status);
- if (!err)
- {
- log_info (_("ldap wrapper %d ready"), (int)ctx->pid);
- ctx->ready = 1;
- gnupg_release_process (ctx->pid);
- ctx->pid = (pid_t)(-1);
- any_action = 1;
- }
- else if (gpg_err_code (err) == GPG_ERR_GENERAL)
- {
- if (status == 10)
- log_info (_("ldap wrapper %d ready: timeout\n"),
- (int)ctx->pid);
- else
- log_info (_("ldap wrapper %d ready: exitcode=%d\n"),
- (int)ctx->pid, status);
- ctx->ready = 1;
- gnupg_release_process (ctx->pid);
- ctx->pid = (pid_t)(-1);
- any_action = 1;
- }
- else if (gpg_err_code (err) != GPG_ERR_TIMEOUT)
- {
- log_error (_("waiting for ldap wrapper %d failed: %s\n"),
- (int)ctx->pid, gpg_strerror (err));
- any_action = 1;
- }
- }
-
- /* Check whether we should terminate the process. */
- if (ctx->pid != (pid_t)(-1)
- && ctx->stamp != (time_t)(-1) && ctx->stamp < exptime)
- {
- gnupg_kill_process (ctx->pid);
- ctx->stamp = (time_t)(-1);
- log_info (_("ldap wrapper %d stalled - killing\n"),
- (int)ctx->pid);
- /* We need to close the log fd because the cleanup loop
- waits for it. */
- SAFE_CLOSE (ctx->log_fd);
- any_action = 1;
- }
- }
-
- /* If something has been printed to the log file or we got an
- EOF from a wrapper, we now print the list of active
- wrappers. */
- if (any_action && DBG_LOOKUP)
- {
- log_info ("ldap worker stati:\n");
- for (ctx = wrapper_list; ctx; ctx = ctx->next)
- log_info (" c=%p pid=%d/%d rdr=%p ctrl=%p/%d la=%lu rdy=%d\n",
- ctx,
- (int)ctx->pid, (int)ctx->printable_pid,
- ctx->reader,
- ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0,
- (unsigned long)ctx->stamp, ctx->ready);
- }
+ 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);
+ }
- /* Use a separate loop to check whether ready marked wrappers
- may be removed. We may only do so if the ksba reader object
- is not anymore in use or we are in shutdown state. */
- again:
- for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next)
- if (ctx->ready
- && ((ctx->log_fd == -1 && !ctx->reader) || shutting_down))
+ /* 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_prev)
- ctx_prev->next = ctx->next;
- else
- wrapper_list = ctx->next;
- destroy_wrapper (ctx);
- /* We need to restart because destroy_wrapper might have
- done a context switch. */
- goto again;
+ 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. */
}
@@ -424,7 +538,7 @@ ldap_wrapper_thread (void *dummy)
/* Start the reaper thread for the ldap wrapper. */
void
-ldap_wrapper_launch_thread (void)
+ldap_reaper_launch_thread (void)
{
static int done;
npth_attr_t tattr;
@@ -435,14 +549,21 @@ ldap_wrapper_launch_thread (void)
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);
- err = npth_create (&thread, &tattr, ldap_wrapper_thread, NULL);
- if (err)
+ if (npth_create (&thread, &tattr, ldap_reaper_thread, NULL))
{
- log_error (_("error spawning ldap wrapper reaper thread: %s\n"),
- strerror (err) );
+ 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");
@@ -451,16 +572,20 @@ ldap_wrapper_launch_thread (void)
-
-
/* Wait until all ldap wrappers have terminated. We assume that the
kill has already been sent to all of them. */
void
ldap_wrapper_wait_connections ()
{
- shutting_down = 1;
- /* FIXME: This is a busy wait. */
- while (wrapper_list)
+ 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);
}
@@ -475,30 +600,35 @@ ldap_wrapper_release_context (ksba_reader_t reader)
if (!reader )
return;
- for (ctx=wrapper_list; ctx; ctx=ctx->next)
- if (ctx->reader == reader)
- {
- if (DBG_LOOKUP)
- log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n",
- ctx,
- (int)ctx->pid, (int)ctx->printable_pid,
- ctx->reader,
- ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0);
-
- ctx->reader = NULL;
- SAFE_CLOSE (ctx->fd);
- if (ctx->ctrl)
- {
- ctx->ctrl->refcount--;
- ctx->ctrl = NULL;
- }
- if (ctx->fd_error)
- log_info (_("reading from ldap wrapper %d failed: %s\n"),
- ctx->printable_pid, gpg_strerror (ctx->fd_error));
- break;
- }
+ 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
@@ -506,41 +636,45 @@ ldap_wrapper_connection_cleanup (ctrl_t ctrl)
{
struct wrapper_context_s *ctx;
- for (ctx=wrapper_list; ctx; ctx=ctx->next)
- if (ctx->ctrl && ctx->ctrl == ctrl)
- {
- ctx->ctrl->refcount--;
- ctx->ctrl = NULL;
- if (ctx->pid != (pid_t)(-1))
- gnupg_kill_process (ctx->pid);
- if (ctx->fd_error)
- log_info (_("reading from ldap wrapper %d failed: %s\n"),
- ctx->printable_pid, gpg_strerror (ctx->fd_error));
- }
+ 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 wrappers stdout. See the description of
- ksba_reader_set_cb for details. */
+ * 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;
- int nfds;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
- int saved_errno;
- fd_set fdset, read_fdset;
+ 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
- the it would be cumbersome to get back already buffered
- stuff). */
+ then it would be cumbersome to get back already buffered stuff). */
if (!buffer && !count && !nread)
return -1; /* Rewind is not supported. */
@@ -548,81 +682,108 @@ reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread)
/* 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->fd_error || ctx->fd == -1)
+ if (ctx->fp_err || !ctx->fp)
{
*nread = 0;
return -1;
}
- FD_ZERO (&fdset);
- FD_SET (ctx->fd, &fdset);
- nfds = ctx->fd + 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)
{
- int n;
- gpg_error_t err;
-
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
err = dirmngr_tick (ctx->ctrl);
if (err)
{
- ctx->fd_error = err;
- SAFE_CLOSE (ctx->fd);
+ 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;
- read_fdset = fdset;
- ret = npth_pselect (nfds, &read_fdset, NULL, NULL, &timeout, NULL);
- saved_errno = errno;
+ if (DBG_EXTPROG)
+ {
+ log_debug ("%s: fp[0] stream=%p want=%d\n",
+ __func__, fparray[0].stream,fparray[0].want_read);
+ }
- if (ret == -1 && saved_errno != EINTR)
+ ret = es_poll (fparray, DIM (fparray), millisecs);
+ if (ret < 0)
{
- ctx->fd_error = gpg_error_from_errno (errno);
- SAFE_CLOSE (ctx->fd);
+ 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 (ret <= 0)
- /* Timeout. Will be handled when calculating the next timeout. */
- continue;
-
- /* This should not block now that select returned with a file
- descriptor. So it shouldn't be necessary to use npth_read
- (and it is slightly dangerous in the sense that a concurrent
- thread might (accidentially?) change the status of ctx->fd
- before we read. FIXME: Set ctx->fd to nonblocking? */
- n = read (ctx->fd, buffer, nleft);
- if (n < 0)
+ if (DBG_EXTPROG)
{
- ctx->fd_error = gpg_error_from_errno (errno);
- SAFE_CLOSE (ctx->fd);
- return -1;
+ 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':'-');
}
- else if (!n)
+ if (!ret)
{
- if (nleft == count)
- return -1; /* EOF. */
- break;
+ /* 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);
}
- 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.
@@ -646,7 +807,7 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
int j;
const char **arg_list;
const char *pgmname;
- int outpipe[2], errpipe[2];
+ 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
@@ -656,7 +817,7 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
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_wrapper_launch_thread ();
+ ldap_reaper_launch_thread ();
*reader = NULL;
@@ -696,41 +857,21 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
return err;
}
- err = gnupg_create_inbound_pipe (outpipe, NULL, 0);
- if (!err)
- {
- err = gnupg_create_inbound_pipe (errpipe, NULL, 0);
- if (err)
- {
- close (outpipe[0]);
- close (outpipe[1]);
- }
- }
- if (err)
- {
- log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
- xfree (arg_list);
- xfree (ctx);
- return err;
- }
-
- err = gnupg_spawn_process_fd (pgmname, arg_list,
- -1, outpipe[1], errpipe[1], &pid);
+ err = gnupg_spawn_process (pgmname, arg_list,
+ NULL, NULL, GNUPG_SPAWN_NONBLOCK,
+ NULL, &outfp, &errfp, &pid);
xfree (arg_list);
- close (outpipe[1]);
- close (errpipe[1]);
if (err)
{
- close (outpipe[0]);
- close (errpipe[0]);
xfree (ctx);
+ log_error ("error running '%s': %s\n", pgmname, gpg_strerror (err));
return err;
}
ctx->pid = pid;
ctx->printable_pid = (int) pid;
- ctx->fd = outpipe[0];
- ctx->log_fd = errpipe[0];
+ ctx->fp = outfp;
+ ctx->log_fp = errfp;
ctx->ctrl = ctrl;
ctrl->refcount++;
ctx->stamp = time (NULL);
@@ -749,12 +890,20 @@ ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[])
}
/* Hook the context into our list of running wrappers. */
- ctx->reader = *reader;
- ctx->next = wrapper_list;
- wrapper_list = ctx;
- if (opt.verbose)
- log_info ("ldap wrapper %d started (reader %p)\n",
- (int)ctx->pid, ctx->reader);
+ 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
diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c
index adf83071e..cb3c0b763 100644
--- a/dirmngr/ldap.c
+++ b/dirmngr/ldap.c
@@ -136,8 +136,12 @@ run_ldap_wrapper (ctrl_t ctrl,
argv[argc++] = "--pass";
argv[argc++] = pass;
}
- if (opt.verbose)
+
+ if (DBG_LOOKUP)
argv[argc++] = "-vv";
+ else if (DBG_EXTPROG)
+ argv[argc++] = "-v";
+
argv[argc++] = "--log-with-pid";
if (multi_mode)
argv[argc++] = "--multi";
@@ -564,8 +568,12 @@ start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *context,
argv[argc++] = "--pass";
argv[argc++] = pass;
}
- if (opt.verbose)
+
+ if (DBG_LOOKUP)
argv[argc++] = "-vv";
+ else if (DBG_EXTPROG)
+ argv[argc++] = "-v";
+
argv[argc++] = "--log-with-pid";
argv[argc++] = "--multi";
if (opt.ldaptimeout)
diff --git a/dirmngr/loadswdb.c b/dirmngr/loadswdb.c
index bc004665d..fb883722a 100644
--- a/dirmngr/loadswdb.c
+++ b/dirmngr/loadswdb.c
@@ -126,7 +126,7 @@ fetch_file (ctrl_t ctrl, const char *url, estream_t *r_fp)
size_t nread, nwritten;
char buffer[1024];
- if ((err = ks_http_fetch (ctrl, url, &httpfp)))
+ 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.
diff --git a/dirmngr/server.c b/dirmngr/server.c
index 60d980211..b7cdb24c9 100644
--- a/dirmngr/server.c
+++ b/dirmngr/server.c
@@ -1105,7 +1105,7 @@ cmd_ldapserver (assuan_context_t ctx, char *line)
static const char hlp_isvalid[] =
"ISVALID [--only-ocsp] [--force-default-responder]"
- " <certificate_id>|<certificate_fpr>\n"
+ " <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"
@@ -1117,8 +1117,9 @@ static const char hlp_isvalid[] =
"delimited by a single dot. The first part is the SHA-1 hash of the\n"
"issuer name and the second part the serial number.\n"
"\n"
- "Alternatively the certificate's fingerprint may be given in which\n"
- "case an OCSP request is done before consulting the CRL.\n"
+ "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"
@@ -1130,7 +1131,7 @@ static gpg_error_t
cmd_isvalid (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
- char *issuerhash, *serialno;
+ char *issuerhash, *serialno, *fpr;
gpg_error_t err;
int did_inquire = 0;
int ocsp_mode = 0;
@@ -1141,25 +1142,36 @@ cmd_isvalid (assuan_context_t ctx, char *line)
force_default_responder = has_option (line, "--force-default-responder");
line = skip_options (line);
- issuerhash = xstrdup (line); /* We need to work on a copy of the
- line because that same Assuan
- context may be used for an inquiry.
- That is because Assuan reuses its
- line buffer.
- */
+ /* 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)
- *serialno++ = 0;
- else
+ if (!serialno)
+ {
+ xfree (issuerhash);
+ return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID")));
+ }
+ *serialno++ = 0;
+ if (strlen (issuerhash) != 40)
{
- char *endp = strchr (issuerhash, ' ');
+ 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 (issuerhash) != 40)
+ if (strlen (fpr) != 40)
{
xfree (issuerhash);
- return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID")));
+ return leave_cmd (ctx, PARM_ERROR ("fingerprint too short"));
}
ocsp_mode = 1;
}
@@ -1168,17 +1180,24 @@ cmd_isvalid (assuan_context_t ctx, char *line)
again:
if (ocsp_mode)
{
- /* Note, that we ignore the given issuer hash and instead rely
- on the current certificate semantics used with this
- command. */
+ /* 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);
- /* Fixme: If we got no ocsp response and --only-ocsp is not used
- we should fall back to CRL mode. Thus we need to clear
- OCSP_MODE, get the issuerhash and the serialno from the
- current certificate and jump to again. */
+
+ 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);
@@ -1858,7 +1877,7 @@ static const char hlp_validate[] =
" INQUIRE CERTLIST\n"
"\n"
"Here the first certificate is the target certificate, the remaining\n"
- "certificates are suggested intermediary certificates. All certifciates\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"
@@ -1909,7 +1928,7 @@ cmd_validate (assuan_context_t ctx, char *line)
err = gpg_error (GPG_ERR_MISSING_CERT);
if (!err)
{
- /* Extraxt the first certificate from the list. */
+ /* Extract the first certificate from the list. */
cert = certlist->cert;
ksba_cert_ref (cert);
}
@@ -1978,6 +1997,38 @@ make_keyserver_item (const char *uri, uri_item_t *r_item)
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 ();
@@ -2489,7 +2540,8 @@ static const char hlp_getinfo[] =
"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";
+ "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)
{
@@ -2557,6 +2609,23 @@ cmd_getinfo (assuan_context_t ctx, char *line)
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");