[BACK]Return to conn.c CVS log [TXT][DIR] Up to [local] / src / usr.sbin / ldapd

File: [local] / src / usr.sbin / ldapd / conn.c (download)

Revision 1.21, Mon Jun 26 10:28:12 2023 UTC (11 months, 1 week ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, HEAD
Changes since 1.20: +3 -2 lines

Improve the conn_err() bufferevent error callback. To better report errors.
OK kn@

/*	$OpenBSD: conn.c,v 1.21 2023/06/26 10:28:12 claudio Exp $ */

/*
 * Copyright (c) 2009, 2010 Martin Hedenfalk <martin@bzero.se>
 *
 * 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/queue.h>
#include <sys/types.h>

#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

#include "ldapd.h"
#include "log.h"

int			 conn_dispatch(struct conn *conn);
int			 conn_tls_init(struct conn *);
unsigned int		 ldap_application(struct ber_element *elm);

struct conn_list	 conn_list;

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

void
request_free(struct request *req)
{
	if (req->root != NULL)
		ober_free_elements(req->root);
	free(req);
}

void
conn_close(struct conn *conn)
{
	struct search	*search, *next;
	struct listener *l = conn->listener;

	log_debug("closing connection %d", conn->fd);

	/* Cancel any ongoing searches on this connection. */
	for (search = TAILQ_FIRST(&conn->searches); search; search = next) {
		next = TAILQ_NEXT(search, next);
		search_close(search);
	}

	/* Cancel any queued requests on this connection. */
	namespace_cancel_conn(conn);

	tls_free(conn->tls);

	TAILQ_REMOVE(&conn_list, conn, next);
	ober_free(&conn->ber);
	if (conn->bev != NULL)
		bufferevent_free(conn->bev);
	close(conn->fd);

	/* Some file descriptors are available again. */
	if (evtimer_pending(&l->evt, NULL)) {
		evtimer_del(&l->evt);
		event_add(&l->ev, NULL);
	}

	free(conn->binddn);
	free(conn->pending_binddn);
	free(conn);

	--stats.conns;
}

/* Marks a connection for disconnect. The connection will be closed when
 * any remaining data has been flushed to the socket.
 */
void
conn_disconnect(struct conn *conn)
{
	conn->disconnect = 1;
	bufferevent_enable(conn->bev, EV_WRITE);
}

void
request_dispatch(struct request *req)
{
	unsigned long		 i;
	struct {
		unsigned int	 type;
		int (*fn)(struct request *);
	} requests[] = {
		{ LDAP_REQ_SEARCH,	ldap_search },
		{ LDAP_REQ_BIND,	ldap_bind },
		{ LDAP_REQ_COMPARE,	ldap_compare },
		{ LDAP_REQ_ADD,		ldap_add },
		{ LDAP_REQ_UNBIND_30,	ldap_unbind },
		{ LDAP_REQ_MODIFY,	ldap_modify },
		{ LDAP_REQ_ABANDON_30,	ldap_abandon },
		{ LDAP_REQ_DELETE_30,	ldap_delete },
		{ LDAP_REQ_EXTENDED,	ldap_extended },
		{ 0,			NULL }
	};

	/* RFC4511, section 4.2.1 says we shouldn't process other requests
	 * while binding. A bind operation can, however, be aborted by sending
	 * another bind operation.
	 */
	if (req->conn->bind_req != NULL && req->type != LDAP_REQ_BIND) {
		log_warnx("got request while bind in progress");
		ldap_respond(req, LDAP_SASL_BIND_IN_PROGRESS);
		return;
	}

	for (i = 0; requests[i].fn != NULL; i++) {
		if (requests[i].type == req->type) {
			requests[i].fn(req);
			break;
		}
	}

	if (requests[i].fn == NULL) {
		log_warnx("unhandled request %u (not implemented)", req->type);
		ldap_respond(req, LDAP_PROTOCOL_ERROR);
	}
}

int
conn_dispatch(struct conn *conn)
{
	int			 class;
	struct request		*req;
	u_char			*rptr;

	++stats.requests;

	if ((req = calloc(1, sizeof(*req))) == NULL) {
		log_warn("calloc");
		conn_disconnect(conn);
		return -1;
	}

	req->conn = conn;
	rptr = conn->ber.br_rptr;	/* save where we start reading */

	if ((req->root = ober_read_elements(&conn->ber, NULL)) == NULL) {
		if (errno != ECANCELED) {
			log_warnx("protocol error");
			hexdump(rptr, conn->ber.br_rend - rptr,
			    "failed to parse request from %zi bytes:",
			    conn->ber.br_rend - rptr);
			conn_disconnect(conn);
		}
		request_free(req);
		return -1;
	}
	log_debug("consumed %ld bytes", conn->ber.br_rptr - rptr);

	/* Read message id and request type.
	 */
	if (ober_scanf_elements(req->root, "{ite",
	    &req->msgid, &class, &req->type, &req->op) != 0) {
		log_warnx("protocol error");
		ldap_debug_elements(req->root, -1,
		    "received invalid request on fd %d", conn->fd);
		conn_disconnect(conn);
		request_free(req);
		return -1;
	}

	ldap_debug_elements(req->root, req->type,
	    "received request on fd %d", conn->fd);

	log_debug("got request type %u, id %lld", req->type, req->msgid);
	request_dispatch(req);
	return 0;
}

