@ISA = qw(DXChannel);
+use 5.10.1;
+
use POSIX qw(:math_h);
use DXUtil;
use DXChannel;
use Sun;
use Internet;
use Script;
-use Net::Telnet;
use QSL;
use DB_File;
use VE7CC;
use DXXml;
+use AsyncMsg;
+use JSON;
+use Time::HiRes qw(gettimeofday tv_interval);
+
+use Mojo::IOLoop;
+use DXSubprocess;
+use Mojo::UserAgent;
use strict;
use vars qw(%Cache %cmd_cache $errstr %aliases $scriptbase %nothereslug
- $maxbadcount $msgpolltime $default_pagelth $cmdimportdir);
+ $maxbadcount $msgpolltime $default_pagelth $cmdimportdir $users $maxusers);
%Cache = (); # cache of dynamically loaded routine's mod times
%cmd_cache = (); # cache of short names
$msgpolltime = 3600; # the time between polls for new messages
$cmdimportdir = "$main::root/cmd_import"; # the base directory for importing command scripts
# this does not exist as default, you need to create it manually
- #
+$users = 0; # no of users on this node currently
+$maxusers = 0; # max no users on this node for this run
#
# obtain a new connection this is derived from dxchannel
my $pkg = shift;
my $call = shift;
# my @rout = $main::routeroot->add_user($call, Route::here(1));
- DXProt::_add_thingy($main::routeroot, [$call, 0, 0, 1, undef, undef, $self->{conn}->peerhost], );
+ DXProt::_add_thingy($main::routeroot, [$call, 0, 0, 1, undef, undef, $self->hostname], );
# ALWAYS output the user
my $ref = Route::User::get($call);
my $host = $self->{conn}->peerhost;
$host ||= "AGW Port #$self->{conn}->{agwport}" if exists $self->{conn}->{agwport};
$host ||= "unknown";
- LogDbg('DXCommand', "$call connected from $host");
+ $self->{hostname} = $host;
$self->{name} = $name ? $name : $call;
$self->send($self->msg('l2',$self->{name}));
$pagelth = $default_pagelth unless defined $pagelth;
$self->{pagelth} = $pagelth;
($self->{width}) = $line =~ /width=(\d+)/; $line =~ s/\s*width=\d+\s*//;
+ if ($line =~ /host=/) {
+ my ($h) = $line =~ /host=(\d+\.\d+\.\d+\.\d+)/;
+ $line =~ s/\s*host=\d+\.\d+\.\d+\.\d+// if $h;
+ unless ($h) {
+ ($h) = $line =~ /host=([\da..fA..F:]+)/;
+ $line =~ s/\s*host=[\da..fA..F:]+// if $h;
+ }
+ $self->{hostname} = $h if $h;
+ }
$self->{width} = 80 unless $self->{width} && $self->{width} > 80;
$self->{consort} = $line; # save the connection type
-
+
+ LogDbg('DXCommand', "$call connected from $self->{hostname}");
+
# set some necessary flags on the user if they are connecting
$self->{beep} = $user->wantbeep;
$self->{ann} = $user->wantann;
$self->{dx} = $user->wantdx;
$self->{logininfo} = $user->wantlogininfo;
$self->{ann_talk} = $user->wantann_talk;
+ $self->{wantrbn} = $user->wantrbn;
$self->{here} = 1;
$self->{prompt} = $user->prompt if $user->prompt;
+ $self->{lastmsgpoll} = 0;
# sort out new dx spot stuff
$user->wantdxcq(0) unless defined $user->{wantdxcq};
$self->send_motd;
# sort out privilege reduction
- $self->{priv} = 0 if $line =~ /^(ax|te)/ && !$self->conn->{usedpasswd};
+ $self->{priv} = 0 unless $self->{hostname} eq '127.0.0.1' || $self->{hostname} eq '::1' || $self->conn->{usedpasswd};
# get the filters
my $nossid = $call;
$self->{annfilter} = Filter::read_in('ann', $call, 0)
|| Filter::read_in('ann', $nossid, 0)
|| Filter::read_in('ann', 'user_default', 0) ;
-
+ $self->{rbnfilter} = Filter::read_in('rbn', $call, 0)
+ || Filter::read_in('rbn', $nossid, 0)
+ || Filter::read_in('rbn', 'user_default', 0);
+
# clean up qra locators
my $qra = $user->qra;
$qra = undef if ($qra && !DXBearing::is_qra($qra));
$script->run($self) if $script;
# send cluster info
- my $info = Route::cluster();
- $self->send("Cluster:$info");
+ $self->send($self->run_cmd("show/cluster"));
# send prompts for qth, name and things
$self->send($self->msg('namee1')) if !$user->name;
}
#
-# this is the thing that runs the command, it is done like this for the
+# this is the thing that preps for running the command, it is done like this for the
# benefit of remote command execution
#
# check cmd
if ($cmd =~ m|^/| || $cmd =~ m|[^-?\w/]|) {
- LogDbg('DXCommand', "cmd: invalid characters in '$cmd'");
+ LogDbg('DXCommand', "cmd: $self->{call} - invalid characters in '$cmd'");
return $self->_error_out('e1');
}
my $package = find_cmd_name($path, $fcmd);
return ($@) if $@;
- if ($package && DXCommandmode->can($package)) {
+ if ($package && $self->can("${package}::handle")) {
no strict 'refs';
dbg("cmd: package $package") if isdbg('command');
- eval { @ans = &$package($self, $args) };
- return (DXDebug::shortmess($@)) if $@;
+ my $t0 = [gettimeofday];
+ eval { @ans = &{"${package}::handle"}($self, $args) };
+ if ($@) {
+ DXDebug::dbgprintring(25);
+ return (DXDebug::shortmess($@));
+ }
+ if (isdbg('progress')) {
+ my $msecs = _diffms($t0);
+ my $s = "CMD: '$cmd $args' by $call ip: $self->{hostname} ${msecs}mS";
+ dbg($s) if $cmd !~ /^(?:echo|blank)/ || isdbg('echo'); # cut down a bit on HRD and other clients' noise
+ }
} else {
dbg("cmd: $package not present") if isdbg('command');
return $self->_error_out('e1');
my $t = time;
my @dxchan = DXChannel::get_all();
my $dxchan;
-
+
+ $users = 0;
foreach $dxchan (@dxchan) {
- next if $dxchan->sort ne 'U';
+ next unless $dxchan->is_user;
# send a outstanding message prompt if required
if ($t >= $dxchan->lastmsgpoll + $msgpolltime) {
$dxchan->prompt() if $dxchan->{state} =~ /^prompt/o;
$dxchan->t($t);
}
+ ++$users;
+ $maxusers = $users if $users > $maxusers;
}
while (my ($k, $v) = each %nothereslug) {
my $s = shift; # the line to be rebroadcast
foreach my $dxchan (DXChannel::get_all()) {
- next unless $dxchan->{sort} eq 'U'; # only interested in user channels
+ next unless $dxchan->is_user; # only interested in user channels
next if grep $dxchan == $_, @_;
$dxchan->send($s); # send it
}
# gimme all the users
sub get_all
{
- return grep {$_->{sort} eq 'U'} DXChannel::get_all();
+ goto &DXChannel::get_all_users;
}
# run a script for this user
{
no strict 'refs';
- for (keys %Cache) {
- undef *{$_} unless /cmd_cache/;
- dbg("Undefining cmd $_") if isdbg('command');
+ for my $k (keys %Cache) {
+ unless ($k =~ /cmd_cache/) {
+ dbg("Undefining cmd $k") if isdbg('command');
+ undef $DXCommandmode::{"${k}::"};
+ }
}
%cmd_cache = ();
- %Cache = ();
+ %Cache = ( cmd_clear_cmd_cache => $Cache{cmd_clear_cmd_cache} );
}
#
#
# This has been nicked directly from the perlembed pages
#
-
#require Devel::Symdump;
sub valid_package_name {
- my($string) = @_;
+ my $string = shift;
$string =~ s|([^A-Za-z0-9_/])|sprintf("_%2x",unpack("C",$1))|eg;
$string =~ s|/|_|g;
return undef;
}
- if(defined $Cache{$package}->{mtime} &&$Cache{$package}->{mtime} <= $mtime) {
+ if(exists $Cache{$package} && exists $Cache{$package}->{mtime} && $Cache{$package}->{mtime} <= $mtime) {
#we have compiled this subroutine already,
#it has not been updated on disk, nothing left to do
#print STDERR "already compiled $package->handler\n";
- ;
+ dbg("find_cmd_name: $package cached") if isdbg('command');
} else {
my $sub = readfilestr($filename);
};
#wrap the code into a subroutine inside our unique package
- my $eval = qq( sub $package { $sub } );
+ my $eval = qq(package DXCommandmode::$package; use 5.10.1; use POSIX qw{:math_h}; use DXLog; use DXDebug; use DXUser; use DXUtil; our \@ISA = qw{DXCommandmode}; );
+
+
+ if ($sub =~ m|\s*sub\s+handle\n|) {
+ $eval .= $sub;
+ } else {
+ $eval .= qq(sub handle { $sub });
+ }
if (isdbg('eval')) {
my @list = split /\n/, $eval;
if (exists $Cache{$package}) {
dbg("find_cmd_name: Redefining $package") if isdbg('command');
- undef *$package;
+ undef $DXCommandmode::{"${package}::"};
+ delete $Cache{$package};
} else {
dbg("find_cmd_name: Defining $package") if isdbg('command');
}
eval $eval;
$Cache{$package} = {mtime => $mtime } unless $@;
-
}
- return $package;
+ return "DXCommandmode::$package";
}
sub send
$buf = dd(['ann', $to, $target, $text, @_])
} else {
$buf = "$to$target de $_[0]: $text";
- $buf =~ s/\%5E/^/g;
+ #$buf =~ s/\%5E/^/g;
$buf .= "\a\a" if $self->{beep};
}
$self->local_send($target eq 'WX' ? 'W' : 'N', $buf);
$buf = dd(['chat', $to, $target, $text, @_])
} else {
$buf = "$target de $_[0]: $text";
- $buf =~ s/\%5E/^/g;
+ #$buf =~ s/\%5E/^/g;
$buf .= "\a\a" if $self->{beep};
}
$self->local_send('C', $buf);
my $t = ztime($_[2]);
my $loc = '';
- my $clth = $self->{consort} eq 'local' ? 29 : 30;
+ my $clth = 31 + $self->{width} - 80; # allow comment to grow according the screen width
+ # --$clth if $self->{consort} eq 'local';
+
my $comment = substr (($_[3] || ''), 0, $clth);
- $comment .= ' ' x ($clth - length($comment));
- if ($self->{user}->wantgrid) {
- my $ref = DXUser::get_current($_[4]);
- if ($ref) {
- $loc = $ref->qra || '';
- $loc = ' ' . substr($loc, 0, 4) if $loc;
+ $comment =~ s/\t/ /g;
+ $comment .= ' ' x ($clth - (length($comment)));
+
+ if ($self->{user}->wantgrid) {
+ my $ref = DXUser::get_current($_[1]);
+ if ($ref && $ref->qra) {
+ my $cloc = ' ' . substr($ref->qra, 0, 4);
+ $comment = substr $comment, 0, ($clth - (length($comment)+length($cloc)));
+ $comment .= $cloc;
}
- }
-
- if ($self->{user}->wantdxitu) {
+ my $origin = $_[4];
+ $origin =~ s/-#$//; # sigh......
+ $ref = DXUser::get_current($origin);
+ if ($ref && $ref->qra) {
+ $loc = ' ' . substr($ref->qra, 0, 4);
+ }
+ } elsif ($self->{user}->wantdxitu) {
$loc = ' ' . sprintf("%2d", $_[10]) if defined $_[10];
- $comment = substr($comment, 0, $self->{consort} eq 'local' ? 26 : 27) . ' ' . sprintf("%2d", $_[8]) if defined $_[8];
+ $comment = substr($comment, 0, $clth-3) . ' ' . sprintf("%2d", $_[8]) if defined $_[8];
} elsif ($self->{user}->wantdxcq) {
$loc = ' ' . sprintf("%2d", $_[11]) if defined $_[11];
- $comment = substr($comment, 0, $self->{consort} eq 'local' ? 26 : 27) . ' ' . sprintf("%2d", $_[9]) if defined $_[9];
+ $comment = substr($comment, 0, $clth-3) . ' ' . sprintf("%2d", $_[9]) if defined $_[9];
} elsif ($self->{user}->wantusstate) {
$loc = ' ' . $_[13] if $_[13];
- $comment = substr($comment, 0, $self->{consort} eq 'local' ? 26 : 27) . ' ' . $_[12] if $_[12];
+ $comment = substr($comment, 0, $clth-3) . ' ' . $_[12] if $_[12];
}
- return sprintf "DX de %-7.7s%11.1f %-12.12s %-s $t$loc", "$_[4]:", $_[0], $_[1], $comment;
+ return sprintf "DX de %-9.9s%10.1f %-12.12s %-s $t$loc", "$_[4]:", $_[0], $_[1], $comment;
}
# send a dx spot
} else {
$buf = $self->format_dx_spot(@_);
$buf .= "\a\a" if $self->{beep};
- $buf =~ s/\%5E/^/g;
+ #$buf =~ s/\%5E/^/g;
}
$self->local_send('X', $buf);
{
my $s = shift; # the line to be rebroadcast
- foreach my $dxchan (DXChannel::get_all) {
+ foreach my $dxchan (DXChannel::get_all_users) {
next unless $dxchan->{enhanced} && $dxchan->{senddbg};
if ($dxchan->{gtk}) {
$dxchan->send_later('L', dd(['db', $s]));
my @names = readdir(DIR);
closedir(DIR);
my $name;
+
+ return unless @names;
+
foreach $name (@names) {
next if $name =~ /^\./;
$dxchan->{priv} = $u->priv;
$dxchan->{user} = $u;
@out = $s->run($dxchan, 1);
- $dxchan->{call} = $call;
+ $dxchan->{call} = $old;
$dxchan->{priv} = $priv;
$dxchan->{user} = $user;
} else {
}
$motd = "${main::motd}_$self->{lang}" unless $motd && -e $motd;
$motd = $main::motd unless $motd && -e $motd;
- if ($self->conn->{csort} eq 'ax25') {
+ if ($self->conn->ax25) {
if ($motd) {
$motd = "${motd}_ax25" if -e "${motd}_ax25";
} else {
}
$self->send_file($motd) if -e $motd;
}
+
+
+# Punt off a long running command into a separate process
+#
+# This is called from commands to run some potentially long running
+# function. The process forks and then runs the function and returns
+# the result back to the cmd.
+#
+# NOTE: this merely forks the current process and then runs the cmd in that (current) context.
+# IT DOES NOT START UP SOME NEW PROGRAM AND RELIES ON THE FACT THAT IT IS RUNNING DXSPIDER
+# THE CURRENT CONTEXT!!
+#
+# call: $self->spawn_cmd($original_cmd_line, \<function>, [cb => sub{...}], [prefix => "cmd> "], [progress => 0|1], [args => [...]]);
+sub spawn_cmd
+{
+ my $self = shift;
+ my $line = shift;
+ my $cmdref = shift;
+ my $call = $self->{call};
+ my %args = @_;
+ my @out;
+
+ my $cb = delete $args{cb};
+ my $prefix = delete $args{prefix};
+ my $progress = delete $args{progress};
+ my $args = delete $args{args} || [];
+ my $t0 = [gettimeofday];
+
+ no strict 'refs';
+
+ # just behave normally if something has set the "one-shot" _nospawn in the channel
+ if ($self->{_nospawn}) {
+ eval { @out = $cmdref->(@$args); };
+ if ($@) {
+ DXDebug::dbgprintring(25);
+ push @out, DXDebug::shortmess($@);
+ }
+ return @out;
+ }
+
+ my $fc = DXSubprocess->new;
+# $fc->serializer(\&encode_json);
+# $fc->deserializer(\&decode_json);
+ $fc->run(
+ sub {
+ my $subpro = shift;
+ if (isdbg('progress')) {
+ my $s = qq{$call line: "$line"};
+ $s .= ", args: " . join(', ', map { defined $_ ? qq{'$_'} : q{'undef'} } @$args) if $args && @$args;
+ dbg($s);
+ }
+ eval {
+ ++$self->{_in_sub_process};
+ dbg "\$self->{_in_sub_process} = $self->{_in_sub_process}";
+ @out = $cmdref->(@$args);
+ --$self->{_in_sub_process} if $self->{_in_sub_process} > 0;
+ };
+ if ($@) {
+ DXDebug::dbgprintring(25);
+ push @out, DXDebug::shortmess($@);
+ }
+ return @out;
+ },
+# $args,
+ sub {
+ my ($fc, $err, @res) = @_;
+ my $dxchan = DXChannel::get($call);
+ return unless $dxchan;
+
+ if ($err) {
+ my $s = "DXProt::spawn_cmd: call $call error $err";
+ dbg($s) if isdbg('chan');
+ $dxchan->send($s);
+ return;
+ }
+ if ($cb) {
+ # transform output if required
+ @res = $cb->($dxchan, @res);
+ }
+ if (@res) {
+ if (defined $prefix) {
+ $dxchan->send(map {"$prefix$_"} @res);
+ } else {
+ $dxchan->send(@res);
+ }
+ }
+ diffms("by $call", $line, $t0, scalar @res) if isdbg('progress');
+ });
+
+ return @out;
+}
+
+sub user_count
+{
+ return ($users, $maxusers);
+}
1;
__END__