summaryrefslogtreecommitdiffstats
path: root/source3/lib/tldap.c
blob: b094c2d3c2904d4fe808cf2fdab01011a6576663 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
generated by cgit  (git 2.34.1) at 2025-10-17 05:18:19 +0000
 


tributed 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/>.
*/

#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; i<num_attrs; i++) {
		if (strcmp(ld->ctx_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; i<lensize; i++) {
		len = (len << 8) | buf[2+i];
	}
	return len;
}

static void read_ldap_done(struct tevent_req *subreq)
{
	struct tevent_req *req = tevent_req_callback_data(
		subreq, struct tevent_req);
	struct read_ldap_state *state = tevent_req_data(
		req, struct read_ldap_state);
	ssize_t nread;
	int err;

	nread = tstream_read_packet_recv(subreq, state, &state->buf, &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; i<num_sctrls; i++) {
		struct tldap_control *c = &sctrls[i];
		asn1_push_tag(data, ASN1_SEQUENCE(0));
		asn1_write_OctetString(data, c->oid, 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;

	if (num_pending == 1) {
		TALLOC_FREE(ld->pending);
		return;
	}

	for (i=0; i<num_pending; i++) {
		if (req == ld->pending[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 int tldap_msg_destructor(struct tevent_req *req)
{
	tldap_msg_unset_pending(req);
	return 0;
}

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;
	talloc_set_destructor(req, tldap_msg_destructor);

	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; i<num_pending; i++) {
		if (id == tldap_msg_msgid(ld->pending[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);

	talloc_set_destructor(req, NULL);
	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];
		talloc_set_destructor(req, NULL);
		tldap_msg_destructor(req);
		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_CONTEXT_SIMPLE(3));
		asn1_write(data, uval, uval_len);
		asn1_pop_tag(data);

		asn1_push_tag(data, ASN1_CONTEXT_SIMPLE(4));
		asn1_write_uint8(data, dn?1:0);
		asn1_pop_tag(data);
		break;

	default:
		e = eq;

		ret = tldap_is_attrdesc(s, e - s, false);
		if (!ret) {
			return false;
		}

		if (strncmp(val, "*)", 2) == 0) {
			/* presence */
			asn1_push_tag(data, TLDAP_FILTER_PRES);
			asn1_write(data, s, e - s);
			*_s = val + 1;
			write_octect = false;
			break;
		}

		ret = tldap_find_first_star(val, &star);
		if (!ret) {
			return false;
		}
		if (*star == '*') {
			/* substring */
			asn1_push_tag(data, TLDAP_FILTER_SUB);
			asn1_write_OctetString(data, s, e - s);
			ret = tldap_push_filter_substring(ld, data, val, &s);
			if (!ret) {
				return false;
			}
			*_s = s;
			write_octect = false;
			break;
		}

		/* if nothing else, then it is just equality */
		asn1_push_tag(data, TLDAP_FILTER_EQ);
		write_octect = true;
		break;
	}

	if (write_octect) {
		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_write_OctetString(data, s, e - s);
		asn1_write_OctetString(data, uval, uval_len);
	}

	if (data->has_error) {
		return false;
	}
	asn1_pop_tag(data);
	return true;
}

static bool tldap_push_filter_substring(struct tldap_context *ld,
					struct asn1_data *data,
					const char *val,
					const char **_s)
{
	TALLOC_CTX *tmpctx = talloc_tos();
	bool initial = true;
	const char *star;
	char *chunk;
	size_t chunk_len;
	bool ret;

	/*
	  SubstringFilter ::= SEQUENCE {
		  type	    AttributeDescription,
		  -- at least one must be present
		  substrings      SEQUENCE OF CHOICE {
			  initial [0] LDAPString,
			  any     [1] LDAPString,
			  final   [2] LDAPString } }
	*/
	asn1_push_tag(data, ASN1_SEQUENCE(0));

	do {
		ret = tldap_find_first_star(val, &star);
		if (!ret) {
			return false;
		}
		chunk_len = star - val;

		switch (*star) {
		case '*':
			if (!initial && chunk_len == 0) {
				/* found '**', which is illegal */
				return false;
			}
			break;
		case ')':
			if (initial) {
				/* no stars ?? */
				return false;
			}
			/* we are done */
			break;
		default:
			/* ?? */
			return false;
		}

		if (initial && chunk_len == 0) {
			val = star + 1;
			initial = false;
			continue;
		}

		chunk = talloc_strndup(tmpctx, val, chunk_len);
		if (!chunk) {
			return false;
		}
		ret = tldap_unescape_inplace(chunk, &chunk_len);
		if (!ret) {
			return false;
		}
		switch (*star) {
		case '*':
			if (initial) {
				asn1_push_tag(data, TLDAP_SUB_INI);
				initial = false;
			} else {
				asn1_push_tag(data, TLDAP_SUB_ANY);
			}
			break;
		case ')':
			asn1_push_tag(data, TLDAP_SUB_FIN);
			break;
		default:
			/* ?? */
			return false;
		}
		asn1_write(data, chunk, chunk_len);
		asn1_pop_tag(data);

		val = star + 1;

	} while (*star == '*');

	*_s = star;

	/* end of sequence */
	asn1_pop_tag(data);
	return true;
}

/* NOTE: although openldap libraries allow for spaces in some places, mosly
 * around parenthesis, we do not allow any spaces (except in values of
 * course) as I couldn't fine any place in RFC 4512 or RFC 4515 where
 * leading or trailing spaces where allowed.
 */
static bool tldap_push_filter(struct tldap_context *ld,
			      struct asn1_data *data,
			      const char *filter)
{
	const char *s = filter;
	bool ret;

	ret = tldap_push_filter_int(ld, data, &s);
	if (ret && *s) {
		tldap_debug(ld, TLDAP_DEBUG_ERROR,
			    "Incomplete or malformed filter\n");
		return false;
	}
	return ret;
}

/*****************************************************************************/

static void tldap_search_done(struct tevent_req *subreq);

struct tevent_req *tldap_search_send(TALLOC_CTX *mem_ctx,
				     struct tevent_context *ev,
				     struct tldap_context *ld,
				     const char *base, int scope,
				     const char *filter,
				     const char **attrs,
				     int num_attrs,
				     int attrsonly,
				     struct tldap_control *sctrls,
				     int num_sctrls,
				     struct tldap_control *cctrls,
				     int num_cctrls,
				     int timelimit,
				     int sizelimit,
				     int deref)
{
	struct tevent_req *req, *subreq;
	struct tldap_req_state *state;
	int i;

	req = tldap_req_create(mem_ctx, ld, &state);
	if (req == NULL) {
		return NULL;
	}

	asn1_push_tag(state->out, TLDAP_REQ_SEARCH);
	asn1_write_OctetString(state->out, base, strlen(base));
	asn1_write_enumerated(state->out, scope);
	asn1_write_enumerated(state->out, deref);
	asn1_write_Integer(state->out, sizelimit);
	asn1_write_Integer(state->out, timelimit);
	asn1_write_BOOLEAN(state->out, attrsonly);

	if (!tldap_push_filter(ld, state->out, filter)) {
		goto encoding_error;
	}

	asn1_push_tag(state->out, ASN1_SEQUENCE(0));
	for (i=0; i<num_attrs; i++) {
		asn1_write_OctetString(state->out, attrs[i], strlen(attrs[i]));
	}
	asn1_pop_tag(state->out);
	asn1_pop_tag(state->out);

	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_search_done, req);
	return req;

 encoding_error:
	tevent_req_error(req, TLDAP_ENCODING_ERROR);
	return tevent_req_post(req, ev);
}

static void tldap_search_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);
	if (err != TLDAP_SUCCESS) {
		tevent_req_error(req, err);
		return;
	}
	switch (state->result->type) {
	case TLDAP_RES_SEARCH_ENTRY:
	case TLDAP_RES_SEARCH_REFERENCE:
		if (!tldap_msg_set_pending(subreq)) {
			tevent_req_oom(req);
			return;
		}
		tevent_req_notify_callback(req);
		break;
	case TLDAP_RES_SEARCH_RESULT:
		TALLOC_FREE(subreq);
		if (!asn1_start_tag(state->result->data,
				    state->result->type) ||
		    !tldap_decode_response(state) ||
		    !asn1_end_tag(state->result->data) ||
		    !tldap_decode_controls(state)) {
			tevent_req_error(req, TLDAP_DECODING_ERROR);
			return;
		}
		tevent_req_done(req);
		break;
	default:
		tevent_req_error(req, TLDAP_PROTOCOL_ERROR);
		return;
	}
}

int tldap_search_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
		      struct tldap_message **pmsg)
{
	struct tldap_req_state *state = tevent_req_data(
		req, struct tldap_req_state);
	int err;

