/* ks-engine-http.c - HTTP OpenPGP key access * 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 . */ #include #include #include #include #include #include "dirmngr.h" #include "misc.h" #include "ks-engine.h" /* How many redirections do we allow. */ #define MAX_REDIRECTS 2 /* Print a help output for the schemata supported by this module. */ gpg_error_t ks_http_help (ctrl_t ctrl, parsed_uri_t uri) { const char data[] = "Handler for HTTP URLs:\n" " http://\n" #if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS " https://\n" #endif "Supported methods: fetch\n"; gpg_error_t err; #if HTTP_USE_GNUTLS || HTTP_USE_NTBTLS const char data2[] = " http\n https"; #else const char data2[] = " http"; #endif if (!uri) err = ks_print_help (ctrl, data2); else if (uri->is_http && strcmp (uri->scheme, "hkp")) err = ks_print_help (ctrl, data); else err = 0; return err; } /* Get the key from URL which is expected to specify a http style * scheme. On success R_FP has an open stream to read the data. * Despite its name this function is also used to retrieve arbitrary * data via https or http. */ gpg_error_t ks_http_fetch (ctrl_t ctrl, const char *url, unsigned int flags, estream_t *r_fp) { gpg_error_t err; http_session_t session = NULL; unsigned int session_flags; http_t http = NULL; http_redir_info_t redirinfo = { MAX_REDIRECTS }; estream_t fp = NULL; char *request_buffer = NULL; parsed_uri_t uri = NULL; parsed_uri_t helpuri = NULL; err = http_parse_uri (&uri, url, 0); if (err) goto leave; redirinfo.ctrl = ctrl; redirinfo.orig_url = url; redirinfo.orig_onion = uri->onion; redirinfo.orig_https = uri->use_tls; redirinfo.allow_downgrade = !!(flags & KS_HTTP_FETCH_ALLOW_DOWNGRADE); /* By default we only use the system provided certificates with this * fetch command. */ session_flags = HTTP_FLAG_TRUST_SYS; if ((flags & KS_HTTP_FETCH_NO_CRL) || ctrl->http_no_crl) session_flags |= HTTP_FLAG_NO_CRL; if ((flags & KS_HTTP_FETCH_TRUST_CFG)) session_flags |= HTTP_FLAG_TRUST_CFG; once_more: err = http_session_new (&session, NULL, session_flags, gnupg_http_tls_verify_cb, ctrl); if (err) goto leave; http_session_set_log_cb (session, cert_log_cb); http_session_set_timeout (session, ctrl->timeout); *r_fp = NULL; err = http_open (&http, HTTP_REQ_GET, url, /* httphost */ NULL, /* fixme: AUTH */ NULL, ((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0) | (DBG_LOOKUP? HTTP_FLAG_LOG_RESP:0) | (dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0) | (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0) | (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)), ctrl->http_proxy, session, 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. */ if ((flags & KS_HTTP_FETCH_NOCACHE)) 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) { log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err)); if (gpg_err_code (err) == GPG_ERR_WRONG_NAME && gpg_err_source (err) == GPG_ERR_SOURCE_TLS) { const char *errhostname; http_release_parsed_uri (helpuri); if (http_parse_uri (&helpuri, url, 0)) errhostname = url; /* On parse error we use the full URL. */ else errhostname = helpuri->host? helpuri->host : "?"; dirmngr_status_printf (ctrl, "NOTE", "tls_cert_error %u" " bad cert for '%s': %s", err, errhostname, "Hostname does not match the certificate"); } 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"), url, gpg_strerror (err)); goto leave; } switch (http_get_status_code (http)) { case 200: err = 0; break; /* Success. */ case 301: case 302: case 307: { xfree (request_buffer); err = http_prepare_redirect (&redirinfo, http_get_status_code (http), http_get_header (http, "Location"), &request_buffer); if (err) goto leave; url = request_buffer; http_close (http, 0); http = NULL; http_session_release (session); session = NULL; } goto once_more; case 413: /* Payload too large */ err = gpg_error (GPG_ERR_TOO_LARGE); goto leave; default: log_error (_("error accessing '%s': http status %u\n"), url, 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); http_session_release (session); xfree (request_buffer); http_release_parsed_uri (uri); http_release_parsed_uri (helpuri); return err; }