Re: (PM) Ugly (but handy) debugging tool (fwd)

Curtis Coleman (curtis.lst.portmaster.users@imap.pangea.ca)
Wed, 29 Jul 1998 01:47:21 -0700 (PDT)

On Tue, 28 Jul 1998, MegaZone wrote:

> Once upon a time Curtis Coleman shaped the electrons to say...
> >to work around what I consider a flaw in the way PortMasters do debugging.
> >Specifically in that there is only one current console, one set of debug
> >flags, lacklustre syslog & snmp support, etc.
>
> The new debug settings (ie, non-hex) are independent. And I believe multiple
> PMVision sessions cna watch different debugs simultaneously, but I'm not
> sure on that.
>
> The need to have multiple admin users debugging at the same time has never
> been seen as a major demand.

At least in my situation, having multiple support staff debugging
concurrently is a much wanted feature. I've written a solution that
connects to n portmasters and allows n support staff to view the debug
stream, but it's incomplete. Though it's currently in working form, it's
functionality is quite limited in that it doesn't yet allow for those
viewing the stream to filter out just the message types they are
interested in. Is also suffers the same problem that my tele.pl does, and
will infact conflict with it, in that it obtains it's information from
telnetting to the PMs. I've attached it below for those who are
interested.

> I don't think the syslog support is lacklustre, especially in recent releases.
> If you have a complaint, be specific. What is missing that you want to see?
> Syslog already covers all the major events, logs commands, allows different
> levels to be set, etc.

I haven't seen any syslog options that approach the level of information
available from the "set debug" commandline. Has there been new additions
above: admin-logins, user-logins, packet-filters, commands, and
termination? The obvious solution would be to allow debugging to be sent
to syslog, sort of like Cisco IOS and it's "logging trap debugging" &
"logging x.x.x.x" commands. Allowing debug to be sent to a syslog host
would remove the reliability problems (such as another terminal session
resetting console) that collecting the information via telnet has
currently.

You asked for a specific complaint, so I guess mine would be that none of
"set debug isdn on", "set debug isdn-d on", "set debug 0x51", nor "set
debug mdp-max on" or any reasonable equivelent can be directed to a syslog
host. These were the debug settings required to associate the calling
station to the Dn/Bn chosen, and to Sn and Mn for complete capture of
sessions.

> SNMP is solid, but incomplete. That is well known, they've been adding to
> SNMP incrimentally in every release for a while now. The 3.8 betas again have
> additional support, with more to come in later revs. Just look at the
> increased support as it evolved from 3.3.x to 3.7.x. I don't think you can
> claim this is something they are not actively addressing.

I appreciate the work being done with the SNMP implementation. It is
suitable for some statistics gathering, and trapping for very significant
events. My only real beef is that Calling-Station-Id and
Called-Station-Id are unavailable, but I do appreciate the availability of
the Acct-Session-Id for consistancy checking of accounting records.
Called-Station-Id availablity via SNMP would be particularily pleasing to
me, as I have two DNs pointed at our pool of PM3s, and offer different
levels of service depending on the DN dialed. I've patched radiusd to
maintain a database associating host/port with current dialed DN as a
solution, but that's just messy.

Here is the my lousy solution to the single debug stream:

-------------------debugd.cfg---------------
# Debugd configuration file
#
#
# Default host setup

# login info
HostLogin(pm3-1.foobar.com, pm3-2.foobar.com, pm3-3.foobar.com) = (ogin:, !root, word:, passwordhere)
# portmasters initially connected to, others with HostLogin set above can
# be connected later using "connect <host>"
HostConnected(pm3-1.foobar.com, pm3-2.foobar.com) = (1)

#
#
# Daemon config properties

# the port support staff telnets into
Config(port) = (9038)
# hi mom!
Config(welcome) = (,Pangea Debugd - version 0.00,)

#
#
# Users