void
conn_read(struct bufferevent *bev, void *data)
{
	size_t			 nused = 0;
	struct conn		*conn = data;
	struct evbuffer		*input;

	input = EVBUFFER_INPUT(bev);
	ober_set_readbuf(&conn->ber,
	    EVBUFFER_DATA(input), EVBUFFER_LENGTH(input));

	while (conn->ber.br_rend - conn->ber.br_rptr > 0) {
		if (conn_dispatch(conn) == 0)
			nused = conn->ber.br_rptr - conn->ber.br_rbuf;
		else
			break;
	}

	evbuffer_drain(input, nused);
}

void
conn_write(struct bufferevent *bev, void *data)
{
	struct search	*search, *next;
	struct conn	*conn = data;

	/* Continue any ongoing searches.
	 * Note that the search may be unlinked and freed by conn_search.
	 */
	for (search = TAILQ_FIRST(&conn->searches); search; search = next) {
		next = TAILQ_NEXT(search, next);
		conn_search(search);
	}

	if (conn->disconnect)
		conn_close(conn);
	else if (conn->s_flags & F_STARTTLS) {
		conn->s_flags &= ~F_STARTTLS;
		if (conn_tls_init(conn) == -1)
			conn_close(conn);
	}
}

void
conn_err(struct bufferevent *bev, short why, void *data)
{
	struct conn	*conn = data;

	if ((why & EVBUFFER_EOF) == EVBUFFER_EOF)
		log_debug("end-of-file on connection %d", conn->fd);
	else if ((why & EVBUFFER_TIMEOUT) == EVBUFFER_TIMEOUT)
		log_debug("timeout on connection %d", conn->fd);
	else
		log_warn("%s error on connection %d",
		    why & EVBUFFER_WRITE ? "write" : "read", conn->fd);

	conn_close(conn);
}

void
conn_accept(int fd, short event, void *data)
{
	int			 afd;
	socklen_t		 addrlen;
	struct conn		*conn;
	struct listener		*l = data;
	struct sockaddr_storage	 remote_addr;
	char			 host[128];

	event_add(&l->ev, NULL);
	if ((event & EV_TIMEOUT))
		return;

	addrlen = sizeof(remote_addr);
	afd = accept_reserve(fd, (struct sockaddr *)&remote_addr, &addrlen,
	    FD_RESERVE);
	if (afd == -1) {
		/*
		 * Pause accept if we are out of file descriptors, or
		 * libevent will haunt us here too.
		 */
		if (errno == ENFILE || errno == EMFILE) {
			struct timeval evtpause = { 1, 0 };

			event_del(&l->ev);
			evtimer_add(&l->evt, &evtpause);
		} else if (errno != EWOULDBLOCK && errno != EINTR)
			log_warn("conn_accept");
		return;
	}

	if (l->ss.ss_family == AF_UNIX) {
		uid_t		 euid;
		gid_t		 egid;

		if (getpeereid(afd, &euid, &egid) == -1)
			log_warnx("conn_accept: getpeereid");
		else
			log_debug("accepted local connection by uid %d", euid);
	} else {
		print_host(&remote_addr, host, sizeof(host));
		log_debug("accepted connection from %s on fd %d", host, afd);
	}

	if ((conn = calloc(1, sizeof(*conn))) == NULL) {
		log_warn("malloc");
		goto giveup;
	}
	ober_set_application(&conn->ber, ldap_application);
	conn->fd = afd;
	conn->listener = l;

	conn->bev = bufferevent_new(afd, conn_read, conn_write,
	    conn_err, conn);
	if (conn->bev == NULL) {
		log_warn("conn_accept: bufferevent_new");
		free(conn);
		goto giveup;
	}
	bufferevent_enable(conn->bev, EV_READ);
	bufferevent_settimeout(conn->bev, 0, 60);
	if (l->flags & F_LDAPS)
		if (conn_tls_init(conn) == -1) {
			conn_close(conn);
			goto giveup;
		}

	TAILQ_INIT(&conn->searches);
	TAILQ_INSERT_HEAD(&conn_list, conn, next);

	if (l->flags & F_SECURE)
		conn->s_flags |= F_SECURE;

	++stats.conns;
	return;
giveup:
	close(afd);
	/* Some file descriptors are available again. */
	if (evtimer_pending(&l->evt, NULL)) {
		evtimer_del(&l->evt);
		event_add(&l->ev, NULL);
	}
}

struct conn *
conn_by_fd(int fd)
{
	struct conn		*conn;

	TAILQ_FOREACH(conn, &conn_list, next) {
		if (conn->fd == fd)
			return conn;
	}
	return NULL;
}

int
conn_close_any(void)
{
	struct conn		*conn;

	/* Close oldest idle connection */
	TAILQ_FOREACH_REVERSE(conn, &conn_list, conn_list, next) {
		if (namespace_conn_queue_count(conn) == 0) {
			conn_close(conn);
			return 0;
		}
	}

	/* Close oldest connection */
	conn = TAILQ_LAST(&conn_list, conn_list);
	if (conn != NULL) {
		conn_close(conn);
		return 0;
	}

	return -1;
}

int
conn_tls_init(struct conn *conn)
{
	struct listener *l = conn->listener;

	if (!(l->flags & F_SSL))
		return 0;

	log_debug("conn_tls_init: switching to TLS");

	if (tls_accept_socket(l->tls, &conn->tls, conn->fd) < 0) {
		log_debug("tls_accept_socket failed");
		return -1;
	}
	
	conn->s_flags |= F_SECURE;
	buffertls_set(&conn->buftls, conn->bev, conn->tls, conn->fd);
	buffertls_accept(&conn->buftls, conn->fd);
	return 0;
}