	if (!tevent_req_is_in_progress(req)
	    && tevent_req_is_ldap_error(req, &err)) {
		return err;
	}

	if (tevent_req_is_in_progress(req)) {
		switch (state->result->type) {
		case TLDAP_RES_SEARCH_ENTRY:
		case TLDAP_RES_SEARCH_REFERENCE:
			break;
		default:
			return TLDAP_OPERATIONS_ERROR;
		}
	}

	*pmsg = talloc_move(mem_ctx, &state->result);
	return TLDAP_SUCCESS;
}

struct tldap_sync_search_state {
	TALLOC_CTX *mem_ctx;
	struct tldap_message **entries;
	struct tldap_message **refs;
	int rc;
};

static void tldap_search_cb(struct tevent_req *req)
{
	struct tldap_sync_search_state *state =
		(struct tldap_sync_search_state *)
		tevent_req_callback_data_void(req);
	struct tldap_message *msg, **tmp;
	int rc, num_entries, num_refs;

	rc = tldap_search_recv(req, talloc_tos(), &msg);
	if (rc != TLDAP_SUCCESS) {
		state->rc = rc;
		return;
	}

	switch (tldap_msg_type(msg)) {
	case TLDAP_RES_SEARCH_ENTRY:
		num_entries = talloc_array_length(state->entries);
		tmp = talloc_realloc(state->mem_ctx, state->entries,
				     struct tldap_message *, num_entries + 1);
		if (tmp == NULL) {
			state->rc = TLDAP_NO_MEMORY;
			return;
		}
		state->entries = tmp;
		state->entries[num_entries] = talloc_move(state->entries,
							  &msg);
		break;
	case TLDAP_RES_SEARCH_REFERENCE:
		num_refs = talloc_array_length(state->refs);
		tmp = talloc_realloc(state->mem_ctx, state->refs,
				     struct tldap_message *, num_refs + 1);
		if (tmp == NULL) {
			state->rc = TLDAP_NO_MEMORY;
			return;
		}
		state->refs = tmp;
		state->refs[num_refs] = talloc_move(state->refs, &msg);
		break;
	case TLDAP_RES_SEARCH_RESULT:
		state->rc = TLDAP_SUCCESS;
		break;
	default:
		state->rc = TLDAP_PROTOCOL_ERROR;
		break;
	}
}

