1 /*
2 ** Zabbix
3 ** Copyright (C) 2001-2021 Zabbix SIA
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 **/
19 
20 #include "common.h"
21 #include "db.h"
22 #include "log.h"
23 #include "events.h"
24 #include "discovery.h"
25 
discovery_get_dhost_by_value(zbx_uint64_t dcheckid,const char * value)26 static DB_RESULT	discovery_get_dhost_by_value(zbx_uint64_t dcheckid, const char *value)
27 {
28 	DB_RESULT	result;
29 	char		*value_esc;
30 
31 	value_esc = DBdyn_escape_field("dservices", "value", value);
32 
33 	result = DBselect(
34 			"select dh.dhostid,dh.status,dh.lastup,dh.lastdown"
35 			" from dhosts dh,dservices ds"
36 			" where ds.dhostid=dh.dhostid"
37 				" and ds.dcheckid=" ZBX_FS_UI64
38 				" and ds.value" ZBX_SQL_STRCMP
39 			" order by dh.dhostid",
40 			dcheckid, ZBX_SQL_STRVAL_EQ(value_esc));
41 
42 	zbx_free(value_esc);
43 
44 	return result;
45 }
46 
discovery_get_dhost_by_ip_port(zbx_uint64_t druleid,const char * ip,int port)47 static DB_RESULT	discovery_get_dhost_by_ip_port(zbx_uint64_t druleid, const char *ip, int port)
48 {
49 	DB_RESULT	result;
50 	char		*ip_esc;
51 
52 	ip_esc = DBdyn_escape_field("dservices", "ip", ip);
53 
54 	result = DBselect(
55 			"select dh.dhostid,dh.status,dh.lastup,dh.lastdown"
56 			" from dhosts dh,dservices ds"
57 			" where ds.dhostid=dh.dhostid"
58 				" and dh.druleid=" ZBX_FS_UI64
59 				" and ds.ip" ZBX_SQL_STRCMP
60 				" and ds.port=%d"
61 			" order by dh.dhostid",
62 			druleid, ZBX_SQL_STRVAL_EQ(ip_esc), port);
63 
64 	zbx_free(ip_esc);
65 
66 	return result;
67 }
68 
69 /******************************************************************************
70  *                                                                            *
71  * Function: discovery_separate_host                                          *
72  *                                                                            *
73  * Purpose: separate multiple-IP hosts                                        *
74  *                                                                            *
75  * Parameters: host ip address                                                *
76  *                                                                            *
77  ******************************************************************************/
discovery_separate_host(const DB_DRULE * drule,DB_DHOST * dhost,const char * ip)78 static void	discovery_separate_host(const DB_DRULE *drule, DB_DHOST *dhost, const char *ip)
79 {
80 	DB_RESULT	result;
81 	char		*ip_esc, *sql = NULL;
82 	zbx_uint64_t	dhostid;
83 
84 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() ip:'%s'", __func__, ip);
85 
86 	ip_esc = DBdyn_escape_field("dservices", "ip", ip);
87 
88 	sql = zbx_dsprintf(sql,
89 			"select dserviceid"
90 			" from dservices"
91 			" where dhostid=" ZBX_FS_UI64
92 				" and ip" ZBX_SQL_STRCMP,
93 			dhost->dhostid, ZBX_SQL_STRVAL_NE(ip_esc));
94 
95 	result = DBselectN(sql, 1);
96 
97 	if (NULL != DBfetch(result))
98 	{
99 		dhostid = DBget_maxid("dhosts");
100 
101 		DBexecute("insert into dhosts (dhostid,druleid)"
102 				" values (" ZBX_FS_UI64 "," ZBX_FS_UI64 ")",
103 				dhostid, drule->druleid);
104 
105 		DBexecute("update dservices"
106 				" set dhostid=" ZBX_FS_UI64
107 				" where dhostid=" ZBX_FS_UI64
108 					" and ip" ZBX_SQL_STRCMP,
109 				dhostid, dhost->dhostid, ZBX_SQL_STRVAL_EQ(ip_esc));
110 
111 		dhost->dhostid = dhostid;
112 		dhost->status = DOBJECT_STATUS_DOWN;
113 		dhost->lastup = 0;
114 		dhost->lastdown = 0;
115 	}
116 	DBfree_result(result);
117 
118 	zbx_free(sql);
119 	zbx_free(ip_esc);
120 
121 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
122 }
123 
124 /******************************************************************************
125  *                                                                            *
126  * Function: discovery_register_host                                          *
127  *                                                                            *
128  * Purpose: register host if one does not exist                               *
129  *                                                                            *
130  * Parameters: host ip address                                                *
131  *                                                                            *
132  ******************************************************************************/
discovery_register_host(const DB_DRULE * drule,zbx_uint64_t dcheckid,DB_DHOST * dhost,const char * ip,int port,int status,const char * value)133 static void	discovery_register_host(const DB_DRULE *drule, zbx_uint64_t dcheckid, DB_DHOST *dhost,
134 		const char *ip, int port, int status, const char *value)
135 {
136 	DB_RESULT	result;
137 	DB_ROW		row;
138 	int		match_value = 0;
139 
140 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() ip:'%s' status:%d value:'%s'", __func__, ip, status, value);
141 
142 	if (drule->unique_dcheckid == dcheckid)
143 	{
144 		result = discovery_get_dhost_by_value(dcheckid, value);
145 
146 		if (NULL == (row = DBfetch(result)))
147 		{
148 			DBfree_result(result);
149 
150 			result = discovery_get_dhost_by_ip_port(drule->druleid, ip, port);
151 			row = DBfetch(result);
152 		}
153 		else
154 			match_value = 1;
155 
156 	}
157 	else
158 	{
159 		result = discovery_get_dhost_by_ip_port(drule->druleid, ip, port);
160 		row = DBfetch(result);
161 	}
162 
163 	if (NULL == row)
164 	{
165 		if (DOBJECT_STATUS_UP == status)	/* add host only if service is up */
166 		{
167 			zabbix_log(LOG_LEVEL_DEBUG, "new host discovered at %s", ip);
168 
169 			dhost->dhostid = DBget_maxid("dhosts");
170 			dhost->status = DOBJECT_STATUS_DOWN;
171 			dhost->lastup = 0;
172 			dhost->lastdown = 0;
173 
174 			DBexecute("insert into dhosts (dhostid,druleid)"
175 					" values (" ZBX_FS_UI64 "," ZBX_FS_UI64 ")",
176 					dhost->dhostid, drule->druleid);
177 		}
178 	}
179 	else
180 	{
181 		zabbix_log(LOG_LEVEL_DEBUG, "host at %s is already in database", ip);
182 
183 		ZBX_STR2UINT64(dhost->dhostid, row[0]);
184 		dhost->status = atoi(row[1]);
185 		dhost->lastup = atoi(row[2]);
186 		dhost->lastdown = atoi(row[3]);
187 
188 		if (0 == match_value)
189 			discovery_separate_host(drule, dhost, ip);
190 	}
191 	DBfree_result(result);
192 
193 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
194 }
195 
196 /******************************************************************************
197  *                                                                            *
198  * Function: discovery_register_service                                       *
199  *                                                                            *
200  * Purpose: register service if one does not exist                            *
201  *                                                                            *
202  * Parameters: host ip address                                                *
203  *                                                                            *
204  ******************************************************************************/
discovery_register_service(zbx_uint64_t dcheckid,DB_DHOST * dhost,DB_DSERVICE * dservice,const char * ip,const char * dns,int port,int status)205 static void	discovery_register_service(zbx_uint64_t dcheckid, DB_DHOST *dhost, DB_DSERVICE *dservice,
206 		const char *ip, const char *dns, int port, int status)
207 {
208 	DB_RESULT	result;
209 	DB_ROW		row;
210 	char		*ip_esc, *dns_esc;
211 
212 	zbx_uint64_t	dhostid;
213 
214 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() ip:'%s' port:%d", __func__, ip, port);
215 
216 	ip_esc = DBdyn_escape_field("dservices", "ip", ip);
217 
218 	result = DBselect(
219 			"select dserviceid,dhostid,status,lastup,lastdown,value,dns"
220 			" from dservices"
221 			" where dcheckid=" ZBX_FS_UI64
222 				" and ip" ZBX_SQL_STRCMP
223 				" and port=%d",
224 			dcheckid, ZBX_SQL_STRVAL_EQ(ip_esc), port);
225 
226 	if (NULL == (row = DBfetch(result)))
227 	{
228 		if (DOBJECT_STATUS_UP == status)	/* add host only if service is up */
229 		{
230 			zabbix_log(LOG_LEVEL_DEBUG, "new service discovered on port %d", port);
231 
232 			dservice->dserviceid = DBget_maxid("dservices");
233 			dservice->status = DOBJECT_STATUS_DOWN;
234 			dservice->value = zbx_strdup(dservice->value, "");
235 
236 			dns_esc = DBdyn_escape_field("dservices", "dns", dns);
237 
238 			DBexecute("insert into dservices (dserviceid,dhostid,dcheckid,ip,dns,port,status)"
239 					" values (" ZBX_FS_UI64 "," ZBX_FS_UI64 "," ZBX_FS_UI64 ",'%s','%s',%d,%d)",
240 					dservice->dserviceid, dhost->dhostid, dcheckid, ip_esc, dns_esc, port,
241 					dservice->status);
242 
243 			zbx_free(dns_esc);
244 		}
245 	}
246 	else
247 	{
248 		zabbix_log(LOG_LEVEL_DEBUG, "service is already in database");
249 
250 		ZBX_STR2UINT64(dservice->dserviceid, row[0]);
251 		ZBX_STR2UINT64(dhostid, row[1]);
252 		dservice->status = atoi(row[2]);
253 		dservice->lastup = atoi(row[3]);
254 		dservice->lastdown = atoi(row[4]);
255 		dservice->value = zbx_strdup(dservice->value, row[5]);
256 
257 		if (dhostid != dhost->dhostid)
258 		{
259 			DBexecute("update dservices"
260 					" set dhostid=" ZBX_FS_UI64
261 					" where dhostid=" ZBX_FS_UI64,
262 					dhost->dhostid, dhostid);
263 
264 			DBexecute("delete from dhosts"
265 					" where dhostid=" ZBX_FS_UI64,
266 					dhostid);
267 		}
268 
269 		if (0 != strcmp(row[6], dns))
270 		{
271 			dns_esc = DBdyn_escape_field("dservices", "dns", dns);
272 
273 			DBexecute("update dservices"
274 					" set dns='%s'"
275 					" where dserviceid=" ZBX_FS_UI64,
276 					dns_esc, dservice->dserviceid);
277 
278 			zbx_free(dns_esc);
279 		}
280 	}
281 	DBfree_result(result);
282 
283 	zbx_free(ip_esc);
284 
285 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
286 }
287 
288 /******************************************************************************
289  *                                                                            *
290  * Function: discovery_update_dservice                                        *
291  *                                                                            *
292  * Purpose: update discovered service details                                 *
293  *                                                                            *
294  ******************************************************************************/
discovery_update_dservice(zbx_uint64_t dserviceid,int status,int lastup,int lastdown,const char * value)295 static void	discovery_update_dservice(zbx_uint64_t dserviceid, int status, int lastup, int lastdown,
296 		const char *value)
297 {
298 	char	*value_esc;
299 
300 	value_esc = DBdyn_escape_field("dservices", "value", value);
301 
302 	DBexecute("update dservices set status=%d,lastup=%d,lastdown=%d,value='%s' where dserviceid=" ZBX_FS_UI64,
303 			status, lastup, lastdown, value_esc, dserviceid);
304 
305 	zbx_free(value_esc);
306 }
307 
308 /******************************************************************************
309  *                                                                            *
310  * Function: discovery_update_dservice_value                                  *
311  *                                                                            *
312  * Purpose: update discovered service details                                 *
313  *                                                                            *
314  ******************************************************************************/
discovery_update_dservice_value(zbx_uint64_t dserviceid,const char * value)315 static void	discovery_update_dservice_value(zbx_uint64_t dserviceid, const char *value)
316 {
317 	char	*value_esc;
318 
319 	value_esc = DBdyn_escape_field("dservices", "value", value);
320 
321 	DBexecute("update dservices set value='%s' where dserviceid=" ZBX_FS_UI64, value_esc, dserviceid);
322 
323 	zbx_free(value_esc);
324 }
325 
326 /******************************************************************************
327  *                                                                            *
328  * Function: discovery_update_dhost                                           *
329  *                                                                            *
330  * Purpose: update discovered host details                                    *
331  *                                                                            *
332  ******************************************************************************/
discovery_update_dhost(const DB_DHOST * dhost)333 static void	discovery_update_dhost(const DB_DHOST *dhost)
334 {
335 	DBexecute("update dhosts set status=%d,lastup=%d,lastdown=%d where dhostid=" ZBX_FS_UI64,
336 			dhost->status, dhost->lastup, dhost->lastdown, dhost->dhostid);
337 }
338 
339 /******************************************************************************
340  *                                                                            *
341  * Function: discovery_update_service_status                                  *
342  *                                                                            *
343  * Purpose: process and update the new service status                         *
344  *                                                                            *
345  ******************************************************************************/
discovery_update_service_status(DB_DHOST * dhost,const DB_DSERVICE * dservice,int service_status,const char * value,int now)346 static void	discovery_update_service_status(DB_DHOST *dhost, const DB_DSERVICE *dservice, int service_status,
347 		const char *value, int now)
348 {
349 	zbx_timespec_t	ts;
350 
351 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
352 
353 	ts.sec = now;
354 	ts.ns = 0;
355 
356 	if (DOBJECT_STATUS_UP == service_status)
357 	{
358 		if (DOBJECT_STATUS_DOWN == dservice->status || 0 == dservice->lastup)
359 		{
360 			discovery_update_dservice(dservice->dserviceid, service_status, now, 0, value);
361 			zbx_add_event(EVENT_SOURCE_DISCOVERY, EVENT_OBJECT_DSERVICE, dservice->dserviceid, &ts,
362 					DOBJECT_STATUS_DISCOVER, NULL, NULL, NULL, 0, 0, NULL, 0, NULL, 0, NULL, NULL,
363 					NULL);
364 
365 			if (DOBJECT_STATUS_DOWN == dhost->status)
366 			{
367 				/* Service went UP, but host status is DOWN. Update host status. */
368 
369 				dhost->status = DOBJECT_STATUS_UP;
370 				dhost->lastup = now;
371 				dhost->lastdown = 0;
372 
373 				discovery_update_dhost(dhost);
374 				zbx_add_event(EVENT_SOURCE_DISCOVERY, EVENT_OBJECT_DHOST, dhost->dhostid, &ts,
375 						DOBJECT_STATUS_DISCOVER, NULL, NULL, NULL, 0, 0, NULL,
376 						0, NULL, 0, NULL, NULL, NULL);
377 			}
378 		}
379 		else if (0 != strcmp(dservice->value, value))
380 		{
381 			discovery_update_dservice_value(dservice->dserviceid, value);
382 		}
383 	}
384 	else	/* DOBJECT_STATUS_DOWN */
385 	{
386 		if (DOBJECT_STATUS_UP == dservice->status || 0 == dservice->lastdown)
387 		{
388 			discovery_update_dservice(dservice->dserviceid, service_status, 0, now, dservice->value);
389 			zbx_add_event(EVENT_SOURCE_DISCOVERY, EVENT_OBJECT_DSERVICE, dservice->dserviceid, &ts,
390 					DOBJECT_STATUS_LOST, NULL, NULL, NULL, 0, 0, NULL, 0, NULL, 0, NULL, NULL,
391 					NULL);
392 
393 			/* service went DOWN, no need to update host status here as other services may be UP */
394 		}
395 	}
396 	zbx_add_event(EVENT_SOURCE_DISCOVERY, EVENT_OBJECT_DSERVICE, dservice->dserviceid, &ts, service_status,
397 			NULL, NULL, NULL, 0, 0, NULL, 0, NULL, 0, NULL, NULL, NULL);
398 
399 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
400 }
401 
402 /******************************************************************************
403  *                                                                            *
404  * Function: discovery_update_host_status                                     *
405  *                                                                            *
406  * Purpose: update new host status                                            *
407  *                                                                            *
408  ******************************************************************************/
discovery_update_host_status(DB_DHOST * dhost,int status,int now)409 static void	discovery_update_host_status(DB_DHOST *dhost, int status, int now)
410 {
411 	zbx_timespec_t	ts;
412 
413 	ts.sec = now;
414 	ts.ns = 0;
415 
416 	/* update host status */
417 	if (DOBJECT_STATUS_UP == status)
418 	{
419 		if (DOBJECT_STATUS_DOWN == dhost->status || 0 == dhost->lastup)
420 		{
421 			dhost->status = status;
422 			dhost->lastdown = 0;
423 			dhost->lastup = now;
424 
425 			discovery_update_dhost(dhost);
426 			zbx_add_event(EVENT_SOURCE_DISCOVERY, EVENT_OBJECT_DHOST, dhost->dhostid, &ts,
427 					DOBJECT_STATUS_DISCOVER, NULL, NULL, NULL, 0, 0, NULL, 0, NULL, 0, NULL, NULL,
428 					NULL);
429 		}
430 	}
431 	else	/* DOBJECT_STATUS_DOWN */
432 	{
433 		if (DOBJECT_STATUS_UP == dhost->status || 0 == dhost->lastdown)
434 		{
435 			dhost->status = status;
436 			dhost->lastdown = now;
437 			dhost->lastup = 0;
438 
439 			discovery_update_dhost(dhost);
440 			zbx_add_event(EVENT_SOURCE_DISCOVERY, EVENT_OBJECT_DHOST, dhost->dhostid, &ts,
441 					DOBJECT_STATUS_LOST, NULL, NULL, NULL, 0, 0, NULL, 0, NULL, 0, NULL, NULL,
442 					NULL);
443 		}
444 	}
445 	zbx_add_event(EVENT_SOURCE_DISCOVERY, EVENT_OBJECT_DHOST, dhost->dhostid, &ts, status, NULL, NULL, NULL, 0, 0,
446 			NULL, 0, NULL, 0, NULL, NULL, NULL);
447 }
448 
449 /******************************************************************************
450  *                                                                            *
451  * Function: discovery_update_host                                            *
452  *                                                                            *
453  * Purpose: process new host status                                           *
454  *                                                                            *
455  * Parameters: host - host info                                               *
456  *                                                                            *
457  ******************************************************************************/
discovery_update_host(DB_DHOST * dhost,int status,int now)458 void	discovery_update_host(DB_DHOST *dhost, int status, int now)
459 {
460 	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);
461 
462 	if (0 != dhost->dhostid)
463 		discovery_update_host_status(dhost, status, now);
464 
465 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
466 }
467 
468 /******************************************************************************
469  *                                                                            *
470  * Function: discovery_update_service                                         *
471  *                                                                            *
472  * Purpose: process new service status                                        *
473  *                                                                            *
474  * Parameters: service - service info                                         *
475  *                                                                            *
476  ******************************************************************************/
discovery_update_service(const DB_DRULE * drule,zbx_uint64_t dcheckid,DB_DHOST * dhost,const char * ip,const char * dns,int port,int status,const char * value,int now)477 void	discovery_update_service(const DB_DRULE *drule, zbx_uint64_t dcheckid, DB_DHOST *dhost, const char *ip,
478 		const char *dns, int port, int status, const char *value, int now)
479 {
480 	DB_DSERVICE	dservice;
481 
482 	zabbix_log(LOG_LEVEL_DEBUG, "In %s() ip:'%s' dns:'%s' port:%d status:%d value:'%s'",
483 			__func__, ip, dns, port, status, value);
484 
485 	memset(&dservice, 0, sizeof(dservice));
486 
487 	/* register host if is not registered yet */
488 	if (0 == dhost->dhostid)
489 		discovery_register_host(drule, dcheckid, dhost, ip, port, status, value);
490 
491 	/* register service if is not registered yet */
492 	if (0 != dhost->dhostid)
493 		discovery_register_service(dcheckid, dhost, &dservice, ip, dns, port, status);
494 
495 	/* service was not registered because we do not add down service */
496 	if (0 != dservice.dserviceid)
497 		discovery_update_service_status(dhost, &dservice, status, value, now);
498 
499 	zbx_free(dservice.value);
500 
501 	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
502 }
503