# setup users you want to be able to use debugd here
ClientPassword(joetech) = (joepwd)
ClientCommands(joetech) = (user,debug)

#
#
# Command list for partial expansion

Command(_) = (help, set, send, connect, disconnect, show, exit)
Command(_set_) = (console, <host>)
Command(_set_console_) = (on, off)
Command(_set_<host>_) = (console, debug)
Command(_set_<host>_console_) = (on, off)
Command(_set_<host>_debug_) = (0x51, isdn, isdn-d, mdp-max, off)
Command(_set_<host>_debug_0x51_) = (on, off)
Command(_set_<host>_debug_isdn_) = (on, off)
Command(_set_<host>_debug_isdn-d_) = (on, off)
Command(_set_<host>_debug_mdp-max_) = (on, off)
Command(_send_) = (<filehandle>)
Command(_connect_) = (<host>)
Command(_show_) = (filehandles)
--------------end debugd.cfg---------------

-----------------debugd.pl-----------------
#!/usr/bin/perl

# $DEBUG = 1;

$|=1;

use Socket;
use Symbol;

$SIG{INT} = \&shutdown;
$SIG{PIPE} = \&handlesigpipe;

&loadconfig();
#&showconfig();
&connectinit();
&connectdefaulthosts();
&initconsole();

while (1) {
$rin = fhbits((values %FileHandle));
$nfound = select($rout=$rin, undef, undef, 60);
if ($nfound != 0) {
foreach $one (keys %FileHandle) {
if (vec($rout, fileno($FileHandle{$one}), 1)) {
$CURRENT = $one;
&{$FileHandleFunc{$one}};
}
}
}
}

sub username {
&doread();
if ($FileBuffer{$CURRENT} =~ /^.*\n$/) {
chop($FileBuffer{$CURRENT});
chop($FileBuffer{$CURRENT});
if ($FileBuffer{$CURRENT} ne "") {
$ClientUsername{$CURRENT} = $FileBuffer{$CURRENT};
$FileBuffer{$CURRENT} = "";
fhwrite($CURRENT, "Password: ");
$FileHandleFunc{$CURRENT} = \&password;
$FileHandleStat{$CURRENT} = "password";
} else {
fhwrite($CURRENT, "Login: ");
}
}
}

sub password {
&doread();
if ($FileBuffer{$CURRENT} =~ /^.*\n$/) {
chop($FileBuffer{$CURRENT});
chop($FileBuffer{$CURRENT});
if (${$ClientPassword{$ClientUsername{$CURRENT}}}[0] ne $FileBuffer{$CURRENT}) {
fhwrite($CURRENT, "Failed!\n\n");
$FileHandleFunc{$CURRENT} = \&username;
$FileHandleStat{$CURRENT} = "username";
fhwrite($CURRENT, "Login: ");
} else {
fhwrite($CURRENT, "Command> ");
$FileHandleFunc{$CURRENT} = \&shell;
$FileHandleStat{$CURRENT} = "shell";
}
$FileBuffer{$CURRENT} = "";
}
}

sub shellhelp {
foreach $one (@{$ClientCommands{$ClientUsername{$CURRENT}}}) {
if ($one =~ /^user$/i) {
fhwrite($CURRENT, "set console on - (user) Start displaying debug\n");
fhwrite($CURRENT, "set console off - (user) Stop displaying debug\n");
fhwrite($CURRENT, "exit - (user) Exit debugd\n");
} elsif ($one =~ /^debug$/i) {
fhwrite($CURRENT, "set <host/all> console on - (admin) Start debug on host(s)\n");
fhwrite($CURRENT, "set <host/all> console off - (admin) Stop debug on host(s)\n");
fhwrite($CURRENT, "set <host/all> debug 0x51 on - (admin) PPP/LCP debugging\n");
fhwrite($CURRENT, "set <host/all> debug isdn on - (admin) ISDN debugging\n");
fhwrite($CURRENT, "set <host/all> debug isdn-d on - (admin) ISDN D-channel debugging\n");
fhwrite($CURRENT, "set <host/all> debug mdp-max on - (admin) modem debugging\n");
fhwrite($CURRENT, "set <host/all> debug off - (admin) turn off all debugging\n");
fhwrite($CURRENT, "send <filehandle> <message> - (admin) send message\n");
fhwrite($CURRENT, "connect <host> - (admin) connect new host\n");
fhwrite($CURRENT, "disconnect <host> - (admin) disconnect host\n");
fhwrite($CURRENT, "show filehandles - (admin) show filehandles open on debugd\n");
}
}
}

