1 /* (C) 2006-2011 by folkert@vanheusden.com GPLv2 applies */
2 #include <stdio.h>
3 #include <stdarg.h>
4 #include <string.h>
5 #include <errno.h>
6 #include <sys/time.h>
7 #include <sys/types.h>
8 #include <unistd.h>
9 #include <time.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <fcntl.h>
13 #include <stdlib.h>
14 #include <signal.h>
15 #include <pwd.h>
16 #include <sys/types.h>
17 #include <regex.h>
18 
19 #include "utils.h"
20 #include "pl.h"
21 extern "C" {
22 #include "error.h"
23 #include "ssl.h"
24 #include "log.h"
25 }
26 #include "anna.h"
27 
28 #define S_DISCONNECTED		1
29 #define S_CONNECTED		2
30 #define S_JOINING_CHANNEL	3
31 #define S_ON_CHANNEL		4
32 #define S_DISCONNECTING		127
33 
34 #define L_HOST			1
35 #define L_FILE			2
36 
37 #define ST_HARD			1
38 #define ST_SOFT			0
39 
40 int check_interval = 60;	/* check for new events every 1 minute */
41 int default_sleep = 2;		/* initial sleep time between connect failures */
42 int max_sleep_time = 300;
43 double sleep_multiply_factor = 2.0;
44 int minimum_time_for_successfull_login = 25; // one needs to be on-channel for at least 5 seconds to be considered a successfull login
45 int join_timeout = 5;	// it should take no longer then 5 seconds to join a channel, otherwhise: abort connection and retry
46 int max_n_join_tries = 2;	// try 2 times to get on a channel
47 int throttle_delay = 1; // don't send more than one message per 1 seconds
48 char *server = "localhost:6667";	/* default irc server */
49 char *channel = "#nagircbot";	/* default channel to connect to */
50 char *nick_prefix = "";   /* prefix text for all messages sent to channel */
51 char *keyword = NULL;	/* keyword for the channel to connect to */
52 char *nick = "nagircbot";
53 char *user = "nagircbot";
54 char *password = NULL;
55 int one_line = 1;
56 char *username = "Nagios IRC Bot " VERSION ", (C) www.vanheusden.com";	/* complete username */
57 int verbose = 255;		/* default is log everything */
58 char *statuslog = "/var/spool/nagios/status.dat";
59 int statuslog_version = 2;
60 int statuslog_location = L_FILE;
61 char use_colors = 0;
62 char topic_summary = 0;
63 char hard_only = 1;
64 int max_time_last_host_update = 300, max_time_oldest_host_update = 3600, max_time_last_host_check = 300, max_time_oldest_host_check = 3 * 86400, max_time_last_service_check = 20 * 60, max_time_oldest_service_check = 3 * 86400, max_time_oldest_next_service_check = 20 * 60;
65 int irc_server_keep_alive = 60;	/* send an irc-command at least every 300 seconds (currently 'TIME') */
66 int announce_global_status_interval = 0;
67 time_t last_announce_global_status_interval = 0;
68 char critical_only = 0;
69 regex_t *filters = NULL;
70 int n_filters = 0;
71 int use_ssl       = 0;
72 
73 
74 server_t server_conn;
75 
76 char *state_str[4] = { " OK ", "WARN", "CRIT", " ?? " };
77 char *color_str[4];
78 struct stats *prev = NULL;
79 int n_prev = 0;
80 char topic[4096] = { 0 };
81 time_t last_irc_io = 0;
82 char *pidfile = NULL;
83 
send_irc(server_t server_conn,char * format,...)84 int send_irc(server_t server_conn, char *format, ...)
85 {
86 	int rc;
87 	char buffer[4096];
88 	va_list ap;
89 
90 	va_start(ap, format);
91 	vsnprintf(buffer, sizeof(buffer) - 3, format, ap);
92 	va_end(ap);
93 
94 	if (verbose > 1) dolog("OUT: `%s'", buffer);
95 
96 	strcat(buffer, "\r\n"); /* yes this is safe: check the vsnprintf command */
97 
98 	rc = IRCWRITE(server_conn, buffer, strlen(buffer));
99 	if (rc == -1)
100 	{
101 		dolog("error sending: -1");
102 		return -1;
103 	}
104 	else if (rc == 0)
105 	{
106 		dolog("connection closed");
107 		return -1;
108 	}
109 
110 	time(&last_irc_io);
111 
112 	return 0;
113 }
114 
irc_set_nick(server_t server_conn,char * nick)115 int irc_set_nick(server_t server_conn, char *nick)
116 {
117 	if (send_irc(server_conn, "NICK %s", nick) == -1)
118 		return -1;
119 
120 	return 0;
121 }
122 
irc_login(server_t server_conn,char * user,char * username,char * server,char * password)123 int irc_login(server_t server_conn, char *user, char *username, char *server, char *password)
124 {
125 	if (password != NULL && send_irc(server_conn, "PASS %s", password) == -1)
126 		return -1;
127 
128 	if (irc_set_nick(server_conn, nick) == -1)
129 		return -1;
130 
131 	/* FIXME: localhost must be, ehr, local host */
132 	if (send_irc(server_conn, "USER %s \"localhost\" \"%s\" :%s", user, server, username) == -1)
133 		return -1;
134 
135 	return 0;
136 }
137 
irc_join_channel(server_t server_conn,char * channel,char * keyword)138 int irc_join_channel(server_t server_conn, char *channel, char *keyword)
139 {
140 	if (keyword != NULL)
141 		return send_irc(server_conn, "JOIN %s %s", channel, keyword);
142 
143 	return send_irc(server_conn, "JOIN %s", channel);
144 }
145 
irc_topic(server_t server_conn,char * channel,char * topic)146 int irc_topic(server_t server_conn, char *channel, char *topic)
147 {
148 	return send_irc(server_conn, "TOPIC %s :%s", channel, topic);
149 }
150 
check_ping(server_t server_conn,char * data)151 int check_ping(server_t server_conn, char *data)
152 {
153 	if (strncmp(data, "PING ", 5) == 0)
154 	{
155 		char *colon = strchr(data, ':');
156 		char *cr = strchr(data, '\r');
157 		char *lf = strchr(data, '\n');
158 
159 		if (cr>lf && cr != NULL)
160 			cr[1] = 0x00;
161 		else if (lf != NULL)
162 			lf[1] = 0x00;
163 		if (colon)
164 		{
165 			if (send_irc(server_conn, "PONG %s", colon + 1) == -1)
166 				return -1;
167 		}
168 		else if (verbose > 1)
169 		{
170 			dolog("Malformed PING request (%s)", data);
171 		}
172 	}
173 
174 	return 0;
175 }
176 
irc_privmsg(server_t server_conn,char * channel,char * msg)177 int irc_privmsg(server_t server_conn, char *channel, char *msg)
178 {
179 	static time_t last_msg = time(NULL);
180 	time_t diff = time(NULL) - last_msg;
181 	if (diff < throttle_delay) {
182 		sleep(throttle_delay - diff);
183 	}
184 	time(&last_msg);
185 
186 	return send_irc(server_conn, "PRIVMSG %s :%s", channel, msg);
187 }
188 
irc_time(server_t server_conn)189 int irc_time(server_t server_conn)
190 {
191 	return send_irc(server_conn, "TIME");
192 }
193 
load_statuslog(char * statuslog,char is_file,char is_20_format,int prev_n_elements_in_stats_array,struct stats ** pstats,int * n_stats)194 void load_statuslog(char *statuslog, char is_file, char is_20_format, int prev_n_elements_in_stats_array, struct stats **pstats, int *n_stats)
195 {
196 	char reload = 0;
197 
198 	for(;;)
199 	{
200 		int fd;
201 
202 		/* open file or connection to nagios status socket */
203 		if (is_file == 1)     /* file */
204 			fd = open(statuslog, O_RDONLY);
205 		else
206 			fd = connect_to(statuslog);
207 		if (fd == -1)
208 		{
209 			dolog("Failed to open nagios status-log at %s\n", statuslog);
210 			sleep(1);
211 			continue;
212 		}
213 
214 		/* file/socket open, load data */
215 		if (is_20_format)
216 			parse_2_0_statuslog(fd, pstats, n_stats);
217 		else
218 			parse_1_0_statuslog(fd, pstats, n_stats);
219 
220 		close(fd);
221 
222 		if (*n_stats == prev_n_elements_in_stats_array)
223 		{
224 			break;
225 		}
226 
227 		dolog("Number of elements in status-log is different (%d) from previous run (%d), retrying", *n_stats, prev_n_elements_in_stats_array);
228 		reload = 1;
229 		sleep(1);
230 
231 		/* remember the current number of elements for this load */
232 		prev_n_elements_in_stats_array = *n_stats;
233 		/* and free up - it'll be reloaded */
234 		free_stats_array(*pstats, *n_stats);
235 	}
236 
237 	if (reload)
238 	{
239 		dolog("Size of status.log is stable (%d elements), continuing", *n_stats);
240 	}
241 }
242 
emit_status(server_t server_conn,char * channel,struct stats * what)243 int emit_status(server_t server_conn, char *channel, struct stats *what)
244 {
245 	int loop;
246 	char buffer[4096];
247 	char *prefix = "", *suffix = "";
248 	struct tm *ptm = localtime(&what -> last_state_change);
249 
250 	if (use_colors)
251 	{
252 		prefix = color_str[what -> current_state];
253 		suffix = "\03";
254 	}
255 
256 	if (n_filters || one_line)
257 	{
258 		snprintf(buffer, sizeof(buffer), "%s%s%04d/%02d/%02d %02d:%02d %s %s %s %s%s",
259 				prefix,
260 				nick_prefix,
261 				ptm -> tm_year + 1900, ptm -> tm_mon + 1, ptm -> tm_mday, ptm -> tm_hour, ptm -> tm_min,
262 				state_str[what -> current_state],
263 				what -> host_name?what -> host_name:"",
264 				what -> service_description?what -> service_description:"",
265 				what -> plugin_output?what -> plugin_output:"",
266 				suffix);
267 	}
268 
269 	/* regexp matches? then do not emit */
270 	for(loop=0; loop<n_filters; loop++)
271 	{
272 		int rc = regexec(&filters[loop], buffer, 0, NULL, 0);
273 
274 		if (rc == 0)
275 		{
276 #ifdef _DEBUG
277 			printf("Line '%s' filtered by regexp\n", buffer);
278 #endif
279 			return 0; /* assume we're still on channel */
280 		}
281 		else if (rc != REG_NOMATCH)
282 		{
283 			char buffer[4096];
284 
285 			regerror(rc, &filters[loop], buffer, sizeof(buffer));
286 
287 			dolog("Executing of regexp failed: %s", buffer);
288 		}
289 	}
290 
291 	if (one_line)
292 	{
293 		if (irc_privmsg(server_conn, channel, buffer) == -1)
294 			return -1;
295 	}
296 	else
297 	{
298 		snprintf(buffer, sizeof(buffer), "%s%04d/%02d/%02d %02d:%02d",
299 				prefix,
300 				ptm -> tm_year + 1900, ptm -> tm_mon + 1, ptm -> tm_mday, ptm -> tm_hour, ptm -> tm_min);
301 
302 		if (irc_privmsg(server_conn, channel, buffer) == -1) return -1;
303 
304 		if (irc_privmsg(server_conn, channel, state_str[what -> current_state]) == -1) return -1;
305 
306 		if (irc_privmsg(server_conn, channel, (char *)(what -> host_name?what -> host_name:"")) == -1) return -1;
307 
308 		if (irc_privmsg(server_conn, channel, (char *)(what -> service_description?what -> service_description:"")) == -1) return -1;
309 
310 		snprintf(buffer, sizeof(buffer), "%s%s", (char *)(what -> plugin_output?what -> plugin_output:""), suffix);
311 		if (irc_privmsg(server_conn, channel, buffer) == -1) return -1;
312 	}
313 
314 	return 0;
315 }
316 
calc_statistics(struct stats * stats,int n_stats,char list_all_problems,char always_notify,char also_acknowledged,char hide_ok,char * buffer,int buffer_size)317 void calc_statistics(struct stats *stats, int n_stats, char list_all_problems, char always_notify, char also_acknowledged, char hide_ok, char *buffer, int buffer_size)
318 {
319 	int n_critical=0, n_warning=0, n_ok=0, n_up=0, n_down=0, n_unreachable=0, n_pending=0;
320 
321 	calc_stats_stats(stats, n_stats, list_all_problems, always_notify, also_acknowledged, hide_ok, &n_critical, &n_warning, &n_ok, &n_up, &n_down, &n_unreachable, &n_pending);
322 	snprintf(buffer, buffer_size, "Critical: %d, warning: %d, ok: %d, up: %d, down: %d, unreachable: %d, pending: %d",
323 			n_critical, n_warning, n_ok, n_up, n_down, n_unreachable, n_pending);
324 }
325 
check_nagios_status(server_t server_conn)326 int check_nagios_status(server_t server_conn)
327 {
328 	struct stats *cur = NULL;
329 	int n_cur = 0;
330 	int loop;
331 	int any_shown = 0;
332 #ifdef _DEBUG
333 	time_t now = time(NULL);
334 	printf("Checking @ %s", ctime(&now));
335 #endif
336 
337 	load_statuslog(statuslog, statuslog_location == L_FILE?1:0, statuslog_version == 2?1:0, n_prev, &cur, &n_cur);
338 
339 	if (topic_summary)
340 	{
341 		char buffer[4096];
342 
343 		calc_statistics(cur, n_cur, 0, 0, 0, 1, buffer, sizeof(buffer));
344 
345 		if (strcmp(topic, buffer) != 0)
346 		{
347 			strcpy(topic, buffer);
348 
349 			if (irc_topic(server_conn, channel, buffer) == -1)
350 				return -1;
351 		}
352 	}
353 
354 	if (announce_global_status_interval > 0)
355 	{
356 		if ((now - last_announce_global_status_interval) >= announce_global_status_interval)
357 		{
358 			char buffer[4096];
359 
360 			last_announce_global_status_interval = now;
361 
362 			calc_statistics(cur, n_cur, 0, 0, 0, 1, buffer, sizeof(buffer));
363 
364 			if (irc_privmsg(server_conn, channel, buffer) == -1)
365 				return -1;
366 		}
367 	}
368 
369 	for(loop=0; loop<n_cur; loop++)
370 	{
371 		char show = 0;
372 		int prev_index = -1;
373 
374 		if (prev)
375 			prev_index = find_index_by_host_and_service(prev, n_prev, cur[loop].host_name, cur[loop].service_description);
376 
377 		if (prev_index == -1)
378 		{
379 			if (prev == NULL)
380 			{
381 				if (should_i_show_entry(cur, n_cur, loop, 0, 0, 0, 1))
382 				{
383 					show = 1;
384 				}
385 			}
386 			else
387 			{
388 				show = 1;
389 			}
390 		}
391 		else
392 		{
393 			if (hard_only)
394 			{
395 				if (prev[prev_index].state_type == ST_HARD && cur[loop].state_type == ST_HARD)
396 				{
397 					if (prev[prev_index].current_state != cur[loop].current_state)
398 					{
399 						show = 1;
400 					}
401 				}
402 				else if (prev[prev_index].state_type == ST_SOFT && cur[loop].state_type == ST_HARD)
403 				{
404 					if (cur[loop].current_state != 0)
405 					{
406 						show = 1;
407 					}
408 				}
409 			}
410 			else
411 			{
412 				if (prev[prev_index].current_state != cur[loop].current_state)
413 				{
414 					show = 1;
415 				}
416 			}
417 		}
418 
419 		/* also 'unknown' is emitted when 'critical_only' is selected */
420 		if (show && cur[loop].current_state < 2 && critical_only)
421 		{
422 			show = 0;
423 		}
424 
425 		if (show)
426 		{
427 #ifdef _DEBUG
428 			if (prev != NULL && prev_index != -1)
429 			{
430 				printf("%s %s\n", cur[loop].host_name, cur[loop].service_description);
431 				printf("cur[current_state]: %d, prev[current_state]: %d\n", cur[loop].current_state, prev[prev_index].current_state);
432 				printf("cur[state_type]: %d, prev[state_type]: %d\n", cur[loop].state_type, prev[prev_index].state_type);
433 			}
434 #endif
435 
436 #ifdef _DEBUG
437 			if (any_shown == 0)
438 			{
439 				any_shown = 1;
440 				dolog("emitting results");
441 			}
442 #endif
443 			if (emit_status(server_conn, channel, &cur[loop]) == -1)
444 				return -1;
445 		}
446 	}
447 
448 	free_stats_array(prev, n_prev);
449 	prev = cur;
450 	n_prev = n_cur;
451 
452 	return 0;
453 }
454 
resend_nagios_status(server_t server_conn,char * to_who)455 int resend_nagios_status(server_t server_conn, char *to_who)
456 {
457 	int something_sent = 0;
458 
459 	if (n_prev == 0)
460 		return irc_privmsg(server_conn, to_who, "not available yet");
461 
462 	for(int loop=0; loop<n_prev; loop++)
463 	{
464 		if (should_i_show_entry(prev, n_prev, loop, 0, 0, 0, 1))
465 		{
466 			something_sent = 1;
467 
468 			if (emit_status(server_conn, to_who, &prev[loop]) == -1)
469 				return -1;
470 		}
471 	}
472 
473 	if (!something_sent)
474 		return irc_privmsg(server_conn, to_who, "all fine");
475 
476 	return 0;
477 }
478 
send_help(server_t server_conn,char * to_who)479 int send_help(server_t server_conn, char *to_who)
480 {
481 	if (irc_privmsg(server_conn, to_who, "'resend' lets the bot resend the current nagios status in a private message") == -1)
482 		return -1;
483 
484 	if (irc_privmsg(server_conn, to_who, "'statistics' sends you the statistics") == -1)
485 		return -1;
486 
487 	if (irc_privmsg(server_conn, to_who, "'check' verifies if Nagios is still checking hosts/services") == -1)
488 		return -1;
489 
490 	return 0;
491 }
492 
reload_statuslog(void)493 int reload_statuslog(void)
494 {
495 	int fd_sl;
496 	struct stats *cur = NULL;
497 	int n_cur = 0;
498 
499 	if (verbose > 1) dolog("reload_statuslog started");
500 
501 	if (statuslog_location == L_FILE)     /* file */
502 		fd_sl = open(statuslog, O_RDONLY);
503 	else
504 		fd_sl = connect_to(statuslog);
505 
506 	if (fd_sl != -1)
507 	{
508 		if (statuslog_version == 2)
509 			parse_2_0_statuslog(fd_sl, &cur, &n_cur);
510 		else
511 			parse_1_0_statuslog(fd_sl, &cur, &n_cur);
512 
513 		close(fd_sl);
514 
515 		free_stats_array(prev, n_prev);
516 		prev = cur;
517 		n_prev = n_cur;
518 
519 		return 0;
520 	}
521 
522 	dolog("reload failed: %s", strerror(errno));
523 
524 	return -1;
525 }
526 
check_nagios()527 char * check_nagios()
528 {
529 	char *message = NULL;
530 
531 	(void)check_max_age_last_check(prev, n_prev, max_time_last_host_update, max_time_oldest_host_update, max_time_last_host_check, max_time_oldest_host_check, max_time_last_service_check, max_time_oldest_service_check, max_time_oldest_next_service_check, &message);
532 
533 	return message;
534 }
535 
do_nagircbot(server_t server_conn,char * channel)536 int do_nagircbot(server_t server_conn, char *channel)
537 {
538 	time_t last_check = 0;
539 
540 	for(;;)
541 	{
542 		fd_set rfds;
543 		struct timeval tv;
544 		time_t now = time(NULL);
545 
546 		if ((now - last_check) >= check_interval)
547 		{
548 			if (check_nagios_status(server_conn) == -1)
549 				return -1;
550 
551 			last_check = now;
552 		}
553 
554 		if (now >= (last_irc_io + irc_server_keep_alive))
555 		{
556 			if (irc_time(server_conn) == -1)
557 				return -1;
558 		}
559 
560 		FD_ZERO(&rfds);
561 		FD_SET(server_conn.fd, &rfds);
562 
563 		tv.tv_sec = max(check_interval - (now - last_check), 0);
564 		tv.tv_usec = 0;
565 
566 		if (irc_server_keep_alive > 0)
567 		{
568 			tv.tv_sec = min(max(0, (last_irc_io + irc_server_keep_alive) - now), tv.tv_sec);
569 		}
570 
571 		if (announce_global_status_interval > 0)
572 		{
573 			tv.tv_sec = min(max(0, (last_announce_global_status_interval + announce_global_status_interval) - now), tv.tv_sec);
574 		}
575 
576 		if (select(server_conn.fd + 1, &rfds, NULL, NULL, &tv) == -1)
577 		{
578 			if (errno == EAGAIN || errno == EINTR)
579 				continue;
580 
581 			error_exit("select() failed %s\n", strerror(errno));
582 		}
583 
584 		if (FD_ISSET(server_conn.fd, &rfds))
585 		{
586 			char recv_buffer[4096];
587 			int rc = IRCREAD(server_conn, recv_buffer, sizeof(recv_buffer) - 1);
588 
589 			if (rc == 0)
590 			{
591 				dolog("connection closed by IRC server");
592 				return -1;
593 			}
594 			else if (rc == -1)
595 			{
596 				if (errno != EAGAIN && errno != EINTR)
597 				{
598 					dolog("error transmitting data to IRC server: %s", strerror(errno));
599 					return -1;
600 				}
601 			}
602 			else
603 			{
604 				char *next_response = recv_buffer;
605 				char *response = NULL;
606 				char *cmd = NULL;
607 				char *crlf = NULL;
608 
609 				recv_buffer[rc] = 0x00;
610 
611 				while ((response = next_response)
612 				       && (crlf = strchr( response, '\r')))
613 				{
614 					if (*(crlf + 1) != '\n')
615 					{
616 						dolog( "Malformed server response: `%s' at `%s'.", recv_buffer, response );
617 						return -1;
618 					}
619 
620 					*crlf = '\0';
621 					if (( crlf + 2 ) - recv_buffer < rc)
622 						next_response = crlf + 2;
623 					else
624 						next_response = NULL;
625 
626 					if (verbose > 1) dolog("IN: `%s'", response);
627 
628 					if (check_ping(server_conn, response) == -1)
629 						return -1;
630 
631 					cmd = strchr(response, ' ');
632 					if (cmd)
633 					{
634 						while(*cmd == ' ') cmd++;
635 
636 						if (strncmp(cmd, "KICK ", 5) == 0)
637 						{
638 							char *dummy = strchr(cmd, ' ');
639 							if (dummy)
640 							{
641 								while(*dummy == ' ') dummy++;
642 								dummy = strchr(dummy, ' ');
643 							}
644 							if (dummy)
645 							{
646 								while(*dummy == ' ') dummy++;
647 
648 								if (strncmp(dummy, nick, strlen(nick)) == 0)
649 								{
650 									dolog("nagircbot got kicked (%s)", response);
651 									return -1;
652 								}
653 								else if (verbose > 1)
654 								{
655 									dolog("user %s got kicked from channel", dummy);
656 								}
657 							}
658 						}
659 						else if (strncmp(cmd, "PRIVMSG ", 8) == 0)
660 						{
661 							/* :flok!~flok@rammstein.amc.nl PRIVMSG nagircbot :test */
662 
663 							char *to_me = &cmd[8];
664 							while(*to_me == ' ') to_me++;
665 
666 							/* message to this bot? */
667 							if (strncmp(to_me, nick, strlen(nick)) == 0)
668 							{
669 								/* yes */
670 								char *msg = strchr(cmd, ':');
671 								char *from_who = response + 1;
672 								char *dummy = strchr(from_who, '!');
673 
674 								if (msg != NULL && dummy != NULL)
675 								{
676 									msg++;
677 									*dummy = 0x00;
678 
679 									if (strcasecmp(msg, "help") == 0)
680 									{
681 										int rc = 0;
682 
683 										dolog("help requested by %s", from_who);
684 
685 										rc |= irc_privmsg(server_conn, from_who, "resend - resend the last known problems");
686 										rc |= irc_privmsg(server_conn, from_who, "statistics - returns the number of criticals/warnings/etc.");
687 										rc |= irc_privmsg(server_conn, from_who, "reload - completely forced reload the nagios status");
688 										rc |= irc_privmsg(server_conn, from_who, "check - check if nagios is still running");
689 
690 										if (rc == -1)
691 											return -1;
692 									}
693 									else if (strcmp(msg, "resend") == 0)
694 									{
695 										dolog("resend requested by %s", from_who);
696 
697 										if (resend_nagios_status(server_conn, from_who) == -1)
698 											return -1;
699 									}
700 									else if (strcmp(msg, "check") == 0)
701 									{
702 										char *message;
703 										int rc;
704 
705 										dolog("nagios check requested by %s", from_who);
706 
707 										message = check_nagios();
708 										if (message)
709 										{
710 											char buffer[4096];
711 											snprintf(buffer, sizeof(buffer), "Nagios has stopped running! -> %s", message);
712 											rc = irc_privmsg(server_conn, from_who, buffer);
713 											free(message);
714 										}
715 										else
716 											rc = irc_privmsg(server_conn, from_who, "Nagios is still running");
717 
718 										if (rc == -1)
719 											return -1;
720 									}
721 									else if (strcmp(msg, "statistics") == 0)
722 									{
723 										char buffer[4096];
724 
725 										dolog("statistics requested by %s", from_who);
726 
727 										calc_statistics(prev, n_prev, 0, 0, 0, 1, buffer, sizeof(buffer));
728 
729 										if (irc_privmsg(server_conn, from_who, buffer) == -1)
730 											return -1;
731 									}
732 									else if (strcmp(msg, "reload") == 0)
733 									{
734 										dolog("reload requested by %s", from_who);
735 
736 										if (reload_statuslog() == -1)
737 										{
738 											if (irc_privmsg(server_conn, from_who, "cannot access status.log") == -1)
739 												return -1;
740 										}
741 										else
742 										{
743 											if (irc_privmsg(server_conn, from_who, "nagios status reloaded") == -1)
744 												return -1;
745 										}
746 									}
747 									else
748 									{
749 										char buffer[4096];
750 
751 										dolog("giberish sent by %s", from_who);
752 
753 										snprintf(buffer, sizeof(buffer), "'%s' is not understood - send 'help' for help", msg);
754 										if (irc_privmsg(server_conn, from_who, buffer) == -1)
755 											return -1;
756 
757 										if (send_help(server_conn, from_who) == -1)
758 											return -1;
759 									}
760 								}
761 							}
762 
763 						}
764 						else if (strncmp(cmd, "QUIT ", 5) == 0)
765 						{
766 							char *quit_nick = &response[1];
767 							char *exclamation_mark = strchr(quit_nick, '!');
768 							if (exclamation_mark) *exclamation_mark = 0x00;
769 
770 							if (strcmp(nick, quit_nick) == 0)
771 							{
772 								dolog("Got QUITed (%s)", cmd);
773 								return -1;
774 							}
775 							else if (verbose > 1)
776 							{
777 								dolog("user %s quit", quit_nick);
778 							}
779 						}
780 
781 					}
782 				}
783 			}
784 		}
785 	}
786 
787 	dolog("NagIRCBot dropped of IRC server");
788 
789 	return -1;
790 }
791 
sighandler(int pid)792 void sighandler(int pid)
793 {
794 	if (pid == SIGTERM)
795 	{
796 		if (pidfile)
797 			unlink(pidfile);
798 
799 		exit(1);
800 	}
801 }
802 
version(void)803 void version(void)
804 {
805 	printf("nagircbot, v" VERSION " (C) 2006-2011 by folkert@vanheusden.com\n");
806 }
807 
usage(void)808 void usage(void)
809 {
810 	version();
811 
812 	printf("\n");
813 
814 	printf("-f     path to status.log ('status_file' parameter in nagios.cfg)\n");
815 	printf("-F     host:port for retrieving status.log\n");
816 	printf("-x     status.log is in nagios 1.0 format\n");
817 	printf("-X     status.log is in nagios 2.0/3.0 format\n");
818 	printf("-s     IRC server to connect to (host:port)\n");
819 	printf("-c     channel to connect to (#channel - do not forget to escape the '#' in your shell)\n");
820 	printf("-k     keyword for the channel to connect to (default: no keyword)\n");
821 	printf("-C     use colors\n");
822 	printf("-n     nick\n");
823 	printf("-u     username (for logging into the irc server)\n");
824 	printf("-U     name (as seen by other users)\n");
825 	printf("-p     password (for logging into the irc server)\n");
826 	printf("-N     prefix for all in-channel messages, e.g. for nick highlight\n");
827 	printf("-m     display all information on separate lines\n");
828 	printf("-t     show a summary in the topic-line\n");
829 	printf("-i     check interval (in seconds - default 60)\n");
830 	printf("-I     how often to announce the global status in the channel (in seconds, 0 for off)\n");
831 	printf("       do not set it smaller then the -i value\n");
832 	printf("-d     do not fork into the background\n");
833 	printf("-T x   checks to see if nagios is still running. comma-seperated list\n");
834 	printf("       (without spaces!) with the following elements:\n");
835 	printf("       max_time_last_host_update, max_time_oldest_host_update,\n");
836 	printf("       max_time_last_host_check, max_time_oldest_host_check,\n");
837 	printf("       max_time_last_service_check, max_time_oldest_service_check,\n");
838 	printf("       max_time_oldest_next_service_check\n");
839 	printf("       send 'check' in a private message to invoke the check\n");
840 	printf("-z user	user to run as\n");
841 	printf("-H     show only state type 'HARD' (default)\n");
842 	printf("-S     show also state type 'SOFT'\n");
843 	printf("-R     only announce CRITICAL/UNKNOWN errors on the channel\n");
844 	printf("-A x   filter (omit) lines that match with the given regular expression\n");
845 	printf("-P x   write pid to file x\n");
846 	printf("-e     Use encryption (SSL) (default: no)\n");
847 }
848 
add_filter(char * string)849 void add_filter(char *string)
850 {
851 	filters = (regex_t *)realloc(filters, sizeof(regex_t) * (n_filters + 1));
852 	if (!filters)
853 		error_exit("add_filter: out of memory");
854 
855 	if (regcomp(&filters[n_filters], string, REG_EXTENDED))
856 		error_exit("add_filter: failed to compile regexp '%s'", string);
857 
858 	n_filters++;
859 }
860 
main(int argc,char * argv[])861 int main(int argc, char *argv[])
862 {
863 	int state = S_DISCONNECTED;
864 	int fd = -1;
865 	int sleep_time = default_sleep;
866 	int c;
867 	int do_fork = 1;
868 	time_t time_join_channel_started = (time_t)0;
869 	time_t time_tcp_connected = (time_t)0;
870 	int join_tries = 0;
871 	char *runas = NULL;
872 
873 	color_str[0] = mystrdup("_3,1 ");
874 	color_str[1] = mystrdup("_8,1 ");
875 	color_str[2] = mystrdup("_4,1 ");
876 	color_str[3] = mystrdup("_11,1 ");
877 	color_str[0][0] = color_str[1][0] = color_str[2][0] = color_str[3][0] = 3;
878 
879 	while((c = getopt(argc, argv, "N:A:eRP:xXF:f:i:hHSs:c:k:Ctn:u:U:p:T:mvdVz:I:")) != -1)
880 	{
881 		switch(c)
882 		{
883 			case 'A':
884 				add_filter(optarg);
885 				break;
886 
887 			case 'R':
888 				critical_only = 1;
889 				break;
890 
891 			case 'e':
892 				use_ssl = 1;
893 				break;
894 
895 			case 'P':
896 				pidfile = optarg;
897 				break;
898 
899 			case 'I':
900 				announce_global_status_interval = atoi(optarg);
901 				break;
902 
903 			case 'z':
904 				runas = optarg;
905 				break;
906 
907 			case 'T':
908 				{
909 					char *dummy = optarg;
910 
911 					max_time_last_host_update = atoi(dummy);
912 					dummy = strchr(dummy, ',') + 1;
913 
914 					max_time_oldest_host_update = atoi(dummy);
915 					dummy = strchr(dummy, ',') + 1;
916 
917 					max_time_last_host_check = atoi(dummy);
918 					dummy = strchr(dummy, ',') + 1;
919 
920 					max_time_oldest_host_check = atoi(dummy);
921 					dummy = strchr(dummy, ',') + 1;
922 
923 					max_time_last_service_check = atoi(dummy);
924 					dummy = strchr(dummy, ',') + 1;
925 
926 					max_time_oldest_service_check = atoi(dummy);
927 					dummy = strchr(dummy, ',') + 1;
928 
929 					max_time_oldest_next_service_check = atoi(dummy);
930 					break;
931 				}
932 
933 			case 'H':
934 				hard_only = 1;
935 				break;
936 
937 			case 'S':
938 				hard_only = 0;
939 				break;
940 
941 			case 'd':
942 				do_fork = 0;
943 				break;
944 
945 			case 't':
946 				topic_summary = 1;
947 				break;
948 
949 			case 'C':
950 				use_colors = 1;
951 				break;
952 
953 			case 'm':
954 				one_line = 0;
955 				break;
956 
957 			case 'N':
958 				nick_prefix = mystrdup(optarg);
959 				break;
960 
961 			case 'p':
962 				password = mystrdup(optarg);
963 				break;
964 
965 			case 'U':
966 				username = mystrdup(optarg);
967 				break;
968 
969 			case 'n':
970 				nick = mystrdup(optarg);
971 				break;
972 
973 			case 'u':
974 				user = mystrdup(optarg);
975 				break;
976 
977 			case 's':
978 				server = mystrdup(optarg);
979 				break;
980 
981 			case 'c':
982 				channel = mystrdup(optarg);
983 				break;
984 
985 			case 'k':
986 				keyword = mystrdup(optarg);
987 				break;
988 
989 			case 'x':
990 				statuslog_version = 1;
991 				break;
992 
993 			case 'X':
994 				statuslog_version = 2;
995 				break;
996 
997 			case 'f':
998 				statuslog = optarg;
999 				statuslog_location = L_FILE;
1000 				break;
1001 
1002 			case 'F':
1003 				statuslog = optarg;
1004 				statuslog_location = L_HOST;
1005 				break;
1006 
1007 			case 'i':
1008 				check_interval = atoi(optarg);
1009 				break;
1010 
1011 			case 'v':
1012 				verbose = 1;
1013 				break;
1014 
1015 			case 'V':
1016 				version();
1017 				return 0;
1018 
1019 			case 'h':
1020 				usage();
1021 				return 0;
1022 
1023 			default:
1024 				usage();
1025 				return 1;
1026 		}
1027 	}
1028 
1029 	if (do_fork)
1030 	{
1031 		if (daemon(0, 0) == -1)
1032 		{
1033 			error_exit("Failed to become daemon process!");
1034 		}
1035 	}
1036 
1037 	if (pidfile)
1038 		write_pidfile(pidfile);
1039 
1040 	if (runas)
1041 	{
1042 		struct passwd *p = getpwnam(runas);
1043 		if (p)
1044 		{
1045 			if (setgid(p -> pw_gid) == -1)
1046 				error_exit("setgid(%d) failed", p -> pw_gid);
1047 
1048 			if (setuid(p -> pw_uid) == -1)
1049 				error_exit("setuid(%d) failed", p -> pw_uid);
1050 		}
1051 		else
1052 		{
1053 			error_exit("User '%s' not found.", runas);
1054 		}
1055 	}
1056 
1057 	signal(SIGPIPE, SIG_IGN);
1058 	signal(SIGTERM, sighandler);
1059 	server_conn.addr = server;
1060 	for(;;)
1061 	{
1062 		if (state == S_DISCONNECTED)
1063 		{
1064 			dolog("Connecting to %s", server);
1065 
1066 			fd = connect_to(server);
1067 			if (fd == -1)
1068 			{
1069 				if (verbose > 1)
1070 					dolog("Cannot connect to %s: %m, will sleep %d seconds", server, sleep_time);
1071 
1072 				sleep(sleep_time);
1073 				if (sleep_time < max_sleep_time)
1074 					sleep_time = (int)((double)sleep_time * 1.5);
1075 				else
1076 					sleep_time = max_sleep_time;
1077 			}
1078 			else
1079 			{
1080 				server_conn.fd = fd;
1081 				if( use_ssl ) {
1082 					dolog("Doing SSL handshake");
1083 					if( !ssl_handshake( &server_conn ) ){
1084 						dolog("SSL handshake failed!");
1085 						return 1;
1086 					}
1087 				}
1088 				state = S_CONNECTED;
1089 				join_tries = 0;
1090 				time_tcp_connected = time(NULL);
1091 			}
1092 
1093 			if (verbose == 1)
1094 				dolog("Connected to IRC server %s", server);
1095 		}
1096 
1097 		if (state == S_CONNECTED)
1098 		{
1099 
1100 			dolog("Logging in...");
1101 
1102 			if (irc_login(server_conn, user, username, server, password) == -1)
1103 				state = S_DISCONNECTING;
1104 
1105 			if (irc_join_channel(server_conn, channel, keyword) == -1)
1106 				state = S_DISCONNECTING;
1107 
1108 			state = S_JOINING_CHANNEL;
1109 
1110 			time_join_channel_started = time(NULL);
1111 		}
1112 
1113 		if (state == S_JOINING_CHANNEL)
1114 		{
1115 			fd_set rfds;
1116 			struct timeval tv;
1117 
1118 			dolog("Joining channel...");
1119 
1120 			FD_ZERO(&rfds);
1121 			FD_SET(server_conn.fd, &rfds);
1122 
1123 			tv.tv_sec = 1;
1124 			tv.tv_usec = 0;
1125 
1126 			if (select(server_conn.fd + 1, &rfds, NULL, NULL, &tv) == -1)
1127 			{
1128 				if (errno != EAGAIN && errno != EINTR)
1129 					error_exit("select() failed");
1130 			}
1131 
1132 			if (FD_ISSET(server_conn.fd, &rfds))
1133 			{
1134 				char recv_buffer[4096];
1135 				int rc = IRCREAD(server_conn, recv_buffer, sizeof(recv_buffer) - 1);
1136 
1137 				if (rc == 0)
1138 					state = S_DISCONNECTING;
1139 				else if (rc == -1)
1140 				{
1141 					if (errno != EAGAIN && errno != EINTR)
1142 					{
1143 						dolog("failed joining channel: %s", strerror(errno));
1144 						state = S_DISCONNECTING;
1145 					}
1146 				}
1147 				else
1148 				{
1149 					char *cmd, *pnt = recv_buffer, *end_marker;
1150 
1151 					recv_buffer[rc] = 0x00;
1152 
1153 					if (check_ping(server_conn, recv_buffer) == -1)
1154 						return -1;
1155 
1156 					do
1157 					{
1158 						end_marker = strchr(pnt, '\n');
1159 						if (end_marker) *end_marker = 0x00;
1160 						if (!end_marker) end_marker = strchr(pnt, '\r');
1161 						if (end_marker) *end_marker = 0x00;
1162 #ifdef _DEBUG
1163 						//						printf("[%s]", pnt);
1164 #endif
1165 
1166 						cmd = strchr(pnt, ' ');
1167 						if (cmd)
1168 						{
1169 							while(*cmd == ' ') cmd++;
1170 
1171 							if (strncmp(cmd, "JOIN ", 5) == 0)
1172 							{
1173 								if (verbose > 1)
1174 									dolog("on channel");
1175 								state = S_ON_CHANNEL;
1176 							}
1177 							else if (strncmp(cmd, "KICK ", 5) == 0)
1178 							{
1179 								char *dummy = strchr(cmd, ' ');
1180 								if (dummy)
1181 								{
1182 									while(*dummy == ' ') dummy++;
1183 									dummy = strchr(dummy, ' ');
1184 								}
1185 								if (dummy)
1186 								{
1187 									while(*dummy == ' ') dummy++;
1188 
1189 									if (strncmp(dummy, nick, strlen(nick)) == 0)
1190 										state = S_CONNECTED;
1191 								}
1192 							}
1193 						}
1194 
1195 						pnt = end_marker + 1;
1196 					}
1197 					while(end_marker);
1198 				}
1199 			}
1200 		}
1201 
1202 		if (state == S_JOINING_CHANNEL && (time(NULL) - time_join_channel_started) > join_timeout)
1203 		{
1204 			if (++join_tries == max_n_join_tries)
1205 			{
1206 				state = S_DISCONNECTING;
1207 				dolog("joining the channel %s too to long (%ds, timeout set to %ds), aborting and retrying ", channel, time(NULL) - time_join_channel_started, join_timeout);
1208 			}
1209 			else
1210 			{
1211 				state = S_CONNECTED;
1212 				dolog("failed joining channel %s, retrying (%d/%d)", channel, join_tries, max_n_join_tries);
1213 			}
1214 		}
1215 
1216 		if (state == S_ON_CHANNEL)
1217 		{
1218 			dolog("On channel, starting nagtail...");
1219 			sleep_time = default_sleep;
1220 
1221 			(void)do_nagircbot(server_conn, channel);
1222 
1223 			state = S_DISCONNECTING;
1224 		}
1225 
1226 		if (state == S_DISCONNECTING)
1227 		{
1228 			int took = time(NULL) - time_tcp_connected;
1229 
1230 			dolog("Disconnected");
1231 
1232 			close(fd);
1233 			fd = -1;
1234 			state = S_DISCONNECTED;
1235 
1236 			dolog("Session took %d seconds", took);
1237 
1238 			if (took < minimum_time_for_successfull_login)
1239 			{
1240 				dolog("Will sleep %d seconds before retrying", sleep_time);
1241 
1242 				sleep(sleep_time);
1243 
1244 				if (sleep_time < max_sleep_time)
1245 					sleep_time = (int)((double)sleep_time * sleep_multiply_factor);
1246 				else
1247 					sleep_time = max_sleep_time;
1248 			}
1249 		}
1250 	}
1251 
1252 	return 0;
1253 }
1254