Coverage Report

Created: 2026-02-14 07:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/samba/source3/libads/krb5_setpw.c
Line
Count
Source
1
/* 
2
   Unix SMB/CIFS implementation.
3
   krb5 set password implementation
4
   Copyright (C) Andrew Tridgell 2001
5
   Copyright (C) Remus Koos 2001 (remuskoos@yahoo.com)
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 "smb_krb5.h"
23
#include "libads/kerberos_proto.h"
24
#include "../lib/util/asn1.h"
25
26
#ifdef HAVE_KRB5
27
28
/* Those are defined by kerberos-set-passwd-02.txt and are probably
29
 * not supported by M$ implementation */
30
0
#define KRB5_KPASSWD_POLICY_REJECT    8
31
0
#define KRB5_KPASSWD_BAD_PRINCIPAL    9
32
0
#define KRB5_KPASSWD_ETYPE_NOSUPP   10
33
34
/*
35
 * we've got to be able to distinguish KRB_ERRORs from other
36
 * requests - valid response for CHPW v2 replies.
37
 */
38
39
static krb5_error_code kpasswd_err_to_krb5_err(krb5_error_code res_code)
40
0
{
41
0
  switch (res_code) {
42
0
  case KRB5_KPASSWD_ACCESSDENIED:
43
0
    return KRB5KDC_ERR_BADOPTION;
44
0
  case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
45
0
    return KRB5KDC_ERR_BADOPTION;
46
    /* return KV5M_ALT_METHOD; MIT-only define */
47
0
  case KRB5_KPASSWD_ETYPE_NOSUPP:
48
0
    return KRB5KDC_ERR_ETYPE_NOSUPP;
49
0
  case KRB5_KPASSWD_BAD_PRINCIPAL:
50
0
    return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
51
0
  case KRB5_KPASSWD_POLICY_REJECT:
52
0
  case KRB5_KPASSWD_SOFTERROR:
53
0
    return KRB5KDC_ERR_POLICY;
54
0
  default:
55
0
    return KRB5KRB_ERR_GENERIC;
56
0
  }
57
0
}
58
59
ADS_STATUS ads_krb5_set_password(const char *principal,
60
         const char *newpw,
61
         const char *ccname)
62
0
{
63
64
0
  ADS_STATUS aret;
65
0
  krb5_error_code ret = 0;
66
0
  krb5_context context = NULL;
67
0
  krb5_principal princ = NULL;
68
0
  krb5_ccache ccache = NULL;
69
0
  int result_code;
70
0
  krb5_data result_code_string = { 0 };
71
0
  krb5_data result_string = { 0 };
72
73
0
  if (ccname == NULL) {
74
0
    DBG_ERR("Missing ccache for [%s] and config [%s]\n",
75
0
      principal, getenv("KRB5_CONFIG"));
76
0
    return ADS_ERROR_NT(NT_STATUS_WRONG_CREDENTIAL_HANDLE);
77
0
  }
78
79
0
  ret = smb_krb5_init_context_common(&context);
80
0
  if (ret) {
81
0
    DBG_ERR("kerberos init context failed (%s)\n",
82
0
      error_message(ret));
83
0
    return ADS_ERROR_KRB5(ret);
84
0
  }
85
86
0
  if (principal) {
87
0
    ret = smb_krb5_parse_name(context, principal, &princ);
88
0
    if (ret) {
89
0
      krb5_free_context(context);
90
0
      DEBUG(1, ("Failed to parse %s (%s)\n", principal,
91
0
          error_message(ret)));
92
0
      return ADS_ERROR_KRB5(ret);
93
0
    }
94
0
  }
95
96
0
  ret = krb5_cc_resolve(context, ccname, &ccache);
97
0
  if (ret) {
98
0
    krb5_free_principal(context, princ);
99
0
    krb5_free_context(context);
100
0
    DBG_WARNING("Failed to get creds from [%s] (%s)\n",
101
0
          ccname, error_message(ret));
102
0
    return ADS_ERROR_KRB5(ret);
103
0
  }
104
105
0
  ret = krb5_set_password_using_ccache(context,
106
0
               ccache,
107
0
               discard_const_p(char, newpw),
108
0
               princ,
109
0
               &result_code,
110
0
               &result_code_string,
111
0
               &result_string);
112
0
  if (ret) {
113
0
    DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
114
0
    aret = ADS_ERROR_KRB5(ret);
115
0
    goto done;
116
0
  }
117
118
0
  if (result_code != KRB5_KPASSWD_SUCCESS) {
119
0
    ret = kpasswd_err_to_krb5_err(result_code);
120
0
    DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
121
0
    aret = ADS_ERROR_KRB5(ret);
122
0
    goto done;
123
0
  }
124
125
0
  aret = ADS_SUCCESS;
126
127
0
 done:
128
0
  smb_krb5_free_data_contents(context, &result_code_string);
129
0
  smb_krb5_free_data_contents(context, &result_string);
130
0
  krb5_free_principal(context, princ);
131
0
  krb5_cc_close(context, ccache);
132
0
  krb5_free_context(context);
133
134
0
  return aret;
135
0
}
136
137
/*
138
  we use a prompter to avoid a crash bug in the kerberos libs when 
139
  dealing with empty passwords
140
  this prompter is just a string copy ...
141
*/
142
static krb5_error_code 
143
kerb_prompter(krb5_context ctx, void *data,
144
         const char *name,
145
         const char *banner,
146
         int num_prompts,
147
         krb5_prompt prompts[])
