/* 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 *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 *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 **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 *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 *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 *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; }