1# Net::Cmd.pm $Id: //depot/libnet/Net/Cmd.pm#34 $ 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.26"; 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_last_ch'}); 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 my $last_ch = ${*$cmd}{'net_cmd_last_ch'}; 396 $last_ch = ${*$cmd}{'net_cmd_last_ch'} = "\012" unless defined $last_ch; 397 398 return 1 unless length $line; 399 400 if($cmd->debug) { 401 foreach my $b (split(/\n/,$line)) { 402 $cmd->debug_print(1, "$b\n"); 403 } 404 } 405 406 $line =~ tr/\r\n/\015\012/ unless "\r" eq "\015"; 407 408 my $first_ch = ''; 409 410 if ($last_ch eq "\015") { 411 $first_ch = "\012" if $line =~ s/^\012//; 412 } 413 elsif ($last_ch eq "\012") { 414 $first_ch = "." if $line =~ /^\./; 415 } 416 417 $line =~ s/\015?\012(\.?)/\015\012$1$1/sg; 418 419 substr($line,0,0) = $first_ch; 420 421 ${*$cmd}{'net_cmd_last_ch'} = substr($line,-1,1); 422 423 my $len = length($line); 424 my $offset = 0; 425 my $win = ""; 426 vec($win,fileno($cmd),1) = 1; 427 my $timeout = $cmd->timeout || undef; 428 429 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS'; 430 431 while($len) 432 { 433 my $wout; 434 if (select(undef,$wout=$win, undef, $timeout) > 0 or -f $cmd) # -f for testing on win32 435 { 436 my $w = syswrite($cmd, $line, $len, $offset); 437 unless (defined($w)) 438 { 439 carp("$cmd: $!") if $cmd->debug; 440 return undef; 441 } 442 $len -= $w; 443 $offset += $w; 444 } 445 else 446 { 447 carp("$cmd: Timeout") if($cmd->debug); 448 return undef; 449 } 450 } 451 452 1; 453} 454 455sub rawdatasend 456{ 457 my $cmd = shift; 458 my $arr = @_ == 1 && ref($_[0]) ? $_[0] : \@_; 459 my $line = join("" ,@$arr); 460 461 return 0 unless defined(fileno($cmd)); 462 463 return 1 464 unless length($line); 465 466 if($cmd->debug) 467 { 468 my $b = "$cmd>>> "; 469 print STDERR $b,join("\n$b",split(/\n/,$line)),"\n"; 470 } 471 472 my $len = length($line); 473 my $offset = 0; 474 my $win = ""; 475 vec($win,fileno($cmd),1) = 1; 476 my $timeout = $cmd->timeout || undef; 477 478 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS'; 479 while($len) 480 { 481 my $wout; 482 if (select(undef,$wout=$win, undef, $timeout) > 0) 483 { 484 my $w = syswrite($cmd, $line, $len, $offset); 485 unless (defined($w)) 486 { 487 carp("$cmd: $!") if $cmd->debug; 488 return undef; 489 } 490 $len -= $w; 491 $offset += $w; 492 } 493 else 494 { 495 carp("$cmd: Timeout") if($cmd->debug); 496 return undef; 497 } 498 } 499 500 1; 501} 502 503sub dataend 504{ 505 my $cmd = shift; 506 507 return 0 unless defined(fileno($cmd)); 508 509 my $ch = ${*$cmd}{'net_cmd_last_ch'}; 510 my $tosend; 511 512 if (!defined $ch) { 513 return 1; 514 } 515 elsif ($ch ne "\012") { 516 $tosend = "\015\012"; 517 } 518 519 $tosend .= ".\015\012"; 520 521 local $SIG{PIPE} = 'IGNORE' unless $^O eq 'MacOS'; 522 523 $cmd->debug_print(1, ".\n") 524 if($cmd->debug); 525 526 syswrite($cmd,$tosend, length $tosend); 527 528 delete ${*$cmd}{'net_cmd_last_ch'}; 529 530 $cmd->response() == CMD_OK; 531} 532 533# read and write to tied filehandle 534sub tied_fh { 535 my $cmd = shift; 536 ${*$cmd}{'net_cmd_readbuf'} = ''; 537 my $fh = gensym(); 538 tie *$fh,ref($cmd),$cmd; 539 return $fh; 540} 541 542# tie to myself 543sub TIEHANDLE { 544 my $class = shift; 545 my $cmd = shift; 546 return $cmd; 547} 548 549# Tied filehandle read. Reads requested data length, returning 550# end-of-file when the dot is encountered. 551sub READ { 552 my $cmd = shift; 553 my ($len,$offset) = @_[1,2]; 554 return unless exists ${*$cmd}{'net_cmd_readbuf'}; 555 my $done = 0; 556 while (!$done and length(${*$cmd}{'net_cmd_readbuf'}) < $len) { 557 ${*$cmd}{'net_cmd_readbuf'} .= $cmd->getline() or return; 558 $done++ if ${*$cmd}{'net_cmd_readbuf'} =~ s/^\.\r?\n\Z//m; 559 } 560 561 $_[0] = ''; 562 substr($_[0],$offset+0) = substr(${*$cmd}{'net_cmd_readbuf'},0,$len); 563 substr(${*$cmd}{'net_cmd_readbuf'},0,$len) = ''; 564 delete ${*$cmd}{'net_cmd_readbuf'} if $done; 565 566 return length $_[0]; 567} 568 569sub READLINE { 570 my $cmd = shift; 571 # in this context, we use the presence of readbuf to 572 # indicate that we have not yet reached the eof 573 return unless exists ${*$cmd}{'net_cmd_readbuf'}; 574 my $line = $cmd->getline; 575 return if $line =~ /^\.\r?\n/; 576 $line; 577} 578 579sub PRINT { 580 my $cmd = shift; 581 my ($buf,$len,$offset) = @_; 582 $len ||= length ($buf); 583 $offset += 0; 584 return unless $cmd->datasend(substr($buf,$offset,$len)); 585 ${*$cmd}{'net_cmd_sending'}++; # flag that we should call dataend() 586 return $len; 587} 588 589sub CLOSE { 590 my $cmd = shift; 591 my $r = exists(${*$cmd}{'net_cmd_sending'}) ? $cmd->dataend : 1; 592 delete ${*$cmd}{'net_cmd_readbuf'}; 593 delete ${*$cmd}{'net_cmd_sending'}; 594 $r; 595} 596 5971; 598 599__END__ 600 601 602=head1 NAME 603 604Net::Cmd - Network Command class (as used by FTP, SMTP etc) 605 606=head1 SYNOPSIS 607 608 use Net::Cmd; 609 610 @ISA = qw(Net::Cmd); 611 612=head1 DESCRIPTION 613 614C<Net::Cmd> is a collection of methods that can be inherited by a sub class 615of C<IO::Handle>. These methods implement the functionality required for a 616command based protocol, for example FTP and SMTP. 617 618=head1 USER METHODS 619 620These methods provide a user interface to the C<Net::Cmd> object. 621 622=over 4 623 624=item debug ( VALUE ) 625 626Set the level of debug information for this object. If C<VALUE> is not given 627then the current state is returned. Otherwise the state is changed to 628C<VALUE> and the previous state returned. 629 630Different packages 631may implement different levels of debug but a non-zero value results in 632copies of all commands and responses also being sent to STDERR. 633 634If C<VALUE> is C<undef> then the debug level will be set to the default 635debug level for the class. 636 637This method can also be called as a I<static> method to set/get the default 638debug level for a given class. 639 640=item message () 641 642Returns the text message returned from the last command 643 644=item code () 645 646Returns the 3-digit code from the last command. If a command is pending 647then the value 0 is returned 648 649=item ok () 650 651Returns non-zero if the last code value was greater than zero and 652less than 400. This holds true for most command servers. Servers 653where this does not hold may override this method. 654 655=item status () 656 657Returns the most significant digit of the current status code. If a command 658is pending then C<CMD_PENDING> is returned. 659 660=item datasend ( DATA ) 661 662Send data to the remote server, converting LF to CRLF. Any line starting 663with a '.' will be prefixed with another '.'. 664C<DATA> may be an array or a reference to an array. 665 666=item dataend () 667 668End the sending of data to the remote server. This is done by ensuring that 669the data already sent ends with CRLF then sending '.CRLF' to end the 670transmission. Once this data has been sent C<dataend> calls C<response> and 671returns true if C<response> returns CMD_OK. 672 673=back 674 675=head1 CLASS METHODS 676 677These methods are not intended to be called by the user, but used or 678over-ridden by a sub-class of C<Net::Cmd> 679 680=over 4 681 682=item debug_print ( DIR, TEXT ) 683 684Print debugging information. C<DIR> denotes the direction I<true> being 685data being sent to the server. Calls C<debug_text> before printing to 686STDERR. 687 688=item debug_text ( TEXT ) 689 690This method is called to print debugging information. TEXT is 691the text being sent. The method should return the text to be printed 692 693This is primarily meant for the use of modules such as FTP where passwords 694are sent, but we do not want to display them in the debugging information. 695 696=item command ( CMD [, ARGS, ... ]) 697 698Send a command to the command server. All arguments a first joined with 699a space character and CRLF is appended, this string is then sent to the 700command server. 701 702Returns undef upon failure 703 704=item unsupported () 705 706Sets the status code to 580 and the response text to 'Unsupported command'. 707Returns zero. 708 709=item response () 710 711Obtain a response from the server. Upon success the most significant digit 712of the status code is returned. Upon failure, timeout etc., I<undef> is 713returned. 714 715=item parse_response ( TEXT ) 716 717This method is called by C<response> as a method with one argument. It should 718return an array of 2 values, the 3-digit status code and a flag which is true 719when this is part of a multi-line response and this line is not the list. 720 721=item getline () 722 723Retrieve one line, delimited by CRLF, from the remote server. Returns I<undef> 724upon failure. 725 726B<NOTE>: If you do use this method for any reason, please remember to add 727some C<debug_print> calls into your method. 728 729=item ungetline ( TEXT ) 730 731Unget a line of text from the server. 732 733=item rawdatasend ( DATA ) 734 735Send data to the remote server without performing any conversions. C<DATA> 736is a scalar. 737 738=item read_until_dot () 739 740Read data from the remote server until a line consisting of a single '.'. 741Any lines starting with '..' will have one of the '.'s removed. 742 743Returns a reference to a list containing the lines, or I<undef> upon failure. 744 745=item tied_fh () 746 747Returns a filehandle tied to the Net::Cmd object. After issuing a 748command, you may read from this filehandle using read() or <>. The 749filehandle will return EOF when the final dot is encountered. 750Similarly, you may write to the filehandle in order to send data to 751the server after issuing a commmand that expects data to be written. 752 753See the Net::POP3 and Net::SMTP modules for examples of this. 754 755=back 756 757=head1 EXPORTS 758 759C<Net::Cmd> exports six subroutines, five of these, C<CMD_INFO>, C<CMD_OK>, 760C<CMD_MORE>, C<CMD_REJECT> and C<CMD_ERROR>, correspond to possible results 761of C<response> and C<status>. The sixth is C<CMD_PENDING>. 762 763=head1 AUTHOR 764 765Graham Barr <gbarr@pobox.com> 766 767=head1 COPYRIGHT 768 769Copyright (c) 1995-1997 Graham Barr. All rights reserved. 770This program is free software; you can redistribute it and/or modify 771it under the same terms as Perl itself. 772 773=for html <hr> 774 775I<$Id: //depot/libnet/Net/Cmd.pm#34 $> 776 777=cut 778