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