39bd3065ab57e0fd3e4af2550d41107c6877b842
[spider.git] / perl / DXMsg.pm
1 #!/usr/bin/perl
2 #
3 # This module impliments the message handling for a dx cluster
4 #
5 # Copyright (c) 1998 Dirk Koopman G1TLH
6 #
7 # $Id$
8 #
9 #
10 # Notes for implementors:-
11 #
12 # PC28 field 11 is the RR required flag
13 # PC28 field 12 is a VIA routing (ie it is a node call) 
14
15 package DXMsg;
16
17 @ISA = qw(DXProt DXChannel);
18
19 use DXUtil;
20 use DXChannel;
21 use DXUser;
22 use DXM;
23 use DXCluster;
24 use DXProtVars;
25 use DXProtout;
26 use DXDebug;
27 use DXLog;
28 use FileHandle;
29 use Carp;
30
31 use strict;
32 use vars qw(%work @msg $msgdir %valid %busy $maxage $last_clean
33                         @badmsg $badmsgfn $forwardfn @forward);
34
35 %work = ();                                             # outstanding jobs
36 @msg = ();                                              # messages we have
37 %busy = ();                                             # station interlocks
38 $msgdir = "$main::root/msg";    # directory contain the msgs
39 $maxage = 30 * 86400;                   # the maximum age that a message shall live for if not marked 
40 $last_clean = 0;                                # last time we did a clean
41 @forward = ();                  # msg forward table
42
43 $badmsgfn = "$msgdir/badmsg.pl";  # list of TO address we wont store
44 $forwardfn = "$msgdir/forward.pl";  # the forwarding table
45
46 %valid = (
47                   fromnode => '9,From Node',
48                   tonode => '9,To Node',
49                   to => '0,To',
50                   from => '0,From',
51                   t => '0,Msg Time,cldatetime',
52                   private => '9,Private',
53                   subject => '0,Subject',
54                   linesreq => '0,Lines per Gob',
55                   rrreq => '9,Read Confirm',
56                   origin => '0,Origin',
57                   lines => '5,Data',
58                   stream => '9,Stream No',
59                   count => '9,Gob Linecnt',
60                   file => '9,File?,yesno',
61                   gotit => '9,Got it Nodes,parray',
62                   lines => '9,Lines,parray',
63                   'read' => '9,Times read',
64                   size => '0,Size',
65                   msgno => '0,Msgno',
66                   keep => '0,Keep this?,yesno',
67                  );
68
69 # allocate a new object
70 # called fromnode, tonode, from, to, datetime, private?, subject, nolinesper  
71 sub alloc                  
72 {
73         my $pkg = shift;
74         my $self = bless {}, $pkg;
75         $self->{msgno} = shift;
76         my $to = shift;
77         #  $to =~ s/-\d+$//o;
78         $self->{to} = $to;
79         my $from = shift;
80         $from =~ s/-\d+$//o;
81         $self->{from} = uc $from;
82         $self->{t} = shift;
83         $self->{private} = shift;
84         $self->{subject} = shift;
85         $self->{origin} = shift;
86         $self->{'read'} = shift;
87         $self->{rrreq} = shift;
88         $self->{gotit} = [];
89     
90         return $self;
91 }
92
93 sub workclean
94 {
95         my $ref = shift;
96         delete $ref->{lines};
97         delete $ref->{linesreq};
98         delete $ref->{tonode};
99         delete $ref->{fromnode};
100         delete $ref->{stream};
101         delete $ref->{lines};
102         delete $ref->{file};
103         delete $ref->{count};
104 }
105
106 sub process
107 {
108         my ($self, $line) = @_;
109         my @f = split /\^/, $line;
110         my ($pcno) = $f[0] =~ /^PC(\d\d)/; # just get the number
111         
112  SWITCH: {
113                 if ($pcno == 28) {              # incoming message
114                         my $t = cltounix($f[5], $f[6]);
115                         my $stream = next_transno($f[2]);
116                         my $ref = DXMsg->alloc($stream, uc $f[3], $f[4], $t, $f[7], $f[8], $f[13], '0', $f[11]);
117                         
118                         # fill in various forwarding state variables
119                         $ref->{fromnode} = $f[2];
120                         $ref->{tonode} = $f[1];
121                         $ref->{rrreq} = $f[11];
122                         $ref->{linesreq} = $f[10];
123                         $ref->{stream} = $stream;
124                         $ref->{count} = 0;      # no of lines between PC31s
125                         dbg('msg', "new message from $f[4] to $f[3] '$f[8]' stream $stream\n");
126                         $work{"$f[2]$stream"} = $ref; # store in work
127                         $busy{$f[2]} = $ref; # set interlock
128                         $self->send(DXProt::pc30($f[2], $f[1], $stream)); # send ack
129                         last SWITCH;
130                 }
131                 
132                 if ($pcno == 29) {              # incoming text
133                         my $ref = $work{"$f[2]$f[3]"};
134                         if ($ref) {
135                                 push @{$ref->{lines}}, $f[4];
136                                 $ref->{count}++;
137                                 if ($ref->{count} >= $ref->{linesreq}) {
138                                         $self->send(DXProt::pc31($f[2], $f[1], $f[3]));
139                                         dbg('msg', "stream $f[3]: $ref->{count} lines received\n");
140                                         $ref->{count} = 0;
141                                 }
142                         }
143                         last SWITCH;
144                 }
145                 
146                 if ($pcno == 30) {              # this is a incoming subject ack
147                         my $ref = $work{$f[2]}; # note no stream at this stage
148                         if ($ref) {
149                                 delete $work{$f[2]};
150                                 $ref->{stream} = $f[3];
151                                 $ref->{count} = 0;
152                                 $ref->{linesreq} = 5;
153                                 $work{"$f[2]$f[3]"} = $ref;     # new ref
154                                 dbg('msg', "incoming subject ack stream $f[3]\n");
155                                 $busy{$f[2]} = $ref; # interlock
156                                 $ref->{lines} = [];
157                                 push @{$ref->{lines}}, ($ref->read_msg_body);
158                                 $ref->send_tranche($self);
159                         } else {
160                                 $self->send(DXProt::pc42($f[2], $f[1], $f[3])); # unknown stream
161                         } 
162                         last SWITCH;
163                 }
164                 
165                 if ($pcno == 31) {              # acknowledge a tranche of lines
166                         my $ref = $work{"$f[2]$f[3]"};
167                         if ($ref) {
168                                 dbg('msg', "tranche ack stream $f[3]\n");
169                                 $ref->send_tranche($self);
170                         } else {
171                                 $self->send(DXProt::pc42($f[2], $f[1], $f[3])); # unknown stream
172                         } 
173                         last SWITCH;
174                 }
175                 
176                 if ($pcno == 32) {              # incoming EOM
177                         dbg('msg', "stream $f[3]: EOM received\n");
178                         my $ref = $work{"$f[2]$f[3]"};
179                         if ($ref) {
180                                 $self->send(DXProt::pc33($f[2], $f[1], $f[3])); # acknowledge it
181                                 
182                                 # get the next msg no - note that this has NOTHING to do with the stream number in PC protocol
183                                 # store the file or message
184                                 # remove extraneous rubbish from the hash
185                                 # remove it from the work in progress vector
186                                 # stuff it on the msg queue
187                                 if ($ref->{lines} && @{$ref->{lines}} > 0) { # ignore messages with 0 lines
188                                         if ($ref->{file}) {
189                                                 $ref->store($ref->{lines});
190                                         } else {
191
192                                                 # does an identical message already exist?
193                                                 my $m;
194                                                 for $m (@msg) {
195                                                         if ($ref->{subject} eq $m->{subject} && $ref->{t} == $m->{t} && $ref->{from} eq $m->{from}) {
196                                                                 $ref->stop_msg($self);
197                                                                 my $msgno = $m->{msgno};
198                                                                 dbg('msg', "duplicate message to $msgno\n");
199                                                                 Log('msg', "duplicate message to $msgno");
200                                                                 return;
201                                                         }
202                                                 }
203                                                         
204                                                 # look for 'bad' to addresses 
205                                                 if (grep $ref->{to} eq $_, @badmsg) {
206                                                         $ref->stop_msg($self);
207                                                         dbg('msg', "'Bad' TO address $ref->{to}");
208                                                         Log('msg', "'Bad' TO address $ref->{to}");
209                                                         return;
210                                                 }
211
212                                                 $ref->{msgno} = next_transno("Msgno");
213                                                 push @{$ref->{gotit}}, $f[2]; # mark this up as being received
214                                                 $ref->store($ref->{lines});
215                                                 add_dir($ref);
216                                                 my $dxchan = DXChannel->get($ref->{to});
217                                                 $dxchan->send($dxchan->msg('msgnew')) if $dxchan;
218                                                 Log('msg', "Message $ref->{msgno} from $ref->{from} received from $f[2] for $ref->{to}");
219                                         }
220                                 }
221                                 $ref->stop_msg($self);
222                                 queue_msg(0);
223                         } else {
224                                 $self->send(DXProt::pc42($f[2], $f[1], $f[3])); # unknown stream
225                         }
226                         queue_msg(0);
227                         last SWITCH;
228                 }
229                 
230                 if ($pcno == 33) {              # acknowledge the end of message
231                         my $ref = $work{"$f[2]$f[3]"};
232                         if ($ref) {
233                                 if ($ref->{private}) { # remove it if it private and gone off site#
234                                         Log('msg', "Message $ref->{msgno} from $ref->{from} sent to $f[2] and deleted");
235                                         $ref->del_msg;
236                                 } else {
237                                         Log('msg', "Message $ref->{msgno} from $ref->{from} sent to $f[2]");
238                                         push @{$ref->{gotit}}, $f[2]; # mark this up as being received
239                                         $ref->store($ref->{lines});     # re- store the file
240                                 }
241                                 $ref->stop_msg($self);
242                         } else {
243                                 $self->send(DXProt::pc42($f[2], $f[1], $f[3])); # unknown stream
244                         } 
245                         queue_msg(0);
246                         last SWITCH;
247                 }
248                 
249                 if ($pcno == 40) {              # this is a file request
250                         $f[3] =~ s/\\/\//og; # change the slashes
251                         $f[3] =~ s/\.//og;      # remove dots
252                         $f[3] =~ s/^\///o;   # remove the leading /
253                         $f[3] = lc $f[3];       # to lower case;
254                         dbg('msg', "incoming file $f[3]\n");
255                         last SWITCH if $f[3] =~ /^(perl|cmd|local|src|lib|include|sys|msg|connect)/; # prevent access to executables
256                         
257                         # create any directories
258                         my @part = split /\//, $f[3];
259                         my $part;
260                         my $fn = "$main::root";
261                         pop @part;                      # remove last part
262                         foreach $part (@part) {
263                                 $fn .= "/$part";
264                                 next if -e $fn;
265                                 last SWITCH if !mkdir $fn, 0777;
266                                 dbg('msg', "created directory $fn\n");
267                         }
268                         my $stream = next_transno($f[2]);
269                         my $ref = DXMsg->alloc($stream, "$main::root/$f[3]", $self->call, time, !$f[4], $f[3], ' ', '0', '0');
270                         
271                         # forwarding variables
272                         $ref->{fromnode} = $f[1];
273                         $ref->{tonode} = $f[2];
274                         $ref->{linesreq} = $f[5];
275                         $ref->{stream} = $stream;
276                         $ref->{count} = 0;      # no of lines between PC31s
277                         $ref->{file} = 1;
278                         $work{"$f[2]$stream"} = $ref; # store in work
279                         $self->send(DXProt::pc30($f[2], $f[1], $stream)); # send ack 
280                         
281                         last SWITCH;
282                 }
283                 
284                 if ($pcno == 42) {              # abort transfer
285                         dbg('msg', "stream $f[3]: abort received\n");
286                         my $ref = $work{"$f[2]$f[3]"};
287                         if ($ref) {
288                                 $ref->stop_msg($self);
289                                 $ref = undef;
290                         }
291                         
292                         last SWITCH;
293                 }
294
295                 if ($pcno == 49) {      # global delete on subject
296                         for (@msg) {
297                                 if ($_->{subject} eq $f[2]) {
298                                         $_->del_msg();
299                                         Log('msg', "Message $_->{msgno} fully deleted by $f[1]");
300                                 }
301                         }
302                 }
303         }
304         
305         clean_old() if $main::systime - $last_clean > 3600 ; # clean the message queue
306 }
307
308
309 # store a message away on disc or whatever
310 #
311 # NOTE the second arg is a REFERENCE not a list
312 sub store
313 {
314         my $ref = shift;
315         my $lines = shift;
316         
317         # we only proceed if there are actually any lines in the file
318         if (!$lines || @{$lines} == 0) {
319                 return;
320         }
321         
322         if ($ref->{file}) {                     # a file
323                 dbg('msg', "To be stored in $ref->{to}\n");
324                 
325                 my $fh = new FileHandle "$ref->{to}", "w";
326                 if (defined $fh) {
327                         my $line;
328                         foreach $line (@{$lines}) {
329                                 print $fh "$line\n";
330                         }
331                         $fh->close;
332                         dbg('msg', "file $ref->{to} stored\n");
333                         Log('msg', "file $ref->{to} from $ref->{from} stored" );
334                 } else {
335                         confess "can't open file $ref->{to} $!";  
336                 }
337         } else {                                        # a normal message
338
339                 # attempt to open the message file
340                 my $fn = filename($ref->{msgno});
341                 
342                 dbg('msg', "To be stored in $fn\n");
343                 
344                 # now save the file, overwriting what's there, YES I KNOW OK! (I will change it if it's a problem)
345                 my $fh = new FileHandle "$fn", "w";
346                 if (defined $fh) {
347                         my $rr = $ref->{rrreq} ? '1' : '0';
348                         my $priv = $ref->{private} ? '1': '0';
349                         print $fh "=== $ref->{msgno}^$ref->{to}^$ref->{from}^$ref->{t}^$priv^$ref->{subject}^$ref->{origin}^$ref->{'read'}^$rr\n";
350                         print $fh "=== ", join('^', @{$ref->{gotit}}), "\n";
351                         my $line;
352                         $ref->{size} = 0;
353                         foreach $line (@{$lines}) {
354                                 $ref->{size} += (length $line) + 1;
355                                 print $fh "$line\n";
356                         }
357                         $fh->close;
358                         dbg('msg', "msg $ref->{msgno} stored\n");
359                         Log('msg', "msg $ref->{msgno} from $ref->{from} to $ref->{to} stored" );
360                 } else {
361                         confess "can't open msg file $fn $!";  
362                 }
363         }
364 }
365
366 # delete a message
367 sub del_msg
368 {
369         my $self = shift;
370         
371         # remove it from the active message list
372         @msg = map { $_ != $self ? $_ : () } @msg;
373         
374         # belt and braces (one day I will ask someone if this is REALLY necessary)
375         delete $self->{gotit};
376         delete $self->{list};
377         
378         # remove the file
379         unlink filename($self->{msgno});
380         dbg('msg', "deleting $self->{msgno}\n");
381 }
382
383 # clean out old messages from the message queue
384 sub clean_old
385 {
386         my $ref;
387         
388         # mark old messages for deletion
389         foreach $ref (@msg) {
390                 if (!$ref->{keep} && $ref->{t} < $main::systime - $maxage) {
391                         $ref->{deleteme} = 1;
392                         delete $ref->{gotit};
393                         delete $ref->{list};
394                         unlink filename($ref->{msgno});
395                         dbg('msg', "deleting old $ref->{msgno}\n");
396                 }
397         }
398         
399         # remove them all from the active message list
400         @msg = map { $_->{deleteme} ? () : $_ } @msg;
401         $last_clean = $main::systime;
402 }
403
404 # read in a message header
405 sub read_msg_header
406
407         my $fn = shift;
408         my $file;
409         my $line;
410         my $ref;
411         my @f;
412         my $size;
413         
414         $file = new FileHandle;
415         if (!open($file, $fn)) {
416                 print "Error reading $fn $!\n";
417                 return undef;
418         }
419         $size = -s $fn;
420         $line = <$file>;                        # first line
421         chomp $line;
422         $size -= length $line;
423         if (! $line =~ /^===/o) {
424                 print "corrupt first line in $fn ($line)\n";
425                 return undef;
426         }
427         $line =~ s/^=== //o;
428         @f = split /\^/, $line;
429         $ref = DXMsg->alloc(@f);
430         
431         $line = <$file>;                        # second line
432         chomp $line;
433         $size -= length $line;
434         if (! $line =~ /^===/o) {
435                 print "corrupt second line in $fn ($line)\n";
436                 return undef;
437         }
438         $line =~ s/^=== //o;
439         $ref->{gotit} = [];
440         @f = split /\^/, $line;
441         push @{$ref->{gotit}}, @f;
442         $ref->{size} = $size;
443         
444         close($file);
445         
446         return $ref;
447 }
448
449 # read in a message header
450 sub read_msg_body
451 {
452         my $self = shift;
453         my $msgno = $self->{msgno};
454         my $file;
455         my $line;
456         my $fn = filename($msgno);
457         my @out;
458         
459         $file = new FileHandle;
460         if (!open($file, $fn)) {
461                 print "Error reading $fn $!\n";
462                 return undef;
463         }
464         chomp (@out = <$file>);
465         close($file);
466         
467         shift @out if $out[0] =~ /^=== /;
468         shift @out if $out[0] =~ /^=== /;
469         return @out;
470 }
471
472 # send a tranche of lines to the other end
473 sub send_tranche
474 {
475         my ($self, $dxchan) = @_;
476         my @out;
477         my $to = $self->{tonode};
478         my $from = $self->{fromnode};
479         my $stream = $self->{stream};
480         my $lines = $self->{lines};
481         my ($c, $i);
482         
483         for ($i = 0, $c = $self->{count}; $i < $self->{linesreq} && $c < @$lines; $i++, $c++) {
484                 push @out, DXProt::pc29($to, $from, $stream, $lines->[$c]);
485     }
486     $self->{count} = $c;
487
488     push @out, DXProt::pc32($to, $from, $stream) if $i < $self->{linesreq};
489         $dxchan->send(@out);
490 }
491
492         
493 # find a message to send out and start the ball rolling
494 sub queue_msg
495 {
496         my $sort = shift;
497         my $call = shift;
498         my $ref;
499         my $clref;
500         my $dxchan;
501         my @nodelist = DXProt::get_all_ak1a();
502         
503         # bat down the message list looking for one that needs to go off site and whose
504         # nearest node is not busy.
505
506         dbg('msg', "queue msg ($sort)\n");
507         foreach $ref (@msg) {
508                 # firstly, is it private and unread? if so can I find the recipient
509                 # in my cluster node list offsite?
510                 if ($ref->{private}) {
511                         if ($ref->{'read'} == 0) {
512                                 $clref = DXCluster->get_exact($ref->{to});
513                                 unless ($clref) {             # otherwise look for a homenode
514                                         my $uref = DXUser->get($ref->{to});
515                                         my $hnode =  $uref->homenode if $uref;
516                                         $clref = DXCluster->get_exact($hnode) if $hnode;
517                                 }
518                                 if ($clref && !grep { $clref->{dxchan} == $_ } DXCommandmode::get_all) {
519                                         $dxchan = $clref->{dxchan};
520                                         $ref->start_msg($dxchan) if $dxchan && $clref && !get_busy($dxchan->call) && $dxchan->state eq 'normal';
521                                 }
522                         }
523                 } elsif (!$sort) {
524                         # otherwise we are dealing with a bulletin, compare the gotit list with
525                         # the nodelist up above, if there are sites that haven't got it yet
526                         # then start sending it - what happens when we get loops is anyone's
527                         # guess, use (to, from, time, subject) tuple?
528                         my $noderef;
529                         foreach $noderef (@nodelist) {
530                                 next if $noderef->call eq $main::mycall;
531                                 next if $noderef->isolate;               # maybe add code for stuff originated here?
532                                 next if grep { $_ eq $noderef->call } @{$ref->{gotit}};
533                                 next if DXUser->get( ${$ref->{gotit}}[0] )->isolate;  # is the origin isolated?
534                                 
535                                 # if we are here we have a node that doesn't have this message
536                                 $ref->start_msg($noderef) if !get_busy($noderef->call)  && $noderef->state eq 'normal';
537                                 last;
538                         }
539                 }
540                 
541                 # if all the available nodes are busy then stop
542                 last if @nodelist == scalar grep { get_busy($_->call) } @nodelist;
543         }
544 }
545
546 # is there a message for me?
547 sub for_me
548 {
549         my $call = uc shift;
550         my $ref;
551         
552         foreach $ref (@msg) {
553                 # is it for me, private and unread? 
554                 if ($ref->{to} eq $call && $ref->{private}) {
555                         return 1 if !$ref->{'read'};
556                 }
557         }
558         return 0;
559 }
560
561 # start the message off on its travels with a PC28
562 sub start_msg
563 {
564         my ($self, $dxchan) = @_;
565         
566         dbg('msg', "start msg $self->{msgno}\n");
567         $self->{linesreq} = 5;
568         $self->{count} = 0;
569         $self->{tonode} = $dxchan->call;
570         $self->{fromnode} = $main::mycall;
571         $busy{$dxchan->call} = $self;
572         $work{"$self->{tonode}"} = $self;
573         $dxchan->send(DXProt::pc28($self->{tonode}, $self->{fromnode}, $self->{to}, $self->{from}, $self->{t}, $self->{private}, $self->{subject}, $self->{origin}, $self->{rrreq}));
574 }
575
576 # get the ref of a busy node
577 sub get_busy
578 {
579         my $call = shift;
580         return $busy{$call};
581 }
582
583 # get the busy queue
584 sub get_all_busy
585 {
586         return values %busy;
587 }
588
589 # get the forwarding queue
590 sub get_fwq
591 {
592         return values %work;
593 }
594
595 # stop a message from continuing, clean it out, unlock interlocks etc
596 sub stop_msg
597 {
598         my ($self, $dxchan) = @_;
599         my $node = $dxchan->call;
600         
601         dbg('msg', "stop msg $self->{msgno} stream $self->{stream}\n");
602         delete $work{$node};
603         delete $work{"$node$self->{stream}"};
604         $self->workclean;
605         delete $busy{$node};
606 }
607
608 # get a new transaction number from the file specified
609 sub next_transno
610 {
611         my $name = shift;
612         $name =~ s/\W//og;                      # remove non-word characters
613         my $fn = "$msgdir/$name";
614         my $msgno;
615         
616         my $fh = new FileHandle;
617         if (sysopen($fh, $fn, O_RDWR|O_CREAT, 0666)) {
618                 $fh->autoflush(1);
619                 $msgno = $fh->getline;
620                 chomp $msgno;
621                 $msgno++;
622                 seek $fh, 0, 0;
623                 $fh->print("$msgno\n");
624                 dbg('msg', "msgno $msgno allocated for $name\n");
625                 $fh->close;
626         } else {
627                 confess "can't open $fn $!";
628         }
629         return $msgno;
630 }
631
632 # initialise the message 'system', read in all the message headers
633 sub init
634 {
635         my $dir = new FileHandle;
636         my @dir;
637         my $ref;
638
639         # load various control files
640         my @in = load_badmsg();
641         print "@in\n" if @in;
642         @in = load_forward();
643         print "@in\n" if @in;
644
645         # read in the directory
646         opendir($dir, $msgdir) or confess "can't open $msgdir $!";
647         @dir = readdir($dir);
648         closedir($dir);
649
650         @msg = ();
651         for (sort @dir) {
652                 next unless /^m\d+$/o;
653                 
654                 $ref = read_msg_header("$msgdir/$_");
655                 next unless $ref;
656                 
657                 # delete any messages to 'badmsg.pl' places
658                 if (grep $ref->{to} eq $_, @badmsg) {
659                         dbg('msg', "'Bad' TO address $ref->{to}");
660                         Log('msg', "'Bad' TO address $ref->{to}");
661                         $ref->del_msg;
662                         next;
663                 }
664
665                 # add the message to the available queue
666                 add_dir($ref); 
667         }
668 }
669
670 # add the message to the directory listing
671 sub add_dir
672 {
673         my $ref = shift;
674         confess "tried to add a non-ref to the msg directory" if !ref $ref;
675         push @msg, $ref;
676 }
677
678 # return all the current messages
679 sub get_all
680 {
681         return @msg;
682 }
683
684 # get a particular message
685 sub get
686 {
687         my $msgno = shift;
688         for (@msg) {
689                 return $_ if $_->{msgno} == $msgno;
690                 last if $_->{msgno} > $msgno;
691         }
692         return undef;
693 }
694
695 # return the official filename for a message no
696 sub filename
697 {
698         return sprintf "$msgdir/m%06d", shift;
699 }
700
701 #
702 # return a list of valid elements 
703
704
705 sub fields
706 {
707         return keys(%valid);
708 }
709
710 #
711 # return a prompt for a field
712 #
713
714 sub field_prompt
715
716         my ($self, $ele) = @_;
717         return $valid{$ele};
718 }
719
720 #
721 # send a message state machine
722 sub do_send_stuff
723 {
724         my $self = shift;
725         my $line = shift;
726         my @out;
727         
728         if ($self->state eq 'send1') {
729                 #  $DB::single = 1;
730                 confess "local var gone missing" if !ref $self->{loc};
731                 my $loc = $self->{loc};
732                 $loc->{subject} = $line;
733                 $loc->{lines} = [];
734                 $self->state('sendbody');
735                 #push @out, $self->msg('sendbody');
736                 push @out, "Enter Message /EX (^Z) to send or /ABORT (^Y) to exit";
737         } elsif ($self->state eq 'sendbody') {
738                 confess "local var gone missing" if !ref $self->{loc};
739                 my $loc = $self->{loc};
740                 if ($line eq "\032" || uc $line eq "/EX") {
741                         my $to;
742                         
743                         if (@{$loc->{lines}} > 0) {
744                                 foreach $to (@{$loc->{to}}) {
745                                         my $ref;
746                                         my $systime = $main::systime;
747                                         my $mycall = $main::mycall;
748                                         $ref = DXMsg->alloc(DXMsg::next_transno('Msgno'),
749                                                                                 uc $to,
750                                                                                 $self->call, 
751                                                                                 $systime,
752                                                                                 $loc->{private}, 
753                                                                                 $loc->{subject}, 
754                                                                                 $mycall,
755                                                                                 '0',
756                                                                                 $loc->{rrreq});
757                                         $ref->store($loc->{lines});
758                                         $ref->add_dir();
759                                         #push @out, $self->msg('sendsent', $to);
760                                         push @out, "msgno $ref->{msgno} sent to $to";
761                                         my $dxchan = DXChannel->get(uc $to);
762                                         if ($dxchan) {
763                                                 if ($dxchan->is_user()) {
764                                                         $dxchan->send("New mail has arrived for you");
765                                                 }
766                                         }
767                                 }
768                         }
769                         delete $loc->{lines};
770                         delete $loc->{to};
771                         delete $self->{loc};
772                         $self->func(undef);
773                         DXMsg::queue_msg(0);
774                         $self->state('prompt');
775                 } elsif ($line eq "\031" || uc $line eq "/ABORT" || uc $line eq "/QUIT") {
776                         #push @out, $self->msg('sendabort');
777                         push @out, "aborted";
778                         delete $loc->{lines};
779                         delete $loc->{to};
780                         delete $self->{loc};
781                         $self->func(undef);
782                         $self->state('prompt');
783                 } else {
784                         
785                         # i.e. it ain't and end or abort, therefore store the line
786                         push @{$loc->{lines}}, length($line) > 0 ? $line : " ";
787                 }
788         }
789         return (1, @out);
790 }
791
792 # return the standard directory line for this ref 
793 sub dir
794 {
795         my $ref = shift;
796         return sprintf "%6d%s%s%5d %8.8s %8.8s %-6.6s %5.5s %-30.30s", 
797                 $ref->msgno, $ref->read ? '-' : ' ', $ref->private ? 'p' : ' ', $ref->size,
798                         $ref->to, $ref->from, cldate($ref->t), ztime($ref->t), $ref->subject;
799 }
800
801 # load the forward table
802 sub load_forward
803 {
804         my @out;
805         do "$forwardfn" if -e "$forwardfn";
806         push @out, $@ if $@;
807         return @out;
808 }
809
810 # load the bad message table
811 sub load_badmsg
812 {
813         my @out;
814         do "$badmsgfn" if -e "$badmsgfn";
815         push @out, $@ if $@;
816         return @out;
817 }
818
819 no strict;
820 sub AUTOLOAD
821 {
822         my $self = shift;
823         my $name = $AUTOLOAD;
824         return if $name =~ /::DESTROY$/;
825         $name =~ s/.*:://o;
826         
827         confess "Non-existant field '$AUTOLOAD'" if !$valid{$name};
828         @_ ? $self->{$name} = shift : $self->{$name} ;
829 }
830
831 1;
832
833 __END__