]> git.stg.codes - stg.git/commitdiff
Merge remote-tracking branch 'github/master'
authorMaksym Mamontov <madf@madf.info>
Fri, 27 Mar 2026 20:31:34 +0000 (22:31 +0200)
committerMaksym Mamontov <madf@madf.info>
Fri, 27 Mar 2026 20:31:34 +0000 (22:31 +0200)
projects/stargazer/inst/linux/etc/stargazer/conf-available.d/mod_radius.conf
projects/stargazer/plugins/CMakeLists.txt
projects/stargazer/plugins/other/radius/config.cpp [new file with mode: 0644]
projects/stargazer/plugins/other/radius/config.h [new file with mode: 0644]
projects/stargazer/plugins/other/radius/radius.cpp
projects/stargazer/plugins/other/radius/radius.h
projects/stargazer/plugins/other/radius/server.cpp
projects/stargazer/plugins/other/radius/server.h

index d4cf6c4c41a621dab57e9f1af4fc24ec825c3e99..7373af2128b012f7a0994231ba93ab27c60da275 100644 (file)
     # Default: 1812
     # Port = 1812
 
+    <auth>
+        send = "Framed-IP-Address = currip, Service-Type = \'Framed-User\'"
+        match = "Calling-Station-Id = userdata0"
+    </auth>
+
+    <autz>
+        send = "Framed-IP-Address = currip, Service-Type = \'Login-User\'"
+        match = "User-Name = login, User-Password = password"
+    </autz>
 </Module>
index c8dd8f503ce0c2f915cbc1be1ced36dbe0b8a8a0..7d0e02e6817c024d7c6792ead5777d2f88181024 100644 (file)
@@ -15,7 +15,8 @@ if ( BUILD_MOD_RADIUS )
     find_package ( Boost REQUIRED )
 
     add_library ( mod_radius MODULE other/radius/radius.cpp
-                                    other/radius/server.cpp)
+                                    other/radius/server.cpp
+                                    other/radius/config.cpp)
     target_link_libraries ( mod_radius PRIVATE scriptexecuter logger common )
     set_target_properties ( mod_radius PROPERTIES PREFIX "" )
 
@@ -23,7 +24,7 @@ if ( BUILD_MOD_RADIUS )
 
     ExternalProject_Add ( async-radius
                           GIT_REPOSITORY https://github.com/madf/async-radius.git
-                          GIT_TAG 1.1.2
+                          GIT_TAG 1.3.0
                           GIT_SHALLOW true
                           INSTALL_COMMAND ""
                           CMAKE_ARGS -DCMAKE_CXX_FLAGS=-fPIC )
