diff options
Diffstat (limited to 'usr.sbin/ypldap')
-rw-r--r-- | usr.sbin/ypldap/Makefile | 20 | ||||
-rw-r--r-- | usr.sbin/ypldap/aldap.c | 1272 | ||||
-rw-r--r-- | usr.sbin/ypldap/aldap.h | 221 | ||||
-rw-r--r-- | usr.sbin/ypldap/ber.c | 1268 | ||||
-rw-r--r-- | usr.sbin/ypldap/ber.h | 129 | ||||
-rw-r--r-- | usr.sbin/ypldap/entries.c | 149 | ||||
-rw-r--r-- | usr.sbin/ypldap/ldapclient.c | 705 | ||||
-rw-r--r-- | usr.sbin/ypldap/log.c | 162 | ||||
-rw-r--r-- | usr.sbin/ypldap/parse.y | 838 | ||||
-rw-r--r-- | usr.sbin/ypldap/yp.c | 652 | ||||
-rw-r--r-- | usr.sbin/ypldap/ypldap.8 | 82 | ||||
-rw-r--r-- | usr.sbin/ypldap/ypldap.c | 651 | ||||
-rw-r--r-- | usr.sbin/ypldap/ypldap.conf.5 | 167 | ||||
-rw-r--r-- | usr.sbin/ypldap/ypldap.h | 222 | ||||
-rw-r--r-- | usr.sbin/ypldap/ypldap_dns.c | 254 |
15 files changed, 6792 insertions, 0 deletions
diff --git a/usr.sbin/ypldap/Makefile b/usr.sbin/ypldap/Makefile new file mode 100644 index 0000000..1d3cabc --- /dev/null +++ b/usr.sbin/ypldap/Makefile @@ -0,0 +1,20 @@ +# $OpenBSD: Makefile,v 1.8 2015/09/09 15:33:18 deraadt Exp $ +# $FreeBSD$ + +PROG= ypldap +SRCS= parse.y ypldap.c log.c \ + ldapclient.c entries.c yp.c \ + aldap.c ber.c \ + ypldap_dns.c + +MAN= ypldap.8 ypldap.conf.5 + +LIBADD= openbsd event util rpcsvc + +CFLAGS+=-I${.CURDIR} +CFLAGS+=-I${.CURDIR}/../../contrib/pf/libevent +CFLAGS+=-I${.CURDIR}/../../lib/libopenbsd + +WARNS= 2 + +.include <bsd.prog.mk> diff --git a/usr.sbin/ypldap/aldap.c b/usr.sbin/ypldap/aldap.c new file mode 100644 index 0000000..7062507 --- /dev/null +++ b/usr.sbin/ypldap/aldap.c @@ -0,0 +1,1272 @@ +/* $Id: aldap.c,v 1.30 2012/04/30 21:40:03 jmatthew Exp $ */ +/* $OpenBSD: aldap.c,v 1.30 2012/04/30 21:40:03 jmatthew Exp $ */ +/* $FreeBSD$ */ + +/* + * 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 <errno.h> +#include <inttypes.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "aldap.h" + +#if 0 +#define DEBUG +#endif +#define VERSION 3 + +static struct ber_element *ldap_parse_search_filter(struct ber_element *, + char *); +static struct ber_element *ldap_do_parse_search_filter( + struct ber_element *, char **); +char **aldap_get_stringset(struct ber_element *); +char *utoa(char *); +char *parseval(char *, size_t); +int aldap_create_page_control(struct ber_element *, + int, struct aldap_page_control *); + +#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 + +int +aldap_close(struct aldap *al) +{ + if (close(al->ber.fd) == -1) + return (-1); + + ber_free(&al->ber); + free(al); + + return (0); +} + +struct aldap * +aldap_init(int fd) +{ + struct aldap *a; + + if ((a = calloc(1, sizeof(*a))) == NULL) + return NULL; + a->ber.fd = fd; + + return a; +} + +int +aldap_bind(struct aldap *ldap, char *binddn, char *bindcred) +{ + struct ber_element *root = NULL, *elm; + int error; + + if (binddn == NULL) + binddn = ""; + if (bindcred == NULL) + bindcred = ""; + + if ((root = ber_add_sequence(NULL)) == NULL) + goto fail; + + elm = ber_printf_elements(root, "d{tdsst", ++ldap->msgid, BER_CLASS_APP, + (unsigned long)LDAP_REQ_BIND, VERSION, binddn, bindcred, + BER_CLASS_CONTEXT, (unsigned long)LDAP_AUTH_SIMPLE); + if (elm == NULL) + goto fail; + + LDAP_DEBUG("aldap_bind", root); + + error = ber_write_elements(&ldap->ber, root); + ber_free_elements(root); + root = NULL; + if (error == -1) + goto fail; + + return (ldap->msgid); +fail: + if (root != NULL) + ber_free_elements(root); + + ldap->err = ALDAP_ERR_OPERATION_FAILED; + return (-1); +} + +int +aldap_unbind(struct aldap *ldap) +{ + struct ber_element *root = NULL, *elm; + int error; + + if ((root = ber_add_sequence(NULL)) == NULL) + goto fail; + elm = ber_printf_elements(root, "d{t", ++ldap->msgid, BER_CLASS_APP, + LDAP_REQ_UNBIND_30); + if (elm == NULL) + goto fail; + + LDAP_DEBUG("aldap_unbind", root); + + error = ber_write_elements(&ldap->ber, root); + ber_free_elements(root); + root = NULL; + if (error == -1) + goto fail; + + return (ldap->msgid); +fail: + if (root != NULL) + ber_free_elements(root); + + ldap->err = ALDAP_ERR_OPERATION_FAILED; + + return (-1); +} + +int +aldap_search(struct aldap *ldap, char *basedn, enum scope scope, char *filter, + char **attrs, int typesonly, int sizelimit, int timelimit, + struct aldap_page_control *page) +{ + struct ber_element *root = NULL, *ber, *c; + int i, error; + + if ((root = ber_add_sequence(NULL)) == NULL) + goto fail; + + ber = ber_printf_elements(root, "d{t", ++ldap->msgid, BER_CLASS_APP, + (unsigned long) LDAP_REQ_SEARCH); + if (ber == NULL) { + ldap->err = ALDAP_ERR_OPERATION_FAILED; + goto fail; + } + + c = ber; + ber = ber_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)) == NULL) { + ldap->err = ALDAP_ERR_PARSER_ERROR; + goto fail; + } + + if ((ber = ber_add_sequence(ber)) == NULL) + goto fail; + if (attrs != NULL) + for (i = 0; attrs[i] != NULL; i++) { + if ((ber = ber_add_string(ber, attrs[i])) == NULL) + goto fail; + } + + aldap_create_page_control(c, 100, page); + + LDAP_DEBUG("aldap_search", root); + + error = ber_write_elements(&ldap->ber, root); + ber_free_elements(root); + root = NULL; + if (error == -1) { + ldap->err = ALDAP_ERR_OPERATION_FAILED; + goto fail; + } + + return (ldap->msgid); + +fail: + if (root != NULL) + ber_free_elements(root); + + return (-1); +} + +int +aldap_create_page_control(struct ber_element *elm, int size, + struct aldap_page_control *page) +{ + int len; + struct ber c; + struct ber_element *ber = NULL; + + c.br_wbuf = NULL; + c.fd = -1; + + ber = ber_add_sequence(NULL); + + if (page == NULL) { + if (ber_printf_elements(ber, "ds", 50, "") == NULL) + goto fail; + } else { + if (ber_printf_elements(ber, "dx", 50, page->cookie, + page->cookie_len) == NULL) + goto fail; + } + + if ((len = ber_write_elements(&c, ber)) < 1) + goto fail; + if (ber_printf_elements(elm, "{t{sx", 2, 0, LDAP_PAGED_OID, + c.br_wbuf, (size_t)len) == NULL) + goto fail; + + ber_free_elements(ber); + ber_free(&c); + return len; +fail: + if (ber != NULL) + ber_free_elements(ber); + ber_free(&c); + + return (-1); +} + +struct aldap_message * +aldap_parse(struct aldap *ldap) +{ + int class; + unsigned long type; + long long msgid = 0; + struct aldap_message *m; + struct ber_element *a = NULL, *ep; + + if ((m = calloc(1, sizeof(struct aldap_message))) == NULL) + return NULL; + + if ((m->msg = ber_read_elements(&ldap->ber, NULL)) == NULL) + goto parsefail; + + LDAP_DEBUG("message", m->msg); + + if (ber_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 (ber_scanf_elements(m->protocol_op, "{EeSeSe", + &m->body.res.rescode, &m->dn, &m->body.res.diagmsg, &a) != 0) + goto parsefail; + if (m->body.res.rescode == LDAP_REFERRAL) + if (ber_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) { + ber_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 (ber_scanf_elements(m->protocol_op, "{eS{e", &m->dn, + &m->body.search.attrs) != 0) + goto parsefail; + break; + case LDAP_RES_SEARCH_REFERENCE: + if (ber_scanf_elements(m->protocol_op, "{e", &m->references) != 0) + goto parsefail; + break; + } + + return m; +parsefail: + ldap->err = ALDAP_ERR_PARSER_ERROR; + 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; + b.fd = -1; + ber_scanf_elements(control, "ss", &oid, &encoded); + ber_set_readbuf(&b, encoded, control->be_next->be_len); + elm = ber_read_elements(&b, NULL); + + if ((page = malloc(sizeof(struct aldap_page_control))) == NULL) { + if (elm != NULL) + ber_free_elements(elm); + ber_free(&b); + return NULL; + } + + ber_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) + ber_free_elements(elm); + ber_free(&b); + free(page); + return NULL; + } + memcpy(page->cookie, s, page->cookie_len); + + ber_free_elements(elm); + ber_free(&b); + return page; +} + +void +aldap_freepage(struct aldap_page_control *page) +{ + free(page->cookie); + free(page); +} + +void +aldap_freemsg(struct aldap_message *msg) +{ + if (msg->msg) + ber_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 (ber_get_string(msg->dn, &dn) == -1) + return NULL; + + return utoa(dn); +} + +char ** +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 (ber_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 && ber_get_eoc(a) != 0; + i++, a = a->be_next) + ; + + return i; +} + +int +aldap_first_attr(struct aldap_message *msg, char **outkey, char ***outvalues) +{ + struct ber_element *b, *c; + char *key; + char **ret; + + if (msg->body.search.attrs == NULL) + goto fail; + + if (ber_scanf_elements(msg->body.search.attrs, "{s(e)}e", + &key, &b, &c) != 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, char ***outvalues) +{ + struct ber_element *a, *b; + char *key; + char **ret; + + if (msg->body.search.iter == NULL) + goto notfound; + + LDAP_DEBUG("attr", msg->body.search.iter); + + if (ber_get_eoc(msg->body.search.iter) == 0) + goto notfound; + + if (ber_scanf_elements(msg->body.search.iter, "{s(e)}e", &key, &a, &b) + != 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(struct aldap_message *msg, char *inkey, char ***outvalues) +{ + struct ber_element *a, *b; + char *descr = NULL; + char **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 (ber_get_eoc(a) == 0) + goto notfound; + if (ber_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); +} + +int +aldap_free_attr(char **values) +{ + int i; + + if (values == NULL) + return -1; + + for (i = 0; values[i] != NULL; i++) + free(values[i]); + + free(values); + + return (1); +} + +#if 0 +void +aldap_free_url(struct aldap_url *lu) +{ + free(lu->buffer); + free(lu->filter); +} + +int +aldap_parse_url(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) + goto fail; + lu->protocol = LDAP; + p += strlen(LDAP_URL); + + /* 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 + lu->port = strtonum(++forward2, 0, PORT_MAX, &errstr); + if (errstr) + goto fail; + } + } + /* 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: + free(url); + 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_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, lu->attributes, + typesonly, sizelimit, timelimit) == -1) + goto fail; + + aldap_free_url(lu); + return (ldap->msgid); +fail: + aldap_free_url(lu); + return (-1); +} +#endif /* 0 */ + +/* + * internal functions + */ + +char ** +aldap_get_stringset(struct ber_element *elm) +{ + struct ber_element *a; + int i; + char **ret; + char *s; + + if (elm->be_type != BER_TYPE_OCTETSTRING) + return NULL; + + for (a = elm, i = 1; i > 0 && a != NULL && a->be_type == + BER_TYPE_OCTETSTRING; a = a->be_next, i++) + ; + if (i == 1) + return NULL; + + if ((ret = calloc(i + 1, sizeof(char *))) == NULL) + return NULL; + + for (a = elm, i = 0; a != NULL && a->be_type == BER_TYPE_OCTETSTRING; + a = a->be_next, i++) { + + ber_get_string(a, &s); + ret[i] = utoa(s); + } + ret[i + 1] = NULL; + + 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, char *filter) +{ + struct ber_element *elm; + char *cp; + + cp = filter; + + if (cp == NULL || *cp == '\0') { + errno = EINVAL; + return (NULL); + } + + if ((elm = ldap_do_parse_search_filter(ber, &cp)) == NULL) + return (NULL); + + if (*cp != '\0') { + ber_free_elements(elm); + ber_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, char **cpp) +{ + struct ber_element *elm, *root = NULL; + char *attr_desc, *attr_val, *parsed_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 = ber_add_set(prev)) == NULL) + goto callfail; + root = elm; + ber_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)) == NULL) + goto bad; + } + + if (*cp != ')') /* trailing `)` of filter */ + goto syntaxfail; + break; + + case '!': /* NOT */ + if ((root = ber_add_sequence(prev)) == NULL) + goto callfail; + ber_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_NOT); + + cp++; /* now points to sub-filter */ + if ((elm = ldap_do_parse_search_filter(root, &cp)) == 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 = + ber_add_nstring(prev, attr_desc, len)) == NULL) + goto bad; + + ber_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_PRES); + break; + } + + if ((root = ber_add_sequence(prev)) == NULL) + goto callfail; + ber_set_header(root, BER_CLASS_CONTEXT, type); + + if ((elm = ber_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; + + ber_set_header(root, BER_CLASS_CONTEXT, LDAP_FILT_SUBS); + + if ((elm = ber_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)) == + NULL) + goto callfail; + elm = ber_add_nstring(elm, parsed_val, + strlen(parsed_val)); + free(parsed_val); + if (elm == NULL) + goto callfail; + ber_set_header(elm, BER_CLASS_CONTEXT, type); + if (type == LDAP_FILT_SUBS_FIN) + break; + } + break; + } + + if ((parsed_val = parseval(attr_val, len)) == NULL) + goto callfail; + elm = ber_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) + ber_free_elements(root); + ber_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 */ + ber_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 (%lu) 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, "(%lu) encoding %lu ", + root->be_type, root->be_encoding); + + if (constructed) + root->be_encoding = constructed; + + switch (root->be_encoding) { + case BER_TYPE_BOOLEAN: + if (ber_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 (ber_get_integer(root, &v) == -1) { + fprintf(stderr, "<INVALID>\n"); + break; + } + fprintf(stderr, "value %lld\n", v); + break; + case BER_TYPE_ENUMERATED: + if (ber_get_enumerated(root, &v) == -1) { + fprintf(stderr, "<INVALID>\n"); + break; + } + fprintf(stderr, "value %lld\n", v); + break; + case BER_TYPE_BITSTRING: + if (ber_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 (ber_get_oid(root, &o) == -1) { + fprintf(stderr, "<INVALID>\n"); + break; + } + fprintf(stderr, "\n"); + break; + case BER_TYPE_OCTETSTRING: + if (ber_get_nstring(root, (void *)&buf, &len) == -1) { + fprintf(stderr, "<INVALID>\n"); + break; + } + fprintf(stderr, "string \"%.*s\"\n", 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 + +/* + * Convert UTF-8 to ASCII. + * 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'; ) { + if ((u[i] & 0xF0) == 0xF0) + i += 4; + else if ((u[i] & 0xE0) == 0xE0) + i += 3; + else if ((u[i] & 0xC0) == 0xC0) + i += 2; + else + i += 1; + 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'; j++) { + if ((u[i] & 0xF0) == 0xF0) { + str[j] = '?'; + i += 4; + } else if ((u[i] & 0xE0) == 0xE0) { + str[j] = '?'; + i += 3; + } else if ((u[i] & 0xC0) == 0xC0) { + str[j] = '?'; + i += 2; + } else { + str[j] = u[i]; + i += 1; + } + } + + return str; +} + +/* + * Parse a LDAP value + * notes: + * the argument u should be a NULL terminated sequence of ASCII bytes. + */ +char * +parseval(char *p, size_t len) +{ + char hex[3]; + char *cp = p, *buffer, *newbuffer; + size_t size, newsize, i, j; + + size = 50; + if ((buffer = calloc(1, size)) == NULL) + return NULL; + + for (i = j = 0; j < len; i++) { + if (i >= size) { + newsize = size + 1024; + if ((newbuffer = realloc(buffer, newsize)) == NULL) { + free(buffer); + return (NULL); + } + buffer = newbuffer; + size = newsize; + } + + if (cp[j] == '\\') { + strlcpy(hex, cp + j + 1, sizeof(hex)); + buffer[i] = (char)strtoumax(hex, NULL, 16); + j += 3; + } else { + buffer[i] = cp[j]; + j++; + } + } + + 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; + default: + *estr = "unknown"; + break; + } + return (a->err); +} diff --git a/usr.sbin/ypldap/aldap.h b/usr.sbin/ypldap/aldap.h new file mode 100644 index 0000000..cdf5316 --- /dev/null +++ b/usr.sbin/ypldap/aldap.h @@ -0,0 +1,221 @@ +/* $Id: aldap.h,v 1.9 2012/04/30 21:40:03 jmatthew Exp $ */ +/* $OpenBSD: aldap.h,v 1.9 2012/04/30 21:40:03 jmatthew Exp $ */ +/* $FreeBSD$ */ + +/* + * 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 <stdio.h> +#include "ber.h" + +#define LDAP_URL "ldap://" +#define LDAP_PORT 389 +#define LDAP_PAGED_OID "1.2.840.113556.1.4.319" + +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 + u_int8_t err; + int msgid; + struct ber ber; +}; + +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 +}; + +struct aldap_url { + int protocol; + char *host; + in_port_t 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, +}; + +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 fd); +int aldap_close(struct aldap *); +struct aldap_message *aldap_parse(struct aldap *); +void aldap_freemsg(struct aldap_message *); + +int aldap_bind(struct aldap *, char *, char *); +int aldap_unbind(struct aldap *); +int aldap_search(struct aldap *, char *, enum scope, char *, char **, 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 *); +char **aldap_get_references(struct aldap_message *); +void aldap_free_references(char **values); +#if 0 +int aldap_parse_url(char *, struct aldap_url *); +void aldap_free_url(struct aldap_url *); +int aldap_search_url(struct aldap *, char *, int, int, int); +#endif + +int aldap_count_attrs(struct aldap_message *); +int aldap_match_attr(struct aldap_message *, char *, char ***); +int aldap_first_attr(struct aldap_message *, char **, char ***); +int aldap_next_attr(struct aldap_message *, char **, char ***); +int aldap_free_attr(char **); + +struct aldap_page_control *aldap_parse_page_control(struct ber_element *, size_t len); +void aldap_freepage(struct aldap_page_control *); diff --git a/usr.sbin/ypldap/ber.c b/usr.sbin/ypldap/ber.c new file mode 100644 index 0000000..540df69 --- /dev/null +++ b/usr.sbin/ypldap/ber.c @@ -0,0 +1,1268 @@ +/* $OpenBSD: ber.c,v 1.9 2015/02/12 00:30:38 pelikan Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2006, 2007 Claudio Jeker <claudio@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 <sys/types.h> + +#include <errno.h> +#include <limits.h> +#include <stdlib.h> +#include <err.h> /* XXX for debug output */ +#include <stdio.h> /* XXX for debug output */ +#include <string.h> +#include <unistd.h> +#include <stdarg.h> + +#include "ber.h" + +#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b)) + +#define BER_TYPE_CONSTRUCTED 0x20 /* otherwise primitive */ +#define BER_TYPE_SINGLE_MAX 30 +#define BER_TAG_MASK 0x1f +#define BER_TAG_MORE 0x80 /* more subsequent octets */ +#define BER_TAG_TYPE_MASK 0x7f +#define BER_CLASS_SHIFT 6 + +static int ber_dump_element(struct ber *ber, struct ber_element *root); +static void ber_dump_header(struct ber *ber, struct ber_element *root); +static void ber_putc(struct ber *ber, u_char c); +static void ber_write(struct ber *ber, void *buf, size_t len); +static ssize_t get_id(struct ber *b, unsigned long *tag, int *class, + int *cstruct); +static ssize_t get_len(struct ber *b, ssize_t *len); +static ssize_t ber_read_element(struct ber *ber, struct ber_element *elm); +static ssize_t ber_readbuf(struct ber *b, void *buf, size_t nbytes); +static ssize_t ber_getc(struct ber *b, u_char *c); +static ssize_t ber_read(struct ber *ber, void *buf, size_t len); + +#ifdef DEBUG +#define DPRINTF(...) printf(__VA_ARGS__) +#else +#define DPRINTF(...) do { } while (0) +#endif + +struct ber_element * +ber_get_element(unsigned long encoding) +{ + struct ber_element *elm; + + if ((elm = calloc(1, sizeof(*elm))) == NULL) + return NULL; + + elm->be_encoding = encoding; + ber_set_header(elm, BER_CLASS_UNIVERSAL, BER_TYPE_DEFAULT); + + return elm; +} + +void +ber_set_header(struct ber_element *elm, int class, unsigned long type) +{ + elm->be_class = class & BER_CLASS_MASK; + if (type == BER_TYPE_DEFAULT) + type = elm->be_encoding; + elm->be_type = type; +} + +void +ber_link_elements(struct ber_element *prev, struct ber_element *elm) +{ + if (prev != NULL) { + if ((prev->be_encoding == BER_TYPE_SEQUENCE || + prev->be_encoding == BER_TYPE_SET) && + prev->be_sub == NULL) + prev->be_sub = elm; + else + prev->be_next = elm; + } +} + +struct ber_element * +ber_unlink_elements(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((prev->be_encoding == BER_TYPE_SEQUENCE || + prev->be_encoding == BER_TYPE_SET) && + prev->be_sub != NULL) { + elm = prev->be_sub; + prev->be_sub = NULL; + } else { + elm = prev->be_next; + prev->be_next = NULL; + } + + return (elm); +} + +void +ber_replace_elements(struct ber_element *prev, struct ber_element *new) +{ + struct ber_element *ber, *next; + + ber = ber_unlink_elements(prev); + next = ber_unlink_elements(ber); + ber_link_elements(new, next); + ber_link_elements(prev, new); + + /* cleanup old element */ + ber_free_elements(ber); +} + +struct ber_element * +ber_add_sequence(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_SEQUENCE)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_set(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_SET)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_enumerated(struct ber_element *prev, long long val) +{ + struct ber_element *elm; + u_int i, len = 0; + u_char cur, last = 0; + + if ((elm = ber_get_element(BER_TYPE_ENUMERATED)) == NULL) + return NULL; + + elm->be_numeric = val; + + for (i = 0; i < sizeof(long long); i++) { + cur = val & 0xff; + if (cur != 0 && cur != 0xff) + len = i; + if ((cur == 0 && last & 0x80) || + (cur == 0xff && (last & 0x80) == 0)) + len = i; + val >>= 8; + last = cur; + } + elm->be_len = len + 1; + + ber_link_elements(prev, elm); + + return elm; +} + +struct ber_element * +ber_add_integer(struct ber_element *prev, long long val) +{ + struct ber_element *elm; + u_int i, len = 0; + u_char cur, last = 0; + + if ((elm = ber_get_element(BER_TYPE_INTEGER)) == NULL) + return NULL; + + elm->be_numeric = val; + + for (i = 0; i < sizeof(long long); i++) { + cur = val & 0xff; + if (cur != 0 && cur != 0xff) + len = i; + if ((cur == 0 && last & 0x80) || + (cur == 0xff && (last & 0x80) == 0)) + len = i; + val >>= 8; + last = cur; + } + elm->be_len = len + 1; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_integer(struct ber_element *elm, long long *n) +{ + if (elm->be_encoding != BER_TYPE_INTEGER) + return -1; + + *n = elm->be_numeric; + return 0; +} + +int +ber_get_enumerated(struct ber_element *elm, long long *n) +{ + if (elm->be_encoding != BER_TYPE_ENUMERATED) + return -1; + + *n = elm->be_numeric; + return 0; +} + + +struct ber_element * +ber_add_boolean(struct ber_element *prev, int bool) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_BOOLEAN)) == NULL) + return NULL; + + elm->be_numeric = bool ? 0xff : 0; + elm->be_len = 1; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_boolean(struct ber_element *elm, int *b) +{ + if (elm->be_encoding != BER_TYPE_BOOLEAN) + return -1; + + *b = !(elm->be_numeric == 0); + return 0; +} + +struct ber_element * +ber_add_string(struct ber_element *prev, const char *string) +{ + return ber_add_nstring(prev, string, strlen(string)); +} + +struct ber_element * +ber_add_nstring(struct ber_element *prev, const char *string0, size_t len) +{ + struct ber_element *elm; + char *string; + + if ((string = calloc(1, len)) == NULL) + return NULL; + if ((elm = ber_get_element(BER_TYPE_OCTETSTRING)) == NULL) { + free(string); + return NULL; + } + + bcopy(string0, string, len); + elm->be_val = string; + elm->be_len = len; + elm->be_free = 1; /* free string on cleanup */ + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_string(struct ber_element *elm, char **s) +{ + if (elm->be_encoding != BER_TYPE_OCTETSTRING) + return -1; + + *s = elm->be_val; + return 0; +} + +int +ber_get_nstring(struct ber_element *elm, void **p, size_t *len) +{ + if (elm->be_encoding != BER_TYPE_OCTETSTRING) + return -1; + + *p = elm->be_val; + *len = elm->be_len; + return 0; +} + +struct ber_element * +ber_add_bitstring(struct ber_element *prev, const void *v0, size_t len) +{ + struct ber_element *elm; + void *v; + + if ((v = calloc(1, len)) == NULL) + return NULL; + if ((elm = ber_get_element(BER_TYPE_BITSTRING)) == NULL) { + free(v); + return NULL; + } + + bcopy(v0, v, len); + elm->be_val = v; + elm->be_len = len; + elm->be_free = 1; /* free string on cleanup */ + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_bitstring(struct ber_element *elm, void **v, size_t *len) +{ + if (elm->be_encoding != BER_TYPE_BITSTRING) + return -1; + + *v = elm->be_val; + *len = elm->be_len; + return 0; +} + +struct ber_element * +ber_add_null(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_NULL)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_null(struct ber_element *elm) +{ + if (elm->be_encoding != BER_TYPE_NULL) + return -1; + + return 0; +} + +struct ber_element * +ber_add_eoc(struct ber_element *prev) +{ + struct ber_element *elm; + + if ((elm = ber_get_element(BER_TYPE_EOC)) == NULL) + return NULL; + + ber_link_elements(prev, elm); + + return elm; +} + +int +ber_get_eoc(struct ber_element *elm) +{ + if (elm->be_encoding != BER_TYPE_EOC) + return -1; + + return 0; +} + +size_t +ber_oid2ber(struct ber_oid *o, u_int8_t *buf, size_t len) +{ + u_int32_t v; + u_int i, j = 0, k; + + if (o->bo_n < BER_MIN_OID_LEN || o->bo_n > BER_MAX_OID_LEN || + o->bo_id[0] > 2 || o->bo_id[1] > 40) + return (0); + + v = (o->bo_id[0] * 40) + o->bo_id[1]; + for (i = 2, j = 0; i <= o->bo_n; v = o->bo_id[i], i++) { + for (k = 28; k >= 7; k -= 7) { + if (v >= (u_int)(1 << k)) { + if (len) + buf[j] = v >> k | BER_TAG_MORE; + j++; + } + } + if (len) + buf[j] = v & BER_TAG_TYPE_MASK; + j++; + } + + return (j); +} + +int +ber_string2oid(const char *oidstr, struct ber_oid *o) +{ + char *sp, *p, str[BUFSIZ]; + const char *errstr; + + if (strlcpy(str, oidstr, sizeof(str)) >= sizeof(str)) + return (-1); + bzero(o, sizeof(*o)); + + /* Parse OID strings in the common forms n.n.n, n_n_n_n, or n-n-n */ + for (p = sp = str; p != NULL; sp = p) { + if ((p = strpbrk(p, "._-")) != NULL) + *p++ = '\0'; + o->bo_id[o->bo_n++] = strtonum(sp, 0, UINT_MAX, &errstr); + if (errstr || o->bo_n > BER_MAX_OID_LEN) + return (-1); + } + + return (0); +} + +struct ber_element * +ber_add_oid(struct ber_element *prev, struct ber_oid *o) +{ + struct ber_element *elm; + u_int8_t *buf; + size_t len; + + if ((elm = ber_get_element(BER_TYPE_OBJECT)) == NULL) + return (NULL); + + if ((len = ber_oid2ber(o, NULL, 0)) == 0) + goto fail; + + if ((buf = calloc(1, len)) == NULL) + goto fail; + + elm->be_val = buf; + elm->be_len = len; + elm->be_free = 1; + + if (ber_oid2ber(o, buf, len) != len) + goto fail; + + ber_link_elements(prev, elm); + + return (elm); + + fail: + ber_free_elements(elm); + return (NULL); +} + +struct ber_element * +ber_add_noid(struct ber_element *prev, struct ber_oid *o, int n) +{ + struct ber_oid no; + + if (n > BER_MAX_OID_LEN) + return (NULL); + no.bo_n = n; + bcopy(&o->bo_id, &no.bo_id, sizeof(no.bo_id)); + + return (ber_add_oid(prev, &no)); +} + +struct ber_element * +ber_add_oidstring(struct ber_element *prev, const char *oidstr) +{ + struct ber_oid o; + + if (ber_string2oid(oidstr, &o) == -1) + return (NULL); + + return (ber_add_oid(prev, &o)); +} + +int +ber_get_oid(struct ber_element *elm, struct ber_oid *o) +{ + u_int8_t *buf; + size_t len, i = 0, j = 0; + + if (elm->be_encoding != BER_TYPE_OBJECT) + return (-1); + + buf = elm->be_val; + len = elm->be_len; + + if (!buf[i]) + return (-1); + + bzero(o, sizeof(*o)); + o->bo_id[j++] = buf[i] / 40; + o->bo_id[j++] = buf[i++] % 40; + for (; i < len && j < BER_MAX_OID_LEN; i++) { + o->bo_id[j] = (o->bo_id[j] << 7) + (buf[i] & ~0x80); + if (buf[i] & 0x80) + continue; + j++; + } + o->bo_n = j; + + return (0); +} + +struct ber_element * +ber_printf_elements(struct ber_element *ber, char *fmt, ...) +{ + va_list ap; + int d, class; + size_t len; + unsigned long type; + long long i; + char *s; + void *p; + struct ber_oid *o; + struct ber_element *sub = ber, *e; + + va_start(ap, fmt); + while (*fmt) { + switch (*fmt++) { + case 'B': + p = va_arg(ap, void *); + len = va_arg(ap, size_t); + if ((ber = ber_add_bitstring(ber, p, len)) == NULL) + goto fail; + break; + case 'b': + d = va_arg(ap, int); + if ((ber = ber_add_boolean(ber, d)) == NULL) + goto fail; + break; + case 'd': + d = va_arg(ap, int); + if ((ber = ber_add_integer(ber, d)) == NULL) + goto fail; + break; + case 'e': + e = va_arg(ap, struct ber_element *); + ber_link_elements(ber, e); + break; + case 'E': + i = va_arg(ap, long long); + if ((ber = ber_add_enumerated(ber, i)) == NULL) + goto fail; + break; + case 'i': + i = va_arg(ap, long long); + if ((ber = ber_add_integer(ber, i)) == NULL) + goto fail; + break; + case 'O': + o = va_arg(ap, struct ber_oid *); + if ((ber = ber_add_oid(ber, o)) == NULL) + goto fail; + break; + case 'o': + s = va_arg(ap, char *); + if ((ber = ber_add_oidstring(ber, s)) == NULL) + goto fail; + break; + case 's': + s = va_arg(ap, char *); + if ((ber = ber_add_string(ber, s)) == NULL) + goto fail; + break; + case 't': + class = va_arg(ap, int); + type = va_arg(ap, unsigned long); + ber_set_header(ber, class, type); + break; + case 'x': + s = va_arg(ap, char *); + len = va_arg(ap, size_t); + if ((ber = ber_add_nstring(ber, s, len)) == NULL) + goto fail; + break; + case '0': + if ((ber = ber_add_null(ber)) == NULL) + goto fail; + break; + case '{': + if ((ber = sub = ber_add_sequence(ber)) == NULL) + goto fail; + break; + case '(': + if ((ber = sub = ber_add_set(ber)) == NULL) + goto fail; + break; + case '}': + case ')': + ber = sub; + break; + case '.': + if ((e = ber_add_eoc(ber)) == NULL) + goto fail; + ber = e; + break; + default: + break; + } + } + va_end(ap); + + return (ber); + fail: + ber_free_elements(ber); + return (NULL); +} + +int +ber_scanf_elements(struct ber_element *ber, char *fmt, ...) +{ +#define _MAX_SEQ 128 + va_list ap; + int *d, level = -1; + unsigned long *t; + long long *i; + void **ptr; + size_t *len, ret = 0, n = strlen(fmt); + char **s; + struct ber_oid *o; + struct ber_element *parent[_MAX_SEQ], **e; + + bzero(parent, sizeof(struct ber_element *) * _MAX_SEQ); + + va_start(ap, fmt); + while (*fmt) { + switch (*fmt++) { + case 'B': + ptr = va_arg(ap, void **); + len = va_arg(ap, size_t *); + if (ber_get_bitstring(ber, ptr, len) == -1) + goto fail; + ret++; + break; + case 'b': + d = va_arg(ap, int *); + if (ber_get_boolean(ber, d) == -1) + goto fail; + ret++; + break; + case 'e': + e = va_arg(ap, struct ber_element **); + *e = ber; + ret++; + continue; + case 'E': + i = va_arg(ap, long long *); + if (ber_get_enumerated(ber, i) == -1) + goto fail; + ret++; + break; + case 'i': + i = va_arg(ap, long long *); + if (ber_get_integer(ber, i) == -1) + goto fail; + ret++; + break; + case 'o': + o = va_arg(ap, struct ber_oid *); + if (ber_get_oid(ber, o) == -1) + goto fail; + ret++; + break; + case 'S': + ret++; + break; + case 's': + s = va_arg(ap, char **); + if (ber_get_string(ber, s) == -1) + goto fail; + ret++; + break; + case 't': + d = va_arg(ap, int *); + t = va_arg(ap, unsigned long *); + *d = ber->be_class; + *t = ber->be_type; + ret++; + continue; + case 'x': + ptr = va_arg(ap, void **); + len = va_arg(ap, size_t *); + if (ber_get_nstring(ber, ptr, len) == -1) + goto fail; + ret++; + break; + case '0': + if (ber->be_encoding != BER_TYPE_NULL) + goto fail; + ret++; + break; + case '.': + if (ber->be_encoding != BER_TYPE_EOC) + goto fail; + ret++; + break; + case '{': + case '(': + if (ber->be_encoding != BER_TYPE_SEQUENCE && + ber->be_encoding != BER_TYPE_SET) + goto fail; + if (ber->be_sub == NULL || level >= _MAX_SEQ-1) + goto fail; + parent[++level] = ber; + ber = ber->be_sub; + ret++; + continue; + case '}': + case ')': + if (parent[level] == NULL) + goto fail; + ber = parent[level--]; + ret++; + continue; + default: + goto fail; + } + + if (ber->be_next == NULL) + continue; + ber = ber->be_next; + } + va_end(ap); + return (ret == n ? 0 : -1); + + fail: + va_end(ap); + return (-1); + +} + +/* + * write ber elements to the socket + * + * params: + * ber holds the socket + * root fully populated element tree + * + * returns: + * >=0 number of bytes written + * -1 on failure and sets errno + */ +int +ber_write_elements(struct ber *ber, struct ber_element *root) +{ + size_t len; + + /* calculate length because only the definite form is required */ + len = ber_calc_len(root); + DPRINTF("write ber element of %zd bytes length\n", len); + + if (ber->br_wbuf != NULL && ber->br_wbuf + len > ber->br_wend) { + free(ber->br_wbuf); + ber->br_wbuf = NULL; + } + if (ber->br_wbuf == NULL) { + if ((ber->br_wbuf = malloc(len)) == NULL) + return -1; + ber->br_wend = ber->br_wbuf + len; + } + + /* reset write pointer */ + ber->br_wptr = ber->br_wbuf; + + if (ber_dump_element(ber, root) == -1) + return -1; + + /* XXX this should be moved to a different function */ + if (ber->fd != -1) + return write(ber->fd, ber->br_wbuf, len); + + return (len); +} + +/* + * read ber elements from the socket + * + * params: + * ber holds the socket and lot more + * root if NULL, build up an element tree from what we receive on + * the wire. If not null, use the specified encoding for the + * elements received. + * + * returns: + * !=NULL, elements read and store in the ber_element tree + * NULL, type mismatch or read error + */ +struct ber_element * +ber_read_elements(struct ber *ber, struct ber_element *elm) +{ + struct ber_element *root = elm; + + if (root == NULL) { + if ((root = ber_get_element(0)) == NULL) + return NULL; + } + + DPRINTF("read ber elements, root %p\n", root); + + if (ber_read_element(ber, root) == -1) { + /* Cleanup if root was allocated by us */ + if (elm == NULL) + ber_free_elements(root); + return NULL; + } + + return root; +} + +void +ber_free_elements(struct ber_element *root) +{ + if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET)) + ber_free_elements(root->be_sub); + if (root->be_next) + ber_free_elements(root->be_next); + if (root->be_free && (root->be_encoding == BER_TYPE_OCTETSTRING || + root->be_encoding == BER_TYPE_BITSTRING || + root->be_encoding == BER_TYPE_OBJECT)) + free(root->be_val); + free(root); +} + +size_t +ber_calc_len(struct ber_element *root) +{ + unsigned long t; + size_t s; + size_t size = 2; /* minimum 1 byte head and 1 byte size */ + + /* calculate the real length of a sequence or set */ + if (root->be_sub && (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET)) + root->be_len = ber_calc_len(root->be_sub); + + /* fix header length for extended types */ + if (root->be_type > BER_TYPE_SINGLE_MAX) + for (t = root->be_type; t > 0; t >>= 7) + size++; + if (root->be_len >= BER_TAG_MORE) + for (s = root->be_len; s > 0; s >>= 8) + size++; + + /* calculate the length of the following elements */ + if (root->be_next) + size += ber_calc_len(root->be_next); + + /* This is an empty element, do not use a minimal size */ + if (root->be_type == BER_TYPE_EOC && root->be_len == 0) + return (0); + + return (root->be_len + size); +} + +/* + * internal functions + */ + +static int +ber_dump_element(struct ber *ber, struct ber_element *root) +{ + unsigned long long l; + int i; + uint8_t u; + + ber_dump_header(ber, root); + + switch (root->be_encoding) { + case BER_TYPE_BOOLEAN: + case BER_TYPE_INTEGER: + case BER_TYPE_ENUMERATED: + l = (unsigned long long)root->be_numeric; + for (i = root->be_len; i > 0; i--) { + u = (l >> ((i - 1) * 8)) & 0xff; + ber_putc(ber, u); + } + break; + case BER_TYPE_BITSTRING: + return -1; + case BER_TYPE_OCTETSTRING: + case BER_TYPE_OBJECT: + ber_write(ber, root->be_val, root->be_len); + break; + case BER_TYPE_NULL: /* no payload */ + case BER_TYPE_EOC: + break; + case BER_TYPE_SEQUENCE: + case BER_TYPE_SET: + if (root->be_sub && ber_dump_element(ber, root->be_sub) == -1) + return -1; + break; + } + + if (root->be_next == NULL) + return 0; + return ber_dump_element(ber, root->be_next); +} + +static void +ber_dump_header(struct ber *ber, struct ber_element *root) +{ + u_char id = 0, t, buf[8]; + unsigned long type; + size_t size; + + /* class universal, type encoding depending on type value */ + /* length encoding */ + if (root->be_type <= BER_TYPE_SINGLE_MAX) { + id = root->be_type | (root->be_class << BER_CLASS_SHIFT); + if (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET) + id |= BER_TYPE_CONSTRUCTED; + + ber_putc(ber, id); + } else { + id = BER_TAG_MASK | (root->be_class << BER_CLASS_SHIFT); + if (root->be_encoding == BER_TYPE_SEQUENCE || + root->be_encoding == BER_TYPE_SET) + id |= BER_TYPE_CONSTRUCTED; + + ber_putc(ber, id); + + for (t = 0, type = root->be_type; type > 0; type >>= 7) + buf[t++] = type & ~BER_TAG_MORE; + + while (t-- > 0) { + if (t > 0) + buf[t] |= BER_TAG_MORE; + ber_putc(ber, buf[t]); + } + } + + if (root->be_len < BER_TAG_MORE) { + /* short form */ + ber_putc(ber, root->be_len); + } else { + for (t = 0, size = root->be_len; size > 0; size >>= 8) + buf[t++] = size & 0xff; + + ber_putc(ber, t | BER_TAG_MORE); + + while (t > 0) + ber_putc(ber, buf[--t]); + } +} + +static void +ber_putc(struct ber *ber, u_char c) +{ + if (ber->br_wptr + 1 <= ber->br_wend) + *ber->br_wptr = c; + ber->br_wptr++; +} + +static void +ber_write(struct ber *ber, void *buf, size_t len) +{ + if (ber->br_wptr + len <= ber->br_wend) + bcopy(buf, ber->br_wptr, len); + ber->br_wptr += len; +} + +/* + * extract a BER encoded tag. There are two types, a short and long form. + */ +static ssize_t +get_id(struct ber *b, unsigned long *tag, int *class, int *cstruct) +{ + u_char u; + size_t i = 0; + unsigned long t = 0; + + if (ber_getc(b, &u) == -1) + return -1; + + *class = (u >> BER_CLASS_SHIFT) & BER_CLASS_MASK; + *cstruct = (u & BER_TYPE_CONSTRUCTED) == BER_TYPE_CONSTRUCTED; + + if ((u & BER_TAG_MASK) != BER_TAG_MASK) { + *tag = u & BER_TAG_MASK; + return 1; + } + + do { + if (ber_getc(b, &u) == -1) + return -1; + t = (t << 7) | (u & ~BER_TAG_MORE); + i++; + } while (u & BER_TAG_MORE); + + if (i > sizeof(unsigned long)) { + errno = ERANGE; + return -1; + } + + *tag = t; + return i + 1; +} + +/* + * extract length of a ber object -- if length is unknown an error is returned. + */ +static ssize_t +get_len(struct ber *b, ssize_t *len) +{ + u_char u, n; + ssize_t s, r; + + if (ber_getc(b, &u) == -1) + return -1; + if ((u & BER_TAG_MORE) == 0) { + /* short form */ + *len = u; + return 1; + } + + n = u & ~BER_TAG_MORE; + if (sizeof(ssize_t) < n) { + errno = ERANGE; + return -1; + } + r = n + 1; + + for (s = 0; n > 0; n--) { + if (ber_getc(b, &u) == -1) + return -1; + s = (s << 8) | u; + } + + if (s < 0) { + /* overflow */ + errno = ERANGE; + return -1; + } + + if (s == 0) { + /* invalid encoding */ + errno = EINVAL; + return -1; + } + + *len = s; + return r; +} + +static ssize_t +ber_read_element(struct ber *ber, struct ber_element *elm) +{ + long long val = 0; + struct ber_element *next; + unsigned long type; + int i, class, cstruct; + ssize_t len, r, totlen = 0; + u_char c; + + if ((r = get_id(ber, &type, &class, &cstruct)) == -1) + return -1; + DPRINTF("ber read got class %d type %lu, %s\n", + class, type, cstruct ? "constructive" : "primitive"); + totlen += r; + if ((r = get_len(ber, &len)) == -1) + return -1; + DPRINTF("ber read element size %zd\n", len); + totlen += r + len; + + elm->be_type = type; + elm->be_len = len; + elm->be_class = class; + + if (elm->be_encoding == 0) { + /* try to figure out the encoding via class, type and cstruct */ + if (cstruct) + elm->be_encoding = BER_TYPE_SEQUENCE; + else if (class == BER_CLASS_UNIVERSAL) + elm->be_encoding = type; + else if (ber->br_application != NULL) { + /* + * Ask the application to map the encoding to a + * universal type. For example, a SMI IpAddress + * type is defined as 4 byte OCTET STRING. + */ + elm->be_encoding = (*ber->br_application)(elm); + } else + /* last resort option */ + elm->be_encoding = BER_TYPE_NULL; + } + + switch (elm->be_encoding) { + case BER_TYPE_EOC: /* End-Of-Content */ + break; + case BER_TYPE_BOOLEAN: + case BER_TYPE_INTEGER: + case BER_TYPE_ENUMERATED: + if (len > (ssize_t)sizeof(long long)) + return -1; + for (i = 0; i < len; i++) { + if (ber_getc(ber, &c) != 1) + return -1; + val <<= 8; + val |= c; + } + + /* sign extend if MSB is set */ + if (val >> ((i - 1) * 8) & 0x80) + val |= ULLONG_MAX << (i * 8); + elm->be_numeric = val; + break; + case BER_TYPE_BITSTRING: + elm->be_val = malloc(len); + if (elm->be_val == NULL) + return -1; + elm->be_free = 1; + elm->be_len = len; + ber_read(ber, elm->be_val, len); + break; + case BER_TYPE_OCTETSTRING: + case BER_TYPE_OBJECT: + elm->be_val = malloc(len + 1); + if (elm->be_val == NULL) + return -1; + elm->be_free = 1; + elm->be_len = len; + ber_read(ber, elm->be_val, len); + ((u_char *)elm->be_val)[len] = '\0'; + break; + case BER_TYPE_NULL: /* no payload */ + if (len != 0) + return -1; + break; + case BER_TYPE_SEQUENCE: + case BER_TYPE_SET: + if (elm->be_sub == NULL) { + if ((elm->be_sub = ber_get_element(0)) == NULL) + return -1; + } + next = elm->be_sub; + while (len > 0) { + r = ber_read_element(ber, next); + if (r == -1) + return -1; + len -= r; + if (len > 0 && next->be_next == NULL) { + if ((next->be_next = ber_get_element(0)) == + NULL) + return -1; + } + next = next->be_next; + } + break; + } + return totlen; +} + +static ssize_t +ber_readbuf(struct ber *b, void *buf, size_t nbytes) +{ + size_t sz; + size_t len; + + if (b->br_rbuf == NULL) + return -1; + + sz = b->br_rend - b->br_rptr; + len = MINIMUM(nbytes, sz); + if (len == 0) { + errno = ECANCELED; + return (-1); /* end of buffer and parser wants more data */ + } + + bcopy(b->br_rptr, buf, len); + b->br_rptr += len; + + return (len); +} + +void +ber_set_readbuf(struct ber *b, void *buf, size_t len) +{ + b->br_rbuf = b->br_rptr = buf; + b->br_rend = (u_int8_t *)buf + len; +} + +ssize_t +ber_get_writebuf(struct ber *b, void **buf) +{ + if (b->br_wbuf == NULL) + return -1; + *buf = b->br_wbuf; + return (b->br_wend - b->br_wbuf); +} + +void +ber_set_application(struct ber *b, unsigned long (*cb)(struct ber_element *)) +{ + b->br_application = cb; +} + +void +ber_free(struct ber *b) +{ + free(b->br_wbuf); +} + +static ssize_t +ber_getc(struct ber *b, u_char *c) +{ + ssize_t r; + /* + * XXX calling read here is wrong in many ways. The most obvious one + * being that we will block till data arrives. + * But for now it is _good enough_ *gulp* + */ + if (b->fd == -1) + r = ber_readbuf(b, c, 1); + else + r = read(b->fd, c, 1); + return r; +} + +static ssize_t +ber_read(struct ber *ber, void *buf, size_t len) +{ + u_char *b = buf; + ssize_t r, remain = len; + + /* + * XXX calling read here is wrong in many ways. The most obvious one + * being that we will block till data arrives. + * But for now it is _good enough_ *gulp* + */ + + while (remain > 0) { + if (ber->fd == -1) + r = ber_readbuf(ber, b, remain); + else + r = read(ber->fd, b, remain); + if (r == -1) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (r == 0) + return (b - (u_char *)buf); + b += r; + remain -= r; + } + return (b - (u_char *)buf); +} diff --git a/usr.sbin/ypldap/ber.h b/usr.sbin/ypldap/ber.h new file mode 100644 index 0000000..eec02d4 --- /dev/null +++ b/usr.sbin/ypldap/ber.h @@ -0,0 +1,129 @@ +/* $OpenBSD: ber.h,v 1.2 2008/12/29 15:48:13 aschrijver Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2007 Reyk Floeter <reyk@vantronix.net> + * Copyright (c) 2006, 2007 Claudio Jeker <claudio@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. + */ + +struct ber_element { + struct ber_element *be_next; + unsigned long be_type; + unsigned long be_encoding; + size_t be_len; + int be_free; + u_int8_t be_class; + union { + struct ber_element *bv_sub; + void *bv_val; + long long bv_numeric; + } be_union; +#define be_sub be_union.bv_sub +#define be_val be_union.bv_val +#define be_numeric be_union.bv_numeric +}; + +struct ber { + int fd; + u_char *br_wbuf; + u_char *br_wptr; + u_char *br_wend; + u_char *br_rbuf; + u_char *br_rptr; + u_char *br_rend; + + unsigned long (*br_application)(struct ber_element *); +}; + +/* well-known ber_element types */ +#define BER_TYPE_DEFAULT ((unsigned long)-1) +#define BER_TYPE_EOC 0 +#define BER_TYPE_BOOLEAN 1 +#define BER_TYPE_INTEGER 2 +#define BER_TYPE_BITSTRING 3 +#define BER_TYPE_OCTETSTRING 4 +#define BER_TYPE_NULL 5 +#define BER_TYPE_OBJECT 6 +#define BER_TYPE_ENUMERATED 10 +#define BER_TYPE_SEQUENCE 16 +#define BER_TYPE_SET 17 + +/* ber classes */ +#define BER_CLASS_UNIVERSAL 0x0 +#define BER_CLASS_UNIV BER_CLASS_UNIVERSAL +#define BER_CLASS_APPLICATION 0x1 +#define BER_CLASS_APP BER_CLASS_APPLICATION +#define BER_CLASS_CONTEXT 0x2 +#define BER_CLASS_PRIVATE 0x3 +#define BER_CLASS_MASK 0x3 + +/* common definitions */ +#define BER_MIN_OID_LEN 2 /* OBJECT */ +#define BER_MAX_OID_LEN 32 /* OBJECT */ + +struct ber_oid { + u_int32_t bo_id[BER_MAX_OID_LEN + 1]; + size_t bo_n; +}; + +__BEGIN_DECLS +struct ber_element *ber_get_element(unsigned long); +void ber_set_header(struct ber_element *, int, + unsigned long); +void ber_link_elements(struct ber_element *, + struct ber_element *); +struct ber_element *ber_unlink_elements(struct ber_element *); +void ber_replace_elements(struct ber_element *, + struct ber_element *); +struct ber_element *ber_add_sequence(struct ber_element *); +struct ber_element *ber_add_set(struct ber_element *); +struct ber_element *ber_add_integer(struct ber_element *, long long); +int ber_get_integer(struct ber_element *, long long *); +struct ber_element *ber_add_enumerated(struct ber_element *, long long); +int ber_get_enumerated(struct ber_element *, long long *); +struct ber_element *ber_add_boolean(struct ber_element *, int); +int ber_get_boolean(struct ber_element *, int *); +struct ber_element *ber_add_string(struct ber_element *, const char *); +struct ber_element *ber_add_nstring(struct ber_element *, const char *, + size_t); +int ber_get_string(struct ber_element *, char **); +int ber_get_nstring(struct ber_element *, void **, + size_t *); +struct ber_element *ber_add_bitstring(struct ber_element *, const void *, + size_t); +int ber_get_bitstring(struct ber_element *, void **, + size_t *); +struct ber_element *ber_add_null(struct ber_element *); +int ber_get_null(struct ber_element *); +struct ber_element *ber_add_eoc(struct ber_element *); +int ber_get_eoc(struct ber_element *); +struct ber_element *ber_add_oid(struct ber_element *, struct ber_oid *); +struct ber_element *ber_add_noid(struct ber_element *, struct ber_oid *, int); +struct ber_element *ber_add_oidstring(struct ber_element *, const char *); +int ber_get_oid(struct ber_element *, struct ber_oid *); +size_t ber_oid2ber(struct ber_oid *, u_int8_t *, size_t); +int ber_string2oid(const char *, struct ber_oid *); +struct ber_element *ber_printf_elements(struct ber_element *, char *, ...); +int ber_scanf_elements(struct ber_element *, char *, ...); +ssize_t ber_get_writebuf(struct ber *, void **); +int ber_write_elements(struct ber *, struct ber_element *); +void ber_set_readbuf(struct ber *, void *, size_t); +struct ber_element *ber_read_elements(struct ber *, struct ber_element *); +void ber_free_elements(struct ber_element *); +size_t ber_calc_len(struct ber_element *); +void ber_set_application(struct ber *, + unsigned long (*)(struct ber_element *)); +void ber_free(struct ber *); +__END_DECLS diff --git a/usr.sbin/ypldap/entries.c b/usr.sbin/ypldap/entries.c new file mode 100644 index 0000000..1adce5a --- /dev/null +++ b/usr.sbin/ypldap/entries.c @@ -0,0 +1,149 @@ +/* $OpenBSD: entries.c,v 1.3 2015/01/16 06:40:22 deraadt Exp $ */ +/* $FreeBSD$ */ +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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 <sys/types.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <unistd.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "ypldap.h" + +void +flatten_entries(struct env *env) +{ + size_t wrlen; + size_t len; + char *linep; + char *endp; + char *tmp; + struct userent *ue; + struct groupent *ge; + + log_debug("flattening trees"); + /* + * This takes all the line pointers in RB elements and + * concatenates them in a single string, to be able to + * implement next element lookup without tree traversal. + * + * An extra octet is alloced to make space for an additional NUL. + */ + wrlen = env->sc_user_line_len; + if ((linep = calloc(1, env->sc_user_line_len + 1)) == NULL) { + /* + * XXX: try allocating a smaller chunk of memory + */ + fatal("out of memory"); + } + endp = linep; + + RB_FOREACH(ue, user_name_tree, env->sc_user_names) { + /* + * we convert the first nul back to a column, + * copy the string and then convert it back to a nul. + */ + ue->ue_line[strlen(ue->ue_line)] = ':'; + log_debug("pushing line: %s", ue->ue_line); + len = strlen(ue->ue_line) + 1; + memcpy(endp, ue->ue_line, len); + endp[strcspn(endp, ":")] = '\0'; + free(ue->ue_line); + ue->ue_line = endp; + endp += len; + wrlen -= len; + + /* + * To save memory strdup(3) the netid_line which originally used + * LINE_WIDTH bytes + */ + tmp = ue->ue_netid_line; + ue->ue_netid_line = strdup(tmp); + if (ue->ue_netid_line == NULL) { + fatal("out of memory"); + } + free(tmp); + } + env->sc_user_lines = linep; + + wrlen = env->sc_group_line_len; + if ((linep = calloc(1, env->sc_group_line_len + 1)) == NULL) { + /* + * XXX: try allocating a smaller chunk of memory + */ + fatal("out of memory"); + } + endp = linep; + RB_FOREACH(ge, group_name_tree, env->sc_group_names) { + /* + * we convert the first nul back to a column, + * copy the string and then convert it back to a nul. + */ + ge->ge_line[strlen(ge->ge_line)] = ':'; + log_debug("pushing line: %s", ge->ge_line); + len = strlen(ge->ge_line) + 1; + memcpy(endp, ge->ge_line, len); + endp[strcspn(endp, ":")] = '\0'; + free(ge->ge_line); + ge->ge_line = endp; + endp += len; + wrlen -= len; + } + env->sc_group_lines = linep; +} + +int +userent_name_cmp(struct userent *ue1, struct userent *ue2) +{ + return (strcmp(ue1->ue_line, ue2->ue_line)); +} + +int +userent_uid_cmp(struct userent *ue1, struct userent *ue2) +{ + return (ue1->ue_uid - ue2->ue_uid); +} + +int +groupent_name_cmp(struct groupent *ge1, struct groupent *ge2) +{ + return (strcmp(ge1->ge_line, ge2->ge_line)); +} + +int +groupent_gid_cmp(struct groupent *ge1, struct groupent *ge2) +{ + return (ge1->ge_gid - ge2->ge_gid); +} + +RB_GENERATE(user_name_tree, userent, ue_name_node, userent_name_cmp); +RB_GENERATE(user_uid_tree, userent, ue_uid_node, userent_uid_cmp); +RB_GENERATE(group_name_tree, groupent, ge_name_node, groupent_name_cmp); +RB_GENERATE(group_gid_tree, groupent, ge_gid_node, groupent_gid_cmp); diff --git a/usr.sbin/ypldap/ldapclient.c b/usr.sbin/ypldap/ldapclient.c new file mode 100644 index 0000000..d522fb4 --- /dev/null +++ b/usr.sbin/ypldap/ldapclient.c @@ -0,0 +1,705 @@ +/* $OpenBSD: ldapclient.c,v 1.31 2014/11/16 23:24:44 tedu Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2008 Alexander Schrijver <aschrijver@openbsd.org> + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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 <sys/types.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <netdb.h> +#include <errno.h> +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <unistd.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "aldap.h" +#include "ypldap.h" + +void client_sig_handler(int, short, void *); +void client_dispatch_dns(int, short, void *); +void client_dispatch_parent(int, short, void *); +void client_shutdown(void); +void client_connect(int, short, void *); +void client_configure(struct env *); +void client_periodic_update(int, short, void *); +int client_build_req(struct idm *, struct idm_req *, struct aldap_message *, + int, int); +int client_search_idm(struct env *, struct idm *, struct aldap *, + char **, char *, int, int, enum imsg_type); +int client_try_idm(struct env *, struct idm *); +int client_addr_init(struct idm *); +int client_addr_free(struct idm *); + +struct aldap *client_aldap_open(struct ypldap_addr *); + +/* + * dummy wrapper to provide aldap_init with its fd's. + */ +struct aldap * +client_aldap_open(struct ypldap_addr *addr) +{ + int fd = -1; + struct ypldap_addr *p; + + for (p = addr; p != NULL; p = p->next) { + char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + struct sockaddr *sa = (struct sockaddr *)&p->ss; + + if (getnameinfo(sa, sa->sa_len, hbuf, sizeof(hbuf), sbuf, + sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV)) + errx(1, "could not get numeric hostname"); + + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + return NULL; + + if (connect(fd, sa, sa->sa_len) == 0) + break; + + warn("connect to %s port %s (%s) failed", hbuf, sbuf, "tcp"); + close(fd); + } + + if (fd == -1) + return NULL; + + return aldap_init(fd); +} + +int +client_addr_init(struct idm *idm) +{ + struct sockaddr_in *sa_in; + struct sockaddr_in6 *sa_in6; + struct ypldap_addr *h; + + for (h = idm->idm_addr; h != NULL; h = h->next) { + switch (h->ss.ss_family) { + case AF_INET: + sa_in = (struct sockaddr_in *)&h->ss; + if (ntohs(sa_in->sin_port) == 0) + sa_in->sin_port = htons(LDAP_PORT); + idm->idm_state = STATE_DNS_DONE; + break; + case AF_INET6: + sa_in6 = (struct sockaddr_in6 *)&h->ss; + if (ntohs(sa_in6->sin6_port) == 0) + sa_in6->sin6_port = htons(LDAP_PORT); + idm->idm_state = STATE_DNS_DONE; + break; + default: + fatalx("king bula sez: wrong AF in client_addr_init"); + /* not reached */ + } + } + + return (0); +} + +int +client_addr_free(struct idm *idm) +{ + struct ypldap_addr *h, *p; + + if (idm->idm_addr == NULL) + return (-1); + + for (h = idm->idm_addr; h != NULL; h = p) { + p = h->next; + free(h); + } + + idm->idm_addr = NULL; + + return (0); +} + +void +client_sig_handler(int sig, short event, void *p) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + client_shutdown(); + break; + default: + fatalx("unexpected signal"); + } +} + +void +client_dispatch_dns(int fd, short events, void *p) +{ + struct imsg imsg; + u_int16_t dlen; + u_char *data; + struct ypldap_addr *h; + int n, wait_cnt = 0; + struct idm *idm; + int shut = 0; + + struct env *env = p; + struct imsgev *iev = env->sc_iev_dns; + struct imsgbuf *ibuf = &iev->ibuf; + + if ((events & (EV_READ | EV_WRITE)) == 0) + fatalx("unknown event"); + + if (events & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) + shut = 1; + } + if (events & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) + shut = 1; + goto done; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("client_dispatch_dns: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_HOST_DNS: + TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) + if (idm->idm_id == imsg.hdr.peerid) + break; + if (idm == NULL) { + log_warnx("IMSG_HOST_DNS with invalid peerID"); + break; + } + if (idm->idm_addr != NULL) { + log_warnx("IMSG_HOST_DNS but addr != NULL!"); + break; + } + + dlen = imsg.hdr.len - IMSG_HEADER_SIZE; + if (dlen == 0) { /* no data -> temp error */ + idm->idm_state = STATE_DNS_TEMPFAIL; + break; + } + + data = (u_char *)imsg.data; + while (dlen >= sizeof(struct sockaddr_storage)) { + if ((h = calloc(1, sizeof(struct ypldap_addr))) == + NULL) + fatal(NULL); + memcpy(&h->ss, data, sizeof(h->ss)); + + if (idm->idm_addr == NULL) + h->next = NULL; + else + h->next = idm->idm_addr; + + idm->idm_addr = h; + + data += sizeof(h->ss); + dlen -= sizeof(h->ss); + } + if (dlen != 0) + fatalx("IMSG_HOST_DNS: dlen != 0"); + + client_addr_init(idm); + + break; + default: + break; + } + imsg_free(&imsg); + } + + TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) { + if (client_try_idm(env, idm) == -1) + idm->idm_state = STATE_LDAP_FAIL; + + if (idm->idm_state < STATE_LDAP_DONE) + wait_cnt++; + } + if (wait_cnt == 0) + imsg_compose_event(env->sc_iev, IMSG_END_UPDATE, 0, 0, -1, + NULL, 0); + +done: + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +client_dispatch_parent(int fd, short events, void *p) +{ + int n; + int shut = 0; + struct imsg imsg; + struct env *env = p; + struct imsgev *iev = env->sc_iev; + struct imsgbuf *ibuf = &iev->ibuf; + + if ((events & (EV_READ | EV_WRITE)) == 0) + fatalx("unknown event"); + + if (events & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) + shut = 1; + } + if (events & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) + shut = 1; + goto done; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("client_dispatch_parent: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_CONF_START: { + struct env params; + + if (env->sc_flags & F_CONFIGURING) { + log_warnx("configuration already in progress"); + break; + } + memcpy(¶ms, imsg.data, sizeof(params)); + log_debug("configuration starting"); + env->sc_flags |= F_CONFIGURING; + purge_config(env); + memcpy(&env->sc_conf_tv, ¶ms.sc_conf_tv, + sizeof(env->sc_conf_tv)); + env->sc_flags |= params.sc_flags; + break; + } + case IMSG_CONF_IDM: { + struct idm *idm; + + if (!(env->sc_flags & F_CONFIGURING)) + break; + if ((idm = calloc(1, sizeof(*idm))) == NULL) + fatal(NULL); + memcpy(idm, imsg.data, sizeof(*idm)); + idm->idm_env = env; + TAILQ_INSERT_TAIL(&env->sc_idms, idm, idm_entry); + break; + } + case IMSG_CONF_END: + env->sc_flags &= ~F_CONFIGURING; + log_debug("applying configuration"); + client_configure(env); + break; + default: + log_debug("client_dispatch_parent: unexpect imsg %d", + imsg.hdr.type); + + break; + } + imsg_free(&imsg); + } + +done: + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +client_shutdown(void) +{ + log_info("ldap client exiting"); + _exit(0); +} + +pid_t +ldapclient(int pipe_main2client[2]) +{ + pid_t pid, dns_pid; + int pipe_dns[2]; + struct passwd *pw; + struct event ev_sigint; + struct event ev_sigterm; + struct env env; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + break; + case 0: + break; + default: + return (pid); + } + + bzero(&env, sizeof(env)); + TAILQ_INIT(&env.sc_idms); + + if ((pw = getpwnam(YPLDAP_USER)) == NULL) + fatal("getpwnam"); + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe_dns) == -1) + fatal("socketpair"); + dns_pid = ypldap_dns(pipe_dns, pw); + close(pipe_dns[1]); + +#ifndef DEBUG + if (chroot(pw->pw_dir) == -1) + fatal("chroot"); + if (chdir("/") == -1) + fatal("chdir"); +#else +#warning disabling chrooting in DEBUG mode +#endif + setproctitle("ldap client"); + ypldap_process = PROC_CLIENT; + +#ifndef DEBUG + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("cannot drop privileges"); +#else +#warning disabling privilege revocation in DEBUG mode +#endif + + event_init(); + signal(SIGPIPE, SIG_IGN); + signal_set(&ev_sigint, SIGINT, client_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, client_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + + close(pipe_main2client[0]); + if ((env.sc_iev = calloc(1, sizeof(*env.sc_iev))) == NULL) + fatal(NULL); + if ((env.sc_iev_dns = calloc(1, sizeof(*env.sc_iev_dns))) == NULL) + fatal(NULL); + + env.sc_iev->events = EV_READ; + env.sc_iev->data = &env; + imsg_init(&env.sc_iev->ibuf, pipe_main2client[1]); + env.sc_iev->handler = client_dispatch_parent; + event_set(&env.sc_iev->ev, env.sc_iev->ibuf.fd, env.sc_iev->events, + env.sc_iev->handler, &env); + event_add(&env.sc_iev->ev, NULL); + + env.sc_iev_dns->events = EV_READ; + env.sc_iev_dns->data = &env; + imsg_init(&env.sc_iev_dns->ibuf, pipe_dns[0]); + env.sc_iev_dns->handler = client_dispatch_dns; + event_set(&env.sc_iev_dns->ev, env.sc_iev_dns->ibuf.fd, + env.sc_iev_dns->events, env.sc_iev_dns->handler, &env); + event_add(&env.sc_iev_dns->ev, NULL); + + event_dispatch(); + client_shutdown(); + + return (0); + +} + +int +client_build_req(struct idm *idm, struct idm_req *ir, struct aldap_message *m, + int min_attr, int max_attr) +{ + char **ldap_attrs; + int i, k; + + bzero(ir, sizeof(*ir)); + for (i = min_attr; i < max_attr; i++) { + if (idm->idm_flags & F_FIXED_ATTR(i)) { + if (strlcat(ir->ir_line, idm->idm_attrs[i], + sizeof(ir->ir_line)) >= sizeof(ir->ir_line)) + /* + * entry yields a line > 1024, trash it. + */ + return (-1); + + if (i == ATTR_UID) { + ir->ir_key.ik_uid = strtonum( + idm->idm_attrs[i], 0, + UID_MAX, NULL); + } else if (i == ATTR_GR_GID) { + ir->ir_key.ik_gid = strtonum( + idm->idm_attrs[i], 0, + GID_MAX, NULL); + } + } else if (idm->idm_list & F_LIST(i)) { + aldap_match_attr(m, idm->idm_attrs[i], &ldap_attrs); + for (k = 0; k >= 0 && ldap_attrs && ldap_attrs[k] != NULL; k++) { + /* XXX: Fail when attributes have illegal characters e.g. ',' */ + if (strlcat(ir->ir_line, ldap_attrs[k], + sizeof(ir->ir_line)) >= sizeof(ir->ir_line)) + continue; + if (ldap_attrs[k+1] != NULL) + if (strlcat(ir->ir_line, ",", + sizeof(ir->ir_line)) + >= sizeof(ir->ir_line)) { + aldap_free_attr(ldap_attrs); + return (-1); + } + } + aldap_free_attr(ldap_attrs); + } else { + if (aldap_match_attr(m, idm->idm_attrs[i], &ldap_attrs) == -1) + return (-1); + if (ldap_attrs[0] == NULL) + return (-1); + if (strlcat(ir->ir_line, ldap_attrs[0], + sizeof(ir->ir_line)) >= sizeof(ir->ir_line)) { + aldap_free_attr(ldap_attrs); + return (-1); + } + if (i == ATTR_UID) { + ir->ir_key.ik_uid = strtonum( + ldap_attrs[0], 0, UID_MAX, NULL); + } else if (i == ATTR_GR_GID) { + ir->ir_key.ik_uid = strtonum( + ldap_attrs[0], 0, GID_MAX, NULL); + } + aldap_free_attr(ldap_attrs); + } + + if (i + 1 != max_attr) + if (strlcat(ir->ir_line, ":", + sizeof(ir->ir_line)) >= sizeof(ir->ir_line)) + return (-1); + } + + return (0); +} + +int +client_search_idm(struct env *env, struct idm *idm, struct aldap *al, + char **attrs, char *filter, int min_attr, int max_attr, + enum imsg_type type) +{ + struct idm_req ir; + struct aldap_message *m; + struct aldap_page_control *pg = NULL; + const char *errstr; + char *dn; + + dn = idm->idm_basedn; + if (type == IMSG_GRP_ENTRY && idm->idm_groupdn[0] != '\0') + dn = idm->idm_groupdn; + + do { + if (aldap_search(al, dn, LDAP_SCOPE_SUBTREE, + filter, attrs, 0, 0, 0, pg) == -1) { + aldap_get_errno(al, &errstr); + log_debug("%s", errstr); + return (-1); + } + + if (pg != NULL) { + aldap_freepage(pg); + pg = NULL; + } + + while ((m = aldap_parse(al)) != NULL) { + if (al->msgid != m->msgid) { + goto fail; + } + + if (m->message_type == LDAP_RES_SEARCH_RESULT) { + if (m->page != NULL && m->page->cookie_len != 0) + pg = m->page; + else + pg = NULL; + + aldap_freemsg(m); + break; + } + + if (m->message_type != LDAP_RES_SEARCH_ENTRY) { + goto fail; + } + + if (client_build_req(idm, &ir, m, min_attr, max_attr) == 0) + imsg_compose_event(env->sc_iev, type, 0, 0, -1, + &ir, sizeof(ir)); + + aldap_freemsg(m); + } + } while (pg != NULL); + + return (0); + +fail: + aldap_freemsg(m); + if (pg != NULL) { + aldap_freepage(pg); + } + + return (-1); +} + +int +client_try_idm(struct env *env, struct idm *idm) +{ + const char *where; + char *attrs[ATTR_MAX+1]; + int i, j; + struct aldap_message *m; + struct aldap *al; + + where = "connect"; + if ((al = client_aldap_open(idm->idm_addr)) == NULL) + return (-1); + + if (idm->idm_flags & F_NEEDAUTH) { + where = "binding"; + if (aldap_bind(al, idm->idm_binddn, idm->idm_bindcred) == -1) + goto bad; + + where = "parsing"; + if ((m = aldap_parse(al)) == NULL) + goto bad; + where = "verifying msgid"; + if (al->msgid != m->msgid) { + aldap_freemsg(m); + goto bad; + } + aldap_freemsg(m); + } + + bzero(attrs, sizeof(attrs)); + for (i = 0, j = 0; i < ATTR_MAX; i++) { + if (idm->idm_flags & F_FIXED_ATTR(i)) + continue; + attrs[j++] = idm->idm_attrs[i]; + } + attrs[j] = NULL; + + /* + * build password line. + */ + where = "search"; + log_debug("searching password entries"); + if (client_search_idm(env, idm, al, attrs, + idm->idm_filters[FILTER_USER], 0, ATTR_MAX, IMSG_PW_ENTRY) == -1) + goto bad; + + bzero(attrs, sizeof(attrs)); + for (i = ATTR_GR_MIN, j = 0; i < ATTR_GR_MAX; i++) { + if (idm->idm_flags & F_FIXED_ATTR(i)) + continue; + attrs[j++] = idm->idm_attrs[i]; + } + attrs[j] = NULL; + + /* + * build group line. + */ + where = "search"; + log_debug("searching group entries"); + if (client_search_idm(env, idm, al, attrs, + idm->idm_filters[FILTER_GROUP], ATTR_GR_MIN, ATTR_GR_MAX, + IMSG_GRP_ENTRY) == -1) + goto bad; + + aldap_close(al); + + idm->idm_state = STATE_LDAP_DONE; + + return (0); +bad: + aldap_close(al); + log_debug("directory %s errored out in %s", idm->idm_name, where); + return (-1); +} + +void +client_periodic_update(int fd, short event, void *p) +{ + struct env *env = p; + + struct idm *idm; + int fail_cnt = 0; + + /* If LDAP isn't finished, notify the master process to trash the + * update. */ + TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) { + if (idm->idm_state < STATE_LDAP_DONE) + fail_cnt++; + + idm->idm_state = STATE_NONE; + + client_addr_free(idm); + } + if (fail_cnt > 0) { + log_debug("trash the update"); + imsg_compose_event(env->sc_iev, IMSG_TRASH_UPDATE, 0, 0, -1, + NULL, 0); + } + + client_configure(env); +} + +void +client_configure(struct env *env) +{ + struct timeval tv; + struct idm *idm; + u_int16_t dlen; + + log_debug("connecting to directories"); + + imsg_compose_event(env->sc_iev, IMSG_START_UPDATE, 0, 0, -1, NULL, 0); + + /* Start the DNS lookups */ + TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) { + dlen = strlen(idm->idm_name) + 1; + imsg_compose_event(env->sc_iev_dns, IMSG_HOST_DNS, idm->idm_id, + 0, -1, idm->idm_name, dlen); + } + + tv.tv_sec = env->sc_conf_tv.tv_sec; + tv.tv_usec = env->sc_conf_tv.tv_usec; + evtimer_set(&env->sc_conf_ev, client_periodic_update, env); + evtimer_add(&env->sc_conf_ev, &tv); +} diff --git a/usr.sbin/ypldap/log.c b/usr.sbin/ypldap/log.c new file mode 100644 index 0000000..7fec6f7 --- /dev/null +++ b/usr.sbin/ypldap/log.c @@ -0,0 +1,162 @@ +/* $OpenBSD: log.c,v 1.1 2008/06/26 15:10:01 pyr Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2003, 2004 Henning Brauer <henning@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 MIND, 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 <sys/types.h> + +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <time.h> + +void log_init(int); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +__dead2 void fatal(const char *); +__dead2 void fatalx(const char *); + +int debug; + +void vlog(int, const char *, va_list); +void logit(int, const char *, ...); + +void +log_init(int n_debug) +{ + extern char *__progname; + + debug = n_debug; + + if (!debug) + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + + tzset(); +} + +void +logit(int pri, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vlog(pri, fmt, ap); + va_end(ap); +} + +void +vlog(int pri, const char *fmt, va_list ap) +{ + char *nfmt; + + if (debug) { + /* best effort in out of mem situations */ + if (asprintf(&nfmt, "%s\n", fmt) == -1) { + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } else { + vfprintf(stderr, nfmt, ap); + free(nfmt); + } + fflush(stderr); + } else + vsyslog(pri, fmt, ap); +} + + +void +log_warn(const char *emsg, ...) +{ + char *nfmt; + va_list ap; + + /* best effort to even work in out of memory situations */ + if (emsg == NULL) + logit(LOG_CRIT, "%s", strerror(errno)); + else { + va_start(ap, emsg); + + if (asprintf(&nfmt, "%s: %s", emsg, strerror(errno)) == -1) { + /* we tried it... */ + vlog(LOG_CRIT, emsg, ap); + logit(LOG_CRIT, "%s", strerror(errno)); + } else { + vlog(LOG_CRIT, nfmt, ap); + free(nfmt); + } + va_end(ap); + } +} + +void +log_warnx(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_CRIT, emsg, ap); + va_end(ap); +} + +void +log_info(const char *emsg, ...) +{ + va_list ap; + + va_start(ap, emsg); + vlog(LOG_INFO, emsg, ap); + va_end(ap); +} + +void +log_debug(const char *emsg, ...) +{ + va_list ap; + + if (debug > 1) { + va_start(ap, emsg); + vlog(LOG_DEBUG, emsg, ap); + va_end(ap); + } +} + +void +fatal(const char *emsg) +{ + if (emsg == NULL) + logit(LOG_CRIT, "fatal: %s", strerror(errno)); + else + if (errno) + logit(LOG_CRIT, "fatal: %s: %s", + emsg, strerror(errno)); + else + logit(LOG_CRIT, "fatal: %s", emsg); + + exit(1); +} + +void +fatalx(const char *emsg) +{ + errno = 0; + fatal(emsg); +} diff --git a/usr.sbin/ypldap/parse.y b/usr.sbin/ypldap/parse.y new file mode 100644 index 0000000..15d3ff2 --- /dev/null +++ b/usr.sbin/ypldap/parse.y @@ -0,0 +1,838 @@ +/* $OpenBSD: parse.y,v 1.18 2015/01/16 06:40:22 deraadt Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2007, 2008 Reyk Floeter <reyk@openbsd.org> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> + * Copyright (c) 2001 Markus Friedl. All rights reserved. + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. + * Copyright (c) 2001 Theo de Raadt. All rights reserved. + * + * 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 <sys/types.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <limits.h> +#include <netdb.h> +#include <pwd.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <unistd.h> + +#include "ypldap.h" + +TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); +static struct file { + TAILQ_ENTRY(file) entry; + FILE *stream; + char *name; + int lineno; + int errors; +} *file, *topfile; +struct file *pushfile(const char *, int); +int popfile(void); +int check_file_secrecy(int, const char *); +int yyparse(void); +int yylex(void); +int yyerror(const char *, ...) + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(int); +int lungetc(int); +int findeol(void); + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; +int symset(const char *, const char *, int); +char *symget(const char *); + +struct env *conf = NULL; +struct idm *idm = NULL; +static int errors = 0; + +typedef struct { + union { + int64_t number; + char *string; + } v; + int lineno; +} YYSTYPE; + +%} + +%token SERVER FILTER ATTRIBUTE BASEDN BINDDN GROUPDN BINDCRED MAPS CHANGE DOMAIN PROVIDE +%token USER GROUP TO EXPIRE HOME SHELL GECOS UID GID INTERVAL +%token PASSWD NAME FIXED LIST GROUPNAME GROUPPASSWD GROUPGID MAP +%token INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS +%token <v.string> STRING +%token <v.number> NUMBER +%type <v.number> opcode attribute +%type <v.string> port + +%% + +grammar : /* empty */ + | grammar '\n' + | grammar include '\n' + | grammar varset '\n' + | grammar directory '\n' + | grammar main '\n' + | grammar error '\n' { file->errors++; } + ; + +nl : '\n' optnl + ; + +optnl : '\n' optnl + | /* empty */ + ; + + +include : INCLUDE STRING { + struct file *nfile; + + if ((nfile = pushfile($2, 0)) == NULL) { + yyerror("failed to include file %s", $2); + free($2); + YYERROR; + } + free($2); + + file = nfile; + lungetc('\n'); + } + ; + +varset : STRING '=' STRING { + if (symset($1, $3, 0) == -1) + fatal("cannot store variable"); + free($1); + free($3); + } + ; + +port : /* empty */ { $$ = NULL; } + | PORT STRING { $$ = $2; } + ; + +opcode : GROUP { $$ = 0; } + | PASSWD { $$ = 1; } + ; + + +attribute : NAME { $$ = 0; } + | PASSWD { $$ = 1; } + | UID { $$ = 2; } + | GID { $$ = 3; } + | CLASS { $$ = 4; } + | CHANGE { $$ = 5; } + | EXPIRE { $$ = 6; } + | GECOS { $$ = 7; } + | HOME { $$ = 8; } + | SHELL { $$ = 9; } + | GROUPNAME { $$ = 10; } + | GROUPPASSWD { $$ = 11; } + | GROUPGID { $$ = 12; } + | GROUPMEMBERS { $$ = 13; } + ; + +diropt : BINDDN STRING { + idm->idm_flags |= F_NEEDAUTH; + if (strlcpy(idm->idm_binddn, $2, + sizeof(idm->idm_binddn)) >= + sizeof(idm->idm_binddn)) { + yyerror("directory binddn truncated"); + free($2); + YYERROR; + } + free($2); + } + | BINDCRED STRING { + idm->idm_flags |= F_NEEDAUTH; + if (strlcpy(idm->idm_bindcred, $2, + sizeof(idm->idm_bindcred)) >= + sizeof(idm->idm_bindcred)) { + yyerror("directory bindcred truncated"); + free($2); + YYERROR; + } + free($2); + } + | BASEDN STRING { + if (strlcpy(idm->idm_basedn, $2, + sizeof(idm->idm_basedn)) >= + sizeof(idm->idm_basedn)) { + yyerror("directory basedn truncated"); + free($2); + YYERROR; + } + free($2); + } + | GROUPDN STRING { + if(strlcpy(idm->idm_groupdn, $2, + sizeof(idm->idm_groupdn)) >= + sizeof(idm->idm_groupdn)) { + yyerror("directory groupdn truncated"); + free($2); + YYERROR; + } + free($2); + } + | opcode FILTER STRING { + if (strlcpy(idm->idm_filters[$1], $3, + sizeof(idm->idm_filters[$1])) >= + sizeof(idm->idm_filters[$1])) { + yyerror("filter truncated"); + free($3); + YYERROR; + } + free($3); + } + | ATTRIBUTE attribute MAPS TO STRING { + if (strlcpy(idm->idm_attrs[$2], $5, + sizeof(idm->idm_attrs[$2])) >= + sizeof(idm->idm_attrs[$2])) { + yyerror("attribute truncated"); + free($5); + YYERROR; + } + free($5); + } + | FIXED ATTRIBUTE attribute STRING { + if (strlcpy(idm->idm_attrs[$3], $4, + sizeof(idm->idm_attrs[$3])) >= + sizeof(idm->idm_attrs[$3])) { + yyerror("attribute truncated"); + free($4); + YYERROR; + } + idm->idm_flags |= F_FIXED_ATTR($3); + free($4); + } + | LIST attribute MAPS TO STRING { + if (strlcpy(idm->idm_attrs[$2], $5, + sizeof(idm->idm_attrs[$2])) >= + sizeof(idm->idm_attrs[$2])) { + yyerror("attribute truncated"); + free($5); + YYERROR; + } + idm->idm_list |= F_LIST($2); + free($5); + } + ; + +directory : DIRECTORY STRING port { + if ((idm = calloc(1, sizeof(*idm))) == NULL) + fatal(NULL); + idm->idm_id = conf->sc_maxid++; + + if (strlcpy(idm->idm_name, $2, + sizeof(idm->idm_name)) >= + sizeof(idm->idm_name)) { + yyerror("attribute truncated"); + free($2); + YYERROR; + } + + free($2); + } '{' optnl diropts '}' { + TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry); + idm = NULL; + } + ; + +main : INTERVAL NUMBER { + conf->sc_conf_tv.tv_sec = $2; + conf->sc_conf_tv.tv_usec = 0; + } + | DOMAIN STRING { + if (strlcpy(conf->sc_domainname, $2, + sizeof(conf->sc_domainname)) >= + sizeof(conf->sc_domainname)) { + yyerror("domainname truncated"); + free($2); + YYERROR; + } + free($2); + } + | PROVIDE MAP STRING { + if (strcmp($3, "passwd.byname") == 0) + conf->sc_flags |= YPMAP_PASSWD_BYNAME; + else if (strcmp($3, "passwd.byuid") == 0) + conf->sc_flags |= YPMAP_PASSWD_BYUID; + else if (strcmp($3, "master.passwd.byname") == 0) + conf->sc_flags |= YPMAP_MASTER_PASSWD_BYNAME; + else if (strcmp($3, "master.passwd.byuid") == 0) + conf->sc_flags |= YPMAP_MASTER_PASSWD_BYUID; + else if (strcmp($3, "group.byname") == 0) + conf->sc_flags |= YPMAP_GROUP_BYNAME; + else if (strcmp($3, "group.bygid") == 0) + conf->sc_flags |= YPMAP_GROUP_BYGID; + else if (strcmp($3, "netid.byname") == 0) + conf->sc_flags |= YPMAP_NETID_BYNAME; + else { + yyerror("unsupported map type: %s", $3); + free($3); + YYERROR; + } + free($3); + } + ; + +diropts : diropts diropt nl + | diropt optnl + ; + +%% + +struct keywords { + const char *k_name; + int k_val; +}; + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *msg; + + file->errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + fatalx("yyerror vasprintf"); + va_end(ap); + logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); + free(msg); + return (0); +} + +int +kw_cmp(const void *k, const void *e) +{ + return (strcmp(k, ((const struct keywords *)e)->k_name)); +} + +int +lookup(char *s) +{ + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "attribute", ATTRIBUTE }, + { "basedn", BASEDN }, + { "bindcred", BINDCRED }, + { "binddn", BINDDN }, + { "change", CHANGE }, + { "class", CLASS }, + { "directory", DIRECTORY }, + { "domain", DOMAIN }, + { "expire", EXPIRE }, + { "filter", FILTER }, + { "fixed", FIXED }, + { "gecos", GECOS }, + { "gid", GID }, + { "group", GROUP }, + { "groupdn", GROUPDN }, + { "groupgid", GROUPGID }, + { "groupmembers", GROUPMEMBERS }, + { "groupname", GROUPNAME }, + { "grouppasswd", GROUPPASSWD }, + { "home", HOME }, + { "include", INCLUDE }, + { "interval", INTERVAL }, + { "list", LIST }, + { "map", MAP }, + { "maps", MAPS }, + { "name", NAME }, + { "passwd", PASSWD }, + { "port", PORT }, + { "provide", PROVIDE }, + { "server", SERVER }, + { "shell", SHELL }, + { "to", TO }, + { "uid", UID }, + { "user", USER }, + }; + const struct keywords *p; + + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + if (p) + return (p->k_val); + else + return (STRING); +} + +#define MAXPUSHBACK 128 + +u_char *parsebuf; +int parseindex; +u_char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(int quotec) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + if (quotec) { + if ((c = getc(file->stream)) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (file == topfile || popfile() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = getc(file->stream)) == '\\') { + next = getc(file->stream); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = file->lineno; + file->lineno++; + } + + while (c == EOF) { + if (file == topfile || popfile() == EOF) + return (EOF); + c = getc(file->stream); + } + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + + /* skip to either EOF or the first real EOL */ + while (1) { + if (pushback_index) + c = pushback_buffer[--pushback_index]; + else + c = lgetc(0); + if (c == '\n') { + file->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + u_char buf[8096]; + u_char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = file->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + file->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || c == ' ' || c == '\t') + c = next; + else if (next == '\n') { + file->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + err(1, "yylex: strdup"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc(*--p); + c = *--p; + if (c == '-') + return (c); + } + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + err(1, "yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = file->lineno; + file->lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + log_warn("cannot stat %s", fname); + return (-1); + } + if (st.st_uid != 0 && st.st_uid != getuid()) { + log_warnx("%s: owner not root or current user", fname); + return (-1); + } + if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { + log_warnx("%s: group writable or world read/writable", fname); + return (-1); + } + return (0); +} + +struct file * +pushfile(const char *name, int secret) +{ + struct file *nfile; + + if ((nfile = calloc(1, sizeof(struct file))) == NULL) { + log_warn("malloc"); + return (NULL); + } + if ((nfile->name = strdup(name)) == NULL) { + log_warn("malloc"); + free(nfile); + return (NULL); + } + if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { + log_warn("%s", nfile->name); + free(nfile->name); + free(nfile); + return (NULL); + } else if (secret && + check_file_secrecy(fileno(nfile->stream), nfile->name)) { + fclose(nfile->stream); + free(nfile->name); + free(nfile); + return (NULL); + } + nfile->lineno = 1; + TAILQ_INSERT_TAIL(&files, nfile, entry); + return (nfile); +} + +int +popfile(void) +{ + struct file *prev; + + if ((prev = TAILQ_PREV(file, files, entry)) != NULL) + prev->errors += file->errors; + + TAILQ_REMOVE(&files, file, entry); + fclose(file->stream); + free(file->name); + free(file); + file = prev; + return (file ? 0 : EOF); +} + +int +parse_config(struct env *x_conf, const char *filename, int opts) +{ + struct sym *sym, *next; + + conf = x_conf; + bzero(conf, sizeof(*conf)); + + TAILQ_INIT(&conf->sc_idms); + conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL; + conf->sc_conf_tv.tv_usec = 0; + + errors = 0; + + if ((file = pushfile(filename, 1)) == NULL) { + return (-1); + } + topfile = file; + + /* + * parse configuration + */ + setservent(1); + yyparse(); + endservent(); + errors = file->errors; + popfile(); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entry); + if ((opts & YPLDAP_OPT_VERBOSE) && !sym->used) + fprintf(stderr, "warning: macro '%s' not " + "used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + if (errors) { + return (-1); + } + + return (0); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + return (0); +} + +int +cmdline_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + errx(1, "cmdline_symset: malloc"); + + (void)strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} diff --git a/usr.sbin/ypldap/yp.c b/usr.sbin/ypldap/yp.c new file mode 100644 index 0000000..e461136 --- /dev/null +++ b/usr.sbin/ypldap/yp.c @@ -0,0 +1,652 @@ +/* $OpenBSD: yp.c,v 1.14 2015/02/11 01:26:00 pelikan Exp $ */ +/* $FreeBSD$ */ +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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 <sys/types.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/tree.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <unistd.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include <rpc/rpc.h> +#include <rpc/xdr.h> +#include <rpc/pmap_clnt.h> +#include <rpc/pmap_prot.h> +#include <rpc/pmap_rmt.h> +#include <rpcsvc/yp.h> +#include <rpcsvc/ypclnt.h> + +#include "ypldap.h" + +void yp_dispatch(struct svc_req *, SVCXPRT *); +void yp_disable_events(void); +void yp_fd_event(int, short, void *); +int yp_check(struct svc_req *); +int yp_valid_domain(char *, struct ypresp_val *); +void yp_make_val(struct ypresp_val *, char *, int); +void yp_make_keyval(struct ypresp_key_val *, char *, char *); + +static struct env *env; + +struct yp_event { + TAILQ_ENTRY(yp_event) ye_entry; + struct event ye_event; +}; + +struct yp_data { + SVCXPRT *yp_trans_udp; + SVCXPRT *yp_trans_tcp; + TAILQ_HEAD(, yp_event) yd_events; +}; + +void +yp_disable_events(void) +{ + struct yp_event *ye; + + while ((ye = TAILQ_FIRST(&env->sc_yp->yd_events)) != NULL) { + TAILQ_REMOVE(&env->sc_yp->yd_events, ye, ye_entry); + event_del(&ye->ye_event); + free(ye); + } +} + +void +yp_enable_events(void) +{ + int i; + extern fd_set svc_fdset; + struct yp_event *ye; + + for (i = 0; i < getdtablesize(); i++) { + if (FD_ISSET(i, &svc_fdset)) { + if ((ye = calloc(1, sizeof(*ye))) == NULL) + fatal(NULL); + event_set(&ye->ye_event, i, EV_READ, yp_fd_event, NULL); + event_add(&ye->ye_event, NULL); + TAILQ_INSERT_TAIL(&env->sc_yp->yd_events, ye, ye_entry); + } + } +} + +void +yp_fd_event(int fd, short event, void *p) +{ + svc_getreq_common(fd); + yp_disable_events(); + yp_enable_events(); +} + +void +yp_init(struct env *x_env) +{ + struct yp_data *yp; + + if ((yp = calloc(1, sizeof(*yp))) == NULL) + fatal(NULL); + TAILQ_INIT(&yp->yd_events); + + env = x_env; + env->sc_yp = yp; + + (void)pmap_unset(YPPROG, YPVERS); + + if ((yp->yp_trans_udp = svcudp_create(RPC_ANYSOCK)) == NULL) + fatal("cannot create udp service"); + if ((yp->yp_trans_tcp = svctcp_create(RPC_ANYSOCK, 0, 0)) == NULL) + fatal("cannot create tcp service"); + + if (!svc_register(yp->yp_trans_udp, YPPROG, YPVERS, + yp_dispatch, IPPROTO_UDP)) { + fatal("unable to register (YPPROG, YPVERS, udp)"); + } + if (!svc_register(yp->yp_trans_tcp, YPPROG, YPVERS, + yp_dispatch, IPPROTO_TCP)) { + fatal("unable to register (YPPROG, YPVERS, tcp)"); + } +} + +/* + * lots of inspiration from ypserv by Mats O Jansson + */ +void +yp_dispatch(struct svc_req *req, SVCXPRT *trans) +{ + xdrproc_t xdr_argument; + xdrproc_t xdr_result; + char *result; + char *(*cb)(char *, struct svc_req *); + union { + domainname ypproc_domain_2_arg; + domainname ypproc_domain_nonack_2_arg; + ypreq_key ypproc_match_2_arg; + ypreq_nokey ypproc_first_2_arg; + ypreq_key ypproc_next_2_arg; + ypreq_xfr ypproc_xfr_2_arg; + ypreq_nokey ypproc_all_2_arg; + ypreq_nokey ypproc_master_2_arg; + ypreq_nokey ypproc_order_2_arg; + domainname ypproc_maplist_2_arg; + } argument; + + xdr_argument = (xdrproc_t) xdr_void; + xdr_result = (xdrproc_t) xdr_void; + cb = NULL; + switch (req->rq_proc) { + case YPPROC_NULL: + xdr_argument = (xdrproc_t) xdr_void; + xdr_result = (xdrproc_t) xdr_void; + if (yp_check(req) == -1) + return; + result = NULL; + if (!svc_sendreply(trans, (xdrproc_t) xdr_void, + (void *)&result)) + svcerr_systemerr(trans); + return; + case YPPROC_DOMAIN: + xdr_argument = (xdrproc_t) xdr_domainname; + xdr_result = (xdrproc_t) xdr_bool; + if (yp_check(req) == -1) + return; + cb = (void *)ypproc_domain_2_svc; + break; + case YPPROC_DOMAIN_NONACK: + xdr_argument = (xdrproc_t) xdr_domainname; + xdr_result = (xdrproc_t) xdr_bool; + if (yp_check(req) == -1) + return; + cb = (void *)ypproc_domain_nonack_2_svc; + break; + case YPPROC_MATCH: + xdr_argument = (xdrproc_t) xdr_ypreq_key; + xdr_result = (xdrproc_t) xdr_ypresp_val; + if (yp_check(req) == -1) + return; + cb = (void *)ypproc_match_2_svc; + break; + case YPPROC_FIRST: + xdr_argument = (xdrproc_t) xdr_ypreq_nokey; + xdr_result = (xdrproc_t) xdr_ypresp_key_val; + if (yp_check(req) == -1) + return; + cb = (void *)ypproc_first_2_svc; + break; + case YPPROC_NEXT: + xdr_argument = (xdrproc_t) xdr_ypreq_key; + xdr_result = (xdrproc_t) xdr_ypresp_key_val; + if (yp_check(req) == -1) + return; + cb = (void *)ypproc_next_2_svc; + break; + case YPPROC_XFR: + if (yp_check(req) == -1) + return; + svcerr_noproc(trans); + return; + case YPPROC_CLEAR: + log_debug("ypproc_clear"); + if (yp_check(req) == -1) + return; + svcerr_noproc(trans); + return; + case YPPROC_ALL: + log_debug("ypproc_all"); + if (yp_check(req) == -1) + return; + cb = (void *)ypproc_all_2_svc; + break; + case YPPROC_MASTER: + log_debug("ypproc_master"); + if (yp_check(req) == -1) + return; + cb = (void *)ypproc_master_2_svc; + break; + case YPPROC_ORDER: + log_debug("ypproc_order"); + if (yp_check(req) == -1) + return; + svcerr_noproc(trans); + return; + case YPPROC_MAPLIST: + log_debug("ypproc_maplist"); + if (yp_check(req) == -1) + return; + cb = (void *)ypproc_maplist_2_svc; + break; + default: + svcerr_noproc(trans); + return; + } + (void)memset(&argument, 0, sizeof(argument)); + + if (!svc_getargs(trans, xdr_argument, (caddr_t)&argument)) { + svcerr_decode(trans); + return; + } + result = (*cb)((char *)&argument, req); + if (result != NULL && !svc_sendreply(trans, xdr_result, result)) + svcerr_systemerr(trans); + if (!svc_freeargs(trans, xdr_argument, (caddr_t)&argument)) { + /* + * ypserv does it too. + */ + fatal("unable to free arguments"); + } +} + +int +yp_check(struct svc_req *req) +{ + struct sockaddr_in *caller; + + caller = svc_getcaller(req->rq_xprt); + /* + * We might want to know who we allow here. + */ + return (0); +} + +int +yp_valid_domain(char *domain, struct ypresp_val *res) +{ + if (domain == NULL) { + log_debug("NULL domain !"); + return (-1); + } + if (strcmp(domain, env->sc_domainname) != 0) { + res->stat = YP_NODOM; + return (-1); + } + return (0); +} + +bool_t * +ypproc_domain_2_svc(domainname *arg, struct svc_req *req) +{ + static bool_t res; + + res = (bool_t)1; + if (strcmp(*arg, env->sc_domainname) != 0) + res = (bool_t)0; + return (&res); +} + +bool_t * +ypproc_domain_nonack_2_svc(domainname *arg, struct svc_req *req) +{ + static bool_t res; + + if (strcmp(*arg, env->sc_domainname) != 0) + return NULL; + res = (bool_t)1; + return (&res); +} + +ypresp_val * +ypproc_match_2_svc(ypreq_key *arg, struct svc_req *req) +{ + struct userent ukey; + struct userent *ue; + struct groupent gkey; + struct groupent *ge; + static struct ypresp_val res; + const char *estr; + char *bp, *cp; + char key[YPMAXRECORD+1]; + + log_debug("matching '%.*s' in map %s", arg->key.keydat_len, + arg->key.keydat_val, arg->map); + + if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1) + return (&res); + + if (env->sc_user_names == NULL) { + /* + * tree not ready. + */ + return (NULL); + } + + if (arg->key.keydat_len > YPMAXRECORD) { + log_debug("argument too long"); + return (NULL); + } + bzero(key, sizeof(key)); + (void)strncpy(key, arg->key.keydat_val, arg->key.keydat_len); + + if (strcmp(arg->map, "passwd.byname") == 0 || + strcmp(arg->map, "master.passwd.byname") == 0) { + ukey.ue_line = key; + if ((ue = RB_FIND(user_name_tree, env->sc_user_names, + &ukey)) == NULL) { + res.stat = YP_NOKEY; + return (&res); + } + + yp_make_val(&res, ue->ue_line, 1); + return (&res); + } else if (strcmp(arg->map, "passwd.byuid") == 0 || + strcmp(arg->map, "master.passwd.byuid") == 0) { + ukey.ue_uid = strtonum(key, 0, UID_MAX, &estr); + if (estr) { + res.stat = YP_BADARGS; + return (&res); + } + + if ((ue = RB_FIND(user_uid_tree, &env->sc_user_uids, + &ukey)) == NULL) { + res.stat = YP_NOKEY; + return (&res); + } + + yp_make_val(&res, ue->ue_line, 1); + return (&res); + } else if (strcmp(arg->map, "group.bygid") == 0) { + gkey.ge_gid = strtonum(key, 0, GID_MAX, &estr); + if (estr) { + res.stat = YP_BADARGS; + return (&res); + } + if ((ge = RB_FIND(group_gid_tree, &env->sc_group_gids, + &gkey)) == NULL) { + res.stat = YP_NOKEY; + return (&res); + } + + yp_make_val(&res, ge->ge_line, 1); + return (&res); + } else if (strcmp(arg->map, "group.byname") == 0) { + gkey.ge_line = key; + if ((ge = RB_FIND(group_name_tree, env->sc_group_names, + &gkey)) == NULL) { + res.stat = YP_NOKEY; + return (&res); + } + + yp_make_val(&res, ge->ge_line, 1); + return (&res); + } else if (strcmp(arg->map, "netid.byname") == 0) { + bp = cp = key; + + if (strncmp(bp, "unix.", strlen("unix.")) != 0) { + res.stat = YP_BADARGS; + return (&res); + } + + bp += strlen("unix."); + + if (*bp == '\0') { + res.stat = YP_BADARGS; + return (&res); + } + + if (!(cp = strsep(&bp, "@"))) { + res.stat = YP_BADARGS; + return (&res); + } + + if (strcmp(bp, arg->domain) != 0) { + res.stat = YP_BADARGS; + return (&res); + } + + ukey.ue_uid = strtonum(cp, 0, UID_MAX, &estr); + if (estr) { + res.stat = YP_BADARGS; + return (&res); + } + + if ((ue = RB_FIND(user_uid_tree, &env->sc_user_uids, + &ukey)) == NULL) { + res.stat = YP_NOKEY; + return (&res); + } + + yp_make_val(&res, ue->ue_netid_line, 0); + return (&res); + + } else { + log_debug("unknown map %s", arg->map); + res.stat = YP_NOMAP; + return (&res); + } +} + +ypresp_key_val * +ypproc_first_2_svc(ypreq_nokey *arg, struct svc_req *req) +{ + static struct ypresp_key_val res; + + if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1) + return (&res); + + if (strcmp(arg->map, "passwd.byname") == 0 || + strcmp(arg->map, "master.passwd.byname") == 0) { + if (env->sc_user_lines == NULL) + return (NULL); + + yp_make_keyval(&res, env->sc_user_lines, env->sc_user_lines); + } else if (strcmp(arg->map, "group.byname") == 0) { + if (env->sc_group_lines == NULL) + return (NULL); + + yp_make_keyval(&res, env->sc_group_lines, env->sc_group_lines); + } else { + log_debug("unknown map %s", arg->map); + res.stat = YP_NOMAP; + } + + return (&res); +} + +ypresp_key_val * +ypproc_next_2_svc(ypreq_key *arg, struct svc_req *req) +{ + struct userent ukey; + struct userent *ue; + struct groupent gkey; + struct groupent *ge; + char *line; + static struct ypresp_key_val res; + char key[YPMAXRECORD+1]; + + if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1) + return (&res); + + if (strcmp(arg->map, "passwd.byname") == 0 || + strcmp(arg->map, "master.passwd.byname") == 0) { + bzero(key, sizeof(key)); + (void)strncpy(key, arg->key.keydat_val, + arg->key.keydat_len); + ukey.ue_line = key; + if ((ue = RB_FIND(user_name_tree, env->sc_user_names, + &ukey)) == NULL) { + /* + * canacar's trick: + * the user might have been deleted in between calls + * to next since the tree may be modified by a reload. + * next should still return the next user in + * lexicographical order, hence insert the search key + * and look up the next field, then remove it again. + */ + RB_INSERT(user_name_tree, env->sc_user_names, &ukey); + if ((ue = RB_NEXT(user_name_tree, &env->sc_user_names, + &ukey)) == NULL) { + RB_REMOVE(user_name_tree, env->sc_user_names, + &ukey); + res.stat = YP_NOKEY; + return (&res); + } + RB_REMOVE(user_name_tree, env->sc_user_names, &ukey); + } + line = ue->ue_line + (strlen(ue->ue_line) + 1); + line = line + (strlen(line) + 1); + yp_make_keyval(&res, line, line); + return (&res); + + + } else if (strcmp(arg->map, "group.byname") == 0) { + bzero(key, sizeof(key)); + (void)strncpy(key, arg->key.keydat_val, + arg->key.keydat_len); + + gkey.ge_line = key; + if ((ge = RB_FIND(group_name_tree, env->sc_group_names, + &gkey)) == NULL) { + /* + * canacar's trick reloaded. + */ + RB_INSERT(group_name_tree, env->sc_group_names, &gkey); + if ((ge = RB_NEXT(group_name_tree, &env->sc_group_names, + &gkey)) == NULL) { + RB_REMOVE(group_name_tree, env->sc_group_names, + &gkey); + res.stat = YP_NOKEY; + return (&res); + } + RB_REMOVE(group_name_tree, env->sc_group_names, &gkey); + } + + line = ge->ge_line + (strlen(ge->ge_line) + 1); + line = line + (strlen(line) + 1); + yp_make_keyval(&res, line, line); + return (&res); + } else { + log_debug("unknown map %s", arg->map); + res.stat = YP_NOMAP; + return (&res); + } +} + +ypresp_all * +ypproc_all_2_svc(ypreq_nokey *arg, struct svc_req *req) +{ + static struct ypresp_all res; + + if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1) + return (&res); + + svcerr_auth(req->rq_xprt, AUTH_FAILED); + return (NULL); +} + +ypresp_master * +ypproc_master_2_svc(ypreq_nokey *arg, struct svc_req *req) +{ + static struct ypresp_master res; + + if (yp_valid_domain(arg->domain, (struct ypresp_val *)&res) == -1) + return (&res); + + res.stat = YP_YPERR; + return (&res); +} + +ypresp_maplist * +ypproc_maplist_2_svc(domainname *arg, struct svc_req *req) +{ + size_t i; + static struct { + char *name; + int cond; + } mapnames[] = { + { "passwd.byname", YPMAP_PASSWD_BYNAME }, + { "passwd.byuid", YPMAP_PASSWD_BYUID }, + { "master.passwd.byname", YPMAP_MASTER_PASSWD_BYNAME }, + { "master.passwd.byuid", YPMAP_MASTER_PASSWD_BYUID }, + { "group.byname", YPMAP_GROUP_BYNAME }, + { "group.bygid", YPMAP_GROUP_BYGID }, + { "netid.byname", YPMAP_NETID_BYNAME }, + }; + static ypresp_maplist res; + static struct ypmaplist maps[sizeof(mapnames) / sizeof(mapnames[0])]; + + if (yp_valid_domain(*arg, (struct ypresp_val *)&res) == -1) + return (&res); + + res.stat = YP_TRUE; + res.maps = NULL; + for (i = 0; i < sizeof(mapnames) / sizeof(mapnames[0]); i++) { + if (!(env->sc_flags & mapnames[i].cond)) + continue; + maps[i].map = mapnames[i].name; + maps[i].next = res.maps; + res.maps = &maps[i]; + } + + return (&res); +} + +void +yp_make_val(struct ypresp_val *res, char *line, int replacecolon) +{ + static char buf[LINE_WIDTH]; + + bzero(buf, sizeof(buf)); + + if (replacecolon) + line[strlen(line)] = ':'; + (void)strlcpy(buf, line, sizeof(buf)); + if (replacecolon) + line[strcspn(line, ":")] = '\0'; + log_debug("sending out %s", buf); + + res->stat = YP_TRUE; + res->val.valdat_len = strlen(buf); + res->val.valdat_val = buf; +} + +void +yp_make_keyval(struct ypresp_key_val *res, char *key, char *line) +{ + static char keybuf[YPMAXRECORD+1]; + static char buf[LINE_WIDTH]; + + bzero(keybuf, sizeof(keybuf)); + bzero(buf, sizeof(buf)); + + (void)strlcpy(keybuf, key, sizeof(keybuf)); + res->key.keydat_len = strlen(keybuf); + res->key.keydat_val = keybuf; + + if (*line == '\0') { + res->stat = YP_NOMORE; + return; + } + res->stat = YP_TRUE; + line[strlen(line)] = ':'; + (void)strlcpy(buf, line, sizeof(buf)); + line[strcspn(line, ":")] = '\0'; + log_debug("sending out %s => %s", keybuf, buf); + + res->val.valdat_len = strlen(buf); + res->val.valdat_val = buf; +} diff --git a/usr.sbin/ypldap/ypldap.8 b/usr.sbin/ypldap/ypldap.8 new file mode 100644 index 0000000..2974e24 --- /dev/null +++ b/usr.sbin/ypldap/ypldap.8 @@ -0,0 +1,82 @@ +.\" $OpenBSD: ypldap.8,v 1.10 2015/07/27 17:28:40 sobrado Exp $ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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. +.\" +.Dd $Mdocdate: July 27 2015 $ +.Dt YPLDAP 8 +.Os +.Sh NAME +.Nm ypldap +.Nd YP map server using LDAP backend +.Sh SYNOPSIS +.Nm +.Op Fl dnv +.Op Fl D Ar macro Ns = Ns Ar value +.Op Fl f Ar file +.Sh DESCRIPTION +.Nm +is a daemon providing YP maps using LDAP as a backend. +RFC 2307 or similar LDAP schemas can be tied to the different YP maps. +.Nm +has the same role as +.Xr ypserv 8 +and the two daemons are exclusive. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D Ar macro Ns = Ns Ar value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the configuration file. +.It Fl d +Do not daemonize. +If this option is specified, +.Nm +will run in the foreground and log to +.Em stderr . +.It Fl f Ar file +Specify an alternative configuration file. +.It Fl n +Configtest mode. +Only check the configuration file for validity. +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/etc/ypldap.confXX" -compact +.It Pa /etc/ypldap.conf +Default +.Nm +configuration file. +.El +.Sh SEE ALSO +.Xr ypldap.conf 5 , +.Xr ypbind 8 +.Sh HISTORY +The +.Nm +program first appeared in +.Ox 4.4 . +.Sh AUTHORS +The +.Nm +program was written by +.An Pierre-Yves Ritschard . diff --git a/usr.sbin/ypldap/ypldap.c b/usr.sbin/ypldap/ypldap.c new file mode 100644 index 0000000..7762270 --- /dev/null +++ b/usr.sbin/ypldap/ypldap.c @@ -0,0 +1,651 @@ +/* $OpenBSD: ypldap.c,v 1.16 2015/11/02 10:06:06 jmatthew Exp $ */ +/* $FreeBSD */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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 <sys/types.h> +#include <sys/param.h> +#include <sys/queue.h> +#include <sys/socket.h> +#include <sys/signal.h> +#include <sys/tree.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <err.h> +#include <errno.h> +#include <event.h> +#include <unistd.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> + +#include "ypldap.h" + +__dead2 void usage(void); +int check_child(pid_t, const char *); +void main_sig_handler(int, short, void *); +void main_shutdown(void); +void main_dispatch_client(int, short, void *); +void main_configure_client(struct env *); +void main_init_timer(int, short, void *); +void main_start_update(struct env *); +void main_trash_update(struct env *); +void main_end_update(struct env *); +int main_create_user_groups(struct env *); +void purge_config(struct env *); +void reconfigure(struct env *); + +int pipe_main2client[2]; + +pid_t client_pid = 0; +char *conffile = YPLDAP_CONF_FILE; +int opts = 0; + +void +usage(void) +{ + extern const char *__progname; + + fprintf(stderr, "usage: %s [-dnv] [-D macro=value] [-f file]\n", + __progname); + exit(1); +} + +int +check_child(pid_t pid, const char *pname) +{ + int status; + + if (waitpid(pid, &status, WNOHANG) > 0) { + if (WIFEXITED(status)) { + log_warnx("check_child: lost child %s exited", pname); + return (1); + } + if (WIFSIGNALED(status)) { + log_warnx("check_child: lost child %s terminated; " + "signal %d", pname, WTERMSIG(status)); + return (1); + } + } + return (0); +} + +/* ARGUSED */ +void +main_sig_handler(int sig, short event, void *p) +{ + int die = 0; + + switch (sig) { + case SIGTERM: + case SIGINT: + die = 1; + /* FALLTHROUGH */ + case SIGCHLD: + if (check_child(client_pid, "ldap client")) { + client_pid = 0; + die = 1; + } + if (die) + main_shutdown(); + break; + case SIGHUP: + /* reconfigure */ + break; + default: + fatalx("unexpected signal"); + } +} + +void +main_shutdown(void) +{ + _exit(0); +} + +void +main_start_update(struct env *env) +{ + env->update_trashed = 0; + + log_debug("starting directory update"); + env->sc_user_line_len = 0; + env->sc_group_line_len = 0; + if ((env->sc_user_names_t = calloc(1, + sizeof(*env->sc_user_names_t))) == NULL || + (env->sc_group_names_t = calloc(1, + sizeof(*env->sc_group_names_t))) == NULL) + fatal(NULL); + RB_INIT(env->sc_user_names_t); + RB_INIT(env->sc_group_names_t); +} + +/* + * XXX: Currently this function should only be called when updating is + * finished. A notification should be send to ldapclient that it should stop + * sending new pwd/grp entries before it can be called from different places. + */ +void +main_trash_update(struct env *env) +{ + struct userent *ue; + struct groupent *ge; + + env->update_trashed = 1; + + while ((ue = RB_ROOT(env->sc_user_names_t)) != NULL) { + RB_REMOVE(user_name_tree, + env->sc_user_names_t, ue); + free(ue->ue_line); + free(ue->ue_netid_line); + free(ue); + } + free(env->sc_user_names_t); + env->sc_user_names_t = NULL; + while ((ge = RB_ROOT(env->sc_group_names_t)) + != NULL) { + RB_REMOVE(group_name_tree, + env->sc_group_names_t, ge); + free(ge->ge_line); + free(ge); + } + free(env->sc_group_names_t); + env->sc_group_names_t = NULL; +} + +int +main_create_user_groups(struct env *env) +{ + struct userent *ue; + struct userent ukey; + struct groupent *ge; + gid_t pw_gid; + char *bp, *cp; + char *p; + const char *errstr = NULL; + size_t len; + + RB_FOREACH(ue, user_name_tree, env->sc_user_names_t) { + bp = cp = ue->ue_line; + + /* name */ + bp += strlen(bp) + 1; + + /* password */ + bp += strcspn(bp, ":") + 1; + + /* uid */ + bp += strcspn(bp, ":") + 1; + + /* gid */ + bp[strcspn(bp, ":")] = '\0'; + + pw_gid = (gid_t)strtonum(bp, 0, GID_MAX, &errstr); + if (errstr) { + log_warnx("main: failed to parse gid for uid: %d\n", ue->ue_uid); + return (-1); + } + + /* bring gid column back to its proper state */ + bp[strlen(bp)] = ':'; + + if ((ue->ue_netid_line = calloc(1, LINE_WIDTH)) == NULL) { + return (-1); + } + + if (snprintf(ue->ue_netid_line, LINE_WIDTH-1, "%d:%d", ue->ue_uid, pw_gid) >= LINE_WIDTH) { + + return (-1); + } + + ue->ue_gid = pw_gid; + } + + RB_FOREACH(ge, group_name_tree, env->sc_group_names_t) { + bp = cp = ge->ge_line; + + /* name */ + bp += strlen(bp) + 1; + + /* password */ + bp += strcspn(bp, ":") + 1; + + /* gid */ + bp += strcspn(bp, ":") + 1; + + cp = bp; + if (*bp == '\0') + continue; + bp = cp; + for (;;) { + if (!(cp = strsep(&bp, ","))) + break; + ukey.ue_line = cp; + if ((ue = RB_FIND(user_name_tree, env->sc_user_names_t, + &ukey)) == NULL) { + /* User not found */ + log_warnx("main: user: %s is referenced as a " + "group member, but can't be found in the " + "users map.\n", ukey.ue_line); + if (bp != NULL) + *(bp-1) = ','; + continue; + } + if (bp != NULL) + *(bp-1) = ','; + + /* Make sure the new group doesn't equal to the main gid */ + if (ge->ge_gid == ue->ue_gid) + continue; + + len = strlen(ue->ue_netid_line); + p = ue->ue_netid_line + len; + + if ((snprintf(p, LINE_WIDTH-len-1, ",%d", + ge->ge_gid)) >= (int)(LINE_WIDTH-len)) { + return (-1); + } + } + } + + return (0); +} + +void +main_end_update(struct env *env) +{ + struct userent *ue; + struct groupent *ge; + + if (env->update_trashed) + return; + + log_debug("updates are over, cleaning up trees now"); + + if (main_create_user_groups(env) == -1) { + main_trash_update(env); + return; + } + + if (env->sc_user_names == NULL) { + env->sc_user_names = env->sc_user_names_t; + env->sc_user_lines = NULL; + env->sc_user_names_t = NULL; + + env->sc_group_names = env->sc_group_names_t; + env->sc_group_lines = NULL; + env->sc_group_names_t = NULL; + + flatten_entries(env); + goto make_uids; + } + + /* + * clean previous tree. + */ + while ((ue = RB_ROOT(env->sc_user_names)) != NULL) { + RB_REMOVE(user_name_tree, env->sc_user_names, + ue); + free(ue->ue_netid_line); + free(ue); + } + free(env->sc_user_names); + free(env->sc_user_lines); + + env->sc_user_names = env->sc_user_names_t; + env->sc_user_lines = NULL; + env->sc_user_names_t = NULL; + + while ((ge = RB_ROOT(env->sc_group_names)) != NULL) { + RB_REMOVE(group_name_tree, + env->sc_group_names, ge); + free(ge); + } + free(env->sc_group_names); + free(env->sc_group_lines); + + env->sc_group_names = env->sc_group_names_t; + env->sc_group_lines = NULL; + env->sc_group_names_t = NULL; + + + flatten_entries(env); + + /* + * trees are flat now. build up uid, gid and netid trees. + */ + +make_uids: + RB_INIT(&env->sc_user_uids); + RB_INIT(&env->sc_group_gids); + RB_FOREACH(ue, user_name_tree, env->sc_user_names) + RB_INSERT(user_uid_tree, + &env->sc_user_uids, ue); + RB_FOREACH(ge, group_name_tree, env->sc_group_names) + RB_INSERT(group_gid_tree, + &env->sc_group_gids, ge); + +} + +void +main_dispatch_client(int fd, short events, void *p) +{ + int n; + int shut = 0; + struct env *env = p; + struct imsgev *iev = env->sc_iev; + struct imsgbuf *ibuf = &iev->ibuf; + struct idm_req ir; + struct imsg imsg; + + if ((events & (EV_READ | EV_WRITE)) == 0) + fatalx("unknown event"); + + if (events & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) + shut = 1; + } + if (events & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) + shut = 1; + goto done; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("main_dispatch_client: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_START_UPDATE: + main_start_update(env); + break; + case IMSG_PW_ENTRY: { + struct userent *ue; + size_t len; + + if (env->update_trashed) + break; + + (void)memcpy(&ir, imsg.data, sizeof(ir)); + if ((ue = calloc(1, sizeof(*ue))) == NULL || + (ue->ue_line = strdup(ir.ir_line)) == NULL) { + /* + * should cancel tree update instead. + */ + fatal("out of memory"); + } + ue->ue_uid = ir.ir_key.ik_uid; + len = strlen(ue->ue_line) + 1; + ue->ue_line[strcspn(ue->ue_line, ":")] = '\0'; + if (RB_INSERT(user_name_tree, env->sc_user_names_t, + ue) != NULL) { /* dup */ + free(ue->ue_line); + free(ue); + } else + env->sc_user_line_len += len; + break; + } + case IMSG_GRP_ENTRY: { + struct groupent *ge; + size_t len; + + if (env->update_trashed) + break; + + (void)memcpy(&ir, imsg.data, sizeof(ir)); + if ((ge = calloc(1, sizeof(*ge))) == NULL || + (ge->ge_line = strdup(ir.ir_line)) == NULL) { + /* + * should cancel tree update instead. + */ + fatal("out of memory"); + } + ge->ge_gid = ir.ir_key.ik_gid; + len = strlen(ge->ge_line) + 1; + ge->ge_line[strcspn(ge->ge_line, ":")] = '\0'; + if (RB_INSERT(group_name_tree, env->sc_group_names_t, + ge) != NULL) { /* dup */ + free(ge->ge_line); + free(ge); + } else + env->sc_group_line_len += len; + break; + } + case IMSG_TRASH_UPDATE: + main_trash_update(env); + break; + case IMSG_END_UPDATE: { + main_end_update(env); + break; + } + default: + log_debug("main_dispatch_client: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + +done: + if (!shut) + imsg_event_add(iev); + else { + log_debug("king bula sez: ran into dead pipe"); + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +void +main_configure_client(struct env *env) +{ + struct idm *idm; + struct imsgev *iev = env->sc_iev; + + imsg_compose_event(iev, IMSG_CONF_START, 0, 0, -1, env, sizeof(*env)); + TAILQ_FOREACH(idm, &env->sc_idms, idm_entry) { + imsg_compose_event(iev, IMSG_CONF_IDM, 0, 0, -1, + idm, sizeof(*idm)); + } + imsg_compose_event(iev, IMSG_CONF_END, 0, 0, -1, NULL, 0); +} + +void +main_init_timer(int fd, short event, void *p) +{ + struct env *env = p; + + main_configure_client(env); +} + +void +purge_config(struct env *env) +{ + struct idm *idm; + + while ((idm = TAILQ_FIRST(&env->sc_idms)) != NULL) { + TAILQ_REMOVE(&env->sc_idms, idm, idm_entry); + free(idm); + } +} + +int +main(int argc, char *argv[]) +{ + int c; + int debug; + struct passwd *pw; + struct env env; + struct event ev_sigint; + struct event ev_sigterm; + struct event ev_sigchld; + struct event ev_sighup; + struct event ev_timer; + struct timeval tv; + + debug = 0; + ypldap_process = PROC_MAIN; + + log_init(1); + + while ((c = getopt(argc, argv, "dD:nf:v")) != -1) { + switch (c) { + case 'd': + debug = 2; + break; + case 'D': + if (cmdline_symset(optarg) < 0) + log_warnx("could not parse macro definition %s", + optarg); + break; + case 'n': + debug = 2; + opts |= YPLDAP_OPT_NOACTION; + break; + case 'f': + conffile = optarg; + break; + case 'v': + opts |= YPLDAP_OPT_VERBOSE; + break; + default: + usage(); + } + } + + argc -= optind; + argv += optind; + + if (argc) + usage(); + + RB_INIT(&env.sc_user_uids); + RB_INIT(&env.sc_group_gids); + + if (parse_config(&env, conffile, opts)) + exit(1); + if (opts & YPLDAP_OPT_NOACTION) { + fprintf(stderr, "configuration OK\n"); + exit(0); + } + + if (geteuid()) + errx(1, "need root privileges"); + + log_init(debug); + + if (!debug) { + if (daemon(1, 0) == -1) + err(1, "failed to daemonize"); + } + + log_info("startup%s", (debug > 1)?" [debug mode]":""); + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, PF_UNSPEC, + pipe_main2client) == -1) + fatal("socketpair"); + + client_pid = ldapclient(pipe_main2client); + + setproctitle("parent"); + event_init(); + + signal_set(&ev_sigint, SIGINT, main_sig_handler, &env); + signal_set(&ev_sigterm, SIGTERM, main_sig_handler, &env); + signal_set(&ev_sighup, SIGHUP, main_sig_handler, &env); + signal_set(&ev_sigchld, SIGCHLD, main_sig_handler, &env); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sighup, NULL); + signal_add(&ev_sigchld, NULL); + + close(pipe_main2client[1]); + if ((env.sc_iev = calloc(1, sizeof(*env.sc_iev))) == NULL) + fatal(NULL); + imsg_init(&env.sc_iev->ibuf, pipe_main2client[0]); + env.sc_iev->handler = main_dispatch_client; + + env.sc_iev->events = EV_READ; + env.sc_iev->data = &env; + event_set(&env.sc_iev->ev, env.sc_iev->ibuf.fd, env.sc_iev->events, + env.sc_iev->handler, &env); + event_add(&env.sc_iev->ev, NULL); + + yp_init(&env); + + if ((pw = getpwnam(YPLDAP_USER)) == NULL) + fatal("getpwnam"); + +#ifndef DEBUG + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("cannot drop privileges"); +#else +#warning disabling privilege revocation in debug mode +#endif + + bzero(&tv, sizeof(tv)); + evtimer_set(&ev_timer, main_init_timer, &env); + evtimer_add(&ev_timer, &tv); + + yp_enable_events(); + event_dispatch(); + main_shutdown(); + + return (0); +} + +void +imsg_event_add(struct imsgev *iev) +{ + if (iev->handler == NULL) { + imsg_flush(&iev->ibuf); + return; + } + + iev->events = EV_READ; + if (iev->ibuf.w.queued) + iev->events |= EV_WRITE; + + event_del(&iev->ev); + event_set(&iev->ev, iev->ibuf.fd, iev->events, iev->handler, iev->data); + event_add(&iev->ev, NULL); +} + +int +imsg_compose_event(struct imsgev *iev, u_int16_t type, u_int32_t peerid, + pid_t pid, int fd, void *data, u_int16_t datalen) +{ + int ret; + + if ((ret = imsg_compose(&iev->ibuf, type, peerid, + pid, fd, data, datalen)) != -1) + imsg_event_add(iev); + return (ret); +} diff --git a/usr.sbin/ypldap/ypldap.conf.5 b/usr.sbin/ypldap/ypldap.conf.5 new file mode 100644 index 0000000..5c22b3b --- /dev/null +++ b/usr.sbin/ypldap/ypldap.conf.5 @@ -0,0 +1,167 @@ +.\" $OpenBSD: ypldap.conf.5,v 1.19 2012/04/30 11:28:25 jmatthew Exp $ +.\" $FreeBSD$ +.\" +.\" Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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. +.\" +.Dd $Mdocdate: April 30 2012 $ +.Dt YPLDAP.CONF 5 +.Os +.Sh NAME +.Nm ypldap.conf +.Nd LDAP YP map daemon configuration file +.Sh DESCRIPTION +The +.Xr ypldap 8 +daemon provides YP maps using LDAP as a backend. +.Sh SECTIONS +The +.Nm +config file is divided into three main sections. +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Global Configuration +Global settings for +.Xr ypldap 8 . +.It Sy Directories +LDAP Directory specific parameters. +.El +.Sh MACROS +Much like +.Xr cpp 1 +or +.Xr m4 1 , +macros can be defined that will later be expanded in context. +Macro names must start with a letter, digit, or underscore, +and may contain any of those characters. +Macro names may not be reserved words (for example, +.Ic domain ) . +Macros are not expanded inside quotes. +.Pp +For example: +.Bd -literal -offset indent + +fixed_gecos="Pulled from LDAP" + +fixed attribute gecos $fixed_gecos +.Ed +.Sh GLOBAL CONFIGURATION +Global settings concern the main behaviour of the daemon. +.Pp +.Bl -tag -width Ds -compact +.It domain Ar string +Specify the name of the NIS domain +.Nm +will provide. +.It interval Ar seconds +Specify the interval in seconds at which the whole directory will be pulled +from LDAP. +.It provide map Ar string +Specify a map that should be provided by +.Nm +The currently implemented maps are: passwd.byname, passwd.byuid, +group.byname, group.bygid. +.El +.Sh DIRECTORIES +Directories are used to describe the LDAP schema and help +.Nm +convert LDAP entries to +.Xr passwd 5 , +.Xr master.passwd 5 , +and +.Xr group 5 +lines. +A directory declaration is of the following form: +.Bd -literal -offset indent +directory "some.host" { + # directives +} +.Ed +.Pp +Valid directives for directories are: +.Bl -tag -width Ds +.It Xo +.Ic attribute Ar name Ic maps to Ar string +.Xc +Map the +.Xr passwd 5 , +.Xr master.passwd 5 , +or +.Xr group 5 +attribute to the LDAP attribute name supplied. +.It Ic basedn Ar string +Use the supplied search base as starting point for the directory search. +.It Ic groupdn Ar string +Use the supplied search base as starting point for the directory search for +groups. +If not supplied, the basedn value will be used. +.It Ic bindcred Ar string +Use the supplied credentials for simple authentication against the directory. +.It Ic binddn Ar string +Use the supplied Distinguished Name to bind to the directory. +.It Ic fixed attribute Ar attribute string +Do not retrieve the specified attribute from LDAP but +instead set it unconditionally to the supplied value for +every entry. +.It Ic group filter Ar string +Use the supplied LDAP filter to retrieve group entries. +.It Xo +.Ic list Ar name Ic maps to Ar string +.Xc +Map the +.Xr passwd 5 , +.Xr master.passwd 5 , +or +.Xr group 5 +attribute to the LDAP attribute name supplied. +A list creates a comma separated list of all the LDAP attributes found. +.Pp +Valid attributes are: +.Pp +.Bl -tag -width groupmembers -offset indent -compact +.It Ic name +.It Ic passwd +.It Ic uid +.It Ic gid +.It Ic gecos +.It Ic home +.It Ic shell +.It Ic change +.It Ic expire +.It Ic class +.It Ic groupname +.It Ic grouppasswd +.It Ic groupgid +.It Ic groupmembers +.El +.It Ic passwd filter Ar string +Use the supplied LDAP filter to retrieve password entries. +.El +.Sh FILES +.Bl -tag -width "/etc/ypldap.conf" -compact +.It Pa /etc/ypldap.conf +.Xr ypldap 8 +configuration file. +.El +.Sh SEE ALSO +.Xr ypbind 8 , +.Xr ypldap 8 , +.Xr ypserv 8 +.Sh HISTORY +The +.Nm +file format first appeared in +.Ox 4.4 . diff --git a/usr.sbin/ypldap/ypldap.h b/usr.sbin/ypldap/ypldap.h new file mode 100644 index 0000000..b5e5fc0 --- /dev/null +++ b/usr.sbin/ypldap/ypldap.h @@ -0,0 +1,222 @@ +/* $OpenBSD: ypldap.h,v 1.16 2015/01/16 06:40:22 deraadt Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@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 <imsg.h> + +#define YPLDAP_USER "_ypldap" +#define YPLDAP_CONF_FILE "/etc/ypldap.conf" +#define DEFAULT_INTERVAL 600 +#define LINE_WIDTH 1024 +#define FILTER_WIDTH 128 +#define ATTR_WIDTH 32 + +#define MAX_SERVERS_DNS 8 + +enum imsg_type { + IMSG_NONE, + IMSG_CONF_START, + IMSG_CONF_IDM, + IMSG_CONF_END, + IMSG_START_UPDATE, + IMSG_END_UPDATE, + IMSG_TRASH_UPDATE, + IMSG_PW_ENTRY, + IMSG_GRP_ENTRY, + IMSG_HOST_DNS +}; + +struct ypldap_addr { + struct ypldap_addr *next; + struct sockaddr_storage ss; +}; + +enum { + PROC_MAIN, + PROC_CLIENT +} ypldap_process; + +struct userent { + RB_ENTRY(userent) ue_name_node; + RB_ENTRY(userent) ue_uid_node; + uid_t ue_uid; + char *ue_line; + char *ue_netid_line; + gid_t ue_gid; +}; + +struct groupent { + RB_ENTRY(groupent) ge_name_node; + RB_ENTRY(groupent) ge_gid_node; + gid_t ge_gid; + char *ge_line; +}; + +enum client_state { + STATE_NONE, + STATE_DNS_INPROGRESS, + STATE_DNS_TEMPFAIL, + STATE_DNS_DONE, + STATE_LDAP_FAIL, + STATE_LDAP_DONE +}; + +/* + * beck, djm, dlg: pay attention to the struct name + */ +struct idm { + TAILQ_ENTRY(idm) idm_entry; + u_int32_t idm_id; + char idm_name[MAXHOSTNAMELEN]; +#define F_SSL 0x00100000 +#define F_CONFIGURING 0x00200000 +#define F_NEEDAUTH 0x00400000 +#define F_FIXED_ATTR(n) (1<<n) +#define F_LIST(n) (1<<n) + enum client_state idm_state; + u_int32_t idm_flags; /* lower 20 reserved */ + u_int32_t idm_list; + struct ypldap_addr *idm_addr; + in_port_t idm_port; + char idm_binddn[LINE_WIDTH]; + char idm_bindcred[LINE_WIDTH]; + char idm_basedn[LINE_WIDTH]; + char idm_groupdn[LINE_WIDTH]; +#define FILTER_USER 1 +#define FILTER_GROUP 0 + char idm_filters[2][FILTER_WIDTH]; +#define ATTR_NAME 0 +#define ATTR_PASSWD 1 +#define ATTR_UID 2 +#define ATTR_GID 3 +#define ATTR_CLASS 4 +#define ATTR_CHANGE 5 +#define ATTR_EXPIRE 6 +#define ATTR_GECOS 7 +#define ATTR_DIR 8 +#define ATTR_SHELL 9 +#define ATTR_GR_NAME 10 +#define ATTR_GR_PASSWD 11 +#define ATTR_GR_GID 12 +#define ATTR_GR_MEMBERS 13 +#define ATTR_MAX 10 +#define ATTR_GR_MIN 10 +#define ATTR_GR_MAX 14 + char idm_attrs[14][ATTR_WIDTH]; + struct env *idm_env; + struct event idm_ev; +#ifdef SSL + struct ssl *idm_ssl; +#endif +}; + +struct idm_req { + union { + uid_t ik_uid; + uid_t ik_gid; + } ir_key; + char ir_line[LINE_WIDTH]; +}; + +struct imsgev { + struct imsgbuf ibuf; + void (*handler)(int, short, void *); + struct event ev; + void *data; + short events; +}; + +struct env { +#define YPLDAP_OPT_VERBOSE 0x01 +#define YPLDAP_OPT_NOACTION 0x02 + u_int8_t sc_opts; +#define YPMAP_PASSWD_BYNAME 0x00000001 +#define YPMAP_PASSWD_BYUID 0x00000002 +#define YPMAP_MASTER_PASSWD_BYNAME 0x00000004 +#define YPMAP_MASTER_PASSWD_BYUID 0x00000008 +#define YPMAP_GROUP_BYNAME 0x00000010 +#define YPMAP_GROUP_BYGID 0x00000020 +#define YPMAP_NETID_BYNAME 0x00000040 + u_int32_t sc_flags; + + u_int32_t sc_maxid; + + char sc_domainname[MAXHOSTNAMELEN]; + struct timeval sc_conf_tv; + struct event sc_conf_ev; + TAILQ_HEAD(idm_list, idm) sc_idms; + struct imsgev *sc_iev; + struct imsgev *sc_iev_dns; + + RB_HEAD(user_name_tree,userent) *sc_user_names; + RB_HEAD(user_uid_tree,userent) sc_user_uids; + RB_HEAD(group_name_tree,groupent)*sc_group_names; + RB_HEAD(group_gid_tree,groupent) sc_group_gids; + struct user_name_tree *sc_user_names_t; + struct group_name_tree *sc_group_names_t; + size_t sc_user_line_len; + size_t sc_group_line_len; + char *sc_user_lines; + char *sc_group_lines; + + struct yp_data *sc_yp; + + int update_trashed; +}; + +/* log.c */ +void log_init(int); +void log_warn(const char *, ...); +void log_warnx(const char *, ...); +void log_info(const char *, ...); +void log_debug(const char *, ...); +void logit(int, const char *, ...); +void vlog(int, const char *, va_list); +__dead2 void fatal(const char *); +__dead2 void fatalx(const char *); + +/* parse.y */ +int parse_config(struct env *, const char *, int); +int cmdline_symset(char *); + +/* ldapclient.c */ +pid_t ldapclient(int []); + +/* ypldap.c */ +void purge_config(struct env *); +void imsg_event_add(struct imsgev *); +int imsg_compose_event(struct imsgev *, u_int16_t, u_int32_t, + pid_t, int, void *, u_int16_t); + +/* entries.c */ +void flatten_entries(struct env *); +int userent_name_cmp(struct userent *, struct userent *); +int userent_uid_cmp(struct userent *, struct userent *); +int groupent_name_cmp(struct groupent *, struct groupent *); +int groupent_gid_cmp(struct groupent *, struct groupent *); +RB_PROTOTYPE( user_name_tree, userent, ue_name_node, userent_name_cmp); +RB_PROTOTYPE( user_uid_tree, userent, ue_uid_node, userent_uid_cmp); +RB_PROTOTYPE( group_name_tree, groupent, ge_name_node, groupent_name_cmp); +RB_PROTOTYPE( group_gid_tree, groupent, ge_gid_node, groupent_gid_cmp); + +/* yp.c */ +void yp_init(struct env *); +void yp_enable_events(void); + +/* ypldap_dns.c */ +pid_t ypldap_dns(int[2], struct passwd *); diff --git a/usr.sbin/ypldap/ypldap_dns.c b/usr.sbin/ypldap/ypldap_dns.c new file mode 100644 index 0000000..507253c --- /dev/null +++ b/usr.sbin/ypldap/ypldap_dns.c @@ -0,0 +1,254 @@ +/* $OpenBSD: ypldap_dns.c,v 1.8 2015/01/16 06:40:22 deraadt Exp $ */ +/* $FreeBSD$ */ + +/* + * Copyright (c) 2003-2008 Henning Brauer <henning@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 MIND, 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 <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/tree.h> +#include <sys/queue.h> + +#include <netinet/in.h> +#include <arpa/nameser.h> + +#include <netdb.h> +#include <pwd.h> +#include <errno.h> +#include <event.h> +#include <resolv.h> +#include <poll.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <limits.h> + +#include "ypldap.h" + +volatile sig_atomic_t quit_dns = 0; +struct imsgev *iev_dns; + +void dns_dispatch_imsg(int, short, void *); +void dns_sig_handler(int, short, void *); +void dns_shutdown(void); +int host_dns(const char *s, struct ypldap_addr **hn); + +void +dns_sig_handler(int sig, short event, void *p) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + dns_shutdown(); + break; + default: + fatalx("unexpected signal"); + } +} + +void +dns_shutdown(void) +{ + log_info("dns engine exiting"); + _exit(0); +} + +pid_t +ypldap_dns(int pipe_ntp[2], struct passwd *pw) +{ + pid_t pid; + struct event ev_sigint; + struct event ev_sigterm; + struct event ev_sighup; + struct env env; + + switch (pid = fork()) { + case -1: + fatal("cannot fork"); + break; + case 0: + break; + default: + return (pid); + } + + setproctitle("dns engine"); + close(pipe_ntp[0]); + + if (setgroups(1, &pw->pw_gid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) + fatal("can't drop privileges"); + endservent(); + + event_init(); + signal_set(&ev_sigint, SIGINT, dns_sig_handler, NULL); + signal_set(&ev_sigterm, SIGTERM, dns_sig_handler, NULL); + signal_set(&ev_sighup, SIGHUP, dns_sig_handler, NULL); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal_add(&ev_sighup, NULL); + + if ((env.sc_iev = calloc(1, sizeof(*env.sc_iev))) == NULL) + fatal(NULL); + + env.sc_iev->events = EV_READ; + env.sc_iev->data = &env; + imsg_init(&env.sc_iev->ibuf, pipe_ntp[1]); + env.sc_iev->handler = dns_dispatch_imsg; + event_set(&env.sc_iev->ev, env.sc_iev->ibuf.fd, env.sc_iev->events, + env.sc_iev->handler, &env); + event_add(&env.sc_iev->ev, NULL); + + event_dispatch(); + dns_shutdown(); + + return (0); +} + +void +dns_dispatch_imsg(int fd, short events, void *p) +{ + struct imsg imsg; + int n, cnt; + char *name; + struct ypldap_addr *h, *hn; + struct ibuf *buf; + struct env *env = p; + struct imsgev *iev = env->sc_iev; + struct imsgbuf *ibuf = &iev->ibuf; + int shut = 0; + + if ((events & (EV_READ | EV_WRITE)) == 0) + fatalx("unknown event"); + + if (events & EV_READ) { + if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) + fatal("imsg_read error"); + if (n == 0) + shut = 1; + } + if (events & EV_WRITE) { + if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN) + fatal("msgbuf_write"); + if (n == 0) + shut = 1; + goto done; + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("client_dispatch_imsg: imsg_get error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_HOST_DNS: + name = imsg.data; + if (imsg.hdr.len < 1 + IMSG_HEADER_SIZE) + fatalx("invalid IMSG_HOST_DNS received"); + imsg.hdr.len -= 1 + IMSG_HEADER_SIZE; + if (name[imsg.hdr.len] != '\0' || + strlen(name) != imsg.hdr.len) + fatalx("invalid IMSG_HOST_DNS received"); + if ((cnt = host_dns(name, &hn)) == -1) + break; + buf = imsg_create(ibuf, IMSG_HOST_DNS, + imsg.hdr.peerid, 0, + cnt * sizeof(struct sockaddr_storage)); + if (buf == NULL) + break; + if (cnt > 0) { + h = hn; + while (h != NULL) { + imsg_add(buf, &h->ss, sizeof(h->ss)); + hn = h->next; + free(h); + h = hn; + } + } + + imsg_close(ibuf, buf); + break; + default: + break; + } + imsg_free(&imsg); + } + +done: + if (!shut) + imsg_event_add(iev); + else { + /* this pipe is dead, so remove the event handler */ + event_del(&iev->ev); + event_loopexit(NULL); + } +} + +int +host_dns(const char *s, struct ypldap_addr **hn) +{ + struct addrinfo hints, *res0, *res; + int error, cnt = 0; + struct sockaddr_in *sa_in; + struct sockaddr_in6 *sa_in6; + struct ypldap_addr *h, *hh = NULL; + + bzero(&hints, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; /* DUMMY */ + error = getaddrinfo(s, NULL, &hints, &res0); + if (error == EAI_AGAIN || error == EAI_NONAME) + return (0); + if (error) { + log_warnx("could not parse \"%s\": %s", s, + gai_strerror(error)); + return (-1); + } + + for (res = res0; res && cnt < MAX_SERVERS_DNS; res = res->ai_next) { + if (res->ai_family != AF_INET && + res->ai_family != AF_INET6) + continue; + if ((h = calloc(1, sizeof(struct ypldap_addr))) == NULL) + fatal(NULL); + h->ss.ss_family = res->ai_family; + if (res->ai_family == AF_INET) { + sa_in = (struct sockaddr_in *)&h->ss; + sa_in->sin_len = sizeof(struct sockaddr_in); + sa_in->sin_addr.s_addr = ((struct sockaddr_in *) + res->ai_addr)->sin_addr.s_addr; + } else { + sa_in6 = (struct sockaddr_in6 *)&h->ss; + sa_in6->sin6_len = sizeof(struct sockaddr_in6); + memcpy(&sa_in6->sin6_addr, &((struct sockaddr_in6 *) + res->ai_addr)->sin6_addr, sizeof(struct in6_addr)); + } + + h->next = hh; + hh = h; + cnt++; + } + freeaddrinfo(res0); + + *hn = hh; + return (cnt); +} |