Index | Thread | Search

From:
Philipp <philipp+openbsd@bureaucracy.de>
Subject:
aldap
To:
tech@openbsd.org
Date:
Sun, 08 Sep 2024 14:52:54 +0200

Download raw body.

Thread
  • Philipp:

    aldap

Hi

Some of you might noticed, that I work on the table-ldap for opensmtpd.
This table uses a copy of aldap.[ch]. There are currently some changes
to the upstream version:

1. aldap_url.port changed from int to char *

 This was because aldap_parse_url() already holds a string like pointer
 to the port number and to use the port in getaddrinfo(3) you need a
 char *. It still checks if the port is a parsable int. So you can
 directly pass the port to getaddrinfo().

2. add template to the filter of aldap_search()

 When use ldap with some user provided input you need to have some
 sort of templates. I have put this inside the parseval() function.
 This avoids ldap injections, without require escaping. This might
 be useful for login_ldap(8).

3. allow non blocking aldap_parse()

 This is quite table-ldap specific, but it might be useful for other
 programs which uses aldap. It just allows to set a bool if you want to
 block till a parseable ldap message is received. The caller can choose
 between a blocking mode and non blocking mode.

4. clean up the aldap_*_free() functions

 Now this functions gracefully handle NULL and all free like functions
 return void.

5. make more parameter const

 This was mostly done by Olmar, but I like the approach.

6. remove includes from header

 This prevents double includes and is a general cleanup.

7. Fix infinite loop in aldap_parse()

 This was already send to tech@, but isn't committed yet.

I think this changes helps the aldap version of OpenBSD. I have attached
the current version of aldap.[ch]. So when someone is interested in
upstreaming (parts of) it, just contact me (or upstream it yourself).
Also it would be simpler to upstream changes, when aldap would be a lib
and not just copied three times in the tree.

I have also some plans to improve aldap. Most of it is only some
idea I though might be useful. Some of this idea might just be
brain farts, so I would just drop then when I notice this during
implementing.

Most important is to write some documentation for the public interface.
I still have no clue about mdoc, but I can provide useful information
about how aldap works and how are the parameters are used.

In general I would like to have aldap able to used fully nonblocking.
This is also mostly important for table-ldap, to allow a reconnect or
update (reread the config and reconnect) without blocking the connection
to opensmtpd.

I would like to simplify the API of aldap_search(). This function has
10 parameters (one added by me). I would like to split it up in two
functions one to prepare the search request and one for the search.

Next I would also like to have a more general ldap struct for some
"global" parameters like the base of the search. This might be helpful
when dealing with alias dereferencing.

So when someone is interested in implementing parts of this or has other
ideas to improve aldap, just contact me so we avoid duplicated work.

Philipp
/*	$OpenBSD: aldap.c,v 1.10 2022/03/31 09:03:48 martijn Exp $ */

/*
 * Copyright (c) 2008 Alexander Schrijver <aschrijver@openbsd.org>
 * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "compat.h"

#include <sys/types.h>

#include <arpa/inet.h>
#include <ber.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <poll.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <tls.h>
#include <unistd.h>

#include <event.h>

#include "aldap.h"

#if 0
#define DEBUG
#endif
#define ALDAP_VERSION 3

static struct ber_element	*ldap_parse_search_filter(struct ber_element *,
				    const char *, const struct aldap_filter_ctx *);
static struct ber_element	*ldap_do_parse_search_filter(
				    struct ber_element *, const char **, const struct aldap_filter_ctx *);
struct aldap_stringset		*aldap_get_stringset(struct ber_element *);
char				*utoa(char *);
static int			 isu8cont(unsigned char);
static char			*parseval(const char *, size_t, const struct aldap_filter_ctx *);
int				aldap_create_page_control(struct ber_element *,
				    int, struct aldap_page_control *);
int				aldap_send(struct aldap *,
				    struct ber_element *);
unsigned int			aldap_application(struct ber_element *);

#ifdef DEBUG
void			 ldap_debug_elements(struct ber_element *);
#endif

#ifdef DEBUG
#define DPRINTF(x...)	printf(x)
#define LDAP_DEBUG(x, y)	do { fprintf(stderr, "*** " x "\n"); ldap_debug_elements(y); } while (0)
#else
#define DPRINTF(x...)	do { } while (0)
#define LDAP_DEBUG(x, y)	do { } while (0)
#endif

unsigned int
aldap_application(struct ber_element *elm)
{
	return BER_TYPE_OCTETSTRING;
}

int
aldap_close(struct aldap *al)
{
	if (al->tls != NULL) {
		tls_close(al->tls);
		tls_free(al->tls);
	}
	close(al->fd);
	ober_free(&al->ber);
	evbuffer_free(al->buf);
	free(al);

	return (0);
}

struct aldap *
aldap_init(int fd)
{
	struct aldap *a;

	if ((a = calloc(1, sizeof(*a))) == NULL)
		return NULL;
	a->buf = evbuffer_new();
	a->fd = fd;
	ober_set_application(&a->ber, aldap_application);

	return a;
}

int
aldap_tls(struct aldap *ldap, struct tls_config *cfg, const char *name)
{
	int ret;

	ldap->tls = tls_client();
	if (ldap->tls == NULL) {
		ldap->err = ALDAP_ERR_OPERATION_FAILED;
		return (-1);
	}

	if (tls_configure(ldap->tls, cfg) == -1) {
		ldap->err = ALDAP_ERR_TLS_ERROR;
		return (-1);
	}

	if (tls_connect_socket(ldap->tls, ldap->fd, name) == -1) {
		ldap->err = ALDAP_ERR_TLS_ERROR;
		return (-1);
	}

	do {
		ret = tls_handshake(ldap->tls);
	} while (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT);
	if (ret == -1) {
		ldap->err = ALDAP_ERR_TLS_ERROR;
		return (-1);
	}

	return (0);
}

int
aldap_send(struct aldap *ldap, struct ber_element *root)
{
	void *ptr;
	char *data;
	size_t len, done;
	ssize_t error, wrote;
	struct pollfd pfd = {ldap->fd, 0, 0};

	len = ober_calc_len(root);
	error = ober_write_elements(&ldap->ber, root);
	ober_free_elements(root);
	if (error == -1)
		return -1;

	ober_get_writebuf(&ldap->ber, &ptr);
	done = 0;
	data = ptr;
	while (len > 0) {
		if (pfd.events) {
			/* TODO handle errors and timeout */
			poll(&pfd, 1, -1);
		}
		pfd.events = 0;

		if (ldap->tls != NULL) {
			wrote = tls_write(ldap->tls, data + done, len);
			switch (wrote) {
			case TLS_WANT_POLLIN:
				pfd.events = POLLIN;
				continue;
			case TLS_WANT_POLLOUT:
				pfd.events = POLLOUT;
				continue;
			default:
				break;
			}
		} else
			wrote = write(ldap->fd, data + done, len);

		if (wrote == -1) {
			if (errno == EAGAIN || errno == EWOULDBLOCK) {
				pfd.events = POLLOUT;
				continue;
			}
			return -1;
		}

		len -= wrote;
		done += wrote;
	}

	return 0;
}

