From 0b24e8e869207dcb567b61272794daef48ee492a Mon Sep 17 00:00:00 2001 From: Simo Sorce Date: Wed, 28 Jul 2010 17:06:51 -0400 Subject: s3-dcerpc: Add SPNEGO incapsulation for KRB5 auth --- source3/Makefile.in | 5 +- source3/include/ntdomain.h | 3 +- source3/include/proto.h | 8 ++ source3/librpc/rpc/dcerpc_spnego.c | 235 +++++++++++++++++++++++++++++++++++++ source3/librpc/rpc/dcerpc_spnego.h | 42 +++++++ source3/rpc_client/cli_pipe.c | 153 ++++++++++++++++++++++-- source3/rpcclient/rpcclient.c | 39 +++--- 7 files changed, 454 insertions(+), 31 deletions(-) create mode 100644 source3/librpc/rpc/dcerpc_spnego.c create mode 100644 source3/librpc/rpc/dcerpc_spnego.h diff --git a/source3/Makefile.in b/source3/Makefile.in index 91856c2945..bf2646dc59 100644 --- a/source3/Makefile.in +++ b/source3/Makefile.in @@ -676,7 +676,9 @@ RPC_SERVER_OBJ = @RPC_STATIC@ $(RPC_PIPE_OBJ) $(NPA_TSTREAM_OBJ) RPC_PARSE_OBJ = $(RPC_PARSE_OBJ2) -RPC_CLIENT_OBJ = rpc_client/cli_pipe.o librpc/rpc/dcerpc_gssapi.o \ +RPC_CLIENT_OBJ = rpc_client/cli_pipe.o \ + librpc/rpc/dcerpc_gssapi.o \ + librpc/rpc/dcerpc_spnego.o \ librpc/rpc/rpc_common.o \ rpc_client/rpc_transport_np.o \ rpc_client/rpc_transport_sock.o \ @@ -1355,6 +1357,7 @@ RPC_OPEN_TCP_OBJ = torture/rpc_open_tcp.o \ librpc/rpc/rpc_common.o \ rpc_client/cli_pipe.o \ librpc/rpc/dcerpc_gssapi.o \ + librpc/rpc/dcerpc_spnego.o \ ../librpc/rpc/binding.o \ $(LIBMSRPC_GEN_OBJ) diff --git a/source3/include/ntdomain.h b/source3/include/ntdomain.h index 065138152b..98f2df9445 100644 --- a/source3/include/ntdomain.h +++ b/source3/include/ntdomain.h @@ -111,7 +111,8 @@ struct pipe_auth_data { union { struct schannel_state *schannel_auth; struct auth_ntlmssp_state *auth_ntlmssp_state; - struct gse_context *gssapi_state; /* Client only for now */ + struct gse_context *gssapi_state; + struct spnego_context *spnego_state; } a_u; /* Only the client code uses these 3 for now */ diff --git a/source3/include/proto.h b/source3/include/proto.h index 1af36dd3e7..bf7a41e13e 100644 --- a/source3/include/proto.h +++ b/source3/include/proto.h @@ -4848,6 +4848,14 @@ NTSTATUS cli_rpc_pipe_open_krb5(struct cli_state *cli, const char *username, const char *password, struct rpc_pipe_client **presult); +NTSTATUS cli_rpc_pipe_open_spnego_krb5(struct cli_state *cli, + const struct ndr_syntax_id *interface, + enum dcerpc_transport_t transport, + enum dcerpc_AuthLevel auth_level, + const char *server, + const char *username, + const char *password, + struct rpc_pipe_client **presult); NTSTATUS cli_get_session_key(TALLOC_CTX *mem_ctx, struct rpc_pipe_client *cli, DATA_BLOB *session_key); diff --git a/source3/librpc/rpc/dcerpc_spnego.c b/source3/librpc/rpc/dcerpc_spnego.c new file mode 100644 index 0000000000..51d294de5e --- /dev/null +++ b/source3/librpc/rpc/dcerpc_spnego.c @@ -0,0 +1,235 @@ +/* + * SPNEGO Encapsulation + * RPC Pipe client routines + * 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 . + */ + +#include "includes.h" +#include "../libcli/auth/spnego.h" +#include "dcerpc_spnego.h" +#include "dcerpc_gssapi.h" + +struct spnego_context { + enum dcerpc_AuthType auth_type; + + union { + struct auth_ntlmssp_state *auth_ntlmssp_state; + struct gse_context *gssapi_state; + } mech_ctx; + + enum { + SPNEGO_CONV_INIT = 0, + SPNEGO_CONV_AUTH_MORE, + SPNEGO_CONV_AUTH_CONFIRM, + SPNEGO_CONV_AUTH_DONE + } state; +}; + +static NTSTATUS spnego_context_init(TALLOC_CTX *mem_ctx, + enum dcerpc_AuthType auth_type, + struct spnego_context **spnego_ctx) +{ + struct spnego_context *sp_ctx; + + sp_ctx = talloc_zero(mem_ctx, struct spnego_context); + if (!sp_ctx) { + return NT_STATUS_NO_MEMORY; + } + + sp_ctx->auth_type = auth_type; + sp_ctx->state = SPNEGO_CONV_INIT; + + *spnego_ctx = sp_ctx; + return NT_STATUS_OK; +} + +NTSTATUS spnego_gssapi_init_client(TALLOC_CTX *mem_ctx, + enum dcerpc_AuthLevel auth_level, + const char *ccache_name, + const char *server, + const char *service, + const char *username, + const char *password, + uint32_t add_gss_c_flags, + struct spnego_context **spnego_ctx) +{ + struct spnego_context *sp_ctx; + NTSTATUS status; + + status = spnego_context_init(mem_ctx, + DCERPC_AUTH_TYPE_KRB5, &sp_ctx); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = gse_init_client(sp_ctx, DCERPC_AUTH_TYPE_KRB5, auth_level, + ccache_name, server, service, + username, password, add_gss_c_flags, + &sp_ctx->mech_ctx.gssapi_state); + if (!NT_STATUS_IS_OK(status)) { + TALLOC_FREE(sp_ctx); + return status; + } + + *spnego_ctx = sp_ctx; + return NT_STATUS_OK; +} + +NTSTATUS spnego_get_client_auth_token(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *spnego_in, + DATA_BLOB *spnego_out) +{ + struct gse_context *gse_ctx; + struct spnego_data sp_in, sp_out; + DATA_BLOB token_in = data_blob_null; + DATA_BLOB token_out = data_blob_null; + const char *mech_oids[2] = { NULL, NULL }; + char *principal = NULL; + ssize_t len_in = 0; + ssize_t len_out = 0; + bool mech_wants_more = false; + NTSTATUS status; + + if (!spnego_in->length) { + /* server didn't send anything, is init ? */ + if (sp_ctx->state != SPNEGO_CONV_INIT) { + return NT_STATUS_INVALID_PARAMETER; + } + } else { + len_in = spnego_read_data(mem_ctx, *spnego_in, &sp_in); + if (len_in == -1) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + if (sp_in.type != SPNEGO_NEG_TOKEN_TARG) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + if (sp_in.negTokenTarg.negResult == SPNEGO_REJECT) { + status = NT_STATUS_ACCESS_DENIED; + goto done; + } + token_in = sp_in.negTokenTarg.responseToken; + } + + if (sp_ctx->state == SPNEGO_CONV_AUTH_CONFIRM) { + if (sp_in.negTokenTarg.negResult == SPNEGO_ACCEPT_COMPLETED) { + sp_ctx->state = SPNEGO_CONV_AUTH_DONE; + status = NT_STATUS_OK; + } else { + status = NT_STATUS_ACCESS_DENIED; + } + goto done; + } + + switch (sp_ctx->auth_type) { + case DCERPC_AUTH_TYPE_KRB5: + + gse_ctx = sp_ctx->mech_ctx.gssapi_state; + status = gse_get_client_auth_token(mem_ctx, gse_ctx, + &token_in, &token_out); + if (!NT_STATUS_IS_OK(status)) { + goto done; + } + + mech_oids[0] = OID_KERBEROS5; + mech_wants_more = gse_require_more_processing(gse_ctx); + + break; + + case DCERPC_AUTH_TYPE_NTLMSSP: + status = NT_STATUS_NOT_IMPLEMENTED; + goto done; + default: + status = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + switch (sp_ctx->state) { + case SPNEGO_CONV_INIT: + *spnego_out = spnego_gen_negTokenInit(mem_ctx, mech_oids, + &token_out, principal); + if (!spnego_out->data) { + status = NT_STATUS_INTERNAL_ERROR; + goto done; + } + sp_ctx->state = SPNEGO_CONV_AUTH_MORE; + break; + + case SPNEGO_CONV_AUTH_MORE: + /* server says it's done and we do not seem to agree */ + if (sp_in.negTokenTarg.negResult == + SPNEGO_ACCEPT_COMPLETED) { + status = NT_STATUS_INVALID_PARAMETER; + goto done; + } + + sp_out.type = SPNEGO_NEG_TOKEN_TARG; + sp_out.negTokenTarg.negResult = SPNEGO_NONE_RESULT; + sp_out.negTokenTarg.supportedMech = NULL; + sp_out.negTokenTarg.responseToken = token_out; + sp_out.negTokenTarg.mechListMIC = data_blob_null; + + len_out = spnego_write_data(mem_ctx, spnego_out, &sp_out); + if (len_out == -1) { + status = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + if (!mech_wants_more) { + /* we still need to get an ack from the server */ + sp_ctx->state = SPNEGO_CONV_AUTH_CONFIRM; + } + + break; + + default: + status = NT_STATUS_INTERNAL_ERROR; + goto done; + } + + status = NT_STATUS_OK; + +done: + if (len_in > 0) { + spnego_free_data(&sp_in); + } + data_blob_free(&token_out); + return status; +} + +bool spnego_require_more_processing(struct spnego_context *sp_ctx) +{ + struct gse_context *gse_ctx; + + /* see if spnego processing itself requires more */ + if (sp_ctx->state == SPNEGO_CONV_AUTH_MORE || + sp_ctx->state == SPNEGO_CONV_AUTH_CONFIRM) { + return true; + } + + /* otherwise see if underlying mechnism does */ + switch (sp_ctx->auth_type) { + case DCERPC_AUTH_TYPE_KRB5: + gse_ctx = sp_ctx->mech_ctx.gssapi_state; + return gse_require_more_processing(gse_ctx); + default: + DEBUG(0, ("Unsupported type in request!\n")); + return false; + } +} + diff --git a/source3/librpc/rpc/dcerpc_spnego.h b/source3/librpc/rpc/dcerpc_spnego.h new file mode 100644 index 0000000000..7f48f39498 --- /dev/null +++ b/source3/librpc/rpc/dcerpc_spnego.h @@ -0,0 +1,42 @@ +/* + * SPNEGO Encapsulation + * RPC Pipe client routines + * 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 . + */ + +#ifndef _DCERPC_SPNEGO_H_ +#define _DCERPC_SPENGO_H_ + +struct spnego_context; + +NTSTATUS spnego_gssapi_init_client(TALLOC_CTX *mem_ctx, + enum dcerpc_AuthLevel auth_level, + const char *ccache_name, + const char *server, + const char *service, + const char *username, + const char *password, + uint32_t add_gss_c_flags, + struct spnego_context **spengo_ctx); + +NTSTATUS spnego_get_client_auth_token(TALLOC_CTX *mem_ctx, + struct spnego_context *sp_ctx, + DATA_BLOB *spnego_in, + DATA_BLOB *spnego_out); + +bool spnego_require_more_processing(struct spnego_context *sp_ctx); + +#endif /* _DCERPC_SPENGO_H_ */ diff --git a/source3/rpc_client/cli_pipe.c b/source3/rpc_client/cli_pipe.c index 6c4525935c..6dc2cd69af 100644 --- a/source3/rpc_client/cli_pipe.c +++ b/source3/rpc_client/cli_pipe.c @@ -31,6 +31,7 @@ #include "librpc/gen_ndr/ndr_dcerpc.h" #include "librpc/rpc/dcerpc.h" #include "librpc/rpc/dcerpc_gssapi.h" +#include "librpc/rpc/dcerpc_spnego.h" #undef DBGC_CLASS #define DBGC_CLASS DBGC_RPC_CLI @@ -944,6 +945,45 @@ static NTSTATUS rpc_api_pipe_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx, return NT_STATUS_OK; } +/******************************************************************* + Creates spnego auth bind. + ********************************************************************/ + +static NTSTATUS create_spnego_auth_bind_req(TALLOC_CTX *mem_ctx, + struct pipe_auth_data *auth, + DATA_BLOB *auth_info) +{ + DATA_BLOB in_token = data_blob_null; + DATA_BLOB auth_token = data_blob_null; + NTSTATUS status; + + /* Negotiate the initial auth token */ + status = spnego_get_client_auth_token(mem_ctx, + auth->a_u.spnego_state, + &in_token, &auth_token); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + status = dcerpc_push_dcerpc_auth(mem_ctx, + auth->auth_type, + auth->auth_level, + 0, /* auth_pad_length */ + 1, /* auth_context_id */ + &auth_token, + auth_info); + if (!NT_STATUS_IS_OK(status)) { + data_blob_free(&auth_token); + return status; + } + + DEBUG(5, ("Created GSS Authentication Token:\n")); + dump_data(5, auth_token.data, auth_token.length); + + data_blob_free(&auth_token); + return NT_STATUS_OK; +} + /******************************************************************* Creates krb5 auth bind. ********************************************************************/ @@ -1207,13 +1247,20 @@ static NTSTATUS create_rpc_bind_req(TALLOC_CTX *mem_ctx, break; case DCERPC_AUTH_TYPE_SPNEGO: - if (auth->spnego_type != PIPE_AUTH_TYPE_SPNEGO_NTLMSSP) { - /* "Can't" happen. */ - return NT_STATUS_INVALID_INFO_CLASS; - } - ret = create_spnego_ntlmssp_auth_rpc_bind_req(cli, + switch (auth->spnego_type) { + case PIPE_AUTH_TYPE_SPNEGO_NTLMSSP: + ret = create_spnego_ntlmssp_auth_rpc_bind_req(cli, auth->auth_level, &auth_info); + break; + + case PIPE_AUTH_TYPE_SPNEGO_KRB5: + ret = create_spnego_auth_bind_req(cli, auth, + &auth_info); + break; + default: + return NT_STATUS_INTERNAL_ERROR; + } if (!NT_STATUS_IS_OK(ret)) { return ret; } @@ -1920,13 +1967,37 @@ static void rpc_pipe_bind_step_one_done(struct tevent_req *subreq) break; case DCERPC_AUTH_TYPE_SPNEGO: - if (state->cli->auth->spnego_type != - PIPE_AUTH_TYPE_SPNEGO_NTLMSSP) { - goto err_out; + switch (pauth->spnego_type) { + case PIPE_AUTH_TYPE_SPNEGO_NTLMSSP: + /* Need to send alter context request and reply. */ + status = rpc_finish_spnego_ntlmssp_bind_send(req, + state, &auth.credentials); + break; + + case PIPE_AUTH_TYPE_SPNEGO_KRB5: + status = spnego_get_client_auth_token(state, + pauth->a_u.spnego_state, + &auth.credentials, + &auth_token); + if (!NT_STATUS_IS_OK(status)) { + break; + } + if (auth_token.length == 0) { + /* Bind complete. */ + tevent_req_done(req); + return; + } + if (spnego_require_more_processing(pauth->a_u.spnego_state)) { + status = rpc_bind_next_send(req, state, + &auth_token); + } else { + status = rpc_bind_finish_send(req, state, + &auth_token); + } + break; + default: + status = NT_STATUS_INTERNAL_ERROR; } - /* Need to send alter context request and reply. */ - status = rpc_finish_spnego_ntlmssp_bind_send(req, state, - &auth.credentials); break; case DCERPC_AUTH_TYPE_KRB5: @@ -3411,7 +3482,65 @@ NTSTATUS cli_rpc_pipe_open_spnego_krb5(struct cli_state *cli, const char *password, struct rpc_pipe_client **presult) { - return NT_STATUS_NOT_IMPLEMENTED; + struct rpc_pipe_client *result; + struct pipe_auth_data *auth; + NTSTATUS status; + + status = cli_rpc_pipe_open(cli, transport, interface, &result); + if (!NT_STATUS_IS_OK(status)) { + return status; + } + + auth = talloc(result, struct pipe_auth_data); + if (auth == NULL) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + auth->auth_type = DCERPC_AUTH_TYPE_SPNEGO; + auth->auth_level = auth_level; + /* compat */ + auth->spnego_type = PIPE_AUTH_TYPE_SPNEGO_KRB5; + + if (!username) { + username = ""; + } + auth->user_name = talloc_strdup(auth, username); + if (!auth->user_name) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + /* Fixme, should we fetch/set the Realm ? */ + auth->domain = talloc_strdup(auth, ""); + if (!auth->domain) { + status = NT_STATUS_NO_MEMORY; + goto err_out; + } + + status = spnego_gssapi_init_client(auth, auth->auth_level, + NULL, server, "cifs", + username, password, + GSS_C_DCE_STYLE, + &auth->a_u.spnego_state); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("spnego_init_client returned %s\n", + nt_errstr(status))); + goto err_out; + } + + status = rpc_pipe_bind(result, auth); + if (!NT_STATUS_IS_OK(status)) { + DEBUG(0, ("cli_rpc_pipe_bind failed with error %s\n", + nt_errstr(status))); + goto err_out; + } + + *presult = result; + return NT_STATUS_OK; + +err_out: + TALLOC_FREE(result); + return status; } NTSTATUS cli_get_session_key(TALLOC_CTX *mem_ctx, diff --git a/source3/rpcclient/rpcclient.c b/source3/rpcclient/rpcclient.c index 675fb1d947..565aaa8288 100644 --- a/source3/rpcclient/rpcclient.c +++ b/source3/rpcclient/rpcclient.c @@ -658,24 +658,29 @@ static NTSTATUS do_cmd(struct cli_state *cli, &cmd_entry->rpc_pipe); break; case DCERPC_AUTH_TYPE_SPNEGO: - if (pipe_default_auth_spnego_type != - PIPE_AUTH_TYPE_SPNEGO_NTLMSSP) { - DEBUG(0, ("Could not initialise %s. " - "Currently only NTLMSSP is " - "supported for SPNEGO\n", - get_pipe_name_from_syntax( - talloc_tos(), - cmd_entry->interface))); - return NT_STATUS_UNSUCCESSFUL; + switch (pipe_default_auth_spnego_type) { + case PIPE_AUTH_TYPE_SPNEGO_NTLMSSP: + ntresult = cli_rpc_pipe_open_spnego_ntlmssp( + cli, cmd_entry->interface, + default_transport, + pipe_default_auth_level, + get_cmdline_auth_info_domain(auth_info), + get_cmdline_auth_info_username(auth_info), + get_cmdline_auth_info_password(auth_info), + &cmd_entry->rpc_pipe); + break; + case PIPE_AUTH_TYPE_SPNEGO_KRB5: + ntresult = cli_rpc_pipe_open_spnego_krb5( + cli, cmd_entry->interface, + default_transport, + pipe_default_auth_level, + cli->desthost, + NULL, NULL, + &cmd_entry->rpc_pipe); + break; + default: + ntresult = NT_STATUS_INTERNAL_ERROR; } - ntresult = cli_rpc_pipe_open_spnego_ntlmssp( - cli, cmd_entry->interface, - default_transport, - pipe_default_auth_level, - get_cmdline_auth_info_domain(auth_info), - get_cmdline_auth_info_username(auth_info), - get_cmdline_auth_info_password(auth_info), - &cmd_entry->rpc_pipe); break; case DCERPC_AUTH_TYPE_NTLMSSP: ntresult = cli_rpc_pipe_open_ntlmssp( -- cgit