1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00
samba-mirror/source3/libads/kerberos_keytab.c
Alexander Bokovoy 4f577c7b68 sync machine password to keytab: handle FreeIPA use case
FreeIPA uses own procedure to retrieve keytabs and during the setup of
Samba on FreeIPA client the keytab is already present, only machine
account needs to be set in the secrets database.

'sync machine password to keytab' option handling broke this use case by
always attempting to contact a domain controller and failing to do so
(Fedora bug https://bugzilla.redhat.com/show_bug.cgi?id=2309199).

The original synchronizing machine account password to keytab feature
did not have a mechanism to disable its logic at all.

Signed-off-by: Alexander Bokovoy <ab@samba.org>
Reviewed-by: Pavel Filipenský <pfilipensky@samba.org>

Autobuild-User(master): Alexander Bokovoy <ab@samba.org>
Autobuild-Date(master): Fri Sep 13 13:16:09 UTC 2024 on atb-devel-224
2024-09-13 13:16:09 +00:00

1190 lines
30 KiB
C

/*
Unix SMB/CIFS implementation.
kerberos keytab utility library
Copyright (C) Andrew Tridgell 2001
Copyright (C) Remus Koos 2001
Copyright (C) Luke Howard 2003
Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
Copyright (C) Guenther Deschner 2003
Copyright (C) Rakesh Patel 2004
Copyright (C) Dan Perry 2004
Copyright (C) Jeremy Allison 2004
Copyright (C) Gerald Carter 2006
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 <http://www.gnu.org/licenses/>.
*/
#include "includes.h"
#include "smb_krb5.h"
#include "ads.h"
#include "secrets.h"
#include "librpc/gen_ndr/ndr_secrets.h"
#ifdef HAVE_KRB5
#ifdef HAVE_ADS
/* This MAX_NAME_LEN is a constant defined in krb5.h */
#ifndef MAX_KEYTAB_NAME_LEN
#define MAX_KEYTAB_NAME_LEN 1100
#endif
enum spn_spec_type {
SPN_SPEC_DEFAULT,
SPN_SPEC_SYNC,
SPN_SPEC_FULL,
SPN_SPEC_PREFIX
};
/* pw2kt_conf contains 1 parsed line from "sync machine password to keytab" */
struct pw2kt_conf {
enum spn_spec_type spn_spec;
char *keytab;
bool sync_etypes;
bool sync_kvno;
bool additional_dns_hostnames;
bool netbios_aliases;
bool machine_password;
char **spn_spec_array;
size_t num_spn_spec;
};
/* State used by pw2kt */
struct pw2kt_state {
/* Array of parsed lines from "sync machine password to keytab" */
struct pw2kt_conf *keytabs;
size_t num_keytabs;
bool sync_etypes;
bool sync_kvno;
bool sync_spns;
/* These are from DC */
krb5_kvno ad_kvno;
uint32_t ad_etypes;
char **ad_spn_array;
size_t ad_num_spns;
/* This is from secrets.db */
struct secrets_domain_info1 *info;
};
/* State used by pw2kt_process_keytab */
struct pw2kt_process_state {
krb5_keytab keytab;
krb5_context context;
krb5_keytab_entry *array1;
krb5_keytab_entry *array2;
krb5_principal *princ_array;
krb5_enctype *enctypes;
krb5_enctype preferred_etype;
};
static ADS_STATUS pw2kt_scan_add_spn(TALLOC_CTX *ctx,
const char *spn,
struct pw2kt_conf *conf)
{
conf->spn_spec_array = talloc_realloc(ctx,
conf->spn_spec_array,
char *,
conf->num_spn_spec + 1);
if (conf->spn_spec_array == NULL) {
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
conf->spn_spec_array[conf->num_spn_spec] = talloc_strdup(
conf->spn_spec_array, spn);
if (conf->spn_spec_array[conf->num_spn_spec] == NULL) {
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
conf->num_spn_spec++;
return ADS_SUCCESS;
}
/*
* Parse the smb.conf and find out if it is needed to read from DC:
* - servicePrincipalNames
* - msDs-KeyVersionNumber
*/
static ADS_STATUS pw2kt_scan_line(const char *line, struct pw2kt_state *state)
{
char *keytabname = NULL;
char *spn_spec = NULL;
char *spn_val = NULL;
char *option = NULL;
struct pw2kt_conf *conf = NULL;
ADS_STATUS status;
state->keytabs = talloc_realloc(state,
state->keytabs,
struct pw2kt_conf,
state->num_keytabs + 1);
if (state->keytabs == NULL) {
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
conf = &state->keytabs[state->num_keytabs];
state->num_keytabs++;
keytabname = talloc_strdup(state->keytabs, line);
if (keytabname == NULL) {
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
ZERO_STRUCT(*conf);
conf->keytab = keytabname;
spn_spec = strchr_m(keytabname, ':');
if (spn_spec == NULL) {
DBG_ERR("Invalid format! ':' expected in '%s'\n", keytabname);
return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
}
*spn_spec++ = 0;
/* reverse match with strrchr_m() */
while ((option = strrchr_m(spn_spec, ':')) != NULL) {
*option++ = 0;
if (strequal(option, "sync_kvno")) {
conf->sync_kvno = state->sync_kvno = true;
} else if (strequal(option, "sync_etypes")) {
conf->sync_etypes = state->sync_etypes = true;
} else if (strequal(option, "additional_dns_hostnames")) {
conf->additional_dns_hostnames = true;
} else if (strequal(option, "netbios_aliases")) {
conf->netbios_aliases = true;
} else if (strequal(option, "machine_password")) {
conf->machine_password = true;
} else {
DBG_WARNING("Unknown option '%s'!\n", option);
return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
}
}
spn_val = strchr_m(spn_spec, '=');
if (spn_val != NULL) {
*spn_val++ = 0;
}
if (strcmp(spn_spec, "account_name") == 0) {
conf->spn_spec = SPN_SPEC_DEFAULT;
} else if (strcmp(spn_spec, "sync_spns") == 0) {
conf->spn_spec = SPN_SPEC_SYNC;
state->sync_spns = true;
} else if (strcmp(spn_spec, "spns") == 0 ||
strcmp(spn_spec, "spn_prefixes") == 0)
{
char *spn = NULL, *tmp = NULL;
conf->spn_spec = strcmp(spn_spec, "spns") == 0
? SPN_SPEC_FULL
: SPN_SPEC_PREFIX;
conf->num_spn_spec = 0;
spn = spn_val;
while ((tmp = strchr_m(spn, ',')) != NULL) {
*tmp++ = 0;
status = pw2kt_scan_add_spn(state->keytabs, spn, conf);
if (!ADS_ERR_OK(status)) {
return status;
}
spn = tmp;
}
/* Do not forget the last entry */
return pw2kt_scan_add_spn(state->keytabs, spn, conf);
} else {
DBG_WARNING("Invalid SPN specifier: %s\n", spn_spec);
return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
}
return ADS_SUCCESS;
}
/*
* Fill struct pw2kt_state with defaults if "sync machine password to keytab"
* is missing in smb.conf
*/
static ADS_STATUS pw2kt_default_cfg(const char *name, struct pw2kt_state *state)
{
char *keytabname = NULL;
struct pw2kt_conf *conf = NULL;
state->keytabs = talloc_zero_array(state->keytabs,
struct pw2kt_conf,
1);
if (state->keytabs == NULL) {
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
conf = &state->keytabs[0];
state->num_keytabs = 1;
keytabname = talloc_strdup(state->keytabs, name);
if (keytabname == NULL) {
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
conf->spn_spec = SPN_SPEC_SYNC;
conf->keytab = keytabname;
conf->machine_password = true;
conf->sync_kvno = state->sync_kvno = true;
state->sync_spns = true;
return ADS_SUCCESS;
}
/*
* For the given principal add to the array entries created from all pw->keys[]
*/
static krb5_error_code pw2kt_process_add_pw(
struct pw2kt_process_state *state2,
krb5_principal princ,
krb5_kvno vno,
struct secrets_domain_info1_password *pw)
{
uint16_t i;
size_t len = talloc_array_length(state2->array1);
for (i = 0; i < pw->num_keys; i++) {
krb5_keytab_entry *kt_entry = NULL;
krb5_keyblock *key = NULL;
krb5_keytab_entry *tmp_a = NULL;
if (state2->preferred_etype != -1 &&
state2->preferred_etype != pw->keys[i].keytype)
{
DBG_DEBUG("Skip enc type '%d'.\n", pw->keys[i].keytype);
continue;
}
len++;
tmp_a = talloc_realloc(state2,
state2->array1,
krb5_keytab_entry,
len);
if (tmp_a == NULL) {
return ENOMEM;
}
state2->array1 = tmp_a;
kt_entry = &state2->array1[len - 1];
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;
}
return 0;
}
/*
* For the given principal add to the array entries based on password,
* old_password, older_password and next_change->password.
*/
static krb5_error_code pw2kt_process_add_info(
struct pw2kt_process_state *state2,
krb5_kvno kvno,
const char *princs,
struct secrets_domain_info1 *info)
{
krb5_error_code ret;
krb5_principal princ = NULL;
krb5_principal *a = NULL;
size_t len;
ret = smb_krb5_parse_name(state2->context, princs, &princ);
if (ret != 0) {
DBG_ERR("Failed to parse principal: %s\n", princs);
return ret;
}
len = talloc_array_length(state2->princ_array);
a = talloc_realloc(state2,
state2->princ_array,
krb5_principal,
len + 1);
if (a == NULL) {
(void)krb5_free_principal(state2->context, princ);
return ENOMEM;
}
a[len] = princ;
state2->princ_array = a;
#define ADD_PW(K, P) \
if (info->P != NULL) { \
ret = pw2kt_process_add_pw(state2, princ, (K), info->P); \
if (ret != 0) { \
DBG_ERR("Failed adding %s keys for %s.\n", \
#P, \
princs); \
return ret; \
} \
}
ADD_PW(kvno, password);
ADD_PW(kvno - 1, old_password);
ADD_PW(kvno - 2, older_password);
if (info->next_change) {
ADD_PW(kvno == -1 ? -4 : kvno + 1, next_change->password);
}
return ret;
}
static int pw2kt_process_state_destructor(struct pw2kt_process_state *state2)
{
int i;
size_t len2 = talloc_array_length(state2->array2);
size_t len_p = talloc_array_length(state2->princ_array);
for (i = 0; i < len2; i++) {
(void)smb_krb5_kt_free_entry(state2->context,
&state2->array2[i]);
}
for (i = 0; i < len_p; i++) {
(void)krb5_free_principal(state2->context,
state2->princ_array[i]);
}
(void)krb5_free_enctypes(state2->context, state2->enctypes);
return 0;
}
/* Read the whole keytab to krb5_keytab_entry array */
static krb5_error_code pw2kt_process_kt2ar(struct pw2kt_process_state *state2)
{
krb5_error_code ret = 0, ret2 = 0;
krb5_kt_cursor cursor;
krb5_keytab_entry *a = NULL;
krb5_keytab_entry e;
size_t num = 0;
ZERO_STRUCT(cursor);
ret = krb5_kt_start_seq_get(state2->context, state2->keytab, &cursor);
if (ret != 0) {
if (ret == KRB5_KT_END || ret == ENOENT) {
ret = 0;
}
return ret;
}
for (;;) {
ret = samba_krb5_kt_next_entry(state2->context,
state2->keytab,
&e,
&cursor);
if (ret != 0) {
break;
}
a = talloc_realloc(state2,
state2->array2,
krb5_keytab_entry,
num + 1);
if (a == NULL) {
smb_krb5_kt_free_entry(state2->context, &e);
return ENOMEM;
}
a[num++] = e;
state2->array2 = a;
}
if (ret == KRB5_KT_END || ret == ENOENT) {
ret = 0;
}
ret2 = krb5_kt_end_seq_get(state2->context, state2->keytab, &cursor);
return ret != 0 ? ret : ret2;
}
static ADS_STATUS pw2kt_process_keytab(struct pw2kt_state *state,
struct pw2kt_conf *keytabptr)
{
krb5_error_code ret = 0;
krb5_kvno kvno = -1;
size_t i, j, len1 = 0, len2 = 0;
char *princ_s = NULL;
const char **netbios_alias = NULL;
const char **addl_hostnames = NULL;
size_t *index_array1 = NULL;
size_t *index_array2 = NULL;
struct pw2kt_process_state *state2 = NULL;
if (!keytabptr->machine_password) {
DBG_ERR("No 'machine_password' option for '%s'. Skip it.\n",
keytabptr->keytab);
return ADS_SUCCESS;
}
state2 = talloc_zero(state, struct pw2kt_process_state);
if (state2 == NULL) {
return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
}
talloc_set_destructor(state2, pw2kt_process_state_destructor);
ret = smb_krb5_init_context_common(&state2->context);
if (ret != 0) {
DBG_ERR("Krb init context failed (%s)\n", error_message(ret));
return ADS_ERROR_KRB5(ret);
}
#define MATCH_ENCTYPE(TYPES, TYPE) \
({ \
int ei, result = 0; \
for (ei = 0; (TYPES)[ei] != 0; ei++) { \
if ((uint32_t)(TYPES)[ei] != (TYPE)) { \
continue; \
} \
result = 1; \
break; \
} \
result; \
})
#define COMMON_ENCTYPE(ETYPE) \
MATCH_ENCTYPE((state2->enctypes), (ETYPE)) && \
((state->ad_etypes) & (ETYPE))
/*
* -1 means there is no information about supported encryption types
* from DC and all encoding types will be added to the keytab.
*/
state2->preferred_etype = -1;
/* Find the highest common enc type for AD and KRB5 lib */
if (keytabptr->sync_etypes) {
ret = smb_krb5_get_allowed_etypes(state2->context,
&state2->enctypes);
if (ret != 0) {
DBG_ERR("Failed to get allowed enc types.\n");
return ADS_ERROR_KRB5(ret);
}
if (COMMON_ENCTYPE(ENCTYPE_AES256_CTS_HMAC_SHA1_96)) {
state2->preferred_etype =
ENCTYPE_AES256_CTS_HMAC_SHA1_96;
} else if (COMMON_ENCTYPE(ENCTYPE_AES128_CTS_HMAC_SHA1_96)) {
state2->preferred_etype =
ENCTYPE_AES128_CTS_HMAC_SHA1_96;
} else if (COMMON_ENCTYPE(ENCTYPE_ARCFOUR_HMAC)) {
state2->preferred_etype = ENCTYPE_ARCFOUR_HMAC;
} else {
DBG_ERR("No common enctype for AD and KRB5 lib.\n");
return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
}
}
if (keytabptr->sync_kvno) {
kvno = state->ad_kvno;
}
#define ADD_INFO(P) \
ret = pw2kt_process_add_info(state2, kvno, (P), state->info); \
if (ret != 0) { \
return ADS_ERROR_KRB5(ret); \
}
/* Add ACCOUNTNAME$ entries */
switch (keytabptr->spn_spec) {
case SPN_SPEC_DEFAULT:
ADD_INFO(state->info->account_name);
break;
case SPN_SPEC_SYNC:
for (i = 0; i < state->ad_num_spns; i++) {
ADD_INFO(state->ad_spn_array[i]);
}
break;
case SPN_SPEC_FULL:
for (i = 0; i < keytabptr->num_spn_spec; i++) {
ADD_INFO(keytabptr->spn_spec_array[i]);
}
break;
case SPN_SPEC_PREFIX:
for (i = 0; i < keytabptr->num_spn_spec; i++) {
princ_s = talloc_asprintf(talloc_tos(),
"%s/%s@%s",
keytabptr->spn_spec_array[i],
lp_netbios_name(),
lp_realm());
if (princ_s == NULL) {
return ADS_ERROR_KRB5(ENOMEM);
}
ADD_INFO(princ_s);
if (!keytabptr->netbios_aliases) {
goto additional_dns_hostnames;
}
for (netbios_alias = lp_netbios_aliases();
netbios_alias != NULL && *netbios_alias != NULL;
netbios_alias++)
{
/* Add PREFIX/netbiosname@REALM */
princ_s = talloc_asprintf(
talloc_tos(),
"%s/%s@%s",
keytabptr->spn_spec_array[i],
*netbios_alias,
lp_realm());
if (princ_s == NULL) {
return ADS_ERROR_KRB5(ENOMEM);
}
ADD_INFO(princ_s);
/* Add PREFIX/netbiosname.domainname@REALM */
princ_s = talloc_asprintf(
talloc_tos(),
"%s/%s.%s@%s",
keytabptr->spn_spec_array[i],
*netbios_alias,
lp_dnsdomain(),
lp_realm());
if (princ_s == NULL) {
return ADS_ERROR_KRB5(ENOMEM);
}
ADD_INFO(princ_s);
}
additional_dns_hostnames:
if (!keytabptr->additional_dns_hostnames) {
continue;
}
for (addl_hostnames = lp_additional_dns_hostnames();
addl_hostnames != NULL && *addl_hostnames != NULL;
addl_hostnames++)
{
/* Add PREFIX/netbiosname@REALM */
princ_s = talloc_asprintf(
talloc_tos(),
"%s/%s@%s",
keytabptr->spn_spec_array[i],
*addl_hostnames,
lp_realm());
if (princ_s == NULL) {
return ADS_ERROR_KRB5(ENOMEM);
}
ADD_INFO(princ_s);
}
}
break;
default:
return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
}
ret = smb_krb5_kt_open(state2->context,
keytabptr->keytab,
true,
&state2->keytab);
if (ret != 0) {
return ADS_ERROR_KRB5(ret);
}
/* The new entries are in array1. Read existing entries to array2. */
ret = pw2kt_process_kt2ar(state2);
if (ret != 0) {
return ADS_ERROR_KRB5(ret);
}
len1 = talloc_array_length(state2->array1);
len2 = talloc_array_length(state2->array2);
if (keytabptr->sync_kvno) {
goto sync_kvno;
}
/* copy existing entries VNO -1, -2, -3, -4 to VNO -11, -12, -13, -14 */
for (j = 0; j < len2; j++) {
krb5_keytab_entry e = state2->array2[j];
/* vno type is 'krb5_kvno' which is 'unsigned int' */
if (e.vno != -1 && e.vno != -2 && e.vno != -3 && e.vno != -4) {
DBG_WARNING("Unexpected keytab entry with VNO = %d (it "
"should be -1, -2, -3, -4) in %s\n",
e.vno,
keytabptr->keytab);
continue;
}
e.vno = state2->array2[j].vno - 10;
ret = samba_krb5_kt_add_entry(state2->context,
state2->keytab,
&e);
if (ret != 0) {
return ADS_ERROR_KRB5(ret);
}
}
/* remove all old entries (they should have VNO -1, -2, -3, -4) */
for (j = 0; j < len2; j++) {
krb5_keytab_entry e = state2->array2[j];
if (e.vno != -1 && e.vno != -2 && e.vno != -3 && e.vno != -4) {
DBG_WARNING("Unexpected keytab entry with VNO = %d (it "
"should be -1, -2, -3, -4) in %s\n",
e.vno,
keytabptr->keytab);
}
ret = samba_krb5_kt_remove_entry(state2->context,
state2->keytab,
&state2->array2[j]);
if (ret != 0) {
D_WARNING("Failed to remove keytab entry from %s\n",
keytabptr->keytab);
ret = 0; /* Be fault tolerant */
}
}
/* add new entries with VNO -1, -2, -3, -4 */
for (i = 0; i < len1; i++) {
ret = samba_krb5_kt_add_entry(state2->context,
state2->keytab,
&state2->array1[i]);
if (ret != 0) {
return ADS_ERROR_KRB5(ret);
}
}
/* remove entries with VNO -11, -12, -13, -14 */
for (j = 0; j < len2; j++) {
krb5_keytab_entry e = state2->array2[j];
e.vno = state2->array2[j].vno - 10;
ret = samba_krb5_kt_remove_entry(state2->context,
state2->keytab,
&e);
if (ret != 0) {
D_WARNING("Failed to remove keytab entry from %s\n",
keytabptr->keytab);
ret = 0; /* Be fault tolerant */
}
}
ret = krb5_kt_close(state2->context, state2->keytab);
return ADS_ERROR_KRB5(ret);
sync_kvno:
index_array1 = talloc_zero_array(state2, size_t, len1);
index_array2 = talloc_zero_array(state2, size_t, len2);
if (index_array1 == NULL || index_array2 == NULL) {
return ADS_ERROR_KRB5(ENOMEM);
}
/*
* Mark entries that are present in both arrays.
* These will not be added or removed.
*/
for (i = 0; i < len1; i++) {
for (j = 0; j < len2; j++) {
krb5_keytab_entry e2 = state2->array2[j];
if (smb_krb5_kt_compare(
state2->context,
&state2->array1[i],
e2.principal,
e2.vno,
KRB5_KEY_TYPE(KRB5_KT_KEY(&e2))
))
{
index_array1[i] = 1;
index_array2[j] = 1;
}
}
}
/* First add the new entries to the keytab.*/
for (i = 0; i < len1; i++) {
if (index_array1[i] == 0) {
ret = samba_krb5_kt_add_entry(state2->context,
state2->keytab,
&state2->array1[i]);
if (ret != 0) {
return ADS_ERROR_KRB5(ret);
}
}
}
/* Now, remove the old entries from the keytab. */
for (j = 0; j < len2; j++) {
if (index_array2[j] == 0) {
ret = samba_krb5_kt_remove_entry(state2->context,
state2->keytab,
&state2->array2[j]);
if (ret != 0) {
D_WARNING("Failed to remove keytab entry from "
"%s\n",
keytabptr->keytab);
ret = 0; /* Be fault tolerant */
}
}
}
ret = krb5_kt_close(state2->context, state2->keytab);
return ADS_ERROR_KRB5(ret);
}
static ADS_STATUS pw2kt_get_dc_info(struct pw2kt_state *state)
{
ADS_STATUS status;
LDAPMessage *res = NULL;
int count;
bool ok;
TALLOC_CTX *tmp_ctx = talloc_stackframe();
ADS_STRUCT *ads = ads_init(
tmp_ctx, lp_realm(), lp_workgroup(), NULL, ADS_SASL_SIGN);
if (ads == NULL) {
DBG_ERR("ads_init() failed\n");
TALLOC_FREE(tmp_ctx);
return ADS_ERROR(LDAP_NO_MEMORY);
}
status = ads_connect_machine(ads);
if (!ADS_ERR_OK(status)) {
DBG_ERR("Failed to refresh keytab, ads_connect() returned %s\n",
ads_errstr(status));
TALLOC_FREE(tmp_ctx);
return status;
}
status = ads_find_machine_acct(ads, &res, lp_netbios_name());
if (!ADS_ERR_OK(status)) {
TALLOC_FREE(tmp_ctx);
return status;
}
count = ads_count_replies(ads, res);
if (count != 1) {
status = ADS_ERROR(LDAP_NO_SUCH_OBJECT);
ads_msgfree(ads, res);
TALLOC_FREE(tmp_ctx);
return status;
}
if (state->sync_etypes) {
ok = ads_pull_uint32(ads,
res,
"msDS-SupportedEncryptionTypes",
&state->ad_etypes);
if (!ok) {
DBG_WARNING("Failed to determine encryption types.\n");
ads_msgfree(ads, res);
TALLOC_FREE(tmp_ctx);
return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
}
}
if (state->sync_kvno) {
uint32_t kvno = -1;
ok = ads_pull_uint32(ads, res, "msDS-KeyVersionNumber", &kvno);
if (!ok) {
DBG_WARNING("Failed to determine the system's kvno.\n");
ads_msgfree(ads, res);
TALLOC_FREE(tmp_ctx);
return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
}
state->ad_kvno = (krb5_kvno) kvno;
}
if (state->sync_spns) {
state->ad_spn_array = ads_pull_strings(ads,
state,
res,
"servicePrincipalName",
&state->ad_num_spns);
if (state->ad_spn_array == NULL) {
DBG_WARNING("Failed to determine SPNs.\n");
ads_msgfree(ads, res);
TALLOC_FREE(tmp_ctx);
return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
}
}
ads_msgfree(ads, res);
TALLOC_FREE(tmp_ctx);
return status;
}
static bool pw2kt_default_keytab_name(char *name_str, size_t name_size)
{
char keytab_str[MAX_KEYTAB_NAME_LEN] = {0};
const char *keytab_name = NULL;
krb5_context context = 0;
krb5_error_code ret;
switch (lp_kerberos_method()) {
case KERBEROS_VERIFY_SYSTEM_KEYTAB:
case KERBEROS_VERIFY_SECRETS_AND_KEYTAB:
ret = smb_krb5_init_context_common(&context);
if (ret) {
DBG_ERR("kerberos init context failed (%s)\n",
error_message(ret));
return false;
}
ret = krb5_kt_default_name(context,
keytab_str,
sizeof(keytab_str) - 2);
krb5_free_context(context);
if (ret != 0) {
DBG_WARNING("Failed to get default keytab name\n");
return false;
}
if (strncmp(keytab_str, "WRFILE:", 7) == 0) {
keytab_name = keytab_str + 7;
} else if (strncmp(keytab_str, "FILE:", 5) == 0) {
keytab_name = keytab_str + 5;
} else {
keytab_name = keytab_str;
}
break;
case KERBEROS_VERIFY_DEDICATED_KEYTAB:
keytab_name = lp_dedicated_keytab_file();
break;
default:
DBG_NOTICE("'kerberos method' is 'secrets only' but "
"'sync machine password to keytab' is not set "
"==> no keytab will be generated.\n");
return false;
}
if (keytab_name == NULL || keytab_name[0] == '\0') {
DBG_ERR("Invalid keytab name\n");
return false;
}
if (strlen(keytab_name) + 1 > name_size) {
DBG_ERR("Too long keytab name\n");
return false;
}
(void)strncpy(name_str, keytab_name, name_size);
return true;
}
NTSTATUS sync_pw2keytabs(void)
{
TALLOC_CTX *frame = talloc_stackframe();
const struct loadparm_substitution *lp_sub =
loadparm_s3_global_substitution();
struct pw2kt_state *state = NULL;
const char **line = NULL;
const char **lp_ptr = NULL;
const char *pwsync_script = NULL;
NTSTATUS status_nt;
ADS_STATUS status_ads;
int i;
DBG_DEBUG("Syncing machine password from secrets to keytabs.\n");
if (lp_server_role() != ROLE_DOMAIN_MEMBER) {
TALLOC_FREE(frame);
return NT_STATUS_OK; /* nothing todo */
}
state = talloc_zero(frame, struct pw2kt_state);
if (state == NULL) {
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
lp_ptr = lp_sync_machine_password_to_keytab();
if (lp_ptr == NULL) {
char name[MAX_KEYTAB_NAME_LEN] = {0};
bool ok = pw2kt_default_keytab_name(name, sizeof(name));
if (!ok) {
TALLOC_FREE(frame);
DBG_WARNING("No default keytab name.\n");
return NT_STATUS_OK; /* nothing todo */
}
status_ads = pw2kt_default_cfg(name, state);
if (!ADS_ERR_OK(status_ads)) {
DBG_WARNING("Cannot create default configuration.\n");
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
goto params_ready;
}
if ((*lp_ptr != NULL) && strequal_m(*lp_ptr, "disabled")) {
DBG_DEBUG("'sync machine password to keytab' is explicitly disabled.\n");
return NT_STATUS_OK;
}
line = lp_ptr;
while (*line) {
DBG_DEBUG("Scanning line: %s\n", *line);
status_ads = pw2kt_scan_line(*line, state);
if (!ADS_ERR_OK(status_ads)) {
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
line++;
}
params_ready:
if (state->sync_etypes || state->sync_kvno || state->sync_spns) {
status_ads = pw2kt_get_dc_info(state);
if (!ADS_ERR_OK(status_ads)) {
DBG_WARNING("cannot read from DC\n");
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
} else {
DBG_DEBUG("No 'sync_etypes', 'sync_kvno' and 'sync_spns' in "
"parameter 'sync machine password to keytab' => "
"no need to talk to DC.\n");
}
if (!secrets_init()) {
DBG_WARNING("secrets_init failed\n");
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
status_nt = secrets_fetch_or_upgrade_domain_info(lp_workgroup(),
frame,
&state->info);
if (!NT_STATUS_IS_OK(status_nt)) {
DBG_WARNING("secrets_fetch_or_upgrade_domain_info(%s) - %s\n",
lp_workgroup(),
nt_errstr(status_nt));
TALLOC_FREE(frame);
return status_nt;
}
for (i = 0; i < state->num_keytabs; i++) {
status_ads = pw2kt_process_keytab(state, &state->keytabs[i]);
if (!ADS_ERR_OK(status_ads)) {
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
}
pwsync_script = lp_sync_machine_password_script(frame, lp_sub);
if (pwsync_script != NULL && pwsync_script[0] != '\0') {
int ret;
DBG_DEBUG("Running script: '%s'\n.", pwsync_script);
ret = smbrun(pwsync_script, NULL, NULL);
if (ret != 0) {
DBG_ERR("Script '%s' failed with: %d.\n",
pwsync_script,
ret);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
}
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
static krb5_error_code ads_keytab_open(krb5_context context,
krb5_keytab *keytab)
{
char keytab_str[MAX_KEYTAB_NAME_LEN] = {0};
const char *keytab_name = NULL;
krb5_error_code ret = 0;
switch (lp_kerberos_method()) {
case KERBEROS_VERIFY_SYSTEM_KEYTAB:
case KERBEROS_VERIFY_SECRETS_AND_KEYTAB:
ret = krb5_kt_default_name(context,
keytab_str,
sizeof(keytab_str) - 2);
if (ret != 0) {
DBG_WARNING("Failed to get default keytab name\n");
goto out;
}
keytab_name = keytab_str;
break;
case KERBEROS_VERIFY_DEDICATED_KEYTAB:
keytab_name = lp_dedicated_keytab_file();
break;
default:
DBG_ERR("Invalid kerberos method set (%d)\n",
lp_kerberos_method());
ret = KRB5_KT_BADNAME;
goto out;
}
if (keytab_name == NULL || keytab_name[0] == '\0') {
DBG_ERR("Invalid keytab name\n");
ret = KRB5_KT_BADNAME;
goto out;
}
ret = smb_krb5_kt_open(context, keytab_name, true, keytab);
if (ret != 0) {
DBG_WARNING("smb_krb5_kt_open failed (%s)\n",
error_message(ret));
goto out;
}
out:
return ret;
}
/**********************************************************************
Flushes all entries from the system keytab.
***********************************************************************/
int ads_keytab_flush(ADS_STRUCT *ads)
{
krb5_error_code ret = 0;
krb5_context context = NULL;
krb5_keytab keytab = NULL;
ADS_STATUS aderr;
ret = smb_krb5_init_context_common(&context);
if (ret) {
DBG_ERR("kerberos init context failed (%s)\n",
error_message(ret));
return ret;
}
ret = ads_keytab_open(context, &keytab);
if (ret != 0) {
goto out;
}
/* Seek and delete all old keytab entries */
ret = smb_krb5_kt_seek_and_delete_old_entries(context,
keytab,
false, /* keep_old_kvno */
-1,
false, /* enctype_only */
ENCTYPE_NULL,
NULL,
NULL,
true); /* flush */
if (ret) {
goto out;
}
aderr = ads_clear_service_principal_names(ads, lp_netbios_name());
if (!ADS_ERR_OK(aderr)) {
DEBUG(1, (__location__ ": Error while clearing service "
"principal listings in LDAP.\n"));
ret = -1;
goto out;
}
out:
if (keytab) {
krb5_kt_close(context, keytab);
}
if (context) {
krb5_free_context(context);
}
return ret;
}
#endif /* HAVE_ADS */
/**********************************************************************
List system keytab.
***********************************************************************/
int ads_keytab_list(const char *keytab_name)
{
krb5_error_code ret = 0;
krb5_context context = NULL;
krb5_keytab keytab = NULL;
krb5_kt_cursor cursor;
krb5_keytab_entry kt_entry;
ZERO_STRUCT(kt_entry);
ZERO_STRUCT(cursor);
ret = smb_krb5_init_context_common(&context);
if (ret) {
DBG_ERR("kerberos init context failed (%s)\n",
error_message(ret));
return ret;
}
if (keytab_name == NULL) {
#ifdef HAVE_ADS
ret = ads_keytab_open(context, &keytab);
#else
ret = ENOENT;
#endif
} else {
ret = smb_krb5_kt_open(context, keytab_name, False, &keytab);
}
if (ret) {
DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
error_message(ret)));
goto out;
}
ret = krb5_kt_start_seq_get(context, keytab, &cursor);
if (ret) {
ZERO_STRUCT(cursor);
goto out;
}
printf("Vno Type Principal\n");
while (samba_krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) ==
0)
{
char *princ_s = NULL;
char *etype_s = NULL;
krb5_enctype enctype = 0;
ret = smb_krb5_unparse_name(talloc_tos(), context,
kt_entry.principal, &princ_s);
if (ret) {
goto out;
}
enctype = smb_krb5_kt_get_enctype_from_entry(&kt_entry);
ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
if (ret &&
(asprintf(&etype_s, "UNKNOWN: %d", enctype) == -1)) {
TALLOC_FREE(princ_s);
goto out;
}
printf("%3d %-43s %s\n", kt_entry.vno, etype_s, princ_s);
TALLOC_FREE(princ_s);
SAFE_FREE(etype_s);
ret = smb_krb5_kt_free_entry(context, &kt_entry);
if (ret) {
goto out;
}
}
ret = krb5_kt_end_seq_get(context, keytab, &cursor);
if (ret) {
goto out;
}
/* Ensure we don't double free. */
ZERO_STRUCT(kt_entry);
ZERO_STRUCT(cursor);
out:
if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
smb_krb5_kt_free_entry(context, &kt_entry);
}
if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
krb5_kt_end_seq_get(context, keytab, &cursor);
}
if (keytab) {
krb5_kt_close(context, keytab);
}
if (context) {
krb5_free_context(context);
}
return ret;
}
#endif /* HAVE_KRB5 */