This patch fixes Authentication for turkish locales in case that the server
uses promised case-insensitivity for the authentication scheme (e.g. for
'Basic' or 'Digest'). RFC 2617 and it's successor RFC 7235 both say:


RFC 7235 2.1.  Challenge and Response

   ...
   It uses a case-insensitive token as a means to identify the authentication
scheme,
   ...

So I amended testenv/server/http/http_server.py to accept any case for 'Basic'
and 'Digest' as well as sending 'BasIc' and 'DIgest'.

Running the test suite with this change with LC_ALL set to 'tr_TR.utf8'
(turkish locale), three tests fail.

The fix is to use a pure ASCII version of strcasecmp()/strncasecmp() and not
the locale dependant versions from the standard C library.

Gnulib has ASCII versions of these functions (c_strcasecmp/c_strncasecmp).
Using these at two places of src/http.c fixes the problem.

There are many other places in Wget where we should use these functions.
(See my email from 24.9.2014 13:08:21).
I am not sure (in fact I doubt) that we can blindly replace strcasecmp and
strncasecmp. So I try to make test cases to prove replacing is correct.

What do you think ?

Tim
From 47f1d2f8834f8944dcf9e6e83a9b25653776e926 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tim Rühsen?= <[email protected]>
Date: Mon, 17 Nov 2014 17:16:54 +0100
Subject: [PATCH] Fix authentication problem with turkish locale

---
 ChangeLog                          |  4 ++++
 bootstrap.conf                     |  1 +
 src/ChangeLog                      |  5 ++++
 src/http.c                         |  5 ++--
 testenv/ChangeLog                  |  5 ++++
 testenv/server/http/http_server.py | 47 ++++++++++++++++++++------------------
 6 files changed, 43 insertions(+), 24 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 767221d..15580c4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2014-11-17  Tim Ruehsen <[email protected]>
+
+	* bootstrap.conf (gnulib_modules): Add module c-strcase
+
 2014-11-16  Darshit Shah  <[email protected]>

 	* po/stamp-po: Remove autogenerated file from checked  out sources
diff --git a/bootstrap.conf b/bootstrap.conf
index 11f5f92..71aa913 100644
--- a/bootstrap.conf
+++ b/bootstrap.conf
@@ -31,6 +31,7 @@ announce-gen
 base32
 bind
 c-ctype
+c-strcase
 clock-time
 close
 connect
diff --git a/src/ChangeLog b/src/ChangeLog
index 0fe61fa..87b1feb 100644
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,5 +1,10 @@
 2014-11-17  Tim Ruehsen  <[email protected]>

+	* http.c: use c_strncasecmp() in BEGINS_WITH macro
+          and in STARTS macro
+
+2014-11-17  Tim Ruehsen  <[email protected]>
+
 	* main.c: code cleanup for redirect_output_signal()

 2014-11-17  Tim Ruehsen  <[email protected]>
diff --git a/src/http.c b/src/http.c
index de96e32..da40e98 100644
--- a/src/http.c
+++ b/src/http.c
@@ -59,6 +59,7 @@ as that of the covered work.  */
 #include "convert.h"
 #include "spider.h"
 #include "warc.h"
+#include "c-strcase.h"

 #ifdef TESTING
 #include "test.h"
@@ -1631,7 +1632,7 @@ read_response_body (struct http_stat *hs, int sock, FILE *fp, wgint contlen,
 }

 #define BEGINS_WITH(line, string_constant)                               \
