summaryrefslogtreecommitdiffstats
path: root/auth
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:20:00 +0000
commit8daa83a594a2e98f39d764422bfbdbc62c9efd44 (patch)
tree4099e8021376c7d8c05bdf8503093d80e9c7bad0 /auth
parentInitial commit. (diff)
downloadsamba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.tar.xz
samba-8daa83a594a2e98f39d764422bfbdbc62c9efd44.zip
Adding upstream version 2:4.20.0+dfsg.upstream/2%4.20.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'auth')
-rw-r--r--auth/auth_log.c1083
-rw-r--r--auth/auth_sam_reply.c955
-rw-r--r--auth/auth_sam_reply.h92
-rw-r--r--auth/auth_util.c71
-rw-r--r--auth/auth_util.h32
-rw-r--r--auth/authn_policy.c198
-rw-r--r--auth/authn_policy.h87
-rw-r--r--auth/authn_policy_impl.h82
-rw-r--r--auth/common_auth.h240
-rw-r--r--auth/credentials/credentials.c1941
-rw-r--r--auth/credentials/credentials.h369
-rw-r--r--auth/credentials/credentials_cmdline.c73
-rw-r--r--auth/credentials/credentials_internal.h142
-rw-r--r--auth/credentials/credentials_krb5.c1581
-rw-r--r--auth/credentials/credentials_krb5.h45
-rw-r--r--auth/credentials/credentials_ntlm.c560
-rw-r--r--auth/credentials/credentials_secrets.c486
-rw-r--r--auth/credentials/pycredentials.c1768
-rw-r--r--auth/credentials/pycredentials.h40
-rw-r--r--auth/credentials/samba-credentials.pc.in12
-rwxr-xr-xauth/credentials/tests/bind.py261
-rw-r--r--auth/credentials/tests/test_creds.c349
-rw-r--r--auth/credentials/wscript_build44
-rw-r--r--auth/gensec/external.c127
-rw-r--r--auth/gensec/gensec.c856
-rw-r--r--auth/gensec/gensec.h327
-rw-r--r--auth/gensec/gensec_internal.h183
-rw-r--r--auth/gensec/gensec_start.c1147
-rw-r--r--auth/gensec/gensec_util.c338
-rw-r--r--auth/gensec/ncalrpc.c356
-rw-r--r--auth/gensec/schannel.c1181
-rw-r--r--auth/gensec/spnego.c2249
-rw-r--r--auth/gensec/wscript_build37
-rw-r--r--auth/kerberos/gssapi_helper.c398
-rw-r--r--auth/kerberos/gssapi_helper.h55
-rw-r--r--auth/kerberos/gssapi_pac.c348
-rw-r--r--auth/kerberos/kerberos_pac.c554
-rw-r--r--auth/kerberos/pac_utils.h81
-rw-r--r--auth/kerberos/wscript_build4
-rw-r--r--auth/ntlmssp/gensec_ntlmssp.c246
-rw-r--r--auth/ntlmssp/gensec_ntlmssp_server.c245
-rw-r--r--auth/ntlmssp/ntlmssp.c408
-rw-r--r--auth/ntlmssp/ntlmssp.h151
-rw-r--r--auth/ntlmssp/ntlmssp_client.c1020
-rw-r--r--auth/ntlmssp/ntlmssp_ndr.c134
-rw-r--r--auth/ntlmssp/ntlmssp_ndr.h38
-rw-r--r--auth/ntlmssp/ntlmssp_private.h192
-rw-r--r--auth/ntlmssp/ntlmssp_server.c1160
-rw-r--r--auth/ntlmssp/ntlmssp_sign.c893
-rw-r--r--auth/ntlmssp/ntlmssp_util.c220
-rw-r--r--auth/ntlmssp/wscript_build27
-rw-r--r--auth/wbc_auth_util.c238
-rw-r--r--auth/wscript_build26
53 files changed, 23750 insertions, 0 deletions
diff --git a/auth/auth_log.c b/auth/auth_log.c
new file mode 100644
index 0000000..9a110fd
--- /dev/null
+++ b/auth/auth_log.c
@@ -0,0 +1,1083 @@
+/*
+
+ 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
+#define KDC_AUTHZ_FAILURE_LEVEL 2
+#define KDC_AUTHZ_SUCCESS_LEVEL 3
+
+/* 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"
+#define KDC_AUTHZ_JSON_TYPE "KDC Authorization"
+
+/*
+ * 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 3
+#define AUTHZ_MAJOR 1
+#define AUTHZ_MINOR 2
+#define KDC_AUTHZ_MAJOR 1
+#define KDC_AUTHZ_MINOR 0
+
+#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 command 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,
+ const struct authn_audit_info *client_audit_info,
+ const struct authn_audit_info *server_audit_info,
+ enum event_id_type event_id,
+ int debug_level)
+{
+ struct json_object wrapper = json_empty_object;
+ struct json_object authentication = json_empty_object;
+ struct json_object client_policy = json_null_object();
+ struct json_object server_policy = json_null_object();
+ 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;
+ }
+ rc = json_add_flags32(
+ &authentication, "netlogonNegotiateFlags",
+ ui->netlogon_trust_account.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;
+ }
+
+ if (client_audit_info != NULL) {
+ client_policy = json_from_audit_info(client_audit_info);
+ if (json_is_invalid(&client_policy)) {
+ goto failure;
+ }
+ }
+
+ rc = json_add_object(&authentication, "clientPolicyAccessCheck", &client_policy);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ if (server_audit_info != NULL) {
+ server_policy = json_from_audit_info(server_audit_info);
+ if (json_is_invalid(&server_policy)) {
+ goto failure;
+ }
+ }
+
+ rc = json_add_object(&authentication, "serverPolicyAccessCheck", &server_policy);
+ 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(&current_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:
+ json_free(&server_policy);
+ json_free(&client_policy);
+ /*
+ * 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 command 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,
+ const struct authn_audit_info *client_audit_info,
+ const struct authn_audit_info *server_audit_info,
+ int debug_level)
+{
+ struct json_object wrapper = json_empty_object;
+ struct json_object authorization = json_empty_object;
+ struct json_object client_policy = json_null_object();
+ struct json_object server_policy = json_null_object();
+ 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[PRIMARY_USER_SID_INDEX]);
+ 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;
+ }
+ rc = json_add_flags32(&authorization, "accountFlags", session_info->info->acct_flags);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ if (client_audit_info != NULL) {
+ client_policy = json_from_audit_info(client_audit_info);
+ if (json_is_invalid(&client_policy)) {
+ goto failure;
+ }
+ }
+
+ rc = json_add_object(&authorization, "clientPolicyAccessCheck", &client_policy);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ if (server_audit_info != NULL) {
+ server_policy = json_from_audit_info(server_audit_info);
+ if (json_is_invalid(&server_policy)) {
+ goto failure;
+ }
+ }
+
+ rc = json_add_object(&authorization, "serverPolicyAccessCheck", &server_policy);
+ 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:
+ json_free(&server_policy);
+ json_free(&client_policy);
+ /*
+ * 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");
+}
+
+/*
+ * Log details of an 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 KDC_AUTHZ_MAJOR
+ *
+ * IF adding a new field please update the minor version number KDC_AUTHZ_MINOR
+ *
+ * To process the resulting log lines from the command line use jq to
+ * parse the json.
+ *
+ * grep "^ {" log_file |\
+ * jq -rc '"\(.timestamp)\t
+ * \(."KDC Authorization".domain)\t
+ * \(."KDC Authorization".account)\t
+ * \(."KDC Authorization".remoteAddress)"'
+ *
+ */
+static void log_authz_event_json(
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ const struct authn_audit_info *server_audit_info,
+ const char *service_description,
+ const char *auth_type,
+ const char *domain_name,
+ const char *account_name,
+ const struct dom_sid *sid,
+ const char *logon_server,
+ const struct timeval authtime,
+ NTSTATUS status,
+ int debug_level)
+{
+ struct json_object wrapper = json_empty_object;
+ struct json_object authorization = json_empty_object;
+ struct json_object server_policy = json_null_object();
+ int rc = 0;
+
+ authorization = json_new_object();
+ if (json_is_invalid(&authorization)) {
+ goto failure;
+ }
+ rc = json_add_version(&authorization, KDC_AUTHZ_MAJOR, KDC_AUTHZ_MINOR);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&authorization, "status", nt_errstr(status));
+ 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", domain_name);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&authorization, "account", account_name);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_sid(&authorization, "sid", sid);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_string(&authorization, "logonServer", logon_server);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_time(&authorization, "authTime", authtime);
+ if (rc != 0) {
+ goto failure;
+ }
+
+ if (server_audit_info != NULL) {
+ server_policy = json_from_audit_info(server_audit_info);
+ if (json_is_invalid(&server_policy)) {
+ goto failure;
+ }
+ }
+
+ rc = json_add_object(&authorization, "serverPolicyAccessCheck", &server_policy);
+ 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", KDC_AUTHZ_JSON_TYPE);
+ if (rc != 0) {
+ goto failure;
+ }
+ rc = json_add_object(&wrapper, KDC_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:
+ json_free(&server_policy);
+ /*
+ * 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 KDC Authorization 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");
+ }
+ }
+}
+
+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,
+ const struct authn_audit_info *client_audit_info,
+ const struct authn_audit_info *server_audit_info,
+ enum event_id_type event_id,
+ int debug_level)
+{
+ log_no_json(msg_ctx, lp_ctx);
+}
+
+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,
+ const struct authn_audit_info *client_audit_info,
+ const struct authn_audit_info *server_audit_info,
+ int debug_level)
+{
+ log_no_json(msg_ctx, lp_ctx);
+}
+
+static void log_authz_event_json(
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ const struct authn_audit_info *server_audit_info,
+ const char *service_description,
+ const char *auth_type,
+ const char *domain_name,
+ const char *account_name,
+ const struct dom_sid *sid,
+ const char *logon_server,
+ const struct timeval authtime,
+ NTSTATUS status,
+ int debug_level)
+{
+ log_no_json(msg_ctx, lp_ctx);
+}
+
+#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,
+ const struct authn_audit_info *client_audit_info,
+ const struct authn_audit_info *server_audit_info)
+{
+ /* 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,
+ client_audit_info,
+ server_audit_info,
+ 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[PRIMARY_USER_SID_INDEX],
+ &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,
+ const struct authn_audit_info *client_audit_info,
+ const struct authn_audit_info *server_audit_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,
+ client_audit_info,
+ server_audit_info,
+ debug_level);
+ }
+}
+
+/*
+ * Log details of an authorization to a service.
+ *
+ * NOTE: msg_ctx and lp_ctx are optional, but when supplied, allow streaming the
+ * authorization events over the message bus.
+ */
+void log_authz_event(
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ const struct authn_audit_info *server_audit_info,
+ const char *service_description,
+ const char *auth_type,
+ const char *domain_name,
+ const char *account_name,
+ const struct dom_sid *sid,
+ const char *logon_server,
+ const struct timeval authtime,
+ NTSTATUS status)
+{
+ /* set the log level */
+ int debug_level = KDC_AUTHZ_FAILURE_LEVEL;
+
+ if (NT_STATUS_IS_OK(status)) {
+ debug_level = KDC_AUTHZ_SUCCESS_LEVEL;
+ }
+
+ if (CHECK_DEBUGLVLC(DBGC_AUTH_AUDIT_JSON, debug_level) ||
+ (msg_ctx && lp_ctx && lpcfg_auth_event_notification(lp_ctx))) {
+ log_authz_event_json(msg_ctx, lp_ctx,
+ remote,
+ local,
+ server_audit_info,
+ service_description,
+ auth_type,
+ domain_name,
+ account_name,
+ sid,
+ logon_server,
+ authtime,
+ status,
+ debug_level);
+ }
+}
diff --git a/auth/auth_sam_reply.c b/auth/auth_sam_reply.c
new file mode 100644
index 0000000..8c0ebe5
--- /dev/null
+++ b/auth/auth_sam_reply.c
@@ -0,0 +1,955 @@
+/*
+ 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"
+
+/* Returns true if this SID belongs in SamBaseInfo, otherwise false. */
+static bool is_base_sid(const struct auth_SidAttr *sid,
+ const struct dom_sid *domain_sid)
+{
+ if (sid->attrs & SE_GROUP_RESOURCE) {
+ /*
+ * Resource groups don't belong in the base
+ * RIDs, they're handled elsewhere.
+ */
+ return false;
+ }
+
+ /*
+ * This SID belongs in the base structure only if it's in the account's
+ * domain.
+ */
+ return dom_sid_in_domain(domain_sid, &sid->sid);
+}
+
+/* Stores a SID in a previously allocated array. */
+static NTSTATUS store_extra_sid(struct netr_SidAttr *sids,
+ uint32_t *sidcount,
+ const uint32_t allocated_sids,
+ const struct auth_SidAttr *sid)
+{
+ /* Check we aren't about to overflow our allocation. */
+ if (*sidcount >= allocated_sids) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ sids[*sidcount].sid = dom_sid_dup(sids, &sid->sid);
+ if (sids[*sidcount].sid == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+ sids[*sidcount].attributes = sid->attrs;
+ *sidcount += 1;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Stores a resource SID in a previously allocated array, either Extra SIDs or
+ * Resource SIDs. Any SID within the domain of the first SID so added is stored
+ * there, while remaining SIDs are stored in Extra SIDs.
+ */
+static NTSTATUS store_resource_sid(struct netr_SidAttr *sids,
+ uint32_t *sidcount,
+ const uint32_t allocated_sids,
+ const struct auth_SidAttr *sid,
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups,
+ const uint32_t allocated_resource_groups)
+{
+ NTSTATUS status;
+
+ struct dom_sid *resource_domain = NULL;
+ uint32_t rid;
+
+ if (resource_groups == NULL) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ /* Split the SID into domain and RID. */
+ status = dom_sid_split_rid(resource_groups, &sid->sid, &resource_domain, &rid);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ if (resource_groups->domain_sid == NULL) {
+ /*
+ * There is no domain SID set. Set it to the domain of this SID.
+ */
+ resource_groups->domain_sid = resource_domain;
+ } else {
+ /*
+ * A domain SID has already been set. Check whether this SID's
+ * domain matches.
+ *
+ * Assuming that resource SIDs have been obtained with
+ * dsdb_expand_nested_groups(), they should all be within the
+ * same domain (ours), so unless something has gone horribly
+ * wrong, we should always find that they match.
+ */
+ bool match = dom_sid_equal(resource_groups->domain_sid, resource_domain);
+ talloc_free(resource_domain);
+ if (!match) {
+ /*
+ * It doesn't match, so we can't store this SID here. It
+ * will have to go in Extra SIDs.
+ */
+ return store_extra_sid(sids, sidcount, allocated_sids, sid);
+ }
+ }
+
+ /* Store the SID in Resource SIDs. */
+
+ /* Check we aren't about to overflow our allocation. */
+ if (resource_groups->groups.count >= allocated_resource_groups) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ resource_groups->groups.rids[resource_groups->groups.count].rid = rid;
+ resource_groups->groups.rids[resource_groups->groups.count].attributes = sid->attrs;
+ resource_groups->groups.count++;
+
+ return NT_STATUS_OK;
+}
+
+/*
+ * Stores a SID in a previously allocated array, or excludes it if we are not
+ * storing resource groups. It will be placed in either Extra SIDs or Resource
+ * SIDs, depending on which is appropriate.
+ */
+static NTSTATUS store_sid(struct netr_SidAttr *sids,
+ uint32_t *sidcount,
+ const uint32_t allocated_sids,
+ const struct auth_SidAttr *sid,
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups,
+ const uint32_t allocated_resource_groups,
+ const enum auth_group_inclusion group_inclusion)
+{
+ /* See if it's a resource SID. */
+ if (sid->attrs & SE_GROUP_RESOURCE) {
+ /*
+ * If this is the SID of a resource group, determine whether it
+ * should be included or filtered out.
+ */
+ switch (group_inclusion) {
+ case AUTH_INCLUDE_RESOURCE_GROUPS:
+ /* Include this SID in Extra SIDs. */
+ break;
+ case AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED:
+ /*
+ * Try to include this SID in Resource Groups. If this
+ * can't be arranged, we shall fall back to Extra
+ * SIDs.
+ */
+ return store_resource_sid(sids,
+ sidcount,
+ allocated_sids,
+ sid,
+ resource_groups,
+ allocated_resource_groups);
+ case AUTH_EXCLUDE_RESOURCE_GROUPS:
+ /* Ignore this SID. */
+ return NT_STATUS_OK;
+ default:
+ /* This means we have a bug. */
+ DBG_ERR("invalid group inclusion parameter: %u\n", group_inclusion);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ /* Just store the SID in Extra SIDs. */
+ return store_extra_sid(sids,
+ sidcount,
+ allocated_sids,
+ sid);
+}
+
+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].sid,
+ &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].sid,
+ 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 > REMAINING_SIDS_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=REMAINING_SIDS_INDEX; i<user_info_dc->num_sids; i++) {
+ struct auth_SidAttr *group_sid = &user_info_dc->sids[i];
+
+ bool belongs_in_base = is_base_sid(group_sid, sam->domain_sid);
+ if (!belongs_in_base) {
+ /* We handle this elsewhere */
+ continue;
+ }
+ sam->groups.rids[sam->groups.count].rid =
+ group_sid->sid.sub_auths[group_sid->sid.num_auths-1];
+
+ sam->groups.rids[sam->groups.count].attributes = group_sid->attrs;
+ sam->groups.count += 1;
+ }
+
+ if (sam->groups.count == 0) {
+ TALLOC_FREE(sam->groups.rids);
+ }
+ }
+
+ sam->user_flags = info->user_flags; /* w2k3 uses NETLOGON_EXTRA_SIDS | NETLOGON_NTLMV2_ENABLED */
+ 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 and resource_groups structures 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,
+ enum auth_group_inclusion group_inclusion,
+ struct netr_SamInfo6 **_sam6,
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP **_resource_groups)
+{
+ NTSTATUS status;
+ struct netr_SamInfo6 *sam6 = NULL;
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP *resource_groups = NULL;
+ size_t i;
+
+ const uint32_t allocated_sids = user_info_dc->num_sids;
+ uint32_t allocated_resource_groups = 0;
+
+ sam6 = talloc_zero(mem_ctx, struct netr_SamInfo6);
+ if (sam6 == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (_resource_groups == NULL) {
+ if (group_inclusion == AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED) {
+ DBG_ERR("_resource_groups parameter not provided to receive resource groups!\n");
+ TALLOC_FREE(sam6);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ } else if (group_inclusion == AUTH_INCLUDE_RESOURCE_GROUPS_COMPRESSED) {
+ *_resource_groups = NULL;
+
+ /* Allocate resource groups structure. */
+ resource_groups = talloc_zero(mem_ctx, struct PAC_DOMAIN_GROUP_MEMBERSHIP);
+ if (resource_groups == NULL) {
+ TALLOC_FREE(sam6);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ * Allocate enough space to store user_info_dc->num_sids
+ * RIDs in the worst case.
+ */
+ allocated_resource_groups = user_info_dc->num_sids;
+ resource_groups->groups.rids = talloc_zero_array(resource_groups,
+ struct samr_RidWithAttribute,
+ allocated_resource_groups);
+ if (resource_groups->groups.rids == NULL) {
+ TALLOC_FREE(sam6);
+ TALLOC_FREE(resource_groups);
+ return NT_STATUS_NO_MEMORY;
+ }
+ } else {
+ /* No resource groups will be provided. */
+ *_resource_groups = NULL;
+ }
+
+ status = auth_convert_user_info_dc_sambaseinfo(sam6,
+ user_info_dc,
+ &sam6->base);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sam6);
+ TALLOC_FREE(resource_groups);
+ return status;
+ }
+
+ /*
+ * Allocate enough space to store user_info_dc->num_sids SIDs in the
+ * worst case.
+ */
+ sam6->sids = talloc_zero_array(sam6, struct netr_SidAttr,
+ allocated_sids);
+ if (sam6->sids == NULL) {
+ TALLOC_FREE(sam6);
+ TALLOC_FREE(resource_groups);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /* We don't put the user and group SIDs in there */
+ for (i=REMAINING_SIDS_INDEX; i<user_info_dc->num_sids; i++) {
+ struct auth_SidAttr *group_sid = &user_info_dc->sids[i];
+ bool belongs_in_base = is_base_sid(group_sid, sam6->base.domain_sid);
+ if (belongs_in_base) {
+ /* We already handled this in the base. */
+ continue;
+ }
+
+ status = store_sid(sam6->sids,
+ &sam6->sidcount,
+ allocated_sids,
+ group_sid,
+ resource_groups,
+ allocated_resource_groups,
+ group_inclusion);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sam6);
+ TALLOC_FREE(resource_groups);
+ return status;
+ }
+ }
+ if (sam6->sidcount) {
+ sam6->base.user_flags |= NETLOGON_EXTRA_SIDS;
+ } else {
+ sam6->base.user_flags &= ~NETLOGON_EXTRA_SIDS;
+ TALLOC_FREE(sam6->sids);
+ }
+
+ 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);
+ TALLOC_FREE(resource_groups);
+ 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);
+ TALLOC_FREE(resource_groups);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *_sam6 = sam6;
+ if (resource_groups != NULL) {
+ if (resource_groups->groups.count > 0) {
+ *_resource_groups = resource_groups;
+ } else {
+ TALLOC_FREE(resource_groups);
+ }
+ }
+ 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,
+ enum auth_group_inclusion group_inclusion,
+ 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,
+ group_inclusion, &sam6,
+ NULL);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(sam2);
+ return status;
+ }
+ sam2->base = sam6->base;
+ /*
+ * We have nowhere to put sam6->sids, so we follow Windows here and drop
+ * it. Any resource groups it happened to contain are lost.
+ */
+ sam2->base.user_flags &= ~NETLOGON_EXTRA_SIDS;
+ TALLOC_FREE(sam6->sids);
+
+ *_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,
+ enum auth_group_inclusion group_inclusion,
+ struct netr_SamInfo3 **_sam3,
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP **_resource_groups)
+{
+ 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,
+ group_inclusion, &sam6,
+ _resource_groups);
+ 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);
+ if (info == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (base->account_name.string) {
+ info->account_name = talloc_strdup(info, base->account_name.string);
+ } else {
+ info->account_name = talloc_strdup(info, account_name);
+ }
+ if (info->account_name == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ if (base->logon_domain.string) {
+ info->domain_name = talloc_strdup(info, base->logon_domain.string);
+ if (info->domain_name == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ if (base->full_name.string) {
+ info->full_name = talloc_strdup(info, base->full_name.string);
+ if (info->full_name == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (base->logon_script.string) {
+ info->logon_script = talloc_strdup(info, base->logon_script.string);
+ if (info->logon_script == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (base->profile_path.string) {
+ info->profile_path = talloc_strdup(info, base->profile_path.string);
+ if (info->profile_path == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (base->home_directory.string) {
+ info->home_directory = talloc_strdup(info, base->home_directory.string);
+ if (info->home_directory == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (base->home_drive.string) {
+ info->home_drive = talloc_strdup(info, base->home_drive.string);
+ if (info->home_drive == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ if (base->logon_server.string) {
+ info->logon_server = talloc_strdup(info, base->logon_server.string);
+ if (info->logon_server == NULL) {
+ talloc_free(info);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+ 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;
+
+ info->user_flags = base->user_flags;
+ if (!authenticated) {
+ /*
+ * We only consider the user authenticated if NETLOGON_GUEST is
+ * not set, and authenticated is set
+ */
+ info->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);
+ if (user_info_dc == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ /*
+ 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\n"));
+ talloc_free(user_info_dc);
+ 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) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ user_info_dc->num_sids = PRIMARY_SIDS_COUNT;
+
+ user_info_dc->sids = talloc_array(user_info_dc, struct auth_SidAttr, user_info_dc->num_sids + base->groups.count);
+ if (user_info_dc->sids == NULL) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ user_info_dc->sids[PRIMARY_USER_SID_INDEX].sid = *base->domain_sid;
+ if (!sid_append_rid(&user_info_dc->sids[PRIMARY_USER_SID_INDEX].sid, base->rid)) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ user_info_dc->sids[PRIMARY_USER_SID_INDEX].attrs = SE_GROUP_DEFAULT_FLAGS;
+
+ user_info_dc->sids[PRIMARY_GROUP_SID_INDEX].sid = *base->domain_sid;
+ if (!sid_append_rid(&user_info_dc->sids[PRIMARY_GROUP_SID_INDEX].sid, base->primary_gid)) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ /*
+ * This attribute value might be wrong if the primary group is a
+ * resource group. But a resource group is not meant to be in a primary
+ * group in the first place, and besides, these attributes will never
+ * make their way into a PAC.
+ */
+ user_info_dc->sids[PRIMARY_GROUP_SID_INDEX].attrs = SE_GROUP_DEFAULT_FLAGS;
+
+ for (i = 0; i < base->groups.count; i++) {
+ user_info_dc->sids[user_info_dc->num_sids].sid = *base->domain_sid;
+ if (!sid_append_rid(&user_info_dc->sids[user_info_dc->num_sids].sid, base->groups.rids[i].rid)) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ user_info_dc->sids[user_info_dc->num_sids].attrs = base->groups.rids[i].attributes;
+ 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) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ if (sidcount > 0) {
+ struct auth_SidAttr *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 auth_SidAttr,
+ dgrps_count);
+ if (dgrps == NULL) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ for (i = 0; i < sidcount; i++) {
+ if (sids[i].sid) {
+ dgrps[user_info_dc->num_sids].sid = *sids[i].sid;
+ dgrps[user_info_dc->num_sids].attrs = sids[i].attributes;
+ 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)) {
+ talloc_free(user_info_dc);
+ 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) {
+ talloc_free(user_info_dc);
+ 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) {
+ talloc_free(user_info_dc);
+ 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));
+ if (user_info_dc->user_session_key.data == NULL) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ 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));
+ if (user_info_dc->lm_session_key.data == NULL) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_NO_MEMORY;
+ }
+ }
+
+ *_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. For group_inclusion, pass AUTH_INCLUDE_RESOURCE_GROUPS if SIDs from
+ * the resource groups are to be included in the resulting structure, and pass
+ * AUTH_EXCLUDE_RESOURCE_GROUPS otherwise.
+ */
+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,
+ const enum auth_group_inclusion group_inclusion,
+ 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;
+
+ 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) {
+ switch (group_inclusion) {
+ case AUTH_INCLUDE_RESOURCE_GROUPS:
+ /* Take resource groups from the PAC. */
+ rg = &pac_logon_info->resource_groups;
+ break;
+ case AUTH_EXCLUDE_RESOURCE_GROUPS:
+ /*
+ * The PAC is from a TGT, or we don't want to process
+ * its resource groups.
+ */
+ break;
+ default:
+ DBG_ERR("invalid group inclusion parameter: %u\n", group_inclusion);
+ talloc_free(user_info_dc);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ if (rg != NULL && 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\n"));
+ 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 auth_SidAttr, 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].sid = *rg->domain_sid;
+ ok = sid_append_rid(&user_info_dc->sids[user_info_dc->num_sids].sid,
+ rg->groups.rids[i].rid);
+ if (!ok) {
+ talloc_free(user_info_dc);
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+ user_info_dc->sids[user_info_dc->num_sids].attrs = rg->groups.rids[i].attributes;
+ 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) {
+ talloc_free(user_info_dc);
+ 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) {
+ talloc_free(user_info_dc);
+ 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..57a9824
--- /dev/null
+++ b/auth/auth_sam_reply.h
@@ -0,0 +1,92 @@
+/*
+ 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,
+ enum auth_group_inclusion group_inclusion,
+ struct netr_SamInfo6 **_sam6,
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP **_resource_groups);
+NTSTATUS auth_convert_user_info_dc_saminfo2(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ enum auth_group_inclusion group_inclusion,
+ struct netr_SamInfo2 **_sam2);
+NTSTATUS auth_convert_user_info_dc_saminfo3(TALLOC_CTX *mem_ctx,
+ const struct auth_user_info_dc *user_info_dc,
+ enum auth_group_inclusion group_inclusion,
+ struct netr_SamInfo3 **_sam3,
+ struct PAC_DOMAIN_GROUP_MEMBERSHIP **_resource_groups);
+
+/**
+ * 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,
+ enum auth_group_inclusion group_inclusion,
+ 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/authn_policy.c b/auth/authn_policy.c
new file mode 100644
index 0000000..5929c00
--- /dev/null
+++ b/auth/authn_policy.c
@@ -0,0 +1,198 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba Active Directory authentication policy functions
+
+ Copyright (C) Catalyst.Net Ltd 2023
+
+ 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 "lib/replace/replace.h"
+#include "auth/authn_policy.h"
+#include "auth/authn_policy_impl.h"
+
+bool authn_policy_is_enforced(const struct authn_policy *policy)
+{
+ return policy->enforced;
+}
+
+/* Authentication policies for Kerberos clients. */
+
+/* Is an authentication policy enforced? */
+bool authn_kerberos_client_policy_is_enforced(const struct authn_kerberos_client_policy *policy)
+{
+ return authn_policy_is_enforced(&policy->policy);
+}
+
+/* Get the raw TGT lifetime enforced by an authentication policy. */
+int64_t authn_policy_enforced_tgt_lifetime_raw(const struct authn_kerberos_client_policy *policy)
+{
+ if (policy == NULL) {
+ return 0;
+ }
+
+ if (!authn_policy_is_enforced(&policy->policy)) {
+ return 0;
+ }
+
+ return policy->tgt_lifetime_raw;
+}
+
+/* Auditing information. */
+
+enum auth_event_id_type authn_audit_info_event_id(const struct authn_audit_info *audit_info)
+{
+ bool is_enforced;
+
+ if (audit_info->event == AUTHN_AUDIT_EVENT_OK) {
+ /* We didn’t get an error. */
+ return AUTH_EVT_ID_NONE;
+ }
+
+ if (audit_info->policy == NULL) {
+ /*
+ * We got an error, but there’s no policy, so it must have
+ * stemmed from something else.
+ */
+ return AUTH_EVT_ID_NONE;
+ }
+
+ is_enforced = authn_policy_is_enforced(audit_info->policy);
+
+ switch (audit_info->event) {
+ case AUTHN_AUDIT_EVENT_KERBEROS_DEVICE_RESTRICTION:
+ if (is_enforced) {
+ return AUTH_EVT_ID_KERBEROS_DEVICE_RESTRICTION;
+ }
+
+ return AUTH_EVT_ID_KERBEROS_DEVICE_RESTRICTION_AUDIT;
+
+ case AUTHN_AUDIT_EVENT_KERBEROS_SERVER_RESTRICTION:
+ if (is_enforced) {
+ return AUTH_EVT_ID_KERBEROS_SERVER_RESTRICTION;
+ }
+
+ return AUTH_EVT_ID_KERBEROS_SERVER_RESTRICTION_AUDIT;
+
+ case AUTHN_AUDIT_EVENT_NTLM_DEVICE_RESTRICTION:
+ if (is_enforced) {
+ return AUTH_EVT_ID_NTLM_DEVICE_RESTRICTION;
+ }
+
+ /* No relevant event ID. */
+ break;
+
+ case AUTHN_AUDIT_EVENT_NTLM_SERVER_RESTRICTION:
+ case AUTHN_AUDIT_EVENT_OTHER_ERROR:
+ default:
+ /* No relevant event ID. */
+ break;
+ }
+
+ return AUTH_EVT_ID_NONE;
+}
+
+const char *authn_audit_info_silo_name(const struct authn_audit_info *audit_info)
+{
+ if (audit_info->policy == NULL) {
+ return NULL;
+ }
+
+ return audit_info->policy->silo_name;
+}
+
+const char *authn_audit_info_policy_name(const struct authn_audit_info *audit_info)
+{
+ if (audit_info->policy == NULL) {
+ return NULL;
+ }
+
+ return audit_info->policy->policy_name;
+}
+
+const bool *authn_audit_info_policy_enforced(const struct authn_audit_info *audit_info)
+{
+ if (audit_info->policy == NULL) {
+ return NULL;
+ }
+
+ return &audit_info->policy->enforced;
+}
+
+const struct auth_user_info_dc *authn_audit_info_client_info(const struct authn_audit_info *audit_info)
+{
+ return audit_info->client_info;
+}
+
+const char *authn_audit_info_event(const struct authn_audit_info *audit_info)
+{
+ switch (audit_info->event) {
+ case AUTHN_AUDIT_EVENT_OK:
+ return "OK";
+ case AUTHN_AUDIT_EVENT_KERBEROS_DEVICE_RESTRICTION:
+ return "KERBEROS_DEVICE_RESTRICTION";
+ case AUTHN_AUDIT_EVENT_KERBEROS_SERVER_RESTRICTION:
+ return "KERBEROS_SERVER_RESTRICTION";
+ case AUTHN_AUDIT_EVENT_NTLM_DEVICE_RESTRICTION:
+ return "NTLM_DEVICE_RESTRICTION";
+ case AUTHN_AUDIT_EVENT_NTLM_SERVER_RESTRICTION:
+ return "NTLM_SERVER_RESTRICTION";
+ case AUTHN_AUDIT_EVENT_OTHER_ERROR:
+ default:
+ return "OTHER_ERROR";
+ }
+}
+
+const char *authn_audit_info_reason(const struct authn_audit_info *audit_info)
+{
+ switch (audit_info->reason) {
+ case AUTHN_AUDIT_REASON_DESCRIPTOR_INVALID:
+ return "DESCRIPTOR_INVALID";
+ case AUTHN_AUDIT_REASON_DESCRIPTOR_NO_OWNER:
+ return "DESCRIPTOR_NO_OWNER";
+ case AUTHN_AUDIT_REASON_SECURITY_TOKEN_FAILURE:
+ return "SECURITY_TOKEN_FAILURE";
+ case AUTHN_AUDIT_REASON_ACCESS_DENIED:
+ return "ACCESS_DENIED";
+ case AUTHN_AUDIT_REASON_FAST_REQUIRED:
+ return "FAST_REQUIRED";
+ case AUTHN_AUDIT_REASON_NONE:
+ default:
+ return NULL;
+ }
+}
+
+NTSTATUS authn_audit_info_policy_status(const struct authn_audit_info *audit_info)
+{
+ return audit_info->policy_status;
+}
+
+const char *authn_audit_info_location(const struct authn_audit_info *audit_info)
+{
+ return audit_info->location;
+}
+
+struct authn_int64_optional authn_audit_info_policy_tgt_lifetime_mins(const struct authn_audit_info *audit_info)
+{
+ int64_t lifetime;
+
+ if (!audit_info->tgt_lifetime_raw.is_present) {
+ return authn_int64_none();
+ }
+
+ lifetime = audit_info->tgt_lifetime_raw.val;
+ lifetime /= INT64_C(1000) * 1000 * 10 * 60;
+
+ return authn_int64_some(lifetime);
+}
diff --git a/auth/authn_policy.h b/auth/authn_policy.h
new file mode 100644
index 0000000..f2142fe
--- /dev/null
+++ b/auth/authn_policy.h
@@ -0,0 +1,87 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba Active Directory authentication policy functions
+
+ Copyright (C) Catalyst.Net Ltd 2023
+
+ 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 KDC_AUTHN_POLICY_H
+#define KDC_AUTHN_POLICY_H
+
+#include "lib/replace/replace.h"
+#include "libcli/util/ntstatus.h"
+#include "librpc/gen_ndr/windows_event_ids.h"
+
+/* Authentication policies for Kerberos clients. */
+
+struct authn_kerberos_client_policy;
+
+/* Is an authentication policy enforced? */
+bool authn_kerberos_client_policy_is_enforced(const struct authn_kerberos_client_policy *policy);
+
+/* Get the raw TGT lifetime enforced by an authentication policy. */
+int64_t authn_policy_enforced_tgt_lifetime_raw(const struct authn_kerberos_client_policy *policy);
+
+/* Auditing information. */
+
+struct authn_audit_info;
+
+/* This enum should be kept in sync with authn_audit_info_event(). */
+enum authn_audit_event {
+ AUTHN_AUDIT_EVENT_OK = 0,
+ AUTHN_AUDIT_EVENT_KERBEROS_DEVICE_RESTRICTION,
+ AUTHN_AUDIT_EVENT_KERBEROS_SERVER_RESTRICTION,
+ AUTHN_AUDIT_EVENT_NTLM_DEVICE_RESTRICTION,
+ AUTHN_AUDIT_EVENT_NTLM_SERVER_RESTRICTION,
+ AUTHN_AUDIT_EVENT_OTHER_ERROR,
+};
+
+/* This enum should be kept in sync with authn_audit_info_reason(). */
+enum authn_audit_reason {
+ AUTHN_AUDIT_REASON_NONE = 0,
+ AUTHN_AUDIT_REASON_DESCRIPTOR_INVALID,
+ AUTHN_AUDIT_REASON_DESCRIPTOR_NO_OWNER,
+ AUTHN_AUDIT_REASON_SECURITY_TOKEN_FAILURE,
+ AUTHN_AUDIT_REASON_ACCESS_DENIED,
+ AUTHN_AUDIT_REASON_FAST_REQUIRED,
+};
+
+enum auth_event_id_type authn_audit_info_event_id(const struct authn_audit_info *audit_info);
+
+const char *authn_audit_info_silo_name(const struct authn_audit_info *audit_info);
+
+const char *authn_audit_info_policy_name(const struct authn_audit_info *audit_info);
+
+const bool *authn_audit_info_policy_enforced(const struct authn_audit_info *audit_info);
+
+const struct auth_user_info_dc *authn_audit_info_client_info(const struct authn_audit_info *audit_info);
+
+const char *authn_audit_info_event(const struct authn_audit_info *audit_info);
+
+const char *authn_audit_info_reason(const struct authn_audit_info *audit_info);
+
+NTSTATUS authn_audit_info_policy_status(const struct authn_audit_info *audit_info);
+
+const char *authn_audit_info_location(const struct authn_audit_info *audit_info);
+
+struct authn_int64_optional {
+ bool is_present;
+ int64_t val;
+};
+
+struct authn_int64_optional authn_audit_info_policy_tgt_lifetime_mins(const struct authn_audit_info *audit_info);
+
+#endif
diff --git a/auth/authn_policy_impl.h b/auth/authn_policy_impl.h
new file mode 100644
index 0000000..121c6cb
--- /dev/null
+++ b/auth/authn_policy_impl.h
@@ -0,0 +1,82 @@
+/*
+ Unix SMB/CIFS implementation.
+ Samba Active Directory authentication policy private implementation details
+
+ Copyright (C) Catalyst.Net Ltd 2023
+
+ 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 KDC_AUTHN_POLICY_IMPL_H
+#define KDC_AUTHN_POLICY_IMPL_H
+
+#include "lib/replace/replace.h"
+
+#include "auth/authn_policy.h"
+#include "lib/util/data_blob.h"
+#include "libcli/util/ntstatus.h"
+
+struct authn_policy {
+ const char *silo_name;
+ const char *policy_name;
+ bool enforced;
+};
+
+bool authn_policy_is_enforced(const struct authn_policy *policy);
+
+struct authn_kerberos_client_policy {
+ struct authn_policy policy;
+ DATA_BLOB allowed_to_authenticate_from;
+ int64_t tgt_lifetime_raw;
+};
+
+struct authn_ntlm_client_policy {
+ struct authn_policy policy;
+ DATA_BLOB allowed_to_authenticate_from;
+ bool allowed_ntlm_network_auth;
+};
+
+struct authn_server_policy {
+ struct authn_policy policy;
+ DATA_BLOB allowed_to_authenticate_to;
+};
+
+/* Auditing information. */
+
+struct authn_audit_info {
+ struct authn_policy *policy;
+ const struct auth_user_info_dc *client_info;
+ enum authn_audit_event event;
+ enum authn_audit_reason reason;
+ NTSTATUS policy_status;
+ const char *location;
+ struct authn_int64_optional tgt_lifetime_raw;
+};
+
+static inline struct authn_int64_optional authn_int64_some(const int64_t val)
+{
+ return (struct authn_int64_optional) {
+ .is_present = true,
+ .val = val,
+ };
+}
+
+static inline struct authn_int64_optional authn_int64_none(void)
+{
+ return (struct authn_int64_optional) {
+ .is_present = false,
+ };
+}
+
+#endif
diff --git a/auth/common_auth.h b/auth/common_auth.h
new file mode 100644
index 0000000..1afb79e
--- /dev/null
+++ b/auth/common_auth.h
@@ -0,0 +1,240 @@
+/*
+ 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 privileges, 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 */
+#define AUTH_SESSION_INFO_FORCE_COMPOUNDED_AUTHENTICATION 0x20 /* The user authenticated with a device. */
+#define AUTH_SESSION_INFO_DEVICE_DEFAULT_GROUPS 0x40 /* Add the device to the default world and network groups */
+#define AUTH_SESSION_INFO_DEVICE_AUTHENTICATED 0x80 /* Add the device to the 'authenticated users' group */
+
+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.
+ */
+struct authn_audit_info;
+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,
+ const struct authn_audit_info *client_audit_info,
+ const struct authn_audit_info *server_audit_info);
+
+/*
+ * 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,
+ const struct authn_audit_info *client_audit_info,
+ const struct authn_audit_info *server_audit_info);
+
+/*
+ * Log details of an authorization to a service.
+ *
+ * NOTE: msg_ctx and lp_ctx are optional, but when supplied, allow streaming the
+ * authorization events over the message bus.
+ */
+void log_authz_event(
+ struct imessaging_context *msg_ctx,
+ struct loadparm_context *lp_ctx,
+ const struct tsocket_address *remote,
+ const struct tsocket_address *local,
+ const struct authn_audit_info *server_audit_info,
+ const char *service_description,
+ const char *auth_type,
+ const char *domain_name,
+ const char *account_name,
+ const struct dom_sid *sid,
+ const char *logon_server,
+ const struct timeval authtime,
+ NTSTATUS status);
+
+#endif
diff --git a/auth/credentials/credentials.c b/auth/credentials/credentials.c
new file mode 100644
index 0000000..20ab858
--- /dev/null
+++ b/auth/credentials/credentials.c
@@ -0,0 +1,1941 @@
+/*
+ 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 occurred.
+ */
+_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 explicitly
+ */
+_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 occurred.
+ */
+_PUBLIC_ const char *
+cli_credentials_get_password_and_obtained(struct cli_credentials *cred,
+ enum credentials_obtained *obtained)
+{
+ const char *password = cli_credentials_get_password(cred);
+
+ if (obtained != NULL) {
+ *obtained = cred->password_obtained;
+ }
+
+ return password;
+}
+
+/* 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;
+}
+
+/**
+ * @brief Obtain the domain for this credential context.
+ *
+ * @param[in] cred The credential context.
+ *
+ * @param[out] obtained A pointer to store the obtained information.
+ *
+ * @return The domain name or NULL if an error occurred.
+ */
+_PUBLIC_ const char *cli_credentials_get_domain_and_obtained(
+ struct cli_credentials *cred,
+ enum credentials_obtained *obtained)
+{
+ const char *domain = cli_credentials_get_domain(cred);
+
+ if (obtained != NULL) {
+ *obtained = cred->domain_obtained;
+ }
+
+ return 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 channel 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 channel 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];
+
+ if (credentials->password_obtained >= obtained) {
+ return false;
+ }
+
+ for(p = pass, *p = '\0'; /* ensure that pass is null-terminated */
+ p && p - pass < sizeof(pass) - 1;) {
+ 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 signing 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 signing 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 signing 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\n");
+ 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\n");
+ 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\n");
+ 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..341c984
--- /dev/null
+++ b/auth/credentials/credentials.h
@@ -0,0 +1,369 @@
+/*
+ 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);
+const char *cli_credentials_get_domain_and_obtained(
+ struct cli_credentials *cred,
+ enum credentials_obtained *obtained);
+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_is_password_nt_hash(struct cli_credentials *cred);
+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);
+
+/**
+ * Kerberos FAST handling
+ */
+
+NTSTATUS cli_credentials_set_krb5_fast_armor_credentials(struct cli_credentials *creds,
+ struct cli_credentials *armor_creds,
+ bool require_fast_armor);
+
+struct cli_credentials *cli_credentials_get_krb5_fast_armor_credentials(struct cli_credentials *creds);
+
+bool cli_credentials_get_krb5_require_fast_armor(struct cli_credentials *creds);
+
+#endif /* __CREDENTIALS_H__ */
diff --git a/auth/credentials/credentials_cmdline.c b/auth/credentials/credentials_cmdline.c
new file mode 100644
index 0000000..c8c7c18
--- /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)
+{
+ /*
+ * If there is no tty, 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..cda361e
--- /dev/null
+++ b/auth/credentials/credentials_internal.h
@@ -0,0 +1,142 @@
+/*
+ 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 mechanism */
+ 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;
+
+ /* Credentials to use for FAST */
+ struct cli_credentials *krb5_fast_armor_credentials;
+
+ /* Should we require FAST? */
+ bool krb5_require_fast_armor;
+};
+
+#endif /* __CREDENTIALS_INTERNAL_H__ */
diff --git a/auth/credentials/credentials_krb5.c b/auth/credentials/credentials_krb5.c
new file mode 100644
index 0000000..4463401
--- /dev/null
+++ b/auth/credentials/credentials_krb5.c
@@ -0,0 +1,1581 @@
+/*
+ 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));
+ krb5_free_principal(ccache->smb_krb5_context->krb5_context, princ);
+ 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 that 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,
+ lp_ctx,
+ 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 lifetime 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);
+ krb5_free_enctypes(ccache->smb_krb5_context->krb5_context,
+ 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 success, 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 client_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, *armor_credentials;
+ int ret;
+
+ dst = talloc(mem_ctx, struct cli_credentials);
+ if (dst == NULL) {
+ return NULL;
+ }
+
+ *dst = *src;
+
+ if (dst->krb5_fast_armor_credentials != NULL) {
+ armor_credentials = talloc_reference(dst, dst->krb5_fast_armor_credentials);
+ if (armor_credentials == NULL) {
+ TALLOC_FREE(dst);
+ return NULL;
+ }
+ }
+
+ 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 = {
+ .length = 0,
+ };
+ 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\n",
+ 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;
+}
+
+/* This take a reference to the armor credentials to ensure the lifetime is appropriate */
+
+NTSTATUS cli_credentials_set_krb5_fast_armor_credentials(struct cli_credentials *creds,
+ struct cli_credentials *armor_creds,
+ bool require_fast_armor)
+{
+ talloc_unlink(creds, creds->krb5_fast_armor_credentials);
+ if (armor_creds == NULL) {
+ creds->krb5_fast_armor_credentials = NULL;
+ return NT_STATUS_OK;
+ }
+
+ creds->krb5_fast_armor_credentials = talloc_reference(creds, armor_creds);
+ if (creds->krb5_fast_armor_credentials == NULL) {
+ return NT_STATUS_NO_MEMORY;
+ }
+
+ creds->krb5_require_fast_armor = require_fast_armor;
+
+ return NT_STATUS_OK;
+}
+
+struct cli_credentials *cli_credentials_get_krb5_fast_armor_credentials(struct cli_credentials *creds)
+{
+ return creds->krb5_fast_armor_credentials;
+}
+
+bool cli_credentials_get_krb5_require_fast_armor(struct cli_credentials *creds)
+{
+ return creds->krb5_require_fast_armor;
+}
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..a018be1
--- /dev/null
+++ b/auth/credentials/credentials_ntlm.c
@@ -0,0 +1,560 @@
+/*
+ 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 lose 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,
+ &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 lose 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,
+ &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_is_password_nt_hash(struct cli_credentials *cred)
+{
+ return cred->password_will_be_nt_hash;
+}
+
+_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..8469d6e
--- /dev/null
+++ b/auth/credentials/credentials_secrets.c
@@ -0,0 +1,486 @@
+/*
+ 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:
+ case ROLE_IPA_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..a27e02d
--- /dev/null
+++ b/auth/credentials/pycredentials.c
@@ -0,0 +1,1768 @@
+/*
+ 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 "lib/replace/system/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 *PyCredentials_from_cli_credentials(struct cli_credentials *creds)
+{
+ return pytalloc_reference(&PyCredentials, creds);
+}
+
+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, "z", &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_set_nt_hash(PyObject *self, PyObject *args)
+{
+ PyObject *py_cp = Py_None;
+ const struct samr_Password *pwd = NULL;
+ 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, "O|i", &py_cp, &_obt)) {
+ return NULL;
+ }
+ obt = _obt;
+
+ if (!py_check_dcerpc_type(py_cp, "samba.dcerpc.samr", "Password")) {
+ /* py_check_dcerpc_type sets TypeError */
+ return NULL;
+ }
+
+ pwd = pytalloc_get_type(py_cp, struct samr_Password);
+ if (pwd == NULL) {
+ /* pytalloc_get_type sets TypeError */
+ return NULL;
+ }
+
+ return PyBool_FromLong(cli_credentials_set_nt_hash(creds, pwd, obt));
+}
+
+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 (creds->netlogon_creds == NULL) {
+ PyErr_Format(PyExc_ValueError, "NetLogon credentials not set");
+ return NULL;
+ }
+
+ if (!PyArg_ParseTuple(args, "O", &py_cp)) {
+ return NULL;
+ }
+
+ if (!py_check_dcerpc_type(py_cp, "samba.dcerpc.samr", "Password")) {
+ /* py_check_dcerpc_type sets TypeError */
+ 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 PyObject *py_creds_get_krb5_fast_armor_credentials(PyObject *self, PyObject *unused)
+{
+ struct cli_credentials *creds = NULL;
+ struct cli_credentials *fast_creds = NULL;
+
+ creds = PyCredentials_AsCliCredentials(self);
+ if (creds == NULL) {
+ PyErr_Format(PyExc_TypeError, "Credentials expected");
+ return NULL;
+ }
+
+ fast_creds = cli_credentials_get_krb5_fast_armor_credentials(creds);
+ if (fast_creds == NULL) {
+ Py_RETURN_NONE;
+ }
+
+ return PyCredentials_from_cli_credentials(fast_creds);
+}
+
+static PyObject *py_creds_set_krb5_fast_armor_credentials(PyObject *self, PyObject *args)
+{
+ struct cli_credentials *creds = NULL;
+ PyObject *pyfast_creds;
+ struct cli_credentials *fast_creds = NULL;
+ int fast_armor_required = 0;
+ NTSTATUS status;
+
+ creds = PyCredentials_AsCliCredentials(self);
+ if (creds == NULL) {
+ PyErr_Format(PyExc_TypeError, "Credentials expected");
+ return NULL;
+ }
+ if (!PyArg_ParseTuple(args, "Op", &pyfast_creds, &fast_armor_required)) {
+ return NULL;
+ }
+ if (pyfast_creds == Py_None) {
+ fast_creds = NULL;
+ } else {
+ fast_creds = PyCredentials_AsCliCredentials(pyfast_creds);
+ if (fast_creds == NULL) {
+ PyErr_Format(PyExc_TypeError, "Credentials expected");
+ return NULL;
+ }
+ }
+
+ status = cli_credentials_set_krb5_fast_armor_credentials(creds,
+ fast_creds,
+ fast_armor_required);
+
+ PyErr_NTSTATUS_IS_ERR_RAISE(status);
+ Py_RETURN_NONE;
+}
+
+static PyObject *py_creds_get_krb5_require_fast_armor(PyObject *self, PyObject *unused)
+{
+ bool krb5_fast_armor_required;
+ struct cli_credentials *creds = NULL;
+
+ creds = PyCredentials_AsCliCredentials(self);
+ if (creds == NULL) {
+ PyErr_Format(PyExc_TypeError, "Credentials expected");
+ return NULL;
+ }
+
+ krb5_fast_armor_required = cli_credentials_get_krb5_require_fast_armor(creds);
+ return PyBool_FromLong(krb5_fast_armor_required);
+}
+
+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 = "set_nt_hash",
+ .ml_meth = py_creds_set_nt_hash,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = "S.set_net_sh(samr_Password[, credentials.SPECIFIED]) -> bool\n"
+ "Change NT hash.",
+ },
+ {
+ .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 = "get_krb5_fast_armor_credentials",
+ .ml_meth = py_creds_get_krb5_fast_armor_credentials,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = "S.get_krb5_fast_armor_credentials() -> Credentials\n"
+ "Get the Kerberos FAST credentials set on this credentials object"
+ },
+ {
+ .ml_name = "set_krb5_fast_armor_credentials",
+ .ml_meth = py_creds_set_krb5_fast_armor_credentials,
+ .ml_flags = METH_VARARGS,
+ .ml_doc = "S.set_krb5_fast_armor_credentials(credentials, required) -> None\n"
+ "Set Kerberos FAST credentials for this credentials object, and if FAST armoring must be used."
+ },
+ {
+ .ml_name = "get_krb5_require_fast_armor",
+ .ml_meth = py_creds_get_krb5_require_fast_armor,
+ .ml_flags = METH_NOARGS,
+ .ml_doc = "S.get_krb5_fast_armor() -> bool\n"
+ "Indicate if Kerberos FAST armor is required"
+ },
+ { .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);
+ krb5_free_string(ccc->smb_krb5_context->krb5_context, 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..2cb2e6d
--- /dev/null
+++ b/auth/credentials/tests/test_creds.c
@@ -0,0 +1,349 @@
+/*
+ * 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 dom_obtained = CRED_UNINITIALISED;
+ 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");
+
+ domain = cli_credentials_get_domain_and_obtained(creds,
+ &dom_obtained);
+ assert_int_equal(dom_obtained, CRED_SPECIFIED);
+ 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);
+}
+
+static const char *torture_get_password(struct cli_credentials *creds)
+{
+ return talloc_strdup(creds, "SECRET");
+}
+
+static void torture_creds_password_callback(void **state)
+{
+ TALLOC_CTX *mem_ctx = *state;
+ struct cli_credentials *creds = NULL;
+ const char *password = NULL;
+ enum credentials_obtained pwd_obtained = CRED_UNINITIALISED;
+ bool ok;
+
+ creds = cli_credentials_init(mem_ctx);
+ assert_non_null(creds);
+
+ ok = cli_credentials_set_domain(creds, "WURST", CRED_SPECIFIED);
+ assert_true(ok);
+ ok = cli_credentials_set_username(creds, "brot", CRED_SPECIFIED);
+ assert_true(ok);
+
+ ok = cli_credentials_set_password_callback(creds, torture_get_password);
+ assert_true(ok);
+ assert_int_equal(creds->password_obtained, CRED_CALLBACK);
+
+ password = cli_credentials_get_password_and_obtained(creds,
+ &pwd_obtained);
+ assert_int_equal(pwd_obtained, CRED_CALLBACK_RESULT);
+ assert_string_equal(password, "SECRET");
+}
+
+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),
+ cmocka_unit_test(torture_creds_password_callback)
+ };
+
+ 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..83c6e8c
--- /dev/null
+++ b/auth/credentials/wscript_build
@@ -0,0 +1,44 @@
+#!/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')
+
+pyrpc_util = bld.pyembed_libname('pyrpc_util')
+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 %s CREDENTIALS_CMDLINE CREDENTIALS_KRB5 CREDENTIALS_SECRETS' % (pyrpc_util, 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..26b5865
--- /dev/null
+++ b/auth/gensec/gensec.c
@@ -0,0 +1,856 @@
+/*
+ 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,
+ NULL /* client_audit_info */,
+ NULL /* server_audit_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..072188a
--- /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 complex.
+ */
+
+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 SPNEGO 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 so 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 mechanism 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 known 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..b6b4a72
--- /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 mechanism
+ *
+ * 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..86527fe
--- /dev/null
+++ b/auth/gensec/schannel.c
@@ -0,0 +1,1181 @@
+/*
+ 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"
+
+#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) {
+ 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 {
+ 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);
+ ZERO_ARRAY(digest1);
+ if (rc < 0) {
+ return gnutls_error_to_ntstatus(rc, NT_STATUS_HMAC_NOT_SUPPORTED);
+ }
+
+ 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) {
+ 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 {
+ 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_HASH_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_HASH_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_HASH_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_HASH_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_HASH_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_HASH_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 available 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..fcb5a06
--- /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 packet 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..4ad7873
--- /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 uninitialized 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..ae4557b
--- /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 %"PRIu32" is not supported\n",
+ 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_info, 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_info, 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_info, 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 re-encode, 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 lose 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..bf8b05c
--- /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 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..49f47c2
--- /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 omitted */
+ 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..337aeed
--- /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(&params, &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 = &timestamp->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. Fortunately 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..64b9628
--- /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'. Therefore, 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..404129a
--- /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 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 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 occur 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..83b22ad
--- /dev/null
+++ b/auth/wbc_auth_util.c
@@ -0,0 +1,238 @@
+/*
+ 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_DEFAULT_FLAGS;
+ 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_DEFAULT_FLAGS;
+ 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[PRIMARY_USER_SID_INDEX].sid, sizeof(user_sid));
+ memcpy(&group_sid, &info->sids[PRIMARY_GROUP_SID_INDEX].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[PRIMARY_GROUP_SID_INDEX],
+ info->num_sids - 1);
+ if (!NT_STATUS_IS_OK(status)) {
+ TALLOC_FREE(info6);
+ return NULL;
+ }
+
+ status = wbcsids_to_netr_SidAttrArray(&domain_sid,
+ &info->sids[PRIMARY_GROUP_SID_INDEX],
+ 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..33cbed0
--- /dev/null
+++ b/auth/wscript_build
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+bld.SAMBA_SUBSYSTEM('authn_policy',
+ source='authn_policy.c',
+ deps='samba-util')
+
+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')