# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: henrik@henriknordstrom.net-20100302165813-\ # xhabeaopfdvbi372 # target_branch: ../trunk/ # testament_sha1: 9ef7f250de84e6076ea7f172491f9695736e71f4 # timestamp: 2010-03-02 17:59:33 +0100 # base_revision_id: squidadm@squid-cache.org-20100301011317-\ # 5miwqblkua9c5gzq # # Begin patch === modified file 'src/HttpHeaderTools.cc' --- src/HttpHeaderTools.cc 2009-09-10 03:08:35 +0000 +++ src/HttpHeaderTools.cc 2010-03-02 14:08:22 +0000 @@ -335,25 +335,29 @@ { const char *end, *pos; val->clean(); - assert (*start == '"'); + if (*start != '"') { + debugs(66, 2, "failed to parse a quoted-string header field near '" << start << "'"); + return 0; + } pos = start + 1; - while (1) { - if (!(end = index (pos,'"'))) { - debugs(66, 2, "failed to parse a quoted-string header field near '" << start << "'"); - return 0; - } - - /* check for quoted-chars */ - if (*(end - 1) != '\\') { - /* done */ - val->append(start + 1, end-start-1); - return 1; - } - - /* try for the end again */ - pos = end + 1; + while (*pos != '"') { + if (*pos == '\\') { + pos++; + } + if (!*pos) { + debugs(66, 2, "failed to parse a quoted-string header field near '" << start << "'"); + val->clean(); + return 0; + } + end = pos + strcspn(pos, "\"\\"); + val->append(pos, end-pos); + pos = end; } + /* Make sure it's defined even if empty "" */ + if (!val->defined()) + val->limitInit("", 0); + return 1; } /** === modified file 'src/auth/digest/auth_digest.cc' --- src/auth/digest/auth_digest.cc 2010-02-14 00:09:05 +0000 +++ src/auth/digest/auth_digest.cc 2010-03-02 16:58:13 +0000 @@ -67,6 +67,33 @@ CBDATA_TYPE(DigestAuthenticateStateData); +enum http_digest_attr_type { + DIGEST_USERNAME=1, + DIGEST_REALM, + DIGEST_QOP, + DIGEST_ALGORITHM, + DIGEST_URI, + DIGEST_NONCE, + DIGEST_NC, + DIGEST_CNONCE, + DIGEST_RESPONSE, + DIGEST_ENUM_END +}; + +static const HttpHeaderFieldAttrs DigestAttrs[DIGEST_ENUM_END] = { + {"username", (http_hdr_type)DIGEST_USERNAME}, + {"realm", (http_hdr_type)DIGEST_REALM}, + {"qop", (http_hdr_type)DIGEST_QOP}, + {"algorithm", (http_hdr_type)DIGEST_ALGORITHM}, + {"uri", (http_hdr_type)DIGEST_URI}, + {"nonce", (http_hdr_type)DIGEST_NONCE}, + {"nc", (http_hdr_type)DIGEST_NC}, + {"cnonce", (http_hdr_type)DIGEST_CNONCE}, + {"response", (http_hdr_type)DIGEST_RESPONSE}, +}; + +static HttpHeaderFieldInfo *DigestFieldsInfo = NULL; + /* * * Nonce Functions @@ -505,6 +532,9 @@ if (digestauthenticators) helperShutdown(digestauthenticators); + httpHeaderDestroyFieldsInfo(DigestFieldsInfo, DIGEST_ENUM_END); + DigestFieldsInfo = NULL; + authdigest_initialised = 0; if (!shutting_down) { @@ -867,6 +897,7 @@ AuthDigestConfig::init(AuthConfig * scheme) { if (authenticate) { + DigestFieldsInfo = httpHeaderBuildFieldsInfo(DigestAttrs, DIGEST_ENUM_END); authenticateDigestNonceSetup(); authdigest_initialised = 1; @@ -1090,124 +1121,84 @@ String temp(proxy_auth); while (strListGetItem(&temp, ',', &item, &ilen, &pos)) { - if ((p = strchr(item, '=')) && (p - item < ilen)) - ilen = p++ - item; - - if (!strncmp(item, "username", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - /* quote mark */ - p++; - + String value; + size_t nlen; + /* isolate directive name */ + if ((p = (const char *)memchr(item, '=', ilen)) && (p - item < ilen)) { + nlen = p++ - item; + if (!httpHeaderParseQuotedString(p, &value)) + value.limitInit(p, ilen - (p - item)); + } else + nlen = ilen; + + if (!value.defined()) { + debugs(29, 9, "authDigestDecodeAuth: Failed to parse attribute '" << temp << "' in '" << proxy_auth << "'"); + continue; + } + + /* find type */ + http_digest_attr_type type = (http_digest_attr_type)httpHeaderIdByName(item, nlen, DigestFieldsInfo, DIGEST_ENUM_END); + + switch (type) { + case DIGEST_USERNAME: safe_free(username); - username = xstrndup(p, strchr(p, '"') + 1 - p); - + username = xstrndup(value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found Username '" << username << "'"); - } else if (!strncmp(item, "realm", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - /* quote mark */ - p++; - + break; + + case DIGEST_REALM: safe_free(digest_request->realm); - digest_request->realm = xstrndup(p, strchr(p, '"') + 1 - p); - + digest_request->realm = xstrndup(value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found realm '" << digest_request->realm << "'"); - } else if (!strncmp(item, "qop", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - if (*p == '\"') - /* quote mark */ - p++; - + break; + + case DIGEST_QOP: safe_free(digest_request->qop); - digest_request->qop = xstrndup(p, strcspn(p, "\" \t\r\n()<>@,;:\\/[]?={}") + 1); - + digest_request->qop = xstrndup(value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found qop '" << digest_request->qop << "'"); - } else if (!strncmp(item, "algorithm", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - if (*p == '\"') - /* quote mark */ - p++; - + break; + + case DIGEST_ALGORITHM: safe_free(digest_request->algorithm); - digest_request->algorithm = xstrndup(p, strcspn(p, "\" \t\r\n()<>@,;:\\/[]?={}") + 1); - + digest_request->algorithm = xstrndup(value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found algorithm '" << digest_request->algorithm << "'"); - } else if (!strncmp(item, "uri", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - /* quote mark */ - p++; - + break; + + case DIGEST_URI: safe_free(digest_request->uri); - digest_request->uri = xstrndup(p, strchr(p, '"') + 1 - p); - + digest_request->uri = xstrndup(value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found uri '" << digest_request->uri << "'"); - } else if (!strncmp(item, "nonce", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - /* quote mark */ - p++; - + break; + + case DIGEST_NONCE: safe_free(digest_request->nonceb64); - digest_request->nonceb64 = xstrndup(p, strchr(p, '"') + 1 - p); - + digest_request->nonceb64 = xstrndup(value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found nonce '" << digest_request->nonceb64 << "'"); - } else if (!strncmp(item, "nc", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - xstrncpy(digest_request->nc, p, 9); - + break; + + case DIGEST_NC: + if (value.size() != 8) { + debugs(29, 9, "authDigestDecodeAuth: Invalid nc '" << value << "' in '" << temp << "'"); + } + xstrncpy(digest_request->nc, value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found noncecount '" << digest_request->nc << "'"); - } else if (!strncmp(item, "cnonce", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - /* quote mark */ - p++; - + break; + + case DIGEST_CNONCE: safe_free(digest_request->cnonce); - digest_request->cnonce = xstrndup(p, strchr(p, '"') + 1 - p); - + digest_request->cnonce = xstrndup(value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found cnonce '" << digest_request->cnonce << "'"); - } else if (!strncmp(item, "response", ilen)) { - /* white space */ - - while (xisspace(*p)) - p++; - - /* quote mark */ - p++; - + break; + + case DIGEST_RESPONSE: safe_free(digest_request->response); - digest_request->response = xstrndup(p, strchr(p, '"') + 1 - p); - + digest_request->response = xstrndup(value.rawBuf(), value.size() + 1); debugs(29, 9, "authDigestDecodeAuth: Found response '" << digest_request->response << "'"); + break; + + default: + debugs(29, 3, "authDigestDecodeAuth: Unknown attribute '" << item << "' in '" << temp << "'"); + } } @@ -1225,60 +1216,36 @@ * correct values - 400/401/407 */ - /* first the NONCE count */ - - if (digest_request->cnonce && strlen(digest_request->nc) != 8) { - debugs(29, 4, "authenticateDigestDecode: nonce count length invalid"); - return authDigestLogUsername(username, digest_request); - } - - /* now the nonce */ - nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64); - - if (!nonce) { - /* we couldn't find a matching nonce! */ - debugs(29, 4, "authenticateDigestDecode: Unexpected or invalid nonce received"); - return authDigestLogUsername(username, digest_request); - } - - digest_request->nonce = nonce; - authDigestNonceLink(nonce); - - /* check the qop is what we expected. Note that for compatability with - * RFC 2069 we should support a missing qop. Tough. */ - - if (digest_request->qop && strcmp(digest_request->qop, QOP_AUTH) != 0) { - /* we received a qop option we didn't send */ - debugs(29, 4, "authenticateDigestDecode: Invalid qop option received"); + /* 2069 requirements */ + + /* do we have a username ? */ + if (!username || username[0] == '\0') { + debugs(29, 2, "authenticateDigestDecode: Empty or not present username"); + return authDigestLogUsername(username, digest_request); + } + + /* do we have a realm ? */ + if (!digest_request->realm || digest_request->realm[0] == '\0') { + debugs(29, 2, "authenticateDigestDecode: Empty or not present realm"); + return authDigestLogUsername(username, digest_request); + } + + /* and a nonce? */ + if (!digest_request->nonceb64 || digest_request->nonceb64[0] == '\0') { + debugs(29, 2, "authenticateDigestDecode: Empty or not present nonce"); return authDigestLogUsername(username, digest_request); } /* we can't check the URI just yet. We'll check it in the - * authenticate phase */ + * authenticate phase, but needs to be given */ + if (!digest_request->uri || digest_request->uri[0] == '\0') { + debugs(29, 2, "authenticateDigestDecode: Missing URI field"); + return authDigestLogUsername(username, digest_request); + } /* is the response the correct length? */ - if (!digest_request->response || strlen(digest_request->response) != 32) { - debugs(29, 4, "authenticateDigestDecode: Response length invalid"); - return authDigestLogUsername(username, digest_request); - } - - /* do we have a username ? */ - if (!username || username[0] == '\0') { - debugs(29, 4, "authenticateDigestDecode: Empty or not present username"); - return authDigestLogUsername(username, digest_request); - } - - /* check that we're not being hacked / the username hasn't changed */ - if (nonce->user && strcmp(username, nonce->user->username())) { - debugs(29, 4, "authenticateDigestDecode: Username for the nonce does not equal the username for the request"); - return authDigestLogUsername(username, digest_request); - } - - /* if we got a qop, did we get a cnonce or did we get a cnonce wihtout a qop? */ - if ((digest_request->qop && !digest_request->cnonce) - || (!digest_request->qop && digest_request->cnonce)) { - debugs(29, 4, "authenticateDigestDecode: qop without cnonce, or vice versa!"); + debugs(29, 2, "authenticateDigestDecode: Response length invalid"); return authDigestLogUsername(username, digest_request); } @@ -1291,6 +1258,54 @@ return authDigestLogUsername(username, digest_request); } + /* 2617 requirements, indicated by qop */ + if (digest_request->qop) { + + /* check the qop is what we expected. */ + if (strcmp(digest_request->qop, QOP_AUTH) != 0) { + /* we received a qop option we didn't send */ + debugs(29, 2, "authenticateDigestDecode: Invalid qop option received"); + return authDigestLogUsername(username, digest_request); + } + + /* check cnonce */ + if (!digest_request->cnonce || digest_request->cnonce[0] == '\0') { + debugs(29, 2, "authenticateDigestDecode: Missing URI field"); + return authDigestLogUsername(username, digest_request); + } + + /* check nc */ + if (strlen(digest_request->nc) != 8 || strspn(digest_request->nc, "0123456789abcdefABCDEF") != 8) { + debugs(29, 2, "authenticateDigestDecode: invalid nonce count"); + return authDigestLogUsername(username, digest_request); + } + } else { + /* cnonce and nc both require qop */ + if (digest_request->cnonce || digest_request->nc) { + debugs(29, 4, "authenticateDigestDecode: missing qop!"); + return authDigestLogUsername(username, digest_request); + } + } + + /** below nonce state dependent **/ + + /* now the nonce */ + nonce = authenticateDigestNonceFindNonce(digest_request->nonceb64); + if (!nonce) { + /* we couldn't find a matching nonce! */ + debugs(29, 2, "authenticateDigestDecode: Unexpected or invalid nonce received"); + return authDigestLogUsername(username, digest_request); + } + + digest_request->nonce = nonce; + authDigestNonceLink(nonce); + + /* check that we're not being hacked / the username hasn't changed */ + if (nonce->user && strcmp(username, nonce->user->username())) { + debugs(29, 2, "authenticateDigestDecode: Username for the nonce does not equal the username for the request"); + return authDigestLogUsername(username, digest_request); + } + /* the method we'll check at the authenticate step as well */ # Begin bundle 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