s3:trusts_util: make use the workstation password change more robust
[samba.git] / source3 / libsmb / trusts_util.c
1 /*
2  *  Unix SMB/CIFS implementation.
3  *  Routines to operate on various trust relationships
4  *  Copyright (C) Andrew Bartlett                   2001
5  *  Copyright (C) Rafal Szczesniak                  2003
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 3 of the License, or
10  *  (at your option) any later version.
11  *  
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *  
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include "includes.h"
22 #include "../libcli/auth/libcli_auth.h"
23 #include "../libcli/auth/netlogon_creds_cli.h"
24 #include "rpc_client/cli_netlogon.h"
25 #include "rpc_client/cli_pipe.h"
26 #include "../librpc/gen_ndr/ndr_netlogon.h"
27 #include "librpc/gen_ndr/secrets.h"
28 #include "secrets.h"
29 #include "passdb.h"
30 #include "libsmb/libsmb.h"
31 #include "source3/include/messages.h"
32 #include "source3/include/g_lock.h"
33
34 /*********************************************************
35  Change the domain password on the PDC.
36  Do most of the legwork ourselfs.  Caller must have
37  already setup the connection to the NETLOGON pipe
38 **********************************************************/
39
40 struct trust_pw_change_state {
41         struct g_lock_ctx *g_ctx;
42         char *g_lock_key;
43 };
44
45 static int trust_pw_change_state_destructor(struct trust_pw_change_state *state)
46 {
47         g_lock_unlock(state->g_ctx, state->g_lock_key);
48         return 0;
49 }
50
51 char *trust_pw_new_value(TALLOC_CTX *mem_ctx,
52                          enum netr_SchannelType sec_channel_type,
53                          int security)
54 {
55         /*
56          * use secure defaults.
57          */
58         size_t min = 128;
59         size_t max = 255;
60
61         switch (sec_channel_type) {
62         case SEC_CHAN_WKSTA:
63         case SEC_CHAN_BDC:
64                 if (security == SEC_DOMAIN) {
65                         /*
66                          * The maximum length of a trust account password.
67                          * Used when we randomly create it, 15 char passwords
68                          * exceed NT4's max password length.
69                          */
70                         min = 14;
71                         max = 14;
72                 }
73                 break;
74         case SEC_CHAN_DNS_DOMAIN:
75                 /*
76                  * new_len * 2 = 498 bytes is the largest possible length
77                  * NL_PASSWORD_VERSION consumes the rest of the possible 512 bytes
78                  * and a confounder with at least 2 bytes is required.
79                  *
80                  * Windows uses new_len = 120 => 240 bytes (utf16)
81                  */
82                 min = 120;
83                 max = 120;
84                 break;
85                 /* fall through */
86         case SEC_CHAN_DOMAIN:
87                 /*
88                  * The maximum length of a trust account password.
89                  * Used when we randomly create it, 15 char passwords
90                  * exceed NT4's max password length.
91                  */
92                 min = 14;
93                 max = 14;
94                 break;
95         default:
96                 break;
97         }
98
99         /*
100          * Create a random machine account password
101          * We create a random buffer and convert that to utf8.
102          * This is similar to what windows is doing.
103          */
104         return generate_random_machine_password(mem_ctx, min, max);
105 }
106
107 NTSTATUS trust_pw_change(struct netlogon_creds_cli_context *context,
108                          struct messaging_context *msg_ctx,
109                          struct dcerpc_binding_handle *b,
110                          const char *domain,
111                          const char *dcname,
112                          bool force)
113 {
114         TALLOC_CTX *frame = talloc_stackframe();
115         const char *context_name = NULL;
116         struct trust_pw_change_state *state;
117         struct cli_credentials *creds = NULL;
118         struct secrets_domain_info1 *info = NULL;
119         struct secrets_domain_info1_change *prev = NULL;
120         const struct samr_Password *current_nt_hash = NULL;
121         const struct samr_Password *previous_nt_hash = NULL;
122         uint8_t num_nt_hashes = 0;
123         uint8_t idx = 0;
124         const struct samr_Password *nt_hashes[1+3] = { NULL, };
125         uint8_t idx_nt_hashes = 0;
126         uint8_t idx_current = UINT8_MAX;
127         enum netr_SchannelType sec_channel_type = SEC_CHAN_NULL;
128         time_t pass_last_set_time;
129         uint32_t old_version = 0;
130         struct pdb_trusted_domain *td = NULL;
131         struct timeval g_timeout = { 0, };
132         int timeout = 0;
133         struct timeval tv = { 0, };
134         char *new_trust_pw_str = NULL;
135         size_t len = 0;
136         DATA_BLOB new_trust_pw_blob = data_blob_null;
137         uint32_t new_version = 0;
138         uint32_t *new_trust_version = NULL;
139         NTSTATUS status;
140         bool ok;
141
142         state = talloc_zero(frame, struct trust_pw_change_state);
143         if (state == NULL) {
144                 TALLOC_FREE(frame);
145                 return NT_STATUS_NO_MEMORY;
146         }
147
148         state->g_ctx = g_lock_ctx_init(state, msg_ctx);
149         if (state->g_ctx == NULL) {
150                 TALLOC_FREE(frame);
151                 return NT_STATUS_NO_MEMORY;
152         }
153
154         state->g_lock_key = talloc_asprintf(state,
155                                 "trust_password_change_%s",
156                                 domain);
157         if (state->g_lock_key == NULL) {
158                 TALLOC_FREE(frame);
159                 return NT_STATUS_NO_MEMORY;
160         }
161
162         g_timeout = timeval_current_ofs(10, 0);
163         status = g_lock_lock(state->g_ctx,
164                              state->g_lock_key,
165                              G_LOCK_WRITE, g_timeout);
166         if (!NT_STATUS_IS_OK(status)) {
167                 DEBUG(1, ("could not get g_lock on [%s]!\n",
168                           state->g_lock_key));
169                 TALLOC_FREE(frame);
170                 return status;
171         }
172
173         talloc_set_destructor(state, trust_pw_change_state_destructor);
174
175         status = pdb_get_trust_credentials(domain, NULL, frame, &creds);
176         if (!NT_STATUS_IS_OK(status)) {
177                 DEBUG(0, ("could not fetch domain creds for domain %s - %s!\n",
178                           domain, nt_errstr(status)));
179                 TALLOC_FREE(frame);
180                 return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
181         }
182
183         current_nt_hash = cli_credentials_get_nt_hash(creds, frame);
184         if (current_nt_hash == NULL) {
185                 DEBUG(0, ("cli_credentials_get_nt_hash failed for domain %s!\n",
186                           domain));
187                 TALLOC_FREE(frame);
188                 return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
189         }
190         previous_nt_hash = cli_credentials_get_old_nt_hash(creds, frame);
191
192         old_version = cli_credentials_get_kvno(creds);
193         pass_last_set_time = cli_credentials_get_password_last_changed_time(creds);
194         sec_channel_type = cli_credentials_get_secure_channel_type(creds);
195
196         new_version = old_version + 1;
197
198         switch (sec_channel_type) {
199         case SEC_CHAN_WKSTA:
200         case SEC_CHAN_BDC:
201                 break;
202         case SEC_CHAN_DNS_DOMAIN:
203         case SEC_CHAN_DOMAIN:
204                 status = pdb_get_trusted_domain(frame, domain, &td);
205                 if (!NT_STATUS_IS_OK(status)) {
206                         DEBUG(0, ("pdb_get_trusted_domain() failed for domain %s - %s!\n",
207                                   domain, nt_errstr(status)));
208                         TALLOC_FREE(frame);
209                         return status;
210                 }
211
212                 new_trust_version = &new_version;
213                 break;
214         default:
215                 TALLOC_FREE(frame);
216                 return NT_STATUS_NOT_SUPPORTED;
217         }
218
219         timeout = lp_machine_password_timeout();
220         if (timeout == 0) {
221                 if (!force) {
222                         DEBUG(10,("machine password never expires\n"));
223                         TALLOC_FREE(frame);
224                         return NT_STATUS_OK;
225                 }
226         }
227
228         tv.tv_sec = pass_last_set_time;
229         DEBUG(10, ("password last changed %s\n",
230                    timeval_string(talloc_tos(), &tv, false)));
231         tv.tv_sec += timeout;
232         DEBUGADD(10, ("password valid until %s\n",
233                       timeval_string(talloc_tos(), &tv, false)));
234
235         if (!force && !timeval_expired(&tv)) {
236                 TALLOC_FREE(frame);
237                 return NT_STATUS_OK;
238         }
239
240         context_name = netlogon_creds_cli_debug_string(context, talloc_tos());
241         if (context_name == NULL) {
242                 TALLOC_FREE(frame);
243                 return NT_STATUS_NO_MEMORY;
244         }
245
246         /*
247          * Create a random machine account password
248          * We create a random buffer and convert that to utf8.
249          * This is similar to what windows is doing.
250          */
251         new_trust_pw_str = trust_pw_new_value(frame, sec_channel_type,
252                                               lp_security());
253         if (new_trust_pw_str == NULL) {
254                 DEBUG(0, ("trust_pw_new_value() failed\n"));
255                 TALLOC_FREE(frame);
256                 return NT_STATUS_NO_MEMORY;
257         }
258
259         len = strlen(new_trust_pw_str);
260         ok = convert_string_talloc(frame, CH_UNIX, CH_UTF16,
261                                    new_trust_pw_str, len,
262                                    (void **)&new_trust_pw_blob.data,
263                                    &new_trust_pw_blob.length);
264         if (!ok) {
265                 status = NT_STATUS_UNMAPPABLE_CHARACTER;
266                 if (errno == ENOMEM) {
267                         status = NT_STATUS_NO_MEMORY;
268                 }
269                 DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
270                         "failed for of %s - %s\n",
271                         domain, nt_errstr(status));
272                 TALLOC_FREE(frame);
273                 return status;
274         }
275
276         switch (sec_channel_type) {
277
278         case SEC_CHAN_WKSTA:
279         case SEC_CHAN_BDC:
280                 status = secrets_prepare_password_change(domain, dcname,
281                                                          new_trust_pw_str,
282                                                          frame, &info, &prev);
283                 if (!NT_STATUS_IS_OK(status)) {
284                         DEBUG(0, ("secrets_prepare_password_change() failed for domain %s!\n",
285                                   domain));
286                         TALLOC_FREE(frame);
287                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
288                 }
289                 TALLOC_FREE(new_trust_pw_str);
290
291                 if (prev != NULL) {
292                         /*
293                          * We had a failure before we changed the password.
294                          */
295                         nt_hashes[idx++] = &prev->password->nt_hash;
296
297                         DEBUG(0,("%s : %s(%s): A password change was already "
298                                  "started against '%s' at %s. Trying to "
299                                  "recover...\n",
300                                  current_timestring(talloc_tos(), false),
301                                  __func__, domain,
302                                  prev->password->change_server,
303                                  nt_time_string(talloc_tos(),
304                                  prev->password->change_time)));
305                         DEBUG(0,("%s : %s(%s): Last failure local[%s] remote[%s] "
306                                  "against '%s' at %s.\n",
307                                  current_timestring(talloc_tos(), false),
308                                  __func__, domain,
309                                  nt_errstr(prev->local_status),
310                                  nt_errstr(prev->remote_status),
311                                  prev->change_server,
312                                  nt_time_string(talloc_tos(),
313                                  prev->change_time)));
314                 }
315
316                 idx_current = idx;
317                 nt_hashes[idx++] = &info->password->nt_hash;
318                 if (info->old_password != NULL) {
319                         nt_hashes[idx++] = &info->old_password->nt_hash;
320                 }
321                 if (info->older_password != NULL) {
322                         nt_hashes[idx++] = &info->older_password->nt_hash;
323                 }
324
325                 /*
326                  * We use the password that's already persitent in
327                  * our database in order to handle failures.
328                  */
329                 data_blob_clear_free(&new_trust_pw_blob);
330                 new_trust_pw_blob = info->next_change->password->cleartext_blob;
331                 break;
332
333         case SEC_CHAN_DNS_DOMAIN:
334         case SEC_CHAN_DOMAIN:
335                 idx_current = idx;
336                 nt_hashes[idx++] = current_nt_hash;
337                 if (previous_nt_hash != NULL) {
338                         nt_hashes[idx++] = previous_nt_hash;
339                 }
340                 break;
341
342         default:
343                 smb_panic("Unsupported secure channel type");
344                 break;
345         }
346         num_nt_hashes = idx;
347
348         DEBUG(0,("%s : %s(%s): Verifying passwords remotely %s.\n",
349                  current_timestring(talloc_tos(), false),
350                  __func__, domain, context_name));
351
352         /*
353          * Check which password the dc knows about.
354          *
355          * TODO:
356          * If the previous password is the only password in common with the dc,
357          * we better skip the password change, or use something like
358          * ServerTrustPasswordsGet() or netr_ServerGetTrustInfo() to fix our
359          * local secrets before doing the change.
360          */
361         status = netlogon_creds_cli_auth(context, b,
362                                          num_nt_hashes,
363                                          nt_hashes,
364                                          &idx_nt_hashes);
365         if (!NT_STATUS_IS_OK(status)) {
366                 DEBUG(0, ("netlogon_creds_cli_auth(%s) failed for old passwords (%u) - %s!\n",
367                           context_name, num_nt_hashes, nt_errstr(status)));
368                 TALLOC_FREE(frame);
369                 return status;
370         }
371
372         if (prev != NULL && idx_nt_hashes == 0) {
373                 DEBUG(0,("%s : %s(%s): Verified new password remotely "
374                          "without changing %s\n",
375                          current_timestring(talloc_tos(), false),
376                          __func__, domain, context_name));
377
378                 status = secrets_finish_password_change(prev->password->change_server,
379                                                         prev->password->change_time,
380                                                         info);
381                 if (!NT_STATUS_IS_OK(status)) {
382                         DEBUG(0, ("secrets_prepare_password_change() failed for domain %s!\n",
383                                   domain));
384                         TALLOC_FREE(frame);
385                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
386                 }
387
388                 DEBUG(0,("%s : %s(%s): Recovered previous password change.\n",
389                          current_timestring(talloc_tos(), false),
390                          __func__, domain));
391                 TALLOC_FREE(frame);
392                 return NT_STATUS_OK;
393         }
394
395         if (idx_nt_hashes != idx_current) {
396                 DEBUG(0,("%s : %s(%s): Verified older password remotely "
397                          "skip changing %s\n",
398                          current_timestring(talloc_tos(), false),
399                          __func__, domain, context_name));
400
401                 if (info == NULL) {
402                         TALLOC_FREE(frame);
403                         return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
404                 }
405
406                 status = secrets_defer_password_change(dcname,
407                                         NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE,
408                                         NT_STATUS_NOT_COMMITTED,
409                                         info);
410                 if (!NT_STATUS_IS_OK(status)) {
411                         DEBUG(0, ("secrets_defer_password_change() failed for domain %s!\n",
412                                   domain));
413                         TALLOC_FREE(frame);
414                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
415                 }
416                 TALLOC_FREE(frame);
417                 return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
418         }
419
420         DEBUG(0,("%s : %s(%s): Verified old password remotely using %s\n",
421                  current_timestring(talloc_tos(), false),
422                  __func__, domain, context_name));
423
424         /*
425          * Return the result of trying to write the new password
426          * back into the trust account file.
427          */
428
429         switch (sec_channel_type) {
430
431         case SEC_CHAN_WKSTA:
432         case SEC_CHAN_BDC:
433                 /*
434                  * we called secrets_prepare_password_change() above.
435                  */
436                 break;
437
438         case SEC_CHAN_DNS_DOMAIN:
439         case SEC_CHAN_DOMAIN:
440                 /*
441                  * we need to get the sid first for the
442                  * pdb_set_trusteddom_pw call
443                  */
444                 ok = pdb_set_trusteddom_pw(domain, new_trust_pw_str,
445                                            &td->security_identifier);
446                 if (!ok) {
447                         DEBUG(0, ("pdb_set_trusteddom_pw() failed for domain %s!\n",
448                                   domain));
449                         TALLOC_FREE(frame);
450                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
451                 }
452                 TALLOC_FREE(new_trust_pw_str);
453                 break;
454
455         default:
456                 smb_panic("Unsupported secure channel type");
457                 break;
458         }
459
460         DEBUG(0,("%s : %s(%s): Changed password locally\n",
461                  current_timestring(talloc_tos(), false), __func__, domain));
462
463         status = netlogon_creds_cli_ServerPasswordSet(context, b,
464                                                       &new_trust_pw_blob,
465                                                       new_trust_version);
466         if (!NT_STATUS_IS_OK(status)) {
467                 NTSTATUS status2;
468                 const char *fn = NULL;
469
470                 ok = dcerpc_binding_handle_is_connected(b);
471
472                 DEBUG(0,("%s : %s(%s) remote password change with %s failed "
473                          "- %s (%s)\n",
474                          current_timestring(talloc_tos(), false),
475                          __func__, domain, context_name,
476                          nt_errstr(status),
477                          ok ? "connected": "disconnected"));
478
479                 if (!ok) {
480                         /*
481                          * The connection is broken, we don't
482                          * know if the password was changed,
483                          * we hope to have more luck next time.
484                          */
485                         status2 = secrets_failed_password_change(dcname,
486                                                         NT_STATUS_NOT_COMMITTED,
487                                                         status,
488                                                         info);
489                         fn = "secrets_failed_password_change";
490                 } else {
491                         /*
492                          * The server rejected the change, we don't
493                          * retry and defer the change to the next
494                          * "machine password timeout" interval.
495                          */
496                         status2 = secrets_defer_password_change(dcname,
497                                                         NT_STATUS_NOT_COMMITTED,
498                                                         status,
499                                                         info);
500                         fn = "secrets_defer_password_change";
501                 }
502                 if (!NT_STATUS_IS_OK(status2)) {
503                         DEBUG(0, ("%s() failed for domain %s!\n",
504                                   fn, domain));
505                         TALLOC_FREE(frame);
506                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
507                 }
508
509                 TALLOC_FREE(frame);
510                 return status;
511         }
512
513         DEBUG(0,("%s : %s(%s): Changed password remotely using %s\n",
514                  current_timestring(talloc_tos(), false),
515                  __func__, domain, context_name));
516
517         switch (sec_channel_type) {
518
519         case SEC_CHAN_WKSTA:
520         case SEC_CHAN_BDC:
521                 status = secrets_finish_password_change(
522                                         info->next_change->change_server,
523                                         info->next_change->change_time,
524                                         info);
525                 if (!NT_STATUS_IS_OK(status)) {
526                         DEBUG(0, ("secrets_finish_password_change() failed for domain %s!\n",
527                                   domain));
528                         TALLOC_FREE(frame);
529                         return NT_STATUS_INTERNAL_DB_CORRUPTION;
530                 }
531
532                 DEBUG(0,("%s : %s(%s): Finished password change.\n",
533                          current_timestring(talloc_tos(), false),
534                          __func__, domain));
535                 break;
536
537         case SEC_CHAN_DNS_DOMAIN:
538         case SEC_CHAN_DOMAIN:
539                 /*
540                  * we used pdb_set_trusteddom_pw().
541                  */
542                 break;
543
544         default:
545                 smb_panic("Unsupported secure channel type");
546                 break;
547         }
548
549         ok = cli_credentials_set_utf16_password(creds,
550                                                 &new_trust_pw_blob,
551                                                 CRED_SPECIFIED);
552         if (!ok) {
553                 DEBUG(0, ("cli_credentials_set_password failed for domain %s!\n",
554                           domain));
555                 TALLOC_FREE(frame);
556                 return NT_STATUS_NO_MEMORY;
557         }
558
559         current_nt_hash = cli_credentials_get_nt_hash(creds, frame);
560         if (current_nt_hash == NULL) {
561                 DEBUG(0, ("cli_credentials_get_nt_hash failed for domain %s!\n",
562                           domain));
563                 TALLOC_FREE(frame);
564                 return NT_STATUS_TRUSTED_RELATIONSHIP_FAILURE;
565         }
566
567         /*
568          * Now we verify the new password.
569          */
570         idx = 0;
571         idx_current = idx;
572         nt_hashes[idx++] = current_nt_hash;
573         num_nt_hashes = idx;
574         status = netlogon_creds_cli_auth(context, b,
575                                          num_nt_hashes,
576                                          nt_hashes,
577                                          &idx_nt_hashes);
578         if (!NT_STATUS_IS_OK(status)) {
579                 DEBUG(0, ("netlogon_creds_cli_auth(%s) failed for new password - %s!\n",
580                           context_name, nt_errstr(status)));
581                 TALLOC_FREE(frame);
582                 return status;
583         }
584
585         DEBUG(0,("%s : %s(%s): Verified new password remotely using %s\n",
586                  current_timestring(talloc_tos(), false),
587                  __func__, domain, context_name));
588
589         TALLOC_FREE(frame);
590         return NT_STATUS_OK;
591 }