r15795: Try to use the async code by default
[jra/samba/.git] / source4 / dsdb / samdb / ldb_modules / password_hash.c
1 /* 
2    ldb database module
3
4    Copyright (C) Simo Sorce  2004
5    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
6    Copyright (C) Andrew Tridgell 2004
7
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.
12    
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.
17    
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.
21 */
22
23 /*
24  *  Name: ldb
25  *
26  *  Component: ldb password_hash module
27  *
28  *  Description: correctly update hash values based on changes to sambaPassword and friends
29  *
30  *  Author: Andrew Bartlett
31  */
32
33 #include "includes.h"
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"
45 #include "ads.h"
46 #include "hdb.h"
47
48 /* If we have decided there is reason to work on this request, then
49  * setup all the password hash types correctly.
50  *
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.
54  *
55  * Once this is done (which could update anything at all), we
56  * calculate the password hashes.
57  *
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...
62  *
63  * Finally, if the administrator has requested that a password history
64  * be maintained, then this should also be written out.
65  *
66  */
67
68
69 static int password_hash_handle(struct ldb_module *module, struct ldb_request *req, 
70                              const struct ldb_message *msg)
71 {
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;
83         uint_t kvno;
84         struct dom_sid *domain_sid;
85         time_t now = time(NULL);
86         NTTIME now_nt;
87         int i;
88         krb5_error_code krb5_ret;
89
90         struct smb_krb5_context *smb_krb5_context;
91
92         struct ldb_message_element *attribute;
93         struct ldb_dn *dn = msg->dn;
94         struct ldb_message *msg2;
95
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;
100
101         struct ldb_message_element *objectclasses;
102         struct ldb_val computer_val;
103         struct ldb_val person_val;
104         BOOL is_computer;
105
106         struct ldb_message *modify_msg;
107
108         const char *domain_expression;
109         const char *old_user_attrs[] = { "lmPwdHash", "ntPwdHash", NULL };
110         const char *user_attrs[] = { "userAccountControl", "sambaLMPwdHistory", 
111                                      "sambaNTPwdHistory", 
112                                      "ntPwdHash", 
113                                      "objectSid", "msDS-KeyVersionNumber", 
114                                      "objectClass", "userPrincipalName",
115                                      "samAccountName", 
116                                      NULL };
117         const char * const domain_attrs[] = { "pwdProperties", "pwdHistoryLength", 
118                                               "dnsDomain", NULL };
119
120         TALLOC_CTX *mem_ctx;
121
122         /* Do the original action */
123         
124         mem_ctx = talloc_new(module);
125         if (!mem_ctx) {
126                 return LDB_ERR_OPERATIONS_ERROR;
127         }
128
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;
134                 }
135
136                 /* Look up the old ntPwdHash and lmPwdHash values, so
137                  * we can later place these into the password
138                  * history */
139
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;
146                 
147                 old_ret = ldb_next_request(module, search_request);
148         }
149
150         /* we can't change things untill we copy it */
151         msg2 = ldb_msg_copy_shallow(mem_ctx, msg);
152
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;
158         }
159
160         /* Wipe out the sambaPassword attribute set, we will handle it in
161          * the second modify.  We might not want it written to disk */
162         
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;
171                 }
172
173                 if (attribute->num_values == 1) {
174                         sambaPassword = (const char *)attribute->values[0].data;
175                         ldb_msg_remove_attr(msg2, "sambaPassword");
176                 }
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;
186                 }
187                 
188                 if (attribute->num_values == 1) {
189                         sambaPassword = (const char *)attribute->values[0].data;
190                         ldb_msg_remove_attr(msg2, "sambaPassword");
191                 }
192         }
193
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;
198         }
199
200         *modified_orig_request = *req;
201         switch (modified_orig_request->operation) {
202         case LDB_REQ_ADD:
203                 modified_orig_request->op.add.message = msg2;
204                 break;
205         case LDB_REQ_MODIFY:
206                 modified_orig_request->op.mod.message = msg2;
207                 break;
208         default:
209                 return LDB_ERR_OPERATIONS_ERROR;
210         }
211
212         /* Send the (modified) request of the original caller down to the database */
213         ret = ldb_next_request(module, modified_orig_request);
214         if (ret) {
215                 talloc_free(mem_ctx);
216                 return ret;
217         }
218
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) {
223                 if (old_ret) {
224                         talloc_free(mem_ctx);
225                         return old_ret;
226                 }
227
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);
232                 
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),
238                                                           old_res->count));
239                         /* What happend?  The above add/modify worked... */
240                         talloc_free(mem_ctx);
241                         return LDB_ERR_NO_SUCH_OBJECT;
242                 }
243
244                 lmOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0],   "lmPwdHash");
245                 ntOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0],   "ntPwdHash");
246         }
247
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. */
253
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;
259         }
260
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;
267         
268         ret = ldb_next_request(module, search_request);
269         if (ret) {
270                 talloc_free(mem_ctx);
271                 return ret;
272         }
273
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);
278
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),
284                                                   res->count));
285                 /* What happend?  The above add/modify worked... */
286                 talloc_free(mem_ctx);
287                 return LDB_ERR_NO_SUCH_OBJECT;
288         }
289
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);
297
298         domain_sid         = samdb_result_sid_prefix(mem_ctx, res->msgs[0], "objectSid");
299
300         
301         objectclasses = ldb_msg_find_element(res->msgs[0], "objectClass");
302         person_val = data_blob_string_const("person");
303         
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
307                  * know... */
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;
314         }
315
316         computer_val = data_blob_string_const("computer");
317         
318         if (ldb_msg_find_val(objectclasses, &computer_val)) {
319                 is_computer = True;
320         } else {
321                 is_computer = False;
322         }
323         
324         domain_expression  = talloc_asprintf(mem_ctx, "(&(objectSid=%s)(objectClass=domain))", 
325                                              ldap_encode_ndr_dom_sid(mem_ctx, domain_sid));
326
327         /* Find the user's domain, then find out the domain password
328          * properties */
329         ret = ldb_search(module->ldb, NULL, LDB_SCOPE_SUBTREE, domain_expression, 
330                          domain_attrs, &dom_res);
331         if (ret) {
332                 talloc_free(mem_ctx);
333                 return ret;
334         }
335
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),
342                                                   dom_res->count));
343                 talloc_free(mem_ctx);
344                 return LDB_ERR_NO_SUCH_OBJECT;
345         }
346
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);
351
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;
356         }
357
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);
361
362 #define CHECK_RET(x) \
363         do {                                    \
364                 int check_ret = x;              \
365                 if (check_ret != LDB_SUCCESS) { \
366                         talloc_free(mem_ctx);   \
367                         return check_ret;       \
368                 }                               \
369         } while(0)
370
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));
375
376         /* Yay, we can compute new password hashes from the unicode
377          * password */
378         if (sambaPassword) {
379                 Principal *salt_principal;
380                 const char *user_principal_name = ldb_msg_find_string(res->msgs[0], "userPrincipalName", NULL);
381                 
382                 Key *keys;
383                 size_t num_keys;
384
385                 /* compute the new nt and lm hashes */
386                 if (E_deshash(sambaPassword, local_lmNewHash.hash)) {
387                         lmPwdHash = &local_lmNewHash;
388                 } else {
389                         lmPwdHash = NULL;
390                 }
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", 
397                                              ntPwdHash));
398                 CHECK_RET(ldb_msg_add_empty(modify_msg, "lmPwdHash", 
399                                             LDB_FLAG_MOD_REPLACE));
400                 if (lmPwdHash) {
401                         CHECK_RET(samdb_msg_add_hash(module->ldb, req, 
402                                                      modify_msg, "lmPwdHash", 
403                                                      lmPwdHash));
404                 }
405
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 */
409
410                 if (is_computer) {
411                         /* Determine a salting principal */
412                         char *samAccountName = talloc_strdup(mem_ctx, ldb_msg_find_string(res->msgs[0], "samAccountName", NULL));
413                         char *saltbody;
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;
421                         }
422                         if (samAccountName[strlen(samAccountName)-1] == '$') {
423                                 samAccountName[strlen(samAccountName)-1] = '\0';
424                         }
425                         saltbody = talloc_asprintf(mem_ctx, "%s.%s", samAccountName, dnsDomain);
426                         
427                         krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, "host", saltbody, NULL);
428                 } else if (user_principal_name) {
429                         char *p;
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;
434                         } else {
435                                 p = strchr(user_principal_name, '@');
436                                 if (p) {
437                                         p[0] = '\0';
438                                 }
439                                 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, user_principal_name, NULL);
440                         } 
441                 } else {
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;
450                         }
451                         krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, samAccountName, NULL);
452                 }
453
454
455                 if (krb5_ret) {
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;
463                 }
464
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);
469
470                 if (krb5_ret) {
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;
478                 }
479
480                 /* Walking all the key types generated, transform each
481                  * key into an ASN.1 blob
482                  */
483                 for (i=0; i < num_keys; i++) {
484                         unsigned char *buf;
485                         size_t buf_size;
486                         size_t len;
487                         struct ldb_val val;
488                         
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 */
494                                 continue;
495                         }
496                         ASN1_MALLOC_ENCODE(Key, buf, buf_size, &keys[i], &len, krb5_ret);
497                         if (krb5_ret) {
498                                 return LDB_ERR_OPERATIONS_ERROR;
499                         }
500                         
501                         val.data = talloc_memdup(req, buf, len);
502                         val.length = len;
503                         free(buf);
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;
508                         }
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);
513                                 return ret;
514                         }
515                 }
516                 
517                 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
518         }
519
520         /* Possibly kill off the cleartext or store it */
521         CHECK_RET(ldb_msg_add_empty(modify_msg, "sambaPassword", LDB_FLAG_MOD_REPLACE));
522
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));
526         }
527         
528         /* Even if we didn't get a sambaPassword, we can still setup
529          * krb5Key from the NT hash. 
530          *
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
534          */
535         if (ntPwdHash) {
536                 unsigned char *buf;
537                 size_t buf_size;
538                 size_t len;
539                 struct ldb_val val;
540                 Key key;
541                 
542                 key.mkvno = 0;
543                 key.salt = NULL; /* No salt for this enc type */
544
545                 krb5_ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
546                                               ETYPE_ARCFOUR_HMAC_MD5,
547                                               ntPwdHash->hash, sizeof(ntPwdHash->hash), 
548                                               &key.key);
549                 if (krb5_ret) {
550                         return LDB_ERR_OPERATIONS_ERROR;
551                 }
552                 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &key, &len, krb5_ret);
553                 if (krb5_ret) {
554                         return LDB_ERR_OPERATIONS_ERROR;
555                 }
556                 krb5_free_keyblock_contents(smb_krb5_context->krb5_context,
557                                             &key.key);
558                 
559                 val.data = talloc_memdup(req, buf, len);
560                 val.length = len;
561                 free(buf);
562                 if (!val.data || ret) {
563                         return LDB_ERR_OPERATIONS_ERROR;
564                 }
565                 CHECK_RET(ldb_msg_add_value(modify_msg, "krb5Key", &val));
566         }
567
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));
575         }
576
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) {
580                 if (kvno == 0) {
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));
584                 } else {
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
589                          * such guarantee) */
590                         
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) {
594                                 return -1;
595                         }
596                         old_kvno.length = strlen((char *)old_kvno.data);
597                         
598                         new_kvno.data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", kvno + 1);
599                         if (!new_kvno.data) {
600                                 return -1;
601                         }
602                         new_kvno.length = strlen((char *)new_kvno.data);
603                         
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",
607                                                     LDB_FLAG_MOD_ADD));
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;
612                 }
613         }
614
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));
619
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, 
627                                                 pwdHistoryLength);
628                 if (!new_sambaLMPwdHistory) {
629                         return LDB_ERR_OPERATIONS_ERROR;
630                 }
631                 new_sambaNTPwdHistory = talloc_array(mem_ctx, struct samr_Password, 
632                                                 pwdHistoryLength);
633                 if (!new_sambaNTPwdHistory) {
634                         return LDB_ERR_OPERATIONS_ERROR;
635                 }
636                 for (i=0;i<MIN(pwdHistoryLength-1, sambaLMPwdHistory_len);i++) {
637                         new_sambaLMPwdHistory[i+1] = sambaLMPwdHistory[i];
638                 }
639                 for (i=0;i<MIN(pwdHistoryLength-1, sambaNTPwdHistory_len);i++) {
640                         new_sambaNTPwdHistory[i+1] = sambaNTPwdHistory[i];
641                 }
642                 
643                 /* Don't store 'long' passwords in the LM history, 
644                    but make sure to 'expire' one password off the other end */
645                 if (lmOldHash) {
646                         new_sambaLMPwdHistory[0] = *lmOldHash;
647                 } else {
648                         ZERO_STRUCT(new_sambaLMPwdHistory[0]);
649                 }
650                 sambaLMPwdHistory_len = MIN(sambaLMPwdHistory_len + 1, pwdHistoryLength);
651                 
652                 /* Likewise, we might not have an old NT password (lm
653                  * only password change function on previous change) */
654                 if (ntOldHash) {
655                         new_sambaNTPwdHistory[0] = *ntOldHash;
656                 } else {
657                         ZERO_STRUCT(new_sambaNTPwdHistory[0]);
658                 }
659                 sambaNTPwdHistory_len = MIN(sambaNTPwdHistory_len + 1, pwdHistoryLength);
660                 
661                 CHECK_RET(samdb_msg_add_hashes(mem_ctx, modify_msg, 
662                                                "sambaLMPwdHistory", 
663                                                new_sambaLMPwdHistory, 
664                                                sambaLMPwdHistory_len));
665                 
666                 CHECK_RET(samdb_msg_add_hashes(mem_ctx, modify_msg, 
667                                                "sambaNTPwdHistory", 
668                                                new_sambaNTPwdHistory, 
669                                                sambaNTPwdHistory_len));
670         }
671
672         /* Too much code above, we should check we got it close to reasonable */
673         CHECK_RET(ldb_msg_sanity_check(modify_msg));
674
675         modify_request = talloc(mem_ctx, struct ldb_request);
676         if (!modify_request) {
677                 talloc_free(mem_ctx);
678                 return LDB_ERR_OPERATIONS_ERROR;
679         }
680
681         modify_request->operation = LDB_REQ_MODIFY;
682         modify_request->op.mod.message = modify_msg;
683         modify_request->controls = NULL;
684
685         ret = ldb_next_request(module, modify_request);
686         
687         talloc_free(mem_ctx);
688         return ret;
689 }
690
691 /* add_record: do things with the sambaPassword attribute */
692 static int password_hash_add(struct ldb_module *module, struct ldb_request *req)
693 {
694         const struct ldb_message *msg = req->op.add.message;
695
696         ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_record\n");
697
698         if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
699                 return ldb_next_request(module, req);
700         }
701         
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);
707         }
708
709         return password_hash_handle(module, req, msg);
710 }
711
712 /* modify_record: do things with the sambaPassword attribute */
713 static int password_hash_modify(struct ldb_module *module, struct ldb_request *req)
714 {
715         const struct ldb_message *msg = req->op.mod.message;
716
717         ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_modify_record\n");
718
719         if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
720                 return ldb_next_request(module, req);
721         }
722         
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);
728         }
729
730         return password_hash_handle(module, req, msg);
731 }
732
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};
735
736 struct ph_async_context {
737
738         enum ph_type type;
739         enum ph_step step;
740
741         struct ldb_module *module;
742         struct ldb_request *orig_req;
743
744         struct ldb_request *dom_req;
745         struct ldb_async_result *dom_res;
746
747         struct ldb_request *down_req;
748
749         struct ldb_request *search_req;
750         struct ldb_async_result *search_res;
751
752         struct ldb_request *mod_req;
753 };
754
755 struct domain_data {
756         uint_t pwdProperties;
757         uint_t pwdHistoryLength;
758         char *dnsDomain;
759         char *realm;
760 };
761
762 static int add_password_hashes(struct ldb_module *module, struct ldb_message *msg, int is_mod)
763 {
764         const char *sambaPassword;
765         struct samr_Password tmp_hash;
766         
767         sambaPassword = ldb_msg_find_string(msg, "sambaPassword", NULL);
768         if (sambaPassword == NULL) { /* impossible, what happened ?! */
769                 return LDB_ERR_OPERATIONS_ERROR;
770         }
771
772         /* compute the new nt and lm hashes */
773         if (is_mod) {
774                 if (ldb_msg_add_empty(msg, "ntPwdHash", LDB_FLAG_MOD_REPLACE) != 0) {
775                         return LDB_ERR_OPERATIONS_ERROR;
776                 }
777         }       
778         E_md4hash(sambaPassword, tmp_hash.hash);
779         if (samdb_msg_add_hash(module->ldb, msg, msg, "ntPwdHash", &tmp_hash) != 0) {
780                 return LDB_ERR_OPERATIONS_ERROR;
781         }
782
783         if (E_deshash(sambaPassword, tmp_hash.hash)) {
784                 if (is_mod) {
785                         if (ldb_msg_add_empty(msg, "lmPwdHash", LDB_FLAG_MOD_REPLACE) != 0) {
786                                 return LDB_ERR_OPERATIONS_ERROR;
787                         }
788                 }
789                 if (samdb_msg_add_hash(module->ldb, msg, msg, "lmPwdHash", &tmp_hash) != 0) {
790                         return LDB_ERR_OPERATIONS_ERROR;
791                 }
792         }
793
794         return LDB_SUCCESS;
795 }
796
797 static int add_krb5_keys_from_password(struct ldb_module *module, struct ldb_message *msg,
798                                         struct smb_krb5_context *smb_krb5_context,
799                                         struct domain_data *domain,
800                                         const char *samAccountName,
801                                         const char *user_principal_name,
802                                         int is_computer)
803 {
804         const char *sambaPassword;
805         Principal *salt_principal;
806         krb5_error_code krb5_ret;
807         size_t num_keys;
808         Key *keys;
809         int i;
810
811         /* Many, many thanks to lukeh@padl.com for this
812          * algorithm, described in his Nov 10 2004 mail to
813          * samba-technical@samba.org */
814
815         sambaPassword = ldb_msg_find_string(msg, "sambaPassword", NULL);
816         if (sambaPassword == NULL) { /* impossible, what happened ?! */
817                 return LDB_ERR_OPERATIONS_ERROR;
818         }
819
820         if (is_computer) {
821                 /* Determine a salting principal */
822                 char *name = talloc_strdup(msg, samAccountName);
823                 char *saltbody;
824                 if (name == NULL) {
825                         ldb_set_errstring(module->ldb,
826                                           talloc_asprintf(msg, "password_hash_handle: "
827                                                           "generation of new kerberos keys failed: %s is a computer without a samAccountName",
828                                                           ldb_dn_linearize(msg, msg->dn)));
829                         return LDB_ERR_OPERATIONS_ERROR;
830                 }
831                 if (name[strlen(name)-1] == '$') {
832                         name[strlen(name)-1] = '\0';
833                 }
834                 saltbody = talloc_asprintf(msg, "%s.%s", name, domain->dnsDomain);
835                 
836                 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context,
837                                                 &salt_principal,
838                                                 domain->realm, "host",
839                                                 saltbody, NULL);
840         } else if (user_principal_name) {
841                 char *p;
842                 user_principal_name = talloc_strdup(msg, user_principal_name);
843                 if (user_principal_name == NULL) {
844                         return LDB_ERR_OPERATIONS_ERROR;
845                 } else {
846                         p = strchr(user_principal_name, '@');
847                         if (p) {
848                                 p[0] = '\0';
849                         }
850                         krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context,
851                                                         &salt_principal,
852                                                         domain->realm, user_principal_name, NULL);
853                 } 
854         } else {
855                 if (!samAccountName) {
856                         ldb_set_errstring(module->ldb,
857                                           talloc_asprintf(msg, "password_hash_handle: "
858                                                           "generation of new kerberos keys failed: %s has no samAccountName",
859                                                           ldb_dn_linearize(msg, msg->dn)));
860                         return LDB_ERR_OPERATIONS_ERROR;
861                 }
862                 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context,
863                                                 &salt_principal,
864                                                 domain->realm, samAccountName,
865                                                 NULL);
866         }
867
868         if (krb5_ret) {
869                 ldb_set_errstring(module->ldb,
870                                   talloc_asprintf(msg, "password_hash_handle: "
871                                                   "generation of a saltking principal failed: %s",
872                                                   smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
873                                                                              krb5_ret, msg)));
874                 return LDB_ERR_OPERATIONS_ERROR;
875         }
876
877         /* TODO: We may wish to control the encryption types chosen in future */
878         krb5_ret = hdb_generate_key_set_password(smb_krb5_context->krb5_context,
879                                                  salt_principal, sambaPassword, &keys, &num_keys);
880         krb5_free_principal(smb_krb5_context->krb5_context, salt_principal);
881
882         if (krb5_ret) {
883                 ldb_set_errstring(module->ldb,
884                                   talloc_asprintf(msg, "password_hash_handle: "
885                                                   "generation of new kerberos keys failed: %s",
886                                                   smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
887                                                                              krb5_ret, msg)));
888                 return LDB_ERR_OPERATIONS_ERROR;
889         }
890
891         /* Walking all the key types generated, transform each
892          * key into an ASN.1 blob
893          */
894         for (i=0; i < num_keys; i++) {
895                 unsigned char *buf;
896                 size_t buf_size;
897                 size_t len;
898                 struct ldb_val val;
899                 int ret;
900                 
901                 if (keys[i].key.keytype == ENCTYPE_ARCFOUR_HMAC) {
902                         /* We might end up doing this below:
903                          * This ensures we get the unicode
904                          * conversion right.  This should also
905                          * be fixed in the Heimdal libs */
906                         continue;
907                 }
908                 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &keys[i], &len, krb5_ret);
909                 if (krb5_ret) {
910                         return LDB_ERR_OPERATIONS_ERROR;
911                 }
912                 
913                 val.data = talloc_memdup(msg, buf, len);
914                 val.length = len;
915                 free(buf);
916                 if (!val.data || krb5_ret) {
917                         hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
918                         return LDB_ERR_OPERATIONS_ERROR;
919                 }
920                 ret = ldb_msg_add_value(msg, "krb5Key", &val);
921                 if (ret != LDB_SUCCESS) {
922                         hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
923                         return ret;
924                 }
925         }
926         
927         hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
928
929         return LDB_SUCCESS;
930 }
931
932 static int add_krb5_keys_from_NThash(struct ldb_module *module, struct ldb_message *msg,
933                                         struct smb_krb5_context *smb_krb5_context)
934 {
935         struct samr_Password *ntPwdHash;
936         krb5_error_code krb5_ret;
937         unsigned char *buf;
938         size_t buf_size;
939         size_t len;
940         struct ldb_val val;
941         Key key;
942         
943         key.mkvno = 0;
944         key.salt = NULL; /* No salt for this enc type */
945
946         ntPwdHash = samdb_result_hash(msg, msg, "ntPwdHash");
947         if (ntPwdHash == NULL) { /* what happened ?! */
948                 return LDB_ERR_OPERATIONS_ERROR;
949         }
950
951         krb5_ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
952                                  ENCTYPE_ARCFOUR_HMAC,
953                                  ntPwdHash->hash, sizeof(ntPwdHash->hash), 
954                                  &key.key);
955         if (krb5_ret) {
956                 return LDB_ERR_OPERATIONS_ERROR;
957         }
958         ASN1_MALLOC_ENCODE(Key, buf, buf_size, &key, &len, krb5_ret);
959         if (krb5_ret) {
960                 return LDB_ERR_OPERATIONS_ERROR;
961         }
962         krb5_free_keyblock_contents(smb_krb5_context->krb5_context,
963                                     &key.key);
964         
965         val.data = talloc_memdup(msg, buf, len);
966         val.length = len;
967         free(buf);
968         if (!val.data) {
969                 return LDB_ERR_OPERATIONS_ERROR;
970         }
971         if (ldb_msg_add_value(msg, "krb5Key", &val) != 0) {
972                 return LDB_ERR_OPERATIONS_ERROR;
973         }
974
975         return LDB_SUCCESS;
976 }
977
978 static int set_pwdLastSet(struct ldb_module *module, struct ldb_message *msg, int is_mod)
979 {
980         NTTIME now_nt;
981
982         /* set it as now */
983         unix_to_nt_time(&now_nt, time(NULL));
984
985         if (!is_mod) {
986                 /* be sure there isn't a 0 value set (eg. coming from the template) */
987                 ldb_msg_remove_attr(msg, "pwdLastSet");
988                 /* add */
989                 if (ldb_msg_add_empty(msg, "pwdLastSet", LDB_FLAG_MOD_ADD) != 0) {
990                         return LDB_ERR_OPERATIONS_ERROR;
991                 }
992         } else {
993                 /* replace */
994                 if (ldb_msg_add_empty(msg, "pwdLastSet", LDB_FLAG_MOD_REPLACE) != 0) {
995                         return LDB_ERR_OPERATIONS_ERROR;
996                 }
997         }
998
999         if (samdb_msg_add_uint64(module->ldb, msg, msg, "pwdLastSet", now_nt) != 0) {
1000                 return LDB_ERR_OPERATIONS_ERROR;
1001         }
1002
1003         return LDB_SUCCESS;
1004 }
1005
1006 static int add_keyVersionNumber(struct ldb_module *module, struct ldb_message *msg, int previous)
1007 {
1008         /* replace or add */
1009         if (ldb_msg_add_empty(msg, "msDS-KeyVersionNumber", LDB_FLAG_MOD_REPLACE) != 0) {
1010                 return LDB_ERR_OPERATIONS_ERROR;
1011         }
1012
1013         if (samdb_msg_add_uint(module->ldb, msg, msg, "msDS-KeyVersionNumber", previous+1) != 0) {
1014                 return LDB_ERR_OPERATIONS_ERROR;
1015         }
1016
1017         return LDB_SUCCESS;
1018 }
1019
1020 static int setPwdHistory(struct ldb_module *module, struct ldb_message *msg, struct ldb_message *old_msg, int hlen)
1021 {
1022         struct samr_Password *nt_hash;
1023         struct samr_Password *lm_hash;
1024         struct samr_Password *nt_history;
1025         struct samr_Password *lm_history;
1026         struct samr_Password *new_nt_history;
1027         struct samr_Password *new_lm_history;
1028         int nt_hist_len;
1029         int lm_hist_len;
1030         int i;
1031
1032         nt_hash = samdb_result_hash(msg, old_msg, "ntPwdHash");
1033         lm_hash = samdb_result_hash(msg, old_msg, "lmPwdHash");
1034
1035         /* if no previous passwords just return */
1036         if (nt_hash == NULL && lm_hash == NULL) return LDB_SUCCESS;
1037
1038         nt_hist_len = samdb_result_hashes(msg, old_msg, "sambaNTPwdHistory", &nt_history);
1039         lm_hist_len = samdb_result_hashes(msg, old_msg, "sambaLMPwdHistory", &lm_history);
1040
1041         /* We might not have an old NT password */
1042         new_nt_history = talloc_array(msg, struct samr_Password, hlen);
1043         if (new_nt_history == NULL) {
1044                 return LDB_ERR_OPERATIONS_ERROR;
1045         }
1046         for (i = 0; i < MIN(hlen-1, nt_hist_len); i++) {
1047                 new_nt_history[i+1] = nt_history[i];
1048         }
1049         nt_hist_len = i + 1;
1050         if (nt_hash) {
1051                 new_nt_history[0] = *nt_hash;
1052         } else {
1053                 ZERO_STRUCT(new_nt_history[0]);
1054         }
1055         if (ldb_msg_add_empty(msg, "sambaNTPwdHistory", LDB_FLAG_MOD_REPLACE) != LDB_SUCCESS) {
1056                 return LDB_ERR_OPERATIONS_ERROR;
1057         }
1058         if (samdb_msg_add_hashes(msg, msg, "sambaNTPwdHistory", new_nt_history, nt_hist_len) != LDB_SUCCESS) {
1059                 return LDB_ERR_OPERATIONS_ERROR;
1060         }
1061                 
1062
1063         /* Don't store 'long' passwords in the LM history, 
1064            but make sure to 'expire' one password off the other end */
1065         new_lm_history = talloc_array(msg, struct samr_Password, hlen);
1066         if (new_lm_history == NULL) {
1067                 return LDB_ERR_OPERATIONS_ERROR;
1068         }
1069         for (i = 0; i < MIN(hlen-1, lm_hist_len); i++) {
1070                 new_lm_history[i+1] = lm_history[i];
1071         }
1072         lm_hist_len = i + 1;
1073         if (lm_hash) {
1074                 new_lm_history[0] = *lm_hash;
1075         } else {
1076                 ZERO_STRUCT(new_lm_history[0]);
1077         }
1078         if (ldb_msg_add_empty(msg, "sambaLMPwdHistory", LDB_FLAG_MOD_REPLACE) != LDB_SUCCESS) {
1079                 return LDB_ERR_OPERATIONS_ERROR;
1080         }
1081         if (samdb_msg_add_hashes(msg, msg, "sambaLMPwdHistory", new_lm_history, lm_hist_len) != LDB_SUCCESS) {
1082                 return LDB_ERR_OPERATIONS_ERROR;
1083         }
1084
1085         return LDB_SUCCESS;
1086 }
1087
1088 static struct ldb_async_handle *ph_init_handle(struct ldb_request *req, struct ldb_module *module, enum ph_type type)
1089 {
1090         struct ph_async_context *ac;
1091         struct ldb_async_handle *h;
1092
1093         h = talloc_zero(req, struct ldb_async_handle);
1094         if (h == NULL) {
1095                 ldb_set_errstring(module->ldb, talloc_asprintf(module, "Out of Memory"));
1096                 return NULL;
1097         }
1098
1099         h->module = module;
1100
1101         ac = talloc_zero(h, struct ph_async_context);
1102         if (ac == NULL) {
1103                 ldb_set_errstring(module->ldb, talloc_asprintf(module, "Out of Memory"));
1104                 talloc_free(h);
1105                 return NULL;
1106         }
1107
1108         h->private_data = (void *)ac;
1109
1110         h->state = LDB_ASYNC_INIT;
1111         h->status = LDB_SUCCESS;
1112
1113         ac->type = type;
1114         ac->module = module;
1115         ac->orig_req = req;
1116
1117         return h;
1118 }
1119
1120 static int get_domain_data_callback(struct ldb_context *ldb, void *context, struct ldb_async_result *ares)
1121 {
1122         struct ph_async_context *ac;
1123
1124         if (!context || !ares) {
1125                 ldb_set_errstring(ldb, talloc_asprintf(ldb, "NULL Context or Result in callback"));
1126                 return LDB_ERR_OPERATIONS_ERROR;
1127         }
1128
1129         ac = talloc_get_type(context, struct ph_async_context);
1130
1131         /* we are interested only in the single reply (base search) we receive here */
1132         if (ares->type == LDB_REPLY_ENTRY) {
1133                 if (ac->dom_res != NULL) {
1134                         ldb_set_errstring(ldb, talloc_asprintf(ldb, "Too many results"));
1135                         talloc_free(ares);
1136                         return LDB_ERR_OPERATIONS_ERROR;
1137                 }
1138                 ac->dom_res = talloc_steal(ac, ares);
1139         } else {
1140                 talloc_free(ares);
1141         }
1142
1143         return LDB_SUCCESS;
1144 }
1145
1146 static int build_domain_data_request(struct ph_async_context *ac,
1147                                      struct dom_sid *sid)
1148 {
1149         const char * const attrs[] = { "pwdProperties", "pwdHistoryLength", "dnsDomain", NULL };
1150         char *filter;
1151
1152         ac->dom_req = talloc_zero(ac, struct ldb_request);
1153         if (ac->dom_req == NULL) {
1154                 ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
1155                 return LDB_ERR_OPERATIONS_ERROR;
1156         }
1157         ac->dom_req->operation = LDB_ASYNC_SEARCH;
1158         ac->dom_req->op.search.base = NULL;
1159         ac->dom_req->op.search.scope = LDB_SCOPE_SUBTREE;
1160
1161         filter = talloc_asprintf(ac->dom_req, "(&(objectSid=%s)(objectClass=domain))", dom_sid_string(ac->dom_req, sid));
1162         if (filter == NULL) {
1163                 ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
1164                 talloc_free(ac->dom_req);
1165                 return LDB_ERR_OPERATIONS_ERROR;
1166         }
1167
1168         ac->dom_req->op.search.tree = ldb_parse_tree(ac->module->ldb, filter);
1169         if (ac->dom_req->op.search.tree == NULL) {
1170                 ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "Invalid search filter"));
1171                 talloc_free(ac->dom_req);
1172                 return LDB_ERR_OPERATIONS_ERROR;
1173         }
1174         ac->dom_req->op.search.attrs = attrs;
1175         ac->dom_req->controls = NULL;
1176         ac->dom_req->creds = ac->orig_req->creds;
1177         ac->dom_req->async.context = ac;
1178         ac->dom_req->async.callback = get_domain_data_callback;
1179         ac->dom_req->async.timeout = ac->orig_req->async.timeout;
1180
1181         return LDB_SUCCESS;
1182 }
1183
1184 static struct domain_data *get_domain_data(struct ldb_module *module, void *mem_ctx, struct ldb_async_result *res)
1185 {
1186         struct domain_data *data;
1187         const char *tmp;
1188         
1189         data = talloc_zero(mem_ctx, struct domain_data);
1190         if (data == NULL) {
1191                 return NULL;
1192         }
1193
1194         data->pwdProperties = samdb_result_uint(res->message, "pwdProperties", 0);
1195         data->pwdHistoryLength = samdb_result_uint(res->message, "pwdHistoryLength", 0);
1196         tmp = ldb_msg_find_string(res->message, "dnsDomain", NULL);
1197
1198         if (tmp != NULL) {
1199                 data->dnsDomain = talloc_strdup(data, tmp);
1200                 if (data->dnsDomain == NULL) {
1201                         ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
1202                         return NULL;
1203                 }
1204                 data->realm = strupper_talloc(mem_ctx, tmp);
1205                 if (data->realm == NULL) {
1206                         ldb_debug(module->ldb, LDB_DEBUG_ERROR, "Out of memory!\n");
1207                         return NULL;
1208                 }
1209         }
1210
1211         return data;
1212 }
1213
1214 static int password_hash_add_async(struct ldb_module *module, struct ldb_request *req)
1215 {
1216         struct ldb_async_handle *h;
1217         struct ph_async_context *ac;
1218         struct ldb_message_element *attribute;
1219         struct dom_sid *domain_sid;
1220         int ret;
1221
1222         ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_async\n");
1223
1224         if (ldb_dn_is_special(req->op.add.message->dn)) { /* do not manipulate our control entries */
1225                 return ldb_next_request(module, req);
1226         }
1227
1228         /* nobody must touch password Histories */
1229         if (ldb_msg_find_element(req->op.add.message, "sambaNTPwdHistory") ||
1230             ldb_msg_find_element(req->op.add.message, "sambaLMPwdHistory")) {
1231                 return LDB_ERR_UNWILLING_TO_PERFORM;
1232         }
1233
1234         /* If no part of this touches the sambaPassword, then we don't
1235          * need to make any changes.  For password changes/set there should
1236          * be a 'delete' or a 'modify' on this attribute. */
1237         if ((attribute = ldb_msg_find_element(req->op.add.message, "sambaPassword")) == NULL ) {
1238                 return ldb_next_request(module, req);
1239         }
1240
1241         /* if it is not an entry of type person its an error */
1242         /* TODO: remove this when sambaPassword will be in schema */
1243         if (!ldb_msg_check_string_attribute(req->op.add.message, "objectClass", "person")) {
1244                 return LDB_ERR_OBJECT_CLASS_VIOLATION;
1245         }
1246
1247         /* check sambaPassword is single valued here */
1248         /* TODO: remove this when sambaPassword will be single valued in schema */
1249         if (attribute->num_values > 1) {
1250                 ldb_set_errstring(module->ldb, talloc_asprintf(req,
1251                                         "mupltiple values for sambaPassword not allowed!\n"));
1252                 return LDB_ERR_CONSTRAINT_VIOLATION;
1253         }
1254
1255         /* get user domain data */
1256         domain_sid = samdb_result_sid_prefix(req, req->op.add.message, "objectSid");
1257         if (domain_sid == NULL) {
1258                 ldb_debug(module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n");
1259                 return LDB_ERR_OPERATIONS_ERROR;
1260         }
1261
1262         h = ph_init_handle(req, module, PH_ADD);
1263         if (!h) {
1264                 return LDB_ERR_OPERATIONS_ERROR;
1265         }
1266         ac = talloc_get_type(h->private_data, struct ph_async_context);
1267
1268         ret = build_domain_data_request(ac, domain_sid);
1269         if (ret != LDB_SUCCESS) {
1270                 return ret;
1271         }
1272
1273         ac->step = PH_ADD_SEARCH_DOM;
1274
1275         req->async.handle = h;
1276
1277         return ldb_next_request(module, ac->dom_req);
1278 }
1279
1280 static int password_hash_add_async_do_add(struct ldb_async_handle *h) {
1281
1282         struct ph_async_context *ac;
1283         struct domain_data *domain;
1284         struct smb_krb5_context *smb_krb5_context;
1285         struct ldb_message *msg;
1286
1287         ac = talloc_get_type(h->private_data, struct ph_async_context);
1288
1289         domain = get_domain_data(ac->module, ac, ac->dom_res);
1290         if (domain == NULL) {
1291                 return LDB_ERR_OPERATIONS_ERROR;
1292         }
1293
1294         ac->down_req = talloc(ac, struct ldb_request);
1295         if (ac->down_req == NULL) {
1296                 return LDB_ERR_OPERATIONS_ERROR;
1297         }
1298
1299         *(ac->down_req) = *(ac->orig_req);
1300         ac->down_req->op.add.message = msg = ldb_msg_copy_shallow(ac->down_req, ac->orig_req->op.add.message);
1301         if (ac->down_req->op.add.message == NULL) {
1302                 return LDB_ERR_OPERATIONS_ERROR;
1303         }
1304         
1305         /* Some operations below require kerberos contexts */
1306         if (smb_krb5_init_context(ac->down_req, &smb_krb5_context) != 0) {
1307                 return LDB_ERR_OPERATIONS_ERROR;
1308         }
1309
1310         /* we can compute new password hashes from the unicode password */
1311         if (add_password_hashes(ac->module, msg, 0) != LDB_SUCCESS) {
1312                 return LDB_ERR_OPERATIONS_ERROR;
1313         }
1314
1315         /* now add krb5 keys based on unicode password */
1316         if (add_krb5_keys_from_password(ac->module, msg, smb_krb5_context, domain,
1317                                         ldb_msg_find_string(msg, "samAccountName", NULL),
1318                                         ldb_msg_find_string(msg, "userPrincipalName", NULL),
1319                                         ldb_msg_check_string_attribute(msg, "objectClass", "computer")
1320                                        ) != LDB_SUCCESS) {
1321                 return LDB_ERR_OPERATIONS_ERROR;
1322         }
1323
1324         /* add also kr5 keys based on NT the hash */
1325         if (add_krb5_keys_from_NThash(ac->module, msg, smb_krb5_context) != LDB_SUCCESS) {
1326                 return LDB_ERR_OPERATIONS_ERROR;
1327         }
1328
1329         /* if both the domain properties and the user account controls do not permit
1330          * clear text passwords then wipe out the sambaPassword */
1331         if ((!(domain->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT)) ||
1332             (!(ldb_msg_find_uint(msg, "userAccountControl", 0) & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED))) {
1333                 ldb_msg_remove_attr(msg, "sambaPassword");
1334         }
1335
1336         /* don't touch it if a value is set. It could be an incoming samsync */
1337         if (ldb_msg_find_uint64(msg, "pwdLastSet", 0) == 0) {
1338                 if (set_pwdLastSet(ac->module, msg, 0) != LDB_SUCCESS) {
1339                         return LDB_ERR_OPERATIONS_ERROR;
1340                 }
1341         }
1342
1343         /* don't touch it if a value is set. It could be an incoming samsync */
1344         if (!ldb_msg_find_element(msg, "msDS-KeyVersionNumber")) {
1345                 if (add_keyVersionNumber(ac->module, msg, 0) != LDB_SUCCESS) {
1346                         return LDB_ERR_OPERATIONS_ERROR;
1347                 }
1348         }
1349
1350         h->state = LDB_ASYNC_INIT;
1351         h->status = LDB_SUCCESS;
1352
1353         ac->step = PH_ADD_DO_ADD;
1354
1355         /* perform the operation */
1356         return ldb_next_request(ac->module, ac->down_req);
1357 }
1358
1359 static int password_hash_mod_async_search_self(struct ldb_async_handle *h);
1360
1361 static int password_hash_modify_async(struct ldb_module *module, struct ldb_request *req)
1362 {
1363         struct ldb_async_handle *h;
1364         struct ph_async_context *ac;
1365         struct ldb_message_element *sambaAttr;
1366         struct ldb_message_element *ntAttr;
1367         struct ldb_message_element *lmAttr;
1368
1369         ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_async\n");
1370
1371         if (ldb_dn_is_special(req->op.mod.message->dn)) { /* do not manipulate our control entries */
1372                 return ldb_next_request(module, req);
1373         }
1374         
1375         /* nobody must touch password Histories */
1376         if (ldb_msg_find_element(req->op.mod.message, "sambaNTPwdHistory") ||
1377             ldb_msg_find_element(req->op.mod.message, "sambaLMPwdHistory")) {
1378                 return LDB_ERR_UNWILLING_TO_PERFORM;
1379         }
1380
1381         sambaAttr = ldb_msg_find_element(req->op.mod.message, "sambaPassword");
1382         ntAttr = ldb_msg_find_element(req->op.mod.message, "ntPwdHash");
1383         lmAttr = ldb_msg_find_element(req->op.mod.message, "lmPwdHash");
1384
1385         /* check passwords are single valued here */
1386         /* TODO: remove this when passwords will be single valued in schema */
1387         if (sambaAttr && (sambaAttr->num_values > 1)) {
1388                 return LDB_ERR_CONSTRAINT_VIOLATION;
1389         }
1390         if (ntAttr && (ntAttr->num_values > 1)) {
1391                 return LDB_ERR_CONSTRAINT_VIOLATION;
1392         }
1393         if (lmAttr && (lmAttr->num_values > 1)) {
1394                 return LDB_ERR_CONSTRAINT_VIOLATION;
1395         }
1396
1397         /* If no part of this touches the sambaPassword OR ntPwdHash and/or lmPwdHash, then we don't
1398          * need to make any changes.  For password changes/set there should
1399          * be a 'delete' or a 'modify' on this attribute. */
1400         /* If the only operation is the deletion of the passwords then go on */
1401         if (       ((!sambaAttr) || ((sambaAttr->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE))
1402                 && ((!ntAttr) || ((ntAttr->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE))
1403                 && ((!lmAttr) || ((lmAttr->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE))  ) {
1404
1405                 return ldb_next_request(module, req);
1406         }
1407
1408         h = ph_init_handle(req, module, PH_MOD);
1409         if (!h) {
1410                 return LDB_ERR_OPERATIONS_ERROR;
1411         }
1412         ac = talloc_get_type(h->private_data, struct ph_async_context);
1413
1414         /* return or own handle to deal with this call */
1415         req->async.handle = h;
1416
1417         /* prepare the first operation */
1418         ac->down_req = talloc_zero(ac, struct ldb_request);
1419         if (ac->down_req == NULL) {
1420                 ldb_set_errstring(module->ldb, talloc_asprintf(module->ldb, "Out of memory!"));
1421                 return LDB_ERR_OPERATIONS_ERROR;
1422         }
1423
1424         *(ac->down_req) = *req; /* copy the request */
1425
1426         /* use a new message structure so that we can modify it */
1427         ac->down_req->op.mod.message = ldb_msg_copy_shallow(ac->down_req, req->op.mod.message);
1428
1429         /* - remove any imodification to the password from the first commit
1430          *   we will make the real modification later */
1431         if (sambaAttr) ldb_msg_remove_attr(ac->down_req->op.mod.message, "sambaPassword");
1432         if (ntAttr) ldb_msg_remove_attr(ac->down_req->op.mod.message, "ntPwdHash");
1433         if (lmAttr) ldb_msg_remove_attr(ac->down_req->op.mod.message, "lmPwdHash");
1434
1435         /* if there was nothing else to be modify skip to next step */
1436         if (ac->down_req->op.mod.message->num_elements == 0) {
1437                 talloc_free(ac->down_req);
1438                 ac->down_req = NULL;
1439                 return password_hash_mod_async_search_self(h);
1440         }
1441         
1442         ac->down_req->async.context = NULL;
1443         ac->down_req->async.callback = NULL;
1444
1445         ac->step = PH_MOD_DO_REQ;
1446
1447         return ldb_next_request(module, ac->down_req);
1448 }
1449
1450 static int get_self_callback(struct ldb_context *ldb, void *context, struct ldb_async_result *ares)
1451 {
1452         struct ph_async_context *ac;
1453
1454         if (!context || !ares) {
1455                 ldb_set_errstring(ldb, talloc_asprintf(ldb, "NULL Context or Result in callback"));
1456                 return LDB_ERR_OPERATIONS_ERROR;
1457         }
1458
1459         ac = talloc_get_type(context, struct ph_async_context);
1460
1461         /* we are interested only in the single reply (base search) we receive here */
1462         if (ares->type == LDB_REPLY_ENTRY) {
1463                 if (ac->search_res != NULL) {
1464                         ldb_set_errstring(ldb, talloc_asprintf(ldb, "Too many results"));
1465                         talloc_free(ares);
1466                         return LDB_ERR_OPERATIONS_ERROR;
1467                 }
1468
1469                 /* if it is not an entry of type person this is an error */
1470                 /* TODO: remove this when sambaPassword will be in schema */
1471                 if (!ldb_msg_check_string_attribute(ares->message, "objectClass", "person")) {
1472                         ldb_set_errstring(ldb, talloc_asprintf(ldb, "Object class violation"));
1473                         talloc_free(ares);
1474                         return LDB_ERR_OBJECT_CLASS_VIOLATION;
1475                 }
1476
1477                 ac->search_res = talloc_steal(ac, ares);
1478         } else {
1479                 talloc_free(ares);
1480         }
1481
1482         return LDB_SUCCESS;
1483 }
1484
1485 static int password_hash_mod_async_search_self(struct ldb_async_handle *h) {
1486
1487         struct ph_async_context *ac;
1488
1489         ac = talloc_get_type(h->private_data, struct ph_async_context);
1490
1491         /* prepare the search operation */
1492         ac->search_req = talloc_zero(ac, struct ldb_request);
1493         if (ac->search_req == NULL) {
1494                 ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "Out of Memory!\n");
1495                 return LDB_ERR_OPERATIONS_ERROR;
1496         }
1497
1498         ac->search_req->operation = LDB_ASYNC_SEARCH;
1499         ac->search_req->op.search.base = ac->orig_req->op.mod.message->dn;
1500         ac->search_req->op.search.scope = LDB_SCOPE_BASE;
1501         ac->search_req->op.search.tree = ldb_parse_tree(ac->module->ldb, NULL);
1502         if (ac->search_req->op.search.tree == NULL) {
1503                 ldb_set_errstring(ac->module->ldb, talloc_asprintf(ac, "Invalid search filter"));
1504                 return LDB_ERR_OPERATIONS_ERROR;
1505         }
1506         ac->search_req->op.search.attrs = NULL;
1507         ac->search_req->controls = NULL;
1508         ac->search_req->creds = ac->orig_req->creds;
1509         ac->search_req->async.context = ac;
1510         ac->search_req->async.callback = get_self_callback;
1511         ac->search_req->async.timeout = ac->orig_req->async.timeout;
1512
1513         ac->step = PH_MOD_SEARCH_SELF;
1514
1515         return ldb_next_request(ac->module, ac->search_req);
1516 }
1517
1518 static int password_hash_mod_async_search_dom(struct ldb_async_handle *h) {
1519
1520         struct ph_async_context *ac;
1521         struct dom_sid *domain_sid;
1522         int ret;
1523
1524         ac = talloc_get_type(h->private_data, struct ph_async_context);
1525
1526         /* get object domain sid */
1527         domain_sid = samdb_result_sid_prefix(ac, ac->search_res->message, "objectSid");
1528         if (domain_sid == NULL) {
1529                 ldb_debug(ac->module->ldb, LDB_DEBUG_ERROR, "can't handle entry with missing objectSid!\n");
1530                 return LDB_ERR_OPERATIONS_ERROR;
1531         }
1532
1533         /* get user domain data */
1534         ret = build_domain_data_request(ac, domain_sid);
1535         if (ret != LDB_SUCCESS) {
1536                 return ret;
1537         }
1538
1539         ac->step = PH_MOD_SEARCH_DOM;
1540
1541         return ldb_next_request(ac->module, ac->dom_req);
1542 }
1543
1544 static int password_hash_mod_async_do_mod(struct ldb_async_handle *h) {
1545
1546         struct ph_async_context *ac;
1547         struct domain_data *domain;
1548         struct smb_krb5_context *smb_krb5_context;
1549         struct ldb_message_element *sambaAttr;
1550         struct ldb_message *msg;
1551         int phlen;
1552
1553         ac = talloc_get_type(h->private_data, struct ph_async_context);
1554
1555         domain = get_domain_data(ac->module, ac, ac->dom_res);
1556         if (domain == NULL) {
1557                 return LDB_ERR_OPERATIONS_ERROR;
1558         }
1559
1560         ac->mod_req = talloc(ac, struct ldb_request);
1561         if (ac->mod_req == NULL) {
1562                 return LDB_ERR_OPERATIONS_ERROR;
1563         }
1564
1565         *(ac->mod_req) = *(ac->orig_req);
1566         
1567         /* use a new message structure so that we can modify it */
1568         ac->mod_req->op.mod.message = msg = ldb_msg_new(ac->mod_req);
1569         if (msg == NULL) {
1570                 return LDB_ERR_OPERATIONS_ERROR;
1571         }
1572
1573         /* modify dn */
1574         msg->dn = ac->orig_req->op.mod.message->dn;
1575
1576         /* Some operations below require kerberos contexts */
1577         if (smb_krb5_init_context(ac->mod_req, &smb_krb5_context) != 0) {
1578                 return LDB_ERR_OPERATIONS_ERROR;
1579         }
1580
1581         /* we are going to replace the existing krb5key or delete it */
1582         if (ldb_msg_add_empty(msg, "krb5key", LDB_FLAG_MOD_REPLACE) != 0) {
1583                 return LDB_ERR_OPERATIONS_ERROR;
1584         }
1585
1586         /* if we have sambaPassword in the original message add the operatio on it here */
1587         sambaAttr = ldb_msg_find_element(ac->orig_req->op.mod.message, "sambaPassword");
1588         if (sambaAttr) {
1589
1590                 if (ldb_msg_add(msg, sambaAttr, sambaAttr->flags) != 0) {
1591                         return LDB_ERR_OPERATIONS_ERROR;
1592                 }
1593
1594                 /* we are not deleteing it add password hashes */
1595                 if ((sambaAttr->flags & LDB_FLAG_MOD_MASK) != LDB_FLAG_MOD_DELETE) {
1596                 
1597                         /* we can compute new password hashes from the unicode password */
1598                         if (add_password_hashes(ac->module, msg, 1) != LDB_SUCCESS) {
1599                                 return LDB_ERR_OPERATIONS_ERROR;
1600                         }
1601
1602                         /* now add krb5 keys based on unicode password */
1603                         if (add_krb5_keys_from_password(ac->module, msg, smb_krb5_context, domain,
1604                                 ldb_msg_find_string(ac->search_res->message, "samAccountName", NULL),
1605                                 ldb_msg_find_string(ac->search_res->message, "userPrincipalName", NULL),
1606                                 ldb_msg_check_string_attribute(ac->search_res->message, "objectClass", "computer")
1607                                                        ) != LDB_SUCCESS) {
1608                                 return LDB_ERR_OPERATIONS_ERROR;
1609                         }
1610
1611                         /* if the domain properties or the user account controls do not permit
1612                          * clear text passwords then wipe out the sambaPassword */
1613                         if ((!(domain->pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT)) ||
1614                             (!(ldb_msg_find_uint(ac->search_res->message, "userAccountControl", 0) & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED))) {
1615                                 ldb_msg_remove_attr(msg, "sambaPassword");
1616                         }
1617
1618                 }
1619         }
1620
1621         /* if we don't have sambaPassword or we are trying to delete it try with nt or lm hasehs */
1622         if ((!sambaAttr) || ((sambaAttr->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_DELETE)) {
1623                 struct ldb_message_element *el;
1624                 
1625                 el = ldb_msg_find_element(ac->orig_req->op.mod.message, "ntPwdHash");
1626                 if (ldb_msg_add(msg, el, el->flags) != 0) {
1627                         return LDB_ERR_OPERATIONS_ERROR;
1628                 }
1629                 
1630                 el = ldb_msg_find_element(ac->orig_req->op.mod.message, "lmPwdHash");
1631                 if (ldb_msg_add(msg, el, el->flags) != 0) {
1632                         return LDB_ERR_OPERATIONS_ERROR;
1633                 }
1634         }
1635
1636         /* add also kr5 keys based on NT the hash */
1637         if (add_krb5_keys_from_NThash(ac->module, msg, smb_krb5_context) != LDB_SUCCESS) {
1638                 return LDB_ERR_OPERATIONS_ERROR;
1639         }
1640
1641         /* set change time */
1642         if (set_pwdLastSet(ac->module, msg, 1) != LDB_SUCCESS) {
1643                 return LDB_ERR_OPERATIONS_ERROR;
1644         }
1645
1646         /* don't touch it if a value is set. It could be an incoming samsync */
1647         if (add_keyVersionNumber(ac->module, msg,
1648                                  ldb_msg_find_uint(msg, "msDS-KeyVersionNumber", 0)
1649                                 ) != LDB_SUCCESS) {
1650                 return LDB_ERR_OPERATIONS_ERROR;
1651         }
1652
1653         if ((phlen = samdb_result_uint(ac->dom_res->message, "pwdHistoryLength", 0)) > 0) {
1654                 if (setPwdHistory(ac->module, msg, ac->search_res->message, phlen) != LDB_SUCCESS) {
1655                         return LDB_ERR_OPERATIONS_ERROR;
1656                 }
1657         }
1658
1659         h->state = LDB_ASYNC_INIT;
1660         h->status = LDB_SUCCESS;
1661
1662         ac->step = PH_MOD_DO_MOD;
1663
1664         /* perform the search */
1665         return ldb_next_request(ac->module, ac->mod_req);
1666 }
1667
1668 static int ph_async_wait(struct ldb_async_handle *handle) {
1669         struct ph_async_context *ac;
1670         int ret;
1671     
1672         if (!handle || !handle->private_data) {
1673                 return LDB_ERR_OPERATIONS_ERROR;
1674         }
1675
1676         if (handle->state == LDB_ASYNC_DONE) {
1677                 return handle->status;
1678         }
1679
1680         handle->state = LDB_ASYNC_PENDING;
1681
1682         ac = talloc_get_type(handle->private_data, struct ph_async_context);
1683
1684         switch (ac->step) {
1685         case PH_ADD_SEARCH_DOM:
1686                 if (ac->dom_req->async.handle->state != LDB_ASYNC_DONE) {
1687                         ret = ldb_async_wait(ac->dom_req->async.handle, LDB_WAIT_NONE);
1688                         if (ret != LDB_SUCCESS) goto done;
1689
1690                         if (ac->dom_req->async.handle->state != LDB_ASYNC_DONE) {
1691                                 return LDB_SUCCESS;
1692                         }
1693                 }
1694
1695                 /* domain search done, go on */
1696                 return password_hash_add_async_do_add(handle);
1697
1698         case PH_ADD_DO_ADD:
1699                 if (ac->down_req->async.handle->state != LDB_ASYNC_DONE) {
1700                         ret = ldb_async_wait(ac->down_req->async.handle, LDB_WAIT_NONE);
1701                         if (ret != LDB_SUCCESS) goto done;
1702
1703                         if (ac->down_req->async.handle->state != LDB_ASYNC_DONE) {
1704                                 return LDB_SUCCESS;
1705                         }
1706                 }
1707
1708                 break;
1709                 
1710         case PH_MOD_DO_REQ:
1711                 if (ac->down_req->async.handle->state != LDB_ASYNC_DONE) {
1712                         ret = ldb_async_wait(ac->down_req->async.handle, LDB_WAIT_NONE);
1713                         if (ret != LDB_SUCCESS) goto done;
1714
1715                         if (ac->down_req->async.handle->state != LDB_ASYNC_DONE) {
1716                                 return LDB_SUCCESS;
1717                         }
1718                 }
1719
1720                 /* non-password mods done, go on */
1721                 return password_hash_mod_async_search_self(handle);
1722                 
1723         case PH_MOD_SEARCH_SELF:
1724                 if (ac->search_req->async.handle->state != LDB_ASYNC_DONE) {
1725                         ret = ldb_async_wait(ac->search_req->async.handle, LDB_WAIT_NONE);
1726                         if (ret != LDB_SUCCESS) goto done;
1727
1728                         if (ac->search_req->async.handle->state != LDB_ASYNC_DONE) {
1729                                 return LDB_SUCCESS;
1730                         }
1731                 }
1732
1733                 /* self search done, go on */
1734                 return password_hash_mod_async_search_dom(handle);
1735                 
1736         case PH_MOD_SEARCH_DOM:
1737                 if (ac->dom_req->async.handle->state != LDB_ASYNC_DONE) {
1738                         ret = ldb_async_wait(ac->dom_req->async.handle, LDB_WAIT_NONE);
1739                         if (ret != LDB_SUCCESS) goto done;
1740
1741                         if (ac->dom_req->async.handle->state != LDB_ASYNC_DONE) {
1742                                 return LDB_SUCCESS;
1743                         }
1744                 }
1745
1746                 /* domain search done, go on */
1747                 return password_hash_mod_async_do_mod(handle);
1748
1749         case PH_MOD_DO_MOD:
1750                 if (ac->mod_req->async.handle->state != LDB_ASYNC_DONE) {
1751                         ret = ldb_async_wait(ac->mod_req->async.handle, LDB_WAIT_NONE);
1752                         if (ret != LDB_SUCCESS) goto done;
1753
1754                         if (ac->mod_req->async.handle->state != LDB_ASYNC_DONE) {
1755                                 return LDB_SUCCESS;
1756                         }
1757                 }
1758
1759                 break;
1760                 
1761         default:
1762                 ret = LDB_ERR_OPERATIONS_ERROR;
1763                 goto done;
1764         }
1765
1766         ret = LDB_SUCCESS;
1767
1768 done:
1769         handle->state = LDB_ASYNC_DONE;
1770         handle->status = ret;
1771         return ret;
1772 }
1773
1774 static int ph_async_wait_all(struct ldb_async_handle *handle) {
1775
1776         int ret;
1777
1778         while (handle->state != LDB_ASYNC_DONE) {
1779                 ret = ph_async_wait(handle);
1780                 if (ret != LDB_SUCCESS) {
1781                         return ret;
1782                 }
1783         }
1784
1785         return handle->status;
1786 }
1787
1788 static int password_hash_async_wait(struct ldb_async_handle *handle, enum ldb_async_wait_type type)
1789 {
1790         if (type == LDB_WAIT_ALL) {
1791                 return ph_async_wait_all(handle);
1792         } else {
1793                 return ph_async_wait(handle);
1794         }
1795 }
1796
1797 static int password_hash_request(struct ldb_module *module, struct ldb_request *req)
1798 {
1799         switch (req->operation) {
1800
1801         case LDB_REQ_ADD:
1802                 return password_hash_add(module, req);
1803
1804         case LDB_REQ_MODIFY:
1805                 return password_hash_modify(module, req);
1806
1807         case LDB_ASYNC_ADD:
1808                 return password_hash_add_async(module, req);
1809
1810         case LDB_ASYNC_MODIFY:
1811                 return password_hash_modify_async(module, req);
1812
1813         default:
1814                 return ldb_next_request(module, req);
1815
1816         }
1817 }
1818
1819 static const struct ldb_module_ops password_hash_ops = {
1820         .name          = "password_hash",
1821         .request       = password_hash_request,
1822         .async_wait    = password_hash_async_wait
1823 };
1824
1825
1826 int password_hash_module_init(void)
1827 {
1828         return ldb_register_module(&password_hash_ops);
1829 }