From: Philipp Subject: aldap To: tech@openbsd.org Date: Sun, 08 Sep 2024 14:52:54 +0200 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 * Copyright (c) 2006, 2007 Marc Balmer * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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: (%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, "\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, "\n"); break; } fprintf(stderr, "value %lld\n", v); break; case BER_TYPE_ENUMERATED: if (ober_get_enumerated(root, &v) == -1) { fprintf(stderr, "\n"); break; } fprintf(stderr, "value %lld\n", v); break; case BER_TYPE_BITSTRING: if (ober_get_bitstring(root, (void *)&buf, &len) == -1) { fprintf(stderr, "\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, "\n"); break; } fprintf(stderr, "\n"); break; case BER_TYPE_OCTETSTRING: if (ober_get_nstring(root, (void *)&buf, &len) == -1) { fprintf(stderr, "\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 * Copyright (c) 2006, 2007 Marc Balmer * * 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 *);