1"""
2NTLM authenticating pool, contributed by erikcederstran
3
4Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
5"""
6from __future__ import absolute_import
7
8import warnings
9from logging import getLogger
10
11from ntlm import ntlm
12
13from .. import HTTPSConnectionPool
14from ..packages.six.moves.http_client import HTTPSConnection
15
16warnings.warn(
17    "The 'urllib3.contrib.ntlmpool' module is deprecated and will be removed "
18    "in urllib3 v2.0 release, urllib3 is not able to support it properly due "
19    "to reasons listed in issue: https://github.com/urllib3/urllib3/issues/2282. "
20    "If you are a user of this module please comment in the mentioned issue.",
21    DeprecationWarning,
22)
23
24log = getLogger(__name__)
25
26
27class NTLMConnectionPool(HTTPSConnectionPool):
28    """
29    Implements an NTLM authentication version of an urllib3 connection pool
30    """
31
32    scheme = "https"
33
34    def __init__(self, user, pw, authurl, *args, **kwargs):
35        """
36        authurl is a random URL on the server that is protected by NTLM.
37        user is the Windows user, probably in the DOMAIN\\username format.
38        pw is the password for the user.
39        """
40        super(NTLMConnectionPool, self).__init__(*args, **kwargs)
41        self.authurl = authurl
42        self.rawuser = user
43        user_parts = user.split("\\", 1)
44        self.domain = user_parts[0].upper()
45        self.user = user_parts[1]
46        self.pw = pw
47
48    def _new_conn(self):
49        # Performs the NTLM handshake that secures the connection. The socket
50        # must be kept open while requests are performed.
51        self.num_connections += 1
52        log.debug(
53            "Starting NTLM HTTPS connection no. %d: https://%s%s",
54            self.num_connections,
55            self.host,
56            self.authurl,
57        )
58
59        headers = {"Connection": "Keep-Alive"}
60        req_header = "Authorization"
61        resp_header = "www-authenticate"
62
63        conn = HTTPSConnection(host=self.host, port=self.port)
64
65        # Send negotiation message
66        headers[req_header] = "NTLM %s" % ntlm.create_NTLM_NEGOTIATE_MESSAGE(
67            self.rawuser
68        )
69        log.debug("Request headers: %s", headers)
70        conn.request("GET", self.authurl, None, headers)
71        res = conn.getresponse()
72        reshdr = dict(res.getheaders())
73        log.debug("Response status: %s %s", res.status, res.reason)
74        log.debug("Response headers: %s", reshdr)
75        log.debug("Response data: %s [...]", res.read(100))
76
77        # Remove the reference to the socket, so that it can not be closed by
78        # the response object (we want to keep the socket open)
79        res.fp = None
80
81        # Server should respond with a challenge message
82        auth_header_values = reshdr[resp_header].split(", ")
83        auth_header_value = None
84        for s in auth_header_values:
85            if s[:5] == "NTLM ":
86                auth_header_value = s[5:]
87        if auth_header_value is None:
88            raise Exception(
89                "Unexpected %s response header: %s" % (resp_header, reshdr[resp_header])
90            )
91
92        # Send authentication message
93        ServerChallenge, NegotiateFlags = ntlm.parse_NTLM_CHALLENGE_MESSAGE(
94            auth_header_value
95        )
96        auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(
97            ServerChallenge, self.user, self.domain, self.pw, NegotiateFlags
98        )
99        headers[req_header] = "NTLM %s" % auth_msg
100        log.debug("Request headers: %s", headers)
101        conn.request("GET", self.authurl, None, headers)
102        res = conn.getresponse()
103        log.debug("Response status: %s %s", res.status, res.reason)
104        log.debug("Response headers: %s", dict(res.getheaders()))
105        log.debug("Response data: %s [...]", res.read()[:100])
106        if res.status != 200:
107            if res.status == 401:
108                raise Exception("Server rejected request: wrong username or password")
109            raise Exception("Wrong server response: %s %s" % (res.status, res.reason))
110
111        res.fp = None
112        log.debug("Connection established")
113        return conn
114
115    def urlopen(
116        self,
117        method,
118        url,
119        body=None,
120        headers=None,
121        retries=3,
122        redirect=True,
123        assert_same_host=True,
124    ):
125        if headers is None:
126            headers = {}
127        headers["Connection"] = "Keep-Alive"
128        return super(NTLMConnectionPool, self).urlopen(
129            method, url, body, headers, retries, redirect, assert_same_host
130        )
131