#!/usr/bin/env perl

=pod

=head1 NAME

checkbandwidth - Alert on bandwidth high/low levels for a host/interface

=head1 DESCRIPTION

The checkbandwidth script uses the perl SNMP module to collect, store
and analyze date from the IF-MIB::ifXTable for the ifHCInOctets and
ifHCOutOctets counters.  It can be configured with high/low levels and
issue warnings, either through email or via output text and exit
codes.  Specifically, the exit codes (1 = warning, 2 = error) and text
output are designed to be used with Nagios.  It calculates bandwidth
by comparing the values from the last run against the values from the
current run and the amonut of time that has passeed between the two
runs [i.e. bandwidth = (this_run - last_run)/time].  Because we need
data from two runs, the first time it is run after configuration will
never detect an error until the second run.

Data is stored in ~/.snmp/checkbandwidth.json, which can be
removed/unlinked to reset the command's data store if needed.  Or the
-r switch can be used to just reset the data without wiping the
configuration.

=head1 SYNOPSIS

1) Setup your snmp.conf file with authentication parameters for your system:

    # snmpconf -g basic_setup

2) Add the interface on the host you want monitor, along with an optional
"threshold" parameters that you want to be warned about:

    # checkbandwidth --add-interface eth0 \
                     --max-in-bandwidth 300000=admin@example.com \
                     foo.example.com

3A) Then run the tool against the host on a regular basis (eg, every 5
minutes in cron):

    # checkbandwidth foo.example.com
    foo.example.com  wlan1    49200.1426 in   5538.7522 out (B/s) IO

The numbers reported are the measured bandwidth during this run.  The
"IO" letters are flags letting you know that the (I)nput or (O)utput
values are out of expected range.

3B) Run the tool inside of nagios:

    define command {
           command_name checkbandwidth
           # specific path to vendor_perl is to fix vir's /usr/local installation
           command_line /usr/bin/perl /usr/local/bin/checkbandwidth -L /var/spool/nagios/.snmp/checkbandwidth.json.lock -N -s $ARG1$
    }

=head1 OPTIONS

=head2 Configuration and setup:

=over

=item  -i STRING

=item  --add-interface=STRING

Add this interface to the list of things to report data for.

=item  -I INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )

=item  --max-in-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )

=item  -O INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )

=item  --max-out-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )

These setup options specify an maximum input and/or output bandwidth that will:

1) When running as a nagios script (using the --nagios flag), the
   script will display an appropriate message and exit with a return
   code based on whether the string passed to the flag is 'nagioswarn'
   or 'nagioscritical'.

2) When run manually or via cron, then the checkbandwidth will send an
   email address to ADDRESS about the error.

The INT field should be the bandwidth above which the error or warning
should occur.  If the :COUNT field is included, no messages will be
sent/generated until at least the COUNT'th time is seen (default: 1).
The frequency of the count is dependent on often often the script gets
called or the loop frequency occurs (--loop).

Note that the --add-interface (-i) flag is required for this flag to be useful.

See the EXAMPLES section below for more usage.

=item  -J INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )

=item  --min-in-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )

=item  -Q INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )

=item  --min-out-bandwidth=INT[:COUNT]=( 'nagioswarn' | 'nagioscritical' | ADDRESS )

These two are similar to --max-in-bandwidth and --max-out-bandwidth,
but set the low-water mark thresholds for an interface.

=back

=head2 Operation:

=over

=item  -c STRING

=item  --cache-file=STRING

Location to store persistent data file, which is a JSON encoded structure.
This defaults to ~/.snmp/checkbandwidth.json.

=item  -r

=item  --reset

Ignore all past data and restart the statists collection from scratch
for all hosts/interfaces.  This wipes the collected data without
wiping the configuration min/max warning/critical levels.



=item  -l INTEGER

=item  --loop=INTEGER

