1 # urllib3/contrib/ntlmpool.py
2 # Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
4 # This module is part of urllib3 and is released under
5 # the MIT License: http://www.opensource.org/licenses/mit-license.php
8 NTLM authenticating pool, contributed by erikcederstran
10 Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
14 from http.client import HTTPSConnection
16 from httplib import HTTPSConnection
17 from logging import getLogger
20 from urllib3 import HTTPSConnectionPool
23 log = getLogger(__name__)
26 class NTLMConnectionPool(HTTPSConnectionPool):
28 Implements an NTLM authentication version of an urllib3 connection pool
33 def __init__(self, user, pw, authurl, *args, **kwargs):
35 authurl is a random URL on the server that is protected by NTLM.
36 user is the Windows user, probably in the DOMAIN\\username format.
37 pw is the password for the user.
39 super(NTLMConnectionPool, self).__init__(*args, **kwargs)
40 self.authurl = authurl
42 user_parts = user.split('\\', 1)
43 self.domain = user_parts[0].upper()
44 self.user = user_parts[1]
48 # Performs the NTLM handshake that secures the connection. The socket
49 # must be kept open while requests are performed.
50 self.num_connections += 1
51 log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s' %
52 (self.num_connections, self.host, self.authurl))
55 headers['Connection'] = 'Keep-Alive'
56 req_header = 'Authorization'
57 resp_header = 'www-authenticate'
59 conn = HTTPSConnection(host=self.host, port=self.port)
61 # Send negotiation message
62 headers[req_header] = (
63 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser))
64 log.debug('Request headers: %s' % headers)
65 conn.request('GET', self.authurl, None, headers)
66 res = conn.getresponse()
67 reshdr = dict(res.getheaders())
68 log.debug('Response status: %s %s' % (res.status, res.reason))
69 log.debug('Response headers: %s' % reshdr)
70 log.debug('Response data: %s [...]' % res.read(100))
72 # Remove the reference to the socket, so that it can not be closed by
73 # the response object (we want to keep the socket open)
76 # Server should respond with a challenge message
77 auth_header_values = reshdr[resp_header].split(', ')
78 auth_header_value = None
79 for s in auth_header_values:
81 auth_header_value = s[5:]
82 if auth_header_value is None:
83 raise Exception('Unexpected %s response header: %s' %
84 (resp_header, reshdr[resp_header]))
86 # Send authentication message
87 ServerChallenge, NegotiateFlags = \
88 ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value)
89 auth_msg = ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge,
94 headers[req_header] = 'NTLM %s' % auth_msg
95 log.debug('Request headers: %s' % headers)
96 conn.request('GET', self.authurl, None, headers)
97 res = conn.getresponse()
98 log.debug('Response status: %s %s' % (res.status, res.reason))
99 log.debug('Response headers: %s' % dict(res.getheaders()))
100 log.debug('Response data: %s [...]' % res.read()[:100])
101 if res.status != 200:
102 if res.status == 401:
103 raise Exception('Server rejected request: wrong '
104 'username or password')
105 raise Exception('Wrong server response: %s %s' %
106 (res.status, res.reason))
109 log.debug('Connection established')
112 def urlopen(self, method, url, body=None, headers=None, retries=3,
113 redirect=True, assert_same_host=True):
116 headers['Connection'] = 'Keep-Alive'
117 return super(NTLMConnectionPool, self).urlopen(method, url, body,