This repository has been archived by the owner on Sep 15, 2020. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added initial NTLM authentication support
- Loading branch information
1 parent
cce42a1
commit d9142e2
Showing
15 changed files
with
1,528 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
# This library is free software: you can redistribute it and/or | ||
# modify it under the terms of the GNU Lesser General Public | ||
# License as published by the Free Software Foundation, either | ||
# version 3 of the License, or (at your option) any later version. | ||
|
||
# This library is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
# Lesser General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU Lesser General Public | ||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>. | ||
|
||
import urllib2 | ||
import httplib, socket | ||
from urllib import addinfourl | ||
import ntlm | ||
|
||
class AbstractNtlmAuthHandler: | ||
def __init__(self, password_mgr=None, debuglevel=0): | ||
if password_mgr is None: | ||
password_mgr = HTTPPasswordMgr() | ||
self.passwd = password_mgr | ||
self.add_password = self.passwd.add_password | ||
self._debuglevel = debuglevel | ||
|
||
def set_http_debuglevel(self, level): | ||
self._debuglevel = level | ||
|
||
def http_error_authentication_required(self, auth_header_field, req, fp, headers): | ||
auth_header_value = headers.get(auth_header_field, None) | ||
if auth_header_field: | ||
if auth_header_value is not None and 'ntlm' in auth_header_value.lower(): | ||
fp.close() | ||
return self.retry_using_http_NTLM_auth(req, auth_header_field, None, headers) | ||
|
||
def retry_using_http_NTLM_auth(self, req, auth_header_field, realm, headers): | ||
user, pw = self.passwd.find_user_password(realm, req.get_full_url()) | ||
if pw is not None: | ||
# ntlm secures a socket, so we must use the same socket for the complete handshake | ||
headers = dict(req.headers) | ||
headers.update(req.unredirected_hdrs) | ||
auth = 'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(user) | ||
if req.headers.get(self.auth_header, None) == auth: | ||
return None | ||
headers[self.auth_header] = auth | ||
|
||
host = req.get_host() | ||
if not host: | ||
raise urllib2.URLError('no host given') | ||
h = None | ||
if req.get_full_url().startswith('https://'): | ||
h = httplib.HTTPSConnection(host) # will parse host:port | ||
else: | ||
h = httplib.HTTPConnection(host) # will parse host:port | ||
h.set_debuglevel(self._debuglevel) | ||
# we must keep the connection because NTLM authenticates the connection, not single requests | ||
headers["Connection"] = "Keep-Alive" | ||
headers = dict((name.title(), val) for name, val in headers.items()) | ||
h.request(req.get_method(), req.get_selector(), req.data, headers) | ||
r = h.getresponse() | ||
r.begin() | ||
r._safe_read(int(r.getheader('content-length'))) | ||
if r.getheader('set-cookie'): | ||
# this is important for some web applications that store authentication-related info in cookies (it took a long time to figure out) | ||
headers['Cookie'] = r.getheader('set-cookie') | ||
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) | ||
auth_header_value = r.getheader(auth_header_field, None) | ||
(ServerChallenge, NegotiateFlags) = ntlm.parse_NTLM_CHALLENGE_MESSAGE(auth_header_value[5:]) | ||
user_parts = user.split('\\', 1) | ||
DomainName = user_parts[0].upper() | ||
UserName = user_parts[1] | ||
auth = 'NTLM %s' % ntlm.create_NTLM_AUTHENTICATE_MESSAGE(ServerChallenge, UserName, DomainName, pw, NegotiateFlags) | ||
headers[self.auth_header] = auth | ||
headers["Connection"] = "Close" | ||
headers = dict((name.title(), val) for name, val in headers.items()) | ||
try: | ||
h.request(req.get_method(), req.get_selector(), req.data, headers) | ||
# none of the configured handlers are triggered, for example redirect-responses are not handled! | ||
response = h.getresponse() | ||
def notimplemented(): | ||
raise NotImplementedError | ||
response.readline = notimplemented | ||
infourl = addinfourl(response, response.msg, req.get_full_url()) | ||
infourl.code = response.status | ||
infourl.msg = response.reason | ||
return infourl | ||
except socket.error, err: | ||
raise urllib2.URLError(err) | ||
else: | ||
return None | ||
|
||
|
||
class HTTPNtlmAuthHandler(AbstractNtlmAuthHandler, urllib2.BaseHandler): | ||
|
||
auth_header = 'Authorization' | ||
|
||
def http_error_401(self, req, fp, code, msg, headers): | ||
return self.http_error_authentication_required('www-authenticate', req, fp, headers) | ||
|
||
|
||
class ProxyNtlmAuthHandler(AbstractNtlmAuthHandler, urllib2.BaseHandler): | ||
""" | ||
CAUTION: this class has NOT been tested at all!!! | ||
use at your own risk | ||
""" | ||
auth_header = 'Proxy-authorization' | ||
|
||
def http_error_407(self, req, fp, code, msg, headers): | ||
return self.http_error_authentication_required('proxy-authenticate', req, fp, headers) | ||
|
||
|
||
if __name__ == "__main__": | ||
url = "http://ntlmprotectedserver/securedfile.html" | ||
user = u'DOMAIN\\User' | ||
password = 'Password' | ||
|
||
passman = urllib2.HTTPPasswordMgrWithDefaultRealm() | ||
passman.add_password(None, url, user , password) | ||
auth_basic = urllib2.HTTPBasicAuthHandler(passman) | ||
auth_digest = urllib2.HTTPDigestAuthHandler(passman) | ||
auth_NTLM = HTTPNtlmAuthHandler(passman) | ||
|
||
# disable proxies (just for testing) | ||
proxy_handler = urllib2.ProxyHandler({}) | ||
|
||
opener = urllib2.build_opener(proxy_handler, auth_NTLM) #, auth_digest, auth_basic) | ||
|
||
urllib2.install_opener(opener) | ||
|
||
response = urllib2.urlopen(url) | ||
print(response.read()) | ||
|
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/ | ||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru> | ||
# | ||
# This library is free software: you can redistribute it and/or | ||
# modify it under the terms of the GNU Lesser General Public | ||
# License as published by the Free Software Foundation, either | ||
# version 3 of the License, or (at your option) any later version. | ||
|
||
# This library is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
# Lesser General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU Lesser General Public | ||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>. | ||
|
||
|
||
C = 0x1000000000L | ||
|
||
def norm(n): | ||
return n & 0xFFFFFFFFL | ||
|
||
|
||
class U32: | ||
v = 0L | ||
|
||
def __init__(self, value = 0): | ||
self.v = C + norm(abs(long(value))) | ||
|
||
def set(self, value = 0): | ||
self.v = C + norm(abs(long(value))) | ||
|
||
def __repr__(self): | ||
return hex(norm(self.v)) | ||
|
||
def __long__(self): return long(norm(self.v)) | ||
def __int__(self): return int(norm(self.v)) | ||
def __chr__(self): return chr(norm(self.v)) | ||
|
||
def __add__(self, b): | ||
r = U32() | ||
r.v = C + norm(self.v + b.v) | ||
return r | ||
|
||
def __sub__(self, b): | ||
r = U32() | ||
if self.v < b.v: | ||
r.v = C + norm(0x100000000L - (b.v - self.v)) | ||
else: r.v = C + norm(self.v - b.v) | ||
return r | ||
|
||
def __mul__(self, b): | ||
r = U32() | ||
r.v = C + norm(self.v * b.v) | ||
return r | ||
|
||
def __div__(self, b): | ||
r = U32() | ||
r.v = C + (norm(self.v) / norm(b.v)) | ||
return r | ||
|
||
def __mod__(self, b): | ||
r = U32() | ||
r.v = C + (norm(self.v) % norm(b.v)) | ||
return r | ||
|
||
def __neg__(self): return U32(self.v) | ||
def __pos__(self): return U32(self.v) | ||
def __abs__(self): return U32(self.v) | ||
|
||
def __invert__(self): | ||
r = U32() | ||
r.v = C + norm(~self.v) | ||
return r | ||
|
||
def __lshift__(self, b): | ||
r = U32() | ||
r.v = C + norm(self.v << b) | ||
return r | ||
|
||
def __rshift__(self, b): | ||
r = U32() | ||
r.v = C + (norm(self.v) >> b) | ||
return r | ||
|
||
def __and__(self, b): | ||
r = U32() | ||
r.v = C + norm(self.v & b.v) | ||
return r | ||
|
||
def __or__(self, b): | ||
r = U32() | ||
r.v = C + norm(self.v | b.v) | ||
return r | ||
|
||
def __xor__(self, b): | ||
r = U32() | ||
r.v = C + norm(self.v ^ b.v) | ||
return r | ||
|
||
def __not__(self): | ||
return U32(not norm(self.v)) | ||
|
||
def truth(self): | ||
return norm(self.v) | ||
|
||
def __cmp__(self, b): | ||
if norm(self.v) > norm(b.v): return 1 | ||
elif norm(self.v) < norm(b.v): return -1 | ||
else: return 0 | ||
|
||
def __nonzero__(self): | ||
return norm(self.v) |
Binary file not shown.
Empty file.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# This file is part of 'NTLM Authorization Proxy Server' http://sourceforge.net/projects/ntlmaps/ | ||
# Copyright 2001 Dmitry A. Rozmanov <dima@xenon.spb.ru> | ||
# | ||
# This library is free software: you can redistribute it and/or | ||
# modify it under the terms of the GNU Lesser General Public | ||
# License as published by the Free Software Foundation, either | ||
# version 3 of the License, or (at your option) any later version. | ||
|
||
# This library is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
# Lesser General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU Lesser General Public | ||
# License along with this library. If not, see <http://www.gnu.org/licenses/> or <http://www.gnu.org/licenses/lgpl.txt>. | ||
|
||
import des_c | ||
|
||
#--------------------------------------------------------------------- | ||
class DES: | ||
|
||
des_c_obj = None | ||
|
||
#----------------------------------------------------------------- | ||
def __init__(self, key_str): | ||
"" | ||
k = str_to_key56(key_str) | ||
k = key56_to_key64(k) | ||
key_str = '' | ||
for i in k: | ||
key_str += chr(i & 0xFF) | ||
self.des_c_obj = des_c.DES(key_str) | ||
|
||
#----------------------------------------------------------------- | ||
def encrypt(self, plain_text): | ||
"" | ||
return self.des_c_obj.encrypt(plain_text) | ||
|
||
#----------------------------------------------------------------- | ||
def decrypt(self, crypted_text): | ||
"" | ||
return self.des_c_obj.decrypt(crypted_text) | ||
|
||
#--------------------------------------------------------------------- | ||
#Some Helpers | ||
#--------------------------------------------------------------------- | ||
|
||
DESException = 'DESException' | ||
|
||
#--------------------------------------------------------------------- | ||
def str_to_key56(key_str): | ||
"" | ||
if type(key_str) != type(''): | ||
#rise DESException, 'ERROR. Wrong key type.' | ||
pass | ||
if len(key_str) < 7: | ||
key_str = key_str + '\000\000\000\000\000\000\000'[:(7 - len(key_str))] | ||
key_56 = [] | ||
for i in key_str[:7]: key_56.append(ord(i)) | ||
|
||
return key_56 | ||
|
||
#--------------------------------------------------------------------- | ||
def key56_to_key64(key_56): | ||
"" | ||
key = [] | ||
for i in range(8): key.append(0) | ||
|
||
key[0] = key_56[0]; | ||
key[1] = ((key_56[0] << 7) & 0xFF) | (key_56[1] >> 1); | ||
key[2] = ((key_56[1] << 6) & 0xFF) | (key_56[2] >> 2); | ||
key[3] = ((key_56[2] << 5) & 0xFF) | (key_56[3] >> 3); | ||
key[4] = ((key_56[3] << 4) & 0xFF) | (key_56[4] >> 4); | ||
key[5] = ((key_56[4] << 3) & 0xFF) | (key_56[5] >> 5); | ||
key[6] = ((key_56[5] << 2) & 0xFF) | (key_56[6] >> 6); | ||
key[7] = (key_56[6] << 1) & 0xFF; | ||
|
||
key = set_key_odd_parity(key) | ||
|
||
return key | ||
|
||
#--------------------------------------------------------------------- | ||
def set_key_odd_parity(key): | ||
"" | ||
for i in range(len(key)): | ||
for k in range(7): | ||
bit = 0 | ||
t = key[i] >> k | ||
bit = (t ^ bit) & 0x1 | ||
key[i] = (key[i] & 0xFE) | bit | ||
|
||
return key |
Binary file not shown.
Oops, something went wrong.