diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /libcli/auth | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'libcli/auth')
28 files changed, 12444 insertions, 0 deletions
diff --git a/libcli/auth/credentials.c b/libcli/auth/credentials.c new file mode 100644 index 0000000..a7f56e7 --- /dev/null +++ b/libcli/auth/credentials.c @@ -0,0 +1,1246 @@ +/* + Unix SMB/CIFS implementation. + + code to manipulate domain credentials + + Copyright (C) Andrew Tridgell 1997-2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/time.h" +#include "libcli/auth/libcli_auth.h" +#include "../libcli/security/dom_sid.h" +#include "lib/util/util_str_escape.h" + +#ifndef HAVE_GNUTLS_AES_CFB8 +#include "lib/crypto/aes.h" +#endif + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +bool netlogon_creds_is_random_challenge(const struct netr_Credential *challenge) +{ + /* + * If none of the first 5 bytes of the client challenge is unique, the + * server MUST fail session-key negotiation without further processing + * of the following steps. + */ + + if (challenge->data[1] == challenge->data[0] && + challenge->data[2] == challenge->data[0] && + challenge->data[3] == challenge->data[0] && + challenge->data[4] == challenge->data[0]) + { + return false; + } + + return true; +} + +void netlogon_creds_random_challenge(struct netr_Credential *challenge) +{ + ZERO_STRUCTP(challenge); + while (!netlogon_creds_is_random_challenge(challenge)) { + generate_random_buffer(challenge->data, sizeof(challenge->data)); + } +} + +static NTSTATUS netlogon_creds_step_crypt(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *in, + struct netr_Credential *out) +{ + NTSTATUS status; + int rc; + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + memcpy(out->data, in->data, sizeof(out->data)); + + status = netlogon_creds_aes_encrypt(creds, + out->data, + sizeof(out->data)); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + rc = des_crypt112(out->data, in->data, creds->session_key, SAMBA_GNUTLS_ENCRYPT); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + } + + return NT_STATUS_OK; +} + +/* + initialise the credentials state for old-style 64 bit session keys + + this call is made after the netr_ServerReqChallenge call +*/ +static NTSTATUS netlogon_creds_init_64bit(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password) +{ + uint32_t sum[2]; + uint8_t sum2[8]; + int rc; + + sum[0] = IVAL(client_challenge->data, 0) + IVAL(server_challenge->data, 0); + sum[1] = IVAL(client_challenge->data, 4) + IVAL(server_challenge->data, 4); + + SIVAL(sum2,0,sum[0]); + SIVAL(sum2,4,sum[1]); + + ZERO_ARRAY(creds->session_key); + + rc = des_crypt128(creds->session_key, sum2, machine_password->hash); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + return NT_STATUS_OK; +} + +/* + initialise the credentials state for ADS-style 128 bit session keys + + this call is made after the netr_ServerReqChallenge call +*/ +static NTSTATUS netlogon_creds_init_128bit(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password) +{ + uint8_t zero[4] = {0}; + uint8_t tmp[gnutls_hash_get_len(GNUTLS_MAC_MD5)]; + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + ZERO_ARRAY(creds->session_key); + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + + rc = gnutls_hash(hash_hnd, zero, sizeof(zero)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + rc = gnutls_hash(hash_hnd, client_challenge->data, 8); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + rc = gnutls_hash(hash_hnd, server_challenge->data, 8); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + + gnutls_hash_deinit(hash_hnd, tmp); + + /* This doesn't require HMAC MD5 RFC2104 as the hash is only 16 bytes */ + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + machine_password->hash, + sizeof(machine_password->hash), + tmp, + sizeof(tmp), + creds->session_key); + ZERO_ARRAY(tmp); + + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + + return NT_STATUS_OK; +} + +/* + initialise the credentials state for AES/HMAC-SHA256-style 128 bit session keys + + this call is made after the netr_ServerReqChallenge call +*/ +static NTSTATUS netlogon_creds_init_hmac_sha256(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password) +{ + gnutls_hmac_hd_t hmac_hnd = NULL; + uint8_t digest[gnutls_hash_get_len(GNUTLS_MAC_SHA256)]; + int rc; + + ZERO_ARRAY(creds->session_key); + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA256, + machine_password->hash, + sizeof(machine_password->hash)); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(hmac_hnd, + client_challenge->data, + 8); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(hmac_hnd, + server_challenge->data, + 8); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + gnutls_hmac_deinit(hmac_hnd, digest); + + memcpy(creds->session_key, digest, sizeof(creds->session_key)); + + ZERO_ARRAY(digest); + + return NT_STATUS_OK; +} + +static NTSTATUS netlogon_creds_first_step(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge) +{ + NTSTATUS status; + + status = netlogon_creds_step_crypt(creds, + client_challenge, + &creds->client); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = netlogon_creds_step_crypt(creds, + server_challenge, + &creds->server); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + creds->seed = creds->client; + + return NT_STATUS_OK; +} + +/* + step the credentials to the next element in the chain, updating the + current client and server credentials and the seed +*/ +static NTSTATUS netlogon_creds_step(struct netlogon_creds_CredentialState *creds) +{ + struct netr_Credential time_cred; + NTSTATUS status; + + DEBUG(5,("\tseed %08x:%08x\n", + IVAL(creds->seed.data, 0), IVAL(creds->seed.data, 4))); + + SIVAL(time_cred.data, 0, IVAL(creds->seed.data, 0) + creds->sequence); + SIVAL(time_cred.data, 4, IVAL(creds->seed.data, 4)); + + DEBUG(5,("\tseed+time %08x:%08x\n", IVAL(time_cred.data, 0), IVAL(time_cred.data, 4))); + + status = netlogon_creds_step_crypt(creds, + &time_cred, + &creds->client); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(5,("\tCLIENT %08x:%08x\n", + IVAL(creds->client.data, 0), IVAL(creds->client.data, 4))); + + SIVAL(time_cred.data, 0, IVAL(creds->seed.data, 0) + creds->sequence + 1); + SIVAL(time_cred.data, 4, IVAL(creds->seed.data, 4)); + + DEBUG(5,("\tseed+time+1 %08x:%08x\n", + IVAL(time_cred.data, 0), IVAL(time_cred.data, 4))); + + status = netlogon_creds_step_crypt(creds, &time_cred, &creds->server); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + DEBUG(5,("\tSERVER %08x:%08x\n", + IVAL(creds->server.data, 0), IVAL(creds->server.data, 4))); + + creds->seed = time_cred; + + return NT_STATUS_OK; +} + +/* + DES encrypt a 8 byte LMSessionKey buffer using the Netlogon session key +*/ +NTSTATUS netlogon_creds_des_encrypt_LMKey(struct netlogon_creds_CredentialState *creds, + struct netr_LMSessionKey *key) +{ + int rc; + struct netr_LMSessionKey tmp; + + rc = des_crypt56_gnutls(tmp.key, key->key, creds->session_key, SAMBA_GNUTLS_ENCRYPT); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + *key = tmp; + + return NT_STATUS_OK; +} + +/* + DES decrypt a 8 byte LMSessionKey buffer using the Netlogon session key +*/ +NTSTATUS netlogon_creds_des_decrypt_LMKey(struct netlogon_creds_CredentialState *creds, + struct netr_LMSessionKey *key) +{ + int rc; + struct netr_LMSessionKey tmp; + + rc = des_crypt56_gnutls(tmp.key, key->key, creds->session_key, SAMBA_GNUTLS_DECRYPT); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + *key = tmp; + + return NT_STATUS_OK; +} + +/* + DES encrypt a 16 byte password buffer using the session key +*/ +NTSTATUS netlogon_creds_des_encrypt(struct netlogon_creds_CredentialState *creds, + struct samr_Password *pass) +{ + struct samr_Password tmp; + int rc; + + rc = des_crypt112_16(tmp.hash, pass->hash, creds->session_key, SAMBA_GNUTLS_ENCRYPT); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + *pass = tmp; + + return NT_STATUS_OK; +} + +/* + DES decrypt a 16 byte password buffer using the session key +*/ +NTSTATUS netlogon_creds_des_decrypt(struct netlogon_creds_CredentialState *creds, + struct samr_Password *pass) +{ + struct samr_Password tmp; + int rc; + + rc = des_crypt112_16(tmp.hash, pass->hash, creds->session_key, SAMBA_GNUTLS_DECRYPT); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + *pass = tmp; + + return NT_STATUS_OK; +} + +/* + ARCFOUR encrypt/decrypt a password buffer using the session key +*/ +NTSTATUS netlogon_creds_arcfour_crypt(struct netlogon_creds_CredentialState *creds, + uint8_t *data, + size_t len) +{ + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t session_key = { + .data = creds->session_key, + .size = sizeof(creds->session_key), + }; + int rc; + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &session_key, + NULL); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + rc = gnutls_cipher_encrypt(cipher_hnd, + data, + len); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + return NT_STATUS_OK; +} + +/* + AES encrypt a password buffer using the session key +*/ +NTSTATUS netlogon_creds_aes_encrypt(struct netlogon_creds_CredentialState *creds, + uint8_t *data, + size_t len) +{ +#ifdef HAVE_GNUTLS_AES_CFB8 + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t key = { + .data = creds->session_key, + .size = sizeof(creds->session_key), + }; + uint32_t iv_size = + gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_CFB8); + uint8_t _iv[iv_size]; + gnutls_datum_t iv = { + .data = _iv, + .size = iv_size, + }; + int rc; + + ZERO_ARRAY(_iv); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_AES_128_CFB8, + &key, + &iv); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + rc = gnutls_cipher_encrypt(cipher_hnd, data, len); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + +#else /* NOT HAVE_GNUTLS_AES_CFB8 */ + AES_KEY key; + uint8_t iv[AES_BLOCK_SIZE] = {0}; + + AES_set_encrypt_key(creds->session_key, 128, &key); + + aes_cfb8_encrypt(data, data, len, &key, iv, AES_ENCRYPT); +#endif /* HAVE_GNUTLS_AES_CFB8 */ + + return NT_STATUS_OK; +} + +/* + AES decrypt a password buffer using the session key +*/ +NTSTATUS netlogon_creds_aes_decrypt(struct netlogon_creds_CredentialState *creds, uint8_t *data, size_t len) +{ +#ifdef HAVE_GNUTLS_AES_CFB8 + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t key = { + .data = creds->session_key, + .size = sizeof(creds->session_key), + }; + uint32_t iv_size = + gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_CFB8); + uint8_t _iv[iv_size]; + gnutls_datum_t iv = { + .data = _iv, + .size = iv_size, + }; + int rc; + + ZERO_ARRAY(_iv); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_AES_128_CFB8, + &key, + &iv); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + rc = gnutls_cipher_decrypt(cipher_hnd, data, len); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + +#else /* NOT HAVE_GNUTLS_AES_CFB8 */ + AES_KEY key; + uint8_t iv[AES_BLOCK_SIZE] = {0}; + + AES_set_encrypt_key(creds->session_key, 128, &key); + + aes_cfb8_encrypt(data, data, len, &key, iv, AES_DECRYPT); +#endif /* HAVE_GNUTLS_AES_CFB8 */ + + return NT_STATUS_OK; +} + +/***************************************************************** +The above functions are common to the client and server interface +next comes the client specific functions +******************************************************************/ + +/* + initialise the credentials chain and return the first client + credentials +*/ + +struct netlogon_creds_CredentialState *netlogon_creds_client_init(TALLOC_CTX *mem_ctx, + const char *client_account, + const char *client_computer_name, + uint16_t secure_channel_type, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password, + struct netr_Credential *initial_credential, + uint32_t negotiate_flags) +{ + struct netlogon_creds_CredentialState *creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState); + NTSTATUS status; + + if (!creds) { + return NULL; + } + + creds->sequence = time(NULL); + creds->negotiate_flags = negotiate_flags; + creds->secure_channel_type = secure_channel_type; + + creds->computer_name = talloc_strdup(creds, client_computer_name); + if (!creds->computer_name) { + talloc_free(creds); + return NULL; + } + creds->account_name = talloc_strdup(creds, client_account); + if (!creds->account_name) { + talloc_free(creds); + return NULL; + } + + dump_data_pw("Client chall", client_challenge->data, sizeof(client_challenge->data)); + dump_data_pw("Server chall", server_challenge->data, sizeof(server_challenge->data)); + dump_data_pw("Machine Pass", machine_password->hash, sizeof(machine_password->hash)); + + if (negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + status = netlogon_creds_init_hmac_sha256(creds, + client_challenge, + server_challenge, + machine_password); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return NULL; + } + } else if (negotiate_flags & NETLOGON_NEG_STRONG_KEYS) { + status = netlogon_creds_init_128bit(creds, + client_challenge, + server_challenge, + machine_password); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return NULL; + } + } else { + status = netlogon_creds_init_64bit(creds, + client_challenge, + server_challenge, + machine_password); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return NULL; + } + } + + status = netlogon_creds_first_step(creds, + client_challenge, + server_challenge); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return NULL; + } + + dump_data_pw("Session key", creds->session_key, 16); + dump_data_pw("Credential ", creds->client.data, 8); + + *initial_credential = creds->client; + return creds; +} + +/* + initialise the credentials structure with only a session key. The caller better know what they are doing! + */ + +struct netlogon_creds_CredentialState *netlogon_creds_client_init_session_key(TALLOC_CTX *mem_ctx, + const uint8_t session_key[16]) +{ + struct netlogon_creds_CredentialState *creds; + + creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState); + if (!creds) { + return NULL; + } + + memcpy(creds->session_key, session_key, 16); + + return creds; +} + +/* + step the credentials to the next element in the chain, updating the + current client and server credentials and the seed + + produce the next authenticator in the sequence ready to send to + the server +*/ +NTSTATUS +netlogon_creds_client_authenticator(struct netlogon_creds_CredentialState *creds, + struct netr_Authenticator *next) +{ + uint32_t t32n = (uint32_t)time(NULL); + NTSTATUS status; + + /* + * we always increment and ignore an overflow here + */ + creds->sequence += 2; + + if (t32n > creds->sequence) { + /* + * we may increment more + */ + creds->sequence = t32n; + } else { + uint32_t d = creds->sequence - t32n; + + if (d >= INT32_MAX) { + /* + * got an overflow of time_t vs. uint32_t + */ + creds->sequence = t32n; + } + } + + status = netlogon_creds_step(creds); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + next->cred = creds->client; + next->timestamp = creds->sequence; + + return NT_STATUS_OK; +} + +/* + check that a credentials reply from a server is correct +*/ +bool netlogon_creds_client_check(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *received_credentials) +{ + if (!received_credentials || + !mem_equal_const_time(received_credentials->data, creds->server.data, 8)) { + DEBUG(2,("credentials check failed\n")); + return false; + } + return true; +} + + +/***************************************************************** +The above functions are common to the client and server interface +next comes the server specific functions +******************************************************************/ + +/* + check that a credentials reply from a server is correct +*/ +static bool netlogon_creds_server_check_internal(const struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *received_credentials) +{ + if (!mem_equal_const_time(received_credentials->data, creds->client.data, 8)) { + DEBUG(2,("credentials check failed\n")); + dump_data_pw("client creds", creds->client.data, 8); + dump_data_pw("calc creds", received_credentials->data, 8); + return false; + } + return true; +} + +/* + initialise the credentials chain and return the first server + credentials +*/ +struct netlogon_creds_CredentialState *netlogon_creds_server_init(TALLOC_CTX *mem_ctx, + const char *client_account, + const char *client_computer_name, + uint16_t secure_channel_type, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password, + const struct netr_Credential *credentials_in, + struct netr_Credential *credentials_out, + uint32_t negotiate_flags) +{ + + struct netlogon_creds_CredentialState *creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState); + NTSTATUS status; + bool ok; + + if (!creds) { + return NULL; + } + + creds->negotiate_flags = negotiate_flags; + creds->secure_channel_type = secure_channel_type; + + dump_data_pw("Client chall", client_challenge->data, sizeof(client_challenge->data)); + dump_data_pw("Server chall", server_challenge->data, sizeof(server_challenge->data)); + dump_data_pw("Machine Pass", machine_password->hash, sizeof(machine_password->hash)); + + ok = netlogon_creds_is_random_challenge(client_challenge); + if (!ok) { + DBG_WARNING("CVE-2020-1472(ZeroLogon): " + "non-random client challenge rejected for " + "client_account[%s] client_computer_name[%s]\n", + log_escape(mem_ctx, client_account), + log_escape(mem_ctx, client_computer_name)); + dump_data(DBGLVL_WARNING, + client_challenge->data, + sizeof(client_challenge->data)); + talloc_free(creds); + return NULL; + } + + creds->computer_name = talloc_strdup(creds, client_computer_name); + if (!creds->computer_name) { + talloc_free(creds); + return NULL; + } + creds->account_name = talloc_strdup(creds, client_account); + if (!creds->account_name) { + talloc_free(creds); + return NULL; + } + + if (negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + status = netlogon_creds_init_hmac_sha256(creds, + client_challenge, + server_challenge, + machine_password); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return NULL; + } + } else if (negotiate_flags & NETLOGON_NEG_STRONG_KEYS) { + status = netlogon_creds_init_128bit(creds, + client_challenge, + server_challenge, + machine_password); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return NULL; + } + } else { + status = netlogon_creds_init_64bit(creds, + client_challenge, + server_challenge, + machine_password); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return NULL; + } + } + + status = netlogon_creds_first_step(creds, + client_challenge, + server_challenge); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return NULL; + } + + dump_data_pw("Session key", creds->session_key, 16); + dump_data_pw("Client Credential ", creds->client.data, 8); + dump_data_pw("Server Credential ", creds->server.data, 8); + + dump_data_pw("Credentials in", credentials_in->data, sizeof(credentials_in->data)); + + /* And before we leak information about the machine account + * password, check that they got the first go right */ + if (!netlogon_creds_server_check_internal(creds, credentials_in)) { + talloc_free(creds); + return NULL; + } + + *credentials_out = creds->server; + + dump_data_pw("Credentials out", credentials_out->data, sizeof(credentials_out->data)); + + return creds; +} + +NTSTATUS netlogon_creds_server_step_check(struct netlogon_creds_CredentialState *creds, + const struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator) +{ + NTSTATUS status; + + if (!received_authenticator || !return_authenticator) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (!creds) { + return NT_STATUS_ACCESS_DENIED; + } + + creds->sequence = received_authenticator->timestamp; + status = netlogon_creds_step(creds); + if (!NT_STATUS_IS_OK(status)) { + ZERO_STRUCTP(return_authenticator); + return status; + } + + if (netlogon_creds_server_check_internal(creds, &received_authenticator->cred)) { + return_authenticator->cred = creds->server; + return_authenticator->timestamp = 0; + return NT_STATUS_OK; + } else { + ZERO_STRUCTP(return_authenticator); + return NT_STATUS_ACCESS_DENIED; + } +} + +static NTSTATUS netlogon_creds_crypt_samlogon_validation(struct netlogon_creds_CredentialState *creds, + uint16_t validation_level, + union netr_Validation *validation, + bool do_encrypt) +{ + struct netr_SamBaseInfo *base = NULL; + NTSTATUS status; + + if (validation == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (validation_level) { + case 2: + if (validation->sam2) { + base = &validation->sam2->base; + } + break; + case 3: + if (validation->sam3) { + base = &validation->sam3->base; + } + break; + case 6: + if (validation->sam6) { + base = &validation->sam6->base; + } + break; + default: + /* If we can't find it, we can't very well decrypt it */ + return NT_STATUS_INVALID_INFO_CLASS; + } + + if (!base) { + return NT_STATUS_INVALID_INFO_CLASS; + } + + /* find and decyrpt the session keys, return in parameters above */ + if (validation_level == 6) { + /* they aren't encrypted! */ + } else if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + /* Don't crypt an all-zero key, it would give away the NETLOGON pipe session key */ + if (!all_zero(base->key.key, sizeof(base->key.key))) { + if (do_encrypt) { + status = netlogon_creds_aes_encrypt( + creds, + base->key.key, + sizeof(base->key.key)); + } else { + status = netlogon_creds_aes_decrypt( + creds, + base->key.key, + sizeof(base->key.key)); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (!all_zero(base->LMSessKey.key, + sizeof(base->LMSessKey.key))) { + if (do_encrypt) { + status = netlogon_creds_aes_encrypt( + creds, + base->LMSessKey.key, + sizeof(base->LMSessKey.key)); + } else { + status = netlogon_creds_aes_decrypt( + creds, + base->LMSessKey.key, + sizeof(base->LMSessKey.key)); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + /* Don't crypt an all-zero key, it would give away the NETLOGON pipe session key */ + if (!all_zero(base->key.key, sizeof(base->key.key))) { + status = netlogon_creds_arcfour_crypt(creds, + base->key.key, + sizeof(base->key.key)); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + if (!all_zero(base->LMSessKey.key, + sizeof(base->LMSessKey.key))) { + status = netlogon_creds_arcfour_crypt(creds, + base->LMSessKey.key, + sizeof(base->LMSessKey.key)); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } else { + /* Don't crypt an all-zero key, it would give away the NETLOGON pipe session key */ + if (!all_zero(base->LMSessKey.key, + sizeof(base->LMSessKey.key))) { + if (do_encrypt) { + status = netlogon_creds_des_encrypt_LMKey(creds, + &base->LMSessKey); + } else { + status = netlogon_creds_des_decrypt_LMKey(creds, + &base->LMSessKey); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } + + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_decrypt_samlogon_validation(struct netlogon_creds_CredentialState *creds, + uint16_t validation_level, + union netr_Validation *validation) +{ + return netlogon_creds_crypt_samlogon_validation(creds, + validation_level, + validation, + false); +} + +NTSTATUS netlogon_creds_encrypt_samlogon_validation(struct netlogon_creds_CredentialState *creds, + uint16_t validation_level, + union netr_Validation *validation) +{ + return netlogon_creds_crypt_samlogon_validation(creds, + validation_level, + validation, + true); +} + +static NTSTATUS netlogon_creds_crypt_samlogon_logon(struct netlogon_creds_CredentialState *creds, + enum netr_LogonInfoClass level, + union netr_LogonLevel *logon, + bool do_encrypt) +{ + NTSTATUS status; + + if (logon == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (level) { + case NetlogonInteractiveInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceInformation: + case NetlogonServiceTransitiveInformation: + if (logon->password == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + uint8_t *h; + + h = logon->password->lmpassword.hash; + if (!all_zero(h, 16)) { + if (do_encrypt) { + status = netlogon_creds_aes_encrypt( + creds, + h, + 16); + } else { + status = netlogon_creds_aes_decrypt( + creds, + h, + 16); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + h = logon->password->ntpassword.hash; + if (!all_zero(h, 16)) { + if (do_encrypt) { + status = netlogon_creds_aes_encrypt(creds, + h, + 16); + } else { + status = netlogon_creds_aes_decrypt(creds, + h, + 16); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + uint8_t *h; + + h = logon->password->lmpassword.hash; + if (!all_zero(h, 16)) { + status = netlogon_creds_arcfour_crypt(creds, + h, + 16); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + + h = logon->password->ntpassword.hash; + if (!all_zero(h, 16)) { + status = netlogon_creds_arcfour_crypt(creds, + h, + 16); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } else { + struct samr_Password *p; + + p = &logon->password->lmpassword; + if (!all_zero(p->hash, 16)) { + if (do_encrypt) { + status = netlogon_creds_des_encrypt(creds, p); + } else { + status = netlogon_creds_des_decrypt(creds, p); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + p = &logon->password->ntpassword; + if (!all_zero(p->hash, 16)) { + if (do_encrypt) { + status = netlogon_creds_des_encrypt(creds, p); + } else { + status = netlogon_creds_des_decrypt(creds, p); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } + } + break; + + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + break; + + case NetlogonGenericInformation: + if (logon->generic == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + if (do_encrypt) { + status = netlogon_creds_aes_encrypt( + creds, + logon->generic->data, + logon->generic->length); + } else { + status = netlogon_creds_aes_decrypt( + creds, + logon->generic->data, + logon->generic->length); + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else if (creds->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + status = netlogon_creds_arcfour_crypt(creds, + logon->generic->data, + logon->generic->length); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + /* Using DES to verify kerberos tickets makes no sense */ + } + break; + } + + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_decrypt_samlogon_logon(struct netlogon_creds_CredentialState *creds, + enum netr_LogonInfoClass level, + union netr_LogonLevel *logon) +{ + return netlogon_creds_crypt_samlogon_logon(creds, level, logon, false); +} + +NTSTATUS netlogon_creds_encrypt_samlogon_logon(struct netlogon_creds_CredentialState *creds, + enum netr_LogonInfoClass level, + union netr_LogonLevel *logon) +{ + return netlogon_creds_crypt_samlogon_logon(creds, level, logon, true); +} + +union netr_LogonLevel *netlogon_creds_shallow_copy_logon(TALLOC_CTX *mem_ctx, + enum netr_LogonInfoClass level, + const union netr_LogonLevel *in) +{ + union netr_LogonLevel *out; + + if (in == NULL) { + return NULL; + } + + out = talloc(mem_ctx, union netr_LogonLevel); + if (out == NULL) { + return NULL; + } + + *out = *in; + + switch (level) { + case NetlogonInteractiveInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceInformation: + case NetlogonServiceTransitiveInformation: + if (in->password == NULL) { + return out; + } + + out->password = talloc(out, struct netr_PasswordInfo); + if (out->password == NULL) { + talloc_free(out); + return NULL; + } + *out->password = *in->password; + + return out; + + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + break; + + case NetlogonGenericInformation: + if (in->generic == NULL) { + return out; + } + + out->generic = talloc(out, struct netr_GenericInfo); + if (out->generic == NULL) { + talloc_free(out); + return NULL; + } + *out->generic = *in->generic; + + if (in->generic->data == NULL) { + return out; + } + + if (in->generic->length == 0) { + return out; + } + + out->generic->data = talloc_memdup(out->generic, + in->generic->data, + in->generic->length); + if (out->generic->data == NULL) { + talloc_free(out); + return NULL; + } + + return out; + } + + return out; +} + +/* + copy a netlogon_creds_CredentialState struct +*/ + +struct netlogon_creds_CredentialState *netlogon_creds_copy( + TALLOC_CTX *mem_ctx, + const struct netlogon_creds_CredentialState *creds_in) +{ + struct netlogon_creds_CredentialState *creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState); + + if (!creds) { + return NULL; + } + + creds->sequence = creds_in->sequence; + creds->negotiate_flags = creds_in->negotiate_flags; + creds->secure_channel_type = creds_in->secure_channel_type; + + creds->computer_name = talloc_strdup(creds, creds_in->computer_name); + if (!creds->computer_name) { + talloc_free(creds); + return NULL; + } + creds->account_name = talloc_strdup(creds, creds_in->account_name); + if (!creds->account_name) { + talloc_free(creds); + return NULL; + } + + if (creds_in->sid) { + creds->sid = dom_sid_dup(creds, creds_in->sid); + if (!creds->sid) { + talloc_free(creds); + return NULL; + } + } + + memcpy(creds->session_key, creds_in->session_key, sizeof(creds->session_key)); + memcpy(creds->seed.data, creds_in->seed.data, sizeof(creds->seed.data)); + memcpy(creds->client.data, creds_in->client.data, sizeof(creds->client.data)); + memcpy(creds->server.data, creds_in->server.data, sizeof(creds->server.data)); + + return creds; +} diff --git a/libcli/auth/credentials.h b/libcli/auth/credentials.h new file mode 100644 index 0000000..7b8fac6 --- /dev/null +++ b/libcli/auth/credentials.h @@ -0,0 +1,70 @@ +/* + Unix SMB/CIFS implementation. + + code to manipulate domain credentials + + Copyright (C) Andrew Tridgell 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "librpc/gen_ndr/netlogon.h" + +/* The 7 here seems to be required to get Win2k not to downgrade us + to NT4. Actually, anything other than 1ff would seem to do... */ +#define NETLOGON_NEG_AUTH2_FLAGS 0x000701ff +/* + (NETLOGON_NEG_ACCOUNT_LOCKOUT | + NETLOGON_NEG_PERSISTENT_SAMREPL | + NETLOGON_NEG_ARCFOUR | + NETLOGON_NEG_PROMOTION_COUNT | + NETLOGON_NEG_CHANGELOG_BDC | + NETLOGON_NEG_FULL_SYNC_REPL | + NETLOGON_NEG_MULTIPLE_SIDS | + NETLOGON_NEG_REDO | + NETLOGON_NEG_PASSWORD_CHANGE_REFUSAL | + NETLOGON_NEG_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_PASSWORD_SET2 | + NETLOGON_NEG_GETDOMAININFO) +*/ +#define NETLOGON_NEG_DOMAIN_TRUST_ACCOUNT 0x2010b000 + +/* these are the flags that ADS clients use */ +/* + (NETLOGON_NEG_ACCOUNT_LOCKOUT | + NETLOGON_NEG_PERSISTENT_SAMREPL | + NETLOGON_NEG_ARCFOUR | + NETLOGON_NEG_PROMOTION_COUNT | + NETLOGON_NEG_CHANGELOG_BDC | + NETLOGON_NEG_FULL_SYNC_REPL | + NETLOGON_NEG_MULTIPLE_SIDS | + NETLOGON_NEG_REDO | + NETLOGON_NEG_PASSWORD_CHANGE_REFUSAL | + NETLOGON_NEG_SEND_PASSWORD_INFO_PDC | + NETLOGON_NEG_GENERIC_PASSTHROUGH | + NETLOGON_NEG_CONCURRENT_RPC | + NETLOGON_NEG_AVOID_ACCOUNT_DB_REPL | + NETLOGON_NEG_AVOID_SECURITYAUTH_DB_REPL | + NETLOGON_NEG_128BIT | + NETLOGON_NEG_TRANSITIVE_TRUSTS | + NETLOGON_NEG_DNS_DOMAIN_TRUSTS | + NETLOGON_NEG_PASSWORD_SET2 | + NETLOGON_NEG_GETDOMAININFO | + NETLOGON_NEG_CROSS_FOREST_TRUSTS | + NETLOGON_NEG_AUTHENTICATED_RPC_LSASS | + NETLOGON_NEG_SCHANNEL) +*/ + +#define NETLOGON_NEG_AUTH2_ADS_FLAGS (0x200fbffb | NETLOGON_NEG_ARCFOUR | NETLOGON_NEG_128BIT | NETLOGON_NEG_SCHANNEL) + diff --git a/libcli/auth/libcli_auth.h b/libcli/auth/libcli_auth.h new file mode 100644 index 0000000..c5c7a7b --- /dev/null +++ b/libcli/auth/libcli_auth.h @@ -0,0 +1,28 @@ +/* + samba -- Unix SMB/CIFS implementation. + + 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/>. +*/ +#ifndef __LIBCLI_AUTH_H__ +#define __LIBCLI_AUTH_H__ + +#include "librpc/gen_ndr/netlogon.h" +#include "librpc/gen_ndr/wkssvc.h" +#include "librpc/gen_ndr/schannel.h" +#include "libcli/auth/credentials.h" +#include "libcli/auth/ntlm_check.h" +#include "libcli/auth/proto.h" +#include "libcli/auth/msrpc_parse.h" + +#endif /* __LIBCLI_AUTH_H__ */ diff --git a/libcli/auth/msrpc_parse.c b/libcli/auth/msrpc_parse.c new file mode 100644 index 0000000..86ba2ec --- /dev/null +++ b/libcli/auth/msrpc_parse.c @@ -0,0 +1,409 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5/SPNEGO routines + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002 + Copyright (C) Andrew Bartlett 2002-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/auth/msrpc_parse.h" + +/* + this is a tiny msrpc packet generator. I am only using this to + avoid tying this code to a particular varient of our rpc code. This + generator is not general enough for all our rpc needs, its just + enough for the spnego/ntlmssp code + + format specifiers are: + + U = unicode string (input is unix string) + a = address (input is char *unix_string) + (1 byte type, 1 byte length, unicode/ASCII string, all inline) + A = ASCII string (input is unix string) + B = data blob (pointer + length) + b = data blob in header (pointer + length) + D + d = word (4 bytes) + C = constant ascii string + */ +NTSTATUS msrpc_gen(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + const char *format, ...) +{ + int i, j; + bool ret; + va_list ap; + char *s; + uint8_t *b; + int head_size=0, data_size=0; + int head_ofs, data_ofs; + int *intargs; + size_t n; + + DATA_BLOB *pointers; + + pointers = talloc_array(mem_ctx, DATA_BLOB, strlen(format)); + if (!pointers) { + return NT_STATUS_NO_MEMORY; + } + intargs = talloc_array(pointers, int, strlen(format)); + if (!intargs) { + return NT_STATUS_NO_MEMORY; + } + + /* first scan the format to work out the header and body size */ + va_start(ap, format); + for (i=0; format[i]; i++) { + switch (format[i]) { + case 'U': + s = va_arg(ap, char *); + head_size += 8; + ret = push_ucs2_talloc( + pointers, + (smb_ucs2_t **)(void *)&pointers[i].data, + s, &n); + if (!ret) { + va_end(ap); + return map_nt_error_from_unix_common(errno); + } + pointers[i].length = n; + pointers[i].length -= 2; + data_size += pointers[i].length; + break; + case 'A': + s = va_arg(ap, char *); + head_size += 8; + ret = push_ascii_talloc( + pointers, (char **)(void *)&pointers[i].data, + s, &n); + if (!ret) { + va_end(ap); + return map_nt_error_from_unix_common(errno); + } + pointers[i].length = n; + pointers[i].length -= 1; + data_size += pointers[i].length; + break; + case 'a': + j = va_arg(ap, int); + intargs[i] = j; + s = va_arg(ap, char *); + ret = push_ucs2_talloc( + pointers, + (smb_ucs2_t **)(void *)&pointers[i].data, + s, &n); + if (!ret) { + va_end(ap); + return map_nt_error_from_unix_common(errno); + } + pointers[i].length = n; + pointers[i].length -= 2; + data_size += pointers[i].length + 4; + break; + case 'B': + b = va_arg(ap, uint8_t *); + head_size += 8; + pointers[i].data = b; + pointers[i].length = va_arg(ap, int); + data_size += pointers[i].length; + break; + case 'b': + b = va_arg(ap, uint8_t *); + pointers[i].data = b; + pointers[i].length = va_arg(ap, int); + head_size += pointers[i].length; + break; + case 'd': + j = va_arg(ap, int); + intargs[i] = j; + head_size += 4; + break; + case 'C': + s = va_arg(ap, char *); + pointers[i].data = (uint8_t *)s; + pointers[i].length = strlen(s)+1; + head_size += pointers[i].length; + break; + default: + va_end(ap); + return NT_STATUS_INVALID_PARAMETER; + } + } + va_end(ap); + + if (head_size + data_size == 0) { + return NT_STATUS_INVALID_PARAMETER; + } + + /* allocate the space, then scan the format again to fill in the values */ + *blob = data_blob_talloc(mem_ctx, NULL, head_size + data_size); + if (!blob->data) { + return NT_STATUS_NO_MEMORY; + } + head_ofs = 0; + data_ofs = head_size; + + va_start(ap, format); + for (i=0; format[i]; i++) { + switch (format[i]) { + case 'U': + case 'A': + case 'B': + n = pointers[i].length; + SSVAL(blob->data, head_ofs, n); head_ofs += 2; + SSVAL(blob->data, head_ofs, n); head_ofs += 2; + SIVAL(blob->data, head_ofs, data_ofs); head_ofs += 4; + if (pointers[i].data && n) /* don't follow null pointers... */ + memcpy(blob->data+data_ofs, pointers[i].data, n); + data_ofs += n; + break; + case 'a': + j = intargs[i]; + SSVAL(blob->data, data_ofs, j); data_ofs += 2; + + n = pointers[i].length; + SSVAL(blob->data, data_ofs, n); data_ofs += 2; + memcpy(blob->data+data_ofs, pointers[i].data, n); + data_ofs += n; + break; + case 'd': + j = intargs[i]; + SIVAL(blob->data, head_ofs, j); + head_ofs += 4; + break; + case 'b': + n = pointers[i].length; + if (pointers[i].data && n) { + /* don't follow null pointers... */ + memcpy(blob->data + head_ofs, pointers[i].data, n); + } + head_ofs += n; + break; + case 'C': + n = pointers[i].length; + memcpy(blob->data + head_ofs, pointers[i].data, n); + head_ofs += n; + break; + default: + va_end(ap); + return NT_STATUS_INVALID_PARAMETER; + } + } + va_end(ap); + + talloc_free(pointers); + + return NT_STATUS_OK; +} + + +/* a helpful macro to avoid running over the end of our blob */ +#define NEED_DATA(amount) \ +if ((head_ofs + amount) > blob->length) { \ + va_end(ap); \ + return false; \ +} + +/** + this is a tiny msrpc packet parser. This the the partner of msrpc_gen + + format specifiers are: + + U = unicode string (output is unix string) + A = ascii string + B = data blob + b = data blob in header + d = word (4 bytes) + C = constant ascii string + */ + +bool msrpc_parse(TALLOC_CTX *mem_ctx, + const DATA_BLOB *blob, + const char *format, ...) +{ + int i; + va_list ap; + char **ps, *s; + DATA_BLOB *b; + size_t head_ofs = 0; + uint16_t len1, len2; + uint32_t ptr; + uint32_t *v; + bool ret = true; + + va_start(ap, format); + for (i=0; format[i]; i++) { + switch (format[i]) { + case 'U': + NEED_DATA(8); + len1 = SVAL(blob->data, head_ofs); head_ofs += 2; + len2 = SVAL(blob->data, head_ofs); head_ofs += 2; + ptr = IVAL(blob->data, head_ofs); head_ofs += 4; + + ps = va_arg(ap, char **); + if (len1 == 0 && len2 == 0) { + *ps = talloc_strdup(mem_ctx, ""); + if (*ps == NULL) { + ret = false; + goto cleanup; + } + } else { + /* make sure its in the right format - be strict */ + if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) { + ret = false; + goto cleanup; + } + if (len1 & 1) { + /* if odd length and unicode */ + ret = false; + goto cleanup; + } + if (blob->data + ptr < (uint8_t *)(uintptr_t)ptr || + blob->data + ptr < blob->data) { + ret = false; + goto cleanup; + } + + if (0 < len1) { + size_t pull_len; + if (!convert_string_talloc(mem_ctx, CH_UTF16, CH_UNIX, + blob->data + ptr, len1, + ps, &pull_len)) { + ret = false; + goto cleanup; + } + } else { + *ps = talloc_strdup(mem_ctx, ""); + if (*ps == NULL) { + ret = false; + goto cleanup; + } + } + } + break; + case 'A': + NEED_DATA(8); + len1 = SVAL(blob->data, head_ofs); head_ofs += 2; + len2 = SVAL(blob->data, head_ofs); head_ofs += 2; + ptr = IVAL(blob->data, head_ofs); head_ofs += 4; + + ps = (char **)va_arg(ap, char **); + /* make sure its in the right format - be strict */ + if (len1 == 0 && len2 == 0) { + *ps = talloc_strdup(mem_ctx, ""); + if (*ps == NULL) { + ret = false; + goto cleanup; + } + } else { + if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) { + ret = false; + goto cleanup; + } + + if (blob->data + ptr < (uint8_t *)(uintptr_t)ptr || + blob->data + ptr < blob->data) { + ret = false; + goto cleanup; + } + + if (0 < len1) { + size_t pull_len; + + if (!convert_string_talloc(mem_ctx, CH_DOS, CH_UNIX, + blob->data + ptr, len1, + ps, &pull_len)) { + ret = false; + goto cleanup; + } + } else { + *ps = talloc_strdup(mem_ctx, ""); + if (*ps == NULL) { + ret = false; + goto cleanup; + } + } + } + break; + case 'B': + NEED_DATA(8); + len1 = SVAL(blob->data, head_ofs); head_ofs += 2; + len2 = SVAL(blob->data, head_ofs); head_ofs += 2; + ptr = IVAL(blob->data, head_ofs); head_ofs += 4; + + b = (DATA_BLOB *)va_arg(ap, void *); + if (len1 == 0 && len2 == 0) { + *b = data_blob_talloc(mem_ctx, NULL, 0); + } else { + /* make sure its in the right format - be strict */ + if ((len1 != len2) || (ptr + len1 < ptr) || (ptr + len1 < len1) || (ptr + len1 > blob->length)) { + ret = false; + goto cleanup; + } + + if (blob->data + ptr < (uint8_t *)(uintptr_t)ptr || + blob->data + ptr < blob->data) { + ret = false; + goto cleanup; + } + + *b = data_blob_talloc(mem_ctx, blob->data + ptr, len1); + } + break; + case 'b': + b = (DATA_BLOB *)va_arg(ap, void *); + len1 = va_arg(ap, unsigned int); + /* make sure its in the right format - be strict */ + NEED_DATA(len1); + if (blob->data + head_ofs < (uint8_t *)head_ofs || + blob->data + head_ofs < blob->data) { + ret = false; + goto cleanup; + } + + *b = data_blob_talloc(mem_ctx, blob->data + head_ofs, len1); + head_ofs += len1; + break; + case 'd': + v = va_arg(ap, uint32_t *); + NEED_DATA(4); + *v = IVAL(blob->data, head_ofs); head_ofs += 4; + break; + case 'C': + s = va_arg(ap, char *); + + if (blob->data + head_ofs < (uint8_t *)head_ofs || + blob->data + head_ofs < blob->data || + (head_ofs + (strlen(s) + 1)) > blob->length) { + ret = false; + goto cleanup; + } + + if (memcmp(blob->data + head_ofs, s, strlen(s)+1) != 0) { + ret = false; + goto cleanup; + } + head_ofs += (strlen(s) + 1); + + break; + } + } + +cleanup: + va_end(ap); + return ret; +} diff --git a/libcli/auth/msrpc_parse.h b/libcli/auth/msrpc_parse.h new file mode 100644 index 0000000..47529f2 --- /dev/null +++ b/libcli/auth/msrpc_parse.h @@ -0,0 +1,58 @@ +/* + Unix SMB/CIFS implementation. + simple kerberos5/SPNEGO routines + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2002 + Copyright (C) Andrew Bartlett 2002-2003 + + 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/>. +*/ + +#ifndef _LIBCLI_AUTH_MSRPC_PARSE_H__ +#define _LIBCLI_AUTH_MSRPC_PARSE_H__ + +#undef _PRINTF_ATTRIBUTE +#define _PRINTF_ATTRIBUTE(a1, a2) PRINTF_ATTRIBUTE(a1, a2) + +/* this file contains prototypes for functions that are private + * to this subsystem or library. These functions should not be + * used outside this particular subsystem! */ + + +/* The following definitions come from /home/jeremy/src/samba/git/master/source3/../source4/../libcli/auth/msrpc_parse.c */ + +NTSTATUS msrpc_gen(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, + const char *format, ...); + +/** + this is a tiny msrpc packet parser. This the the partner of msrpc_gen + + format specifiers are: + + U = unicode string (output is unix string) + A = ascii string + B = data blob + b = data blob in header + d = word (4 bytes) + C = constant ascii string + */ +bool msrpc_parse(TALLOC_CTX *mem_ctx, + const DATA_BLOB *blob, + const char *format, ...); +#undef _PRINTF_ATTRIBUTE +#define _PRINTF_ATTRIBUTE(a1, a2) + +#endif + diff --git a/libcli/auth/netlogon_creds_cli.c b/libcli/auth/netlogon_creds_cli.c new file mode 100644 index 0000000..fb53ee5 --- /dev/null +++ b/libcli/auth/netlogon_creds_cli.c @@ -0,0 +1,4254 @@ +/* + Unix SMB/CIFS implementation. + + module to store/fetch session keys for the schannel client + + Copyright (C) Stefan Metzmacher 2013 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "lib/dbwrap/dbwrap.h" +#include "lib/dbwrap/dbwrap_rbt.h" +#include "lib/util/util_tdb.h" +#include "libcli/security/security.h" +#include "../lib/param/param.h" +#include "../libcli/auth/schannel.h" +#include "../librpc/gen_ndr/ndr_schannel.h" +#include "../librpc/gen_ndr/ndr_netlogon_c.h" +#include "../librpc/gen_ndr/ndr_netlogon.h" +#include "../librpc/gen_ndr/server_id.h" +#include "netlogon_creds_cli.h" +#include "source3/include/messages.h" +#include "source3/include/g_lock.h" +#include "libds/common/roles.h" +#include "lib/crypto/md4.h" +#include "auth/credentials/credentials.h" +#include "lib/param/loadparm.h" + +struct netlogon_creds_cli_locked_state; + +struct netlogon_creds_cli_context { + struct { + const char *computer; + const char *account; + uint32_t proposed_flags; + uint32_t required_flags; + enum netr_SchannelType type; + enum dcerpc_AuthLevel auth_level; + } client; + + struct { + const char *computer; + const char *netbios_domain; + const char *dns_domain; + uint32_t cached_flags; + bool try_validation6; + bool try_logon_ex; + bool try_logon_with; + } server; + + struct { + const char *key_name; + TDB_DATA key_data; + struct db_context *ctx; + struct g_lock_ctx *g_ctx; + struct netlogon_creds_cli_locked_state *locked_state; + enum netlogon_creds_cli_lck_type lock; + } db; +}; + +struct netlogon_creds_cli_locked_state { + struct netlogon_creds_cli_context *context; + bool is_glocked; + struct netlogon_creds_CredentialState *creds; +}; + +static int netlogon_creds_cli_locked_state_destructor( + struct netlogon_creds_cli_locked_state *state) +{ + struct netlogon_creds_cli_context *context = state->context; + + if (context == NULL) { + return 0; + } + + if (context->db.locked_state == state) { + context->db.locked_state = NULL; + } + + if (state->is_glocked) { + g_lock_unlock(context->db.g_ctx, + string_term_tdb_data(context->db.key_name)); + } + + return 0; +} + +static NTSTATUS netlogon_creds_cli_context_common( + const char *client_computer, + const char *client_account, + enum netr_SchannelType type, + enum dcerpc_AuthLevel auth_level, + uint32_t proposed_flags, + uint32_t required_flags, + const char *server_computer, + const char *server_netbios_domain, + const char *server_dns_domain, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_cli_context **_context) +{ + struct netlogon_creds_cli_context *context = NULL; + char *_key_name = NULL; + size_t server_netbios_name_len; + char *p = NULL; + + *_context = NULL; + + context = talloc_zero(mem_ctx, struct netlogon_creds_cli_context); + if (context == NULL) { + return NT_STATUS_NO_MEMORY; + } + + context->client.computer = talloc_strdup(context, client_computer); + if (context->client.computer == NULL) { + TALLOC_FREE(context); + return NT_STATUS_NO_MEMORY; + } + + context->client.account = talloc_strdup(context, client_account); + if (context->client.account == NULL) { + TALLOC_FREE(context); + return NT_STATUS_NO_MEMORY; + } + + context->client.proposed_flags = proposed_flags; + context->client.required_flags = required_flags; + context->client.type = type; + context->client.auth_level = auth_level; + + context->server.computer = talloc_strdup(context, server_computer); + if (context->server.computer == NULL) { + TALLOC_FREE(context); + return NT_STATUS_NO_MEMORY; + } + + context->server.netbios_domain = talloc_strdup(context, server_netbios_domain); + if (context->server.netbios_domain == NULL) { + TALLOC_FREE(context); + return NT_STATUS_NO_MEMORY; + } + + context->server.dns_domain = talloc_strdup(context, server_dns_domain); + if (context->server.dns_domain == NULL) { + TALLOC_FREE(context); + return NT_STATUS_NO_MEMORY; + } + + /* + * TODO: + * Force the callers to provide a unique + * value for server_computer and use this directly. + * + * For now we have to deal with + * "HOSTNAME" vs. "hostname.example.com". + */ + + p = strchr(server_computer, '.'); + if (p != NULL) { + server_netbios_name_len = p-server_computer; + } else { + server_netbios_name_len = strlen(server_computer); + } + + _key_name = talloc_asprintf(context, "CLI[%s/%s]/SRV[%.*s/%s]", + client_computer, + client_account, + (int)server_netbios_name_len, + server_computer, + server_netbios_domain); + if (_key_name == NULL) { + TALLOC_FREE(context); + return NT_STATUS_NO_MEMORY; + } + + context->db.key_name = talloc_strdup_upper(context, _key_name); + TALLOC_FREE(_key_name); + if (context->db.key_name == NULL) { + TALLOC_FREE(context); + return NT_STATUS_NO_MEMORY; + } + + context->db.key_data = string_term_tdb_data(context->db.key_name); + + *_context = context; + return NT_STATUS_OK; +} + +static struct db_context *netlogon_creds_cli_global_db; + +NTSTATUS netlogon_creds_cli_set_global_db(struct loadparm_context *lp_ctx, + struct db_context **db) +{ + netlogon_creds_cli_warn_options(lp_ctx); + + if (netlogon_creds_cli_global_db != NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + netlogon_creds_cli_global_db = talloc_move(NULL, db); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_open_global_db(struct loadparm_context *lp_ctx) +{ + char *fname; + struct db_context *global_db; + int hash_size, tdb_flags; + + netlogon_creds_cli_warn_options(lp_ctx); + + if (netlogon_creds_cli_global_db != NULL) { + return NT_STATUS_OK; + } + + fname = lpcfg_private_db_path(NULL, lp_ctx, "netlogon_creds_cli"); + if (fname == NULL) { + return NT_STATUS_NO_MEMORY; + } + + hash_size = lpcfg_tdb_hash_size(lp_ctx, fname); + tdb_flags = lpcfg_tdb_flags( + lp_ctx, + TDB_CLEAR_IF_FIRST|TDB_INCOMPATIBLE_HASH); + + global_db = dbwrap_local_open( + NULL, + fname, + hash_size, + tdb_flags, + O_RDWR|O_CREAT, + 0600, + DBWRAP_LOCK_ORDER_2, + DBWRAP_FLAG_NONE); + if (global_db == NULL) { + DEBUG(0,("netlogon_creds_cli_open_global_db: Failed to open %s - %s\n", + fname, strerror(errno))); + talloc_free(fname); + return NT_STATUS_NO_MEMORY; + } + TALLOC_FREE(fname); + + netlogon_creds_cli_global_db = global_db; + return NT_STATUS_OK; +} + +void netlogon_creds_cli_close_global_db(void) +{ + TALLOC_FREE(netlogon_creds_cli_global_db); +} + +void netlogon_creds_cli_warn_options(struct loadparm_context *lp_ctx) +{ + bool global_reject_md5_servers = lpcfg_reject_md5_servers(lp_ctx); + bool global_require_strong_key = lpcfg_require_strong_key(lp_ctx); + int global_client_schannel = lpcfg_client_schannel(lp_ctx); + bool global_seal_secure_channel = lpcfg_winbind_sealed_pipes(lp_ctx); + int global_kerberos_enctypes = lpcfg_kerberos_encryption_types(lp_ctx); + static bool warned_global_reject_md5_servers = false; + static bool warned_global_require_strong_key = false; + static bool warned_global_client_schannel = false; + static bool warned_global_seal_secure_channel = false; + static bool warned_global_kerberos_encryption_types = false; + static int warned_global_pid = 0; + int current_pid = tevent_cached_getpid(); + + if (warned_global_pid != current_pid) { + warned_global_reject_md5_servers = false; + warned_global_require_strong_key = false; + warned_global_client_schannel = false; + warned_global_seal_secure_channel = false; + warned_global_kerberos_encryption_types = false; + warned_global_pid = current_pid; + } + + if (!global_reject_md5_servers && !warned_global_reject_md5_servers) { + /* + * We want admins to notice their misconfiguration! + */ + DBG_ERR("CVE-2022-38023 (and others): " + "Please configure 'reject md5 servers = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_reject_md5_servers = true; + } + + if (!global_require_strong_key && !warned_global_require_strong_key) { + /* + * We want admins to notice their misconfiguration! + */ + DBG_ERR("CVE-2022-38023 (and others): " + "Please configure 'require strong key = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_require_strong_key = true; + } + + if (global_client_schannel != true && !warned_global_client_schannel) { + /* + * We want admins to notice their misconfiguration! + */ + DBG_ERR("CVE-2022-38023 (and others): " + "Please configure 'client schannel = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_client_schannel = true; + } + + if (!global_seal_secure_channel && !warned_global_seal_secure_channel) { + /* + * We want admins to notice their misconfiguration! + */ + DBG_ERR("CVE-2022-38023 (and others): " + "Please configure 'winbind sealed pipes = yes' (the default), " + "See https://bugzilla.samba.org/show_bug.cgi?id=15240\n"); + warned_global_seal_secure_channel = true; + } + + if (global_kerberos_enctypes == KERBEROS_ETYPES_LEGACY && + !warned_global_kerberos_encryption_types) + { + /* + * We want admins to notice their misconfiguration! + */ + DBG_ERR("CVE-2022-37966: " + "Please void 'kerberos encryption types = legacy', " + "See https://bugzilla.samba.org/show_bug.cgi?id=15237\n"); + warned_global_kerberos_encryption_types = true; + } +} + +NTSTATUS netlogon_creds_cli_context_global(struct loadparm_context *lp_ctx, + struct messaging_context *msg_ctx, + const char *client_account, + enum netr_SchannelType type, + const char *server_computer, + const char *server_netbios_domain, + const char *server_dns_domain, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_cli_context **_context) +{ + TALLOC_CTX *frame = talloc_stackframe(); + NTSTATUS status; + struct netlogon_creds_cli_context *context = NULL; + const char *client_computer; + uint32_t proposed_flags; + uint32_t required_flags = 0; + bool reject_md5_servers = true; + bool require_strong_key = true; + int require_sign_or_seal = true; + bool seal_secure_channel = true; + enum dcerpc_AuthLevel auth_level = DCERPC_AUTH_LEVEL_NONE; + bool neutralize_nt4_emulation = false; + + *_context = NULL; + + if (msg_ctx == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + client_computer = lpcfg_netbios_name(lp_ctx); + if (strlen(client_computer) > 15) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + /* + * allow overwrite per domain + * reject md5 servers:<netbios_domain> + */ + reject_md5_servers = lpcfg_reject_md5_servers(lp_ctx); + reject_md5_servers = lpcfg_parm_bool(lp_ctx, NULL, + "reject md5 servers", + server_netbios_domain, + reject_md5_servers); + + /* + * allow overwrite per domain + * require strong key:<netbios_domain> + */ + require_strong_key = lpcfg_require_strong_key(lp_ctx); + require_strong_key = lpcfg_parm_bool(lp_ctx, NULL, + "require strong key", + server_netbios_domain, + require_strong_key); + + /* + * allow overwrite per domain + * client schannel:<netbios_domain> + */ + require_sign_or_seal = lpcfg_client_schannel(lp_ctx); + require_sign_or_seal = lpcfg_parm_int(lp_ctx, NULL, + "client schannel", + server_netbios_domain, + require_sign_or_seal); + + /* + * allow overwrite per domain + * winbind sealed pipes:<netbios_domain> + */ + seal_secure_channel = lpcfg_winbind_sealed_pipes(lp_ctx); + seal_secure_channel = lpcfg_parm_bool(lp_ctx, NULL, + "winbind sealed pipes", + server_netbios_domain, + seal_secure_channel); + + /* + * allow overwrite per domain + * neutralize nt4 emulation:<netbios_domain> + */ + neutralize_nt4_emulation = lpcfg_neutralize_nt4_emulation(lp_ctx); + neutralize_nt4_emulation = lpcfg_parm_bool(lp_ctx, NULL, + "neutralize nt4 emulation", + server_netbios_domain, + neutralize_nt4_emulation); + + proposed_flags = NETLOGON_NEG_AUTH2_ADS_FLAGS; + proposed_flags |= NETLOGON_NEG_SUPPORTS_AES; + + switch (type) { + case SEC_CHAN_WKSTA: + if (lpcfg_security(lp_ctx) == SEC_ADS) { + /* + * AD domains should be secure + */ + required_flags |= NETLOGON_NEG_PASSWORD_SET2; + require_sign_or_seal = true; + require_strong_key = true; + } + break; + + case SEC_CHAN_DOMAIN: + break; + + case SEC_CHAN_DNS_DOMAIN: + /* + * AD domains should be secure + */ + required_flags |= NETLOGON_NEG_PASSWORD_SET2; + require_sign_or_seal = true; + require_strong_key = true; + neutralize_nt4_emulation = true; + break; + + case SEC_CHAN_BDC: + required_flags |= NETLOGON_NEG_PASSWORD_SET2; + require_sign_or_seal = true; + require_strong_key = true; + break; + + case SEC_CHAN_RODC: + required_flags |= NETLOGON_NEG_RODC_PASSTHROUGH; + required_flags |= NETLOGON_NEG_PASSWORD_SET2; + require_sign_or_seal = true; + require_strong_key = true; + neutralize_nt4_emulation = true; + break; + + default: + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + if (neutralize_nt4_emulation) { + proposed_flags |= NETLOGON_NEG_NEUTRALIZE_NT4_EMULATION; + } + + if (require_sign_or_seal) { + required_flags |= NETLOGON_NEG_ARCFOUR; + required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC; + } else { + proposed_flags &= ~NETLOGON_NEG_AUTHENTICATED_RPC; + } + + if (reject_md5_servers) { + required_flags |= NETLOGON_NEG_ARCFOUR; + required_flags |= NETLOGON_NEG_PASSWORD_SET2; + required_flags |= NETLOGON_NEG_SUPPORTS_AES; + required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC; + } + + if (require_strong_key) { + required_flags |= NETLOGON_NEG_ARCFOUR; + required_flags |= NETLOGON_NEG_STRONG_KEYS; + required_flags |= NETLOGON_NEG_AUTHENTICATED_RPC; + } + + /* + * If weak crypto is disabled, do not announce that we support RC4 and + * require AES. + */ + if (lpcfg_weak_crypto(lp_ctx) == SAMBA_WEAK_CRYPTO_DISALLOWED) { + required_flags &= ~NETLOGON_NEG_ARCFOUR; + required_flags |= NETLOGON_NEG_SUPPORTS_AES; + proposed_flags &= ~NETLOGON_NEG_ARCFOUR; + proposed_flags |= NETLOGON_NEG_SUPPORTS_AES; + } + + proposed_flags |= required_flags; + + if (seal_secure_channel) { + auth_level = DCERPC_AUTH_LEVEL_PRIVACY; + } else { + auth_level = DCERPC_AUTH_LEVEL_INTEGRITY; + } + + status = netlogon_creds_cli_context_common(client_computer, + client_account, + type, + auth_level, + proposed_flags, + required_flags, + server_computer, + server_netbios_domain, + "", + mem_ctx, + &context); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return status; + } + + context->db.g_ctx = g_lock_ctx_init(context, msg_ctx); + if (context->db.g_ctx == NULL) { + TALLOC_FREE(context); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + status = netlogon_creds_cli_open_global_db(lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(context); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + context->db.ctx = netlogon_creds_cli_global_db; + *_context = context; + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_bind_cli_credentials( + struct netlogon_creds_cli_context *context, TALLOC_CTX *mem_ctx, + struct cli_credentials **pcli_creds) +{ + struct cli_credentials *cli_creds; + struct netlogon_creds_CredentialState *ncreds; + NTSTATUS status; + + cli_creds = cli_credentials_init(mem_ctx); + if (cli_creds == NULL) { + return NT_STATUS_NO_MEMORY; + } + cli_credentials_set_secure_channel_type(cli_creds, + context->client.type); + cli_credentials_set_username(cli_creds, context->client.account, + CRED_SPECIFIED); + cli_credentials_set_domain(cli_creds, context->server.netbios_domain, + CRED_SPECIFIED); + cli_credentials_set_realm(cli_creds, context->server.dns_domain, + CRED_SPECIFIED); + + status = netlogon_creds_cli_get(context, cli_creds, &ncreds); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(cli_creds); + return status; + } + cli_credentials_set_netlogon_creds(cli_creds, ncreds); + + *pcli_creds = cli_creds; + return NT_STATUS_OK; +} + +char *netlogon_creds_cli_debug_string( + const struct netlogon_creds_cli_context *context, + TALLOC_CTX *mem_ctx) +{ + return talloc_asprintf(mem_ctx, "netlogon_creds_cli:%s", + context->db.key_name); +} + +enum dcerpc_AuthLevel netlogon_creds_cli_auth_level( + struct netlogon_creds_cli_context *context) +{ + return context->client.auth_level; +} + +static bool netlogon_creds_cli_downgraded(uint32_t negotiated_flags, + uint32_t proposed_flags, + uint32_t required_flags) +{ + uint32_t req_flags = required_flags; + uint32_t tmp_flags; + + req_flags = required_flags; + if ((negotiated_flags & NETLOGON_NEG_SUPPORTS_AES) && + (proposed_flags & NETLOGON_NEG_SUPPORTS_AES)) + { + req_flags &= ~NETLOGON_NEG_ARCFOUR|NETLOGON_NEG_STRONG_KEYS; + } + + tmp_flags = negotiated_flags; + tmp_flags &= req_flags; + if (tmp_flags != req_flags) { + return true; + } + + return false; +} + +struct netlogon_creds_cli_fetch_state { + TALLOC_CTX *mem_ctx; + struct netlogon_creds_CredentialState *creds; + uint32_t proposed_flags; + uint32_t required_flags; + NTSTATUS status; +}; + +static void netlogon_creds_cli_fetch_parser(TDB_DATA key, TDB_DATA data, + void *private_data) +{ + struct netlogon_creds_cli_fetch_state *state = + (struct netlogon_creds_cli_fetch_state *)private_data; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + bool downgraded; + + state->creds = talloc_zero(state->mem_ctx, + struct netlogon_creds_CredentialState); + if (state->creds == NULL) { + state->status = NT_STATUS_NO_MEMORY; + return; + } + + blob.data = data.dptr; + blob.length = data.dsize; + + ndr_err = ndr_pull_struct_blob(&blob, state->creds, state->creds, + (ndr_pull_flags_fn_t)ndr_pull_netlogon_creds_CredentialState); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(state->creds); + state->status = ndr_map_error2ntstatus(ndr_err); + return; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netlogon_creds_CredentialState, state->creds); + } + + downgraded = netlogon_creds_cli_downgraded( + state->creds->negotiate_flags, + state->proposed_flags, + state->required_flags); + if (downgraded) { + TALLOC_FREE(state->creds); + state->status = NT_STATUS_DOWNGRADE_DETECTED; + return; + } + + state->status = NT_STATUS_OK; +} + +static NTSTATUS netlogon_creds_cli_get_internal( + struct netlogon_creds_cli_context *context, + TALLOC_CTX *mem_ctx, struct netlogon_creds_CredentialState **pcreds); + +NTSTATUS netlogon_creds_cli_get(struct netlogon_creds_cli_context *context, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState **_creds) +{ + NTSTATUS status; + struct netlogon_creds_CredentialState *creds; + + *_creds = NULL; + + status = netlogon_creds_cli_get_internal(context, mem_ctx, &creds); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * mark it as invalid for step operations. + */ + creds->sequence = 0; + creds->seed = (struct netr_Credential) {{0}}; + creds->client = (struct netr_Credential) {{0}}; + creds->server = (struct netr_Credential) {{0}}; + + *_creds = creds; + return NT_STATUS_OK; +} + +bool netlogon_creds_cli_validate(struct netlogon_creds_cli_context *context, + const struct netlogon_creds_CredentialState *creds1) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct netlogon_creds_CredentialState *creds2; + DATA_BLOB blob1; + DATA_BLOB blob2; + NTSTATUS status; + enum ndr_err_code ndr_err; + bool equal; + + status = netlogon_creds_cli_get(context, frame, &creds2); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return false; + } + + ndr_err = ndr_push_struct_blob(&blob1, frame, creds1, + (ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return false; + } + + ndr_err = ndr_push_struct_blob(&blob2, frame, creds2, + (ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + TALLOC_FREE(frame); + return false; + } + + equal = data_blob_equal_const_time(&blob1, &blob2); + + TALLOC_FREE(frame); + + return equal; +} + +static NTSTATUS netlogon_creds_cli_store_internal( + struct netlogon_creds_cli_context *context, + struct netlogon_creds_CredentialState *creds) +{ + NTSTATUS status; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + TDB_DATA data; + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds); + } + + ndr_err = ndr_push_struct_blob(&blob, creds, creds, + (ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + return status; + } + + data.dptr = blob.data; + data.dsize = blob.length; + + status = dbwrap_store(context->db.ctx, + context->db.key_data, + data, TDB_REPLACE); + TALLOC_FREE(data.dptr); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_store(struct netlogon_creds_cli_context *context, + struct netlogon_creds_CredentialState *creds) +{ + NTSTATUS status; + + if (context->db.locked_state == NULL) { + /* + * this was not the result of netlogon_creds_cli_lock*() + */ + return NT_STATUS_INVALID_PAGE_PROTECTION; + } + + if (context->db.locked_state->creds != creds) { + /* + * this was not the result of netlogon_creds_cli_lock*() + */ + return NT_STATUS_INVALID_PAGE_PROTECTION; + } + + status = netlogon_creds_cli_store_internal(context, creds); + return status; +} + +static NTSTATUS netlogon_creds_cli_delete_internal( + struct netlogon_creds_cli_context *context) +{ + NTSTATUS status; + status = dbwrap_delete(context->db.ctx, context->db.key_data); + return status; +} + +NTSTATUS netlogon_creds_cli_delete_lck( + struct netlogon_creds_cli_context *context) +{ + NTSTATUS status; + + if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) { + return NT_STATUS_NOT_LOCKED; + } + + status = netlogon_creds_cli_delete_internal(context); + return status; +} + +NTSTATUS netlogon_creds_cli_delete(struct netlogon_creds_cli_context *context, + struct netlogon_creds_CredentialState *creds) +{ + NTSTATUS status; + + if (context->db.locked_state == NULL) { + /* + * this was not the result of netlogon_creds_cli_lock*() + */ + return NT_STATUS_INVALID_PAGE_PROTECTION; + } + + if (context->db.locked_state->creds != creds) { + /* + * this was not the result of netlogon_creds_cli_lock*() + */ + return NT_STATUS_INVALID_PAGE_PROTECTION; + } + + status = netlogon_creds_cli_delete_internal(context); + return status; +} + +struct netlogon_creds_cli_lock_state { + struct netlogon_creds_cli_locked_state *locked_state; + struct netlogon_creds_CredentialState *creds; +}; + +static void netlogon_creds_cli_lock_done(struct tevent_req *subreq); + +struct tevent_req *netlogon_creds_cli_lock_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context) +{ + struct tevent_req *req; + struct netlogon_creds_cli_lock_state *state; + struct netlogon_creds_cli_locked_state *locked_state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_lock_state); + if (req == NULL) { + return NULL; + } + + if (context->db.locked_state != NULL) { + tevent_req_nterror(req, NT_STATUS_LOCK_NOT_GRANTED); + return tevent_req_post(req, ev); + } + + locked_state = talloc_zero(state, struct netlogon_creds_cli_locked_state); + if (tevent_req_nomem(locked_state, req)) { + return tevent_req_post(req, ev); + } + talloc_set_destructor(locked_state, + netlogon_creds_cli_locked_state_destructor); + locked_state->context = context; + + context->db.locked_state = locked_state; + state->locked_state = locked_state; + + if (context->db.g_ctx == NULL) { + NTSTATUS status; + + status = netlogon_creds_cli_get_internal( + context, state, &state->creds); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + return req; + } + + subreq = g_lock_lock_send(state, ev, + context->db.g_ctx, + string_term_tdb_data(context->db.key_name), + G_LOCK_WRITE); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, netlogon_creds_cli_lock_done, req); + + return req; +} + +static void netlogon_creds_cli_lock_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_lock_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_lock_state); + NTSTATUS status; + + status = g_lock_lock_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + state->locked_state->is_glocked = true; + + status = netlogon_creds_cli_get_internal(state->locked_state->context, + state, &state->creds); + if (tevent_req_nterror(req, status)) { + return; + } + tevent_req_done(req); +} + +static NTSTATUS netlogon_creds_cli_get_internal( + struct netlogon_creds_cli_context *context, + TALLOC_CTX *mem_ctx, struct netlogon_creds_CredentialState **pcreds) +{ + struct netlogon_creds_cli_fetch_state fstate = { + .status = NT_STATUS_INTERNAL_ERROR, + .proposed_flags = context->client.proposed_flags, + .required_flags = context->client.required_flags, + }; + NTSTATUS status; + + fstate.mem_ctx = mem_ctx; + status = dbwrap_parse_record(context->db.ctx, + context->db.key_data, + netlogon_creds_cli_fetch_parser, + &fstate); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + if (!NT_STATUS_IS_OK(fstate.status)) { + return fstate.status; + } + + if (context->server.cached_flags == fstate.creds->negotiate_flags) { + *pcreds = fstate.creds; + return NT_STATUS_OK; + } + + /* + * It is really important to try SamLogonEx here, + * because multiple processes can talk to the same + * domain controller, without using the credential + * chain. + * + * With a normal SamLogon call, we must keep the + * credentials chain updated and intact between all + * users of the machine account (which would imply + * cross-node communication for every NTLM logon). + * + * The credentials chain is not per NETLOGON pipe + * connection, but globally on the server/client pair + * by computer name. + * + * It's also important to use NetlogonValidationSamInfo4 (6), + * because it relies on the rpc transport encryption + * and avoids using the global netlogon schannel + * session key to en/decrypt secret information + * like the user_session_key for network logons. + * + * [MS-APDS] 3.1.5.2 NTLM Network Logon + * says NETLOGON_NEG_CROSS_FOREST_TRUSTS and + * NETLOGON_NEG_AUTHENTICATED_RPC set together + * are the indication that the server supports + * NetlogonValidationSamInfo4 (6). And it must only + * be used if "SealSecureChannel" is used. + * + * The "SealSecureChannel" AUTH_TYPE_SCHANNEL/AUTH_LEVEL_PRIVACY + * check is done in netlogon_creds_cli_LogonSamLogon*(). + */ + + context->server.cached_flags = fstate.creds->negotiate_flags; + context->server.try_validation6 = true; + context->server.try_logon_ex = true; + context->server.try_logon_with = true; + + if (!(context->server.cached_flags & NETLOGON_NEG_AUTHENTICATED_RPC)) { + context->server.try_validation6 = false; + context->server.try_logon_ex = false; + } + if (!(context->server.cached_flags & NETLOGON_NEG_CROSS_FOREST_TRUSTS)) { + context->server.try_validation6 = false; + } + + *pcreds = fstate.creds; + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_lock_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState **creds) +{ + struct netlogon_creds_cli_lock_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_lock_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + talloc_steal(state->creds, state->locked_state); + state->locked_state->creds = state->creds; + *creds = talloc_move(mem_ctx, &state->creds); + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_lock(struct netlogon_creds_cli_context *context, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState **creds) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_lock_send(frame, ev, context); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_lock_recv(req, mem_ctx, creds); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_lck { + struct netlogon_creds_cli_context *context; +}; + +struct netlogon_creds_cli_lck_state { + struct netlogon_creds_cli_lck *lck; + enum netlogon_creds_cli_lck_type type; +}; + +static void netlogon_creds_cli_lck_locked(struct tevent_req *subreq); +static int netlogon_creds_cli_lck_destructor( + struct netlogon_creds_cli_lck *lck); + +struct tevent_req *netlogon_creds_cli_lck_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + enum netlogon_creds_cli_lck_type type) +{ + struct tevent_req *req, *subreq; + struct netlogon_creds_cli_lck_state *state; + enum g_lock_type gtype; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_lck_state); + if (req == NULL) { + return NULL; + } + + if (context->db.lock != NETLOGON_CREDS_CLI_LCK_NONE) { + DBG_DEBUG("context already locked\n"); + tevent_req_nterror(req, NT_STATUS_INVALID_LOCK_SEQUENCE); + return tevent_req_post(req, ev); + } + + switch (type) { + case NETLOGON_CREDS_CLI_LCK_SHARED: + gtype = G_LOCK_READ; + break; + case NETLOGON_CREDS_CLI_LCK_EXCLUSIVE: + gtype = G_LOCK_WRITE; + break; + default: + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + state->lck = talloc(state, struct netlogon_creds_cli_lck); + if (tevent_req_nomem(state->lck, req)) { + return tevent_req_post(req, ev); + } + state->lck->context = context; + state->type = type; + + subreq = g_lock_lock_send(state, ev, + context->db.g_ctx, + string_term_tdb_data(context->db.key_name), + gtype); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, netlogon_creds_cli_lck_locked, req); + + return req; +} + +static void netlogon_creds_cli_lck_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct netlogon_creds_cli_lck_state *state = tevent_req_data( + req, struct netlogon_creds_cli_lck_state); + NTSTATUS status; + + status = g_lock_lock_recv(subreq); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + state->lck->context->db.lock = state->type; + talloc_set_destructor(state->lck, netlogon_creds_cli_lck_destructor); + + tevent_req_done(req); +} + +static int netlogon_creds_cli_lck_destructor( + struct netlogon_creds_cli_lck *lck) +{ + struct netlogon_creds_cli_context *ctx = lck->context; + NTSTATUS status; + + status = g_lock_unlock(ctx->db.g_ctx, + string_term_tdb_data(ctx->db.key_name)); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("g_lock_unlock failed: %s\n", nt_errstr(status)); + smb_panic("g_lock_unlock failed"); + } + ctx->db.lock = NETLOGON_CREDS_CLI_LCK_NONE; + return 0; +} + +NTSTATUS netlogon_creds_cli_lck_recv( + struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct netlogon_creds_cli_lck **lck) +{ + struct netlogon_creds_cli_lck_state *state = tevent_req_data( + req, struct netlogon_creds_cli_lck_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + return status; + } + *lck = talloc_move(mem_ctx, &state->lck); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_lck( + struct netlogon_creds_cli_context *context, + enum netlogon_creds_cli_lck_type type, + TALLOC_CTX *mem_ctx, struct netlogon_creds_cli_lck **lck) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_lck_send(frame, ev, context, type); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_lck_recv(req, mem_ctx, lck); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_auth_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + uint8_t num_nt_hashes; + uint8_t idx_nt_hashes; + const struct samr_Password * const *nt_hashes; + const struct samr_Password *used_nt_hash; + char *srv_name_slash; + uint32_t current_flags; + struct netr_Credential client_challenge; + struct netr_Credential server_challenge; + struct netlogon_creds_CredentialState *creds; + struct netr_Credential client_credential; + struct netr_Credential server_credential; + uint32_t rid; + bool try_auth3; + bool try_auth2; + bool require_auth2; +}; + +static void netlogon_creds_cli_auth_challenge_start(struct tevent_req *req); + +struct tevent_req *netlogon_creds_cli_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + uint8_t num_nt_hashes, + const struct samr_Password * const *nt_hashes) +{ + struct tevent_req *req; + struct netlogon_creds_cli_auth_state *state; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_auth_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + if (num_nt_hashes < 1) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + if (num_nt_hashes > 4) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + state->num_nt_hashes = num_nt_hashes; + state->idx_nt_hashes = 0; + state->nt_hashes = nt_hashes; + + if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) { + tevent_req_nterror(req, NT_STATUS_NOT_LOCKED); + return tevent_req_post(req, ev); + } + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + state->try_auth3 = true; + state->try_auth2 = true; + + if (context->client.required_flags != 0) { + state->require_auth2 = true; + } + + state->used_nt_hash = state->nt_hashes[state->idx_nt_hashes]; + state->current_flags = context->client.proposed_flags; + + status = dbwrap_purge(state->context->db.ctx, + state->context->db.key_data); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + netlogon_creds_cli_auth_challenge_start(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_auth_challenge_start(struct tevent_req *req) +{ + struct netlogon_creds_cli_auth_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_auth_state); + struct tevent_req *subreq; + + TALLOC_FREE(state->creds); + + netlogon_creds_random_challenge(&state->client_challenge); + + subreq = dcerpc_netr_ServerReqChallenge_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->context->client.computer, + &state->client_challenge, + &state->server_challenge); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + netlogon_creds_cli_auth_challenge_done, + req); +} + +static void netlogon_creds_cli_auth_srvauth_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_auth_challenge_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_auth_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_auth_state); + NTSTATUS status; + NTSTATUS result; + + status = dcerpc_netr_ServerReqChallenge_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + if (tevent_req_nterror(req, result)) { + return; + } + + if (!state->try_auth3 && !state->try_auth2) { + state->current_flags = 0; + } + + /* Calculate the session key and client credentials */ + + state->creds = netlogon_creds_client_init(state, + state->context->client.account, + state->context->client.computer, + state->context->client.type, + &state->client_challenge, + &state->server_challenge, + state->used_nt_hash, + &state->client_credential, + state->current_flags); + if (tevent_req_nomem(state->creds, req)) { + return; + } + + if (state->try_auth3) { + subreq = dcerpc_netr_ServerAuthenticate3_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->context->client.account, + state->context->client.type, + state->context->client.computer, + &state->client_credential, + &state->server_credential, + &state->creds->negotiate_flags, + &state->rid); + if (tevent_req_nomem(subreq, req)) { + return; + } + } else if (state->try_auth2) { + state->rid = 0; + + subreq = dcerpc_netr_ServerAuthenticate2_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->context->client.account, + state->context->client.type, + state->context->client.computer, + &state->client_credential, + &state->server_credential, + &state->creds->negotiate_flags); + if (tevent_req_nomem(subreq, req)) { + return; + } + } else { + state->rid = 0; + + subreq = dcerpc_netr_ServerAuthenticate_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->context->client.account, + state->context->client.type, + state->context->client.computer, + &state->client_credential, + &state->server_credential); + if (tevent_req_nomem(subreq, req)) { + return; + } + } + tevent_req_set_callback(subreq, + netlogon_creds_cli_auth_srvauth_done, + req); +} + +static void netlogon_creds_cli_auth_srvauth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_auth_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_auth_state); + NTSTATUS status; + NTSTATUS result; + bool ok; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + TDB_DATA data; + bool downgraded; + + if (state->try_auth3) { + status = dcerpc_netr_ServerAuthenticate3_recv(subreq, state, + &result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + state->try_auth3 = false; + netlogon_creds_cli_auth_challenge_start(req); + return; + } + if (tevent_req_nterror(req, status)) { + return; + } + } else if (state->try_auth2) { + status = dcerpc_netr_ServerAuthenticate2_recv(subreq, state, + &result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + state->try_auth2 = false; + if (state->require_auth2) { + status = NT_STATUS_DOWNGRADE_DETECTED; + tevent_req_nterror(req, status); + return; + } + netlogon_creds_cli_auth_challenge_start(req); + return; + } + if (tevent_req_nterror(req, status)) { + return; + } + } else { + status = dcerpc_netr_ServerAuthenticate_recv(subreq, state, + &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + } + + if (!NT_STATUS_IS_OK(result) && + !NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) + { + tevent_req_nterror(req, result); + return; + } + + downgraded = netlogon_creds_cli_downgraded( + state->creds->negotiate_flags, + state->context->client.proposed_flags, + state->context->client.required_flags); + if (downgraded) { + if (NT_STATUS_IS_OK(result)) { + tevent_req_nterror(req, NT_STATUS_DOWNGRADE_DETECTED); + return; + } + tevent_req_nterror(req, result); + return; + } + + if (NT_STATUS_EQUAL(result, NT_STATUS_ACCESS_DENIED)) { + uint32_t tmp_flags = state->context->client.proposed_flags; + if ((state->current_flags == tmp_flags) && + (state->creds->negotiate_flags != tmp_flags)) + { + /* + * lets retry with the negotiated flags + */ + state->current_flags = state->creds->negotiate_flags; + netlogon_creds_cli_auth_challenge_start(req); + return; + } + + state->idx_nt_hashes += 1; + if (state->idx_nt_hashes >= state->num_nt_hashes) { + /* + * we already retried, giving up... + */ + tevent_req_nterror(req, result); + return; + } + + /* + * lets retry with the old nt hash. + */ + state->used_nt_hash = state->nt_hashes[state->idx_nt_hashes]; + state->current_flags = state->context->client.proposed_flags; + netlogon_creds_cli_auth_challenge_start(req); + return; + } + + ok = netlogon_creds_client_check(state->creds, + &state->server_credential); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_ACCESS_DENIED); + return; + } + + ndr_err = ndr_push_struct_blob(&blob, state, state->creds, + (ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + tevent_req_nterror(req, status); + return; + } + + data.dptr = blob.data; + data.dsize = blob.length; + + status = dbwrap_store(state->context->db.ctx, + state->context->db.key_data, + data, TDB_REPLACE); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_auth_recv(struct tevent_req *req, + uint8_t *idx_nt_hashes) +{ + struct netlogon_creds_cli_auth_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_auth_state); + NTSTATUS status; + + *idx_nt_hashes = 0; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *idx_nt_hashes = state->idx_nt_hashes; + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_auth(struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + uint8_t num_nt_hashes, + const struct samr_Password * const *nt_hashes, + uint8_t *idx_nt_hashes) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + *idx_nt_hashes = 0; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_auth_send(frame, ev, context, b, + num_nt_hashes, nt_hashes); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_auth_recv(req, idx_nt_hashes); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_check_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + + char *srv_name_slash; + + union netr_Capabilities caps; + + struct netlogon_creds_CredentialState *creds; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; +}; + +static void netlogon_creds_cli_check_cleanup(struct tevent_req *req, + NTSTATUS status); +static void netlogon_creds_cli_check_caps(struct tevent_req *subreq); + +struct tevent_req *netlogon_creds_cli_check_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b) +{ + struct tevent_req *req; + struct netlogon_creds_cli_check_state *state; + struct tevent_req *subreq; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_check_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + + if (context->db.lock != NETLOGON_CREDS_CLI_LCK_EXCLUSIVE) { + tevent_req_nterror(req, NT_STATUS_NOT_LOCKED); + return tevent_req_post(req, ev); + } + + status = netlogon_creds_cli_get_internal(context, state, + &state->creds); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + dcerpc_binding_handle_auth_info(state->binding_handle, + &auth_type, &auth_level); + + if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + switch (auth_level) { + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PRIVACY: + break; + default: + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + /* + * we defer all callbacks in order to cleanup + * the database record. + */ + tevent_req_defer_callback(req, state->ev); + + status = netlogon_creds_client_authenticator(state->creds, + &state->req_auth); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + ZERO_STRUCT(state->rep_auth); + + subreq = dcerpc_netr_LogonGetCapabilities_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->context->client.computer, + &state->req_auth, + &state->rep_auth, + 1, + &state->caps); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_check_caps, + req); + + return req; +} + +static void netlogon_creds_cli_check_cleanup(struct tevent_req *req, + NTSTATUS status) +{ + struct netlogon_creds_cli_check_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_check_state); + + if (state->creds == NULL) { + return; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) && + !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) && + !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + TALLOC_FREE(state->creds); + return; + } + + netlogon_creds_cli_delete_lck(state->context); + TALLOC_FREE(state->creds); +} + +static void netlogon_creds_cli_check_caps(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_check_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_check_state); + NTSTATUS status; + NTSTATUS result; + bool ok; + + status = dcerpc_netr_LogonGetCapabilities_recv(subreq, state, + &result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + /* + * Note that the negotiated flags are already checked + * for our required flags after the ServerAuthenticate3/2 call. + */ + uint32_t negotiated = state->creds->negotiate_flags; + + if (negotiated & NETLOGON_NEG_SUPPORTS_AES) { + /* + * If we have negotiated NETLOGON_NEG_SUPPORTS_AES + * already, we expect this to work! + */ + status = NT_STATUS_DOWNGRADE_DETECTED; + tevent_req_nterror(req, status); + netlogon_creds_cli_check_cleanup(req, status); + return; + } + + if (negotiated & NETLOGON_NEG_STRONG_KEYS) { + /* + * If we have negotiated NETLOGON_NEG_STRONG_KEYS + * we expect this to work at least as far as the + * NOT_SUPPORTED error handled below! + * + * NT 4.0 and Old Samba servers are not + * allowed without "require strong key = no" + */ + status = NT_STATUS_DOWNGRADE_DETECTED; + tevent_req_nterror(req, status); + netlogon_creds_cli_check_cleanup(req, status); + return; + } + + /* + * If we not require NETLOGON_NEG_SUPPORTS_AES or + * NETLOGON_NEG_STRONG_KEYS, it's ok to ignore + * NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE. + * + * This is needed against NT 4.0 and old Samba servers. + * + * As we're using DCERPC_AUTH_TYPE_SCHANNEL with + * DCERPC_AUTH_LEVEL_INTEGRITY or DCERPC_AUTH_LEVEL_PRIVACY + * we should detect a faked NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE + * with the next request as the sequence number processing + * gets out of sync. + */ + netlogon_creds_cli_check_cleanup(req, status); + tevent_req_done(req); + return; + } + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_check_cleanup(req, status); + return; + } + + if (NT_STATUS_EQUAL(result, NT_STATUS_NOT_IMPLEMENTED)) { + /* + * Note that the negotiated flags are already checked + * for our required flags after the ServerAuthenticate3/2 call. + */ + uint32_t negotiated = state->creds->negotiate_flags; + + if (negotiated & NETLOGON_NEG_SUPPORTS_AES) { + /* + * If we have negotiated NETLOGON_NEG_SUPPORTS_AES + * already, we expect this to work! + */ + status = NT_STATUS_DOWNGRADE_DETECTED; + tevent_req_nterror(req, status); + netlogon_creds_cli_check_cleanup(req, status); + return; + } + + /* + * This is ok, the server does not support + * NETLOGON_NEG_SUPPORTS_AES. + * + * netr_LogonGetCapabilities() was + * netr_LogonDummyRoutine1() before + * NETLOGON_NEG_SUPPORTS_AES was invented. + */ + netlogon_creds_cli_check_cleanup(req, result); + tevent_req_done(req); + return; + } + + ok = netlogon_creds_client_check(state->creds, &state->rep_auth.cred); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_check_cleanup(req, status); + return; + } + + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_check_cleanup(req, result); + return; + } + + if (state->caps.server_capabilities != state->creds->negotiate_flags) { + status = NT_STATUS_DOWNGRADE_DETECTED; + tevent_req_nterror(req, status); + netlogon_creds_cli_check_cleanup(req, status); + return; + } + + /* + * This is the key check that makes this check secure. If we + * get OK here (rather than NOT_SUPPORTED), then the server + * did support AES. If the server only proposed STRONG_KEYS + * and not AES, then it should have failed with + * NOT_IMPLEMENTED. We always send AES as a client, so the + * server should always have returned it. + */ + if (!(state->caps.server_capabilities & NETLOGON_NEG_SUPPORTS_AES)) { + status = NT_STATUS_DOWNGRADE_DETECTED; + tevent_req_nterror(req, status); + netlogon_creds_cli_check_cleanup(req, status); + return; + } + + status = netlogon_creds_cli_store_internal(state->context, + state->creds); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_check_recv(struct tevent_req *req, + union netr_Capabilities *capabilities) +{ + struct netlogon_creds_cli_check_state *state = tevent_req_data( + req, struct netlogon_creds_cli_check_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + netlogon_creds_cli_check_cleanup(req, status); + tevent_req_received(req); + return status; + } + + if (capabilities != NULL) { + *capabilities = state->caps; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_check(struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + union netr_Capabilities *capabilities) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_check_send(frame, ev, context, b); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_check_recv(req, capabilities); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_ServerPasswordSet_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + uint32_t old_timeout; + + char *srv_name_slash; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + struct samr_CryptPassword samr_crypt_password; + struct netr_CryptPassword netr_crypt_password; + struct samr_Password samr_password; + + struct netlogon_creds_CredentialState *creds; + struct netlogon_creds_CredentialState tmp_creds; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; +}; + +static void netlogon_creds_cli_ServerPasswordSet_cleanup(struct tevent_req *req, + NTSTATUS status); +static void netlogon_creds_cli_ServerPasswordSet_locked(struct tevent_req *subreq); + +struct tevent_req *netlogon_creds_cli_ServerPasswordSet_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + const DATA_BLOB *new_password, + const uint32_t *new_version) +{ + struct tevent_req *req; + struct netlogon_creds_cli_ServerPasswordSet_state *state; + struct tevent_req *subreq; + bool ok; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_ServerPasswordSet_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + + if (new_password->length < 14) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + /* + * netr_ServerPasswordSet + */ + mdfour(state->samr_password.hash, new_password->data, new_password->length); + + /* + * netr_ServerPasswordSet2 + */ + ok = set_pw_in_buffer(state->samr_crypt_password.data, + new_password); + if (!ok) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + + if (new_version != NULL) { + struct NL_PASSWORD_VERSION version; + uint32_t len = IVAL(state->samr_crypt_password.data, 512); + uint32_t ofs = 512 - len; + uint8_t *p; + + if (len > 500) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return tevent_req_post(req, ev); + } + ofs -= 12; + + version.ReservedField = 0; + version.PasswordVersionNumber = *new_version; + version.PasswordVersionPresent = + NETLOGON_PASSWORD_VERSION_NUMBER_PRESENT; + + p = state->samr_crypt_password.data + ofs; + SIVAL(p, 0, version.ReservedField); + SIVAL(p, 4, version.PasswordVersionNumber); + SIVAL(p, 8, version.PasswordVersionPresent); + } + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + dcerpc_binding_handle_auth_info(state->binding_handle, + &state->auth_type, + &state->auth_level); + + subreq = netlogon_creds_cli_lock_send(state, state->ev, + state->context); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_ServerPasswordSet_locked, + req); + + return req; +} + +static void netlogon_creds_cli_ServerPasswordSet_cleanup(struct tevent_req *req, + NTSTATUS status) +{ + struct netlogon_creds_cli_ServerPasswordSet_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_ServerPasswordSet_state); + + if (state->creds == NULL) { + return; + } + + dcerpc_binding_handle_set_timeout(state->binding_handle, + state->old_timeout); + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) && + !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) && + !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + TALLOC_FREE(state->creds); + return; + } + + netlogon_creds_cli_delete(state->context, state->creds); + TALLOC_FREE(state->creds); +} + +static void netlogon_creds_cli_ServerPasswordSet_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_ServerPasswordSet_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_ServerPasswordSet_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_ServerPasswordSet_state); + NTSTATUS status; + + status = netlogon_creds_cli_lock_recv(subreq, state, + &state->creds); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + switch (state->auth_level) { + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PRIVACY: + break; + default: + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } else { + uint32_t tmp = state->creds->negotiate_flags; + + if (tmp & NETLOGON_NEG_AUTHENTICATED_RPC) { + /* + * if DCERPC_AUTH_TYPE_SCHANNEL is supported + * it should be used, which means + * we had a chance to verify no downgrade + * happened. + * + * This relies on netlogon_creds_cli_check* + * being called before, as first request after + * the DCERPC bind. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } + + state->old_timeout = dcerpc_binding_handle_set_timeout( + state->binding_handle, 600000); + + /* + * we defer all callbacks in order to cleanup + * the database record. + */ + tevent_req_defer_callback(req, state->ev); + + state->tmp_creds = *state->creds; + status = netlogon_creds_client_authenticator(&state->tmp_creds, + &state->req_auth); + if (tevent_req_nterror(req, status)) { + return; + } + ZERO_STRUCT(state->rep_auth); + + if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_PASSWORD_SET2) { + + if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + status = netlogon_creds_aes_encrypt(&state->tmp_creds, + state->samr_crypt_password.data, + 516); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + } else { + status = netlogon_creds_arcfour_crypt(&state->tmp_creds, + state->samr_crypt_password.data, + 516); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + } + + memcpy(state->netr_crypt_password.data, + state->samr_crypt_password.data, 512); + state->netr_crypt_password.length = + IVAL(state->samr_crypt_password.data, 512); + + subreq = dcerpc_netr_ServerPasswordSet2_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->tmp_creds.account_name, + state->tmp_creds.secure_channel_type, + state->tmp_creds.computer_name, + &state->req_auth, + &state->rep_auth, + &state->netr_crypt_password); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + } else { + status = netlogon_creds_des_encrypt(&state->tmp_creds, + &state->samr_password); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + + subreq = dcerpc_netr_ServerPasswordSet_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->tmp_creds.account_name, + state->tmp_creds.secure_channel_type, + state->tmp_creds.computer_name, + &state->req_auth, + &state->rep_auth, + &state->samr_password); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_ServerPasswordSet_done, + req); +} + +static void netlogon_creds_cli_ServerPasswordSet_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_ServerPasswordSet_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_ServerPasswordSet_state); + NTSTATUS status; + NTSTATUS result; + bool ok; + + if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_PASSWORD_SET2) { + status = dcerpc_netr_ServerPasswordSet2_recv(subreq, state, + &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + } else { + status = dcerpc_netr_ServerPasswordSet_recv(subreq, state, + &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + } + + ok = netlogon_creds_client_check(&state->tmp_creds, + &state->rep_auth.cred); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_ServerPasswordSet_cleanup(req, result); + return; + } + + dcerpc_binding_handle_set_timeout(state->binding_handle, + state->old_timeout); + + *state->creds = state->tmp_creds; + status = netlogon_creds_cli_store(state->context, + state->creds); + TALLOC_FREE(state->creds); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_ServerPasswordSet_recv(struct tevent_req *req) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + netlogon_creds_cli_ServerPasswordSet_cleanup(req, status); + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_ServerPasswordSet( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + const DATA_BLOB *new_password, + const uint32_t *new_version) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_ServerPasswordSet_send(frame, ev, context, b, + new_password, + new_version); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_ServerPasswordSet_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_LogonSamLogon_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + + char *srv_name_slash; + + enum netr_LogonInfoClass logon_level; + const union netr_LogonLevel *const_logon; + union netr_LogonLevel *logon; + uint32_t flags; + + uint16_t validation_level; + union netr_Validation *validation; + uint8_t authoritative; + + /* + * do we need encryption at the application layer? + */ + bool user_encrypt; + bool try_logon_ex; + bool try_validation6; + + /* + * the read only credentials before we started the operation + * used for netr_LogonSamLogonEx() if required (validation_level = 3). + */ + struct netlogon_creds_CredentialState *ro_creds; + + /* + * The (locked) credentials used for the credential chain + * used for netr_LogonSamLogonWithFlags() or + * netr_LogonSamLogonWith(). + */ + struct netlogon_creds_CredentialState *lk_creds; + + /* + * While we have locked the global credentials (lk_creds above) + * we operate an a temporary copy, because a server + * may not support netr_LogonSamLogonWithFlags() and + * didn't process our netr_Authenticator, so we need to + * restart from lk_creds. + */ + struct netlogon_creds_CredentialState tmp_creds; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; +}; + +static void netlogon_creds_cli_LogonSamLogon_start(struct tevent_req *req); +static void netlogon_creds_cli_LogonSamLogon_cleanup(struct tevent_req *req, + NTSTATUS status); + +struct tevent_req *netlogon_creds_cli_LogonSamLogon_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + enum netr_LogonInfoClass logon_level, + const union netr_LogonLevel *logon, + uint32_t flags) +{ + struct tevent_req *req; + struct netlogon_creds_cli_LogonSamLogon_state *state; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_LogonSamLogon_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + + state->logon_level = logon_level; + state->const_logon = logon; + state->flags = flags; + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + switch (logon_level) { + case NetlogonInteractiveInformation: + case NetlogonInteractiveTransitiveInformation: + case NetlogonServiceInformation: + case NetlogonServiceTransitiveInformation: + case NetlogonGenericInformation: + state->user_encrypt = true; + break; + + case NetlogonNetworkInformation: + case NetlogonNetworkTransitiveInformation: + break; + } + + state->validation = talloc_zero(state, union netr_Validation); + if (tevent_req_nomem(state->validation, req)) { + return tevent_req_post(req, ev); + } + + netlogon_creds_cli_LogonSamLogon_start(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + /* + * we defer all callbacks in order to cleanup + * the database record. + */ + tevent_req_defer_callback(req, state->ev); + return req; +} + +static void netlogon_creds_cli_LogonSamLogon_cleanup(struct tevent_req *req, + NTSTATUS status) +{ + struct netlogon_creds_cli_LogonSamLogon_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_LogonSamLogon_state); + + if (state->lk_creds == NULL) { + return; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + /* + * This is a hack to recover from a bug in old + * Samba servers, when LogonSamLogonEx() fails: + * + * api_net_sam_logon_ex: Failed to marshall NET_R_SAM_LOGON_EX. + * + * All following request will get NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE. + * + * A second bug generates NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE, + * instead of NT_STATUS_ACCESS_DENIED or NT_STATUS_RPC_SEC_PKG_ERROR + * If the sign/seal check fails. + * + * In that case we need to cleanup the netlogon session. + * + * It's the job of the caller to disconnect the current + * connection, if netlogon_creds_cli_LogonSamLogon() + * returns NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE. + */ + if (!state->context->server.try_logon_with) { + status = NT_STATUS_NETWORK_ACCESS_DENIED; + } + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) && + !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) && + !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + TALLOC_FREE(state->lk_creds); + return; + } + + netlogon_creds_cli_delete(state->context, state->lk_creds); + TALLOC_FREE(state->lk_creds); +} + +static void netlogon_creds_cli_LogonSamLogon_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_LogonSamLogon_start(struct tevent_req *req) +{ + struct netlogon_creds_cli_LogonSamLogon_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_LogonSamLogon_state); + struct tevent_req *subreq; + NTSTATUS status; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + TALLOC_FREE(state->ro_creds); + TALLOC_FREE(state->logon); + ZERO_STRUCTP(state->validation); + + dcerpc_binding_handle_auth_info(state->binding_handle, + &auth_type, &auth_level); + + state->try_logon_ex = state->context->server.try_logon_ex; + state->try_validation6 = state->context->server.try_validation6; + + if (auth_type != DCERPC_AUTH_TYPE_SCHANNEL) { + state->try_logon_ex = false; + } + + if (auth_level != DCERPC_AUTH_LEVEL_PRIVACY) { + state->try_validation6 = false; + } + + if (state->try_logon_ex) { + if (state->try_validation6) { + state->validation_level = 6; + } else { + state->validation_level = 3; + state->user_encrypt = true; + } + + state->logon = netlogon_creds_shallow_copy_logon(state, + state->logon_level, + state->const_logon); + if (tevent_req_nomem(state->logon, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + if (state->user_encrypt) { + status = netlogon_creds_cli_get(state->context, + state, + &state->ro_creds); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + status = netlogon_creds_encrypt_samlogon_logon(state->ro_creds, + state->logon_level, + state->logon); + if (!NT_STATUS_IS_OK(status)) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + } + + subreq = dcerpc_netr_LogonSamLogonEx_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->context->client.computer, + state->logon_level, + state->logon, + state->validation_level, + state->validation, + &state->authoritative, + &state->flags); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + tevent_req_set_callback(subreq, + netlogon_creds_cli_LogonSamLogon_done, + req); + return; + } + + if (state->lk_creds == NULL) { + subreq = netlogon_creds_cli_lock_send(state, state->ev, + state->context); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + tevent_req_set_callback(subreq, + netlogon_creds_cli_LogonSamLogon_done, + req); + return; + } + + state->tmp_creds = *state->lk_creds; + status = netlogon_creds_client_authenticator(&state->tmp_creds, + &state->req_auth); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + ZERO_STRUCT(state->rep_auth); + + state->logon = netlogon_creds_shallow_copy_logon(state, + state->logon_level, + state->const_logon); + if (tevent_req_nomem(state->logon, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + status = netlogon_creds_encrypt_samlogon_logon(&state->tmp_creds, + state->logon_level, + state->logon); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + state->validation_level = 3; + + if (state->context->server.try_logon_with) { + subreq = dcerpc_netr_LogonSamLogonWithFlags_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->context->client.computer, + &state->req_auth, + &state->rep_auth, + state->logon_level, + state->logon, + state->validation_level, + state->validation, + &state->authoritative, + &state->flags); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + } else { + state->flags = 0; + + subreq = dcerpc_netr_LogonSamLogon_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->context->client.computer, + &state->req_auth, + &state->rep_auth, + state->logon_level, + state->logon, + state->validation_level, + state->validation, + &state->authoritative); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_LogonSamLogon_done, + req); +} + +static void netlogon_creds_cli_LogonSamLogon_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_LogonSamLogon_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_LogonSamLogon_state); + NTSTATUS status; + NTSTATUS result; + bool ok; + + if (state->try_logon_ex) { + status = dcerpc_netr_LogonSamLogonEx_recv(subreq, + state->validation, + &result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + state->context->server.try_validation6 = false; + state->context->server.try_logon_ex = false; + netlogon_creds_cli_LogonSamLogon_start(req); + return; + } + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + if ((state->validation_level == 6) && + (NT_STATUS_EQUAL(result, NT_STATUS_INVALID_INFO_CLASS) || + NT_STATUS_EQUAL(result, NT_STATUS_INVALID_PARAMETER) || + NT_STATUS_EQUAL(result, NT_STATUS_BUFFER_TOO_SMALL))) + { + state->context->server.try_validation6 = false; + netlogon_creds_cli_LogonSamLogon_start(req); + return; + } + + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, result); + return; + } + + if (state->ro_creds == NULL) { + tevent_req_done(req); + return; + } + + ok = netlogon_creds_cli_validate(state->context, state->ro_creds); + if (!ok) { + /* + * We got a race, lets retry with on authenticator + * protection. + * + * netlogon_creds_cli_LogonSamLogon_start() + * will TALLOC_FREE(state->ro_creds); + */ + state->try_logon_ex = false; + netlogon_creds_cli_LogonSamLogon_start(req); + return; + } + + status = netlogon_creds_decrypt_samlogon_validation(state->ro_creds, + state->validation_level, + state->validation); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + tevent_req_done(req); + return; + } + + if (state->lk_creds == NULL) { + status = netlogon_creds_cli_lock_recv(subreq, state, + &state->lk_creds); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + netlogon_creds_cli_LogonSamLogon_start(req); + return; + } + + if (state->context->server.try_logon_with) { + status = dcerpc_netr_LogonSamLogonWithFlags_recv(subreq, + state->validation, + &result); + TALLOC_FREE(subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_RPC_PROCNUM_OUT_OF_RANGE)) { + state->context->server.try_logon_with = false; + netlogon_creds_cli_LogonSamLogon_start(req); + return; + } + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + } else { + status = dcerpc_netr_LogonSamLogon_recv(subreq, + state->validation, + &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + } + + ok = netlogon_creds_client_check(&state->tmp_creds, + &state->rep_auth.cred); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + *state->lk_creds = state->tmp_creds; + status = netlogon_creds_cli_store(state->context, + state->lk_creds); + TALLOC_FREE(state->lk_creds); + + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + return; + } + + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, result); + return; + } + + status = netlogon_creds_decrypt_samlogon_validation(&state->tmp_creds, + state->validation_level, + state->validation); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, result); + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_LogonSamLogon_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint16_t *validation_level, + union netr_Validation **validation, + uint8_t *authoritative, + uint32_t *flags) +{ + struct netlogon_creds_cli_LogonSamLogon_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_LogonSamLogon_state); + NTSTATUS status; + + /* authoritative is also returned on error */ + *authoritative = state->authoritative; + + if (tevent_req_is_nterror(req, &status)) { + netlogon_creds_cli_LogonSamLogon_cleanup(req, status); + tevent_req_received(req); + return status; + } + + *validation_level = state->validation_level; + *validation = talloc_move(mem_ctx, &state->validation); + *flags = state->flags; + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_LogonSamLogon( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + enum netr_LogonInfoClass logon_level, + const union netr_LogonLevel *logon, + TALLOC_CTX *mem_ctx, + uint16_t *validation_level, + union netr_Validation **validation, + uint8_t *authoritative, + uint32_t *flags) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_LogonSamLogon_send(frame, ev, context, b, + logon_level, logon, + *flags); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_LogonSamLogon_recv(req, mem_ctx, + validation_level, + validation, + authoritative, + flags); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + + char *srv_name_slash; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + const char *site_name; + uint32_t dns_ttl; + struct NL_DNS_NAME_INFO_ARRAY *dns_names; + + struct netlogon_creds_CredentialState *creds; + struct netlogon_creds_CredentialState tmp_creds; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; +}; + +static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(struct tevent_req *req, + NTSTATUS status); +static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked(struct tevent_req *subreq); + +struct tevent_req *netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + const char *site_name, + uint32_t dns_ttl, + struct NL_DNS_NAME_INFO_ARRAY *dns_names) +{ + struct tevent_req *req; + struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + state->site_name = site_name; + state->dns_ttl = dns_ttl; + state->dns_names = dns_names; + + dcerpc_binding_handle_auth_info(state->binding_handle, + &state->auth_type, + &state->auth_level); + + subreq = netlogon_creds_cli_lock_send(state, state->ev, + state->context); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked, + req); + + return req; +} + +static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(struct tevent_req *req, + NTSTATUS status) +{ + struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state); + + if (state->creds == NULL) { + return; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) && + !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) && + !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + TALLOC_FREE(state->creds); + return; + } + + netlogon_creds_cli_delete(state->context, state->creds); + TALLOC_FREE(state->creds); +} + +static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state); + NTSTATUS status; + + status = netlogon_creds_cli_lock_recv(subreq, state, + &state->creds); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + switch (state->auth_level) { + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PRIVACY: + break; + default: + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } else { + uint32_t tmp = state->creds->negotiate_flags; + + if (tmp & NETLOGON_NEG_AUTHENTICATED_RPC) { + /* + * if DCERPC_AUTH_TYPE_SCHANNEL is supported + * it should be used, which means + * we had a chance to verify no downgrade + * happened. + * + * This relies on netlogon_creds_cli_check* + * being called before, as first request after + * the DCERPC bind. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } + + /* + * we defer all callbacks in order to cleanup + * the database record. + */ + tevent_req_defer_callback(req, state->ev); + + state->tmp_creds = *state->creds; + status = netlogon_creds_client_authenticator(&state->tmp_creds, + &state->req_auth); + if (tevent_req_nterror(req, status)) { + return; + } + ZERO_STRUCT(state->rep_auth); + + subreq = dcerpc_netr_DsrUpdateReadOnlyServerDnsRecords_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->tmp_creds.computer_name, + &state->req_auth, + &state->rep_auth, + state->site_name, + state->dns_ttl, + state->dns_names); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status); + return; + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done, + req); +} + +static void netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_state); + NTSTATUS status; + NTSTATUS result; + bool ok; + + /* + * We use state->dns_names as the memory context, as this is + * the only in/out variable and it has been overwritten by the + * out parameter from the server. + * + * We need to preserve the return value until the caller can use it. + */ + status = dcerpc_netr_DsrUpdateReadOnlyServerDnsRecords_recv(subreq, state->dns_names, + &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status); + return; + } + + ok = netlogon_creds_client_check(&state->tmp_creds, + &state->rep_auth.cred); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status); + return; + } + + *state->creds = state->tmp_creds; + status = netlogon_creds_cli_store(state->context, + state->creds); + TALLOC_FREE(state->creds); + + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status); + return; + } + + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, result); + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_recv(struct tevent_req *req) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_cleanup(req, status); + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + const char *site_name, + uint32_t dns_ttl, + struct NL_DNS_NAME_INFO_ARRAY *dns_names) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_send(frame, ev, context, b, + site_name, + dns_ttl, + dns_names); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_ServerGetTrustInfo_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + + char *srv_name_slash; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + struct samr_Password new_owf_password; + struct samr_Password old_owf_password; + struct netr_TrustInfo *trust_info; + + struct netlogon_creds_CredentialState *creds; + struct netlogon_creds_CredentialState tmp_creds; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; +}; + +static void netlogon_creds_cli_ServerGetTrustInfo_cleanup(struct tevent_req *req, + NTSTATUS status); +static void netlogon_creds_cli_ServerGetTrustInfo_locked(struct tevent_req *subreq); + +struct tevent_req *netlogon_creds_cli_ServerGetTrustInfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b) +{ + struct tevent_req *req; + struct netlogon_creds_cli_ServerGetTrustInfo_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_ServerGetTrustInfo_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + dcerpc_binding_handle_auth_info(state->binding_handle, + &state->auth_type, + &state->auth_level); + + subreq = netlogon_creds_cli_lock_send(state, state->ev, + state->context); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_ServerGetTrustInfo_locked, + req); + + return req; +} + +static void netlogon_creds_cli_ServerGetTrustInfo_cleanup(struct tevent_req *req, + NTSTATUS status) +{ + struct netlogon_creds_cli_ServerGetTrustInfo_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_ServerGetTrustInfo_state); + + if (state->creds == NULL) { + return; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) && + !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) && + !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + TALLOC_FREE(state->creds); + return; + } + + netlogon_creds_cli_delete(state->context, state->creds); + TALLOC_FREE(state->creds); +} + +static void netlogon_creds_cli_ServerGetTrustInfo_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_ServerGetTrustInfo_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_ServerGetTrustInfo_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_ServerGetTrustInfo_state); + NTSTATUS status; + + status = netlogon_creds_cli_lock_recv(subreq, state, + &state->creds); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + switch (state->auth_level) { + case DCERPC_AUTH_LEVEL_PRIVACY: + break; + default: + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } else { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + + /* + * we defer all callbacks in order to cleanup + * the database record. + */ + tevent_req_defer_callback(req, state->ev); + + state->tmp_creds = *state->creds; + status = netlogon_creds_client_authenticator(&state->tmp_creds, + &state->req_auth); + if (tevent_req_nterror(req, status)) { + return; + } + ZERO_STRUCT(state->rep_auth); + + subreq = dcerpc_netr_ServerGetTrustInfo_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->tmp_creds.account_name, + state->tmp_creds.secure_channel_type, + state->tmp_creds.computer_name, + &state->req_auth, + &state->rep_auth, + &state->new_owf_password, + &state->old_owf_password, + &state->trust_info); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status); + return; + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_ServerGetTrustInfo_done, + req); +} + +static void netlogon_creds_cli_ServerGetTrustInfo_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_ServerGetTrustInfo_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_ServerGetTrustInfo_state); + NTSTATUS status; + NTSTATUS result; + const struct samr_Password zero = {}; + bool cmp; + bool ok; + + /* + * We use state->dns_names as the memory context, as this is + * the only in/out variable and it has been overwritten by the + * out parameter from the server. + * + * We need to preserve the return value until the caller can use it. + */ + status = dcerpc_netr_ServerGetTrustInfo_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status); + return; + } + + ok = netlogon_creds_client_check(&state->tmp_creds, + &state->rep_auth.cred); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status); + return; + } + + cmp = mem_equal_const_time(state->new_owf_password.hash, + zero.hash, sizeof(zero.hash)); + if (!cmp) { + status = netlogon_creds_des_decrypt(&state->tmp_creds, + &state->new_owf_password); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status); + return; + } + } + cmp = mem_equal_const_time(state->old_owf_password.hash, + zero.hash, sizeof(zero.hash)); + if (!cmp) { + status = netlogon_creds_des_decrypt(&state->tmp_creds, + &state->old_owf_password); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status); + return; + } + } + + *state->creds = state->tmp_creds; + status = netlogon_creds_cli_store(state->context, + state->creds); + TALLOC_FREE(state->creds); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status); + return; + } + + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, result); + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_ServerGetTrustInfo_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct samr_Password *new_owf_password, + struct samr_Password *old_owf_password, + struct netr_TrustInfo **trust_info) +{ + struct netlogon_creds_cli_ServerGetTrustInfo_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_ServerGetTrustInfo_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + netlogon_creds_cli_ServerGetTrustInfo_cleanup(req, status); + tevent_req_received(req); + return status; + } + + if (new_owf_password != NULL) { + *new_owf_password = state->new_owf_password; + } + if (old_owf_password != NULL) { + *old_owf_password = state->old_owf_password; + } + if (trust_info != NULL) { + *trust_info = talloc_move(mem_ctx, &state->trust_info); + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_ServerGetTrustInfo( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct samr_Password *new_owf_password, + struct samr_Password *old_owf_password, + struct netr_TrustInfo **trust_info) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_ServerGetTrustInfo_send(frame, ev, context, b); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_ServerGetTrustInfo_recv(req, + mem_ctx, + new_owf_password, + old_owf_password, + trust_info); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_GetForestTrustInformation_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + + char *srv_name_slash; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + uint32_t flags; + struct lsa_ForestTrustInformation *forest_trust_info; + + struct netlogon_creds_CredentialState *creds; + struct netlogon_creds_CredentialState tmp_creds; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; +}; + +static void netlogon_creds_cli_GetForestTrustInformation_cleanup(struct tevent_req *req, + NTSTATUS status); +static void netlogon_creds_cli_GetForestTrustInformation_locked(struct tevent_req *subreq); + +struct tevent_req *netlogon_creds_cli_GetForestTrustInformation_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b) +{ + struct tevent_req *req; + struct netlogon_creds_cli_GetForestTrustInformation_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_GetForestTrustInformation_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + state->flags = 0; + + dcerpc_binding_handle_auth_info(state->binding_handle, + &state->auth_type, + &state->auth_level); + + subreq = netlogon_creds_cli_lock_send(state, state->ev, + state->context); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_GetForestTrustInformation_locked, + req); + + return req; +} + +static void netlogon_creds_cli_GetForestTrustInformation_cleanup(struct tevent_req *req, + NTSTATUS status) +{ + struct netlogon_creds_cli_GetForestTrustInformation_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_GetForestTrustInformation_state); + + if (state->creds == NULL) { + return; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) && + !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) && + !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + TALLOC_FREE(state->creds); + return; + } + + netlogon_creds_cli_delete(state->context, state->creds); + TALLOC_FREE(state->creds); +} + +static void netlogon_creds_cli_GetForestTrustInformation_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_GetForestTrustInformation_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_GetForestTrustInformation_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_GetForestTrustInformation_state); + NTSTATUS status; + + status = netlogon_creds_cli_lock_recv(subreq, state, + &state->creds); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + switch (state->auth_level) { + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PRIVACY: + break; + default: + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } else { + uint32_t tmp = state->creds->negotiate_flags; + + if (tmp & NETLOGON_NEG_AUTHENTICATED_RPC) { + /* + * if DCERPC_AUTH_TYPE_SCHANNEL is supported + * it should be used, which means + * we had a chance to verify no downgrade + * happened. + * + * This relies on netlogon_creds_cli_check* + * being called before, as first request after + * the DCERPC bind. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } + + /* + * we defer all callbacks in order to cleanup + * the database record. + */ + tevent_req_defer_callback(req, state->ev); + + state->tmp_creds = *state->creds; + status = netlogon_creds_client_authenticator(&state->tmp_creds, + &state->req_auth); + if (tevent_req_nterror(req, status)) { + return; + } + ZERO_STRUCT(state->rep_auth); + + subreq = dcerpc_netr_GetForestTrustInformation_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->tmp_creds.computer_name, + &state->req_auth, + &state->rep_auth, + state->flags, + &state->forest_trust_info); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status); + return; + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_GetForestTrustInformation_done, + req); +} + +static void netlogon_creds_cli_GetForestTrustInformation_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_GetForestTrustInformation_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_GetForestTrustInformation_state); + NTSTATUS status; + NTSTATUS result; + bool ok; + + /* + * We use state->dns_names as the memory context, as this is + * the only in/out variable and it has been overwritten by the + * out parameter from the server. + * + * We need to preserve the return value until the caller can use it. + */ + status = dcerpc_netr_GetForestTrustInformation_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status); + return; + } + + ok = netlogon_creds_client_check(&state->tmp_creds, + &state->rep_auth.cred); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status); + return; + } + + *state->creds = state->tmp_creds; + status = netlogon_creds_cli_store(state->context, + state->creds); + TALLOC_FREE(state->creds); + + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status); + return; + } + + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_GetForestTrustInformation_cleanup(req, result); + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_GetForestTrustInformation_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct lsa_ForestTrustInformation **forest_trust_info) +{ + struct netlogon_creds_cli_GetForestTrustInformation_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_GetForestTrustInformation_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + netlogon_creds_cli_GetForestTrustInformation_cleanup(req, status); + tevent_req_received(req); + return status; + } + + *forest_trust_info = talloc_move(mem_ctx, &state->forest_trust_info); + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_GetForestTrustInformation( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct lsa_ForestTrustInformation **forest_trust_info) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_GetForestTrustInformation_send(frame, ev, context, b); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_GetForestTrustInformation_recv(req, + mem_ctx, + forest_trust_info); + fail: + TALLOC_FREE(frame); + return status; +} +struct netlogon_creds_cli_SendToSam_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + + char *srv_name_slash; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + DATA_BLOB opaque; + + struct netlogon_creds_CredentialState *creds; + struct netlogon_creds_CredentialState tmp_creds; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; +}; + +static void netlogon_creds_cli_SendToSam_cleanup(struct tevent_req *req, + NTSTATUS status); +static void netlogon_creds_cli_SendToSam_locked(struct tevent_req *subreq); + +struct tevent_req *netlogon_creds_cli_SendToSam_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + struct netr_SendToSamBase *message) +{ + struct tevent_req *req; + struct netlogon_creds_cli_SendToSam_state *state; + struct tevent_req *subreq; + enum ndr_err_code ndr_err; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_SendToSam_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + ndr_err = ndr_push_struct_blob(&state->opaque, mem_ctx, message, + (ndr_push_flags_fn_t)ndr_push_netr_SendToSamBase); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + NTSTATUS status = ndr_map_error2ntstatus(ndr_err); + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + dcerpc_binding_handle_auth_info(state->binding_handle, + &state->auth_type, + &state->auth_level); + + subreq = netlogon_creds_cli_lock_send(state, state->ev, + state->context); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_SendToSam_locked, + req); + + return req; +} + +static void netlogon_creds_cli_SendToSam_cleanup(struct tevent_req *req, + NTSTATUS status) +{ + struct netlogon_creds_cli_SendToSam_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_SendToSam_state); + + if (state->creds == NULL) { + return; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) && + !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) && + !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + TALLOC_FREE(state->creds); + return; + } + + netlogon_creds_cli_delete(state->context, state->creds); + TALLOC_FREE(state->creds); +} + +static void netlogon_creds_cli_SendToSam_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_SendToSam_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_SendToSam_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_SendToSam_state); + NTSTATUS status; + + status = netlogon_creds_cli_lock_recv(subreq, state, + &state->creds); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + switch (state->auth_level) { + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PRIVACY: + break; + default: + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } else { + uint32_t tmp = state->creds->negotiate_flags; + + if (tmp & NETLOGON_NEG_AUTHENTICATED_RPC) { + /* + * if DCERPC_AUTH_TYPE_SCHANNEL is supported + * it should be used, which means + * we had a chance to verify no downgrade + * happened. + * + * This relies on netlogon_creds_cli_check* + * being called before, as first request after + * the DCERPC bind. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } + + /* + * we defer all callbacks in order to cleanup + * the database record. + */ + tevent_req_defer_callback(req, state->ev); + + state->tmp_creds = *state->creds; + status = netlogon_creds_client_authenticator(&state->tmp_creds, + &state->req_auth); + if (tevent_req_nterror(req, status)) { + return; + } + ZERO_STRUCT(state->rep_auth); + + if (state->tmp_creds.negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + status = netlogon_creds_aes_encrypt(&state->tmp_creds, + state->opaque.data, + state->opaque.length); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_SendToSam_cleanup(req, status); + return; + } + } else { + status = netlogon_creds_arcfour_crypt(&state->tmp_creds, + state->opaque.data, + state->opaque.length); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_SendToSam_cleanup(req, status); + return; + } + } + + subreq = dcerpc_netr_NetrLogonSendToSam_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->tmp_creds.computer_name, + &state->req_auth, + &state->rep_auth, + state->opaque.data, + state->opaque.length); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_SendToSam_cleanup(req, status); + return; + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_SendToSam_done, + req); +} + +static void netlogon_creds_cli_SendToSam_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_SendToSam_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_SendToSam_state); + NTSTATUS status; + NTSTATUS result; + bool ok; + + status = dcerpc_netr_NetrLogonSendToSam_recv(subreq, state, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_SendToSam_cleanup(req, status); + return; + } + + ok = netlogon_creds_client_check(&state->tmp_creds, + &state->rep_auth.cred); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_SendToSam_cleanup(req, status); + return; + } + + *state->creds = state->tmp_creds; + status = netlogon_creds_cli_store(state->context, + state->creds); + TALLOC_FREE(state->creds); + + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_SendToSam_cleanup(req, status); + return; + } + + /* + * Creds must be stored before we send back application errors + * e.g. NT_STATUS_NOT_IMPLEMENTED + */ + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_SendToSam_cleanup(req, result); + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_SendToSam_recv(struct tevent_req *req) +{ + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + netlogon_creds_cli_SendToSam_cleanup(req, status); + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_SendToSam(struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + struct netr_SendToSamBase *message) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_NO_MEMORY; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_SendToSam_send(frame, ev, context, b, message); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_SendToSam_recv(req); + fail: + TALLOC_FREE(frame); + return status; +} + +struct netlogon_creds_cli_LogonGetDomainInfo_state { + struct tevent_context *ev; + struct netlogon_creds_cli_context *context; + struct dcerpc_binding_handle *binding_handle; + + char *srv_name_slash; + enum dcerpc_AuthType auth_type; + enum dcerpc_AuthLevel auth_level; + + uint32_t level; + union netr_WorkstationInfo *query; + union netr_DomainInfo *info; + + struct netlogon_creds_CredentialState *creds; + struct netlogon_creds_CredentialState tmp_creds; + struct netr_Authenticator req_auth; + struct netr_Authenticator rep_auth; +}; + +static void netlogon_creds_cli_LogonGetDomainInfo_cleanup(struct tevent_req *req, + NTSTATUS status); +static void netlogon_creds_cli_LogonGetDomainInfo_locked(struct tevent_req *subreq); + +struct tevent_req *netlogon_creds_cli_LogonGetDomainInfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + uint32_t level, + union netr_WorkstationInfo *query) +{ + struct tevent_req *req; + struct netlogon_creds_cli_LogonGetDomainInfo_state *state; + struct tevent_req *subreq; + + req = tevent_req_create(mem_ctx, &state, + struct netlogon_creds_cli_LogonGetDomainInfo_state); + if (req == NULL) { + return NULL; + } + + state->ev = ev; + state->context = context; + state->binding_handle = b; + + state->srv_name_slash = talloc_asprintf(state, "\\\\%s", + context->server.computer); + if (tevent_req_nomem(state->srv_name_slash, req)) { + return tevent_req_post(req, ev); + } + + state->level = level; + state->query = query; + state->info = talloc_zero(state, union netr_DomainInfo); + if (tevent_req_nomem(state->info, req)) { + return tevent_req_post(req, ev); + } + + dcerpc_binding_handle_auth_info(state->binding_handle, + &state->auth_type, + &state->auth_level); + + subreq = netlogon_creds_cli_lock_send(state, state->ev, + state->context); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_LogonGetDomainInfo_locked, + req); + + return req; +} + +static void netlogon_creds_cli_LogonGetDomainInfo_cleanup(struct tevent_req *req, + NTSTATUS status) +{ + struct netlogon_creds_cli_LogonGetDomainInfo_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_LogonGetDomainInfo_state); + + if (state->creds == NULL) { + return; + } + + if (!NT_STATUS_EQUAL(status, NT_STATUS_NETWORK_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT) && + !NT_STATUS_EQUAL(status, NT_STATUS_DOWNGRADE_DETECTED) && + !NT_STATUS_EQUAL(status, NT_STATUS_ACCESS_DENIED) && + !NT_STATUS_EQUAL(status, NT_STATUS_RPC_SEC_PKG_ERROR)) { + TALLOC_FREE(state->creds); + return; + } + + netlogon_creds_cli_delete(state->context, state->creds); +} + +static void netlogon_creds_cli_LogonGetDomainInfo_done(struct tevent_req *subreq); + +static void netlogon_creds_cli_LogonGetDomainInfo_locked(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_LogonGetDomainInfo_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_LogonGetDomainInfo_state); + NTSTATUS status; + + status = netlogon_creds_cli_lock_recv(subreq, state, + &state->creds); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + return; + } + + if (state->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + switch (state->auth_level) { + case DCERPC_AUTH_LEVEL_INTEGRITY: + case DCERPC_AUTH_LEVEL_PRIVACY: + break; + default: + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } else { + uint32_t tmp = state->creds->negotiate_flags; + + if (tmp & NETLOGON_NEG_AUTHENTICATED_RPC) { + /* + * if DCERPC_AUTH_TYPE_SCHANNEL is supported + * it should be used, which means + * we had a chance to verify no downgrade + * happened. + * + * This relies on netlogon_creds_cli_check* + * being called before, as first request after + * the DCERPC bind. + */ + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER_MIX); + return; + } + } + + /* + * we defer all callbacks in order to cleanup + * the database record. + */ + tevent_req_defer_callback(req, state->ev); + + state->tmp_creds = *state->creds; + status = netlogon_creds_client_authenticator(&state->tmp_creds, + &state->req_auth); + if (tevent_req_nterror(req, status)) { + return; + } + ZERO_STRUCT(state->rep_auth); + + subreq = dcerpc_netr_LogonGetDomainInfo_send(state, state->ev, + state->binding_handle, + state->srv_name_slash, + state->tmp_creds.computer_name, + &state->req_auth, + &state->rep_auth, + state->level, + state->query, + state->info); + if (tevent_req_nomem(subreq, req)) { + status = NT_STATUS_NO_MEMORY; + netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status); + return; + } + + tevent_req_set_callback(subreq, + netlogon_creds_cli_LogonGetDomainInfo_done, + req); +} + +static void netlogon_creds_cli_LogonGetDomainInfo_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct netlogon_creds_cli_LogonGetDomainInfo_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_LogonGetDomainInfo_state); + NTSTATUS status; + NTSTATUS result; + bool ok; + + /* + * We use state->dns_names as the memory context, as this is + * the only in/out variable and it has been overwritten by the + * out parameter from the server. + * + * We need to preserve the return value until the caller can use it. + */ + status = dcerpc_netr_LogonGetDomainInfo_recv(subreq, state->info, &result); + TALLOC_FREE(subreq); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status); + return; + } + + ok = netlogon_creds_client_check(&state->tmp_creds, + &state->rep_auth.cred); + if (!ok) { + status = NT_STATUS_ACCESS_DENIED; + tevent_req_nterror(req, status); + netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status); + return; + } + + if (tevent_req_nterror(req, result)) { + netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, result); + return; + } + + *state->creds = state->tmp_creds; + status = netlogon_creds_cli_store(state->context, + state->creds); + if (tevent_req_nterror(req, status)) { + netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status); + return; + } + + tevent_req_done(req); +} + +NTSTATUS netlogon_creds_cli_LogonGetDomainInfo_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + union netr_DomainInfo **info) +{ + struct netlogon_creds_cli_LogonGetDomainInfo_state *state = + tevent_req_data(req, + struct netlogon_creds_cli_LogonGetDomainInfo_state); + NTSTATUS status; + + if (tevent_req_is_nterror(req, &status)) { + netlogon_creds_cli_LogonGetDomainInfo_cleanup(req, status); + tevent_req_received(req); + return status; + } + + *info = talloc_move(mem_ctx, &state->info); + + tevent_req_received(req); + return NT_STATUS_OK; +} + +NTSTATUS netlogon_creds_cli_LogonGetDomainInfo( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + uint32_t level, + union netr_WorkstationInfo *query, + union netr_DomainInfo **info) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct tevent_context *ev; + struct tevent_req *req; + NTSTATUS status = NT_STATUS_OK; + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + goto fail; + } + req = netlogon_creds_cli_LogonGetDomainInfo_send(frame, ev, context, b, + level, query); + if (req == NULL) { + goto fail; + } + if (!tevent_req_poll_ntstatus(req, ev, &status)) { + goto fail; + } + status = netlogon_creds_cli_LogonGetDomainInfo_recv(req, + mem_ctx, + info); + fail: + TALLOC_FREE(frame); + return status; +} diff --git a/libcli/auth/netlogon_creds_cli.h b/libcli/auth/netlogon_creds_cli.h new file mode 100644 index 0000000..600242e --- /dev/null +++ b/libcli/auth/netlogon_creds_cli.h @@ -0,0 +1,236 @@ +/* + Unix SMB/CIFS implementation. + + module to store/fetch session keys for the schannel client + + Copyright (C) Stefan Metzmacher 2013 + + 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/>. +*/ + +#ifndef NETLOGON_CREDS_CLI_H +#define NETLOGON_CREDS_CLI_H + +#include "librpc/gen_ndr/dcerpc.h" +#include "librpc/gen_ndr/schannel.h" + +struct netlogon_creds_cli_context; +struct cli_credentials; +struct messaging_context; +struct dcerpc_binding_handle; +struct db_context; + +NTSTATUS netlogon_creds_cli_set_global_db(struct loadparm_context *lp_ctx, struct db_context **db); +NTSTATUS netlogon_creds_cli_open_global_db(struct loadparm_context *lp_ctx); +void netlogon_creds_cli_close_global_db(void); + +void netlogon_creds_cli_warn_options(struct loadparm_context *lp_ctx); + +NTSTATUS netlogon_creds_cli_context_global(struct loadparm_context *lp_ctx, + struct messaging_context *msg_ctx, + const char *client_account, + enum netr_SchannelType type, + const char *server_computer, + const char *server_netbios_domain, + const char *server_dns_domain, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_cli_context **_context); +NTSTATUS netlogon_creds_bind_cli_credentials( + struct netlogon_creds_cli_context *context, TALLOC_CTX *mem_ctx, + struct cli_credentials **pcli_creds); + +char *netlogon_creds_cli_debug_string( + const struct netlogon_creds_cli_context *context, + TALLOC_CTX *mem_ctx); + +enum dcerpc_AuthLevel netlogon_creds_cli_auth_level( + struct netlogon_creds_cli_context *context); + +NTSTATUS netlogon_creds_cli_get(struct netlogon_creds_cli_context *context, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState **_creds); +bool netlogon_creds_cli_validate(struct netlogon_creds_cli_context *context, + const struct netlogon_creds_CredentialState *creds1); + +NTSTATUS netlogon_creds_cli_store(struct netlogon_creds_cli_context *context, + struct netlogon_creds_CredentialState *creds); +NTSTATUS netlogon_creds_cli_delete(struct netlogon_creds_cli_context *context, + struct netlogon_creds_CredentialState *creds); +NTSTATUS netlogon_creds_cli_delete_lck( + struct netlogon_creds_cli_context *context); + +struct tevent_req *netlogon_creds_cli_lock_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context); +NTSTATUS netlogon_creds_cli_lock_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState **creds); +NTSTATUS netlogon_creds_cli_lock(struct netlogon_creds_cli_context *context, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState **creds); + +struct netlogon_creds_cli_lck; + +enum netlogon_creds_cli_lck_type { + NETLOGON_CREDS_CLI_LCK_NONE, + NETLOGON_CREDS_CLI_LCK_SHARED, + NETLOGON_CREDS_CLI_LCK_EXCLUSIVE, +}; + +struct tevent_req *netlogon_creds_cli_lck_send( + TALLOC_CTX *mem_ctx, struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + enum netlogon_creds_cli_lck_type type); +NTSTATUS netlogon_creds_cli_lck_recv( + struct tevent_req *req, TALLOC_CTX *mem_ctx, + struct netlogon_creds_cli_lck **lck); +NTSTATUS netlogon_creds_cli_lck( + struct netlogon_creds_cli_context *context, + enum netlogon_creds_cli_lck_type type, + TALLOC_CTX *mem_ctx, struct netlogon_creds_cli_lck **lck); + +struct tevent_req *netlogon_creds_cli_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + uint8_t num_nt_hashes, + const struct samr_Password * const *nt_hashes); +NTSTATUS netlogon_creds_cli_auth_recv(struct tevent_req *req, + uint8_t *idx_nt_hashes); +NTSTATUS netlogon_creds_cli_auth(struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + uint8_t num_nt_hashes, + const struct samr_Password * const *nt_hashes, + uint8_t *idx_nt_hashes); + +struct tevent_req *netlogon_creds_cli_check_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b); +NTSTATUS netlogon_creds_cli_check_recv(struct tevent_req *req, + union netr_Capabilities *capabilities); +NTSTATUS netlogon_creds_cli_check(struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + union netr_Capabilities *capabilities); + +struct tevent_req *netlogon_creds_cli_ServerPasswordSet_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + const DATA_BLOB *new_password, + const uint32_t *new_version); +NTSTATUS netlogon_creds_cli_ServerPasswordSet_recv(struct tevent_req *req); +NTSTATUS netlogon_creds_cli_ServerPasswordSet( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + const DATA_BLOB *new_password, + const uint32_t *new_version); + +struct tevent_req *netlogon_creds_cli_LogonSamLogon_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + enum netr_LogonInfoClass logon_level, + const union netr_LogonLevel *logon, + uint32_t flags); +NTSTATUS netlogon_creds_cli_LogonSamLogon_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint16_t *validation_level, + union netr_Validation **validation, + uint8_t *authoritative, + uint32_t *flags); +NTSTATUS netlogon_creds_cli_LogonSamLogon( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + enum netr_LogonInfoClass logon_level, + const union netr_LogonLevel *logon, + TALLOC_CTX *mem_ctx, + uint16_t *validation_level, + union netr_Validation **validation, + uint8_t *authoritative, + uint32_t *flags); +struct tevent_req *netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + const char *site_name, + uint32_t dns_ttl, + struct NL_DNS_NAME_INFO_ARRAY *dns_names); +NTSTATUS netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords_recv(struct tevent_req *req); +NTSTATUS netlogon_creds_cli_DsrUpdateReadOnlyServerDnsRecords( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + const char *site_name, + uint32_t dns_ttl, + struct NL_DNS_NAME_INFO_ARRAY *dns_names); + +struct tevent_req *netlogon_creds_cli_ServerGetTrustInfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b); +NTSTATUS netlogon_creds_cli_ServerGetTrustInfo_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct samr_Password *new_owf_password, + struct samr_Password *old_owf_password, + struct netr_TrustInfo **trust_info); +NTSTATUS netlogon_creds_cli_ServerGetTrustInfo( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct samr_Password *new_owf_password, + struct samr_Password *old_owf_password, + struct netr_TrustInfo **trust_info); + +struct tevent_req *netlogon_creds_cli_GetForestTrustInformation_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b); +NTSTATUS netlogon_creds_cli_GetForestTrustInformation_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct lsa_ForestTrustInformation **forest_trust_info); +NTSTATUS netlogon_creds_cli_GetForestTrustInformation( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + struct lsa_ForestTrustInformation **forest_trust_info); + +struct tevent_req *netlogon_creds_cli_SendToSam_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + struct netr_SendToSamBase *message); +NTSTATUS netlogon_creds_cli_SendToSam_recv(struct tevent_req *req); +NTSTATUS netlogon_creds_cli_SendToSam( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + struct netr_SendToSamBase *message); + +struct tevent_req *netlogon_creds_cli_LogonGetDomainInfo_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + uint32_t level, + union netr_WorkstationInfo *query); +NTSTATUS netlogon_creds_cli_LogonGetDomainInfo_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + union netr_DomainInfo **info); +NTSTATUS netlogon_creds_cli_LogonGetDomainInfo( + struct netlogon_creds_cli_context *context, + struct dcerpc_binding_handle *b, + TALLOC_CTX *mem_ctx, + uint32_t level, + union netr_WorkstationInfo *query, + union netr_DomainInfo **info); + +#endif /* NETLOGON_CREDS_CLI_H */ diff --git a/libcli/auth/ntlm_check.c b/libcli/auth/ntlm_check.c new file mode 100644 index 0000000..cb4be7f --- /dev/null +++ b/libcli/auth/ntlm_check.c @@ -0,0 +1,648 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2004 + Copyright (C) Gerald Carter 2003 + Copyright (C) Luke Kenneth Casson Leighton 1996-2000 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/crypto/md4.h" +#include "librpc/gen_ndr/netlogon.h" +#include "libcli/auth/libcli_auth.h" + +/**************************************************************************** + Core of smb password checking routine. +****************************************************************************/ + +static bool smb_pwd_check_ntlmv1(TALLOC_CTX *mem_ctx, + const DATA_BLOB *nt_response, + const uint8_t *part_passwd, + const DATA_BLOB *sec_blob, + DATA_BLOB *user_sess_key) +{ + /* Finish the encryption of part_passwd. */ + uint8_t p24[24]; + int rc; + bool ok; + + if (part_passwd == NULL) { + DEBUG(10,("No password set - DISALLOWING access\n")); + /* No password set - always false ! */ + return false; + } + + if (sec_blob->length != 8) { + DBG_ERR("incorrect challenge size (%zu)\n", sec_blob->length); + return false; + } + + if (nt_response->length != 24) { + DBG_ERR("incorrect password length (%zu)\n", + nt_response->length); + return false; + } + + rc = SMBOWFencrypt(part_passwd, sec_blob->data, p24); + if (rc != 0) { + return false; + } + +#if DEBUG_PASSWORD + DEBUG(100,("Part password (P16) was |\n")); + dump_data(100, part_passwd, 16); + DEBUGADD(100,("Password from client was |\n")); + dump_data(100, nt_response->data, nt_response->length); + DEBUGADD(100,("Given challenge was |\n")); + dump_data(100, sec_blob->data, sec_blob->length); + DEBUGADD(100,("Value from encryption was |\n")); + dump_data(100, p24, 24); +#endif + ok = mem_equal_const_time(p24, nt_response->data, 24); + if (!ok) { + return false; + } + if (user_sess_key != NULL) { + *user_sess_key = data_blob_talloc(mem_ctx, NULL, 16); + if (user_sess_key->data == NULL) { + DBG_ERR("data_blob_talloc failed\n"); + return false; + } + SMBsesskeygen_ntv1(part_passwd, user_sess_key->data); + } + return true; +} + +/**************************************************************************** + Core of smb password checking routine. (NTLMv2, LMv2) + Note: The same code works with both NTLMv2 and LMv2. +****************************************************************************/ + +static bool smb_pwd_check_ntlmv2(TALLOC_CTX *mem_ctx, + const DATA_BLOB *ntv2_response, + const uint8_t *part_passwd, + const DATA_BLOB *sec_blob, + const char *user, const char *domain, + DATA_BLOB *user_sess_key) +{ + /* Finish the encryption of part_passwd. */ + uint8_t kr[16]; + uint8_t value_from_encryption[16]; + DATA_BLOB client_key_data; + NTSTATUS status; + bool ok; + + if (part_passwd == NULL) { + DEBUG(10,("No password set - DISALLOWING access\n")); + /* No password set - always false */ + return false; + } + + if (sec_blob->length != 8) { + DBG_ERR("incorrect challenge size (%zu)\n", sec_blob->length); + return false; + } + + if (ntv2_response->length < 24) { + /* We MUST have more than 16 bytes, or the stuff below will go + crazy. No known implementation sends less than the 24 bytes + for LMv2, let alone NTLMv2. */ + DBG_ERR("incorrect password length (%zu)\n", + ntv2_response->length); + return false; + } + + client_key_data = data_blob_talloc(mem_ctx, ntv2_response->data+16, ntv2_response->length-16); + /* + todo: should we be checking this for anything? We can't for LMv2, + but for NTLMv2 it is meant to contain the current time etc. + */ + + if (!ntv2_owf_gen(part_passwd, user, domain, kr)) { + return false; + } + + status = SMBOWFencrypt_ntv2(kr, + sec_blob, + &client_key_data, + value_from_encryption); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + +#if DEBUG_PASSWORD + DEBUG(100,("Part password (P16) was |\n")); + dump_data(100, part_passwd, 16); + DEBUGADD(100,("Password from client was |\n")); + dump_data(100, ntv2_response->data, ntv2_response->length); + DEBUGADD(100,("Variable data from client was |\n")); + dump_data(100, client_key_data.data, client_key_data.length); + DEBUGADD(100,("Given challenge was |\n")); + dump_data(100, sec_blob->data, sec_blob->length); + DEBUGADD(100,("Value from encryption was |\n")); + dump_data(100, value_from_encryption, 16); +#endif + data_blob_clear_free(&client_key_data); + + ok = mem_equal_const_time(value_from_encryption, ntv2_response->data, 16); + if (!ok) { + return false; + } + if (user_sess_key != NULL) { + *user_sess_key = data_blob_talloc(mem_ctx, NULL, 16); + if (user_sess_key->data == NULL) { + DBG_ERR("data_blob_talloc failed\n"); + return false; + } + + status = SMBsesskeygen_ntv2( + kr, value_from_encryption, user_sess_key->data); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + } + return true; +} + +/**************************************************************************** + Core of smb password checking routine. (NTLMv2, LMv2) + Note: The same code works with both NTLMv2 and LMv2. +****************************************************************************/ + +static bool smb_sess_key_ntlmv2(TALLOC_CTX *mem_ctx, + const DATA_BLOB *ntv2_response, + const uint8_t *part_passwd, + const DATA_BLOB *sec_blob, + const char *user, const char *domain, + DATA_BLOB *user_sess_key) +{ + /* Finish the encryption of part_passwd. */ + uint8_t kr[16]; + uint8_t value_from_encryption[16]; + DATA_BLOB client_key_data; + NTSTATUS status; + + if (part_passwd == NULL) { + DEBUG(10,("No password set - DISALLOWING access\n")); + /* No password set - always false */ + return false; + } + + if (sec_blob->length != 8) { + DBG_ERR("incorrect challenge size (%zu)\n", sec_blob->length); + return false; + } + + if (ntv2_response->length < 24) { + /* We MUST have more than 16 bytes, or the stuff below will go + crazy. No known implementation sends less than the 24 bytes + for LMv2, let alone NTLMv2. */ + DBG_ERR("incorrect password length (%zu)\n", + ntv2_response->length); + return false; + } + + client_key_data = data_blob_talloc(mem_ctx, ntv2_response->data+16, ntv2_response->length-16); + + if (!ntv2_owf_gen(part_passwd, user, domain, kr)) { + return false; + } + + status = SMBOWFencrypt_ntv2(kr, + sec_blob, + &client_key_data, + value_from_encryption); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + *user_sess_key = data_blob_talloc(mem_ctx, NULL, 16); + if (user_sess_key->data == NULL) { + DBG_ERR("data_blob_talloc failed\n"); + return false; + } + status = SMBsesskeygen_ntv2(kr, + value_from_encryption, + user_sess_key->data); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + return true; +} + +/** + * Compare password hashes against those from the SAM + * + * @param mem_ctx talloc context + * @param client_lanman LANMAN password hash, as supplied by the client + * @param client_nt NT (MD4) password hash, as supplied by the client + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param stored_lanman LANMAN password hash, as stored on the SAM + * @param stored_nt NT (MD4) password hash, as stored on the SAM + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS hash_password_check(TALLOC_CTX *mem_ctx, + bool lanman_auth, + const struct samr_Password *client_lanman, + const struct samr_Password *client_nt, + const char *username, + const struct samr_Password *stored_lanman, + const struct samr_Password *stored_nt) +{ + if (stored_nt == NULL) { + DEBUG(3,("hash_password_check: NO NT password stored for user %s.\n", + username)); + } + + if (client_nt && stored_nt) { + if (mem_equal_const_time(client_nt->hash, stored_nt->hash, sizeof(stored_nt->hash))) { + return NT_STATUS_OK; + } else { + DEBUG(3,("hash_password_check: Interactive logon: NT password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + } else if (client_lanman && stored_lanman) { + if (!lanman_auth) { + DEBUG(3,("hash_password_check: Interactive logon: only LANMAN password supplied for user %s, and LM passwords are disabled!\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + if (strchr_m(username, '@')) { + return NT_STATUS_NOT_FOUND; + } + + if (mem_equal_const_time(client_lanman->hash, stored_lanman->hash, sizeof(stored_lanman->hash))) { + return NT_STATUS_OK; + } else { + DEBUG(3,("hash_password_check: Interactive logon: LANMAN password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + } + if (strchr_m(username, '@')) { + return NT_STATUS_NOT_FOUND; + } + return NT_STATUS_WRONG_PASSWORD; +} + +/** + * Check a challenge-response password against the value of the NT or + * LM password hash. + * + * @param mem_ctx talloc context + * @param challenge 8-byte challenge. If all zero, forces plaintext comparison + * @param nt_response 'unicode' NT response to the challenge, or unicode password + * @param lm_response ASCII or LANMAN response to the challenge, or password in DOS code page + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param stored_lanman LANMAN ASCII password from our passdb or similar + * @param stored_nt MD4 unicode password from our passdb or similar + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS ntlm_password_check(TALLOC_CTX *mem_ctx, + bool lanman_auth, + enum ntlm_auth_level ntlm_auth, + uint32_t logon_parameters, + const DATA_BLOB *challenge, + const DATA_BLOB *lm_response, + const DATA_BLOB *nt_response, + const char *username, + const char *client_username, + const char *client_domain, + const struct samr_Password *stored_lanman, + const struct samr_Password *stored_nt, + DATA_BLOB *user_sess_key, + DATA_BLOB *lm_sess_key) +{ + DATA_BLOB tmp_sess_key; + const char *upper_client_domain = NULL; + + if (ntlm_auth == NTLM_AUTH_DISABLED) { + DBG_WARNING("ntlm_password_check: NTLM authentication not " + "permitted by configuration.\n"); + return NT_STATUS_NTLM_BLOCKED; + } + + if (client_domain != NULL) { + upper_client_domain = talloc_strdup_upper(mem_ctx, client_domain); + if (upper_client_domain == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (stored_nt == NULL) { + DEBUG(3,("ntlm_password_check: NO NT password stored for user %s.\n", + username)); + } + + *lm_sess_key = data_blob(NULL, 0); + *user_sess_key = data_blob(NULL, 0); + + /* Check for cleartext netlogon. Used by Exchange 5.5. */ + if ((logon_parameters & MSV1_0_CLEARTEXT_PASSWORD_ALLOWED) + && challenge->length == 8 + && (all_zero(challenge->data, challenge->length))) { + struct samr_Password client_nt; + struct samr_Password client_lm; + char *unix_pw = NULL; + bool lm_ok; + size_t converted_size = 0; + + DEBUG(4,("ntlm_password_check: checking plaintext passwords for user %s\n", + username)); + mdfour(client_nt.hash, nt_response->data, nt_response->length); + + if (lm_response->length && + (convert_string_talloc(mem_ctx, CH_DOS, CH_UNIX, + lm_response->data, lm_response->length, + (void *)&unix_pw, &converted_size))) { + if (E_deshash(unix_pw, client_lm.hash)) { + lm_ok = true; + } else { + lm_ok = false; + } + } else { + lm_ok = false; + } + return hash_password_check(mem_ctx, + lanman_auth, + lm_ok ? &client_lm : NULL, + nt_response->length ? &client_nt : NULL, + username, + stored_lanman, stored_nt); + } + + if (nt_response->length != 0 && nt_response->length < 24) { + DBG_NOTICE("invalid NT password length (%zu) for user %s\n", + nt_response->length, + username); + } + + if (nt_response->length > 24 && stored_nt) { + /* We have the NT MD4 hash challenge available - see if we can + use it + */ + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password with domain [%s]\n", + client_domain ? client_domain : "<NULL>")); + if (smb_pwd_check_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + client_domain, + user_sess_key)) { + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password with uppercased version of domain [%s]\n", + upper_client_domain ? upper_client_domain : "<NULL>")); + if (smb_pwd_check_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + upper_client_domain, + user_sess_key)) { + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking NTLMv2 password without a domain\n")); + if (smb_pwd_check_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + "", + user_sess_key)) { + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: NTLMv2 password check failed\n")); + } + } else if (nt_response->length == 24 && stored_nt) { + if (ntlm_auth == NTLM_AUTH_ON + || (ntlm_auth == NTLM_AUTH_MSCHAPv2_NTLMV2_ONLY && (logon_parameters & MSV1_0_ALLOW_MSVCHAPV2))) { + /* We have the NT MD4 hash challenge available - see if we can + use it (ie. does it exist in the smbpasswd file). + */ + DEBUG(4,("ntlm_password_check: Checking NT MD4 password\n")); + if (smb_pwd_check_ntlmv1(mem_ctx, + nt_response, + stored_nt->hash, challenge, + user_sess_key)) { + /* The LM session key for this response is not very secure, + so use it only if we otherwise allow LM authentication */ + + if (lanman_auth && stored_lanman) { + *lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } else { + DEBUG(3,("ntlm_password_check: NT MD4 password check failed for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + } else { + DEBUG(2,("ntlm_password_check: NTLMv1 passwords NOT PERMITTED for user %s\n", + username)); + /* no return, because we might pick up LMv2 in the LM field */ + } + } + + if (lm_response->length == 0) { + DEBUG(3,("ntlm_password_check: NEITHER LanMan nor NT password supplied for user %s\n", + username)); + return NT_STATUS_WRONG_PASSWORD; + } + + if (lm_response->length < 24) { + DBG_NOTICE("invalid LanMan password length (%zu) for " + "user %s\n", + nt_response->length, username); + return NT_STATUS_WRONG_PASSWORD; + } + + if (!lanman_auth) { + DEBUG(3,("ntlm_password_check: Lanman passwords NOT PERMITTED for user %s\n", + username)); + } else if (!stored_lanman) { + DEBUG(3,("ntlm_password_check: NO LanMan password set for user %s (and no NT password supplied)\n", + username)); + } else if (strchr_m(username, '@')) { + DEBUG(3,("ntlm_password_check: NO LanMan password allowed for username@realm logins (user: %s)\n", + username)); + } else { + DEBUG(4,("ntlm_password_check: Checking LM password\n")); + if (smb_pwd_check_ntlmv1(mem_ctx, + lm_response, + stored_lanman->hash, challenge, + NULL)) { + /* The session key for this response is still very odd. + It not very secure, so use it only if we otherwise + allow LM authentication */ + + if (lanman_auth && stored_lanman) { + uint8_t first_8_lm_hash[16]; + memcpy(first_8_lm_hash, stored_lanman->hash, 8); + memset(first_8_lm_hash + 8, '\0', 8); + *user_sess_key = data_blob_talloc(mem_ctx, first_8_lm_hash, 16); + *lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, 8); + } + return NT_STATUS_OK; + } + } + + if (!stored_nt) { + DEBUG(4,("ntlm_password_check: LM password check failed for user, no NT password %s\n",username)); + return NT_STATUS_WRONG_PASSWORD; + } + + /* This is for 'LMv2' authentication. almost NTLMv2 but limited to 24 bytes. + - related to Win9X, legacy NAS pass-though authentication + */ + DEBUG(4,("ntlm_password_check: Checking LMv2 password with domain %s\n", + client_domain ? client_domain : "<NULL>")); + if (smb_pwd_check_ntlmv2(mem_ctx, + lm_response, + stored_nt->hash, challenge, + client_username, + client_domain, + &tmp_sess_key)) { + if (nt_response->length > 24) { + /* If NTLMv2 authentication has preceded us + * (even if it failed), then use the session + * key from that. See the RPC-SAMLOGON + * torture test */ + smb_sess_key_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + client_domain, + user_sess_key); + } else { + /* Otherwise, use the LMv2 session key */ + *user_sess_key = tmp_sess_key; + } + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking LMv2 password with upper-cased version of domain %s\n", + upper_client_domain ? upper_client_domain : "<NULL>")); + if (smb_pwd_check_ntlmv2(mem_ctx, + lm_response, + stored_nt->hash, challenge, + client_username, + upper_client_domain, + &tmp_sess_key)) { + if (nt_response->length > 24) { + /* If NTLMv2 authentication has preceded us + * (even if it failed), then use the session + * key from that. See the RPC-SAMLOGON + * torture test */ + smb_sess_key_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + upper_client_domain, + user_sess_key); + } else { + /* Otherwise, use the LMv2 session key */ + *user_sess_key = tmp_sess_key; + } + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + DEBUG(4,("ntlm_password_check: Checking LMv2 password without a domain\n")); + if (smb_pwd_check_ntlmv2(mem_ctx, + lm_response, + stored_nt->hash, challenge, + client_username, + "", + &tmp_sess_key)) { + if (nt_response->length > 24) { + /* If NTLMv2 authentication has preceded us + * (even if it failed), then use the session + * key from that. See the RPC-SAMLOGON + * torture test */ + smb_sess_key_ntlmv2(mem_ctx, + nt_response, + stored_nt->hash, challenge, + client_username, + "", + user_sess_key); + } else { + /* Otherwise, use the LMv2 session key */ + *user_sess_key = tmp_sess_key; + } + if (user_sess_key->length) { + *lm_sess_key = data_blob_talloc(mem_ctx, user_sess_key->data, MIN(8, user_sess_key->length)); + } + return NT_STATUS_OK; + } + + /* Apparently NT accepts NT responses in the LM field + - I think this is related to Win9X pass-though authentication + */ + DEBUG(4,("ntlm_password_check: Checking NT MD4 password in LM field\n")); + if (ntlm_auth == NTLM_AUTH_ON) { + if (smb_pwd_check_ntlmv1(mem_ctx, + lm_response, + stored_nt->hash, challenge, + NULL)) { + /* The session key for this response is still very odd. + It not very secure, so use it only if we otherwise + allow LM authentication */ + + if (lanman_auth && stored_lanman) { + uint8_t first_8_lm_hash[16]; + memcpy(first_8_lm_hash, stored_lanman->hash, 8); + memset(first_8_lm_hash + 8, '\0', 8); + *user_sess_key = data_blob_talloc(mem_ctx, first_8_lm_hash, 16); + *lm_sess_key = data_blob_talloc(mem_ctx, stored_lanman->hash, 8); + } + return NT_STATUS_OK; + } + DEBUG(3,("ntlm_password_check: LM password, NT MD4 password in LM field and LMv2 failed for user %s\n",username)); + } else { + DEBUG(3,("ntlm_password_check: LM password and LMv2 failed for user %s, and NT MD4 password in LM field not permitted\n",username)); + } + + /* Try and match error codes */ + if (strchr_m(username, '@')) { + return NT_STATUS_NOT_FOUND; + } + return NT_STATUS_WRONG_PASSWORD; +} + diff --git a/libcli/auth/ntlm_check.h b/libcli/auth/ntlm_check.h new file mode 100644 index 0000000..86cab9b --- /dev/null +++ b/libcli/auth/ntlm_check.h @@ -0,0 +1,86 @@ +/* + Unix SMB/CIFS implementation. + Password and authentication handling + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2004 + Copyright (C) Gerald Carter 2003 + Copyright (C) Luke Kenneth Casson Leighton 1996-2000 + + 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/>. +*/ +#ifndef __LIBCLI_AUTH_NTLM_CHECK_H__ +#define __LIBCLI_AUTH_NTLM_CHECK_H__ + +/* mangled names options */ +enum ntlm_auth_level {NTLM_AUTH_DISABLED, NTLM_AUTH_ON, + NTLM_AUTH_NTLMV2_ONLY, + NTLM_AUTH_MSCHAPv2_NTLMV2_ONLY}; + +struct samr_Password; + +/** + * Compare password hashes against those from the SAM + * + * @param mem_ctx talloc context + * @param client_lanman LANMAN password hash, as supplied by the client + * @param client_nt NT (MD4) password hash, as supplied by the client + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param stored_lanman LANMAN password hash, as stored on the SAM + * @param stored_nt NT (MD4) password hash, as stored on the SAM + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS hash_password_check(TALLOC_CTX *mem_ctx, + bool lanman_auth, + const struct samr_Password *client_lanman, + const struct samr_Password *client_nt, + const char *username, + const struct samr_Password *stored_lanman, + const struct samr_Password *stored_nt); + +/** + * Check a challenge-response password against the value of the NT or + * LM password hash. + * + * @param mem_ctx talloc context + * @param challenge 8-byte challenge. If all zero, forces plaintext comparison + * @param nt_response 'unicode' NT response to the challenge, or unicode password + * @param lm_response ASCII or LANMAN response to the challenge, or password in DOS code page + * @param username internal Samba username, for log messages + * @param client_username username the client used + * @param client_domain domain name the client used (may be mapped) + * @param stored_lanman LANMAN ASCII password from our passdb or similar + * @param stored_nt MD4 unicode password from our passdb or similar + * @param user_sess_key User session key + * @param lm_sess_key LM session key (first 8 bytes of the LM hash) + */ + +NTSTATUS ntlm_password_check(TALLOC_CTX *mem_ctx, + bool lanman_auth, + enum ntlm_auth_level ntlm_auth, + uint32_t logon_parameters, + const DATA_BLOB *challenge, + const DATA_BLOB *lm_response, + const DATA_BLOB *nt_response, + const char *username, + const char *client_username, + const char *client_domain, + const struct samr_Password *stored_lanman, + const struct samr_Password *stored_nt, + DATA_BLOB *user_sess_key, + DATA_BLOB *lm_sess_key); + +#endif /* __LIBCLI_AUTH_NTLM_CHECK_H__ */ diff --git a/libcli/auth/pam_errors.c b/libcli/auth/pam_errors.c new file mode 100644 index 0000000..5592d39 --- /dev/null +++ b/libcli/auth/pam_errors.c @@ -0,0 +1,143 @@ +/* + * Unix SMB/CIFS implementation. + * PAM error mapping functions + * Copyright (C) Andrew Bartlett 2002 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "includes.h" +#include "../libcli/auth/pam_errors.h" + +#ifdef WITH_PAM +#if defined(HAVE_SECURITY_PAM_APPL_H) +#include <security/pam_appl.h> +#elif defined(HAVE_PAM_PAM_APPL_H) +#include <pam/pam_appl.h> +#endif + +#if defined(PAM_AUTHTOK_RECOVERY_ERR) && !defined(PAM_AUTHTOK_RECOVER_ERR) +#define PAM_AUTHTOK_RECOVER_ERR PAM_AUTHTOK_RECOVERY_ERR +#endif + +/* PAM -> NT_STATUS map */ +static const struct { + int pam_code; + NTSTATUS ntstatus; +} pam_to_nt_status_map[] = { + {PAM_OPEN_ERR, NT_STATUS_UNSUCCESSFUL}, + {PAM_SYMBOL_ERR, NT_STATUS_UNSUCCESSFUL}, + {PAM_SERVICE_ERR, NT_STATUS_UNSUCCESSFUL}, + {PAM_SYSTEM_ERR, NT_STATUS_UNSUCCESSFUL}, + {PAM_BUF_ERR, NT_STATUS_NO_MEMORY}, + {PAM_PERM_DENIED, NT_STATUS_ACCESS_DENIED}, + {PAM_AUTH_ERR, NT_STATUS_WRONG_PASSWORD}, + {PAM_CRED_INSUFFICIENT, NT_STATUS_INSUFFICIENT_LOGON_INFO}, /* FIXME: Is this correct? */ + {PAM_AUTHINFO_UNAVAIL, NT_STATUS_LOGON_FAILURE}, + {PAM_USER_UNKNOWN, NT_STATUS_NO_SUCH_USER}, + {PAM_MAXTRIES, NT_STATUS_REMOTE_SESSION_LIMIT}, /* FIXME: Is this correct? */ + {PAM_NEW_AUTHTOK_REQD, NT_STATUS_PASSWORD_MUST_CHANGE}, + {PAM_ACCT_EXPIRED, NT_STATUS_ACCOUNT_EXPIRED}, + {PAM_SESSION_ERR, NT_STATUS_INSUFFICIENT_RESOURCES}, + {PAM_CRED_UNAVAIL, NT_STATUS_NO_TOKEN}, /* FIXME: Is this correct? */ + {PAM_CRED_EXPIRED, NT_STATUS_PASSWORD_EXPIRED}, /* FIXME: Is this correct? */ + {PAM_CRED_ERR, NT_STATUS_UNSUCCESSFUL}, + {PAM_AUTHTOK_ERR, NT_STATUS_UNSUCCESSFUL}, +#ifdef PAM_AUTHTOK_RECOVER_ERR + {PAM_AUTHTOK_RECOVER_ERR, NT_STATUS_UNSUCCESSFUL}, +#endif + {PAM_AUTHTOK_EXPIRED, NT_STATUS_PASSWORD_EXPIRED}, + {PAM_SUCCESS, NT_STATUS_OK} +}; + +/* NT_STATUS -> PAM map */ +static const struct { + NTSTATUS ntstatus; + int pam_code; +} nt_status_to_pam_map[] = { + {NT_STATUS_UNSUCCESSFUL, PAM_SYSTEM_ERR}, + {NT_STATUS_NO_SUCH_USER, PAM_USER_UNKNOWN}, + {NT_STATUS_WRONG_PASSWORD, PAM_AUTH_ERR}, + {NT_STATUS_LOGON_FAILURE, PAM_AUTH_ERR}, + {NT_STATUS_ACCOUNT_EXPIRED, PAM_ACCT_EXPIRED}, + {NT_STATUS_ACCOUNT_DISABLED, PAM_ACCT_EXPIRED}, + {NT_STATUS_PASSWORD_EXPIRED, PAM_AUTHTOK_EXPIRED}, + {NT_STATUS_PASSWORD_MUST_CHANGE, PAM_NEW_AUTHTOK_REQD}, + {NT_STATUS_ACCOUNT_LOCKED_OUT, PAM_MAXTRIES}, + {NT_STATUS_NO_MEMORY, PAM_BUF_ERR}, + {NT_STATUS_PASSWORD_RESTRICTION, PAM_AUTHTOK_ERR}, + {NT_STATUS_PWD_HISTORY_CONFLICT, PAM_AUTHTOK_ERR}, + {NT_STATUS_PWD_TOO_RECENT, PAM_AUTHTOK_ERR}, + {NT_STATUS_PWD_TOO_SHORT, PAM_AUTHTOK_ERR}, + {NT_STATUS_BACKUP_CONTROLLER, PAM_AUTHINFO_UNAVAIL}, + {NT_STATUS_DOMAIN_CONTROLLER_NOT_FOUND, PAM_AUTHINFO_UNAVAIL}, + {NT_STATUS_NO_LOGON_SERVERS, PAM_AUTHINFO_UNAVAIL}, + {NT_STATUS_INVALID_WORKSTATION, PAM_PERM_DENIED}, + {NT_STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT, PAM_AUTHINFO_UNAVAIL}, + {NT_STATUS_NOLOGON_SERVER_TRUST_ACCOUNT, PAM_AUTHINFO_UNAVAIL}, + {NT_STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT, PAM_AUTHINFO_UNAVAIL}, + {NT_STATUS_OK, PAM_SUCCESS} +}; + +/***************************************************************************** +convert a PAM error to a NT status32 code + *****************************************************************************/ +NTSTATUS pam_to_nt_status(int pam_error) +{ + int i; + if (pam_error == 0) return NT_STATUS_OK; + + for (i=0; NT_STATUS_V(pam_to_nt_status_map[i].ntstatus); i++) { + if (pam_error == pam_to_nt_status_map[i].pam_code) + return pam_to_nt_status_map[i].ntstatus; + } + return NT_STATUS_UNSUCCESSFUL; +} + +/***************************************************************************** +convert an NT status32 code to a PAM error + *****************************************************************************/ +int nt_status_to_pam(NTSTATUS nt_status) +{ + int i; + if NT_STATUS_IS_OK(nt_status) return PAM_SUCCESS; + + for (i=0; NT_STATUS_V(nt_status_to_pam_map[i].ntstatus); i++) { + if (NT_STATUS_EQUAL(nt_status,nt_status_to_pam_map[i].ntstatus)) + return nt_status_to_pam_map[i].pam_code; + } + return PAM_SYSTEM_ERR; +} + +#else + +/***************************************************************************** +convert a PAM error to a NT status32 code + *****************************************************************************/ +NTSTATUS pam_to_nt_status(int pam_error) +{ + if (pam_error == 0) return NT_STATUS_OK; + return NT_STATUS_UNSUCCESSFUL; +} + +/***************************************************************************** +convert an NT status32 code to a PAM error + *****************************************************************************/ +int nt_status_to_pam(NTSTATUS nt_status) +{ + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_OK)) return 0; + return 4; /* PAM_SYSTEM_ERR */ +} + +#endif diff --git a/libcli/auth/pam_errors.h b/libcli/auth/pam_errors.h new file mode 100644 index 0000000..128910f --- /dev/null +++ b/libcli/auth/pam_errors.h @@ -0,0 +1,33 @@ +/* + * Unix SMB/CIFS implementation. + * PAM error mapping functions + * Copyright (C) Andrew Bartlett 2002 + * + * 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/>. + */ + +#ifndef __LIBCLI_AUTH_PAM_ERRORS_H__ +#define __LIBCLI_AUTH_PAM_ERRORS_H__ + +/***************************************************************************** +convert a PAM error to a NT status32 code + *****************************************************************************/ +NTSTATUS pam_to_nt_status(int pam_error); + +/***************************************************************************** +convert an NT status32 code to a PAM error + *****************************************************************************/ +int nt_status_to_pam(NTSTATUS nt_status); + +#endif /* __LIBCLI_AUTH_PAM_ERRORS_H__ */ diff --git a/libcli/auth/proto.h b/libcli/auth/proto.h new file mode 100644 index 0000000..baf5730 --- /dev/null +++ b/libcli/auth/proto.h @@ -0,0 +1,296 @@ +#ifndef _LIBCLI_AUTH_PROTO_H__ +#define _LIBCLI_AUTH_PROTO_H__ + +#undef _PRINTF_ATTRIBUTE +#define _PRINTF_ATTRIBUTE(a1, a2) PRINTF_ATTRIBUTE(a1, a2) + +#include "lib/crypto/gnutls_helpers.h" + +/* this file contains prototypes for functions that are private + * to this subsystem or library. These functions should not be + * used outside this particular subsystem! */ + + +/* The following definitions come from /home/jeremy/src/samba/git/master/source3/../source4/../libcli/auth/credentials.c */ + +bool netlogon_creds_is_random_challenge(const struct netr_Credential *challenge); +void netlogon_creds_random_challenge(struct netr_Credential *challenge); + +NTSTATUS netlogon_creds_des_encrypt_LMKey(struct netlogon_creds_CredentialState *creds, + struct netr_LMSessionKey *key); +NTSTATUS netlogon_creds_des_decrypt_LMKey(struct netlogon_creds_CredentialState *creds, + struct netr_LMSessionKey *key); +NTSTATUS netlogon_creds_des_encrypt(struct netlogon_creds_CredentialState *creds, + struct samr_Password *pass); +NTSTATUS netlogon_creds_des_decrypt(struct netlogon_creds_CredentialState *creds, + struct samr_Password *pass); +NTSTATUS netlogon_creds_arcfour_crypt(struct netlogon_creds_CredentialState *creds, + uint8_t *data, + size_t len); +NTSTATUS netlogon_creds_aes_encrypt(struct netlogon_creds_CredentialState *creds, + uint8_t *data, + size_t len); +NTSTATUS netlogon_creds_aes_decrypt(struct netlogon_creds_CredentialState *creds, + uint8_t *data, + size_t len); + +/***************************************************************** +The above functions are common to the client and server interface +next comes the client specific functions +******************************************************************/ +struct netlogon_creds_CredentialState *netlogon_creds_client_init(TALLOC_CTX *mem_ctx, + const char *client_account, + const char *client_computer_name, + uint16_t secure_channel_type, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password, + struct netr_Credential *initial_credential, + uint32_t negotiate_flags); +struct netlogon_creds_CredentialState *netlogon_creds_client_init_session_key(TALLOC_CTX *mem_ctx, + const uint8_t session_key[16]); +NTSTATUS +netlogon_creds_client_authenticator(struct netlogon_creds_CredentialState *creds, + struct netr_Authenticator *next); +bool netlogon_creds_client_check(struct netlogon_creds_CredentialState *creds, + const struct netr_Credential *received_credentials); +struct netlogon_creds_CredentialState *netlogon_creds_copy( + TALLOC_CTX *mem_ctx, + const struct netlogon_creds_CredentialState *creds_in); + +/***************************************************************** +The above functions are common to the client and server interface +next comes the server specific functions +******************************************************************/ +struct netlogon_creds_CredentialState *netlogon_creds_server_init(TALLOC_CTX *mem_ctx, + const char *client_account, + const char *client_computer_name, + uint16_t secure_channel_type, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const struct samr_Password *machine_password, + const struct netr_Credential *credentials_in, + struct netr_Credential *credentials_out, + uint32_t negotiate_flags); +NTSTATUS netlogon_creds_server_step_check(struct netlogon_creds_CredentialState *creds, + const struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator) ; +NTSTATUS netlogon_creds_decrypt_samlogon_validation(struct netlogon_creds_CredentialState *creds, + uint16_t validation_level, + union netr_Validation *validation); +NTSTATUS netlogon_creds_encrypt_samlogon_validation(struct netlogon_creds_CredentialState *creds, + uint16_t validation_level, + union netr_Validation *validation); +NTSTATUS netlogon_creds_decrypt_samlogon_logon(struct netlogon_creds_CredentialState *creds, + enum netr_LogonInfoClass level, + union netr_LogonLevel *logon); +NTSTATUS netlogon_creds_encrypt_samlogon_logon(struct netlogon_creds_CredentialState *creds, + enum netr_LogonInfoClass level, + union netr_LogonLevel *logon); +union netr_LogonLevel *netlogon_creds_shallow_copy_logon(TALLOC_CTX *mem_ctx, + enum netr_LogonInfoClass level, + const union netr_LogonLevel *in); + +/* The following definitions come from /home/jeremy/src/samba/git/master/source3/../source4/../libcli/auth/session.c */ + +int sess_crypt_blob(DATA_BLOB *out, const DATA_BLOB *in, const DATA_BLOB *session_key, + enum samba_gnutls_direction encrypt); +DATA_BLOB sess_encrypt_string(const char *str, const DATA_BLOB *session_key); +char *sess_decrypt_string(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, const DATA_BLOB *session_key); +DATA_BLOB sess_encrypt_blob(TALLOC_CTX *mem_ctx, DATA_BLOB *blob_in, const DATA_BLOB *session_key); +NTSTATUS sess_decrypt_blob(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, const DATA_BLOB *session_key, + DATA_BLOB *ret); + +/* The following definitions come from /home/jeremy/src/samba/git/master/source3/../source4/../libcli/auth/smbencrypt.c */ + +int SMBencrypt_hash(const uint8_t lm_hash[16], const uint8_t *c8, uint8_t p24[24]); +bool SMBencrypt(const char *passwd, const uint8_t *c8, uint8_t p24[24]); + +/** + * Creates the MD4 Hash of the users password in NT UNICODE. + * @param passwd password in 'unix' charset. + * @param p16 return password hashed with md4, caller allocated 16 byte buffer + */ +bool E_md4hash(const char *passwd, uint8_t p16[16]); + +/** + * Creates the DES forward-only Hash of the users password in DOS ASCII charset + * @param passwd password in 'unix' charset. + * @param p16 return password hashed with DES, caller allocated 16 byte buffer + * @return false if password was > 14 characters, and therefore may be incorrect, otherwise true + * @note p16 is filled in regardless + */ +bool E_deshash(const char *passwd, uint8_t p16[16]); + +/** + * Creates the MD4 and DES (LM) Hash of the users password. + * MD4 is of the NT Unicode, DES is of the DOS UPPERCASE password. + * @param passwd password in 'unix' charset. + * @param nt_p16 return password hashed with md4, caller allocated 16 byte buffer + * @param p16 return password hashed with des, caller allocated 16 byte buffer + */ +void nt_lm_owf_gen(const char *pwd, uint8_t nt_p16[16], uint8_t p16[16]); +bool ntv2_owf_gen(const uint8_t owf[16], + const char *user_in, const char *domain_in, + uint8_t kr_buf[16]); +int SMBOWFencrypt(const uint8_t passwd[16], const uint8_t *c8, uint8_t p24[24]); +int SMBNTencrypt_hash(const uint8_t nt_hash[16], const uint8_t *c8, uint8_t *p24); +int SMBNTencrypt(const char *passwd, const uint8_t *c8, uint8_t *p24); +NTSTATUS SMBOWFencrypt_ntv2(const uint8_t kr[16], + const DATA_BLOB *srv_chal, + const DATA_BLOB *smbcli_chal, + uint8_t resp_buf[16]); +NTSTATUS SMBsesskeygen_ntv2(const uint8_t kr[16], + const uint8_t *nt_resp, + uint8_t sess_key[16]); +void SMBsesskeygen_ntv1(const uint8_t kr[16], uint8_t sess_key[16]); +NTSTATUS SMBsesskeygen_lm_sess_key(const uint8_t lm_hash[16], + const uint8_t lm_resp[24], /* only uses 8 */ + uint8_t sess_key[16]); +DATA_BLOB NTLMv2_generate_names_blob(TALLOC_CTX *mem_ctx, + const char *hostname, + const char *domain); +bool SMBNTLMv2encrypt_hash(TALLOC_CTX *mem_ctx, + const char *user, const char *domain, const uint8_t nt_hash[16], + const DATA_BLOB *server_chal, + const NTTIME *server_timestamp, + const DATA_BLOB *names_blob, + DATA_BLOB *lm_response, DATA_BLOB *nt_response, + DATA_BLOB *lm_session_key, DATA_BLOB *user_session_key) ; +bool SMBNTLMv2encrypt(TALLOC_CTX *mem_ctx, + const char *user, const char *domain, + const char *password, + const DATA_BLOB *server_chal, + const DATA_BLOB *names_blob, + DATA_BLOB *lm_response, DATA_BLOB *nt_response, + DATA_BLOB *lm_session_key, DATA_BLOB *user_session_key) ; +NTSTATUS NTLMv2_RESPONSE_verify_netlogon_creds(const char *account_name, + const char *account_domain, + const DATA_BLOB response, + const struct netlogon_creds_CredentialState *creds, + const char *workgroup); + +/*********************************************************** + encode a password buffer with a unicode password. The buffer + is filled with random data to make it harder to attack. +************************************************************/ +bool encode_pw_buffer(uint8_t buffer[516], const char *password, int string_flags); + +/*********************************************************** + decode a password buffer + *new_pw_len is the length in bytes of the possibly mulitbyte + returned password including termination. +************************************************************/ +bool decode_pw_buffer(TALLOC_CTX *ctx, + uint8_t in_buffer[516], + char **pp_new_pwrd, + size_t *new_pw_len, + charset_t string_charset); + +/** + * @brief Encode an password buffer before we encrypt it. + * + * @param buffer[514] The buffer to encode into. + * + * @param password The password we want to encode into the buffer. + * + * @param string_flags String flags for encoding (e.g. STR_UNICODE). + * + * @return true on success, false otherwise. + */ +bool encode_pwd_buffer514_from_str(uint8_t buffer[514], + const char *password, + uint32_t string_flags); + +/** + * @brief Extract AES password blob from buffer. + * + * This extracts the password from the in_buffer as a data blob. It should + * then contain an UTF-16 encoded password. + * + * @param mem_ctx The memory context to allowcate the password on. + * + * @param in_buffer[514] The input buffer to extract the password from. + * + * @param new_password A pointer to the store the extracted password blob. + * + * @return true on success, false otherwise. + */ +bool extract_pwd_blob_from_buffer514(TALLOC_CTX *mem_ctx, + const uint8_t in_buffer[514], + DATA_BLOB *new_password); + +/** + * @brief Decode AES password buffer to password in the given charset. + * + * @param mem_ctx The memory context to allocate the deocded passwrod on. + * + * @param in_buffer[514] The in buffer with the decrypted password data. + * + * @param string_charset The charset to decode to. + * + * @param decoded_password A pointer to store the blob for the decoded password. + * It ensures that the password is NULL terminated. + * + * @return true on success, false otherwise. + */ +bool decode_pwd_string_from_buffer514(TALLOC_CTX *mem_ctx, + const uint8_t in_buffer[514], + charset_t string_charset, + DATA_BLOB *decoded_password); + +/*********************************************************** + Encode an arc4 password change buffer. +************************************************************/ +NTSTATUS encode_rc4_passwd_buffer(const char *passwd, + const DATA_BLOB *session_key, + struct samr_CryptPasswordEx *out_crypt_pwd); + +/*********************************************************** + Decode an arc4 encrypted password change buffer. +************************************************************/ +NTSTATUS decode_rc4_passwd_buffer(const DATA_BLOB *psession_key, + struct samr_CryptPasswordEx *inout_crypt_pwd); + +/*********************************************************** + encode a password buffer with an already unicode password. The + rest of the buffer is filled with random data to make it harder to attack. +************************************************************/ +bool set_pw_in_buffer(uint8_t buffer[516], const DATA_BLOB *password); + +/*********************************************************** + decode a password buffer + *new_pw_size is the length in bytes of the extracted unicode password +************************************************************/ +bool extract_pw_from_buffer(TALLOC_CTX *mem_ctx, + uint8_t in_buffer[516], DATA_BLOB *new_pass); +struct wkssvc_PasswordBuffer; +WERROR encode_wkssvc_join_password_buffer(TALLOC_CTX *mem_ctx, + const char *pwd, + DATA_BLOB *session_key, + struct wkssvc_PasswordBuffer **pwd_buf); +WERROR decode_wkssvc_join_password_buffer(TALLOC_CTX *mem_ctx, + struct wkssvc_PasswordBuffer *pwd_buf, + DATA_BLOB *session_key, + char **pwd); + +/* The following definitions come from /home/jeremy/src/samba/git/master/source3/../source4/../libcli/auth/smbdes.c */ + +int des_crypt56_gnutls(uint8_t out[8], const uint8_t in[8], const uint8_t key[7], + enum samba_gnutls_direction encrypt); +int E_P16(const uint8_t *p14,uint8_t *p16); +int E_P24(const uint8_t *p21, const uint8_t *c8, uint8_t *p24); +int E_old_pw_hash( uint8_t *p14, const uint8_t *in, uint8_t *out); +int des_crypt128(uint8_t out[8], const uint8_t in[8], const uint8_t key[16]); +int des_crypt112(uint8_t out[8], const uint8_t in[8], const uint8_t key[14], + enum samba_gnutls_direction encrypt); +int des_crypt112_16(uint8_t out[16], const uint8_t in[16], const uint8_t key[14], + enum samba_gnutls_direction encrypt); +int sam_rid_crypt(unsigned int rid, const uint8_t *in, uint8_t *out, + enum samba_gnutls_direction encrypt); +#undef _PRINTF_ATTRIBUTE +#define _PRINTF_ATTRIBUTE(a1, a2) + +#endif + diff --git a/libcli/auth/schannel.h b/libcli/auth/schannel.h new file mode 100644 index 0000000..c53d68e --- /dev/null +++ b/libcli/auth/schannel.h @@ -0,0 +1,25 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc schannel operations + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "libcli/auth/libcli_auth.h" +#include "libcli/auth/schannel_state.h" +#include "libcli/auth/schannel_proto.h" diff --git a/libcli/auth/schannel_proto.h b/libcli/auth/schannel_proto.h new file mode 100644 index 0000000..bce37c8 --- /dev/null +++ b/libcli/auth/schannel_proto.h @@ -0,0 +1,31 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc schannel operations + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + 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/>. +*/ + +#ifndef _LIBCLI_AUTH_SCHANNEL_PROTO_H__ +#define _LIBCLI_AUTH_SCHANNEL_PROTO_H__ + +struct schannel_state; + +struct db_context *open_schannel_session_store(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx); + +#endif diff --git a/libcli/auth/schannel_state.h b/libcli/auth/schannel_state.h new file mode 100644 index 0000000..a333098 --- /dev/null +++ b/libcli/auth/schannel_state.h @@ -0,0 +1,54 @@ +/* + Unix SMB/CIFS implementation. + + module to store/fetch session keys for the schannel server + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2008 + + 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/>. +*/ + +#ifndef _LIBCLI_AUTH_SCHANNEL_STATE_H__ +#define _LIBCLI_AUTH_SCHANNEL_STATE_H__ + +NTSTATUS schannel_get_creds_state(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *computer_name, + struct netlogon_creds_CredentialState **creds); + +NTSTATUS schannel_save_creds_state(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct netlogon_creds_CredentialState *creds); + +NTSTATUS schannel_check_creds_state(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *computer_name, + struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator, + struct netlogon_creds_CredentialState **creds_out); + +NTSTATUS schannel_get_challenge(struct loadparm_context *lp_ctx, + struct netr_Credential *client_challenge, + struct netr_Credential *server_challenge, + const char *computer_name); + +NTSTATUS schannel_save_challenge(struct loadparm_context *lp_ctx, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const char *computer_name); + +NTSTATUS schannel_delete_challenge(struct loadparm_context *lp_ctx, + const char *computer_name); +#endif diff --git a/libcli/auth/schannel_state_tdb.c b/libcli/auth/schannel_state_tdb.c new file mode 100644 index 0000000..e0ac8a3 --- /dev/null +++ b/libcli/auth/schannel_state_tdb.c @@ -0,0 +1,646 @@ +/* + Unix SMB/CIFS implementation. + + module to store/fetch session keys for the schannel server + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2006-2009 + Copyright (C) Guenther Deschner 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/filesys.h" +#include "../lib/tdb/include/tdb.h" +#include "../lib/util/util_tdb.h" +#include "../lib/param/param.h" +#include "../libcli/auth/schannel.h" +#include "../librpc/gen_ndr/ndr_schannel.h" +#include "lib/dbwrap/dbwrap.h" + +#define SECRETS_SCHANNEL_STATE "SECRETS/SCHANNEL" + +/****************************************************************************** + Open or create the schannel session store tdb. Non-static so it can + be called from parent processes to corectly handle TDB_CLEAR_IF_FIRST +*******************************************************************************/ + +struct db_context *open_schannel_session_store(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx) +{ + struct db_context *db_sc = NULL; + char *fname = lpcfg_private_db_path(mem_ctx, lp_ctx, "schannel_store"); + int hash_size, tdb_flags; + + if (!fname) { + return NULL; + } + + hash_size = lpcfg_tdb_hash_size(lp_ctx, fname); + tdb_flags = lpcfg_tdb_flags(lp_ctx, TDB_CLEAR_IF_FIRST|TDB_NOSYNC); + + db_sc = dbwrap_local_open( + mem_ctx, + fname, + hash_size, + tdb_flags, + O_RDWR|O_CREAT, + 0600, + DBWRAP_LOCK_ORDER_NONE, + DBWRAP_FLAG_NONE); + + if (!db_sc) { + DEBUG(0,("open_schannel_session_store: Failed to open %s - %s\n", + fname, strerror(errno))); + TALLOC_FREE(fname); + return NULL; + } + + TALLOC_FREE(fname); + + return db_sc; +} + +/******************************************************************** + ********************************************************************/ + +static +NTSTATUS schannel_store_session_key_tdb(struct db_context *db_sc, + TALLOC_CTX *mem_ctx, + struct netlogon_creds_CredentialState *creds) +{ + enum ndr_err_code ndr_err; + DATA_BLOB blob; + TDB_DATA value; + char *keystr; + char *name_upper; + NTSTATUS status; + + if (strlen(creds->computer_name) > 15) { + /* + * We may want to check for a completely + * valid netbios name. + */ + return STATUS_BUFFER_OVERFLOW; + } + + name_upper = strupper_talloc(mem_ctx, creds->computer_name); + if (!name_upper) { + return NT_STATUS_NO_MEMORY; + } + + keystr = talloc_asprintf(mem_ctx, "%s/%s", + SECRETS_SCHANNEL_STATE, name_upper); + TALLOC_FREE(name_upper); + if (!keystr) { + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, creds, + (ndr_push_flags_fn_t)ndr_push_netlogon_creds_CredentialState); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + talloc_free(keystr); + return ndr_map_error2ntstatus(ndr_err); + } + + value.dptr = blob.data; + value.dsize = blob.length; + + status = dbwrap_store_bystring(db_sc, keystr, value, TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("Unable to add %s to session key db - %s\n", + keystr, nt_errstr(status))); + talloc_free(keystr); + return status; + } + + DEBUG(3,("schannel_store_session_key_tdb: stored schannel info with key %s\n", + keystr)); + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds); + } + + talloc_free(keystr); + + return NT_STATUS_OK; +} + +/******************************************************************** + ********************************************************************/ + +static +NTSTATUS schannel_fetch_session_key_tdb(struct db_context *db_sc, + TALLOC_CTX *mem_ctx, + const char *computer_name, + struct netlogon_creds_CredentialState **pcreds) +{ + NTSTATUS status; + TDB_DATA value; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + struct netlogon_creds_CredentialState *creds = NULL; + char *keystr = NULL; + char *name_upper; + + *pcreds = NULL; + + name_upper = strupper_talloc(mem_ctx, computer_name); + if (!name_upper) { + return NT_STATUS_NO_MEMORY; + } + + keystr = talloc_asprintf(mem_ctx, "%s/%s", + SECRETS_SCHANNEL_STATE, name_upper); + TALLOC_FREE(name_upper); + if (!keystr) { + return NT_STATUS_NO_MEMORY; + } + + status = dbwrap_fetch_bystring(db_sc, keystr, keystr, &value); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(10,("schannel_fetch_session_key_tdb: Failed to find entry with key %s\n", + keystr )); + goto done; + } + + creds = talloc_zero(mem_ctx, struct netlogon_creds_CredentialState); + if (!creds) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + blob = data_blob_const(value.dptr, value.dsize); + + ndr_err = ndr_pull_struct_blob(&blob, creds, creds, + (ndr_pull_flags_fn_t)ndr_pull_netlogon_creds_CredentialState); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + goto done; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netlogon_creds_CredentialState, creds); + } + + DEBUG(3,("schannel_fetch_session_key_tdb: restored schannel info key %s\n", + keystr)); + + status = NT_STATUS_OK; + + done: + + talloc_free(keystr); + + if (!NT_STATUS_IS_OK(status)) { + talloc_free(creds); + return status; + } + + *pcreds = creds; + + return NT_STATUS_OK; +} + +/****************************************************************************** + Wrapper around schannel_fetch_session_key_tdb() + Note we must be root here. +*******************************************************************************/ + +NTSTATUS schannel_get_creds_state(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *computer_name, + struct netlogon_creds_CredentialState **_creds) +{ + TALLOC_CTX *tmpctx; + struct db_context *db_sc; + struct netlogon_creds_CredentialState *creds; + NTSTATUS status; + + tmpctx = talloc_named(mem_ctx, 0, "schannel_get_creds_state"); + if (!tmpctx) { + return NT_STATUS_NO_MEMORY; + } + + db_sc = open_schannel_session_store(tmpctx, lp_ctx); + if (!db_sc) { + TALLOC_FREE(tmpctx); + return NT_STATUS_ACCESS_DENIED; + } + + status = schannel_fetch_session_key_tdb(db_sc, tmpctx, + computer_name, &creds); + if (NT_STATUS_IS_OK(status)) { + *_creds = talloc_steal(mem_ctx, creds); + if (!*_creds) { + status = NT_STATUS_NO_MEMORY; + } + } + + talloc_free(tmpctx); + return status; +} + +/****************************************************************************** + Wrapper around schannel_store_session_key_tdb() + Note we must be root here. +*******************************************************************************/ + +NTSTATUS schannel_save_creds_state(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + struct netlogon_creds_CredentialState *creds) +{ + TALLOC_CTX *tmpctx; + struct db_context *db_sc; + NTSTATUS status; + + tmpctx = talloc_named(mem_ctx, 0, "schannel_save_creds_state"); + if (!tmpctx) { + return NT_STATUS_NO_MEMORY; + } + + db_sc = open_schannel_session_store(tmpctx, lp_ctx); + if (!db_sc) { + status = NT_STATUS_ACCESS_DENIED; + goto fail; + } + + status = schannel_store_session_key_tdb(db_sc, tmpctx, creds); + +fail: + talloc_free(tmpctx); + return status; +} + + +/* + * Create a very lossy hash of the computer name. + * + * The idea here is to compress the computer name into small space so + * that malicious clients cannot fill the database with junk, as only a + * maximum of 16k of entries are possible. + * + * Collisions are certainly possible, and the design behaves in the + * same way as when the hostname is reused, but clients that use the + * same connection do not go via the cache, and the cache only needs + * to function between the ReqChallenge and ServerAuthenticate + * packets. + */ +static void hash_computer_name(const char *computer_name, + char keystr[16]) +{ + unsigned int hash; + TDB_DATA computer_tdb_data = { + .dptr = (uint8_t *)discard_const_p(char, computer_name), + .dsize = strlen(computer_name) + }; + hash = tdb_jenkins_hash(&computer_tdb_data); + + /* we are using 14 bits of the digest to index our connections, so + that we use at most 16,384 buckets.*/ + snprintf(keystr, 15, "CHALLENGE/%x%x", hash & 0xFF, + (hash & 0xFF00 >> 8) & 0x3f); + return; +} + + +static +NTSTATUS schannel_store_challenge_tdb(struct db_context *db_sc, + TALLOC_CTX *mem_ctx, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const char *computer_name) +{ + enum ndr_err_code ndr_err; + DATA_BLOB blob; + TDB_DATA value; + char *name_upper = NULL; + NTSTATUS status; + char keystr[16] = { 0, }; + struct netlogon_cache_entry cache_entry; + + if (strlen(computer_name) > 255) { + /* + * We don't make this a limit at 15 chars as Samba has + * a test showing this can be longer :-( + */ + return STATUS_BUFFER_OVERFLOW; + } + + name_upper = strupper_talloc(mem_ctx, computer_name); + if (name_upper == NULL) { + return NT_STATUS_NO_MEMORY; + } + + hash_computer_name(name_upper, keystr); + + cache_entry.computer_name = name_upper; + cache_entry.client_challenge = *client_challenge; + cache_entry.server_challenge = *server_challenge; + + ndr_err = ndr_push_struct_blob(&blob, mem_ctx, &cache_entry, + (ndr_push_flags_fn_t)ndr_push_netlogon_cache_entry); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + return NT_STATUS_UNSUCCESSFUL; + } + + value.dptr = blob.data; + value.dsize = blob.length; + + status = dbwrap_store_bystring(db_sc, keystr, value, TDB_REPLACE); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0,("%s: failed to stored challenge info for '%s' " + "with key %s - %s\n", + __func__, cache_entry.computer_name, keystr, + nt_errstr(status))); + return status; + } + + DEBUG(3,("%s: stored challenge info for '%s' with key %s\n", + __func__, cache_entry.computer_name, keystr)); + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netlogon_cache_entry, &cache_entry); + } + + return NT_STATUS_OK; +} + +/******************************************************************** + Fetch a single challenge from the TDB. + ********************************************************************/ + +static +NTSTATUS schannel_fetch_challenge_tdb(struct db_context *db_sc, + TALLOC_CTX *mem_ctx, + struct netr_Credential *client_challenge, + struct netr_Credential *server_challenge, + const char *computer_name) +{ + NTSTATUS status; + TDB_DATA value; + enum ndr_err_code ndr_err; + DATA_BLOB blob; + char keystr[16] = { 0, }; + struct netlogon_cache_entry cache_entry; + char *name_upper = NULL; + + name_upper = strupper_talloc(mem_ctx, computer_name); + if (name_upper == NULL) { + return NT_STATUS_NO_MEMORY; + } + + hash_computer_name(name_upper, keystr); + + status = dbwrap_fetch_bystring(db_sc, mem_ctx, keystr, &value); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3,("%s: Failed to find entry for %s with key %s - %s\n", + __func__, name_upper, keystr, nt_errstr(status))); + goto done; + } + + blob = data_blob_const(value.dptr, value.dsize); + + ndr_err = ndr_pull_struct_blob_all(&blob, mem_ctx, &cache_entry, + (ndr_pull_flags_fn_t)ndr_pull_netlogon_cache_entry); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(3,("%s: Failed to parse entry for %s with key %s - %s\n", + __func__, name_upper, keystr, nt_errstr(status))); + goto done; + } + + if (DEBUGLEVEL >= 10) { + NDR_PRINT_DEBUG(netlogon_cache_entry, &cache_entry); + } + + if (strcmp(cache_entry.computer_name, name_upper) != 0) { + status = NT_STATUS_NOT_FOUND; + + DEBUG(1, ("%s: HASH COLLISION with key %s ! " + "Wanted to fetch record for %s but got %s.", + __func__, keystr, name_upper, + cache_entry.computer_name)); + } else { + + DEBUG(3,("%s: restored key %s for %s\n", + __func__, keystr, cache_entry.computer_name)); + + *client_challenge = cache_entry.client_challenge; + *server_challenge = cache_entry.server_challenge; + } + done: + + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + return NT_STATUS_OK; +} + +/****************************************************************************** + Wrapper around schannel_fetch_challenge_tdb() + Note we must be root here. + +*******************************************************************************/ + +NTSTATUS schannel_get_challenge(struct loadparm_context *lp_ctx, + struct netr_Credential *client_challenge, + struct netr_Credential *server_challenge, + const char *computer_name) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct db_context *db_sc; + NTSTATUS status; + + db_sc = open_schannel_session_store(frame, lp_ctx); + if (!db_sc) { + TALLOC_FREE(frame); + return NT_STATUS_ACCESS_DENIED; + } + + status = schannel_fetch_challenge_tdb(db_sc, frame, + client_challenge, + server_challenge, + computer_name); + TALLOC_FREE(frame); + return status; +} + +/****************************************************************************** + Wrapper around dbwrap_delete_bystring() + Note we must be root here. + + This allows the challenge to be removed from the TDB, which should be + as soon as the TDB or in-memory copy it is used, to avoid reuse. +*******************************************************************************/ + +NTSTATUS schannel_delete_challenge(struct loadparm_context *lp_ctx, + const char *computer_name) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct db_context *db_sc; + char *name_upper; + char keystr[16] = { 0, }; + + db_sc = open_schannel_session_store(frame, lp_ctx); + if (!db_sc) { + TALLOC_FREE(frame); + return NT_STATUS_ACCESS_DENIED; + } + + name_upper = strupper_talloc(frame, computer_name); + if (!name_upper) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + hash_computer_name(name_upper, keystr); + + /* Now delete it, we do not want to permit fetch of this twice */ + dbwrap_delete_bystring(db_sc, keystr); + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +/****************************************************************************** + Wrapper around schannel_store_session_key_tdb() + Note we must be root here. +*******************************************************************************/ + +NTSTATUS schannel_save_challenge(struct loadparm_context *lp_ctx, + const struct netr_Credential *client_challenge, + const struct netr_Credential *server_challenge, + const char *computer_name) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct db_context *db_sc; + NTSTATUS status; + + db_sc = open_schannel_session_store(frame, lp_ctx); + if (!db_sc) { + TALLOC_FREE(frame); + return NT_STATUS_ACCESS_DENIED; + } + + status = schannel_store_challenge_tdb(db_sc, frame, + client_challenge, + server_challenge, + computer_name); + + TALLOC_FREE(frame); + return status; +} + +/******************************************************************** + Validate an incoming authenticator against the credentials for the + remote machine stored in the schannel database. + + The credentials are (re)read and from the schannel database, and + written back after the caclulations are performed. + + If the creds_out parameter is not NULL returns the credentials. + ********************************************************************/ + +NTSTATUS schannel_check_creds_state(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *computer_name, + struct netr_Authenticator *received_authenticator, + struct netr_Authenticator *return_authenticator, + struct netlogon_creds_CredentialState **creds_out) +{ + TALLOC_CTX *tmpctx; + struct db_context *db_sc; + struct netlogon_creds_CredentialState *creds; + NTSTATUS status; + char *name_upper = NULL; + char *keystr = NULL; + struct db_record *record; + TDB_DATA key; + + if (creds_out != NULL) { + *creds_out = NULL; + } + + tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state"); + if (!tmpctx) { + return NT_STATUS_NO_MEMORY; + } + + name_upper = strupper_talloc(tmpctx, computer_name); + if (!name_upper) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + keystr = talloc_asprintf(tmpctx, "%s/%s", + SECRETS_SCHANNEL_STATE, name_upper); + if (!keystr) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + + key = string_term_tdb_data(keystr); + + db_sc = open_schannel_session_store(tmpctx, lp_ctx); + if (!db_sc) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + + record = dbwrap_fetch_locked(db_sc, tmpctx, key); + if (!record) { + status = NT_STATUS_INTERNAL_DB_CORRUPTION; + goto done; + } + + /* Because this is a shared structure (even across + * disconnects) we must update the database every time we + * update the structure */ + + status = schannel_fetch_session_key_tdb(db_sc, tmpctx, + computer_name, &creds); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = netlogon_creds_server_step_check(creds, + received_authenticator, + return_authenticator); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + status = schannel_store_session_key_tdb(db_sc, tmpctx, creds); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + if (creds_out) { + *creds_out = talloc_steal(mem_ctx, creds); + if (!*creds_out) { + status = NT_STATUS_NO_MEMORY; + goto done; + } + } + + status = NT_STATUS_OK; + +done: + talloc_free(tmpctx); + return status; +} + diff --git a/libcli/auth/session.c b/libcli/auth/session.c new file mode 100644 index 0000000..43ce9d5 --- /dev/null +++ b/libcli/auth/session.c @@ -0,0 +1,243 @@ +/* + Unix SMB/CIFS implementation. + + code to encrypt/decrypt data using the user session key + + Copyright (C) Andrew Tridgell 2004 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/auth/libcli_auth.h" + +/* + encrypt or decrypt a blob of data using the user session key + as used in lsa_SetSecret + + before calling, the out blob must be initialised to be the same size + as the in blob +*/ +int sess_crypt_blob(DATA_BLOB *out, const DATA_BLOB *in, const DATA_BLOB *session_key, + enum samba_gnutls_direction encrypt) +{ + int i, k, rc; + + if (in->length % 8 != 0) { + return GNUTLS_E_INVALID_REQUEST; + } + + for (i=0,k=0; + i<in->length; + i += 8, k += 7) { + uint8_t bin[8], bout[8], key[7]; + + memcpy(bin, &in->data[i], 8); + + if (k + 7 > session_key->length) { + k = (session_key->length - k); + } + memcpy(key, &session_key->data[k], 7); + + rc = des_crypt56_gnutls(bout, bin, key, encrypt); + if (rc != 0) { + return rc; + } + + memcpy(&out->data[i], bout, 8); + } + return 0; +} + + +/* + a convenient wrapper around sess_crypt_blob() for strings, using the LSA convention + + note that we round the length to a multiple of 8. This seems to be needed for + compatibility with windows + + caller should free using data_blob_free() +*/ +DATA_BLOB sess_encrypt_string(const char *str, const DATA_BLOB *session_key) +{ + DATA_BLOB ret, src; + int slen = strlen(str); + int dlen = (slen+7) & ~7; + int rc; + + src = data_blob(NULL, 8+dlen); + if (!src.data) { + return data_blob(NULL, 0); + } + + ret = data_blob(NULL, 8+dlen); + if (!ret.data) { + data_blob_free(&src); + return data_blob(NULL, 0); + } + + SIVAL(src.data, 0, slen); + SIVAL(src.data, 4, 1); + memset(src.data+8, 0, dlen); + memcpy(src.data+8, str, slen); + + rc = sess_crypt_blob(&ret, &src, session_key, SAMBA_GNUTLS_ENCRYPT); + + data_blob_free(&src); + if (rc != 0) { + data_blob_free(&ret); + return data_blob(NULL, 0); + } + + return ret; +} + +/* + a convenient wrapper around sess_crypt_blob() for strings, using the LSA convention + + caller should free the returned string +*/ +char *sess_decrypt_string(TALLOC_CTX *mem_ctx, + DATA_BLOB *blob, const DATA_BLOB *session_key) +{ + DATA_BLOB out; + int rc, slen; + char *ret; + + if (blob->length < 8) { + return NULL; + } + + out = data_blob_talloc(mem_ctx, NULL, blob->length); + if (!out.data) { + return NULL; + } + + rc = sess_crypt_blob(&out, blob, session_key, SAMBA_GNUTLS_DECRYPT); + if (rc != 0) { + data_blob_free(&out); + return NULL; + } + + if (IVAL(out.data, 4) != 1) { + DEBUG(0,("Unexpected revision number %d in session crypted string\n", + IVAL(out.data, 4))); + data_blob_free(&out); + return NULL; + } + + slen = IVAL(out.data, 0); + if (slen > blob->length - 8) { + DEBUG(0,("Invalid crypt length %d\n", slen)); + data_blob_free(&out); + return NULL; + } + + ret = talloc_strndup(mem_ctx, (const char *)(out.data+8), slen); + + data_blob_free(&out); + + DEBUG(0,("decrypted string '%s' of length %d\n", ret, slen)); + + return ret; +} + +/* + a convenient wrapper around sess_crypt_blob() for DATA_BLOBs, using the LSA convention + + note that we round the length to a multiple of 8. This seems to be needed for + compatibility with windows + + caller should free using data_blob_free() +*/ +DATA_BLOB sess_encrypt_blob(TALLOC_CTX *mem_ctx, DATA_BLOB *blob_in, const DATA_BLOB *session_key) +{ + DATA_BLOB ret, src; + int dlen = (blob_in->length+7) & ~7; + int rc; + + src = data_blob_talloc(mem_ctx, NULL, 8+dlen); + if (!src.data) { + return data_blob(NULL, 0); + } + + ret = data_blob_talloc(mem_ctx, NULL, 8+dlen); + if (!ret.data) { + data_blob_free(&src); + return data_blob(NULL, 0); + } + + SIVAL(src.data, 0, blob_in->length); + SIVAL(src.data, 4, 1); + memset(src.data+8, 0, dlen); + memcpy(src.data+8, blob_in->data, blob_in->length); + + rc = sess_crypt_blob(&ret, &src, session_key, SAMBA_GNUTLS_ENCRYPT); + + data_blob_free(&src); + if (rc != 0) { + data_blob_free(&ret); + return data_blob(NULL, 0); + } + + return ret; +} + +/* + Decrypt a DATA_BLOB using the LSA convention +*/ +NTSTATUS sess_decrypt_blob(TALLOC_CTX *mem_ctx, const DATA_BLOB *blob, const DATA_BLOB *session_key, + DATA_BLOB *ret) +{ + DATA_BLOB out; + int rc, slen; + + if (blob->length < 8) { + DEBUG(0, ("Unexpected length %d in session crypted secret (BLOB)\n", + (int)blob->length)); + return NT_STATUS_INVALID_PARAMETER; + } + + out = data_blob_talloc(mem_ctx, NULL, blob->length); + if (!out.data) { + return NT_STATUS_NO_MEMORY; + } + + rc = sess_crypt_blob(&out, blob, session_key, SAMBA_GNUTLS_DECRYPT); + if (rc != 0) { + data_blob_free(&out); + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + if (IVAL(out.data, 4) != 1) { + DEBUG(2,("Unexpected revision number %d in session crypted secret (BLOB)\n", + IVAL(out.data, 4))); + return NT_STATUS_UNKNOWN_REVISION; + } + + slen = IVAL(out.data, 0); + if (slen > blob->length - 8) { + DEBUG(0,("Invalid crypt length %d in session crypted secret (BLOB)\n", slen)); + return NT_STATUS_WRONG_PASSWORD; + } + + *ret = data_blob_talloc(mem_ctx, out.data+8, slen); + if (slen && !ret->data) { + return NT_STATUS_NO_MEMORY; + } + + data_blob_free(&out); + + return NT_STATUS_OK; +} diff --git a/libcli/auth/smbdes.c b/libcli/auth/smbdes.c new file mode 100644 index 0000000..c6c4441 --- /dev/null +++ b/libcli/auth/smbdes.c @@ -0,0 +1,213 @@ +/* + Unix SMB/CIFS implementation. + + a partial implementation of DES designed for use in the + SMB authentication protocol + + Copyright (C) Andrew Tridgell 1998 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/auth/libcli_auth.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +static void str_to_key(const uint8_t *str,uint8_t *key) +{ + int i; + + key[0] = str[0]>>1; + key[1] = ((str[0]&0x01)<<6) | (str[1]>>2); + key[2] = ((str[1]&0x03)<<5) | (str[2]>>3); + key[3] = ((str[2]&0x07)<<4) | (str[3]>>4); + key[4] = ((str[3]&0x0F)<<3) | (str[4]>>5); + key[5] = ((str[4]&0x1F)<<2) | (str[5]>>6); + key[6] = ((str[5]&0x3F)<<1) | (str[6]>>7); + key[7] = str[6]&0x7F; + for (i=0;i<8;i++) { + key[i] = (key[i]<<1); + } +} + +int des_crypt56_gnutls(uint8_t out[8], const uint8_t in[8], + const uint8_t key_in[7], + enum samba_gnutls_direction encrypt) +{ + /* + * A single block DES-CBC op, with an all-zero IV is the same as DES + * because the IV is combined with the data using XOR. + * This allows us to use GNUTLS_CIPHER_DES_CBC from GnuTLS and not + * implement single-DES in Samba. + * + * In turn this is used to build DES-ECB, which is used + * for example in the NTLM challenge/response calculation. + */ + static const uint8_t iv8[8]; + gnutls_datum_t iv = { discard_const(iv8), 8 }; + gnutls_datum_t key; + gnutls_cipher_hd_t ctx; + uint8_t key2[8]; + uint8_t outb[8]; + int ret; + + memset(out, 0, 8); + + str_to_key(key_in, key2); + + key.data = key2; + key.size = 8; + + ret = gnutls_global_init(); + if (ret != 0) { + return ret; + } + + ret = gnutls_cipher_init(&ctx, GNUTLS_CIPHER_DES_CBC, &key, &iv); + if (ret != 0) { + return ret; + } + + memcpy(outb, in, 8); + if (encrypt == SAMBA_GNUTLS_ENCRYPT) { + ret = gnutls_cipher_encrypt(ctx, outb, 8); + } else { + ret = gnutls_cipher_decrypt(ctx, outb, 8); + } + + if (ret == 0) { + memcpy(out, outb, 8); + } + + gnutls_cipher_deinit(ctx); + + return ret; +} + +int E_P16(const uint8_t *p14,uint8_t *p16) +{ + const uint8_t sp8[8] = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25}; + int ret; + + ret = des_crypt56_gnutls(p16, sp8, p14, SAMBA_GNUTLS_ENCRYPT); + if (ret != 0) { + return ret; + } + + return des_crypt56_gnutls(p16+8, sp8, p14+7, SAMBA_GNUTLS_ENCRYPT); +} + +int E_P24(const uint8_t *p21, const uint8_t *c8, uint8_t *p24) +{ + int ret; + + ret = des_crypt56_gnutls(p24, c8, p21, SAMBA_GNUTLS_ENCRYPT); + if (ret != 0) { + return ret; + } + + ret = des_crypt56_gnutls(p24+8, c8, p21+7, SAMBA_GNUTLS_ENCRYPT); + if (ret != 0) { + return ret; + } + + return des_crypt56_gnutls(p24+16, c8, p21+14, SAMBA_GNUTLS_ENCRYPT); +} + +int E_old_pw_hash( uint8_t *p14, const uint8_t *in, uint8_t *out) +{ + int ret; + + ret = des_crypt56_gnutls(out, in, p14, SAMBA_GNUTLS_ENCRYPT); + if (ret != 0) { + return ret; + } + + return des_crypt56_gnutls(out+8, in+8, p14+7, SAMBA_GNUTLS_ENCRYPT); +} + +/* des encryption with a 128 bit key */ +int des_crypt128(uint8_t out[8], const uint8_t in[8], const uint8_t key[16]) +{ + uint8_t buf[8]; + int ret; + + ret = des_crypt56_gnutls(buf, in, key, SAMBA_GNUTLS_ENCRYPT); + if (ret != 0) { + return ret; + } + + return des_crypt56_gnutls(out, buf, key+9, SAMBA_GNUTLS_ENCRYPT); +} + +/* des encryption with a 112 bit (14 byte) key */ +int des_crypt112(uint8_t out[8], const uint8_t in[8], const uint8_t key[14], + enum samba_gnutls_direction encrypt) +{ + uint8_t buf[8]; + int ret; + + if (encrypt == SAMBA_GNUTLS_ENCRYPT) { + ret = des_crypt56_gnutls(buf, in, key, SAMBA_GNUTLS_ENCRYPT); + if (ret != 0) { + return ret; + } + + return des_crypt56_gnutls(out, buf, key+7, SAMBA_GNUTLS_ENCRYPT); + } + + ret = des_crypt56_gnutls(buf, in, key+7, SAMBA_GNUTLS_DECRYPT); + if (ret != 0) { + return ret; + } + + return des_crypt56_gnutls(out, buf, key, SAMBA_GNUTLS_DECRYPT); +} + +/* des encryption of a 16 byte lump of data with a 112 bit key */ +int des_crypt112_16(uint8_t out[16], const uint8_t in[16], const uint8_t key[14], + enum samba_gnutls_direction encrypt) +{ + int ret; + + ret = des_crypt56_gnutls(out, in, key, encrypt); + if (ret != 0) { + return ret; + } + + return des_crypt56_gnutls(out + 8, in + 8, key+7, encrypt); +} + +/* Decode a sam password hash into a password. The password hash is the + same method used to store passwords in the NT registry. The DES key + used is based on the RID of the user. */ +int sam_rid_crypt(unsigned int rid, const uint8_t *in, uint8_t *out, + enum samba_gnutls_direction encrypt) +{ + uint8_t s[14]; + int ret; + + s[0] = s[4] = s[8] = s[12] = (uint8_t)(rid & 0xFF); + s[1] = s[5] = s[9] = s[13] = (uint8_t)((rid >> 8) & 0xFF); + s[2] = s[6] = s[10] = (uint8_t)((rid >> 16) & 0xFF); + s[3] = s[7] = s[11] = (uint8_t)((rid >> 24) & 0xFF); + + ret = des_crypt56_gnutls(out, in, s, encrypt); + if (ret != 0) { + return ret; + } + return des_crypt56_gnutls(out+8, in+8, s+7, encrypt); +} diff --git a/libcli/auth/smbencrypt.c b/libcli/auth/smbencrypt.c new file mode 100644 index 0000000..8492202 --- /dev/null +++ b/libcli/auth/smbencrypt.c @@ -0,0 +1,1325 @@ +/* + Unix SMB/CIFS implementation. + SMB parameters and setup + Copyright (C) Andrew Tridgell 1992-1998 + Modified by Jeremy Allison 1995. + Copyright (C) Jeremy Allison 1995-2000. + Copyright (C) Luke Kennethc Casson Leighton 1996-2000. + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2002-2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/time.h" +#include "../libcli/auth/msrpc_parse.h" +#include "../lib/crypto/crypto.h" +#include "../libcli/auth/libcli_auth.h" +#include "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "lib/util/bytearray.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +int SMBencrypt_hash(const uint8_t lm_hash[16], const uint8_t *c8, uint8_t p24[24]) +{ + uint8_t p21[21]; + int rc; + + memset(p21,'\0',21); + memcpy(p21, lm_hash, 16); + + rc = SMBOWFencrypt(p21, c8, p24); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("SMBencrypt_hash: lm#, challenge, response\n")); + dump_data(100, p21, 16); + dump_data(100, c8, 8); + dump_data(100, p24, 24); +#endif + + return rc; +} + +/* + This implements the X/Open SMB password encryption + It takes a password ('unix' string), a 8 byte "crypt key" + and puts 24 bytes of encrypted password into p24 + + Returns False if password must have been truncated to create LM hash +*/ + +bool SMBencrypt(const char *passwd, const uint8_t *c8, uint8_t p24[24]) +{ + bool ret; + uint8_t lm_hash[16]; + int rc; + + ret = E_deshash(passwd, lm_hash); + rc = SMBencrypt_hash(lm_hash, c8, p24); + if (rc != 0) { + ret = false; + } + return ret; +} + +/** + * Creates the MD4 Hash of the users password in NT UNICODE. + * @param passwd password in 'unix' charset. + * @param p16 return password hashed with md4, caller allocated 16 byte buffer + */ + +bool E_md4hash(const char *passwd, uint8_t p16[16]) +{ + size_t len; + smb_ucs2_t *wpwd; + bool ret; + + ret = push_ucs2_talloc(NULL, &wpwd, passwd, &len); + if (!ret || len < 2) { + /* We don't want to return fixed data, as most callers + * don't check */ + mdfour(p16, (const uint8_t *)passwd, strlen(passwd)); + return false; + } + + len -= 2; + mdfour(p16, (const uint8_t *)wpwd, len); + + talloc_free(wpwd); + return true; +} + +/** + * Creates the DES forward-only Hash of the users password in DOS ASCII charset + * @param passwd password in 'unix' charset. + * @param p16 return password hashed with DES, caller allocated 16 byte buffer + * @return false if password was > 14 characters, and therefore may be incorrect, otherwise true + * @note p16 is filled in regardless + */ + +bool E_deshash(const char *passwd, uint8_t p16[16]) +{ + bool ret; + int rc; + uint8_t dospwd[14]; + TALLOC_CTX *frame = talloc_stackframe(); + + size_t converted_size; + + char *tmpbuf; + + ZERO_STRUCT(dospwd); + + tmpbuf = strupper_talloc(frame, passwd); + if (tmpbuf == NULL) { + /* Too many callers don't check this result, we need to fill in the buffer with something */ + strlcpy((char *)dospwd, passwd ? passwd : "", sizeof(dospwd)); + E_P16(dospwd, p16); + talloc_free(frame); + return false; + } + + ZERO_STRUCT(dospwd); + + ret = convert_string_error(CH_UNIX, CH_DOS, tmpbuf, strlen(tmpbuf), dospwd, sizeof(dospwd), &converted_size); + talloc_free(frame); + + /* Only the first 14 chars are considered, password need not + * be null terminated. We do this in the error and success + * case to avoid returning a fixed 'password' buffer, but + * callers should not use it when E_deshash returns false */ + + rc = E_P16((const uint8_t *)dospwd, p16); + if (rc != 0) { + ret = false; + } + + ZERO_STRUCT(dospwd); + + return ret; +} + +/** + * Creates the MD4 and DES (LM) Hash of the users password. + * MD4 is of the NT Unicode, DES is of the DOS UPPERCASE password. + * @param passwd password in 'unix' charset. + * @param nt_p16 return password hashed with md4, caller allocated 16 byte buffer + * @param p16 return password hashed with des, caller allocated 16 byte buffer + */ + +/* Does both the NT and LM owfs of a user's password */ +void nt_lm_owf_gen(const char *pwd, uint8_t nt_p16[16], uint8_t p16[16]) +{ + /* Calculate the MD4 hash (NT compatible) of the password */ + memset(nt_p16, '\0', 16); + E_md4hash(pwd, nt_p16); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("nt_lm_owf_gen: pwd, nt#\n")); + dump_data(120, (const uint8_t *)pwd, strlen(pwd)); + dump_data(100, nt_p16, 16); +#endif + + E_deshash(pwd, (uint8_t *)p16); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("nt_lm_owf_gen: pwd, lm#\n")); + dump_data(120, (const uint8_t *)pwd, strlen(pwd)); + dump_data(100, p16, 16); +#endif +} + +/* Does both the NTLMv2 owfs of a user's password */ +bool ntv2_owf_gen(const uint8_t owf[16], + const char *user_in, const char *domain_in, + uint8_t kr_buf[16]) +{ + smb_ucs2_t *user; + smb_ucs2_t *domain; + size_t user_byte_len; + size_t domain_byte_len; + gnutls_hmac_hd_t hmac_hnd = NULL; + int rc; + bool ok = false; + TALLOC_CTX *mem_ctx = talloc_init("ntv2_owf_gen for %s\\%s", domain_in, user_in); + + if (!mem_ctx) { + return false; + } + + if (!user_in) { + user_in = ""; + } + + if (!domain_in) { + domain_in = ""; + } + + user_in = strupper_talloc(mem_ctx, user_in); + if (user_in == NULL) { + talloc_free(mem_ctx); + return false; + } + + ok = push_ucs2_talloc(mem_ctx, &user, user_in, &user_byte_len ); + if (!ok) { + DEBUG(0, ("push_uss2_talloc() for user failed)\n")); + talloc_free(mem_ctx); + return false; + } + + ok = push_ucs2_talloc(mem_ctx, &domain, domain_in, &domain_byte_len); + if (!ok) { + DEBUG(0, ("push_ucs2_talloc() for domain failed\n")); + talloc_free(mem_ctx); + return false; + } + + SMB_ASSERT(user_byte_len >= 2); + SMB_ASSERT(domain_byte_len >= 2); + + /* We don't want null termination */ + user_byte_len = user_byte_len - 2; + domain_byte_len = domain_byte_len - 2; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + owf, + 16); + if (rc < 0) { + ok = false; + goto out; + } + + rc = gnutls_hmac(hmac_hnd, user, user_byte_len); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + ok = false; + goto out; + } + rc = gnutls_hmac(hmac_hnd, domain, domain_byte_len); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + ok = false; + goto out; + } + + gnutls_hmac_deinit(hmac_hnd, kr_buf); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("ntv2_owf_gen: user, domain, owfkey, kr\n")); + dump_data(100, (uint8_t *)user, user_byte_len); + dump_data(100, (uint8_t *)domain, domain_byte_len); + dump_data(100, owf, 16); + dump_data(100, kr_buf, 16); +#endif + + ok = true; +out: + talloc_free(mem_ctx); + return ok; +} + +/* Does the des encryption from the NT or LM MD4 hash. */ +int SMBOWFencrypt(const uint8_t passwd[16], const uint8_t *c8, uint8_t p24[24]) +{ + uint8_t p21[21]; + + ZERO_STRUCT(p21); + + memcpy(p21, passwd, 16); + return E_P24(p21, c8, p24); +} + +/* Does the des encryption. */ + +int SMBNTencrypt_hash(const uint8_t nt_hash[16], const uint8_t *c8, uint8_t *p24) +{ + uint8_t p21[21]; + int rc; + + memset(p21,'\0',21); + memcpy(p21, nt_hash, 16); + rc = SMBOWFencrypt(p21, c8, p24); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("SMBNTencrypt: nt#, challenge, response\n")); + dump_data(100, p21, 16); + dump_data(100, c8, 8); + dump_data(100, p24, 24); +#endif + + return rc; +} + +/* Does the NT MD4 hash then des encryption. Plaintext version of the above. */ + +int SMBNTencrypt(const char *passwd, const uint8_t *c8, uint8_t *p24) +{ + uint8_t nt_hash[16]; + E_md4hash(passwd, nt_hash); + return SMBNTencrypt_hash(nt_hash, c8, p24); +} + + +/* Does the md5 encryption from the Key Response for NTLMv2. */ +NTSTATUS SMBOWFencrypt_ntv2(const uint8_t kr[16], + const DATA_BLOB *srv_chal, + const DATA_BLOB *smbcli_chal, + uint8_t resp_buf[16]) +{ + gnutls_hmac_hd_t hmac_hnd = NULL; + NTSTATUS status; + int rc; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + kr, + 16); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_hmac(hmac_hnd, srv_chal->data, srv_chal->length); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + goto out; + } + rc = gnutls_hmac(hmac_hnd, smbcli_chal->data, smbcli_chal->length); + if (rc < 0) { + status = gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + goto out; + } + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBOWFencrypt_ntv2: srv_chal, smbcli_chal, resp_buf\n")); + dump_data(100, srv_chal->data, srv_chal->length); + dump_data(100, smbcli_chal->data, smbcli_chal->length); + dump_data(100, resp_buf, 16); +#endif + + status = NT_STATUS_OK; +out: + gnutls_hmac_deinit(hmac_hnd, resp_buf); + return status; +} + +NTSTATUS SMBsesskeygen_ntv2(const uint8_t kr[16], + const uint8_t *nt_resp, + uint8_t sess_key[16]) +{ + int rc; + + /* a very nice, 128 bit, variable session key */ + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + kr, + 16, + nt_resp, + 16, + sess_key); + if (rc != 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HASH_NOT_SUPPORTED); + } + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_ntv2:\n")); + dump_data(100, sess_key, 16); +#endif + + return NT_STATUS_OK; +} + +void SMBsesskeygen_ntv1(const uint8_t kr[16], uint8_t sess_key[16]) +{ + /* yes, this session key does not change - yes, this + is a problem - but it is 128 bits */ + + mdfour((uint8_t *)sess_key, kr, 16); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_ntv1:\n")); + dump_data(100, sess_key, 16); +#endif +} + +NTSTATUS SMBsesskeygen_lm_sess_key(const uint8_t lm_hash[16], + const uint8_t lm_resp[24], /* only uses 8 */ + uint8_t sess_key[16]) +{ + /* Calculate the LM session key (effective length 40 bits, + but changes with each session) */ + uint8_t p24[24]; + uint8_t partial_lm_hash[14]; + int rc; + + memcpy(partial_lm_hash, lm_hash, 8); + memset(partial_lm_hash + 8, 0xbd, 6); + + rc = des_crypt56_gnutls(p24, lm_resp, partial_lm_hash, SAMBA_GNUTLS_ENCRYPT); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + rc = des_crypt56_gnutls(p24+8, lm_resp, partial_lm_hash + 7, SAMBA_GNUTLS_ENCRYPT); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + memcpy(sess_key, p24, 16); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("SMBsesskeygen_lm_sess_key: \n")); + dump_data(100, sess_key, 16); +#endif + + return NT_STATUS_OK; +} + +DATA_BLOB NTLMv2_generate_names_blob(TALLOC_CTX *mem_ctx, + const char *hostname, + const char *domain) +{ + DATA_BLOB names_blob = data_blob_talloc(mem_ctx, NULL, 0); + + /* Deliberately ignore return here.. */ + if (hostname != NULL) { + (void)msrpc_gen(mem_ctx, &names_blob, + "aaa", + MsvAvNbDomainName, domain, + MsvAvNbComputerName, hostname, + MsvAvEOL, ""); + } else { + (void)msrpc_gen(mem_ctx, &names_blob, + "aa", + MsvAvNbDomainName, domain, + MsvAvEOL, ""); + } + return names_blob; +} + +static DATA_BLOB NTLMv2_generate_client_data(TALLOC_CTX *mem_ctx, + NTTIME nttime, + const DATA_BLOB *names_blob) +{ + uint8_t client_chal[8]; + DATA_BLOB response = data_blob(NULL, 0); + uint8_t long_date[8]; + + generate_random_buffer(client_chal, sizeof(client_chal)); + + push_nttime(long_date, 0, nttime); + + /* See http://www.ubiqx.org/cifs/SMB.html#SMB.8.5 */ + + /* Deliberately ignore return here.. */ + (void)msrpc_gen(mem_ctx, &response, "ddbbdb", + 0x00000101, /* Header */ + 0, /* 'Reserved' */ + long_date, 8, /* Timestamp */ + client_chal, 8, /* client challenge */ + 0, /* Unknown */ + names_blob->data, names_blob->length); /* End of name list */ + + return response; +} + +static DATA_BLOB NTLMv2_generate_response(TALLOC_CTX *out_mem_ctx, + const uint8_t ntlm_v2_hash[16], + const DATA_BLOB *server_chal, + NTTIME nttime, + const DATA_BLOB *names_blob) +{ + uint8_t ntlmv2_response[16]; + DATA_BLOB ntlmv2_client_data; + DATA_BLOB final_response; + NTSTATUS status; + + TALLOC_CTX *mem_ctx = talloc_named(out_mem_ctx, 0, + "NTLMv2_generate_response internal context"); + + if (!mem_ctx) { + return data_blob(NULL, 0); + } + + /* NTLMv2 */ + /* generate some data to pass into the response function - including + the hostname and domain name of the server */ + ntlmv2_client_data = NTLMv2_generate_client_data(mem_ctx, nttime, names_blob); + + /* Given that data, and the challenge from the server, generate a response */ + status = SMBOWFencrypt_ntv2(ntlm_v2_hash, + server_chal, + &ntlmv2_client_data, + ntlmv2_response); + if (!NT_STATUS_IS_OK(status)) { + talloc_free(mem_ctx); + return data_blob(NULL, 0); + } + + final_response = data_blob_talloc(out_mem_ctx, NULL, sizeof(ntlmv2_response) + ntlmv2_client_data.length); + + memcpy(final_response.data, ntlmv2_response, sizeof(ntlmv2_response)); + + memcpy(final_response.data+sizeof(ntlmv2_response), + ntlmv2_client_data.data, ntlmv2_client_data.length); + + talloc_free(mem_ctx); + + return final_response; +} + +static DATA_BLOB LMv2_generate_response(TALLOC_CTX *mem_ctx, + const uint8_t ntlm_v2_hash[16], + const DATA_BLOB *server_chal) +{ + uint8_t lmv2_response[16]; + DATA_BLOB lmv2_client_data = data_blob_talloc(mem_ctx, NULL, 8); + DATA_BLOB final_response = data_blob_talloc(mem_ctx, NULL,24); + NTSTATUS status; + + /* LMv2 */ + /* client-supplied random data */ + generate_random_buffer(lmv2_client_data.data, lmv2_client_data.length); + + /* Given that data, and the challenge from the server, generate a response */ + status = SMBOWFencrypt_ntv2(ntlm_v2_hash, + server_chal, + &lmv2_client_data, + lmv2_response); + if (!NT_STATUS_IS_OK(status)) { + data_blob_free(&lmv2_client_data); + return data_blob(NULL, 0); + } + memcpy(final_response.data, lmv2_response, sizeof(lmv2_response)); + + /* after the first 16 bytes is the random data we generated above, + so the server can verify us with it */ + memcpy(final_response.data+sizeof(lmv2_response), + lmv2_client_data.data, lmv2_client_data.length); + + data_blob_free(&lmv2_client_data); + + return final_response; +} + +bool SMBNTLMv2encrypt_hash(TALLOC_CTX *mem_ctx, + const char *user, const char *domain, const uint8_t nt_hash[16], + const DATA_BLOB *server_chal, + const NTTIME *server_timestamp, + const DATA_BLOB *names_blob, + DATA_BLOB *lm_response, DATA_BLOB *nt_response, + DATA_BLOB *lm_session_key, DATA_BLOB *user_session_key) +{ + uint8_t ntlm_v2_hash[16]; + NTSTATUS status; + + /* We don't use the NT# directly. Instead we use it mashed up with + the username and domain. + This prevents username swapping during the auth exchange + */ + if (!ntv2_owf_gen(nt_hash, user, domain, ntlm_v2_hash)) { + return false; + } + + if (nt_response) { + const NTTIME *nttime = server_timestamp; + NTTIME _now = 0; + + if (nttime == NULL) { + struct timeval tv_now = timeval_current(); + _now = timeval_to_nttime(&tv_now); + nttime = &_now; + } + + *nt_response = NTLMv2_generate_response(mem_ctx, + ntlm_v2_hash, + server_chal, + *nttime, + names_blob); + if (user_session_key) { + *user_session_key = data_blob_talloc(mem_ctx, NULL, 16); + + /* The NTLMv2 calculations also provide a session key, for signing etc later */ + /* use only the first 16 bytes of nt_response for session key */ + status = SMBsesskeygen_ntv2(ntlm_v2_hash, + nt_response->data, + user_session_key->data); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + } + } + + /* LMv2 */ + + if (lm_response) { + if (server_timestamp != NULL) { + *lm_response = data_blob_talloc_zero(mem_ctx, 24); + } else { + *lm_response = LMv2_generate_response(mem_ctx, + ntlm_v2_hash, + server_chal); + } + if (lm_session_key) { + *lm_session_key = data_blob_talloc(mem_ctx, NULL, 16); + + /* The NTLMv2 calculations also provide a session key, for signing etc later */ + /* use only the first 16 bytes of lm_response for session key */ + status = SMBsesskeygen_ntv2(ntlm_v2_hash, + lm_response->data, + lm_session_key->data); + if (!NT_STATUS_IS_OK(status)) { + return false; + } + } + } + + return true; +} + +bool SMBNTLMv2encrypt(TALLOC_CTX *mem_ctx, + const char *user, const char *domain, + const char *password, + const DATA_BLOB *server_chal, + const DATA_BLOB *names_blob, + DATA_BLOB *lm_response, DATA_BLOB *nt_response, + DATA_BLOB *lm_session_key, DATA_BLOB *user_session_key) +{ + uint8_t nt_hash[16]; + E_md4hash(password, nt_hash); + + return SMBNTLMv2encrypt_hash(mem_ctx, + user, domain, nt_hash, + server_chal, NULL, names_blob, + lm_response, nt_response, lm_session_key, user_session_key); +} + +NTSTATUS NTLMv2_RESPONSE_verify_netlogon_creds(const char *account_name, + const char *account_domain, + const DATA_BLOB response, + const struct netlogon_creds_CredentialState *creds, + const char *workgroup) +{ + TALLOC_CTX *frame = NULL; + /* RespType + HiRespType */ + static const char *magic = "\x01\x01"; + int cmp; + struct NTLMv2_RESPONSE v2_resp; + enum ndr_err_code err; + const struct AV_PAIR *av_nb_cn = NULL; + const struct AV_PAIR *av_nb_dn = NULL; + + if (response.length < 48) { + /* + * NTLMv2_RESPONSE has at least 48 bytes. + */ + return NT_STATUS_OK; + } + + cmp = memcmp(response.data + 16, magic, 2); + if (cmp != 0) { + /* + * It doesn't look like a valid NTLMv2_RESPONSE + */ + return NT_STATUS_OK; + } + + if (response.length == 95) { + /* + * ndr_pull_NTLMv2_RESPONSE() fails on this strange blob, + * because the AvPairs content is not valid + * as AvLen of the first pair is 33032 (0x8108). + * + * I saw a single machine sending the following 3 times + * in a row, but I'm not sure if everything is static. + * + * Note this is NTLMv2_CLIENT_CHALLENGE only, not + * the full NTLMv2_RESPONSE (which has Response of 16 bytes + * before the NTLMv2_CLIENT_CHALLENGE). + * + * Note this code only prevents + * ndr_pull_error(Buffer Size Error): Pull bytes 39016 + * debug message for a known case, the actual + * bug is also handled below in a generic way to + * map NT_STATUS_BUFFER_TOO_SMALL to NT_STATUS_OK. + * + * See https://bugzilla.samba.org/show_bug.cgi?id=14932 + */ + static const char *netapp_magic = + "\x01\x01\x00\x00\x00\x00\x00\x00" + "\x3f\x3f\x3f\x3f\x3f\x3f\x3f\x3f" + "\xb8\x82\x3a\xf1\xb3\xdd\x08\x15" + "\x00\x00\x00\x00\x11\xa2\x08\x81" + "\x50\x38\x22\x78\x2b\x94\x47\xfe" + "\x54\x94\x7b\xff\x17\x27\x5a\xb4" + "\xf4\x18\xba\xdc\x2c\x38\xfd\x5b" + "\xfb\x0e\xc1\x85\x1e\xcc\x92\xbb" + "\x9b\xb1\xc4\xd5\x53\x14\xff\x8c" + "\x76\x49\xf5\x45\x90\x19\xa2"; + /* + * First we check the initial bytes + * and the 0x3F timestamp. + */ + cmp = memcmp(response.data + 16, + netapp_magic, + 16); + if (cmp == 0) { + /* + * Then check everything after the + * client challenge + */ + cmp = memcmp(response.data + 40, + netapp_magic + 24, + response.length - 40); + if (cmp == 0) { + DBG_DEBUG("Invalid NETAPP NTLMv2_RESPONSE " + "for user[%s\\%s] against " + "SEC_CHAN(%u)[%s/%s] " + "in workgroup[%s]\n", + account_domain, + account_name, + creds->secure_channel_type, + creds->computer_name, + creds->account_name, + workgroup); + return NT_STATUS_OK; + } + } + } + + frame = talloc_stackframe(); + + err = ndr_pull_struct_blob(&response, frame, &v2_resp, + (ndr_pull_flags_fn_t)ndr_pull_NTLMv2_RESPONSE); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + NTSTATUS status; + status = ndr_map_error2ntstatus(err); + if (NT_STATUS_EQUAL(status, NT_STATUS_BUFFER_TOO_SMALL)) { + /* + * We are supposed to ignore invalid buffers, + * see https://bugzilla.samba.org/show_bug.cgi?id=14932 + */ + status = NT_STATUS_OK; + } + DEBUG(2,("%s: Failed to parse NTLMv2_RESPONSE length=%u " + "for user[%s\\%s] against SEC_CHAN(%u)[%s/%s] " + "in workgroup[%s] - %s %s %s\n", + __func__, + (unsigned)response.length, + account_domain, + account_name, + creds->secure_channel_type, + creds->computer_name, + creds->account_name, + workgroup, + ndr_map_error2string(err), + NT_STATUS_IS_OK(status) ? "(ignoring) =>" : "=>", + nt_errstr(status))); + dump_data(2, response.data, response.length); + TALLOC_FREE(frame); + return status; + } + + if (DEBUGLVL(10)) { + NDR_PRINT_DEBUG(NTLMv2_RESPONSE, &v2_resp); + } + + /* + * Make sure the netbios computer name in the + * NTLMv2_RESPONSE matches the computer name + * in the secure channel credentials for workstation + * trusts. + * + * And the netbios domain name matches our + * workgroup. + * + * This prevents workstations from requesting + * the session key of NTLMSSP sessions of clients + * to other hosts. + */ + if (creds->secure_channel_type == SEC_CHAN_WKSTA) { + av_nb_cn = ndr_ntlmssp_find_av(&v2_resp.Challenge.AvPairs, + MsvAvNbComputerName); + av_nb_dn = ndr_ntlmssp_find_av(&v2_resp.Challenge.AvPairs, + MsvAvNbDomainName); + } + + if (av_nb_cn != NULL) { + const char *v = NULL; + char *a = NULL; + size_t len; + + v = av_nb_cn->Value.AvNbComputerName; + + a = talloc_strdup(frame, creds->account_name); + if (a == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + len = strlen(a); + if (len > 0 && a[len - 1] == '$') { + a[len - 1] = '\0'; + } + + cmp = strcasecmp_m(a, v); + if (cmp != 0) { + DEBUG(2,("%s: NTLMv2_RESPONSE with " + "NbComputerName[%s] rejected " + "for user[%s\\%s] " + "against SEC_CHAN_WKSTA[%s/%s] " + "in workgroup[%s]\n", + __func__, v, + account_domain, + account_name, + creds->computer_name, + creds->account_name, + workgroup)); + TALLOC_FREE(frame); + return NT_STATUS_LOGON_FAILURE; + } + } + if (av_nb_dn != NULL) { + const char *v = NULL; + + v = av_nb_dn->Value.AvNbDomainName; + + cmp = strcasecmp_m(workgroup, v); + if (cmp != 0) { + DEBUG(2,("%s: NTLMv2_RESPONSE with " + "NbDomainName[%s] rejected " + "for user[%s\\%s] " + "against SEC_CHAN_WKSTA[%s/%s] " + "in workgroup[%s]\n", + __func__, v, + account_domain, + account_name, + creds->computer_name, + creds->account_name, + workgroup)); + TALLOC_FREE(frame); + return NT_STATUS_LOGON_FAILURE; + } + } + + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +enum encode_order { + ENCODE_ORDER_PASSWORD_FIRST, + ENCODE_ORDER_PASSWORD_LAST, +}; + +#define PASSWORD_BUFFER_LEN 512 + +static ssize_t _encode_pwd_buffer_from_str(uint8_t buf[PASSWORD_BUFFER_LEN], + const char *password, + int string_flags, + enum encode_order order) +{ + ssize_t new_pw_len; + size_t pw_pos = 0; + size_t random_pos = 0; + size_t random_len = 0; + + /* The incoming buffer can be any alignment. */ + string_flags |= STR_NOALIGN; + + new_pw_len = push_string(buf, + password, + PASSWORD_BUFFER_LEN, + string_flags); + if (new_pw_len < 0) { + BURN_DATA_SIZE(buf, PASSWORD_BUFFER_LEN); + return -1; + } + + if (new_pw_len == PASSWORD_BUFFER_LEN) { + return new_pw_len; + } + + switch (order) { + case ENCODE_ORDER_PASSWORD_FIRST: + pw_pos = 0; + random_pos = new_pw_len; + random_len = PASSWORD_BUFFER_LEN - random_pos; + break; + case ENCODE_ORDER_PASSWORD_LAST: + pw_pos = PASSWORD_BUFFER_LEN - new_pw_len; + random_pos = 0; + random_len = pw_pos; + memmove(buf + pw_pos, buf, new_pw_len); + break; + } + + generate_random_buffer(buf + random_pos, random_len); + + return new_pw_len; +} + +/*********************************************************** + encode a password buffer with a unicode password. The buffer + is filled with random data to make it harder to attack. +************************************************************/ +bool encode_pw_buffer(uint8_t buffer[516], const char *password, int string_flags) +{ + ssize_t pw_len; + + pw_len = _encode_pwd_buffer_from_str(buffer, + password, + string_flags, + ENCODE_ORDER_PASSWORD_LAST); + if (pw_len < 0 || pw_len > PASSWORD_BUFFER_LEN) { + return false; + } + + PUSH_LE_U32(buffer, PASSWORD_BUFFER_LEN, pw_len); + + return true; +} + + +/*********************************************************** + decode a password buffer + *new_pw_len is the length in bytes of the possibly mulitbyte + returned password including termination. +************************************************************/ + +bool decode_pw_buffer(TALLOC_CTX *ctx, + uint8_t in_buffer[516], + char **pp_new_pwrd, + size_t *new_pw_len, + charset_t string_charset) +{ + DATA_BLOB new_password; + int byte_len=0; + bool ok; + + *pp_new_pwrd = NULL; + *new_pw_len = 0; + + ok = extract_pw_from_buffer(ctx, in_buffer, &new_password); + if (!ok) { + return false; + } + + /* + Warning !!! : This function is called from some rpc call. + The password IN the buffer may be a UNICODE string. + The password IN new_pwrd is an ASCII string + If you reuse that code somewhere else check first. + */ + + /* decode into the return buffer. */ + ok = convert_string_talloc(ctx, + string_charset, + CH_UNIX, + new_password.data, + new_password.length, + (void *)pp_new_pwrd, + new_pw_len); + data_blob_free(&new_password); + if (!ok) { + DBG_ERR("Failed to convert incoming password\n"); + return false; + } + talloc_keep_secret(*pp_new_pwrd); + +#ifdef DEBUG_PASSWORD + DEBUG(100,("decode_pw_buffer: new_pwrd: ")); + dump_data(100, (uint8_t *)*pp_new_pwrd, *new_pw_len); + DEBUG(100,("multibyte len:%lu\n", (unsigned long int)*new_pw_len)); + DEBUG(100,("original char len:%d\n", byte_len/2)); +#endif + + return true; +} + +#define MAX_PASSWORD_LEN 256 + +/* + * [MS-SAMR] 2.2.6.32 This creates the buffer to be sent. It is of type + * SAMPR_USER_PASSWORD_AES. + */ +bool encode_pwd_buffer514_from_str(uint8_t buffer[514], + const char *password, + uint32_t string_flags) +{ + ssize_t pw_len; + + pw_len = _encode_pwd_buffer_from_str(buffer + 2, + password, + string_flags, + ENCODE_ORDER_PASSWORD_FIRST); + if (pw_len < 0) { + return false; + } + + PUSH_LE_U16(buffer, 0, pw_len); + + return true; +} + +bool extract_pwd_blob_from_buffer514(TALLOC_CTX *mem_ctx, + const uint8_t in_buffer[514], + DATA_BLOB *new_password) +{ +#ifdef DEBUG_PASSWORD + DEBUG(100, ("in_buffer: ")); + dump_data(100, in_buffer, 514); +#endif + + new_password->length = PULL_LE_U16(in_buffer, 0); + if (new_password->length == 0 || new_password->length > 512) { + return false; + } + + new_password->data = + talloc_memdup(mem_ctx, in_buffer + 2, new_password->length); + if (new_password->data == NULL) { + return false; + } + talloc_keep_secret(new_password->data); + +#ifdef DEBUG_PASSWORD + DEBUG(100, ("new_pwd_len: %zu\n", new_password->length)); + DEBUG(100, ("new_pwd: ")); + dump_data(100, new_password->data, new_password->length); +#endif + + return true; +} + +bool decode_pwd_string_from_buffer514(TALLOC_CTX *mem_ctx, + const uint8_t in_buffer[514], + charset_t string_charset, + DATA_BLOB *decoded_password) +{ + DATA_BLOB new_password = { + .length = 0, + }; + bool ok; + + ok = extract_pwd_blob_from_buffer514(mem_ctx, in_buffer, &new_password); + if (!ok) { + return false; + } + + ok = convert_string_talloc(mem_ctx, + string_charset, + CH_UNIX, + new_password.data, + new_password.length, + (void *)&decoded_password->data, + &decoded_password->length); + data_blob_free(&new_password); + if (!ok) { + return false; + } + talloc_keep_secret(decoded_password->data); + + return true; +} + +/*********************************************************** + Encode an arc4 password change buffer. +************************************************************/ +NTSTATUS encode_rc4_passwd_buffer(const char *passwd, + const DATA_BLOB *session_key, + struct samr_CryptPasswordEx *out_crypt_pwd) +{ + uint8_t _confounder[16] = {0}; + DATA_BLOB confounder = data_blob_const(_confounder, 16); + DATA_BLOB pw_data = data_blob_const(out_crypt_pwd->data, 516); + bool ok; + int rc; + + ok = encode_pw_buffer(pw_data.data, passwd, STR_UNICODE); + if (!ok) { + return NT_STATUS_INVALID_PARAMETER; + } + + generate_random_buffer(confounder.data, confounder.length); + + rc = samba_gnutls_arcfour_confounded_md5(&confounder, + session_key, + &pw_data, + SAMBA_GNUTLS_ENCRYPT); + if (rc < 0) { + ZERO_ARRAY(_confounder); + data_blob_clear(&pw_data); + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + /* + * The packet format is the 516 byte RC4 encrypted + * pasword followed by the 16 byte counfounder + * The confounder is a salt to prevent pre-computed hash attacks on the + * database. + */ + memcpy(&out_crypt_pwd->data[516], confounder.data, confounder.length); + ZERO_ARRAY(_confounder); + + return NT_STATUS_OK; +} + +/*********************************************************** + Decode an arc4 encrypted password change buffer. +************************************************************/ + +NTSTATUS decode_rc4_passwd_buffer(const DATA_BLOB *psession_key, + struct samr_CryptPasswordEx *inout_crypt_pwd) +{ + /* Confounder is last 16 bytes. */ + DATA_BLOB confounder = data_blob_const(&inout_crypt_pwd->data[516], 16); + DATA_BLOB pw_data = data_blob_const(&inout_crypt_pwd->data, 516); + int rc; + + rc = samba_gnutls_arcfour_confounded_md5(&confounder, + psession_key, + &pw_data, + SAMBA_GNUTLS_DECRYPT); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + return NT_STATUS_OK; +} + +/*********************************************************** + encode a password buffer with an already unicode password. The + rest of the buffer is filled with random data to make it harder to attack. +************************************************************/ + +static bool create_pw_buffer_from_blob(uint8_t buffer[512], + const DATA_BLOB *in_password, + enum encode_order order) +{ + size_t pwd_pos = 0; + size_t random_pos = 0; + size_t random_len = 0; + + if (in_password->length > 512) { + return false; + } + + switch (order) { + case ENCODE_ORDER_PASSWORD_FIRST: + pwd_pos = 0; + random_pos = in_password->length; + break; + case ENCODE_ORDER_PASSWORD_LAST: + pwd_pos = PASSWORD_BUFFER_LEN - in_password->length; + random_pos = 0; + break; + } + random_len = PASSWORD_BUFFER_LEN - in_password->length; + + memcpy(buffer + pwd_pos, in_password->data, in_password->length); + generate_random_buffer(buffer + random_pos, random_len); + + return true; +} + +bool set_pw_in_buffer(uint8_t buffer[516], const DATA_BLOB *password) +{ + bool ok; + + ok = create_pw_buffer_from_blob(buffer, + password, + ENCODE_ORDER_PASSWORD_LAST); + if (!ok) { + return false; + } + + /* + * The length of the new password is in the last 4 bytes of + * the data buffer. + */ + PUSH_LE_U32(buffer, PASSWORD_BUFFER_LEN, password->length); + + return true; +} + +/*********************************************************** + decode a password buffer + *new_pw_size is the length in bytes of the extracted unicode password +************************************************************/ +bool extract_pw_from_buffer(TALLOC_CTX *mem_ctx, + uint8_t in_buffer[516], DATA_BLOB *new_pass) +{ + int byte_len=0; + + /* The length of the new password is in the last 4 bytes of the data buffer. */ + + byte_len = IVAL(in_buffer, 512); + +#ifdef DEBUG_PASSWORD + dump_data(100, in_buffer, 516); +#endif + + /* Password cannot be longer than the size of the password buffer */ + if ( (byte_len < 0) || (byte_len > 512)) { + return false; + } + + *new_pass = data_blob_talloc(mem_ctx, &in_buffer[512 - byte_len], byte_len); + + if (!new_pass->data) { + return false; + } + talloc_keep_secret(new_pass->data); + + return true; +} + + +/* encode a wkssvc_PasswordBuffer: + * + * similar to samr_CryptPasswordEx. Different: 8byte confounder (instead of + * 16byte), confounder in front of the 516 byte buffer (instead of after that + * buffer), calling MD5Update() first with session_key and then with confounder + * (vice versa in samr) - Guenther */ + +WERROR encode_wkssvc_join_password_buffer(TALLOC_CTX *mem_ctx, + const char *pwd, + DATA_BLOB *session_key, + struct wkssvc_PasswordBuffer **out_pwd_buf) +{ + struct wkssvc_PasswordBuffer *pwd_buf = NULL; + uint8_t _confounder[8] = {0}; + DATA_BLOB confounder = data_blob_const(_confounder, 8); + uint8_t pwbuf[516] = {0}; + DATA_BLOB encrypt_pwbuf = data_blob_const(pwbuf, 516); + int rc; + + pwd_buf = talloc_zero(mem_ctx, struct wkssvc_PasswordBuffer); + if (pwd_buf == NULL) { + return WERR_NOT_ENOUGH_MEMORY; + } + + encode_pw_buffer(pwbuf, pwd, STR_UNICODE); + + generate_random_buffer(_confounder, sizeof(_confounder)); + + rc = samba_gnutls_arcfour_confounded_md5(session_key, + &confounder, + &encrypt_pwbuf, + SAMBA_GNUTLS_ENCRYPT); + if (rc < 0) { + ZERO_ARRAY(_confounder); + TALLOC_FREE(pwd_buf); + return gnutls_error_to_werror(rc, WERR_CONTENT_BLOCKED); + } + + memcpy(&pwd_buf->data[0], confounder.data, confounder.length); + ZERO_ARRAY(_confounder); + memcpy(&pwd_buf->data[8], encrypt_pwbuf.data, encrypt_pwbuf.length); + ZERO_ARRAY(pwbuf); + + *out_pwd_buf = pwd_buf; + + return WERR_OK; +} + +WERROR decode_wkssvc_join_password_buffer(TALLOC_CTX *mem_ctx, + struct wkssvc_PasswordBuffer *pwd_buf, + DATA_BLOB *session_key, + char **pwd) +{ + uint8_t _confounder[8] = { 0 }; + DATA_BLOB confounder = data_blob_const(_confounder, 8); + uint8_t pwbuf[516] = {0}; + DATA_BLOB decrypt_pwbuf = data_blob_const(pwbuf, 516); + bool ok; + int rc; + + if (pwd_buf == NULL) { + return WERR_INVALID_PASSWORD; + } + + *pwd = NULL; + + if (session_key->length != 16) { + DEBUG(10,("invalid session key\n")); + return WERR_INVALID_PASSWORD; + } + + confounder = data_blob_const(&pwd_buf->data[0], 8); + memcpy(&pwbuf, &pwd_buf->data[8], 516); + + rc = samba_gnutls_arcfour_confounded_md5(session_key, + &confounder, + &decrypt_pwbuf, + SAMBA_GNUTLS_ENCRYPT); + if (rc < 0) { + ZERO_ARRAY(_confounder); + TALLOC_FREE(pwd_buf); + return gnutls_error_to_werror(rc, WERR_CONTENT_BLOCKED); + } + + ok = decode_pw_buffer(mem_ctx, + decrypt_pwbuf.data, + pwd, + &decrypt_pwbuf.length, + CH_UTF16); + ZERO_ARRAY(pwbuf); + + if (!ok) { + return WERR_INVALID_PASSWORD; + } + + return WERR_OK; +} diff --git a/libcli/auth/spnego.h b/libcli/auth/spnego.h new file mode 100644 index 0000000..49645e0 --- /dev/null +++ b/libcli/auth/spnego.h @@ -0,0 +1,82 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + + 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/>. +*/ + +#define OID_SPNEGO "1.3.6.1.5.5.2" +#define OID_NTLMSSP "1.3.6.1.4.1.311.2.2.10" +#define OID_KERBEROS5_OLD "1.2.840.48018.1.2.2" +#define OID_KERBEROS5 "1.2.840.113554.1.2.2" + +#define ADS_IGNORE_PRINCIPAL "not_defined_in_RFC4178@please_ignore" + +#define SPNEGO_DELEG_FLAG 0x01 +#define SPNEGO_MUTUAL_FLAG 0x02 +#define SPNEGO_REPLAY_FLAG 0x04 +#define SPNEGO_SEQUENCE_FLAG 0x08 +#define SPNEGO_ANON_FLAG 0x10 +#define SPNEGO_CONF_FLAG 0x20 +#define SPNEGO_INTEG_FLAG 0x40 + +#define TOK_ID_KRB_AP_REQ ((const uint8_t *)"\x01\x00") +#define TOK_ID_KRB_AP_REP ((const uint8_t *)"\x02\x00") +#define TOK_ID_KRB_ERROR ((const uint8_t *)"\x03\x00") +#define TOK_ID_GSS_GETMIC ((const uint8_t *)"\x01\x01") +#define TOK_ID_GSS_WRAP ((const uint8_t *)"\x02\x01") + +enum spnego_negResult { + SPNEGO_ACCEPT_COMPLETED = 0, + SPNEGO_ACCEPT_INCOMPLETE = 1, + SPNEGO_REJECT = 2, + SPNEGO_REQUEST_MIC = 3, + /* + * The max value is 0xff (255) on the wire + */ + SPNEGO_NONE_RESULT = 256 +}; + +struct spnego_negTokenInit { + const char * const *mechTypes; + DATA_BLOB reqFlags; + uint8_t reqFlagsPadding; + DATA_BLOB mechToken; + DATA_BLOB mechListMIC; + char *targetPrincipal; +}; + +struct spnego_negTokenTarg { + enum spnego_negResult negResult; + const char *supportedMech; + DATA_BLOB responseToken; + DATA_BLOB mechListMIC; +}; + +struct spnego_data { + int type; + struct spnego_negTokenInit negTokenInit; + struct spnego_negTokenTarg negTokenTarg; +}; + +enum spnego_message_type { + SPNEGO_NEG_TOKEN_INIT = 0, + SPNEGO_NEG_TOKEN_TARG = 1, +}; + +#include "../libcli/auth/spnego_proto.h" diff --git a/libcli/auth/spnego_parse.c b/libcli/auth/spnego_parse.c new file mode 100644 index 0000000..f7f19b1 --- /dev/null +++ b/libcli/auth/spnego_parse.c @@ -0,0 +1,446 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "../libcli/auth/spnego.h" +#include "../lib/util/asn1.h" + +static bool read_negTokenInit(struct asn1_data *asn1, TALLOC_CTX *mem_ctx, + struct spnego_negTokenInit *token) +{ + ZERO_STRUCTP(token); + + if (!asn1_start_tag(asn1, ASN1_CONTEXT(0))) return false; + if (!asn1_start_tag(asn1, ASN1_SEQUENCE(0))) return false; + + while (asn1_tag_remaining(asn1) > 0) { + int i; + uint8_t context; + + if (!asn1_peek_uint8(asn1, &context)) { + asn1_set_error(asn1); + break; + } + + switch (context) { + /* Read mechTypes */ + case ASN1_CONTEXT(0): { + const char **mechTypes; + + if (!asn1_start_tag(asn1, ASN1_CONTEXT(0))) return false; + if (!asn1_start_tag(asn1, ASN1_SEQUENCE(0))) return false; + + mechTypes = talloc(mem_ctx, const char *); + if (mechTypes == NULL) { + asn1_set_error(asn1); + return false; + } + for (i = 0; asn1_tag_remaining(asn1) > 0; i++) { + char *oid; + const char **p; + p = talloc_realloc(mem_ctx, + mechTypes, + const char *, i+2); + if (p == NULL) { + talloc_free(mechTypes); + asn1_set_error(asn1); + return false; + } + mechTypes = p; + + if (!asn1_read_OID(asn1, mechTypes, &oid)) return false; + mechTypes[i] = oid; + } + mechTypes[i] = NULL; + token->mechTypes = mechTypes; + + asn1_end_tag(asn1); + asn1_end_tag(asn1); + break; + } + /* Read reqFlags */ + case ASN1_CONTEXT(1): + if (!asn1_start_tag(asn1, ASN1_CONTEXT(1))) return false; + if (!asn1_read_BitString(asn1, mem_ctx, &token->reqFlags, + &token->reqFlagsPadding)) return false; + if (!asn1_end_tag(asn1)) return false; + break; + /* Read mechToken */ + case ASN1_CONTEXT(2): + if (!asn1_start_tag(asn1, ASN1_CONTEXT(2))) return false; + if (!asn1_read_OctetString(asn1, mem_ctx, &token->mechToken)) return false; + if (!asn1_end_tag(asn1)) return false; + break; + /* Read mecListMIC */ + case ASN1_CONTEXT(3): + { + uint8_t type_peek; + if (!asn1_start_tag(asn1, ASN1_CONTEXT(3))) return false; + if (!asn1_peek_uint8(asn1, &type_peek)) { + asn1_set_error(asn1); + break; + } + if (type_peek == ASN1_OCTET_STRING) { + if (!asn1_read_OctetString(asn1, mem_ctx, + &token->mechListMIC)) return false; + } else { + /* RFC 2478 says we have an Octet String here, + but W2k sends something different... */ + char *mechListMIC; + if (!asn1_start_tag(asn1, ASN1_SEQUENCE(0))) return false; + if (!asn1_start_tag(asn1, ASN1_CONTEXT(0))) return false; + if (!asn1_read_GeneralString(asn1, mem_ctx, &mechListMIC)) return false; + if (!asn1_end_tag(asn1)) return false; + if (!asn1_end_tag(asn1)) return false; + + token->targetPrincipal = mechListMIC; + } + if (!asn1_end_tag(asn1)) return false; + break; + } + default: + asn1_set_error(asn1); + break; + } + } + + if (!asn1_end_tag(asn1)) return false; + if (!asn1_end_tag(asn1)) return false; + + return !asn1_has_error(asn1); +} + +static bool write_negTokenInit(struct asn1_data *asn1, struct spnego_negTokenInit *token) +{ + if (!asn1_push_tag(asn1, ASN1_CONTEXT(0))) return false; + if (!asn1_push_tag(asn1, ASN1_SEQUENCE(0))) return false; + + /* Write mechTypes */ + if (token->mechTypes && *token->mechTypes) { + int i; + + if (!asn1_push_tag(asn1, ASN1_CONTEXT(0))) return false; + if (!asn1_push_tag(asn1, ASN1_SEQUENCE(0))) return false; + for (i = 0; token->mechTypes[i]; i++) { + if (!asn1_write_OID(asn1, token->mechTypes[i])) return false; + } + if (!asn1_pop_tag(asn1)) return false; + if (!asn1_pop_tag(asn1)) return false; + } + + /* write reqFlags */ + if (token->reqFlags.length > 0) { + if (!asn1_push_tag(asn1, ASN1_CONTEXT(1))) return false; + if (!asn1_write_BitString(asn1, token->reqFlags.data, + token->reqFlags.length, + token->reqFlagsPadding)) return false; + if (!asn1_pop_tag(asn1)) return false; + } + + /* write mechToken */ + if (token->mechToken.data) { + if (!asn1_push_tag(asn1, ASN1_CONTEXT(2))) return false; + if (!asn1_write_OctetString(asn1, token->mechToken.data, + token->mechToken.length)) return false; + if (!asn1_pop_tag(asn1)) return false; + } + + /* write mechListMIC */ + if (token->mechListMIC.data) { + if (!asn1_push_tag(asn1, ASN1_CONTEXT(3))) return false; +#if 0 + /* This is what RFC 2478 says ... */ + asn1_write_OctetString(asn1, token->mechListMIC.data, + token->mechListMIC.length); +#else + /* ... but unfortunately this is what Windows + sends/expects */ + if (!asn1_push_tag(asn1, ASN1_SEQUENCE(0))) return false; + if (!asn1_push_tag(asn1, ASN1_CONTEXT(0))) return false; + if (!asn1_push_tag(asn1, ASN1_GENERAL_STRING)) return false; + if (!asn1_write(asn1, token->mechListMIC.data, + token->mechListMIC.length)) return false; + if (!asn1_pop_tag(asn1)) return false; + if (!asn1_pop_tag(asn1)) return false; + if (!asn1_pop_tag(asn1)) return false; +#endif + if (!asn1_pop_tag(asn1)) return false; + } + + if (!asn1_pop_tag(asn1)) return false; + if (!asn1_pop_tag(asn1)) return false; + + return !asn1_has_error(asn1); +} + +static bool read_negTokenTarg(struct asn1_data *asn1, TALLOC_CTX *mem_ctx, + struct spnego_negTokenTarg *token) +{ + ZERO_STRUCTP(token); + + if (!asn1_start_tag(asn1, ASN1_CONTEXT(1))) return false; + if (!asn1_start_tag(asn1, ASN1_SEQUENCE(0))) return false; + + while (asn1_tag_remaining(asn1) > 0) { + uint8_t context; + uint8_t neg_result; + char *oid; + + if (!asn1_peek_uint8(asn1, &context)) { + asn1_set_error(asn1); + break; + } + + switch (context) { + case ASN1_CONTEXT(0): + if (!asn1_start_tag(asn1, ASN1_CONTEXT(0))) return false; + if (!asn1_start_tag(asn1, ASN1_ENUMERATED)) return false; + if (!asn1_read_uint8(asn1, &neg_result)) return false; + token->negResult = neg_result; + if (!asn1_end_tag(asn1)) return false; + if (!asn1_end_tag(asn1)) return false; + break; + case ASN1_CONTEXT(1): + if (!asn1_start_tag(asn1, ASN1_CONTEXT(1))) return false; + if (!asn1_read_OID(asn1, mem_ctx, &oid)) return false; + token->supportedMech = oid; + if (!asn1_end_tag(asn1)) return false; + break; + case ASN1_CONTEXT(2): + if (!asn1_start_tag(asn1, ASN1_CONTEXT(2))) return false; + if (!asn1_read_OctetString(asn1, mem_ctx, &token->responseToken)) return false; + if (!asn1_end_tag(asn1)) return false; + break; + case ASN1_CONTEXT(3): + if (!asn1_start_tag(asn1, ASN1_CONTEXT(3))) return false; + if (!asn1_read_OctetString(asn1, mem_ctx, &token->mechListMIC)) return false; + if (!asn1_end_tag(asn1)) return false; + break; + default: + asn1_set_error(asn1); + break; + } + } + + if (!asn1_end_tag(asn1)) return false; + if (!asn1_end_tag(asn1)) return false; + + return !asn1_has_error(asn1); +} + +static bool write_negTokenTarg(struct asn1_data *asn1, struct spnego_negTokenTarg *token) +{ + if (!asn1_push_tag(asn1, ASN1_CONTEXT(1))) return false; + if (!asn1_push_tag(asn1, ASN1_SEQUENCE(0))) return false; + + if (token->negResult != SPNEGO_NONE_RESULT) { + if (!asn1_push_tag(asn1, ASN1_CONTEXT(0))) return false; + if (!asn1_write_enumerated(asn1, token->negResult)) return false; + if (!asn1_pop_tag(asn1)) return false; + } + + if (token->supportedMech) { + if (!asn1_push_tag(asn1, ASN1_CONTEXT(1))) return false; + if (!asn1_write_OID(asn1, token->supportedMech)) return false; + if (!asn1_pop_tag(asn1)) return false; + } + + if (token->responseToken.data) { + if (!asn1_push_tag(asn1, ASN1_CONTEXT(2))) return false; + if (!asn1_write_OctetString(asn1, token->responseToken.data, + token->responseToken.length)) return false; + if (!asn1_pop_tag(asn1)) return false; + } + + if (token->mechListMIC.data) { + if (!asn1_push_tag(asn1, ASN1_CONTEXT(3))) return false; + if (!asn1_write_OctetString(asn1, token->mechListMIC.data, + token->mechListMIC.length)) return false; + if (!asn1_pop_tag(asn1)) return false; + } + + if (!asn1_pop_tag(asn1)) return false; + if (!asn1_pop_tag(asn1)) return false; + + return !asn1_has_error(asn1); +} + +ssize_t spnego_read_data(TALLOC_CTX *mem_ctx, DATA_BLOB data, struct spnego_data *token) +{ + struct asn1_data *asn1; + ssize_t ret = -1; + uint8_t context; + + ZERO_STRUCTP(token); + + if (data.length == 0) { + return ret; + } + + asn1 = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); + if (asn1 == NULL) { + return -1; + } + + if (!asn1_load(asn1, data)) goto err; + + if (!asn1_peek_uint8(asn1, &context)) { + asn1_set_error(asn1); + } else { + switch (context) { + case ASN1_APPLICATION(0): + if (!asn1_start_tag(asn1, ASN1_APPLICATION(0))) goto err; + if (!asn1_check_OID(asn1, OID_SPNEGO)) goto err; + if (read_negTokenInit(asn1, mem_ctx, &token->negTokenInit)) { + token->type = SPNEGO_NEG_TOKEN_INIT; + } + if (!asn1_end_tag(asn1)) goto err; + break; + case ASN1_CONTEXT(1): + if (read_negTokenTarg(asn1, mem_ctx, &token->negTokenTarg)) { + token->type = SPNEGO_NEG_TOKEN_TARG; + } + break; + default: + asn1_set_error(asn1); + break; + } + } + + if (!asn1_has_error(asn1)) { + ret = asn1_current_ofs(asn1); + } + + err: + + asn1_free(asn1); + + return ret; +} + +ssize_t spnego_write_data(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct spnego_data *spnego) +{ + struct asn1_data *asn1 = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); + ssize_t ret = -1; + + if (asn1 == NULL) { + return -1; + } + + switch (spnego->type) { + case SPNEGO_NEG_TOKEN_INIT: + if (!asn1_push_tag(asn1, ASN1_APPLICATION(0))) goto err; + if (!asn1_write_OID(asn1, OID_SPNEGO)) goto err; + if (!write_negTokenInit(asn1, &spnego->negTokenInit)) goto err; + if (!asn1_pop_tag(asn1)) goto err; + break; + case SPNEGO_NEG_TOKEN_TARG: + write_negTokenTarg(asn1, &spnego->negTokenTarg); + break; + default: + asn1_set_error(asn1); + break; + } + + if (!asn1_extract_blob(asn1, mem_ctx, blob)) { + goto err; + } + + ret = asn1_current_ofs(asn1); + + err: + + asn1_free(asn1); + + return ret; +} + +bool spnego_free_data(struct spnego_data *spnego) +{ + bool ret = true; + + if (!spnego) goto out; + + switch(spnego->type) { + case SPNEGO_NEG_TOKEN_INIT: + if (spnego->negTokenInit.mechTypes) { + talloc_free(discard_const(spnego->negTokenInit.mechTypes)); + } + data_blob_free(&spnego->negTokenInit.reqFlags); + data_blob_free(&spnego->negTokenInit.mechToken); + data_blob_free(&spnego->negTokenInit.mechListMIC); + talloc_free(spnego->negTokenInit.targetPrincipal); + break; + case SPNEGO_NEG_TOKEN_TARG: + if (spnego->negTokenTarg.supportedMech) { + talloc_free(discard_const(spnego->negTokenTarg.supportedMech)); + } + data_blob_free(&spnego->negTokenTarg.responseToken); + data_blob_free(&spnego->negTokenTarg.mechListMIC); + break; + default: + ret = false; + break; + } + ZERO_STRUCTP(spnego); +out: + return ret; +} + +bool spnego_write_mech_types(TALLOC_CTX *mem_ctx, + const char * const *mech_types, + DATA_BLOB *blob) +{ + bool ret = false; + struct asn1_data *asn1 = asn1_init(mem_ctx, ASN1_MAX_TREE_DEPTH); + + if (asn1 == NULL) { + return false; + } + + /* Write mechTypes */ + if (mech_types && *mech_types) { + int i; + + if (!asn1_push_tag(asn1, ASN1_SEQUENCE(0))) goto err; + for (i = 0; mech_types[i]; i++) { + if (!asn1_write_OID(asn1, mech_types[i])) goto err; + } + if (!asn1_pop_tag(asn1)) goto err; + } + + if (asn1_has_error(asn1)) { + goto err; + } + + if (!asn1_extract_blob(asn1, mem_ctx, blob)) { + goto err; + } + + ret = true; + + err: + + asn1_free(asn1); + + return ret; +} diff --git a/libcli/auth/spnego_proto.h b/libcli/auth/spnego_proto.h new file mode 100644 index 0000000..c0fa934 --- /dev/null +++ b/libcli/auth/spnego_proto.h @@ -0,0 +1,28 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + + 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/>. +*/ + +ssize_t spnego_read_data(TALLOC_CTX *mem_ctx, DATA_BLOB data, struct spnego_data *token); +ssize_t spnego_write_data(TALLOC_CTX *mem_ctx, DATA_BLOB *blob, struct spnego_data *spnego); +bool spnego_free_data(struct spnego_data *spnego); +bool spnego_write_mech_types(TALLOC_CTX *mem_ctx, + const char * const *mech_types, + DATA_BLOB *blob); diff --git a/libcli/auth/tests/ntlm_check.c b/libcli/auth/tests/ntlm_check.c new file mode 100644 index 0000000..79b8ec9 --- /dev/null +++ b/libcli/auth/tests/ntlm_check.c @@ -0,0 +1,413 @@ +/* + * Unit tests for the ntlm_check password hash check library. + * + * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018 + * + * 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/>. + * + */ + +/* + * from cmocka.c: + * These headers or their equivalents should be included prior to + * including + * this header file. + * + * #include <stdarg.h> + * #include <stddef.h> + * #include <setjmp.h> + * + * This allows test applications to use custom definitions of C standard + * library functions and types. + * + */ + +/* + * Note that the messaging routines (audit_message_send and get_event_server) + * are not tested by these unit tests. Currently they are for integration + * test support, and as such are exercised by the integration tests. + */ +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "includes.h" +#include "librpc/gen_ndr/netlogon.h" +#include "libcli/auth/libcli_auth.h" +#include "auth/credentials/credentials.h" + +struct ntlm_state { + const char *username; + const char *domain; + DATA_BLOB challenge; + DATA_BLOB ntlm; + DATA_BLOB lm; + DATA_BLOB ntlm_key; + DATA_BLOB lm_key; + const struct samr_Password *nt_hash; +}; + +static int test_ntlm_setup_with_options(void **state, + int flags, bool upn) +{ + NTSTATUS status; + DATA_BLOB challenge = { + .data = discard_const_p(uint8_t, "I am a teapot"), + .length = 8 + }; + struct ntlm_state *ntlm_state = talloc(NULL, struct ntlm_state); + DATA_BLOB target_info = NTLMv2_generate_names_blob(ntlm_state, + NULL, + "serverdom"); + struct cli_credentials *creds = cli_credentials_init(ntlm_state); + cli_credentials_set_username(creds, + "testuser", + CRED_SPECIFIED); + cli_credentials_set_domain(creds, + "testdom", + CRED_SPECIFIED); + cli_credentials_set_workstation(creds, + "testwksta", + CRED_SPECIFIED); + cli_credentials_set_password(creds, + "testpass", + CRED_SPECIFIED); + + if (upn) { + cli_credentials_set_principal(creds, + "testuser@samba.org", + CRED_SPECIFIED); + } + + cli_credentials_get_ntlm_username_domain(creds, + ntlm_state, + &ntlm_state->username, + &ntlm_state->domain); + + status = cli_credentials_get_ntlm_response(creds, + ntlm_state, + &flags, + challenge, + NULL, + target_info, + &ntlm_state->lm, + &ntlm_state->ntlm, + &ntlm_state->lm_key, + &ntlm_state->ntlm_key); + ntlm_state->challenge = challenge; + + ntlm_state->nt_hash = cli_credentials_get_nt_hash(creds, + ntlm_state); + + if (!NT_STATUS_IS_OK(status)) { + return -1; + } + + *state = ntlm_state; + return 0; +} + +static int test_ntlm_setup(void **state) { + return test_ntlm_setup_with_options(state, 0, false); +} + +static int test_ntlm_and_lm_setup(void **state) { + return test_ntlm_setup_with_options(state, + CLI_CRED_LANMAN_AUTH, + false); +} + +static int test_ntlm2_setup(void **state) { + return test_ntlm_setup_with_options(state, + CLI_CRED_NTLM2, + false); +} + +static int test_ntlmv2_setup(void **state) { + return test_ntlm_setup_with_options(state, + CLI_CRED_NTLMv2_AUTH, + false); +} + +static int test_ntlm_teardown(void **state) +{ + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + TALLOC_FREE(ntlm_state); + *state = NULL; + return 0; +} + +static void test_ntlm_allowed(void **state) +{ + DATA_BLOB user_sess_key, lm_sess_key; + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + NTSTATUS status; + status = ntlm_password_check(ntlm_state, + false, + NTLM_AUTH_ON, + 0, + &ntlm_state->challenge, + &ntlm_state->lm, + &ntlm_state->ntlm, + ntlm_state->username, + ntlm_state->username, + ntlm_state->domain, + NULL, + ntlm_state->nt_hash, + &user_sess_key, + &lm_sess_key); + + assert_int_equal(NT_STATUS_V(status), NT_STATUS_V(NT_STATUS_OK)); +} + +static void test_ntlm_allowed_lm_supplied(void **state) +{ + test_ntlm_allowed(state); +} + +static void test_ntlm_disabled(void **state) +{ + DATA_BLOB user_sess_key, lm_sess_key; + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + NTSTATUS status; + status = ntlm_password_check(ntlm_state, + false, + NTLM_AUTH_DISABLED, + 0, + &ntlm_state->challenge, + &ntlm_state->lm, + &ntlm_state->ntlm, + ntlm_state->username, + ntlm_state->username, + ntlm_state->domain, + NULL, + ntlm_state->nt_hash, + &user_sess_key, + &lm_sess_key); + + assert_int_equal(NT_STATUS_V(status), NT_STATUS_V(NT_STATUS_NTLM_BLOCKED)); +} + +static void test_ntlm2(void **state) +{ + DATA_BLOB user_sess_key, lm_sess_key; + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + NTSTATUS status; + status = ntlm_password_check(ntlm_state, + false, + NTLM_AUTH_ON, + 0, + &ntlm_state->challenge, + &ntlm_state->lm, + &ntlm_state->ntlm, + ntlm_state->username, + ntlm_state->username, + ntlm_state->domain, + NULL, + ntlm_state->nt_hash, + &user_sess_key, + &lm_sess_key); + + /* + * NTLM2 session security (where the real challenge is the + * MD5(challenge, client-challenge) (in the first 8 bytes of + * the lm) isn't decoded by ntlm_password_check(), it must + * first be converted back into normal NTLM by the NTLMSSP + * layer + */ + assert_int_equal(NT_STATUS_V(status), + NT_STATUS_V(NT_STATUS_WRONG_PASSWORD)); +} + +static void test_ntlm_mschapv2_only_allowed(void **state) +{ + DATA_BLOB user_sess_key, lm_sess_key; + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + NTSTATUS status; + status = ntlm_password_check(ntlm_state, + false, + NTLM_AUTH_MSCHAPv2_NTLMV2_ONLY, + MSV1_0_ALLOW_MSVCHAPV2, + &ntlm_state->challenge, + &ntlm_state->lm, + &ntlm_state->ntlm, + ntlm_state->username, + ntlm_state->username, + ntlm_state->domain, + NULL, + ntlm_state->nt_hash, + &user_sess_key, + &lm_sess_key); + + assert_int_equal(NT_STATUS_V(status), NT_STATUS_V(NT_STATUS_OK)); +} + +static void test_ntlm_mschapv2_only_denied(void **state) +{ + DATA_BLOB user_sess_key, lm_sess_key; + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + NTSTATUS status; + status = ntlm_password_check(ntlm_state, + false, + NTLM_AUTH_MSCHAPv2_NTLMV2_ONLY, + 0, + &ntlm_state->challenge, + &ntlm_state->lm, + &ntlm_state->ntlm, + ntlm_state->username, + ntlm_state->username, + ntlm_state->domain, + NULL, + ntlm_state->nt_hash, + &user_sess_key, + &lm_sess_key); + + assert_int_equal(NT_STATUS_V(status), + NT_STATUS_V(NT_STATUS_WRONG_PASSWORD)); +} + +static void test_ntlmv2_only_ntlmv2(void **state) +{ + DATA_BLOB user_sess_key, lm_sess_key; + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + NTSTATUS status; + status = ntlm_password_check(ntlm_state, + false, + NTLM_AUTH_NTLMV2_ONLY, + 0, + &ntlm_state->challenge, + &ntlm_state->lm, + &ntlm_state->ntlm, + ntlm_state->username, + ntlm_state->username, + ntlm_state->domain, + NULL, + ntlm_state->nt_hash, + &user_sess_key, + &lm_sess_key); + + assert_int_equal(NT_STATUS_V(status), NT_STATUS_V(NT_STATUS_OK)); +} + +static void test_ntlmv2_only_ntlm(void **state) +{ + DATA_BLOB user_sess_key, lm_sess_key; + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + NTSTATUS status; + status = ntlm_password_check(ntlm_state, + false, + NTLM_AUTH_NTLMV2_ONLY, + 0, + &ntlm_state->challenge, + &ntlm_state->lm, + &ntlm_state->ntlm, + ntlm_state->username, + ntlm_state->username, + ntlm_state->domain, + NULL, + ntlm_state->nt_hash, + &user_sess_key, + &lm_sess_key); + + assert_int_equal(NT_STATUS_V(status), + NT_STATUS_V(NT_STATUS_WRONG_PASSWORD)); +} + +static void test_ntlmv2_only_ntlm_and_lanman(void **state) +{ + test_ntlmv2_only_ntlm(state); +} + +static void test_ntlmv2_only_ntlm_once(void **state) +{ + DATA_BLOB user_sess_key, lm_sess_key; + struct ntlm_state *ntlm_state + = talloc_get_type_abort(*state, + struct ntlm_state); + NTSTATUS status; + status = ntlm_password_check(ntlm_state, + false, + NTLM_AUTH_NTLMV2_ONLY, + 0, + &ntlm_state->challenge, + &data_blob_null, + &ntlm_state->ntlm, + ntlm_state->username, + ntlm_state->username, + ntlm_state->domain, + NULL, + ntlm_state->nt_hash, + &user_sess_key, + &lm_sess_key); + + assert_int_equal(NT_STATUS_V(status), + NT_STATUS_V(NT_STATUS_WRONG_PASSWORD)); +} + +int main(int argc, const char **argv) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_ntlm_allowed, + test_ntlm_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlm_allowed_lm_supplied, + test_ntlm_and_lm_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlm_disabled, + test_ntlm_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlm2, + test_ntlm2_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlm_mschapv2_only_allowed, + test_ntlm_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlm_mschapv2_only_denied, + test_ntlm_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlmv2_only_ntlm, + test_ntlm_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlmv2_only_ntlm_and_lanman, + test_ntlm_and_lm_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlmv2_only_ntlm_once, + test_ntlm_setup, + test_ntlm_teardown), + cmocka_unit_test_setup_teardown(test_ntlmv2_only_ntlmv2, + test_ntlmv2_setup, + test_ntlm_teardown) + }; + + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/libcli/auth/tests/test_encode_decode.c b/libcli/auth/tests/test_encode_decode.c new file mode 100644 index 0000000..4683edf --- /dev/null +++ b/libcli/auth/tests/test_encode_decode.c @@ -0,0 +1,162 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) 2022 Andreas Schneider <asn@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "libcli/auth/smbencrypt.c" + +/* The following hexdumps are from a Windows Server 2022 time trace */ +static const uint8_t plaintext_data[] = { + 0x14, 0x00, 0x50, 0x00, 0x61, 0x00, 0x24, 0x00, + 0x24, 0x00, 0x77, 0x00, 0x30, 0x00, 0x72, 0x00, + 0x64, 0x00, 0x40, 0x00, 0x32, 0x00, 0xc2, 0x34, + 0x7d, 0x21, 0x79, 0x05, 0xef, 0x88, 0xd7, 0x11, + 0xec, 0xe2, 0xce, 0xb5, 0xd4, 0x4d, 0x64, 0x2d, + 0x15, 0x79, 0x01, 0x39, 0xb8, 0xb9, 0x89, 0x5c, + 0x4e, 0x71, 0xbd, 0xf0, 0x14, 0x0c, 0x87, 0x72, + 0xa5, 0xfa, 0x90, 0xbe, 0x62, 0x55, 0xad, 0x7f, + 0xe9, 0x7f, 0x0d, 0x20, 0x19, 0x3a, 0x76, 0xbe, + 0xb2, 0x14, 0x6d, 0x5b, 0x25, 0x1c, 0x67, 0x3a, + 0x23, 0x45, 0x1f, 0x7e, 0x36, 0xa0, 0x95, 0xb7, + 0xa7, 0xb1, 0x33, 0xe1, 0xc4, 0xb6, 0xe6, 0x2d, + 0xd8, 0x2f, 0xe7, 0xdf, 0x01, 0xe8, 0xba, 0x02, + 0x54, 0x36, 0xe9, 0xb6, 0x5e, 0x00, 0x52, 0x9e, + 0x64, 0x00, 0xcb, 0x3c, 0x6d, 0x05, 0x43, 0x7d, + 0x01, 0x9c, 0x22, 0x18, 0x92, 0xe7, 0xa3, 0x55, + 0x65, 0x6d, 0x2e, 0xa3, 0x53, 0x6e, 0xc0, 0x67, + 0x26, 0xac, 0xaa, 0x98, 0xa4, 0xcb, 0xb4, 0x49, + 0x13, 0x60, 0xd4, 0x33, 0x2c, 0x77, 0x58, 0x5e, + 0x50, 0x45, 0xaa, 0x1e, 0x05, 0x15, 0x18, 0x59, + 0x55, 0xca, 0x14, 0x37, 0x31, 0xac, 0x63, 0xfc, + 0x63, 0xa8, 0x2a, 0xa9, 0x99, 0xec, 0x49, 0x87, + 0x64, 0x1d, 0x4e, 0xdd, 0xa3, 0xd0, 0xdc, 0x08, + 0x00, 0x17, 0xf4, 0x2f, 0x9c, 0x4a, 0x17, 0xc7, + 0xbd, 0x30, 0xb7, 0x0e, 0x81, 0xe4, 0xd5, 0x94, + 0x31, 0xff, 0xd6, 0xcc, 0xc6, 0xbb, 0x39, 0xcd, + 0x72, 0xfe, 0xa6, 0x3d, 0x0d, 0x88, 0x68, 0x40, + 0xf8, 0x51, 0x2b, 0xe6, 0xc9, 0xaa, 0x84, 0xf3, + 0xf4, 0x6e, 0x55, 0x37, 0xbf, 0x5d, 0x87, 0xce, + 0xa6, 0x80, 0x4f, 0x8f, 0x8f, 0x7b, 0xe8, 0x30, + 0xc3, 0x2e, 0x24, 0xc7, 0x3e, 0xf1, 0x9f, 0xa6, + 0x77, 0xca, 0x04, 0xbe, 0xb5, 0xe1, 0x40, 0x59, + 0x43, 0xc5, 0x30, 0xc8, 0xe7, 0xbf, 0xab, 0xfa, + 0x86, 0x62, 0xd9, 0x3a, 0x8e, 0xa9, 0x34, 0x73, + 0x20, 0x7b, 0x61, 0x1b, 0x0e, 0xca, 0x98, 0xec, + 0xa1, 0xc1, 0x78, 0xa9, 0xa7, 0x6c, 0x8c, 0xe3, + 0x21, 0x7d, 0xb9, 0x90, 0xe2, 0x73, 0x1a, 0x99, + 0x1d, 0x44, 0xa8, 0xd5, 0x7f, 0x0a, 0x59, 0x47, + 0xd0, 0xf5, 0x6c, 0x14, 0xff, 0x4a, 0x29, 0x20, + 0xb5, 0xfc, 0xe9, 0xf0, 0xa5, 0x35, 0x9e, 0x1c, + 0xa1, 0x4c, 0xec, 0xb5, 0x7d, 0x2d, 0x27, 0xff, + 0x7a, 0x42, 0x18, 0xb8, 0x53, 0x4e, 0xfb, 0xec, + 0xb1, 0xc1, 0x65, 0x2d, 0xa4, 0x69, 0x85, 0x56, + 0x61, 0x6d, 0x21, 0x66, 0x88, 0x31, 0xdf, 0xba, + 0x28, 0xc6, 0x9a, 0xf8, 0xb7, 0xf6, 0x2a, 0x43, + 0xba, 0x9b, 0x84, 0x14, 0xce, 0xa9, 0xc9, 0xf5, + 0x85, 0x6f, 0x31, 0x89, 0x8d, 0xfc, 0x25, 0x2e, + 0x98, 0x25, 0x5a, 0x03, 0xf0, 0xb8, 0x5d, 0x4a, + 0xd4, 0x4c, 0xc8, 0x62, 0x4e, 0xeb, 0x07, 0xc8, + 0x5c, 0x9e, 0x63, 0x30, 0xfe, 0x9f, 0x0f, 0x96, + 0xd0, 0xd7, 0x70, 0xad, 0xcd, 0x84, 0xbc, 0x1e, + 0x48, 0xa0, 0x20, 0x14, 0x10, 0xa4, 0xb1, 0x5b, + 0x05, 0x36, 0x9a, 0x6d, 0xb0, 0x10, 0x98, 0xbd, + 0x8d, 0xa2, 0xd1, 0xb2, 0xfa, 0x23, 0x37, 0xeb, + 0xb0, 0x04, 0x53, 0xcb, 0xa1, 0xa9, 0xc4, 0x88, + 0xdd, 0xf9, 0x80, 0xf5, 0xa9, 0xcd, 0x7b, 0xf8, + 0x77, 0x0b, 0x19, 0x84, 0x4c, 0xef, 0x2c, 0x14, + 0xa1, 0xdc, 0x9f, 0x2f, 0x41, 0xd0, 0xd0, 0x33, + 0x29, 0x8a, 0xb9, 0x39, 0x7e, 0xc9, 0x7f, 0xe7, + 0x63, 0x64, 0xa4, 0x7b, 0x4a, 0x6a, 0x33, 0xa7, + 0xaa, 0xf6, 0xca, 0x98, 0xc4, 0x9b, 0x62, 0x5b, + 0xcd, 0x53, 0x82, 0xbf, 0xf0, 0x0b, 0x9c, 0xe7, + 0xb2, 0x44, 0x1b, 0x88, 0x71, 0x61, 0xa1, 0x36, + 0x9e, 0x7a, 0x0a, 0x3c, 0x20, 0xd8, 0xff, 0xa1, + 0x23, 0x66 +}; + +static void torture_encode_pwd_buffer514_from_str(void **state) +{ + uint8_t data[514] = {0}; + bool ok; + + ok = encode_pwd_buffer514_from_str(data, "Pa$$w0rd@2", STR_UNICODE); + assert_true(ok); + + /* We can only compare the first 22 bytes as the rest is random data */ + assert_memory_equal(data, plaintext_data, 22); + assert_memory_not_equal(data + 22, + plaintext_data + 22, + sizeof(plaintext_data) - 22); +} + +static void torture_extract_pwd_blob_from_buffer514(void **state) +{ + DATA_BLOB new_password = { + .length = 0, + }; + bool ok; + + ok = extract_pwd_blob_from_buffer514(NULL, plaintext_data, &new_password); + assert_true(ok); + assert_int_equal(new_password.length, 20); + assert_memory_equal(new_password.data, + plaintext_data + 2, + new_password.length); + data_blob_free(&new_password); +} + +static void torture_decode_pwd_string_from_buffer514(void **state) +{ + DATA_BLOB decoded_password = { + .length = 0, + }; + bool ok; + + ok = decode_pwd_string_from_buffer514(NULL, + plaintext_data, + CH_UTF16, + &decoded_password); + assert_true(ok); + assert_memory_equal(decoded_password.data, "Pa$$w0rd@2", decoded_password.length); + data_blob_free(&decoded_password); +} + +int main(int argc, char *argv[]) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_encode_pwd_buffer514_from_str), + cmocka_unit_test(torture_extract_pwd_blob_from_buffer514), + cmocka_unit_test(torture_decode_pwd_string_from_buffer514), + }; + + if (argc == 2) { + cmocka_set_test_filter(argv[1]); + } + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/libcli/auth/tests/test_gnutls.c b/libcli/auth/tests/test_gnutls.c new file mode 100644 index 0000000..da7a3b4 --- /dev/null +++ b/libcli/auth/tests/test_gnutls.c @@ -0,0 +1,528 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) 2019 Guenther Deschner <gd@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "includes.h" +#include "libcli/auth/libcli_auth.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#if defined(HAVE_GNUTLS_AES_CFB8) && GNUTLS_VERSION_NUMBER > 0x03060a +static void torture_gnutls_aes_128_cfb_flags(void **state, + const DATA_BLOB session_key, + const DATA_BLOB seq_num_initial, + const DATA_BLOB confounder_initial, + const DATA_BLOB confounder_expected, + const DATA_BLOB clear_initial, + const DATA_BLOB crypt_expected) +{ + uint8_t confounder[8]; + DATA_BLOB io; + gnutls_cipher_hd_t cipher_hnd = NULL; + uint8_t sess_kf0[16] = {0}; + gnutls_datum_t key = { + .data = sess_kf0, + .size = sizeof(sess_kf0), + }; + uint32_t iv_size = + gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_CFB8); + uint8_t _iv[iv_size]; + gnutls_datum_t iv = { + .data = _iv, + .size = iv_size, + }; + uint32_t i; + int rc; + + assert_int_equal(session_key.length, 16); + assert_int_equal(seq_num_initial.length, 8); + assert_int_equal(confounder_initial.length, 8); + assert_int_equal(confounder_expected.length, 8); + assert_int_equal(clear_initial.length, crypt_expected.length); + + DEBUG(0,("checking buffer size: %d\n", (int)clear_initial.length)); + + io = data_blob_dup_talloc(NULL, clear_initial); + assert_non_null(io.data); + assert_int_equal(io.length, clear_initial.length); + + memcpy(confounder, confounder_initial.data, 8); + + DEBUG(0,("confounder before crypt:\n")); + dump_data(0, confounder, 8); + DEBUG(0,("initial seq num:\n")); + dump_data(0, seq_num_initial.data, 8); + DEBUG(0,("io data before crypt:\n")); + dump_data(0, io.data, io.length); + + for (i = 0; i < key.size; i++) { + key.data[i] = session_key.data[i] ^ 0xf0; + } + + ZERO_ARRAY(_iv); + + memcpy(iv.data + 0, seq_num_initial.data, 8); + memcpy(iv.data + 8, seq_num_initial.data, 8); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_AES_128_CFB8, + &key, + &iv); + assert_int_equal(rc, 0); + + rc = gnutls_cipher_encrypt(cipher_hnd, + confounder, + 8); + assert_int_equal(rc, 0); + + rc = gnutls_cipher_encrypt(cipher_hnd, + io.data, + io.length); + assert_int_equal(rc, 0); + + DEBUG(0,("confounder after crypt:\n")); + dump_data(0, confounder, 8); + DEBUG(0,("initial seq num:\n")); + dump_data(0, seq_num_initial.data, 8); + DEBUG(0,("io data after crypt:\n")); + dump_data(0, io.data, io.length); + assert_memory_equal(io.data, crypt_expected.data, crypt_expected.length); + assert_memory_equal(confounder, confounder_expected.data, confounder_expected.length); + + rc = gnutls_cipher_decrypt(cipher_hnd, + confounder, + 8); + assert_int_equal(rc, 0); + + rc = gnutls_cipher_decrypt(cipher_hnd, + io.data, + io.length); + assert_int_equal(rc, 0); + gnutls_cipher_deinit(cipher_hnd); + + DEBUG(0,("confounder after decrypt:\n")); + dump_data(0, confounder, 8); + DEBUG(0,("initial seq num:\n")); + dump_data(0, seq_num_initial.data, 8); + DEBUG(0,("io data after decrypt:\n")); + dump_data(0, io.data, io.length); + assert_memory_equal(io.data, clear_initial.data, clear_initial.length); + assert_memory_equal(confounder, confounder_initial.data, confounder_initial.length); +} +#endif + +static void torture_gnutls_aes_128_cfb(void **state) +{ +#if defined(HAVE_GNUTLS_AES_CFB8) && GNUTLS_VERSION_NUMBER > 0x03060a + const uint8_t _session_key[16] = { + 0x8E, 0xE8, 0x27, 0x85, 0x83, 0x41, 0x3C, 0x8D, + 0xC9, 0x54, 0x70, 0x75, 0x8E, 0xC9, 0x69, 0x91 + }; + const DATA_BLOB session_key = data_blob_const(_session_key, 16); + const uint8_t _seq_num_initial[8] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 + }; + const DATA_BLOB seq_num_initial = + data_blob_const(_seq_num_initial, 8); + const uint8_t _confounder_initial[8] = { + 0x6E, 0x09, 0x25, 0x94, 0x01, 0xA0, 0x09, 0x31 + }; + const DATA_BLOB confounder_initial = + data_blob_const(_confounder_initial, 8); + const uint8_t _confounder_expected[8] = { + 0xCA, 0xFB, 0xAC, 0xFB, 0xA8, 0x26, 0x75, 0x2A + }; + const DATA_BLOB confounder_expected = + data_blob_const(_confounder_expected, 8); + const uint8_t _clear_initial[] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8A, 0xE3, 0x13, 0x71, 0x02, 0xF4, 0x36, 0x71, + 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x40, 0x28, 0x00, 0x78, 0x57, 0x34, 0x12, + 0x34, 0x12, 0xCD, 0xAB, 0xEF, 0x00, 0x01, 0x23, + 0x45, 0x67, 0x89, 0xAB, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x5D, 0x88, 0x8A, 0xEB, 0x1C, 0xC9, 0x11, + 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + const DATA_BLOB clear_initial = data_blob_const(_clear_initial, + sizeof(_clear_initial)); + const uint8_t crypt_buffer[] = { + 0xE2, 0xE5, 0xE3, 0x26, 0x45, 0xFB, 0xFC, 0xF3, + 0x9C, 0x14, 0xDD, 0xE1, 0x39, 0x23, 0xE0, 0x55, + 0xED, 0x8F, 0xF4, 0x92, 0xA1, 0xBD, 0xDC, 0x40, + 0x58, 0x6F, 0xD2, 0x5B, 0xF9, 0xC9, 0xA3, 0x87, + 0x46, 0x4B, 0x7F, 0xB2, 0x03, 0xD2, 0x35, 0x22, + 0x3E, 0x70, 0x9F, 0x1E, 0x3F, 0x1F, 0xDB, 0x7D, + 0x79, 0x88, 0x5A, 0x3D, 0xD3, 0x40, 0x1E, 0x69, + 0xD7, 0xE2, 0x1D, 0x5A, 0xE9, 0x3B, 0xE1, 0xE2, + 0x98, 0xFD, 0xCB, 0x3A, 0xF7, 0xB5, 0x1C, 0xF8, + 0xCA, 0x02, 0x00, 0x99, 0x9F, 0x0C, 0x01, 0xE6, + 0xD2, 0x00, 0xAF, 0xE0, 0x51, 0x88, 0x62, 0x50, + 0xB7, 0xE8, 0x6D, 0x63, 0x4B, 0x97, 0x05, 0xC1, + 0xD4, 0x83, 0x96, 0x29, 0x80, 0xAE, 0xD8, 0xA2, + 0xED, 0xC9, 0x5D, 0x0D, 0x29, 0xFF, 0x2C, 0x23, + 0x02, 0xFA, 0x3B, 0xEE, 0xE8, 0xBA, 0x06, 0x01, + 0x95, 0xDF, 0x80, 0x76, 0x0B, 0x17, 0x0E, 0xD8 + }; + const DATA_BLOB crypt_expected = data_blob_const(crypt_buffer, + sizeof(crypt_buffer)); + int buffer_sizes[] = { + 0, 1, 3, 7, 8, 9, 15, 16, 17 + }; + int i; + + torture_gnutls_aes_128_cfb_flags(state, + session_key, + seq_num_initial, + confounder_initial, + confounder_expected, + clear_initial, + crypt_expected); + + /* repeat the test for varying buffer sizes */ + + for (i = 0; i < ARRAY_SIZE(buffer_sizes); i++) { + DATA_BLOB clear_initial_trunc = + data_blob_const(clear_initial.data, buffer_sizes[i]); + DATA_BLOB crypt_expected_trunc = + data_blob_const(crypt_expected.data, buffer_sizes[i]); + torture_gnutls_aes_128_cfb_flags(state, + session_key, + seq_num_initial, + confounder_initial, + confounder_expected, + clear_initial_trunc, + crypt_expected_trunc); + } +#endif +} + +static void torture_gnutls_des_crypt56(void **state) +{ + static const uint8_t key[7] = { + 0x69, 0x88, 0x96, 0x8E, 0xB5, 0x3A, 0x24 + }; + static const uint8_t clear[8] = { + 0x3F, 0x49, 0x5B, 0x20, 0xA7, 0x84, 0xC2, 0x34 + }; + static const uint8_t crypt_expected[8] = { + 0x54, 0x86, 0xCF, 0x51, 0x49, 0x3A, 0x53, 0x5B + }; + + uint8_t crypt[8]; + uint8_t decrypt[8]; + int rc; + + rc = des_crypt56_gnutls(crypt, clear, key, SAMBA_GNUTLS_ENCRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(crypt, crypt_expected, 8); + + rc = des_crypt56_gnutls(decrypt, crypt, key, SAMBA_GNUTLS_DECRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(decrypt, clear, 8); +} + +static void torture_gnutls_E_P16(void **state) +{ + static const uint8_t key[14] = { + 0x98, 0xFD, 0xCB, 0x3A, 0xF7, 0xB5, 0x1C, 0xF8, + 0x69, 0x88, 0x96, 0x8E, 0xB5, 0x3A + }; + uint8_t buffer[16] = { + 0x9C, 0x14, 0xDD, 0xE1, 0x39, 0x23, 0xE0, 0x55, + 0x3F, 0x49, 0x5B, 0x20, 0xA7, 0x84, 0xC2, 0x34 + }; + static const uint8_t crypt_expected[16] = { + 0x41, 0x4A, 0x7B, 0xEA, 0xAB, 0xBB, 0x95, 0xCE, + 0x1D, 0xEA, 0xD9, 0xFF, 0xB0, 0xA9, 0xA4, 0x05 + }; + + int rc; + + rc = E_P16(key, buffer); + assert_int_equal(rc, 0); + assert_memory_equal(buffer, crypt_expected, 16); +} + +static void torture_gnutls_E_P24(void **state) +{ + static const uint8_t key[21] = { + 0xFB, 0x67, 0x99, 0xA4, 0x83, 0xF3, 0xD4, 0xED, + 0x98, 0xFD, 0xCB, 0x3A, 0xF7, 0xB5, 0x1C, 0xF8, + 0x69, 0x88, 0x96, 0x8E, 0x3A + }; + const uint8_t c8[8] = { + 0x44, 0xFB, 0xAC, 0xFB, 0x83, 0xB6, 0x75, 0x2A + }; + static const uint8_t crypt_expected[24] = { + 0x1A, 0x5E, 0x11, 0xA1, 0x59, 0xA9, 0x6B, 0x4E, + 0x12, 0x5D, 0x81, 0x75, 0xA6, 0x62, 0x15, 0x6D, + 0x5D, 0x20, 0x25, 0xC1, 0xA3, 0x92, 0xB3, 0x28 + }; + + uint8_t crypt[24]; + int rc; + + rc = E_P24(key, c8, crypt); + assert_int_equal(rc, 0); + assert_memory_equal(crypt, crypt_expected, 24); +} + +static void torture_gnutls_SMBOWFencrypt(void **state) +{ + static const uint8_t password[16] = { + 'M', 'y', 'p', 'a', 's', 's', 'w', 'o', + 'r', 'd', 'i', 's', '1', '1', '1', '1' + }; + const uint8_t c8[8] = { + 0x79, 0x88, 0x5A, 0x3D, 0xD3, 0x40, 0x1E, 0x69 + }; + static const uint8_t crypt_expected[24] = { + 0x3F, 0xE3, 0x53, 0x75, 0x81, 0xB4, 0xF0, 0xE7, + 0x0C, 0xDE, 0xCD, 0xAE, 0x39, 0x1F, 0x14, 0xB4, + 0xA4, 0x2B, 0x3E, 0x39, 0x16, 0xFD, 0x1D, 0x62 + }; + + uint8_t crypt[24]; + int rc; + + rc = SMBOWFencrypt(password, c8, crypt); + assert_int_equal(rc, 0); + assert_memory_equal(crypt, crypt_expected, 24); +} + +static void torture_gnutls_E_old_pw_hash(void **state) +{ + static uint8_t key[14] = { + 0x98, 0xFD, 0xCB, 0x3A, 0xF7, 0xB5, 0x1C, 0xF8, + 0x69, 0x88, 0x96, 0x8E, 0xB5, 0x3A + }; + uint8_t clear[16] = { + 0x9C, 0x14, 0xDD, 0xE1, 0x39, 0x23, 0xE0, 0x55, + 0x3F, 0x49, 0x5B, 0x20, 0xA7, 0x84, 0xC2, 0x34 + }; + static const uint8_t crypt_expected[16] = { + 0x6A, 0xC7, 0x08, 0xCA, 0x2A, 0xC1, 0xAA, 0x64, + 0x37, 0xEF, 0xBE, 0x58, 0xC2, 0x59, 0x33, 0xEC + }; + uint8_t crypt[16]; + int rc; + + rc = E_old_pw_hash(key, clear, crypt); + assert_int_equal(rc, 0); + assert_memory_equal(crypt, crypt_expected, 16); +} + +static void torture_gnutls_des_crypt128(void **state) +{ + static uint8_t key[16] = { + 0x98, 0xFD, 0xCB, 0x3A, 0xF7, 0xB5, 0x1C, 0xF8, + 0xA9, 0x69, 0x88, 0x96, 0x8E, 0xB5, 0x3A, 0x24 + }; + static const uint8_t clear[8] = { + 0x3F, 0x49, 0x5B, 0x20, 0xA7, 0x84, 0xC2, 0x34 + }; + static const uint8_t crypt_expected[8] = { + 0x4C, 0xB4, 0x4B, 0xD3, 0xC8, 0xC1, 0xA5, 0x50 + }; + + uint8_t crypt[8]; + int rc; + + rc = des_crypt128(crypt, clear, key); + assert_int_equal(rc, 0); + assert_memory_equal(crypt, crypt_expected, 8); +} + +static void torture_gnutls_des_crypt112(void **state) +{ + static uint8_t key[14] = { + 0x98, 0xFD, 0xCB, 0x3A, 0xF7, 0xB5, 0x1C, 0xF8, + 0x88, 0x96, 0x8E, 0xB5, 0x3A, 0x24 + }; + static const uint8_t clear[8] = { + 0x2F, 0x49, 0x5B, 0x20, 0xD7, 0x84, 0xC2, 0x34 + }; + static const uint8_t crypt_expected[8] = { + 0x87, 0x35, 0xFA, 0xA4, 0x5D, 0x7A, 0xA5, 0x05 + }; + + uint8_t crypt[8]; + uint8_t decrypt[8]; + int rc; + + rc = des_crypt112(crypt, clear, key, SAMBA_GNUTLS_ENCRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(crypt, crypt_expected, 8); + + rc = des_crypt112(decrypt, crypt, key, SAMBA_GNUTLS_DECRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(decrypt, clear, 8); +} + +static void torture_gnutls_des_crypt112_16(void **state) +{ + static uint8_t key[14] = { + 0x1E, 0x38, 0x27, 0x5B, 0x3B, 0xB8, 0x67, 0xEB, + 0x88, 0x96, 0x8E, 0xB5, 0x3A, 0x24 + }; + static const uint8_t clear[16] = { + 0x02, 0xFA, 0x3B, 0xEE, 0xE8, 0xBA, 0x06, 0x01, + 0xFB, 0x67, 0x99, 0xA4, 0x83, 0xF3, 0xD4, 0xED + }; + static const uint8_t crypt_expected[16] = { + 0x3C, 0x10, 0x37, 0x67, 0x96, 0x95, 0xF7, 0x96, + 0xAA, 0x03, 0xB9, 0xEA, 0xD6, 0xB3, 0xC3, 0x2D + }; + + uint8_t crypt[16]; + uint8_t decrypt[16]; + int rc; + + rc = des_crypt112_16(crypt, clear, key, SAMBA_GNUTLS_ENCRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(crypt, crypt_expected, 16); + + rc = des_crypt112_16(decrypt, crypt, key, SAMBA_GNUTLS_DECRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(decrypt, clear, 16); +} + +static void torture_gnutls_sam_rid_crypt(void **state) +{ + static const uint8_t clear[16] = { + 0x02, 0xFA, 0x3B, 0xEE, 0xE8, 0xBA, 0x06, 0x01, + 0x3F, 0x49, 0x5B, 0x20, 0xA7, 0x84, 0xC2, 0x34 + }; + static const uint8_t crypt_expected[16] = { + 0x1E, 0x38, 0x27, 0x5B, 0x3B, 0xB8, 0x67, 0xEB, + 0xFB, 0x67, 0x99, 0xA4, 0x83, 0xF3, 0xD4, 0xED + }; + + uint8_t crypt[16]; + uint8_t decrypt[16]; + int rid = 500; + int rc; + + rc = sam_rid_crypt(rid, clear, crypt, SAMBA_GNUTLS_ENCRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(crypt, crypt_expected, 16); + + rc = sam_rid_crypt(rid, crypt, decrypt, SAMBA_GNUTLS_DECRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(decrypt, clear, 16); +} + +static void torture_gnutls_SMBsesskeygen_lm_sess_key(void **state) +{ + static const uint8_t lm_hash[16] = { + 0xFB, 0x67, 0x99, 0xA4, 0x83, 0xF3, 0xD4, 0xED, + 0x9C, 0x14, 0xDD, 0xE1, 0x39, 0x23, 0xE0, 0x55 + }; + static const uint8_t lm_resp[24] = { + 0x02, 0xFA, 0x3B, 0xEE, 0xE8, 0xBA, 0x06, 0x01, + 0x02, 0xFA, 0x3B, 0xEE, 0xE8, 0xBA, 0x06, 0x01, + 0x1E, 0x38, 0x27, 0x5B, 0x3B, 0xB8, 0x67, 0xEB + }; + static const uint8_t crypt_expected[16] = { + 0x52, 0x8D, 0xB2, 0xD3, 0x89, 0x83, 0xFB, 0x9C, + 0x96, 0x45, 0x15, 0x4B, 0xC3, 0xF5, 0xD5, 0x7F + }; + + uint8_t crypt_sess_key[16]; + NTSTATUS status; + + status = SMBsesskeygen_lm_sess_key(lm_hash, lm_resp, crypt_sess_key); + assert_true(NT_STATUS_IS_OK(status)); + assert_memory_equal(crypt_sess_key, crypt_expected, 16); +} + +static void torture_gnutls_sess_crypt_blob(void **state) +{ + static uint8_t _key[16] = { + 0x1E, 0x38, 0x27, 0x5B, 0x3B, 0xB8, 0x67, 0xEB, + 0xFA, 0xEE, 0xE8, 0xBA, 0x06, 0x01, 0x2D, 0x95 + }; + DATA_BLOB key = data_blob_const(_key, 16); + static const uint8_t _clear[24] = { + 0x98, 0xFD, 0xCB, 0x3A, 0xF7, 0xB5, 0x1C, 0xF8, + 0x02, 0xFA, 0x3B, 0xEE, 0xE8, 0xBA, 0x06, 0x01, + 0x3F, 0x49, 0x5B, 0x20, 0xA7, 0x84, 0xC2, 0x34 + }; + DATA_BLOB clear = data_blob_const(_clear, 24); + static const uint8_t crypt_expected[24] = { + 0x2B, 0xDD, 0x3B, 0xFA, 0x48, 0xC9, 0x63, 0x56, + 0xAE, 0x8B, 0x3E, 0xCF, 0xEF, 0xDF, 0x7A, 0x42, + 0xB3, 0x00, 0x71, 0x7F, 0x5D, 0x1D, 0xE4, 0x70 + }; + DATA_BLOB crypt = data_blob(NULL, 24); + DATA_BLOB decrypt = data_blob(NULL, 24); + int rc; + + rc = sess_crypt_blob(&crypt, &clear, &key, SAMBA_GNUTLS_ENCRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(crypt.data, crypt_expected, 24); + + rc = sess_crypt_blob(&decrypt, &crypt, &key, SAMBA_GNUTLS_DECRYPT); + assert_int_equal(rc, 0); + assert_memory_equal(decrypt.data, clear.data, 24); +} + +int main(int argc, char *argv[]) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_gnutls_aes_128_cfb), + cmocka_unit_test(torture_gnutls_des_crypt56), + cmocka_unit_test(torture_gnutls_E_P16), + cmocka_unit_test(torture_gnutls_E_P24), + cmocka_unit_test(torture_gnutls_SMBOWFencrypt), + cmocka_unit_test(torture_gnutls_E_old_pw_hash), + cmocka_unit_test(torture_gnutls_des_crypt128), + cmocka_unit_test(torture_gnutls_des_crypt112), + cmocka_unit_test(torture_gnutls_des_crypt112_16), + cmocka_unit_test(torture_gnutls_sam_rid_crypt), + cmocka_unit_test(torture_gnutls_SMBsesskeygen_lm_sess_key), + cmocka_unit_test(torture_gnutls_sess_crypt_blob), + }; + + if (argc == 2) { + cmocka_set_test_filter(argv[1]); + } + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/libcli/auth/tests/test_rc4_passwd_buffer.c b/libcli/auth/tests/test_rc4_passwd_buffer.c new file mode 100644 index 0000000..6d97ac6 --- /dev/null +++ b/libcli/auth/tests/test_rc4_passwd_buffer.c @@ -0,0 +1,336 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) 2018-2019 Andreas Schneider <asn@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "includes.h" +#include "libcli/auth/libcli_auth.h" +#include "rpc_client/init_samr.h" + +#define PASSWORD "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" + +static const uint8_t encrypted_test_blob[] = { + 0x37, 0x8e, 0x1d, 0xd5, 0xd3, 0x9f, 0xca, 0x8e, + 0x2f, 0x2d, 0xee, 0xc3, 0xb5, 0x50, 0xcd, 0x4e, + 0xc9, 0x08, 0x04, 0x68, 0x32, 0xc3, 0xac, 0x8e, + 0x53, 0x69, 0xd6, 0xb7, 0x56, 0xcc, 0xc0, 0xbe, + 0x4e, 0x96, 0xa7, 0x74, 0xe9, 0xaa, 0x10, 0x3d, + 0xd5, 0x8c, 0xaa, 0x12, 0x56, 0xb6, 0xf1, 0x85, + 0x21, 0xfa, 0xe9, 0xa1, 0x76, 0xe6, 0xa5, 0x33, + 0x33, 0x2f, 0x47, 0x29, 0xd6, 0xbd, 0xde, 0x64, + 0x4d, 0x15, 0x3e, 0x6a, 0x11, 0x9b, 0x52, 0xbf, + 0x7e, 0x3a, 0xeb, 0x1c, 0x55, 0xd1, 0xb2, 0xa4, + 0x35, 0x03, 0x6c, 0x39, 0x61, 0x28, 0x98, 0xc3, + 0x2d, 0xd4, 0x70, 0x69, 0x8b, 0x83, 0xe9, 0x62, + 0xbe, 0xd8, 0x72, 0x4e, 0xdf, 0xd4, 0xe9, 0xe3, + 0x46, 0x2a, 0xf9, 0x3c, 0x0f, 0x41, 0x62, 0xe1, + 0x43, 0xf0, 0x91, 0xbe, 0x72, 0xa0, 0xc9, 0x08, + 0x73, 0x20, 0x1f, 0x0d, 0x68, 0x2e, 0x32, 0xa1, + 0xb8, 0x9b, 0x08, 0xa1, 0xb4, 0x81, 0x6b, 0xf1, + 0xde, 0x0c, 0x28, 0x34, 0xe2, 0x65, 0x62, 0x54, + 0xeb, 0xc0, 0x71, 0x14, 0xad, 0x36, 0x43, 0x0e, + 0x92, 0x4d, 0x11, 0xe8, 0xdd, 0x2d, 0x5f, 0x05, + 0xff, 0x07, 0xda, 0x81, 0x4e, 0x27, 0x42, 0xa8, + 0xa9, 0x64, 0x4c, 0x74, 0xc8, 0x05, 0xbb, 0x83, + 0x5a, 0xd9, 0x90, 0x3e, 0x0d, 0x9d, 0xe5, 0x2f, + 0x08, 0xf9, 0x1b, 0xbd, 0x26, 0xc3, 0x0d, 0xac, + 0x43, 0xd5, 0x17, 0xf2, 0x61, 0xf5, 0x74, 0x9b, + 0xf3, 0x5b, 0x5f, 0xe1, 0x8a, 0xa6, 0xfd, 0xdf, + 0xff, 0xb5, 0x8b, 0xf1, 0x26, 0xf7, 0xe0, 0xa7, + 0x4f, 0x5b, 0xb8, 0x6d, 0xeb, 0xf6, 0x52, 0x68, + 0x8d, 0xa3, 0xd4, 0x7f, 0x56, 0x43, 0xaa, 0xec, + 0x58, 0x47, 0x03, 0xee, 0x9b, 0x59, 0xd9, 0x78, + 0x9a, 0xfb, 0x9e, 0xe9, 0xa6, 0x61, 0x4e, 0x6d, + 0x92, 0x35, 0xd3, 0x37, 0x6e, 0xf2, 0x34, 0x39, + 0xd4, 0xd2, 0xeb, 0xcf, 0x1c, 0x10, 0xb3, 0x2b, + 0x3e, 0x07, 0x42, 0x3e, 0x20, 0x90, 0x07, 0x3e, + 0xc7, 0xed, 0xd4, 0xdf, 0x50, 0xe5, 0xff, 0xaf, + 0x05, 0xce, 0x29, 0xbe, 0x01, 0xf8, 0xb0, 0x30, + 0x96, 0xae, 0x1b, 0x62, 0x23, 0x93, 0x91, 0x1a, + 0x52, 0x98, 0xd9, 0x59, 0xb8, 0x11, 0xec, 0xb8, + 0xcf, 0x20, 0x32, 0x90, 0x9e, 0xf2, 0x06, 0x43, + 0xb8, 0x36, 0xe3, 0x33, 0x4e, 0x6f, 0x75, 0xeb, + 0xf7, 0x6c, 0xac, 0x06, 0x5f, 0x24, 0x8e, 0x4a, + 0x03, 0xdf, 0x50, 0x31, 0xaa, 0x91, 0xd5, 0x85, + 0x95, 0x78, 0x5b, 0xf4, 0x7f, 0x3e, 0xbc, 0x41, + 0xfa, 0x10, 0xd3, 0x0f, 0x86, 0x8b, 0x23, 0xed, + 0xfc, 0xcc, 0x3e, 0x7d, 0x8c, 0xb4, 0x7c, 0xec, + 0x04, 0x7d, 0x12, 0x53, 0xa1, 0x30, 0xc5, 0xac, + 0xf3, 0x1e, 0x54, 0x1f, 0x97, 0x05, 0x86, 0x74, + 0x51, 0x13, 0x45, 0x98, 0xb8, 0x50, 0x62, 0x18, + 0x0f, 0x6d, 0x66, 0xa4, 0x88, 0x31, 0x76, 0xa3, + 0xb0, 0x75, 0x55, 0x44, 0x18, 0x7c, 0x67, 0xc7, + 0x09, 0x9c, 0xab, 0x53, 0x49, 0xc0, 0xc9, 0x27, + 0x53, 0xa6, 0x99, 0x01, 0x10, 0x49, 0x67, 0x8e, + 0x5b, 0x12, 0x96, 0x40, 0x16, 0x39, 0x11, 0x53, + 0x44, 0x8f, 0xa9, 0xbe, 0x84, 0xbe, 0xe0, 0x45, + 0xe3, 0xfd, 0x48, 0x46, 0x43, 0x53, 0x13, 0x5f, + 0xfa, 0xcf, 0x09, 0x67, 0x90, 0xa3, 0x94, 0xaa, + 0x0d, 0x1f, 0xc2, 0xc3, 0xd4, 0x7e, 0xc8, 0x14, + 0xbe, 0x84, 0xa5, 0x55, 0xee, 0x49, 0x8e, 0x03, + 0x1d, 0xaf, 0xad, 0x65, 0x2f, 0xf0, 0xd5, 0x90, + 0x5e, 0x8d, 0x29, 0x40, 0xba, 0x57, 0x26, 0xa8, + 0x6c, 0x4b, 0x59, 0x40, 0x4e, 0xc2, 0xc4, 0x88, + 0x0a, 0x06, 0x2b, 0x6c, 0x2a, 0xc7, 0x3f, 0xfe, + 0x37, 0x2c, 0x41, 0x58, 0xfe, 0x7e, 0xaf, 0xd1, + 0xd9, 0xd2, 0x9c, 0xd7, 0x8a, 0x01, 0x0e, 0x8c, + 0x9e, 0x8b, 0x5d, 0x72, 0x54, 0x00, 0xbe, 0xb2, + 0x9a, 0xc7, 0xfd, 0x83, 0x1e, 0x9c, 0x79, 0xdd, + 0x15, 0x13, 0xdc, 0x15, +}; + + +static const uint8_t encrypted_wkssvc_test_blob[] = { + 0x13, 0x79, 0x1f, 0x1a, 0x02, 0x15, 0x72, 0x1c, + 0xa6, 0x26, 0x37, 0xeb, 0x1d, 0x41, 0x7f, 0x76, + 0x11, 0x3f, 0x49, 0x4c, 0xf9, 0x69, 0x17, 0xc8, + 0x90, 0x92, 0x53, 0xb9, 0x3f, 0xcd, 0x06, 0xfe, + 0x5c, 0x17, 0x82, 0xbd, 0x86, 0xab, 0x49, 0xee, + 0x61, 0x76, 0x55, 0xc0, 0x10, 0x51, 0xcd, 0xd9, + 0x6f, 0x12, 0x86, 0xc6, 0x19, 0x59, 0x9a, 0x2f, + 0x27, 0x1d, 0x99, 0x30, 0x60, 0x0d, 0x65, 0xc6, + 0x43, 0xd6, 0xda, 0x6b, 0x66, 0x95, 0xd4, 0xca, + 0xf5, 0x04, 0xf7, 0x01, 0x5a, 0x55, 0xb0, 0x5e, + 0x72, 0x8a, 0x75, 0xe5, 0x33, 0x4c, 0xd8, 0xc4, + 0x0e, 0xf4, 0x6d, 0x23, 0xdd, 0x05, 0x90, 0xff, + 0xe0, 0x91, 0x7b, 0x62, 0x86, 0xee, 0x78, 0x31, + 0x07, 0xad, 0x8b, 0xf9, 0xdf, 0x6f, 0x8b, 0xbd, + 0x15, 0xde, 0x1b, 0xae, 0x84, 0x68, 0xe5, 0x41, + 0x7a, 0xe3, 0x47, 0x99, 0xba, 0x61, 0xe5, 0x51, + 0x64, 0x9a, 0xa0, 0x41, 0x44, 0xa1, 0x3a, 0x52, + 0x59, 0x7d, 0x6c, 0xcf, 0xcc, 0xf0, 0x11, 0xbc, + 0xb7, 0x51, 0xa9, 0xd8, 0xfd, 0xbf, 0x58, 0x77, + 0x28, 0x86, 0xa1, 0x27, 0x94, 0xe5, 0xf6, 0x1a, + 0x6b, 0x76, 0xf7, 0x72, 0x6e, 0x17, 0x09, 0xd8, + 0x3c, 0x6f, 0x39, 0x91, 0xea, 0x48, 0x98, 0xdc, + 0x1d, 0x50, 0x2e, 0x02, 0x6e, 0x7f, 0x80, 0x5d, + 0x6e, 0x96, 0xe1, 0xcf, 0x8b, 0x6b, 0xb6, 0xed, + 0xb4, 0x6a, 0x08, 0xd2, 0x45, 0x09, 0x88, 0x86, + 0x32, 0x58, 0xd8, 0x5e, 0x33, 0x8c, 0x29, 0x1a, + 0x8f, 0xc5, 0x54, 0x9b, 0xa8, 0x32, 0xb2, 0xc1, + 0x72, 0x14, 0x6c, 0x5d, 0x9d, 0xd3, 0xf2, 0x6c, + 0x6e, 0xa4, 0x84, 0x52, 0x26, 0x73, 0x7a, 0x30, + 0x56, 0x75, 0xef, 0xd1, 0x9d, 0xcd, 0xb7, 0x87, + 0xa9, 0x5c, 0xaf, 0xe6, 0xda, 0x1d, 0x3c, 0x9c, + 0xa3, 0xb1, 0x03, 0xb0, 0x8e, 0xf6, 0xc8, 0x8f, + 0x57, 0x1c, 0xce, 0x05, 0x54, 0x99, 0xf1, 0xf9, + 0x35, 0xe6, 0xf7, 0x67, 0x94, 0xb2, 0x67, 0x5b, + 0xe7, 0xa0, 0xa2, 0x1e, 0xa2, 0x74, 0xd3, 0x99, + 0x9c, 0xd5, 0xd9, 0x90, 0x86, 0x24, 0x0e, 0x1a, + 0x0d, 0xc8, 0x9e, 0x68, 0x4c, 0x43, 0x2f, 0x42, + 0xb1, 0x7c, 0xce, 0x1e, 0xb6, 0xac, 0x56, 0xb0, + 0x8d, 0x93, 0xf1, 0x53, 0x7d, 0x0e, 0x00, 0x46, + 0xba, 0x2e, 0x14, 0x7a, 0x0b, 0xaa, 0xcb, 0x07, + 0x9b, 0x09, 0x05, 0xa0, 0xd3, 0xa1, 0x80, 0xc2, + 0xd3, 0x59, 0x92, 0x27, 0x66, 0x1f, 0xdd, 0x76, + 0x36, 0xb3, 0x3c, 0xeb, 0xd7, 0x61, 0x94, 0xb1, + 0xf8, 0x3a, 0xe0, 0xba, 0x91, 0x0f, 0xef, 0x72, + 0x2b, 0x26, 0xc6, 0xb8, 0x6d, 0x0b, 0xdb, 0x60, + 0xf8, 0xb4, 0x98, 0xd7, 0x8b, 0x8d, 0xfb, 0xa7, + 0x4e, 0x27, 0xeb, 0x00, 0xe8, 0xf7, 0x5a, 0xec, + 0xf5, 0x60, 0x28, 0x37, 0xb2, 0xc4, 0x13, 0x48, + 0x2a, 0xe1, 0x34, 0xb2, 0x53, 0xcb, 0x44, 0x34, + 0x08, 0x7e, 0x56, 0x5c, 0x2b, 0x9b, 0xe2, 0xca, + 0x90, 0xb0, 0x57, 0xee, 0x10, 0x88, 0x39, 0x84, + 0xc6, 0x66, 0x07, 0x50, 0x63, 0xcc, 0x2a, 0x7c, + 0x99, 0x8c, 0x05, 0xf9, 0xf0, 0xb8, 0x62, 0xf0, + 0x92, 0xf7, 0x2a, 0x4a, 0x17, 0x14, 0x78, 0xa1, + 0x71, 0xb6, 0x42, 0xf0, 0x87, 0xa8, 0xa4, 0x48, + 0xeb, 0xdb, 0xed, 0x8a, 0x15, 0x19, 0x1a, 0xd9, + 0xfe, 0x6f, 0x07, 0x93, 0x5d, 0x39, 0xe8, 0x0e, + 0x47, 0xe6, 0x7a, 0x7d, 0x52, 0x2e, 0x40, 0x6f, + 0x31, 0x1b, 0xf6, 0x0c, 0xc2, 0x83, 0xae, 0xc1, + 0xf0, 0xf5, 0x71, 0xcd, 0xe2, 0xf5, 0x19, 0xb6, + 0xd8, 0xb0, 0x4d, 0xa9, 0x51, 0x1c, 0xb4, 0xaf, + 0x69, 0x9a, 0x89, 0xb6, 0x5b, 0x4d, 0xfa, 0x1b, + 0xca, 0xc8, 0x61, 0x92, 0x3a, 0xd6, 0x76, 0xad, + 0x5d, 0xa6, 0x17, 0x60, 0x3e, 0xea, 0x94, 0xcf, + 0x6d, 0x1b, 0x98, 0x5c, 0x19, 0x9e, 0x4e, 0xd3, + 0x21, 0x55, 0xda, 0xe3, +}; + +static void torture_decode_rc4_passwd_buffer(void **state) +{ + char *password_decoded = NULL; + size_t password_decoded_len = 0; + DATA_BLOB session_key = data_blob_const("SystemLibraryDTC", 16); + struct samr_CryptPasswordEx out_pwd_buf = { + .data = {0}, + }; + NTSTATUS status; + bool ok; + + memcpy(out_pwd_buf.data, + encrypted_test_blob, + sizeof(out_pwd_buf.data)); + + status = decode_rc4_passwd_buffer(&session_key, &out_pwd_buf); + assert_true(NT_STATUS_IS_OK(status)); + + ok = decode_pw_buffer(NULL, + out_pwd_buf.data, + &password_decoded, + &password_decoded_len, + CH_UTF16); + assert_true(ok); + assert_int_equal(password_decoded_len, strlen(PASSWORD)); + assert_string_equal(password_decoded, PASSWORD); +} + +static void torture_rc4_passwd_buffer(void **state) +{ + char *password_decoded = NULL; + size_t password_decoded_len = 0; + DATA_BLOB session_key = data_blob_const("SystemLibraryDTC", 16); + struct samr_CryptPasswordEx out_pwd_buf = { + .data = {0}, + }; + NTSTATUS status; + bool ok; + + status = init_samr_CryptPasswordEx(PASSWORD, + &session_key, + &out_pwd_buf); + assert_true(NT_STATUS_IS_OK(status)); + + status = decode_rc4_passwd_buffer(&session_key, &out_pwd_buf); + assert_true(NT_STATUS_IS_OK(status)); + + ok = decode_pw_buffer(NULL, + out_pwd_buf.data, + &password_decoded, + &password_decoded_len, + CH_UTF16); + assert_true(ok); + assert_int_equal(password_decoded_len, strlen(PASSWORD)); + assert_string_equal(password_decoded, PASSWORD); + talloc_free(password_decoded); +} + +static void torture_endode_decode_rc4_passwd_buffer(void **state) +{ + char *password_decoded = NULL; + size_t password_decoded_len = 0; + DATA_BLOB session_key = data_blob_const("SystemLibraryDTC", 16); + struct samr_CryptPasswordEx out_pwd_buf = { + .data = {0}, + }; + NTSTATUS status; + bool ok; + + status = encode_rc4_passwd_buffer(PASSWORD, + &session_key, + &out_pwd_buf); + assert_true(NT_STATUS_IS_OK(status)); + + status = decode_rc4_passwd_buffer(&session_key, &out_pwd_buf); + assert_true(NT_STATUS_IS_OK(status)); + + ok = decode_pw_buffer(NULL, + out_pwd_buf.data, + &password_decoded, + &password_decoded_len, + CH_UTF16); + assert_true(ok); + assert_int_equal(password_decoded_len, strlen(PASSWORD)); + assert_string_equal(password_decoded, PASSWORD); + talloc_free(password_decoded); +} + +static void torture_decode_wkssvc_join_password_buffer(void **state) +{ + DATA_BLOB session_key = data_blob_const("SystemLibraryDTC", 16); + struct wkssvc_PasswordBuffer pwd_buf = { + .data = {0}, + }; + char *password_decoded = NULL; + TALLOC_CTX *mem_ctx = NULL; + WERROR werr; + + mem_ctx = talloc_new(NULL); + assert_non_null(mem_ctx); + + memcpy(pwd_buf.data, + encrypted_wkssvc_test_blob, + sizeof(pwd_buf.data)); + + werr = decode_wkssvc_join_password_buffer(mem_ctx, + &pwd_buf, + &session_key, + &password_decoded); + assert_true(W_ERROR_IS_OK(werr)); + assert_non_null(password_decoded); + assert_string_equal(password_decoded, PASSWORD); + + TALLOC_FREE(mem_ctx); +} + +static void torture_wkssvc_join_password_buffer(void **state) +{ + DATA_BLOB session_key = data_blob_const("SystemLibraryDTC", 16); + struct wkssvc_PasswordBuffer *pwd_buf = NULL; + char *password_decoded = NULL; + TALLOC_CTX *mem_ctx = NULL; + WERROR werr; + + mem_ctx = talloc_new(NULL); + assert_non_null(mem_ctx); + + werr = encode_wkssvc_join_password_buffer(mem_ctx, + PASSWORD, + &session_key, + &pwd_buf); + assert_true(W_ERROR_IS_OK(werr)); + assert_non_null(pwd_buf); + + werr = decode_wkssvc_join_password_buffer(mem_ctx, + pwd_buf, + &session_key, + &password_decoded); + assert_true(W_ERROR_IS_OK(werr)); + assert_non_null(password_decoded); + assert_string_equal(password_decoded, PASSWORD); + + TALLOC_FREE(mem_ctx); +} + +int main(int argc, char *argv[]) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_decode_rc4_passwd_buffer), + cmocka_unit_test(torture_rc4_passwd_buffer), + cmocka_unit_test(torture_endode_decode_rc4_passwd_buffer), + cmocka_unit_test(torture_decode_wkssvc_join_password_buffer), + cmocka_unit_test(torture_wkssvc_join_password_buffer), + }; + + if (argc == 2) { + cmocka_set_test_filter(argv[1]); + } + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/libcli/auth/tests/test_schannel.c b/libcli/auth/tests/test_schannel.c new file mode 100644 index 0000000..b1c88fd --- /dev/null +++ b/libcli/auth/tests/test_schannel.c @@ -0,0 +1,305 @@ +/* + * Unix SMB/CIFS implementation. + * + * Copyright (C) 2019 Guenther Deschner <gd@samba.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <setjmp.h> +#include <cmocka.h> + +#include "includes.h" +#include "auth/gensec/schannel.c" + +static void torture_schannel_seal_flags(void **state, uint32_t flags, + const DATA_BLOB session_key, + const DATA_BLOB seq_num_initial, + const DATA_BLOB confounder_initial, + const DATA_BLOB confounder_expected, + const DATA_BLOB clear_initial, + const DATA_BLOB crypt_expected) +{ + NTSTATUS status; + struct schannel_state *schannel_state; + struct netlogon_creds_CredentialState *creds; + uint8_t confounder[8]; + DATA_BLOB io; + + assert_int_equal(session_key.length, 16); + assert_int_equal(seq_num_initial.length, 8); + assert_int_equal(confounder_initial.length, 8); + assert_int_equal(confounder_expected.length, 8); + assert_int_equal(clear_initial.length, crypt_expected.length); + + DEBUG(0,("checking buffer size: %d\n", (int)clear_initial.length)); + + schannel_state = talloc_zero(NULL, struct schannel_state); + assert_non_null(schannel_state); + creds = talloc_zero(schannel_state, + struct netlogon_creds_CredentialState); + assert_non_null(creds); + schannel_state->creds = creds; + + io = data_blob_dup_talloc(schannel_state, clear_initial); + assert_non_null(io.data); + assert_int_equal(io.length, clear_initial.length); + + schannel_state->creds->negotiate_flags = flags; + memcpy(schannel_state->creds->session_key, session_key.data, 16); + + memcpy(confounder, confounder_initial.data, 8); + + DEBUG(0,("confounder before crypt:\n")); + dump_data(0, confounder, 8); + dump_data(0, seq_num_initial.data, 8); + dump_data(0, io.data, io.length); + + status = netsec_do_seal(schannel_state, + seq_num_initial.data, + confounder, + io.data, + io.length, + true); + + assert_true(NT_STATUS_IS_OK(status)); + dump_data(0, io.data, io.length); + DEBUG(0,("confounder after crypt:\n")); + dump_data(0, confounder, 8); + dump_data(0, seq_num_initial.data, 8); + assert_memory_equal(io.data, crypt_expected.data, crypt_expected.length); + assert_memory_equal(confounder, confounder_expected.data, confounder_expected.length); + + status = netsec_do_seal(schannel_state, + seq_num_initial.data, + confounder, + io.data, + io.length, + false); + + assert_true(NT_STATUS_IS_OK(status)); + dump_data(0, io.data, io.length); + DEBUG(0,("confounder after decrypt:\n")); + dump_data(0, confounder, 8); + dump_data(0, seq_num_initial.data, 8); + assert_memory_equal(io.data, clear_initial.data, clear_initial.length); + assert_memory_equal(confounder, confounder_initial.data, confounder_initial.length); + + talloc_free(schannel_state); +} + +static void torture_schannel_seal_rc4(void **state) +{ + const uint8_t _session_key[16] = { + 0x14, 0xD5, 0x7F, 0x8D, 0x8E, 0xCF, 0xFB, 0x56, + 0x71, 0x29, 0x9D, 0x9C, 0x2A, 0x75, 0x00, 0xA1 + }; + const DATA_BLOB session_key = data_blob_const(_session_key, 16); + const uint8_t _seq_num_initial[8] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 + }; + const DATA_BLOB seq_num_initial = + data_blob_const(_seq_num_initial, 8); + const uint8_t _confounder_initial[8] = { + 0x1A, 0x5A, 0xE8, 0xC7, 0xBE, 0x4F, 0x1F, 0x07 + }; + const DATA_BLOB confounder_initial = + data_blob_const(_confounder_initial, 8); + const uint8_t _confounder_expected[8] = { + 0x25, 0x4A, 0x9C, 0x15, 0x82, 0x3E, 0x4A, 0x42 + }; + const DATA_BLOB confounder_expected = + data_blob_const(_confounder_expected, 8); + const uint8_t _clear_initial[] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8A, 0xE3, 0x13, 0x71, 0x02, 0xF4, 0x36, 0x71, + 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x40, 0x28, 0x00, 0x78, 0x57, 0x34, 0x12, + 0x34, 0x12, 0xCD, 0xAB, 0xEF, 0x00, 0x01, 0x23, + 0x45, 0x67, 0x89, 0xAB, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x5D, 0x88, 0x8A, 0xEB, 0x1C, 0xC9, 0x11, + 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + const DATA_BLOB clear_initial = data_blob_const(_clear_initial, + sizeof(_clear_initial)); + const uint8_t crypt_buffer[] = { + 0x3E, 0x10, 0x74, 0xD2, 0x3C, 0x71, 0x57, 0x45, + 0xB8, 0xAA, 0xCF, 0xE3, 0x84, 0xBE, 0xC4, 0x00, + 0xF4, 0x4D, 0x88, 0x0A, 0x9B, 0xCC, 0x53, 0xFC, + 0x32, 0xAA, 0x8E, 0x4B, 0x0E, 0xDE, 0x5F, 0x7D, + 0x6D, 0x31, 0x4E, 0xAB, 0xE0, 0x7D, 0x37, 0x9D, + 0x3D, 0x16, 0xD8, 0xBA, 0x6A, 0xB0, 0xD0, 0x99, + 0x14, 0x05, 0x37, 0xCF, 0x63, 0xD3, 0xD7, 0x60, + 0x63, 0x3C, 0x03, 0x0A, 0x30, 0xA0, 0x3E, 0xC7, + 0xDA, 0x94, 0x3B, 0x40, 0x63, 0x74, 0xEF, 0xCF, + 0xE5, 0x48, 0x87, 0xE9, 0x6A, 0x5A, 0xC7, 0x61, + 0xF7, 0x09, 0xB7, 0x7C, 0xDE, 0xDB, 0xB0, 0x94, + 0x9B, 0x99, 0xC0, 0xA7, 0x7E, 0x78, 0x09, 0x35, + 0xB4, 0xF4, 0x11, 0xC3, 0xB3, 0x77, 0xB5, 0x77, + 0x25, 0xEE, 0xFD, 0x2F, 0x9A, 0x15, 0x95, 0x27, + 0x08, 0xDA, 0xD0, 0x28, 0xD6, 0x31, 0xB4, 0xB7, + 0x7A, 0x19, 0xBB, 0xF3, 0x78, 0xF8, 0xC2, 0x5B + }; + const DATA_BLOB crypt_expected = data_blob_const(crypt_buffer, + sizeof(crypt_buffer)); + int buffer_sizes[] = { + 0, 1, 3, 7, 8, 9, 15, 16, 17 + }; + int i; + + torture_schannel_seal_flags(state, 0, + session_key, + seq_num_initial, + confounder_initial, + confounder_expected, + clear_initial, + crypt_expected); + + /* repeat the test for varying buffer sizes */ + + for (i = 0; i < ARRAY_SIZE(buffer_sizes); i++) { + DATA_BLOB clear_initial_trunc = + data_blob_const(clear_initial.data, buffer_sizes[i]); + DATA_BLOB crypt_expected_trunc = + data_blob_const(crypt_expected.data, buffer_sizes[i]); + torture_schannel_seal_flags(state, 0, + session_key, + seq_num_initial, + confounder_initial, + confounder_expected, + clear_initial_trunc, + crypt_expected_trunc); + } +} + +static void torture_schannel_seal_aes(void **state) +{ + const uint8_t _session_key[16] = { + 0x8E, 0xE8, 0x27, 0x85, 0x83, 0x41, 0x3C, 0x8D, + 0xC9, 0x54, 0x70, 0x75, 0x8E, 0xC9, 0x69, 0x91 + }; + const DATA_BLOB session_key = data_blob_const(_session_key, 16); + const uint8_t _seq_num_initial[8] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00 + }; + const DATA_BLOB seq_num_initial = + data_blob_const(_seq_num_initial, 8); + const uint8_t _confounder_initial[8] = { + 0x6E, 0x09, 0x25, 0x94, 0x01, 0xA0, 0x09, 0x31 + }; + const DATA_BLOB confounder_initial = + data_blob_const(_confounder_initial, 8); + const uint8_t _confounder_expected[8] = { + 0xCA, 0xFB, 0xAC, 0xFB, 0xA8, 0x26, 0x75, 0x2A + }; + const DATA_BLOB confounder_expected = + data_blob_const(_confounder_expected, 8); + const uint8_t _clear_initial[] = { + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x8A, 0xE3, 0x13, 0x71, 0x02, 0xF4, 0x36, 0x71, + 0x01, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x02, 0x40, 0x28, 0x00, 0x78, 0x57, 0x34, 0x12, + 0x34, 0x12, 0xCD, 0xAB, 0xEF, 0x00, 0x01, 0x23, + 0x45, 0x67, 0x89, 0xAB, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x5D, 0x88, 0x8A, 0xEB, 0x1C, 0xC9, 0x11, + 0x9F, 0xE8, 0x08, 0x00, 0x2B, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + const DATA_BLOB clear_initial = data_blob_const(_clear_initial, + sizeof(_clear_initial)); + const uint8_t crypt_buffer[] = { + 0xE2, 0xE5, 0xE3, 0x26, 0x45, 0xFB, 0xFC, 0xF3, + 0x9C, 0x14, 0xDD, 0xE1, 0x39, 0x23, 0xE0, 0x55, + 0xED, 0x8F, 0xF4, 0x92, 0xA1, 0xBD, 0xDC, 0x40, + 0x58, 0x6F, 0xD2, 0x5B, 0xF9, 0xC9, 0xA3, 0x87, + 0x46, 0x4B, 0x7F, 0xB2, 0x03, 0xD2, 0x35, 0x22, + 0x3E, 0x70, 0x9F, 0x1E, 0x3F, 0x1F, 0xDB, 0x7D, + 0x79, 0x88, 0x5A, 0x3D, 0xD3, 0x40, 0x1E, 0x69, + 0xD7, 0xE2, 0x1D, 0x5A, 0xE9, 0x3B, 0xE1, 0xE2, + 0x98, 0xFD, 0xCB, 0x3A, 0xF7, 0xB5, 0x1C, 0xF8, + 0xCA, 0x02, 0x00, 0x99, 0x9F, 0x0C, 0x01, 0xE6, + 0xD2, 0x00, 0xAF, 0xE0, 0x51, 0x88, 0x62, 0x50, + 0xB7, 0xE8, 0x6D, 0x63, 0x4B, 0x97, 0x05, 0xC1, + 0xD4, 0x83, 0x96, 0x29, 0x80, 0xAE, 0xD8, 0xA2, + 0xED, 0xC9, 0x5D, 0x0D, 0x29, 0xFF, 0x2C, 0x23, + 0x02, 0xFA, 0x3B, 0xEE, 0xE8, 0xBA, 0x06, 0x01, + 0x95, 0xDF, 0x80, 0x76, 0x0B, 0x17, 0x0E, 0xD8 + }; + const DATA_BLOB crypt_expected = data_blob_const(crypt_buffer, + sizeof(crypt_buffer)); + int buffer_sizes[] = { + 0, 1, 3, 7, 8, 9, 15, 16, 17 + }; + int i; + + torture_schannel_seal_flags(state, NETLOGON_NEG_SUPPORTS_AES, + session_key, + seq_num_initial, + confounder_initial, + confounder_expected, + clear_initial, + crypt_expected); + + /* repeat the test for varying buffer sizes */ + + for (i = 0; i < ARRAY_SIZE(buffer_sizes); i++) { + DATA_BLOB clear_initial_trunc = + data_blob_const(clear_initial.data, buffer_sizes[i]); + DATA_BLOB crypt_expected_trunc = + data_blob_const(crypt_expected.data, buffer_sizes[i]); + torture_schannel_seal_flags(state, NETLOGON_NEG_SUPPORTS_AES, + session_key, + seq_num_initial, + confounder_initial, + confounder_expected, + clear_initial_trunc, + crypt_expected_trunc); + } +} + +int main(int argc, char *argv[]) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_schannel_seal_rc4), + cmocka_unit_test(torture_schannel_seal_aes), + }; + + if (argc == 2) { + cmocka_set_test_filter(argv[1]); + } + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/libcli/auth/wscript_build b/libcli/auth/wscript_build new file mode 100644 index 0000000..c73e0d1 --- /dev/null +++ b/libcli/auth/wscript_build @@ -0,0 +1,100 @@ +#!/usr/bin/env python + +bld.SAMBA_LIBRARY('cliauth', + source='', + deps='MSRPC_PARSE LIBCLI_AUTH COMMON_SCHANNEL PAM_ERRORS SPNEGO_PARSE krb5samba samba-errors NTLM_CHECK UTIL_LSARPC', + private_library=True, + grouping_library=True) + +bld.SAMBA_SUBSYSTEM('MSRPC_PARSE', + source='msrpc_parse.c', + deps='talloc' + ) + +bld.SAMBA_SUBSYSTEM('NTLM_CHECK', + source='ntlm_check.c', + deps = 'talloc LIBCLI_AUTH' + ) + +bld.SAMBA_SUBSYSTEM('LIBCLI_AUTH', + source='credentials.c session.c smbencrypt.c smbdes.c', + public_deps='MSRPC_PARSE gnutls GNUTLS_HELPERS util_str_escape', + public_headers='credentials.h:domain_credentials.h' + ) + + +bld.SAMBA_SUBSYSTEM('COMMON_SCHANNEL', + source='schannel_state_tdb.c', + deps='dbwrap util_tdb samba-hostconfig NDR_NETLOGON' + ) + +bld.SAMBA_SUBSYSTEM('NETLOGON_CREDS_CLI', + source='netlogon_creds_cli.c', + deps=''' + dbwrap + util_tdb + tevent-util + samba-hostconfig + gensec + RPC_NDR_NETLOGON + NDR_NETLOGON + ''' + ) + +bld.SAMBA_SUBSYSTEM('PAM_ERRORS', + source='pam_errors.c', + deps='talloc' + ) + +bld.SAMBA_SUBSYSTEM('SPNEGO_PARSE', + source='spnego_parse.c', + deps='asn1util') + +bld.SAMBA_BINARY( + 'test_ntlm_check', + source='tests/ntlm_check.c', + deps=''' + NTLM_CHECK + CREDENTIALS_NTLM + samba-credentials + cmocka + talloc + ''', + for_selftest=True + ) + +bld.SAMBA_BINARY('test_rc4_passwd_buffer', + source='tests/test_rc4_passwd_buffer.c', + deps=''' + INIT_SAMR + LIBCLI_AUTH + cmocka + ''', + for_selftest=True) + +bld.SAMBA_BINARY('test_schannel', + source='tests/test_schannel.c', + deps=''' + gensec + cmocka + ''', + for_selftest=True) + +bld.SAMBA_BINARY('test_gnutls', + source='tests/test_gnutls.c', + deps=''' + gnutls + LIBCLI_AUTH + cmocka + samba-util + ''', + for_selftest=True) + +bld.SAMBA_BINARY('test_encode_decode', + source='tests/test_encode_decode.c', + deps=''' + LIBCLI_AUTH + cmocka + samba-util + ''', + for_selftest=True) |