/******************************************************************************
*
* CHECK_INET_FPING.C
*
* Program: Fping plugin for Nagios
* License: GPL
* Copyright (c) 1999 Didi Rieder (adrieder@sbox.tu-graz.ac.at)
* $Id$
*
* Modifications:
*
* 08-24-1999 Didi Rieder (adrieder@sbox.tu-graz.ac.at)
*            Intial Coding
* 09-11-1999 Karl DeBisschop (kdebiss@alum.mit.edu)
*            Change to spopen
*            Fix so that state unknown is returned by default
*            (formerly would give state ok if no fping specified)
*            Add server_name to output
*            Reformat to 80-character standard screen
* 11-18-1999 Karl DeBisschop (kdebiss@alum.mit.edu)
*            set STATE_WARNING of stderr written or nonzero status returned
* 09-29-2000 Matthew Grant (matthewg@plain.co.nz)
*            changes for monitoring multiple hosts for checking Internet
*            reachibility
*
* Description:
*
* This plugin will use the /bin/fping command (from nagios) to ping
* the specified host for a fast check if the host is alive. Note that
* it is necessary to set the suid flag on fping.
******************************************************************************/

#include "config.h"
#include "common.h"
#include "popen.h"
#include "utils.h"

const char *progname = "check_fping_in";
#define PACKET_COUNT 15
#define PACKET_SIZE 56
#define CRITICAL_COUNT 2
#define WARNING_COUNT 1
#define UNKNOWN_PACKET_LOSS 200 /* 200% */
#define UNKNOWN_TRIP_TIME -1.0 /* -1 seconds */

#define STRSZ 100

int textscan(char *buf);
int process_arguments(int, char **);
int get_threshold (char *arg, char *rv[2]);
void print_usage(void);
void print_help(void);

char *server_names=NULL;
char *name="INTERNET";
int cthresh=CRITICAL_COUNT;
int wthresh=WARNING_COUNT;
int nnames=0;
int tpl=UNKNOWN_PACKET_LOSS;
double trta=UNKNOWN_TRIP_TIME;
int packet_size=PACKET_SIZE;
int packet_count=PACKET_COUNT;
int verbose=FALSE;
int fail = 0;
int not_found = 0;
int rta_fail = 0;
int pl_fail = 0;
int unreachable = 0;

int main(int argc, char **argv){
	int result;
	int status=STATE_UNKNOWN;
	char *servers=NULL;
	char *command_line=NULL;
	char *input_buffer=NULL;
	char *pl_buffer=NULL; 
	char *rta_buffer=NULL; 
	input_buffer=malloc(MAX_INPUT_BUFFER);
	rta_buffer = malloc(80);
	pl_buffer = malloc(80);
	memset(rta_buffer, 0, 80);
	memset(pl_buffer, 0, 80);
	
	if(process_arguments(argc,argv)==ERROR)
		usage("Could not parse arguments\n");

	servers=strscpy(servers,server_names);

	/* compose the command */
	command_line=ssprintf
		(command_line,"%s -b %d -c %d %s",
		 PATH_TO_FPING,
		 packet_size,
		 packet_count,
		 servers);

	if (verbose) printf("%s\n",command_line);

	/* run the command */
	child_process=spopen(command_line);
	if(child_process==NULL){
		printf("Unable to open pipe: %s\n",command_line);
		return STATE_UNKNOWN;
	}

	child_stderr=fdopen(child_stderr_array[fileno(child_process)],"r");
	if(child_stderr==NULL){
		printf("Could not open stderr for %s\n",command_line);
	}

	while (fgets(input_buffer,MAX_INPUT_BUFFER-1,child_process)) {
		if (verbose) printf("%s",input_buffer);
		result = textscan(input_buffer);
		status = max(status,result);
	}

	while(fgets(input_buffer,MAX_INPUT_BUFFER-1,child_stderr)) {
		if (verbose) printf("%s",input_buffer);
		result = textscan(input_buffer);
		status = max(status,result);
	}
	
	(void)fclose(child_stderr);

	/* close the pipe */
	if(spclose(child_process))
		status=max(status,STATE_WARNING);

	/* Analyse fail count and produce results */
	if (fail >= wthresh) {
		status = max(status, STATE_WARNING);
	}

	if (fail >= cthresh) {
		status = max(status, STATE_CRITICAL);
	}
	
	if( tpl != UNKNOWN_PACKET_LOSS ) {
		snprintf(pl_buffer, 80, ", %d PL", pl_fail);
	}

	if( trta != UNKNOWN_TRIP_TIME ) {
		snprintf(rta_buffer, 80, ", %d RTA", rta_fail);

	}
	
	printf("FPING %s - %s, %d of %d fail, %d NF, %d UR%s%s\n",
			state_text(status),
			(name != NULL ? name : server_names),
			fail,
			nnames,
			not_found,
			unreachable,
			pl_buffer,
			rta_buffer);
	
	return status;
}



