Prepare for git repository
[spider.git] / perl / ExtMsg.pm
1 #
2 # This class is the internal subclass that deals with the external port
3 # communications for Msg.pm
4 #
5 # This is where the cluster handles direct connections coming both in
6 # and out
7 #
8 # $Id$
9 #
10 # Copyright (c) 2001 - Dirk Koopman G1TLH
11 #
12
13 package ExtMsg;
14
15 use strict;
16 use Msg;
17 use DXVars;
18 use DXUtil;
19 use DXDebug;
20 use IO::File;
21 use IO::Socket;
22 use IPC::Open3;
23
24 use vars qw(@ISA $deftimeout);
25
26 @ISA = qw(Msg);
27 $deftimeout = 60;
28
29 sub login
30 {
31         goto &main::login;        # save some writing, this was the default
32 }
33
34 sub enqueue
35 {
36         my ($conn, $msg) = @_;
37         unless ($msg =~ /^[ABZ]/) {
38                 if ($msg =~ /^E[-\w]+\|([01])/ && $conn->{csort} eq 'telnet') {
39                         $conn->{echo} = $1;
40                         if ($1) {
41 #                               $conn->send_raw("\xFF\xFC\x01");
42                         } else {
43 #                               $conn->send_raw("\xFF\xFB\x01");
44                         }
45                 } else {
46                         $msg =~ s/^[-\w]+\|//;
47                         push (@{$conn->{outqueue}}, $msg . $conn->{lineend});
48                 }
49         }
50 }
51
52 sub send_raw
53 {
54         my ($conn, $msg) = @_;
55     my $sock = $conn->{sock};
56     return unless defined($sock);
57         push (@{$conn->{outqueue}}, $msg);
58         dbg("connect $conn->{cnum}: $msg") if $conn->{state} ne 'C' && isdbg('connect');
59     Msg::set_event_handler ($sock, "write" => sub {$conn->_send(0)});
60 }
61
62 sub echo
63 {
64         my $conn = shift;
65         $conn->{echo} = shift;
66 }
67
68 sub dequeue
69 {
70         my $conn = shift;
71         my $msg;
72
73         if ($conn->{csort} eq 'ax25' && exists $conn->{msg}) {
74                 $conn->{msg} =~ s/\cM/\cJ/g;
75         }
76         if ($conn->{state} eq 'WC') {
77                 if (exists $conn->{cmd}) {
78                         if (@{$conn->{cmd}}) {
79                                 dbg("connect $conn->{cnum}: $conn->{msg}") if isdbg('connect');
80                                 $conn->_docmd($conn->{msg});
81                         } 
82                 }
83                 if ($conn->{state} eq 'WC' && exists $conn->{cmd} && @{$conn->{cmd}} == 0) {
84                         $conn->to_connected($conn->{call}, 'O', $conn->{csort});
85                 }
86         } elsif ($conn->{msg} =~ /\cJ/) {
87                 my @lines =  $conn->{msg} =~ /([^\cM\cJ]*)\cM?\cJ/g;
88                 if ($conn->{msg} =~ /\cJ$/) {
89                         delete $conn->{msg};
90                 } else {
91                         $conn->{msg} =~ s/([^\cM\cJ]*)\cM?\cJ//g;
92                 }
93                 while (defined ($msg = shift @lines)) {
94                         dbg("connect $conn->{cnum}: $msg") if $conn->{state} ne 'C' && isdbg('connect');
95                 
96                         $msg =~ s/\xff\xfa.*\xff\xf0|\xff[\xf0-\xfe].//g; # remove telnet options
97 #                       $msg =~ s/[\x00-\x08\x0a-\x19\x1b-\x1f\x80-\x9f]/./g;         # immutable CSI sequence + control characters
98                         
99                         if ($conn->{state} eq 'C') {
100                                 &{$conn->{rproc}}($conn, "I$conn->{call}|$msg");
101                         } elsif ($conn->{state} eq 'WL' ) {
102                                 $msg = uc $msg;
103                                 if (is_callsign($msg) && $msg !~ m|/| ) {
104                                         my $sort = $conn->{csort};
105                                         $sort = 'local' if $conn->{peerhost} eq "127.0.0.1";
106                                         my $uref;
107                                         if ($main::passwdreq || ($uref = DXUser->get_current($msg)) && $uref->passwd ) {
108                                                 $conn->conns($msg);
109                                                 $conn->{state} = 'WP';
110                                                 $conn->{decho} = $conn->{echo};
111                                                 $conn->{echo} = 0;
112                                                 $conn->send_raw('password: ');
113                                         } else {
114                                                 $conn->to_connected($msg, 'A', $sort);
115                                         }
116                                 } else {
117                                         $conn->send_now("Sorry $msg is an invalid callsign");
118                                         $conn->disconnect;
119                                 }
120                         } elsif ($conn->{state} eq 'WP' ) {
121                                 my $uref = DXUser->get_current($conn->{call});
122                                 $msg =~ s/[\r\n]+$//;
123                                 if ($uref && $msg eq $uref->passwd) {
124                                         my $sort = $conn->{csort};
125                                         $conn->{echo} = $conn->{decho};
126                                         delete $conn->{decho};
127                                         $sort = 'local' if $conn->{peerhost} eq "127.0.0.1";
128                                         $conn->{usedpasswd} = 1;
129                                         $conn->to_connected($conn->{call}, 'A', $sort);
130                                 } else {
131                                         $conn->send_now("Sorry");
132                                         $conn->disconnect;
133                                 }
134                         } elsif ($conn->{state} eq 'WC') {
135                                 if (exists $conn->{cmd} && @{$conn->{cmd}}) {
136                                         $conn->_docmd($msg);
137                                         if ($conn->{state} eq 'WC' && exists $conn->{cmd} &&  @{$conn->{cmd}} == 0) {
138                                                 $conn->to_connected($conn->{call}, 'O', $conn->{csort});
139                                         }
140                                 }
141                         }
142                 }
143         }
144 }
145
146 sub to_connected
147 {
148         my ($conn, $call, $dir, $sort) = @_;
149         $conn->{state} = 'C';
150         $conn->conns($call);
151         delete $conn->{cmd};
152         $conn->{timeout}->del if $conn->{timeout};
153         delete $conn->{timeout};
154         $conn->nolinger;
155         &{$conn->{rproc}}($conn, "$dir$call|$sort");
156         $conn->_send_file("$main::data/connected") unless $conn->{outgoing};
157 }
158
159 sub new_client {
160         my $server_conn = shift;
161     my $sock = $server_conn->{sock}->accept();
162         if ($sock) {
163                 my $conn = $server_conn->new($server_conn->{rproc});
164                 $conn->{sock} = $sock;
165                 $conn->nolinger;
166                 Msg::blocking($sock, 0);
167                 $conn->{blocking} = 0;
168                 eval {$conn->{peerhost} = $sock->peerhost};
169                 if ($@) {
170                         dbg($@) if isdbg('connll');
171                         $conn->disconnect;
172                 } else {
173                         eval {$conn->{peerport} = $sock->peerport};
174                         $conn->{peerport} = 0 if $@;
175                         my ($rproc, $eproc) = &{$server_conn->{rproc}} ($conn, $conn->{peerhost}, $conn->{peerport});
176                         dbg("accept $conn->{cnum} from $conn->{peerhost} $conn->{peerport}") if isdbg('connll');
177                         if ($eproc) {
178                                 $conn->{eproc} = $eproc;
179                                 Msg::set_event_handler ($sock, "error" => $eproc);
180                         }
181                         if ($rproc) {
182                                 $conn->{rproc} = $rproc;
183                                 my $callback = sub {$conn->_rcv};
184                                 Msg::set_event_handler ($sock, "read" => $callback);
185                                 # send login prompt
186                                 $conn->{state} = 'WL';
187                                 #               $conn->send_raw("\xff\xfe\x01\xff\xfc\x01\ff\fd\x22");
188                                 #               $conn->send_raw("\xff\xfa\x22\x01\x01\xff\xf0");
189                                 #               $conn->send_raw("\xFF\xFC\x01");
190                                 $conn->_send_file("$main::data/issue");
191                                 $conn->send_raw("login: ");
192                                 $conn->_dotimeout(60);
193                                 $conn->{echo} = 1;
194                         } else { 
195                                 &{$conn->{eproc}}() if $conn->{eproc};
196                                 $conn->disconnect();
197                         }
198                 }
199         } else {
200                 dbg("ExtMsg: error on accept ($!)") if isdbg('err');
201         }
202 }
203
204 sub start_connect
205 {
206         my $call = shift;
207         my $fn = shift;
208         my $conn = ExtMsg->new(\&main::new_channel); 
209         $conn->{outgoing} = 1;
210         $conn->conns($call);
211         
212         my $f = new IO::File $fn;
213         push @{$conn->{cmd}}, <$f>;
214         $f->close;
215         $conn->{state} = 'WC';
216         $conn->_dotimeout($deftimeout);
217         $conn->_docmd;
218 }
219
220 sub _docmd
221 {
222         my $conn = shift;
223         my $msg = shift;
224         my $cmd;
225
226         while ($cmd = shift @{$conn->{cmd}}) {
227                 chomp $cmd;
228                 next if $cmd =~ /^\s*\#/o;
229                 next if $cmd =~ /^\s*$/o;
230                 $conn->_doabort($1) if $cmd =~ /^\s*a\w*\s+(.*)/i;
231                 $conn->_dotimeout($1) if $cmd =~ /^\s*t\w*\s+(\d+)/i;
232                 $conn->_dolineend($1) if $cmd =~ /^\s*[Ll]\w*\s+\'((?:\\[rn])+)\'/i;
233                 if ($cmd =~ /^\s*co\w*\s+(\w+)\s+(.*)$/i) {
234                         unless ($conn->_doconnect($1, $2)) {
235                                 $conn->disconnect;
236                                 @{$conn->{cmd}} = [];    # empty any further commands
237                                 last;
238                         }  
239                 }
240                 if ($cmd =~ /^\s*\'([^\']*)\'\s+\'([^\']*)\'/) {
241                         $conn->_dochat($cmd, $msg, $1, $2);
242                         last;
243                 }
244                 if ($cmd =~ /^\s*cl\w+\s+(.*)/i) {
245                         $conn->_doclient($1);
246                         last;
247                 }
248                 last if $conn->{state} eq 'E';
249         }
250 }
251
252 sub _doconnect
253 {
254         my ($conn, $sort, $line) = @_;
255         my $r;
256
257         $sort = lc $sort;
258         dbg("CONNECT $conn->{cnum} sort: $sort command: $line") if isdbg('connect');
259         if ($sort eq 'telnet') {
260                 # this is a straight network connect
261                 my ($host, $port) = split /\s+/, $line;
262                 $port = 23 if !$port;
263                 $r = $conn->connect($host, $port);
264                 if ($r) {
265                         dbg("Connected $conn->{cnum} to $host $port") if isdbg('connect');
266                 } else {
267                         dbg("***Connect $conn->{cnum} Failed to $host $port $!") if isdbg('connect');
268                 }
269         } elsif ($sort eq 'agw') {
270                 # turn it into an AGW object
271                 bless $conn, 'AGWMsg';
272                 $r = $conn->connect($line);
273         } elsif ($sort eq 'ax25' || $sort eq 'prog') {
274                 $r = $conn->start_program($line, $sort);
275         } else {
276                 dbg("invalid type of connection ($sort)");
277         }
278         $conn->disconnect unless $r;
279         return $r;
280 }
281
282 sub _doabort
283 {
284         my $conn = shift;
285         my $string = shift;
286         dbg("connect $conn->{cnum}: abort $string") if isdbg('connect');
287         $conn->{abort} = $string;
288 }
289
290 sub _dotimeout
291 {
292         my $conn = shift;
293         my $val = shift;
294         dbg("connect $conn->{cnum}: timeout set to $val") if isdbg('connect');
295         $conn->{timeout}->del if $conn->{timeout};
296         $conn->{timeval} = $val;
297         $conn->{timeout} = Timer->new($val, sub{ &_timedout($conn) });
298 }
299
300 sub _dolineend
301 {
302         my $conn = shift;
303         my $val = shift;
304         dbg("connect $conn->{cnum}: lineend set to $val ") if isdbg('connect');
305         $val =~ s/\\r/\r/g;
306         $val =~ s/\\n/\n/g;
307         $conn->{lineend} = $val;
308 }
309
310 sub _dochat
311 {
312         my $conn = shift;
313         my $cmd = shift;
314         my $line = shift;
315         my $expect = shift;
316         my $send = shift;
317                 
318         if ($line) {
319                 if ($expect) {
320                         dbg("connect $conn->{cnum}: expecting: \"$expect\" received: \"$line\"") if isdbg('connect');
321                         if ($conn->{abort} && $line =~ /\Q$conn->{abort}/i) {
322                                 dbg("connect $conn->{cnum}: aborted on /$conn->{abort}/") if isdbg('connect');
323                                 $conn->disconnect;
324                                 delete $conn->{cmd};
325                                 return;
326                         }
327                         if ($line =~ /\Q$expect/i) {
328                                 if (length $send) {
329                                         dbg("connect $conn->{cnum}: got: \"$expect\" sending: \"$send\"") if isdbg('connect');
330                                         $conn->send_later("D$conn->{call}|$send");
331                                 }
332                                 delete $conn->{msg}; # get rid any input if a match
333                                 return;
334                         }
335                 }
336         }
337         $conn->{state} = 'WC';
338         unshift @{$conn->{cmd}}, $cmd;
339 }
340
341 sub _timedout
342 {
343         my $conn = shift;
344         dbg("connect $conn->{cnum}: timed out after $conn->{timeval} seconds") if isdbg('connect');
345         $conn->disconnect;
346 }
347
348 # handle callsign and connection type firtling
349 sub _doclient
350 {
351         my $conn = shift;
352         my $line = shift;
353         my @f = split /\s+/, $line;
354         my $call = uc $f[0] if $f[0];
355         $conn->conns($call);
356         $conn->{csort} = $f[1] if $f[1];
357         $conn->{state} = 'C';
358         &{$conn->{rproc}}($conn, "O$call|$conn->{csort}");
359         delete $conn->{cmd};
360         $conn->{timeout}->del if $conn->{timeout};
361 }
362
363 sub _send_file
364 {
365         my $conn = shift;
366         my $fn = shift;
367         
368         if (-e $fn) {
369                 my $f = new IO::File $fn;
370                 if ($f) {
371                         while (<$f>) {
372                                 chomp;
373                                 my $l = $_;
374                                 dbg("connect $conn->{cnum}: $l") if isdbg('connll');
375                                 $conn->send_raw($l . $conn->{lineend});
376                         }
377                         $f->close;
378                 }
379         }
380 }