int
aldap_req_starttls(struct aldap *ldap)
{
	struct ber_element *root = NULL, *ber;

	if ((root = ober_add_sequence(NULL)) == NULL)
		goto fail;

	ber = ober_printf_elements(root, "d{tst", ++ldap->msgid, BER_CLASS_APP,
	    LDAP_REQ_EXTENDED, LDAP_STARTTLS_OID, BER_CLASS_CONTEXT, 0);
	if (ber == NULL) {
		ldap->err = ALDAP_ERR_OPERATION_FAILED;
		goto fail;
	}

	if (aldap_send(ldap, root) == -1)
		goto fail;

	return (ldap->msgid);
fail:
	if (root != NULL)
		ober_free_elements(root);

	ldap->err = ALDAP_ERR_OPERATION_FAILED;
	return (-1);
}

int
aldap_bind(struct aldap *ldap, char *binddn, char *bindcred)
{
	struct ber_element *root = NULL, *elm;

	if (binddn == NULL)
		binddn = "";
	if (bindcred == NULL)
		bindcred = "";

	if ((root = ober_add_sequence(NULL)) == NULL)
		goto fail;

	elm = ober_printf_elements(root, "d{tdsst", ++ldap->msgid, BER_CLASS_APP,
	    LDAP_REQ_BIND, ALDAP_VERSION, binddn, bindcred, BER_CLASS_CONTEXT,
	    LDAP_AUTH_SIMPLE);
	if (elm == NULL)
		goto fail;

	LDAP_DEBUG("aldap_bind", root);

	if (aldap_send(ldap, root) == -1) {
		root = NULL;
		goto fail;
	}
	return (ldap->msgid);
fail:
	if (root != NULL)
		ober_free_elements(root);

	ldap->err = ALDAP_ERR_OPERATION_FAILED;
	return (-1);
}

int
aldap_unbind(struct aldap *ldap)
{
	struct ber_element *root = NULL, *elm;

	if ((root = ober_add_sequence(NULL)) == NULL)
		goto fail;
	elm = ober_printf_elements(root, "d{t", ++ldap->msgid, BER_CLASS_APP,
	    LDAP_REQ_UNBIND_30);
	if (elm == NULL)
		goto fail;

	LDAP_DEBUG("aldap_unbind", root);

	if (aldap_send(ldap, root) == -1) {
		root = NULL;
		goto fail;
	}
	return (ldap->msgid);
fail:
	if (root != NULL)
		ober_free_elements(root);

	ldap->err = ALDAP_ERR_OPERATION_FAILED;

	return (-1);
}

int
aldap_search(struct aldap *ldap, char *basedn, enum scope scope, const char *filter,
    const struct aldap_filter_ctx *ctx, char * const *attrs, int typesonly, int sizelimit, int timelimit,
    struct aldap_page_control *page)
{
	struct ber_element *root = NULL, *ber, *c;
	int i;

	if ((root = ober_add_sequence(NULL)) == NULL) {
		ldap->err = ALDAP_ERR_OPERATION_FAILED;
		goto fail;
	}

	ber = ober_printf_elements(root, "d{t", ++ldap->msgid, BER_CLASS_APP,
	    LDAP_REQ_SEARCH);
	if (ber == NULL) {
		ldap->err = ALDAP_ERR_OPERATION_FAILED;
		goto fail;
	}

	c = ber;	
	ber = ober_printf_elements(ber, "sEEddb", basedn, (long long)scope,
	                         (long long)LDAP_DEREF_NEVER, sizelimit, 
				 timelimit, typesonly);
	if (ber == NULL) {
		ldap->err = ALDAP_ERR_OPERATION_FAILED;
		goto fail;
	}

	if ((ber = ldap_parse_search_filter(ber, filter, ctx)) == NULL) {
		ldap->err = ALDAP_ERR_PARSER_ERROR;
		goto fail;
	}

	if ((ber = ober_add_sequence(ber)) == NULL) {
		ldap->err = ALDAP_ERR_OPERATION_FAILED;
		goto fail;
	}
	if (attrs != NULL)
		for (i = 0; attrs[i] != NULL; i++) {
			if ((ber = ober_add_string(ber, attrs[i])) == NULL)
				goto fail;
		}

	aldap_create_page_control(c, 100, page);

	LDAP_DEBUG("aldap_search", root);

	if (aldap_send(ldap, root) == -1) {
		root = NULL;
		ldap->err = ALDAP_ERR_OPERATION_FAILED;
		goto fail;
	}

	return (ldap->msgid);

fail:
	if (root != NULL)
		ober_free_elements(root);

	return (-1);
}

int
aldap_create_page_control(struct ber_element *elm, int size,
    struct aldap_page_control *page)
{
	ssize_t len;
	struct ber c;
	struct ber_element *ber = NULL;

	c.br_wbuf = NULL;

	ber = ober_add_sequence(NULL);

	if (page == NULL) {
		if (ober_printf_elements(ber, "ds", 50, "") == NULL)
			goto fail;
	} else {
		if (ober_printf_elements(ber, "dx", 50, page->cookie,
			    page->cookie_len) == NULL)
			goto fail;
	}

	if ((len = ober_write_elements(&c, ber)) < 1)
		goto fail;
	if (ober_printf_elements(elm, "{t{sx", 2, 0, LDAP_PAGED_OID,
		                c.br_wbuf, (size_t)len) == NULL)
		goto fail;

	ober_free_elements(ber);
	ober_free(&c);
	return len;
fail:
	if (ber != NULL)
		ober_free_elements(ber);
	ober_free(&c);	

	return (-1);
}