sub shell {
&doread();
if ($FileBuffer{$CURRENT} =~ /^.*\n$/) {
chop($FileBuffer{$CURRENT});
chop($FileBuffer{$CURRENT});
$command = $FileBuffer{$CURRENT};
$FileBuffer{$CURRENT} = "";

# check command consistancy

$realcommand = "_"; $sortarealcommand = "_"; $diecommand=0;
cmdloop: foreach $one (split(/ +/, $command)) {
$matchcount = 0;
foreach $two (@{$Command{$sortarealcommand}}) {
if ($two =~ /^<host>$/) {
if ($one =~ /^[a-zA-Z0-9\-\.]+$/) {
if ($matchcount==0) {
$realcommand.= $one . "_";
$sortarealcommand.= $two . "_";
$matchcount++;
}
}
} elsif ($two =~ /^$one/) {
if ($matchcount==0) {
$realcommand.= $two . "_";
$sortarealcommand.= $two . "_";
$matchcount++;
}
}
}
if ($matchcount!=1) {
$realcommand =~ s/_/ /g;
fhwrite($CURRENT, "Ambiguous command:$realcommand$one\n");
$diecommand=1;
last cmdloop;
}
}
if ($diecommand == 0) {
$realcommand =~ s/_/ /g;
$realcommand =~ s/^ +//g;
$realcommand =~ s/ +$//g;
if ($realcommand ne $command) {
fhwrite($CURRENT, "expanded to: $realcommand\n");
$command = $realcommand;
}
for ($i=0; $i<=$#{$ClientCommands{$ClientUsername{$CURRENT}}}; $i++) {
if (${$ClientCommands{$ClientUsername{$CURRENT}}}[$i] =~ /^user$/i) {
if ($command =~ /^help$/i) {
&shellhelp();
} elsif ($command =~ /^set console on$/i) {
$ClientDebug{$CURRENT} = 1;
fhwrite($CURRENT,"Setting CONSOLE to admin session\n");
} elsif ($command =~ /^set console off$/i) {
$ClientDebug{$CURRENT} = 0;
fhwrite($CURRENT,"Console RESET\n");
} elsif ($command =~ /^show filehandles$/i) {
foreach $one (keys %FileHandle) {
if ($CURRENT eq $one) {
fhwrite($CURRENT,sprintf("%-24s $FileHandleStat{$one}\n", "$one (you)"));
} else {
fhwrite($CURRENT,sprintf("%-24s $FileHandleStat{$one}\n", "$one"));
}
}
} elsif ($command =~ /^exit$/) {
close($FileHandle{$CURRENT});
delete $FileHandle{$CURRENT};
}
} elsif (${$ClientCommands{$ClientUsername{$CURRENT}}}[$i] =~ /^debug$/i) {
if ($command =~ /^set ([A-Za-z0-9\-\.]+) debug 0x51$/i) {
if ($1 =~ /^all$/) {
foreach $one (keys %HostConnected) {
fhwrite($one,"set debug 0x51\n");
}
} else {
fhwrite($1,"set debug 0x51\n");
}
} elsif ($command =~ /^set ([A-Za-z0-9\-\.]+) debug isdn on$/i) {
if ($1 =~ /^all$/) {
foreach $one (keys %HostConnected) {
fhwrite($one,"set debug isdn on\n");
}
} else {
fhwrite($1,"set debug isdn on\n");
}
} elsif ($command =~ /^set ([A-Za-z0-9\-\.]+) debug isdn-d on$/i) {
if ($1 =~ /^all$/) {
foreach $one (keys %HostConnected) {
fhwrite($one,"set debug isdn-d on\n");
}
} else {
fhwrite($1,"set debug isdn-d on\n");
}
} elsif ($command =~ /^set ([A-Za-z0-9\-\.]+) debug mdp-max on$/i) {
if ($1 =~ /^all$/) {
foreach $one (keys %HostConnected) {
fhwrite($one,"set debug mdp-max on\n");
}
} else {
fhwrite($1,"set debug mdp-max on\n");
}
} elsif ($command =~ /^set ([A-Za-z0-9\-\.]+) debug off$/i) {
if ($1 =~ /^all$/) {
foreach $one (keys %HostConnected) {
fhwrite($one,"set debug off\n");
}
} else {
fhwrite($1,"set debug off\n");
}
} elsif ($command =~ /^set ([A-Za-z0-9\-\.]+) console on$/i) {
if ($1 =~ /^all$/) {
foreach $one (keys %HostConnected) {
fhwrite($one,"set console\n");
}
} else {
fhwrite($1,"set console\n");
}
} elsif ($command =~ /^set ([A-Za-z0-9\-\.]+) console off$/i) {
if ($1 =~ /^all$/) {
foreach $one (keys %HostConnected) {
fhwrite($one,"reset console\n");
}
} else {
fhwrite($1,"reset console\n");
}
} elsif ($command =~ /^send ([^ ]+) (.*)$/i) {
if ($1 =~ /^all$/) {
foreach $one (keys %HostConnected) {
fhwrite($one, "$2\n");
}
} else {
fhwrite($1,"$2\n");
}
} elsif ($command =~ /^connect ([^ ]+)$/i) {
if (${$HostConnected{$1}}[0] == 1) {
fhwrite($CURRENT, "Host already connected\n");
} else {
if ($HostDefined{$1} == 1) {
my @args = (1);
$HostConnected{$1} = \@args;
&connecttohost($1);
} else {
fhwrite($CURRENT, "No login information for host available for host $1\n");
}
}
} elsif ($command =~ /^disconnect ([^ ]+)$/i) {
if (${$HostConnected{$1}}[0] != 1) {
fhwrite($CURRENT, "Host not connected\n");
} else {
delete $HostConnected{$1};
close($FileHandle{$1});
delete $FileHandle{$1};
}
}
} elsif ($command =~ /^$/) {
# this is okay
} else {
fhwrite($CURRENT,"Unknown command: $command\n");
}
}
}
fhwrite($CURRENT,"Command> ");
}
}

