/* 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 Werner Koch
+ * Copyright (C) 2014, 2015, 2016 Werner Koch
*
* This file is part of GnuPG.
*
#if USE_LDAP
# include "ldap-parse-uri.h"
#endif
-#include "dns-cert.h"
+#include "dns-stuff.h"
#include "mbox-util.h"
+#include "zb32.h"
+#include "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
/* 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;
};
/* Cookie definition for assuan data line output. */
-static ssize_t data_line_cookie_write (void *cookie,
- const void *buffer, size_t size);
+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 =
{
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
if (! ctrl->server_local)
return;
- while (ctrl->server_local->keyservers)
- {
- uri_item_t tmp = ctrl->server_local->keyservers->next;
- http_release_parsed_uri (ctrl->server_local->keyservers->parsed_uri);
- xfree (ctrl->server_local->keyservers);
- ctrl->server_local->keyservers = tmp;
- }
+ release_uri_item_list (ctrl->server_local->keyservers);
+ ctrl->server_local->keyservers = NULL;
}
/* A write handler used by es_fopencookie to write assuan data
lines. */
-static ssize_t
+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 (ssize_t)size;
+ return (gpgrt_ssize_t)size;
}
}
-/* Check whether the option NAME appears in LINE */
+/* This fucntion returns true if a Tor server is running. The sattus
+ is cached for the current conenction. */
static int
-has_option (const char *line, const char *name)
+is_tor_running (ctrl_t ctrl)
{
- const char *s;
- int n = strlen (name);
-
- s = strstr (line, name);
- return (s && (s == line || spacep (s-1)) && (!s[n] || spacep (s+n)));
-}
+#if ASSUAN_VERSION_NUMBER >= 0x020402
+ /* Check whether we can connect to the proxy. We use a
+ special feature introduced with libassuan 2.4.2. */
-/* Same as has_option but only considers options at the begin of the
- line. This is useful for commands which allow arbitrary strings on
- the line. */
-static int
-has_leading_option (const char *line, const char *name)
-{
- const char *s;
- int n;
+ if (!ctrl || !ctrl->server_local)
+ return 0; /* Ooops. */
- if (name[0] != '-' || name[1] != '-' || !name[2] || spacep (name+2))
- return 0;
- n = strlen (name);
- while ( *line == '-' && line[1] == '-' )
+ if (!ctrl->server_local->tor_state)
{
- s = line;
- while (*line && !spacep (line))
- line++;
- if (n == (line - s) && !strncmp (s, name, n))
- return 1;
- while (spacep (line))
- line++;
- }
- return 0;
-}
-
+ assuan_fd_t sock;
-/* Same as has_option but does only test for the name of the option
- and ignores an argument, i.e. with NAME being "--hash" it would
- return a pointer for "--hash" as well as for "--hash=foo". If
- thhere is no such option NULL is returned. The pointer returned
- points right behind the option name, this may be an equal sign, Nul
- or a space. */
-/* static const char * */
-/* has_option_name (const char *line, const char *name) */
-/* { */
-/* const char *s; */
-/* int n = strlen (name); */
-
-/* s = strstr (line, name); */
-/* return (s && (s == line || spacep (s-1)) */
-/* && (!s[n] || spacep (s+n) || s[n] == '=')) ? (s+n) : NULL; */
-/* } */
-
-
-/* Skip over options. It is assumed that leading spaces have been
- removed (this is the case for lines passed to a handler from
- assuan). Blanks after the options are also removed. */
-static char *
-skip_options (char *line)
-{
- while ( *line == '-' && line[1] == '-' )
- {
- while (*line && !spacep (line))
- line++;
- while (spacep (line))
- line++;
+ 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 line;
+ return (ctrl->server_local->tor_state > 0);
+#else /* Libassuan < 2.4.2 */
+ return 0; /* We don't know. */
+#endif
}
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 (opt.use_tor)
+ err = gpg_error (GPG_ERR_FORBIDDEN);
+ }
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
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 certifciate is available (PGP) it is returned\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.";
+ "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;
+ int pka_mode, dane_mode;
char *mbox = NULL;
char *namebuf = NULL;
char *encodedhash = NULL;
char *url = NULL;
pka_mode = has_option (line, "--pka");
+ dane_mode = has_option (line, "--dane");
line = skip_options (line);
- if (pka_mode)
+
+ 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
{
}
}
- if (pka_mode)
+ if (opt.use_tor && (err = enable_dns_tormode (0)))
+ {
+ /* Tor mode is requested but the DNS code can't enable it. */
+ assuan_set_error (ctx, err, "error enabling Tor mode");
+ goto leave;
+ }
+
+ if (pka_mode || dane_mode)
{
- char *domain; /* Points to mbox. */
- char hashbuf[20];
+ 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, '@')))
{
}
*domain++ = 0;
- gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox));
- encodedhash = zb32_encode (hashbuf, 8*20);
- if (!encodedhash)
+ if (pka_mode)
{
- err = gpg_error_from_syserror ();
- goto leave;
+ 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;
}
- namebuf = strconcat (encodedhash, "._pka.", domain, NULL);
- if (!namebuf)
+ else
{
- err = gpg_error_from_syserror ();
- goto leave;
+ /* 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;
}
- name = namebuf;
- certtype = DNS_CERTTYPE_IPGP;
}
else
name = line;
\f
+static const char hlp_wkd_get[] =
+ "WKD_GET <user_id>\n"
+ "\n"
+ "Return the key for <user_id> from a Web Key Directory.\n";
+static gpg_error_t
+cmd_wkd_get (assuan_context_t ctx, char *line)
+{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
+ gpg_error_t err = 0;
+ char *mbox = NULL;
+ char *domain; /* Points to mbox. */
+ char sha1buf[20];
+ char *uri = NULL;
+ char *encodedhash = NULL;
+
+ line = skip_options (line);
+
+ 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;
+
+ 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;
+ }
+
+ uri = strconcat ("https://",
+ domain,
+ "/.well-known/openpgpkey/hu/",
+ domain,
+ "/",
+ encodedhash,
+ NULL);
+ if (!uri)
+ {
+ err = gpg_error_from_syserror ();
+ goto leave;
+ }
+
+ /* Setup an output stream and perform the get. */
+ {
+ estream_t outfp;
+
+ 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_fetch (ctrl, uri, outfp);
+ es_fclose (outfp);
+ }
+ }
+
+ leave:
+ xfree (uri);
+ xfree (encodedhash);
+ xfree (mbox);
+ return leave_cmd (ctx, err);
+}
+
+
+\f
static const char hlp_ldapserver[] =
"LDAPSERVER <data>\n"
"\n"
"Processing then takes place without further interaction; in\n"
"particular dirmngr tries to locate other required certificates by\n"
"its own mechanism which includes a local certificate store as well\n"
- "as a list of trusted root certifciates.\n"
+ "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"
return leave_cmd (ctx, err);
}
+
\f
+/* 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;
+ 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)
+ return 0; /* No global option set. */
+
+ 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 sesssion 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"
if (resolve_flag)
{
- err = ks_action_resolve (ctrl, ctrl->server_local->keyservers);
+ err = ensure_keyserver (ctrl);
+ if (!err)
+ err = ks_action_resolve (ctrl, ctrl->server_local->keyservers);
if (err)
goto leave;
}
if (add_flag)
{
- item = xtrymalloc (sizeof *item + strlen (line));
- if (!item)
- {
- err = gpg_error_from_syserror ();
- goto leave;
- }
- item->next = NULL;
- item->parsed_uri = NULL;
- strcpy (item->uri, line);
-
-#if USE_LDAP
- if (ldap_uri_p (item->uri))
- err = ldap_parse_uri (&item->parsed_uri, line);
- else
-#endif
- {
- err = http_parse_uri (&item->parsed_uri, line, 1);
- }
+ err = make_keyserver_item (line, &item);
if (err)
- {
- xfree (item);
- goto leave;
- }
+ goto leave;
}
if (clear_flag)
release_ctrl_keyservers (ctrl);
ctrl->server_local->keyservers = item;
}
- if (!add_flag && !clear_flag && !help_flag) /* List configured keyservers. */
+ 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 = 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 = 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)
/* No options for now. */
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)
es_fclose (outfp);
}
+ leave:
return leave_cmd (ctx, err);
}
/* 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);
"\n"
"version - Return the version of the program.\n"
"pid - Return the process id of the server.\n"
- "\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";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
+ ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
if (!strcmp (line, "version"))
else
err = gpg_error (GPG_ERR_NO_DATA);
}
+ else if (!strcmp (line, "tor"))
+ {
+ if (opt.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, "- Tor mode is enabled");
+ }
+ else
+ err = set_error (GPG_ERR_FALSE, "Tor mode is NOT enabled");
+ }
+ else if (!strcmp (line, "dnsinfo"))
+ {
+#if USE_ADNS && HAVE_ADNS_IF_TORMODE
+ assuan_set_okay_line (ctx, "- ADNS with Tor support");
+#elif USE_ADNS
+ assuan_set_okay_line (ctx, "- ADNS w/o Tor support");
+#else
+ assuan_set_okay_line (ctx, "- System resolver w/o Tor support");
+#endif
+ err = 0;
+ }
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
if (!err)
{
ctrl->server_local->stopme = 1;
+ assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
err = gpg_error (GPG_ERR_EOF);
}
return err;
{
#ifndef HAVE_W32_SYSTEM
{
- gpg_err_code_t ec;
- assuan_peercred_t cred;
-
- ec = gpg_err_code (assuan_get_peercred (ctx, &cred));
- if (!ec && cred->uid)
- ec = GPG_ERR_EPERM; /* Only root may terminate. */
- if (ec)
- return set_error (ec, "no permission to reload this process");
+ gpg_error_t err;
+
+ err = check_owner_permission (ctx,
+ "no permission to reload this process");
+ if (err)
+ return err;
}
#endif
}
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 },
}
}
+
#if USE_LDAP
ldap_wrapper_connection_cleanup (ctrl);
#endif /*USE_LDAP*/
ctrl->server_local->ldapservers = NULL;
+ release_ctrl_keyservers (ctrl);
+
ctrl->server_local->assuan_ctx = NULL;
assuan_release (ctx);