struct aldap_message *
aldap_parse(struct aldap *ldap, bool blocking)
{
	int			 class;
	unsigned int		 type;
	long long		 msgid = 0;
	struct aldap_message	*m;
	struct ber_element	*a = NULL, *ep;
	char			 rbuf[512];
	int			 ret, retry;
	struct pollfd		 pfd = {ldap->fd, 0, 0};

	if ((m = calloc(1, sizeof(struct aldap_message))) == NULL)
		goto opfail;

	retry = 0;
	while (m->msg == NULL) {
		if (retry || EVBUFFER_LENGTH(ldap->buf) == 0) {
			if (pfd.events) {
				/* TODO handle errors and timeout */
				poll(&pfd, 1, -1);
			}
			pfd.events = 0;

			if (ldap->tls) {
				ret = tls_read(ldap->tls, rbuf, sizeof(rbuf));
				switch (ret) {
				case TLS_WANT_POLLOUT:
					if (blocking) {
						pfd.events = POLLOUT;
						continue;
					}
					goto epollout;
				case TLS_WANT_POLLIN:
					if (blocking) {
						pfd.events = POLLIN;
						continue;
					}
					goto epollin;
				default:
					break;
				}
			} else
				ret = read(ldap->fd, rbuf, sizeof(rbuf));

			if (ret == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
				if (blocking) {
					pfd.events = POLLIN;
					continue;
				}
				goto epollin;
			}
			if (ret <= 0) {
				goto opfail;
			}

			if (evbuffer_add(ldap->buf, rbuf, ret) == -1)
				goto enomem;
		}

		if (EVBUFFER_LENGTH(ldap->buf) > 0) {
			ober_set_readbuf(&ldap->ber, EVBUFFER_DATA(ldap->buf),
			    EVBUFFER_LENGTH(ldap->buf));
			errno = 0;
			m->msg = ober_read_elements(&ldap->ber, NULL);
			if (errno != 0 && errno != ECANCELED) {
				goto parsefail;
			}

			retry = 1;
		}
	}

	evbuffer_drain(ldap->buf, ldap->ber.br_rptr - ldap->ber.br_rbuf);

	LDAP_DEBUG("message", m->msg);

	if (ober_scanf_elements(m->msg, "{ite", &msgid, &class, &type, &a) != 0)
		goto parsefail;
	m->msgid = msgid;
	m->message_type = type;
	m->protocol_op = a;

	switch (m->message_type) {
	case LDAP_RES_BIND:
	case LDAP_RES_MODIFY:
	case LDAP_RES_ADD:
	case LDAP_RES_DELETE:
	case LDAP_RES_MODRDN:
	case LDAP_RES_COMPARE:
	case LDAP_RES_SEARCH_RESULT:
		if (ober_scanf_elements(m->protocol_op, "{EeSe",
		    &m->body.res.rescode, &m->dn, &m->body.res.diagmsg) != 0)
			goto parsefail;
		if (m->body.res.rescode == LDAP_REFERRAL) {
			a = m->body.res.diagmsg->be_next;
			if (ober_scanf_elements(a, "{e", &m->references) != 0)
				goto parsefail;
		}
		if (m->msg->be_sub) {
			for (ep = m->msg->be_sub; ep != NULL; ep = ep->be_next) {
				ober_scanf_elements(ep, "t", &class, &type);
				if (class == 2 && type == 0)
					m->page = aldap_parse_page_control(ep->be_sub->be_sub,
					    ep->be_sub->be_sub->be_len);
			}
		} else
			m->page = NULL;
		break;
	case LDAP_RES_SEARCH_ENTRY:
		if (ober_scanf_elements(m->protocol_op, "{eS{e", &m->dn,
		    &m->body.search.attrs) != 0)
			goto parsefail;
		break;
	case LDAP_RES_SEARCH_REFERENCE:
		if (ober_scanf_elements(m->protocol_op, "{e", &m->references) != 0)
			goto parsefail;
		break;
	case LDAP_RES_EXTENDED:
		if (ober_scanf_elements(m->protocol_op, "{E",
		    &m->body.res.rescode) != 0) {
			goto parsefail;
		}
		break;
	}

	return m;

parsefail:
	evbuffer_drain(ldap->buf, EVBUFFER_LENGTH(ldap->buf));
	ldap->err = ALDAP_ERR_PARSER_ERROR;
	aldap_freemsg(m);
	return NULL;
opfail:
	evbuffer_drain(ldap->buf, EVBUFFER_LENGTH(ldap->buf));
	ldap->err = ALDAP_ERR_OPERATION_FAILED;
	aldap_freemsg(m);
	return NULL;
enomem:
	ldap->err = ALDAP_ERR_NOMEM;
	aldap_freemsg(m);
	return NULL;
epollin:
	ldap->err = ALDAP_ERR_NEED_POLLIN;
	aldap_freemsg(m);
	return NULL;
epollout:
	ldap->err = ALDAP_ERR_NEED_POLLOUT;
	aldap_freemsg(m);
	return NULL;
}

struct aldap_page_control *
aldap_parse_page_control(struct ber_element *control, size_t len) 
{
	char *oid, *s;
	char *encoded;
	struct ber b;
	struct ber_element *elm;
	struct aldap_page_control *page;

	b.br_wbuf = NULL;
	ober_scanf_elements(control, "ss", &oid, &encoded);
	ober_set_readbuf(&b, encoded, control->be_next->be_len);
	elm = ober_read_elements(&b, NULL);

	if ((page = malloc(sizeof(struct aldap_page_control))) == NULL) {
		if (elm != NULL)
			ober_free_elements(elm);
		ober_free(&b);
		return NULL;
	}

	ober_scanf_elements(elm->be_sub, "is", &page->size, &s);
	page->cookie_len = elm->be_sub->be_next->be_len;

	if ((page->cookie = malloc(page->cookie_len)) == NULL) {
		if (elm != NULL)
			ober_free_elements(elm);
		ober_free(&b);
		free(page);
		return NULL;
	}
	memcpy(page->cookie, s, page->cookie_len);

	ober_free_elements(elm);
	ober_free(&b);
	return page;
}

void
aldap_freepage(struct aldap_page_control *page)
{
	if (!page)
		return;
	free(page->cookie);
	free(page);
}

void
aldap_freemsg(struct aldap_message *msg)
{
	if (msg && msg->msg)
		ober_free_elements(msg->msg);
	free(msg);
}

int
aldap_get_resultcode(struct aldap_message *msg)
{
	return msg->body.res.rescode;
}

char *
aldap_get_dn(struct aldap_message *msg)
{
	char *dn;

	if (msg->dn == NULL)
		return NULL;

	if (ober_get_string(msg->dn, &dn) == -1)
		return NULL;

	return utoa(dn);
}

struct aldap_stringset *
aldap_get_references(struct aldap_message *msg)
{
	if (msg->references == NULL)
		return NULL;
	return aldap_get_stringset(msg->references);
}

void
aldap_free_references(char **values)
{
	int i;

	if (values == NULL)
		return;

	for (i = 0; values[i] != NULL; i++)
		free(values[i]);

	free(values);
}

char *
aldap_get_diagmsg(struct aldap_message *msg)
{
	char *s;

	if (msg->body.res.diagmsg == NULL)
		return NULL;

	if (ober_get_string(msg->body.res.diagmsg, &s) == -1)
		return NULL;

	return utoa(s);
}

