/[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

Contents of /lib/Net/TFTPd.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 195 - (show 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 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