Skip to content
This repository was archived by the owner on Sep 15, 2020. It is now read-only.

Commit d9142e2

Browse files
committed
Added initial NTLM authentication support
1 parent cce42a1 commit d9142e2

15 files changed

+1528
-0
lines changed

ntlm/HTTPNtlmAuthHandler.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# This library is free software: you can redistribute it and/or
2+
# modify it under the terms of the GNU Lesser General Public
3+
# License as published by the Free Software Foundation, either
4+
# version 3 of the License, or (at your option) any later version.
5+
6+
# This library is distributed in the hope that it will be useful,
7+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
8+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9+
# Lesser General Public License for more details.
10+
#
11+
# You should have received a copy of the GNU Lesser General Public
12+
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
13+
14+
import urllib2
15+
import httplib, socket
16+
from urllib import addinfourl
17+
import ntlm
18+
19+
class AbstractNtlmAuthHandler:
20+
def __init__(self, password_mgr=None, debuglevel=0):
21+
if password_mgr is None:
22+
password_mgr = HTTPPasswordMgr()
23+
self.passwd = password_mgr
24+
self.add_password = self.passwd.add_password
25+
self._debuglevel = debuglevel
26+
27+
def set_http_debuglevel(self, level):
28+
self._debuglevel = level
29+
30+
def http_error_authentication_required(self, auth_header_field, req, fp, headers):
31+
auth_header_value = headers.get(auth_header_field, None)
32+
if auth_header_field:
33+
if auth_header_value is not None and 'ntlm' in auth_header_value.lower():
34+
fp.close()
35+
return self.retry_using_http_NTLM_auth(req, auth_header_field, None, headers)
36+
37+
def retry_using_http_NTLM_auth(self, req, auth_header_field, realm, headers):
38+
user, pw = self.passwd.find_user_password(realm, req.get_full_url())
39+
if pw is not None:
40+
# ntlm secures a socket, so we must use the same socket for the complete handshake
41+
headers = dict(req.headers)
42+
headers.update(req.unredirected_hdrs)
43+
auth = 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(user)
44+
if req.headers.get(self.auth_header, None) == auth:
45+
return None
46+
headers[self.auth_header] = auth
47+
48+
host = req.get_host()
49+
if not host:
50+
raise urllib2.URLError('no host given')
51+
h = None
52+
if req.get_full_url().startswith('https://'):
53+
h = httplib.HTTPSConnection(host) # will parse host:port
54+
else:
55+
h = httplib.HTTPConnection(host) # will parse host:port
56+
h.set_debuglevel(self._debuglevel)
57+
# we must keep the connection because NTLM authenticates the connection, not single requests
58+
headers["Connection"] = "Keep-Alive"
59+
headers = dict((name.title(), val) for name, val in headers.items())
60+
h.request(req.get_method(), req.get_selector(), req.data, headers)
61+
r = h.getresponse()
62+
r.begin()
63+
r._safe_read(int(r.getheader('content-length')))
64+
if r.getheader('set-cookie'):
65+
# this is important for some web applications that store authentication-related info in cookies (it took a long time to figure out)
66+
headers['Cookie'] = r.getheader('set-cookie')
67+
r.fp = None # remove the reference to the socket, so that it can not be closed by the response object (we want to keep the socket open)
68+
auth_header_value = r.getheader(auth_header_field, None)
69+
(ServerChallenge, NegotiateFlags) = ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value[5:])
70+
user_parts = user.split('\\', 1)
71+
DomainName = user_parts[0].upper()
72+
UserName = user_parts[1]
73+
auth = 'NTLM %s' % ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, UserName, DomainName, pw, NegotiateFlags)
74+
headers[self.auth_header] = auth
75+
headers["Connection"] = "Close"
76+
headers = dict((name.title(), val) for name, val in headers.items())
77+
try:
78+
h.request(req.get_method(), req.get_selector(), req.data, headers)
79+
# none of the configured handlers are triggered, for example redirect-responses are not handled!
80+
response = h.getresponse()
81+
def notimplemented():
82+
raise NotImplementedError
83+
response.readline = notimplemented
84+
infourl = addinfourl(response, response.msg, req.get_full_url())
85+
infourl.code = response.status
86+
infourl.msg = response.reason
87+
return infourl
88+
except socket.error, err:
89+
raise urllib2.URLError(err)
90+
else:
91+
return None
92+
93+
94+
class HTTPNtlmAuthHandler(AbstractNtlmAuthHandler, urllib2.BaseHandler):
95+
96+
auth_header = 'Authorization'
97+
98+
def http_error_401(self, req, fp, code, msg, headers):
99+
return self.http_error_authentication_required('www-authenticate', req, fp, headers)
100+
101+
102+
class ProxyNtlmAuthHandler(AbstractNtlmAuthHandler, urllib2.BaseHandler):
103+
"""
104+
CAUTION: this class has NOT been tested at all!!!
105+
use at your own risk
106+
"""
107+
auth_header = 'Proxy-authorization'
108+
109+
def http_error_407(self, req, fp, code, msg, headers):
110+
return self.http_error_authentication_required('proxy-authenticate', req, fp, headers)
111+
112+
113+
if __name__ == "__main__":
114+
url = "http://ntlmprotectedserver/securedfile.html"
115+
user = u'DOMAIN\\User'
116+
password = 'Password'
117+
118+
passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
119+
passman.add_password(None, url, user , password)
120+
auth_basic = urllib2.HTTPBasicAuthHandler(passman)
121+
auth_digest = urllib2.HTTPDigestAuthHandler(passman)
122+
auth_NTLM = HTTPNtlmAuthHandler(passman)
123+
124+
# disable proxies (just for testing)
125+
proxy_handler = urllib2.ProxyHandler({})
126+
127+
opener = urllib2.build_opener(proxy_handler, auth_NTLM) #, auth_digest, auth_basic)
128+
129+
urllib2.install_opener(opener)
130+
131+
response = urllib2.urlopen(url)
132+
print(response.read())
133+

