Author: rwinston
Date: Sun Feb 24 13:40:34 2008
New Revision: 630684

URL: http://svn.apache.org/viewvc?rev=630684&view=rev
Log:
Add TFTP server (NET-187)

Added:
    
commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/tftp/TFTPServer.java
    
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/
    
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTests.java
    
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPTests.java

Added: 
commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/tftp/TFTPServer.java
URL: 
http://svn.apache.org/viewvc/commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/tftp/TFTPServer.java?rev=630684&view=auto
==============================================================================
--- 
commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/tftp/TFTPServer.java
 (added)
+++ 
commons/proper/net/branches/NET_2_0/src/main/java/org/apache/commons/net/tftp/TFTPServer.java
 Sun Feb 24 13:40:34 2008
@@ -0,0 +1,806 @@
+package org.apache.commons.net.tftp;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import org.apache.commons.net.io.*;
+
+/**
+ * A fully multi-threaded tftp server. Can handle multiple clients at the same 
time. Implements RFC
+ * 1350 and wrapping block numbers for large file support.
+ * 
+ * To launch, just create an instance of the class. An IOException will be 
thrown if the server
+ * fails to start for reasons such as port in use, port denied, etc.
+ * 
+ * To stop, use the shutdown method.
+ * 
+ * To check to see if the server is still running (or if it stopped because of 
an error), call the
+ * isRunning() method.
+ * 
+ * By default, logs debug info and errors to the console streams. This can be 
changed with the
+ * setLog and setLogError methods.
+ * 
+ * @author <A HREF="mailto:[EMAIL PROTECTED]">Dan Armbrust</A>
+ */
+
+public class TFTPServer implements Runnable
+{
+       // Modes for the server. These should be an enum, in java 1.5
+       public static final int GET_ONLY = 0;
+       public static final int PUT_ONLY = 1;
+       public static final int GET_AND_PUT = 2;
+
+       private HashSet transfers_ = new HashSet();
+       private boolean shutdown_ = false;
+       private TFTP serverTftp_;
+       private File serverReadDirectory_;
+       private File serverWriteDirectory_;
+       private int port_;
+       private Exception serverException = null;
+       private int mode_;
+
+       // don't have access to a logger api, so we will log to these streams, 
which
+       // by default are set to the console.
+       private PrintStream log_;
+       private PrintStream logError_;
+
+       private int maxTimeoutRetries_ = 3;
+       private int socketTimeout_;
+
+       public static void main(String[] args) throws Exception
+       {
+               if (args.length != 1)
+               {
+                       System.out
+                                       .println("You must provide 1 argument - 
the base path for the server to serve from.");
+                       System.exit(1);
+               }
+
+               TFTPServer ts = new TFTPServer(new File(args[0]), new 
File(args[0]), GET_AND_PUT);
+               ts.setSocketTimeout(2000);
+
+               System.out.println("TFTP Server running.  Press enter to 
stop.");
+               new InputStreamReader(System.in).read();
+
+               ts.shutdown();
+               System.out.println("Server shut down.");
+               System.exit(0);
+       }
+
+       /**
+        * Start a TFTP Server on the default port (69). Gets and Puts occur in 
the specified
+        * directories.
+        * 
+        * The server will start in another thread, allowing this constructor 
to return immediately.
+        * 
+        * If a get or a put comes in with a relative path that tries to get 
outside of the
+        * serverDirectory, then the get or put will be denied.
+        * 
+        * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and 
GET_AND_PUT allows both.
+        * Modes are defined as int constants in this class.
+        * 
+        * @param serverDirectory
+        * @param mode A value as specified above.
+        * @throws IOException if the server directory is invalid or does not 
exist.
+        */
+       public TFTPServer(File serverReadDirectory, File serverWriteDirectory, 
int mode)
+                       throws IOException
+       {
+               this(serverReadDirectory, serverWriteDirectory, 69, mode, null, 
null);
+       }
+
+       /**
+        * Start a TFTP Server on the specified port. Gets and Puts occur in 
the specified directory.
+        * 
+        * The server will start in another thread, allowing this constructor 
to return immediately.
+        * 
+        * If a get or a put comes in with a relative path that tries to get 
outside of the
+        * serverDirectory, then the get or put will be denied.
+        * 
+        * GET_ONLY mode only allows gets, PUT_ONLY mode only allows puts, and 
GET_AND_PUT allows both.
+        * Modes are defined as int constants in this class.
+        * 
+        * @param serverDirectory
+        * @param mode A value as specified above.
+        * @param log Stream to write log message to. If not provided, uses 
System.out
+        * @param errorLog Stream to write error messages to. If not provided, 
uses System.err.
+        * @throws IOException if the server directory is invalid or does not 
exist.
+        */
+       public TFTPServer(File serverReadDirectory, File serverWriteDirectory, 
int port, int mode,
+                       PrintStream log, PrintStream errorLog) throws 
IOException
+       {
+               port_ = port;
+               mode_ = mode;
+               log_ = (log == null ? System.out : log);
+               logError_ = (errorLog == null ? System.err : errorLog);
+               launch(serverReadDirectory, serverWriteDirectory);
+       }
+
+       /**
+        * Set the max number of retries in response to a timeout. Default 3. 
Min 0.
+        * 
+        * @param retries
+        */
+       public void setMaxTimeoutRetries(int retries)
+       {
+               if (retries < 0)
+               {
+                       throw new RuntimeException("Invalid Value");
+               }
+               maxTimeoutRetries_ = retries;
+       }
+
+       /**
+        * Get the current value for maxTimeoutRetries
+        */
+       public int getMaxTimeoutRetries()
+       {
+               return maxTimeoutRetries_;
+       }
+
+       /**
+        * Set the socket timeout in milliseconds used in transfers. Defaults 
to the value here:
+        * 
http://commons.apache.org/net/apidocs/org/apache/commons/net/tftp/TFTP.html#DEFAULT_TIMEOUT
+        * (5000 at the time I write this) Min value of 10.
+        */
+       public void setSocketTimeout(int timeout)
+       {
+               if (timeout < 10)
+               {
+                       throw new RuntimeException("Invalid Value");
+               }
+               socketTimeout_ = timeout;
+       }
+
+       /**
+        * The current socket timeout used during transfers in milliseconds.
+        */
+       public int getSocketTimeout()
+       {
+               return socketTimeout_;
+       }
+
+       /*
+        * start the server, throw an error if it can't start.
+        */
+       private void launch(File serverReadDirectory, File 
serverWriteDirectory) throws IOException
+       {
+               log_.println("Starting TFTP Server on port " + port_ + ".  Read 
directory: "
+                               + serverReadDirectory + " Write directory: " + 
serverWriteDirectory
+                               + " Server Mode is " + mode_);
+
+               serverReadDirectory_ = serverReadDirectory.getCanonicalFile();
+               if (!serverReadDirectory_.exists() || 
!serverReadDirectory.isDirectory())
+               {
+                       throw new IOException("The server read directory " + 
serverReadDirectory_
+                                       + " does not exist");
+               }
+
+               serverWriteDirectory_ = serverWriteDirectory.getCanonicalFile();
+               if (!serverWriteDirectory_.exists() || 
!serverWriteDirectory.isDirectory())
+               {
+                       throw new IOException("The server write directory " + 
serverWriteDirectory_
+                                       + " does not exist");
+               }
+
+               serverTftp_ = new TFTP();
+
+               // This is the value used in response to each client.
+               socketTimeout_ = serverTftp_.getDefaultTimeout();
+
+               // we want the server thread to listen forever.
+               serverTftp_.setDefaultTimeout(0);
+
+               serverTftp_.open(port_);
+
+               Thread go = new Thread(this);
+               go.setDaemon(true);
+               go.start();
+       }
+
+       protected void finalize() throws Throwable
+       {
+               shutdown();
+       }
+
+       /**
+        * check if the server thread is still running.
+        * 
+        * @return true if running, false if stopped.
+        * @throws Exception throws the exception that stopped the server if 
the server is stopped from
+        *             an exception.
+        */
+       public boolean isRunning() throws Exception
+       {
+               if (shutdown_ && serverException != null)
+               {
+                       throw serverException;
+               }
+               return !shutdown_;
+       }
+
+       public void run()
+       {
+               try
+               {
+                       while (!shutdown_)
+                       {
+                               TFTPPacket tftpPacket;
+
+                               tftpPacket = serverTftp_.receive();
+
+                               TFTPTransfer tt = new TFTPTransfer(tftpPacket);
+                               synchronized(transfers_)
+                               {
+                                       transfers_.add(tt);
+                               }
+
+                               Thread thread = new Thread(tt);
+                               thread.setDaemon(true);
+                               thread.start();
+                       }
+               }
+               catch (Exception e)
+               {
+                       if (!shutdown_)
+                       {
+                               serverException = e;
+                               logError_.println("Unexpected Error in TFTP 
Server - Server shut down! + " + e);
+                       }
+               }
+               finally
+               {
+                       shutdown_ = true; // set this to true, so the launching 
thread can check to see if it started.
+                       if (serverTftp_ != null && serverTftp_.isOpen())
+                       {
+                               serverTftp_.close();
+                       }
+               }
+       }
+
+       /**
+        * Stop the tftp server (and any currently running transfers) and 
release all opened network
+        * resources.
+        */
+       public void shutdown()
+       {
+               shutdown_ = true;
+
+               synchronized(transfers_)
+               {
+                       Iterator it = transfers_.iterator();
+                       while (it.hasNext())
+                       {
+                               ((TFTPTransfer) it.next()).shutdown();
+                       }
+               }
+
+               try
+               {
+                       serverTftp_.close();
+               }
+               catch (RuntimeException e)
+               {
+                       // noop
+               }
+       }
+
+       /*
+        * An instance of an ongoing transfer.
+        */
+       private class TFTPTransfer implements Runnable
+       {
+               private TFTPPacket tftpPacket_;
+
+               private boolean shutdown_ = false;
+
+               TFTP transferTftp_ = null;
+
+               public TFTPTransfer(TFTPPacket tftpPacket)
+               {
+                       tftpPacket_ = tftpPacket;
+               }
+
+               public void shutdown()
+               {
+                       shutdown_ = true;
+                       try
+                       {
+                               transferTftp_.close();
+                       }
+                       catch (RuntimeException e)
+                       {
+                               // noop
+                       }
+               }
+
+               public void run()
+               {
+                       try
+                       {
+                               transferTftp_ = new TFTP();
+
+                               transferTftp_.beginBufferedOps();
+                               transferTftp_.setDefaultTimeout(socketTimeout_);
+
+                               transferTftp_.open();
+
+                               if (tftpPacket_ instanceof 
TFTPReadRequestPacket)
+                               {
+                                       handleRead(((TFTPReadRequestPacket) 
tftpPacket_));
+                               }
+                               else if (tftpPacket_ instanceof 
TFTPWriteRequestPacket)
+                               {
+                                       handleWrite((TFTPWriteRequestPacket) 
tftpPacket_);
+                               }
+                               else
+                               {
+                                       log_.println("Unsupported TFTP request 
(" + tftpPacket_ + ") - ignored.");
+                               }
+                       }
+                       catch (Exception e)
+                       {
+                               if (!shutdown_)
+                               {
+                                       logError_
+                                                       .println("Unexpected 
Error in during TFTP file transfer.  Transfer aborted. "
+                                                                       + e);
+                               }
+                       }
+                       finally
+                       {
+                               try
+                               {
+                                       if (transferTftp_ != null && 
transferTftp_.isOpen())
+                                       {
+                                               transferTftp_.endBufferedOps();
+                                               transferTftp_.close();
+                                       }
+                               }
+                               catch (Exception e)
+                               {
+                                       // noop
+                               }
+                               synchronized(transfers_)
+                               {
+                                       transfers_.remove(this);
+                               }
+                       }
+               }
+
+               /*
+                * Handle a tftp read request.
+                */
+               private void handleRead(TFTPReadRequestPacket trrp) throws 
IOException, TFTPPacketException
+               {
+                       InputStream is = null;
+                       try
+                       {
+                               if (mode_ == PUT_ONLY)
+                               {
+                                       transferTftp_.bufferedSend(new 
TFTPErrorPacket(trrp.getAddress(), trrp
+                                                       .getPort(), 
TFTPErrorPacket.ILLEGAL_OPERATION,
+                                                       "Read not allowed by 
server."));
+                                       return;
+                               }
+
+                               try
+                               {
+                                       is = new BufferedInputStream(new 
FileInputStream(buildSafeFile(
+                                                       serverReadDirectory_, 
trrp.getFilename(), false)));
+                               }
+                               catch (FileNotFoundException e)
+                               {
+                                       transferTftp_.bufferedSend(new 
TFTPErrorPacket(trrp.getAddress(), trrp
+                                                       .getPort(), 
TFTPErrorPacket.FILE_NOT_FOUND, e.getMessage()));
+                                       return;
+                               }
+                               catch (Exception e)
+                               {
+                                       transferTftp_.bufferedSend(new 
TFTPErrorPacket(trrp.getAddress(), trrp
+                                                       .getPort(), 
TFTPErrorPacket.UNDEFINED, e.getMessage()));
+                                       return;
+                               }
+
+                               if (trrp.getMode() == TFTP.NETASCII_MODE)
+                               {
+                                       is = new ToNetASCIIInputStream(is);
+                               }
+
+                               byte[] temp = new 
byte[TFTPDataPacket.MAX_DATA_LENGTH];
+
+                               TFTPPacket answer;
+
+                               int block = 1;
+                               boolean sendNext = true;
+
+                               int readLength = TFTPDataPacket.MAX_DATA_LENGTH;
+
+                               TFTPDataPacket lastSentData = null;
+
+                               // We are reading a file, so when we read less 
than the
+                               // requested bytes, we know that we are at the 
end of the file.
+                               while (readLength == 
TFTPDataPacket.MAX_DATA_LENGTH && !shutdown_)
+                               {
+                                       if (sendNext)
+                                       {
+                                               readLength = is.read(temp);
+                                               if (readLength == -1)
+                                               {
+                                                       readLength = 0;
+                                               }
+
+                                               lastSentData = new 
TFTPDataPacket(trrp.getAddress(), trrp.getPort(), block,
+                                                               temp, 0, 
readLength);
+                                               
transferTftp_.bufferedSend(lastSentData);
+                                       }
+
+                                       answer = null;
+
+                                       int timeoutCount = 0;
+
+                                       while (!shutdown_
+                                                       && (answer == null || 
!answer.getAddress().equals(trrp.getAddress()) || answer
+                                                                       
.getPort() != trrp.getPort()))
+                                       {
+                                               // listen for an answer.
+                                               if (answer != null)
+                                               {
+                                                       // The answer that we 
got didn't come from the
+                                                       // expected source, 
fire back an error, and continue
+                                                       // listening.
+                                                       log_.println("TFTP 
Server ignoring message from unexpected source.");
+                                                       
transferTftp_.bufferedSend(new TFTPErrorPacket(answer.getAddress(),
+                                                                       
answer.getPort(), TFTPErrorPacket.UNKNOWN_TID,
+                                                                       
"Unexpected Host or Port"));
+                                               }
+                                               try
+                                               {
+                                                       answer = 
transferTftp_.bufferedReceive();
+                                               }
+                                               catch (SocketTimeoutException e)
+                                               {
+                                                       if (timeoutCount >= 
maxTimeoutRetries_)
+                                                       {
+                                                               throw e;
+                                                       }
+                                                       // didn't get an ack 
for this data. need to resend
+                                                       // it.
+                                                       timeoutCount++;
+                                                       
transferTftp_.bufferedSend(lastSentData);
+                                                       continue;
+                                               }
+                                       }
+
+                                       if (answer == null || !(answer 
instanceof TFTPAckPacket))
+                                       {
+                                               if (!shutdown_)
+                                               {
+                                                       logError_
+                                                                       
.println("Unexpected response from tftp client during transfer ("
+                                                                               
        + answer + ").  Transfer aborted.");
+                                               }
+                                               break;
+                                       }
+                                       else
+                                       {
+                                               // once we get here, we know we 
have an answer packet
+                                               // from the correct host.
+                                               TFTPAckPacket ack = 
(TFTPAckPacket) answer;
+                                               if (ack.getBlockNumber() != 
block)
+                                               {
+                                                       /*
+                                                        * The origional tftp 
spec would have called on us to resend the
+                                                        * previous data here, 
however, that causes the SAS Syndrome.
+                                                        * 
http://www.faqs.org/rfcs/rfc1123.html section 4.2.3.1 The modified
+                                                        * spec says that we 
ignore a duplicate ack. If the packet was really
+                                                        * lost, we will time 
out on receive, and resend the previous data at
+                                                        * that point.
+                                                        */
+                                                       sendNext = false;
+                                               }
+                                               else
+                                               {
+                                                       // send the next block
+                                                       block++;
+                                                       if (block > 65535)
+                                                       {
+                                                               // wrap the 
block number
+                                                               block = 0;
+                                                       }
+                                                       sendNext = true;
+                                               }
+                                       }
+                               }
+                       }
+                       finally
+                       {
+                               try
+                               {
+                                       if (is != null)
+                                       {
+                                               is.close();
+                                       }
+                               }
+                               catch (IOException e)
+                               {
+                                       // noop
+                               }
+                       }
+               }
+
+               /*
+                * handle a tftp write request.
+                */
+               private void handleWrite(TFTPWriteRequestPacket twrp) throws 
IOException,
+                               TFTPPacketException
+               {
+                       OutputStream bos = null;
+                       try
+                       {
+                               if (mode_ == GET_ONLY)
+                               {
+                                       transferTftp_.bufferedSend(new 
TFTPErrorPacket(twrp.getAddress(), twrp
+                                                       .getPort(), 
TFTPErrorPacket.ILLEGAL_OPERATION,
+                                                       "Write not allowed by 
server."));
+                                       return;
+                               }
+
+                               int lastBlock = 0;
+                               String fileName = twrp.getFilename();
+
+                               try
+                               {
+                                       File temp = 
buildSafeFile(serverWriteDirectory_, fileName, true);
+                                       if (temp.exists())
+                                       {
+                                               transferTftp_.bufferedSend(new 
TFTPErrorPacket(twrp.getAddress(), twrp
+                                                               .getPort(), 
TFTPErrorPacket.FILE_EXISTS, "File already exists"));
+                                               return;
+                                       }
+                                       bos = new BufferedOutputStream(new 
FileOutputStream(temp));
+                                       
+                                       if (twrp.getMode() == 
TFTP.NETASCII_MODE)
+                                       {
+                                               bos = new 
FromNetASCIIOutputStream(bos);
+                                       }
+                               }
+                               catch (Exception e)
+                               {
+                                       transferTftp_.bufferedSend(new 
TFTPErrorPacket(twrp.getAddress(), twrp
+                                                       .getPort(), 
TFTPErrorPacket.UNDEFINED, e.getMessage()));
+                                       return;
+                               }
+
+                               TFTPAckPacket lastSentAck = new 
TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0);
+                               transferTftp_.bufferedSend(lastSentAck);
+
+                               while (true)
+                               {
+                                       // get the response - ensure it is from 
the right place.
+                                       TFTPPacket dataPacket = null;
+
+                                       int timeoutCount = 0;
+
+                                       while (!shutdown_
+                                                       && (dataPacket == null
+                                                                       || 
!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket
+                                                                       
.getPort() != twrp.getPort()))
+                                       {
+                                               // listen for an answer.
+                                               if (dataPacket != null)
+                                               {
+                                                       // The data that we got 
didn't come from the
+                                                       // expected source, 
fire back an error, and continue
+                                                       // listening.
+                                                       log_.println("TFTP 
Server ignoring message from unexpected source.");
+                                                       
transferTftp_.bufferedSend(new TFTPErrorPacket(dataPacket.getAddress(),
+                                                                       
dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID,
+                                                                       
"Unexpected Host or Port"));
+                                               }
+
+                                               try
+                                               {
+                                                       dataPacket = 
transferTftp_.bufferedReceive();
+                                               }
+                                               catch (SocketTimeoutException e)
+                                               {
+                                                       if (timeoutCount >= 
maxTimeoutRetries_)
+                                                       {
+                                                               throw e;
+                                                       }
+                                                       // It didn't get our 
ack. Resend it.
+                                                       
transferTftp_.bufferedSend(lastSentAck);
+                                                       timeoutCount++;
+                                                       continue;
+                                               }
+                                       }
+
+                                       if (dataPacket != null && dataPacket 
instanceof TFTPWriteRequestPacket)
+                                       {
+                                               // it must have missed our 
initial ack. Send another.
+                                               lastSentAck = new 
TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0);
+                                               
transferTftp_.bufferedSend(lastSentAck);
+                                       }
+                                       else if (dataPacket == null || 
!(dataPacket instanceof TFTPDataPacket))
+                                       {
+                                               if (!shutdown_)
+                                               {
+                                                       logError_
+                                                                       
.println("Unexpected response from tftp client during transfer ("
+                                                                               
        + dataPacket + ").  Transfer aborted.");
+                                               }
+                                               break;
+                                       }
+                                       else
+                                       {
+                                               int block = ((TFTPDataPacket) 
dataPacket).getBlockNumber();
+                                               byte[] data = ((TFTPDataPacket) 
dataPacket).getData();
+                                               int dataLength = 
((TFTPDataPacket) dataPacket).getDataLength();
+                                               int dataOffset = 
((TFTPDataPacket) dataPacket).getDataOffset();
+
+                                               if (block > lastBlock || 
(lastBlock == 65535 && block == 0))
+                                               {
+                                                       // it might resend a 
data block if it missed our ack
+                                                       // - don't rewrite the 
block.
+                                                       bos.write(data, 
dataOffset, dataLength);
+                                                       lastBlock = block;
+                                               }
+
+                                               lastSentAck = new 
TFTPAckPacket(twrp.getAddress(), twrp.getPort(), block);
+                                               
transferTftp_.bufferedSend(lastSentAck);
+                                               if (dataLength < 
TFTPDataPacket.MAX_DATA_LENGTH)
+                                               {
+                                                       // end of stream signal 
- The tranfer is complete.
+                                                       bos.close();
+
+                                                       // But my ack may be 
lost - so listen to see if I
+                                                       // need to resend the 
ack.
+                                                       for (int i = 0; i < 
maxTimeoutRetries_; i++)
+                                                       {
+                                                               try
+                                                               {
+                                                                       
dataPacket = transferTftp_.bufferedReceive();
+                                                               }
+                                                               catch 
(SocketTimeoutException e)
+                                                               {
+                                                                       // this 
is the expected route - the client
+                                                                       // 
shouldn't be sending any more packets.
+                                                                       break;
+                                                               }
+
+                                                               if (dataPacket 
!= null
+                                                                               
&& (!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket
+                                                                               
                .getPort() != twrp.getPort()))
+                                                               {
+                                                                       // make 
sure it was from the right client...
+                                                                       
transferTftp_
+                                                                               
        .bufferedSend(new TFTPErrorPacket(dataPacket
+                                                                               
                        .getAddress(), dataPacket.getPort(),
+                                                                               
                        TFTPErrorPacket.UNKNOWN_TID,
+                                                                               
                        "Unexpected Host or Port"));
+                                                               }
+                                                               else
+                                                               {
+                                                                       // This 
means they sent us the last
+                                                                       // 
datapacket again, must have missed our
+                                                                       // ack. 
resend it.
+                                                                       
transferTftp_.bufferedSend(lastSentAck);
+                                                               }
+                                                       }
+
+                                                       // all done.
+                                                       break;
+                                               }
+                                       }
+                               }
+                       }
+                       finally
+                       {
+                               if (bos != null)
+                               {
+                                       bos.close();
+                               }
+                       }
+               }
+
+               /*
+                * Utility method to make sure that paths provided by tftp 
clients do not get outside of the
+                * serverRoot directory.
+                */
+               private File buildSafeFile(File serverDirectory, String 
fileName, boolean createSubDirs)
+                               throws IOException
+               {
+                       File temp = new File(serverDirectory, fileName);
+                       temp = temp.getCanonicalFile();
+
+                       if (!isSubdirectoryOf(serverDirectory, temp))
+                       {
+                               throw new IOException("Cannot access files 
outside of tftp server root.");
+                       }
+
+                       // ensure directory exists (if requested)
+                       if (createSubDirs)
+                       {
+                               createDirectory(temp.getParentFile());
+                       }
+
+                       return temp;
+               }
+
+               /*
+                * recursively create subdirectories
+                */
+               private void createDirectory(File file) throws IOException
+               {
+                       File parent = file.getParentFile();
+                       if (parent == null)
+                       {
+                               throw new IOException("Unexpected error 
creating requested directory");
+                       }
+                       if (!parent.exists())
+                       {
+                               // recurse...
+                               createDirectory(parent);
+                       }
+
+                       if (parent.isDirectory())
+                       {
+                               if (file.isDirectory())
+                               {
+                                       return;
+                               }
+                               boolean result = file.mkdir();
+                               if (!result)
+                               {
+                                       throw new IOException("Couldn't create 
requested directory");
+                               }
+                       }
+                       else
+                       {
+                               throw new IOException(
+                                               "Invalid directory path - file 
in the way of requested folder");
+                       }
+               }
+
+               /*
+                * recursively check to see if one directory is a parent of 
another.
+                */
+               private boolean isSubdirectoryOf(File parent, File child)
+               {
+                       File childsParent = child.getParentFile();
+                       if (childsParent == null)
+                       {
+                               return false;
+                       }
+                       if (childsParent.equals(parent))
+                       {
+                               return true;
+                       }
+                       else
+                       {
+                               return isSubdirectoryOf(parent, childsParent);
+                       }
+               }
+       }
+
+       /**
+        * Set the stream object to log debug / informational messages. By 
default, this is System.out
+        * 
+        * @param log
+        */
+       public void setLog(PrintStream log)
+       {
+               this.log_ = log;
+       }
+
+       /**
+        * Set the stream object to log error messsages. By default, this is 
System.err
+        * 
+        * @param logError
+        */
+       public void setLogError(PrintStream logError)
+       {
+               this.logError_ = logError;
+       }
+}

Added: 
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTests.java
URL: 
http://svn.apache.org/viewvc/commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTests.java?rev=630684&view=auto
==============================================================================
--- 
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTests.java
 (added)
+++ 
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPServerPathTests.java
 Sun Feb 24 13:40:34 2008
@@ -0,0 +1,148 @@
+package org.apache.commons.net.tftp;
+
+import java.io.*;
+import junit.framework.*;
+
+/**
+ * Some basic tests to ensure that the TFTP Server is honoring its read/write 
mode, and preventing
+ * files from being read or written from outside of the assigned roots.
+ * 
+ * @author <A HREF="mailto:[EMAIL PROTECTED]">Dan Armbrust</A>
+ * 
+ */
+public class TFTPServerPathTests extends TestCase
+{
+       String filePrefix = "tftp-";
+       File serverDirectory = new File(System.getProperty("java.io.tmpdir"));
+
+       public void testReadOnly() throws IOException
+       {
+               // Start a read-only server
+               TFTPServer tftpS = new TFTPServer(serverDirectory, 
serverDirectory, 6900,
+                               TFTPServer.GET_ONLY, null, null);
+
+               // Create our TFTP instance to handle the file transfer.
+               TFTPClient tftp = new TFTPClient();
+               tftp.open();
+               tftp.setSoTimeout(2000);
+
+               // make a file to work with.
+               File file = new File(serverDirectory, filePrefix + 
"source.txt");
+               file.createNewFile();
+
+               // Read the file from the tftp server.
+               File out = new File(serverDirectory, filePrefix + "out");
+
+               // cleanup old failed runs
+               out.delete();
+               assertTrue("Couldn't clear output location", !out.exists());
+
+               FileOutputStream output = new FileOutputStream(out);
+
+               tftp.receiveFile(file.getName(), TFTP.BINARY_MODE, output, 
"localhost", 6900);
+               output.close();
+
+               assertTrue("file not created", out.exists());
+
+               out.delete();
+
+               FileInputStream fis = new FileInputStream(file);
+               try
+               {
+                       tftp.sendFile(out.getName(), TFTP.BINARY_MODE, fis, 
"localhost", 6900);
+                       fail("Server allowed write");
+               }
+               catch (IOException e)
+               {
+                       // expected path
+               }
+               fis.close();
+               file.delete();
+               tftpS.shutdown();
+       }
+
+       public void testWriteOnly() throws IOException
+       {
+               // Start a write-only server
+               TFTPServer tftpS = new TFTPServer(serverDirectory, 
serverDirectory, 6900,
+                               TFTPServer.PUT_ONLY, null, null);
+
+               // Create our TFTP instance to handle the file transfer.
+               TFTPClient tftp = new TFTPClient();
+               tftp.open();
+               tftp.setSoTimeout(2000);
+
+               // make a file to work with.
+               File file = new File(serverDirectory, filePrefix + 
"source.txt");
+               file.createNewFile();
+
+               File out = new File(serverDirectory, filePrefix + "out");
+
+               // cleanup old failed runs
+               out.delete();
+               assertTrue("Couldn't clear output location", !out.exists());
+
+               FileOutputStream output = new FileOutputStream(out);
+
+               try
+               {
+                       tftp.receiveFile(file.getName(), TFTP.BINARY_MODE, 
output, "localhost", 6900);
+                       fail("Server allowed read");
+               }
+               catch (IOException e)
+               {
+                       // expected path
+               }
+               output.close();
+               out.delete();
+
+               FileInputStream fis = new FileInputStream(file);
+               tftp.sendFile(out.getName(), TFTP.BINARY_MODE, fis, 
"localhost", 6900);
+
+               fis.close();
+
+               assertTrue("file not created", out.exists());
+
+               // cleanup
+               file.delete();
+               out.delete();
+               tftpS.shutdown();
+       }
+
+       public void testWriteOutsideHome() throws IOException
+       {
+               // Start a server
+               TFTPServer tftpS = new TFTPServer(serverDirectory, 
serverDirectory, 6900,
+                               TFTPServer.GET_AND_PUT, null, null);
+
+               // Create our TFTP instance to handle the file transfer.
+               TFTPClient tftp = new TFTPClient();
+               tftp.open();
+
+               File file = new File(serverDirectory, filePrefix + 
"source.txt");
+               file.createNewFile();
+
+               assertFalse("test construction error", new 
File(serverDirectory, "../foo").exists());
+
+               FileInputStream fis = new FileInputStream(file);
+               try
+               {
+                       tftp.sendFile("../foo", TFTP.BINARY_MODE, fis, 
"localhost", 6900);
+                       fail("Server allowed write!");
+               }
+               catch (IOException e)
+               {
+                       // expected path
+               }
+
+               fis.close();
+
+               assertFalse("file created when it should not have been",
+                               new File(serverDirectory, "../foo").exists());
+
+               // cleanup
+               file.delete();
+
+               tftpS.shutdown();
+       }
+}

Added: 
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPTests.java
URL: 
http://svn.apache.org/viewvc/commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPTests.java?rev=630684&view=auto
==============================================================================
--- 
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPTests.java
 (added)
+++ 
commons/proper/net/branches/NET_2_0/src/test/java/org/apache/commons/net/tftp/TFTPTests.java
 Sun Feb 24 13:40:34 2008
@@ -0,0 +1,219 @@
+package org.apache.commons.net.tftp;
+
+import java.io.*;
+import junit.framework.TestCase;
+import org.apache.commons.net.tftp.TFTP;
+import org.apache.commons.net.tftp.TFTPClient;
+
+/**
+ * Test the TFTP Server and TFTP Client by creating some files in the system 
temp folder and then
+ * uploading and downloading them.
+ * 
+ * @author <A HREF="mailto:[EMAIL PROTECTED]">Dan Armbrust</A>
+ * 
+ */
+public class TFTPTests extends TestCase
+{
+       static TFTPServer tftpS;
+       static File serverDirectory = new 
File(System.getProperty("java.io.tmpdir"));
+       static String filePrefix = "tftp-";
+       static File[] files = new File[8];
+
+       static int testsLeftToRun = 6;
+
+       // only want to do this once...
+       static
+       {
+               try
+               {
+                       files[0] = createFile(new File(serverDirectory, 
filePrefix + "empty.txt"), 0);
+                       files[1] = createFile(new File(serverDirectory, 
filePrefix + "small.txt"), 1);
+                       files[2] = createFile(new File(serverDirectory, 
filePrefix + "511.txt"), 511);
+                       files[3] = createFile(new File(serverDirectory, 
filePrefix + "512.txt"), 512);
+                       files[4] = createFile(new File(serverDirectory, 
filePrefix + "513.txt"), 513);
+                       files[5] = createFile(new File(serverDirectory, 
filePrefix + "med.txt"), 1000 * 1024);
+                       files[6] = createFile(new File(serverDirectory, 
filePrefix + "big.txt"), 5000 * 1024);
+                       files[7] = createFile(new File(serverDirectory, 
filePrefix + "huge.txt"), 37000 * 1024);
+
+                       // Start the server
+                       tftpS = new TFTPServer(serverDirectory, 
serverDirectory, 6900, TFTPServer.GET_AND_PUT,
+                                       null, null);
+                       tftpS.setSocketTimeout(2000);
+               }
+               catch (IOException e)
+               {
+                       e.printStackTrace();
+               }
+
+       }
+
+       protected void tearDown() throws Exception
+       {
+               testsLeftToRun--;
+               if (testsLeftToRun <= 0)
+               {
+                       if (tftpS != null)
+                       {
+                               tftpS.shutdown();
+                       }
+                       for (int i = 0; i < files.length; i++)
+                       {
+                               files[i].delete();
+                       }
+               }
+               super.tearDown();
+       }
+
+       /*
+        * Create a file, size specified in bytes
+        */
+       private static File createFile(File file, int size) throws IOException
+       {
+               OutputStream os = new BufferedOutputStream(new 
FileOutputStream(file));
+               byte[] temp = "0".getBytes();
+               for (int i = 0; i < size; i++)
+               {
+                       os.write(temp);
+               }
+               os.close();
+               return file;
+       }
+
+       public void testTFTPBinaryDownloads() throws Exception
+       {
+               // test with the smaller files
+               for (int i = 0; i < 6; i++)
+               {
+                       testDownload(TFTP.BINARY_MODE, files[i]);
+               }
+       }
+
+       public void testASCIIDownloads() throws Exception
+       {
+               // test with the smaller files
+               for (int i = 0; i < 6; i++)
+               {
+                       testDownload(TFTP.ASCII_MODE, files[i]);
+               }
+       }
+
+       public void testTFTPBinaryUploads() throws Exception
+       {
+               // test with the smaller files
+               for (int i = 0; i < 6; i++)
+               {
+                       testUpload(TFTP.BINARY_MODE, files[i]);
+               }
+       }
+
+       public void testASCIIUploads() throws Exception
+       {
+               // test with the smaller files
+               for (int i = 0; i < 6; i++)
+               {
+                       testUpload(TFTP.ASCII_MODE, files[i]);
+               }
+       }
+
+       public void testHugeUploads() throws Exception
+       {
+               for (int i = 5; i < files.length; i++)
+               {
+                       testUpload(TFTP.BINARY_MODE, files[i]);
+               }
+       }
+
+       public void testHugeDownloads() throws Exception
+       {
+               // test with the smaller files
+               for (int i = 5; i < files.length; i++)
+               {
+                       testDownload(TFTP.BINARY_MODE, files[i]);
+               }
+       }
+
+       private void testDownload(int mode, File file) throws IOException
+       {
+               // Create our TFTP instance to handle the file transfer.
+               TFTPClient tftp = new TFTPClient();
+               tftp.open();
+               tftp.setSoTimeout(2000);
+
+               File out = new File(serverDirectory, filePrefix + "download");
+
+               // cleanup old failed runs
+               out.delete();
+               assertTrue("Couldn't clear output location", !out.exists());
+
+               FileOutputStream output = new FileOutputStream(out);
+
+               tftp.receiveFile(file.getName(), mode, output, "localhost", 
6900);
+               output.close();
+
+               assertTrue("file not created", out.exists());
+               assertTrue("files not identical on file " + file, 
filesIdentical(out, file));
+
+               // delete the downloaded file
+               out.delete();
+       }
+
+       private void testUpload(int mode, File file) throws Exception
+       {
+               // Create our TFTP instance to handle the file transfer.
+               TFTPClient tftp = new TFTPClient();
+               tftp.open();
+               tftp.setSoTimeout(2000);
+
+               File in = new File(serverDirectory, filePrefix + "upload");
+               // cleanup old failed runs
+               in.delete();
+               assertTrue("Couldn't clear output location", !in.exists());
+
+               FileInputStream fis = new FileInputStream(file);
+               tftp.sendFile(in.getName(), mode, fis, "localhost", 6900);
+               fis.close();
+
+               // need to give the server a bit of time to receive our last 
packet, and
+               // close out its file buffers, etc.
+               Thread.sleep(100);
+               assertTrue("file not created", in.exists());
+               assertTrue("files not identical on file " + file, 
filesIdentical(file, in));
+
+               in.delete();
+       }
+
+       private boolean filesIdentical(File a, File b) throws IOException
+       {
+               if (!a.exists() || !b.exists())
+               {
+                       return false;
+               }
+
+               if (a.length() != b.length())
+               {
+                       return false;
+               }
+
+               InputStream fisA = new BufferedInputStream(new 
FileInputStream(a));
+               InputStream fisB = new BufferedInputStream(new 
FileInputStream(b));
+
+               int aBit = fisA.read();
+               int bBit = fisB.read();
+
+               while (aBit != -1)
+               {
+                       if (aBit != bBit)
+                       {
+                               fisA.close();
+                               fisB.close();
+                               return false;
+                       }
+                       aBit = fisA.read();
+                       bBit = fisB.read();
+               }
+
+               fisA.close();
+               fisB.close();
+               return true;
+       }
+}


Reply via email to