mirror of
https://github.com/samba-team/samba.git
synced 2024-12-23 17:34:34 +03:00
7f692601c5
Doing so is undefined behaviour. Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz> Reviewed-by: Andrew Bartlett <abartlet@samba.org>
1210 lines
27 KiB
C
1210 lines
27 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
cldap client library
|
|
|
|
Copyright (C) Andrew Tridgell 2005
|
|
Copyright (C) Stefan Metzmacher 2009
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/*
|
|
see RFC1798 for details of CLDAP
|
|
|
|
basic properties
|
|
- carried over UDP on port 389
|
|
- request and response matched by message ID
|
|
- request consists of only a single searchRequest element
|
|
- response can be in one of two forms
|
|
- a single searchResponse, followed by a searchResult
|
|
- a single searchResult
|
|
*/
|
|
|
|
#include "includes.h"
|
|
#include <tevent.h>
|
|
#include "../lib/util/dlinklist.h"
|
|
#include "../libcli/ldap/ldap_message.h"
|
|
#include "../libcli/ldap/ldap_ndr.h"
|
|
#include "../libcli/cldap/cldap.h"
|
|
#include "../lib/tsocket/tsocket.h"
|
|
#include "../libcli/security/dom_sid.h"
|
|
#include "../librpc/gen_ndr/ndr_nbt.h"
|
|
#include "../lib/util/asn1.h"
|
|
#include "../lib/util/tevent_ntstatus.h"
|
|
#include "lib/util/idtree_random.h"
|
|
|
|
#undef strcasecmp
|
|
|
|
/*
|
|
context structure for operations on cldap packets
|
|
*/
|
|
struct cldap_socket {
|
|
/* the low level socket */
|
|
struct tdgram_context *sock;
|
|
|
|
/*
|
|
* Are we in connected mode, which means
|
|
* we get ICMP errors back instead of timing
|
|
* out requests. And we can only send requests
|
|
* to the connected peer.
|
|
*/
|
|
bool connected;
|
|
|
|
/* the queue for outgoing dgrams */
|
|
struct tevent_queue *send_queue;
|
|
|
|
/* do we have an async tsocket_recvfrom request pending */
|
|
struct tevent_req *recv_subreq;
|
|
|
|
struct {
|
|
/* a queue of pending search requests */
|
|
struct cldap_search_state *list;
|
|
|
|
/* mapping from message_id to pending request */
|
|
struct idr_context *idr;
|
|
} searches;
|
|
|
|
/* what to do with incoming request packets */
|
|
struct {
|
|
struct tevent_context *ev;
|
|
void (*handler)(struct cldap_socket *,
|
|
void *private_data,
|
|
struct cldap_incoming *);
|
|
void *private_data;
|
|
} incoming;
|
|
};
|
|
|
|
struct cldap_search_state {
|
|
struct cldap_search_state *prev, *next;
|
|
|
|
struct {
|
|
struct tevent_context *ev;
|
|
struct cldap_socket *cldap;
|
|
} caller;
|
|
|
|
int message_id;
|
|
|
|
struct {
|
|
uint32_t idx;
|
|
uint32_t delay;
|
|
uint32_t count;
|
|
struct tsocket_address *dest;
|
|
DATA_BLOB blob;
|
|
} request;
|
|
|
|
struct {
|
|
struct cldap_incoming *in;
|
|
struct asn1_data *asn1;
|
|
} response;
|
|
|
|
struct tevent_req *req;
|
|
};
|
|
|
|
/*
|
|
* For CLDAP we limit the maximum search request size to 4kb
|
|
*/
|
|
#define MAX_SEARCH_REQUEST 4096
|
|
|
|
static int cldap_socket_destructor(struct cldap_socket *c)
|
|
{
|
|
while (c->searches.list) {
|
|
struct cldap_search_state *s = c->searches.list;
|
|
DLIST_REMOVE(c->searches.list, s);
|
|
ZERO_STRUCT(s->caller);
|
|
}
|
|
|
|
talloc_free(c->recv_subreq);
|
|
talloc_free(c->send_queue);
|
|
talloc_free(c->sock);
|
|
return 0;
|
|
}
|
|
|
|
static void cldap_recvfrom_done(struct tevent_req *subreq);
|
|
|
|
static bool cldap_recvfrom_setup(struct cldap_socket *c)
|
|
{
|
|
struct tevent_context *ev;
|
|
|
|
if (c->recv_subreq) {
|
|
return true;
|
|
}
|
|
|
|
if (!c->searches.list && !c->incoming.handler) {
|
|
return true;
|
|
}
|
|
|
|
ev = c->incoming.ev;
|
|
if (ev == NULL) {
|
|
/* this shouldn't happen but should be protected against */
|
|
if (c->searches.list == NULL) {
|
|
return false;
|
|
}
|
|
ev = c->searches.list->caller.ev;
|
|
}
|
|
|
|
c->recv_subreq = tdgram_recvfrom_send(c, ev, c->sock);
|
|
if (!c->recv_subreq) {
|
|
return false;
|
|
}
|
|
tevent_req_set_callback(c->recv_subreq, cldap_recvfrom_done, c);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void cldap_recvfrom_stop(struct cldap_socket *c)
|
|
{
|
|
if (!c->recv_subreq) {
|
|
return;
|
|
}
|
|
|
|
if (c->searches.list || c->incoming.handler) {
|
|
return;
|
|
}
|
|
|
|
talloc_free(c->recv_subreq);
|
|
c->recv_subreq = NULL;
|
|
}
|
|
|
|
static bool cldap_socket_recv_dgram(struct cldap_socket *c,
|
|
struct cldap_incoming *in);
|
|
|
|
static void cldap_recvfrom_done(struct tevent_req *subreq)
|
|
{
|
|
struct cldap_socket *c = tevent_req_callback_data(subreq,
|
|
struct cldap_socket);
|
|
struct cldap_incoming *in = NULL;
|
|
ssize_t ret;
|
|
bool setup_done;
|
|
|
|
c->recv_subreq = NULL;
|
|
|
|
in = talloc_zero(c, struct cldap_incoming);
|
|
if (!in) {
|
|
goto nomem;
|
|
}
|
|
|
|
ret = tdgram_recvfrom_recv(subreq,
|
|
&in->recv_errno,
|
|
in,
|
|
&in->buf,
|
|
&in->src);
|
|
talloc_free(subreq);
|
|
subreq = NULL;
|
|
if (ret >= 0) {
|
|
in->len = ret;
|
|
}
|
|
if (ret == -1 && in->recv_errno == 0) {
|
|
in->recv_errno = EIO;
|
|
}
|
|
|
|
/* this function should free or steal 'in' */
|
|
setup_done = cldap_socket_recv_dgram(c, in);
|
|
in = NULL;
|
|
|
|
if (!setup_done && !cldap_recvfrom_setup(c)) {
|
|
goto nomem;
|
|
}
|
|
|
|
return;
|
|
|
|
nomem:
|
|
talloc_free(subreq);
|
|
talloc_free(in);
|
|
}
|
|
|
|
/*
|
|
handle recv events on a cldap socket
|
|
*/
|
|
static bool cldap_socket_recv_dgram(struct cldap_socket *c,
|
|
struct cldap_incoming *in)
|
|
{
|
|
struct asn1_data *asn1;
|
|
void *p;
|
|
struct cldap_search_state *search;
|
|
NTSTATUS status;
|
|
struct ldap_request_limits limits = {
|
|
.max_search_size = MAX_SEARCH_REQUEST
|
|
};
|
|
|
|
if (in->recv_errno != 0) {
|
|
goto error;
|
|
}
|
|
|
|
asn1 = asn1_init(in, ASN1_MAX_TREE_DEPTH);
|
|
if (!asn1) {
|
|
goto nomem;
|
|
}
|
|
|
|
asn1_load_nocopy(asn1, in->buf, in->len);
|
|
|
|
in->ldap_msg = talloc(in, struct ldap_message);
|
|
if (in->ldap_msg == NULL) {
|
|
goto nomem;
|
|
}
|
|
|
|
/* this initial decode is used to find the message id */
|
|
status = ldap_decode(asn1, &limits, NULL, in->ldap_msg);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto nterror;
|
|
}
|
|
|
|
/* find the pending request */
|
|
p = idr_find(c->searches.idr, in->ldap_msg->messageid);
|
|
if (p == NULL) {
|
|
if (!c->incoming.handler) {
|
|
TALLOC_FREE(in);
|
|
return true;
|
|
}
|
|
|
|
/* this function should free or steal 'in' */
|
|
c->incoming.handler(c, c->incoming.private_data, in);
|
|
return false;
|
|
}
|
|
|
|
search = talloc_get_type_abort(p, struct cldap_search_state);
|
|
search->response.in = talloc_move(search, &in);
|
|
|
|
search->response.asn1 = asn1;
|
|
|
|
asn1_load_nocopy(search->response.asn1,
|
|
search->response.in->buf, search->response.in->len);
|
|
|
|
DLIST_REMOVE(c->searches.list, search);
|
|
|
|
if (cldap_recvfrom_setup(c)) {
|
|
tevent_req_done(search->req);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* This request was ok, just defer the notify of the caller
|
|
* and then just fail the next request if needed
|
|
*/
|
|
tevent_req_defer_callback(search->req, search->caller.ev);
|
|
tevent_req_done(search->req);
|
|
|
|
status = NT_STATUS_NO_MEMORY;
|
|
/* in is NULL it this point */
|
|
goto nterror;
|
|
nomem:
|
|
in->recv_errno = ENOMEM;
|
|
error:
|
|
status = map_nt_error_from_unix_common(in->recv_errno);
|
|
nterror:
|
|
TALLOC_FREE(in);
|
|
/* in connected mode the first pending search gets the error */
|
|
if (!c->connected) {
|
|
/* otherwise we just ignore the error */
|
|
return false;
|
|
}
|
|
if (!c->searches.list) {
|
|
return false;
|
|
}
|
|
/*
|
|
* We might called tevent_req_done() for a successful
|
|
* search before, so we better deliver the failure
|
|
* after the success, that is why we better also
|
|
* use tevent_req_defer_callback() here.
|
|
*/
|
|
tevent_req_defer_callback(c->searches.list->req,
|
|
c->searches.list->caller.ev);
|
|
tevent_req_nterror(c->searches.list->req, status);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
initialise a cldap_sock
|
|
*/
|
|
NTSTATUS cldap_socket_init(TALLOC_CTX *mem_ctx,
|
|
const struct tsocket_address *local_addr,
|
|
const struct tsocket_address *remote_addr,
|
|
struct cldap_socket **_cldap)
|
|
{
|
|
struct cldap_socket *c = NULL;
|
|
struct tsocket_address *any = NULL;
|
|
NTSTATUS status;
|
|
int ret;
|
|
const char *fam = NULL;
|
|
|
|
if (local_addr == NULL && remote_addr == NULL) {
|
|
return NT_STATUS_INVALID_PARAMETER_MIX;
|
|
}
|
|
|
|
if (remote_addr) {
|
|
bool is_ipv4;
|
|
bool is_ipv6;
|
|
|
|
is_ipv4 = tsocket_address_is_inet(remote_addr, "ipv4");
|
|
is_ipv6 = tsocket_address_is_inet(remote_addr, "ipv6");
|
|
|
|
if (is_ipv4) {
|
|
fam = "ipv4";
|
|
} else if (is_ipv6) {
|
|
fam = "ipv6";
|
|
} else {
|
|
return NT_STATUS_INVALID_ADDRESS;
|
|
}
|
|
}
|
|
|
|
c = talloc_zero(mem_ctx, struct cldap_socket);
|
|
if (!c) {
|
|
goto nomem;
|
|
}
|
|
|
|
if (!local_addr) {
|
|
/*
|
|
* Here we know the address family of the remote address.
|
|
*/
|
|
if (fam == NULL) {
|
|
return NT_STATUS_INVALID_PARAMETER_MIX;
|
|
}
|
|
|
|
ret = tsocket_address_inet_from_strings(c, fam,
|
|
NULL, 0,
|
|
&any);
|
|
if (ret != 0) {
|
|
status = map_nt_error_from_unix_common(errno);
|
|
goto nterror;
|
|
}
|
|
local_addr = any;
|
|
}
|
|
|
|
c->searches.idr = idr_init(c);
|
|
if (!c->searches.idr) {
|
|
goto nomem;
|
|
}
|
|
|
|
ret = tdgram_inet_udp_socket(local_addr, remote_addr,
|
|
c, &c->sock);
|
|
if (ret != 0) {
|
|
status = map_nt_error_from_unix_common(errno);
|
|
goto nterror;
|
|
}
|
|
talloc_free(any);
|
|
|
|
if (remote_addr) {
|
|
c->connected = true;
|
|
}
|
|
|
|
c->send_queue = tevent_queue_create(c, "cldap_send_queue");
|
|
if (!c->send_queue) {
|
|
goto nomem;
|
|
}
|
|
|
|
talloc_set_destructor(c, cldap_socket_destructor);
|
|
|
|
*_cldap = c;
|
|
return NT_STATUS_OK;
|
|
|
|
nomem:
|
|
status = NT_STATUS_NO_MEMORY;
|
|
nterror:
|
|
talloc_free(c);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
setup a handler for incoming requests
|
|
*/
|
|
NTSTATUS cldap_set_incoming_handler(struct cldap_socket *c,
|
|
struct tevent_context *ev,
|
|
void (*handler)(struct cldap_socket *,
|
|
void *private_data,
|
|
struct cldap_incoming *),
|
|
void *private_data)
|
|
{
|
|
if (c->connected) {
|
|
return NT_STATUS_PIPE_CONNECTED;
|
|
}
|
|
|
|
c->incoming.ev = ev;
|
|
c->incoming.handler = handler;
|
|
c->incoming.private_data = private_data;
|
|
|
|
if (!cldap_recvfrom_setup(c)) {
|
|
ZERO_STRUCT(c->incoming);
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
struct cldap_reply_state {
|
|
struct tsocket_address *dest;
|
|
DATA_BLOB blob;
|
|
};
|
|
|
|
static void cldap_reply_state_destroy(struct tevent_req *subreq);
|
|
|
|
/*
|
|
queue a cldap reply for send
|
|
*/
|
|
NTSTATUS cldap_reply_send(struct cldap_socket *cldap, struct cldap_reply *io)
|
|
{
|
|
struct cldap_reply_state *state = NULL;
|
|
struct ldap_message *msg;
|
|
DATA_BLOB blob1, blob2;
|
|
NTSTATUS status;
|
|
struct tevent_req *subreq;
|
|
|
|
if (cldap->connected) {
|
|
return NT_STATUS_PIPE_CONNECTED;
|
|
}
|
|
|
|
if (cldap->incoming.ev == NULL) {
|
|
return NT_STATUS_INVALID_PIPE_STATE;
|
|
}
|
|
|
|
if (!io->dest) {
|
|
return NT_STATUS_INVALID_ADDRESS;
|
|
}
|
|
|
|
state = talloc(cldap, struct cldap_reply_state);
|
|
NT_STATUS_HAVE_NO_MEMORY(state);
|
|
|
|
state->dest = tsocket_address_copy(io->dest, state);
|
|
if (!state->dest) {
|
|
goto nomem;
|
|
}
|
|
|
|
msg = talloc(state, struct ldap_message);
|
|
if (!msg) {
|
|
goto nomem;
|
|
}
|
|
|
|
msg->messageid = io->messageid;
|
|
msg->controls = NULL;
|
|
|
|
if (io->response) {
|
|
msg->type = LDAP_TAG_SearchResultEntry;
|
|
msg->r.SearchResultEntry = *io->response;
|
|
|
|
if (!ldap_encode(msg, NULL, &blob1, state)) {
|
|
status = NT_STATUS_INVALID_PARAMETER;
|
|
goto failed;
|
|
}
|
|
} else {
|
|
blob1 = data_blob(NULL, 0);
|
|
}
|
|
|
|
msg->type = LDAP_TAG_SearchResultDone;
|
|
msg->r.SearchResultDone = *io->result;
|
|
|
|
if (!ldap_encode(msg, NULL, &blob2, state)) {
|
|
status = NT_STATUS_INVALID_PARAMETER;
|
|
goto failed;
|
|
}
|
|
talloc_free(msg);
|
|
|
|
state->blob = data_blob_talloc(state, NULL, blob1.length + blob2.length);
|
|
if (!state->blob.data) {
|
|
goto nomem;
|
|
}
|
|
|
|
if (blob1.data != NULL) {
|
|
memcpy(state->blob.data, blob1.data, blob1.length);
|
|
}
|
|
memcpy(state->blob.data+blob1.length, blob2.data, blob2.length);
|
|
data_blob_free(&blob1);
|
|
data_blob_free(&blob2);
|
|
|
|
subreq = tdgram_sendto_queue_send(state,
|
|
cldap->incoming.ev,
|
|
cldap->sock,
|
|
cldap->send_queue,
|
|
state->blob.data,
|
|
state->blob.length,
|
|
state->dest);
|
|
if (!subreq) {
|
|
goto nomem;
|
|
}
|
|
/* the callback will just free the state, as we don't need a result */
|
|
tevent_req_set_callback(subreq, cldap_reply_state_destroy, state);
|
|
|
|
return NT_STATUS_OK;
|
|
|
|
nomem:
|
|
status = NT_STATUS_NO_MEMORY;
|
|
failed:
|
|
talloc_free(state);
|
|
return status;
|
|
}
|
|
|
|
static void cldap_reply_state_destroy(struct tevent_req *subreq)
|
|
{
|
|
struct cldap_reply_state *state = tevent_req_callback_data(subreq,
|
|
struct cldap_reply_state);
|
|
|
|
/* we don't want to know the result here, we just free the state */
|
|
talloc_free(subreq);
|
|
talloc_free(state);
|
|
}
|
|
|
|
static int cldap_search_state_destructor(struct cldap_search_state *s)
|
|
{
|
|
if (s->caller.cldap) {
|
|
if (s->message_id != -1) {
|
|
idr_remove(s->caller.cldap->searches.idr, s->message_id);
|
|
s->message_id = -1;
|
|
}
|
|
DLIST_REMOVE(s->caller.cldap->searches.list, s);
|
|
cldap_recvfrom_stop(s->caller.cldap);
|
|
ZERO_STRUCT(s->caller);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void cldap_search_state_queue_done(struct tevent_req *subreq);
|
|
static void cldap_search_state_wakeup_done(struct tevent_req *subreq);
|
|
|
|
/*
|
|
queue a cldap reply for send
|
|
*/
|
|
struct tevent_req *cldap_search_send(TALLOC_CTX *mem_ctx,
|
|
struct tevent_context *ev,
|
|
struct cldap_socket *cldap,
|
|
const struct cldap_search *io)
|
|
{
|
|
struct tevent_req *req, *subreq;
|
|
struct cldap_search_state *state = NULL;
|
|
struct ldap_message *msg;
|
|
struct ldap_SearchRequest *search;
|
|
struct timeval now;
|
|
struct timeval end;
|
|
uint32_t i;
|
|
int ret;
|
|
|
|
req = tevent_req_create(mem_ctx, &state,
|
|
struct cldap_search_state);
|
|
if (!req) {
|
|
return NULL;
|
|
}
|
|
state->caller.ev = ev;
|
|
state->req = req;
|
|
state->caller.cldap = cldap;
|
|
state->message_id = -1;
|
|
|
|
talloc_set_destructor(state, cldap_search_state_destructor);
|
|
|
|
if (state->caller.cldap == NULL) {
|
|
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
|
|
goto post;
|
|
}
|
|
|
|
if (io->in.dest_address) {
|
|
if (cldap->connected) {
|
|
tevent_req_nterror(req, NT_STATUS_PIPE_CONNECTED);
|
|
goto post;
|
|
}
|
|
ret = tsocket_address_inet_from_strings(state,
|
|
"ip",
|
|
io->in.dest_address,
|
|
io->in.dest_port,
|
|
&state->request.dest);
|
|
if (ret != 0) {
|
|
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
|
|
goto post;
|
|
}
|
|
} else {
|
|
if (!cldap->connected) {
|
|
tevent_req_nterror(req, NT_STATUS_INVALID_ADDRESS);
|
|
goto post;
|
|
}
|
|
state->request.dest = NULL;
|
|
}
|
|
|
|
state->message_id = idr_get_new_random(
|
|
cldap->searches.idr, state, 1, UINT16_MAX);
|
|
if (state->message_id == -1) {
|
|
tevent_req_nterror(req, NT_STATUS_INSUFFICIENT_RESOURCES);
|
|
goto post;
|
|
}
|
|
|
|
msg = talloc(state, struct ldap_message);
|
|
if (tevent_req_nomem(msg, req)) {
|
|
goto post;
|
|
}
|
|
|
|
msg->messageid = state->message_id;
|
|
msg->type = LDAP_TAG_SearchRequest;
|
|
msg->controls = NULL;
|
|
search = &msg->r.SearchRequest;
|
|
|
|
search->basedn = "";
|
|
search->scope = LDAP_SEARCH_SCOPE_BASE;
|
|
search->deref = LDAP_DEREFERENCE_NEVER;
|
|
search->timelimit = 0;
|
|
search->sizelimit = 0;
|
|
search->attributesonly = false;
|
|
search->num_attributes = str_list_length(io->in.attributes);
|
|
search->attributes = io->in.attributes;
|
|
search->tree = ldb_parse_tree(msg, io->in.filter);
|
|
if (tevent_req_nomem(search->tree, req)) {
|
|
goto post;
|
|
}
|
|
|
|
if (!ldap_encode(msg, NULL, &state->request.blob, state)) {
|
|
tevent_req_nterror(req, NT_STATUS_INVALID_PARAMETER);
|
|
goto post;
|
|
}
|
|
talloc_free(msg);
|
|
|
|
state->request.idx = 0;
|
|
state->request.delay = 10*1000*1000;
|
|
state->request.count = 3;
|
|
if (io->in.timeout > 0) {
|
|
state->request.delay = io->in.timeout * 1000 * 1000;
|
|
state->request.count = io->in.retries + 1;
|
|
}
|
|
|
|
now = tevent_timeval_current();
|
|
end = now;
|
|
for (i = 0; i < state->request.count; i++) {
|
|
end = tevent_timeval_add(&end, state->request.delay / 1000000,
|
|
state->request.delay % 1000000);
|
|
}
|
|
|
|
if (!tevent_req_set_endtime(req, state->caller.ev, end)) {
|
|
goto post;
|
|
}
|
|
|
|
subreq = tdgram_sendto_queue_send(state,
|
|
state->caller.ev,
|
|
state->caller.cldap->sock,
|
|
state->caller.cldap->send_queue,
|
|
state->request.blob.data,
|
|
state->request.blob.length,
|
|
state->request.dest);
|
|
if (tevent_req_nomem(subreq, req)) {
|
|
goto post;
|
|
}
|
|
tevent_req_set_callback(subreq, cldap_search_state_queue_done, req);
|
|
|
|
DLIST_ADD_END(cldap->searches.list, state);
|
|
|
|
return req;
|
|
|
|
post:
|
|
return tevent_req_post(req, state->caller.ev);
|
|
}
|
|
|
|
static void cldap_search_state_queue_done(struct tevent_req *subreq)
|
|
{
|
|
struct tevent_req *req = tevent_req_callback_data(subreq,
|
|
struct tevent_req);
|
|
struct cldap_search_state *state = tevent_req_data(req,
|
|
struct cldap_search_state);
|
|
ssize_t ret;
|
|
int sys_errno = 0;
|
|
struct timeval next;
|
|
|
|
ret = tdgram_sendto_queue_recv(subreq, &sys_errno);
|
|
talloc_free(subreq);
|
|
if (ret == -1) {
|
|
NTSTATUS status;
|
|
status = map_nt_error_from_unix_common(sys_errno);
|
|
DLIST_REMOVE(state->caller.cldap->searches.list, state);
|
|
ZERO_STRUCT(state->caller.cldap);
|
|
tevent_req_nterror(req, status);
|
|
return;
|
|
}
|
|
|
|
state->request.idx++;
|
|
|
|
/* wait for incoming traffic */
|
|
if (!cldap_recvfrom_setup(state->caller.cldap)) {
|
|
tevent_req_oom(req);
|
|
return;
|
|
}
|
|
|
|
if (state->request.idx > state->request.count) {
|
|
/* we just wait for the response or a timeout */
|
|
return;
|
|
}
|
|
|
|
next = tevent_timeval_current_ofs(state->request.delay / 1000000,
|
|
state->request.delay % 1000000);
|
|
subreq = tevent_wakeup_send(state,
|
|
state->caller.ev,
|
|
next);
|
|
if (tevent_req_nomem(subreq, req)) {
|
|
return;
|
|
}
|
|
tevent_req_set_callback(subreq, cldap_search_state_wakeup_done, req);
|
|
}
|
|
|
|
static void cldap_search_state_wakeup_done(struct tevent_req *subreq)
|
|
{
|
|
struct tevent_req *req = tevent_req_callback_data(subreq,
|
|
struct tevent_req);
|
|
struct cldap_search_state *state = tevent_req_data(req,
|
|
struct cldap_search_state);
|
|
bool ok;
|
|
|
|
ok = tevent_wakeup_recv(subreq);
|
|
talloc_free(subreq);
|
|
if (!ok) {
|
|
tevent_req_nterror(req, NT_STATUS_INTERNAL_ERROR);
|
|
return;
|
|
}
|
|
|
|
subreq = tdgram_sendto_queue_send(state,
|
|
state->caller.ev,
|
|
state->caller.cldap->sock,
|
|
state->caller.cldap->send_queue,
|
|
state->request.blob.data,
|
|
state->request.blob.length,
|
|
state->request.dest);
|
|
if (tevent_req_nomem(subreq, req)) {
|
|
return;
|
|
}
|
|
tevent_req_set_callback(subreq, cldap_search_state_queue_done, req);
|
|
}
|
|
|
|
/*
|
|
receive a cldap reply
|
|
*/
|
|
NTSTATUS cldap_search_recv(struct tevent_req *req,
|
|
TALLOC_CTX *mem_ctx,
|
|
struct cldap_search *io)
|
|
{
|
|
struct cldap_search_state *state = tevent_req_data(req,
|
|
struct cldap_search_state);
|
|
struct ldap_message *ldap_msg;
|
|
NTSTATUS status;
|
|
struct ldap_request_limits limits = {
|
|
.max_search_size = MAX_SEARCH_REQUEST
|
|
};
|
|
|
|
if (tevent_req_is_nterror(req, &status)) {
|
|
goto failed;
|
|
}
|
|
|
|
ldap_msg = talloc(mem_ctx, struct ldap_message);
|
|
if (!ldap_msg) {
|
|
goto nomem;
|
|
}
|
|
|
|
status = ldap_decode(state->response.asn1, &limits, NULL, ldap_msg);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto failed;
|
|
}
|
|
|
|
ZERO_STRUCT(io->out);
|
|
|
|
/* the first possible form has a search result in first place */
|
|
if (ldap_msg->type == LDAP_TAG_SearchResultEntry) {
|
|
io->out.response = talloc(mem_ctx, struct ldap_SearchResEntry);
|
|
if (!io->out.response) {
|
|
goto nomem;
|
|
}
|
|
*io->out.response = ldap_msg->r.SearchResultEntry;
|
|
|
|
/* decode the 2nd part */
|
|
status = ldap_decode(
|
|
state->response.asn1, &limits, NULL, ldap_msg);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
if (ldap_msg->type != LDAP_TAG_SearchResultDone) {
|
|
status = NT_STATUS_LDAP(LDAP_PROTOCOL_ERROR);
|
|
goto failed;
|
|
}
|
|
|
|
io->out.result = talloc(mem_ctx, struct ldap_Result);
|
|
if (!io->out.result) {
|
|
goto nomem;
|
|
}
|
|
*io->out.result = ldap_msg->r.SearchResultDone;
|
|
|
|
if (io->out.result->resultcode != LDAP_SUCCESS) {
|
|
status = NT_STATUS_LDAP(io->out.result->resultcode);
|
|
goto failed;
|
|
}
|
|
|
|
tevent_req_received(req);
|
|
return NT_STATUS_OK;
|
|
|
|
nomem:
|
|
status = NT_STATUS_NO_MEMORY;
|
|
failed:
|
|
tevent_req_received(req);
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
synchronous cldap search
|
|
*/
|
|
NTSTATUS cldap_search(struct cldap_socket *cldap,
|
|
TALLOC_CTX *mem_ctx,
|
|
struct cldap_search *io)
|
|
{
|
|
TALLOC_CTX *frame;
|
|
struct tevent_req *req;
|
|
struct tevent_context *ev;
|
|
NTSTATUS status;
|
|
|
|
if (cldap->searches.list) {
|
|
return NT_STATUS_PIPE_BUSY;
|
|
}
|
|
|
|
if (cldap->incoming.handler) {
|
|
return NT_STATUS_INVALID_PIPE_STATE;
|
|
}
|
|
|
|
frame = talloc_stackframe();
|
|
|
|
ev = samba_tevent_context_init(frame);
|
|
if (ev == NULL) {
|
|
TALLOC_FREE(frame);
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
req = cldap_search_send(mem_ctx, ev, cldap, io);
|
|
if (req == NULL) {
|
|
TALLOC_FREE(frame);
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
if (!tevent_req_poll(req, ev)) {
|
|
status = map_nt_error_from_unix_common(errno);
|
|
TALLOC_FREE(frame);
|
|
return status;
|
|
}
|
|
|
|
status = cldap_search_recv(req, mem_ctx, io);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
TALLOC_FREE(frame);
|
|
return status;
|
|
}
|
|
|
|
TALLOC_FREE(frame);
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
struct cldap_netlogon_state {
|
|
struct cldap_search search;
|
|
};
|
|
|
|
char *cldap_netlogon_create_filter(TALLOC_CTX *mem_ctx,
|
|
const struct cldap_netlogon *io)
|
|
{
|
|
char *filter;
|
|
|
|
filter = talloc_asprintf(mem_ctx, "(&(NtVer=%s)",
|
|
ldap_encode_ndr_uint32(mem_ctx, io->in.version));
|
|
|
|
if (io->in.user) {
|
|
talloc_asprintf_addbuf(&filter, "(User=%s)", io->in.user);
|
|
}
|
|
if (io->in.host) {
|
|
talloc_asprintf_addbuf(&filter, "(Host=%s)", io->in.host);
|
|
}
|
|
if (io->in.realm) {
|
|
talloc_asprintf_addbuf(&filter, "(DnsDomain=%s)", io->in.realm);
|
|
}
|
|
if (io->in.acct_control != -1) {
|
|
talloc_asprintf_addbuf(
|
|
&filter,
|
|
"(AAC=%s)",
|
|
ldap_encode_ndr_uint32(mem_ctx, io->in.acct_control));
|
|
}
|
|
if (io->in.domain_sid) {
|
|
struct dom_sid *sid = dom_sid_parse_talloc(mem_ctx, io->in.domain_sid);
|
|
|
|
talloc_asprintf_addbuf(&filter, "(domainSid=%s)",
|
|
ldap_encode_ndr_dom_sid(mem_ctx, sid));
|
|
}
|
|
if (io->in.domain_guid) {
|
|
struct GUID guid;
|
|
GUID_from_string(io->in.domain_guid, &guid);
|
|
|
|
talloc_asprintf_addbuf(&filter, "(DomainGuid=%s)",
|
|
ldap_encode_ndr_GUID(mem_ctx, &guid));
|
|
}
|
|
talloc_asprintf_addbuf(&filter, ")");
|
|
|
|
return filter;
|
|
}
|
|
|
|
static void cldap_netlogon_state_done(struct tevent_req *subreq);
|
|
/*
|
|
queue a cldap netlogon for send
|
|
*/
|
|
struct tevent_req *cldap_netlogon_send(TALLOC_CTX *mem_ctx,
|
|
struct tevent_context *ev,
|
|
struct cldap_socket *cldap,
|
|
const struct cldap_netlogon *io)
|
|
{
|
|
struct tevent_req *req, *subreq;
|
|
struct cldap_netlogon_state *state;
|
|
char *filter;
|
|
static const char * const attr[] = { "NetLogon", NULL };
|
|
|
|
req = tevent_req_create(mem_ctx, &state,
|
|
struct cldap_netlogon_state);
|
|
if (!req) {
|
|
return NULL;
|
|
}
|
|
|
|
filter = cldap_netlogon_create_filter(state, io);
|
|
if (tevent_req_nomem(filter, req)) {
|
|
goto post;
|
|
}
|
|
|
|
if (io->in.dest_address) {
|
|
state->search.in.dest_address = talloc_strdup(state,
|
|
io->in.dest_address);
|
|
if (tevent_req_nomem(state->search.in.dest_address, req)) {
|
|
goto post;
|
|
}
|
|
state->search.in.dest_port = io->in.dest_port;
|
|
} else {
|
|
state->search.in.dest_address = NULL;
|
|
state->search.in.dest_port = 0;
|
|
}
|
|
state->search.in.filter = filter;
|
|
state->search.in.attributes = attr;
|
|
state->search.in.timeout = 2;
|
|
state->search.in.retries = 2;
|
|
|
|
subreq = cldap_search_send(state, ev, cldap, &state->search);
|
|
if (tevent_req_nomem(subreq, req)) {
|
|
goto post;
|
|
}
|
|
tevent_req_set_callback(subreq, cldap_netlogon_state_done, req);
|
|
|
|
return req;
|
|
post:
|
|
return tevent_req_post(req, ev);
|
|
}
|
|
|
|
static void cldap_netlogon_state_done(struct tevent_req *subreq)
|
|
{
|
|
struct tevent_req *req = tevent_req_callback_data(subreq,
|
|
struct tevent_req);
|
|
struct cldap_netlogon_state *state = tevent_req_data(req,
|
|
struct cldap_netlogon_state);
|
|
NTSTATUS status;
|
|
|
|
status = cldap_search_recv(subreq, state, &state->search);
|
|
talloc_free(subreq);
|
|
|
|
if (tevent_req_nterror(req, status)) {
|
|
return;
|
|
}
|
|
|
|
tevent_req_done(req);
|
|
}
|
|
|
|
/*
|
|
receive a cldap netlogon reply
|
|
*/
|
|
NTSTATUS cldap_netlogon_recv(struct tevent_req *req,
|
|
TALLOC_CTX *mem_ctx,
|
|
struct cldap_netlogon *io)
|
|
{
|
|
struct cldap_netlogon_state *state = tevent_req_data(req,
|
|
struct cldap_netlogon_state);
|
|
NTSTATUS status = NT_STATUS_UNSUCCESSFUL;
|
|
DATA_BLOB *data;
|
|
|
|
if (tevent_req_is_nterror(req, &status)) {
|
|
goto failed;
|
|
}
|
|
|
|
if (state->search.out.response == NULL) {
|
|
status = NT_STATUS_NOT_FOUND;
|
|
goto failed;
|
|
}
|
|
|
|
if (state->search.out.response->num_attributes != 1 ||
|
|
strcasecmp(state->search.out.response->attributes[0].name, "netlogon") != 0 ||
|
|
state->search.out.response->attributes[0].num_values != 1 ||
|
|
state->search.out.response->attributes[0].values->length < 2) {
|
|
status = NT_STATUS_UNEXPECTED_NETWORK_ERROR;
|
|
goto failed;
|
|
}
|
|
data = state->search.out.response->attributes[0].values;
|
|
|
|
status = pull_netlogon_samlogon_response(data, mem_ctx,
|
|
&io->out.netlogon);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
goto failed;
|
|
}
|
|
|
|
if (io->in.map_response) {
|
|
map_netlogon_samlogon_response(&io->out.netlogon);
|
|
}
|
|
|
|
status = NT_STATUS_OK;
|
|
failed:
|
|
tevent_req_received(req);
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
sync cldap netlogon search
|
|
*/
|
|
NTSTATUS cldap_netlogon(struct cldap_socket *cldap,
|
|
TALLOC_CTX *mem_ctx,
|
|
struct cldap_netlogon *io)
|
|
{
|
|
TALLOC_CTX *frame;
|
|
struct tevent_req *req;
|
|
struct tevent_context *ev;
|
|
NTSTATUS status;
|
|
|
|
if (cldap->searches.list) {
|
|
return NT_STATUS_PIPE_BUSY;
|
|
}
|
|
|
|
if (cldap->incoming.handler) {
|
|
return NT_STATUS_INVALID_PIPE_STATE;
|
|
}
|
|
|
|
frame = talloc_stackframe();
|
|
|
|
ev = samba_tevent_context_init(frame);
|
|
if (ev == NULL) {
|
|
TALLOC_FREE(frame);
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
req = cldap_netlogon_send(mem_ctx, ev, cldap, io);
|
|
if (req == NULL) {
|
|
TALLOC_FREE(frame);
|
|
return NT_STATUS_NO_MEMORY;
|
|
}
|
|
|
|
if (!tevent_req_poll(req, ev)) {
|
|
status = map_nt_error_from_unix_common(errno);
|
|
TALLOC_FREE(frame);
|
|
return status;
|
|
}
|
|
|
|
status = cldap_netlogon_recv(req, mem_ctx, io);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
TALLOC_FREE(frame);
|
|
return status;
|
|
}
|
|
|
|
TALLOC_FREE(frame);
|
|
return NT_STATUS_OK;
|
|
}
|
|
|
|
|
|
/*
|
|
send an empty reply (used on any error, so the client doesn't keep waiting
|
|
or send the bad request again)
|
|
*/
|
|
NTSTATUS cldap_empty_reply(struct cldap_socket *cldap,
|
|
uint32_t message_id,
|
|
struct tsocket_address *dest)
|
|
{
|
|
NTSTATUS status;
|
|
struct cldap_reply reply;
|
|
struct ldap_Result result;
|
|
|
|
reply.messageid = message_id;
|
|
reply.dest = dest;
|
|
reply.response = NULL;
|
|
reply.result = &result;
|
|
|
|
ZERO_STRUCT(result);
|
|
|
|
status = cldap_reply_send(cldap, &reply);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
send an error reply (used on any error, so the client doesn't keep waiting
|
|
or send the bad request again)
|
|
*/
|
|
NTSTATUS cldap_error_reply(struct cldap_socket *cldap,
|
|
uint32_t message_id,
|
|
struct tsocket_address *dest,
|
|
int resultcode,
|
|
const char *errormessage)
|
|
{
|
|
NTSTATUS status;
|
|
struct cldap_reply reply;
|
|
struct ldap_Result result;
|
|
|
|
reply.messageid = message_id;
|
|
reply.dest = dest;
|
|
reply.response = NULL;
|
|
reply.result = &result;
|
|
|
|
ZERO_STRUCT(result);
|
|
result.resultcode = resultcode;
|
|
result.errormessage = errormessage;
|
|
|
|
status = cldap_reply_send(cldap, &reply);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
send a netlogon reply
|
|
*/
|
|
NTSTATUS cldap_netlogon_reply(struct cldap_socket *cldap,
|
|
uint32_t message_id,
|
|
struct tsocket_address *dest,
|
|
uint32_t version,
|
|
struct netlogon_samlogon_response *netlogon)
|
|
{
|
|
NTSTATUS status;
|
|
struct cldap_reply reply;
|
|
struct ldap_SearchResEntry response;
|
|
struct ldap_Result result;
|
|
TALLOC_CTX *tmp_ctx = talloc_new(cldap);
|
|
DATA_BLOB blob;
|
|
|
|
status = push_netlogon_samlogon_response(&blob, tmp_ctx,
|
|
netlogon);
|
|
if (!NT_STATUS_IS_OK(status)) {
|
|
talloc_free(tmp_ctx);
|
|
return status;
|
|
}
|
|
reply.messageid = message_id;
|
|
reply.dest = dest;
|
|
reply.response = &response;
|
|
reply.result = &result;
|
|
|
|
ZERO_STRUCT(result);
|
|
|
|
response.dn = "";
|
|
response.num_attributes = 1;
|
|
response.attributes = talloc(tmp_ctx, struct ldb_message_element);
|
|
NT_STATUS_HAVE_NO_MEMORY(response.attributes);
|
|
response.attributes->name = "netlogon";
|
|
response.attributes->num_values = 1;
|
|
response.attributes->values = &blob;
|
|
|
|
status = cldap_reply_send(cldap, &reply);
|
|
|
|
talloc_free(tmp_ctx);
|
|
|
|
return status;
|
|
}
|
|
|