File Coverage

blib/lib/Net/TFTPd.pm
Criterion Covered Total %
statement 54 424 12.7
branch 0 188 0.0
condition 0 35 0.0
subroutine 18 42 42.8
pod 10 24 41.6
total 82 713 11.5


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