summaryrefslogtreecommitdiff
path: root/source4/auth/gensec/socket.c
diff options
context:
space:
mode:
authorAndrew Bartlett <abartlet@samba.org>2006-07-23 02:50:08 +0000
committerGerald (Jerry) Carter <jerry@samba.org>2007-10-10 14:10:18 -0500
commitba07fa43d0b0090f5e686d8c1822468049f52416 (patch)
tree2feede783ba0741ffdb8943405b8da1bbcf0018c /source4/auth/gensec/socket.c
parent74b68a75554f338a4af09fb3db0e01dcab97a72b (diff)
downloadsamba-ba07fa43d0b0090f5e686d8c1822468049f52416.tar.gz
samba-ba07fa43d0b0090f5e686d8c1822468049f52416.tar.bz2
samba-ba07fa43d0b0090f5e686d8c1822468049f52416.zip
r17197: This patch moves the encryption of bulk data on SASL negotiated security
contexts from the application layer into the socket layer. This improves a number of correctness aspects, as we now allow LDAP packets to cross multiple SASL packets. It should also make it much easier to write async LDAP tests from windows clients, as they use SASL by default. It is also vital to allowing OpenLDAP clients to use GSSAPI against Samba4, as it negotiates a rather small SASL buffer size. This patch mirrors the earlier work done to move TLS into the socket layer. Unusual in this pstch is the extra read callback argument I take. As SASL is a layer on top of a socket, it is entirely possible for the SASL layer to drain a socket dry, but for the caller not to have read all the decrypted data. This would leave the system without an event to restart the read (as the socket is dry). As such, I re-invoke the read handler from a timed callback, which should trigger on the next running of the event loop. I believe that the TLS code does require a similar callback. In trying to understand why this is required, imagine a SASL-encrypted LDAP packet in the following formation: +-----------------+---------------------+ | SASL Packet #1 | SASL Packet #2 | ----------------------------------------+ | LDAP Packet #1 | LDAP Packet #2 | ----------------------------------------+ In the old code, this was illegal, but it is perfectly standard SASL-encrypted LDAP. Without the callback, we would read and process the first LDAP packet, and the SASL code would have read the second SASL packet (to decrypt enough data for the LDAP packet), and no data would remain on the socket. Without data on the socket, read events stop. That is why I add timed events, until the SASL buffer is drained. Another approach would be to add a hack to the event system, to have it pretend there remained data to read off the network (but that is ugly). In improving the code, to handle more real-world cases, I've been able to remove almost all the special-cases in the testnonblock code. The only special case is that we must use a deterministic partial packet when calling send, rather than a random length. (1 + n/2). This is needed because of the way the SASL and TLS code works, and the 'resend on failure' requirements. Andrew Bartlett (This used to be commit 5d7c9c12cb2b39673172a357092b80cd814850b0)
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
+};
+