codekingpro/portable-devtools
114k
1"""
2"""
3
4# Created on 2013.09.11
5#
6# Author: Giovanni Cannata
7#
8# Copyright 2013 - 2020 Giovanni Cannata
9#
10# This file is part of ldap3.
11#
12# ldap3 is free software: you can redistribute it and/or modify
13# it under the terms of the GNU Lesser General Public License as published
14# by the Free Software Foundation, either version 3 of the License, or
15# (at your option) any later version.
16#
17# ldap3 is distributed in the hope that it will be useful,
18# but WITHOUT ANY WARRANTY; without even the implied warranty of
19# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20# GNU Lesser General Public License for more details.
21#
22# You should have received a copy of the GNU Lesser General Public License
23# along with ldap3 in the COPYING and COPYING.LESSER files.
24# If not, see <http://www.gnu.org/licenses/>.
25
26import stringprep
27from unicodedata import ucd_3_2_0 as unicode32
28from os import urandom
29from binascii import hexlify
30
31from ... import SASL
32from ...core.results import RESULT_AUTH_METHOD_NOT_SUPPORTED
33from ...core.exceptions import LDAPSASLPrepError, LDAPPasswordIsMandatoryError
34
35
36def sasl_prep(data):
37 """
38 implement SASLPrep profile as per RFC4013:
39 it defines the "SASLprep" profile of the "stringprep" algorithm [StringPrep].
40 The profile is designed for use in Simple Authentication and Security
41 Layer ([SASL]) mechanisms, such as [PLAIN], [CRAM-MD5], and
42 [DIGEST-MD5]. It may be applicable where simple user names and
43 passwords are used. This profile is not intended for use in
44 preparing identity strings that are not simple user names (e.g.,
45 email addresses, domain names, distinguished names), or where
46 identity or password strings that are not character data, or require
47 different handling (e.g., case folding).
48 """
49
50 # mapping
51 prepared_data = ''
52 for c in data:
53 if stringprep.in_table_c12(c):
54 # non-ASCII space characters [StringPrep, C.1.2] that can be mapped to SPACE (U+0020)
55 prepared_data += ' '
56 elif stringprep.in_table_b1(c):
57 # the "commonly mapped to nothing" characters [StringPrep, B.1] that can be mapped to nothing.
58 pass
59 else:
60 prepared_data += c
61
62 # normalizing
63 # This profile specifies using Unicode normalization form KC
64 # The repertoire is Unicode 3.2 as per RFC 4013 (2)
65
66 prepared_data = unicode32.normalize('NFKC', prepared_data)
67
68 if not prepared_data:
69 raise LDAPSASLPrepError('SASLprep error: unable to normalize string')
70
71 # prohibit
72 for c in prepared_data:
73 if stringprep.in_table_c12(c):
74 # Non-ASCII space characters [StringPrep, C.1.2]
75 raise LDAPSASLPrepError('SASLprep error: non-ASCII space character present')
76 elif stringprep.in_table_c21(c):
77 # ASCII control characters [StringPrep, C.2.1]
78 raise LDAPSASLPrepError('SASLprep error: ASCII control character present')
79 elif stringprep.in_table_c22(c):
80 # Non-ASCII control characters [StringPrep, C.2.2]
81 raise LDAPSASLPrepError('SASLprep error: non-ASCII control character present')
82 elif stringprep.in_table_c3(c):
83 # Private Use characters [StringPrep, C.3]
84 raise LDAPSASLPrepError('SASLprep error: private character present')
85 elif stringprep.in_table_c4(c):
86 # Non-character code points [StringPrep, C.4]
87 raise LDAPSASLPrepError('SASLprep error: non-character code point present')
88 elif stringprep.in_table_c5(c):
89 # Surrogate code points [StringPrep, C.5]
90 raise LDAPSASLPrepError('SASLprep error: surrogate code point present')
91 elif stringprep.in_table_c6(c):
92 # Inappropriate for plain text characters [StringPrep, C.6]
93 raise LDAPSASLPrepError('SASLprep error: inappropriate for plain text character present')
94 elif stringprep.in_table_c7(c):
95 # Inappropriate for canonical representation characters [StringPrep, C.7]
96 raise LDAPSASLPrepError('SASLprep error: inappropriate for canonical representation character present')
97 elif stringprep.in_table_c8(c):
98 # Change display properties or deprecated characters [StringPrep, C.8]
99 raise LDAPSASLPrepError('SASLprep error: change display property or deprecated character present')
100 elif stringprep.in_table_c9(c):
101 # Tagging characters [StringPrep, C.9]
102 raise LDAPSASLPrepError('SASLprep error: tagging character present')
103
104 # check bidi
105 # if a string contains any r_and_al_cat character, the string MUST NOT contain any l_cat character.
106 flag_r_and_al_cat = False
107 flag_l_cat = False
108 for c in prepared_data:
109 if stringprep.in_table_d1(c):
110 flag_r_and_al_cat = True
111 elif stringprep.in_table_d2(c):
112 flag_l_cat = True
113
114 if flag_r_and_al_cat and flag_l_cat:
115 raise LDAPSASLPrepError('SASLprep error: string cannot contain (R or AL) and L bidirectional chars')
116
117 # If a string contains any r_and_al_cat character, a r_and_al_cat character MUST be the first character of the string
118 # and a r_and_al_cat character MUST be the last character of the string.
119 if flag_r_and_al_cat and not stringprep.in_table_d1(prepared_data[0]) and not stringprep.in_table_d2(prepared_data[-1]):
120 raise LDAPSASLPrepError('r_and_al_cat character present, must be first and last character of the string')
121
122 return prepared_data
123
124
125def validate_simple_password(password, accept_empty=False):
126 """
127 validate simple password as per RFC4013 using sasl_prep:
128 """
129
130 if accept_empty and not password:
131 return password
132 elif not password:
133 raise LDAPPasswordIsMandatoryError("simple password can't be empty")
134
135 if not isinstance(password, bytes): # bytes are returned raw, as per RFC (4.2)
136 password = sasl_prep(password)
137 if not isinstance(password, bytes):
138 password = password.encode('utf-8')
139
140 return password
141
142
143def abort_sasl_negotiation(connection, controls):
144 from ...operation.bind import bind_operation
145
146 request = bind_operation(connection.version, SASL, None, None, '', None)
147 response = connection.post_send_single_response(connection.send('bindRequest', request, controls))
148 if connection.strategy.sync:
149 result = connection.result
150 else:
151 result = connection.get_response(response)[0][0]
152
153 return True if result['result'] == RESULT_AUTH_METHOD_NOT_SUPPORTED else False
154
155
156def send_sasl_negotiation(connection, controls, payload):
157 from ...operation.bind import bind_operation
158
159 request = bind_operation(connection.version, SASL, None, None, connection.sasl_mechanism, payload)
160 response = connection.post_send_single_response(connection.send('bindRequest', request, controls))
161
162 if connection.strategy.sync:
163 result = connection.result
164 else:
165 _, result = connection.get_response(response)
166
167 return result
168
169
170def random_hex_string(size):
171 return str(hexlify(urandom(size)).decode('ascii')) # str fix for Python 2
172 