diff options
-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 | ||||
-rw-r--r-- | source4/ldap_server/config.mk | 2 | ||||
-rw-r--r-- | source4/ldap_server/ldap_bind.c | 28 | ||||
-rw-r--r-- | source4/ldap_server/ldap_server.c | 127 | ||||
-rw-r--r-- | source4/ldap_server/ldap_server.h | 7 | ||||
-rw-r--r-- | source4/lib/socket/socket.c | 25 | ||||
-rw-r--r-- | source4/lib/socket/socket.h | 8 | ||||
-rw-r--r-- | source4/lib/stream/packet.c | 33 | ||||
-rw-r--r-- | source4/lib/stream/packet.h | 6 | ||||
-rw-r--r-- | source4/lib/tls/tls.c | 4 | ||||
-rw-r--r-- | source4/libcli/ldap/config.mk | 2 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_bind.c | 22 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_client.c | 94 | ||||
-rw-r--r-- | source4/libcli/ldap/ldap_client.h | 3 | ||||
-rw-r--r-- | source4/smbd/service_stream.c | 5 |
18 files changed, 639 insertions, 248 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) diff --git a/source4/ldap_server/config.mk b/source4/ldap_server/config.mk index 0ef8ced928..fc9d04c46a 100644 --- a/source4/ldap_server/config.mk +++ b/source4/ldap_server/config.mk @@ -11,6 +11,6 @@ OBJ_FILES = \ ldap_backend.o \ ldap_bind.o PUBLIC_DEPENDENCIES = \ - LIBCLI_LDAP SAMDB process_model auth + LIBCLI_LDAP SAMDB process_model auth GENSEC_SOCKET # End SUBSYSTEM SMB ####################### diff --git a/source4/ldap_server/ldap_bind.c b/source4/ldap_server/ldap_bind.c index 35b6ad5fbf..7fce390450 100644 --- a/source4/ldap_server/ldap_bind.c +++ b/source4/ldap_server/ldap_bind.c @@ -22,10 +22,11 @@ #include "ldap_server/ldap_server.h" #include "auth/auth.h" #include "libcli/ldap/ldap.h" -#include "smbd/service_stream.h" +#include "smbd/service.h" #include "lib/ldb/include/ldb.h" #include "lib/ldb/include/ldb_errors.h" #include "dsdb/samdb/samdb.h" +#include "auth/gensec/socket.h" static NTSTATUS ldapsrv_BindSimple(struct ldapsrv_call *call) { @@ -89,6 +90,23 @@ static NTSTATUS ldapsrv_BindSimple(struct ldapsrv_call *call) return NT_STATUS_OK; } +static void ldapsrv_set_sasl(void *private) +{ + struct ldapsrv_connection *conn = talloc_get_type(private, struct ldapsrv_connection); + struct socket_context *socket = gensec_socket_init(conn->gensec, + conn->connection->socket, + conn->connection->event.ctx, + stream_io_handler_callback, + conn->connection); + if (socket) { + conn->connection->socket = socket; + talloc_steal(conn->connection->socket, socket); + packet_set_socket(conn->packet, socket); + } else { + ldapsrv_terminate_connection(conn, "Failed to setup SASL wrapping on socket"); + } +} + static NTSTATUS ldapsrv_BindSASL(struct ldapsrv_call *call) { struct ldap_BindRequest *req = &call->request->r.BindRequest; @@ -175,10 +193,10 @@ static NTSTATUS ldapsrv_BindSASL(struct ldapsrv_call *call) result = LDAP_SUCCESS; errstr = NULL; - if (gensec_have_feature(conn->gensec, GENSEC_FEATURE_SEAL) || - gensec_have_feature(conn->gensec, GENSEC_FEATURE_SIGN)) { - conn->enable_wrap = True; - } + + call->send_callback = ldapsrv_set_sasl; + call->send_private = conn; + old_session_info = conn->session_info; conn->session_info = NULL; status = gensec_session_info(conn->gensec, &conn->session_info); diff --git a/source4/ldap_server/ldap_server.c b/source4/ldap_server/ldap_server.c index 07b1bc6a27..cfbe6eb5b2 100644 --- a/source4/ldap_server/ldap_server.c +++ b/source4/ldap_server/ldap_server.c @@ -31,10 +31,8 @@ #include "smbd/service_task.h" #include "smbd/service_stream.h" #include "smbd/service.h" -#include "lib/socket/socket.h" #include "lib/tls/tls.h" #include "lib/messaging/irpc.h" -#include "lib/stream/packet.h" #include "lib/ldb/include/ldb.h" #include "lib/ldb/include/ldb_errors.h" #include "system/network.h" @@ -43,7 +41,7 @@ /* close the socket and shutdown a server_context */ -static void ldapsrv_terminate_connection(struct ldapsrv_connection *conn, +void ldapsrv_terminate_connection(struct ldapsrv_connection *conn, const char *reason) { stream_terminate_connection(conn->connection, reason); @@ -68,7 +66,6 @@ static void ldapsrv_process_message(struct ldapsrv_connection *conn, struct ldapsrv_call *call; NTSTATUS status; DATA_BLOB blob; - BOOL enable_wrap = conn->enable_wrap; call = talloc(conn, struct ldapsrv_call); if (!call) { @@ -79,11 +76,14 @@ static void ldapsrv_process_message(struct ldapsrv_connection *conn, call->request = talloc_steal(call, msg); call->conn = conn; call->replies = NULL; - + call->send_callback = NULL; + call->send_private = NULL; + /* make the call */ status = ldapsrv_do_call(call); if (!NT_STATUS_IS_OK(status)) { - goto failed; + talloc_free(call); + return; } blob = data_blob(NULL, 0); @@ -100,49 +100,36 @@ static void ldapsrv_process_message(struct ldapsrv_connection *conn, msg = call->replies->msg; if (!ldap_encode(msg, &b, call)) { DEBUG(0,("Failed to encode ldap reply of type %d\n", msg->type)); - goto failed; + talloc_free(call); + return; } status = data_blob_append(call, &blob, b.data, b.length); data_blob_free(&b); - if (!NT_STATUS_IS_OK(status)) goto failed; - - DLIST_REMOVE(call->replies, call->replies); - } + talloc_set_name_const(blob.data, "Outgoing, encoded LDAP packet"); - /* possibly encrypt/sign the reply */ - if (enable_wrap) { - DATA_BLOB wrapped; - - status = gensec_wrap(conn->gensec, call, &blob, &wrapped); if (!NT_STATUS_IS_OK(status)) { - goto failed; - } - data_blob_free(&blob); - blob = data_blob_talloc(call, NULL, wrapped.length + 4); - if (blob.data == NULL) { - goto failed; + talloc_free(call); + return; } - RSIVAL(blob.data, 0, wrapped.length); - memcpy(blob.data+4, wrapped.data, wrapped.length); - data_blob_free(&wrapped); + + DLIST_REMOVE(call->replies, call->replies); } - packet_send(conn->packet, blob); + packet_send_callback(conn->packet, blob, + call->send_callback, call->send_private); talloc_free(call); return; - -failed: - talloc_free(call); } - /* - decode the input buffer + decode/process data */ -static NTSTATUS ldapsrv_decode_plain(struct ldapsrv_connection *conn, DATA_BLOB blob) +static NTSTATUS ldapsrv_decode(void *private, DATA_BLOB blob) { + struct ldapsrv_connection *conn = talloc_get_type(private, + struct ldapsrv_connection); struct asn1_data asn1; struct ldap_message *msg = talloc(conn, struct ldap_message); @@ -165,63 +152,6 @@ static NTSTATUS ldapsrv_decode_plain(struct ldapsrv_connection *conn, DATA_BLOB return NT_STATUS_OK; } - -/* - decode/process wrapped data -*/ -static NTSTATUS ldapsrv_decode_wrapped(struct ldapsrv_connection *conn, - DATA_BLOB blob) -{ - DATA_BLOB wrapped, unwrapped; - struct asn1_data asn1; - struct ldap_message *msg = talloc(conn, struct ldap_message); - NTSTATUS status; - - if (msg == NULL) { - return NT_STATUS_NO_MEMORY; - } - - wrapped = data_blob_const(blob.data+4, blob.length-4); - - status = gensec_unwrap(conn->gensec, msg, &wrapped, &unwrapped); - if (!NT_STATUS_IS_OK(status)) { - return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); - } - - data_blob_free(&blob); - - if (!asn1_load(&asn1, unwrapped)) { - return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); - } - - while (ldap_decode(&asn1, msg)) { - ldapsrv_process_message(conn, msg); - msg = talloc(conn, struct ldap_message); - } - - if (asn1.ofs < asn1.length) { - return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); - } - - talloc_free(msg); - asn1_free(&asn1); - - return NT_STATUS_OK; -} - -/* - decode/process data -*/ -static NTSTATUS ldapsrv_decode(void *private, DATA_BLOB blob) -{ - struct ldapsrv_connection *conn = talloc_get_type(private, - struct ldapsrv_connection); - if (conn->enable_wrap) { - return ldapsrv_decode_wrapped(conn, blob); - } - return ldapsrv_decode_plain(conn, blob); -} - /* Idle timeout handler */ @@ -238,7 +168,7 @@ static void ldapsrv_conn_idle_timeout(struct event_context *ev, /* called when a LDAP socket becomes readable */ -static void ldapsrv_recv(struct stream_connection *c, uint16_t flags) +void ldapsrv_recv(struct stream_connection *c, uint16_t flags) { struct ldapsrv_connection *conn = talloc_get_type(c->private, struct ldapsrv_connection); @@ -262,20 +192,6 @@ static void ldapsrv_recv(struct stream_connection *c, uint16_t flags) } /* - check if a blob is a complete ldap packet - handle wrapper or unwrapped connections -*/ -NTSTATUS ldapsrv_complete_packet(void *private, DATA_BLOB blob, size_t *size) -{ - struct ldapsrv_connection *conn = talloc_get_type(private, - struct ldapsrv_connection); - if (conn->enable_wrap) { - return packet_full_request_u32(private, blob, size); - } - return ldap_full_packet(private, blob, size); -} - -/* called when a LDAP socket becomes writable */ static void ldapsrv_send(struct stream_connection *c, uint16_t flags) @@ -411,7 +327,6 @@ static void ldapsrv_accept(struct stream_connection *c) return; } - conn->enable_wrap = False; conn->packet = NULL; conn->connection = c; conn->service = ldapsrv_service; @@ -445,7 +360,7 @@ static void ldapsrv_accept(struct stream_connection *c) packet_set_private(conn->packet, conn); packet_set_socket(conn->packet, c->socket); packet_set_callback(conn->packet, ldapsrv_decode); - packet_set_full_request(conn->packet, ldapsrv_complete_packet); + packet_set_full_request(conn->packet, ldap_full_packet); packet_set_error_handler(conn->packet, ldapsrv_error_handler); packet_set_event_context(conn->packet, c->event.ctx); packet_set_fde(conn->packet, c->event.fde); diff --git a/source4/ldap_server/ldap_server.h b/source4/ldap_server/ldap_server.h index 0b0b78ea7f..c35f62f134 100644 --- a/source4/ldap_server/ldap_server.h +++ b/source4/ldap_server/ldap_server.h @@ -20,6 +20,8 @@ */ #include "libcli/ldap/ldap.h" +#include "lib/socket/socket.h" +#include "lib/stream/packet.h" struct ldapsrv_connection { struct stream_connection *connection; @@ -29,9 +31,6 @@ struct ldapsrv_connection { struct cli_credentials *server_credentials; struct ldb_context *ldb; - /* are we using gensec wrapping? */ - BOOL enable_wrap; - BOOL global_catalog; struct packet_context *packet; @@ -54,6 +53,8 @@ struct ldapsrv_call { struct ldapsrv_reply *prev, *next; struct ldap_message *msg; } *replies; + packet_send_callback_fn_t send_callback; + void *send_private; }; struct ldapsrv_service; diff --git a/source4/lib/socket/socket.c b/source4/lib/socket/socket.c index ac64bc4ddc..eca668885c 100644 --- a/source4/lib/socket/socket.c +++ b/source4/lib/socket/socket.c @@ -189,15 +189,9 @@ _PUBLIC_ NTSTATUS socket_recv(struct socket_context *sock, void *buf, if ((sock->flags & SOCKET_FLAG_TESTNONBLOCK) && wantlen > 1) { - /* The returning of 0 and MORE_ENTRIES is incompatible - with TLS and SASL sockets, as there is not a - constant event source to re-trigger the reads */ - - if (!(sock->flags & SOCKET_FLAG_FAKE)) { - if (random() % 10 == 0) { - *nread = 0; - return STATUS_MORE_ENTRIES; - } + if (random() % 10 == 0) { + *nread = 0; + return STATUS_MORE_ENTRIES; } return sock->ops->fn_recv(sock, buf, 1+(random() % wantlen), nread); } @@ -240,17 +234,22 @@ _PUBLIC_ NTSTATUS socket_send(struct socket_context *sock, if ((sock->flags & SOCKET_FLAG_TESTNONBLOCK) && blob->length > 1) { + DATA_BLOB blob2 = *blob; if (random() % 10 == 0) { *sendlen = 0; return STATUS_MORE_ENTRIES; } - /* The variable size sends are incompatilbe with TLS and SASL + /* The random size sends are incompatible with TLS and SASL * sockets, which require re-sends to be consistant */ - if (!(sock->flags & SOCKET_FLAG_FAKE)) { - DATA_BLOB blob2 = *blob; + if (!(sock->flags & SOCKET_FLAG_ENCRYPT)) { blob2.length = 1+(random() % blob2.length); - return sock->ops->fn_send(sock, &blob2, sendlen); + } else { + /* This is particularly stressful on buggy + * LDAP clients, that don't expect on LDAP + * packet in many SASL packets */ + blob2.length = 1 + blob2.length/2; } + return sock->ops->fn_send(sock, &blob2, sendlen); } return sock->ops->fn_send(sock, blob, sendlen); } diff --git a/source4/lib/socket/socket.h b/source4/lib/socket/socket.h index c0cf429887..025fc7e13d 100644 --- a/source4/lib/socket/socket.h +++ b/source4/lib/socket/socket.h @@ -102,7 +102,13 @@ enum socket_state { #define SOCKET_FLAG_BLOCK 0x00000001 #define SOCKET_FLAG_PEEK 0x00000002 #define SOCKET_FLAG_TESTNONBLOCK 0x00000004 -#define SOCKET_FLAG_FAKE 0x00000008 /* This is an implementation not directly on top of a real socket */ +#define SOCKET_FLAG_ENCRYPT 0x00000008 /* This socket + * implementation requires + * that re-sends be + * consistant, because it + * is encrypting data. + * This modifies the + * TESTNONBLOCK case */ struct socket_context { enum socket_type type; diff --git a/source4/lib/stream/packet.c b/source4/lib/stream/packet.c index e06f4985ef..2759c75214 100644 --- a/source4/lib/stream/packet.c +++ b/source4/lib/stream/packet.c @@ -28,7 +28,6 @@ #include "lib/socket/socket.h" #include "lib/stream/packet.h" - struct packet_context { packet_callback_fn_t callback; packet_full_request_fn_t full_request; @@ -53,6 +52,8 @@ struct packet_context { struct send_element *next, *prev; DATA_BLOB blob; size_t nsent; + packet_send_callback_fn_t send_callback; + void *send_callback_private; } *send_queue; }; @@ -374,6 +375,7 @@ next_partial: return; } + /* Have we consumed the whole buffer yet? */ if (pc->partial.length == 0) { return; } @@ -436,7 +438,7 @@ _PUBLIC_ void packet_queue_run(struct packet_context *pc) status = socket_send(pc->sock, &blob, &nwritten); if (NT_STATUS_IS_ERR(status)) { - packet_error(pc, NT_STATUS_NET_WRITE_FAULT); + packet_error(pc, status); return; } if (!NT_STATUS_IS_OK(status)) { @@ -445,6 +447,9 @@ _PUBLIC_ void packet_queue_run(struct packet_context *pc) el->nsent += nwritten; if (el->nsent == el->blob.length) { DLIST_REMOVE(pc->send_queue, el); + if (el->send_callback) { + el->send_callback(el->send_callback_private); + } talloc_free(el); } } @@ -455,9 +460,15 @@ _PUBLIC_ void packet_queue_run(struct packet_context *pc) } /* - put a packet in the send queue + put a packet in the send queue. When the packet is actually sent, + call send_callback. + + Useful for operations that must occour after sending a message, such + as the switch to SASL encryption after as sucessful LDAP bind relpy. */ -_PUBLIC_ NTSTATUS packet_send(struct packet_context *pc, DATA_BLOB blob) +_PUBLIC_ NTSTATUS packet_send_callback(struct packet_context *pc, DATA_BLOB blob, + packet_send_callback_fn_t send_callback, + void *private) { struct send_element *el; el = talloc(pc, struct send_element); @@ -466,6 +477,8 @@ _PUBLIC_ NTSTATUS packet_send(struct packet_context *pc, DATA_BLOB blob) DLIST_ADD_END(pc->send_queue, el, struct send_element *); el->blob = blob; el->nsent = 0; + el->send_callback = send_callback; + el->send_callback_private = private; /* if we aren't going to free the packet then we must reference it to ensure it doesn't disappear before going out */ @@ -477,11 +490,23 @@ _PUBLIC_ NTSTATUS packet_send(struct packet_context *pc, DATA_BLOB blob) talloc_steal(el, blob.data); } + if (private && !talloc_reference(el, private)) { + return NT_STATUS_NO_MEMORY; + } + EVENT_FD_WRITEABLE(pc->fde); return NT_STATUS_OK; } +/* + put a packet in the send queue +*/ +_PUBLIC_ NTSTATUS packet_send(struct packet_context *pc, DATA_BLOB blob) +{ + return packet_send_callback(pc, blob, NULL, NULL); +} + /* a full request checker for NBT formatted packets (first 3 bytes are length) diff --git a/source4/lib/stream/packet.h b/source4/lib/stream/packet.h index b7ee428186..0d875d777c 100644 --- a/source4/lib/stream/packet.h +++ b/source4/lib/stream/packet.h @@ -24,6 +24,9 @@ typedef NTSTATUS (*packet_full_request_fn_t)(void *private, DATA_BLOB blob, size_t *packet_size); typedef NTSTATUS (*packet_callback_fn_t)(void *private, DATA_BLOB blob); + +/* Used to notify that a packet has been sent, and is on the wire */ +typedef void (*packet_send_callback_fn_t)(void *private); typedef void (*packet_error_handler_fn_t)(void *private, NTSTATUS status); @@ -43,6 +46,9 @@ void packet_recv(struct packet_context *pc); void packet_recv_disable(struct packet_context *pc); void packet_recv_enable(struct packet_context *pc); NTSTATUS packet_send(struct packet_context *pc, DATA_BLOB blob); +NTSTATUS packet_send_callback(struct packet_context *pc, DATA_BLOB blob, + packet_send_callback_fn_t send_callback, + void *private); void packet_queue_run(struct packet_context *pc); /* diff --git a/source4/lib/tls/tls.c b/source4/lib/tls/tls.c index 1ba8ae9779..f9213af2a7 100644 --- a/source4/lib/tls/tls.c +++ b/source4/lib/tls/tls.c @@ -444,7 +444,7 @@ struct socket_context *tls_init_server(struct tls_params *params, nt_status = socket_create_with_ops(socket, &tls_socket_ops, &new_sock, SOCKET_TYPE_STREAM, - socket->flags | SOCKET_FLAG_FAKE); + socket->flags | SOCKET_FLAG_ENCRYPT); if (!NT_STATUS_IS_OK(nt_status)) { return NULL; } @@ -524,7 +524,7 @@ struct socket_context *tls_init_client(struct socket_context *socket, nt_status = socket_create_with_ops(socket, &tls_socket_ops, &new_sock, SOCKET_TYPE_STREAM, - socket->flags | SOCKET_FLAG_FAKE); + socket->flags | SOCKET_FLAG_ENCRYPT); if (!NT_STATUS_IS_OK(nt_status)) { return NULL; } diff --git a/source4/libcli/ldap/config.mk b/source4/libcli/ldap/config.mk index 88ebc3256f..e5a7133cfa 100644 --- a/source4/libcli/ldap/config.mk +++ b/source4/libcli/ldap/config.mk @@ -11,7 +11,7 @@ OBJ_FILES = ldap.o \ ldap_ildap.o \ ldap_controls.o PUBLIC_DEPENDENCIES = LIBSAMBA-ERRORS LIBEVENTS LIBPACKET -PRIVATE_DEPENDENCIES = LIBCLI_COMPOSITE samba-socket LIBCLI_RESOLVE NDR_SAMR LIBTLS ASN1_UTIL +PRIVATE_DEPENDENCIES = LIBCLI_COMPOSITE samba-socket LIBCLI_RESOLVE NDR_SAMR LIBTLS ASN1_UTIL GENSEC_SOCKET #PRIVATE_DEPENDENCIES = gensec # End SUBSYSTEM LIBCLI_LDAP ################################# diff --git a/source4/libcli/ldap/ldap_bind.c b/source4/libcli/ldap/ldap_bind.c index 6714d68b0e..2b209c3871 100644 --- a/source4/libcli/ldap/ldap_bind.c +++ b/source4/libcli/ldap/ldap_bind.c @@ -27,6 +27,8 @@ #include "libcli/ldap/ldap_client.h" #include "lib/tls/tls.h" #include "auth/auth.h" +#include "auth/gensec/socket.h" +#include "lib/stream/packet.h" struct ldap_simple_creds { const char *dn; @@ -365,15 +367,23 @@ NTSTATUS ldap_bind_sasl(struct ldap_connection *conn, struct cli_credentials *cr } } - if (NT_STATUS_IS_OK(status) && - (gensec_have_feature(conn->gensec, GENSEC_FEATURE_SEAL) || - gensec_have_feature(conn->gensec, GENSEC_FEATURE_SIGN))) { - conn->enable_wrap = True; - } - talloc_free(tmp_ctx); if (NT_STATUS_IS_OK(status)) { + struct socket_context *socket = gensec_socket_init(conn->gensec, + conn->sock, + conn->event.event_ctx, + ldap_read_io_handler, + conn); + if (socket) { + conn->sock = socket; + talloc_steal(conn->sock, socket); + packet_set_socket(conn->packet, socket); + } else { + status = NT_STATUS_NO_MEMORY; + goto failed; + } + conn->bind.type = LDAP_BIND_SASL; conn->bind.creds = creds; } diff --git a/source4/libcli/ldap/ldap_client.c b/source4/libcli/ldap/ldap_client.c index 07b7f2b412..2e834b5244 100644 --- a/source4/libcli/ldap/ldap_client.c +++ b/source4/libcli/ldap/ldap_client.c @@ -165,25 +165,13 @@ static void ldap_match_message(struct ldap_connection *conn, struct ldap_message /* - check if a blob is a complete ldap packet - handle wrapper or unwrapped connections + decode/process LDAP data */ -NTSTATUS ldap_complete_packet(void *private_data, DATA_BLOB blob, size_t *size) -{ - struct ldap_connection *conn = talloc_get_type(private_data, - struct ldap_connection); - if (conn->enable_wrap) { - return packet_full_request_u32(private_data, blob, size); - } - return ldap_full_packet(private_data, blob, size); -} - -/* - decode/process plain data -*/ -static NTSTATUS ldap_decode_plain(struct ldap_connection *conn, DATA_BLOB blob) +static NTSTATUS ldap_recv_handler(void *private_data, DATA_BLOB blob) { struct asn1_data asn1; + struct ldap_connection *conn = talloc_get_type(private_data, + struct ldap_connection); struct ldap_message *msg = talloc(conn, struct ldap_message); if (msg == NULL) { @@ -205,60 +193,14 @@ static NTSTATUS ldap_decode_plain(struct ldap_connection *conn, DATA_BLOB blob) return NT_STATUS_OK; } -/* - decode/process wrapped data -*/ -static NTSTATUS ldap_decode_wrapped(struct ldap_connection *conn, DATA_BLOB blob) -{ - DATA_BLOB wrapped, unwrapped; - struct asn1_data asn1; - struct ldap_message *msg = talloc(conn, struct ldap_message); - NTSTATUS status; - - if (msg == NULL) { - return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); - } - - wrapped = data_blob_const(blob.data+4, blob.length-4); - - status = gensec_unwrap(conn->gensec, msg, &wrapped, &unwrapped); - if (!NT_STATUS_IS_OK(status)) { - return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); - } - - data_blob_free(&blob); - - if (!asn1_load(&asn1, unwrapped)) { - return NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR); - } - - while (ldap_decode(&asn1, msg)) { - ldap_match_message(conn, msg); - msg = talloc(conn, struct ldap_message); - } - - talloc_free(msg); - asn1_free(&asn1); - - return NT_STATUS_OK; -} - - -/* - handle ldap recv events -*/ -static NTSTATUS ldap_recv_handler(void *private_data, DATA_BLOB blob) +/* Handle read events, from the GENSEC socket callback, or real events */ +void ldap_read_io_handler(void *private_data, uint16_t flags) { struct ldap_connection *conn = talloc_get_type(private_data, struct ldap_connection); - if (conn->enable_wrap) { - return ldap_decode_wrapped(conn, blob); - } - - return ldap_decode_plain(conn, blob); + packet_recv(conn->packet); } - /* handle ldap socket events */ @@ -272,7 +214,7 @@ static void ldap_io_handler(struct event_context *ev, struct fd_event *fde, if (!tls_enabled(conn->sock)) return; } if (flags & EVENT_FD_READ) { - packet_recv(conn->packet); + ldap_read_io_handler(private_data, flags); } } @@ -417,7 +359,7 @@ static void ldap_connect_recv_conn(struct composite_context *ctx) packet_set_private(conn->packet, conn); packet_set_socket(conn->packet, conn->sock); packet_set_callback(conn->packet, ldap_recv_handler); - packet_set_full_request(conn->packet, ldap_complete_packet); + packet_set_full_request(conn->packet, ldap_full_packet); packet_set_error_handler(conn->packet, ldap_error_handler); packet_set_event_context(conn->packet, conn->event.event_ctx); packet_set_fde(conn->packet, conn->event.fde); @@ -561,24 +503,6 @@ struct ldap_request *ldap_request_send(struct ldap_connection *conn, goto failed; } - /* possibly encrypt/sign the request */ - if (conn->enable_wrap) { - DATA_BLOB wrapped; - - status = gensec_wrap(conn->gensec, req, &req->data, &wrapped); - if (!NT_STATUS_IS_OK(status)) { - goto failed; - } - data_blob_free(&req->data); - req->data = data_blob_talloc(req, NULL, wrapped.length + 4); - if (req->data.data == NULL) { - goto failed; - } - RSIVAL(req->data.data, 0, wrapped.length); - memcpy(req->data.data+4, wrapped.data, wrapped.length); - data_blob_free(&wrapped); - } - status = packet_send(conn->packet, req->data); if (!NT_STATUS_IS_OK(status)) { goto failed; diff --git a/source4/libcli/ldap/ldap_client.h b/source4/libcli/ldap/ldap_client.h index 28b9f2763c..849737d8a9 100644 --- a/source4/libcli/ldap/ldap_client.h +++ b/source4/libcli/ldap/ldap_client.h @@ -80,9 +80,6 @@ struct ldap_connection { /* Let's support SASL */ struct gensec_security *gensec; - /* set if we are wrapping requests */ - BOOL enable_wrap; - /* the default timeout for messages */ int timeout; diff --git a/source4/smbd/service_stream.c b/source4/smbd/service_stream.c index 59e87304d3..d238fc4128 100644 --- a/source4/smbd/service_stream.c +++ b/source4/smbd/service_stream.c @@ -100,6 +100,11 @@ static void stream_io_handler(struct event_context *ev, struct fd_event *fde, } } +void stream_io_handler_callback(void *conn, uint16_t flags) +{ + stream_io_handler(NULL, NULL, flags, conn); +} + /* this creates a stream_connection from an already existing connection, used for protocols, where a client connection needs to switched into |