summaryrefslogtreecommitdiff
path: root/source4/auth/gensec/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'source4/auth/gensec/socket.c')
-rw-r--r--source4/auth/gensec/socket.c421
1 files changed, 421 insertions, 0 deletions
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
+};
+