/src/freeradius-server/src/lib/server/password.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * @file src/lib/server/password.c |
19 | | * @brief Password normalisation functions |
20 | | * |
21 | | * @copyright 2019 The FreeRADIUS server project |
22 | | * @copyright 2019 Arran Cudbard-Bell \<a.cudbardb@freeradius.org\> |
23 | | */ |
24 | | RCSID("$Id: c1c61498cd5a90d713b7cb5fb8e43cc76c3b78e4 $") |
25 | | |
26 | | #include <freeradius-devel/server/password.h> |
27 | | |
28 | | #include <freeradius-devel/util/atexit.h> |
29 | | #include <freeradius-devel/util/base64.h> |
30 | | #include <freeradius-devel/util/base16.h> |
31 | | #include <freeradius-devel/util/md4.h> |
32 | | #include <freeradius-devel/util/md5.h> |
33 | | #include <freeradius-devel/util/misc.h> |
34 | | #include <freeradius-devel/util/sha1.h> |
35 | | #include <freeradius-devel/util/value.h> |
36 | | |
37 | | #include <freeradius-devel/protocol/freeradius/freeradius.internal.password.h> |
38 | | |
39 | | #ifdef HAVE_OPENSSL_EVP_H |
40 | | # include <freeradius-devel/tls/openssl_user_macros.h> |
41 | | # include <openssl/evp.h> |
42 | | # include <openssl/sha.h> |
43 | | #endif |
44 | | |
45 | | typedef enum { |
46 | | PASSWORD_CLEARTEXT = 0, //!< Variable length. |
47 | | PASSWORD_HASH, //!< Fixed length. |
48 | | PASSWORD_HASH_SALTED, //!< Fixed length hash, variable length salt. |
49 | | PASSWORD_HASH_VARIABLE //!< Variable length everything. |
50 | | } password_type_t; |
51 | | |
52 | | /** Apply preprocessing logic to a password value |
53 | | * |
54 | | * @param[in] ctx to allocate returned value in. |
55 | | * @param[in] request currently being processed. |
56 | | * @param[in] in Pair containing the password to process. |
57 | | * @ |
58 | | */ |
59 | | typedef fr_pair_t *(*password_preprocess_t)(TALLOC_CTX *ctx, request_t *request, fr_pair_t *in); |
60 | | |
61 | | /** Password information |
62 | | * |
63 | | */ |
64 | | typedef struct { |
65 | | password_type_t type; //!< What type of password value this is. |
66 | | fr_dict_attr_t const **da; //!< Dictionary attribute representing this type of password. |
67 | | password_preprocess_t func; //!< Preprocessing function. |
68 | | size_t min_hash_len; //!< Minimum length of the decoded string if normifying. |
69 | | ///< If 0, will be ignored. |
70 | | size_t max_hash_len; //!< Maximum length of the decoded string if normifying. |
71 | | ///< If 0, will be ignored. |
72 | | bool no_normify; //!< Don't attempt to normalise the contents of this |
73 | | ///< attribute using the hex/base64 decoders. |
74 | | bool always_allow; //!< Always allow processing of this attribute, irrespective |
75 | | ///< of what the caller says. |
76 | | } password_info_t; |
77 | | |
78 | | static fr_dict_t const *dict_freeradius = NULL; |
79 | | static fr_dict_t const *dict_radius = NULL; |
80 | | |
81 | | static fr_dict_attr_t const *attr_cleartext; |
82 | | static fr_dict_attr_t const *attr_with_header; |
83 | | static fr_dict_attr_t const *attr_root; |
84 | | |
85 | | static fr_dict_attr_t const *attr_md5; |
86 | | static fr_dict_attr_t const *attr_smd5; |
87 | | static fr_dict_attr_t const *attr_crypt; |
88 | | |
89 | | static fr_dict_attr_t const *attr_sha1; |
90 | | static fr_dict_attr_t const *attr_ssha1; |
91 | | |
92 | | static fr_dict_attr_t const *attr_sha2; |
93 | | static fr_dict_attr_t const *attr_sha2_224; |
94 | | static fr_dict_attr_t const *attr_sha2_256; |
95 | | static fr_dict_attr_t const *attr_sha2_384; |
96 | | static fr_dict_attr_t const *attr_sha2_512; |
97 | | |
98 | | static fr_dict_attr_t const *attr_ssha2_224; |
99 | | static fr_dict_attr_t const *attr_ssha2_256; |
100 | | static fr_dict_attr_t const *attr_ssha2_384; |
101 | | static fr_dict_attr_t const *attr_ssha2_512; |
102 | | |
103 | | static fr_dict_attr_t const *attr_sha3; |
104 | | static fr_dict_attr_t const *attr_sha3_224; |
105 | | static fr_dict_attr_t const *attr_sha3_256; |
106 | | static fr_dict_attr_t const *attr_sha3_384; |
107 | | static fr_dict_attr_t const *attr_sha3_512; |
108 | | |
109 | | static fr_dict_attr_t const *attr_ssha3_224; |
110 | | static fr_dict_attr_t const *attr_ssha3_256; |
111 | | static fr_dict_attr_t const *attr_ssha3_384; |
112 | | static fr_dict_attr_t const *attr_ssha3_512; |
113 | | |
114 | | static fr_dict_attr_t const *attr_pbkdf2; |
115 | | static fr_dict_attr_t const *attr_pbkdf2_sha1; |
116 | | static fr_dict_attr_t const *attr_pbkdf2_sha256; |
117 | | static fr_dict_attr_t const *attr_pbkdf2_sha512; |
118 | | static fr_dict_attr_t const *attr_pbkdf2_sha256_legacy; |
119 | | static fr_dict_attr_t const *attr_lm; |
120 | | static fr_dict_attr_t const *attr_nt; |
121 | | static fr_dict_attr_t const *attr_ns_mta_md5; |
122 | | |
123 | | static fr_dict_attr_t const *attr_psk; |
124 | | |
125 | | static fr_dict_attr_t const *attr_user; |
126 | | |
127 | | extern fr_dict_autoload_t password_dict[]; |
128 | | fr_dict_autoload_t password_dict[] = { |
129 | | { .out = &dict_freeradius, .proto = "freeradius" }, |
130 | | { .out = &dict_radius, .proto = "radius" }, |
131 | | DICT_AUTOLOAD_TERMINATOR |
132 | | }; |
133 | | |
134 | | extern fr_dict_attr_autoload_t password_dict_attr[]; |
135 | | fr_dict_attr_autoload_t password_dict_attr[] = { |
136 | | { .out = &attr_cleartext, .name = "Password.Cleartext", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, |
137 | | { .out = &attr_with_header, .name = "Password.With-Header", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, |
138 | | { .out = &attr_root, .name = "Password", .type = FR_TYPE_TLV, .dict = &dict_freeradius }, |
139 | | |
140 | | { .out = &attr_md5, .name = "Password.MD5", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
141 | | { .out = &attr_smd5, .name = "Password.SMD5", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
142 | | { .out = &attr_crypt, .name = "Password.Crypt", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, |
143 | | { .out = &attr_sha1, .name = "Password.SHA1", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
144 | | { .out = &attr_ssha1, .name = "Password.SSHA1", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
145 | | |
146 | | { .out = &attr_sha2, .name = "Password.SHA2", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
147 | | { .out = &attr_sha2_224, .name = "Password.SHA2-224", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
148 | | { .out = &attr_sha2_256, .name = "Password.SHA2-256", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
149 | | { .out = &attr_sha2_384, .name = "Password.SHA2-384", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
150 | | { .out = &attr_sha2_512, .name = "Password.SHA2-512", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
151 | | |
152 | | { .out = &attr_ssha2_224, .name = "Password.SSHA2-224", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
153 | | { .out = &attr_ssha2_256, .name = "Password.SSHA2-256", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
154 | | { .out = &attr_ssha2_384, .name = "Password.SSHA2-384", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
155 | | { .out = &attr_ssha2_512, .name = "Password.SSHA2-512", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
156 | | |
157 | | { .out = &attr_sha3, .name = "Password.SHA3", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
158 | | { .out = &attr_sha3_224, .name = "Password.SHA3-224", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
159 | | { .out = &attr_sha3_256, .name = "Password.SHA3-256", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
160 | | { .out = &attr_sha3_384, .name = "Password.SHA3-384", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
161 | | { .out = &attr_sha3_512, .name = "Password.SHA3-512", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
162 | | |
163 | | { .out = &attr_ssha3_224, .name = "Password.SSHA3-224", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
164 | | { .out = &attr_ssha3_256, .name = "Password.SSHA3-256", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
165 | | { .out = &attr_ssha3_384, .name = "Password.SSHA3-384", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
166 | | { .out = &attr_ssha3_512, .name = "Password.SSHA3-512", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
167 | | |
168 | | { .out = &attr_pbkdf2, .name = "Password.PBKDF2", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
169 | | { .out = &attr_pbkdf2_sha1, .name = "Password.PBKDF2-SHA1", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
170 | | { .out = &attr_pbkdf2_sha256, .name = "Password.PBKDF2-SHA256", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
171 | | { .out = &attr_pbkdf2_sha512, .name = "Password.PBKDF2-SHA512", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
172 | | { .out = &attr_pbkdf2_sha256_legacy, .name = "Password.PBKDF2-SHA256-LEGACY", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
173 | | { .out = &attr_lm, .name = "Password.LM", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
174 | | { .out = &attr_nt, .name = "Password.NT", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
175 | | { .out = &attr_ns_mta_md5, .name = "Password.NS-MTA-MD5", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, |
176 | | |
177 | | { .out = &attr_psk, .name = "Password.PSK", .type = FR_TYPE_OCTETS, .dict = &dict_freeradius }, |
178 | | |
179 | | { .out = &attr_user, .name = "User-Password", .type = FR_TYPE_STRING, .dict = &dict_radius }, |
180 | | |
181 | | DICT_AUTOLOAD_TERMINATOR |
182 | | }; |
183 | | |
184 | | typedef enum { |
185 | | NORMALISED_NOTHING = 0, |
186 | | NORMALISED_B64, |
187 | | NORMALISED_HEX |
188 | | } normalise_t; |
189 | | |
190 | | static fr_table_num_sorted_t const normalise_table[] = { |
191 | | { L("base64"), NORMALISED_B64 }, |
192 | | { L("hex"), NORMALISED_HEX }, |
193 | | { L("nothing"), NORMALISED_NOTHING } |
194 | | }; |
195 | | static size_t normalise_table_len = NUM_ELEMENTS(normalise_table); |
196 | | |
197 | | static fr_table_num_sorted_t const password_type_table[] = { |
198 | | { L("cleartext"), PASSWORD_CLEARTEXT }, |
199 | | { L("hashed"), PASSWORD_HASH }, |
200 | | { L("salted-hash"), PASSWORD_HASH_SALTED }, |
201 | | { L("variable-length-hash"), PASSWORD_HASH_VARIABLE } |
202 | | }; |
203 | | static size_t password_type_table_len = NUM_ELEMENTS(password_type_table); |
204 | | |
205 | | /* |
206 | | * Headers for the Password-with-Header attribute |
207 | | * |
208 | | * @note Header comparison is case insensitive. |
209 | | */ |
210 | | static fr_table_num_sorted_t const password_header_table[] = { |
211 | | { L("{base64_md5}"), FR_MD5 }, |
212 | | { L("{clear}"), FR_CLEARTEXT }, |
213 | | { L("{cleartext}"), FR_CLEARTEXT }, |
214 | | { L("{crypt}"), FR_CRYPT }, |
215 | | { L("{md4}"), FR_NT }, |
216 | | { L("{md5}"), FR_MD5 }, |
217 | | { L("{ns-mta-md5}"), FR_NS_MTA_MD5 }, |
218 | | { L("{nt}"), FR_NT }, |
219 | | { L("{nthash}"), FR_NT }, |
220 | | |
221 | | { L("{pbkdf2-sha1}"), FR_PBKDF2_SHA1 }, |
222 | | { L("{pbkdf2-sha256}"), FR_PBKDF2_SHA256 }, |
223 | | { L("{pbkdf2-sha512}"), FR_PBKDF2_SHA512 }, |
224 | | { L("{pbkdf2_sha256}"), FR_PBKDF2_SHA256_LEGACY }, |
225 | | |
226 | | #ifdef HAVE_OPENSSL_EVP_H |
227 | | { L("{sha224}"), FR_SHA2 }, |
228 | | { L("{sha256}"), FR_SHA2 }, |
229 | | { L("{sha2}"), FR_SHA2 }, |
230 | | { L("{sha384}"), FR_SHA2_384 }, |
231 | | { L("{sha512}"), FR_SHA2_512 }, |
232 | | #endif |
233 | | { L("{sha}"), FR_SHA1 }, |
234 | | { L("{smd5}"), FR_SMD5 }, |
235 | | #ifdef HAVE_OPENSSL_EVP_H |
236 | | { L("{ssha224}"), FR_SSHA2_224 }, |
237 | | { L("{ssha256}"), FR_SSHA2_256 }, |
238 | | { L("{ssha3-224}"), FR_SSHA3_224 }, |
239 | | { L("{ssha3-256}"), FR_SSHA3_256 }, |
240 | | { L("{ssha3-384}"), FR_SSHA3_384 }, |
241 | | { L("{ssha3-512}"), FR_SSHA3_512 }, |
242 | | { L("{ssha384}"), FR_SSHA2_384 }, |
243 | | { L("{ssha512}"), FR_SSHA2_512 }, |
244 | | #endif |
245 | | { L("{ssha}"), FR_SSHA1 }, |
246 | | { L("{x- orcllmv}"), FR_LM }, |
247 | | { L("{x- orclntv}"), FR_NT }, |
248 | | { L("{x-nthash}"), FR_NT }, |
249 | | { L("{x-pbkdf2}"), FR_PBKDF2 }, |
250 | | }; |
251 | | static size_t password_header_table_len = NUM_ELEMENTS(password_header_table); |
252 | | |
253 | | #ifdef HAVE_OPENSSL_EVP_H |
254 | | static fr_pair_t *password_process_sha2(TALLOC_CTX *ctx, request_t *request, fr_pair_t *known_good); |
255 | | static fr_pair_t *password_process_sha3(TALLOC_CTX *ctx, request_t *request, fr_pair_t *known_good); |
256 | | #endif |
257 | | static fr_pair_t *password_process_header(TALLOC_CTX *ctx, request_t *request, fr_pair_t *known_good); |
258 | | |
259 | | /** Metadata for various password attributes |
260 | | * |
261 | | */ |
262 | | static password_info_t password_info[] = { |
263 | | [FR_CLEARTEXT] = { |
264 | | .type = PASSWORD_CLEARTEXT, |
265 | | .da = &attr_cleartext, |
266 | | .no_normify = true |
267 | | }, |
268 | | [FR_CRYPT] = { |
269 | | .type = PASSWORD_HASH, |
270 | | .da = &attr_crypt |
271 | | }, |
272 | | [FR_LM] = { |
273 | | .type = PASSWORD_HASH, |
274 | | .da = &attr_lm, |
275 | | .min_hash_len = MD4_DIGEST_LENGTH |
276 | | }, |
277 | | [FR_MD5] = { |
278 | | .type = PASSWORD_HASH, |
279 | | .da = &attr_md5, |
280 | | .min_hash_len = MD5_DIGEST_LENGTH |
281 | | }, |
282 | | [FR_NS_MTA_MD5] = { |
283 | | .type = PASSWORD_HASH, |
284 | | .da = &attr_ns_mta_md5 |
285 | | }, |
286 | | [FR_NT] = { |
287 | | .type = PASSWORD_HASH, |
288 | | .da = &attr_nt, |
289 | | .min_hash_len = MD4_DIGEST_LENGTH |
290 | | }, |
291 | | [FR_WITH_HEADER] = { |
292 | | .type = PASSWORD_HASH_VARIABLE, |
293 | | .da = &attr_with_header, |
294 | | .func = password_process_header, |
295 | | .always_allow = true |
296 | | }, |
297 | | [FR_PBKDF2] = { |
298 | | .type = PASSWORD_HASH_VARIABLE, |
299 | | .da = &attr_pbkdf2 |
300 | | }, |
301 | | [FR_PBKDF2_SHA1] = { |
302 | | .type = PASSWORD_HASH_VARIABLE, |
303 | | .da = &attr_pbkdf2_sha1 |
304 | | }, |
305 | | [FR_PBKDF2_SHA256] = { |
306 | | .type = PASSWORD_HASH_VARIABLE, |
307 | | .da = &attr_pbkdf2_sha256 |
308 | | }, |
309 | | [FR_PBKDF2_SHA512] = { |
310 | | .type = PASSWORD_HASH_VARIABLE, |
311 | | .da = &attr_pbkdf2_sha512 |
312 | | }, |
313 | | [FR_PBKDF2_SHA256_LEGACY] = { |
314 | | .type = PASSWORD_HASH_VARIABLE, |
315 | | .da = &attr_pbkdf2_sha256_legacy |
316 | | }, |
317 | | [FR_SHA1] = { |
318 | | .type = PASSWORD_HASH, |
319 | | .da = &attr_sha1, |
320 | | .min_hash_len = SHA1_DIGEST_LENGTH |
321 | | }, |
322 | | #ifdef HAVE_OPENSSL_EVP_H |
323 | | [FR_SHA2] = { |
324 | | .type = PASSWORD_HASH_VARIABLE, |
325 | | .da = &attr_sha2, |
326 | | .func = password_process_sha2, |
327 | | .min_hash_len = SHA224_DIGEST_LENGTH, |
328 | | .max_hash_len = SHA512_DIGEST_LENGTH |
329 | | }, |
330 | | [FR_SHA2_224] = { |
331 | | .type = PASSWORD_HASH, |
332 | | .da = &attr_sha2_224, |
333 | | .min_hash_len = SHA224_DIGEST_LENGTH, |
334 | | }, |
335 | | [FR_SHA2_256] = { |
336 | | .type = PASSWORD_HASH, |
337 | | .da = &attr_sha2_256, |
338 | | .min_hash_len = SHA256_DIGEST_LENGTH, |
339 | | }, |
340 | | [FR_SHA2_384] = { |
341 | | .type = PASSWORD_HASH, |
342 | | .da = &attr_sha2_384, |
343 | | .min_hash_len = SHA384_DIGEST_LENGTH, |
344 | | }, |
345 | | [FR_SHA2_512] = { |
346 | | .type = PASSWORD_HASH, |
347 | | .da = &attr_sha2_512, |
348 | | .min_hash_len = SHA512_DIGEST_LENGTH, |
349 | | }, |
350 | | [FR_SHA3] = { |
351 | | .type = PASSWORD_HASH_VARIABLE, |
352 | | .da = &attr_sha3, |
353 | | .func = password_process_sha3, |
354 | | .min_hash_len = SHA224_DIGEST_LENGTH, |
355 | | }, |
356 | | [FR_SHA3_224] = { |
357 | | .type = PASSWORD_HASH, |
358 | | .da = &attr_sha3_224, |
359 | | .min_hash_len = SHA224_DIGEST_LENGTH, |
360 | | }, |
361 | | [FR_SHA3_256] = { |
362 | | .type = PASSWORD_HASH, |
363 | | .da = &attr_sha3_256, |
364 | | .min_hash_len = SHA256_DIGEST_LENGTH, |
365 | | }, |
366 | | [FR_SHA3_384] = { |
367 | | .type = PASSWORD_HASH, |
368 | | .da = &attr_sha3_384, |
369 | | .min_hash_len = SHA384_DIGEST_LENGTH, |
370 | | }, |
371 | | [FR_SHA3_512] = { |
372 | | .type = PASSWORD_HASH, |
373 | | .da = &attr_sha3_512, |
374 | | .min_hash_len = SHA512_DIGEST_LENGTH |
375 | | }, |
376 | | #endif |
377 | | [FR_SMD5] = { |
378 | | .type = PASSWORD_HASH, |
379 | | .da = &attr_smd5, |
380 | | .min_hash_len = MD5_DIGEST_LENGTH |
381 | | }, |
382 | | [FR_SSHA1] = { |
383 | | .type = PASSWORD_HASH_SALTED, |
384 | | .da = &attr_ssha1, |
385 | | .min_hash_len = SHA1_DIGEST_LENGTH |
386 | | }, |
387 | | #ifdef HAVE_OPENSSL_EVP_H |
388 | | [FR_SSHA2_224] = { |
389 | | .type = PASSWORD_HASH_SALTED, |
390 | | .da = &attr_ssha2_224, |
391 | | .min_hash_len = SHA224_DIGEST_LENGTH |
392 | | }, |
393 | | [FR_SSHA2_256] = { |
394 | | .type = PASSWORD_HASH_SALTED, |
395 | | .da = &attr_ssha2_256, |
396 | | .min_hash_len = SHA256_DIGEST_LENGTH |
397 | | }, |
398 | | [FR_SSHA2_384] = { |
399 | | .type = PASSWORD_HASH_SALTED, |
400 | | .da = &attr_ssha2_384, |
401 | | .min_hash_len = SHA384_DIGEST_LENGTH |
402 | | }, |
403 | | [FR_SSHA2_512] = { |
404 | | .type = PASSWORD_HASH_SALTED, |
405 | | .da = &attr_ssha2_512, |
406 | | .min_hash_len = SHA512_DIGEST_LENGTH |
407 | | }, |
408 | | [FR_SSHA3_224] = { |
409 | | .type = PASSWORD_HASH_SALTED, |
410 | | .da = &attr_ssha3_224, |
411 | | .min_hash_len = SHA224_DIGEST_LENGTH, |
412 | | }, |
413 | | [FR_SSHA3_256] = { |
414 | | .type = PASSWORD_HASH_SALTED, |
415 | | .da = &attr_ssha3_256, |
416 | | .min_hash_len = SHA256_DIGEST_LENGTH |
417 | | }, |
418 | | [FR_SSHA3_384] = { |
419 | | .type = PASSWORD_HASH_SALTED, |
420 | | .da = &attr_ssha3_384, |
421 | | .min_hash_len = SHA384_DIGEST_LENGTH |
422 | | }, |
423 | | [FR_SSHA3_512] = { |
424 | | .type = PASSWORD_HASH_SALTED, |
425 | | .da = &attr_ssha3_512, |
426 | | .min_hash_len = SHA512_DIGEST_LENGTH |
427 | | }, |
428 | | #endif |
429 | | |
430 | | [FR_PSK] = { |
431 | | .type = PASSWORD_HASH, |
432 | | .da = &attr_psk, |
433 | | .min_hash_len = 16 |
434 | | }, |
435 | | }; |
436 | | |
437 | 0 | #define MIN_LEN(_info) ((_info)->type == PASSWORD_HASH_SALTED ? ((_info)->min_hash_len + 1) : (_info)->min_hash_len) |
438 | | |
439 | | static ssize_t normify(normalise_t *action, uint8_t *buffer, size_t bufflen, |
440 | | char const *known_good, size_t len, size_t min_len) |
441 | 0 | { |
442 | | /* |
443 | | * Else unknown encoding, or already binary. Leave it. |
444 | | */ |
445 | 0 | if (action) *action = NORMALISED_NOTHING; |
446 | |
|
447 | 0 | if (min_len >= bufflen) return 0; /* paranoia */ |
448 | | |
449 | | /* |
450 | | * Hex encoding. Length is even, and it's greater than |
451 | | * twice the minimum length. |
452 | | */ |
453 | 0 | if (!(len & 0x01) && len >= (2 * min_len)) { |
454 | 0 | ssize_t decoded; |
455 | |
|
456 | 0 | buffer[0] = 0x00; /* clang scan */ |
457 | |
|
458 | 0 | decoded = fr_base16_decode(NULL, &FR_DBUFF_TMP(buffer, bufflen), &FR_SBUFF_IN(known_good, len), true); |
459 | 0 | if (decoded == (ssize_t)(len >> 1)) { |
460 | 0 | if (action) *action = NORMALISED_HEX; |
461 | 0 | return decoded; |
462 | 0 | } |
463 | 0 | } |
464 | | |
465 | | /* |
466 | | * Base 64 encoding. It's at least 4/3 the original size, |
467 | | * and we want to avoid division... |
468 | | */ |
469 | 0 | if ((len * 3) >= ((min_len * 4))) { |
470 | 0 | ssize_t decoded; |
471 | |
|
472 | 0 | decoded = fr_base64_decode(&FR_DBUFF_TMP(buffer, bufflen), &FR_SBUFF_IN(known_good, len), true, true); |
473 | 0 | if (decoded <= 0) return 0; |
474 | 0 | if (decoded >= (ssize_t) min_len) { |
475 | 0 | if (action) *action = NORMALISED_B64; |
476 | 0 | return decoded; |
477 | 0 | } |
478 | 0 | } |
479 | | |
480 | 0 | return 0; |
481 | 0 | } |
482 | | |
483 | | /** Hex or base64 or bin auto-discovery |
484 | | * |
485 | | * Here we try and autodiscover what encoding was used for the password/hash, and |
486 | | * convert it back to binary or plaintext. |
487 | | * |
488 | | * @note Earlier versions used a 0x prefix as a hard indicator that the string was |
489 | | * hex encoded, and would fail if the 0x was present but the string didn't |
490 | | * consist of hexits. The base64 char set is a superset of hex, and it was |
491 | | * observed in the wild, that occasionally base64 encoded data really could |
492 | | * start with 0x. That's why min_len (and decodability) are used as the |
493 | | * only heuristics now. |
494 | | * |
495 | | * @param[in] ctx to allocate new pairs in. |
496 | | * @param[in] request The current request. |
497 | | * @param[in] known_good password to normify. |
498 | | * @return |
499 | | * - NULL if known_good was already normalised, or couldn't be normalised. |
500 | | * - A new normalised password pair. |
501 | | */ |
502 | | static fr_pair_t *password_normify(TALLOC_CTX *ctx, request_t *request, fr_pair_t const *known_good) |
503 | | { |
504 | | uint8_t buffer[256]; |
505 | | ssize_t decoded; |
506 | | fr_pair_t *out; |
507 | | normalise_t normalised; |
508 | | password_info_t *info; |
509 | | size_t min_len; |
510 | | |
511 | | if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL; |
512 | | |
513 | | info = &password_info[known_good->da->attr]; |
514 | | min_len = MIN_LEN(info); |
515 | | if (min_len >= sizeof(buffer)) return NULL; /* paranoia */ |
516 | | |
517 | | switch (known_good->vp_type) { |
518 | | case FR_TYPE_OCTETS: |
519 | | decoded = normify(&normalised, buffer, sizeof(buffer), |
520 | | (char const *)known_good->vp_octets, known_good->vp_length, min_len); |
521 | | break; |
522 | | |
523 | | case FR_TYPE_STRING: |
524 | | decoded = normify(&normalised, buffer, sizeof(buffer), |
525 | | known_good->vp_strvalue, known_good->vp_length, min_len); |
526 | | break; |
527 | | |
528 | | default: |
529 | | return NULL; |
530 | | } |
531 | | |
532 | | if (normalised != NORMALISED_NOTHING) { |
533 | | RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes", |
534 | | known_good->da->name, fr_table_str_by_value(normalise_table, normalised, 0), |
535 | | known_good->vp_length, decoded); |
536 | | MEM(out = fr_pair_afrom_da(ctx, known_good->da)); |
537 | | fr_pair_value_memdup(out, buffer, decoded, known_good->vp_tainted); |
538 | | return out; |
539 | | } |
540 | | |
541 | | /* |
542 | | * Else unknown encoding, or already binary. Leave it. |
543 | | */ |
544 | | return NULL; |
545 | | } |
546 | | |
547 | | #ifdef HAVE_OPENSSL_EVP_H |
548 | | /** Split SHA2 hashes into separate attributes based on their length |
549 | | * |
550 | | * @param[in] ctx to allocate attributes in. |
551 | | * @param[in] request The current request. |
552 | | * @param[in] known_good attribute to split. |
553 | | * @return |
554 | | * - A SHA2 length specific attribute. |
555 | | * - NULL on error. |
556 | | */ |
557 | | static fr_pair_t *password_process_sha2(TALLOC_CTX *ctx, request_t *request, fr_pair_t *known_good) |
558 | 0 | { |
559 | 0 | fr_pair_t *out, *normalised; |
560 | |
|
561 | 0 | switch (known_good->vp_length) { |
562 | 0 | case SHA224_DIGEST_LENGTH: |
563 | 0 | MEM(out = fr_pair_afrom_da(ctx, attr_sha2_224)); |
564 | 0 | fr_pair_value_copy(out, known_good); |
565 | 0 | return out; |
566 | | |
567 | 0 | case SHA256_DIGEST_LENGTH: |
568 | 0 | MEM(out = fr_pair_afrom_da(ctx, attr_sha2_256)); |
569 | 0 | fr_pair_value_copy(out, known_good); |
570 | 0 | return out; |
571 | | |
572 | 0 | case SHA384_DIGEST_LENGTH: |
573 | 0 | MEM(out = fr_pair_afrom_da(ctx, attr_sha2_384)); |
574 | 0 | fr_pair_value_copy(out, known_good); |
575 | 0 | return out; |
576 | | |
577 | 0 | case SHA512_DIGEST_LENGTH: |
578 | 0 | MEM(out = fr_pair_afrom_da(ctx, attr_sha2_512)); |
579 | 0 | fr_pair_value_copy(out, known_good); |
580 | 0 | return out; |
581 | | |
582 | 0 | default: |
583 | 0 | out = password_normify(ctx, request, known_good); |
584 | 0 | if (!out) return NULL; |
585 | | |
586 | 0 | normalised = password_process_sha2(ctx, request, out); |
587 | 0 | TALLOC_FREE(out); |
588 | |
|
589 | 0 | return normalised; |
590 | 0 | } |
591 | 0 | } |
592 | | |
593 | | /** Split SHA3 hashes into separate attributes based on their length |
594 | | * |
595 | | * @param[in] ctx to allocate attributes in. |
596 | | * @param[in] request The current request. |
597 | | * @param[in] known_good attribute to split. |
598 | | * @return |
599 | | * - A SHA3 length specific attribute. |
600 | | * - NULL on error. |
601 | | */ |
602 | | static fr_pair_t *password_process_sha3(TALLOC_CTX *ctx, request_t *request, fr_pair_t *known_good) |
603 | 0 | { |
604 | 0 | fr_pair_t *out, *normalised; |
605 | |
|
606 | 0 | switch (known_good->vp_length) { |
607 | 0 | case SHA224_DIGEST_LENGTH: |
608 | 0 | MEM(out = fr_pair_afrom_da(ctx, attr_sha3_224)); |
609 | 0 | fr_pair_value_copy(out, known_good); |
610 | 0 | return out; |
611 | | |
612 | 0 | case SHA256_DIGEST_LENGTH: |
613 | 0 | MEM(out = fr_pair_afrom_da(ctx, attr_sha3_256)); |
614 | 0 | fr_pair_value_copy(out, known_good); |
615 | 0 | return out; |
616 | | |
617 | 0 | case SHA384_DIGEST_LENGTH: |
618 | 0 | MEM(out = fr_pair_afrom_da(ctx, attr_sha3_384)); |
619 | 0 | fr_pair_value_copy(out, known_good); |
620 | 0 | return out; |
621 | | |
622 | 0 | case SHA512_DIGEST_LENGTH: |
623 | 0 | MEM(out = fr_pair_afrom_da(ctx, attr_sha3_512)); |
624 | 0 | fr_pair_value_copy(out, known_good); |
625 | 0 | return out; |
626 | | |
627 | 0 | default: |
628 | 0 | out = password_normify(ctx, request, known_good); |
629 | 0 | if (!out) return NULL; |
630 | | |
631 | 0 | normalised = password_process_sha3(ctx, request, out); |
632 | 0 | TALLOC_FREE(out); |
633 | |
|
634 | 0 | return normalised; |
635 | 0 | } |
636 | 0 | } |
637 | | #endif |
638 | | |
639 | | /** Convert a Password.With-Header attribute to the correct type |
640 | | * |
641 | | * Attribute may be base64 encoded, in which case it will be decoded |
642 | | * first, then evaluated. |
643 | | * |
644 | | * @note The buffer for octets types\ attributes is extended by one byte |
645 | | * and '\0' terminated, to allow it to be used as a char buff. |
646 | | * |
647 | | * @param[in] ctx to allocate new pairs in. |
648 | | * @param[in] request Current request. |
649 | | * @param[in] known_good Password.With-Header attribute to convert. |
650 | | * @return |
651 | | * - Buffer containing normified value on success. |
652 | | * - NULL on error. |
653 | | */ |
654 | | static fr_pair_t *password_process_header(TALLOC_CTX *ctx, request_t *request, fr_pair_t *known_good) |
655 | | { |
656 | | char const *p, *q, *end; |
657 | | |
658 | | uint8_t n1[256], n2[256]; |
659 | | ssize_t decoded; |
660 | | |
661 | | char header[128]; |
662 | | normalise_t normalised; |
663 | | |
664 | | fr_pair_t *new; |
665 | | fr_dict_attr_t const *def = attr_cleartext; |
666 | | |
667 | | PAIR_VERIFY(known_good); |
668 | | |
669 | | /* |
670 | | * Ensure this is only ever called with a |
671 | | * string type attribute. |
672 | | */ |
673 | | fr_assert(known_good->vp_type == FR_TYPE_STRING); |
674 | | |
675 | | p = known_good->vp_strvalue; |
676 | | end = p + known_good->vp_length; |
677 | | |
678 | | /* |
679 | | * Has a header {...} prefix |
680 | | */ |
681 | | do_header: |
682 | | if ((*p == '{') && (q = memchr(p, '}', end - p))) { |
683 | | size_t hlen; |
684 | | int attr; |
685 | | password_info_t *info; |
686 | | |
687 | | hlen = (q - p) + 1; |
688 | | if (hlen >= sizeof(header)) { |
689 | | REDEBUG("Password header too long. Got %zu bytes must be less than %zu bytes", |
690 | | hlen, sizeof(header)); |
691 | | return NULL; |
692 | | } |
693 | | |
694 | | memcpy(header, p, hlen); |
695 | | header[hlen] = '\0'; |
696 | | |
697 | | attr = fr_table_value_by_substr(password_header_table, header, hlen, -1); |
698 | | if (attr < 0) { |
699 | | /* |
700 | | * header buffer retains { and } |
701 | | */ |
702 | | if (RDEBUG_ENABLED3) { |
703 | | RDEBUG3("Unknown header %s in %pP, re-writing to %s", |
704 | | header, known_good, def->name); |
705 | | } else { |
706 | | RDEBUG2("Unknown header %s in %s, re-writing to %s", |
707 | | header, known_good->da->name, def->name); |
708 | | } |
709 | | p = q + 1; |
710 | | goto bad_header; |
711 | | } |
712 | | |
713 | | p = q + 1; |
714 | | |
715 | | if (!fr_cond_assert((size_t) attr < NUM_ELEMENTS(password_info))) return NULL; |
716 | | info = &password_info[attr]; |
717 | | |
718 | | MEM(new = fr_pair_afrom_da(ctx, *(info->da))); |
719 | | switch ((*(info->da))->type) { |
720 | | case FR_TYPE_OCTETS: |
721 | | fr_pair_value_memdup(new, (uint8_t const *)p, end - p, true); |
722 | | break; |
723 | | |
724 | | case FR_TYPE_STRING: |
725 | | fr_pair_value_bstrndup(new, p, end - p, true); |
726 | | break; |
727 | | |
728 | | default: |
729 | | talloc_free(new); |
730 | | fr_assert_fail(NULL); |
731 | | return NULL; |
732 | | } |
733 | | return new; |
734 | | } |
735 | | |
736 | | #ifdef STATIC_ANALYZER |
737 | | /* |
738 | | * static analyzer isn't smart enough to notice that "normify" clears out n1. |
739 | | */ |
740 | | memset(n1, 0, sizeof(n1)); |
741 | | #endif |
742 | | |
743 | | /* |
744 | | * Doesn't have a header {...} prefix |
745 | | * |
746 | | * See if it's base64 or hex, if it is, decode it and check again! |
747 | | * |
748 | | * We ignore request not to normify, as curly braces aren't |
749 | | * in either of the character sets for the encoding schemes |
750 | | * we're normifying, so there's not the possibility for error |
751 | | * as there is normifying other password hashes. |
752 | | */ |
753 | | decoded = normify(&normalised, n1, sizeof(n1), p, end - p, 4); /* { + <char> + } + <char> */ |
754 | | if (decoded > 0) { |
755 | | if ((n1[0] == '{') && (memchr(n1, '}', decoded) != NULL)) { |
756 | | RDEBUG2("Normalizing %s %s encoding, %zu bytes -> %zu bytes", |
757 | | known_good->da->name, fr_table_str_by_value(normalise_table, normalised, 0), |
758 | | known_good->vp_length, decoded); |
759 | | |
760 | | /* |
761 | | * Password.With-Header is a string attribute. |
762 | | * Even though we're handling binary data, the header |
763 | | * must be \0 terminated. |
764 | | */ |
765 | | memcpy(n2, n1, decoded); |
766 | | p = (char const *)n2; |
767 | | end = p + decoded; |
768 | | goto do_header; |
769 | | } |
770 | | } |
771 | | |
772 | | /* |
773 | | * Rewrite to the default attribute type |
774 | | * currently Password.Cleartext. |
775 | | * |
776 | | * This is usually correct if there's no |
777 | | * header to indicate hash type. |
778 | | */ |
779 | | if (RDEBUG_ENABLED3) { |
780 | | RDEBUG3("No {...} in control.%pP, re-writing to %s", known_good, def->name); |
781 | | } else { |
782 | | RDEBUG2("No {...} in control.%s, re-writing to %s", known_good->da->name, def->name); |
783 | | } |
784 | | |
785 | | bad_header: |
786 | | MEM(new = fr_pair_afrom_da(ctx, def)); |
787 | | fr_pair_value_bstrndup(new, p, end - p, true); |
788 | | |
789 | | return new; |
790 | | } |
791 | | |
792 | | /** Apply any processing and normification |
793 | | * |
794 | | */ |
795 | | static fr_pair_t *password_process(TALLOC_CTX *ctx, request_t *request, fr_pair_t *known_good, bool normify) |
796 | 0 | { |
797 | 0 | password_info_t *info; |
798 | 0 | fr_pair_t *out; |
799 | |
|
800 | 0 | if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL; |
801 | | |
802 | 0 | info = &password_info[known_good->da->attr]; |
803 | 0 | if (info->func) { |
804 | 0 | fr_pair_t *from_func, *from_recurse; |
805 | | |
806 | | /* |
807 | | * Pass our input attribute to a custom preprocessing |
808 | | * function to manipulate it. |
809 | | */ |
810 | 0 | from_func = info->func(ctx, request, known_good); |
811 | 0 | if (!from_func) return NULL; |
812 | | |
813 | | /* |
814 | | * Processing function may have produced a different |
815 | | * password type, recurse to deal with it... |
816 | | */ |
817 | 0 | from_recurse = password_process(ctx, request, from_func, normify); |
818 | | |
819 | | /* |
820 | | * Cleanup any intermediary password attributes created |
821 | | * from running the different normalisation and parsing |
822 | | * operations. |
823 | | */ |
824 | 0 | if (!from_recurse) { |
825 | 0 | if (from_func != known_good) TALLOC_FREE(from_func); |
826 | 0 | return NULL; |
827 | 0 | } |
828 | 0 | if ((from_func != known_good) && (from_recurse != from_func)) TALLOC_FREE(from_func); |
829 | |
|
830 | 0 | return from_recurse; |
831 | 0 | } |
832 | | |
833 | | /* |
834 | | * Only normify if we're told to, and we have more data |
835 | | * than the minimum length. |
836 | | */ |
837 | 0 | if (normify && !info->no_normify && (known_good->vp_length > info->min_hash_len)) { |
838 | 0 | fr_pair_t *from_normify; |
839 | |
|
840 | 0 | from_normify = password_normify(ctx, request, known_good); |
841 | 0 | out = from_normify ? from_normify : known_good; |
842 | 0 | } else { |
843 | 0 | out = known_good; |
844 | 0 | } |
845 | | |
846 | | /* |
847 | | * Sanity checks - Too short |
848 | | */ |
849 | 0 | if (info->min_hash_len && (out->vp_length < MIN_LEN(info))) { |
850 | 0 | if (RDEBUG_ENABLED3) { |
851 | 0 | RWDEBUG3("control.%pP too short, expected %zu bytes, got %zu bytes", |
852 | 0 | out, MIN_LEN(info), out->vp_length); |
853 | 0 | } else { |
854 | 0 | RWDEBUG2("control.%s too short, expected %zu bytes, got %zu bytes", |
855 | 0 | out->da->name, MIN_LEN(info), out->vp_length); |
856 | 0 | } |
857 | 0 | invalid: |
858 | 0 | if (out != known_good) TALLOC_FREE(out); /* Free attribute we won't be returning */ |
859 | 0 | return NULL; |
860 | 0 | } |
861 | | |
862 | | /* |
863 | | * Sanity checks - Too long |
864 | | */ |
865 | 0 | if (info->max_hash_len && (out->vp_length > info->max_hash_len)) { |
866 | 0 | if (RDEBUG_ENABLED3) { |
867 | 0 | RWDEBUG3("control.%pP too long, expected %zu bytes, got %zu bytes", |
868 | 0 | out, info->max_hash_len, out->vp_length); |
869 | 0 | } else { |
870 | 0 | RWDEBUG2("control.%s too long, expected %zu bytes, got %zu bytes", |
871 | 0 | out->da->name, info->max_hash_len, out->vp_length); |
872 | 0 | } |
873 | 0 | goto invalid; |
874 | 0 | } |
875 | | |
876 | | /* |
877 | | * Sanity checks - Hashes are a fixed length |
878 | | */ |
879 | 0 | if ((info->type == PASSWORD_HASH) && (out->vp_length != info->min_hash_len)) { |
880 | |
|
881 | 0 | if (RDEBUG_ENABLED3) { |
882 | 0 | RWDEBUG3("control.%pP incorrect length, expected %zu bytes, got %zu bytes", |
883 | 0 | out, info->min_hash_len, out->vp_length); |
884 | 0 | } else { |
885 | 0 | RWDEBUG2("control.%s incorrect length, expected %zu bytes, got %zu bytes", |
886 | 0 | out->da->name, info->min_hash_len, out->vp_length); |
887 | 0 | } |
888 | 0 | goto invalid; |
889 | 0 | } |
890 | | |
891 | 0 | return out; |
892 | 0 | } |
893 | | |
894 | | /** Find all password attributes in the control list of a request and normalise them |
895 | | * |
896 | | * @param[in] request The current request. |
897 | | * @param[in] normify Apply hex/base64 normalisation to attributes. |
898 | | * @return the number of attributes normalised. |
899 | | */ |
900 | | int password_normalise_and_replace(request_t *request, bool normify) |
901 | 0 | { |
902 | 0 | fr_dcursor_t cursor; |
903 | 0 | int replaced = 0; |
904 | 0 | fr_pair_t *known_good, *new; |
905 | |
|
906 | 0 | for (known_good = fr_pair_dcursor_by_ancestor_init(&cursor, &request->control_pairs, attr_root); |
907 | 0 | known_good; |
908 | 0 | known_good = fr_dcursor_next(&cursor)) { |
909 | 0 | if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return -1; |
910 | | |
911 | | /* |
912 | | * Apply preprocessing steps and normalisation. |
913 | | */ |
914 | 0 | new = password_process(request, request, known_good, normify); |
915 | 0 | if (!new) continue; /* Process next input attribute */ |
916 | | |
917 | | /* |
918 | | * If we didn't do anything to it, we do nothing. |
919 | | */ |
920 | 0 | if (new == known_good) { |
921 | 0 | replaced++; |
922 | 0 | continue; |
923 | 0 | } |
924 | | |
925 | 0 | if (RDEBUG_ENABLED3) { |
926 | 0 | RDEBUG3("Replacing control.%pP with control.%pP", |
927 | 0 | known_good, new); |
928 | |
|
929 | 0 | } else { |
930 | 0 | RDEBUG2("Replacing control.%s with control.%s", |
931 | 0 | known_good->da->name, new->da->name); |
932 | 0 | } |
933 | 0 | fr_dcursor_free_item(&cursor); |
934 | 0 | fr_dcursor_prepend(&cursor, new); |
935 | 0 | replaced++; |
936 | 0 | } |
937 | | |
938 | 0 | return replaced; |
939 | 0 | } |
940 | | |
941 | | static fr_pair_t *password_normalise_and_recheck(TALLOC_CTX *ctx, request_t *request, |
942 | | fr_dict_attr_t const *allowed_attrs[], size_t allowed_attrs_len, |
943 | | bool normify, fr_pair_t *const known_good) |
944 | 0 | { |
945 | 0 | fr_pair_t *new; |
946 | 0 | size_t j; |
947 | |
|
948 | 0 | if (!fr_cond_assert(known_good->da->attr < NUM_ELEMENTS(password_info))) return NULL; |
949 | | |
950 | | /* |
951 | | * Apply preprocessing steps and normalisation. |
952 | | */ |
953 | 0 | new = password_process(ctx, request, known_good, normify); |
954 | 0 | if (!new) return NULL; |
955 | | |
956 | | /* |
957 | | * If new != known_good, then we need |
958 | | * to check what was produced is still |
959 | | * acceptable. |
960 | | */ |
961 | 0 | if (new->da != known_good->da) { |
962 | 0 | for (j = 0; j < allowed_attrs_len; j++) if (allowed_attrs[j] == new->da) return new; |
963 | | |
964 | | /* |
965 | | * New attribute not in our allowed list |
966 | | */ |
967 | 0 | TALLOC_FREE(new); /* da didn't match, treat as ephemeral */ |
968 | 0 | return NULL; /* Process next input attribute */ |
969 | 0 | } |
970 | | |
971 | | /* |
972 | | * Return attribute for processing |
973 | | */ |
974 | 0 | return new; |
975 | 0 | } |
976 | | |
977 | | /** Find a "known good" password in the control list of a request |
978 | | * |
979 | | * Searches for a "known good" password attribute, and applies any processing |
980 | | * and normification operations to it, returning a new normalised fr_pair_t. |
981 | | * |
982 | | * The ctx passed in should be freed when the caller is done with the returned |
983 | | * fr_pair_t, or alternatively, a persistent ctx may be used and the value |
984 | | * of ephemeral checked. |
985 | | * If ephemeral is false the returned pair *MUST NOT BE FREED*, it may be an |
986 | | * attribute in the request->control_pairs list. If ephemeral is true, the returned |
987 | | * pair *MUST* be freed, or added to one of the pair lists appropriate to the |
988 | | * ctx passed in. |
989 | | * |
990 | | * @param[out] ephemeral If true, the caller must use TALLOC_FREE |
991 | | * to free the return value of this function. |
992 | | * Alternatively 'ctx' can be freed, which is |
993 | | * simpler and cleaner, but some people have |
994 | | * religious objections to that. |
995 | | * @param[in] ctx Ephemeral ctx to allocate new attributes in. |
996 | | * @param[in] request The current request. |
997 | | * @param[in] allowed_attrs Optional list of allowed attributes. |
998 | | * @param[in] allowed_attrs_len Length of allowed attributes list. |
999 | | * @param[in] normify Apply hex/base64 normalisation to attributes. |
1000 | | * @return |
1001 | | * - A fr_pair_t containing a "known good" password. |
1002 | | * - NULL on error, or if no usable password attributes were found. |
1003 | | */ |
1004 | | fr_pair_t *password_find(bool *ephemeral, TALLOC_CTX *ctx, request_t *request, |
1005 | | fr_dict_attr_t const *allowed_attrs[], size_t allowed_attrs_len, bool normify) |
1006 | 0 | { |
1007 | 0 | fr_dcursor_t cursor; |
1008 | 0 | fr_pair_t *known_good; |
1009 | |
|
1010 | 0 | if (fr_pair_find_by_da(&request->control_pairs, NULL, attr_user) != NULL) { |
1011 | 0 | RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); |
1012 | 0 | RWDEBUG("!!! Ignoring control.User-Password. Update your !!!"); |
1013 | 0 | RWDEBUG("!!! configuration so that the \"known good\" clear text !!!"); |
1014 | 0 | RWDEBUG("!!! password is in Password.Cleartext and NOT in !!!"); |
1015 | 0 | RWDEBUG("!!! User-Password. !!!"); |
1016 | 0 | RWDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); |
1017 | 0 | } |
1018 | |
|
1019 | 0 | for (known_good = fr_pair_dcursor_by_ancestor_init(&cursor, &request->control_pairs, attr_root); |
1020 | 0 | known_good; |
1021 | 0 | known_good = fr_dcursor_next(&cursor)) { |
1022 | 0 | password_info_t *info; |
1023 | 0 | fr_pair_t *out; |
1024 | 0 | size_t i; |
1025 | |
|
1026 | 0 | if (known_good->da->attr >= NUM_ELEMENTS(password_info)) continue; |
1027 | | |
1028 | 0 | info = &password_info[known_good->da->attr]; |
1029 | | |
1030 | | /* |
1031 | | * Minor reduction in work for the caller |
1032 | | * for a moderate increase in code complexity. |
1033 | | */ |
1034 | 0 | if (info->always_allow) { |
1035 | 0 | out = password_normalise_and_recheck(ctx, request, |
1036 | 0 | allowed_attrs, allowed_attrs_len, |
1037 | 0 | normify, known_good); |
1038 | 0 | if (!out) continue; |
1039 | 0 | done: |
1040 | 0 | if (RDEBUG_ENABLED3) { |
1041 | 0 | RDEBUG3("Using \"known good\" %s password %pP", |
1042 | 0 | fr_table_str_by_value(password_type_table, |
1043 | 0 | password_info[out->da->attr].type, |
1044 | 0 | "<INVALID>"), out); |
1045 | 0 | } else { |
1046 | 0 | RDEBUG2("Using \"known good\" %s password %s", |
1047 | 0 | fr_table_str_by_value(password_type_table, |
1048 | 0 | password_info[out->da->attr].type, |
1049 | 0 | "<INVALID>"), out->da->name); |
1050 | 0 | } |
1051 | 0 | if (ephemeral) *ephemeral = (known_good != out); |
1052 | 0 | return out; |
1053 | 0 | } |
1054 | | |
1055 | 0 | for (i = 0; i < allowed_attrs_len; i++) { |
1056 | 0 | if (allowed_attrs[i] != known_good->da) continue; |
1057 | | |
1058 | 0 | out = password_normalise_and_recheck(ctx, request, |
1059 | 0 | allowed_attrs, allowed_attrs_len, |
1060 | 0 | normify, known_good); |
1061 | 0 | if (!out) continue; |
1062 | 0 | goto done; |
1063 | 0 | } |
1064 | 0 | } |
1065 | | |
1066 | 0 | return NULL; |
1067 | 0 | } |
1068 | | |
1069 | | static int _password_init(UNUSED void *uctx) |
1070 | 0 | { |
1071 | 0 | if (fr_dict_autoload(password_dict) < 0) { |
1072 | 0 | PERROR("%s", __FUNCTION__); |
1073 | 0 | return -1; |
1074 | 0 | } |
1075 | 0 | if (fr_dict_attr_autoload(password_dict_attr) < 0) { |
1076 | 0 | PERROR("%s", __FUNCTION__); |
1077 | 0 | fr_dict_autofree(password_dict); |
1078 | 0 | return -1; |
1079 | 0 | } |
1080 | | |
1081 | 0 | return 0; |
1082 | 0 | } |
1083 | | |
1084 | | static int _password_free(UNUSED void *uctx) |
1085 | 0 | { |
1086 | 0 | fr_dict_autofree(password_dict); |
1087 | |
|
1088 | 0 | return 0; |
1089 | 0 | } |
1090 | | |
1091 | | /** Load our dictionaries |
1092 | | * |
1093 | | */ |
1094 | | int password_init(void) |
1095 | 0 | { |
1096 | 0 | int ret; |
1097 | |
|
1098 | 0 | fr_atexit_global_once_ret(&ret, _password_init, _password_free, NULL); |
1099 | |
|
1100 | 0 | return ret; |
1101 | 0 | } |