diff --git a/projects/stargazer/plugins/other/radius/config.cpp b/projects/stargazer/plugins/other/radius/config.cpp
new file mode 100644 (file)
index 0000000..108b61e
--- /dev/null
@@ -0,0 +1,146 @@
+#include "config.h"
+#include "radproto/error.h"
+#include "stg/common.h"
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string.hpp>
+
+#include <vector>
+#include <utility>
+#include <iterator>
+#include <algorithm>
+#include <iostream>
+
+using STG::Config;
+using AttrValue = Config::AttrValue;
+using ASection = Config::ASection;
+
+namespace
+{
+    std::string ShowRules(const std::vector<std::pair<std::string, AttrValue>>& rules)
+    {
+        std::string result;
+        for (const auto& at : rules)
+        {
+            if (!result.empty())
+                result += ", ";
+
+            if (at.second.type == AttrValue::Type::PARAM_NAME)
+                result.append(at.first + " = " + at.second.value);
+            else
+                result.append(at.first + " = '" + at.second.value + "'");
+        }
+        return result;
+    }
+}
+
+std::vector<std::pair<std::string, AttrValue>> Config::ParseRules(const std::string& value, const std::string& paramName)
+{
+    using tokenizer = boost::tokenizer<boost::char_separator<char>>;
+    const boost::char_separator<char> sep(",");
+
+    const tokenizer tokens(value, sep);
+
+    std::vector<std::pair<std::string, AttrValue>> res;
+
+    for (const auto& token : tokens)
+    {
+        std::vector<std::string> keyValue;
+
+        split(keyValue, boost::algorithm::trim_copy_if(token, boost::is_any_of(" \t")), boost::is_any_of(" ="), boost::token_compress_on);
+
+        if (keyValue.size() != 2)
+        {
+            m_logger("The '%s' attribute specification has an incorrect format: '%s'.", paramName.c_str(),  token.c_str());
+            printfd(__FILE__, "The '%s' attribute specification has an incorrect format: '%s'.", paramName.c_str(), token.c_str());
+            return {};
+        }
+
+        auto type = AttrValue::Type::PARAM_NAME;
+        std::string valueName = keyValue[1];
+        if (valueName.front() == '\'' && valueName.back() == '\'')
+        {
+            type = AttrValue::Type::VALUE;
+            valueName.erase(0, 1);
+            valueName.erase(valueName.length() - 1, 1);
+        }
+        else if ((valueName.front() == '\'' && valueName.back() != '\'') || (valueName.front() != '\'' && valueName.back() == '\''))
+        {
+            m_logger("Error ParseRules: '%s' attribute parameter value is invalid.\n", paramName.c_str());
+            printfd(__FILE__, "Error ParseRules: '%s' attribute parameter value is invalid.\n", paramName.c_str());
+            return {};
+        }
+        res.emplace_back(keyValue[0], AttrValue{valueName, type});
+    }
+    return res;
+}
+
+ASection Config::parseASection(const std::vector<ParamValue>& conf)
+{
+    ASection res;
+    const auto mit = std::find(conf.begin(), conf.end(), ParamValue("match", {}));
+    if (mit != conf.end())
+        res.match = ParseRules(mit->value[0], mit->param);
+
+    const auto sit = std::find(conf.begin(), conf.end(), ParamValue("send", {}));
+    if (sit != conf.end())
+        res.send = ParseRules(sit->value[0], sit->param);
+
+    return res;
+}
+
+Config::Config()
+    : m_port(1812),
+      m_dictionaries("/usr/share/freeradius/dictionary"),
+      m_logger(PluginLogger::get("radius"))
+{}
+
+int Config::ParseSettings(const ModuleSettings & s)
+{
+    ParamValue pv;
+    int p;
+
+    pv.param = "Port";
+    auto pvi = std::find(s.moduleParams.begin(), s.moduleParams.end(), pv);
+    if (pvi != s.moduleParams.end() && !pvi->value.empty())
+    {
+        if (ParseIntInRange(pvi->value[0], 2, 65535, &p) != 0)
+        {
+            m_errorStr = "Cannot parse parameter \'Port\': " + m_errorStr;
+            printfd(__FILE__, "Cannot parse parameter 'Port'\n");
+            return -1;
+        }
+        m_port = static_cast<uint16_t>(p);
+    }
+
+    pv.param = "Secret";
+    pvi = std::find(s.moduleParams.begin(), s.moduleParams.end(), pv);
+    if (pvi == s.moduleParams.end() || pvi->value.empty())
+    {
+        m_errorStr = "Parameter \'Secret\' not found.";
+        printfd(__FILE__, "Parameter 'Secret' not found\n");
+        return -1;
+    }
+    else
+        m_secret = pvi->value[0];
+
+    pv.param = "Dictionaries";
+    pvi = std::find(s.moduleParams.begin(), s.moduleParams.end(), pv);
+    if (pvi != s.moduleParams.end() && !pvi->value.empty())
+        m_dictionaries = pvi->value[0];
+
+    const auto authIt = std::find(s.moduleParams.begin(), s.moduleParams.end(), ParamValue("auth", {}));
+    if (authIt != s.moduleParams.end())
+        m_auth = parseASection(authIt->sections);
+
+    const auto autzIt = std::find(s.moduleParams.begin(), s.moduleParams.end(), ParamValue("autz", {}));
+    if (autzIt != s.moduleParams.end())
+        m_autz = parseASection(autzIt->sections);
+
+    printfd(__FILE__, " auth.match = \"%s\"\n", ShowRules(m_auth.match).c_str());
+    printfd(__FILE__, " auth.send = \"%s\"\n", ShowRules(m_auth.send).c_str());
+    printfd(__FILE__, " autz.match = \"%s\"\n", ShowRules(m_autz.match).c_str());
+    printfd(__FILE__, " autz.send = \"%s\"\n", ShowRules(m_autz.send).c_str());
+
+    return 0;
+}
+
diff --git a/projects/stargazer/plugins/other/radius/config.h b/projects/stargazer/plugins/other/radius/config.h
new file mode 100644 (file)
index 0000000..74e483a
--- /dev/null
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "stg/module_settings.h"
+#include "stg/subscriptions.h"
+#include "stg/logger.h"
+
+#include <string>
+#include <cstdint> //uint8_t, uint32_t
+
+namespace STG
+{
+    struct Settings;
+
+    class Config
+    {
+        public:
+            Config();
+
+            struct AttrValue
+            {
+                enum class Type
+                {
+                    PARAM_NAME,
+                    VALUE
+                };
+                std::string value;
+                Type type;
+            };
+
+            struct ASection
+            {
+                using Pairs = std::vector<std::pair<std::string, AttrValue>>;
+                Pairs match;
+                Pairs send;
+            };
+
+            const std::string& GetStrError() const { return m_errorStr; }
+            int ParseSettings(const ModuleSettings& s);
+
+            uint16_t GetPort() const { return m_port; }
+            const std::string& GetDictionaries() const { return m_dictionaries; }
+            const std::string& GetSecret() const { return m_secret; }
+            const ASection& getAuth() const { return m_auth; }
+            const ASection& getAutz() const { return m_autz; }
+
+        private:
+            std::vector<std::pair<std::string, AttrValue>> ParseRules(const std::string& value, const std::string& paramName);
+            ASection parseASection(const std::vector<ParamValue>& conf);
+
+            std::string m_errorStr;
+            uint16_t m_port;
+            std::string m_dictionaries;
+            std::string m_secret;
+
+            ASection m_auth;
+            ASection m_autz;
+
+            PluginLogger m_logger;
+    };
+}
index 8e5b37a895edc3f5b2cdb195087faf5787276721..11fbcf7be132d9547732c0ad16b8f5b126d74b48 100644 (file)
@@ -1,12 +1,11 @@
 #include "radius.h"
 #include "radproto/error.h"
 #include "stg/common.h"