int tldap_search(struct tldap_context *ld,
		 const char *base, int scope, const char *filter,
		 const char **attrs, int num_attrs, int attrsonly,
		 struct tldap_control *sctrls, int num_sctrls,
		 struct tldap_control *cctrls, int num_cctrls,
		 int timelimit, int sizelimit, int deref,
		 TALLOC_CTX *mem_ctx, struct tldap_message ***entries,
		 struct tldap_message ***refs)
{
	TALLOC_CTX *frame = talloc_stackframe();
	struct tevent_context *ev;
	struct tevent_req *req;
	struct tldap_sync_search_state state;

	ZERO_STRUCT(state);
	state.mem_ctx = mem_ctx;
	state.rc = TLDAP_SUCCESS;

	ev = samba_tevent_context_init(frame);
	if (ev == NULL) {
		state.rc = TLDAP_NO_MEMORY;
		goto fail;
	}

	req = tldap_search_send(frame, ev, ld, base, scope, filter,
				attrs, num_attrs, attrsonly,
				sctrls, num_sctrls, cctrls, num_cctrls,
				timelimit, sizelimit, deref);
	if (req == NULL) {
		state.rc = TLDAP_NO_MEMORY;
		goto fail;
	}

	tevent_req_set_callback(req, tldap_search_cb, &state);

