summaryrefslogtreecommitdiffstats
path: root/dirmngr/ks-engine-hkp.c
diff options
context:
space:
mode:
Diffstat (limited to 'dirmngr/ks-engine-hkp.c')
-rw-r--r--dirmngr/ks-engine-hkp.c558
1 files changed, 558 insertions, 0 deletions
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;
+}