int
aldap_count_attrs(struct aldap_message *msg)
{
	int i;
	struct ber_element *a;

	if (msg->body.search.attrs == NULL)
		return (-1);

	for (i = 0, a = msg->body.search.attrs;
	    a != NULL && ober_get_eoc(a) != 0;
	    i++, a = a->be_next)
		;

	return i;
}

int
aldap_first_attr(struct aldap_message *msg, char **outkey,
    struct aldap_stringset **outvalues)
{
	struct ber_element *b;
	char *key;
	struct aldap_stringset *ret;

	if (msg->body.search.attrs == NULL)
		goto fail;

	if (ober_scanf_elements(msg->body.search.attrs, "{s(e)}",
	    &key, &b) != 0)
		goto fail;

	msg->body.search.iter = msg->body.search.attrs->be_next;

	if ((ret = aldap_get_stringset(b)) == NULL)
		goto fail;

	(*outvalues) = ret;
	(*outkey) = utoa(key);

	return (1);
fail:
	(*outkey) = NULL;
	(*outvalues) = NULL;
	return (-1);
}

int
aldap_next_attr(struct aldap_message *msg, char **outkey,
    struct aldap_stringset **outvalues)
{
	struct ber_element *a;
	char *key;
	struct aldap_stringset *ret;

	if (msg->body.search.iter == NULL)
		goto notfound;

	LDAP_DEBUG("attr", msg->body.search.iter);

	if (ober_get_eoc(msg->body.search.iter) == 0)
		goto notfound;

	if (ober_scanf_elements(msg->body.search.iter, "{s(e)}", &key, &a) != 0)
		goto fail;

	msg->body.search.iter = msg->body.search.iter->be_next;

	if ((ret = aldap_get_stringset(a)) == NULL)
		goto fail;

	(*outvalues) = ret;
	(*outkey) = utoa(key);

	return (1);
fail:
notfound:
	(*outkey) = NULL;
	(*outvalues) = NULL;
	return (-1);
}

int
aldap_match_attr(const struct aldap_message *msg, char *inkey,
    struct aldap_stringset **outvalues)
{
	struct ber_element *a, *b;
	char *descr = NULL;
	struct aldap_stringset *ret;

	if (msg->body.search.attrs == NULL)
		goto fail;

	LDAP_DEBUG("attr", msg->body.search.attrs);

	for (a = msg->body.search.attrs;;) {
		if (a == NULL)
			goto notfound;
		if (ober_get_eoc(a) == 0)
			goto notfound;
		if (ober_scanf_elements(a, "{s(e", &descr, &b) != 0)
			goto fail;
		if (strcasecmp(descr, inkey) == 0)
			goto attrfound;
		a = a->be_next;
	}

attrfound:
	if ((ret = aldap_get_stringset(b)) == NULL)
		goto fail;

	(*outvalues) = ret;

	return (1);
fail:
notfound:
	(*outvalues) = NULL;
	return (-1);
}

void
aldap_free_attr(struct aldap_stringset *values)
{
	if (values == NULL)
		return;

	free(values->str);
	free(values);

	return;
}

void
aldap_free_url(struct aldap_url *lu)
{
	if (!lu)
		return;
	free(lu->buffer);
}

int
aldap_parse_url(const char *url, struct aldap_url *lu)
{
	char		*p, *forward, *forward2;
	const char	*errstr = NULL;
	int		 i;

	if ((lu->buffer = p = strdup(url)) == NULL)
		return (-1);

	/* protocol */
	if (strncasecmp(LDAP_URL, p, strlen(LDAP_URL)) == 0) {
		lu->protocol = LDAP;
		p += strlen(LDAP_URL);
	} else if (strncasecmp(LDAPS_URL, p, strlen(LDAPS_URL)) == 0) {
		lu->protocol = LDAPS;
		p += strlen(LDAPS_URL);
	} else if (strncasecmp(LDAPTLS_URL, p, strlen(LDAPTLS_URL)) == 0) {
		lu->protocol = LDAPTLS;
		p += strlen(LDAPTLS_URL);
	} else if (strncasecmp(LDAPI_URL, p, strlen(LDAPI_URL)) == 0) {
		lu->protocol = LDAPI;
		p += strlen(LDAPI_URL);
	} else
		lu->protocol = -1;

	/* host and optional port */
	if ((forward = strchr(p, '/')) != NULL)
		*forward = '\0';
	/* find the optional port */
	if ((forward2 = strchr(p, ':')) != NULL) {
		*forward2 = '\0';
		/* if a port is given */
		if (*(forward2+1) != '\0') {
#define PORT_MAX UINT16_MAX
			strtonum(++forward2, 0, PORT_MAX, &errstr);
			if (errstr)
				goto fail;
			lu->port = forward2;
		}
	} else {
		lu->port = LDAP_PORT;
		if (lu->protocol == LDAPS)
			lu->port = LDAPS_PORT;
	}
	/* fail if no host is given */
	if (strlen(p) == 0)
		goto fail;
	lu->host = p;
	if (forward == NULL)
		goto done;
	/* p is assigned either a pointer to a character or to '\0' */
	p = ++forward;
	if (strlen(p) == 0)
		goto done;

	/* dn */
	if ((forward = strchr(p, '?')) != NULL)
		*forward = '\0';
	lu->dn = p;
	if (forward == NULL)
		goto done;
	/* p is assigned either a pointer to a character or to '\0' */
	p = ++forward;
	if (strlen(p) == 0)
		goto done;

	/* attributes */
	if ((forward = strchr(p, '?')) != NULL)
		*forward = '\0';
	for (i = 0; i < MAXATTR; i++) {
		if ((forward2 = strchr(p, ',')) == NULL) {
			if (strlen(p) == 0)
				break;
			lu->attributes[i] = p;
			break;
		}
		*forward2 = '\0';
		lu->attributes[i] = p;
		p = ++forward2;
	}
	if (forward == NULL)
		goto done;
	/* p is assigned either a pointer to a character or to '\0' */
	p = ++forward;
	if (strlen(p) == 0)
		goto done;

	/* scope */
	if ((forward = strchr(p, '?')) != NULL)
		*forward = '\0';
	if (strcmp(p, "base") == 0)
		lu->scope = LDAP_SCOPE_BASE;
	else if (strcmp(p, "one") == 0)
		lu->scope = LDAP_SCOPE_ONELEVEL;
	else if (strcmp(p, "sub") == 0)
		lu->scope = LDAP_SCOPE_SUBTREE;
	else
		goto fail;
	if (forward == NULL)
		goto done;
	p = ++forward;
	if (strlen(p) == 0)
		goto done;

	/* filter */
	if (p)
		lu->filter = p;
done:
	return (1);
fail:
	free(lu->buffer);
	lu->buffer = NULL;
	return (-1);
}

