diff options
Diffstat (limited to 'dirmngr')
-rw-r--r-- | dirmngr/ks-action.c | 60 | ||||
-rw-r--r-- | dirmngr/ks-action.h | 1 | ||||
-rw-r--r-- | dirmngr/ks-engine-hkp.c | 294 | ||||
-rw-r--r-- | dirmngr/ks-engine.h | 2 | ||||
-rw-r--r-- | dirmngr/server.c | 61 |
5 files changed, 335 insertions, 83 deletions
diff --git a/dirmngr/ks-action.c b/dirmngr/ks-action.c index 5ad4b1810..f376c27d1 100644 --- a/dirmngr/ks-action.c +++ b/dirmngr/ks-action.c @@ -79,6 +79,7 @@ ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp) { err = copy_stream (infp, outfp); es_fclose (infp); + break; } } } @@ -88,3 +89,62 @@ ks_action_search (ctrl_t ctrl, strlist_t patterns, estream_t outfp) return err; } + +/* Get the requested keys (macthing 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; +} + diff --git a/dirmngr/ks-action.h b/dirmngr/ks-action.h index 57903398d..4a92ed1a4 100644 --- a/dirmngr/ks-action.h +++ b/dirmngr/ks-action.h @@ -21,6 +21,7 @@ #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); #endif /*DIRMNGR_KS_ACTION_H*/ diff --git a/dirmngr/ks-engine-hkp.c b/dirmngr/ks-engine-hkp.c index 356f64348..662e9e4cb 100644 --- a/dirmngr/ks-engine-hkp.c +++ b/dirmngr/ks-engine-hkp.c @@ -37,6 +37,117 @@ #define MAX_REDIRECTS 2 +/* Send an HTTP request. On success returns an estream object at + R_FP. HOSTPORTSTR is only used for diagnostics. */ +static gpg_error_t +send_request (ctrl_t ctrl, const char *request, const char *hostportstr, + 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, 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); + 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; + fp = NULL; + http_close (http, 1); + http = NULL; + + leave: + es_fclose (fp); + http_close (http, 0); + xfree (request_buffer); + return err; +} + + + /* 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 @@ -48,10 +159,8 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, char fprbuf[2+40+1]; const char *scheme; char portstr[10]; - http_t http = NULL; char *hostport = NULL; char *request = NULL; - int redirects_left = MAX_REDIRECTS; estream_t fp = NULL; *r_fp = NULL; @@ -142,87 +251,11 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, } /* Send the request. */ - once_more: - err = http_open (&http, 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); - http_start_data (http); - if (es_ferror (fp)) - err = gpg_error_from_syserror (); - } + err = send_request (ctrl, request, hostport, &fp); if (err) - { - /* Fixme: After a redirection we show the old host name. */ - log_error (_("error connecting to `%s': %s\n"), - hostport, 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"), - hostport, gpg_strerror (err)); - goto leave; - } - - switch (http_get_status_code (http)) - { - case 200: - 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); - request = xtrystrdup (s); - if (request) - { - 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; - } + goto leave; /* Start reading the response. */ - fp = http_get_read_ptr (http); - if (!fp) - { - err = gpg_error (GPG_ERR_BUG); - goto leave; - } { int c = es_getc (fp); if (c == -1) @@ -241,15 +274,110 @@ ks_hkp_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern, 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, &fp); + if (err) + goto leave; + /* Return the read stream and close the HTTP context. */ *r_fp = fp; fp = NULL; - http_close (http, 1); - http = NULL; leave: es_fclose (fp); - http_close (http, 0); xfree (request); xfree (hostport); return err; diff --git a/dirmngr/ks-engine.h b/dirmngr/ks-engine.h index f68782f49..4b26662e8 100644 --- a/dirmngr/ks-engine.h +++ b/dirmngr/ks-engine.h @@ -26,6 +26,8 @@ /*-- 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); diff --git a/dirmngr/server.c b/dirmngr/server.c index 40e8dabd4..5d61da898 100644 --- a/dirmngr/server.c +++ b/dirmngr/server.c @@ -1475,6 +1475,66 @@ cmd_ks_search (assuan_context_t ctx, char *line) } + +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_getinfo[] = @@ -1611,6 +1671,7 @@ register_commands (assuan_context_t ctx) { "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 }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr }, { "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr }, |