1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-23 17:34:34 +03:00
samba-mirror/source3/passdb/machine_account_secrets.c
Andreas Schneider 13d98c8fa6 s3:passdb: Fix memory leak caused by recursion of get_global_sam_sid()
Direct leak of 68 byte(s) in 1 object(s) allocated from:
    #0 0x7f4f39cdc03f in malloc (/lib64/libasan.so.8+0xdc03f) (BuildId: 3e1694ad218c99a8b1b69231666a27df63cf19d0)
    #1 0x7f4f36fbe427 in malloc_ ../../source3/lib/util_malloc.c:38
    #2 0x7f4f394b5e19 in pdb_generate_sam_sid ../../source3/passdb/machine_sid.c:90
    #3 0x7f4f394b5e19 in get_global_sam_sid ../../source3/passdb/machine_sid.c:211
    #4 0x7f4f394af366 in secrets_store_domain_sid ../../source3/passdb/machine_account_secrets.c:143
    #5 0x7f4f394b5eb5 in pdb_generate_sam_sid ../../source3/passdb/machine_sid.c:110
    #6 0x7f4f394b5eb5 in get_global_sam_sid ../../source3/passdb/machine_sid.c:211
    #7 0x7f4f394af366 in secrets_store_domain_sid ../../source3/passdb/machine_account_secrets.c:143
    #8 0x557a1f11d62c in net_setlocalsid ../../source3/utils/net.c:416
    #9 0x557a1f1c9972 in net_run_function ../../source3/utils/net_util.c:464
    #10 0x557a1f121129 in main ../../source3/utils/net.c:1372
    #11 0x7f4f34c281af in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58

Signed-off-by: Andreas Schneider <asn@samba.org>
Reviewed-by: Volker Lendecke <vl@samba.org>

Autobuild-User(master): Andreas Schneider <asn@cryptomilk.org>
Autobuild-Date(master): Tue Jan 23 14:30:58 UTC 2024 on atb-devel-224
2024-01-23 14:30:58 +00:00

2081 lines
53 KiB
C

