File Coverage

blib/lib/chat2.pl
Criterion Covered Total %
statement 9 165 5.4
branch 0 70 0.0
condition 0 2 0.0
subroutine 3 11 27.2
pod n/a
total 12 248 4.8


line stmt bran cond sub pod time code
1             # chat.pl: chat with a server
2             #
3             # This library is no longer being maintained, and is included for backward
4             # compatibility with Perl 4 programs which may require it.
5             #
6             # In particular, this should not be used as an example of modern Perl
7             # programming techniques.
8             #
9             # Suggested alternative: Socket
10             #
11             # Based on: V2.01.alpha.7 91/06/16
12             # Randal L. Schwartz (was )
13             # multihome additions by A.Macpherson@bnr.co.uk
14             # allow for /dev/pts based systems by Joe Doupnik
15              
16             package chat;
17              
18 1     1   1084 no warnings "ambiguous";
  1         2  
  1         31  
19              
20 1     1   507 use Socket ();
  1         3089  
  1         1393  
21              
22             {
23             $pf_inet = Socket::PF_INET;
24             $sock_stream = Socket::SOCK_STREAM;
25             local($name, $aliases, $proto) = getprotobyname( 'tcp' );
26             $tcp_proto = $proto;
27             }
28              
29              
30             $sockaddr = 'S n a4 x8';
31             chop($thishost = `hostname`);
32              
33             # *S = symbol for current I/O, gets assigned *chatsymbol....
34             $next = "chatsymbol000000"; # next one
35             $nextpat = "^chatsymbol"; # patterns that match next++, ++, ++, ++
36              
37              
38             ## $handle = &chat::open_port("server.address",$port_number);
39             ## opens a named or numbered TCP server
40              
41             sub open_port { ## public
42 0     0     local($server, $port) = @_;
43              
44 0           local($serveraddr,$serverproc);
45              
46             # We may be multi-homed, start with 0, fixup once connexion is made
47 0           $thisaddr = "\0\0\0\0" ;
48 0           $thisproc = pack($sockaddr, 2, 0, $thisaddr);
49              
50 0           *S = ++$next;
51 0 0         if ($server =~ /^(\d+)+\.(\d+)\.(\d+)\.(\d+)$/) {
52 0           $serveraddr = pack('C4', $1, $2, $3, $4);
53             } else {
54 0           local(@x) = gethostbyname($server);
55 0 0         return undef unless @x;
56 0           $serveraddr = $x[4];
57             }
58 0           $serverproc = pack($sockaddr, 2, $port, $serveraddr);
59 0 0         unless (socket(S, $pf_inet, $sock_stream, $tcp_proto)) {
60 0           ($!) = ($!, close(S)); # close S while saving $!
61 0           return undef;
62             }
63 0 0         unless (bind(S, $thisproc)) {
64 0           ($!) = ($!, close(S)); # close S while saving $!
65 0           return undef;
66             }
67 0 0         unless (connect(S, $serverproc)) {
68 0           ($!) = ($!, close(S)); # close S while saving $!
69 0           return undef;
70             }
71             # We opened with the local address set to ANY, at this stage we know
72             # which interface we are using. This is critical if our machine is
73             # multi-homed, with IP forwarding off, so fix-up.
74 0           local($fam,$lport);
75 0           ($fam,$lport,$thisaddr) = unpack($sockaddr, getsockname(S));
76 0           $thisproc = pack($sockaddr, 2, 0, $thisaddr);
77             # end of post-connect fixup
78 0           select((select(S), $| = 1)[0]);
79 0           $next; # return symbol for switcharound
80             }
81              
82             ## ($host, $port, $handle) = &chat::open_listen([$port_number]);
83             ## opens a TCP port on the current machine, ready to be listened to
84             ## if $port_number is absent or zero, pick a default port number
85             ## process must be uid 0 to listen to a low port number
86              
87             sub open_listen { ## public
88              
89 0     0     *S = ++$next;
90 0   0       local($thisport) = shift || 0;
91 0           local($thisproc_local) = pack($sockaddr, 2, $thisport, $thisaddr);
92 0           local(*NS) = "__" . time;
93 0 0         unless (socket(NS, $pf_inet, $sock_stream, $tcp_proto)) {
94 0           ($!) = ($!, close(NS));
95 0           return undef;
96             }
97 0 0         unless (bind(NS, $thisproc_local)) {
98 0           ($!) = ($!, close(NS));
99 0           return undef;
100             }
101 0 0         unless (listen(NS, 1)) {
102 0           ($!) = ($!, close(NS));
103 0           return undef;
104             }
105 0           select((select(NS), $| = 1)[0]);
106 0           local($family, $port, @myaddr) =
107             unpack("S n C C C C x8", getsockname(NS));
108 0           $S{"needs_accept"} = *NS; # so expect will open it
109 0           (@myaddr, $port, $next); # returning this
110             }
111              
112             ## $handle = &chat::open_proc("command","arg1","arg2",...);
113             ## opens a /bin/sh on a pseudo-tty
114              
115             sub open_proc { ## public
116 0     0     local(@cmd) = @_;
117              
118 0           *S = ++$next;
119 0           local(*TTY) = "__TTY" . time;
120 0           local($pty,$tty) = &_getpty(S,TTY);
121 0 0         die "Cannot find a new pty" unless defined $pty;
122 0           $pid = fork;
123 0 0         die "Cannot fork: $!" unless defined $pid;
124 0 0         unless ($pid) {
125 0           close STDIN; close STDOUT; close STDERR;
  0            
  0            
126 0           setpgrp(0,$$);
127 0 0         if (open(DEVTTY, "/dev/tty")) {
128 0           ioctl(DEVTTY,0x20007471,0); # XXX s/b &TIOCNOTTY
129 0           close DEVTTY;
130             }
131 0           open(STDIN,"<&TTY");
132 0           open(STDOUT,">&TTY");
133 0           open(STDERR,">&STDOUT");
134 0 0         die "Oops" unless fileno(STDERR) == 2; # sanity
135 0           close(S);
136 0           exec @cmd;
137 0           die "Cannot exec @cmd: $!";
138             }
139 0           close(TTY);
140 0           $next; # return symbol for switcharound
141             }
142              
143             # $S is the read-ahead buffer
144              
145             ## $return = &chat::expect([$handle,] $timeout_time,
146             ## $pat1, $body1, $pat2, $body2, ... )
147             ## $handle is from previous &chat::open_*().
148             ## $timeout_time is the time (either relative to the current time, or
149             ## absolute, ala time(2)) at which a timeout event occurs.
150             ## $pat1, $pat2, and so on are regexs which are matched against the input
151             ## stream. If a match is found, the entire matched string is consumed,
152             ## and the corresponding body eval string is evaled.
153             ##
154             ## Each pat is a regular-expression (probably enclosed in single-quotes
155             ## in the invocation). ^ and $ will work, respecting the current value of $*.
156             ## If pat is 'TIMEOUT', the body is executed if the timeout is exceeded.
157             ## If pat is 'EOF', the body is executed if the process exits before
158             ## the other patterns are seen.
159             ##
160             ## Pats are scanned in the order given, so later pats can contain
161             ## general defaults that won't be examined unless the earlier pats
162             ## have failed.
163             ##
164             ## The result of eval'ing body is returned as the result of
165             ## the invocation. Recursive invocations are not thought
166             ## through, and may work only accidentally. :-)
167             ##
168             ## undef is returned if either a timeout or an eof occurs and no
169             ## corresponding body has been defined.
170             ## I/O errors of any sort are treated as eof.
171              
172             $nextsubname = "expectloop000000"; # used for subroutines
173              
174             sub expect { ## public
175 0 0   0     if ($_[0] =~ /$nextpat/) {
176 0           *S = shift;
177             }
178 0           local($endtime) = shift;
179              
180 0           local($timeout,$eof) = (1,1);
181 0           local($caller) = caller;
182 0           local($rmask, $nfound, $timeleft, $thisbuf);
183 0           local($cases, $pattern, $action, $subname);
184 0 0         $endtime += time if $endtime < 600_000_000;
185              
186 0 0         if (defined $S{"needs_accept"}) { # is it a listen socket?
187 0           local(*NS) = $S{"needs_accept"};
188 0           delete $S{"needs_accept"};
189 0           $S{"needs_close"} = *NS;
190 0 0         unless(accept(S,NS)) {
191 0           ($!) = ($!, close(S), close(NS));
192 0           return undef;
193             }
194 0           select((select(S), $| = 1)[0]);
195             }
196              
197             # now see whether we need to create a new sub:
198              
199 0 0         unless ($subname = $expect_subname{$caller,@_}) {
200             # nope. make a new one:
201 0           $expect_subname{$caller,@_} = $subname = $nextsubname++;
202              
203 0           $cases .= <<"EDQ"; # header is funny to make everything elsif's
204             sub $subname {
205             LOOP: {
206             if (0) { ; }
207             EDQ
208 0           while (@_) {
209 0           ($pattern,$action) = splice(@_,0,2);
210 0 0         if ($pattern =~ /^eof$/i) {
    0          
211 0           $cases .= <<"EDQ";
212             elsif (\$eof) {
213             package $caller;
214             $action;
215             }
216             EDQ
217 0           $eof = 0;
218             } elsif ($pattern =~ /^timeout$/i) {
219 0           $cases .= <<"EDQ";
220             elsif (\$timeout) {
221             package $caller;
222             $action;
223             }
224             EDQ
225 0           $timeout = 0;
226             } else {
227 0           $pattern =~ s#/#\\/#g;
228 0           $cases .= <<"EDQ";
229             elsif (\$S =~ /$pattern/) {
230             \$S = \$';
231             package $caller;
232             $action;
233             }
234             EDQ
235             }
236             }
237 0 0         $cases .= <<"EDQ" if $eof;
238             elsif (\$eof) {
239             undef;
240             }
241             EDQ
242 0 0         $cases .= <<"EDQ" if $timeout;
243             elsif (\$timeout) {
244             undef;
245             }
246             EDQ
247 0           $cases .= <<'ESQ';
248             else {
249             $rmask = "";
250             vec($rmask,fileno(S),1) = 1;
251             ($nfound, $rmask) =
252             select($rmask, undef, undef, $endtime - time);
253             if ($nfound) {
254             $nread = sysread(S, $thisbuf, 1024);
255             if ($nread > 0) {
256             $S .= $thisbuf;
257             } else {
258             $eof++, redo LOOP; # any error is also eof
259             }
260             } else {
261             $timeout++, redo LOOP; # timeout
262             }
263             redo LOOP;
264             }
265             }
266             }
267             ESQ
268 0 0         eval $cases; die "$cases:\n$@" if $@;
  0            
269             }
270 0           $eof = $timeout = 0;
271 0           $subname->();
272             }
273              
274             ## &chat::print([$handle,] @data)
275             ## $handle is from previous &chat::open().
276             ## like print $handle @data
277              
278             sub print { ## public
279 0 0   0     if ($_[0] =~ /$nextpat/) {
280 0           *S = shift;
281             }
282              
283 0           local $out = join $, , @_;
284 0           syswrite(S, $out, length $out);
285 0 0         if( $chat::debug ){
286 0           print STDERR "printed:";
287 0           print STDERR @_;
288             }
289             }
290              
291             ## &chat::close([$handle,])
292             ## $handle is from previous &chat::open().
293             ## like close $handle
294              
295             sub close { ## public
296 0 0   0     if ($_[0] =~ /$nextpat/) {
297 0           *S = shift;
298             }
299 0           close(S);
300 0 0         if (defined $S{"needs_close"}) { # is it a listen socket?
301 0           local(*NS) = $S{"needs_close"};
302 0           delete $S{"needs_close"};
303 0           close(NS);
304             }
305             }
306              
307             ## @ready_handles = &chat::select($timeout, @handles)
308             ## select()'s the handles with a timeout value of $timeout seconds.
309             ## Returns an array of handles that are ready for I/O.
310             ## Both user handles and chat handles are supported (but beware of
311             ## stdio's buffering for user handles).
312              
313             sub select { ## public
314 0     0     local($timeout) = shift;
315 0           local(@handles) = @_;
316 0           local(%handlename) = ();
317 0           local(%ready) = ();
318 0           local($caller) = caller;
319 0           local($rmask) = "";
320 0           for (@handles) {
321 0 0         if (/$nextpat/o) { # one of ours... see if ready
322 0           local(*SYM) = $_;
323 0 0         if (length($SYM)) {
324 0           $timeout = 0; # we have a winner
325 0           $ready{$_}++;
326             }
327 0           $handlename{fileno($_)} = $_;
328             } else {
329 0 0         $handlename{fileno(/(?:::|')/ ? $_ : "$caller\::$_")} =
330             $_;
331             }
332             }
333 0           for (sort keys %handlename) {
334 0           vec($rmask, $_, 1) = 1;
335             }
336 0           select($rmask, undef, undef, $timeout);
337 0           for (sort keys %handlename) {
338 0 0         $ready{$handlename{$_}}++ if vec($rmask,$_,1);
339             }
340 0           sort keys %ready;
341             }
342              
343             # ($pty,$tty) = $chat::_getpty(PTY,TTY):
344             # internal procedure to get the next available pty.
345             # opens pty on handle PTY, and matching tty on handle TTY.
346             # returns undef if can't find a pty.
347             # Modify "/dev/pty" to "/dev/pts" for Dell Unix v2.2 (aka SVR4.04). Joe Doupnik.
348              
349             sub _getpty { ## private
350 0     0     local($_PTY,$_TTY) = @_;
351 1     1   525 $_PTY =~ s/^([^':]+)$/(caller)[$[]."::".$1/e;
  1         246  
  1         289  
  0            
  0            
352 0           $_TTY =~ s/^([^':]+)$/(caller)[$[]."::".$1/e;
  0            
353 0           local($pty, $tty, $kind);
354 0 0         if( -e "/dev/pts000" ){ ## mods by Joe Doupnik Dec 1992
355 0           $kind = "pts"; ## SVR4 Streams
356             } else {
357 0           $kind = "pty"; ## BSD Clist stuff
358             }
359 0           for $bank (112..127) {
360 0 0         next unless -e sprintf("/dev/$kind%c0", $bank);
361 0           for $unit (48..57) {
362 0           $pty = sprintf("/dev/$kind%c%c", $bank, $unit);
363 0 0         open($_PTY,"+>$pty") || next;
364 0           select((select($_PTY), $| = 1)[0]);
365 0           ($tty = $pty) =~ s/pty/tty/;
366 0 0         open($_TTY,"+>$tty") || next;
367 0           select((select($_TTY), $| = 1)[0]);
368 0           system "stty nl>$tty";
369 0           return ($pty,$tty);
370             }
371             }
372 0           undef;
373             }
374              
375             1;