int
aldap_search_url(struct aldap *ldap, char *url, int typesonly, int sizelimit,
    int timelimit, struct aldap_page_control *page)
{
	struct aldap_url *lu;

	if ((lu = calloc(1, sizeof(*lu))) == NULL)
		return (-1);

	if (aldap_parse_url(url, lu))
		goto fail;

	if (aldap_search(ldap, lu->dn, lu->scope, lu->filter, NULL,
            lu->attributes, typesonly, sizelimit, timelimit, page) == -1)
		goto fail;

	aldap_free_url(lu);
	return (ldap->msgid);
fail:
	aldap_free_url(lu);
	return (-1);
}

/*
 * internal functions
 */

struct aldap_stringset *
aldap_get_stringset(struct ber_element *elm)
{
	struct ber_element *a;
	int i;
	struct aldap_stringset *ret;

	if (elm->be_type != BER_TYPE_OCTETSTRING)
		return NULL;

	if ((ret = malloc(sizeof(*ret))) == NULL)
		return NULL;
	for (a = elm, ret->len = 0; a != NULL && a->be_type ==
	    BER_TYPE_OCTETSTRING; a = a->be_next, ret->len++)
		;
	if (ret->len == 0) {
		free(ret);
		return NULL;
	}

	if ((ret->str = reallocarray(NULL, ret->len,
	    sizeof(*(ret->str)))) == NULL) {
		free(ret);
		return NULL;
	}

	for (a = elm, i = 0; a != NULL && a->be_type == BER_TYPE_OCTETSTRING;
	    a = a->be_next, i++)
		(void) ober_get_ostring(a, &(ret->str[i]));

	return ret;
}

/*
 * Base case for ldap_do_parse_search_filter
 *
 * returns:
 *	struct ber_element *, ber_element tree
 *	NULL, parse failed
 */
static struct ber_element *
ldap_parse_search_filter(struct ber_element *ber, const char *filter, const struct aldap_filter_ctx *ctx)
{
	struct ber_element *elm;
	const char *cp;

	cp = filter;

	if (cp == NULL || *cp == '\0') {
		errno = EINVAL;
		return (NULL);
	}

	if ((elm = ldap_do_parse_search_filter(ber, &cp, ctx)) == NULL)
		return (NULL);

	if (*cp != '\0') {
		ober_free_elements(elm);
		ober_link_elements(ber, NULL);
		errno = EINVAL;
		return (NULL);
	}

	return (elm);
}

/*
 * Translate RFC4515 search filter string into ber_element tree
 *
 * returns:
 *	struct ber_element *, ber_element tree
 *	NULL, parse failed
 *
 * notes:
 *	when cp is passed to a recursive invocation, it is updated
 *	    to point one character beyond the filter that was passed
 *	    i.e., cp jumps to "(filter)" upon return
 *	                               ^
 *	goto's used to discriminate error-handling based on error type
 *	doesn't handle extended filters (yet)
 *
 */
static struct ber_element *
ldap_do_parse_search_filter(struct ber_element *prev, const char **cpp, const struct aldap_filter_ctx *ctx)
{
	struct ber_element *elm, *root = NULL;
	char *parsed_val;
	const char *attr_desc, *attr_val, *cp;
	size_t len;
	unsigned long type;

	root = NULL;

	/* cpp should pass in pointer to opening parenthesis of "(filter)" */
	cp = *cpp;
	if (*cp != '(')
		goto syntaxfail;

	switch (*++cp) {
	case '&':		/* AND */
	case '|':		/* OR */
		if (*cp == '&')
			type = LDAP_FILT_AND;
		else
			type = LDAP_FILT_OR;

		if ((elm = ober_add_set(prev)) == NULL)
			goto callfail;
		root = elm;
		ober_set_header(elm, BER_CLASS_CONTEXT, type);

		if (*++cp != '(')		/* opening `(` of filter */
			goto syntaxfail;

		while (*cp == '(') {
			if ((elm =
			    ldap_do_parse_search_filter(elm, &cp, ctx)) == NULL)
				goto bad;
		}

		if (*cp != ')')			/* trailing `)` of filter */
			goto syntaxfail;
		break;

	case '!':		/* NOT */
		if ((root = ober_add_sequence(prev)) == NULL)
			goto callfail;
		ober_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_NOT);

		cp++;				/* now points to sub-filter */
		if ((elm = ldap_do_parse_search_filter(root, &cp, ctx)) == NULL)
			goto bad;

		if (*cp != ')')			/* trailing `)` of filter */
			goto syntaxfail;
		break;

	default:	/* SIMPLE || PRESENCE */
		attr_desc = cp;

		len = strcspn(cp, "()<>~=");
		cp += len;
		switch (*cp) {
		case '~':
			type = LDAP_FILT_APPR;
			cp++;
			break;
		case '<':
			type = LDAP_FILT_LE;
			cp++;
			break;
		case '>':
			type = LDAP_FILT_GE;
			cp++;
			break;
		case '=':
			type = LDAP_FILT_EQ;	/* assume EQ until disproven */
			break;
		case '(':
		case ')':
		default:
			goto syntaxfail;
		}
		attr_val = ++cp;

		/* presence filter */
		if (strncmp(attr_val, "*)", 2) == 0) {
			cp++;			/* point to trailing `)` */
			if ((root =
			    ober_add_nstring(prev, attr_desc, len)) == NULL)
				goto bad;

			ober_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_PRES);
			break;
		}

		if ((root = ober_add_sequence(prev)) == NULL)
			goto callfail;
		ober_set_header(root, BER_CLASS_CONTEXT, type);

		if ((elm = ober_add_nstring(root, attr_desc, len)) == NULL)
			goto callfail;

		len = strcspn(attr_val, "*)");
		if (len == 0 && *cp != '*')
			goto syntaxfail;
		cp += len;
		if (*cp == '\0')
			goto syntaxfail;

		if (*cp == '*') {	/* substring filter */
			int initial;

			cp = attr_val;

			ober_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_SUBS);

			if ((elm = ober_add_sequence(elm)) == NULL)
				goto callfail;

			for (initial = 1;; cp++, initial = 0) {
				attr_val = cp;

				len = strcspn(attr_val, "*)");
				if (len == 0) {
					if (*cp == ')')
						break;
					else
						continue;
				}
				cp += len;
				if (*cp == '\0')
					goto syntaxfail;

				if (initial)
					type = LDAP_FILT_SUBS_INIT;
				else if (*cp == ')')
					type = LDAP_FILT_SUBS_FIN;
				else
					type = LDAP_FILT_SUBS_ANY;

				if ((parsed_val = parseval(attr_val, len, ctx)) ==
				    NULL)
					goto callfail;
				elm = ober_add_nstring(elm, parsed_val,
				    strlen(parsed_val));
				free(parsed_val);
				if (elm == NULL)
					goto callfail;
				ober_set_header(elm, BER_CLASS_CONTEXT, type);
				if (type == LDAP_FILT_SUBS_FIN)
					break;
			}
			break;
		}

		if ((parsed_val = parseval(attr_val, len, ctx)) == NULL)
			goto callfail;
		elm = ober_add_nstring(elm, parsed_val, strlen(parsed_val));
		free(parsed_val);
		if (elm == NULL)
			goto callfail;
		break;
	}

	cp++;		/* now points one char beyond the trailing `)` */

	*cpp = cp;
	return (root);

