freeipa/daemons/ipa-otpd/stdio.c
Alexander Bokovoy 3f6656e09a ipa-otpd: add support for SSSD OIDC helper
SSSD OIDC helper is used for negotiating with OAUTH2 or OIDC end points
of external identity providers (IdPs).

ipa-otpd daemon now is capable to take either Issuer URL or individual
endpoints and call SSSD OIDC helper accordingly.

Communication with SSSD OIDC helper can be debugged with the use of a
debug variable set in /etc/ipa/default.conf. Man page for
default.conf(5) has been updated to provide this information.

Fixes: https://pagure.io/freeipa/issue/8805

Signed-off-by: Sumit Bose <sbose@redhat.com>
Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Francisco Trivino <ftrivino@redhat.com>
Reviewed-By: Sumit Bose <sbose@redhat.com>
2022-05-10 15:52:41 +03:00

208 lines
5.9 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);
otpd_log_req(item->req, "sent: %d data: %d", item->sent, data->length);
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;
otpd_log_req(item->req, "..sent: %d data: %d", item->sent, data->length);
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;
}