summaryrefslogtreecommitdiffstats
path: root/daemons/ipa-otpd/main.c
diff options
context:
space:
mode:
authorNathaniel McCallum <npmccallum@redhat.com>2013-04-11 14:03:25 -0400
committerMartin Kosek <mkosek@redhat.com>2013-05-17 09:30:51 +0200
commit203754691c28243dd3cf378e98390fc0a455b485 (patch)
treef1574334a744f2b2b54c90a0eec08a985151447b /daemons/ipa-otpd/main.c
parent5d51ae50a59466fa2d6d230d7f2879de34210f0c (diff)
downloadfreeipa-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/main.c')
-rw-r--r--daemons/ipa-otpd/main.c340
1 files changed, 340 insertions, 0 deletions
diff --git a/daemons/ipa-otpd/main.c b/daemons/ipa-otpd/main.c
new file mode 100644
index 000000000..a5d1f93ff
--- /dev/null
+++ b/daemons/ipa-otpd/main.c
@@ -0,0 +1,340 @@
+/*
+ * 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 initializes a systemd socket-activated daemon which receives
+ * RADIUS packets on STDIN and either proxies them to a third party RADIUS
+ * server or performs authentication directly by binding to the LDAP server.
+ * The choice between bind or proxy is made by evaluating LDAP configuration
+ * for the given user.
+ */
+
+#include "internal.h"
+
+#include <signal.h>
+#include <stdbool.h>
+
+/* Our global state. */
+struct otpd_context ctx;
+
+/* Implementation function for logging a request's state. See internal.h. */
+void otpd_log_req_(const char * const file, int line, krad_packet *req,
+ const char * const tmpl, ...)
+{
+ const krb5_data *data;
+ va_list ap;
+
+#ifdef DEBUG
+ if (file != NULL)
+ fprintf(stderr, "%8s:%03d: ", file, line);
+#else
+ (void)file;
+ (void)line;
+#endif
+
+ data = krad_packet_get_attr(req, krad_attr_name2num("User-Name"), 0);
+ if (data == NULL)
+ fprintf(stderr, "<unknown>: ");
+ else
+ fprintf(stderr, "%*s: ", data->length, data->data);
+
+ va_start(ap, tmpl);
+ vfprintf(stderr, tmpl, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+
+/* Implementation function for logging a generic error. See internal.h. */
+void otpd_log_err_(const char * const file, int line, krb5_error_code code,
+ const char * const tmpl, ...)
+{
+ const char *msg;
+ va_list ap;
+
+ if (file != NULL)
+ fprintf(stderr, "%10s:%03d: ", file, line);
+
+ if (code != 0) {
+ msg = krb5_get_error_message(ctx.kctx, code);
+ fprintf(stderr, "%s: ", msg);
+ krb5_free_error_message(ctx.kctx, msg);
+ }
+
+ va_start(ap, tmpl);
+ vfprintf(stderr, tmpl, ap);
+ va_end(ap);
+
+ fprintf(stderr, "\n");
+}
+
+static void on_ldap_free(verto_ctx *vctx, verto_ev *ev)
+{
+ (void)vctx; /* Unused */
+ ldap_unbind_ext_s(verto_get_private(ev), NULL, NULL);
+}
+
+static void on_signal(verto_ctx *vctx, verto_ev *ev)
+{
+ (void)ev; /* Unused */
+ fprintf(stderr, "Signaled, exiting...\n");
+ verto_break(vctx);
+}
+
+static char *find_base(LDAP *ldp)
+{
+ LDAPMessage *results = NULL, *entry;
+ struct berval **vals = NULL;
+ struct timeval timeout;
+ int i, len;
+ char *base = NULL, *attrs[] = {
+ "namingContexts",
+ "defaultNamingContext",
+ NULL
+ };
+
+ timeout.tv_sec = -1;
+ i = ldap_search_ext_s(ldp, "", LDAP_SCOPE_BASE, NULL, attrs,
+ 0, NULL, NULL, &timeout, 1, &results);
+ if (i != LDAP_SUCCESS) {
+ otpd_log_err(0, "Unable to search for query base: %s",
+ ldap_err2string(i));
+ goto egress;
+ }
+
+ entry = ldap_first_entry(ldp, results);
+ if (entry == NULL) {
+ otpd_log_err(0, "No entries found");
+ goto egress;
+ }
+
+ vals = ldap_get_values_len(ldp, entry, "defaultNamingContext");
+ if (vals == NULL) {
+ vals = ldap_get_values_len(ldp, entry, "namingContexts");
+ if (vals == NULL) {
+ otpd_log_err(0, "No namingContexts found");
+ goto egress;
+ }
+ }
+
+ len = ldap_count_values_len(vals);
+ if (len == 1)
+ base = strndup(vals[0]->bv_val, vals[0]->bv_len);
+ else
+ otpd_log_err(0, "Too many namingContexts found");
+
+ /* TODO: search multiple namingContexts to find the base? */
+
+egress:
+ ldap_value_free_len(vals);
+ ldap_msgfree(results);
+ return base;
+}
+
+/* Set up an LDAP connection as a verto event. */
+static krb5_error_code setup_ldap(const char *uri, krb5_boolean bind,
+ verto_callback *io, verto_ev **ev,
+ char **base)
+{
+ struct timeval timeout;
+ int err, ver, fd;
+ char *basetmp;
+ LDAP *ldp;
+
+ err = ldap_initialize(&ldp, uri);
+ if (err != LDAP_SUCCESS)
+ return errno;
+
+ ver = LDAP_VERSION3;
+ ldap_set_option(ldp, LDAP_OPT_PROTOCOL_VERSION, &ver);
+
+ if (bind) {
+ err = ldap_sasl_bind_s(ldp, NULL, "EXTERNAL", NULL, NULL, NULL, NULL);
+ if (err != LDAP_SUCCESS)
+ return errno;
+ }
+
+ /* Always find the base since this forces open the socket. */
+ basetmp = find_base(ldp);
+ if (basetmp == NULL)
+ return ENOTCONN;
+ if (base != NULL)
+ *base = basetmp;
+ else
+ free(basetmp);
+
+ /* Set default timeout to just return immediately for async requests. */
+ memset(&timeout, 0, sizeof(timeout));
+ err = ldap_set_option(ldp, LDAP_OPT_TIMEOUT, &timeout);
+ if (err != LDAP_OPT_SUCCESS) {
+ ldap_unbind_ext_s(ldp, NULL, NULL);
+ return ENOMEM; /* What error code do I use? */
+ }
+
+ /* Get the file descriptor. */
+ if (ldap_get_option(ldp, LDAP_OPT_DESC, &fd) != LDAP_OPT_SUCCESS) {
+ ldap_unbind_ext_s(ldp, NULL, NULL);
+ return EINVAL;
+ }
+
+ *ev = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
+ VERTO_EV_FLAG_IO_ERROR |
+ VERTO_EV_FLAG_IO_READ,
+ io, fd);
+ if (*ev == NULL) {
+ ldap_unbind_ext_s(ldp, NULL, NULL);
+ return ENOMEM; /* What error code do I use? */
+ }
+
+ verto_set_private(*ev, ldp, on_ldap_free);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ char hostname[HOST_NAME_MAX + 1];
+ krb5_error_code retval;
+ krb5_data hndata;
+ verto_ev *sig;
+
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <ldap_uri>\n", argv[0]);
+ return 1;
+ } else {
+ fprintf(stderr, "LDAP: %s\n", argv[1]);
+ }
+
+ memset(&ctx, 0, sizeof(ctx));
+ ctx.exitstatus = 1;
+
+ if (gethostname(hostname, sizeof(hostname)) < 0) {
+ otpd_log_err(errno, "Unable to get hostname");
+ goto error;
+ }
+
+ retval = krb5_init_context(&ctx.kctx);
+ if (retval != 0) {
+ otpd_log_err(retval, "Unable to initialize context");
+ goto error;
+ }
+
+ ctx.vctx = verto_new(NULL, VERTO_EV_TYPE_IO | VERTO_EV_TYPE_SIGNAL);
+ if (ctx.vctx == NULL) {
+ otpd_log_err(ENOMEM, "Unable to initialize event loop");
+ goto error;
+ }
+
+ /* Build attrset. */
+ retval = krad_attrset_new(ctx.kctx, &ctx.attrs);
+ if (retval != 0) {
+ otpd_log_err(retval, "Unable to initialize attrset");
+ goto error;
+ }
+
+ /* Set NAS-Identifier. */
+ hndata.data = hostname;
+ hndata.length = strlen(hndata.data);
+ retval = krad_attrset_add(ctx.attrs, krad_attr_name2num("NAS-Identifier"),
+ &hndata);
+ if (retval != 0) {
+ otpd_log_err(retval, "Unable to set NAS-Identifier");
+ goto error;
+ }
+
+ /* Set Service-Type. */
+ retval = krad_attrset_add_number(ctx.attrs,
+ krad_attr_name2num("Service-Type"),
+ KRAD_SERVICE_TYPE_AUTHENTICATE_ONLY);
+ if (retval != 0) {
+ otpd_log_err(retval, "Unable to set Service-Type");
+ goto error;
+ }
+
+ /* Radius Client */
+ retval = krad_client_new(ctx.kctx, ctx.vctx, &ctx.client);
+ if (retval != 0) {
+ otpd_log_err(retval, "Unable to initialize radius client");
+ goto error;
+ }
+
+ /* Signals */
+ sig = verto_add_signal(ctx.vctx, VERTO_EV_FLAG_NONE, on_signal, SIGTERM);
+ if (sig == NULL) {
+ otpd_log_err(ENOMEM, "Unable to initialize signal event");
+ goto error;
+ }
+ sig = verto_add_signal(ctx.vctx, VERTO_EV_FLAG_NONE, on_signal, SIGINT);
+ if (sig == NULL) {
+ otpd_log_err(ENOMEM, "Unable to initialize signal event");
+ goto error;
+ }
+
+ /* Standard IO */
+ ctx.stdio.reader = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
+ VERTO_EV_FLAG_IO_ERROR |
+ VERTO_EV_FLAG_IO_READ,
+ otpd_on_stdin_readable, STDIN_FILENO);
+ if (ctx.stdio.reader == NULL) {
+ otpd_log_err(ENOMEM, "Unable to initialize reader event");
+ goto error;
+ }
+ ctx.stdio.writer = verto_add_io(ctx.vctx, VERTO_EV_FLAG_PERSIST |
+ VERTO_EV_FLAG_IO_ERROR |
+ VERTO_EV_FLAG_IO_READ,
+ otpd_on_stdout_writable, STDOUT_FILENO);
+ if (ctx.stdio.writer == NULL) {
+ otpd_log_err(ENOMEM, "Unable to initialize writer event");
+ goto error;
+ }
+
+ /* LDAP (Query) */
+ retval = setup_ldap(argv[1], TRUE, otpd_on_query_io,
+ &ctx.query.io, &ctx.query.base);
+ if (retval != 0) {
+ otpd_log_err(retval, "Unable to initialize LDAP (Query)");
+ goto error;
+ }
+
+ /* LDAP (Bind) */
+ retval = setup_ldap(argv[1], FALSE, otpd_on_bind_io,
+ &ctx.bind.io, NULL);
+ if (retval != 0) {
+ otpd_log_err(retval, "Unable to initialize LDAP (Bind)");
+ goto error;
+ }
+
+ ctx.exitstatus = 0;
+ verto_run(ctx.vctx);
+
+error:
+ krad_client_free(ctx.client);
+ otpd_queue_free_items(&ctx.stdio.responses);
+ otpd_queue_free_items(&ctx.query.requests);
+ otpd_queue_free_items(&ctx.query.responses);
+ otpd_queue_free_items(&ctx.bind.requests);
+ otpd_queue_free_items(&ctx.bind.responses);
+ free(ctx.query.base);
+ verto_free(ctx.vctx);
+ krb5_free_context(ctx.kctx);
+ return ctx.exitstatus;
+}
+