Coverage Report

Created: 2025-07-23 07:04

/src/samba/auth/credentials/credentials_secrets.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
   Unix SMB/CIFS implementation.
3
4
   User credentials handling (as regards on-disk files)
5
6
   Copyright (C) Jelmer Vernooij 2005
7
   Copyright (C) Tim Potter 2001
8
   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
9
10
   This program is free software; you can redistribute it and/or modify
11
   it under the terms of the GNU General Public License as published by
12
   the Free Software Foundation; either version 3 of the License, or
13
   (at your option) any later version.
14
15
   This program is distributed in the hope that it will be useful,
16
   but WITHOUT ANY WARRANTY; without even the implied warranty of
17
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
   GNU General Public License for more details.
19
20
   You should have received a copy of the GNU General Public License
21
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
*/
23
24
#include "includes.h"
25
#include "lib/events/events.h"
26
#include <ldb.h>
27
#include "librpc/gen_ndr/samr.h" /* for struct samrPassword */
28
#include "param/secrets.h"
29
#include "system/filesys.h"
30
#include "auth/credentials/credentials.h"
31
#include "auth/credentials/credentials_internal.h"
32
#include "auth/credentials/credentials_krb5.h"
33
#include "auth/kerberos/kerberos_util.h"
34
#include "param/param.h"
35
#include "lib/events/events.h"
36
#include "dsdb/samdb/samdb.h"
37
#include "source3/include/secrets.h"
38
#include "dbwrap/dbwrap.h"
39
#include "dbwrap/dbwrap_open.h"
40
#include "lib/util/util_tdb.h"
41
#include "libds/common/roles.h"
42
43
#undef DBGC_CLASS
44
0
#define DBGC_CLASS DBGC_AUTH
45
46
/**
47
 * Fill in credentials for the machine trust account, from the secrets database.
48
 *
49
 * @param cred Credentials structure to fill in
50
 * @retval NTSTATUS error detailing any failure
51
 */
52
static NTSTATUS cli_credentials_set_secrets_lct(struct cli_credentials *cred,
53
            struct loadparm_context *lp_ctx,
54
            struct ldb_context *ldb,
55
            const char *base,
56
            const char *filter,
57
            time_t secrets_tdb_last_change_time,
58
            const char *secrets_tdb_password,
59
            char **error_string)
