#!/usr/bin/perl
#
# Purpose: Dial the OOB on a cisco router
# and determine the OOB if the
# OOB line is up and functioning
# properly
#
# Code by Brad Lhotsky <brad@divisionbyzero.net>
# Based off code by jcwren on http://perlmonks.org
#
# Note: The modem (US Robotics 56k v.90 Fax Sportster)
# had no support for advanced error messages such
# as "ring no answer" and various other "higher
# level" error messages so I am using my own methods
# to report what error messages get generated.
#
$|++;
use strict; # always
use Device::SerialPort; # so we can communicat
+e with modem
use IPC::ShareLite; # for sharing memory
use POSIX "sys_wait_h"; # for flags for waitpi
+d
use Time::HiRes qw(gettimeofday); # for more error handl
+ing using times
use Time::localtime; # for logging date for
+mat crap.
use Getopt::Std; # to get command line
+options
###################
# Defaults, for my
# sanity more than
# anything else
my $DEF_DEV = '/dev/cua1'; # set this for fun
my $DEF_USR = ''; # should be a "read only" use
+r
my $DEF_PWD = ''; # see above
my $DEF_RTR = 3; # for sanity
my $DEF_TMO = 90; # for sanity
###################
# configs.
my %OPTS = (); # options hash
getopts("d:l:nr:t:u:p:hva", \%OPTS); # get options
help() if $OPTS{'h'};
my $DEVICE = $OPTS{'i'} || $DEF_DEV; # serial port ext. mod
+em is on
my $NOISEDIAL = $OPTS{'n'}; # Turn on/off the mode
+m speaker
my $BAUD = 9600; # our initial baud rat
+e
my $MINBAUD = 9600; # if we find a baud le
+ss than this, bitch
my $USERNAME = ($OPTS{'u'} || $DEF_USR) . "\n"; # username for the rou
+ter
my $PASSWORD = ($OPTS{'p'} || $DEF_PWD)."\n"; # password for the rou
+ter
my $MAX_RETRY = $OPTS{'r'} || $DEF_RTR; # number of times we r
+etry
my $LOG = $OPTS{'l'}; # log file
my $TIMEOUT = $OPTS{'t'} || $DEF_TMO; # how long before we d
+etermine failure
my $DIALPREFIX = 91; # to get an outside li
+ne
if($LOG) {
open LOG, ">>$LOG" or die "couldn't open debug.out: $!\n";
select(LOG); $|++; select('stdout');
}
###################
# Globals.
my %FAILED = ();
my @GOOD = ();
####################
# Modem commands
# that might be fun.
my $MODEM_HANGUP = "ATH1\n"; # modem hangup, clear the line
+.
my $MODEM_QUIET = "ATM0\n"; # silence the speaker
my $MODEM_SOUND = "ATM1\n"; # unsilence the speaker
####################
# assemble our list
# of modems to dial
my $LIST = $ARGV[0];
die "usage: $0 sitefile ($ -h for more info)\n" unless -e $LIST;
my %SITES = (); # Global sites hash.
open(CFG, "<$LIST") or die "could not open $LIST: $!\n";
my $TOTALNUM = 0;
while(local $_ = <CFG>) {
s/^\s+//; s/\s+$//;
next if !$_ || /^#/;
($_, undef) = split /\s*#\s*/;
next if !$_;
my ($site,$number) = split/\s*:\s*/;
next unless $site && $number;
next if exists $SITES{$site}; # no duplicates over writing c
+rap.
$SITES{$site} = $number;
$TOTALNUM++;
}
close CFG;
die "$LIST: empty configuration file!\n" unless $TOTALNUM;
logthis("SYSTEM","Loaded $TOTALNUM numbers to check!");
foreach my $site (sort keys %SITES) {
print "Testing $site ... " unless $OPTS{'v'};
my $share = new IPC::ShareLite( -key => 6969,
-create => 'yes',
-destroy => 'no',
-size => 1024) or die "Couldn'
+t share memory: $!\n";
my $SUCCESS = 0;
my $REASON = "";
# using this method of forking
# is the only way to "next" in a loop using
# alarm and trapping SIG_ALRM
# many thanks to mikfire for this idea
my $pid = fork();
if($pid == 0) {
# we're the child, do the grunt work
exit OOBtest($site,$SITES{$site});
} elsif(defined $pid) {
# wait for the child to finish
my $tmp = waitpid($pid,'');
# Success/failure returned through OOBtest()
# gets thrown in $? so lets check it.
$SUCCESS = $?;
} else {
die "fork error: $!\n";
}
if(!$SUCCESS) {
$REASON = $share->fetch() || "Unknown failure conditio
+n";
$FAILED{$site} = $REASON;
} else {
push @GOOD,$site;
}
print "done.\n" unless $OPTS{'v'};
} # EOForeach
for(sort keys %FAILED) { print "FAILED: $_ : " . $FAILED{$_} . "\n"; }
for(sort @GOOD) { print "GOOD: $_\n"; }
#################################
# here's where the work is done.
sub OOBtest {
my ($site,$number) = @_;
# we'll use this hash to determine whether
# or not we miss any steps along the way
my %STAGES = ( "connect" => 0,
"login" => 0,
"password" => 0,
"prompt" => 0
);
my @STAGES = ("connect", "login", "password", "prompt");
my $share = new IPC::ShareLite( -key => 6969,
-create => 'no',
-destroy => 'no',
-size => 1024) or die "couldn'
+t share memory: $!\n";
$share->store('NONE');
my $ob = new Device::SerialPort ($DEVICE, 1);
die "Couldn't access $DEVICE: $!\n" unless $ob;
$SIG{"ALRM"} = sub {
select undef, undef, undef, 0.5;
if($share->fetch ne 'NONE') {
my $newmsg = $share->fetch() .
+ ", Timeout after $TIMEOUT seconds";
$share->store($newmsg);
} else {
$share->store("Timeout after $
+TIMEOUT seconds");
}
logthis($site,"FATAL ERROR: Connection
+ Timedout after $TIMEOUT seconds");
$ob->close;
select undef, undef, undef, 0.5;
undef $ob;
select undef, undef, undef, 0.5;
exit 0;
};
alarm($TIMEOUT);
###################
# setup device
select undef, undef, undef, 0.5; # sleep for half a sec
+ond
$ob->baudrate($BAUD);
$ob->parity('none');
$ob->databits(8);
$ob->stopbits(1);
$ob->handshake('none');
$ob->stty_icrnl(1);
$ob->stty_ocrnl(1);
$ob->stty_onlcr(1);
$ob->stty_opost(1);
$ob->write_settings;
select undef, undef, undef, 0.5; # sleep for half a sec
+ond
$ob->write($MODEM_HANGUP); # clear the line
select undef, undef, undef, 1.5; # sleep for one and ha
+lf a second
my $buff = $ob->input;
if($buff =~ /OK/) {
logthis($site,"INIT: Modem hangup sent, line cleared."
+);
} else {
logthis($site,"INIT ERROR: Modem did not respond to ha
+ngup");
}
if(!$NOISEDIAL && !$OPTS{'a'}) {
$ob->write($MODEM_QUIET); # silence the
+modem
select undef, undef, undef, 1.5; # sleep for on
+e and half a second
my $buff = $ob->input;
if($buff =~ /OK/) {
logthis($site,"INIT: Modem sound off.");
} else {
logthis($site,"INIT ERROR: Modem did not respo
+nd to sound off");
}
} else {
$ob->write($MODEM_SOUND); # make some no
+ise
select undef, undef, undef, 1.5; # sleep for on
+e and half a second
my $buff = $ob->input;
if($buff =~ /OK/) {
logthis($site,"INIT: Modem sound on.");
} else {
logthis($site,"INIT ERROR: Modem did not respo
+nd to sound on");
}
}
select undef, undef, undef, 0.5; # sleep for half a sec
+ond
my $dial = $OPTS{'a'} ? "ATDP $DIALPREFIX$number\n" : "ATDT $D
+IALPREFIX$number\n";
logthis($site,"DIAL STR: $dial");
########################
# Setup the environment
my $RETRY = 0;
my $DONE = 0;
my $TOTALIN = 0;
my $TOTALOUT = 0;
my $SUCCESS = 0;
my $ERROR = "";
my $DIAL_TIME = gettimeofday;
$ob->write($dial);
select undef, undef, undef, 0.5; # sleep for half a sec
+ond
while(!$DONE) {
# this next bit of code is purely
# for my own amusement.
my ($inbytes,$outbytes) = (0,0);
(undef, $inbytes, $outbytes, undef) = $ob->status or
warn "could not get port status!\n";
$TOTALIN += $inbytes;
$TOTALOUT += $outbytes;
# end amusement
if(my $line = $ob->input) {
$line =~ s/[\r\n\cM]//mg;
if($line =~ /CONNECT\s+(\d+)/i) {
# We have initialized!
$STAGES{"connect"}++;
# Check other end's baudrate and
# adjust if need be.
if($1 != $BAUD) {
if($1 < $MINBAUD) {
$share->store("Baudrat
+e of $1 is less than minimum ($MINBAUD)");
}
logthis($site,"CONN: baudrate
+is now $1");
$ob->baudrate($1);
$ob->write_settings;
# pause momentarily
select undef, undef, undef, 1.
+2;
}
# now we have to "press enter a few ti
+mes"
$ob->write("\n\n");
} elsif($line =~ /(NO CARRIER)/i || $line =~ /
+(BUSY)/i) {
# we failed, keep trying!
my $err = $1;
if($RETRY > $MAX_RETRY) {
if($line =~ /(BUSY)/i) {
logthis($site,"DIAL ER
+ROR: Remote line busy");
$share->store("Line wa
+s busy");
} else {
# using the time betwe
+en dialing to determine
# local or remote lack
+ of a carrier
my $diff = gettimeofda
+y - $DIAL_TIME;
$diff *= 100;
logthis($site,"DIAL DI
+FF: $diff");
if($diff > 800) {
$share->store(
+"Local modem had no free line");
} else {
$share->store(
+"No answer from remote modem");
}
}
logthis($site,"FATAL ERROR: Re
+ached maximum retry for $site");
$DONE++;
last;
}
$share->store("line status $err (retry
+ $RETRY of $MAX_RETRY)");
logthis($site,"ERROR: line status $err
+ (retry $RETRY of $MAX_RETRY)");
$RETRY++;
my $diff = (gettimeofday - $DIAL_TIME)
+*100;
if($diff > 800) {
if($line =~ /BUSY/i) {
logthis($site,"DIAL ER
+ROR: Remote line was busy");
} else {
logthis($site,"DIAL ER
+ROR: Remote modem did not pickup");
}
} else {
if($line =~ /BUSY/i) {
logthis($site,"DIAL ER
+ROR: Remote line was busy");
} else {
logthis($site,"DIAL ER
+ROR: Local modem had no free line");
}
}
$ob->write($MODEM_HANGUP);
logthis($site,"REDIAL: clearing line")
+;
select undef, undef, undef, 1.5;
$DIAL_TIME = gettimeofday;
$ob->write($dial);
select undef, undef, undef, 0.5;
logthis($site,"REDIAL ($RETRY of $MAX_
+RETRY): dialing $number");
} elsif($line =~ /sername\:/i || $line =~ /ogi
+n\:/i) {
# got login prompt
$STAGES{"login"}++;
# attempt to login
$ob->write($USERNAME);
logthis($site,"CONN: sent username");
} elsif($line =~ /assword\:/i) {
# password prompt
$STAGES{"password"}++;
$ob->write($PASSWORD);
logthis($site,"CONN: sent password");
} elsif($line =~ /[\w().-]*[\$#>]/) {
# prompt
# which is all we were looking for!
$STAGES{"prompt"}++;
logthis($site,"CONN: Got to our prompt
+!");
$DONE++;
last;
}
}
select undef, undef, undef, 0.5; # sleep for ha
+lf a second.
}
logthis($site,"Closing serial port.");
sleep 1;
$ob->close;
sleep 1;
undef $ob;
logthis($site,"Total bytes transferred: $TOTALIN (in), $TOTALO
+UT (out)","Session closed.");
my $stageError = 0;
for(keys %STAGES) { $stageError++ if !$STAGES{$_}; }
if($stageError) {
my $logmsg = "FAILED CONNECTION STAGES: ";
if($share->fetch eq 'NONE') {
$share->store("FAILED CONNECTION STAGES: ");
} else {
my $new = $share->fetch() . ", FAILED CONNECTI
+ON STAGES:";
$share->store($new);
}
foreach my $stage (@STAGES) {
if(!$STAGES{$stage}) {
$logmsg .= " $stage";
my $new = $share->fetch() . " $stage";
$share->store($new);
}
}
logthis($site,$logmsg);
return 0;
}
return 1;
}
sub logthis {
my $site = shift;
my @msgs = @_;
my $date = ctime();
foreach my $msg (@msgs) {
$msg =~ s/[\r\n\cM]//g;
$LOG && print LOG "$date - $site - $msg\n";
print "$date - $site - $msg\n" if $OPTS{'v'};
}
}
sub help {
print <<" EOHELP";
OOB Test Dialer v0.1
--------------------
Code by:
Brad Lhotsky <brad\@divisionbyzero.net>
Distributed under the same license as perl itself,
use at your own risk, and fight UCITA.
usage: $0 [-anhv] [-l logfile] [-r maxretry] [-d device] \
[-t timeout (in seconds)] [-u username] [-p password]
+\
filewithnumbers
options:
h - display this message
d <device> - interface, typically /dev/cuaN of /dev/tt
+ySN
where N is the port number.
default: $DEF_DEV
l <logfile> - file to append information to
default: off
v - display all log info to STDOUT (can be used with -
+l)
default: off
n - noisy dialing (turn modem speaker on)
default: off
a - annoying (enable pulsa dialing)
default: off
t <timeout> - timeout in seconds
default: $DEF_TMO
r <number> - number of retries per number
default: $DEF_RTR
u <username> - username to login
p <password> - password to login
EOHELP
exit 0;
}
|