1import logging
2import urllib2
3
4import kerberos as krb
5
6
7import re
8
9RGX = re.compile(r"(?:.*,)*\s*Negotiate\s*([^,]*),?", re.I)
10
11
12class GssapiAuthError(Exception):
13    """raised on error during authentication process"""
14
15
16def get_negociate_value(headers):
17    for authreq in headers.getheaders("www-authenticate"):
18        match = RGX.search(authreq)
19        if match:
20            return match.group(1)
21
22
23class HTTPGssapiAuthHandler(urllib2.BaseHandler):
24    """Negotiate HTTP authentication using context from GSSAPI"""
25
26    handler_order = 400  # before Digest Auth
27
28    def __init__(self):
29        self._reset()
30
31    def _reset(self):
32        self._retried = 0
33        self._context = None
34
35    def clean_context(self):
36        if self._context is not None:
37            krb.authGSSClientClean(self._context)
38
39    def http_error_401(self, req, fp, code, msg, headers):
40        try:
41            if self._retried > 5:
42                raise urllib2.HTTPError(
43                    req.get_full_url(), 401, "negotiate auth failed", headers, None
44                )
45            self._retried += 1
46            logging.debug("gssapi handler, try %s" % self._retried)
47            negotiate = get_negociate_value(headers)
48            if negotiate is None:
49                logging.debug("no negociate found in a www-authenticate header")
50                return None
51            logging.debug("HTTPGssapiAuthHandler: negotiate 1 is %r" % negotiate)
52            result, self._context = krb.authGSSClientInit("HTTP@%s" % req.get_host())
53            if result < 1:
54                raise GssapiAuthError("HTTPGssapiAuthHandler: init failed with %d" % result)
55            result = krb.authGSSClientStep(self._context, negotiate)
56            if result < 0:
57                raise GssapiAuthError("HTTPGssapiAuthHandler: step 1 failed with %d" % result)
58            client_response = krb.authGSSClientResponse(self._context)
59            logging.debug("HTTPGssapiAuthHandler: client response is %s..." % client_response[:10])
60            req.add_unredirected_header("Authorization", "Negotiate %s" % client_response)
61            server_response = self.parent.open(req)
62            negotiate = get_negociate_value(server_response.info())
63            if negotiate is None:
64                logging.warning("HTTPGssapiAuthHandler: failed to authenticate server")
65            else:
66                logging.debug("HTTPGssapiAuthHandler negotiate 2: %s" % negotiate)
67                result = krb.authGSSClientStep(self._context, negotiate)
68                if result < 1:
69                    raise GssapiAuthError("HTTPGssapiAuthHandler: step 2 failed with %d" % result)
70            return server_response
71        except GssapiAuthError as exc:
72            logging.error(repr(exc))
73        finally:
74            self.clean_context()
75            self._reset()
76
77
78if __name__ == "__main__":
79    import sys
80
81    # debug
82    import httplib
83
84    httplib.HTTPConnection.debuglevel = 1
85    httplib.HTTPSConnection.debuglevel = 1
86
87    logging.basicConfig(level=logging.DEBUG)
88    # handle cookies
89    import cookielib
90
91    cj = cookielib.CookieJar()
92    ch = urllib2.HTTPCookieProcessor(cj)
93    # test with url sys.argv[1]
94    h = HTTPGssapiAuthHandler()
95    response = urllib2.build_opener(h, ch).open(sys.argv[1])
96    print("\nresponse: %s\n--------------\n" % response.code, response.info())
97