-  (!strncasecmp (line, string_constant, sizeof (string_constant) - 1)    \
+  (!c_strncasecmp (line, string_constant, sizeof (string_constant) - 1)    \
    && (c_isspace (line[sizeof (string_constant) - 1])                      \
        || !line[sizeof (string_constant) - 1]))

@@ -3958,7 +3959,7 @@ digest_authentication_encode (const char *au, const char *user,
 #define STARTS(literal, b, e)                           \
   ((e > b) \
    && ((size_t) ((e) - (b))) >= STRSIZE (literal)   \
-   && 0 == strncasecmp (b, literal, STRSIZE (literal))  \
+   && 0 == c_strncasecmp (b, literal, STRSIZE (literal))  \
    && ((size_t) ((e) - (b)) == STRSIZE (literal)          \
        || c_isspace (b[STRSIZE (literal)])))

diff --git a/testenv/ChangeLog b/testenv/ChangeLog
index 5ac01ac..f230ac6 100644
--- a/testenv/ChangeLog
+++ b/testenv/ChangeLog
@@ -1,3 +1,8 @@
+2014-11-17  Tim Ruehsen <[email protected]>
+
+	* server/http/http_server.py: allow case-insensitive auth-type,
+          send BasIc and DIgest to provoke Wget failures with turkish locales
+
 2014-11-15  Darshit Shah  <[email protected]>

 	* certs/README: Remove trailing whitespaces
diff --git a/testenv/server/http/http_server.py b/testenv/server/http/http_server.py
index 1b67bfc..e28759a 100644
--- a/testenv/server/http/http_server.py
+++ b/testenv/server/http/http_server.py
@@ -205,34 +205,37 @@ class _Handler (BaseHTTPRequestHandler):
         return string.decode ('utf-8')

     def send_challenge (self, auth_type):
-        if auth_type == "Both":
-            self.send_challenge ("Digest")
-            self.send_challenge ("Basic")
+        auth_type = auth_type.lower()
+        if auth_type == "both":
+            self.send_challenge ("digest")
+            self.send_challenge ("basic")
             return
-        if auth_type == "Basic":
-            challenge_str = 'Basic realm="Wget-Test"'
-        elif auth_type == "Digest" or auth_type == "Both_inline":
+        if auth_type == "basic":
+            challenge_str = 'BasIc realm="Wget-Test"'
+        elif auth_type == "digest" or auth_type == "both_inline":
             self.nonce = md5 (str (random ()).encode ('utf-8')).hexdigest()
             self.opaque = md5 (str (random ()).encode ('utf-8')).hexdigest()
-            challenge_str = 'Digest realm="Test", nonce="%s", opaque="%s"' % (
+            # 'DIgest' to provoke a Wget failure with turkish locales
+            challenge_str = 'DIgest realm="Test", nonce="%s", opaque="%s"' % (
                             self.nonce,
                             self.opaque)
             challenge_str += ', qop="auth"'
-            if auth_type == "Both_inline":
-                challenge_str = 'Basic realm="Wget-Test", ' + challenge_str
+            if auth_type == "both_inline":
+                # 'BasIc' to provoke a Wget failure with turkish locales
+                challenge_str = 'BasIc realm="Wget-Test", ' + challenge_str
         self.send_header ("WWW-Authenticate", challenge_str)

-    def authorize_Basic (self, auth_header, auth_rule):
-        if auth_header is None or auth_header.split(' ')[0] != 'Basic':
+    def authorize_basic (self, auth_header, auth_rule):
+        if auth_header is None or auth_header.split(' ')[0].lower() != 'basic':
             return False
         else:
             self.user = auth_rule.auth_user
             self.passw = auth_rule.auth_pass
-            auth_str = "Basic " + self.base64 (self.user + ":" + self.passw)
-            return True if auth_str == auth_header else False
+            auth_str = "basic " + self.base64 (self.user + ":" + self.passw)
+            return True if auth_str.lower() == auth_header.lower() else False

     def parse_auth_header (self, auth_header):
-        n = len("Digest ")
+        n = len("digest ")
         auth_header = auth_header[n:].strip()
         items = auth_header.split(", ")
         keyvals = [i.split("=", 1) for i in items]
@@ -264,8 +267,8 @@ class _Handler (BaseHTTPRequestHandler):

         return True if resp == params['response'] else False

-    def authorize_Digest (self, auth_header, auth_rule):
-        if auth_header is None or auth_header.split(' ')[0] != 'Digest':
+    def authorize_digest (self, auth_header, auth_rule):
+        if auth_header is None or auth_header.split(' ')[0].lower() != 'digest':
             return False
         else:
             self.user = auth_rule.auth_user
@@ -284,10 +287,10 @@ class _Handler (BaseHTTPRequestHandler):
                 pass_auth = False
             return pass_auth

-    def authorize_Both (self, auth_header, auth_rule):
+    def authorize_both (self, auth_header, auth_rule):
         return False

-    def authorize_Both_inline (self, auth_header, auth_rule):
+    def authorize_both_inline (self, auth_header, auth_rule):
         return False

     def Authentication (self, auth_rule):
@@ -302,16 +305,16 @@ class _Handler (BaseHTTPRequestHandler):
     def handle_auth (self, auth_rule):
         is_auth = True
         auth_header = self.headers.get ("Authorization")
-        required_auth = auth_rule.auth_type
-        if required_auth == "Both" or required_auth == "Both_inline":
-            auth_type = auth_header.split(' ')[0] if auth_header else required_auth
+        required_auth = auth_rule.auth_type.lower()
+        if required_auth == "both" or required_auth == "both_inline":
+            auth_type = auth_header.split(' ')[0].lower() if auth_header else required_auth
         else:
             auth_type = required_auth
         try:
             assert hasattr (self, "authorize_" + auth_type)
             is_auth = getattr (self, "authorize_" + auth_type) (auth_header, auth_rule)
         except AssertionError:
-            raise ServerError ("Authentication Mechanism " + auth_rule + " not supported")
+            raise ServerError ("Authentication Mechanism " + auth_type + " not supported")
         except AttributeError as ae:
             raise ServerError (ae.__str__())
         if is_auth is False:
--
2.1.3

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to