* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
-#include <assert.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include "gpg.h"
#include <assuan.h>
-#include "util.h"
-#include "membuf.h"
+#include "../common/util.h"
+#include "../common/membuf.h"
#include "options.h"
-#include "i18n.h"
-#include "asshelp.h"
-#include "keyserver.h"
+#include "../common/i18n.h"
+#include "../common/asshelp.h"
+#include "../common/keyserver.h"
+#include "../common/status.h"
#include "call-dirmngr.h"
-/* Parameter structure used to gather status info. */
+/* Parameter structure used to gather status info. Note that it is
+ * also used for WKD requests. */
struct ks_status_parm_s
{
+ const char *keyword; /* Look for this keyword or NULL for "SOURCE". */
char *source;
};
};
+/* Parameter structure used with the DNS_CERT command. */
+struct dns_cert_parm_s
+{
+ estream_t memfp;
+ unsigned char *fpr;
+ size_t fprlen;
+ char *url;
+};
+
+
/* Data used to associate an session with dirmngr contexts. We can't
use a simple one to one mapping because we sometimes need two
connections to the dirmngr; for example while doing a listing and
/* The active Assuan context. */
assuan_context_t ctx;
+ /* Flag set when the keyserver names have been send. */
+ int set_keyservers_done;
+
/* Flag set to true while an operation is running on CTX. */
int is_active;
};
}
+/* Print a warning if the server's version number is less than our
+ version number. Returns an error code on a connection problem. */
+static gpg_error_t
+warn_version_mismatch (assuan_context_t ctx, const char *servername)
+{
+ gpg_error_t err;
+ char *serverversion;
+ const char *myversion = strusage (13);
+
+ err = get_assuan_server_version (ctx, 0, &serverversion);
+ if (err)
+ log_error (_("error getting version from '%s': %s\n"),
+ servername, gpg_strerror (err));
+ else if (compare_version_strings (serverversion, myversion) < 0)
+ {
+ char *warn;
+
+ warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
+ servername, serverversion, myversion);
+ if (!warn)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ log_info (_("WARNING: %s\n"), warn);
+ if (!opt.quiet)
+ {
+ log_info (_("Note: Outdated servers may lack important"
+ " security fixes.\n"));
+ log_info (_("Note: Use the command \"%s\" to restart them.\n"),
+ "gpgconf --kill all");
+ }
+
+ write_status_strings (STATUS_WARNING, "server_version_mismatch 0",
+ " ", warn, NULL);
+ xfree (warn);
+ }
+ }
+ xfree (serverversion);
+ return err;
+}
+
+
/* Try to connect to the Dirmngr via a socket or spawn it if possible.
Handle the server's initial greeting and set global options. */
static gpg_error_t
assuan_context_t ctx;
*r_ctx = NULL;
+
+ if (opt.disable_dirmngr)
+ return gpg_error (GPG_ERR_NO_DIRMNGR);
+
err = start_new_dirmngr (&ctx,
GPG_ERR_SOURCE_DEFAULT,
- opt.homedir,
opt.dirmngr_program,
opt.autostart, opt.verbose, DBG_IPC,
NULL /*gpg_status2*/, ctrl);
log_info (_("no dirmngr running in this session\n"));
}
}
- else if (!err)
+ else if (!err && !(err = warn_version_mismatch (ctx, DIRMNGR_NAME)))
{
- keyserver_spec_t ksi;
+ char *line;
/* Tell the dirmngr that we want to collect audit event. */
/* err = assuan_transact (agent_ctx, "OPTION audit-events=1", */
/* NULL, NULL, NULL, NULL, NULL, NULL); */
-
- /* Set all configured keyservers. We clear existing keyservers
- so that any keyserver configured in GPG overrides keyservers
- possibly still configured in Dirmngr for the session (Note
- that the keyserver list of a session in Dirmngr survives a
- RESET. */
- for (ksi = opt.keyserver; !err && ksi; ksi = ksi->next)
+ if (opt.keyserver_options.http_proxy)
{
- char *line;
-
- line = xtryasprintf ("KEYSERVER%s %s",
- ksi == opt.keyserver? " --clear":"", ksi->uri);
+ line = xtryasprintf ("OPTION http-proxy=%s",
+ opt.keyserver_options.http_proxy);
if (!line)
err = gpg_error_from_syserror ();
else
{
- err = assuan_transact (ctx, line,
- NULL, NULL, NULL, NULL, NULL, NULL);
+ err = assuan_transact (ctx, line, NULL, NULL, NULL,
+ NULL, NULL, NULL);
xfree (line);
}
}
+
+ if (err)
+ ;
+ else if ((opt.keyserver_options.options & KEYSERVER_HONOR_KEYSERVER_URL))
+ {
+ /* Tell the dirmngr that this possibly privacy invading
+ option is in use. If Dirmngr is running in Tor mode, it
+ will return an error. */
+ err = assuan_transact (ctx, "OPTION honor-keyserver-url-used",
+ NULL, NULL, NULL, NULL, NULL, NULL);
+ if (gpg_err_code (err) == GPG_ERR_FORBIDDEN)
+ log_error (_("keyserver option \"honor-keyserver-url\""
+ " may not be used in Tor mode\n"));
+ else if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
+ err = 0; /* Old dirmngr versions do not support this option. */
+ }
}
if (err)
if (dml)
{
/* Found an inactive local session - return that. */
- assert (!dml->is_active);
+ log_assert (!dml->is_active);
+
+ /* But first do the per session init if not yet done. */
+ if (!dml->set_keyservers_done)
+ {
+ keyserver_spec_t ksi;
+
+ /* Set all configured keyservers. We clear existing
+ keyservers so that any keyserver configured in GPG
+ overrides keyservers possibly still configured in Dirmngr
+ for the session (Note that the keyserver list of a
+ session in Dirmngr survives a RESET. */
+ for (ksi = opt.keyserver; ksi; ksi = ksi->next)
+ {
+ char *line;
+
+ line = xtryasprintf
+ ("KEYSERVER%s %s",
+ ksi == opt.keyserver? " --clear":"", ksi->uri);
+ if (!line)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ err = assuan_transact (dml->ctx, line, NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ xfree (line);
+ }
+
+ if (err)
+ return err;
+ }
+
+ dml->set_keyservers_done = 1;
+ }
+
dml->is_active = 1;
+
*r_ctx = dml->ctx;
return 0;
}
xfree (dml);
return err;
}
+
/* To be on the nPth thread safe site we need to add it to a
list; this is far easier than to have a lock for this
function. It should not happen anyway but the code is free
}
+/* Clear the set_keyservers_done flag on context CTX. */
+static void
+clear_context_flags (ctrl_t ctrl, assuan_context_t ctx)
+{
+ dirmngr_local_t dml;
+
+ if (!ctx)
+ return;
+
+ for (dml = ctrl->dirmngr_local; dml; dml = dml->next)
+ {
+ if (dml->ctx == ctx)
+ {
+ if (!dml->is_active)
+ log_fatal ("clear_context_flags on inactive dirmngr ctx %p\n", ctx);
+ dml->set_keyservers_done = 0;
+ return;
+ }
+ }
+ log_fatal ("clear_context_flags on unknown dirmngr ctx %p\n", ctx);
+}
+
+
\f
-/* Status callback for ks_get and ks_search. */
+/* Status callback for ks_list, ks_get, ks_search, and wkd_get */
static gpg_error_t
ks_status_cb (void *opaque, const char *line)
{
struct ks_status_parm_s *parm = opaque;
gpg_error_t err = 0;
- const char *s;
+ const char *s, *s2;
+ const char *warn;
- if ((s = has_leading_keyword (line, "SOURCE")))
+ if ((s = has_leading_keyword (line, parm->keyword? parm->keyword : "SOURCE")))
{
+ /* Note that the arg for "S SOURCE" is the URL of a keyserver. */
if (!parm->source)
{
parm->source = xtrystrdup (s);
err = gpg_error_from_syserror ();
}
}
+ else if ((s = has_leading_keyword (line, "WARNING")))
+ {
+ if ((s2 = has_leading_keyword (s, "tor_not_running")))
+ warn = _("Tor is not running");
+ else if ((s2 = has_leading_keyword (s, "tor_config_problem")))
+ warn = _("Tor is not properly configured");
+ else
+ warn = NULL;
+
+ if (warn)
+ {
+ log_info (_("WARNING: %s\n"), warn);
+ if (s2)
+ {
+ while (*s2 && !spacep (s2))
+ s2++;
+ while (*s2 && spacep (s2))
+ s2++;
+ if (*s2)
+ print_further_info ("%s", s2);
+ }
+ }
+ }
return err;
}
\f
+/* Run the "KEYSERVER" command to return the name of the used
+ keyserver at R_KEYSERVER. */
+gpg_error_t
+gpg_dirmngr_ks_list (ctrl_t ctrl, char **r_keyserver)
+{
+ gpg_error_t err;
+ assuan_context_t ctx;
+ struct ks_status_parm_s stparm;
+
+ memset (&stparm, 0, sizeof stparm);
+ stparm.keyword = "KEYSERVER";
+ if (r_keyserver)
+ *r_keyserver = NULL;
+
+ err = open_context (ctrl, &ctx);
+ if (err)
+ return err;
+
+ err = assuan_transact (ctx, "KEYSERVER", NULL, NULL,
+ NULL, NULL, ks_status_cb, &stparm);
+ if (err)
+ goto leave;
+ if (!stparm.source)
+ {
+ err = gpg_error (GPG_ERR_NO_KEYSERVER);
+ goto leave;
+ }
+
+ if (r_keyserver)
+ *r_keyserver = stparm.source;
+ else
+ xfree (stparm.source);
+ stparm.source = NULL;
+
+ leave:
+ xfree (stparm.source);
+ close_context (ctrl, ctx);
+ return err;
+}
+
+
+\f
/* Data callback for the KS_SEARCH command. */
static gpg_error_t
ks_search_data_cb (void *opaque, const void *data, size_t datalen)
don't need to escape the patterns before sending them to the
server.
+ If QUICK is set the dirmngr is advised to use a shorter timeout.
+
If R_SOURCE is not NULL the source of the data is stored as a
malloced string there. If a source is not known NULL is stored.
are able to ask for (1000-10-1)/(2+8+1) = 90 keys at once. */
gpg_error_t
gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern,
+ keyserver_spec_t override_keyserver, int quick,
estream_t *r_fp, char **r_source)
{
gpg_error_t err;
if (err)
return err;
+ /* If we have an override keyserver we first indicate that the next
+ user of the context needs to again setup the global keyservers and
+ them we send the override keyserver. */
+ if (override_keyserver)
+ {
+ clear_context_flags (ctrl, ctx);
+ line = xtryasprintf ("KEYSERVER --clear %s", override_keyserver->uri);
+ if (!line)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ err = assuan_transact (ctx, line, NULL, NULL, NULL,
+ NULL, NULL, NULL);
+ if (err)
+ goto leave;
+
+ xfree (line);
+ line = NULL;
+ }
+
/* Lump all patterns into one string. */
init_membuf (&mb, 1024);
- put_membuf_str (&mb, "KS_GET --");
+ put_membuf_str (&mb, quick? "KS_GET --quick --" : "KS_GET --");
for (idx=0; pattern[idx]; idx++)
{
put_membuf (&mb, " ", 1); /* Append Delimiter. */
type_str = "sig";
break;
default:
- assert (! "Unhandled type.");
+ log_assert (! "Unhandled type.");
}
if (pub_key_length > 0)
int i;
i = 0;
- if (uid->is_revoked)
+ if (uid->flags.revoked)
validity[i ++] = 'r';
- if (uid->is_expired)
+ if (uid->flags.expired)
validity[i ++] = 'e';
validity[i] = '\0';
/* We are going to parse the keyblock, thus we better make sure the
all information is readily available. */
if (keyblock)
- merge_keys_and_selfsig (keyblock);
+ merge_keys_and_selfsig (ctrl, keyblock);
err = open_context (ctrl, &ctx);
if (err)
close_context (ctrl, ctx);
return err;
}
+
+
+\f
+/* Data callback for the DNS_CERT and WKD_GET commands. */
+static gpg_error_t
+dns_cert_data_cb (void *opaque, const void *data, size_t datalen)
+{
+ struct dns_cert_parm_s *parm = opaque;
+ gpg_error_t err = 0;
+ size_t nwritten;
+
+ if (!data)
+ return 0; /* Ignore END commands. */
+ if (!parm->memfp)
+ return 0; /* Data is not required. */
+
+ if (es_write (parm->memfp, data, datalen, &nwritten))
+ err = gpg_error_from_syserror ();
+
+ return err;
+}
+
+
+/* Status callback for the DNS_CERT command. */
+static gpg_error_t
+dns_cert_status_cb (void *opaque, const char *line)
+{
+ struct dns_cert_parm_s *parm = opaque;
+ gpg_error_t err = 0;
+ const char *s;
+ size_t nbytes;
+
+ if ((s = has_leading_keyword (line, "FPR")))
+ {
+ char *buf;
+
+ if (!(buf = xtrystrdup (s)))
+ err = gpg_error_from_syserror ();
+ else if (parm->fpr)
+ err = gpg_error (GPG_ERR_DUP_KEY);
+ else if (!hex2str (buf, buf, strlen (buf)+1, &nbytes))
+ err = gpg_error_from_syserror ();
+ else if (nbytes < 20)
+ err = gpg_error (GPG_ERR_TOO_SHORT);
+ else
+ {
+ parm->fpr = xtrymalloc (nbytes);
+ if (!parm->fpr)
+ err = gpg_error_from_syserror ();
+ else
+ memcpy (parm->fpr, buf, (parm->fprlen = nbytes));
+ }
+ xfree (buf);
+ }
+ else if ((s = has_leading_keyword (line, "URL")) && *s)
+ {
+ if (parm->url)
+ err = gpg_error (GPG_ERR_DUP_KEY);
+ else if (!(parm->url = xtrystrdup (s)))
+ err = gpg_error_from_syserror ();
+ }
+
+ return err;
+}
+
+/* Ask the dirmngr for a DNS CERT record. Depending on the found
+ subtypes different return values are set:
+
+ - For a PGP subtype a new estream with that key will be returned at
+ R_KEY and the other return parameters are set to NULL/0.
+
+ - For an IPGP subtype the fingerprint is stored as a malloced block
+ at (R_FPR,R_FPRLEN). If an URL is available it is stored as a
+ malloced string at R_URL; NULL is stored if there is no URL.
+
+ If CERTTYPE is DNS_CERTTYPE_ANY this function returns the first
+ CERT record found with a supported type; it is expected that only
+ one CERT record is used. If CERTTYPE is one of the supported
+ certtypes, only records with this certtype are considered and the
+ first one found is returned. All R_* args are optional.
+
+ If CERTTYPE is NULL the DANE method is used to fetch the key.
+ */
+gpg_error_t
+gpg_dirmngr_dns_cert (ctrl_t ctrl, const char *name, const char *certtype,
+ estream_t *r_key,
+ unsigned char **r_fpr, size_t *r_fprlen,
+ char **r_url)
+{
+ gpg_error_t err;
+ assuan_context_t ctx;
+ struct dns_cert_parm_s parm;
+ char *line = NULL;
+
+ memset (&parm, 0, sizeof parm);
+ if (r_key)
+ *r_key = NULL;
+ if (r_fpr)
+ *r_fpr = NULL;
+ if (r_fprlen)
+ *r_fprlen = 0;
+ if (r_url)
+ *r_url = NULL;
+
+ err = open_context (ctrl, &ctx);
+ if (err)
+ return err;
+
+ line = es_bsprintf ("DNS_CERT %s %s", certtype? certtype : "--dane", name);
+ if (!line)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
+ {
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ goto leave;
+ }
+
+ parm.memfp = es_fopenmem (0, "rwb");
+ if (!parm.memfp)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
+ NULL, NULL, dns_cert_status_cb, &parm);
+ if (err)
+ goto leave;
+
+ if (r_key)
+ {
+ es_rewind (parm.memfp);
+ *r_key = parm.memfp;
+ parm.memfp = NULL;
+ }
+
+ if (r_fpr && parm.fpr)
+ {
+ *r_fpr = parm.fpr;
+ parm.fpr = NULL;
+ }
+ if (r_fprlen)
+ *r_fprlen = parm.fprlen;
+
+ if (r_url && parm.url)
+ {
+ *r_url = parm.url;
+ parm.url = NULL;
+ }
+
+ leave:
+ xfree (parm.fpr);
+ xfree (parm.url);
+ es_fclose (parm.memfp);
+ xfree (line);
+ close_context (ctrl, ctx);
+ return err;
+}
+
+
+/* Ask the dirmngr for PKA info. On success the retrieved fingerprint
+ is returned in a malloced buffer at R_FPR and its length is stored
+ at R_FPRLEN. If an URL is available it is stored as a malloced
+ string at R_URL. On error all return values are set to NULL/0. */
+gpg_error_t
+gpg_dirmngr_get_pka (ctrl_t ctrl, const char *userid,
+ unsigned char **r_fpr, size_t *r_fprlen,
+ char **r_url)
+{
+ gpg_error_t err;
+ assuan_context_t ctx;
+ struct dns_cert_parm_s parm;
+ char *line = NULL;
+
+ memset (&parm, 0, sizeof parm);
+ if (r_fpr)
+ *r_fpr = NULL;
+ if (r_fprlen)
+ *r_fprlen = 0;
+ if (r_url)
+ *r_url = NULL;
+
+ err = open_context (ctrl, &ctx);
+ if (err)
+ return err;
+
+ line = es_bsprintf ("DNS_CERT --pka -- %s", userid);
+ if (!line)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
+ {
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ goto leave;
+ }
+
+ err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
+ NULL, NULL, dns_cert_status_cb, &parm);
+ if (err)
+ goto leave;
+
+ if (r_fpr && parm.fpr)
+ {
+ *r_fpr = parm.fpr;
+ parm.fpr = NULL;
+ }
+ if (r_fprlen)
+ *r_fprlen = parm.fprlen;
+
+ if (r_url && parm.url)
+ {
+ *r_url = parm.url;
+ parm.url = NULL;
+ }
+
+ leave:
+ xfree (parm.fpr);
+ xfree (parm.url);
+ xfree (line);
+ close_context (ctrl, ctx);
+ return err;
+}
+
+
+\f
+/* Ask the dirmngr to retrieve a key via the Web Key Directory
+ * protocol. If QUICK is set the dirmngr is advised to use a shorter
+ * timeout. On success a new estream with the key stored at R_KEY and the
+ * url of the lookup (if any) stored at R_URL. Note that
+ */
+gpg_error_t
+gpg_dirmngr_wkd_get (ctrl_t ctrl, const char *name, int quick,
+ estream_t *r_key, char **r_url)
+{
+ gpg_error_t err;
+ assuan_context_t ctx;
+ struct ks_status_parm_s stparm = { NULL };
+ struct dns_cert_parm_s parm = { NULL };
+ char *line = NULL;
+
+ if (r_key)
+ *r_key = NULL;
+
+ if (r_url)
+ *r_url = NULL;
+
+ err = open_context (ctrl, &ctx);
+ if (err)
+ return err;
+
+ line = es_bsprintf ("WKD_GET%s -- %s", quick?" --quick":"", name);
+ if (!line)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
+ {
+ err = gpg_error (GPG_ERR_TOO_LARGE);
+ goto leave;
+ }
+
+ parm.memfp = es_fopenmem (0, "rwb");
+ if (!parm.memfp)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+ err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
+ NULL, NULL, ks_status_cb, &stparm);
+ if (err)
+ goto leave;
+
+ if (r_key)
+ {
+ es_rewind (parm.memfp);
+ *r_key = parm.memfp;
+ parm.memfp = NULL;
+ }
+
+ if (r_url)
+ {
+ *r_url = stparm.source;
+ stparm.source = NULL;
+ }
+
+ leave:
+ xfree (stparm.source);
+ xfree (parm.fpr);
+ xfree (parm.url);
+ es_fclose (parm.memfp);
+ xfree (line);
+ close_context (ctrl, ctx);
+ return err;
+}