aboutsummaryrefslogtreecommitdiffstats
path: root/dirmngr/http.c
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-03-18 18:41:07 +0000
committerWerner Koch <[email protected]>2019-03-18 18:41:07 +0000
commita52d883fdbe6e0de8cb26f9c6aedf01a7f66cbe7 (patch)
treee59dfb41b24a12c314dbd1137637366315ac1453 /dirmngr/http.c
parentkbx: Add framework for a public key daemon. (diff)
parentspeedo: Fix installer build with NSIS-3 (diff)
downloadgnupg-a52d883fdbe6e0de8cb26f9c6aedf01a7f66cbe7.tar.gz
gnupg-a52d883fdbe6e0de8cb26f9c6aedf01a7f66cbe7.zip
Merge branch 'master' into switch-to-gpgk
--
Diffstat (limited to 'dirmngr/http.c')
-rw-r--r--dirmngr/http.c230
1 files changed, 228 insertions, 2 deletions
diff --git a/dirmngr/http.c b/dirmngr/http.c
index 5fb7eed04..d6856fe05 100644
--- a/dirmngr/http.c
+++ b/dirmngr/http.c
@@ -1350,6 +1350,8 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
uri->v6lit = 0;
uri->onion = 0;
uri->explicit_port = 0;
+ uri->off_host = 0;
+ uri->off_path = 0;
/* A quick validity check. */
if (strspn (p, VALID_URI_CHARS) != n)
@@ -1393,7 +1395,19 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
{
p += 2;
if ((p2 = strchr (p, '/')))
- *p2++ = 0;
+ {
+ if (p2 - uri->buffer > 10000)
+ return GPG_ERR_BAD_URI;
+ uri->off_path = p2 - uri->buffer;
+ *p2++ = 0;
+ }
+ else
+ {
+ n = (p - uri->buffer) + strlen (p);
+ if (n > 10000)
+ return GPG_ERR_BAD_URI;
+ uri->off_path = n;
+ }
/* Check for username/password encoding */
if ((p3 = strchr (p, '@')))
@@ -1412,11 +1426,19 @@ do_parse_uri (parsed_uri_t uri, int only_local_part,
*p3++ = '\0';
/* worst case, uri->host should have length 0, points to \0 */
uri->host = p + 1;
+ if (p - uri->buffer > 10000)
+ return GPG_ERR_BAD_URI;
+ uri->off_host = (p + 1) - uri->buffer;
uri->v6lit = 1;
p = p3;
}
else
- uri->host = p;
+ {
+ uri->host = p;
+ if (p - uri->buffer > 10000)
+ return GPG_ERR_BAD_URI;
+ uri->off_host = p - uri->buffer;
+ }
if ((p3 = strchr (p, ':')))
{
@@ -3496,3 +3518,207 @@ uri_query_lookup (parsed_uri_t uri, const char *key)
return NULL;
}
+
+
+/* Return true if both URI point to the same host for the purpose of
+ * redirection check. A is the original host and B the host given in
+ * the Location header. As a temporary workaround a fixed list of
+ * exceptions is also consulted. */
+static int
+same_host_p (parsed_uri_t a, parsed_uri_t b)
+{
+ static struct
+ {
+ const char *from; /* NULL uses the last entry from the table. */
+ const char *to;
+ } allow[] =
+ {
+ { "protonmail.com", "api.protonmail.com" },
+ { NULL, "api.protonmail.ch" },
+ { "protonmail.ch", "api.protonmail.com" },
+ { NULL, "api.protonmail.ch" }
+ };
+ int i;
+ const char *from;
+
+ if (!a->host || !b->host)
+ return 0;
+
+ if (!ascii_strcasecmp (a->host, b->host))
+ return 1;
+
+ from = NULL;
+ for (i=0; i < DIM (allow); i++)
+ {
+ if (allow[i].from)
+ from = allow[i].from;
+ if (!from)
+ continue;
+ if (!ascii_strcasecmp (from, a->host)
+ && !ascii_strcasecmp (allow[i].to, b->host))
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* Prepare a new URL for a HTTP redirect. INFO has flags controlling
+ * the operation, STATUS_CODE is used for diagnostics, LOCATION is the
+ * value of the "Location" header, and R_URL reveives the new URL on
+ * success or NULL or error. Note that INFO->ORIG_URL is
+ * required. */
+gpg_error_t
+http_prepare_redirect (http_redir_info_t *info, unsigned int status_code,
+ const char *location, char **r_url)
+{
+ gpg_error_t err;
+ parsed_uri_t locuri;
+ parsed_uri_t origuri;
+ char *newurl;
+ char *p;
+
+ *r_url = NULL;
+
+ if (!info || !info->orig_url)
+ return gpg_error (GPG_ERR_INV_ARG);
+
+ if (!info->silent)
+ log_info (_("URL '%s' redirected to '%s' (%u)\n"),
+ info->orig_url, location? location:"[none]", status_code);
+
+ if (!info->redirects_left)
+ {
+ if (!info->silent)
+ log_error (_("too many redirections\n"));
+ return gpg_error (GPG_ERR_NO_DATA);
+ }
+ info->redirects_left--;
+
+ if (!location || !*location)
+ return gpg_error (GPG_ERR_NO_DATA);
+
+ err = http_parse_uri (&locuri, location, 0);
+ if (err)
+ return err;
+
+ /* Make sure that an onion address only redirects to another
+ * onion address, or that a https address only redirects to a
+ * https address. */
+ if (info->orig_onion && !locuri->onion)
+ {
+ http_release_parsed_uri (locuri);
+ return gpg_error (GPG_ERR_FORBIDDEN);
+ }
+ if (!info->allow_downgrade && info->orig_https && !locuri->use_tls)
+ {
+ http_release_parsed_uri (locuri);
+ return gpg_error (GPG_ERR_FORBIDDEN);
+ }
+
+ if (info->trust_location)
+ {
+ /* We trust the Location - return it verbatim. */
+ http_release_parsed_uri (locuri);
+ newurl = xtrystrdup (location);
+ if (!newurl)
+ {
+ err = gpg_error_from_syserror ();
+ http_release_parsed_uri (locuri);
+ return err;
+ }
+ }
+ else if ((err = http_parse_uri (&origuri, info->orig_url, 0)))
+ {
+ http_release_parsed_uri (locuri);
+ return err;
+ }
+ else if (same_host_p (origuri, locuri))
+ {
+ /* The host is the same or on an exception list and thus we can
+ * take the location verbatim. */
+ http_release_parsed_uri (origuri);
+ http_release_parsed_uri (locuri);
+ newurl = xtrystrdup (location);
+ if (!newurl)
+ {
+ err = gpg_error_from_syserror ();
+ http_release_parsed_uri (locuri);
+ return err;
+ }
+ }
+ else
+ {
+ /* We take only the host and port from the URL given in the
+ * Location. This limits the effects of redirection attacks by
+ * rogue hosts returning an URL to servers in the client's own
+ * network. We don't even include the userinfo because they
+ * should be considered similar to the path and query parts.
+ */
+ if (!(locuri->off_path - locuri->off_host))
+ {
+ http_release_parsed_uri (origuri);
+ http_release_parsed_uri (locuri);
+ return gpg_error (GPG_ERR_BAD_URI);
+ }
+ if (!(origuri->off_path - origuri->off_host))
+ {
+ http_release_parsed_uri (origuri);
+ http_release_parsed_uri (locuri);
+ return gpg_error (GPG_ERR_BAD_URI);
+ }
+
+ newurl = xtrymalloc (strlen (origuri->original)
+ + (locuri->off_path - locuri->off_host) + 1);
+ if (!newurl)
+ {
+ err = gpg_error_from_syserror ();
+ http_release_parsed_uri (origuri);
+ http_release_parsed_uri (locuri);
+ return err;
+ }
+ /* Build new URL from
+ * uriguri: scheme userinfo ---- ---- path rest
+ * locuri: ------ -------- host port ---- ----
+ */
+ p = newurl;
+ memcpy (p, origuri->original, origuri->off_host);
+ p += origuri->off_host;
+ memcpy (p, locuri->original + locuri->off_host,
+ (locuri->off_path - locuri->off_host));
+ p += locuri->off_path - locuri->off_host;
+ strcpy (p, origuri->original + origuri->off_path);
+
+ http_release_parsed_uri (origuri);
+ http_release_parsed_uri (locuri);
+ if (!info->silent)
+ log_info (_("redirection changed to '%s'\n"), newurl);
+ }
+
+ *r_url = newurl;
+ return 0;
+}
+
+
+/* Return string describing the http STATUS. Returns an empty string
+ * for an unknown status. */
+const char *
+http_status2string (unsigned int status)
+{
+ switch (status)
+ {
+ case 500: return "Internal Server Error";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
+ case 503: return "Service Unavailable";
+ case 504: return "Gateway Timeout";
+ case 505: return "HTTP version Not Supported";
+ case 506: return "Variant Also Negation";
+ case 507: return "Insufficient Storage";
+ case 508: return "Loop Detected";
+ case 510: return "Not Extended";
+ case 511: return "Network Authentication Required";
+ }
+
+ return "";
+}