root/misc/tftpd.pl

Revision 2624, 8.5 kB (checked in by qldrob, 7 years ago)

Adding tftpd.pl, found this on a website, possibly useful for 3.0 with
devicestaketwo and provisioning etc. Untested, unlooked at, unknown copyright.

  • Property svn:mime-type set to text/plain
  • Property svn:executable set to *
Line 
1 #!/usr/bin/perl -w
2 # Found this on
3 # http://xaxxon.slackworks.com/viewcvs/viewcvs.cgi/src/tftp-server/tftp.pl?rev=1.5&content-type=text/vnd.viewcvs-markup
4 #
5 # Unknown licence, at the moment. Potentially useful for our provisioning
6 # project.
7 use strict;
8
9 sub nonblock ( $ );
10
11 use Fcntl;
12 use IO::Socket;
13 use IO::Select;
14 use IO::File; # lets you use filehandles like variables
15
16
17 # Set the port to 69 unless otherwise specified
18 my $Port = 69;
19 if ( @ARGV == 1 ) {
20   $Port = $ARGV [ 0 ];
21 } elsif ( @ARGV > 1 ) {
22   print "Usage: $0 <port>\n";
23   exit ( 0 );
24 }
25
26 # Get the CVSROOT out of the specified file
27 my $CVS_INFO_FILENAME = "cvs_info";
28 my $CVSROOT;
29 $CVSROOT = ReadCvsInfo ( $CVS_INFO_FILENAME );
30
31 # Set up the main listening port and set to nonblocking
32 my $Server = IO::Socket::INET->new ( LocalPort => $Port,
33                    Proto     => "udp" ) or
34   die "Couldn't bind to port: $Port (Ports <= 1024 require root permission)";
35 nonblock ( $Server );
36
37 # hash for holding clients
38 my %Clients;
39
40 # create a select object and add the main port
41 my $Select = IO::Select->new ( );
42 $Select->add ( $Server );
43
44 # main loop
45 while ( 1 ) {
46
47     # select from all handles to see if we've got anything incoming
48
49     foreach my $Readable ( $Select->can_read ( ) ) {
50
51     if ( $Readable == $Server ) {
52
53       # We just got a new connection
54
55       my $Data;
56       my $Sender = $Server->recv ( $Data, 1024 );
57
58       # Get IP info on client
59       my ( $Port, $IP ) = sockaddr_in ( $Sender );
60       my $Hostname = gethostbyaddr ( $IP, AF_INET );
61
62       # make a new socket or just toss this connection in the bit bucket if we can't
63       my $OutFd = IO::Socket::INET->new ( PeerAddr => $Hostname,
64                         PeerPort => $Port,
65                         Proto    => "udp" ) or
66                           next;
67       print "Got new connection\n";
68       nonblock ( $OutFd );
69
70       # add our new socket to the select group
71       $Select->add ( $OutFd );
72      
73      
74       $Clients{$OutFd} = {};
75       $Clients{$OutFd}->{ip} = $IP;
76       $Clients{$OutFd}->{port} = $Port;
77       $Clients{$OutFd}->{data} = $Data;
78
79       # Create new entry in Clients hash for this client
80       $Clients{$OutFd}->{outfd} = $OutFd;
81
82       my ( $Opcode, $Filename, $Mode ) = UnpackRequest ( $Clients{$OutFd}->{data} );
83      
84       $Clients{$OutFd}->{opcode} = $Opcode;
85       $Clients{$OutFd}->{filename} = $Filename;
86       $Clients{$OutFd}->{transfermode} = $Mode;
87
88
89       nonblock $OutFd;
90       $Clients{$OutFd}->{outfd} = $OutFd;
91
92       DealWithClient ( $Clients{$OutFd} );
93
94     } else {
95
96       # Got new data on existing connection
97       $Readable->recv ( $Clients{$Readable}->{data}, 1024 );
98
99       DealWithClient ( $Clients{$Readable} );
100
101     }
102
103     }
104
105 }
106
107
108 sub CleanupClient
109 {
110
111     my ( $Client ) = @_;
112  
113     # delete the client object from the Clients hash based on the index of its outgoing FD
114     delete $Clients{$Client->{outfd}};
115  
116     # Close their data file
117     $Client->{file}->close ( ) if exists $Client->{file} and $Client->{file}->opened ( );
118
119     #  remove their outoing FD from the select list
120     $Select->remove ( $Client->{outfd} ) if $Select->exists ( $Client->{outfd} );
121    
122     # close the outgoing connection
123     $Client->{outfd}->close ( );
124
125   # clean up the file
126   `rm -rf $Client->{basedir}` if exists $Client->{basedir};
127  
128 }
129
130 sub DealWithClient {
131
132     my ( $Client ) = @_;
133
134     $Client->{opcode} = unpack ( "n", $Client->{data} );
135
136     if ( $Client->{opcode} == 1 ) {
137
138     # if we got a read request
139     $Client->{action} = "read";
140     $Client->{lastack} = 0;
141     SendData ( $Client );
142
143     } elsif ( $Client->{opcode} == 2 ) {
144
145     # if we got a write request
146     $Client->{action} = "write";
147     ReceiveData ( $Client );
148
149     } elsif ( $Client->{opcode} == 3 ) {
150
151     # if we got a data block (512 bytes)
152     ReceiveData ( $Client );
153
154     } elsif ( $Client->{opcode} == 4 ) {
155    
156     # if we got an acknowledgment
157    
158     # break down the ack
159     my $AckPacketNumber = UnpackAck ( $Client->{data} );
160
161     if ( $AckPacketNumber < $Client->{lastack} ) {
162       # toss it, it's old
163     } elsif ( $AckPacketNumber > $Client->{lastack} + 1 ) {
164       # that's an error
165       SendError();
166       $Client->{disconnect} = 1;
167     } else {
168
169       # if we're done sending data
170       if ( $Client->{alldataread} ) {
171         CleanupClient ( $Client );
172       } else {
173         $Client->{lastack} = $AckPacketNumber;
174         SendData ( $Client );
175       }
176     }
177
178     } elsif ( $Client->{opcode} == 5 ) {
179
180     # if we got an error
181     CleanupClient ( $Client );
182
183
184     } else {
185     #die "Unknown opcode '$Client->{opcode}'";
186     CleanupClient ( $Client );
187     }
188
189 }
190
191 sub ReceiveData
192 {
193
194     my ( $Client ) = @_;
195  
196     # send back an ack of 0 to say we got the write request
197   if ( !exists $Client->{file} ) {
198    
199     if ( -e $Client->{filename} ) {
200      
201       $Client->{outfd}->send ( CreateError ( 6, "File already exists" ) );
202       CleanupClient ( $Client );
203      
204     }
205
206     $Client->{file} = IO::File->new ( ">$Client->{filename}" );
207
208     # if we couldn't open the file, send back an error
209     if ( !$Client->{file}->opened ( ) ) {
210       $Client->{outfd}->send ( CreateError ( 2, "Access Violation" ) );
211
212       CleanupClient ( $Client );
213
214     }
215    
216     # send a packet 0 ack back
217     $Client->{outfd}->send ( CreateAck ( 0 ) );
218     $Client->{lastack} = 0;
219    
220     } else {
221    
222    
223     my ( undef, $PacketNumber, $Data ) = UnpackData ( $Client->{data} );
224
225     # if this is a duplicate of an old packet, then disregard it
226     if ( $PacketNumber <= $Client->{lastack} ) {
227       return;
228     }
229
230     # else if this packet is too far ahead, then error
231     if ( $PacketNumber > $Client->{lastack} + 1 ) {
232
233       $Client->{outfd}->
234         send (CreateError ( 0, "Received invalid packet number" ) );
235
236       CleanupClient ( $Client );
237       return;
238
239     }
240
241     # otherwise this is the packet we want
242     $Client->{file}->print ($Data);
243     $Client->{outfd}->send ( CreateAck ( $PacketNumber ) );
244     $Client->{lastack} = $PacketNumber;
245
246     if ( length $Data < 512 ) {
247
248       $Client->{file}->close ( );
249       CleanupClient ( $Client );
250
251     }
252    
253     }
254
255 }
256
257 sub SendData {
258
259     my ( $Client ) = @_;
260
261     if ( !exists $Client->{file} ) {
262    
263     # check the file out of CVS
264     GetFileFromCvs ( $Client );
265
266     $Client->{file} = IO::File->new;
267     if ( !$Client->{file}->open ( "$Client->{basedir}/$Client->{filename}" ) )
268     {
269
270       $Client->{outfd}->send ( CreateError ( 1, "File not found" ) );
271       CleanupClient ( $Client );
272       return ;
273     }
274
275
276     }
277
278     my $Data;
279     my $BytesRead = read $Client->{file}, $Data, 512;
280
281     # store the data in case we have to re-send
282     $Client->{lastdata} = $Data;
283     $Client->{outfd}->send ( CreateData ( $Client->{lastack} + 1, $Data ) );
284
285     if ( $BytesRead < 512 ) {
286
287     # close the filehandle since we've read everything
288     $Client->{file}->close ( );
289    
290     # set a flag saying we've sent everything, so the next ack disconnects
291     $Client->{alldataread} = 1;
292    
293     }
294    
295 }
296
297
298
299
300
301
302
303
304 # Create TFTP Data packet
305 # $Packet is the packet sequence number
306 # $Buffer is the actual data
307 sub CreateData
308 {
309     my ( $Packet, $Buffer ) = @_;
310
311     my $BufferLength = length ( $Buffer );
312     return pack ( "nna$BufferLength", 3, $Packet, $Buffer );
313
314 }
315
316 sub CreateError
317 {
318
319     my ( $ErrorCode, $ErrorString ) = @_;
320
321     return pack ( "nnZ*", 5, $ErrorCode, $ErrorString );
322
323 }
324
325 # Returns the opcode (3), the block number, and data
326 sub UnpackData
327 {
328
329     my ( $Data ) = @_;
330     return unpack ( "nna*", $Data );
331
332 }
333
334 sub CreateAck
335 {
336     my ( $Block ) = @_;
337     return pack ( "nn", 4, $Block );
338 }
339
340 sub UnpackAck
341 {
342
343     my ( $Data ) = @_;
344     my ( undef, $Packet ) = unpack ( "nn", $Data );
345
346     return $Packet;
347
348 }
349
350 # Returns the opcode, filename, and mode
351 sub UnpackRequest
352 {
353     my ( $Data ) = @_;
354     return unpack ( "nZ*Z*", $Data );
355 }
356
357 # set $Socket to nonblocking
358 sub nonblock ( $ )
359 {
360     return;
361     my ( $Socket ) = @_;
362
363     my $Flags;
364
365     $Flags = fcntl ( $Socket, F_GETFL, 0 );
366    
367     fcntl ( $Socket, F_SETFL, $Flags | O_NONBLOCK );
368
369 }
370
371
372 sub CleanupCvsFilesinTmp
373 {
374
375   my ( $Client ) = @_;
376
377   `rm -rf /tmp/$Client->{outfd}`;
378
379 }
380
381
382 sub ReadCvsInfo
383 {
384
385   my ( $Filename ) = @_;
386
387   open CVSINFO, $Filename or
388     die "Couldn't open CVS info file: $Filename";
389
390   my $CvsInfo = <CVSINFO>;
391
392   chomp $CvsInfo;
393  
394   return $CvsInfo;
395
396 }
397
398
399 sub GetFileFromCvs
400 {
401
402   my ( $User ) = @_;
403
404   my $SockPort = $User->{outfd}->sockport();
405   $User->{basedir}="/tmp/CVS-TFTPD-$SockPort";
406
407   # Set the CVS info
408   $ENV{CVS_RSH}="ssh";
409   $ENV{CVSROOT}=$CVSROOT;
410
411   print "CVSROOT = '$CVSROOT'\n";
412
413   # make a directory to put it in and do the actual checkout.  Note that it
414   #   always checks out the file into the entire path.
415   print "'mkdir $User->{basedir}'\n";
416   print `mkdir $User->{basedir}`;
417   chdir ( $User->{basedir} );
418   print "Doing checkout\n";
419   print `cvs checkout $User->{filename}`;
420   print "Done checking out\n";
421
422 }
423
424 sub DeleteTempCvsFile
425 {
426
427   my ( $User ) = @_;
428
429   print `rm $User->{basedir}/$User->{filename}`;
430
431 }
432
433
Note: See TracBrowser for help on using the browser.