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