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:[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/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:[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/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:[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;
+ }
+}