	if (!tevent_req_is_in_progress(req)) {
		/* an error happend before sending */
		if (tevent_req_is_ldap_error(req, &state.rc)) {
			goto fail;
		}
	}

	while (tevent_req_is_in_progress(req)
	       && (state.rc == TLDAP_SUCCESS)) {
		if (tevent_loop_once(ev) == -1) {
			return TLDAP_OPERATIONS_ERROR;
		}
	}

	if (state.rc != TLDAP_SUCCESS) {
		return state.rc;
	}

	if (entries != NULL) {
		*entries = state.entries;
	} else {
		TALLOC_FREE(state.entries);
	}
	if (refs != NULL) {
		*refs = state.refs;
	} else {
		TALLOC_FREE(state.refs);
	}
	tldap_save_msg(ld, req);
fail:
	TALLOC_FREE(frame);
	return state.rc;
}

static bool tldap_parse_search_entry(struct tldap_message *msg)
{
	int num_attribs = 0;

	asn1_start_tag(msg->data, msg->type);

	/* dn */

	asn1_read_OctetString_talloc(msg, msg->data, &msg->dn);
	if (msg->dn == NULL) {
		return false;
	}

	/*
	 * Attributes: We overallocate msg->attribs by one, so that while
	 * looping over the attributes we can directly parse into the last
	 * array element. Same for the values in the inner loop.
	 */

	msg->attribs = talloc_array(msg, struct tldap_attribute, 1);
	if (msg->attribs == NULL) {
		return false;
	}

	asn1_start_tag(msg->data, ASN1_SEQUENCE(0));
	while (asn1_peek_tag(msg->data, ASN1_SEQUENCE(0))) {
		struct tldap_attribute *attrib;
		int num_values = 0;

		attrib = &msg->attribs[num_attribs];
		attrib->values = talloc_array(msg->attribs, DATA_BLOB, 1);
		if (attrib->values == NULL) {
			return false;
		}
		asn1_start_tag(msg->data, ASN1_SEQUENCE(0));
		asn1_read_OctetString_talloc(msg->attribs, msg->data,
					     &attrib->name);
		asn1_start_tag(msg->data, ASN1_SET);

		while (asn1_peek_tag(msg->data, ASN1_OCTET_STRING)) {
			asn1_read_OctetString(msg->data, msg,
					      &attrib->values[num_values]);

			attrib->values = talloc_realloc(
				msg->attribs, attrib->values, DATA_BLOB,
				num_values + 2);
			if (attrib->values == NULL) {
				return false;
			}
			num_values += 1;
		}
		attrib->values = talloc_realloc(msg->attribs, attrib->values,
						DATA_BLOB, num_values);
		attrib->num_values = num_values;

		asn1_end_tag(msg->data); /* ASN1_SET */
		asn1_end_tag(msg->data); /* ASN1_SEQUENCE(0) */
		msg->attribs = talloc_realloc(
			msg, msg->attribs, struct tldap_attribute,
			num_attribs + 2);
		if (msg->attribs == NULL) {
			return false;
		}
		num_attribs += 1;
	}
	msg->attribs = talloc_realloc(
		msg, msg->attribs, struct tldap_attribute, num_attribs);
	asn1_end_tag(msg->data);
	if (msg->data->has_error) {
		return false;
	}
	return true;
}

bool tldap_entry_dn(struct tldap_message *msg, char **dn)
{
	if ((msg->dn == NULL) && (!tldap_parse_search_entry(msg))) {
		return false;
	}
	*dn = msg->dn;
	return true;
}

bool tldap_entry_attributes(struct tldap_message *msg,
			    struct tldap_attribute **attributes,
			    int *num_attributes)
{
	if ((msg->dn == NULL) && (!tldap_parse_search_entry(msg))) {
		return false;
	}
	*attributes = msg->attribs;
	*num_attributes = talloc_array_length(msg->attribs);
	return true;
}

