xref: /onnv-gate/usr/src/cmd/perl/5.8.4/distrib/lib/Net/Cmd.pm (revision 0:68f95e015346)
1# Net::Cmd.pm $Id: //depot/libnet/Net/Cmd.pm#33 $
2#
3# Copyright (c) 1995-1997 Graham Barr <gbarr@pobox.com>. All rights reserved.
4# This program is free software; you can redistribute it and/or
5# modify it under the same terms as Perl itself.
6
7package Net::Cmd;
8
9require 5.001;
10require Exporter;
11
12use strict;
13use vars qw(@ISA @EXPORT $VERSION);
14use Carp;
15use Symbol 'gensym';
16
17BEGIN {
18  if ($^O eq 'os390') {
19    require Convert::EBCDIC;
20#    Convert::EBCDIC->import;
21  }
22}
23
24$VERSION = "2.24";
25@ISA     = qw(Exporter);
26@EXPORT  = qw(CMD_INFO CMD_OK CMD_MORE CMD_REJECT CMD_ERROR CMD_PENDING);
27
28sub CMD_INFO	{ 1 }
29sub CMD_OK	{ 2 }
30sub CMD_MORE	{ 3 }
31sub CMD_REJECT	{ 4 }
32sub CMD_ERROR	{ 5 }
33sub CMD_PENDING { 0 }
34
35my %debug = ();
36
37my $tr = $^O eq 'os390' ? Convert::EBCDIC->new() : undef;
38
39sub toebcdic
40{
41 my $cmd = shift;
42
43 unless (exists ${*$cmd}{'net_cmd_asciipeer'})
44  {
45   my $string = $_[0];
46   my $ebcdicstr = $tr->toebcdic($string);
47   ${*$cmd}{'net_cmd_asciipeer'} = $string !~ /^\d+/ && $ebcdicstr =~ /^\d+/;
48  }
49
50  ${*$cmd}{'net_cmd_asciipeer'}
51    ? $tr->toebcdic($_[0])
52    : $_[0];
53}
54
55sub toascii
56{
57  my $cmd = shift;
58  ${*$cmd}{'net_cmd_asciipeer'}
59    ? $tr->toascii($_[0])
60    : $_[0];
61}
62
63sub _print_isa
64{
65 no strict qw(refs);
66
67 my $pkg = shift;
68 my $cmd = $pkg;
69
70 $debug{$pkg} ||= 0;
71
72 my %done = ();
73 my @do   = ($pkg);
74 my %spc = ( $pkg , "");
75
76 while ($pkg = shift @do)
77  {
78   next if defined $done{$pkg};
79
80   $done{$pkg} = 1;
81
82   my $v = defined ${"${pkg}::VERSION"}
83                ? "(" . ${"${pkg}::VERSION"} . ")"
84                : "";
85
86   my $spc = $spc{$pkg};
87   $cmd->debug_print(1,"${spc}${pkg}${v}\n");
88
89   if(@{"${pkg}::ISA"})
90    {
91     @spc{@{"${pkg}::ISA"}} = ("  " . $spc{$pkg}) x @{"${pkg}::ISA"};
92     unshift(@do, @{"${pkg}::ISA"});
93    }
94  }
95}
96
97sub debug
98{
99 @_ == 1 or @_ == 2 or croak 'usage: $obj->debug([LEVEL])';
100
101 my($cmd,$level) = @_;
102 my $pkg = ref($cmd) || $cmd;
103 my $oldval = 0;
104
105 if(ref($cmd))
106  {
107   $oldval = ${*$cmd}{'net_cmd_debug'} || 0;
108  }
109 else
110  {
111   $oldval = $debug{$pkg} || 0;
112  }
113
114 return $oldval
115    unless @_ == 2;
116
117 $level = $debug{$pkg} || 0
118    unless defined $level;
119
120 _print_isa($pkg)
121    if($level && !exists $debug{$pkg});
122
123 if(ref($cmd))
124  {
125   ${*$cmd}{'net_cmd_debug'} = $level;
126  }
127 else
128  {
129   $debug{$pkg} = $level;
130  }
131
132 $oldval;
133}
134
135sub message
136{
137 @_ == 1 or croak 'usage: $obj->message()';
138
139 my $cmd = shift;
140
141 wantarray ? @{${*$cmd}{'net_cmd_resp'}}
142    	   : join("", @{${*$cmd}{'net_cmd_resp'}});
143}
144
145sub debug_text { $_[2] }
146
147sub debug_print
148{
149 my($cmd,$out,$text) = @_;
150 print STDERR $cmd,($out ? '>>> ' : '<<< '), $cmd->debug_text($out,$text);
151}
152
153sub code
154{
155 @_ == 1 or croak 'usage: $obj->code()';
156
157 my $cmd = shift;
158
159 ${*$cmd}{'net_cmd_code'} = "000"
160	unless exists ${*$cmd}{'net_cmd_code'};
161
162 ${*$cmd}{'net_cmd_code'};
163}
164
165sub status
166{
167 @_ == 1 or croak 'usage: $obj->status()';
168
169 my $cmd = shift;
170
171 substr(${*$cmd}{'net_cmd_code'},0,1);
172}
173
174sub set_status
175{
176 @_ == 3 or croak 'usage: $obj->set_status(CODE, MESSAGE)';
177
178 my $cmd = shift;
179 my($code,$resp) = @_;
180
181 $resp = [ $resp ]
182	unless ref($resp);
183
184 (${*$cmd}{'net_cmd_code'},${*$cmd}{'net_cmd_resp'}) = ($code, $resp);
185
186 1;
187}
188
189sub command
190{
191 my $cmd = shift;
192
193 unless (defined fileno($cmd))
194  {
195    $cmd->set_status("599", "Connection closed");
196    return $cmd;
197  }
198
199
200 $cmd->dataend()
201    if(exists ${*$cmd}{'net_cmd_need_crlf'});
202
203 if (scalar(@_))
204  {
205   local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
206
207   my $str =  join(" ", map { /\n/ ? do { my $n = $_; $n =~ tr/\n/ /; $n } : $_; } @_);
208   $str = $cmd->toascii($str) if $tr;
209   $str .= "\015\012";
210
211   my $len = length $str;
212   my $swlen;
213
214   $cmd->close
215	unless (defined($swlen = syswrite($cmd,$str,$len)) && $swlen == $len);
216
217   $cmd->debug_print(1,$str)
218	if($cmd->debug);
219
220   ${*$cmd}{'net_cmd_resp'} = [];      # the response
221   ${*$cmd}{'net_cmd_code'} = "000";	# Made this one up :-)
222  }
223
224 $cmd;
225}
226
227sub ok
228{
229 @_ == 1 or croak 'usage: $obj->ok()';
230
231 my $code = $_[0]->code;
232 0 < $code && $code < 400;
233}
234
235sub unsupported
236{
237 my $cmd = shift;
238
239 ${*$cmd}{'net_cmd_resp'} = [ 'Unsupported command' ];
240 ${*$cmd}{'net_cmd_code'} = 580;
241 0;
242}
243
244sub getline
245{
246 my $cmd = shift;
247
248 ${*$cmd}{'net_cmd_lines'} ||= [];
249
250 return shift @{${*$cmd}{'net_cmd_lines'}}
251    if scalar(@{${*$cmd}{'net_cmd_lines'}});
252
253 my $partial = defined(${*$cmd}{'net_cmd_partial'})
254		? ${*$cmd}{'net_cmd_partial'} : "";
255 my $fd = fileno($cmd);
256
257 return undef
258	unless defined $fd;
259
260 my $rin = "";
261 vec($rin,$fd,1) = 1;
262
263 my $buf;
264
265 until(scalar(@{${*$cmd}{'net_cmd_lines'}}))
266  {
267   my $timeout = $cmd->timeout || undef;
268   my $rout;
269   if (select($rout=$rin, undef, undef, $timeout))
270    {
271     unless (sysread($cmd, $buf="", 1024))
272      {
273       carp(ref($cmd) . ": Unexpected EOF on command channel")
274		if $cmd->debug;
275       $cmd->close;
276       return undef;
277      }
278
279     substr($buf,0,0) = $partial;	## prepend from last sysread
280
281     my @buf = split(/\015?\012/, $buf, -1);	## break into lines
282
283     $partial = pop @buf;
284
285     push(@{${*$cmd}{'net_cmd_lines'}}, map { "$_\n" } @buf);
286
287    }
288   else
289    {
290     carp("$cmd: Timeout") if($cmd->debug);
291     return undef;
292    }
293  }
294
295 ${*$cmd}{'net_cmd_partial'} = $partial;
296
297 if ($tr)
298  {
299   foreach my $ln (@{${*$cmd}{'net_cmd_lines'}})
300    {
301     $ln = $cmd->toebcdic($ln);
302    }
303  }
304
305 shift @{${*$cmd}{'net_cmd_lines'}};
306}
307
308sub ungetline
309{
310 my($cmd,$str) = @_;
311
312 ${*$cmd}{'net_cmd_lines'} ||= [];
313 unshift(@{${*$cmd}{'net_cmd_lines'}}, $str);
314}
315
316sub parse_response
317{
318 return ()
319    unless $_[1] =~ s/^(\d\d\d)(.?)//o;
320 ($1, $2 eq "-");
321}
322
323sub response
324{
325 my $cmd = shift;
326 my($code,$more) = (undef) x 2;
327
328 ${*$cmd}{'net_cmd_resp'} ||= [];
329
330 while(1)
331  {
332   my $str = $cmd->getline();
333
334   return CMD_ERROR
335	unless defined($str);
336
337   $cmd->debug_print(0,$str)
338     if ($cmd->debug);
339
340   ($code,$more) = $cmd->parse_response($str);
341   unless(defined $code)
342    {
343     $cmd->ungetline($str);
344     last;
345    }
346
347   ${*$cmd}{'net_cmd_code'} = $code;
348
349   push(@{${*$cmd}{'net_cmd_resp'}},$str);
350
351   last unless($more);
352  }
353
354 substr($code,0,1);
355}
356
357sub read_until_dot
358{
359 my $cmd = shift;
360 my $fh  = shift;
361 my $arr = [];
362
363 while(1)
364  {
365   my $str = $cmd->getline() or return undef;
366
367   $cmd->debug_print(0,$str)
368     if ($cmd->debug & 4);
369
370   last if($str =~ /^\.\r?\n/o);
371
372   $str =~ s/^\.\././o;
373
374   if (defined $fh)
375    {
376     print $fh $str;
377    }
378   else
379    {
380     push(@$arr,$str);
381    }
382  }
383
384 $arr;
385}
386
387sub datasend
388{
389 my $cmd = shift;
390 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
391 my $line = join("" ,@$arr);
392
393 return 0 unless defined(fileno($cmd));
394
395 unless (length $line) {
396   # Even though we are not sending anything, the fact we were
397   # called means that dataend needs to be called before the next
398   # command, which happens of net_cmd_need_crlf exists
399   ${*$cmd}{'net_cmd_need_crlf'} ||= 0;
400   return 1;
401 }
402
403 if($cmd->debug) {
404   foreach my $b (split(/\n/,$line)) {
405     $cmd->debug_print(1, "$b\n");
406   }
407  }
408
409 $line =~ s/\r?\n/\r\n/sg;
410 $line =~ tr/\r\n/\015\012/ unless "\r" eq "\015";
411
412 $line =~ s/(\012\.)/$1./sog;
413 $line =~ s/^\./../ unless ${*$cmd}{'net_cmd_need_crlf'};
414
415 ${*$cmd}{'net_cmd_need_crlf'} = substr($line,-1,1) ne "\012";
416
417 my $len = length($line);
418 my $offset = 0;
419 my $win = "";
420 vec($win,fileno($cmd),1) = 1;
421 my $timeout = $cmd->timeout || undef;
422
423 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
424
425 while($len)
426  {
427   my $wout;
428   if (select(undef,$wout=$win, undef, $timeout) > 0)
429    {
430     my $w = syswrite($cmd, $line, $len, $offset);
431     unless (defined($w))
432      {
433       carp("$cmd: $!") if $cmd->debug;
434       return undef;
435      }
436     $len -= $w;
437     $offset += $w;
438    }
439   else
440    {
441     carp("$cmd: Timeout") if($cmd->debug);
442     return undef;
443    }
444  }
445
446 1;
447}
448
449sub rawdatasend
450{
451 my $cmd = shift;
452 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_;
453 my $line = join("" ,@$arr);
454
455 return 0 unless defined(fileno($cmd));
456
457 return 1
458    unless length($line);
459
460 if($cmd->debug)
461  {
462   my $b = "$cmd>>> ";
463   print STDERR $b,join("\n$b",split(/\n/,$line)),"\n";
464  }
465
466 my $len = length($line);
467 my $offset = 0;
468 my $win = "";
469 vec($win,fileno($cmd),1) = 1;
470 my $timeout = $cmd->timeout || undef;
471
472 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
473 while($len)
474  {
475   my $wout;
476   if (select(undef,$wout=$win, undef, $timeout) > 0)
477    {
478     my $w = syswrite($cmd, $line, $len, $offset);
479     unless (defined($w))
480      {
481       carp("$cmd: $!") if $cmd->debug;
482       return undef;
483      }
484     $len -= $w;
485     $offset += $w;
486    }
487   else
488    {
489     carp("$cmd: Timeout") if($cmd->debug);
490     return undef;
491    }
492  }
493
494 1;
495}
496
497sub dataend
498{
499 my $cmd = shift;
500
501 return 0 unless defined(fileno($cmd));
502
503 return 1
504    unless(exists ${*$cmd}{'net_cmd_need_crlf'});
505
506 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS';
507 syswrite($cmd,"\015\012",2)
508    if ${*$cmd}{'net_cmd_need_crlf'};
509
510 $cmd->debug_print(1, ".\n")
511    if($cmd->debug);
512
513 syswrite($cmd,".\015\012",3);
514
515 delete ${*$cmd}{'net_cmd_need_crlf'};
516
517 $cmd->response() == CMD_OK;
518}
519
520# read and write to tied filehandle
521sub tied_fh {
522  my $cmd = shift;
523  ${*$cmd}{'net_cmd_readbuf'} = '';
524  my $fh = gensym();
525  tie *$fh,ref($cmd),$cmd;
526  return $fh;
527}
528
529# tie to myself
530sub TIEHANDLE {
531  my $class = shift;
532  my $cmd = shift;
533  return $cmd;
534}
535
536# Tied filehandle read.  Reads requested data length, returning
537# end-of-file when the dot is encountered.
538sub READ {
539  my $cmd = shift;
540  my ($len,$offset) = @_[1,2];
541  return unless exists ${*$cmd}{'net_cmd_readbuf'};
542  my $done = 0;
543  while (!$done and length(${*$cmd}{'net_cmd_readbuf'}) < $len) {
544     ${*$cmd}{'net_cmd_readbuf'} .= $cmd->getline() or return;
545     $done++ if ${*$cmd}{'net_cmd_readbuf'} =~ s/^\.\r?\n\Z//m;
546  }
547
548  $_[0] = '';
549  substr($_[0],$offset+0) = substr(${*$cmd}{'net_cmd_readbuf'},0,$len);
550  substr(${*$cmd}{'net_cmd_readbuf'},0,$len) = '';
551  delete ${*$cmd}{'net_cmd_readbuf'} if $done;
552
553  return length $_[0];
554}
555
556sub READLINE {
557  my $cmd = shift;
558  # in this context, we use the presence of readbuf to
559  # indicate that we have not yet reached the eof
560  return unless exists ${*$cmd}{'net_cmd_readbuf'};
561  my $line = $cmd->getline;
562  return if $line =~ /^\.\r?\n/;
563  $line;
564}
565
566sub PRINT {
567  my $cmd = shift;
568  my ($buf,$len,$offset) = @_;
569  $len    ||= length ($buf);
570  $offset += 0;
571  return unless $cmd->datasend(substr($buf,$offset,$len));
572  ${*$cmd}{'net_cmd_sending'}++;  # flag that we should call dataend()
573  return $len;
574}
575
576sub CLOSE {
577  my $cmd = shift;
578  my $r = exists(${*$cmd}{'net_cmd_sending'}) ? $cmd->dataend : 1;
579  delete ${*$cmd}{'net_cmd_readbuf'};
580  delete ${*$cmd}{'net_cmd_sending'};
581  $r;
582}
583
5841;
585
586__END__
587
588
589=head1 NAME
590
591Net::Cmd - Network Command class (as used by FTP, SMTP etc)
592
593=head1 SYNOPSIS
594
595    use Net::Cmd;
596
597    @ISA = qw(Net::Cmd);
598
599=head1 DESCRIPTION
600
601C<Net::Cmd> is a collection of methods that can be inherited by a sub class
602of C<IO::Handle>. These methods implement the functionality required for a
603command based protocol, for example FTP and SMTP.
604
605=head1 USER METHODS
606
607These methods provide a user interface to the C<Net::Cmd> object.
608
609=over 4
610
611=item debug ( VALUE )
612
613Set the level of debug information for this object. If C<VALUE> is not given
614then the current state is returned. Otherwise the state is changed to
615C<VALUE> and the previous state returned.
616
617Different packages
618may implement different levels of debug but a non-zero value results in
619copies of all commands and responses also being sent to STDERR.
620
621If C<VALUE> is C<undef> then the debug level will be set to the default
622debug level for the class.
623
624This method can also be called as a I<static> method to set/get the default
625debug level for a given class.
626
627=item message ()
628
629Returns the text message returned from the last command
630
631=item code ()
632
633Returns the 3-digit code from the last command. If a command is pending
634then the value 0 is returned
635
636=item ok ()
637
638Returns non-zero if the last code value was greater than zero and
639less than 400. This holds true for most command servers. Servers
640where this does not hold may override this method.
641
642=item status ()
643
644Returns the most significant digit of the current status code. If a command
645is pending then C<CMD_PENDING> is returned.
646
647=item datasend ( DATA )
648
649Send data to the remote server, converting LF to CRLF. Any line starting
650with a '.' will be prefixed with another '.'.
651C<DATA> may be an array or a reference to an array.
652
653=item dataend ()
654
655End the sending of data to the remote server. This is done by ensuring that
656the data already sent ends with CRLF then sending '.CRLF' to end the
657transmission. Once this data has been sent C<dataend> calls C<response> and
658returns true if C<response> returns CMD_OK.
659
660=back
661
662=head1 CLASS METHODS
663
664These methods are not intended to be called by the user, but used or
665over-ridden by a sub-class of C<Net::Cmd>
666
667=over 4
668
669=item debug_print ( DIR, TEXT )
670
671Print debugging information. C<DIR> denotes the direction I<true> being
672data being sent to the server. Calls C<debug_text> before printing to
673STDERR.
674
675=item debug_text ( TEXT )
676
677This method is called to print debugging information. TEXT is
678the text being sent. The method should return the text to be printed
679
680This is primarily meant for the use of modules such as FTP where passwords
681are sent, but we do not want to display them in the debugging information.
682
683=item command ( CMD [, ARGS, ... ])
684
685Send a command to the command server. All arguments a first joined with
686a space character and CRLF is appended, this string is then sent to the
687command server.
688
689Returns undef upon failure
690
691=item unsupported ()
692
693Sets the status code to 580 and the response text to 'Unsupported command'.
694Returns zero.
695
696=item response ()
697
698Obtain a response from the server. Upon success the most significant digit
699of the status code is returned. Upon failure, timeout etc., I<undef> is
700returned.
701
702=item parse_response ( TEXT )
703
704This method is called by C<response> as a method with one argument. It should
705return an array of 2 values, the 3-digit status code and a flag which is true
706when this is part of a multi-line response and this line is not the list.
707
708=item getline ()
709
710Retrieve one line, delimited by CRLF, from the remote server. Returns I<undef>
711upon failure.
712
713B<NOTE>: If you do use this method for any reason, please remember to add
714some C<debug_print> calls into your method.
715
716=item ungetline ( TEXT )
717
718Unget a line of text from the server.
719
720=item rawdatasend ( DATA )
721
722Send data to the remote server without performing any conversions. C<DATA>
723is a scalar.
724
725=item read_until_dot ()
726
727Read data from the remote server until a line consisting of a single '.'.
728Any lines starting with '..' will have one of the '.'s removed.
729
730Returns a reference to a list containing the lines, or I<undef> upon failure.
731
732=item tied_fh ()
733
734Returns a filehandle tied to the Net::Cmd object.  After issuing a
735command, you may read from this filehandle using read() or <>.  The
736filehandle will return EOF when the final dot is encountered.
737Similarly, you may write to the filehandle in order to send data to
738the server after issuing a commmand that expects data to be written.
739
740See the Net::POP3 and Net::SMTP modules for examples of this.
741
742=back
743
744=head1 EXPORTS
745
746C<Net::Cmd> exports six subroutines, five of these, C<CMD_INFO>, C<CMD_OK>,
747C<CMD_MORE>, C<CMD_REJECT> and C<CMD_ERROR>, correspond to possible results
748of C<response> and C<status>. The sixth is C<CMD_PENDING>.
749
750=head1 AUTHOR
751
752Graham Barr <gbarr@pobox.com>
753
754=head1 COPYRIGHT
755
756Copyright (c) 1995-1997 Graham Barr. All rights reserved.
757This program is free software; you can redistribute it and/or modify
758it under the same terms as Perl itself.
759
760=for html <hr>
761
762I<$Id: //depot/libnet/Net/Cmd.pm#33 $>
763
764=cut
765