4 Copyright (C) Simo Sorce 2004
5 Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
6 Copyright (C) Andrew Tridgell 2004
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 * Component: ldb password_hash module
28 * Description: correctly update hash values based on changes to sambaPassword and friends
30 * Author: Andrew Bartlett
34 #include "libcli/ldap/ldap.h"
35 #include "ldb/include/ldb_errors.h"
36 #include "ldb/include/ldb_private.h"
37 #include "librpc/gen_ndr/misc.h"
38 #include "librpc/gen_ndr/samr.h"
39 #include "libcli/auth/libcli_auth.h"
40 #include "libcli/security/security.h"
41 #include "system/kerberos.h"
42 #include "auth/kerberos/kerberos.h"
43 #include "system/time.h"
44 #include "dsdb/samdb/samdb.h"
48 /* If we have decided there is reason to work on this request, then
49 * setup all the password hash types correctly.
51 * If the administrator doesn't want the sambaPassword stored (set in the
52 * domain and per-account policies) then we must strip that out before
53 * we do the first operation.
55 * Once this is done (which could update anything at all), we
56 * calculate the password hashes.
58 * This function must not only update the ntPwdHash, lmPwdHash and
59 * krb5Key fields, it must also atomicly increment the
60 * msDS-KeyVersionNumber. We should be in a transaction, so all this
61 * should be quite safe...
63 * Finally, if the administrator has requested that a password history
64 * be maintained, then this should also be written out.
69 static int password_hash_handle(struct ldb_module *module, struct ldb_request *req,
70 const struct ldb_message *msg)
72 int ret, old_ret = -1;
73 uint_t pwdProperties, pwdHistoryLength;
74 uint_t userAccountControl;
75 const char *dnsDomain, *realm;
76 const char *sambaPassword = NULL;
77 struct samr_Password *sambaLMPwdHistory, *sambaNTPwdHistory;
78 struct samr_Password *lmPwdHash, *ntPwdHash;
79 struct samr_Password *lmOldHash = NULL, *ntOldHash = NULL;
80 struct samr_Password *new_sambaLMPwdHistory, *new_sambaNTPwdHistory;
81 struct samr_Password local_lmNewHash, local_ntNewHash;
82 int sambaLMPwdHistory_len, sambaNTPwdHistory_len;
84 struct dom_sid *domain_sid;
85 time_t now = time(NULL);
88 krb5_error_code krb5_ret;
90 struct smb_krb5_context *smb_krb5_context;
92 struct ldb_message_element *attribute;
93 struct ldb_dn *dn = msg->dn;
94 struct ldb_message *msg2;
96 struct ldb_request *search_request = NULL;
97 struct ldb_request *modify_request;
98 struct ldb_request *modified_orig_request;
99 struct ldb_result *res, *dom_res, *old_res;
101 struct ldb_message_element *objectclasses;
102 struct ldb_val computer_val;
103 struct ldb_val person_val;
106 struct ldb_message *modify_msg;
108 const char *domain_expression;
109 const char *old_user_attrs[] = { "lmPwdHash", "ntPwdHash", NULL };
110 const char *user_attrs[] = { "userAccountControl", "sambaLMPwdHistory",
113 "objectSid", "msDS-KeyVersionNumber",
114 "objectClass", "userPrincipalName",
117 const char * const domain_attrs[] = { "pwdProperties", "pwdHistoryLength",
122 /* Do the original action */
124 mem_ctx = talloc_new(module);
126 return LDB_ERR_OPERATIONS_ERROR;
129 if (req->operation == LDB_REQ_MODIFY) {
130 search_request = talloc(mem_ctx, struct ldb_request);
131 if (!search_request) {
132 talloc_free(mem_ctx);
133 return LDB_ERR_OPERATIONS_ERROR;
136 /* Look up the old ntPwdHash and lmPwdHash values, so
137 * we can later place these into the password
140 search_request->operation = LDB_REQ_SEARCH;
141 search_request->op.search.base = dn;
142 search_request->op.search.scope = LDB_SCOPE_BASE;
143 search_request->op.search.tree = ldb_parse_tree(module->ldb, NULL);
144 search_request->op.search.attrs = old_user_attrs;
145 search_request->controls = NULL;
147 old_ret = ldb_next_request(module, search_request);
150 /* we can't change things untill we copy it */
151 msg2 = ldb_msg_copy_shallow(mem_ctx, msg);
153 /* look again, this time at the copied attribute */
154 if (!msg2 || (attribute = ldb_msg_find_element(msg2, "sambaPassword")) == NULL ) {
155 talloc_free(mem_ctx);
156 /* Gah? where did it go? Oh well... */
157 return LDB_ERR_OPERATIONS_ERROR;
160 /* Wipe out the sambaPassword attribute set, we will handle it in
161 * the second modify. We might not want it written to disk */
163 if (req->operation == LDB_REQ_ADD) {
164 if (attribute->num_values > 1) {
165 ldb_set_errstring(module->ldb,
166 talloc_asprintf(mem_ctx, "sambaPassword_handle: "
167 "attempted set of multiple sambaPassword attributes on %s rejected",
168 ldb_dn_linearize(mem_ctx, dn)));
169 talloc_free(mem_ctx);
170 return LDB_ERR_CONSTRAINT_VIOLATION;
173 if (attribute->num_values == 1) {
174 sambaPassword = (const char *)attribute->values[0].data;
175 ldb_msg_remove_attr(msg2, "sambaPassword");
177 } else if (((attribute->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_ADD)
178 || ((attribute->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE)) {
179 if (attribute->num_values > 1) {
180 ldb_set_errstring(module->ldb,
181 talloc_asprintf(mem_ctx, "sambaPassword_handle: "
182 "attempted set of multiple sambaPassword attributes on %s rejected",
183 ldb_dn_linearize(mem_ctx, dn)));
184 talloc_free(mem_ctx);
185 return LDB_ERR_CONSTRAINT_VIOLATION;
188 if (attribute->num_values == 1) {
189 sambaPassword = (const char *)attribute->values[0].data;
190 ldb_msg_remove_attr(msg2, "sambaPassword");
194 modified_orig_request = talloc(mem_ctx, struct ldb_request);
195 if (!modified_orig_request) {
196 talloc_free(mem_ctx);
197 return LDB_ERR_OPERATIONS_ERROR;
200 *modified_orig_request = *req;
201 switch (modified_orig_request->operation) {
203 modified_orig_request->op.add.message = msg2;
206 modified_orig_request->op.mod.message = msg2;
209 return LDB_ERR_OPERATIONS_ERROR;
212 /* Send the (modified) request of the original caller down to the database */
213 ret = ldb_next_request(module, modified_orig_request);
215 talloc_free(mem_ctx);
219 /* While we do the search first (for the old password hashes),
220 * we don't want to override any error that the modify may
221 * have returned. Now check the error */
222 if (req->operation == LDB_REQ_MODIFY) {
224 talloc_free(mem_ctx);
228 /* Find out the old passwords details of the user */
229 old_res = search_request->op.search.res;
230 talloc_steal(mem_ctx, old_res);
231 talloc_free(search_request);
233 if (old_res->count != 1) {
234 ldb_set_errstring(module->ldb,
235 talloc_asprintf(mem_ctx, "password_hash_handle: "
236 "(pre) search for %s found %d != 1 objects, for entry we just modified",
237 ldb_dn_linearize(mem_ctx, dn),
239 /* What happend? The above add/modify worked... */
240 talloc_free(mem_ctx);
241 return LDB_ERR_NO_SUCH_OBJECT;
244 lmOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0], "lmPwdHash");
245 ntOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0], "ntPwdHash");
248 /* Start finding out details we need for the second modify.
249 * We do this after the first add/modify because other modules
250 * will have filled in the templates, and we may have had
251 * things like the username (affecting the salt) changed along
252 * with the password. */
254 /* Now find out what is on the entry after the above add/modify */
255 search_request = talloc(mem_ctx, struct ldb_request);
256 if (!search_request) {
257 talloc_free(mem_ctx);
258 return LDB_ERR_OPERATIONS_ERROR;
261 search_request->operation = LDB_REQ_SEARCH;
262 search_request->op.search.base = dn;
263 search_request->op.search.scope = LDB_SCOPE_BASE;
264 search_request->op.search.tree = ldb_parse_tree(module->ldb, NULL);
265 search_request->op.search.attrs = user_attrs;
266 search_request->controls = NULL;
268 ret = ldb_next_request(module, search_request);
270 talloc_free(mem_ctx);
274 /* Find out the full details of the user */
275 res = search_request->op.search.res;
276 talloc_steal(mem_ctx, res);
277 talloc_free(search_request);
279 if (res->count != 1) {
280 ldb_set_errstring(module->ldb,
281 talloc_asprintf(mem_ctx, "password_hash_handle: "
282 "search for %s found %d != 1 objects, for entry we just added/modified",
283 ldb_dn_linearize(mem_ctx, dn),
285 /* What happend? The above add/modify worked... */
286 talloc_free(mem_ctx);
287 return LDB_ERR_NO_SUCH_OBJECT;
290 userAccountControl = samdb_result_uint(res->msgs[0], "userAccountControl", 0);
291 sambaLMPwdHistory_len = samdb_result_hashes(mem_ctx, res->msgs[0],
292 "sambaLMPwdHistory", &sambaLMPwdHistory);
293 sambaNTPwdHistory_len = samdb_result_hashes(mem_ctx, res->msgs[0],
294 "sambaNTPwdHistory", &sambaNTPwdHistory);
295 ntPwdHash = samdb_result_hash(mem_ctx, res->msgs[0], "ntPwdHash");
296 kvno = samdb_result_uint(res->msgs[0], "msDS-KeyVersionNumber", 0);
298 domain_sid = samdb_result_sid_prefix(mem_ctx, res->msgs[0], "objectSid");
301 objectclasses = ldb_msg_find_element(res->msgs[0], "objectClass");
302 person_val = data_blob_string_const("person");
304 if (!objectclasses || !ldb_msg_find_val(objectclasses, &person_val)) {
305 /* Not a 'person', so the rest of this doesn't make
306 * sense. How we got a sambaPassword this far I don't
308 ldb_set_errstring(module->ldb,
309 talloc_asprintf(mem_ctx, "password_hash_handle: "
310 "attempted set of sambaPassword on non-'person' object %s rejected",
311 ldb_dn_linearize(mem_ctx, dn)));
312 talloc_free(mem_ctx);
313 return LDB_ERR_CONSTRAINT_VIOLATION;
316 computer_val = data_blob_string_const("computer");
318 if (ldb_msg_find_val(objectclasses, &computer_val)) {
324 domain_expression = talloc_asprintf(mem_ctx, "(&(objectSid=%s)(objectClass=domain))",
325 ldap_encode_ndr_dom_sid(mem_ctx, domain_sid));
327 /* Find the user's domain, then find out the domain password
329 ret = ldb_search(module->ldb, NULL, LDB_SCOPE_SUBTREE, domain_expression,
330 domain_attrs, &dom_res);
332 talloc_free(mem_ctx);
336 if (dom_res->count != 1) {
337 /* What happend? The user we are modifying must be odd... */
338 ldb_set_errstring(module->ldb,
339 talloc_asprintf(mem_ctx, "password_hash_handle: "
340 "search for domain %s found %d != 1 objects",
341 dom_sid_string(mem_ctx, domain_sid),
343 talloc_free(mem_ctx);
344 return LDB_ERR_NO_SUCH_OBJECT;
347 pwdProperties = samdb_result_uint(dom_res->msgs[0], "pwdProperties", 0);
348 pwdHistoryLength = samdb_result_uint(dom_res->msgs[0], "pwdHistoryLength", 0);
349 dnsDomain = ldb_msg_find_string(dom_res->msgs[0], "dnsDomain", NULL);
350 realm = strupper_talloc(mem_ctx, dnsDomain);
352 /* Some operations below require kerberos contexts */
353 if (smb_krb5_init_context(mem_ctx, &smb_krb5_context) != 0) {
354 talloc_free(mem_ctx);
355 return LDB_ERR_OPERATIONS_ERROR;
358 /* Prepare the modifications to set all the hash/key types */
359 modify_msg = ldb_msg_new(req);
360 modify_msg->dn = talloc_reference(modify_msg, dn);
362 #define CHECK_RET(x) \
365 if (check_ret != LDB_SUCCESS) { \
366 talloc_free(mem_ctx); \
371 /* Setup krb5Key (we want to either delete an existing value,
372 * or replace with a new one). Both the unicode and NT hash
373 * only branches append keys to this multivalued entry. */
374 CHECK_RET(ldb_msg_add_empty(modify_msg, "krb5Key", LDB_FLAG_MOD_REPLACE));
376 /* Yay, we can compute new password hashes from the unicode
379 Principal *salt_principal;
380 const char *user_principal_name = ldb_msg_find_string(res->msgs[0], "userPrincipalName", NULL);
385 /* compute the new nt and lm hashes */
386 if (E_deshash(sambaPassword, local_lmNewHash.hash)) {
387 lmPwdHash = &local_lmNewHash;
391 E_md4hash(sambaPassword, local_ntNewHash.hash);
392 ntPwdHash = &local_ntNewHash;
393 CHECK_RET(ldb_msg_add_empty(modify_msg, "ntPwdHash",
394 LDB_FLAG_MOD_REPLACE));
395 CHECK_RET(samdb_msg_add_hash(module->ldb, req,
396 modify_msg, "ntPwdHash",
398 CHECK_RET(ldb_msg_add_empty(modify_msg, "lmPwdHash",
399 LDB_FLAG_MOD_REPLACE));
401 CHECK_RET(samdb_msg_add_hash(module->ldb, req,
402 modify_msg, "lmPwdHash",
406 /* Many, many thanks to lukeh@padl.com for this
407 * algorithm, described in his Nov 10 2004 mail to
408 * samba-technical@samba.org */
411 /* Determine a salting principal */
412 char *samAccountName = talloc_strdup(mem_ctx, ldb_msg_find_string(res->msgs[0], "samAccountName", NULL));
414 if (!samAccountName) {
415 ldb_set_errstring(module->ldb,
416 talloc_asprintf(mem_ctx, "password_hash_handle: "
417 "generation of new kerberos keys failed: %s is a computer without a samAccountName",
418 ldb_dn_linearize(mem_ctx, dn)));
419 talloc_free(mem_ctx);
420 return LDB_ERR_OPERATIONS_ERROR;
422 if (samAccountName[strlen(samAccountName)-1] == '$') {
423 samAccountName[strlen(samAccountName)-1] = '\0';
425 saltbody = talloc_asprintf(mem_ctx, "%s.%s", samAccountName, dnsDomain);
427 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, "host", saltbody, NULL);
428 } else if (user_principal_name) {
430 user_principal_name = talloc_strdup(mem_ctx, user_principal_name);
431 if (!user_principal_name) {
432 talloc_free(mem_ctx);
433 return LDB_ERR_OPERATIONS_ERROR;
435 p = strchr(user_principal_name, '@');
439 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, user_principal_name, NULL);
442 const char *samAccountName = ldb_msg_find_string(res->msgs[0], "samAccountName", NULL);
443 if (!samAccountName) {
444 ldb_set_errstring(module->ldb,
445 talloc_asprintf(mem_ctx, "password_hash_handle: "
446 "generation of new kerberos keys failed: %s has no samAccountName",
447 ldb_dn_linearize(mem_ctx, dn)));
448 talloc_free(mem_ctx);
449 return LDB_ERR_OPERATIONS_ERROR;
451 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, samAccountName, NULL);
456 ldb_set_errstring(module->ldb,
457 talloc_asprintf(mem_ctx, "password_hash_handle: "
458 "generation of a saltking principal failed: %s",
459 smb_get_krb5_error_message(smb_krb5_context->krb5_context,
460 krb5_ret, mem_ctx)));
461 talloc_free(mem_ctx);
462 return LDB_ERR_OPERATIONS_ERROR;
465 /* TODO: We may wish to control the encryption types chosen in future */
466 krb5_ret = hdb_generate_key_set_password(smb_krb5_context->krb5_context,
467 salt_principal, sambaPassword, &keys, &num_keys);
468 krb5_free_principal(smb_krb5_context->krb5_context, salt_principal);
471 ldb_set_errstring(module->ldb,
472 talloc_asprintf(mem_ctx, "password_hash_handle: "
473 "generation of new kerberos keys failed: %s",
474 smb_get_krb5_error_message(smb_krb5_context->krb5_context,
475 krb5_ret, mem_ctx)));
476 talloc_free(mem_ctx);
477 return LDB_ERR_OPERATIONS_ERROR;
480 /* Walking all the key types generated, transform each
481 * key into an ASN.1 blob
483 for (i=0; i < num_keys; i++) {
489 if (keys[i].key.keytype == ETYPE_ARCFOUR_HMAC_MD5) {
490 /* We might end up doing this below:
491 * This ensures we get the unicode
492 * conversion right. This should also
493 * be fixed in the Heimdal libs */
496 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &keys[i], &len, krb5_ret);
498 return LDB_ERR_OPERATIONS_ERROR;
501 val.data = talloc_memdup(req, buf, len);
504 if (!val.data || krb5_ret) {
505 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
506 talloc_free(mem_ctx);
507 return LDB_ERR_OPERATIONS_ERROR;
509 ret = ldb_msg_add_value(modify_msg, "krb5Key", &val);
510 if (ret != LDB_SUCCESS) {
511 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
512 talloc_free(mem_ctx);
517 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
520 /* Possibly kill off the cleartext or store it */
521 CHECK_RET(ldb_msg_add_empty(modify_msg, "sambaPassword", LDB_FLAG_MOD_REPLACE));
523 if (sambaPassword && (pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT) &&
524 (userAccountControl & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) {
525 CHECK_RET(ldb_msg_add_string(modify_msg, "sambaPassword", sambaPassword));
528 /* Even if we didn't get a sambaPassword, we can still setup
529 * krb5Key from the NT hash.
531 * This is an append, so it works with the 'continue' in the
532 * unicode loop above, to use Samba's NT hash function, which
533 * is more correct than Heimdal's
543 key.salt = NULL; /* No salt for this enc type */
545 krb5_ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
546 ETYPE_ARCFOUR_HMAC_MD5,
547 ntPwdHash->hash, sizeof(ntPwdHash->hash),
550 return LDB_ERR_OPERATIONS_ERROR;
552 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &key, &len, krb5_ret);
554 return LDB_ERR_OPERATIONS_ERROR;
556 krb5_free_keyblock_contents(smb_krb5_context->krb5_context,
559 val.data = talloc_memdup(req, buf, len);
562 if (!val.data || ret) {
563 return LDB_ERR_OPERATIONS_ERROR;
565 CHECK_RET(ldb_msg_add_value(modify_msg, "krb5Key", &val));
568 /* If the original caller did anything with pwdLastSet then skip this. It could be an incoming samsync */
569 attribute = ldb_msg_find_element(msg, "pwdLastSet");
570 if (attribute == NULL) {
571 /* Update the password last set time */
572 unix_to_nt_time(&now_nt, now);
573 CHECK_RET(ldb_msg_add_empty(modify_msg, "pwdLastSet", LDB_FLAG_MOD_REPLACE));
574 CHECK_RET(samdb_msg_add_uint64(module->ldb, mem_ctx, modify_msg, "pwdLastSet", now_nt));
577 /* If the original caller did anything with "msDS-KeyVersionNumber" then skip this. It could be an incoming samsync */
578 attribute = ldb_msg_find_element(msg, "msDS-KeyVersionNumber");
579 if (attribute == NULL) {
581 CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
582 LDB_FLAG_MOD_REPLACE));
583 CHECK_RET(samdb_msg_add_uint(module->ldb, mem_ctx, modify_msg, "msDS-KeyVersionNumber", kvno + 1));
585 /* While we should be in a transaction, go one extra
586 * step in the dance for an 'atomic' increment. This
587 * may be of value against remote LDAP servers. (Note
588 * however that Mulitmaster replication stil offers no
591 struct ldb_val old_kvno, new_kvno;
592 old_kvno.data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", kvno);
593 if (!old_kvno.data) {
596 old_kvno.length = strlen((char *)old_kvno.data);
598 new_kvno.data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", kvno + 1);
599 if (!new_kvno.data) {
602 new_kvno.length = strlen((char *)new_kvno.data);
604 CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
605 LDB_FLAG_MOD_DELETE));
606 CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
608 modify_msg->elements[modify_msg->num_elements - 2].num_values = 1;
609 modify_msg->elements[modify_msg->num_elements - 2].values = &old_kvno;
610 modify_msg->elements[modify_msg->num_elements - 1].num_values = 1;
611 modify_msg->elements[modify_msg->num_elements - 1].values = &new_kvno;
615 CHECK_RET(ldb_msg_add_empty(modify_msg, "sambaLMPwdHistory",
616 LDB_FLAG_MOD_REPLACE));
617 CHECK_RET(ldb_msg_add_empty(modify_msg, "sambaNTPwdHistory",
618 LDB_FLAG_MOD_REPLACE));
620 /* If we have something to put into the history, or an old
621 * history element to expire, update the history */
622 if (pwdHistoryLength > 0 &&
623 ((sambaNTPwdHistory_len > 0) || (sambaLMPwdHistory_len > 0)
624 || lmOldHash || ntOldHash)) {
625 /* store the password history */
626 new_sambaLMPwdHistory = talloc_array(mem_ctx, struct samr_Password,
628 if (!new_sambaLMPwdHistory) {
629 return LDB_ERR_OPERATIONS_ERROR;
631 new_sambaNTPwdHistory = talloc_array(mem_ctx, struct samr_Password,
633 if (!new_sambaNTPwdHistory) {
634 return LDB_ERR_OPERATIONS_ERROR;
636 for (i=0;i<MIN(pwdHistoryLength-1, sambaLMPwdHistory_len);i++) {
637 new_sambaLMPwdHistory[i+1] = sambaLMPwdHistory[i];
639 for (i=0;i<MIN(pwdHistoryLength-1, sambaNTPwdHistory_len);i++) {
640 new_sambaNTPwdHistory[i+1] = sambaNTPwdHistory[i];
643 /* Don't store 'long' passwords in the LM history,
644 but make sure to 'expire' one password off the other end */
646 new_sambaLMPwdHistory[0] = *lmOldHash;
648 ZERO_STRUCT(new_sambaLMPwdHistory[0]);
650 sambaLMPwdHistory_len = MIN(sambaLMPwdHistory_len + 1, pwdHistoryLength);
652 /* Likewise, we might not have an old NT password (lm
653 * only password change function on previous change) */
655 new_sambaNTPwdHistory[0] = *ntOldHash;
657 ZERO_STRUCT(new_sambaNTPwdHistory[0]);
659 sambaNTPwdHistory_len = MIN(sambaNTPwdHistory_len + 1, pwdHistoryLength);
661 CHECK_RET(samdb_msg_add_hashes(mem_ctx, modify_msg,
663 new_sambaLMPwdHistory,
664 sambaLMPwdHistory_len));
666 CHECK_RET(samdb_msg_add_hashes(mem_ctx, modify_msg,
668 new_sambaNTPwdHistory,
669 sambaNTPwdHistory_len));
672 /* Too much code above, we should check we got it close to reasonable */
673 CHECK_RET(ldb_msg_sanity_check(modify_msg));
675 modify_request = talloc(mem_ctx, struct ldb_request);
676 if (!modify_request) {
677 talloc_free(mem_ctx);
678 return LDB_ERR_OPERATIONS_ERROR;
681 modify_request->operation = LDB_REQ_MODIFY;
682 modify_request->op.mod.message = modify_msg;
683 modify_request->controls = NULL;
685 ret = ldb_next_request(module, modify_request);
687 talloc_free(mem_ctx);
691 /* add_record: do things with the sambaPassword attribute */
692 static int password_hash_add(struct ldb_module *module, struct ldb_request *req)
694 const struct ldb_message *msg = req->op.add.message;
696 ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_record\n");
698 if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
699 return ldb_next_request(module, req);
702 /* If no part of this touches the sambaPassword, then we don't
703 * need to make any changes. For password changes/set there should
704 * be a 'delete' or a 'modify' on this attribute. */
705 if (ldb_msg_find_element(msg, "sambaPassword") == NULL ) {
706 return ldb_next_request(module, req);
709 return password_hash_handle(module, req, msg);
712 /* modify_record: do things with the sambaPassword attribute */
713 static int password_hash_modify(struct ldb_module *module, struct ldb_request *req)
715 const struct ldb_message *msg = req->op.mod.message;
717 ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_modify_record\n");
719 if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
720 return ldb_next_request(module, req);
723 /* If no part of this touches the sambaPassword, then we don't
724 * need to make any changes. For password changes/set there should
725 * be a 'delete' or a 'modify' on this attribute. */
726 if (ldb_msg_find_element(msg, "sambaPassword") == NULL ) {
727 return ldb_next_request(module, req);
730 return password_hash_handle(module, req, msg);
733 enum ph_type {PH_ADD, PH_MOD};
734 enum ph_step {PH_ADD_SEARCH_DOM, PH_ADD_DO_ADD, PH_MOD_DO_REQ, PH_MOD_SEARCH_SELF, PH_MOD_SEARCH_DOM, PH_MOD_DO_MOD};
736 struct ph_async_context {
741 struct ldb_module *module;
742 struct ldb_request *orig_req;
744 struct ldb_request *dom_req;
745 struct ldb_async_result *dom_res;
747 struct ldb_request *down_req;
749 struct ldb_request *search_req;
750 struct ldb_async_result *search_res;
752 struct ldb_request *mod_req;
756 uint_t pwdProperties;
757 uint_t pwdHistoryLength;
762 static int add_password_hashes(struct ldb_module *module, struct ldb_message *msg, int is_mod)
764 const char *sambaPassword;
765 struct samr_Password tmp_hash;
767 sambaPassword = ldb_msg_find_string(msg, "sambaPassword", NULL);
768 if (sambaPassword == NULL) { /* impossible, what happened ?! */
769 return LDB_ERR_OPERATIONS_ERROR;
773 if (ldb_msg_add_empty(msg, "ntPwdHash", LDB_FLAG_MOD_REPLACE) != 0) {
774 return LDB_ERR_OPERATIONS_ERROR;
776 if (ldb_msg_add_empty(msg, "lmPwdHash", LDB_FLAG_MOD_REPLACE) != 0) {
777 return LDB_ERR_OPERATIONS_ERROR;
781 /* compute the new nt and lm hashes */
782 E_md4hash(sambaPassword, tmp_hash.hash);
783 if (samdb_msg_add_hash(module->ldb, msg, msg, "ntPwdHash", &tmp_hash) != 0) {
784 return LDB_ERR_OPERATIONS_ERROR;
787 if (E_deshash(sambaPassword, tmp_hash.hash)) {
788 if (samdb_msg_add_hash(module->ldb, msg, msg, "lmPwdHash", &tmp_hash) != 0) {
789 return LDB_ERR_OPERATIONS_ERROR;
796 static int add_krb5_keys_from_password(struct ldb_module *module, struct ldb_message *msg,
797 struct smb_krb5_context *smb_krb5_context,
798 struct domain_data *domain,
799 const char *samAccountName,
800 const char *user_principal_name,
803 const char *sambaPassword;
804 Principal *salt_principal;
805 krb5_error_code krb5_ret;
810 /* Many, many thanks to lukeh@padl.com for this
811 * algorithm, described in his Nov 10 2004 mail to
812 * samba-technical@samba.org */
814 sambaPassword = ldb_msg_find_string(msg, "sambaPassword", NULL);
815 if (sambaPassword == NULL) { /* impossible, what happened ?! */
816 return LDB_ERR_OPERATIONS_ERROR;
820 /* Determine a salting principal */
821 char *name = talloc_strdup(msg, samAccountName);
824 ldb_set_errstring(module->ldb,
825 talloc_asprintf(msg, "password_hash_handle: "
826 "generation of new kerberos keys failed: %s is a computer without a samAccountName",
827 ldb_dn_linearize(msg, msg->dn)));
828 return LDB_ERR_OPERATIONS_ERROR;
830 if (name[strlen(name)-1] == '$') {
831 name[strlen(name)-1] = '\0';
833 saltbody = talloc_asprintf(msg, "%s.%s", name, domain->dnsDomain);
835 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context,
837 domain->realm, "host",
839 } else if (user_principal_name) {
841 user_principal_name = talloc_strdup(msg, user_principal_name);
842 if (user_principal_name == NULL) {
843 return LDB_ERR_OPERATIONS_ERROR;
845 p = strchr(user_principal_name, '@');
849 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context,
851 domain->realm, user_principal_name, NULL);
854 if (!samAccountName) {
855 ldb_set_errstring(module->ldb,
856 talloc_asprintf(msg, "password_hash_handle: "
857 "generation of new kerberos keys failed: %s has no samAccountName",
858 ldb_dn_linearize(msg, msg->dn)));
859 return LDB_ERR_OPERATIONS_ERROR;
861 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context,
863 domain->realm, samAccountName,
868 ldb_set_errstring(module->ldb,
869 talloc_asprintf(msg, "password_hash_handle: "
870 "generation of a saltking principal failed: %s",
871 smb_get_krb5_error_message(smb_krb5_context->krb5_context,
873 return LDB_ERR_OPERATIONS_ERROR;
876 /* TODO: We may wish to control the encryption types chosen in future */
877 krb5_ret = hdb_generate_key_set_password(smb_krb5_context->krb5_context,
878 salt_principal, sambaPassword, &keys, &num_keys);
879 krb5_free_principal(smb_krb5_context->krb5_context, salt_principal);
882 ldb_set_errstring(module->ldb,
883 talloc_asprintf(msg, "password_hash_handle: "
884 "generation of new kerberos keys failed: %s",
885 smb_get_krb5_error_message(smb_krb5_context->krb5_context,
887 return LDB_ERR_OPERATIONS_ERROR;
890 /* Walking all the key types generated, transform each
891 * key into an ASN.1 blob
893 for (i=0; i < num_keys; i++) {
900 if (keys[i].key.keytype == ENCTYPE_ARCFOUR_HMAC) {
901 /* We might end up doing this below:
902 * This ensures we get the unicode
903 * conversion right. This should also
904 * be fixed in the Heimdal libs */
907 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &keys[i], &len, krb5_ret);
909 return LDB_ERR_OPERATIONS_ERROR;
912 val.data = talloc_memdup(msg, buf, len);
915 if (!val.data || krb5_ret) {
916 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
917 return LDB_ERR_OPERATIONS_ERROR;
919 ret = ldb_msg_add_value(msg, "krb5Key", &val);
920 if (ret != LDB_SUCCESS) {
921 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
926 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
931 static int add_krb5_keys_from_NThash(struct ldb_module *module, struct ldb_message *msg,
932 struct smb_krb5_context *smb_krb5_context)
934 struct samr_Password *ntPwdHash;
935 krb5_error_code krb5_ret;
943 key.salt = NULL; /* No salt for this enc type */
945 ntPwdHash = samdb_result_hash(msg, msg, "ntPwdHash");
946 if (ntPwdHash == NULL) { /* what happened ?! */
947 return LDB_ERR_OPERATIONS_ERROR;
950 krb5_ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
951 ENCTYPE_ARCFOUR_HMAC,
952 ntPwdHash->hash, sizeof(ntPwdHash->hash),
955 return LDB_ERR_OPERATIONS_ERROR;
957 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &key, &len, krb5_ret);
959 return LDB_ERR_OPERATIONS_ERROR;
961 krb5_free_keyblock_contents(smb_krb5_context->krb5_context,
964 val.data = talloc_memdup(msg, buf, len);
968 return LDB_ERR_OPERATIONS_ERROR;
970 if (ldb_msg_add_value(msg, "krb5Key", &val) != 0) {
971 return LDB_ERR_OPERATIONS_ERROR;
977 static int set_pwdLastSet(struct ldb_module *module, struct ldb_message *msg, int is_mod)
982 unix_to_nt_time(&now_nt, time(NULL));
985 /* be sure there isn't a 0 value set (eg. coming from the template) */
986 ldb_msg_remove_attr(msg, "pwdLastSet");
988 if (ldb_msg_add_empty(msg, "pwdLastSet", LDB_FLAG_MOD_ADD) != 0) {
989 return LDB_ERR_OPERATIONS_ERROR;
993 if (ldb_msg_add_empty(msg, "pwdLastSet", LDB_FLAG_MOD_REPLACE) != 0) {
994 return LDB_ERR_OPERATIONS_ERROR;
998 if (samdb_msg_add_uint64(module->ldb, msg, msg, "pwdLastSet", now_nt) != 0) {
999 return LDB_ERR_OPERATIONS_ERROR;
1005 static int add_keyVersionNumber(struct ldb_module *module, struct ldb_message *msg, int previous)
1007 /* replace or add */
1008 if (ldb_msg_add_empty(msg, "msDS-KeyVersionNumber", LDB_FLAG_MOD_REPLACE) != 0) {
1009 return LDB_ERR_OPERATIONS_ERROR;
1012 if (samdb_msg_add_uint(module->ldb, msg, msg, "msDS-KeyVersionNumber", previous+1) != 0) {
1013 return LDB_ERR_OPERATIONS_ERROR;
1019 static int setPwdHistory(struct ldb_module *module, struct ldb_message *msg, struct ldb_message *old_msg, int hlen)
1021 struct samr_Password *nt_hash;
1022 struct samr_Password *lm_hash;
1023 struct samr_Password *nt_history;
1024 struct samr_Password *lm_history;
1025 struct samr_Password *new_nt_history;
1026 struct samr_Password *new_lm_history;
1031 nt_hash = samdb_result_hash(msg, old_msg, "ntPwdHash");
1032 lm_hash = samdb_result_hash(msg, old_msg, "lmPwdHash");
1034 /* if no previous passwords just return */
1035 if (nt_hash == NULL && lm_hash == NULL) return LDB_SUCCESS;
1037 nt_hist_len = samdb_result_hashes(msg, old_msg, "sambaNTPwdHistory", &nt_history);
1038 lm_hist_len = samdb_result_hashes(msg, old_msg, "sambaLMPwdHistory", &lm_history);
1040 /* We might not have an old NT password */
1041 new_nt_history = talloc_array(msg, struct samr_Password, hlen);
1042 if (new_nt_history == NULL) {
1043 return LDB_ERR_OPERATIONS_ERROR;
1045 for (i = 0; i < MIN(hlen-1, nt_hist_len); i++) {
1046 new_nt_history[i+1] = nt_history[i];
1048 nt_hist_len = i + 1;
1050 new_nt_history[0] = *nt_hash;
1052 ZERO_STRUCT(new_nt_history[0]);
1054 if (ldb_msg_add_empty(msg, "sambaNTPwdHistory", LDB_FLAG_MOD_REPLACE) != LDB_SUCCESS) {
1055 return LDB_ERR_OPERATIONS_ERROR;
1057 if (samdb_msg_add_hashes(msg, msg, "sambaNTPwdHistory", new_nt_history, nt_hist_len) != LDB_SUCCESS) {
1058 return LDB_ERR_OPERATIONS_ERROR;
1062 /* Don't store 'long' passwords in the LM history,
1063 but make sure to 'expire' one password off the other end */
1064 new_lm_history = talloc_array(msg, struct samr_Password, hlen);
1065 if (new_lm_history == NULL) {
1066 return LDB_ERR_OPERATIONS_ERROR;
1068 for (i = 0; i < MIN(hlen-1, lm_hist_len); i++) {
1069 new_lm_history[i+1] = lm_history[i];
1071 lm_hist_len = i + 1;
1073 new_lm_history[0] = *lm_hash;
1075 ZERO_STRUCT(new_lm_history[0]);
1077 if (ldb_msg_add_empty(msg, "sambaLMPwdHistory", LDB_FLAG_MOD_REPLACE) != LDB_SUCCESS) {
1078 return LDB_ERR_OPERATIONS_ERROR;
1080 if (samdb_msg_add_hashes(msg, msg, "sambaLMPwdHistory", new_lm_history, lm_hist_len) != LDB_SUCCESS) {
1081 return LDB_ERR_OPERATIONS_ERROR;
1087 static struct ldb_async_handle *ph_init_handle(struct ldb_request *req, struct ldb_module *module, enum ph_type type)
1089 struct ph_async_context *ac;
1090 struct ldb_async_handle *h;
1092 h = talloc_zero(req, struct ldb_async_handle);
1094 ldb_set_errstring(module->ldb, talloc_asprintf(module, "Out of Memory"));
1100 ac = talloc_zero(h, struct ph_async_context);
1102 ldb_set_errstring(module->ldb, talloc_asprintf(module, "Out of Memory"));
1107 h->private_data = (void *)ac;
1109 h->state = LDB_ASYNC_INIT;
1110 h->status = LDB_SUCCESS;
1113 ac->module = module;
1119 static int get_domain_data_callback(struct ldb_context *ldb, void *context, struct ldb_async_result *ares)
1121 struct ph_async_context *ac;
1123 if (!context || !ares) {
1124 ldb_set_errstring(ldb, talloc_asprintf(ldb, "NULL Context or Result in callback"));
1125 return LDB_ERR_OPERATIONS_ERROR;
1128 ac = talloc_get_type(context, struct ph_async_context);
1130 /* we are interested only in the single reply (base search) we receive here */
1131 if (ares->type == LDB_REPLY_ENTRY) {
1132 if (ac->dom_res != NULL) {
1133 ldb_set_errstring(ldb, talloc_asprintf(ldb, "Too many results"));
1135 return LDB_ERR_OPERATIONS_ERROR;
1137 ac->dom_res = talloc_steal(ac, ares);
1145 static int build_domain_data_request(struct ph_async_context *ac,
1146 struct dom_sid *sid)
1148 /* attrs[] is returned from this function in
1149 ac->dom_req->op.search.attrs, so it must be static, as
1150 otherwise the compiler can put it on the stack */
1151 static const char * const attrs[] = { "pwdProperties", "pwdHistoryLength", "dnsDomain", NULL };
1154 ac->dom_req = talloc_zero(ac, struct ldb_request);
1155 if (ac->dom_req == NULL) {
1156 ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
1157 return LDB_ERR_OPERATIONS_ERROR;
1159 ac->dom_req->operation = LDB_ASYNC_SEARCH;
1160 ac->dom_req->op.search.base = NULL;
1161 ac->dom_req->op.search.scope = LDB_SCOPE_SUBTREE;
1163 filter = talloc_asprintf(ac->dom_req, "(&(objectSid=%s)(objectClass=domain))", dom_sid_string(ac->dom_req, sid));
1164 if (filter == NULL) {
1165 ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
1166 talloc_free(ac->dom_req);
1167 return LDB_ERR_OPERATIONS_ERROR;
1170 ac->dom_req->op.search.tree = ldb_parse_tree(ac->module->ldb, filter);
1171 if (ac->dom_req->op.search.tree == NULL) {
1172 ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "Invalid search filter"));
1173 talloc_free(ac->dom_req);
1174 return LDB_ERR_OPERATIONS_ERROR;
1176 ac->dom_req->op.search.attrs = attrs;
1177 ac->dom_req->controls = NULL;
1178 ac->dom_req->creds = ac->orig_req->creds;
1179 ac->dom_req->async.context = ac;
1180 ac->dom_req->async.callback = get_domain_data_callback;
1181 ac->dom_req->async.timeout = ac->orig_req->async.timeout;
1186 static struct domain_data *get_domain_data(struct ldb_module *module, void *mem_ctx, struct ldb_async_result *res)
1188 struct domain_data *data;
1191 data = talloc_zero(mem_ctx, struct domain_data);
1196 data->pwdProperties = samdb_result_uint(res->message, "pwdProperties", 0);
1197 data->pwdHistoryLength = samdb_result_uint(res->message, "pwdHistoryLength", 0);
1198 tmp = ldb_msg_find_string(res->message, "dnsDomain", NULL);
1201 data->dnsDomain = talloc_strdup(data, tmp);
1202 if (data->dnsDomain == NULL) {
1203 ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
1206 data->realm = strupper_talloc(mem_ctx, tmp);
1207 if (data->realm == NULL) {
1208 ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
1216 static int password_hash_add_async(struct ldb_module *module, struct ldb_request *req)
1218 struct ldb_async_handle *h;
1219 struct ph_async_context *ac;
1220 struct ldb_message_element *attribute;
1221 struct dom_sid *domain_sid;
1224 ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_async\n");
1226 if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
1227 return ldb_next_request(module, req);
1230 /* nobody must touch password Histories */
1231 if (ldb_msg_find_element(req->op.add.message, "sambaNTPwdHistory") ||
1232 ldb_msg_find_element(req->op.add.message, "sambaLMPwdHistory")) {
1233 return LDB_ERR_UNWILLING_TO_PERFORM;
1236 /* If no part of this touches the sambaPassword, then we don't
1237 * need to make any changes. For password changes/set there should
1238 * be a 'delete' or a 'modify' on this attribute. */
1239 if ((attribute = ldb_msg_find_element(req->op.add.message, "sambaPassword")) == NULL ) {
1240 return ldb_next_request(module, req);
1243 /* if it is not an entry of type person its an error */
1244 /* TODO: remove this when sambaPassword will be in schema */
1245 if (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "person")) {
1246 return LDB_ERR_OBJECT_CLASS_VIOLATION;
1249 /* check sambaPassword is single valued here */
1250 /* TODO: remove this when sambaPassword will be single valued in schema */
1251 if (attribute->num_values > 1) {
1252 ldb_set_errstring(module->ldb, talloc_asprintf(req,
1253 "mupltiple values for sambaPassword not allowed!\n"));
1254 return LDB_ERR_CONSTRAINT_VIOLATION;
1257 /* get user domain data */
1258 domain_sid = samdb_result_sid_prefix(req, req->op.add.message, "objectSid");
1259 if (domain_sid == NULL) {
1260 ldb_debug(module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n");
1261 return LDB_ERR_OPERATIONS_ERROR;
1264 h = ph_init_handle(req, module, PH_ADD);
1266 return LDB_ERR_OPERATIONS_ERROR;
1268 ac = talloc_get_type(h->private_data, struct ph_async_context);
1270 ret = build_domain_data_request(ac, domain_sid);
1271 if (ret != LDB_SUCCESS) {
1275 ac->step = PH_ADD_SEARCH_DOM;
1277 req->async.handle = h;
1279 return ldb_next_request(module, ac->dom_req);
1282 static int password_hash_add_async_do_add(struct ldb_async_handle *h) {
1284 struct ph_async_context *ac;
1285 struct domain_data *domain;
1286 struct smb_krb5_context *smb_krb5_context;
1287 struct ldb_message *msg;
1289 ac = talloc_get_type(h->private_data, struct ph_async_context);
1291 domain = get_domain_data(ac->module, ac, ac->dom_res);
1292 if (domain == NULL) {
1293 return LDB_ERR_OPERATIONS_ERROR;
1296 ac->down_req = talloc(ac, struct ldb_request);
1297 if (ac->down_req == NULL) {
1298 return LDB_ERR_OPERATIONS_ERROR;
1301 *(ac->down_req) = *(ac->orig_req);
1302 ac->down_req->op.add.message = msg = ldb_msg_copy_shallow(ac->down_req, ac->orig_req->op.add.message);
1303 if (ac->down_req->op.add.message == NULL) {
1304 return LDB_ERR_OPERATIONS_ERROR;
1307 /* Some operations below require kerberos contexts */
1308 if (smb_krb5_init_context(ac->down_req, &smb_krb5_context) != 0) {
1309 return LDB_ERR_OPERATIONS_ERROR;
1312 /* we can compute new password hashes from the unicode password */
1313 if (add_password_hashes(ac->module, msg, 0) != LDB_SUCCESS) {
1314 return LDB_ERR_OPERATIONS_ERROR;
1317 /* now add krb5 keys based on unicode password */
1318 if (add_krb5_keys_from_password(ac->module, msg, smb_krb5_context, domain,
1319 ldb_msg_find_string(msg, "samAccountName", NULL),
1320 ldb_msg_find_string(msg, "userPrincipalName", NULL),
1321 ldb_msg_check_string_attribute(msg, "objectClass", "computer")
1323 return LDB_ERR_OPERATIONS_ERROR;
1326 /* add also kr5 keys based on NT the hash */
1327 if (add_krb5_keys_from_NThash(ac->module, msg, smb_krb5_context) != LDB_SUCCESS) {
1328 return LDB_ERR_OPERATIONS_ERROR;
1331 /* if both the domain properties and the user account controls do not permit
1332 * clear text passwords then wipe out the sambaPassword */
1333 if ((!(domain->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT)) ||
1334 (!(ldb_msg_find_uint(msg, "userAccountControl", 0) & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED))) {
1335 ldb_msg_remove_attr(msg, "sambaPassword");
1338 /* don't touch it if a value is set. It could be an incoming samsync */
1339 if (ldb_msg_find_uint64(msg, "pwdLastSet", 0) == 0) {
1340 if (set_pwdLastSet(ac->module, msg, 0) != LDB_SUCCESS) {
1341 return LDB_ERR_OPERATIONS_ERROR;
1345 /* don't touch it if a value is set. It could be an incoming samsync */
1346 if (!ldb_msg_find_element(msg, "msDS-KeyVersionNumber")) {
1347 if (add_keyVersionNumber(ac->module, msg, 0) != LDB_SUCCESS) {
1348 return LDB_ERR_OPERATIONS_ERROR;
1352 h->state = LDB_ASYNC_INIT;
1353 h->status = LDB_SUCCESS;
1355 ac->step = PH_ADD_DO_ADD;
1357 /* perform the operation */
1358 return ldb_next_request(ac->module, ac->down_req);
1361 static int password_hash_mod_async_search_self(struct ldb_async_handle *h);
1363 static int password_hash_modify_async(struct ldb_module *module, struct ldb_request *req)
1365 struct ldb_async_handle *h;
1366 struct ph_async_context *ac;
1367 struct ldb_message_element *sambaAttr;
1368 struct ldb_message_element *ntAttr;
1369 struct ldb_message_element *lmAttr;
1371 ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_async\n");
1373 if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
1374 return ldb_next_request(module, req);
1377 /* nobody must touch password Histories */
1378 if (ldb_msg_find_element(req->op.mod.message, "sambaNTPwdHistory") ||
1379 ldb_msg_find_element(req->op.mod.message, "sambaLMPwdHistory")) {
1380 return LDB_ERR_UNWILLING_TO_PERFORM;
1383 sambaAttr = ldb_msg_find_element(req->op.mod.message, "sambaPassword");
1384 ntAttr = ldb_msg_find_element(req->op.mod.message, "ntPwdHash");
1385 lmAttr = ldb_msg_find_element(req->op.mod.message, "lmPwdHash");
1387 /* check passwords are single valued here */
1388 /* TODO: remove this when passwords will be single valued in schema */
1389 if (sambaAttr && (sambaAttr->num_values > 1)) {
1390 return LDB_ERR_CONSTRAINT_VIOLATION;
1392 if (ntAttr && (ntAttr->num_values > 1)) {
1393 return LDB_ERR_CONSTRAINT_VIOLATION;
1395 if (lmAttr && (lmAttr->num_values > 1)) {
1396 return LDB_ERR_CONSTRAINT_VIOLATION;
1399 /* If no part of this touches the sambaPassword OR ntPwdHash and/or lmPwdHash, then we don't
1400 * need to make any changes. For password changes/set there should
1401 * be a 'delete' or a 'modify' on this attribute. */
1402 /* If the only operation is the deletion of the passwords then go on */
1403 if ( ((!sambaAttr) || ((sambaAttr->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE))
1404 && ((!ntAttr) || ((ntAttr->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE))
1405 && ((!lmAttr) || ((lmAttr->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE)) ) {
1407 return ldb_next_request(module, req);
1410 h = ph_init_handle(req, module, PH_MOD);
1412 return LDB_ERR_OPERATIONS_ERROR;
1414 ac = talloc_get_type(h->private_data, struct ph_async_context);
1416 /* return or own handle to deal with this call */
1417 req->async.handle = h;
1419 /* prepare the first operation */
1420 ac->down_req = talloc_zero(ac, struct ldb_request);
1421 if (ac->down_req == NULL) {
1422 ldb_set_errstring(module->ldb, talloc_asprintf(module->ldb, "Out of memory!"));
1423 return LDB_ERR_OPERATIONS_ERROR;
1426 *(ac->down_req) = *req; /* copy the request */
1428 /* use a new message structure so that we can modify it */
1429 ac->down_req->op.mod.message = ldb_msg_copy_shallow(ac->down_req, req->op.mod.message);
1431 /* - remove any imodification to the password from the first commit
1432 * we will make the real modification later */
1433 if (sambaAttr) ldb_msg_remove_attr(ac->down_req->op.mod.message, "sambaPassword");
1434 if (ntAttr) ldb_msg_remove_attr(ac->down_req->op.mod.message, "ntPwdHash");
1435 if (lmAttr) ldb_msg_remove_attr(ac->down_req->op.mod.message, "lmPwdHash");
1437 /* if there was nothing else to be modify skip to next step */
1438 if (ac->down_req->op.mod.message->num_elements == 0) {
1439 talloc_free(ac->down_req);
1440 ac->down_req = NULL;
1441 return password_hash_mod_async_search_self(h);
1444 ac->down_req->async.context = NULL;
1445 ac->down_req->async.callback = NULL;
1447 ac->step = PH_MOD_DO_REQ;
1449 return ldb_next_request(module, ac->down_req);
1452 static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_async_result *ares)
1454 struct ph_async_context *ac;
1456 if (!context || !ares) {
1457 ldb_set_errstring(ldb, talloc_asprintf(ldb, "NULL Context or Result in callback"));
1458 return LDB_ERR_OPERATIONS_ERROR;
1461 ac = talloc_get_type(context, struct ph_async_context);
1463 /* we are interested only in the single reply (base search) we receive here */
1464 if (ares->type == LDB_REPLY_ENTRY) {
1465 if (ac->search_res != NULL) {
1466 ldb_set_errstring(ldb, talloc_asprintf(ldb, "Too many results"));
1468 return LDB_ERR_OPERATIONS_ERROR;
1471 /* if it is not an entry of type person this is an error */
1472 /* TODO: remove this when sambaPassword will be in schema */
1473 if (!ldb_msg_check_string_attribute(ares->message, "objectClass", "person")) {
1474 ldb_set_errstring(ldb, talloc_asprintf(ldb, "Object class violation"));
1476 return LDB_ERR_OBJECT_CLASS_VIOLATION;
1479 ac->search_res = talloc_steal(ac, ares);
1487 static int password_hash_mod_async_search_self(struct ldb_async_handle *h) {
1489 struct ph_async_context *ac;
1491 ac = talloc_get_type(h->private_data, struct ph_async_context);
1493 /* prepare the search operation */
1494 ac->search_req = talloc_zero(ac, struct ldb_request);
1495 if (ac->search_req == NULL) {
1496 ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
1497 return LDB_ERR_OPERATIONS_ERROR;
1500 ac->search_req->operation = LDB_ASYNC_SEARCH;
1501 ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn;
1502 ac->search_req->op.search.scope = LDB_SCOPE_BASE;
1503 ac->search_req->op.search.tree = ldb_parse_tree(ac->module->ldb, NULL);
1504 if (ac->search_req->op.search.tree == NULL) {
1505 ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "Invalid search filter"));
1506 return LDB_ERR_OPERATIONS_ERROR;
1508 ac->search_req->op.search.attrs = NULL;
1509 ac->search_req->controls = NULL;
1510 ac->search_req->creds = ac->orig_req->creds;
1511 ac->search_req->async.context = ac;
1512 ac->search_req->async.callback = get_self_callback;
1513 ac->search_req->async.timeout = ac->orig_req->async.timeout;
1515 ac->step = PH_MOD_SEARCH_SELF;
1517 return ldb_next_request(ac->module, ac->search_req);
1520 static int password_hash_mod_async_search_dom(struct ldb_async_handle *h) {
1522 struct ph_async_context *ac;
1523 struct dom_sid *domain_sid;
1526 ac = talloc_get_type(h->private_data, struct ph_async_context);
1528 /* get object domain sid */
1529 domain_sid = samdb_result_sid_prefix(ac, ac->search_res->message, "objectSid");
1530 if (domain_sid == NULL) {
1531 ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n");
1532 return LDB_ERR_OPERATIONS_ERROR;
1535 /* get user domain data */
1536 ret = build_domain_data_request(ac, domain_sid);
1537 if (ret != LDB_SUCCESS) {
1541 ac->step = PH_MOD_SEARCH_DOM;
1543 return ldb_next_request(ac->module, ac->dom_req);
1546 static int password_hash_mod_async_do_mod(struct ldb_async_handle *h) {
1548 struct ph_async_context *ac;
1549 struct domain_data *domain;
1550 struct smb_krb5_context *smb_krb5_context;
1551 struct ldb_message_element *sambaAttr;
1552 struct ldb_message *msg;
1555 ac = talloc_get_type(h->private_data, struct ph_async_context);
1557 domain = get_domain_data(ac->module, ac, ac->dom_res);
1558 if (domain == NULL) {
1559 return LDB_ERR_OPERATIONS_ERROR;
1562 ac->mod_req = talloc(ac, struct ldb_request);
1563 if (ac->mod_req == NULL) {
1564 return LDB_ERR_OPERATIONS_ERROR;
1567 *(ac->mod_req) = *(ac->orig_req);
1569 /* use a new message structure so that we can modify it */
1570 ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req);
1572 return LDB_ERR_OPERATIONS_ERROR;
1576 msg->dn = ac->orig_req->op.mod.message->dn;
1578 /* Some operations below require kerberos contexts */
1579 if (smb_krb5_init_context(ac->mod_req, &smb_krb5_context) != 0) {
1580 return LDB_ERR_OPERATIONS_ERROR;
1583 /* we are going to replace the existing krb5key or delete it */
1584 if (ldb_msg_add_empty(msg, "krb5key", LDB_FLAG_MOD_REPLACE) != 0) {
1585 return LDB_ERR_OPERATIONS_ERROR;
1588 /* if we have sambaPassword in the original message add the operatio on it here */
1589 sambaAttr = ldb_msg_find_element(ac->orig_req->op.mod.message, "sambaPassword");
1592 if (ldb_msg_add(msg, sambaAttr, sambaAttr->flags) != 0) {
1593 return LDB_ERR_OPERATIONS_ERROR;
1596 /* we are not deleteing it add password hashes */
1597 if ((sambaAttr->flags & LDB_FLAG_MOD_MASK) != LDB_FLAG_MOD_DELETE) {
1599 /* we can compute new password hashes from the unicode password */
1600 if (add_password_hashes(ac->module, msg, 1) != LDB_SUCCESS) {
1601 return LDB_ERR_OPERATIONS_ERROR;
1604 /* now add krb5 keys based on unicode password */
1605 if (add_krb5_keys_from_password(ac->module, msg, smb_krb5_context, domain,
1606 ldb_msg_find_string(ac->search_res->message, "samAccountName", NULL),
1607 ldb_msg_find_string(ac->search_res->message, "userPrincipalName", NULL),
1608 ldb_msg_check_string_attribute(ac->search_res->message, "objectClass", "computer")
1610 return LDB_ERR_OPERATIONS_ERROR;
1613 /* if the domain properties or the user account controls do not permit
1614 * clear text passwords then wipe out the sambaPassword */
1615 if ((!(domain->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT)) ||
1616 (!(ldb_msg_find_uint(ac->search_res->message, "userAccountControl", 0) & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED))) {
1617 ldb_msg_remove_attr(msg, "sambaPassword");
1623 /* if we don't have sambaPassword or we are trying to delete it try with nt or lm hasehs */
1624 if ((!sambaAttr) || ((sambaAttr->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE)) {
1625 struct ldb_message_element *el;
1627 el = ldb_msg_find_element(ac->orig_req->op.mod.message, "ntPwdHash");
1628 if (ldb_msg_add(msg, el, el->flags) != 0) {
1629 return LDB_ERR_OPERATIONS_ERROR;
1632 el = ldb_msg_find_element(ac->orig_req->op.mod.message, "lmPwdHash");
1633 if (ldb_msg_add(msg, el, el->flags) != 0) {
1634 return LDB_ERR_OPERATIONS_ERROR;
1638 /* add also kr5 keys based on NT the hash */
1639 if (add_krb5_keys_from_NThash(ac->module, msg, smb_krb5_context) != LDB_SUCCESS) {
1640 return LDB_ERR_OPERATIONS_ERROR;
1643 /* set change time */
1644 if (set_pwdLastSet(ac->module, msg, 1) != LDB_SUCCESS) {
1645 return LDB_ERR_OPERATIONS_ERROR;
1648 /* don't touch it if a value is set. It could be an incoming samsync */
1649 if (add_keyVersionNumber(ac->module, msg,
1650 ldb_msg_find_uint(msg, "msDS-KeyVersionNumber", 0)
1652 return LDB_ERR_OPERATIONS_ERROR;
1655 if ((phlen = samdb_result_uint(ac->dom_res->message, "pwdHistoryLength", 0)) > 0) {
1656 if (setPwdHistory(ac->module, msg, ac->search_res->message, phlen) != LDB_SUCCESS) {
1657 return LDB_ERR_OPERATIONS_ERROR;
1661 h->state = LDB_ASYNC_INIT;
1662 h->status = LDB_SUCCESS;
1664 ac->step = PH_MOD_DO_MOD;
1666 /* perform the search */
1667 return ldb_next_request(ac->module, ac->mod_req);
1670 static int ph_async_wait(struct ldb_async_handle *handle) {
1671 struct ph_async_context *ac;
1674 if (!handle || !handle->private_data) {
1675 return LDB_ERR_OPERATIONS_ERROR;
1678 if (handle->state == LDB_ASYNC_DONE) {
1679 return handle->status;
1682 handle->state = LDB_ASYNC_PENDING;
1683 handle->status = LDB_SUCCESS;
1685 ac = talloc_get_type(handle->private_data, struct ph_async_context);
1688 case PH_ADD_SEARCH_DOM:
1689 ret = ldb_async_wait(ac->dom_req->async.handle, LDB_WAIT_NONE);
1691 if (ret != LDB_SUCCESS) {
1692 handle->status = ret;
1695 if (ac->dom_req->async.handle->status != LDB_SUCCESS) {
1696 handle->status = ac->dom_req->async.handle->status;
1700 if (ac->dom_req->async.handle->state != LDB_ASYNC_DONE) {
1704 /* domain search done, go on */
1705 return password_hash_add_async_do_add(handle);
1708 ret = ldb_async_wait(ac->down_req->async.handle, LDB_WAIT_NONE);
1710 if (ret != LDB_SUCCESS) {
1711 handle->status = ret;
1714 if (ac->down_req->async.handle->status != LDB_SUCCESS) {
1715 handle->status = ac->down_req->async.handle->status;
1719 if (ac->down_req->async.handle->state != LDB_ASYNC_DONE) {
1726 ret = ldb_async_wait(ac->down_req->async.handle, LDB_WAIT_NONE);
1728 if (ret != LDB_SUCCESS) {
1729 handle->status = ret;
1732 if (ac->down_req->async.handle->status != LDB_SUCCESS) {
1733 handle->status = ac->down_req->async.handle->status;
1737 if (ac->down_req->async.handle->state != LDB_ASYNC_DONE) {
1741 /* non-password mods done, go on */
1742 return password_hash_mod_async_search_self(handle);
1744 case PH_MOD_SEARCH_SELF:
1745 ret = ldb_async_wait(ac->search_req->async.handle, LDB_WAIT_NONE);
1747 if (ret != LDB_SUCCESS) {
1748 handle->status = ret;
1751 if (ac->search_req->async.handle->status != LDB_SUCCESS) {
1752 handle->status = ac->search_req->async.handle->status;
1756 if (ac->search_req->async.handle->state != LDB_ASYNC_DONE) {
1760 /* self search done, go on */
1761 return password_hash_mod_async_search_dom(handle);
1763 case PH_MOD_SEARCH_DOM:
1764 ret = ldb_async_wait(ac->dom_req->async.handle, LDB_WAIT_NONE);
1766 if (ret != LDB_SUCCESS) {
1767 handle->status = ret;
1770 if (ac->dom_req->async.handle->status != LDB_SUCCESS) {
1771 handle->status = ac->dom_req->async.handle->status;
1775 if (ac->dom_req->async.handle->state != LDB_ASYNC_DONE) {
1779 /* domain search done, go on */
1780 return password_hash_mod_async_do_mod(handle);
1783 ret = ldb_async_wait(ac->mod_req->async.handle, LDB_WAIT_NONE);
1785 if (ret != LDB_SUCCESS) {
1786 handle->status = ret;
1789 if (ac->mod_req->async.handle->status != LDB_SUCCESS) {
1790 handle->status = ac->mod_req->async.handle->status;
1794 if (ac->mod_req->async.handle->state != LDB_ASYNC_DONE) {
1801 ret = LDB_ERR_OPERATIONS_ERROR;
1808 handle->state = LDB_ASYNC_DONE;
1812 static int ph_async_wait_all(struct ldb_async_handle *handle) {
1816 while (handle->state != LDB_ASYNC_DONE) {
1817 ret = ph_async_wait(handle);
1818 if (ret != LDB_SUCCESS) {
1823 return handle->status;
1826 static int password_hash_async_wait(struct ldb_async_handle *handle, enum ldb_async_wait_type type)
1828 if (type == LDB_WAIT_ALL) {
1829 return ph_async_wait_all(handle);
1831 return ph_async_wait(handle);
1835 static int password_hash_request(struct ldb_module *module, struct ldb_request *req)
1837 switch (req->operation) {
1840 return password_hash_add(module, req);
1842 case LDB_REQ_MODIFY:
1843 return password_hash_modify(module, req);
1846 return password_hash_add_async(module, req);
1848 case LDB_ASYNC_MODIFY:
1849 return password_hash_modify_async(module, req);
1852 return ldb_next_request(module, req);
1857 static const struct ldb_module_ops password_hash_ops = {
1858 .name = "password_hash",
1859 .request = password_hash_request,
1860 .async_wait = password_hash_async_wait
1864 int password_hash_module_init(void)
1866 return ldb_register_module(&password_hash_ops);