mirror of
https://github.com/samba-team/samba.git
synced 2024-12-24 21:34:56 +03:00
fed660877c
(This used to be commit a0ac9a8ffd
)
647 lines
14 KiB
C
647 lines
14 KiB
C
/*
|
|
Unix SMB/CIFS implementation.
|
|
|
|
winbind ldap proxy code
|
|
|
|
Copyright (C) Volker Lendecke
|
|
|
|
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 "winbindd.h"
|
|
|
|
/* This rw-buf api is made to avoid memcpy. For now do that like mad... The
|
|
idea is to write into a circular list of buffers where the ideal case is
|
|
that a read(2) holds a complete request that is then thrown away
|
|
completely. */
|
|
|
|
struct ldap_message_queue {
|
|
struct ldap_message_queue *prev, *next;
|
|
struct ldap_message *msg;
|
|
};
|
|
|
|
struct rw_buffer {
|
|
uint8_t *data;
|
|
size_t ofs, length;
|
|
};
|
|
|
|
struct winbind_ldap_client {
|
|
struct winbind_ldap_client *next, *prev;
|
|
int sock;
|
|
BOOL finished;
|
|
struct rw_buffer in_buffer, out_buffer;
|
|
};
|
|
|
|
static struct winbind_ldap_client *ldap_clients;
|
|
|
|
struct winbind_ldap_server {
|
|
struct winbind_ldap_server *next, *prev;
|
|
int sock;
|
|
BOOL ready; /* Bind successful? */
|
|
BOOL finished;
|
|
struct rw_buffer in_buffer, out_buffer;
|
|
int messageid;
|
|
};
|
|
|
|
static struct winbind_ldap_server *ldap_servers;
|
|
|
|
struct pending_ldap_message {
|
|
struct pending_ldap_message *next, *prev;
|
|
struct ldap_message *msg; /* The message the client sent us */
|
|
int our_msgid; /* The messageid we used */
|
|
struct winbind_ldap_client *client;
|
|
};
|
|
|
|
struct pending_ldap_message *pending_messages;
|
|
|
|
static BOOL append_to_buf(struct rw_buffer *buf, uint8_t *data, size_t length)
|
|
{
|
|
buf->data = SMB_REALLOC(buf->data, buf->length+length);
|
|
|
|
if (buf->data == NULL)
|
|
return False;
|
|
|
|
memcpy(buf->data+buf->length, data, length);
|
|
|
|
buf->length += length;
|
|
return True;
|
|
}
|
|
|
|
static BOOL read_into_buf(int fd, struct rw_buffer *buf)
|
|
{
|
|
char tmp_buf[1024];
|
|
int len;
|
|
|
|
len = read(fd, tmp_buf, sizeof(tmp_buf));
|
|
if (len == 0)
|
|
return False;
|
|
|
|
return append_to_buf(buf, tmp_buf, len);
|
|
}
|
|
|
|
static void peek_into_buf(struct rw_buffer *buf, uint8_t **out,
|
|
size_t *out_length)
|
|
{
|
|
*out = buf->data;
|
|
*out_length = buf->length;
|
|
}
|
|
|
|
static void consumed_from_buf(struct rw_buffer *buf, size_t length)
|
|
{
|
|
uint8_t *new = memdup(buf->data+length, buf->length-length);
|
|
free(buf->data);
|
|
buf->data = new;
|
|
buf->length -= length;
|
|
}
|
|
|
|
static BOOL write_out_of_buf(int fd, struct rw_buffer *buf)
|
|
{
|
|
uint8_t *tmp;
|
|
size_t tmp_length, written;
|
|
|
|
peek_into_buf(buf, &tmp, &tmp_length);
|
|
if (tmp_length == 0)
|
|
return True;
|
|
|
|
written = write(fd, tmp, tmp_length);
|
|
if (written < 0)
|
|
return False;
|
|
|
|
consumed_from_buf(buf, written);
|
|
return True;
|
|
}
|
|
|
|
static BOOL ldap_append_to_buf(struct ldap_message *msg, struct rw_buffer *buf)
|
|
{
|
|
DATA_BLOB blob;
|
|
BOOL res;
|
|
|
|
if (!ldap_encode(msg, &blob))
|
|
return False;
|
|
|
|
res = append_to_buf(buf, blob.data, blob.length);
|
|
|
|
data_blob_free(&blob);
|
|
return res;
|
|
}
|
|
|
|
static void new_ldap_client(int listen_sock)
|
|
{
|
|
struct sockaddr_un sunaddr;
|
|
struct winbind_ldap_client *client;
|
|
socklen_t len;
|
|
int sock;
|
|
|
|
/* Accept connection */
|
|
|
|
len = sizeof(sunaddr);
|
|
|
|
do {
|
|
sock = accept(listen_sock, (struct sockaddr *)&sunaddr, &len);
|
|
} while (sock == -1 && errno == EINTR);
|
|
|
|
if (sock == -1)
|
|
return;
|
|
|
|
DEBUG(6,("accepted socket %d\n", sock));
|
|
|
|
/* Create new connection structure */
|
|
|
|
client = SMB_MALLOC_P(struct winbind_ldap_client);
|
|
|
|
if (client == NULL)
|
|
return;
|
|
|
|
ZERO_STRUCTP(client);
|
|
|
|
client->sock = sock;
|
|
client->finished = False;
|
|
|
|
DLIST_ADD(ldap_clients, client);
|
|
}
|
|
|
|
static struct ldap_message *get_msg_from_buf(struct rw_buffer *buffer,
|
|
BOOL *error)
|
|
{
|
|
uint8_t *buf;
|
|
int buf_length, msg_length;
|
|
DATA_BLOB blob;
|
|
ASN1_DATA data;
|
|
struct ldap_message *msg;
|
|
|
|
DEBUG(10,("ldapsrv_recv\n"));
|
|
|
|
*error = False;
|
|
|
|
peek_into_buf(buffer, &buf, &buf_length);
|
|
|
|
if (buf_length < 8) {
|
|
/* Arbitrary heuristics: ldap messages are longer than eight
|
|
* bytes, and their tag length fits into the eight bytes */
|
|
return NULL;
|
|
}
|
|
|
|
/* LDAP Messages are always SEQUENCES */
|
|
|
|
if (!asn1_object_length(buf, buf_length, ASN1_SEQUENCE(0),
|
|
&msg_length))
|
|
goto disconnect;
|
|
|
|
if (buf_length < msg_length) {
|
|
/* Not enough yet */
|
|
return NULL;
|
|
}
|
|
|
|
/* We've got a complete LDAP request in the in-buffer */
|
|
|
|
blob.data = buf;
|
|
blob.length = msg_length;
|
|
|
|
if (!asn1_load(&data, blob))
|
|
goto disconnect;
|
|
|
|
msg = new_ldap_message();
|
|
|
|
if ((msg == NULL) || !ldap_decode(&data, msg)) {
|
|
asn1_free(&data);
|
|
goto disconnect;
|
|
}
|
|
|
|
asn1_free(&data);
|
|
|
|
consumed_from_buf(buffer, msg_length);
|
|
|
|
return msg;
|
|
|
|
disconnect:
|
|
|
|
*error = True;
|
|
return NULL;
|
|
}
|
|
|
|
static int send_msg_to_server(struct ldap_message *msg,
|
|
struct winbind_ldap_server *server)
|
|
{
|
|
int cli_messageid;
|
|
|
|
cli_messageid = msg->messageid;
|
|
msg->messageid = ldap_servers->messageid;
|
|
|
|
if (!ldap_append_to_buf(msg, &ldap_servers->out_buffer))
|
|
return -1;
|
|
|
|
msg->messageid = cli_messageid;
|
|
return ldap_servers->messageid++;
|
|
}
|
|
|
|
static int send_msg(struct ldap_message *msg)
|
|
{
|
|
/* This is the scheduling routine that should decide where to send
|
|
* stuff. The first attempt is easy: We only have one server. This
|
|
* will change once we handle referrals etc. */
|
|
|
|
SMB_ASSERT(ldap_servers != NULL);
|
|
|
|
if (!ldap_servers->ready)
|
|
return -1;
|
|
|
|
return send_msg_to_server(msg, ldap_servers);
|
|
}
|
|
|
|
static void fake_bind_response(struct winbind_ldap_client *client,
|
|
int messageid)
|
|
{
|
|
struct ldap_message *msg = new_ldap_message();
|
|
|
|
if (msg == NULL) {
|
|
client->finished = True;
|
|
return;
|
|
}
|
|
|
|
msg->messageid = messageid;
|
|
msg->type = LDAP_TAG_BindResponse;
|
|
msg->r.BindResponse.response.resultcode = 0;
|
|
msg->r.BindResponse.response.dn = "";
|
|
msg->r.BindResponse.response.dn = "";
|
|
msg->r.BindResponse.response.errormessage = "";
|
|
msg->r.BindResponse.response.referral = "";
|
|
ldap_append_to_buf(msg, &client->out_buffer);
|
|
destroy_ldap_message(msg);
|
|
}
|
|
|
|
static int open_ldap_socket(void)
|
|
{
|
|
static int fd = -1;
|
|
|
|
if (fd >= 0)
|
|
return fd;
|
|
|
|
fd = create_pipe_sock(get_winbind_priv_pipe_dir(), "ldapi", 0750);
|
|
return fd;
|
|
}
|
|
|
|
static BOOL do_sigterm = False;
|
|
|
|
static void ldap_termination_handler(int signum)
|
|
{
|
|
do_sigterm = True;
|
|
sys_select_signal();
|
|
}
|
|
|
|
static BOOL handled_locally(struct ldap_message *msg,
|
|
struct winbind_ldap_server *server)
|
|
{
|
|
struct ldap_Result *r = &msg->r.BindResponse.response;
|
|
|
|
if (msg->type != LDAP_TAG_BindResponse)
|
|
return False;
|
|
|
|
if (r->resultcode != 0) {
|
|
destroy_ldap_message(msg);
|
|
server->finished = True;
|
|
}
|
|
destroy_ldap_message(msg);
|
|
server->ready = True;
|
|
return True;
|
|
}
|
|
|
|
static void client_has_data(struct winbind_ldap_client *client)
|
|
{
|
|
|
|
struct ldap_message *msg;
|
|
|
|
if (!read_into_buf(client->sock, &client->in_buffer)) {
|
|
client->finished = True;
|
|
return;
|
|
}
|
|
|
|
while ((msg = get_msg_from_buf(&client->in_buffer,
|
|
&client->finished))) {
|
|
struct pending_ldap_message *pending;
|
|
|
|
if (msg->type == LDAP_TAG_BindRequest) {
|
|
fake_bind_response(client, msg->messageid);
|
|
destroy_ldap_message(msg);
|
|
continue;
|
|
}
|
|
|
|
if (msg->type == LDAP_TAG_UnbindRequest) {
|
|
destroy_ldap_message(msg);
|
|
client->finished = True;
|
|
break;
|
|
}
|
|
|
|
pending = SMB_MALLOC_P(struct pending_ldap_message);
|
|
if (pending == NULL)
|
|
continue;
|
|
|
|
pending->msg = msg;
|
|
pending->client = client;
|
|
pending->our_msgid = send_msg(msg);
|
|
|
|
if (pending->our_msgid < 0) {
|
|
/* could not send */
|
|
client->finished = True;
|
|
free(pending);
|
|
}
|
|
DLIST_ADD(pending_messages, pending);
|
|
}
|
|
}
|
|
|
|
static struct ldap_Result *ldap_msg2result(struct ldap_message *msg)
|
|
{
|
|
switch(msg->type) {
|
|
case LDAP_TAG_BindResponse:
|
|
return &msg->r.BindResponse.response;
|
|
case LDAP_TAG_SearchResultDone:
|
|
return &msg->r.SearchResultDone;
|
|
case LDAP_TAG_ModifyResponse:
|
|
return &msg->r.ModifyResponse;
|
|
case LDAP_TAG_AddResponse:
|
|
return &msg->r.AddResponse;
|
|
case LDAP_TAG_DelResponse:
|
|
return &msg->r.DelResponse;
|
|
case LDAP_TAG_ModifyDNResponse:
|
|
return &msg->r.ModifyDNResponse;
|
|
case LDAP_TAG_CompareResponse:
|
|
return &msg->r.CompareResponse;
|
|
case LDAP_TAG_ExtendedResponse:
|
|
return &msg->r.ExtendedResponse.response;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void server_has_data(struct winbind_ldap_server *server)
|
|
{
|
|
struct ldap_message *msg;
|
|
|
|
if (!read_into_buf(server->sock, &server->in_buffer)) {
|
|
server->finished = True;
|
|
return;
|
|
}
|
|
|
|
while ((msg = get_msg_from_buf(&server->in_buffer,
|
|
&server->finished))) {
|
|
struct pending_ldap_message *pending;
|
|
struct rw_buffer *buf;
|
|
struct ldap_Result *res;
|
|
|
|
if (handled_locally(msg, server))
|
|
continue;
|
|
|
|
res = ldap_msg2result(msg);
|
|
|
|
if ( (res != NULL) && (res->resultcode == 10) )
|
|
DEBUG(5, ("Got Referral %s\n", res->referral));
|
|
|
|
for (pending = pending_messages;
|
|
pending != NULL;
|
|
pending = pending->next) {
|
|
if (pending->our_msgid == msg->messageid)
|
|
break;
|
|
}
|
|
|
|
if (pending == NULL) {
|
|
talloc_destroy(msg->mem_ctx);
|
|
continue;
|
|
}
|
|
|
|
msg->messageid = pending->msg->messageid;
|
|
|
|
buf = &pending->client->out_buffer;
|
|
ldap_append_to_buf(msg, buf);
|
|
|
|
if ( (msg->type != LDAP_TAG_SearchResultEntry) &&
|
|
(msg->type != LDAP_TAG_SearchResultReference) ) {
|
|
destroy_ldap_message(pending->msg);
|
|
DLIST_REMOVE(pending_messages,
|
|
pending);
|
|
SAFE_FREE(pending);
|
|
}
|
|
destroy_ldap_message(msg);
|
|
}
|
|
}
|
|
|
|
static void process_ldap_loop(void)
|
|
{
|
|
struct winbind_ldap_client *client;
|
|
struct winbind_ldap_server *server;
|
|
fd_set r_fds, w_fds;
|
|
int maxfd, listen_sock, selret;
|
|
struct timeval timeout;
|
|
|
|
/* Free up temporary memory */
|
|
|
|
lp_talloc_free();
|
|
main_loop_talloc_free();
|
|
|
|
if (do_sigterm) {
|
|
#if 0
|
|
TALLOC_CTX *mem_ctx = talloc_init("describe");
|
|
DEBUG(0, ("%s\n", talloc_describe_all(mem_ctx)));
|
|
talloc_destroy(mem_ctx);
|
|
#endif
|
|
exit(0);
|
|
}
|
|
|
|
/* Initialise fd lists for select() */
|
|
|
|
listen_sock = open_ldap_socket();
|
|
|
|
if (listen_sock == -1) {
|
|
perror("open_ldap_socket");
|
|
exit(1);
|
|
}
|
|
|
|
maxfd = listen_sock;
|
|
|
|
FD_ZERO(&r_fds);
|
|
FD_ZERO(&w_fds);
|
|
FD_SET(listen_sock, &r_fds);
|
|
|
|
timeout.tv_sec = WINBINDD_ESTABLISH_LOOP;
|
|
timeout.tv_usec = 0;
|
|
|
|
/* Set up client readers and writers */
|
|
|
|
client = ldap_clients;
|
|
|
|
while (client != NULL) {
|
|
|
|
if (client->finished) {
|
|
struct winbind_ldap_client *next = client->next;
|
|
DLIST_REMOVE(ldap_clients, client);
|
|
close(client->sock);
|
|
SAFE_FREE(client->in_buffer.data);
|
|
SAFE_FREE(client->out_buffer.data);
|
|
SAFE_FREE(client);
|
|
client = next;
|
|
continue;
|
|
}
|
|
|
|
if (client->sock > maxfd)
|
|
maxfd = client->sock;
|
|
|
|
FD_SET(client->sock, &r_fds);
|
|
|
|
if (client->out_buffer.length > 0)
|
|
FD_SET(client->sock, &w_fds);
|
|
|
|
client = client->next;
|
|
}
|
|
|
|
/* And now the servers */
|
|
|
|
server = ldap_servers;
|
|
|
|
while (server != NULL) {
|
|
|
|
if (server->finished) {
|
|
struct winbind_ldap_server *next = server->next;
|
|
DLIST_REMOVE(ldap_servers, server);
|
|
close(server->sock);
|
|
SAFE_FREE(server);
|
|
server = next;
|
|
continue;
|
|
}
|
|
|
|
if (server->sock > maxfd)
|
|
maxfd = server->sock;
|
|
|
|
FD_SET(server->sock, &r_fds);
|
|
|
|
if (server->out_buffer.length > 0)
|
|
FD_SET(server->sock, &w_fds);
|
|
|
|
server = server->next;
|
|
}
|
|
|
|
selret = sys_select(maxfd + 1, &r_fds, &w_fds, NULL, &timeout);
|
|
|
|
if (selret == 0)
|
|
return;
|
|
|
|
if (selret == -1 && errno != EINTR) {
|
|
perror("select");
|
|
exit(1);
|
|
}
|
|
|
|
if (FD_ISSET(listen_sock, &r_fds))
|
|
new_ldap_client(listen_sock);
|
|
|
|
for (client = ldap_clients; client != NULL; client = client->next) {
|
|
|
|
if (FD_ISSET(client->sock, &r_fds))
|
|
client_has_data(client);
|
|
|
|
if ((!client->finished) && FD_ISSET(client->sock, &w_fds))
|
|
write_out_of_buf(client->sock, &client->out_buffer);
|
|
}
|
|
|
|
for (server = ldap_servers; server != NULL; server = server->next) {
|
|
|
|
if (FD_ISSET(server->sock, &r_fds))
|
|
server_has_data(server);
|
|
|
|
if (!server->finished && FD_ISSET(server->sock, &w_fds))
|
|
write_out_of_buf(server->sock, &server->out_buffer);
|
|
}
|
|
}
|
|
|
|
static BOOL setup_ldap_serverconn(void)
|
|
{
|
|
char *host;
|
|
uint16 port;
|
|
BOOL ldaps;
|
|
struct hostent *hp;
|
|
struct in_addr ip;
|
|
TALLOC_CTX *mem_ctx = talloc_init("server");
|
|
struct ldap_message *msg;
|
|
char *dn, *pw;
|
|
|
|
ldap_servers = SMB_MALLOC_P(struct winbind_ldap_server);
|
|
|
|
if ((ldap_servers == NULL) || (mem_ctx == NULL))
|
|
return False;
|
|
|
|
if (!ldap_parse_basic_url(mem_ctx, "ldap://192.168.234.1:3899/",
|
|
&host, &port, &ldaps))
|
|
return False;
|
|
|
|
hp = sys_gethostbyname(host);
|
|
|
|
if ((hp == NULL) || (hp->h_addr == NULL))
|
|
return False;
|
|
|
|
putip((char *)&ip, (char *)hp->h_addr);
|
|
|
|
ZERO_STRUCTP(ldap_servers);
|
|
ldap_servers->sock = open_socket_out(SOCK_STREAM, &ip, port, 10000);
|
|
ldap_servers->messageid = 1;
|
|
|
|
if (!fetch_ldap_pw(&dn, &pw))
|
|
return False;
|
|
|
|
msg = new_ldap_simple_bind_msg(dn, pw);
|
|
|
|
SAFE_FREE(dn);
|
|
SAFE_FREE(pw);
|
|
|
|
if (msg == NULL)
|
|
return False;
|
|
|
|
msg->messageid = ldap_servers->messageid++;
|
|
|
|
ldap_append_to_buf(msg, &ldap_servers->out_buffer);
|
|
|
|
destroy_ldap_message(msg);
|
|
|
|
return (ldap_servers->sock >= 0);
|
|
}
|
|
|
|
void do_ldap_proxy(void)
|
|
{
|
|
int ldap_child;
|
|
|
|
ldap_child = sys_fork();
|
|
|
|
if (ldap_child != 0)
|
|
return;
|
|
|
|
/* tdb needs special fork handling */
|
|
if (tdb_reopen_all() == -1) {
|
|
DEBUG(0,("tdb_reopen_all failed.\n"));
|
|
_exit(0);
|
|
}
|
|
|
|
if (!message_init()) {
|
|
DEBUG(0, ("message_init failed\n"));
|
|
_exit(0);
|
|
}
|
|
|
|
CatchSignal(SIGINT, ldap_termination_handler);
|
|
CatchSignal(SIGQUIT, ldap_termination_handler);
|
|
CatchSignal(SIGTERM, ldap_termination_handler);
|
|
|
|
if (!setup_ldap_serverconn())
|
|
return;
|
|
|
|
while (1)
|
|
process_ldap_loop();
|
|
|
|
return;
|
|
}
|