60
0
{
61
0
  TALLOC_CTX *mem_ctx;
62
63
0
  int ldb_ret;
64
0
  struct ldb_message *msg;
65
66
0
  const char *machine_account;
67
0
  const char *password;
68
0
  const char *domain;
69
0
  const char *realm;
70
0
  enum netr_SchannelType sct;
71
0
  const char *salt_principal;
72
0
  char *keytab;
73
0
  const struct ldb_val *whenChanged;
74
0
  time_t lct;
75
76
  /* ok, we are going to get it now, don't recurse back here */
77
0
  cred->machine_account_pending = false;
78
79
  /* some other parts of the system will key off this */
80
0
  cred->machine_account = true;
81
82
0
  mem_ctx = talloc_named(cred, 0, "cli_credentials_set_secrets from ldb");
83
84
0
  if (!ldb) {
85
    /* Local secrets are stored in secrets.ldb */
86
0
    ldb = secrets_db_connect(mem_ctx, lp_ctx);
87
0
    if (!ldb) {
88
0
      *error_string = talloc_strdup(cred, "Could not open secrets.ldb");
89
0
      talloc_free(mem_ctx);
90
0
      return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
91
0
    }
92
0
  }
93
94
0
  ldb_ret = dsdb_search_one(ldb, mem_ctx, &msg,
95
0
          ldb_dn_new(mem_ctx, ldb, base),
96
0
          LDB_SCOPE_SUBTREE,
97
0
          NULL, 0, "%s", filter);
98
99
0
  if (ldb_ret != LDB_SUCCESS) {
100
0
    *error_string = talloc_asprintf(cred, "Could not find entry to match filter: '%s' base: '%s': %s: %s",
101
0
            filter, base ? base : "",
102
0
            ldb_strerror(ldb_ret), ldb_errstring(ldb));
103
0
    talloc_free(mem_ctx);
104
0
    return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
105
0
  }
106
107
0
  password = ldb_msg_find_attr_as_string(msg, "secret", NULL);
108
109
0
  whenChanged = ldb_msg_find_ldb_val(msg, "whenChanged");
110
0
  if (!whenChanged || ldb_val_to_time(whenChanged, &lct) != LDB_SUCCESS) {
111
    /* This attribute is mandatory */
112
0
    talloc_free(mem_ctx);
113
0
    return NT_STATUS_NOT_FOUND;
114
0
  }
115
116
  /* Don't set secrets.ldb info if the secrets.tdb entry was more recent */
117
0
  if (lct < secrets_tdb_last_change_time) {
118
0
    talloc_free(mem_ctx);
119
0
    return NT_STATUS_NOT_FOUND;
120
0
  }
121
122
0
  if ((lct == secrets_tdb_last_change_time) &&
123
0
      (secrets_tdb_password != NULL) &&
124
0
      (password != NULL) &&
125
0
      (strcmp(password, secrets_tdb_password) != 0)) {
126
0
    talloc_free(mem_ctx);
127
0
    return NT_STATUS_NOT_FOUND;
128
0
  }
129
130
0
  cli_credentials_set_password_last_changed_time(cred, lct);
131
132
0
  machine_account = ldb_msg_find_attr_as_string(msg, "sAMAccountName", NULL);
133
134
0
  if (!machine_account) {
135
0
    machine_account = ldb_msg_find_attr_as_string(msg, "servicePrincipalName", NULL);
136
137
0
    if (!machine_account) {
138
0
      const char *ldap_bind_dn = ldb_msg_find_attr_as_string(msg, "ldapBindDn", NULL);
139
0
      if (!ldap_bind_dn) {
140
0
        *error_string = talloc_asprintf(cred,
141
0
                "Could not find 'samAccountName', "
142
0
                "'servicePrincipalName' or "
143
0
                "'ldapBindDn' in secrets record: %s",
144
0
                ldb_dn_get_linearized(msg->dn));
145
0
        talloc_free(mem_ctx);
146
0
        return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
147
0
      } else {
148
        /* store bind dn in credentials */
149
0
        cli_credentials_set_bind_dn(cred, ldap_bind_dn);
150
0
      }
151
0
    }
152
0
  }
153
154
0
  salt_principal = ldb_msg_find_attr_as_string(msg, "saltPrincipal", NULL);
155
0
  cli_credentials_set_salt_principal(cred, salt_principal);
156
157
0
  sct = ldb_msg_find_attr_as_int(msg, "secureChannelType", 0);
158
0
  if (sct) {
159
0
    cli_credentials_set_secure_channel_type(cred, sct);
160
0
  }
161
162
0
  if (!password) {
163
0
    const struct ldb_val *nt_password_hash = ldb_msg_find_ldb_val(msg, "unicodePwd");
164
0
    struct samr_Password hash;
165
0
    ZERO_STRUCT(hash);
166
0
    if (nt_password_hash) {
167
0
      memcpy(hash.hash, nt_password_hash->data,
168
0
             MIN(nt_password_hash->length, sizeof(hash.hash)));
169
170
0
      cli_credentials_set_nt_hash(cred, &hash, CRED_SPECIFIED);
171
0
    } else {
172
0
      cli_credentials_set_password(cred, NULL, CRED_SPECIFIED);
173
0
    }
174
0
  } else {
175
0
    cli_credentials_set_password(cred, password, CRED_SPECIFIED);
176
0
  }
177
178
0
  domain = ldb_msg_find_attr_as_string(msg, "flatname", NULL);
179
0
  if (domain) {
180
0
    cli_credentials_set_domain(cred, domain, CRED_SPECIFIED);
181
0
  }
182
183
0
  realm = ldb_msg_find_attr_as_string(msg, "realm", NULL);
184
0
  if (realm) {
185
0
    cli_credentials_set_realm(cred, realm, CRED_SPECIFIED);
186
0
  }
187
188
0
  if (machine_account) {
189
0
    cli_credentials_set_username(cred, machine_account, CRED_SPECIFIED);
190
0
  }
191
192
0
  cli_credentials_set_kvno(cred, ldb_msg_find_attr_as_int(msg, "msDS-KeyVersionNumber", 0));
193
194
  /* If there was an external keytab specified by reference in
195
   * the LDB, then use this.  Otherwise we will make one up
196
   * (chewing CPU time) from the password */
197
0
  keytab = keytab_name_from_msg(cred, ldb, msg);
198
0
  if (keytab) {
199
0
    cli_credentials_set_keytab_name(cred, lp_ctx, keytab, CRED_SPECIFIED);
200
0
    talloc_free(keytab);
201
0
  }
202
0
  talloc_free(mem_ctx);
203
204
0
  return NT_STATUS_OK;
205
0
}
206
207
208
/**
209
 * Fill in credentials for the machine trust account, from the secrets database.
210
 *
211
 * @param cred Credentials structure to fill in
212
 * @retval NTSTATUS error detailing any failure
213
 */
