krb5_wrap: Rename smb_krb5_open_keytab()
[Samba.git] / source3 / libads / kerberos_keytab.c
blob8c7c1c36d14bbadd0b064322bb4e3aca9dfecc35
1 /*
2 Unix SMB/CIFS implementation.
3 kerberos keytab utility library
4 Copyright (C) Andrew Tridgell 2001
5 Copyright (C) Remus Koos 2001
6 Copyright (C) Luke Howard 2003
7 Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
8 Copyright (C) Guenther Deschner 2003
9 Copyright (C) Rakesh Patel 2004
10 Copyright (C) Dan Perry 2004
11 Copyright (C) Jeremy Allison 2004
12 Copyright (C) Gerald Carter 2006
14 This program is free software; you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation; either version 3 of the License, or
17 (at your option) any later version.
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
24 You should have received a copy of the GNU General Public License
25 along with this program. If not, see <http://www.gnu.org/licenses/>.
28 #include "includes.h"
29 #include "smb_krb5.h"
30 #include "ads.h"
31 #include "secrets.h"
33 #ifdef HAVE_KRB5
35 #ifdef HAVE_ADS
37 /**********************************************************************
38 Adds a single service principal, i.e. 'host' to the system keytab
39 ***********************************************************************/
41 int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc)
43 krb5_error_code ret = 0;
44 krb5_context context = NULL;
45 krb5_keytab keytab = NULL;
46 krb5_data password;
47 krb5_kvno kvno;
48 krb5_enctype enctypes[6] = {
49 ENCTYPE_DES_CBC_CRC,
50 ENCTYPE_DES_CBC_MD5,
51 #ifdef HAVE_ENCTYPE_AES128_CTS_HMAC_SHA1_96
52 ENCTYPE_AES128_CTS_HMAC_SHA1_96,
53 #endif
54 #ifdef HAVE_ENCTYPE_AES256_CTS_HMAC_SHA1_96
55 ENCTYPE_AES256_CTS_HMAC_SHA1_96,
56 #endif
57 ENCTYPE_ARCFOUR_HMAC,
60 char *princ_s = NULL;
61 char *short_princ_s = NULL;
62 char *salt_princ_s = NULL;
63 char *password_s = NULL;
64 char *my_fqdn;
65 TALLOC_CTX *tmpctx = NULL;
66 char *machine_name;
67 ADS_STATUS aderr;
68 int i;
70 initialize_krb5_error_table();
71 ret = krb5_init_context(&context);
72 if (ret) {
73 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
74 error_message(ret)));
75 return -1;
78 ret = smb_krb5_kt_open(context, NULL, True, &keytab);
79 if (ret) {
80 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
81 error_message(ret)));
82 goto out;
85 /* retrieve the password */
86 if (!secrets_init()) {
87 DEBUG(1, (__location__ ": secrets_init failed\n"));
88 ret = -1;
89 goto out;
91 password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
92 if (!password_s) {
93 DEBUG(1, (__location__ ": failed to fetch machine password\n"));
94 ret = -1;
95 goto out;
97 ZERO_STRUCT(password);
98 password.data = password_s;
99 password.length = strlen(password_s);
101 /* we need the dNSHostName value here */
102 tmpctx = talloc_init(__location__);
103 if (!tmpctx) {
104 DEBUG(0, (__location__ ": talloc_init() failed!\n"));
105 ret = -1;
106 goto out;
109 my_fqdn = ads_get_dnshostname(ads, tmpctx, lp_netbios_name());
110 if (!my_fqdn) {
111 DEBUG(0, (__location__ ": unable to determine machine "
112 "account's dns name in AD!\n"));
113 ret = -1;
114 goto out;
117 machine_name = ads_get_samaccountname(ads, tmpctx, lp_netbios_name());
118 if (!machine_name) {
119 DEBUG(0, (__location__ ": unable to determine machine "
120 "account's short name in AD!\n"));
121 ret = -1;
122 goto out;
124 /*strip the trailing '$' */
125 machine_name[strlen(machine_name)-1] = '\0';
127 /* Construct our principal */
128 if (strchr_m(srvPrinc, '@')) {
129 /* It's a fully-named principal. */
130 princ_s = talloc_asprintf(tmpctx, "%s", srvPrinc);
131 if (!princ_s) {
132 ret = -1;
133 goto out;
135 } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
136 /* It's the machine account, as used by smbclient clients. */
137 princ_s = talloc_asprintf(tmpctx, "%s@%s",
138 srvPrinc, lp_realm());
139 if (!princ_s) {
140 ret = -1;
141 goto out;
143 } else {
144 /* It's a normal service principal. Add the SPN now so that we
145 * can obtain credentials for it and double-check the salt value
146 * used to generate the service's keys. */
148 princ_s = talloc_asprintf(tmpctx, "%s/%s@%s",
149 srvPrinc, my_fqdn, lp_realm());
150 if (!princ_s) {
151 ret = -1;
152 goto out;
154 short_princ_s = talloc_asprintf(tmpctx, "%s/%s@%s",
155 srvPrinc, machine_name,
156 lp_realm());
157 if (short_princ_s == NULL) {
158 ret = -1;
159 goto out;
162 /* According to http://support.microsoft.com/kb/326985/en-us,
163 certain principal names are automatically mapped to the
164 host/... principal in the AD account.
165 So only create these in the keytab, not in AD. --jerry */
167 if (!strequal(srvPrinc, "cifs") &&
168 !strequal(srvPrinc, "host")) {
169 DEBUG(3, (__location__ ": Attempting to add/update "
170 "'%s'\n", princ_s));
172 aderr = ads_add_service_principal_name(ads,
173 lp_netbios_name(), my_fqdn, srvPrinc);
174 if (!ADS_ERR_OK(aderr)) {
175 DEBUG(1, (__location__ ": failed to "
176 "ads_add_service_principal_name.\n"));
177 goto out;
182 kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
183 if (kvno == -1) {
184 /* -1 indicates failure, everything else is OK */
185 DEBUG(1, (__location__ ": ads_get_machine_kvno failed to "
186 "determine the system's kvno.\n"));
187 ret = -1;
188 goto out;
191 for (i = 0; enctypes[i]; i++) {
192 salt_princ_s = kerberos_fetch_salt_princ_for_host_princ(context,
193 princ_s,
194 enctypes[i]);
196 /* add the fqdn principal to the keytab */
197 ret = smb_krb5_kt_add_entry(context,
198 keytab,
199 kvno,
200 princ_s,
201 salt_princ_s,
202 enctypes[i],
203 &password,
204 false,
205 false);
206 if (ret) {
207 DEBUG(1, (__location__ ": Failed to add entry to keytab\n"));
208 SAFE_FREE(salt_princ_s);
209 goto out;
212 /* add the short principal name if we have one */
213 if (short_princ_s) {
214 ret = smb_krb5_kt_add_entry(context,
215 keytab,
216 kvno,
217 short_princ_s,
218 salt_princ_s,
219 enctypes[i],
220 &password,
221 false,
222 false);
223 if (ret) {
224 DEBUG(1, (__location__
225 ": Failed to add short entry to keytab\n"));
226 SAFE_FREE(salt_princ_s);
227 goto out;
230 SAFE_FREE(salt_princ_s);
233 out:
234 TALLOC_FREE(tmpctx);
236 if (keytab) {
237 krb5_kt_close(context, keytab);
239 if (context) {
240 krb5_free_context(context);
242 return (int)ret;
245 /**********************************************************************
246 Flushes all entries from the system keytab.
247 ***********************************************************************/
249 int ads_keytab_flush(ADS_STRUCT *ads)
251 krb5_error_code ret = 0;
252 krb5_context context = NULL;
253 krb5_keytab keytab = NULL;
254 krb5_kvno kvno;
255 ADS_STATUS aderr;
257 initialize_krb5_error_table();
258 ret = krb5_init_context(&context);
259 if (ret) {
260 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
261 error_message(ret)));
262 return ret;
265 ret = smb_krb5_kt_open(context, NULL, True, &keytab);
266 if (ret) {
267 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
268 error_message(ret)));
269 goto out;
272 kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
273 if (kvno == -1) {
274 /* -1 indicates a failure */
275 DEBUG(1, (__location__ ": Error determining the kvno.\n"));
276 goto out;
279 /* Seek and delete old keytab entries */
280 ret = smb_krb5_kt_seek_and_delete_old_entries(context,
281 keytab,
282 kvno,
283 ENCTYPE_NULL,
284 NULL,
285 NULL,
286 true,
287 false);
288 if (ret) {
289 goto out;
292 aderr = ads_clear_service_principal_names(ads, lp_netbios_name());
293 if (!ADS_ERR_OK(aderr)) {
294 DEBUG(1, (__location__ ": Error while clearing service "
295 "principal listings in LDAP.\n"));
296 goto out;
299 out:
300 if (keytab) {
301 krb5_kt_close(context, keytab);
303 if (context) {
304 krb5_free_context(context);
306 return ret;
309 /**********************************************************************
310 Adds all the required service principals to the system keytab.
311 ***********************************************************************/
313 int ads_keytab_create_default(ADS_STRUCT *ads)
315 krb5_error_code ret = 0;
316 krb5_context context = NULL;
317 krb5_keytab keytab = NULL;
318 krb5_kt_cursor cursor = {0};
319 krb5_keytab_entry kt_entry = {0};
320 krb5_kvno kvno;
321 size_t found = 0;
322 char *sam_account_name, *upn;
323 char **oldEntries = NULL, *princ_s[26];
324 TALLOC_CTX *frame;
325 char *machine_name;
326 char **spn_array;
327 size_t num_spns;
328 size_t i;
329 ADS_STATUS status;
331 ZERO_STRUCT(kt_entry);
332 ZERO_STRUCT(cursor);
334 frame = talloc_stackframe();
335 if (frame == NULL) {
336 ret = -1;
337 goto done;
340 status = ads_get_service_principal_names(frame,
341 ads,
342 lp_netbios_name(),
343 &spn_array,
344 &num_spns);
345 if (!ADS_ERR_OK(status)) {
346 ret = -1;
347 goto done;
350 for (i = 0; i < num_spns; i++) {
351 char *srv_princ;
352 char *p;
354 srv_princ = strlower_talloc(frame, spn_array[i]);
355 if (srv_princ == NULL) {
356 ret = -1;
357 goto done;
360 p = strchr_m(srv_princ, '/');
361 if (p == NULL) {
362 continue;
364 p[0] = '\0';
366 /* Add the SPNs found on the DC */
367 ret = ads_keytab_add_entry(ads, srv_princ);
368 if (ret != 0) {
369 DEBUG(1, ("ads_keytab_add_entry failed while "
370 "adding '%s' principal.\n",
371 spn_array[i]));
372 goto done;
376 #if 0 /* don't create the CIFS/... keytab entries since no one except smbd
377 really needs them and we will fall back to verifying against
378 secrets.tdb */
380 ret = ads_keytab_add_entry(ads, "cifs"));
381 if (ret != 0 ) {
382 DEBUG(1, (__location__ ": ads_keytab_add_entry failed while "
383 "adding 'cifs'.\n"));
384 return ret;
386 #endif
388 memset(princ_s, '\0', sizeof(princ_s));
390 initialize_krb5_error_table();
391 ret = krb5_init_context(&context);
392 if (ret) {
393 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
394 error_message(ret)));
395 goto done;
398 machine_name = talloc_strdup(frame, lp_netbios_name());
399 if (!machine_name) {
400 ret = -1;
401 goto done;
404 /* now add the userPrincipalName and sAMAccountName entries */
405 sam_account_name = ads_get_samaccountname(ads, frame, machine_name);
406 if (!sam_account_name) {
407 DEBUG(0, (__location__ ": unable to determine machine "
408 "account's name in AD!\n"));
409 ret = -1;
410 goto done;
413 /* upper case the sAMAccountName to make it easier for apps to
414 know what case to use in the keytab file */
415 if (!strupper_m(sam_account_name)) {
416 ret = -1;
417 goto done;
420 ret = ads_keytab_add_entry(ads, sam_account_name);
421 if (ret != 0) {
422 DEBUG(1, (__location__ ": ads_keytab_add_entry() failed "
423 "while adding sAMAccountName (%s)\n",
424 sam_account_name));
425 goto done;
428 /* remember that not every machine account will have a upn */
429 upn = ads_get_upn(ads, frame, machine_name);
430 if (upn) {
431 ret = ads_keytab_add_entry(ads, upn);
432 if (ret != 0) {
433 DEBUG(1, (__location__ ": ads_keytab_add_entry() "
434 "failed while adding UPN (%s)\n", upn));
435 goto done;
439 /* Now loop through the keytab and update any other existing entries */
440 kvno = (krb5_kvno)ads_get_machine_kvno(ads, machine_name);
441 if (kvno == (krb5_kvno)-1) {
442 DEBUG(1, (__location__ ": ads_get_machine_kvno() failed to "
443 "determine the system's kvno.\n"));
444 goto done;
447 DEBUG(3, (__location__ ": Searching for keytab entries to preserve "
448 "and update.\n"));
450 ret = smb_krb5_kt_open(context, NULL, True, &keytab);
451 if (ret) {
452 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
453 error_message(ret)));
454 goto done;
457 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
458 if (ret != KRB5_KT_END && ret != ENOENT ) {
459 while ((ret = krb5_kt_next_entry(context, keytab,
460 &kt_entry, &cursor)) == 0) {
461 smb_krb5_kt_free_entry(context, &kt_entry);
462 ZERO_STRUCT(kt_entry);
463 found++;
466 krb5_kt_end_seq_get(context, keytab, &cursor);
467 ZERO_STRUCT(cursor);
470 * Hmmm. There is no "rewind" function for the keytab. This means we
471 * have a race condition where someone else could add entries after
472 * we've counted them. Re-open asap to minimise the race. JRA.
474 DEBUG(3, (__location__ ": Found %zd entries in the keytab.\n", found));
475 if (!found) {
476 goto done;
479 oldEntries = talloc_zero_array(frame, char *, found + 1);
480 if (!oldEntries) {
481 DEBUG(1, (__location__ ": Failed to allocate space to store "
482 "the old keytab entries (talloc failed?).\n"));
483 ret = -1;
484 goto done;
487 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
488 if (ret == KRB5_KT_END || ret == ENOENT) {
489 krb5_kt_end_seq_get(context, keytab, &cursor);
490 ZERO_STRUCT(cursor);
491 goto done;
494 while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
495 if (kt_entry.vno != kvno) {
496 char *ktprinc = NULL;
497 char *p;
499 /* This returns a malloc'ed string in ktprinc. */
500 ret = smb_krb5_unparse_name(oldEntries, context,
501 kt_entry.principal,
502 &ktprinc);
503 if (ret) {
504 DEBUG(1, (__location__
505 ": smb_krb5_unparse_name failed "
506 "(%s)\n", error_message(ret)));
507 goto done;
510 * From looking at the krb5 source they don't seem to
511 * take locale or mb strings into account.
512 * Maybe this is because they assume utf8 ?
513 * In this case we may need to convert from utf8 to
514 * mb charset here ? JRA.
516 p = strchr_m(ktprinc, '@');
517 if (p) {
518 *p = '\0';
521 p = strchr_m(ktprinc, '/');
522 if (p) {
523 *p = '\0';
525 for (i = 0; i < found; i++) {
526 if (!oldEntries[i]) {
527 oldEntries[i] = ktprinc;
528 break;
530 if (!strcmp(oldEntries[i], ktprinc)) {
531 TALLOC_FREE(ktprinc);
532 break;
535 if (i == found) {
536 TALLOC_FREE(ktprinc);
539 smb_krb5_kt_free_entry(context, &kt_entry);
540 ZERO_STRUCT(kt_entry);
542 krb5_kt_end_seq_get(context, keytab, &cursor);
543 ZERO_STRUCT(cursor);
545 ret = 0;
546 for (i = 0; oldEntries[i]; i++) {
547 ret |= ads_keytab_add_entry(ads, oldEntries[i]);
548 TALLOC_FREE(oldEntries[i]);
551 done:
552 TALLOC_FREE(oldEntries);
553 TALLOC_FREE(frame);
555 if (context) {
556 krb5_keytab_entry zero_kt_entry;
557 krb5_kt_cursor zero_csr;
559 ZERO_STRUCT(zero_kt_entry);
560 ZERO_STRUCT(zero_csr);
562 if (memcmp(&zero_kt_entry, &kt_entry,
563 sizeof(krb5_keytab_entry))) {
564 smb_krb5_kt_free_entry(context, &kt_entry);
566 if ((memcmp(&cursor, &zero_csr,
567 sizeof(krb5_kt_cursor)) != 0) && keytab) {
568 krb5_kt_end_seq_get(context, keytab, &cursor);
570 if (keytab) {
571 krb5_kt_close(context, keytab);
573 krb5_free_context(context);
575 return ret;
578 #endif /* HAVE_ADS */
580 /**********************************************************************
581 List system keytab.
582 ***********************************************************************/
584 int ads_keytab_list(const char *keytab_name)
586 krb5_error_code ret = 0;
587 krb5_context context = NULL;
588 krb5_keytab keytab = NULL;
589 krb5_kt_cursor cursor;
590 krb5_keytab_entry kt_entry;
592 ZERO_STRUCT(kt_entry);
593 ZERO_STRUCT(cursor);
595 initialize_krb5_error_table();
596 ret = krb5_init_context(&context);
597 if (ret) {
598 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
599 error_message(ret)));
600 return ret;
603 ret = smb_krb5_kt_open(context, keytab_name, False, &keytab);
604 if (ret) {
605 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
606 error_message(ret)));
607 goto out;
610 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
611 if (ret) {
612 ZERO_STRUCT(cursor);
613 goto out;
616 printf("Vno Type Principal\n");
618 while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
620 char *princ_s = NULL;
621 char *etype_s = NULL;
622 krb5_enctype enctype = 0;
624 ret = smb_krb5_unparse_name(talloc_tos(), context,
625 kt_entry.principal, &princ_s);
626 if (ret) {
627 goto out;
630 enctype = smb_krb5_kt_get_enctype_from_entry(&kt_entry);
632 ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
633 if (ret &&
634 (asprintf(&etype_s, "UNKNOWN: %d\n", enctype) == -1)) {
635 TALLOC_FREE(princ_s);
636 goto out;
639 printf("%3d %-43s %s\n", kt_entry.vno, etype_s, princ_s);
641 TALLOC_FREE(princ_s);
642 SAFE_FREE(etype_s);
644 ret = smb_krb5_kt_free_entry(context, &kt_entry);
645 if (ret) {
646 goto out;
650 ret = krb5_kt_end_seq_get(context, keytab, &cursor);
651 if (ret) {
652 goto out;
655 /* Ensure we don't double free. */
656 ZERO_STRUCT(kt_entry);
657 ZERO_STRUCT(cursor);
658 out:
661 krb5_keytab_entry zero_kt_entry;
662 ZERO_STRUCT(zero_kt_entry);
663 if (memcmp(&zero_kt_entry, &kt_entry,
664 sizeof(krb5_keytab_entry))) {
665 smb_krb5_kt_free_entry(context, &kt_entry);
669 krb5_kt_cursor zero_csr;
670 ZERO_STRUCT(zero_csr);
671 if ((memcmp(&cursor, &zero_csr,
672 sizeof(krb5_kt_cursor)) != 0) && keytab) {
673 krb5_kt_end_seq_get(context, keytab, &cursor);
677 if (keytab) {
678 krb5_kt_close(context, keytab);
680 if (context) {
681 krb5_free_context(context);
683 return ret;
686 #endif /* HAVE_KRB5 */