libmiscencoding_la_SOURCES = \
base64.c \
- charset.c \
html_quote.c \
md5.c \
rfc1738.c \
+++ /dev/null
-/*
- * Copyright (C) 1996-2019 The Squid Software Foundation and contributors
- *
- * Squid software is distributed under GPLv2+ license and includes
- * contributions from numerous individuals and organizations.
- * Please see the COPYING and CONTRIBUTORS files for details.
- */
-
-#include "squid.h"
-#include "charset.h"
-
-/** Convert ISO-LATIN-1 to UTF-8 */
-char *
-latin1_to_utf8(char *out, size_t size, const char *in)
-{
- unsigned char *p = (unsigned char *)out;
- for (; *in && size > 2; in++) {
- unsigned char ch = (unsigned char)*in;
- if (ch < 0x80) {
- *p++ = ch;
- size--;
- } else {
- *p++ = (ch >> 6) | 0xc0;
- size--;
- *p++ = (ch & 0x3f) | 0x80;
- size--;
- }
- }
- *p = '\0';
- if (*in)
- return NULL;
- return out;
-}
-
SchemesConfig.cc \
State.h \
State.cc \
+ toUtf.h \
+ toUtf.cc \
User.h \
User.cc \
UserRequest.h \
#include "ConfigParser.h"
#include "Debug.h"
#include "format/Format.h"
+#include "errorpage.h"
#include "globals.h"
#include "Store.h"
#include "wordlist.h"
config->keyExtras->assemble(rmb, al, 0);
}
- return config->decode(proxy_auth, rmb.hasContent() ? rmb.content() : NULL);
+ return config->decode(proxy_auth, al->request, rmb.hasContent() ? rmb.content() : nullptr);
}
Auth::SchemeConfig *
keyExtrasLine.clean();
}
+bool
+Auth::SchemeConfig::isCP1251EncodingAllowed(const HttpRequest *request)
+{
+ String hdr;
+
+ if (!request || !request->header.getList(Http::HdrType::ACCEPT_LANGUAGE, &hdr))
+ return false;
+
+ char lang[256];
+ size_t pos = 0; // current parsing position in header string
+
+ while (strHdrAcptLangGetItem(hdr, lang, 256, pos)) {
+
+ /* wildcard uses the configured default language */
+ if (lang[0] == '*' && lang[1] == '\0')
+ return false;
+
+ if ((strncmp(lang, "ru", 2) == 0 // Russian
+ || strncmp(lang, "uk", 2) == 0 // Ukrainian
+ || strncmp(lang, "be", 2) == 0 // Belorussian
+ || strncmp(lang, "bg", 2) == 0 // Bulgarian
+ || strncmp(lang, "sr", 2) == 0)) { // Serbian
+ if (lang[2] == '-') {
+ if (strcmp(lang + 3, "latn") == 0) // not Cyrillic
+ return false;
+ } else if (xisalpha(lang[2])) {
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
\param proxy_auth Login Pattern to parse.
\retval * Details needed to authenticate.
*/
- virtual UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm) = 0;
+ virtual UserRequest::Pointer decode(char const *proxy_auth, const HttpRequest *request, const char *requestRealm) = 0;
/**
* squid is finished with this config, release any unneeded resources.
int utf8 = 0; ///< wheter to accept UTF-8 characterset instead of ASCII. default: off
protected:
+ /**
+ * Parse Accept-Language header and return whether a CP1251 encoding
+ * allowed or not.
+ *
+ * CP1251 (aka Windows-1251) is an 8-bit character encoding, designed
+ * to cover languages that use the Cyrillic script.
+ */
+ bool isCP1251EncodingAllowed(const HttpRequest *request);
+
/// RFC 7235 section 2.2 - Protection Space (Realm)
SBuf realm;
};
#include "auth/CredentialsCache.h"
#include "auth/Gadgets.h"
#include "auth/State.h"
+#include "auth/toUtf.h"
#include "base64.h"
#include "cache_cf.h"
-#include "charset.h"
#include "helper.h"
#include "HttpHeaderTools.h"
#include "HttpReply.h"
#include "mgr/Registration.h"
#include "rfc1738.h"
+#include "sbuf/SBuf.h"
#include "SquidTime.h"
#include "Store.h"
#include "util.h"
Auth::Basic::Config::fixHeader(Auth::UserRequest::Pointer, HttpReply *rep, Http::HdrType hdrType, HttpRequest *)
{
if (authenticateProgram) {
- debugs(29, 9, "Sending type:" << hdrType << " header: 'Basic realm=\"" << realm << "\"'");
- httpHeaderPutStrf(&rep->header, hdrType, "Basic realm=\"" SQUIDSBUFPH "\"", SQUIDSBUFPRINT(realm));
+ if (utf8) {
+ debugs(29, 9, "Sending type:" << hdrType << " header: 'Basic realm=\"" << realm << "\", charset=\"UTF-8\"'");
+ httpHeaderPutStrf(&rep->header, hdrType, "Basic realm=\"" SQUIDSBUFPH "\", charset=\"UTF-8\"", SQUIDSBUFPRINT(realm));
+ } else {
+ debugs(29, 9, "Sending type:" << hdrType << " header: 'Basic realm=\"" << realm << "\"'");
+ httpHeaderPutStrf(&rep->header, hdrType, "Basic realm=\"" SQUIDSBUFPH "\"", SQUIDSBUFPRINT(realm));
+ }
}
}
}
char *
-Auth::Basic::Config::decodeCleartext(const char *httpAuthHeader)
+Auth::Basic::Config::decodeCleartext(const char *httpAuthHeader, const HttpRequest *request)
{
const char *proxy_auth = httpAuthHeader;
if (base64_decode_update(&ctx, &dstLen, reinterpret_cast<uint8_t*>(cleartext), srcLen, eek) && base64_decode_final(&ctx)) {
cleartext[dstLen] = '\0';
+ if (utf8 && !isValidUtf8String(cleartext, cleartext + dstLen)) {
+ auto str = isCP1251EncodingAllowed(request) ?
+ Cp1251ToUtf8(cleartext) : Latin1ToUtf8(cleartext);
+ safe_free(cleartext);
+ cleartext = xstrdup(str.c_str());
+ }
+
/*
* Don't allow NL or CR in the credentials.
* Oezguer Kesim <oec@codeblau.de>
* descriptive message to the user.
*/
Auth::UserRequest::Pointer
-Auth::Basic::Config::decode(char const *proxy_auth, const char *aRequestRealm)
+Auth::Basic::Config::decode(char const *proxy_auth, const HttpRequest *request, const char *aRequestRealm)
{
Auth::UserRequest::Pointer auth_user_request = dynamic_cast<Auth::UserRequest*>(new Auth::Basic::UserRequest);
/* decode the username */
// retrieve the cleartext (in a dynamically allocated char*)
- char *cleartext = decodeCleartext(proxy_auth);
+ const auto cleartext = decodeCleartext(proxy_auth, request);
// empty header? no auth details produced...
if (!cleartext)
Config();
virtual bool active() const;
virtual bool configured() const;
- virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm);
+ virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const HttpRequest *request, const char *requestRealm);
virtual void done();
virtual void rotateHelpers();
virtual bool dump(StoreEntry *, const char *, Auth::SchemeConfig *) const;
int casesensitive;
private:
- char * decodeCleartext(const char *httpAuthHeader);
+ char * decodeCleartext(const char *httpAuthHeader, const HttpRequest *request);
};
} // namespace Basic
#include "auth/basic/UserRequest.h"
#include "auth/QueueNode.h"
#include "auth/State.h"
-#include "charset.h"
#include "Debug.h"
#include "format/Format.h"
#include "helper.h"
char buf[HELPER_INPUT_BUFFER];
static char usern[HELPER_INPUT_BUFFER];
static char pass[HELPER_INPUT_BUFFER];
- if (static_cast<Auth::Basic::Config*>(user()->config)->utf8) {
- latin1_to_utf8(usern, sizeof(usern), user()->username());
- latin1_to_utf8(pass, sizeof(pass), basic_auth->passwd);
- xstrncpy(usern, rfc1738_escape(usern), sizeof(usern));
- xstrncpy(pass, rfc1738_escape(pass), sizeof(pass));
- } else {
- xstrncpy(usern, rfc1738_escape(user()->username()), sizeof(usern));
- xstrncpy(pass, rfc1738_escape(basic_auth->passwd), sizeof(pass));
- }
+
+ xstrncpy(usern, rfc1738_escape(user()->username()), sizeof(usern));
+ xstrncpy(pass, rfc1738_escape(basic_auth->passwd), sizeof(pass));
+
int sz = 0;
if (const char *keyExtras = helperRequestKeyExtras(request, al))
sz = snprintf(buf, sizeof(buf), "%s %s %s\n", usern, pass, keyExtras);
#include "auth/digest/UserRequest.h"
#include "auth/Gadgets.h"
#include "auth/State.h"
+#include "auth/toUtf.h"
#include "base/LookupTable.h"
#include "cache_cf.h"
#include "event.h"
#include "mgr/Registration.h"
#include "rfc2617.h"
#include "sbuf/SBuf.h"
+#include "sbuf/StringConvert.h"
#include "SquidTime.h"
#include "Store.h"
#include "StrList.h"
* Auth_user structure.
*/
Auth::UserRequest::Pointer
-Auth::Digest::Config::decode(char const *proxy_auth, const char *aRequestRealm)
+Auth::Digest::Config::decode(char const *proxy_auth, const HttpRequest *request, const char *aRequestRealm)
{
const char *item;
const char *p;
switch (t) {
case DIGEST_USERNAME:
safe_free(username);
- if (value.size() != 0)
+ if (value.size() != 0) {
+ const auto v = value.termedBuf();
+ if (utf8 && !isValidUtf8String(v, v + value.size())) {
+ auto str = isCP1251EncodingAllowed(request) ? Cp1251ToUtf8(v) : Latin1ToUtf8(v);
+ value = SBufToString(str);
+ }
username = xstrndup(value.rawBuf(), value.size() + 1);
+ }
debugs(29, 9, "Found Username '" << username << "'");
break;
Config();
virtual bool active() const;
virtual bool configured() const;
- virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm);
+ virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const HttpRequest *request, const char *requestRealm);
virtual void done();
virtual void rotateHelpers();
virtual bool dump(StoreEntry *, const char *, Auth::SchemeConfig *) const;
#include "auth/digest/User.h"
#include "auth/digest/UserRequest.h"
#include "auth/State.h"
-#include "charset.h"
#include "format/Format.h"
#include "helper.h"
#include "helper/Reply.h"
}
const char *keyExtras = helperRequestKeyExtras(request, al);
- if (static_cast<Auth::Digest::Config*>(Auth::SchemeConfig::Find("digest"))->utf8) {
- char userstr[1024];
- latin1_to_utf8(userstr, sizeof(userstr), user()->username());
- if (keyExtras)
- snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", userstr, realm, keyExtras);
- else
- snprintf(buf, 8192, "\"%s\":\"%s\"\n", userstr, realm);
- } else {
- if (keyExtras)
- snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras);
- else
- snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm);
- }
+ if (keyExtras)
+ snprintf(buf, 8192, "\"%s\":\"%s\" %s\n", user()->username(), realm, keyExtras);
+ else
+ snprintf(buf, 8192, "\"%s\":\"%s\"\n", user()->username(), realm);
helperSubmit(digestauthenticators, buf, Auth::Digest::UserRequest::HandleReply,
new Auth::StateData(this, handler, data));
* Auth_user structure.
*/
Auth::UserRequest::Pointer
-Auth::Negotiate::Config::decode(char const *proxy_auth, const char *aRequestRealm)
+Auth::Negotiate::Config::decode(char const *proxy_auth, const HttpRequest *, const char *aRequestRealm)
{
Auth::Negotiate::User *newUser = new Auth::Negotiate::User(Auth::SchemeConfig::Find("negotiate"), aRequestRealm);
Auth::UserRequest *auth_user_request = new Auth::Negotiate::UserRequest();
public:
virtual bool active() const;
virtual bool configured() const;
- virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm);
+ virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const HttpRequest *request, const char *requestRealm);
virtual void done();
virtual void rotateHelpers();
virtual void fixHeader(Auth::UserRequest::Pointer, HttpReply *, Http::HdrType, HttpRequest *);
* Auth_user structure.
*/
Auth::UserRequest::Pointer
-Auth::Ntlm::Config::decode(char const *proxy_auth, const char *aRequestRealm)
+Auth::Ntlm::Config::decode(char const *proxy_auth, const HttpRequest *, const char *aRequestRealm)
{
Auth::Ntlm::User *newUser = new Auth::Ntlm::User(Auth::SchemeConfig::Find("ntlm"), aRequestRealm);
Auth::UserRequest::Pointer auth_user_request = new Auth::Ntlm::UserRequest();
public:
virtual bool active() const;
virtual bool configured() const;
- virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const char *requestRealm);
+ virtual Auth::UserRequest::Pointer decode(char const *proxy_auth, const HttpRequest *request, const char *requestRealm);
virtual void done();
virtual void rotateHelpers();
virtual void fixHeader(Auth::UserRequest::Pointer, HttpReply *, Http::HdrType, HttpRequest *);
--- /dev/null
+/*
+ * Copyright (C) 1996-2019 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#include "squid.h"
+#include "sbuf/SBuf.h"
+#include "auth/toUtf.h"
+
+SBuf
+Latin1ToUtf8(const char *in)
+{
+ SBuf result;
+
+ if (!in)
+ return result;
+
+ for (; *in; in++) {
+ const auto ch = static_cast<unsigned char>(*in);
+
+ if (ch < 0x80) {
+ result.append(ch);
+ } else {
+ result.append(static_cast<char>((ch >> 6) | 0xc0));
+ result.append(static_cast<char>((ch & 0x3f) | 0x80));
+ }
+ }
+ return result;
+}
+
+SBuf
+Cp1251ToUtf8(const char *in)
+{
+ static const unsigned char firstByteMark[] = { 0x00, 0x00, 0xC0, 0xE0 };
+ static const unsigned unicodevalues[] = {
+ 0x0402, 0x0403, 0x201A, 0x0453, 0x201E, 0x2026, 0x2020, 0x2021,
+ 0x20AC, 0x2030, 0x0409, 0x2039, 0x040A, 0x040C, 0x040B, 0x040F,
+ 0x0452, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
+ 0xFFFD, 0x2122, 0x0459, 0x203A, 0x045A, 0x045C, 0x045B, 0x045F,
+ 0x00A0, 0x040E, 0x045E, 0x0408, 0x00A4, 0x0490, 0x00A6, 0x00A7,
+ 0x0401, 0x00A9, 0x0404, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x0407,
+ 0x00B0, 0x00B1, 0x0406, 0x0456, 0x0491, 0x00B5, 0x00B6, 0x00B7,
+ 0x0451, 0x2116, 0x0454, 0x00BB, 0x0458, 0x0405, 0x0455, 0x0457
+ };
+ SBuf result;
+
+ if (!in)
+ return result;
+
+ for (; *in; in++) {
+ const auto ch = static_cast<unsigned char>(*in);
+ unsigned u = 0;
+ size_t bytesToWrite = 0;
+ char sequence[4] = {0, 0, 0, 0};
+
+ if (ch < 0x80)
+ u = ch;
+ else if (ch >= 0xC0 && ch <= 0xFF) // 0x0410..0x044F
+ u = 0x0350 + ch;
+ else
+ u = unicodevalues[ch - 0x80];
+
+ if (u < 0x80)
+ bytesToWrite = 1;
+ else if (u < 0x800)
+ bytesToWrite = 2;
+ else
+ bytesToWrite = 3;
+
+ switch (bytesToWrite) {
+ case 3:
+ sequence[2] = static_cast<char>(u & 0x3f) | 0x80;
+ u >>= 6;
+ // fall through
+ case 2:
+ sequence[1] = static_cast<char>(u & 0x3f) | 0x80;
+ u >>= 6;
+ // fall through
+ case 1:
+ sequence[0] = static_cast<char>(u) | firstByteMark[bytesToWrite];
+ // fall through
+ }
+ result.append(sequence, bytesToWrite);
+ }
+ return result;
+}
+
+/**
+ * \returns the length of a UTF-8 code point that starts at the given byte
+ * \retval 0 indicates an invalid code point
+ *
+ * \param b0 the first byte of a UTF-8 code point
+ */
+static inline size_t
+utf8CodePointLength(const char b0)
+{
+ if ((b0 & 0x80) == 0)
+ return 1;
+ if ((b0 & 0xC0) != 0xC0)
+ return 0; // invalid code point
+ if ((b0 & 0xE0) == 0xC0)
+ return 2;
+ if ((b0 & 0xF0) == 0xE0)
+ return 3;
+ if ((b0 & 0xF8) == 0xF0)
+ return 4;
+ return 0; // invalid code point
+}
+
+/**
+ * Utility routine to tell whether a sequence of bytes is valid UTF-8.
+ * This must be called with the length pre-determined by the first byte.
+ * If presented with a length > 4, this returns false. The Unicode
+ * definition of UTF-8 goes up to 4-byte code points.
+ */
+static bool
+isValidUtf8CodePoint(const unsigned char* source, const size_t length)
+{
+ unsigned char a;
+ const unsigned char* srcptr = source + length;
+ switch (length) {
+ default:
+ return false;
+ // Everything else falls through when "true"...
+ case 4:
+ if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
+ case 3:
+ if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
+ case 2:
+ if ((a = (*--srcptr)) > 0xBF) return false;
+
+ switch (*source) {
+ // no fall-through in this inner switch
+ case 0xE0:
+ if (a < 0xA0) return false;
+ break;
+ case 0xED:
+ if (a > 0x9F) return false;
+ break;
+ case 0xF0:
+ if (a < 0x90) return false;
+ break;
+ case 0xF4:
+ if (a > 0x8F) return false;
+ break;
+ default:
+ if (a < 0x80) return false;
+ break;
+ }
+
+ case 1:
+ if (*source >= 0x80 && *source < 0xC2) return false;
+ }
+ if (*source > 0xF4)
+ return false;
+ return true;
+}
+
+/**
+ * \returns whether the given input is a valid (or empty) sequence of UTF-8 code points
+ */
+bool
+isValidUtf8String(const char *source, const char *sourceEnd) {
+ while (source < sourceEnd) {
+ const auto length = utf8CodePointLength(*source);
+ if (source + length > sourceEnd || !isValidUtf8CodePoint(reinterpret_cast<const unsigned char*>(source), length))
+ return false;
+ source += length;
+ }
+ return true; // including zero-length input
+}
+
#ifndef _SQUID_CHARSET_H
#define _SQUID_CHARSET_H
-#ifdef __cplusplus
-extern "C"
-#else
-extern
-#endif
+#include "sbuf/forward.h"
-char *latin1_to_utf8(char *out, size_t size, const char *in);
+/// converts ISO-LATIN-1 to UTF-8
+SBuf Latin1ToUtf8(const char *in);
+
+/// converts CP1251 to UTF-8
+SBuf Cp1251ToUtf8(const char *in);
+
+/// returns whether the given input is a valid (or empty) sequence of UTF-8 code points
+bool isValidUtf8String(const char *source, const char *sourceEnd);
#endif /* _SQUID_CHARSET_H */
For Basic and Digest this parameter is ignored.
"utf8" on|off
- HTTP uses iso-latin-1 as character set, while some
- authentication backends such as LDAP expects UTF-8. If this is
- set to on Squid will translate the HTTP iso-latin-1 charset to
- UTF-8 before sending the username and password to the helper.
-
- For NTLM and Negotiate this parameter is ignored.
+ Useful for sending credentials to authentication backends that
+ expect UTF-8 encoding (e.g., LDAP).
+
+ When this option is enabled, Squid uses HTTP Accept-Language
+ request header to guess the received credentials encoding
+ (ISO-Latin-1, CP1251, or UTF-8) and then converts the first
+ two encodings into UTF-8.
+
+ When this option is disabled and by default, Squid sends
+ credentials in their original (i.e. received) encoding.
+
+ This parameter is only honored for Basic and Digest schemes.
+ For Basic, the entire username:password credentials are
+ checked and, if necessary, re-encoded. For Digest -- just the
+ username component. For NTLM and Negotiate schemes, this
+ parameter is ignored.
IF HAVE_AUTH_MODULE_BASIC
=== Basic authentication parameters ===