File Coverage

blib/lib/IO/Socket/Telnet.pm
Criterion Covered Total %
statement 72 117 61.5
branch 25 62 40.3
condition 3 20 15.0
subroutine 11 16 68.7
pod 1 8 12.5
total 112 223 50.2


line stmt bran cond sub pod time code
1             package IO::Socket::Telnet;
2 9     9   348377 use strict;
  9         23  
  9         396  
3 9     9   51 use warnings;
  9         20  
  9         298  
4 9     9   62 use base 'IO::Socket::INET';
  9         13  
  9         14904  
5              
6             our $VERSION = '0.04';
7              
8             sub new {
9 9     9 1 2029 my $class = shift;
10 9         30 my %args = @_;
11              
12 9 50 0     101 $args{PeerPort} ||= 23
      33        
13             if exists $args{PeerAddr}
14             || exists $args{PeerHost};
15              
16 9         124 my $self = $class->SUPER::new(%args);
17 9 50       1248 return undef if !defined($self);
18              
19 9         23 ${*$self}{telnet_mode} = 'normal';
  9         36  
20 9         23 ${*$self}{telnet_sb_buffer} = '';
  9         27  
21              
22 9         37 return $self;
23             }
24              
25             sub recv {
26 0     0 0 0 my $self = shift;
27              
28 0         0 $self->SUPER::recv(@_);
29 0         0 $_[0] = $self->_parse($_[0]);
30             };
31              
32             sub telnet_simple_callback {
33 1     1 0 8 my $self = shift;
34 1 50       5 ${*$self}{telnet_simple_cb} = $_[0] if @_;
  1         4  
35 1         2 ${*$self}{telnet_simple_cb};
  1         4  
36             }
37              
38             sub telnet_complex_callback {
39 1     1 0 10 my $self = shift;
40 1 50       5 ${*$self}{telnet_complex_cb} = $_[0] if @_;
  1         5  
41 1         2 ${*$self}{telnet_complex_cb};
  1         4  
42             }
43              
44             our @options = qw(
45             BINARY ECHO RCP SGA NAMS STATUS TM RCTE NAOL NAOP NAOCRD NAOHTS NAOHTD
46             NAOFFD NAOVTS NAOVTD NAOLFD XASCII LOGOUT BM DET SUPDUP SUPDUPOUTPUT SNDLOC
47             TTYPE EOR TUID OUTMRK TTYLOC VT3270REGIME X3PAD NAWS TSPEED LFLOW LINEMODE
48             XDISPLOC OLD_ENVIRON AUTHENTICATION ENCRYPT NEW_ENVIRON
49             );
50              
51             our @meta;
52              
53             my $IAC = chr(255); $meta[255] = 'IAC';
54             my $SB = chr(250); $meta[250] = 'SB';
55             my $SE = chr(240); $meta[240] = 'SE';
56              
57             my $WILL = chr(251); $meta[251] = 'WILL';
58             my $WONT = chr(252); $meta[252] = 'WONT';
59             my $DO = chr(253); $meta[253] = 'DO';
60             my $DONT = chr(254); $meta[254] = 'DONT';
61              
62             our %options;
63             our %meta;
64              
65             {
66 9     9   302944 no warnings 'uninitialized';
  9         22  
  9         14885  
67             @options{ @options } = 0 .. @options;
68             @meta{ @meta } = 0 .. @meta;
69             }
70              
71             sub will {
72 0     0 0 0 my ($self, $opt) = @_;
73 0 0       0 if (exists $options{$opt}) {
74 0         0 $opt = $options{$opt};
75             }
76 0         0 $self->send($IAC . $WILL . $opt);
77             }
78              
79             sub wont {
80 0     0 0 0 my ($self, $opt) = @_;
81 0 0       0 if (exists $options{$opt}) {
82 0         0 $opt = $options{$opt};
83             }
84 0         0 $self->send($IAC . $WONT . $opt);
85             }
86              
87             sub do {
88 0     0 0 0 my ($self, $opt) = @_;
89 0 0       0 if (exists $options{$opt}) {
90 0         0 $opt = $options{$opt};
91             }
92 0         0 $self->send($IAC . $DO . $opt);
93             }
94              
95             sub dont {
96 0     0 0 0 my ($self, $opt) = @_;
97 0 0       0 if (exists $options{$opt}) {
98 0         0 $opt = $options{$opt};
99             }
100 0         0 $self->send($IAC . $DONT . $opt);
101             }
102              
103             *WILL = \&will;
104             *WONT = \&wont;
105             *DO = \&do;
106             *DONT = \&dont;
107              
108             # this is a finite state machine. each state can:
109             # add some text to the output buffer
110             # change to a different state
111             # run other code (such as adding text to the subnegotiation buffer)
112              
113             # the states are:
114             # normal: every char is added to the output buffer, except IAC
115             # iac: we've received an IAC, this is the start of a command
116             # if we receive an IAC in state iac, append IAC to the output
117             # buffer and switch back to normal mode (IAC IAC is like \\)
118             # do: IAC DO OPTION: I want you to DO option
119             # dont: IAC DONT OPTION: I want you to not do this option
120             # will: IAC WILL OPTION: I WILL do this option (is this ok?)
121             # wont: IAC WONT OPTION: I WONT do this option (is this ok?)
122             # sb: IAC SB OPTION arbitrary text IAC SE
123             # sbiac: IAC received during "arbitrary text" of sb if we receive an IAC
124             # in this mode, append IAC to the subneg buffer and switch back
125             # to sb mode. if we receive an SE (subneg-end) in this mode,
126             # perform some kind of action and go back to normal mode
127              
128             my %dispatch = (
129             normal => sub {
130             my ($self, $c) = @_;
131             return $c unless $c eq $IAC;
132             return (undef, $IAC);
133             },
134              
135             $IAC => sub {
136             my ($self, $c) = @_;
137             return ($IAC, 'normal') if $c eq $IAC;
138             return (undef, $c) if $c eq $DO || $c eq $DONT
139             || $c eq $WILL || $c eq $WONT
140             || $c eq $SB;
141              
142             # IAC followed by something that we don't know about yet
143             require Carp;
144             Carp::croak "Invalid telnet stream: ... IAC $c (chr ".chr($c).") ...";
145             },
146              
147             $DO => sub {
148             my ($self, $c, $m) = @_;
149             $self->_telnet_simple_callback($m, $c);
150             return (undef, 'normal');
151             },
152              
153             $SB => sub {
154             my ($self, $c) = @_;
155             return (undef, 'sbiac') if $c eq $IAC;
156             ${*$self}{telnet_sb_buffer} .= $c;
157             return;
158             },
159              
160             sbiac => sub {
161             my ($self, $c) = @_;
162              
163             if ($c eq $IAC) {
164             ${*$self}{telnet_sb_buffer} .= $IAC;
165             return (undef, $SB);
166             }
167              
168             if ($c eq $SE) {
169             $self->_telnet_complex_callback(${*$self}{telnet_sb_buffer});
170             ${*$self}{telnet_sb_buffer} = '';
171             return (undef, 'normal');
172             }
173              
174             # IAC followed by something other than IAC and SE.. what??
175             require Carp;
176             Carp::croak "Invalid telnet stream: IAC SE ... IAC $c (chr ".chr($c).") ...";
177             },
178             );
179              
180             $dispatch{$DONT} = $dispatch{$WILL} = $dispatch{$WONT} = $dispatch{$DO};
181              
182             # this takes the input stream and jams it through the FSM
183             sub _parse {
184 49     49   156 my ($self, $in) = @_;
185 49         74 my $out = '';
186              
187             # optimization: if we're in normal mode then we can quickly move all the
188             # input up to the first IAC into the output buffer.
189 49 100       139 if (${*$self}{telnet_mode} eq 'normal') {
  49         225  
190             # if there is no IAC then we can skip telnet entirely
191 32         433 $in =~ s/^([^$IAC]*)//o;
192 32 100       152 return $1 if length $in == 0;
193 22         938 $out = $1;
194             }
195              
196 39         288 for my $c (split '', $in) {
197 122         342 my ($o, $m)
198 122         320 = $dispatch{${*$self}{telnet_mode}}
199 122         149 ->($self, $c, ${*$self}{telnet_mode});
200              
201 122 100       325 defined $o and $out .= $o;
202 122 100       259 defined $m and ${*$self}{telnet_mode} = $m;
  74         227  
203             }
204              
205 39         220 return $out;
206             }
207              
208             # called when we get a full DO/DONT/WILL/WONT
209             sub _telnet_simple_callback {
210 13     13   58 my ($self, $mode, $opt) = @_;
211 13         19 my $response;
212              
213 13 100       16 if (${*$self}{telnet_simple_cb}) {
  13         66  
214             {
215 1         2 my $wopt = ord $opt;
  1         2  
216 1   33     5 $wopt = $options[$wopt] || $wopt;
217              
218 1         3 my $wmode = ord $mode;
219 1   33     4 $wmode = $meta[$wmode] || $wmode;
220              
221 1         3 $response = ${*$self}{telnet_simple_cb}->($self, "$wmode $wopt");
  1         5  
222              
223 1 50       8 last if !defined($response);
224              
225 0 0       0 if ($response eq "0") {
226 0 0       0 if ($mode eq $DONT) { $response = $IAC . $WILL . $opt }
  0         0  
227 0 0       0 if ($mode eq $DO) { $response = $IAC . $WONT . $opt }
  0         0  
228 0 0       0 if ($mode eq $WILL) { $response = $IAC . $DONT . $opt }
  0         0  
229 0 0       0 if ($mode eq $WONT) { $response = $IAC . $DO . $opt }
  0         0  
230 0         0 last;
231             }
232              
233 0 0       0 if ($response eq "1") {
234 0 0       0 if ($mode eq $DO) { $response = $IAC . $WILL . $opt }
  0         0  
235 0 0       0 if ($mode eq $DONT) { $response = $IAC . $WONT . $opt }
  0         0  
236 0 0       0 if ($mode eq $WONT) { $response = $IAC . $DONT . $opt }
  0         0  
237 0 0       0 if ($mode eq $WILL) { $response = $IAC . $DO . $opt }
  0         0  
238 0         0 last;
239             }
240              
241 0         0 my $r = $response;
242 0         0 $r =~ s/'//g; # just in case they said "DON'T" or "WON'T"
243              
244 0 0 0     0 if ($r eq 'DO' || $r eq 'DONT' || $r eq 'WILL' || $r eq 'WONT') {
      0        
      0        
245 0         0 $r = chr($meta{$r});
246 0         0 $response = $IAC . $r . $opt;
247             }
248             }
249             }
250              
251 13 50       61 $response = $self->_reasonable_response($mode, $opt)
252             if !defined($response);
253              
254 13         47 $self->send($response);
255             }
256              
257             sub _reasonable_response {
258 13     13   23 my ($self, $mode, $opt) = @_;
259              
260 13 100       60 if ($mode eq $DO) { return "$IAC$WONT$opt" }
  4 100       13  
    100          
    50          
261 3         10 elsif ($mode eq $DONT) { return "$IAC$WONT$opt" }
262 3         8 elsif ($mode eq $WILL) { return "$IAC$DONT$opt" }
263 3         10 elsif ($mode eq $WONT) { return "$IAC$DONT$opt" }
264              
265 0         0 return "";
266             }
267              
268             sub _telnet_complex_callback {
269 5     5   9 my ($self, $sb) = @_;
270 5 100       6 ${*$self}{telnet_complex_cb} or return;
  5         20  
271 1         2 ${*$self}{telnet_complex_cb}->($self, $sb);
  1         6  
272             }
273              
274             1;
275              
276             __END__