syntaxfail:		/* XXX -- error reporting */
callfail:
bad:
	if (root != NULL)
		ober_free_elements(root);
	ober_link_elements(prev, NULL);
	return (NULL);
}

#ifdef DEBUG
/*
 * Display a list of ber elements.
 *
 */
void
ldap_debug_elements(struct ber_element *root)
{
	static int	 indent = 0;
	long long	 v;
	int		 d;
	char		*buf;
	size_t		 len;
	u_int		 i;
	int		 constructed;
	struct ber_oid	 o;

	/* calculate lengths */
	ober_calc_len(root);

	switch (root->be_encoding) {
	case BER_TYPE_SEQUENCE:
	case BER_TYPE_SET:
		constructed = root->be_encoding;
		break;
	default:
		constructed = 0;
		break;
	}

	fprintf(stderr, "%*slen %lu ", indent, "", root->be_len);
	switch (root->be_class) {
	case BER_CLASS_UNIVERSAL:
		fprintf(stderr, "class: universal(%u) type: ", root->be_class);
		switch (root->be_type) {
		case BER_TYPE_EOC:
			fprintf(stderr, "end-of-content");
			break;
		case BER_TYPE_BOOLEAN:
			fprintf(stderr, "boolean");
			break;
		case BER_TYPE_INTEGER:
			fprintf(stderr, "integer");
			break;
		case BER_TYPE_BITSTRING:
			fprintf(stderr, "bit-string");
			break;
		case BER_TYPE_OCTETSTRING:
			fprintf(stderr, "octet-string");
			break;
		case BER_TYPE_NULL:
			fprintf(stderr, "null");
			break;
		case BER_TYPE_OBJECT:
			fprintf(stderr, "object");
			break;
		case BER_TYPE_ENUMERATED:
			fprintf(stderr, "enumerated");
			break;
		case BER_TYPE_SEQUENCE:
			fprintf(stderr, "sequence");
			break;
		case BER_TYPE_SET:
			fprintf(stderr, "set");
			break;
		}
		break;
	case BER_CLASS_APPLICATION:
		fprintf(stderr, "class: application(%u) type: ",
		    root->be_class);
		switch (root->be_type) {
		case LDAP_REQ_BIND:
			fprintf(stderr, "bind");
			break;
		case LDAP_RES_BIND:
			fprintf(stderr, "bind");
			break;
		case LDAP_REQ_UNBIND_30:
			break;
		case LDAP_REQ_SEARCH:
			fprintf(stderr, "search");
			break;
		case LDAP_RES_SEARCH_ENTRY:
			fprintf(stderr, "search_entry");
			break;
		case LDAP_RES_SEARCH_RESULT:
			fprintf(stderr, "search_result");
			break;
		case LDAP_REQ_MODIFY:
			fprintf(stderr, "modify");
			break;
		case LDAP_RES_MODIFY:
			fprintf(stderr, "modify");
			break;
		case LDAP_REQ_ADD:
			fprintf(stderr, "add");
			break;
		case LDAP_RES_ADD:
			fprintf(stderr, "add");
			break;
		case LDAP_REQ_DELETE_30:
			fprintf(stderr, "delete");
			break;
		case LDAP_RES_DELETE:
			fprintf(stderr, "delete");
			break;
		case LDAP_REQ_MODRDN:
			fprintf(stderr, "modrdn");
			break;
		case LDAP_RES_MODRDN:
			fprintf(stderr, "modrdn");
			break;
		case LDAP_REQ_COMPARE:
			fprintf(stderr, "compare");
			break;
		case LDAP_RES_COMPARE:
			fprintf(stderr, "compare");
			break;
		case LDAP_REQ_ABANDON_30:
			fprintf(stderr, "abandon");
			break;
		}
		break;
	case BER_CLASS_PRIVATE:
		fprintf(stderr, "class: private(%u) type: ", root->be_class);
		fprintf(stderr, "encoding (%u) type: ", root->be_encoding);
		break;
	case BER_CLASS_CONTEXT:
		/* XXX: this is not correct */
		fprintf(stderr, "class: context(%u) type: ", root->be_class);
		switch(root->be_type) {
		case LDAP_AUTH_SIMPLE:
			fprintf(stderr, "auth simple");
			break;
		}
		break;
	default:
		fprintf(stderr, "class: <INVALID>(%u) type: ", root->be_class);
		break;
	}
	fprintf(stderr, "(%u) encoding %u ",
	    root->be_type, root->be_encoding);

	if (constructed)
		root->be_encoding = constructed;

	switch (root->be_encoding) {
	case BER_TYPE_BOOLEAN:
		if (ober_get_boolean(root, &d) == -1) {
			fprintf(stderr, "<INVALID>\n");
			break;
		}
		fprintf(stderr, "%s(%d)\n", d ? "true" : "false", d);
		break;
	case BER_TYPE_INTEGER:
		if (ober_get_integer(root, &v) == -1) {
			fprintf(stderr, "<INVALID>\n");
			break;
		}
		fprintf(stderr, "value %lld\n", v);
		break;
	case BER_TYPE_ENUMERATED:
		if (ober_get_enumerated(root, &v) == -1) {
			fprintf(stderr, "<INVALID>\n");
			break;
		}
		fprintf(stderr, "value %lld\n", v);
		break;
	case BER_TYPE_BITSTRING:
		if (ober_get_bitstring(root, (void *)&buf, &len) == -1) {
			fprintf(stderr, "<INVALID>\n");
			break;
		}
		fprintf(stderr, "hexdump ");
		for (i = 0; i < len; i++)
			fprintf(stderr, "%02x", buf[i]);
		fprintf(stderr, "\n");
		break;
	case BER_TYPE_OBJECT:
		if (ober_get_oid(root, &o) == -1) {
			fprintf(stderr, "<INVALID>\n");
			break;
		}
		fprintf(stderr, "\n");
		break;
	case BER_TYPE_OCTETSTRING:
		if (ober_get_nstring(root, (void *)&buf, &len) == -1) {
			fprintf(stderr, "<INVALID>\n");
			break;
		}
		fprintf(stderr, "string \"%.*s\"\n",  (int)len, buf);
		break;
	case BER_TYPE_NULL:	/* no payload */
	case BER_TYPE_EOC:
	case BER_TYPE_SEQUENCE:
	case BER_TYPE_SET:
	default:
		fprintf(stderr, "\n");
		break;
	}

	if (constructed && root->be_sub) {
		indent += 2;
		ldap_debug_elements(root->be_sub);
		indent -= 2;
	}
	if (root->be_next)
		ldap_debug_elements(root->be_next);
}
#endif

