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/>.
37 /* This MAX_NAME_LEN is a constant defined in krb5.h */
38 #ifndef MAX_KEYTAB_NAME_LEN
39 #define MAX_KEYTAB_NAME_LEN 1100
42 static krb5_error_code ads_keytab_open(krb5_context context,
45 char keytab_str[MAX_KEYTAB_NAME_LEN] = {0};
46 const char *keytab_name = NULL;
47 krb5_error_code ret = 0;
49 switch (lp_kerberos_method()) {
50 case KERBEROS_VERIFY_SYSTEM_KEYTAB:
51 case KERBEROS_VERIFY_SECRETS_AND_KEYTAB:
52 ret = krb5_kt_default_name(context,
54 sizeof(keytab_str) - 2);
56 DBG_WARNING("Failed to get default keytab name\n");
59 keytab_name = keytab_str;
61 case KERBEROS_VERIFY_DEDICATED_KEYTAB:
62 keytab_name = lp_dedicated_keytab_file();
65 DBG_ERR("Invalid kerberos method set (%d)\n",
66 lp_kerberos_method());
67 ret = KRB5_KT_BADNAME;
71 if (keytab_name == NULL || keytab_name[0] == '\0') {
72 DBG_ERR("Invalid keytab name\n");
73 ret = KRB5_KT_BADNAME;
77 ret = smb_krb5_kt_open(context, keytab_name, true, keytab);
79 DBG_WARNING("smb_krb5_kt_open failed (%s)\n",
88 static bool fill_default_spns(TALLOC_CTX *ctx, const char *machine_name,
89 const char *my_fqdn, const char *spn,
95 *spns = talloc_zero_array(ctx, const char*, 3);
101 psp1 = talloc_asprintf(ctx,
109 if (!strlower_m(&psp1[strlen(spn) + 1])) {
114 psp2 = talloc_asprintf(ctx,
122 if (!strlower_m(&psp2[strlen(spn) + 1])) {
131 static bool ads_set_machine_account_spns(TALLOC_CTX *ctx,
133 const char *service_or_spn,
136 const char **spn_names = NULL;
138 struct spn_struct* spn_struct = NULL;
141 /* SPN should have '/' */
142 tmp = strchr_m(service_or_spn, '/');
144 spn_struct = parse_spn(ctx, service_or_spn);
145 if (spn_struct == NULL) {
150 DBG_INFO("Attempting to add/update '%s'\n", service_or_spn);
152 if (spn_struct != NULL) {
153 spn_names = talloc_zero_array(ctx, const char*, 2);
154 spn_names[0] = service_or_spn;
158 ok = fill_default_spns(ctx,
167 aderr = ads_add_service_principal_names(ads,
170 if (!ADS_ERR_OK(aderr)) {
171 DBG_WARNING("Failed to add service principal name.\n");
179 * Create kerberos principal(s) from SPN or service name.
181 static bool service_or_spn_to_kerberos_princ(TALLOC_CTX *ctx,
182 const char *service_or_spn,
185 char **p_short_princ_s)
187 char *princ_s = NULL;
188 char *short_princ_s = NULL;
189 const char *service = service_or_spn;
190 const char *host = my_fqdn;
191 struct spn_struct* spn_struct = NULL;
195 /* SPN should have '/' */
196 tmp = strchr_m(service_or_spn, '/');
198 spn_struct = parse_spn(ctx, service_or_spn);
199 if (spn_struct == NULL) {
204 if (spn_struct != NULL) {
205 service = spn_struct->serviceclass;
206 host = spn_struct->host;
208 princ_s = talloc_asprintf(ctx, "%s/%s@%s",
211 if (princ_s == NULL) {
216 if (spn_struct == NULL) {
217 short_princ_s = talloc_asprintf(ctx, "%s/%s@%s",
218 service, lp_netbios_name(),
220 if (short_princ_s == NULL) {
225 *p_princ_s = princ_s;
226 *p_short_princ_s = short_princ_s;
231 static int add_kt_entry_etypes(krb5_context context, TALLOC_CTX *tmpctx,
232 ADS_STRUCT *ads, const char *salt_princ_s,
233 krb5_keytab keytab, krb5_kvno kvno,
234 const char *srvPrinc, const char *my_fqdn,
235 krb5_data *password, bool update_ads)
237 krb5_error_code ret = 0;
238 char *princ_s = NULL;
239 char *short_princ_s = NULL;
240 krb5_enctype enctypes[4] = {
241 ENCTYPE_AES256_CTS_HMAC_SHA1_96,
242 ENCTYPE_AES128_CTS_HMAC_SHA1_96,
243 ENCTYPE_ARCFOUR_HMAC,
248 /* Construct our principal */
249 if (strchr_m(srvPrinc, '@')) {
250 /* It's a fully-named principal. */
251 princ_s = talloc_asprintf(tmpctx, "%s", srvPrinc);
256 } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
257 /* It's the machine account, as used by smbclient clients. */
258 princ_s = talloc_asprintf(tmpctx, "%s@%s",
259 srvPrinc, lp_realm());
265 /* It's a normal service principal. Add the SPN now so that we
266 * can obtain credentials for it and double-check the salt value
267 * used to generate the service's keys. */
269 if (!service_or_spn_to_kerberos_princ(tmpctx,
278 /* According to http://support.microsoft.com/kb/326985/en-us,
279 certain principal names are automatically mapped to the
280 host/... principal in the AD account.
281 So only create these in the keytab, not in AD. --jerry */
283 if (update_ads && !strequal(srvPrinc, "cifs") &&
284 !strequal(srvPrinc, "host")) {
285 if (!ads_set_machine_account_spns(tmpctx,
295 for (i = 0; enctypes[i]; i++) {
297 /* add the fqdn principal to the keytab */
298 ret = smb_krb5_kt_add_password(context,
306 DBG_WARNING("Failed to add entry to keytab\n");
310 /* add the short principal name if we have one */
312 ret = smb_krb5_kt_add_password(context,
320 DBG_WARNING("Failed to add short entry to keytab\n");
329 /**********************************************************************
330 Adds a single service principal, i.e. 'host' to the system keytab
331 ***********************************************************************/
333 int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc, bool update_ads)
335 krb5_error_code ret = 0;
336 krb5_context context = NULL;
337 krb5_keytab keytab = NULL;
340 char *salt_princ_s = NULL;
341 char *password_s = NULL;
343 TALLOC_CTX *tmpctx = NULL;
344 char **hostnames_array = NULL;
345 size_t num_hostnames = 0;
347 ret = smb_krb5_init_context_common(&context);
349 DBG_ERR("kerberos init context failed (%s)\n",
354 ret = ads_keytab_open(context, &keytab);
359 /* retrieve the password */
360 if (!secrets_init()) {
361 DBG_WARNING("secrets_init failed\n");
365 password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
367 DBG_WARNING("failed to fetch machine password\n");
371 ZERO_STRUCT(password);
372 password.data = password_s;
373 password.length = strlen(password_s);
375 /* we need the dNSHostName value here */
376 tmpctx = talloc_init(__location__);
378 DBG_ERR("talloc_init() failed!\n");
383 my_fqdn = ads_get_dnshostname(ads, tmpctx, lp_netbios_name());
385 DBG_ERR("unable to determine machine account's dns name in "
391 /* make sure we have a single instance of the computer account */
392 if (!ads_has_samaccountname(ads, tmpctx, lp_netbios_name())) {
393 DBG_ERR("unable to determine machine account's short name in "
399 kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
401 /* -1 indicates failure, everything else is OK */
402 DBG_WARNING("ads_get_machine_kvno failed to determine the "
408 salt_princ_s = kerberos_secrets_fetch_salt_princ();
409 if (salt_princ_s == NULL) {
410 DBG_WARNING("kerberos_secrets_fetch_salt_princ() failed\n");
415 ret = add_kt_entry_etypes(context, tmpctx, ads, salt_princ_s, keytab,
416 kvno, srvPrinc, my_fqdn, &password,
422 if (ADS_ERR_OK(ads_get_additional_dns_hostnames(tmpctx, ads,
428 for (i = 0; i < num_hostnames; i++) {
430 ret = add_kt_entry_etypes(context, tmpctx, ads,
431 salt_princ_s, keytab,
434 &password, update_ads);
442 SAFE_FREE(salt_princ_s);
446 krb5_kt_close(context, keytab);
449 krb5_free_context(context);
454 /**********************************************************************
455 Delete a single service principal, i.e. 'host' from the system keytab
456 ***********************************************************************/
458 int ads_keytab_delete_entry(ADS_STRUCT *ads, const char *srvPrinc)
460 TALLOC_CTX *frame = talloc_stackframe();
461 krb5_error_code ret = 0;
462 krb5_context context = NULL;
463 krb5_keytab keytab = NULL;
464 char *princ_s = NULL;
465 krb5_principal princ = NULL;
466 char *short_princ_s = NULL;
467 krb5_principal short_princ = NULL;
470 ret = smb_krb5_init_context_common(&context);
472 DBG_ERR("kerberos init context failed (%s)\n",
477 ret = ads_keytab_open(context, &keytab);
482 /* Construct our principal */
483 if (strchr_m(srvPrinc, '@')) {
484 /* It's a fully-named principal. */
485 princ_s = talloc_asprintf(frame, "%s", srvPrinc);
490 } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
491 /* It's the machine account, as used by smbclient clients. */
492 princ_s = talloc_asprintf(frame, "%s@%s",
493 srvPrinc, lp_realm());
500 * It's a normal service principal.
502 char *my_fqdn = NULL;
506 * SPN should have '/' otherwise we
507 * need to fallback and find our dnshostname
509 tmp = strchr_m(srvPrinc, '/');
511 my_fqdn = ads_get_dnshostname(ads, frame, lp_netbios_name());
513 DBG_ERR("unable to determine machine account's dns name in "
520 ok = service_or_spn_to_kerberos_princ(frame,
531 ret = smb_krb5_parse_name(context, princ_s, &princ);
533 DEBUG(1, (__location__ ": smb_krb5_parse_name(%s) "
534 "failed (%s)\n", princ_s, error_message(ret)));
538 if (short_princ_s != NULL) {
539 ret = smb_krb5_parse_name(context, short_princ_s, &short_princ);
541 DEBUG(1, (__location__ ": smb_krb5_parse_name(%s) "
542 "failed (%s)\n", short_princ_s, error_message(ret)));
547 /* Seek and delete old keytab entries */
548 ret = smb_krb5_kt_seek_and_delete_old_entries(context,
550 false, /* keep_old_kvno */
552 false, /* enctype_only */
561 if (short_princ_s == NULL) {
565 /* Seek and delete old keytab entries */
566 ret = smb_krb5_kt_seek_and_delete_old_entries(context,
568 false, /* keep_old_kvno */
570 false, /* enctype_only */
581 krb5_free_principal(context, princ);
584 krb5_free_principal(context, short_princ);
587 krb5_kt_close(context, keytab);
590 krb5_free_context(context);
596 /**********************************************************************
597 Flushes all entries from the system keytab.
598 ***********************************************************************/
600 int ads_keytab_flush(ADS_STRUCT *ads)
602 krb5_error_code ret = 0;
603 krb5_context context = NULL;
604 krb5_keytab keytab = NULL;
607 ret = smb_krb5_init_context_common(&context);
609 DBG_ERR("kerberos init context failed (%s)\n",
614 ret = ads_keytab_open(context, &keytab);
619 /* Seek and delete all old keytab entries */
620 ret = smb_krb5_kt_seek_and_delete_old_entries(context,
622 false, /* keep_old_kvno */
624 false, /* enctype_only */
633 aderr = ads_clear_service_principal_names(ads, lp_netbios_name());
634 if (!ADS_ERR_OK(aderr)) {
635 DEBUG(1, (__location__ ": Error while clearing service "
636 "principal listings in LDAP.\n"));
643 krb5_kt_close(context, keytab);
646 krb5_free_context(context);
651 /**********************************************************************
652 Adds all the required service principals to the system keytab.
653 ***********************************************************************/
655 int ads_keytab_create_default(ADS_STRUCT *ads)
657 krb5_error_code ret = 0;
658 krb5_context context = NULL;
659 krb5_keytab keytab = NULL;
660 krb5_kt_cursor cursor = {0};
661 krb5_keytab_entry kt_entry = {0};
664 char *sam_account_name, *upn;
665 char **oldEntries = NULL, *princ_s[26];
674 ZERO_STRUCT(kt_entry);
677 frame = talloc_stackframe();
683 status = ads_get_service_principal_names(frame,
688 if (!ADS_ERR_OK(status)) {
693 for (i = 0; i < num_spns; i++) {
697 srv_princ = strlower_talloc(frame, spn_array[i]);
698 if (srv_princ == NULL) {
703 p = strchr_m(srv_princ, '/');
709 /* Add the SPNs found on the DC */
710 ret = ads_keytab_add_entry(ads, srv_princ, false);
712 DEBUG(1, ("ads_keytab_add_entry failed while "
713 "adding '%s' principal.\n",
719 #if 0 /* don't create the CIFS/... keytab entries since no one except smbd
720 really needs them and we will fall back to verifying against
723 ret = ads_keytab_add_entry(ads, "cifs", false));
725 DEBUG(1, (__location__ ": ads_keytab_add_entry failed while "
726 "adding 'cifs'.\n"));
731 memset(princ_s, '\0', sizeof(princ_s));
733 ret = smb_krb5_init_context_common(&context);
735 DBG_ERR("kerberos init context failed (%s)\n",
740 machine_name = talloc_strdup(frame, lp_netbios_name());
746 /* now add the userPrincipalName and sAMAccountName entries */
747 ok = ads_has_samaccountname(ads, frame, machine_name);
749 DEBUG(0, (__location__ ": unable to determine machine "
750 "account's name in AD!\n"));
756 * append '$' to netbios name so 'ads_keytab_add_entry' recognises
757 * it as a machine account rather than a service or Windows SPN.
759 sam_account_name = talloc_asprintf(frame, "%s$",machine_name);
760 if (sam_account_name == NULL) {
764 /* upper case the sAMAccountName to make it easier for apps to
765 know what case to use in the keytab file */
766 if (!strupper_m(sam_account_name)) {
771 ret = ads_keytab_add_entry(ads, sam_account_name, false);
773 DEBUG(1, (__location__ ": ads_keytab_add_entry() failed "
774 "while adding sAMAccountName (%s)\n",
779 /* remember that not every machine account will have a upn */
780 upn = ads_get_upn(ads, frame, machine_name);
782 ret = ads_keytab_add_entry(ads, upn, false);
784 DEBUG(1, (__location__ ": ads_keytab_add_entry() "
785 "failed while adding UPN (%s)\n", upn));
790 /* Now loop through the keytab and update any other existing entries */
791 kvno = (krb5_kvno)ads_get_machine_kvno(ads, machine_name);
792 if (kvno == (krb5_kvno)-1) {
793 DEBUG(1, (__location__ ": ads_get_machine_kvno() failed to "
794 "determine the system's kvno.\n"));
798 DEBUG(3, (__location__ ": Searching for keytab entries to preserve "
801 ret = ads_keytab_open(context, &keytab);
806 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
807 if (ret != KRB5_KT_END && ret != ENOENT ) {
808 while ((ret = krb5_kt_next_entry(context, keytab,
809 &kt_entry, &cursor)) == 0) {
810 smb_krb5_kt_free_entry(context, &kt_entry);
811 ZERO_STRUCT(kt_entry);
815 krb5_kt_end_seq_get(context, keytab, &cursor);
819 * Hmmm. There is no "rewind" function for the keytab. This means we
820 * have a race condition where someone else could add entries after
821 * we've counted them. Re-open asap to minimise the race. JRA.
823 DEBUG(3, (__location__ ": Found %zd entries in the keytab.\n", found));
828 oldEntries = talloc_zero_array(frame, char *, found + 1);
830 DEBUG(1, (__location__ ": Failed to allocate space to store "
831 "the old keytab entries (talloc failed?).\n"));
836 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
837 if (ret == KRB5_KT_END || ret == ENOENT) {
838 krb5_kt_end_seq_get(context, keytab, &cursor);
843 while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
844 if (kt_entry.vno != kvno) {
845 char *ktprinc = NULL;
848 /* This returns a malloc'ed string in ktprinc. */
849 ret = smb_krb5_unparse_name(oldEntries, context,
853 DEBUG(1, (__location__
854 ": smb_krb5_unparse_name failed "
855 "(%s)\n", error_message(ret)));
859 * From looking at the krb5 source they don't seem to
860 * take locale or mb strings into account.
861 * Maybe this is because they assume utf8 ?
862 * In this case we may need to convert from utf8 to
863 * mb charset here ? JRA.
865 p = strchr_m(ktprinc, '@');
870 p = strchr_m(ktprinc, '/');
874 for (i = 0; i < found; i++) {
875 if (!oldEntries[i]) {
876 oldEntries[i] = ktprinc;
879 if (!strcmp(oldEntries[i], ktprinc)) {
880 TALLOC_FREE(ktprinc);
885 TALLOC_FREE(ktprinc);
888 smb_krb5_kt_free_entry(context, &kt_entry);
889 ZERO_STRUCT(kt_entry);
891 krb5_kt_end_seq_get(context, keytab, &cursor);
895 for (i = 0; oldEntries[i]; i++) {
896 ret |= ads_keytab_add_entry(ads, oldEntries[i], false);
897 TALLOC_FREE(oldEntries[i]);
901 TALLOC_FREE(oldEntries);
905 if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
906 smb_krb5_kt_free_entry(context, &kt_entry);
908 if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
909 krb5_kt_end_seq_get(context, keytab, &cursor);
912 krb5_kt_close(context, keytab);
914 krb5_free_context(context);
919 #endif /* HAVE_ADS */
921 /**********************************************************************
923 ***********************************************************************/
925 int ads_keytab_list(const char *keytab_name)
927 krb5_error_code ret = 0;
928 krb5_context context = NULL;
929 krb5_keytab keytab = NULL;
930 krb5_kt_cursor cursor;
931 krb5_keytab_entry kt_entry;
933 ZERO_STRUCT(kt_entry);
936 ret = smb_krb5_init_context_common(&context);
938 DBG_ERR("kerberos init context failed (%s)\n",
943 if (keytab_name == NULL) {
945 ret = ads_keytab_open(context, &keytab);
950 ret = smb_krb5_kt_open(context, keytab_name, False, &keytab);
953 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
954 error_message(ret)));
958 ret = krb5_kt_start_seq_get(context, keytab, &cursor);
964 printf("Vno Type Principal\n");
966 while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
968 char *princ_s = NULL;
969 char *etype_s = NULL;
970 krb5_enctype enctype = 0;
972 ret = smb_krb5_unparse_name(talloc_tos(), context,
973 kt_entry.principal, &princ_s);
978 enctype = smb_krb5_kt_get_enctype_from_entry(&kt_entry);
980 ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
982 (asprintf(&etype_s, "UNKNOWN: %d", enctype) == -1)) {
983 TALLOC_FREE(princ_s);
987 printf("%3d %-43s %s\n", kt_entry.vno, etype_s, princ_s);
989 TALLOC_FREE(princ_s);
992 ret = smb_krb5_kt_free_entry(context, &kt_entry);
998 ret = krb5_kt_end_seq_get(context, keytab, &cursor);
1003 /* Ensure we don't double free. */
1004 ZERO_STRUCT(kt_entry);
1005 ZERO_STRUCT(cursor);
1008 if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
1009 smb_krb5_kt_free_entry(context, &kt_entry);
1011 if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
1012 krb5_kt_end_seq_get(context, keytab, &cursor);
1016 krb5_kt_close(context, keytab);
1019 krb5_free_context(context);
1024 #endif /* HAVE_KRB5 */