freebsd-nq/usr.sbin/ypldap/ldapclient.c
Don Lewis 9d80a8b09e Fix Coverity CIDs 1340544 Resource leak and 1340543 Use after free
At line 479 of ldapclient.c in client_build_req(), the error return
leaks ldap_attrs (CID 1340544).  It looks like this can happen if
the first utoa() call in aldap_get_stringset() fails.  It looks
like other leaks can happen if other utoa() calls fail since scanning
this array when it is freed stops when the first NULL is encountered.
Fix these problems by not storing NULL in the array when utoa()
fails, and by freeing ret and returning NULL if nothing is stored
in the array.  That way the caller will never see the
ldap_attrs[0] == NULL case, so delete that check.

The ber_printf_element() calls ber_free_elements() on its ber
argument and returns NULL on failure.  When each of its callers
detects failure, they do a goto fail, which then calls ber_free_elements()
with the same pointer (CID 1340543).  Fix is to delete the
ber_free_elements() from ber_printf_element()

Reported by:	Coverity
CID:		1340543, 1340544
Reviewed by:	araujo
Differential Revision:	https://reviews.freebsd.org/D6550
2016-05-25 01:37:25 +00:00

693 lines
16 KiB
C

/* $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_list *);
/*
* dummy wrapper to provide aldap_init with its fd's.
*/
struct aldap *
client_aldap_open(struct ypldap_addr_list *addr)
{
int fd = -1;
struct ypldap_addr *p;
TAILQ_FOREACH(p, addr, 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;
TAILQ_FOREACH(h, &idm->idm_addr, 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;
while (!TAILQ_EMPTY(&idm->idm_addr)) {
h = TAILQ_FIRST(&idm->idm_addr);
TAILQ_REMOVE(&idm->idm_addr, h, next);
free(h);
}
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 (!TAILQ_EMPTY(&idm->idm_addr)) {
log_warnx("IMSG_HOST_DNS but addrs set!");
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(*h))) == NULL)
fatal(NULL);
memcpy(&h->ss, data, sizeof(h->ss));
TAILQ_INSERT_HEAD(&idm->idm_addr, h, next);
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(&params, imsg.data, sizeof(params));
log_debug("configuration starting");
env->sc_flags |= F_CONFIGURING;
purge_config(env);
memcpy(&env->sc_conf_tv, &params.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 (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);
}