File: [local] / src / usr.sbin / ldapd / ldape.c (download)
Revision 1.38, Wed Jan 17 08:28:15 2024 UTC (4 months, 3 weeks ago) by claudio
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD Changes since 1.37: +7 -5 lines
Use imsg_get_fd() and a local variable.
OK florian@
|
/* $OpenBSD: ldape.c,v 1.38 2024/01/17 08:28:15 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 <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "ldapd.h"
#include "log.h"
void ldape_sig_handler(int fd, short why, void *data);
static void ldape_auth_result(struct imsg *imsg);
static void ldape_open_result(struct imsg *imsg);
static void ldape_imsgev(struct imsgev *iev, int code,
struct imsg *imsg);
static void ldape_needfd(struct imsgev *iev);
int ldap_starttls(struct request *req);
void send_ldap_extended_response(struct conn *conn,
int msgid, unsigned int type,
long long result_code,
const char *extended_oid);
struct imsgev *iev_ldapd;
struct control_sock csock;
void
ldape_sig_handler(int sig, short why, void *data)
{
log_debug("ldape: got signal %d", sig);
if (sig == SIGCHLD) {
for (;;) {
pid_t pid;
int status;
pid = waitpid(WAIT_ANY, &status, WNOHANG);
if (pid <= 0)
break;
}
return;
}
event_loopexit(NULL);
}
void
send_ldap_extended_response(struct conn *conn, int msgid, unsigned int type,
long long result_code, const char *extended_oid)
{
ssize_t rc;
struct ber_element *root, *elm;
void *buf;
log_debug("sending response %u with result %lld", type, result_code);
if ((root = ober_add_sequence(NULL)) == NULL)
goto fail;
elm = ober_printf_elements(root, "d{tEss",
msgid, BER_CLASS_APP, type, result_code, "", "");
if (elm == NULL)
goto fail;
if (extended_oid)
if (ober_add_string(elm, extended_oid) == NULL)
goto fail;
ldap_debug_elements(root, type, "sending response on fd %d", conn->fd);
rc = ober_write_elements(&conn->ber, root);
ober_free_elements(root);
if (rc < 0)
log_warn("failed to create ldap result");
else {
ober_get_writebuf(&conn->ber, &buf);
if (bufferevent_write(conn->bev, buf, rc) != 0)
log_warn("failed to send ldap result");
}
return;
fail:
if (root)
ober_free_elements(root);
}
int
ldap_refer(struct request *req, const char *basedn, struct search *search,
struct referrals *refs)
{
struct ber_element *root, *elm, *ref_root = NULL;
struct referral *ref;
long long result_code = LDAP_REFERRAL;
unsigned int type;
ssize_t rc;
void *buf;
char *url, *scope_str = NULL;
if (req->type == LDAP_REQ_SEARCH)
type = LDAP_RES_SEARCH_RESULT;
else
type = req->type + 1;
if (search != NULL) {
if (search->scope != LDAP_SCOPE_SUBTREE)
scope_str = "base";
else
scope_str = "sub";
}
log_debug("sending referral in response %u on msgid %lld",
type, req->msgid);
if ((root = ober_add_sequence(NULL)) == NULL)
goto fail;
if ((elm = ref_root = ober_add_sequence(NULL)) == NULL)
goto fail;
ober_set_header(ref_root, BER_CLASS_CONTEXT, LDAP_REQ_SEARCH);
SLIST_FOREACH(ref, refs, next) {
if (search != NULL)
rc = asprintf(&url, "%s/%s??%s", ref->url, basedn,
scope_str);
else
rc = asprintf(&url, "%s/%s", ref->url, basedn);
if (rc == -1) {
log_warn("asprintf");
goto fail;
}
log_debug("adding referral '%s'", url);
elm = ober_add_string(elm, url);
free(url);
if (elm == NULL)
goto fail;
}
elm = ober_printf_elements(root, "d{tEsse",
req->msgid, BER_CLASS_APP, type, result_code, "", "", ref_root);
if (elm == NULL)
goto fail;
ref_root = NULL;
rc = ober_write_elements(&req->conn->ber, root);
ober_free_elements(root);
if (rc < 0)
log_warn("failed to create ldap result");
else {
ober_get_writebuf(&req->conn->ber, &buf);
if (bufferevent_write(req->conn->bev, buf, rc) != 0)
log_warn("failed to send ldap result");
}
request_free(req);
return LDAP_REFERRAL;
fail:
if (root != NULL)
ober_free_elements(root);
if (ref_root != NULL)
ober_free_elements(ref_root);
request_free(req);
return LDAP_REFERRAL;
}
void
send_ldap_result(struct conn *conn, int msgid, unsigned int type,
long long result_code)
{
send_ldap_extended_response(conn, msgid, type, result_code, NULL);
}
int
ldap_respond(struct request *req, int code)
{
if (code >= 0)
send_ldap_result(req->conn, req->msgid, req->type + 1, code);
request_free(req);
return code;
}
int
ldap_abandon(struct request *req)
{
long long msgid;
struct search *search;
if (ober_scanf_elements(req->op, "i", &msgid) != 0) {
request_free(req);
return -1; /* protocol error, but don't respond */
}
TAILQ_FOREACH(search, &req->conn->searches, next) {
if (search->req->msgid == msgid) {
/* unlinks the search from conn->searches */
search_close(search);
break;
}
}
request_free(req);
return -1;
}
int
ldap_unbind(struct request *req)
{
log_debug("current bind dn = %s",
req->conn->binddn == NULL ? "" : req->conn->binddn);
conn_disconnect(req->conn);
request_free(req);
return -1; /* don't send any response */
}
int
ldap_compare(struct request *req)
{
struct ber_element *entry, *elm, *attr;
struct namespace *ns;
struct referrals *refs;
struct attr_type *at;
char *dn, *aname, *value, *s;
if (ober_scanf_elements(req->op, "{s{ss", &dn, &aname, &value) != 0) {
log_debug("%s: protocol error", __func__);
request_free(req);
return -1;
}
if ((at = lookup_attribute(conf->schema, aname)) == NULL)
return ldap_respond(req, LDAP_UNDEFINED_TYPE);
if ((ns = namespace_for_base(dn)) == NULL) {
refs = namespace_referrals(dn);
if (refs == NULL)
return ldap_respond(req, LDAP_NO_SUCH_OBJECT);
else
return ldap_refer(req, dn, NULL, refs);
}
if ((entry = namespace_get(ns, dn)) == NULL)
return ldap_respond(req, LDAP_NO_SUCH_OBJECT);
if ((attr = ldap_find_attribute(entry, at)) == NULL)
return ldap_respond(req, LDAP_NO_SUCH_ATTRIBUTE);
if ((attr = attr->be_next) == NULL) /* skip attribute name */
return ldap_respond(req, LDAP_OTHER);
for (elm = attr->be_sub; elm != NULL; elm = elm->be_next) {
if (ober_get_string(elm, &s) != 0)
return ldap_respond(req, LDAP_OTHER);
if (strcasecmp(value, s) == 0)
return ldap_respond(req, LDAP_COMPARE_TRUE);
}
return ldap_respond(req, LDAP_COMPARE_FALSE);
}
int
ldap_starttls(struct request *req)
{
if ((req->conn->listener->flags & F_STARTTLS) == 0) {
log_debug("StartTLS not configured for this connection");
return LDAP_OPERATIONS_ERROR;
}
req->conn->s_flags |= F_STARTTLS;
return LDAP_SUCCESS;
}
int
ldap_extended(struct request *req)
{
int i, rc = LDAP_PROTOCOL_ERROR;
char *oid = NULL;
struct {
const char *oid;
int (*fn)(struct request *);
} extended_ops[] = {
{ "1.3.6.1.4.1.1466.20037", ldap_starttls },
{ NULL }
};
if (ober_scanf_elements(req->op, "{s", &oid) != 0)
goto done;
log_debug("got extended operation %s", oid);
req->op = req->op->be_sub->be_next;
for (i = 0; extended_ops[i].oid != NULL; i++) {
if (strcmp(oid, extended_ops[i].oid) == 0) {
rc = extended_ops[i].fn(req);
break;
}
}
if (extended_ops[i].fn == NULL)
log_warnx("unimplemented extended operation %s", oid);
done:
send_ldap_extended_response(req->conn, req->msgid, LDAP_RES_EXTENDED,
rc, oid);
request_free(req);
return 0;
}
void
ldape(int debug, int verbose, char *csockpath)
{
int on = 1;
struct namespace *ns;
struct listener *l;
struct sockaddr_un *sun = NULL;
struct event ev_sigint;
struct event ev_sigterm;
struct event ev_sigchld;
struct event ev_sighup;
struct ssl key;
struct passwd *pw;
char host[128];
mode_t old_umask = 0;
TAILQ_INIT(&conn_list);
ldap_loginit("ldap server", debug, verbose);
setproctitle("ldap server");
event_init();
signal_set(&ev_sigint, SIGINT, ldape_sig_handler, NULL);
signal_set(&ev_sigterm, SIGTERM, ldape_sig_handler, NULL);
signal_set(&ev_sigchld, SIGCHLD, ldape_sig_handler, NULL);
signal_set(&ev_sighup, SIGHUP, ldape_sig_handler, NULL);
signal_add(&ev_sigint, NULL);
signal_add(&ev_sigterm, NULL);
signal_add(&ev_sigchld, NULL);
signal_add(&ev_sighup, NULL);
signal(SIGPIPE, SIG_IGN);
/* Initialize parent imsg events. */
if ((iev_ldapd = calloc(1, sizeof(struct imsgev))) == NULL)
fatal("calloc");
imsgev_init(iev_ldapd, PROC_PARENT_SOCK_FILENO, NULL, ldape_imsgev,
ldape_needfd);
/* Initialize control socket. */
memset(&csock, 0, sizeof(csock));
csock.cs_name = csockpath;
control_init(&csock);
control_listen(&csock);
/* Initialize LDAP listeners.
*/
TAILQ_FOREACH(l, &conf->listeners, entry) {
l->fd = socket(l->ss.ss_family, SOCK_STREAM | SOCK_NONBLOCK,
0);
if (l->fd == -1)
fatal("ldape: socket");
setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (l->ss.ss_family == AF_UNIX) {
sun = (struct sockaddr_un *)&l->ss;
log_info("listening on %s", sun->sun_path);
if (unlink(sun->sun_path) == -1 && errno != ENOENT)
fatal("ldape: unlink");
} else {
print_host(&l->ss, host, sizeof(host));
log_info("listening on %s:%d", host, ntohs(l->port));
}
if (l->ss.ss_family == AF_UNIX) {
old_umask = umask(S_IXUSR|S_IXGRP|S_IXOTH);
}
if (bind(l->fd, (struct sockaddr *)&l->ss, l->ss.ss_len) != 0)
fatal("ldape: bind");
if (l->ss.ss_family == AF_UNIX) {
mode_t mode = S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH;
(void)umask(old_umask);
if (chmod(sun->sun_path, mode) == -1) {
unlink(sun->sun_path);
fatal("ldape: chmod");
}
}
if (listen(l->fd, 20) != 0)
fatal("ldape: listen");
event_set(&l->ev, l->fd, EV_READ, conn_accept, l);
event_add(&l->ev, NULL);
evtimer_set(&l->evt, conn_accept, l);
if (l->flags & F_SSL) {
if (strlcpy(key.ssl_name, l->ssl_cert_name,
sizeof(key.ssl_name)) >= sizeof(key.ssl_name))
fatal("ldape: certificate name truncated");
l->ssl = SPLAY_FIND(ssltree, conf->sc_ssl, &key);
if (l->ssl == NULL)
fatal("ldape: certificate tree corrupted");
l->tls = tls_server();
if (l->tls == NULL)
fatal("ldape: couldn't allocate tls context");
if (tls_configure(l->tls, l->ssl->config)) {
log_warnx("ldape: %s", tls_error(l->tls));
fatalx("ldape: couldn't configure tls");
}
}
}
TAILQ_FOREACH(ns, &conf->namespaces, next) {
if (!namespace_has_referrals(ns) && namespace_open(ns) != 0)
fatal("%s", ns->suffix);
}
if ((pw = getpwnam(LDAPD_USER)) == NULL)
fatal("getpwnam");
if (pw != NULL) {
if (chroot(pw->pw_dir) == -1)
fatal("chroot");
if (chdir("/") == -1)
fatal("chdir(\"/\")");
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");
}
if (pledge("stdio flock inet unix recvfd", NULL) == -1)
fatal("pledge");
log_debug("ldape: entering event loop");
event_dispatch();
while ((ns = TAILQ_FIRST(&conf->namespaces)) != NULL)
namespace_remove(ns);
control_cleanup(&csock);
log_info("ldape: exiting");
exit(0);
}
static void
ldape_imsgev(struct imsgev *iev, int code, struct imsg *imsg)
{
switch (code) {
case IMSGEV_IMSG:
log_debug("%s: got imsg %d on fd %d",
__func__, imsg->hdr.type, iev->ibuf.fd);
switch (imsg->hdr.type) {
case IMSG_LDAPD_AUTH_RESULT:
ldape_auth_result(imsg);
break;
case IMSG_LDAPD_OPEN_RESULT:
ldape_open_result(imsg);
break;
default:
log_debug("%s: unexpected imsg %d",
__func__, imsg->hdr.type);
break;
}
break;
case IMSGEV_EREAD:
case IMSGEV_EWRITE:
case IMSGEV_EIMSG:
fatal("imsgev read/write error");
break;
case IMSGEV_DONE:
event_loopexit(NULL);
break;
}
}
static void
ldape_needfd(struct imsgev *iev)
{
/* Try to close a control connection first */
if (control_close_any(&csock) == 0) {
log_warn("closed a control connection");
return;
}
if (conn_close_any() == 0) {
log_warn("closed a client connection");
return;
}
fatal("unable to free an fd");
}
static void
ldape_auth_result(struct imsg *imsg)
{
struct conn *conn;
struct auth_res *ares = imsg->data;
log_debug("authentication on conn %d/%lld = %d", ares->fd, ares->msgid,
ares->ok);
conn = conn_by_fd(ares->fd);
if (conn->bind_req != NULL && conn->bind_req->msgid == ares->msgid)
ldap_bind_continue(conn, ares->ok);
else
log_warnx("spurious auth result");
}
static void
ldape_open_result(struct imsg *imsg)
{
struct namespace *ns;
struct open_req *oreq = imsg->data;
int fd;
if (imsg->hdr.len != sizeof(*oreq) + IMSG_HEADER_SIZE)
fatal("invalid size of open result");
if (oreq->path[PATH_MAX-1] != '\0')
fatal("bogus path");
fd = imsg_get_fd(imsg);
log_debug("open(%s) returned fd %d", oreq->path, fd);
TAILQ_FOREACH(ns, &conf->namespaces, next) {
if (namespace_has_referrals(ns))
continue;
if (strcmp(oreq->path, ns->data_path) == 0) {
namespace_set_data_fd(ns, fd);
break;
}
if (strcmp(oreq->path, ns->indx_path) == 0) {
namespace_set_indx_fd(ns, fd);
break;
}
}
if (ns == NULL) {
log_warnx("spurious open result");
close(fd);
} else
namespace_queue_schedule(ns, 0);
}