/*
 * Strip UTF-8 down to ASCII without validation.
 * notes:
 *	non-ASCII characters are displayed as '?'
 *	the argument u should be a NULL terminated sequence of UTF-8 bytes.
 */
char *
utoa(char *u)
{
	int	 len, i, j;
	char	*str;

	/* calculate the length to allocate */
	for (len = 0, i = 0; u[i] != '\0'; i++)
		if (!isu8cont(u[i]))
			len++;

	if ((str = calloc(len + 1, sizeof(char))) == NULL)
		return NULL;

	/* copy the ASCII characters to the newly allocated string */
	for (i = 0, j = 0; u[i] != '\0'; i++)
		if (!isu8cont(u[i]))
			str[j++] = isascii((unsigned char)u[i]) ? u[i] : '?';

	return str;
}

static int
isu8cont(unsigned char c)
{
	return (c & (0x80 | 0x40)) == 0x80;
}

static ssize_t
attach_param(char **buffer, size_t *size, size_t pos, const char *param)
{
	char *newbuffer;
	size_t len, newsize;

	if (!param) {
		return 0;
	}

	len = strlen(param);
	if (!len) {
		return 0;
	}

	newsize = *size + len;
	newbuffer = realloc(*buffer, newsize);
	if (!newbuffer) {
		return -1;
	}
	memcpy(newbuffer + pos, param, len);
	*buffer = newbuffer;
	*size = newsize;
	return len;
}

/*
 * Parse a LDAP value
 * notes:
 *	the argument p should be a NUL-terminated sequence of ASCII bytes
 */
static char *
parseval(const char *p, size_t len, const struct aldap_filter_ctx *ctx)
{
	char	 hex[3];
	char	*buffer;
	size_t	 i, j, size;
	ssize_t	 paramlen;
	size = len + 1;

	if ((buffer = calloc(1, size)) == NULL)
		return NULL;

	for (i = j = 0; j < len; i++) {
		if (p[j] == '\\') {
			strlcpy(hex, p + j + 1, sizeof(hex));
			buffer[i] = (char)strtoumax(hex, NULL, 16);
			j += 3;
		} else if (p[j] == '%') {
			switch (p[j + 1]) {
			case '%':
				buffer[i] = '%';
				j += 2;
				break;
			case 's': /* Backwards compatibility */
			case 'u': /* username */
				if (ctx) {
					paramlen = attach_param(&buffer, &size, i, ctx->username);
				} else {
					paramlen = 0;
				}

				if (paramlen == -1) {
					free(buffer);
					return NULL;
				}

				/* Important
				 * the loop increases i (the position on the output) on each iteration
				 * but this might insert nothing
				 * so it needs to decrease i by 1 to be on the last written position
				 */
				i += paramlen - 1;
				j += 2;
				break;
			case 'h': /* hostname */
				if (ctx) {
					paramlen = attach_param(&buffer, &size, i, ctx->hostname);
				} else {
					paramlen = 0;
				}

				if (paramlen == -1) {
					free(buffer);
					return NULL;
				}

				/* Important
				 * the loop increases i (the position on the output) on each iteration
				 * but this might insert nothing
				 * so it needs to decrease i by 1 to be on the last written position
				 */
				i += paramlen - 1;
				j += 2;
				break;
			default:
				free(buffer);
				return NULL;
			}
		} else {
			buffer[i] = p[j];
			j++;
		}
	}

	buffer[i] = 0;
	return buffer;
}

int
aldap_get_errno(struct aldap *a, const char **estr)
{
	switch (a->err) {
	case ALDAP_ERR_SUCCESS:
		*estr = "success";
		break;
	case ALDAP_ERR_PARSER_ERROR:
		*estr = "parser failed";
		break;
	case ALDAP_ERR_INVALID_FILTER:
		*estr = "invalid filter";
		break;
	case ALDAP_ERR_OPERATION_FAILED:
		*estr = "operation failed";
		break;
	case ALDAP_ERR_TLS_ERROR:
		*estr = tls_error(a->tls);
		break;
	default:
		*estr = "unknown";
		break;
	}
	return (a->err);
}
/*	$OpenBSD: aldap.h,v 1.4 2019/05/11 17:46:02 rob Exp $ */