ntlm/HTTPNtlmAuthHandler.pyc

5.8 KB
Binary file not shown.

ntlm/U32.py

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
2+
# Copyright 2001 Dmitry A. Rozmanov <[email protected]>
3+
#
4+
# This library is free software: you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License as published by the Free Software Foundation, either
7+
# version 3 of the License, or (at your option) any later version.
8+
9+
# This library is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
# Lesser General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU Lesser General Public
15+
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
16+
17+
18+
C = 0x1000000000L
19+
20+
def norm(n):
21+
return n & 0xFFFFFFFFL
22+
23+
24+
class U32:
25+
v = 0L
26+
27+
def __init__(self, value = 0):
28+
self.v = C + norm(abs(long(value)))
29+
30+
def set(self, value = 0):
31+
self.v = C + norm(abs(long(value)))
32+
33+
def __repr__(self):
34+
return hex(norm(self.v))
35+
36+
def __long__(self): return long(norm(self.v))
37+
def __int__(self): return int(norm(self.v))
38+
def __chr__(self): return chr(norm(self.v))
39+
40+
def __add__(self, b):
41+
r = U32()
42+
r.v = C + norm(self.v + b.v)
43+
return r
44+
45+
def __sub__(self, b):
46+
r = U32()
47+
if self.v < b.v:
48+
r.v = C + norm(0x100000000L - (b.v - self.v))
49+
else: r.v = C + norm(self.v - b.v)
50+
return r
51+
52+
def __mul__(self, b):
53+
r = U32()
54+
r.v = C + norm(self.v * b.v)
55+
return r
56+
57+
def __div__(self, b):
58+
r = U32()
59+
r.v = C + (norm(self.v) / norm(b.v))
60+
return r
61+
62+
def __mod__(self, b):
63+
r = U32()
64+
r.v = C + (norm(self.v) % norm(b.v))
65+
return r
66+
67+
def __neg__(self): return U32(self.v)
68+
def __pos__(self): return U32(self.v)
69+
def __abs__(self): return U32(self.v)
70+
71+
def __invert__(self):
72+
r = U32()
73+
r.v = C + norm(~self.v)
74+
return r
75+
76+
def __lshift__(self, b):
77+
r = U32()
78+
r.v = C + norm(self.v << b)
79+
return r
80+
81+
def __rshift__(self, b):
82+
r = U32()
83+
r.v = C + (norm(self.v) >> b)
84+
return r
85+
86+
def __and__(self, b):
87+
r = U32()
88+
r.v = C + norm(self.v & b.v)
89+
return r
90+
91+
def __or__(self, b):
92+
r = U32()
93+
r.v = C + norm(self.v | b.v)
94+
return r
95+
96+
def __xor__(self, b):
97+
r = U32()
98+
r.v = C + norm(self.v ^ b.v)
99+
return r
100+
101+
def __not__(self):
102+
return U32(not norm(self.v))
103+
104+
def truth(self):
105+
return norm(self.v)
106+
107+
def __cmp__(self, b):
108+
if norm(self.v) > norm(b.v): return 1
109+
elif norm(self.v) < norm(b.v): return -1
110+
else: return 0
111+
112+
def __nonzero__(self):
113+
return norm(self.v)

