1
# urllib3/contrib/ntlmpool.py
2
# Copyright 2008-2012 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,