mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
aac22d9eba
Signed-off-by: Stefan Metzmacher <metze@samba.org> Reviewed-by: Andreas Schneider <asn@samba.org> Autobuild-User(master): Stefan Metzmacher <metze@samba.org> Autobuild-Date(master): Mon Sep 30 17:03:55 UTC 2024 on atb-devel-224
2116 lines
54 KiB
C
2116 lines
54 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 = 3;
|
|
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
|
|
*
|
|
* We don't include ENCTYPE_DES_CBC_CRC
|
|
* and ENCTYPE_DES_CBC_MD5
|
|
* as they are no longer supported.
|
|
*
|
|
* 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,
|
|
NTSTATUS (*sync_pw2keytabs_fn)(void))
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (prev == NULL && sync_pw2keytabs_fn != NULL) {
|
|
status = sync_pw2keytabs_fn();
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("Sync of machine password failed.\n");
|
|
dbwrap_transaction_cancel(db);
|
|
TALLOC_FREE(frame);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
*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,
|
|
NTSTATUS (*sync_pw2keytabs_fn)(void))
|
|
{
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* For the clustered samba, it is important to have following order:
|
|
* 1. dbwrap_transaction_commit()
|
|
* 2. sync_pw2keytabs()
|
|
* Only this order ensures a correct behavior of
|
|
* the 'sync machine password script' that does:
|
|
* 'onnode all net ads keytab create'
|
|
*
|
|
* If we would call sync_pw2keytabs() before committing the changes to
|
|
* the secrets.tdb, it will not be updated on other nodes, so triggering
|
|
* 'net ads keytab create' will not see the new password yet.
|
|
*
|
|
* This applies also to secrets_prepare_password_change().
|
|
*/
|
|
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;
|
|
}
|
|
|
|
if (sync_pw2keytabs_fn != NULL) {
|
|
status = sync_pw2keytabs_fn();
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
DBG_ERR("Sync of machine password failed.\n");
|
|
TALLOC_FREE(frame);
|
|
return status;
|
|
}
|
|
}
|
|
|
|
TALLOC_FREE(frame);
|
|
return NT_STATUS_OK;
|
|
}
|