r12895: Error strings save lives.
[kai/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/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 sambaPassword 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 *sambaPassword = NULL;
75         struct samr_Password *sambaLMPwdHistory, *sambaNTPwdHistory;
76         struct samr_Password *lmPwdHash, *ntPwdHash;
77         struct samr_Password *lmOldHash = NULL, *ntOldHash = NULL;
78         struct samr_Password *new_sambaLMPwdHistory, *new_sambaNTPwdHistory;
79         struct samr_Password local_lmNewHash, local_ntNewHash;
80         int sambaLMPwdHistory_len, sambaNTPwdHistory_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 = NULL;
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", "sambaLMPwdHistory", 
109                                      "sambaNTPwdHistory", 
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 sambaPassword, 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, "sambaPassword")) == 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                 search_request = talloc(mem_ctx, struct ldb_request);
136                 if (!search_request) {
137                         talloc_free(mem_ctx);
138                         return LDB_ERR_OPERATIONS_ERROR;
139                 }
140
141                 /* Look up the old ntPwdHash and lmPwdHash values, so
142                  * we can later place these into the password
143                  * history */
144
145                 search_request->operation = LDB_REQ_SEARCH;
146                 search_request->op.search.base = dn;
147                 search_request->op.search.scope = LDB_SCOPE_BASE;
148                 search_request->op.search.tree = ldb_parse_tree(module->ldb, NULL);
149                 search_request->op.search.attrs = old_user_attrs;
150                 search_request->controls = NULL;
151                 
152                 old_ret = ldb_next_request(module, search_request);
153         }
154
155         /* we can't change things untill we copy it */
156         msg2 = ldb_msg_copy_shallow(mem_ctx, msg);
157
158         /* look again, this time at the copied attribute */
159         if (!msg2 || (attribute = ldb_msg_find_element(msg2, "sambaPassword")) == NULL ) {
160                 /* Gah?  where did it go?  Oh well... */
161                 return LDB_ERR_OPERATIONS_ERROR;
162         }
163
164         /* Wipe out the sambaPassword attribute set, we will handle it in
165          * the second modify.  We might not want it written to disk */
166         
167         if (req->operation == LDB_REQ_ADD) {
168                 if (attribute->num_values > 1) {
169                         ldb_set_errstring(module, 
170                                           talloc_asprintf(mem_ctx, "sambaPassword_handle: "
171                                                           "attempted set of multiple sambaPassword attributes on %s rejected",
172                                                           ldb_dn_linearize(mem_ctx, dn)));
173                         return LDB_ERR_CONSTRAINT_VIOLATION;
174                 }
175
176                 if (attribute->num_values == 1) {
177                         sambaPassword = (const char *)attribute->values[0].data;
178                         ldb_msg_remove_attr(msg2, "sambaPassword");
179                 }
180         } else if (((attribute->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_ADD)
181                    || ((attribute->flags & LDB_FLAG_MOD_MASK) == LDB_FLAG_MOD_REPLACE)) {
182                 if (attribute->num_values > 1) {
183                         ldb_set_errstring(module, 
184                                           talloc_asprintf(mem_ctx, "sambaPassword_handle: "
185                                                           "attempted set of multiple sambaPassword attributes on %s rejected",
186                                                           ldb_dn_linearize(mem_ctx, dn)));
187                         return LDB_ERR_CONSTRAINT_VIOLATION;
188                 }
189                 
190                 if (attribute->num_values == 1) {
191                         sambaPassword = (const char *)attribute->values[0].data;
192                         ldb_msg_remove_attr(msg2, "sambaPassword");
193                 }
194         }
195
196         modified_orig_request = talloc(mem_ctx, struct ldb_request);
197         if (!modified_orig_request) {
198                 talloc_free(mem_ctx);
199                 return LDB_ERR_OPERATIONS_ERROR;
200         }
201
202         *modified_orig_request = *req;
203         switch (modified_orig_request->operation) {
204         case LDB_REQ_ADD:
205                 modified_orig_request->op.add.message = msg2;
206                 break;
207         case LDB_REQ_MODIFY:
208                 modified_orig_request->op.mod.message = msg2;
209                 break;
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                 return ret;
216         }
217
218         /* While we do the search first (for the old password hashes),
219          * we don't want to override any error that the modify may
220          * have returned.  Now check the error */
221         if (req->operation == LDB_REQ_MODIFY) {
222                 if (old_ret) {
223                         talloc_free(mem_ctx);
224                         return old_ret;
225                 }
226
227                 /* Find out the old passwords details of the user */
228                 old_res = search_request->op.search.res;
229                 
230                 if (old_res->count != 1) {
231                         ldb_set_errstring(module, 
232                                           talloc_asprintf(mem_ctx, "password_hash_handle: "
233                                                           "(pre) search for %s found %d != 1 objects, for entry we just modified",
234                                                           ldb_dn_linearize(mem_ctx, dn),
235                                                           old_res->count));
236                         /* What happend?  The above add/modify worked... */
237                         talloc_free(mem_ctx);
238                         return LDB_ERR_NO_SUCH_OBJECT;
239                 }
240
241                 lmOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0],   "lmPwdHash");
242                 ntOldHash = samdb_result_hash(mem_ctx, old_res->msgs[0],   "ntPwdHash");
243         }
244
245         /* Start finding out details we need for the second modify.
246          * We do this after the first add/modify because other modules
247          * will have filled in the templates, and we may have had
248          * things like the username (affecting the salt) changed along
249          * with the password. */
250
251         /* Now find out what is on the entry after the above add/modify */
252         search_request = talloc(mem_ctx, struct ldb_request);
253         if (!search_request) {
254                 talloc_free(mem_ctx);
255                 return LDB_ERR_OPERATIONS_ERROR;
256         }
257
258         search_request->operation       = LDB_REQ_SEARCH;
259         search_request->op.search.base  = dn;
260         search_request->op.search.scope = LDB_SCOPE_BASE;
261         search_request->op.search.tree  = ldb_parse_tree(module->ldb, NULL);
262         search_request->op.search.attrs = user_attrs;
263         search_request->controls = NULL;
264         
265         ret = ldb_next_request(module, search_request);
266         if (ret) {
267                 talloc_free(mem_ctx);
268                 return ret;
269         }
270
271         /* Find out the full details of the user */
272         res = search_request->op.search.res;
273         if (res->count != 1) {
274                 ldb_set_errstring(module, 
275                                   talloc_asprintf(mem_ctx, "password_hash_handle: "
276                                                   "search for %s found %d != 1 objects, for entry we just added/modified",
277                                                   ldb_dn_linearize(mem_ctx, dn),
278                                                   res->count));
279                 /* What happend?  The above add/modify worked... */
280                 talloc_free(mem_ctx);
281                 return LDB_ERR_NO_SUCH_OBJECT;
282         }
283
284         userAccountControl = samdb_result_uint(res->msgs[0],   "userAccountControl", 0);
285         sambaLMPwdHistory_len   = samdb_result_hashes(mem_ctx, res->msgs[0], 
286                                                  "sambaLMPwdHistory", &sambaLMPwdHistory);
287         sambaNTPwdHistory_len   = samdb_result_hashes(mem_ctx, res->msgs[0], 
288                                                  "sambaNTPwdHistory", &sambaNTPwdHistory);
289         ntPwdHash          = samdb_result_hash(mem_ctx, res->msgs[0],   "ntPwdHash");
290         kvno               = samdb_result_uint(res->msgs[0],   "msDS-KeyVersionNumber", 0);
291
292         domain_sid         = samdb_result_sid_prefix(mem_ctx, res->msgs[0], "objectSid");
293
294         
295         objectclasses = ldb_msg_find_element(res->msgs[0], "objectClass");
296         person_val = data_blob_string_const("person");
297         
298         if (!objectclasses || !ldb_msg_find_val(objectclasses, &person_val)) {
299                 /* Not a 'person', so the rest of this doesn't make
300                  * sense.  How we got a sambaPassword this far I don't
301                  * know... */
302                 ldb_set_errstring(module, 
303                                   talloc_asprintf(mem_ctx, "password_hash_handle: "
304                                                   "attempted set of sambaPassword on non-'person' object %s rejected",
305                                                   ldb_dn_linearize(mem_ctx, dn)));
306                 talloc_free(mem_ctx);
307                 return LDB_ERR_CONSTRAINT_VIOLATION;
308         }
309
310         computer_val = data_blob_string_const("computer");
311         
312         if (ldb_msg_find_val(objectclasses, &computer_val)) {
313                 is_computer = True;
314         } else {
315                 is_computer = False;
316         }
317         
318         domain_expression  = talloc_asprintf(mem_ctx, "(&(objectSid=%s)(objectClass=domain))", 
319                                              ldap_encode_ndr_dom_sid(mem_ctx, domain_sid));
320
321         /* Find the user's domain, then find out the domain password
322          * properties */
323         ret = ldb_search(module->ldb, NULL, LDB_SCOPE_SUBTREE, domain_expression, 
324                          domain_attrs, &dom_res);
325         if (ret) {
326                 talloc_free(mem_ctx);
327                 return ret;
328         }
329
330         if (dom_res->count != 1) {
331                 /* What happend?  The user we are modifying must be odd... */
332                 ldb_set_errstring(module, 
333                                   talloc_asprintf(mem_ctx, "password_hash_handle: "
334                                                   "search for domain %s found %d != 1 objects",
335                                                   dom_sid_string(mem_ctx, domain_sid),
336                                                   dom_res->count));
337                 talloc_free(mem_ctx);
338                 return LDB_ERR_NO_SUCH_OBJECT;
339         }
340
341         pwdProperties    = samdb_result_uint(dom_res->msgs[0],   "pwdProperties", 0);
342         pwdHistoryLength = samdb_result_uint(dom_res->msgs[0],   "pwdHistoryLength", 0);
343         dnsDomain        = ldb_msg_find_string(dom_res->msgs[0], "dnsDomain", NULL);
344         realm            = strupper_talloc(mem_ctx, dnsDomain);
345
346         /* Some operations below require kerberos contexts */
347         if (smb_krb5_init_context(mem_ctx, &smb_krb5_context) != 0) {
348                 talloc_free(mem_ctx);
349                 return LDB_ERR_OPERATIONS_ERROR;
350         }
351
352         /* Prepare the modifications to set all the hash/key types */
353         modify_msg = ldb_msg_new(req);
354         modify_msg->dn = talloc_reference(modify_msg, dn);
355
356 #define CHECK_RET(x) \
357         do {                                    \
358                 int check_ret = x;              \
359                 if (check_ret != LDB_SUCCESS) { \
360                         talloc_free(mem_ctx);   \
361                         return check_ret;       \
362                 }                               \
363         } while(0)
364
365         /* Setup krb5Key (we want to either delete an existing value,
366          * or replace with a new one).  Both the unicode and NT hash
367          * only branches append keys to this multivalued entry. */
368         CHECK_RET(ldb_msg_add_empty(modify_msg, "krb5Key", LDB_FLAG_MOD_REPLACE));
369         /* Yay, we can compute new password hashes from the unicode
370          * password */
371         if (sambaPassword) {
372                 Principal *salt_principal;
373                 const char *user_principal_name = ldb_msg_find_string(res->msgs[0], "userPrincipalName", NULL);
374                 
375                 Key *keys;
376                 size_t num_keys;
377
378                 /* compute the new nt and lm hashes */
379                 if (E_deshash(sambaPassword, local_lmNewHash.hash)) {
380                         lmPwdHash = &local_lmNewHash;
381                 } else {
382                         lmPwdHash = NULL;
383                 }
384                 E_md4hash(sambaPassword, local_ntNewHash.hash);
385                 ntPwdHash = &local_ntNewHash;
386                 CHECK_RET(ldb_msg_add_empty(modify_msg, "ntPwdHash", 
387                                             LDB_FLAG_MOD_REPLACE));
388                 CHECK_RET(samdb_msg_add_hash(module->ldb, req, 
389                                              modify_msg, "ntPwdHash", 
390                                              ntPwdHash));
391                 CHECK_RET(ldb_msg_add_empty(modify_msg, "lmPwdHash", 
392                                             LDB_FLAG_MOD_REPLACE));
393                 if (lmPwdHash) {
394                         CHECK_RET(samdb_msg_add_hash(module->ldb, req, 
395                                                      modify_msg, "lmPwdHash", 
396                                                      lmPwdHash));
397                 }
398
399                 /* Many, many thanks to lukeh@padl.com for this
400                  * algorithm, described in his Nov 10 2004 mail to
401                  * samba-technical@samba.org */
402
403                 if (is_computer) {
404                         /* Determine a salting principal */
405                         char *samAccountName = talloc_strdup(mem_ctx, ldb_msg_find_string(res->msgs[0], "samAccountName", NULL));
406                         char *saltbody;
407                         if (!samAccountName) {
408                                 ldb_set_errstring(module, 
409                                                   talloc_asprintf(mem_ctx, "password_hash_handle: "
410                                                                   "generation of new kerberos keys failed: %s is a computer without a samAccountName",
411                                                                   ldb_dn_linearize(mem_ctx, dn)));
412                                 talloc_free(mem_ctx);
413                                 return LDB_ERR_OPERATIONS_ERROR;
414                         }
415                         if (samAccountName[strlen(samAccountName)-1] == '$') {
416                                 samAccountName[strlen(samAccountName)-1] = '\0';
417                         }
418                         saltbody = talloc_asprintf(mem_ctx, "%s.%s", samAccountName, dnsDomain);
419                         
420                         krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, "host", saltbody, NULL);
421                 } else if (user_principal_name) {
422                         char *p;
423                         user_principal_name = talloc_strdup(mem_ctx, user_principal_name);
424                         if (!user_principal_name) {
425                                 talloc_free(mem_ctx);
426                                 return LDB_ERR_OPERATIONS_ERROR;
427                         } else {
428                                 p = strchr(user_principal_name, '@');
429                                 if (p) {
430                                         p[0] = '\0';
431                                 }
432                                 krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, user_principal_name, NULL);
433                         } 
434                 } else {
435                         const char *samAccountName = ldb_msg_find_string(res->msgs[0], "samAccountName", NULL);
436                         if (!samAccountName) {
437                                 ldb_set_errstring(module, 
438                                                   talloc_asprintf(mem_ctx, "password_hash_handle: "
439                                                                   "generation of new kerberos keys failed: %s has no samAccountName",
440                                                                   ldb_dn_linearize(mem_ctx, dn)));
441                                 talloc_free(mem_ctx);
442                                 return LDB_ERR_OPERATIONS_ERROR;
443                         }
444                         krb5_ret = krb5_make_principal(smb_krb5_context->krb5_context, &salt_principal, realm, samAccountName, NULL);
445                 }
446
447
448                 if (krb5_ret) {
449                         ldb_set_errstring(module, 
450                                           talloc_asprintf(mem_ctx, "password_hash_handle: "
451                                                           "generation of a saltking principal failed: %s",
452                                                           smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
453                                                                                      krb5_ret, mem_ctx)));
454                         talloc_free(mem_ctx);
455                         return LDB_ERR_OPERATIONS_ERROR;
456                 }
457
458                 /* TODO: We may wish to control the encryption types chosen in future */
459                 krb5_ret = hdb_generate_key_set_password(smb_krb5_context->krb5_context,
460                                                     salt_principal, sambaPassword, &keys, &num_keys);
461                 krb5_free_principal(smb_krb5_context->krb5_context, salt_principal);
462
463                 if (krb5_ret) {
464                         ldb_set_errstring(module, 
465                                           talloc_asprintf(mem_ctx, "password_hash_handle: "
466                                                           "generation of new kerberos keys failed: %s",
467                                                           smb_get_krb5_error_message(smb_krb5_context->krb5_context, 
468                                                                                      krb5_ret, mem_ctx)));
469                         talloc_free(mem_ctx);
470                         return LDB_ERR_OPERATIONS_ERROR;
471                 }
472
473                 /* Walking 
474                  */
475                 for (i=0; i < num_keys; i++) {
476                         unsigned char *buf;
477                         size_t buf_size;
478                         size_t len;
479                         struct ldb_val val;
480                         
481                         if (keys[i].key.keytype == ENCTYPE_ARCFOUR_HMAC) {
482                                 /* We might end up doing this below:
483                                  * This ensures we get the unicode
484                                  * conversion right.  This should also
485                                  * be fixed in the Heimdal libs */
486                                 continue;
487                         }
488                         ASN1_MALLOC_ENCODE(Key, buf, buf_size, &keys[i], &len, krb5_ret);
489                         
490                         val.data = talloc_memdup(req, buf, len);
491                         val.length = len;
492                         free(buf);
493                         if (!val.data || krb5_ret) {
494                                 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
495                                 talloc_free(mem_ctx);
496                                 return LDB_ERR_OPERATIONS_ERROR;
497                         }
498                         ret = ldb_msg_add_value(modify_msg, "krb5Key", &val);
499                         if (ret != LDB_SUCCESS) {
500                                 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
501                                 talloc_free(mem_ctx);
502                                 return ret;
503                         }
504                 }
505                 
506                 hdb_free_keys (smb_krb5_context->krb5_context, num_keys, keys);
507         }
508
509         /* Possibly kill off the cleartext or store it */
510         CHECK_RET(ldb_msg_add_empty(modify_msg, "sambaPassword", LDB_FLAG_MOD_REPLACE));
511
512         if (sambaPassword && (pwdProperties & DOMAIN_PASSWORD_STORE_CLEARTEXT) &&
513             (userAccountControl & UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED)) {
514                 CHECK_RET(ldb_msg_add_string(modify_msg, "sambaPassword", sambaPassword));
515         }
516         
517         /* Even if we didn't get a sambaPassword, we can still setup
518          * krb5Key from the NT hash. 
519          *
520          * This is an append, so it works with the 'continue' in the
521          * unicode loop above, to use Samba's NT hash function, which
522          * is more correct than Heimdal's
523          */
524         if (ntPwdHash) {
525                 unsigned char *buf;
526                 size_t buf_size;
527                 size_t len;
528                 struct ldb_val val;
529                 Key key;
530                 
531                 key.mkvno = 0;
532                 key.salt = NULL; /* No salt for this enc type */
533
534                 krb5_ret = krb5_keyblock_init(smb_krb5_context->krb5_context,
535                                          ENCTYPE_ARCFOUR_HMAC,
536                                          ntPwdHash->hash, sizeof(ntPwdHash->hash), 
537                                          &key.key);
538                 if (krb5_ret) {
539                         return LDB_ERR_OPERATIONS_ERROR;
540                 }
541                 ASN1_MALLOC_ENCODE(Key, buf, buf_size, &key, &len, krb5_ret);
542                 krb5_free_keyblock_contents(smb_krb5_context->krb5_context,
543                                             &key.key);
544                 
545                 val.data = talloc_memdup(req, buf, len);
546                 val.length = len;
547                 free(buf);
548                 if (!val.data || ret) {
549                         return LDB_ERR_OPERATIONS_ERROR;
550                 }
551                 CHECK_RET(ldb_msg_add_value(modify_msg, "krb5Key", &val));
552         }
553
554         /* If the original caller did anything with pwdLastSet then skip this.  It could be an incoming samsync */
555         if ((attribute = ldb_msg_find_element(msg, "pwdLastSet")) == NULL ) {
556                 /* Update the password last set time */
557                 unix_to_nt_time(&now_nt, now);
558                 CHECK_RET(ldb_msg_add_empty(modify_msg, "pwdLastSet", LDB_FLAG_MOD_REPLACE));
559                 CHECK_RET(samdb_msg_add_uint64(module->ldb, mem_ctx, modify_msg, "pwdLastSet", now_nt));
560         }
561
562         /* If the original caller did anything with "msDS-KeyVersionNumber" then skip this.  It could be an incoming samsync */
563         if ((attribute = ldb_msg_find_element(msg, "msDS-KeyVersionNumber")) == NULL ) {
564                 if (kvno == 0) {
565                         CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
566                                                     LDB_FLAG_MOD_REPLACE));
567                         CHECK_RET(samdb_msg_add_uint(module->ldb, mem_ctx, modify_msg, "msDS-KeyVersionNumber", kvno + 1));
568                 } else {
569                         /* While we should be in a transaction, go one extra
570                          * step in the dance for an 'atomic' increment.  This
571                          * may be of value against remote LDAP servers.  (Note
572                          * however that Mulitmaster replication stil offers no
573                          * such guarantee) */
574                         
575                         struct ldb_val old_kvno, new_kvno;
576                         old_kvno.data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", kvno);
577                         if (!old_kvno.data) {
578                                 return -1;
579                         }
580                         old_kvno.length = strlen((char *)old_kvno.data);
581                         
582                         new_kvno.data = (uint8_t *)talloc_asprintf(mem_ctx, "%u", kvno + 1);
583                         if (!new_kvno.data) {
584                                 return -1;
585                         }
586                         new_kvno.length = strlen((char *)new_kvno.data);
587                         
588                         CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
589                                                     LDB_FLAG_MOD_DELETE));
590                         CHECK_RET(ldb_msg_add_empty(modify_msg, "msDS-KeyVersionNumber",
591                                                     LDB_FLAG_MOD_ADD));
592                         modify_msg->elements[modify_msg->num_elements - 2].num_values = 1;
593                         modify_msg->elements[modify_msg->num_elements - 2].values = &old_kvno;
594                         modify_msg->elements[modify_msg->num_elements - 1].num_values = 1;
595                         modify_msg->elements[modify_msg->num_elements - 1].values = &new_kvno;
596                 }
597         }
598
599         CHECK_RET(ldb_msg_add_empty(modify_msg, "sambaLMPwdHistory",
600                                     LDB_FLAG_MOD_REPLACE));
601         CHECK_RET(ldb_msg_add_empty(modify_msg, "sambaNTPwdHistory",
602                                     LDB_FLAG_MOD_REPLACE));
603
604         /* If we have something to put into the history, or an old
605          * history element to expire, update the history */
606         if (pwdHistoryLength > 0 && 
607             ((sambaNTPwdHistory_len > 0) || (sambaLMPwdHistory_len > 0) 
608              || lmOldHash || ntOldHash)) {
609                 /* store the password history */
610                 new_sambaLMPwdHistory = talloc_array(mem_ctx, struct samr_Password, 
611                                                 pwdHistoryLength);
612                 if (!new_sambaLMPwdHistory) {
613                         return LDB_ERR_OPERATIONS_ERROR;
614                 }
615                 new_sambaNTPwdHistory = talloc_array(mem_ctx, struct samr_Password, 
616                                                 pwdHistoryLength);
617                 if (!new_sambaNTPwdHistory) {
618                         return LDB_ERR_OPERATIONS_ERROR;
619                 }
620                 for (i=0;i<MIN(pwdHistoryLength-1, sambaLMPwdHistory_len);i++) {
621                         new_sambaLMPwdHistory[i+1] = sambaLMPwdHistory[i];
622                 }
623                 for (i=0;i<MIN(pwdHistoryLength-1, sambaNTPwdHistory_len);i++) {
624                         new_sambaNTPwdHistory[i+1] = sambaNTPwdHistory[i];
625                 }
626                 
627                 /* Don't store 'long' passwords in the LM history, 
628                    but make sure to 'expire' one password off the other end */
629                 if (lmOldHash) {
630                         new_sambaLMPwdHistory[0] = *lmOldHash;
631                 } else {
632                         ZERO_STRUCT(new_sambaLMPwdHistory[0]);
633                 }
634                 sambaLMPwdHistory_len = MIN(sambaLMPwdHistory_len + 1, pwdHistoryLength);
635                 
636                 /* Likewise, we might not have a new NT password (lm
637                  * only password change function) */
638                 if (ntOldHash) {
639                         new_sambaNTPwdHistory[0] = *ntOldHash;
640                 } else {
641                         ZERO_STRUCT(new_sambaNTPwdHistory[0]);
642                 }
643                 sambaNTPwdHistory_len = MIN(sambaNTPwdHistory_len + 1, pwdHistoryLength);
644                 
645                 CHECK_RET(samdb_msg_add_hashes(module->ldb, mem_ctx, modify_msg, 
646                                                "sambaLMPwdHistory", 
647                                                new_sambaLMPwdHistory, 
648                                                sambaLMPwdHistory_len));
649                 
650                 CHECK_RET(samdb_msg_add_hashes(module->ldb, mem_ctx, modify_msg, 
651                                                "sambaNTPwdHistory", 
652                                                new_sambaNTPwdHistory, 
653                                                sambaNTPwdHistory_len));
654         }
655
656         /* Too much code above, we should check we got it close to reasonable */
657         CHECK_RET(ldb_msg_sanity_check(modify_msg));
658
659         modify_request = talloc(mem_ctx, struct ldb_request);
660         if (!modify_request) {
661                 talloc_free(mem_ctx);
662                 return LDB_ERR_OPERATIONS_ERROR;
663         }
664
665         modify_request->operation = LDB_REQ_MODIFY;
666         modify_request->op.mod.message = modify_msg;
667         modify_request->controls = NULL;
668
669         ret = ldb_next_request(module, modify_request);
670         
671         talloc_free(mem_ctx);
672         return ret;
673 }
674
675 /* add_record: do things with the sambaPassword attribute */
676 static int password_hash_add(struct ldb_module *module, struct ldb_request *req)
677 {
678         const struct ldb_message *msg = req->op.add.message;
679
680         ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_add_record\n");
681
682         if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
683                 return ldb_next_request(module, req);
684         }
685         
686         return password_hash_handle(module, req, msg);
687 }
688
689 /* modify_record: do things with the sambaPassword attribute */
690 static int password_hash_modify(struct ldb_module *module, struct ldb_request *req)
691 {
692         const struct ldb_message *msg = req->op.mod.message;
693
694         ldb_debug(module->ldb, LDB_DEBUG_TRACE, "password_hash_modify_record\n");
695
696         if (ldb_dn_is_special(msg->dn)) { /* do not manipulate our control entries */
697                 return ldb_next_request(module, req);
698         }
699         
700         return password_hash_handle(module, req, msg);
701 }
702
703 static int password_hash_request(struct ldb_module *module, struct ldb_request *req)
704 {
705         switch (req->operation) {
706
707         case LDB_REQ_ADD:
708                 return password_hash_add(module, req);
709
710         case LDB_REQ_MODIFY:
711                 return password_hash_modify(module, req);
712
713         default:
714                 return ldb_next_request(module, req);
715
716         }
717 }
718
719 static const struct ldb_module_ops password_hash_ops = {
720         .name          = "password_hash",
721         .request       = password_hash_request
722 };
723
724
725 /* the init function */
726 struct ldb_module *password_hash_module_init(struct ldb_context *ldb, const char *options[])
727 {
728         struct ldb_module *ctx;
729
730         ctx = talloc(ldb, struct ldb_module);
731         if (!ctx)
732                 return NULL;
733
734         ctx->private_data = NULL;
735         ctx->ldb = ldb;
736         ctx->prev = ctx->next = NULL;
737         ctx->ops = &password_hash_ops;
738
739         return ctx;
740 }