/* * GSSAPI Security Extensions * Krb5 helpers * Copyright (C) Simo Sorce 2010. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . */ #include "includes.h" #include "smb_krb5.h" #include "secrets.h" #include "librpc/gen_ndr/secrets.h" #include "gse_krb5.h" #include "lib/param/loadparm.h" #include "libads/kerberos_proto.h" #include "lib/util/string_wrappers.h" #ifdef HAVE_KRB5 static krb5_error_code flush_keytab(krb5_context krbctx, krb5_keytab keytab) { krb5_error_code ret; krb5_kt_cursor kt_cursor; krb5_keytab_entry kt_entry; ZERO_STRUCT(kt_entry); ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor); if (ret != 0) { return ret; } ret = krb5_kt_next_entry(krbctx, keytab, &kt_entry, &kt_cursor); while (ret == 0) { /* we need to close and reopen enumeration because we modify * the keytab */ ret = krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor); if (ret != 0) { DEBUG(1, (__location__ ": krb5_kt_end_seq_get() " "failed (%s)\n", error_message(ret))); goto out; } /* remove the entry */ ret = krb5_kt_remove_entry(krbctx, keytab, &kt_entry); if (ret != 0) { DEBUG(1, (__location__ ": krb5_kt_remove_entry() " "failed (%s)\n", error_message(ret))); goto out; } smb_krb5_kt_free_entry(krbctx, &kt_entry); ZERO_STRUCT(kt_entry); /* now reopen */ ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor); if (ret != 0) { DEBUG(1, (__location__ ": krb5_kt_start_seq() failed " "(%s)\n", error_message(ret))); goto out; } ret = krb5_kt_next_entry(krbctx, keytab, &kt_entry, &kt_cursor); } if (ret != KRB5_KT_END && ret != ENOENT) { DEBUG(1, (__location__ ": flushing keytab we got [%s]!\n", error_message(ret))); } ret = krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor); if (ret != 0) { DEBUG(1, (__location__ ": krb5_kt_end_seq_get() " "failed (%s)\n", error_message(ret))); goto out; } ret = 0; out: return ret; } static krb5_error_code fill_keytab_from_password(krb5_context krbctx, krb5_keytab keytab, krb5_principal princ, krb5_kvno vno, struct secrets_domain_info1_password *pw) { krb5_error_code ret; krb5_enctype *enctypes; uint16_t i; ret = smb_krb5_get_allowed_etypes(krbctx, &enctypes); if (ret) { DEBUG(1, (__location__ ": Can't determine permitted enctypes!\n")); return ret; } for (i = 0; i < pw->num_keys; i++) { krb5_keytab_entry kt_entry; krb5_keyblock *key = NULL; unsigned int ei; bool found_etype = false; for (ei=0; enctypes[ei] != 0; ei++) { if ((uint32_t)enctypes[ei] != pw->keys[i].keytype) { continue; } found_etype = true; break; } if (!found_etype) { continue; } ZERO_STRUCT(kt_entry); kt_entry.principal = princ; kt_entry.vno = vno; key = KRB5_KT_KEY(&kt_entry); KRB5_KEY_TYPE(key) = pw->keys[i].keytype; KRB5_KEY_DATA(key) = pw->keys[i].value.data; KRB5_KEY_LENGTH(key) = pw->keys[i].value.length; ret = krb5_kt_add_entry(krbctx, keytab, &kt_entry); if (ret) { DEBUG(1, (__location__ ": Failed to add entry to " "keytab for enctype %d (error: %s)\n", (unsigned)pw->keys[i].keytype, error_message(ret))); goto out; } } ret = 0; out: krb5_free_enctypes(krbctx, enctypes); return ret; } #define SRV_MEM_KEYTAB_NAME "MEMORY:cifs_srv_keytab" #define CLEARTEXT_PRIV_ENCTYPE -99 static krb5_error_code fill_mem_keytab_from_secrets(krb5_context krbctx, krb5_keytab *keytab) { TALLOC_CTX *frame = talloc_stackframe(); krb5_error_code ret, ret2; const char *domain = lp_workgroup(); struct secrets_domain_info1 *info = NULL; const char *realm = NULL; const DATA_BLOB *ct = NULL; krb5_kt_cursor kt_cursor; krb5_keytab_entry kt_entry; krb5_principal princ = NULL; krb5_kvno kvno = 0; /* FIXME: fetch current vno from KDC ? */ NTSTATUS status; if (!secrets_init()) { DEBUG(1, (__location__ ": secrets_init failed\n")); TALLOC_FREE(frame); return KRB5_CONFIG_CANTOPEN; } status = secrets_fetch_or_upgrade_domain_info(domain, frame, &info); if (!NT_STATUS_IS_OK(status)) { DBG_WARNING("secrets_fetch_or_upgrade_domain_info(%s) - %s\n", domain, nt_errstr(status)); TALLOC_FREE(frame); return KRB5_LIBOS_CANTREADPWD; } ct = &info->password->cleartext_blob; if (info->domain_info.dns_domain.string != NULL) { realm = strupper_talloc(frame, info->domain_info.dns_domain.string); if (realm == NULL) { TALLOC_FREE(frame); return ENOMEM; } } ZERO_STRUCT(kt_entry); ZERO_STRUCT(kt_cursor); /* check if the keytab already has any entry */ ret = krb5_kt_start_seq_get(krbctx, *keytab, &kt_cursor); if (ret != 0) { goto out; } /* check if we have our special enctype used to hold * the clear text password. If so, check it out so that * we can verify if the keytab needs to be upgraded */ while ((ret = krb5_kt_next_entry(krbctx, *keytab, &kt_entry, &kt_cursor)) == 0) { if (smb_krb5_kt_get_enctype_from_entry(&kt_entry) == CLEARTEXT_PRIV_ENCTYPE) { break; } smb_krb5_kt_free_entry(krbctx, &kt_entry); ZERO_STRUCT(kt_entry); } ret2 = krb5_kt_end_seq_get(krbctx, *keytab, &kt_cursor); if (ret2 != 0) { ret = ret2; DEBUG(1, (__location__ ": krb5_kt_end_seq_get() " "failed (%s)\n", error_message(ret))); goto out; } if (ret != 0 && ret != KRB5_KT_END && ret != ENOENT ) { /* Error parsing keytab */ DEBUG(1, (__location__ ": Failed to parse memory " "keytab!\n")); goto out; } if (ret == 0) { /* found private entry, * check if keytab is up to date */ if ((ct->length == KRB5_KEY_LENGTH(KRB5_KT_KEY(&kt_entry))) && (mem_equal_const_time(KRB5_KEY_DATA(KRB5_KT_KEY(&kt_entry)), ct->data, ct->length))) { /* keytab is already up to date, return */ smb_krb5_kt_free_entry(krbctx, &kt_entry); goto out; } smb_krb5_kt_free_entry(krbctx, &kt_entry); ZERO_STRUCT(kt_entry); /* flush keytab, we need to regen it */ ret = flush_keytab(krbctx, *keytab); if (ret) { DEBUG(1, (__location__ ": Failed to flush " "memory keytab!\n")); goto out; } } /* keytab is not up to date, fill it up */ ret = smb_krb5_make_principal(krbctx, &princ, realm, info->account_name, NULL); if (ret) { DEBUG(1, (__location__ ": Failed to get host principal!\n")); goto out; } ret = fill_keytab_from_password(krbctx, *keytab, princ, kvno, info->password); if (ret) { DBG_WARNING("fill_keytab_from_password() failed for " "info->password.\n."); goto out; } if (info->old_password != NULL) { ret = fill_keytab_from_password(krbctx, *keytab, princ, kvno - 1, info->old_password); if (ret) { DBG_WARNING("fill_keytab_from_password() failed for " "info->old_password.\n."); goto out; } } if (info->older_password != NULL) { ret = fill_keytab_from_password(krbctx, *keytab, princ, kvno - 2, info->older_password); if (ret) { DBG_WARNING("fill_keytab_from_password() failed for " "info->older_password.\n."); goto out; } } if (info->next_change != NULL) { ret = fill_keytab_from_password(krbctx, *keytab, princ, kvno - 3, info->next_change->password); if (ret) { DBG_WARNING("fill_keytab_from_password() failed for " "info->next_change->password.\n."); goto out; } } /* add our private enctype + cleartext password so that we can * update the keytab if secrets change later on */ ZERO_STRUCT(kt_entry); kt_entry.principal = princ; kt_entry.vno = 0; KRB5_KEY_TYPE(KRB5_KT_KEY(&kt_entry)) = CLEARTEXT_PRIV_ENCTYPE; KRB5_KEY_LENGTH(KRB5_KT_KEY(&kt_entry)) = ct->length; KRB5_KEY_DATA(KRB5_KT_KEY(&kt_entry)) = ct->data; ret = krb5_kt_add_entry(krbctx, *keytab, &kt_entry); if (ret) { DEBUG(1, (__location__ ": Failed to add entry to " "keytab for private enctype (%d) (error: %s)\n", CLEARTEXT_PRIV_ENCTYPE, error_message(ret))); goto out; } ret = 0; out: if (princ) { krb5_free_principal(krbctx, princ); } TALLOC_FREE(frame); return ret; } static krb5_error_code fill_mem_keytab_from_system_keytab(krb5_context krbctx, krb5_keytab *mkeytab) { krb5_error_code ret = 0; krb5_keytab keytab = NULL; krb5_kt_cursor kt_cursor = { 0, }; krb5_keytab_entry kt_entry = { 0, }; char *valid_princ_formats[7] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL }; char *entry_princ_s = NULL; fstring my_name, my_fqdn; unsigned i; int err; /* Generate the list of principal names which we expect * clients might want to use for authenticating to the file * service. We allow name$,{host,cifs}/{name,fqdn,name.REALM}. */ fstrcpy(my_name, lp_netbios_name()); my_fqdn[0] = '\0'; name_to_fqdn(my_fqdn, lp_netbios_name()); err = asprintf(&valid_princ_formats[0], "%s$@%s", my_name, lp_realm()); if (err == -1) { ret = ENOMEM; goto out; } err = asprintf(&valid_princ_formats[1], "host/%s@%s", my_name, lp_realm()); if (err == -1) { ret = ENOMEM; goto out; } err = asprintf(&valid_princ_formats[2], "host/%s@%s", my_fqdn, lp_realm()); if (err == -1) { ret = ENOMEM; goto out; } err = asprintf(&valid_princ_formats[3], "host/%s.%s@%s", my_name, lp_realm(), lp_realm()); if (err == -1) { ret = ENOMEM; goto out; } err = asprintf(&valid_princ_formats[4], "cifs/%s@%s", my_name, lp_realm()); if (err == -1) { ret = ENOMEM; goto out; } err = asprintf(&valid_princ_formats[5], "cifs/%s@%s", my_fqdn, lp_realm()); if (err == -1) { ret = ENOMEM; goto out; } err = asprintf(&valid_princ_formats[6], "cifs/%s.%s@%s", my_name, lp_realm(), lp_realm()); if (err == -1) { ret = ENOMEM; goto out; } ret = smb_krb5_kt_open_relative(krbctx, NULL, false, &keytab); if (ret) { DEBUG(1, ("smb_krb5_kt_open failed (%s)\n", error_message(ret))); goto out; } /* * Iterate through the keytab. For each key, if the principal * name case-insensitively matches one of the allowed formats, * copy it to the memory keytab. */ ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor); if (ret) { DEBUG(1, (__location__ ": krb5_kt_start_seq_get failed (%s)\n", error_message(ret))); /* * krb5_kt_start_seq_get() may leaves bogus data * in kt_cursor. And we want to use the all_zero() * logic below. * * See bug #10490 */ ZERO_STRUCT(kt_cursor); goto out; } while ((krb5_kt_next_entry(krbctx, keytab, &kt_entry, &kt_cursor) == 0)) { ret = smb_krb5_unparse_name(talloc_tos(), krbctx, kt_entry.principal, &entry_princ_s); if (ret) { DEBUG(1, (__location__ ": smb_krb5_unparse_name " "failed (%s)\n", error_message(ret))); goto out; } for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) { if (!strequal(entry_princ_s, valid_princ_formats[i])) { continue; } ret = krb5_kt_add_entry(krbctx, *mkeytab, &kt_entry); if (ret) { DEBUG(1, (__location__ ": smb_krb5_unparse_name " "failed (%s)\n", error_message(ret))); goto out; } } /* Free the name we parsed. */ TALLOC_FREE(entry_princ_s); /* Free the entry we just read. */ smb_krb5_kt_free_entry(krbctx, &kt_entry); ZERO_STRUCT(kt_entry); } krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor); ZERO_STRUCT(kt_cursor); out: for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) { SAFE_FREE(valid_princ_formats[i]); } TALLOC_FREE(entry_princ_s); if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) { smb_krb5_kt_free_entry(krbctx, &kt_entry); } if (!all_zero((uint8_t *)&kt_cursor, sizeof(kt_cursor)) && keytab) { krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor); } if (keytab) { krb5_kt_close(krbctx, keytab); } return ret; } static krb5_error_code fill_mem_keytab_from_dedicated_keytab(krb5_context krbctx, krb5_keytab *mkeytab) { krb5_error_code ret = 0; krb5_keytab keytab = NULL; krb5_kt_cursor kt_cursor; krb5_keytab_entry kt_entry; ret = smb_krb5_kt_open(krbctx, lp_dedicated_keytab_file(), false, &keytab); if (ret) { DEBUG(1, ("smb_krb5_kt_open of %s failed (%s)\n", lp_dedicated_keytab_file(), error_message(ret))); return ret; } /* * Copy the dedicated keyab to our in-memory keytab. */ ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor); if (ret) { DEBUG(1, (__location__ ": krb5_kt_start_seq_get on %s " "failed (%s)\n", lp_dedicated_keytab_file(), error_message(ret))); goto out; } while ((krb5_kt_next_entry(krbctx, keytab, &kt_entry, &kt_cursor) == 0)) { ret = krb5_kt_add_entry(krbctx, *mkeytab, &kt_entry); /* Free the entry we just read. */ smb_krb5_kt_free_entry(krbctx, &kt_entry); if (ret) { DEBUG(1, (__location__ ": smb_krb5_unparse_name " "failed (%s)\n", error_message(ret))); break; } } krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor); out: krb5_kt_close(krbctx, keytab); return ret; } krb5_error_code gse_krb5_get_server_keytab(krb5_context krbctx, krb5_keytab *keytab) { krb5_error_code ret = 0; krb5_error_code ret1 = 0; krb5_error_code ret2 = 0; *keytab = NULL; /* create memory keytab */ ret = krb5_kt_resolve(krbctx, SRV_MEM_KEYTAB_NAME, keytab); if (ret) { DEBUG(1, (__location__ ": Failed to get memory " "keytab!\n")); return ret; } switch (lp_kerberos_method()) { default: case KERBEROS_VERIFY_SECRETS: ret = fill_mem_keytab_from_secrets(krbctx, keytab); break; case KERBEROS_VERIFY_SYSTEM_KEYTAB: ret = fill_mem_keytab_from_system_keytab(krbctx, keytab); break; case KERBEROS_VERIFY_DEDICATED_KEYTAB: /* just use whatever keytab is configured */ ret = fill_mem_keytab_from_dedicated_keytab(krbctx, keytab); break; case KERBEROS_VERIFY_SECRETS_AND_KEYTAB: ret1 = fill_mem_keytab_from_secrets(krbctx, keytab); if (ret1) { DEBUG(3, (__location__ ": Warning! Unable to set mem " "keytab from secrets!\n")); } /* Now append system keytab keys too */ ret2 = fill_mem_keytab_from_system_keytab(krbctx, keytab); if (ret2) { DEBUG(3, (__location__ ": Warning! Unable to set mem " "keytab from system keytab!\n")); } if (ret1 == 0 || ret2 == 0) { ret = 0; } else { ret = ret1; } break; } if (ret) { krb5_kt_close(krbctx, *keytab); *keytab = NULL; DEBUG(1,("%s: Error! Unable to set mem keytab - %d\n", __location__, ret)); } return ret; } #endif /* HAVE_KRB5 */