diff options
author | Werner Koch <wk@gnupg.org> | 2011-01-24 12:24:11 +0100 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2011-01-24 12:24:11 +0100 |
commit | c5e8a4c0fdde7f4aef2163a3710483c87bdf3161 (patch) | |
tree | ae9da511231485f5c8ba8faeb3e7db3b971fe8ff /dirmngr | |
parent | Fix regression introduced by "editing only change". (diff) | |
parent | Remove keyserver/ from the build system. (diff) | |
download | gnupg2-c5e8a4c0fdde7f4aef2163a3710483c87bdf3161.tar.xz gnupg2-c5e8a4c0fdde7f4aef2163a3710483c87bdf3161.zip |
Merge branch 'master' into ECC-INTEGRATION-2-1
Diffstat (limited to 'dirmngr')
-rw-r--r-- | dirmngr/ChangeLog | 20 | ||||
-rw-r--r-- | dirmngr/Makefile.am | 3 | ||||
-rw-r--r-- | dirmngr/crlfetch.c | 4 | ||||
-rw-r--r-- | dirmngr/dirmngr.c | 58 | ||||
-rw-r--r-- | dirmngr/dirmngr.h | 14 | ||||
-rw-r--r-- | dirmngr/ks-action.c | 183 | ||||
-rw-r--r-- | dirmngr/ks-action.h | 28 | ||||
-rw-r--r-- | dirmngr/ks-engine-hkp.c | 558 | ||||
-rw-r--r-- | dirmngr/ks-engine.h | 36 | ||||
-rw-r--r-- | dirmngr/server.c | 285 |
10 files changed, 1150 insertions, 39 deletions
diff --git a/dirmngr/ChangeLog b/dirmngr/ChangeLog index 2c208755d..39df05d0b 100644 --- a/dirmngr/ChangeLog +++ b/dirmngr/ChangeLog @@ -1,3 +1,20 @@ +2011-01-20 Werner Koch <wk@g10code.com> + + * server.c (release_ctrl_keyservers): New. + (cmd_keyserver, cmd_ks_seach, cmd_ks_get, cmd_ks_put): New. + * dirmngr.h (uri_item_t): New. + (struct server_control_s): Add field KEYSERVERS. + * ks-engine-hkp.c: New. + * ks-engine.h: New. + * ks-action.c, ks-action.h: New. + * server.c: Include ks-action.h. + (cmd_ks_search): New. + * Makefile.am (dirmngr_SOURCES): Add new files. + +2011-01-19 Werner Koch <wk@g10code.com> + + * dirmngr.c (main): Use es_printf for --gpgconf-list. + 2010-12-14 Werner Koch <wk@g10code.com> * cdb.h (struct cdb) [W32]: Add field CDB_MAPPING. @@ -1488,7 +1505,8 @@ [Update after merge with GnuPG: see ./ChangeLog.1] - Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010 g10 Code GmbH + Copyright 2004, 2005, 2006, 2007, 2008, 2009, 2010, + 2011 Free Software Foundation, Inc. This file is free software; as a special exception the author gives unlimited permission to copy and/or distribute it, with or without diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index 128d7c383..79acae9f7 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -49,7 +49,8 @@ noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \ ldapserver.h ldapserver.c certcache.c certcache.h \ cdb.h cdblib.c ldap.c misc.c dirmngr-err.h w32-ldap-help.h \ - ocsp.c ocsp.h validate.c validate.h ldap-wrapper.h $(ldap_url) + ocsp.c ocsp.h validate.c validate.h ldap-wrapper.h $(ldap_url) \ + ks-action.c ks-action.h ks-engine.h ks-engine-hkp.c if USE_LDAPWRAPPER dirmngr_SOURCES += ldap-wrapper.c diff --git a/dirmngr/crlfetch.c b/dirmngr/crlfetch.c index 83897a698..057742389 100644 --- a/dirmngr/crlfetch.c +++ b/dirmngr/crlfetch.c @@ -160,7 +160,7 @@ crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) *reader = NULL; once_more: - err = http_parse_uri (&uri, url); + err = http_parse_uri (&uri, url, 0); http_release_parsed_uri (uri); if (err && url && !strncmp (url, "https:", 6)) { @@ -172,7 +172,7 @@ crl_fetch (ctrl_t ctrl, const char *url, ksba_reader_t *reader) if (free_this) { strcpy (stpcpy (free_this,"http:"), url+6); - err = http_parse_uri (&uri, free_this); + err = http_parse_uri (&uri, free_this, 0); http_release_parsed_uri (uri); if (!err) { diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 9b06851bd..ae922fa31 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -1019,7 +1019,7 @@ main (int argc, char **argv) start of the dirmngr. */ #ifdef HAVE_W32_SYSTEM pid = getpid (); - printf ("set DIRMNGR_INFO=%s;%lu;1\n", socket_name, (ulong) pid); + es_printf ("set DIRMNGR_INFO=%s;%lu;1\n", socket_name, (ulong) pid); #else pid = pth_fork (); if (pid == (pid_t)-1) @@ -1051,11 +1051,11 @@ main (int argc, char **argv) if (csh_style) { *strchr (infostr, '=') = ' '; - printf ( "setenv %s\n", infostr); + es_printf ( "setenv %s\n", infostr); } else { - printf ( "%s; export DIRMNGR_INFO;\n", infostr); + es_printf ( "%s; export DIRMNGR_INFO;\n", infostr); } free (infostr); exit (0); @@ -1220,15 +1220,15 @@ main (int argc, char **argv) "dirmngr.conf", NULL ); filename = percent_escape (opt.config_filename, NULL); - printf ("gpgconf-dirmngr.conf:%lu:\"%s\n", + es_printf ("gpgconf-dirmngr.conf:%lu:\"%s\n", GC_OPT_FLAG_DEFAULT, filename); xfree (filename); - printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT); - printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("verbose:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("quiet:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT); + es_printf ("log-file:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("force:%lu:\n", flags | GC_OPT_FLAG_NONE); /* --csh and --sh are mutually exclusive, something we can not express in GPG Conf. --options is only usable from the @@ -1241,34 +1241,34 @@ main (int argc, char **argv) "ldapservers.conf":"dirmngr_ldapservers.conf", NULL); filename_esc = percent_escape (filename, NULL); - printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT, + es_printf ("ldapserverlist-file:%lu:\"%s\n", flags | GC_OPT_FLAG_DEFAULT, filename_esc); xfree (filename_esc); xfree (filename); - printf ("ldaptimeout:%lu:%u\n", + es_printf ("ldaptimeout:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT); - printf ("max-replies:%lu:%u\n", + es_printf ("max-replies:%lu:%u\n", flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES); - printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE); - - printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE); - - printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE); - printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); - printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("allow-ocsp:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ocsp-responder:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ocsp-signer:%lu:\n", flags | GC_OPT_FLAG_NONE); + + es_printf ("faked-system-time:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("no-greeting:%lu:\n", flags | GC_OPT_FLAG_NONE); + + es_printf ("disable-http:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("disable-ldap:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("honor-http-proxy:%lu\n", flags | GC_OPT_FLAG_NONE); + es_printf ("http-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("only-ldap-proxy:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ignore-ldap-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ignore-http-dp:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ignore-ocsp-service-url:%lu:\n", flags | GC_OPT_FLAG_NONE); /* Note: The next one is to fix a typo in gpgconf - should be removed eventually. */ - printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE); + es_printf ("ignore-ocsp-servic-url:%lu:\n", flags | GC_OPT_FLAG_NONE); } cleanup (); return !!rc; diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h index 01478a64f..1ba90b8ed 100644 --- a/dirmngr/dirmngr.h +++ b/dirmngr/dirmngr.h @@ -32,7 +32,7 @@ #include "../common/membuf.h" #include "../common/sysutils.h" /* (gnupg_fd_t) */ #include "../common/i18n.h" - +#include "../common/http.h" /* (parsed_uri_t) */ /* This objects keeps information about a particular LDAP server and is used as item of a single linked list of servers. */ @@ -49,6 +49,17 @@ struct ldap_server_s typedef struct ldap_server_s *ldap_server_t; +/* This objects is used to build a list of URI consisting of the + original and the parsed URI. */ +struct uri_item_s +{ + struct uri_item_s *next; + parsed_uri_t parsed_uri; /* The broken down URI. */ + char uri[1]; /* The original URI. */ +}; +typedef struct uri_item_s *uri_item_t; + + /* A list of fingerprints. */ struct fingerprint_list_s; typedef struct fingerprint_list_s *fingerprint_list_t; @@ -163,6 +174,7 @@ struct server_control_s response. */ int audit_events; /* Send audit events to client. */ + uri_item_t keyservers; /* List of keyservers. */ }; diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c new file mode 100644 index 000000000..fd2a2b568 --- /dev/null +++ b/dirmngr/ks-action.c @@ -0,0 +1,183 @@ +/* ks-action.c - OpenPGP keyserver actions + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "dirmngr.h" +#include "misc.h" +#include "ks-engine.h" +#include "ks-action.h" + + +/* Copy all data from IN to OUT. */ +static gpg_error_t +copy_stream (estream_t in, estream_t out) +{ + char buffer[512]; + size_t nread; + + while (!es_read (in, buffer, sizeof buffer, &nread)) + { + if (!nread) + return 0; /* EOF */ + if (es_write (out, buffer, nread, NULL)) + break; + + } + return gpg_error_from_syserror (); +} + + + +/* Search all configured keyservers for keys matching PATTERNS and + write the result to the provided output stream. */ +gpg_error_t +ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp) +{ + gpg_error_t err = 0; + int any = 0; + uri_item_t uri; + estream_t infp; + + if (!patterns) + return gpg_error (GPG_ERR_NO_USER_ID); + + /* FIXME: We only take care of the first pattern. To fully support + multiple patterns we might either want to run several queries in + parallel and merge them. We also need to decide what to do with + errors - it might not be the best idea to ignore an error from + one server and silently continue with another server. For now we + stop at the first error. */ + for (uri = ctrl->keyservers; !err && uri; uri = uri->next) + { + if (uri->parsed_uri->is_http) + { + any = 1; + err = ks_hkp_search (ctrl, uri->parsed_uri, patterns->d, &infp); + if (!err) + { + err = copy_stream (infp, outfp); + es_fclose (infp); + break; + } + } + } + + if (!any) + err = gpg_error (GPG_ERR_NO_KEYSERVER); + return err; +} + + +/* Get the requested keys (matching PATTERNS) using all configured + keyservers and write the result to the provided output stream. */ +gpg_error_t +ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp) +{ + gpg_error_t err = 0; + gpg_error_t first_err = 0; + int any = 0; + strlist_t sl; + uri_item_t uri; + estream_t infp; + + if (!patterns) + return gpg_error (GPG_ERR_NO_USER_ID); + + /* FIXME: We only take care of the first keyserver. To fully + support multiple keyservers we need to track the result for each + pattern and use the next keyserver if one key was not found. The + keyservers might not all be fully synced thus it is not clear + whether the first keyserver has the freshest copy of the key. + Need to think about a better strategy. */ + for (uri = ctrl->keyservers; !err && uri; uri = uri->next) + { + if (uri->parsed_uri->is_http) + { + any = 1; + for (sl = patterns; !err && sl; sl = sl->next) + { + err = ks_hkp_get (ctrl, uri->parsed_uri, sl->d, &infp); + if (err) + { + /* It is possible that a server does not carry a + key, thus we only save the error and continue + with the next pattern. FIXME: It is an open + question how to return such an error condition to + the caller. */ + first_err = err; + err = 0; + } + else + { + err = copy_stream (infp, outfp); + /* Reading from the keyserver should nver fail, thus + return this error. */ + es_fclose (infp); + infp = NULL; + } + } + } + } + + if (!any) + err = gpg_error (GPG_ERR_NO_KEYSERVER); + else if (!err && first_err) + err = first_err; /* fixme: Do we really want to do that? */ + return err; +} + + + +/* Send an OpenPGP key to all keyservers. The key in {DATA,DATALEN} + is expected in OpenPGP binary transport format. */ +gpg_error_t +ks_action_put (ctrl_t ctrl, const void *data, size_t datalen) +{ + gpg_error_t err = 0; + gpg_error_t first_err = 0; + int any = 0; + uri_item_t uri; + + for (uri = ctrl->keyservers; !err && uri; uri = uri->next) + { + if (uri->parsed_uri->is_http) + { + any = 1; + err = ks_hkp_put (ctrl, uri->parsed_uri, data, datalen); + if (err) + { + first_err = err; + err = 0; + } + } + } + + if (!any) + err = gpg_error (GPG_ERR_NO_KEYSERVER); + else if (!err && first_err) + err = first_err; /* fixme: Do we really want to do that? */ + return err; +} + diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h new file mode 100644 index 000000000..b3bd3fc46 --- /dev/null +++ b/dirmngr/ks-action.h @@ -0,0 +1,28 @@ +/* ks-action.h - OpenPGP keyserver actions definitions + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DIRMNGR_KS_ACTION_H +#define DIRMNGR_KS_ACTION_H 1 + +gpg_error_t ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp); +gpg_error_t ks_action_get (ctrl_t ctrl, strlist_t patterns, estream_t outfp); +gpg_error_t ks_action_put (ctrl_t ctrl, const void *data, size_t datalen); + + +#endif /*DIRMNGR_KS_ACTION_H*/ diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c new file mode 100644 index 000000000..e25900ae1 --- /dev/null +++ b/dirmngr/ks-engine-hkp.c @@ -0,0 +1,558 @@ +/* ks-engine-hkp.c - HKP keyserver engine + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +#include "dirmngr.h" +#include "misc.h" +#include "userids.h" +#include "ks-engine.h" + +/* To match the behaviour of our old gpgkeys helper code we escape + more characters than actually needed. */ +#define EXTRA_ESCAPE_CHARS "@!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~" + +/* How many redirections do we allow. */ +#define MAX_REDIRECTS 2 + + +/* Send an HTTP request. On success returns an estream object at + R_FP. HOSTPORTSTR is only used for diagnostics. If POST_CB is not + NULL a post request is used and that callback is called to allow + writing the post data. */ +static gpg_error_t +send_request (ctrl_t ctrl, const char *request, const char *hostportstr, + gpg_error_t (*post_cb)(void *, http_t), void *post_cb_value, + estream_t *r_fp) +{ + gpg_error_t err; + http_t http = NULL; + int redirects_left = MAX_REDIRECTS; + estream_t fp = NULL; + char *request_buffer = NULL; + + *r_fp = NULL; + once_more: + err = http_open (&http, + post_cb? HTTP_REQ_POST : HTTP_REQ_GET, + request, + /* fixme: AUTH */ NULL, + 0, + /* fixme: proxy*/ NULL, + NULL, NULL, + /*FIXME curl->srvtag*/NULL); + if (!err) + { + fp = http_get_write_ptr (http); + /* Avoid caches to get the most recent copy of the key. We set + both the Pragma and Cache-Control versions of the header, so + we're good with both HTTP 1.0 and 1.1. */ + es_fputs ("Pragma: no-cache\r\n" + "Cache-Control: no-cache\r\n", fp); + if (post_cb) + err = post_cb (post_cb_value, http); + if (!err) + { + http_start_data (http); + if (es_ferror (fp)) + err = gpg_error_from_syserror (); + } + } + if (err) + { + /* Fixme: After a redirection we show the old host name. */ + log_error (_("error connecting to `%s': %s\n"), + hostportstr, gpg_strerror (err)); + goto leave; + } + + /* Wait for the response. */ + dirmngr_tick (ctrl); + err = http_wait_response (http); + if (err) + { + log_error (_("error reading HTTP response for `%s': %s\n"), + hostportstr, gpg_strerror (err)); + goto leave; + } + + switch (http_get_status_code (http)) + { + case 200: + err = 0; + break; /* Success. */ + + case 301: + case 302: + { + const char *s = http_get_header (http, "Location"); + + log_info (_("URL `%s' redirected to `%s' (%u)\n"), + request, s?s:"[none]", http_get_status_code (http)); + if (s && *s && redirects_left-- ) + { + xfree (request_buffer); + request_buffer = xtrystrdup (s); + if (request_buffer) + { + request = request_buffer; + http_close (http, 0); + http = NULL; + goto once_more; + } + err = gpg_error_from_syserror (); + } + else + err = gpg_error (GPG_ERR_NO_DATA); + log_error (_("too many redirections\n")); + } + goto leave; + + default: + log_error (_("error accessing `%s': http status %u\n"), + request, http_get_status_code (http)); + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + fp = http_get_read_ptr (http); + if (!fp) + { + err = gpg_error (GPG_ERR_BUG); + goto leave; + } + + /* Return the read stream and close the HTTP context. */ + *r_fp = fp; + http_close (http, 1); + http = NULL; + + leave: + http_close (http, 0); + xfree (request_buffer); + return err; +} + + +static gpg_error_t +armor_data (char **r_string, const void *data, size_t datalen) +{ + gpg_error_t err; + struct b64state b64state; + estream_t fp; + long length; + char *buffer; + size_t nread; + + *r_string = NULL; + + fp = es_fopenmem (0, "rw"); + if (!fp) + return gpg_error_from_syserror (); + + if ((err=b64enc_start_es (&b64state, fp, "PGP PUBLIC KEY BLOCK")) + || (err=b64enc_write (&b64state, data, datalen)) + || (err = b64enc_finish (&b64state))) + { + es_fclose (fp); + return err; + } + + /* FIXME: To avoid the extra buffer allocation estream should + provide a function to snatch the internal allocated memory from + such a memory stream. */ + length = es_ftell (fp); + if (length < 0) + { + err = gpg_error_from_syserror (); + es_fclose (fp); + return err; + } + + buffer = xtrymalloc (length+1); + if (!buffer) + { + err = gpg_error_from_syserror (); + es_fclose (fp); + return err; + } + + es_rewind (fp); + if (es_read (fp, buffer, length, &nread)) + { + err = gpg_error_from_syserror (); + es_fclose (fp); + return err; + } + buffer[nread] = 0; + es_fclose (fp); + + *r_string = buffer; + return 0; +} + + + + +/* Search the keyserver identified by URI for keys matching PATTERN. + On success R_FP has an open stream to read the data. */ +gpg_error_t +ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, + estream_t *r_fp) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + char fprbuf[2+40+1]; + const char *scheme; + char portstr[10]; + char *hostport = NULL; + char *request = NULL; + estream_t fp = NULL; + + *r_fp = NULL; + + /* Remove search type indicator and adjust PATTERN accordingly. + Note that HKP keyservers like the 0x to be present when searching + by keyid. We need to re-format the fingerprint and keyids so to + remove the gpg specific force-use-of-this-key flag ("!"). */ + err = classify_user_id (pattern, &desc); + if (err) + return err; + switch (desc.mode) + { + case KEYDB_SEARCH_MODE_EXACT: + case KEYDB_SEARCH_MODE_SUBSTR: + case KEYDB_SEARCH_MODE_MAIL: + case KEYDB_SEARCH_MODE_MAILSUB: + pattern = desc.u.name; + break; + case KEYDB_SEARCH_MODE_SHORT_KID: + snprintf (fprbuf, sizeof fprbuf, "0x%08lX", (ulong)desc.u.kid[1]); + pattern = fprbuf; + break; + case KEYDB_SEARCH_MODE_LONG_KID: + snprintf (fprbuf, sizeof fprbuf, "0x%08lX%08lX", + (ulong)desc.u.kid[0], (ulong)desc.u.kid[1]); + pattern = fprbuf; + break; + case KEYDB_SEARCH_MODE_FPR16: + bin2hex (desc.u.fpr, 16, fprbuf); + pattern = fprbuf; + break; + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + bin2hex (desc.u.fpr, 20, fprbuf); + pattern = fprbuf; + break; + default: + return gpg_error (GPG_ERR_INV_USER_ID); + } + + /* Map scheme and port. */ + if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https")) + { + scheme = "https"; + strcpy (portstr, "443"); + } + else /* HKP or HTTP. */ + { + scheme = "http"; + strcpy (portstr, "11371"); + } + if (uri->port) + snprintf (portstr, sizeof portstr, "%hu", uri->port); + else + {} /*fixme_do_srv_lookup ()*/ + + /* Build the request string. */ + { + char *searchkey; + + hostport = strconcat (scheme, "://", + *uri->host? uri->host: "localhost", + ":", portstr, NULL); + if (!hostport) + { + err = gpg_error_from_syserror (); + goto leave; + } + + searchkey = http_escape_string (pattern, EXTRA_ESCAPE_CHARS); + if (!searchkey) + { + err = gpg_error_from_syserror (); + goto leave; + } + + request = strconcat (hostport, + "/pks/lookup?op=index&options=mr&search=", + searchkey, + NULL); + xfree (searchkey); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + /* Send the request. */ + err = send_request (ctrl, request, hostport, NULL, NULL, &fp); + if (err) + goto leave; + + /* Start reading the response. */ + { + int c = es_getc (fp); + if (c == -1) + { + err = es_ferror (fp)?gpg_error_from_syserror ():gpg_error (GPG_ERR_EOF); + log_error ("error reading response: %s\n", gpg_strerror (err)); + goto leave; + } + if (c == '<') + { + /* The document begins with a '<', assume it's a HTML + response, which we don't support. */ + err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING); + goto leave; + } + es_ungetc (c, fp); + } + + /* Return the read stream. */ + *r_fp = fp; + fp = NULL; + + leave: + es_fclose (fp); + xfree (request); + xfree (hostport); + return err; +} + + +/* Get the key described key the KEYSPEC string from the keyserver + identified by URI. On success R_FP has an open stream to read the + data. */ +gpg_error_t +ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec, estream_t *r_fp) +{ + gpg_error_t err; + KEYDB_SEARCH_DESC desc; + char kidbuf[8+1]; + const char *scheme; + char portstr[10]; + char *hostport = NULL; + char *request = NULL; + estream_t fp = NULL; + + *r_fp = NULL; + + /* Remove search type indicator and adjust PATTERN accordingly. + Note that HKP keyservers like the 0x to be present when searching + by keyid. We need to re-format the fingerprint and keyids so to + remove the gpg specific force-use-of-this-key flag ("!"). */ + err = classify_user_id (keyspec, &desc); + if (err) + return err; + switch (desc.mode) + { + case KEYDB_SEARCH_MODE_SHORT_KID: + case KEYDB_SEARCH_MODE_LONG_KID: + snprintf (kidbuf, sizeof kidbuf, "%08lX", (ulong)desc.u.kid[1]); + break; + case KEYDB_SEARCH_MODE_FPR20: + case KEYDB_SEARCH_MODE_FPR: + /* This is a v4 fingerprint. Take the last 8 hex digits from + the fingerprint which is the expected short keyid. */ + bin2hex (desc.u.fpr+16, 4, kidbuf); + break; + + case KEYDB_SEARCH_MODE_FPR16: + log_error ("HKP keyserver do not support v3 fingerprints\n"); + default: + return gpg_error (GPG_ERR_INV_USER_ID); + } + + /* Map scheme and port. */ + if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https")) + { + scheme = "https"; + strcpy (portstr, "443"); + } + else /* HKP or HTTP. */ + { + scheme = "http"; + strcpy (portstr, "11371"); + } + if (uri->port) + snprintf (portstr, sizeof portstr, "%hu", uri->port); + else + {} /*fixme_do_srv_lookup ()*/ + + /* Build the request string. */ + { + hostport = strconcat (scheme, "://", + *uri->host? uri->host: "localhost", + ":", portstr, NULL); + if (!hostport) + { + err = gpg_error_from_syserror (); + goto leave; + } + + request = strconcat (hostport, + "/pks/lookup?op=get&options=mr&search=0x", + kidbuf, + NULL); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + /* Send the request. */ + err = send_request (ctrl, request, hostport, NULL, NULL, &fp); + if (err) + goto leave; + + /* Return the read stream and close the HTTP context. */ + *r_fp = fp; + fp = NULL; + + leave: + es_fclose (fp); + xfree (request); + xfree (hostport); + return err; +} + + + + +/* Callback parameters for put_post_cb. */ +struct put_post_parm_s +{ + char *datastring; +}; + + +/* Helper for ks_hkp_put. */ +static gpg_error_t +put_post_cb (void *opaque, http_t http) +{ + struct put_post_parm_s *parm = opaque; + gpg_error_t err = 0; + estream_t fp; + size_t len; + + fp = http_get_write_ptr (http); + len = strlen (parm->datastring); + + es_fprintf (fp, + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %zu\r\n", len+8 /* 8 is for "keytext" */); + http_start_data (http); + if (es_fputs ("keytext=", fp) || es_write (fp, parm->datastring, len, NULL)) + err = gpg_error_from_syserror (); + return err; +} + + +/* Send the key in {DATA,DATALEN} to the keyserver identified by URI. */ +gpg_error_t +ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, const void *data, size_t datalen) +{ + gpg_error_t err; + const char *scheme; + char portstr[10]; + char *hostport = NULL; + char *request = NULL; + estream_t fp = NULL; + struct put_post_parm_s parm; + char *armored = NULL; + + parm.datastring = NULL; + + /* Map scheme and port. */ + if (!strcmp (uri->scheme,"hkps") || !strcmp (uri->scheme,"https")) + { + scheme = "https"; + strcpy (portstr, "443"); + } + else /* HKP or HTTP. */ + { + scheme = "http"; + strcpy (portstr, "11371"); + } + if (uri->port) + snprintf (portstr, sizeof portstr, "%hu", uri->port); + else + {} /*fixme_do_srv_lookup ()*/ + + err = armor_data (&armored, data, datalen); + if (err) + goto leave; + + parm.datastring = http_escape_string (armored, EXTRA_ESCAPE_CHARS); + if (!parm.datastring) + { + err = gpg_error_from_syserror (); + goto leave; + } + xfree (armored); + armored = NULL; + + /* Build the request string. */ + hostport = strconcat (scheme, "://", + *uri->host? uri->host: "localhost", + ":", portstr, NULL); + if (!hostport) + { + err = gpg_error_from_syserror (); + goto leave; + } + + request = strconcat (hostport, "/pks/add", NULL); + if (!request) + { + err = gpg_error_from_syserror (); + goto leave; + } + + /* Send the request. */ + err = send_request (ctrl, request, hostport, put_post_cb, &parm, &fp); + if (err) + goto leave; + + leave: + es_fclose (fp); + xfree (parm.datastring); + xfree (armored); + xfree (request); + xfree (hostport); + return err; +} diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h new file mode 100644 index 000000000..304fc4d1a --- /dev/null +++ b/dirmngr/ks-engine.h @@ -0,0 +1,36 @@ +/* ks-engine.h - Keyserver engines definitions + * Copyright (C) 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DIRMNGR_KS_ENGINE_H +#define DIRMNGR_KS_ENGINE_H 1 + +#include "../common/estream.h" +#include "../common/http.h" + +/*-- ks-engine-hkp.c --*/ +gpg_error_t ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, + estream_t *r_fp); +gpg_error_t ks_hkp_get (ctrl_t ctrl, parsed_uri_t uri, + const char *keyspec, estream_t *r_fp); +gpg_error_t ks_hkp_put (ctrl_t ctrl, parsed_uri_t uri, + const void *data, size_t datalen); + + + +#endif /*DIRMNGR_KS_ENGINE_H*/ diff --git a/dirmngr/server.c b/dirmngr/server.c index 11ba1fb87..fc7b22989 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1,6 +1,6 @@ /* dirmngr.c - LDAP access * Copyright (C) 2002 Klarälvdalens Datakonsult AB - * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009 g10 Code GmbH + * Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011 g10 Code GmbH * * This file is part of DirMngr. * @@ -41,11 +41,18 @@ #include "validate.h" #include "misc.h" #include "ldap-wrapper.h" +#include "ks-action.h" /* To avoid DoS attacks we limit the size of a certificate to something reasonable. */ #define MAX_CERT_LENGTH (8*1024) +/* The same goes for OpenPGP keyblocks, but here we need to allow for + much longer blocks; a 200k keyblock is not too unusual for keys + with a lot of signatures (e.g. 0x5b0358a2). */ +#define MAX_KEYBLOCK_LENGTH (512*1024) + + #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) #define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) @@ -58,7 +65,7 @@ struct server_local_s /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; - /* Per-session LDAP serfver. */ + /* Per-session LDAP servers. */ ldap_server_t ldapservers; /* If this flag is set to true this dirmngr process will be @@ -94,6 +101,21 @@ get_ldapservers_from_ctrl (ctrl_t ctrl) } +/* Release all configured keyserver info from CTRL. */ +void +release_ctrl_keyservers (ctrl_t ctrl) +{ + while (ctrl->keyservers) + { + uri_item_t tmp = ctrl->keyservers->next; + http_release_parsed_uri (ctrl->keyservers->parsed_uri); + xfree (ctrl->keyservers); + ctrl->keyservers = tmp; + } +} + + + /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) @@ -147,7 +169,7 @@ data_line_cookie_close (void *cookie) /* Copy the % and + escaped string S into the buffer D and replace the escape sequences. Note, that it is sufficient to allocate the target string D as long as the source string S, i.e.: strlen(s)+1. - NOte further that If S contains an escaped binary nul the resulting + Note further that if S contains an escaped binary Nul the resulting string D will contain the 0 as well as all other characters but it will be impossible to know whether this is the original EOS or a copied Nul. */ @@ -1335,6 +1357,254 @@ cmd_validate (assuan_context_t ctx, char *line) return leave_cmd (ctx, err); } + +static const char hlp_keyserver[] = + "KEYSERVER [--clear] [<uri>]\n" + "\n" + "If called without arguments list all configured keyserver URLs.\n" + "If called with option \"--clear\" remove all configured keyservers\n" + "If called with an URI add this as keyserver. Note that keyservers\n" + "are configured on a per-session base. A default keyserver may already be\n" + "present, thus the \"--clear\" option must be used to get full control.\n" + "If \"--clear\" and an URI are used together the clear command is\n" + "obviously executed first. A RESET command does not change the list\n" + "of configured keyservers."; +static gpg_error_t +cmd_keyserver (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + int clear_flag, add_flag; + uri_item_t item = NULL; /* gcc 4.4.5 is not able to detect that it + is always initialized. */ + + clear_flag = has_option (line, "--clear"); + line = skip_options (line); + add_flag = !!*line; + + 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); + + err = http_parse_uri (&item->parsed_uri, line, 1); + if (err) + { + xfree (item); + goto leave; + } + } + if (clear_flag) + release_ctrl_keyservers (ctrl); + if (add_flag) + { + item->next = ctrl->keyservers; + ctrl->keyservers = item; + } + + if (!add_flag && !clear_flag) /* List configured keyservers. */ + { + uri_item_t u; + + for (u=ctrl->keyservers; u; u = u->next) + dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL); + } + err = 0; + + leave: + return leave_cmd (ctx, err); +} + + + +static const char hlp_ks_search[] = + "KS_SEARCH {<pattern>}\n" + "\n" + "Search the configured OpenPGP keyservers (see command KEYSERVER)\n" + "for keys matching PATTERN"; +static gpg_error_t +cmd_ks_search (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + strlist_t list, sl; + char *p; + estream_t outfp; + + /* No options for now. */ + line = skip_options (line); + + /* Break the line down into an strlist. Each pattern is + percent-plus escaped. */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + err = gpg_error_from_syserror (); + free_strlist (list); + goto leave; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + /* Setup an output stream and perform the search. */ + outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!outfp) + err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); + else + { + err = ks_action_search (ctrl, list, outfp); + es_fclose (outfp); + } + + leave: + return leave_cmd (ctx, err); +} + + + +static const char hlp_ks_get[] = + "KS_GET {<pattern>}\n" + "\n" + "Get the keys matching PATTERN from the configured OpenPGP keyservers\n" + "(see command KEYSERVER). Each pattern should be a keyid or a fingerprint"; +static gpg_error_t +cmd_ks_get (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + strlist_t list, sl; + char *p; + estream_t outfp; + + /* No options for now. */ + line = skip_options (line); + + /* Break the line down into an strlist. Each pattern is by + definition percent-plus escaped. However we only support keyids + and fingerprints and thus the client has no need to apply the + escaping. */ + list = NULL; + for (p=line; *p; line = p) + { + while (*p && *p != ' ') + p++; + if (*p) + *p++ = 0; + if (*line) + { + sl = xtrymalloc (sizeof *sl + strlen (line)); + if (!sl) + { + err = gpg_error_from_syserror (); + free_strlist (list); + goto leave; + } + sl->flags = 0; + strcpy_escaped_plus (sl->d, line); + sl->next = list; + list = sl; + } + } + + /* Setup an output stream and perform the get. */ + outfp = es_fopencookie (ctx, "w", data_line_cookie_functions); + if (!outfp) + err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream"); + else + { + err = ks_action_get (ctrl, list, outfp); + es_fclose (outfp); + } + + leave: + return leave_cmd (ctx, err); +} + + + +static const char hlp_ks_put[] = + "KS_PUT\n" + "\n" + "Send a key to the configured OpenPGP keyservers. The actual key material\n" + "is then requested by Dirmngr using\n" + "\n" + " INQUIRE KEYBLOCK\n" + "\n" + "The client shall respond with a binary version of the keyblock. For LDAP\n" + "keyservers Dirmngr may ask for meta information of the provided keyblock\n" + "using:\n" + "\n" + " INQUIRE KEYBLOCK_INFO\n" + "\n" + "The client shall respond with a colon delimited info lines"; +static gpg_error_t +cmd_ks_put (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + unsigned char *value = NULL; + size_t valuelen; + unsigned char *info = NULL; + size_t infolen; + + /* No options for now. */ + line = skip_options (line); + + /* Ask for the key material. */ + err = assuan_inquire (ctx, "KEYBLOCK", + &value, &valuelen, MAX_KEYBLOCK_LENGTH); + if (err) + { + log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + if (!valuelen) /* No data returned; return a comprehensible error. */ + { + err = gpg_error (GPG_ERR_MISSING_CERT); + goto leave; + } + + /* Ask for the key meta data. Not actually needed for HKP servers + but we do it anyway test the client implementaion. */ + err = assuan_inquire (ctx, "KEYBLOCK_INFO", + &info, &infolen, MAX_KEYBLOCK_LENGTH); + if (err) + { + log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err)); + goto leave; + } + + /* Send the key. */ + err = ks_action_put (ctrl, value, valuelen); + + leave: + xfree (info); + xfree (value); + return leave_cmd (ctx, err); +} + + static const char hlp_getinfo[] = @@ -1469,6 +1739,10 @@ register_commands (assuan_context_t ctx) { "LISTCRLS", cmd_listcrls, hlp_listcrls }, { "CACHECERT", cmd_cachecert, hlp_cachecert }, { "VALIDATE", cmd_validate, hlp_validate }, + { "KEYSERVER", cmd_keyserver, hlp_keyserver }, + { "KS_SEARCH", cmd_ks_search, hlp_ks_search }, + { "KS_GET", cmd_ks_get, hlp_ks_get }, + { "KS_PUT", cmd_ks_put, hlp_ks_put }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr }, @@ -1487,6 +1761,7 @@ register_commands (assuan_context_t ctx) } +/* Note that we do not reset the list of configured keyservers. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { @@ -1681,8 +1956,8 @@ dirmngr_status (ctrl_t ctrl, const char *keyword, ...) } -/* Note, that we ignore CTRL for now but use the first connection to - send the progress info back. */ +/* Send a tick progress indicator back. Fixme: This is only does for + the currently active channel. */ gpg_error_t dirmngr_tick (ctrl_t ctrl) { |