/* analyse fping output - each event resulting in an increment of fail
 * must be mutually exclusive. packet loss and round trip time analysed 
 * together, both at once just results in one increment of fail
 */
int textscan(char *buf)
{
	char *rtastr=NULL;
	char *losstr=NULL;
	double loss;
	double rta;
	int status=STATE_OK;
	
	if (strstr(buf,"not found")) {
		fail++;
		not_found++;
	} else if(strstr(buf,"xmt/rcv/%loss") 
		&& strstr(buf,"min/avg/max")) {
		losstr = strstr(buf,"=");
		losstr = 1+strstr(losstr,"/");
		losstr = 1+strstr(losstr,"/");
		rtastr = strstr(buf,"min/avg/max");
		rtastr = strstr(rtastr,"=");
		rtastr = 1+index(rtastr,'/');
		loss = strtod(losstr,NULL);
		rta = strtod(rtastr,NULL);
		/* Increment fail counter
		 */
		if (tpl!=UNKNOWN_PACKET_LOSS && loss>tpl) {
			fail++;
		}
		else if (trta!=UNKNOWN_TRIP_TIME && rta>trta) {
			fail++;
		}
		else if (loss >= 100) {
			fail++;
		}
		/* Increment other counters 
		 */
		if (trta!=UNKNOWN_TRIP_TIME && rta>trta) 
			rta_fail++;
		if (tpl!=UNKNOWN_PACKET_LOSS && loss>tpl) 
			pl_fail++;
		if (loss >= 100) 
			unreachable++;
	} else if(strstr(buf,"xmt/rcv/%loss") ) {
		losstr = strstr(buf,"=");
		losstr = 1+strstr(losstr,"/");
		losstr = 1+strstr(losstr,"/");
		loss = strtod(losstr,NULL);
		/* Increment fail counter
		 */
		if (tpl!=UNKNOWN_PACKET_LOSS && loss>tpl) {
			fail++;
		}
		else if (loss >= 100) {
			fail++;
		}
		/* Increment other counters 
		 */
		if (tpl!=UNKNOWN_PACKET_LOSS && loss>tpl)
			pl_fail++;
		if (loss >= 100) 
			unreachable++;
	}
	
	return status;
}