sub dock {
&doread();
foreach $four (split(/\n/,$FileBuffer{$CURRENT})) {
chop($four);
foreach $one (keys %ClientDebug) {
$OUTPUT = $one;
$four =~ s/^Command/\n/;
if ($DEBUG==0) {
fhwrite($one, "$CURRENT: $four\n");
} else { # This is not at all complete, and proved
# to be too CPU consuming to accomplish
if ($four =~ /^Received ([^ ]+) on port S(\d+) of (\d+) bytes/) {
if ($FileState{$CURRENT}) {
&dockparse();
}
$FileState{$CURRENT} = "R:$1:$2:$3";
} elsif ($four =~ /^Sending ([^ ]+) to port S(\d+) of (\d+) bytes/) {
if ($FileState{$CURRENT}) {
&dockparse();
}
$FileState{$CURRENT} = "S:$1:$2:$3";
} elsif ($four =~ /^[0-9a-f ]+$/) {
push @{$FileHex{$CURRENT}}, split(/ /, $four);
} else {
if ($FileState{$CURRENT}) {
&dockparse();
} else {
# print "$four\n";
}
}
}

}
# $FileBuffer{$CURRENT} = "";
}
$FileBuffer{$CURRENT} = "";
}

sub dockparse {
# Don't use this, it consumes way too much CPU time, and hence
# I haven't bothered decoding other protocols.

if ($FileState{$CURRENT} =~ /^R:PAP_AUTH_REQ:/) {
$i=0;
$mode=0;
$username="";
$password="";
foreach $one (@{$FileHex{$CURRENT}}) {
$char = unpack('c',pack('H2',$one));
if ($i==4) {
$len = $char;
$mode=1;
} elsif ($mode == 1 && $i<(5+$len)) {
$username .= chr($char);
} elsif ($mode == 1) {
$len += $char;
$mode=2;
} elsif ($mode == 2 && $i<(6+$len)) {
$password .= chr($char);
} elsif ($mode == 2) {
$mode=3;
}
$i++;
}
foreach $one (keys %ClientDebug) {
fhwrite($one, "user = $username, pass = $password\n");
}
print "user = $username, pass = $password\n";
} else {
foreach $one (@{$FileHex{$CURRENT}}) {
print "$CURRENT:$FileState{$CURRENT}: $one\n";
}
print "\n";
}
delete $FileState{$CURRENT};
delete $FileHex{$CURRENT};
}

