wazuh是從ossec-hids衍生過來的,部分架構設計有所不同, 多進程多線程模式。本機的進程之間通過Unix domain socket 進行通信的。
今天簡單介紹一下數據蒐集的相關功能的實現(Linux系統)。
注意由於篇幅所限, 在函數中我們只列出相關代碼。
可以先看一下官網的架構設計
https://documentation.wazuh.com/3.9/getting-started/architecture.html?highlight=architecture
agent的wazuh-modulesd端處理流程:
- 數據蒐集的代碼主要在src/wazuh_modules/下面。wazuh-modules進程是一個多線程模式,每個線程負責一個模塊的功能。
- 所有模塊都放在wmodule *wmodules(單向鏈表)全局變量中。
主要數據結構
// Main module structure
typedef struct wmodule {
pthread_t thread; // Thread ID
const wm_context *context; // Context (common structure) 比如 WM_SYS_CONTEXT
char *tag; // Module tag 這裏是 "syscollector"
void *data; // Data (module-dependent structure) 是 wm_sys_t 實例
struct wmodule *next; // Pointer to next module
} wmodule;
typedef struct wm_context {
const char *name; // Name for module
wm_routine start; // Main function 比如 wm_sys_main(信息蒐集主入口)
wm_routine destroy; // Destructor 資源回收
cJSON *(* dump)(const void *);
} wm_context;
/* wm_sys_main 輸入參數, 可以由配置參數填充*/
typedef struct wm_sys_t {
unsigned int interval; // Time interval between cycles (seconds) 定時間隔
wm_sys_flags_t flags; // Flag bitfield 每一個字段都是用來控制收集的方式、是否收集
wm_sys_state_t state; // Running state
} wm_sys_t;
/* 如果想增加收集信息,可通過在該結構中增加一個字段(1 bit)來控制是否收集 */
typedef struct wm_sys_flags_t {
unsigned int enabled:1; // Main switch 如果這個關閉, 該線程退出, 見wm_sys_check實現
unsigned int scan_on_start:1; // Scan always on start 如果沒設置,延遲1秒
unsigned int hwinfo:1; // Hardware inventory 這些默認都是收集的
unsigned int netinfo:1; // Network inventory
unsigned int osinfo:1; // OS inventory
unsigned int programinfo:1; // Installed packages inventory
unsigned int portsinfo:1; // Opened ports inventory
unsigned int allports:1; // Scan only listening ports or all
unsigned int procinfo:1; // Running processes inventory
} wm_sys_flags_t;
默認所有信息是必須收集的比 os version 、Hardware等等。可以通過修改配置文件不蒐集。通過定義配置名稱例如"processes",在wmodules_syscollector.c中實現相關代碼。
下面的代碼是講解如何收集os信息的,因爲這類數據是靜態的,一般不會變化,所以發送的記錄字段type 沒有類似xxx_end
的信息,比如port_end; 這種信息會在manager端將歷史數據刪除掉。
ossec.conf中關於syscollector的默認配置如下:默認開啓,1小時收集一次等。
<!-- System inventory -->
<wodle name="syscollector">
<disabled>no</disabled>
<interval>1h</interval>
<scan_on_start>yes</scan_on_start>
<hardware>yes</hardware>
<os>yes</os>
<network>yes</network>
<packages>yes</packages>
<ports all="no">yes</ports>
<processes>yes</processes>
</wodle>
wazuh-modulesd的啓動流程:
-
1:讀取配置文件,創建需要的數據結構, 創建模塊鏈表;
-
2:爲每個模塊創建線程,每個線程負責收集自己的信息,並轉發到/var/ossec/queue/ossec/queue unix socket中;
-
3: 等待線程退出;
在src/wazuh_modules/main.c 中
- 啓動時讀取配置的流程:main =>wm_setup=>wm_config=>ReadConfig=>read_main_elements=>Read_WModule=>wm_sys_read,在Read_WModule函數中建立wmodules鏈表。
下面列出wm_sys_read相關代碼:
// 這個函數先設置初值,然後再通過配置文件去覆蓋初值
int wm_sys_read(XML_NODE node, wmodule *module) {
wm_sys_t *syscollector;
int i;
/* 創建對象 並設置初值*/
if(!module->data) {
os_calloc(1, sizeof(wm_sys_t), syscollector);
syscollector->flags.enabled = 1;
syscollector->flags.scan_on_start = 1;
syscollector->flags.netinfo = 1;
syscollector->flags.osinfo = 1;
syscollector->flags.hwinfo = 1;
syscollector->flags.programinfo = 1;
syscollector->flags.portsinfo = 1;
syscollector->flags.allports = 0;
syscollector->flags.procinfo = 1;
module->context = &WM_SYS_CONTEXT;
module->tag = strdup(module->context->name);
module->data = syscollector;
}
syscollector = module->data;
if (!node)
return 0;
// Iterate over elements
/* 讀取配置的信息,進行覆蓋初值、設置循環蒐集信息的時間間隔*/
for (i = 0; node[i]; i++) {
if (!node[i]->element) {
merror(XML_ELEMNULL);
return OS_INVALID;
} else if (!strcmp(node[i]->element, XML_INTERVAL)) {
char *endptr;
syscollector->interval = strtoul(node[i]->content, &endptr, 0);
if (syscollector->interval == 0 || syscollector->interval == UINT_MAX) {
merror("Invalid interval at module '%s'", WM_SYS_CONTEXT.name);
return OS_INVALID;
}
switch (*endptr) {
case 'd':
syscollector->interval *= 86400;
break;
case 'h':
syscollector->interval *= 3600;
break;
case 'm':
syscollector->interval *= 60;
break;
case 's':
case '\0':
break;
default:
merror("Invalid interval at module '%s'", WM_SYS_CONTEXT.name);
return OS_INVALID;
}
} else if (!strcmp(node[i]->element, XML_SCAN_ON_START)) {
if (!strcmp(node[i]->content, "yes"))
syscollector->flags.scan_on_start = 1;
else if (!strcmp(node[i]->content, "no"))
syscollector->flags.scan_on_start = 0;
else {
merror("Invalid content for tag '%s' at module '%s'.", XML_SCAN_ON_START, WM_SYS_CONTEXT.name);
return OS_INVALID;
}
} else if (!strcmp(node[i]->element, XML_DISABLED)) {
if (!strcmp(node[i]->content, "yes"))
syscollector->flags.enabled = 0;
else if (!strcmp(node[i]->content, "no"))
syscollector->flags.enabled = 1;
else {
merror("Invalid content for tag '%s' at module '%s'.", XML_DISABLED, WM_SYS_CONTEXT.name);
return OS_INVALID;
}
}
...
else {
merror("No such tag '%s' at module '%s'.", node[i]->element, WM_SYS_CONTEXT.name);
return OS_INVALID;
}
}
return 0;
}
然後介紹一下os_version 這些信息怎麼讀的:wm_sys_main=>sys_os_unix=>getunameJSON=>get_unix_version
// Module main function. It won't return
void* wm_sys_main(wm_sys_t *sys) {
time_t time_start = 0;
time_t time_sleep = 0;
// Check configuration and show debug information
/* 連接unix socket等, 如果是agent端則是和Agent Daemon建立socket*/
wm_sys_setup(sys);
mtinfo(WM_SYS_LOGTAG, "Module started.");
// First sleeping
if (!sys->flags.scan_on_start) {
time_start = time(NULL);
// On first run, take into account the interval of time specified
if (sys->state.next_time == 0) {
sys->state.next_time = time_start + sys->interval;
}
if (sys->state.next_time > time_start) {
mtinfo(WM_SYS_LOGTAG, "Waiting for turn to evaluate.");
wm_delay(1000 * (sys->state.next_time - time_start));
}
} else {
// Wait for Wazuh DB start
wm_delay(1000);
}
// Main loop
while (1) {
mtinfo(WM_SYS_LOGTAG, "Starting evaluation.");
// Get time and execute
time_start = time(NULL);
...
/* 獲取操作系統信息 */
if (sys->flags.osinfo){
#ifdef WIN32
sys_os_windows(WM_SYS_LOCATION);
#else
sys_os_unix(queue_fd, WM_SYS_LOCATION);
#endif
}
...
// 這塊實現定時掃描
time_sleep = time(NULL) - time_start;
mtinfo(WM_SYS_LOGTAG, "Evaluation finished.");
if ((time_t)sys->interval >= time_sleep) {
time_sleep = sys->interval - time_sleep;
sys->state.next_time = sys->interval + time_start;
} else {
mterror(WM_SYS_LOGTAG, "Interval overtaken.");
time_sleep = sys->state.next_time = 0;
}
if (wm_state_io(WM_SYS_CONTEXT.name, WM_IO_WRITE, &sys->state, sizeof(sys->state)) < 0)
mterror(WM_SYS_LOGTAG, "Couldn't save running state: %s (%d)", strerror(errno), errno);
// If time_sleep=0, yield CPU
wm_delay(1000 * time_sleep);
}
return NULL;
}
/* 將獲取的數據通cJSON 構造json字符串發送給其他進程*/
void sys_os_unix(int queue_fd, const char* LOCATION){
char *string;
int random_id = os_random();
char *timestamp;
time_t now;
struct tm localtm;
now = time(NULL);
localtime_r(&now, &localtm);
os_calloc(TIME_LENGTH, sizeof(char), timestamp);
snprintf(timestamp,TIME_LENGTH-1,"%d/%02d/%02d %02d:%02d:%02d",
localtm.tm_year + 1900, localtm.tm_mon + 1,
localtm.tm_mday, localtm.tm_hour, localtm.tm_min, localtm.tm_sec);
if (random_id < 0)
random_id = -random_id;
mtdebug1(WM_SYS_LOGTAG, "Starting Operating System inventory.");
/*每條蒐集都包含type 、ID、timestamp ,用於唯一定位一條記錄,方便增刪改查 */
cJSON *object = cJSON_CreateObject();
cJSON_AddStringToObject(object, "type", "OS");
cJSON_AddNumberToObject(object, "ID", random_id);
cJSON_AddStringToObject(object, "timestamp", timestamp);
/* 真正去獲取os 信息的地方*/
cJSON *os_inventory = getunameJSON();
if (os_inventory != NULL)
cJSON_AddItemToObject(object, "inventory", os_inventory);
/* 將json數據格式化成字符串,都是這套路, 通過SendMSG發送給其他進程進行轉發處理。通過 SYSCOLLECTOR_MQ進行區分信息蒐集*/
string = cJSON_PrintUnformatted(object);
mtdebug2(WM_SYS_LOGTAG, "sys_os_unix() sending '%s'", string);
SendMSG(queue_fd, string, LOCATION, SYSCOLLECTOR_MQ);
cJSON_Delete(object);
free(timestamp);
free(string);
}
cJSON* getunameJSON()
{
os_info *read_info;
cJSON* root=cJSON_CreateObject();
#ifndef WIN32
/* 通過讀取一些相關文件獲取os信息*/
if (read_info = get_unix_version(), read_info) {
#else
if (read_info = get_win_version(), read_info) {
#endif
...
}
}
下面看看初始化的調用過程:
- 從 src/wazuh_modules/main.c中開始 main=>wm_setup=>wm_config=>ReadConfig=>read_main_elements=>Read_WModule=>wm_sys_read;
int main(int argc, char **argv)
{
...
// Setup daemon
/* 讀取配置,初始化的入口*/
wm_setup();
if (test_config)
exit(EXIT_SUCCESS);
minfo("Process started.");
// Run modules
/* 每種模塊都創建一個線程來處理, 例如信息蒐集線程設置wm_sys_main回調函數*/
for (cur_module = wmodules; cur_module; cur_module = cur_module->next) {
if (CreateThreadJoinable(&cur_module->thread, cur_module->context->start, cur_module->data) < 0) {
merror_exit("CreateThreadJoinable() for '%s': %s", cur_module->tag, strerror(errno));
}
mdebug2("Created new thread for the '%s' module.", cur_module->tag);
}
// Start com request thread
w_create_thread(wmcom_main, NULL);
// Wait for threads
for (cur_module = wmodules; cur_module; cur_module = cur_module->next) {
pthread_join(cur_module->thread, NULL);
}
return EXIT_SUCCESS;
}
- 看一下 wm_sys_setup函數的實現, 介紹如何關聯到Agent Daemon進程,文件路徑爲/var/ossec/queue/ossec/queue
#ifndef DEFAULTDIR
#define DEFAULTDIR "/var/ossec"
#endif
#define DEFAULTQUEUE "/queue/ossec/queue"
#define DEFAULTQPATH DEFAULTDIR DEFAULTQUEUE
static void wm_sys_setup(wm_sys_t *_sys) {
sys = _sys;
...
// DEFAULTQPATH宏定位文件路徑爲/var/ossec/queue/ossec/queue
for (i = 0; (queue_fd = StartMQ(DEFAULTQPATH, WRITE)) < 0 && i < WM_MAX_ATTEMPTS; i++)
wm_delay(1000 * WM_MAX_WAIT);
if (i == WM_MAX_ATTEMPTS) {
mterror(WM_SYS_LOGTAG, "Can't connect to queue.");
pthread_exit(NULL);
}
...
}
ossec-agentd的執行流程
-
1:初始化:創建unix socket,負責收集本地各個模塊收集到的數據;
-
2:轉發: 將數據發送到manager端:先壓縮再加密;
- Agent Daemon進程創建unix socket的代碼, src/client_agent/main.c
#ifndef DEFAULTDIR
#define DEFAULTDIR "/var/ossec"
#endif
int main(int argc, char **argv)
{
int c = 0;
int test_config = 0;
int debug_level = 0;
agent_debug_level = getDefine_Int("agent", "debug", 0, 2);
const char *dir = DEFAULTDIR;/* 用來切換工作目錄 可通過命令行修改*/
const char *user = USER;
const char *group = GROUPGLOBAL;
const char *cfg = DEFAULTCPATH;
uid_t uid;
gid_t gid;
run_foreground = 0;
/* Set the name */
OS_SetName(ARGV0);
while ((c = getopt(argc, argv, "Vtdfhu:g:D:c:")) != -1) {
switch (c) {
case 'V':
print_version();
break;
case 'h':
help_agentd();
break;
case 'd':
nowDebug();
debug_level = 1;
break;
case 'f':
run_foreground = 1;
break;
case 'u':
if (!optarg) {
merror_exit("-u needs an argument");
}
user = optarg;
break;
case 'g':
if (!optarg) {
merror_exit("-g needs an argument");
}
group = optarg;
break;
case 't':
test_config = 1;
break;
case 'D'://這個參數可以修改工作目錄
if (!optarg) {
merror_exit("-D needs an argument");
}
dir = optarg;
break;
case 'c':
if (!optarg) {
merror_exit("-c needs an argument.");
}
cfg = optarg;
break;
default:
help_agentd();
break;
}
}
...
/* 主要入口函數,初始化工作目錄、創建unix socket、 連接到manager、創建狀態統計線程、接收其他進程發送過來的信息,並轉發到manager端(先使用zip壓縮再使用對稱加密算法加密)*/
AgentdStart(dir, uid, gid, user, group);
return (0);
}
- agent的主入口函數 :初始化、建立連接、發送心跳、轉發數據等
/* Start the agent daemon */
void AgentdStart(const char *dir, int uid, int gid, const char *user, const char *group)
{
int rc = 0;
int maxfd = 0;
fd_set fdset;
struct timeval fdtimeout;
available_server = 0;
/* Initial random numbers must happen before chroot */
srandom_init();
/* 後臺運行 */
if (!run_foreground) {
nowDaemon();
goDaemon();
}
...
/* 改變工作路徑 默認是/var/ossec*/
if (Privsep_Chroot(dir) < 0) {
merror_exit(CHROOT_ERROR, dir, errno, strerror(errno));
}
nowChroot();
if (Privsep_SetUser(uid) < 0) {
merror_exit(SETUID_ERROR, user, errno, strerror(errno));
}
/* Try to connect to server */
os_setwait();
/* 創建消息隊列 unix socket 路徑是 /var/ossec/ + DEFAULTQUEUE = /var/ossec//queue/ossec/queue.剛好與前面的信息蒐集的socket一致,他們相互通信轉發數據 */
if ((agt->m_queue = StartMQ(DEFAULTQUEUE, READ)) < 0) {
merror_exit(QUEUE_ERROR, DEFAULTQUEUE, strerror(errno));
}
...
maxfd = agt->m_queue;
agt->sock = -1;
/* Create PID file */
if (CreatePID(ARGV0, getpid()) < 0) {
merror_exit(PID_ERROR);
}
/* Read private keys */
minfo(ENC_READ);
OS_StartCounter(&keys);
/*讀取agent的操作系統信息 */
os_write_agent_info(keys.keyentries[0]->name, NULL, keys.keyentries[0]->id,
agt->profile);
/*設置與manager通信使用的加密算法, 對稱加密 */
os_set_agent_crypto_method(&keys,agt->crypto_method);
switch (agt->crypto_method) {
case W_METH_AES:
minfo("Using AES as encryption method.");
break;
case W_METH_BLOWFISH:
minfo("Using Blowfish as encryption method.");
break;
default:
merror("Invalid encryption method.");
}
/* Start up message */
minfo(STARTUP_MSG, (int)getpid());
os_random();
/* Ignore SIGPIPE, it will be detected on recv */
signal(SIGPIPE, SIG_IGN);
/* Launch rotation thread */
rotate_log = getDefine_Int("monitord", "rotate_log", 0, 1);
if (rotate_log && CreateThread(w_rotate_log_thread, (void *)NULL) != 0) {
merror_exit(THREAD_ERROR);
}
/* 啓動轉發數據線程, 通過buffer_append觸發, 如果該函數返回0,正常處理:將數據放入buffer數組中,該線程調用dispatch_buffer處理轉發數據, 如果返回-1, 數據不保存在buffer中,在調用dispatch_buffer函數時發送告警信息給manager,不再讀取本地數據,等待下一次循環*/
if (agt->buffer){
buffer_init();
if (CreateThread(dispatch_buffer, (void *)NULL) != 0) {
merror_exit(THREAD_ERROR);
}
}else{
minfo(DISABLED_BUFFER);
}
/* Connect remote */
rc = 0;
while (rc < agt->rip_id) {
minfo("Server IP Address: %s", agt->server[rc].rip);
rc++;
}
w_create_thread(state_main, NULL);
/* 連接到manager端 */
if (!connect_server(0)) {
merror_exit(UNABLE_CONN);
}
/* Set max fd for select */
if (agt->sock > maxfd) {
maxfd = agt->sock;
}
/* Connect to the execd queue */
if (agt->execdq == 0) {
if ((agt->execdq = StartMQ(EXECQUEUE, WRITE)) < 0) {
minfo("Unable to connect to the active response "
"queue (disabled).");
agt->execdq = -1;
}
}
start_agent(1);
os_delwait();
update_status(GA_STATUS_ACTIVE);
// Ignore SIGPIPE signal to prevent the process from crashing
struct sigaction act;
memset(&act, 0, sizeof(act));
act.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &act, NULL);
/* Send integrity message for agent configs */
intcheck_file(OSSECCONF, dir);
intcheck_file(OSSEC_DEFINES, dir);
// Start request module
req_init();
w_create_thread(req_receiver, NULL);
/* 發送心跳數據 */
run_notify();
/* Maxfd must be higher socket +1 */
maxfd++;
/* 死循環, 處理本地和manager的數據 */
while (1) {
/* 類似心跳數據 keep alive */
run_notify();
if (agt->sock > maxfd - 1) {
maxfd = agt->sock + 1;
}
/* Monitor all available sockets from here */
FD_ZERO(&fdset);
FD_SET(agt->sock, &fdset);
FD_SET(agt->m_queue, &fdset);
fdtimeout.tv_sec = 1;
fdtimeout.tv_usec = 0;
/* Wait with a timeout for any descriptor */
rc = select(maxfd, &fdset, NULL, NULL, &fdtimeout);
if (rc == -1) {
merror_exit(SELECT_ERROR, errno, strerror(errno));
} else if (rc == 0) {
continue;
}
/* 接收數據,判定manager是否在線 數據格式"Received message: '#!-agent ack '", 可通過將agent進程的日誌級別調成 agent.debug=2, 重啓就行了*/
if (FD_ISSET(agt->sock, &fdset)) {
if (receive_msg() < 0) {
update_status(GA_STATUS_NACTIVE);
merror(LOST_ERROR);
os_setwait();
start_agent(0);
minfo(SERVER_UP);
os_delwait();
update_status(GA_STATUS_ACTIVE);
}
}
/* 將信息蒐集的數據進行轉發到manager,其他類似日誌也是從這裏轉發的 */
if (FD_ISSET(agt->m_queue, &fdset)) {
EventForward();
}
}
}
/* Receive a message locally on the agent and forward it to the manager */
void *EventForward()
{
ssize_t recv_b;
char msg[OS_MAXSTR + 1];
/* Initialize variables */
msg[0] = '\0';
msg[OS_MAXSTR] = '\0';
/* 讀取數據 */
while ((recv_b = recv(agt->m_queue, msg, OS_MAXSTR, MSG_DONTWAIT)) > 0) {
msg[recv_b] = '\0';
if (agt->buffer){//默認走這個分支,進行控制處理數據的速率
if (buffer_append(msg) < 0) {
break;
}
}else{
agent_state.msg_count++;
/*轉發到manager端*/
if (send_msg(msg, -1) < 0) {
break;
}
}
}
return (NULL);
}
/* 發送數據到server端 */
int send_msg(const char *msg, ssize_t msg_length)
{
ssize_t msg_size;
char crypt_msg[OS_MAXSTR + 1];
int retval;
int error;
merror("send_msg '%.*s'.",
msg_length, msg);
/* 構造消息頭, 壓縮 加密*/
msg_size = CreateSecMSG(&keys, msg, msg_length < 0 ? strlen(msg) : (size_t)msg_length, crypt_msg, 0);
if (msg_size <= 0) {
merror(SEC_ERROR);
return (-1);
}
/* Send msg_size of crypt_msg */
if (agt->server[agt->rip_id].protocol == UDP_PROTO) {
retval = OS_SendUDPbySize(agt->sock, msg_size, crypt_msg);
#ifndef WIN32
error = errno;
#endif
} else {
w_mutex_lock(&send_mutex);
/* 轉發數據到manager*/
retval = OS_SendSecureTCP(agt->sock, msg_size, crypt_msg);
#ifndef WIN32
error = errno;
#endif
w_mutex_unlock(&send_mutex);
}
...
sleep(1);
}
return retval;
}
manager端處理流程:
ossec-remoted進程:
-
1:初始化:切換工作路徑(/var/ossec),創建 網絡連接(udp )和 unix socket
-
2: 通過網絡連接接收到agent的數據,解析先解密再解壓;
-
3:通過 unix socket進行轉發給ossec-analysisd進程;
ossec-analysisd進程:
-
1:初始化 :切換工作目錄,創建 unix socket ,讀取配置的規則: decoder和rules ,在ruleset目錄下;創建decoder 和process event等線程組,和線程之間數據共享的各種隊列。 我們主要關心的是ad_input_main線程、w_decode_syscollector_thread線程組、decode_queue_syscollector_input、decode_queue_event_output 隊列
-
2:ad_input_main負責從網絡中獲取數據, 將數據存入decode_queue_syscollector_input
-
3:w_decode_syscollector_thread負責從decode_queue_syscollector_input拿出數據,通過DecodeSyscollector函數進行解析,解析方式按照agent端發過來的標誌進行判定是哪種類型的數據(OS),然後進入decode_osinfo函數中進行解析json數據,然後拼接數據以'|'分隔,例如agent 000 osinfo save 1216023267|2019/11/07 07:56:58|ubuntu|x86_64|Ubuntu|14.04.6 LTS, Trusty Tahr|NULL|14|04|NULL|ubuntu|Linux|4.4.0-142-generic|#168~14.04.1-Ubuntu SMP Sat Jan 19 11:26:28 UTC 2019,
-
再通過WDB_LOCAL_SOCK unix socket 轉發到wazuh-db進程, 將數據入隊decode_queue_event_output;
-
4: w_process_event_thread線程從decode_queue_event_output拿出數據,進行後續處理;
wazuh-db進程:
-
1: 初始化:切換工作路徑,創建線程組run_dealer、run_worker、run_gc、run_up, 創建notify_queue、WDB_LOCAL_SOCK unix socket、open_dbs哈希表;
-
2: run_dealer線程負責accept連接,並存入notify_queue中;
-
3: run_worker負責從notify_queue中取出socket,接收數據,通過wdb_parse函數並根據類型調用對應的解析函數(wdb_parse_osinfo)進行解析,並調用對應的存儲函數wdb_osinfo_save存入sqlite數據庫,如果是新增加的類型需要在schema_agents.sql中增加schema,並在wdb.c文件中的SQL_STMT數組中,定義插入刪除更新sql語句,並增加對應的枚舉變量wdb_stmt的元素;
-
4:run_up 循環遍歷/var/ossec/queue/db目錄,讀取 db文件,調用wdb_open_agent2函數判定是否已經打開過,如果沒有,則創建wdb_t實例(給run_worker寫入數據庫用), 調用sqlit api 打開對應數據庫文件, 其中open_dbs用於保存打開的sqlite db文件來去重:例如:/var/ossec/queue/db/000.db;
-
5:run_gc用於提交事務;
代碼分析:
src/remoted/main.c
int main(int argc, char **argv)
{
...
/* 切換工作目錄 */
if (Privsep_Chroot(dir) < 0) {
merror_exit(CHROOT_ERROR, dir, errno, strerror(errno));
}
nowChroot();
...
/* Really start the program */
i = 0;
/* 一個子進程處理一種類型的網絡連接, 默認udp*/
while (logr.conn[i] != 0) {
/* Fork for each connection handler */
if (fork() == 0) {
/* On the child */
mdebug1("Forking remoted: '%d'.", i);
logr.position = i;
/* 內部處理agent發送過來的數據,並轉發到消息隊列中,給後續處理*/
HandleRemote(uid);
} else {
i++;
continue;
}
}
}
void HandleRemote(int uid)
{
int position = logr.position;
int recv_timeout; //timeout in seconds waiting for a client reply
int send_timeout;
/*獲取配置文件信息或者默認值*/
recv_timeout = getDefine_Int("remoted", "recv_timeout", 1, 60);
send_timeout = getDefine_Int("remoted", "send_timeout", 1, 60);
tcp_keepidle = getDefine_Int("remoted", "tcp_keepidle", 1, 7200);
tcp_keepintvl = getDefine_Int("remoted", "tcp_keepintvl", 1, 100);
tcp_keepcnt = getDefine_Int("remoted", "tcp_keepcnt", 1, 50);
/* If syslog connection and allowips is not defined, exit */
if (logr.conn[position] == SYSLOG_CONN) {
if (logr.allowips == NULL) {
exit(0);
} else {
os_ip **tmp_ips;
tmp_ips = logr.allowips;
while (*tmp_ips) {
minfo("Remote syslog allowed from: '%s'", (*tmp_ips)->ip);
tmp_ips++;
}
}
}
// Set resource limit for file descriptors
{
nofile = getDefine_Int("remoted", "rlimit_nofile", 1024, 1048576);
struct rlimit rlimit = { nofile, nofile };
if (setrlimit(RLIMIT_NOFILE, &rlimit) < 0) {
merror("Could not set resource limit for file descriptors to %d: %s (%d)", (int)nofile, strerror(errno), errno);
}
}
/* 綁定tcp 連接 IP PORT,socket fd 保存在logr.sock中,後面accept時會用到 */
if (logr.proto[position] == TCP_PROTO) {
if ((logr.sock = OS_Bindporttcp(logr.port[position], logr.lip[position], logr.ipv6[position])) < 0) {
merror_exit(BIND_ERROR, logr.port[position], errno, strerror(errno));
} else if (logr.conn[position] == SECURE_CONN) {
if (OS_SetKeepalive(logr.sock) < 0){
merror("OS_SetKeepalive failed with error '%s'", strerror(errno));
}
#ifndef CLIENT
else {
OS_SetKeepalive_Options(logr.sock, tcp_keepidle, tcp_keepintvl, tcp_keepcnt);
}
#endif
if (OS_SetRecvTimeout(logr.sock, recv_timeout, 0) < 0){
merror("OS_SetRecvTimeout failed with error '%s'", strerror(errno));
}
if (OS_SetSendTimeout(logr.sock, send_timeout) < 0){
merror("OS_SetSendTimeout failed with error '%s'", strerror(errno));
}
}
} else {
/* UDP相關設置 */
if ((logr.sock =
OS_Bindportudp(logr.port[position], logr.lip[position], logr.ipv6[position])) < 0) {
merror_exit(BIND_ERROR, logr.port[position], errno, strerror(errno));
}
}
/* Revoke privileges */
if (Privsep_SetUser(uid) < 0) {
merror_exit(SETUID_ERROR, REMUSER, errno, strerror(errno));
}
/* Create PID */
if (CreatePID(ARGV0, getpid()) < 0) {
merror_exit(PID_ERROR);
}
/* 啓動的相關日誌 */
minfo(STARTUP_MSG " Listening on port %d/%s (%s).",
(int)getpid(),
logr.port[position],
logr.proto[position] == TCP_PROTO ? "TCP" : "UDP",
logr.conn[position] == SECURE_CONN ? "secure" : "syslog");
/* 如果是安全網絡連接, 調用 HandleSecure 處理*/
if (logr.conn[position] == SECURE_CONN) {
HandleSecure();
}
else if (logr.proto[position] == TCP_PROTO) {
HandleSyslogTCP();
}
/* If not, deal with syslog */
else {
HandleSyslog();
}
}
/* 處理安全連接主入口 */
void HandleSecure()
{
const int protocol = logr.proto[logr.position];
int sock_client;
int n_events = 0;
char buffer[OS_MAXSTR + 1];
ssize_t recv_b;
struct sockaddr_in peer_info;
wnotify_t * notify = NULL;
/* manager端的初始化 */
manager_init();
// 初始化消息隊列,在接收到數據包的時候,經過相關處理後,入隊
rem_msginit(logr.queue_size);
...
/* 創建等待消息線程池,還沒分析其作用*/
{
int i;
sender_pool = getDefine_Int("remoted", "sender_pool", 1, 64);
mdebug2("Creating %d sender threads.", sender_pool);
for (i = 0; i < sender_pool; i++) {
w_create_thread(wait_for_msgs, NULL);
}
}
//創建消息處理線程池
{
int worker_pool = getDefine_Int("remoted", "worker_pool", 1, 16);
while (worker_pool > 0) {
w_create_thread(rem_handler_main, NULL);
worker_pool--;
}
}
/* Connect to the message queue
* 連接到消息隊列, unix socket 路徑/var/ossec/queue/ossec/queue.
*/
if ((logr.m_queue = StartMQ(DEFAULTQUEUE, WRITE)) < 0) {
merror_exit(QUEUE_FATAL, DEFAULTQUEUE);
}
...
/* Set up peer size */
logr.peer_size = sizeof(peer_info);
/* Initialize some variables */
memset(buffer, '\0', OS_MAXSTR + 1);
/* 如果走TCP協議, 初始化 epoll,並將監聽的fd添加進epoll*/
if (protocol == TCP_PROTO) {
if (notify = wnotify_init(MAX_EVENTS), !notify) {
merror_exit("wnotify_init(): %s (%d)", strerror(errno), errno);
}
if (wnotify_add(notify, logr.sock) < 0) {
merror_exit("wnotify_add(%d): %s (%d)", logr.sock, strerror(errno), errno);
}
}
while (1) {
/* 死循環接收消息 */
if (protocol == TCP_PROTO) {
/* 獲取活躍的event*/
if (n_events = wnotify_wait(notify, EPOLL_MILLIS), n_events < 0) {
if (errno != EINTR) {
merror("Waiting for connection: %s (%d)", strerror(errno), errno);
sleep(1);
}
continue;
}
/* 遍歷活躍的event */
int i;
for (i = 0; i < n_events; i++) {
int fd = wnotify_get(notify, i);
/* 如果是監聽的fd,說明是agent連上來了, accept它*/
if (fd == logr.sock) {
sock_client = accept(logr.sock, (struct sockaddr *)&peer_info, &logr.peer_size);
if (sock_client < 0) {
merror_exit(ACCEPT_ERROR);
}
/* 初始化新的socket fd */
nb_open(&netbuffer, sock_client, &peer_info);
rem_inc_tcp();
/* 將新的fd加入epoll進行監控*/
if (wnotify_add(notify, sock_client) < 0) {
merror("wnotify_add(%d, %d): %s (%d)", notify->fd, sock_client, strerror(errno), errno);
_close_sock(&keys, sock_client);
}
} else {
/* 如果是已經accept的socket ,直接接收數據,並放入消息隊列中*/
sock_client = fd;
/*nb_recv內部最終還是調用rem_msgpush進行轉發到消息隊列*/
switch (recv_b = nb_recv(&netbuffer, sock_client), recv_b) {
case -2:
...
continue;
case -1:
...
case 0:
if (wnotify_delete(notify, sock_client) < 0) {
merror("wnotify_delete(%d): %s (%d)", sock_client, strerror(errno), errno);
}
_close_sock(&keys, sock_client);
continue;
default:
rem_add_recv((unsigned long)recv_b);
}
}
}
} else {
/* udp 直接接收數據*/
recv_b = recvfrom(logr.sock, buffer, OS_MAXSTR, 0, (struct sockaddr *)&peer_info, &logr.peer_size);
/* Nothing received */
if (recv_b <= 0) {
continue;
} else {
/* 數據不爲空,放入消息隊列,等待後續處理*/
rem_msgpush(buffer, recv_b, &peer_info, -1);
rem_add_recv((unsigned long)recv_b);
}
}
}
}
前面我提到消息處理線程池,就是在輪詢,消息隊列中是否有消息存在,如果有,就進行解析(解密、解壓)轉發到analysis 進程。
rem_handler_main=>HandleSecureMessage
我們看看HandleSecureMessage的實現
static void HandleSecureMessage(char *buffer, int recv_b, struct sockaddr_in *peer_info, int sock_client) {
/*解析消息頭 如果agent ID不對關閉socket 返回 代碼比較長, */
...
/* ReadSecMSG內部先解密數據,然後解壓 */
if (r = ReadSecMSG(&keys, tmp_msg, cleartext_msg, agentid, recv_b - 1, &msg_length, srcip, &tmp_msg), r != KS_VALID) {
/* If duplicated, a warning was already generated */
key_unlock();
if (r == KS_ENCKEY) {
if (ip_found) {
push_request(srcip,"ip");
} else {
push_request(buffer + 1, "id");
}
}
if (sock_client >= 0)
_close_sock(&keys, sock_client);
return;
}
...
/*
* 如果我們不能轉發消息到analysis進程,嘗試重連一次,如果失敗,退出
*/
if (SendMSG(logr.m_queue, tmp_msg, srcmsg,
SECURE_MQ) < 0) {
merror(QUEUE_ERROR, DEFAULTQUEUE, strerror(errno));
if ((logr.m_queue = StartMQ(DEFAULTQUEUE, WRITE)) < 0) {
merror_exit(QUEUE_FATAL, DEFAULTQUEUE);
}
} else {
rem_inc_evt();
}
}
接下來看一下analysis 怎麼處理的:
main函數代碼 (src/analysisd/analysisd.c):
#ifndef TESTRULE
int main(int argc, char **argv)
#else
__attribute__((noreturn))
int main_analysisd(int argc, char **argv)
#endif
{
int c = 0, m_queue = 0, test_config = 0, run_foreground = 0;
int debug_level = 0;
const char *dir = DEFAULTDIR;
const char *user = USER;
const char *group = GROUPGLOBAL;
uid_t uid;
gid_t gid;
...
/* Set the group */
if (Privsep_SetGroup(gid) < 0) {
merror_exit(SETGID_ERROR, group, errno, strerror(errno));
}
/* Chroot 切換工作目錄 進行隔離*/
if (Privsep_Chroot(dir) < 0) {
merror_exit(CHROOT_ERROR, dir, errno, strerror(errno));
}
nowChroot();
...
/* 創建消息隊列從ossec-remoted進程獲取數據 */
if ((m_queue = StartMQ(DEFAULTQUEUE, READ)) < 0) {
merror_exit(QUEUE_ERROR, DEFAULTQUEUE, strerror(errno));
}
...
/* 主要入口函數 */
OS_ReadMSG(m_queue);
exit(0);
}
/* Main function. Receives the messages(events) and analyze them all */
#ifndef TESTRULE
__attribute__((noreturn))
void OS_ReadMSG(int m_queue)
#else
__attribute__((noreturn))
void OS_ReadMSG_analysisd(int m_queue)
#endif
{
...
/* 信息蒐集初始化 */
SyscollectorInit();
...
/* 創建接收ossec-remoted消息隊列處理線程,並保存到相應隊列中 */
w_create_thread(ad_input_main, &m_queue);
...
/* 創建 解析 信息蒐集線程池 */
for(i = 0; i < num_decode_syscollector_threads;i++){
w_create_thread(w_decode_syscollector_thread,NULL);
}
}
// 消息處理線程函數
void * ad_input_main(void * args) {
int m_queue = *(int *)args;
char buffer[OS_MAXSTR + 1] = "";
char * copy;
char *msg;
int result;
int recv = 0;
while (1) {
//從/var/ossec/queue/ossec/queue socket中讀取數據
if (recv = OS_RecvUnix(m_queue, OS_MAXSTR, buffer),recv) {
buffer[recv] = '\0';
msg = buffer;
/* Get the time we received the event */
gettime(&c_timespec);
/* Check for a valid message */
if (strlen(msg) < 4) {
merror(IMSG_ERROR, msg);
continue;
}
s_events_received++;
// 根據消息類型進行區分 我們需要關注的是SYSCOLLECTOR_MQ
if (msg[0] == SYSCHECK_MQ) {
os_strdup(buffer, copy);
if(queue_full(decode_queue_syscheck_input)){
if(!reported_syscheck){
reported_syscheck = 1;
mwarn("Syscheck decoder queue is full.");
}
w_inc_dropped_events();
free(copy);
continue;
}
result = queue_push_ex(decode_queue_syscheck_input,copy);
if(result < 0){
if(!reported_syscheck){
reported_syscheck = 1;
mwarn("Syscheck decoder queue is full.");
}
w_inc_dropped_events();
free(copy);
continue;
}
hourly_syscheck++;
/* Increment number of events received */
hourly_events++;
}
...
// 信息蒐集處理分支
else if(msg[0] == SYSCOLLECTOR_MQ){
os_strdup(buffer, copy);
if(queue_full(decode_queue_syscollector_input)){
if(!reported_syscollector){
reported_syscollector = 1;
mwarn("Syscollector decoder queue is full.");
}
w_inc_dropped_events();
free(copy);
continue;
}
//將信息放入decode_queue_syscollector_input隊列中
result = queue_push_ex(decode_queue_syscollector_input,copy);
if(result < 0){
if(!reported_syscollector){
reported_syscollector = 1;
mwarn("Syscollector decoder queue is full.");
}
w_inc_dropped_events();
free(copy);
continue;
}
/* Increment number of events received */
hourly_events++;
}
...
}
}
return NULL;
}
/* 將數據出隊,然後調用DecodeSyscollector進行解析*/
void * w_decode_syscollector_thread(__attribute__((unused)) void * args){
Eventinfo *lf = NULL;
char *msg = NULL;
int socket = -1;
while(1){
/* Receive message from queue */
if (msg = queue_pop_ex(decode_queue_syscollector_input), msg) {
...
if (!DecodeSyscollector(lf,&socket)) {
/* We don't process syscollector events further */
w_free_event_info(lf);
}
else{
if (queue_push_ex_block(decode_queue_event_output,lf) < 0) {
w_free_event_info(lf);
}
}
w_inc_syscollector_decoded_events();
}
}
}
/* 信息蒐集數據的解析 */
int DecodeSyscollector(Eventinfo *lf,int *socket)
{
cJSON *logJSON;
cJSON *json_type;
char *msg_type = NULL;
lf->decoder_info = sysc_decoder;
// Check location
if (lf->location[0] == '(') {
char* search;
search = strchr(lf->location, '>');
if (!search) {
mdebug1("Invalid received event.");
return (0);
}
else if (strcmp(search + 1, "syscollector") != 0) {
mdebug1("Invalid received event. Not syscollector.");
return (0);
}
} else if (strcmp(lf->location, "syscollector") != 0) {
mdebug1("Invalid received event. (Location)");
return (0);
}
// Parsing event.
logJSON = cJSON_Parse(lf->log);
if (!logJSON) {
mdebug1("Error parsing JSON event. %s", cJSON_GetErrorPtr());
return (0);
}
// Detect message type
json_type = cJSON_GetObjectItem(logJSON, "type");
if (!(json_type && (msg_type = json_type->valuestring))) {
mdebug1("Invalid message. Type not found.");
cJSON_Delete (logJSON);
return (0);
}
fillData(lf,"type",msg_type);
if (strcmp(msg_type, "port") == 0 || strcmp(msg_type, "port_end") == 0) {
if (decode_port(lf, logJSON,socket) < 0) {
mdebug1("Unable to send ports information to Wazuh DB.");
cJSON_Delete (logJSON);
return (0);
}
}
else if (strcmp(msg_type, "OS") == 0) {
/* 解析os 相關信息*/
if (decode_osinfo(lf, logJSON,socket) < 0) {
mdebug1("Unable to send osinfo message to Wazuh DB.");
cJSON_Delete (logJSON);
return (0);
}
}
...
else {
mdebug1("Invalid message type: %s.", msg_type);
cJSON_Delete (logJSON);
return (0);
}
cJSON_Delete (logJSON);
return (1);
}
/* 解析os 信息 操作系統的版本 名稱等等, */
int decode_osinfo( Eventinfo *lf, cJSON * logJSON,int *socket) {
cJSON * inventory;
if (inventory = cJSON_GetObjectItem(logJSON, "inventory"), inventory) {
cJSON * scan_id = cJSON_GetObjectItem(logJSON, "ID");
cJSON * scan_time = cJSON_GetObjectItem(logJSON, "timestamp");
cJSON * os_name = cJSON_GetObjectItem(inventory, "os_name");
cJSON * os_version = cJSON_GetObjectItem(inventory, "os_version");
cJSON * os_codename = cJSON_GetObjectItem(inventory, "os_codename");
cJSON * hostname = cJSON_GetObjectItem(inventory, "hostname");
cJSON * architecture = cJSON_GetObjectItem(inventory, "architecture");
cJSON * os_major = cJSON_GetObjectItem(inventory, "os_major");
cJSON * os_minor = cJSON_GetObjectItem(inventory, "os_minor");
cJSON * os_build = cJSON_GetObjectItem(inventory, "os_build");
cJSON * os_platform = cJSON_GetObjectItem(inventory, "os_platform");
cJSON * sysname = cJSON_GetObjectItem(inventory, "sysname");
cJSON * release = cJSON_GetObjectItem(inventory, "release");
cJSON * version = cJSON_GetObjectItem(inventory, "version");
char * msg = NULL;
os_calloc(OS_SIZE_6144, sizeof(char), msg);
snprintf(msg, OS_SIZE_6144 - 1, "agent %s osinfo save", lf->agent_id);
if (scan_id) {
char id[OS_SIZE_1024];
snprintf(id, OS_SIZE_1024 - 1, "%d", scan_id->valueint);
wm_strcat(&msg, id, ' ');
} else {
wm_strcat(&msg, "NULL", ' ');
}
if (scan_time) {
wm_strcat(&msg, scan_time->valuestring, '|');
} else {
wm_strcat(&msg, "NULL", '|');
}
if (hostname) {
wm_strcat(&msg, hostname->valuestring, '|');
fillData(lf,"os.hostname",hostname->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (architecture) {
wm_strcat(&msg, architecture->valuestring, '|');
fillData(lf,"os.architecture",architecture->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (os_name) {
wm_strcat(&msg, os_name->valuestring, '|');
fillData(lf,"os.name",os_name->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (os_version) {
wm_strcat(&msg, os_version->valuestring, '|');
fillData(lf,"os.version",os_version->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (os_codename) {
wm_strcat(&msg, os_codename->valuestring, '|');
fillData(lf,"os.codename",os_codename->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (os_major) {
wm_strcat(&msg, os_major->valuestring, '|');
fillData(lf,"os.major",os_major->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (os_minor) {
wm_strcat(&msg, os_minor->valuestring, '|');
fillData(lf,"os.minor",os_minor->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (os_build) {
wm_strcat(&msg, os_build->valuestring, '|');
fillData(lf,"os.build",os_build->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (os_platform) {
wm_strcat(&msg, os_platform->valuestring, '|');
fillData(lf,"os.platform",os_platform->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (sysname) {
wm_strcat(&msg, sysname->valuestring, '|');
fillData(lf,"os.sysname",sysname->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (release) {
wm_strcat(&msg, release->valuestring, '|');
fillData(lf,"os.release",release->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
if (version) {
wm_strcat(&msg, version->valuestring, '|');
fillData(lf,"os.release_version",version->valuestring);
} else {
wm_strcat(&msg, "NULL", '|');
}
/*將解析好的數據發送給 wazuh-db 進行後續處理, 也是通過unix socket實現 /var/ossec/queue/db/wdb*/
if (sc_send_db(msg,socket) < 0) {
return -1;
}
}
return 0;
}
wazuh-db進程的處理:
int main(int argc, char ** argv) {
...
// 啓動相關線程, run_dealer 將/var/ossec/queue/db/wdb 的socket 放入notify_queue,等待run_worker 去處理,然後run_worker接收數據並調用wdb_parse解析調用wdb_parse_osinfo=>wdb_osinfo_save=>wdb_osinfo_insert存儲到sqlite中
if (status = pthread_create(&thread_dealer, NULL, run_dealer, NULL), status != 0) {
merror("Couldn't create thread: %s", strerror(status));
return EXIT_FAILURE;
}
os_malloc(sizeof(pthread_t) * config.worker_pool_size, worker_pool);
for (i = 0; i < config.worker_pool_size; i++) {
if (status = pthread_create(worker_pool + i, NULL, run_worker, NULL), status != 0) {
merror("Couldn't create thread: %s", strerror(status));
return EXIT_FAILURE;
}
}
...
}
void * run_dealer(__attribute__((unused)) void * args) {
int sock;
int peer;
fd_set fdset;
struct timeval timeout;
/* 創建/var/ossec/queue/db/wdb socket */
if (sock = OS_BindUnixDomain(WDB_LOCAL_SOCK, SOCK_STREAM, OS_MAXSTR), sock < 0) {
merror_exit("Unable to bind to socket '%s': '%s'. Closing local server.",
WDB_LOCAL_SOCK, strerror(errno));
}
while (running) {
// Wait for socket
FD_ZERO(&fdset);
FD_SET(sock, &fdset);
timeout.tv_sec = 1;
timeout.tv_usec = 0;
switch (select(sock + 1, &fdset, NULL, NULL, &timeout)) {
...
}
// 接受新的節點
if (peer = accept(sock, NULL, NULL), peer < 0) {
...
continue;
}
/*將對應的socket 添加到notify_queue中,給run_worker來處理*/
if (wnotify_add(notify_queue, peer) < 0) {
merror("at run_dealer(): wnotify_add(%d): %s (%d)",
peer, strerror(errno), errno);
goto error;
}
}
error:
close(sock);
unlink(WDB_LOCAL_SOCK);
return NULL;
}
void * run_worker(__attribute__((unused)) void * args) {
char buffer[OS_MAXSTR + 1];
char response[OS_MAXSTR + 1];
ssize_t length;
int terminal;
int peer;
while (running) {
// Dequeue peer
w_mutex_lock(&queue_mutex);
// 等待事件
switch (wnotify_wait(notify_queue, 100)) {
...
}
// 將給節點從等待隊列中刪除
peer = wnotify_get(notify_queue, 0);
if (wnotify_delete(notify_queue, peer) < 0) {
merror("at run_worker(): wnotify_delete(%d): %s (%d)",
peer, strerror(errno), errno);
}
w_mutex_unlock(&queue_mutex);
ssize_t count;
length = 0;
// 接收數據
count = OS_RecvSecureTCP(peer, buffer, OS_MAXSTR);
...
switch (length) {
...
default:
if (length > 0 && buffer[length - 1] == '\n') {
buffer[length - 1] = '\0';
terminal = 1;
} else {
buffer[length] = '\0';
terminal = 0;
}
*response = '\0';
// 開始解析數據
wdb_parse(buffer, response);
...
break;
}
// 處理完之後重新加入等待隊列中
if (wnotify_add(notify_queue, peer) < 0) {
merror("at run_worker(): wnotify_add(%d): %s (%d)",
peer, strerror(errno), errno);
}
}
return NULL;
}
int wdb_parse(char * input, char * output) {
char * actor;
char * id;
char * query;
char * sql;
char * next;
int agent_id;
char sagent_id[64];
wdb_t * wdb;
cJSON * data;
char * out;
int result = 0;
...
actor = input;
*next++ = '\0';
if (strcmp(actor, "agent") == 0) {
id = next;
...
if (strcmp(query, "syscheck") == 0) {
if (!next) {
mdebug1("DB(%s) Invalid FIM query syntax.", sagent_id);
mdebug2("DB(%s) FIM query error near: %s", sagent_id, query);
snprintf(output, OS_MAXSTR + 1, "err Invalid Syscheck query syntax, near '%.32s'", query);
result = -1;
} else {
result = wdb_parse_syscheck(wdb, next, output);
}
} else if (strcmp(query, "osinfo") == 0) {//處理 os信息
if (!next) {
mdebug1("DB(%s) Invalid DB query syntax.", sagent_id);
mdebug2("DB(%s) query error near: %s", sagent_id, query);
snprintf(output, OS_MAXSTR + 1, "err Invalid DB query syntax, near '%.32s'", query);
result = -1;
} else {
if (wdb_parse_osinfo(wdb, next, output) == 0){
mdebug2("Updated 'sys_osinfo' table for agent '%s'", sagent_id);
} else {
merror("Unable to update 'sys_osinfo' table for agent '%s'", sagent_id);
}
}
...
}
...
return result;
} else {
mdebug1("DB(%s) Invalid DB query actor: %s", sagent_id, actor);
snprintf(output, OS_MAXSTR + 1, "err Invalid DB query actor: '%.32s'", actor);
return -1;
}
}
int wdb_parse_osinfo(wdb_t * wdb, char * input, char * output) {
char * curr;
char * next;
char * scan_id;
char * scan_time;
char * hostname;
char * architecture;
char * os_name;
char * os_version;
char * os_codename;
char * os_major;
char * os_minor;
char * os_build;
char * os_platform;
char * sysname;
char * release;
char * version;
int result;
if (next = strchr(input, ' '), !next) {
mdebug1("Invalid OS info query syntax.");
mdebug2("OS info query: %s", input);
snprintf(output, OS_MAXSTR + 1, "err Invalid OS info query syntax, near '%.32s'", input);
return -1;
}
curr = input;
*next++ = '\0';
if (strcmp(curr, "save") == 0) {
curr = next;
//解析字段 以'|' 分隔
if (next = strchr(curr, '|'), !next) {
mdebug1("Invalid OS info query syntax.");
mdebug2("OS info query: %s", curr);
snprintf(output, OS_MAXSTR + 1, "err Invalid OS info query syntax, near '%.32s'", curr);
return -1;
}
scan_id = curr;
*next++ = '\0';
curr = next;
if (!strcmp(scan_id, "NULL"))
scan_id = NULL;
...
//將各個字段解析出來,並存人sqlite
if (result = wdb_osinfo_save(wdb, scan_id, scan_time, hostname, architecture, os_name, os_version, os_codename, os_major, os_minor, os_build, os_platform, sysname, release, version), result < 0) {
mdebug1("Cannot save OS information.");
snprintf(output, OS_MAXSTR + 1, "err Cannot save OS information.");
} else {
snprintf(output, OS_MAXSTR + 1, "ok");
}
return result;
} else {
mdebug1("Invalid OS info query syntax.");
mdebug2("DB query error near: %s", curr);
snprintf(output, OS_MAXSTR + 1, "err Invalid OS info query syntax, near '%.32s'", curr);
return -1;
}
}
// 記錄到數據庫中, 插入語句定義在SQL_STMT數組中(src/wazuh_db/wdb.c)
int wdb_osinfo_save(wdb_t * wdb, const char * scan_id, const char * scan_time, const char * hostname, const char * architecture, const char * os_name, const char * os_version, const char * os_codename, const char * os_major, const char * os_minor, const char * os_build, const char * os_platform, const char * sysname, const char * release, const char * version) {
sqlite3_stmt *stmt = NULL;
...
// 存入數據庫 沒什麼難度
if (wdb_osinfo_insert(wdb,
scan_id,
scan_time,
hostname,
architecture,
os_name,
os_version,
os_codename,
os_major,
os_minor,
os_build,
os_platform,
sysname,
release,
version) < 0) {
return -1;
}
return 0;
}
c代碼就講解完畢了;
接下來可以參照wazuh官網去部署manager agent wazuh-api
展示
manager 端進程
使用curl 調用restfull API 查詢(需要在manager端部署wazuh-api):
curl -u foo:bar -k -X GET "http://127.0.0.1:55000/syscollector/007/os?pretty"