diff options
Diffstat (limited to 'auth/credentials')
-rw-r--r-- | auth/credentials/credentials.c | 1913 | ||||
-rw-r--r-- | auth/credentials/credentials.h | 353 | ||||
-rw-r--r-- | auth/credentials/credentials_cmdline.c | 73 | ||||
-rw-r--r-- | auth/credentials/credentials_internal.h | 136 | ||||
-rw-r--r-- | auth/credentials/credentials_krb5.c | 1530 | ||||
-rw-r--r-- | auth/credentials/credentials_krb5.h | 45 | ||||
-rw-r--r-- | auth/credentials/credentials_ntlm.c | 555 | ||||
-rw-r--r-- | auth/credentials/credentials_secrets.c | 485 | ||||
-rw-r--r-- | auth/credentials/pycredentials.c | 1626 | ||||
-rw-r--r-- | auth/credentials/pycredentials.h | 40 | ||||
-rw-r--r-- | auth/credentials/samba-credentials.pc.in | 12 | ||||
-rwxr-xr-x | auth/credentials/tests/bind.py | 261 | ||||
-rw-r--r-- | auth/credentials/tests/test_creds.c | 311 | ||||
-rw-r--r-- | auth/credentials/wscript_build | 43 |
14 files changed, 7383 insertions, 0 deletions
diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c new file mode 100644 index 0000000..67644e8 --- /dev/null +++ b/auth/credentials/credentials.c @@ -0,0 +1,1913 @@ +/* + Unix SMB/CIFS implementation. + + User credentials handling + + Copyright (C) Jelmer Vernooij 2005 + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 "includes.h" +#include "librpc/gen_ndr/samr.h" /* for struct samrPassword */ +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_internal.h" +#include "auth/gensec/gensec.h" +#include "libcli/auth/libcli_auth.h" +#include "tevent.h" +#include "param/param.h" +#include "system/filesys.h" +#include "system/passwd.h" + +/** + * Create a new credentials structure + * @param mem_ctx TALLOC_CTX parent for credentials structure + */ +_PUBLIC_ struct cli_credentials *cli_credentials_init(TALLOC_CTX *mem_ctx) +{ + struct cli_credentials *cred = talloc_zero(mem_ctx, struct cli_credentials); + if (cred == NULL) { + return cred; + } + + cred->winbind_separator = '\\'; + + cred->kerberos_state = CRED_USE_KERBEROS_DESIRED; + + cred->signing_state = SMB_SIGNING_DEFAULT; + + /* + * The default value of lpcfg_client_ipc_signing() is REQUIRED, so use + * the same value here. + */ + cred->ipc_signing_state = SMB_SIGNING_REQUIRED; + cred->encryption_state = SMB_ENCRYPTION_DEFAULT; + + return cred; +} + +_PUBLIC_ +struct cli_credentials *cli_credentials_init_server(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx) +{ + struct cli_credentials *server_creds = NULL; + NTSTATUS status; + bool ok; + + server_creds = cli_credentials_init(mem_ctx); + if (server_creds == NULL) { + return NULL; + } + + ok = cli_credentials_set_conf(server_creds, lp_ctx); + if (!ok) { + TALLOC_FREE(server_creds); + return NULL; + } + + status = cli_credentials_set_machine_account(server_creds, lp_ctx); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to obtain server credentials: %s\n", + nt_errstr(status))); + TALLOC_FREE(server_creds); + return NULL; + } + + return server_creds; +} + +_PUBLIC_ void cli_credentials_set_callback_data(struct cli_credentials *cred, + void *callback_data) +{ + cred->priv_data = callback_data; +} + +_PUBLIC_ void *_cli_credentials_callback_data(struct cli_credentials *cred) +{ + return cred->priv_data; +} + +/** + * Create a new anonymous credential + * @param mem_ctx TALLOC_CTX parent for credentials structure + */ +_PUBLIC_ struct cli_credentials *cli_credentials_init_anon(TALLOC_CTX *mem_ctx) +{ + struct cli_credentials *anon_credentials; + + anon_credentials = cli_credentials_init(mem_ctx); + cli_credentials_set_anonymous(anon_credentials); + + return anon_credentials; +} + +_PUBLIC_ bool cli_credentials_set_kerberos_state(struct cli_credentials *creds, + enum credentials_use_kerberos kerberos_state, + enum credentials_obtained obtained) +{ + if (obtained >= creds->kerberos_state_obtained) { + creds->kerberos_state = kerberos_state; + creds->kerberos_state_obtained = obtained; + + return true; + } + + return false; +} + +_PUBLIC_ void cli_credentials_set_forced_sasl_mech(struct cli_credentials *creds, + const char *sasl_mech) +{ + TALLOC_FREE(creds->forced_sasl_mech); + creds->forced_sasl_mech = talloc_strdup(creds, sasl_mech); +} + +_PUBLIC_ void cli_credentials_set_krb_forwardable(struct cli_credentials *creds, + enum credentials_krb_forwardable krb_forwardable) +{ + creds->krb_forwardable = krb_forwardable; +} + +_PUBLIC_ enum credentials_use_kerberos cli_credentials_get_kerberos_state(struct cli_credentials *creds) +{ + return creds->kerberos_state; +} + +_PUBLIC_ const char *cli_credentials_get_forced_sasl_mech(struct cli_credentials *creds) +{ + return creds->forced_sasl_mech; +} + +_PUBLIC_ enum credentials_krb_forwardable cli_credentials_get_krb_forwardable(struct cli_credentials *creds) +{ + return creds->krb_forwardable; +} + +_PUBLIC_ bool cli_credentials_set_gensec_features(struct cli_credentials *creds, + uint32_t gensec_features, + enum credentials_obtained obtained) +{ + if (obtained >= creds->gensec_features_obtained) { + creds->gensec_features_obtained = obtained; + creds->gensec_features = gensec_features; + + return true; + } + + return false; +} + +_PUBLIC_ uint32_t cli_credentials_get_gensec_features(struct cli_credentials *creds) +{ + return creds->gensec_features; +} + + +/** + * Obtain the username for this credentials context. + * @param cred credentials context + * @retval The username set on this context. + * @note Return value will never be NULL except by programmer error. + */ +_PUBLIC_ const char *cli_credentials_get_username(struct cli_credentials *cred) +{ + if (cred->machine_account_pending) { + cli_credentials_set_machine_account(cred, + cred->machine_account_pending_lp_ctx); + } + + if (cred->username_obtained == CRED_CALLBACK && + !cred->callback_running) { + cred->callback_running = true; + cred->username = cred->username_cb(cred); + cred->callback_running = false; + if (cred->username_obtained == CRED_CALLBACK) { + cred->username_obtained = CRED_CALLBACK_RESULT; + cli_credentials_invalidate_ccache(cred, cred->username_obtained); + } + } + + return cred->username; +} + +/** + * @brief Obtain the username for this credentials context. + * + * @param[in] cred The credential context. + * + * @param[in] obtained A pointer to store the obtained information. + * + * return The user name or NULL if an error occured. + */ +_PUBLIC_ const char * +cli_credentials_get_username_and_obtained(struct cli_credentials *cred, + enum credentials_obtained *obtained) +{ + if (obtained != NULL) { + *obtained = cred->username_obtained; + } + + return cli_credentials_get_username(cred); +} + +_PUBLIC_ bool cli_credentials_set_username(struct cli_credentials *cred, + const char *val, enum credentials_obtained obtained) +{ + if (obtained >= cred->username_obtained) { + cred->username = talloc_strdup(cred, val); + cred->username_obtained = obtained; + cli_credentials_invalidate_ccache(cred, cred->username_obtained); + return true; + } + + return false; +} + +_PUBLIC_ bool cli_credentials_set_username_callback(struct cli_credentials *cred, + const char *(*username_cb) (struct cli_credentials *)) +{ + if (cred->username_obtained < CRED_CALLBACK) { + cred->username_cb = username_cb; + cred->username_obtained = CRED_CALLBACK; + return true; + } + + return false; +} + +_PUBLIC_ bool cli_credentials_set_bind_dn(struct cli_credentials *cred, + const char *bind_dn) +{ + cred->bind_dn = talloc_strdup(cred, bind_dn); + return true; +} + +/** + * Obtain the BIND DN for this credentials context. + * @param cred credentials context + * @retval The username set on this context. + * @note Return value will be NULL if not specified explictly + */ +_PUBLIC_ const char *cli_credentials_get_bind_dn(struct cli_credentials *cred) +{ + return cred->bind_dn; +} + + +/** + * Obtain the client principal for this credentials context. + * @param cred credentials context + * @retval The username set on this context. + * @note Return value will never be NULL except by programmer error. + */ +_PUBLIC_ char *cli_credentials_get_principal_and_obtained(struct cli_credentials *cred, TALLOC_CTX *mem_ctx, enum credentials_obtained *obtained) +{ + if (cred->machine_account_pending) { + cli_credentials_set_machine_account(cred, + cred->machine_account_pending_lp_ctx); + } + + if (cred->principal_obtained == CRED_CALLBACK && + !cred->callback_running) { + cred->callback_running = true; + cred->principal = cred->principal_cb(cred); + cred->callback_running = false; + if (cred->principal_obtained == CRED_CALLBACK) { + cred->principal_obtained = CRED_CALLBACK_RESULT; + cli_credentials_invalidate_ccache(cred, cred->principal_obtained); + } + } + + if (cred->principal_obtained < cred->username_obtained + || cred->principal_obtained < MAX(cred->domain_obtained, cred->realm_obtained)) { + const char *effective_username = NULL; + const char *effective_realm = NULL; + enum credentials_obtained effective_obtained; + + effective_username = cli_credentials_get_username(cred); + if (effective_username == NULL || strlen(effective_username) == 0) { + *obtained = cred->username_obtained; + return NULL; + } + + if (cred->domain_obtained > cred->realm_obtained) { + effective_realm = cli_credentials_get_domain(cred); + effective_obtained = MIN(cred->domain_obtained, + cred->username_obtained); + } else { + effective_realm = cli_credentials_get_realm(cred); + effective_obtained = MIN(cred->realm_obtained, + cred->username_obtained); + } + + if (effective_realm == NULL || strlen(effective_realm) == 0) { + effective_realm = cli_credentials_get_domain(cred); + effective_obtained = MIN(cred->domain_obtained, + cred->username_obtained); + } + + if (effective_realm != NULL && strlen(effective_realm) != 0) { + *obtained = effective_obtained; + return talloc_asprintf(mem_ctx, "%s@%s", + effective_username, + effective_realm); + } + } + *obtained = cred->principal_obtained; + return talloc_strdup(mem_ctx, cred->principal); +} + +/** + * Obtain the client principal for this credentials context. + * @param cred credentials context + * @retval The username set on this context. + * @note Return value will never be NULL except by programmer error. + */ +_PUBLIC_ char *cli_credentials_get_principal(struct cli_credentials *cred, TALLOC_CTX *mem_ctx) +{ + enum credentials_obtained obtained; + return cli_credentials_get_principal_and_obtained(cred, mem_ctx, &obtained); +} + +_PUBLIC_ bool cli_credentials_set_principal(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained) +{ + if (obtained >= cred->principal_obtained) { + cred->principal = talloc_strdup(cred, val); + if (cred->principal == NULL) { + return false; + } + cred->principal_obtained = obtained; + + cli_credentials_invalidate_ccache(cred, cred->principal_obtained); + return true; + } + + return false; +} + +/* Set a callback to get the principal. This could be a popup dialog, + * a terminal prompt or similar. */ +_PUBLIC_ bool cli_credentials_set_principal_callback(struct cli_credentials *cred, + const char *(*principal_cb) (struct cli_credentials *)) +{ + if (cred->principal_obtained < CRED_CALLBACK) { + cred->principal_cb = principal_cb; + cred->principal_obtained = CRED_CALLBACK; + return true; + } + + return false; +} + +/* Some of our tools are 'anonymous by default'. This is a single + * function to determine if authentication has been explicitly + * requested */ + +_PUBLIC_ bool cli_credentials_authentication_requested(struct cli_credentials *cred) +{ + uint32_t gensec_features = 0; + + if (cred->bind_dn) { + return true; + } + + /* + * If we forced the mech we clearly want authentication. E.g. to use + * SASL/EXTERNAL which has no credentials. + */ + if (cred->forced_sasl_mech) { + return true; + } + + if (cli_credentials_is_anonymous(cred)){ + return false; + } + + if (cred->principal_obtained >= CRED_SPECIFIED) { + return true; + } + if (cred->username_obtained >= CRED_SPECIFIED) { + return true; + } + + if (cli_credentials_get_kerberos_state(cred) == CRED_USE_KERBEROS_REQUIRED) { + return true; + } + + gensec_features = cli_credentials_get_gensec_features(cred); + if (gensec_features & GENSEC_FEATURE_NTLM_CCACHE) { + return true; + } + + if (gensec_features & GENSEC_FEATURE_SIGN) { + return true; + } + + if (gensec_features & GENSEC_FEATURE_SEAL) { + return true; + } + + return false; +} + +/** + * Obtain the password for this credentials context. + * @param cred credentials context + * @retval If set, the cleartext password, otherwise NULL + */ +_PUBLIC_ const char *cli_credentials_get_password(struct cli_credentials *cred) +{ + if (cred->machine_account_pending) { + cli_credentials_set_machine_account(cred, + cred->machine_account_pending_lp_ctx); + } + + if (cred->password_obtained == CRED_CALLBACK && + !cred->callback_running && + !cred->password_will_be_nt_hash) { + cred->callback_running = true; + cred->password = cred->password_cb(cred); + cred->callback_running = false; + if (cred->password_obtained == CRED_CALLBACK) { + cred->password_obtained = CRED_CALLBACK_RESULT; + cli_credentials_invalidate_ccache(cred, cred->password_obtained); + } + } + + return cred->password; +} + +/** + * @brief Obtain the password for this credentials context. + * + * @param[in] cred The credential context. + * + * @param[in] obtained A pointer to store the obtained information. + * + * return The user name or NULL if an error occured. + */ +_PUBLIC_ const char * +cli_credentials_get_password_and_obtained(struct cli_credentials *cred, + enum credentials_obtained *obtained) +{ + if (obtained != NULL) { + *obtained = cred->password_obtained; + } + + return cli_credentials_get_password(cred); +} + +/* Set a password on the credentials context, including an indication + * of 'how' the password was obtained */ + +_PUBLIC_ bool cli_credentials_set_password(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained) +{ + if (obtained >= cred->password_obtained) { + + cred->lm_response = data_blob_null; + cred->nt_response = data_blob_null; + cred->nt_hash = NULL; + cred->password = NULL; + + cli_credentials_invalidate_ccache(cred, obtained); + + cred->password_tries = 0; + + if (val == NULL) { + cred->password_obtained = obtained; + return true; + } + + if (cred->password_will_be_nt_hash) { + struct samr_Password *nt_hash = NULL; + size_t val_len = strlen(val); + size_t converted; + + nt_hash = talloc(cred, struct samr_Password); + if (nt_hash == NULL) { + return false; + } + + converted = strhex_to_str((char *)nt_hash->hash, + sizeof(nt_hash->hash), + val, val_len); + if (converted != sizeof(nt_hash->hash)) { + TALLOC_FREE(nt_hash); + return false; + } + + cred->nt_hash = nt_hash; + cred->password_obtained = obtained; + return true; + } + + cred->password = talloc_strdup(cred, val); + if (cred->password == NULL) { + return false; + } + + /* Don't print the actual password in talloc memory dumps */ + talloc_set_name_const(cred->password, + "password set via cli_credentials_set_password"); + cred->password_obtained = obtained; + + return true; + } + + return false; +} + +_PUBLIC_ bool cli_credentials_set_password_callback(struct cli_credentials *cred, + const char *(*password_cb) (struct cli_credentials *)) +{ + if (cred->password_obtained < CRED_CALLBACK) { + cred->password_tries = 3; + cred->password_cb = password_cb; + cred->password_obtained = CRED_CALLBACK; + cli_credentials_invalidate_ccache(cred, cred->password_obtained); + return true; + } + + return false; +} + +/** + * Obtain the 'old' password for this credentials context (used for join accounts). + * @param cred credentials context + * @retval If set, the cleartext password, otherwise NULL + */ +_PUBLIC_ const char *cli_credentials_get_old_password(struct cli_credentials *cred) +{ + if (cred->machine_account_pending) { + cli_credentials_set_machine_account(cred, + cred->machine_account_pending_lp_ctx); + } + + return cred->old_password; +} + +_PUBLIC_ bool cli_credentials_set_old_password(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained) +{ + cred->old_password = talloc_strdup(cred, val); + if (cred->old_password) { + /* Don't print the actual password in talloc memory dumps */ + talloc_set_name_const(cred->old_password, "password set via cli_credentials_set_old_password"); + } + cred->old_nt_hash = NULL; + return true; +} + +/** + * Obtain the password, in the form MD4(unicode(password)) for this credentials context. + * + * Sometimes we only have this much of the password, while the rest of + * the time this call avoids calling E_md4hash themselves. + * + * @param cred credentials context + * @retval If set, the cleartext password, otherwise NULL + */ +_PUBLIC_ struct samr_Password *cli_credentials_get_nt_hash(struct cli_credentials *cred, + TALLOC_CTX *mem_ctx) +{ + enum credentials_obtained password_obtained; + enum credentials_obtained ccache_threshold; + enum credentials_obtained client_gss_creds_threshold; + bool password_is_nt_hash; + const char *password = NULL; + struct samr_Password *nt_hash = NULL; + + if (cred->nt_hash != NULL) { + /* + * If we already have a hash it's easy. + */ + goto return_hash; + } + + /* + * This is a bit tricky, with password_will_be_nt_hash + * we still need to get the value via the password_callback + * but if we did that we should not remember it's state + * in the long run so we need to undo it. + */ + + password_obtained = cred->password_obtained; + ccache_threshold = cred->ccache_threshold; + client_gss_creds_threshold = cred->client_gss_creds_threshold; + password_is_nt_hash = cred->password_will_be_nt_hash; + + cred->password_will_be_nt_hash = false; + password = cli_credentials_get_password(cred); + + cred->password_will_be_nt_hash = password_is_nt_hash; + if (password_is_nt_hash && password_obtained == CRED_CALLBACK) { + /* + * We got the nt_hash as string via the callback, + * so we need to undo the state change. + * + * And also don't remember it as plaintext password. + */ + cred->client_gss_creds_threshold = client_gss_creds_threshold; + cred->ccache_threshold = ccache_threshold; + cred->password_obtained = password_obtained; + cred->password = NULL; + } + + if (password == NULL) { + return NULL; + } + + nt_hash = talloc(cred, struct samr_Password); + if (nt_hash == NULL) { + return NULL; + } + + if (password_is_nt_hash) { + size_t password_len = strlen(password); + size_t converted; + + converted = strhex_to_str((char *)nt_hash->hash, + sizeof(nt_hash->hash), + password, password_len); + if (converted != sizeof(nt_hash->hash)) { + TALLOC_FREE(nt_hash); + return NULL; + } + } else { + E_md4hash(password, nt_hash->hash); + } + + cred->nt_hash = nt_hash; + nt_hash = NULL; + +return_hash: + nt_hash = talloc(mem_ctx, struct samr_Password); + if (nt_hash == NULL) { + return NULL; + } + + *nt_hash = *cred->nt_hash; + + return nt_hash; +} + +/** + * Obtain the old password, in the form MD4(unicode(password)) for this credentials context. + * + * Sometimes we only have this much of the password, while the rest of + * the time this call avoids calling E_md4hash themselves. + * + * @param cred credentials context + * @retval If set, the cleartext password, otherwise NULL + */ +_PUBLIC_ struct samr_Password *cli_credentials_get_old_nt_hash(struct cli_credentials *cred, + TALLOC_CTX *mem_ctx) +{ + const char *old_password = NULL; + + if (cred->old_nt_hash != NULL) { + struct samr_Password *nt_hash = talloc(mem_ctx, struct samr_Password); + if (!nt_hash) { + return NULL; + } + + *nt_hash = *cred->old_nt_hash; + + return nt_hash; + } + + old_password = cli_credentials_get_old_password(cred); + if (old_password) { + struct samr_Password *nt_hash = talloc(mem_ctx, struct samr_Password); + if (!nt_hash) { + return NULL; + } + + E_md4hash(old_password, nt_hash->hash); + + return nt_hash; + } + + return NULL; +} + +/** + * Obtain the 'short' or 'NetBIOS' domain for this credentials context. + * @param cred credentials context + * @retval The domain set on this context. + * @note Return value will never be NULL except by programmer error. + */ +_PUBLIC_ const char *cli_credentials_get_domain(struct cli_credentials *cred) +{ + if (cred->machine_account_pending) { + cli_credentials_set_machine_account(cred, + cred->machine_account_pending_lp_ctx); + } + + if (cred->domain_obtained == CRED_CALLBACK && + !cred->callback_running) { + cred->callback_running = true; + cred->domain = cred->domain_cb(cred); + cred->callback_running = false; + if (cred->domain_obtained == CRED_CALLBACK) { + cred->domain_obtained = CRED_CALLBACK_RESULT; + cli_credentials_invalidate_ccache(cred, cred->domain_obtained); + } + } + + return cred->domain; +} + + +_PUBLIC_ bool cli_credentials_set_domain(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained) +{ + if (obtained >= cred->domain_obtained) { + /* it is important that the domain be in upper case, + * particularly for the sensitive NTLMv2 + * calculations */ + cred->domain = strupper_talloc(cred, val); + cred->domain_obtained = obtained; + /* setting domain does not mean we have to invalidate ccache + * because domain in not used for Kerberos operations. + * If ccache invalidation is required, one will anyway specify + * a password to kinit, and that will force invalidation of the ccache + */ + return true; + } + + return false; +} + +bool cli_credentials_set_domain_callback(struct cli_credentials *cred, + const char *(*domain_cb) (struct cli_credentials *)) +{ + if (cred->domain_obtained < CRED_CALLBACK) { + cred->domain_cb = domain_cb; + cred->domain_obtained = CRED_CALLBACK; + return true; + } + + return false; +} + +/** + * Obtain the Kerberos realm for this credentials context. + * @param cred credentials context + * @retval The realm set on this context. + * @note Return value will never be NULL except by programmer error. + */ +_PUBLIC_ const char *cli_credentials_get_realm(struct cli_credentials *cred) +{ + if (cred->machine_account_pending) { + cli_credentials_set_machine_account(cred, + cred->machine_account_pending_lp_ctx); + } + + if (cred->realm_obtained == CRED_CALLBACK && + !cred->callback_running) { + cred->callback_running = true; + cred->realm = cred->realm_cb(cred); + cred->callback_running = false; + if (cred->realm_obtained == CRED_CALLBACK) { + cred->realm_obtained = CRED_CALLBACK_RESULT; + cli_credentials_invalidate_ccache(cred, cred->realm_obtained); + } + } + + return cred->realm; +} + +/** + * Set the realm for this credentials context, and force it to + * uppercase for the sanity of our local kerberos libraries + */ +_PUBLIC_ bool cli_credentials_set_realm(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained) +{ + if (obtained >= cred->realm_obtained) { + cred->realm = strupper_talloc(cred, val); + cred->realm_obtained = obtained; + cli_credentials_invalidate_ccache(cred, cred->realm_obtained); + return true; + } + + return false; +} + +bool cli_credentials_set_realm_callback(struct cli_credentials *cred, + const char *(*realm_cb) (struct cli_credentials *)) +{ + if (cred->realm_obtained < CRED_CALLBACK) { + cred->realm_cb = realm_cb; + cred->realm_obtained = CRED_CALLBACK; + return true; + } + + return false; +} + +/** + * Obtain the 'short' or 'NetBIOS' workstation name for this credentials context. + * + * @param cred credentials context + * @retval The workstation name set on this context. + * @note Return value will never be NULL except by programmer error. + */ +_PUBLIC_ const char *cli_credentials_get_workstation(struct cli_credentials *cred) +{ + if (cred->workstation_obtained == CRED_CALLBACK && + !cred->callback_running) { + cred->callback_running = true; + cred->workstation = cred->workstation_cb(cred); + cred->callback_running = false; + if (cred->workstation_obtained == CRED_CALLBACK) { + cred->workstation_obtained = CRED_CALLBACK_RESULT; + } + } + + return cred->workstation; +} + +_PUBLIC_ bool cli_credentials_set_workstation(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained) +{ + if (obtained >= cred->workstation_obtained) { + cred->workstation = talloc_strdup(cred, val); + cred->workstation_obtained = obtained; + return true; + } + + return false; +} + +bool cli_credentials_set_workstation_callback(struct cli_credentials *cred, + const char *(*workstation_cb) (struct cli_credentials *)) +{ + if (cred->workstation_obtained < CRED_CALLBACK) { + cred->workstation_cb = workstation_cb; + cred->workstation_obtained = CRED_CALLBACK; + return true; + } + + return false; +} + +/** + * Given a string, typically obtained from a -U argument, parse it into domain, username, realm and password fields + * + * The format accepted is [domain\\]user[%password] or user[@realm][%password] + * + * @param credentials Credentials structure on which to set the password + * @param data the string containing the username, password etc + * @param obtained This enum describes how 'specified' this password is + */ + +_PUBLIC_ void cli_credentials_parse_string(struct cli_credentials *credentials, const char *data, enum credentials_obtained obtained) +{ + char *uname, *p; + char *uname_free = NULL; + + if (strcmp("%",data) == 0) { + cli_credentials_set_anonymous(credentials); + return; + } + + uname = talloc_strdup(credentials, data); + uname_free = uname; + + if ((p = strchr_m(uname,'%'))) { + *p = 0; + cli_credentials_set_password(credentials, p+1, obtained); + } + + if ((p = strchr_m(uname,'@'))) { + /* + * We also need to set username and domain + * in order to undo the effect of + * cli_credentials_guess(). + */ + cli_credentials_set_username(credentials, uname, obtained); + cli_credentials_set_domain(credentials, "", obtained); + + cli_credentials_set_principal(credentials, uname, obtained); + *p = 0; + cli_credentials_set_realm(credentials, p+1, obtained); + TALLOC_FREE(uname_free); + return; + } else if ((p = strchr_m(uname,'\\')) + || (p = strchr_m(uname, '/')) + || (p = strchr_m(uname, credentials->winbind_separator))) + { + const char *domain = NULL; + + domain = uname; + *p = 0; + uname = p+1; + + if (obtained == credentials->realm_obtained && + !strequal_m(credentials->domain, domain)) + { + /* + * We need to undo a former set with the same level + * in order to get the expected result from + * cli_credentials_get_principal(). + * + * But we only need to do that if the domain + * actually changes. + */ + cli_credentials_set_realm(credentials, domain, obtained); + } + cli_credentials_set_domain(credentials, domain, obtained); + } + if (obtained == credentials->principal_obtained && + !strequal_m(credentials->username, uname)) + { + /* + * We need to undo a former set with the same level + * in order to get the expected result from + * cli_credentials_get_principal(). + * + * But we only need to do that if the username + * actually changes. + */ + credentials->principal_obtained = CRED_UNINITIALISED; + credentials->principal = NULL; + } + cli_credentials_set_username(credentials, uname, obtained); + + TALLOC_FREE(uname_free); +} + +/** + * Given a a credentials structure, print it as a string + * + * The format output is [domain\\]user[%password] or user[@realm][%password] + * + * @param credentials Credentials structure on which to set the password + * @param mem_ctx The memory context to place the result on + */ + +_PUBLIC_ char *cli_credentials_get_unparsed_name(struct cli_credentials *credentials, TALLOC_CTX *mem_ctx) +{ + const char *bind_dn = cli_credentials_get_bind_dn(credentials); + const char *domain = NULL; + const char *username = NULL; + char *name = NULL; + + if (bind_dn) { + name = talloc_strdup(mem_ctx, bind_dn); + } else { + cli_credentials_get_ntlm_username_domain(credentials, mem_ctx, &username, &domain); + if (domain && domain[0]) { + name = talloc_asprintf(mem_ctx, "%s\\%s", + domain, username); + } else { + name = talloc_asprintf(mem_ctx, "%s", + username); + } + } + return name; +} + + +/** + * Specifies default values for domain, workstation and realm + * from the smb.conf configuration file + * + * @param cred Credentials structure to fill in + * + * @return true on success, false on error. + */ +_PUBLIC_ bool cli_credentials_set_conf(struct cli_credentials *cred, + struct loadparm_context *lp_ctx) +{ + const char *sep = NULL; + const char *realm = lpcfg_realm(lp_ctx); + enum credentials_client_protection protection = + lpcfg_client_protection(lp_ctx); + const char *workgroup = lpcfg_workgroup(lp_ctx); + const char *netbios_name = lpcfg_netbios_name(lp_ctx); + bool ok; + + (void)cli_credentials_set_username(cred, "", CRED_UNINITIALISED); + + if (workgroup != NULL && strlen(workgroup) == 0) { + workgroup = NULL; + } + + if (workgroup != NULL) { + if (lpcfg_parm_is_cmdline(lp_ctx, "workgroup")) { + ok = cli_credentials_set_domain(cred, + workgroup, + CRED_SPECIFIED); + if (!ok) { + DBG_ERR("Failed to set domain!\n"); + return false; + } + } else { + (void)cli_credentials_set_domain(cred, + workgroup, + CRED_SMB_CONF); + } + } + + if (netbios_name != NULL && strlen(netbios_name) == 0) { + netbios_name = NULL; + } + + if (netbios_name != NULL) { + if (lpcfg_parm_is_cmdline(lp_ctx, "netbios name")) { + ok = cli_credentials_set_workstation(cred, + netbios_name, + CRED_SPECIFIED); + if (!ok) { + DBG_ERR("Failed to set workstation!\n"); + return false; + } + } else { + (void)cli_credentials_set_workstation(cred, + netbios_name, + CRED_SMB_CONF); + } + } + + if (realm != NULL && strlen(realm) == 0) { + realm = NULL; + } + + if (realm != NULL) { + if (lpcfg_parm_is_cmdline(lp_ctx, "realm")) { + ok = cli_credentials_set_realm(cred, + realm, + CRED_SPECIFIED); + if (!ok) { + DBG_ERR("Failed to set realm!\n"); + return false; + } + } else { + (void)cli_credentials_set_realm(cred, + realm, + CRED_SMB_CONF); + } + } + + sep = lpcfg_winbind_separator(lp_ctx); + if (sep != NULL && sep[0] != '\0') { + cred->winbind_separator = *lpcfg_winbind_separator(lp_ctx); + } + + if (cred->signing_state_obtained <= CRED_SMB_CONF) { + /* Will be set to default for invalid smb.conf values */ + cred->signing_state = lpcfg_client_signing(lp_ctx); + if (cred->signing_state == SMB_SIGNING_DEFAULT) { + switch (protection) { + case CRED_CLIENT_PROTECTION_DEFAULT: + break; + case CRED_CLIENT_PROTECTION_PLAIN: + cred->signing_state = SMB_SIGNING_OFF; + break; + case CRED_CLIENT_PROTECTION_SIGN: + case CRED_CLIENT_PROTECTION_ENCRYPT: + cred->signing_state = SMB_SIGNING_REQUIRED; + break; + } + } + + cred->signing_state_obtained = CRED_SMB_CONF; + } + + if (cred->ipc_signing_state_obtained <= CRED_SMB_CONF) { + /* Will be set to required for invalid smb.conf values */ + cred->ipc_signing_state = lpcfg_client_ipc_signing(lp_ctx); + cred->ipc_signing_state_obtained = CRED_SMB_CONF; + } + + if (cred->encryption_state_obtained <= CRED_SMB_CONF) { + /* Will be set to default for invalid smb.conf values */ + cred->encryption_state = lpcfg_client_smb_encrypt(lp_ctx); + if (cred->encryption_state == SMB_ENCRYPTION_DEFAULT) { + switch (protection) { + case CRED_CLIENT_PROTECTION_DEFAULT: + break; + case CRED_CLIENT_PROTECTION_PLAIN: + case CRED_CLIENT_PROTECTION_SIGN: + cred->encryption_state = SMB_ENCRYPTION_OFF; + break; + case CRED_CLIENT_PROTECTION_ENCRYPT: + cred->encryption_state = SMB_ENCRYPTION_REQUIRED; + break; + } + } + } + + if (cred->kerberos_state_obtained <= CRED_SMB_CONF) { + /* Will be set to default for invalid smb.conf values */ + cred->kerberos_state = lpcfg_client_use_kerberos(lp_ctx); + cred->kerberos_state_obtained = CRED_SMB_CONF; + } + + if (cred->gensec_features_obtained <= CRED_SMB_CONF) { + switch (protection) { + case CRED_CLIENT_PROTECTION_DEFAULT: + break; + case CRED_CLIENT_PROTECTION_PLAIN: + cred->gensec_features = 0; + break; + case CRED_CLIENT_PROTECTION_SIGN: + cred->gensec_features = GENSEC_FEATURE_SIGN; + break; + case CRED_CLIENT_PROTECTION_ENCRYPT: + cred->gensec_features = + GENSEC_FEATURE_SIGN|GENSEC_FEATURE_SEAL; + break; + } + cred->gensec_features_obtained = CRED_SMB_CONF; + } + + return true; +} + +/** + * Guess defaults for credentials from environment variables, + * and from the configuration file + * + * @param cred Credentials structure to fill in + */ +_PUBLIC_ bool cli_credentials_guess(struct cli_credentials *cred, + struct loadparm_context *lp_ctx) +{ + const char *error_string; + const char *env = NULL; + struct passwd *pwd = NULL; + bool ok; + + if (lp_ctx != NULL) { + ok = cli_credentials_set_conf(cred, lp_ctx); + if (!ok) { + return false; + } + } + + pwd = getpwuid(getuid()); + if (pwd != NULL) { + size_t len = strlen(pwd->pw_name); + + if (len > 0 && len <= 1024) { + (void)cli_credentials_parse_string(cred, + pwd->pw_name, + CRED_GUESS_ENV); + } + } + + env = getenv("LOGNAME"); + if (env != NULL) { + size_t len = strlen(env); + + if (len > 0 && len <= 1024) { + (void)cli_credentials_set_username(cred, + env, + CRED_GUESS_ENV); + } + } + + env = getenv("USER"); + if (env != NULL) { + size_t len = strlen(env); + + if (len > 0 && len <= 1024) { + char *p = NULL; + + (void)cli_credentials_parse_string(cred, + env, + CRED_GUESS_ENV); + if ((p = strchr_m(env, '%'))) { + memset(p, '\0', strlen(cred->password)); + } + } + } + + env = getenv("PASSWD"); + if (env != NULL) { + size_t len = strlen(env); + + if (len > 0 && len <= 1024) { + (void)cli_credentials_set_password(cred, + env, + CRED_GUESS_ENV); + } + } + + env = getenv("PASSWD_FD"); + if (env != NULL) { + size_t len = strlen(env); + + if (len > 0 && len <= 1024) { + int fd = atoi(env); + + (void)cli_credentials_parse_password_fd(cred, + fd, + CRED_GUESS_FILE); + } + } + + env = getenv("PASSWD_FILE"); + if (env != NULL) { + size_t len = strlen(env); + + if (len > 0 && len <= 4096) { + (void)cli_credentials_parse_password_file(cred, + env, + CRED_GUESS_FILE); + } + } + + if (lp_ctx != NULL && + cli_credentials_get_kerberos_state(cred) != CRED_USE_KERBEROS_DISABLED) { + (void)cli_credentials_set_ccache(cred, + lp_ctx, + NULL, + CRED_GUESS_FILE, + &error_string); + } + + return true; +} + +/** + * Attach NETLOGON credentials for use with SCHANNEL + */ + +_PUBLIC_ void cli_credentials_set_netlogon_creds( + struct cli_credentials *cred, + const struct netlogon_creds_CredentialState *netlogon_creds) +{ + TALLOC_FREE(cred->netlogon_creds); + if (netlogon_creds == NULL) { + return; + } + cred->netlogon_creds = netlogon_creds_copy(cred, netlogon_creds); +} + +/** + * Return attached NETLOGON credentials + */ + +_PUBLIC_ struct netlogon_creds_CredentialState *cli_credentials_get_netlogon_creds(struct cli_credentials *cred) +{ + return cred->netlogon_creds; +} + +/** + * Set NETLOGON secure channel type + */ + +_PUBLIC_ void cli_credentials_set_secure_channel_type(struct cli_credentials *cred, + enum netr_SchannelType secure_channel_type) +{ + cred->secure_channel_type = secure_channel_type; +} + +/** + * Return NETLOGON secure chanel type + */ + +_PUBLIC_ time_t cli_credentials_get_password_last_changed_time(struct cli_credentials *cred) +{ + return cred->password_last_changed_time; +} + +/** + * Set NETLOGON secure channel type + */ + +_PUBLIC_ void cli_credentials_set_password_last_changed_time(struct cli_credentials *cred, + time_t last_changed_time) +{ + cred->password_last_changed_time = last_changed_time; +} + +/** + * Return NETLOGON secure chanel type + */ + +_PUBLIC_ enum netr_SchannelType cli_credentials_get_secure_channel_type(struct cli_credentials *cred) +{ + return cred->secure_channel_type; +} + +/** + * Fill in a credentials structure as the anonymous user + */ +_PUBLIC_ void cli_credentials_set_anonymous(struct cli_credentials *cred) +{ + cli_credentials_set_username(cred, "", CRED_SPECIFIED); + cli_credentials_set_domain(cred, "", CRED_SPECIFIED); + cli_credentials_set_password(cred, NULL, CRED_SPECIFIED); + cli_credentials_set_principal(cred, NULL, CRED_SPECIFIED); + cli_credentials_set_realm(cred, NULL, CRED_SPECIFIED); + cli_credentials_set_workstation(cred, "", CRED_UNINITIALISED); + cli_credentials_set_kerberos_state(cred, + CRED_USE_KERBEROS_DISABLED, + CRED_SPECIFIED); +} + +/** + * Describe a credentials context as anonymous or authenticated + * @retval true if anonymous, false if a username is specified + */ + +_PUBLIC_ bool cli_credentials_is_anonymous(struct cli_credentials *cred) +{ + const char *username; + + /* if bind dn is set it's not anonymous */ + if (cred->bind_dn) { + return false; + } + + if (cred->machine_account_pending) { + cli_credentials_set_machine_account(cred, + cred->machine_account_pending_lp_ctx); + } + + /* if principal is set, it's not anonymous */ + if ((cred->principal != NULL) && cred->principal_obtained >= cred->username_obtained) { + return false; + } + + username = cli_credentials_get_username(cred); + + /* Yes, it is deliberate that we die if we have a NULL pointer + * here - anonymous is "", not NULL, which is 'never specified, + * never guessed', ie programmer bug */ + if (!username[0]) { + return true; + } + + return false; +} + +/** + * Mark the current password for a credentials struct as wrong. This will + * cause the password to be prompted again (if a callback is set). + * + * This will decrement the number of times the password can be tried. + * + * @retval whether the credentials struct is finished + */ +_PUBLIC_ bool cli_credentials_wrong_password(struct cli_credentials *cred) +{ + if (cred->password_obtained != CRED_CALLBACK_RESULT) { + return false; + } + + if (cred->password_tries == 0) { + return false; + } + + cred->password_tries--; + + if (cred->password_tries == 0) { + return false; + } + + cred->password_obtained = CRED_CALLBACK; + return true; +} + +_PUBLIC_ void cli_credentials_get_ntlm_username_domain(struct cli_credentials *cred, TALLOC_CTX *mem_ctx, + const char **username, + const char **domain) +{ + if (cred->principal_obtained >= cred->username_obtained) { + *domain = talloc_strdup(mem_ctx, ""); + *username = cli_credentials_get_principal(cred, mem_ctx); + } else { + *domain = cli_credentials_get_domain(cred); + *username = cli_credentials_get_username(cred); + } +} + +/** + * Read a named file, and parse it for username, domain, realm and password + * + * @param credentials Credentials structure on which to set the password + * @param file a named file to read the details from + * @param obtained This enum describes how 'specified' this password is + */ + +_PUBLIC_ bool cli_credentials_parse_file(struct cli_credentials *cred, const char *file, enum credentials_obtained obtained) +{ + uint16_t len = 0; + char *ptr, *val, *param; + char **lines; + int i, numlines; + const char *realm = NULL; + const char *domain = NULL; + const char *password = NULL; + const char *username = NULL; + + lines = file_lines_load(file, &numlines, 0, NULL); + + if (lines == NULL) + { + /* fail if we can't open the credentials file */ + d_printf("ERROR: Unable to open credentials file!\n"); + return false; + } + + for (i = 0; i < numlines; i++) { + len = strlen(lines[i]); + + if (len == 0) + continue; + + /* break up the line into parameter & value. + * will need to eat a little whitespace possibly */ + param = lines[i]; + if (!(ptr = strchr_m (lines[i], '='))) + continue; + + val = ptr+1; + *ptr = '\0'; + + /* eat leading white space */ + while ((*val!='\0') && ((*val==' ') || (*val=='\t'))) + val++; + + if (strwicmp("password", param) == 0) { + password = val; + } else if (strwicmp("username", param) == 0) { + username = val; + } else if (strwicmp("domain", param) == 0) { + domain = val; + } else if (strwicmp("realm", param) == 0) { + realm = val; + } + + /* + * We need to readd '=' in order to let + * the strlen() work in the last loop + * that clears the memory. + */ + *ptr = '='; + } + + if (realm != NULL && strlen(realm) != 0) { + /* + * only overwrite with a valid string + */ + cli_credentials_set_realm(cred, realm, obtained); + } + + if (domain != NULL && strlen(domain) != 0) { + /* + * only overwrite with a valid string + */ + cli_credentials_set_domain(cred, domain, obtained); + } + + if (password != NULL) { + /* + * Here we allow "". + */ + cli_credentials_set_password(cred, password, obtained); + } + + if (username != NULL) { + /* + * The last "username" line takes preference + * if the string also contains domain, realm or + * password. + */ + cli_credentials_parse_string(cred, username, obtained); + } + + for (i = 0; i < numlines; i++) { + len = strlen(lines[i]); + memset(lines[i], 0, len); + } + talloc_free(lines); + + return true; +} + +/** + * Read a named file, and parse it for a password + * + * @param credentials Credentials structure on which to set the password + * @param file a named file to read the password from + * @param obtained This enum describes how 'specified' this password is + */ + +_PUBLIC_ bool cli_credentials_parse_password_file(struct cli_credentials *credentials, const char *file, enum credentials_obtained obtained) +{ + int fd = open(file, O_RDONLY, 0); + bool ret; + + if (fd < 0) { + fprintf(stderr, "Error opening password file %s: %s\n", + file, strerror(errno)); + return false; + } + + ret = cli_credentials_parse_password_fd(credentials, fd, obtained); + + close(fd); + + return ret; +} + + +/** + * Read a file descriptor, and parse it for a password (eg from a file or stdin) + * + * @param credentials Credentials structure on which to set the password + * @param fd open file descriptor to read the password from + * @param obtained This enum describes how 'specified' this password is + */ + +_PUBLIC_ bool cli_credentials_parse_password_fd(struct cli_credentials *credentials, + int fd, enum credentials_obtained obtained) +{ + char *p; + char pass[128]; + + for(p = pass, *p = '\0'; /* ensure that pass is null-terminated */ + p && p - pass < sizeof(pass);) { + switch (read(fd, p, 1)) { + case 1: + if (*p != '\n' && *p != '\0') { + *++p = '\0'; /* advance p, and null-terminate pass */ + break; + } + + FALL_THROUGH; + case 0: + if (p - pass) { + *p = '\0'; /* null-terminate it, just in case... */ + p = NULL; /* then force the loop condition to become false */ + break; + } + + fprintf(stderr, + "Error reading password from file descriptor " + "%d: empty password\n", + fd); + return false; + + default: + fprintf(stderr, "Error reading password from file descriptor %d: %s\n", + fd, strerror(errno)); + return false; + } + } + + cli_credentials_set_password(credentials, pass, obtained); + return true; +} + +/** + * @brief Set the SMB signing state to request for a SMB connection. + * + * @param[in] creds The credentials structure to update. + * + * @param[in] signing_state The signing state to set. + * + * @param obtained This way the described signing state was specified. + * + * @return true if we could set the signing state, false otherwise. + */ +_PUBLIC_ bool cli_credentials_set_smb_signing(struct cli_credentials *creds, + enum smb_signing_setting signing_state, + enum credentials_obtained obtained) +{ + if (obtained >= creds->signing_state_obtained) { + creds->signing_state_obtained = obtained; + creds->signing_state = signing_state; + return true; + } + + return false; +} + +/** + * @brief Obtain the SMB signing state from a credentials structure. + * + * @param[in] creds The credential structure to obtain the SMB signing state + * from. + * + * @return The SMB singing state. + */ +_PUBLIC_ enum smb_signing_setting +cli_credentials_get_smb_signing(struct cli_credentials *creds) +{ + return creds->signing_state; +} + +/** + * @brief Set the SMB IPC signing state to request for a SMB connection. + * + * @param[in] creds The credentials structure to update. + * + * @param[in] signing_state The signing state to set. + * + * @param obtained This way the described signing state was specified. + * + * @return true if we could set the signing state, false otherwise. + */ +_PUBLIC_ bool +cli_credentials_set_smb_ipc_signing(struct cli_credentials *creds, + enum smb_signing_setting ipc_signing_state, + enum credentials_obtained obtained) +{ + if (obtained >= creds->ipc_signing_state_obtained) { + creds->ipc_signing_state_obtained = obtained; + creds->ipc_signing_state = ipc_signing_state; + return true; + } + + return false; +} + +/** + * @brief Obtain the SMB IPC signing state from a credentials structure. + * + * @param[in] creds The credential structure to obtain the SMB IPC signing + * state from. + * + * @return The SMB singing state. + */ +_PUBLIC_ enum smb_signing_setting +cli_credentials_get_smb_ipc_signing(struct cli_credentials *creds) +{ + return creds->ipc_signing_state; +} + +/** + * @brief Set the SMB encryption state to request for a SMB connection. + * + * @param[in] creds The credentials structure to update. + * + * @param[in] encryption_state The encryption state to set. + * + * @param obtained This way the described encryption state was specified. + * + * @return true if we could set the encryption state, false otherwise. + */ +_PUBLIC_ bool cli_credentials_set_smb_encryption(struct cli_credentials *creds, + enum smb_encryption_setting encryption_state, + enum credentials_obtained obtained) +{ + if (obtained >= creds->encryption_state_obtained) { + creds->encryption_state_obtained = obtained; + creds->encryption_state = encryption_state; + return true; + } + + return false; +} + +static const char *obtained_to_str(enum credentials_obtained obtained) +{ + switch (obtained) { + case CRED_UNINITIALISED: + return "CRED_UNINITIALISED"; + case CRED_SMB_CONF: + return "CRED_SMB_CONF"; + case CRED_CALLBACK: + return "CRED_CALLBACK"; + case CRED_GUESS_ENV: + return "CRED_GUESS_ENV"; + case CRED_GUESS_FILE: + return "CRED_GUESS_FILE"; + case CRED_CALLBACK_RESULT: + return "CRED_CALLBACK_RESULT"; + case CRED_SPECIFIED: + return "CRED_SPECIFIED"; + } + + /* Never reached */ + return ""; +} + +static const char *krb5_state_to_str(enum credentials_use_kerberos krb5_state) +{ + switch (krb5_state) { + case CRED_USE_KERBEROS_DISABLED: + return "CRED_USE_KERBEROS_DISABLED"; + case CRED_USE_KERBEROS_DESIRED: + return "CRED_USE_KERBEROS_DESIRED"; + case CRED_USE_KERBEROS_REQUIRED: + return "CRED_USE_KERBEROS_REQUIRED"; + } + + /* Never reached */ + return ""; +} + +static const char *krb5_fwd_to_str(enum credentials_krb_forwardable krb5_fwd) +{ + switch (krb5_fwd) { + case CRED_AUTO_KRB_FORWARDABLE: + return "CRED_AUTO_KRB_FORWARDABLE"; + case CRED_NO_KRB_FORWARDABLE: + return "CRED_NO_KRB_FORWARDABLE"; + case CRED_FORCE_KRB_FORWARDABLE: + return "CRED_FORCE_KRB_FORWARDABLE"; + } + + /* Never reached */ + return ""; +} + +static const char *signing_state_to_str(enum smb_signing_setting signing_state) +{ + switch(signing_state) { + case SMB_SIGNING_IPC_DEFAULT: + return "SMB_SIGNING_IPC_DEFAULT"; + case SMB_SIGNING_DEFAULT: + return "SMB_SIGNING_DEFAULT"; + case SMB_SIGNING_OFF: + return "SMB_SIGNING_OFF"; + case SMB_SIGNING_IF_REQUIRED: + return "SMB_SIGNING_IF_REQUIRED"; + case SMB_SIGNING_DESIRED: + return "SMB_SIGNING_DESIRED"; + case SMB_SIGNING_REQUIRED: + return "SMB_SIGNING_REQUIRED"; + } + + /* Never reached */ + return ""; +} + +static const char *encryption_state_to_str(enum smb_encryption_setting encryption_state) +{ + switch(encryption_state) { + case SMB_ENCRYPTION_DEFAULT: + return "SMB_ENCRYPTION_DEFAULT"; + case SMB_ENCRYPTION_OFF: + return "SMB_ENCRYPTION_OFF"; + case SMB_ENCRYPTION_IF_REQUIRED: + return "SMB_ENCRYPTION_IF_REQUIRED"; + case SMB_ENCRYPTION_DESIRED: + return "SMB_ENCRYPTION_DESIRED"; + case SMB_ENCRYPTION_REQUIRED: + return "SMB_ENCRYPTION_REQUIRED"; + } + + /* Never reached */ + return ""; +} + +_PUBLIC_ void cli_credentials_dump(struct cli_credentials *creds) +{ + DBG_ERR("CLI_CREDENTIALS:\n"); + DBG_ERR("\n"); + DBG_ERR(" Username: %s - %s\n", + creds->username, + obtained_to_str(creds->username_obtained)); + DBG_ERR(" Workstation: %s - %s\n", + creds->workstation, + obtained_to_str(creds->workstation_obtained)); + DBG_ERR(" Domain: %s - %s\n", + creds->domain, + obtained_to_str(creds->domain_obtained)); + DBG_ERR(" Password: %s - %s\n", + creds->password != NULL ? "*SECRET*" : "NULL", + obtained_to_str(creds->password_obtained)); + DBG_ERR(" Old password: %s\n", + creds->old_password != NULL ? "*SECRET*" : "NULL"); + DBG_ERR(" Password tries: %u\n", + creds->password_tries); + DBG_ERR(" Realm: %s - %s\n", + creds->realm, + obtained_to_str(creds->realm_obtained)); + DBG_ERR(" Principal: %s - %s\n", + creds->principal, + obtained_to_str(creds->principal_obtained)); + DBG_ERR(" Salt principal: %s\n", + creds->salt_principal); + DBG_ERR(" Impersonate principal: %s\n", + creds->impersonate_principal); + DBG_ERR(" Self service: %s\n", + creds->self_service); + DBG_ERR(" Target service: %s\n", + creds->target_service); + DBG_ERR(" Kerberos state: %s - %s\n", + krb5_state_to_str(creds->kerberos_state), + obtained_to_str(creds->kerberos_state_obtained)); + DBG_ERR(" Kerberos forwardable ticket: %s\n", + krb5_fwd_to_str(creds->krb_forwardable)); + DBG_ERR(" Signing state: %s - %s\n", + signing_state_to_str(creds->signing_state), + obtained_to_str(creds->signing_state_obtained)); + DBG_ERR(" IPC signing state: %s - %s\n", + signing_state_to_str(creds->ipc_signing_state), + obtained_to_str(creds->ipc_signing_state_obtained)); + DBG_ERR(" Encryption state: %s - %s\n", + encryption_state_to_str(creds->encryption_state), + obtained_to_str(creds->encryption_state_obtained)); + DBG_ERR(" Gensec features: %#X\n", + creds->gensec_features); + DBG_ERR(" Forced sasl mech: %s\n", + creds->forced_sasl_mech); + DBG_ERR(" CCACHE: %p - %s\n", + creds->ccache, + obtained_to_str(creds->ccache_obtained)); + DBG_ERR(" CLIENT_GSS_CREDS: %p - %s\n", + creds->client_gss_creds, + obtained_to_str(creds->client_gss_creds_obtained)); + DBG_ERR(" SERVER_GSS_CREDS: %p - %s\n", + creds->server_gss_creds, + obtained_to_str(creds->server_gss_creds_obtained)); + DBG_ERR(" KEYTAB: %p - %s\n", + creds->keytab, + obtained_to_str(creds->keytab_obtained)); + DBG_ERR(" KVNO: %u\n", + creds->kvno); + DBG_ERR("\n"); +} + +/** + * @brief Obtain the SMB encryption state from a credentials structure. + * + * @param[in] creds The credential structure to obtain the SMB encryption state + * from. + * + * @return The SMB singing state. + */ +_PUBLIC_ enum smb_encryption_setting +cli_credentials_get_smb_encryption(struct cli_credentials *creds) +{ + return creds->encryption_state; +} + +/** + * Encrypt a data blob using the session key and the negotiated encryption + * algorithm + * + * @param state Credential state, contains the session key and algorithm + * @param data Data blob containing the data to be encrypted. + * + */ +_PUBLIC_ NTSTATUS netlogon_creds_session_encrypt( + struct netlogon_creds_CredentialState *state, + DATA_BLOB data) +{ + NTSTATUS status; + + if (data.data == NULL || data.length == 0) { + DBG_ERR("Nothing to encrypt " + "data.data == NULL or data.length == 0"); + return NT_STATUS_INVALID_PARAMETER; + } + /* + * Don't crypt an all-zero password it will give away the + * NETLOGON pipe session key . + */ + if (all_zero(data.data, data.length)) { + DBG_ERR("Supplied data all zeros, could leak session key"); + return NT_STATUS_INVALID_PARAMETER; + } + if (state->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + status = netlogon_creds_aes_encrypt(state, + data.data, + data.length); + } else if (state->negotiate_flags & NETLOGON_NEG_ARCFOUR) { + status = netlogon_creds_arcfour_crypt(state, + data.data, + data.length); + } else { + DBG_ERR("Unsupported encryption option negotiated"); + status = NT_STATUS_NOT_SUPPORTED; + } + if (!NT_STATUS_IS_OK(status)) { + return status; + } + return NT_STATUS_OK; +} + diff --git a/auth/credentials/credentials.h b/auth/credentials/credentials.h new file mode 100644 index 0000000..e9d8b8a --- /dev/null +++ b/auth/credentials/credentials.h @@ -0,0 +1,353 @@ +/* + samba -- Unix SMB/CIFS implementation. + + Client credentials structure + + Copyright (C) Jelmer Vernooij 2004-2006 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 __CREDENTIALS_H__ +#define __CREDENTIALS_H__ + +#include "../lib/util/time.h" +#include "../lib/util/data_blob.h" +#include "librpc/gen_ndr/misc.h" + +struct cli_credentials; +struct ccache_container; +struct tevent_context; +struct netlogon_creds_CredentialState; +struct ldb_context; +struct ldb_message; +struct loadparm_context; +struct ccache_container; +struct gssapi_creds_container; +struct smb_krb5_context; +struct keytab_container; +struct db_context; +enum smb_signing_setting; +enum smb_encryption_setting; + +/* In order of priority */ +enum credentials_obtained { + CRED_UNINITIALISED = 0, /* We don't even have a guess yet */ + CRED_SMB_CONF, /* Current value should be used, which comes from smb.conf */ + CRED_CALLBACK, /* Callback should be used to obtain value */ + CRED_GUESS_ENV, /* Current value should be used, which was guessed */ + CRED_GUESS_FILE, /* A guess from a file (or file pointed at in env variable) */ + CRED_CALLBACK_RESULT, /* Value was obtained from a callback */ + CRED_SPECIFIED /* Was explicitly specified on the command-line */ +}; + +enum credentials_use_kerberos { + /** Sometimes trying kerberos just does 'bad things', so don't */ + CRED_USE_KERBEROS_DISABLED = 0, + /** Default, we try kerberos if available */ + CRED_USE_KERBEROS_DESIRED, + /** Sometimes administrators are paranoid, so always do kerberos */ + CRED_USE_KERBEROS_REQUIRED, +}; + +enum credentials_client_protection { + CRED_CLIENT_PROTECTION_DEFAULT = -1, + CRED_CLIENT_PROTECTION_PLAIN = 0, + CRED_CLIENT_PROTECTION_SIGN, + CRED_CLIENT_PROTECTION_ENCRYPT, +}; + +enum credentials_krb_forwardable { + CRED_AUTO_KRB_FORWARDABLE = 0, /* Default, follow library defaults */ + CRED_NO_KRB_FORWARDABLE, /* not forwardable */ + CRED_FORCE_KRB_FORWARDABLE /* forwardable */ +}; + +#define CLI_CRED_NTLM2 0x01 +#define CLI_CRED_NTLMv2_AUTH 0x02 +#define CLI_CRED_LANMAN_AUTH 0x04 +#define CLI_CRED_NTLM_AUTH 0x08 +#define CLI_CRED_CLEAR_AUTH 0x10 /* TODO: Push cleartext auth with this flag */ + +const char *cli_credentials_get_workstation(struct cli_credentials *cred); +bool cli_credentials_set_workstation(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained); +bool cli_credentials_is_anonymous(struct cli_credentials *cred); +struct cli_credentials *cli_credentials_init(TALLOC_CTX *mem_ctx); +struct cli_credentials *cli_credentials_init_server(TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx); +void cli_credentials_set_anonymous(struct cli_credentials *cred); +bool cli_credentials_wrong_password(struct cli_credentials *cred); +const char *cli_credentials_get_password(struct cli_credentials *cred); +const char *cli_credentials_get_password_and_obtained(struct cli_credentials *cred, + enum credentials_obtained *obtained); +void cli_credentials_get_ntlm_username_domain(struct cli_credentials *cred, TALLOC_CTX *mem_ctx, + const char **username, + const char **domain); +NTSTATUS cli_credentials_get_ntlm_response(struct cli_credentials *cred, TALLOC_CTX *mem_ctx, + int *flags, + DATA_BLOB challenge, + const NTTIME *server_timestamp, + DATA_BLOB target_info, + DATA_BLOB *_lm_response, DATA_BLOB *_nt_response, + DATA_BLOB *_lm_session_key, DATA_BLOB *_session_key); +const char *cli_credentials_get_realm(struct cli_credentials *cred); +const char *cli_credentials_get_username(struct cli_credentials *cred); +const char *cli_credentials_get_username_and_obtained(struct cli_credentials *cred, + enum credentials_obtained *obtained); +int cli_credentials_get_krb5_context(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct smb_krb5_context **smb_krb5_context); +int cli_credentials_get_ccache(struct cli_credentials *cred, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct ccache_container **ccc, + const char **error_string); +int cli_credentials_get_named_ccache(struct cli_credentials *cred, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + char *ccache_name, + struct ccache_container **ccc, const char **error_string); +bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred, + const char *principal, + unsigned int *count); +int cli_credentials_get_keytab(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct keytab_container **_ktc); +const char *cli_credentials_get_domain(struct cli_credentials *cred); +struct netlogon_creds_CredentialState *cli_credentials_get_netlogon_creds(struct cli_credentials *cred); +void cli_credentials_set_machine_account_pending(struct cli_credentials *cred, + struct loadparm_context *lp_ctx); +bool cli_credentials_set_conf(struct cli_credentials *cred, + struct loadparm_context *lp_ctx); +char *cli_credentials_get_principal(struct cli_credentials *cred, TALLOC_CTX *mem_ctx); +int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct gssapi_creds_container **_gcc); +int cli_credentials_get_client_gss_creds(struct cli_credentials *cred, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct gssapi_creds_container **_gcc, + const char **error_string); +void cli_credentials_set_forced_sasl_mech(struct cli_credentials *creds, + const char *sasl_mech); +bool cli_credentials_set_kerberos_state(struct cli_credentials *creds, + enum credentials_use_kerberos kerberos_state, + enum credentials_obtained obtained); +void cli_credentials_set_krb_forwardable(struct cli_credentials *creds, + enum credentials_krb_forwardable krb_forwardable); +bool cli_credentials_set_domain(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained); +bool cli_credentials_set_domain_callback(struct cli_credentials *cred, + const char *(*domain_cb) (struct cli_credentials *)); +bool cli_credentials_set_username(struct cli_credentials *cred, + const char *val, enum credentials_obtained obtained); +bool cli_credentials_set_username_callback(struct cli_credentials *cred, + const char *(*username_cb) (struct cli_credentials *)); +bool cli_credentials_set_principal(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained); +bool cli_credentials_set_principal_callback(struct cli_credentials *cred, + const char *(*principal_cb) (struct cli_credentials *)); +bool cli_credentials_set_password(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained); +struct cli_credentials *cli_credentials_init_anon(TALLOC_CTX *mem_ctx); +void cli_credentials_parse_string(struct cli_credentials *credentials, const char *data, enum credentials_obtained obtained); +struct samr_Password *cli_credentials_get_nt_hash(struct cli_credentials *cred, + TALLOC_CTX *mem_ctx); +struct samr_Password *cli_credentials_get_old_nt_hash(struct cli_credentials *cred, + TALLOC_CTX *mem_ctx); +bool cli_credentials_set_realm(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained); +void cli_credentials_set_secure_channel_type(struct cli_credentials *cred, + enum netr_SchannelType secure_channel_type); +void cli_credentials_set_password_last_changed_time(struct cli_credentials *cred, + time_t last_change_time); +void cli_credentials_set_netlogon_creds( + struct cli_credentials *cred, + const struct netlogon_creds_CredentialState *netlogon_creds); +NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred, + struct smb_krb5_context *smb_krb5_context); +NTSTATUS cli_credentials_set_stored_principal(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + const char *serviceprincipal); +NTSTATUS cli_credentials_set_machine_account(struct cli_credentials *cred, + struct loadparm_context *lp_ctx); +/** + * Fill in credentials for the machine trust account, from the + * secrets.ldb or passed in handle to secrets.tdb (perhaps in CTDB). + * + * This version is used in parts of the code that can link in the + * CTDB dbwrap backend, by passing down the already open handle. + * + * @param cred Credentials structure to fill in + * @param db_ctx dbwrap context for secrets.tdb + * @retval NTSTATUS error detailing any failure + */ +NTSTATUS cli_credentials_set_machine_account_db_ctx(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct db_context *db_ctx); + +bool cli_credentials_authentication_requested(struct cli_credentials *cred); +bool cli_credentials_guess(struct cli_credentials *cred, + struct loadparm_context *lp_ctx); +bool cli_credentials_set_bind_dn(struct cli_credentials *cred, + const char *bind_dn); +const char *cli_credentials_get_bind_dn(struct cli_credentials *cred); +bool cli_credentials_parse_file(struct cli_credentials *cred, const char *file, enum credentials_obtained obtained); +char *cli_credentials_get_unparsed_name(struct cli_credentials *credentials, TALLOC_CTX *mem_ctx); +bool cli_credentials_set_password_callback(struct cli_credentials *cred, + const char *(*password_cb) (struct cli_credentials *)); +enum netr_SchannelType cli_credentials_get_secure_channel_type(struct cli_credentials *cred); +time_t cli_credentials_get_password_last_changed_time(struct cli_credentials *cred); +void cli_credentials_set_kvno(struct cli_credentials *cred, + int kvno); +bool cli_credentials_set_utf16_password(struct cli_credentials *cred, + const DATA_BLOB *password_utf16, + enum credentials_obtained obtained); +bool cli_credentials_set_old_utf16_password(struct cli_credentials *cred, + const DATA_BLOB *password_utf16); +void cli_credentials_set_password_will_be_nt_hash(struct cli_credentials *cred, + bool val); +bool cli_credentials_set_nt_hash(struct cli_credentials *cred, + const struct samr_Password *nt_hash, + enum credentials_obtained obtained); +bool cli_credentials_set_old_nt_hash(struct cli_credentials *cred, + const struct samr_Password *nt_hash); +bool cli_credentials_set_ntlm_response(struct cli_credentials *cred, + const DATA_BLOB *lm_response, + const DATA_BLOB *lm_session_key, + const DATA_BLOB *nt_response, + const DATA_BLOB *nt_session_key, + enum credentials_obtained obtained); +int cli_credentials_set_keytab_name(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + const char *keytab_name, + enum credentials_obtained obtained); +bool cli_credentials_set_gensec_features(struct cli_credentials *creds, + uint32_t gensec_features, + enum credentials_obtained obtained); +uint32_t cli_credentials_get_gensec_features(struct cli_credentials *creds); +int cli_credentials_set_ccache(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + const char *name, + enum credentials_obtained obtained, + const char **error_string); +bool cli_credentials_parse_password_file(struct cli_credentials *credentials, const char *file, enum credentials_obtained obtained); +bool cli_credentials_parse_password_fd(struct cli_credentials *credentials, + int fd, enum credentials_obtained obtained); +void cli_credentials_invalidate_ccache(struct cli_credentials *cred, + enum credentials_obtained obtained); +void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal); +void cli_credentials_set_impersonate_principal(struct cli_credentials *cred, + const char *principal, + const char *self_service); +void cli_credentials_set_target_service(struct cli_credentials *cred, const char *principal); +const char *cli_credentials_get_salt_principal(struct cli_credentials *cred); +const char *cli_credentials_get_impersonate_principal(struct cli_credentials *cred); +const char *cli_credentials_get_self_service(struct cli_credentials *cred); +const char *cli_credentials_get_target_service(struct cli_credentials *cred); +enum credentials_use_kerberos cli_credentials_get_kerberos_state(struct cli_credentials *creds); +const char *cli_credentials_get_forced_sasl_mech(struct cli_credentials *cred); +enum credentials_krb_forwardable cli_credentials_get_krb_forwardable(struct cli_credentials *creds); +NTSTATUS cli_credentials_set_secrets(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct ldb_context *ldb, + const char *base, + const char *filter, + char **error_string); + int cli_credentials_get_kvno(struct cli_credentials *cred); + +bool cli_credentials_set_username_callback(struct cli_credentials *cred, + const char *(*username_cb) (struct cli_credentials *)); + +/** + * Obtain the client principal for this credentials context. + * @param cred credentials context + * @retval The username set on this context. + * @note Return value will never be NULL except by programmer error. + */ +char *cli_credentials_get_principal_and_obtained(struct cli_credentials *cred, TALLOC_CTX *mem_ctx, enum credentials_obtained *obtained); +bool cli_credentials_set_principal(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained); +bool cli_credentials_set_principal_callback(struct cli_credentials *cred, + const char *(*principal_cb) (struct cli_credentials *)); + +/** + * Obtain the 'old' password for this credentials context (used for join accounts). + * @param cred credentials context + * @retval If set, the cleartext password, otherwise NULL + */ +const char *cli_credentials_get_old_password(struct cli_credentials *cred); +bool cli_credentials_set_old_password(struct cli_credentials *cred, + const char *val, + enum credentials_obtained obtained); +bool cli_credentials_set_domain_callback(struct cli_credentials *cred, + const char *(*domain_cb) (struct cli_credentials *)); +bool cli_credentials_set_realm_callback(struct cli_credentials *cred, + const char *(*realm_cb) (struct cli_credentials *)); +bool cli_credentials_set_workstation_callback(struct cli_credentials *cred, + const char *(*workstation_cb) (struct cli_credentials *)); + +void cli_credentials_set_callback_data(struct cli_credentials *cred, + void *callback_data); +void *_cli_credentials_callback_data(struct cli_credentials *cred); +#define cli_credentials_callback_data(_cred, _type) \ + talloc_get_type_abort(_cli_credentials_callback_data(_cred), _type) +#define cli_credentials_callback_data_void(_cred) \ + _cli_credentials_callback_data(_cred) + +bool cli_credentials_set_smb_signing(struct cli_credentials *cred, + enum smb_signing_setting signing_state, + enum credentials_obtained obtained); +enum smb_signing_setting +cli_credentials_get_smb_signing(struct cli_credentials *cred); + +bool cli_credentials_set_smb_ipc_signing(struct cli_credentials *cred, + enum smb_signing_setting ipc_signing_state, + enum credentials_obtained obtained); +enum smb_signing_setting +cli_credentials_get_smb_ipc_signing(struct cli_credentials *cred); + +bool cli_credentials_set_smb_encryption(struct cli_credentials *cred, + enum smb_encryption_setting encryption_state, + enum credentials_obtained obtained); +enum smb_encryption_setting +cli_credentials_get_smb_encryption(struct cli_credentials *cred); + +bool cli_credentials_set_cmdline_callbacks(struct cli_credentials *cred); + +void cli_credentials_dump(struct cli_credentials *creds); + +/** + * Return attached NETLOGON credentials + */ +struct netlogon_creds_CredentialState *cli_credentials_get_netlogon_creds(struct cli_credentials *cred); + +NTSTATUS netlogon_creds_session_encrypt( + struct netlogon_creds_CredentialState *state, + DATA_BLOB data); + +int cli_credentials_get_aes256_key(struct cli_credentials *cred, + TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *salt, + DATA_BLOB *aes_256); + +#endif /* __CREDENTIALS_H__ */ diff --git a/auth/credentials/credentials_cmdline.c b/auth/credentials/credentials_cmdline.c new file mode 100644 index 0000000..11b1ab9 --- /dev/null +++ b/auth/credentials/credentials_cmdline.c @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2005 Jelmer Vernooij <jelmer@samba.org> + * Copyright (c) 2016 Stefan Metzmacher <metze@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 "includes.h" +#include "system/filesys.h" +#include "auth/credentials/credentials.h" + +static const char *cmdline_get_userpassword(struct cli_credentials *creds) +{ + TALLOC_CTX *frame = talloc_stackframe(); + const char *name = NULL; + char *label = NULL; + char *ret = NULL; + char pwd[256] = {0}; + int rc; + + name = cli_credentials_get_unparsed_name(creds, frame); + if (name == NULL) { + goto fail; + } + label = talloc_asprintf(frame, "Password for [%s]:", name); + if (label == NULL) { + goto fail; + } + rc = samba_getpass(label, pwd, sizeof(pwd), false, false); + if (rc != 0) { + goto fail; + } + ret = talloc_strdup(creds, pwd); + if (ret == NULL) { + goto fail; + } + talloc_set_name_const(ret, __location__); +fail: + ZERO_STRUCT(pwd); + TALLOC_FREE(frame); + return ret; +} + +/** + * @brief Set the command line password callback. + * + * This will set the callback to get the password from the command prompt or + * read it from 'stdin'. + * + * @param[in] cred The credential context. + * + * @return On success true, false otherwise. + */ +bool cli_credentials_set_cmdline_callbacks(struct cli_credentials *cred) +{ + /* + * The there is no tty, then we will try to read the password from + * stdin. + */ + return cli_credentials_set_password_callback(cred, + cmdline_get_userpassword); +} diff --git a/auth/credentials/credentials_internal.h b/auth/credentials/credentials_internal.h new file mode 100644 index 0000000..3b1581a --- /dev/null +++ b/auth/credentials/credentials_internal.h @@ -0,0 +1,136 @@ +/* + samba -- Unix SMB/CIFS implementation. + + Client credentials structure + + Copyright (C) Jelmer Vernooij 2004-2006 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 __CREDENTIALS_INTERNAL_H__ +#define __CREDENTIALS_INTERNAL_H__ + +#include "../lib/util/data_blob.h" +#include "librpc/gen_ndr/misc.h" +#include "libcli/smb/smb_constants.h" + +struct cli_credentials { + enum credentials_obtained workstation_obtained; + enum credentials_obtained username_obtained; + enum credentials_obtained password_obtained; + enum credentials_obtained domain_obtained; + enum credentials_obtained realm_obtained; + enum credentials_obtained ccache_obtained; + enum credentials_obtained client_gss_creds_obtained; + enum credentials_obtained principal_obtained; + enum credentials_obtained keytab_obtained; + enum credentials_obtained server_gss_creds_obtained; + enum credentials_obtained signing_state_obtained; + enum credentials_obtained ipc_signing_state_obtained; + enum credentials_obtained encryption_state_obtained; + enum credentials_obtained kerberos_state_obtained; + enum credentials_obtained gensec_features_obtained; + + /* Threshold values (essentially a MAX() over a number of the + * above) for the ccache and GSS credentials, to ensure we + * regenerate/pick correctly */ + + enum credentials_obtained ccache_threshold; + enum credentials_obtained client_gss_creds_threshold; + + const char *workstation; + const char *username; + const char *password; + const char *old_password; + const char *domain; + const char *realm; + const char *principal; + char *salt_principal; + char *impersonate_principal; + char *self_service; + char *target_service; + + const char *bind_dn; + + /* Allows authentication from a keytab or similar */ + struct samr_Password *nt_hash; + struct samr_Password *old_nt_hash; + + /* Allows NTLM pass-though authentication */ + DATA_BLOB lm_response; + DATA_BLOB lm_session_key; + DATA_BLOB nt_response; + DATA_BLOB nt_session_key; + + struct ccache_container *ccache; + struct gssapi_creds_container *client_gss_creds; + struct keytab_container *keytab; + struct gssapi_creds_container *server_gss_creds; + + const char *(*workstation_cb) (struct cli_credentials *); + const char *(*password_cb) (struct cli_credentials *); + const char *(*username_cb) (struct cli_credentials *); + const char *(*domain_cb) (struct cli_credentials *); + const char *(*realm_cb) (struct cli_credentials *); + const char *(*principal_cb) (struct cli_credentials *); + + /* Private handle for the callback routines to use */ + void *priv_data; + + struct netlogon_creds_CredentialState *netlogon_creds; + enum netr_SchannelType secure_channel_type; + int kvno; + time_t password_last_changed_time; + + struct smb_krb5_context *smb_krb5_context; + + /* We are flagged to get machine account details from the + * secrets.ldb when we are asked for a username or password */ + bool machine_account_pending; + struct loadparm_context *machine_account_pending_lp_ctx; + + /* Is this a machine account? */ + bool machine_account; + + /* Should we be trying to use kerberos? */ + enum credentials_use_kerberos kerberos_state; + + /* Should we get a forwardable ticket? */ + enum credentials_krb_forwardable krb_forwardable; + + /* Forced SASL mechansim */ + char *forced_sasl_mech; + + /* gensec features which should be used for connections */ + uint32_t gensec_features; + + /* Number of retries left before bailing out */ + uint32_t password_tries; + + /* Whether any callback is currently running */ + bool callback_running; + + char winbind_separator; + + bool password_will_be_nt_hash; + + enum smb_signing_setting signing_state; + + enum smb_signing_setting ipc_signing_state; + + enum smb_encryption_setting encryption_state; +}; + +#endif /* __CREDENTIALS_INTERNAL_H__ */ diff --git a/auth/credentials/credentials_krb5.c b/auth/credentials/credentials_krb5.c new file mode 100644 index 0000000..bd47113 --- /dev/null +++ b/auth/credentials/credentials_krb5.c @@ -0,0 +1,1530 @@ +/* + Unix SMB/CIFS implementation. + + Handle user credentials (as regards krb5) + + Copyright (C) Jelmer Vernooij 2005 + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 "includes.h" +#include "system/kerberos.h" +#include "system/gssapi.h" +#include "auth/kerberos/kerberos.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_internal.h" +#include "auth/credentials/credentials_krb5.h" +#include "auth/kerberos/kerberos_credentials.h" +#include "auth/kerberos/kerberos_srv_keytab.h" +#include "auth/kerberos/kerberos_util.h" +#include "auth/kerberos/pac_utils.h" +#include "param/param.h" +#include "../libds/common/flags.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#undef strncasecmp + +static void cli_credentials_invalidate_client_gss_creds( + struct cli_credentials *cred, + enum credentials_obtained obtained); + +/* Free a memory ccache */ +static int free_mccache(struct ccache_container *ccc) +{ + if (ccc->ccache != NULL) { + krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, + ccc->ccache); + ccc->ccache = NULL; + } + + return 0; +} + +/* Free a disk-based ccache */ +static int free_dccache(struct ccache_container *ccc) +{ + if (ccc->ccache != NULL) { + krb5_cc_close(ccc->smb_krb5_context->krb5_context, + ccc->ccache); + ccc->ccache = NULL; + } + + return 0; +} + +static uint32_t smb_gss_krb5_copy_ccache(uint32_t *min_stat, + gss_cred_id_t cred, + struct ccache_container *ccc) +{ +#ifndef SAMBA4_USES_HEIMDAL /* MIT 1.10 */ + krb5_context context = ccc->smb_krb5_context->krb5_context; + krb5_ccache dummy_ccache = NULL; + krb5_creds creds = {0}; + krb5_cc_cursor cursor = NULL; + krb5_principal princ = NULL; + krb5_error_code code; + char *dummy_name; + uint32_t maj_stat = GSS_S_FAILURE; + + dummy_name = talloc_asprintf(ccc, + "MEMORY:gss_krb5_copy_ccache-%p", + &ccc->ccache); + if (dummy_name == NULL) { + *min_stat = ENOMEM; + return GSS_S_FAILURE; + } + + /* + * Create a dummy ccache, so we can iterate over the credentials + * and find the default principal for the ccache we want to + * copy. The new ccache needs to be initialized with this + * principal. + */ + code = krb5_cc_resolve(context, dummy_name, &dummy_ccache); + TALLOC_FREE(dummy_name); + if (code != 0) { + *min_stat = code; + return GSS_S_FAILURE; + } + + /* + * We do not need set a default principal on the temporary dummy + * ccache, as we do consume it at all in this function. + */ + maj_stat = gss_krb5_copy_ccache(min_stat, cred, dummy_ccache); + if (maj_stat != 0) { + krb5_cc_close(context, dummy_ccache); + return maj_stat; + } + + code = krb5_cc_start_seq_get(context, dummy_ccache, &cursor); + if (code != 0) { + krb5_cc_close(context, dummy_ccache); + *min_stat = EINVAL; + return GSS_S_FAILURE; + } + + code = krb5_cc_next_cred(context, + dummy_ccache, + &cursor, + &creds); + if (code != 0) { + krb5_cc_close(context, dummy_ccache); + *min_stat = EINVAL; + return GSS_S_FAILURE; + } + + do { + if (creds.ticket_flags & TKT_FLG_PRE_AUTH) { + krb5_data *tgs; + + tgs = krb5_princ_component(context, + creds.server, + 0); + if (tgs != NULL && tgs->length >= 1) { + int cmp; + + cmp = memcmp(tgs->data, + KRB5_TGS_NAME, + tgs->length); + if (cmp == 0 && creds.client != NULL) { + princ = creds.client; + code = KRB5_CC_END; + break; + } + } + } + + krb5_free_cred_contents(context, &creds); + + code = krb5_cc_next_cred(context, + dummy_ccache, + &cursor, + &creds); + } while (code == 0); + + if (code == KRB5_CC_END) { + krb5_cc_end_seq_get(context, dummy_ccache, &cursor); + code = 0; + } + krb5_cc_close(context, dummy_ccache); + + if (code != 0 || princ == NULL) { + krb5_free_cred_contents(context, &creds); + *min_stat = EINVAL; + return GSS_S_FAILURE; + } + + /* + * Set the default principal for the cache we copy + * into. This is needed to be able that other calls + * can read it with e.g. gss_acquire_cred() or + * krb5_cc_get_principal(). + */ + code = krb5_cc_initialize(context, ccc->ccache, princ); + if (code != 0) { + krb5_free_cred_contents(context, &creds); + *min_stat = EINVAL; + return GSS_S_FAILURE; + } + krb5_free_cred_contents(context, &creds); + +#endif /* SAMBA4_USES_HEIMDAL */ + + return gss_krb5_copy_ccache(min_stat, + cred, + ccc->ccache); +} + +_PUBLIC_ int cli_credentials_get_krb5_context(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct smb_krb5_context **smb_krb5_context) +{ + int ret; + if (cred->smb_krb5_context) { + *smb_krb5_context = cred->smb_krb5_context; + return 0; + } + + ret = smb_krb5_init_context(cred, lp_ctx, + &cred->smb_krb5_context); + if (ret) { + cred->smb_krb5_context = NULL; + return ret; + } + *smb_krb5_context = cred->smb_krb5_context; + return 0; +} + +/* For most predictable behaviour, this needs to be called directly after the cli_credentials_init(), + * otherwise we may still have references to the old smb_krb5_context in a credential cache etc + */ +_PUBLIC_ NTSTATUS cli_credentials_set_krb5_context(struct cli_credentials *cred, + struct smb_krb5_context *smb_krb5_context) +{ + if (smb_krb5_context == NULL) { + talloc_unlink(cred, cred->smb_krb5_context); + cred->smb_krb5_context = NULL; + return NT_STATUS_OK; + } + + if (!talloc_reference(cred, smb_krb5_context)) { + return NT_STATUS_NO_MEMORY; + } + cred->smb_krb5_context = smb_krb5_context; + return NT_STATUS_OK; +} + +static int cli_credentials_set_from_ccache(struct cli_credentials *cred, + struct ccache_container *ccache, + enum credentials_obtained obtained, + const char **error_string) +{ + bool ok; + char *realm; + krb5_principal princ; + krb5_error_code ret; + char *name; + + if (cred->ccache_obtained > obtained) { + return 0; + } + + ret = krb5_cc_get_principal(ccache->smb_krb5_context->krb5_context, + ccache->ccache, &princ); + + if (ret) { + (*error_string) = talloc_asprintf(cred, "failed to get principal from ccache: %s\n", + smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context, + ret, cred)); + return ret; + } + + ret = krb5_unparse_name(ccache->smb_krb5_context->krb5_context, princ, &name); + if (ret) { + (*error_string) = talloc_asprintf(cred, "failed to unparse principal from ccache: %s\n", + smb_get_krb5_error_message(ccache->smb_krb5_context->krb5_context, + ret, cred)); + return ret; + } + + ok = cli_credentials_set_principal(cred, name, obtained); + krb5_free_unparsed_name(ccache->smb_krb5_context->krb5_context, name); + if (!ok) { + krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ); + return ENOMEM; + } + + realm = smb_krb5_principal_get_realm( + cred, ccache->smb_krb5_context->krb5_context, princ); + krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ); + if (realm == NULL) { + return ENOMEM; + } + ok = cli_credentials_set_realm(cred, realm, obtained); + TALLOC_FREE(realm); + if (!ok) { + return ENOMEM; + } + + /* set the ccache_obtained here, as it just got set to UNINITIALISED by the calls above */ + cred->ccache_obtained = obtained; + + return 0; +} + +_PUBLIC_ int cli_credentials_set_ccache(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + const char *name, + enum credentials_obtained obtained, + const char **error_string) +{ + krb5_error_code ret; + krb5_principal princ; + struct ccache_container *ccc; + if (cred->ccache_obtained > obtained) { + return 0; + } + + ccc = talloc(cred, struct ccache_container); + if (!ccc) { + (*error_string) = error_message(ENOMEM); + return ENOMEM; + } + + ret = cli_credentials_get_krb5_context(cred, lp_ctx, + &ccc->smb_krb5_context); + if (ret) { + (*error_string) = error_message(ret); + talloc_free(ccc); + return ret; + } + if (!talloc_reference(ccc, ccc->smb_krb5_context)) { + talloc_free(ccc); + (*error_string) = error_message(ENOMEM); + return ENOMEM; + } + + if (name) { + ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, name, &ccc->ccache); + if (ret) { + (*error_string) = talloc_asprintf(cred, "failed to read krb5 ccache: %s: %s\n", + name, + smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, + ret, ccc)); + talloc_free(ccc); + return ret; + } + } else { + ret = krb5_cc_default(ccc->smb_krb5_context->krb5_context, &ccc->ccache); + if (ret) { + (*error_string) = talloc_asprintf(cred, "failed to read default krb5 ccache: %s\n", + smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, + ret, ccc)); + talloc_free(ccc); + return ret; + } + } + + talloc_set_destructor(ccc, free_dccache); + + ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, ccc->ccache, &princ); + + if (ret == 0) { + krb5_free_principal(ccc->smb_krb5_context->krb5_context, princ); + ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string); + + if (ret) { + (*error_string) = error_message(ret); + TALLOC_FREE(ccc); + return ret; + } + } + + cred->ccache = ccc; + cred->ccache_obtained = obtained; + + cli_credentials_invalidate_client_gss_creds( + cred, cred->ccache_obtained); + + return 0; +} + +#ifndef SAMBA4_USES_HEIMDAL +/* + * This function is a workaround for old MIT Kerberos versions which did not + * implement the krb5_cc_remove_cred function. It creates a temporary + * credentials cache to copy the credentials in the current cache + * except the one we want to remove and then overwrites the contents of the + * current cache with the temporary copy. + */ +static krb5_error_code krb5_cc_remove_cred_wrap(struct ccache_container *ccc, + krb5_creds *creds) +{ + krb5_ccache dummy_ccache = NULL; + krb5_creds cached_creds = {0}; + krb5_cc_cursor cursor = NULL; + krb5_error_code code; + char *dummy_name; + + dummy_name = talloc_asprintf(ccc, + "MEMORY:copy_ccache-%p", + &ccc->ccache); + if (dummy_name == NULL) { + return KRB5_CC_NOMEM; + } + + code = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, + dummy_name, + &dummy_ccache); + if (code != 0) { + DBG_ERR("krb5_cc_resolve failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + code, ccc)); + TALLOC_FREE(dummy_name); + return code; + } + + TALLOC_FREE(dummy_name); + + code = krb5_cc_start_seq_get(ccc->smb_krb5_context->krb5_context, + ccc->ccache, + &cursor); + if (code != 0) { + krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, + dummy_ccache); + + DBG_ERR("krb5_cc_start_seq_get failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + code, ccc)); + return code; + } + + while ((code = krb5_cc_next_cred(ccc->smb_krb5_context->krb5_context, + ccc->ccache, + &cursor, + &cached_creds)) == 0) { + /* If the principal matches skip it and do not copy to the + * temporary cache as this is the one we want to remove */ + if (krb5_principal_compare_flags( + ccc->smb_krb5_context->krb5_context, + creds->server, + cached_creds.server, + 0)) { + continue; + } + + code = krb5_cc_store_cred( + ccc->smb_krb5_context->krb5_context, + dummy_ccache, + &cached_creds); + if (code != 0) { + krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, + dummy_ccache); + DBG_ERR("krb5_cc_store_cred failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + code, ccc)); + return code; + } + } + + if (code == KRB5_CC_END) { + krb5_cc_end_seq_get(ccc->smb_krb5_context->krb5_context, + dummy_ccache, + &cursor); + code = 0; + } + + if (code != 0) { + krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, + dummy_ccache); + DBG_ERR("krb5_cc_next_cred failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + code, ccc)); + return code; + } + + code = krb5_cc_initialize(ccc->smb_krb5_context->krb5_context, + ccc->ccache, + creds->client); + if (code != 0) { + krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, + dummy_ccache); + DBG_ERR("krb5_cc_initialize failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + code, ccc)); + return code; + } + + code = krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context, + dummy_ccache, + ccc->ccache); + if (code != 0) { + krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, + dummy_ccache); + DBG_ERR("krb5_cc_copy_creds failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + code, ccc)); + return code; + } + + code = krb5_cc_destroy(ccc->smb_krb5_context->krb5_context, + dummy_ccache); + if (code != 0) { + DBG_ERR("krb5_cc_destroy failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + code, ccc)); + return code; + } + + return code; +} +#endif + +/* + * Indicate the we failed to log in to this service/host with these + * credentials. The caller passes an unsigned int which they + * initialise to the number of times they would like to retry. + * + * This method is used to support re-trying with freshly fetched + * credentials in case a server is rebuilt while clients have + * non-expired tickets. When the client code gets a logon failure they + * throw away the existing credentials for the server and retry. + */ +_PUBLIC_ bool cli_credentials_failed_kerberos_login(struct cli_credentials *cred, + const char *principal, + unsigned int *count) +{ + struct ccache_container *ccc; + krb5_creds creds, creds2; + int ret; + + if (principal == NULL) { + /* no way to delete if we don't know the principal */ + return false; + } + + ccc = cred->ccache; + if (ccc == NULL) { + /* not a kerberos connection */ + return false; + } + + if (*count > 0) { + /* We have already tried discarding the credentials */ + return false; + } + (*count)++; + + ZERO_STRUCT(creds); + ret = krb5_parse_name(ccc->smb_krb5_context->krb5_context, principal, &creds.server); + if (ret != 0) { + return false; + } + + /* MIT kerberos requires creds.client to match against cached + * credentials */ + ret = krb5_cc_get_principal(ccc->smb_krb5_context->krb5_context, + ccc->ccache, + &creds.client); + if (ret != 0) { + krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, + &creds); + DBG_ERR("krb5_cc_get_principal failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + ret, ccc)); + return false; + } + + ret = krb5_cc_retrieve_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds, &creds2); + if (ret != 0) { + /* don't retry - we didn't find these credentials to remove */ + krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds); + return false; + } + + ret = krb5_cc_remove_cred(ccc->smb_krb5_context->krb5_context, ccc->ccache, KRB5_TC_MATCH_SRV_NAMEONLY, &creds); +#ifndef SAMBA4_USES_HEIMDAL + if (ret == KRB5_CC_NOSUPP) { + /* Old MIT kerberos versions did not implement + * krb5_cc_remove_cred */ + ret = krb5_cc_remove_cred_wrap(ccc, &creds); + } +#endif + krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds); + krb5_free_cred_contents(ccc->smb_krb5_context->krb5_context, &creds2); + if (ret != 0) { + /* don't retry - we didn't find these credentials to + * remove. Note that with the current backend this + * never happens, as it always returns 0 even if the + * creds don't exist, which is why we do a separate + * krb5_cc_retrieve_cred() above. + */ + DBG_ERR("krb5_cc_remove_cred failed: %s\n", + smb_get_krb5_error_message( + ccc->smb_krb5_context->krb5_context, + ret, ccc)); + return false; + } + return true; +} + + +static int cli_credentials_new_ccache(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + char *ccache_name, + struct ccache_container **_ccc, + const char **error_string) +{ + bool must_free_cc_name = false; + krb5_error_code ret; + struct ccache_container *ccc = talloc(cred, struct ccache_container); + if (!ccc) { + return ENOMEM; + } + + ret = cli_credentials_get_krb5_context(cred, lp_ctx, + &ccc->smb_krb5_context); + if (ret) { + talloc_free(ccc); + (*error_string) = talloc_asprintf(cred, "Failed to get krb5_context: %s", + error_message(ret)); + return ret; + } + if (!talloc_reference(ccc, ccc->smb_krb5_context)) { + talloc_free(ccc); + (*error_string) = strerror(ENOMEM); + return ENOMEM; + } + + if (!ccache_name) { + must_free_cc_name = true; + + if (lpcfg_parm_bool(lp_ctx, NULL, "credentials", "krb5_cc_file", false)) { + ccache_name = talloc_asprintf(ccc, "FILE:/tmp/krb5_cc_samba_%u_%p", + (unsigned int)getpid(), ccc); + } else { + ccache_name = talloc_asprintf(ccc, "MEMORY:%p", + ccc); + } + + if (!ccache_name) { + talloc_free(ccc); + (*error_string) = strerror(ENOMEM); + return ENOMEM; + } + } + + ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, ccache_name, + &ccc->ccache); + if (ret) { + (*error_string) = talloc_asprintf(cred, "failed to resolve a krb5 ccache (%s): %s\n", + ccache_name, + smb_get_krb5_error_message(ccc->smb_krb5_context->krb5_context, + ret, ccc)); + talloc_free(ccache_name); + talloc_free(ccc); + return ret; + } + + if (strncasecmp(ccache_name, "MEMORY:", 7) == 0) { + talloc_set_destructor(ccc, free_mccache); + } else { + talloc_set_destructor(ccc, free_dccache); + } + + if (must_free_cc_name) { + talloc_free(ccache_name); + } + + *_ccc = ccc; + + return 0; +} + +_PUBLIC_ int cli_credentials_get_named_ccache(struct cli_credentials *cred, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + char *ccache_name, + struct ccache_container **ccc, + const char **error_string) +{ + krb5_error_code ret; + enum credentials_obtained obtained; + + if (cred->machine_account_pending) { + cli_credentials_set_machine_account(cred, lp_ctx); + } + + if (cred->ccache_obtained >= cred->ccache_threshold && + cred->ccache_obtained > CRED_UNINITIALISED) { + time_t lifetime; + bool expired = false; + ret = smb_krb5_cc_get_lifetime(cred->ccache->smb_krb5_context->krb5_context, + cred->ccache->ccache, &lifetime); + if (ret == KRB5_CC_END || ret == ENOENT) { + /* If we have a particular ccache set, without + * an initial ticket, then assume there is a + * good reason */ + } else if (ret == 0) { + if (lifetime == 0) { + DEBUG(3, ("Ticket in credentials cache for %s expired, will refresh\n", + cli_credentials_get_principal(cred, cred))); + expired = true; + } else if (lifetime < 300) { + DEBUG(3, ("Ticket in credentials cache for %s will shortly expire (%u secs), will refresh\n", + cli_credentials_get_principal(cred, cred), (unsigned int)lifetime)); + expired = true; + } + } else { + (*error_string) = talloc_asprintf(cred, "failed to get ccache lifetime: %s\n", + smb_get_krb5_error_message(cred->ccache->smb_krb5_context->krb5_context, + ret, cred)); + return ret; + } + + DEBUG(5, ("Ticket in credentials cache for %s will expire in %u secs\n", + cli_credentials_get_principal(cred, cred), (unsigned int)lifetime)); + + if (!expired) { + *ccc = cred->ccache; + return 0; + } + } + if (cli_credentials_is_anonymous(cred)) { + (*error_string) = "Cannot get anonymous kerberos credentials"; + return EINVAL; + } + + ret = cli_credentials_new_ccache(cred, lp_ctx, ccache_name, ccc, error_string); + if (ret) { + return ret; + } + + ret = kinit_to_ccache(cred, cred, (*ccc)->smb_krb5_context, event_ctx, (*ccc)->ccache, &obtained, error_string); + if (ret) { + return ret; + } + + ret = cli_credentials_set_from_ccache(cred, *ccc, + obtained, error_string); + + cred->ccache = *ccc; + cred->ccache_obtained = cred->principal_obtained; + if (ret) { + return ret; + } + cli_credentials_invalidate_client_gss_creds(cred, cred->ccache_obtained); + return 0; +} + +_PUBLIC_ int cli_credentials_get_ccache(struct cli_credentials *cred, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct ccache_container **ccc, + const char **error_string) +{ + return cli_credentials_get_named_ccache(cred, event_ctx, lp_ctx, NULL, ccc, error_string); +} + +/* We have good reason to think the ccache in these credentials is invalid - blow it away */ +static void cli_credentials_unconditionally_invalidate_client_gss_creds(struct cli_credentials *cred) +{ + if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) { + talloc_unlink(cred, cred->client_gss_creds); + cred->client_gss_creds = NULL; + } + cred->client_gss_creds_obtained = CRED_UNINITIALISED; +} + +void cli_credentials_invalidate_client_gss_creds(struct cli_credentials *cred, + enum credentials_obtained obtained) +{ + /* If the caller just changed the username/password etc, then + * any cached credentials are now invalid */ + if (obtained >= cred->client_gss_creds_obtained) { + if (cred->client_gss_creds_obtained > CRED_UNINITIALISED) { + talloc_unlink(cred, cred->client_gss_creds); + cred->client_gss_creds = NULL; + } + cred->client_gss_creds_obtained = CRED_UNINITIALISED; + } + /* Now that we know that the data is 'this specified', then + * don't allow something less 'known' to be returned as a + * ccache. Ie, if the username is on the command line, we + * don't want to later guess to use a file-based ccache */ + if (obtained > cred->client_gss_creds_threshold) { + cred->client_gss_creds_threshold = obtained; + } +} + +/* We have good reason to think this CCACHE is invalid. Blow it away */ +static void cli_credentials_unconditionally_invalidate_ccache(struct cli_credentials *cred) +{ + if (cred->ccache_obtained > CRED_UNINITIALISED) { + talloc_unlink(cred, cred->ccache); + cred->ccache = NULL; + } + cred->ccache_obtained = CRED_UNINITIALISED; + + cli_credentials_unconditionally_invalidate_client_gss_creds(cred); +} + +_PUBLIC_ void cli_credentials_invalidate_ccache(struct cli_credentials *cred, + enum credentials_obtained obtained) +{ + /* If the caller just changed the username/password etc, then + * any cached credentials are now invalid */ + if (obtained >= cred->ccache_obtained) { + if (cred->ccache_obtained > CRED_UNINITIALISED) { + talloc_unlink(cred, cred->ccache); + cred->ccache = NULL; + } + cred->ccache_obtained = CRED_UNINITIALISED; + } + /* Now that we know that the data is 'this specified', then + * don't allow something less 'known' to be returned as a + * ccache. i.e, if the username is on the command line, we + * don't want to later guess to use a file-based ccache */ + if (obtained > cred->ccache_threshold) { + cred->ccache_threshold = obtained; + } + + cli_credentials_invalidate_client_gss_creds(cred, + obtained); +} + +static int free_gssapi_creds(struct gssapi_creds_container *gcc) +{ + OM_uint32 min_stat; + (void)gss_release_cred(&min_stat, &gcc->creds); + return 0; +} + +_PUBLIC_ int cli_credentials_get_client_gss_creds(struct cli_credentials *cred, + struct tevent_context *event_ctx, + struct loadparm_context *lp_ctx, + struct gssapi_creds_container **_gcc, + const char **error_string) +{ + int ret = 0; + OM_uint32 maj_stat, min_stat; + struct gssapi_creds_container *gcc; + struct ccache_container *ccache; +#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X + gss_buffer_desc empty_buffer = GSS_C_EMPTY_BUFFER; + gss_OID oid = discard_const(GSS_KRB5_CRED_NO_CI_FLAGS_X); +#endif + krb5_enctype *etypes = NULL; + + if (cred->client_gss_creds_obtained >= cred->client_gss_creds_threshold && + cred->client_gss_creds_obtained > CRED_UNINITIALISED) { + bool expired = false; + OM_uint32 lifetime = 0; + gss_cred_usage_t usage = 0; + maj_stat = gss_inquire_cred(&min_stat, cred->client_gss_creds->creds, + NULL, &lifetime, &usage, NULL); + if (maj_stat == GSS_S_CREDENTIALS_EXPIRED) { + DEBUG(3, ("Credentials for %s expired, must refresh credentials cache\n", cli_credentials_get_principal(cred, cred))); + expired = true; + } else if (maj_stat == GSS_S_COMPLETE && lifetime < 300) { + DEBUG(3, ("Credentials for %s will expire shortly (%u sec), must refresh credentials cache\n", cli_credentials_get_principal(cred, cred), lifetime)); + expired = true; + } else if (maj_stat != GSS_S_COMPLETE) { + *error_string = talloc_asprintf(cred, "inquiry of credential lifefime via GSSAPI gss_inquire_cred failed: %s\n", + gssapi_error_string(cred, maj_stat, min_stat, NULL)); + return EINVAL; + } + if (expired) { + cli_credentials_unconditionally_invalidate_client_gss_creds(cred); + } else { + DEBUG(5, ("GSSAPI credentials for %s will expire in %u secs\n", + cli_credentials_get_principal(cred, cred), (unsigned int)lifetime)); + + *_gcc = cred->client_gss_creds; + return 0; + } + } + + ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx, + &ccache, error_string); + if (ret) { + if (cli_credentials_get_kerberos_state(cred) == CRED_USE_KERBEROS_REQUIRED) { + DEBUG(1, ("Failed to get kerberos credentials (kerberos required): %s\n", *error_string)); + } else { + DEBUG(4, ("Failed to get kerberos credentials: %s\n", *error_string)); + } + return ret; + } + + gcc = talloc(cred, struct gssapi_creds_container); + if (!gcc) { + (*error_string) = error_message(ENOMEM); + return ENOMEM; + } + + maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context, + ccache->ccache, NULL, NULL, + &gcc->creds); + if ((maj_stat == GSS_S_FAILURE) && + (min_stat == (OM_uint32)KRB5_CC_END || + min_stat == (OM_uint32)KRB5_CC_NOTFOUND || + min_stat == (OM_uint32)KRB5_FCC_NOFILE)) + { + /* This CCACHE is no good. Ensure we don't use it again */ + cli_credentials_unconditionally_invalidate_ccache(cred); + + /* Now try again to get a ccache */ + ret = cli_credentials_get_ccache(cred, event_ctx, lp_ctx, + &ccache, error_string); + if (ret) { + DEBUG(1, ("Failed to re-get CCACHE for GSSAPI client: %s\n", error_message(ret))); + return ret; + } + + maj_stat = smb_gss_krb5_import_cred(&min_stat, ccache->smb_krb5_context->krb5_context, + ccache->ccache, NULL, NULL, + &gcc->creds); + + } + + if (maj_stat) { + talloc_free(gcc); + if (min_stat) { + ret = min_stat; + } else { + ret = EINVAL; + } + (*error_string) = talloc_asprintf(cred, "smb_gss_krb5_import_cred failed: %s", error_message(ret)); + return ret; + } + + + /* + * transfer the enctypes from the smb_krb5_context to the gssapi layer + * + * We use 'our' smb_krb5_context to do the AS-REQ and it is possible + * to configure the enctypes via the krb5.conf. + * + * And the gss_init_sec_context() creates it's own krb5_context and + * the TGS-REQ had all enctypes in it and only the ones configured + * and used for the AS-REQ, so it wasn't possible to disable the usage + * of AES keys. + */ + min_stat = smb_krb5_get_allowed_etypes(ccache->smb_krb5_context->krb5_context, + &etypes); + if (min_stat == 0) { + OM_uint32 num_ktypes; + + for (num_ktypes = 0; etypes[num_ktypes]; num_ktypes++); + + maj_stat = gss_krb5_set_allowable_enctypes(&min_stat, gcc->creds, + num_ktypes, + (int32_t *) etypes); + SAFE_FREE(etypes); + if (maj_stat) { + talloc_free(gcc); + if (min_stat) { + ret = min_stat; + } else { + ret = EINVAL; + } + (*error_string) = talloc_asprintf(cred, "gss_krb5_set_allowable_enctypes failed: %s", error_message(ret)); + return ret; + } + } + +#ifdef HAVE_GSS_KRB5_CRED_NO_CI_FLAGS_X + /* + * Don't force GSS_C_CONF_FLAG and GSS_C_INTEG_FLAG. + * + * This allows us to disable SIGN and SEAL on a TLS connection with + * GSS-SPNENO. For example ldaps:// connections. + * + * https://groups.yahoo.com/neo/groups/cat-ietf/conversations/topics/575 + * http://krbdev.mit.edu/rt/Ticket/Display.html?id=6938 + */ + maj_stat = gss_set_cred_option(&min_stat, &gcc->creds, + oid, + &empty_buffer); + if (maj_stat) { + talloc_free(gcc); + if (min_stat) { + ret = min_stat; + } else { + ret = EINVAL; + } + (*error_string) = talloc_asprintf(cred, "gss_set_cred_option failed: %s", error_message(ret)); + return ret; + } +#endif + cred->client_gss_creds_obtained = cred->ccache_obtained; + talloc_set_destructor(gcc, free_gssapi_creds); + cred->client_gss_creds = gcc; + *_gcc = gcc; + return 0; +} + +/** + Set a gssapi cred_id_t into the credentials system. (Client case) + + This grabs the credentials both 'intact' and getting the krb5 + ccache out of it. This routine can be generalised in future for + the case where we deal with GSSAPI mechs other than krb5. + + On sucess, the caller must not free gssapi_cred, as it now belongs + to the credentials system. +*/ + + int cli_credentials_set_client_gss_creds(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + gss_cred_id_t gssapi_cred, + enum credentials_obtained obtained, + const char **error_string) +{ + int ret; + OM_uint32 maj_stat, min_stat; + struct ccache_container *ccc = NULL; + struct gssapi_creds_container *gcc = NULL; + if (cred->client_gss_creds_obtained > obtained) { + return 0; + } + + gcc = talloc(cred, struct gssapi_creds_container); + if (!gcc) { + (*error_string) = error_message(ENOMEM); + return ENOMEM; + } + + ret = cli_credentials_new_ccache(cred, lp_ctx, NULL, &ccc, error_string); + if (ret != 0) { + return ret; + } + + maj_stat = smb_gss_krb5_copy_ccache(&min_stat, + gssapi_cred, + ccc); + if (maj_stat) { + if (min_stat) { + ret = min_stat; + } else { + ret = EINVAL; + } + if (ret) { + (*error_string) = error_message(ENOMEM); + } + } + + if (ret == 0) { + ret = cli_credentials_set_from_ccache(cred, ccc, obtained, error_string); + } + cred->ccache = ccc; + cred->ccache_obtained = obtained; + if (ret == 0) { + gcc->creds = gssapi_cred; + talloc_set_destructor(gcc, free_gssapi_creds); + + /* set the clinet_gss_creds_obtained here, as it just + got set to UNINITIALISED by the calls above */ + cred->client_gss_creds_obtained = obtained; + cred->client_gss_creds = gcc; + } + return ret; +} + +static int cli_credentials_shallow_ccache(struct cli_credentials *cred) +{ + krb5_error_code ret; + const struct ccache_container *old_ccc = NULL; + enum credentials_obtained old_obtained; + struct ccache_container *ccc = NULL; + char *ccache_name = NULL; + krb5_principal princ; + + old_obtained = cred->ccache_obtained; + old_ccc = cred->ccache; + if (old_ccc == NULL) { + return 0; + } + + cred->ccache = NULL; + cred->ccache_obtained = CRED_UNINITIALISED; + cred->client_gss_creds = NULL; + cred->client_gss_creds_obtained = CRED_UNINITIALISED; + + ret = krb5_cc_get_principal( + old_ccc->smb_krb5_context->krb5_context, + old_ccc->ccache, + &princ); + if (ret != 0) { + /* + * This is an empty ccache. No point in copying anything. + */ + return 0; + } + krb5_free_principal(old_ccc->smb_krb5_context->krb5_context, princ); + + ccc = talloc(cred, struct ccache_container); + if (ccc == NULL) { + return ENOMEM; + } + *ccc = *old_ccc; + ccc->ccache = NULL; + + ccache_name = talloc_asprintf(ccc, "MEMORY:%p", ccc); + + ret = krb5_cc_resolve(ccc->smb_krb5_context->krb5_context, + ccache_name, &ccc->ccache); + if (ret != 0) { + TALLOC_FREE(ccc); + return ret; + } + + talloc_set_destructor(ccc, free_mccache); + + TALLOC_FREE(ccache_name); + + ret = smb_krb5_cc_copy_creds(ccc->smb_krb5_context->krb5_context, + old_ccc->ccache, ccc->ccache); + if (ret != 0) { + TALLOC_FREE(ccc); + return ret; + } + + cred->ccache = ccc; + cred->ccache_obtained = old_obtained; + return ret; +} + +_PUBLIC_ struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ctx, + struct cli_credentials *src) +{ + struct cli_credentials *dst; + int ret; + + dst = talloc(mem_ctx, struct cli_credentials); + if (dst == NULL) { + return NULL; + } + + *dst = *src; + + ret = cli_credentials_shallow_ccache(dst); + if (ret != 0) { + TALLOC_FREE(dst); + return NULL; + } + + return dst; +} + +/* Get the keytab (actually, a container containing the krb5_keytab) + * attached to this context. If this hasn't been done or set before, + * it will be generated from the password. + */ +_PUBLIC_ int cli_credentials_get_keytab(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct keytab_container **_ktc) +{ + krb5_error_code ret; + struct keytab_container *ktc; + struct smb_krb5_context *smb_krb5_context; + const char *keytab_name; + krb5_keytab keytab; + TALLOC_CTX *mem_ctx; + const char *username = cli_credentials_get_username(cred); + const char *upn = NULL; + const char *realm = cli_credentials_get_realm(cred); + char *salt_principal = NULL; + uint32_t uac_flags = 0; + + if (cred->keytab_obtained >= (MAX(cred->principal_obtained, + cred->username_obtained))) { + *_ktc = cred->keytab; + return 0; + } + + if (cli_credentials_is_anonymous(cred)) { + return EINVAL; + } + + ret = cli_credentials_get_krb5_context(cred, lp_ctx, + &smb_krb5_context); + if (ret) { + return ret; + } + + mem_ctx = talloc_new(cred); + if (!mem_ctx) { + return ENOMEM; + } + + switch (cred->secure_channel_type) { + case SEC_CHAN_WKSTA: + case SEC_CHAN_RODC: + uac_flags = UF_WORKSTATION_TRUST_ACCOUNT; + break; + case SEC_CHAN_BDC: + uac_flags = UF_SERVER_TRUST_ACCOUNT; + break; + case SEC_CHAN_DOMAIN: + case SEC_CHAN_DNS_DOMAIN: + uac_flags = UF_INTERDOMAIN_TRUST_ACCOUNT; + break; + default: + upn = cli_credentials_get_principal(cred, mem_ctx); + if (upn == NULL) { + TALLOC_FREE(mem_ctx); + return ENOMEM; + } + uac_flags = UF_NORMAL_ACCOUNT; + break; + } + + ret = smb_krb5_salt_principal_str(realm, + username, /* sAMAccountName */ + upn, /* userPrincipalName */ + uac_flags, + mem_ctx, + &salt_principal); + if (ret) { + talloc_free(mem_ctx); + return ret; + } + + ret = smb_krb5_create_memory_keytab(mem_ctx, + smb_krb5_context->krb5_context, + cli_credentials_get_password(cred), + username, + realm, + salt_principal, + cli_credentials_get_kvno(cred), + &keytab, + &keytab_name); + if (ret) { + talloc_free(mem_ctx); + return ret; + } + + ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context, + keytab, keytab_name, &ktc); + if (ret) { + talloc_free(mem_ctx); + return ret; + } + + cred->keytab_obtained = (MAX(cred->principal_obtained, + cred->username_obtained)); + + /* We make this keytab up based on a password. Therefore + * match-by-key is acceptable, we can't match on the wrong + * principal */ + ktc->password_based = true; + + talloc_steal(cred, ktc); + cred->keytab = ktc; + *_ktc = cred->keytab; + talloc_free(mem_ctx); + return ret; +} + +/* Given the name of a keytab (presumably in the format + * FILE:/etc/krb5.keytab), open it and attach it */ + +_PUBLIC_ int cli_credentials_set_keytab_name(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + const char *keytab_name, + enum credentials_obtained obtained) +{ + krb5_error_code ret; + struct keytab_container *ktc; + struct smb_krb5_context *smb_krb5_context; + TALLOC_CTX *mem_ctx; + + if (cred->keytab_obtained >= obtained) { + return 0; + } + + ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context); + if (ret) { + return ret; + } + + mem_ctx = talloc_new(cred); + if (!mem_ctx) { + return ENOMEM; + } + + ret = smb_krb5_get_keytab_container(mem_ctx, smb_krb5_context, + NULL, keytab_name, &ktc); + if (ret) { + return ret; + } + + cred->keytab_obtained = obtained; + + talloc_steal(cred, ktc); + cred->keytab = ktc; + talloc_free(mem_ctx); + + return ret; +} + +/* Get server gss credentials (in gsskrb5, this means the keytab) */ + +_PUBLIC_ int cli_credentials_get_server_gss_creds(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct gssapi_creds_container **_gcc) +{ + int ret = 0; + OM_uint32 maj_stat, min_stat; + struct gssapi_creds_container *gcc; + struct keytab_container *ktc; + struct smb_krb5_context *smb_krb5_context; + TALLOC_CTX *mem_ctx; + krb5_principal princ; + const char *error_string; + enum credentials_obtained obtained; + + mem_ctx = talloc_new(cred); + if (!mem_ctx) { + return ENOMEM; + } + + ret = cli_credentials_get_krb5_context(cred, lp_ctx, &smb_krb5_context); + if (ret) { + return ret; + } + + ret = principal_from_credentials(mem_ctx, cred, smb_krb5_context, &princ, &obtained, &error_string); + if (ret) { + DEBUG(1,("cli_credentials_get_server_gss_creds: making krb5 principal failed (%s)\n", + error_string)); + talloc_free(mem_ctx); + return ret; + } + + if (cred->server_gss_creds_obtained >= (MAX(cred->keytab_obtained, obtained))) { + talloc_free(mem_ctx); + *_gcc = cred->server_gss_creds; + return 0; + } + + ret = cli_credentials_get_keytab(cred, lp_ctx, &ktc); + if (ret) { + DEBUG(1, ("Failed to get keytab for GSSAPI server: %s\n", error_message(ret))); + return ret; + } + + gcc = talloc(cred, struct gssapi_creds_container); + if (!gcc) { + talloc_free(mem_ctx); + return ENOMEM; + } + + if (ktc->password_based || obtained < CRED_SPECIFIED) { + /* + * This creates a GSSAPI cred_id_t for match-by-key with only + * the keytab set + */ + princ = NULL; + } + maj_stat = smb_gss_krb5_import_cred(&min_stat, + smb_krb5_context->krb5_context, + NULL, princ, + ktc->keytab, + &gcc->creds); + if (maj_stat) { + if (min_stat) { + ret = min_stat; + } else { + ret = EINVAL; + } + } + if (ret == 0) { + cred->server_gss_creds_obtained = cred->keytab_obtained; + talloc_set_destructor(gcc, free_gssapi_creds); + cred->server_gss_creds = gcc; + *_gcc = gcc; + } + talloc_free(mem_ctx); + return ret; +} + +/** + * Set Kerberos KVNO + */ + +_PUBLIC_ void cli_credentials_set_kvno(struct cli_credentials *cred, + int kvno) +{ + cred->kvno = kvno; +} + +/** + * Return Kerberos KVNO + */ + +_PUBLIC_ int cli_credentials_get_kvno(struct cli_credentials *cred) +{ + return cred->kvno; +} + + +const char *cli_credentials_get_salt_principal(struct cli_credentials *cred) +{ + return cred->salt_principal; +} + +_PUBLIC_ void cli_credentials_set_salt_principal(struct cli_credentials *cred, const char *principal) +{ + talloc_free(cred->salt_principal); + cred->salt_principal = talloc_strdup(cred, principal); +} + +/* The 'impersonate_principal' is used to allow one Kerberos principal + * (and it's associated keytab etc) to impersonate another. The + * ability to do this is controlled by the KDC, but it is generally + * permitted to impersonate anyone to yourself. This allows any + * member of the domain to get the groups of a user. This is also + * known as S4U2Self */ + +_PUBLIC_ const char *cli_credentials_get_impersonate_principal(struct cli_credentials *cred) +{ + return cred->impersonate_principal; +} + +/* + * The 'self_service' is the service principal that + * represents the same object (by its objectSid) + * as the client principal (typically our machine account). + * When trying to impersonate 'impersonate_principal' with + * S4U2Self. + */ +_PUBLIC_ const char *cli_credentials_get_self_service(struct cli_credentials *cred) +{ + return cred->self_service; +} + +_PUBLIC_ void cli_credentials_set_impersonate_principal(struct cli_credentials *cred, + const char *principal, + const char *self_service) +{ + talloc_free(cred->impersonate_principal); + cred->impersonate_principal = talloc_strdup(cred, principal); + talloc_free(cred->self_service); + cred->self_service = talloc_strdup(cred, self_service); + cli_credentials_set_kerberos_state(cred, + CRED_USE_KERBEROS_REQUIRED, + CRED_SPECIFIED); +} + +/* + * when impersonating for S4U2proxy we need to set the target principal. + * Similarly, we may only be authorized to do general impersonation to + * some particular services. + * + * Likewise, password changes typically require a ticket to kpasswd/realm directly, not via a TGT + * + * NULL means that tickets will be obtained for the krbtgt service. +*/ + +const char *cli_credentials_get_target_service(struct cli_credentials *cred) +{ + return cred->target_service; +} + +_PUBLIC_ void cli_credentials_set_target_service(struct cli_credentials *cred, const char *target_service) +{ + talloc_free(cred->target_service); + cred->target_service = talloc_strdup(cred, target_service); +} + +_PUBLIC_ int cli_credentials_get_aes256_key(struct cli_credentials *cred, + TALLOC_CTX *mem_ctx, + struct loadparm_context *lp_ctx, + const char *salt, + DATA_BLOB *aes_256) +{ + struct smb_krb5_context *smb_krb5_context = NULL; + krb5_error_code krb5_ret; + int ret; + const char *password = NULL; + krb5_data cleartext_data; + krb5_data salt_data; + krb5_keyblock key; + + if (cred->password_will_be_nt_hash) { + DEBUG(1,("cli_credentials_get_aes256_key: cannot generate AES256 key using NT hash\n")); + return EINVAL; + } + + password = cli_credentials_get_password(cred); + if (password == NULL) { + return EINVAL; + } + + cleartext_data.data = discard_const_p(char, password); + cleartext_data.length = strlen(password); + + ret = cli_credentials_get_krb5_context(cred, lp_ctx, + &smb_krb5_context); + if (ret != 0) { + return ret; + } + + salt_data.data = discard_const_p(char, salt); + salt_data.length = strlen(salt); + + /* + * create ENCTYPE_AES256_CTS_HMAC_SHA1_96 key out of + * the salt and the cleartext password + */ + krb5_ret = smb_krb5_create_key_from_string(smb_krb5_context->krb5_context, + NULL, + &salt_data, + &cleartext_data, + ENCTYPE_AES256_CTS_HMAC_SHA1_96, + &key); + if (krb5_ret != 0) { + DEBUG(1,("cli_credentials_get_aes256_key: " + "generation of a aes256-cts-hmac-sha1-96 key failed: %s", + smb_get_krb5_error_message(smb_krb5_context->krb5_context, + krb5_ret, mem_ctx))); + return EINVAL; + } + *aes_256 = data_blob_talloc(mem_ctx, + KRB5_KEY_DATA(&key), + KRB5_KEY_LENGTH(&key)); + krb5_free_keyblock_contents(smb_krb5_context->krb5_context, &key); + if (aes_256->data == NULL) { + return ENOMEM; + } + talloc_keep_secret(aes_256->data); + + return 0; +} diff --git a/auth/credentials/credentials_krb5.h b/auth/credentials/credentials_krb5.h new file mode 100644 index 0000000..ae60104 --- /dev/null +++ b/auth/credentials/credentials_krb5.h @@ -0,0 +1,45 @@ +/* + samba -- Unix SMB/CIFS implementation. + + Client credentials structure + + Copyright (C) Jelmer Vernooij 2004-2006 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 __CREDENTIALS_KRB5_H__ +#define __CREDENTIALS_KRB5_H__ + +#include <gssapi/gssapi.h> +#include <gssapi/gssapi_krb5.h> +#include <krb5.h> + +struct gssapi_creds_container { + gss_cred_id_t creds; +}; + +/* Manually prototyped here to avoid needing gss headers in most callers */ +int cli_credentials_set_client_gss_creds(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + gss_cred_id_t gssapi_cred, + enum credentials_obtained obtained, + const char **error_string); + +struct cli_credentials *cli_credentials_shallow_copy(TALLOC_CTX *mem_ctx, + struct cli_credentials *src); + + +#endif /* __CREDENTIALS_KRB5_H__ */ diff --git a/auth/credentials/credentials_ntlm.c b/auth/credentials/credentials_ntlm.c new file mode 100644 index 0000000..1c17148 --- /dev/null +++ b/auth/credentials/credentials_ntlm.c @@ -0,0 +1,555 @@ +/* + Unix SMB/CIFS implementation. + + User credentials handling + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2001-2005 + Copyright (C) Stefan Metzmacher 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 "includes.h" +#include "librpc/gen_ndr/samr.h" /* for struct samrPassword */ +#include "../lib/crypto/crypto.h" +#include "libcli/auth/libcli_auth.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_internal.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +_PUBLIC_ NTSTATUS cli_credentials_get_ntlm_response(struct cli_credentials *cred, TALLOC_CTX *mem_ctx, + int *flags, + DATA_BLOB challenge, + const NTTIME *server_timestamp, + DATA_BLOB target_info, + DATA_BLOB *_lm_response, DATA_BLOB *_nt_response, + DATA_BLOB *_lm_session_key, DATA_BLOB *_session_key) +{ + TALLOC_CTX *frame = talloc_stackframe(); + const char *user = NULL; + const char *domain = NULL; + DATA_BLOB lm_response = data_blob_null; + DATA_BLOB nt_response = data_blob_null; + DATA_BLOB lm_session_key = data_blob_null; + DATA_BLOB session_key = data_blob_null; + const struct samr_Password *nt_hash = NULL; + int rc; + + if (cred->kerberos_state == CRED_USE_KERBEROS_REQUIRED) { + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + /* We may already have an NTLM response we prepared earlier. + * This is used for NTLM pass-though authentication */ + if (cred->nt_response.data || cred->lm_response.data) { + if (cred->nt_response.length != 0) { + nt_response = data_blob_dup_talloc(frame, + cred->nt_response); + if (nt_response.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + if (cred->nt_session_key.length != 0) { + session_key = data_blob_dup_talloc(frame, + cred->nt_session_key); + if (session_key.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + if (cred->lm_response.length != 0) { + lm_response = data_blob_dup_talloc(frame, + cred->lm_response); + if (lm_response.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + if (cred->lm_session_key.length != 0) { + lm_session_key = data_blob_dup_talloc(frame, + cred->lm_session_key); + if (lm_session_key.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + + if (cred->lm_response.data == NULL) { + *flags = *flags & ~CLI_CRED_LANMAN_AUTH; + } + goto done; + } + + nt_hash = cli_credentials_get_nt_hash(cred, frame); + + cli_credentials_get_ntlm_username_domain(cred, frame, &user, &domain); + if (user == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + if (domain == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + /* If we are sending a username@realm login (see function + * above), then we will not send LM, it will not be + * accepted */ + if (cred->principal_obtained > cred->username_obtained) { + *flags = *flags & ~CLI_CRED_LANMAN_AUTH; + } + + /* Likewise if we are a machine account (avoid protocol downgrade attacks) */ + if (cred->machine_account) { + *flags = *flags & ~CLI_CRED_LANMAN_AUTH; + } + + if (!nt_hash) { + /* do nothing - blobs are zero length */ + + /* session key is all zeros */ + session_key = data_blob_talloc_zero(frame, 16); + if (session_key.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + lm_session_key = data_blob_talloc_zero(frame, 16); + if (lm_session_key.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + /* not doing NTLM2 without a password */ + *flags &= ~CLI_CRED_NTLM2; + } else if (*flags & CLI_CRED_NTLMv2_AUTH) { + + if (!target_info.length) { + /* be lazy, match win2k - we can't do NTLMv2 without it */ + DEBUG(1, ("Server did not provide 'target information', required for NTLMv2\n")); + TALLOC_FREE(frame); + return NT_STATUS_INVALID_PARAMETER; + } + + /* TODO: if the remote server is standalone, then we should replace 'domain' + with the server name as supplied above */ + + if (!SMBNTLMv2encrypt_hash(frame, + user, + domain, + nt_hash->hash, &challenge, + server_timestamp, &target_info, + &lm_response, &nt_response, + NULL, &session_key)) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + /* LM Key is incompatible... */ + *flags &= ~CLI_CRED_LANMAN_AUTH; + if (lm_response.length != 0) { + /* + * We should not expose the lm key. + */ + memset(lm_response.data, 0, lm_response.length); + } + } else if (*flags & CLI_CRED_NTLM2) { + uint8_t session_nonce[16]; + uint8_t session_nonce_hash[16]; + uint8_t user_session_key[16]; + + lm_response = data_blob_talloc_zero(frame, 24); + if (lm_response.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + generate_random_buffer(lm_response.data, 8); + + memcpy(session_nonce, challenge.data, 8); + memcpy(&session_nonce[8], lm_response.data, 8); + + rc = gnutls_hash_fast(GNUTLS_DIG_MD5, + session_nonce, + sizeof(session_nonce), + session_nonce_hash); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + DEBUG(5, ("NTLMSSP challenge set by NTLM2\n")); + DEBUG(5, ("challenge is: \n")); + dump_data(5, session_nonce_hash, 8); + + nt_response = data_blob_talloc_zero(frame, 24); + if (nt_response.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + rc = SMBOWFencrypt(nt_hash->hash, + session_nonce_hash, + nt_response.data); + if (rc != 0) { + TALLOC_FREE(frame); + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + ZERO_ARRAY(session_nonce_hash); + + session_key = data_blob_talloc_zero(frame, 16); + if (session_key.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + SMBsesskeygen_ntv1(nt_hash->hash, user_session_key); + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + user_session_key, + sizeof(user_session_key), + session_nonce, + sizeof(session_nonce), + session_key.data); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + ZERO_ARRAY(user_session_key); + + dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length); + + /* LM Key is incompatible... */ + *flags &= ~CLI_CRED_LANMAN_AUTH; + } else { + const char *password = cli_credentials_get_password(cred); + uint8_t lm_hash[16]; + bool do_lm = false; + + nt_response = data_blob_talloc_zero(frame, 24); + if (nt_response.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + rc = SMBOWFencrypt(nt_hash->hash, challenge.data, + nt_response.data); + if (rc != 0) { + TALLOC_FREE(frame); + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + + session_key = data_blob_talloc_zero(frame, 16); + if (session_key.data == NULL) { + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + SMBsesskeygen_ntv1(nt_hash->hash, session_key.data); + dump_data_pw("NT session key:\n", session_key.data, session_key.length); + + /* lanman auth is insecure, it may be disabled. + We may also not have a password */ + + if (password != NULL) { + do_lm = E_deshash(password, lm_hash); + } + + if (*flags & CLI_CRED_LANMAN_AUTH && do_lm) { + lm_response = data_blob_talloc_zero(frame, 24); + if (lm_response.data == NULL) { + ZERO_STRUCT(lm_hash); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + + rc = SMBencrypt_hash(lm_hash, + challenge.data, + lm_response.data); + if (rc != 0) { + ZERO_STRUCT(lm_hash); + TALLOC_FREE(frame); + return gnutls_error_to_ntstatus(rc, NT_STATUS_ACCESS_DISABLED_BY_POLICY_OTHER); + } + } else { + /* just copy the nt_response */ + lm_response = data_blob_dup_talloc(frame, nt_response); + if (lm_response.data == NULL) { + ZERO_STRUCT(lm_hash); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + } + + if (do_lm) { + lm_session_key = data_blob_talloc_zero(frame, 16); + if (lm_session_key.data == NULL) { + ZERO_STRUCT(lm_hash); + TALLOC_FREE(frame); + return NT_STATUS_NO_MEMORY; + } + memcpy(lm_session_key.data, lm_hash, 8); + + if (!(*flags & CLI_CRED_NTLM_AUTH)) { + memcpy(session_key.data, lm_session_key.data, 16); + } + ZERO_STRUCT(lm_hash); + } + } + +done: + if (_lm_response != NULL) { + talloc_steal(mem_ctx, lm_response.data); + *_lm_response = lm_response; + } else { + data_blob_clear(&lm_response); + } + if (_nt_response != NULL) { + talloc_steal(mem_ctx, nt_response.data); + *_nt_response = nt_response; + } else { + data_blob_clear(&nt_response); + } + if (_lm_session_key != NULL) { + talloc_steal(mem_ctx, lm_session_key.data); + *_lm_session_key = lm_session_key; + } else { + data_blob_clear(&lm_session_key); + } + if (_session_key != NULL) { + talloc_steal(mem_ctx, session_key.data); + *_session_key = session_key; + } else { + data_blob_clear(&session_key); + } + TALLOC_FREE(frame); + return NT_STATUS_OK; +} + +/* + * Set a utf16 password on the credentials context, including an indication + * of 'how' the password was obtained + * + * This is required because the nt_hash is calculated over the raw utf16 blob, + * which might not be completely valid utf16, which means the conversion + * from CH_UTF16MUNGED to CH_UTF8 might loose information. + */ +_PUBLIC_ bool cli_credentials_set_utf16_password(struct cli_credentials *cred, + const DATA_BLOB *password_utf16, + enum credentials_obtained obtained) +{ + cred->password_will_be_nt_hash = false; + + if (password_utf16 == NULL) { + return cli_credentials_set_password(cred, NULL, obtained); + } + + if (obtained >= cred->password_obtained) { + struct samr_Password *nt_hash = NULL; + char *password_talloc = NULL; + size_t password_len = 0; + bool ok; + + nt_hash = talloc(cred, struct samr_Password); + if (nt_hash == NULL) { + return false; + } + + ok = convert_string_talloc(cred, + CH_UTF16MUNGED, CH_UTF8, + password_utf16->data, + password_utf16->length, + (void *)&password_talloc, + &password_len); + if (!ok) { + TALLOC_FREE(nt_hash); + return false; + } + + ok = cli_credentials_set_password(cred, password_talloc, obtained); + TALLOC_FREE(password_talloc); + if (!ok) { + TALLOC_FREE(nt_hash); + return false; + } + + mdfour(nt_hash->hash, password_utf16->data, password_utf16->length); + cred->nt_hash = nt_hash; + return true; + } + + return false; +} + +/* + * Set a old utf16 password on the credentials context. + * + * This is required because the nt_hash is calculated over the raw utf16 blob, + * which might not be completely valid utf16, which means the conversion + * from CH_UTF16MUNGED to CH_UTF8 might loose information. + */ +_PUBLIC_ bool cli_credentials_set_old_utf16_password(struct cli_credentials *cred, + const DATA_BLOB *password_utf16) +{ + struct samr_Password *nt_hash = NULL; + char *password_talloc = NULL; + size_t password_len = 0; + bool ok; + + if (password_utf16 == NULL) { + return cli_credentials_set_old_password(cred, NULL, CRED_SPECIFIED); + } + + nt_hash = talloc(cred, struct samr_Password); + if (nt_hash == NULL) { + return false; + } + + ok = convert_string_talloc(cred, + CH_UTF16MUNGED, CH_UTF8, + password_utf16->data, + password_utf16->length, + (void *)&password_talloc, + &password_len); + if (!ok) { + TALLOC_FREE(nt_hash); + return false; + } + + ok = cli_credentials_set_old_password(cred, password_talloc, CRED_SPECIFIED); + TALLOC_FREE(password_talloc); + if (!ok) { + TALLOC_FREE(nt_hash); + return false; + } + + mdfour(nt_hash->hash, password_utf16->data, password_utf16->length); + cred->old_nt_hash = nt_hash; + return true; +} + +_PUBLIC_ void cli_credentials_set_password_will_be_nt_hash(struct cli_credentials *cred, + bool val) +{ + /* + * We set this here and the next cli_credentials_set_password() + * that resets the password or password callback + * will pick this up. + * + * cli_credentials_set_nt_hash() and + * cli_credentials_set_utf16_password() will reset this + * to false. + */ + cred->password_will_be_nt_hash = val; +} + +_PUBLIC_ bool cli_credentials_set_nt_hash(struct cli_credentials *cred, + const struct samr_Password *nt_hash, + enum credentials_obtained obtained) +{ + cred->password_will_be_nt_hash = false; + + if (obtained >= cred->password_obtained) { + cli_credentials_set_password(cred, NULL, obtained); + if (nt_hash) { + cred->nt_hash = talloc(cred, struct samr_Password); + if (cred->nt_hash == NULL) { + return false; + } + *cred->nt_hash = *nt_hash; + } else { + cred->nt_hash = NULL; + } + return true; + } + + return false; +} + +_PUBLIC_ bool cli_credentials_set_old_nt_hash(struct cli_credentials *cred, + const struct samr_Password *nt_hash) +{ + cli_credentials_set_old_password(cred, NULL, CRED_SPECIFIED); + if (nt_hash) { + cred->old_nt_hash = talloc(cred, struct samr_Password); + if (cred->old_nt_hash == NULL) { + return false; + } + *cred->old_nt_hash = *nt_hash; + } else { + cred->old_nt_hash = NULL; + } + + return true; +} + +_PUBLIC_ bool cli_credentials_set_ntlm_response(struct cli_credentials *cred, + const DATA_BLOB *lm_response, + const DATA_BLOB *lm_session_key, + const DATA_BLOB *nt_response, + const DATA_BLOB *nt_session_key, + enum credentials_obtained obtained) +{ + if (obtained >= cred->password_obtained) { + cli_credentials_set_password(cred, NULL, obtained); + + data_blob_clear_free(&cred->lm_response); + data_blob_clear_free(&cred->lm_session_key); + data_blob_clear_free(&cred->nt_response); + data_blob_clear_free(&cred->nt_session_key); + + if (lm_response != NULL && lm_response->length != 0) { + cred->lm_response = data_blob_talloc(cred, + lm_response->data, + lm_response->length); + if (cred->lm_response.data == NULL) { + return false; + } + } + if (lm_session_key != NULL && lm_session_key->length != 0) { + cred->lm_session_key = data_blob_talloc(cred, + lm_session_key->data, + lm_session_key->length); + if (cred->lm_session_key.data == NULL) { + return false; + } + } + + if (nt_response != NULL && nt_response->length != 0) { + cred->nt_response = data_blob_talloc(cred, + nt_response->data, + nt_response->length); + if (cred->nt_response.data == NULL) { + return false; + } + } + if (nt_session_key != NULL && nt_session_key->length != 0) { + cred->nt_session_key = data_blob_talloc(cred, + nt_session_key->data, + nt_session_key->length); + if (cred->nt_session_key.data == NULL) { + return false; + } + } + + return true; + } + + return false; +} + diff --git a/auth/credentials/credentials_secrets.c b/auth/credentials/credentials_secrets.c new file mode 100644 index 0000000..ab2c9dd --- /dev/null +++ b/auth/credentials/credentials_secrets.c @@ -0,0 +1,485 @@ +/* + Unix SMB/CIFS implementation. + + User credentials handling (as regards on-disk files) + + Copyright (C) Jelmer Vernooij 2005 + Copyright (C) Tim Potter 2001 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 "includes.h" +#include "lib/events/events.h" +#include <ldb.h> +#include "librpc/gen_ndr/samr.h" /* for struct samrPassword */ +#include "param/secrets.h" +#include "system/filesys.h" +#include "auth/credentials/credentials.h" +#include "auth/credentials/credentials_internal.h" +#include "auth/credentials/credentials_krb5.h" +#include "auth/kerberos/kerberos_util.h" +#include "param/param.h" +#include "lib/events/events.h" +#include "dsdb/samdb/samdb.h" +#include "source3/include/secrets.h" +#include "dbwrap/dbwrap.h" +#include "dbwrap/dbwrap_open.h" +#include "lib/util/util_tdb.h" +#include "libds/common/roles.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/** + * Fill in credentials for the machine trust account, from the secrets database. + * + * @param cred Credentials structure to fill in + * @retval NTSTATUS error detailing any failure + */ +static NTSTATUS cli_credentials_set_secrets_lct(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct ldb_context *ldb, + const char *base, + const char *filter, + time_t secrets_tdb_last_change_time, + const char *secrets_tdb_password, + char **error_string) +{ + TALLOC_CTX *mem_ctx; + + int ldb_ret; + struct ldb_message *msg; + + const char *machine_account; + const char *password; + const char *domain; + const char *realm; + enum netr_SchannelType sct; + const char *salt_principal; + char *keytab; + const struct ldb_val *whenChanged; + time_t lct; + + /* ok, we are going to get it now, don't recurse back here */ + cred->machine_account_pending = false; + + /* some other parts of the system will key off this */ + cred->machine_account = true; + + mem_ctx = talloc_named(cred, 0, "cli_credentials_set_secrets from ldb"); + + if (!ldb) { + /* Local secrets are stored in secrets.ldb */ + ldb = secrets_db_connect(mem_ctx, lp_ctx); + if (!ldb) { + *error_string = talloc_strdup(cred, "Could not open secrets.ldb"); + talloc_free(mem_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + } + + ldb_ret = dsdb_search_one(ldb, mem_ctx, &msg, + ldb_dn_new(mem_ctx, ldb, base), + LDB_SCOPE_SUBTREE, + NULL, 0, "%s", filter); + + if (ldb_ret != LDB_SUCCESS) { + *error_string = talloc_asprintf(cred, "Could not find entry to match filter: '%s' base: '%s': %s: %s", + filter, base ? base : "", + ldb_strerror(ldb_ret), ldb_errstring(ldb)); + talloc_free(mem_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } + + password = ldb_msg_find_attr_as_string(msg, "secret", NULL); + + whenChanged = ldb_msg_find_ldb_val(msg, "whenChanged"); + if (!whenChanged || ldb_val_to_time(whenChanged, &lct) != LDB_SUCCESS) { + /* This attribute is mandatory */ + talloc_free(mem_ctx); + return NT_STATUS_NOT_FOUND; + } + + /* Don't set secrets.ldb info if the secrets.tdb entry was more recent */ + if (lct < secrets_tdb_last_change_time) { + talloc_free(mem_ctx); + return NT_STATUS_NOT_FOUND; + } + + if ((lct == secrets_tdb_last_change_time) && + (secrets_tdb_password != NULL) && + (password != NULL) && + (strcmp(password, secrets_tdb_password) != 0)) { + talloc_free(mem_ctx); + return NT_STATUS_NOT_FOUND; + } + + cli_credentials_set_password_last_changed_time(cred, lct); + + machine_account = ldb_msg_find_attr_as_string(msg, "samAccountName", NULL); + + if (!machine_account) { + machine_account = ldb_msg_find_attr_as_string(msg, "servicePrincipalName", NULL); + + if (!machine_account) { + const char *ldap_bind_dn = ldb_msg_find_attr_as_string(msg, "ldapBindDn", NULL); + if (!ldap_bind_dn) { + *error_string = talloc_asprintf(cred, + "Could not find 'samAccountName', " + "'servicePrincipalName' or " + "'ldapBindDn' in secrets record: %s", + ldb_dn_get_linearized(msg->dn)); + talloc_free(mem_ctx); + return NT_STATUS_CANT_ACCESS_DOMAIN_INFO; + } else { + /* store bind dn in credentials */ + cli_credentials_set_bind_dn(cred, ldap_bind_dn); + } + } + } + + salt_principal = ldb_msg_find_attr_as_string(msg, "saltPrincipal", NULL); + cli_credentials_set_salt_principal(cred, salt_principal); + + sct = ldb_msg_find_attr_as_int(msg, "secureChannelType", 0); + if (sct) { + cli_credentials_set_secure_channel_type(cred, sct); + } + + if (!password) { + const struct ldb_val *nt_password_hash = ldb_msg_find_ldb_val(msg, "unicodePwd"); + struct samr_Password hash; + ZERO_STRUCT(hash); + if (nt_password_hash) { + memcpy(hash.hash, nt_password_hash->data, + MIN(nt_password_hash->length, sizeof(hash.hash))); + + cli_credentials_set_nt_hash(cred, &hash, CRED_SPECIFIED); + } else { + cli_credentials_set_password(cred, NULL, CRED_SPECIFIED); + } + } else { + cli_credentials_set_password(cred, password, CRED_SPECIFIED); + } + + domain = ldb_msg_find_attr_as_string(msg, "flatname", NULL); + if (domain) { + cli_credentials_set_domain(cred, domain, CRED_SPECIFIED); + } + + realm = ldb_msg_find_attr_as_string(msg, "realm", NULL); + if (realm) { + cli_credentials_set_realm(cred, realm, CRED_SPECIFIED); + } + + if (machine_account) { + cli_credentials_set_username(cred, machine_account, CRED_SPECIFIED); + } + + cli_credentials_set_kvno(cred, ldb_msg_find_attr_as_int(msg, "msDS-KeyVersionNumber", 0)); + + /* If there was an external keytab specified by reference in + * the LDB, then use this. Otherwise we will make one up + * (chewing CPU time) from the password */ + keytab = keytab_name_from_msg(cred, ldb, msg); + if (keytab) { + cli_credentials_set_keytab_name(cred, lp_ctx, keytab, CRED_SPECIFIED); + talloc_free(keytab); + } + talloc_free(mem_ctx); + + return NT_STATUS_OK; +} + + +/** + * Fill in credentials for the machine trust account, from the secrets database. + * + * @param cred Credentials structure to fill in + * @retval NTSTATUS error detailing any failure + */ +_PUBLIC_ NTSTATUS cli_credentials_set_secrets(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct ldb_context *ldb, + const char *base, + const char *filter, + char **error_string) +{ + NTSTATUS status = cli_credentials_set_secrets_lct(cred, lp_ctx, ldb, base, filter, 0, NULL, error_string); + if (!NT_STATUS_IS_OK(status)) { + /* set anonymous as the fallback, if the machine account won't work */ + cli_credentials_set_anonymous(cred); + } + return status; +} + +/** + * Fill in credentials for the machine trust account, from the secrets database. + * + * @param cred Credentials structure to fill in + * @retval NTSTATUS error detailing any failure + */ +_PUBLIC_ NTSTATUS cli_credentials_set_machine_account(struct cli_credentials *cred, + struct loadparm_context *lp_ctx) +{ + struct db_context *db_ctx; + char *secrets_tdb_path; + int hash_size, tdb_flags; + + secrets_tdb_path = lpcfg_private_db_path(cred, lp_ctx, "secrets"); + if (secrets_tdb_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + + hash_size = lpcfg_tdb_hash_size(lp_ctx, secrets_tdb_path); + tdb_flags = lpcfg_tdb_flags(lp_ctx, TDB_DEFAULT); + + db_ctx = dbwrap_local_open( + cred, + secrets_tdb_path, + hash_size, + tdb_flags, + O_RDWR, + 0600, + DBWRAP_LOCK_ORDER_1, + DBWRAP_FLAG_NONE); + TALLOC_FREE(secrets_tdb_path); + + /* + * We do not check for errors here, we might not have a + * secrets.tdb at all, and so we just need to check the + * secrets.ldb + */ + return cli_credentials_set_machine_account_db_ctx(cred, lp_ctx, db_ctx); +} + +/** + * Fill in credentials for the machine trust account, from the + * secrets.ldb or passed in handle to secrets.tdb (perhaps in CTDB). + * + * This version is used in parts of the code that can link in the + * CTDB dbwrap backend, by passing down the already open handle. + * + * @param cred Credentials structure to fill in + * @param db_ctx dbwrap context for secrets.tdb + * @retval NTSTATUS error detailing any failure + */ +_PUBLIC_ NTSTATUS cli_credentials_set_machine_account_db_ctx(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + struct db_context *db_ctx) +{ + NTSTATUS status; + char *filter; + char *error_string = NULL; + const char *domain; + bool secrets_tdb_password_more_recent; + time_t secrets_tdb_lct = 0; + char *secrets_tdb_password = NULL; + char *secrets_tdb_old_password = NULL; + uint32_t secrets_tdb_secure_channel_type = SEC_CHAN_NULL; + int server_role = lpcfg_server_role(lp_ctx); + int security = lpcfg_security(lp_ctx); + char *keystr; + char *keystr_upper = NULL; + TALLOC_CTX *tmp_ctx = talloc_named(cred, 0, "cli_credentials_set_secrets from ldb"); + if (!tmp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + /* Bleh, nasty recursion issues: We are setting a machine + * account here, so we don't want the 'pending' flag around + * any more */ + cred->machine_account_pending = false; + + /* We have to do this, as the fallback in + * cli_credentials_set_secrets is to run as anonymous, so the domain is wiped */ + domain = cli_credentials_get_domain(cred); + + if (db_ctx) { + TDB_DATA dbuf; + keystr = talloc_asprintf(tmp_ctx, "%s/%s", + SECRETS_MACHINE_LAST_CHANGE_TIME, + domain); + keystr_upper = strupper_talloc(tmp_ctx, keystr); + status = dbwrap_fetch(db_ctx, tmp_ctx, string_tdb_data(keystr_upper), + &dbuf); + if (NT_STATUS_IS_OK(status) && dbuf.dsize == 4) { + secrets_tdb_lct = IVAL(dbuf.dptr,0); + } + + keystr = talloc_asprintf(tmp_ctx, "%s/%s", + SECRETS_MACHINE_PASSWORD, + domain); + keystr_upper = strupper_talloc(tmp_ctx, keystr); + status = dbwrap_fetch(db_ctx, tmp_ctx, string_tdb_data(keystr_upper), + &dbuf); + if (NT_STATUS_IS_OK(status)) { + secrets_tdb_password = (char *)dbuf.dptr; + } + + keystr = talloc_asprintf(tmp_ctx, "%s/%s", + SECRETS_MACHINE_PASSWORD_PREV, + domain); + keystr_upper = strupper_talloc(tmp_ctx, keystr); + status = dbwrap_fetch(db_ctx, tmp_ctx, string_tdb_data(keystr_upper), + &dbuf); + if (NT_STATUS_IS_OK(status)) { + secrets_tdb_old_password = (char *)dbuf.dptr; + } + + keystr = talloc_asprintf(tmp_ctx, "%s/%s", + SECRETS_MACHINE_SEC_CHANNEL_TYPE, + domain); + keystr_upper = strupper_talloc(tmp_ctx, keystr); + status = dbwrap_fetch(db_ctx, tmp_ctx, string_tdb_data(keystr_upper), + &dbuf); + if (NT_STATUS_IS_OK(status) && dbuf.dsize == 4) { + secrets_tdb_secure_channel_type = IVAL(dbuf.dptr,0); + } + } + + filter = talloc_asprintf(cred, SECRETS_PRIMARY_DOMAIN_FILTER, + domain); + status = cli_credentials_set_secrets_lct(cred, lp_ctx, NULL, + SECRETS_PRIMARY_DOMAIN_DN, + filter, secrets_tdb_lct, secrets_tdb_password, &error_string); + if (secrets_tdb_password == NULL) { + secrets_tdb_password_more_recent = false; + } else if (NT_STATUS_EQUAL(NT_STATUS_CANT_ACCESS_DOMAIN_INFO, status) + || NT_STATUS_EQUAL(NT_STATUS_NOT_FOUND, status)) { + secrets_tdb_password_more_recent = true; + } else if (secrets_tdb_lct > cli_credentials_get_password_last_changed_time(cred)) { + secrets_tdb_password_more_recent = true; + } else if (secrets_tdb_lct == cli_credentials_get_password_last_changed_time(cred)) { + secrets_tdb_password_more_recent = strcmp(secrets_tdb_password, cli_credentials_get_password(cred)) != 0; + } else { + secrets_tdb_password_more_recent = false; + } + + if (secrets_tdb_password_more_recent) { + enum credentials_use_kerberos use_kerberos = + CRED_USE_KERBEROS_DISABLED; + char *machine_account = talloc_asprintf(tmp_ctx, "%s$", lpcfg_netbios_name(lp_ctx)); + cli_credentials_set_password(cred, secrets_tdb_password, CRED_SPECIFIED); + cli_credentials_set_old_password(cred, secrets_tdb_old_password, CRED_SPECIFIED); + cli_credentials_set_domain(cred, domain, CRED_SPECIFIED); + if (strequal(domain, lpcfg_workgroup(lp_ctx))) { + cli_credentials_set_realm(cred, lpcfg_realm(lp_ctx), CRED_SPECIFIED); + + switch (server_role) { + case ROLE_DOMAIN_MEMBER: + if (security != SEC_ADS) { + break; + } + + FALL_THROUGH; + case ROLE_ACTIVE_DIRECTORY_DC: + use_kerberos = CRED_USE_KERBEROS_DESIRED; + break; + } + } + cli_credentials_set_kerberos_state(cred, + use_kerberos, + CRED_SPECIFIED); + cli_credentials_set_username(cred, machine_account, CRED_SPECIFIED); + cli_credentials_set_password_last_changed_time(cred, secrets_tdb_lct); + cli_credentials_set_secure_channel_type(cred, secrets_tdb_secure_channel_type); + status = NT_STATUS_OK; + } else if (!NT_STATUS_IS_OK(status)) { + if (db_ctx) { + error_string + = talloc_asprintf(cred, + "Failed to fetch machine account password for %s from both " + "secrets.ldb (%s) and from %s", + domain, + error_string == NULL ? "error" : error_string, + dbwrap_name(db_ctx)); + } else { + char *secrets_tdb_path; + + secrets_tdb_path = lpcfg_private_db_path(tmp_ctx, + lp_ctx, + "secrets"); + if (secrets_tdb_path == NULL) { + return NT_STATUS_NO_MEMORY; + } + + error_string = talloc_asprintf(cred, + "Failed to fetch machine account password from " + "secrets.ldb: %s and failed to open %s", + error_string == NULL ? "error" : error_string, + secrets_tdb_path); + } + DEBUG(1, ("Could not find machine account in secrets database: %s: %s\n", + error_string == NULL ? "error" : error_string, + nt_errstr(status))); + /* set anonymous as the fallback, if the machine account won't work */ + cli_credentials_set_anonymous(cred); + } + + TALLOC_FREE(tmp_ctx); + return status; +} + +/** + * Fill in credentials for a particular principal, from the secrets database. + * + * @param cred Credentials structure to fill in + * @retval NTSTATUS error detailing any failure + */ +_PUBLIC_ NTSTATUS cli_credentials_set_stored_principal(struct cli_credentials *cred, + struct loadparm_context *lp_ctx, + const char *serviceprincipal) +{ + NTSTATUS status; + char *filter; + char *error_string = NULL; + /* Bleh, nasty recursion issues: We are setting a machine + * account here, so we don't want the 'pending' flag around + * any more */ + cred->machine_account_pending = false; + filter = talloc_asprintf(cred, SECRETS_PRINCIPAL_SEARCH, + cli_credentials_get_realm(cred), + cli_credentials_get_domain(cred), + serviceprincipal); + status = cli_credentials_set_secrets_lct(cred, lp_ctx, NULL, + SECRETS_PRINCIPALS_DN, filter, + 0, NULL, &error_string); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Could not find %s principal in secrets database: %s: %s\n", + serviceprincipal, nt_errstr(status), + error_string ? error_string : "<no error>")); + } + return status; +} + +/** + * Ask that when required, the credentials system will be filled with + * machine trust account, from the secrets database. + * + * @param cred Credentials structure to fill in + * @note This function is used to call the above function after, rather + * than during, popt processing. + * + */ +_PUBLIC_ void cli_credentials_set_machine_account_pending(struct cli_credentials *cred, + struct loadparm_context *lp_ctx) +{ + cred->machine_account_pending = true; + cred->machine_account_pending_lp_ctx = lp_ctx; +} + + diff --git a/auth/credentials/pycredentials.c b/auth/credentials/pycredentials.c new file mode 100644 index 0000000..013d295 --- /dev/null +++ b/auth/credentials/pycredentials.c @@ -0,0 +1,1626 @@ +/* + Unix SMB/CIFS implementation. + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 2007 + + 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 <Python.h> +#include "python/py3compat.h" +#include "includes.h" +#include "python/modules.h" +#include "pycredentials.h" +#include "param/param.h" +#include "auth/credentials/credentials_internal.h" +#include "librpc/gen_ndr/samr.h" /* for struct samr_Password */ +#include "librpc/gen_ndr/netlogon.h" +#include "libcli/util/pyerrors.h" +#include "libcli/auth/libcli_auth.h" +#include "param/pyparam.h" +#include <tevent.h> +#include "libcli/auth/libcli_auth.h" +#include "system/kerberos.h" +#include "auth/kerberos/kerberos.h" +#include "libcli/smb/smb_constants.h" + +void initcredentials(void); + +static PyObject *py_creds_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + return pytalloc_steal(type, cli_credentials_init(NULL)); +} + +static PyObject *py_creds_get_username(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyString_FromStringOrNULL(cli_credentials_get_username(creds)); +} + +static PyObject *py_creds_set_username(PyObject *self, PyObject *args) +{ + char *newval; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|i", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + return PyBool_FromLong(cli_credentials_set_username(creds, newval, obt)); +} + +static PyObject *py_creds_get_ntlm_username_domain(PyObject *self, PyObject *unused) +{ + TALLOC_CTX *frame = talloc_stackframe(); + const char *user = NULL; + const char *domain = NULL; + PyObject *ret = NULL; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + cli_credentials_get_ntlm_username_domain(creds, + frame, &user, &domain); + ret = Py_BuildValue("(ss)", + user, + domain); + + TALLOC_FREE(frame); + return ret; +} + +static PyObject *py_creds_get_ntlm_response(PyObject *self, PyObject *args, PyObject *kwargs) +{ + TALLOC_CTX *frame = talloc_stackframe(); + PyObject *ret = NULL; + int flags; + struct timeval tv_now; + NTTIME server_timestamp; + DATA_BLOB challenge = data_blob_null; + DATA_BLOB target_info = data_blob_null; + NTSTATUS status; + DATA_BLOB lm_response = data_blob_null; + DATA_BLOB nt_response = data_blob_null; + DATA_BLOB lm_session_key = data_blob_null; + DATA_BLOB nt_session_key = data_blob_null; + const char *kwnames[] = { "flags", "challenge", + "target_info", + NULL }; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + tv_now = timeval_current(); + server_timestamp = timeval_to_nttime(&tv_now); + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "is#|s#", + discard_const_p(char *, kwnames), + &flags, + &challenge.data, + &challenge.length, + &target_info.data, + &target_info.length)) { + return NULL; + } + + status = cli_credentials_get_ntlm_response(creds, + frame, &flags, + challenge, + &server_timestamp, + target_info, + &lm_response, &nt_response, + &lm_session_key, &nt_session_key); + + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetNTSTATUS(status); + TALLOC_FREE(frame); + return NULL; + } + + ret = Py_BuildValue("{sis" PYARG_BYTES_LEN "s" PYARG_BYTES_LEN + "s" PYARG_BYTES_LEN "s" PYARG_BYTES_LEN "}", + "flags", flags, + "lm_response", + (const char *)lm_response.data, lm_response.length, + "nt_response", + (const char *)nt_response.data, nt_response.length, + "lm_session_key", + (const char *)lm_session_key.data, lm_session_key.length, + "nt_session_key", + (const char *)nt_session_key.data, nt_session_key.length); + TALLOC_FREE(frame); + return ret; +} + +static PyObject *py_creds_get_principal(PyObject *self, PyObject *unused) +{ + TALLOC_CTX *frame = talloc_stackframe(); + PyObject *ret = NULL; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + ret = PyString_FromStringOrNULL(cli_credentials_get_principal(creds, frame)); + TALLOC_FREE(frame); + return ret; +} + +static PyObject *py_creds_set_principal(PyObject *self, PyObject *args) +{ + char *newval; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|i", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + return PyBool_FromLong(cli_credentials_set_principal(creds, newval, obt)); +} + +static PyObject *py_creds_get_password(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyString_FromStringOrNULL(cli_credentials_get_password(creds)); +} + +static PyObject *py_creds_set_password(PyObject *self, PyObject *args) +{ + const char *newval = NULL; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + PyObject *result = NULL; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, PYARG_STR_UNI"|i", "utf8", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + result = PyBool_FromLong(cli_credentials_set_password(creds, newval, obt)); + PyMem_Free(discard_const_p(void*, newval)); + return result; +} + +static PyObject *py_creds_set_utf16_password(PyObject *self, PyObject *args) +{ + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + PyObject *newval = NULL; + DATA_BLOB blob = data_blob_null; + Py_ssize_t size = 0; + int result; + bool ok; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "O|i", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + result = PyBytes_AsStringAndSize(newval, (char **)&blob.data, &size); + if (result != 0) { + PyErr_SetString(PyExc_RuntimeError, "Failed to convert passed value to Bytes"); + return NULL; + } + blob.length = size; + + ok = cli_credentials_set_utf16_password(creds, + &blob, obt); + + return PyBool_FromLong(ok); +} + +static PyObject *py_creds_get_old_password(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyString_FromStringOrNULL(cli_credentials_get_old_password(creds)); +} + +static PyObject *py_creds_set_old_password(PyObject *self, PyObject *args) +{ + char *oldval; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|i", &oldval, &_obt)) { + return NULL; + } + obt = _obt; + + return PyBool_FromLong(cli_credentials_set_old_password(creds, oldval, obt)); +} + +static PyObject *py_creds_set_old_utf16_password(PyObject *self, PyObject *args) +{ + PyObject *oldval = NULL; + DATA_BLOB blob = data_blob_null; + Py_ssize_t size = 0; + int result; + bool ok; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "O", &oldval)) { + return NULL; + } + + result = PyBytes_AsStringAndSize(oldval, (char **)&blob.data, &size); + if (result != 0) { + PyErr_SetString(PyExc_RuntimeError, "Failed to convert passed value to Bytes"); + return NULL; + } + blob.length = size; + + ok = cli_credentials_set_old_utf16_password(creds, + &blob); + + return PyBool_FromLong(ok); +} + +static PyObject *py_creds_get_domain(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyString_FromStringOrNULL(cli_credentials_get_domain(creds)); +} + +static PyObject *py_creds_set_domain(PyObject *self, PyObject *args) +{ + char *newval; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|i", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + return PyBool_FromLong(cli_credentials_set_domain(creds, newval, obt)); +} + +static PyObject *py_creds_get_realm(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyString_FromStringOrNULL(cli_credentials_get_realm(creds)); +} + +static PyObject *py_creds_set_realm(PyObject *self, PyObject *args) +{ + char *newval; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|i", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + return PyBool_FromLong(cli_credentials_set_realm(creds, newval, obt)); +} + +static PyObject *py_creds_get_bind_dn(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyString_FromStringOrNULL(cli_credentials_get_bind_dn(creds)); +} + +static PyObject *py_creds_set_bind_dn(PyObject *self, PyObject *args) +{ + char *newval; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + if (!PyArg_ParseTuple(args, "s", &newval)) + return NULL; + + return PyBool_FromLong(cli_credentials_set_bind_dn(creds, newval)); +} + +static PyObject *py_creds_get_workstation(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyString_FromStringOrNULL(cli_credentials_get_workstation(creds)); +} + +static PyObject *py_creds_set_workstation(PyObject *self, PyObject *args) +{ + char *newval; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|i", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + return PyBool_FromLong(cli_credentials_set_workstation(creds, newval, obt)); +} + +static PyObject *py_creds_is_anonymous(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyBool_FromLong(cli_credentials_is_anonymous(creds)); +} + +static PyObject *py_creds_set_anonymous(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + cli_credentials_set_anonymous(creds); + Py_RETURN_NONE; +} + +static PyObject *py_creds_authentication_requested(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyBool_FromLong(cli_credentials_authentication_requested(creds)); +} + +static PyObject *py_creds_wrong_password(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyBool_FromLong(cli_credentials_wrong_password(creds)); +} + +static PyObject *py_creds_set_cmdline_callbacks(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyBool_FromLong(cli_credentials_set_cmdline_callbacks(creds)); +} + +static PyObject *py_creds_parse_string(PyObject *self, PyObject *args) +{ + char *newval; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|i", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + cli_credentials_parse_string(creds, newval, obt); + Py_RETURN_NONE; +} + +static PyObject *py_creds_parse_file(PyObject *self, PyObject *args) +{ + char *newval; + enum credentials_obtained obt = CRED_SPECIFIED; + int _obt = obt; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|i", &newval, &_obt)) { + return NULL; + } + obt = _obt; + + cli_credentials_parse_file(creds, newval, obt); + Py_RETURN_NONE; +} + +static PyObject *py_cli_credentials_set_password_will_be_nt_hash(PyObject *self, PyObject *args) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + PyObject *py_val = NULL; + bool val = false; + + if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &py_val)) { + return NULL; + } + val = PyObject_IsTrue(py_val); + + cli_credentials_set_password_will_be_nt_hash(creds, val); + Py_RETURN_NONE; +} + +static PyObject *py_creds_get_nt_hash(PyObject *self, PyObject *unused) +{ + PyObject *ret; + struct samr_Password *ntpw = NULL; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + ntpw = cli_credentials_get_nt_hash(creds, creds); + + ret = PyBytes_FromStringAndSize(discard_const_p(char, ntpw->hash), 16); + TALLOC_FREE(ntpw); + return ret; +} + +static PyObject *py_creds_get_kerberos_state(PyObject *self, PyObject *unused) +{ + int state; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + state = cli_credentials_get_kerberos_state(creds); + return PyLong_FromLong(state); +} + +static PyObject *py_creds_set_kerberos_state(PyObject *self, PyObject *args) +{ + int state; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + if (!PyArg_ParseTuple(args, "i", &state)) + return NULL; + + cli_credentials_set_kerberos_state(creds, state, CRED_SPECIFIED); + Py_RETURN_NONE; +} + +static PyObject *py_creds_set_krb_forwardable(PyObject *self, PyObject *args) +{ + int state; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + if (!PyArg_ParseTuple(args, "i", &state)) + return NULL; + + cli_credentials_set_krb_forwardable(creds, state); + Py_RETURN_NONE; +} + + +static PyObject *py_creds_get_forced_sasl_mech(PyObject *self, PyObject *unused) +{ + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + return PyString_FromStringOrNULL(cli_credentials_get_forced_sasl_mech(creds)); +} + +static PyObject *py_creds_set_forced_sasl_mech(PyObject *self, PyObject *args) +{ + char *newval; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s", &newval)) { + return NULL; + } + + cli_credentials_set_forced_sasl_mech(creds, newval); + Py_RETURN_NONE; +} + +static PyObject *py_creds_set_conf(PyObject *self, PyObject *args) +{ + PyObject *py_lp_ctx = Py_None; + struct loadparm_context *lp_ctx; + TALLOC_CTX *mem_ctx; + struct cli_credentials *creds; + bool ok; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "|O", &py_lp_ctx)) { + return NULL; + } + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + talloc_free(mem_ctx); + return NULL; + } + + ok = cli_credentials_set_conf(creds, lp_ctx); + talloc_free(mem_ctx); + if (!ok) { + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_creds_guess(PyObject *self, PyObject *args) +{ + PyObject *py_lp_ctx = Py_None; + struct loadparm_context *lp_ctx; + TALLOC_CTX *mem_ctx; + struct cli_credentials *creds; + bool ok; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "|O", &py_lp_ctx)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + talloc_free(mem_ctx); + return NULL; + } + + ok = cli_credentials_guess(creds, lp_ctx); + talloc_free(mem_ctx); + if (!ok) { + return NULL; + } + + Py_RETURN_NONE; +} + +static PyObject *py_creds_set_machine_account(PyObject *self, PyObject *args) +{ + PyObject *py_lp_ctx = Py_None; + struct loadparm_context *lp_ctx; + NTSTATUS status; + struct cli_credentials *creds; + TALLOC_CTX *mem_ctx; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "|O", &py_lp_ctx)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + talloc_free(mem_ctx); + return NULL; + } + + status = cli_credentials_set_machine_account(creds, lp_ctx); + talloc_free(mem_ctx); + + PyErr_NTSTATUS_IS_ERR_RAISE(status); + + Py_RETURN_NONE; +} + +static PyObject *PyCredentialCacheContainer_from_ccache_container(struct ccache_container *ccc) +{ + return pytalloc_reference(&PyCredentialCacheContainer, ccc); +} + + +static PyObject *py_creds_get_named_ccache(PyObject *self, PyObject *args) +{ + PyObject *py_lp_ctx = Py_None; + char *ccache_name = NULL; + struct loadparm_context *lp_ctx; + struct ccache_container *ccc; + struct tevent_context *event_ctx; + int ret; + const char *error_string; + struct cli_credentials *creds; + TALLOC_CTX *mem_ctx; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "|Os", &py_lp_ctx, &ccache_name)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + talloc_free(mem_ctx); + return NULL; + } + + event_ctx = samba_tevent_context_init(mem_ctx); + + ret = cli_credentials_get_named_ccache(creds, event_ctx, lp_ctx, + ccache_name, &ccc, &error_string); + talloc_unlink(mem_ctx, lp_ctx); + if (ret == 0) { + talloc_steal(ccc, event_ctx); + talloc_free(mem_ctx); + return PyCredentialCacheContainer_from_ccache_container(ccc); + } + + PyErr_SetString(PyExc_RuntimeError, error_string?error_string:"NULL"); + + talloc_free(mem_ctx); + return NULL; +} + +static PyObject *py_creds_set_named_ccache(PyObject *self, PyObject *args) +{ + struct loadparm_context *lp_ctx = NULL; + enum credentials_obtained obt = CRED_SPECIFIED; + const char *error_string = NULL; + TALLOC_CTX *mem_ctx = NULL; + char *newval = NULL; + PyObject *py_lp_ctx = Py_None; + int _obt = obt; + int ret; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|iO", &newval, &_obt, &py_lp_ctx)) + return NULL; + obt = _obt; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + talloc_free(mem_ctx); + return NULL; + } + + ret = cli_credentials_set_ccache(creds, + lp_ctx, + newval, obt, + &error_string); + + if (ret != 0) { + PyErr_SetString(PyExc_RuntimeError, + error_string != NULL ? error_string : "NULL"); + talloc_free(mem_ctx); + return NULL; + } + + talloc_free(mem_ctx); + Py_RETURN_NONE; +} + +static PyObject *py_creds_set_gensec_features(PyObject *self, PyObject *args) +{ + unsigned int gensec_features; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "I", &gensec_features)) + return NULL; + + cli_credentials_set_gensec_features(creds, + gensec_features, + CRED_SPECIFIED); + + Py_RETURN_NONE; +} + +static PyObject *py_creds_get_gensec_features(PyObject *self, PyObject *args) +{ + unsigned int gensec_features; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + gensec_features = cli_credentials_get_gensec_features(creds); + return PyLong_FromLong(gensec_features); +} + +static PyObject *py_creds_new_client_authenticator(PyObject *self, + PyObject *args) +{ + struct netr_Authenticator auth; + struct cli_credentials *creds = NULL; + struct netlogon_creds_CredentialState *nc = NULL; + PyObject *ret = NULL; + NTSTATUS status; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to get credentials from python"); + return NULL; + } + + nc = creds->netlogon_creds; + if (nc == NULL) { + PyErr_SetString(PyExc_ValueError, + "No netlogon credentials cannot make " + "client authenticator"); + return NULL; + } + + status = netlogon_creds_client_authenticator(nc, &auth); + if (!NT_STATUS_IS_OK(status)) { + PyErr_SetString(PyExc_ValueError, + "Failed to create client authenticator"); + return NULL; + } + + ret = Py_BuildValue("{s"PYARG_BYTES_LEN"si}", + "credential", + (const char *) &auth.cred, sizeof(auth.cred), + "timestamp", auth.timestamp); + return ret; +} + +static PyObject *py_creds_set_secure_channel_type(PyObject *self, PyObject *args) +{ + unsigned int channel_type; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "I", &channel_type)) + return NULL; + + cli_credentials_set_secure_channel_type( + creds, + channel_type); + + Py_RETURN_NONE; +} + +static PyObject *py_creds_get_secure_channel_type(PyObject *self, PyObject *args) +{ + enum netr_SchannelType channel_type = SEC_CHAN_NULL; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + channel_type = cli_credentials_get_secure_channel_type(creds); + + return PyLong_FromLong(channel_type); +} + +static PyObject *py_creds_get_aes256_key(PyObject *self, PyObject *args) +{ + struct loadparm_context *lp_ctx = NULL; + TALLOC_CTX *mem_ctx = NULL; + PyObject *py_lp_ctx = Py_None; + const char *salt = NULL; + DATA_BLOB aes_256; + int code; + PyObject *ret = NULL; + struct cli_credentials *creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s|O", &salt, &py_lp_ctx)) + return NULL; + + mem_ctx = talloc_new(NULL); + if (mem_ctx == NULL) { + PyErr_NoMemory(); + return NULL; + } + + lp_ctx = lpcfg_from_py_object(mem_ctx, py_lp_ctx); + if (lp_ctx == NULL) { + talloc_free(mem_ctx); + return NULL; + } + + code = cli_credentials_get_aes256_key(creds, + mem_ctx, + lp_ctx, + salt, + &aes_256); + if (code != 0) { + PyErr_SetString(PyExc_RuntimeError, + "Failed to generate AES256 key"); + talloc_free(mem_ctx); + return NULL; + } + + ret = PyBytes_FromStringAndSize((const char *)aes_256.data, + aes_256.length); + talloc_free(mem_ctx); + return ret; +} + +static PyObject *py_creds_encrypt_netr_crypt_password(PyObject *self, + PyObject *args) +{ + DATA_BLOB data = data_blob_null; + struct cli_credentials *creds = NULL; + struct netr_CryptPassword *pwd = NULL; + NTSTATUS status; + PyObject *py_cp = Py_None; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "O", &py_cp)) { + return NULL; + } + + pwd = pytalloc_get_type(py_cp, struct netr_CryptPassword); + if (pwd == NULL) { + /* pytalloc_get_type sets TypeError */ + return NULL; + } + data.length = sizeof(struct netr_CryptPassword); + data.data = (uint8_t *)pwd; + status = netlogon_creds_session_encrypt(creds->netlogon_creds, data); + + PyErr_NTSTATUS_IS_ERR_RAISE(status); + + Py_RETURN_NONE; +} + +static PyObject *py_creds_encrypt_samr_password(PyObject *self, + PyObject *args) +{ + DATA_BLOB data = data_blob_null; + struct cli_credentials *creds = NULL; + struct samr_Password *pwd = NULL; + NTSTATUS status; + PyObject *py_cp = Py_None; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "O", &py_cp)) { + return NULL; + } + + pwd = pytalloc_get_type(py_cp, struct samr_Password); + if (pwd == NULL) { + /* pytalloc_get_type sets TypeError */ + return NULL; + } + data = data_blob_const(pwd->hash, sizeof(pwd->hash)); + status = netlogon_creds_session_encrypt(creds->netlogon_creds, data); + + PyErr_NTSTATUS_IS_ERR_RAISE(status); + + Py_RETURN_NONE; +} + +static PyObject *py_creds_get_smb_signing(PyObject *self, PyObject *unused) +{ + enum smb_signing_setting signing_state; + struct cli_credentials *creds = NULL; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + signing_state = cli_credentials_get_smb_signing(creds); + return PyLong_FromLong(signing_state); +} + +static PyObject *py_creds_set_smb_signing(PyObject *self, PyObject *args) +{ + enum smb_signing_setting signing_state; + struct cli_credentials *creds = NULL; + enum credentials_obtained obt = CRED_SPECIFIED; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + if (!PyArg_ParseTuple(args, "i|i", &signing_state, &obt)) { + return NULL; + } + + switch (signing_state) { + case SMB_SIGNING_DEFAULT: + case SMB_SIGNING_OFF: + case SMB_SIGNING_IF_REQUIRED: + case SMB_SIGNING_DESIRED: + case SMB_SIGNING_REQUIRED: + break; + default: + PyErr_Format(PyExc_TypeError, "Invalid signing state value"); + return NULL; + } + + cli_credentials_set_smb_signing(creds, signing_state, obt); + Py_RETURN_NONE; +} + +static PyObject *py_creds_get_smb_ipc_signing(PyObject *self, PyObject *unused) +{ + enum smb_signing_setting signing_state; + struct cli_credentials *creds = NULL; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + signing_state = cli_credentials_get_smb_ipc_signing(creds); + return PyLong_FromLong(signing_state); +} + +static PyObject *py_creds_set_smb_ipc_signing(PyObject *self, PyObject *args) +{ + enum smb_signing_setting signing_state; + struct cli_credentials *creds = NULL; + enum credentials_obtained obt = CRED_SPECIFIED; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + if (!PyArg_ParseTuple(args, "i|i", &signing_state, &obt)) { + return NULL; + } + + switch (signing_state) { + case SMB_SIGNING_DEFAULT: + case SMB_SIGNING_OFF: + case SMB_SIGNING_IF_REQUIRED: + case SMB_SIGNING_DESIRED: + case SMB_SIGNING_REQUIRED: + break; + default: + PyErr_Format(PyExc_TypeError, "Invalid signing state value"); + return NULL; + } + + cli_credentials_set_smb_ipc_signing(creds, signing_state, obt); + Py_RETURN_NONE; +} + +static PyObject *py_creds_get_smb_encryption(PyObject *self, PyObject *unused) +{ + enum smb_encryption_setting encryption_state; + struct cli_credentials *creds = NULL; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + + encryption_state = cli_credentials_get_smb_encryption(creds); + return PyLong_FromLong(encryption_state); +} + +static PyObject *py_creds_set_smb_encryption(PyObject *self, PyObject *args) +{ + enum smb_encryption_setting encryption_state; + struct cli_credentials *creds = NULL; + enum credentials_obtained obt = CRED_SPECIFIED; + + creds = PyCredentials_AsCliCredentials(self); + if (creds == NULL) { + PyErr_Format(PyExc_TypeError, "Credentials expected"); + return NULL; + } + if (!PyArg_ParseTuple(args, "i|i", &encryption_state, &obt)) { + return NULL; + } + + switch (encryption_state) { + case SMB_ENCRYPTION_DEFAULT: + case SMB_ENCRYPTION_OFF: + case SMB_ENCRYPTION_IF_REQUIRED: + case SMB_ENCRYPTION_DESIRED: + case SMB_ENCRYPTION_REQUIRED: + break; + default: + PyErr_Format(PyExc_TypeError, "Invalid encryption state value"); + return NULL; + } + + (void)cli_credentials_set_smb_encryption(creds, encryption_state, obt); + Py_RETURN_NONE; +} + +static PyMethodDef py_creds_methods[] = { + { + .ml_name = "get_username", + .ml_meth = py_creds_get_username, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_username() -> username\nObtain username.", + }, + { + .ml_name = "set_username", + .ml_meth = py_creds_set_username, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_username(name[, credentials.SPECIFIED]) -> None\n" + "Change username.", + }, + { + .ml_name = "get_principal", + .ml_meth = py_creds_get_principal, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_principal() -> user@realm\nObtain user principal.", + }, + { + .ml_name = "set_principal", + .ml_meth = py_creds_set_principal, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_principal(name[, credentials.SPECIFIED]) -> None\n" + "Change principal.", + }, + { + .ml_name = "get_password", + .ml_meth = py_creds_get_password, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_password() -> password\n" + "Obtain password.", + }, + { + .ml_name = "get_ntlm_username_domain", + .ml_meth = py_creds_get_ntlm_username_domain, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_ntlm_username_domain() -> (domain, username)\n" + "Obtain NTLM username and domain, split up either as (DOMAIN, user) or (\"\", \"user@realm\").", + }, + { + .ml_name = "get_ntlm_response", + .ml_meth = PY_DISCARD_FUNC_SIG(PyCFunction, + py_creds_get_ntlm_response), + .ml_flags = METH_VARARGS | METH_KEYWORDS, + .ml_doc = "S.get_ntlm_response" + "(flags, challenge[, target_info]) -> " + "(flags, lm_response, nt_response, lm_session_key, nt_session_key)\n" + "Obtain LM or NTLM response.", + }, + { + .ml_name = "set_password", + .ml_meth = py_creds_set_password, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_password(password[, credentials.SPECIFIED]) -> None\n" + "Change password.", + }, + { + .ml_name = "set_utf16_password", + .ml_meth = py_creds_set_utf16_password, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_utf16_password(password[, credentials.SPECIFIED]) -> None\n" + "Change password.", + }, + { + .ml_name = "get_old_password", + .ml_meth = py_creds_get_old_password, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_old_password() -> password\n" + "Obtain old password.", + }, + { + .ml_name = "set_old_password", + .ml_meth = py_creds_set_old_password, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_old_password(password[, credentials.SPECIFIED]) -> None\n" + "Change old password.", + }, + { + .ml_name = "set_old_utf16_password", + .ml_meth = py_creds_set_old_utf16_password, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_old_utf16_password(password[, credentials.SPECIFIED]) -> None\n" + "Change old password.", + }, + { + .ml_name = "get_domain", + .ml_meth = py_creds_get_domain, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_domain() -> domain\n" + "Obtain domain name.", + }, + { + .ml_name = "set_domain", + .ml_meth = py_creds_set_domain, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_domain(domain[, credentials.SPECIFIED]) -> None\n" + "Change domain name.", + }, + { + .ml_name = "get_realm", + .ml_meth = py_creds_get_realm, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_realm() -> realm\n" + "Obtain realm name.", + }, + { + .ml_name = "set_realm", + .ml_meth = py_creds_set_realm, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_realm(realm[, credentials.SPECIFIED]) -> None\n" + "Change realm name.", + }, + { + .ml_name = "get_bind_dn", + .ml_meth = py_creds_get_bind_dn, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_bind_dn() -> bind dn\n" + "Obtain bind DN.", + }, + { + .ml_name = "set_bind_dn", + .ml_meth = py_creds_set_bind_dn, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_bind_dn(bind_dn) -> None\n" + "Change bind DN.", + }, + { + .ml_name = "is_anonymous", + .ml_meth = py_creds_is_anonymous, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "set_anonymous", + .ml_meth = py_creds_set_anonymous, + .ml_flags = METH_NOARGS, + .ml_doc = "S.set_anonymous() -> None\n" + "Use anonymous credentials.", + }, + { + .ml_name = "get_workstation", + .ml_meth = py_creds_get_workstation, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "set_workstation", + .ml_meth = py_creds_set_workstation, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "authentication_requested", + .ml_meth = py_creds_authentication_requested, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "wrong_password", + .ml_meth = py_creds_wrong_password, + .ml_flags = METH_NOARGS, + .ml_doc = "S.wrong_password() -> bool\n" + "Indicate the returned password was incorrect.", + }, + { + .ml_name = "set_cmdline_callbacks", + .ml_meth = py_creds_set_cmdline_callbacks, + .ml_flags = METH_NOARGS, + .ml_doc = "S.set_cmdline_callbacks() -> bool\n" + "Use command-line to obtain credentials not explicitly set.", + }, + { + .ml_name = "parse_string", + .ml_meth = py_creds_parse_string, + .ml_flags = METH_VARARGS, + .ml_doc = "S.parse_string(text[, credentials.SPECIFIED]) -> None\n" + "Parse credentials string.", + }, + { + .ml_name = "parse_file", + .ml_meth = py_creds_parse_file, + .ml_flags = METH_VARARGS, + .ml_doc = "S.parse_file(filename[, credentials.SPECIFIED]) -> None\n" + "Parse credentials file.", + }, + { + .ml_name = "set_password_will_be_nt_hash", + .ml_meth = py_cli_credentials_set_password_will_be_nt_hash, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_password_will_be_nt_hash(bool) -> None\n" + "Alters the behaviour of S.set_password() " + "to expect the NTHASH as hexstring.", + }, + { + .ml_name = "get_nt_hash", + .ml_meth = py_creds_get_nt_hash, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "get_kerberos_state", + .ml_meth = py_creds_get_kerberos_state, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "set_kerberos_state", + .ml_meth = py_creds_set_kerberos_state, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "set_krb_forwardable", + .ml_meth = py_creds_set_krb_forwardable, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "set_conf", + .ml_meth = py_creds_set_conf, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "guess", + .ml_meth = py_creds_guess, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "set_machine_account", + .ml_meth = py_creds_set_machine_account, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "get_named_ccache", + .ml_meth = py_creds_get_named_ccache, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "set_named_ccache", + .ml_meth = py_creds_set_named_ccache, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_named_ccache(krb5_ccache_name, obtained, lp) -> None\n" + "Set credentials to KRB5 Credentials Cache (by name).", + }, + { + .ml_name = "set_gensec_features", + .ml_meth = py_creds_set_gensec_features, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "get_gensec_features", + .ml_meth = py_creds_get_gensec_features, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "get_forced_sasl_mech", + .ml_meth = py_creds_get_forced_sasl_mech, + .ml_flags = METH_NOARGS, + .ml_doc = "S.get_forced_sasl_mech() -> SASL mechanism\nObtain forced SASL mechanism.", + }, + { + .ml_name = "set_forced_sasl_mech", + .ml_meth = py_creds_set_forced_sasl_mech, + .ml_flags = METH_VARARGS, + .ml_doc = "S.set_forced_sasl_mech(name) -> None\n" + "Set forced SASL mechanism.", + }, + { + .ml_name = "new_client_authenticator", + .ml_meth = py_creds_new_client_authenticator, + .ml_flags = METH_NOARGS, + .ml_doc = "S.new_client_authenticator() -> Authenticator\n" + "Get a new client NETLOGON_AUTHENTICATOR"}, + { + .ml_name = "set_secure_channel_type", + .ml_meth = py_creds_set_secure_channel_type, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "get_secure_channel_type", + .ml_meth = py_creds_get_secure_channel_type, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "get_aes256_key", + .ml_meth = py_creds_get_aes256_key, + .ml_flags = METH_VARARGS, + .ml_doc = "S.get_aes256_key(salt[, lp]) -> bytes\n" + "Generate an AES256 key using the current password and\n" + "the specified salt", + }, + { + .ml_name = "encrypt_netr_crypt_password", + .ml_meth = py_creds_encrypt_netr_crypt_password, + .ml_flags = METH_VARARGS, + .ml_doc = "S.encrypt_netr_crypt_password(password) -> None\n" + "Encrypt the supplied password using the session key and\n" + "the negotiated encryption algorithm in place\n" + "i.e. it overwrites the original data"}, + { + .ml_name = "encrypt_samr_password", + .ml_meth = py_creds_encrypt_samr_password, + .ml_flags = METH_VARARGS, + .ml_doc = "S.encrypt_samr_password(password) -> None\n" + "Encrypt the supplied password using the session key and\n" + "the negotiated encryption algorithm in place\n" + "i.e. it overwrites the original data" + }, + { + .ml_name = "get_smb_signing", + .ml_meth = py_creds_get_smb_signing, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "set_smb_signing", + .ml_meth = py_creds_set_smb_signing, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "get_smb_ipc_signing", + .ml_meth = py_creds_get_smb_ipc_signing, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "set_smb_ipc_signing", + .ml_meth = py_creds_set_smb_ipc_signing, + .ml_flags = METH_VARARGS, + }, + { + .ml_name = "get_smb_encryption", + .ml_meth = py_creds_get_smb_encryption, + .ml_flags = METH_NOARGS, + }, + { + .ml_name = "set_smb_encryption", + .ml_meth = py_creds_set_smb_encryption, + .ml_flags = METH_VARARGS, + }, + { .ml_name = NULL } +}; + +static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + .m_name = "credentials", + .m_doc = "Credentials management.", + .m_size = -1, + .m_methods = py_creds_methods, +}; + +PyTypeObject PyCredentials = { + .tp_name = "credentials.Credentials", + .tp_new = py_creds_new, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_methods = py_creds_methods, +}; + +static PyObject *py_ccache_name(PyObject *self, PyObject *unused) +{ + struct ccache_container *ccc = NULL; + char *name = NULL; + PyObject *py_name = NULL; + int ret; + + ccc = pytalloc_get_type(self, struct ccache_container); + + ret = krb5_cc_get_full_name(ccc->smb_krb5_context->krb5_context, + ccc->ccache, &name); + if (ret == 0) { + py_name = PyString_FromStringOrNULL(name); + SAFE_FREE(name); + } else { + PyErr_SetString(PyExc_RuntimeError, + "Failed to get ccache name"); + return NULL; + } + return py_name; +} + +static PyMethodDef py_ccache_container_methods[] = { + { "get_name", py_ccache_name, METH_NOARGS, + "S.get_name() -> name\nObtain KRB5 credentials cache name." }, + {0} +}; + +PyTypeObject PyCredentialCacheContainer = { + .tp_name = "credentials.CredentialCacheContainer", + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = py_ccache_container_methods, +}; + +MODULE_INIT_FUNC(credentials) +{ + PyObject *m; + if (pytalloc_BaseObject_PyType_Ready(&PyCredentials) < 0) + return NULL; + + if (pytalloc_BaseObject_PyType_Ready(&PyCredentialCacheContainer) < 0) + return NULL; + + m = PyModule_Create(&moduledef); + if (m == NULL) + return NULL; + + PyModule_AddObject(m, "UNINITIALISED", PyLong_FromLong(CRED_UNINITIALISED)); + PyModule_AddObject(m, "SMB_CONF", PyLong_FromLong(CRED_SMB_CONF)); + PyModule_AddObject(m, "CALLBACK", PyLong_FromLong(CRED_CALLBACK)); + PyModule_AddObject(m, "GUESS_ENV", PyLong_FromLong(CRED_GUESS_ENV)); + PyModule_AddObject(m, "GUESS_FILE", PyLong_FromLong(CRED_GUESS_FILE)); + PyModule_AddObject(m, "CALLBACK_RESULT", PyLong_FromLong(CRED_CALLBACK_RESULT)); + PyModule_AddObject(m, "SPECIFIED", PyLong_FromLong(CRED_SPECIFIED)); + + PyModule_AddObject(m, "AUTO_USE_KERBEROS", PyLong_FromLong(CRED_USE_KERBEROS_DESIRED)); + PyModule_AddObject(m, "DONT_USE_KERBEROS", PyLong_FromLong(CRED_USE_KERBEROS_DISABLED)); + PyModule_AddObject(m, "MUST_USE_KERBEROS", PyLong_FromLong(CRED_USE_KERBEROS_REQUIRED)); + + PyModule_AddObject(m, "AUTO_KRB_FORWARDABLE", PyLong_FromLong(CRED_AUTO_KRB_FORWARDABLE)); + PyModule_AddObject(m, "NO_KRB_FORWARDABLE", PyLong_FromLong(CRED_NO_KRB_FORWARDABLE)); + PyModule_AddObject(m, "FORCE_KRB_FORWARDABLE", PyLong_FromLong(CRED_FORCE_KRB_FORWARDABLE)); + PyModule_AddObject(m, "CLI_CRED_NTLM2", PyLong_FromLong(CLI_CRED_NTLM2)); + PyModule_AddObject(m, "CLI_CRED_NTLMv2_AUTH", PyLong_FromLong(CLI_CRED_NTLMv2_AUTH)); + PyModule_AddObject(m, "CLI_CRED_LANMAN_AUTH", PyLong_FromLong(CLI_CRED_LANMAN_AUTH)); + PyModule_AddObject(m, "CLI_CRED_NTLM_AUTH", PyLong_FromLong(CLI_CRED_NTLM_AUTH)); + PyModule_AddObject(m, "CLI_CRED_CLEAR_AUTH", PyLong_FromLong(CLI_CRED_CLEAR_AUTH)); + + PyModule_AddObject(m, "SMB_SIGNING_DEFAULT", PyLong_FromLong(SMB_SIGNING_DEFAULT)); + PyModule_AddObject(m, "SMB_SIGNING_OFF", PyLong_FromLong(SMB_SIGNING_OFF)); + PyModule_AddObject(m, "SMB_SIGNING_IF_REQUIRED", PyLong_FromLong(SMB_SIGNING_IF_REQUIRED)); + PyModule_AddObject(m, "SMB_SIGNING_DESIRED", PyLong_FromLong(SMB_SIGNING_DESIRED)); + PyModule_AddObject(m, "SMB_SIGNING_REQUIRED", PyLong_FromLong(SMB_SIGNING_REQUIRED)); + + PyModule_AddObject(m, "SMB_ENCRYPTION_DEFAULT", PyLong_FromLong(SMB_ENCRYPTION_DEFAULT)); + PyModule_AddObject(m, "SMB_ENCRYPTION_OFF", PyLong_FromLong(SMB_ENCRYPTION_OFF)); + PyModule_AddObject(m, "SMB_ENCRYPTION_IF_REQUIRED", PyLong_FromLong(SMB_ENCRYPTION_IF_REQUIRED)); + PyModule_AddObject(m, "SMB_ENCRYPTION_DESIRED", PyLong_FromLong(SMB_ENCRYPTION_DESIRED)); + PyModule_AddObject(m, "SMB_ENCRYPTION_REQUIRED", PyLong_FromLong(SMB_ENCRYPTION_REQUIRED)); + + Py_INCREF(&PyCredentials); + PyModule_AddObject(m, "Credentials", (PyObject *)&PyCredentials); + Py_INCREF(&PyCredentialCacheContainer); + PyModule_AddObject(m, "CredentialCacheContainer", (PyObject *)&PyCredentialCacheContainer); + return m; +} diff --git a/auth/credentials/pycredentials.h b/auth/credentials/pycredentials.h new file mode 100644 index 0000000..bf6962c --- /dev/null +++ b/auth/credentials/pycredentials.h @@ -0,0 +1,40 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Jelmer Vernooij <jelmer@samba.org> 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 _PYCREDENTIALS_H_ +#define _PYCREDENTIALS_H_ + +#include "auth/credentials/credentials.h" +#include "librpc/rpc/pyrpc_util.h" +#include <pytalloc.h> + +extern PyTypeObject PyCredentials; +extern PyTypeObject PyCredentialCacheContainer; +#define PyCredentials_Check(py_obj) \ + py_check_dcerpc_type(py_obj, "samba.credentials", "Credentials") + +#define PyCredentials_AsCliCredentials(py_obj) \ + (PyCredentials_Check(py_obj) ? \ + pytalloc_get_type(py_obj, struct cli_credentials) : NULL) + +#define cli_credentials_from_py_object(py_obj) \ + ((py_obj == Py_None) ? \ + cli_credentials_init_anon(NULL) : \ + PyCredentials_AsCliCredentials(py_obj)) + +#endif /* _PYCREDENTIALS_H_ */ diff --git a/auth/credentials/samba-credentials.pc.in b/auth/credentials/samba-credentials.pc.in new file mode 100644 index 0000000..d25bf5e --- /dev/null +++ b/auth/credentials/samba-credentials.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ +modulesdir=${prefix}/modules/gensec + +Name: samba-credentials +Description: Credentials management +Requires: samba-util ndr +Version: @PACKAGE_VERSION@ +Libs: @LIB_RPATH@ -L${libdir} -lsamba-credentials +Cflags: -I${includedir} -DHAVE_IMMEDIATE_STRUCTURES=1 diff --git a/auth/credentials/tests/bind.py b/auth/credentials/tests/bind.py new file mode 100755 index 0000000..ce81b73 --- /dev/null +++ b/auth/credentials/tests/bind.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# This is unit with tests for LDAP access checks + +import optparse +import sys +import base64 +import copy + +sys.path.insert(0, "bin/python") +import samba +from samba.tests.subunitrun import SubunitOptions, TestProgram + +import samba.getopt as options + +from ldb import SCOPE_BASE, SCOPE_SUBTREE, LdbError, ERR_INVALID_CREDENTIALS + +from samba import gensec +import samba.tests +from samba.tests import delete_force +from samba.credentials import Credentials + +def create_credential(lp, other): + c = Credentials() + c.guess(lp) + c.set_gensec_features(other.get_gensec_features()) + return c + +parser = optparse.OptionParser("bind [options] <host>") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) + +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +subunitopts = SubunitOptions(parser) +parser.add_option_group(subunitopts) +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +host = args[0] +lp = sambaopts.get_loadparm() +creds = credopts.get_credentials(lp) +creds.set_gensec_features(creds.get_gensec_features() | gensec.FEATURE_SEAL) + +creds_machine = create_credential(lp, creds) +creds_virtual = create_credential(lp, creds) +creds_user1 = create_credential(lp, creds) +creds_user2 = create_credential(lp, creds) +creds_user3 = create_credential(lp, creds) +creds_user4 = create_credential(lp, creds) +creds_user5 = create_credential(lp, creds) +creds_user6 = create_credential(lp, creds) +creds_user7 = create_credential(lp, creds) + +class BindTests(samba.tests.TestCase): + + info_dc = None + + def setUp(self): + super(BindTests, self).setUp() + # fetch rootDSEs + + self.ldb = samba.tests.connect_samdb(host, credentials=creds, lp=lp, ldap_only=True) + + if self.info_dc is None: + res = self.ldb.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + self.assertEqual(len(res), 1) + BindTests.info_dc = res[0] + # cache some of RootDSE props + self.schema_dn = self.info_dc["schemaNamingContext"][0] + self.domain_dn = self.info_dc["defaultNamingContext"][0] + self.config_dn = self.info_dc["configurationNamingContext"][0] + self.realm = self.info_dc["ldapServiceName"][0].split(b'@')[1].decode('utf-8') + self.computer_dn = "CN=centos53,CN=Computers,%s" % self.domain_dn + self.virtual_user_dn = "CN=frednurk@%s,CN=Computers,%s" % (self.realm, self.domain_dn) + self.password = "P@ssw0rd" + self.username = "BindTestUser" + + def tearDown(self): + delete_force(self.ldb, self.virtual_user_dn) + super(BindTests, self).tearDown() + + def test_virtual_email_account_style_bind(self): + # create a user in the style often deployed for authentication + # of virtual email account at a hosting provider + # + # The userPrincipalName must not match the samAccountName for + # this test to detect when the LDAP DN is being double-parsed + # but must be in the user@realm style to allow the account to + # be created + try: + self.ldb.add_ldif(""" +dn: """ + self.virtual_user_dn + """ +cn: frednurk@""" + self.realm + """ +displayName: Fred Nurk +sAMAccountName: frednurk@""" + self.realm + """ +userPrincipalName: frednurk@NOT.""" + self.realm + """ +countryCode: 0 +objectClass: computer +objectClass: organizationalPerson +objectClass: person +objectClass: top +objectClass: user +""") + except LdbError as e: + (num, msg) = e.args + self.fail(f"Failed to create e-mail user: {msg}") + + self.addCleanup(delete_force, self.ldb, self.virtual_user_dn) + try: + self.ldb.modify_ldif(""" +dn: """ + self.virtual_user_dn + """ +changetype: modify +replace: unicodePwd +unicodePwd:: """ + base64.b64encode(u"\"P@ssw0rd\"".encode('utf-16-le')).decode('utf8') + """ +""") + except LdbError as e: + (num, msg) = e.args + self.fail(f"Failed to set password on e-mail user: {msg}") + + self.ldb.enable_account('distinguishedName=%s' % self.virtual_user_dn) + + # do a simple bind and search with the machine account + creds_virtual.set_bind_dn(self.virtual_user_dn) + creds_virtual.set_password(self.password) + print("BindTest with: " + creds_virtual.get_bind_dn()) + try: + ldb_virtual = samba.tests.connect_samdb(host, credentials=creds_virtual, + lp=lp, ldap_only=True) + except LdbError as e: + (num, msg) = e.args + if num != ERR_INVALID_CREDENTIALS: + raise + self.fail(msg) + + res = ldb_virtual.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + def test_computer_account_bind(self): + # create a computer acocount for the test + delete_force(self.ldb, self.computer_dn) + self.ldb.add_ldif(""" +dn: """ + self.computer_dn + """ +cn: CENTOS53 +displayName: CENTOS53$ +name: CENTOS53 +sAMAccountName: CENTOS53$ +countryCode: 0 +objectClass: computer +objectClass: organizationalPerson +objectClass: person +objectClass: top +objectClass: user +codePage: 0 +userAccountControl: 4096 +dNSHostName: centos53.alabala.test +operatingSystemVersion: 5.2 (3790) +operatingSystem: Windows Server 2003 +""") + self.ldb.modify_ldif(""" +dn: """ + self.computer_dn + """ +changetype: modify +replace: unicodePwd +unicodePwd:: """ + base64.b64encode(u"\"P@ssw0rd\"".encode('utf-16-le')).decode('utf8') + """ +""") + + # do a simple bind and search with the machine account + creds_machine.set_bind_dn(self.computer_dn) + creds_machine.set_password(self.password) + print("BindTest with: " + creds_machine.get_bind_dn()) + ldb_machine = samba.tests.connect_samdb(host, credentials=creds_machine, + lp=lp, ldap_only=True) + res = ldb_machine.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + def test_user_account_bind(self): + # create user + self.ldb.newuser(username=self.username, password=self.password) + ldb_res = self.ldb.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=%s)" % self.username, + attrs=["objectSid"]) + self.assertEqual(len(ldb_res), 1) + user_dn = ldb_res[0]["dn"] + self.addCleanup(delete_force, self.ldb, user_dn) + + # do a simple bind and search with the user account in format user@realm + creds_user1.set_bind_dn(self.username + "@" + creds.get_realm()) + creds_user1.set_password(self.password) + print("BindTest with: " + creds_user1.get_bind_dn()) + ldb_user1 = samba.tests.connect_samdb(host, credentials=creds_user1, + lp=lp, ldap_only=True) + res = ldb_user1.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + # do a simple bind and search with the user account in format domain\user + creds_user2.set_bind_dn(creds.get_domain() + "\\" + self.username) + creds_user2.set_password(self.password) + print("BindTest with: " + creds_user2.get_bind_dn()) + ldb_user2 = samba.tests.connect_samdb(host, credentials=creds_user2, + lp=lp, ldap_only=True) + res = ldb_user2.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + # do a simple bind and search with the user account DN + creds_user3.set_bind_dn(str(user_dn)) + creds_user3.set_password(self.password) + print("BindTest with: " + creds_user3.get_bind_dn()) + ldb_user3 = samba.tests.connect_samdb(host, credentials=creds_user3, + lp=lp, ldap_only=True) + res = ldb_user3.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + # do a simple bind and search with the user account SID + creds_user5.set_bind_dn(self.ldb.schema_format_value("objectSid", ldb_res[0]["objectSid"][0]).decode('utf8')) + creds_user5.set_password(self.password) + print("BindTest with: " + creds_user5.get_bind_dn()) + ldb_user5 = samba.tests.connect_samdb(host, credentials=creds_user5, + lp=lp, ldap_only=True) + res = ldb_user5.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + # do a simple bind and search with the canonical name + creds_user6.set_bind_dn(user_dn.canonical_str()) + creds_user6.set_password(self.password) + print("BindTest with: " + creds_user6.get_bind_dn()) + ldb_user6 = samba.tests.connect_samdb(host, credentials=creds_user6, + lp=lp, ldap_only=True) + res = ldb_user6.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + # do a simple bind and search with the extended canonical name + creds_user7.set_bind_dn(user_dn.canonical_ex_str()) + creds_user7.set_password(self.password) + print("BindTest with: " + creds_user7.get_bind_dn()) + ldb_user7 = samba.tests.connect_samdb(host, credentials=creds_user7, + lp=lp, ldap_only=True) + res = ldb_user7.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + def test_user_account_bind_no_domain(self): + # create user + self.ldb.newuser(username=self.username, password=self.password) + ldb_res = self.ldb.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=%s)" % self.username) + self.assertEqual(len(ldb_res), 1) + user_dn = ldb_res[0]["dn"] + self.addCleanup(delete_force, self.ldb, user_dn) + + creds_user4.set_username(self.username) + creds_user4.set_password(self.password) + creds_user4.set_domain('') + creds_user4.set_workstation('') + print("BindTest (no domain) with: " + self.username) + try: + ldb_user4 = samba.tests.connect_samdb(host, credentials=creds_user4, + lp=lp, ldap_only=True) + except: + self.fail("Failed to connect without the domain set") + + res = ldb_user4.search(base="", expression="", scope=SCOPE_BASE, attrs=["*"]) + + +TestProgram(module=__name__, opts=subunitopts) diff --git a/auth/credentials/tests/test_creds.c b/auth/credentials/tests/test_creds.c new file mode 100644 index 0000000..a2f9642 --- /dev/null +++ b/auth/credentials/tests/test_creds.c @@ -0,0 +1,311 @@ +/* + * 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 "lib/replace/replace.h" +#include "auth/credentials/credentials.c" + +static int setup_talloc_context(void **state) +{ + TALLOC_CTX *frame = talloc_stackframe(); + + *state = frame; + return 0; +} + +static int teardown_talloc_context(void **state) +{ + TALLOC_CTX *frame = *state; + TALLOC_FREE(frame); + return 0; +} + +static void torture_creds_init(void **state) +{ + TALLOC_CTX *mem_ctx = *state; + struct cli_credentials *creds = NULL; + const char *username = NULL; + const char *domain = NULL; + const char *password = NULL; + enum credentials_obtained usr_obtained = CRED_UNINITIALISED; + enum credentials_obtained pwd_obtained = CRED_UNINITIALISED; + bool ok; + + creds = cli_credentials_init(mem_ctx); + assert_non_null(creds); + assert_null(creds->username); + assert_int_equal(creds->username_obtained, CRED_UNINITIALISED); + + domain = cli_credentials_get_domain(creds); + assert_null(domain); + ok = cli_credentials_set_domain(creds, "WURST", CRED_SPECIFIED); + assert_true(ok); + assert_int_equal(creds->domain_obtained, CRED_SPECIFIED); + domain = cli_credentials_get_domain(creds); + assert_string_equal(domain, "WURST"); + + username = cli_credentials_get_username(creds); + assert_null(username); + ok = cli_credentials_set_username(creds, "brot", CRED_SPECIFIED); + assert_true(ok); + assert_int_equal(creds->username_obtained, CRED_SPECIFIED); + username = cli_credentials_get_username(creds); + assert_string_equal(username, "brot"); + + username = cli_credentials_get_username_and_obtained(creds, + &usr_obtained); + assert_int_equal(usr_obtained, CRED_SPECIFIED); + assert_string_equal(username, "brot"); + + password = cli_credentials_get_password(creds); + assert_null(password); + ok = cli_credentials_set_password(creds, "SECRET", CRED_SPECIFIED); + assert_true(ok); + assert_int_equal(creds->password_obtained, CRED_SPECIFIED); + password = cli_credentials_get_password(creds); + assert_string_equal(password, "SECRET"); + + password = cli_credentials_get_password_and_obtained(creds, + &pwd_obtained); + assert_int_equal(pwd_obtained, CRED_SPECIFIED); + assert_string_equal(password, "SECRET"); + + /* Run dump to check it works */ + cli_credentials_dump(creds); +} + +static void torture_creds_init_anonymous(void **state) +{ + TALLOC_CTX *mem_ctx = *state; + struct cli_credentials *creds = NULL; + + creds = cli_credentials_init_anon(mem_ctx); + assert_non_null(creds); + + assert_string_equal(creds->domain, ""); + assert_int_equal(creds->domain_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->username, ""); + assert_int_equal(creds->username_obtained, CRED_SPECIFIED); + + assert_null(creds->password); + assert_int_equal(creds->password_obtained, CRED_SPECIFIED); +} + +static void torture_creds_guess(void **state) +{ + TALLOC_CTX *mem_ctx = *state; + struct cli_credentials *creds = NULL; + const char *env_user = getenv("USER"); + bool ok; + + creds = cli_credentials_init(mem_ctx); + assert_non_null(creds); + + setenv("PASSWD", "SECRET", 1); + ok = cli_credentials_guess(creds, NULL); + assert_true(ok); + + assert_string_equal(creds->username, env_user); + assert_int_equal(creds->username_obtained, CRED_GUESS_ENV); + + assert_string_equal(creds->password, "SECRET"); + assert_int_equal(creds->password_obtained, CRED_GUESS_ENV); + unsetenv("PASSWD"); +} + +static void torture_creds_anon_guess(void **state) +{ + TALLOC_CTX *mem_ctx = *state; + struct cli_credentials *creds = NULL; + bool ok; + + creds = cli_credentials_init_anon(mem_ctx); + assert_non_null(creds); + + setenv("PASSWD", "SECRET", 1); + ok = cli_credentials_guess(creds, NULL); + assert_true(ok); + + assert_string_equal(creds->username, ""); + assert_int_equal(creds->username_obtained, CRED_SPECIFIED); + + assert_null(creds->password); + assert_int_equal(creds->password_obtained, CRED_SPECIFIED); + unsetenv("PASSWD"); +} + +static void torture_creds_parse_string(void **state) +{ + TALLOC_CTX *mem_ctx = *state; + struct cli_credentials *creds = NULL; + + creds = cli_credentials_init(mem_ctx); + assert_non_null(creds); + + /* Anonymous */ + cli_credentials_parse_string(creds, "%", CRED_SPECIFIED); + + assert_string_equal(creds->domain, ""); + assert_int_equal(creds->domain_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->username, ""); + assert_int_equal(creds->username_obtained, CRED_SPECIFIED); + + assert_null(creds->password); + assert_int_equal(creds->password_obtained, CRED_SPECIFIED); + + /* Username + password */ + cli_credentials_parse_string(creds, "wurst%BROT", CRED_SPECIFIED); + + assert_string_equal(creds->domain, ""); + assert_int_equal(creds->domain_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->username, "wurst"); + assert_int_equal(creds->username_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->password, "BROT"); + assert_int_equal(creds->password_obtained, CRED_SPECIFIED); + + /* Domain + username + password */ + cli_credentials_parse_string(creds, "XXL\\wurst%BROT", CRED_SPECIFIED); + + assert_string_equal(creds->domain, "XXL"); + assert_int_equal(creds->domain_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->username, "wurst"); + assert_int_equal(creds->username_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->password, "BROT"); + assert_int_equal(creds->password_obtained, CRED_SPECIFIED); + + /* Principal */ + cli_credentials_parse_string(creds, "wurst@brot.realm", CRED_SPECIFIED); + + assert_string_equal(creds->domain, ""); + assert_int_equal(creds->domain_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->username, "wurst@brot.realm"); + assert_int_equal(creds->username_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->principal, "wurst@brot.realm"); + assert_int_equal(creds->principal_obtained, CRED_SPECIFIED); + + assert_string_equal(creds->password, "BROT"); + assert_int_equal(creds->password_obtained, CRED_SPECIFIED); +} + +static void torture_creds_krb5_state(void **state) +{ + TALLOC_CTX *mem_ctx = *state; + struct cli_credentials *creds = NULL; + struct loadparm_context *lp_ctx = NULL; + bool ok; + + lp_ctx = loadparm_init_global(true); + assert_non_null(lp_ctx); + + creds = cli_credentials_init(mem_ctx); + assert_non_null(creds); + assert_int_equal(creds->kerberos_state_obtained, CRED_UNINITIALISED); + assert_int_equal(creds->kerberos_state, CRED_USE_KERBEROS_DESIRED); + + ok = cli_credentials_set_conf(creds, lp_ctx); + assert_true(ok); + assert_int_equal(creds->kerberos_state_obtained, CRED_SMB_CONF); + assert_int_equal(creds->kerberos_state, CRED_USE_KERBEROS_DESIRED); + + ok = cli_credentials_guess(creds, lp_ctx); + assert_true(ok); + assert_int_equal(creds->kerberos_state_obtained, CRED_SMB_CONF); + assert_int_equal(creds->kerberos_state, CRED_USE_KERBEROS_DESIRED); + assert_int_equal(creds->ccache_obtained, CRED_GUESS_FILE); + assert_non_null(creds->ccache); + + ok = cli_credentials_set_kerberos_state(creds, + CRED_USE_KERBEROS_REQUIRED, + CRED_SPECIFIED); + assert_true(ok); + assert_int_equal(creds->kerberos_state_obtained, CRED_SPECIFIED); + assert_int_equal(creds->kerberos_state, CRED_USE_KERBEROS_REQUIRED); + + ok = cli_credentials_set_kerberos_state(creds, + CRED_USE_KERBEROS_DISABLED, + CRED_SMB_CONF); + assert_false(ok); + assert_int_equal(creds->kerberos_state_obtained, CRED_SPECIFIED); + assert_int_equal(creds->kerberos_state, CRED_USE_KERBEROS_REQUIRED); + +} + +static void torture_creds_gensec_feature(void **state) +{ + TALLOC_CTX *mem_ctx = *state; + struct cli_credentials *creds = NULL; + bool ok; + + creds = cli_credentials_init(mem_ctx); + assert_non_null(creds); + assert_int_equal(creds->gensec_features_obtained, CRED_UNINITIALISED); + assert_int_equal(creds->gensec_features, 0); + + ok = cli_credentials_set_gensec_features(creds, + GENSEC_FEATURE_SIGN, + CRED_SPECIFIED); + assert_true(ok); + assert_int_equal(creds->gensec_features_obtained, CRED_SPECIFIED); + assert_int_equal(creds->gensec_features, GENSEC_FEATURE_SIGN); + + ok = cli_credentials_set_gensec_features(creds, + GENSEC_FEATURE_SEAL, + CRED_SMB_CONF); + assert_false(ok); + assert_int_equal(creds->gensec_features_obtained, CRED_SPECIFIED); + assert_int_equal(creds->gensec_features, GENSEC_FEATURE_SIGN); +} + +int main(int argc, char *argv[]) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_creds_init), + cmocka_unit_test(torture_creds_init_anonymous), + cmocka_unit_test(torture_creds_guess), + cmocka_unit_test(torture_creds_anon_guess), + cmocka_unit_test(torture_creds_parse_string), + cmocka_unit_test(torture_creds_krb5_state), + cmocka_unit_test(torture_creds_gensec_feature), + }; + + if (argc == 2) { + cmocka_set_test_filter(argv[1]); + } + cmocka_set_message_output(CM_OUTPUT_SUBUNIT); + + rc = cmocka_run_group_tests(tests, + setup_talloc_context, + teardown_talloc_context); + + return rc; +} diff --git a/auth/credentials/wscript_build b/auth/credentials/wscript_build new file mode 100644 index 0000000..7568554 --- /dev/null +++ b/auth/credentials/wscript_build @@ -0,0 +1,43 @@ +#!/usr/bin/env python + +bld.SAMBA_LIBRARY('samba-credentials', + source='credentials.c', + public_headers='credentials.h', + pc_files='samba-credentials.pc', + deps='LIBCRYPTO samba-errors events LIBCLI_AUTH samba-security CREDENTIALS_SECRETS CREDENTIALS_KRB5', + vnum='1.0.0' + ) + +bld.SAMBA_SUBSYSTEM('CREDENTIALS_KRB5', + source='credentials_krb5.c', + deps='KERBEROS_SRV_KEYTAB KERBEROS_UTIL gssapi samba-credentials', + public_deps='com_err authkrb5', + ) + +bld.SAMBA_SUBSYSTEM('CREDENTIALS_SECRETS', + source='credentials_secrets.c', + deps='CREDENTIALS_KRB5 CREDENTIALS_NTLM ldb SECRETS samdb-common dbwrap', + ) + +bld.SAMBA_SUBSYSTEM('CREDENTIALS_NTLM', + source='credentials_ntlm.c', + deps='samba-credentials GNUTLS_HELPERS') + +bld.SAMBA_SUBSYSTEM('CREDENTIALS_CMDLINE', + source='credentials_cmdline.c', + deps='samba-credentials') + +pytalloc_util = bld.pyembed_libname('pytalloc-util') +pyparam_util = bld.pyembed_libname('pyparam_util') + +bld.SAMBA_PYTHON('pycredentials', + source='pycredentials.c', + public_deps='samba-credentials %s %s CREDENTIALS_CMDLINE CREDENTIALS_KRB5 CREDENTIALS_SECRETS' % (pytalloc_util, pyparam_util), + realname='samba/credentials.so' +) + +bld.SAMBA_BINARY('test_creds', + source='tests/test_creds.c', + deps='cmocka samba-credentials', + local_include=False, + for_selftest=True) |