--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/radius/server.c
+ * @brief Functions to support RADIUS bio handlers for server sockets
+ *
+ * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/radius/server.h>
+#include <freeradius-devel/radius/server_udp.h>
+#include <freeradius-devel/radius/server_priv.h>
+
+fr_bio_packet_t *fr_radius_server_bio_alloc(TALLOC_CTX *ctx, fr_radius_server_config_t *cfg, fr_bio_fd_config_t const *fd_cfg)
+{
+ fr_assert(fd_cfg->type == FR_BIO_FD_UNCONNECTED); /* UDP sockets only for now */
+
+ if (fd_cfg->path || fd_cfg->filename) {
+ fr_strerror_const("Domain sockets and files are not supported");
+ return NULL;
+ }
+
+ if (fd_cfg->socket_type == SOCK_DGRAM) return fr_radius_server_udp_bio_alloc(ctx, cfg, fd_cfg);
+
+ fr_strerror_const("No TCP for you.");
+ return -1;
+
+// return fr_radius_server_tcp_bio_alloc(ctx, cfg, fd_cfg);
+}
+
+static int _radius_server_fd_bio_free(fr_radius_server_fd_bio_t *my)
+{
+ if (fr_bio_shutdown(my->common.bio) < 0) return -1;
+
+ if (fr_bio_free(my->common.bio) < 0) return -1;
+
+ return 0;
+}
+
+
+fr_radius_server_fd_bio_t *fr_radius_server_fd_bio_alloc(TALLOC_CTX *ctx, size_t read_size, fr_radius_server_config_t *cfg, fr_bio_fd_config_t const *fd_cfg)
+{
+ fr_radius_server_fd_bio_t *my;
+
+ /*
+ * For now we only support unconnected UDP server sockets.
+ *
+ * Connected TCP server sockets require the ability to create new BIOs and add new sockets on the fly.
+ */
+ fr_assert(fd_cfg->type == FR_BIO_FD_UNCONNECTED);
+
+ my = talloc_zero(ctx, fr_radius_server_fd_bio_t);
+ if (!my) return NULL;
+
+ my->fd = fr_bio_fd_alloc(my, fd_cfg, 0);
+ if (!my->fd) {
+ fail:
+ talloc_free(my);
+ return NULL;
+ }
+
+ /*
+ * So that read / write pause / resume callbacks can find us
+ */
+ my->fd->uctx = my;
+
+ my->info.fd_info = fr_bio_fd_info(my->fd);
+ fr_assert(my->info.fd_info != NULL);
+
+ my->mem = fr_bio_mem_alloc(my, read_size, 2 * 4096, my->fd);
+ if (!my->mem) goto fail;
+ my->mem->uctx = &my->cfg.verify;
+
+ my->cfg = *cfg;
+
+ my->common.bio = my->mem;
+
+ talloc_set_destructor(my, _radius_server_fd_bio_free);
+
+ return my;
+}
+
+int fr_radius_server_fd_bio_write(fr_bio_packet_t *bio, UNUSED void *pctx, fr_packet_t *reply, fr_pair_list_t *list)
+{
+ fr_radius_server_fd_bio_t *my = talloc_get_type_abort(bio, fr_radius_server_fd_bio_t);
+ fr_packet_t *request = reply->uctx;
+ ssize_t slen;
+
+ fr_assert(!reply->data);
+
+ fr_assert(reply->code > 0);
+ fr_assert(reply->code < FR_RADIUS_CODE_MAX);
+
+ /*
+ * Encode the packet.
+ */
+ if (fr_packet_encode(reply, list, request, (char const *) my->cfg.verify.secret) < 0) {
+ fail:
+ return fr_bio_error(GENERIC);
+ }
+
+ if (fr_packet_sign(reply, request, (char const *) my->cfg.verify.secret) < 0) goto fail;
+
+ slen = fr_bio_write(my->common.bio, &reply->socket, reply->data, reply->data_len);
+ if (slen < 0) {
+ fr_assert((slen != fr_bio_error(IO_WOULD_BLOCK)) || my->common.write_blocked);
+
+ return slen;
+ }
+
+ my->info.write_blocked = false;
+
+ return 0;
+}
+
+
+int fr_radius_server_fd_bio_read(fr_bio_packet_t *bio, UNUSED void **packet_ctx_p, fr_packet_t **packet_p,
+ TALLOC_CTX *out_ctx, fr_pair_list_t *out)
+{
+ ssize_t slen;
+ fr_radius_server_fd_bio_t *my = talloc_get_type_abort(bio, fr_radius_server_fd_bio_t);
+ fr_packet_t *packet;
+
+ /*
+ * We don't need to set up response.socket for connected bios.
+ */
+ fr_packet_t base = {};
+
+ /*
+ * We read the response packet ctx into our local structure. If we have a real response, we will
+ * swap to using the request context, and not the response context.
+ */
+ slen = fr_bio_read(my->common.bio, &base, &my->buffer, sizeof(my->buffer));
+ if (!slen) return 0;
+
+ if (slen < 0) {
+ fr_assert(slen != fr_bio_error(IO_WOULD_BLOCK));
+ return slen;
+ }
+
+
+ /*
+ * Allocate the packet data structure
+ */
+ packet = fr_packet_alloc(out_ctx, false);
+ if (!packet) return -1;
+
+ packet->data = talloc_memdup(packet, my->buffer, slen);
+ if (!packet->data) {
+ talloc_free(packet);
+ return -1;
+ }
+ packet->data_len = slen;
+
+ packet->code = packet->data[0];
+ packet->id = packet->data[1];
+ memcpy(packet->vector, packet->data + 4, sizeof(packet->vector));
+
+ /*
+ * If this fails, we're out of memory.
+ */
+ if (fr_radius_decode_simple(packet, out, packet->data, packet->data_len,
+ NULL, (char const *) my->cfg.verify.secret) < 0) {
+ talloc_free(packet);
+ return -1;
+ }
+
+ *packet_p = packet;
+
+ return 1;
+}
+
+fr_bio_t *fr_radius_server_bio_get_fd(fr_bio_packet_t *bio)
+{
+ fr_radius_server_fd_bio_t *my = talloc_get_type_abort(bio, fr_radius_server_fd_bio_t);
+
+ return my->fd;
+}
+
+fr_radius_server_bio_info_t const *fr_radius_server_bio_info(fr_bio_packet_t *bio)
+{
+ fr_radius_server_fd_bio_t *my = talloc_get_type_abort(bio, fr_radius_server_fd_bio_t);
+
+ return &my->info;
+}
--- /dev/null
+#pragma once
+/*
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/radius/server.h
+ * @brief RADIUS bio handlers for outgoing RADIUS server sockets
+ *
+ * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSIDH(radius_server_h, "$Id$")
+
+#include <freeradius-devel/radius/radius.h>
+#include <freeradius-devel/radius/bio.h>
+#include <freeradius-devel/bio/packet.h>
+#include <freeradius-devel/bio/fd.h>
+#include <freeradius-devel/bio/dedup.h>
+
+typedef struct {
+ fr_log_t *log;
+
+ fr_radius_bio_verify_t verify;
+
+ fr_bio_dedup_config_t dedup_cfg;
+
+ fr_bio_packet_cb_funcs_t packet_cb_cfg;
+} fr_radius_server_config_t;
+
+typedef struct {
+ bool connected;
+ bool write_blocked;
+ bool read_blocked;
+
+ fr_bio_fd_info_t const *fd_info;
+} fr_radius_server_bio_info_t;
+
+typedef struct {
+ fr_bio_fd_packet_ctx_t fd;
+ fr_bio_dedup_entry_t *dedup;
+} fr_radius_server_bio_pctx_t;
+
+fr_bio_packet_t *fr_radius_server_bio_alloc(TALLOC_CTX *ctx, fr_radius_server_config_t *cfg, fr_bio_fd_config_t const *fd_cfg) CC_HINT(nonnull);
+
+fr_bio_t *fr_radius_server_bio_get_fd(fr_bio_packet_t *bio) CC_HINT(nonnull);
+
+fr_radius_server_bio_info_t const *fr_radius_server_bio_info(fr_bio_packet_t *bio) CC_HINT(nonnull);
--- /dev/null
+/*
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/radius/server_udp.c
+ * @brief Functions to support RADIUS bio handlers for server udp sockets
+ *
+ * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/bio/packet.h>
+#include <freeradius-devel/radius/server_udp.h>
+#include <freeradius-devel/radius/server_priv.h>
+
+static bool radius_server_dedup_receive(fr_bio_t *bio, fr_bio_dedup_entry_t *dedup_ctx, void *packet_ctx)
+{
+ fr_radius_server_fd_bio_t *my = talloc_get_type_abort(bio->uctx, fr_radius_server_fd_bio_t);
+ fr_bio_dedup_entry_t *prev;
+ fr_radius_server_bio_pctx_t *ctx = packet_ctx;
+
+ /*
+ * Find any previous entry.
+ */
+ prev = fr_rb_find(&my->rb, dedup_ctx);
+ if (prev) {
+ // @todo - signal duplicate packet
+ return false;
+ }
+
+ if (!fr_rb_insert(&my->rb, dedup_ctx)) {
+ // @todo - signal an error
+ return false;
+ }
+
+ /*
+ * Glue it all together
+ */
+ dedup_ctx->uctx = ctx;
+ ctx->dedup = dedup_ctx;
+
+ return true;
+}
+
+static fr_bio_dedup_entry_t *radius_server_dedup_get_item(UNUSED fr_bio_t *bio, void *packet_ctx)
+{
+ fr_radius_server_bio_pctx_t *ctx = packet_ctx;
+
+ return ctx->dedup;
+}
+
+static void radius_server_dedup_release(fr_bio_t *bio, fr_bio_dedup_entry_t *dedup_ctx, UNUSED fr_bio_dedup_release_reason_t reason)
+{
+ fr_radius_server_fd_bio_t *my = talloc_get_type_abort(bio->uctx, fr_radius_server_fd_bio_t);
+ fr_radius_server_bio_pctx_t *ctx = dedup_ctx->uctx;
+
+ (void) fr_rb_delete(&my->rb, dedup_ctx);
+ ctx->dedup = NULL;
+}
+
+static int fr_radius_server_udp_bio_read(fr_bio_packet_t *bio, void **packet_ctx_p, fr_packet_t **packet_p,
+ TALLOC_CTX *out_ctx, fr_pair_list_t *out)
+{
+ int rcode;
+ fr_radius_server_bio_pctx_t *ctx;
+
+ /*
+ * Read the packet.
+ */
+ rcode = fr_radius_server_fd_bio_read(bio, packet_ctx_p, packet_p, out_ctx, out);
+ if (rcode < 0) return rcode;
+
+ ctx = *packet_ctx_p;
+
+ /*
+ * The dedup_ctx starts off with the raw data in a buffer somewhere. That buffer will get
+ * over-written with a later packet. So be sure to update the dedup_ctx with the long-term
+ * version of the packet contents.
+ */
+ fr_assert(ctx->dedup->packet_size == (*packet_p)->data_len);
+
+ ctx->dedup->packet = (*packet_p)->data;
+ ctx->dedup->packet_size = (*packet_p)->data_len;
+
+ return 0;
+}
+
+/** Allocate a RADIUS bio for receiving packets from clients.
+ *
+ * It also verifies that the packets we receive are valid for RADIUS.
+ */
+fr_bio_packet_t *fr_radius_server_udp_bio_alloc(TALLOC_CTX *ctx, fr_radius_server_config_t *cfg, fr_bio_fd_config_t const *fd_cfg)
+{
+ fr_radius_server_fd_bio_t *my;
+
+ my = fr_radius_server_fd_bio_alloc(ctx, 2 * 4096, cfg, fd_cfg);
+ if (!my) return NULL;
+
+ if (fr_bio_mem_set_verify(my->mem, fr_radius_bio_verify_datagram, true) < 0) {
+ fail:
+ talloc_free(my);
+ return NULL;
+ }
+
+ /*
+ * Once we've allocated a FD and memory BIO, UDP needs de-duping.
+ */
+ my->dedup = fr_bio_dedup_alloc(my, 256, radius_server_dedup_receive, radius_server_dedup_release,
+ radius_server_dedup_get_item, &cfg->dedup_cfg, my->mem);
+ if (!my->dedup) goto fail;
+ my->dedup->uctx = my;
+
+ my->common.bio = my->dedup;
+
+ my->common.read = fr_radius_server_udp_bio_read;
+ my->common.write = fr_radius_server_fd_bio_write;
+
+ // @todo - insert comparison function
+ // @todo - comparison function is different for connected and unconnected sockets
+
+ return (fr_bio_packet_t *) my;
+}
--- /dev/null
+#pragma once
+/*
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file protocols/radius/server_udp.h
+ * @brief RADIUS bio handlers for outgoing RADIUS server sockets over UDP
+ *
+ * @copyright 2024 Network RADIUS SAS (legal@networkradius.com)
+ */
+RCSIDH(radius_server_udp_h, "$Id$")
+
+#include <freeradius-devel/radius/server.h>
+
+fr_bio_packet_t *fr_radius_server_udp_bio_alloc(TALLOC_CTX *ctx, fr_radius_server_config_t *cfg, fr_bio_fd_config_t const *fd_cfg) CC_HINT(nonnull);