static bool tldap_decode_controls(struct tldap_req_state *state)
{
	struct tldap_message *msg = state->result;
	struct asn1_data *data = msg->data;
	struct tldap_control *sctrls = NULL;
	int num_controls = 0;

	msg->res_sctrls = NULL;

	if (!asn1_peek_tag(data, ASN1_CONTEXT(0))) {
		return true;
	}

	asn1_start_tag(data, ASN1_CONTEXT(0));

	while (asn1_peek_tag(data, ASN1_SEQUENCE(0))) {
		struct tldap_control *c;
		char *oid = NULL;

		sctrls = talloc_realloc(msg, sctrls, struct tldap_control,
					num_controls + 1);
		if (sctrls == NULL) {
			return false;
		}
		c = &sctrls[num_controls];

		asn1_start_tag(data, ASN1_SEQUENCE(0));
		asn1_read_OctetString_talloc(msg, data, &oid);
		if ((data->has_error) || (oid == NULL)) {
			return false;
		}
		c->oid = oid;
		if (asn1_peek_tag(data, ASN1_BOOLEAN)) {
			asn1_read_BOOLEAN(data, &c->critical);
		} else {
			c->critical = false;
		}
		c->value = data_blob_null;
		if (asn1_peek_tag(data, ASN1_OCTET_STRING) &&
		    !asn1_read_OctetString(data, msg, &c->value)) {
			return false;
		}
		asn1_end_tag(data); /* ASN1_SEQUENCE(0) */

		num_controls += 1;
	}

	asn1_end_tag(data); 	/* ASN1_CONTEXT(0) */

	if (data->has_error) {
		TALLOC_FREE(sctrls);
		return false;
	}
	msg->res_sctrls = sctrls;
	return true;
}

static void tldap_simple_done(struct tevent_req *subreq, int type)
{
	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 != type) {
		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) ||
	    !tldap_decode_controls(state)) {
		tevent_req_error(req, TLDAP_DECODING_ERROR);
		return;
	}
	if (state->result->lderr != TLDAP_SUCCESS) {
		tevent_req_error(req, state->result->lderr);
		return;
	}
	tevent_req_done(req);
}

static int tldap_simple_recv(struct tevent_req *req)
{
	int err;
	if (tevent_req_is_ldap_error(req, &err)) {
		return err;
	}
	return TLDAP_SUCCESS;
}

static void tldap_add_done(struct tevent_req *subreq);

struct tevent_req *tldap_add_send(TALLOC_CTX *mem_ctx,
				  struct tevent_context *ev,
				  struct tldap_context *ld,
				  const char *dn,
				  struct tldap_mod *attributes,
				  int num_attributes,
				  struct tldap_control *sctrls,
				  int num_sctrls,
				  struct tldap_control *cctrls,
				  int num_cctrls)
{
	struct tevent_req *req, *subreq;
	struct tldap_req_state *state;
	int i, j;

	req = tldap_req_create(mem_ctx, ld, &state);
	if (req == NULL) {
		return NULL;
	}

	asn1_push_tag(state->out, TLDAP_REQ_ADD);
	asn1_write_OctetString(state->out, dn, strlen(dn));
	asn1_push_tag(state->out, ASN1_SEQUENCE(0));

	for (i=0; i<num_attributes; i++) {
		struct tldap_mod *attrib = &attributes[i];
		asn1_push_tag(state->out, ASN1_SEQUENCE(0));
		asn1_write_OctetString(state->out, attrib->attribute,
				       strlen(attrib->attribute));
		asn1_push_tag(state->out, ASN1_SET);
		for (j=0; j<attrib->num_values; j++) {
			asn1_write_OctetString(state->out,
					       attrib->values[j].data,
					       attrib->values[j].length);
		}
		asn1_pop_tag(state->out);
		asn1_pop_tag(state->out);
	}

	asn1_pop_tag(state->out);
	asn1_pop_tag(state->out);

	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_add_done, req);
	return req;
}

static void tldap_add_done(struct tevent_req *subreq)
{
	tldap_simple_done(subreq, TLDAP_RES_ADD);
}

int tldap_add_recv(struct tevent_req *req)
{
	return tldap_simple_recv(req);
}