ntlm/U32.pyc

5.77 KB
Binary file not shown.

ntlm/__init__.py

Whitespace-only changes.

ntlm/__init__.pyc

156 Bytes
Binary file not shown.

ntlm/des.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/
2+
# Copyright 2001 Dmitry A. Rozmanov <[email protected]>
3+
#
4+
# This library is free software: you can redistribute it and/or
5+
# modify it under the terms of the GNU Lesser General Public
6+
# License as published by the Free Software Foundation, either
7+
# version 3 of the License, or (at your option) any later version.
8+
9+
# This library is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12+
# Lesser General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU Lesser General Public
15+
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>.
16+
17+
import des_c
18+
19+
#---------------------------------------------------------------------
20+
class DES:
21+
22+
des_c_obj = None
23+
24+
#-----------------------------------------------------------------
25+
def __init__(self, key_str):
26+
""
27+
k = str_to_key56(key_str)
28+
k = key56_to_key64(k)
29+
key_str = ''
30+
for i in k:
31+
key_str += chr(i & 0xFF)
32+
self.des_c_obj = des_c.DES(key_str)
33+
34+
#-----------------------------------------------------------------
35+
def encrypt(self, plain_text):
36+
""
37+
return self.des_c_obj.encrypt(plain_text)
38+
39+
#-----------------------------------------------------------------
40+
def decrypt(self, crypted_text):
41+
""
42+
return self.des_c_obj.decrypt(crypted_text)
43+
44+
#---------------------------------------------------------------------
45+
#Some Helpers
46+
#---------------------------------------------------------------------
47+
48+
DESException = 'DESException'
49+
50+
#---------------------------------------------------------------------
51+
def str_to_key56(key_str):
52+
""
53+
if type(key_str) != type(''):
54+
#rise DESException, 'ERROR. Wrong key type.'
55+
pass
56+
if len(key_str) < 7:
57+
key_str = key_str + '\000\000\000\000\000\000\000'[:(7 - len(key_str))]
58+
key_56 = []
59+
for i in key_str[:7]: key_56.append(ord(i))
60+
61+
return key_56
62+
63+
#---------------------------------------------------------------------
64+
def key56_to_key64(key_56):
65+
""
66+
key = []
67+
for i in range(8): key.append(0)
68+
69+
key[0] = key_56[0];
70+
key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1);
71+
key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2);
72+
key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3);
73+
key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4);
74+
key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5);
75+
key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6);
76+
key[7] = (key_56[6] << 1) & 0xFF;
77+
78+
key = set_key_odd_parity(key)
79+
80+
return key
81+
82+
#---------------------------------------------------------------------
83+
def set_key_odd_parity(key):
84+
""
85+
for i in range(len(key)):
86+
for k in range(7):
87+
bit = 0
88+
t = key[i] >> k
89+
bit = (t ^ bit) & 0x1
90+
key[i] = (key[i] & 0xFE) | bit
91+
92+
return key

ntlm/des.pyc

2.44 KB
Binary file not shown.

0 commit comments

Comments
 (0)