/[pxelator]/lib/Net/TFTPd.pm
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /lib/Net/TFTPd.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 195 - (hide annotations)
Tue Aug 11 12:35:41 2009 UTC (14 years, 8 months ago) by dpavlin
File size: 31352 byte(s)
Import Net::TFTPd 0.04 so we don't depend on CPAN for deployment
1 dpavlin 195 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    

  ViewVC Help
Powered by ViewVC 1.1.26