#!/usr/bin/perl

# This fencing agent is written for the Baytech RPC27-20nc in conjunction with
# a Cyclades terminal server.  The Cyclades TS exports the RPC's serial port
# via a Telnet interface.  Other interfaces, such as SSH, are possible.  
# However, this script relys upon the assumption that Telnet is used.  Future
# features to this agent would allow the agent to work with a mulitude of 
# different communication protocols such as Telnet, SSH or Kermit.
#
# The other assumption that is made is that Outlet names do not end in space.
# The name "Foo" and "Foo    " are identical when the RPC prints them with
# the status command.

use Net::Telnet;
use Getopt::Std;

my $ME = $0;

END {
  defined fileno STDOUT or return;
  close STDOUT and return;
  warn "$ME: failed to close standard output: $!\n";
  $? ||= 1;
}

# WARNING!! Do not add code bewteen "#BEGIN_VERSION_GENERATION" and 
# "#END_VERSION_GENERATION"  It is generated by the Makefile

#BEGIN_VERSION_GENERATION
$RELEASE_VERSION="3.0.12";
$BUILD_DATE="(built Fri Jul 2 09:32:20 UTC 2010)";
$REDHAT_COPYRIGHT="Copyright (C) Red Hat, Inc. 2004-2010 All rights reserved.";
#END_VERSION_GENERATION

# Get the program name from $0 and strip directory names
$_=$0;
s/.*\///;
my $pname = $_;


sub rpc_error 
{
	if (defined $error_message && $error_message ne "")
	{
		chomp $error_message;
		die "$error_message\n";
	}
	else
	{
		die "read timed-out\n"
	}
}

sub usage 
{

    print "Usage:\n";
    print "\n";
    print "$pname [options]\n";
    print "\n";
    print "Options:\n";
    print " -a host        host to connect to\n";
    print " -D             debugging output\n";
    print " -h             usage\n";
    print " -l string      user name\n";
    print " -o string      action: on,off,status or reboot (default)\n";
    print " -n string      outlet name\n";
    print " -p string      password\n";
    print " -S path        script to run to retrieve password\n";
    print " -V             version\n";

    exit 0;
}

sub fail
{
  ($msg)=@_;
  print $msg."\n" unless defined $quiet;
  $t->close if defined $t;
  exit 1;
}

sub fail_usage
{
  ($msg)=@_;
  print STDERR $msg."\n" if $msg;
  print STDERR "Please use '-h' for usage.\n";
  exit 1;
}

sub version
{
  print "$pname $RELEASE_VERSION $BUILD_DATE\n";
  print "$REDHAT_COPYRIGHT\n" if ( $REDHAT_COPYRIGHT );

  exit 0;
}

# Get operating paramters, either with getopts or from STDIN
sub get_options
{
   $action = "Reboot";
   if (@ARGV > 0) {
      getopts("n:l:p:S:o:a:VhD") || fail_usage ;

      usage if defined $opt_h;
      version if defined $opt_V;

      fail_usage "Unkown parameter." if (@ARGV > 0);

   } else {
      get_options_stdin();
   } 

   fail "failed: must specify hostname" unless defined $opt_a;
   $host=$opt_a;
   $port=23 unless ($opt_a =~ /:/);

   $action = $opt_o if defined $opt_o;
   fail "failed: unrecognised action: $action"
         unless $action=~ /^(Off|On|Reboot|status)$/i;
   
   fail "failed: no outletname" unless defined $opt_n;
   $outlet = $opt_n;

   $debug=$opt_D if defined $opt_D;
   $quiet=$opt_q if defined $opt_q;
   $user=$opt_l if defined $opt_l;
   $passwd=$opt_p if defined $opt_p;
   if (defined $opt_S) {
     $pwd_script_out = `$opt_S`;
     chomp($pwd_script_out);
     if ($pwd_script_out) {
       $passwd=$pwd_script_out;
     }
   }

   if(defined $passwd && !defined $user)
   {
      fail "failed: password given without username";
   }
}