148
0
{
149
0
  if (num_prompts == 0) return 0;
150
151
0
  memset(prompts[0].reply->data, 0, prompts[0].reply->length);
152
0
  if (prompts[0].reply->length > 0) {
153
0
    if (data) {
154
0
      strncpy((char *)prompts[0].reply->data,
155
0
        (const char *)data,
156
0
        prompts[0].reply->length-1);
157
0
      prompts[0].reply->length = strlen((const char *)prompts[0].reply->data);
158
0
    } else {
159
0
      prompts[0].reply->length = 0;
160
0
    }
161
0
  }
162
0
  return 0;
163
0
}
164
165
static ADS_STATUS ads_krb5_chg_password(const char *principal,
166
          const char *oldpw,
167
          const char *newpw)
168
0
{
169
0
  ADS_STATUS aret;
170
0
  krb5_error_code ret;
171
0
  krb5_context context = NULL;
172
0
  krb5_principal princ;
173
0
  krb5_get_init_creds_opt *opts = NULL;
174
0
  krb5_creds creds;
175
0
  char *chpw_princ = NULL, *password;
176
0
  char *realm = NULL;
177
0
  int result_code;
178
0
  krb5_data result_code_string = { 0 };
179
0
  krb5_data result_string = { 0 };
180
0
  smb_krb5_addresses *addr = NULL;
181
182
0
  ret = smb_krb5_init_context_common(&context);
183
0
  if (ret) {
184
0
    DBG_ERR("kerberos init context failed (%s)\n",
185
0
      error_message(ret));
186
0
    return ADS_ERROR_KRB5(ret);
187
0
  }
188
189
0
  if ((ret = smb_krb5_parse_name(context, principal, &princ))) {
190
0
    krb5_free_context(context);
191
0
    DEBUG(1,("Failed to parse %s (%s)\n", principal, error_message(ret)));
192
0
    return ADS_ERROR_KRB5(ret);
193
0
  }
194
195
0
  ret = krb5_get_init_creds_opt_alloc(context, &opts);
196
0
  if (ret != 0) {
197
0
    krb5_free_context(context);
198
0
    DBG_WARNING("krb5_get_init_creds_opt_alloc failed: %s\n",
199
0
          error_message(ret));
200
0
    return ADS_ERROR_KRB5(ret);
201
0
  }
202
203
0
  krb5_get_init_creds_opt_set_tkt_life(opts, 5 * 60);
204
0
  krb5_get_init_creds_opt_set_renew_life(opts, 0);
205
0
  krb5_get_init_creds_opt_set_forwardable(opts, 0);
206
0
  krb5_get_init_creds_opt_set_proxiable(opts, 0);
207
0
#ifdef SAMBA4_USES_HEIMDAL
208
0
  krb5_get_init_creds_opt_set_win2k(context, opts, true);
209
0
  krb5_get_init_creds_opt_set_canonicalize(context, opts, true);
210
#else /* MIT */
211
#if 0
212
  /*
213
   * FIXME
214
   *
215
   * Due to an upstream MIT Kerberos bug, this feature is not
216
   * not working. Affection versions (2019-10-09): <= 1.17
217
   *
218
   * Reproducer:
219
   * kinit -C aDmInIsTrAtOr@ACME.COM -S kadmin/changepw@ACME.COM
220
   *
221
   * This is NOT a problem if the service is a krbtgt.
222
   *
223
   * https://bugzilla.samba.org/show_bug.cgi?id=14155
224
   */
225
  krb5_get_init_creds_opt_set_canonicalize(opts, true);
226
#endif
227
#endif /* MIT */
228
229
  /* note that heimdal will fill in the local addresses if the addresses
230
   * in the creds_init_opt are all empty and then later fail with invalid
231
   * address, sending our local netbios krb5 address - just like windows
232
   * - avoids this - gd */
233
0
  ret = smb_krb5_gen_netbios_krb5_address(&addr, lp_netbios_name());
234
0
  if (ret) {
235
0
    krb5_free_principal(context, princ);
236
0
    krb5_get_init_creds_opt_free(context, opts);
237
0
    krb5_free_context(context);
238
0
    return ADS_ERROR_KRB5(ret);
239
0
  }
240
0
  krb5_get_init_creds_opt_set_address_list(opts, addr->addrs);
241
242
0
  realm = smb_krb5_principal_get_realm(NULL, context, princ);
243
244
  /* We have to obtain an INITIAL changepw ticket for changing password */
245
0
  if (asprintf(&chpw_princ, "kadmin/changepw@%s", realm) == -1) {
246
0
    krb5_free_principal(context, princ);
247
0
    krb5_get_init_creds_opt_free(context, opts);
248
0
    smb_krb5_free_addresses(context, addr);
249
0
    krb5_free_context(context);
250
0
    TALLOC_FREE(realm);
251
0
    DEBUG(1, ("ads_krb5_chg_password: asprintf fail\n"));
252
0
    return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
253
0
  }
254
255
0
  TALLOC_FREE(realm);
256
0
  password = SMB_STRDUP(oldpw);
257
0
  ret = krb5_get_init_creds_password(context, &creds, princ, password,
258
0
             kerb_prompter, NULL,
259
0
             0, chpw_princ, opts);
260
0
  krb5_get_init_creds_opt_free(context, opts);
261
0
  smb_krb5_free_addresses(context, addr);
262
0
  SAFE_FREE(chpw_princ);
263
0
  SAFE_FREE(password);
264
265
0
  if (ret) {
266
0
    if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
267
0
      DEBUG(1,("Password incorrect while getting initial ticket\n"));
268
0
    } else {
269
0
      DEBUG(1,("krb5_get_init_creds_password failed (%s)\n", error_message(ret)));
270
0
    }
271
0
    krb5_free_principal(context, princ);
272
0
    krb5_free_context(context);
273
0
    return ADS_ERROR_KRB5(ret);
274
0
  }