int tldap_add(struct tldap_context *ld, const char *dn,
	      struct tldap_mod *attributes, int num_attributes,
	      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_add_send(frame, ev, ld, dn, attributes, num_attributes,
			     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_add_recv(req);
	tldap_save_msg(ld, req);
 fail:
	TALLOC_FREE(frame);
	return result;
}

static void tldap_modify_done(struct tevent_req *subreq);

struct tevent_req *tldap_modify_send(TALLOC_CTX *mem_ctx,
				     struct tevent_context *ev,
				     struct tldap_context *ld,
				     const char *dn,
				     struct tldap_mod *mods, int num_mods,
				     struct tldap_control *sctrls,
				     int num_sctrls,
				     struct tldap_control *cctrls,
				     int num_cctrls)
{
	struct tevent_req *req, *subreq;
	struct tldap_req_state *state;
	int i, j;

	req = tldap_req_create(mem_ctx, ld, &state);
	if (req == NULL) {
		return NULL;
	}

	asn1_push_tag(state->out, TLDAP_REQ_MODIFY);
	asn1_write_OctetString(state->out, dn, strlen(dn));
	asn1_push_tag(state->out, ASN1_SEQUENCE(0));

	for (i=0; i<num_mods; i++) {
		struct tldap_mod *mod = &mods[i];
		asn1_push_tag(state->out, ASN1_SEQUENCE(0));
		asn1_write_enumerated(state->out, mod->mod_op),
		asn1_push_tag(state->out, ASN1_SEQUENCE(0));
		asn1_write_OctetString(state->out, mod->attribute,
				       strlen(mod->attribute));
		asn1_push_tag(state->out, ASN1_SET);
		for (j=0; j<mod->num_values; j++) {
			asn1_write_OctetString(state->out,
					       mod->values[j].data,
					       mod->values[j].length);
		}
		asn1_pop_tag(state->out);
		asn1_pop_tag(state->out);
		asn1_pop_tag(state->out);
	}

	asn1_pop_tag(state->out);
	asn1_pop_tag(state->out);

	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_modify_done, req);
	return req;
}

static void tldap_modify_done(struct tevent_req *subreq)
{
	tldap_simple_done(subreq, TLDAP_RES_MODIFY);
}

int tldap_modify_recv(struct tevent_req *req)
{
	return tldap_simple_recv(req);
}

int tldap_modify(struct tldap_context *ld, const char *dn,
		 struct tldap_mod *mods, int num_mods,
		 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_modify_send(frame, ev, ld, dn, mods, num_mods,
				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_modify_recv(req);
	tldap_save_msg(ld, req);
 fail:
	TALLOC_FREE(frame);
	return result;
}

static void tldap_delete_done(struct tevent_req *subreq);

struct tevent_req *tldap_delete_send(TALLOC_CTX *mem_ctx,
				     struct tevent_context *ev,
				     struct tldap_context *ld,
				     const char *dn,
				     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;
	}

	asn1_push_tag(state->out, TLDAP_REQ_DELETE);
	asn1_write(state->out, dn, strlen(dn));
	asn1_pop_tag(state->out);

	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_delete_done, req);
	return req;
}

static void tldap_delete_done(struct tevent_req *subreq)
{
	tldap_simple_done(subreq, TLDAP_RES_DELETE);
}

int tldap_delete_recv(struct tevent_req *req)
{
	return tldap_simple_recv(req);
}

