mirror of
https://github.com/samba-team/samba.git
synced 2024-12-24 21:34:56 +03:00
893b213876
Avoid overriding default ccache for ads operations. Nowadays various samba components may need to use GSSAPI and a default cred cache to perform their tasks. This code was completely overriding the whole process default ccache name, thus altering the current credentials and sometimes hijacking them (or getting preemptively hijaked). By using gss_krb5_import_cred we can instead use a private ccache (necessary sometimes to use a different set of credentials fromt he default cifs/fqdn@realm one, for example when contacting foreign DCs using trust credentials) that does not affect the rest of the process. For the kerberos versions which don't have gss_krb5_import_cred we fallback to temp override of KRB5CCNAME and gss_acquire_cred. Signed-off-by: Alexander Bokovoy <ab@samba.org> Signed-off-by: Günther Deschner <gd@samba.org> Autobuild-User(master): Alexander Bokovoy <ab@samba.org> Autobuild-Date(master): Wed Sep 12 21:18:09 CEST 2012 on sn-devel-104
1330 lines
34 KiB
C
1330 lines
34 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
ads sasl code
|
|
Copyright (C) Andrew Tridgell 2001
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include "../libcli/auth/spnego.h"
|
|
#include "auth/gensec/gensec.h"
|
|
#include "auth_generic.h"
|
|
#include "ads.h"
|
|
#include "smb_krb5.h"
|
|
#include "system/gssapi.h"
|
|
#include "lib/param/loadparm.h"
|
|
|
|
#ifdef HAVE_LDAP
|
|
|
|
static ADS_STATUS ads_sasl_ntlmssp_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len)
|
|
{
|
|
struct gensec_security *gensec_security =
|
|
talloc_get_type_abort(ads->ldap.wrap_private_data,
|
|
struct gensec_security);
|
|
NTSTATUS nt_status;
|
|
DATA_BLOB unwrapped, wrapped;
|
|
TALLOC_CTX *frame = talloc_stackframe();
|
|
|
|
unwrapped = data_blob_const(buf, len);
|
|
|
|
nt_status = gensec_wrap(gensec_security, frame, &unwrapped, &wrapped);
|
|
if (!NT_STATUS_IS_OK(nt_status)) {
|
|
TALLOC_FREE(frame);
|
|
return ADS_ERROR_NT(nt_status);
|
|
}
|
|
|
|
if ((ads->ldap.out.size - 4) < wrapped.length) {
|
|
return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
|
|
}
|
|
|
|
/* copy the wrapped blob to the right location */
|
|
memcpy(ads->ldap.out.buf + 4, wrapped.data, wrapped.length);
|
|
|
|
/* set how many bytes must be written to the underlying socket */
|
|
ads->ldap.out.left = 4 + wrapped.length;
|
|
|
|
TALLOC_FREE(frame);
|
|
|
|
return ADS_SUCCESS;
|
|
}
|
|
|
|
static ADS_STATUS ads_sasl_ntlmssp_unwrap(ADS_STRUCT *ads)
|
|
{
|
|
struct gensec_security *gensec_security =
|
|
talloc_get_type_abort(ads->ldap.wrap_private_data,
|
|
struct gensec_security);
|
|
NTSTATUS nt_status;
|
|
DATA_BLOB unwrapped, wrapped;
|
|
TALLOC_CTX *frame = talloc_stackframe();
|
|
|
|
wrapped = data_blob_const(ads->ldap.in.buf + 4, ads->ldap.in.ofs - 4);
|
|
|
|
nt_status = gensec_unwrap(gensec_security, frame, &wrapped, &unwrapped);
|
|
if (!NT_STATUS_IS_OK(nt_status)) {
|
|
TALLOC_FREE(frame);
|
|
return ADS_ERROR_NT(nt_status);
|
|
}
|
|
|
|
if (wrapped.length < unwrapped.length) {
|
|
TALLOC_FREE(frame);
|
|
return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
|
|
}
|
|
|
|
/* copy the wrapped blob to the right location */
|
|
memcpy(ads->ldap.in.buf + 4, unwrapped.data, unwrapped.length);
|
|
|
|
/* set how many bytes must be written to the underlying socket */
|
|
ads->ldap.in.left = unwrapped.length;
|
|
ads->ldap.in.ofs = 4;
|
|
|
|
TALLOC_FREE(frame);
|
|
|
|
return ADS_SUCCESS;
|
|
}
|
|
|
|
static void ads_sasl_ntlmssp_disconnect(ADS_STRUCT *ads)
|
|
{
|
|
struct gensec_security *gensec_security =
|
|
talloc_get_type_abort(ads->ldap.wrap_private_data,
|
|
struct gensec_security);
|
|
|
|
TALLOC_FREE(gensec_security);
|
|
|
|
ads->ldap.wrap_ops = NULL;
|
|
ads->ldap.wrap_private_data = NULL;
|
|
}
|
|
|
|
static const struct ads_saslwrap_ops ads_sasl_ntlmssp_ops = {
|
|
.name = "ntlmssp",
|
|
.wrap = ads_sasl_ntlmssp_wrap,
|
|
.unwrap = ads_sasl_ntlmssp_unwrap,
|
|
.disconnect = ads_sasl_ntlmssp_disconnect
|
|
};
|
|
|
|
/*
|
|
perform a LDAP/SASL/SPNEGO/NTLMSSP bind (just how many layers can
|
|
we fit on one socket??)
|
|
*/
|
|
static ADS_STATUS ads_sasl_spnego_ntlmssp_bind(ADS_STRUCT *ads)
|
|
{
|
|
DATA_BLOB msg1 = data_blob_null;
|
|
DATA_BLOB blob = data_blob_null;
|
|
DATA_BLOB blob_in = data_blob_null;
|
|
DATA_BLOB blob_out = data_blob_null;
|
|
struct berval cred, *scred = NULL;
|
|
int rc;
|
|
NTSTATUS nt_status;
|
|
ADS_STATUS status;
|
|
int turn = 1;
|
|
|
|
struct auth_generic_state *auth_generic_state;
|
|
|
|
nt_status = auth_generic_client_prepare(NULL, &auth_generic_state);
|
|
if (!NT_STATUS_IS_OK(nt_status)) {
|
|
return ADS_ERROR_NT(nt_status);
|
|
}
|
|
|
|
if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_username(auth_generic_state, ads->auth.user_name))) {
|
|
return ADS_ERROR_NT(nt_status);
|
|
}
|
|
if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_domain(auth_generic_state, ads->auth.realm))) {
|
|
return ADS_ERROR_NT(nt_status);
|
|
}
|
|
if (!NT_STATUS_IS_OK(nt_status = auth_generic_set_password(auth_generic_state, ads->auth.password))) {
|
|
return ADS_ERROR_NT(nt_status);
|
|
}
|
|
|
|
switch (ads->ldap.wrap_type) {
|
|
case ADS_SASLWRAP_TYPE_SEAL:
|
|
gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
|
|
gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SEAL);
|
|
break;
|
|
case ADS_SASLWRAP_TYPE_SIGN:
|
|
if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
|
|
gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
|
|
} else {
|
|
/*
|
|
* windows servers are broken with sign only,
|
|
* so we need to use seal here too
|
|
*/
|
|
gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SIGN);
|
|
gensec_want_feature(auth_generic_state->gensec_security, GENSEC_FEATURE_SEAL);
|
|
ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL;
|
|
}
|
|
break;
|
|
case ADS_SASLWRAP_TYPE_PLAIN:
|
|
break;
|
|
}
|
|
|
|
nt_status = auth_generic_client_start(auth_generic_state, GENSEC_OID_NTLMSSP);
|
|
if (!NT_STATUS_IS_OK(nt_status)) {
|
|
return ADS_ERROR_NT(nt_status);
|
|
}
|
|
|
|
blob_in = data_blob_null;
|
|
|
|
do {
|
|
nt_status = gensec_update(auth_generic_state->gensec_security,
|
|
talloc_tos(), NULL, blob_in, &blob_out);
|
|
data_blob_free(&blob_in);
|
|
if ((NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)
|
|
|| NT_STATUS_IS_OK(nt_status))
|
|
&& blob_out.length) {
|
|
if (turn == 1) {
|
|
const char *OIDs_ntlm[] = {OID_NTLMSSP, NULL};
|
|
/* and wrap it in a SPNEGO wrapper */
|
|
msg1 = spnego_gen_negTokenInit(talloc_tos(),
|
|
OIDs_ntlm, &blob_out, NULL);
|
|
} else {
|
|
/* wrap it in SPNEGO */
|
|
msg1 = spnego_gen_auth(talloc_tos(), blob_out);
|
|
}
|
|
|
|
data_blob_free(&blob_out);
|
|
|
|
cred.bv_val = (char *)msg1.data;
|
|
cred.bv_len = msg1.length;
|
|
scred = NULL;
|
|
rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
|
|
data_blob_free(&msg1);
|
|
if ((rc != LDAP_SASL_BIND_IN_PROGRESS) && (rc != 0)) {
|
|
if (scred) {
|
|
ber_bvfree(scred);
|
|
}
|
|
|
|
TALLOC_FREE(auth_generic_state);
|
|
return ADS_ERROR(rc);
|
|
}
|
|
if (scred) {
|
|
blob = data_blob(scred->bv_val, scred->bv_len);
|
|
ber_bvfree(scred);
|
|
} else {
|
|
blob = data_blob_null;
|
|
}
|
|
|
|
} else {
|
|
|
|
TALLOC_FREE(auth_generic_state);
|
|
data_blob_free(&blob_out);
|
|
return ADS_ERROR_NT(nt_status);
|
|
}
|
|
|
|
if ((turn == 1) &&
|
|
(rc == LDAP_SASL_BIND_IN_PROGRESS)) {
|
|
DATA_BLOB tmp_blob = data_blob_null;
|
|
/* the server might give us back two challenges */
|
|
if (!spnego_parse_challenge(talloc_tos(), blob, &blob_in,
|
|
&tmp_blob)) {
|
|
|
|
TALLOC_FREE(auth_generic_state);
|
|
data_blob_free(&blob);
|
|
DEBUG(3,("Failed to parse challenges\n"));
|
|
return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
|
|
}
|
|
data_blob_free(&tmp_blob);
|
|
} else if (rc == LDAP_SASL_BIND_IN_PROGRESS) {
|
|
if (!spnego_parse_auth_response(talloc_tos(), blob, nt_status, OID_NTLMSSP,
|
|
&blob_in)) {
|
|
|
|
TALLOC_FREE(auth_generic_state);
|
|
data_blob_free(&blob);
|
|
DEBUG(3,("Failed to parse auth response\n"));
|
|
return ADS_ERROR_NT(NT_STATUS_INVALID_PARAMETER);
|
|
}
|
|
}
|
|
data_blob_free(&blob);
|
|
data_blob_free(&blob_out);
|
|
turn++;
|
|
} while (rc == LDAP_SASL_BIND_IN_PROGRESS && !NT_STATUS_IS_OK(nt_status));
|
|
|
|
if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
|
|
uint32_t sig_size = gensec_sig_size(auth_generic_state->gensec_security, 0);
|
|
ads->ldap.out.max_unwrapped = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED - sig_size;
|
|
ads->ldap.out.sig_size = sig_size;
|
|
ads->ldap.in.min_wrapped = ads->ldap.out.sig_size;
|
|
ads->ldap.in.max_wrapped = ADS_SASL_WRAPPING_IN_MAX_WRAPPED;
|
|
status = ads_setup_sasl_wrapping(ads, &ads_sasl_ntlmssp_ops, auth_generic_state->gensec_security);
|
|
if (!ADS_ERR_OK(status)) {
|
|
DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
|
|
ads_errstr(status)));
|
|
TALLOC_FREE(auth_generic_state);
|
|
return status;
|
|
}
|
|
/* Only keep the gensec_security element around long-term */
|
|
talloc_steal(NULL, auth_generic_state->gensec_security);
|
|
}
|
|
TALLOC_FREE(auth_generic_state);
|
|
|
|
return ADS_ERROR(rc);
|
|
}
|
|
|
|
#ifdef HAVE_KRB5
|
|
static ADS_STATUS ads_init_gssapi_cred(ADS_STRUCT *ads, gss_cred_id_t *cred)
|
|
{
|
|
ADS_STATUS status;
|
|
krb5_context kctx;
|
|
krb5_error_code kerr;
|
|
krb5_ccache kccache = NULL;
|
|
uint32_t maj, min;
|
|
|
|
*cred = GSS_C_NO_CREDENTIAL;
|
|
|
|
if (!ads->auth.ccache_name) {
|
|
return ADS_SUCCESS;
|
|
}
|
|
|
|
kerr = krb5_init_context(&kctx);
|
|
if (kerr) {
|
|
return ADS_ERROR_KRB5(kerr);
|
|
}
|
|
|
|
#ifdef HAVE_GSS_KRB5_IMPORT_CRED
|
|
kerr = krb5_cc_resolve(kctx, ads->auth.ccache_name, &kccache);
|
|
if (kerr) {
|
|
status = ADS_ERROR_KRB5(kerr);
|
|
goto done;
|
|
}
|
|
|
|
maj = gss_krb5_import_cred(&min, kccache, NULL, NULL, cred);
|
|
if (maj != GSS_S_COMPLETE) {
|
|
status = ADS_ERROR_GSS(maj, min);
|
|
goto done;
|
|
}
|
|
#else
|
|
/* We need to fallback to overriding the default creds.
|
|
* This operation is not thread safe as it changes the process
|
|
* environment variable, but we do not have any better option
|
|
* with older kerberos libraries */
|
|
{
|
|
const char *oldccname = NULL;
|
|
|
|
oldccname = getenv("KRB5CCNAME");
|
|
setenv("KRB5CCNAME", ads->auth.ccache_name, 1);
|
|
|
|
maj = gss_acquire_cred(&min, GSS_C_NO_NAME, GSS_C_INDEFINITE,
|
|
NULL, GSS_C_INITIATE, cred, NULL, NULL);
|
|
|
|
if (oldccname) {
|
|
setenv("KRB5CCNAME", oldccname, 1);
|
|
} else {
|
|
unsetenv("KRB5CCNAME");
|
|
}
|
|
|
|
if (maj != GSS_S_COMPLETE) {
|
|
status = ADS_ERROR_GSS(maj, min);
|
|
goto done;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
status = ADS_SUCCESS;
|
|
|
|
done:
|
|
if (!ADS_ERR_OK(status) && kccache != NULL) {
|
|
krb5_cc_close(kctx, kccache);
|
|
}
|
|
krb5_free_context(kctx);
|
|
return status;
|
|
}
|
|
|
|
static ADS_STATUS ads_sasl_gssapi_wrap(ADS_STRUCT *ads, uint8 *buf, uint32 len)
|
|
{
|
|
gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data;
|
|
ADS_STATUS status;
|
|
int gss_rc;
|
|
uint32 minor_status;
|
|
gss_buffer_desc unwrapped, wrapped;
|
|
int conf_req_flag, conf_state;
|
|
|
|
unwrapped.value = buf;
|
|
unwrapped.length = len;
|
|
|
|
/* for now request sign and seal */
|
|
conf_req_flag = (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL);
|
|
|
|
gss_rc = gss_wrap(&minor_status, context_handle,
|
|
conf_req_flag, GSS_C_QOP_DEFAULT,
|
|
&unwrapped, &conf_state,
|
|
&wrapped);
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
if (!ADS_ERR_OK(status)) return status;
|
|
|
|
if (conf_req_flag && conf_state == 0) {
|
|
return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED);
|
|
}
|
|
|
|
if ((ads->ldap.out.size - 4) < wrapped.length) {
|
|
return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
|
|
}
|
|
|
|
/* copy the wrapped blob to the right location */
|
|
memcpy(ads->ldap.out.buf + 4, wrapped.value, wrapped.length);
|
|
|
|
/* set how many bytes must be written to the underlying socket */
|
|
ads->ldap.out.left = 4 + wrapped.length;
|
|
|
|
gss_release_buffer(&minor_status, &wrapped);
|
|
|
|
return ADS_SUCCESS;
|
|
}
|
|
|
|
static ADS_STATUS ads_sasl_gssapi_unwrap(ADS_STRUCT *ads)
|
|
{
|
|
gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data;
|
|
ADS_STATUS status;
|
|
int gss_rc;
|
|
uint32 minor_status;
|
|
gss_buffer_desc unwrapped, wrapped;
|
|
int conf_state;
|
|
|
|
wrapped.value = ads->ldap.in.buf + 4;
|
|
wrapped.length = ads->ldap.in.ofs - 4;
|
|
|
|
gss_rc = gss_unwrap(&minor_status, context_handle,
|
|
&wrapped, &unwrapped,
|
|
&conf_state, GSS_C_QOP_DEFAULT);
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
if (!ADS_ERR_OK(status)) return status;
|
|
|
|
if (ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL && conf_state == 0) {
|
|
return ADS_ERROR_NT(NT_STATUS_ACCESS_DENIED);
|
|
}
|
|
|
|
if (wrapped.length < unwrapped.length) {
|
|
return ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
|
|
}
|
|
|
|
/* copy the wrapped blob to the right location */
|
|
memcpy(ads->ldap.in.buf + 4, unwrapped.value, unwrapped.length);
|
|
|
|
/* set how many bytes must be written to the underlying socket */
|
|
ads->ldap.in.left = unwrapped.length;
|
|
ads->ldap.in.ofs = 4;
|
|
|
|
gss_release_buffer(&minor_status, &unwrapped);
|
|
|
|
return ADS_SUCCESS;
|
|
}
|
|
|
|
static void ads_sasl_gssapi_disconnect(ADS_STRUCT *ads)
|
|
{
|
|
gss_ctx_id_t context_handle = (gss_ctx_id_t)ads->ldap.wrap_private_data;
|
|
uint32 minor_status;
|
|
|
|
gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
|
|
|
|
ads->ldap.wrap_ops = NULL;
|
|
ads->ldap.wrap_private_data = NULL;
|
|
}
|
|
|
|
static const struct ads_saslwrap_ops ads_sasl_gssapi_ops = {
|
|
.name = "gssapi",
|
|
.wrap = ads_sasl_gssapi_wrap,
|
|
.unwrap = ads_sasl_gssapi_unwrap,
|
|
.disconnect = ads_sasl_gssapi_disconnect
|
|
};
|
|
|
|
/*
|
|
perform a LDAP/SASL/SPNEGO/GSSKRB5 bind
|
|
*/
|
|
static ADS_STATUS ads_sasl_spnego_gsskrb5_bind(ADS_STRUCT *ads, const gss_name_t serv_name)
|
|
{
|
|
ADS_STATUS status;
|
|
bool ok;
|
|
uint32 minor_status;
|
|
int gss_rc, rc;
|
|
gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
|
|
gss_OID_desc krb5_mech_type =
|
|
{9, discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02") };
|
|
gss_OID mech_type = &krb5_mech_type;
|
|
gss_OID actual_mech_type = GSS_C_NULL_OID;
|
|
const char *spnego_mechs[] = {OID_KERBEROS5_OLD, OID_KERBEROS5, OID_NTLMSSP, NULL};
|
|
gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
|
|
gss_buffer_desc input_token, output_token;
|
|
uint32 req_flags, ret_flags;
|
|
uint32 req_tmp, ret_tmp;
|
|
DATA_BLOB unwrapped;
|
|
DATA_BLOB wrapped;
|
|
struct berval cred, *scred = NULL;
|
|
|
|
status = ads_init_gssapi_cred(ads, &gss_cred);
|
|
if (!ADS_ERR_OK(status)) {
|
|
goto failed;
|
|
}
|
|
|
|
input_token.value = NULL;
|
|
input_token.length = 0;
|
|
|
|
req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG;
|
|
switch (ads->ldap.wrap_type) {
|
|
case ADS_SASLWRAP_TYPE_SEAL:
|
|
req_flags |= GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG;
|
|
break;
|
|
case ADS_SASLWRAP_TYPE_SIGN:
|
|
req_flags |= GSS_C_INTEG_FLAG;
|
|
break;
|
|
case ADS_SASLWRAP_TYPE_PLAIN:
|
|
break;
|
|
}
|
|
|
|
/* Note: here we explicit ask for the krb5 mech_type */
|
|
gss_rc = gss_init_sec_context(&minor_status,
|
|
gss_cred,
|
|
&context_handle,
|
|
serv_name,
|
|
mech_type,
|
|
req_flags,
|
|
0,
|
|
NULL,
|
|
&input_token,
|
|
&actual_mech_type,
|
|
&output_token,
|
|
&ret_flags,
|
|
NULL);
|
|
if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
goto failed;
|
|
}
|
|
|
|
/*
|
|
* As some gssapi krb5 mech implementations
|
|
* automaticly add GSS_C_INTEG_FLAG and GSS_C_CONF_FLAG
|
|
* to req_flags internaly, it's not possible to
|
|
* use plain or signing only connection via
|
|
* the gssapi interface.
|
|
*
|
|
* Because of this we need to check it the ret_flags
|
|
* has more flags as req_flags and correct the value
|
|
* of ads->ldap.wrap_type.
|
|
*
|
|
* I ads->auth.flags has ADS_AUTH_SASL_FORCE
|
|
* we need to give an error.
|
|
*/
|
|
req_tmp = req_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG);
|
|
ret_tmp = ret_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG);
|
|
|
|
if (req_tmp == ret_tmp) {
|
|
/* everythings fine... */
|
|
|
|
} else if (req_flags & GSS_C_CONF_FLAG) {
|
|
/*
|
|
* here we wanted sealing but didn't got it
|
|
* from the gssapi library
|
|
*/
|
|
status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
|
|
goto failed;
|
|
|
|
} else if ((req_flags & GSS_C_INTEG_FLAG) &&
|
|
!(ret_flags & GSS_C_INTEG_FLAG)) {
|
|
/*
|
|
* here we wanted siging but didn't got it
|
|
* from the gssapi library
|
|
*/
|
|
status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
|
|
goto failed;
|
|
|
|
} else if (ret_flags & GSS_C_CONF_FLAG) {
|
|
/*
|
|
* here we didn't want sealing
|
|
* but the gssapi library forces it
|
|
* so correct the needed wrap_type if
|
|
* the caller didn't forced siging only
|
|
*/
|
|
if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
|
|
status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
|
|
goto failed;
|
|
}
|
|
|
|
ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL;
|
|
req_flags = ret_flags;
|
|
|
|
} else if (ret_flags & GSS_C_INTEG_FLAG) {
|
|
/*
|
|
* here we didn't want signing
|
|
* but the gssapi library forces it
|
|
* so correct the needed wrap_type if
|
|
* the caller didn't forced plain
|
|
*/
|
|
if (ads->auth.flags & ADS_AUTH_SASL_FORCE) {
|
|
status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
|
|
goto failed;
|
|
}
|
|
|
|
ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN;
|
|
req_flags = ret_flags;
|
|
} else {
|
|
/*
|
|
* This could (should?) not happen
|
|
*/
|
|
status = ADS_ERROR_NT(NT_STATUS_INTERNAL_ERROR);
|
|
goto failed;
|
|
|
|
}
|
|
|
|
/* and wrap that in a shiny SPNEGO wrapper */
|
|
unwrapped = data_blob_const(output_token.value, output_token.length);
|
|
wrapped = spnego_gen_negTokenInit(talloc_tos(),
|
|
spnego_mechs, &unwrapped, NULL);
|
|
gss_release_buffer(&minor_status, &output_token);
|
|
if (unwrapped.length > wrapped.length) {
|
|
status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
|
|
goto failed;
|
|
}
|
|
|
|
cred.bv_val = (char *)wrapped.data;
|
|
cred.bv_len = wrapped.length;
|
|
|
|
rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL,
|
|
&scred);
|
|
data_blob_free(&wrapped);
|
|
if (rc != LDAP_SUCCESS) {
|
|
status = ADS_ERROR(rc);
|
|
goto failed;
|
|
}
|
|
|
|
if (scred) {
|
|
wrapped = data_blob_const(scred->bv_val, scred->bv_len);
|
|
} else {
|
|
wrapped = data_blob_null;
|
|
}
|
|
|
|
ok = spnego_parse_auth_response(talloc_tos(), wrapped, NT_STATUS_OK,
|
|
OID_KERBEROS5_OLD,
|
|
&unwrapped);
|
|
if (scred) ber_bvfree(scred);
|
|
if (!ok) {
|
|
status = ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
|
|
goto failed;
|
|
}
|
|
|
|
input_token.value = unwrapped.data;
|
|
input_token.length = unwrapped.length;
|
|
|
|
/*
|
|
* As we asked for mutal authentication
|
|
* we need to pass the servers response
|
|
* to gssapi
|
|
*/
|
|
gss_rc = gss_init_sec_context(&minor_status,
|
|
gss_cred,
|
|
&context_handle,
|
|
serv_name,
|
|
mech_type,
|
|
req_flags,
|
|
0,
|
|
NULL,
|
|
&input_token,
|
|
&actual_mech_type,
|
|
&output_token,
|
|
&ret_flags,
|
|
NULL);
|
|
data_blob_free(&unwrapped);
|
|
if (gss_rc) {
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
goto failed;
|
|
}
|
|
|
|
gss_release_buffer(&minor_status, &output_token);
|
|
|
|
/*
|
|
* If we the sign and seal options
|
|
* doesn't match after getting the response
|
|
* from the server, we don't want to use the connection
|
|
*/
|
|
req_tmp = req_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG);
|
|
ret_tmp = ret_flags & (GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG);
|
|
|
|
if (req_tmp != ret_tmp) {
|
|
/* everythings fine... */
|
|
status = ADS_ERROR_NT(NT_STATUS_INVALID_NETWORK_RESPONSE);
|
|
goto failed;
|
|
}
|
|
|
|
if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
|
|
uint32 max_msg_size = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED;
|
|
|
|
gss_rc = gss_wrap_size_limit(&minor_status, context_handle,
|
|
(ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL),
|
|
GSS_C_QOP_DEFAULT,
|
|
max_msg_size, &ads->ldap.out.max_unwrapped);
|
|
if (gss_rc) {
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
goto failed;
|
|
}
|
|
|
|
ads->ldap.out.sig_size = max_msg_size - ads->ldap.out.max_unwrapped;
|
|
ads->ldap.in.min_wrapped = 0x2C; /* taken from a capture with LDAP unbind */
|
|
ads->ldap.in.max_wrapped = max_msg_size;
|
|
status = ads_setup_sasl_wrapping(ads, &ads_sasl_gssapi_ops, context_handle);
|
|
if (!ADS_ERR_OK(status)) {
|
|
DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
|
|
ads_errstr(status)));
|
|
goto failed;
|
|
}
|
|
/* make sure we don't free context_handle */
|
|
context_handle = GSS_C_NO_CONTEXT;
|
|
}
|
|
|
|
status = ADS_SUCCESS;
|
|
|
|
failed:
|
|
if (gss_cred != GSS_C_NO_CREDENTIAL)
|
|
gss_release_cred(&minor_status, &gss_cred);
|
|
if (context_handle != GSS_C_NO_CONTEXT)
|
|
gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
|
|
return status;
|
|
}
|
|
|
|
#endif /* HAVE_KRB5 */
|
|
|
|
#ifdef HAVE_KRB5
|
|
struct ads_service_principal {
|
|
char *string;
|
|
#ifdef HAVE_KRB5
|
|
gss_name_t name;
|
|
#endif
|
|
};
|
|
|
|
static void ads_free_service_principal(struct ads_service_principal *p)
|
|
{
|
|
SAFE_FREE(p->string);
|
|
|
|
#ifdef HAVE_KRB5
|
|
if (p->name) {
|
|
uint32 minor_status;
|
|
gss_release_name(&minor_status, &p->name);
|
|
}
|
|
#endif
|
|
ZERO_STRUCTP(p);
|
|
}
|
|
|
|
|
|
static ADS_STATUS ads_guess_service_principal(ADS_STRUCT *ads,
|
|
char **returned_principal)
|
|
{
|
|
char *princ = NULL;
|
|
|
|
if (ads->server.realm && ads->server.ldap_server) {
|
|
char *server, *server_realm;
|
|
|
|
server = SMB_STRDUP(ads->server.ldap_server);
|
|
server_realm = SMB_STRDUP(ads->server.realm);
|
|
|
|
if (!server || !server_realm) {
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
|
|
if (!strlower_m(server)) {
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
|
|
if (!strupper_m(server_realm)) {
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
|
|
if (asprintf(&princ, "ldap/%s@%s", server, server_realm) == -1) {
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
|
|
if (!princ) {
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
} else if (ads->config.realm && ads->config.ldap_server_name) {
|
|
char *server, *server_realm;
|
|
|
|
server = SMB_STRDUP(ads->config.ldap_server_name);
|
|
server_realm = SMB_STRDUP(ads->config.realm);
|
|
|
|
if (!server || !server_realm) {
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
|
|
if (!strlower_m(server)) {
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
|
|
if (!strupper_m(server_realm)) {
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
if (asprintf(&princ, "ldap/%s@%s", server, server_realm) == -1) {
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
|
|
SAFE_FREE(server);
|
|
SAFE_FREE(server_realm);
|
|
|
|
if (!princ) {
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
}
|
|
|
|
if (!princ) {
|
|
return ADS_ERROR(LDAP_PARAM_ERROR);
|
|
}
|
|
|
|
*returned_principal = princ;
|
|
|
|
return ADS_SUCCESS;
|
|
}
|
|
|
|
static ADS_STATUS ads_generate_service_principal(ADS_STRUCT *ads,
|
|
const char *given_principal,
|
|
struct ads_service_principal *p)
|
|
{
|
|
ADS_STATUS status;
|
|
#ifdef HAVE_KRB5
|
|
gss_buffer_desc input_name;
|
|
/* GSS_KRB5_NT_PRINCIPAL_NAME */
|
|
gss_OID_desc nt_principal =
|
|
{10, discard_const_p(char, "\x2a\x86\x48\x86\xf7\x12\x01\x02\x02\x01")};
|
|
uint32 minor_status;
|
|
int gss_rc;
|
|
#endif
|
|
|
|
ZERO_STRUCTP(p);
|
|
|
|
/* I've seen a child Windows 2000 domain not send
|
|
the principal name back in the first round of
|
|
the SASL bind reply. So we guess based on server
|
|
name and realm. --jerry */
|
|
/* Also try best guess when we get the w2k8 ignore principal
|
|
back, or when we are configured to ignore it - gd,
|
|
abartlet */
|
|
|
|
if (!lp_client_use_spnego_principal() ||
|
|
!given_principal ||
|
|
strequal(given_principal, ADS_IGNORE_PRINCIPAL)) {
|
|
|
|
status = ads_guess_service_principal(ads, &p->string);
|
|
if (!ADS_ERR_OK(status)) {
|
|
return status;
|
|
}
|
|
} else {
|
|
p->string = SMB_STRDUP(given_principal);
|
|
if (!p->string) {
|
|
return ADS_ERROR(LDAP_NO_MEMORY);
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_KRB5
|
|
input_name.value = p->string;
|
|
input_name.length = strlen(p->string);
|
|
|
|
gss_rc = gss_import_name(&minor_status, &input_name, &nt_principal, &p->name);
|
|
if (gss_rc) {
|
|
ads_free_service_principal(p);
|
|
return ADS_ERROR_GSS(gss_rc, minor_status);
|
|
}
|
|
#endif
|
|
|
|
return ADS_SUCCESS;
|
|
}
|
|
|
|
/*
|
|
perform a LDAP/SASL/SPNEGO/KRB5 bind
|
|
*/
|
|
static ADS_STATUS ads_sasl_spnego_rawkrb5_bind(ADS_STRUCT *ads, const char *principal)
|
|
{
|
|
DATA_BLOB blob = data_blob_null;
|
|
struct berval cred, *scred = NULL;
|
|
DATA_BLOB session_key = data_blob_null;
|
|
int rc;
|
|
|
|
if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
|
|
return ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
|
|
}
|
|
|
|
rc = spnego_gen_krb5_negTokenInit(talloc_tos(), principal,
|
|
ads->auth.time_offset, &blob, &session_key, 0,
|
|
ads->auth.ccache_name,
|
|
&ads->auth.tgs_expire);
|
|
|
|
if (rc) {
|
|
return ADS_ERROR_KRB5(rc);
|
|
}
|
|
|
|
/* now send the auth packet and we should be done */
|
|
cred.bv_val = (char *)blob.data;
|
|
cred.bv_len = blob.length;
|
|
|
|
rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", &cred, NULL, NULL, &scred);
|
|
|
|
data_blob_free(&blob);
|
|
data_blob_free(&session_key);
|
|
if(scred)
|
|
ber_bvfree(scred);
|
|
|
|
return ADS_ERROR(rc);
|
|
}
|
|
|
|
static ADS_STATUS ads_sasl_spnego_krb5_bind(ADS_STRUCT *ads,
|
|
struct ads_service_principal *p)
|
|
{
|
|
#ifdef HAVE_KRB5
|
|
/*
|
|
* we only use the gsskrb5 based implementation
|
|
* when sasl sign or seal is requested.
|
|
*
|
|
* This has the following reasons:
|
|
* - it's likely that the gssapi krb5 mech implementation
|
|
* doesn't support to negotiate plain connections
|
|
* - the ads_sasl_spnego_rawkrb5_bind is more robust
|
|
* against clock skew errors
|
|
*/
|
|
if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
|
|
return ads_sasl_spnego_gsskrb5_bind(ads, p->name);
|
|
}
|
|
#endif
|
|
return ads_sasl_spnego_rawkrb5_bind(ads, p->string);
|
|
}
|
|
#endif /* HAVE_KRB5 */
|
|
|
|
/*
|
|
this performs a SASL/SPNEGO bind
|
|
*/
|
|
static ADS_STATUS ads_sasl_spnego_bind(ADS_STRUCT *ads)
|
|
{
|
|
struct berval *scred=NULL;
|
|
int rc, i;
|
|
ADS_STATUS status;
|
|
DATA_BLOB blob;
|
|
char *given_principal = NULL;
|
|
char *OIDs[ASN1_MAX_OIDS];
|
|
#ifdef HAVE_KRB5
|
|
bool got_kerberos_mechanism = False;
|
|
#endif
|
|
|
|
rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSS-SPNEGO", NULL, NULL, NULL, &scred);
|
|
|
|
if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
|
|
status = ADS_ERROR(rc);
|
|
goto failed;
|
|
}
|
|
|
|
blob = data_blob(scred->bv_val, scred->bv_len);
|
|
|
|
ber_bvfree(scred);
|
|
|
|
#if 0
|
|
file_save("sasl_spnego.dat", blob.data, blob.length);
|
|
#endif
|
|
|
|
/* the server sent us the first part of the SPNEGO exchange in the negprot
|
|
reply */
|
|
if (!spnego_parse_negTokenInit(talloc_tos(), blob, OIDs, &given_principal, NULL) ||
|
|
OIDs[0] == NULL) {
|
|
data_blob_free(&blob);
|
|
status = ADS_ERROR(LDAP_OPERATIONS_ERROR);
|
|
goto failed;
|
|
}
|
|
data_blob_free(&blob);
|
|
|
|
/* make sure the server understands kerberos */
|
|
for (i=0;OIDs[i];i++) {
|
|
DEBUG(3,("ads_sasl_spnego_bind: got OID=%s\n", OIDs[i]));
|
|
#ifdef HAVE_KRB5
|
|
if (strcmp(OIDs[i], OID_KERBEROS5_OLD) == 0 ||
|
|
strcmp(OIDs[i], OID_KERBEROS5) == 0) {
|
|
got_kerberos_mechanism = True;
|
|
}
|
|
#endif
|
|
talloc_free(OIDs[i]);
|
|
}
|
|
DEBUG(3,("ads_sasl_spnego_bind: got server principal name = %s\n", given_principal));
|
|
|
|
#ifdef HAVE_KRB5
|
|
if (!(ads->auth.flags & ADS_AUTH_DISABLE_KERBEROS) &&
|
|
got_kerberos_mechanism)
|
|
{
|
|
struct ads_service_principal p;
|
|
|
|
status = ads_generate_service_principal(ads, given_principal, &p);
|
|
TALLOC_FREE(given_principal);
|
|
if (!ADS_ERR_OK(status)) {
|
|
return status;
|
|
}
|
|
|
|
status = ads_sasl_spnego_krb5_bind(ads, &p);
|
|
if (ADS_ERR_OK(status)) {
|
|
ads_free_service_principal(&p);
|
|
return status;
|
|
}
|
|
|
|
DEBUG(10,("ads_sasl_spnego_krb5_bind failed with: %s, "
|
|
"calling kinit\n", ads_errstr(status)));
|
|
|
|
status = ADS_ERROR_KRB5(ads_kinit_password(ads));
|
|
|
|
if (ADS_ERR_OK(status)) {
|
|
status = ads_sasl_spnego_krb5_bind(ads, &p);
|
|
if (!ADS_ERR_OK(status)) {
|
|
DEBUG(0,("kinit succeeded but "
|
|
"ads_sasl_spnego_krb5_bind failed: %s\n",
|
|
ads_errstr(status)));
|
|
}
|
|
}
|
|
|
|
ads_free_service_principal(&p);
|
|
|
|
/* only fallback to NTLMSSP if allowed */
|
|
if (ADS_ERR_OK(status) ||
|
|
!(ads->auth.flags & ADS_AUTH_ALLOW_NTLMSSP)) {
|
|
return status;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
TALLOC_FREE(given_principal);
|
|
}
|
|
|
|
/* lets do NTLMSSP ... this has the big advantage that we don't need
|
|
to sync clocks, and we don't rely on special versions of the krb5
|
|
library for HMAC_MD4 encryption */
|
|
return ads_sasl_spnego_ntlmssp_bind(ads);
|
|
|
|
failed:
|
|
return status;
|
|
}
|
|
|
|
#ifdef HAVE_KRB5
|
|
#define MAX_GSS_PASSES 3
|
|
|
|
/* this performs a SASL/gssapi bind
|
|
we avoid using cyrus-sasl to make Samba more robust. cyrus-sasl
|
|
is very dependent on correctly configured DNS whereas
|
|
this routine is much less fragile
|
|
see RFC2078 and RFC2222 for details
|
|
*/
|
|
static ADS_STATUS ads_sasl_gssapi_do_bind(ADS_STRUCT *ads, const gss_name_t serv_name)
|
|
{
|
|
uint32 minor_status;
|
|
gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL;
|
|
gss_ctx_id_t context_handle = GSS_C_NO_CONTEXT;
|
|
gss_OID mech_type = GSS_C_NULL_OID;
|
|
gss_buffer_desc output_token, input_token;
|
|
uint32 req_flags, ret_flags;
|
|
int conf_state;
|
|
struct berval cred;
|
|
struct berval *scred = NULL;
|
|
int i=0;
|
|
int gss_rc, rc;
|
|
uint8 *p;
|
|
uint32 max_msg_size = ADS_SASL_WRAPPING_OUT_MAX_WRAPPED;
|
|
uint8 wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
|
|
ADS_STATUS status;
|
|
|
|
input_token.value = NULL;
|
|
input_token.length = 0;
|
|
|
|
status = ads_init_gssapi_cred(ads, &gss_cred);
|
|
if (!ADS_ERR_OK(status)) {
|
|
goto failed;
|
|
}
|
|
|
|
/*
|
|
* Note: here we always ask the gssapi for sign and seal
|
|
* as this is negotiated later after the mutal
|
|
* authentication
|
|
*/
|
|
req_flags = GSS_C_MUTUAL_FLAG | GSS_C_REPLAY_FLAG | GSS_C_INTEG_FLAG | GSS_C_CONF_FLAG;
|
|
|
|
for (i=0; i < MAX_GSS_PASSES; i++) {
|
|
gss_rc = gss_init_sec_context(&minor_status,
|
|
gss_cred,
|
|
&context_handle,
|
|
serv_name,
|
|
mech_type,
|
|
req_flags,
|
|
0,
|
|
NULL,
|
|
&input_token,
|
|
NULL,
|
|
&output_token,
|
|
&ret_flags,
|
|
NULL);
|
|
if (scred) {
|
|
ber_bvfree(scred);
|
|
scred = NULL;
|
|
}
|
|
if (gss_rc && gss_rc != GSS_S_CONTINUE_NEEDED) {
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
goto failed;
|
|
}
|
|
|
|
cred.bv_val = (char *)output_token.value;
|
|
cred.bv_len = output_token.length;
|
|
|
|
rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSSAPI", &cred, NULL, NULL,
|
|
&scred);
|
|
if (rc != LDAP_SASL_BIND_IN_PROGRESS) {
|
|
status = ADS_ERROR(rc);
|
|
goto failed;
|
|
}
|
|
|
|
if (output_token.value) {
|
|
gss_release_buffer(&minor_status, &output_token);
|
|
}
|
|
|
|
if (scred) {
|
|
input_token.value = scred->bv_val;
|
|
input_token.length = scred->bv_len;
|
|
} else {
|
|
input_token.value = NULL;
|
|
input_token.length = 0;
|
|
}
|
|
|
|
if (gss_rc == 0) break;
|
|
}
|
|
|
|
gss_rc = gss_unwrap(&minor_status,context_handle,&input_token,&output_token,
|
|
&conf_state,NULL);
|
|
if (scred) {
|
|
ber_bvfree(scred);
|
|
scred = NULL;
|
|
}
|
|
if (gss_rc) {
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
goto failed;
|
|
}
|
|
|
|
p = (uint8 *)output_token.value;
|
|
|
|
#if 0
|
|
file_save("sasl_gssapi.dat", output_token.value, output_token.length);
|
|
#endif
|
|
|
|
if (p) {
|
|
wrap_type = CVAL(p,0);
|
|
SCVAL(p,0,0);
|
|
max_msg_size = RIVAL(p,0);
|
|
}
|
|
|
|
gss_release_buffer(&minor_status, &output_token);
|
|
|
|
if (!(wrap_type & ads->ldap.wrap_type)) {
|
|
/*
|
|
* the server doesn't supports the wrap
|
|
* type we want :-(
|
|
*/
|
|
DEBUG(0,("The ldap sasl wrap type doesn't match wanted[%d] server[%d]\n",
|
|
ads->ldap.wrap_type, wrap_type));
|
|
DEBUGADD(0,("You may want to set the 'client ldap sasl wrapping' option\n"));
|
|
status = ADS_ERROR_NT(NT_STATUS_NOT_SUPPORTED);
|
|
goto failed;
|
|
}
|
|
|
|
/* 0x58 is the minimum windows accepts */
|
|
if (max_msg_size < 0x58) {
|
|
max_msg_size = 0x58;
|
|
}
|
|
|
|
output_token.length = 4;
|
|
output_token.value = SMB_MALLOC(output_token.length);
|
|
if (!output_token.value) {
|
|
output_token.length = 0;
|
|
status = ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
|
|
goto failed;
|
|
}
|
|
p = (uint8 *)output_token.value;
|
|
|
|
RSIVAL(p,0,max_msg_size);
|
|
SCVAL(p,0,ads->ldap.wrap_type);
|
|
|
|
/*
|
|
* we used to add sprintf("dn:%s", ads->config.bind_path) here.
|
|
* but using ads->config.bind_path is the wrong! It should be
|
|
* the DN of the user object!
|
|
*
|
|
* w2k3 gives an error when we send an incorrect DN, but sending nothing
|
|
* is ok and matches the information flow used in GSS-SPNEGO.
|
|
*/
|
|
|
|
gss_rc = gss_wrap(&minor_status, context_handle,0,GSS_C_QOP_DEFAULT,
|
|
&output_token, /* used as *input* here. */
|
|
&conf_state,
|
|
&input_token); /* Used as *output* here. */
|
|
if (gss_rc) {
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
output_token.length = 0;
|
|
SAFE_FREE(output_token.value);
|
|
goto failed;
|
|
}
|
|
|
|
/* We've finished with output_token. */
|
|
SAFE_FREE(output_token.value);
|
|
output_token.length = 0;
|
|
|
|
cred.bv_val = (char *)input_token.value;
|
|
cred.bv_len = input_token.length;
|
|
|
|
rc = ldap_sasl_bind_s(ads->ldap.ld, NULL, "GSSAPI", &cred, NULL, NULL,
|
|
&scred);
|
|
gss_release_buffer(&minor_status, &input_token);
|
|
status = ADS_ERROR(rc);
|
|
if (!ADS_ERR_OK(status)) {
|
|
goto failed;
|
|
}
|
|
|
|
if (ads->ldap.wrap_type > ADS_SASLWRAP_TYPE_PLAIN) {
|
|
gss_rc = gss_wrap_size_limit(&minor_status, context_handle,
|
|
(ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_SEAL),
|
|
GSS_C_QOP_DEFAULT,
|
|
max_msg_size, &ads->ldap.out.max_unwrapped);
|
|
if (gss_rc) {
|
|
status = ADS_ERROR_GSS(gss_rc, minor_status);
|
|
goto failed;
|
|
}
|
|
|
|
ads->ldap.out.sig_size = max_msg_size - ads->ldap.out.max_unwrapped;
|
|
ads->ldap.in.min_wrapped = 0x2C; /* taken from a capture with LDAP unbind */
|
|
ads->ldap.in.max_wrapped = max_msg_size;
|
|
status = ads_setup_sasl_wrapping(ads, &ads_sasl_gssapi_ops, context_handle);
|
|
if (!ADS_ERR_OK(status)) {
|
|
DEBUG(0, ("ads_setup_sasl_wrapping() failed: %s\n",
|
|
ads_errstr(status)));
|
|
goto failed;
|
|
}
|
|
/* make sure we don't free context_handle */
|
|
context_handle = GSS_C_NO_CONTEXT;
|
|
}
|
|
|
|
failed:
|
|
if (gss_cred != GSS_C_NO_CREDENTIAL)
|
|
gss_release_cred(&minor_status, &gss_cred);
|
|
if (context_handle != GSS_C_NO_CONTEXT)
|
|
gss_delete_sec_context(&minor_status, &context_handle, GSS_C_NO_BUFFER);
|
|
|
|
if(scred)
|
|
ber_bvfree(scred);
|
|
return status;
|
|
}
|
|
|
|
static ADS_STATUS ads_sasl_gssapi_bind(ADS_STRUCT *ads)
|
|
{
|
|
ADS_STATUS status;
|
|
struct ads_service_principal p;
|
|
|
|
status = ads_generate_service_principal(ads, NULL, &p);
|
|
if (!ADS_ERR_OK(status)) {
|
|
return status;
|
|
}
|
|
|
|
status = ads_sasl_gssapi_do_bind(ads, p.name);
|
|
if (ADS_ERR_OK(status)) {
|
|
ads_free_service_principal(&p);
|
|
return status;
|
|
}
|
|
|
|
DEBUG(10,("ads_sasl_gssapi_do_bind failed with: %s, "
|
|
"calling kinit\n", ads_errstr(status)));
|
|
|
|
status = ADS_ERROR_KRB5(ads_kinit_password(ads));
|
|
|
|
if (ADS_ERR_OK(status)) {
|
|
status = ads_sasl_gssapi_do_bind(ads, p.name);
|
|
}
|
|
|
|
ads_free_service_principal(&p);
|
|
|
|
return status;
|
|
}
|
|
|
|
#endif /* HAVE_KRB5 */
|
|
|
|
/* mapping between SASL mechanisms and functions */
|
|
static struct {
|
|
const char *name;
|
|
ADS_STATUS (*fn)(ADS_STRUCT *);
|
|
} sasl_mechanisms[] = {
|
|
{"GSS-SPNEGO", ads_sasl_spnego_bind},
|
|
#ifdef HAVE_KRB5
|
|
{"GSSAPI", ads_sasl_gssapi_bind}, /* doesn't work with .NET RC1. No idea why */
|
|
#endif
|
|
{NULL, NULL}
|
|
};
|
|
|
|
ADS_STATUS ads_sasl_bind(ADS_STRUCT *ads)
|
|
{
|
|
const char *attrs[] = {"supportedSASLMechanisms", NULL};
|
|
char **values;
|
|
ADS_STATUS status;
|
|
int i, j;
|
|
LDAPMessage *res;
|
|
|
|
/* get a list of supported SASL mechanisms */
|
|
status = ads_do_search(ads, "", LDAP_SCOPE_BASE, "(objectclass=*)", attrs, &res);
|
|
if (!ADS_ERR_OK(status)) return status;
|
|
|
|
values = ldap_get_values(ads->ldap.ld, res, "supportedSASLMechanisms");
|
|
|
|
if (ads->auth.flags & ADS_AUTH_SASL_SEAL) {
|
|
ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SEAL;
|
|
} else if (ads->auth.flags & ADS_AUTH_SASL_SIGN) {
|
|
ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN;
|
|
} else {
|
|
ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_PLAIN;
|
|
}
|
|
|
|
/* try our supported mechanisms in order */
|
|
for (i=0;sasl_mechanisms[i].name;i++) {
|
|
/* see if the server supports it */
|
|
for (j=0;values && values[j];j++) {
|
|
if (strcmp(values[j], sasl_mechanisms[i].name) == 0) {
|
|
DEBUG(4,("Found SASL mechanism %s\n", values[j]));
|
|
retry:
|
|
status = sasl_mechanisms[i].fn(ads);
|
|
if (status.error_type == ENUM_ADS_ERROR_LDAP &&
|
|
status.err.rc == LDAP_STRONG_AUTH_REQUIRED &&
|
|
ads->ldap.wrap_type == ADS_SASLWRAP_TYPE_PLAIN)
|
|
{
|
|
DEBUG(3,("SASL bin got LDAP_STRONG_AUTH_REQUIRED "
|
|
"retrying with signing enabled\n"));
|
|
ads->ldap.wrap_type = ADS_SASLWRAP_TYPE_SIGN;
|
|
goto retry;
|
|
}
|
|
ldap_value_free(values);
|
|
ldap_msgfree(res);
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
|
|
ldap_value_free(values);
|
|
ldap_msgfree(res);
|
|
return ADS_ERROR(LDAP_AUTH_METHOD_NOT_SUPPORTED);
|
|
}
|
|
|
|
#endif /* HAVE_LDAP */
|
|
|