275
276
0
  ret = krb5_set_password(context,
277
0
        &creds,
278
0
        discard_const_p(char, newpw),
279
0
        NULL,
280
0
        &result_code,
281
0
        &result_code_string,
282
0
        &result_string);
283
284
0
  if (ret) {
285
0
    DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
286
0
    aret = ADS_ERROR_KRB5(ret);
287
0
    goto done;
288
0
  }
289
290
0
  if (result_code != KRB5_KPASSWD_SUCCESS) {
291
0
    ret = kpasswd_err_to_krb5_err(result_code);
292
0
    DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
293
0
    aret = ADS_ERROR_KRB5(ret);
294
0
    goto done;
295
0
  }
296
297
0
  aret = ADS_SUCCESS;
298
299
0
 done:
300
0
  smb_krb5_free_data_contents(context, &result_code_string);
301
0
  smb_krb5_free_data_contents(context, &result_string);
302
0
  krb5_free_principal(context, princ);
303
0
  krb5_free_context(context);
304
305
0
  return aret;
306
0
}
307
308
ADS_STATUS kerberos_set_password(const char *auth_principal,
309
         const char *auth_password,
310
         const char *target_principal,
311
         const char *new_password)
312
0
{
313
0
  TALLOC_CTX *frame = NULL;
314
0
  krb5_context ctx = NULL;
315
0
  krb5_ccache ccid = NULL;
316
0
  char *ccname = NULL;
317
0
  ADS_STATUS status;
318
0
  int ret;
319
320
0
  if (strcmp(auth_principal, target_principal) == 0) {
321
    /*
322
     * kinit is done inside of ads_krb5_chg_password()
323
     * without any ccache, just with raw krb5_creds.
324
     */
325
0
    return ads_krb5_chg_password(target_principal,
326
0
               auth_password,
327
0
               new_password);
328
0
  }
329
330
0
  frame = talloc_stackframe();
331
332
0
  ret = smb_krb5_init_context_common(&ctx);
333
0
  if (ret != 0) {
334
0
    status = ADS_ERROR_KRB5(ret);
335
0
    goto done;
336
0
  }
337
338
0
  ret = smb_krb5_cc_new_unique_memory(ctx,
339
0
              frame,
340
0
              &ccname,
341
0
              &ccid);
342
0
  if (ret != 0) {
343
0
    status = ADS_ERROR_KRB5(ret);
344
0
    goto done;
345
0
  }
346
347
0
  ret = kerberos_kinit_password(auth_principal,
348
0
              auth_password,
349
0
              ccname);
350
0
  if (ret != 0) {
351
0
    DBG_ERR("Failed kinit for principal %s (%s)\n",
352
0
      auth_principal, error_message(ret));
353
0
    status = ADS_ERROR_KRB5(ret);
354
0
    goto done;
355
0
  }
356
357
0
  status = ads_krb5_set_password(target_principal,
358
0
               new_password,
359
0
               ccname);
360
0
  if (!ADS_ERR_OK(status)) {
361
0
    DBG_ERR("Failed to set password for %s as %s: %s\n",
362
0
      target_principal,
363
0
      auth_principal,
364
0
      ads_errstr(status));
365
0
    goto done;
366
0
  }
367
368
0
done:
369
0
  if (ccid != NULL) {
370
0
    krb5_cc_destroy(ctx, ccid);
371
0
    ccid = NULL;
372
0
  }
373
0
  if (ctx != NULL) {
374
0
    krb5_free_context(ctx);
375
0
    ctx = NULL;
376
0
  }
377
  TALLOC_FREE(frame);
378
0
  return status;
379
0
}
380
381
#endif