#include "auth/credentials/credentials.h"
#include "auth/gensec/gensec.h"
#include "auth/gensec/gensec_proto.h"
+#include "auth/gensec/gensec_toplevel_proto.h"
#include "param/param.h"
+#include "lib/util/asn1.h"
+
+_PUBLIC_ NTSTATUS gensec_spnego_init(void);
enum spnego_state_position {
SPNEGO_SERVER_START,
const char *neg_oid;
DATA_BLOB mech_types;
+
+ /*
+ * 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;
};
{
struct spnego_state *spnego_state;
- spnego_state = talloc(gensec_security, struct spnego_state);
+ spnego_state = talloc_zero(gensec_security, struct spnego_state);
if (!spnego_state) {
return NT_STATUS_NO_MEMORY;
}
spnego_state->sub_sec_security = NULL;
spnego_state->no_response_expected = false;
spnego_state->mech_types = data_blob(NULL, 0);
+ spnego_state->out_max_length = gensec_max_update_size(gensec_security);
+ spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
gensec_security->private_data = spnego_state;
return NT_STATUS_OK;
{
struct spnego_state *spnego_state;
- spnego_state = talloc(gensec_security, struct spnego_state);
+ spnego_state = talloc_zero(gensec_security, struct spnego_state);
if (!spnego_state) {
return NT_STATUS_NO_MEMORY;
}
spnego_state->sub_sec_security = NULL;
spnego_state->no_response_expected = false;
spnego_state->mech_types = data_blob(NULL, 0);
+ spnego_state->out_max_length = gensec_max_update_size(gensec_security);
+ spnego_state->out_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
gensec_security->private_data = spnego_state;
return NT_STATUS_OK;
wrappers for the spnego_*() functions
*/
static NTSTATUS gensec_spnego_unseal_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,
const DATA_BLOB *sig)
}
return gensec_unseal_packet(spnego_state->sub_sec_security,
- mem_ctx,
data, length,
whole_pdu, pdu_length,
sig);
}
static NTSTATUS gensec_spnego_check_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,
const DATA_BLOB *sig)
}
return gensec_check_packet(spnego_state->sub_sec_security,
- mem_ctx,
data, length,
whole_pdu, pdu_length,
sig);
}
static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security,
+ TALLOC_CTX *mem_ctx,
DATA_BLOB *session_key)
{
struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
}
return gensec_session_key(spnego_state->sub_sec_security,
+ mem_ctx,
session_key);
}
static NTSTATUS gensec_spnego_session_info(struct gensec_security *gensec_security,
- struct auth_session_info **session_info)
+ TALLOC_CTX *mem_ctx,
+ struct auth_session_info **session_info)
{
struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
if (!spnego_state->sub_sec_security) {
}
return gensec_session_info(spnego_state->sub_sec_security,
+ mem_ctx,
session_info);
}
static NTSTATUS gensec_spnego_server_try_fallback(struct gensec_security *gensec_security,
struct spnego_state *spnego_state,
+ struct tevent_context *ev,
TALLOC_CTX *out_mem_ctx,
const DATA_BLOB in, DATA_BLOB *out)
{
return nt_status;
}
nt_status = gensec_update(spnego_state->sub_sec_security,
- out_mem_ctx, in, out);
+ ev, out_mem_ctx, in, out);
return nt_status;
}
DEBUG(1, ("Failed to parse SPNEGO request\n"));
static NTSTATUS gensec_spnego_parse_negTokenInit(struct gensec_security *gensec_security,
struct spnego_state *spnego_state,
TALLOC_CTX *out_mem_ctx,
+ struct tevent_context *ev,
const char **mechType,
const DATA_BLOB unwrapped_in, DATA_BLOB *unwrapped_out)
{
}
if (spnego_state->state_position == SPNEGO_SERVER_START) {
- for (i=0; all_sec && all_sec[i].op; i++) {
- /* optomisitic token */
- if (strcmp(all_sec[i].oid, mechType[0]) == 0) {
+ uint32_t j;
+ for (j=0; mechType && mechType[j]; j++) {
+ for (i=0; all_sec && all_sec[i].op; i++) {
+ if (strcmp(mechType[j], all_sec[i].oid) != 0) {
+ continue;
+ }
+
nt_status = gensec_subcontext_start(spnego_state,
gensec_security,
&spnego_state->sub_sec_security);
spnego_state->sub_sec_security = NULL;
break;
}
-
+
+ if (j > 0) {
+ /* no optimistic token */
+ spnego_state->neg_oid = all_sec[i].oid;
+ *unwrapped_out = data_blob_null;
+ nt_status = NT_STATUS_MORE_PROCESSING_REQUIRED;
+ break;
+ }
+
nt_status = gensec_update(spnego_state->sub_sec_security,
out_mem_ctx,
+ ev,
unwrapped_in,
unwrapped_out);
if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) ||
spnego_state->neg_oid = all_sec[i].oid;
break;
}
+ if (spnego_state->sub_sec_security) {
+ break;
+ }
+ }
+
+ if (!spnego_state->sub_sec_security) {
+ DEBUG(1, ("SPNEGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
+ return NT_STATUS_INVALID_PARAMETER;
}
}
- /* Having tried any optomisitc token from the client (if we
+ /* Having tried any optimistic token from the client (if we
* were the server), if we didn't get anywhere, walk our list
* in our preference order */
/* only get the helping start blob for the first OID */
nt_status = gensec_update(spnego_state->sub_sec_security,
out_mem_ctx,
+ ev,
null_data_blob,
unwrapped_out);
* of this mech */
if (spnego_state->state_position != SPNEGO_SERVER_START) {
if (NT_STATUS_EQUAL(nt_status, NT_STATUS_INVALID_PARAMETER) ||
+ NT_STATUS_EQUAL(nt_status, NT_STATUS_NO_LOGON_SERVERS) ||
+ NT_STATUS_EQUAL(nt_status, NT_STATUS_TIME_DIFFERENCE_AT_DC) ||
NT_STATUS_EQUAL(nt_status, NT_STATUS_CANT_ACCESS_DOMAIN_INFO)) {
/* Pretend we never started it (lets the first run find some incompatible demand) */
- DEBUG(1, ("SPNEGO(%s) NEG_TOKEN_INIT failed to parse: %s\n",
+ DEBUG(3, ("SPNEGO(%s) NEG_TOKEN_INIT failed: %s\n",
spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status)));
talloc_free(spnego_state->sub_sec_security);
spnego_state->sub_sec_security = NULL;
static NTSTATUS gensec_spnego_create_negTokenInit(struct gensec_security *gensec_security,
struct spnego_state *spnego_state,
TALLOC_CTX *out_mem_ctx,
+ struct tevent_context *ev,
const DATA_BLOB in, DATA_BLOB *out)
{
int i;
const char **mechTypes = NULL;
DATA_BLOB unwrapped_out = data_blob(NULL, 0);
const struct gensec_security_ops_wrapper *all_sec;
- const char *principal = NULL;
mechTypes = gensec_security_oids(gensec_security,
out_mem_ctx, GENSEC_OID_SPNEGO);
if (spnego_state->state_position == SPNEGO_CLIENT_START) {
nt_status = gensec_update(spnego_state->sub_sec_security,
out_mem_ctx,
+ ev,
null_data_blob,
&unwrapped_out);
spnego_out.negTokenInit.reqFlagsPadding = 0;
if (spnego_state->state_position == SPNEGO_SERVER_START) {
- /* server credentials */
- struct cli_credentials *creds = gensec_get_credentials(gensec_security);
- if (creds) {
- principal = cli_credentials_get_principal(creds, out_mem_ctx);
- }
- }
- if (principal) {
spnego_out.negTokenInit.mechListMIC
- = data_blob_string_const(principal);
+ = data_blob_string_const(ADS_IGNORE_PRINCIPAL);
} else {
spnego_out.negTokenInit.mechListMIC = null_data_blob;
}
static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx,
+ struct tevent_context *ev,
const DATA_BLOB in, DATA_BLOB *out)
{
struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
switch (spnego_state->state_position) {
case SPNEGO_FALLBACK:
- return gensec_update(spnego_state->sub_sec_security,
+ return gensec_update(spnego_state->sub_sec_security, ev,
out_mem_ctx, in, out);
case SPNEGO_SERVER_START:
{
len = spnego_read_data(gensec_security, in, &spnego);
if (len == -1) {
- return gensec_spnego_server_try_fallback(gensec_security, spnego_state,
- out_mem_ctx, in, out);
+ return gensec_spnego_server_try_fallback(gensec_security, spnego_state,
+ out_mem_ctx, ev, in, out);
}
/* client sent NegTargetInit, we send NegTokenTarg */
nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
spnego_state,
out_mem_ctx,
+ ev,
spnego.negTokenInit.mechTypes,
spnego.negTokenInit.mechToken,
&unwrapped_out);
return nt_status;
} else {
nt_status = gensec_spnego_create_negTokenInit(gensec_security, spnego_state,
- out_mem_ctx, in, out);
+ out_mem_ctx, ev, in, out);
spnego_state->state_position = SPNEGO_SERVER_START;
spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
return nt_status;
if (!in.length) {
/* client to produce negTokenInit */
nt_status = gensec_spnego_create_negTokenInit(gensec_security, spnego_state,
- out_mem_ctx, in, out);
+ out_mem_ctx, ev, in, out);
spnego_state->state_position = SPNEGO_CLIENT_TARG;
spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
return nt_status;
return NT_STATUS_INVALID_PARAMETER;
}
- if (spnego.negTokenInit.targetPrincipal) {
+ if (spnego.negTokenInit.targetPrincipal
+ && strcmp(spnego.negTokenInit.targetPrincipal, ADS_IGNORE_PRINCIPAL) != 0) {
DEBUG(5, ("Server claims it's principal name is %s\n", spnego.negTokenInit.targetPrincipal));
- if (lp_client_use_spnego_principal(gensec_security->settings->lp_ctx)) {
+ if (lpcfg_client_use_spnego_principal(gensec_security->settings->lp_ctx)) {
gensec_set_target_principal(gensec_security, spnego.negTokenInit.targetPrincipal);
}
}
nt_status = gensec_spnego_parse_negTokenInit(gensec_security,
spnego_state,
out_mem_ctx,
+ ev,
spnego.negTokenInit.mechTypes,
spnego.negTokenInit.mechToken,
&unwrapped_out);
}
nt_status = gensec_update(spnego_state->sub_sec_security,
- out_mem_ctx,
+ out_mem_ctx, ev,
spnego.negTokenTarg.responseToken,
&unwrapped_out);
if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.mechListMIC.length > 0) {
new_spnego = true;
nt_status = gensec_check_packet(spnego_state->sub_sec_security,
- out_mem_ctx,
spnego_state->mech_types.data,
spnego_state->mech_types.length,
spnego_state->mech_types.data,
}
nt_status = gensec_update(spnego_state->sub_sec_security,
- out_mem_ctx,
+ out_mem_ctx, ev,
spnego.negTokenTarg.responseToken,
&unwrapped_out);
spnego_state->neg_oid = talloc_strdup(spnego_state, spnego.negTokenTarg.supportedMech);
}
if (NT_STATUS_IS_OK(nt_status) && spnego.negTokenTarg.mechListMIC.length > 0) {
nt_status = gensec_check_packet(spnego_state->sub_sec_security,
- out_mem_ctx,
spnego_state->mech_types.data,
spnego_state->mech_types.length,
spnego_state->mech_types.data,
bool new_spnego = false;
nt_status = gensec_update(spnego_state->sub_sec_security,
- out_mem_ctx,
+ out_mem_ctx, ev,
spnego.negTokenTarg.responseToken,
&unwrapped_out);
return NT_STATUS_INVALID_PARAMETER;
}
+static NTSTATUS gensec_spnego_update_in(struct gensec_security *gensec_security,
+ const DATA_BLOB in, DATA_BLOB *full_in)
+{
+ struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+ size_t expected;
+ uint8_t *buf;
+ NTSTATUS status;
+ bool ok;
+
+ *full_in = data_blob_null;
+
+ if (spnego_state->in_needed == 0) {
+ size_t size = 0;
+
+ /*
+ * try to work out the size of the full
+ * input token, it might be fragmented
+ */
+ status = asn1_peek_full_tag(in, ASN1_APPLICATION(0), &size);
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ status = asn1_peek_full_tag(in, ASN1_CONTEXT(1), &size);
+ }
+
+ if (NT_STATUS_IS_OK(status) ||
+ NT_STATUS_EQUAL(status, STATUS_MORE_ENTRIES)) {
+ 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;
+ 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;
+ return NT_STATUS_OK;
+}
+
+static NTSTATUS gensec_spnego_update_out(struct gensec_security *gensec_security,
+ TALLOC_CTX *out_mem_ctx,
+ DATA_BLOB *_out)
+{
+ struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+ size_t new_length;
+ uint8_t *buf;
+ DATA_BLOB out = data_blob_null;
+
+ *_out = data_blob_null;
+
+ if (spnego_state->out_frag.length == 0) {
+ return spnego_state->out_status;
+ }
+
+ /*
+ * There is still more data to be delivered
+ * to the remote peer.
+ */
+
+ if (spnego_state->out_frag.length <= spnego_state->out_max_length) {
+ /*
+ * Fast path, we can deliver everything
+ */
+
+ *_out = spnego_state->out_frag;
+ talloc_steal(out_mem_ctx, _out->data);
+ spnego_state->out_frag = data_blob_null;
+ return spnego_state->out_status;
+ }
+
+ 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
+ */
+ data_blob_realloc(spnego_state, &out, spnego_state->out_max_length);
+
+ talloc_steal(out_mem_ctx, out.data);
+ *_out = out;
+ return NT_STATUS_MORE_PROCESSING_REQUIRED;
+}
+
+static NTSTATUS gensec_spnego_update_wrapper(struct gensec_security *gensec_security,
+ TALLOC_CTX *out_mem_ctx,
+ struct tevent_context *ev,
+ const DATA_BLOB in, DATA_BLOB *out)
+{
+ struct spnego_state *spnego_state = (struct spnego_state *)gensec_security->private_data;
+ DATA_BLOB full_in = data_blob_null;
+ NTSTATUS status;
+
+ *out = data_blob_null;
+
+ if (spnego_state->out_frag.length > 0) {
+ if (in.length > 0) {
+ return NT_STATUS_INVALID_PARAMETER;
+ }
+
+ return gensec_spnego_update_out(gensec_security,
+ out_mem_ctx,
+ out);
+ }
+
+ status = gensec_spnego_update_in(gensec_security,
+ in, &full_in);
+ if (!NT_STATUS_IS_OK(status)) {
+ return status;
+ }
+
+ status = gensec_spnego_update(gensec_security,
+ spnego_state, ev,
+ full_in,
+ &spnego_state->out_frag);
+ data_blob_free(&spnego_state->in_frag);
+ spnego_state->in_needed = 0;
+ if (!NT_STATUS_IS_OK(status) &&
+ !NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
+ return status;
+ }
+
+ spnego_state->out_status = status;
+
+ return gensec_spnego_update_out(gensec_security,
+ out_mem_ctx,
+ out);
+}
+
static void gensec_spnego_want_feature(struct gensec_security *gensec_security,
uint32_t feature)
{
.oid = gensec_spnego_oids,
.client_start = gensec_spnego_client_start,
.server_start = gensec_spnego_server_start,
- .update = gensec_spnego_update,
+ .update = gensec_spnego_update_wrapper,
.seal_packet = gensec_spnego_seal_packet,
.sign_packet = gensec_spnego_sign_packet,
.sig_size = gensec_spnego_sig_size,