diff options
author | Nathaniel McCallum <npmccallum@redhat.com> | 2013-04-11 14:03:25 -0400 |
---|---|---|
committer | Martin Kosek <mkosek@redhat.com> | 2013-05-17 09:30:51 +0200 |
commit | 203754691c28243dd3cf378e98390fc0a455b485 (patch) | |
tree | f1574334a744f2b2b54c90a0eec08a985151447b /daemons/ipa-otpd/query.c | |
parent | 5d51ae50a59466fa2d6d230d7f2879de34210f0c (diff) | |
download | freeipa-203754691c28243dd3cf378e98390fc0a455b485.tar.gz freeipa-203754691c28243dd3cf378e98390fc0a455b485.tar.xz freeipa-203754691c28243dd3cf378e98390fc0a455b485.zip |
Add the krb5/FreeIPA RADIUS companion daemon
This daemon listens for RADIUS packets on a well known
UNIX domain socket. When a packet is received, it queries
LDAP to see if the user is configured for RADIUS authentication.
If so, then the packet is forwarded to the 3rd party RADIUS server.
Otherwise, a bind is attempted against the LDAP server.
https://fedorahosted.org/freeipa/ticket/3366
http://freeipa.org/page/V3/OTP
Diffstat (limited to 'daemons/ipa-otpd/query.c')
-rw-r--r-- | daemons/ipa-otpd/query.c | 253 |
1 files changed, 253 insertions, 0 deletions
diff --git a/daemons/ipa-otpd/query.c b/daemons/ipa-otpd/query.c new file mode 100644 index 000000000..67e2d751d --- /dev/null +++ b/daemons/ipa-otpd/query.c @@ -0,0 +1,253 @@ +/* + * FreeIPA 2FA companion daemon + * + * Authors: Nathaniel McCallum <npmccallum@redhat.com> + * + * Copyright (C) 2013 Nathaniel McCallum, Red Hat + * see file 'COPYING' for use and warranty information + * + * This program is free software you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This file receives requests (from stdio.c) and queries the LDAP server for + * the user's configuration. When the user's configuration is received, it is + * parsed (parse.c). Once the configuration is parsed, the request packet is + * either forwarded to a third-party RADIUS server (forward.c) or authenticated + * directly via an LDAP bind (bind.c) based on the configuration received. + */ + +#define _GNU_SOURCE 1 /* for asprintf() */ +#include "internal.h" +#include <ctype.h> + +#define DEFAULT_TIMEOUT 15 +#define DEFAULT_RETRIES 3 + +static char *user[] = { + "uid", + "ipatokenRadiusUserName", + "ipatokenRadiusConfigLink", + NULL +}; + +static char *radius[] = { + "ipatokenRadiusServer", + "ipatokenRadiusSecret", + "ipatokenRadiusTimeout", + "ipatokenRadiusRetries", + "ipatokenUserMapAttribute", + NULL +}; + +/* Send queued LDAP requests to the server. */ +static void on_query_writable(verto_ctx *vctx, verto_ev *ev) +{ + struct otpd_queue *push = &ctx.stdio.responses; + const krb5_data *princ = NULL; + char *filter = NULL, *attrs[2]; + int i = LDAP_SUCCESS; + struct otpd_queue_item *item; + (void)vctx; + + item = otpd_queue_pop(&ctx.query.requests); + if (item == NULL) { + verto_set_flags(ctx.query.io, VERTO_EV_FLAG_PERSIST | + VERTO_EV_FLAG_IO_ERROR | + VERTO_EV_FLAG_IO_READ); + return; + } + + if (item->user.dn == NULL) { + princ = krad_packet_get_attr(item->req, + krad_attr_name2num("User-Name"), 0); + if (princ == NULL) + goto error; + + otpd_log_req(item->req, "user query start"); + + if (asprintf(&filter, "(&(objectClass=Person)(krbPrincipalName=%*s))", + princ->length, princ->data) < 0) + goto error; + + i = ldap_search_ext(verto_get_private(ev), ctx.query.base, + LDAP_SCOPE_SUBTREE, filter, user, 0, NULL, + NULL, NULL, 1, &item->msgid); + free(filter); + + } else if (item->radius.ipatokenRadiusSecret == NULL) { + otpd_log_req(item->req, "radius query start: %s", + item->user.ipatokenRadiusConfigLink); + + i = ldap_search_ext(verto_get_private(ev), + item->user.ipatokenRadiusConfigLink, + LDAP_SCOPE_BASE, NULL, radius, 0, NULL, + NULL, NULL, 1, &item->msgid); + + } else if (item->radius.ipatokenUserMapAttribute != NULL) { + otpd_log_req(item->req, "username query start: %s", + item->radius.ipatokenUserMapAttribute); + + attrs[0] = item->radius.ipatokenUserMapAttribute; + attrs[1] = NULL; + i = ldap_search_ext(verto_get_private(ev), item->user.dn, + LDAP_SCOPE_BASE, NULL, attrs, 0, NULL, + NULL, NULL, 1, &item->msgid); + } + + if (i == LDAP_SUCCESS) { + item->sent++; + push = &ctx.query.responses; + } + +error: + otpd_queue_push(push, item); +} + +/* Read LDAP responses from the server. */ +static void on_query_readable(verto_ctx *vctx, verto_ev *ev) +{ + struct otpd_queue *push = &ctx.stdio.responses; + verto_ev *event = ctx.stdio.writer; + LDAPMessage *results, *entry; + struct otpd_queue_item *item = NULL; + const char *err; + LDAP *ldp; + int i; + (void)vctx; + + ldp = verto_get_private(ev); + + i = ldap_result(ldp, LDAP_RES_ANY, 0, NULL, &results); + if (i != LDAP_RES_SEARCH_ENTRY && i != LDAP_RES_SEARCH_RESULT) { + if (i <= 0) + results = NULL; + goto egress; + } + + item = otpd_queue_pop_msgid(&ctx.query.responses, ldap_msgid(results)); + if (item == NULL) + goto egress; + + if (i == LDAP_RES_SEARCH_ENTRY) { + entry = ldap_first_entry(ldp, results); + if (entry == NULL) + goto egress; + + err = NULL; + switch (item->sent) { + case 1: + err = otpd_parse_user(ldp, entry, item); + break; + case 2: + err = otpd_parse_radius(ldp, entry, item); + break; + case 3: + err = otpd_parse_radius_username(ldp, entry, item); + break; + default: + ldap_msgfree(entry); + goto egress; + } + + ldap_msgfree(entry); + + if (err != NULL) { + if (item->error != NULL) + free(item->error); + item->error = strdup(err); + if (item->error == NULL) + goto egress; + } + + otpd_queue_push_head(&ctx.query.responses, item); + return; + } + + item->msgid = -1; + + switch (item->sent) { + case 1: + otpd_log_req(item->req, "user query end: %s", + item->error == NULL ? item->user.dn : item->error); + if (item->user.dn == NULL || item->user.uid == NULL) + goto egress; + break; + case 2: + otpd_log_req(item->req, "radius query end: %s", + item->error == NULL + ? item->radius.ipatokenRadiusServer + : item->error); + if (item->radius.ipatokenRadiusServer == NULL || + item->radius.ipatokenRadiusSecret == NULL) + goto egress; + break; + case 3: + otpd_log_req(item->req, "username query end: %s", + item->error == NULL ? item->user.other : item->error); + break; + default: + goto egress; + } + + if (item->error != NULL) + goto egress; + + if (item->sent == 1 && item->user.ipatokenRadiusConfigLink != NULL) { + push = &ctx.query.requests; + event = ctx.query.io; + goto egress; + } else if (item->sent == 2 && + item->radius.ipatokenUserMapAttribute != NULL && + item->user.ipatokenRadiusUserName == NULL) { + push = &ctx.query.requests; + event = ctx.query.io; + goto egress; + } + + /* Forward to RADIUS if necessary. */ + i = otpd_forward(&item); + if (i != 0) + goto egress; + + push = &ctx.bind.requests; + event = ctx.bind.io; + +egress: + ldap_msgfree(results); + otpd_queue_push(push, item); + + if (item != NULL) + verto_set_flags(event, VERTO_EV_FLAG_PERSIST | + VERTO_EV_FLAG_IO_ERROR | + VERTO_EV_FLAG_IO_READ | + VERTO_EV_FLAG_IO_WRITE); +} + +/* Handle the reading/writing of LDAP query requests asynchronously. */ +void otpd_on_query_io(verto_ctx *vctx, verto_ev *ev) +{ + verto_ev_flag flags; + + flags = verto_get_fd_state(ev); + if (flags & VERTO_EV_FLAG_IO_WRITE) + on_query_writable(vctx, ev); + if (flags & VERTO_EV_FLAG_IO_READ) + on_query_readable(vctx, ev); + if (flags & VERTO_EV_FLAG_IO_ERROR) { + otpd_log_err(EIO, "IO error received on query socket"); + verto_break(ctx.vctx); + ctx.exitstatus = 1; + } +} |