214
_PUBLIC_ NTSTATUS cli_credentials_set_secrets(struct cli_credentials *cred,
215
                struct loadparm_context *lp_ctx,
216
                struct ldb_context *ldb,
217
                const char *base,
218
                const char *filter,
219
                char **error_string)
220
0
{
221
0
  NTSTATUS status = cli_credentials_set_secrets_lct(cred, lp_ctx, ldb, base, filter, 0, NULL, error_string);
222
0
  if (!NT_STATUS_IS_OK(status)) {
223
    /* set anonymous as the fallback, if the machine account won't work */
224
0
    cli_credentials_set_anonymous(cred);
225
0
  }
226
0
  return status;
227
0
}
228
229
/**
230
 * Fill in credentials for the machine trust account, from the secrets database.
231
 *
232
 * @param cred Credentials structure to fill in
233
 * @retval NTSTATUS error detailing any failure
234
 */
235
_PUBLIC_ NTSTATUS cli_credentials_set_machine_account(struct cli_credentials *cred,
236
                  struct loadparm_context *lp_ctx)
237
0
{
238
0
  struct db_context *db_ctx;
239
0
  char *secrets_tdb_path;
240
0
  int hash_size, tdb_flags;
241
242
0
  secrets_tdb_path = lpcfg_private_db_path(cred, lp_ctx, "secrets");
243
0
  if (secrets_tdb_path == NULL) {
244
0
    return NT_STATUS_NO_MEMORY;
245
0
  }
246
247
0
  hash_size = lpcfg_tdb_hash_size(lp_ctx, secrets_tdb_path);
248
0
  tdb_flags = lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT);
249
250
0
  db_ctx = dbwrap_local_open(
251
0
    cred,
252
0
    secrets_tdb_path,
253
0
    hash_size,
254
0
    tdb_flags,
255
0
    O_RDWR,
256
0
    0600,
257
0
    DBWRAP_LOCK_ORDER_1,
258
0
    DBWRAP_FLAG_NONE);
259
0
  TALLOC_FREE(secrets_tdb_path);
260
261
  /*
262
   * We do not check for errors here, we might not have a
263
   * secrets.tdb at all, and so we just need to check the
264
   * secrets.ldb
265
   */
266
0
  return cli_credentials_set_machine_account_db_ctx(cred, lp_ctx, db_ctx);