Run as a daemon and loop forever, displaying bandwidth performing
checks every INTEGER seconds and displaying an output summary line.

=back

=head2 Nagios mode:

The following options only make sense when acting as an interface with
nagios.  An example nagios command definition is as follows:

    define command {
           command_name checkbandwidth
           command_line /usr/local/bin/checkbandwidth -c /var/spool/nagios/.snmp/checkbandwidth.nagios.json -L /var/spool/nagios/.snmp/checkbandwidth.nagios.json.lock -N $ARG1
    }

Note that if you need to specify perl module paths, you'll need to call it as:

    /usr/bin/perl -I /usr/lib64/perl5/vendor_perl/ /usr/local/bin/checkbandwidth

You can then use a service definition such as this one:

    define service {
           service_description  bandwidth
           host_name            localhost
           check_command        checkbandwidth!localhost
           use                  ...

    }

Recommendation: Use a different configuration file for nagios hosts
than other command line usage.

Recommendation: Use a lock file (-L) to ensure that data from two
parallel calls in nagios won't be written to at once, causing the file
to be corrupted.

Recommendation: If you're monitoring a large number of hosts, use a
sepearate configuration file (and lock file) for each host by passing
an argument like -c /var/spool/nagios/.snmp/checkbandwidth.nagios.$ARG1.json

=over

=item  -N

=item  --nagios

Operate in nagios mode, which will display appropriate nagios display
error codes and exit with an exit code that nagios will process.

=item  -s 

=item --nagios-summary

Place a basic summary line of bandwidth in the output even when no
errors are currently present.  Otherwise, the output will simply be
"All interfaces ok"



=item  -L LOCKFILE

=item  --lock-file LOCKFILE

Use this file as a lockfile to ensure multiple checkbandwidth's
running won't try to write to the file at the same time.

=back

=head2 Sending mail on errors from cron

When not in nagios mode, checkbandwidth will send mail if an error
condition is reached.  The following options specify the SMTP server
parameters used to send mail.

=over

=item  -S STRING

=item  --smtp-server=STRING

The hostname or IP address of the SMTP serve to use.



=item  -P INTEGER

=item  --port=INTEGER

The port number of the SMTP server to use.



=item  -F STRING

=item  --from=STRING

The email address to put in the From: line.


=item  -q

=item  --quiet

Don't output data to the terminal (i.e., email only).

=back

=head2 Debugging:

=over

=item  -d

=item  --dump

Dump the raw JSON data when finished.

=item  -v

=item  --verbose

Log (debugging) messages to stderr about what is being done

=item  -n

=item  --noop

Don't store the collected information from the SNMP agent to the
persistent data storage file.  I.E., only compare the results to last time.

[This is highly useful when you just want to check the server from the
command line using a data store configuration cache that is being used
by another daemon (--loop mode) or by nagios], without affecting the
timing between runs]

=back

=head1 EXAMPLES

=head2 Example Setup

With these goals:

* monitor eth0
* send a message to root@localhost when the input bandwidth goes above 50000 B/s
* send a message to ops@localhost when the input bandwidth goes above 100000 B/s

This can be configured as following:

    # checkbandwidth -i eth0 -I 50000=root@localhost localhost
    # checkbandwidth -i eth0 -I 100000=ops@localhost localhost

If we want to add new configuration in the future, say for the output
interface at 50000 but with a minimum number of 3 "times seen", we can do so:

    # checkbandwidth -i eth0 -O 50000:3=root@localhost localhost

Testing it on a regalur basis is as simple as running:

    # checkbandwidth
    localhost       eth0           3907.2973 in        1638.1081 out (B/s)

=head2 Nagios usage example

* monitor eth0
* Alert at nagios 'warning' when the input bandwidth goes above 50000 B/s
* Alert at nagios 'critical' when the input bandwidth goes above 100000 B/s

    # checkbandwidth -i eth0 -I 50000=nagioswarning -O 50000=nagioswarning localhost
    # checkbandwidth -i eth0 -I 50000=nagioscritical -O 50000=nagioscritical localhost