/* process command-line arguments */
int process_arguments(int argc, char **argv)
{
	int c;

#ifdef HAVE_GETOPT_H
	int option_index = 0;
	static struct option long_options[] =
	{ 
		{"hostname"           ,required_argument,0,'H'},
		{"critical"           ,required_argument,0,'c'},
		{"warning"            ,required_argument,0,'w'},
		{"bytes"              ,required_argument,0,'b'},
		{"number"             ,required_argument,0,'n'},
		{"pl-threshold"       ,required_argument,0,'p'},
		{"rta-threshold"      ,required_argument,0,'r'},
		{"name"               ,required_argument,0,'N'},
		{"verbose"            ,no_argument,      0,'v'},
		{"version"            ,no_argument,      0,'V'},
		{"help"               ,no_argument,      0,'h'},
		{0,0,0,0}
	};
#else

 	if(argc<2) return ERROR;
	
	if (!is_option(argv[1])){
		server_names=argv[1];
		argv[1]=argv[0];
		argv=&argv[1];
		argc--;
	}
#endif
	
	while (1){
#ifdef HAVE_GETOPT_H
		c = getopt_long(argc,argv,"+hVvH:c:w:b:n:N:p:r:",long_options,&option_index);
#else
		c = getopt(argc,argv,"+hVvH:c:w:b:n:N:p:r:");
#endif

		if (c==-1||c==EOF||c==1)
			break;

		switch (c)
			{
			case '?': /* print short usage statement if args not parsable */
				printf("%s: Unknown argument: %s\n\n",my_basename(argv[0]),optarg);
				print_usage();
				exit(STATE_UNKNOWN);
			case 'h': /* help */
				print_help();
				exit(STATE_OK);
			case 'V': /* version */
				print_revision(my_basename(argv[0]),"$Revision$");
				exit(STATE_OK);
			case 'v': /* verbose mode */
				verbose=TRUE;
				break;
			case 'H': /* hostname */
				if(is_host(optarg)==FALSE){
					printf("Invalid host name/address\n\n");
					print_usage();
					exit(STATE_UNKNOWN);
				}
				if (server_names != NULL) 
					server_names=strscat(server_names," ");
				server_names=strscat(server_names,optarg);
				nnames++;
				break;
			case 'c':
				if (is_intpos(optarg))
					cthresh = atoi(optarg);
				else
					usage("Critical threshold must be a positive integer"); 
				break;
			case 'w':
				if (is_intpos(optarg))
					wthresh = atoi(optarg);
				else
					usage("Warning threshold must be a postive integer");
				break;
			case 'r':
				if (is_intpos(optarg)) {
					trta=strtod(optarg,NULL);
				} 
				else {
					usage("RTA threshold must be a positive integer");
				}
				break;
			case 'p':
				if (is_intpos(optarg)) {
					tpl=strtod(optarg,NULL);
				} 
				else {
					usage("RTA threshold must be a positive integer");
				}
				break;
			case 'b': /* bytes per packet */
				if (is_intpos(optarg))
					packet_size=atoi(optarg);
				else
					usage("Packet size must be a positive integer");
				break;
			case 'N': /* Name of service */
				name = optarg;
				break;
			case 'n': /* number of packets */
				if (is_intpos(optarg))
					packet_count=atoi(optarg);
				else
					usage("Packet count must be a positive integer");
				break;
			}
	}

	while (optind < argc) {
		if(is_host(argv[optind])==FALSE) {
			printf("Invalid host name/address\n\n");
			print_usage();
			exit(STATE_UNKNOWN);
		}
		if (server_names != NULL)
			server_names=strscat(server_names," ");
		server_names=strscat(server_names,argv[optind]);
		nnames++;
		optind++;
	}
	
	if (server_names==NULL || nnames < 2)
		usage("At least 2 hostnames must be supplied\n\n");
	
	if (cthresh < 2) 
		usage("Critical threshold must be at least 2");
	if (cthresh > nnames)
		usage("Critical threshold cannot be greater than number of hosts tested");
	if (wthresh < 1)
		usage("Warning threshold must be at least 1");
	if (wthresh > nnames)
		usage("Warning threshold cannot be greater than number of hosts tested");
	if(wthresh >= cthresh)
		usage("Warning threshold must be less than the critical threshold"); 
	
	return OK;
}


void print_usage(void)
{
	printf("Usage: %s <host_address> <host_address> [<host_address>] ...\n",progname);
}





void print_help(void)
{

	print_revision(progname,"$Revision$");

	printf
		("Copyright (c) 1999 Didi Rieder (adrieder@sbox.tu-graz.ac.at)\n"
		 "          (c) 2000 Matthew Grant (matthewg@plain.co.nz)\n"
		 "This plugin will use the /bin/fping command (from saint) to ping the\n"
		 "specified hosts for a fast check to see if the Internet is still \n"
		 "reachable, and the results of the testing aggregated. Note that it\n"
		 "is necessary to set the suid flag on fping.\n\n");

	print_usage();

  printf
		("\nOptions:\n"
		 "-b, --bytes=INTEGER\n"
		 "   Size of ICMP packet (default: %d)\n"
		 "-c, --critical=INTEGER (default: %d)\n"
		 "   critical threshold failure count\n"
		 "-n, --number=INTEGER\n"
		 "   Number of ICMP packets to send (default: %d)\n"
		 "-H, --hostname=HOST\n"
		 "   Name or IP Address of host to ping (IP Address bypasses name lookup,\n"
		 "   reducing system load)\n"
		 "-h, --help\n"
		 "   Print this help screen\n"
		 "-N, --name\n"
		 "   Service name to print in results, defaults to INTERNET\n"
		 "-p, --pl-threshold\n"
		 "   Packet loss threshold - specify to turn on packet loss testing\n"
		 "-r, --rta-threshold\n"
		 "   Round trip average threshold - specify to turn on RTA testing\n"
		 "-V, --version\n"
		 "   Print version information\n"
		 "-v, --verbose\n"
		 "   Show details for command-line debugging (do not use with nagios server)\n"
		 "-w, --warning=INTEGER (default: %d)\n"
		 "   warning threshold failure count\n",
		 PACKET_SIZE, CRITICAL_COUNT, PACKET_COUNT, WARNING_COUNT);
}