Revision 119

Date:
2009/08/03 08:53:47
Author:
dpavlin
Revision Log:
import Net::TFTPd for local modifications
Files:

Legend:

 
Added
 
Removed
 
Modified
  • lib/Net/TFTPd.pm

     
    1 package Net::TFTPd;
    2
    3 use 5.006;
    4 use Carp;
    5 use strict;
    6 use warnings;
    7 use IO::Socket;
    8
    9 require Exporter;
    10
    11 # modified for supporting small block sizes, O.Z. 15.08.2007
    12 use constant TFTP_MIN_BLKSIZE => 8;
    13 use constant TFTP_DEFAULT_BLKSIZE => 512;
    14 use constant TFTP_MAX_BLKSIZE => 65464;
    15 use constant TFTP_MIN_TIMEOUT => 1;
    16 use constant TFTP_MAX_TIMEOUT => 60;
    17 use constant TFTP_DEFAULT_PORT => 69;
    18
    19 use constant TFTP_OPCODE_RRQ => 1;
    20 use constant TFTP_OPCODE_WRQ => 2;
    21 use constant TFTP_OPCODE_DATA => 3;
    22 use constant TFTP_OPCODE_ACK => 4;
    23 use constant TFTP_OPCODE_ERROR => 5;
    24 use constant TFTP_OPCODE_OACK => 6;
    25
    26 # Type Op # Format without header
    27 #
    28 # 2 bytes string 1 byte string 1 byte
    29 # -------------------------------------------------
    30 # RRQ/ | 01/02 | Filename | 0 | Mode | 0 |
    31 # WRQ -------------------------------------------------
    32 # 2 bytes 2 bytes n bytes
    33 # -----------------------------------
    34 # DATA | 03 | Block # | Data |
    35 # -----------------------------------
    36 # 2 bytes 2 bytes
    37 # ----------------------
    38 # ACK | 04 | Block # |
    39 # ----------------------
    40 # 2 bytes 2 bytes string 1 byte
    41 # ------------------------------------------
    42 # ERROR | 05 | ErrorCode | ErrMsg | 0 |
    43 # ------------------------------------------
    44
    45 our %OPCODES = (
    46 1 => 'RRQ',
    47 2 => 'WRQ',
    48 3 => 'DATA',
    49 4 => 'ACK',
    50 5 => 'ERROR',
    51 6 => 'OACK',
    52 'RRQ' => TFTP_OPCODE_RRQ,
    53 'WRQ' => TFTP_OPCODE_WRQ,
    54 'DATA' => TFTP_OPCODE_DATA,
    55 'ACK' => TFTP_OPCODE_ACK,
    56 'ERROR' => TFTP_OPCODE_ERROR,
    57 'OACK' => TFTP_OPCODE_OACK
    58 );
    59
    60 my %ERRORS = (
    61 0 => 'Not defined, see error message (if any)',
    62 1 => 'File not found',
    63 2 => 'Access violation',
    64 3 => 'Disk full or allocation exceeded',
    65 4 => 'Illegal TFTP operation',
    66 5 => 'Unknown transfer ID',
    67 6 => 'File already exists',
    68 7 => 'No such user',
    69 8 => 'Option negotiation'
    70 );
    71
    72 our @ISA = qw(Exporter);
    73
    74 # Items to export into callers namespace by default. Note: do not export
    75 # names by default without a very good reason. Use EXPORT_OK instead.
    76 # Do not simply export all your public functions/methods/constants.
    77
    78 # This allows declaration use Net::TFTPd ':all';
    79 # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
    80 # will save memory.
    81 our %EXPORT_TAGS = (
    82 'all' => [ qw( %OPCODES ) ]
    83 );
    84
    85 our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
    86
    87 our @EXPORT = qw( );
    88
    89 our $VERSION = '0.04';
    90
    91 our $LASTERROR;
    92
    93 my $debug;
    94
    95 #
    96 # Usage: $tftpdOBJ = Net::TFTPd->new( ['RootDir' => 'path/to/files' | 'FileName' => 'path/to/file'] );
    97 # return the tftpdOBJ object if success or undef if error
    98 #
    99 sub new
    100 {
    101 # create the future TFTPd object
    102 my $self = shift;
    103 my $class = ref($self) || $self;
    104
    105 # read parameters
    106 my %cfg = @_;
    107
    108 # setting defaults
    109 $cfg{'FileName'} or $cfg{'RootDir'} or croak "Usage: \$tftpdOBJ = Net::TFTPd->new(['RootDir' => 'path/to/files' | 'FileName' => 'path/to/file'] [, [ LocalPort => portnum ] [, ...]] );";
    110
    111 if($cfg{'RootDir'} and not -d($cfg{'RootDir'}) )
    112 {
    113 $LASTERROR = sprintf 'RootDir \'%s\' not found or is not a valid directory name\n', $cfg{'RootDir'};
    114 return(undef);
    115 }
    116
    117 if($cfg{'FileName'} and not -e($cfg{'FileName'}) )
    118 {
    119 $LASTERROR = sprintf 'FileName \'%s\' not found or is not a valid filename\n', $cfg{'FileName'};
    120 return(undef);
    121 }
    122
    123 my %params = (
    124 'Proto' => 'udp',
    125 'LocalPort' => $cfg{'LocalPort'} || TFTP_DEFAULT_PORT,
    126 );
    127
    128 # bind only to specified address
    129 if($cfg{'LocalAddr'})
    130 {
    131 $params{'LocalAddr'} = $cfg{'LocalAddr'};
    132 }
    133
    134 if(my $udpserver = IO::Socket::INET->new(%params))
    135 {
    136 #removed for using this module with IO v. 1.2301 under SUSE 10.1, O.Z. 15.08.2007
    137 # $udpserver->setsockopt(SOL_SOCKET, SO_RCVBUF, 0);
    138 # $udpserver->setsockopt(SOL_SOCKET, SO_SNDBUF, 0);
    139
    140 return bless {
    141 'LocalPort' => TFTP_DEFAULT_PORT,
    142 'Timeout' => 10,
    143 'ACKtimeout' => 4,
    144 'ACKretries' => 4,
    145 'Readable' => 1,
    146 'Writable' => 0,
    147 'CallBack' => undef,
    148 'BlkSize' => TFTP_DEFAULT_BLKSIZE,
    149 'Debug' => 0,
    150 %cfg, # merge user parameters
    151 '_UDPSERVER_' => $udpserver
    152 }, $class;
    153 }
    154 else
    155 {
    156 $LASTERROR = "Error opening socket for listener: $@\n";
    157 return(undef);
    158 }
    159 }
    160
    161 #
    162 # Usage: $tftpdOBJ->waitRQ($timeout);
    163 # return requestOBJ if success, 0 if $timeout elapsed, undef if error
    164 #
    165 sub waitRQ
    166 {
    167 # the tftpd object
    168 # my $tftpd = shift;
    169
    170 my $self = shift;
    171 my $class = ref($self) || $self;
    172 # return bless {}, $class;
    173
    174 # clone the object
    175 my $request;
    176 foreach my $key (keys(%{$self}))
    177 {
    178 # everything but '_xxx_'
    179 $key =~ /^\_.+\_$/ and next;
    180 $request->{$key} = $self->{$key};
    181 }
    182
    183 # use $timeout or default from $tftpdOBJ
    184 my $Timeout = shift || $request->{'Timeout'};
    185
    186 my $udpserver = $self->{'_UDPSERVER_'};
    187
    188 my ($datagram, $opcode, $datain);
    189
    190 # vars for IO select
    191 my ($rin, $rout, $ein, $eout) = ('', '', '', '');
    192 vec($rin, fileno($udpserver), 1) = 1;
    193
    194 # check if a message is waiting
    195 if (select($rout=$rin, undef, $eout=$ein, $Timeout))
    196 {
    197 # read the message
    198 if($udpserver->recv($datagram, TFTP_MAX_BLKSIZE + 4))
    199 {
    200 # decode the message
    201 ($opcode, $datain) = unpack("na*", $datagram);
    202
    203 $request->{'_REQUEST_'}{'OPCODE'} = $opcode;
    204
    205 # get peer port and address
    206 my($peerport, $peeraddr) = sockaddr_in($udpserver->peername);
    207 $request->{'_REQUEST_'}{'PeerPort'} = $peerport;
    208 $request->{'_REQUEST_'}{'PeerAddr'} = inet_ntoa($peeraddr);
    209
    210 # get filename and transfer mode
    211 my @datain = split("\0", $datain);
    212
    213 $request->{'_REQUEST_'}{'FileName'} = shift(@datain);
    214 $request->{'_REQUEST_'}{'Mode'} = uc(shift(@datain));
    215 $request->{'_REQUEST_'}{'BlkSize'} = TFTP_DEFAULT_BLKSIZE;
    216 $request->{'_REQUEST_'}{'LASTACK'} = 0;
    217 $request->{'_REQUEST_'}{'PREVACK'} = -1;
    218 # counter for transferred bytes
    219 $request->{'_REQUEST_'}{'TotalBytes'} = 0;
    220
    221 if(scalar(@datain) >= 2)
    222 {
    223 $request->{'_REQUEST_'}{'RFC2347'} = { @datain };
    224 }
    225
    226 return bless $request, $class;
    227 }
    228 else
    229 {
    230 $! = $udpserver->sockopt(SO_ERROR);
    231 $LASTERROR = sprintf "Socket RECV error: %s\n", $!;
    232 return(undef);
    233 }
    234 }
    235 else
    236 {
    237 $LASTERROR = "Timed out waiting for RRQ/WRQ";
    238 return(0);
    239 }
    240 }
    241
    242 #
    243 # Usage: $requestOBJ->processRQ();
    244 # return 1 if success, undef if error
    245 #
    246 sub processRQ
    247 {
    248 # the request object
    249 my $self = shift;
    250
    251 if(defined($self->newSOCK()))
    252 {
    253 # modified for supporting NETASCII transfers on 25/05/2009
    254 if(($self->{'_REQUEST_'}{'Mode'} ne 'OCTET') && ($self->{'_REQUEST_'}{'Mode'} ne 'NETASCII'))
    255 {
    256 #request is not OCTET
    257 $LASTERROR = sprintf "%s transfer mode is not supported\n", $self->{'_REQUEST_'}{'Mode'};
    258 $self->sendERR(0, $LASTERROR);
    259 return(undef);
    260 }
    261
    262 # new socket opened successfully
    263 if($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_RRQ)
    264 {
    265 #################
    266 # opcode is RRQ #
    267 #################
    268 if($self->{'Readable'})
    269 {
    270 # read is permitted
    271 if($self->{'_REQUEST_'}{'FileName'} =~ /\.\.[\\\/]/)
    272 {
    273 # requested file contains '..\' or '../'
    274 $LASTERROR = sprintf 'Access to \'%s\' is not permitted to %s', $self->{'_REQUEST_'}{'FileName'}, $self->{'_REQUEST_'}{'PeerAddr'};
    275 $self->sendERR(2);
    276 return(undef);
    277 }
    278
    279 if(defined($self->checkFILE()))
    280 {
    281 # file is present
    282 if(defined($self->negotiateOPTS()))
    283 {
    284 # RFC 2347 options negotiated
    285 if(defined($self->openFILE()))
    286 {
    287 # file opened for read, start the transfer
    288 if(defined($self->sendFILE()))
    289 {
    290 # file sent successfully
    291 return(1);
    292 }
    293 else
    294 {
    295 # error sending file
    296 return(undef);
    297 }
    298 }
    299 else
    300 {
    301 # error opening file
    302 return(undef);
    303 }
    304 }
    305 else
    306 {
    307 # error negotiating options
    308 $LASTERROR = "TFTP error 8: Option negotiation\n";
    309 $self->sendERR(8);
    310 return(undef);
    311 }
    312 }
    313 else
    314 {
    315 # file not found
    316 $LASTERROR = sprintf 'File \'%s\' not found', $self->{'_REQUEST_'}{'FileName'};
    317 $self->sendERR(1);
    318 return(undef);
    319 }
    320 }
    321 else
    322 {
    323 # if server is not readable
    324 $LASTERROR = "TFTP Error: Access violation";
    325 $self->sendERR(2);
    326 return(undef);
    327 }
    328 }
    329 elsif($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_WRQ)
    330 {
    331 #################
    332 # opcode is WRQ #
    333 #################
    334 if($self->{'Writable'})
    335 {
    336 # write is permitted
    337 if($self->{'_REQUEST_'}{'FileName'} =~ /\.\.[\\\/]/)
    338 {
    339 # requested file contains '..\' or '../'
    340 $LASTERROR = sprintf 'Access to \'%s\' is not permitted to %s', $self->{'_REQUEST_'}{'FileName'}, $self->{'_REQUEST_'}{'PeerAddr'};
    341 $self->sendERR(2);
    342 return(undef);
    343 }
    344
    345 if(!defined($self->checkFILE()))
    346 {
    347 # RFC 2347 options negotiated
    348 if(defined($self->openFILE()))
    349 {
    350 # file is not present
    351 if(defined($self->negotiateOPTS()))
    352 {
    353 # file opened for write, start the transfer
    354 if(defined($self->recvFILE()))
    355 {
    356 # file received successfully
    357 return(1);
    358 }
    359 else
    360 {
    361 # error receiving file
    362 return(undef);
    363 }
    364 }
    365 else
    366 {
    367 # error negotiating options
    368 $LASTERROR = "TFTP error 8: Option negotiation\n";
    369 $self->sendERR(8);
    370 return(undef);
    371 }
    372 }
    373 else
    374 {
    375 # error opening file
    376 $self->sendERR(3);
    377 return(undef);
    378 }
    379 }
    380 else
    381 {
    382 # file not found
    383 $LASTERROR = sprintf 'File \'%s\' already exists', $self->{'_REQUEST_'}{'FileName'};
    384 $self->sendERR(6);
    385 return(undef);
    386 }
    387 }
    388 else
    389 {
    390 # if server is not writable
    391 $LASTERROR = "TFTP Error: Access violation";
    392 $self->sendERR(2);
    393 return(undef);
    394 }
    395 }
    396 else
    397 {
    398 #################
    399 # other opcodes #
    400 #################
    401 $LASTERROR = sprintf "Opcode %d not supported as request", $self->{'_REQUEST_'}{'OPCODE'};
    402 $self->sendERR(4);
    403 return(undef);
    404 }
    405 }
    406 else
    407 {
    408 return(undef);
    409 }
    410 }
    411
    412 #
    413 # Usage: $requestOBJ->getTotalBytes();
    414 # returns the number of bytes transferred by the request
    415 #
    416 sub getTotalBytes
    417 {
    418 # the request object
    419 my $self = shift;
    420
    421 return $self->{'_REQUEST_'}{'TotalBytes'};
    422 }
    423
    424 #
    425 # Usage: $requestOBJ->getFileName();
    426 # returns the requested file name
    427 #
    428 sub getFileName
    429 {
    430 # the request object
    431 my $self = shift;
    432
    433 return $self->{'_REQUEST_'}{'FileName'};
    434 }
    435
    436 #
    437 # Usage: $requestOBJ->getMode();
    438 # returns the transfer mode for the request
    439 #
    440 sub getMode
    441 {
    442 # the request object
    443 my $self = shift;
    444
    445 return $self->{'_REQUEST_'}{'Mode'};
    446 }
    447
    448 #
    449 # Usage: $requestOBJ->getPeerAddr();
    450 # returns the address of the requesting client
    451 #
    452 sub getPeerAddr
    453 {
    454 # the request object
    455 my $self = shift;
    456
    457 return $self->{'_REQUEST_'}{'PeerAddr'};
    458 }
    459
    460 #
    461 # Usage: $requestOBJ->getPeerPort();
    462 # returns the port of the requesting client
    463 #
    464 sub getPeerPort
    465 {
    466 # the request object
    467 my $self = shift;
    468
    469 return $self->{'_REQUEST_'}{'PeerPort'};
    470 }
    471
    472 #
    473 # Usage: $requestOBJ->getBlkSize();
    474 # returns the block size used for the transfer
    475 #
    476 sub getBlkSize
    477 {
    478 # the request object
    479 my $self = shift;
    480
    481 return $self->{'_REQUEST_'}{'BlkSize'};
    482 }
    483
    484 #
    485 # Usage: $requestOBJ->newSOCK();
    486 # return 1 if success or undef if error
    487 #
    488 sub newSOCK
    489 {
    490 # the request object
    491 my $self = shift;
    492
    493 # set parameters for the new socket
    494 my %params = (
    495 'Proto' => 'udp',
    496 'PeerPort' => $self->{'_REQUEST_'}{'PeerPort'},
    497 'PeerAddr' => $self->{'_REQUEST_'}{'PeerAddr'}
    498 );
    499
    500 # bind only to specified address
    501 if($self->{'Address'})
    502 {
    503 $params{'LocalAddr'} = $self->{'Address'};
    504 }
    505
    506 # open socket
    507 if(my $udpserver = IO::Socket::INET->new(%params))
    508 {
    509 #removed for using this module with IO v. 1.2301 under SUSE 10.1, O.Z. 15.08.2007
    510 # $udpserver->setsockopt(SOL_SOCKET, SO_RCVBUF, 0);
    511 # $udpserver->setsockopt(SOL_SOCKET, SO_SNDBUF, 0);
    512
    513 $self->{'_UDPSERVER_'} = $udpserver;
    514 return(1);
    515 }
    516 else
    517 {
    518 $LASTERROR = "Error opening socket for reply: $@\n";
    519 return(undef);
    520 }
    521 }
    522
    523
    524 #
    525 # Usage: $requestOBJ->negotiateOPTS();
    526 # return 1 if success or undef if error
    527 #
    528 sub negotiateOPTS
    529 {
    530 # the request object
    531 my $self = shift;
    532
    533 if($self->{'_REQUEST_'}{'RFC2347'})
    534 {
    535 # parse RFC 2347 options if present
    536 foreach my $option (keys(%{ $self->{'_REQUEST_'}{'RFC2347'} }))
    537 {
    538 if(uc($option) eq 'BLKSIZE')
    539 {
    540 # Negotiate the blocksize
    541 if($self->{'_REQUEST_'}{'RFC2347'}{$option} > TFTP_MAX_BLKSIZE or $self->{'_REQUEST_'}{'RFC2347'}{$option} < TFTP_MIN_BLKSIZE)
    542 {
    543 $self->{'_REQUEST_'}{'RFC2347'}{$option} = $self->{'BlkSize'};
    544 }
    545 else
    546 {
    547 $self->{'_RESPONSE_'}{'RFC2347'}{$option} = $self->{'_REQUEST_'}{'RFC2347'}{$option};
    548 $self->{'BlkSize'} = $self->{'_RESPONSE_'}{'RFC2347'}{$option};
    549 }
    550 }
    551 elsif(uc($option) eq 'TSIZE')
    552 {
    553 # Negotiate the transfer size
    554 if($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_RRQ)
    555 {
    556 $self->{'_RESPONSE_'}{'RFC2347'}{$option} = $self->{'FileSize'};
    557 }
    558 else
    559 {
    560 $self->{'FileSize'} = $self->{'_REQUEST_'}{'RFC2347'}{$option};
    561 }
    562 }
    563 elsif(uc($option) eq 'TIMEOUT')
    564 {
    565 # Negotiate the transfer timeout
    566 if($self->{'_REQUEST_'}{'RFC2347'}{$option} > TFTP_MAX_TIMEOUT or $self->{'_REQUEST_'}{'RFC2347'}{$option} < TFTP_MIN_TIMEOUT)
    567 {
    568 $self->{'_RESPONSE_'}{'RFC2347'}{$option} = $self->{'ACKtimeout'};
    569 }
    570 else
    571 {
    572 $self->{'_RESPONSE_'}{'RFC2347'}{$option} = $self->{'_REQUEST_'}{'RFC2347'}{$option};
    573 $self->{'ACKtimeout'} = $self->{'_REQUEST_'}{'RFC2347'}{$option};
    574 }
    575 }
    576 else
    577 {
    578 # Negotiate other options...
    579 }
    580 }
    581
    582 # post processing
    583 if($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_WRQ)
    584 {
    585 if($self->{'FileSize'} and $self->{'BlkSize'})
    586 {
    587 $self->{'_REQUEST_'}{'LASTACK'} = int($self->{'FileSize'} / $self->{'BlkSize'}) + 1;
    588 }
    589 }
    590
    591 # send OACK for RFC 2347 options
    592 return($self->sendOACK());
    593 }
    594 else
    595 {
    596 if($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_WRQ)
    597 {
    598 # opcode is WRQ: send ACK for datablock 0
    599 if($self->{'_UDPSERVER_'}->send(pack("nn", TFTP_OPCODE_ACK, 0)))
    600 {
    601 return(1);
    602 }
    603 else
    604 {
    605 $! = $self->{'_UDPSERVER_'}->sockopt(SO_ERROR);
    606 $LASTERROR = sprintf "Socket SEND error: %s\n", $!;
    607 return(undef);
    608 }
    609 }
    610 else
    611 {
    612 return(1);
    613 }
    614 }
    615 }
    616
    617
    618 #
    619 # Usage: $requestOBJ->readFILE(\$data);
    620 # return number of bytes read from file if success or undef if error
    621 #
    622 sub readFILE
    623 {
    624 my $self = shift;
    625 my $datablk = shift;
    626
    627 if($self->{'_REQUEST_'}{'PREVACK'} < $self->{'_REQUEST_'}{'LASTACK'})
    628 {
    629 # if requested block is next block, read next block and return bytes read
    630 my $fh = $self->{'_REQUEST_'}{'_FH_'};
    631 # modified for supporting NETASCII transfers on 25/05/2009
    632 # my $bytes = read($fh, $$datablk, $self->{'BlkSize'});
    633 my $bytes = sysread($fh, $$datablk, $self->{'BlkSize'});
    634 if(defined($bytes))
    635 {
    636 return($bytes);
    637 }
    638 else
    639 {
    640 $LASTERROR = sprintf "Error $! reading file '%s'", $self->{'_REQUEST_'}{'FileName'};
    641 return(undef);
    642 }
    643 }
    644 else
    645 {
    646 # if requested block is last block, return length of last block
    647 return(length($$datablk));
    648 }
    649 }
    650
    651
    652 #
    653 # Usage: $requestOBJ->writeFILE(\$data);
    654 # return number of bytes written to file if success or undef if error
    655 #
    656 sub writeFILE
    657 {
    658 my $self = shift;
    659 my $datablk = shift;
    660
    661 if($self->{'_REQUEST_'}{'PREVBLK'} > $self->{'_REQUEST_'}{'LASTBLK'})
    662 {
    663 # if last block is < than previous block, return length of last block
    664 return(length($$datablk));
    665 }
    666 elsif($self->{'_REQUEST_'}{'LASTBLK'} eq ($self->{'_REQUEST_'}{'PREVBLK'} + 1))
    667 {
    668 # if block is next block, write next block and return bytes written
    669 my $fh = $self->{'_REQUEST_'}{'_FH_'};
    670 my $bytes = syswrite($fh, $$datablk);
    671 return($bytes);
    672 }
    673 else
    674 {
    675 $LASTERROR = sprintf "TFTP Error DATA block %d is out of sequence, expected block was %d", $self->{'_REQUEST_'}{'LASTBLK'}, $self->{'_REQUEST_'}{'PREVBLK'} + 1;
    676 $self->sendERR(5);
    677 return(undef);
    678 }
    679 }
    680
    681
    682 #
    683 # Usage: $requestOBJ->sendFILE();
    684 # return 1 if success or undef if error
    685 #
    686 sub sendFILE
    687 {
    688 my $self = shift;
    689
    690 while(1)
    691 {
    692 if($self->{'_REQUEST_'}{'LASTACK'} < $self->{'_REQUEST_'}{'LASTBLK'})
    693 {
    694 my $datablk = 0;
    695 if(defined($self->readFILE(\$datablk)))
    696 {
    697 # read from file successful
    698 # increment the transferred bytes counter
    699 $self->{'_REQUEST_'}{'TotalBytes'} += length($datablk);
    700 if($self->sendDATA(\$datablk))
    701 {
    702 # send to socket successful
    703 if($self->{'CallBack'})
    704 {
    705 &{$self->{'CallBack'}}($self);
    706 }
    707 }
    708 else
    709 {
    710 # error sending to socket
    711 return(undef);
    712 }
    713 }
    714 else
    715 {
    716 # error reading from file
    717 return(undef);
    718 }
    719 }
    720 else
    721 {
    722 # transfer completed
    723 return(1);
    724 }
    725 }
    726 }
    727
    728
    729 #
    730 # Usage: $requestOBJ->recvFILE();
    731 # return 1 if success or undef if error
    732 #
    733 sub recvFILE
    734 {
    735 my $self = shift;
    736
    737 $self->{'_REQUEST_'}{'LASTBLK'} = 0;
    738 $self->{'_REQUEST_'}{'PREVBLK'} = 0;
    739
    740 while(1)
    741 {
    742 my $datablk = 0;
    743 if($self->recvDATA(\$datablk))
    744 {
    745 # DATA received
    746 if(defined($self->writeFILE(\$datablk)))
    747 {
    748 # DATA written to file
    749 my $udpserver = $self->{'_UDPSERVER_'};
    750
    751 if(defined($udpserver->send(pack("nn", TFTP_OPCODE_ACK, $self->{'_REQUEST_'}{'LASTBLK'}))))
    752 {
    753 # sent ACK
    754 # increment the transferred bytes counter
    755 $self->{'_REQUEST_'}{'TotalBytes'} += length($datablk);
    756 if(length($datablk) < $self->{'BlkSize'})
    757 {
    758 return(1);
    759 }
    760 else
    761 {
    762 next;
    763 }
    764 }
    765 else
    766 {
    767 $! = $udpserver->sockopt(SO_ERROR);
    768 $LASTERROR = sprintf "Socket SEND error: %s\n", $!;
    769 return(undef);
    770 }
    771 }
    772 else
    773 {
    774 # error writing data
    775 return(undef);
    776 }
    777 }
    778 else
    779 {
    780 # timeout waiting for data
    781 return(undef);
    782 }
    783 }
    784 }
    785
    786 #
    787 # Usage: $requestOBJ->recvDATA(\$data);
    788 # return 1 if success or undef if error
    789 #
    790 sub recvDATA
    791 {
    792 my $self = shift;
    793 my $datablk = shift;
    794
    795 my ($datagram, $opcode, $datain);
    796
    797 my $udpserver = $self->{'_UDPSERVER_'};
    798
    799 # vars for IO select
    800 my ($rin, $rout, $ein, $eout) = ('', '', '', '');
    801 vec($rin, fileno($udpserver), 1) = 1;
    802
    803 # wait for data
    804 if(select($rout=$rin, undef, $eout=$ein, $self->{'ACKtimeout'}))
    805 {
    806 # read the message
    807 if($udpserver->recv($datagram, $self->{'BlkSize'} + 4))
    808 {
    809 # decode the message
    810 ($opcode, $datain) = unpack("na*", $datagram);
    811 if($opcode eq TFTP_OPCODE_DATA)
    812 {
    813 # message is DATA
    814 $self->{'_REQUEST_'}{'PREVBLK'} = $self->{'_REQUEST_'}{'LASTBLK'};
    815 ($self->{'_REQUEST_'}{'LASTBLK'}, $$datablk) = unpack("na*", $datain);
    816
    817 if($self->{'CallBack'})
    818 {
    819 &{$self->{'CallBack'}}($self);
    820 }
    821
    822 return(1);
    823 }
    824 elsif($opcode eq TFTP_OPCODE_ERROR)
    825 {
    826 # message is ERR
    827 $LASTERROR = sprintf "TFTP error message: %s", $datain;
    828 return(undef);
    829 }
    830 else
    831 {
    832 # other messages...
    833 $LASTERROR = sprintf "Opcode %d not supported waiting for DATA\n", $opcode;
    834 return(undef);
    835 }
    836 }
    837 else
    838 {
    839 $! = $udpserver->sockopt(SO_ERROR);
    840 $LASTERROR = sprintf "Socket RECV error: %s\n", $!;
    841 return(undef);
    842 }
    843 }
    844 else
    845 {
    846 $LASTERROR = sprintf "Timeout occurred on DATA packet %d\n", $self->{'_REQUEST_'}{'LASTBLK'} + 1;
    847 return(undef);
    848 }
    849 }
    850
    851
    852 #
    853 # Usage: $requestOBJ->sendDATA(\$data);
    854 # return 1 if success or undef if error
    855 #
    856 sub sendDATA
    857 {
    858 my $self = shift;
    859 my $datablk = shift;
    860
    861 my $udpserver = $self->{'_UDPSERVER_'};
    862 my $retry = 0;
    863
    864 my ($datagram, $opcode, $datain);
    865
    866 while($retry < $self->{'ACKretries'})
    867 {
    868 if($udpserver->send(pack("nna*", TFTP_OPCODE_DATA, $self->{'_REQUEST_'}{'LASTACK'} + 1, $$datablk)))
    869 {
    870 # vars for IO select
    871 my ($rin, $rout, $ein, $eout) = ('', '', '', '');
    872 vec($rin, fileno($udpserver), 1) = 1;
    873
    874 # wait for acknowledge
    875 if(select($rout=$rin, undef, $eout=$ein, $self->{'ACKtimeout'}))
    876 {
    877 # read the message
    878 if($udpserver->recv($datagram, TFTP_MAX_BLKSIZE + 4))
    879 {
    880 # decode the message
    881 ($opcode, $datain) = unpack("na*", $datagram);
    882 if($opcode eq TFTP_OPCODE_ACK)
    883 {
    884 # message is ACK
    885 # modified for supporting more blocks count than 65535, O.Z. 15.08.2007
    886 $self->{'_REQUEST_'}{'PREVACK'} = $self->{'_REQUEST_'}{'LASTACK'};
    887 if(int(($self->{'_REQUEST_'}{'LASTACK'}+1) % 65536) == unpack("n", $datain)){
    888 $self->{'_REQUEST_'}{'LASTACK'}++;
    889 };
    890 return(1);
    891 }
    892 elsif($opcode eq TFTP_OPCODE_ERROR)
    893 {
    894 # message is ERR
    895 $LASTERROR = sprintf "TFTP error message: %s", $datain;
    896 return(undef);
    897 }
    898 else
    899 {
    900 # other messages...
    901 $LASTERROR = sprintf "Opcode %d not supported as a reply to DATA\n", $opcode;
    902 return(undef);
    903 }
    904 }
    905 else
    906 {
    907 $! = $udpserver->sockopt(SO_ERROR);
    908 $LASTERROR = sprintf "Socket RECV error: %s\n", $!;
    909 return(undef);
    910 }
    911 }
    912 else
    913 {
    914 $LASTERROR = sprintf "Retry %d - timeout occurred on ACK packet %d\n", $retry, $self->{'_REQUEST_'}{'LASTACK'} + 1;
    915 $debug and carp($LASTERROR);
    916 $retry++;
    917 }
    918 }
    919 else
    920 {
    921 $! = $udpserver->sockopt(SO_ERROR);
    922 $LASTERROR = sprintf "Socket SEND error: %s\n", $!;
    923 return(undef);
    924 }
    925 }
    926 }
    927
    928 #
    929 # Usage: $requestOBJ->openFILE()
    930 # returns 1 if file is opened, undef if error
    931 #
    932 sub openFILE
    933 {
    934 # the request object
    935 my $self = shift;
    936
    937 if($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_RRQ)
    938 {
    939 ########################################
    940 # opcode is RRQ, open file for reading #
    941 ########################################
    942 if(open(RFH, "<".$self->{'_REQUEST_'}{'FileName'}))
    943 {
    944 # if OCTET mode, set FileHandle to binary mode...
    945 if($self->{'_REQUEST_'}{'Mode'} eq 'OCTET')
    946 {
    947 binmode(RFH);
    948 }
    949
    950 my $size = -s($self->{'_REQUEST_'}{'FileName'});
    951 $self->{'_REQUEST_'}{'LASTBLK'} = 1 + int($size / $self->{'BlkSize'});
    952
    953 # save the filehandle reference...
    954 $self->{'_REQUEST_'}{'_FH_'} = *RFH;
    955
    956 return(1);
    957 }
    958 else
    959 {
    960 $LASTERROR = sprintf "Error opening file \'%s\' for reading\n", $self->{'_REQUEST_'}{'FileName'};
    961 return(undef);
    962 }
    963 }
    964 elsif($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_WRQ)
    965 {
    966 ########################################
    967 # opcode is WRQ, open file for writing #
    968 ########################################
    969 if(open(WFH, ">".$self->{'_REQUEST_'}{'FileName'}))
    970 {
    971 # if OCTET mode, set FileHandle to binary mode...
    972 if($self->{'_REQUEST_'}{'Mode'} eq 'OCTET')
    973 {
    974 binmode(WFH);
    975 }
    976
    977 # save the filehandle reference...
    978 $self->{'_REQUEST_'}{'_FH_'} = *WFH;
    979
    980 return(1);
    981 }
    982 else
    983 {
    984 $LASTERROR = sprintf "Error opening file \'%s\' for writing\n", $self->{'_REQUEST_'}{'FileName'};
    985 return(undef);
    986 }
    987 }
    988 else
    989 {
    990 ############################
    991 # other opcodes are errors #
    992 ############################
    993 $LASTERROR = sprintf "OPCODE %d is not supported\n", $self->{'_REQUEST_'}{'OPCODE'};
    994 return(undef);
    995 }
    996 }
    997
    998 #
    999 # Usage: $requestOBJ->closeFILE()
    1000 # returns 1 if file is success, undef if error
    1001 #
    1002 sub closeFILE
    1003 {
    1004 my $self = shift;
    1005
    1006 if($self->{'_REQUEST_'}{'_FH_'})
    1007 {
    1008 if(close($self->{'_REQUEST_'}{'_FH_'}))
    1009 {
    1010 return(1);
    1011 }
    1012 else
    1013 {
    1014 $LASTERROR = "Error closing filehandle\n";
    1015 return(undef);
    1016 }
    1017 }
    1018 else
    1019 {
    1020 return(1);
    1021 }
    1022 }
    1023
    1024 #
    1025 # Usage: $requestOBJ->checkFILE()
    1026 # returns 1 if file is found, undef if file is not found
    1027 #
    1028 sub checkFILE
    1029 {
    1030 # the request object
    1031 my $self = shift;
    1032
    1033 # requested file
    1034 my $reqfile = $self->{'_REQUEST_'}{'FileName'};
    1035
    1036 if($self->{'FileName'})
    1037 {
    1038 # filename is fixed
    1039 $self->{'_REQUEST_'}{'FileName'} = $self->{'FileName'};
    1040
    1041 if(($self->{'FileName'} =~ /$reqfile/) and -e($self->{'FileName'}))
    1042 {
    1043 # fixed name contains requested file and file exists
    1044 $self->{'FileSize'} = -s($self->{'FileName'});
    1045 return(1);
    1046 }
    1047 }
    1048 elsif($self->{'RootDir'})
    1049 {
    1050 # rootdir is fixed
    1051 $reqfile = $self->{'RootDir'}.'/'.$reqfile;
    1052 $self->{'_REQUEST_'}{'FileName'} = $reqfile;
    1053
    1054 if(-e($reqfile))
    1055 {
    1056 # file exists in rootdir
    1057 $self->{'FileSize'} = -s($reqfile);
    1058 return(1);
    1059 }
    1060 }
    1061
    1062 return(undef);
    1063 }
    1064
    1065 #
    1066 # Usage: $requestOBJ->sendOACK();
    1067 # return 1 for success and undef for error (see $Net::TFTPd::LASTERROR for cause)
    1068 #
    1069 sub sendOACK
    1070 {
    1071 # the request object
    1072 my $self = shift;
    1073 my $udpserver = $self->{'_UDPSERVER_'};
    1074 my $retry = 0;
    1075
    1076 my ($datagram, $opcode, $datain);
    1077
    1078 while($retry < $self->{'ACKretries'})
    1079 {
    1080 # send oack
    1081 my $data = join("\0", %{ $self->{'_RESPONSE_'}{'RFC2347'} })."\0";
    1082 if($udpserver->send(pack("na*", TFTP_OPCODE_OACK, $data)))
    1083 {
    1084 # opcode is RRQ
    1085 if($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_RRQ)
    1086 {
    1087 # vars for IO select
    1088 my ($rin, $rout, $ein, $eout) = ('', '', '', '');
    1089 vec($rin, fileno($udpserver), 1) = 1;
    1090
    1091 # wait for acknowledge
    1092 if(select($rout=$rin, undef, $eout=$ein, $self->{'ACKtimeout'}))
    1093 {
    1094 # read the message
    1095 if($udpserver->recv($datagram, TFTP_MAX_BLKSIZE + 4))
    1096 {
    1097 # decode the message
    1098 ($opcode, $datain) = unpack("na*", $datagram);
    1099 if($opcode == TFTP_OPCODE_ACK)
    1100 {
    1101 # message is ACK
    1102 my $lastack = unpack("n", $datain);
    1103 if($lastack)
    1104 {
    1105 # ack is not for block 0... ERROR
    1106 $LASTERROR = sprintf "Received ACK for block %d instead of 0", $lastack;
    1107 return(undef);
    1108 }
    1109 return 1;
    1110 }
    1111 elsif($opcode == TFTP_OPCODE_ERROR)
    1112 {
    1113 # message is ERR
    1114 $LASTERROR = sprintf "TFTP error message: %s", $datain;
    1115 return(undef);
    1116 }
    1117 else
    1118 {
    1119 # other messages...
    1120 $LASTERROR = sprintf "Opcode %d not supported as a reply to OACK\n", $opcode;
    1121 return(undef);
    1122 }
    1123 }
    1124 else
    1125 {
    1126 $! = $udpserver->sockopt(SO_ERROR);
    1127 $LASTERROR = sprintf "Socket RECV error: %s\n", $!;
    1128 return (undef);
    1129 }
    1130 }
    1131 else
    1132 {
    1133 $LASTERROR = sprintf "Retry %d - timeout occurred waiting reply for OACK packet\n", $retry;
    1134 $debug and carp($LASTERROR);
    1135 $retry++;
    1136 }
    1137 }
    1138 elsif($self->{'_REQUEST_'}{'OPCODE'} eq TFTP_OPCODE_WRQ)
    1139 {
    1140 # opcode is WRQ
    1141 return(1);
    1142 }
    1143 }
    1144 else
    1145 {
    1146 $! = $udpserver->sockopt(SO_ERROR);
    1147 $LASTERROR = sprintf "Socket SEND error: %s\n", $!;
    1148 return(undef);
    1149 }
    1150 }
    1151 }
    1152
    1153 #
    1154 # Usage: $requestOBJ->sendERR($code, $message);
    1155 # returns 1 if success, undef if error
    1156 #
    1157 sub sendERR
    1158 {
    1159 my $self = shift;
    1160 my($errcode, $errmsg) = @_;
    1161 # modified for supporting NETASCII transfers on 25/05/2009
    1162 #$errmsg or $errmsg = '';
    1163 $errmsg or $errmsg = $ERRORS{$errcode};
    1164
    1165 my $udpserver = $self->{'_UDPSERVER_'};
    1166
    1167 if($udpserver->send(pack("nnZ*", 5, $errcode, $errmsg)))
    1168 {
    1169 return(1);
    1170 }
    1171 else
    1172 {
    1173 $! = $udpserver->sockopt(SO_ERROR);
    1174 $LASTERROR = sprintf "Socket SEND error: %s\n", $!;
    1175 return(undef);
    1176 }
    1177 }
    1178
    1179 sub error
    1180 {
    1181 return($LASTERROR);
    1182 }
    1183
    1184 # Preloaded methods go here.
    1185
    1186 1;
    1187 __END__
    1188
    1189 # Below is stub documentation for your module. You better edit it!
    1190
    1191 =head1 NAME
    1192
    1193 Net::TFTPd - Perl extension for Trivial File Transfer Protocol Server
    1194
    1195 =head1 SYNOPSIS
    1196
    1197 use strict;
    1198 use Net::TFTPd;
    1199
    1200 my $tftpdOBJ = Net::TFTPd->new('RootDir' => 'path/to/files')
    1201 or die "Error creating TFTPd listener: %s", Net::TFTPd->error;
    1202
    1203 my $tftpRQ = $tftpdOBJ->waitRQ(10)
    1204 or die "Error waiting for TFTP request: %s", Net::TFTPd->error;
    1205
    1206 $tftpRQ->processRQ()
    1207 or die "Error processing TFTP request: %s", Net::TFTPd->error;
    1208
    1209 printf "%u bytes has been transferred", $tftpRQ->getTotalBytes() || 0;
    1210
    1211 =head1 DESCRIPTION
    1212
    1213 C<Net::TFTPd> is a class implementing a simple I<Trivial File Transfer Protocol> server in Perl as described in RFC1350.
    1214
    1215 C<Net::TFTPd> also supports the TFTP Option Extension (as described in RFC2347), with the following options:
    1216
    1217 RFC2348 TFTP Blocksize Option
    1218 RFC2349 TFTP Timeout Interval and Transfer Size Options
    1219
    1220 =head1 EXPORT
    1221
    1222 None by default.
    1223
    1224 =head2 %OPCODES
    1225
    1226 The %OPCODES tag exports the I<%OPCODES> hash:
    1227
    1228 %OPCODES = (
    1229 1 => 'RRQ',
    1230 2 => 'WRQ',
    1231 3 => 'DATA',
    1232 4 => 'ACK',
    1233 5 => 'ERROR',
    1234 6 => 'OACK',
    1235 'RRQ' => 1,
    1236 'WRQ' => 2,
    1237 'DATA' => 3,
    1238 'ACK' => 4,
    1239 'ERROR' => 5,
    1240 'OACK' => 6
    1241 );
    1242
    1243 =head1 Listener constructor
    1244
    1245 =head2 new()
    1246
    1247 $listener = new Net::TFTPd( ['RootDir' => 'path/to/files' | 'FileName' => 'path/to/file'] [, OPTIONS ] );
    1248
    1249 or
    1250
    1251 $listener = Net::TFTPd->new( ['RootDir' => 'path/to/files' | 'FileName' => 'path/to/file'] [, OPTIONS ] );
    1252
    1253 Create a new Net::TFTPd object where 'path/to/files' is the default path to file repository
    1254 or 'path/to/file' is the single file allowed for download, and OPTIONS are the default server
    1255 options.
    1256
    1257 Valid options are:
    1258
    1259 Option Description Default
    1260 ------ ----------- -------
    1261 LocalAddr Interface to bind to (for multi-homed server) any
    1262 LocalPort Port to bind server to 69
    1263 Timeout Timeout in seconds to wait for a request 10
    1264 ACKtimeout Timeout in seconds to wait for an ACK packet 4
    1265 ACKretries Maximum number of retries waiting for ACK 4
    1266 Readable Clients are allowed to read files 1
    1267 Writable Clients are allowed to write files 0
    1268 BlkSize Minimum blocksize to negotiate for transfers 512
    1269 CallBack Reference to code executed for each transferred block -
    1270 Debug Activates debug mode (verbose) 0
    1271
    1272 =head2 CallBack
    1273
    1274 The CallBack code is called by processRQ method for each tranferred block.
    1275
    1276 The code receives (into @_ array) a reference to internal I<$request> object.
    1277
    1278 Example:
    1279
    1280 sub callback
    1281 {
    1282 my $req = shift;
    1283 printf "block: %u\/%u\n", $req->{'_REQUEST_'}{'LASTACK'}, $req->{'_REQUEST_'}{'LASTBLK'};
    1284 }
    1285
    1286 my $tftpdOBJ = Net::TFTPd->new('RootDir' => 'c:/temp', 'Timeout' => 60, 'CallBack' => \&callback) or die Net::TFTPd->error;
    1287
    1288 =head1 Listener methods
    1289
    1290 =head2 waitRQ()
    1291
    1292 $request = $listener->waitRQ([Timeout]);
    1293
    1294 Waits for a client request (RRQ or WRQ) and returns a I<$request> object or I<undef> if timed out.
    1295
    1296 If I<Timeout> is missing, the timeout defined for I<$listener> object is used instead.
    1297
    1298 When the method returns, the program should fork() and process the request invoking processRQ() while the parent process should re-start waiting for another request.
    1299
    1300 =head1 Request methods
    1301
    1302 =head2 processRQ()
    1303
    1304 $ret = $request->processRQ();
    1305
    1306 Processes a request and returns 1 if success, undef if error.
    1307
    1308 =head2 getFileName()
    1309
    1310 $ret = $request->getFileName();
    1311
    1312 Returns the requested file name.
    1313
    1314 =head2 getMode()
    1315
    1316 $ret = $request->getMode();
    1317
    1318 Returns the transfer mode for the request.
    1319
    1320 =head2 getBlkSize()
    1321
    1322 $ret = $request->getBlkSize();
    1323
    1324 Returns the block size used for the transfer.
    1325
    1326 =head2 getPeerAddr()
    1327
    1328 $ret = $request->getPeerAddr();
    1329
    1330 Returns the address of the requesting client.
    1331
    1332 =head2 getPeerPort()
    1333
    1334 $ret = $request->getPeerMode();
    1335
    1336 Returns the port of the requesting client.
    1337
    1338 =head2 getTotalBytes()
    1339
    1340 $ret = $request->getTotalBytes();
    1341
    1342 Returns the number of bytes transferred for the request.
    1343
    1344 =head1 CREDITS
    1345
    1346 Thanks to E<lt>VinceE<gt> for the NETASCII support and transferred bytes patch.
    1347
    1348 =head1 AUTHOR
    1349
    1350 Luigino Masarati, E<lt>lmasarati@hotmail.comE<gt>
    1351
    1352 =head1 SEE ALSO
    1353
    1354 L<Net::TFTP>.
    1355
    1356 =cut
    1357