sub process {
my($host) = @_;

breakout: {
if (/^ *$/) { last breakout; }
($docknum) = $host =~ /^dock(\d+)/;
$_ = $host;
if ($Debug{$CURRENT} =~ /^sending ([^:]+):([^:]+):([^:]+)$/) {
$portnice = sprintf("$docknum/%02d", $2);
if ($1 =~ /^PAP_AUTH_ACK$/i) {
if ($HEX[0] eq "02") {
fhwrite($OUTPUT, "$portnice: PAP_AUTH_ACK(");
for ($i=1; $i<=$#HEX; $i++) {
fhwrite($OUTPUT, pack("2h", $HEX[$i]));
}
fhwrite($OUTPUT, ")\n");
} else {
fhwrite($OUTPUT, "Wrong hexcode\n");
}
}
fhwrite($OUTPUT, "$portnice: sending $1\n");
} elsif ($Debug{$CURRENT} =~ /^receiving ([^:]+):([^:]+):([^:]+)$/) {
$portnice = sprintf("$docknum/%02d", $2);
if ($1 =~ /^PAP_AUTH_REQ$/i) {
if ($HEX[0] eq "01") {
fhwrite($OUTPUT, "$portnice: PAP_AUTH_REQ(");
for ($i=1; $i<=$#HEX; $i++) {
fhwrite($OUTPUT, pack("2h", $HEX[$i]));
}
fhwrite($OUTPUT, ")\n");
} else {
fhwrite($OUTPUT, "Wrong hexcode\n");
}

}
fhwrite($OUTPUT, "$portnice: receiving $1\n");
}
}
}

sub fhwrite {
my($fh, $line) = @_;

syswrite($FileHandle{$fh}, $line, length($line));
}

sub listening {
($port) = $CURRENT =~ /^listening-(\d+)/;
$client++;
$FileHandle{"client-$port-$client"} = gensym();
$FileHandleFunc{"client-$port-$client"} = \&username;
$FileHandleStat{"client-$port-$client"} = "username";
accept($FileHandle{"client-$port-$client"}, $FileHandle{$CURRENT});
foreach $one (@{$Config{"welcome"}}) {
$one.="\n";
syswrite($FileHandle{"client-$port-$client"}, $one, length($one));
}
syswrite($FileHandle{"client-$port-$client"}, "Login: ", 7);
}

sub doread {
my($bufstring);
my($gotnum);

$gotnum = sysread($FileHandle{$CURRENT}, $bufstring, 256);
if ($gotnum == 0) {
&shutdown();
} else {
$FileBuffer{$CURRENT} .= $bufstring;
}
}

