1 /*
2  ex: set tabstop=4 shiftwidth=4 autoindent:
3  +-------------------------------------------------------------------------+
4  | Copyright (C) 2002-2016 The Cacti Group                                 |
5  |                                                                         |
6  | This program is free software; you can redistribute it and/or           |
7  | modify it under the terms of the GNU Lesser General Public              |
8  | License as published by the Free Software Foundation; either            |
9  | version 2.1 of the License, or (at your option) any later version. 	   |
10  |                                                                         |
11  | This program is distributed in the hope that it will be useful,         |
12  | but WITHOUT ANY WARRANTY; without even the implied warranty of          |
13  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           |
14  | GNU Lesser General Public License for more details.                     |
15  |                                                                         |
16  | You should have received a copy of the GNU Lesser General Public        |
17  | License along with this library; if not, write to the Free Software     |
18  | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA           |
19  | 02110-1301, USA                                                         |
20  |                                                                         |
21  +-------------------------------------------------------------------------+
22  | spine: a backend data gatherer for cacti                                |
23  +-------------------------------------------------------------------------+
24  | This poller would not have been possible without:                       |
25  |   - Larry Adams (current development and enhancements)                  |
26  |   - Rivo Nurges (rrd support, mysql poller cache, misc functions)       |
27  |   - RTG (core poller code, pthreads, snmp, autoconf examples)           |
28  |   - Brady Alleman/Doug Warner (threading ideas, implimentation details) |
29  +-------------------------------------------------------------------------+
30  | - Cacti - http://www.cacti.net/                                         |
31  +-------------------------------------------------------------------------+
32 */
33 
34 #include "common.h"
35 #include "spine.h"
36 
37 /*! \fn int ping_host(host_t *host, ping_t *ping)
38  *  \brief ping a host to determine if it is reachable for polling
39  *  \param host a pointer to the current host structure
40  *  \param ping a pointer to the current hosts ping structure
41  *
42  *  This function pings a host using the method specified within the system
43  *  configuration and then returns the host status to the calling function.
44  *
45  *  \return HOST_UP if the host is reachable, HOST_DOWN otherwise.
46  */
ping_host(host_t * host,ping_t * ping)47 int ping_host(host_t *host, ping_t *ping) {
48 	int ping_result;
49 	int snmp_result;
50 
51 	/* snmp pinging has been selected at a minimum */
52 	ping_result = 0;
53 	snmp_result = 0;
54 
55 	/* icmp/tcp/udp ping test */
56 	if ((host->availability_method == AVAIL_SNMP_AND_PING) ||
57 		(host->availability_method == AVAIL_PING) ||
58 		(host->availability_method == AVAIL_SNMP_OR_PING)) {
59 
60 		if (host->ping_method == PING_ICMP) {
61 			if (set.icmp_avail == FALSE) {
62 				SPINE_LOG_DEBUG(("Host[%i] DEBUG Falling back to UDP Ping Due to SetUID Issues", host->id));
63 				host->ping_method = PING_UDP;
64 			}
65 		}
66 
67 		if (!strstr(host->hostname, "localhost")) {
68 			if (host->ping_method == PING_ICMP) {
69 				ping_result = ping_icmp(host, ping);
70 			}else if (host->ping_method == PING_UDP) {
71 				ping_result = ping_udp(host, ping);
72 			}else if (host->ping_method == PING_TCP) {
73 				ping_result = ping_tcp(host, ping);
74 			}
75 		}else{
76 			snprintf(ping->ping_status, 50, "0.000");
77 			snprintf(ping->ping_response, SMALL_BUFSIZE, "PING: Host does not require ping");
78 			ping_result = HOST_UP;
79 		}
80 	}
81 
82 	/* snmp test */
83 	if ((host->availability_method == AVAIL_SNMP) ||
84 		(host->availability_method == AVAIL_SNMP_GET_SYSDESC) ||
85 		(host->availability_method == AVAIL_SNMP_GET_NEXT) ||
86 		(host->availability_method == AVAIL_SNMP_AND_PING) ||
87 		((host->availability_method == AVAIL_SNMP_OR_PING) && (ping_result != HOST_UP))) {
88 		snmp_result = ping_snmp(host, ping);
89 	}
90 
91 	switch (host->availability_method) {
92 		case AVAIL_SNMP_AND_PING:
93 			if ((strlen(host->snmp_community) == 0) && (host->snmp_version < 3)) {
94 				if (ping_result == HOST_UP) {
95 					return HOST_UP;
96 				}else{
97 					return HOST_DOWN;
98 				}
99 			}
100 
101 			if ((snmp_result == HOST_UP) && (ping_result == HOST_UP)) {
102 				return HOST_UP;
103 			}else{
104 				return HOST_DOWN;
105 			}
106 		case AVAIL_SNMP_OR_PING:
107 			if ((strlen(host->snmp_community) == 0) && (host->snmp_version < 3)) {
108 				if (ping_result == HOST_UP) {
109 					return HOST_UP;
110 				}else{
111 					return HOST_DOWN;
112 				}
113 			}
114 
115 			if (snmp_result == HOST_UP) {
116 				return HOST_UP;
117 			}
118 
119 			if (ping_result == HOST_UP) {
120 				return HOST_UP;
121 			}else{
122 				return HOST_DOWN;
123 			}
124 		case AVAIL_SNMP:
125 		case AVAIL_SNMP_GET_NEXT:
126 		case AVAIL_SNMP_GET_SYSDESC:
127 			if (snmp_result == HOST_UP) {
128 				return HOST_UP;
129 			}else{
130 				return HOST_DOWN;
131 			}
132 		case AVAIL_PING:
133 			if (ping_result == HOST_UP) {
134 				return HOST_UP;
135 			}else{
136 				return HOST_DOWN;
137 			}
138 		case AVAIL_NONE:
139 			return HOST_UP;
140 		default:
141 			return HOST_DOWN;
142 	}
143 }
144 
145 /*! \fn int ping_snmp(host_t *host, ping_t *ping)
146  *  \brief ping a host using snmp sysUptime
147  *  \param host a pointer to the current host structure
148  *  \param ping a pointer to the current hosts ping structure
149  *
150  *  This function pings a host using snmp.  It polls sysUptime by default.
151  *  It will modify the ping structure to include the specifics of the ping results.
152  *
153  *  \return HOST_UP if the host is reachable, HOST_DOWN otherwise.
154  *
155  */
ping_snmp(host_t * host,ping_t * ping)156 int ping_snmp(host_t *host, ping_t *ping) {
157 	char *poll_result;
158 	char *oid;
159 	double begin_time, end_time, total_time;
160 	double one_thousand = 1000.00;
161 
162 	SPINE_LOG_DEBUG(("Host[%i] DEBUG: Entering SNMP Ping", host->id));
163 
164 	if (host->snmp_session) {
165 		if ((strlen(host->snmp_community) != 0) || (host->snmp_version == 3)) {
166 			/* by default, we look at sysUptime */
167 			if (host->availability_method == AVAIL_SNMP_GET_NEXT) {
168 				oid = strdup(".1.3");
169 			}else if (host->availability_method == AVAIL_SNMP_GET_SYSDESC) {
170 				oid = strdup(".1.3.6.1.2.1.1.1.0");
171 			}else {
172 				oid = strdup(".1.3.6.1.2.1.1.3.0");
173 			}
174 
175 			if (oid == NULL) die("ERROR: malloc(): strdup() oid ping.c failed");
176 
177 			/* record start time */
178 			begin_time = get_time_as_double();
179 
180 			if (host->availability_method == AVAIL_SNMP_GET_NEXT) {
181 				poll_result = snmp_getnext(host, oid);
182 			} else {
183 				poll_result = snmp_get(host, oid);
184 			}
185 
186 			/* record end time */
187 			end_time = get_time_as_double();
188 
189 			free(oid);
190 
191 			total_time = (end_time - begin_time) * one_thousand;
192 
193 			/* do positive test cases first */
194 			if (host->snmp_status == SNMPERR_UNKNOWN_OBJID) {
195 				snprintf(ping->snmp_response, SMALL_BUFSIZE, "Host responded to SNMP");
196 				snprintf(ping->snmp_status, 50, "%.5f", total_time);
197 				free(poll_result);
198 				return HOST_UP;
199 			}else if (host->snmp_status != SNMPERR_SUCCESS) {
200 				SPINE_LOG_MEDIUM(("Host[%i] SNMP Ping Error: %s", host->id, snmp_api_errstring(host->snmp_status)));
201 				snprintf(ping->snmp_response, SMALL_BUFSIZE, "Host did not respond to SNMP");
202 				free(poll_result);
203 				return HOST_DOWN;
204 			}else{
205 				snprintf(ping->snmp_response, SMALL_BUFSIZE, "Host responded to SNMP");
206 				snprintf(ping->snmp_status, 50, "%.5f", total_time);
207 				free(poll_result);
208 				return HOST_UP;
209 			}
210 		}else{
211 			snprintf(ping->snmp_status, 50, "0.00");
212 			snprintf(ping->snmp_response, SMALL_BUFSIZE, "Host does not require SNMP");
213 			return HOST_UP;
214 		}
215 	}else{
216 		snprintf(ping->snmp_status, 50, "0.00");
217 		snprintf(ping->snmp_response, SMALL_BUFSIZE, "Invalid SNMP Session");
218 		return HOST_DOWN;
219 	}
220 }
221 
222 /*! \fn int ping_icmp(host_t *host, ping_t *ping)
223  *  \brief ping a host using an ICMP packet
224  *  \param host a pointer to the current host structure
225  *  \param ping a pointer to the current hosts ping structure
226  *
227  *  This function pings a host using ICMP.  The ICMP packet contains a marker
228  *  to the "Cacti" application so that firewall's can be configured to allow.
229  *  It will modify the ping structure to include the specifics of the ping results.
230  *
231  *  \return HOST_UP if the host is reachable, HOST_DOWN otherwise.
232  *
233  */
ping_icmp(host_t * host,ping_t * ping)234 int ping_icmp(host_t *host, ping_t *ping) {
235 	int    icmp_socket;
236 
237 	double begin_time, end_time, total_time;
238 	double host_timeout;
239 	double one_thousand = 1000.00;
240 	struct timeval timeout;
241 
242 	struct sockaddr_in recvname;
243 	struct sockaddr_in fromname;
244 	char   socket_reply[BUFSIZE];
245 	int    retry_count;
246 	char   *cacti_msg = "cacti-monitoring-system\0";
247 	int    packet_len;
248 	socklen_t    fromlen;
249 	ssize_t    return_code;
250 	fd_set socket_fds;
251 
252 	static   unsigned int seq = 0;
253 	struct   icmp  *icmp;
254 	struct   ip    *ip;
255 	struct   icmp  *pkt;
256 	unsigned char  *packet;
257 	char     *new_hostname;
258 
259 	SPINE_LOG_DEBUG(("Host[%i] DEBUG: Entering ICMP Ping", host->id));
260 
261 	/* remove "tcp:" from hostname */
262 	new_hostname = remove_tcp_udp_from_hostname(host->hostname);
263 
264 	/* get ICMP socket */
265 	retry_count = 0;
266 	while ( TRUE ) {
267 		#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
268 		if (hasCaps() != TRUE) {
269 			thread_mutex_lock(LOCK_SETEUID);
270 			seteuid(0);
271 		}
272 		#endif
273 
274 		if ((icmp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1) {
275 			usleep(500000);
276 			retry_count++;
277 
278 			if (retry_count > 4) {
279 				snprintf(ping->ping_response, SMALL_BUFSIZE, "ICMP: Ping unable to create ICMP Socket");
280 				snprintf(ping->ping_status, 50, "down");
281 				free(new_hostname);
282 				#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
283 				if (hasCaps() != TRUE) {
284 					seteuid(getuid());
285 					thread_mutex_unlock(LOCK_SETEUID);
286 				}
287 				#endif
288 
289 				return HOST_DOWN;
290 
291 				break;
292 			}
293 		}else{
294 			break;
295 		}
296 	}
297 	#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
298 	if (hasCaps() != TRUE) {
299 		seteuid(getuid());
300 		thread_mutex_unlock(LOCK_SETEUID);
301 	}
302 	#endif
303 
304 	/* convert the host timeout to a double precision number in seconds */
305 	host_timeout = host->ping_timeout;
306 
307 	/* allocate the packet in memory */
308 	packet_len = ICMP_HDR_SIZE + strlen(cacti_msg);
309 
310 	if (!(packet = malloc(packet_len))) {
311 		die("ERROR: Fatal malloc error: ping.c ping_icmp!");
312 	}
313 	memset(packet, 0, packet_len);
314 
315 	/* set the memory of the ping address */
316 	memset(&fromname, 0, sizeof(struct sockaddr_in));
317 	memset(&recvname, 0, sizeof(struct sockaddr_in));
318 
319 	icmp = (struct icmp*) packet;
320 
321 	icmp->icmp_type = ICMP_ECHO;
322 	icmp->icmp_code = 0;
323 	icmp->icmp_id   = getpid() & 0xFFFF;
324 
325 	/* lock set/get the sequence and unlock */
326 	thread_mutex_lock(LOCK_GHBN);
327 	icmp->icmp_seq = seq++;
328 	thread_mutex_unlock(LOCK_GHBN);
329 
330 	icmp->icmp_cksum = 0;
331 	memcpy(packet+ICMP_HDR_SIZE, cacti_msg, strlen(cacti_msg));
332 	icmp->icmp_cksum = get_checksum(packet, packet_len);
333 
334 	/* hostname must be nonblank */
335 	if ((strlen(host->hostname) != 0) && (icmp_socket != -1)) {
336 		/* initialize variables */
337 		snprintf(ping->ping_status, 50, "down");
338 		snprintf(ping->ping_response, SMALL_BUFSIZE, "default");
339 
340 		/* get address of hostname */
341 		if (init_sockaddr(&fromname, new_hostname, 7)) {
342 			retry_count = 0;
343 			total_time  = 0;
344 			begin_time  = 0;
345 
346 			/* initialize file descriptor to review for input/output */
347 			FD_ZERO(&socket_fds);
348 			FD_SET(icmp_socket,&socket_fds);
349 
350 			while (1) {
351 				if (retry_count > host->ping_retries) {
352 					snprintf(ping->ping_response, SMALL_BUFSIZE, "ICMP: Ping timed out");
353 					snprintf(ping->ping_status, 50, "down");
354 					free(new_hostname);
355 					free(packet);
356 					close(icmp_socket);
357 					return HOST_DOWN;
358 				}
359 
360 				/* record start time */
361 				if (total_time == 0) {
362 					/* establish timeout value */
363 					timeout.tv_sec  = 0;
364 					timeout.tv_usec = host->ping_timeout * 1000;
365 
366 					/* set the socket send and receive timeout */
367 					setsockopt(icmp_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
368 					setsockopt(icmp_socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
369 
370 					begin_time = get_time_as_double();
371 				}else{
372 					/* decrement the timeout value by the total time */
373 					timeout.tv_usec = (host->ping_timeout - total_time) * 1000;
374 				}
375 
376 				/* send packet to destination */
377 				return_code = sendto(icmp_socket, packet, packet_len, 0, (struct sockaddr *) &fromname, sizeof(fromname));
378 
379 				fromlen = sizeof(fromname);
380 
381 				/* wait for a response on the socket */
382 				keep_listening:
383 				return_code = select(FD_SETSIZE, &socket_fds, NULL, NULL, &timeout);
384 
385 				/* record end time */
386 				end_time = get_time_as_double();
387 
388 				/* caculate total time */
389 				total_time = (end_time - begin_time) * one_thousand;
390 
391 				/* check to see which socket talked */
392 				if (total_time < host_timeout) {
393 					#if !(defined(__CYGWIN__))
394 					return_code = recvfrom(icmp_socket, socket_reply, BUFSIZE, MSG_WAITALL, (struct sockaddr *) &recvname, &fromlen);
395 					#else
396 					return_code = recvfrom(icmp_socket, socket_reply, BUFSIZE, MSG_PEEK, (struct sockaddr *) &recvname, &fromlen);
397 					#endif
398 
399 					if (return_code < 0) {
400 						if (errno == EINTR) {
401 							SPINE_LOG_DEBUG(("Host[%i] DEBUG: Received EINTR", host->id));
402 							/* call was interrupted by some system event */
403 							goto keep_listening;
404 						}
405 					}else{
406 						ip  = (struct ip *) socket_reply;
407 						pkt = (struct icmp *)  (socket_reply + (ip->ip_hl << 2));
408 
409 						if (fromname.sin_addr.s_addr == recvname.sin_addr.s_addr) {
410 							if ((pkt->icmp_type == ICMP_ECHOREPLY)) {
411 								SPINE_LOG_DEBUG(("Host[%i] DEBUG: ICMP Host Alive, Try Count:%i, Time:%.4f ms", host->id, retry_count+1, (total_time)));
412 								snprintf(ping->ping_response, SMALL_BUFSIZE, "ICMP: Host is Alive");
413 								snprintf(ping->ping_status, 50, "%.5f", total_time);
414 								free(new_hostname);
415 								free(packet);
416 								#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
417 								if (hasCaps() != TRUE) {
418 									thread_mutex_lock(LOCK_SETEUID);
419 									seteuid(0);
420 								}
421 								#endif
422 								close(icmp_socket);
423 								#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
424 								if (hasCaps() != TRUE) {
425 									seteuid(getuid());
426 									thread_mutex_unlock(LOCK_SETEUID);
427 								}
428 								#endif
429 
430 								return HOST_UP;
431 							}else{
432 								/* received a response other than an echo reply */
433 								if (total_time > host_timeout) {
434 									retry_count++;
435 									total_time = 0;
436 								}
437 
438 								continue;
439 							}
440 						}else{
441 							/* another host responded */
442 							goto keep_listening;
443 						}
444 					}
445 				}else{
446 					SPINE_LOG_DEBUG(("Host[%i] DEBUG: Exceeded Host Timeout, Retrying", host->id));
447 				}
448 
449 				total_time = 0;
450 				retry_count++;
451 				#ifndef SOLAR_THREAD
452 				usleep(1000);
453 				#endif
454 			}
455 		}else{
456 			snprintf(ping->ping_response, SMALL_BUFSIZE, "ICMP: Destination hostname invalid");
457 			snprintf(ping->ping_status, 50, "down");
458 			free(new_hostname);
459 			free(packet);
460 			#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
461 			if (hasCaps() != TRUE) {
462 				thread_mutex_lock(LOCK_SETEUID);
463 				seteuid(0);
464 			}
465 			#endif
466 			close(icmp_socket);
467 			#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
468 			if (hasCaps() != TRUE) {
469 				seteuid(getuid());
470 				thread_mutex_unlock(LOCK_SETEUID);
471 			}
472 			#endif
473 			return HOST_DOWN;
474 		}
475 	}else{
476 		snprintf(ping->ping_response, SMALL_BUFSIZE, "ICMP: Destination address not specified");
477 		snprintf(ping->ping_status, 50, "down");
478 		free(new_hostname);
479 		free(packet);
480 		if (icmp_socket != -1) {
481 			#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
482 			if (hasCaps() != TRUE) {
483 				thread_mutex_lock(LOCK_SETEUID);
484 				seteuid(0);
485 			}
486 			#endif
487 			close(icmp_socket);
488 			#if !(defined(__CYGWIN__) && !defined(SOLAR_PRIV))
489 			if (hasCaps() != TRUE) {
490 				seteuid(getuid());
491 				thread_mutex_unlock(LOCK_SETEUID);
492 			}
493 			#endif
494 		}
495 		return HOST_DOWN;
496 	}
497 }
498 
499 /*! \fn int ping_udp(host_t *host, ping_t *ping)
500  *  \brief ping a host using an UDP datagram
501  *  \param host a pointer to the current host structure
502  *  \param ping a pointer to the current hosts ping structure
503  *
504  *  This function pings a host using UDP.  The UDP datagram contains a marker
505  *  to the "Cacti" application so that firewall's can be configured to allow.
506  *  It will modify the ping structure to include the specifics of the ping results.
507  *
508  *  \return HOST_UP if the host is reachable, HOST_DOWN otherwise.
509  *
510  */
ping_udp(host_t * host,ping_t * ping)511 int ping_udp(host_t *host, ping_t *ping) {
512 	double begin_time, end_time, total_time;
513 	double host_timeout;
514 	double one_thousand = 1000.00;
515 	struct timeval timeout;
516 	int    udp_socket;
517 	struct sockaddr_in servername;
518 	char   socket_reply[BUFSIZE];
519 	int    retry_count;
520 	char   request[BUFSIZE];
521 	int    request_len;
522 	int    return_code;
523 	fd_set socket_fds;
524 	char   *new_hostname;
525 
526 	SPINE_LOG_DEBUG(("Host[%i] DEBUG: Entering UDP Ping", host->id));
527 
528 	/* set total time */
529 	total_time = 0;
530 
531 	/* remove "udp:" from hostname */
532 	new_hostname = remove_tcp_udp_from_hostname(host->hostname);
533 
534 	/* convert the host timeout to a double precision number in seconds */
535 	host_timeout = host->ping_timeout;
536 
537 	/* establish timeout value */
538 	if (host->ping_timeout >= 1000) {
539 		timeout.tv_sec  = rint(floor(host_timeout / 1000));
540 		timeout.tv_usec = (timeout.tv_sec * 1000000) - (host->ping_timeout * 1000);
541 	}else{
542 		timeout.tv_sec  = 0;
543 		timeout.tv_usec = (host->ping_timeout * 1000);
544 	}
545 
546 	/* initilize the socket */
547 	udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
548 
549 	/* hostname must be nonblank */
550 	if ((strlen(host->hostname) != 0) && (udp_socket != -1)) {
551 		/* initialize variables */
552 		snprintf(ping->ping_status, 50, "down");
553 		snprintf(ping->ping_response, SMALL_BUFSIZE, "default");
554 
555 		/* set the socket timeout */
556 		setsockopt(udp_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
557 		setsockopt(udp_socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
558 
559 		/* get address of hostname */
560 		if (init_sockaddr(&servername, new_hostname, host->ping_port)) {
561 			if (connect(udp_socket, (struct sockaddr *) &servername, sizeof(servername)) < 0) {
562 				snprintf(ping->ping_status, 50, "down");
563 				snprintf(ping->ping_response, SMALL_BUFSIZE, "UDP: Cannot connect to host");
564 				free(new_hostname);
565 				close(udp_socket);
566 				return HOST_DOWN;
567 			}
568 
569 			/* format packet */
570 			snprintf(request, BUFSIZE, "cacti-monitoring-system"); /* the actual test data */
571 			request_len = strlen(request);
572 
573 			retry_count = 0;
574 
575 			/* initialize file descriptor to review for input/output */
576 			FD_ZERO(&socket_fds);
577 			FD_SET(udp_socket,&socket_fds);
578 
579 			while (1) {
580 				if (retry_count > host->ping_retries) {
581 					snprintf(ping->ping_response, SMALL_BUFSIZE, "UDP: Ping timed out");
582 					snprintf(ping->ping_status, 50, "down");
583 					free(new_hostname);
584 					close(udp_socket);
585 					return HOST_DOWN;
586 				}
587 
588 				/* record start time */
589 				begin_time = get_time_as_double();
590 
591 				/* establish timeout value */
592 				if (host->ping_timeout >= 1000) {
593 					timeout.tv_sec  = rint(floor(host_timeout / 1000));
594 					timeout.tv_usec = (timeout.tv_sec * 1000000) - (host->ping_timeout * 1000);
595 				}else{
596 					timeout.tv_sec  = 0;
597 					timeout.tv_usec = (host->ping_timeout * 1000);
598 				}
599 
600 				/* send packet to destination */
601 				send(udp_socket, request, request_len, 0);
602 
603 				/* wait for a response on the socket */
604 				wait_more:
605 				return_code = select(FD_SETSIZE, &socket_fds, NULL, NULL, &timeout);
606 
607 				/* record end time */
608 				end_time = get_time_as_double();
609 
610 				/* caculate total time */
611 				total_time = (end_time - begin_time) * one_thousand;
612 
613 				/* check to see which socket talked */
614 				if (return_code > 0) {
615 					if (FD_ISSET(udp_socket, &socket_fds)) {
616 						return_code = read(udp_socket, socket_reply, BUFSIZE);
617 
618 						if ((return_code == -1) && ((errno == ECONNRESET) || (errno == ECONNREFUSED))) {
619 							SPINE_LOG_DEBUG(("Host[%i] DEBUG: UDP Host Alive, Try Count:%i, Time:%.4f ms", host->id, retry_count+1, (total_time)));
620 							snprintf(ping->ping_response, SMALL_BUFSIZE, "UDP: Host is Alive");
621 							snprintf(ping->ping_status, 50, "%.5f", total_time);
622 							free(new_hostname);
623 							close(udp_socket);
624 							return HOST_UP;
625 						}
626 					}
627 				}else if (return_code == -1) {
628 					if (errno == EINTR) {
629 						/* interrupted, try again */
630 						goto wait_more;
631 					}else{
632 						snprintf(ping->ping_response, SMALL_BUFSIZE, "UDP: Host is Down");
633 						snprintf(ping->ping_status, 50, "%.5f", total_time);
634 						free(new_hostname);
635 						close(udp_socket);
636 						return HOST_DOWN;
637 					}
638 				}else{
639 					/* timeout */
640 				}
641 
642 				SPINE_LOG_DEBUG(("Host[%i] DEBUG: UDP Timeout, Try Count:%i, Time:%.4f ms", host->id, retry_count+1, (total_time)));
643 
644 				retry_count++;
645 				#ifndef SOLAR_THREAD
646 				usleep(1000);
647 				#endif
648 			}
649 		}else{
650 			snprintf(ping->ping_response, SMALL_BUFSIZE, "UDP: Destination hostname invalid");
651 			snprintf(ping->ping_status, 50, "down");
652 			free(new_hostname);
653 			close(udp_socket);
654 			return HOST_DOWN;
655 		}
656 	}else{
657 		snprintf(ping->ping_response, SMALL_BUFSIZE, "UDP: Destination address invalid or unable to create socket");
658 		snprintf(ping->ping_status, 50, "down");
659 		free(new_hostname);
660 		if (udp_socket != -1) close(udp_socket);
661 		return HOST_DOWN;
662 	}
663 }
664 
665 
666 /*! \fn int ping_tcp(host_t *host, ping_t *ping)
667  *  \brief ping a host using an TCP syn
668  *  \param host a pointer to the current host structure
669  *  \param ping a pointer to the current hosts ping structure
670  *
671  *  This function pings a host using TCP.  The TCP socket contains a marker
672  *  to the "Cacti" application so that firewall's can be configured to allow.
673  *  It will modify the ping structure to include the specifics of the ping results.
674  *
675  *  \return HOST_UP if the host is reachable, HOST_DOWN otherwise.
676  *
677  */
ping_tcp(host_t * host,ping_t * ping)678 int ping_tcp(host_t *host, ping_t *ping) {
679 	double begin_time, end_time, total_time;
680 	double host_timeout;
681 	double one_thousand = 1000.00;
682 	struct timeval timeout;
683 	int    tcp_socket;
684 	struct sockaddr_in servername;
685 	int    retry_count;
686 	int    return_code;
687 	char   *new_hostname;
688 
689 	SPINE_LOG_DEBUG(("Host[%i] DEBUG: Entering TCP Ping", host->id));
690 
691 	/* remove "tcp:" from hostname */
692 	new_hostname = remove_tcp_udp_from_hostname(host->hostname);
693 
694 	/* convert the host timeout to a double precision number in seconds */
695 	host_timeout = host->ping_timeout;
696 
697 	/* establish timeout value */
698 	if (host->ping_timeout >= 1000) {
699 		timeout.tv_sec  = rint(floor(host_timeout / 1000));
700 		timeout.tv_usec = (timeout.tv_sec * 1000000) - (host->ping_timeout * 1000);
701 	}else{
702 		timeout.tv_sec  = 0;
703 		timeout.tv_usec = (host->ping_timeout * 1000);
704 	}
705 
706 	/* initilize the socket */
707 	tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
708 
709 	/* hostname must be nonblank */
710 	if ((strlen(host->hostname) != 0) && (tcp_socket != -1)) {
711 		/* initialize variables */
712 		snprintf(ping->ping_status, 50, "down");
713 		snprintf(ping->ping_response, SMALL_BUFSIZE, "default");
714 
715 		/* set the socket timeout */
716 		setsockopt(tcp_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
717 		setsockopt(tcp_socket, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
718 
719 		/* get address of hostname */
720 		if (init_sockaddr(&servername, new_hostname, host->ping_port)) {
721 			/* first attempt a connect */
722 			retry_count = 0;
723 
724 			while (1) {
725 				/* record start time */
726 				begin_time  = get_time_as_double();
727 
728 				/* make the connection */
729 				return_code = connect(tcp_socket, (struct sockaddr *) &servername, sizeof(servername));
730 
731 				/* record end time */
732 				end_time = get_time_as_double();
733 
734 				/* caculate total time */
735 				total_time = (end_time - begin_time) * one_thousand;
736 
737 				if (((return_code == -1) && (errno == ECONNREFUSED)) ||
738 					(return_code == 0)) {
739 					SPINE_LOG_DEBUG(("Host[%i] DEBUG: TCP Host Alive, Try Count:%i, Time:%.4f ms", host->id, retry_count+1, (total_time)));
740 					snprintf(ping->ping_response, SMALL_BUFSIZE, "TCP: Host is Alive");
741 					snprintf(ping->ping_status, 50, "%.5f", total_time);
742 					free(new_hostname);
743 					close(tcp_socket);
744 					return HOST_UP;
745 				}else{
746 					#if defined(__CYGWIN__)
747 					snprintf(ping->ping_status, 50, "down");
748 					snprintf(ping->ping_response, SMALL_BUFSIZE, "TCP: Cannot connect to host");
749 					free(new_hostname);
750 					close(tcp_socket);
751 					return HOST_DOWN;
752 					#else
753 					if (retry_count > host->ping_retries) {
754 						snprintf(ping->ping_status, 50, "down");
755 						snprintf(ping->ping_response, SMALL_BUFSIZE, "TCP: Cannot connect to host");
756 						free(new_hostname);
757 						close(tcp_socket);
758 						return HOST_DOWN;
759 					}else{
760 						retry_count++;
761 					}
762 					#endif
763 				}
764 			}
765 		}else{
766 			snprintf(ping->ping_response, SMALL_BUFSIZE, "TCP: Destination hostname invalid");
767 			snprintf(ping->ping_status, 50, "down");
768 			free(new_hostname);
769 			close(tcp_socket);
770 			return HOST_DOWN;
771 		}
772 	}else{
773 		snprintf(ping->ping_response, SMALL_BUFSIZE, "TCP: Destination address invalid or unable to create socket");
774 		snprintf(ping->ping_status, 50, "down");
775 		free(new_hostname);
776 		if (tcp_socket != -1) close(tcp_socket);
777 		return HOST_DOWN;
778 	}
779 }
780 
781 /*! \fn int init_sockaddr(struct sockaddr_in *name, const char *hostname, unsigned short int port)
782  *  \brief converts a hostname to an internet address
783  *
784  *  \return TRUE if successful, FALSE otherwise.
785  *
786  */
init_sockaddr(struct sockaddr_in * name,const char * hostname,unsigned short int port)787 int init_sockaddr(struct sockaddr_in *name, const char *hostname, unsigned short int port) {
788 	struct hostent *hostinfo;
789 	int retry_count;
790 	#if !defined(H_ERRNO_DECLARED) && !defined(_AIX)
791 	extern int h_errno;
792 	#endif
793 
794 	name->sin_family = AF_INET;
795 	name->sin_port   = htons (port);
796 
797 	retry_count = 0;
798 
799 	#ifdef HAVE_THREADSAFE_GETHOSTBYNAME
800 	retry:
801 	hostinfo = gethostbyname(hostname);
802 
803 	if (!hostinfo) {
804 		if (h_errno == TRY_AGAIN && retry_count < 3) {
805 			retry_count++;
806 			usleep(50000);
807 			goto retry;
808 		}else{
809 			return NULL;
810 		}
811 	}else{
812 		name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
813 	}
814 
815 	#else
816 	#ifdef HAVE_GETHOSTBYNAME_R_GLIBC
817 	struct hostent result_buf;
818 	size_t len = 1024;
819 	char   *buf;
820 	int    herr;
821 	int    rv;
822 
823 	buf = malloc(len*sizeof(char));
824 	memset(buf, 0, sizeof(buf));
825 
826 	while (1) {
827 		rv = gethostbyname_r(hostname, &result_buf, buf, len,
828 		&hostinfo, &herr);
829 
830 		if (!hostinfo) {
831 			if (rv == ERANGE) {
832 				len *= 2;
833 				buf = realloc(buf, len*sizeof(char));
834 
835 				continue;
836 			}else if (herr == TRY_AGAIN && retry_count < 3) {
837 				retry_count++;
838 				usleep(50000);
839 				continue;
840 			}else{
841 				free(buf);
842 				return FALSE;
843 			}
844 		}else{
845 			break;
846 		}
847 	}
848 
849 	name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
850 
851 	free(buf);
852 	#else
853 	#ifdef HAVE_GETHOSTBYNAME_R_SOLARIS
854 	size_t  len = 8192;
855 	char   *buf = NULL;
856 	struct hostent result;
857 
858 	buf = malloc(len*sizeof(char));
859 	memset(buf, 0, sizeof(buf));
860 
861 	while (1) {
862 		hostinfo = gethostbyname_r(hostname, &result, buf, len, &h_errno);
863 		if (!hostinfo) {
864 			if (errno == ERANGE) {
865 				len += 1024;
866 				buf = realloc(buf, len*sizeof(char));
867 				memset(buf, 0, sizeof(buf));
868 
869 				continue;
870 			}else if (h_errno == TRY_AGAIN && retry_count < 3) {
871 				retry_count++;
872 				usleep(50000);
873 				continue;
874 			}else{
875 				free(buf);
876 				return NULL;
877 			}
878 		}else{
879 			break;
880 		}
881 	}
882 
883 	name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
884 
885 	free(buf);
886 	#else
887 	#ifdef HAVE_GETHOSTBYNAME_R_HPUX
888 	struct hostent hostent;
889 	struct hostent_data buf;
890 	int rv;
891 
892 	rv = gethostbyname_r(hostname, &hostent, &buf);
893 	if (!rv) {
894 		name->sin_addr = *(struct in_addr *) hostent->h_addr;
895 	}
896 
897 	#else
898 	retry:
899 	thread_mutex_lock(LOCK_GHBN);
900 	hostinfo = gethostbyname(hostname);
901 	if (!hostinfo) {
902 		thread_mutex_unlock(LOCK_GHBN);
903 		if (h_errno == TRY_AGAIN && retry_count < 3) {
904 			retry_count++;
905 			usleep(50000);
906 			goto retry;
907 		}else{
908 			hostinfo = NULL;
909 		}
910 	}else{
911 		name->sin_addr = *(struct in_addr *) hostinfo->h_addr;
912 		thread_mutex_unlock(LOCK_GHBN);
913 	}
914 	#endif
915 	#endif
916 	#endif
917 	#endif
918 
919 	if (hostinfo == NULL) {
920 		SPINE_LOG(("WARNING: Unknown host %s", hostname));
921 		return FALSE;
922 	}else{
923 		return TRUE;
924 	}
925 }
926 
927 /*! \fn char *remove_tcp_udp_from_hostname(char *hostname)
928  *  \brief removes 'TCP:' or 'UDP:' from a hostname required to ping
929  *
930  *  \return char hostname a trimmed hostname
931  *
932  */
remove_tcp_udp_from_hostname(char * hostname)933 char *remove_tcp_udp_from_hostname(char *hostname) {
934 	char *cleaned_hostname;
935 
936 	if (!(cleaned_hostname = (char *) malloc(strlen(hostname)+1))) {
937 		die("ERROR: Fatal malloc error: ping.c remove_tcp_udp_from_hostname");
938 	}
939 
940 	if (!strncasecmp(hostname, "TCP:", 4) ||
941 		!strncasecmp(hostname, "UDP:", 4)) {
942 		memcpy(cleaned_hostname, hostname+4, strlen(hostname)-4);
943 		cleaned_hostname[strlen(hostname)-4] = '\0';
944 	}else{
945 		strcpy(cleaned_hostname, hostname);
946 	}
947 
948 	return(cleaned_hostname);
949 }
950 
951 /*! \fn unsigned short int get_checksum(void* buf, int len)
952  *  \brief calculates a 16bit checksum of a packet buffer
953  *  \param buf the input buffer to calculate the checksum of
954  *  \param len the size of the input buffer
955  *
956  *  \return 16bit checksum of an input buffer of size len.
957  *
958  */
get_checksum(void * buf,int len)959 unsigned short int get_checksum(void* buf, int len) {
960 	int      nleft = len;
961 	int32_t  sum   = 0;
962 	unsigned short int answer;
963 	unsigned short int* w = (unsigned short int*)buf;
964 	unsigned short int odd_byte = 0;
965 
966 	while (nleft > 1) {
967 		sum += *w++;
968 		nleft -= 2;
969 	}
970 
971 	if (nleft == 1) {
972    		*(unsigned char*)(&odd_byte) = *(unsigned char*)w;
973    		sum += odd_byte;
974 	}
975 
976 	sum    = (sum >> 16) + (sum & 0xffff);
977 	sum   += (sum >> 16);
978 	answer = ~sum;				/* truncate to 16 bits */
979 
980 	return answer;
981 }
982 
983 /*! \fn void update_host_status(int status, host_t *host, ping_t *ping, int availability_method)
984  *  \brief update the host table in Cacti with the result of the ping of the host.
985  *  \param status the current poll status of the host, either HOST_UP, or HOST_DOWN
986  *  \param host a pointer to the current host structure
987  *  \param ping a pointer to the current hosts ping structure
988  *  \param availability_method the method that was used to poll the host
989  *
990  *  This function will determine if the host is UP, DOWN, or RECOVERING based upon
991  *  the ping result and it's current status.  It will update the Cacti database
992  *  with the calculated status.
993  *
994  */
update_host_status(int status,host_t * host,ping_t * ping,int availability_method)995 void update_host_status(int status, host_t *host, ping_t *ping, int availability_method) {
996 	int    issue_log_message = FALSE;
997 	double ping_time;
998  	double hundred_percent = 100.00;
999 	char   current_date[40];
1000 
1001 	time_t nowbin;
1002 	struct tm now_time;
1003 	struct tm *now_ptr;
1004 
1005 	/* get time for poller_output table */
1006 	if (time(&nowbin) == (time_t) - 1) {
1007 		die("ERROR: Could not get time of day from time()");
1008 	}
1009 	localtime_r(&nowbin,&now_time);
1010 	now_ptr = &now_time;
1011 
1012 	strftime(current_date, 40, "%Y-%m-%d %H:%M", now_ptr);
1013 
1014 	/* host is down */
1015 	if (status == HOST_DOWN) {
1016 		/* update total polls, failed polls and availability */
1017 		host->failed_polls = host->failed_polls + 1;
1018 		host->total_polls = host->total_polls + 1;
1019 		host->availability = hundred_percent * (host->total_polls - host->failed_polls) / host->total_polls;
1020 
1021 		/*determine the error message to display */
1022 		switch (availability_method) {
1023 		case AVAIL_SNMP_OR_PING:
1024 		case AVAIL_SNMP_AND_PING:
1025 			if ((strlen(host->snmp_community) == 0) && (host->snmp_version < 3)) {
1026 				snprintf(host->status_last_error, SMALL_BUFSIZE, "%s", ping->ping_response);
1027 			}else {
1028 				snprintf(host->status_last_error, SMALL_BUFSIZE,"%s, %s",ping->snmp_response,ping->ping_response);
1029 			}
1030 			break;
1031 		case AVAIL_SNMP:
1032 			if ((strlen(host->snmp_community) == 0) && (host->snmp_version < 3)) {
1033 				snprintf(host->status_last_error, SMALL_BUFSIZE, "%s", "Device does not require SNMP");
1034 			}else {
1035 				snprintf(host->status_last_error, SMALL_BUFSIZE, "%s", ping->snmp_response);
1036 			}
1037 			break;
1038 		default:
1039 			snprintf(host->status_last_error, SMALL_BUFSIZE, "%s", ping->ping_response);
1040 		}
1041 
1042 		/* determine if to send an alert and update remainder of statistics */
1043 		if (host->status == HOST_UP) {
1044 			/* increment the event failure count */
1045 			host->status_event_count++;
1046 
1047 			/* if it's time to issue an error message, indicate so */
1048 			if (host->status_event_count >= set.ping_failure_count) {
1049 				/* host is now down, flag it that way */
1050 				host->status = HOST_DOWN;
1051 
1052 				issue_log_message = TRUE;
1053 
1054 				/* update the failure date only if the failure count is 1 */
1055 				if (set.ping_failure_count == 1) {
1056 					snprintf(host->status_fail_date, 40, "%s", current_date);
1057 				}
1058 			/* host is down, but not ready to issue log message */
1059 			}else{
1060 				/* host down for the first time, set event date */
1061 				if (host->status_event_count == 1) {
1062 					snprintf(host->status_fail_date, 40, "%s", current_date);
1063 				}
1064 			}
1065 		/* host is recovering, put back in failed state */
1066 		}else if (host->status == HOST_RECOVERING) {
1067 			host->status_event_count = 1;
1068 			host->status = HOST_DOWN;
1069 
1070 		/* host was unknown and now is down */
1071 		}else if (host->status == HOST_UNKNOWN) {
1072 			host->status = HOST_DOWN;
1073 			host->status_event_count = 0;
1074 		}else{
1075 			host->status_event_count++;
1076 		}
1077 	/* host is up!! */
1078 	}else{
1079 		/* update total polls and availability */
1080 		host->total_polls = host->total_polls + 1;
1081 		host->availability = hundred_percent * (host->total_polls - host->failed_polls) / host->total_polls;
1082 
1083 		/* determine the ping statistic to set and do so */
1084 		if (availability_method == AVAIL_SNMP_AND_PING) {
1085 			if (strlen(host->snmp_community) == 0) {
1086 				ping_time = atof(ping->ping_status);
1087 			}else {
1088 				/* calculate the average of the two times */
1089 				ping_time = (atof(ping->snmp_status) + atof(ping->ping_status)) / 2;
1090 			}
1091 		}else if (availability_method == AVAIL_SNMP) {
1092 			if (strlen(host->snmp_community) == 0) {
1093 				ping_time = 0.000;
1094 			}else {
1095 				ping_time = atof(ping->snmp_status);
1096 			}
1097 		}else if (availability_method == AVAIL_NONE) {
1098 			ping_time = 0.000;
1099 		}else {
1100 			ping_time = atof(ping->ping_status);
1101 		}
1102 
1103 		/* update times as required */
1104 		host->cur_time = ping_time;
1105 
1106 		/* maximum time */
1107 		if (ping_time > host->max_time)
1108 			host->max_time = ping_time;
1109 
1110 		/* minimum time */
1111 		if (ping_time < host->min_time)
1112 			host->min_time = ping_time;
1113 
1114 		/* average time */
1115 		host->avg_time = (((host->total_polls-1-host->failed_polls)
1116 			* host->avg_time) + ping_time) / (host->total_polls-host->failed_polls);
1117 
1118 		/* the host was down, now it's recovering */
1119 		if ((host->status == HOST_DOWN) || (host->status == HOST_RECOVERING )) {
1120 			/* just up, change to recovering */
1121 			if (host->status == HOST_DOWN) {
1122 				host->status = HOST_RECOVERING;
1123 				host->status_event_count = 1;
1124 			}else{
1125 				host->status_event_count++;
1126 			}
1127 
1128 			/* if it's time to issue a recovery message, indicate so */
1129 			if (host->status_event_count >= set.ping_recovery_count) {
1130 				/* host is up, flag it that way */
1131 				host->status = HOST_UP;
1132 
1133 				issue_log_message = TRUE;
1134 
1135 				/* update the recovery date only if the recovery count is 1 */
1136 				if (set.ping_recovery_count == 1) {
1137 					snprintf(host->status_rec_date, 40, "%s", current_date);
1138 				}
1139 
1140 				/* reset the event counter */
1141 				host->status_event_count = 0;
1142 			/* host is recovering, but not ready to issue log message */
1143 			}else{
1144 				/* host recovering for the first time, set event date */
1145 				if (host->status_event_count == 1) {
1146 					snprintf(host->status_rec_date, 40, "%s", current_date);
1147 				}
1148 			}
1149 		}else{
1150 		/* host was unknown and now is up */
1151 			host->status = HOST_UP;
1152 			host->status_event_count = 0;
1153 		}
1154 	}
1155 	/* if the user wants a flood of information then flood them */
1156 	if (set.log_level >= POLLER_VERBOSITY_HIGH) {
1157 		if ((host->status == HOST_UP) || (host->status == HOST_RECOVERING)) {
1158 			/* log ping result if we are to use a ping for reachability testing */
1159 			if (availability_method == AVAIL_SNMP_AND_PING) {
1160 				SPINE_LOG_HIGH(("Host[%i] PING Result: %s", host->id, ping->ping_response));
1161 				SPINE_LOG_HIGH(("Host[%i] SNMP Result: %s", host->id, ping->snmp_response));
1162 			}else if (availability_method == AVAIL_SNMP_OR_PING) {
1163 				SPINE_LOG_HIGH(("Host[%i] PING Result: %s", host->id, ping->ping_response));
1164 				SPINE_LOG_HIGH(("Host[%i] SNMP Result: %s", host->id, ping->snmp_response));
1165 			}else if (availability_method == AVAIL_SNMP) {
1166 				if ((strlen(host->snmp_community) == 0) && (host->snmp_version < 3)) {
1167 					SPINE_LOG_HIGH(("Host[%i] SNMP Result: Device does not require SNMP", host->id));
1168 				}else{
1169 					SPINE_LOG_HIGH(("Host[%i] SNMP Result: %s", host->id, ping->snmp_response));
1170 				}
1171 			}else if (availability_method == AVAIL_NONE) {
1172 				SPINE_LOG_HIGH(("Host[%i] No Host Availability Method Selected", host->id));
1173 			}else{
1174 				SPINE_LOG_HIGH(("Host[%i] PING: Result %s", host->id, ping->ping_response));
1175 			}
1176 		}else{
1177 			if (availability_method == AVAIL_SNMP_AND_PING) {
1178 				SPINE_LOG_HIGH(("Host[%i] PING Result: %s", host->id, ping->ping_response));
1179 				SPINE_LOG_HIGH(("Host[%i] SNMP Result: %s", host->id, ping->snmp_response));
1180 			}else if (availability_method == AVAIL_SNMP) {
1181 				SPINE_LOG_HIGH(("Host[%i] SNMP Result: %s", host->id, ping->snmp_response));
1182 			}else if (availability_method == AVAIL_NONE) {
1183 				SPINE_LOG_HIGH(("Host[%i] No Host Availability Method Selected", host->id));
1184 			}else{
1185 				SPINE_LOG_HIGH(("Host[%i] PING Result: %s", host->id, ping->ping_response));
1186 			}
1187 		}
1188 	}
1189 
1190 	/* if there is supposed to be an event generated, do it */
1191 	if (issue_log_message) {
1192 		if (host->status == HOST_DOWN) {
1193 			SPINE_LOG(("Host[%i] Hostname[%s] ERROR: HOST EVENT: Host is DOWN Message: %s", host->id, host->hostname, host->status_last_error));
1194 		}else{
1195 			SPINE_LOG(("Host[%i] Hostname[%s] NOTICE: HOST EVENT: Host Returned from DOWN State", host->id, host->hostname));
1196 		}
1197 	}
1198 }
1199