git: 88938fcfb8d1 - main - security/wazuh-agent: Add users and groups function support
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 15 Jan 2026 02:25:41 UTC
The branch main has been updated by acm:
URL: https://cgit.FreeBSD.org/ports/commit/?id=88938fcfb8d169ecc932f3bea0d51c5316e43372
commit 88938fcfb8d169ecc932f3bea0d51c5316e43372
Author: Jose Alonso Cardenas Marquez <acm@FreeBSD.org>
AuthorDate: 2026-01-15 02:18:58 +0000
Commit: Jose Alonso Cardenas Marquez <acm@FreeBSD.org>
CommitDate: 2026-01-15 02:18:58 +0000
security/wazuh-agent: Add users and groups function support
- Now wazuh-agent can obtain users and groups information
- Fix start_time data to show correct datetime data from wazuh-dashboard processes option
- Bump PORTREVISION
---
security/wazuh-agent/Makefile | 3 +-
.../files/patch-src_data__provider-CMakeLists.txt | 39 +++
.../files/patch-src_data__provider_CMakeLists.txt | 11 -
...atch-src_data__provider_src-sysInfoFreeBSD.cpp} | 300 +++++++++++++++++----
...__provider_src_extended__sources-CMakeLists.txt | 18 ++
...der_src_extended__sources_groups-CMakeLists.txt | 22 ++
...nded__sources_groups_include-groups_freebsd.hpp | 46 ++++
..._sources_groups_include-user_groups_freebsd.hpp | 84 ++++++
...extended__sources_groups_src-groups_freebsd.hpp | 95 +++++++
...ded__sources_groups_src-user_groups_freebsd.hpp | 262 ++++++++++++++++++
...ider_src_extended__sources_users-CMakeLists.txt | 21 ++
...urces_users_include-logged_in_users_freebsd.hpp | 37 +++
...tended__sources_users_include-users_freebsd.hpp | 69 +++++
...__sources_users_src-logged_in_users_freebsd.cpp | 72 +++++
...c_extended__sources_users_src-users_freebsd.cpp | 106 ++++++++
...ended__sources_wrappers_unix-iutmpx_wrapper.hpp | 12 +
...tended__sources_wrappers_unix-utmpx_wrapper.hpp | 16 ++
...sources_wrappers_unix_freebsd-group_wrapper.hpp | 93 +++++++
...ources_wrappers_unix_freebsd-igroup_wrapper.hpp | 70 +++++
...urces_wrappers_unix_freebsd-ipasswd_wrapper.hpp | 72 +++++
...ources_wrappers_unix_freebsd-passwd_wrapper.hpp | 96 +++++++
21 files changed, 1482 insertions(+), 62 deletions(-)
diff --git a/security/wazuh-agent/Makefile b/security/wazuh-agent/Makefile
index 7fce39e00bf9..c5f31572243c 100644
--- a/security/wazuh-agent/Makefile
+++ b/security/wazuh-agent/Makefile
@@ -1,7 +1,7 @@
PORTNAME= wazuh
DISTVERSION= 4.14.1
DISTVERSIONPREFIX= v
-PORTREVISION= 5
+PORTREVISION= 6
CATEGORIES= security
MASTER_SITES= https://packages.wazuh.com/deps/47/libraries/sources/:wazuh_sources
PKGNAMESUFFIX= -agent
@@ -120,6 +120,7 @@ post-extract:
@cd ${WRKSRC}/src/external && ${EXTRACT_CMD} ${EXTRACT_BEFORE_ARGS} ${_DISTDIR}/${FILE:S/:wazuh_sources//} ${EXTRACT_AFTER_ARGS}
.endfor
@${MKDIR} ${WRKSRC}/ruleset/sca/freebsd
+ @${MKDIR} ${WRKSRC}/src/data_provider/src/extended_sources/wrappers/unix/freebsd
@cd ${WRKDIR} && ${EXTRACT_CMD} ${EXTRACT_BEFORE_ARGS} ${_DISTDIR}/${WAZUH_EXTRAFILE} ${EXTRACT_AFTER_ARGS}
@${MV} ${WRKDIR}/${PORTNAME}-freebsd-${WAZUH_EXTRAFILE_TAGNAME} ${WRKDIR}/wazuh-freebsd
@cd ${WRKDIR}/wazuh-freebsd/var/ossec/ruleset/sca && ${CP} *.yml ${WRKSRC}/ruleset/sca/freebsd/
diff --git a/security/wazuh-agent/files/patch-src_data__provider-CMakeLists.txt b/security/wazuh-agent/files/patch-src_data__provider-CMakeLists.txt
new file mode 100644
index 000000000000..03dd0efcd223
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider-CMakeLists.txt
@@ -0,0 +1,39 @@
+--- src/data_provider/CMakeLists.txt 2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/CMakeLists.txt 2026-01-13 15:21:46.999172000 +0000
+@@ -104,7 +104,6 @@
+ include_directories(${CMAKE_SOURCE_DIR}/src/extended_sources/wrappers/unix/darwin)
+ endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+
+-
+ if(CMAKE_SYSTEM_NAME STREQUAL "HP-UX")
+ link_directories(${INSTALL_PREFIX}/lib)
+ endif(CMAKE_SYSTEM_NAME STREQUAL "HP-UX")
+@@ -119,6 +118,11 @@
+ include_directories(${CMAKE_SOURCE_DIR}/src/extended_sources/wrappers/unix/darwin)
+ endif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+
++if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++ include_directories(${CMAKE_SOURCE_DIR}/src/extended_sources/wrappers/unix/)
++ include_directories(${CMAKE_SOURCE_DIR}/src/extended_sources/wrappers/unix/freebsd)
++endif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++
+ link_directories(${SRC_FOLDER})
+ link_directories(${SRC_FOLDER}/external/sqlite/)
+ link_directories(${SRC_FOLDER}/external/cJSON/)
+@@ -210,6 +214,7 @@
+
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR
+ CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
++ CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR
+ CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ add_subdirectory(src/extended_sources)
+ endif()
+@@ -250,7 +255,7 @@
+ target_link_libraries(sysinfo cjson ${SRC_FOLDER}/external/libplist/bin/lib/libplist-2.0.a ${iokit_lib} ${corefoundation_lib} groups users services browser_extensions)
+ endif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+
+-target_link_libraries(sysinfo wazuhext)
++target_link_libraries(sysinfo nghttp2 wazuhext users groups)
+
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ set(CURL_DEP "wazuhext")
diff --git a/security/wazuh-agent/files/patch-src_data__provider_CMakeLists.txt b/security/wazuh-agent/files/patch-src_data__provider_CMakeLists.txt
deleted file mode 100644
index 0cec8b79cb08..000000000000
--- a/security/wazuh-agent/files/patch-src_data__provider_CMakeLists.txt
+++ /dev/null
@@ -1,11 +0,0 @@
---- src/data_provider/CMakeLists.txt.orig 2023-05-24 19:23:05 UTC
-+++ src/data_provider/CMakeLists.txt
-@@ -152,7 +152,7 @@ elseif(APPLE)
- target_link_libraries(sysinfo cjson ${SRC_FOLDER}/external/libplist/bin/lib/libplist-2.0.a)
- endif(CMAKE_SYSTEM_NAME STREQUAL "Windows")
-
--target_link_libraries(sysinfo wazuhext)
-+target_link_libraries(sysinfo nghttp2 wazuhext)
-
-
- if(APPLE)
diff --git a/security/wazuh-agent/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp b/security/wazuh-agent/files/patch-src_data__provider_src-sysInfoFreeBSD.cpp
similarity index 68%
rename from security/wazuh-agent/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp
rename to security/wazuh-agent/files/patch-src_data__provider_src-sysInfoFreeBSD.cpp
index 9fb64aa3c105..8ef1dd376f3e 100644
--- a/security/wazuh-agent/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp
+++ b/security/wazuh-agent/files/patch-src_data__provider_src-sysInfoFreeBSD.cpp
@@ -1,6 +1,6 @@
---- src/data_provider/src/sysInfoFreeBSD.cpp.orig 2025-11-07 04:46:03.000000000 -0400
-+++ src/data_provider/src/sysInfoFreeBSD.cpp 2026-01-06 19:37:15.309352000 -0400
-@@ -11,20 +11,28 @@
+--- src/data_provider/src/sysInfoFreeBSD.cpp 2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/src/sysInfoFreeBSD.cpp 2026-01-14 16:59:37.014537000 +0000
+@@ -11,20 +11,33 @@
#include "sysInfo.hpp"
#include "cmdHelper.h"
#include "stringHelper.h"
@@ -13,6 +13,11 @@
#include <sys/utsname.h>
#include "sharedDefs.h"
+#include <regex>
++#include "groups_freebsd.hpp"
++#include "user_groups_freebsd.hpp"
++#include "logged_in_users_freebsd.hpp"
++#include "sudoers_unix.hpp"
++#include "users_freebsd.hpp"
+const std::string PKG_DB_PATHNAME {"/var/db/pkg/local.sqlite"};
+const std::string PKG_QUERY {"SELECT p.name, p.maintainer, p.version, p.arch, p.comment, p.flatsize, p.time, v.annotation AS repository,p.origin FROM packages p LEFT JOIN (SELECT pa.package_id, pa.value_id FROM pkg_annotation pa JOIN annotation t ON t.annotation_id = pa.tag_id AND t.annotation = 'repository') pr ON pr.package_id = p.id LEFT JOIN annotation v ON v.annotation_id = pr.value_id;"};
@@ -32,7 +37,7 @@
if (ret)
{
-@@ -52,11 +60,23 @@
+@@ -52,11 +65,23 @@
};
}
@@ -59,7 +64,7 @@
if (ret)
{
-@@ -64,11 +84,11 @@
+@@ -64,11 +89,11 @@
{
ret,
std::system_category(),
@@ -73,7 +78,7 @@
info["ram_free"] = ramFree;
info["ram_usage"] = 100 - (100 * ramFree / ramTotal);
}
-@@ -96,7 +116,43 @@
+@@ -96,7 +121,43 @@
static std::string getSerialNumber()
{
@@ -118,7 +123,7 @@
}
static int getCpuCores()
-@@ -184,8 +240,12 @@
+@@ -184,8 +245,12 @@
nlohmann::json SysInfo::getProcessesInfo() const
{
@@ -133,7 +138,7 @@
}
nlohmann::json SysInfo::getOsInfo() const
-@@ -196,11 +256,12 @@
+@@ -196,11 +261,12 @@
if (!spParser->parseUname(Utils::exec("uname -r"), ret))
{
@@ -147,7 +152,7 @@
if (uname(&uts) >= 0)
{
ret["sysname"] = uts.sysname;
-@@ -215,44 +276,257 @@
+@@ -215,44 +281,260 @@
nlohmann::json SysInfo::getPorts() const
{
@@ -164,23 +169,19 @@
-void SysInfo::getProcessesInfo(std::function<void(nlohmann::json&)> /*callback*/) const
-{
- // Currently not supported for this OS.
--}
+ if (!query.empty())
+ {
+ nlohmann::json portsjson;
+ portsjson = nlohmann::json::parse(query);
+ auto &portsResult = portsjson["sockstat"]["socket"];
-
--void SysInfo::getPackages(std::function<void(nlohmann::json&)> callback) const
--{
-- const auto query{Utils::exec(R"(pkg query -a "%n|%m|%v|%q|%c")")};
++
+ for(auto &port : portsResult) {
+ std::string localip = "";
+ std::string localport = "";
+ std::string remoteip = "";
+ std::string remoteport = "";
+ std::string statedata = "";
-
++
+ if (port["pid"] != nullptr) {
+
+ localip = port["local"]["address"];
@@ -228,32 +229,16 @@
+#else
+ const auto query{Utils::exec(R"(sockstat -46qs)")};
+
- if (!query.empty())
- {
-- const auto lines{Utils::split(query, '\n')};
++ if (!query.empty())
++ {
+ const auto lines{Utils::split(Utils::trimToOneSpace(query), '\n')};
-
++
+ std::regex expression(R"(^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s*(\S+)\s+(\S+)\s+(\S+)(?:\s+(\S+))?\s*$)");
+
- for (const auto& line : lines)
- {
-- const auto data{Utils::split(line, '|')};
-- nlohmann::json package;
++ for (const auto& line : lines)
++ {
+ std::smatch data;
-
-- package["name"] = data[0];
-- package["vendor"] = data[1];
-- package["version"] = data[2];
-- package["install_time"] = UNKNOWN_VALUE;
-- package["location"] = UNKNOWN_VALUE;
-- package["architecture"] = data[3];
-- package["groups"] = UNKNOWN_VALUE;
-- package["description"] = data[4];
-- package["size"] = 0;
-- package["priority"] = UNKNOWN_VALUE;
-- package["source"] = UNKNOWN_VALUE;
-- package["format"] = "pkg";
-- // The multiarch field won't have a default value
++
+ if (std::regex_search(line, data, expression))
+ {
+ std::string localip = "";
@@ -261,8 +246,7 @@
+ std::string remoteip = "";
+ std::string remoteport = "";
+ std::string statedata = "";
-
-- callback(package);
++
+ auto localdata{Utils::split(data[6], ':')};
+ auto remotedata{Utils::split(data[7], ':')};
+
@@ -315,21 +299,28 @@
+ }
+#endif
+ return ports;
-+}
-+
+ }
+
+-void SysInfo::getPackages(std::function<void(nlohmann::json&)> callback) const
+void SysInfo::getProcessesInfo(std::function<void(nlohmann::json&)> callback) const
-+{
+ {
+- const auto query{Utils::exec(R"(pkg query -a "%n|%m|%v|%q|%c")")};
+ const auto query{Utils::exec(R"(ps -ax -w -o pid,comm,state,ppid,usertime,systime,user,ruser,svuid,group,rgroup,svgid,pri,nice,ssiz,vsz,rss,pmem,etimes,sid,pgid,tpgid,tty,cpu,nlwp,args --libxo json)")};
-+
-+ if (!query.empty())
-+ {
+
+ if (!query.empty())
+ {
+- const auto lines{Utils::split(query, '\n')};
+ nlohmann::json psjson;
++ int64_t agenttime = {Utils::getSecondsFromEpoch()};
++ int64_t etimes{0};
+ psjson = nlohmann::json::parse(query);
+ auto &processes = psjson["process-information"]["process"];
-+
+
+- for (const auto& line : lines)
+ for(auto &process : processes) {
+ std::string user_time{""};
+ std::string system_time{""};
++ etimes = std::stoll(process["elapsed-times"].get<std::string>());
+
+ user_time = process["user-time"].get<std::string>();
+ system_time = process["system-time"].get<std::string>();
@@ -356,7 +347,7 @@
+ jsProcessInfo["vm_size"] = process["virtual-size"].get<std::string>();
+ jsProcessInfo["resident"] = process["rss"].get<std::string>();
+ //jsProcessInfo["share"] = process["percent-memory"].get<std::string>();
-+ jsProcessInfo["start_time"] = process["elapsed-times"].get<std::string>() == "-" ? "0" : process["elapsed-times"].get<std::string>();
++ jsProcessInfo["start_time"] = agenttime - etimes;
+ jsProcessInfo["pgrp"] = process["process-group"].get<std::string>();
+ jsProcessInfo["session"] = process["sid"].get<std::string>();
+ jsProcessInfo["tgid"] = process["terminal-process-gid"].get<std::string>();
@@ -374,15 +365,31 @@
+ if (Utils::existsRegular(PKG_DB_PATHNAME))
+ {
+ try
-+ {
+ {
+- const auto data{Utils::split(line, '|')};
+- nlohmann::json package;
+ std::shared_ptr<SQLite::IConnection> sqliteConnection = std::make_shared<SQLite::Connection>(PKG_DB_PATHNAME, SQLITE_OPEN_READONLY);
-+
+
+- package["name"] = data[0];
+- package["vendor"] = data[1];
+- package["version"] = data[2];
+- package["install_time"] = UNKNOWN_VALUE;
+- package["location"] = UNKNOWN_VALUE;
+- package["architecture"] = data[3];
+- package["groups"] = UNKNOWN_VALUE;
+- package["description"] = data[4];
+- package["size"] = 0;
+- package["priority"] = UNKNOWN_VALUE;
+- package["source"] = UNKNOWN_VALUE;
+- package["format"] = "pkg";
+- // The multiarch field won't have a default value
+ SQLite::Statement stmt
+ {
+ sqliteConnection,
+ PKG_QUERY
+ };
-+
+
+- callback(package);
+ while (SQLITE_ROW == stmt.step())
+ {
+ try
@@ -432,3 +439,196 @@
}
}
+@@ -264,14 +546,188 @@
+
+ nlohmann::json SysInfo::getGroups() const
+ {
+- //TODO: Pending implementation.
+- return nlohmann::json();
++ nlohmann::json result;
++ GroupsProvider groupsProvider;
++ UserGroupsProvider userGroupsProvider;
++
++ auto collectedGroups = groupsProvider.collect({});
++
++ for (auto& group : collectedGroups)
++ {
++ nlohmann::json groupItem {};
++
++ groupItem["group_id"] = group["gid"];
++ groupItem["group_name"] = (group.contains("groupname") && !group["groupname"].get<std::string>().empty()) ? group["groupname"] : UNKNOWN_VALUE;
++ groupItem["group_description"] = UNKNOWN_VALUE;
++ groupItem["group_id_signed"] = group["gid_signed"];
++ groupItem["group_uuid"] = UNKNOWN_VALUE;
++ groupItem["group_is_hidden"] = 0;
++
++ std::set<gid_t> gids {static_cast<gid_t>(group["gid"].get<int>())};
++ auto collectedUsersGroups = userGroupsProvider.getUserNamesByGid(gids);
++
++ if (collectedUsersGroups.empty())
++ {
++ groupItem["group_users"] = UNKNOWN_VALUE;
++ }
++ else
++ {
++ std::string usersConcatenated;
++
++ for (const auto& user : collectedUsersGroups)
++ {
++ if (!usersConcatenated.empty())
++ {
++ usersConcatenated += secondaryArraySeparator;
++ }
++
++ usersConcatenated += user.get<std::string>();
++ }
++
++ groupItem["group_users"] = usersConcatenated;
++ }
++
++ result.push_back(std::move(groupItem));
++
++ }
++
++ return result;
+ }
+
+ nlohmann::json SysInfo::getUsers() const
+ {
+- //TODO: Pending implementation.
+- return nlohmann::json();
++ nlohmann::json result;
++
++ UsersProvider usersProvider;
++ auto collectedUsers = usersProvider.collect();
++
++ LoggedInUsersProvider loggedInUserProvider;
++ auto collectedLoggedInUser = loggedInUserProvider.collect();
++
++ UserGroupsProvider userGroupsProvider;
++
++ for (auto& user : collectedUsers)
++ {
++ nlohmann::json userItem {};
++
++ std::string username = (user.contains("username") && !user["username"].get<std::string>().empty()) ? user["username"] : UNKNOWN_VALUE;
++
++ userItem["user_id"] = user["uid"];
++ userItem["user_full_name"] = user["description"];
++ userItem["user_home"] = user["directory"];
++ userItem["user_is_remote"] = user["include_remote"];
++ userItem["user_name"] = username;
++ userItem["user_shell"] = user["shell"];
++ userItem["user_uid_signed"] = user["uid_signed"];
++ userItem["user_group_id_signed"] = user["gid_signed"];
++ userItem["user_group_id"] = user["gid"];
++
++ std::set<uid_t> uid {static_cast<uid_t>(user["uid"].get<int>())};
++ auto collectedUsersGroups = userGroupsProvider.getGroupNamesByUid(uid);
++
++ if (collectedUsersGroups.empty())
++ {
++ userItem["user_groups"] = UNKNOWN_VALUE;
++ }
++ else
++ {
++ std::string accumGroups;
++
++ for (const auto& group : collectedUsersGroups)
++ {
++ if (!accumGroups.empty())
++ {
++ accumGroups += secondaryArraySeparator;
++ }
++
++ accumGroups += group.get<std::string>();
++ }
++
++ userItem["user_groups"] = accumGroups;
++ }
++
++ // Only in windows
++ userItem["user_type"] = UNKNOWN_VALUE;
++
++ // Macos or windows
++ userItem["user_uuid"] = UNKNOWN_VALUE;
++
++ // Macos
++ userItem["user_is_hidden"] = 0;
++ userItem["user_created"] = 0;
++ userItem["user_auth_failed_count"] = 0;
++ userItem["user_auth_failed_timestamp"] = 0;
++
++ auto matched = false;
++ auto lastLogin = 0;
++
++ userItem["host_ip"] = UNKNOWN_VALUE;
++
++ //TODO: Avoid this iteration, move logic to LoggedInUsersProvider
++ for (auto& item : collectedLoggedInUser)
++ {
++ // By default, user is not logged in.
++ userItem["login_status"] = 0;
++
++ // tty,host,time and pid can take more than one value due to different logins.
++ if (item["user"] == username)
++ {
++ matched = true;
++ userItem["login_status"] = 1;
++
++ auto newDate = item["time"].get<int32_t>();
++
++ if (newDate > lastLogin)
++ {
++ lastLogin = newDate;
++ userItem["user_last_login"] = newDate;
++ userItem["login_tty"] = item["tty"].get<std::string>();
++ userItem["login_type"] = item["type"].get<std::string>();
++ userItem["process_pid"] = item["pid"].get<int32_t>();
++ }
++
++ const auto& hostStr = item["host"].get_ref<const std::string&>();
++
++ if (!hostStr.empty())
++ {
++ userItem["host_ip"] = userItem["host_ip"].get<std::string>() == UNKNOWN_VALUE
++ ? hostStr
++ : (userItem["host_ip"].get<std::string>() + primaryArraySeparator + hostStr);
++ }
++ }
++ }
++
++ if (!matched)
++ {
++ userItem["login_status"] = 0;
++ userItem["login_tty"] = UNKNOWN_VALUE;
++ userItem["login_type"] = UNKNOWN_VALUE;
++ userItem["process_pid"] = 0;
++ userItem["user_last_login"] = 0;
++ }
++
++ matched = false;
++
++ if (!matched)
++ {
++ userItem["user_password_expiration_date"] = 0;
++ userItem["user_password_hash_algorithm"] = UNKNOWN_VALUE;
++ userItem["user_password_inactive_days"] = 0;
++ userItem["user_password_last_change"] = 0;
++ userItem["user_password_max_days_between_changes"] = 0;
++ userItem["user_password_min_days_between_changes"] = 0;
++ userItem["user_password_status"] = UNKNOWN_VALUE;
++ userItem["user_password_warning_days_before_expiration"] = 0;
++ }
++
++
++ // By default, user is not sudoer.
++ userItem["user_roles"] = UNKNOWN_VALUE;
++
++ result.push_back(std::move(userItem));
++ }
++
++ return result;
+ }
+
+ nlohmann::json SysInfo::getServices() const
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt
new file mode 100644
index 000000000000..458b05f1655e
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt
@@ -0,0 +1,18 @@
+--- src/data_provider/src/extended_sources/CMakeLists.txt 2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/src/extended_sources/CMakeLists.txt 2026-01-13 15:00:46.789677000 +0000
+@@ -1,6 +1,11 @@
+ include_directories(wrappers)
+
+-add_subdirectory(groups)
+-add_subdirectory(services)
+-add_subdirectory(users)
+-add_subdirectory(browser_extensions)
++if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++ add_subdirectory(groups)
++ add_subdirectory(users)
++else()
++ add_subdirectory(groups)
++ add_subdirectory(services)
++ add_subdirectory(users)
++ add_subdirectory(browser_extensions)
++endif()
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt
new file mode 100644
index 000000000000..0f34a531e850
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt
@@ -0,0 +1,22 @@
+--- src/data_provider/src/extended_sources/groups/CMakeLists.txt 2026-01-13 15:01:19.871247000 +0000
++++ src/data_provider/src/extended_sources/groups/CMakeLists.txt 2026-01-13 15:07:52.828437000 +0000
+@@ -18,6 +18,8 @@
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../wrappers/unix/linux)
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../wrappers/unix/darwin)
++ elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++ include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../wrappers/unix/freebsd)
+ endif()
+ endif()
+
+@@ -35,6 +37,10 @@
+ list(APPEND SRC_FILES
+ src/groups_linux.cpp
+ src/user_groups_linux.cpp)
++ elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++ list(APPEND SRC_FILES
++ src/groups_freebsd.cpp
++ src/user_groups_freebsd.cpp)
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ find_library(OPEN_DIRECTORY OpenDirectory)
+ find_library(FOUNDATION Foundation)
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-groups_freebsd.hpp b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-groups_freebsd.hpp
new file mode 100644
index 000000000000..a756e0bb02f7
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-groups_freebsd.hpp
@@ -0,0 +1,46 @@
+--- /dev/null 2026-01-13 23:10:56.926889000 +0000
++++ src/data_provider/src/extended_sources/groups/include/groups_freebsd.hpp 2026-01-13 22:43:51.149789000 +0000
+@@ -0,0 +1,43 @@
++/* Copyright (C) 2015, Wazuh Inc.
++ * All rights reserved.
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General Public
++ * License (version 2) as published by the FSF - Free Software
++ * Foundation.
++ */
++
++#pragma once
++
++#include <set>
++
++#include "json.hpp"
++#include "igroup_wrapper.hpp"
++
++/// @brief Class for collecting group information on FreeBSD systems.
++///
++/// This class provides methods to collect group information from the
++/// Darwin operating system. It uses the system's group database to retrieve
++/// group details such as group name and GID. The collected data is returned
++/// in JSON format.
++class GroupsProvider
++{
++ public:
++ explicit GroupsProvider(std::shared_ptr<IGroupWrapperFreeBSD> groupWrapper);
++
++ /// @brief Default destructor.
++ GroupsProvider();
++
++ /// @brief Collects group information based on provided group IDs.
++ /// @param gids A set of group IDs to collect information for.
++ /// @return A JSON array containing group information.
++ nlohmann::json collect(const std::set<gid_t>& gids = {});
++
++ private:
++ std::shared_ptr<IGroupWrapperFreeBSD> m_groupWrapper;
++
++ /// @brief Adds a group to the results JSON array.
++ /// @param results A reference to the JSON array where the group information will be added.
++ /// @param group A pointer to the group structure containing the group information.
++ void addGroupToResults(nlohmann::json& results, const struct group* group);
++};
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-user_groups_freebsd.hpp b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-user_groups_freebsd.hpp
new file mode 100644
index 000000000000..3fdc455fed39
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_include-user_groups_freebsd.hpp
@@ -0,0 +1,84 @@
+--- /dev/null 2026-01-13 23:11:31.853795000 +0000
++++ src/data_provider/src/extended_sources/groups/include/user_groups_freebsd.hpp 2026-01-13 22:43:51.149735000 +0000
+@@ -0,0 +1,81 @@
++/* Copyright (C) 2015, Wazuh Inc.
++ * All rights reserved.
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General Public
++ * License (version 2) as published by the FSF - Free Software
++ * Foundation.
++ */
++
++#pragma once
++
++#include <set>
++
++#include "json.hpp"
++#include "igroup_wrapper.hpp"
++#include "ipasswd_wrapper.hpp"
++
++#define EXPECTED_GROUPS_MAX 64
++
++class UserGroupsProvider
++{
++ public:
++ /// @brief Constructs a UserGroupsProvider with specific wrappers.
++ /// @param groupWrapper A shared pointer to an IGroupWrapperFreeBSD instance for group operations.
++ /// @param passwdWrapper A shared pointer to an IPasswdWrapperFreeBSD instance for passwd operations.
++ /// @param sysWrapper A shared pointer to an ISystemWrapper instance for system operations.
++ explicit UserGroupsProvider(std::shared_ptr<IGroupWrapperFreeBSD> groupWrapper,
++ std::shared_ptr<IPasswdWrapperFreeBSD> passwdWrapper);
++
++ /// @brief Default constructor that initializes the UserGroupsProvider with default wrappers.
++ /// @note This constructor uses default implementations of IGroupWrapperFreeBSD, and IPasswdWrapperFreeBSD.
++ UserGroupsProvider();
++
++ /// @brief Collects user groups information.
++ /// @param uids A set of user IDs (UIDs) to filter the results. If empty, all users are collected.
++ /// @return A JSON array containing user groups information, where each entry includes UID, GID, and group details.
++ nlohmann::json collect(const std::set<uid_t>& uids = {});
++
++ /// @brief Retrieves group names associated with the specified UIDs.
++ /// @param uids A set of user IDs (UIDs) for which to retrieve group names.
++ /// @return A JSON object where keys are UIDs and values are arrays of group names associated with those UIDs.
++ /// If a UID has no associated groups, the value will be an empty array.
++ /// @note This method is useful for quickly mapping UIDs to their group names without retrieving full group details.
++ /// @note If `uids` is empty, it retrieves group names for all users.
++ nlohmann::json getGroupNamesByUid(const std::set<uid_t>& uids = {});
++
++ /// @brief Retrieves usernames associated with the specified GIDs.
++ /// @param gids A set of group IDs (GIDs) for which to retrieve usernames.
++ /// @return A JSON object where keys are GIDs and values are arrays of usernames associated with those GIDs.
++ /// If a GID has no associated usernames, the value will be an empty array.
++ /// @note This method is useful for quickly mapping GIDs to their usernames without retrieving full user details.
++ /// @note If `gids` is empty, it retrieves usernames for all groups.
++ nlohmann::json getUserNamesByGid(const std::set<gid_t>& gids = {});
++
++ private:
++ std::shared_ptr<IGroupWrapperFreeBSD> m_groupWrapper;
++ std::shared_ptr<IPasswdWrapperFreeBSD> m_passwdWrapper;
++
++ /// @brief Structure to hold user information.
++ struct UserInfo
++ {
++ const char* name;
++ uid_t uid;
++ gid_t gid;
++ };
++
++ /// @brief Retrieves groups for each user and returns a vector of pairs containing UID and their associated groups.
++ /// @param uids A set of user IDs (UIDs) to filter the results. If empty, all users are processed.
++ /// @return A vector of pairs, where each pair contains a UID and a vector of GIDs representing the groups associated with that UID.
++ /// @note This method is used internally to gather user-group associations before formatting the results into JSON.
++ /// @note If a user has no associated groups, the vector of GIDs will be empty.
++ std::vector<std::pair<uid_t, std::vector<gid_t>>> getUserGroups(const std::set<uid_t>& uids);
++
++ /// @brief Adds groups to the results JSON array for a specific user.
++ /// @param results A reference to the JSON array where the group information will be added.
++ /// @param uid The user ID for which the groups are being added.
++ /// @param groups A pointer to an array of group IDs (GIDs) associated with the user.
++ /// @param ngroups The number of groups in the `groups` array.
++ /// @note This method formats the group information into JSON objects and appends them to the results array.
++ void addGroupsToResults(nlohmann::json& results, uid_t uid, const gid_t* groups, int ngroups);
++};
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-groups_freebsd.hpp b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-groups_freebsd.hpp
new file mode 100644
index 000000000000..d65c4b178507
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-groups_freebsd.hpp
@@ -0,0 +1,95 @@
+--- /dev/null 2026-01-13 23:12:14.070467000 +0000
++++ src/data_provider/src/extended_sources/groups/src/groups_freebsd.cpp 2026-01-13 22:43:51.150488000 +0000
+@@ -0,0 +1,92 @@
++/* Copyright (C) 2015, Wazuh Inc.
++ * All rights reserved.
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General Public
++ * License (version 2) as published by the FSF - Free Software
++ * Foundation.
++ */
++
++#include "groups_freebsd.hpp"
++#include "group_wrapper.hpp"
++
++constexpr size_t MAX_GETPW_R_BUF_SIZE = 16 * 1024;
++
++GroupsProvider::GroupsProvider(std::shared_ptr<IGroupWrapperFreeBSD> groupWrapper)
++ : m_groupWrapper(std::move(groupWrapper)) {}
++
++GroupsProvider::GroupsProvider()
++ : m_groupWrapper(std::make_shared<GroupWrapperFreeBSD>()) {}
++
++nlohmann::json GroupsProvider::collect(const std::set<gid_t>& gids)
++{
++ nlohmann::json results = nlohmann::json::array();
++ struct group* groupResult
++ {
++ nullptr
++ };
++ struct group group;
++
++ size_t bufSize = MAX_GETPW_R_BUF_SIZE;
++ auto buf = std::make_unique<char[]>(bufSize);
++
++ if (!gids.empty())
++ {
++ for (const auto& gid : gids)
++ {
++ while (m_groupWrapper->getgrgid_r(gid, &group, buf.get(), bufSize, &groupResult) == ERANGE)
++ {
++ bufSize *= 2;
++ buf = std::make_unique<char[]>(bufSize);
++ }
++
++ if (groupResult == nullptr)
++ {
++ continue;
++ }
++
++ addGroupToResults(results, groupResult);
++ }
++ }
++ else
++ {
++ std::set<long> groupsIn;
++ m_groupWrapper->setgrent();
++
++ while (1)
++ {
++ while (m_groupWrapper->getgrent_r(&group, buf.get(), bufSize, &groupResult) == ERANGE)
++ {
++ bufSize *= 2;
++ buf = std::make_unique<char[]>(bufSize);
++ }
++
++ if (groupResult == nullptr)
++ {
++ break;
++ }
++
++ if (std::find(groupsIn.begin(), groupsIn.end(), groupResult->gr_gid) == groupsIn.end())
++ {
++ addGroupToResults(results, groupResult);
++ groupsIn.insert(groupResult->gr_gid);
++ }
++ }
++
++ m_groupWrapper->endgrent();
++ groupsIn.clear();
++ }
++
++ return results;
++}
++
++void GroupsProvider::addGroupToResults(nlohmann::json& results, const group* group)
++{
++ nlohmann::json groupJson;
++
++ groupJson["groupname"] = group->gr_name;
++ groupJson["gid"] = group->gr_gid;
++ groupJson["gid_signed"] = static_cast<int32_t>(group->gr_gid);
++
++ results.push_back(groupJson);
++}
diff --git a/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-user_groups_freebsd.hpp b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-user_groups_freebsd.hpp
new file mode 100644
index 000000000000..ef6229d3ef82
--- /dev/null
+++ b/security/wazuh-agent/files/patch-src_data__provider_src_extended__sources_groups_src-user_groups_freebsd.hpp
@@ -0,0 +1,262 @@
+--- /dev/null 2026-01-13 23:12:49.062343000 +0000
++++ src/data_provider/src/extended_sources/groups/src/user_groups_freebsd.cpp 2026-01-13 22:43:51.150552000 +0000
+@@ -0,0 +1,259 @@
++/* Copyright (C) 2015, Wazuh Inc.
++ * All rights reserved.
++ *
++ * This program is free software; you can redistribute it
++ * and/or modify it under the terms of the GNU General Public
++ * License (version 2) as published by the FSF - Free Software
++ * Foundation.
++ */
++
++#include <iostream>
++#include <unistd.h>
++#include "user_groups_freebsd.hpp"
++#include "group_wrapper.hpp"
++#include "passwd_wrapper.hpp"
++
++constexpr size_t MAX_GETPW_R_BUF_SIZE = 16 * 1024;
++
++UserGroupsProvider::UserGroupsProvider(std::shared_ptr<IGroupWrapperFreeBSD> groupWrapper,
++ std::shared_ptr<IPasswdWrapperFreeBSD> passwdWrapper)
++ : m_groupWrapper(std::move(groupWrapper))
++ , m_passwdWrapper(std::move(passwdWrapper))
++{
++}
++
++UserGroupsProvider::UserGroupsProvider()
++ : m_groupWrapper(std::make_shared<GroupWrapperFreeBSD>())
++ , m_passwdWrapper(std::make_shared<PasswdWrapperFreeBSD>())
++{
++}
++
++nlohmann::json UserGroupsProvider::collect(const std::set<uid_t>& uids)
++{
++ nlohmann::json results = nlohmann::json::array();
++ auto usersGroups = getUserGroups(uids);
++
++ for (const auto& [uid, groups] : usersGroups)
++ {
++ addGroupsToResults(results, uid, groups.data(), static_cast<int>(groups.size()));
++ }
++
++ return results;
++}
++
++nlohmann::json UserGroupsProvider::getGroupNamesByUid(const std::set<uid_t>& uids)
++{
++ const bool singleUid = (uids.size() == 1);
++ nlohmann::json result = singleUid ? nlohmann::json::array() : nlohmann::json::object();
++ auto usersGroups = getUserGroups(uids);
++
++ size_t bufSize = sysconf(_SC_GETGR_R_SIZE_MAX);
++
++ if (bufSize > MAX_GETPW_R_BUF_SIZE)
++ {
++ bufSize = MAX_GETPW_R_BUF_SIZE;
++ }
++
++ for (const auto& [uid, groups] : usersGroups)
++ {
++ nlohmann::json groupNames = nlohmann::json::array();
++
++ for (const auto& gid : groups)
++ {
++ struct group grp;
++ struct group* grpResult = nullptr;
++ auto groupBuf = std::make_unique<char[]>(bufSize);
++
++ if (m_groupWrapper->getgrgid_r(gid, &grp, groupBuf.get(), bufSize, &grpResult) == 0 && grpResult != nullptr)
++ {
++ groupNames.push_back(grpResult->gr_name);
++ }
++ }
++
++ if (singleUid)
++ {
++ result = groupNames;
++ }
++ else
++ {
++ result[std::to_string(uid)] = groupNames;
++ }
++ }
++
++ return result;
++}
++
++nlohmann::json UserGroupsProvider::getUserNamesByGid(const std::set<gid_t>& gids)
++{
++ const bool allGroups = gids.empty();
++ const bool singleGid = (!allGroups && gids.size() == 1);
++ nlohmann::json result = singleGid ? nlohmann::json::array() : nlohmann::json::object();
++
++ size_t bufSize = sysconf(_SC_GETPW_R_SIZE_MAX);
++
++ if (bufSize > MAX_GETPW_R_BUF_SIZE)
++ {
++ bufSize = MAX_GETPW_R_BUF_SIZE;
++ }
++
++ std::map<gid_t, std::set<std::string>> gidToUsernames;
++
++ if (allGroups)
++ {
++ struct group* grp = nullptr;
++ m_groupWrapper->setgrent();
++
++ while ((grp = m_groupWrapper->getgrent()) != nullptr)
++ {
++ gid_t gid = grp->gr_gid;
++ char** members = grp->gr_mem;
++
++ while (members && *members)
++ {
++ gidToUsernames[gid].insert(*members);
++ ++members;
++ }
++ }
++
++ m_groupWrapper->endgrent();
++ }
++ else
++ {
++ for (const auto& gid : gids)
++ {
++ struct group grp;
++ struct group* grpResult = nullptr;
++ auto groupBuf = std::make_unique<char[]>(bufSize);
*** 863 LINES SKIPPED ***