b8cd933034cc48ef733209850380ec4ebdc79e10
[sdc/sdc-distribution-client.git] /
1 # urllib3/contrib/ntlmpool.py
2 # Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
3 #
4 # This module is part of urllib3 and is released under
5 # the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7 """
8 NTLM authenticating pool, contributed by erikcederstran
9
10 Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
11 """
12
13 try:
14     from http.client import HTTPSConnection
15 except ImportError:
16     from httplib import HTTPSConnection
17 from logging import getLogger
18 from ntlm import ntlm
19
20 from urllib3 import HTTPSConnectionPool
21
22
23 log = getLogger(__name__)
24
25
26 class NTLMConnectionPool(HTTPSConnectionPool):
27     """
28     Implements an NTLM authentication version of an urllib3 connection pool
29     """
30
31     scheme = 'https'
32
33     def __init__(self, user, pw, authurl, *args, **kwargs):
34         """
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.
38         """
39         super(NTLMConnectionPool, self).__init__(*args, **kwargs)
40         self.authurl = authurl
41         self.rawuser = user
42         user_parts = user.split('\\', 1)
43         self.domain = user_parts[0].upper()
44         self.user = user_parts[1]
45         self.pw = pw
46
47     def _new_conn(self):
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))
53
54         headers = {}
55         headers['Connection'] = 'Keep-Alive'
56         req_header = 'Authorization'
57         resp_header = 'www-authenticate'
58
59         conn = HTTPSConnection(host=self.host, port=self.port)
60
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))
71
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)
74         res.fp = None
75
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:
80             if s[:5] == 'NTLM ':
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]))
85
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,
90                                                          self.user,
91                                                          self.domain,
92                                                          self.pw,
93                                                          NegotiateFlags)
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))
107
108         res.fp = None
109         log.debug('Connection established')
110         return conn
111
112     def urlopen(self, method, url, body=None, headers=None, retries=3,
113                 redirect=True, assert_same_host=True):
114         if headers is None:
115             headers = {}
116         headers['Connection'] = 'Keep-Alive'
117         return super(NTLMConnectionPool, self).urlopen(method, url, body,
118                                                        headers, retries,
119                                                        redirect,
120                                                        assert_same_host)