Hi

I have worked on this forther. The async ldap table is ready to test.
It's still partialy blocking (write and reconnect blocks), but search
is nonblocking.

I have also started to use the new table_api for the postgres table.
This is not yet finished.

[2024-07-15 12:47] Philipp <[email protected]>
> [2024-07-11 12:00] Omar Polo <[email protected]>
> > Philipp <[email protected]> wrote:
> [...]
> > > > My next step is to change the getline loop to a pool based loop. Then
> > > > add some registration for other fd. This way the backend can be full
> > > > asyncron.
> > [...]
> > I'd leave the choice of the event loop to use to the actual table
> > implementations.  So that table-passwd can not care, and table-ldap can
> > use a poll-based loop or libevent or whatever it wants.  Same for the
> > databases.
>
> > Running a fully asynchronous table on top of this with, say, libevent is
> > just a matter of a few lines of glue code, which is acceptable IMHO.
>
> > Suggestion: I make the handle_request() public and add a result struct.
> > So a table can choose to use the loop or implement it's own.

I have implemented this as I suggested.

> [...]
>
> Seperation between the single request types is because they are used all
> in different ways. Ihmo overloading the result functions to mutch makes
> only the code more complicated. Simple responses function for each response
> type don't have this problem.

Ok I was wrong, I have moved all request callbacks in one.

The current diff is attached. So others can review and test it. I also need
to do some more tests. Also I need to clean up the commit history.

Philipp
diff --git a/Makefile.am b/Makefile.am
index 5572b66..9132a4f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,11 +1,11 @@
 noinst_PROGRAMS =	table-ldap
 
-table_ldap_SOURCES =	table_ldap.c aldap.c ber.c dict.c log.c table_stdio.c util.c
+table_ldap_SOURCES =	table_ldap.c aldap.c ber.c dict.c log.c table_api.c util.c
 
 LDADD =			$(LIBOBJS)
 
 EXTRA_DIST =		aldap.h ber.h compat.h config.h.in \
-			dict.h log.h table_stdio.h util.h
+			dict.h log.h table_api.h util.h
 
 smtpdir =		${prefix}/libexec/smtpd
 
diff --git a/aldap.c b/aldap.c
index 1e86c7a..eec5c23 100644
--- a/aldap.c
+++ b/aldap.c
@@ -20,11 +20,15 @@
 #include "compat.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>
@@ -136,6 +140,7 @@ aldap_send(struct aldap *ldap, struct ber_element *root)
 	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);
@@ -147,16 +152,34 @@ aldap_send(struct aldap *ldap, struct ber_element *root)
 	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);
-			if (wrote == TLS_WANT_POLLIN ||
-			    wrote == TLS_WANT_POLLOUT)
+			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 (wrote == -1) {
+			if (errno == EAGAIN || errno == EWOULDBLOCK) {
+				pfd.events = POLLOUT;
+				continue;
+			}
 			return -1;
+		}
 
 		len -= wrote;
 		done += wrote;
@@ -256,14 +279,16 @@ fail:
 
 int
 aldap_search(struct aldap *ldap, char *basedn, enum scope scope, const char *filter,
-    const char *key, char **attrs, int typesonly, int sizelimit, int timelimit,
+    const char *key, 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)
+	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);
@@ -286,8 +311,10 @@ aldap_search(struct aldap *ldap, char *basedn, enum scope scope, const char *fil
 		goto fail;
 	}
 
-	if ((ber = ober_add_sequence(ber)) == NULL)
+	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)
@@ -352,7 +379,7 @@ fail:
 }
 
 struct aldap_message *
-aldap_parse(struct aldap *ldap)
+aldap_parse(struct aldap *ldap, bool blocking)
 {
 	int			 class;
 	unsigned int		 type;
@@ -361,26 +388,54 @@ aldap_parse(struct aldap *ldap)
 	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)
-		return 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));
-				if (ret == TLS_WANT_POLLIN ||
-				    ret == TLS_WANT_POLLOUT)
-					continue;
+				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 parsefail;
+				goto opfail;
 			}
 
-			evbuffer_add(ldap->buf, rbuf, ret);
+			if (evbuffer_add(ldap->buf, rbuf, ret) == -1)
+				goto enomem;
 		}
 
 		if (EVBUFFER_LENGTH(ldap->buf) > 0) {
@@ -450,11 +505,29 @@ aldap_parse(struct aldap *ldap)
 	}
 
 	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 *
@@ -498,6 +571,8 @@ aldap_parse_page_control(struct ber_element *control, size_t len)
 void
 aldap_freepage(struct aldap_page_control *page)
 {
+	if (!page)
+		return;
 	free(page->cookie);
 	free(page);
 }
@@ -505,7 +580,7 @@ aldap_freepage(struct aldap_page_control *page)
 void
 aldap_freemsg(struct aldap_message *msg)
 {
-	if (msg->msg)
+	if (msg && msg->msg)
 		ober_free_elements(msg->msg);
 	free(msg);
 }
@@ -649,7 +724,7 @@ notfound:
 }
 
 int
-aldap_match_attr(struct aldap_message *msg, char *inkey,
+aldap_match_attr(const struct aldap_message *msg, char *inkey,
     struct aldap_stringset **outvalues)
 {
 	struct ber_element *a, *b;
@@ -686,21 +761,23 @@ notfound:
 	return (-1);
 }
 
-int
+void
 aldap_free_attr(struct aldap_stringset *values)
 {
 	if (values == NULL)
-		return -1;
+		return;
 
 	free(values->str);
 	free(values);
 
-	return (1);
+	return;
 }
 
 void
 aldap_free_url(struct aldap_url *lu)
 {
+	if (!lu)
+		return;
 	free(lu->buffer);
 }
 
diff --git a/aldap.h b/aldap.h
index 9bbc35a..9a23f29 100644
--- a/aldap.h
+++ b/aldap.h
@@ -17,10 +17,6 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <ber.h>
-#include <stdio.h>
-#include <tls.h>
-
 #define LDAP_URL 		"ldap://";
 #define LDAPS_URL 		"ldaps://"
 #define LDAPTLS_URL 		"ldap+tls://"
@@ -37,6 +33,9 @@ struct aldap {
 #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
 	u_int8_t	err;
 	int		msgid;
 	struct ber	ber;
@@ -215,14 +214,14 @@ 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 *);
+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 char *, char **, int, int, int, struct aldap_page_control *);
+int	 aldap_search(struct aldap *, char *, enum scope, const char *, const char *, char * const *, int, int, int, struct aldap_page_control *);
 int	 aldap_get_errno(struct aldap *, const char **);
 
 int	 aldap_get_resultcode(struct aldap_message *);