sub initconsole {
foreach $one (@{$Config{"port"}}) {
my $this = pack('S n a4 x8', AF_INET, $one, $thisaddr);
$FileHandle{"listening-$one"} = gensym();
$FileHandleFunc{"listening-$one"} = \&listening;
$FileHandleStat{"listening-$one"} = "listening";
socket($FileHandle{"listening-$one"}, AF_INET, SOCK_STREAM, 6) or die $!;
bind($FileHandle{"listening-$one"}, $this) or die $!;
listen($FileHandle{"listening-$one"},5) or die $!;
}
}

sub connectinit {
chop($localhost = `hostname`);
($name,$aliases,$type,$len,$thisaddr) = gethostbyname($localhost);
my $this = pack('S n a4 x8', AF_INET, 0, $thisaddr);
}

sub connectdefaulthosts {
foreach $one (keys %HostConnected) {
if (${$HostConnected{$one}}[0] == 1) {
&connecttohost($one);
}
}
}

sub connecttohost {
my($host) = shift;

($name,$aliases,$type,$len,$thataddr) = gethostbyname($host);
$FileHandle{$host} = gensym();
$FileHandleFunc{$host} = \&dock;
$FileHandleStat{$host} = "dock";
my $this = pack('S n a4 x8', AF_INET, $one, $thisaddr);
$that = pack('S n a4 x8', AF_INET, 23, $thataddr);
socket($FileHandle{$host}, AF_INET, SOCK_STREAM, 6) or die $!;
bind($FileHandle{$host}, $this) or die $!;
connect($FileHandle{$host}, $that) or die $!;

for ($i=0; $i<=$#{$HostLogin{$host}}; $i+=2) {
$buf = "";
while ($buf !~ /${$HostLogin{$host}}[$i]/) {
sysread($FileHandle{$host}, $bufchar, 1);
$buf .= $bufchar;
}
$string = ${$HostLogin{$host}}[$i+1] . "\n";
syswrite($FileHandle{$host}, $string, length($string));
}
}

sub loadconfig {
my $i;
open(CFG, "<debugd.cfg");
loop: while (<CFG>) {
chomp;
if (/^[^\#]*?([A-Za-z0-9]+)[ \t]*\([ \t]*([^\)]+)[ \t]*\)[ \t]*=[ \t]*\([ \t]*([^\)]*?)[ \t]*\)/) {
$option = $1;
my @group = split(/ *, */,$2);
my @args = split(/ *, */,$3);

foreach $one (@group) {
if ($option =~ /^hostlogin$/i) {
$HostLogin{$one} = \@args;
$HostDefined{$one} = 1;
} elsif ($option =~ /^hostconnected$/i) {
$HostConnected{$one} = \@args;
$HostDefined{$one} = 1;
} elsif ($option =~ /^config$/i) {
$Config{$one} = \@args;
} elsif ($option =~ /^clientpassword$/i) {
$ClientPassword{$one} = \@args;
} elsif ($option =~ /^clientcommands$/i) {
$ClientCommands{$one} = \@args;
} elsif ($option =~ /^command$/i) {
$Command{$one} = \@args;
} else {
print "Unrecognized option $option\n";
}
}
}
}
close(CFG);
}

sub fhbits {
my @fhlist = @_;
my $bits;
for (@fhlist) {
vec($bits, fileno($_), 1) = 1;
}
return $bits;
}

sub shutdown {
print "Shutting down...\n";
foreach $one (keys %FileHandle) {
close($FileHandle{$one});
}
exit(0);
}

sub handlesigpipe {
print "Caught sigpipe... ";

print "unknown sigpipe!\n";
&shutdown;
}
------------end debugd.pl-------------

Curtis
-
To unsubscribe, email 'majordomo@livingston.com' with
'unsubscribe portmaster-users' in the body of the message.
Searchable list archive: <URL:http://www.livingston.com/Tech/archive/>