/*
 * Copyright (c) 2008 Alexander Schrijver <aschrijver@openbsd.org>
 * Copyright (c) 2006, 2007 Marc Balmer <mbalmer@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#define LDAP_URL 		"ldap://"
#define LDAPS_URL 		"ldaps://"
#define LDAPTLS_URL 		"ldap+tls://"
#define LDAPI_URL 		"ldapi://"

#define LDAP_PORT 		"389"
#define LDAPS_PORT 		"636"
#define LDAP_PAGED_OID		"1.2.840.113556.1.4.319"
#define LDAP_STARTTLS_OID	"1.3.6.1.4.1.1466.20037"

struct aldap {
#define ALDAP_ERR_SUCCESS		0
#define ALDAP_ERR_PARSER_ERROR		1
#define ALDAP_ERR_INVALID_FILTER	2
#define ALDAP_ERR_OPERATION_FAILED	3
#define ALDAP_ERR_TLS_ERROR		4
#define ALDAP_ERR_NEED_POLLOUT		5
#define ALDAP_ERR_NEED_POLLIN		6
#define ALDAP_ERR_NOMEM			7
	uint8_t	err;
	int		msgid;
	struct ber	ber;

	int		fd;
	struct tls	*tls;

	struct evbuffer *buf;
};

struct aldap_page_control {
	int size;
	char *cookie;
	unsigned int cookie_len;
};

struct aldap_message {
	int msgid;
	int message_type;

	struct ber_element	*msg;

	struct ber_element	*header;
	struct ber_element	*protocol_op;

	struct ber_element	*dn;

	union {
		struct {
			long long		 rescode;
			struct ber_element	*diagmsg;
		}			 res;
		struct {
			struct ber_element	*iter;
			struct ber_element	*attrs;
		}			 search;
	} body;
	struct ber_element	*references;
	struct aldap_page_control *page; 
};

enum aldap_protocol {
	LDAP,
	LDAPS,
	LDAPTLS,
	LDAPI
};

struct aldap_stringset {
	size_t			 len;
	struct ber_octetstring	*str;
};

struct aldap_filter_ctx {
	char *username;
	char *hostname;
};

struct aldap_url {
	int		 protocol;
	char		*host;
	char	 	*port;
	char		*dn;
#define MAXATTR 1024
	char		*attributes[MAXATTR];
	int		 scope;
	char		*filter;
	char		*buffer;
};

enum protocol_op {
	LDAP_REQ_BIND		= 0,
	LDAP_RES_BIND		= 1,
	LDAP_REQ_UNBIND_30	= 2,
	LDAP_REQ_SEARCH		= 3,
	LDAP_RES_SEARCH_ENTRY	= 4,
	LDAP_RES_SEARCH_RESULT	= 5,
	LDAP_REQ_MODIFY		= 6,
	LDAP_RES_MODIFY		= 7,
	LDAP_REQ_ADD		= 8,
	LDAP_RES_ADD		= 9,
	LDAP_REQ_DELETE_30	= 10,
	LDAP_RES_DELETE		= 11,
	LDAP_REQ_MODRDN		= 12,
	LDAP_RES_MODRDN		= 13,
	LDAP_REQ_COMPARE	= 14,
	LDAP_RES_COMPARE	= 15,
	LDAP_REQ_ABANDON_30	= 16,

	LDAP_RES_SEARCH_REFERENCE = 19,

	LDAP_REQ_EXTENDED	= 23,
	LDAP_RES_EXTENDED	= 24
};

enum deref_aliases {
	LDAP_DEREF_NEVER	= 0,
	LDAP_DEREF_SEARCHING	= 1,
	LDAP_DEREF_FINDING	= 2,
	LDAP_DEREF_ALWAYS	= 3,
};

enum authentication_choice {
	LDAP_AUTH_SIMPLE	= 0,
};

enum scope {
	LDAP_SCOPE_BASE		= 0,
	LDAP_SCOPE_ONELEVEL	= 1,
	LDAP_SCOPE_SUBTREE	= 2,
};

enum result_code {
	LDAP_SUCCESS				= 0,
	LDAP_OPERATIONS_ERROR			= 1,
	LDAP_PROTOCOL_ERROR			= 2,
	LDAP_TIMELIMIT_EXCEEDED			= 3,
	LDAP_SIZELIMIT_EXCEEDED			= 4,
	LDAP_COMPARE_FALSE			= 5,
	LDAP_COMPARE_TRUE			= 6,
	LDAP_STRONG_AUTH_NOT_SUPPORTED		= 7,
	LDAP_STRONG_AUTH_REQUIRED		= 8,

	LDAP_REFERRAL				= 10,
	LDAP_ADMINLIMIT_EXCEEDED		= 11,
	LDAP_UNAVAILABLE_CRITICAL_EXTENSION	= 12,
	LDAP_CONFIDENTIALITY_REQUIRED		= 13,
	LDAP_SASL_BIND_IN_PROGRESS		= 14,
	LDAP_NO_SUCH_ATTRIBUTE			= 16,
	LDAP_UNDEFINED_TYPE			= 17,
	LDAP_INAPPROPRIATE_MATCHING		= 18,
	LDAP_CONSTRAINT_VIOLATION		= 19,
	LDAP_TYPE_OR_VALUE_EXISTS		= 20,
	LDAP_INVALID_SYNTAX			= 21,

	LDAP_NO_SUCH_OBJECT			= 32,
	LDAP_ALIAS_PROBLEM			= 33,
	LDAP_INVALID_DN_SYNTAX			= 34,

	LDAP_ALIAS_DEREF_PROBLEM		= 36,

	LDAP_INAPPROPRIATE_AUTH			= 48,
	LDAP_INVALID_CREDENTIALS		= 49,
	LDAP_INSUFFICIENT_ACCESS		= 50,
	LDAP_BUSY				= 51,
	LDAP_UNAVAILABLE			= 52,
	LDAP_UNWILLING_TO_PERFORM		= 53,
	LDAP_LOOP_DETECT			= 54,

	LDAP_NAMING_VIOLATION			= 64,
	LDAP_OBJECT_CLASS_VIOLATION		= 65,
	LDAP_NOT_ALLOWED_ON_NONLEAF		= 66,
	LDAP_NOT_ALLOWED_ON_RDN			= 67,
	LDAP_ALREADY_EXISTS			= 68,
	LDAP_NO_OBJECT_CLASS_MODS		= 69,

	LDAP_AFFECTS_MULTIPLE_DSAS		= 71,

	LDAP_OTHER				= 80,
};

enum filter {
	LDAP_FILT_AND		= 0,
	LDAP_FILT_OR		= 1,
	LDAP_FILT_NOT		= 2,
	LDAP_FILT_EQ		= 3,
	LDAP_FILT_SUBS		= 4,
	LDAP_FILT_GE		= 5,
	LDAP_FILT_LE		= 6,
	LDAP_FILT_PRES		= 7,
	LDAP_FILT_APPR		= 8,
};

enum subfilter {
	LDAP_FILT_SUBS_INIT	= 0,
	LDAP_FILT_SUBS_ANY	= 1,
	LDAP_FILT_SUBS_FIN	= 2,
};

struct aldap		*aldap_init(int);
int			 aldap_tls(struct aldap *, struct tls_config *,
			    const char *);
int			 aldap_close(struct aldap *);
struct aldap_message	*aldap_parse(struct aldap *, bool);
void			 aldap_freemsg(struct aldap_message *);

int	 		 aldap_req_starttls(struct aldap *);

int	 aldap_bind(struct aldap *, char *, char *);
int	 aldap_unbind(struct aldap *);
int	 aldap_search(struct aldap *, char *, enum scope, const char *, const struct aldap_filter_ctx *, char * const *, int, int, int, struct aldap_page_control *);
int	 aldap_get_errno(struct aldap *, const char **);

int	 aldap_get_resultcode(struct aldap_message *);
char	*aldap_get_dn(struct aldap_message *);
char	*aldap_get_diagmsg(struct aldap_message *);
struct aldap_stringset	*aldap_get_references(struct aldap_message *);
void	 aldap_free_references(char **values);
int	 aldap_parse_url(const char *, struct aldap_url *);
void	 aldap_free_url(struct aldap_url *);
int	 aldap_search_url(struct aldap *, char *, int, int, int,
	    struct aldap_page_control *);

int	 aldap_count_attrs(struct aldap_message *);
int	 aldap_match_attr(const struct aldap_message *, char *,
	    struct aldap_stringset **);
int	 aldap_first_attr(struct aldap_message *, char **, struct
	    aldap_stringset **);
int	 aldap_next_attr(struct aldap_message *, char **,
	    struct aldap_stringset **);
void	 aldap_free_attr(struct aldap_stringset *);

struct aldap_page_control *aldap_parse_page_control(struct ber_element *, size_t len);
void	 aldap_freepage(struct aldap_page_control *);