267
0
}
268
269
/**
270
 * Fill in credentials for the machine trust account, from the
271
 * secrets.ldb or passed in handle to secrets.tdb (perhaps in CTDB).
272
 *
273
 * This version is used in parts of the code that can link in the
274
 * CTDB dbwrap backend, by passing down the already open handle.
275
 *
276
 * @param cred Credentials structure to fill in
277
 * @param db_ctx dbwrap context for secrets.tdb
278
 * @retval NTSTATUS error detailing any failure
279
 */
280
_PUBLIC_ NTSTATUS cli_credentials_set_machine_account_db_ctx(struct cli_credentials *cred,
281
                   struct loadparm_context *lp_ctx,
282
                   struct db_context *db_ctx)
283
0
{
284
0
  NTSTATUS status;
285
0
  char *filter;
286
0
  char *error_string = NULL;
287
0
  const char *domain;
288
0
  bool secrets_tdb_password_more_recent;
289
0
  time_t secrets_tdb_lct = 0;
290
0
  char *secrets_tdb_password = NULL;
291
0
  char *secrets_tdb_old_password = NULL;
292
0
  uint32_t secrets_tdb_secure_channel_type = SEC_CHAN_NULL;
293
0
  int server_role = lpcfg_server_role(lp_ctx);
294
0
  int security = lpcfg_security(lp_ctx);
295
0
  char *keystr;
296
0
  char *keystr_upper = NULL;
297
0
  TALLOC_CTX *tmp_ctx = talloc_named(cred, 0, "cli_credentials_set_secrets from ldb");
298
0
  if (!tmp_ctx) {
299
0
    return NT_STATUS_NO_MEMORY;
300
0
  }
301
302
  /* Bleh, nasty recursion issues: We are setting a machine
303
   * account here, so we don't want the 'pending' flag around
304
   * any more */
305
0
  cred->machine_account_pending = false;
306
307
  /* We have to do this, as the fallback in
308
   * cli_credentials_set_secrets is to run as anonymous, so the domain is wiped */
309
0
  domain = cli_credentials_get_domain(cred);
310
311
0
  if (db_ctx) {
312
0
    TDB_DATA dbuf;
313
0
    keystr = talloc_asprintf(tmp_ctx, "%s/%s",
314
0
           SECRETS_MACHINE_LAST_CHANGE_TIME,
315
0
           domain);
316
0
    keystr_upper = strupper_talloc(tmp_ctx, keystr);
317
0
    status = dbwrap_fetch(db_ctx, tmp_ctx, string_tdb_data(keystr_upper),
318
0
              &dbuf);
319
0
    if (NT_STATUS_IS_OK(status) && dbuf.dsize == 4) {
320
0
      secrets_tdb_lct = IVAL(dbuf.dptr,0);
321
0
    }
322
323
0
    keystr = talloc_asprintf(tmp_ctx, "%s/%s",
324
0
           SECRETS_MACHINE_PASSWORD,
325
0
           domain);
326
0
    keystr_upper = strupper_talloc(tmp_ctx, keystr);
327
0
    status = dbwrap_fetch(db_ctx, tmp_ctx, string_tdb_data(keystr_upper),
328
0
              &dbuf);
329
0
    if (NT_STATUS_IS_OK(status)) {
330
0
      secrets_tdb_password = (char *)dbuf.dptr;
331
0
    }
332
333
0
    keystr = talloc_asprintf(tmp_ctx, "%s/%s",
334
0
           SECRETS_MACHINE_PASSWORD_PREV,
335
0
           domain);
336
0
    keystr_upper = strupper_talloc(tmp_ctx, keystr);
337
0
    status = dbwrap_fetch(db_ctx, tmp_ctx, string_tdb_data(keystr_upper),
338
0
              &dbuf);
339
0
    if (NT_STATUS_IS_OK(status)) {
340
0
      secrets_tdb_old_password = (char *)dbuf.dptr;
341
0
    }
342
343
0
    keystr = talloc_asprintf(tmp_ctx, "%s/%s",
344
0
           SECRETS_MACHINE_SEC_CHANNEL_TYPE,
345
0
           domain);
346
0
    keystr_upper = strupper_talloc(tmp_ctx, keystr);
347
0
    status = dbwrap_fetch(db_ctx, tmp_ctx, string_tdb_data(keystr_upper),
348
0
              &dbuf);
349
0
    if (NT_STATUS_IS_OK(status) && dbuf.dsize == 4) {
350
0
      secrets_tdb_secure_channel_type = IVAL(dbuf.dptr,0);
351
0
    }
352
0
  }
353
354
0
  filter = talloc_asprintf(cred, SECRETS_PRIMARY_DOMAIN_FILTER, 
355
0
         domain);
356
0
  status = cli_credentials_set_secrets_lct(cred, lp_ctx, NULL,
357
0
             SECRETS_PRIMARY_DOMAIN_DN,
358
0
             filter, secrets_tdb_lct, secrets_tdb_password, &error_string);
359
0
  if (secrets_tdb_password == NULL) {
360
0
    secrets_tdb_password_more_recent = false;
361
0
  } else if (NT_STATUS_EQUAL(NT_STATUS_CANT_ACCESS_DOMAIN_INFO, status)
362
0
      || NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, status)) {
363
0
    secrets_tdb_password_more_recent = true;
364
0
  } else if (secrets_tdb_lct > cli_credentials_get_password_last_changed_time(cred)) {
365
0
    secrets_tdb_password_more_recent = true;
366
0
  } else if (secrets_tdb_lct == cli_credentials_get_password_last_changed_time(cred)) {
367
0
    secrets_tdb_password_more_recent = strcmp(secrets_tdb_password, cli_credentials_get_password(cred)) != 0;
368
0
  } else {
369
0
    secrets_tdb_password_more_recent = false;
370
0
  }