@@ -236,13 +235,13 @@ 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(struct aldap_message *, char *,
+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 **);
-int	 aldap_free_attr(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 *);
diff --git a/table_api.c b/table_api.c
new file mode 100644
index 0000000..da40c5d
--- /dev/null
+++ b/table_api.c
@@ -0,0 +1,715 @@
+/*	$OpenBSD$	*/
+
+/*
+ * Copyright (c) Philipp Takacs <[email protected]>
+ * Copyright (c) 2024 Omar Polo <[email protected]>
+ *
+ * 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 "config.h"
+
+#include <sys/tree.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <event.h>
+
+#include "dict.h"
+#include "log.h"
+#include "table_api.h"
+
+#ifndef MAXFDS
+#define MAXFDS 16
+#endif
+
+static void (*handler_async)(struct request *);
+static int (*handler_update)(void);
+static int (*handler_check)(int, struct dict *, const char *);
+static int (*handler_lookup)(int, struct dict *, const char *, char *, size_t);
+static int (*handler_fetch)(int, struct dict *, char *, size_t);
+
+static nfds_t		 nfds;
+static struct pollfd	 fds[MAXFDS];
+static fd_callback	 cbs[MAXFDS];
+
+static struct evbuffer	*inbuffer;
+
+static bool		 configured;
+
+static char		 tablename[128];
+
+/*
+ * backword compatibility:
+ * register all the services since we don't have a clue yet what the
+ * table will do
+ */
+static int		 registered_services = K_ANY;
+
+/* Dummy; just kept for backward compatibility */
+static struct dict	 params;
+static struct dict	 lookup_entries;
+
+static int
+service_id(const char *service)
+{
+	if (!strcmp(service, "alias"))
+		return (K_ALIAS);
+	if (!strcmp(service, "domain"))
+		return (K_DOMAIN);
+	if (!strcmp(service, "credentials"))
+		return (K_CREDENTIALS);
+	if (!strcmp(service, "netaddr"))
+		return (K_NETADDR);
+	if (!strcmp(service, "userinfo"))
+		return (K_USERINFO);
+	if (!strcmp(service, "source"))
+		return (K_SOURCE);
+	if (!strcmp(service, "mailaddr"))
+		return (K_MAILADDR);
+	if (!strcmp(service, "addrname"))
+		return (K_ADDRNAME);
+	if (!strcmp(service, "mailaddrmap"))
+		return (K_MAILADDRMAP);
+
+	errx(1, "unknown service %s", service);
+}
+
+static char *
+table_api_service_name(enum table_service s)
+{
+	switch (s) {
+	case K_ALIAS:		return "alias";
+	case K_DOMAIN:		return "domain";
+	case K_CREDENTIALS:	return "credentials";
+	case K_NETADDR:		return "netaddr";
+	case K_USERINFO:	return "userinfo";
+	case K_SOURCE:		return "source";
+	case K_MAILADDR:	return "mailaddr";
+	case K_ADDRNAME:	return "addrname";
+	case K_MAILADDRMAP:	return "mailaddrmap";
+	default:		return "???";
+	}
+}
+
+static void
+fallback_update_handler(const char *id, const char *tname)
+{
+	int r;
+	strlcpy(tablename, tname, sizeof(tablename));
+
+	if (handler_update == NULL)
+		errx(1, "no update handler registered");
+
+	r = handler_update();
+	if (r == 1)
+		table_api_update_finish(id);
+	else
+		table_api_error(id, O_UPDATE, NULL);
+}
+
+static void
+fallback_check_handler(const char *id, const char *tname, int service, const char *key)
+{
+	int r;
+	strlcpy(tablename, tname, sizeof(tablename));
+
+	if (handler_check == NULL)
+		errx(1, "no check handler registered");
+
+	r = handler_check(service, &params, key);
+	if (r == 0 || r == 1)
+		table_api_check_result(id, r == 1);
+	else
+		table_api_error(id, O_CHECK, NULL);
+}
+
+static void
+fallback_lookup_handler(const char *id, const char *tname, int service, const char *key)
+{
+	char buf[LINE_MAX];
+	int r;
+	strlcpy(tablename, tname, sizeof(tablename));
+
+	if (handler_lookup == NULL)
+		errx(1, "no lookup handler registered");
+
+	r = handler_lookup(service, &params, key, buf, sizeof(buf));
+	if (r == 1) {
+		table_api_lookup_result(id, service, buf);
+	}
+	if (r == 1 || r == 0)
+		table_api_lookup_finish(id);
+	else
+		table_api_error(id, O_LOOKUP, NULL);
+}
+
+static void
+fallback_fetch_handler(const char *id, const char *tname, int service)
+{
+	char buf[LINE_MAX];
+	int r;
+	strlcpy(tablename, tname, sizeof(tablename));
+
+	if (handler_fetch == NULL)
+		errx(1, "no fetch handler registered");
+
+	r = handler_fetch(service, &params, buf, sizeof(buf));
+	switch(r) {
+	case 1:
+		table_api_fetch_result(id, buf);
+		break;
+	case 0:
+		table_api_fetch_result(id, NULL);
+		break;
+	default:
+		table_api_error(id, O_FETCH, NULL);
+	}
+}
+
+void
+table_api_register_services(int s)
+{
+	registered_services = K_ANY & s;
+}
+
+void
+table_api_on_update(int(*cb)(void))
+{
+	handler_update = cb;
+}
+
+void
+table_api_on_check(int(*cb)(int, struct dict *, const char *))
+{
+	handler_check = cb;
+}
+
+void
+table_api_on_lookup(int(*cb)(int, struct dict  *, const char *, char *, size_t))
+{
+	handler_lookup = cb;
+}
+
+void
+table_api_on_fetch(int(*cb)(int, struct dict *, char *, size_t))
+{
+	handler_fetch = cb;
+}
+
+void
+table_api_on_request(void(*cb)(struct request *))
+{
+	handler_async = cb;
+}
+
+const char *
+table_api_get_name(void)
+{
+	return tablename;
+}
+
+void
+table_api_error(const char *id, enum table_operation o, const char *error)
+{
+	struct evbuffer	*res;
+
+	switch(o) {
+	case O_UPDATE:
+		printf("update-result|%s|error", id);
+		break;
+	case O_CHECK:
+		printf("check-result|%s|error", id);
+		break;
+	case O_LOOKUP:
+		printf("lookup-result|%s|error", id);
+		break;
+	case O_FETCH:
+		printf("fetch-result|%s|error", id);
+		break;
+	}
+
+#ifdef errormassage
+	if (error && *error) {
+		printf("|%s\n", error);
+	} else {
+		puts("|unknown");
+	}
+#else
+	(void)error;
+	puts("");
+#endif
+	if (fflush(stdout) == EOF)
+		err(1, "fflush");
+	res = dict_pop(&lookup_entries, id);
+	if (res)
+		evbuffer_free(res);
+}
+
+void
+table_api_update_finish(const char *id)
+{
+	if (!id) {
+		log_warnx("%s: unknow id %s", __func__, id);
+		return;
+	}
+
+	printf("update-result|%s|ok\n", id);
+	if (fflush(stdout) == EOF)
+		err(1, "fflush");
+}
+
+void
+table_api_check_result(const char *id, bool found)
+{
+	if (found)
+		printf("check-result|%s|found\n", id);
+	else
+		printf("check-result|%s|not-found\n", id);
+
+	if (fflush(stdout) == EOF)
+		err(1, "fflush");
+}
+
+void
+table_api_lookup_result(const char *id, enum table_service s, const char *buf)
+{
+	const char alias_sep[] = ", ";
+	struct evbuffer *res;
+
+	res = dict_get(&lookup_entries, id);
+
+	if (!res) {
+		res = evbuffer_new();
+		if (!res) {
+			table_api_error(id, O_LOOKUP, "can not alloc result");
+			return;
+		}
+		if (evbuffer_add(res, buf, strlen(buf)) == -1) {
+			table_api_error(id, O_LOOKUP, "can not alloc result");
+			evbuffer_free(res);
+			return;
+		}
+		dict_set(&lookup_entries, id, res);
+		return;
+	}
+	switch(s) {
+	case K_ALIAS:
+		if (evbuffer_add(res, alias_sep, sizeof(alias_sep)-1) == -1) {
+			table_api_error(id, O_LOOKUP, "can not extend result");
+			dict_pop(&lookup_entries, id);
+			evbuffer_free(res);
+			return;
+		}
+		if (evbuffer_add(res, buf, 0) == -1) {
+			table_api_error(id, O_LOOKUP, "can not extend result");
+			dict_pop(&lookup_entries, id);
+			evbuffer_free(res);
+			return;
+		}
+		break;
+	default:
+		log_warnx("id: %s lookup result override", id);
+		evbuffer_drain(res, evbuffer_get_length(res));
+		if (evbuffer_add(res, buf, sizeof(buf)) == -1) {
+			table_api_error(id, O_LOOKUP, "can not alloc result");
+			dict_pop(&lookup_entries, id);
+			evbuffer_free(res);
+			return;
+		}
+	}
+}
+
+void
+table_api_lookup_finish(const char *id)
+{
+	struct evbuffer	*res;
+
+	res = dict_pop(&lookup_entries, id);
+	if (res && evbuffer_get_length(res)) {
+		if (evbuffer_add(res, "\0", 1) == -1) {
+			table_api_error(id, O_LOOKUP, "can not extend result");
+			evbuffer_free(res);
+			return;
+		}
+		printf("lookup-result|%s|found|%s\n", id, evbuffer_pullup(res, -1));
+	} else {
+		printf("lookup-result|%s|not-found\n", id);
+	}
+
+	if (fflush(stdout) == EOF)
+		err(1, "fflush");
+	if (res)
+		evbuffer_free(res);
+}
+
+void
+table_api_fetch_result(const char *id, const char *buf)
+{
+	if (buf && *buf)
+		printf("fetch-result|%s|found|%s\n", id, buf);
+	else
+		printf("fetch-result|%s|not-found\n", id);
+
+	if (fflush(stdout) == EOF)
+		err(1, "fflush");
+}
+
+void
+table_api_register_fd(int fd, short events, fd_callback cb)
+{
+	/* first fd is reservated for stdin */
+	if (!nfds)
+		nfds++;
+
+	if (nfds >= MAXFDS)
+		exit(1);
+
+	fds[nfds].fd = fd;
+	fds[nfds].events = events;
+	cbs[nfds] = cb;
+	nfds++;
+}
+
+void
+table_api_replace_fd(int old, int new)
+{
+	for (nfds_t i = 1; i < nfds; i++) {
+		if (fds[i].fd != old) {
+			continue;
+		}
+		fds[i].fd = new;
+	}
+}
+
+void
+table_api_fd_set_events(int fd, short events)
+{
+	for (size_t i = 0; i < nfds; i++) {
+		if (fds[i].fd != fd)
+			continue;
+		fds[i].events = events;
+		return;
+	}
+}
+
+void
+table_api_remove_fd(int fd)
+{
+	for (nfds_t i = 1; i < nfds; i++) {
+		if (fds[i].fd != fd) {
+			continue;
+		}
+		nfds--;
+		if (i+1 > nfds) {
+			break;
+		}
+		memmove(fds+i, fds+i+1, (nfds-i)*sizeof(*fds));
+		memmove(cbs+i, cbs+i+1, (nfds-i)*sizeof(*cbs));
+	}
+}
+
+bool
+table_api_parse_line(char *line, size_t linelen, struct request *req)
+{
+	char		*t, *vers, *tname, *type, *service, *id, *key;
+	int		 sid;
+
+	t = line;
+	(void) linelen;
+
+	if (strncmp(t, "table|", 6)) {
+		log_warnx("malformed line");
+		return false;
+	}
+	t += 6;
+
+	vers = t;
+	if ((t = strchr(t, '|')) == NULL) {
+		log_warnx("malformed line: missing version");
+		return false;
+	}
+	*t++ = '\0';
+
+	if (strcmp(vers, "0.1") != 0) {
+		log_warnx("unsupported protocol version: %s", vers);
+		return false;
+	}
+
+	/* skip timestamp */
+	if ((t = strchr(t, '|')) == NULL) {
+		log_warnx("malformed line: missing timestamp");
+		return false;
+	}
+	*t++ = '\0';
+
+	tname = t;
+	if ((t = strchr(t, '|')) == NULL) {
+		log_warnx("malformed line: missing table name");
+		return false;
+	}
+	*t++ = '\0';
+	if (t - tname - 1 > (ptrdiff_t)req->tablesize) {
+		req->table = realloc(req->table, t - tname - 1);
+		req->tablesize = t - tname - 1;
+		if (!req->table)
+			fatal("realloc");
+	}
+	memcpy(req->table, tname, req->tablesize);
+
+	type = t;
+	if ((t = strchr(t, '|')) == NULL) {
+		log_warnx("malformed line: missing type");
+		return false;
+	}
+	*t++ = '\0';
+
+	if (!strcmp(type, "update")) {
+		if (line + linelen - t > (ptrdiff_t)req->idsize) {
+			req->id = realloc(req->id, line + linelen - t);
+			req->idsize = line + linelen - t;
+			if (!req->id)
+				fatal("realloc");
+		}
+		memcpy(req->id, t, line + linelen - t);
+		req->o = O_UPDATE;
+		return true;
+	}
+
+	service = t;
+	if ((t = strchr(t, '|')) == NULL) {
+		log_warnx("malformed line: missing service");
+		return false;
+	}
+	*t++ = '\0';
+	sid = service_id(service);
+
+	id = t;
+
+	if (!strcmp(type, "fetch")) {
+		if (line + linelen - id > (ptrdiff_t)req->idsize) {
+			req->id = realloc(req->id, line + linelen - id);
+			req->idsize = line + linelen - id;
+			if (!req->id)
+				fatal("realloc");
+		}
+		memcpy(req->id, id, line + linelen - id);
+		req->o = O_FETCH;
+		req->s = sid;
+		req->key = NULL;
+		return true;
+	}
+
+	if ((t = strchr(t, '|')) == NULL) {
+		log_warnx("malformed line: missing key");
+		return false;
+	}
+	*t++ = '\0';
+	if (t - id - 1 > (ptrdiff_t)req->idsize) {
+		req->id = realloc(req->id, t - id - 1);
+		req->idsize = t - id - 1;
+		if (!req->id)
+			fatal("realloc");
+	}
+	memcpy(req->id, id, t - id - 1);
+
+	key = t;
+	if (line + linelen - key > (ptrdiff_t)req->keysize) {
+		req->key = realloc(req->key, line + linelen - key);
+		req->keysize = line + linelen - key;
+		if (!req->key)
+			fatal("realloc");
+	}
+	memcpy(req->key, key, line + linelen - key);
+
+	if (!strcmp(type, "check")) {
+		req->o = O_CHECK;
+		req->s = sid;
+		return true;
+	} else if (!strcmp(type, "lookup")) {
+		req->o = O_LOOKUP;
+		req->s = sid;
+		return true;
+	} else {
+		log_warnx("unknown action %s", type);
+		return false;
+	}
+}
+
+void
+table_api_free_request(struct request *r)
+{
+	if (!r)
+		return;
+
+	free(r->id);
+	free(r->table);
+	free(r->key);
+	free(r);
+}
+
+static void
+do_callback(struct request *req)
+{
+	struct request *r;
+	switch (req->o) {
+	case O_UPDATE:
+		if (handler_update) {
+			fallback_update_handler(req->id, req->table);
+			return;
+		}
+		break;
+	case O_CHECK:
+		if (handler_check) {
+			fallback_check_handler(req->id, req->table, req->s, req->key);
+			return;
+		}
+		break;
+	case O_LOOKUP:
+		if (handler_lookup) {
+			fallback_lookup_handler(req->id, req->table, req->s, req->key);
+			return;
+		}
+		break;
+	case O_FETCH:
+		if (handler_fetch) {
+			fallback_fetch_handler(req->id, req->table, req->s);
+			return;
+		}
+		break;
+	default:
+		break;
+	}
+
+	r = malloc(sizeof(*r));
+	*r = *req;
+	handler_async(r);
+}
+
+static void
+api_callback(int fd, short revents)
+{
+	bool		 retry = true;
+	char		 buf[BUFSIZ];
+	size_t		 linelen;
+	int		 serrno, ret;
+	char		*line, *t;
+	struct request	 req = {0};
+
+	if (revents & (POLLERR|POLLNVAL)) {
+		exit(1);
+	}
+	if (revents & POLLHUP) {
+		exit(0);
+	}
+
+	do {
+		ret = read(fd, buf, sizeof(buf));
+		if (ret == 0) {
+			/* EOF */
+			exit(0);
+		}
+		if (ret < 0) {
+			serrno = errno;
+			if (serrno == EAGAIN || serrno == EWOULDBLOCK) {
+				retry = false;
+				continue;
+			}
+			err(1, "read");
+		}
+		evbuffer_add(inbuffer, buf, ret);
+	} while (retry);
+
+	while ((line = evbuffer_readline(inbuffer))) {
+		linelen = strlen(line);
+		t = line;
+		if (configured) {
+			if (!table_api_parse_line(line, linelen, &req)) {
+				errx(1, "can not parse input line: %s", line);
+			}
+			do_callback(&req);
+			free(line);
+			continue;
+		}
+
+		if (strncmp(t, "config|", 7) != 0)
+			errx(1, "unexpected config line: %s", line);
+		t += 7;
+
+		if (!strcmp(t, "ready")) {
+			configured = 1;
+
+			for (int s = K_ALIAS; s <= K_MAILADDRMAP; s <<= 1) {
+				printf("register|%s\n", table_api_service_name(s));
+			}
+
+			puts("register|ready");
+			if (fflush(stdout) == EOF)
+				err(1, "fflush");
+		}
+
+		free(line);
+	}
+}
+
+int
+table_api_dispatch(void)
+{
+	int	 serrno, r;
+	int flags;
+
+	dict_init(&params);
+	dict_init(&lookup_entries);
+
+	inbuffer = evbuffer_new();
+
+	flags = fcntl(STDIN_FILENO, F_GETFL, 0);
+	fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
+
+	fds[0].fd = STDIN_FILENO;
+	fds[0].events = POLLIN;
+	fds[0].revents = 0;
+	cbs[0] = api_callback;
+	if (!nfds)
+		nfds++;
+
+	while (nfds) {
+		r = poll(fds, nfds, 1024);
+		if (r == 0) {
+			/* TODO implement some timeout handling */
+			continue;
+		}
+		if (r < 0) {
+			serrno = errno;
+			if (serrno == ENOMEM || serrno == EAGAIN) {
+				continue;
+			}
+		}
+
+		for (nfds_t i = 0; i < nfds; i++) {
+			if (fds[i].revents) {
+				cbs[i](fds[i].fd, fds[i].revents);
+			}
+		}
+	}
+
+	return (0);
+}
diff --git a/table_stdio.h b/table_api.h
similarity index 55%
rename from table_stdio.h
rename to table_api.h
index 3afbbf2..eea37aa 100644
--- a/table_stdio.h
+++ b/table_api.h
@@ -15,6 +15,21 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#if defined(__clang__) || defined(__GNUC__)
+#define DEPRECATED __attribute__((deprecated))
+#else
+#define DEPRECATED
+#endif
+
+typedef void (*fd_callback)(int, short);
+
+enum table_operation {
+	O_UPDATE,
+	O_CHECK,
+	O_LOOKUP,
+	O_FETCH,
+};
+
 enum table_service {
 	K_ALIAS =	0x001,	/* returns struct expand	*/
 	K_DOMAIN =	0x002,	/* returns struct destination	*/
@@ -28,10 +43,34 @@ enum table_service {
 	K_ANY =		0xfff,
 };
 
+struct request {
+	char	*id;
+	size_t	 idsize;
+	enum	 table_operation o;
+	char	*table;
+	size_t	 tablesize;
+	enum	 table_service s;
+	char	*key;
+	size_t	 keysize;
+};
+
+bool		 table_api_parse_line(char *line, size_t linelen, struct request *req);
+void		 table_api_free_request(struct request *req);
 void		 table_api_register_services(int);
-void		 table_api_on_update(int(*)(void));
-void		 table_api_on_check(int(*)(int, struct dict *, const char *));
-void		 table_api_on_lookup(int(*)(int, struct dict *, const char *, char *, size_t));
+void		 table_api_on_update(int(*)(void)) DEPRECATED;
+void		 table_api_on_check(int(*)(int, struct dict *, const char *)) DEPRECATED;
+void		 table_api_on_lookup(int(*)(int, struct dict *, const char *, char *, size_t)) DEPRECATED;
 void		 table_api_on_fetch(int(*)(int, struct dict *, char *, size_t));
+void		 table_api_on_request(void(*)(struct request *));
 int		 table_api_dispatch(void);
-const char	*table_api_get_name(void);
+void		 table_api_error(const char *, enum table_operation, const char *);
+void		 table_api_update_finish(const char *);
+void		 table_api_check_result(const char *, bool);
+void		 table_api_lookup_result(const char *, enum table_service, const char *);
+void		 table_api_lookup_finish(const char *);
+void		 table_api_fetch_result(const char *, const char *);
+const char	*table_api_get_name(void) DEPRECATED;
+void		 table_api_register_fd(int fd, short events, fd_callback cb);
+void		 table_api_replace_fd(int old, int new);
+void		 table_api_remove_fd(int fd);
+void		 table_api_fd_set_events(int fd, short events);
diff --git a/table_ldap.c b/table_ldap.c
index d3bf260..7d6b267 100644
--- a/table_ldap.c
+++ b/table_ldap.c
@@ -25,15 +25,20 @@
 #include <netdb.h>
 
 #include <ctype.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <stdbool.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <tls.h>
 #include <unistd.h>
 
+#include "ber.h"
 #include "aldap.h"
 #include "dict.h"
 #include "log.h"
-#include "table_stdio.h"
+#include "table_api.h"
 #include "util.h"
 
 #ifndef MAXIMUM
@@ -66,24 +71,22 @@ struct query_result {
 	char	**v[MAX_ATTRS];
 };
 
-static int ldap_run_query(int type, const char *, char *, size_t);
+static int ldap_open(void);
+static struct query * lookup_query(int type);
+static void ldap_lookup_entry(const struct request *req, const struct aldap_message *m);
+static struct aldap *ldap_connect(const char *addr);
+static void ldap_handle_response(const char *ldapid, const struct aldap_message *m, struct request *req);
+static void ldap_fd_callback(int fd, short revents);
+static int read_value(char **store, const char *key, const char *value);
 
 static char *config, *url, *username, *password, *basedn, *ca_file;
+static struct dict	requests;
 
 static struct aldap *aldap;
 static struct query queries[LDAP_MAX];
 
-static int
-table_ldap_update(void)
-{
-	return 1;
-}
+static char *ldap_dn_attr[2] = { "dn", NULL };
 
-static int
-table_ldap_fetch(int service, struct dict *params, char *dst, size_t sz)
-{
-	return -1;
-}
 
 static struct aldap *
 ldap_connect(const char *addr)
@@ -93,6 +96,7 @@ ldap_connect(const char *addr)
 	struct tls_config *tls_config = NULL;
 	struct addrinfo	 hints, *res0, *res;
 	int		 error, fd = -1;
+	int		 flags;
 
 	if (aldap_parse_url(addr, &lu) != 1) {
 		log_warnx("warn: ldap_parse_url fail");
@@ -140,6 +144,9 @@ ldap_connect(const char *addr)
 		goto out;
 	}
 
+	flags = fcntl(fd, F_GETFL, 0);
+	fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+
 	if (lu.protocol == LDAPS || lu.protocol == LDAPTLS) {
 		tls_config = tls_config_new();
 		if (!tls_config) {
@@ -168,6 +175,169 @@ out:
 	return ldap;
 }
 
+static void
+ldap_lookup_entry(const struct request *req, const struct aldap_message *m)
+{
+	struct aldap_stringset *attr = NULL;
+	struct query *q = lookup_query(req->s);
+	char tmp[BUFSIZ];
+
+	switch (req->s) {
+	case K_ALIAS:
+	case K_MAILADDRMAP:
+		if (aldap_match_attr(m, q->attrs[0], &attr) == -1) {
+			return;
+		}
+		for (size_t i = 0; i < attr->len; i++) {
+			table_api_lookup_result(req->id, req->s, attr->str[i].ostr_val);
+		}
+		aldap_free_attr(attr);
+		break;
+	case K_DOMAIN:
+	case K_MAILADDR:
+		if (aldap_match_attr(m, q->attrs[0], &attr) == -1) {
+			break;
+		}
+		if (attr->len > 1)
+			log_warnx("req \"%s\" returned more then one attr \"%s\"", req->key, q->attrs[0]);
+		table_api_lookup_result(req->id, req->s, attr->str[0].ostr_val);
+		aldap_free_attr(attr);
+		break;
+	case K_CREDENTIALS:
+		if (aldap_match_attr(m, q->attrs[0], &attr) == -1)
+			break;
+		if (attr->len > 1)
+			log_warnx("req \"%s\" returned more then one attr \"%s\"", req->key, q->attrs[1]);
+		if (strlcat(tmp, attr->str[0].ostr_val, sizeof(tmp)) > sizeof(tmp))
+			break;
+		if (strlcat(tmp, ":", sizeof(tmp)) > sizeof(tmp))
+			break;
+		aldap_free_attr(attr);
+		if (aldap_match_attr(m, q->attrs[1], &attr) == -1)
+			break;
+		if (attr->len > 1)
+			log_warnx("req \"%s\" returned more then one attr \"%s\"", req->key, q->attrs[1]);
+		if (strlcat(tmp, attr->str[1].ostr_val, sizeof(tmp)) > sizeof(tmp))
+			break;
+		table_api_lookup_result(req->id, req->s, tmp);
+		break;
+	case K_USERINFO:
+		if (aldap_match_attr(m, q->attrs[0], &attr) == -1)
+			break;
+		if (attr->len > 1)
+			log_warnx("req \"%s\" returned more then one attr \"%s\"", req->key, q->attrs[0]);
+		if (strlcat(tmp, attr->str[0].ostr_val, sizeof(tmp)) > sizeof(tmp))
+			break;
+		if (strlcat(tmp, ":", sizeof(tmp)) > sizeof(tmp))
+			break;
+		aldap_free_attr(attr);
+		if (aldap_match_attr(m, q->attrs[1], &attr) == -1)
+			break;
+		if (attr->len > 1)
+			log_warnx("req \"%s\" returned more then one attr \"%s\"", req->key, q->attrs[1]);
+		if (strlcat(tmp, attr->str[1].ostr_val, sizeof(tmp)) > sizeof(tmp))
+			break;
+		if (strlcat(tmp, ":", sizeof(tmp)) > sizeof(tmp))
+			break;
+		aldap_free_attr(attr);
+		if (aldap_match_attr(m, q->attrs[2], &attr) == -1)
+			break;
+		if (attr->len > 1)
+			log_warnx("req \"%s\" returned more then one attr \"%s\"", req->key, q->attrs[2]);
+		if (strlcat(tmp, attr->str[1].ostr_val, sizeof(tmp)) > sizeof(tmp))
+			break;
+		table_api_lookup_result(req->id, req->s, tmp);
+		break;
+	default:
+		log_warnx("unhandled service");
+		break;
+	}
+
+	aldap_free_attr(attr);
+}
+
+static void
+ldap_handle_response(const char *ldapid, const struct aldap_message *m, struct request *req)
+{
+	switch (req->o) {
+	case O_CHECK:
+		switch (m->message_type) {
+		case LDAP_RES_SEARCH_ENTRY:
+			table_api_check_result(req->id, true);
+			break;
+		case LDAP_RES_SEARCH_RESULT:
+			table_api_check_result(req->id, false);
+			break;
+		default:
+			table_api_error(req->id, req->o, "unknown ldap response");
+			break;
+		}
+		dict_pop(&requests, ldapid);
+		table_api_free_request(req);
+		break;
+	case O_LOOKUP:
+		switch (m->message_type) {
+		case LDAP_RES_SEARCH_ENTRY:
+			ldap_lookup_entry(req, m);
+			break;
+		case LDAP_RES_SEARCH_RESULT:
+			if (m->page && m->page->cookie_len) {
+				table_api_error(req->id, req->o, "paginagion not yet implemented");
+			} else {
+				table_api_lookup_finish(req->id);
+			}
+			dict_pop(&requests, ldapid);
+			table_api_free_request(req);
+			break;
+		default:
+			table_api_error(req->id, req->o, "unknown ldap response");
+			dict_pop(&requests, ldapid);
+			table_api_free_request(req);
+			break;
+		}
+	default:
+		table_api_error(req->id, req->o, NULL);
+		dict_pop(&requests, ldapid);
+		table_api_free_request(req);
+	}
+}
+
+static void
+ldap_fd_callback(int fd, short revents)
+{
+	struct aldap_message	*m = NULL;
+	struct request		*req;
+	char			 ldapid[sizeof(int)*2+1];
+
+	if (revents & POLLHUP || revents & POLLERR) {
+		ldap_open();
+		return;
+	}
+
+	do {
+		aldap_freemsg(m);
+		m = aldap_parse(aldap, false);
+		if (!m) {
+			switch (aldap->err) {
+			case ALDAP_ERR_NEED_POLLOUT:
+				table_api_fd_set_events(aldap->fd, POLLOUT);
+				break;
+			case ALDAP_ERR_NEED_POLLIN:
+				table_api_fd_set_events(aldap->fd, POLLIN);
+				break;
+			default:
+				ldap_open();
+			}
+			continue;
+		}
+		snprintf(ldapid, sizeof(ldapid), "%x", m->msgid);
+		req = dict_get(&requests, ldapid);
+		if (req)
+			ldap_handle_response(ldapid, m, req);
+	} while (m);
+	aldap_freemsg(m);
+}
+
 static int
 read_value(char **store, const char *key, const char *value)
 {
@@ -331,8 +501,10 @@ static int
 ldap_open(void)
 {
 	struct aldap_message	*amsg = NULL;
+	int			 oldfd = 0;
 
 	if (aldap) {
+		oldfd = aldap->fd;
 		aldap_close(aldap);
 		log_info("info: table-ldap: closed previous connection");
 	}
@@ -348,7 +520,7 @@ ldap_open(void)
 		goto err;
 	}
 
-	if ((amsg = aldap_parse(aldap)) == NULL) {
+	if ((amsg = aldap_parse(aldap, true)) == NULL) {
 		log_warnx("warn: aldap_parse");
 		goto err;
 	}
@@ -366,6 +538,13 @@ ldap_open(void)
 		goto err;
 	}
 
+	if (!oldfd) {
+		table_api_register_fd(aldap->fd, POLLIN, ldap_fd_callback);
+	} else {
+		table_api_replace_fd(oldfd, aldap->fd);
+		table_api_fd_set_events(aldap->fd, POLLIN);
+	}
+
 	if (amsg)
 		aldap_freemsg(amsg);
 	return 1;
@@ -378,134 +557,10 @@ err:
 	return 0;
 }
 
-static int
-table_ldap_lookup(int service, struct dict *params, const char *key, char *dst, size_t sz)
+static struct query *
+lookup_query(int type)
 {
-	int ret;
-
-	switch(service) {
-	case K_ALIAS:
-	case K_DOMAIN:
-	case K_CREDENTIALS:
-	case K_USERINFO:
-	case K_MAILADDR:
-	case K_MAILADDRMAP:
-	case K_NETADDR:
-		if ((ret = ldap_run_query(service, key, dst, sz)) >= 0) {
-			return ret;
-		}
-		log_debug("debug: table-ldap: reconnecting");
-		if (!ldap_open()) {
-			log_warnx("warn: table-ldap: failed to connect");
-			return -1;
-		}
-		return ldap_run_query(service, key, dst, sz);
-	default:
-		return -1;
-	}
-}
-
-static int
-realloc_results(struct query_result **r, size_t *num)
-{
-	struct query_result *new;
-	size_t newsize = MAXIMUM(1, (*num)*2);
-	if ((new = reallocarray(*r, newsize, sizeof(**r))) == NULL)
-		return 0;
-	*num = newsize;
-	*r = new;
-	return 1;
-}
-
-static int
-ldap_query(const char *filter, const char *key, char **attributes, size_t attrn, struct query_result **results, size_t *nresults)
-{
-	struct aldap_message		*m = NULL;
-	struct aldap_page_control	*pg = NULL;
-	struct aldap_stringset		*ldap_res;
-	struct query_result		*res = NULL;
-	int				 ret;
-	size_t				 i, j, k, found = 0, nres = 0;
-
-	do {
-		ret = -1;
-		if (aldap_search(aldap, basedn, LDAP_SCOPE_SUBTREE,
-		    filter, key, attributes, 0, 0, 0, pg) == -1) {
-			goto end;
-		}
-		if (pg != NULL) {
-			aldap_freepage(pg);
-			pg = NULL;
-		}
-
-		while ((m = aldap_parse(aldap)) != NULL) {
-			if (aldap->msgid != m->msgid)
-				goto end;
-			if (m->message_type == LDAP_RES_SEARCH_RESULT) {
-				if (m->page != NULL && m->page->cookie_len)
-					pg = m->page;
-				aldap_freemsg(m);
-				m = NULL;
-				ret = 0;
-				break;
-			}
-			if (m->message_type != LDAP_RES_SEARCH_ENTRY)
-				goto end;
-
-			if (found >= nres) {
-				if (!realloc_results(&res, &nres)) {
-					goto end;
-				}
-			}
-			memset(&res[found], 0, sizeof(res[found]));
-			for (i = 0; i < attrn; ++i) {
-				if (aldap_match_attr(m, attributes[i], &ldap_res) != 1) {
-					goto end;
-				}
-				res[found].v[i] = calloc(ldap_res->len + 1, sizeof(*res[found].v[i]));
-				for (j = 0; j < ldap_res->len; j++) {
-					res[found].v[i][j] = strndup(ldap_res->str[j].ostr_val, ldap_res->str[j].ostr_len);
-				}
-				aldap_free_attr(ldap_res);
-			}
-			aldap_freemsg(m);
-			m = NULL;
-			found++;
-		}
-	} while (pg != NULL);
-
-end:
-	if (ret == -1) {
-		for (i = 0; i < found; i++) {
-			for (j = 0; j < attrn; j++) {
-				for (k = 0; res[i].v[j][k]; k++) {
-					free(res[i].v[j][k]);
-				}
-				free(res[i].v[j]);
-			}
-		}
-		free(res);
-	} else {
-		ret = found ? 1 : 0;
-		*results = res;
-		*nresults = found;
-	}
-
-	if (m)
-		aldap_freemsg(m);
-	log_debug("debug: table_ldap: ldap_query: filter=%s, key=%s, ret=%d", filter, key, ret);
-	return ret;
-}
-
-static int
-ldap_run_query(int type, const char *key, char *dst, size_t sz)
-{
-	struct query	 	*q;
-	struct query_result	*res = NULL;
-	int		  	 ret;
-	size_t			 i, j, k, nres = 0;
-	char			*r, *user, *pwhash, *uid, *gid, *home;
-
+	struct query *q;
 	switch (type) {
 	case K_ALIAS:		q = &queries[LDAP_ALIAS];	break;
 	case K_DOMAIN:		q = &queries[LDAP_DOMAIN];	break;
@@ -517,103 +572,56 @@ ldap_run_query(int type, const char *key, char *dst, size_t sz)
 	case K_MAILADDRMAP:	q = &queries[LDAP_MAILADDRMAP];	break;
 	case K_ADDRNAME:	q = &queries[LDAP_ADDRNAME];	break;
 	default:
-		return -1;
+		return NULL;
 	}
+	return q;
+}
 
-	if (!q->filter) {
-		/* XXX get the string of the type */
-		log_warnx("warn: query %d without a filter configured", type);
-		return -1;
+static void
+table_ldap_callback(struct request *req)
+{
+	char		  ldapid[sizeof(int)*2+1];
+	int		  ret;
+	struct query	 *q = lookup_query(req->s);
+	char		 * const *attrs;
+	int		  num;
+
+	if (!q) {
+		table_api_error(req->id, req->o, "service not configured");
+		return;
 	}
 
-	ret = ldap_query(q->filter, key, q->attrs, q->attrn, &res, &nres);
-	if (ret <= 0 || dst == NULL)
-		goto end;
-
-	switch (type) {
-
-	case K_ALIAS:
-	case K_MAILADDRMAP:
-		memset(dst, 0, sz);
-		for (i = 0; ret != -1 && i < nres; i++) {
-			for (j = 0; res[i].v[0][j]; j++) {
-				if ((i || j) && strlcat(dst, ", ", sz) >= sz) {
-					ret = -1;
-					break;
-				}
-				if (strlcat(dst, res[i].v[0][j], sz) >= sz) {
-					ret = -1;
-					break;
-				}
-			}
-		}
-		break;
-	case K_DOMAIN:
-	case K_MAILADDR:
-		r = res[0].v[0][0];
-		if (!r || strlcpy(dst, r, sz) >= sz)
-			ret = -1;
+	switch (req->o) {
+	case O_UPDATE:
+		table_api_error(req->id, req->o, "update not implemented");
+		table_api_free_request(req);
+		return;
+	case O_FETCH:
+		table_api_error(req->id, req->o, "fetch not implemented");
+		table_api_free_request(req);
+		return;
+	case O_CHECK:
+		attrs = ldap_dn_attr;
+		num = 1;
 		break;
-	case K_CREDENTIALS:
-		user = res[0].v[0][0];
-		pwhash = res[0].v[1][0];
-		if (!user || !pwhash || snprintf(dst, sz, "%s:%s", user, pwhash) >= (int)sz)
-			ret = -1;
-		break;
-	case K_USERINFO:
-		uid = res[0].v[0][0];
-		gid = res[0].v[1][0];
-		home = res[0].v[2][0];
-		if (!uid || !gid || !home || snprintf(dst, sz, "%s:%s:%s", uid, gid, home) >= (int)sz)
-			ret = -1;
+	case O_LOOKUP:
+		attrs = q->attrs;
+		num = 100;
 		break;
 	default:
-		log_warnx("warn: unsupported lookup kind");
-		ret = -1;
+		table_api_error(req->id, req->o, "unknown operation not implemented");
+		table_api_free_request(req);
+		return;
 	}
 
-	if (ret == -1)
-		log_warnx("warn: could not format result");
-
-end:
-	for (i = 0; i < nres; i++) {
-		for (j = 0; j < q->attrn; ++j) {
-			for (k = 0; res[i].v[j][k]; k++) {
-				free(res[i].v[j][k]);
-			}
-			free(res[i].v[j]);
-		}
-	}
-	free(res);
-
-	return ret;
-}
-
-static int
-table_ldap_check(int service, struct dict *params, const char *key)
-{
-	int ret;
-
-	switch(service) {
-	case K_ALIAS:
-	case K_DOMAIN:
-	case K_CREDENTIALS:
-	case K_USERINFO:
-	case K_MAILADDR:
-	case K_MAILADDRMAP:
-	case K_NETADDR:
-		if ((ret = ldap_run_query(service, key, NULL, 0)) >= 0) {
-			return ret;
-		}
-		log_debug("debug: table-ldap: reconnecting");
-		if (!ldap_open()) {
-			log_warnx("warn: table-ldap: failed to connect");
-			return -1;
-		}
-		return ldap_run_query(service, key, NULL, 0);
-	default:
-		return -1;
+	ret = aldap_search(aldap, basedn, LDAP_SCOPE_SUBTREE, q->filter, req->key, attrs, false, num, 0, NULL);
+	if (ret < 0) {
+		table_api_error(req->id, req->o, NULL);
+		ldap_open();
+		return;
 	}
+	snprintf(ldapid, sizeof(ldapid), "%x", ret);
+	dict_xset(&requests, ldapid, req);
 }
 
 int
@@ -623,6 +631,7 @@ main(int argc, char **argv)
 
 	log_init(1);
 	log_setverbose(~0);
+	dict_init(&requests);
 
 	while ((ch = getopt(argc, argv, "")) != -1) {
 		switch (ch) {
@@ -647,10 +656,7 @@ main(int argc, char **argv)
 		fatalx("failed to connect");
 	log_debug("debug: connected");
 
-	table_api_on_update(table_ldap_update);
-	table_api_on_check(table_ldap_check);
-	table_api_on_lookup(table_ldap_lookup);
-	table_api_on_fetch(table_ldap_fetch);
+	table_api_on_request(table_ldap_callback);
 	table_api_dispatch();
 
 	return 0;
diff --git a/table_stdio.c b/table_stdio.c
deleted file mode 100644
index 289deab..0000000
--- a/table_stdio.c
+++ /dev/null
@@ -1,276 +0,0 @@
-/*	$OpenBSD$	*/
-
-/*
- * Copyright (c) 2024 Omar Polo <[email protected]>
- *
- * 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 "config.h"
-
-#include <sys/tree.h>
-
-#include <err.h>
-#include <limits.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "dict.h"
-#include "table_stdio.h"
-
-static int (*handler_update)(void);
-static int (*handler_check)(int, struct dict *, const char *);
-static int (*handler_lookup)(int, struct dict *, const char *, char *, size_t);
-static int (*handler_fetch)(int, struct dict *, char *, size_t);
-
-static char		 tablename[128];
-
-/*
- * backword compatibility:
- * register all the services since we don't have a clue yet what the
- * table will do
- */
-static int		 registered_services = K_ANY;
-
-/* Dummy; just kept for backward compatibility */
-static struct dict	 params;
-
-static int
-service_id(const char *service)
-{
-	if (!strcmp(service, "alias"))
-		return (K_ALIAS);
-	if (!strcmp(service, "domain"))
-		return (K_DOMAIN);
-	if (!strcmp(service, "credentials"))
-		return (K_CREDENTIALS);
-	if (!strcmp(service, "netaddr"))
-		return (K_NETADDR);
-	if (!strcmp(service, "userinfo"))
-		return (K_USERINFO);
-	if (!strcmp(service, "source"))
-		return (K_SOURCE);
-	if (!strcmp(service, "mailaddr"))
-		return (K_MAILADDR);
-	if (!strcmp(service, "addrname"))
-		return (K_ADDRNAME);
-	if (!strcmp(service, "mailaddrmap"))
-		return (K_MAILADDRMAP);
-
-	errx(1, "unknown service %s", service);
-}
-
-static char *
-table_api_service_name(enum table_service s)
-{
-	switch (s) {
-	case K_ALIAS:		return "alias";
-	case K_DOMAIN:		return "domain";
-	case K_CREDENTIALS:	return "credentials";
-	case K_NETADDR:		return "netaddr";
-	case K_USERINFO:	return "userinfo";
-	case K_SOURCE:		return "source";
-	case K_MAILADDR:	return "mailaddr";
-	case K_ADDRNAME:	return "addrname";
-	case K_MAILADDRMAP:	return "mailaddrmap";
-	default:		return "???";
-	}
-}
-
-void
-table_api_register_services(int s)
-{
-	registered_services = K_ANY & s;
-}
-
-void
-table_api_on_update(int(*cb)(void))
-{
-	handler_update = cb;
-}
-
-void
-table_api_on_check(int(*cb)(int, struct dict *, const char *))
-{
-	handler_check = cb;
-}
-
-void
-table_api_on_lookup(int(*cb)(int, struct dict  *, const char *, char *, size_t))
-{
-	handler_lookup = cb;
-}
-
-void
-table_api_on_fetch(int(*cb)(int, struct dict *, char *, size_t))
-{
-	handler_fetch = cb;
-}
-
-const char *
-table_api_get_name(void)
-{
-	return tablename;
-}
-
-int
-table_api_dispatch(void)
-{
-	char		 buf[LINE_MAX];
-	char		*t, *vers, *tname, *type, *service, *id, *key;
-	char		*line = NULL;
-	size_t		 linesize = 0;
-	ssize_t		 linelen;
-	int		 sid, r, configured = 0;
-
-	dict_init(&params);
-
-	while ((linelen = getline(&line, &linesize, stdin)) != -1) {
-		if (line[linelen - 1] == '\n')
-			line[--linelen] = '\0';
-		t = line;
-
-		if (!configured) {
-			if (strncmp(t, "config|", 7) != 0)
-				errx(1, "unexpected config line: %s", line);
-			t += 7;
-
-			if (!strcmp(t, "ready")) {
-				configured = 1;
-
-				for (int s = K_ALIAS; s <= K_MAILADDRMAP; s <<= 1) {
-					printf("register|%s\n", table_api_service_name(s));
-				}
-
-				puts("register|ready");
-				if (fflush(stdout) == EOF)
-					err(1, "fflush");
-				continue;
-			}
-
-			continue;
-		}
-
-		if (strncmp(t, "table|", 6))
-			errx(1, "malformed line");
-		t += 6;
-
-		vers = t;
-		if ((t = strchr(t, '|')) == NULL)
-			errx(1, "malformed line: missing version");
-		*t++ = '\0';
-
-		if (strcmp(vers, "0.1") != 0)
-			errx(1, "unsupported protocol version: %s", vers);
-
-		/* skip timestamp */
-		if ((t = strchr(t, '|')) == NULL)
-			errx(1, "malformed line: missing timestamp");
-		*t++ = '\0';
-
-		tname = t;
-		if ((t = strchr(t, '|')) == NULL)
-			errx(1, "malformed line: missing table name");
-		*t++ = '\0';
-		strlcpy(tablename, tname, sizeof(tablename));
-
-		type = t;
-		if ((t = strchr(t, '|')) == NULL)
-			errx(1, "malformed line: missing type");
-		*t++ = '\0';
-
-		if (!strcmp(type, "update")) {
-			if (handler_update == NULL)
-				errx(1, "no update handler registered");
-
-			id = t;
-			r = handler_update();
-			printf("update-result|%s|%s\n", id,
-			    r == -1 ? "error" : "ok");
-			if (fflush(stdout) == EOF)
-				err(1, "fflush");
-			continue;
-		}
-
-		service = t;
-		if ((t = strchr(t, '|')) == NULL)
-			errx(1, "malformed line: missing service");
-		*t++ = '\0';
-		sid = service_id(service);
-
-		id = t;
-
-		r = -1;
-		if (!strcmp(type, "fetch")) {
-			if (handler_fetch == NULL)
-				errx(1, "no fetch handler registered");
-
-			if (registered_services & sid) {
-				r = handler_fetch(sid, &params,
-				    buf, sizeof(buf));
-			}
-			if (r == 1)
-				printf("fetch-result|%s|found|%s\n", id, buf);
-			else if (r == 0)
-				printf("fetch-result|%s|not-found\n", id);
-			else
-				printf("fetch-result|%s|error\n", id);
-			if (fflush(stdout) == EOF)
-				err(1, "fflush");
-			memset(buf, 0, sizeof(buf));
-			continue;
-		}
-
-		if ((t = strchr(t, '|')) == NULL)
-			errx(1, "malformed line: missing key");
-		*t++ = '\0';
-		key = t;
-
-		if (!strcmp(type, "check")) {
-			if (handler_check == NULL)
-				errx(1, "no check handler registered");
-			if (registered_services & sid) {
-				r = handler_check(sid, &params, key);
-			}
-			if (r == 1)
-				printf("check-result|%s|found\n", id);
-			else if (r == 0)
-				printf("check-result|%s|not-found\n", id);
-			else
-				printf("check-result|%s|error\n", id);
-		} else if (!strcmp(type, "lookup")) {
-			if (handler_lookup == NULL)
-				errx(1, "no lookup handler registered");
-			if (registered_services & sid) {
-				r = handler_lookup(sid, &params, key,
-				    buf, sizeof(buf));
-			}
-			if (r == 1)
-				printf("lookup-result|%s|found|%s\n", id, buf);
-			else if (r == 0)
-				printf("lookup-result|%s|not-found\n", id);
-			else
-				printf("lookup-result|%s|error\n", id);
-			memset(buf, 0, sizeof(buf));
-		} else
-			errx(1, "unknown action %s", type);
-
-		if (fflush(stdout) == EOF)
-			err(1, "fflush");
-	}
-
-	if (ferror(stdin))
-		err(1, "getline");
-
-	return (0);
-}

Reply via email to