diff options
Diffstat (limited to 'source4/auth')
-rw-r--r-- | source4/auth/gensec/config.mk | 11 | ||||
-rw-r--r-- | source4/auth/gensec/socket.c | 421 | ||||
-rw-r--r-- | source4/auth/gensec/socket.h | 27 | ||||
-rw-r--r-- | source4/auth/gensec/spnego.c | 62 |
4 files changed, 503 insertions, 18 deletions
diff --git a/source4/auth/gensec/config.mk b/source4/auth/gensec/config.mk index 5aeca28689..0aaf82949e 100644 --- a/source4/auth/gensec/config.mk +++ b/source4/auth/gensec/config.mk @@ -67,3 +67,14 @@ OBJ_FILES = \ # End SUBSYSTEM SCHANNELDB ################################################ +################################################ +# Start SUBSYSTEM GENSEC_SOCKET +[SUBSYSTEM::GENSEC_SOCKET] +OBJ_FILES = \ + socket.o +PUBLIC_DEPENDENCIES = samba-socket +#PUBLIC_DEPENDENCIES = gensec +# +# End SUBSYSTEM GENSEC_SOCKET +################################################ + diff --git a/source4/auth/gensec/socket.c b/source4/auth/gensec/socket.c new file mode 100644 index 0000000000..f308f72712 --- /dev/null +++ b/source4/auth/gensec/socket.c @@ -0,0 +1,421 @@ +/* + Unix SMB/CIFS implementation. + + GENSEC socket interface + + Copyright (C) Andrew Bartlett 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 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" +#include "lib/events/events.h" +#include "lib/socket/socket.h" +#include "lib/stream/packet.h" +#include "auth/gensec/gensec.h" + +static const struct socket_ops gensec_socket_ops; + +struct gensec_socket { + struct gensec_security *gensec_security; + struct socket_context *socket; + struct event_context *ev; + struct packet_context *packet; + DATA_BLOB read_buffer; /* SASL packets are turned into liniarlised data here, for reading */ + size_t orig_send_len; + BOOL eof; + NTSTATUS error; + BOOL interrupted; + void (*recv_handler)(void *, uint16_t); + void *recv_private; + int in_extra_read; +}; + +static NTSTATUS gensec_socket_init_fn(struct socket_context *sock) +{ + switch (sock->type) { + case SOCKET_TYPE_STREAM: + break; + default: + return NT_STATUS_INVALID_PARAMETER; + } + + sock->backend_name = "gensec"; + + return NT_STATUS_OK; +} + +/* Try to figure out how much data is waiting to be read */ +static NTSTATUS gensec_socket_pending(struct socket_context *sock, size_t *npending) +{ + struct gensec_socket *gensec_socket = talloc_get_type(sock->private_data, struct gensec_socket); + if (gensec_socket->read_buffer.length > 0) { + *npending = gensec_socket->read_buffer.length; + return NT_STATUS_OK; + } + + /* This is a lie. We hope the decrypted data will always be + * less than this value, so the application just gets a short + * read. Without reading and decrypting it, we can't tell. + * If the SASL mech does compression, then we just need to + * manually trigger read events */ + return socket_pending(gensec_socket->socket, npending); +} + +/* Note if an error occours, so we can return it up the stack */ +static void gensec_socket_error_handler(void *private, NTSTATUS status) +{ + struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket); + if (NT_STATUS_EQUAL(status, NT_STATUS_END_OF_FILE)) { + gensec_socket->eof = True; + } else { + gensec_socket->error = status; + } +} + +static void gensec_socket_trigger_read(struct event_context *ev, + struct timed_event *te, + struct timeval t, void *private) +{ + struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket); + + gensec_socket->in_extra_read++; + gensec_socket->recv_handler(gensec_socket->recv_private, EVENT_FD_READ); + gensec_socket->in_extra_read--; + + /* It may well be that, having run the recv handler, we still + * have even more data waiting for us! + */ + if (gensec_socket->read_buffer.length && gensec_socket->recv_handler) { + /* Schedule this funcion to run again */ + event_add_timed(gensec_socket->ev, gensec_socket, timeval_zero(), + gensec_socket_trigger_read, gensec_socket); + } +} + +/* These two routines could be changed to use a circular buffer of + * some kind, or linked lists, or ... */ +static NTSTATUS gensec_socket_recv(struct socket_context *sock, void *buf, + size_t wantlen, size_t *nread) +{ + struct gensec_socket *gensec_socket = talloc_get_type(sock->private_data, struct gensec_socket); + + gensec_socket->error = NT_STATUS_OK; + + if (gensec_socket->read_buffer.length == 0) { + /* Process any data on the socket, into the read buffer. At + * this point, the socket is not available for read any + * longer */ + packet_recv(gensec_socket->packet); + + if (gensec_socket->eof) { + *nread = 0; + return NT_STATUS_OK; + } + + if (!NT_STATUS_IS_OK(gensec_socket->error)) { + return gensec_socket->error; + } + + if (gensec_socket->read_buffer.length == 0) { + /* Clearly we don't have the entire SASL packet yet, + * so it has not been written into the buffer */ + *nread = 0; + return STATUS_MORE_ENTRIES; + } + } + + + *nread = MIN(wantlen, gensec_socket->read_buffer.length); + memcpy(buf, gensec_socket->read_buffer.data, *nread); + + if (gensec_socket->read_buffer.length > *nread) { + memmove(gensec_socket->read_buffer.data, + gensec_socket->read_buffer.data + *nread, + gensec_socket->read_buffer.length - *nread); + } + + gensec_socket->read_buffer.length -= *nread; + gensec_socket->read_buffer.data = talloc_realloc(gensec_socket, + gensec_socket->read_buffer.data, + uint8_t, + gensec_socket->read_buffer.length); + + if (gensec_socket->read_buffer.length && + gensec_socket->in_extra_read == 0 && + gensec_socket->recv_handler) { + /* Manually call a read event, to get this moving + * again (as the socket should be dry, so the normal + * event handler won't trigger) */ + event_add_timed(gensec_socket->ev, gensec_socket, timeval_zero(), + gensec_socket_trigger_read, gensec_socket); + } + + return NT_STATUS_OK; +} + +/* Completed SASL packet callback. When we have a 'whole' SASL + * packet, decrypt it, and add it to the read buffer + * + * This function (and anything under it) MUST NOT call the event system + */ +static NTSTATUS gensec_socket_unwrap(void *private, DATA_BLOB blob) +{ + struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket); + DATA_BLOB wrapped; + DATA_BLOB unwrapped; + NTSTATUS nt_status; + TALLOC_CTX *mem_ctx; + uint32_t packet_size; + + if (blob.length < 4) { + /* Missing the header we already had! */ + DEBUG(0, ("Asked to unwrap packed of bogus length! How did we get the short packet?!\n")); + return NT_STATUS_INVALID_PARAMETER; + } + + wrapped = data_blob_const(blob.data + 4, blob.length - 4); + + packet_size = RIVAL(blob.data, 0); + if (packet_size != wrapped.length) { + DEBUG(0, ("Asked to unwrap packed of bogus length! How did we get this?!\n")); + return NT_STATUS_INTERNAL_ERROR; + } + + mem_ctx = talloc_new(gensec_socket); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + nt_status = gensec_unwrap(gensec_socket->gensec_security, + mem_ctx, + &wrapped, &unwrapped); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(mem_ctx); + return nt_status; + } + /* We could change this into a linked list, and have + * gensec_socket_recv() and gensec_socket_pending() walk the + * linked list */ + + nt_status = data_blob_append(gensec_socket, &gensec_socket->read_buffer, + unwrapped.data, unwrapped.length); + talloc_free(mem_ctx); + return nt_status; +} + +/* when the data is sent, we know we have not been interrupted */ +static void send_callback(void *private) +{ + struct gensec_socket *gensec_socket = talloc_get_type(private, struct gensec_socket); + gensec_socket->interrupted = False; +} + +/* + send data, but only as much as we allow in one packet. + + If this returns STATUS_MORE_ENTRIES, the caller must retry with + exactly the same data, or a NULL blob. +*/ +static NTSTATUS gensec_socket_send(struct socket_context *sock, + const DATA_BLOB *blob, size_t *sendlen) +{ + NTSTATUS nt_status; + struct gensec_socket *gensec_socket = talloc_get_type(sock->private_data, struct gensec_socket); + DATA_BLOB unwrapped, wrapped, out; + TALLOC_CTX *mem_ctx; + size_t max_input_size; + + *sendlen = 0; + + /* We have have been interupted, so the caller should be + * giving us the same data again. */ + if (gensec_socket->interrupted) { + packet_queue_run(gensec_socket->packet); + + if (!NT_STATUS_IS_OK(gensec_socket->error)) { + return gensec_socket->error; + } else if (gensec_socket->interrupted) { + return STATUS_MORE_ENTRIES; + } else { + *sendlen = gensec_socket->orig_send_len; + gensec_socket->orig_send_len = 0; + return NT_STATUS_OK; + } + } + + mem_ctx = talloc_new(gensec_socket); + if (!mem_ctx) { + return NT_STATUS_NO_MEMORY; + } + + max_input_size = gensec_max_input_size(gensec_socket->gensec_security); + unwrapped = data_blob_const(blob->data, MIN(max_input_size, (size_t)blob->length)); + + nt_status = gensec_wrap(gensec_socket->gensec_security, + mem_ctx, + &unwrapped, &wrapped); + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(mem_ctx); + return nt_status; + } + + out = data_blob_talloc(mem_ctx, NULL, 4); + if (!out.data) { + talloc_free(mem_ctx); + return NT_STATUS_NO_MEMORY; + } + RSIVAL(out.data, 0, wrapped.length); + + nt_status = data_blob_append(gensec_socket, &out, wrapped.data, wrapped.length); + + if (!NT_STATUS_IS_OK(nt_status)) { + talloc_free(mem_ctx); + return nt_status; + } + + gensec_socket->interrupted = True; + gensec_socket->error = NT_STATUS_OK; + gensec_socket->orig_send_len + = unwrapped.length; + + nt_status = packet_send_callback(gensec_socket->packet, + out, + send_callback, gensec_socket); + + talloc_free(mem_ctx); + + packet_queue_run(gensec_socket->packet); + + if (!NT_STATUS_IS_OK(gensec_socket->error)) { + return gensec_socket->error; + } else if (gensec_socket->interrupted) { + return STATUS_MORE_ENTRIES; + } else { + *sendlen = gensec_socket->orig_send_len; + gensec_socket->orig_send_len = 0; + return NT_STATUS_OK; + } +} + +struct socket_context *gensec_socket_init(struct gensec_security *gensec_security, + struct socket_context *socket, + struct event_context *ev, + void (*recv_handler)(void *, uint16_t), + void *recv_private) +{ + struct gensec_socket *gensec_socket; + struct socket_context *new_sock; + NTSTATUS nt_status; + + /* Nothing to do here, if we are not actually wrapping on this socket */ + if (!gensec_have_feature(gensec_security, GENSEC_FEATURE_SEAL) && + !gensec_have_feature(gensec_security, GENSEC_FEATURE_SIGN)) { + return socket; + } + + nt_status = socket_create_with_ops(socket, &gensec_socket_ops, &new_sock, + SOCKET_TYPE_STREAM, socket->flags | SOCKET_FLAG_ENCRYPT); + if (!NT_STATUS_IS_OK(nt_status)) { + return NULL; + } + + gensec_socket = talloc(new_sock, struct gensec_socket); + if (gensec_socket == NULL) { + return NULL; + } + + gensec_socket->eof = False; + gensec_socket->error = NT_STATUS_OK; + gensec_socket->interrupted = False; + gensec_socket->in_extra_read = 0; + + gensec_socket->read_buffer = data_blob(NULL, 0); + + gensec_socket->gensec_security = gensec_security; + gensec_socket->socket = socket; + if (talloc_reference(gensec_socket, socket) == NULL) { + return NULL; + } + gensec_socket->recv_handler = recv_handler; + gensec_socket->recv_private = recv_private; + gensec_socket->ev = ev; + + new_sock->private_data = gensec_socket; + + gensec_socket->packet = packet_init(gensec_socket); + if (gensec_socket->packet == NULL) { + return NULL; + } + + packet_set_private(gensec_socket->packet, gensec_socket); + packet_set_socket(gensec_socket->packet, socket); + packet_set_callback(gensec_socket->packet, gensec_socket_unwrap); + packet_set_full_request(gensec_socket->packet, packet_full_request_u32); + packet_set_error_handler(gensec_socket->packet, gensec_socket_error_handler); + packet_set_serialise(gensec_socket->packet); + + /* TODO: full-request that knows about maximum packet size */ + + new_sock->state = socket->state; + + return new_sock; +} + + +static NTSTATUS gensec_socket_set_option(struct socket_context *sock, const char *option, const char *val) +{ + set_socket_options(socket_get_fd(sock), option); + return NT_STATUS_OK; +} + +static char *gensec_socket_get_peer_name(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct gensec_socket *gensec = talloc_get_type(sock->private_data, struct gensec_socket); + return socket_get_peer_name(gensec->socket, mem_ctx); +} + +static struct socket_address *gensec_socket_get_peer_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct gensec_socket *gensec = talloc_get_type(sock->private_data, struct gensec_socket); + return socket_get_peer_addr(gensec->socket, mem_ctx); +} + +static struct socket_address *gensec_socket_get_my_addr(struct socket_context *sock, TALLOC_CTX *mem_ctx) +{ + struct gensec_socket *gensec = talloc_get_type(sock->private_data, struct gensec_socket); + return socket_get_my_addr(gensec->socket, mem_ctx); +} + +static int gensec_socket_get_fd(struct socket_context *sock) +{ + struct gensec_socket *gensec = talloc_get_type(sock->private_data, struct gensec_socket); + return socket_get_fd(gensec->socket); +} + +static const struct socket_ops gensec_socket_ops = { + .name = "gensec", + .fn_init = gensec_socket_init_fn, + .fn_recv = gensec_socket_recv, + .fn_send = gensec_socket_send, + .fn_pending = gensec_socket_pending, + + .fn_set_option = gensec_socket_set_option, + + .fn_get_peer_name = gensec_socket_get_peer_name, + .fn_get_peer_addr = gensec_socket_get_peer_addr, + .fn_get_my_addr = gensec_socket_get_my_addr, + .fn_get_fd = gensec_socket_get_fd +}; + diff --git a/source4/auth/gensec/socket.h b/source4/auth/gensec/socket.h new file mode 100644 index 0000000000..1641e50868 --- /dev/null +++ b/source4/auth/gensec/socket.h @@ -0,0 +1,27 @@ +/* + Unix SMB/CIFS implementation. + + Generic Authentication Interface (socket wrapper) + + Copyright (C) Andrew Bartlett <abartlet@samba.org> 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 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. +*/ + +struct socket_context *gensec_socket_init(struct gensec_security *gensec_security, + struct socket_context *socket, + struct event_context *ev, + void (*recv_handler)(void *, uint16_t), + void *recv_private); diff --git a/source4/auth/gensec/spnego.c b/source4/auth/gensec/spnego.c index 6ede774cc8..a57e8cc846 100644 --- a/source4/auth/gensec/spnego.c +++ b/source4/auth/gensec/spnego.c @@ -211,6 +211,30 @@ static size_t gensec_spnego_sig_size(struct gensec_security *gensec_security, si return gensec_sig_size(spnego_state->sub_sec_security, data_size); } +static size_t gensec_spnego_max_input_size(struct gensec_security *gensec_security) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + return 0; + } + + return gensec_max_input_size(spnego_state->sub_sec_security); +} + +static size_t gensec_spnego_max_wrapped_size(struct gensec_security *gensec_security) +{ + struct spnego_state *spnego_state = gensec_security->private_data; + + if (spnego_state->state_position != SPNEGO_DONE + && spnego_state->state_position != SPNEGO_FALLBACK) { + return 0; + } + + return gensec_max_wrapped_size(spnego_state->sub_sec_security); +} + static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security, DATA_BLOB *session_key) { @@ -938,24 +962,26 @@ static const char *gensec_spnego_oids[] = { }; 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 = gensec_spnego_update, - .seal_packet = gensec_spnego_seal_packet, - .sign_packet = gensec_spnego_sign_packet, - .sig_size = gensec_spnego_sig_size, - .check_packet = gensec_spnego_check_packet, - .unseal_packet = gensec_spnego_unseal_packet, - .wrap = gensec_spnego_wrap, - .unwrap = gensec_spnego_unwrap, - .session_key = gensec_spnego_session_key, - .session_info = gensec_spnego_session_info, - .have_feature = gensec_spnego_have_feature, - .enabled = True, + .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 = gensec_spnego_update, + .seal_packet = gensec_spnego_seal_packet, + .sign_packet = gensec_spnego_sign_packet, + .sig_size = gensec_spnego_sig_size, + .max_wrapped_size = gensec_spnego_max_wrapped_size, + .max_input_size = gensec_spnego_max_input_size, + .check_packet = gensec_spnego_check_packet, + .unseal_packet = gensec_spnego_unseal_packet, + .wrap = gensec_spnego_wrap, + .unwrap = gensec_spnego_unwrap, + .session_key = gensec_spnego_session_key, + .session_info = gensec_spnego_session_info, + .have_feature = gensec_spnego_have_feature, + .enabled = True, }; NTSTATUS gensec_spnego_init(void) |