# Get options from STDIN
sub get_options_stdin
{
    my $opt;
    my $line = 0;
    while( defined($in = <>) )
    {
	$_ = $in;
        chomp;

	# strip leading and trailing whitespace
        s/^\s*//;
        s/\s*$//;

	# skip comments
	next if /^#/;

        $line+=1;
        $opt=$_;
	next unless $opt;

	($name,$val)=split /\s*=\s*/, $opt;

	if ( $name eq "" )
	{
	   print STDERR "parse error: illegal name in option $line\n";
	   exit 2;
	} 

        # DO NOTHING -- this field is used by fenced 
	elsif ($name eq "agent" ) { } 

	elsif ($name eq "host" ) 
	{
	    $opt_a = $val;
	} 

	elsif ($name eq "login" ) 
	{
	    $opt_l = $val;
	} 

	elsif ($name eq "passwd" ) 
	{
	    $opt_p = $val;
	} 

    elsif ($name eq "passwd_script") {
        $opt_S = $val;
    }

	elsif ($name eq "action" ) 
	{
	    $opt_o = $val;
	} 

	elsif ($name eq "outlet" ) 
	{
	    $opt_n = $val;
	} 

    }
}

# Get a bunch of lines.  The newlines must terminate complete lines.
sub getlines
{
	my $data=$t->get();
	return undef unless defined $data;
	my @chars = split //,$data;
	my @lines;
	my $line="";

	for (my $i=0;$i<@chars;$i++)
	{
		$line = $line.$chars[$i];
		next unless $chars[$i] eq "\n";
		$lines[@lines] = $line;
		$line = "";
	}
	$lines[@lines] = $line unless $line eq "";

	return @lines;
}

# Fill the global input buffer of lines read.  All lines are terminated with
# a newline.  If a line is not terminated, the next call to fill buffer will
# append the last line of the input buffer with the first line that it gets from
# getlines()
sub fill_buffer
{
	my @lines = getlines();
	return undef unless @lines;

	if(@buffer)
	{
		if ( $buffer[$#buffer]=~/\n/) { }
		else
		{
			$buffer[$#buffer] = $buffer[$#buffer].$lines[0];
			shift @lines;
		}
	}

	foreach (@lines) 
	{ 
		push @buffer,$_;
	}
}



#
# ($p_index,@data) = get_match @patterns;
#
# searches the input buffers for the patterns specified by the regeps in 
# @patterns, when a match is found, all the lines through the matched 
# pattern line are removed from the global input buffer and returned in the
# array @data.  The index into @patterns for the matching pattern is also
# returned.
sub get_match
{
	my (@patterns) = @_;
	$b_index = 0 unless defined $b_index;

	fill_buffer() unless defined @buffer;

	for(;;)
	{
		for(my $bi=$b_index; $bi<@buffer; $bi++)
		{
			for(my $pat=0; $pat<@patterns; $pat++)
			{
				if($buffer[$bi] =~ /$patterns[$pat]/)
				{
					$b_index = 0;
					my @rtrn = splice(@buffer,0,$bi);
					shift @buffer;
				
					if($debug)
					{
						foreach (@rtrn) { print $_ }
						print "$patterns[$pat] ";
					}
					
					return ($pat,@rtrn);
				}
			}
			$b_index = $bi;
		}

		fill_buffer();
	}
}

#
# ($bt_num,$bt_name,$bt_state,$bt_locked) = parse_status $outlet,@data;
#
# This parses the data @data and searches for an outlet named $outlet.
# The data will be in the form:
# 
#   Average Power:    0 Watts        Apparent Power:   17 VA
# 
#   True RMS Voltage: 120.0 Volts
# 
#   True RMS Current:   0.1 Amps     Maximum Detected:   0.2 Amps     
# 
#   Internal Temperature:  19.5 C
# 
#   Outlet Circuit Breaker: Good
# 
#    1)...Outlet  1       : Off           2)...Outlet  2       : Off          
#    3)...Outlet  3       : On            4)...Outlet  4       : On           
#    5)...Outlet  5       : On            6)...Outlet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On           
#    5)...Outet  6 On      