+#include <boost/tokenizer.hpp>
 
-#include <iterator>
-#include <iostream>
+#include <utility>
 
 using STG::RADIUS;
-using STG::RAD_SETTINGS;
 
 extern "C" STG::Plugin* GetPlugin()
 {
@@ -14,60 +13,18 @@ extern "C" STG::Plugin* GetPlugin()
     return &plugin;
 }
 
-RAD_SETTINGS::RAD_SETTINGS()
-    : m_port(1812),
-      m_dictionaries("/usr/share/freeradius/dictionary")
-{}
-
-int RAD_SETTINGS::ParseSettings(const ModuleSettings & s)
-{
-    ParamValue pv;
-    int p;
-
-    pv.param = "Port";
-    auto pvi = std::find(s.moduleParams.begin(), s.moduleParams.end(), pv);
-    if (pvi != s.moduleParams.end() && !pvi->value.empty())
-    {
-        if (ParseIntInRange(pvi->value[0], 2, 65535, &p) != 0)
-        {
-            m_errorStr = "Cannot parse parameter \'Port\': " + m_errorStr;
-            printfd(__FILE__, "Cannot parse parameter 'Port'\n");
-            return -1;
-        }
-        m_port = static_cast<uint16_t>(p);
-    }
-
-    pv.param = "Secret";
-    pvi = std::find(s.moduleParams.begin(), s.moduleParams.end(), pv);
-    if (pvi == s.moduleParams.end() || pvi->value.empty())
-    {
-        m_errorStr = "Parameter \'Secret\' not found.";
-        printfd(__FILE__, "Parameter 'Secret' not found\n");
-        m_secret = "";
-    }
-    else
-    {
-        m_secret = pvi->value[0];
-    }
-
-    pv.param = "Dictionaries";
-    pvi = std::find(s.moduleParams.begin(), s.moduleParams.end(), pv);
-    if (pvi != s.moduleParams.end() && !pvi->value.empty())
-        m_dictionaries = pvi->value[0];
-    return 0;
-}
-
 RADIUS::RADIUS()
     : m_running(false),