/*
Unix SMB/CIFS implementation.
Copyright (C) Andrew Tridgell 1992-2001
Copyright (C) Andrew Bartlett 2002
Copyright (C) Rafal Szczesniak 2002
Copyright (C) Tim Potter 2001
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/>.
*/
/* the Samba secrets database stores any generated, private information
such as the local SID and machine trust password */
#include "includes.h"
#include "passdb.h"
#include "../libcli/auth/libcli_auth.h"
#include "secrets.h"
#include "dbwrap/dbwrap.h"
#include "../librpc/ndr/libndr.h"
#include "util_tdb.h"
#include "libcli/security/security.h"
#include "librpc/gen_ndr/libnet_join.h"
#include "librpc/gen_ndr/ndr_secrets.h"
#include "lib/crypto/crypto.h"
#include "lib/krb5_wrap/krb5_samba.h"
#include "lib/util/time_basic.h"
#include "../libds/common/flags.h"
#include "lib/util/string_wrappers.h"
#undef DBGC_CLASS
#define DBGC_CLASS DBGC_PASSDB
static char *domain_info_keystr(const char *domain);
static char *des_salt_key(const char *realm);
/**
* Form a key for fetching the domain sid
*
* @param domain domain name
*
* @return keystring
**/
static const char *domain_sid_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_DOMAIN_SID, domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
static const char *domain_guid_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_DOMAIN_GUID, domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
static const char *protect_ids_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_PROTECT_IDS, domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
/* N O T E: never use this outside of passdb modules that store the SID on their own */
bool secrets_mark_domain_protected(const char *domain)
{
bool ret;
ret = secrets_store(protect_ids_keystr(domain), "TRUE", 5);
if (!ret) {
DEBUG(0, ("Failed to protect the Domain IDs\n"));
}
return ret;
}
bool secrets_clear_domain_protection(const char *domain)
{
bool ret;
void *protection = secrets_fetch(protect_ids_keystr(domain), NULL);
if (protection) {
SAFE_FREE(protection);
ret = secrets_delete_entry(protect_ids_keystr(domain));
if (!ret) {
DEBUG(0, ("Failed to remove Domain IDs protection\n"));
}
return ret;
}
return true;
}
bool secrets_store_domain_sid(const char *domain, const struct dom_sid *sid)
{
char *protect_ids;
bool ret;
struct dom_sid clean_sid = { 0 };
protect_ids = secrets_fetch(protect_ids_keystr(domain), NULL);
if (protect_ids) {
if (strncmp(protect_ids, "TRUE", 4)) {
DEBUG(0, ("Refusing to store a Domain SID, "
"it has been marked as protected!\n"));
SAFE_FREE(protect_ids);
return false;
}
}
SAFE_FREE(protect_ids);
/*
* use a copy to prevent uninitialized memory from being carried over
* to the tdb
*/
sid_copy(&clean_sid, sid);
ret = secrets_store(domain_sid_keystr(domain),
&clean_sid,
sizeof(struct dom_sid));
/* Force a re-query */
if (ret) {
/*
* Do not call get_global_domain_sid() here, or we will call it
* recursively.
*/
reset_global_sam_sid();
}
return ret;
}
bool secrets_fetch_domain_sid(const char *domain, struct dom_sid *sid)
{
struct dom_sid *dyn_sid;
size_t size = 0;
dyn_sid = (struct dom_sid *)secrets_fetch(domain_sid_keystr(domain), &size);
if (dyn_sid == NULL)
return False;
if (size != sizeof(struct dom_sid)) {
SAFE_FREE(dyn_sid);
return False;
}
*sid = *dyn_sid;
SAFE_FREE(dyn_sid);
return True;
}
bool secrets_store_domain_guid(const char *domain, const struct GUID *guid)
{
char *protect_ids;
const char *key;
protect_ids = secrets_fetch(protect_ids_keystr(domain), NULL);
if (protect_ids) {
if (strncmp(protect_ids, "TRUE", 4)) {
DEBUG(0, ("Refusing to store a Domain SID, "
"it has been marked as protected!\n"));
SAFE_FREE(protect_ids);
return false;
}
}
SAFE_FREE(protect_ids);
key = domain_guid_keystr(domain);
return secrets_store(key, guid, sizeof(struct GUID));
}
bool secrets_fetch_domain_guid(const char *domain, struct GUID *guid)
{
struct GUID *dyn_guid;
const char *key;
size_t size = 0;
struct GUID new_guid;
key = domain_guid_keystr(domain);
dyn_guid = (struct GUID *)secrets_fetch(key, &size);
if (!dyn_guid) {
if (lp_server_role() == ROLE_DOMAIN_PDC ||
lp_server_role() == ROLE_IPA_DC) {
new_guid = GUID_random();
if (!secrets_store_domain_guid(domain, &new_guid))
return False;
dyn_guid = (struct GUID *)secrets_fetch(key, &size);
}
if (dyn_guid == NULL) {
return False;
}
}
if (size != sizeof(struct GUID)) {
DEBUG(1,("UUID size %d is wrong!\n", (int)size));
SAFE_FREE(dyn_guid);
return False;
}
*guid = *dyn_guid;
SAFE_FREE(dyn_guid);
return True;
}
/**
* Form a key for fetching the machine trust account sec channel type
*
* @param domain domain name
*
* @return keystring
**/
static const char *machine_sec_channel_type_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_MACHINE_SEC_CHANNEL_TYPE,
domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
/**
* Form a key for fetching the machine trust account last change time
*
* @param domain domain name
*
* @return keystring
**/
static const char *machine_last_change_time_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_MACHINE_LAST_CHANGE_TIME,
domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
/**
* Form a key for fetching the machine previous trust account password
*
* @param domain domain name
*
* @return keystring
**/
static const char *machine_prev_password_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_MACHINE_PASSWORD_PREV, domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
/**
* Form a key for fetching the machine trust account password
*
* @param domain domain name
*
* @return keystring
**/
static const char *machine_password_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_MACHINE_PASSWORD, domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
/**
* Form a key for fetching the machine trust account password
*
* @param domain domain name
*
* @return stored password's key
**/
static const char *trust_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_MACHINE_ACCT_PASS, domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
/************************************************************************
Routine to get the default secure channel type for trust accounts
************************************************************************/
enum netr_SchannelType get_default_sec_channel(void)
{
if (IS_DC) {
return SEC_CHAN_BDC;
} else {
return SEC_CHAN_WKSTA;
}
}
/************************************************************************
Routine to get the trust account password for a domain.
This only tries to get the legacy hashed version of the password.
The user of this function must have locked the trust password file using
the above secrets_lock_trust_account_password().
************************************************************************/
bool secrets_fetch_trust_account_password_legacy(const char *domain,
uint8_t ret_pwd[16],
time_t *pass_last_set_time,
enum netr_SchannelType *channel)
{
struct machine_acct_pass *pass;
size_t size = 0;
if (!(pass = (struct machine_acct_pass *)secrets_fetch(
trust_keystr(domain), &size))) {
DEBUG(5, ("secrets_fetch failed!\n"));
return False;
}
if (size != sizeof(*pass)) {
DEBUG(0, ("secrets were of incorrect size!\n"));
BURN_FREE(pass, size);
return False;
}
if (pass_last_set_time) {
*pass_last_set_time = pass->mod_time;
}
memcpy(ret_pwd, pass->hash, 16);
if (channel) {
*channel = get_default_sec_channel();
}
BURN_FREE(pass, size);
return True;
}
/************************************************************************
Routine to delete all information related to the domain joined machine.
************************************************************************/
bool secrets_delete_machine_password_ex(const char *domain, const char *realm)
{
const char *tmpkey = NULL;
bool ok;
tmpkey = domain_info_keystr(domain);
ok = secrets_delete(tmpkey);
if (!ok) {
return false;
}
if (realm != NULL) {
tmpkey = des_salt_key(domain);
ok = secrets_delete(tmpkey);
if (!ok) {
return false;
}
}
tmpkey = domain_guid_keystr(domain);
ok = secrets_delete(tmpkey);
if (!ok) {
return false;
}
tmpkey = machine_prev_password_keystr(domain);
ok = secrets_delete(tmpkey);
if (!ok) {
return false;
}
tmpkey = machine_password_keystr(domain);
ok = secrets_delete(tmpkey);
if (!ok) {
return false;
}
tmpkey = machine_sec_channel_type_keystr(domain);
ok = secrets_delete(tmpkey);
if (!ok) {
return false;
}
tmpkey = machine_last_change_time_keystr(domain);
ok = secrets_delete(tmpkey);
if (!ok) {
return false;
}
tmpkey = domain_sid_keystr(domain);
ok = secrets_delete(tmpkey);
if (!ok) {
return false;
}
return true;
}
/************************************************************************
Routine to delete the domain sid
************************************************************************/
bool secrets_delete_domain_sid(const char *domain)
{
return secrets_delete_entry(domain_sid_keystr(domain));
}
/************************************************************************
Set the machine trust account password, the old pw and last change
time, domain SID and salting principals based on values passed in
(added to support the secrets_tdb_sync module on secrets.ldb)
************************************************************************/
bool secrets_store_machine_pw_sync(const char *pass, const char *oldpass, const char *domain,
const char *realm,
const char *salting_principal, uint32_t supported_enc_types,
const struct dom_sid *domain_sid, uint32_t last_change_time,
uint32_t secure_channel_type,
bool delete_join)
{
bool ret;
uint8_t last_change_time_store[4];
TALLOC_CTX *frame = talloc_stackframe();
uint8_t sec_channel_bytes[4];
if (delete_join) {
secrets_delete_machine_password_ex(domain, realm);
TALLOC_FREE(frame);
return true;
}
ret = secrets_store(machine_password_keystr(domain), pass, strlen(pass)+1);
if (!ret) {
TALLOC_FREE(frame);
return ret;
}
if (oldpass) {
ret = secrets_store(machine_prev_password_keystr(domain), oldpass, strlen(oldpass)+1);
} else {
ret = secrets_delete(machine_prev_password_keystr(domain));
}
if (!ret) {
TALLOC_FREE(frame);
return ret;
}
if (secure_channel_type == 0) {
/* We delete this and instead have the read code fall back to
* a default based on server role, as our caller can't specify
* this with any more certainty */
ret = secrets_delete(machine_sec_channel_type_keystr(domain));
if (!ret) {
TALLOC_FREE(frame);
return ret;
}
} else {
SIVAL(&sec_channel_bytes, 0, secure_channel_type);
ret = secrets_store(machine_sec_channel_type_keystr(domain),
&sec_channel_bytes, sizeof(sec_channel_bytes));
if (!ret) {
TALLOC_FREE(frame);
return ret;
}
}
SIVAL(&last_change_time_store, 0, last_change_time);
ret = secrets_store(machine_last_change_time_keystr(domain),
&last_change_time_store, sizeof(last_change_time));
if (!ret) {
TALLOC_FREE(frame);
return ret;
}
ret = secrets_store_domain_sid(domain, domain_sid);
if (!ret) {
TALLOC_FREE(frame);
return ret;
}
if (realm != NULL) {
char *key = des_salt_key(realm);
if (salting_principal != NULL) {
ret = secrets_store(key,
salting_principal,
strlen(salting_principal)+1);
} else {
ret = secrets_delete(key);
}
}
TALLOC_FREE(frame);
return ret;
}
/************************************************************************
Return the standard DES salt key
************************************************************************/
char* kerberos_standard_des_salt( void )
{
fstring salt;
fstr_sprintf( salt, "host/%s.%s@", lp_netbios_name(), lp_realm() );
(void)strlower_m( salt );
fstrcat( salt, lp_realm() );
return SMB_STRDUP( salt );
}
/************************************************************************
************************************************************************/
static char *des_salt_key(const char *realm)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/DES/%s",
SECRETS_SALTING_PRINCIPAL,
realm);
SMB_ASSERT(keystr != NULL);
return keystr;
}
/************************************************************************
************************************************************************/
bool kerberos_secrets_store_des_salt( const char* salt )
{
char* key;
bool ret;
key = des_salt_key(lp_realm());
if (key == NULL) {
DEBUG(0,("kerberos_secrets_store_des_salt: failed to generate key!\n"));
return False;
}
if ( !salt ) {
DEBUG(8,("kerberos_secrets_store_des_salt: deleting salt\n"));
secrets_delete_entry( key );
return True;
}
DEBUG(3,("kerberos_secrets_store_des_salt: Storing salt \"%s\"\n", salt));
ret = secrets_store( key, salt, strlen(salt)+1 );
TALLOC_FREE(key);
return ret;
}
/************************************************************************
************************************************************************/
static
char* kerberos_secrets_fetch_des_salt( void )
{
char *salt, *key;
key = des_salt_key(lp_realm());
if (key == NULL) {
DEBUG(0,("kerberos_secrets_fetch_des_salt: failed to generate key!\n"));
return NULL;
}
salt = (char*)secrets_fetch( key, NULL );
TALLOC_FREE(key);
return salt;
}
/************************************************************************
Routine to get the salting principal for this service.
Caller must free if return is not null.
************************************************************************/
char *kerberos_secrets_fetch_salt_princ(void)
{
char *salt_princ_s;
/* lookup new key first */
salt_princ_s = kerberos_secrets_fetch_des_salt();
if (salt_princ_s == NULL) {
/* fall back to host/machine.realm@REALM */
salt_princ_s = kerberos_standard_des_salt();
}
return salt_princ_s;
}
/************************************************************************
Routine to fetch the previous plaintext machine account password for a realm
the password is assumed to be a null terminated ascii string.
************************************************************************/
char *secrets_fetch_prev_machine_password(const char *domain)
{
return (char *)secrets_fetch(machine_prev_password_keystr(domain), NULL);
}
/************************************************************************
Routine to fetch the last change time of the machine account password
for a realm
************************************************************************/
time_t secrets_fetch_pass_last_set_time(const char *domain)
{
uint32_t *last_set_time;
time_t pass_last_set_time;
last_set_time = secrets_fetch(machine_last_change_time_keystr(domain),
NULL);
if (last_set_time) {
pass_last_set_time = IVAL(last_set_time,0);
SAFE_FREE(last_set_time);
} else {
pass_last_set_time = 0;
}
return pass_last_set_time;
}
/************************************************************************
Routine to fetch the plaintext machine account password for a realm
the password is assumed to be a null terminated ascii string.
************************************************************************/
char *secrets_fetch_machine_password(const char *domain,
time_t *pass_last_set_time,
enum netr_SchannelType *channel)
{
char *ret;
ret = (char *)secrets_fetch(machine_password_keystr(domain), NULL);
if (pass_last_set_time) {
*pass_last_set_time = secrets_fetch_pass_last_set_time(domain);
}
if (channel) {
size_t size;
uint32_t *channel_type;
channel_type = (unsigned int *)secrets_fetch(machine_sec_channel_type_keystr(domain), &size);
if (channel_type) {
*channel = IVAL(channel_type,0);
SAFE_FREE(channel_type);
} else {
*channel = get_default_sec_channel();
}
}
return ret;
}
static int password_nt_hash_destructor(struct secrets_domain_info1_password *pw)
{
ZERO_STRUCT(pw->nt_hash);
return 0;
}
static int setup_password_zeroing(struct secrets_domain_info1_password *pw)
{
if (pw != NULL) {
size_t i;
talloc_keep_secret(pw->cleartext_blob.data);
talloc_set_destructor(pw, password_nt_hash_destructor);
for (i = 0; i < pw->num_keys; i++) {
talloc_keep_secret(pw->keys[i].value.data);
}
}
return 0;
}
static char *domain_info_keystr(const char *domain)
{
char *keystr;
keystr = talloc_asprintf_strupper_m(talloc_tos(), "%s/%s",
SECRETS_MACHINE_DOMAIN_INFO,
domain);
SMB_ASSERT(keystr != NULL);
return keystr;
}
/************************************************************************
Routine to get account password to trusted domain
************************************************************************/
static NTSTATUS secrets_fetch_domain_info1_by_key(const char *key,
TALLOC_CTX *mem_ctx,
struct secrets_domain_info1 **_info1)
{
struct secrets_domain_infoB sdib = { .version = 0, };
enum ndr_err_code ndr_err;
/* unpacking structures */
DATA_BLOB blob;
/* fetching trusted domain password structure */
blob.data = (uint8_t *)secrets_fetch(key, &blob.length);
if (blob.data == NULL) {
DBG_NOTICE("secrets_fetch failed!\n");
return NT_STATUS_OBJECT_NAME_NOT_FOUND;
}
/* unpack trusted domain password */
ndr_err = ndr_pull_struct_blob(&blob, mem_ctx, &sdib,
(ndr_pull_flags_fn_t)ndr_pull_secrets_domain_infoB);
BURN_FREE(blob.data, blob.length);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
DBG_ERR("ndr_pull_struct_blob failed - %s!\n",
ndr_errstr(ndr_err));
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
if (sdib.info.info1->next_change != NULL) {
setup_password_zeroing(sdib.info.info1->next_change->password);
}
setup_password_zeroing(sdib.info.info1->password);
setup_password_zeroing(sdib.info.info1->old_password);
setup_password_zeroing(sdib.info.info1->older_password);
if (sdib.version != SECRETS_DOMAIN_INFO_VERSION_1) {
DBG_ERR("sdib.version = %u\n", (unsigned)sdib.version);
return NT_STATUS_INTERNAL_DB_CORRUPTION;
}
*_info1 = sdib.info.info1;
return NT_STATUS_OK;;
}
static NTSTATUS secrets_fetch_domain_info(const char *domain,
TALLOC_CTX *mem_ctx,
struct secrets_domain_info1 **pinfo)
{
char *key = domain_info_keystr(domain);
return secrets_fetch_domain_info1_by_key(key, mem_ctx, pinfo);
}
void secrets_debug_domain_info(int lvl, const struct secrets_domain_info1 *info1,
const char *name)
{
struct secrets_domain_infoB sdib = {
.version = SECRETS_DOMAIN_INFO_VERSION_1,
};
sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
NDR_PRINT_DEBUG_LEVEL(lvl, secrets_domain_infoB, &sdib);
}
char *secrets_domain_info_string(TALLOC_CTX *mem_ctx, const struct secrets_domain_info1 *info1,
const char *name, bool include_secrets)
{
TALLOC_CTX *frame = talloc_stackframe();
struct secrets_domain_infoB sdib = {
.version = SECRETS_DOMAIN_INFO_VERSION_1,
};
struct ndr_print *ndr = NULL;
char *ret = NULL;
sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
ndr = talloc_zero(frame, struct ndr_print);
if (ndr == NULL) {
TALLOC_FREE(frame);
return NULL;
}
ndr->private_data = talloc_strdup(ndr, "");
if (ndr->private_data == NULL) {
TALLOC_FREE(frame);
return NULL;
}
ndr->print = ndr_print_string_helper;
ndr->depth = 1;
ndr->print_secrets = include_secrets;
ndr_print_secrets_domain_infoB(ndr, name, &sdib);
ret = talloc_steal(mem_ctx, (char *)ndr->private_data);
TALLOC_FREE(frame);
return ret;
}
static NTSTATUS secrets_store_domain_info1_by_key(const char *key,
const struct secrets_domain_info1 *info1)
{
struct secrets_domain_infoB sdib = {
.version = SECRETS_DOMAIN_INFO_VERSION_1,
};
/* packing structures */
DATA_BLOB blob;
enum ndr_err_code ndr_err;
bool ok;
sdib.info.info1 = discard_const_p(struct secrets_domain_info1, info1);
ndr_err = ndr_push_struct_blob(&blob, talloc_tos(), &sdib,
(ndr_push_flags_fn_t)ndr_push_secrets_domain_infoB);
if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
return ndr_map_error2ntstatus(ndr_err);
}
ok = secrets_store(key, blob.data, blob.length);
data_blob_clear_free(&blob);
if (!ok) {
return NT_STATUS_INTERNAL_DB_ERROR;
}
return NT_STATUS_OK;
}
static NTSTATUS secrets_store_domain_info(const struct secrets_domain_info1 *info,
bool upgrade)
{
TALLOC_CTX *frame = talloc_stackframe();
const char *domain = info->domain_info.name.string;
const char *realm = info->domain_info.dns_domain.string;
char *key = domain_info_keystr(domain);
struct db_context *db = NULL;
struct timeval last_change_tv;
const DATA_BLOB *cleartext_blob = NULL;
DATA_BLOB pw_blob = data_blob_null;
DATA_BLOB old_pw_blob = data_blob_null;
const char *pw = NULL;
const char *old_pw = NULL;
bool ok;
NTSTATUS status;
int ret;
int role = lp_server_role();
switch (info->secure_channel_type) {
case SEC_CHAN_WKSTA:
case SEC_CHAN_BDC:
if (!upgrade && role >= ROLE_ACTIVE_DIRECTORY_DC) {
DBG_ERR("AD_DC not supported for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
break;
default:
DBG_ERR("SEC_CHAN_* not supported for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
db = secrets_db_ctx();
ret = dbwrap_transaction_start(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_start() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
ok = secrets_clear_domain_protection(domain);
if (!ok) {
DBG_ERR("secrets_clear_domain_protection(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
ok = secrets_delete_machine_password_ex(domain, realm);
if (!ok) {
DBG_ERR("secrets_delete_machine_password_ex(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
status = secrets_store_domain_info1_by_key(key, info);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_store_domain_info1_by_key() failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
/*
* We use info->password_last_change instead
* of info->password.change_time because
* we may want to defer the next change approach
* if the server rejected the change the last time,
* e.g. due to RefusePasswordChange=1.
*/
nttime_to_timeval(&last_change_tv, info->password_last_change);
cleartext_blob = &info->password->cleartext_blob;
ok = convert_string_talloc(frame, CH_UTF16MUNGED, CH_UNIX,
cleartext_blob->data,
cleartext_blob->length,
(void **)&pw_blob.data,
&pw_blob.length);
if (!ok) {
status = NT_STATUS_UNMAPPABLE_CHARACTER;
if (errno == ENOMEM) {
status = NT_STATUS_NO_MEMORY;
}
DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
"failed for pw of %s - %s\n",
domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
pw = (const char *)pw_blob.data;
if (info->old_password != NULL) {
cleartext_blob = &info->old_password->cleartext_blob;
ok = convert_string_talloc(frame, CH_UTF16MUNGED, CH_UNIX,
cleartext_blob->data,
cleartext_blob->length,
(void **)&old_pw_blob.data,
&old_pw_blob.length);
if (!ok) {
status = NT_STATUS_UNMAPPABLE_CHARACTER;
if (errno == ENOMEM) {
status = NT_STATUS_NO_MEMORY;
}
DBG_ERR("convert_string_talloc(CH_UTF16MUNGED, CH_UNIX) "
"failed for old_pw of %s - %s\n",
domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
data_blob_clear_free(&pw_blob);
TALLOC_FREE(frame);
return status;
}
old_pw = (const char *)old_pw_blob.data;
}
ok = secrets_store_machine_pw_sync(pw, old_pw,
domain, realm,
info->salt_principal,
info->supported_enc_types,
info->domain_info.sid,
last_change_tv.tv_sec,
info->secure_channel_type,
false); /* delete_join */
data_blob_clear_free(&pw_blob);
data_blob_clear_free(&old_pw_blob);
if (!ok) {
DBG_ERR("secrets_store_machine_pw_sync(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
if (!GUID_all_zero(&info->domain_info.domain_guid)) {
ok = secrets_store_domain_guid(domain,
&info->domain_info.domain_guid);
if (!ok) {
DBG_ERR("secrets_store_domain_guid(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
}
ok = secrets_mark_domain_protected(domain);
if (!ok) {
DBG_ERR("secrets_mark_domain_protected(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
ret = dbwrap_transaction_commit(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
static int secrets_domain_info_kerberos_keys(struct secrets_domain_info1_password *p,
const char *salt_principal)
{
#ifdef HAVE_ADS
krb5_error_code krb5_ret;
krb5_context krb5_ctx = NULL;
DATA_BLOB cleartext_utf8_b = data_blob_null;
krb5_data cleartext_utf8;
krb5_data salt;
krb5_keyblock key;
DATA_BLOB aes_256_b = data_blob_null;
DATA_BLOB aes_128_b = data_blob_null;
bool ok;
#endif /* HAVE_ADS */
DATA_BLOB arc4_b = data_blob_null;
const uint16_t max_keys = 4;
struct secrets_domain_info1_kerberos_key *keys = NULL;
uint16_t idx = 0;
char *salt_data = NULL;
/*
* We calculate:
* ENCTYPE_AES256_CTS_HMAC_SHA1_96
* ENCTYPE_AES128_CTS_HMAC_SHA1_96
* ENCTYPE_ARCFOUR_HMAC
* ENCTYPE_DES_CBC_MD5
*
* We don't include ENCTYPE_DES_CBC_CRC
* as W2008R2 also doesn't store it anymore.
*
* Note we store all enctypes we support,
* including the weak encryption types,
* but that's no problem as we also
* store the cleartext password anyway.
*
* Which values are then used to construct
* a keytab is configured at runtime and the
* configuration of msDS-SupportedEncryptionTypes.
*
* If we don't have kerberos support or no
* salt, we only generate an entry for arcfour-hmac-md5.
*/
keys = talloc_zero_array(p,
struct secrets_domain_info1_kerberos_key,
max_keys);
if (keys == NULL) {
return ENOMEM;
}
arc4_b = data_blob_talloc(keys,
p->nt_hash.hash,
sizeof(p->nt_hash.hash));
if (arc4_b.data == NULL) {
DBG_ERR("data_blob_talloc failed for arcfour-hmac-md5.\n");
TALLOC_FREE(keys);
return ENOMEM;
}
talloc_keep_secret(arc4_b.data);
#ifdef HAVE_ADS
if (salt_principal == NULL) {
goto no_kerberos;
}
krb5_ret = smb_krb5_init_context_common(&krb5_ctx);
if (krb5_ret != 0) {
DBG_ERR("kerberos init context failed (%s)\n",
error_message(krb5_ret));
TALLOC_FREE(keys);
return krb5_ret;
}
krb5_ret = smb_krb5_salt_principal2data(krb5_ctx, salt_principal,
p, &salt_data);
if (krb5_ret != 0) {
DBG_ERR("smb_krb5_salt_principal2data(%s) failed: %s\n",
salt_principal,
smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
krb5_free_context(krb5_ctx);
TALLOC_FREE(keys);
return krb5_ret;
}
salt = (krb5_data) {
.data = discard_const(salt_data),
.length = strlen(salt_data),
};
ok = convert_string_talloc(keys, CH_UTF16MUNGED, CH_UTF8,
p->cleartext_blob.data,
p->cleartext_blob.length,
(void **)&cleartext_utf8_b.data,
&cleartext_utf8_b.length);
if (!ok) {
if (errno != 0) {
krb5_ret = errno;
} else {
krb5_ret = EINVAL;
}
krb5_free_context(krb5_ctx);
TALLOC_FREE(keys);
return krb5_ret;
}
talloc_keep_secret(cleartext_utf8_b.data);
cleartext_utf8.data = (void *)cleartext_utf8_b.data;
cleartext_utf8.length = cleartext_utf8_b.length;
krb5_ret = smb_krb5_create_key_from_string(krb5_ctx,
NULL,
&salt,
&cleartext_utf8,
ENCTYPE_AES256_CTS_HMAC_SHA1_96,
&key);
if (krb5_ret != 0) {
DBG_ERR("generation of a aes256-cts-hmac-sha1-96 key failed: %s\n",
smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
krb5_free_context(krb5_ctx);
TALLOC_FREE(keys);
TALLOC_FREE(salt_data);
return krb5_ret;
}
aes_256_b = data_blob_talloc(keys,
KRB5_KEY_DATA(&key),
KRB5_KEY_LENGTH(&key));
krb5_free_keyblock_contents(krb5_ctx, &key);
if (aes_256_b.data == NULL) {
DBG_ERR("data_blob_talloc failed for aes-256.\n");
krb5_free_context(krb5_ctx);
TALLOC_FREE(keys);
TALLOC_FREE(salt_data);
return ENOMEM;
}
talloc_keep_secret(aes_256_b.data);
krb5_ret = smb_krb5_create_key_from_string(krb5_ctx,
NULL,
&salt,
&cleartext_utf8,
ENCTYPE_AES128_CTS_HMAC_SHA1_96,
&key);
if (krb5_ret != 0) {
DBG_ERR("generation of a aes128-cts-hmac-sha1-96 key failed: %s\n",
smb_get_krb5_error_message(krb5_ctx, krb5_ret, keys));
krb5_free_context(krb5_ctx);
TALLOC_FREE(keys);
TALLOC_FREE(salt_data);
return krb5_ret;
}
aes_128_b = data_blob_talloc(keys,
KRB5_KEY_DATA(&key),
KRB5_KEY_LENGTH(&key));
krb5_free_keyblock_contents(krb5_ctx, &key);
if (aes_128_b.data == NULL) {
DBG_ERR("data_blob_talloc failed for aes-128.\n");
krb5_free_context(krb5_ctx);
TALLOC_FREE(keys);
TALLOC_FREE(salt_data);
return ENOMEM;
}
talloc_keep_secret(aes_128_b.data);
krb5_free_context(krb5_ctx);
no_kerberos:
if (aes_256_b.length != 0) {
keys[idx].keytype = ENCTYPE_AES256_CTS_HMAC_SHA1_96;
keys[idx].iteration_count = 4096;
keys[idx].value = aes_256_b;
idx += 1;
}
if (aes_128_b.length != 0) {
keys[idx].keytype = ENCTYPE_AES128_CTS_HMAC_SHA1_96;
keys[idx].iteration_count = 4096;
keys[idx].value = aes_128_b;
idx += 1;
}
#endif /* HAVE_ADS */
keys[idx].keytype = ENCTYPE_ARCFOUR_HMAC;
keys[idx].iteration_count = 4096;
keys[idx].value = arc4_b;
idx += 1;
p->salt_data = salt_data;
p->default_iteration_count = 4096;
p->num_keys = idx;
p->keys = keys;
return 0;
}
static NTSTATUS secrets_domain_info_password_create(TALLOC_CTX *mem_ctx,
const char *cleartext_unix,
const char *salt_principal,
NTTIME change_time,
const char *change_server,
struct secrets_domain_info1_password **_p)
{
struct secrets_domain_info1_password *p = NULL;
bool ok;
size_t len;
int ret;
if (change_server == NULL) {
return NT_STATUS_INVALID_PARAMETER_MIX;
}
p = talloc_zero(mem_ctx, struct secrets_domain_info1_password);
if (p == NULL) {
return NT_STATUS_NO_MEMORY;
}
p->change_time = change_time;
p->change_server = talloc_strdup(p, change_server);
if (p->change_server == NULL) {
TALLOC_FREE(p);
return NT_STATUS_NO_MEMORY;
}
len = strlen(cleartext_unix);
ok = convert_string_talloc(p, CH_UNIX, CH_UTF16,
cleartext_unix, len,
(void **)&p->cleartext_blob.data,
&p->cleartext_blob.length);
if (!ok) {
NTSTATUS status = NT_STATUS_UNMAPPABLE_CHARACTER;
if (errno == ENOMEM) {
status = NT_STATUS_NO_MEMORY;
}
TALLOC_FREE(p);
return status;
}
talloc_keep_secret(p->cleartext_blob.data);
mdfour(p->nt_hash.hash,
p->cleartext_blob.data,
p->cleartext_blob.length);
talloc_set_destructor(p, password_nt_hash_destructor);
ret = secrets_domain_info_kerberos_keys(p, salt_principal);
if (ret != 0) {
NTSTATUS status = krb5_to_nt_status(ret);
TALLOC_FREE(p);
return status;
}
*_p = p;
return NT_STATUS_OK;
}
NTSTATUS secrets_fetch_or_upgrade_domain_info(const char *domain,
TALLOC_CTX *mem_ctx,
struct secrets_domain_info1 **pinfo)
{
TALLOC_CTX *frame = NULL;
struct secrets_domain_info1 *old = NULL;
struct secrets_domain_info1 *info = NULL;
const char *dns_domain = NULL;
const char *server = NULL;
struct db_context *db = NULL;
time_t last_set_time;
NTTIME last_set_nt;
enum netr_SchannelType channel;
char *pw = NULL;
char *old_pw = NULL;
struct dom_sid domain_sid;
struct GUID domain_guid;
bool ok;
NTSTATUS status;
int ret;
ok = strequal(domain, lp_workgroup());
if (ok) {
dns_domain = lp_dnsdomain();
if (dns_domain != NULL && dns_domain[0] == '\0') {
dns_domain = NULL;
}
}
last_set_time = secrets_fetch_pass_last_set_time(domain);
if (last_set_time == 0) {
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
unix_to_nt_time(&last_set_nt, last_set_time);
frame = talloc_stackframe();
status = secrets_fetch_domain_info(domain, frame, &old);
if (NT_STATUS_IS_OK(status)) {
if (old->password_last_change >= last_set_nt) {
*pinfo = talloc_move(mem_ctx, &old);
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
TALLOC_FREE(old);
}
info = talloc_zero(frame, struct secrets_domain_info1);
if (info == NULL) {
DBG_ERR("talloc_zero failed\n");
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
db = secrets_db_ctx();
ret = dbwrap_transaction_start(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_start() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
pw = secrets_fetch_machine_password(domain,
&last_set_time,
&channel);
if (pw == NULL) {
DBG_ERR("secrets_fetch_machine_password(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
unix_to_nt_time(&last_set_nt, last_set_time);
old_pw = secrets_fetch_prev_machine_password(domain);
ok = secrets_fetch_domain_sid(domain, &domain_sid);
if (!ok) {
DBG_ERR("secrets_fetch_domain_sid(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
BURN_FREE_STR(old_pw);
BURN_FREE_STR(pw);
TALLOC_FREE(frame);
return NT_STATUS_CANT_ACCESS_DOMAIN_INFO;
}
ok = secrets_fetch_domain_guid(domain, &domain_guid);
if (!ok) {
domain_guid = GUID_zero();
}
info->computer_name = lp_netbios_name();
info->account_name = talloc_asprintf(frame, "%s$", info->computer_name);
if (info->account_name == NULL) {
DBG_ERR("talloc_asprintf(%s$) failed\n", info->computer_name);
dbwrap_transaction_cancel(db);
BURN_FREE_STR(old_pw);
BURN_FREE_STR(pw);
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
info->secure_channel_type = channel;
info->domain_info.name.string = domain;
info->domain_info.dns_domain.string = dns_domain;
info->domain_info.dns_forest.string = dns_domain;
info->domain_info.domain_guid = domain_guid;
info->domain_info.sid = &domain_sid;
info->trust_flags = NETR_TRUST_FLAG_PRIMARY;
info->trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
if (dns_domain != NULL) {
/*
* We just assume all AD domains are
* NETR_TRUST_FLAG_NATIVE these days.
*
* This isn't used anyway for now.
*/
info->trust_flags |= NETR_TRUST_FLAG_NATIVE;
info->trust_type = LSA_TRUST_TYPE_UPLEVEL;
server = info->domain_info.dns_domain.string;
} else {
info->trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
server = talloc_asprintf(info,
"%s#%02X",
domain,
NBT_NAME_PDC);
if (server == NULL) {
DBG_ERR("talloc_asprintf(%s#%02X) failed\n",
domain, NBT_NAME_PDC);
dbwrap_transaction_cancel(db);
BURN_FREE_STR(pw);
BURN_FREE_STR(old_pw);
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
}
info->trust_attributes = LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL;
info->join_time = 0;
/*
* We don't have enough information about the configured
* enctypes.
*/
info->supported_enc_types = 0;
info->salt_principal = NULL;
if (info->trust_type == LSA_TRUST_TYPE_UPLEVEL) {
char *p = NULL;
p = kerberos_secrets_fetch_salt_princ();
if (p == NULL) {
dbwrap_transaction_cancel(db);
BURN_FREE_STR(old_pw);
BURN_FREE_STR(pw);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_ERROR;
}
info->salt_principal = talloc_strdup(info, p);
SAFE_FREE(p);
if (info->salt_principal == NULL) {
dbwrap_transaction_cancel(db);
BURN_FREE_STR(pw);
BURN_FREE_STR(old_pw);
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
}
info->password_last_change = last_set_nt;
info->password_changes = 1;
info->next_change = NULL;
status = secrets_domain_info_password_create(info,
pw,
info->salt_principal,
last_set_nt, server,
&info->password);
BURN_FREE_STR(pw);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_domain_info_password_create(pw) failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
BURN_FREE_STR(old_pw);
TALLOC_FREE(frame);
return status;
}
/*
* After a join we don't have old passwords.
*/
if (old_pw != NULL) {
status = secrets_domain_info_password_create(info,
old_pw,
info->salt_principal,
0, server,
&info->old_password);
BURN_FREE_STR(old_pw);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_domain_info_password_create(old) failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
info->password_changes += 1;
} else {
info->old_password = NULL;
}
info->older_password = NULL;
secrets_debug_domain_info(DBGLVL_INFO, info, "upgrade");
status = secrets_store_domain_info(info, true /* upgrade */);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_store_domain_info() failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
/*
* We now reparse it.
*/
status = secrets_fetch_domain_info(domain, frame, &info);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_fetch_domain_info() failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
ret = dbwrap_transaction_commit(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
*pinfo = talloc_move(mem_ctx, &info);
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
NTSTATUS secrets_store_JoinCtx(const struct libnet_JoinCtx *r)
{
TALLOC_CTX *frame = talloc_stackframe();
struct secrets_domain_info1 *old = NULL;
struct secrets_domain_info1 *info = NULL;
struct db_context *db = NULL;
struct timeval tv = timeval_current();
NTTIME now = timeval_to_nttime(&tv);
const char *domain = r->out.netbios_domain_name;
NTSTATUS status;
int ret;
info = talloc_zero(frame, struct secrets_domain_info1);
if (info == NULL) {
DBG_ERR("talloc_zero failed\n");
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
info->computer_name = r->in.machine_name;
info->account_name = r->out.account_name;
info->secure_channel_type = r->in.secure_channel_type;
info->domain_info.name.string =
r->out.netbios_domain_name;
info->domain_info.dns_domain.string =
r->out.dns_domain_name;
info->domain_info.dns_forest.string =
r->out.forest_name;
info->domain_info.domain_guid = r->out.domain_guid;
info->domain_info.sid = r->out.domain_sid;
info->trust_flags = NETR_TRUST_FLAG_PRIMARY;
info->trust_flags |= NETR_TRUST_FLAG_OUTBOUND;
if (r->out.domain_is_ad) {
/*
* We just assume all AD domains are
* NETR_TRUST_FLAG_NATIVE these days.
*
* This isn't used anyway for now.
*/
info->trust_flags |= NETR_TRUST_FLAG_NATIVE;
info->trust_type = LSA_TRUST_TYPE_UPLEVEL;
} else {
info->trust_type = LSA_TRUST_TYPE_DOWNLEVEL;
}
info->trust_attributes = LSA_TRUST_ATTRIBUTE_TREAT_AS_EXTERNAL;
info->join_time = now;
info->supported_enc_types = r->out.set_encryption_types;
info->salt_principal = r->out.krb5_salt;
if (info->salt_principal == NULL && r->out.domain_is_ad) {
char *p = NULL;
ret = smb_krb5_salt_principal_str(info->domain_info.dns_domain.string,
info->account_name,
NULL /* userPrincipalName */,
UF_WORKSTATION_TRUST_ACCOUNT,
info, &p);
if (ret != 0) {
status = krb5_to_nt_status(ret);
DBG_ERR("smb_krb5_salt_principal() failed "
"for %s - %s\n", domain, nt_errstr(status));
TALLOC_FREE(frame);
return status;
}
info->salt_principal = p;
}
info->password_last_change = now;
info->password_changes = 1;
info->next_change = NULL;
status = secrets_domain_info_password_create(info,
r->in.machine_password,
info->salt_principal,
now, r->in.dc_name,
&info->password);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_domain_info_password_create(pw) failed "
"for %s - %s\n", domain, nt_errstr(status));
TALLOC_FREE(frame);
return status;
}
db = secrets_db_ctx();
ret = dbwrap_transaction_start(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_start() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
status = secrets_fetch_or_upgrade_domain_info(domain, frame, &old);
if (NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) {
DBG_DEBUG("no old join for domain(%s) available\n",
domain);
old = NULL;
} else if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_fetch_or_upgrade_domain_info(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
/*
* We reuse values from an old join, so that
* we still accept already granted kerberos tickets.
*/
if (old != NULL) {
info->old_password = old->password;
info->older_password = old->old_password;
}
secrets_debug_domain_info(DBGLVL_INFO, info, "join");
status = secrets_store_domain_info(info, false /* upgrade */);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_store_domain_info() failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
ret = dbwrap_transaction_commit(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
NTSTATUS secrets_prepare_password_change(const char *domain, const char *dcname,
const char *cleartext_unix,
TALLOC_CTX *mem_ctx,
struct secrets_domain_info1 **pinfo,
struct secrets_domain_info1_change **pprev)
{
TALLOC_CTX *frame = talloc_stackframe();
struct db_context *db = NULL;
struct secrets_domain_info1 *info = NULL;
struct secrets_domain_info1_change *prev = NULL;
struct secrets_domain_info1_change *next = NULL;
struct timeval tv = timeval_current();
NTTIME now = timeval_to_nttime(&tv);
NTSTATUS status;
int ret;
db = secrets_db_ctx();
ret = dbwrap_transaction_start(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_start() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
status = secrets_fetch_or_upgrade_domain_info(domain, frame, &info);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_fetch_or_upgrade_domain_info(%s) failed\n",
domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
prev = info->next_change;
info->next_change = NULL;
next = talloc_zero(frame, struct secrets_domain_info1_change);
if (next == NULL) {
DBG_ERR("talloc_zero failed\n");
TALLOC_FREE(frame);
return NT_STATUS_NO_MEMORY;
}
if (prev != NULL) {
*next = *prev;
} else {
status = secrets_domain_info_password_create(next,
cleartext_unix,
info->salt_principal,
now, dcname,
&next->password);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_domain_info_password_create(next) failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
}
next->local_status = NT_STATUS_OK;
next->remote_status = NT_STATUS_NOT_COMMITTED;
next->change_time = now;
next->change_server = dcname;
info->next_change = next;
secrets_debug_domain_info(DBGLVL_INFO, info, "prepare_change");
status = secrets_store_domain_info(info, false /* upgrade */);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_store_domain_info() failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
/*
* We now reparse it.
*/
status = secrets_fetch_domain_info(domain, frame, &info);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_fetch_domain_info(%s) failed\n", domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
ret = dbwrap_transaction_commit(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
*pinfo = talloc_move(mem_ctx, &info);
if (prev != NULL) {
*pprev = talloc_move(mem_ctx, &prev);
} else {
*pprev = NULL;
}
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
static NTSTATUS secrets_check_password_change(const struct secrets_domain_info1 *cookie,
TALLOC_CTX *mem_ctx,
struct secrets_domain_info1 **pstored)
{
const char *domain = cookie->domain_info.name.string;
struct secrets_domain_info1 *stored = NULL;
struct secrets_domain_info1_change *sn = NULL;
struct secrets_domain_info1_change *cn = NULL;
NTSTATUS status;
bool cmp;
if (cookie->next_change == NULL) {
DBG_ERR("cookie->next_change == NULL for %s.\n", domain);
return NT_STATUS_INTERNAL_ERROR;
}
if (cookie->next_change->password == NULL) {
DBG_ERR("cookie->next_change->password == NULL for %s.\n", domain);
return NT_STATUS_INTERNAL_ERROR;
}
if (cookie->password == NULL) {
DBG_ERR("cookie->password == NULL for %s.\n", domain);
return NT_STATUS_INTERNAL_ERROR;
}
/*
* Here we check that the given structure still contains the
* same secrets_domain_info1_change as currently stored.
*
* There's always a gap between secrets_prepare_password_change()
* and the callers of secrets_check_password_change().
*/
status = secrets_fetch_domain_info(domain, mem_ctx, &stored);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_fetch_domain_info(%s) failed\n", domain);
return status;
}
if (stored->next_change == NULL) {
/*
* We hit a race..., the administrator
* rejoined or something similar happened.
*/
DBG_ERR("stored->next_change == NULL for %s.\n", domain);
TALLOC_FREE(stored);
return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
}
if (stored->password_last_change != cookie->password_last_change) {
struct timeval store_tv;
struct timeval_buf store_buf;
struct timeval cookie_tv;
struct timeval_buf cookie_buf;
nttime_to_timeval(&store_tv, stored->password_last_change);
nttime_to_timeval(&cookie_tv, cookie->password_last_change);
DBG_ERR("password_last_change differs %s != %s for %s.\n",
timeval_str_buf(&store_tv, false, false, &store_buf),
timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
domain);
TALLOC_FREE(stored);
return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
}
sn = stored->next_change;
cn = cookie->next_change;
if (sn->change_time != cn->change_time) {
struct timeval store_tv;
struct timeval_buf store_buf;
struct timeval cookie_tv;
struct timeval_buf cookie_buf;
nttime_to_timeval(&store_tv, sn->change_time);
nttime_to_timeval(&cookie_tv, cn->change_time);
DBG_ERR("next change_time differs %s != %s for %s.\n",
timeval_str_buf(&store_tv, false, false, &store_buf),
timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
domain);
TALLOC_FREE(stored);
return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
}
if (sn->password->change_time != cn->password->change_time) {
struct timeval store_tv;
struct timeval_buf store_buf;
struct timeval cookie_tv;
struct timeval_buf cookie_buf;
nttime_to_timeval(&store_tv, sn->password->change_time);
nttime_to_timeval(&cookie_tv, cn->password->change_time);
DBG_ERR("next password.change_time differs %s != %s for %s.\n",
timeval_str_buf(&store_tv, false, false, &store_buf),
timeval_str_buf(&cookie_tv, false, false, &cookie_buf),
domain);
TALLOC_FREE(stored);
return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
}
cmp = mem_equal_const_time(sn->password->nt_hash.hash,
cn->password->nt_hash.hash,
16);
if (!cmp) {
DBG_ERR("next password.nt_hash differs for %s.\n",
domain);
TALLOC_FREE(stored);
return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
}
cmp = mem_equal_const_time(stored->password->nt_hash.hash,
cookie->password->nt_hash.hash,
16);
if (!cmp) {
DBG_ERR("password.nt_hash differs for %s.\n",
domain);
TALLOC_FREE(stored);
return NT_STATUS_NETWORK_CREDENTIAL_CONFLICT;
}
*pstored = stored;
return NT_STATUS_OK;
}
static NTSTATUS secrets_abort_password_change(const char *change_server,
NTSTATUS local_status,
NTSTATUS remote_status,
const struct secrets_domain_info1 *cookie,
bool defer)
{
const char *domain = cookie->domain_info.name.string;
TALLOC_CTX *frame = talloc_stackframe();
struct db_context *db = NULL;
struct secrets_domain_info1 *info = NULL;
const char *reason = defer ? "defer_change" : "failed_change";
struct timeval tv = timeval_current();
NTTIME now = timeval_to_nttime(&tv);
NTSTATUS status;
int ret;
db = secrets_db_ctx();
ret = dbwrap_transaction_start(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_start() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
/*
* secrets_check_password_change()
* checks that cookie->next_change
* is valid and the same as store
* in the database.
*/
status = secrets_check_password_change(cookie, frame, &info);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_check_password_change(%s) failed\n", domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
/*
* Remember the last server and error.
*/
info->next_change->change_server = change_server;
info->next_change->change_time = now;
info->next_change->local_status = local_status;
info->next_change->remote_status = remote_status;
/*
* Make sure the next automatic change is deferred.
*/
if (defer) {
info->password_last_change = now;
}
secrets_debug_domain_info(DBGLVL_WARNING, info, reason);
status = secrets_store_domain_info(info, false /* upgrade */);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_store_domain_info() failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
ret = dbwrap_transaction_commit(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
TALLOC_FREE(frame);
return NT_STATUS_OK;
}
NTSTATUS secrets_failed_password_change(const char *change_server,
NTSTATUS local_status,
NTSTATUS remote_status,
const struct secrets_domain_info1 *cookie)
{
static const bool defer = false;
return secrets_abort_password_change(change_server,
local_status,
remote_status,
cookie, defer);
}
NTSTATUS secrets_defer_password_change(const char *change_server,
NTSTATUS local_status,
NTSTATUS remote_status,
const struct secrets_domain_info1 *cookie)
{
static const bool defer = true;
return secrets_abort_password_change(change_server,
local_status,
remote_status,
cookie, defer);
}
NTSTATUS secrets_finish_password_change(const char *change_server,
NTTIME change_time,
const struct secrets_domain_info1 *cookie)
{
const char *domain = cookie->domain_info.name.string;
TALLOC_CTX *frame = talloc_stackframe();
struct db_context *db = NULL;
struct secrets_domain_info1 *info = NULL;
struct secrets_domain_info1_change *nc = NULL;
NTSTATUS status;
int ret;
db = secrets_db_ctx();
ret = dbwrap_transaction_start(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_start() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
/*
* secrets_check_password_change() checks that cookie->next_change is
* valid and the same as store in the database.
*/
status = secrets_check_password_change(cookie, frame, &info);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_check_password_change(%s) failed\n", domain);
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
nc = info->next_change;
nc->password->change_server = change_server;
nc->password->change_time = change_time;
info->password_last_change = change_time;
info->password_changes += 1;
info->next_change = NULL;
info->older_password = info->old_password;
info->old_password = info->password;
info->password = nc->password;
secrets_debug_domain_info(DBGLVL_WARNING, info, "finish_change");
status = secrets_store_domain_info(info, false /* upgrade */);
if (!NT_STATUS_IS_OK(status)) {
DBG_ERR("secrets_store_domain_info() failed "
"for %s - %s\n", domain, nt_errstr(status));
dbwrap_transaction_cancel(db);
TALLOC_FREE(frame);
return status;
}
ret = dbwrap_transaction_commit(db);
if (ret != 0) {
DBG_ERR("dbwrap_transaction_commit() failed for %s\n",
domain);
TALLOC_FREE(frame);
return NT_STATUS_INTERNAL_DB_ERROR;
}
TALLOC_FREE(frame);
return NT_STATUS_OK;
}