/*
Unix SMB/CIFS implementation.
Infrastructure for async ldap client requests
Copyright (C) Volker Lendecke 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 .
*/
#include "includes.h"
#include "tldap.h"
#include "../lib/util/asn1.h"
#include "../lib/tsocket/tsocket.h"
#include "../lib/util/tevent_unix.h"
static int tldap_simple_recv(struct tevent_req *req);
bool tevent_req_is_ldap_error(struct tevent_req *req, int *perr)
{
enum tevent_req_state state;
uint64_t err;
if (!tevent_req_is_error(req, &state, &err)) {
return false;
}
switch (state) {
case TEVENT_REQ_TIMED_OUT:
*perr = TLDAP_TIMEOUT;
break;
case TEVENT_REQ_NO_MEMORY:
*perr = TLDAP_NO_MEMORY;
break;
case TEVENT_REQ_USER_ERROR:
*perr = err;
break;
default:
*perr = TLDAP_OPERATIONS_ERROR;
break;
}
return true;
}
struct tldap_ctx_attribute {
char *name;
void *ptr;
};
struct tldap_context {
int ld_version;
int ld_deref;
int ld_sizelimit;
int ld_timelimit;
struct tstream_context *conn;
bool server_down;
int msgid;
struct tevent_queue *outgoing;
struct tevent_req **pending;
/* For the sync wrappers we need something like get_last_error... */
struct tldap_message *last_msg;
/* debug */
void (*log_fn)(void *context, enum tldap_debug_level level,
const char *fmt, va_list ap);
void *log_private;
struct tldap_ctx_attribute *ctx_attrs;
};
struct tldap_message {
struct asn1_data *data;
uint8_t *inbuf;
int type;
int id;
/* RESULT_ENTRY */
char *dn;
struct tldap_attribute *attribs;
/* Error data sent by the server */
int lderr;
char *res_matcheddn;
char *res_diagnosticmessage;
char *res_referral;
struct tldap_control *res_sctrls;
/* Controls sent by the server */
struct tldap_control *ctrls;
};
void tldap_set_debug(struct tldap_context *ld,
void (*log_fn)(void *log_private,
enum tldap_debug_level level,
const char *fmt,
va_list ap) PRINTF_ATTRIBUTE(3,0),
void *log_private)
{
ld->log_fn = log_fn;
ld->log_private = log_private;
}
static void tldap_debug(struct tldap_context *ld,
enum tldap_debug_level level,
const char *fmt, ...)
{
va_list ap;
if (!ld) {
return;
}
if (ld->log_fn == NULL) {
return;
}
va_start(ap, fmt);
ld->log_fn(ld->log_private, level, fmt, ap);
va_end(ap);
}
static int tldap_next_msgid(struct tldap_context *ld)
{
int result;
result = ld->msgid++;
if (ld->msgid == 2147483647) {
ld->msgid = 1;
}
return result;
}
struct tldap_context *tldap_context_create(TALLOC_CTX *mem_ctx, int fd)
{
struct tldap_context *ctx;
int ret;
ctx = talloc_zero(mem_ctx, struct tldap_context);
if (ctx == NULL) {
return NULL;
}
ret = tstream_bsd_existing_socket(ctx, fd, &ctx->conn);
if (ret == -1) {
TALLOC_FREE(ctx);
return NULL;
}
ctx->msgid = 1;
ctx->ld_version = 3;
ctx->outgoing = tevent_queue_create(ctx, "tldap_outgoing");
if (ctx->outgoing == NULL) {
TALLOC_FREE(ctx);
return NULL;
}
return ctx;
}
bool tldap_connection_ok(struct tldap_context *ld)
{
if (ld == NULL) {
return false;
}
return !ld->server_down;
}
static struct tldap_ctx_attribute *tldap_context_findattr(
struct tldap_context *ld, const char *name)
{
int i, num_attrs;
num_attrs = talloc_array_length(ld->ctx_attrs);
for (i=0; ictx_attrs[i].name, name) == 0) {
return &ld->ctx_attrs[i];
}
}
return NULL;
}
bool tldap_context_setattr(struct tldap_context *ld,
const char *name, const void *_pptr)
{
struct tldap_ctx_attribute *tmp, *attr;
char *tmpname;
int num_attrs;
void **pptr = (void **)discard_const_p(void,_pptr);
attr = tldap_context_findattr(ld, name);
if (attr != NULL) {
/*
* We don't actually delete attrs, we don't expect tons of
* attributes being shuffled around.
*/
TALLOC_FREE(attr->ptr);
if (*pptr != NULL) {
attr->ptr = talloc_move(ld->ctx_attrs, pptr);
*pptr = NULL;
}
return true;
}
tmpname = talloc_strdup(ld, name);
if (tmpname == NULL) {
return false;
}
num_attrs = talloc_array_length(ld->ctx_attrs);
tmp = talloc_realloc(ld, ld->ctx_attrs, struct tldap_ctx_attribute,
num_attrs+1);
if (tmp == NULL) {
TALLOC_FREE(tmpname);
return false;
}
tmp[num_attrs].name = talloc_move(tmp, &tmpname);
if (*pptr != NULL) {
tmp[num_attrs].ptr = talloc_move(tmp, pptr);
} else {
tmp[num_attrs].ptr = NULL;
}
*pptr = NULL;
ld->ctx_attrs = tmp;
return true;
}
void *tldap_context_getattr(struct tldap_context *ld, const char *name)
{
struct tldap_ctx_attribute *attr = tldap_context_findattr(ld, name);
if (attr == NULL) {
return NULL;
}
return attr->ptr;
}
struct read_ldap_state {
uint8_t *buf;
bool done;
};
static ssize_t read_ldap_more(uint8_t *buf, size_t buflen, void *private_data);
static void read_ldap_done(struct tevent_req *subreq);
static struct tevent_req *read_ldap_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tstream_context *conn)
{
struct tevent_req *req, *subreq;
struct read_ldap_state *state;
req = tevent_req_create(mem_ctx, &state, struct read_ldap_state);
if (req == NULL) {
return NULL;
}
state->done = false;
subreq = tstream_read_packet_send(state, ev, conn, 2, read_ldap_more,
state);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, read_ldap_done, req);
return req;
}
static ssize_t read_ldap_more(uint8_t *buf, size_t buflen, void *private_data)
{
struct read_ldap_state *state = talloc_get_type_abort(
private_data, struct read_ldap_state);
size_t len;
int i, lensize;
if (state->done) {
/* We've been here, we're done */
return 0;
}
/*
* From ldap.h: LDAP_TAG_MESSAGE is 0x30
*/
if (buf[0] != 0x30) {
return -1;
}
len = buf[1];
if ((len & 0x80) == 0) {
state->done = true;
return len;
}
lensize = (len & 0x7f);
len = 0;
if (buflen == 2) {
/* Please get us the full length */
return lensize;
}
if (buflen > 2 + lensize) {
state->done = true;
return 0;
}
if (buflen != 2 + lensize) {
return -1;
}
for (i=0; ibuf, &err);
TALLOC_FREE(subreq);
if (nread == -1) {
tevent_req_error(req, err);
return;
}
tevent_req_done(req);
}
static ssize_t read_ldap_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
uint8_t **pbuf, int *perrno)
{
struct read_ldap_state *state = tevent_req_data(
req, struct read_ldap_state);
if (tevent_req_is_unix_error(req, perrno)) {
return -1;
}
*pbuf = talloc_move(mem_ctx, &state->buf);
return talloc_get_size(*pbuf);
}
struct tldap_msg_state {
struct tldap_context *ld;
struct tevent_context *ev;
int id;
struct iovec iov;
struct asn1_data *data;
uint8_t *inbuf;
};
static void tldap_push_controls(struct asn1_data *data,
struct tldap_control *sctrls,
int num_sctrls)
{
int i;
if ((sctrls == NULL) || (num_sctrls == 0)) {
return;
}
asn1_push_tag(data, ASN1_CONTEXT(0));
for (i=0; ioid, strlen(c->oid));
if (c->critical) {
asn1_write_BOOLEAN(data, true);
}
if (c->value.data != NULL) {
asn1_write_OctetString(data, c->value.data,
c->value.length);
}
asn1_pop_tag(data); /* ASN1_SEQUENCE(0) */
}
asn1_pop_tag(data); /* ASN1_CONTEXT(0) */
}
static void tldap_msg_sent(struct tevent_req *subreq);
static void tldap_msg_received(struct tevent_req *subreq);
static struct tevent_req *tldap_msg_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tldap_context *ld,
int id, struct asn1_data *data,
struct tldap_control *sctrls,
int num_sctrls)
{
struct tevent_req *req, *subreq;
struct tldap_msg_state *state;
DATA_BLOB blob;
tldap_debug(ld, TLDAP_DEBUG_TRACE, "tldap_msg_send: sending msg %d\n",
id);
req = tevent_req_create(mem_ctx, &state, struct tldap_msg_state);
if (req == NULL) {
return NULL;
}
state->ld = ld;
state->ev = ev;
state->id = id;
if (state->ld->server_down) {
tevent_req_error(req, TLDAP_SERVER_DOWN);
return tevent_req_post(req, ev);
}
tldap_push_controls(data, sctrls, num_sctrls);
asn1_pop_tag(data);
if (!asn1_blob(data, &blob)) {
tevent_req_error(req, TLDAP_ENCODING_ERROR);
return tevent_req_post(req, ev);
}
state->iov.iov_base = (void *)blob.data;
state->iov.iov_len = blob.length;
subreq = tstream_writev_queue_send(state, ev, ld->conn, ld->outgoing,
&state->iov, 1);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, tldap_msg_sent, req);
return req;
}
static void tldap_msg_unset_pending(struct tevent_req *req)
{
struct tldap_msg_state *state = tevent_req_data(
req, struct tldap_msg_state);
struct tldap_context *ld = state->ld;
int num_pending = talloc_array_length(ld->pending);
int i;
tevent_req_set_cleanup_fn(req, NULL);
if (num_pending == 1) {
TALLOC_FREE(ld->pending);
return;
}
for (i=0; ipending[i]) {
break;
}
}
if (i == num_pending) {
/*
* Something's seriously broken. Just returning here is the
* right thing nevertheless, the point of this routine is to
* remove ourselves from cli->pending.
*/
return;
}
/*
* Remove ourselves from the cli->pending array
*/
if (num_pending > 1) {
ld->pending[i] = ld->pending[num_pending-1];
}
/*
* No NULL check here, we're shrinking by sizeof(void *), and
* talloc_realloc just adjusts the size for this.
*/
ld->pending = talloc_realloc(NULL, ld->pending, struct tevent_req *,
num_pending - 1);
return;
}
static void tldap_msg_cleanup(struct tevent_req *req,
enum tevent_req_state req_state)
{
switch (req_state) {
case TEVENT_REQ_USER_ERROR:
case TEVENT_REQ_RECEIVED:
tldap_msg_unset_pending(req);
return;
default:
return;
}
}
static bool tldap_msg_set_pending(struct tevent_req *req)
{
struct tldap_msg_state *state = tevent_req_data(
req, struct tldap_msg_state);
struct tldap_context *ld;
struct tevent_req **pending;
int num_pending;
struct tevent_req *subreq;
ld = state->ld;
num_pending = talloc_array_length(ld->pending);
pending = talloc_realloc(ld, ld->pending, struct tevent_req *,
num_pending+1);
if (pending == NULL) {
return false;
}
pending[num_pending] = req;
ld->pending = pending;
tevent_req_set_cleanup_fn(req, tldap_msg_cleanup);
if (num_pending > 0) {
return true;
}
/*
* We're the first one, add the read_ldap request that waits for the
* answer from the server
*/
subreq = read_ldap_send(ld->pending, state->ev, ld->conn);
if (subreq == NULL) {
tldap_msg_unset_pending(req);
return false;
}
tevent_req_set_callback(subreq, tldap_msg_received, ld);
return true;
}
static void tldap_msg_sent(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct tldap_msg_state *state = tevent_req_data(
req, struct tldap_msg_state);
ssize_t nwritten;
int err;
nwritten = tstream_writev_queue_recv(subreq, &err);
TALLOC_FREE(subreq);
if (nwritten == -1) {
state->ld->server_down = true;
tevent_req_error(req, TLDAP_SERVER_DOWN);
return;
}
if (!tldap_msg_set_pending(req)) {
tevent_req_oom(req);
return;
}
}
static int tldap_msg_msgid(struct tevent_req *req)
{
struct tldap_msg_state *state = tevent_req_data(
req, struct tldap_msg_state);
return state->id;
}
static void tldap_msg_received(struct tevent_req *subreq)
{
struct tldap_context *ld = tevent_req_callback_data(
subreq, struct tldap_context);
struct tevent_req *req;
struct tldap_msg_state *state;
struct asn1_data *data;
uint8_t *inbuf;
ssize_t received;
size_t num_pending;
int i, err, status;
int id;
uint8_t type;
bool ok;
received = read_ldap_recv(subreq, talloc_tos(), &inbuf, &err);
TALLOC_FREE(subreq);
if (received == -1) {
status = TLDAP_SERVER_DOWN;
goto fail;
}
data = asn1_init(talloc_tos());
if (data == NULL) {
status = TLDAP_NO_MEMORY;
goto fail;
}
asn1_load_nocopy(data, inbuf, received);
ok = true;
ok &= asn1_start_tag(data, ASN1_SEQUENCE(0));
ok &= asn1_read_Integer(data, &id);
ok &= asn1_peek_uint8(data, &type);
if (!ok) {
status = TLDAP_PROTOCOL_ERROR;
goto fail;
}
tldap_debug(ld, TLDAP_DEBUG_TRACE, "tldap_msg_received: got msg %d "
"type %d\n", id, (int)type);
num_pending = talloc_array_length(ld->pending);
for (i=0; ipending[i])) {
break;
}
}
if (i == num_pending) {
/* Dump unexpected reply */
tldap_debug(ld, TLDAP_DEBUG_WARNING, "tldap_msg_received: "
"No request pending for msg %d\n", id);
TALLOC_FREE(data);
TALLOC_FREE(inbuf);
goto done;
}
req = ld->pending[i];
state = tevent_req_data(req, struct tldap_msg_state);
state->inbuf = talloc_move(state, &inbuf);
state->data = talloc_move(state, &data);
tldap_msg_unset_pending(req);
num_pending = talloc_array_length(ld->pending);
tevent_req_done(req);
done:
if (num_pending == 0) {
return;
}
if (talloc_array_length(ld->pending) > num_pending) {
/*
* The callback functions called from tevent_req_done() above
* have put something on the pending queue. We don't have to
* trigger the read_ldap_send(), tldap_msg_set_pending() has
* done it for us already.
*/
return;
}
state = tevent_req_data(ld->pending[0], struct tldap_msg_state);
subreq = read_ldap_send(ld->pending, state->ev, ld->conn);
if (subreq == NULL) {
status = TLDAP_NO_MEMORY;
goto fail;
}
tevent_req_set_callback(subreq, tldap_msg_received, ld);
return;
fail:
while (talloc_array_length(ld->pending) > 0) {
req = ld->pending[0];
state = tevent_req_data(req, struct tldap_msg_state);
tevent_req_defer_callback(req, state->ev);
tevent_req_error(req, status);
}
}
static int tldap_msg_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
struct tldap_message **pmsg)
{
struct tldap_msg_state *state = tevent_req_data(
req, struct tldap_msg_state);
struct tldap_message *msg;
int err;
uint8_t msgtype;
if (tevent_req_is_ldap_error(req, &err)) {
return err;
}
if (!asn1_peek_uint8(state->data, &msgtype)) {
return TLDAP_PROTOCOL_ERROR;
}
if (pmsg == NULL) {
return TLDAP_SUCCESS;
}
msg = talloc_zero(mem_ctx, struct tldap_message);
if (msg == NULL) {
return TLDAP_NO_MEMORY;
}
msg->id = state->id;
msg->inbuf = talloc_move(msg, &state->inbuf);
msg->data = talloc_move(msg, &state->data);
msg->type = msgtype;
*pmsg = msg;
return TLDAP_SUCCESS;
}
struct tldap_req_state {
int id;
struct asn1_data *out;
struct tldap_message *result;
};
static struct tevent_req *tldap_req_create(TALLOC_CTX *mem_ctx,
struct tldap_context *ld,
struct tldap_req_state **pstate)
{
struct tevent_req *req;
struct tldap_req_state *state;
req = tevent_req_create(mem_ctx, &state, struct tldap_req_state);
if (req == NULL) {
return NULL;
}
ZERO_STRUCTP(state);
state->out = asn1_init(state);
if (state->out == NULL) {
TALLOC_FREE(req);
return NULL;
}
state->result = NULL;
state->id = tldap_next_msgid(ld);
asn1_push_tag(state->out, ASN1_SEQUENCE(0));
asn1_write_Integer(state->out, state->id);
*pstate = state;
return req;
}
static void tldap_save_msg(struct tldap_context *ld, struct tevent_req *req)
{
struct tldap_req_state *state = tevent_req_data(
req, struct tldap_req_state);
TALLOC_FREE(ld->last_msg);
ld->last_msg = talloc_move(ld, &state->result);
}
static char *blob2string_talloc(TALLOC_CTX *mem_ctx, DATA_BLOB blob)
{
char *result = talloc_array(mem_ctx, char, blob.length+1);
if (result == NULL) {
return NULL;
}
memcpy(result, blob.data, blob.length);
result[blob.length] = '\0';
return result;
}
static bool asn1_read_OctetString_talloc(TALLOC_CTX *mem_ctx,
struct asn1_data *data,
char **presult)
{
DATA_BLOB string;
char *result;
if (!asn1_read_OctetString(data, mem_ctx, &string))
return false;
result = blob2string_talloc(mem_ctx, string);
data_blob_free(&string);
if (result == NULL) {
return false;
}
*presult = result;
return true;
}
static bool tldap_decode_controls(struct tldap_req_state *state);
static bool tldap_decode_response(struct tldap_req_state *state)
{
struct asn1_data *data = state->result->data;
struct tldap_message *msg = state->result;
bool ok = true;
ok &= asn1_read_enumerated(data, &msg->lderr);
ok &= asn1_read_OctetString_talloc(msg, data, &msg->res_matcheddn);
ok &= asn1_read_OctetString_talloc(msg, data,
&msg->res_diagnosticmessage);
if (asn1_peek_tag(data, ASN1_CONTEXT(3))) {
ok &= asn1_start_tag(data, ASN1_CONTEXT(3));
ok &= asn1_read_OctetString_talloc(msg, data,
&msg->res_referral);
ok &= asn1_end_tag(data);
} else {
msg->res_referral = NULL;
}
return ok;
}
static void tldap_sasl_bind_done(struct tevent_req *subreq);
struct tevent_req *tldap_sasl_bind_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tldap_context *ld,
const char *dn,
const char *mechanism,
DATA_BLOB *creds,
struct tldap_control *sctrls,
int num_sctrls,
struct tldap_control *cctrls,
int num_cctrls)
{
struct tevent_req *req, *subreq;
struct tldap_req_state *state;
req = tldap_req_create(mem_ctx, ld, &state);
if (req == NULL) {
return NULL;
}
if (dn == NULL) {
dn = "";
}
asn1_push_tag(state->out, TLDAP_REQ_BIND);
asn1_write_Integer(state->out, ld->ld_version);
asn1_write_OctetString(state->out, dn, strlen(dn));
if (mechanism == NULL) {
asn1_push_tag(state->out, ASN1_CONTEXT_SIMPLE(0));
asn1_write(state->out, creds->data, creds->length);
asn1_pop_tag(state->out);
} else {
asn1_push_tag(state->out, ASN1_CONTEXT(3));
asn1_write_OctetString(state->out, mechanism,
strlen(mechanism));
if ((creds != NULL) && (creds->data != NULL)) {
asn1_write_OctetString(state->out, creds->data,
creds->length);
}
asn1_pop_tag(state->out);
}
if (!asn1_pop_tag(state->out)) {
tevent_req_error(req, TLDAP_ENCODING_ERROR);
return tevent_req_post(req, ev);
}
subreq = tldap_msg_send(state, ev, ld, state->id, state->out,
sctrls, num_sctrls);
if (tevent_req_nomem(subreq, req)) {
return tevent_req_post(req, ev);
}
tevent_req_set_callback(subreq, tldap_sasl_bind_done, req);
return req;
}
static void tldap_sasl_bind_done(struct tevent_req *subreq)
{
struct tevent_req *req = tevent_req_callback_data(
subreq, struct tevent_req);
struct tldap_req_state *state = tevent_req_data(
req, struct tldap_req_state);
int err;
err = tldap_msg_recv(subreq, state, &state->result);
TALLOC_FREE(subreq);
if (err != TLDAP_SUCCESS) {
tevent_req_error(req, err);
return;
}
if (state->result->type != TLDAP_RES_BIND) {
tevent_req_error(req, TLDAP_PROTOCOL_ERROR);
return;
}
if (!asn1_start_tag(state->result->data, state->result->type) ||
!tldap_decode_response(state) ||
!asn1_end_tag(state->result->data)) {
tevent_req_error(req, TLDAP_DECODING_ERROR);
return;
}
/*
* TODO: pull the reply blob
*/
if (state->result->lderr != TLDAP_SUCCESS) {
tevent_req_error(req, state->result->lderr);
return;
}
tevent_req_done(req);
}
int tldap_sasl_bind_recv(struct tevent_req *req)
{
return tldap_simple_recv(req);
}
int tldap_sasl_bind(struct tldap_context *ld,
const char *dn,
const char *mechanism,
DATA_BLOB *creds,
struct tldap_control *sctrls,
int num_sctrls,
struct tldap_control *cctrls,
int num_cctrls)
{
TALLOC_CTX *frame = talloc_stackframe();
struct tevent_context *ev;
struct tevent_req *req;
int result;
ev = samba_tevent_context_init(frame);
if (ev == NULL) {
result = TLDAP_NO_MEMORY;
goto fail;
}
req = tldap_sasl_bind_send(frame, ev, ld, dn, mechanism, creds,
sctrls, num_sctrls, cctrls, num_cctrls);
if (req == NULL) {
result = TLDAP_NO_MEMORY;
goto fail;
}
if (!tevent_req_poll(req, ev)) {
result = TLDAP_OPERATIONS_ERROR;
goto fail;
}
result = tldap_sasl_bind_recv(req);
tldap_save_msg(ld, req);
fail:
TALLOC_FREE(frame);
return result;
}
struct tevent_req *tldap_simple_bind_send(TALLOC_CTX *mem_ctx,
struct tevent_context *ev,
struct tldap_context *ld,
const char *dn,
const char *passwd)
{
DATA_BLOB cred;
if (passwd != NULL) {
cred.data = discard_const_p(uint8_t, passwd);
cred.length = strlen(passwd);
} else {
cred.data = discard_const_p(uint8_t, "");
cred.length = 0;
}
return tldap_sasl_bind_send(mem_ctx, ev, ld, dn, NULL, &cred, NULL, 0,
NULL, 0);
}
int tldap_simple_bind_recv(struct tevent_req *req)
{
return tldap_sasl_bind_recv(req);
}
int tldap_simple_bind(struct tldap_context *ld, const char *dn,
const char *passwd)
{
DATA_BLOB cred;
if (passwd != NULL) {
cred.data = discard_const_p(uint8_t, passwd);
cred.length = strlen(passwd);
} else {
cred.data = discard_const_p(uint8_t, "");
cred.length = 0;
}
return tldap_sasl_bind(ld, dn, NULL, &cred, NULL, 0, NULL, 0);
}
/*****************************************************************************/
/* can't use isalpha() as only a strict set is valid for LDAP */
static bool tldap_is_alpha(char c)
{
return (((c >= 'a') && (c <= 'z')) || \
((c >= 'A') && (c <= 'Z')));
}
static bool tldap_is_adh(char c)
{
return tldap_is_alpha(c) || isdigit(c) || (c == '-');
}
#define TLDAP_FILTER_AND ASN1_CONTEXT(0)
#define TLDAP_FILTER_OR ASN1_CONTEXT(1)
#define TLDAP_FILTER_NOT ASN1_CONTEXT(2)
#define TLDAP_FILTER_EQ ASN1_CONTEXT(3)
#define TLDAP_FILTER_SUB ASN1_CONTEXT(4)
#define TLDAP_FILTER_LE ASN1_CONTEXT(5)
#define TLDAP_FILTER_GE ASN1_CONTEXT(6)
#define TLDAP_FILTER_PRES ASN1_CONTEXT_SIMPLE(7)
#define TLDAP_FILTER_APX ASN1_CONTEXT(8)
#define TLDAP_FILTER_EXT ASN1_CONTEXT(9)
#define TLDAP_SUB_INI ASN1_CONTEXT_SIMPLE(0)
#define TLDAP_SUB_ANY ASN1_CONTEXT_SIMPLE(1)
#define TLDAP_SUB_FIN ASN1_CONTEXT_SIMPLE(2)
/* oid's should be numerical only in theory,
* but apparently some broken servers may have alphanum aliases instead.
* Do like openldap libraries and allow alphanum aliases for oids, but
* do not allow Tagging options in that case.
*/
static bool tldap_is_attrdesc(const char *s, int len, bool no_tagopts)
{
bool is_oid = false;
bool dot = false;
int i;
/* first char has stricter rules */
if (isdigit(*s)) {
is_oid = true;
} else if (!tldap_is_alpha(*s)) {
/* bad first char */
return false;
}
for (i = 1; i < len; i++) {
if (is_oid) {
if (isdigit(s[i])) {
dot = false;
continue;
}
if (s[i] == '.') {
if (dot) {
/* malformed */
return false;
}
dot = true;
continue;
}
} else {
if (tldap_is_adh(s[i])) {
continue;
}
}
if (s[i] == ';') {
if (no_tagopts) {
/* no tagging options */
return false;
}
if (dot) {
/* malformed */
return false;
}
if ((i + 1) == len) {
/* malformed */
return false;
}
is_oid = false;
continue;
}
}
if (dot) {
/* malformed */
return false;
}
return true;
}
/* this function copies the value until the closing parenthesis is found. */
static char *tldap_get_val(TALLOC_CTX *memctx,
const char *value, const char **_s)
{
const char *s = value;
/* find terminator */
while (*s) {
s = strchr(s, ')');
if (s && (*(s - 1) == '\\')) {
continue;
}
break;
}
if (!s || !(*s == ')')) {
/* malformed filter */
return NULL;
}
*_s = s;
return talloc_strndup(memctx, value, s - value);
}
static int tldap_hex2char(const char *x)
{
if (isxdigit(x[0]) && isxdigit(x[1])) {
const char h1 = x[0], h2 = x[1];
int c = 0;
if (h1 >= 'a') c = h1 - (int)'a' + 10;
else if (h1 >= 'A') c = h1 - (int)'A' + 10;
else if (h1 >= '0') c = h1 - (int)'0';
c = c << 4;
if (h2 >= 'a') c += h2 - (int)'a' + 10;
else if (h2 >= 'A') c += h2 - (int)'A' + 10;
else if (h2 >= '0') c += h2 - (int)'0';
return c;
}
return -1;
}
static bool tldap_find_first_star(const char *val, const char **star)
{
const char *s;
for (s = val; *s; s++) {
switch (*s) {
case '\\':
if (isxdigit(s[1]) && isxdigit(s[2])) {
s += 2;
break;
}
/* not hex based escape, check older syntax */
switch (s[1]) {
case '(':
case ')':
case '*':
case '\\':
s++;
break;
default:
/* invalid escape sequence */
return false;
}
break;
case ')':
/* end of val, nothing found */
*star = s;
return true;
case '*':
*star = s;
return true;
}
}
/* string ended without closing parenthesis, filter is malformed */
return false;
}
static bool tldap_unescape_inplace(char *value, size_t *val_len)
{
int c, i, p;
for (i = 0,p = 0; i < *val_len; i++) {
switch (value[i]) {
case '(':
case ')':
case '*':
/* these must be escaped */
return false;
case '\\':
if (!value[i + 1]) {
/* invalid EOL */
return false;
}
i++;
c = tldap_hex2char(&value[i]);
if (c >= 0 && c < 256) {
value[p] = c;
i++;
p++;
break;
}
switch (value[i]) {
case '(':
case ')':
case '*':
case '\\':
value[p] = value[i];
p++;
default:
/* invalid */
return false;
}
break;
default:
value[p] = value[i];
p++;
}
}
value[p] = '\0';
*val_len = p;
return true;
}
static bool tldap_push_filter_basic(struct tldap_context *ld,
struct asn1_data *data,
const char **_s);
static bool tldap_push_filter_substring(struct tldap_context *ld,
struct asn1_data *data,
const char *val,
const char **_s);
static bool tldap_push_filter_int(struct tldap_context *ld,
struct asn1_data *data,
const char **_s)
{
const char *s = *_s;
bool ret;
if (*s != '(') {
tldap_debug(ld, TLDAP_DEBUG_ERROR,
"Incomplete or malformed filter\n");
return false;
}
s++;
/* we are right after a parenthesis,
* find out what op we have at hand */
switch (*s) {
case '&':
tldap_debug(ld, TLDAP_DEBUG_TRACE, "Filter op: AND\n");
asn1_push_tag(data, TLDAP_FILTER_AND);
s++;
break;
case '|':
tldap_debug(ld, TLDAP_DEBUG_TRACE, "Filter op: OR\n");
asn1_push_tag(data, TLDAP_FILTER_OR);
s++;
break;
case '!':
tldap_debug(ld, TLDAP_DEBUG_TRACE, "Filter op: NOT\n");
asn1_push_tag(data, TLDAP_FILTER_NOT);
s++;
ret = tldap_push_filter_int(ld, data, &s);
if (!ret) {
return false;
}
asn1_pop_tag(data);
goto done;
case '(':
case ')':
tldap_debug(ld, TLDAP_DEBUG_ERROR,
"Invalid parenthesis '%c'\n", *s);
return false;
case '\0':
tldap_debug(ld, TLDAP_DEBUG_ERROR,
"Invalid filter termination\n");
return false;
default:
ret = tldap_push_filter_basic(ld, data, &s);
if (!ret) {
return false;
}
goto done;
}
/* only and/or filters get here.
* go through the list of filters */
if (*s == ')') {
/* RFC 4526: empty and/or */
asn1_pop_tag(data);
goto done;
}
while (*s) {
ret = tldap_push_filter_int(ld, data, &s);
if (!ret) {
return false;
}
if (*s == ')') {
/* end of list, return */
asn1_pop_tag(data);
break;
}
}
done:
if (*s != ')') {
tldap_debug(ld, TLDAP_DEBUG_ERROR,
"Incomplete or malformed filter\n");
return false;
}
s++;
if (data->has_error) {
return false;
}
*_s = s;
return true;
}
static bool tldap_push_filter_basic(struct tldap_context *ld,
struct asn1_data *data,
const char **_s)
{
TALLOC_CTX *tmpctx = talloc_tos();
const char *s = *_s;
const char *e;
const char *eq;
const char *val;
const char *type;
const char *dn;
const char *rule;
const char *star;
size_t type_len = 0;
char *uval;
size_t uval_len;
bool write_octect = true;
bool ret;
eq = strchr(s, '=');
if (!eq) {
tldap_debug(ld, TLDAP_DEBUG_ERROR,
"Invalid filter, missing equal sign\n");
return false;
}
val = eq + 1;
e = eq - 1;
switch (*e) {
case '<':
asn1_push_tag(data, TLDAP_FILTER_LE);
break;
case '>':
asn1_push_tag(data, TLDAP_FILTER_GE);
break;
case '~':
asn1_push_tag(data, TLDAP_FILTER_APX);
break;
case ':':
asn1_push_tag(data, TLDAP_FILTER_EXT);
write_octect = false;
type = NULL;
dn = NULL;
rule = NULL;
if (*s == ':') { /* [:dn]:rule:= value */
if (s == e) {
/* malformed filter */
return false;
}
dn = s;
} else { /* type[:dn][:rule]:= value */
type = s;
dn = strchr(s, ':');
type_len = dn - type;
if (dn == e) { /* type:= value */
dn = NULL;
}
}
if (dn) {
dn++;
rule = strchr(dn, ':');
if (rule == NULL) {
return false;
}
if ((rule == dn + 1) || rule + 1 == e) {
/* malformed filter, contains "::" */
return false;
}
if (strncasecmp_m(dn, "dn:", 3) != 0) {
if (rule == e) {
rule = dn;
dn = NULL;
} else {
/* malformed filter. With two
* optionals, the first must be "dn"
*/
return false;
}
} else {
if (rule == e) {
rule = NULL;
} else {
rule++;
}
}
}
if (!type && !dn && !rule) {
/* malformed filter, there must be at least one */
return false;
}
/*
MatchingRuleAssertion ::= SEQUENCE {
matchingRule [1] MatchingRuleID OPTIONAL,
type [2] AttributeDescription OPTIONAL,
matchValue [3] AssertionValue,
dnAttributes [4] BOOLEAN DEFAULT FALSE
}
*/
/* check and add rule */
if (rule) {
ret = tldap_is_attrdesc(rule, e - rule, true);
if (!ret) {
return false;
}
asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(1));
asn1_write(data, rule, e - rule);
asn1_pop_tag(data);
}
/* check and add type */
if (type) {
ret = tldap_is_attrdesc(type, type_len, false);
if (!ret) {
return false;
}
asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(2));
asn1_write(data, type, type_len);
asn1_pop_tag(data);
}
uval = tldap_get_val(tmpctx, val, _s);
if (!uval) {
return false;
}
uval_len = *_s - val;
ret = tldap_unescape_inplace(uval, &uval_len);
if (!ret) {
return false;
}
asn1_push_tag(data, ASN1
><DD
><P
>is optional and can be either our own workgroup
or that of the remote network. If you use the
workgroup name of the remote network then our
NetBIOS machine names will end up looking like
they belong to that workgroup, this may cause
name resolution problems and should be avoided.</P
></DD
></DL
></DIV
></P
></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="AEN399">3.3. Use of the "Remote Browse Sync" parameter</H1
><P
>The "remote browse sync" parameter of smb.conf is used to announce to
another LMB that it must synchronise it's NetBIOS name list with our
Samba LMB. It works ONLY if the Samba server that has this option is
simultaneously the LMB on it's network segment.</P
><P
>The syntax of the "remote browse sync" parameter is:
<PRE
CLASS="PROGRAMLISTING"
> remote browse sync = a.b.c.d</PRE
>
where a.b.c.d is either the IP address of the remote LMB or else is the network broadcast address of the remote segment.</P
></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="AEN404">3.4. Use of WINS</H1
><P
>Use of WINS (either Samba WINS _or_ MS Windows NT Server WINS) is highly
recommended. Every NetBIOS machine registers it's name together with a
name_type value for each of of several types of service it has available.
eg: It registers it's name directly as a unique (the type 0x03) name.
It also registers it's name if it is running the lanmanager compatible
server service (used to make shares and printers available to other users)
by registering the server (the type 0x20) name.</P
><P
>All NetBIOS names are up to 15 characters in length. The name_type variable
is added to the end of the name - thus creating a 16 character name. Any
name that is shorter than 15 characters is padded with spaces to the 15th
character. ie: All NetBIOS names are 16 characters long (including the
name_type information).</P
><P
>WINS can store these 16 character names as they get registered. A client
that wants to log onto the network can ask the WINS server for a list
of all names that have registered the NetLogon service name_type. This saves
broadcast traffic and greatly expedites logon processing. Since broadcast
name resolution can not be used across network segments this type of
information can only be provided via WINS _or_ via statically configured
"lmhosts" files that must reside on all clients in the absence of WINS.</P
><P
>WINS also serves the purpose of forcing browse list synchronisation by all
LMB's. LMB's must synchronise their browse list with the DMB (domain master
browser) and WINS helps the LMB to identify it's DMB. By definition this
will work only within a single workgroup. Note that the domain master browser
has NOTHING to do with what is referred to as an MS Windows NT Domain. The
later is a reference to a security environment while the DMB refers to the
master controller for browse list information only.</P
><P
>Use of WINS will work correctly only if EVERY client TCP/IP protocol stack
has been configured to use the WINS server/s. Any client that has not been
configured to use the WINS server will continue to use only broadcast based
name registration so that WINS may NEVER get to know about it. In any case,
machines that have not registered with a WINS server will fail name to address
lookup attempts by other clients and will therefore cause workstation access
errors.</P
><P
>To configure Samba as a WINS server just add "wins support = yes" to the
smb.conf file [globals] section.</P
><P
>To configure Samba to register with a WINS server just add
"wins server = a.b.c.d" to your smb.conf file [globals] section.</P
><P
><SPAN
CLASS="emphasis"
><I
CLASS="EMPHASIS"
>DO NOT EVER</I
></SPAN
> use both "wins support = yes" together with "wins server = a.b.c.d"
particularly not using it's own IP address.</P
></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="AEN415">3.5. Do NOT use more than one (1) protocol on MS Windows machines</H1
><P
>A very common cause of browsing problems results from installing more than
one protocol on an MS Windows machine.</P
><P
>Every NetBIOS machine take part in a process of electing the LMB (and DMB)
every 15 minutes. A set of election criteria is used to determine the order
of precidence for winning this election process. A machine running Samba or
Windows NT will be biased so that the most suitable machine will predictably
win and thus retain it's role.</P
><P
>The election process is "fought out" so to speak over every NetBIOS network
interface. In the case of a Windows 9x machine that has both TCP/IP and IPX
installed and has NetBIOS enabled over both protocols the election will be
decided over both protocols. As often happens, if the Windows 9x machine is
the only one with both protocols then the LMB may be won on the NetBIOS
interface over the IPX protocol. Samba will then lose the LMB role as Windows
9x will insist it knows who the LMB is. Samba will then cease to function
as an LMB and thus browse list operation on all TCP/IP only machines will
fail.</P
><P
>The safest rule of all to follow it this - USE ONLY ONE PROTOCOL!</P
></DIV
><DIV
CLASS="SECT1"
><H1
CLASS="SECT1"
><A
NAME="AEN421">3.6. Name Resolution Order</H1
><P
>Resolution of NetBIOS names to IP addresses can take place using a number
of methods. The only ones that can provide NetBIOS name_type information
are:
<P
></P
><TABLE
BORDER="0"
><TBODY
><TR
><TD
>WINS: the best tool!</TD
></TR
><TR
><TD
>LMHOSTS: is static and hard to maintain.</TD
></TR
><TR
><TD
>Broadcast: uses UDP and can not resolve names across remote segments.</TD
></TR
></TBODY
></TABLE
><P
></P
></P
><P
>Alternative means of name resolution includes:
<P
></P
><TABLE
BORDER="0"
><TBODY
><TR
><TD
>/etc/hosts: is static, hard to maintain, and lacks name_type info</TD
></TR
><TR
><TD
>DNS: is a good choice but lacks essential name_type info.</TD
></TR
></TBODY
></TABLE
><P
></P
></P
><P
>Many sites want to restrict DNS lookups and want to avoid broadcast name
resolution traffic. The "name resolve order" parameter is of great help here.
The syntax of the "name resolve order" parameter is:
<PRE
CLASS="PROGRAMLISTING"
> name resolve order = wins lmhosts bcast host</PRE
>
_or_
<PRE
CLASS="PROGRAMLISTING"
> name resolve order = wins lmhosts (eliminates bcast and host)</PRE
>
The default is:
<PRE
CLASS="PROGRAMLISTING"
> name resolve order = host lmhost wins bcast</PRE
>.
where "host" refers the the native methods used by the Unix system
to implement the gethostbyname() function call. This is normally
controlled by <TT
CLASS="FILENAME"
>/etc/host.conf</TT
>, <TT
CLASS="FILENAME"
>/etc/nsswitch.conf</TT
> and <TT
CLASS="FILENAME"
>/etc/resolv.conf</TT
>.</P
></DIV
></DIV
><DIV
CLASS="NAVFOOTER"
><HR
ALIGN="LEFT"
WIDTH="100%"><TABLE
SUMMARY="Footer navigation table"
WIDTH="100%"
BORDER="0"
CELLPADDING="0"
CELLSPACING="0"
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
><A
HREF="improved-browsing.html"
ACCESSKEY="P"
>Prev</A
></TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="samba-howto-collection.html"
ACCESSKEY="H"
>Home</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
><A
HREF="pwencrypt.html"
ACCESSKEY="N"
>Next</A
></TD
></TR
><TR
><TD
WIDTH="33%"
ALIGN="left"
VALIGN="top"
>Improved browsing in samba</TD
><TD
WIDTH="34%"
ALIGN="center"
VALIGN="top"
><A
HREF="introduction.html"
ACCESSKEY="U"
>Up</A
></TD
><TD
WIDTH="33%"
ALIGN="right"
VALIGN="top"
>LanMan and NT Password Encryption in Samba</TD
></TR
></TABLE
></DIV
></BODY
></HTML
>