371
372
0
  if (secrets_tdb_password_more_recent) {
373
0
    char *machine_account = talloc_asprintf(tmp_ctx, "%s$", lpcfg_netbios_name(lp_ctx));
374
0
    cli_credentials_set_password(cred, secrets_tdb_password, CRED_SPECIFIED);
375
0
    cli_credentials_set_old_password(cred, secrets_tdb_old_password, CRED_SPECIFIED);
376
0
    cli_credentials_set_domain(cred, domain, CRED_SPECIFIED);
377
0
    if (strequal(domain, lpcfg_workgroup(lp_ctx))) {
378
0
      enum credentials_use_kerberos use_kerberos =
379
0
        cli_credentials_get_kerberos_state(cred);
380
0
      enum credentials_obtained use_kerberos_obtained =
381
0
        cli_credentials_get_kerberos_state_obtained(cred);
382
0
      bool is_ad = false;
383
384
0
      cli_credentials_set_realm(cred, lpcfg_realm(lp_ctx), CRED_SPECIFIED);
385
386
0
      switch (server_role) {
387
0
      case ROLE_DOMAIN_MEMBER:
388
0
        if (security != SEC_ADS) {
389
0
          break;
390
0
        }
391
392
0
        FALL_THROUGH;
393
0
      case ROLE_ACTIVE_DIRECTORY_DC:
394
0
      case ROLE_IPA_DC:
395
0
        is_ad = true;
396
0
        break;
397
0
      }
398
399
0
      if (use_kerberos != CRED_USE_KERBEROS_DESIRED || is_ad) {
400
        /*
401
         * Keep an explicit selection
402
         *
403
         * For AD domains we also keep
404
         * CRED_USE_KERBEROS_DESIRED
405
         */
406
0
      } else if (use_kerberos_obtained <= CRED_SMB_CONF) {
407
        /*
408
         * Disable kerberos by default within
409
         * an NT4 domain.
410
         */
411
0
        cli_credentials_set_kerberos_state(cred,
412
0
            CRED_USE_KERBEROS_DISABLED,
413
0
            CRED_SMB_CONF);
414
0
      }
415
0
    }
416
417
0
    cli_credentials_set_username(cred, machine_account, CRED_SPECIFIED);
418
0
    cli_credentials_set_password_last_changed_time(cred, secrets_tdb_lct);
419
0
    cli_credentials_set_secure_channel_type(cred, secrets_tdb_secure_channel_type);
420
0
    status = NT_STATUS_OK;
421
0
  } else if (!NT_STATUS_IS_OK(status)) {
422
0
    if (db_ctx) {
423
0
      error_string
424
0
        = talloc_asprintf(cred,
425
0
              "Failed to fetch machine account password for %s from both "
426
0
              "secrets.ldb (%s) and from %s",
427
0
              domain,
428
0
              error_string == NULL ? "error" : error_string,
429
0
              dbwrap_name(db_ctx));
430
0
    } else {
431
0
      char *secrets_tdb_path;
432
433
0
      secrets_tdb_path = lpcfg_private_db_path(tmp_ctx,
434
0
                 lp_ctx,
435
0
                 "secrets");
436
0
      if (secrets_tdb_path == NULL) {
437
0
        return NT_STATUS_NO_MEMORY;
438
0
      }
439
440
0
      error_string = talloc_asprintf(cred,
441
0
                   "Failed to fetch machine account password from "
442
0
                   "secrets.ldb: %s and failed to open %s",
443
0
                   error_string == NULL ? "error" : error_string,
444
0
                   secrets_tdb_path);
445
0
    }
446
0
    DEBUG(1, ("Could not find machine account in secrets database: %s: %s\n",
447
0
        error_string == NULL ? "error" : error_string,
448
0
        nt_errstr(status)));
449
    /* set anonymous as the fallback, if the machine account won't work */
450
0
    cli_credentials_set_anonymous(cred);
451
0
  }
