From: Brian 'redbeard' Harrington Date: Wed, 9 Apr 2025 22:00:42 +0000 (-0700) Subject: net/radius-mac: add new package X-Git-Url: http://git.openwrt.org/?a=commitdiff_plain;h=9044134aa2cdc62ec2f3613af0388085ab1a083c;p=feed%2Fpackages.git net/radius-mac: add new package This introduces the package `radius-mac` to OpenWrt, documented here: https://anton.lindstrom.io/radius-mac/ `radius-mac` is a minimal RADIUS server focusing solely on the use case of MAC address authentication for assigning VLANs to an 802.1x device Co-authored-by: Josef Schlehofer Signed-off-by: Brian 'redbeard' Harrington --- diff --git a/net/radius-mac/Makefile b/net/radius-mac/Makefile new file mode 100644 index 0000000000..579b650f56 --- /dev/null +++ b/net/radius-mac/Makefile @@ -0,0 +1,53 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=radius-mac +# Generally, our PKG_VERSION should match our PKG_SOURCE_VERSION +PKG_VERSION:=0.1 +PKG_RELEASE:=1 + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=https://github.com/carlanton/radius-mac.git +PKG_SOURCE_VERSION:=release-0.1 +PKG_MIRROR_HASH:=3d52453fd6c3aaef80aa6f0a65114721c38561a440a5165e42c864650a250cd3 + +PKG_MAINTAINER:=Brian 'redbeard' Harrington +PKG_LICENSE:=MIT +PKG_LICENSE_FILES:=LICENSE + +include $(INCLUDE_DIR)/package.mk + +define Package/radius-mac + SECTION:=net + CATEGORY:=Network + TITLE:=RADIUS server providing MAC address authentication + URL:=https://github.com/carlanton/radius-mac + DEPENDS:=+libuci +endef + +define Package/radius-mac/description + A simple RADIUS server for MAC address authentication. +endef + +define Package/radius-mac/conffiles +/etc/config/radius-mac +endef + +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR)/src \ + CC="$(TARGET_CC)" \ + CFLAGS="$(TARGET_CFLAGS)" \ + LDFLAGS="$(TARGET_LDFLAGS)" +endef + +define Package/radius-mac/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/radius-mac $(1)/usr/bin/ + + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./files/radius-mac.conf $(1)/etc/config/radius-mac + + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/radius-mac.init $(1)/etc/init.d/radius-mac +endef + +$(eval $(call BuildPackage,radius-mac)) diff --git a/net/radius-mac/files/radius-mac.conf b/net/radius-mac/files/radius-mac.conf new file mode 100644 index 0000000000..9704f9348c --- /dev/null +++ b/net/radius-mac/files/radius-mac.conf @@ -0,0 +1,28 @@ +# Server configuration parameters +config radius-mac-server 'default' + option enabled '0' + option address '0.0.0.0' + option port '1812' + option secret 'shared-secret-xyz' + option default_vlan '1' + +# # Client device: SmartTV +# config radius-mac-client 'smarttv' +# option server 'default' +# option mac '10:11:12:13:14' +# option description 'SmartTV' +# option vlan '22' +# +# # Client device: Chromecast +# config radius-mac-client 'chromecast' +# option server 'default' +# option mac '10:11:12:13:44' +# option description 'Chromecast' +# option vlan '23' +# +# # Client device: Toaster +# config radius-mac-client 'toaster' +# option server 'default' +# option mac '20:81:12:13:44' +# option description 'Toaster' +# option vlan '24' diff --git a/net/radius-mac/files/radius-mac.init b/net/radius-mac/files/radius-mac.init new file mode 100644 index 0000000000..b9179c4cde --- /dev/null +++ b/net/radius-mac/files/radius-mac.init @@ -0,0 +1,207 @@ +#!/bin/sh /etc/rc.common +# Copyright (C) 2025 Brian 'redbeard' Harrington +# This is free software, licensed under the MIT License + +START=99 +USE_PROCD=1 + +PROG=/usr/bin/radius-mac +CONFIG_BASE_DIR="/etc" # Directory for generated .ini files + +# Helper to determine the path for the generated .ini file +# $1: section_name (from UCI, e.g., 'default', 'guest_server') +# $2: variable name to store the result (output parameter) +get_cfg_file_path() { + local section_name="$1" + local __result_var="$2" + local cfg_path + + if [ "$section_name" = "default" ]; then + cfg_path="$CONFIG_BASE_DIR/radius-mac.ini" + else + cfg_path="$CONFIG_BASE_DIR/radius-mac-$section_name.ini" + fi + eval "$__result_var='$cfg_path'" +} + +# Validation helper functions +_log_error() { + logger -t radius-mac-init "Error: $1" +} + +_is_valid_number_in_range() { + local val="$1" + local min="$2" + local max="$3" + local context="$4" # For logging + + if ! echo "$val" | grep -qE '^[0-9]+$'; then + _log_error "$context: Value '$val' is not a number." + return 1 + fi + if [ "$val" -lt "$min" ] || [ "$val" -gt "$max" ]; then + _log_error "$context: Value '$val' is out of range ($min-$max)." + return 1 + fi + return 0 +} + +_is_valid_string_length() { + local val="$1" + local min_len="$2" + local max_len="$3" + local context="$4" # For logging + local len=${#val} + + if [ "$len" -lt "$min_len" ] || [ "$len" -gt "$max_len" ]; then + _log_error "$context: Length '$len' is out of range ($min_len-$max_len)." + return 1 + fi + return 0 +} + +_is_valid_ipv4() { + local val="$1" + local context="$2" + # Basic regex, not exhaustive + if ! echo "$val" | grep -qE '^([0-9]{1,3}\.){3}[0-9]{1,3}$'; then + _log_error "$context: Value '$val' is not a valid IPv4 address format." + return 1 + fi + # Further checks for valid octets (0-255) could be added here if needed + return 0 +} + +_is_valid_mac_address() { + local val="$1" + local context="$2" + if ! echo "$val" | grep -qEi '^([0-9a-f]{2}[:-]){5}[0-9a-f]{2}$'; then + _log_error "$context: Value '$val' is not a valid MAC address format." + return 1 + fi + return 0 +} + +# Client callback function for processing radius-mac-client sections +client_cb() { + local client_section="$1" # UCI section name of the client + local server_section_name="$2" + local cfg_file="$3" + local client_server_link mac description vlan + + config_get client_server_link "$client_section" server + if [ "$client_server_link" = "$server_section_name" ]; then + config_get mac "$client_section" mac + config_get description "$client_section" description + config_get vlan "$client_section" vlan + + # Validate client options + local client_validation_ok=1 + [ -z "$mac" ] && { _log_error "Client section '$client_section' for server '$server_section_name': Missing MAC address."; client_validation_ok=0; } + [ -n "$mac" ] && _is_valid_mac_address "$mac" "Client '$client_section' MAC" || client_validation_ok=0 + [ -n "$description" ] && _is_valid_string_length "$description" 1 256 "Client '$client_section' description" || client_validation_ok=0 + [ -n "$vlan" ] && _is_valid_number_in_range "$vlan" 1 4094 "Client '$client_section' vlan" || client_validation_ok=0 + + if [ "$client_validation_ok" -eq 1 ] && [ -n "$mac" ]; then + echo "[$mac]" >> "$cfg_file" + [ -n "$description" ] && echo "description=$description" >> "$cfg_file" + [ -n "$vlan" ] && echo "vlan=$vlan" >> "$cfg_file" + echo "" >> "$cfg_file" + else + _log_error "Client section '$client_section' for server '$server_section_name' has validation errors or missing MAC. Skipping." + fi + fi +} + +# Generates the .ini file for a given server instance +# $1: server_section_name (UCI section name of the radius-mac-server) +# Returns 0 on success, 1 on critical validation failure for server options +generate_ini_file() { + local server_section_name="$1" + local cfg_file + local address port secret default_vlan + local validation_ok=1 + + get_cfg_file_path "$server_section_name" cfg_file + + # Clear/create the file + > "$cfg_file" + + # Server options from radius-mac-server section + config_get address "$server_section_name" address + config_get port "$server_section_name" port + config_get secret "$server_section_name" secret + config_get default_vlan "$server_section_name" default_vlan + + # Validate server options + [ -n "$address" ] && _is_valid_ipv4 "$address" "Server '$server_section_name' address" || validation_ok=0 + [ -n "$port" ] && _is_valid_number_in_range "$port" 1 65535 "Server '$server_section_name' port" || validation_ok=0 + [ -n "$secret" ] && _is_valid_string_length "$secret" 1 256 "Server '$server_section_name' secret" || validation_ok=0 + [ -n "$default_vlan" ] && _is_valid_number_in_range "$default_vlan" 1 4094 "Server '$server_section_name' default_vlan" || validation_ok=0 + + if [ "$validation_ok" -eq 0 ]; then + _log_error "Critical validation failed for server '$server_section_name'. Configuration not generated." + return 1 + fi + + # Write validated server options + # Ensure required options are present (example: address, port, secret are usually essential) + if [ -z "$address" ] || [ -z "$port" ] || [ -z "$secret" ]; then + _log_error "Server '$server_section_name': Missing one or more required fields (address, port, secret)." + return 1 + fi + + # Write validated server options with [server] header + echo "[server]" >> "$cfg_file" + echo "address=$address" >> "$cfg_file" + echo "port=$port" >> "$cfg_file" + echo "secret=$secret" >> "$cfg_file" + [ -n "$default_vlan" ] && echo "default_vlan=$default_vlan" >> "$cfg_file" + + echo "" >> "$cfg_file" # Blank line + echo "; Clients:" >> "$cfg_file" + echo "" >> "$cfg_file" # Blank line before clients + + # Client options from radius-mac-client sections linked to this server + config_foreach client_cb radius-mac-client "$server_section_name" "$cfg_file" + + return 0 +} + +# Callback for starting a single instance, called by config_foreach +# $1: server_section_name (UCI section name of the radius-mac-server) +start_one_instance() { + local section_name="$1" + local cfg_file + local enabled + + config_get_bool enabled "$section_name" enabled 1 # Check if the server instance itself is enabled + get_cfg_file_path "$section_name" cfg_file + + if [ "$enabled" -eq 0 ]; then + rm -f "$cfg_file" # Clean up config for disabled instance + return 1 + fi + + if ! generate_ini_file "$section_name"; then + _log_error "Failed to generate configuration for '$section_name' due to validation errors. Instance will not start." + # Ensure potentially partially written or empty file is removed if generation failed critically + rm -f "$cfg_file" + return 1 + fi + + procd_open_instance "$section_name" + procd_set_param command "$PROG" -c "$cfg_file" + procd_set_param respawn + procd_set_param pidfile "/var/run/radius-mac-$section_name.pid" + procd_close_instance +} + +start_service() { + config_load radius-mac + config_foreach start_one_instance radius-mac-server +} + +service_triggers() { + procd_add_reload_trigger radius-mac +}