mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2024-12-28 01:41:14 -06:00
203754691c
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
206 lines
5.7 KiB
C
206 lines
5.7 KiB
C
/*
|
|
* 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 reads and writes RADIUS packets on STDIN/STDOUT.
|
|
*
|
|
* Incoming requests are placed into a "query" queue to look up the user's
|
|
* configuration from LDAP (query.c).
|
|
*/
|
|
|
|
#include "internal.h"
|
|
|
|
static const struct otpd_queue *const queues[] = {
|
|
&ctx.stdio.responses,
|
|
&ctx.query.requests,
|
|
&ctx.query.responses,
|
|
&ctx.bind.requests,
|
|
&ctx.bind.responses,
|
|
NULL
|
|
};
|
|
|
|
/* Read a RADIUS request from stdin. */
|
|
void otpd_on_stdin_readable(verto_ctx *vctx, verto_ev *ev)
|
|
{
|
|
static char _buffer[KRAD_PACKET_SIZE_MAX];
|
|
static krb5_data buffer = { .data = _buffer, .length = 0 };
|
|
(void)vctx;
|
|
|
|
const krad_packet *dup;
|
|
const krb5_data *data;
|
|
struct otpd_queue_iter *iter;
|
|
struct otpd_queue_item *item;
|
|
krad_packet *req;
|
|
ssize_t pktlen;
|
|
int i;
|
|
|
|
pktlen = krad_packet_bytes_needed(&buffer);
|
|
if (pktlen < 0) {
|
|
otpd_log_err(EBADMSG, "Received a malformed packet");
|
|
goto shutdown;
|
|
}
|
|
|
|
/* Read the item. */
|
|
i = read(verto_get_fd(ev), buffer.data + buffer.length, pktlen);
|
|
if (i < 1) {
|
|
/* On EOF, shutdown gracefully. */
|
|
if (i == 0) {
|
|
fprintf(stderr, "Socket closed, shutting down...\n");
|
|
verto_break(ctx.vctx);
|
|
return;
|
|
}
|
|
|
|
if (errno != EAGAIN && errno != EINTR) {
|
|
otpd_log_err(errno, "Error receiving packet");
|
|
goto shutdown;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* If we have a partial read or just the header, try again. */
|
|
buffer.length += i;
|
|
pktlen = krad_packet_bytes_needed(&buffer);
|
|
if (pktlen > 0)
|
|
return;
|
|
|
|
/* Create the iterator. */
|
|
i = otpd_queue_iter_new(queues, &iter);
|
|
if (i != 0) {
|
|
otpd_log_err(i, "Unable to create iterator");
|
|
goto shutdown;
|
|
}
|
|
|
|
/* Decode the item. */
|
|
i = krad_packet_decode_request(ctx.kctx, SECRET, &buffer,
|
|
otpd_queue_iter_func, iter, &dup, &req);
|
|
buffer.length = 0;
|
|
if (i == EAGAIN)
|
|
return;
|
|
else if (i != 0) {
|
|
otpd_log_err(i, "Unable to decode item");
|
|
goto shutdown;
|
|
}
|
|
|
|
/* Drop duplicate requests. */
|
|
if (dup != NULL) {
|
|
krad_packet_free(req);
|
|
return;
|
|
}
|
|
|
|
/* Ensure the packet has the User-Name attribute. */
|
|
data = krad_packet_get_attr(req, krad_attr_name2num("User-Name"), 0);
|
|
if (data == NULL) {
|
|
krad_packet_free(req);
|
|
return;
|
|
}
|
|
|
|
/* Create the new queue item. */
|
|
i = otpd_queue_item_new(req, &item);
|
|
if (i != 0) {
|
|
krad_packet_free(req);
|
|
return;
|
|
}
|
|
|
|
/* Push it to the query queue. */
|
|
otpd_queue_push(&ctx.query.requests, item);
|
|
verto_set_flags(ctx.query.io, VERTO_EV_FLAG_PERSIST |
|
|
VERTO_EV_FLAG_IO_ERROR |
|
|
VERTO_EV_FLAG_IO_READ |
|
|
VERTO_EV_FLAG_IO_WRITE);
|
|
|
|
otpd_log_req(req, "request received");
|
|
return;
|
|
|
|
shutdown:
|
|
verto_break(ctx.vctx);
|
|
ctx.exitstatus = 1;
|
|
}
|
|
|
|
/* Send a RADIUS response to stdout. */
|
|
void otpd_on_stdout_writable(verto_ctx *vctx, verto_ev *ev)
|
|
{
|
|
const krb5_data *data;
|
|
struct otpd_queue_item *item;
|
|
int i;
|
|
(void)vctx;
|
|
|
|
item = otpd_queue_peek(&ctx.stdio.responses);
|
|
if (item == NULL) {
|
|
verto_set_flags(ctx.stdio.writer, VERTO_EV_FLAG_PERSIST |
|
|
VERTO_EV_FLAG_IO_ERROR |
|
|
VERTO_EV_FLAG_IO_READ);
|
|
return;
|
|
}
|
|
|
|
/* If no response has been generated thus far, send Access-Reject. */
|
|
if (item->rsp == NULL) {
|
|
item->sent = 0;
|
|
i = krad_packet_new_response(ctx.kctx, SECRET,
|
|
krad_code_name2num("Access-Reject"),
|
|
NULL, item->req, &item->rsp);
|
|
if (i != 0) {
|
|
otpd_log_err(errno, "Unable to craft response");
|
|
goto shutdown;
|
|
}
|
|
}
|
|
|
|
/* Send the packet. */
|
|
data = krad_packet_encode(item->rsp);
|
|
i = write(verto_get_fd(ev), data->data + item->sent,
|
|
data->length - item->sent);
|
|
if (i < 0) {
|
|
switch (errno) {
|
|
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || EAGAIN - EWOULDBLOCK != 0)
|
|
case EWOULDBLOCK:
|
|
#endif
|
|
#if defined(EAGAIN)
|
|
case EAGAIN:
|
|
#endif
|
|
case ENOBUFS:
|
|
case EINTR:
|
|
/* In this case, we just need to try again. */
|
|
return;
|
|
default:
|
|
/* Unrecoverable. */
|
|
break;
|
|
}
|
|
|
|
otpd_log_err(errno, "Error writing to stdout!");
|
|
goto shutdown;
|
|
}
|
|
|
|
/* If the packet was completely sent, free the response. */
|
|
item->sent += i;
|
|
if (item->sent == data->length) {
|
|
otpd_log_req(item->req, "response sent: %s",
|
|
krad_code_num2name(krad_packet_get_code(item->rsp)));
|
|
otpd_queue_item_free(otpd_queue_pop(&ctx.stdio.responses));
|
|
}
|
|
|
|
return;
|
|
|
|
shutdown:
|
|
verto_break(ctx.vctx);
|
|
ctx.exitstatus = 1;
|
|
}
|