aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJose Alonso Cardenas Marquez <acm@FreeBSD.org>2026-01-15 02:37:39 +0000
committerJose Alonso Cardenas Marquez <acm@FreeBSD.org>2026-01-15 02:37:39 +0000
commite9cebac52c06bab51b406304d7e5a6397fddec77 (patch)
tree14717e1cc30249b3692176486bacb9dbbfb714e6
parent88938fcfb8d169ecc932f3bea0d51c5316e43372 (diff)
security/wazuh-manager: Add users and groups functions support
- Now wazuh-manager can obtain users and groups information from host - Fix start_time data to show correct datetime data from wazuh-dashboard processes option - Bump PORTREVISION
-rw-r--r--security/wazuh-manager/Makefile3
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider-CMakeLists.txt39
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_CMakeLists.txt11
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src-sysInfoFreeBSD.cpp (renamed from security/wazuh-manager/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp)300
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt18
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt22
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_groups_include-groups_freebsd.hpp46
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_groups_include-user_groups_freebsd.hpp84
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_groups_src-groups_freebsd.hpp95
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_groups_src-user_groups_freebsd.hpp262
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users-CMakeLists.txt21
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_include-logged_in_users_freebsd.hpp37
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_include-users_freebsd.hpp69
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_src-logged_in_users_freebsd.cpp72
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_src-users_freebsd.cpp106
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix-iutmpx_wrapper.hpp12
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix-utmpx_wrapper.hpp16
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-group_wrapper.hpp93
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-igroup_wrapper.hpp70
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-ipasswd_wrapper.hpp72
-rw-r--r--security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-passwd_wrapper.hpp96
21 files changed, 1482 insertions, 62 deletions
diff --git a/security/wazuh-manager/Makefile b/security/wazuh-manager/Makefile
index 1478ab9416b3..c7b2d1213a97 100644
--- a/security/wazuh-manager/Makefile
+++ b/security/wazuh-manager/Makefile
@@ -1,7 +1,7 @@
PORTNAME= wazuh
DISTVERSIONPREFIX= v
DISTVERSION= 4.14.1
-PORTREVISION= 6
+PORTREVISION= 7
CATEGORIES= security
MASTER_SITES= https://packages.wazuh.com/deps/47/libraries/sources/:wazuh_sources \
LOCAL/acm/${PORTNAME}/:wazuh_cache
@@ -224,6 +224,7 @@ post-extract:
@${RM} ${WRKSRC}/src/external/cpython/python ${WRKSRC}/src/external/cpython/libpython* ${WRKSRC}/src/external/cpython/Modules/*.o
@${MKDIR} ${WRKSRC}/ruleset/sca/freebsd
+ @${MKDIR} ${WRKSRC}/src/data_provider/src/extended_sources/wrappers/unix/freebsd
@cd ${WRKDIR}/wazuh-freebsd/var/ossec/ruleset/sca && ${CP} *.yml ${WRKSRC}/ruleset/sca/freebsd/
${FIND} ${WRKSRC}/ruleset/sca -type f -name "*.yml" -exec ${MV} "{}" "{}.disabled" \;
diff --git a/security/wazuh-manager/files/patch-src_data__provider-CMakeLists.txt b/security/wazuh-manager/files/patch-src_data__provider-CMakeLists.txt
new file mode 100644
index 000000000000..03dd0efcd223
--- /dev/null
+++ b/security/wazuh-manager/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-manager/files/patch-src_data__provider_CMakeLists.txt b/security/wazuh-manager/files/patch-src_data__provider_CMakeLists.txt
deleted file mode 100644
index a5527dcc913a..000000000000
--- a/security/wazuh-manager/files/patch-src_data__provider_CMakeLists.txt
+++ /dev/null
@@ -1,11 +0,0 @@
---- src/data_provider/CMakeLists.txt.orig 2023-05-22 04:53:06 UTC
-+++ src/data_provider/CMakeLists.txt
-@@ -153,7 +153,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-manager/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp b/security/wazuh-manager/files/patch-src_data__provider_src-sysInfoFreeBSD.cpp
index 9fb64aa3c105..8ef1dd376f3e 100644
--- a/security/wazuh-manager/files/patch-src-data_provider-src_sysInfoFreeBSD.cpp
+++ b/security/wazuh-manager/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-manager/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources-CMakeLists.txt
new file mode 100644
index 000000000000..458b05f1655e
--- /dev/null
+++ b/security/wazuh-manager/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-manager/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_groups-CMakeLists.txt
new file mode 100644
index 000000000000..0f34a531e850
--- /dev/null
+++ b/security/wazuh-manager/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-manager/files/patch-src_data__provider_src_extended__sources_groups_include-groups_freebsd.hpp b/security/wazuh-manager/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-manager/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-manager/files/patch-src_data__provider_src_extended__sources_groups_include-user_groups_freebsd.hpp b/security/wazuh-manager/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-manager/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-manager/files/patch-src_data__provider_src_extended__sources_groups_src-groups_freebsd.hpp b/security/wazuh-manager/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-manager/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-manager/files/patch-src_data__provider_src_extended__sources_groups_src-user_groups_freebsd.hpp b/security/wazuh-manager/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-manager/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);
++
++ if (m_groupWrapper->getgrgid_r(gid, &grp, groupBuf.get(), bufSize, &grpResult) == 0 && grpResult != nullptr)
++ {
++ char** members = grpResult->gr_mem;
++
++ while (members && *members)
++ {
++ gidToUsernames[gid].insert(*members);
++ ++members;
++ }
++ }
++ }
++ }
++
++ struct passwd* pwd = nullptr;
++
++ m_passwdWrapper->setpwent();
++
++ while ((pwd = m_passwdWrapper->getpwent()) != nullptr)
++ {
++ if (allGroups || gids.count(pwd->pw_gid))
++ {
++ gidToUsernames[pwd->pw_gid].insert(pwd->pw_name);
++ }
++ }
++
++ m_passwdWrapper->endpwent();
++
++ for (const auto& [gid, usernames] : gidToUsernames)
++ {
++ nlohmann::json jsonUsernames = nlohmann::json::array();
++
++ for (const auto& name : usernames)
++ {
++ jsonUsernames.push_back(name);
++ }
++
++ if (singleGid)
++ {
++ result = jsonUsernames;
++ }
++ else
++ {
++ result[std::to_string(gid)] = jsonUsernames;
++ }
++ }
++
++ return result;
++}
++
++std::vector<std::pair<uid_t, std::vector<gid_t>>> UserGroupsProvider::getUserGroups(const std::set<uid_t>& uids)
++{
++ std::vector<std::pair<uid_t, std::vector<gid_t>>> userGroups;
++ struct passwd pwd;
++ struct passwd* pwdResults = nullptr;
++
++ size_t bufSize = sysconf(_SC_GETPW_R_SIZE_MAX);
++
++ if (bufSize > MAX_GETPW_R_BUF_SIZE)
++ {
++ bufSize = MAX_GETPW_R_BUF_SIZE;
++ }
++
++ auto buf = std::make_unique<char[]>(bufSize);
++
++ auto processUser = [&](const struct passwd * pwdInfo)
++ {
++ UserInfo user {pwdInfo->pw_name, pwdInfo->pw_uid, pwdInfo->pw_gid};
++
++ std::vector<gid_t> groups(EXPECTED_GROUPS_MAX);
++ int nGroups = EXPECTED_GROUPS_MAX;
++
++ if (m_groupWrapper->getgrouplist(user.name, user.gid, groups.data(), &nGroups) < 0)
++ {
++ groups.resize(nGroups);
++
++ if (m_groupWrapper->getgrouplist(user.name, user.gid, groups.data(), &nGroups) < 0)
++ {
++ // std::cerr << "Could not get user's group list" << std::endl;
++ return;
++ }
++
++ groups.resize(nGroups);
++ }
++ else
++ {
++ groups.resize(nGroups);
++ }
++
++ userGroups.emplace_back(user.uid, std::move(groups));
++ };
++
++ if (!uids.empty())
++ {
++ for (const auto& uid : uids)
++ {
++ if (m_passwdWrapper->getpwuid_r(uid, &pwd, buf.get(), bufSize, &pwdResults) == 0 && pwdResults != nullptr)
++ {
++ processUser(pwdResults);
++ }
++ }
++ }
++ else
++ {
++ std::set<uid_t> processed_uids;
++ m_passwdWrapper->setpwent();
++
++ while (m_passwdWrapper->getpwent_r(&pwd, buf.get(), bufSize, &pwdResults) == 0 && pwdResults != nullptr)
++ {
++ if (processed_uids.insert(pwdResults->pw_uid).second)
++ {
++ processUser(pwdResults);
++ }
++ }
++
++ m_passwdWrapper->endpwent();
++ }
++
++ return userGroups;
++}
++
++void UserGroupsProvider::addGroupsToResults(nlohmann::json& results, uid_t uid, const gid_t* groups, int nGroups)
++{
++ for (int i = 0; i < nGroups; i++)
++ {
++ nlohmann::json groupJson;
++
++ groupJson["uid"] = uid;
++ groupJson["gid"] = groups[i];
++
++ results.push_back(groupJson);
++ }
++}
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users-CMakeLists.txt b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users-CMakeLists.txt
new file mode 100644
index 000000000000..2dc3067c5068
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users-CMakeLists.txt
@@ -0,0 +1,21 @@
+--- src/data_provider/src/extended_sources/users/CMakeLists.txt 2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/src/extended_sources/users/CMakeLists.txt 2026-01-12 14:53:32.100182000 +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()
+
+@@ -40,6 +42,9 @@
+
+ list(APPEND SRC_FILES src/users_darwin.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../wrappers/unix/darwin/od_wrapper.mm)
+ set(PLATFORM_LIBS_USERS ${OPEN_DIRECTORY} ${FOUNDATION})
++ elseif(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
++ list(APPEND SRC_FILES src/logged_in_users_freebsd.cpp)
++ list(APPEND SRC_FILES src/users_freebsd.cpp)
+ endif()
+ endif()
+
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_include-logged_in_users_freebsd.hpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_include-logged_in_users_freebsd.hpp
new file mode 100644
index 000000000000..83195510bddb
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_include-logged_in_users_freebsd.hpp
@@ -0,0 +1,37 @@
+--- /dev/null 2026-01-13 23:08:55.189229000 +0000
++++ src/data_provider/src/extended_sources/users/include/logged_in_users_freebsd.hpp 2026-01-13 22:43:51.148959000 +0000
+@@ -0,0 +1,34 @@
++/* 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 <map>
++#include <mutex>
++#include <string>
++#include <vector>
++
++#include "json.hpp"
++#include "iutmpx_wrapper.hpp"
++
++class LoggedInUsersProvider
++{
++ public:
++ explicit LoggedInUsersProvider(std::shared_ptr<IUtmpxWrapper> utmpxWrapper);
++
++ LoggedInUsersProvider();
++
++ nlohmann::json collect();
++
++ private:
++ std::shared_ptr<IUtmpxWrapper> m_utmpxWrapper;
++
++ static std::mutex utmpxMutex;
++ static const std::map<size_t, std::string> loginTypes;
++};
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_include-users_freebsd.hpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_include-users_freebsd.hpp
new file mode 100644
index 000000000000..f6884ce8f1fc
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_include-users_freebsd.hpp
@@ -0,0 +1,69 @@
+--- /dev/null 2026-01-13 23:09:49.988051000 +0000
++++ src/data_provider/src/extended_sources/users/include/users_freebsd.hpp 2026-01-13 22:43:51.149043000 +0000
+@@ -0,0 +1,66 @@
++/* 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 <map>
++#include <memory>
++#include <set>
++#include <string>
++
++#include "json.hpp"
++
++#include "ipasswd_wrapper.hpp"
++
++/// @brief Provides user information on FreeBSD systems.
++///
++/// Collects user data by combining information from the passwd database,
++///
++/// Dependencies are injected via wrappers for testability and abstraction.
++class UsersProvider
++{
++ public:
++ /// @brief Constructs a UsersProvider with the given wrappers.
++ /// @param passwdWrapper Wrapper for password database operations.
++ /// @param sysWrapper Wrapper for system-level operations.
++ explicit UsersProvider(
++ std::shared_ptr<IPasswdWrapperFreeBSD> passwdWrapper);
++
++ /// @brief Default constructor.
++ UsersProvider();
++
++ /// @brief Collects all user information, optionally including remote users.
++ /// @param include_remote Whether to include remote users in the collection (default: true).
++ /// @return JSON array of user information objects.
++ nlohmann::json collect(bool include_remote = true);
++
++ /// @brief Collects user information filtered by usernames and UIDs, optionally including remote users.
++ /// @param usernames Set of usernames to filter.
++ /// @param uids Set of UIDs to filter.
++ /// @param include_remote Whether to include remote users in the collection.
++ /// @return JSON array of user information objects matching the constraints.
++ nlohmann::json collectWithConstraints(const std::set<std::string>& usernames,
++ const std::set<uid_t>& uids,
++ bool include_remote);
++
++ private:
++ /// @brief Generates a JSON representation of a user from passwd struct.
++ /// @param pwd Pointer to passwd struct representing the user.
++ /// @param include_remote Boolean indicating whether remote users are included.
++ /// @return JSON object representing the user.
++ nlohmann::json genUserJson(const struct passwd* pwd, bool include_remote);
++
++ /// @brief Collects local users filtered by usernames and UIDs.
++ /// @param usernames Set of usernames to filter.
++ /// @param uids Set of UIDs to filter.
++ /// @return JSON array of local user information.
++ nlohmann::json collectUsers(const std::set<std::string>& usernames,
++ const std::set<uid_t>& uids);
++
++ /// @brief Passwd wrapper dependency.
++ std::shared_ptr<IPasswdWrapperFreeBSD> m_passwdWrapper;
++};
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_src-logged_in_users_freebsd.cpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_src-logged_in_users_freebsd.cpp
new file mode 100644
index 000000000000..d5359d1524b8
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_src-logged_in_users_freebsd.cpp
@@ -0,0 +1,72 @@
+--- /dev/null 2026-01-13 23:08:16.219538000 +0000
++++ src/data_provider/src/extended_sources/users/src/logged_in_users_freebsd.cpp 2026-01-13 22:43:51.148867000 +0000
+@@ -0,0 +1,69 @@
++/* 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 "logged_in_users_freebsd.hpp"
++#include "utmpx_wrapper.hpp"
++
++#include <utmpx.h>
++#include <paths.h>
++
++#include <cstring>
++#include <string>
++
++std::mutex LoggedInUsersProvider::utmpxMutex;
++
++const std::map<size_t, std::string> LoggedInUsersProvider::loginTypes =
++{
++ {EMPTY, "empty"},
++ {BOOT_TIME, "boot_time"},
++ {NEW_TIME, "new_time"},
++ {OLD_TIME, "old_time"},
++ {INIT_PROCESS, "init"},
++ {LOGIN_PROCESS, "login"},
++ {USER_PROCESS, "user"},
++ {DEAD_PROCESS, "dead"},
++};
++
++LoggedInUsersProvider::LoggedInUsersProvider(std::shared_ptr<IUtmpxWrapper> wrapper)
++ : m_utmpxWrapper(std::move(wrapper)) {}
++
++LoggedInUsersProvider::LoggedInUsersProvider()
++ : m_utmpxWrapper(std::make_shared<UtmpxWrapper>()) {}
++
++nlohmann::json LoggedInUsersProvider::collect()
++{
++ std::lock_guard<std::mutex> lock(utmpxMutex);
++ nlohmann::json results = nlohmann::json::array();
++ struct utmpx* entry = nullptr;
++
++ m_utmpxWrapper->setutxent();
++
++ while ((entry = m_utmpxWrapper->getutxent()) != nullptr)
++ {
++ if (entry->ut_pid == 1)
++ {
++ continue;
++ }
++
++ nlohmann::json row;
++ auto it = loginTypes.find(entry->ut_type);
++ row["type"] = (it != loginTypes.end()) ? it->second : "unknown";
++
++ row["user"] = std::string(entry->ut_user, strnlen(entry->ut_user, sizeof(entry->ut_user)));
++ row["tty"] = std::string(entry->ut_line, strnlen(entry->ut_line, sizeof(entry->ut_line)));
++ row["host"] = std::string(entry->ut_host, strnlen(entry->ut_host, sizeof(entry->ut_host)));
++ row["time"] = entry->ut_tv.tv_sec;
++ row["pid"] = entry->ut_pid;
++
++ results.push_back(std::move(row));
++ }
++
++ m_utmpxWrapper->endutxent();
++ return results;
++}
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_src-users_freebsd.cpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_src-users_freebsd.cpp
new file mode 100644
index 000000000000..195be19ed3a9
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_users_src-users_freebsd.cpp
@@ -0,0 +1,106 @@
+--- /dev/null 2026-01-13 23:07:27.483265000 +0000
++++ src/data_provider/src/extended_sources/users/src/users_freebsd.cpp 2026-01-13 22:53:15.807751000 +0000
+@@ -0,0 +1,103 @@
++/* 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 <unistd.h>
++#include <set>
++#include <string>
++#include <map>
++#include <memory>
++
++#include "users_freebsd.hpp"
++#include "passwd_wrapper.hpp"
++
++constexpr size_t MAX_GETPW_R_BUF_SIZE = 16 * 1024;
++
++UsersProvider::UsersProvider(
++ std::shared_ptr<IPasswdWrapperFreeBSD> passwdWrapper)
++ : m_passwdWrapper(std::move(passwdWrapper)) {}
++
++UsersProvider::UsersProvider()
++ : m_passwdWrapper(std::make_shared<PasswdWrapperFreeBSD>()) {}
++
++nlohmann::json UsersProvider::collect(bool include_remote)
++{
++ return collectWithConstraints({}, {}, include_remote);
++}
++
++nlohmann::json UsersProvider::collectWithConstraints(const std::set<std::string>& usernames,
++ const std::set<uid_t>& uids,
++ bool include_remote)
++{
++// if (include_remote)
++// {
++// return collectRemoteUsers(usernames, uids);
++// }
++
++ return collectUsers(usernames, uids);
++}
++
++nlohmann::json UsersProvider::genUserJson(const struct passwd* pwd, bool include_remote)
++{
++ nlohmann::json r;
++ r["uid"] = pwd->pw_uid;
++ r["gid"] = pwd->pw_gid;
++ r["uid_signed"] = static_cast<int32_t>(pwd->pw_uid);
++ r["gid_signed"] = static_cast<int32_t>(pwd->pw_gid);
++
++ r["username"] = (pwd->pw_name != nullptr) ? pwd->pw_name : "";
++ r["description"] = (pwd->pw_gecos != nullptr) ? pwd->pw_gecos : "";
++ r["directory"] = (pwd->pw_dir != nullptr) ? pwd->pw_dir : "";
++ r["shell"] = (pwd->pw_shell != nullptr) ? pwd->pw_shell : "";
++
++ r["pid_with_namespace"] = "0";
++ r["include_remote"] = static_cast<int>(include_remote);
++
++ return r;
++}
++
++nlohmann::json UsersProvider::collectUsers(const std::set<std::string>& usernames,
++ const std::set<uid_t>& uids)
++{
++ nlohmann::json results = nlohmann::json::array();
++
++ size_t bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
++
++ if (bufsize > MAX_GETPW_R_BUF_SIZE)
++ {
++ bufsize = MAX_GETPW_R_BUF_SIZE;
++ }
++
++ auto buf = std::make_unique<char[]>(bufsize);
++
++ struct passwd pwd;
++ struct passwd* pwd_results
++ {
++ nullptr
++ };
++
++ m_passwdWrapper->setpwent();
++
++ while (m_passwdWrapper->getpwent_r(&pwd, buf.get(), bufsize, &pwd_results) == 0 && pwd_results != nullptr)
++ {
++ if (!usernames.empty() && usernames.find(pwd_results->pw_name) == usernames.end())
++ {
++ continue;
++ }
++ else if (!uids.empty() && uids.find(pwd_results->pw_uid) == uids.end())
++ {
++ continue;
++ }
++
++ results.push_back(genUserJson(pwd_results, true));
++ }
++
++ m_passwdWrapper->endpwent();
++
++ return results;
++}
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix-iutmpx_wrapper.hpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix-iutmpx_wrapper.hpp
new file mode 100644
index 000000000000..2f646ad8c1d6
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix-iutmpx_wrapper.hpp
@@ -0,0 +1,12 @@
+--- src/data_provider/src/extended_sources/wrappers/unix/iutmpx_wrapper.hpp 2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/src/extended_sources/wrappers/unix/iutmpx_wrapper.hpp 2026-01-12 14:51:47.552260000 +0000
+@@ -20,7 +20,9 @@
+
+ /// @brief Sets the utmpx file to be used by the library functions.
+ /// @param file Path to the utmpx file.
++#if !defined(__FreeBSD__)
+ virtual void utmpxname(const char* file) = 0;
++#endif
+
+ /// @brief Resets the input stream to the beginning of the utmpx file.
+ virtual void setutxent() = 0;
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix-utmpx_wrapper.hpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix-utmpx_wrapper.hpp
new file mode 100644
index 000000000000..edf75f578f7b
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix-utmpx_wrapper.hpp
@@ -0,0 +1,16 @@
+--- src/data_provider/src/extended_sources/wrappers/unix/utmpx_wrapper.hpp 2025-11-07 08:46:03.000000000 +0000
++++ src/data_provider/src/extended_sources/wrappers/unix/utmpx_wrapper.hpp 2026-01-12 14:51:47.552488000 +0000
+@@ -18,11 +18,12 @@
+ public:
+ /// @brief Sets the utmpx file to be used by the library functions.
+ /// @param file Path to the utmpx file.
++#if !defined(__FreeBSD__)
+ void utmpxname(const char* file) override
+ {
+ ::utmpxname(file);
+ }
+-
++#endif
+ /// @brief Resets the input stream to the beginning of the utmpx file.
+ void setutxent() override
+ {
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-group_wrapper.hpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-group_wrapper.hpp
new file mode 100644
index 000000000000..57202254a120
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-group_wrapper.hpp
@@ -0,0 +1,93 @@
+--- /dev/null 2026-01-13 23:16:40.390101000 +0000
++++ src/data_provider/src/extended_sources/wrappers/unix/freebsd/group_wrapper.hpp 2026-01-13 22:43:51.149572000 +0000
+@@ -0,0 +1,90 @@
++/* 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 "igroup_wrapper.hpp"
++
++#include <grp.h>
++#include <unistd.h>
++
++/// @brief Wrapper class for group-related operations on FreeBSD systems.
++///
++/// Provides methods to retrieve group lists and counts for users, allowing for dependency injection
++/// and easier testing. This class implements the IGroupWrapperFreeBSD interface and uses the system calls
++/// to interact with the group database.
++class GroupWrapperFreeBSD : public IGroupWrapperFreeBSD
++{
++ public:
++ /// @brief Retrieves the group entry for a given group ID.
++ /// @param gid The group ID to search for.
++ /// @param resultbuf A pointer to a group structure where the result will be stored.
++ /// @param buffer A buffer to hold the string data for the group entry.
++ /// @param buflen The size of the buffer.
++ /// @param result A pointer to a group structure pointer that will point to the result.
++ /// @return 0 on success, or an error code on failure.
++ int getgrgid_r(gid_t gid, struct group* resultbuf, char* buffer, size_t buflen, struct group** result) const override
++ {
++ return ::getgrgid_r(gid, resultbuf, buffer, buflen, result);
++ }
++
++ /// @brief Retrieves a group by its GID.
++ /// @param gid The group ID for which to retrieve the group structure.
++ /// @return A pointer to the group structure if found, or nullptr if not found.
++ struct group* getgrgid(gid_t gid) const override
++ {
++ return ::getgrgid(gid);
++ }
++
++ /// @brief Retrieves the group entry for a given group name.
++ /// @param resultbuf A pointer to a group structure where the result will be stored.
++ /// @param buffer A buffer to hold the string data for the group entry.
++ /// @param buflen The size of the buffer.
++ /// @param result A pointer to a group structure pointer that will point to the result.
++ int getgrent_r(struct group* resultbuf, char* buffer, size_t buflen, struct group** result) const override
++ {
++ return ::getgrent_r(resultbuf, buffer, buflen, result);
++ }
++
++ /// @brief Retrieves the next group entry from the group database.
++ /// @return A pointer to the next group structure, or nullptr if there are no more entries.
++ struct group* getgrent() const override
++ {
++ return ::getgrent();
++ }
++
++ /// @brief Rewind the group-file stream.
++ void setgrent() const override
++ {
++ ::setgrent();
++ }
++ /// @brief Close the group-file stream.
++ void endgrent() const override
++ {
++ ::endgrent();
++ }
++ /// @brief Retrieves a group by its name.
++ /// @param name The name of the group to retrieve.
++ /// @return A pointer to the group structure if found, or nullptr if not found.
++ struct group* getgrnam(const char* name) const override
++ {
++ return ::getgrnam(name);
++ }
++
++ /// @brief Retrieves the list of groups for a user.
++ /// @param user The username for which to retrieve the group list.
++ /// @param group The primary group ID of the user.
++ /// @param groups Pointer to an array where the group IDs will be stored.
++ /// @param ngroups Pointer to an integer that indicates the size of the groups array.
++ /// @return The number of groups the user belongs to, or -1 on error.
++ int getgrouplist(const char* user, gid_t group, gid_t* groups, int* ngroups) const override
++ {
++ return ::getgrouplist(user, group, groups, ngroups);
++ }
++};
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-igroup_wrapper.hpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-igroup_wrapper.hpp
new file mode 100644
index 000000000000..a62f2fa241fc
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-igroup_wrapper.hpp
@@ -0,0 +1,70 @@
+--- /dev/null 2026-01-13 23:16:28.442788000 +0000
++++ src/data_provider/src/extended_sources/wrappers/unix/freebsd/igroup_wrapper.hpp 2026-01-13 22:43:51.149445000 +0000
+@@ -0,0 +1,67 @@
++/* 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 <grp.h>
++
++/// @brief Interface for group-related operations on FreeBSD systems.
++///
++/// Provides methods to retrieve group lists and counts for users, allowing for dependency injection
++/// and easier testing. This interface abstracts the system calls used to interact with the group database.
++class IGroupWrapperFreeBSD
++{
++ public:
++ /// @brief Default constructor.
++ virtual ~IGroupWrapperFreeBSD() = default;
++
++ /// @brief Retrieves the group entry for a given group ID.
++ /// @param gid The group ID to search for.
++ /// @param resultbuf A pointer to a group structure where the result will be stored.
++ /// @param buffer A buffer to hold the string data for the group entry.
++ /// @param buflen The size of the buffer.
++ /// @param result A pointer to a group structure pointer that will point to the result.
++ /// @return 0 on success, or an error code on failure.
++ virtual int getgrgid_r(gid_t gid, struct group* resultbuf, char* buffer, size_t buflen, struct group** result) const = 0;
++
++ /// @brief Retrieves the group entry for a given group name.
++ /// @param resultbuf A pointer to a group structure where the result will be stored.
++ /// @param buffer A buffer to hold the string data for the group entry.
++ /// @param buflen The size of the buffer.
++ /// @param result A pointer to a group structure pointer that will point to the result.
++ virtual int getgrent_r(struct group* resultbuf, char* buffer, size_t buflen, struct group** result) const = 0;
++
++ /// @brief Retrieves a group by its GID.
++ /// @param gid The group ID for which to retrieve the group structure.
++ /// @return A pointer to the group structure if found, or nullptr if not found.
++ virtual struct group* getgrgid(gid_t gid) const = 0;
++
++ /// @brief Retrieves the next group entry from the group database.
++ /// @return A pointer to the next group structure, or nullptr if there are no more entries
++ virtual struct group* getgrent() const = 0;
++
++ /// @brief Rewind the group-file stream.
++ virtual void setgrent() const = 0;
++
++ /// @brief Close the group-file stream.
++ virtual void endgrent() const = 0;
++
++ /// @brief Retrieves a group by its name.
++ /// @param name The name of the group to retrieve.
++ /// @return A pointer to the group structure if found, or nullptr if not found.
++ virtual struct group* getgrnam(const char* name) const = 0;
++
++ /// @brief Retrieves the list of groups for a user.
++ /// @param user The username for which to retrieve the group list.
++ /// @param group The primary group ID of the user.
++ /// @param groups Pointer to an array where the group IDs will be stored.
++ /// @param ngroups Pointer to an integer that indicates the size of the groups array.
++ /// @return The number of groups the user belongs to, or -1 on error.
++ virtual int getgrouplist(const char* user, gid_t group, gid_t* groups, int* ngroups) const = 0;
++};
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-ipasswd_wrapper.hpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-ipasswd_wrapper.hpp
new file mode 100644
index 000000000000..a2119a381499
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-ipasswd_wrapper.hpp
@@ -0,0 +1,72 @@
+--- /dev/null 2026-01-13 23:17:10.457712000 +0000
++++ src/data_provider/src/extended_sources/wrappers/unix/freebsd/ipasswd_wrapper.hpp 2026-01-13 22:43:51.149517000 +0000
+@@ -0,0 +1,69 @@
++/* 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 <pwd.h>
++
++// Interface for the pwd darwin wrapper
++class IPasswdWrapperFreeBSD
++{
++ public:
++ /// Destructor
++ virtual ~IPasswdWrapperFreeBSD() = default;
++
++ /// @brief Retrieves the passwd entry for the given username.
++ /// @param name The username to search for.
++ /// @return A pointer to the passwd structure, or nullptr if not found.
++ virtual struct passwd* getpwnam(const char* name) = 0;
++
++ /// @brief Retrieves the passwd entry for the given user ID.
++ /// @param uid The user ID to search for.
++ /// @return A pointer to the passwd structure, or nullptr if not found.
++ virtual struct passwd* getpwuid(uid_t uid) = 0;
++
++ /// @brief Retrieves the passwd entry for the given user ID.
++ /// @param uid User ID to search.
++ /// @param pwd Pointer to a passwd structure to fill.
++ /// @param buf Buffer used to store string fields.
++ /// @param buflen Size of the buffer.
++ /// @param result Pointer to store the result (null if not found).
++ /// @return 0 on success, or an error number on failure.
++ virtual int getpwuid_r(uid_t uid, struct passwd* pwd,
++ char* buf, size_t buflen, struct passwd** result) = 0;
++
++ /// @brief Retrieves the passwd entry for the given username.
++ /// @param name Username to search.
++ /// @param pwd Pointer to a passwd structure to fill.
++ /// @param buf Buffer used to store string fields.
++ /// @param buflen Size of the buffer.
++ /// @param result Pointer to store the result (null if not found).
++ /// @return 0 on success, or an error number on failure.
++ virtual int getpwnam_r(const char* name, struct passwd* pwd,
++ char* buf, size_t buflen, struct passwd** result) = 0;
++
++ /// @brief Rewinds the passwd database to the beginning.
++ virtual void setpwent() = 0;
++
++ /// @brief Retrieves the next entry from the passwd database.
++ /// @return A pointer to the passwd structure, or nullptr if no more entries.
++ virtual struct passwd* getpwent() = 0;
++
++ /// @brief Retrieves the next entry from the passwd database.
++ /// @param pwd Pointer to a passwd structure to fill.
++ /// @param buf Buffer used to store string fields.
++ /// @param buflen Size of the buffer.
++ /// @param result Pointer to store the result (null if not found).
++ /// @return 0 on success, or an error number on failure.
++ virtual int getpwent_r(struct passwd* pwd, char* buf,
++ size_t buflen, struct passwd** result) = 0;
++
++ /// @brief Closes the passwd database.
++ virtual void endpwent() = 0;
++};
diff --git a/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-passwd_wrapper.hpp b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-passwd_wrapper.hpp
new file mode 100644
index 000000000000..9d12098e14f5
--- /dev/null
+++ b/security/wazuh-manager/files/patch-src_data__provider_src_extended__sources_wrappers_unix_freebsd-passwd_wrapper.hpp
@@ -0,0 +1,96 @@
+--- /dev/null 2026-01-13 23:17:03.875999000 +0000
++++ src/data_provider/src/extended_sources/wrappers/unix/freebsd/passwd_wrapper.hpp 2026-01-13 22:43:51.149624000 +0000
+@@ -0,0 +1,93 @@
++/* 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 "ipasswd_wrapper.hpp"
++
++/// @brief Wrapper class for FreeBSD-specific passwd database access.
++///
++/// Encapsulates system calls used to retrieve user information on FreeBSD,
++/// allowing for dependency injection and easier testing.
++class PasswdWrapperFreeBSD : public IPasswdWrapperFreeBSD
++{
++ public:
++ /// @brief Retrieves the passwd entry for the given username.
++ /// @param name The username to search for.
++ /// @return A pointer to the passwd structure, or nullptr if not found.
++ struct passwd* getpwnam(const char* name) override
++ {
++ return ::getpwnam(name);
++ }
++
++ /// @brief Retrieves the passwd entry for the given user ID.
++ /// @param uid The user ID to search for.
++ /// @return A pointer to the passwd structure, or nullptr if not found.
++ struct passwd* getpwuid(uid_t uid) override
++ {
++ return ::getpwuid(uid);
++ }
++
++ /// @brief Retrieves the passwd entry for the given user ID.
++ /// @param uid User ID to search.
++ /// @param pwd Pointer to a passwd structure to fill.
++ /// @param buf Buffer used to store string fields.
++ /// @param buflen Size of the buffer.
++ /// @param result Pointer to store the result (null if not found).
++ /// @return 0 on success, or an error number on failure.
++ int getpwuid_r(uid_t uid, struct passwd* pwd,
++ char* buf, size_t buflen, struct passwd** result) override
++ {
++ return ::getpwuid_r(uid, pwd, buf, buflen, result);
++ }
++
++ /// @brief Retrieves the passwd entry for the given username.
++ /// @param name Username to search.
++ /// @param pwd Pointer to a passwd structure to fill.
++ /// @param buf Buffer used to store string fields.
++ /// @param buflen Size of the buffer.
++ /// @param result Pointer to store the result (null if not found).
++ /// @return 0 on success, or an error number on failure.
++ int getpwnam_r(const char* name, struct passwd* pwd,
++ char* buf, size_t buflen, struct passwd** result) override
++ {
++ return ::getpwnam_r(name, pwd, buf, buflen, result);
++ }
++
++ /// @brief Rewinds the passwd database to the beginning.
++ void setpwent() override
++ {
++ ::setpwent();
++ }
++
++ /// @brief Retrieves the next entry from the passwd database.
++ /// @return A pointer to the passwd structure, or nullptr if no more entries.
++ struct passwd* getpwent() override
++ {
++ return ::getpwent();
++ }
++
++ /// @brief Retrieves the next entry from the passwd database.
++ /// @param pwd Pointer to a passwd structure to fill.
++ /// @param buf Buffer used to store string fields.
++ /// @param buflen Size of the buffer.
++ /// @param result Pointer to store the result (null if not found).
++ /// @return 0 on success, or an error number on failure.
++ int getpwent_r(struct passwd* pwd, char* buf,
++ size_t buflen, struct passwd** result) override
++ {
++ return ::getpwent_r(pwd, buf, buflen, result);
++ }
++
++ /// @brief Closes the passwd database.
++ void endpwent() override
++ {
++ ::endpwent();
++ }
++};