diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-05 17:47:29 +0000 |
commit | 4f5791ebd03eaec1c7da0865a383175b05102712 (patch) | |
tree | 8ce7b00f7a76baa386372422adebbe64510812d4 /auth | |
parent | Initial commit. (diff) | |
download | samba-4f5791ebd03eaec1c7da0865a383175b05102712.tar.xz samba-4f5791ebd03eaec1c7da0865a383175b05102712.zip |
Adding upstream version 2:4.17.12+dfsg.upstream/2%4.17.12+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'auth')
50 files changed, 22520 insertions, 0 deletions
diff --git a/auth/auth_log.c b/auth/auth_log.c new file mode 100644 index 0000000..dc1cea1 --- /dev/null +++ b/auth/auth_log.c @@ -0,0 +1,818 @@ +/* + + Authentication and authorization logging + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017 + + 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/>. +*/ + +/* + * Debug log levels for authentication logging (these both map to + * LOG_NOTICE in syslog) + */ +#define AUTH_FAILURE_LEVEL 2 +#define AUTH_SUCCESS_LEVEL 3 +#define AUTHZ_SUCCESS_LEVEL 4 + +/* 5 is used for both authentication and authorization */ +#define AUTH_ANONYMOUS_LEVEL 5 +#define AUTHZ_ANONYMOUS_LEVEL 5 + +#define AUTHZ_JSON_TYPE "Authorization" +#define AUTH_JSON_TYPE "Authentication" + +/* + * JSON message version numbers + * + * If adding a field increment the minor version + * If removing or changing the format/meaning of a field + * increment the major version. + */ +#define AUTH_MAJOR 1 +#define AUTH_MINOR 2 +#define AUTHZ_MAJOR 1 +#define AUTHZ_MINOR 1 + +#include "includes.h" +#include "../lib/tsocket/tsocket.h" +#include "common_auth.h" +#include "lib/util/util_str_escape.h" +#include "libcli/security/dom_sid.h" +#include "libcli/security/security_token.h" +#include "librpc/gen_ndr/server_id.h" +#include "source4/lib/messaging/messaging.h" +#include "source4/lib/messaging/irpc.h" +#include "lib/util/server_id_db.h" +#include "lib/param/param.h" +#include "librpc/ndr/libndr.h" +#include "librpc/gen_ndr/windows_event_ids.h" +#include "lib/audit_logging/audit_logging.h" + +/* + * Determine the type of the password supplied for the + * authorisation attempt. + * + */ +static const char* get_password_type(const struct auth_usersupplied_info *ui); + +#ifdef HAVE_JANSSON + +#include <jansson.h> +#include "system/time.h" + +/* + * Write the json object to the debug logs. + * + */ +static void log_json(struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + struct json_object *object, + int debug_class, + int debug_level) +{ + audit_log_json(object, debug_class, debug_level); + if (msg_ctx && lp_ctx && lpcfg_auth_event_notification(lp_ctx)) { + audit_message_send(msg_ctx, + AUTH_EVENT_NAME, + MSG_AUTH_LOG, + object); + } +} + +/* + * Determine the Windows logon type for the current authorisation attempt. + * + * Currently Samba only supports + * + * 2 Interactive A user logged on to this computer. + * 3 Network A user or computer logged on to this computer from + * the network. + * 8 NetworkCleartext A user logged on to this computer from the network. + * The user's password was passed to the authentication + * package in its unhashed form. + * + */ +static enum event_logon_type get_logon_type( + const struct auth_usersupplied_info *ui) +{ + if ((ui->logon_parameters & MSV1_0_CLEARTEXT_PASSWORD_SUPPLIED) + || (ui->password_state == AUTH_PASSWORD_PLAIN)) { + return EVT_LOGON_NETWORK_CLEAR_TEXT; + } else if (ui->flags & USER_INFO_INTERACTIVE_LOGON) { + return EVT_LOGON_INTERACTIVE; + } + return EVT_LOGON_NETWORK; +} + +/* + * Write a machine parsable json formatted authentication log entry. + * + * IF removing or changing the format/meaning of a field please update the + * major version number AUTH_MAJOR + * + * IF adding a new field please update the minor version number AUTH_MINOR + * + * To process the resulting log lines from the commend line use jq to + * parse the json. + * + * grep "^ {" log file | + * jq -rc '"\(.timestamp)\t\(.Authentication.status)\t + * \(.Authentication.clientDomain)\t + * \(.Authentication.clientAccount) + * \t\(.Authentication.workstation) + * \t\(.Authentication.remoteAddress) + * \t\(.Authentication.localAddress)"' + */ +static void log_authentication_event_json( + struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct timeval *start_time, + const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *domain_name, + const char *account_name, + struct dom_sid *sid, + enum event_id_type event_id, + int debug_level) +{ + struct json_object wrapper = json_empty_object; + struct json_object authentication = json_empty_object; + char negotiate_flags[11]; + char logon_id[19]; + int rc = 0; + const char *clientDomain = ui->orig_client.domain_name ? + ui->orig_client.domain_name : + ui->client.domain_name; + const char *clientAccount = ui->orig_client.account_name ? + ui->orig_client.account_name : + ui->client.account_name; + + authentication = json_new_object(); + if (json_is_invalid(&authentication)) { + goto failure; + } + rc = json_add_version(&authentication, AUTH_MAJOR, AUTH_MINOR); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&authentication, + "eventId", + event_id); + if (rc != 0) { + goto failure; + } + snprintf(logon_id, + sizeof( logon_id), + "%"PRIx64"", + ui->logon_id); + rc = json_add_string(&authentication, "logonId", logon_id); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&authentication, "logonType", get_logon_type(ui)); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&authentication, "status", nt_errstr(status)); + if (rc != 0) { + goto failure; + } + rc = json_add_address(&authentication, "localAddress", ui->local_host); + if (rc != 0) { + goto failure; + } + rc = + json_add_address(&authentication, "remoteAddress", ui->remote_host); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authentication, "serviceDescription", ui->service_description); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authentication, "authDescription", ui->auth_description); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authentication, "clientDomain", clientDomain); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authentication, "clientAccount", clientAccount); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authentication, "workstation", ui->workstation_name); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&authentication, "becameAccount", account_name); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&authentication, "becameDomain", domain_name); + if (rc != 0) { + goto failure; + } + rc = json_add_sid(&authentication, "becameSid", sid); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authentication, "mappedAccount", ui->mapped.account_name); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authentication, "mappedDomain", ui->mapped.domain_name); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&authentication, + "netlogonComputer", + ui->netlogon_trust_account.computer_name); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&authentication, + "netlogonTrustAccount", + ui->netlogon_trust_account.account_name); + if (rc != 0) { + goto failure; + } + snprintf(negotiate_flags, + sizeof( negotiate_flags), + "0x%08X", + ui->netlogon_trust_account.negotiate_flags); + rc = json_add_string( + &authentication, "netlogonNegotiateFlags", negotiate_flags); + if (rc != 0) { + goto failure; + } + rc = json_add_int(&authentication, + "netlogonSecureChannelType", + ui->netlogon_trust_account.secure_channel_type); + if (rc != 0) { + goto failure; + } + rc = json_add_sid(&authentication, + "netlogonTrustAccountSid", + ui->netlogon_trust_account.sid); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authentication, "passwordType", get_password_type(ui)); + if (rc != 0) { + goto failure; + } + + wrapper = json_new_object(); + if (json_is_invalid(&wrapper)) { + goto failure; + } + rc = json_add_timestamp(&wrapper); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&wrapper, "type", AUTH_JSON_TYPE); + if (rc != 0) { + goto failure; + } + rc = json_add_object(&wrapper, AUTH_JSON_TYPE, &authentication); + if (rc != 0) { + goto failure; + } + + /* + * While not a general-purpose profiling solution this will + * assist some to determine how long NTLM and KDC + * authentication takes once this process can handle it. This + * covers transactions elsewhere but not (eg) the delay while + * this is waiting unread on the input socket. + */ + if (start_time != NULL) { + struct timeval current_time = timeval_current(); + uint64_t duration = usec_time_diff(¤t_time, + start_time); + rc = json_add_int(&authentication, "duration", duration); + if (rc != 0) { + goto failure; + } + } + + log_json(msg_ctx, + lp_ctx, + &wrapper, + DBGC_AUTH_AUDIT_JSON, + debug_level); + json_free(&wrapper); + return; +failure: + /* + * On a failure authentication will not have been added to wrapper so it + * needs to be freed to avoid a leak. + * + */ + json_free(&authentication); + json_free(&wrapper); + DBG_ERR("Failed to write authentication event JSON log message\n"); +} + +/* + * Log details of a successful authorization to a service, + * in a machine parsable json format + * + * IF removing or changing the format/meaning of a field please update the + * major version number AUTHZ_MAJOR + * + * IF adding a new field please update the minor version number AUTHZ_MINOR + * + * To process the resulting log lines from the commend line use jq to + * parse the json. + * + * grep "^ {" log_file |\ + * jq -rc '"\(.timestamp)\t + * \(.Authorization.domain)\t + * \(.Authorization.account)\t + * \(.Authorization.remoteAddress)"' + * + */ +static void log_successful_authz_event_json( + struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct tsocket_address *remote, + const struct tsocket_address *local, + const char *service_description, + const char *auth_type, + const char *transport_protection, + struct auth_session_info *session_info, + int debug_level) +{ + struct json_object wrapper = json_empty_object; + struct json_object authorization = json_empty_object; + char account_flags[11]; + int rc = 0; + + authorization = json_new_object(); + if (json_is_invalid(&authorization)) { + goto failure; + } + rc = json_add_version(&authorization, AUTHZ_MAJOR, AUTHZ_MINOR); + if (rc != 0) { + goto failure; + } + rc = json_add_address(&authorization, "localAddress", local); + if (rc != 0) { + goto failure; + } + rc = json_add_address(&authorization, "remoteAddress", remote); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authorization, "serviceDescription", service_description); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&authorization, "authType", auth_type); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authorization, "domain", session_info->info->domain_name); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authorization, "account", session_info->info->account_name); + if (rc != 0) { + goto failure; + } + rc = json_add_sid( + &authorization, "sid", &session_info->security_token->sids[0]); + if (rc != 0) { + goto failure; + } + rc = json_add_guid( + &authorization, "sessionId", &session_info->unique_session_token); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authorization, "logonServer", session_info->info->logon_server); + if (rc != 0) { + goto failure; + } + rc = json_add_string( + &authorization, "transportProtection", transport_protection); + if (rc != 0) { + goto failure; + } + + snprintf(account_flags, + sizeof(account_flags), + "0x%08X", + session_info->info->acct_flags); + rc = json_add_string(&authorization, "accountFlags", account_flags); + if (rc != 0) { + goto failure; + } + + wrapper = json_new_object(); + if (json_is_invalid(&wrapper)) { + goto failure; + } + rc = json_add_timestamp(&wrapper); + if (rc != 0) { + goto failure; + } + rc = json_add_string(&wrapper, "type", AUTHZ_JSON_TYPE); + if (rc != 0) { + goto failure; + } + rc = json_add_object(&wrapper, AUTHZ_JSON_TYPE, &authorization); + if (rc != 0) { + goto failure; + } + + log_json(msg_ctx, + lp_ctx, + &wrapper, + DBGC_AUTH_AUDIT_JSON, + debug_level); + json_free(&wrapper); + return; +failure: + /* + * On a failure authorization will not have been added to wrapper so it + * needs to be freed to avoid a leak. + * + */ + json_free(&authorization); + json_free(&wrapper); + DBG_ERR("Unable to log Authentication event JSON audit message\n"); +} + +#else + +static void log_no_json(struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx) +{ + if (msg_ctx && lp_ctx && lpcfg_auth_event_notification(lp_ctx)) { + static bool auth_event_logged = false; + if (auth_event_logged == false) { + auth_event_logged = true; + DBG_ERR("auth event notification = true but Samba was " + "not compiled with jansson\n"); + } + } else { + static bool json_logged = false; + if (json_logged == false) { + json_logged = true; + DBG_NOTICE("JSON auth logs not available unless " + "compiled with jansson\n"); + } + } + + return; +} + +static void log_authentication_event_json( + struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct timeval *start_time, + const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *domain_name, + const char *account_name, + struct dom_sid *sid, + enum event_id_type event_id, + int debug_level) +{ + log_no_json(msg_ctx, lp_ctx); + return; +} + +static void log_successful_authz_event_json( + struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct tsocket_address *remote, + const struct tsocket_address *local, + const char *service_description, + const char *auth_type, + const char *transport_protection, + struct auth_session_info *session_info, + int debug_level) +{ + log_no_json(msg_ctx, lp_ctx); + return; +} + +#endif + +/* + * Determine the type of the password supplied for the + * authorisation attempt. + * + */ +static const char* get_password_type(const struct auth_usersupplied_info *ui) +{ + + const char *password_type = NULL; + + if (ui->password_type != NULL) { + password_type = ui->password_type; + } else if (ui->auth_description != NULL && + strncmp("ServerAuthenticate", ui->auth_description, 18) == 0) + { + if (ui->netlogon_trust_account.negotiate_flags + & NETLOGON_NEG_SUPPORTS_AES) { + password_type = "HMAC-SHA256"; + } else if (ui->netlogon_trust_account.negotiate_flags + & NETLOGON_NEG_STRONG_KEYS) { + password_type = "HMAC-MD5"; + } else { + password_type = "DES"; + } + } else if (ui->password_state == AUTH_PASSWORD_RESPONSE && + (ui->logon_parameters & MSV1_0_ALLOW_MSVCHAPV2) && + ui->password.response.nt.length == 24) { + password_type = "MSCHAPv2"; + } else if ((ui->logon_parameters & MSV1_0_CLEARTEXT_PASSWORD_SUPPLIED) + || (ui->password_state == AUTH_PASSWORD_PLAIN)) { + password_type = "Plaintext"; + } else if (ui->password_state == AUTH_PASSWORD_HASH) { + password_type = "Supplied-NT-Hash"; + } else if (ui->password_state == AUTH_PASSWORD_RESPONSE + && ui->password.response.nt.length > 24) { + password_type = "NTLMv2"; + } else if (ui->password_state == AUTH_PASSWORD_RESPONSE + && ui->password.response.nt.length == 24) { + password_type = "NTLMv1"; + } else if (ui->password_state == AUTH_PASSWORD_RESPONSE + && ui->password.response.lanman.length == 24) { + password_type = "LANMan"; + } else if (ui->password_state == AUTH_PASSWORD_RESPONSE + && ui->password.response.nt.length == 0 + && ui->password.response.lanman.length == 0) { + password_type = "No-Password"; + } + return password_type; +} + +/* + * Write a human readable authentication log entry. + * + */ +static void log_authentication_event_human_readable( + const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *domain_name, + const char *account_name, + struct dom_sid *sid, + int debug_level) +{ + TALLOC_CTX *frame = NULL; + + const char *ts = NULL; /* formatted current time */ + char *remote = NULL; /* formatted remote host */ + char *local = NULL; /* formatted local host */ + char *nl = NULL; /* NETLOGON details if present */ + char *trust_computer_name = NULL; + char *trust_account_name = NULL; + char *logon_line = NULL; + const char *password_type = NULL; + const char *clientDomain = ui->orig_client.domain_name ? + ui->orig_client.domain_name : + ui->client.domain_name; + const char *clientAccount = ui->orig_client.account_name ? + ui->orig_client.account_name : + ui->client.account_name; + + frame = talloc_stackframe(); + + password_type = get_password_type(ui); + /* Get the current time */ + ts = audit_get_timestamp(frame); + + /* Only log the NETLOGON details if they are present */ + if (ui->netlogon_trust_account.computer_name || + ui->netlogon_trust_account.account_name) { + trust_computer_name = log_escape(frame, + ui->netlogon_trust_account.computer_name); + trust_account_name = log_escape(frame, + ui->netlogon_trust_account.account_name); + nl = talloc_asprintf(frame, + " NETLOGON computer [%s] trust account [%s]", + trust_computer_name, trust_account_name); + } + + remote = tsocket_address_string(ui->remote_host, frame); + local = tsocket_address_string(ui->local_host, frame); + + if (NT_STATUS_IS_OK(status)) { + struct dom_sid_buf sid_buf; + + logon_line = talloc_asprintf(frame, + " became [%s]\\[%s] [%s].", + log_escape(frame, domain_name), + log_escape(frame, account_name), + dom_sid_str_buf(sid, &sid_buf)); + } else { + logon_line = talloc_asprintf( + frame, + " mapped to [%s]\\[%s].", + log_escape(frame, ui->mapped.domain_name), + log_escape(frame, ui->mapped.account_name)); + } + + DEBUGC(DBGC_AUTH_AUDIT, debug_level, + ("Auth: [%s,%s] user [%s]\\[%s]" + " at [%s] with [%s] status [%s]" + " workstation [%s] remote host [%s]" + "%s local host [%s]" + " %s\n", + ui->service_description, + ui->auth_description, + log_escape(frame, clientDomain), + log_escape(frame, clientAccount), + ts, + password_type, + nt_errstr(status), + log_escape(frame, ui->workstation_name), + remote, + logon_line, + local, + nl ? nl : "" + )); + + talloc_free(frame); +} + +/* + * Log details of an authentication attempt. + * Successful and unsuccessful attempts are logged. + * + * NOTE: msg_ctx and lp_ctx is optional, but when supplied allows streaming the + * authentication events over the message bus. + */ +void log_authentication_event( + struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct timeval *start_time, + const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *domain_name, + const char *account_name, + struct dom_sid *sid) +{ + /* set the log level */ + int debug_level = AUTH_FAILURE_LEVEL; + enum event_id_type event_id = EVT_ID_UNSUCCESSFUL_LOGON; + + if (NT_STATUS_IS_OK(status)) { + debug_level = AUTH_SUCCESS_LEVEL; + event_id = EVT_ID_SUCCESSFUL_LOGON; + if (dom_sid_equal(sid, &global_sid_Anonymous)) { + debug_level = AUTH_ANONYMOUS_LEVEL; + } + } + + if (CHECK_DEBUGLVLC(DBGC_AUTH_AUDIT, debug_level)) { + log_authentication_event_human_readable(ui, + status, + domain_name, + account_name, + sid, + debug_level); + } + if (CHECK_DEBUGLVLC(DBGC_AUTH_AUDIT_JSON, debug_level) || + (msg_ctx && lp_ctx && lpcfg_auth_event_notification(lp_ctx))) { + log_authentication_event_json(msg_ctx, + lp_ctx, + start_time, + ui, + status, + domain_name, + account_name, + sid, + event_id, + debug_level); + } +} + + + +/* + * Log details of a successful authorization to a service, + * in a human readable format. + * + */ +static void log_successful_authz_event_human_readable( + const struct tsocket_address *remote, + const struct tsocket_address *local, + const char *service_description, + const char *auth_type, + struct auth_session_info *session_info, + int debug_level) +{ + TALLOC_CTX *frame = NULL; + + const char *ts = NULL; /* formatted current time */ + char *remote_str = NULL; /* formatted remote host */ + char *local_str = NULL; /* formatted local host */ + struct dom_sid_buf sid_buf; + + frame = talloc_stackframe(); + + /* Get the current time */ + ts = audit_get_timestamp(frame); + + remote_str = tsocket_address_string(remote, frame); + local_str = tsocket_address_string(local, frame); + + DEBUGC(DBGC_AUTH_AUDIT, debug_level, + ("Successful AuthZ: [%s,%s] user [%s]\\[%s] [%s]" + " at [%s]" + " Remote host [%s]" + " local host [%s]\n", + service_description, + auth_type, + log_escape(frame, session_info->info->domain_name), + log_escape(frame, session_info->info->account_name), + dom_sid_str_buf(&session_info->security_token->sids[0], + &sid_buf), + ts, + remote_str, + local_str)); + + talloc_free(frame); +} + +/* + * Log details of a successful authorization to a service. + * + * Only successful authorizations are logged. For clarity: + * - NTLM bad passwords will be recorded by log_authentication_event + * - Kerberos decrypt failures need to be logged in gensec_gssapi et al + * + * The service may later refuse authorization due to an ACL. + * + * NOTE: msg_ctx and lp_ctx is optional, but when supplied allows streaming the + * authentication events over the message bus. + */ +void log_successful_authz_event( + struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct tsocket_address *remote, + const struct tsocket_address *local, + const char *service_description, + const char *auth_type, + const char *transport_protection, + struct auth_session_info *session_info) +{ + int debug_level = AUTHZ_SUCCESS_LEVEL; + + /* set the log level */ + if (security_token_is_anonymous(session_info->security_token)) { + debug_level = AUTH_ANONYMOUS_LEVEL; + } + + if (CHECK_DEBUGLVLC(DBGC_AUTH_AUDIT, debug_level)) { + log_successful_authz_event_human_readable(remote, + local, + service_description, + auth_type, + session_info, + debug_level); + } + if (CHECK_DEBUGLVLC(DBGC_AUTH_AUDIT_JSON, debug_level) || + (msg_ctx && lp_ctx && lpcfg_auth_event_notification(lp_ctx))) { + log_successful_authz_event_json(msg_ctx, lp_ctx, + remote, + local, + service_description, + auth_type, + transport_protection, + session_info, + debug_level); + } +} diff --git a/auth/auth_sam_reply.c b/auth/auth_sam_reply.c new file mode 100644 index 0000000..173a513 --- /dev/null +++ b/auth/auth_sam_reply.c @@ -0,0 +1,642 @@ +/* + Unix SMB/CIFS implementation. + + Convert a server info struct into the form for PAC and NETLOGON replies + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2011 + Copyright (C) Stefan Metzmacher <metze@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/auth.h" +#include "libcli/security/security.h" +#include "auth/auth_sam_reply.h" + +static NTSTATUS auth_convert_user_info_dc_sambaseinfo(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + struct netr_SamBaseInfo *sam) +{ + NTSTATUS status; + const struct auth_user_info *info; + + ZERO_STRUCTP(sam); + + if (user_info_dc->num_sids > PRIMARY_USER_SID_INDEX) { + status = dom_sid_split_rid(sam, &user_info_dc->sids[PRIMARY_USER_SID_INDEX], + &sam->domain_sid, &sam->rid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + return NT_STATUS_INVALID_PARAMETER; + } + + if (user_info_dc->num_sids > PRIMARY_GROUP_SID_INDEX) { + status = dom_sid_split_rid(NULL, &user_info_dc->sids[PRIMARY_GROUP_SID_INDEX], + NULL, &sam->primary_gid); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + } else { + /* if we have to encode something like SYSTEM (with no + * second SID in the token) then this is the only + * choice */ + sam->primary_gid = sam->rid; + } + + info = user_info_dc->info; + + sam->logon_time = info->last_logon; + sam->logoff_time = info->last_logoff; + sam->kickoff_time = info->acct_expiry; + sam->last_password_change = info->last_password_change; + sam->allow_password_change = info->allow_password_change; + sam->force_password_change = info->force_password_change; + +#define _COPY_STRING_TALLOC(src_name, dst_name) do { \ + if (info->src_name != NULL) {\ + sam->dst_name.string = talloc_strdup(mem_ctx, info->src_name); \ + if (sam->dst_name.string == NULL) { \ + return NT_STATUS_NO_MEMORY; \ + } \ + } \ +} while(0) + _COPY_STRING_TALLOC(account_name, account_name); + _COPY_STRING_TALLOC(full_name, full_name); + _COPY_STRING_TALLOC(logon_script, logon_script); + _COPY_STRING_TALLOC(profile_path, profile_path); + _COPY_STRING_TALLOC(home_directory, home_directory); + _COPY_STRING_TALLOC(home_drive, home_drive); + _COPY_STRING_TALLOC(logon_server, logon_server); + _COPY_STRING_TALLOC(domain_name, logon_domain); +#undef _COPY_STRING_TALLOC + + sam->logon_count = info->logon_count; + sam->bad_password_count = info->bad_password_count; + sam->groups.count = 0; + sam->groups.rids = NULL; + + if (user_info_dc->num_sids > PRIMARY_GROUP_SID_INDEX) { + size_t i; + sam->groups.rids = talloc_array(mem_ctx, struct samr_RidWithAttribute, + user_info_dc->num_sids); + + if (sam->groups.rids == NULL) + return NT_STATUS_NO_MEMORY; + + for (i=PRIMARY_GROUP_SID_INDEX; i<user_info_dc->num_sids; i++) { + struct dom_sid *group_sid = &user_info_dc->sids[i]; + if (!dom_sid_in_domain(sam->domain_sid, group_sid)) { + /* We handle this elsewhere */ + continue; + } + sam->groups.rids[sam->groups.count].rid = + group_sid->sub_auths[group_sid->num_auths-1]; + + sam->groups.rids[sam->groups.count].attributes = + SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; + sam->groups.count += 1; + } + } + + sam->user_flags = 0; /* w2k3 uses NETLOGON_EXTRA_SIDS | NETLOGON_NTLMV2_ENABLED */ + if (!user_info_dc->info->authenticated) { + sam->user_flags |= NETLOGON_GUEST; + } + sam->acct_flags = user_info_dc->info->acct_flags; + sam->sub_auth_status = 0; + sam->last_successful_logon = 0; + sam->last_failed_logon = 0; + sam->failed_logon_count = 0; + sam->reserved = 0; + + ZERO_STRUCT(sam->key); + if (user_info_dc->user_session_key.length == sizeof(sam->key.key)) { + memcpy(sam->key.key, user_info_dc->user_session_key.data, sizeof(sam->key.key)); + } + + ZERO_STRUCT(sam->LMSessKey); + if (user_info_dc->lm_session_key.length == sizeof(sam->LMSessKey.key)) { + memcpy(sam->LMSessKey.key, user_info_dc->lm_session_key.data, + sizeof(sam->LMSessKey.key)); + } + + return NT_STATUS_OK; +} + +/* Note that the validity of the _sam6 structure is only as long as + * the user_info_dc it was generated from */ +NTSTATUS auth_convert_user_info_dc_saminfo6(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + struct netr_SamInfo6 **_sam6) +{ + NTSTATUS status; + struct netr_SamInfo6 *sam6 = NULL; + size_t i; + + sam6 = talloc_zero(mem_ctx, struct netr_SamInfo6); + if (sam6 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = auth_convert_user_info_dc_sambaseinfo(sam6, + user_info_dc, + &sam6->base); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(sam6); + return status; + } + + sam6->sids = talloc_array(sam6, struct netr_SidAttr, + user_info_dc->num_sids); + if (sam6->sids == NULL) { + TALLOC_FREE(sam6); + return NT_STATUS_NO_MEMORY; + } + + /* We don't put the user and group SIDs in there */ + for (i=2; i<user_info_dc->num_sids; i++) { + if (dom_sid_in_domain(sam6->base.domain_sid, &user_info_dc->sids[i])) { + continue; + } + sam6->sids[sam6->sidcount].sid = dom_sid_dup(sam6->sids, &user_info_dc->sids[i]); + if (sam6->sids[sam6->sidcount].sid == NULL) { + TALLOC_FREE(sam6); + return NT_STATUS_NO_MEMORY; + } + sam6->sids[sam6->sidcount].attributes = + SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; + sam6->sidcount += 1; + } + if (sam6->sidcount) { + sam6->base.user_flags |= NETLOGON_EXTRA_SIDS; + } else { + sam6->sids = NULL; + } + + if (user_info_dc->info->dns_domain_name != NULL) { + sam6->dns_domainname.string = talloc_strdup(sam6, + user_info_dc->info->dns_domain_name); + if (sam6->dns_domainname.string == NULL) { + TALLOC_FREE(sam6); + return NT_STATUS_NO_MEMORY; + } + } + + if (user_info_dc->info->user_principal_name != NULL) { + sam6->principal_name.string = talloc_strdup(sam6, + user_info_dc->info->user_principal_name); + if (sam6->principal_name.string == NULL) { + TALLOC_FREE(sam6); + return NT_STATUS_NO_MEMORY; + } + } + + *_sam6 = sam6; + return NT_STATUS_OK; +} + +/* Note that the validity of the _sam2 structure is only as long as + * the user_info_dc it was generated from */ +NTSTATUS auth_convert_user_info_dc_saminfo2(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + struct netr_SamInfo2 **_sam2) +{ + NTSTATUS status; + struct netr_SamInfo6 *sam6 = NULL; + struct netr_SamInfo2 *sam2 = NULL; + + sam2 = talloc_zero(mem_ctx, struct netr_SamInfo2); + if (sam2 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = auth_convert_user_info_dc_saminfo6(sam2, user_info_dc, &sam6); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(sam2); + return status; + } + sam2->base = sam6->base; + + *_sam2 = sam2; + return NT_STATUS_OK; +} + +/* Note that the validity of the _sam3 structure is only as long as + * the user_info_dc it was generated from */ +NTSTATUS auth_convert_user_info_dc_saminfo3(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + struct netr_SamInfo3 **_sam3) +{ + NTSTATUS status; + struct netr_SamInfo6 *sam6 = NULL; + struct netr_SamInfo3 *sam3 = NULL; + + sam3 = talloc_zero(mem_ctx, struct netr_SamInfo3); + if (sam3 == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = auth_convert_user_info_dc_saminfo6(sam3, user_info_dc, &sam6); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(sam3); + return status; + } + sam3->base = sam6->base; + sam3->sidcount = sam6->sidcount; + sam3->sids = sam6->sids; + + *_sam3 = sam3; + return NT_STATUS_OK; +} + +/** + * Make a user_info struct from the info3 or similar returned by a domain logon. + * + * The netr_SamInfo3 is also a key structure in the source3 auth subsystem + */ + +NTSTATUS make_user_info_SamBaseInfo(TALLOC_CTX *mem_ctx, + const char *account_name, + const struct netr_SamBaseInfo *base, + bool authenticated, + struct auth_user_info **_user_info) +{ + struct auth_user_info *info; + + info = talloc_zero(mem_ctx, struct auth_user_info); + NT_STATUS_HAVE_NO_MEMORY(info); + + if (base->account_name.string) { + info->account_name = talloc_strdup(info, base->account_name.string); + } else { + info->account_name = talloc_strdup(info, account_name); + } + NT_STATUS_HAVE_NO_MEMORY(info->account_name); + + if (base->logon_domain.string) { + info->domain_name = talloc_strdup(info, base->logon_domain.string); + NT_STATUS_HAVE_NO_MEMORY(info->domain_name); + } + + if (base->full_name.string) { + info->full_name = talloc_strdup(info, base->full_name.string); + NT_STATUS_HAVE_NO_MEMORY(info->full_name); + } + if (base->logon_script.string) { + info->logon_script = talloc_strdup(info, base->logon_script.string); + NT_STATUS_HAVE_NO_MEMORY(info->logon_script); + } + if (base->profile_path.string) { + info->profile_path = talloc_strdup(info, base->profile_path.string); + NT_STATUS_HAVE_NO_MEMORY(info->profile_path); + } + if (base->home_directory.string) { + info->home_directory = talloc_strdup(info, base->home_directory.string); + NT_STATUS_HAVE_NO_MEMORY(info->home_directory); + } + if (base->home_drive.string) { + info->home_drive = talloc_strdup(info, base->home_drive.string); + NT_STATUS_HAVE_NO_MEMORY(info->home_drive); + } + if (base->logon_server.string) { + info->logon_server = talloc_strdup(info, base->logon_server.string); + NT_STATUS_HAVE_NO_MEMORY(info->logon_server); + } + info->last_logon = base->logon_time; + info->last_logoff = base->logoff_time; + info->acct_expiry = base->kickoff_time; + info->last_password_change = base->last_password_change; + info->allow_password_change = base->allow_password_change; + info->force_password_change = base->force_password_change; + info->logon_count = base->logon_count; + info->bad_password_count = base->bad_password_count; + info->acct_flags = base->acct_flags; + + /* Only set authenticated if both NETLOGON_GUEST is not set, and authenticated is set */ + info->authenticated = (authenticated && (!(base->user_flags & NETLOGON_GUEST))); + + *_user_info = info; + return NT_STATUS_OK; +} + +struct auth_user_info *auth_user_info_copy(TALLOC_CTX *mem_ctx, + const struct auth_user_info *src) +{ + struct auth_user_info *dst = NULL; + + dst = talloc_zero(mem_ctx, struct auth_user_info); + if (dst == NULL) { + return NULL; + } + + *dst = *src; +#define _COPY_STRING(_mem, _str) do { \ + if ((_str) != NULL) { \ + (_str) = talloc_strdup((_mem), (_str)); \ + if ((_str) == NULL) { \ + TALLOC_FREE(dst); \ + return NULL; \ + } \ + } \ +} while(0) + _COPY_STRING(dst, dst->account_name); + _COPY_STRING(dst, dst->user_principal_name); + _COPY_STRING(dst, dst->domain_name); + _COPY_STRING(dst, dst->dns_domain_name); + _COPY_STRING(dst, dst->full_name); + _COPY_STRING(dst, dst->logon_script); + _COPY_STRING(dst, dst->profile_path); + _COPY_STRING(dst, dst->home_directory); + _COPY_STRING(dst, dst->home_drive); + _COPY_STRING(dst, dst->logon_server); +#undef _COPY_STRING + + return dst; +} + +/** + * Make a user_info_dc struct from the info3 returned by a domain logon + */ +NTSTATUS make_user_info_dc_netlogon_validation(TALLOC_CTX *mem_ctx, + const char *account_name, + uint16_t validation_level, + const union netr_Validation *validation, + bool authenticated, + struct auth_user_info_dc **_user_info_dc) +{ + NTSTATUS status; + struct auth_user_info_dc *user_info_dc = NULL; + const struct netr_SamBaseInfo *base = NULL; + uint32_t sidcount = 0; + const struct netr_SidAttr *sids = NULL; + const char *dns_domainname = NULL; + const char *principal = NULL; + uint32_t i; + + switch (validation_level) { + case 2: + if (!validation || !validation->sam2) { + return NT_STATUS_INVALID_PARAMETER; + } + base = &validation->sam2->base; + break; + case 3: + if (!validation || !validation->sam3) { + return NT_STATUS_INVALID_PARAMETER; + } + base = &validation->sam3->base; + sidcount = validation->sam3->sidcount; + sids = validation->sam3->sids; + break; + case 6: + if (!validation || !validation->sam6) { + return NT_STATUS_INVALID_PARAMETER; + } + base = &validation->sam6->base; + sidcount = validation->sam6->sidcount; + sids = validation->sam6->sids; + dns_domainname = validation->sam6->dns_domainname.string; + principal = validation->sam6->principal_name.string; + break; + default: + return NT_STATUS_INVALID_LEVEL; + } + + user_info_dc = talloc_zero(mem_ctx, struct auth_user_info_dc); + NT_STATUS_HAVE_NO_MEMORY(user_info_dc); + + /* + Here is where we should check the list of + trusted domains, and verify that the SID + matches. + */ + if (!base->domain_sid) { + DEBUG(0, ("Cannot operate on a Netlogon Validation without a domain SID")); + return NT_STATUS_INVALID_PARAMETER; + } + + /* The IDL layer would be a better place to check this, but to + * guard the integer addition below, we double-check */ + if (base->groups.count > 65535) { + return NT_STATUS_INVALID_PARAMETER; + } + + user_info_dc->num_sids = 2; + + user_info_dc->sids = talloc_array(user_info_dc, struct dom_sid, user_info_dc->num_sids + base->groups.count); + NT_STATUS_HAVE_NO_MEMORY(user_info_dc->sids); + + user_info_dc->sids[PRIMARY_USER_SID_INDEX] = *base->domain_sid; + if (!sid_append_rid(&user_info_dc->sids[PRIMARY_USER_SID_INDEX], base->rid)) { + return NT_STATUS_INVALID_PARAMETER; + } + + user_info_dc->sids[PRIMARY_GROUP_SID_INDEX] = *base->domain_sid; + if (!sid_append_rid(&user_info_dc->sids[PRIMARY_GROUP_SID_INDEX], base->primary_gid)) { + return NT_STATUS_INVALID_PARAMETER; + } + + for (i = 0; i < base->groups.count; i++) { + /* Skip primary group, already added above */ + if (base->groups.rids[i].rid == base->primary_gid) { + continue; + } + user_info_dc->sids[user_info_dc->num_sids] = *base->domain_sid; + if (!sid_append_rid(&user_info_dc->sids[user_info_dc->num_sids], base->groups.rids[i].rid)) { + return NT_STATUS_INVALID_PARAMETER; + } + user_info_dc->num_sids++; + } + + /* Copy 'other' sids. We need to do sid filtering here to + prevent possible elevation of privileges. See: + + http://www.microsoft.com/windows2000/techinfo/administration/security/sidfilter.asp + */ + + /* + * The IDL layer would be a better place to check this, but to + * guard the integer addition below, we double-check + */ + if (sidcount > UINT16_MAX) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (sidcount > 0) { + struct dom_sid *dgrps = user_info_dc->sids; + size_t dgrps_count; + + dgrps_count = user_info_dc->num_sids + sidcount; + dgrps = talloc_realloc(user_info_dc, dgrps, struct dom_sid, + dgrps_count); + if (dgrps == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < sidcount; i++) { + if (sids[i].sid) { + dgrps[user_info_dc->num_sids] = *sids[i].sid; + user_info_dc->num_sids++; + } + } + + user_info_dc->sids = dgrps; + + /* Where are the 'global' sids?... */ + } + + status = make_user_info_SamBaseInfo(user_info_dc, account_name, base, authenticated, &user_info_dc->info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + if (dns_domainname != NULL) { + user_info_dc->info->dns_domain_name = talloc_strdup(user_info_dc->info, + dns_domainname); + if (user_info_dc->info->dns_domain_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (principal != NULL) { + user_info_dc->info->user_principal_name = talloc_strdup(user_info_dc->info, + principal); + if (user_info_dc->info->user_principal_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + /* ensure we are never given NULL session keys */ + + if (all_zero(base->key.key, sizeof(base->key.key))) { + user_info_dc->user_session_key = data_blob(NULL, 0); + } else { + user_info_dc->user_session_key = data_blob_talloc(user_info_dc, base->key.key, sizeof(base->key.key)); + NT_STATUS_HAVE_NO_MEMORY(user_info_dc->user_session_key.data); + } + + if (all_zero(base->LMSessKey.key, sizeof(base->LMSessKey.key))) { + user_info_dc->lm_session_key = data_blob(NULL, 0); + } else { + user_info_dc->lm_session_key = data_blob_talloc(user_info_dc, base->LMSessKey.key, sizeof(base->LMSessKey.key)); + NT_STATUS_HAVE_NO_MEMORY(user_info_dc->lm_session_key.data); + } + + *_user_info_dc = user_info_dc; + return NT_STATUS_OK; +} + +/** + * Make a user_info_dc struct from the PAC_LOGON_INFO supplied in the krb5 logon + */ +NTSTATUS make_user_info_dc_pac(TALLOC_CTX *mem_ctx, + const struct PAC_LOGON_INFO *pac_logon_info, + const struct PAC_UPN_DNS_INFO *pac_upn_dns_info, + struct auth_user_info_dc **_user_info_dc) +{ + uint32_t i; + NTSTATUS nt_status; + union netr_Validation validation; + struct auth_user_info_dc *user_info_dc; + const struct PAC_DOMAIN_GROUP_MEMBERSHIP *rg = NULL; + size_t sidcount; + + rg = &pac_logon_info->resource_groups; + + validation.sam3 = discard_const_p(struct netr_SamInfo3, &pac_logon_info->info3); + + nt_status = make_user_info_dc_netlogon_validation(mem_ctx, "", 3, &validation, + true, /* This user was authenticated */ + &user_info_dc); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (pac_logon_info->info3.base.user_flags & NETLOGON_RESOURCE_GROUPS) { + rg = &pac_logon_info->resource_groups; + } + + if (rg == NULL) { + *_user_info_dc = user_info_dc; + return NT_STATUS_OK; + } + + if (rg->groups.count > 0) { + /* The IDL layer would be a better place to check this, but to + * guard the integer addition below, we double-check */ + if (rg->groups.count > 65535) { + talloc_free(user_info_dc); + return NT_STATUS_INVALID_PARAMETER; + } + + /* + Here is where we should check the list of + trusted domains, and verify that the SID + matches. + */ + if (rg->domain_sid == NULL) { + talloc_free(user_info_dc); + DEBUG(0, ("Cannot operate on a PAC without a resource domain SID")); + return NT_STATUS_INVALID_PARAMETER; + } + + sidcount = user_info_dc->num_sids + rg->groups.count; + user_info_dc->sids + = talloc_realloc(user_info_dc, user_info_dc->sids, struct dom_sid, sidcount); + if (user_info_dc->sids == NULL) { + TALLOC_FREE(user_info_dc); + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < rg->groups.count; i++) { + bool ok; + + user_info_dc->sids[user_info_dc->num_sids] = *rg->domain_sid; + ok = sid_append_rid(&user_info_dc->sids[user_info_dc->num_sids], + rg->groups.rids[i].rid); + if (!ok) { + return NT_STATUS_INVALID_PARAMETER; + } + user_info_dc->num_sids++; + } + } + + if (pac_upn_dns_info != NULL) { + if (pac_upn_dns_info->upn_name != NULL) { + user_info_dc->info->user_principal_name = + talloc_strdup(user_info_dc->info, + pac_upn_dns_info->upn_name); + if (user_info_dc->info->user_principal_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + user_info_dc->info->dns_domain_name = + talloc_strdup(user_info_dc->info, + pac_upn_dns_info->dns_domain_name); + if (user_info_dc->info->dns_domain_name == NULL) { + return NT_STATUS_NO_MEMORY; + } + + if (pac_upn_dns_info->flags & PAC_UPN_DNS_FLAG_CONSTRUCTED) { + user_info_dc->info->user_principal_constructed = true; + } + } + + *_user_info_dc = user_info_dc; + return NT_STATUS_OK; +} diff --git a/auth/auth_sam_reply.h b/auth/auth_sam_reply.h new file mode 100644 index 0000000..d8a30c6 --- /dev/null +++ b/auth/auth_sam_reply.h @@ -0,0 +1,86 @@ +/* + Unix SMB/CIFS implementation. + + Convert a server info struct into the form for PAC and NETLOGON replies + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004 + Copyright (C) Stefan Metzmacher <metze@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 __AUTH_AUTH_SAM_REPLY_H__ +#define __AUTH_AUTH_SAM_REPLY_H__ + +#include "libcli/util/ntstatus.h" +#include "libcli/util/werror.h" +#include "librpc/gen_ndr/auth.h" + +#undef _PRINTF_ATTRIBUTE +#define _PRINTF_ATTRIBUTE(a1, a2) PRINTF_ATTRIBUTE(a1, a2) +/* this file contains prototypes for functions that are private + * to this subsystem or library. These functions should not be + * used outside this particular subsystem! */ + + +/* The following definitions come from auth/auth_sam_reply.c */ + +NTSTATUS make_user_info_SamBaseInfo(TALLOC_CTX *mem_ctx, + const char *account_name, + const struct netr_SamBaseInfo *base, + bool authenticated, + struct auth_user_info **_user_info); + +struct auth_user_info *auth_user_info_copy(TALLOC_CTX *mem_ctx, + const struct auth_user_info *src); + +NTSTATUS auth_convert_user_info_dc_saminfo6(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + struct netr_SamInfo6 **_sam6); +NTSTATUS auth_convert_user_info_dc_saminfo2(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + struct netr_SamInfo2 **_sam2); +NTSTATUS auth_convert_user_info_dc_saminfo3(TALLOC_CTX *mem_ctx, + const struct auth_user_info_dc *user_info_dc, + struct netr_SamInfo3 **_sam3); + +/** + * Make a user_info_dc struct from the info3 returned by a domain logon + */ +NTSTATUS make_user_info_dc_netlogon_validation(TALLOC_CTX *mem_ctx, + const char *account_name, + uint16_t validation_level, + const union netr_Validation *validation, + bool authenticated, + struct auth_user_info_dc **_user_info_dc); + +/** + * Make a user_info_dc struct from the PAC_LOGON_INFO supplied in the krb5 logon + */ +NTSTATUS make_user_info_dc_pac(TALLOC_CTX *mem_ctx, + const struct PAC_LOGON_INFO *pac_logon_info, + const struct PAC_UPN_DNS_INFO *pac_upn_dns_info, + struct auth_user_info_dc **_user_info_dc); + +/* The following definitions come from auth/wbc_auth_util.c */ + +struct wbcAuthUserInfo; + +struct netr_SamInfo6 *wbcAuthUserInfo_to_netr_SamInfo6(TALLOC_CTX *mem_ctx, + const struct wbcAuthUserInfo *info); + +#undef _PRINTF_ATTRIBUTE +#define _PRINTF_ATTRIBUTE(a1, a2) + +#endif /* __AUTH_AUTH_SAM_REPLY_H__ */ diff --git a/auth/auth_util.c b/auth/auth_util.c new file mode 100644 index 0000000..ec9094d --- /dev/null +++ b/auth/auth_util.c @@ -0,0 +1,71 @@ +/* + Unix SMB/CIFS implementation. + Authentication utility functions + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017 + + 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/ndr/libndr.h" +#include "librpc/gen_ndr/ndr_auth.h" +#include "auth_util.h" + +struct auth_session_info *copy_session_info(TALLOC_CTX *mem_ctx, + const struct auth_session_info *src) +{ + TALLOC_CTX *frame = talloc_stackframe(); + struct auth_session_info *dst; + DATA_BLOB blob; + enum ndr_err_code ndr_err; + + ndr_err = ndr_push_struct_blob( + &blob, + frame, + src, + (ndr_push_flags_fn_t)ndr_push_auth_session_info); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("copy_session_info(): ndr_push_auth_session_info " + "failed: %s\n", + ndr_errstr(ndr_err)); + TALLOC_FREE(frame); + return NULL; + } + + dst = talloc_zero(mem_ctx, struct auth_session_info); + if (dst == NULL) { + DBG_ERR("talloc failed\n"); + TALLOC_FREE(frame); + return NULL; + } + + ndr_err = ndr_pull_struct_blob( + &blob, + dst, + dst, + (ndr_pull_flags_fn_t)ndr_pull_auth_session_info); + + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + DBG_ERR("copy_session_info(): ndr_pull_auth_session_info " + "failed: %s\n", + ndr_errstr(ndr_err)); + TALLOC_FREE(dst); + TALLOC_FREE(frame); + return NULL; + } + + TALLOC_FREE(frame); + return dst; +} diff --git a/auth/auth_util.h b/auth/auth_util.h new file mode 100644 index 0000000..0af6ff5 --- /dev/null +++ b/auth/auth_util.h @@ -0,0 +1,32 @@ +/* + Unix SMB/CIFS implementation. + Authentication utility functions + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017 + + 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 __AUTH_AUTH_UTIL_H__ +#define __AUTH_AUTH_UTIL_H__ + +#include "replace.h" +#include <talloc.h> +#include "librpc/gen_ndr/auth.h" + +struct auth_session_info *copy_session_info( + TALLOC_CTX *mem_ctx, + const struct auth_session_info *src); + +#endif diff --git a/auth/common_auth.h b/auth/common_auth.h new file mode 100644 index 0000000..fb41d48 --- /dev/null +++ b/auth/common_auth.h @@ -0,0 +1,210 @@ +/* + Unix SMB/CIFS implementation. + Standardised Authentication types + Copyright (C) Andrew Bartlett 2001-2010 + + 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 AUTH_COMMON_AUTH_H +#define AUTH_COMMON_AUTH_H + +#include "librpc/gen_ndr/auth.h" + +#define USER_INFO_CASE_INSENSITIVE_USERNAME 0x01 /* username may be in any case */ +#define USER_INFO_CASE_INSENSITIVE_PASSWORD 0x02 /* password may be in any case */ +#define USER_INFO_DONT_CHECK_UNIX_ACCOUNT 0x04 /* don't check unix account status */ +#define USER_INFO_INTERACTIVE_LOGON 0x08 /* Interactive logon */ +/*unused #define USER_INFO_LOCAL_SAM_ONLY 0x10 Only authenticate against the local SAM, do not map missing passwords to NO_SUCH_USER */ +#define USER_INFO_INFO3_AND_NO_AUTHZ 0x20 /* Only fill in server_info->info3 and do not do any authorization steps */ + +enum auth_password_state { + AUTH_PASSWORD_PLAIN = 1, + AUTH_PASSWORD_HASH = 2, + AUTH_PASSWORD_RESPONSE = 3 +}; + +#define AUTH_SESSION_INFO_DEFAULT_GROUPS 0x01 /* Add the user to the default world and network groups */ +#define AUTH_SESSION_INFO_AUTHENTICATED 0x02 /* Add the user to the 'authenticated users' group */ +#define AUTH_SESSION_INFO_SIMPLE_PRIVILEGES 0x04 /* Use a trivial map between users and privilages, rather than a DB */ +#define AUTH_SESSION_INFO_UNIX_TOKEN 0x08 /* The returned token must have the unix_token and unix_info elements provided */ +#define AUTH_SESSION_INFO_NTLM 0x10 /* The returned token must have authenticated-with-NTLM flag set */ + +struct auth_usersupplied_info +{ + const char *workstation_name; + const struct tsocket_address *remote_host; + const struct tsocket_address *local_host; + + uint32_t logon_parameters; + + bool cracknames_called; + bool was_mapped; + uint64_t logon_id; + /* the values the client gives us */ + struct { + const char *account_name; + const char *domain_name; + } client, mapped, orig_client; + + enum auth_password_state password_state; + + struct { + struct { + DATA_BLOB lanman; + DATA_BLOB nt; + } response; + struct { + struct samr_Password *lanman; + struct samr_Password *nt; + } hash; + + char *plaintext; + } password; + uint32_t flags; + + struct { + uint32_t negotiate_flags; + enum netr_SchannelType secure_channel_type; + const char *computer_name; /* [charset(UTF8)] */ + const char *account_name; /* [charset(UTF8)] */ + struct dom_sid *sid; /* [unique] */ + } netlogon_trust_account; + + const char *service_description; + const char *auth_description; + + /* + * for logging only, normally worked out from the password but + * for krb5 logging only (krb5 normally doesn't use this) we + * record the enc type here + */ + const char *password_type; +}; + +struct auth_method_context; +struct tevent_context; +struct imessaging_context; +struct loadparm_context; +struct ldb_context; +struct smb_krb5_context; + +struct auth4_context { + struct { + /* Who set this up in the first place? */ + const char *set_by; + + DATA_BLOB data; + } challenge; + + /* methods, in the order they should be called */ + struct auth_method_context *methods; + + /* the event context to use for calls that can block */ + struct tevent_context *event_ctx; + + /* the messaging context which can be used by backends */ + struct imessaging_context *msg_ctx; + + /* loadparm context */ + struct loadparm_context *lp_ctx; + + /* SAM database for this local machine - to fill in local groups, or to authenticate local NTLM users */ + struct ldb_context *sam_ctx; + + /* The time this authentication started */ + struct timeval start_time; + + /* Private data for the callbacks on this auth context */ + void *private_data; + + /* Kerberos context, set up on demand */ + struct smb_krb5_context *smb_krb5_context; + + struct tevent_req *(*check_ntlm_password_send)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct auth4_context *auth_ctx, + const struct auth_usersupplied_info *user_info); + NTSTATUS (*check_ntlm_password_recv)(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + uint8_t *pauthoritative, + void **server_returned_info, + DATA_BLOB *nt_session_key, + DATA_BLOB *lm_session_key); + + NTSTATUS (*get_ntlm_challenge)(struct auth4_context *auth_ctx, uint8_t chal[8]); + + NTSTATUS (*set_ntlm_challenge)(struct auth4_context *auth_ctx, const uint8_t chal[8], const char *set_by); + + NTSTATUS (*generate_session_info)(struct auth4_context *auth_context, + TALLOC_CTX *mem_ctx, + void *server_returned_info, + const char *original_user_name, + uint32_t session_info_flags, + struct auth_session_info **session_info); + + NTSTATUS (*generate_session_info_pac)(struct auth4_context *auth_ctx, + TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + DATA_BLOB *pac_blob, + const char *principal_name, + const struct tsocket_address *remote_address, + uint32_t session_info_flags, + struct auth_session_info **session_info); +}; + +#define AUTHZ_TRANSPORT_PROTECTION_NONE "NONE" +#define AUTHZ_TRANSPORT_PROTECTION_SMB "SMB" +#define AUTHZ_TRANSPORT_PROTECTION_TLS "TLS" +#define AUTHZ_TRANSPORT_PROTECTION_SEAL "SEAL" +#define AUTHZ_TRANSPORT_PROTECTION_SIGN "SIGN" + +/* + * Log details of an authentication attempt. + * Successful and unsuccessful attempts are logged. + * + * NOTE: msg_ctx and lp_ctx is optional, but when supplied allows streaming the + * authentication events over the message bus. + */ +void log_authentication_event(struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct timeval *start_time, + const struct auth_usersupplied_info *ui, + NTSTATUS status, + const char *account_name, + const char *domain_name, + struct dom_sid *sid); + +/* + * Log details of a successful authorization to a service. + * + * Only successful authorizations are logged. For clarity: + * - NTLM bad passwords will be recorded by log_authentication_event + * - Kerberos decrypt failures need to be logged in gensec_gssapi et al + * + * The service may later refuse authorization due to an ACL. + * + * + * NOTE: msg_ctx and lp_ctx is optional, but when supplied allows streaming the + * authorization events over the message bus. + */ +void log_successful_authz_event(struct imessaging_context *msg_ctx, + struct loadparm_context *lp_ctx, + const struct tsocket_address *remote, + const struct tsocket_address *local, + const char *service_description, + const char *auth_type, + const char *transport_protection, + struct auth_session_info *session_info); +#endif 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) diff --git a/auth/gensec/external.c b/auth/gensec/external.c new file mode 100644 index 0000000..300ce6b --- /dev/null +++ b/auth/gensec/external.c @@ -0,0 +1,127 @@ +/* + Unix SMB/CIFS implementation. + + SASL/EXTERNAL authentication. + + Copyright (C) Howard Chu <hyc@symas.com> 2013 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/gensec/gensec_proto.h" +#include "auth/gensec/gensec_toplevel_proto.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/* SASL/EXTERNAL is essentially a no-op; it is only usable when the transport + * layer is already mutually authenticated. + */ + +NTSTATUS gensec_external_init(TALLOC_CTX *ctx); + +static NTSTATUS gensec_external_start(struct gensec_security *gensec_security) +{ + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) + return NT_STATUS_INVALID_PARAMETER; + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) + return NT_STATUS_INVALID_PARAMETER; + + return NT_STATUS_OK; +} + +struct gensec_external_update_state { + DATA_BLOB out; +}; + +static struct tevent_req *gensec_external_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req; + struct gensec_external_update_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_external_update_state); + if (req == NULL) { + return NULL; + } + + state->out = data_blob_talloc(state, "", 0); + if (tevent_req_nomem(state->out.data, req)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS gensec_external_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_external_update_state *state = + tevent_req_data(req, + struct gensec_external_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + tevent_req_received(req); + return NT_STATUS_OK; +} + +/* We have no features */ +static bool gensec_external_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + return false; +} + +static const struct gensec_security_ops gensec_external_ops = { + .name = "sasl-EXTERNAL", + .sasl_name = "EXTERNAL", + .client_start = gensec_external_start, + .update_send = gensec_external_update_send, + .update_recv = gensec_external_update_recv, + .have_feature = gensec_external_have_feature, + .enabled = true, + .priority = GENSEC_EXTERNAL +}; + + +NTSTATUS gensec_external_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = gensec_register(ctx, &gensec_external_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_external_ops.name)); + } + return ret; +} diff --git a/auth/gensec/gensec.c b/auth/gensec/gensec.c new file mode 100644 index 0000000..3641d4b --- /dev/null +++ b/auth/gensec/gensec.c @@ -0,0 +1,854 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#define TEVENT_DEPRECATED 1 +#include <tevent.h> +#include "lib/tsocket/tsocket.h" +#include "lib/util/tevent_ntstatus.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "auth/common_auth.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +_PRIVATE_ NTSTATUS gensec_may_reset_crypto(struct gensec_security *gensec_security, + bool full_reset) +{ + if (!gensec_security->ops->may_reset_crypto) { + return NT_STATUS_OK; + } + + return gensec_security->ops->may_reset_crypto(gensec_security, full_reset); +} + +/* + wrappers for the gensec function pointers +*/ +_PUBLIC_ NTSTATUS gensec_unseal_packet(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + if (!gensec_security->ops->unseal_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->unseal_packet(gensec_security, + data, length, + whole_pdu, pdu_length, + sig); +} + +_PUBLIC_ NTSTATUS gensec_check_packet(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + if (!gensec_security->ops->check_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->check_packet(gensec_security, data, length, whole_pdu, pdu_length, sig); +} + +_PUBLIC_ NTSTATUS gensec_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (!gensec_security->ops->seal_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->seal_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +_PUBLIC_ NTSTATUS gensec_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (!gensec_security->ops->sign_packet) { + return NT_STATUS_NOT_IMPLEMENTED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_security->ops->sign_packet(gensec_security, mem_ctx, data, length, whole_pdu, pdu_length, sig); +} + +_PUBLIC_ size_t gensec_sig_size(struct gensec_security *gensec_security, size_t data_size) +{ + if (!gensec_security->ops->sig_size) { + return 0; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return 0; + } + if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE)) { + return 0; + } + } + + return gensec_security->ops->sig_size(gensec_security, data_size); +} + +_PUBLIC_ size_t gensec_max_wrapped_size(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->max_wrapped_size) { + return (1 << 17); + } + + return gensec_security->ops->max_wrapped_size(gensec_security); +} + +_PUBLIC_ size_t gensec_max_input_size(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->max_input_size) { + return (1 << 17) - gensec_sig_size(gensec_security, 1 << 17); + } + + return gensec_security->ops->max_input_size(gensec_security); +} + +_PUBLIC_ NTSTATUS gensec_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (!gensec_security->ops->wrap) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return gensec_security->ops->wrap(gensec_security, mem_ctx, in, out); +} + +_PUBLIC_ NTSTATUS gensec_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (!gensec_security->ops->unwrap) { + return NT_STATUS_NOT_IMPLEMENTED; + } + return gensec_security->ops->unwrap(gensec_security, mem_ctx, in, out); +} + +_PUBLIC_ NTSTATUS gensec_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key) +{ + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SESSION_KEY)) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (!gensec_security->ops->session_key) { + return NT_STATUS_NOT_IMPLEMENTED; + } + + return gensec_security->ops->session_key(gensec_security, mem_ctx, session_key); +} + +const char *gensec_final_auth_type(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->final_auth_type) { + return gensec_security->ops->name; + } + + return gensec_security->ops->final_auth_type(gensec_security); +} + +/* + * Log details of a successful GENSEC authorization to a service. + * + * Only successful authorizations are logged, as only these call gensec_session_info() + * + * The service may later refuse authorization due to an ACL. + * + */ +static void log_successful_gensec_authz_event(struct gensec_security *gensec_security, + struct auth_session_info *session_info) +{ + const struct tsocket_address *remote + = gensec_get_remote_address(gensec_security); + const struct tsocket_address *local + = gensec_get_local_address(gensec_security); + const char *service_description + = gensec_get_target_service_description(gensec_security); + const char *final_auth_type + = gensec_final_auth_type(gensec_security); + const char *transport_protection = NULL; + if (gensec_security->want_features & GENSEC_FEATURE_SMB_TRANSPORT) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_SMB; + } else if (gensec_security->want_features & GENSEC_FEATURE_LDAPS_TRANSPORT) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_TLS; + } else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_SEAL; + } else if (gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_SIGN; + } else { + transport_protection = AUTHZ_TRANSPORT_PROTECTION_NONE; + } + log_successful_authz_event(gensec_security->auth_context->msg_ctx, + gensec_security->auth_context->lp_ctx, + remote, local, + service_description, + final_auth_type, + transport_protection, + session_info); +} + + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. This will also make an + * authorization log entry, as it is already called by all the + * callers. + * + */ + +_PUBLIC_ NTSTATUS gensec_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info) +{ + NTSTATUS status; + if (!gensec_security->ops->session_info) { + return NT_STATUS_NOT_IMPLEMENTED; + } + status = gensec_security->ops->session_info(gensec_security, mem_ctx, session_info); + + if (NT_STATUS_IS_OK(status) && !gensec_security->subcontext + && (gensec_security->want_features & GENSEC_FEATURE_NO_AUTHZ_LOG) == 0) { + log_successful_gensec_authz_event(gensec_security, *session_info); + } + + return status; +} + +_PUBLIC_ void gensec_set_max_update_size(struct gensec_security *gensec_security, + uint32_t max_update_size) +{ + gensec_security->max_update_size = max_update_size; +} + +_PUBLIC_ size_t gensec_max_update_size(struct gensec_security *gensec_security) +{ + if (gensec_security->max_update_size == 0) { + return UINT32_MAX; + } + + return gensec_security->max_update_size; +} + +static NTSTATUS gensec_verify_features(struct gensec_security *gensec_security) +{ + bool ok; + + /* + * gensec_want_feature(GENSEC_FEATURE_SIGN) + * and + * gensec_want_feature(GENSEC_FEATURE_SEAL) + * require these flags to be available. + */ + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + DEBUG(0,("Did not manage to negotiate mandatory feature " + "SIGN\n")); + return NT_STATUS_ACCESS_DENIED; + } + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL)) { + DEBUG(0,("Did not manage to negotiate mandatory feature " + "SEAL\n")); + return NT_STATUS_ACCESS_DENIED; + } + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + DEBUG(0,("Did not manage to negotiate mandatory feature " + "SIGN for SEAL\n")); + return NT_STATUS_ACCESS_DENIED; + } + } + + if (gensec_security->dcerpc_auth_level < DCERPC_AUTH_LEVEL_PACKET) { + return NT_STATUS_OK; + } + + ok = gensec_have_feature(gensec_security, + GENSEC_FEATURE_SIGN_PKT_HEADER); + if (!ok) { + DBG_ERR("backend [%s] does not support header signing! " + "auth_level[0x%x]\n", + gensec_security->ops->name, + gensec_security->dcerpc_auth_level); + return NT_STATUS_INTERNAL_ERROR; + } + + return NT_STATUS_OK; +} + +/** + * Next state function for the GENSEC state machine + * + * @param gensec_security GENSEC State + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param in The request, as a DATA_BLOB + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ +_PUBLIC_ NTSTATUS gensec_update(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + NTSTATUS status; + TALLOC_CTX *frame = NULL; + struct tevent_context *ev = NULL; + struct tevent_req *subreq = NULL; + bool ok; + + if (gensec_security->subcontext) { + /* + * gensec modules are not allowed to call the sync version. + */ + return NT_STATUS_INTERNAL_ERROR; + } + + frame = talloc_stackframe(); + + ev = samba_tevent_context_init(frame); + if (ev == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + /* + * TODO: remove this hack once the backends + * are fixed. + */ + tevent_loop_allow_nesting(ev); + + subreq = gensec_update_send(frame, ev, gensec_security, in); + if (subreq == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + ok = tevent_req_poll_ntstatus(subreq, ev, &status); + if (!ok) { + goto fail; + } + status = gensec_update_recv(subreq, out_mem_ctx, out); + fail: + TALLOC_FREE(frame); + return status; +} + +struct gensec_update_state { + const struct gensec_security_ops *ops; + struct gensec_security *gensec_security; + NTSTATUS status; + DATA_BLOB out; +}; + +static void gensec_update_cleanup(struct tevent_req *req, + enum tevent_req_state req_state); +static void gensec_update_done(struct tevent_req *subreq); + +/** + * Next state function for the GENSEC state machine async version + * + * @param mem_ctx The memory context for the request + * @param ev The event context for the request + * @param gensec_security GENSEC State + * @param in The request, as a DATA_BLOB + * + * @return The request handle or NULL on no memory failure + */ + +_PUBLIC_ struct tevent_req *gensec_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req = NULL; + struct gensec_update_state *state = NULL; + struct tevent_req *subreq = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_update_state); + if (req == NULL) { + return NULL; + } + state->ops = gensec_security->ops; + state->gensec_security = gensec_security; + + if (gensec_security->update_busy_ptr != NULL) { + tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR); + return tevent_req_post(req, ev); + } + + if (gensec_security->child_security != NULL) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + gensec_security->update_busy_ptr = &state->gensec_security; + tevent_req_set_cleanup_fn(req, gensec_update_cleanup); + + subreq = state->ops->update_send(state, ev, gensec_security, in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, gensec_update_done, req); + + DBG_DEBUG("%s[%p]: subreq: %p\n", state->ops->name, + state->gensec_security, subreq); + + return req; +} + +static void gensec_update_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct gensec_update_state *state = + tevent_req_data(req, + struct gensec_update_state); + + if (state->gensec_security == NULL) { + return; + } + + if (state->gensec_security->update_busy_ptr == &state->gensec_security) { + state->gensec_security->update_busy_ptr = NULL; + } + + state->gensec_security = NULL; +} + +static void gensec_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct gensec_update_state *state = + tevent_req_data(req, + struct gensec_update_state); + NTSTATUS status; + const char *debug_subreq = NULL; + + if (CHECK_DEBUGLVL(DBGLVL_DEBUG)) { + /* + * We need to call tevent_req_print() + * before calling the _recv function, + * before tevent_req_received() was called. + * in order to print the pointer value of + * the subreq state. + */ + debug_subreq = tevent_req_print(state, subreq); + } + + status = state->ops->update_recv(subreq, state, &state->out); + TALLOC_FREE(subreq); + state->status = status; + if (GENSEC_UPDATE_IS_NTERROR(status)) { + NTSTATUS orig_status = status; + bool force_no_such_user = false; + + /* + * callers only expect NT_STATUS_NO_SUCH_USER. + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_ACCOUNT_NAME)) { + force_no_such_user = true; + } else if (NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN)) { + force_no_such_user = true; + } + + if (state->gensec_security->subcontext) { + /* + * We should only map on the outer + * gensec_update exchange, spnego + * needs the raw status. + */ + force_no_such_user = false; + } + + if (force_no_such_user) { + /* + * nt_status_squash() may map + * to NT_STATUS_LOGON_FAILURE later + */ + status = NT_STATUS_NO_SUCH_USER; + } + + DBG_INFO("%s[%p]: %s%s%s%s%s\n", + state->ops->name, + state->gensec_security, + NT_STATUS_EQUAL(status, orig_status) ? + "" : nt_errstr(orig_status), + NT_STATUS_EQUAL(status, orig_status) ? + "" : " ", + nt_errstr(status), + debug_subreq ? " " : "", + debug_subreq ? debug_subreq : ""); + tevent_req_nterror(req, status); + return; + } + DBG_DEBUG("%s[%p]: %s %s\n", state->ops->name, + state->gensec_security, nt_errstr(status), + debug_subreq); + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return; + } + + /* + * Because callers using the + * gensec_start_mech_by_authtype() never call + * gensec_want_feature(), it isn't sensible for them + * to have to call gensec_have_feature() manually, and + * these are not points of negotiation, but are + * asserted by the client + */ + status = gensec_verify_features(state->gensec_security); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +/** + * Next state function for the GENSEC state machine + * + * @param req request state + * @param out_mem_ctx The TALLOC_CTX for *out to be allocated on + * @param out The reply, as an talloc()ed DATA_BLOB, on *out_mem_ctx + * @return Error, MORE_PROCESSING_REQUIRED if a reply is sent, + * or NT_STATUS_OK if the user is authenticated. + */ +_PUBLIC_ NTSTATUS gensec_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_update_state *state = + tevent_req_data(req, struct gensec_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, out->data); + status = state->status; + tevent_req_received(req); + return status; +} + +/** + * Set the requirement for a certain feature on the connection + * + */ + +_PUBLIC_ void gensec_want_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (!gensec_security->ops || !gensec_security->ops->want_feature) { + gensec_security->want_features |= feature; + return; + } + gensec_security->ops->want_feature(gensec_security, feature); +} + +/** + * Check the requirement for a certain feature on the connection + * + */ + +_PUBLIC_ bool gensec_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (!gensec_security->ops || !gensec_security->ops->have_feature) { + return false; + } + + /* We might 'have' features that we don't 'want', because the + * other end demanded them, or we can't negotiate them off */ + return gensec_security->ops->have_feature(gensec_security, feature); +} + +_PUBLIC_ NTTIME gensec_expire_time(struct gensec_security *gensec_security) +{ + if (!gensec_security->ops->expire_time) { + return GENSEC_EXPIRE_TIME_INFINITY; + } + + return gensec_security->ops->expire_time(gensec_security); +} +/** + * Return the credentials structure associated with a GENSEC context + * + */ + +_PUBLIC_ struct cli_credentials *gensec_get_credentials(struct gensec_security *gensec_security) +{ + if (!gensec_security) { + return NULL; + } + return gensec_security->credentials; +} + +/** + * Set the target service (such as 'http' or 'host') on a GENSEC context - ensures it is talloc()ed + * + * This is used for Kerberos service principal name resolution. + */ + +_PUBLIC_ NTSTATUS gensec_set_target_service(struct gensec_security *gensec_security, const char *service) +{ + gensec_security->target.service = talloc_strdup(gensec_security, service); + if (!gensec_security->target.service) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +_PUBLIC_ const char *gensec_get_target_service(struct gensec_security *gensec_security) +{ + if (gensec_security->target.service) { + return gensec_security->target.service; + } + + return "host"; +} + +/** + * Set the target service (such as 'samr') on an GENSEC context - ensures it is talloc()ed. + * + * This is not the Kerberos service principal, instead this is a + * constant value that can be logged as part of authentication and + * authorization logging + */ +_PUBLIC_ NTSTATUS gensec_set_target_service_description(struct gensec_security *gensec_security, + const char *service) +{ + gensec_security->target.service_description = talloc_strdup(gensec_security, service); + if (!gensec_security->target.service_description) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +_PUBLIC_ const char *gensec_get_target_service_description(struct gensec_security *gensec_security) +{ + if (gensec_security->target.service_description) { + return gensec_security->target.service_description; + } else if (gensec_security->target.service) { + return gensec_security->target.service; + } + + return NULL; +} + +/** + * Set the target hostname (suitable for kerberos resolutation) on a GENSEC context - ensures it is talloc()ed + * + */ + +_PUBLIC_ NTSTATUS gensec_set_target_hostname(struct gensec_security *gensec_security, const char *hostname) +{ + gensec_security->target.hostname = talloc_strdup(gensec_security, hostname); + if (hostname && !gensec_security->target.hostname) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +_PUBLIC_ const char *gensec_get_target_hostname(struct gensec_security *gensec_security) +{ + /* We allow the target hostname to be overridden for testing purposes */ + if (gensec_security->settings->target_hostname) { + return gensec_security->settings->target_hostname; + } + + if (gensec_security->target.hostname) { + return gensec_security->target.hostname; + } + + /* We could add use the 'set sockaddr' call, and do a reverse + * lookup, but this would be both insecure (compromising the + * way kerberos works) and add DNS timeouts */ + return NULL; +} + +/** + * Set (and copy) local and peer socket addresses onto a socket + * context on the GENSEC context. + * + * This is so that kerberos can include these addresses in + * cryptographic tokens, to avoid certain attacks. + */ + +/** + * @brief Set the local gensec address. + * + * @param gensec_security The gensec security context to use. + * + * @param remote The local address to set. + * + * @return On success NT_STATUS_OK is returned or an NT_STATUS + * error. + */ +_PUBLIC_ NTSTATUS gensec_set_local_address(struct gensec_security *gensec_security, + const struct tsocket_address *local) +{ + TALLOC_FREE(gensec_security->local_addr); + + if (local == NULL) { + return NT_STATUS_OK; + } + + gensec_security->local_addr = tsocket_address_copy(local, gensec_security); + if (gensec_security->local_addr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/** + * @brief Set the remote gensec address. + * + * @param gensec_security The gensec security context to use. + * + * @param remote The remote address to set. + * + * @return On success NT_STATUS_OK is returned or an NT_STATUS + * error. + */ +_PUBLIC_ NTSTATUS gensec_set_remote_address(struct gensec_security *gensec_security, + const struct tsocket_address *remote) +{ + TALLOC_FREE(gensec_security->remote_addr); + + if (remote == NULL) { + return NT_STATUS_OK; + } + + gensec_security->remote_addr = tsocket_address_copy(remote, gensec_security); + if (gensec_security->remote_addr == NULL) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +/** + * @brief Get the local address from a gensec security context. + * + * @param gensec_security The security context to get the address from. + * + * @return The address as tsocket_address which could be NULL if + * no address is set. + */ +_PUBLIC_ const struct tsocket_address *gensec_get_local_address(struct gensec_security *gensec_security) +{ + if (gensec_security == NULL) { + return NULL; + } + return gensec_security->local_addr; +} + +/** + * @brief Get the remote address from a gensec security context. + * + * @param gensec_security The security context to get the address from. + * + * @return The address as tsocket_address which could be NULL if + * no address is set. + */ +_PUBLIC_ const struct tsocket_address *gensec_get_remote_address(struct gensec_security *gensec_security) +{ + if (gensec_security == NULL) { + return NULL; + } + return gensec_security->remote_addr; +} + +/** + * Set the target principal (assuming it it known, say from the SPNEGO reply) + * - ensures it is talloc()ed + * + */ + +_PUBLIC_ NTSTATUS gensec_set_target_principal(struct gensec_security *gensec_security, const char *principal) +{ + gensec_security->target.principal = talloc_strdup(gensec_security, principal); + if (!gensec_security->target.principal) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; +} + +_PUBLIC_ const char *gensec_get_target_principal(struct gensec_security *gensec_security) +{ + if (gensec_security->target.principal) { + return gensec_security->target.principal; + } + + return NULL; +} diff --git a/auth/gensec/gensec.h b/auth/gensec/gensec.h new file mode 100644 index 0000000..29d5e92 --- /dev/null +++ b/auth/gensec/gensec.h @@ -0,0 +1,327 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __GENSEC_H__ +#define __GENSEC_H__ + +#include "../lib/util/data_blob.h" +#include "libcli/util/ntstatus.h" + +#define GENSEC_SASL_NAME_NTLMSSP "NTLM" + +#define GENSEC_OID_NTLMSSP "1.3.6.1.4.1.311.2.2.10" +#define GENSEC_OID_SPNEGO "1.3.6.1.5.5.2" +#define GENSEC_OID_KERBEROS5 "1.2.840.113554.1.2.2" +#define GENSEC_OID_KERBEROS5_OLD "1.2.840.48018.1.2.2" +#define GENSEC_OID_KERBEROS5_USER2USER "1.2.840.113554.1.2.2.3" + +#define GENSEC_FINAL_AUTH_TYPE_KRB5 "krb5" +#define GENSEC_FINAL_AUTH_TYPE_NTLMSSP "NTLMSSP" + +enum gensec_priority { + GENSEC_SPNEGO = 90, + GENSEC_GSSAPI = 80, + GENSEC_KRB5 = 70, + GENSEC_SCHANNEL = 60, + GENSEC_NTLMSSP = 50, + GENSEC_SASL = 20, + GENSEC_OTHER = 10, + GENSEC_EXTERNAL = 0 +}; + +struct gensec_security; +struct gensec_target { + const char *principal; + const char *hostname; + const char *service; + const char *service_description; +}; + +#define GENSEC_FEATURE_SESSION_KEY 0x00000001 +#define GENSEC_FEATURE_SIGN 0x00000002 +#define GENSEC_FEATURE_SEAL 0x00000004 +#define GENSEC_FEATURE_DCE_STYLE 0x00000008 +#define GENSEC_FEATURE_ASYNC_REPLIES 0x00000010 +#define GENSEC_FEATURE_DATAGRAM_MODE 0x00000020 +#define GENSEC_FEATURE_SIGN_PKT_HEADER 0x00000040 +#define GENSEC_FEATURE_NEW_SPNEGO 0x00000080 +#define GENSEC_FEATURE_UNIX_TOKEN 0x00000100 +#define GENSEC_FEATURE_NTLM_CCACHE 0x00000200 +#define GENSEC_FEATURE_LDAP_STYLE 0x00000400 +#define GENSEC_FEATURE_NO_AUTHZ_LOG 0x00000800 +#define GENSEC_FEATURE_SMB_TRANSPORT 0x00001000 +#define GENSEC_FEATURE_LDAPS_TRANSPORT 0x00002000 + +#define GENSEC_EXPIRE_TIME_INFINITY (NTTIME)0x8000000000000000LL + +/* GENSEC mode */ +enum gensec_role +{ + GENSEC_SERVER, + GENSEC_CLIENT +}; + +struct auth_session_info; +struct cli_credentials; +struct gensec_settings; +struct tevent_context; +struct tevent_req; +struct smb_krb5_context; +struct tsocket_address; + +struct gensec_settings { + struct loadparm_context *lp_ctx; + const char *target_hostname; + + /* this allows callers to specify a specific set of ops that + * should be used, rather than those loaded by the plugin + * mechanism */ + const struct gensec_security_ops * const *backends; + + /* To fill in our own name in the NTLMSSP server */ + const char *server_dns_domain; + const char *server_dns_name; + const char *server_netbios_domain; + const char *server_netbios_name; +}; + +struct gensec_security_ops; +struct gensec_security_ops_wrapper; + +/* Change to 1, loadable modules now take a TALLOC_CTX * init() parameter. */ +#define GENSEC_INTERFACE_VERSION 1 + +/* this structure is used by backends to determine the size of some critical types */ +struct gensec_critical_sizes; +const struct gensec_critical_sizes *gensec_interface_version(void); + +/* Socket wrapper */ + +struct gensec_security; +struct auth4_context; +struct auth_user_info_dc; + +struct loadparm_context; + +NTSTATUS gensec_subcontext_start(TALLOC_CTX *mem_ctx, + struct gensec_security *parent, + struct gensec_security **gensec_security); +NTSTATUS gensec_client_start(TALLOC_CTX *mem_ctx, + struct gensec_security **gensec_security, + struct gensec_settings *settings); +NTSTATUS gensec_start_mech_by_ops(struct gensec_security *gensec_security, + const struct gensec_security_ops *ops); +NTSTATUS gensec_start_mech_by_sasl_list(struct gensec_security *gensec_security, + const char **sasl_names); +void gensec_set_max_update_size(struct gensec_security *gensec_security, + uint32_t max_update_size); +size_t gensec_max_update_size(struct gensec_security *gensec_security); +NTSTATUS gensec_update(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); +struct tevent_req *gensec_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in); +NTSTATUS gensec_update_recv(struct tevent_req *req, TALLOC_CTX *out_mem_ctx, DATA_BLOB *out); + +#define GENSEC_UPDATE_IS_NTERROR(status) ( \ + !NT_STATUS_IS_OK(status) && \ + !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED) \ + ) + +/** + * @brief Ask for features for a following authentication + * + * Typically only one specific feature bit should be passed, + * but it also works to ask for more features. + * + * The features must be requested before starting the + * gensec_update*() loop. + * + * The current exception is GENSEC_FEATURE_SIGN_PKT_HEADER, + * it can also be requested once the gensec_update*() loop + * returned NT_STATUS_OK. + * + * The features should not be changed during the gensec_update*() + * loop. + * + * @param[in] gensec_security The context to be used + * + * @param[in] feature The requested feature[s]. + * + */ +void gensec_want_feature(struct gensec_security *gensec_security, + uint32_t feature); +/** + * @brief Ask for one feature after the finished authentication + * + * Because the return value is bool, the caller can only + * ask for one feature at a time. + * + * The features must be requested after the finished + * gensec_update*() loop. + * + * The current exception is GENSEC_FEATURE_SIGN_PKT_HEADER, + * it can also be requested before the gensec_update*() loop, + * as the return value only indicates if the backend supports + * dcerpc header signing, not if header signing will be used + * between client and server. It will be used only if the caller + * also used gensec_want_feature(GENSEC_FEATURE_SIGN_PKT_HEADER). + * + * @param[in] gensec_security The context to be used. + * + * @param[in] feature The requested feature. + * + * @return true if the feature is supported, false if not. + */ +bool gensec_have_feature(struct gensec_security *gensec_security, + uint32_t feature); +NTTIME gensec_expire_time(struct gensec_security *gensec_security); +NTSTATUS gensec_set_credentials(struct gensec_security *gensec_security, struct cli_credentials *credentials); +/** + * Set the target service (such as 'http' or 'host') on a GENSEC context - ensures it is talloc()ed + * + * This is used for Kerberos service principal name resolution. + */ + +NTSTATUS gensec_set_target_service(struct gensec_security *gensec_security, const char *service); +const char *gensec_get_target_service(struct gensec_security *gensec_security); +NTSTATUS gensec_set_target_hostname(struct gensec_security *gensec_security, const char *hostname); +const char *gensec_get_target_hostname(struct gensec_security *gensec_security); +/** + * Set the target service (such as 'samr') on an GENSEC context - ensures it is talloc()ed. + * + * This is not the Kerberos service principal, instead this is a + * constant value that can be logged as part of authentication and + * authorization logging + */ +const char *gensec_get_target_service_description(struct gensec_security *gensec_security); +NTSTATUS gensec_set_target_service_description(struct gensec_security *gensec_security, + const char *service); +NTSTATUS gensec_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key); +NTSTATUS gensec_start_mech_by_oid(struct gensec_security *gensec_security, + const char *mech_oid); +const char *gensec_get_name_by_oid(struct gensec_security *gensec_security, const char *oid_string); +struct cli_credentials *gensec_get_credentials(struct gensec_security *gensec_security); +NTSTATUS gensec_init(void); +NTSTATUS gensec_register(TALLOC_CTX *ctx, + const struct gensec_security_ops *ops); +const struct gensec_security_ops *gensec_security_by_oid(struct gensec_security *gensec_security, + const char *oid_string); +const struct gensec_security_ops *gensec_security_by_sasl_name(struct gensec_security *gensec_security, + const char *sasl_name); +const struct gensec_security_ops *gensec_security_by_auth_type( + struct gensec_security *gensec_security, + uint32_t auth_type); +const struct gensec_security_ops *gensec_security_by_name(struct gensec_security *gensec_security, + const char *name); +const struct gensec_security_ops **gensec_security_mechs(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx); +const struct gensec_security_ops_wrapper *gensec_security_by_oid_list( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char * const *oid_strings, + const char *skip); +const char **gensec_security_oids(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char *skip); +const char **gensec_security_oids_from_ops_wrapped(TALLOC_CTX *mem_ctx, + const struct gensec_security_ops_wrapper *wops); +size_t gensec_max_input_size(struct gensec_security *gensec_security); +size_t gensec_max_wrapped_size(struct gensec_security *gensec_security); +NTSTATUS gensec_unseal_packet(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +NTSTATUS gensec_check_packet(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +size_t gensec_sig_size(struct gensec_security *gensec_security, size_t data_size); +NTSTATUS gensec_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS gensec_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS gensec_start_mech_by_authtype(struct gensec_security *gensec_security, + uint8_t auth_type, uint8_t auth_level); +const char *gensec_get_name_by_authtype(struct gensec_security *gensec_security, uint8_t authtype); +NTSTATUS gensec_server_start(TALLOC_CTX *mem_ctx, + struct gensec_settings *settings, + struct auth4_context *auth_context, + struct gensec_security **gensec_security); +NTSTATUS gensec_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info); + +NTSTATUS gensec_set_local_address(struct gensec_security *gensec_security, + const struct tsocket_address *local); +NTSTATUS gensec_set_remote_address(struct gensec_security *gensec_security, + const struct tsocket_address *remote); +const struct tsocket_address *gensec_get_local_address(struct gensec_security *gensec_security); +const struct tsocket_address *gensec_get_remote_address(struct gensec_security *gensec_security); + +NTSTATUS gensec_start_mech_by_name(struct gensec_security *gensec_security, + const char *name); + +NTSTATUS gensec_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS gensec_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + +bool gensec_security_ops_enabled(const struct gensec_security_ops *ops, struct gensec_security *security); + +NTSTATUS gensec_start_mech_by_sasl_name(struct gensec_security *gensec_security, + const char *sasl_name); +const char **gensec_security_sasl_names(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx); + +int gensec_setting_int(struct gensec_settings *settings, const char *mechanism, const char *name, int default_value); +bool gensec_setting_bool(struct gensec_settings *settings, const char *mechanism, const char *name, bool default_value); + +NTSTATUS gensec_set_target_principal(struct gensec_security *gensec_security, const char *principal); +const char *gensec_get_target_principal(struct gensec_security *gensec_security); + +NTSTATUS gensec_generate_session_info_pac(TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + struct smb_krb5_context *smb_krb5_context, + DATA_BLOB *pac_blob, + const char *principal_string, + const struct tsocket_address *remote_address, + struct auth_session_info **session_info); + +NTSTATUS gensec_magic_check_krb5_oid(struct gensec_security *unused, + const DATA_BLOB *blob); + +#endif /* __GENSEC_H__ */ diff --git a/auth/gensec/gensec_internal.h b/auth/gensec/gensec_internal.h new file mode 100644 index 0000000..8efb1bd --- /dev/null +++ b/auth/gensec/gensec_internal.h @@ -0,0 +1,183 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#ifndef __GENSEC_INTERNAL_H__ +#define __GENSEC_INTERNAL_H__ + +struct gensec_security; + +struct gensec_security_ops { + const char *name; + const char *sasl_name; + bool weak_crypto; + uint8_t auth_type; /* 0 if not offered on DCE-RPC */ + const char **oid; /* NULL if not offered by SPNEGO */ + NTSTATUS (*client_start)(struct gensec_security *gensec_security); + NTSTATUS (*server_start)(struct gensec_security *gensec_security); + /** + Determine if a packet has the right 'magic' for this mechanism + */ + NTSTATUS (*magic)(struct gensec_security *gensec_security, + const DATA_BLOB *first_packet); + struct tevent_req *(*update_send)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in); + NTSTATUS (*update_recv)(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); + NTSTATUS (*may_reset_crypto)(struct gensec_security *gensec_security, + bool full_reset); + NTSTATUS (*seal_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); + NTSTATUS (*sign_packet)(struct gensec_security *gensec_security, TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); + size_t (*sig_size)(struct gensec_security *gensec_security, size_t data_size); + size_t (*max_input_size)(struct gensec_security *gensec_security); + size_t (*max_wrapped_size)(struct gensec_security *gensec_security); + NTSTATUS (*check_packet)(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); + NTSTATUS (*unseal_packet)(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); + NTSTATUS (*wrap)(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + NTSTATUS (*unwrap)(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + NTSTATUS (*session_key)(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key); + NTSTATUS (*session_info)(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info); + void (*want_feature)(struct gensec_security *gensec_security, + uint32_t feature); + bool (*have_feature)(struct gensec_security *gensec_security, + uint32_t feature); + NTTIME (*expire_time)(struct gensec_security *gensec_security); + const char *(*final_auth_type)(struct gensec_security *gensec_security); + bool enabled; + bool kerberos; + enum gensec_priority priority; + bool glue; +}; + +struct gensec_security_ops_wrapper { + const struct gensec_security_ops *op; + const char *oid; +}; + +struct gensec_security { + const struct gensec_security_ops *ops; + void *private_data; + struct cli_credentials *credentials; + struct gensec_target target; + enum gensec_role gensec_role; + bool subcontext; + uint32_t want_features; + uint32_t max_update_size; + uint8_t dcerpc_auth_level; + struct tsocket_address *local_addr, *remote_addr; + struct gensec_settings *settings; + + /* When we are a server, this may be filled in to provide an + * NTLM authentication backend, and user lookup (such as if no + * PAC is found) */ + struct auth4_context *auth_context; + + struct gensec_security *parent_security; + struct gensec_security *child_security; + + /* + * This is used to mark the context as being + * busy in an async gensec_update_send(). + */ + struct gensec_security **update_busy_ptr; +}; + +/* this structure is used by backends to determine the size of some critical types */ +struct gensec_critical_sizes { + int interface_version; + int sizeof_gensec_security_ops; + int sizeof_gensec_security; +}; + +NTSTATUS gensec_may_reset_crypto(struct gensec_security *gensec_security, + bool full_reset); + +const char *gensec_final_auth_type(struct gensec_security *gensec_security); + +NTSTATUS gensec_child_ready(struct gensec_security *parent, + struct gensec_security *child); +void gensec_child_want_feature(struct gensec_security *gensec_security, + uint32_t feature); +bool gensec_child_have_feature(struct gensec_security *gensec_security, + uint32_t feature); +NTSTATUS gensec_child_unseal_packet(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +NTSTATUS gensec_child_check_packet(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +NTSTATUS gensec_child_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS gensec_child_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS gensec_child_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS gensec_child_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +size_t gensec_child_sig_size(struct gensec_security *gensec_security, + size_t data_size); +size_t gensec_child_max_input_size(struct gensec_security *gensec_security); +size_t gensec_child_max_wrapped_size(struct gensec_security *gensec_security); +NTSTATUS gensec_child_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key); +NTSTATUS gensec_child_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info); +NTTIME gensec_child_expire_time(struct gensec_security *gensec_security); +const char *gensec_child_final_auth_type(struct gensec_security *gensec_security); + +#endif /* __GENSEC_H__ */ diff --git a/auth/gensec/gensec_start.c b/auth/gensec/gensec_start.c new file mode 100644 index 0000000..bd5b725 --- /dev/null +++ b/auth/gensec/gensec_start.c @@ -0,0 +1,1147 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/network.h" +#include "tevent.h" +#include "../lib/util/tevent_ntstatus.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "lib/param/param.h" +#include "lib/param/loadparm.h" +#include "lib/util/tsort.h" +#include "lib/util/samba_modules.h" +#include "lib/util/base64.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#undef strcasecmp + +/* the list of currently registered GENSEC backends */ +static const struct gensec_security_ops **generic_security_ops; +static int gensec_num_backends; + +bool gensec_security_ops_enabled(const struct gensec_security_ops *ops, struct gensec_security *security) +{ + bool ok = lpcfg_parm_bool(security->settings->lp_ctx, + NULL, + "gensec", + ops->name, + ops->enabled); + + if (ops->weak_crypto && + lpcfg_weak_crypto(security->settings->lp_ctx) != SAMBA_WEAK_CRYPTO_ALLOWED) { + ok = false; + } + + return ok; +} + +/* Sometimes we want to force only kerberos, sometimes we want to + * force it's avoidance. The old list could be either + * gensec_security_all(), or from cli_credentials_gensec_list() (ie, + * an existing list we have trimmed down) + * + * The intended logic is: + * + * if we are in the default AUTO have kerberos: + * - take a reference to the master list + * otherwise + * - always add spnego then: + * - if we 'MUST' have kerberos: + * only add kerberos mechs + * - if we 'DONT' want kerberos': + * only add non-kerberos mechs + * + * Once we get things like NegoEx or moonshot, this will of course get + * more compplex. + */ + +static const struct gensec_security_ops **gensec_use_kerberos_mechs( + TALLOC_CTX *mem_ctx, + const struct gensec_security_ops * const *old_gensec_list, + enum credentials_use_kerberos use_kerberos, + bool keep_schannel) +{ + const struct gensec_security_ops **new_gensec_list; + int i, j, num_mechs_in; + + for (num_mechs_in=0; old_gensec_list && old_gensec_list[num_mechs_in]; num_mechs_in++) { + /* noop */ + } + + new_gensec_list = talloc_array(mem_ctx, + const struct gensec_security_ops *, + num_mechs_in + 1); + if (!new_gensec_list) { + return NULL; + } + + j = 0; + for (i=0; old_gensec_list && old_gensec_list[i]; i++) { + bool keep = false; + + /* + * We want to keep SPNGEO and other backends + */ + keep = old_gensec_list[i]->glue; + + if (old_gensec_list[i]->auth_type == DCERPC_AUTH_TYPE_SCHANNEL) { + keep = keep_schannel; + } + + switch (use_kerberos) { + case CRED_USE_KERBEROS_DESIRED: + keep = true; + break; + + case CRED_USE_KERBEROS_DISABLED: + if (old_gensec_list[i]->kerberos == false) { + keep = true; + } + + break; + + case CRED_USE_KERBEROS_REQUIRED: + if (old_gensec_list[i]->kerberos == true) { + keep = true; + } + + break; + default: + /* Can't happen or invalid parameter */ + return NULL; + } + + if (!keep) { + continue; + } + + new_gensec_list[j] = old_gensec_list[i]; + j++; + } + new_gensec_list[j] = NULL; + + return new_gensec_list; +} + +_PUBLIC_ const struct gensec_security_ops **gensec_security_mechs( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx) +{ + const struct gensec_security_ops * const *backends = + generic_security_ops; + enum credentials_use_kerberos use_kerberos = CRED_USE_KERBEROS_DESIRED; + bool keep_schannel = false; + + if (gensec_security != NULL) { + struct cli_credentials *creds = NULL; + + creds = gensec_get_credentials(gensec_security); + if (creds != NULL) { + use_kerberos = cli_credentials_get_kerberos_state(creds); + if (cli_credentials_get_netlogon_creds(creds) != NULL) { + keep_schannel = true; + } + + /* + * Even if Kerberos is set to REQUIRED, keep the + * schannel auth mechanism that machine accounts are + * able to authenticate via netlogon. + */ + if (gensec_security->gensec_role == GENSEC_SERVER) { + keep_schannel = true; + } + } + + if (gensec_security->settings->backends) { + backends = gensec_security->settings->backends; + } + } + + return gensec_use_kerberos_mechs(mem_ctx, backends, + use_kerberos, keep_schannel); + +} + +_PUBLIC_ const struct gensec_security_ops *gensec_security_by_oid( + struct gensec_security *gensec_security, + const char *oid_string) +{ + int i, j; + const struct gensec_security_ops **backends; + const struct gensec_security_ops *backend; + TALLOC_CTX *mem_ctx = talloc_new(gensec_security); + if (!mem_ctx) { + return NULL; + } + backends = gensec_security_mechs(gensec_security, mem_ctx); + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], + gensec_security)) + continue; + if (backends[i]->oid) { + for (j=0; backends[i]->oid[j]; j++) { + if (backends[i]->oid[j] && + (strcmp(backends[i]->oid[j], oid_string) == 0)) { + backend = backends[i]; + talloc_free(mem_ctx); + return backend; + } + } + } + } + talloc_free(mem_ctx); + + return NULL; +} + +_PUBLIC_ const struct gensec_security_ops *gensec_security_by_sasl_name( + struct gensec_security *gensec_security, + const char *sasl_name) +{ + int i; + const struct gensec_security_ops **backends; + const struct gensec_security_ops *backend; + TALLOC_CTX *mem_ctx = talloc_new(gensec_security); + if (!mem_ctx) { + return NULL; + } + backends = gensec_security_mechs(gensec_security, mem_ctx); + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) { + continue; + } + if (backends[i]->sasl_name + && (strcmp(backends[i]->sasl_name, sasl_name) == 0)) { + backend = backends[i]; + talloc_free(mem_ctx); + return backend; + } + } + talloc_free(mem_ctx); + + return NULL; +} + +_PUBLIC_ const struct gensec_security_ops *gensec_security_by_auth_type( + struct gensec_security *gensec_security, + uint32_t auth_type) +{ + int i; + const struct gensec_security_ops **backends; + const struct gensec_security_ops *backend; + TALLOC_CTX *mem_ctx; + + if (auth_type == DCERPC_AUTH_TYPE_NONE) { + return NULL; + } + + mem_ctx = talloc_new(gensec_security); + if (!mem_ctx) { + return NULL; + } + backends = gensec_security_mechs(gensec_security, mem_ctx); + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) { + continue; + } + if (backends[i]->auth_type == auth_type) { + backend = backends[i]; + talloc_free(mem_ctx); + return backend; + } + } + talloc_free(mem_ctx); + + return NULL; +} + +const struct gensec_security_ops *gensec_security_by_name(struct gensec_security *gensec_security, + const char *name) +{ + int i; + const struct gensec_security_ops **backends; + const struct gensec_security_ops *backend; + TALLOC_CTX *mem_ctx = talloc_new(gensec_security); + if (!mem_ctx) { + return NULL; + } + backends = gensec_security_mechs(gensec_security, mem_ctx); + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) + continue; + if (backends[i]->name + && (strcmp(backends[i]->name, name) == 0)) { + backend = backends[i]; + talloc_free(mem_ctx); + return backend; + } + } + talloc_free(mem_ctx); + return NULL; +} + +static const char **gensec_security_sasl_names_from_ops( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const struct gensec_security_ops * const *ops) +{ + const char **sasl_names = NULL; + size_t i, sasl_names_count = 0; + + if (ops == NULL) { + return NULL; + } + + sasl_names = talloc_array(mem_ctx, const char *, 1); + if (sasl_names == NULL) { + return NULL; + } + + for (i = 0; ops[i] != NULL; i++) { + enum gensec_role role = GENSEC_SERVER; + const char **tmp = NULL; + + if (ops[i]->sasl_name == NULL) { + continue; + } + + if (gensec_security != NULL) { + if (!gensec_security_ops_enabled(ops[i], + gensec_security)) { + continue; + } + + role = gensec_security->gensec_role; + } + + switch (role) { + case GENSEC_CLIENT: + if (ops[i]->client_start == NULL) { + continue; + } + break; + case GENSEC_SERVER: + if (ops[i]->server_start == NULL) { + continue; + } + break; + } + + tmp = talloc_realloc(mem_ctx, + sasl_names, + const char *, + sasl_names_count + 2); + if (tmp == NULL) { + TALLOC_FREE(sasl_names); + return NULL; + } + sasl_names = tmp; + + sasl_names[sasl_names_count] = ops[i]->sasl_name; + sasl_names_count++; + } + sasl_names[sasl_names_count] = NULL; + + return sasl_names; +} + +/** + * @brief Get the sasl names from the gensec security context. + * + * @param[in] gensec_security The gensec security context. + * + * @param[in] mem_ctx The memory context to allocate memory on. + * + * @return An allocated array with sasl names, NULL on error. + */ +_PUBLIC_ +const char **gensec_security_sasl_names(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx) +{ + const struct gensec_security_ops **ops = NULL; + + ops = gensec_security_mechs(gensec_security, mem_ctx); + + return gensec_security_sasl_names_from_ops(gensec_security, + mem_ctx, + ops); +} + +/** + * Return a unique list of security subsystems from those specified in + * the list of SASL names. + * + * Use the list of enabled GENSEC mechanisms from the credentials + * attached to the gensec_security, and return in our preferred order. + */ + +static const struct gensec_security_ops **gensec_security_by_sasl_list( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char **sasl_names) +{ + const struct gensec_security_ops **backends_out; + const struct gensec_security_ops **backends; + int i, k, sasl_idx; + int num_backends_out = 0; + + if (!sasl_names) { + return NULL; + } + + backends = gensec_security_mechs(gensec_security, mem_ctx); + + backends_out = talloc_array(mem_ctx, const struct gensec_security_ops *, 1); + if (!backends_out) { + return NULL; + } + backends_out[0] = NULL; + + /* Find backends in our preferred order, by walking our list, + * then looking in the supplied list */ + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) + continue; + for (sasl_idx = 0; sasl_names[sasl_idx]; sasl_idx++) { + if (!backends[i]->sasl_name || + !(strcmp(backends[i]->sasl_name, + sasl_names[sasl_idx]) == 0)) { + continue; + } + + for (k=0; backends_out[k]; k++) { + if (backends_out[k] == backends[i]) { + break; + } + } + + if (k < num_backends_out) { + /* already in there */ + continue; + } + + backends_out = talloc_realloc(mem_ctx, backends_out, + const struct gensec_security_ops *, + num_backends_out + 2); + if (!backends_out) { + return NULL; + } + + backends_out[num_backends_out] = backends[i]; + num_backends_out++; + backends_out[num_backends_out] = NULL; + } + } + return backends_out; +} + +/** + * Return a unique list of security subsystems from those specified in + * the OID list. That is, where two OIDs refer to the same module, + * return that module only once. + * + * Use the list of enabled GENSEC mechanisms from the credentials + * attached to the gensec_security, and return in our preferred order. + */ + +_PUBLIC_ const struct gensec_security_ops_wrapper *gensec_security_by_oid_list( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char * const *oid_strings, + const char *skip) +{ + struct gensec_security_ops_wrapper *backends_out; + const struct gensec_security_ops **backends; + int i, j, k, oid_idx; + int num_backends_out = 0; + + if (!oid_strings) { + return NULL; + } + + backends = gensec_security_mechs(gensec_security, gensec_security); + + backends_out = talloc_array(mem_ctx, struct gensec_security_ops_wrapper, 1); + if (!backends_out) { + return NULL; + } + backends_out[0].op = NULL; + backends_out[0].oid = NULL; + + /* Find backends in our preferred order, by walking our list, + * then looking in the supplied list */ + for (i=0; backends && backends[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(backends[i], gensec_security)) + continue; + if (!backends[i]->oid) { + continue; + } + for (oid_idx = 0; oid_strings[oid_idx]; oid_idx++) { + if (strcmp(oid_strings[oid_idx], skip) == 0) { + continue; + } + + for (j=0; backends[i]->oid[j]; j++) { + if (!backends[i]->oid[j] || + !(strcmp(backends[i]->oid[j], + oid_strings[oid_idx]) == 0)) { + continue; + } + + for (k=0; backends_out[k].op; k++) { + if (backends_out[k].op == backends[i]) { + break; + } + } + + if (k < num_backends_out) { + /* already in there */ + continue; + } + + backends_out = talloc_realloc(mem_ctx, backends_out, + struct gensec_security_ops_wrapper, + num_backends_out + 2); + if (!backends_out) { + return NULL; + } + + backends_out[num_backends_out].op = backends[i]; + backends_out[num_backends_out].oid = backends[i]->oid[j]; + num_backends_out++; + backends_out[num_backends_out].op = NULL; + backends_out[num_backends_out].oid = NULL; + } + } + } + return backends_out; +} + +/** + * Return OIDS from the security subsystems listed + */ + +static const char **gensec_security_oids_from_ops( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const struct gensec_security_ops * const *ops, + const char *skip) +{ + int i; + int j = 0; + int k; + const char **oid_list; + if (!ops) { + return NULL; + } + oid_list = talloc_array(mem_ctx, const char *, 1); + if (!oid_list) { + return NULL; + } + + for (i=0; ops && ops[i]; i++) { + if (gensec_security != NULL && + !gensec_security_ops_enabled(ops[i], gensec_security)) { + continue; + } + if (!ops[i]->oid) { + continue; + } + + for (k = 0; ops[i]->oid[k]; k++) { + if (skip && strcmp(skip, ops[i]->oid[k])==0) { + } else { + oid_list = talloc_realloc(mem_ctx, oid_list, const char *, j + 2); + if (!oid_list) { + return NULL; + } + oid_list[j] = ops[i]->oid[k]; + j++; + } + } + } + oid_list[j] = NULL; + return oid_list; +} + + +/** + * Return OIDS from the security subsystems listed + */ + +_PUBLIC_ const char **gensec_security_oids_from_ops_wrapped(TALLOC_CTX *mem_ctx, + const struct gensec_security_ops_wrapper *wops) +{ + int i; + int j = 0; + int k; + const char **oid_list; + if (!wops) { + return NULL; + } + oid_list = talloc_array(mem_ctx, const char *, 1); + if (!oid_list) { + return NULL; + } + + for (i=0; wops[i].op; i++) { + if (!wops[i].op->oid) { + continue; + } + + for (k = 0; wops[i].op->oid[k]; k++) { + oid_list = talloc_realloc(mem_ctx, oid_list, const char *, j + 2); + if (!oid_list) { + return NULL; + } + oid_list[j] = wops[i].op->oid[k]; + j++; + } + } + oid_list[j] = NULL; + return oid_list; +} + + +/** + * Return all the security subsystems currently enabled on a GENSEC context. + * + * This is taken from a list attached to the cli_credentials, and + * skips the OID in 'skip'. (Typically the SPNEGO OID) + * + */ + +_PUBLIC_ const char **gensec_security_oids(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const char *skip) +{ + const struct gensec_security_ops **ops; + + ops = gensec_security_mechs(gensec_security, mem_ctx); + + return gensec_security_oids_from_ops(gensec_security, mem_ctx, ops, skip); +} + +static int gensec_security_destructor(struct gensec_security *gctx) +{ + if (gctx->parent_security != NULL) { + if (gctx->parent_security->child_security == gctx) { + gctx->parent_security->child_security = NULL; + } + gctx->parent_security = NULL; + } + + if (gctx->child_security != NULL) { + if (gctx->child_security->parent_security == gctx) { + gctx->child_security->parent_security = NULL; + } + gctx->child_security = NULL; + } + + return 0; +} + +/** + Start the GENSEC system, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. + @note, the auth context is moved to be a referenced pointer of the + @ gensec_security return +*/ +static NTSTATUS gensec_start(TALLOC_CTX *mem_ctx, + struct gensec_settings *settings, + struct auth4_context *auth_context, + struct gensec_security **gensec_security) +{ + (*gensec_security) = talloc_zero(mem_ctx, struct gensec_security); + NT_STATUS_HAVE_NO_MEMORY(*gensec_security); + + (*gensec_security)->max_update_size = 0; + + SMB_ASSERT(settings->lp_ctx != NULL); + (*gensec_security)->settings = talloc_reference(*gensec_security, settings); + + /* We need to reference this, not steal, as the caller may be + * python, which won't like it if we steal it's object away + * from it */ + (*gensec_security)->auth_context = talloc_reference(*gensec_security, auth_context); + + talloc_set_destructor((*gensec_security), gensec_security_destructor); + return NT_STATUS_OK; +} + +/** + * Start a GENSEC subcontext, with a copy of the properties of the parent + * @param mem_ctx The parent TALLOC memory context. + * @param parent The parent GENSEC context + * @param gensec_security Returned GENSEC context pointer. + * @note Used by SPNEGO in particular, for the actual implementation mechanism + */ + +_PUBLIC_ NTSTATUS gensec_subcontext_start(TALLOC_CTX *mem_ctx, + struct gensec_security *parent, + struct gensec_security **gensec_security) +{ + if (parent->child_security != NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + (*gensec_security) = talloc_zero(mem_ctx, struct gensec_security); + NT_STATUS_HAVE_NO_MEMORY(*gensec_security); + + (**gensec_security) = *parent; + (*gensec_security)->ops = NULL; + (*gensec_security)->private_data = NULL; + (*gensec_security)->update_busy_ptr = NULL; + + (*gensec_security)->subcontext = true; + (*gensec_security)->want_features = parent->want_features; + (*gensec_security)->max_update_size = parent->max_update_size; + (*gensec_security)->dcerpc_auth_level = parent->dcerpc_auth_level; + (*gensec_security)->auth_context = talloc_reference(*gensec_security, parent->auth_context); + (*gensec_security)->settings = talloc_reference(*gensec_security, parent->settings); + (*gensec_security)->auth_context = talloc_reference(*gensec_security, parent->auth_context); + + talloc_set_destructor((*gensec_security), gensec_security_destructor); + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS gensec_child_ready(struct gensec_security *parent, + struct gensec_security *child) +{ + if (parent->child_security != NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + if (child->parent_security != NULL) { + return NT_STATUS_INTERNAL_ERROR; + } + + parent->child_security = child; + child->parent_security = parent; + return NT_STATUS_OK; +} + +/** + Start the GENSEC system, in client mode, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. +*/ +_PUBLIC_ NTSTATUS gensec_client_start(TALLOC_CTX *mem_ctx, + struct gensec_security **gensec_security, + struct gensec_settings *settings) +{ + NTSTATUS status; + + if (settings == NULL) { + DEBUG(0,("gensec_client_start: no settings given!\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + status = gensec_start(mem_ctx, settings, NULL, gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + (*gensec_security)->gensec_role = GENSEC_CLIENT; + + return status; +} + + + +/** + Start the GENSEC system, in server mode, returning a context pointer. + @param mem_ctx The parent TALLOC memory context. + @param gensec_security Returned GENSEC context pointer. + @note The mem_ctx is only a parent and may be NULL. +*/ +_PUBLIC_ NTSTATUS gensec_server_start(TALLOC_CTX *mem_ctx, + struct gensec_settings *settings, + struct auth4_context *auth_context, + struct gensec_security **gensec_security) +{ + NTSTATUS status; + + if (!settings) { + DEBUG(0,("gensec_server_start: no settings given!\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + status = gensec_start(mem_ctx, settings, auth_context, gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + (*gensec_security)->gensec_role = GENSEC_SERVER; + + return status; +} + +static NTSTATUS gensec_start_mech(struct gensec_security *gensec_security) +{ + NTSTATUS status; + + /* + * Callers sometimes just reuse a context, we should + * clear the internal state before starting it again. + */ + talloc_unlink(gensec_security, gensec_security->private_data); + gensec_security->private_data = NULL; + + if (gensec_security->child_security != NULL) { + /* + * The talloc_unlink(.., gensec_security->private_data) + * should have cleared this via + * gensec_security_destructor(). + */ + return NT_STATUS_INTERNAL_ERROR; + } + + if (gensec_security->credentials) { + const char *forced_mech = cli_credentials_get_forced_sasl_mech(gensec_security->credentials); + if (forced_mech && + (gensec_security->ops->sasl_name == NULL || + strcasecmp(forced_mech, gensec_security->ops->sasl_name) != 0)) { + DEBUG(5, ("GENSEC mechanism %s (%s) skipped, as it " + "did not match forced mechanism %s\n", + gensec_security->ops->name, + gensec_security->ops->sasl_name, + forced_mech)); + return NT_STATUS_INVALID_PARAMETER; + } + } + DEBUG(5, ("Starting GENSEC %smechanism %s\n", + gensec_security->subcontext ? "sub" : "", + gensec_security->ops->name)); + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + if (gensec_security->ops->client_start) { + status = gensec_security->ops->client_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(gensec_security->subcontext?4:2, ("Failed to start GENSEC client mech %s: %s\n", + gensec_security->ops->name, nt_errstr(status))); + } + return status; + } + break; + case GENSEC_SERVER: + if (gensec_security->ops->server_start) { + status = gensec_security->ops->server_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Failed to start GENSEC server mech %s: %s\n", + gensec_security->ops->name, nt_errstr(status))); + } + return status; + } + break; + } + return NT_STATUS_INVALID_PARAMETER; +} + +/** + * Start a GENSEC sub-mechanism with a specified mechansim structure, used in SPNEGO + * + */ + +NTSTATUS gensec_start_mech_by_ops(struct gensec_security *gensec_security, + const struct gensec_security_ops *ops) +{ + gensec_security->ops = ops; + return gensec_start_mech(gensec_security); +} + + +/** + * Start a GENSEC sub-mechanism by DCERPC allocated 'auth type' number + * @param gensec_security GENSEC context pointer. + * @param auth_type DCERPC auth type + * @param auth_level DCERPC auth level + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_authtype(struct gensec_security *gensec_security, + uint8_t auth_type, uint8_t auth_level) +{ + gensec_security->ops = gensec_security_by_auth_type(gensec_security, auth_type); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for auth_type=%d\n", (int)auth_type)); + return NT_STATUS_INVALID_PARAMETER; + } + gensec_security->dcerpc_auth_level = auth_level; + /* + * We need to reset sign/seal in order to reset it. + * We may got some default features inherited by the credentials + */ + gensec_security->want_features &= ~GENSEC_FEATURE_SIGN; + gensec_security->want_features &= ~GENSEC_FEATURE_SEAL; + gensec_want_feature(gensec_security, GENSEC_FEATURE_DCE_STYLE); + gensec_want_feature(gensec_security, GENSEC_FEATURE_ASYNC_REPLIES); + if (auth_level == DCERPC_AUTH_LEVEL_INTEGRITY) { + if (gensec_security->gensec_role == GENSEC_CLIENT) { + gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN); + } + } else if (auth_level == DCERPC_AUTH_LEVEL_PACKET) { + /* + * For connection oriented DCERPC DCERPC_AUTH_LEVEL_PACKET (4) + * has the same behavior as DCERPC_AUTH_LEVEL_INTEGRITY (5). + */ + if (gensec_security->gensec_role == GENSEC_CLIENT) { + gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN); + } + } else if (auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + gensec_want_feature(gensec_security, GENSEC_FEATURE_SIGN); + gensec_want_feature(gensec_security, GENSEC_FEATURE_SEAL); + } else if (auth_level == DCERPC_AUTH_LEVEL_CONNECT) { + /* Default features */ + } else { + DEBUG(2,("auth_level %d not supported in DCE/RPC authentication\n", + auth_level)); + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_start_mech(gensec_security); +} + +_PUBLIC_ const char *gensec_get_name_by_authtype(struct gensec_security *gensec_security, uint8_t authtype) +{ + const struct gensec_security_ops *ops; + ops = gensec_security_by_auth_type(gensec_security, authtype); + if (ops) { + return ops->name; + } + return NULL; +} + + +_PUBLIC_ const char *gensec_get_name_by_oid(struct gensec_security *gensec_security, + const char *oid_string) +{ + const struct gensec_security_ops *ops; + ops = gensec_security_by_oid(gensec_security, oid_string); + if (ops) { + return ops->name; + } + return oid_string; +} + +/** + * Start a GENSEC sub-mechanism by OID, used in SPNEGO + * + * @note This should also be used when you wish to just start NLTMSSP (for example), as it uses a + * well-known #define to hook it in. + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_oid(struct gensec_security *gensec_security, + const char *mech_oid) +{ + SMB_ASSERT(gensec_security != NULL); + + gensec_security->ops = gensec_security_by_oid(gensec_security, mech_oid); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for oid=%s\n", mech_oid)); + return NT_STATUS_INVALID_PARAMETER; + } + return gensec_start_mech(gensec_security); +} + +/** + * Start a GENSEC sub-mechanism by a well know SASL name + * + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_sasl_name(struct gensec_security *gensec_security, + const char *sasl_name) +{ + gensec_security->ops = gensec_security_by_sasl_name(gensec_security, sasl_name); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for sasl_name=%s\n", sasl_name)); + return NT_STATUS_INVALID_PARAMETER; + } + return gensec_start_mech(gensec_security); +} + +/** + * Start a GENSEC sub-mechanism with the preferred option from a SASL name list + * + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_sasl_list(struct gensec_security *gensec_security, + const char **sasl_names) +{ + NTSTATUS nt_status = NT_STATUS_INVALID_PARAMETER; + TALLOC_CTX *mem_ctx = talloc_new(gensec_security); + const struct gensec_security_ops **ops; + int i; + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + ops = gensec_security_by_sasl_list(gensec_security, mem_ctx, sasl_names); + if (!ops || !*ops) { + DEBUG(3, ("Could not find GENSEC backend for any of sasl_name = %s\n", + str_list_join(mem_ctx, + sasl_names, ' '))); + talloc_free(mem_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + for (i=0; ops[i]; i++) { + nt_status = gensec_start_mech_by_ops(gensec_security, ops[i]); + if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER)) { + break; + } + } + talloc_free(mem_ctx); + return nt_status; +} + +/** + * Start a GENSEC sub-mechanism by an internal name + * + */ + +_PUBLIC_ NTSTATUS gensec_start_mech_by_name(struct gensec_security *gensec_security, + const char *name) +{ + gensec_security->ops = gensec_security_by_name(gensec_security, name); + if (!gensec_security->ops) { + DEBUG(3, ("Could not find GENSEC backend for name=%s\n", name)); + return NT_STATUS_INVALID_PARAMETER; + } + return gensec_start_mech(gensec_security); +} + +/** + * Associate a credentials structure with a GENSEC context - talloc_reference()s it to the context + * + */ + +_PUBLIC_ NTSTATUS gensec_set_credentials(struct gensec_security *gensec_security, struct cli_credentials *credentials) +{ + gensec_security->credentials = talloc_reference(gensec_security, credentials); + NT_STATUS_HAVE_NO_MEMORY(gensec_security->credentials); + gensec_want_feature(gensec_security, cli_credentials_get_gensec_features(gensec_security->credentials)); + return NT_STATUS_OK; +} + +/* + register a GENSEC backend. + + The 'name' can be later used by other backends to find the operations + structure for this backend. +*/ +_PUBLIC_ NTSTATUS gensec_register(TALLOC_CTX *ctx, + const struct gensec_security_ops *ops) +{ + if (gensec_security_by_name(NULL, ops->name) != NULL) { + /* its already registered! */ + DEBUG(0,("GENSEC backend '%s' already registered\n", + ops->name)); + return NT_STATUS_OBJECT_NAME_COLLISION; + } + + generic_security_ops = talloc_realloc(ctx, + generic_security_ops, + const struct gensec_security_ops *, + gensec_num_backends+2); + if (!generic_security_ops) { + return NT_STATUS_NO_MEMORY; + } + + generic_security_ops[gensec_num_backends] = ops; + gensec_num_backends++; + generic_security_ops[gensec_num_backends] = NULL; + + DEBUG(3,("GENSEC backend '%s' registered\n", + ops->name)); + + return NT_STATUS_OK; +} + +/* + return the GENSEC interface version, and the size of some critical types + This can be used by backends to either detect compilation errors, or provide + multiple implementations for different smbd compilation options in one module +*/ +_PUBLIC_ const struct gensec_critical_sizes *gensec_interface_version(void) +{ + static const struct gensec_critical_sizes critical_sizes = { + GENSEC_INTERFACE_VERSION, + sizeof(struct gensec_security_ops), + sizeof(struct gensec_security), + }; + + return &critical_sizes; +} + +static int sort_gensec(const struct gensec_security_ops **gs1, const struct gensec_security_ops **gs2) { + return (*gs2)->priority - (*gs1)->priority; +} + +int gensec_setting_int(struct gensec_settings *settings, const char *mechanism, const char *name, int default_value) +{ + return lpcfg_parm_int(settings->lp_ctx, NULL, mechanism, name, default_value); +} + +bool gensec_setting_bool(struct gensec_settings *settings, const char *mechanism, const char *name, bool default_value) +{ + return lpcfg_parm_bool(settings->lp_ctx, NULL, mechanism, name, default_value); +} + +/* + initialise the GENSEC subsystem +*/ +_PUBLIC_ NTSTATUS gensec_init(void) +{ + static bool initialized = false; +#define _MODULE_PROTO(init) extern NTSTATUS init(TALLOC_CTX *); +#ifdef STATIC_gensec_MODULES + STATIC_gensec_MODULES_PROTO; + init_module_fn static_init[] = { STATIC_gensec_MODULES }; +#else + init_module_fn *static_init = NULL; +#endif + init_module_fn *shared_init; + + if (initialized) return NT_STATUS_OK; + initialized = true; + + shared_init = load_samba_modules(NULL, "gensec"); + + run_init_functions(NULL, static_init); + run_init_functions(NULL, shared_init); + + talloc_free(shared_init); + + TYPESAFE_QSORT(generic_security_ops, gensec_num_backends, sort_gensec); + + return NT_STATUS_OK; +} diff --git a/auth/gensec/gensec_util.c b/auth/gensec/gensec_util.c new file mode 100644 index 0000000..1075b9f --- /dev/null +++ b/auth/gensec/gensec_util.c @@ -0,0 +1,338 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface + + Copyright (C) Andrew Tridgell 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2006 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/common_auth.h" +#include "../lib/util/asn1.h" +#include "param/param.h" +#include "libds/common/roles.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +NTSTATUS gensec_generate_session_info_pac(TALLOC_CTX *mem_ctx, + struct gensec_security *gensec_security, + struct smb_krb5_context *smb_krb5_context, + DATA_BLOB *pac_blob, + const char *principal_string, + const struct tsocket_address *remote_address, + struct auth_session_info **session_info) +{ + uint32_t session_info_flags = 0; + struct auth4_context *auth_context = NULL; + NTSTATUS status; + + if (gensec_security->want_features & GENSEC_FEATURE_UNIX_TOKEN) { + session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN; + } + + session_info_flags |= AUTH_SESSION_INFO_DEFAULT_GROUPS; + + if (!pac_blob) { + enum server_role server_role = + lpcfg_server_role(gensec_security->settings->lp_ctx); + + /* + * For any domain setup (DC or member) we require having + * a PAC, as the service ticket comes from an AD DC, + * which will always provide a PAC, unless + * UF_NO_AUTH_DATA_REQUIRED is configured for our + * account, but that's just an invalid configuration, + * the admin configured for us! + * + * As a legacy case, we still allow kerberos tickets from an MIT + * realm, but only in standalone mode. In that mode we'll only + * ever accept a kerberos authentication with a keytab file + * being explicitly configured via the 'keytab method' option. + */ + if (server_role != ROLE_STANDALONE) { + DBG_WARNING("Unable to find PAC in ticket from %s, " + "failing to allow access\n", + principal_string); + return NT_STATUS_NO_IMPERSONATION_TOKEN; + } + DBG_NOTICE("Unable to find PAC for %s, resorting to local " + "user lookup\n", principal_string); + } + + auth_context = gensec_security->auth_context; + + if ((auth_context == NULL) || + (auth_context->generate_session_info_pac == NULL)) { + DBG_ERR("Cannot generate a session_info without " + "the auth_context\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + status = auth_context->generate_session_info_pac( + auth_context, + mem_ctx, + smb_krb5_context, + pac_blob, + principal_string, + remote_address, + session_info_flags, + session_info); + return status; +} + +/* + magic check a GSS-API wrapper packet for an Kerberos OID +*/ +static bool gensec_gssapi_check_oid(const DATA_BLOB *blob, const char *oid) +{ + bool ret = false; + struct asn1_data *data = asn1_init(NULL, ASN1_MAX_TREE_DEPTH); + + if (!data) return false; + + if (!asn1_load(data, *blob)) goto err; + if (!asn1_start_tag(data, ASN1_APPLICATION(0))) goto err; + if (!asn1_check_OID(data, oid)) goto err; + + ret = !asn1_has_error(data); + + err: + + asn1_free(data); + return ret; +} + +/** + * Check if the packet is one for the KRB5 mechansim + * + * NOTE: This is a helper that can be employed by multiple mechanisms, do + * not make assumptions about the private_data + * + * @param gensec_security GENSEC state, unused + * @param in The request, as a DATA_BLOB + * @return Error, INVALID_PARAMETER if it's not a packet for us + * or NT_STATUS_OK if the packet is ok. + */ + +NTSTATUS gensec_magic_check_krb5_oid(struct gensec_security *unused, + const DATA_BLOB *blob) +{ + if (gensec_gssapi_check_oid(blob, GENSEC_OID_KERBEROS5)) { + return NT_STATUS_OK; + } else { + return NT_STATUS_INVALID_PARAMETER; + } +} + +void gensec_child_want_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_security *child_security = gensec_security->child_security; + + gensec_security->want_features |= feature; + if (child_security == NULL) { + return; + } + gensec_want_feature(child_security, feature); +} + +bool gensec_child_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_security *child_security = gensec_security->child_security; + + if (feature & GENSEC_FEATURE_SIGN_PKT_HEADER) { + /* + * All mechs with sub (child) mechs need to provide DCERPC + * header signing! This is required because the negotiation + * of header signing is done before the authentication + * is completed. + */ + return true; + } + + if (child_security == NULL) { + return false; + } + + return gensec_have_feature(child_security, feature); +} + +NTSTATUS gensec_child_unseal_packet(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_unseal_packet(gensec_security->child_security, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_child_check_packet(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_check_packet(gensec_security->child_security, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_child_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_seal_packet(gensec_security->child_security, + mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_child_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_sign_packet(gensec_security->child_security, + mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); +} + +NTSTATUS gensec_child_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_wrap(gensec_security->child_security, + mem_ctx, in, out); +} + +NTSTATUS gensec_child_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_unwrap(gensec_security->child_security, + mem_ctx, in, out); +} + +size_t gensec_child_sig_size(struct gensec_security *gensec_security, + size_t data_size) +{ + if (gensec_security->child_security == NULL) { + return 0; + } + + return gensec_sig_size(gensec_security->child_security, data_size); +} + +size_t gensec_child_max_input_size(struct gensec_security *gensec_security) +{ + if (gensec_security->child_security == NULL) { + return 0; + } + + return gensec_max_input_size(gensec_security->child_security); +} + +size_t gensec_child_max_wrapped_size(struct gensec_security *gensec_security) +{ + if (gensec_security->child_security == NULL) { + return 0; + } + + return gensec_max_wrapped_size(gensec_security->child_security); +} + +NTSTATUS gensec_child_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_session_key(gensec_security->child_security, + mem_ctx, + session_key); +} + +NTSTATUS gensec_child_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info) +{ + if (gensec_security->child_security == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + return gensec_session_info(gensec_security->child_security, + mem_ctx, + session_info); +} + +NTTIME gensec_child_expire_time(struct gensec_security *gensec_security) +{ + if (gensec_security->child_security == NULL) { + return GENSEC_EXPIRE_TIME_INFINITY; + } + + return gensec_expire_time(gensec_security->child_security); +} + +const char *gensec_child_final_auth_type(struct gensec_security *gensec_security) +{ + if (gensec_security->child_security == NULL) { + return "NONE"; + } + + return gensec_final_auth_type(gensec_security->child_security); +} diff --git a/auth/gensec/ncalrpc.c b/auth/gensec/ncalrpc.c new file mode 100644 index 0000000..f845947 --- /dev/null +++ b/auth/gensec/ncalrpc.c @@ -0,0 +1,356 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc ncalrpc as system operations + + Copyright (C) 2014 Andreas Schneider <asn@samba.org> + Copyright (C) 2014 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 <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "auth/auth.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "lib/param/param.h" +#include "tsocket.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +_PUBLIC_ NTSTATUS gensec_ncalrpc_as_system_init(TALLOC_CTX *ctx); + +struct gensec_ncalrpc_state { + enum { + GENSEC_NCALRPC_START, + GENSEC_NCALRPC_MORE, + GENSEC_NCALRPC_DONE, + GENSEC_NCALRPC_ERROR, + } step; + + struct auth_user_info_dc *user_info_dc; +}; + +static NTSTATUS gensec_ncalrpc_client_start(struct gensec_security *gensec_security) +{ + struct gensec_ncalrpc_state *state; + + state = talloc_zero(gensec_security, + struct gensec_ncalrpc_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + gensec_security->private_data = state; + + state->step = GENSEC_NCALRPC_START; + return NT_STATUS_OK; +} + +static NTSTATUS gensec_ncalrpc_server_start(struct gensec_security *gensec_security) +{ + struct gensec_ncalrpc_state *state; + + state = talloc_zero(gensec_security, + struct gensec_ncalrpc_state); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + gensec_security->private_data = state; + + state->step = GENSEC_NCALRPC_START; + return NT_STATUS_OK; +} + +struct gensec_ncalrpc_update_state { + NTSTATUS status; + DATA_BLOB out; +}; + +static NTSTATUS gensec_ncalrpc_update_internal( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB in, + DATA_BLOB *out); + +static struct tevent_req *gensec_ncalrpc_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req; + struct gensec_ncalrpc_update_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_ncalrpc_update_state); + if (req == NULL) { + return NULL; + } + + status = gensec_ncalrpc_update_internal(gensec_security, + state, in, + &state->out); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + status = NT_STATUS_OK; + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS gensec_ncalrpc_update_internal( + struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const DATA_BLOB in, + DATA_BLOB *out) +{ + struct gensec_ncalrpc_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ncalrpc_state); + DATA_BLOB magic_req = data_blob_string_const("NCALRPC_AUTH_TOKEN"); + DATA_BLOB magic_ok = data_blob_string_const("NCALRPC_AUTH_OK"); + DATA_BLOB magic_fail = data_blob_string_const("NCALRPC_AUTH_FAIL"); + char *unix_path = NULL; + int cmp; + NTSTATUS status; + + *out = data_blob_null; + + if (state->step >= GENSEC_NCALRPC_DONE) { + return NT_STATUS_INVALID_PARAMETER; + } + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + switch (state->step) { + case GENSEC_NCALRPC_START: + *out = data_blob_dup_talloc(mem_ctx, magic_req); + if (out->data == NULL) { + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_NO_MEMORY; + } + + state->step = GENSEC_NCALRPC_MORE; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + + case GENSEC_NCALRPC_MORE: + cmp = data_blob_cmp(&in, &magic_ok); + if (cmp != 0) { + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_LOGON_FAILURE; + } + + state->step = GENSEC_NCALRPC_DONE; + return NT_STATUS_OK; + + case GENSEC_NCALRPC_DONE: + case GENSEC_NCALRPC_ERROR: + break; + } + + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_INTERNAL_ERROR; + + case GENSEC_SERVER: + if (state->step != GENSEC_NCALRPC_START) { + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_INTERNAL_ERROR; + } + + cmp = data_blob_cmp(&in, &magic_req); + if (cmp != 0) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_LOGON_FAILURE; + } + + if (gensec_security->remote_addr == NULL) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_LOGON_FAILURE; + } + + unix_path = tsocket_address_unix_path(gensec_security->remote_addr, + state); + if (unix_path == NULL) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_LOGON_FAILURE; + } + + cmp = strcmp(unix_path, AS_SYSTEM_MAGIC_PATH_TOKEN); + TALLOC_FREE(unix_path); + if (cmp != 0) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_LOGON_FAILURE; + } + + status = auth_system_user_info_dc(state, + lpcfg_netbios_name(gensec_security->settings->lp_ctx), + &state->user_info_dc); + if (!NT_STATUS_IS_OK(status)) { + state->step = GENSEC_NCALRPC_ERROR; + *out = data_blob_dup_talloc(mem_ctx, magic_fail); + if (out->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + return status; + } + + *out = data_blob_dup_talloc(mem_ctx, magic_ok); + if (out->data == NULL) { + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_NO_MEMORY; + } + + state->step = GENSEC_NCALRPC_DONE; + return NT_STATUS_OK; + } + + state->step = GENSEC_NCALRPC_ERROR; + return NT_STATUS_INTERNAL_ERROR; +} + +static NTSTATUS gensec_ncalrpc_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_ncalrpc_update_state *state = + tevent_req_data(req, + struct gensec_ncalrpc_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + status = state->status; + talloc_steal(out_mem_ctx, state->out.data); + *out = state->out; + tevent_req_received(req); + return status; +} + +static NTSTATUS gensec_ncalrpc_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **psession_info) +{ + struct gensec_ncalrpc_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ncalrpc_state); + struct auth4_context *auth_ctx = gensec_security->auth_context; + struct auth_session_info *session_info = NULL; + uint32_t session_info_flags = 0; + NTSTATUS status; + + if (gensec_security->gensec_role != GENSEC_SERVER) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (state->step != GENSEC_NCALRPC_DONE) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (auth_ctx == NULL) { + DEBUG(0, ("Cannot generate a session_info without the auth_context\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + if (auth_ctx->generate_session_info == NULL) { + DEBUG(0, ("Cannot generate a session_info without the generate_session_info hook\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + if (gensec_security->want_features & GENSEC_FEATURE_UNIX_TOKEN) { + session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN; + } + + session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES; + + status = auth_ctx->generate_session_info( + auth_ctx, + mem_ctx, + state->user_info_dc, + state->user_info_dc->info->account_name, + session_info_flags, + &session_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *psession_info = session_info; + return NT_STATUS_OK; +} + +/* We have no features */ +static bool gensec_ncalrpc_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (feature & GENSEC_FEATURE_DCE_STYLE) { + return true; + } + + return false; +} + +static const struct gensec_security_ops gensec_ncalrpc_security_ops = { + .name = "ncalrpc_as_system", + .auth_type = DCERPC_AUTH_TYPE_NCALRPC_AS_SYSTEM, + .client_start = gensec_ncalrpc_client_start, + .server_start = gensec_ncalrpc_server_start, + .update_send = gensec_ncalrpc_update_send, + .update_recv = gensec_ncalrpc_update_recv, + .session_info = gensec_ncalrpc_session_info, + .have_feature = gensec_ncalrpc_have_feature, + .enabled = true, + .priority = GENSEC_EXTERNAL, +}; + +_PUBLIC_ NTSTATUS gensec_ncalrpc_as_system_init(TALLOC_CTX *ctx) +{ + NTSTATUS status; + + status = gensec_register(ctx, &gensec_ncalrpc_security_ops); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("Failed to register '%s' gensec backend!\n", + gensec_ncalrpc_security_ops.name)); + return status; + } + + return status; +} diff --git a/auth/gensec/schannel.c b/auth/gensec/schannel.c new file mode 100644 index 0000000..9860559 --- /dev/null +++ b/auth/gensec/schannel.c @@ -0,0 +1,1222 @@ +/* + Unix SMB/CIFS implementation. + + dcerpc schannel operations + + Copyright (C) Andrew Tridgell 2004 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "librpc/gen_ndr/ndr_schannel.h" +#include "auth/auth.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/gensec/gensec_proto.h" +#include "../libcli/auth/schannel.h" +#include "librpc/gen_ndr/dcerpc.h" +#include "param/param.h" +#include "auth/gensec/gensec_toplevel_proto.h" +#include "libds/common/roles.h" + +#ifndef HAVE_GNUTLS_AES_CFB8 +#include "lib/crypto/aes.h" +#endif + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +struct schannel_state { + struct gensec_security *gensec; + uint64_t seq_num; + bool initiator; + struct netlogon_creds_CredentialState *creds; + struct auth_user_info_dc *user_info_dc; +}; + +#define SETUP_SEQNUM(state, buf, initiator) do { \ + uint8_t *_buf = buf; \ + uint32_t _seq_num_low = (state)->seq_num & UINT32_MAX; \ + uint32_t _seq_num_high = (state)->seq_num >> 32; \ + if (initiator) { \ + _seq_num_high |= 0x80000000; \ + } \ + RSIVAL(_buf, 0, _seq_num_low); \ + RSIVAL(_buf, 4, _seq_num_high); \ +} while(0) + +static struct schannel_state *netsec_create_state( + struct gensec_security *gensec, + struct netlogon_creds_CredentialState *creds, + bool initiator) +{ + struct schannel_state *state; + + state = talloc_zero(gensec, struct schannel_state); + if (state == NULL) { + return NULL; + } + + state->gensec = gensec; + state->initiator = initiator; + state->creds = netlogon_creds_copy(state, creds); + if (state->creds == NULL) { + talloc_free(state); + return NULL; + } + + gensec->private_data = state; + + return state; +} + +static void netsec_offset_and_sizes(struct schannel_state *state, + bool do_seal, + uint32_t *_min_sig_size, + uint32_t *_used_sig_size, + uint32_t *_checksum_length, + uint32_t *_confounder_ofs) +{ + uint32_t min_sig_size; + uint32_t used_sig_size; + uint32_t checksum_length; + uint32_t confounder_ofs; + + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + min_sig_size = 48; + used_sig_size = 56; + /* + * Note: windows has a bug here and uses the old values... + * + * checksum_length = 32; + * confounder_ofs = 48; + */ + checksum_length = 8; + confounder_ofs = 24; + } else { + min_sig_size = 24; + used_sig_size = 32; + checksum_length = 8; + confounder_ofs = 24; + } + + if (do_seal) { + min_sig_size += 8; + } + + if (_min_sig_size) { + *_min_sig_size = min_sig_size; + } + + if (_used_sig_size) { + *_used_sig_size = used_sig_size; + } + + if (_checksum_length) { + *_checksum_length = checksum_length; + } + + if (_confounder_ofs) { + *_confounder_ofs = confounder_ofs; + } +} + +/******************************************************************* + Encode or Decode the sequence number (which is symmetric) + ********************************************************************/ +static NTSTATUS netsec_do_seq_num(struct schannel_state *state, + const uint8_t *checksum, + uint32_t checksum_length, + uint8_t seq_num[8]) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { +#ifdef HAVE_GNUTLS_AES_CFB8 + gnutls_cipher_hd_t cipher_hnd = NULL; + gnutls_datum_t key = { + .data = state->creds->session_key, + .size = sizeof(state->creds->session_key), + }; + uint32_t iv_size = + gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_CFB8); + uint8_t _iv[iv_size]; + gnutls_datum_t iv = { + .data = _iv, + .size = iv_size, + }; + int rc; + + ZERO_ARRAY(_iv); + + memcpy(iv.data + 0, checksum, 8); + memcpy(iv.data + 8, checksum, 8); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_AES_128_CFB8, + &key, + &iv); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + rc = gnutls_cipher_encrypt(cipher_hnd, seq_num, 8); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, + NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + +#else /* NOT HAVE_GNUTLS_AES_CFB8 */ + AES_KEY key; + uint8_t iv[AES_BLOCK_SIZE]; + + AES_set_encrypt_key(state->creds->session_key, 128, &key); + ZERO_STRUCT(iv); + memcpy(iv+0, checksum, 8); + memcpy(iv+8, checksum, 8); + + aes_cfb8_encrypt(seq_num, seq_num, 8, &key, iv, AES_ENCRYPT); +#endif /* HAVE_GNUTLS_AES_CFB8 */ + } else { + static const uint8_t zeros[4]; + uint8_t _sequence_key[16]; + gnutls_cipher_hd_t cipher_hnd; + gnutls_datum_t sequence_key = { + .data = _sequence_key, + .size = sizeof(_sequence_key), + }; + uint8_t digest1[16]; + int rc; + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + state->creds->session_key, + sizeof(state->creds->session_key), + zeros, + sizeof(zeros), + digest1); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + digest1, + sizeof(digest1), + checksum, + checksum_length, + _sequence_key); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + ZERO_ARRAY(digest1); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &sequence_key, + NULL); + if (rc < 0) { + ZERO_ARRAY(_sequence_key); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + seq_num, + 8); + gnutls_cipher_deinit(cipher_hnd); + ZERO_ARRAY(_sequence_key); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + state->seq_num++; + + return NT_STATUS_OK; +} + +static NTSTATUS netsec_do_seal(struct schannel_state *state, + const uint8_t seq_num[8], + uint8_t confounder[8], + uint8_t *data, uint32_t length, + bool forward) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { +#ifdef HAVE_GNUTLS_AES_CFB8 + gnutls_cipher_hd_t cipher_hnd = NULL; + uint8_t sess_kf0[16] = {0}; + gnutls_datum_t key = { + .data = sess_kf0, + .size = sizeof(sess_kf0), + }; + uint32_t iv_size = + gnutls_cipher_get_iv_size(GNUTLS_CIPHER_AES_128_CFB8); + uint8_t _iv[iv_size]; + gnutls_datum_t iv = { + .data = _iv, + .size = iv_size, + }; + uint32_t i; + int rc; + + for (i = 0; i < key.size; i++) { + key.data[i] = state->creds->session_key[i] ^ 0xf0; + } + + ZERO_ARRAY(_iv); + + memcpy(iv.data + 0, seq_num, 8); + memcpy(iv.data + 8, seq_num, 8); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_AES_128_CFB8, + &key, + &iv); + if (rc < 0) { + DBG_ERR("ERROR: gnutls_cipher_init: %s\n", + gnutls_strerror(rc)); + return NT_STATUS_NO_MEMORY; + } + + if (forward) { + rc = gnutls_cipher_encrypt(cipher_hnd, + confounder, + 8); + if (rc < 0) { + gnutls_cipher_deinit(cipher_hnd); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + rc = gnutls_cipher_encrypt(cipher_hnd, + data, + length); + if (rc < 0) { + gnutls_cipher_deinit(cipher_hnd); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + } else { + + /* + * Workaround bug present in gnutls 3.6.8: + * + * gnutls_cipher_decrypt() uses an optimization + * internally that breaks decryption when processing + * buffers with their length not being a multiple + * of the blocksize. + */ + + uint8_t tmp[16] = { 0, }; + uint32_t tmp_dlength = MIN(length, sizeof(tmp) - 8); + + memcpy(tmp, confounder, 8); + memcpy(tmp + 8, data, tmp_dlength); + + rc = gnutls_cipher_decrypt(cipher_hnd, + tmp, + 8 + tmp_dlength); + if (rc < 0) { + ZERO_STRUCT(tmp); + gnutls_cipher_deinit(cipher_hnd); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + + memcpy(confounder, tmp, 8); + memcpy(data, tmp + 8, tmp_dlength); + ZERO_STRUCT(tmp); + + if (length > tmp_dlength) { + rc = gnutls_cipher_decrypt(cipher_hnd, + data + tmp_dlength, + length - tmp_dlength); + if (rc < 0) { + gnutls_cipher_deinit(cipher_hnd); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + } + } + gnutls_cipher_deinit(cipher_hnd); +#else /* NOT HAVE_GNUTLS_AES_CFB8 */ + AES_KEY key; + uint8_t iv[AES_BLOCK_SIZE]; + uint8_t sess_kf0[16]; + int i; + + for (i = 0; i < 16; i++) { + sess_kf0[i] = state->creds->session_key[i] ^ 0xf0; + } + + AES_set_encrypt_key(sess_kf0, 128, &key); + ZERO_STRUCT(iv); + memcpy(iv+0, seq_num, 8); + memcpy(iv+8, seq_num, 8); + + if (forward) { + aes_cfb8_encrypt(confounder, confounder, 8, &key, iv, AES_ENCRYPT); + aes_cfb8_encrypt(data, data, length, &key, iv, AES_ENCRYPT); + } else { + aes_cfb8_encrypt(confounder, confounder, 8, &key, iv, AES_DECRYPT); + aes_cfb8_encrypt(data, data, length, &key, iv, AES_DECRYPT); + } +#endif /* HAVE_GNUTLS_AES_CFB8 */ + } else { + gnutls_cipher_hd_t cipher_hnd; + uint8_t _sealing_key[16]; + gnutls_datum_t sealing_key = { + .data = _sealing_key, + .size = sizeof(_sealing_key), + }; + static const uint8_t zeros[4]; + uint8_t digest2[16]; + uint8_t sess_kf0[16]; + int rc; + int i; + + for (i = 0; i < 16; i++) { + sess_kf0[i] = state->creds->session_key[i] ^ 0xf0; + } + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + sess_kf0, + sizeof(sess_kf0), + zeros, + 4, + digest2); + if (rc < 0) { + ZERO_ARRAY(digest2); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + digest2, + sizeof(digest2), + seq_num, + 8, + _sealing_key); + + ZERO_ARRAY(digest2); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &sealing_key, + NULL); + if (rc < 0) { + ZERO_ARRAY(_sealing_key); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + rc = gnutls_cipher_encrypt(cipher_hnd, + confounder, + 8); + if (rc < 0) { + ZERO_ARRAY(_sealing_key); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + gnutls_cipher_deinit(cipher_hnd); + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &sealing_key, + NULL); + if (rc < 0) { + ZERO_ARRAY(_sealing_key); + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + rc = gnutls_cipher_encrypt(cipher_hnd, + data, + length); + gnutls_cipher_deinit(cipher_hnd); + ZERO_ARRAY(_sealing_key); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_CRYPTO_SYSTEM_INVALID); + } + } + + return NT_STATUS_OK; +} + +/******************************************************************* + Create a digest over the entire packet (including the data), and + MD5 it with the session key. + ********************************************************************/ +static NTSTATUS netsec_do_sign(struct schannel_state *state, + const uint8_t *confounder, + const uint8_t *data, size_t length, + uint8_t header[8], + uint8_t *checksum) +{ + if (state->creds->negotiate_flags & NETLOGON_NEG_SUPPORTS_AES) { + gnutls_hmac_hd_t hmac_hnd = NULL; + int rc; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_SHA256, + state->creds->session_key, + sizeof(state->creds->session_key)); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + if (confounder) { + SSVAL(header, 0, NL_SIGN_HMAC_SHA256); + SSVAL(header, 2, NL_SEAL_AES128); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + rc = gnutls_hmac(hmac_hnd, header, 8); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hmac(hmac_hnd, confounder, 8); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } else { + SSVAL(header, 0, NL_SIGN_HMAC_SHA256); + SSVAL(header, 2, NL_SEAL_NONE); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + rc = gnutls_hmac(hmac_hnd, header, 8); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + rc = gnutls_hmac(hmac_hnd, data, length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + gnutls_hmac_deinit(hmac_hnd, checksum); + } else { + uint8_t packet_digest[16]; + static const uint8_t zeros[4]; + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + + rc = gnutls_hash(hash_hnd, zeros, sizeof(zeros)); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + if (confounder) { + SSVAL(header, 0, NL_SIGN_HMAC_MD5); + SSVAL(header, 2, NL_SEAL_RC4); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + rc = gnutls_hash(hash_hnd, header, 8); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + rc = gnutls_hash(hash_hnd, confounder, 8); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } else { + SSVAL(header, 0, NL_SIGN_HMAC_MD5); + SSVAL(header, 2, NL_SEAL_NONE); + SSVAL(header, 4, 0xFFFF); + SSVAL(header, 6, 0x0000); + + rc = gnutls_hash(hash_hnd, header, 8); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + rc = gnutls_hash(hash_hnd, data, length); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + gnutls_hash_deinit(hash_hnd, packet_digest); + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + state->creds->session_key, + sizeof(state->creds->session_key), + packet_digest, + sizeof(packet_digest), + checksum); + ZERO_ARRAY(packet_digest); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED); + } + } + + return NT_STATUS_OK; +} + +static NTSTATUS netsec_incoming_packet(struct schannel_state *state, + bool do_unseal, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + uint32_t min_sig_size = 0; + uint8_t header[8]; + uint8_t checksum[32]; + uint32_t checksum_length = sizeof(checksum_length); + uint8_t _confounder[8]; + uint8_t *confounder = NULL; + uint32_t confounder_ofs = 0; + uint8_t seq_num[8]; + bool ret; + const uint8_t *sign_data = NULL; + size_t sign_length = 0; + NTSTATUS status; + + netsec_offset_and_sizes(state, + do_unseal, + &min_sig_size, + NULL, + &checksum_length, + &confounder_ofs); + + if (sig->length < min_sig_size) { + return NT_STATUS_ACCESS_DENIED; + } + + if (do_unseal) { + confounder = _confounder; + memcpy(confounder, sig->data+confounder_ofs, 8); + } else { + confounder = NULL; + } + + SETUP_SEQNUM(state, seq_num, !state->initiator); + + if (do_unseal) { + status = netsec_do_seal(state, + seq_num, + confounder, + data, + length, + false); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_seal failed: %s\n", nt_errstr(status)); + return NT_STATUS_ACCESS_DENIED; + } + } + + if (state->gensec->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) { + sign_data = whole_pdu; + sign_length = pdu_length; + } else { + sign_data = data; + sign_length = length; + } + + status = netsec_do_sign(state, + confounder, + sign_data, + sign_length, + header, + checksum); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_sign failed: %s\n", nt_errstr(status)); + return NT_STATUS_ACCESS_DENIED; + } + + ret = mem_equal_const_time(checksum, sig->data+16, checksum_length); + if (!ret) { + dump_data_pw("calc digest:", checksum, checksum_length); + dump_data_pw("wire digest:", sig->data+16, checksum_length); + return NT_STATUS_ACCESS_DENIED; + } + + status = netsec_do_seq_num(state, checksum, checksum_length, seq_num); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_seq_num failed: %s\n", + nt_errstr(status)); + return status; + } + + ZERO_ARRAY(checksum); + + ret = mem_equal_const_time(seq_num, sig->data+8, 8); + if (!ret) { + dump_data_pw("calc seq num:", seq_num, 8); + dump_data_pw("wire seq num:", sig->data+8, 8); + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} + +static uint32_t netsec_outgoing_sig_size(struct schannel_state *state) +{ + uint32_t sig_size = 0; + + netsec_offset_and_sizes(state, + true, + NULL, + &sig_size, + NULL, + NULL); + + return sig_size; +} + +static NTSTATUS netsec_outgoing_packet(struct schannel_state *state, + TALLOC_CTX *mem_ctx, + bool do_seal, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + uint32_t min_sig_size = 0; + uint32_t used_sig_size = 0; + uint8_t header[8]; + uint8_t checksum[32]; + uint32_t checksum_length = sizeof(checksum_length); + uint8_t _confounder[8]; + uint8_t *confounder = NULL; + uint32_t confounder_ofs = 0; + uint8_t seq_num[8]; + const uint8_t *sign_data = NULL; + size_t sign_length = 0; + NTSTATUS status; + + netsec_offset_and_sizes(state, + do_seal, + &min_sig_size, + &used_sig_size, + &checksum_length, + &confounder_ofs); + + SETUP_SEQNUM(state, seq_num, state->initiator); + + if (do_seal) { + confounder = _confounder; + generate_random_buffer(confounder, 8); + } else { + confounder = NULL; + } + + if (state->gensec->want_features & GENSEC_FEATURE_SIGN_PKT_HEADER) { + sign_data = whole_pdu; + sign_length = pdu_length; + } else { + sign_data = data; + sign_length = length; + } + + status = netsec_do_sign(state, + confounder, + sign_data, + sign_length, + header, + checksum); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_sign failed: %s\n", nt_errstr(status)); + return NT_STATUS_ACCESS_DENIED; + } + + if (do_seal) { + status = netsec_do_seal(state, + seq_num, + confounder, + data, + length, + true); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_seal failed: %s\n", + nt_errstr(status)); + return status; + } + } + + status = netsec_do_seq_num(state, checksum, checksum_length, seq_num); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("netsec_do_seq_num failed: %s\n", + nt_errstr(status)); + return status; + } + + (*sig) = data_blob_talloc_zero(mem_ctx, used_sig_size); + + memcpy(sig->data, header, 8); + memcpy(sig->data+8, seq_num, 8); + memcpy(sig->data+16, checksum, checksum_length); + + if (confounder) { + memcpy(sig->data+confounder_ofs, confounder, 8); + } + + dump_data_pw("signature:", sig->data+ 0, 8); + dump_data_pw("seq_num :", sig->data+ 8, 8); + dump_data_pw("digest :", sig->data+16, checksum_length); + dump_data_pw("confound :", sig->data+confounder_ofs, 8); + + return NT_STATUS_OK; +} + +_PUBLIC_ NTSTATUS gensec_schannel_init(TALLOC_CTX *ctx); + +static size_t schannel_sig_size(struct gensec_security *gensec_security, size_t data_size) +{ + struct schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_outgoing_sig_size(state); +} + +struct schannel_update_state { + NTSTATUS status; + DATA_BLOB out; +}; + +static NTSTATUS schannel_update_internal(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); + +static struct tevent_req *schannel_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct tevent_req *req; + struct schannel_update_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct schannel_update_state); + if (req == NULL) { + return NULL; + } + + status = schannel_update_internal(gensec_security, + state, in, + &state->out); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + status = NT_STATUS_OK; + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static NTSTATUS schannel_update_internal(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct schannel_state *state = + talloc_get_type(gensec_security->private_data, + struct schannel_state); + NTSTATUS status; + enum ndr_err_code ndr_err; + struct NL_AUTH_MESSAGE bind_schannel = { + .Flags = 0, + }; + struct NL_AUTH_MESSAGE bind_schannel_ack; + struct netlogon_creds_CredentialState *creds; + const char *workstation; + const char *domain; + + *out = data_blob(NULL, 0); + + if (gensec_security->dcerpc_auth_level < DCERPC_AUTH_LEVEL_INTEGRITY) { + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + return NT_STATUS_INVALID_PARAMETER_MIX; + case GENSEC_SERVER: + return NT_STATUS_INVALID_PARAMETER; + } + return NT_STATUS_INTERNAL_ERROR; + } + + switch (gensec_security->gensec_role) { + case GENSEC_CLIENT: + if (state != NULL) { + /* we could parse the bind ack, but we don't know what it is yet */ + return NT_STATUS_OK; + } + + creds = cli_credentials_get_netlogon_creds(gensec_security->credentials); + if (creds == NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + state = netsec_create_state(gensec_security, + creds, true /* initiator */); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + bind_schannel.MessageType = NL_NEGOTIATE_REQUEST; + + bind_schannel.Flags = NL_FLAG_OEM_NETBIOS_DOMAIN_NAME | + NL_FLAG_OEM_NETBIOS_COMPUTER_NAME; + bind_schannel.oem_netbios_domain.a = cli_credentials_get_domain(gensec_security->credentials); + bind_schannel.oem_netbios_computer.a = creds->computer_name; + + if (creds->secure_channel_type == SEC_CHAN_DNS_DOMAIN) { + bind_schannel.Flags |= NL_FLAG_UTF8_DNS_DOMAIN_NAME; + bind_schannel.utf8_dns_domain.u = cli_credentials_get_realm(gensec_security->credentials); + + bind_schannel.Flags |= NL_FLAG_UTF8_NETBIOS_COMPUTER_NAME; + bind_schannel.utf8_netbios_computer.u = creds->computer_name; + } + + ndr_err = ndr_push_struct_blob(out, out_mem_ctx, &bind_schannel, + (ndr_push_flags_fn_t)ndr_push_NL_AUTH_MESSAGE); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(3, ("Could not create schannel bind: %s\n", + nt_errstr(status))); + return status; + } + + return NT_STATUS_MORE_PROCESSING_REQUIRED; + case GENSEC_SERVER: + + if (state != NULL) { + /* no third leg on this protocol */ + return NT_STATUS_INVALID_PARAMETER; + } + + /* parse the schannel startup blob */ + ndr_err = ndr_pull_struct_blob(&in, out_mem_ctx, &bind_schannel, + (ndr_pull_flags_fn_t)ndr_pull_NL_AUTH_MESSAGE); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(3, ("Could not parse incoming schannel bind: %s\n", + nt_errstr(status))); + return status; + } + + if (bind_schannel.Flags & NL_FLAG_OEM_NETBIOS_DOMAIN_NAME) { + domain = bind_schannel.oem_netbios_domain.a; + if (strcasecmp_m(domain, lpcfg_workgroup(gensec_security->settings->lp_ctx)) != 0) { + DEBUG(3, ("Request for schannel to incorrect domain: %s != our domain %s\n", + domain, lpcfg_workgroup(gensec_security->settings->lp_ctx))); + return NT_STATUS_LOGON_FAILURE; + } + } else if (bind_schannel.Flags & NL_FLAG_UTF8_DNS_DOMAIN_NAME) { + domain = bind_schannel.utf8_dns_domain.u; + if (strcasecmp_m(domain, lpcfg_dnsdomain(gensec_security->settings->lp_ctx)) != 0) { + DEBUG(3, ("Request for schannel to incorrect domain: %s != our domain %s\n", + domain, lpcfg_dnsdomain(gensec_security->settings->lp_ctx))); + return NT_STATUS_LOGON_FAILURE; + } + } else { + DEBUG(3, ("Request for schannel to without domain\n")); + return NT_STATUS_LOGON_FAILURE; + } + + if (bind_schannel.Flags & NL_FLAG_OEM_NETBIOS_COMPUTER_NAME) { + workstation = bind_schannel.oem_netbios_computer.a; + } else if (bind_schannel.Flags & NL_FLAG_UTF8_NETBIOS_COMPUTER_NAME) { + workstation = bind_schannel.utf8_netbios_computer.u; + } else { + DEBUG(3, ("Request for schannel to without netbios workstation\n")); + return NT_STATUS_LOGON_FAILURE; + } + + status = schannel_get_creds_state(out_mem_ctx, + gensec_security->settings->lp_ctx, + workstation, &creds); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(3, ("Could not find session key for attempted schannel connection from %s: %s\n", + workstation, nt_errstr(status))); + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_HANDLE)) { + return NT_STATUS_LOGON_FAILURE; + } + return status; + } + + state = netsec_create_state(gensec_security, + creds, false /* not initiator */); + if (state == NULL) { + return NT_STATUS_NO_MEMORY; + } + + status = auth_anonymous_user_info_dc(state, + lpcfg_netbios_name(gensec_security->settings->lp_ctx), + &state->user_info_dc); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + bind_schannel_ack.MessageType = NL_NEGOTIATE_RESPONSE; + bind_schannel_ack.Flags = 0; + bind_schannel_ack.Buffer.dummy = 0x6c0000; /* actually I think + * this does not have + * any meaning here + * - gd */ + + ndr_err = ndr_push_struct_blob(out, out_mem_ctx, &bind_schannel_ack, + (ndr_push_flags_fn_t)ndr_push_NL_AUTH_MESSAGE); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(3, ("Could not return schannel bind ack for client %s: %s\n", + workstation, nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; + } + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS schannel_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct schannel_update_state *state = + tevent_req_data(req, + struct schannel_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + status = state->status; + talloc_steal(out_mem_ctx, state->out.data); + *out = state->out; + tevent_req_received(req); + return status; +} + +/** + * Returns anonymous credentials for schannel, matching Win2k3. + * + */ + +static NTSTATUS schannel_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **_session_info) +{ + struct schannel_state *state = + talloc_get_type(gensec_security->private_data, + struct schannel_state); + struct auth4_context *auth_ctx = gensec_security->auth_context; + struct auth_session_info *session_info = NULL; + uint32_t session_info_flags = 0; + NTSTATUS status; + + if (auth_ctx == NULL) { + DEBUG(0, ("Cannot generate a session_info without the auth_context\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + if (auth_ctx->generate_session_info == NULL) { + DEBUG(0, ("Cannot generate a session_info without the generate_session_info hook\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + if (gensec_security->want_features & GENSEC_FEATURE_UNIX_TOKEN) { + session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN; + } + + session_info_flags |= AUTH_SESSION_INFO_SIMPLE_PRIVILEGES; + + status = auth_ctx->generate_session_info( + auth_ctx, + mem_ctx, + state->user_info_dc, + state->user_info_dc->info->account_name, + session_info_flags, + &session_info); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + *_session_info = session_info; + return NT_STATUS_OK; +} + +/* + * Reduce the attack surface by ensuring schannel is not availble when + * we are not a DC + */ +static NTSTATUS schannel_server_start(struct gensec_security *gensec_security) +{ + enum server_role server_role + = lpcfg_server_role(gensec_security->settings->lp_ctx); + + switch (server_role) { + case ROLE_DOMAIN_BDC: + case ROLE_DOMAIN_PDC: + case ROLE_ACTIVE_DIRECTORY_DC: + case ROLE_IPA_DC: + return NT_STATUS_OK; + default: + return NT_STATUS_NOT_IMPLEMENTED; + } +} + +static NTSTATUS schannel_client_start(struct gensec_security *gensec_security) +{ + return NT_STATUS_OK; +} + +static bool schannel_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + if (gensec_security->dcerpc_auth_level >= DCERPC_AUTH_LEVEL_INTEGRITY) { + if (feature & GENSEC_FEATURE_SIGN) { + return true; + } + } + if (gensec_security->dcerpc_auth_level == DCERPC_AUTH_LEVEL_PRIVACY) { + if (feature & GENSEC_FEATURE_SEAL) { + return true; + } + } + if (feature & GENSEC_FEATURE_DCE_STYLE) { + return true; + } + if (feature & GENSEC_FEATURE_SIGN_PKT_HEADER) { + return true; + } + return false; +} + +/* + unseal a packet +*/ +static NTSTATUS schannel_unseal_packet(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_incoming_packet(state, true, + discard_const_p(uint8_t, data), + length, + whole_pdu, pdu_length, + sig); +} + +/* + check the signature on a packet +*/ +static NTSTATUS schannel_check_packet(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_incoming_packet(state, false, + discard_const_p(uint8_t, data), + length, + whole_pdu, pdu_length, + sig); +} +/* + seal a packet +*/ +static NTSTATUS schannel_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_outgoing_packet(state, mem_ctx, true, + data, length, + whole_pdu, pdu_length, + sig); +} + +/* + sign a packet +*/ +static NTSTATUS schannel_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct schannel_state *state = + talloc_get_type_abort(gensec_security->private_data, + struct schannel_state); + + return netsec_outgoing_packet(state, mem_ctx, false, + discard_const_p(uint8_t, data), + length, + whole_pdu, pdu_length, + sig); +} + +static const struct gensec_security_ops gensec_schannel_security_ops = { + .name = "schannel", + .auth_type = DCERPC_AUTH_TYPE_SCHANNEL, + .client_start = schannel_client_start, + .server_start = schannel_server_start, + .update_send = schannel_update_send, + .update_recv = schannel_update_recv, + .seal_packet = schannel_seal_packet, + .sign_packet = schannel_sign_packet, + .check_packet = schannel_check_packet, + .unseal_packet = schannel_unseal_packet, + .session_info = schannel_session_info, + .sig_size = schannel_sig_size, + .have_feature = schannel_have_feature, + .enabled = true, + .priority = GENSEC_SCHANNEL +}; + +_PUBLIC_ NTSTATUS gensec_schannel_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + ret = gensec_register(ctx, &gensec_schannel_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_schannel_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/auth/gensec/spnego.c b/auth/gensec/spnego.c new file mode 100644 index 0000000..87545d8 --- /dev/null +++ b/auth/gensec/spnego.c @@ -0,0 +1,2249 @@ +/* + Unix SMB/CIFS implementation. + + RFC2478 Compliant SPNEGO implementation + + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Stefan Metzmacher <metze@samba.org> 2004-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/>. +*/ + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "../libcli/auth/spnego.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "param/param.h" +#include "lib/util/asn1.h" +#include "lib/util/base64.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#undef strcasecmp + +_PUBLIC_ NTSTATUS gensec_spnego_init(TALLOC_CTX *ctx); + +enum spnego_state_position { + SPNEGO_SERVER_START, + SPNEGO_CLIENT_START, + SPNEGO_SERVER_TARG, + SPNEGO_CLIENT_TARG, + SPNEGO_FALLBACK, + SPNEGO_DONE +}; + +struct spnego_state; +struct spnego_neg_ops; +struct spnego_neg_state; + +struct spnego_neg_state { + const struct spnego_neg_ops *ops; + const struct gensec_security_ops_wrapper *all_sec; + size_t all_idx; + const char * const *mech_types; + size_t mech_idx; +}; + +struct spnego_neg_ops { + const char *name; + /* + * The start hook does the initial processing on the incoming paket and + * may starts the first possible subcontext. It indicates that + * gensec_update() is required on the subcontext by returning + * NT_STATUS_MORE_PROCESSING_REQUIRED and return something useful in + * 'in_next'. Note that 'in_mem_ctx' is just passed as a hint, the + * caller should treat 'in_next' as const and don't attempt to free the + * content. NT_STATUS_OK indicates the finish hook should be invoked + * directly within the need of gensec_update() on the subcontext. + * Every other error indicates an error that's returned to the caller. + */ + NTSTATUS (*start_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next); + /* + * The step hook processes the result of a failed gensec_update() and + * can decide to ignore a failure and continue the negotiation by + * setting up the next possible subcontext. It indicates that + * gensec_update() is required on the subcontext by returning + * NT_STATUS_MORE_PROCESSING_REQUIRED and return something useful in + * 'in_next'. Note that 'in_mem_ctx' is just passed as a hint, the + * caller should treat 'in_next' as const and don't attempt to free the + * content. NT_STATUS_OK indicates the finish hook should be invoked + * directly within the need of gensec_update() on the subcontext. + * Every other error indicates an error that's returned to the caller. + */ + NTSTATUS (*step_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next); + /* + * The finish hook processes the result of a successful gensec_update() + * (NT_STATUS_OK or NT_STATUS_MORE_PROCESSING_REQUIRED). It forms the + * response pdu that will be returned from the toplevel gensec_update() + * together with NT_STATUS_OK or NT_STATUS_MORE_PROCESSING_REQUIRED. It + * may also alter the state machine to prepare receiving the next pdu + * from the peer. + */ + NTSTATUS (*finish_fn)(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); +}; + +struct spnego_state { + enum spnego_message_type expected_packet; + enum spnego_state_position state_position; + struct gensec_security *sub_sec_security; + bool sub_sec_ready; + + const char *neg_oid; + + DATA_BLOB mech_types; + size_t num_targs; + bool downgraded; + bool mic_requested; + bool needs_mic_sign; + bool needs_mic_check; + bool may_skip_mic_check; + bool done_mic_check; + + bool simulate_w2k; + bool no_optimistic; + + /* + * The following is used to implement + * the update token fragmentation + */ + size_t in_needed; + DATA_BLOB in_frag; + size_t out_max_length; + DATA_BLOB out_frag; + NTSTATUS out_status; +}; + +static struct spnego_neg_state *gensec_spnego_neg_state(TALLOC_CTX *mem_ctx, + const struct spnego_neg_ops *ops) +{ + struct spnego_neg_state *n = NULL; + + n = talloc_zero(mem_ctx, struct spnego_neg_state); + if (n == NULL) { + return NULL; + } + n->ops = ops; + + return n; +} + +static void gensec_spnego_reset_sub_sec(struct spnego_state *spnego_state) +{ + spnego_state->sub_sec_ready = false; + TALLOC_FREE(spnego_state->sub_sec_security); +} + +static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security) +{ + struct spnego_state *spnego_state; + + spnego_state = talloc_zero(gensec_security, struct spnego_state); + if (!spnego_state) { + return NT_STATUS_NO_MEMORY; + } + + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; + spnego_state->state_position = SPNEGO_CLIENT_START; + spnego_state->sub_sec_security = NULL; + spnego_state->sub_sec_ready = false; + spnego_state->mech_types = data_blob_null; + spnego_state->out_max_length = gensec_max_update_size(gensec_security); + spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + + spnego_state->simulate_w2k = gensec_setting_bool(gensec_security->settings, + "spnego", "simulate_w2k", false); + spnego_state->no_optimistic = gensec_setting_bool(gensec_security->settings, + "spnego", + "client_no_optimistic", + false); + + gensec_security->private_data = spnego_state; + return NT_STATUS_OK; +} + +static NTSTATUS gensec_spnego_server_start(struct gensec_security *gensec_security) +{ + struct spnego_state *spnego_state; + + spnego_state = talloc_zero(gensec_security, struct spnego_state); + if (!spnego_state) { + return NT_STATUS_NO_MEMORY; + } + + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; + spnego_state->state_position = SPNEGO_SERVER_START; + spnego_state->sub_sec_security = NULL; + spnego_state->sub_sec_ready = false; + spnego_state->mech_types = data_blob_null; + spnego_state->out_max_length = gensec_max_update_size(gensec_security); + spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED; + + spnego_state->simulate_w2k = gensec_setting_bool(gensec_security->settings, + "spnego", "simulate_w2k", false); + + gensec_security->private_data = spnego_state; + return NT_STATUS_OK; +} + +/** Fallback to another GENSEC mechanism, based on magic strings + * + * This is the 'fallback' case, where we don't get SPNEGO, and have to + * try all the other options (and hope they all have a magic string + * they check) +*/ + +static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + TALLOC_CTX *mem_ctx, + const DATA_BLOB in) +{ + int i,j; + const struct gensec_security_ops **all_ops; + + all_ops = gensec_security_mechs(gensec_security, mem_ctx); + + for (i=0; all_ops && all_ops[i]; i++) { + bool is_spnego; + NTSTATUS nt_status; + + if (gensec_security != NULL && + !gensec_security_ops_enabled(all_ops[i], gensec_security)) + { + continue; + } + + if (!all_ops[i]->oid) { + continue; + } + + is_spnego = false; + for (j=0; all_ops[i]->oid[j]; j++) { + if (strcasecmp(GENSEC_OID_SPNEGO,all_ops[i]->oid[j]) == 0) { + is_spnego = true; + } + } + if (is_spnego) { + continue; + } + + if (!all_ops[i]->magic) { + continue; + } + + nt_status = all_ops[i]->magic(gensec_security, &in); + if (!NT_STATUS_IS_OK(nt_status)) { + continue; + } + + spnego_state->state_position = SPNEGO_FALLBACK; + + nt_status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + /* select the sub context */ + nt_status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + all_ops[i]); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + return NT_STATUS_OK; + } + DEBUG(1, ("Failed to parse SPNEGO request\n")); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_create_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + n->mech_idx = 0; + n->mech_types = gensec_security_oids(gensec_security, n, + GENSEC_OID_SPNEGO); + if (n->mech_types == NULL) { + DBG_WARNING("gensec_security_oids() failed\n"); + return NT_STATUS_NO_MEMORY; + } + + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_NO_MEMORY; + } + + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); +} + +static NTSTATUS gensec_spnego_create_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + const char *principal = NULL; + int dbg_level = DBGLVL_WARNING; + NTSTATUS status = last_status; + + if (cur_sec[1].op != NULL) { + next_sec = &cur_sec[1]; + } + + if (next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; + } + + if (gensec_security->target.principal != NULL) { + principal = gensec_security->target.principal; + } else if (gensec_security->target.service != NULL && + gensec_security->target.hostname != NULL) + { + principal = talloc_asprintf(spnego_state->sub_sec_security, + "%s/%s", + gensec_security->target.service, + gensec_security->target.hostname); + } else { + principal = gensec_security->target.hostname; + } + + DBG_PREFIX(dbg_level, ( + "%s: creating NEG_TOKEN_INIT for %s failed " + "(next[%s]): %s\n", cur_sec->op->name, + principal, next, nt_errstr(status))); + + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } + + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); + + /* + * And try the next one... + */ + n->all_idx += 1; + } + + for (; n->all_sec[n->all_idx].op != NULL; n->all_idx++) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + NTSTATUS status; + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* select the sub context */ + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + gensec_spnego_reset_sub_sec(spnego_state); + continue; + } + + /* In the client, try and produce the first (optimistic) packet */ + if (spnego_state->state_position == SPNEGO_CLIENT_START) { + *in_next = data_blob_null; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + *in_next = data_blob_null; + return NT_STATUS_OK; + } + + DBG_WARNING("Failed to setup SPNEGO negTokenInit request\n"); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_create_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + struct spnego_data spnego_out; + bool ok; + + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + + n->mech_types = gensec_security_oids_from_ops_wrapped(n, cur_sec); + if (n->mech_types == NULL) { + DBG_WARNING("gensec_security_oids_from_ops_wrapped() failed\n"); + return NT_STATUS_NO_MEMORY; + } + + ok = spnego_write_mech_types(spnego_state, + n->mech_types, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("Failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } + + /* List the remaining mechs as options */ + spnego_out.negTokenInit.mechTypes = n->mech_types; + spnego_out.negTokenInit.reqFlags = data_blob_null; + spnego_out.negTokenInit.reqFlagsPadding = 0; + + if (spnego_state->state_position == SPNEGO_SERVER_START) { + spnego_out.negTokenInit.mechListMIC + = data_blob_string_const(ADS_IGNORE_PRINCIPAL); + } else { + spnego_out.negTokenInit.mechListMIC = data_blob_null; + } + + spnego_out.negTokenInit.mechToken = sub_out; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_ERR("Failed to write NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + + /* set next state */ + if (spnego_state->state_position == SPNEGO_SERVER_START) { + spnego_state->state_position = SPNEGO_SERVER_START; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; + } else { + spnego_state->state_position = SPNEGO_CLIENT_TARG; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + } + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_create_negTokenInit_ops = { + .name = "create_negTokenInit", + .start_fn = gensec_spnego_create_negTokenInit_start, + .step_fn = gensec_spnego_create_negTokenInit_step, + .finish_fn = gensec_spnego_create_negTokenInit_finish, +}; + +static NTSTATUS gensec_spnego_client_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + const char *tp = NULL; + + /* The server offers a list of mechanisms */ + + tp = spnego_in->negTokenInit.targetPrincipal; + if (tp != NULL && strcmp(tp, ADS_IGNORE_PRINCIPAL) != 0) { + DBG_INFO("Server claims it's principal name is %s\n", tp); + if (lpcfg_client_use_spnego_principal(gensec_security->settings->lp_ctx)) { + gensec_set_target_principal(gensec_security, tp); + } + } + + n->mech_idx = 0; + + /* Do not use server mech list as it isn't protected. Instead, get all + * supported mechs (excluding SPNEGO). */ + n->mech_types = gensec_security_oids(gensec_security, n, + GENSEC_OID_SPNEGO); + if (n->mech_types == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); +} + +static NTSTATUS gensec_spnego_client_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + const char *principal = NULL; + int dbg_level = DBGLVL_WARNING; + bool allow_fallback = false; + NTSTATUS status = last_status; + + if (cur_sec[1].op != NULL) { + next_sec = &cur_sec[1]; + } + + /* + * it is likely that a NULL input token will + * not be liked by most server mechs, but if + * we are in the client, we want the first + * update packet to be able to abort the use + * of this mech + */ + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) || + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_ACCOUNT_NAME) || + NT_STATUS_EQUAL(status, NT_STATUS_INVALID_COMPUTER_NAME) || + NT_STATUS_EQUAL(status, NT_STATUS_NO_SUCH_DOMAIN) || + NT_STATUS_EQUAL(status, NT_STATUS_NO_LOGON_SERVERS) || + NT_STATUS_EQUAL(status, NT_STATUS_TIME_DIFFERENCE_AT_DC) || + NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) + { + allow_fallback = true; + } + + if (allow_fallback && next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; + } + + if (gensec_security->target.principal != NULL) { + principal = gensec_security->target.principal; + } else if (gensec_security->target.service != NULL && + gensec_security->target.hostname != NULL) + { + principal = talloc_asprintf(spnego_state->sub_sec_security, + "%s/%s", + gensec_security->target.service, + gensec_security->target.hostname); + } else { + principal = gensec_security->target.hostname; + } + + DBG_PREFIX(dbg_level, ( + "%s: creating NEG_TOKEN_INIT for %s failed " + "(next[%s]): %s\n", cur_sec->op->name, + principal, next, nt_errstr(status))); + + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } + + /* + * Pretend we never started it. + */ + gensec_spnego_reset_sub_sec(spnego_state); + + /* + * And try the next one... + */ + n->all_idx += 1; + } + + for (; n->all_sec[n->all_idx].op != NULL; n->all_idx++) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + NTSTATUS status; + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* select the sub context */ + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + gensec_spnego_reset_sub_sec(spnego_state); + continue; + } + + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + + /* + * As client we don't use an optimistic token from the server. + * But try to produce one for the server. + */ + *in_next = data_blob_null; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + DBG_WARNING("Could not find a suitable mechtype in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_client_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct spnego_data spnego_out; + const char * const *mech_types = NULL; + bool ok; + + if (n->mech_types == NULL) { + DBG_WARNING("No mech_types list\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + for (mech_types = n->mech_types; *mech_types != NULL; mech_types++) { + int cmp = strcmp(*mech_types, spnego_state->neg_oid); + + if (cmp == 0) { + break; + } + } + + if (*mech_types == NULL) { + DBG_ERR("Can't find selected sub mechanism in mech_types\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_INIT; + spnego_out.negTokenInit.mechTypes = mech_types; + spnego_out.negTokenInit.reqFlags = data_blob_null; + spnego_out.negTokenInit.reqFlagsPadding = 0; + spnego_out.negTokenInit.mechListMIC = data_blob_null; + spnego_out.negTokenInit.mechToken = sub_out; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_ERR("Failed to write SPNEGO reply to NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + ok = spnego_write_mech_types(spnego_state, + mech_types, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } + + /* set next state */ + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->state_position = SPNEGO_CLIENT_TARG; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_client_negTokenInit_ops = { + .name = "client_negTokenInit", + .start_fn = gensec_spnego_client_negTokenInit_start, + .step_fn = gensec_spnego_client_negTokenInit_step, + .finish_fn = gensec_spnego_client_negTokenInit_finish, +}; + +static NTSTATUS gensec_spnego_client_negTokenTarg_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + NTSTATUS status; + + spnego_state->num_targs++; + + if (ta->negResult == SPNEGO_REJECT) { + return NT_STATUS_LOGON_FAILURE; + } + + if (ta->negResult == SPNEGO_REQUEST_MIC) { + spnego_state->mic_requested = true; + } + + if (ta->mechListMIC.length > 0) { + DATA_BLOB *m = &ta->mechListMIC; + const DATA_BLOB *r = &ta->responseToken; + + /* + * Windows 2000 has a bug, it repeats the + * responseToken in the mechListMIC field. + */ + if (m->length == r->length) { + int cmp; + + cmp = memcmp(m->data, r->data, m->length); + if (cmp == 0) { + data_blob_free(m); + } + } + } + + /* Server didn't like our choice of mech, and chose something else */ + if (((ta->negResult == SPNEGO_ACCEPT_INCOMPLETE) || + (ta->negResult == SPNEGO_REQUEST_MIC)) && + ta->supportedMech != NULL && + strcmp(ta->supportedMech, spnego_state->neg_oid) != 0) + { + const char *client_mech = NULL; + const char *client_oid = NULL; + const char *server_mech = NULL; + const char *server_oid = NULL; + + client_mech = gensec_get_name_by_oid(gensec_security, + spnego_state->neg_oid); + client_oid = spnego_state->neg_oid; + server_mech = gensec_get_name_by_oid(gensec_security, + ta->supportedMech); + server_oid = ta->supportedMech; + + DBG_NOTICE("client preferred mech (%s[%s]) not accepted, " + "server wants: %s[%s]\n", + client_mech, client_oid, server_mech, server_oid); + + spnego_state->downgraded = true; + gensec_spnego_reset_sub_sec(spnego_state); + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* select the sub context */ + status = gensec_start_mech_by_oid(spnego_state->sub_sec_security, + ta->supportedMech); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + spnego_state->neg_oid = talloc_strdup(spnego_state, + ta->supportedMech); + if (spnego_state->neg_oid == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + if (ta->mechListMIC.length > 0) { + if (spnego_state->sub_sec_ready) { + spnego_state->needs_mic_check = true; + } + } + + if (spnego_state->needs_mic_check) { + if (ta->responseToken.length != 0) { + DBG_WARNING("non empty response token not expected\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (ta->mechListMIC.length == 0 + && spnego_state->may_skip_mic_check) { + /* + * In this case we don't require + * a mechListMIC from the server. + * + * This works around bugs in the Azure + * and Apple spnego implementations. + * + * See + * https://bugzilla.samba.org/show_bug.cgi?id=11994 + */ + spnego_state->needs_mic_check = false; + return NT_STATUS_OK; + } + + status = gensec_check_packet(spnego_state->sub_sec_security, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + return NT_STATUS_OK; + } + + if (!spnego_state->sub_sec_ready) { + *in_next = ta->responseToken; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_spnego_client_negTokenTarg_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (GENSEC_UPDATE_IS_NTERROR(last_status)) { + DBG_WARNING("SPNEGO(%s) login failed: %s\n", + spnego_state->sub_sec_security->ops->name, + nt_errstr(last_status)); + return last_status; + } + + /* + * This should never be reached! + * The step function is only called on errors! + */ + smb_panic(__location__); + return NT_STATUS_INTERNAL_ERROR; +} + +static NTSTATUS gensec_spnego_client_negTokenTarg_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct spnego_negTokenTarg *ta = + &spnego_in->negTokenTarg; + DATA_BLOB mech_list_mic = data_blob_null; + NTSTATUS status; + struct spnego_data spnego_out; + + if (!spnego_state->sub_sec_ready) { + /* + * We're not yet ready to deal with signatures. + */ + goto client_response; + } + + if (spnego_state->done_mic_check) { + /* + * We already checked the mic, + * either the in last round here + * in gensec_spnego_client_negTokenTarg_finish() + * or during this round in + * gensec_spnego_client_negTokenTarg_start(). + * + * Both cases we're sure we don't have to + * call gensec_sign_packet(). + */ + goto client_response; + } + + if (spnego_state->may_skip_mic_check) { + /* + * This can only be set during + * the last round here in + * gensec_spnego_client_negTokenTarg_finish() + * below. And during this round + * we already passed the checks in + * gensec_spnego_client_negTokenTarg_start(). + * + * So we need to skip to deal with + * any signatures now. + */ + goto client_response; + } + + if (!spnego_state->done_mic_check) { + bool have_sign = true; + bool new_spnego = false; + + have_sign = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_SIGN); + if (spnego_state->simulate_w2k) { + have_sign = false; + } + new_spnego = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_NEW_SPNEGO); + + switch (ta->negResult) { + case SPNEGO_ACCEPT_COMPLETED: + case SPNEGO_NONE_RESULT: + if (spnego_state->num_targs == 1) { + /* + * the first exchange doesn't require + * verification + */ + new_spnego = false; + } + + break; + + case SPNEGO_ACCEPT_INCOMPLETE: + if (ta->mechListMIC.length > 0) { + new_spnego = true; + break; + } + + if (spnego_state->downgraded) { + /* + * A downgrade should be protected if + * supported + */ + break; + } + + /* + * The caller may just asked for + * GENSEC_FEATURE_SESSION_KEY, this + * is only reflected in the want_features. + * + * As it will imply + * gensec_have_features(GENSEC_FEATURE_SIGN) + * to return true. + */ + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + break; + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + break; + } + /* + * Here we're sure our preferred mech was + * selected by the server and our caller doesn't + * need GENSEC_FEATURE_SIGN nor + * GENSEC_FEATURE_SEAL support. + * + * In this case we don't require + * a mechListMIC from the server. + * + * This works around bugs in the Azure + * and Apple spnego implementations. + * + * See + * https://bugzilla.samba.org/show_bug.cgi?id=11994 + */ + spnego_state->may_skip_mic_check = true; + break; + + case SPNEGO_REQUEST_MIC: + if (ta->mechListMIC.length > 0) { + new_spnego = true; + } + break; + default: + break; + } + + if (spnego_state->mic_requested) { + if (have_sign) { + new_spnego = true; + } + } + + if (have_sign && new_spnego) { + spnego_state->needs_mic_check = true; + spnego_state->needs_mic_sign = true; + } + } + + if (ta->mechListMIC.length > 0) { + status = gensec_check_packet(spnego_state->sub_sec_security, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + } + + if (spnego_state->needs_mic_sign) { + status = gensec_sign_packet(spnego_state->sub_sec_security, + n, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &mech_list_mic); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to sign mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_sign = false; + } + + client_response: + if (sub_out.length == 0 && mech_list_mic.length == 0) { + *out = data_blob_null; + + if (!spnego_state->sub_sec_ready) { + /* somethings wrong here... */ + DBG_ERR("gensec_update not ready without output\n"); + return NT_STATUS_INTERNAL_ERROR; + } + + if (ta->negResult != SPNEGO_ACCEPT_COMPLETED) { + /* unless of course it did not accept */ + DBG_WARNING("gensec_update ok but not accepted\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!spnego_state->needs_mic_check) { + spnego_state->state_position = SPNEGO_DONE; + return NT_STATUS_OK; + } + } + + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_TARG; + spnego_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT; + spnego_out.negTokenTarg.supportedMech = NULL; + spnego_out.negTokenTarg.responseToken = sub_out; + spnego_out.negTokenTarg.mechListMIC = mech_list_mic; + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DBG_WARNING("Failed to write NEG_TOKEN_TARG\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + spnego_state->num_targs++; + + /* set next state */ + spnego_state->state_position = SPNEGO_CLIENT_TARG; + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static const struct spnego_neg_ops gensec_spnego_client_negTokenTarg_ops = { + .name = "client_negTokenTarg", + .start_fn = gensec_spnego_client_negTokenTarg_start, + .step_fn = gensec_spnego_client_negTokenTarg_step, + .finish_fn = gensec_spnego_client_negTokenTarg_finish, +}; + +/** create a server negTokenTarg + * + * This is the case, where the client is the first one who sends data +*/ + +static NTSTATUS gensec_spnego_server_response(struct spnego_state *spnego_state, + TALLOC_CTX *out_mem_ctx, + NTSTATUS nt_status, + const DATA_BLOB unwrapped_out, + DATA_BLOB mech_list_mic, + DATA_BLOB *out) +{ + struct spnego_data spnego_out; + + /* compose reply */ + spnego_out.type = SPNEGO_NEG_TOKEN_TARG; + spnego_out.negTokenTarg.responseToken = unwrapped_out; + spnego_out.negTokenTarg.mechListMIC = mech_list_mic; + spnego_out.negTokenTarg.supportedMech = NULL; + + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; + if (spnego_state->mic_requested) { + spnego_out.negTokenTarg.negResult = SPNEGO_REQUEST_MIC; + spnego_state->mic_requested = false; + } else { + spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; + } + spnego_state->state_position = SPNEGO_SERVER_TARG; + } else if (NT_STATUS_IS_OK(nt_status)) { + if (unwrapped_out.data) { + spnego_out.negTokenTarg.supportedMech = spnego_state->neg_oid; + } + spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_COMPLETED; + spnego_state->state_position = SPNEGO_DONE; + } + + if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { + DEBUG(1, ("Failed to write SPNEGO reply to NEG_TOKEN_TARG\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; + spnego_state->num_targs++; + + return nt_status; +} + +static NTSTATUS gensec_spnego_server_negTokenInit_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + bool ok; + + n->mech_idx = 0; + n->mech_types = spnego_in->negTokenInit.mechTypes; + if (n->mech_types == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + n->all_idx = 0; + n->all_sec = gensec_security_by_oid_list(gensec_security, + n, n->mech_types, + GENSEC_OID_SPNEGO); + if (n->all_sec == NULL) { + DBG_WARNING("gensec_security_by_oid_list() failed\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + ok = spnego_write_mech_types(spnego_state, + n->mech_types, + &spnego_state->mech_types); + if (!ok) { + DBG_ERR("Failed to write mechTypes\n"); + return NT_STATUS_NO_MEMORY; + } + + return n->ops->step_fn(gensec_security, spnego_state, n, + spnego_in, NT_STATUS_OK, in_mem_ctx, in_next); +} + +static NTSTATUS gensec_spnego_server_negTokenInit_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (!NT_STATUS_IS_OK(last_status)) { + const struct gensec_security_ops_wrapper *cur_sec = + &n->all_sec[n->all_idx]; + const char *next_mech = n->mech_types[n->mech_idx+1]; + const struct gensec_security_ops_wrapper *next_sec = NULL; + const char *next = NULL; + int dbg_level = DBGLVL_WARNING; + bool allow_fallback = false; + NTSTATUS status = last_status; + size_t i; + + for (i = 0; next_mech != NULL && n->all_sec[i].op != NULL; i++) { + if (strcmp(next_mech, n->all_sec[i].oid) != 0) { + continue; + } + + next_sec = &n->all_sec[i]; + break; + } + + if (NT_STATUS_EQUAL(status, NT_STATUS_INVALID_PARAMETER) || + NT_STATUS_EQUAL(status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) + { + allow_fallback = true; + } + + if (allow_fallback && next_sec != NULL) { + next = next_sec->op->name; + dbg_level = DBGLVL_NOTICE; + } + + DBG_PREFIX(dbg_level, ( + "%s: parsing NEG_TOKEN_INIT content failed " + "(next[%s]): %s\n", cur_sec->op->name, + next, nt_errstr(status))); + + if (next == NULL) { + /* + * A hard error without a possible fallback. + */ + return status; + } + + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); + + /* + * And try the next one, based on the clients + * mech type list... + */ + n->mech_idx += 1; + } + + /* + * we always reset all_idx here, as the negotiation is + * done via mech_idx! + */ + n->all_idx = 0; + + for (; n->mech_types[n->mech_idx] != NULL; n->mech_idx++) { + const char *cur_mech = n->mech_types[n->mech_idx]; + const struct gensec_security_ops_wrapper *cur_sec = NULL; + NTSTATUS status; + DATA_BLOB sub_in = data_blob_null; + size_t i; + + for (i = 0; n->all_sec[i].op != NULL; i++) { + if (strcmp(cur_mech, n->all_sec[i].oid) != 0) { + continue; + } + + cur_sec = &n->all_sec[i]; + n->all_idx = i; + break; + } + + if (cur_sec == NULL) { + continue; + } + + status = gensec_subcontext_start(spnego_state, + gensec_security, + &spnego_state->sub_sec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* select the sub context */ + status = gensec_start_mech_by_ops(spnego_state->sub_sec_security, + cur_sec->op); + if (!NT_STATUS_IS_OK(status)) { + /* + * Pretend we never started it + */ + gensec_spnego_reset_sub_sec(spnego_state); + continue; + } + + if (n->mech_idx == 0) { + /* + * We can use the optimistic token. + */ + sub_in = spnego_in->negTokenInit.mechToken; + } else { + /* + * Indicate the downgrade and request a + * mic. + */ + spnego_state->downgraded = true; + spnego_state->mic_requested = true; + } + + if (sub_in.length == 0) { + spnego_state->no_optimistic = true; + } + + /* + * Note that 'cur_sec' is temporary memory, but + * cur_sec->oid points to a const string in the + * backends gensec_security_ops structure. + */ + spnego_state->neg_oid = cur_sec->oid; + + /* we need some content from the mech */ + *in_next = sub_in; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + DBG_WARNING("Could not find a suitable mechtype in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; +} + +static NTSTATUS gensec_spnego_server_negTokenInit_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + DATA_BLOB mech_list_mic = data_blob_null; + + if (spnego_state->simulate_w2k) { + /* + * Windows 2000 returns the unwrapped token + * also in the mech_list_mic field. + * + * In order to verify our client code, + * we need a way to have a server with this + * broken behaviour + */ + mech_list_mic = sub_out; + } + + return gensec_spnego_server_response(spnego_state, + out_mem_ctx, + sub_status, + sub_out, + mech_list_mic, + out); +} + +static const struct spnego_neg_ops gensec_spnego_server_negTokenInit_ops = { + .name = "server_negTokenInit", + .start_fn = gensec_spnego_server_negTokenInit_start, + .step_fn = gensec_spnego_server_negTokenInit_step, + .finish_fn = gensec_spnego_server_negTokenInit_finish, +}; + +static NTSTATUS gensec_spnego_server_negTokenTarg_start( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + const struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + NTSTATUS status; + + spnego_state->num_targs++; + + if (spnego_state->sub_sec_security == NULL) { + DBG_ERR("SPNEGO: Did not setup a mech in NEG_TOKEN_INIT\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + if (spnego_state->needs_mic_check) { + if (ta->responseToken.length != 0) { + DBG_WARNING("non empty response token not expected\n"); + return NT_STATUS_INVALID_PARAMETER; + } + + status = gensec_check_packet(spnego_state->sub_sec_security, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + return NT_STATUS_OK; + } + + if (!spnego_state->sub_sec_ready) { + *in_next = ta->responseToken; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + return NT_STATUS_OK; +} + +static NTSTATUS gensec_spnego_server_negTokenTarg_step( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS last_status, + TALLOC_CTX *in_mem_ctx, + DATA_BLOB *in_next) +{ + if (GENSEC_UPDATE_IS_NTERROR(last_status)) { + DBG_NOTICE("SPNEGO(%s) login failed: %s\n", + spnego_state->sub_sec_security->ops->name, + nt_errstr(last_status)); + return last_status; + } + + /* + * This should never be reached! + * The step function is only called on errors! + */ + smb_panic(__location__); + return NT_STATUS_INTERNAL_ERROR; +} + +static NTSTATUS gensec_spnego_server_negTokenTarg_finish( + struct gensec_security *gensec_security, + struct spnego_state *spnego_state, + struct spnego_neg_state *n, + struct spnego_data *spnego_in, + NTSTATUS sub_status, + const DATA_BLOB sub_out, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + const struct spnego_negTokenTarg *ta = &spnego_in->negTokenTarg; + DATA_BLOB mech_list_mic = data_blob_null; + NTSTATUS status; + bool have_sign = true; + bool new_spnego = false; + + status = sub_status; + + if (!spnego_state->sub_sec_ready) { + /* + * We're not yet ready to deal with signatures. + */ + goto server_response; + } + + if (spnego_state->done_mic_check) { + /* + * We already checked the mic, + * either the in last round here + * in gensec_spnego_server_negTokenTarg_finish() + * or during this round in + * gensec_spnego_server_negTokenTarg_start(). + * + * Both cases we're sure we don't have to + * call gensec_sign_packet(). + */ + goto server_response; + } + + have_sign = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_SIGN); + if (spnego_state->simulate_w2k) { + have_sign = false; + } + new_spnego = gensec_have_feature(spnego_state->sub_sec_security, + GENSEC_FEATURE_NEW_SPNEGO); + if (ta->mechListMIC.length > 0) { + new_spnego = true; + } + + if (have_sign && new_spnego) { + spnego_state->needs_mic_check = true; + spnego_state->needs_mic_sign = true; + } + + if (have_sign && ta->mechListMIC.length > 0) { + status = gensec_check_packet(spnego_state->sub_sec_security, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &ta->mechListMIC); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to verify mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + + spnego_state->needs_mic_check = false; + spnego_state->done_mic_check = true; + } + + if (spnego_state->needs_mic_sign) { + status = gensec_sign_packet(spnego_state->sub_sec_security, + n, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + spnego_state->mech_types.data, + spnego_state->mech_types.length, + &mech_list_mic); + if (!NT_STATUS_IS_OK(status)) { + DBG_WARNING("failed to sign mechListMIC: %s\n", + nt_errstr(status)); + return status; + } + spnego_state->needs_mic_sign = false; + } + + if (spnego_state->needs_mic_check) { + status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + server_response: + return gensec_spnego_server_response(spnego_state, + out_mem_ctx, + status, + sub_out, + mech_list_mic, + out); +} + +static const struct spnego_neg_ops gensec_spnego_server_negTokenTarg_ops = { + .name = "server_negTokenTarg", + .start_fn = gensec_spnego_server_negTokenTarg_start, + .step_fn = gensec_spnego_server_negTokenTarg_step, + .finish_fn = gensec_spnego_server_negTokenTarg_finish, +}; + +struct gensec_spnego_update_state { + struct tevent_context *ev; + struct gensec_security *gensec; + struct spnego_state *spnego; + + DATA_BLOB full_in; + struct spnego_data _spnego_in; + struct spnego_data *spnego_in; + + struct { + bool needed; + DATA_BLOB in; + NTSTATUS status; + DATA_BLOB out; + } sub; + + struct spnego_neg_state *n; + + NTSTATUS status; + DATA_BLOB out; +}; + +static void gensec_spnego_update_cleanup(struct tevent_req *req, + enum tevent_req_state req_state) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + + switch (req_state) { + case TEVENT_REQ_USER_ERROR: + case TEVENT_REQ_TIMED_OUT: + case TEVENT_REQ_NO_MEMORY: + /* + * A fatal error, further updates are not allowed. + */ + state->spnego->state_position = SPNEGO_DONE; + break; + default: + break; + } +} + +static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, + const DATA_BLOB in, TALLOC_CTX *mem_ctx, + DATA_BLOB *full_in); +static void gensec_spnego_update_pre(struct tevent_req *req); +static void gensec_spnego_update_done(struct tevent_req *subreq); +static void gensec_spnego_update_post(struct tevent_req *req); +static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *_out); + +static struct tevent_req *gensec_spnego_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); + struct tevent_req *req = NULL; + struct gensec_spnego_update_state *state = NULL; + NTSTATUS status; + ssize_t len; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_spnego_update_state); + if (req == NULL) { + return NULL; + } + state->ev = ev; + state->gensec = gensec_security; + state->spnego = spnego_state; + tevent_req_set_cleanup_fn(req, gensec_spnego_update_cleanup); + + if (spnego_state->out_frag.length > 0) { + if (in.length > 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + status = gensec_spnego_update_out(gensec_security, + state, &state->out); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return tevent_req_post(req, ev); + } + + state->status = status; + tevent_req_done(req); + return tevent_req_post(req, ev); + } + + status = gensec_spnego_update_in(gensec_security, in, + state, &state->full_in); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + /* Check if we got a valid SPNEGO blob... */ + + switch (spnego_state->state_position) { + case SPNEGO_FALLBACK: + break; + + case SPNEGO_CLIENT_TARG: + case SPNEGO_SERVER_TARG: + if (state->full_in.length == 0) { + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + FALL_THROUGH; + case SPNEGO_CLIENT_START: + case SPNEGO_SERVER_START: + + if (state->full_in.length == 0) { + /* create_negTokenInit later */ + break; + } + + len = spnego_read_data(state, + state->full_in, + &state->_spnego_in); + if (len == -1) { + if (spnego_state->state_position != SPNEGO_SERVER_START) { + DEBUG(1, ("Invalid SPNEGO request:\n")); + dump_data(1, state->full_in.data, + state->full_in.length); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + /* + * This is the 'fallback' case, where we don't get + * SPNEGO, and have to try all the other options (and + * hope they all have a magic string they check) + */ + status = gensec_spnego_server_try_fallback(gensec_security, + spnego_state, + state, + state->full_in); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + /* + * We'll continue with SPNEGO_FALLBACK below... + */ + break; + } + state->spnego_in = &state->_spnego_in; + + /* OK, so it's real SPNEGO, check the packet's the one we expect */ + if (state->spnego_in->type != spnego_state->expected_packet) { + DEBUG(1, ("Invalid SPNEGO request: %d, expected %d\n", + state->spnego_in->type, + spnego_state->expected_packet)); + dump_data(1, state->full_in.data, + state->full_in.length); + tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER); + return tevent_req_post(req, ev); + } + + break; + + default: + smb_panic(__location__); + return NULL; + } + + gensec_spnego_update_pre(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + if (state->sub.needed) { + struct tevent_req *subreq = NULL; + + /* + * We may need one more roundtrip... + */ + subreq = gensec_update_send(state, state->ev, + spnego_state->sub_sec_security, + state->sub.in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + gensec_spnego_update_done, + req); + state->sub.needed = false; + return req; + } + + gensec_spnego_update_post(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security, + const DATA_BLOB in, TALLOC_CTX *mem_ctx, + DATA_BLOB *full_in) +{ + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); + size_t expected; + bool ok; + + *full_in = data_blob_null; + + switch (spnego_state->state_position) { + case SPNEGO_FALLBACK: + *full_in = in; + spnego_state->in_needed = 0; + return NT_STATUS_OK; + + case SPNEGO_CLIENT_START: + case SPNEGO_CLIENT_TARG: + case SPNEGO_SERVER_START: + case SPNEGO_SERVER_TARG: + break; + + case SPNEGO_DONE: + default: + return NT_STATUS_INVALID_PARAMETER; + } + + if (spnego_state->in_needed == 0) { + size_t size = 0; + int ret; + + /* + * try to work out the size of the full + * input token, it might be fragmented + */ + ret = asn1_peek_full_tag(in, ASN1_APPLICATION(0), &size); + if ((ret != 0) && (ret != EAGAIN)) { + ret = asn1_peek_full_tag(in, ASN1_CONTEXT(1), &size); + } + + if ((ret == 0) || (ret == EAGAIN)) { + spnego_state->in_needed = size; + } else { + /* + * If it is not an asn1 message + * just call the next layer. + */ + spnego_state->in_needed = in.length; + } + } + + if (spnego_state->in_needed > UINT16_MAX) { + /* + * limit the incoming message to 0xFFFF + * to avoid DoS attacks. + */ + return NT_STATUS_INVALID_BUFFER_SIZE; + } + + if ((spnego_state->in_needed > 0) && (in.length == 0)) { + /* + * If we reach this, we know we got at least + * part of an asn1 message, getting 0 means + * the remote peer wants us to spin. + */ + return NT_STATUS_INVALID_PARAMETER; + } + + expected = spnego_state->in_needed - spnego_state->in_frag.length; + if (in.length > expected) { + /* + * we got more than expected + */ + return NT_STATUS_INVALID_PARAMETER; + } + + if (in.length == spnego_state->in_needed) { + /* + * if the in.length contains the full blob + * we are done. + * + * Note: this implies spnego_state->in_frag.length == 0, + * but we do not need to check this explicitly + * because we already know that we did not get + * more than expected. + */ + *full_in = in; + spnego_state->in_needed = 0; + return NT_STATUS_OK; + } + + ok = data_blob_append(spnego_state, &spnego_state->in_frag, + in.data, in.length); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + if (spnego_state->in_needed > spnego_state->in_frag.length) { + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + *full_in = spnego_state->in_frag; + talloc_steal(mem_ctx, full_in->data); + spnego_state->in_frag = data_blob_null; + spnego_state->in_needed = 0; + return NT_STATUS_OK; +} + +static void gensec_spnego_update_pre(struct tevent_req *req) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + const struct spnego_neg_ops *ops = NULL; + NTSTATUS status; + + state->sub.needed = false; + state->sub.in = data_blob_null; + state->sub.status = NT_STATUS_INTERNAL_ERROR; + state->sub.out = data_blob_null; + + if (spnego_state->state_position == SPNEGO_FALLBACK) { + state->sub.in = state->full_in; + state->full_in = data_blob_null; + state->sub.needed = true; + return; + } + + switch (spnego_state->state_position) { + case SPNEGO_CLIENT_START: + if (state->spnego_in == NULL) { + /* client to produce negTokenInit */ + ops = &gensec_spnego_create_negTokenInit_ops; + break; + } + + ops = &gensec_spnego_client_negTokenInit_ops; + break; + + case SPNEGO_CLIENT_TARG: + ops = &gensec_spnego_client_negTokenTarg_ops; + break; + + case SPNEGO_SERVER_START: + if (state->spnego_in == NULL) { + /* server to produce negTokenInit */ + ops = &gensec_spnego_create_negTokenInit_ops; + break; + } + + ops = &gensec_spnego_server_negTokenInit_ops; + break; + + case SPNEGO_SERVER_TARG: + ops = &gensec_spnego_server_negTokenTarg_ops; + break; + + default: + smb_panic(__location__); + return; + } + + state->n = gensec_spnego_neg_state(state, ops); + if (tevent_req_nomem(state->n, req)) { + return; + } + + status = ops->start_fn(state->gensec, spnego_state, state->n, + state->spnego_in, state, &state->sub.in); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + if (NT_STATUS_IS_OK(status)) { + /* + * Call finish_fn() with an empty + * blob and NT_STATUS_OK. + */ + state->sub.status = NT_STATUS_OK; + } else if (spnego_state->state_position == SPNEGO_CLIENT_START && + spnego_state->no_optimistic) { + /* + * Skip optimistic token per conf. + */ + state->sub.status = NT_STATUS_MORE_PROCESSING_REQUIRED; + } else if (spnego_state->state_position == SPNEGO_SERVER_START && + state->sub.in.length == 0 && spnego_state->no_optimistic) { + /* + * If we didn't like the mechanism for which the client sent us + * an optimistic token, or if he didn't send any, don't call + * the sub mechanism just yet. + */ + state->sub.status = NT_STATUS_MORE_PROCESSING_REQUIRED; + spnego_state->no_optimistic = false; + } else { + /* + * MORE_PROCESSING_REQUIRED => + * we need to call gensec_update_send(). + */ + state->sub.needed = true; + } +} + +static void gensec_spnego_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + + state->sub.status = gensec_update_recv(subreq, state, &state->sub.out); + TALLOC_FREE(subreq); + if (NT_STATUS_IS_OK(state->sub.status)) { + spnego_state->sub_sec_ready = true; + } + + gensec_spnego_update_post(req); +} + +static void gensec_spnego_update_post(struct tevent_req *req) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + struct spnego_state *spnego_state = state->spnego; + const struct spnego_neg_ops *ops = NULL; + NTSTATUS status; + + state->sub.in = data_blob_null; + state->sub.needed = false; + + if (spnego_state->state_position == SPNEGO_FALLBACK) { + status = state->sub.status; + spnego_state->out_frag = state->sub.out; + talloc_steal(spnego_state, spnego_state->out_frag.data); + state->sub.out = data_blob_null; + goto respond; + } + + ops = state->n->ops; + + if (GENSEC_UPDATE_IS_NTERROR(state->sub.status)) { + + + /* + * gensec_update_recv() returned an error, + * let's see if the step_fn() want to + * handle it and negotiate something else. + */ + + status = ops->step_fn(state->gensec, + spnego_state, + state->n, + state->spnego_in, + state->sub.status, + state, + &state->sub.in); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + state->sub.out = data_blob_null; + state->sub.status = NT_STATUS_INTERNAL_ERROR; + + if (NT_STATUS_IS_OK(status)) { + /* + * Call finish_fn() with an empty + * blob and NT_STATUS_OK. + */ + state->sub.status = NT_STATUS_OK; + } else { + /* + * MORE_PROCESSING_REQUIRED... + */ + state->sub.needed = true; + } + } + + if (state->sub.needed) { + struct tevent_req *subreq = NULL; + + /* + * We may need one more roundtrip... + */ + subreq = gensec_update_send(state, state->ev, + spnego_state->sub_sec_security, + state->sub.in); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, + gensec_spnego_update_done, + req); + state->sub.needed = false; + return; + } + + status = ops->finish_fn(state->gensec, + spnego_state, + state->n, + state->spnego_in, + state->sub.status, + state->sub.out, + spnego_state, + &spnego_state->out_frag); + TALLOC_FREE(state->n); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + if (NT_STATUS_IS_OK(status)) { + bool reset_full = true; + + reset_full = !spnego_state->done_mic_check; + + status = gensec_may_reset_crypto(spnego_state->sub_sec_security, + reset_full); + if (tevent_req_nterror(req, status)) { + return; + } + } + +respond: + spnego_state->out_status = status; + + status = gensec_spnego_update_out(state->gensec, + state, &state->out); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + state->status = status; + tevent_req_done(req); + return; +} + +static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *_out) +{ + struct spnego_state *spnego_state = + talloc_get_type_abort(gensec_security->private_data, + struct spnego_state); + DATA_BLOB out = data_blob_null; + bool ok; + + *_out = data_blob_null; + + if (spnego_state->out_frag.length <= spnego_state->out_max_length) { + /* + * Fast path, we can deliver everything + */ + + *_out = spnego_state->out_frag; + if (spnego_state->out_frag.length > 0) { + talloc_steal(out_mem_ctx, _out->data); + spnego_state->out_frag = data_blob_null; + } + + if (!NT_STATUS_IS_OK(spnego_state->out_status)) { + return spnego_state->out_status; + } + + /* + * We're completely done, further updates are not allowed. + */ + spnego_state->state_position = SPNEGO_DONE; + return gensec_child_ready(gensec_security, + spnego_state->sub_sec_security); + } + + out = spnego_state->out_frag; + + /* + * copy the remaining bytes + */ + spnego_state->out_frag = data_blob_talloc(spnego_state, + out.data + spnego_state->out_max_length, + out.length - spnego_state->out_max_length); + if (spnego_state->out_frag.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* + * truncate the buffer + */ + ok = data_blob_realloc(spnego_state, &out, + spnego_state->out_max_length); + if (!ok) { + return NT_STATUS_NO_MEMORY; + } + + talloc_steal(out_mem_ctx, out.data); + *_out = out; + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +static NTSTATUS gensec_spnego_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_spnego_update_state *state = + tevent_req_data(req, + struct gensec_spnego_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, state->out.data); + status = state->status; + tevent_req_received(req); + return status; +} + +static const char *gensec_spnego_oids[] = { + GENSEC_OID_SPNEGO, + NULL +}; + +static const struct gensec_security_ops gensec_spnego_security_ops = { + .name = "spnego", + .sasl_name = "GSS-SPNEGO", + .auth_type = DCERPC_AUTH_TYPE_SPNEGO, + .oid = gensec_spnego_oids, + .client_start = gensec_spnego_client_start, + .server_start = gensec_spnego_server_start, + .update_send = gensec_spnego_update_send, + .update_recv = gensec_spnego_update_recv, + .seal_packet = gensec_child_seal_packet, + .sign_packet = gensec_child_sign_packet, + .sig_size = gensec_child_sig_size, + .max_wrapped_size = gensec_child_max_wrapped_size, + .max_input_size = gensec_child_max_input_size, + .check_packet = gensec_child_check_packet, + .unseal_packet = gensec_child_unseal_packet, + .wrap = gensec_child_wrap, + .unwrap = gensec_child_unwrap, + .session_key = gensec_child_session_key, + .session_info = gensec_child_session_info, + .want_feature = gensec_child_want_feature, + .have_feature = gensec_child_have_feature, + .expire_time = gensec_child_expire_time, + .final_auth_type = gensec_child_final_auth_type, + .enabled = true, + .priority = GENSEC_SPNEGO, + .glue = true, +}; + +_PUBLIC_ NTSTATUS gensec_spnego_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + ret = gensec_register(ctx, &gensec_spnego_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_spnego_security_ops.name)); + return ret; + } + + return ret; +} diff --git a/auth/gensec/wscript_build b/auth/gensec/wscript_build new file mode 100644 index 0000000..1d8071d --- /dev/null +++ b/auth/gensec/wscript_build @@ -0,0 +1,37 @@ +#!/usr/bin/env python +bld.SAMBA_LIBRARY('gensec', + source='gensec.c gensec_start.c gensec_util.c', + autoproto='gensec_toplevel_proto.h', + public_deps='tevent-util samba-util samba-errors auth_system_session samba-modules gensec_util asn1util', + private_headers='gensec.h', + deps='com_err', + private_library=True, + ) + +bld.SAMBA_MODULE('gensec_spnego', + source='spnego.c', + subsystem='gensec', + init_function='gensec_spnego_init', + deps='asn1util samba-credentials SPNEGO_PARSE' + ) + +bld.SAMBA_MODULE('gensec_schannel', + source='schannel.c', + subsystem='gensec', + init_function='gensec_schannel_init', + deps='COMMON_SCHANNEL NDR_SCHANNEL samba-credentials auth_session GNUTLS_HELPERS' + ) + +bld.SAMBA_MODULE('gensec_ncalrpc', + source='ncalrpc.c', + subsystem='gensec', + init_function='gensec_ncalrpc_as_system_init', + deps='samba-util auth_session' + ) + +bld.SAMBA_MODULE('gensec_external', + source='external.c', + subsystem='gensec', + deps='samba-debug talloc tevent tevent-util', + init_function='gensec_external_init' + ) diff --git a/auth/kerberos/gssapi_helper.c b/auth/kerberos/gssapi_helper.c new file mode 100644 index 0000000..52c953c --- /dev/null +++ b/auth/kerberos/gssapi_helper.c @@ -0,0 +1,398 @@ +/* + Unix SMB/CIFS implementation. + GSSAPI helper functions + + Copyright (C) Stefan Metzmacher 2008,2015 + + 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/gssapi.h" +#include "auth/kerberos/pac_utils.h" +#include "auth/kerberos/gssapi_helper.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +size_t gssapi_get_sig_size(gss_ctx_id_t gssapi_context, + const gss_OID mech, + uint32_t gss_want_flags, + size_t data_size) +{ + TALLOC_CTX *frame = talloc_stackframe(); + size_t sig_size = 0; + + if (gss_want_flags & GSS_C_CONF_FLAG) { + OM_uint32 min_stat, maj_stat; + bool want_sealing = true; + int sealed = 0; + gss_iov_buffer_desc iov[2]; + + if (!(gss_want_flags & GSS_C_DCE_STYLE)) { + TALLOC_FREE(frame); + return 0; + } + + /* + * gss_wrap_iov_length() only needs the type and length + */ + iov[0].type = GSS_IOV_BUFFER_TYPE_HEADER; + iov[0].buffer.value = NULL; + iov[0].buffer.length = 0; + iov[1].type = GSS_IOV_BUFFER_TYPE_DATA; + iov[1].buffer.value = NULL; + iov[1].buffer.length = data_size; + + maj_stat = gss_wrap_iov_length(&min_stat, + gssapi_context, + want_sealing, + GSS_C_QOP_DEFAULT, + &sealed, + iov, ARRAY_SIZE(iov)); + if (maj_stat) { + DEBUG(0, ("gss_wrap_iov_length failed with [%s]\n", + gssapi_error_string(frame, + maj_stat, + min_stat, + mech))); + TALLOC_FREE(frame); + return 0; + } + + sig_size = iov[0].buffer.length; + } else if (gss_want_flags & GSS_C_INTEG_FLAG) { + NTSTATUS status; + uint32_t keytype; + + status = gssapi_get_session_key(frame, + gssapi_context, + NULL, &keytype); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(frame); + return 0; + } + + switch (keytype) { + case ENCTYPE_DES_CBC_MD5: + case ENCTYPE_DES_CBC_CRC: + case ENCTYPE_ARCFOUR_HMAC: + case ENCTYPE_ARCFOUR_HMAC_EXP: + sig_size = 37; + break; + default: + sig_size = 28; + break; + } + } + + TALLOC_FREE(frame); + return sig_size; +} + +NTSTATUS gssapi_seal_packet(gss_ctx_id_t gssapi_context, + const gss_OID mech, + bool hdr_signing, size_t sig_size, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + TALLOC_CTX *mem_ctx, + DATA_BLOB *sig) +{ + OM_uint32 maj_stat, min_stat; + gss_iov_buffer_desc iov[4]; + int req_seal = 1; + int sealed = 0; + const uint8_t *pre_sign_ptr = NULL; + size_t pre_sign_len = 0; + const uint8_t *post_sign_ptr = NULL; + size_t post_sign_len = 0; + + if (hdr_signing) { + const uint8_t *de = data + length; + const uint8_t *we = whole_pdu + pdu_length; + + if (data < whole_pdu) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (de > we) { + return NT_STATUS_INVALID_PARAMETER; + } + + pre_sign_len = data - whole_pdu; + if (pre_sign_len > 0) { + pre_sign_ptr = whole_pdu; + } + post_sign_len = we - de; + if (post_sign_len > 0) { + post_sign_ptr = de; + } + } + + sig->length = sig_size; + if (sig->length == 0) { + return NT_STATUS_ACCESS_DENIED; + } + + sig->data = talloc_zero_array(mem_ctx, uint8_t, sig->length); + if (sig->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + iov[0].type = GSS_IOV_BUFFER_TYPE_HEADER; + iov[0].buffer.length = sig->length; + iov[0].buffer.value = sig->data; + + if (pre_sign_ptr != NULL) { + iov[1].type = GSS_IOV_BUFFER_TYPE_SIGN_ONLY; + iov[1].buffer.length = pre_sign_len; + iov[1].buffer.value = discard_const(pre_sign_ptr); + } else { + iov[1].type = GSS_IOV_BUFFER_TYPE_EMPTY; + iov[1].buffer.length = 0; + iov[1].buffer.value = NULL; + } + + /* data is encrypted in place, which is ok */ + iov[2].type = GSS_IOV_BUFFER_TYPE_DATA; + iov[2].buffer.length = length; + iov[2].buffer.value = data; + + if (post_sign_ptr != NULL) { + iov[3].type = GSS_IOV_BUFFER_TYPE_SIGN_ONLY; + iov[3].buffer.length = post_sign_len; + iov[3].buffer.value = discard_const(post_sign_ptr); + } else { + iov[3].type = GSS_IOV_BUFFER_TYPE_EMPTY; + iov[3].buffer.length = 0; + iov[3].buffer.value = NULL; + } + + maj_stat = gss_wrap_iov(&min_stat, + gssapi_context, + req_seal, + GSS_C_QOP_DEFAULT, + &sealed, + iov, ARRAY_SIZE(iov)); + if (GSS_ERROR(maj_stat)) { + char *error_string = gssapi_error_string(mem_ctx, + maj_stat, + min_stat, + mech); + DEBUG(1, ("gss_wrap_iov failed: %s\n", error_string)); + talloc_free(error_string); + data_blob_free(sig); + return NT_STATUS_ACCESS_DENIED; + } + + if (req_seal == 1 && sealed == 0) { + DEBUG(0, ("gss_wrap_iov says data was not sealed!\n")); + data_blob_free(sig); + return NT_STATUS_ACCESS_DENIED; + } + + dump_data_pw("gssapi_seal_packet: sig\n", sig->data, sig->length); + dump_data_pw("gssapi_seal_packet: sealed\n", data, length); + + DEBUG(10, ("Sealed %d bytes, and got %d bytes header/signature.\n", + (int)iov[2].buffer.length, (int)iov[0].buffer.length)); + + return NT_STATUS_OK; +} + +NTSTATUS gssapi_unseal_packet(gss_ctx_id_t gssapi_context, + const gss_OID mech, + bool hdr_signing, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + OM_uint32 maj_stat, min_stat; + gss_iov_buffer_desc iov[4]; + gss_qop_t qop_state; + int sealed = 0; + const uint8_t *pre_sign_ptr = NULL; + size_t pre_sign_len = 0; + const uint8_t *post_sign_ptr = NULL; + size_t post_sign_len = 0; + + if (hdr_signing) { + const uint8_t *de = data + length; + const uint8_t *we = whole_pdu + pdu_length; + + if (data < whole_pdu) { + return NT_STATUS_INVALID_PARAMETER; + } + + if (de > we) { + return NT_STATUS_INVALID_PARAMETER; + } + + pre_sign_len = data - whole_pdu; + if (pre_sign_len > 0) { + pre_sign_ptr = whole_pdu; + } + post_sign_len = we - de; + if (post_sign_len > 0) { + post_sign_ptr = de; + } + } + + dump_data_pw("gssapi_unseal_packet: sig\n", sig->data, sig->length); + dump_data_pw("gssapi_unseal_packet: sealed\n", data, length); + + iov[0].type = GSS_IOV_BUFFER_TYPE_HEADER; + iov[0].buffer.length = sig->length; + iov[0].buffer.value = sig->data; + + if (pre_sign_ptr != NULL) { + iov[1].type = GSS_IOV_BUFFER_TYPE_SIGN_ONLY; + iov[1].buffer.length = pre_sign_len; + iov[1].buffer.value = discard_const(pre_sign_ptr); + } else { + iov[1].type = GSS_IOV_BUFFER_TYPE_EMPTY; + iov[1].buffer.length = 0; + iov[1].buffer.value = NULL; + } + + /* data is encrypted in place, which is ok */ + iov[2].type = GSS_IOV_BUFFER_TYPE_DATA; + iov[2].buffer.length = length; + iov[2].buffer.value = data; + + if (post_sign_ptr != NULL) { + iov[3].type = GSS_IOV_BUFFER_TYPE_SIGN_ONLY; + iov[3].buffer.length = post_sign_len; + iov[3].buffer.value = discard_const(post_sign_ptr); + } else { + iov[3].type = GSS_IOV_BUFFER_TYPE_EMPTY; + iov[3].buffer.length = 0; + iov[3].buffer.value = NULL; + } + + maj_stat = gss_unwrap_iov(&min_stat, + gssapi_context, + &sealed, + &qop_state, + iov, ARRAY_SIZE(iov)); + if (GSS_ERROR(maj_stat)) { + char *error_string = gssapi_error_string(NULL, + maj_stat, + min_stat, + mech); + DEBUG(1, ("gss_unwrap_iov failed: %s\n", error_string)); + talloc_free(error_string); + + return NT_STATUS_ACCESS_DENIED; + } + + if (sealed == 0) { + DEBUG(0, ("gss_unwrap_iov says data was not sealed!\n")); + return NT_STATUS_ACCESS_DENIED; + } + + DEBUG(10, ("Unsealed %d bytes, with %d bytes header/signature.\n", + (int)iov[2].buffer.length, (int)iov[0].buffer.length)); + + return NT_STATUS_OK; +} + +NTSTATUS gssapi_sign_packet(gss_ctx_id_t gssapi_context, + const gss_OID mech, + bool hdr_signing, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + TALLOC_CTX *mem_ctx, + DATA_BLOB *sig) +{ + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token; + + if (hdr_signing) { + input_token.length = pdu_length; + input_token.value = discard_const_p(uint8_t *, whole_pdu); + } else { + input_token.length = length; + input_token.value = discard_const_p(uint8_t *, data); + } + + maj_stat = gss_get_mic(&min_stat, + gssapi_context, + GSS_C_QOP_DEFAULT, + &input_token, + &output_token); + if (GSS_ERROR(maj_stat)) { + char *error_string = gssapi_error_string(mem_ctx, + maj_stat, + min_stat, + mech); + DEBUG(1, ("GSS GetMic failed: %s\n", error_string)); + talloc_free(error_string); + return NT_STATUS_ACCESS_DENIED; + } + + *sig = data_blob_talloc(mem_ctx, (uint8_t *)output_token.value, output_token.length); + gss_release_buffer(&min_stat, &output_token); + if (sig->data == NULL) { + return NT_STATUS_NO_MEMORY; + } + + dump_data_pw("gssapi_sign_packet: sig\n", sig->data, sig->length); + + return NT_STATUS_OK; +} + +NTSTATUS gssapi_check_packet(gss_ctx_id_t gssapi_context, + const gss_OID mech, + bool hdr_signing, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token; + gss_buffer_desc input_message; + gss_qop_t qop_state; + + dump_data_pw("gssapi_check_packet: sig\n", sig->data, sig->length); + + if (hdr_signing) { + input_message.length = pdu_length; + input_message.value = discard_const(whole_pdu); + } else { + input_message.length = length; + input_message.value = discard_const(data); + } + + input_token.length = sig->length; + input_token.value = sig->data; + + maj_stat = gss_verify_mic(&min_stat, + gssapi_context, + &input_message, + &input_token, + &qop_state); + if (GSS_ERROR(maj_stat)) { + char *error_string = gssapi_error_string(NULL, + maj_stat, + min_stat, + mech); + DEBUG(1, ("GSS VerifyMic failed: %s\n", error_string)); + talloc_free(error_string); + + return NT_STATUS_ACCESS_DENIED; + } + + return NT_STATUS_OK; +} diff --git a/auth/kerberos/gssapi_helper.h b/auth/kerberos/gssapi_helper.h new file mode 100644 index 0000000..f40adf1 --- /dev/null +++ b/auth/kerberos/gssapi_helper.h @@ -0,0 +1,55 @@ +/* + Unix SMB/CIFS implementation. + GSSAPI helper functions + + Copyright (C) Stefan Metzmacher 2008,2015 + + 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 AUTH_KERBEROS_GSSAPI_HELPER_H +#define AUTH_KERBEROS_GSSAPI_HELPER_H 1 + +size_t gssapi_get_sig_size(gss_ctx_id_t gssapi_context, + const gss_OID mech, + uint32_t gss_want_flags, + size_t data_size); +NTSTATUS gssapi_seal_packet(gss_ctx_id_t gssapi_context, + const gss_OID mech, + bool hdr_signing, size_t sig_size, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + TALLOC_CTX *mem_ctx, + DATA_BLOB *sig); +NTSTATUS gssapi_unseal_packet(gss_ctx_id_t gssapi_context, + const gss_OID mech, + bool hdr_signing, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +NTSTATUS gssapi_sign_packet(gss_ctx_id_t gssapi_context, + const gss_OID mech, + bool hdr_signing, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + TALLOC_CTX *mem_ctx, + DATA_BLOB *sig); +NTSTATUS gssapi_check_packet(gss_ctx_id_t gssapi_context, + const gss_OID mech, + bool hdr_signing, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); + +#endif /* AUTH_KERBEROS_GSSAPI_HELPER_H */ diff --git a/auth/kerberos/gssapi_pac.c b/auth/kerberos/gssapi_pac.c new file mode 100644 index 0000000..1204399 --- /dev/null +++ b/auth/kerberos/gssapi_pac.c @@ -0,0 +1,348 @@ +/* + Unix SMB/CIFS implementation. + kerberos authorization data (PAC) utility library + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2011 + Copyright (C) Simo Sorce 2010. + + 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" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#ifdef HAVE_KRB5 + +#include "auth/kerberos/pac_utils.h" + +#if 0 +/* FIXME - need proper configure/waf test + * to determine if gss_mech_krb5 and friends + * exist. JRA. + */ +/* + * These are not exported by Solaris -lkrb5 + * Maybe move to libreplace somewhere? + */ +static const gss_OID_desc krb5_gss_oid_array[] = { + /* this is the official, rfc-specified OID */ + { 9, "\052\206\110\206\367\022\001\002\002" }, + /* this is the pre-RFC mech OID */ + { 5, "\053\005\001\005\002" }, + /* this is the unofficial, incorrect mech OID emitted by MS */ + { 9, "\052\206\110\202\367\022\001\002\002" }, + { 0, 0 } +}; + +const gss_OID_desc * const gss_mech_krb5 = krb5_gss_oid_array+0; +const gss_OID_desc * const gss_mech_krb5_old = krb5_gss_oid_array+1; +const gss_OID_desc * const gss_mech_krb5_wrong = krb5_gss_oid_array+2; +#endif + +#ifndef GSS_KRB5_INQ_SSPI_SESSION_KEY_OID +#define GSS_KRB5_INQ_SSPI_SESSION_KEY_OID_LENGTH 11 +#define GSS_KRB5_INQ_SSPI_SESSION_KEY_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x05\x05" +#endif + +gss_OID_desc gse_sesskey_inq_oid = { + GSS_KRB5_INQ_SSPI_SESSION_KEY_OID_LENGTH, + discard_const(GSS_KRB5_INQ_SSPI_SESSION_KEY_OID) +}; + +#ifndef GSS_KRB5_SESSION_KEY_ENCTYPE_OID +#define GSS_KRB5_SESSION_KEY_ENCTYPE_OID_LENGTH 10 +#define GSS_KRB5_SESSION_KEY_ENCTYPE_OID "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x04" +#endif + +gss_OID_desc gse_sesskeytype_oid = { + GSS_KRB5_SESSION_KEY_ENCTYPE_OID_LENGTH, + discard_const(GSS_KRB5_SESSION_KEY_ENCTYPE_OID) +}; + +/* The Heimdal OID for getting the PAC */ +#define EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID_LENGTH 8 +/* EXTRACTION OID AUTHZ ID */ +#define EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID "\x2a\x85\x70\x2b\x0d\x03" "\x81\x00" + +NTSTATUS gssapi_obtain_pac_blob(TALLOC_CTX *mem_ctx, + gss_ctx_id_t gssapi_context, + gss_name_t gss_client_name, + DATA_BLOB *pac_blob) +{ + NTSTATUS status; + OM_uint32 gss_maj, gss_min; +#ifdef HAVE_GSS_GET_NAME_ATTRIBUTE +/* + * gss_get_name_attribute() in MIT krb5 1.10.0 can return unintialized pac_display_buffer + * and later gss_release_buffer() will crash on attempting to release it. + * + * So always initialize the buffer descriptors. + * + * See following links for more details: + * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=658514 + * http://krbdev.mit.edu/rt/Ticket/Display.html?user=guest&pass=guest&id=7087 + */ + gss_buffer_desc pac_buffer = { + .value = NULL, + .length = 0 + }; + gss_buffer_desc pac_display_buffer = { + .value = NULL, + .length = 0 + }; + gss_buffer_desc pac_name = { + .value = discard_const("urn:mspac:"), + .length = sizeof("urn:mspac:")-1 + }; + int more = -1; + int authenticated = false; + int complete = false; + + gss_maj = gss_get_name_attribute( + &gss_min, gss_client_name, &pac_name, + &authenticated, &complete, + &pac_buffer, &pac_display_buffer, &more); + + if (gss_maj != 0) { + gss_OID oid = discard_const(gss_mech_krb5); + DBG_NOTICE("obtaining PAC via GSSAPI gss_get_name_attribute " + "failed: %s\n", gssapi_error_string(mem_ctx, + gss_maj, gss_min, + oid)); + return NT_STATUS_ACCESS_DENIED; + } else if (authenticated && complete) { + /* The PAC blob is returned directly */ + *pac_blob = data_blob_talloc(mem_ctx, pac_buffer.value, + pac_buffer.length); + + if (!pac_blob->data) { + status = NT_STATUS_NO_MEMORY; + } else { + status = NT_STATUS_OK; + } + + gss_release_buffer(&gss_min, &pac_buffer); + gss_release_buffer(&gss_min, &pac_display_buffer); + return status; + } else { + DEBUG(0, ("obtaining PAC via GSSAPI failed: authenticated: %s, complete: %s, more: %s\n", + authenticated ? "true" : "false", + complete ? "true" : "false", + more ? "true" : "false")); + return NT_STATUS_ACCESS_DENIED; + } + +#elif defined(HAVE_GSS_INQUIRE_SEC_CONTEXT_BY_OID) + gss_OID_desc pac_data_oid = { + .elements = discard_const(EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID), + .length = EXTRACT_PAC_AUTHZ_DATA_FROM_SEC_CONTEXT_OID_LENGTH + }; + + gss_buffer_set_t set = GSS_C_NO_BUFFER_SET; + + /* If we didn't have the routine to get a verified, validated + * PAC (supplied only by MIT at the time of writing), then try + * with the Heimdal OID (fetches the PAC directly and always + * validates) */ + gss_maj = gss_inquire_sec_context_by_oid( + &gss_min, gssapi_context, + &pac_data_oid, &set); + + /* First check for the error MIT gives for an unknown OID */ + if (gss_maj == GSS_S_UNAVAILABLE) { + DEBUG(1, ("unable to obtain a PAC against this GSSAPI library. " + "GSSAPI secured connections are available only with Heimdal or MIT Kerberos >= 1.8\n")); + } else if (gss_maj != 0) { + DEBUG(2, ("obtaining PAC via GSSAPI gss_inquire_sec_context_by_oid (Heimdal OID) failed: %s\n", + gssapi_error_string(mem_ctx, gss_maj, gss_min, gss_mech_krb5))); + } else { + if (set == GSS_C_NO_BUFFER_SET) { + DEBUG(0, ("gss_inquire_sec_context_by_oid returned unknown " + "data in results.\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + /* The PAC blob is returned directly */ + *pac_blob = data_blob_talloc(mem_ctx, set->elements[0].value, + set->elements[0].length); + if (!pac_blob->data) { + status = NT_STATUS_NO_MEMORY; + } else { + status = NT_STATUS_OK; + } + + gss_maj = gss_release_buffer_set(&gss_min, &set); + return status; + } +#else + DEBUG(1, ("unable to obtain a PAC against this GSSAPI library. " + "GSSAPI secured connections are available only with Heimdal or MIT Kerberos >= 1.8\n")); +#endif + return NT_STATUS_ACCESS_DENIED; +} + +NTSTATUS gssapi_get_session_key(TALLOC_CTX *mem_ctx, + gss_ctx_id_t gssapi_context, + DATA_BLOB *session_key, + uint32_t *keytype) +{ + OM_uint32 gss_min, gss_maj; + gss_buffer_set_t set = GSS_C_NO_BUFFER_SET; + + gss_maj = gss_inquire_sec_context_by_oid( + &gss_min, gssapi_context, + &gse_sesskey_inq_oid, &set); + if (gss_maj) { + DEBUG(0, ("gss_inquire_sec_context_by_oid failed [%s]\n", + gssapi_error_string(mem_ctx, + gss_maj, + gss_min, + discard_const_p(struct gss_OID_desc_struct, + gss_mech_krb5)))); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if ((set == GSS_C_NO_BUFFER_SET) || + (set->count == 0)) { +#ifdef HAVE_GSSKRB5_GET_SUBKEY + krb5_keyblock *subkey; + gss_maj = gsskrb5_get_subkey(&gss_min, + gssapi_context, + &subkey); + if (gss_maj != 0) { + DEBUG(1, ("NO session key for this mech\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + if (session_key) { + *session_key = data_blob_talloc(mem_ctx, + KRB5_KEY_DATA(subkey), KRB5_KEY_LENGTH(subkey)); + } + if (keytype) { + *keytype = KRB5_KEY_TYPE(subkey); + } + krb5_free_keyblock(NULL /* should be krb5_context */, subkey); + return NT_STATUS_OK; +#else + DEBUG(0, ("gss_inquire_sec_context_by_oid didn't return any session key (and no alternative method available)\n")); + return NT_STATUS_NO_USER_SESSION_KEY; +#endif + } + + if (session_key) { + *session_key = data_blob_talloc(mem_ctx, set->elements[0].value, + set->elements[0].length); + } + + if (keytype) { + int diflen, i; + const uint8_t *p; + + *keytype = 0; + if (set->count < 2) { + +#ifdef HAVE_GSSKRB5_GET_SUBKEY + krb5_keyblock *subkey; + gss_maj = gsskrb5_get_subkey(&gss_min, + gssapi_context, + &subkey); + if (gss_maj == 0) { + *keytype = KRB5_KEY_TYPE(subkey); + krb5_free_keyblock(NULL /* should be krb5_context */, subkey); + } +#endif + gss_release_buffer_set(&gss_min, &set); + + return NT_STATUS_OK; + + } else if (memcmp(set->elements[1].value, + gse_sesskeytype_oid.elements, + gse_sesskeytype_oid.length) != 0) { + /* Perhaps a non-krb5 session key */ + gss_release_buffer_set(&gss_min, &set); + return NT_STATUS_OK; + } + p = (const uint8_t *)set->elements[1].value + gse_sesskeytype_oid.length; + diflen = set->elements[1].length - gse_sesskeytype_oid.length; + if (diflen <= 0) { + gss_release_buffer_set(&gss_min, &set); + return NT_STATUS_INVALID_PARAMETER; + } + for (i = 0; i < diflen; i++) { + *keytype = (*keytype << 7) | (p[i] & 0x7f); + if (i + 1 != diflen && (p[i] & 0x80) == 0) { + gss_release_buffer_set(&gss_min, &set); + return NT_STATUS_INVALID_PARAMETER; + } + } + } + + gss_release_buffer_set(&gss_min, &set); + return NT_STATUS_OK; +} + + +char *gssapi_error_string(TALLOC_CTX *mem_ctx, + OM_uint32 maj_stat, OM_uint32 min_stat, + const gss_OID mech) +{ + OM_uint32 disp_min_stat, disp_maj_stat; + gss_buffer_desc maj_error_message; + gss_buffer_desc min_error_message; + char *maj_error_string, *min_error_string; + OM_uint32 msg_ctx = 0; + + char *ret; + + maj_error_message.value = NULL; + min_error_message.value = NULL; + maj_error_message.length = 0; + min_error_message.length = 0; + + disp_maj_stat = gss_display_status(&disp_min_stat, maj_stat, + GSS_C_GSS_CODE, mech, + &msg_ctx, &maj_error_message); + if (disp_maj_stat != 0) { + maj_error_message.value = NULL; + maj_error_message.length = 0; + } + disp_maj_stat = gss_display_status(&disp_min_stat, min_stat, + GSS_C_MECH_CODE, mech, + &msg_ctx, &min_error_message); + if (disp_maj_stat != 0) { + min_error_message.value = NULL; + min_error_message.length = 0; + } + + maj_error_string = talloc_strndup(mem_ctx, + (char *)maj_error_message.value, + maj_error_message.length); + + min_error_string = talloc_strndup(mem_ctx, + (char *)min_error_message.value, + min_error_message.length); + + ret = talloc_asprintf(mem_ctx, "%s: %s", + maj_error_string, min_error_string); + + talloc_free(maj_error_string); + talloc_free(min_error_string); + + gss_release_buffer(&disp_min_stat, &maj_error_message); + gss_release_buffer(&disp_min_stat, &min_error_message); + + return ret; +} + +#endif /* HAVE_KRB5 */ diff --git a/auth/kerberos/kerberos_pac.c b/auth/kerberos/kerberos_pac.c new file mode 100644 index 0000000..650c851 --- /dev/null +++ b/auth/kerberos/kerberos_pac.c @@ -0,0 +1,554 @@ +/* + Unix SMB/CIFS implementation. + kerberos authorization data (PAC) utility library + Copyright (C) Jim McDonough <jmcd@us.ibm.com> 2003 + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004-2005 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Luke Howard 2002-2003 + Copyright (C) Stefan Metzmacher 2004-2005 + Copyright (C) Guenther Deschner 2005,2007,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/>. +*/ + +#include "includes.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#ifdef HAVE_KRB5 + +#include "librpc/gen_ndr/ndr_krb5pac.h" +#include "librpc/gen_ndr/auth.h" +#include "auth/common_auth.h" +#include "auth/kerberos/pac_utils.h" + +krb5_error_code check_pac_checksum(DATA_BLOB pac_data, + struct PAC_SIGNATURE_DATA *sig, + krb5_context context, + const krb5_keyblock *keyblock) +{ + krb5_error_code ret; + krb5_checksum cksum; + krb5_keyusage usage = 0; + krb5_boolean checksum_valid = false; + krb5_data input; + + switch (sig->type) { + case CKSUMTYPE_HMAC_MD5: + /* ignores the key type */ + break; + case CKSUMTYPE_HMAC_SHA1_96_AES_256: + if (KRB5_KEY_TYPE(keyblock) != ENCTYPE_AES256_CTS_HMAC_SHA1_96) { + return EINVAL; + } + /* ok */ + break; + case CKSUMTYPE_HMAC_SHA1_96_AES_128: + if (KRB5_KEY_TYPE(keyblock) != ENCTYPE_AES128_CTS_HMAC_SHA1_96) { + return EINVAL; + } + /* ok */ + break; + default: + DEBUG(2,("check_pac_checksum: Checksum Type %d is not supported\n", + (int)sig->type)); + return EINVAL; + } + +#ifdef HAVE_CHECKSUM_IN_KRB5_CHECKSUM /* Heimdal */ + cksum.cksumtype = (krb5_cksumtype)sig->type; + cksum.checksum.length = sig->signature.length; + cksum.checksum.data = sig->signature.data; +#else /* MIT */ + cksum.checksum_type = (krb5_cksumtype)sig->type; + cksum.length = sig->signature.length; + cksum.contents = sig->signature.data; +#endif + +#ifdef HAVE_KRB5_KU_OTHER_CKSUM /* Heimdal */ + usage = KRB5_KU_OTHER_CKSUM; +#elif defined(HAVE_KRB5_KEYUSAGE_APP_DATA_CKSUM) /* MIT */ + usage = KRB5_KEYUSAGE_APP_DATA_CKSUM; +#else +#error UNKNOWN_KRB5_KEYUSAGE +#endif + + input.data = (char *)pac_data.data; + input.length = pac_data.length; + + ret = krb5_c_verify_checksum(context, + keyblock, + usage, + &input, + &cksum, + &checksum_valid); + if (!checksum_valid) { + ret = KRB5KRB_AP_ERR_BAD_INTEGRITY; + } + if (ret){ + DEBUG(2,("check_pac_checksum: PAC Verification failed: %s (%d)\n", + error_message(ret), ret)); + return ret; + } + + return ret; +} + +/** +* @brief Decode a blob containing a NDR encoded PAC structure +* +* @param mem_ctx - The memory context +* @param pac_data_blob - The data blob containing the NDR encoded data +* @param context - The Kerberos Context +* @param service_keyblock - The Service Key used to verify the checksum +* @param client_principal - The client principal +* @param tgs_authtime - The ticket timestamp +* @param pac_data_out - [out] The decoded PAC +* +* @return - A NTSTATUS error code +*/ +NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx, + DATA_BLOB pac_data_blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + struct PAC_DATA **pac_data_out) +{ + NTSTATUS status; + enum ndr_err_code ndr_err; + krb5_error_code ret; + DATA_BLOB modified_pac_blob; + + NTTIME tgs_authtime_nttime; + int i; + + struct PAC_SIGNATURE_DATA *srv_sig_ptr = NULL; + struct PAC_SIGNATURE_DATA *kdc_sig_ptr = NULL; + struct PAC_SIGNATURE_DATA *srv_sig_wipe = NULL; + struct PAC_SIGNATURE_DATA *kdc_sig_wipe = NULL; + struct PAC_LOGON_NAME *logon_name = NULL; + struct PAC_LOGON_INFO *logon_info = NULL; + struct PAC_DATA *pac_data = NULL; + struct PAC_DATA_RAW *pac_data_raw = NULL; + + DATA_BLOB *srv_sig_blob = NULL; + DATA_BLOB *kdc_sig_blob = NULL; + + bool bool_ret; + + TALLOC_CTX *tmp_ctx = talloc_new(mem_ctx); + if (!tmp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + if (pac_data_out) { + *pac_data_out = NULL; + } + + pac_data = talloc(tmp_ctx, struct PAC_DATA); + pac_data_raw = talloc(tmp_ctx, struct PAC_DATA_RAW); + kdc_sig_wipe = talloc(tmp_ctx, struct PAC_SIGNATURE_DATA); + srv_sig_wipe = talloc(tmp_ctx, struct PAC_SIGNATURE_DATA); + if (!pac_data_raw || !pac_data || !kdc_sig_wipe || !srv_sig_wipe) { + talloc_free(tmp_ctx); + return NT_STATUS_NO_MEMORY; + } + + ndr_err = ndr_pull_struct_blob(&pac_data_blob, pac_data, pac_data, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + if (pac_data->num_buffers < 4) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0,("less than 4 PAC buffers\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + ndr_err = ndr_pull_struct_blob( + &pac_data_blob, pac_data_raw, pac_data_raw, + (ndr_pull_flags_fn_t)ndr_pull_PAC_DATA_RAW); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the PAC: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + if (pac_data_raw->num_buffers < 4) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0,("less than 4 PAC buffers\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (pac_data->num_buffers != pac_data_raw->num_buffers) { + /* we need logon_ingo, service_key and kdc_key */ + DEBUG(0, ("misparse! PAC_DATA has %d buffers while " + "PAC_DATA_RAW has %d\n", pac_data->num_buffers, + pac_data_raw->num_buffers)); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + for (i=0; i < pac_data->num_buffers; i++) { + struct PAC_BUFFER *data_buf = &pac_data->buffers[i]; + struct PAC_BUFFER_RAW *raw_buf = &pac_data_raw->buffers[i]; + + if (data_buf->type != raw_buf->type) { + DEBUG(0, ("misparse! PAC_DATA buffer %d has type " + "%d while PAC_DATA_RAW has %d\n", i, + data_buf->type, raw_buf->type)); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + switch (data_buf->type) { + case PAC_TYPE_LOGON_INFO: + if (!data_buf->info) { + break; + } + logon_info = data_buf->info->logon_info.info; + break; + case PAC_TYPE_SRV_CHECKSUM: + if (!data_buf->info) { + break; + } + srv_sig_ptr = &data_buf->info->srv_cksum; + srv_sig_blob = &raw_buf->info->remaining; + break; + case PAC_TYPE_KDC_CHECKSUM: + if (!data_buf->info) { + break; + } + kdc_sig_ptr = &data_buf->info->kdc_cksum; + kdc_sig_blob = &raw_buf->info->remaining; + break; + case PAC_TYPE_LOGON_NAME: + logon_name = &data_buf->info->logon_name; + break; + default: + break; + } + } + + if (!logon_info) { + DEBUG(0,("PAC no logon_info\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!logon_name) { + DEBUG(0,("PAC no logon_name\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!srv_sig_ptr || !srv_sig_blob) { + DEBUG(0,("PAC no srv_key\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!kdc_sig_ptr || !kdc_sig_blob) { + DEBUG(0,("PAC no kdc_key\n")); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + /* Find and zero out the signatures, + * as required by the signing algorithm */ + + /* We find the data blobs above, + * now we parse them to get at the exact portion we should zero */ + ndr_err = ndr_pull_struct_blob( + kdc_sig_blob, kdc_sig_wipe, kdc_sig_wipe, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the KDC signature: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + ndr_err = ndr_pull_struct_blob( + srv_sig_blob, srv_sig_wipe, srv_sig_wipe, + (ndr_pull_flags_fn_t)ndr_pull_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't parse the SRV signature: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + /* Now zero the decoded structure */ + memset(kdc_sig_wipe->signature.data, + '\0', kdc_sig_wipe->signature.length); + memset(srv_sig_wipe->signature.data, + '\0', srv_sig_wipe->signature.length); + + /* and reencode, back into the same place it came from */ + ndr_err = ndr_push_struct_blob( + kdc_sig_blob, pac_data_raw, kdc_sig_wipe, + (ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the KDC signature: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + ndr_err = ndr_push_struct_blob( + srv_sig_blob, pac_data_raw, srv_sig_wipe, + (ndr_push_flags_fn_t)ndr_push_PAC_SIGNATURE_DATA); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the SRV signature: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + /* push out the whole structure, but now with zero'ed signatures */ + ndr_err = ndr_push_struct_blob( + &modified_pac_blob, pac_data_raw, pac_data_raw, + (ndr_push_flags_fn_t)ndr_push_PAC_DATA_RAW); + if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) { + status = ndr_map_error2ntstatus(ndr_err); + DEBUG(0,("can't repack the RAW PAC: %s\n", + nt_errstr(status))); + talloc_free(tmp_ctx); + return status; + } + + if (service_keyblock) { + /* verify by service_key */ + ret = check_pac_checksum(modified_pac_blob, srv_sig_ptr, + context, + service_keyblock); + if (ret) { + DEBUG(5, ("PAC Decode: Failed to verify the service " + "signature: %s\n", error_message(ret))); + return NT_STATUS_ACCESS_DENIED; + } + + if (krbtgt_keyblock) { + /* verify the service key checksum by krbtgt_key */ + ret = check_pac_checksum(srv_sig_ptr->signature, kdc_sig_ptr, + context, krbtgt_keyblock); + if (ret) { + DEBUG(1, ("PAC Decode: Failed to verify the KDC signature: %s\n", + smb_get_krb5_error_message(context, ret, tmp_ctx))); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + } + } + + if (tgs_authtime) { + /* Convert to NT time, so as not to loose accuracy in comparison */ + unix_to_nt_time(&tgs_authtime_nttime, tgs_authtime); + + if (tgs_authtime_nttime != logon_name->logon_time) { + DEBUG(2, ("PAC Decode: " + "Logon time mismatch between ticket and PAC!\n")); + DEBUG(2, ("PAC Decode: PAC: %s\n", + nt_time_string(tmp_ctx, logon_name->logon_time))); + DEBUG(2, ("PAC Decode: Ticket: %s\n", + nt_time_string(tmp_ctx, tgs_authtime_nttime))); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + } + + if (client_principal) { + char *client_principal_string; + ret = krb5_unparse_name_flags(context, client_principal, + KRB5_PRINCIPAL_UNPARSE_NO_REALM|KRB5_PRINCIPAL_UNPARSE_DISPLAY, + &client_principal_string); + if (ret) { + DEBUG(2, ("Could not unparse name from ticket to match with name from PAC: [%s]:%s\n", + logon_name->account_name, error_message(ret))); + talloc_free(tmp_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + bool_ret = strcmp(client_principal_string, logon_name->account_name) == 0; + + if (!bool_ret) { + DEBUG(2, ("Name in PAC [%s] does not match principal name " + "in ticket [%s]\n", + logon_name->account_name, + client_principal_string)); + SAFE_FREE(client_principal_string); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + SAFE_FREE(client_principal_string); + + } + + DEBUG(3,("Found account name from PAC: %s [%s]\n", + logon_info->info3.base.account_name.string, + logon_info->info3.base.full_name.string)); + + DEBUG(10,("Successfully validated Kerberos PAC\n")); + + if (DEBUGLEVEL >= 10) { + const char *s; + s = NDR_PRINT_STRUCT_STRING(tmp_ctx, PAC_DATA, pac_data); + if (s) { + DEBUGADD(10,("%s\n", s)); + } + } + + if (pac_data_out) { + *pac_data_out = talloc_steal(mem_ctx, pac_data); + } + + return NT_STATUS_OK; +} + +NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx, + DATA_BLOB blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + struct PAC_LOGON_INFO **logon_info) +{ + NTSTATUS nt_status; + struct PAC_DATA *pac_data; + int i; + nt_status = kerberos_decode_pac(mem_ctx, + blob, + context, + krbtgt_keyblock, + service_keyblock, + client_principal, + tgs_authtime, + &pac_data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + *logon_info = NULL; + for (i=0; i < pac_data->num_buffers; i++) { + if (pac_data->buffers[i].type != PAC_TYPE_LOGON_INFO) { + continue; + } + *logon_info = pac_data->buffers[i].info->logon_info.info; + } + if (!*logon_info) { + return NT_STATUS_INVALID_PARAMETER; + } + return NT_STATUS_OK; +} + +static NTSTATUS auth4_context_fetch_PAC_DATA_CTR( + struct auth4_context *auth_ctx, + TALLOC_CTX *mem_ctx, + struct smb_krb5_context *smb_krb5_context, + DATA_BLOB *pac_blob, + const char *princ_name, + const struct tsocket_address *remote_address, + uint32_t session_info_flags, + struct auth_session_info **session_info) +{ + struct PAC_DATA_CTR *pac_data_ctr = NULL; + NTSTATUS status; + + if (pac_blob == NULL) { + return NT_STATUS_NO_IMPERSONATION_TOKEN; + } + + pac_data_ctr = talloc_zero(mem_ctx, struct PAC_DATA_CTR); + if (pac_data_ctr == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + status = kerberos_decode_pac(pac_data_ctr, + *pac_blob, + NULL, + NULL, + NULL, + NULL, + 0, + &pac_data_ctr->pac_data); + if (!NT_STATUS_IS_OK(status)) { + goto fail; + } + + pac_data_ctr->pac_blob = data_blob_talloc(pac_data_ctr, + pac_blob->data, + pac_blob->length); + if (pac_data_ctr->pac_blob.length != pac_blob->length) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + *session_info = talloc_zero(mem_ctx, struct auth_session_info); + if (*session_info == NULL) { + status = NT_STATUS_NO_MEMORY; + goto fail; + } + + TALLOC_FREE(auth_ctx->private_data); + auth_ctx->private_data = talloc_move(auth_ctx, &pac_data_ctr); + + return NT_STATUS_OK; + +fail: + TALLOC_FREE(pac_data_ctr); + + return status; +} + +struct auth4_context *auth4_context_for_PAC_DATA_CTR(TALLOC_CTX *mem_ctx) +{ + struct auth4_context *auth_ctx = NULL; + + auth_ctx = talloc_zero(mem_ctx, struct auth4_context); + if (auth_ctx == NULL) { + return NULL; + } + auth_ctx->generate_session_info_pac = auth4_context_fetch_PAC_DATA_CTR; + + return auth_ctx; +} + +struct PAC_DATA_CTR *auth4_context_get_PAC_DATA_CTR(struct auth4_context *auth_ctx, + TALLOC_CTX *mem_ctx) +{ + struct PAC_DATA_CTR *p = NULL; + SMB_ASSERT(auth_ctx->generate_session_info_pac == auth4_context_fetch_PAC_DATA_CTR); + p = talloc_get_type_abort(auth_ctx->private_data, struct PAC_DATA_CTR); + auth_ctx->private_data = NULL; + return talloc_move(mem_ctx, &p); +} + +#endif diff --git a/auth/kerberos/pac_utils.h b/auth/kerberos/pac_utils.h new file mode 100644 index 0000000..36fd60c --- /dev/null +++ b/auth/kerberos/pac_utils.h @@ -0,0 +1,81 @@ +/* + Unix SMB/CIFS implementation. + kerberos authorization data (PAC) utility library + Copyright (C) Andrew Bartlett <abartlet@samba.org> 2011 + Copyright (C) Simo Sorce 2010-2012 + + 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 _PAC_UTILS_H +#define _PAC_UTILS_H + +#ifdef HAVE_KRB5 + +#include "lib/krb5_wrap/krb5_samba.h" +#include "lib/krb5_wrap/gss_samba.h" + +struct PAC_SIGNATURE_DATA; +struct PAC_DATA; +struct PAC_LOGON_INFO; + +krb5_error_code check_pac_checksum(DATA_BLOB pac_data, + struct PAC_SIGNATURE_DATA *sig, + krb5_context context, + const krb5_keyblock *keyblock); + +NTSTATUS kerberos_decode_pac(TALLOC_CTX *mem_ctx, + DATA_BLOB pac_data_blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + struct PAC_DATA **pac_data_out); + +NTSTATUS kerberos_pac_logon_info(TALLOC_CTX *mem_ctx, + DATA_BLOB blob, + krb5_context context, + const krb5_keyblock *krbtgt_keyblock, + const krb5_keyblock *service_keyblock, + krb5_const_principal client_principal, + time_t tgs_authtime, + struct PAC_LOGON_INFO **logon_info); + +struct PAC_DATA; +struct PAC_DATA_CTR { + DATA_BLOB pac_blob; + struct PAC_DATA *pac_data; +}; + +struct auth4_context *auth4_context_for_PAC_DATA_CTR(TALLOC_CTX *mem_ctx); +struct PAC_DATA_CTR *auth4_context_get_PAC_DATA_CTR(struct auth4_context *auth_ctx, + TALLOC_CTX *mem_ctx); + +NTSTATUS gssapi_obtain_pac_blob(TALLOC_CTX *mem_ctx, + gss_ctx_id_t gssapi_context, + gss_name_t gss_client_name, + DATA_BLOB *pac_data); +NTSTATUS gssapi_get_session_key(TALLOC_CTX *mem_ctx, + gss_ctx_id_t gssapi_context, + DATA_BLOB *session_key, + uint32_t *keytype); + +/* not the best place here, need to move to a more generic gssapi + * wrapper later */ +char *gssapi_error_string(TALLOC_CTX *mem_ctx, + OM_uint32 maj_stat, OM_uint32 min_stat, + const gss_OID mech); +#endif /* HAVE_KRB5 */ +#endif /* _PAC_UTILS_H */ diff --git a/auth/kerberos/wscript_build b/auth/kerberos/wscript_build new file mode 100644 index 0000000..1fa1b51 --- /dev/null +++ b/auth/kerberos/wscript_build @@ -0,0 +1,4 @@ +#!/usr/bin/env python +bld.SAMBA_SUBSYSTEM('KRB5_PAC', + source='gssapi_pac.c kerberos_pac.c gssapi_helper.c', + deps='gssapi_krb5 ndr-krb5pac krb5samba') diff --git a/auth/ntlmssp/gensec_ntlmssp.c b/auth/ntlmssp/gensec_ntlmssp.c new file mode 100644 index 0000000..329d8eb --- /dev/null +++ b/auth/ntlmssp/gensec_ntlmssp.c @@ -0,0 +1,246 @@ +/* + * Unix SMB/CIFS implementation. + * Version 3.0 + * NTLMSSP Signing routines + * Copyright (C) Luke Kenneth Casson Leighton 1996-2001 + * Copyright (C) Andrew Bartlett <abartlet@samba.org> 2003-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 "auth/ntlmssp/ntlmssp.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/ntlmssp/ntlmssp_private.h" + +NTSTATUS gensec_ntlmssp_magic(struct gensec_security *gensec_security, + const DATA_BLOB *first_packet) +{ + if (ntlmssp_blob_matches_magic(first_packet)) { + return NT_STATUS_OK; + } else { + return NT_STATUS_INVALID_PARAMETER; + } +} + +/** + * Return the NTLMSSP master session key + * + * @param ntlmssp_state NTLMSSP State + */ + +NTSTATUS gensec_ntlmssp_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + + if (ntlmssp_state->expected_state != NTLMSSP_DONE) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (!ntlmssp_state->session_key.data) { + return NT_STATUS_NO_USER_SESSION_KEY; + } + *session_key = data_blob_talloc(mem_ctx, ntlmssp_state->session_key.data, ntlmssp_state->session_key.length); + if (!session_key->data) { + return NT_STATUS_NO_MEMORY; + } + + return NT_STATUS_OK; +} + +bool gensec_ntlmssp_have_feature(struct gensec_security *gensec_security, + uint32_t feature) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + + if (feature & GENSEC_FEATURE_SIGN) { + if (!ntlmssp_state->session_key.length) { + return false; + } + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + return true; + } + } + if (feature & GENSEC_FEATURE_SEAL) { + if (!ntlmssp_state->session_key.length) { + return false; + } + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + return true; + } + } + if (feature & GENSEC_FEATURE_SESSION_KEY) { + if (ntlmssp_state->session_key.length) { + return true; + } + } + if (feature & GENSEC_FEATURE_DCE_STYLE) { + return true; + } + if (feature & GENSEC_FEATURE_ASYNC_REPLIES) { + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + return true; + } + } + if (feature & GENSEC_FEATURE_SIGN_PKT_HEADER) { + return true; + } + if (feature & GENSEC_FEATURE_NEW_SPNEGO) { + if (!ntlmssp_state->session_key.length) { + return false; + } + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + return false; + } + return ntlmssp_state->new_spnego; + } + + return false; +} + +NTSTATUS gensec_ntlmssp_start(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp; + + gensec_ntlmssp = talloc_zero(gensec_security, + struct gensec_ntlmssp_context); + if (!gensec_ntlmssp) { + return NT_STATUS_NO_MEMORY; + } + + gensec_security->private_data = gensec_ntlmssp; + return NT_STATUS_OK; +} + +NTSTATUS gensec_ntlmssp_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + NTSTATUS nt_status; + + nt_status = ntlmssp_sign_packet(gensec_ntlmssp->ntlmssp_state, + sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); + + return nt_status; +} + +NTSTATUS gensec_ntlmssp_check_packet(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + NTSTATUS nt_status; + + nt_status = ntlmssp_check_packet(gensec_ntlmssp->ntlmssp_state, + data, length, + whole_pdu, pdu_length, + sig); + + return nt_status; +} + +NTSTATUS gensec_ntlmssp_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + NTSTATUS nt_status; + + nt_status = ntlmssp_seal_packet(gensec_ntlmssp->ntlmssp_state, + sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + sig); + + return nt_status; +} + +/* + wrappers for the ntlmssp_*() functions +*/ +NTSTATUS gensec_ntlmssp_unseal_packet(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + NTSTATUS nt_status; + + nt_status = ntlmssp_unseal_packet(gensec_ntlmssp->ntlmssp_state, + data, length, + whole_pdu, pdu_length, + sig); + + return nt_status; +} + +size_t gensec_ntlmssp_sig_size(struct gensec_security *gensec_security, size_t data_size) +{ + return NTLMSSP_SIG_SIZE; +} + +NTSTATUS gensec_ntlmssp_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + + return ntlmssp_wrap(gensec_ntlmssp->ntlmssp_state, + out_mem_ctx, + in, out); +} + + +NTSTATUS gensec_ntlmssp_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + + return ntlmssp_unwrap(gensec_ntlmssp->ntlmssp_state, + out_mem_ctx, + in, out); +} diff --git a/auth/ntlmssp/gensec_ntlmssp_server.c b/auth/ntlmssp/gensec_ntlmssp_server.c new file mode 100644 index 0000000..ab92f4d --- /dev/null +++ b/auth/ntlmssp/gensec_ntlmssp_server.c @@ -0,0 +1,245 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, client server side parsing + + 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 "system/network.h" +#include "lib/tsocket/tsocket.h" +#include "auth/ntlmssp/ntlmssp.h" +#include "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "auth/ntlmssp/ntlmssp_ndr.h" +#include "auth/ntlmssp/ntlmssp_private.h" +#include "../libcli/auth/libcli_auth.h" +#include "../lib/crypto/crypto.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/common_auth.h" +#include "param/param.h" +#include "param/loadparm.h" +#include "libds/common/roles.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. + * + */ + +NTSTATUS gensec_ntlmssp_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info) +{ + NTSTATUS nt_status; + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + uint32_t session_info_flags = 0; + + if (gensec_security->want_features & GENSEC_FEATURE_UNIX_TOKEN) { + session_info_flags |= AUTH_SESSION_INFO_UNIX_TOKEN; + } + + session_info_flags |= AUTH_SESSION_INFO_DEFAULT_GROUPS; + session_info_flags |= AUTH_SESSION_INFO_NTLM; + + if (gensec_security->auth_context && gensec_security->auth_context->generate_session_info) { + nt_status = gensec_security->auth_context->generate_session_info(gensec_security->auth_context, mem_ctx, + gensec_ntlmssp->server_returned_info, + gensec_ntlmssp->ntlmssp_state->user, + session_info_flags, + session_info); + } else { + DEBUG(0, ("Cannot generate a session_info without the auth_context\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + NT_STATUS_NOT_OK_RETURN(nt_status); + + nt_status = gensec_ntlmssp_session_key(gensec_security, *session_info, + &(*session_info)->session_key); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_USER_SESSION_KEY)) { + (*session_info)->session_key = data_blob_null; + nt_status = NT_STATUS_OK; + } + + return nt_status; +} + +/** + * Start NTLMSSP on the server side + * + */ +NTSTATUS gensec_ntlmssp_server_start(struct gensec_security *gensec_security) +{ + NTSTATUS nt_status; + struct ntlmssp_state *ntlmssp_state; + struct gensec_ntlmssp_context *gensec_ntlmssp; + const char *netbios_name; + const char *netbios_domain; + const char *dns_name; + const char *dns_domain; + enum server_role role; + + role = lpcfg_server_role(gensec_security->settings->lp_ctx); + + nt_status = gensec_ntlmssp_start(gensec_security); + NT_STATUS_NOT_OK_RETURN(nt_status); + + gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + + ntlmssp_state = talloc_zero(gensec_ntlmssp, + struct ntlmssp_state); + if (!ntlmssp_state) { + return NT_STATUS_NO_MEMORY; + } + gensec_ntlmssp->ntlmssp_state = ntlmssp_state; + + ntlmssp_state->role = NTLMSSP_SERVER; + + ntlmssp_state->expected_state = NTLMSSP_NEGOTIATE; + + ntlmssp_state->allow_lm_response = + lpcfg_lanman_auth(gensec_security->settings->lp_ctx); + + if (ntlmssp_state->allow_lm_response && + gensec_setting_bool(gensec_security->settings, + "ntlmssp_server", "allow_lm_key", false)) + { + ntlmssp_state->allow_lm_key = true; + } + + ntlmssp_state->force_old_spnego = false; + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "force_old_spnego", false)) { + /* + * For testing Windows 2000 mode + */ + ntlmssp_state->force_old_spnego = true; + } + + ntlmssp_state->neg_flags = + NTLMSSP_NEGOTIATE_NTLM | NTLMSSP_NEGOTIATE_VERSION; + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "128bit", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_128; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "56bit", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_56; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "keyexchange", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "alwayssign", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_server", "ntlm2", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2; + } + + if (ntlmssp_state->allow_lm_key) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + } + + /* + * We always allow NTLMSSP_NEGOTIATE_SIGN and NTLMSSP_NEGOTIATE_SEAL. + * + * These will be removed if the client doesn't want them. + */ + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL; + + + if (role == ROLE_STANDALONE) { + ntlmssp_state->server.is_standalone = true; + } else { + ntlmssp_state->server.is_standalone = false; + } + + if (gensec_security->settings->server_netbios_name) { + netbios_name = gensec_security->settings->server_netbios_name; + } else { + netbios_name = lpcfg_netbios_name(gensec_security->settings->lp_ctx); + } + + if (gensec_security->settings->server_netbios_domain) { + netbios_domain = gensec_security->settings->server_netbios_domain; + } else { + netbios_domain = lpcfg_workgroup(gensec_security->settings->lp_ctx); + } + + if (gensec_security->settings->server_dns_name) { + dns_name = gensec_security->settings->server_dns_name; + } else { + const char *dnsdomain = lpcfg_dnsdomain(gensec_security->settings->lp_ctx); + char *lower_netbiosname; + + lower_netbiosname = strlower_talloc(ntlmssp_state, netbios_name); + NT_STATUS_HAVE_NO_MEMORY(lower_netbiosname); + + /* Find out the DNS host name */ + if (dnsdomain && dnsdomain[0] != '\0') { + dns_name = talloc_asprintf(ntlmssp_state, "%s.%s", + lower_netbiosname, + dnsdomain); + talloc_free(lower_netbiosname); + NT_STATUS_HAVE_NO_MEMORY(dns_name); + } else { + dns_name = lower_netbiosname; + } + } + + if (gensec_security->settings->server_dns_domain) { + dns_domain = gensec_security->settings->server_dns_domain; + } else { + dns_domain = lpcfg_dnsdomain(gensec_security->settings->lp_ctx); + } + + ntlmssp_state->server.netbios_name = talloc_strdup(ntlmssp_state, netbios_name); + NT_STATUS_HAVE_NO_MEMORY(ntlmssp_state->server.netbios_name); + + ntlmssp_state->server.netbios_domain = talloc_strdup(ntlmssp_state, netbios_domain); + NT_STATUS_HAVE_NO_MEMORY(ntlmssp_state->server.netbios_domain); + + ntlmssp_state->server.dns_name = talloc_strdup(ntlmssp_state, dns_name); + NT_STATUS_HAVE_NO_MEMORY(ntlmssp_state->server.dns_name); + + ntlmssp_state->server.dns_domain = talloc_strdup(ntlmssp_state, dns_domain); + NT_STATUS_HAVE_NO_MEMORY(ntlmssp_state->server.dns_domain); + + ntlmssp_state->neg_flags |= ntlmssp_state->required_flags; + ntlmssp_state->conf_flags = ntlmssp_state->neg_flags; + + return NT_STATUS_OK; +} + diff --git a/auth/ntlmssp/ntlmssp.c b/auth/ntlmssp/ntlmssp.c new file mode 100644 index 0000000..745f262 --- /dev/null +++ b/auth/ntlmssp/ntlmssp.c @@ -0,0 +1,408 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, client server side parsing + + 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/>. +*/ + +struct auth_session_info; + +#include "includes.h" +#include <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "auth/ntlmssp/ntlmssp.h" +#include "auth/ntlmssp/ntlmssp_private.h" +#include "../libcli/auth/libcli_auth.h" +#include "librpc/gen_ndr/ndr_dcerpc.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/** + * Callbacks for NTLMSSP - for both client and server operating modes + * + */ + +static const struct ntlmssp_callbacks { + enum ntlmssp_role role; + enum ntlmssp_message_type command; + NTSTATUS (*sync_fn)(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out); + struct tevent_req *(*send_fn)(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in); + NTSTATUS (*recv_fn)(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); +} ntlmssp_callbacks[] = { + { + .role = NTLMSSP_CLIENT, + .command = NTLMSSP_INITIAL, + .sync_fn = ntlmssp_client_initial, + },{ + .role = NTLMSSP_CLIENT, + .command = NTLMSSP_NEGOTIATE, + .sync_fn = gensec_ntlmssp_resume_ccache, + },{ + .role = NTLMSSP_SERVER, + .command = NTLMSSP_NEGOTIATE, + .sync_fn = gensec_ntlmssp_server_negotiate, + },{ + .role = NTLMSSP_CLIENT, + .command = NTLMSSP_CHALLENGE, + .sync_fn = ntlmssp_client_challenge, + },{ + .role = NTLMSSP_SERVER, + .command = NTLMSSP_AUTH, + .send_fn = ntlmssp_server_auth_send, + .recv_fn = ntlmssp_server_auth_recv, + } +}; + + +static NTSTATUS gensec_ntlmssp_update_find(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + const DATA_BLOB input, uint32_t *idx) +{ + uint32_t ntlmssp_command; + uint32_t i; + + if (gensec_ntlmssp->ntlmssp_state->expected_state == NTLMSSP_DONE) { + /* We are strict here because other modules, which we + * don't fully control (such as GSSAPI) are also + * strict, but are tested less often */ + + DEBUG(1, ("Called NTLMSSP after state machine was 'done'\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!input.length) { + switch (gensec_ntlmssp->ntlmssp_state->role) { + case NTLMSSP_CLIENT: + if (gensec_ntlmssp->ntlmssp_state->resume_ccache) { + /* + * make sure gensec_ntlmssp_resume_ccache() + * will be called + */ + ntlmssp_command = NTLMSSP_NEGOTIATE; + break; + } + + ntlmssp_command = NTLMSSP_INITIAL; + break; + case NTLMSSP_SERVER: + if (gensec_security->want_features & GENSEC_FEATURE_DATAGRAM_MODE) { + /* 'datagram' mode - no neg packet */ + ntlmssp_command = NTLMSSP_NEGOTIATE; + } else { + /* This is normal in SPNEGO mech negotiation fallback */ + DEBUG(2, ("Failed to parse NTLMSSP packet: zero length\n")); + return NT_STATUS_INVALID_PARAMETER; + } + break; + default: + DEBUG(1, ("NTLMSSP state has invalid role %d\n", + gensec_ntlmssp->ntlmssp_state->role)); + return NT_STATUS_INVALID_PARAMETER; + } + } else { + if (!msrpc_parse(gensec_ntlmssp->ntlmssp_state, + &input, "Cd", + "NTLMSSP", + &ntlmssp_command)) { + DEBUG(1, ("Failed to parse NTLMSSP packet, could not extract NTLMSSP command\n")); + dump_data(2, input.data, input.length); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (ntlmssp_command != gensec_ntlmssp->ntlmssp_state->expected_state) { + DEBUG(2, ("got NTLMSSP command %u, expected %u\n", ntlmssp_command, + gensec_ntlmssp->ntlmssp_state->expected_state)); + return NT_STATUS_INVALID_PARAMETER; + } + + for (i=0; i < ARRAY_SIZE(ntlmssp_callbacks); i++) { + if (ntlmssp_callbacks[i].role == gensec_ntlmssp->ntlmssp_state->role && + ntlmssp_callbacks[i].command == ntlmssp_command) { + *idx = i; + return NT_STATUS_OK; + } + } + + DEBUG(1, ("failed to find NTLMSSP callback for NTLMSSP mode %u, command %u\n", + gensec_ntlmssp->ntlmssp_state->role, ntlmssp_command)); + + return NT_STATUS_INVALID_PARAMETER; +} + +struct gensec_ntlmssp_update_state { + const struct ntlmssp_callbacks *cb; + NTSTATUS status; + DATA_BLOB out; +}; + +static void gensec_ntlmssp_update_done(struct tevent_req *subreq); + +static struct tevent_req *gensec_ntlmssp_update_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct tevent_req *req = NULL; + struct gensec_ntlmssp_update_state *state = NULL; + NTSTATUS status; + uint32_t i = 0; + + req = tevent_req_create(mem_ctx, &state, + struct gensec_ntlmssp_update_state); + if (req == NULL) { + return NULL; + } + + status = gensec_ntlmssp_update_find(gensec_security, + gensec_ntlmssp, + in, &i); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + if (ntlmssp_callbacks[i].send_fn != NULL) { + struct tevent_req *subreq = NULL; + + state->cb = &ntlmssp_callbacks[i]; + + subreq = state->cb->send_fn(state, ev, + gensec_security, + in); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, + gensec_ntlmssp_update_done, + req); + return req; + } + + status = ntlmssp_callbacks[i].sync_fn(gensec_security, + state, + in, &state->out); + state->status = status; + if (NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + tevent_req_done(req); + return tevent_req_post(req, ev); + } + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + tevent_req_done(req); + return tevent_req_post(req, ev); +} + +static void gensec_ntlmssp_update_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct gensec_ntlmssp_update_state *state = + tevent_req_data(req, + struct gensec_ntlmssp_update_state); + NTSTATUS status; + + status = state->cb->recv_fn(subreq, state, &state->out); + TALLOC_FREE(subreq); + if (GENSEC_UPDATE_IS_NTERROR(status)) { + tevent_req_nterror(req, status); + return; + } + + state->status = status; + tevent_req_done(req); +} + +static NTSTATUS gensec_ntlmssp_update_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + struct gensec_ntlmssp_update_state *state = + tevent_req_data(req, + struct gensec_ntlmssp_update_state); + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + *out = state->out; + talloc_steal(out_mem_ctx, state->out.data); + status = state->status; + tevent_req_received(req); + return status; +} + +static NTSTATUS gensec_ntlmssp_may_reset_crypto(struct gensec_security *gensec_security, + bool full_reset) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + NTSTATUS status; + bool reset_seqnums = full_reset; + + if (!gensec_ntlmssp_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return NT_STATUS_OK; + } + + status = ntlmssp_sign_reset(ntlmssp_state, reset_seqnums); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("Could not reset NTLMSSP signing/sealing system (error was: %s)\n", + nt_errstr(status))); + return status; + } + + return NT_STATUS_OK; +} + +static const char *gensec_ntlmssp_final_auth_type(struct gensec_security *gensec_security) +{ + return GENSEC_FINAL_AUTH_TYPE_NTLMSSP; +} + +static const char *gensec_ntlmssp_oids[] = { + GENSEC_OID_NTLMSSP, + NULL +}; + +static const struct gensec_security_ops gensec_ntlmssp_security_ops = { + .name = "ntlmssp", + .sasl_name = GENSEC_SASL_NAME_NTLMSSP, /* "NTLM" */ + .auth_type = DCERPC_AUTH_TYPE_NTLMSSP, + .weak_crypto = true, + .oid = gensec_ntlmssp_oids, + .client_start = gensec_ntlmssp_client_start, + .server_start = gensec_ntlmssp_server_start, + .magic = gensec_ntlmssp_magic, + .update_send = gensec_ntlmssp_update_send, + .update_recv = gensec_ntlmssp_update_recv, + .may_reset_crypto= gensec_ntlmssp_may_reset_crypto, + .sig_size = gensec_ntlmssp_sig_size, + .sign_packet = gensec_ntlmssp_sign_packet, + .check_packet = gensec_ntlmssp_check_packet, + .seal_packet = gensec_ntlmssp_seal_packet, + .unseal_packet = gensec_ntlmssp_unseal_packet, + .wrap = gensec_ntlmssp_wrap, + .unwrap = gensec_ntlmssp_unwrap, + .session_key = gensec_ntlmssp_session_key, + .session_info = gensec_ntlmssp_session_info, + .have_feature = gensec_ntlmssp_have_feature, + .final_auth_type = gensec_ntlmssp_final_auth_type, + .enabled = true, + .priority = GENSEC_NTLMSSP +}; + +static const struct gensec_security_ops gensec_ntlmssp_resume_ccache_ops = { + .name = "ntlmssp_resume_ccache", + .weak_crypto = true, + .client_start = gensec_ntlmssp_resume_ccache_start, + .update_send = gensec_ntlmssp_update_send, + .update_recv = gensec_ntlmssp_update_recv, + .session_key = gensec_ntlmssp_session_key, + .have_feature = gensec_ntlmssp_have_feature, + .enabled = true, + .priority = GENSEC_NTLMSSP +}; + +_PUBLIC_ NTSTATUS gensec_ntlmssp_init(TALLOC_CTX *ctx) +{ + NTSTATUS ret; + + ret = gensec_register(ctx, &gensec_ntlmssp_security_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_ntlmssp_security_ops.name)); + return ret; + } + + ret = gensec_register(ctx, &gensec_ntlmssp_resume_ccache_ops); + if (!NT_STATUS_IS_OK(ret)) { + DEBUG(0,("Failed to register '%s' gensec backend!\n", + gensec_ntlmssp_resume_ccache_ops.name)); + return ret; + } + + return ret; +} + +static struct gensec_security *gensec_find_child_by_ops(struct gensec_security *gensec_security, + const struct gensec_security_ops *ops) +{ + struct gensec_security *current = gensec_security; + + while (current != NULL) { + if (current->ops == ops) { + return current; + } + + current = current->child_security; + } + + return NULL; +} + +uint32_t gensec_ntlmssp_neg_flags(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp; + + gensec_security = gensec_find_child_by_ops(gensec_security, + &gensec_ntlmssp_security_ops); + if (gensec_security == NULL) { + return 0; + } + + gensec_ntlmssp = talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + return gensec_ntlmssp->ntlmssp_state->neg_flags; +} + +const char *gensec_ntlmssp_server_domain(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp; + + gensec_security = gensec_find_child_by_ops(gensec_security, + &gensec_ntlmssp_security_ops); + if (gensec_security == NULL) { + return NULL; + } + + gensec_ntlmssp = talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + return gensec_ntlmssp->ntlmssp_state->server.netbios_domain; +} diff --git a/auth/ntlmssp/ntlmssp.h b/auth/ntlmssp/ntlmssp.h new file mode 100644 index 0000000..658d3fa --- /dev/null +++ b/auth/ntlmssp/ntlmssp.h @@ -0,0 +1,151 @@ +/* + Unix SMB/CIFS implementation. + SMB parameters and setup + Copyright (C) Andrew Tridgell 1992-1997 + Copyright (C) Luke Kenneth Casson Leighton 1996-1997 + Copyright (C) Paul Ashton 1997 + Copyright (C) Andrew Bartlett 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "../librpc/gen_ndr/ntlmssp.h" + +struct auth_context; +struct auth_serversupplied_info; +struct tsocket_address; +struct auth_user_info_dc; +struct gensec_security; +struct ntlmssp_state; + +/* NTLMSSP mode */ +enum ntlmssp_role +{ + NTLMSSP_SERVER, + NTLMSSP_CLIENT +}; + +/* NTLMSSP message types */ +enum ntlmssp_message_type +{ + NTLMSSP_INITIAL = 0 /* samba internal state */, + NTLMSSP_NEGOTIATE = 1, + NTLMSSP_CHALLENGE = 2, + NTLMSSP_AUTH = 3, + NTLMSSP_UNKNOWN = 4, + NTLMSSP_DONE = 5 /* samba final state */ +}; + +#define NTLMSSP_FEATURE_SESSION_KEY 0x00000001 +#define NTLMSSP_FEATURE_SIGN 0x00000002 +#define NTLMSSP_FEATURE_SEAL 0x00000004 +#define NTLMSSP_FEATURE_CCACHE 0x00000008 + +union ntlmssp_crypt_state; + +struct ntlmssp_state +{ + enum ntlmssp_role role; + uint32_t expected_state; + + bool unicode; + bool use_ntlmv2; + bool use_ccache; + bool resume_ccache; + bool use_nt_response; /* Set to 'False' to debug what happens when the NT response is omited */ + bool allow_lm_response;/* The LM_RESPONSE code is not very secure... */ + bool allow_lm_key; /* The LM_KEY code is not very secure... */ + + const char *user; + const char *domain; + uint8_t *nt_hash; + uint8_t *lm_hash; + + DATA_BLOB negotiate_blob; + DATA_BLOB challenge_blob; + bool new_spnego; + bool force_old_spnego; + + struct { + const char *netbios_name; + const char *netbios_domain; + struct AV_PAIR_LIST av_pair_list; + } client; + + struct { + bool is_standalone; + const char *netbios_name; + const char *netbios_domain; + const char *dns_name; + const char *dns_domain; + NTTIME challenge_endtime; + struct AV_PAIR_LIST av_pair_list; + } server; + + DATA_BLOB internal_chal; /* Random challenge as supplied to the client for NTLM authentication */ + + DATA_BLOB chal; /* Random challenge as input into the actual NTLM (or NTLM2) authentication */ + DATA_BLOB lm_resp; + DATA_BLOB nt_resp; + DATA_BLOB session_key; + + uint32_t conf_flags; + uint32_t required_flags; + uint32_t neg_flags; /* the current state of negotiation with the NTLMSSP partner */ + + bool force_wrap_seal; + + union ntlmssp_crypt_state *crypt; +}; + +/* The following definitions come from libcli/auth/ntlmssp_sign.c */ + +NTSTATUS ntlmssp_sign_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS ntlmssp_check_packet(struct ntlmssp_state *ntlmssp_state, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) ; +NTSTATUS ntlmssp_seal_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS ntlmssp_unseal_packet(struct ntlmssp_state *ntlmssp_state, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +NTSTATUS ntlmssp_wrap(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS ntlmssp_unwrap(struct ntlmssp_state *ntlmssp_stae, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS ntlmssp_sign_reset(struct ntlmssp_state *ntlmssp_state, + bool reset_seqnums); +NTSTATUS ntlmssp_sign_init(struct ntlmssp_state *ntlmssp_state); + +bool ntlmssp_blob_matches_magic(const DATA_BLOB *blob); + +/* The following definitions come from auth/ntlmssp/gensec_ntlmssp.c */ + +NTSTATUS gensec_ntlmssp_init(TALLOC_CTX *ctx); + +uint32_t gensec_ntlmssp_neg_flags(struct gensec_security *gensec_security); +const char *gensec_ntlmssp_server_domain(struct gensec_security *gensec_security); diff --git a/auth/ntlmssp/ntlmssp_client.c b/auth/ntlmssp/ntlmssp_client.c new file mode 100644 index 0000000..af1642d --- /dev/null +++ b/auth/ntlmssp/ntlmssp_client.c @@ -0,0 +1,1020 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, client server side parsing + + 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/>. +*/ + +struct auth_session_info; + +#include "includes.h" +#include "auth/ntlmssp/ntlmssp.h" +#include "../libcli/auth/libcli_auth.h" +#include "auth/credentials/credentials.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "param/param.h" +#include "auth/ntlmssp/ntlmssp_private.h" +#include "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "../auth/ntlmssp/ntlmssp_ndr.h" +#include "../nsswitch/libwbclient/wbclient.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/********************************************************************* + Client side NTLMSSP +*********************************************************************/ + +/** + * Next state function for the Initial packet + * + * @param ntlmssp_state NTLMSSP State + * @param out_mem_ctx The DATA_BLOB *out will be allocated on this context + * @param in A NULL data blob (input ignored) + * @param out The initial negotiate request to the server, as an talloc()ed DATA_BLOB, on out_mem_ctx + * @return Errors or NT_STATUS_OK. + */ + +NTSTATUS ntlmssp_client_initial(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + NTSTATUS status; + const DATA_BLOB version_blob = ntlmssp_version_blob(); + + /* generate the ntlmssp negotiate packet */ + status = msrpc_gen(out_mem_ctx, + out, "CddAAb", + "NTLMSSP", + NTLMSSP_NEGOTIATE, + ntlmssp_state->neg_flags, + "", /* domain */ + "", /* workstation */ + version_blob.data, version_blob.length); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("ntlmssp_client_initial: failed to generate " + "ntlmssp negotiate packet\n")); + return status; + } + + if (DEBUGLEVEL >= 10) { + struct NEGOTIATE_MESSAGE *negotiate = talloc( + ntlmssp_state, struct NEGOTIATE_MESSAGE); + if (negotiate != NULL) { + status = ntlmssp_pull_NEGOTIATE_MESSAGE( + out, negotiate, negotiate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(NEGOTIATE_MESSAGE, + negotiate); + } + TALLOC_FREE(negotiate); + } + } + + ntlmssp_state->negotiate_blob = data_blob_dup_talloc(ntlmssp_state, + *out); + if (ntlmssp_state->negotiate_blob.length != out->length) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->expected_state = NTLMSSP_CHALLENGE; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +NTSTATUS gensec_ntlmssp_resume_ccache(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + uint32_t neg_flags = 0; + uint32_t ntlmssp_command; + NTSTATUS status; + bool ok; + + *out = data_blob_null; + + if (in.length == 0) { + /* + * This is compat code for older callers + * which were missing the "initial_blob"/"negotiate_blob". + * + * That means we can't calculate the NTLMSSP_MIC + * field correctly and need to force the + * old_spnego behaviour. + */ + DEBUG(10, ("%s: in.length==%u force_old_spnego!\n", + __func__, (unsigned int)in.length)); + ntlmssp_state->force_old_spnego = true; + ntlmssp_state->neg_flags |= ntlmssp_state->required_flags; + ntlmssp_state->required_flags = 0; + ntlmssp_state->expected_state = NTLMSSP_CHALLENGE; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + /* parse the NTLMSSP packet */ + + if (in.length > UINT16_MAX) { + DEBUG(1, ("%s: reject large request of length %u\n", + __func__, (unsigned int)in.length)); + return NT_STATUS_INVALID_PARAMETER; + } + + ok = msrpc_parse(ntlmssp_state, &in, "Cdd", + "NTLMSSP", + &ntlmssp_command, + &neg_flags); + if (!ok) { + DEBUG(1, ("%s: failed to parse NTLMSSP Negotiate of length %u\n", + __func__, (unsigned int)in.length)); + dump_data(2, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + if (ntlmssp_command != NTLMSSP_NEGOTIATE) { + DEBUG(1, ("%s: no NTLMSSP Negotiate message (length %u)\n", + __func__, (unsigned int)in.length)); + dump_data(2, in.data, in.length); + return NT_STATUS_INVALID_PARAMETER; + } + + ntlmssp_state->neg_flags = neg_flags; + DEBUG(3, ("Imported Negotiate flags:\n")); + debug_ntlmssp_flags(neg_flags); + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_UNICODE) { + ntlmssp_state->unicode = true; + } else { + ntlmssp_state->unicode = false; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + gensec_security->want_features |= GENSEC_FEATURE_SIGN; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + gensec_security->want_features |= GENSEC_FEATURE_SEAL; + } + + ntlmssp_state->conf_flags = ntlmssp_state->neg_flags; + ntlmssp_state->required_flags = 0; + + if (DEBUGLEVEL >= 10) { + struct NEGOTIATE_MESSAGE *negotiate = talloc( + ntlmssp_state, struct NEGOTIATE_MESSAGE); + if (negotiate != NULL) { + status = ntlmssp_pull_NEGOTIATE_MESSAGE( + &in, negotiate, negotiate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(NEGOTIATE_MESSAGE, + negotiate); + } + TALLOC_FREE(negotiate); + } + } + + ntlmssp_state->negotiate_blob = data_blob_dup_talloc(ntlmssp_state, + in); + if (ntlmssp_state->negotiate_blob.length != in.length) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->expected_state = NTLMSSP_CHALLENGE; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +/** + * Next state function for the Challenge Packet. Generate an auth packet. + * + * @param gensec_security GENSEC state + * @param out_mem_ctx Memory context for *out + * @param in The server challnege, as a DATA_BLOB. reply.data must be NULL + * @param out The next request (auth packet) to the server, as an allocated DATA_BLOB, on the out_mem_ctx context + * @return Errors or NT_STATUS_OK. + */ + +NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + uint32_t chal_flags, ntlmssp_command, unkn1 = 0, unkn2 = 0; + DATA_BLOB server_domain_blob; + DATA_BLOB challenge_blob; + DATA_BLOB target_info = data_blob(NULL, 0); + char *server_domain; + const char *chal_parse_string; + const char *chal_parse_string_short = NULL; + const char *auth_gen_string; + DATA_BLOB lm_response = data_blob(NULL, 0); + DATA_BLOB nt_response = data_blob(NULL, 0); + DATA_BLOB session_key = data_blob(NULL, 0); + DATA_BLOB lm_session_key = data_blob(NULL, 0); + DATA_BLOB encrypted_session_key = data_blob(NULL, 0); + NTSTATUS nt_status; + int flags = 0; + const char *user = NULL, *domain = NULL, *workstation = NULL; + bool is_anonymous = false; + const DATA_BLOB version_blob = ntlmssp_version_blob(); + const NTTIME *server_timestamp = NULL; + uint8_t mic_buffer[NTLMSSP_MIC_SIZE] = { 0, }; + DATA_BLOB mic_blob = data_blob_const(mic_buffer, sizeof(mic_buffer)); + gnutls_hmac_hd_t hmac_hnd = NULL; + int rc; + + TALLOC_CTX *mem_ctx = talloc_new(out_mem_ctx); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + + if (!msrpc_parse(mem_ctx, + &in, "CdBd", + "NTLMSSP", + &ntlmssp_command, + &server_domain_blob, + &chal_flags)) { + DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#1)\n")); + dump_data(2, in.data, in.length); + talloc_free(mem_ctx); + + return NT_STATUS_INVALID_PARAMETER; + } + + data_blob_free(&server_domain_blob); + + DEBUG(3, ("Got challenge flags:\n")); + debug_ntlmssp_flags(chal_flags); + + nt_status = ntlmssp_handle_neg_flags(ntlmssp_state, + chal_flags, "challenge"); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (ntlmssp_state->unicode) { + if (chal_flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + chal_parse_string = "CdUdbddB"; + } else { + chal_parse_string = "CdUdbdd"; + chal_parse_string_short = "CdUdb"; + } + auth_gen_string = "CdBBUUUBdbb"; + } else { + if (chal_flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + chal_parse_string = "CdAdbddB"; + } else { + chal_parse_string = "CdAdbdd"; + chal_parse_string_short = "CdAdb"; + } + + auth_gen_string = "CdBBAAABdbb"; + } + + if (!msrpc_parse(mem_ctx, + &in, chal_parse_string, + "NTLMSSP", + &ntlmssp_command, + &server_domain, + &chal_flags, + &challenge_blob, 8, + &unkn1, &unkn2, + &target_info)) { + + bool ok = false; + + DEBUG(1, ("Failed to parse the NTLMSSP Challenge: (#2)\n")); + + if (chal_parse_string_short != NULL) { + /* + * In the case where NTLMSSP_NEGOTIATE_TARGET_INFO + * is not used, some NTLMSSP servers don't return + * the unused unkn1 and unkn2 fields. + * See bug: + * https://bugzilla.samba.org/show_bug.cgi?id=10016 + * for packet traces. + * Try and parse again without them. + */ + ok = msrpc_parse(mem_ctx, + &in, chal_parse_string_short, + "NTLMSSP", + &ntlmssp_command, + &server_domain, + &chal_flags, + &challenge_blob, 8); + if (!ok) { + DEBUG(1, ("Failed to short parse " + "the NTLMSSP Challenge: (#2)\n")); + } + } + + if (!ok) { + dump_data(2, in.data, in.length); + talloc_free(mem_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + } + + if (DEBUGLEVEL >= 10) { + struct CHALLENGE_MESSAGE *challenge = + talloc(ntlmssp_state, struct CHALLENGE_MESSAGE); + if (challenge != NULL) { + NTSTATUS status; + challenge->NegotiateFlags = chal_flags; + status = ntlmssp_pull_CHALLENGE_MESSAGE( + &in, challenge, challenge); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(CHALLENGE_MESSAGE, + challenge); + } + TALLOC_FREE(challenge); + } + } + + if (chal_flags & NTLMSSP_TARGET_TYPE_SERVER) { + ntlmssp_state->server.is_standalone = true; + } else { + ntlmssp_state->server.is_standalone = false; + } + /* TODO: parse struct_blob and fill in the rest */ + ntlmssp_state->server.netbios_name = ""; + ntlmssp_state->server.netbios_domain = talloc_move(ntlmssp_state, &server_domain); + ntlmssp_state->server.dns_name = ""; + ntlmssp_state->server.dns_domain = ""; + + if (challenge_blob.length != 8) { + talloc_free(mem_ctx); + return NT_STATUS_INVALID_PARAMETER; + } + + is_anonymous = cli_credentials_is_anonymous(gensec_security->credentials); + cli_credentials_get_ntlm_username_domain(gensec_security->credentials, mem_ctx, + &user, &domain); + + workstation = cli_credentials_get_workstation(gensec_security->credentials); + + if (user == NULL) { + DEBUG(10, ("User is NULL, returning INVALID_PARAMETER\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (domain == NULL) { + DEBUG(10, ("Domain is NULL, returning INVALID_PARAMETER\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (workstation == NULL) { + DEBUG(10, ("Workstation is NULL, returning INVALID_PARAMETER\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (is_anonymous) { + ntlmssp_state->neg_flags |= NTLMSSP_ANONYMOUS; + /* + * don't use the ccache for anonymous auth + */ + ntlmssp_state->use_ccache = false; + } + if (ntlmssp_state->use_ccache) { + struct samr_Password *nt_hash = NULL; + + /* + * If we have a password given we don't + * use the ccache + */ + nt_hash = cli_credentials_get_nt_hash(gensec_security->credentials, + mem_ctx); + if (nt_hash != NULL) { + ZERO_STRUCTP(nt_hash); + TALLOC_FREE(nt_hash); + ntlmssp_state->use_ccache = false; + } + } + + if (ntlmssp_state->use_ccache) { + struct wbcCredentialCacheParams params; + struct wbcCredentialCacheInfo *info = NULL; + struct wbcAuthErrorInfo *error = NULL; + struct wbcNamedBlob auth_blobs[2]; + const struct wbcBlob *wbc_auth_blob = NULL; + const struct wbcBlob *wbc_session_key = NULL; + wbcErr wbc_status; + size_t i; + bool new_spnego = false; + + params.account_name = user; + params.domain_name = domain; + params.level = WBC_CREDENTIAL_CACHE_LEVEL_NTLMSSP; + + auth_blobs[0].name = "challenge_blob"; + auth_blobs[0].flags = 0; + auth_blobs[0].blob.data = in.data; + auth_blobs[0].blob.length = in.length; + auth_blobs[1].name = "negotiate_blob"; + auth_blobs[1].flags = 0; + auth_blobs[1].blob.data = ntlmssp_state->negotiate_blob.data; + auth_blobs[1].blob.length = ntlmssp_state->negotiate_blob.length; + params.num_blobs = ARRAY_SIZE(auth_blobs); + params.blobs = auth_blobs; + + wbc_status = wbcCredentialCache(¶ms, &info, &error); + wbcFreeMemory(error); + if (!WBC_ERROR_IS_OK(wbc_status)) { + return NT_STATUS_WRONG_CREDENTIAL_HANDLE; + } + + for (i=0; i<info->num_blobs; i++) { + if (strequal(info->blobs[i].name, "auth_blob")) { + wbc_auth_blob = &info->blobs[i].blob; + } + if (strequal(info->blobs[i].name, "session_key")) { + wbc_session_key = &info->blobs[i].blob; + } + if (strequal(info->blobs[i].name, "new_spnego")) { + new_spnego = true; + } + } + if ((wbc_auth_blob == NULL) || (wbc_session_key == NULL)) { + wbcFreeMemory(info); + return NT_STATUS_WRONG_CREDENTIAL_HANDLE; + } + + session_key = data_blob_talloc(mem_ctx, + wbc_session_key->data, + wbc_session_key->length); + if (session_key.length != wbc_session_key->length) { + wbcFreeMemory(info); + return NT_STATUS_NO_MEMORY; + } + *out = data_blob_talloc(mem_ctx, + wbc_auth_blob->data, + wbc_auth_blob->length); + if (out->length != wbc_auth_blob->length) { + wbcFreeMemory(info); + return NT_STATUS_NO_MEMORY; + } + ntlmssp_state->new_spnego = new_spnego; + + wbcFreeMemory(info); + goto done; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + flags |= CLI_CRED_NTLM2; + } + if (ntlmssp_state->use_ntlmv2) { + flags |= CLI_CRED_NTLMv2_AUTH; + } + if (ntlmssp_state->use_nt_response) { + flags |= CLI_CRED_NTLM_AUTH; + } + if (ntlmssp_state->allow_lm_response) { + flags |= CLI_CRED_LANMAN_AUTH; + } + + if (target_info.length != 0 && !is_anonymous) { + struct AV_PAIR *pairs = NULL; + uint32_t count = 0; + enum ndr_err_code err; + struct AV_PAIR *timestamp = NULL; + struct AV_PAIR *eol = NULL; + uint32_t i = 0; + const char *service = NULL; + const char *hostname = NULL; + + err = ndr_pull_struct_blob(&target_info, + ntlmssp_state, + &ntlmssp_state->server.av_pair_list, + (ndr_pull_flags_fn_t)ndr_pull_AV_PAIR_LIST); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + return ndr_map_error2ntstatus(err); + } + + count = ntlmssp_state->server.av_pair_list.count; + /* + * We need room for Flags, SingleHost, + * ChannelBindings and Target + */ + pairs = talloc_zero_array(ntlmssp_state, struct AV_PAIR, + count + 4); + if (pairs == NULL) { + return NT_STATUS_NO_MEMORY; + } + + for (i = 0; i < count; i++) { + pairs[i] = ntlmssp_state->server.av_pair_list.pair[i]; + } + + ntlmssp_state->client.av_pair_list.count = count; + ntlmssp_state->client.av_pair_list.pair = pairs; + + eol = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list, + MsvAvEOL); + if (eol == NULL) { + return NT_STATUS_INVALID_PARAMETER; + } + + timestamp = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list, + MsvAvTimestamp); + if (timestamp != NULL) { + uint32_t sign_features = + GENSEC_FEATURE_SESSION_KEY | + GENSEC_FEATURE_SIGN | + GENSEC_FEATURE_SEAL; + + server_timestamp = ×tamp->Value.AvTimestamp; + + if (ntlmssp_state->force_old_spnego) { + sign_features = 0; + } + + if (gensec_security->want_features & sign_features) { + struct AV_PAIR *av_flags = NULL; + + av_flags = ndr_ntlmssp_find_av(&ntlmssp_state->client.av_pair_list, + MsvAvFlags); + if (av_flags == NULL) { + av_flags = eol; + eol++; + count++; + *eol = *av_flags; + av_flags->AvId = MsvAvFlags; + av_flags->Value.AvFlags = 0; + } + + av_flags->Value.AvFlags |= NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE; + ntlmssp_state->new_spnego = true; + } + } + + { + struct AV_PAIR *SingleHost = NULL; + + SingleHost = eol; + eol++; + count++; + *eol = *SingleHost; + + /* + * This is not really used, but we want to + * add some more random bytes and match + * Windows. + */ + SingleHost->AvId = MsvAvSingleHost; + SingleHost->Value.AvSingleHost.token_info.Flags = 0; + SingleHost->Value.AvSingleHost.token_info.TokenIL = 0; + generate_random_buffer(SingleHost->Value.AvSingleHost.token_info.MachineId, + sizeof(SingleHost->Value.AvSingleHost.token_info.MachineId)); + SingleHost->Value.AvSingleHost.remaining = data_blob_null; + } + + { + struct AV_PAIR *ChannelBindings = NULL; + + ChannelBindings = eol; + eol++; + count++; + *eol = *ChannelBindings; + + /* + * gensec doesn't support channel bindings yet, + * but we want to match Windows on the wire + */ + ChannelBindings->AvId = MsvChannelBindings; + memset(ChannelBindings->Value.ChannelBindings, 0, + sizeof(ChannelBindings->Value.ChannelBindings)); + } + + service = gensec_get_target_service(gensec_security); + hostname = gensec_get_target_hostname(gensec_security); + if (service != NULL && hostname != NULL) { + struct AV_PAIR *target = NULL; + + target = eol; + eol++; + count++; + *eol = *target; + + target->AvId = MsvAvTargetName; + target->Value.AvTargetName = talloc_asprintf(pairs, "%s/%s", + service, + hostname); + if (target->Value.AvTargetName == NULL) { + return NT_STATUS_NO_MEMORY; + } + } + + ntlmssp_state->client.av_pair_list.count = count; + ntlmssp_state->client.av_pair_list.pair = pairs; + + err = ndr_push_struct_blob(&target_info, + ntlmssp_state, + &ntlmssp_state->client.av_pair_list, + (ndr_push_flags_fn_t)ndr_push_AV_PAIR_LIST); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + return NT_STATUS_NO_MEMORY; + } + } + + nt_status = cli_credentials_get_ntlm_response(gensec_security->credentials, mem_ctx, + &flags, challenge_blob, + server_timestamp, target_info, + &lm_response, &nt_response, + &lm_session_key, &session_key); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + if (!(flags & CLI_CRED_LANMAN_AUTH)) { + /* LM Key is still possible, just silly, so we do not + * allow it. Fortunetly all LM crypto is off by + * default and we require command line options to end + * up here */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (!(flags & CLI_CRED_NTLM2)) { + /* NTLM2 is incompatible... */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } + + if ((ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + && ntlmssp_state->allow_lm_key && lm_session_key.length == 16) { + DATA_BLOB new_session_key = data_blob_talloc(mem_ctx, NULL, 16); + if (lm_response.length == 24) { + nt_status = SMBsesskeygen_lm_sess_key(lm_session_key.data, + lm_response.data, + new_session_key.data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } else { + static const uint8_t zeros[24]; + nt_status = SMBsesskeygen_lm_sess_key(lm_session_key.data, + zeros, + new_session_key.data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + } + session_key = new_session_key; + dump_data_pw("LM session key\n", session_key.data, session_key.length); + } + + + /* Key exchange encryptes a new client-generated session key with + the password-derived key */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + /* Make up a new session key */ + uint8_t client_session_key[16]; + gnutls_cipher_hd_t cipher_hnd; + gnutls_datum_t enc_session_key = { + .data = session_key.data, + .size = session_key.length, + }; + + generate_random_buffer(client_session_key, sizeof(client_session_key)); + + /* Encrypt the new session key with the old one */ + encrypted_session_key = data_blob_talloc(ntlmssp_state, + client_session_key, sizeof(client_session_key)); + dump_data_pw("KEY_EXCH session key:\n", encrypted_session_key.data, encrypted_session_key.length); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &enc_session_key, + NULL); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + ZERO_ARRAY(client_session_key); + goto done; + } + rc = gnutls_cipher_encrypt(cipher_hnd, + encrypted_session_key.data, + encrypted_session_key.length); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + ZERO_ARRAY(client_session_key); + goto done; + } + + dump_data_pw("KEY_EXCH session key (enc):\n", encrypted_session_key.data, encrypted_session_key.length); + + /* Mark the new session key as the 'real' session key */ + session_key = data_blob_talloc(mem_ctx, client_session_key, sizeof(client_session_key)); + ZERO_ARRAY(client_session_key); + } + + /* this generates the actual auth packet */ + nt_status = msrpc_gen(mem_ctx, + out, auth_gen_string, + "NTLMSSP", + NTLMSSP_AUTH, + lm_response.data, lm_response.length, + nt_response.data, nt_response.length, + domain, + user, + workstation, + encrypted_session_key.data, encrypted_session_key.length, + ntlmssp_state->neg_flags, + version_blob.data, version_blob.length, + mic_blob.data, mic_blob.length); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(mem_ctx); + return nt_status; + } + + if (DEBUGLEVEL >= 10) { + struct AUTHENTICATE_MESSAGE *authenticate = + talloc(ntlmssp_state, struct AUTHENTICATE_MESSAGE); + if (authenticate != NULL) { + NTSTATUS status; + authenticate->NegotiateFlags = ntlmssp_state->neg_flags; + status = ntlmssp_pull_AUTHENTICATE_MESSAGE( + out, authenticate, authenticate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(AUTHENTICATE_MESSAGE, + authenticate); + } + TALLOC_FREE(authenticate); + } + } + + /* + * We always include the MIC, even without: + * av_flags->Value.AvFlags |= NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE; + * ntlmssp_state->new_spnego = true; + * + * This matches a Windows client. + */ + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + session_key.data, + MIN(session_key.length, 64)); + if (rc < 0) { + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + goto done; + } + + rc = gnutls_hmac(hmac_hnd, + ntlmssp_state->negotiate_blob.data, + ntlmssp_state->negotiate_blob.length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + goto done; + } + rc = gnutls_hmac(hmac_hnd, in.data, in.length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + goto done; + } + rc = gnutls_hmac(hmac_hnd, out->data, out->length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + nt_status = gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + goto done; + } + + gnutls_hmac_deinit(hmac_hnd, mic_buffer); + + memcpy(out->data + NTLMSSP_MIC_OFFSET, mic_buffer, NTLMSSP_MIC_SIZE); + ZERO_ARRAY(mic_buffer); + + nt_status = NT_STATUS_OK; +done: + ZERO_ARRAY_LEN(ntlmssp_state->negotiate_blob.data, + ntlmssp_state->negotiate_blob.length); + data_blob_free(&ntlmssp_state->negotiate_blob); + + ntlmssp_state->session_key = session_key; + talloc_steal(ntlmssp_state, session_key.data); + + DEBUG(3, ("NTLMSSP: Set final flags:\n")); + debug_ntlmssp_flags(ntlmssp_state->neg_flags); + + talloc_steal(out_mem_ctx, out->data); + + ntlmssp_state->expected_state = NTLMSSP_DONE; + + if (gensec_ntlmssp_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + nt_status = ntlmssp_sign_init(ntlmssp_state); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("Could not setup NTLMSSP signing/sealing system (error was: %s)\n", + nt_errstr(nt_status))); + talloc_free(mem_ctx); + return nt_status; + } + } + + talloc_free(mem_ctx); + return nt_status; +} + +NTSTATUS gensec_ntlmssp_client_start(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp; + struct ntlmssp_state *ntlmssp_state; + NTSTATUS nt_status; + + nt_status = gensec_ntlmssp_start(gensec_security); + NT_STATUS_NOT_OK_RETURN(nt_status); + + gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + + ntlmssp_state = talloc_zero(gensec_ntlmssp, + struct ntlmssp_state); + if (!ntlmssp_state) { + return NT_STATUS_NO_MEMORY; + } + + gensec_ntlmssp->ntlmssp_state = ntlmssp_state; + + ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + + ntlmssp_state->role = NTLMSSP_CLIENT; + + ntlmssp_state->client.netbios_domain = lpcfg_workgroup(gensec_security->settings->lp_ctx); + ntlmssp_state->client.netbios_name = cli_credentials_get_workstation(gensec_security->credentials); + + ntlmssp_state->unicode = gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "unicode", true); + + ntlmssp_state->use_nt_response = \ + gensec_setting_bool(gensec_security->settings, + "ntlmssp_client", + "send_nt_response", + true); + + ntlmssp_state->allow_lm_response = lpcfg_client_lanman_auth(gensec_security->settings->lp_ctx); + + ntlmssp_state->allow_lm_key = (ntlmssp_state->allow_lm_response + && (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "allow_lm_key", false) + || gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "lm_key", false))); + + ntlmssp_state->use_ntlmv2 = lpcfg_client_ntlmv2_auth(gensec_security->settings->lp_ctx); + + ntlmssp_state->force_old_spnego = gensec_setting_bool(gensec_security->settings, + "ntlmssp_client", "force_old_spnego", false); + + ntlmssp_state->expected_state = NTLMSSP_INITIAL; + + ntlmssp_state->neg_flags = + NTLMSSP_NEGOTIATE_NTLM | + NTLMSSP_NEGOTIATE_VERSION | + NTLMSSP_REQUEST_TARGET; + + if (ntlmssp_state->unicode) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE; + } else { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "128bit", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_128; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "56bit", false)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_56; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "lm_key", false)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "keyexchange", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "alwayssign", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + } + + if (gensec_setting_bool(gensec_security->settings, "ntlmssp_client", "ntlm2", true)) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_NTLM2; + } else { + /* apparently we can't do ntlmv2 if we don't do ntlm2 */ + ntlmssp_state->use_ntlmv2 = false; + } + + if (ntlmssp_state->use_ntlmv2) { + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_NTLM2; + ntlmssp_state->allow_lm_response = false; + ntlmssp_state->allow_lm_key = false; + } + + if (ntlmssp_state->allow_lm_key) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (gensec_security->want_features & GENSEC_FEATURE_SESSION_KEY) { + /* + * We need to set this to allow a later SetPassword + * via the SAMR pipe to succeed. Strange.... We could + * also add NTLMSSP_NEGOTIATE_SEAL here. JRA. + * + * Without this, Windows will not create the master key + * that it thinks is only used for NTLMSSP signing and + * sealing. (It is actually pulled out and used directly) + * + * We don't require this here as some servers (e.g. NetAPP) + * doesn't support this. + */ + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SIGN; + } + if (gensec_security->want_features & GENSEC_FEATURE_SIGN) { + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_SIGN; + + if (gensec_security->want_features & GENSEC_FEATURE_LDAP_STYLE) { + /* + * We need to handle NTLMSSP_NEGOTIATE_SIGN as + * NTLMSSP_NEGOTIATE_SEAL if GENSEC_FEATURE_LDAP_STYLE + * is requested. + */ + ntlmssp_state->force_wrap_seal = true; + } + } + if (ntlmssp_state->force_wrap_seal) { + bool ret; + + /* + * We want also work against old Samba servers + * which didn't had GENSEC_FEATURE_LDAP_STYLE + * we negotiate SEAL too. We may remove this + * in a few years. As all servers should have + * GENSEC_FEATURE_LDAP_STYLE by then. + */ + ret = gensec_setting_bool(gensec_security->settings, + "ntlmssp_client", + "ldap_style_send_seal", + true); + if (ret) { + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_SEAL; + } + } + if (gensec_security->want_features & GENSEC_FEATURE_SEAL) { + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_SIGN; + ntlmssp_state->required_flags |= NTLMSSP_NEGOTIATE_SEAL; + } + if (gensec_security->want_features & GENSEC_FEATURE_NTLM_CCACHE) { + ntlmssp_state->use_ccache = true; + } + + ntlmssp_state->neg_flags |= ntlmssp_state->required_flags; + ntlmssp_state->conf_flags = ntlmssp_state->neg_flags; + + return NT_STATUS_OK; +} + +NTSTATUS gensec_ntlmssp_resume_ccache_start(struct gensec_security *gensec_security) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = NULL; + NTSTATUS status; + + status = gensec_ntlmssp_client_start(gensec_security); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + gensec_ntlmssp = talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + gensec_ntlmssp->ntlmssp_state->use_ccache = false; + gensec_ntlmssp->ntlmssp_state->resume_ccache = true; + gensec_ntlmssp->ntlmssp_state->expected_state = NTLMSSP_NEGOTIATE; + + return NT_STATUS_OK; +} diff --git a/auth/ntlmssp/ntlmssp_ndr.c b/auth/ntlmssp/ntlmssp_ndr.c new file mode 100644 index 0000000..ea5d6f0 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_ndr.c @@ -0,0 +1,134 @@ +/* + Unix SMB/Netbios implementation. + NTLMSSP ndr functions + + Copyright (C) Guenther Deschner 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "ntlmssp_ndr.h" + +#define NTLMSSP_PULL_MESSAGE(type, blob, mem_ctx, r) \ +do { \ + enum ndr_err_code __ndr_err; \ + ZERO_STRUCTP(r); /* in order to deal with unset neg flags */\ + __ndr_err = ndr_pull_struct_blob(blob, mem_ctx, r, \ + (ndr_pull_flags_fn_t)ndr_pull_ ##type); \ + if (!NDR_ERR_CODE_IS_SUCCESS(__ndr_err)) { \ + return ndr_map_error2ntstatus(__ndr_err); \ + } \ + if (!mem_equal_const_time(r->Signature, "NTLMSSP\0", 8)) { \ + return NT_STATUS_INVALID_PARAMETER; \ + } \ + return NT_STATUS_OK; \ +} while(0); + +#define NTLMSSP_PUSH_MESSAGE(type, blob, mem_ctx, r) \ +do { \ + enum ndr_err_code __ndr_err; \ + __ndr_err = ndr_push_struct_blob(blob, mem_ctx, r, \ + (ndr_push_flags_fn_t)ndr_push_ ##type); \ + if (!NDR_ERR_CODE_IS_SUCCESS(__ndr_err)) { \ + return ndr_map_error2ntstatus(__ndr_err); \ + } \ + return NT_STATUS_OK; \ +} while(0); + + +/** + * Pull NTLMSSP NEGOTIATE_MESSAGE struct from a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP NEGOTIATE_MESSAGE structure + */ + +NTSTATUS ntlmssp_pull_NEGOTIATE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct NEGOTIATE_MESSAGE *r) +{ + NTLMSSP_PULL_MESSAGE(NEGOTIATE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Pull NTLMSSP CHALLENGE_MESSAGE struct from a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP CHALLENGE_MESSAGE structure + */ + +NTSTATUS ntlmssp_pull_CHALLENGE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct CHALLENGE_MESSAGE *r) +{ + NTLMSSP_PULL_MESSAGE(CHALLENGE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Pull NTLMSSP AUTHENTICATE_MESSAGE struct from a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP AUTHENTICATE_MESSAGE structure + */ + +NTSTATUS ntlmssp_pull_AUTHENTICATE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct AUTHENTICATE_MESSAGE *r) +{ + NTLMSSP_PULL_MESSAGE(AUTHENTICATE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Push NTLMSSP NEGOTIATE_MESSAGE struct into a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP NEGOTIATE_MESSAGE structure + */ + +NTSTATUS ntlmssp_push_NEGOTIATE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct NEGOTIATE_MESSAGE *r) +{ + NTLMSSP_PUSH_MESSAGE(NEGOTIATE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Push NTLMSSP CHALLENGE_MESSAGE struct into a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP CHALLENGE_MESSAGE structure + */ + +NTSTATUS ntlmssp_push_CHALLENGE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct CHALLENGE_MESSAGE *r) +{ + NTLMSSP_PUSH_MESSAGE(CHALLENGE_MESSAGE, blob, mem_ctx, r); +} + +/** + * Push NTLMSSP AUTHENTICATE_MESSAGE struct into a blob + * @param blob The plain packet blob + * @param mem_ctx A talloc context + * @param r Pointer to a NTLMSSP AUTHENTICATE_MESSAGE structure + */ + +NTSTATUS ntlmssp_push_AUTHENTICATE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct AUTHENTICATE_MESSAGE *r) +{ + NTLMSSP_PUSH_MESSAGE(AUTHENTICATE_MESSAGE, blob, mem_ctx, r); +} diff --git a/auth/ntlmssp/ntlmssp_ndr.h b/auth/ntlmssp/ntlmssp_ndr.h new file mode 100644 index 0000000..e619231 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_ndr.h @@ -0,0 +1,38 @@ +/* + Unix SMB/Netbios implementation. + NTLMSSP ndr functions + + Copyright (C) Guenther Deschner 2009 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +NTSTATUS ntlmssp_pull_NEGOTIATE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct NEGOTIATE_MESSAGE *r); +NTSTATUS ntlmssp_pull_CHALLENGE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct CHALLENGE_MESSAGE *r); +NTSTATUS ntlmssp_pull_AUTHENTICATE_MESSAGE(const DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + struct AUTHENTICATE_MESSAGE *r); +NTSTATUS ntlmssp_push_NEGOTIATE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct NEGOTIATE_MESSAGE *r); +NTSTATUS ntlmssp_push_CHALLENGE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct CHALLENGE_MESSAGE *r); +NTSTATUS ntlmssp_push_AUTHENTICATE_MESSAGE(DATA_BLOB *blob, + TALLOC_CTX *mem_ctx, + const struct AUTHENTICATE_MESSAGE *r); diff --git a/auth/ntlmssp/ntlmssp_private.h b/auth/ntlmssp/ntlmssp_private.h new file mode 100644 index 0000000..4d84e33 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_private.h @@ -0,0 +1,192 @@ +/* + * Unix SMB/CIFS implementation. + * Version 3.0 + * NTLMSSP Signing routines + * Copyright (C) Andrew Bartlett 2003-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/>. + */ + +/* For structures internal to the NTLMSSP implementation that should not be exposed */ + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +struct auth_session_info; + +struct ntlmssp_crypt_direction { + uint32_t seq_num; + uint8_t sign_key[16]; + gnutls_cipher_hd_t seal_state; +}; + +union ntlmssp_crypt_state { + /* NTLM */ + struct ntlmssp_crypt_direction ntlm; + + /* NTLM2 */ + struct { + struct ntlmssp_crypt_direction sending; + struct ntlmssp_crypt_direction receiving; + } ntlm2; +}; + +struct gensec_ntlmssp_context { + /* For GENSEC users */ + void *server_returned_info; + + /* used by both client and server implementation */ + struct ntlmssp_state *ntlmssp_state; +}; + +/* The following definitions come from auth/ntlmssp_util.c */ + +void debug_ntlmssp_flags(uint32_t neg_flags); +NTSTATUS ntlmssp_handle_neg_flags(struct ntlmssp_state *ntlmssp_state, + uint32_t neg_flags, const char *name); +const DATA_BLOB ntlmssp_version_blob(void); + +/* The following definitions come from auth/ntlmssp_server.c */ + +const char *ntlmssp_target_name(struct ntlmssp_state *ntlmssp_state, + uint32_t neg_flags, uint32_t *chal_flags); +NTSTATUS ntlmssp_server_negotiate(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out); +NTSTATUS ntlmssp_server_auth(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB request, DATA_BLOB *reply); +/* The following definitions come from auth/ntlmssp/ntlmssp_client.c */ + + +/** + * Next state function for the Initial packet + * + * @param ntlmssp_state NTLMSSP State + * @param out_mem_ctx The DATA_BLOB *out will be allocated on this context + * @param in A NULL data blob (input ignored) + * @param out The initial negotiate request to the server, as an talloc()ed DATA_BLOB, on out_mem_ctx + * @return Errors or NT_STATUS_OK. + */ +NTSTATUS ntlmssp_client_initial(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out) ; + +NTSTATUS gensec_ntlmssp_resume_ccache(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB in, DATA_BLOB *out); + +/** + * Next state function for the Challenge Packet. Generate an auth packet. + * + * @param gensec_security GENSEC state + * @param out_mem_ctx Memory context for *out + * @param in The server challnege, as a DATA_BLOB. reply.data must be NULL + * @param out The next request (auth packet) to the server, as an allocated DATA_BLOB, on the out_mem_ctx context + * @return Errors or NT_STATUS_OK. + */ +NTSTATUS ntlmssp_client_challenge(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB in, DATA_BLOB *out) ; +NTSTATUS gensec_ntlmssp_client_start(struct gensec_security *gensec_security); +NTSTATUS gensec_ntlmssp_resume_ccache_start(struct gensec_security *gensec_security); + +/* The following definitions come from auth/ntlmssp/gensec_ntlmssp_server.c */ + + +/** + * Next state function for the Negotiate packet (GENSEC wrapper) + * + * @param gensec_security GENSEC state + * @param out_mem_ctx Memory context for *out + * @param in The request, as a DATA_BLOB. reply.data must be NULL + * @param out The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or MORE_PROCESSING_REQUIRED if (normal) a reply is required. + */ +NTSTATUS gensec_ntlmssp_server_negotiate(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB request, DATA_BLOB *reply); + +struct tevent_req *ntlmssp_server_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in); +NTSTATUS ntlmssp_server_auth_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out); + + +/** + * Start NTLMSSP on the server side + * + */ +NTSTATUS gensec_ntlmssp_server_start(struct gensec_security *gensec_security); + +/** + * Return the credentials of a logged on user, including session keys + * etc. + * + * Only valid after a successful authentication + * + * May only be called once per authentication. + * + */ +NTSTATUS gensec_ntlmssp_session_info(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + struct auth_session_info **session_info) ; + +/* The following definitions come from auth/ntlmssp/gensec_ntlmssp.c */ + +NTSTATUS gensec_ntlmssp_sign_packet(struct gensec_security *gensec_security, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS gensec_ntlmssp_check_packet(struct gensec_security *gensec_security, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +NTSTATUS gensec_ntlmssp_seal_packet(struct gensec_security *gensec_security, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig); +NTSTATUS gensec_ntlmssp_unseal_packet(struct gensec_security *gensec_security, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig); +size_t gensec_ntlmssp_sig_size(struct gensec_security *gensec_security, size_t data_size) ; +NTSTATUS gensec_ntlmssp_wrap(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); +NTSTATUS gensec_ntlmssp_unwrap(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out); + +/** + * Return the NTLMSSP master session key + * + * @param ntlmssp_state NTLMSSP State + */ +NTSTATUS gensec_ntlmssp_magic(struct gensec_security *gensec_security, + const DATA_BLOB *first_packet); +bool gensec_ntlmssp_have_feature(struct gensec_security *gensec_security, + uint32_t feature); +NTSTATUS gensec_ntlmssp_session_key(struct gensec_security *gensec_security, + TALLOC_CTX *mem_ctx, + DATA_BLOB *session_key); +NTSTATUS gensec_ntlmssp_start(struct gensec_security *gensec_security); + diff --git a/auth/ntlmssp/ntlmssp_server.c b/auth/ntlmssp/ntlmssp_server.c new file mode 100644 index 0000000..6a27db1 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_server.c @@ -0,0 +1,1160 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NTLMSSP, server side + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett 2001-2010 + + 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 <tevent.h> +#include "lib/util/tevent_ntstatus.h" +#include "lib/util/time_basic.h" +#include "auth/ntlmssp/ntlmssp.h" +#include "auth/ntlmssp/ntlmssp_private.h" +#include "../librpc/gen_ndr/ndr_ntlmssp.h" +#include "auth/ntlmssp/ntlmssp_ndr.h" +#include "../libcli/auth/libcli_auth.h" +#include "auth/gensec/gensec.h" +#include "auth/gensec/gensec_internal.h" +#include "auth/common_auth.h" +#include "param/param.h" +#include "param/loadparm.h" +#include "libcli/security/session.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +/** + * Determine correct target name flags for reply, given server role + * and negotiated flags + * + * @param ntlmssp_state NTLMSSP State + * @param neg_flags The flags from the packet + * @param chal_flags The flags to be set in the reply packet + * @return The 'target name' string. + */ + +const char *ntlmssp_target_name(struct ntlmssp_state *ntlmssp_state, + uint32_t neg_flags, uint32_t *chal_flags) +{ + if (neg_flags & NTLMSSP_REQUEST_TARGET) { + *chal_flags |= NTLMSSP_NEGOTIATE_TARGET_INFO; + *chal_flags |= NTLMSSP_REQUEST_TARGET; + if (ntlmssp_state->server.is_standalone) { + *chal_flags |= NTLMSSP_TARGET_TYPE_SERVER; + return ntlmssp_state->server.netbios_name; + } else { + *chal_flags |= NTLMSSP_TARGET_TYPE_DOMAIN; + return ntlmssp_state->server.netbios_domain; + }; + } else { + return ""; + } +} + +/** + * Next state function for the NTLMSSP Negotiate packet + * + * @param gensec_security GENSEC state + * @param out_mem_ctx Memory context for *out + * @param in The request, as a DATA_BLOB. reply.data must be NULL + * @param out The reply, as an allocated DATA_BLOB, caller to free. + * @return Errors or MORE_PROCESSING_REQUIRED if (normal) a reply is required. + */ + +NTSTATUS gensec_ntlmssp_server_negotiate(struct gensec_security *gensec_security, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB request, DATA_BLOB *reply) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + struct auth4_context *auth_context = gensec_security->auth_context; + DATA_BLOB struct_blob; + uint32_t neg_flags = 0; + uint32_t ntlmssp_command, chal_flags; + uint8_t cryptkey[8]; + const char *target_name; + NTSTATUS status; + struct timeval tv_now = timeval_current(); + /* + * See [MS-NLMP] + * + * Windows NT 4.0, windows_2000: use 30 minutes, + * Windows XP, Windows Server 2003, Windows Vista, + * Windows Server 2008, Windows 7, and Windows Server 2008 R2 + * use 36 hours. + * + * Newer systems doesn't check this, likely because the + * connectionless NTLMSSP is no longer supported. + * + * As we expect the AUTHENTICATION_MESSAGE to arrive + * directly after the NEGOTIATE_MESSAGE (typically less than + * as 1 second later). We use a hard timeout of 30 Minutes. + * + * We don't look at AUTHENTICATE_MESSAGE.NtChallengeResponse.TimeStamp + * instead we just remember our own time. + */ + uint32_t max_lifetime = 30 * 60; + struct timeval tv_end = timeval_add(&tv_now, max_lifetime, 0); + + /* parse the NTLMSSP packet */ +#if 0 + file_save("ntlmssp_negotiate.dat", request.data, request.length); +#endif + + if (request.length) { + if (request.length > UINT16_MAX) { + DEBUG(1, ("ntlmssp_server_negotiate: reject large request of length %u\n", + (unsigned int)request.length)); + return NT_STATUS_INVALID_PARAMETER; + } + + if ((request.length < 16) || !msrpc_parse(ntlmssp_state, &request, "Cdd", + "NTLMSSP", + &ntlmssp_command, + &neg_flags)) { + DEBUG(1, ("ntlmssp_server_negotiate: failed to parse NTLMSSP Negotiate of length %u\n", + (unsigned int)request.length)); + dump_data(2, request.data, request.length); + return NT_STATUS_INVALID_PARAMETER; + } + debug_ntlmssp_flags(neg_flags); + + if (DEBUGLEVEL >= 10) { + struct NEGOTIATE_MESSAGE *negotiate = talloc( + ntlmssp_state, struct NEGOTIATE_MESSAGE); + if (negotiate != NULL) { + status = ntlmssp_pull_NEGOTIATE_MESSAGE( + &request, negotiate, negotiate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(NEGOTIATE_MESSAGE, + negotiate); + } + TALLOC_FREE(negotiate); + } + } + } + + status = ntlmssp_handle_neg_flags(ntlmssp_state, neg_flags, "negotiate"); + if (!NT_STATUS_IS_OK(status)){ + return status; + } + + /* Ask our caller what challenge they would like in the packet */ + if (auth_context->get_ntlm_challenge) { + status = auth_context->get_ntlm_challenge(auth_context, cryptkey); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1, ("gensec_ntlmssp_server_negotiate: failed to get challenge: %s\n", + nt_errstr(status))); + return status; + } + } else { + DEBUG(1, ("gensec_ntlmssp_server_negotiate: backend doesn't give a challenge\n")); + return NT_STATUS_NOT_IMPLEMENTED; + } + + /* The flags we send back are not just the negotiated flags, + * they are also 'what is in this packet'. Therfore, we + * operate on 'chal_flags' from here on + */ + + chal_flags = ntlmssp_state->neg_flags; + ntlmssp_state->server.challenge_endtime = timeval_to_nttime(&tv_end); + + /* get the right name to fill in as 'target' */ + target_name = ntlmssp_target_name(ntlmssp_state, + neg_flags, &chal_flags); + if (target_name == NULL) + return NT_STATUS_INVALID_PARAMETER; + + ntlmssp_state->chal = data_blob_talloc(ntlmssp_state, cryptkey, 8); + ntlmssp_state->internal_chal = data_blob_talloc(ntlmssp_state, + cryptkey, 8); + + /* This creates the 'blob' of names that appears at the end of the packet */ + if (chal_flags & NTLMSSP_NEGOTIATE_TARGET_INFO) { + enum ndr_err_code err; + struct AV_PAIR *pairs = NULL; + uint32_t count = 5; + + pairs = talloc_zero_array(ntlmssp_state, struct AV_PAIR, count + 1); + if (pairs == NULL) { + return NT_STATUS_NO_MEMORY; + } + + pairs[0].AvId = MsvAvNbDomainName; + pairs[0].Value.AvNbDomainName = target_name; + + pairs[1].AvId = MsvAvNbComputerName; + pairs[1].Value.AvNbComputerName = ntlmssp_state->server.netbios_name; + + pairs[2].AvId = MsvAvDnsDomainName; + pairs[2].Value.AvDnsDomainName = ntlmssp_state->server.dns_domain; + + pairs[3].AvId = MsvAvDnsComputerName; + pairs[3].Value.AvDnsComputerName= ntlmssp_state->server.dns_name; + + if (!ntlmssp_state->force_old_spnego) { + pairs[4].AvId = MsvAvTimestamp; + pairs[4].Value.AvTimestamp = + timeval_to_nttime(&tv_now); + count += 1; + + pairs[5].AvId = MsvAvEOL; + } else { + pairs[4].AvId = MsvAvEOL; + } + + ntlmssp_state->server.av_pair_list.count = count; + ntlmssp_state->server.av_pair_list.pair = pairs; + + err = ndr_push_struct_blob(&struct_blob, + ntlmssp_state, + &ntlmssp_state->server.av_pair_list, + (ndr_push_flags_fn_t)ndr_push_AV_PAIR_LIST); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + return NT_STATUS_NO_MEMORY; + } + } else { + struct_blob = data_blob_null; + } + + { + /* Marshal the packet in the right format, be it unicode or ASCII */ + const char *gen_string; + const DATA_BLOB version_blob = ntlmssp_version_blob(); + + if (ntlmssp_state->unicode) { + gen_string = "CdUdbddBb"; + } else { + gen_string = "CdAdbddBb"; + } + + status = msrpc_gen(out_mem_ctx, reply, gen_string, + "NTLMSSP", + NTLMSSP_CHALLENGE, + target_name, + chal_flags, + cryptkey, 8, + 0, 0, + struct_blob.data, struct_blob.length, + version_blob.data, version_blob.length); + + if (!NT_STATUS_IS_OK(status)) { + data_blob_free(&struct_blob); + return status; + } + + if (DEBUGLEVEL >= 10) { + struct CHALLENGE_MESSAGE *challenge = talloc( + ntlmssp_state, struct CHALLENGE_MESSAGE); + if (challenge != NULL) { + challenge->NegotiateFlags = chal_flags; + status = ntlmssp_pull_CHALLENGE_MESSAGE( + reply, challenge, challenge); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(CHALLENGE_MESSAGE, + challenge); + } + TALLOC_FREE(challenge); + } + } + } + + data_blob_free(&struct_blob); + + ntlmssp_state->negotiate_blob = data_blob_dup_talloc(ntlmssp_state, + request); + if (ntlmssp_state->negotiate_blob.length != request.length) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->challenge_blob = data_blob_dup_talloc(ntlmssp_state, + *reply); + if (ntlmssp_state->challenge_blob.length != reply->length) { + return NT_STATUS_NO_MEMORY; + } + + ntlmssp_state->expected_state = NTLMSSP_AUTH; + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + +struct ntlmssp_server_auth_state { + struct gensec_security *gensec_security; + struct gensec_ntlmssp_context *gensec_ntlmssp; + DATA_BLOB in; + struct auth_usersupplied_info *user_info; + DATA_BLOB user_session_key; + DATA_BLOB lm_session_key; + /* internal variables used by KEY_EXCH (client-supplied user session key */ + DATA_BLOB encrypted_session_key; + bool doing_ntlm2; + /* internal variables used by NTLM2 */ + uint8_t session_nonce[16]; +}; + +static NTSTATUS ntlmssp_server_preauth(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + struct ntlmssp_server_auth_state *state, + const DATA_BLOB request); +static void ntlmssp_server_auth_done(struct tevent_req *subreq); +static NTSTATUS ntlmssp_server_postauth(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + struct ntlmssp_server_auth_state *state, + DATA_BLOB request); + +struct tevent_req *ntlmssp_server_auth_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct gensec_security *gensec_security, + const DATA_BLOB in) +{ + struct gensec_ntlmssp_context *gensec_ntlmssp = + talloc_get_type_abort(gensec_security->private_data, + struct gensec_ntlmssp_context); + struct auth4_context *auth_context = gensec_security->auth_context; + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct ntlmssp_server_auth_state *state = NULL; + NTSTATUS status; + + req = tevent_req_create(mem_ctx, &state, + struct ntlmssp_server_auth_state); + if (req == NULL) { + return NULL; + } + state->gensec_security = gensec_security; + state->gensec_ntlmssp = gensec_ntlmssp; + state->in = in; + + status = ntlmssp_server_preauth(gensec_security, + gensec_ntlmssp, + state, in); + if (tevent_req_nterror(req, status)) { + return tevent_req_post(req, ev); + } + + subreq = auth_context->check_ntlm_password_send( + state, ev, auth_context, state->user_info); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, ntlmssp_server_auth_done, req); + return req; +} + +/** + * Next state function for the Authenticate packet + * + * @param ntlmssp_state NTLMSSP State + * @param request The request, as a DATA_BLOB + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_server_preauth(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + struct ntlmssp_server_auth_state *state, + const DATA_BLOB request) +{ + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + struct auth4_context *auth_context = gensec_security->auth_context; + struct auth_usersupplied_info *user_info = NULL; + uint32_t ntlmssp_command, auth_flags; + NTSTATUS nt_status; + const unsigned int version_len = 8; + DATA_BLOB version_blob = data_blob_null; + const unsigned int mic_len = NTLMSSP_MIC_SIZE; + DATA_BLOB mic_blob = data_blob_null; + const char *parse_string; + bool ok; + struct timeval endtime; + bool expired = false; + +#if 0 + file_save("ntlmssp_auth.dat", request.data, request.length); +#endif + + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUUBdbb"; + } else { + parse_string = "CdBBAAABdbb"; + } + + /* zero these out */ + data_blob_free(&ntlmssp_state->session_key); + data_blob_free(&ntlmssp_state->lm_resp); + data_blob_free(&ntlmssp_state->nt_resp); + + ntlmssp_state->user = NULL; + ntlmssp_state->domain = NULL; + ntlmssp_state->client.netbios_name = NULL; + + /* now the NTLMSSP encoded auth hashes */ + ok = msrpc_parse(ntlmssp_state, &request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &ntlmssp_state->domain, + &ntlmssp_state->user, + &ntlmssp_state->client.netbios_name, + &state->encrypted_session_key, + &auth_flags, + &version_blob, version_len, + &mic_blob, mic_len); + if (!ok) { + DEBUG(10, ("ntlmssp_server_auth: failed to parse NTLMSSP (nonfatal):\n")); + dump_data(10, request.data, request.length); + + data_blob_free(&version_blob); + data_blob_free(&mic_blob); + + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUUBd"; + } else { + parse_string = "CdBBAAABd"; + } + + ok = msrpc_parse(ntlmssp_state, &request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &ntlmssp_state->domain, + &ntlmssp_state->user, + &ntlmssp_state->client.netbios_name, + &state->encrypted_session_key, + &auth_flags); + } + + if (!ok) { + DEBUG(10, ("ntlmssp_server_auth: failed to parse NTLMSSP (nonfatal):\n")); + dump_data(10, request.data, request.length); + + /* zero this out */ + data_blob_free(&state->encrypted_session_key); + auth_flags = 0; + + /* Try again with a shorter string (Win9X truncates this packet) */ + if (ntlmssp_state->unicode) { + parse_string = "CdBBUUU"; + } else { + parse_string = "CdBBAAA"; + } + + /* now the NTLMSSP encoded auth hashes */ + if (!msrpc_parse(ntlmssp_state, &request, parse_string, + "NTLMSSP", + &ntlmssp_command, + &ntlmssp_state->lm_resp, + &ntlmssp_state->nt_resp, + &ntlmssp_state->domain, + &ntlmssp_state->user, + &ntlmssp_state->client.netbios_name)) { + DEBUG(1, ("ntlmssp_server_auth: failed to parse NTLMSSP (tried both formats):\n")); + dump_data(2, request.data, request.length); + + return NT_STATUS_INVALID_PARAMETER; + } + } + + talloc_steal(state, state->encrypted_session_key.data); + + if (auth_flags != 0) { + nt_status = ntlmssp_handle_neg_flags(ntlmssp_state, + auth_flags, + "authenticate"); + if (!NT_STATUS_IS_OK(nt_status)){ + return nt_status; + } + } + + if (DEBUGLEVEL >= 10) { + struct AUTHENTICATE_MESSAGE *authenticate = talloc( + ntlmssp_state, struct AUTHENTICATE_MESSAGE); + if (authenticate != NULL) { + NTSTATUS status; + authenticate->NegotiateFlags = auth_flags; + status = ntlmssp_pull_AUTHENTICATE_MESSAGE( + &request, authenticate, authenticate); + if (NT_STATUS_IS_OK(status)) { + NDR_PRINT_DEBUG(AUTHENTICATE_MESSAGE, + authenticate); + } + TALLOC_FREE(authenticate); + } + } + + DEBUG(3,("Got user=[%s] domain=[%s] workstation=[%s] len1=%lu len2=%lu\n", + ntlmssp_state->user, ntlmssp_state->domain, + ntlmssp_state->client.netbios_name, + (unsigned long)ntlmssp_state->lm_resp.length, + (unsigned long)ntlmssp_state->nt_resp.length)); + +#if 0 + file_save("nthash1.dat", &ntlmssp_state->nt_resp.data, &ntlmssp_state->nt_resp.length); + file_save("lmhash1.dat", &ntlmssp_state->lm_resp.data, &ntlmssp_state->lm_resp.length); +#endif + + if (ntlmssp_state->nt_resp.length > 24) { + struct NTLMv2_RESPONSE v2_resp; + enum ndr_err_code err; + uint32_t i = 0; + uint32_t count = 0; + const struct AV_PAIR *flags = NULL; + const struct AV_PAIR *eol = NULL; + uint32_t av_flags = 0; + + err = ndr_pull_struct_blob(&ntlmssp_state->nt_resp, + ntlmssp_state, + &v2_resp, + (ndr_pull_flags_fn_t)ndr_pull_NTLMv2_RESPONSE); + if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + nt_status = ndr_map_error2ntstatus(err); + if (NT_STATUS_EQUAL(nt_status, NT_STATUS_BUFFER_TOO_SMALL)) { + /* + * Note that invalid blobs should result in + * INVALID_PARAMETER, as demonstrated by + * smb2.session.ntlmssp_bug14932 + */ + nt_status = NT_STATUS_INVALID_PARAMETER; + } + DEBUG(1,("%s: failed to parse NTLMv2_RESPONSE of length %zu for " + "user=[%s] domain=[%s] workstation=[%s] - %s %s\n", + __func__, ntlmssp_state->nt_resp.length, + ntlmssp_state->user, ntlmssp_state->domain, + ntlmssp_state->client.netbios_name, + ndr_errstr(err), nt_errstr(nt_status))); + return nt_status; + } + + if (DEBUGLVL(10)) { + NDR_PRINT_DEBUG(NTLMv2_RESPONSE, &v2_resp); + } + + eol = ndr_ntlmssp_find_av(&v2_resp.Challenge.AvPairs, + MsvAvEOL); + if (eol == NULL) { + DEBUG(1,("%s: missing MsvAvEOL for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, ntlmssp_state->user, ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + flags = ndr_ntlmssp_find_av(&v2_resp.Challenge.AvPairs, + MsvAvFlags); + if (flags != NULL) { + av_flags = flags->Value.AvFlags; + } + + if (av_flags & NTLMSSP_AVFLAG_MIC_IN_AUTHENTICATE_MESSAGE) { + if (mic_blob.length != NTLMSSP_MIC_SIZE) { + DEBUG(1,("%s: mic_blob.length[%u] for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, + (unsigned)mic_blob.length, + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + if (request.length < + (NTLMSSP_MIC_OFFSET + NTLMSSP_MIC_SIZE)) + { + DEBUG(1,("%s: missing MIC " + "request.length[%u] for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, + (unsigned)request.length, + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + ntlmssp_state->new_spnego = true; + } + + count = ntlmssp_state->server.av_pair_list.count; + if (v2_resp.Challenge.AvPairs.count < count) { + return NT_STATUS_INVALID_PARAMETER; + } + + for (i = 0; i < count; i++) { + const struct AV_PAIR *sp = + &ntlmssp_state->server.av_pair_list.pair[i]; + const struct AV_PAIR *cp = NULL; + + if (sp->AvId == MsvAvEOL) { + continue; + } + + cp = ndr_ntlmssp_find_av(&v2_resp.Challenge.AvPairs, + sp->AvId); + if (cp == NULL) { + DEBUG(1,("%s: AvId 0x%x missing for" + "user=[%s] domain=[%s] " + "workstation=[%s]\n", + __func__, + (unsigned)sp->AvId, + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + switch (cp->AvId) { +#define CASE_STRING(v) case Msv ## v: do { \ + int cmp; \ + if (sp->Value.v == NULL) { \ + return NT_STATUS_INTERNAL_ERROR; \ + } \ + if (cp->Value.v == NULL) { \ + DEBUG(1,("%s: invalid %s " \ + "got[%s] expect[%s] for " \ + "user=[%s] domain=[%s] workstation=[%s]\n", \ + __func__, #v, \ + cp->Value.v, \ + sp->Value.v, \ + ntlmssp_state->user, \ + ntlmssp_state->domain, \ + ntlmssp_state->client.netbios_name)); \ + return NT_STATUS_INVALID_PARAMETER; \ + } \ + cmp = strcmp(cp->Value.v, sp->Value.v); \ + if (cmp != 0) { \ + DEBUG(1,("%s: invalid %s " \ + "got[%s] expect[%s] for " \ + "user=[%s] domain=[%s] workstation=[%s]\n", \ + __func__, #v, \ + cp->Value.v, \ + sp->Value.v, \ + ntlmssp_state->user, \ + ntlmssp_state->domain, \ + ntlmssp_state->client.netbios_name)); \ + return NT_STATUS_INVALID_PARAMETER; \ + } \ +} while(0); break + CASE_STRING(AvNbComputerName); + CASE_STRING(AvNbDomainName); + CASE_STRING(AvDnsComputerName); + CASE_STRING(AvDnsDomainName); + CASE_STRING(AvDnsTreeName); + case MsvAvTimestamp: + if (cp->Value.AvTimestamp != sp->Value.AvTimestamp) { + struct timeval ct; + struct timeval st; + struct timeval_buf tmp1; + struct timeval_buf tmp2; + + nttime_to_timeval(&ct, + cp->Value.AvTimestamp); + nttime_to_timeval(&st, + sp->Value.AvTimestamp); + + DEBUG(1,("%s: invalid AvTimestamp " + "got[%s] expect[%s] for " + "user=[%s] domain=[%s] " + "workstation=[%s]\n", + __func__, + timeval_str_buf(&ct, false, + true, &tmp1), + timeval_str_buf(&st, false, + true, &tmp2), + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + break; + default: + /* + * This can't happen as we control + * ntlmssp_state->server.av_pair_list + */ + return NT_STATUS_INTERNAL_ERROR; + } + } + } + + nttime_to_timeval(&endtime, ntlmssp_state->server.challenge_endtime); + expired = timeval_expired(&endtime); + if (expired) { + struct timeval_buf tmp; + DEBUG(1,("%s: challenge invalid (expired %s) for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, + timeval_str_buf(&endtime, false, true, &tmp), + ntlmssp_state->user, ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + return NT_STATUS_INVALID_PARAMETER; + } + + /* NTLM2 uses a 'challenge' that is made of up both the server challenge, and a + client challenge + + However, the NTLM2 flag may still be set for the real NTLMv2 logins, be careful. + */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + if (ntlmssp_state->nt_resp.length == 24 && ntlmssp_state->lm_resp.length == 24) { + state->doing_ntlm2 = true; + + memcpy(state->session_nonce, ntlmssp_state->internal_chal.data, 8); + memcpy(&state->session_nonce[8], ntlmssp_state->lm_resp.data, 8); + + SMB_ASSERT(ntlmssp_state->internal_chal.data && ntlmssp_state->internal_chal.length == 8); + + /* LM response is no longer useful */ + data_blob_free(&ntlmssp_state->lm_resp); + + /* We changed the effective challenge - set it */ + if (auth_context->set_ntlm_challenge) { + uint8_t session_nonce_hash[16]; + int rc; + + rc = gnutls_hash_fast(GNUTLS_DIG_MD5, + state->session_nonce, + 16, + session_nonce_hash); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + + nt_status = auth_context->set_ntlm_challenge(auth_context, + session_nonce_hash, + "NTLMSSP callback (NTLM2)"); + ZERO_ARRAY(session_nonce_hash); + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(1, ("gensec_ntlmssp_server_negotiate: failed to get challenge: %s\n", + nt_errstr(nt_status))); + return nt_status; + } + } else { + DEBUG(1, ("gensec_ntlmssp_server_negotiate: backend doesn't have facility for challenge to be set\n")); + + return NT_STATUS_NOT_IMPLEMENTED; + } + + /* LM Key is incompatible. */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + } + + user_info = talloc_zero(state, struct auth_usersupplied_info); + if (!user_info) { + return NT_STATUS_NO_MEMORY; + } + + user_info->logon_parameters = MSV1_0_ALLOW_SERVER_TRUST_ACCOUNT | MSV1_0_ALLOW_WORKSTATION_TRUST_ACCOUNT; + user_info->flags = 0; + user_info->client.account_name = ntlmssp_state->user; + user_info->client.domain_name = ntlmssp_state->domain; + user_info->workstation_name = ntlmssp_state->client.netbios_name; + user_info->remote_host = gensec_get_remote_address(gensec_security); + user_info->local_host = gensec_get_local_address(gensec_security); + user_info->service_description + = gensec_get_target_service_description(gensec_security); + + /* + * This will just be the string "NTLMSSP" from + * gensec_ntlmssp_final_auth_type, but ensures it stays in sync + * with the same use in the authorization logging triggered by + * gensec_session_info() later + */ + user_info->auth_description = gensec_final_auth_type(gensec_security); + + user_info->password_state = AUTH_PASSWORD_RESPONSE; + user_info->password.response.lanman = ntlmssp_state->lm_resp; + user_info->password.response.nt = ntlmssp_state->nt_resp; + + state->user_info = user_info; + return NT_STATUS_OK; +} + +static void ntlmssp_server_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = + tevent_req_callback_data(subreq, + struct tevent_req); + struct ntlmssp_server_auth_state *state = + tevent_req_data(req, + struct ntlmssp_server_auth_state); + struct gensec_security *gensec_security = state->gensec_security; + struct gensec_ntlmssp_context *gensec_ntlmssp = state->gensec_ntlmssp; + struct auth4_context *auth_context = gensec_security->auth_context; + uint8_t authoritative = 1; + NTSTATUS status; + + status = auth_context->check_ntlm_password_recv(subreq, + gensec_ntlmssp, + &authoritative, + &gensec_ntlmssp->server_returned_info, + &state->user_session_key, + &state->lm_session_key); + TALLOC_FREE(subreq); + if (!NT_STATUS_IS_OK(status)) { + DBG_INFO("Checking NTLMSSP password for %s\\%s failed: %s\n", + state->user_info->client.domain_name, + state->user_info->client.account_name, + nt_errstr(status)); + } + if (tevent_req_nterror(req, status)) { + return; + } + talloc_steal(state, state->user_session_key.data); + talloc_steal(state, state->lm_session_key.data); + + status = ntlmssp_server_postauth(state->gensec_security, + state->gensec_ntlmssp, + state, state->in); + if (tevent_req_nterror(req, status)) { + return; + } + + tevent_req_done(req); +} + +/** + * Next state function for the Authenticate packet + * (after authentication - figures out the session keys etc) + * + * @param ntlmssp_state NTLMSSP State + * @return Errors or NT_STATUS_OK. + */ + +static NTSTATUS ntlmssp_server_postauth(struct gensec_security *gensec_security, + struct gensec_ntlmssp_context *gensec_ntlmssp, + struct ntlmssp_server_auth_state *state, + DATA_BLOB request) +{ + struct ntlmssp_state *ntlmssp_state = gensec_ntlmssp->ntlmssp_state; + struct auth4_context *auth_context = gensec_security->auth_context; + DATA_BLOB user_session_key = state->user_session_key; + DATA_BLOB lm_session_key = state->lm_session_key; + NTSTATUS nt_status = NT_STATUS_OK; + DATA_BLOB session_key = data_blob(NULL, 0); + struct auth_session_info *session_info = NULL; + + TALLOC_FREE(state->user_info); + + if (lpcfg_map_to_guest(gensec_security->settings->lp_ctx) != NEVER_MAP_TO_GUEST + && auth_context->generate_session_info != NULL) + { + NTSTATUS tmp_status; + + /* + * We need to check if the auth is anonymous or mapped to guest + */ + tmp_status = auth_context->generate_session_info(auth_context, state, + gensec_ntlmssp->server_returned_info, + gensec_ntlmssp->ntlmssp_state->user, + AUTH_SESSION_INFO_SIMPLE_PRIVILEGES, + &session_info); + if (!NT_STATUS_IS_OK(tmp_status)) { + /* + * We don't care about failures, + * the worst result is that we try MIC checking + * for a map to guest authentication. + */ + TALLOC_FREE(session_info); + } + } + + if (session_info != NULL) { + if (security_session_user_level(session_info, NULL) < SECURITY_USER) { + /* + * Anonymous and GUEST are not secure anyway. + * avoid new_spnego and MIC checking. + */ + ntlmssp_state->new_spnego = false; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SEAL; + } + TALLOC_FREE(session_info); + } + + dump_data_pw("NT session key:\n", user_session_key.data, user_session_key.length); + dump_data_pw("LM first-8:\n", lm_session_key.data, lm_session_key.length); + + /* Handle the different session key derivation for NTLM2 */ + if (state->doing_ntlm2) { + if (user_session_key.data && user_session_key.length == 16) { + int rc; + + session_key = data_blob_talloc(ntlmssp_state, + NULL, 16); + + rc = gnutls_hmac_fast(GNUTLS_MAC_MD5, + user_session_key.data, + user_session_key.length, + state->session_nonce, + sizeof(state->session_nonce), + session_key.data); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + DEBUG(10,("ntlmssp_server_auth: Created NTLM2 session key.\n")); + dump_data_pw("NTLM2 session key:\n", session_key.data, session_key.length); + + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM2 session key.\n")); + session_key = data_blob_null; + } + } else if ((ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) + /* Ensure we can never get here on NTLMv2 */ + && (ntlmssp_state->nt_resp.length == 0 || ntlmssp_state->nt_resp.length == 24)) { + + if (lm_session_key.data && lm_session_key.length >= 8) { + if (ntlmssp_state->lm_resp.data && ntlmssp_state->lm_resp.length == 24) { + session_key = data_blob_talloc(ntlmssp_state, + NULL, 16); + if (session_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + nt_status = SMBsesskeygen_lm_sess_key(lm_session_key.data, + ntlmssp_state->lm_resp.data, + session_key.data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n")); + } else { + static const uint8_t zeros[24] = {0, }; + session_key = data_blob_talloc( + ntlmssp_state, NULL, 16); + if (session_key.data == NULL) { + return NT_STATUS_NO_MEMORY; + } + nt_status = SMBsesskeygen_lm_sess_key(zeros, zeros, + session_key.data); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + DEBUG(10,("ntlmssp_server_auth: Created NTLM session key.\n")); + } + dump_data_pw("LM session key:\n", session_key.data, + session_key.length); + } else { + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + DEBUG(10,("ntlmssp_server_auth: Failed to create NTLM session key.\n")); + session_key = data_blob_null; + } + + } else if (user_session_key.data) { + session_key = user_session_key; + DEBUG(10,("ntlmssp_server_auth: Using unmodified nt session key.\n")); + dump_data_pw("unmodified session key:\n", session_key.data, session_key.length); + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + } else if (lm_session_key.data) { + /* Very weird to have LM key, but no user session key, but anyway.. */ + session_key = lm_session_key; + DEBUG(10,("ntlmssp_server_auth: Using unmodified lm session key.\n")); + dump_data_pw("unmodified session key:\n", session_key.data, session_key.length); + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + + } else { + DEBUG(10,("ntlmssp_server_auth: Failed to create unmodified session key.\n")); + session_key = data_blob_null; + + /* LM Key not selected */ + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + /* With KEY_EXCH, the client supplies the proposed session key, + but encrypts it with the long-term key */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + if (!state->encrypted_session_key.data + || state->encrypted_session_key.length != 16) { + DEBUG(1, ("Client-supplied KEY_EXCH session key was of invalid length (%u)!\n", + (unsigned)state->encrypted_session_key.length)); + return NT_STATUS_INVALID_PARAMETER; + } else if (!session_key.data || session_key.length != 16) { + DEBUG(5, ("server session key is invalid (len == %u), cannot do KEY_EXCH!\n", + (unsigned int)session_key.length)); + ntlmssp_state->session_key = session_key; + talloc_steal(ntlmssp_state, session_key.data); + } else { + gnutls_cipher_hd_t cipher_hnd; + gnutls_datum_t enc_session_key = { + .data = session_key.data, + .size = session_key.length, + }; + int rc; + + dump_data_pw("KEY_EXCH session key (enc):\n", + state->encrypted_session_key.data, + state->encrypted_session_key.length); + + rc = gnutls_cipher_init(&cipher_hnd, + GNUTLS_CIPHER_ARCFOUR_128, + &enc_session_key, + NULL); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_cipher_encrypt(cipher_hnd, + state->encrypted_session_key.data, + state->encrypted_session_key.length); + gnutls_cipher_deinit(cipher_hnd); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + ntlmssp_state->session_key = data_blob_talloc(ntlmssp_state, + state->encrypted_session_key.data, + state->encrypted_session_key.length); + dump_data_pw("KEY_EXCH session key:\n", + state->encrypted_session_key.data, + state->encrypted_session_key.length); + } + } else { + ntlmssp_state->session_key = session_key; + talloc_steal(ntlmssp_state, session_key.data); + } + + if (ntlmssp_state->new_spnego) { + gnutls_hmac_hd_t hmac_hnd = NULL; + uint8_t mic_buffer[NTLMSSP_MIC_SIZE] = { 0, }; + bool cmp; + int rc; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + ntlmssp_state->session_key.data, + MIN(ntlmssp_state->session_key.length, 64)); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, + ntlmssp_state->negotiate_blob.data, + ntlmssp_state->negotiate_blob.length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, + ntlmssp_state->challenge_blob.data, + ntlmssp_state->challenge_blob.length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + /* checked were we set ntlmssp_state->new_spnego */ + SMB_ASSERT(request.length > + (NTLMSSP_MIC_OFFSET + NTLMSSP_MIC_SIZE)); + + rc = gnutls_hmac(hmac_hnd, request.data, NTLMSSP_MIC_OFFSET); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, mic_buffer, NTLMSSP_MIC_SIZE); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, + request.data + (NTLMSSP_MIC_OFFSET + NTLMSSP_MIC_SIZE), + request.length - (NTLMSSP_MIC_OFFSET + NTLMSSP_MIC_SIZE)); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + gnutls_hmac_deinit(hmac_hnd, mic_buffer); + + cmp = mem_equal_const_time(request.data + NTLMSSP_MIC_OFFSET, + mic_buffer, NTLMSSP_MIC_SIZE); + if (!cmp) { + DEBUG(1,("%s: invalid NTLMSSP_MIC for " + "user=[%s] domain=[%s] workstation=[%s]\n", + __func__, + ntlmssp_state->user, + ntlmssp_state->domain, + ntlmssp_state->client.netbios_name)); + dump_data(11, request.data + NTLMSSP_MIC_OFFSET, + NTLMSSP_MIC_SIZE); + dump_data(11, mic_buffer, + NTLMSSP_MIC_SIZE); + } + + ZERO_ARRAY(mic_buffer); + + if (!cmp) { + return NT_STATUS_INVALID_PARAMETER; + } + } + + data_blob_free(&ntlmssp_state->negotiate_blob); + data_blob_free(&ntlmssp_state->challenge_blob); + + if (gensec_ntlmssp_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + if (gensec_security->want_features & GENSEC_FEATURE_LDAP_STYLE) { + /* + * We need to handle NTLMSSP_NEGOTIATE_SIGN as + * NTLMSSP_NEGOTIATE_SEAL if GENSEC_FEATURE_LDAP_STYLE + * is requested. + */ + ntlmssp_state->force_wrap_seal = true; + } + nt_status = ntlmssp_sign_init(ntlmssp_state); + } + + data_blob_clear_free(&ntlmssp_state->internal_chal); + data_blob_clear_free(&ntlmssp_state->chal); + data_blob_clear_free(&ntlmssp_state->lm_resp); + data_blob_clear_free(&ntlmssp_state->nt_resp); + + ntlmssp_state->expected_state = NTLMSSP_DONE; + + return nt_status; +} + +NTSTATUS ntlmssp_server_auth_recv(struct tevent_req *req, + TALLOC_CTX *out_mem_ctx, + DATA_BLOB *out) +{ + NTSTATUS status; + + *out = data_blob_null; + + if (tevent_req_is_nterror(req, &status)) { + tevent_req_received(req); + return status; + } + + tevent_req_received(req); + return NT_STATUS_OK; +} diff --git a/auth/ntlmssp/ntlmssp_sign.c b/auth/ntlmssp/ntlmssp_sign.c new file mode 100644 index 0000000..11e5930 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_sign.c @@ -0,0 +1,893 @@ +/* + * Unix SMB/CIFS implementation. + * Version 3.0 + * NTLMSSP Signing routines + * Copyright (C) Andrew Bartlett 2003-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 "../auth/ntlmssp/ntlmssp.h" +#include "../libcli/auth/libcli_auth.h" +#include "zlib.h" +#include "../auth/ntlmssp/ntlmssp_private.h" + +#include "lib/crypto/gnutls_helpers.h" +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +#define CLI_SIGN "session key to client-to-server signing key magic constant" +#define CLI_SEAL "session key to client-to-server sealing key magic constant" +#define SRV_SIGN "session key to server-to-client signing key magic constant" +#define SRV_SEAL "session key to server-to-client sealing key magic constant" + +/** + * Some notes on the NTLM2 code: + * + * NTLM2 is a AEAD system. This means that the data encrypted is not + * all the data that is signed. In DCE-RPC case, the headers of the + * DCE-RPC packets are also signed. This prevents some of the + * fun-and-games one might have by changing them. + * + */ + +static void dump_arc4_state(const char *description, + gnutls_cipher_hd_t *state) +{ + DBG_DEBUG("%s\n", description); +} + +static NTSTATUS calc_ntlmv2_key(uint8_t subkey[16], + DATA_BLOB session_key, + const char *constant) +{ + gnutls_hash_hd_t hash_hnd = NULL; + int rc; + + rc = gnutls_hash_init(&hash_hnd, GNUTLS_DIG_MD5); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hash(hash_hnd, session_key.data, session_key.length); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hash(hash_hnd, constant, strlen(constant) + 1); + if (rc < 0) { + gnutls_hash_deinit(hash_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + gnutls_hash_deinit(hash_hnd, subkey); + + return NT_STATUS_OK; +} + +enum ntlmssp_direction { + NTLMSSP_SEND, + NTLMSSP_RECEIVE +}; + +static NTSTATUS ntlmssp_make_packet_signature(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + enum ntlmssp_direction direction, + DATA_BLOB *sig, bool encrypt_sig) +{ + NTSTATUS status = NT_STATUS_UNSUCCESSFUL; + int rc; + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + gnutls_hmac_hd_t hmac_hnd = NULL; + uint8_t digest[16]; + uint8_t seq_num[4]; + + *sig = data_blob_talloc(sig_mem_ctx, NULL, NTLMSSP_SIG_SIZE); + if (!sig->data) { + return NT_STATUS_NO_MEMORY; + } + + switch (direction) { + case NTLMSSP_SEND: + DEBUG(100,("ntlmssp_make_packet_signature: SEND seq = %u, len = %u, pdu_len = %u\n", + ntlmssp_state->crypt->ntlm2.sending.seq_num, + (unsigned int)length, + (unsigned int)pdu_length)); + + SIVAL(seq_num, 0, ntlmssp_state->crypt->ntlm2.sending.seq_num); + ntlmssp_state->crypt->ntlm2.sending.seq_num++; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + ntlmssp_state->crypt->ntlm2.sending.sign_key, + 16); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + break; + case NTLMSSP_RECEIVE: + + DEBUG(100,("ntlmssp_make_packet_signature: RECV seq = %u, len = %u, pdu_len = %u\n", + ntlmssp_state->crypt->ntlm2.receiving.seq_num, + (unsigned int)length, + (unsigned int)pdu_length)); + + SIVAL(seq_num, 0, ntlmssp_state->crypt->ntlm2.receiving.seq_num); + ntlmssp_state->crypt->ntlm2.receiving.seq_num++; + + rc = gnutls_hmac_init(&hmac_hnd, + GNUTLS_MAC_MD5, + ntlmssp_state->crypt->ntlm2.receiving.sign_key, + 16); + if (rc < 0) { + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + break; + } + + dump_data_pw("pdu data ", whole_pdu, pdu_length); + + rc = gnutls_hmac(hmac_hnd, seq_num, sizeof(seq_num)); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + rc = gnutls_hmac(hmac_hnd, whole_pdu, pdu_length); + if (rc < 0) { + gnutls_hmac_deinit(hmac_hnd, NULL); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + gnutls_hmac_deinit(hmac_hnd, digest); + + if (encrypt_sig && (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { + switch (direction) { + case NTLMSSP_SEND: + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm2.sending.seal_state, + digest, + 8); + break; + case NTLMSSP_RECEIVE: + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm2.receiving.seal_state, + digest, + 8); + break; + } + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt for NTLMv2 EXCH " + "%s packet signature failed: %s\n", + direction == NTLMSSP_SEND ? + "send" : "receive", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + } + + SIVAL(sig->data, 0, NTLMSSP_SIGN_VERSION); + memcpy(sig->data + 4, digest, 8); + ZERO_ARRAY(digest); + memcpy(sig->data + 12, seq_num, 4); + ZERO_ARRAY(seq_num); + + dump_data_pw("ntlmssp v2 sig ", sig->data, sig->length); + + } else { + uint32_t crc; + + crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, data, length); + + status = msrpc_gen(sig_mem_ctx, + sig, "dddd", + NTLMSSP_SIGN_VERSION, 0, crc, + ntlmssp_state->crypt->ntlm.seq_num); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + ntlmssp_state->crypt->ntlm.seq_num++; + + dump_arc4_state("ntlmssp hash: \n", + &ntlmssp_state->crypt->ntlm.seal_state); + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm.seal_state, + sig->data + 4, + sig->length - 4); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt for NTLM packet " + "signature failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + } + + return NT_STATUS_OK; +} + +NTSTATUS ntlmssp_sign_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + NTSTATUS nt_status; + + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + DEBUG(3, ("NTLMSSP Signing not negotiated - cannot sign packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot check sign packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_SEND, sig, true); + + return nt_status; +} + +/** + * Check the signature of an incoming packet + * @note caller *must* check that the signature is the size it expects + * + */ + +NTSTATUS ntlmssp_check_packet(struct ntlmssp_state *ntlmssp_state, + const uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + DATA_BLOB local_sig; + NTSTATUS nt_status; + TALLOC_CTX *tmp_ctx; + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot check packet signature\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + if (sig->length < 8) { + DEBUG(0, ("NTLMSSP packet check failed due to short signature (%lu bytes)!\n", + (unsigned long)sig->length)); + } + + tmp_ctx = talloc_new(ntlmssp_state); + if (!tmp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + tmp_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_RECEIVE, + &local_sig, true); + + if (!NT_STATUS_IS_OK(nt_status)) { + DEBUG(0,("NTLMSSP packet sig creation failed with %s\n", + nt_errstr(nt_status))); + talloc_free(tmp_ctx); + return nt_status; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + if (local_sig.length != sig->length || + !mem_equal_const_time(local_sig.data, sig->data, sig->length)) { + DEBUG(5, ("BAD SIG NTLM2: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM2 packet check failed due to invalid signature!\n")); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + } else { + if (local_sig.length != sig->length || + !mem_equal_const_time(local_sig.data + 8, sig->data + 8, sig->length - 8)) { + DEBUG(5, ("BAD SIG NTLM1: wanted signature of\n")); + dump_data(5, local_sig.data, local_sig.length); + + DEBUG(5, ("BAD SIG: got signature of\n")); + dump_data(5, sig->data, sig->length); + + DEBUG(0, ("NTLMSSP NTLM1 packet check failed due to invalid signature!\n")); + talloc_free(tmp_ctx); + return NT_STATUS_ACCESS_DENIED; + } + } + dump_data_pw("checked ntlmssp signature\n", sig->data, sig->length); + DEBUG(10,("ntlmssp_check_packet: NTLMSSP signature OK !\n")); + + talloc_free(tmp_ctx); + return NT_STATUS_OK; +} + +/** + * Seal data with the NTLMSSP algorithm + * + */ + +NTSTATUS ntlmssp_seal_packet(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *sig_mem_ctx, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + DATA_BLOB *sig) +{ + int rc; + + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL)) { + DEBUG(3, ("NTLMSSP Sealing not negotiated - cannot seal packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!(ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) { + DEBUG(3, ("NTLMSSP Sealing not negotiated - cannot seal packet!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot seal packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + DEBUG(10,("ntlmssp_seal_data: seal\n")); + dump_data_pw("ntlmssp clear data\n", data, length); + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + NTSTATUS nt_status; + /* + * The order of these two operations matters - we + * must first seal the packet, then seal the + * sequence number - this is because the + * send_seal_hash is not constant, but is is rather + * updated with each iteration + */ + nt_status = ntlmssp_make_packet_signature(ntlmssp_state, + sig_mem_ctx, + data, length, + whole_pdu, pdu_length, + NTLMSSP_SEND, + sig, false); + if (!NT_STATUS_IS_OK(nt_status)) { + return nt_status; + } + + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm2.sending.seal_state, + data, + length); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt ntlmv2 sealing the data " + "failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_KEY_EXCH) { + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm2.sending.seal_state, + sig->data + 4, + 8); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt ntlmv2 sealing " + "the EXCH signature data failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + } + } else { + NTSTATUS status; + uint32_t crc; + + crc = crc32(0, Z_NULL, 0); + crc = crc32(crc, data, length); + + status = msrpc_gen(sig_mem_ctx, + sig, "dddd", + NTLMSSP_SIGN_VERSION, 0, crc, + ntlmssp_state->crypt->ntlm.seq_num); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + /* + * The order of these two operations matters - we + * must first seal the packet, then seal the + * sequence number - this is because the ntlmv1_arc4_state + * is not constant, but is is rather updated with + * each iteration + */ + dump_arc4_state("ntlmv1 arc4 state:\n", + &ntlmssp_state->crypt->ntlm.seal_state); + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm.seal_state, + data, + length); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt ntlmv1 sealing data" + "failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + dump_arc4_state("ntlmv1 arc4 state:\n", + &ntlmssp_state->crypt->ntlm.seal_state); + + rc = gnutls_cipher_encrypt(ntlmssp_state->crypt->ntlm.seal_state, + sig->data + 4, + sig->length - 4); + if (rc < 0) { + DBG_ERR("gnutls_cipher_encrypt ntlmv1 sealing signing " + "data failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + ntlmssp_state->crypt->ntlm.seq_num++; + } + dump_data_pw("ntlmssp signature\n", sig->data, sig->length); + dump_data_pw("ntlmssp sealed data\n", data, length); + + return NT_STATUS_OK; +} + +/** + * Unseal data with the NTLMSSP algorithm + * + */ + +NTSTATUS ntlmssp_unseal_packet(struct ntlmssp_state *ntlmssp_state, + uint8_t *data, size_t length, + const uint8_t *whole_pdu, size_t pdu_length, + const DATA_BLOB *sig) +{ + NTSTATUS status; + int rc; + + if (!ntlmssp_state->session_key.length) { + DEBUG(3, ("NO session key, cannot unseal packet\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + DEBUG(10,("ntlmssp_unseal_packet: seal\n")); + dump_data_pw("ntlmssp sealed data\n", data, length); + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + /* First unseal the data. */ + rc = gnutls_cipher_decrypt(ntlmssp_state->crypt->ntlm2.receiving.seal_state, + data, + length); + if (rc < 0) { + DBG_ERR("gnutls_cipher_decrypt ntlmv2 unsealing the " + "data failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + dump_data_pw("ntlmv2 clear data\n", data, length); + } else { + rc = gnutls_cipher_decrypt(ntlmssp_state->crypt->ntlm.seal_state, + data, + length); + if (rc < 0) { + DBG_ERR("gnutls_cipher_decrypt ntlmv1 unsealing the " + "data failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + dump_data_pw("ntlmv1 clear data\n", data, length); + } + + status = ntlmssp_check_packet(ntlmssp_state, + data, length, + whole_pdu, pdu_length, + sig); + + if (!NT_STATUS_IS_OK(status)) { + DEBUG(1,("NTLMSSP packet check for unseal failed due to invalid signature on %llu bytes of input:\n", + (unsigned long long)length)); + } + return status; +} + +NTSTATUS ntlmssp_wrap(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + NTSTATUS nt_status; + DATA_BLOB sig; + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + if (in->length + NTLMSSP_SIG_SIZE < in->length) { + return NT_STATUS_INVALID_PARAMETER; + } + + *out = data_blob_talloc(out_mem_ctx, NULL, in->length + NTLMSSP_SIG_SIZE); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + memcpy(out->data + NTLMSSP_SIG_SIZE, in->data, in->length); + + nt_status = ntlmssp_seal_packet(ntlmssp_state, out_mem_ctx, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + &sig); + + if (NT_STATUS_IS_OK(nt_status)) { + memcpy(out->data, sig.data, NTLMSSP_SIG_SIZE); + talloc_free(sig.data); + } + return nt_status; + + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + if (in->length + NTLMSSP_SIG_SIZE < in->length) { + return NT_STATUS_INVALID_PARAMETER; + } + + *out = data_blob_talloc(out_mem_ctx, NULL, in->length + NTLMSSP_SIG_SIZE); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + memcpy(out->data + NTLMSSP_SIG_SIZE, in->data, in->length); + + nt_status = ntlmssp_sign_packet(ntlmssp_state, out_mem_ctx, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + out->data + NTLMSSP_SIG_SIZE, + out->length - NTLMSSP_SIG_SIZE, + &sig); + + if (NT_STATUS_IS_OK(nt_status)) { + memcpy(out->data, sig.data, NTLMSSP_SIG_SIZE); + talloc_free(sig.data); + } + return nt_status; + } else { + *out = data_blob_talloc(out_mem_ctx, in->data, in->length); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; + } +} + +NTSTATUS ntlmssp_unwrap(struct ntlmssp_state *ntlmssp_state, + TALLOC_CTX *out_mem_ctx, + const DATA_BLOB *in, + DATA_BLOB *out) +{ + DATA_BLOB sig; + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SEAL) { + if (in->length < NTLMSSP_SIG_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + sig.data = in->data; + sig.length = NTLMSSP_SIG_SIZE; + + *out = data_blob_talloc(out_mem_ctx, in->data + NTLMSSP_SIG_SIZE, in->length - NTLMSSP_SIG_SIZE); + + return ntlmssp_unseal_packet(ntlmssp_state, + out->data, out->length, + out->data, out->length, + &sig); + + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN) { + if (in->length < NTLMSSP_SIG_SIZE) { + return NT_STATUS_INVALID_PARAMETER; + } + sig.data = in->data; + sig.length = NTLMSSP_SIG_SIZE; + + *out = data_blob_talloc(out_mem_ctx, in->data + NTLMSSP_SIG_SIZE, in->length - NTLMSSP_SIG_SIZE); + + return ntlmssp_check_packet(ntlmssp_state, + out->data, out->length, + out->data, out->length, + &sig); + } else { + *out = data_blob_talloc(out_mem_ctx, in->data, in->length); + if (!out->data) { + return NT_STATUS_NO_MEMORY; + } + return NT_STATUS_OK; + } +} + +/** + Initialise the state for NTLMSSP signing. +*/ +NTSTATUS ntlmssp_sign_reset(struct ntlmssp_state *ntlmssp_state, + bool reset_seqnums) +{ + int rc; + + DEBUG(3, ("NTLMSSP Sign/Seal - Initialising with flags:\n")); + debug_ntlmssp_flags(ntlmssp_state->neg_flags); + + if (ntlmssp_state->crypt == NULL) { + return NT_STATUS_INVALID_PARAMETER_MIX; + } + + if (ntlmssp_state->force_wrap_seal && + (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_SIGN)) + { + /* + * We need to handle NTLMSSP_NEGOTIATE_SIGN as + * NTLMSSP_NEGOTIATE_SEAL if GENSEC_FEATURE_LDAP_STYLE + * is requested. + * + * The negotiation of flags (and authentication) + * is completed when ntlmssp_sign_init() is called + * so we can safely pretent NTLMSSP_NEGOTIATE_SEAL + * was negotiated. + */ + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_SEAL; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + DATA_BLOB weak_session_key = ntlmssp_state->session_key; + const char *send_sign_const; + const char *send_seal_const; + const char *recv_sign_const; + const char *recv_seal_const; + uint8_t send_seal_key[16] = {0}; + gnutls_datum_t send_seal_blob = { + .data = send_seal_key, + .size = sizeof(send_seal_key), + }; + uint8_t recv_seal_key[16] = {0}; + gnutls_datum_t recv_seal_blob = { + .data = recv_seal_key, + .size = sizeof(recv_seal_key), + }; + NTSTATUS status; + + switch (ntlmssp_state->role) { + case NTLMSSP_CLIENT: + send_sign_const = CLI_SIGN; + send_seal_const = CLI_SEAL; + recv_sign_const = SRV_SIGN; + recv_seal_const = SRV_SEAL; + break; + case NTLMSSP_SERVER: + send_sign_const = SRV_SIGN; + send_seal_const = SRV_SEAL; + recv_sign_const = CLI_SIGN; + recv_seal_const = CLI_SEAL; + break; + default: + return NT_STATUS_INTERNAL_ERROR; + } + + /* + * Weaken NTLMSSP keys to cope with down-level + * clients, servers and export restrictions. + * + * We probably should have some parameters to + * control this, once we get NTLM2 working. + * + * Key weakening was not performed on the master key + * for NTLM2, but must be done on the encryption subkeys only. + */ + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_128) { + /* nothing to do */ + } else if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + weak_session_key.length = 7; + } else { /* forty bits */ + weak_session_key.length = 5; + } + + dump_data_pw("NTLMSSP weakend master key:\n", + weak_session_key.data, + weak_session_key.length); + + /* SEND: sign key */ + status = calc_ntlmv2_key(ntlmssp_state->crypt->ntlm2.sending.sign_key, + ntlmssp_state->session_key, send_sign_const); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dump_data_pw("NTLMSSP send sign key:\n", + ntlmssp_state->crypt->ntlm2.sending.sign_key, 16); + + /* SEND: seal ARCFOUR pad */ + status = calc_ntlmv2_key(send_seal_key, + weak_session_key, + send_seal_const); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dump_data_pw("NTLMSSP send seal key:\n", + send_seal_key, + sizeof(send_seal_key)); + + if (ntlmssp_state->crypt->ntlm2.sending.seal_state != NULL) { + gnutls_cipher_deinit(ntlmssp_state->crypt->ntlm2.sending.seal_state); + } + rc = gnutls_cipher_init(&ntlmssp_state->crypt->ntlm2.sending.seal_state, + GNUTLS_CIPHER_ARCFOUR_128, + &send_seal_blob, + NULL); + if (rc < 0) { + DBG_ERR("gnutls_cipher_init failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + dump_arc4_state("NTLMSSP send seal arc4 state:\n", + &ntlmssp_state->crypt->ntlm2.sending.seal_state); + + /* SEND: seq num */ + if (reset_seqnums) { + ntlmssp_state->crypt->ntlm2.sending.seq_num = 0; + } + + /* RECV: sign key */ + status = calc_ntlmv2_key(ntlmssp_state->crypt->ntlm2.receiving.sign_key, + ntlmssp_state->session_key, recv_sign_const); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dump_data_pw("NTLMSSP recv sign key:\n", + ntlmssp_state->crypt->ntlm2.receiving.sign_key, 16); + + /* RECV: seal ARCFOUR pad */ + status = calc_ntlmv2_key(recv_seal_key, + weak_session_key, + recv_seal_const); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + dump_data_pw("NTLMSSP recv seal key:\n", + recv_seal_key, + sizeof(recv_seal_key)); + + if (ntlmssp_state->crypt->ntlm2.receiving.seal_state != NULL) { + gnutls_cipher_deinit(ntlmssp_state->crypt->ntlm2.receiving.seal_state); + } + rc = gnutls_cipher_init(&ntlmssp_state->crypt->ntlm2.receiving.seal_state, + GNUTLS_CIPHER_ARCFOUR_128, + &recv_seal_blob, + NULL); + if (rc < 0) { + DBG_ERR("gnutls_cipher_init failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + dump_arc4_state("NTLMSSP recv seal arc4 state:\n", + &ntlmssp_state->crypt->ntlm2.receiving.seal_state); + + /* RECV: seq num */ + if (reset_seqnums) { + ntlmssp_state->crypt->ntlm2.receiving.seq_num = 0; + } + } else { + gnutls_datum_t seal_session_key = { + .data = ntlmssp_state->session_key.data, + .size = ntlmssp_state->session_key.length, + }; + bool do_weak = false; + + DEBUG(5, ("NTLMSSP Sign/Seal - using NTLM1\n")); + + /* + * Key weakening not performed on the master key for NTLM2 + * and does not occour for NTLM1. Therefore we only need + * to do this for the LM_KEY. + */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_LM_KEY) { + do_weak = true; + } + + /* + * Nothing to weaken. + * We certainly don't want to 'extend' the length... + */ + if (ntlmssp_state->session_key.length < 16) { + /* TODO: is this really correct? */ + do_weak = false; + } + + if (do_weak) { + uint8_t weak_session_key[8]; + + memcpy(weak_session_key, seal_session_key.data, 8); + seal_session_key = (gnutls_datum_t) { + .data = weak_session_key, + .size = sizeof(weak_session_key), + }; + + /* + * LM key doesn't support 128 bit crypto, so this is + * the best we can do. If you negotiate 128 bit, but + * not 56, you end up with 40 bit... + */ + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_56) { + weak_session_key[7] = 0xa0; + } else { /* forty bits */ + weak_session_key[5] = 0xe5; + weak_session_key[6] = 0x38; + weak_session_key[7] = 0xb0; + } + } + + if (ntlmssp_state->crypt->ntlm.seal_state != NULL) { + gnutls_cipher_deinit(ntlmssp_state->crypt->ntlm.seal_state); + } + rc = gnutls_cipher_init(&ntlmssp_state->crypt->ntlm.seal_state, + GNUTLS_CIPHER_ARCFOUR_128, + &seal_session_key, + NULL); + if (rc < 0) { + DBG_ERR("gnutls_cipher_init failed: %s\n", + gnutls_strerror(rc)); + return gnutls_error_to_ntstatus(rc, NT_STATUS_NTLM_BLOCKED); + } + + dump_arc4_state("NTLMv1 arc4 state:\n", + &ntlmssp_state->crypt->ntlm.seal_state); + + if (reset_seqnums) { + ntlmssp_state->crypt->ntlm.seq_num = 0; + } + } + + return NT_STATUS_OK; +} + +static int ntlmssp_crypt_free_gnutls_cipher_state(union ntlmssp_crypt_state *c) +{ + if (c->ntlm2.sending.seal_state != NULL) { + gnutls_cipher_deinit(c->ntlm2.sending.seal_state); + c->ntlm2.sending.seal_state = NULL; + } + if (c->ntlm2.receiving.seal_state != NULL) { + gnutls_cipher_deinit(c->ntlm2.receiving.seal_state); + c->ntlm2.receiving.seal_state = NULL; + } + if (c->ntlm.seal_state != NULL) { + gnutls_cipher_deinit(c->ntlm.seal_state); + c->ntlm.seal_state = NULL; + } + + return 0; +} + +NTSTATUS ntlmssp_sign_init(struct ntlmssp_state *ntlmssp_state) +{ + if (ntlmssp_state->session_key.length < 8) { + DEBUG(3, ("NO session key, cannot initialise signing\n")); + return NT_STATUS_NO_USER_SESSION_KEY; + } + + ntlmssp_state->crypt = talloc_zero(ntlmssp_state, + union ntlmssp_crypt_state); + if (ntlmssp_state->crypt == NULL) { + return NT_STATUS_NO_MEMORY; + } + talloc_set_destructor(ntlmssp_state->crypt, + ntlmssp_crypt_free_gnutls_cipher_state); + + return ntlmssp_sign_reset(ntlmssp_state, true); +} diff --git a/auth/ntlmssp/ntlmssp_util.c b/auth/ntlmssp/ntlmssp_util.c new file mode 100644 index 0000000..6f3b474 --- /dev/null +++ b/auth/ntlmssp/ntlmssp_util.c @@ -0,0 +1,220 @@ +/* + Unix SMB/Netbios implementation. + Version 3.0 + handle NLTMSSP, server side + + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Andrew Bartlett 2001-2003 + Copyright (C) Andrew Bartlett 2005 (Updated from gensec). + + 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 "../auth/ntlmssp/ntlmssp.h" +#include "../auth/ntlmssp/ntlmssp_private.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +static void debug_ntlmssp_flags_raw(int level, uint32_t flags) +{ +#define _PRINT_FLAG_LINE(v) do { \ + if (flags & (v)) { \ + DEBUGADD(level, (" " #v "\n")); \ + } \ +} while (0) + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_UNICODE); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_OEM); + _PRINT_FLAG_LINE(NTLMSSP_REQUEST_TARGET); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_SIGN); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_SEAL); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_DATAGRAM); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_LM_KEY); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_NETWARE); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_NTLM); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_NT_ONLY); + _PRINT_FLAG_LINE(NTLMSSP_ANONYMOUS); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_OEM_DOMAIN_SUPPLIED); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_OEM_WORKSTATION_SUPPLIED); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_THIS_IS_LOCAL_CALL); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_ALWAYS_SIGN); + _PRINT_FLAG_LINE(NTLMSSP_TARGET_TYPE_DOMAIN); + _PRINT_FLAG_LINE(NTLMSSP_TARGET_TYPE_SERVER); + _PRINT_FLAG_LINE(NTLMSSP_TARGET_TYPE_SHARE); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_IDENTIFY); + _PRINT_FLAG_LINE(NTLMSSP_REQUEST_NON_NT_SESSION_KEY); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_TARGET_INFO); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_VERSION); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_128); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_KEY_EXCH); + _PRINT_FLAG_LINE(NTLMSSP_NEGOTIATE_56); +} + +/** + * Print out the NTLMSSP flags for debugging + * @param neg_flags The flags from the packet + */ +void debug_ntlmssp_flags(uint32_t neg_flags) +{ + DEBUG(3,("Got NTLMSSP neg_flags=0x%08x\n", neg_flags)); + debug_ntlmssp_flags_raw(4, neg_flags); +} + +NTSTATUS ntlmssp_handle_neg_flags(struct ntlmssp_state *ntlmssp_state, + uint32_t flags, const char *name) +{ + uint32_t missing_flags = ntlmssp_state->required_flags; + + if (ntlmssp_state->use_ntlmv2) { + /* + * Using NTLMv2 as a client implies + * using NTLMSSP_NEGOTIATE_NTLM2 + * (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) + * + * Note that 'use_ntlmv2' is only set + * true in the client case. + * + * Even if the server has a bug and does not announce + * it, we need to assume it's present. + * + * Note that we also have the flag + * in ntlmssp_state->required_flags, + * see gensec_ntlmssp_client_start(). + * + * See bug #12862. + */ + flags |= NTLMSSP_NEGOTIATE_NTLM2; + } + + if (flags & NTLMSSP_NEGOTIATE_UNICODE) { + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_UNICODE; + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_OEM; + ntlmssp_state->unicode = true; + } else { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_UNICODE; + ntlmssp_state->neg_flags |= NTLMSSP_NEGOTIATE_OEM; + ntlmssp_state->unicode = false; + } + + /* + * NTLMSSP_NEGOTIATE_NTLM2 (NTLMSSP_NEGOTIATE_EXTENDED_SESSIONSECURITY) + * has priority over NTLMSSP_NEGOTIATE_LM_KEY + */ + if (!(flags & NTLMSSP_NEGOTIATE_NTLM2)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_NTLM2; + } + + if (ntlmssp_state->neg_flags & NTLMSSP_NEGOTIATE_NTLM2) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (!(flags & NTLMSSP_NEGOTIATE_LM_KEY)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_LM_KEY; + } + + if (!(flags & NTLMSSP_NEGOTIATE_ALWAYS_SIGN)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_ALWAYS_SIGN; + } + + if (!(flags & NTLMSSP_NEGOTIATE_128)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_128; + } + + if (!(flags & NTLMSSP_NEGOTIATE_56)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_56; + } + + if (!(flags & NTLMSSP_NEGOTIATE_KEY_EXCH)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_KEY_EXCH; + } + + if (!(flags & NTLMSSP_NEGOTIATE_SIGN)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SIGN; + } + + if (!(flags & NTLMSSP_NEGOTIATE_SEAL)) { + ntlmssp_state->neg_flags &= ~NTLMSSP_NEGOTIATE_SEAL; + } + + if ((flags & NTLMSSP_REQUEST_TARGET)) { + ntlmssp_state->neg_flags |= NTLMSSP_REQUEST_TARGET; + } + + missing_flags &= ~ntlmssp_state->neg_flags; + if (missing_flags != 0) { + HRESULT hres = HRES_SEC_E_UNSUPPORTED_FUNCTION; + NTSTATUS status = NT_STATUS(HRES_ERROR_V(hres)); + DEBUG(1, ("%s: Got %s flags[0x%08x] " + "- possible downgrade detected! " + "missing_flags[0x%08x] - %s\n", + __func__, name, + (unsigned)flags, + (unsigned)missing_flags, + nt_errstr(status))); + debug_ntlmssp_flags_raw(1, missing_flags); + DEBUGADD(4, ("neg_flags[0x%08x]\n", + (unsigned)ntlmssp_state->neg_flags)); + debug_ntlmssp_flags_raw(4, ntlmssp_state->neg_flags); + return status; + } + + return NT_STATUS_OK; +} + +/* Does this blob looks like it could be NTLMSSP? */ +bool ntlmssp_blob_matches_magic(const DATA_BLOB *blob) +{ + if (blob->length > 8 && memcmp("NTLMSSP\0", blob->data, 8) == 0) { + return true; + } else { + return false; + } +} + +const DATA_BLOB ntlmssp_version_blob(void) +{ + /* + * This is a simplified version of + * + * enum ndr_err_code err; + * struct ntlmssp_VERSION vers; + * + * ZERO_STRUCT(vers); + * vers.ProductMajorVersion = NTLMSSP_WINDOWS_MAJOR_VERSION_6; + * vers.ProductMinorVersion = NTLMSSP_WINDOWS_MINOR_VERSION_1; + * vers.ProductBuild = 0; + * vers.NTLMRevisionCurrent = NTLMSSP_REVISION_W2K3; + * + * err = ndr_push_struct_blob(&version_blob, + * ntlmssp_state, + * &vers, + * (ndr_push_flags_fn_t)ndr_push_ntlmssp_VERSION); + * + * if (!NDR_ERR_CODE_IS_SUCCESS(err)) { + * data_blob_free(&struct_blob); + * return NT_STATUS_NO_MEMORY; + * } + */ + static const uint8_t version_buffer[8] = { + NTLMSSP_WINDOWS_MAJOR_VERSION_6, + NTLMSSP_WINDOWS_MINOR_VERSION_1, + 0x00, 0x00, /* product build */ + 0x00, 0x00, 0x00, /* reserved */ + NTLMSSP_REVISION_W2K3 + }; + + return data_blob_const(version_buffer, ARRAY_SIZE(version_buffer)); +} diff --git a/auth/ntlmssp/wscript_build b/auth/ntlmssp/wscript_build new file mode 100644 index 0000000..20836ef --- /dev/null +++ b/auth/ntlmssp/wscript_build @@ -0,0 +1,27 @@ +bld.SAMBA_SUBSYSTEM('NTLMSSP_COMMON', + source='''gensec_ntlmssp.c + ntlmssp.c + ntlmssp_util.c + ntlmssp_ndr.c + ntlmssp_client.c + ntlmssp_server.c + ntlmssp_sign.c + gensec_ntlmssp_server.c''', + deps=''' + samba-util + NDR_NTLMSSP + MSRPC_PARSE + NTLM_CHECK + samba-credentials + wbclient + z + GNUTLS_HELPERS + ''') + +bld.SAMBA_MODULE('gensec_ntlmssp', + source='''''', + subsystem='gensec', + init_function='gensec_ntlmssp_init', + deps='NTLMSSP_COMMON', + internal_module=True + ) diff --git a/auth/wbc_auth_util.c b/auth/wbc_auth_util.c new file mode 100644 index 0000000..52573e2 --- /dev/null +++ b/auth/wbc_auth_util.c @@ -0,0 +1,242 @@ +/* + Unix SMB/CIFS implementation. + Authentication utility functions + Copyright (C) Volker Lendecke 2010 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "libcli/security/security.h" +#include "librpc/gen_ndr/netlogon.h" +#include "nsswitch/libwbclient/wbclient.h" +#include "librpc/gen_ndr/auth.h" +#include "auth/auth_sam_reply.h" + +#undef DBGC_CLASS +#define DBGC_CLASS DBGC_AUTH + +static NTSTATUS wbcsids_to_samr_RidWithAttributeArray( + TALLOC_CTX *mem_ctx, + struct samr_RidWithAttributeArray *groups, + const struct dom_sid *domain_sid, + const struct wbcSidWithAttr *sids, + size_t num_sids) +{ + unsigned int i, j = 0; + bool ok; + + groups->rids = talloc_array(mem_ctx, + struct samr_RidWithAttribute, num_sids); + if (!groups->rids) { + return NT_STATUS_NO_MEMORY; + } + + /* a wbcDomainSid is the same as a dom_sid */ + for (i = 0; i < num_sids; i++) { + ok = sid_peek_check_rid(domain_sid, + (const struct dom_sid *)&sids[i].sid, + &groups->rids[j].rid); + if (!ok) continue; + + groups->rids[j].attributes = SE_GROUP_MANDATORY | + SE_GROUP_ENABLED_BY_DEFAULT | + SE_GROUP_ENABLED; + j++; + } + + groups->count = j; + return NT_STATUS_OK; +} + +static NTSTATUS wbcsids_to_netr_SidAttrArray( + const struct dom_sid *domain_sid, + const struct wbcSidWithAttr *sids, + size_t num_sids, + TALLOC_CTX *mem_ctx, + struct netr_SidAttr **_info3_sids, + uint32_t *info3_num_sids) +{ + unsigned int i, j = 0; + struct netr_SidAttr *info3_sids; + + info3_sids = talloc_array(mem_ctx, struct netr_SidAttr, num_sids); + if (info3_sids == NULL) { + return NT_STATUS_NO_MEMORY; + } + + /* a wbcDomainSid is the same as a dom_sid */ + for (i = 0; i < num_sids; i++) { + const struct dom_sid *sid; + + sid = (const struct dom_sid *)&sids[i].sid; + + if (dom_sid_in_domain(domain_sid, sid)) { + continue; + } + + info3_sids[j].sid = dom_sid_dup(info3_sids, sid); + if (info3_sids[j].sid == NULL) { + talloc_free(info3_sids); + return NT_STATUS_NO_MEMORY; + } + info3_sids[j].attributes = SE_GROUP_MANDATORY | + SE_GROUP_ENABLED_BY_DEFAULT | + SE_GROUP_ENABLED; + j++; + } + + *info3_num_sids = j; + *_info3_sids = info3_sids; + return NT_STATUS_OK; +} + +#undef RET_NOMEM + +#define RET_NOMEM(ptr) do { \ + if (!ptr) { \ + TALLOC_FREE(info6); \ + return NULL; \ + } } while(0) + +struct netr_SamInfo6 *wbcAuthUserInfo_to_netr_SamInfo6(TALLOC_CTX *mem_ctx, + const struct wbcAuthUserInfo *info) +{ + struct netr_SamInfo6 *info6; + struct dom_sid user_sid; + struct dom_sid group_sid; + struct dom_sid domain_sid; + NTSTATUS status; + bool ok; + + memcpy(&user_sid, &info->sids[0].sid, sizeof(user_sid)); + memcpy(&group_sid, &info->sids[1].sid, sizeof(group_sid)); + + info6 = talloc_zero(mem_ctx, struct netr_SamInfo6); + if (!info6) return NULL; + + unix_to_nt_time(&info6->base.logon_time, info->logon_time); + unix_to_nt_time(&info6->base.logoff_time, info->logoff_time); + unix_to_nt_time(&info6->base.kickoff_time, info->kickoff_time); + unix_to_nt_time(&info6->base.last_password_change, info->pass_last_set_time); + unix_to_nt_time(&info6->base.allow_password_change, + info->pass_can_change_time); + unix_to_nt_time(&info6->base.force_password_change, + info->pass_must_change_time); + + if (info->account_name) { + info6->base.account_name.string = + talloc_strdup(info6, info->account_name); + RET_NOMEM(info6->base.account_name.string); + } + if (info->user_principal) { + info6->principal_name.string = + talloc_strdup(info6, info->user_principal); + RET_NOMEM(info6->principal_name.string); + } + if (info->full_name) { + info6->base.full_name.string = + talloc_strdup(info6, info->full_name); + RET_NOMEM(info6->base.full_name.string); + } + if (info->domain_name) { + info6->base.logon_domain.string = + talloc_strdup(info6, info->domain_name); + RET_NOMEM(info6->base.logon_domain.string); + } + if (info->dns_domain_name) { + info6->dns_domainname.string = + talloc_strdup(info6, info->dns_domain_name); + RET_NOMEM(info6->dns_domainname.string); + } + if (info->logon_script) { + info6->base.logon_script.string = + talloc_strdup(info6, info->logon_script); + RET_NOMEM(info6->base.logon_script.string); + } + if (info->profile_path) { + info6->base.profile_path.string = + talloc_strdup(info6, info->profile_path); + RET_NOMEM(info6->base.profile_path.string); + } + if (info->home_directory) { + info6->base.home_directory.string = + talloc_strdup(info6, info->home_directory); + RET_NOMEM(info6->base.home_directory.string); + } + if (info->home_drive) { + info6->base.home_drive.string = + talloc_strdup(info6, info->home_drive); + RET_NOMEM(info6->base.home_drive.string); + } + + info6->base.logon_count = info->logon_count; + info6->base.bad_password_count = info->bad_password_count; + + sid_copy(&domain_sid, &user_sid); + sid_split_rid(&domain_sid, &info6->base.rid); + + ok = sid_peek_check_rid(&domain_sid, &group_sid, + &info6->base.primary_gid); + if (!ok) { + DEBUG(1, ("The primary group sid domain does not" + "match user sid domain for user: %s\n", + info->account_name)); + TALLOC_FREE(info6); + return NULL; + } + + status = wbcsids_to_samr_RidWithAttributeArray(info6, + &info6->base.groups, + &domain_sid, + &info->sids[1], + info->num_sids - 1); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(info6); + return NULL; + } + + status = wbcsids_to_netr_SidAttrArray(&domain_sid, + &info->sids[1], + info->num_sids - 1, + info6, + &info6->sids, + &info6->sidcount); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(info6); + return NULL; + } + + info6->base.user_flags = info->user_flags; + memcpy(info6->base.key.key, info->user_session_key, 16); + + if (info->logon_server) { + info6->base.logon_server.string = + talloc_strdup(info6, info->logon_server); + RET_NOMEM(info6->base.logon_server.string); + } + if (info->domain_name) { + info6->base.logon_domain.string = + talloc_strdup(info6, info->domain_name); + RET_NOMEM(info6->base.logon_domain.string); + } + + info6->base.domain_sid = dom_sid_dup(info6, &domain_sid); + RET_NOMEM(info6->base.domain_sid); + + memcpy(info6->base.LMSessKey.key, info->lm_session_key, 8); + info6->base.acct_flags = info->acct_flags; + + return info6; +} diff --git a/auth/wscript_build b/auth/wscript_build new file mode 100644 index 0000000..a8b0e6c --- /dev/null +++ b/auth/wscript_build @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +bld.SAMBA_LIBRARY('common_auth', + source='''auth_sam_reply.c + wbc_auth_util.c + auth_log.c + auth_util.c''', + deps='''talloc + samba-security + samba-util + util_str_escape + LIBTSOCKET + audit_logging + jansson + MESSAGING_SEND + server_id_db + ndr-samba''', + private_library=True) + +bld.RECURSE('gensec') +bld.RECURSE('ntlmssp') +bld.RECURSE('credentials') |