int tldap_delete(struct tldap_context *ld, const char *dn,
		 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_delete_send(frame, ev, ld, dn, 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_delete_recv(req);
	tldap_save_msg(ld, req);
 fail:
	TALLOC_FREE(frame);
	return result;
}

int tldap_msg_id(const struct tldap_message *msg)
{
	return msg->id;
}

int tldap_msg_type(const struct tldap_message *msg)
{
	return msg->type;
}

const char *tldap_msg_matcheddn(struct tldap_message *msg)
{
	if (msg == NULL) {
		return NULL;
	}
	return msg->res_matcheddn;
}

const char *tldap_msg_diagnosticmessage(struct tldap_message *msg)
{
	if (msg == NULL) {
		return NULL;
	}
	return msg->res_diagnosticmessage;
}

const char *tldap_msg_referral(struct tldap_message *msg)
{
	if (msg == NULL) {
		return NULL;
	}
	return msg->res_referral;
}

void tldap_msg_sctrls(struct tldap_message *msg, int *num_sctrls,
		      struct tldap_control **sctrls)
{
	if (msg == NULL) {
		*sctrls = NULL;
		*num_sctrls = 0;
		return;
	}
	*sctrls = msg->res_sctrls;
	*num_sctrls = talloc_array_length(msg->res_sctrls);
}

struct tldap_message *tldap_ctx_lastmsg(struct tldap_context *ld)
{
	return ld->last_msg;
}

const char *tldap_err2string(int rc)
{
	const char *res = NULL;

	/*
	 * This would normally be a table, but the error codes are not fully
	 * sequential. Let the compiler figure out the optimum implementation
	 * :-)
	 */

	switch (rc) {
	case TLDAP_SUCCESS:
		res = "TLDAP_SUCCESS";
		break;
	case TLDAP_OPERATIONS_ERROR:
		res = "TLDAP_OPERATIONS_ERROR";
		break;
	case TLDAP_PROTOCOL_ERROR:
		res = "TLDAP_PROTOCOL_ERROR";
		break;
	case TLDAP_TIMELIMIT_EXCEEDED:
		res = "TLDAP_TIMELIMIT_EXCEEDED";
		break;
	case TLDAP_SIZELIMIT_EXCEEDED:
		res = "TLDAP_SIZELIMIT_EXCEEDED";
		break;
	case TLDAP_COMPARE_FALSE:
		res = "TLDAP_COMPARE_FALSE";
		break;
	case TLDAP_COMPARE_TRUE:
		res = "TLDAP_COMPARE_TRUE";
		break;
	case TLDAP_STRONG_AUTH_NOT_SUPPORTED:
		res = "TLDAP_STRONG_AUTH_NOT_SUPPORTED";
		break;
	case TLDAP_STRONG_AUTH_REQUIRED:
		res = "TLDAP_STRONG_AUTH_REQUIRED";
		break;
	case TLDAP_REFERRAL:
		res = "TLDAP_REFERRAL";
		break;
	case TLDAP_ADMINLIMIT_EXCEEDED:
		res = "TLDAP_ADMINLIMIT_EXCEEDED";
		break;
	case TLDAP_UNAVAILABLE_CRITICAL_EXTENSION:
		res = "TLDAP_UNAVAILABLE_CRITICAL_EXTENSION";
		break;
	case TLDAP_CONFIDENTIALITY_REQUIRED:
		res = "TLDAP_CONFIDENTIALITY_REQUIRED";
		break;
	case TLDAP_SASL_BIND_IN_PROGRESS:
		res = "TLDAP_SASL_BIND_IN_PROGRESS";
		break;
	case TLDAP_NO_SUCH_ATTRIBUTE:
		res = "TLDAP_NO_SUCH_ATTRIBUTE";
		break;
	case TLDAP_UNDEFINED_TYPE:
		res = "TLDAP_UNDEFINED_TYPE";
		break;
	case TLDAP_INAPPROPRIATE_MATCHING:
		res = "TLDAP_INAPPROPRIATE_MATCHING";
		break;
	case TLDAP_CONSTRAINT_VIOLATION:
		res = "TLDAP_CONSTRAINT_VIOLATION";
		break;
	case TLDAP_TYPE_OR_VALUE_EXISTS:
		res = "TLDAP_TYPE_OR_VALUE_EXISTS";
		break;
	case TLDAP_INVALID_SYNTAX:
		res = "TLDAP_INVALID_SYNTAX";
		break;
	case TLDAP_NO_SUCH_OBJECT:
		res = "TLDAP_NO_SUCH_OBJECT";
		break;
	case TLDAP_ALIAS_PROBLEM:
		res = "TLDAP_ALIAS_PROBLEM";
		break;
	case TLDAP_INVALID_DN_SYNTAX:
		res = "TLDAP_INVALID_DN_SYNTAX";
		break;
	case TLDAP_IS_LEAF:
		res = "TLDAP_IS_LEAF";
		break;
	case TLDAP_ALIAS_DEREF_PROBLEM:
		res = "TLDAP_ALIAS_DEREF_PROBLEM";
		break;
	case TLDAP_INAPPROPRIATE_AUTH:
		res = "TLDAP_INAPPROPRIATE_AUTH";
		break;
	case TLDAP_INVALID_CREDENTIALS:
		res = "TLDAP_INVALID_CREDENTIALS";
		break;
	case TLDAP_INSUFFICIENT_ACCESS:
		res = "TLDAP_INSUFFICIENT_ACCESS";
		break;
	case TLDAP_BUSY:
		res = "TLDAP_BUSY";
		break;
	case TLDAP_UNAVAILABLE:
		res = "TLDAP_UNAVAILABLE";
		break;
	case TLDAP_UNWILLING_TO_PERFORM:
		res = "TLDAP_UNWILLING_TO_PERFORM";
		break;
	case TLDAP_LOOP_DETECT:
		res = "TLDAP_LOOP_DETECT";
		break;
	case TLDAP_NAMING_VIOLATION:
		res = "TLDAP_NAMING_VIOLATION";
		break;
	case TLDAP_OBJECT_CLASS_VIOLATION:
		res = "TLDAP_OBJECT_CLASS_VIOLATION";
		break;
	case TLDAP_NOT_ALLOWED_ON_NONLEAF:
		res = "TLDAP_NOT_ALLOWED_ON_NONLEAF";
		break;
	case TLDAP_NOT_ALLOWED_ON_RDN:
		res = "TLDAP_NOT_ALLOWED_ON_RDN";
		break;
	case TLDAP_ALREADY_EXISTS:
		res = "TLDAP_ALREADY_EXISTS";
		break;
	case TLDAP_NO_OBJECT_CLASS_MODS:
		res = "TLDAP_NO_OBJECT_CLASS_MODS";
		break;
	case TLDAP_RESULTS_TOO_LARGE:
		res = "TLDAP_RESULTS_TOO_LARGE";
		break;
	case TLDAP_AFFECTS_MULTIPLE_DSAS:
		res = "TLDAP_AFFECTS_MULTIPLE_DSAS";
		break;
	case TLDAP_OTHER:
		res = "TLDAP_OTHER";
		break;
	case TLDAP_SERVER_DOWN:
		res = "TLDAP_SERVER_DOWN";
		break;
	case TLDAP_LOCAL_ERROR:
		res = "TLDAP_LOCAL_ERROR";
		break;
	case TLDAP_ENCODING_ERROR:
		res = "TLDAP_ENCODING_ERROR";
		break;
	case TLDAP_DECODING_ERROR:
		res = "TLDAP_DECODING_ERROR";
		break;
	case TLDAP_TIMEOUT:
		res = "TLDAP_TIMEOUT";
		break;
	case TLDAP_AUTH_UNKNOWN:
		res = "TLDAP_AUTH_UNKNOWN";
		break;
	case TLDAP_FILTER_ERROR:
		res = "TLDAP_FILTER_ERROR";
		break;
	case TLDAP_USER_CANCELLED:
		res = "TLDAP_USER_CANCELLED";
		break;
	case TLDAP_PARAM_ERROR:
		res = "TLDAP_PARAM_ERROR";
		break;
	case TLDAP_NO_MEMORY:
		res = "TLDAP_NO_MEMORY";
		break;
	case TLDAP_CONNECT_ERROR:
		res = "TLDAP_CONNECT_ERROR";
		break;
	case TLDAP_NOT_SUPPORTED:
		res = "TLDAP_NOT_SUPPORTED";
		break;
	case TLDAP_CONTROL_NOT_FOUND:
		res = "TLDAP_CONTROL_NOT_FOUND";
		break;
	case TLDAP_NO_RESULTS_RETURNED:
		res = "TLDAP_NO_RESULTS_RETURNED";
		break;
	case TLDAP_MORE_RESULTS_TO_RETURN:
		res = "TLDAP_MORE_RESULTS_TO_RETURN";
		break;
	case TLDAP_CLIENT_LOOP:
		res = "TLDAP_CLIENT_LOOP";
		break;
	case TLDAP_REFERRAL_LIMIT_EXCEEDED:
		res = "TLDAP_REFERRAL_LIMIT_EXCEEDED";
		break;
	default:
		res = talloc_asprintf(talloc_tos(), "Unknown LDAP Error (%d)",
				      rc);
		break;
	}
	if (res == NULL) {
		res = "Unknown LDAP Error";
	}
	return res;
}