/* Unix SMB/CIFS implementation. RFC2478 Compliant SPNEGO implementation Copyright (C) Jim McDonough 2003 Copyright (C) Andrew Bartlett 2004 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 2 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, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "includes.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_AUTH NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security) { struct spnego_state *spnego_state; TALLOC_CTX *mem_ctx = talloc_init("gensec_spengo_client_start"); if (!mem_ctx) { return NT_STATUS_NO_MEMORY; } spnego_state = talloc_p(mem_ctx, struct spnego_state); if (!spnego_state) { return NT_STATUS_NO_MEMORY; } spnego_state->role = SPNEGO_CLIENT; spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT; spnego_state->state_position = SPNEGO_CLIENT_GET_MECHS; spnego_state->result = SPNEGO_ACCEPT_INCOMPLETE; spnego_state->mem_ctx = mem_ctx; gensec_security->private_data = spnego_state; return NT_STATUS_OK; } /* wrappers for the spnego_*() functions */ NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, uint8_t *data, size_t length, DATA_BLOB *sig) { struct spnego_state *spnego_state = gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return spnego_state->sub_sec_security.ops->unseal(&spnego_state->sub_sec_security, mem_ctx, data, length, sig); } NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, const uint8_t *data, size_t length, const DATA_BLOB *sig) { struct spnego_state *spnego_state = gensec_security->private_data; return NT_STATUS_NOT_IMPLEMENTED; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return spnego_state->sub_sec_security.ops->check_sig(&spnego_state->sub_sec_security, mem_ctx, data, length, sig); } NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, uint8_t *data, size_t length, DATA_BLOB *sig) { struct spnego_state *spnego_state = gensec_security->private_data; return NT_STATUS_NOT_IMPLEMENTED; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return spnego_state->sub_sec_security.ops->seal(&spnego_state->sub_sec_security, mem_ctx, data, length, sig); } NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security, TALLOC_CTX *mem_ctx, const uint8_t *data, size_t length, DATA_BLOB *sig) { struct spnego_state *spnego_state = gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return spnego_state->sub_sec_security.ops->sign(&spnego_state->sub_sec_security, mem_ctx, data, length, sig); } NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security, DATA_BLOB *session_key) { struct spnego_state *spnego_state = gensec_security->private_data; if (spnego_state->state_position != SPNEGO_DONE && spnego_state->state_position != SPNEGO_FALLBACK) { return NT_STATUS_INVALID_PARAMETER; } return spnego_state->sub_sec_security.ops->session_key(&spnego_state->sub_sec_security, session_key); } NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, const DATA_BLOB in, DATA_BLOB *out) { struct spnego_state *spnego_state = gensec_security->private_data; DATA_BLOB null_data_blob = data_blob(NULL, 0); DATA_BLOB unwrapped_out; struct spnego_data spnego_out; struct spnego_data spnego; const struct gensec_security_ops *op; ssize_t len; if (!out_mem_ctx) { out_mem_ctx = spnego_state->mem_ctx; } if (spnego_state->state_position == SPNEGO_FALLBACK) { return spnego_state->sub_sec_security.ops->update(&spnego_state->sub_sec_security, out_mem_ctx, in, out); } len = spnego_read_data(in, &spnego); if (len == -1 && spnego_state->state_position == SPNEGO_SERVER_START) { int i; const struct gensec_security_ops **all_ops = gensec_security_all(); for (i=0; all_ops[i]; i++) { NTSTATUS nt_status; op = all_ops[i]; if (!op->oid) { continue; } nt_status = op->server_start(&spnego_state->sub_sec_security); if (!NT_STATUS_IS_OK(nt_status)) { continue; } nt_status = op->update(&spnego_state->sub_sec_security, out_mem_ctx, in, out); if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { spnego_state->state_position = SPNEGO_FALLBACK; return nt_status; } op->end(&spnego_state->sub_sec_security); } DEBUG(1, ("Failed to parse SPENGO request\n")); return NT_STATUS_INVALID_PARAMETER; } else { if (spnego.type != spnego_state->expected_packet) { spnego_free_data(&spnego); DEBUG(1, ("Invalid SPENGO request: %d, expected %d\n", spnego.type, spnego_state->expected_packet)); return NT_STATUS_INVALID_PARAMETER; } if (spnego_state->state_position == SPNEGO_CLIENT_GET_MECHS) { /* The server offers a list of mechanisms */ char **mechType = spnego.negTokenInit.mechTypes; char *my_mechs[] = {NULL, NULL}; int i; NTSTATUS nt_status; for (i=0; mechType[i]; i++) { op = gensec_security_by_oid(mechType[i]); if (!op) { continue; } spnego_state->sub_sec_security.ops = op; spnego_state->sub_sec_security.user = gensec_security->user; nt_status = op->client_start(&spnego_state->sub_sec_security); if (!NT_STATUS_IS_OK(nt_status)) { op->end(&spnego_state->sub_sec_security); continue; } if (i == 0) { nt_status = op->update(&spnego_state->sub_sec_security, out_mem_ctx, spnego.negTokenInit.mechToken, &unwrapped_out); } else { /* only get the helping start blob for the first OID */ nt_status = op->update(&spnego_state->sub_sec_security, out_mem_ctx, null_data_blob, &unwrapped_out); } if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { DEBUG(1, ("SPENGO(%s) NEG_TOKEN_INIT failed: %s\n", op->name, nt_errstr(nt_status))); op->end(&spnego_state->sub_sec_security); } else { break; } } if (!mechType[i]) { DEBUG(1, ("SPENGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n")); } spnego_free_data(&spnego); if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { return nt_status; } /* compose reply */ my_mechs[0] = op->oid; spnego_out.type = SPNEGO_NEG_TOKEN_INIT; spnego_out.negTokenInit.mechTypes = my_mechs; spnego_out.negTokenInit.reqFlags = 0; spnego_out.negTokenInit.mechListMIC = null_data_blob; spnego_out.negTokenInit.mechToken = unwrapped_out; if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { DEBUG(1, ("Failed to write SPENGO reply to NEG_TOKEN_INIT\n")); return NT_STATUS_INVALID_PARAMETER; } /* set next state */ spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG; spnego_state->state_position = SPNEGO_TARG; return nt_status; } else if (spnego_state->state_position == SPNEGO_TARG) { NTSTATUS nt_status; if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) { return NT_STATUS_ACCESS_DENIED; } op = spnego_state->sub_sec_security.ops; if (spnego.negTokenTarg.responseToken.length) { nt_status = op->update(&spnego_state->sub_sec_security, out_mem_ctx, spnego.negTokenTarg.responseToken, &unwrapped_out); } else { unwrapped_out = data_blob(NULL, 0); nt_status = NT_STATUS_OK; } if (NT_STATUS_IS_OK(nt_status) && (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED)) { nt_status = NT_STATUS_INVALID_PARAMETER; } spnego_state->result = spnego.negTokenTarg.negResult; spnego_free_data(&spnego); if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { /* compose reply */ spnego_out.type = SPNEGO_NEG_TOKEN_TARG; spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE; spnego_out.negTokenTarg.supportedMech = op->oid; spnego_out.negTokenTarg.responseToken = unwrapped_out; spnego_out.negTokenTarg.mechListMIC = null_data_blob; if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) { DEBUG(1, ("Failed to write SPENGO reply to NEG_TOKEN_TARG\n")); return NT_STATUS_INVALID_PARAMETER; } spnego_state->state_position = SPNEGO_TARG; } else if (NT_STATUS_IS_OK(nt_status)) { spnego_state->state_position = SPNEGO_DONE; } else { DEBUG(1, ("SPENGO(%s) login failed: %s\n", op->name, nt_errstr(nt_status))); return nt_status; } return nt_status; } else { spnego_free_data(&spnego); DEBUG(1, ("Invalid SPENGO request: %d\n", spnego.type)); return NT_STATUS_INVALID_PARAMETER; } } } void gensec_spnego_end(struct gensec_security *gensec_security) { struct spnego_state *spnego_state = gensec_security->private_data; spnego_state->sub_sec_security.ops->end(&spnego_state->sub_sec_security); talloc_destroy(spnego_state->mem_ctx); gensec_security->private_data = NULL; }