3 # conmux -- the main console multiplexor daemon
5 # Main console multiplexor daemon. There is one of these daemons for
6 # each open console supported in the system. Clients are directed to
7 # this daemon via the conmux-registry deamon.
9 # (C) Copyright IBM Corp. 2004, 2005, 2006
10 # Author: Andy Whitcroft <andyw@uk.ibm.com>
12 # The Console Multiplexor is released under the GNU Public License V2
17 use Symbol
qw(gensym);
24 # Find our internal libraries.
25 use lib
$FindBin::Bin
;
26 use lib
"$FindBin::Bin/../lib/";
27 use lib
"$FindBin::Bin/lib/";
33 $SIG{'CHLD'} = "IGNORE";
38 # CALLBACK: this class is used to provide a timed callback. The multiplexor
39 # libarary allows us to set a timeout on any open file we have registered.
40 # So, we open a new file descriptor to /dev/null and set a timeout on that.
45 my ($class, $mux, $who, $time) = @_;
46 my $self = bless { 'who' => $who }, $class;
50 print "Callback::new [$self] mux<$mux> who<$who> time<$time>\n"
53 # Open a file handle to nothing, we need this to hang the timeout
54 # on in the multiplexor. It will fail with a mux_eof, which we ignore.
55 open($fh, "</dev/null") || die "$P: /dev/null: open failed - $!\n";
58 $mux->set_callback_object($self, $fh);
60 $mux->set_timeout($fh, $time);
65 my ($self, $mux, $fh) = @_;
67 print "Callback::mux_timeout [$self] mux<$mux> fh<$fh>\n"
70 $self->{'who'}->callback_timeout();
76 print "Callback::DESTROY [$self]\n" if ($main::debug
);
80 # LISTENER SOCKET: creates an intenet listener for new clients and
81 # connects them to the junction provided.
83 package ListenerSocket
;
86 my ($class, $mux, $port) = @_;
87 my $self = bless { 'mux' => $mux }, $class;
89 print "ListenerSocket::new [$self] mux<$mux> port<$port>\n"
92 $self->initialise($port);
98 my ($self, $port) = @_;
101 print "ListenerSocket::initialise [$self] port<$port> "
104 # Create a listening socket and add it to the multiplexor.
105 my $sock = new IO
::Socket
::INET
(Proto
=> 'tcp',
111 print " adding $self $sock\n" if ($main::debug
);
112 $self->mux->listen($sock);
113 $self->mux->set_callback_object($self, $sock);
114 $self->listener($sock);
120 if (@_) { $self->{'mux'} = shift }
121 return $self->{'mux'};
125 if (@_) { $self->{'listener'} = shift }
126 return $self->{'listener'};
131 Net
::Domain
::hostfqdn
() . ':' . $self->{'listener'}->sockport();
134 # JUNCTION: callbacks.
135 ##sub junctionInput {
140 ## $self->{'junction'}->junctionRemove($self, 'console-client');
141 ## $self->{'mux'}->close($self->{'listener'});
144 # Handle new connections by instantiating a new client class.
146 my ($self, $mux, $fh) = @_;
149 print "ListenerSocket::mux_connection [$self] mux<$mux> fh<$fh>\n"
152 # Make a new client connection.
153 $client = ClientCmd
->new($mux, $fh);
154 print " new connection $self $client\n" if ($main::debug
);
160 print "ListenerSocket::DESTROY [$self]\n" if ($main::debug
);
162 close($self->listener);
166 # JUNCTION: generic junction box object, connects names groups of objects
167 # to other named groups.
169 # Expects the following callbacks to be defined on each object registered:
170 # junctionInput($from, $data)
171 # junctionEOF($from, $to)
177 my $self = bless { }, $class;
179 print "Junction::new [$self]\n" if ($main::debug
);
185 my ($self, $client) = @_;
187 print "Junction::junctionAdd [$self] client<$client>\n"
190 # Add ourselves to the list of recipients.
191 $self->{$client} = $client;
195 my ($self, $client, $data) = @_;
198 print "Junction::junctionInput [$self] client<$client> " .
199 "data<$data>\n" if ($main::debug
);
201 # Send this data on to the clients listed in the output list.
202 for $c (values %{$self}) {
203 print " sending to $c\n" if ($main::debug
);
204 $c->junctionInput($client, $data);
208 my ($self, $client) = @_;
211 print "Junction::junctionEOF [$self] client<$client>\n"
214 # Send this eof on to the clients listed in the output list.
215 for $c (values %{$self}) {
216 print " sending to $c\n" if ($main::debug
);
217 $c->junctionEOF($client);
221 my ($self, $client) = @_;
223 print "Junction::junctionRemove [$self] client<$client>\n"
226 # Drop this client from our lists.
227 delete $self->{$client};
231 # PAYLOAD: generic payload object, connects itself to the requisite junction.
239 my ($class, $name) = @_;
245 my ($class, $name, $self) = @_;
247 print "Payloads::found name<$name> self<$self>\n" if ($main::debug
);
249 $payloads{$name} = $self;
253 my ($class, $name, $self) = @_;
255 print "Payloads::lost name<$name> self<$self>\n" if ($main::debug
);
257 undef $payloads{$name};
258 if (--$payloads == 0) {
264 my ($class, $name, $title, $mux, @a) = @_;
265 my $self = bless { }, $class;
267 print "Payload::new [$self] name<$name> title<$title> mux<$mux>\n"
270 Payload
->found($name, $self);
273 $self->title($title);
277 $self->cin(Junction
->new);
278 $self->cout(Junction
->new);
280 $self->initialise(@a);
288 if (@_) { $self->{'name'} = shift }
289 return $self->{'name'};
293 if (@_) { $self->{'title'} = shift }
294 return $self->{'title'};
298 if (@_) { $self->{'mux'} = shift }
299 return $self->{'mux'};
303 if (@_) { $self->{'cin'} = shift }
304 return $self->{'cin'};
308 if (@_) { $self->{'cout'} = shift }
309 return $self->{'cout'};
313 if (@_) { $self->{'enabled'} = shift }
314 return $self->{'enabled'};
318 if (@_) { $self->{'connected'} = shift }
320 return $self->{'connected'};
325 if (($time - $self->{'trans_minor'}) > 30) {
326 $self->{'trans_major'} = $time;
328 $self->{'trans_minor'} = $time;
332 my $time = time - $self->{'trans_major'};
336 } elsif ($time < 120) {
344 my $ctime = $self->{'connected'};
345 my $ttime = $self->{'trans_major'};
348 if ($ctime && ($time - $ctime) > 30) {
350 } elsif ($ttime && ($time - $ttime) < 60) {
361 print "Payload::initialise [$self]\n" if ($main::debug
);
363 # Ensure we recieve client input.
364 $self->cin->junctionAdd($self);
366 $self->connected(time);
370 my $TN_IAC = sprintf("%c", 255);
371 my $TN_DONT = sprintf("%c", 254);
372 my $TN_DO = sprintf("%c", 253);
373 my $TN_WONT = sprintf("%c", 252);
374 my $TN_WILL = sprintf("%c", 251);
375 my $TN_SB = sprintf("%c", 250);
376 my $TN_SE = sprintf("%c", 240);
377 my $TN_BREAK = sprintf("%c", 243);
379 my $TNOPT_ECHO = sprintf("%c", 1);
380 my $TNOPT_SGA = sprintf("%c", 3);
383 # If we get here then we have accumulated a complete telnet
384 # negotiation string.
386 # Telnet negotiation protocol - RFC#854:
388 # DO We are being asked to DO an option
389 # DONT We are being asked to NOT DO an option
390 # WILL We are being told they will DO an option
391 # WONT We are being told they will NOT DO an option
393 # DO/DONT requests indicate we should {en,dis}able a mode.
394 # We are expected to respond with WILL or WONT. To prevent
395 # loops, we should not respond if the request matches our
398 # WILL/WONT requests indicate the other end would like to
399 # {en,dis}able a mode. We are expected to respond with
402 # If we want a particular mode {en,dis}abled then we may start
403 # negotiation of that mode with a WILL/WONT.
405 # We want the other end to perform echo by default so we will
406 # DO any request for ECHO and DONT all other requests.
410 my ($self, $mux, $fh, $input) = @_;
413 print "Payload::mux_input [$self] mux<$mux> fh<$fh> input<$$input>\n"
416 while ($$input ne "") {
418 if ($$input =~ s/^([^$TN_IAC]+)//) {
419 # Data coming in from the payload, this needs to go to
420 # all of the clients.
421 $self->cout->junctionInput($self, $1);
426 if ($$input =~ s/^$TN_IAC$TN_SB([^$TN_SE]+)$TN_SE//) {
427 print "SB\n" if ($main::debug
);
430 # IAC,[DO|DONT|WILL|WONT],<what>
431 if ($$input =~ s/^$TN_IAC$TN_DO(.)//) {
432 my $c = unpack("C", $1);
433 print "DO<$c:$1>\n" if ($main::debug
);
434 # We are DONT on all options so WONT all requests.
435 $self->junctionInput($self, "$TN_IAC$TN_WONT$1");
438 if ($$input =~ s/^$TN_IAC$TN_DONT(.)//) {
439 my $c = unpack("C", $1);
440 print "DONT<$c:$1>\n" if ($main::debug
);
441 # We are already DONT on all options, no reply.
444 if ($$input =~ s/^$TN_IAC$TN_WILL(.)//) {
445 my $c = unpack("C", $1);
446 print "WILL<$c:$1>\n" if ($main::debug
);
448 my $reply = $TN_DONT;
449 if ($1 == $TNOPT_ECHO || $1 == $TNOPT_SGA) {
452 $self->junctionInput($self, "$TN_IAC$reply$1");
455 if ($$input =~ s/^$TN_IAC$TN_WONT(.)//) {
456 my $c = unpack("C", $1);
457 print "WONT<$c:$1>\n" if ($main::debug
);
458 $self->junctionInput($self, "$TN_IAC$TN_DONT$1");
462 if ($$input =~ s/^$TN_IAC([^$TN_SB$TN_DO$TN_DONT$TN_WILL$TN_WONT])//) {
463 print "OPTION<$1>\n" if ($main::debug
);
468 if ($$input =~ /^$TN_IAC/) {
474 my ($self, $from, $data) = @_;
477 print "Payload::junctionInput [$self] from<$from> data<$data>\n"
480 ##$self->{'mux'}->write($self->{'wfh'}, $data);
481 # If we are connected ...
482 if ($self->{'wfh'}) {
483 $fh = $self->{'wfh'};
486 $from->junctionInput($self, "<<<NOT CONNECTED>>>\n");
491 my ($self, $mux, $fh) = @_;
494 print "Payload::mux_eof [$self] mux<$mux> fh<$fh>\n" if ($main::debug
);
496 # Check for a restartable connection.
497 if ($self->can("restart")) {
498 my ($timeout) = $self->retry_timeout();
500 $self->cout->junctionInput($self,
501 "<<<PAYLOAD LOST ... retrying in $timeout secs>>>\n");
503 # Schedule a timeout to trigger a reconnect.
504 Callback
->new($mux, $self, $timeout);
507 $self->cout->junctionEOF($self);
508 $self->cin->junctionRemove($self);
510 Payload
->lost($self->name, $self);
513 # Close down the payload ...
514 $mux->close($self->{'rfh'});
515 ##$mux->remove($self->{'wfh'});
519 my ($self, $mux, $fh) = @_;
523 #close($self->{'rfh'});
524 close($self->{'wfh'});
525 undef $self->{'rfh'};
526 undef $self->{'wfh'};
528 if ($self->{'pid'}) {
529 # Kill the process group for this pid.
530 kill 1, 0 - $self->{'pid'};
531 undef $self->{'pid'};
535 sub callback_timeout
{
538 print "Payload::callback_timeout [$self]\n" if ($main::debug
);
540 if ($self->enabled) {
541 $self->cout->junctionInput($self, "<<<PAYLOAD RESTART>>>\n");
542 $self->openPayload();
544 $self->cout->junctionInput($self, "<<<PAYLOAD DISABLED>>>\n");
551 if ($self->connected) {
552 $self->cout->junctionInput($self, "<<<PAYLOAD CLOSED>>>\n");
554 # Close down the payload ...
555 $self->mux->close($self->{'rfh'});
557 if ($self->enabled) {
568 if (!$self->connected) {
569 if ($self->can("restart")) {
579 my ($self, $cmd, $msg) = @_;
581 push(@
{$self->{'help'}}, [ $cmd, $msg ]);
587 [ 'break', 'send a break sequence' ]
590 if (defined $self->{'help'}) {
591 ( @entries, @
{$self->{'help'}} );
598 my ($self, $cmd, @a) = @_;
600 $self->{'cmd'}->{$cmd} = [ @a ];
603 my ($self, $client, $cmd, $a) = @_;
606 print "Payload::commandExec [$self] client<$client> cmd<$cmd> a<$a>\n"
609 $exe = $self->{'cmd'}->{$cmd};
611 if ($cmd eq "break") {
612 # Send a telnet break ...
613 $self->junctionInput($self, "$TN_IAC$TN_BREAK");
616 } elsif ($cmd eq "close") {
617 if (!$self->enabled && !$self->connected) {
618 $client->junctionInput($self,
619 "console already closed\n");
621 } elsif ($self->closePayload()) {
622 $self->cout->junctionInput($self, "(" . $client->id .
623 ") triggered a console close\n");
626 $client->junctionInput($self, "ERROR: close failed\n");
630 } elsif ($cmd eq "open") {
631 if ($self->connected) {
632 $client->junctionInput($self, "console already open\n");
634 } elsif ($self->openPayload()) {
635 $self->cout->junctionInput($self, "(" . $client->id .
636 ") triggered a console open\n");
639 $client->junctionInput($self, "open failed\n");
644 # Ensure we error if we have no command.
646 $client->junctionInput($self, "Command not recognised\n");
650 my ($msg, $run) = @
{$exe};
652 $self->cout->junctionInput($self, "(" . $client->id .
656 local(*IN
, *OUT
, *ERR
);
657 my ($cmd, @args) = split(m/'(.*?)'|"(.*?)"|\s(.*?)\s/g, $run . " $a");
659 for (my $i=0; $i < @args; $i++) {
660 next if not $args[$i];
661 push(@opts, $args[$i]);
663 my $pid = IPC
::Open3
::open3
(*IN
, *OUT
, *ERR
, $cmd, @opts);
666 # XXX: this should not be happening here.
667 $self->mux->add(*OUT
{IO
});
668 my $data = ClientData
->new($self->mux, *OUT
{IO
});
669 $data->{'id'} = "cmd:$cmd stdout";
671 $data->payload($self);
672 $data->cout($self->cout);
674 # XXX: this should not be happening here.
675 $self->mux->add(*ERR
{IO
});
676 my $data = ClientData
->new($self->mux, *ERR
{IO
});
677 $data->{'id'} = "cmd:$cmd stderr";
679 $data->payload($self);
680 $data->cout($client);
686 print "Payload::DESTROY [$self]\n" if ($main::debug
);
690 # PAYLOAD APPLICATION: handles forking off a command as a payload.
692 package PayloadApplication
;
696 my ($self, $cmd) = @_;
697 my ($pid, $wfh, $rfh);
699 print "PayloadApplication::initialise [$self] cmd<$cmd>"
702 $self->SUPER::initialise
();
704 # XXX: we cannot use the write buffering offered by the mux package
705 # without suffering a read error from the PWR file handle, there
706 # is no a way to add a write-only channel.
708 $self->{'args'} = $cmd;
710 # Start the payload ...
711 $pid = IPC
::Open3
::open3
($wfh, $rfh, 0, "setsid " . $cmd);
713 $self->{'rfh'} = $rfh;
714 $self->{'wfh'} = $wfh;
715 $self->{'pid'} = $pid;
717 $self->mux->add($rfh);
720 $self->mux->set_callback_object($self, $rfh);
721 ##$mux->set_callback_object($self, $wfh);
723 print "SHARE PAYLOAD: $self $wfh/$rfh (to
$cmd) [fd
=" .
724 fileno($wfh) . "/" . fileno($rfh) . "]\n" if ($main::debug);
725 print "payload
'$cmd' on fd
=" . fileno($wfh) . "/" .
734 $self->initialise($self->{'args'});
738 # PAYLOAD SOCKET: handles a network socket as payload.
740 package PayloadSocket;
744 my ($self, $addr) = @_;
747 print "PayloadSocket
::initialise
[$self] addr
<$addr>\n"
750 $self->SUPER::initialise();
752 $self->{'args'} = $addr;
754 # Create a listening socket and add it to the multiplexor.
755 my $payload = new IO::Socket::INET(PeerAddr => $addr);
758 if ($self->can("restart
")) {
759 my ($timeout) = $self->retry_timeout();
761 $self->cout->junctionInput($self,
762 "<<<PAYLOAD ERROR ($!) ... retrying in $timeout secs>>>\n");
763 # Schedule a timeout to trigger a reconnect.
764 Callback->new($self->mux, $self, $timeout);
766 $self->cout->junctionEOF($self);
767 $self->cin->junctionRemove($self);
769 Payload->lost($self->name, $self);
773 $self->{'rfh'} = $payload;
774 $self->{'wfh'} = $payload;
776 print "SHARE PAYLOAD: $self $payload (to
$addr) [fd
=" .
777 fileno($payload) . "]\n" if ($main::debug);
778 print "payload
'$addr' on fd
=" . fileno($payload) . "\n";
779 $self->mux->add($payload);
781 $self->mux->set_callback_object($self, $payload);
784 print "SHARE PAYLOAD
: $self $payload ... done
\n" if ($main::debug);
792 $self->initialise($self->{'args'});
796 # CLIENT: general client object, represents a remote client channel
801 my ($class, $mux, $fh) = @_;
802 my $self = bless { 'mux' => $mux,
803 'fh' => $fh }, $class;
805 print "Client
::new
[$self] mux
<$mux> fh
<$fh>\n"
814 my ($class, $from) = @_;
816 my $self = bless { %{$from} }, $class;
818 print "Client
::clone
[$self] from
<$from>\n" if ($main::debug);
828 if (@_) { $self->{'mux'} = shift }
829 return $self->{'mux'};
833 if (@_) { $self->{'payload'} = shift }
834 return $self->{'payload'};
838 if (@_) { $self->{'fh'} = shift }
839 return $self->{'fh'};
843 if (@_) { $self->{'id'} = shift }
844 return $self->{'id'};
848 if (@_) { $self->{'announce'} = shift }
849 return $self->{'announce'};
853 if (@_) { $self->{'cout'} = shift }
854 return $self->{'cout'};
859 $self->{'cin'}->junctionRemove($self) if ($self->{'cin'});
860 $self->{'cin'} = shift;
861 $self->{'cin'}->junctionAdd($self) if ($self->{'cin'} != undef);
863 return $self->{'cin'};
869 print "Client
::initialise
[$self]\n" if ($main::debug);
871 $self->mux->set_callback_object($self, $self->fh);
875 my ($self, $from, $data) = @_;
877 print "Client
::junctionInput
[$self] data
<$data>\n" if ($main::debug);
879 $self->mux->write($self->fh, $data);
882 my ($self, $from, $data) = @_;
884 print "Client
::junctionEOF
[$self] data
<$data>\n" if ($main::debug);
890 my ($self, $mux, $fh, $input) = @_;
892 print "Client
::mux_eof
[$self] mux
<$mux> fh
<$fh> input
<$input>\n"
895 # Handle any pending input, then remove myself from the clients list.
896 $self->mux_input($mux, $fh, $input);
900 # Tell the multiplexor we no longer are using this channel.
901 $mux->shutdown($fh, 1);
904 my ($self, $mux, $fn) = @_;
906 print "Client
::close [$self]\n" if ($main::debug);
908 if ($self->announce) {
909 $self->announce->junctionInput($self, "(" . $self->id .
912 print "$self->{'id'} disconnected
\n";
918 print "Client
::shutdown [$self]\n" if ($main::debug);
920 # Close myself down and tell the payload.
921 $self->mux->shutdown($self->fh, 2);
926 print "Client
::DESTROY
[$self]\n" if ($main::debug);
930 # CLIENT CMD: represents a client whilst in command mode, when we have commited
931 # to connecting this will pass the client connection off to a ClientData
938 my ($self, $mux, $fh, $input) = @_;
940 print "Client
::shutdown [$self] mux
<$mux> fh
<$fh> input
<$$input>\n"
943 while ($$input =~ s/^(.*?)\n//) {
944 my ($cmd, $args) = split(' ', $1, 2);
945 my (%args) = Conmux::decodeArgs($args);
948 'status' => 'ENOSYS unknown command',
951 # XXX: check authentication if required and reject the
952 # command out of hand - leak _nothing_.
953 if (!defined $args{'id'}) {
954 $reply->{'status'} = 'EACCES identifier required';
957 # They are who they say they are, record who that is.
958 $self->{'id'} = $args{'id'};
960 if ($cmd eq "CONNECT
") {
961 # Switch over to data mode, hand this connection off
962 # to a data client instance, I am done.
963 my ($data, $to, $in, $out);
964 $data = ClientData->clone($self);
968 $reply->{'status'} = "EINVAL CONNECT
" .
969 " requires
'to' specifier
";
972 my $payload = Payload->lookup($to);
973 if (!defined $payload) {
974 $reply->{'status'} = "EINVAL
'$to' not a
" .
975 " valid destination specifier
";
979 $reply->{'status'} = 'OK';
981 # Get the payload title and pass that back.
982 $reply->{'title'} = $payload->title . ' [channel ' .
983 $payload->state() . ']';
984 $reply->{'state'} = $payload->state();
985 # Get connected clients and pass back as the motd
986 for my $cl (keys(%{$payload->cout})) {
987 $reply->{'motd'} .= '(' . $payload->cout->{$cl}->id;
988 $reply->{'motd'} .= ") is already connected
\n";
991 $data->payload($payload);
992 $args{'type'} = 'client' if (!$args{'type'});
993 if ($args{'type'} eq 'status') {
994 $data->cout($payload->cout);
995 } elsif ($args{'type'} eq 'client') {
996 if (!$args{'hide'}) {
997 $data->announce($payload->cout);
998 $payload->cout->junctionInput(
999 $self, "(" . $self->id .
1002 $data->cin($payload->cout);
1003 $data->cout($payload->cin);
1005 $reply->{'status'} = "EINVAL
'$args{'type
'}' " .
1006 "not a valid destination type
";
1010 print "$self->{'id'} connected to
$to/$args{'type'}\n";
1012 $self->junctionInput($self,
1013 Conmux::encodeArgs($reply) . "\n");
1015 # Don't handle any more input - its not going to be
1021 # We're done, send back our response to this.
1022 $self->junctionInput($self, Conmux::encodeArgs($reply) . "\n");
1027 # CLIENT DATA: handles a client connection when in data mode, attaches
1028 # the client connection to the relevant junction.
1034 [ 'msg', 'send a message to all connected clients' ],
1035 [ 'quit', 'disconnect from the console' ],
1038 my ($self, $mux, $fh, $input) = @_;
1040 print "ClientData
::mux_input
[$self] mux
<$mux> fh
<$fh> input
<$$input>\n"
1043 while ($$input ne "") {
1044 if ($self->{'cmd'} eq '') {
1045 # Check for an incomplete escape ... wait for more.
1046 if ($$input =~ /^~$/s) {
1049 if ($$input =~ s/^~\$//s) {
1050 $self->{'cmd'} = '>';
1051 my $title = $self->payload->title;
1052 $self->junctionInput($self, "\r\nCommand
($title)> ");
1055 # Its not an escape ... pass it on.
1056 # Ship anything before that cannot be the escape.
1057 if ($$input =~ s/^(.[^~]*)(~|$)/\2/s) {
1058 # Data coming in from the client, send it to
1060 $self->cout->junctionInput($self, $1);
1063 # Consume characters upto a newline, echo them back
1064 # to the client as we go.
1065 while ($$input =~ s/^([^\r\n])//) {
1067 if ($c eq "\b" || $c eq "\x7f") {
1068 if (length($self->{'cmd'}) > 1) {
1070 substr($self->{'cmd'},
1076 $self->{'cmd'} .= $c;
1078 $self->junctionInput($self, $c);
1080 # If we arn't at a newline, then wait for more input.
1081 if ($$input !~ s/^[\r\n]+//) {
1085 $self->junctionInput($self, "\n");
1087 my ($cmd, $a) = split(' ', substr($self->{'cmd'},
1089 $self->{'cmd'} = '';
1093 } elsif ($cmd eq 'help') {
1094 my @cmds = $self->payload->commandHelp();
1097 my $help = "Conmux commands
:\n";
1098 for $ent (@cmds, @help) {
1099 $help .= sprintf(" %-20s
%s\n",
1100 $ent->[0], $ent->[1]);
1102 $self->junctionInput($self, $help);
1104 } elsif ($cmd eq 'quit') {
1107 } elsif ($cmd eq 'msg') {
1108 $self->cin->junctionInput($self,
1109 "($self->{'id'}) $a\n");
1111 # Not a client command ... pass it to the payload.
1113 $self->payload->commandExec($self, $cmd, $a);
1120 # LIBRARY: split a string honouring quoting.
1126 my ($pos, @args, $argc, $quote, $real, $c, $inc);
1136 while (substr($str, $pos, 1) eq " ") {
1139 for (; $pos < length($str); $pos++) {
1140 $c = substr($str, $pos, 1);
1141 if ($quote != 2 && $c eq '\\') {
1144 $c = substr($str, $pos, 1);
1149 if ($quote != 2 && $c eq '"' && !$real) {
1151 } elsif ($quote != 1 && $c eq "'" && !$real) {
1153 } elsif ($c eq " " && $quote == 0 && !$real) {
1154 while (substr($str, $pos, 1) eq " ") {
1172 # MAIN: makes the IO multiplexor, junction, listener and payload and stitches
1173 # them all together.
1178 if ($#ARGV != 0 && $#ARGV != 3) {
1179 print STDERR "Usage
: $P <config file
>\n";
1180 print STDERR " $P <local port
> <title
> socket <host
>:<port
>\n";
1181 print STDERR " $P <local port
> <title
> cmd
<cmd
>\n";
1186 my ($lport, $title, $what, $arg) = @ARGV;
1188 "listener
'$lport'",
1189 "'$what' console
'$title' '$arg'"
1193 open(CONF, '<', $cf) || die "$P: $cf: open failed
- $!\n";
1198 # Make a new multiplexer.
1199 my $mux = new IO::Multiplex;
1201 my ($line, $seg, $listener, $payload);
1204 # Handle comments, blank lines and line continuation.
1205 chomp($seg); $seg =~ s/^\s+//;
1206 next if ($seg =~ /^#/);
1208 if ($line =~ m/\\$/) {
1214 my ($cmd, @a) = parse($line);
1217 if ($cmd eq "listener
") {
1219 warn "$P: Usage
: listener
<port
>\n" .
1225 my ($rhost, $rname);
1228 if ($lport =~ m@^\d+$@) {
1229 # Already in the right format.
1232 } elsif ($lport =~ m@(.*)/(.*)@) {
1233 ($rhost, $rname, $lport) = ($1, $2, 0);
1237 ($rhost, $rname, $lport) = ('-', $lport, 0);
1240 # Create the client listener socket.
1241 $listener = ListenerSocket->new($mux, $lport);
1243 # Register us with the registry.
1245 Conmux::Registry::add($rhost, $rname, $listener->address);
1248 } elsif ($cmd eq 'socket') {
1250 warn "$P: Usage
: socket <name
> <title
> <host
:port
>\n" .
1254 my ($name, $title, $sock) = @a;
1256 # Create the payload.
1257 $payload = PayloadSocket->new($name, $title, $mux, $sock);
1259 } elsif ($cmd eq 'application') {
1261 warn "$P: Usage
: application
<name
> <title
> <host
:port
>\n" .
1265 my ($name, $title, $app) = @a;
1267 $payload = PayloadApplication->new($name, $title, $mux, $app);
1269 } elsif ($cmd eq 'command') {
1271 warn "$P: Usage
: command
<name
> <msg
> <cmd
>\n" .
1275 my ($name, $msg, $cmd) = @a;
1277 $payload->commandAdd($name, $msg, $cmd);
1279 } elsif ($cmd eq 'help') {
1281 warn "$P: Usage
: $cmd <name
> <msg
>\n" .
1285 my ($name, $msg) = @a;
1287 $payload->helpAdd($name, $msg);
1289 warn "$P: $cmd: unknown configuration command
\n";
1293 # Hand over to the multiplexor.
1295 eval { $mux->loop; };
1297 } while ($@ =~ /^Use of freed value in iteration/);