452
453
0
  TALLOC_FREE(tmp_ctx);
454
0
  return status;
455
0
}
456
457
/**
458
 * Fill in credentials for a particular principal, from the secrets database.
459
 *
460
 * @param cred Credentials structure to fill in
461
 * @retval NTSTATUS error detailing any failure
462
 */
463
_PUBLIC_ NTSTATUS cli_credentials_set_stored_principal(struct cli_credentials *cred,
464
                struct loadparm_context *lp_ctx,
465
                const char *serviceprincipal)
466
0
{
467
0
  NTSTATUS status;
468
0
  char *filter;
469
0
  char *error_string = NULL;
470
  /* Bleh, nasty recursion issues: We are setting a machine
471
   * account here, so we don't want the 'pending' flag around
472
   * any more */
473
0
  cred->machine_account_pending = false;
474
0
  filter = talloc_asprintf(cred, SECRETS_PRINCIPAL_SEARCH,
475
0
         cli_credentials_get_realm(cred),
476
0
         cli_credentials_get_domain(cred),
477
0
         serviceprincipal);
478
0
  status = cli_credentials_set_secrets_lct(cred, lp_ctx, NULL,
479
0
               SECRETS_PRINCIPALS_DN, filter,
480
0
               0, NULL, &error_string);
481
0
  if (!NT_STATUS_IS_OK(status)) {
482
0
    DEBUG(1, ("Could not find %s principal in secrets database: %s: %s\n",
483
0
        serviceprincipal, nt_errstr(status),
484
0
        error_string ? error_string : "<no error>"));
485
0
  }
486
0
  return status;
487
0
}
488
489
/**
490
 * Ask that when required, the credentials system will be filled with
491
 * machine trust account, from the secrets database.
492
 *
493
 * @param cred Credentials structure to fill in
494
 * @note This function is used to call the above function after, rather
495
 *       than during, popt processing.
496
 *
497
 */
498
_PUBLIC_ void cli_credentials_set_machine_account_pending(struct cli_credentials *cred,
499
             struct loadparm_context *lp_ctx)
500
0
{
501
0
  cred->machine_account_pending = true;
502
0
  cred->machine_account_pending_lp_ctx = lp_ctx;
503
0
}
504
505