2025-12-25 upload
This commit is contained in:
0
venv/Lib/site-packages/ldap3/utils/__init__.py
Normal file
0
venv/Lib/site-packages/ldap3/utils/__init__.py
Normal file
245
venv/Lib/site-packages/ldap3/utils/asn1.py
Normal file
245
venv/Lib/site-packages/ldap3/utils/asn1.py
Normal file
@@ -0,0 +1,245 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2015.08.19
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyasn1 import __version__ as pyasn1_version
|
||||
from pyasn1.codec.ber import decoder # for usage in other modules
|
||||
from pyasn1.codec.ber.encoder import Encoder # for monkeypatching of boolean value
|
||||
from ..core.results import RESULT_CODES
|
||||
from ..utils.conv import to_unicode
|
||||
from ..protocol.convert import referrals_to_list
|
||||
|
||||
CLASSES = {(False, False): 0, # Universal
|
||||
(False, True): 1, # Application
|
||||
(True, False): 2, # Context
|
||||
(True, True): 3} # Private
|
||||
|
||||
|
||||
# Monkeypatching of pyasn1 for encoding Boolean with the value 0xFF for TRUE
|
||||
# THIS IS NOT PART OF THE FAST BER DECODER
|
||||
if pyasn1_version == 'xxx0.2.3':
|
||||
from pyasn1.codec.ber.encoder import tagMap, BooleanEncoder, encode
|
||||
from pyasn1.type.univ import Boolean
|
||||
from pyasn1.compat.octets import ints2octs
|
||||
class BooleanCEREncoder(BooleanEncoder):
|
||||
_true = ints2octs((255,))
|
||||
|
||||
tagMap[Boolean.tagSet] = BooleanCEREncoder()
|
||||
else:
|
||||
from pyasn1.codec.ber.encoder import tagMap, typeMap, AbstractItemEncoder
|
||||
from pyasn1.type.univ import Boolean
|
||||
from copy import deepcopy
|
||||
|
||||
class LDAPBooleanEncoder(AbstractItemEncoder):
|
||||
supportIndefLenMode = False
|
||||
if pyasn1_version <= '0.2.3':
|
||||
from pyasn1.compat.octets import ints2octs
|
||||
_true = ints2octs((255,))
|
||||
_false = ints2octs((0,))
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
return value and self._true or self._false, 0
|
||||
elif pyasn1_version <= '0.3.1':
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize):
|
||||
return value and (255,) or (0,), False, False
|
||||
elif pyasn1_version <= '0.3.4':
|
||||
def encodeValue(self, encodeFun, value, defMode, maxChunkSize, ifNotEmpty=False):
|
||||
return value and (255,) or (0,), False, False
|
||||
elif pyasn1_version <= '0.3.7':
|
||||
def encodeValue(self, value, encodeFun, **options):
|
||||
return value and (255,) or (0,), False, False
|
||||
else:
|
||||
def encodeValue(self, value, asn1Spec, encodeFun, **options):
|
||||
return value and (255,) or (0,), False, False
|
||||
|
||||
customTagMap = deepcopy(tagMap)
|
||||
customTypeMap = deepcopy(typeMap)
|
||||
customTagMap[Boolean.tagSet] = LDAPBooleanEncoder()
|
||||
customTypeMap[Boolean.typeId] = LDAPBooleanEncoder()
|
||||
|
||||
encode = Encoder(customTagMap, customTypeMap)
|
||||
# end of monkey patching
|
||||
|
||||
# a fast BER decoder for LDAP responses only
|
||||
def compute_ber_size(data):
|
||||
"""
|
||||
Compute size according to BER definite length rules
|
||||
Returns size of value and value offset
|
||||
"""
|
||||
|
||||
if data[1] <= 127: # BER definite length - short form. Highest bit of byte 1 is 0, message length is in the last 7 bits - Value can be up to 127 bytes long
|
||||
return data[1], 2
|
||||
else: # BER definite length - long form. Highest bit of byte 1 is 1, last 7 bits counts the number of following octets containing the value length
|
||||
bytes_length = data[1] - 128
|
||||
value_length = 0
|
||||
cont = bytes_length
|
||||
for byte in data[2: 2 + bytes_length]:
|
||||
cont -= 1
|
||||
value_length += byte * (256 ** cont)
|
||||
return value_length, bytes_length + 2
|
||||
|
||||
|
||||
def decode_message_fast(message):
|
||||
ber_len, ber_value_offset = compute_ber_size(get_bytes(message[:10])) # get start of sequence, at maximum 3 bytes for length
|
||||
decoded = decode_sequence(message, ber_value_offset, ber_len + ber_value_offset, LDAP_MESSAGE_CONTEXT)
|
||||
return {
|
||||
'messageID': decoded[0][3],
|
||||
'protocolOp': decoded[1][2],
|
||||
'payload': decoded[1][3],
|
||||
'controls': decoded[2][3] if len(decoded) == 3 else None
|
||||
}
|
||||
|
||||
|
||||
def decode_sequence(message, start, stop, context_decoders=None):
|
||||
decoded = []
|
||||
while start < stop:
|
||||
octet = get_byte(message[start])
|
||||
ber_class = CLASSES[(bool(octet & 0b10000000), bool(octet & 0b01000000))]
|
||||
ber_constructed = bool(octet & 0b00100000)
|
||||
ber_type = octet & 0b00011111
|
||||
ber_decoder = DECODERS[(ber_class, octet & 0b00011111)] if ber_class < 2 else None
|
||||
ber_len, ber_value_offset = compute_ber_size(get_bytes(message[start: start + 10]))
|
||||
start += ber_value_offset
|
||||
if ber_decoder:
|
||||
value = ber_decoder(message, start, start + ber_len, context_decoders) # call value decode function
|
||||
else:
|
||||
# try:
|
||||
value = context_decoders[ber_type](message, start, start + ber_len) # call value decode function for context class
|
||||
# except KeyError:
|
||||
# if ber_type == 3: # Referral in result
|
||||
# value = decode_sequence(message, start, start + ber_len)
|
||||
# else:
|
||||
# raise # re-raise, should never happen
|
||||
decoded.append((ber_class, ber_constructed, ber_type, value))
|
||||
start += ber_len
|
||||
|
||||
return decoded
|
||||
|
||||
|
||||
def decode_integer(message, start, stop, context_decoders=None):
|
||||
first = message[start]
|
||||
value = -1 if get_byte(first) & 0x80 else 0
|
||||
for octet in message[start: stop]:
|
||||
value = value << 8 | get_byte(octet)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def decode_octet_string(message, start, stop, context_decoders=None):
|
||||
return message[start: stop]
|
||||
|
||||
|
||||
def decode_boolean(message, start, stop, context_decoders=None):
|
||||
return False if message[start: stop] == 0 else True
|
||||
|
||||
|
||||
def decode_bind_response(message, start, stop, context_decoders=None):
|
||||
return decode_sequence(message, start, stop, BIND_RESPONSE_CONTEXT)
|
||||
|
||||
|
||||
def decode_extended_response(message, start, stop, context_decoders=None):
|
||||
return decode_sequence(message, start, stop, EXTENDED_RESPONSE_CONTEXT)
|
||||
|
||||
|
||||
def decode_intermediate_response(message, start, stop, context_decoders=None):
|
||||
return decode_sequence(message, start, stop, INTERMEDIATE_RESPONSE_CONTEXT)
|
||||
|
||||
|
||||
def decode_controls(message, start, stop, context_decoders=None):
|
||||
return decode_sequence(message, start, stop, CONTROLS_CONTEXT)
|
||||
|
||||
|
||||
def ldap_result_to_dict_fast(response):
|
||||
response_dict = dict()
|
||||
response_dict['result'] = int(response[0][3]) # resultCode
|
||||
response_dict['description'] = RESULT_CODES[response_dict['result']]
|
||||
response_dict['dn'] = to_unicode(response[1][3], from_server=True) # matchedDN
|
||||
response_dict['message'] = to_unicode(response[2][3], from_server=True) # diagnosticMessage
|
||||
if len(response) == 4:
|
||||
response_dict['referrals'] = referrals_to_list([to_unicode(referral[3], from_server=True) for referral in response[3][3]]) # referrals
|
||||
else:
|
||||
response_dict['referrals'] = None
|
||||
|
||||
return response_dict
|
||||
|
||||
|
||||
######
|
||||
|
||||
if str is not bytes: # Python 3
|
||||
def get_byte(x):
|
||||
return x
|
||||
|
||||
def get_bytes(x):
|
||||
return x
|
||||
else: # Python 2
|
||||
def get_byte(x):
|
||||
return ord(x)
|
||||
|
||||
def get_bytes(x):
|
||||
return bytearray(x)
|
||||
|
||||
DECODERS = {
|
||||
# Universal
|
||||
(0, 1): decode_boolean, # Boolean
|
||||
(0, 2): decode_integer, # Integer
|
||||
(0, 4): decode_octet_string, # Octet String
|
||||
(0, 10): decode_integer, # Enumerated
|
||||
(0, 16): decode_sequence, # Sequence
|
||||
(0, 17): decode_sequence, # Set
|
||||
# Application
|
||||
(1, 1): decode_bind_response, # Bind response
|
||||
(1, 4): decode_sequence, # Search result entry
|
||||
(1, 5): decode_sequence, # Search result done
|
||||
(1, 7): decode_sequence, # Modify response
|
||||
(1, 9): decode_sequence, # Add response
|
||||
(1, 11): decode_sequence, # Delete response
|
||||
(1, 13): decode_sequence, # ModifyDN response
|
||||
(1, 15): decode_sequence, # Compare response
|
||||
(1, 19): decode_sequence, # Search result reference
|
||||
(1, 24): decode_extended_response, # Extended response
|
||||
(1, 25): decode_intermediate_response, # intermediate response
|
||||
(2, 3): decode_octet_string #
|
||||
}
|
||||
|
||||
BIND_RESPONSE_CONTEXT = {
|
||||
7: decode_octet_string # SaslCredentials
|
||||
}
|
||||
|
||||
EXTENDED_RESPONSE_CONTEXT = {
|
||||
10: decode_octet_string, # ResponseName
|
||||
11: decode_octet_string # Response Value
|
||||
}
|
||||
|
||||
INTERMEDIATE_RESPONSE_CONTEXT = {
|
||||
0: decode_octet_string, # IntermediateResponseName
|
||||
1: decode_octet_string # IntermediateResponseValue
|
||||
}
|
||||
|
||||
LDAP_MESSAGE_CONTEXT = {
|
||||
0: decode_controls, # Controls
|
||||
3: decode_sequence # Referral
|
||||
}
|
||||
|
||||
CONTROLS_CONTEXT = {
|
||||
0: decode_sequence # Control
|
||||
}
|
||||
199
venv/Lib/site-packages/ldap3/utils/ciDict.py
Normal file
199
venv/Lib/site-packages/ldap3/utils/ciDict.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.08.23
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
try:
|
||||
from collections.abc import MutableMapping, Mapping
|
||||
except ImportError:
|
||||
from collections import MutableMapping, Mapping
|
||||
|
||||
from .. import SEQUENCE_TYPES
|
||||
|
||||
|
||||
class CaseInsensitiveDict(MutableMapping):
|
||||
def __init__(self, other=None, **kwargs):
|
||||
self._store = dict() # store use the original key
|
||||
self._case_insensitive_keymap = dict() # is a mapping ci_key -> key
|
||||
if other or kwargs:
|
||||
if other is None:
|
||||
other = dict()
|
||||
self.update(other, **kwargs)
|
||||
|
||||
def __contains__(self, item):
|
||||
try:
|
||||
self.__getitem__(item)
|
||||
return True
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _ci_key(key):
|
||||
return key.strip().lower() if hasattr(key, 'lower') else key
|
||||
|
||||
def __delitem__(self, key):
|
||||
ci_key = self._ci_key(key)
|
||||
del self._store[self._case_insensitive_keymap[ci_key]]
|
||||
del self._case_insensitive_keymap[ci_key]
|
||||
|
||||
def __setitem__(self, key, item):
|
||||
ci_key = self._ci_key(key)
|
||||
if ci_key in self._case_insensitive_keymap: # updates existing value
|
||||
self._store[self._case_insensitive_keymap[ci_key]] = item
|
||||
else: # new key
|
||||
self._store[key] = item
|
||||
self._case_insensitive_keymap[ci_key] = key
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._store[self._case_insensitive_keymap[self._ci_key(key)]]
|
||||
|
||||
def __iter__(self):
|
||||
return self._store.__iter__()
|
||||
|
||||
def __len__(self): # if len is 0 then the cidict appears as False in IF statement
|
||||
return len(self._store)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._store)
|
||||
|
||||
def __str__(self):
|
||||
return str(self._store)
|
||||
|
||||
def keys(self):
|
||||
return self._store.keys()
|
||||
|
||||
def values(self):
|
||||
return self._store.values()
|
||||
|
||||
def items(self):
|
||||
return self._store.items()
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, (Mapping, dict)):
|
||||
return NotImplemented
|
||||
|
||||
if isinstance(other, CaseInsensitiveDict):
|
||||
if len(self.items()) != len(other.items()):
|
||||
return False
|
||||
else:
|
||||
for key, value in self.items():
|
||||
if not (key in other and other[key] == value):
|
||||
return False
|
||||
return True
|
||||
|
||||
return self == CaseInsensitiveDict(other)
|
||||
|
||||
def copy(self):
|
||||
return CaseInsensitiveDict(self._store)
|
||||
|
||||
|
||||
class CaseInsensitiveWithAliasDict(CaseInsensitiveDict):
|
||||
def __init__(self, other=None, **kwargs):
|
||||
self._aliases = dict()
|
||||
self._alias_keymap = dict() # is a mapping key -> [alias1, alias2, ...]
|
||||
CaseInsensitiveDict.__init__(self, other, **kwargs)
|
||||
|
||||
def aliases(self):
|
||||
return self._aliases.keys()
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(key, SEQUENCE_TYPES):
|
||||
ci_key = self._ci_key(key[0])
|
||||
if ci_key not in self._aliases:
|
||||
CaseInsensitiveDict.__setitem__(self, key[0], value)
|
||||
self.set_alias(ci_key, key[1:])
|
||||
else:
|
||||
raise KeyError('\'' + str(key[0] + ' already used as alias'))
|
||||
else:
|
||||
ci_key = self._ci_key(key)
|
||||
if ci_key not in self._aliases:
|
||||
CaseInsensitiveDict.__setitem__(self, key, value)
|
||||
else:
|
||||
self[self._aliases[ci_key]] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
ci_key = self._ci_key(key)
|
||||
try:
|
||||
CaseInsensitiveDict.__delitem__(self, ci_key)
|
||||
if ci_key in self._alias_keymap:
|
||||
for alias in self._alias_keymap[ci_key][:]: # removes aliases, uses a copy of _alias_keymap because iterator gets confused when aliases are removed from _alias_keymap
|
||||
self.remove_alias(alias)
|
||||
return
|
||||
except KeyError: # try to remove alias
|
||||
if ci_key in self._aliases:
|
||||
self.remove_alias(ci_key)
|
||||
|
||||
def set_alias(self, key, alias, ignore_duplicates=False):
|
||||
if not isinstance(alias, SEQUENCE_TYPES):
|
||||
alias = [alias]
|
||||
for alias_to_add in alias:
|
||||
ci_key = self._ci_key(key)
|
||||
if ci_key in self._case_insensitive_keymap:
|
||||
ci_alias = self._ci_key(alias_to_add)
|
||||
if ci_alias not in self._case_insensitive_keymap: # checks if alias is used a key
|
||||
if ci_alias not in self._aliases: # checks if alias is used as another alias
|
||||
self._aliases[ci_alias] = ci_key
|
||||
if ci_key in self._alias_keymap: # extends alias keymap
|
||||
self._alias_keymap[ci_key].append(self._ci_key(ci_alias))
|
||||
else:
|
||||
self._alias_keymap[ci_key] = list()
|
||||
self._alias_keymap[ci_key].append(self._ci_key(ci_alias))
|
||||
else:
|
||||
if ci_key in self._alias_keymap and ci_alias in self._alias_keymap[ci_key]: # passes if alias is already defined to the same key
|
||||
pass
|
||||
elif not ignore_duplicates:
|
||||
raise KeyError('\'' + str(alias_to_add) + '\' already used as alias')
|
||||
else:
|
||||
if ci_key == self._ci_key(self._case_insensitive_keymap[ci_alias]): # passes if alias is already defined to the same key
|
||||
pass
|
||||
elif not ignore_duplicates:
|
||||
raise KeyError('\'' + str(alias_to_add) + '\' already used as key')
|
||||
else:
|
||||
for keymap in self._alias_keymap:
|
||||
if ci_key in self._alias_keymap[keymap]: # kye is already aliased
|
||||
self.set_alias(keymap, alias + [ci_key], ignore_duplicates=ignore_duplicates)
|
||||
break
|
||||
else:
|
||||
raise KeyError('\'' + str(ci_key) + '\' is not an existing alias or key')
|
||||
|
||||
def remove_alias(self, alias):
|
||||
if not isinstance(alias, SEQUENCE_TYPES):
|
||||
alias = [alias]
|
||||
for alias_to_remove in alias:
|
||||
ci_alias = self._ci_key(alias_to_remove)
|
||||
self._alias_keymap[self._aliases[ci_alias]].remove(ci_alias)
|
||||
if not self._alias_keymap[self._aliases[ci_alias]]: # remove keymap if empty
|
||||
del self._alias_keymap[self._aliases[ci_alias]]
|
||||
del self._aliases[ci_alias]
|
||||
|
||||
def __getitem__(self, key):
|
||||
try:
|
||||
return CaseInsensitiveDict.__getitem__(self, key)
|
||||
except KeyError:
|
||||
return CaseInsensitiveDict.__getitem__(self, self._aliases[self._ci_key(key)])
|
||||
|
||||
def copy(self):
|
||||
new = CaseInsensitiveWithAliasDict(self._store)
|
||||
new._aliases = self._aliases.copy()
|
||||
new._alias_keymap = self._alias_keymap
|
||||
return new
|
||||
299
venv/Lib/site-packages/ldap3/utils/config.py
Normal file
299
venv/Lib/site-packages/ldap3/utils/config.py
Normal file
@@ -0,0 +1,299 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2016.08.31
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2013 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from sys import stdin, getdefaultencoding
|
||||
|
||||
from .. import ALL_ATTRIBUTES, ALL_OPERATIONAL_ATTRIBUTES, NO_ATTRIBUTES, SEQUENCE_TYPES
|
||||
from ..core.exceptions import LDAPConfigurationParameterError
|
||||
|
||||
# checks
|
||||
_CLASSES_EXCLUDED_FROM_CHECK = ['subschema']
|
||||
_ATTRIBUTES_EXCLUDED_FROM_CHECK = [ALL_ATTRIBUTES,
|
||||
ALL_OPERATIONAL_ATTRIBUTES,
|
||||
NO_ATTRIBUTES,
|
||||
'ldapSyntaxes',
|
||||
'matchingRules',
|
||||
'matchingRuleUse',
|
||||
'dITContentRules',
|
||||
'dITStructureRules',
|
||||
'nameForms',
|
||||
'altServer',
|
||||
'namingContexts',
|
||||
'supportedControl',
|
||||
'supportedExtension',
|
||||
'supportedFeatures',
|
||||
'supportedCapabilities',
|
||||
'supportedLdapVersion',
|
||||
'supportedSASLMechanisms',
|
||||
'vendorName',
|
||||
'vendorVersion',
|
||||
'subschemaSubentry',
|
||||
'ACL']
|
||||
_UTF8_ENCODED_SYNTAXES = ['1.2.840.113556.1.4.904', # DN String [MICROSOFT]
|
||||
'1.2.840.113556.1.4.1362', # String (Case) [MICROSOFT]
|
||||
'1.3.6.1.4.1.1466.115.121.1.12', # DN String [RFC4517]
|
||||
'1.3.6.1.4.1.1466.115.121.1.15', # Directory String [RFC4517]
|
||||
'1.3.6.1.4.1.1466.115.121.1.41', # Postal Address) [RFC4517]
|
||||
'1.3.6.1.4.1.1466.115.121.1.58', # Substring Assertion [RFC4517]
|
||||
'2.16.840.1.113719.1.1.5.1.6', # Case Ignore List [NOVELL]
|
||||
'2.16.840.1.113719.1.1.5.1.14', # Tagged String [NOVELL]
|
||||
'2.16.840.1.113719.1.1.5.1.15', # Tagged Name and String [NOVELL]
|
||||
'2.16.840.1.113719.1.1.5.1.23', # Tagged Name [NOVELL]
|
||||
'2.16.840.1.113719.1.1.5.1.25'] # Typed Name [NOVELL]
|
||||
|
||||
_UTF8_ENCODED_TYPES = []
|
||||
|
||||
_ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF = ['msds-memberOfTransitive', 'msds-memberTransitive', 'entryDN']
|
||||
_IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF = ['instanceType', 'nTSecurityDescriptor', 'objectCategory']
|
||||
|
||||
_CASE_INSENSITIVE_ATTRIBUTE_NAMES = True
|
||||
_CASE_INSENSITIVE_SCHEMA_NAMES = True
|
||||
|
||||
# abstraction layer
|
||||
_ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX = 'OA_'
|
||||
|
||||
# communication
|
||||
_POOLING_LOOP_TIMEOUT = 10 # number of seconds to wait before restarting a cycle to find an active server in the pool
|
||||
_RESPONSE_SLEEPTIME = 0.05 # seconds to wait while waiting for a response in asynchronous strategies
|
||||
_RESPONSE_WAITING_TIMEOUT = 20 # waiting timeout for receiving a response in asynchronous strategies
|
||||
_SOCKET_SIZE = 4096 # socket byte size
|
||||
_CHECK_AVAILABILITY_TIMEOUT = 2.5 # default timeout for socket connect when checking availability
|
||||
_RESET_AVAILABILITY_TIMEOUT = 5 # default timeout for resetting the availability status when checking candidate addresses
|
||||
_RESTARTABLE_SLEEPTIME = 2 # time to wait in a restartable strategy before retrying the request
|
||||
_RESTARTABLE_TRIES = 30 # number of times to retry in a restartable strategy before giving up. Set to True for unlimited retries
|
||||
_REUSABLE_THREADED_POOL_SIZE = 5
|
||||
_REUSABLE_THREADED_LIFETIME = 3600 # 1 hour
|
||||
_DEFAULT_THREADED_POOL_NAME = 'REUSABLE_DEFAULT_POOL'
|
||||
_ADDRESS_INFO_REFRESH_TIME = 300 # seconds to wait before refreshing address info from dns
|
||||
_ADDITIONAL_SERVER_ENCODINGS = ['latin-1', 'koi8-r'] # some broken LDAP implementation may have different encoding than those expected by RFCs
|
||||
_ADDITIONAL_CLIENT_ENCODINGS = ['utf-8']
|
||||
_IGNORE_MALFORMED_SCHEMA = False # some flaky LDAP servers returns malformed schema. If True no expection is raised and schema is thrown away
|
||||
_DEFAULT_SERVER_ENCODING = 'utf-8' # should always be utf-8
|
||||
_LDIF_LINE_LENGTH = 78 # as stated in RFC 2849
|
||||
|
||||
if stdin and hasattr(stdin, 'encoding') and stdin.encoding:
|
||||
_DEFAULT_CLIENT_ENCODING = stdin.encoding
|
||||
elif getdefaultencoding():
|
||||
_DEFAULT_CLIENT_ENCODING = getdefaultencoding()
|
||||
else:
|
||||
_DEFAULT_CLIENT_ENCODING = 'utf-8'
|
||||
|
||||
PARAMETERS = ['CASE_INSENSITIVE_ATTRIBUTE_NAMES',
|
||||
'CASE_INSENSITIVE_SCHEMA_NAMES',
|
||||
'ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX',
|
||||
'POOLING_LOOP_TIMEOUT',
|
||||
'RESPONSE_SLEEPTIME',
|
||||
'RESPONSE_WAITING_TIMEOUT',
|
||||
'SOCKET_SIZE',
|
||||
'CHECK_AVAILABILITY_TIMEOUT',
|
||||
'RESTARTABLE_SLEEPTIME',
|
||||
'RESTARTABLE_TRIES',
|
||||
'REUSABLE_THREADED_POOL_SIZE',
|
||||
'REUSABLE_THREADED_LIFETIME',
|
||||
'DEFAULT_THREADED_POOL_NAME',
|
||||
'ADDRESS_INFO_REFRESH_TIME',
|
||||
'RESET_AVAILABILITY_TIMEOUT',
|
||||
'DEFAULT_CLIENT_ENCODING',
|
||||
'DEFAULT_SERVER_ENCODING',
|
||||
'CLASSES_EXCLUDED_FROM_CHECK',
|
||||
'ATTRIBUTES_EXCLUDED_FROM_CHECK',
|
||||
'UTF8_ENCODED_SYNTAXES',
|
||||
'UTF8_ENCODED_TYPES',
|
||||
'ADDITIONAL_SERVER_ENCODINGS',
|
||||
'ADDITIONAL_CLIENT_ENCODINGS',
|
||||
'IGNORE_MALFORMED_SCHEMA',
|
||||
'ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF',
|
||||
'IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF',
|
||||
'LDIF_LINE_LENGTH'
|
||||
]
|
||||
|
||||
|
||||
def get_config_parameter(parameter):
|
||||
if parameter == 'CASE_INSENSITIVE_ATTRIBUTE_NAMES': # Boolean
|
||||
return _CASE_INSENSITIVE_ATTRIBUTE_NAMES
|
||||
elif parameter == 'CASE_INSENSITIVE_SCHEMA_NAMES': # Boolean
|
||||
return _CASE_INSENSITIVE_SCHEMA_NAMES
|
||||
elif parameter == 'ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX': # String
|
||||
return _ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX
|
||||
elif parameter == 'POOLING_LOOP_TIMEOUT': # Integer
|
||||
return _POOLING_LOOP_TIMEOUT
|
||||
elif parameter == 'RESPONSE_SLEEPTIME': # Integer
|
||||
return _RESPONSE_SLEEPTIME
|
||||
elif parameter == 'RESPONSE_WAITING_TIMEOUT': # Integer
|
||||
return _RESPONSE_WAITING_TIMEOUT
|
||||
elif parameter == 'SOCKET_SIZE': # Integer
|
||||
return _SOCKET_SIZE
|
||||
elif parameter == 'CHECK_AVAILABILITY_TIMEOUT': # Integer
|
||||
return _CHECK_AVAILABILITY_TIMEOUT
|
||||
elif parameter == 'RESTARTABLE_SLEEPTIME': # Integer
|
||||
return _RESTARTABLE_SLEEPTIME
|
||||
elif parameter == 'RESTARTABLE_TRIES': # Integer
|
||||
return _RESTARTABLE_TRIES
|
||||
elif parameter == 'REUSABLE_THREADED_POOL_SIZE': # Integer
|
||||
return _REUSABLE_THREADED_POOL_SIZE
|
||||
elif parameter == 'REUSABLE_THREADED_LIFETIME': # Integer
|
||||
return _REUSABLE_THREADED_LIFETIME
|
||||
elif parameter == 'DEFAULT_THREADED_POOL_NAME': # String
|
||||
return _DEFAULT_THREADED_POOL_NAME
|
||||
elif parameter == 'ADDRESS_INFO_REFRESH_TIME': # Integer
|
||||
return _ADDRESS_INFO_REFRESH_TIME
|
||||
elif parameter == 'RESET_AVAILABILITY_TIMEOUT': # Integer
|
||||
return _RESET_AVAILABILITY_TIMEOUT
|
||||
elif parameter in ['DEFAULT_CLIENT_ENCODING', 'DEFAULT_ENCODING']: # String - DEFAULT_ENCODING for backward compatibility
|
||||
return _DEFAULT_CLIENT_ENCODING
|
||||
elif parameter == 'DEFAULT_SERVER_ENCODING': # String
|
||||
return _DEFAULT_SERVER_ENCODING
|
||||
elif parameter == 'CLASSES_EXCLUDED_FROM_CHECK': # Sequence
|
||||
if isinstance(_CLASSES_EXCLUDED_FROM_CHECK, SEQUENCE_TYPES):
|
||||
return _CLASSES_EXCLUDED_FROM_CHECK
|
||||
else:
|
||||
return [_CLASSES_EXCLUDED_FROM_CHECK]
|
||||
elif parameter == 'ATTRIBUTES_EXCLUDED_FROM_CHECK': # Sequence
|
||||
if isinstance(_ATTRIBUTES_EXCLUDED_FROM_CHECK, SEQUENCE_TYPES):
|
||||
return _ATTRIBUTES_EXCLUDED_FROM_CHECK
|
||||
else:
|
||||
return [_ATTRIBUTES_EXCLUDED_FROM_CHECK]
|
||||
elif parameter == 'UTF8_ENCODED_SYNTAXES': # Sequence
|
||||
if isinstance(_UTF8_ENCODED_SYNTAXES, SEQUENCE_TYPES):
|
||||
return _UTF8_ENCODED_SYNTAXES
|
||||
else:
|
||||
return [_UTF8_ENCODED_SYNTAXES]
|
||||
elif parameter == 'UTF8_ENCODED_TYPES': # Sequence
|
||||
if isinstance(_UTF8_ENCODED_TYPES, SEQUENCE_TYPES):
|
||||
return _UTF8_ENCODED_TYPES
|
||||
else:
|
||||
return [_UTF8_ENCODED_TYPES]
|
||||
elif parameter in ['ADDITIONAL_SERVER_ENCODINGS', 'ADDITIONAL_ENCODINGS']: # Sequence - ADDITIONAL_ENCODINGS for backward compatibility
|
||||
if isinstance(_ADDITIONAL_SERVER_ENCODINGS, SEQUENCE_TYPES):
|
||||
return _ADDITIONAL_SERVER_ENCODINGS
|
||||
else:
|
||||
return [_ADDITIONAL_SERVER_ENCODINGS]
|
||||
elif parameter in ['ADDITIONAL_CLIENT_ENCODINGS']: # Sequence
|
||||
if isinstance(_ADDITIONAL_CLIENT_ENCODINGS, SEQUENCE_TYPES):
|
||||
return _ADDITIONAL_CLIENT_ENCODINGS
|
||||
else:
|
||||
return [_ADDITIONAL_CLIENT_ENCODINGS]
|
||||
elif parameter == 'IGNORE_MALFORMED_SCHEMA': # Boolean
|
||||
return _IGNORE_MALFORMED_SCHEMA
|
||||
elif parameter == 'ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF': # Sequence
|
||||
if isinstance(_ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF, SEQUENCE_TYPES):
|
||||
return _ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF
|
||||
else:
|
||||
return [_ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF]
|
||||
elif parameter == 'IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF': # Sequence
|
||||
if isinstance(_IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF, SEQUENCE_TYPES):
|
||||
return _IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF
|
||||
else:
|
||||
return [_IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF]
|
||||
elif parameter == 'LDIF_LINE_LENGTH': # Integer
|
||||
return _LDIF_LINE_LENGTH
|
||||
|
||||
raise LDAPConfigurationParameterError('configuration parameter %s not valid' % parameter)
|
||||
|
||||
|
||||
def set_config_parameter(parameter, value):
|
||||
if parameter == 'CASE_INSENSITIVE_ATTRIBUTE_NAMES':
|
||||
global _CASE_INSENSITIVE_ATTRIBUTE_NAMES
|
||||
_CASE_INSENSITIVE_ATTRIBUTE_NAMES = value
|
||||
elif parameter == 'CASE_INSENSITIVE_SCHEMA_NAMES':
|
||||
global _CASE_INSENSITIVE_SCHEMA_NAMES
|
||||
_CASE_INSENSITIVE_SCHEMA_NAMES = value
|
||||
elif parameter == 'ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX':
|
||||
global _ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX
|
||||
_ABSTRACTION_OPERATIONAL_ATTRIBUTE_PREFIX = value
|
||||
elif parameter == 'POOLING_LOOP_TIMEOUT':
|
||||
global _POOLING_LOOP_TIMEOUT
|
||||
_POOLING_LOOP_TIMEOUT = value
|
||||
elif parameter == 'RESPONSE_SLEEPTIME':
|
||||
global _RESPONSE_SLEEPTIME
|
||||
_RESPONSE_SLEEPTIME = value
|
||||
elif parameter == 'RESPONSE_WAITING_TIMEOUT':
|
||||
global _RESPONSE_WAITING_TIMEOUT
|
||||
_RESPONSE_WAITING_TIMEOUT = value
|
||||
elif parameter == 'SOCKET_SIZE':
|
||||
global _SOCKET_SIZE
|
||||
_SOCKET_SIZE = value
|
||||
elif parameter == 'CHECK_AVAILABILITY_TIMEOUT':
|
||||
global _CHECK_AVAILABILITY_TIMEOUT
|
||||
_CHECK_AVAILABILITY_TIMEOUT = value
|
||||
elif parameter == 'RESTARTABLE_SLEEPTIME':
|
||||
global _RESTARTABLE_SLEEPTIME
|
||||
_RESTARTABLE_SLEEPTIME = value
|
||||
elif parameter == 'RESTARTABLE_TRIES':
|
||||
global _RESTARTABLE_TRIES
|
||||
_RESTARTABLE_TRIES = value
|
||||
elif parameter == 'REUSABLE_THREADED_POOL_SIZE':
|
||||
global _REUSABLE_THREADED_POOL_SIZE
|
||||
_REUSABLE_THREADED_POOL_SIZE = value
|
||||
elif parameter == 'REUSABLE_THREADED_LIFETIME':
|
||||
global _REUSABLE_THREADED_LIFETIME
|
||||
_REUSABLE_THREADED_LIFETIME = value
|
||||
elif parameter == 'DEFAULT_THREADED_POOL_NAME':
|
||||
global _DEFAULT_THREADED_POOL_NAME
|
||||
_DEFAULT_THREADED_POOL_NAME = value
|
||||
elif parameter == 'ADDRESS_INFO_REFRESH_TIME':
|
||||
global _ADDRESS_INFO_REFRESH_TIME
|
||||
_ADDRESS_INFO_REFRESH_TIME = value
|
||||
elif parameter == 'RESET_AVAILABILITY_TIMEOUT':
|
||||
global _RESET_AVAILABILITY_TIMEOUT
|
||||
_RESET_AVAILABILITY_TIMEOUT = value
|
||||
elif parameter in ['DEFAULT_CLIENT_ENCODING', 'DEFAULT_ENCODING']:
|
||||
global _DEFAULT_CLIENT_ENCODING
|
||||
_DEFAULT_CLIENT_ENCODING = value
|
||||
elif parameter == 'DEFAULT_SERVER_ENCODING':
|
||||
global _DEFAULT_SERVER_ENCODING
|
||||
_DEFAULT_SERVER_ENCODING = value
|
||||
elif parameter == 'CLASSES_EXCLUDED_FROM_CHECK':
|
||||
global _CLASSES_EXCLUDED_FROM_CHECK
|
||||
_CLASSES_EXCLUDED_FROM_CHECK = value
|
||||
elif parameter == 'ATTRIBUTES_EXCLUDED_FROM_CHECK':
|
||||
global _ATTRIBUTES_EXCLUDED_FROM_CHECK
|
||||
_ATTRIBUTES_EXCLUDED_FROM_CHECK = value
|
||||
elif parameter == 'UTF8_ENCODED_SYNTAXES':
|
||||
global _UTF8_ENCODED_SYNTAXES
|
||||
_UTF8_ENCODED_SYNTAXES = value
|
||||
elif parameter == 'UTF8_ENCODED_TYPES':
|
||||
global _UTF8_ENCODED_TYPES
|
||||
_UTF8_ENCODED_TYPES = value
|
||||
elif parameter in ['ADDITIONAL_SERVER_ENCODINGS', 'ADDITIONAL_ENCODINGS']:
|
||||
global _ADDITIONAL_SERVER_ENCODINGS
|
||||
_ADDITIONAL_SERVER_ENCODINGS = value if isinstance(value, SEQUENCE_TYPES) else [value]
|
||||
elif parameter in ['ADDITIONAL_CLIENT_ENCODINGS']:
|
||||
global _ADDITIONAL_CLIENT_ENCODINGS
|
||||
_ADDITIONAL_CLIENT_ENCODINGS = value if isinstance(value, SEQUENCE_TYPES) else [value]
|
||||
elif parameter == 'IGNORE_MALFORMED_SCHEMA':
|
||||
global _IGNORE_MALFORMED_SCHEMA
|
||||
_IGNORE_MALFORMED_SCHEMA = value
|
||||
elif parameter == 'ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF':
|
||||
global _ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF
|
||||
_ATTRIBUTES_EXCLUDED_FROM_OBJECT_DEF = value
|
||||
elif parameter == 'IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF':
|
||||
global _IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF
|
||||
_IGNORED_MANDATORY_ATTRIBUTES_IN_OBJECT_DEF = value
|
||||
elif parameter == 'LDIF_LINE_LENGTH':
|
||||
global _LDIF_LINE_LENGTH
|
||||
_LDIF_LINE_LENGTH = value
|
||||
else:
|
||||
raise LDAPConfigurationParameterError('unable to set configuration parameter %s' % parameter)
|
||||
272
venv/Lib/site-packages/ldap3/utils/conv.py
Normal file
272
venv/Lib/site-packages/ldap3/utils/conv.py
Normal file
@@ -0,0 +1,272 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.04.26
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from base64 import b64encode, b64decode
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from .. import SEQUENCE_TYPES, STRING_TYPES, NUMERIC_TYPES, get_config_parameter
|
||||
from ..utils.ciDict import CaseInsensitiveDict
|
||||
from ..core.exceptions import LDAPDefinitionError
|
||||
|
||||
|
||||
def to_unicode(obj, encoding=None, from_server=False):
|
||||
"""Try to convert bytes (and str in python2) to unicode.
|
||||
Return object unmodified if python3 string, else raise an exception
|
||||
"""
|
||||
conf_default_client_encoding = get_config_parameter('DEFAULT_CLIENT_ENCODING')
|
||||
conf_default_server_encoding = get_config_parameter('DEFAULT_SERVER_ENCODING')
|
||||
conf_additional_server_encodings = get_config_parameter('ADDITIONAL_SERVER_ENCODINGS')
|
||||
conf_additional_client_encodings = get_config_parameter('ADDITIONAL_CLIENT_ENCODINGS')
|
||||
if isinstance(obj, NUMERIC_TYPES):
|
||||
obj = str(obj)
|
||||
|
||||
if isinstance(obj, (bytes, bytearray)):
|
||||
if from_server: # data from server
|
||||
if encoding is None:
|
||||
encoding = conf_default_server_encoding
|
||||
try:
|
||||
return obj.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
for encoding in conf_additional_server_encodings: # AD could have DN not encoded in utf-8 (even if this is not allowed by RFC4510)
|
||||
try:
|
||||
return obj.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
raise UnicodeError("Unable to convert server data to unicode: %r" % obj)
|
||||
else: # data from client
|
||||
if encoding is None:
|
||||
encoding = conf_default_client_encoding
|
||||
try:
|
||||
return obj.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
for encoding in conf_additional_client_encodings: # tries additional encodings
|
||||
try:
|
||||
return obj.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
raise UnicodeError("Unable to convert client data to unicode: %r" % obj)
|
||||
|
||||
if isinstance(obj, STRING_TYPES): # python3 strings, python 2 unicode
|
||||
return obj
|
||||
|
||||
raise UnicodeError("Unable to convert type %s to unicode: %r" % (obj.__class__.__name__, obj))
|
||||
|
||||
|
||||
def to_raw(obj, encoding='utf-8'):
|
||||
"""Tries to convert to raw bytes from unicode"""
|
||||
if isinstance(obj, NUMERIC_TYPES):
|
||||
obj = str(obj)
|
||||
|
||||
if not (isinstance(obj, bytes)):
|
||||
if isinstance(obj, SEQUENCE_TYPES):
|
||||
return [to_raw(element) for element in obj]
|
||||
elif isinstance(obj, STRING_TYPES):
|
||||
return obj.encode(encoding)
|
||||
return obj
|
||||
|
||||
|
||||
def escape_filter_chars(text, encoding=None):
|
||||
""" Escape chars mentioned in RFC4515. """
|
||||
if encoding is None:
|
||||
encoding = get_config_parameter('DEFAULT_ENCODING')
|
||||
|
||||
try:
|
||||
text = to_unicode(text, encoding)
|
||||
escaped = text.replace('\\', '\\5c')
|
||||
escaped = escaped.replace('*', '\\2a')
|
||||
escaped = escaped.replace('(', '\\28')
|
||||
escaped = escaped.replace(')', '\\29')
|
||||
escaped = escaped.replace('\x00', '\\00')
|
||||
except Exception: # probably raw bytes values, return escaped bytes value
|
||||
escaped = to_unicode(escape_bytes(text))
|
||||
# escape all octets greater than 0x7F that are not part of a valid UTF-8
|
||||
# escaped = ''.join(c if c <= ord(b'\x7f') else escape_bytes(to_raw(to_unicode(c, encoding))) for c in escaped)
|
||||
return escaped
|
||||
|
||||
|
||||
def unescape_filter_chars(text, encoding=None):
|
||||
""" unescape chars mentioned in RFC4515. """
|
||||
if encoding is None:
|
||||
encoding = get_config_parameter('DEFAULT_ENCODING')
|
||||
|
||||
unescaped = to_raw(text, encoding)
|
||||
unescaped = unescaped.replace(b'\\5c', b'\\')
|
||||
unescaped = unescaped.replace(b'\\5C', b'\\')
|
||||
unescaped = unescaped.replace(b'\\2a', b'*')
|
||||
unescaped = unescaped.replace(b'\\2A', b'*')
|
||||
unescaped = unescaped.replace(b'\\28', b'(')
|
||||
unescaped = unescaped.replace(b'\\29', b')')
|
||||
unescaped = unescaped.replace(b'\\00', b'\x00')
|
||||
return unescaped
|
||||
|
||||
|
||||
def escape_bytes(bytes_value):
|
||||
""" Convert a byte sequence to a properly escaped for LDAP (format BACKSLASH HEX HEX) string"""
|
||||
if bytes_value:
|
||||
if str is not bytes: # Python 3
|
||||
if isinstance(bytes_value, str):
|
||||
bytes_value = bytearray(bytes_value, encoding='utf-8')
|
||||
escaped = '\\'.join([('%02x' % int(b)) for b in bytes_value])
|
||||
else: # Python 2
|
||||
if isinstance(bytes_value, unicode):
|
||||
bytes_value = bytes_value.encode('utf-8')
|
||||
escaped = '\\'.join([('%02x' % ord(b)) for b in bytes_value])
|
||||
else:
|
||||
escaped = ''
|
||||
|
||||
return ('\\' + escaped) if escaped else ''
|
||||
|
||||
|
||||
def prepare_for_stream(value):
|
||||
if str is not bytes: # Python 3
|
||||
return value
|
||||
else: # Python 2
|
||||
return value.decode()
|
||||
|
||||
|
||||
def json_encode_b64(obj):
|
||||
try:
|
||||
return dict(encoding='base64', encoded=b64encode(obj))
|
||||
except Exception as e:
|
||||
raise LDAPDefinitionError('unable to encode ' + str(obj) + ' - ' + str(e))
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
def check_json_dict(json_dict):
|
||||
# needed for python 2
|
||||
|
||||
for k, v in json_dict.items():
|
||||
if isinstance(v, dict):
|
||||
check_json_dict(v)
|
||||
elif isinstance(v, CaseInsensitiveDict):
|
||||
check_json_dict(v._store)
|
||||
elif isinstance(v, SEQUENCE_TYPES):
|
||||
for i, e in enumerate(v):
|
||||
if isinstance(e, dict):
|
||||
check_json_dict(e)
|
||||
elif isinstance(e, CaseInsensitiveDict):
|
||||
check_json_dict(e._store)
|
||||
else:
|
||||
v[i] = format_json(e)
|
||||
else:
|
||||
json_dict[k] = format_json(v)
|
||||
|
||||
|
||||
def json_hook(obj):
|
||||
if hasattr(obj, 'keys') and len(list(obj.keys())) == 2 and 'encoding' in obj.keys() and 'encoded' in obj.keys():
|
||||
return b64decode(obj['encoded'])
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
def format_json(obj, iso_format=False):
|
||||
if isinstance(obj, CaseInsensitiveDict):
|
||||
return obj._store
|
||||
|
||||
if isinstance(obj, datetime.datetime):
|
||||
return str(obj)
|
||||
|
||||
if isinstance(obj, int):
|
||||
return obj
|
||||
|
||||
if isinstance(obj, datetime.timedelta):
|
||||
if iso_format:
|
||||
return obj.isoformat()
|
||||
return str(obj)
|
||||
|
||||
if str is bytes: # Python 2
|
||||
if isinstance(obj, long): # long exists only in python2
|
||||
return obj
|
||||
|
||||
try:
|
||||
if str is not bytes: # Python 3
|
||||
if isinstance(obj, bytes):
|
||||
# return check_escape(str(obj, 'utf-8', errors='strict'))
|
||||
return str(obj, 'utf-8', errors='strict')
|
||||
raise LDAPDefinitionError('unable to serialize ' + str(obj))
|
||||
else: # Python 2
|
||||
if isinstance(obj, unicode):
|
||||
return obj
|
||||
else:
|
||||
# return unicode(check_escape(obj))
|
||||
return unicode(obj)
|
||||
except (TypeError, UnicodeDecodeError):
|
||||
pass
|
||||
|
||||
try:
|
||||
return json_encode_b64(bytes(obj))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
raise LDAPDefinitionError('unable to serialize ' + str(obj))
|
||||
|
||||
|
||||
def is_filter_escaped(text):
|
||||
if not type(text) == ((str is not bytes) and str or unicode): # requires str for Python 3 and unicode for Python 2
|
||||
raise ValueError('unicode input expected')
|
||||
|
||||
return all(c not in text for c in '()*\0') and not re.search('\\\\([^0-9a-fA-F]|(.[^0-9a-fA-F]))', text)
|
||||
|
||||
|
||||
def ldap_escape_to_bytes(text):
|
||||
bytesequence = bytearray()
|
||||
i = 0
|
||||
try:
|
||||
if isinstance(text, STRING_TYPES):
|
||||
while i < len(text):
|
||||
if text[i] == '\\':
|
||||
if len(text) > i + 2:
|
||||
try:
|
||||
bytesequence.append(int(text[i+1:i+3], 16))
|
||||
i += 3
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
bytesequence.append(92) # "\" ASCII code
|
||||
else:
|
||||
raw = to_raw(text[i])
|
||||
for c in raw:
|
||||
bytesequence.append(c)
|
||||
i += 1
|
||||
elif isinstance(text, (bytes, bytearray)):
|
||||
while i < len(text):
|
||||
if text[i] == 92: # "\" ASCII code
|
||||
if len(text) > i + 2:
|
||||
try:
|
||||
bytesequence.append(int(text[i + 1:i + 3], 16))
|
||||
i += 3
|
||||
continue
|
||||
except ValueError:
|
||||
pass
|
||||
bytesequence.append(92) # "\" ASCII code
|
||||
else:
|
||||
bytesequence.append(text[i])
|
||||
i += 1
|
||||
except Exception:
|
||||
raise LDAPDefinitionError('badly formatted LDAP byte escaped sequence')
|
||||
|
||||
return bytes(bytesequence)
|
||||
405
venv/Lib/site-packages/ldap3/utils/dn.py
Normal file
405
venv/Lib/site-packages/ldap3/utils/dn.py
Normal file
@@ -0,0 +1,405 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.09.08
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from string import hexdigits, ascii_letters, digits
|
||||
|
||||
from .. import SEQUENCE_TYPES
|
||||
from ..core.exceptions import LDAPInvalidDnError
|
||||
|
||||
|
||||
STATE_ANY = 0
|
||||
STATE_ESCAPE = 1
|
||||
STATE_ESCAPE_HEX = 2
|
||||
|
||||
|
||||
def _add_ava(ava, decompose, remove_space, space_around_equal):
|
||||
if not ava:
|
||||
return ''
|
||||
|
||||
space = ' ' if space_around_equal else ''
|
||||
attr_name, _, value = ava.partition('=')
|
||||
if decompose:
|
||||
if remove_space:
|
||||
component = (attr_name.strip(), value.strip())
|
||||
else:
|
||||
component = (attr_name, value)
|
||||
else:
|
||||
if remove_space:
|
||||
component = attr_name.strip() + space + '=' + space + value.strip()
|
||||
else:
|
||||
component = attr_name + space + '=' + space + value
|
||||
|
||||
return component
|
||||
|
||||
|
||||
def to_dn(iterator, decompose=False, remove_space=False, space_around_equal=False, separate_rdn=False):
|
||||
"""
|
||||
Convert an iterator to a list of dn parts
|
||||
if decompose=True return a list of tuple (one for each dn component) else return a list of strings
|
||||
if remove_space=True removes unneeded spaces
|
||||
if space_around_equal=True add spaces around equal in returned strings
|
||||
if separate_rdn=True consider multiple RDNs as different component of DN
|
||||
"""
|
||||
dn = []
|
||||
component = ''
|
||||
escape_sequence = False
|
||||
for c in iterator:
|
||||
if c == '\\': # escape sequence
|
||||
escape_sequence = True
|
||||
elif escape_sequence and c != ' ':
|
||||
escape_sequence = False
|
||||
elif c == '+' and separate_rdn:
|
||||
dn.append(_add_ava(component, decompose, remove_space, space_around_equal))
|
||||
component = ''
|
||||
continue
|
||||
elif c == ',':
|
||||
if '=' in component:
|
||||
dn.append(_add_ava(component, decompose, remove_space, space_around_equal))
|
||||
component = ''
|
||||
continue
|
||||
|
||||
component += c
|
||||
|
||||
dn.append(_add_ava(component, decompose, remove_space, space_around_equal))
|
||||
return dn
|
||||
|
||||
|
||||
def _find_first_unescaped(dn, char, pos):
|
||||
while True:
|
||||
pos = dn.find(char, pos)
|
||||
if pos == -1:
|
||||
break # no char found
|
||||
if pos > 0 and dn[pos - 1] != '\\': # unescaped char
|
||||
break
|
||||
elif pos > 1 and dn[pos - 1] == '\\': # may be unescaped
|
||||
escaped = True
|
||||
for c in dn[pos - 2:0:-1]:
|
||||
if c == '\\':
|
||||
escaped = not escaped
|
||||
else:
|
||||
break
|
||||
if not escaped:
|
||||
break
|
||||
pos += 1
|
||||
|
||||
return pos
|
||||
|
||||
|
||||
def _find_last_unescaped(dn, char, start, stop=0):
|
||||
while True:
|
||||
stop = dn.rfind(char, start, stop)
|
||||
if stop == -1:
|
||||
break
|
||||
if stop >= 0 and dn[stop - 1] != '\\':
|
||||
break
|
||||
elif stop > 1 and dn[stop - 1] == '\\': # may be unescaped
|
||||
escaped = True
|
||||
for c in dn[stop - 2:0:-1]:
|
||||
if c == '\\':
|
||||
escaped = not escaped
|
||||
else:
|
||||
break
|
||||
if not escaped:
|
||||
break
|
||||
if stop < start:
|
||||
stop = -1
|
||||
break
|
||||
|
||||
return stop
|
||||
|
||||
|
||||
def _get_next_ava(dn):
|
||||
comma = _find_first_unescaped(dn, ',', 0)
|
||||
plus = _find_first_unescaped(dn, '+', 0)
|
||||
|
||||
if plus > 0 and (plus < comma or comma == -1):
|
||||
equal = _find_first_unescaped(dn, '=', plus + 1)
|
||||
if equal > plus + 1:
|
||||
plus = _find_last_unescaped(dn, '+', plus, equal)
|
||||
return dn[:plus], '+'
|
||||
|
||||
if comma > 0:
|
||||
equal = _find_first_unescaped(dn, '=', comma + 1)
|
||||
if equal > comma + 1:
|
||||
comma = _find_last_unescaped(dn, ',', comma, equal)
|
||||
return dn[:comma], ','
|
||||
|
||||
return dn, ''
|
||||
|
||||
|
||||
def _split_ava(ava, escape=False, strip=True):
|
||||
equal = ava.find('=')
|
||||
while equal > 0: # not first character
|
||||
if ava[equal - 1] != '\\': # not an escaped equal so it must be an ava separator
|
||||
# attribute_type1 = ava[0:equal].strip() if strip else ava[0:equal]
|
||||
if strip:
|
||||
attribute_type = ava[0:equal].strip()
|
||||
attribute_value = _escape_attribute_value(ava[equal + 1:].strip()) if escape else ava[equal + 1:].strip()
|
||||
else:
|
||||
attribute_type = ava[0:equal]
|
||||
attribute_value = _escape_attribute_value(ava[equal + 1:]) if escape else ava[equal + 1:]
|
||||
|
||||
return attribute_type, attribute_value
|
||||
equal = ava.find('=', equal + 1)
|
||||
|
||||
return '', (ava.strip if strip else ava) # if no equal found return only value
|
||||
|
||||
|
||||
def _validate_attribute_type(attribute_type):
|
||||
if not attribute_type:
|
||||
raise LDAPInvalidDnError('attribute type not present')
|
||||
|
||||
if attribute_type == '<GUID': # patch for AD DirSync
|
||||
return True
|
||||
|
||||
for c in attribute_type:
|
||||
if not (c in ascii_letters or c in digits or c == '-'): # allowed uppercase and lowercase letters, digits and hyphen as per RFC 4512
|
||||
raise LDAPInvalidDnError('character \'' + c + '\' not allowed in attribute type')
|
||||
|
||||
if attribute_type[0] in digits or attribute_type[0] == '-': # digits and hyphen not allowed as first character
|
||||
raise LDAPInvalidDnError('character \'' + attribute_type[0] + '\' not allowed as first character of attribute type')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _validate_attribute_value(attribute_value):
|
||||
if not attribute_value:
|
||||
return False
|
||||
|
||||
if attribute_value[0] == '#': # only hex characters are valid
|
||||
for c in attribute_value:
|
||||
if c not in hexdigits: # allowed only hex digits as per RFC 4514
|
||||
raise LDAPInvalidDnError('character ' + c + ' not allowed in hex representation of attribute value')
|
||||
if len(attribute_value) % 2 == 0: # string must be # + HEX HEX (an odd number of chars)
|
||||
raise LDAPInvalidDnError('hex representation must be in the form of <HEX><HEX> pairs')
|
||||
if attribute_value[0] == ' ': # unescaped space cannot be used as leading or last character
|
||||
raise LDAPInvalidDnError('SPACE must be escaped as leading character of attribute value')
|
||||
if attribute_value.endswith(' ') and not attribute_value.endswith('\\ '):
|
||||
raise LDAPInvalidDnError('SPACE must be escaped as trailing character of attribute value')
|
||||
|
||||
state = STATE_ANY
|
||||
for c in attribute_value:
|
||||
if state == STATE_ANY:
|
||||
if c == '\\':
|
||||
state = STATE_ESCAPE
|
||||
elif c in '"#+,;<=>\00':
|
||||
raise LDAPInvalidDnError('special character ' + c + ' must be escaped')
|
||||
elif state == STATE_ESCAPE:
|
||||
if c in hexdigits:
|
||||
state = STATE_ESCAPE_HEX
|
||||
elif c in ' "#+,;<=>\\\00':
|
||||
state = STATE_ANY
|
||||
else:
|
||||
raise LDAPInvalidDnError('invalid escaped character ' + c)
|
||||
elif state == STATE_ESCAPE_HEX:
|
||||
if c in hexdigits:
|
||||
state = STATE_ANY
|
||||
else:
|
||||
raise LDAPInvalidDnError('invalid escaped character ' + c)
|
||||
|
||||
# final state
|
||||
if state != STATE_ANY:
|
||||
raise LDAPInvalidDnError('invalid final character')
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def _escape_attribute_value(attribute_value):
|
||||
if not attribute_value:
|
||||
return ''
|
||||
|
||||
if attribute_value[0] == '#': # with leading SHARP only pairs of hex characters are valid
|
||||
valid_hex = True
|
||||
if len(attribute_value) % 2 == 0: # string must be # + HEX HEX (an odd number of chars)
|
||||
valid_hex = False
|
||||
|
||||
if valid_hex:
|
||||
for c in attribute_value:
|
||||
if c not in hexdigits: # allowed only hex digits as per RFC 4514
|
||||
valid_hex = False
|
||||
break
|
||||
|
||||
if valid_hex:
|
||||
return attribute_value
|
||||
|
||||
state = STATE_ANY
|
||||
escaped = ''
|
||||
tmp_buffer = ''
|
||||
for c in attribute_value:
|
||||
if state == STATE_ANY:
|
||||
if c == '\\':
|
||||
state = STATE_ESCAPE
|
||||
elif c in '"#+,;<=>\00':
|
||||
escaped += '\\' + c
|
||||
else:
|
||||
escaped += c
|
||||
elif state == STATE_ESCAPE:
|
||||
if c in hexdigits:
|
||||
tmp_buffer = c
|
||||
state = STATE_ESCAPE_HEX
|
||||
elif c in ' "#+,;<=>\\\00':
|
||||
escaped += '\\' + c
|
||||
state = STATE_ANY
|
||||
else:
|
||||
escaped += '\\\\' + c
|
||||
elif state == STATE_ESCAPE_HEX:
|
||||
if c in hexdigits:
|
||||
escaped += '\\' + tmp_buffer + c
|
||||
else:
|
||||
escaped += '\\\\' + tmp_buffer + c
|
||||
tmp_buffer = ''
|
||||
state = STATE_ANY
|
||||
|
||||
# final state
|
||||
if state == STATE_ESCAPE:
|
||||
escaped += '\\\\'
|
||||
elif state == STATE_ESCAPE_HEX:
|
||||
escaped += '\\\\' + tmp_buffer
|
||||
|
||||
if escaped[0] == ' ': # leading SPACE must be escaped
|
||||
escaped = '\\' + escaped
|
||||
|
||||
if escaped[-1] == ' ' and len(escaped) > 1 and escaped[-2] != '\\': # trailing SPACE must be escaped
|
||||
escaped = escaped[:-1] + '\\ '
|
||||
|
||||
return escaped
|
||||
|
||||
|
||||
def parse_dn(dn, escape=False, strip=False):
|
||||
"""
|
||||
Parses a DN into syntactic components
|
||||
:param dn:
|
||||
:param escape:
|
||||
:param strip:
|
||||
:return:
|
||||
a list of tripels representing `attributeTypeAndValue` elements
|
||||
containing `attributeType`, `attributeValue` and the following separator (`COMMA` or `PLUS`) if given, else an empty `str`.
|
||||
in their original representation, still containing escapes or encoded as hex.
|
||||
"""
|
||||
rdns = []
|
||||
avas = []
|
||||
while dn:
|
||||
ava, separator = _get_next_ava(dn) # if returned ava doesn't containg any unescaped equal it'a appended to last ava in avas
|
||||
|
||||
dn = dn[len(ava) + 1:]
|
||||
if _find_first_unescaped(ava, '=', 0) > 0 or len(avas) == 0:
|
||||
avas.append((ava, separator))
|
||||
else:
|
||||
avas[len(avas) - 1] = (avas[len(avas) - 1][0] + avas[len(avas) - 1][1] + ava, separator)
|
||||
|
||||
for ava, separator in avas:
|
||||
attribute_type, attribute_value = _split_ava(ava, escape, strip)
|
||||
|
||||
if not _validate_attribute_type(attribute_type):
|
||||
raise LDAPInvalidDnError('unable to validate attribute type in ' + ava)
|
||||
|
||||
if not _validate_attribute_value(attribute_value):
|
||||
raise LDAPInvalidDnError('unable to validate attribute value in ' + ava)
|
||||
|
||||
rdns.append((attribute_type, attribute_value, separator))
|
||||
dn = dn[len(ava) + 1:]
|
||||
|
||||
if not rdns:
|
||||
raise LDAPInvalidDnError('empty dn')
|
||||
|
||||
return rdns
|
||||
|
||||
|
||||
def safe_dn(dn, decompose=False, reverse=False):
|
||||
"""
|
||||
normalize and escape a dn, if dn is a sequence it is joined.
|
||||
the reverse parameter changes the join direction of the sequence
|
||||
"""
|
||||
if isinstance(dn, SEQUENCE_TYPES):
|
||||
components = [rdn for rdn in dn]
|
||||
if reverse:
|
||||
dn = ','.join(reversed(components))
|
||||
else:
|
||||
dn = ','.join(components)
|
||||
if decompose:
|
||||
escaped_dn = []
|
||||
else:
|
||||
escaped_dn = ''
|
||||
|
||||
if dn.startswith('<GUID=') and dn.endswith('>'): # Active Directory allows looking up objects by putting its GUID in a specially-formatted DN (e.g. '<GUID=7b95f0d5-a3ed-486c-919c-077b8c9731f2>')
|
||||
escaped_dn = dn
|
||||
elif dn.startswith('<WKGUID=') and dn.endswith('>'): # Active Directory allows Binding to Well-Known Objects Using WKGUID in a specially-formatted DN (e.g. <WKGUID=a9d1ca15768811d1aded00c04fd8d5cd,dc=Fabrikam,dc=com>)
|
||||
escaped_dn = dn
|
||||
elif dn.startswith('<SID=') and dn.endswith('>'): # Active Directory allows looking up objects by putting its security identifier (SID) in a specially-formatted DN (e.g. '<SID=S-#-#-##-##########-##########-##########-######>')
|
||||
escaped_dn = dn
|
||||
elif '@' not in dn: # active directory UPN (User Principal Name) consist of an account, the at sign (@) and a domain, or the domain level logn name domain\username
|
||||
for component in parse_dn(dn, escape=True):
|
||||
if decompose:
|
||||
escaped_dn.append((component[0], component[1], component[2]))
|
||||
else:
|
||||
escaped_dn += component[0] + '=' + component[1] + component[2]
|
||||
elif '@' in dn and '=' not in dn and len(dn.split('@')) != 2:
|
||||
raise LDAPInvalidDnError('Active Directory User Principal Name must consist of name@domain')
|
||||
elif '\\' in dn and '=' not in dn and len(dn.split('\\')) != 2:
|
||||
raise LDAPInvalidDnError('Active Directory Domain Level Logon Name must consist of name\\domain')
|
||||
else:
|
||||
escaped_dn = dn
|
||||
|
||||
return escaped_dn
|
||||
|
||||
|
||||
def safe_rdn(dn, decompose=False):
|
||||
"""Returns a list of rdn for the dn, usually there is only one rdn, but it can be more than one when the + sign is used"""
|
||||
escaped_rdn = []
|
||||
one_more = True
|
||||
for component in parse_dn(dn, escape=True):
|
||||
if component[2] == '+' or one_more:
|
||||
if decompose:
|
||||
escaped_rdn.append((component[0], component[1]))
|
||||
else:
|
||||
escaped_rdn.append(component[0] + '=' + component[1])
|
||||
if component[2] == '+':
|
||||
one_more = True
|
||||
else:
|
||||
one_more = False
|
||||
break
|
||||
|
||||
if one_more:
|
||||
raise LDAPInvalidDnError('bad dn ' + str(dn))
|
||||
|
||||
return escaped_rdn
|
||||
|
||||
|
||||
def escape_rdn(rdn):
|
||||
"""
|
||||
Escape rdn characters to prevent injection according to RFC 4514.
|
||||
"""
|
||||
|
||||
# '/' must be handled first or the escape slashes will be escaped!
|
||||
for char in ['\\', ',', '+', '"', '<', '>', ';', '=', '\x00']:
|
||||
rdn = rdn.replace(char, '\\' + char)
|
||||
|
||||
if rdn[0] == '#' or rdn[0] == ' ':
|
||||
rdn = ''.join(('\\', rdn))
|
||||
|
||||
if rdn[-1] == ' ':
|
||||
rdn = ''.join((rdn[:-1], '\\ '))
|
||||
|
||||
return rdn
|
||||
94
venv/Lib/site-packages/ldap3/utils/hashed.py
Normal file
94
venv/Lib/site-packages/ldap3/utils/hashed.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2015.07.16
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .. import HASHED_NONE, HASHED_MD5, HASHED_SALTED_MD5, HASHED_SALTED_SHA, HASHED_SALTED_SHA256, \
|
||||
HASHED_SALTED_SHA384, HASHED_SALTED_SHA512, HASHED_SHA, HASHED_SHA256, HASHED_SHA384, HASHED_SHA512
|
||||
|
||||
import hashlib
|
||||
from os import urandom
|
||||
from base64 import b64encode
|
||||
|
||||
from ..core.exceptions import LDAPInvalidHashAlgorithmError
|
||||
|
||||
# each tuple: (the string to include between braces in the digest, the name of the algorithm to invoke with the new() function)
|
||||
|
||||
algorithms_table = {
|
||||
HASHED_MD5: ('md5', 'MD5'),
|
||||
HASHED_SHA: ('sha', 'SHA1'),
|
||||
HASHED_SHA256: ('sha256', 'SHA256'),
|
||||
HASHED_SHA384: ('sha384', 'SHA384'),
|
||||
HASHED_SHA512: ('sha512', 'SHA512')
|
||||
}
|
||||
|
||||
|
||||
salted_table = {
|
||||
HASHED_SALTED_MD5: ('smd5', HASHED_MD5),
|
||||
HASHED_SALTED_SHA: ('ssha', HASHED_SHA),
|
||||
HASHED_SALTED_SHA256: ('ssha256', HASHED_SHA256),
|
||||
HASHED_SALTED_SHA384: ('ssha384', HASHED_SHA384),
|
||||
HASHED_SALTED_SHA512: ('ssha512', HASHED_SHA512)
|
||||
}
|
||||
|
||||
|
||||
def hashed(algorithm, value, salt=None, raw=False, encoding='utf-8'):
|
||||
if str is not bytes and not isinstance(value, bytes): # Python 3
|
||||
value = value.encode(encoding)
|
||||
|
||||
if algorithm is None or algorithm == HASHED_NONE:
|
||||
return value
|
||||
|
||||
# algorithm name can be already coded in the ldap3 constants or can be any value passed in the 'algorithm' parameter
|
||||
|
||||
if algorithm in algorithms_table:
|
||||
try:
|
||||
digest = hashlib.new(algorithms_table[algorithm][1], value).digest()
|
||||
except ValueError:
|
||||
raise LDAPInvalidHashAlgorithmError('Hash algorithm ' + str(algorithm) + ' not available')
|
||||
|
||||
if raw:
|
||||
return digest
|
||||
return ('{%s}' % algorithms_table[algorithm][0]) + b64encode(digest).decode('ascii')
|
||||
elif algorithm in salted_table:
|
||||
if not salt:
|
||||
salt = urandom(8)
|
||||
digest = hashed(salted_table[algorithm][1], value + salt, raw=True) + salt
|
||||
if raw:
|
||||
return digest
|
||||
return ('{%s}' % salted_table[algorithm][0]) + b64encode(digest).decode('ascii')
|
||||
else:
|
||||
# if an unknown (to the library) algorithm is requested passes the name as the string in braces and as the algorithm name
|
||||
# if salt is present uses it to salt the digest
|
||||
try:
|
||||
if not salt:
|
||||
digest = hashlib.new(algorithm, value).digest()
|
||||
else:
|
||||
digest = hashlib.new(algorithm, value + salt).digest() + salt
|
||||
except ValueError:
|
||||
raise LDAPInvalidHashAlgorithmError('Hash algorithm ' + str(algorithm) + ' not available')
|
||||
|
||||
if raw:
|
||||
return digest
|
||||
return ('{%s}' % algorithm) + b64encode(digest).decode('ascii')
|
||||
|
||||
216
venv/Lib/site-packages/ldap3/utils/log.py
Normal file
216
venv/Lib/site-packages/ldap3/utils/log.py
Normal file
@@ -0,0 +1,216 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2015.05.01
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from logging import getLogger, DEBUG
|
||||
from copy import deepcopy
|
||||
from pprint import pformat
|
||||
from ..protocol.rfc4511 import LDAPMessage
|
||||
|
||||
# logging levels
|
||||
OFF = 0
|
||||
ERROR = 10
|
||||
BASIC = 20
|
||||
PROTOCOL = 30
|
||||
NETWORK = 40
|
||||
EXTENDED = 50
|
||||
|
||||
_sensitive_lines = ('simple', 'credentials', 'serversaslcreds') # must be a tuple, not a list, lowercase
|
||||
_sensitive_args = ('simple', 'password', 'sasl_credentials', 'saslcreds', 'server_creds')
|
||||
_sensitive_attrs = ('userpassword', 'unicodepwd')
|
||||
|
||||
_hide_sensitive_data = None
|
||||
|
||||
DETAIL_LEVELS = [OFF, ERROR, BASIC, PROTOCOL, NETWORK, EXTENDED]
|
||||
|
||||
_max_line_length = 4096
|
||||
_logging_level = 0
|
||||
_detail_level = 0
|
||||
_logging_encoding = 'ascii'
|
||||
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError: # NullHandler not present in Python < 2.7
|
||||
from logging import Handler
|
||||
|
||||
class NullHandler(Handler):
|
||||
def handle(self, record):
|
||||
pass
|
||||
|
||||
def emit(self, record):
|
||||
pass
|
||||
|
||||
def createLock(self):
|
||||
self.lock = None
|
||||
|
||||
|
||||
def _strip_sensitive_data_from_dict(d):
|
||||
if not isinstance(d, dict):
|
||||
return d
|
||||
|
||||
try:
|
||||
d = deepcopy(d)
|
||||
except Exception: # if deepcopy goes wrong gives up and returns the dict unchanged
|
||||
return d
|
||||
for k in d.keys():
|
||||
if isinstance(d[k], dict):
|
||||
d[k] = _strip_sensitive_data_from_dict(d[k])
|
||||
elif k.lower() in _sensitive_args and d[k]:
|
||||
d[k] = '<stripped %d characters of sensitive data>' % len(d[k])
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def get_detail_level_name(level_name):
|
||||
if level_name == OFF:
|
||||
return 'OFF'
|
||||
elif level_name == ERROR:
|
||||
return 'ERROR'
|
||||
elif level_name == BASIC:
|
||||
return 'BASIC'
|
||||
elif level_name == PROTOCOL:
|
||||
return 'PROTOCOL'
|
||||
elif level_name == NETWORK:
|
||||
return 'NETWORK'
|
||||
elif level_name == EXTENDED:
|
||||
return 'EXTENDED'
|
||||
raise ValueError('unknown detail level')
|
||||
|
||||
|
||||
def log(detail, message, *args):
|
||||
if detail <= _detail_level:
|
||||
if _hide_sensitive_data:
|
||||
args = tuple([_strip_sensitive_data_from_dict(arg) if isinstance(arg, dict) else arg for arg in args])
|
||||
|
||||
if str is not bytes: # Python 3
|
||||
encoded_message = (get_detail_level_name(detail) + ':' + message % args).encode(_logging_encoding, 'backslashreplace')
|
||||
encoded_message = encoded_message.decode()
|
||||
else:
|
||||
try:
|
||||
encoded_message = (get_detail_level_name(detail) + ':' + message % args).encode(_logging_encoding, 'replace')
|
||||
except Exception:
|
||||
encoded_message = (get_detail_level_name(detail) + ':' + message % args).decode(_logging_encoding, 'replace')
|
||||
|
||||
if len(encoded_message) > _max_line_length:
|
||||
logger.log(_logging_level, encoded_message[:_max_line_length] + ' <removed %d remaining bytes in this log line>' % (len(encoded_message) - _max_line_length, ))
|
||||
else:
|
||||
logger.log(_logging_level, encoded_message)
|
||||
|
||||
|
||||
def log_enabled(detail):
|
||||
if detail <= _detail_level:
|
||||
if logger.isEnabledFor(_logging_level):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def set_library_log_hide_sensitive_data(hide=True):
|
||||
global _hide_sensitive_data
|
||||
if hide:
|
||||
_hide_sensitive_data = True
|
||||
else:
|
||||
_hide_sensitive_data = False
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'hide sensitive data set to ' + str(_hide_sensitive_data))
|
||||
|
||||
|
||||
def get_library_log_hide_sensitive_data():
|
||||
return True if _hide_sensitive_data else False
|
||||
|
||||
|
||||
def set_library_log_activation_level(logging_level):
|
||||
if isinstance(logging_level, int):
|
||||
global _logging_level
|
||||
_logging_level = logging_level
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid library log activation level <%s> ', logging_level)
|
||||
raise ValueError('invalid library log activation level')
|
||||
|
||||
|
||||
def get_library_log_activation_lavel():
|
||||
return _logging_level
|
||||
|
||||
|
||||
def set_library_log_max_line_length(length):
|
||||
if isinstance(length, int):
|
||||
global _max_line_length
|
||||
_max_line_length = length
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'invalid log max line length <%s> ', length)
|
||||
raise ValueError('invalid library log max line length')
|
||||
|
||||
|
||||
def get_library_log_max_line_length():
|
||||
return _max_line_length
|
||||
|
||||
|
||||
def set_library_log_detail_level(detail):
|
||||
if detail in DETAIL_LEVELS:
|
||||
global _detail_level
|
||||
_detail_level = detail
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'detail level set to ' + get_detail_level_name(_detail_level))
|
||||
else:
|
||||
if log_enabled(ERROR):
|
||||
log(ERROR, 'unable to set log detail level to <%s>', detail)
|
||||
raise ValueError('invalid library log detail level')
|
||||
|
||||
|
||||
def get_library_log_detail_level():
|
||||
return _detail_level
|
||||
|
||||
|
||||
def format_ldap_message(message, prefix):
|
||||
if isinstance(message, LDAPMessage):
|
||||
try: # pyasn1 prettyprint raises exception in version 0.4.3
|
||||
formatted = message.prettyPrint().split('\n') # pyasn1 pretty print
|
||||
except Exception as e:
|
||||
formatted = ['pyasn1 exception', str(e)]
|
||||
else:
|
||||
formatted = pformat(message).split('\n')
|
||||
|
||||
prefixed = ''
|
||||
for line in formatted:
|
||||
if line:
|
||||
if _hide_sensitive_data and line.strip().lower().startswith(_sensitive_lines): # _sensitive_lines is a tuple. startswith() method checks each tuple element
|
||||
tag, _, data = line.partition('=')
|
||||
if data.startswith("b'") and data.endswith("'") or data.startswith('b"') and data.endswith('"'):
|
||||
prefixed += '\n' + prefix + tag + '=<stripped %d characters of sensitive data>' % (len(data) - 3, )
|
||||
else:
|
||||
prefixed += '\n' + prefix + tag + '=<stripped %d characters of sensitive data>' % len(data)
|
||||
else:
|
||||
prefixed += '\n' + prefix + line
|
||||
return prefixed
|
||||
|
||||
# sets a logger for the library with NullHandler. It can be used by the application with its own logging configuration
|
||||
logger = getLogger('ldap3')
|
||||
logger.addHandler(NullHandler())
|
||||
|
||||
# sets defaults for the library logging
|
||||
set_library_log_activation_level(DEBUG)
|
||||
set_library_log_detail_level(OFF)
|
||||
set_library_log_hide_sensitive_data(True)
|
||||
505
venv/Lib/site-packages/ldap3/utils/ntlm.py
Normal file
505
venv/Lib/site-packages/ldap3/utils/ntlm.py
Normal file
@@ -0,0 +1,505 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2015.04.02
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# NTLMv2 authentication as per [MS-NLMP] (https://msdn.microsoft.com/en-us/library/cc236621.aspx)
|
||||
|
||||
from struct import pack, unpack
|
||||
from platform import system, version
|
||||
from socket import gethostname
|
||||
from time import time
|
||||
import hmac
|
||||
import hashlib
|
||||
import binascii
|
||||
from os import urandom
|
||||
|
||||
try:
|
||||
from locale import getpreferredencoding
|
||||
oem_encoding = getpreferredencoding()
|
||||
except Exception:
|
||||
oem_encoding = 'utf-8'
|
||||
|
||||
from ..protocol.formatters.formatters import format_ad_timestamp
|
||||
|
||||
NTLM_SIGNATURE = b'NTLMSSP\x00'
|
||||
NTLM_MESSAGE_TYPE_NTLM_NEGOTIATE = 1
|
||||
NTLM_MESSAGE_TYPE_NTLM_CHALLENGE = 2
|
||||
NTLM_MESSAGE_TYPE_NTLM_AUTHENTICATE = 3
|
||||
|
||||
FLAG_NEGOTIATE_56 = 31 # W
|
||||
FLAG_NEGOTIATE_KEY_EXCH = 30 # V
|
||||
FLAG_NEGOTIATE_128 = 29 # U
|
||||
FLAG_NEGOTIATE_VERSION = 25 # T
|
||||
FLAG_NEGOTIATE_TARGET_INFO = 23 # S
|
||||
FLAG_REQUEST_NOT_NT_SESSION_KEY = 22 # R
|
||||
FLAG_NEGOTIATE_IDENTIFY = 20 # Q
|
||||
FLAG_NEGOTIATE_EXTENDED_SESSIONSECURITY = 19 # P
|
||||
FLAG_TARGET_TYPE_SERVER = 17 # O
|
||||
FLAG_TARGET_TYPE_DOMAIN = 16 # N
|
||||
FLAG_NEGOTIATE_ALWAYS_SIGN = 15 # M
|
||||
FLAG_NEGOTIATE_OEM_WORKSTATION_SUPPLIED = 13 # L
|
||||
FLAG_NEGOTIATE_OEM_DOMAIN_SUPPLIED = 12 # K
|
||||
FLAG_NEGOTIATE_ANONYMOUS = 11 # J
|
||||
FLAG_NEGOTIATE_NTLM = 9 # H
|
||||
FLAG_NEGOTIATE_LM_KEY = 7 # G
|
||||
FLAG_NEGOTIATE_DATAGRAM = 6 # F
|
||||
FLAG_NEGOTIATE_SEAL = 5 # E
|
||||
FLAG_NEGOTIATE_SIGN = 4 # D
|
||||
FLAG_REQUEST_TARGET = 2 # C
|
||||
FLAG_NEGOTIATE_OEM = 1 # B
|
||||
FLAG_NEGOTIATE_UNICODE = 0 # A
|
||||
|
||||
FLAG_TYPES = [FLAG_NEGOTIATE_56,
|
||||
FLAG_NEGOTIATE_KEY_EXCH,
|
||||
FLAG_NEGOTIATE_128,
|
||||
FLAG_NEGOTIATE_VERSION,
|
||||
FLAG_NEGOTIATE_TARGET_INFO,
|
||||
FLAG_REQUEST_NOT_NT_SESSION_KEY,
|
||||
FLAG_NEGOTIATE_IDENTIFY,
|
||||
FLAG_NEGOTIATE_EXTENDED_SESSIONSECURITY,
|
||||
FLAG_TARGET_TYPE_SERVER,
|
||||
FLAG_TARGET_TYPE_DOMAIN,
|
||||
FLAG_NEGOTIATE_ALWAYS_SIGN,
|
||||
FLAG_NEGOTIATE_OEM_WORKSTATION_SUPPLIED,
|
||||
FLAG_NEGOTIATE_OEM_DOMAIN_SUPPLIED,
|
||||
FLAG_NEGOTIATE_ANONYMOUS,
|
||||
FLAG_NEGOTIATE_NTLM,
|
||||
FLAG_NEGOTIATE_LM_KEY,
|
||||
FLAG_NEGOTIATE_DATAGRAM,
|
||||
FLAG_NEGOTIATE_SEAL,
|
||||
FLAG_NEGOTIATE_SIGN,
|
||||
FLAG_REQUEST_TARGET,
|
||||
FLAG_NEGOTIATE_OEM,
|
||||
FLAG_NEGOTIATE_UNICODE]
|
||||
|
||||
AV_END_OF_LIST = 0
|
||||
AV_NETBIOS_COMPUTER_NAME = 1
|
||||
AV_NETBIOS_DOMAIN_NAME = 2
|
||||
AV_DNS_COMPUTER_NAME = 3
|
||||
AV_DNS_DOMAIN_NAME = 4
|
||||
AV_DNS_TREE_NAME = 5
|
||||
AV_FLAGS = 6
|
||||
AV_TIMESTAMP = 7
|
||||
AV_SINGLE_HOST_DATA = 8
|
||||
AV_TARGET_NAME = 9
|
||||
AV_CHANNEL_BINDINGS = 10
|
||||
|
||||
AV_TYPES = [AV_END_OF_LIST,
|
||||
AV_NETBIOS_COMPUTER_NAME,
|
||||
AV_NETBIOS_DOMAIN_NAME,
|
||||
AV_DNS_COMPUTER_NAME,
|
||||
AV_DNS_DOMAIN_NAME,
|
||||
AV_DNS_TREE_NAME,
|
||||
AV_FLAGS,
|
||||
AV_TIMESTAMP,
|
||||
AV_SINGLE_HOST_DATA,
|
||||
AV_TARGET_NAME,
|
||||
AV_CHANNEL_BINDINGS]
|
||||
|
||||
AV_FLAG_CONSTRAINED = 0
|
||||
AV_FLAG_INTEGRITY = 1
|
||||
AV_FLAG_TARGET_SPN_UNTRUSTED = 2
|
||||
|
||||
AV_FLAG_TYPES = [AV_FLAG_CONSTRAINED,
|
||||
AV_FLAG_INTEGRITY,
|
||||
AV_FLAG_TARGET_SPN_UNTRUSTED]
|
||||
|
||||
|
||||
def pack_windows_version(debug=False):
|
||||
if debug:
|
||||
if system().lower() == 'windows':
|
||||
try:
|
||||
major_release, minor_release, build = version().split('.')
|
||||
major_release = int(major_release)
|
||||
minor_release = int(minor_release)
|
||||
build = int(build)
|
||||
except Exception:
|
||||
major_release = 5
|
||||
minor_release = 1
|
||||
build = 2600
|
||||
else:
|
||||
major_release = 5
|
||||
minor_release = 1
|
||||
build = 2600
|
||||
else:
|
||||
major_release = 0
|
||||
minor_release = 0
|
||||
build = 0
|
||||
|
||||
return pack('<B', major_release) + \
|
||||
pack('<B', minor_release) + \
|
||||
pack('<H', build) + \
|
||||
pack('<B', 0) + \
|
||||
pack('<B', 0) + \
|
||||
pack('<B', 0) + \
|
||||
pack('<B', 15)
|
||||
|
||||
|
||||
def unpack_windows_version(version_message):
|
||||
if len(version_message) != 8:
|
||||
raise ValueError('version field must be 8 bytes long')
|
||||
|
||||
if str is bytes: # Python 2
|
||||
return (unpack('<B', version_message[0])[0],
|
||||
unpack('<B', version_message[1])[0],
|
||||
unpack('<H', version_message[2:4])[0],
|
||||
unpack('<B', version_message[7])[0])
|
||||
else: # Python 3
|
||||
return (int(version_message[0]),
|
||||
int(version_message[1]),
|
||||
int(unpack('<H', version_message[2:4])[0]),
|
||||
int(version_message[7]))
|
||||
|
||||
|
||||
class NtlmClient(object):
|
||||
def __init__(self, domain, user_name, password):
|
||||
self.client_config_flags = 0
|
||||
self.exported_session_key = None
|
||||
self.negotiated_flags = None
|
||||
self.user_name = user_name
|
||||
self.user_domain = domain
|
||||
self.no_lm_response_ntlm_v1 = None
|
||||
self.client_blocked = False
|
||||
self.client_block_exceptions = []
|
||||
self.client_require_128_bit_encryption = None
|
||||
self.max_life_time = None
|
||||
self.client_signing_key = None
|
||||
self.client_sealing_key = None
|
||||
self.sequence_number = None
|
||||
self.server_sealing_key = None
|
||||
self.server_signing_key = None
|
||||
self.integrity = False
|
||||
self.replay_detect = False
|
||||
self.sequence_detect = False
|
||||
self.confidentiality = False
|
||||
self.datagram = False
|
||||
self.identity = False
|
||||
self.client_supplied_target_name = None
|
||||
self.client_channel_binding_unhashed = None
|
||||
self.unverified_target_name = None
|
||||
self._password = password
|
||||
self.server_challenge = None
|
||||
self.server_target_name = None
|
||||
self.server_target_info = None
|
||||
self.server_version = None
|
||||
self.server_av_netbios_computer_name = None
|
||||
self.server_av_netbios_domain_name = None
|
||||
self.server_av_dns_computer_name = None
|
||||
self.server_av_dns_domain_name = None
|
||||
self.server_av_dns_forest_name = None
|
||||
self.server_av_target_name = None
|
||||
self.server_av_flags = None
|
||||
self.server_av_timestamp = None
|
||||
self.server_av_single_host_data = None
|
||||
self.server_av_channel_bindings = None
|
||||
self.server_av_flag_constrained = None
|
||||
self.server_av_flag_integrity = None
|
||||
self.server_av_flag_target_spn_untrusted = None
|
||||
self.current_encoding = None
|
||||
self.client_challenge = None
|
||||
self.server_target_info_raw = None
|
||||
|
||||
def get_client_flag(self, flag):
|
||||
if not self.client_config_flags:
|
||||
return False
|
||||
|
||||
if flag in FLAG_TYPES:
|
||||
return True if self.client_config_flags & (1 << flag) else False
|
||||
|
||||
raise ValueError('invalid flag')
|
||||
|
||||
def get_negotiated_flag(self, flag):
|
||||
if not self.negotiated_flags:
|
||||
return False
|
||||
|
||||
if flag not in FLAG_TYPES:
|
||||
raise ValueError('invalid flag')
|
||||
|
||||
return True if self.negotiated_flags & (1 << flag) else False
|
||||
|
||||
def get_server_av_flag(self, flag):
|
||||
if not self.server_av_flags:
|
||||
return False
|
||||
|
||||
if flag not in AV_FLAG_TYPES:
|
||||
raise ValueError('invalid AV flag')
|
||||
|
||||
return True if self.server_av_flags & (1 << flag) else False
|
||||
|
||||
def set_client_flag(self, flags):
|
||||
if type(flags) == int:
|
||||
flags = [flags]
|
||||
for flag in flags:
|
||||
if flag in FLAG_TYPES:
|
||||
self.client_config_flags |= (1 << flag)
|
||||
else:
|
||||
raise ValueError('invalid flag')
|
||||
|
||||
def reset_client_flags(self):
|
||||
self.client_config_flags = 0
|
||||
|
||||
def unset_client_flag(self, flags):
|
||||
if type(flags) == int:
|
||||
flags = [flags]
|
||||
for flag in flags:
|
||||
if flag in FLAG_TYPES:
|
||||
self.client_config_flags &= ~(1 << flag)
|
||||
else:
|
||||
raise ValueError('invalid flag')
|
||||
|
||||
def create_negotiate_message(self):
|
||||
"""
|
||||
Microsoft MS-NLMP 2.2.1.1
|
||||
"""
|
||||
self.reset_client_flags()
|
||||
self.set_client_flag([FLAG_REQUEST_TARGET,
|
||||
FLAG_NEGOTIATE_56,
|
||||
FLAG_NEGOTIATE_128,
|
||||
FLAG_NEGOTIATE_NTLM,
|
||||
FLAG_NEGOTIATE_ALWAYS_SIGN,
|
||||
FLAG_NEGOTIATE_OEM,
|
||||
FLAG_NEGOTIATE_UNICODE,
|
||||
FLAG_NEGOTIATE_EXTENDED_SESSIONSECURITY])
|
||||
|
||||
message = NTLM_SIGNATURE # 8 bytes
|
||||
message += pack('<I', NTLM_MESSAGE_TYPE_NTLM_NEGOTIATE) # 4 bytes
|
||||
message += pack('<I', self.client_config_flags) # 4 bytes
|
||||
message += self.pack_field('', 40) # domain name field # 8 bytes
|
||||
if self.get_client_flag(FLAG_NEGOTIATE_VERSION): # version 8 bytes - used for debug in ntlm
|
||||
message += pack_windows_version(True)
|
||||
else:
|
||||
message += pack_windows_version(False)
|
||||
return message
|
||||
|
||||
def parse_challenge_message(self, message):
|
||||
"""
|
||||
Microsoft MS-NLMP 2.2.1.2
|
||||
"""
|
||||
if len(message) < 56: # minimum size of challenge message
|
||||
return False
|
||||
|
||||
if message[0:8] != NTLM_SIGNATURE: # NTLM signature - 8 bytes
|
||||
return False
|
||||
|
||||
if int(unpack('<I', message[8:12])[0]) != NTLM_MESSAGE_TYPE_NTLM_CHALLENGE: # type of message - 4 bytes
|
||||
return False
|
||||
|
||||
target_name_len, _, target_name_offset = self.unpack_field(message[12:20]) # targetNameFields - 8 bytes
|
||||
self.negotiated_flags = unpack('<I', message[20:24])[0] # negotiated flags - 4 bytes
|
||||
self.current_encoding = 'utf-16-le' if self.get_negotiated_flag(
|
||||
FLAG_NEGOTIATE_UNICODE) else oem_encoding # set encoding
|
||||
|
||||
self.server_challenge = message[24:32] # server challenge - 8 bytes
|
||||
target_info_len, _, target_info_offset = self.unpack_field(message[40:48]) # targetInfoFields - 8 bytes
|
||||
self.server_version = unpack_windows_version(message[48:56])
|
||||
if self.get_negotiated_flag(FLAG_REQUEST_TARGET) and target_name_len:
|
||||
self.server_target_name = message[target_name_offset: target_name_offset + target_name_len].decode(
|
||||
self.current_encoding)
|
||||
if self.get_negotiated_flag(FLAG_NEGOTIATE_TARGET_INFO) and target_info_len:
|
||||
self.server_target_info_raw = message[target_info_offset: target_info_offset + target_info_len]
|
||||
self.server_target_info = self.unpack_av_info(self.server_target_info_raw)
|
||||
for attribute, value in self.server_target_info:
|
||||
if attribute == AV_NETBIOS_COMPUTER_NAME:
|
||||
self.server_av_netbios_computer_name = value.decode('utf-16-le') # always unicode
|
||||
elif attribute == AV_NETBIOS_DOMAIN_NAME:
|
||||
self.server_av_netbios_domain_name = value.decode('utf-16-le') # always unicode
|
||||
elif attribute == AV_DNS_COMPUTER_NAME:
|
||||
self.server_av_dns_computer_name = value.decode('utf-16-le') # always unicode
|
||||
elif attribute == AV_DNS_DOMAIN_NAME:
|
||||
self.server_av_dns_domain_name = value.decode('utf-16-le') # always unicode
|
||||
elif attribute == AV_DNS_TREE_NAME:
|
||||
self.server_av_dns_forest_name = value.decode('utf-16-le') # always unicode
|
||||
elif attribute == AV_FLAGS:
|
||||
if self.get_server_av_flag(AV_FLAG_CONSTRAINED):
|
||||
self.server_av_flag_constrained = True
|
||||
if self.get_server_av_flag(AV_FLAG_INTEGRITY):
|
||||
self.server_av_flag_integrity = True
|
||||
if self.get_server_av_flag(AV_FLAG_TARGET_SPN_UNTRUSTED):
|
||||
self.server_av_flag_target_spn_untrusted = True
|
||||
elif attribute == AV_TIMESTAMP:
|
||||
self.server_av_timestamp = format_ad_timestamp(unpack('<Q', value)[0])
|
||||
elif attribute == AV_SINGLE_HOST_DATA:
|
||||
self.server_av_single_host_data = value
|
||||
elif attribute == AV_TARGET_NAME:
|
||||
self.server_av_target_name = value.decode('utf-16-le') # always unicode
|
||||
elif attribute == AV_CHANNEL_BINDINGS:
|
||||
self.server_av_channel_bindings = value
|
||||
else:
|
||||
raise ValueError('unknown AV type')
|
||||
|
||||
def create_authenticate_message(self):
|
||||
"""
|
||||
Microsoft MS-NLMP 2.2.1.3
|
||||
"""
|
||||
# 3.1.5.2
|
||||
if not self.client_config_flags and not self.negotiated_flags:
|
||||
return False
|
||||
|
||||
# 3.1.5.2
|
||||
if self.get_client_flag(FLAG_NEGOTIATE_128) and not self.get_negotiated_flag(FLAG_NEGOTIATE_128):
|
||||
return False
|
||||
|
||||
# 3.1.5.2
|
||||
if (not self.server_av_netbios_computer_name or not self.server_av_netbios_domain_name) and self.server_av_flag_integrity:
|
||||
return False
|
||||
|
||||
message = NTLM_SIGNATURE # 8 bytes
|
||||
message += pack('<I', NTLM_MESSAGE_TYPE_NTLM_AUTHENTICATE) # 4 bytes
|
||||
pos = 88 # payload starts at 88
|
||||
# 3.1.5.2
|
||||
if self.server_target_info:
|
||||
lm_challenge_response = b''
|
||||
else:
|
||||
# computed LmChallengeResponse - todo
|
||||
lm_challenge_response = b''
|
||||
|
||||
message += self.pack_field(lm_challenge_response, pos) # LmChallengeResponseField field # 8 bytes
|
||||
pos += len(lm_challenge_response)
|
||||
nt_challenge_response = self.compute_nt_response()
|
||||
message += self.pack_field(nt_challenge_response, pos) # NtChallengeResponseField field # 8 bytes
|
||||
pos += len(nt_challenge_response)
|
||||
domain_name = self.user_domain.encode(self.current_encoding)
|
||||
message += self.pack_field(domain_name, pos) # DomainNameField field # 8 bytes
|
||||
pos += len(domain_name)
|
||||
user_name = self.user_name.encode(self.current_encoding)
|
||||
message += self.pack_field(user_name, pos) # UserNameField field # 8 bytes
|
||||
pos += len(user_name)
|
||||
if self.get_negotiated_flag(FLAG_NEGOTIATE_OEM_WORKSTATION_SUPPLIED) or self.get_negotiated_flag(
|
||||
FLAG_NEGOTIATE_VERSION):
|
||||
workstation = gethostname().encode(self.current_encoding)
|
||||
else:
|
||||
workstation = b''
|
||||
message += self.pack_field(workstation, pos) # empty WorkstationField field # 8 bytes
|
||||
pos += len(workstation)
|
||||
encrypted_random_session_key = b''
|
||||
message += self.pack_field(encrypted_random_session_key, pos) # EncryptedRandomSessionKeyField field # 8 bytes
|
||||
pos += len(encrypted_random_session_key)
|
||||
message += pack('<I', self.negotiated_flags) # negotiated flags - 4 bytes
|
||||
if self.get_negotiated_flag(FLAG_NEGOTIATE_VERSION):
|
||||
message += pack_windows_version(True) # windows version - 8 bytes
|
||||
else:
|
||||
message += pack_windows_version() # empty windows version - 8 bytes
|
||||
message += pack('<Q', 0) # mic
|
||||
message += pack('<Q', 0) # mic - total of 16 bytes
|
||||
# payload starts at 88
|
||||
message += lm_challenge_response
|
||||
message += nt_challenge_response
|
||||
message += domain_name
|
||||
message += user_name
|
||||
message += workstation
|
||||
message += encrypted_random_session_key
|
||||
|
||||
return message
|
||||
|
||||
@staticmethod
|
||||
def pack_field(value, offset):
|
||||
return pack('<HHI', len(value), len(value), offset)
|
||||
|
||||
@staticmethod
|
||||
def unpack_field(field_message):
|
||||
if len(field_message) != 8:
|
||||
raise ValueError('ntlm field must be 8 bytes long')
|
||||
return unpack('<H', field_message[0:2])[0], \
|
||||
unpack('<H', field_message[2:4])[0], \
|
||||
unpack('<I', field_message[4:8])[0]
|
||||
|
||||
@staticmethod
|
||||
def unpack_av_info(info):
|
||||
if info:
|
||||
avs = list()
|
||||
done = False
|
||||
pos = 0
|
||||
while not done:
|
||||
av_type = unpack('<H', info[pos: pos + 2])[0]
|
||||
if av_type not in AV_TYPES:
|
||||
raise ValueError('unknown AV type')
|
||||
av_len = unpack('<H', info[pos + 2: pos + 4])[0]
|
||||
av_value = info[pos + 4: pos + 4 + av_len]
|
||||
pos += av_len + 4
|
||||
if av_type == AV_END_OF_LIST:
|
||||
done = True
|
||||
else:
|
||||
avs.append((av_type, av_value))
|
||||
else:
|
||||
return list()
|
||||
|
||||
return avs
|
||||
|
||||
@staticmethod
|
||||
def pack_av_info(avs):
|
||||
# avs is a list of tuples, each tuple is made of av_type and av_value
|
||||
info = b''
|
||||
for av_type, av_value in avs:
|
||||
if av_type(0) == AV_END_OF_LIST:
|
||||
continue
|
||||
info += pack('<H', av_type)
|
||||
info += pack('<H', len(av_value))
|
||||
info += av_value
|
||||
|
||||
# add AV_END_OF_LIST
|
||||
info += pack('<H', AV_END_OF_LIST)
|
||||
info += pack('<H', 0)
|
||||
|
||||
return info
|
||||
|
||||
@staticmethod
|
||||
def pack_windows_timestamp():
|
||||
return pack('<Q', (int(time()) + 11644473600) * 10000000)
|
||||
|
||||
def compute_nt_response(self):
|
||||
if not self.user_name and not self._password: # anonymous authentication
|
||||
return b''
|
||||
|
||||
self.client_challenge = urandom(8)
|
||||
temp = b''
|
||||
temp += pack('<B', 1) # ResponseVersion - 1 byte
|
||||
temp += pack('<B', 1) # HiResponseVersion - 1 byte
|
||||
temp += pack('<H', 0) # Z(2)
|
||||
temp += pack('<I', 0) # Z(4) - total Z(6)
|
||||
temp += self.pack_windows_timestamp() # time - 8 bytes
|
||||
temp += self.client_challenge # random client challenge - 8 bytes
|
||||
temp += pack('<I', 0) # Z(4)
|
||||
temp += self.server_target_info_raw
|
||||
temp += pack('<I', 0) # Z(4)
|
||||
response_key_nt = self.ntowf_v2()
|
||||
nt_proof_str = hmac.new(response_key_nt, self.server_challenge + temp, digestmod=hashlib.md5).digest()
|
||||
nt_challenge_response = nt_proof_str + temp
|
||||
return nt_challenge_response
|
||||
|
||||
def ntowf_v2(self):
|
||||
passparts = self._password.split(':')
|
||||
if len(passparts) == 2 and len(passparts[0]) == 32 and len(passparts[1]) == 32:
|
||||
# The specified password is an LM:NTLM hash
|
||||
password_digest = binascii.unhexlify(passparts[1])
|
||||
else:
|
||||
try:
|
||||
password_digest = hashlib.new('MD4', self._password.encode('utf-16-le')).digest()
|
||||
except ValueError as e:
|
||||
try:
|
||||
from Crypto.Hash import MD4 # try with the Crypto library if present
|
||||
password_digest = MD4.new(self._password.encode('utf-16-le')).digest()
|
||||
except ImportError:
|
||||
raise e # raise original exception
|
||||
|
||||
return hmac.new(password_digest, (self.user_name.upper() + self.user_domain).encode('utf-16-le'), digestmod=hashlib.md5).digest()
|
||||
130
venv/Lib/site-packages/ldap3/utils/ordDict.py
Normal file
130
venv/Lib/site-packages/ldap3/utils/ordDict.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# Copyright (c) 2009 Raymond Hettinger
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
# OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
# used only in Python 2.6
|
||||
|
||||
from UserDict import DictMixin
|
||||
|
||||
|
||||
class OrderedDict(dict, DictMixin):
|
||||
|
||||
def __init__(self, *args, **kwds):
|
||||
if len(args) > 1:
|
||||
raise TypeError('expected at most 1 arguments, got %d' % len(args))
|
||||
try:
|
||||
self.__end
|
||||
except AttributeError:
|
||||
self.clear()
|
||||
self.update(*args, **kwds)
|
||||
|
||||
def clear(self):
|
||||
self.__end = end = []
|
||||
end += [None, end, end] # sentinel node for doubly linked list
|
||||
self.__map = {} # key --> [key, prev, next]
|
||||
dict.clear(self)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key not in self:
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
curr[2] = end[1] = self.__map[key] = [key, curr, end]
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
dict.__delitem__(self, key)
|
||||
key, prev, next = self.__map.pop(key)
|
||||
prev[2] = next
|
||||
next[1] = prev
|
||||
|
||||
def __iter__(self):
|
||||
end = self.__end
|
||||
curr = end[2]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[2]
|
||||
|
||||
def __reversed__(self):
|
||||
end = self.__end
|
||||
curr = end[1]
|
||||
while curr is not end:
|
||||
yield curr[0]
|
||||
curr = curr[1]
|
||||
|
||||
def popitem(self, last=True):
|
||||
if not self:
|
||||
raise KeyError('dictionary is empty')
|
||||
if last:
|
||||
key = reversed(self).next()
|
||||
else:
|
||||
key = iter(self).next()
|
||||
value = self.pop(key)
|
||||
return key, value
|
||||
|
||||
def __reduce__(self):
|
||||
items = [[k, self[k]] for k in self]
|
||||
tmp = self.__map, self.__end
|
||||
del self.__map, self.__end
|
||||
inst_dict = vars(self).copy()
|
||||
self.__map, self.__end = tmp
|
||||
if inst_dict:
|
||||
return (self.__class__, (items,), inst_dict)
|
||||
return self.__class__, (items,)
|
||||
|
||||
def keys(self):
|
||||
return list(self)
|
||||
|
||||
setdefault = DictMixin.setdefault
|
||||
update = DictMixin.update
|
||||
pop = DictMixin.pop
|
||||
values = DictMixin.values
|
||||
items = DictMixin.items
|
||||
iterkeys = DictMixin.iterkeys
|
||||
itervalues = DictMixin.itervalues
|
||||
iteritems = DictMixin.iteritems
|
||||
|
||||
def __repr__(self):
|
||||
if not self:
|
||||
return '%s()' % (self.__class__.__name__,)
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items())
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(self)
|
||||
|
||||
@classmethod
|
||||
def fromkeys(cls, iterable, value=None):
|
||||
d = cls()
|
||||
for key in iterable:
|
||||
d[key] = value
|
||||
return d
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, OrderedDict):
|
||||
if len(self) != len(other):
|
||||
return False
|
||||
for p, q in zip(self.items(), other.items()):
|
||||
if p != q:
|
||||
return False
|
||||
return True
|
||||
return dict.__eq__(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self == other
|
||||
37
venv/Lib/site-packages/ldap3/utils/port_validators.py
Normal file
37
venv/Lib/site-packages/ldap3/utils/port_validators.py
Normal file
@@ -0,0 +1,37 @@
|
||||
""" Some helper functions for validation of ports and lists of ports. """
|
||||
|
||||
|
||||
def check_port(port):
|
||||
""" Check if a port is valid. Return an error message indicating what is invalid if something isn't valid. """
|
||||
if isinstance(port, int):
|
||||
if port not in range(0, 65535):
|
||||
return 'Source port must in range from 0 to 65535'
|
||||
else:
|
||||
return 'Source port must be an integer'
|
||||
return None
|
||||
|
||||
|
||||
def check_port_and_port_list(port, port_list):
|
||||
""" Take in a port and a port list and check that at most one is non-null. Additionally check that if either
|
||||
is non-null, it is valid.
|
||||
Return an error message indicating what is invalid if something isn't valid.
|
||||
"""
|
||||
if port is not None and port_list is not None:
|
||||
return 'Cannot specify both a source port and a source port list'
|
||||
elif port is not None:
|
||||
if isinstance(port, int):
|
||||
if port not in range(0, 65535):
|
||||
return 'Source port must in range from 0 to 65535'
|
||||
else:
|
||||
return 'Source port must be an integer'
|
||||
elif port_list is not None:
|
||||
try:
|
||||
_ = iter(port_list)
|
||||
except TypeError:
|
||||
return 'Source port list must be an iterable {}'.format(port_list)
|
||||
|
||||
for candidate_port in port_list:
|
||||
err = check_port(candidate_port)
|
||||
if err:
|
||||
return err
|
||||
return None
|
||||
51
venv/Lib/site-packages/ldap3/utils/repr.py
Normal file
51
venv/Lib/site-packages/ldap3/utils/repr.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2015.07.09
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2015 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
from binascii import hexlify
|
||||
|
||||
from .. import STRING_TYPES
|
||||
|
||||
try:
|
||||
from sys import stdout
|
||||
repr_encoding = stdout.encoding # get the encoding of the stdout for printing (repr)
|
||||
if not repr_encoding:
|
||||
repr_encoding = 'ascii' # default
|
||||
except Exception:
|
||||
repr_encoding = 'ascii' # default
|
||||
|
||||
|
||||
def to_stdout_encoding(value):
|
||||
if not isinstance(value, STRING_TYPES):
|
||||
value = str(value)
|
||||
|
||||
if str is bytes: # Python 2
|
||||
try:
|
||||
return value.encode(repr_encoding, 'backslashreplace')
|
||||
except UnicodeDecodeError: # Python 2.6
|
||||
return hexlify(value)
|
||||
else: # Python 3
|
||||
try:
|
||||
return value.encode(repr_encoding, errors='backslashreplace').decode(repr_encoding, errors='backslashreplace')
|
||||
except UnicodeDecodeError:
|
||||
return hexlify(value)
|
||||
133
venv/Lib/site-packages/ldap3/utils/tls_backport.py
Normal file
133
venv/Lib/site-packages/ldap3/utils/tls_backport.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.10.05
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
import re
|
||||
from ..utils.log import log, log_enabled, NETWORK
|
||||
|
||||
try:
|
||||
from backports.ssl_match_hostname import match_hostname, CertificateError
|
||||
except ImportError:
|
||||
class CertificateError(ValueError): # fix for Python 2, code from Python 3.5 standard library
|
||||
pass
|
||||
|
||||
|
||||
def _dnsname_match(dn, hostname, max_wildcards=1):
|
||||
"""Backported from Python 3.4.3 standard library
|
||||
|
||||
Matching according to RFC 6125, section 6.4.3
|
||||
|
||||
http://tools.ietf.org/html/rfc6125#section-6.4.3
|
||||
"""
|
||||
if log_enabled(NETWORK):
|
||||
log(NETWORK, "matching dn %s with hostname %s", dn, hostname)
|
||||
pats = []
|
||||
if not dn:
|
||||
return False
|
||||
|
||||
pieces = dn.split(r'.')
|
||||
leftmost = pieces[0]
|
||||
remainder = pieces[1:]
|
||||
|
||||
wildcards = leftmost.count('*')
|
||||
if wildcards > max_wildcards:
|
||||
# Issue #17980: avoid denials of service by refusing more
|
||||
# than one wildcard per fragment. A survey of established
|
||||
# policy among SSL implementations showed it to be a
|
||||
# reasonable choice.
|
||||
raise CertificateError(
|
||||
"too many wildcards in certificate DNS name: " + repr(dn))
|
||||
|
||||
# speed up common case w/o wildcards
|
||||
if not wildcards:
|
||||
return dn.lower() == hostname.lower()
|
||||
|
||||
# RFC 6125, section 6.4.3, subitem 1.
|
||||
# The client SHOULD NOT attempt to match a presented identifier in which
|
||||
# the wildcard character comprises a label other than the left-most label.
|
||||
if leftmost == '*':
|
||||
# When '*' is a fragment by itself, it matches a non-empty dotless
|
||||
# fragment.
|
||||
pats.append('[^.]+')
|
||||
elif leftmost.startswith('xn--') or hostname.startswith('xn--'):
|
||||
# RFC 6125, section 6.4.3, subitem 3.
|
||||
# The client SHOULD NOT attempt to match a presented identifier
|
||||
# where the wildcard character is embedded within an A-label or
|
||||
# U-label of an internationalized domain name.
|
||||
pats.append(re.escape(leftmost))
|
||||
else:
|
||||
# Otherwise, '*' matches any dotless string, e.g. www*
|
||||
pats.append(re.escape(leftmost).replace(r'\*', '[^.]*'))
|
||||
|
||||
# add the remaining fragments, ignore any wildcards
|
||||
for frag in remainder:
|
||||
pats.append(re.escape(frag))
|
||||
|
||||
pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
|
||||
return pat.match(hostname)
|
||||
|
||||
|
||||
def match_hostname(cert, hostname):
|
||||
"""Backported from Python 3.4.3 standard library.
|
||||
|
||||
Verify that *cert* (in decoded format as returned by
|
||||
SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125
|
||||
rules are followed, but IP addresses are not accepted for *hostname*.
|
||||
|
||||
CertificateError is raised on failure. On success, the function
|
||||
returns nothing.
|
||||
"""
|
||||
|
||||
if not cert:
|
||||
raise ValueError("empty or no certificate, match_hostname needs a "
|
||||
"SSL socket or SSL context with either "
|
||||
"CERT_OPTIONAL or CERT_REQUIRED")
|
||||
dnsnames = []
|
||||
san = cert.get('subjectAltName', ())
|
||||
for key, value in san:
|
||||
if key == 'DNS':
|
||||
if _dnsname_match(value, hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if not dnsnames:
|
||||
# The subject is only checked when there is no dNSName entry
|
||||
# in subjectAltName
|
||||
for sub in cert.get('subject', ()):
|
||||
for key, value in sub:
|
||||
# XXX according to RFC 2818, the most specific Common Name
|
||||
# must be used.
|
||||
if key == 'commonName':
|
||||
if _dnsname_match(value, hostname):
|
||||
return
|
||||
dnsnames.append(value)
|
||||
if len(dnsnames) > 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match either of %s"
|
||||
% (hostname, ', '.join(map(repr, dnsnames))))
|
||||
elif len(dnsnames) == 1:
|
||||
raise CertificateError("hostname %r "
|
||||
"doesn't match %r"
|
||||
% (hostname, dnsnames[0]))
|
||||
else:
|
||||
raise CertificateError("no appropriate commonName or "
|
||||
"subjectAltName fields were found")
|
||||
118
venv/Lib/site-packages/ldap3/utils/uri.py
Normal file
118
venv/Lib/site-packages/ldap3/utils/uri.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""
|
||||
"""
|
||||
|
||||
# Created on 2014.09.08
|
||||
#
|
||||
# Author: Giovanni Cannata
|
||||
#
|
||||
# Copyright 2014 - 2020 Giovanni Cannata
|
||||
#
|
||||
# This file is part of ldap3.
|
||||
#
|
||||
# ldap3 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.
|
||||
#
|
||||
# ldap3 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 ldap3 in the COPYING and COPYING.LESSER files.
|
||||
# If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
try:
|
||||
from urllib.parse import unquote # Python3
|
||||
except ImportError:
|
||||
from urllib import unquote # Python 2
|
||||
|
||||
from .. import SUBTREE, BASE, LEVEL
|
||||
|
||||
|
||||
def parse_uri(uri):
|
||||
"""
|
||||
Decode LDAP URI as specified in RFC 4516 relaxing specifications
|
||||
permitting 'ldaps' as scheme for ssl-ldap
|
||||
"""
|
||||
|
||||
# ldapurl = scheme COLON SLASH SLASH [host [COLON port]]
|
||||
# [SLASH dn [QUESTION [attributes]
|
||||
# [QUESTION [scope] [QUESTION [filter]
|
||||
# [QUESTION extensions]]]]]
|
||||
# ; <host> and <port> are defined
|
||||
# ; in Sections 3.2.2 and 3.2.3
|
||||
# ; of [RFC3986].
|
||||
# ; <filter> is from Section 3 of
|
||||
# ; [RFC4515], subject to the
|
||||
# ; provisions of the
|
||||
# ; "Percent-Encoding" section
|
||||
# ; below.
|
||||
#
|
||||
# scheme = "ldap" / "ldaps" <== not RFC4516 compliant (original is 'scheme = "ldap"')
|
||||
# dn = distinguishedName ; From Section 3 of [RFC4514],
|
||||
# ; subject to the provisions of
|
||||
# ; the "Percent-Encoding"
|
||||
# ; section below.
|
||||
#
|
||||
# attributes = attrdesc *(COMMA attrdesc)
|
||||
# attrdesc = selector *(COMMA selector)
|
||||
# selector = attributeSelector ; From Section 4.5.1 of
|
||||
# ; [RFC4511], subject to the
|
||||
# ; provisions of the
|
||||
# ; "Percent-Encoding" section
|
||||
# ; below.
|
||||
#
|
||||
# scope = "base" / "one" / "sub"
|
||||
# extensions = extension *(COMMA extension)
|
||||
# extension = [EXCLAMATION] extype [EQUALS exvalue]
|
||||
# extype = oid ; From section 1.4 of [RFC4512].
|
||||
#
|
||||
# exvalue = LDAPString ; From section 4.1.2 of
|
||||
# ; [RFC4511], subject to the
|
||||
# ; provisions of the
|
||||
# ; "Percent-Encoding" section
|
||||
# ; below.
|
||||
#
|
||||
# EXCLAMATION = %x21 ; exclamation mark ("!")
|
||||
# SLASH = %x2F ; forward slash ("/")
|
||||
# COLON = %x3A ; colon (":")
|
||||
# QUESTION = %x3F ; question mark ("?")
|
||||
|
||||
uri_components = dict()
|
||||
parts = unquote(uri).split('?') # encoding defaults to utf-8 in Python 3
|
||||
scheme, sep, remain = parts[0].partition('://')
|
||||
if sep != '://' or scheme not in ['ldap', 'ldaps']:
|
||||
return None
|
||||
|
||||
address, _, uri_components['base'] = remain.partition('/')
|
||||
|
||||
uri_components['ssl'] = True if scheme == 'ldaps' else False
|
||||
uri_components['host'], sep, uri_components['port'] = address.partition(':')
|
||||
if sep != ':':
|
||||
if uri_components['ssl']:
|
||||
uri_components['port'] = 636
|
||||
else:
|
||||
uri_components['port'] = None
|
||||
else:
|
||||
if not uri_components['port'].isdigit() or not (0 < int(uri_components['port']) < 65536):
|
||||
return None
|
||||
else:
|
||||
uri_components['port'] = int(uri_components['port'])
|
||||
|
||||
uri_components['attributes'] = parts[1].split(',') if len(parts) > 1 and parts[1] else None
|
||||
uri_components['scope'] = parts[2] if len(parts) > 2 else None
|
||||
if uri_components['scope'] == 'base':
|
||||
uri_components['scope'] = BASE
|
||||
elif uri_components['scope'] == 'sub':
|
||||
uri_components['scope'] = SUBTREE
|
||||
elif uri_components['scope'] == 'one':
|
||||
uri_components['scope'] = LEVEL
|
||||
elif uri_components['scope']:
|
||||
return None
|
||||
|
||||
uri_components['filter'] = parts[3] if len(parts) > 3 else None
|
||||
uri_components['extensions'] = parts[4].split(',') if len(parts) > 4 else None
|
||||
|
||||
return uri_components
|
||||
Reference in New Issue
Block a user