+      m_users(nullptr),
       m_logger(PluginLogger::get("radius"))
 {
 }
 
 int RADIUS::ParseSettings()
 {
-    auto ret = m_radSettings.ParseSettings(m_settings);
+    auto ret = m_config.ParseSettings(m_settings);
     if (ret != 0)
-        m_errorStr = m_radSettings.GetStrError();
+        m_errorStr = m_config.GetStrError();
 
     return ret;
 }
@@ -116,8 +73,8 @@ int RADIUS::Run(std::stop_token token)
     try
     {
         if (!m_server)
-           m_server = std::make_unique<Server>(m_ioService, m_radSettings.GetSecret(), m_radSettings.GetPort(), m_radSettings.GetDictionaries(), std::move(token), m_logger);
-        m_ioService.run();
+           m_server = std::make_unique<Server>(m_ioContext, m_config.GetSecret(), m_config.GetPort(), m_config.GetDictionaries(), std::move(token), m_logger, m_users, m_config);
+        m_ioContext.run();
     }
     catch (const std::exception& e)
     {
index 6b742de5395bf4c60b493bb79e4764142371ab55..59cbf0d5afc467ac290c6555716f18869790fb4f 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "stg/auth.h"
 #include "stg/plugin.h"
+#include "config.h"
 #include "stg/module_settings.h"
 #include "stg/subscriptions.h"
 #include "stg/logger.h"
 
 namespace STG
 {
-    struct Settings;
-
-    class RAD_SETTINGS
-    {
-        public:
-            RAD_SETTINGS();
-            virtual ~RAD_SETTINGS() {}
-            const std::string & GetStrError() const { return m_errorStr; }
-            int ParseSettings(const ModuleSettings & s);
-
-            uint16_t GetPort() const { return m_port; }
-            const std::string & GetDictionaries() const { return m_dictionaries; }
-            const std::string & GetSecret() const { return m_secret; }
-
-        private:
-            std::string m_errorStr;
-            uint16_t m_port;
-            std::string m_dictionaries;
-            std::string m_secret;
-    };
+    class Users;
 
     class RADIUS : public Auth
     {
@@ -44,37 +26,38 @@ namespace STG
             RADIUS(const RADIUS&) = delete;
             RADIUS& operator=(const RADIUS&) = delete;
 
-            void SetSettings(const ModuleSettings & s) override { m_settings = s; }
+            void SetUsers(Users* u) override { m_users = u; }
+            void SetSettings(const ModuleSettings& s) override { m_settings = s; }
             int ParseSettings() override;
 
             int Start() override;
             int Stop() override;
-            int Reload(const ModuleSettings & /*ms*/) override { return 0; }
+            int Reload(const ModuleSettings& /*ms*/) override { return 0; }
             bool IsRunning() override;
-            void SetRunning(bool val);
 
-            const std::string & GetStrError() const override { return m_errorStr; }
+            const std::string& GetStrError() const override { return m_errorStr; }
             std::string GetVersion() const override;
 
             uint16_t GetStartPosition() const override { return 0; }
             uint16_t GetStopPosition() const override { return 0; }
 
-            int SendMessage(const Message & msg, uint32_t ip) const override { return 0; }
+            int SendMessage(const Message& /*msg*/, uint32_t /*ip*/) const override { return 0; }
 
         private:
             std::mutex m_mutex;
 
-            boost::asio::io_service m_ioService;
+            boost::asio::io_context m_ioContext;
+            void SetRunning(bool val);
             int Run(std::stop_token token);
 
             mutable std::string m_errorStr;
-            RAD_SETTINGS m_radSettings;
+            Config m_config;
             ModuleSettings m_settings;
 
             bool m_running;
 
             std::jthread m_thread;
-
+            Users* m_users;
             PluginLogger m_logger;
 
             std::unique_ptr<Server> m_server;
index 850847fa1ad711f9667b366de2e3fd6e23278bc4..11aa9a79344ee506c28e2b4f7d1fa1efe3af4ccd 100644 (file)
@@ -1,15 +1,26 @@
 #include "server.h"
+#include "radproto/attribute.h"
 #include "radproto/packet_codes.h"
+#include "radproto/attribute_codes.h"
+#include "stg/user.h"
+#include "stg/users.h"
 #include "stg/common.h"
+#include <vector>
+#include <string>
+#include <sstream>
 #include <cstring>
 #include <functional>
+#include <cstdint> //uint8_t, uint32_t
 
 using STG::Server;
+using STG::User;
 using boost::system::error_code;
 
-Server::Server(boost::asio::io_service& io_service, const std::string& secret, uint16_t port, const std::string& filePath, std::stop_token token, PluginLogger& logger)
-    : m_radius(io_service, secret, port),
+Server::Server(boost::asio::io_context& io_context, const std::string& secret, uint16_t port, const std::string& filePath, std::stop_token token, PluginLogger& logger, Users* users, const Config& config)
+    : m_radius(io_context, secret, port),
       m_dictionaries(filePath),
+      m_users(users),
+      m_config(config),
       m_token(std::move(token)),
       m_logger(logger)
 {
@@ -32,26 +43,45 @@ void Server::startReceive()
     m_radius.asyncReceive([this](const auto& error, const auto& packet, const boost::asio::ip::udp::endpoint& source){ handleReceive(error, packet, source); });
 }
 
-RadProto::Packet Server::makeResponse(const RadProto::Packet& request)
+std::vector<RadProto::Attribute*> Server::makeAttributes(const User* user)
 {
     std::vector<RadProto::Attribute*> attributes;
-    attributes.push_back(new RadProto::String(m_dictionaries.attributeCode("User-Name"), "test"));
-    attributes.push_back(new RadProto::Integer(m_dictionaries.attributeCode("NAS-Port"), 20));
-    std::array<uint8_t, 4> address {127, 104, 22, 17};
-    attributes.push_back(new RadProto::IpAddress(m_dictionaries.attributeCode("NAS-IP-Address"), address));
-    std::vector<uint8_t> bytes {'1', '2', '3', 'a', 'b', 'c'};
-    attributes.push_back(new RadProto::Bytes(m_dictionaries.attributeCode("Callback-Number"), bytes));
-    std::vector<uint8_t> chapPassword {'1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g' };
-    attributes.push_back(new RadProto::ChapPassword(m_dictionaries.attributeCode("CHAP-Password"), 1, chapPassword));
-
-    std::vector<RadProto::VendorSpecific> vendorSpecific;
-    std::vector<uint8_t> vendorValue {0, 0, 0, 3};
-    vendorSpecific.push_back(RadProto::VendorSpecific(m_dictionaries.vendorCode("Dlink"), m_dictionaries.vendorAttributeCode("Dlink", "Dlink-User-Level"), vendorValue));
-
-    if (request.type() == RadProto::ACCESS_REQUEST)
-        return RadProto::Packet(RadProto::ACCESS_ACCEPT, request.id(), request.auth(), attributes, vendorSpecific);
-
-    return RadProto::Packet(RadProto::ACCESS_REJECT, request.id(), request.auth(), attributes, vendorSpecific);
+
+    for (const auto& at : m_config.getAuth().send)
+    {
+        std::string attrValue;
+
+        if (at.second.type == Config::AttrValue::Type::PARAM_NAME)
+            attrValue = user->GetParamValue(at.second.value);
+        else
+            attrValue = at.second.value;
+
+        const auto attrName = at.first;
+        const auto attrCode = m_dictionaries.attributeCode(attrName);
+        const auto attrType = m_dictionaries.attributeType(attrCode);
+
+        if ((attrType == "integer") && (m_dictionaries.attributeValueFindByName(attrName, attrValue)))
+            attributes.push_back(RadProto::Attribute::make(attrCode, attrType, std::to_string(m_dictionaries.attributeValueCode(attrName, attrValue))));
+        else
+            attributes.push_back(RadProto::Attribute::make(attrCode, attrType, attrValue));
+    }
+    return attributes;
+}
+
+RadProto::Packet Server::makeResponse(const RadProto::Packet& request)
+{
+    if (request.code() != RadProto::ACCESS_REQUEST)
+        return RadProto::Packet(RadProto::ACCESS_REJECT, request.id(), request.auth(), {}, {});
+
+    const User* user;
+
+    user = findUser(request);
+
+    if (user != nullptr)
+        return RadProto::Packet(RadProto::ACCESS_ACCEPT, request.id(), request.auth(), makeAttributes(user), {});
+
+    printfd(__FILE__, "Error findUser\n");
+    return RadProto::Packet(RadProto::ACCESS_REJECT, request.id(), request.auth(), {}, {});
 }
 
 void Server::handleSend(const error_code& ec)
@@ -76,6 +106,7 @@ void Server::handleReceive(const error_code& error, const std::optional<RadProto
     {
         m_logger("Error asyncReceive: %s", error.message().c_str());
         printfd(__FILE__, "Error asyncReceive: '%s'\n", error.message().c_str());
+        return;
     }
 
     if (packet == std::nullopt)
@@ -84,5 +115,38 @@ void Server::handleReceive(const error_code& error, const std::optional<RadProto
         printfd(__FILE__, "Error asyncReceive: the request packet is missing\n");
         return;
     }
+
     m_radius.asyncSend(makeResponse(*packet), source, [this](const auto& ec){ handleSend(ec); });
 }
+
+const User* Server::findUser(const RadProto::Packet& packet)
+{
+    std::string login;
+    std::string password;
+    for (const auto& attribute : packet.attributes())
+    {
+        if (attribute->code() == RadProto::USER_NAME)
+            login = attribute->toString();
+
+        if (attribute->code() == RadProto::USER_PASSWORD)
+            password = attribute->toString();
+    }
+
+    User* user = nullptr;
+    if (m_users->FindByName(login, &user))
+    {
+        m_logger("User '%s' not found.", login.c_str());
+        printfd(__FILE__, "User '%s' NOT found!\n", login.c_str());
+        return nullptr;
+    }
+
+    printfd(__FILE__, "User '%s' FOUND!\n", user->GetLogin().c_str());
+
+    if (password != user->GetProperties().password.Get())
+    {
+        m_logger("User's password is incorrect.");
+        printfd(__FILE__, "User's password is incorrect.\n");
+        return nullptr;
+    }
+    return user;
+}
index e33a23b3f20bcc99113776a0a08792b91956b42c..1523179ed877a2edc6efaff6ed0108f524d2913d 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "radproto/socket.h"
 #include "radproto/packet.h"
+#include "config.h"
 #include "radproto/dictionaries.h"
 #include "stg/logger.h"
 #include <boost/asio.hpp>
 
 namespace STG
 {
+    class Users;
+    class User;
+
     class Server
     {
         public:
-            Server(boost::asio::io_service& io_service, const std::string& secret, uint16_t port, const std::string& filePath, std::stop_token token, PluginLogger& logger);
+            Server(boost::asio::io_context& io_context, const std::string& secret, uint16_t port, const std::string& filePath, std::stop_token token, PluginLogger& logger, Users* users, const Config& config);
             void stop();
         private:
+            std::vector<RadProto::Attribute*> makeAttributes(const User* user);
             RadProto::Packet makeResponse(const RadProto::Packet& request);
+            const User* findUser(const RadProto::Packet& packet);
             void handleReceive(const boost::system::error_code& error, const std::optional<RadProto::Packet>& packet, const boost::asio::ip::udp::endpoint& source);
             void handleSend(const boost::system::error_code& ec);
             void start();
@@ -25,6 +31,8 @@ namespace STG
 
             RadProto::Socket m_radius;
             RadProto::Dictionaries m_dictionaries;
+            Users* m_users;
+            const Config& m_config;
             std::stop_token m_token;
 
             PluginLogger& m_logger;