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 unicodePwd 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/ndr_misc.h"
38 #include "librpc/gen_ndr/ndr_samr.h"
39 #include "system/kerberos.h"
40 #include "auth/kerberos/kerberos.h"
41 #include "system/time.h"
42 #include "dsdb/samdb/samdb.h"
46 /* If we have decided there is reason to work on this request, then
47 * setup all the password hash types correctly.
49 * If the administrator doesn't want the unicodePwd stored (set in the
50 * domain and per-account policies) then we must strip that out before
51 * we do the first operation.
53 * Once this is done (which could update anything at all), we
54 * calculate the password hashes.
56 * This function must not only update the ntPwdHash, lmPwdHash and
57 * krb5Key fields, it must also atomicly increment the
58 * msDS-KeyVersionNumber. We should be in a transaction, so all this
59 * should be quite safe...
61 * Finally, if the administrator has requested that a password history
62 * be maintained, then this should also be written out.
67 static int password_hash_handle(struct ldb_module *module, struct ldb_request *req,
68 const struct ldb_message *msg)
70 int ret, old_ret = -1;
71 uint_t pwdProperties, pwdHistoryLength;
72 uint_t userAccountControl;
73 const char *dnsDomain, *realm;
74 const char *unicodePwd;
75 struct samr_Password *lmPwdHistory, *ntPwdHistory;
76 struct samr_Password *lmPwdHash, *ntPwdHash;
77 struct samr_Password *lmOldHash = NULL, *ntOldHash = NULL;
78 struct samr_Password *new_lmPwdHistory, *new_ntPwdHistory;
79 struct samr_Password local_lmNewHash, local_ntNewHash;
80 int lmPwdHistory_len, ntPwdHistory_len;
82 struct dom_sid *domain_sid;
83 time_t now = time(NULL);
86 krb5_error_code krb5_ret;
88 struct smb_krb5_context *smb_krb5_context;
90 struct ldb_message_element *attribute;
91 struct ldb_dn *dn = msg->dn;
92 struct ldb_message *msg2;
94 struct ldb_request search_request;
95 struct ldb_request modify_request;
96 struct ldb_request modified_orig_request;
97 struct ldb_result *res, *dom_res, *old_res;
99 struct ldb_message_element *objectclasses;
100 struct ldb_val computer_val;
101 struct ldb_val person_val;
104 struct ldb_message *modify_msg;
106 const char *domain_expression;
107 const char *old_user_attrs[] = { "lmPwdHash", "ntPwdHash", NULL };
108 const char *user_attrs[] = { "userAccountControl", "lmPwdHistory",
111 "objectSid", "msDS-KeyVersionNumber",
112 "objectClass", "userPrincipalName",
115 const char * const domain_attrs[] = { "pwdProperties", "pwdHistoryLength",
120 /* Do the original action */
122 /* If no part of this touches the unicodePwd, then we don't
123 * need to make any changes. For password changes/set there should
124 * be a 'delete' or a 'modify' on this attribute. */
125 if ((attribute = ldb_msg_find_element(msg, "unicodePwd")) == NULL ) {
126 return ldb_next_request(module, req);
129 mem_ctx = talloc_new(module);
131 return LDB_ERR_OPERATIONS_ERROR;
134 if (req->operation == LDB_REQ_MODIFY) {
135 /* Look up the old ntPwdHash and lmPwdHash values, so
136 * we can later place these into the password
139 search_request.operation = LDB_REQ_SEARCH;
140 search_request.op.search.base = dn;
141 search_request.op.search.scope = LDB_SCOPE_BASE;
142 search_request.op.search.tree = ldb_parse_tree(module->ldb, NULL);
143 search_request.op.search.attrs = old_user_attrs;
145 old_ret = ldb_next_request(module, &search_request);
148 /* we can't change things untill we copy it */
149 msg2 = ldb_msg_copy_shallow(mem_ctx, msg);
151 /* look again, this time at the copied attribute */
152 if (!msg2 || (attribute = ldb_msg_find_element(msg2, "unicodePwd")) == NULL ) {
153 /* Gah? where did it go? Oh well... */
154 return LDB_ERR_OPERATIONS_ERROR;
157 /* Wipe out the unicodePwd attribute set, we will handle it in
158 * the second modify. We might not want it written to disk */
160 if (req->operation == LDB_REQ_ADD) {
161 if (attribute->num_values != 1) {
162 ldb_set_errstring(module,
163 talloc_asprintf(mem_ctx, "unicodePwd_handle: "
164 "attempted set of multiple unicodePwd attributes on %s rejected",
165 ldb_dn_linearize(mem_ctx, dn)));
166 return LDB_ERR_CONSTRAINT_VIOLAION;
169 unicodePwd = (const char *)attribute->values[0].data;
170 ldb_msg_remove_attr(msg2, "unicodePwd");
171 } else if (((attribute->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_ADD)
172 || ((attribute->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE)) {
173 if (attribute->num_values != 1) {
174 return LDB_ERR_CONSTRAINT_VIOLAION;
177 unicodePwd = (const char *)attribute->values[0].data;
178 ldb_msg_remove_attr(msg2, "unicodePwd");
183 modified_orig_request = *req;
184 switch (modified_orig_request.operation) {
186 modified_orig_request.op.add.message = msg2;
189 modified_orig_request.op.mod.message = msg2;
193 /* Send the (modified) request of the original caller down to the database */
194 ret = ldb_next_request(module, &modified_orig_request);
199 /* While we do the search first (for the old password hashes),
200 * we don't want to override any error that the modify may
201 * have returned. Now check the error */
202 if (req->operation == LDB_REQ_MODIFY) {
204 talloc_free(mem_ctx);
208 /* Find out the old passwords details of the user */
209 old_res = search_request.op.search.res;
211 if (old_res->count != 1) {
212 ldb_set_errstring(module,
213 talloc_asprintf(mem_ctx, "password_hash_handle: "
214 "(pre) search for %s found %d != 1 objects, for entry we just modified",
215 ldb_dn_linearize(mem_ctx, dn),
217 /* What happend? The above add/modify worked... */
218 talloc_free(mem_ctx);
219 return LDB_ERR_NO_SUCH_OBJECT;
222 lmOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0], "lmPwdHash");
223 ntOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0], "ntPwdHash");
226 /* Start finding out details we need for the second modify.
227 * We do this after the first add/modify because other modules
228 * will have filled in the templates, and we may have had
229 * things like the username (affecting the salt) changed along
230 * with the password. */
232 /* Now find out what is on the entry after the above add/modify */
233 search_request.operation = LDB_REQ_SEARCH;
234 search_request.op.search.base = dn;
235 search_request.op.search.scope = LDB_SCOPE_BASE;
236 search_request.op.search.tree = ldb_parse_tree(module->ldb, NULL);
237 search_request.op.search.attrs = user_attrs;
239 ret = ldb_next_request(module, &search_request);
241 talloc_free(mem_ctx);
245 /* Find out the full details of the user */
246 res = search_request.op.search.res;
247 if (res->count != 1) {
248 ldb_set_errstring(module,
249 talloc_asprintf(mem_ctx, "password_hash_handle: "
250 "search for %s found %d != 1 objects, for entry we just added/modified",
251 ldb_dn_linearize(mem_ctx, dn),
253 /* What happend? The above add/modify worked... */
254 talloc_free(mem_ctx);
255 return LDB_ERR_NO_SUCH_OBJECT;
258 userAccountControl = samdb_result_uint(res->msgs[0], "userAccountControl", 0);
259 lmPwdHistory_len = samdb_result_hashes(mem_ctx, res->msgs[0],
260 "lmPwdHistory", &lmPwdHistory);
261 ntPwdHistory_len = samdb_result_hashes(mem_ctx, res->msgs[0],
262 "ntPwdHistory", &ntPwdHistory);
263 ntPwdHash = samdb_result_hash(mem_ctx, res->msgs[0], "ntPwdHash");
264 kvno = samdb_result_uint(res->msgs[0], "msDS-KeyVersionNumber", 0);
266 domain_sid = samdb_result_sid_prefix(mem_ctx, res->msgs[0], "objectSid");
269 objectclasses = ldb_msg_find_element(res->msgs[0], "objectClass");
270 person_val = data_blob_string_const("person");
272 if (!objectclasses || !ldb_msg_find_val(objectclasses, &person_val)) {
273 /* Not a 'person', so the rest of this doesn't make
274 * sense. How we got a unicodePwd this far I don't
276 ldb_set_errstring(module,
277 talloc_asprintf(mem_ctx, "password_hash_handle: "
278 "attempted set of unicodePwd on non-'person' object %s rejected",
279 ldb_dn_linearize(mem_ctx, dn)));
280 talloc_free(mem_ctx);
281 return LDB_ERR_CONSTRAINT_VIOLAION;
284 computer_val = data_blob_string_const("computer");
286 if (ldb_msg_find_val(objectclasses, &computer_val)) {
292 domain_expression = talloc_asprintf(mem_ctx, "(&(objectSid=%s)(objectClass=domain))",
293 ldap_encode_ndr_dom_sid(mem_ctx, domain_sid));
295 /* Find the user's domain, then find out the domain password
297 ret = ldb_search(module->ldb, NULL, LDB_SCOPE_SUBTREE, domain_expression,
298 domain_attrs, &dom_res);
300 talloc_free(mem_ctx);
304 if (dom_res->count != 1) {
305 /* What happend? The user we are modifying must be odd... */
306 ldb_set_errstring(module,
307 talloc_asprintf(mem_ctx, "password_hash_handle: "
308 "search for domain %s found %d != 1 objects",
309 dom_sid_string(mem_ctx, domain_sid),
311 talloc_free(mem_ctx);
312 return LDB_ERR_NO_SUCH_OBJECT;
315 pwdProperties = samdb_result_uint(dom_res->msgs[0], "pwdProperties", 0);
316 pwdHistoryLength = samdb_result_uint(dom_res->msgs[0], "pwdHistoryLength", 0);
317 dnsDomain = ldb_msg_find_string(dom_res->msgs[0], "dnsDomain", NULL);
318 realm = strupper_talloc(mem_ctx, dnsDomain);
320 /* Some operations below require kerberos contexts */
321 if (smb_krb5_init_context(mem_ctx, &smb_krb5_context) != 0) {
322 talloc_free(mem_ctx);
323 return LDB_ERR_OPERATIONS_ERROR;
326 /* Prepare the modifications to set all the hash/key types */
327 modify_msg = ldb_msg_new(req);
328 modify_msg->dn = talloc_reference(modify_msg, dn);
330 #define CHECK_RET(x) \
333 if (check_ret != LDB_SUCCESS) { \
334 talloc_free(mem_ctx); \
339 /* Setup krb5Key (we want to either delete an existing value,
340 * or replace with a new one). Both the unicode and NT hash
341 * only branches append keys to this multivalued entry. */
342 CHECK_RET(ldb_msg_add_empty(modify_msg, "krb5Key", LDB_FLAG_MOD_REPLACE));
343 /* Yay, we can compute new password hashes from the unicode
346 Principal *salt_principal;
347 const char *user_principal_name = ldb_msg_find_string(res->msgs[0], "userPrincipalName", NULL);
352 /* compute the new nt and lm hashes */
353 if (E_deshash(unicodePwd, local_lmNewHash.hash)) {
354 lmPwdHash = &local_lmNewHash;
358 E_md4hash(unicodePwd, local_ntNewHash.hash);
359 ntPwdHash = &local_ntNewHash;
360 CHECK_RET(ldb_msg_add_empty(modify_msg, "ntPwdHash",
361 LDB_FLAG_MOD_REPLACE));
362 CHECK_RET(samdb_msg_add_hash(module->ldb, req,
363 modify_msg, "ntPwdHash",
365 CHECK_RET(ldb_msg_add_empty(modify_msg, "lmPwdHash",
366 LDB_FLAG_MOD_REPLACE));
368 CHECK_RET(samdb_msg_add_hash(module->ldb, req,
369 modify_msg, "lmPwdHash",
373 /* Many, many thanks to lukeh@padl.com for this
374 * algorithm, described in his Nov 10 2004 mail to
375 * samba-technical@samba.org */
378 /* Determine a salting principal */
379 char *samAccountName = talloc_strdup(mem_ctx, ldb_msg_find_string(res->msgs[0], "samAccountName", NULL));
381 if (!samAccountName) {
382 ldb_set_errstring(module,
383 talloc_asprintf(mem_ctx, "password_hash_handle: "
384 "generation of new kerberos keys failed: %s is a computer without a samAccountName",
385 ldb_dn_linearize(mem_ctx, dn)));
386 talloc_free(mem_ctx);
387 return LDB_ERR_OPERATIONS_ERROR;
389 if (samAccountName[strlen(samAccountName)-1] == '$') {
390 samAccountName[strlen(samAccountName)-1] = '\0';
392 saltbody = talloc_asprintf(mem_ctx, "%s.%s", samAccountName, dnsDomain);
394 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, "host", saltbody, NULL);
395 } else if (user_principal_name) {
397 user_principal_name = talloc_strdup(mem_ctx, user_principal_name);
398 if (!user_principal_name) {
399 talloc_free(mem_ctx);
400 return LDB_ERR_OPERATIONS_ERROR;
402 p = strchr(user_principal_name, '@');
406 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, user_principal_name, NULL);
409 const char *samAccountName = ldb_msg_find_string(res->msgs[0], "samAccountName", NULL);
410 if (!samAccountName) {
411 ldb_set_errstring(module,
412 talloc_asprintf(mem_ctx, "password_hash_handle: "
413 "generation of new kerberos keys failed: %s has no samAccountName",
414 ldb_dn_linearize(mem_ctx, dn)));
415 talloc_free(mem_ctx);
416 return LDB_ERR_OPERATIONS_ERROR;
418 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, samAccountName, NULL);
423 ldb_set_errstring(module,
424 talloc_asprintf(mem_ctx, "password_hash_handle: "
425 "generation of a saltking principal failed: %s",
426 smb_get_krb5_error_message(smb_krb5_context->krb5_context,
427 krb5_ret, mem_ctx)));
428 talloc_free(mem_ctx);
429 return LDB_ERR_OPERATIONS_ERROR;
432 /* TODO: We may wish to control the encryption types chosen in future */
433 krb5_ret = hdb_generate_key_set_password(smb_krb5_context->krb5_context,
434 salt_principal, unicodePwd, &keys, &num_keys);
435 krb5_free_principal(smb_krb5_context->krb5_context, salt_principal);
438 ldb_set_errstring(module,
439 talloc_asprintf(mem_ctx, "password_hash_handle: "
440 "generation of new kerberos keys failed: %s",
441 smb_get_krb5_error_message(smb_krb5_context->krb5_context,
442 krb5_ret, mem_ctx)));
443 talloc_free(mem_ctx);
444 return LDB_ERR_OPERATIONS_ERROR;
449 for (i=0; i < num_keys; i++) {
455 if (keys[i].key.keytype == ENCTYPE_ARCFOUR_HMAC) {
456 /* We might end up doing this below:
457 * This ensures we get the unicode
458 * conversion right. This should also
459 * be fixed in the Heimdal libs */
462 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &keys[i], &len, krb5_ret);
464 val.data = talloc_memdup(req, buf, len);
467 if (!val.data || krb5_ret) {
468 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
469 talloc_free(mem_ctx);
470 return LDB_ERR_OPERATIONS_ERROR;
472 ret = ldb_msg_add_value(modify_msg, "krb5Key", &val);
473 if (ret != LDB_SUCCESS) {
474 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
475 talloc_free(mem_ctx);
480 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
483 /* Possibly kill off the cleartext or store it */
484 CHECK_RET(ldb_msg_add_empty(modify_msg, "unicodePwd", LDB_FLAG_MOD_REPLACE));
486 if (unicodePwd && (pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT) &&
487 (userAccountControl & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) {
488 CHECK_RET(ldb_msg_add_string(modify_msg, "unicodePwd", unicodePwd));
491 /* Even if we didn't get a unicodePwd, we can still setup
492 * krb5Key from the NT hash.
494 * This is an append, so it works with the 'continue' in the
495 * unicode loop above, to use Samba's NT hash function, which
496 * is more correct than Heimdal's
506 key.salt = NULL; /* No salt for this enc type */
508 krb5_ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
509 ENCTYPE_ARCFOUR_HMAC,
510 ntPwdHash->hash, sizeof(ntPwdHash->hash),
513 return LDB_ERR_OPERATIONS_ERROR;
515 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &key, &len, krb5_ret);
516 krb5_free_keyblock_contents(smb_krb5_context->krb5_context,
519 val.data = talloc_memdup(req, buf, len);
522 if (!val.data || ret) {
523 return LDB_ERR_OPERATIONS_ERROR;
525 CHECK_RET(ldb_msg_add_value(modify_msg, "krb5Key", &val));
528 /* If the original caller did anything with pwdLastSet then skip this. It could be an incoming samsync */
529 if ((attribute = ldb_msg_find_element(msg, "pwdLastSet")) == NULL ) {
530 /* Update the password last set time */
531 unix_to_nt_time(&now_nt, now);
532 CHECK_RET(ldb_msg_add_empty(modify_msg, "pwdLastSet", LDB_FLAG_MOD_REPLACE));
533 CHECK_RET(samdb_msg_add_uint64(module->ldb, mem_ctx, modify_msg, "pwdLastSet", now_nt));
536 /* If the original caller did anything with "msDS-KeyVersionNumber" then skip this. It could be an incoming samsync */
537 if ((attribute = ldb_msg_find_element(msg, "msDS-KeyVersionNumber")) == NULL ) {
539 CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
540 LDB_FLAG_MOD_REPLACE));
541 CHECK_RET(samdb_msg_add_uint(module->ldb, mem_ctx, modify_msg, "msDS-KeyVersionNumber", kvno + 1));
543 /* While we should be in a transaction, go one extra
544 * step in the dance for an 'atomic' increment. This
545 * may be of value against remote LDAP servers. (Note
546 * however that Mulitmaster replication stil offers no
549 struct ldb_val old_kvno, new_kvno;
550 old_kvno.data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", kvno);
551 if (!old_kvno.data) {
554 old_kvno.length = strlen((char *)old_kvno.data);
556 new_kvno.data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", kvno + 1);
557 if (!new_kvno.data) {
560 new_kvno.length = strlen((char *)new_kvno.data);
562 CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
563 LDB_FLAG_MOD_DELETE));
564 CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
566 modify_msg->elements[modify_msg->num_elements - 2].num_values = 1;
567 modify_msg->elements[modify_msg->num_elements - 2].values = &old_kvno;
568 modify_msg->elements[modify_msg->num_elements - 1].num_values = 1;
569 modify_msg->elements[modify_msg->num_elements - 1].values = &new_kvno;
573 CHECK_RET(ldb_msg_add_empty(modify_msg, "lmPwdHistory",
574 LDB_FLAG_MOD_REPLACE));
575 CHECK_RET(ldb_msg_add_empty(modify_msg, "ntPwdHistory",
576 LDB_FLAG_MOD_REPLACE));
578 /* If we have something to put into the history, or an old
579 * history element to expire, update the history */
580 if (pwdHistoryLength > 0 &&
581 ((ntPwdHistory_len > 0) || (lmPwdHistory_len > 0)
582 || lmOldHash || ntOldHash)) {
583 /* store the password history */
584 new_lmPwdHistory = talloc_array(mem_ctx, struct samr_Password,
586 if (!new_lmPwdHistory) {
587 return LDB_ERR_OPERATIONS_ERROR;
589 new_ntPwdHistory = talloc_array(mem_ctx, struct samr_Password,
591 if (!new_ntPwdHistory) {
592 return LDB_ERR_OPERATIONS_ERROR;
594 for (i=0;i<MIN(pwdHistoryLength-1, lmPwdHistory_len);i++) {
595 new_lmPwdHistory[i+1] = lmPwdHistory[i];
597 for (i=0;i<MIN(pwdHistoryLength-1, ntPwdHistory_len);i++) {
598 new_ntPwdHistory[i+1] = ntPwdHistory[i];
601 /* Don't store 'long' passwords in the LM history,
602 but make sure to 'expire' one password off the other end */
604 new_lmPwdHistory[0] = *lmOldHash;
606 ZERO_STRUCT(new_lmPwdHistory[0]);
608 lmPwdHistory_len = MIN(lmPwdHistory_len + 1, pwdHistoryLength);
610 /* Likewise, we might not have a new NT password (lm
611 * only password change function) */
613 new_ntPwdHistory[0] = *ntOldHash;
615 ZERO_STRUCT(new_ntPwdHistory[0]);
617 ntPwdHistory_len = MIN(ntPwdHistory_len + 1, pwdHistoryLength);
619 CHECK_RET(samdb_msg_add_hashes(module->ldb, mem_ctx, modify_msg,
624 CHECK_RET(samdb_msg_add_hashes(module->ldb, mem_ctx, modify_msg,
630 /* Too much code above, we should check we got it close to reasonable */
631 CHECK_RET(ldb_msg_sanity_check(modify_msg));
633 modify_request.operation = LDB_REQ_MODIFY;
634 modify_request.op.mod.message = modify_msg;
636 ret = ldb_next_request(module, &modify_request);
638 talloc_free(mem_ctx);
642 /* add_record: do things with the unicodePwd attribute */
643 static int password_hash_add(struct ldb_module *module, struct ldb_request *req)
645 const struct ldb_message *msg = req->op.add.message;
647 ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_record\n");
649 if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
650 return ldb_next_request(module, req);
653 return password_hash_handle(module, req, msg);
656 /* modify_record: do things with the unicodePwd attribute */
657 static int password_hash_modify(struct ldb_module *module, struct ldb_request *req)
659 const struct ldb_message *msg = req->op.mod.message;
661 ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_modify_record\n");
663 if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
664 return ldb_next_request(module, req);
667 return password_hash_handle(module, req, msg);
670 static int password_hash_request(struct ldb_module *module, struct ldb_request *req)
672 switch (req->operation) {
675 return password_hash_add(module, req);
678 return password_hash_modify(module, req);
681 return ldb_next_request(module, req);
686 static const struct ldb_module_ops password_hash_ops = {
687 .name = "password_hash",
688 .request = password_hash_request
692 /* the init function */
693 #ifdef HAVE_DLOPEN_DISABLED
694 struct ldb_module *init_module(struct ldb_context *ldb, const char *options[])
696 struct ldb_module *password_hash_module_init(struct ldb_context *ldb, const char *options[])
699 struct ldb_module *ctx;
701 ctx = talloc(ldb, struct ldb_module);
705 ctx->private_data = NULL;
707 ctx->prev = ctx->next = NULL;
708 ctx->ops = &password_hash_ops;