You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by rw...@apache.org on 2008/02/24 22:41:05 UTC
svn commit: r630685 - in /commons/proper/net/trunk/src:
java/org/apache/commons/net/tftp/TFTPServer.java
test/org/apache/commons/net/tftp/
test/org/apache/commons/net/tftp/TFTPServerPathTests.java
test/org/apache/commons/net/tftp/TFTPTests.java
Author: rwinston
Date: Sun Feb 24 13:41:05 2008
New Revision: 630685
URL: http://svn.apache.org/viewvc?rev=630685&view=rev
Log:
Add TFTP server (NET-187)
Added:
commons/proper/net/trunk/src/java/org/apache/commons/net/tftp/TFTPServer.java
commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/
commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/TFTPServerPathTests.java
commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/TFTPTests.java
Added: commons/proper/net/trunk/src/java/org/apache/commons/net/tftp/TFTPServer.java
URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/java/org/apache/commons/net/tftp/TFTPServer.java?rev=630685&view=auto
==============================================================================
--- commons/proper/net/trunk/src/java/org/apache/commons/net/tftp/TFTPServer.java (added)
+++ commons/proper/net/trunk/src/java/org/apache/commons/net/tftp/TFTPServer.java Sun Feb 24 13:41:05 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:daniel.armbrust.list@gmail.com">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/trunk/src/test/org/apache/commons/net/tftp/TFTPServerPathTests.java
URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/TFTPServerPathTests.java?rev=630685&view=auto
==============================================================================
--- commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/TFTPServerPathTests.java (added)
+++ commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/TFTPServerPathTests.java Sun Feb 24 13:41:05 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:daniel.armbrust.list@gmail.com">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/trunk/src/test/org/apache/commons/net/tftp/TFTPTests.java
URL: http://svn.apache.org/viewvc/commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/TFTPTests.java?rev=630685&view=auto
==============================================================================
--- commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/TFTPTests.java (added)
+++ commons/proper/net/trunk/src/test/org/apache/commons/net/tftp/TFTPTests.java Sun Feb 24 13:41:05 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:daniel.armbrust.list@gmail.com">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;
+ }
+}