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