r12599: This new LDB module (and associated changes) allows Samba4 to operate
[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 unicodePwd 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/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"
43 #include "ads.h"
44 #include "hdb.h"
45
46 /* If we have decided there is reason to work on this request, then
47  * setup all the password hash types correctly.
48  *
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.
52  *
53  * Once this is done (which could update anything at all), we
54  * calculate the password hashes.
55  *
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...
60  *
61  * Finally, if the administrator has requested that a password history
62  * be maintained, then this should also be written out.
63  *
64  */
65
66
67 static int password_hash_handle(struct ldb_module *module, struct ldb_request *req, 
68                              const struct ldb_message *msg)
69 {
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;
81         uint_t kvno;
82         struct dom_sid *domain_sid;
83         time_t now = time(NULL);
84         NTTIME now_nt;
85         int i;
86         krb5_error_code krb5_ret;
87
88         struct smb_krb5_context *smb_krb5_context;
89
90         struct ldb_message_element *attribute;
91         struct ldb_dn *dn = msg->dn;
92         struct ldb_message *msg2;
93
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;
98
99         struct ldb_message_element *objectclasses;
100         struct ldb_val computer_val;
101         struct ldb_val person_val;
102         BOOL is_computer;
103
104         struct ldb_message *modify_msg;
105
106         const char *domain_expression;
107         const char *old_user_attrs[] = { "lmPwdHash", "ntPwdHash", NULL };
108         const char *user_attrs[] = { "userAccountControl", "lmPwdHistory", 
109                                      "ntPwdHistory", 
110                                      "ntPwdHash", 
111                                      "objectSid", "msDS-KeyVersionNumber", 
112                                      "objectClass", "userPrincipalName",
113                                      "samAccountName", 
114                                      NULL };
115         const char * const domain_attrs[] = { "pwdProperties", "pwdHistoryLength", 
116                                               "dnsDomain", NULL };
117
118         TALLOC_CTX *mem_ctx;
119
120         /* Do the original action */
121         
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);
127         }
128
129         mem_ctx = talloc_new(module);
130         if (!mem_ctx) {
131                 return LDB_ERR_OPERATIONS_ERROR;
132         }
133
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
137                  * history */
138
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;
144                 
145                 old_ret = ldb_next_request(module, &search_request);
146         }
147
148         /* we can't change things untill we copy it */
149         msg2 = ldb_msg_copy_shallow(mem_ctx, msg);
150
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;
155         }
156
157         /* Wipe out the unicodePwd attribute set, we will handle it in
158          * the second modify.  We might not want it written to disk */
159         
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;
167                 }
168         
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;
175                 }
176                 
177                 unicodePwd = (const char *)attribute->values[0].data;
178                 ldb_msg_remove_attr(msg2, "unicodePwd");
179         } else {
180                 unicodePwd = NULL;
181         }
182
183         modified_orig_request = *req;
184         switch (modified_orig_request.operation) {
185         case LDB_REQ_ADD:
186                 modified_orig_request.op.add.message = msg2;
187                 break;
188         case LDB_REQ_MODIFY:
189                 modified_orig_request.op.mod.message = msg2;
190                 break;
191         }
192
193         /* Send the (modified) request of the original caller down to the database */
194         ret = ldb_next_request(module, &modified_orig_request);
195         if (ret) {
196                 return ret;
197         }
198
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) {
203                 if (old_ret) {
204                         talloc_free(mem_ctx);
205                         return old_ret;
206                 }
207
208                 /* Find out the old passwords details of the user */
209                 old_res = search_request.op.search.res;
210                 
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),
216                                                           old_res->count));
217                         /* What happend?  The above add/modify worked... */
218                         talloc_free(mem_ctx);
219                         return LDB_ERR_NO_SUCH_OBJECT;
220                 }
221
222                 lmOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0],   "lmPwdHash");
223                 ntOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0],   "ntPwdHash");
224         }
225
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. */
231
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;
238         
239         ret = ldb_next_request(module, &search_request);
240         if (ret) {
241                 talloc_free(mem_ctx);
242                 return ret;
243         }
244
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),
252                                                   res->count));
253                 /* What happend?  The above add/modify worked... */
254                 talloc_free(mem_ctx);
255                 return LDB_ERR_NO_SUCH_OBJECT;
256         }
257
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);
265
266         domain_sid         = samdb_result_sid_prefix(mem_ctx, res->msgs[0], "objectSid");
267
268         
269         objectclasses = ldb_msg_find_element(res->msgs[0], "objectClass");
270         person_val = data_blob_string_const("person");
271         
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
275                  * know... */
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;
282         }
283
284         computer_val = data_blob_string_const("computer");
285         
286         if (ldb_msg_find_val(objectclasses, &computer_val)) {
287                 is_computer = True;
288         } else {
289                 is_computer = False;
290         }
291         
292         domain_expression  = talloc_asprintf(mem_ctx, "(&(objectSid=%s)(objectClass=domain))", 
293                                              ldap_encode_ndr_dom_sid(mem_ctx, domain_sid));
294
295         /* Find the user's domain, then find out the domain password
296          * properties */
297         ret = ldb_search(module->ldb, NULL, LDB_SCOPE_SUBTREE, domain_expression, 
298                          domain_attrs, &dom_res);
299         if (ret) {
300                 talloc_free(mem_ctx);
301                 return ret;
302         }
303
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),
310                                                   dom_res->count));
311                 talloc_free(mem_ctx);
312                 return LDB_ERR_NO_SUCH_OBJECT;
313         }
314
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);
319
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;
324         }
325
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);
329
330 #define CHECK_RET(x) \
331         do {                                    \
332                 int check_ret = x;              \
333                 if (check_ret != LDB_SUCCESS) { \
334                         talloc_free(mem_ctx);   \
335                         return check_ret;       \
336                 }                               \
337         } while(0)
338
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
344          * password */
345         if (unicodePwd) {
346                 Principal *salt_principal;
347                 const char *user_principal_name = ldb_msg_find_string(res->msgs[0], "userPrincipalName", NULL);
348                 
349                 Key *keys;
350                 size_t num_keys;
351
352                 /* compute the new nt and lm hashes */
353                 if (E_deshash(unicodePwd, local_lmNewHash.hash)) {
354                         lmPwdHash = &local_lmNewHash;
355                 } else {
356                         lmPwdHash = NULL;
357                 }
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", 
364                                              ntPwdHash));
365                 CHECK_RET(ldb_msg_add_empty(modify_msg, "lmPwdHash", 
366                                             LDB_FLAG_MOD_REPLACE));
367                 if (lmPwdHash) {
368                         CHECK_RET(samdb_msg_add_hash(module->ldb, req, 
369                                                      modify_msg, "lmPwdHash", 
370                                                      lmPwdHash));
371                 }
372
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 */
376
377                 if (is_computer) {
378                         /* Determine a salting principal */
379                         char *samAccountName = talloc_strdup(mem_ctx, ldb_msg_find_string(res->msgs[0], "samAccountName", NULL));
380                         char *saltbody;
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;
388                         }
389                         if (samAccountName[strlen(samAccountName)-1] == '$') {
390                                 samAccountName[strlen(samAccountName)-1] = '\0';
391                         }
392                         saltbody = talloc_asprintf(mem_ctx, "%s.%s", samAccountName, dnsDomain);
393                         
394                         krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, "host", saltbody, NULL);
395                 } else if (user_principal_name) {
396                         char *p;
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;
401                         } else {
402                                 p = strchr(user_principal_name, '@');
403                                 if (p) {
404                                         p[0] = '\0';
405                                 }
406                                 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, user_principal_name, NULL);
407                         } 
408                 } else {
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;
417                         }
418                         krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, samAccountName, NULL);
419                 }
420
421
422                 if (krb5_ret) {
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;
430                 }
431
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);
436
437                 if (krb5_ret) {
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;
445                 }
446
447                 /* Walking 
448                  */
449                 for (i=0; i < num_keys; i++) {
450                         unsigned char *buf;
451                         size_t buf_size;
452                         size_t len;
453                         struct ldb_val val;
454                         
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 */
460                                 continue;
461                         }
462                         ASN1_MALLOC_ENCODE(Key, buf, buf_size, &keys[i], &len, krb5_ret);
463                         
464                         val.data = talloc_memdup(req, buf, len);
465                         val.length = len;
466                         free(buf);
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;
471                         }
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);
476                                 return ret;
477                         }
478                 }
479                 
480                 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
481         }
482
483         /* Possibly kill off the cleartext or store it */
484         CHECK_RET(ldb_msg_add_empty(modify_msg, "unicodePwd", LDB_FLAG_MOD_REPLACE));
485
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));
489         }
490         
491         /* Even if we didn't get a unicodePwd, we can still setup
492          * krb5Key from the NT hash. 
493          *
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
497          */
498         if (ntPwdHash) {
499                 unsigned char *buf;
500                 size_t buf_size;
501                 size_t len;
502                 struct ldb_val val;
503                 Key key;
504                 
505                 key.mkvno = 0;
506                 key.salt = NULL; /* No salt for this enc type */
507
508                 krb5_ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
509                                          ENCTYPE_ARCFOUR_HMAC,
510                                          ntPwdHash->hash, sizeof(ntPwdHash->hash), 
511                                          &key.key);
512                 if (krb5_ret) {
513                         return LDB_ERR_OPERATIONS_ERROR;
514                 }
515                 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &key, &len, krb5_ret);
516                 krb5_free_keyblock_contents(smb_krb5_context->krb5_context,
517                                             &key.key);
518                 
519                 val.data = talloc_memdup(req, buf, len);
520                 val.length = len;
521                 free(buf);
522                 if (!val.data || ret) {
523                         return LDB_ERR_OPERATIONS_ERROR;
524                 }
525                 CHECK_RET(ldb_msg_add_value(modify_msg, "krb5Key", &val));
526         }
527
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));
534         }
535
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 ) {
538                 if (kvno == 0) {
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));
542                 } else {
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
547                          * such guarantee) */
548                         
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) {
552                                 return -1;
553                         }
554                         old_kvno.length = strlen((char *)old_kvno.data);
555                         
556                         new_kvno.data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", kvno + 1);
557                         if (!new_kvno.data) {
558                                 return -1;
559                         }
560                         new_kvno.length = strlen((char *)new_kvno.data);
561                         
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",
565                                                     LDB_FLAG_MOD_ADD));
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;
570                 }
571         }
572
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));
577
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, 
585                                                 pwdHistoryLength);
586                 if (!new_lmPwdHistory) {
587                         return LDB_ERR_OPERATIONS_ERROR;
588                 }
589                 new_ntPwdHistory = talloc_array(mem_ctx, struct samr_Password, 
590                                                 pwdHistoryLength);
591                 if (!new_ntPwdHistory) {
592                         return LDB_ERR_OPERATIONS_ERROR;
593                 }
594                 for (i=0;i<MIN(pwdHistoryLength-1, lmPwdHistory_len);i++) {
595                         new_lmPwdHistory[i+1] = lmPwdHistory[i];
596                 }
597                 for (i=0;i<MIN(pwdHistoryLength-1, ntPwdHistory_len);i++) {
598                         new_ntPwdHistory[i+1] = ntPwdHistory[i];
599                 }
600                 
601                 /* Don't store 'long' passwords in the LM history, 
602                    but make sure to 'expire' one password off the other end */
603                 if (lmOldHash) {
604                         new_lmPwdHistory[0] = *lmOldHash;
605                 } else {
606                         ZERO_STRUCT(new_lmPwdHistory[0]);
607                 }
608                 lmPwdHistory_len = MIN(lmPwdHistory_len + 1, pwdHistoryLength);
609                 
610                 /* Likewise, we might not have a new NT password (lm
611                  * only password change function) */
612                 if (ntOldHash) {
613                         new_ntPwdHistory[0] = *ntOldHash;
614                 } else {
615                         ZERO_STRUCT(new_ntPwdHistory[0]);
616                 }
617                 ntPwdHistory_len = MIN(ntPwdHistory_len + 1, pwdHistoryLength);
618                 
619                 CHECK_RET(samdb_msg_add_hashes(module->ldb, mem_ctx, modify_msg, 
620                                                "lmPwdHistory", 
621                                                new_lmPwdHistory, 
622                                                lmPwdHistory_len));
623                 
624                 CHECK_RET(samdb_msg_add_hashes(module->ldb, mem_ctx, modify_msg, 
625                                                "ntPwdHistory", 
626                                                new_ntPwdHistory, 
627                                                ntPwdHistory_len));
628         }
629
630         /* Too much code above, we should check we got it close to reasonable */
631         CHECK_RET(ldb_msg_sanity_check(modify_msg));
632
633         modify_request.operation = LDB_REQ_MODIFY;
634         modify_request.op.mod.message = modify_msg;
635
636         ret = ldb_next_request(module, &modify_request);
637         
638         talloc_free(mem_ctx);
639         return ret;
640 }
641
642 /* add_record: do things with the unicodePwd attribute */
643 static int password_hash_add(struct ldb_module *module, struct ldb_request *req)
644 {
645         const struct ldb_message *msg = req->op.add.message;
646
647         ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_record\n");
648
649         if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
650                 return ldb_next_request(module, req);
651         }
652         
653         return password_hash_handle(module, req, msg);
654 }
655
656 /* modify_record: do things with the unicodePwd attribute */
657 static int password_hash_modify(struct ldb_module *module, struct ldb_request *req)
658 {
659         const struct ldb_message *msg = req->op.mod.message;
660
661         ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_modify_record\n");
662
663         if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
664                 return ldb_next_request(module, req);
665         }
666         
667         return password_hash_handle(module, req, msg);
668 }
669
670 static int password_hash_request(struct ldb_module *module, struct ldb_request *req)
671 {
672         switch (req->operation) {
673
674         case LDB_REQ_ADD:
675                 return password_hash_add(module, req);
676
677         case LDB_REQ_MODIFY:
678                 return password_hash_modify(module, req);
679
680         default:
681                 return ldb_next_request(module, req);
682
683         }
684 }
685
686 static const struct ldb_module_ops password_hash_ops = {
687         .name          = "password_hash",
688         .request       = password_hash_request
689 };
690
691
692 /* the init function */
693 #ifdef HAVE_DLOPEN_DISABLED
694  struct ldb_module *init_module(struct ldb_context *ldb, const char *options[])
695 #else
696 struct ldb_module *password_hash_module_init(struct ldb_context *ldb, const char *options[])
697 #endif
698 {
699         struct ldb_module *ctx;
700
701         ctx = talloc(ldb, struct ldb_module);
702         if (!ctx)
703                 return NULL;
704
705         ctx->private_data = NULL;
706         ctx->ldb = ldb;
707         ctx->prev = ctx->next = NULL;
708         ctx->ops = &password_hash_ops;
709
710         return ctx;
711 }