(and you may or may not want to add -s to always include summary information)

=head1 BUGS / TODO

* There is no way to automatically remove a configured option.  IE,
  once an delivery address is designated, there is no way to remove it
  without manually editing the json file.

=head1 AUTHOR

Wes Hardaker <hardaker@users.sourceforge.net>
USC/ISI

=head1 COPYRIGHT and LICENSING
 
See the Net-SNMP COPYING file for licensing information for this script.

=cut

use JSON;
use Data::Dumper;
use Mail::Sender;
use SNMP;
use Fcntl ':flock';

use strict;

my %opts = (
            c => "$ENV{HOME}/.snmp/checkbandwidth.json",
            S => 'localhost',
            F => 'root',
            P => 25,
            );
my %storage;
my ($NAGIOS_NORMAL, $NAGIOS_WARNING, $NAGIOS_CRITICAL) = (0,1,2);
my $nagiosexit = $NAGIOS_NORMAL;
my $nagiosstr = "";

LocalGetOptions(\%opts,
           ["GUI:separator",     "Configuration and setup:"],
           ["i|add-interface=s", "Add this interface to the list of things to monitor"],
           ["I|max-in-bandwidth=s", "Alert to ADDRESS on bandwidth > INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
           ["O|max-out-bandwidth=s", "Alert to ADDRESS on bandwidth > INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
           ["J|min-in-bandwidth=s", "Alert to ADDRESS on bandwidth < INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],
           ["Q|min-out-bandwidth=s", "Alert to ADDRESS on bandwidth < INT, COUNT times; STRING format: INT[:COUNT]=ADDRESS"],

           ["GUI:separator",     "Operation:"],
           ["c|cache-file=s", 	 "Location to store persistent data"],
           ["r|reset",         	 "Ignore all past data and start from scratch for all hosts/interfaces"],
           ["l|loop=i",          "Loop forever, displaying bandwidth every INTEGER seconds"],

           ["GUI:separator",     "Nagios mode:"],
           ["N|nagios",          "Operate in nagios mode, displaying errors/exit code for a given host"],
           ["s|nagios-summary",  "Always include summary information in the nagios output"],
           ["L|lock-file=s",     "Use a lockfile to ensure no simultaneous runs"],

           ["GUI:separator",     "Sending mail:"],
           ["S|smtp-server=s",   "SMTP server to use"],
           ["P|port=i",          "SMTP port to use"],
           ["F|from=s",          "From address to use"],

           ["GUI:separator",     "Debugging:"],
           ["q|quiet",           "Don't output data to the terminal (i.e., email only)"],
           ["d|dump",         	 "Dump the raw JSON data when finished"], 
           ["v|verbose",      	 "Log (debugging) messages to stderr about what is being done"],
           ["n|noop",         	 "Don\'t store persistent data ; compare only to last time"],
          ) || die "Illegal usage; see -h for help";

my @hosts = @ARGV; # remaining arguments should be hosts

my $cache = read_cache();

if ($#hosts == -1) {
	@hosts = keys(%{$cache->{'hosts'}});
	Verbose("loading hosts from cache: ", join(", ", @hosts));
}

my $lockfileh;

if ($opts{'d'}) {
	print to_json($cache, { ascii => 1, pretty => 1}),"\n";
} elsif ($opts{'i'}) {
	add_interface($cache, $opts{'i'}, $opts{'I'}, $opts{'O'}, $opts{'J'}, $opts{'Q'}, \@hosts);
} else {
	# just process per norm
	do {
		if ($opts{'L'}) {
			open($lockfileh, ">>$opts{L}");
			my $have_lock = flock($lockfileh, LOCK_EX | LOCK_NB);
			if (! $have_lock) {
				print STDERR "failed to get lock on $opts{L}\n";
				print "failed to get lock on $opts{L}\n";
				exit($NAGIOS_NORMAL);
			}
		}
		$cache = process_hosts($cache, \@hosts);
		if (defined($opts{'l'})) {
			Output("");
			save_cache($cache);
			sleep($opts{'l'});
		}
	} while ($opts{'l'});
}

save_cache($cache);
if ($lockfileh) {
	unlink($lockfileh);
	flock($lockfileh, LOCK_UN);  # it's ok if it fails
}
nagios_exit() if ($opts{'N'});

sub read_cache {
	# read in the json-based cache file
	my $cache = {};
	my ($buf, $content);
	
	if (-f $opts{'c'}) {
		Verbose("Reading cache file from '$opts{c}'");

		open(my $cacheh, "<", $opts{'c'});
		while (read($cacheh, $buf, 4096 * 16) > 0) {
			$content .= $buf;
		}
		close($cacheh);
			
		$cache = from_json($content); # cheap hack
	}
	return $cache;
}

sub process_hosts($$) {
	my ($cache, $hosts) = @_;
	
	# process hosts
	foreach my $host (sort @$hosts) {
		Verbose("Procesing host '$host'");

		my $sess = get_snmp_session($host);

		# we always want the uptime
		my @vars;
		push @vars, ['sysUpTime',0];

		foreach my $interface (sort keys(%{$cache->{'hosts'}{$host}{'interfaces'}})) {
			push @vars, ['ifHCInOctets', $cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'}];
			push @vars, ['ifHCOutOctets', $cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'}];
		}
		
		my $list = new SNMP::VarList(@vars);
		my $res = $sess->get($list);

		my $sysUpTime = shift @$list;
		my $sysUpTime = $sysUpTime->[2];

		my $sysUpTimeDiff = $sysUpTime - $cache->{'hosts'}{$host}{'sysUpTime'};

		foreach my $interface (sort keys(%{$cache->{'hosts'}{$host}{'interfaces'}})) {
			my $ifHCInOctets = shift @$list;
			my $ifHCOutOctets = shift @$list;

			my $ifData = $cache->{'hosts'}{$host}{'interfaces'}{$interface};

			if ($opts{'r'} ||
			    !exists($ifData->{'data'}{'ifHCInOctets'}) ||
			    !exists($cache->{'hosts'}{$host}{'sysUpTime'}) ||
			    $sysUpTimeDiff < 0) {
				# reset all past data or...
				# no data is collected yet -- just store the sysUpTime
				# XXX: should collect engine boots too
				$ifData->{'data'}{'ifHCInOctets'}  = $ifHCInOctets->[2];
				$ifData->{'data'}{'ifHCOutOctets'} = $ifHCOutOctets->[2];
				Verbose("interface $interface on $host needs to start with new data");
			} else {
				# the great analysis

				my $diffIn  = $ifHCInOctets->[2] - $ifData->{'data'}{'ifHCInOctets'};
				my $diffOut = $ifHCOutOctets->[2] - $ifData->{'data'}{'ifHCOutOctets'};
				
				Verbose("$interface   in: $ifHCInOctets->[2] - $ifData->{'data'}{'ifHCInOctets'}");
				Verbose("$interface diff: $diffIn/$sysUpTimeDiff = " . (100 * $diffIn/$sysUpTimeDiff) . " B/s");

				Verbose("$interface  out: $ifHCOutOctets->[2] - $ifData->{'data'}{'ifHCOutOctets'}");
				Verbose("$interface diff: $diffOut/$sysUpTimeDiff = " . (100 * $diffOut/$sysUpTimeDiff) . " B/s");

				my $inRate = (100 * $diffIn/$sysUpTimeDiff);
				my $outRate = (100 * $diffOut/$sysUpTimeDiff);
				
				my $inMarker = " ";
				my $outMarker = " ";

				# check maximum bandwidth limits
				if (defined($ifData->{'maxInBandwidth'})) {
					foreach my $limit (@{$ifData->{'maxInBandwidth'}}) {
						if ($inRate > 0 && $inRate > $limit->{'rate'}) {
							$inMarker = "I";
							$ifData->{'maxInBandwidthCount'} ++;

							if ($ifData->{'maxInBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
								Verbose("  input too high!!!  $inRate > $limit->{'rate'} ; emailing $limit->{'email'}");
								send_rate_message($limit->{'email'},
								                  $host, $interface, 'in', 'gt',
								                  $ifData->{'maxInBandwidthCount'},
								                  $inRate, $limit->{'rate'});
							}
						} else {
							$ifData->{'maxInBandwidthCount'} = 0;
						}
					}
				}

				if (defined($ifData->{'maxOutBandwidth'})) {
					foreach my $limit (@{$ifData->{'maxOutBandwidth'}}) {
						if ($outRate > 0 && $outRate > $limit->{'rate'}) {
							$outMarker = "O";
							$ifData->{'maxOutBandwidthCount'} ++;
							
							if ($ifData->{'maxOutBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
								Verbose("  output too high!!! $outRate > $limit->{'rate'} ; emailing $limit->{'email'}");

								send_rate_message($limit->{'email'},
								                  $host, $interface, 'out', 'gt',
								                  $ifData->{'maxOutBandwidthCount'},
								                  $outRate, $limit->{'rate'});
							}
						} else {
							$ifData->{'maxOutBandwidthCount'} = 0;
						}
					}
				}

				# check minimum bandwidth limits
				if (defined($ifData->{'minInBandwidth'})) {
					foreach my $limit (@{$ifData->{'minInBandwidth'}}) {
						if ($inRate > 0 && $inRate < $limit->{'rate'}) {
							$inMarker = "I";
							$ifData->{'minInBandwidthCount'} ++;

							if ($ifData->{'minInBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
								Verbose("  input too low!!!  $inRate < $limit->{'rate'} ; emailing $limit->{'email'}");
								send_rate_message($limit->{'email'},
								                  $host, $interface, 'in', 'lt',
								                  $ifData->{'minInBandwidthCount'},
								                  $inRate, $limit->{'rate'});
							}
						} else {
							$ifData->{'minInBandwidthCount'} = 0;
						}
					}
				}

				if (defined($ifData->{'minOutBandwidth'})) {
					foreach my $limit (@{$ifData->{'minOutBandwidth'}}) {
						if ($outRate > 0 && $outRate < $limit->{'rate'}) {
							$outMarker = "O";
							$ifData->{'minOutBandwidthCount'} ++;
							
							if ($ifData->{'minOutBandwidthCount'} == $limit->{'maxcount'} || $opts{'N'}) {
								Verbose("  output too low!!! $outRate < $limit->{'rate'} ; emailing $limit->{'email'}");

								send_rate_message($limit->{'email'},
								                  $host, $interface, 'out', 'lt',
								                  $ifData->{'minOutBandwidthCount'},
								                  $outRate, $limit->{'rate'});
							}
						} else {
							$ifData->{'minOutBandwidthCount'} = 0;
						}
					}
				}
				
				Output(sprintf("%-15s %-8s %16.0f in %16.0f out (B/s) %s%s",
				               $host, $interface,
				               $inRate, $outRate,
				               $inMarker, $outMarker));

				# Update the cache data.  Maybe.
				if (! $opts{'n'}) {
					$ifData->{'data'}{'ifHCInOctets'}  = $ifHCInOctets->[2];
					$ifData->{'data'}{'ifHCOutOctets'} = $ifHCOutOctets->[2];
				}
			}
		}

		# store the sysUpTime
		$cache->{'hosts'}{$host}{'sysUpTime'} = $sysUpTime unless ($opts{'n'});
	}

	return $cache;
}

sub add_interface($$$$$) {
	my ($cache, $interface, $maxInBandwidth, $maxOutBandwidth, $minInBandwidth, $minOutBandwidth, $hosts) = @_;
	foreach my $host (@$hosts) {
		Verbose("adding interface $interface to $host");

		my $session = get_snmp_session($host);
		my $tabledata = $session->gettable('ifTable');
		my $found = 0;

		foreach my $key (keys(%$tabledata)) {
			# each key is an interface number; find the matching interface
			if ($tabledata->{$key}{'ifDescr'} eq $interface) {
				# found it!

				Verbose("found $interface on $host at $key; adding it");
				
				$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'index'} = $key;
				if ($maxInBandwidth) {
					my ($ratespec, $email) = split(/=/, $maxInBandwidth);
					my ($rate, $count)     = split(/:/, $ratespec);
					if (!defined($count)) {
						$rate = $ratespec;
						$count = 1;
					}
					push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'maxInBandwidth'}},
					  { rate => $rate,
					    email => $email,
					    maxcount => $count };
				}
				if ($maxOutBandwidth) {
					my ($ratespec, $email) = split(/=/, $maxOutBandwidth);
					my ($rate, $count)     = split(/:/, $ratespec);
					if (!defined($count)) {
						$rate = $ratespec;
						$count = 1;
					}
					push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'maxOutBandwidth'}},
					  { rate => $rate,
					    email => $email,
					    maxcount => $count};
				}
				if ($minInBandwidth) {
					my ($ratespec, $email) = split(/=/, $minInBandwidth);
					my ($rate, $count)     = split(/:/, $ratespec);
					if (!defined($count)) {
						$rate = $ratespec;
						$count = 1;
					}
					push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'minInBandwidth'}},
					  { rate => $rate,
					    email => $email,
					    maxcount => $count };
				}
				if ($minOutBandwidth) {
					my ($ratespec, $email) = split(/=/, $minOutBandwidth);
					my ($rate, $count)     = split(/:/, $ratespec);
					if (!defined($count)) {
						$rate = $ratespec;
						$count = 1;
					}
					push @{$cache->{'hosts'}{$host}{'interfaces'}{$interface}{'minOutBandwidth'}},
					  { rate => $rate,
					    email => $email,
					    maxcount => $count};
				}
				$found = 1;
				Output("Configured $interface for $host");
			}
		}

		if (!$found) {
			Log("  failed to find interface '$interface' on '$host'");
		}
	}
}

my %session_cache;
sub get_snmp_session($) {
	my ($host) = @_;
	if (exists($session_cache{$host})) {
		return $session_cache{$host};
	}

	my @args;
	
	if (-f "/etc/snmp/perl/$host.conf") {
		open(CC, "/etc/snmp/perl/$host.conf");
		while (<CC>) {
			next if (/^\s*#/);
			chomp;
			push @args, split();
		}
	}

	# hack to work around perl not handling per-host files correctly
	$session_cache{$host} = new SNMP::Session(DestHost => $host, @args);
	return	$session_cache{$host};
}

sub save_cache($) {
	my ($cache) = @_;

	# save data to the json cache (maybe)
	if (!$opts{'n'}) {
		my $dir = $opts{'c'};
		$dir =~ s/(.*)\/.*/$1/;
		if (! -d $dir) {
			mkdir($dir);
			if (! -d $dir) {
				Log("failed to create directory $dir");
			} else {
				Verbose("note: created directory $dir");
			}
		}
		# XXX: mkdir
		
		Verbose("Writing cache file to '$opts{c}'"); 
		
		my $jsondata = to_json($cache);
		open(my $outh, ">", $opts{'c'} . $$);
		print $outh $jsondata,"\n";
		$outh->close();
		rename($opts{'c'} . $$, $opts{'c'});
	}
}

sub send_rate_message($$$$$$) {
	my ($to, $host, $interface, $direction, $highlow, $count, $measured, $max) = @_;

	if ($opts{'N'} && ($to eq 'nagioswarn' || $to eq 'nagioscritical')) {
		# nagios mode ; collect simple output for later
		nagios_collect($to, $host, $interface, $direction, $highlow, $count, $measured, $max);
	} elsif($to ne 'nagioswarn' && $to ne 'nagioscritical') {
		# send email
		my $word = ($highlow eq '>' ? 'exceeded' : 'too low');
		send_message($to,
		             "rate $word: $host/$interface=$direction ($count times)",
		             "Rate limit $word for:\n\n" .
		             "\thost:\t\t$host\n" .
		             "\tinterface:\t$interface ($direction)\n" .
		             "\trate:\t\t$measured\n" .
		             "\tmax allowed:\t$max\n" .
		             "\tcount:\t\t$count times in a row\n");
	}
}

sub send_message($$$) {
	my ($to, $subject, $text) = @_;

	my $sender = new Mail::Sender { smtp => $opts{'S'} ,
	                                port => $opts{'P'},
	                                from => $opts{'F'},
                                  };

	my $status =
	  $sender->MailMsg({
	                    to      => $to,
	                    subject => $subject,
	                    msg     => $text
	                   });
	if ($status < 0) {
		Log("Failed to send mail with error code $status: $Mail::Sender::Error");
	}
}

sub nagios_collect($$$$$$) {
	my ($to, $host, $interface, $direction, $highlow, $count, $measured, $max) = @_;

	if ($to eq 'nagioscritical') {
		$nagiosexit = $NAGIOS_CRITICAL;
	} elsif ($to eq 'nagioswarn' && $nagiosexit < $NAGIOS_CRITICAL) {
		$nagiosexit = $NAGIOS_WARNING;
	}

	$nagiosstr .= sprintf(", %s $direction %16.0f B/s $highlow %s", $interface, $measured, $max);
}

sub nagios_exit() {
	if ($nagiosstr eq '') {
		$nagiosstr = "All interfaces ok: " . join(", ", keys(%{$cache->{'hosts'}{$hosts[0]}{'interfaces'}}));
	} else {
		$nagiosstr =~ s/^, //;
	}

	print $nagiosstr, "\n";
	exit $nagiosexit;
}

sub Verbose {
	if ($opts{'v'}) {
		print STDERR @_, "\n";
	}
}

sub Log {
	print STDERR @_,"\n";
}

sub Output {
	if (! $opts{'q'} && !$opts{'N'}) {
		print @_, "\n";
	}
	if ($opts{'N'} && $opts{'s'}) {
		$nagiosstr .= ", " . join(" ", @_);
	}
}

#### portability for not requiring Getopt::GUI::Long directly
sub LocalGetOptions {
	if (eval {require Getopt::GUI::Long;}) {
		import Getopt::GUI::Long;
		# optional configure call
		Getopt::GUI::Long::Configure(qw(display_help no_ignore_case allow_zero));
		return GetOptions(@_);
	}
	require Getopt::Long;
	import Getopt::Long;
	# optional configure call
	Getopt::Long::Configure(qw(auto_help no_ignore_case));
	GetOptions(LocalOptionsMap(@_));
}

sub LocalOptionsMap {
	my ($st, $cb, @opts) = ((ref($_[0]) eq 'HASH') 
	                        ? (1, 1, $_[0]) : (0, 2));
	for (my $i = $st; $i <= $#_; $i += $cb) {
		if ($_[$i]) {
			next if (ref($_[$i]) eq 'ARRAY' && $_[$i][0] =~ /^GUI:/);
			push @opts, ((ref($_[$i]) eq 'ARRAY') ? $_[$i][0] : $_[$i]);
			push @opts, $_[$i+1] if ($cb == 2);
		}
	}
	return @opts;
}

# Local Variables:
# tab-width: 4
# End:
