From f47082223bdd83bdeffe651ace1b3018d6bb9efc Mon Sep 17 00:00:00 2001 From: Roman Koldaev Date: Mon, 2 May 2022 10:08:44 +0400 Subject: [PATCH] ping_remark --- log/jul.properties | 13 + log/log4j.properties | 15 + log/log4j.xml | 25 + pom.xml | 6 + src/main/java/my/harp07/GenericPJ.java | 1 + src/main/java/my/harp07/PjFrame.java | 7 +- src/main/java/my/harp07/PjPing.java | 6 +- src/main/java/my/harp07/PjPingScanner.java | 5 +- src/main/java/my/harp07/TFTPServer.java | 790 +++++++++++++++++++++ src/main/resources/img/tftp-well-16.png | Bin 0 -> 770 bytes 10 files changed, 862 insertions(+), 6 deletions(-) create mode 100644 log/jul.properties create mode 100644 log/log4j.properties create mode 100644 log/log4j.xml create mode 100644 src/main/java/my/harp07/TFTPServer.java create mode 100644 src/main/resources/img/tftp-well-16.png diff --git a/log/jul.properties b/log/jul.properties new file mode 100644 index 0000000..48b2ffc --- /dev/null +++ b/log/jul.properties @@ -0,0 +1,13 @@ +handlers=java.util.logging.FileHandler, java.util.logging.ConsoleHandler +.level=INFO +############ +java.util.logging.FileHandler.level=INFO +java.util.logging.FileHandler.pattern = log/app.log +java.util.logging.FileHandler.limit = 999000 +java.util.logging.FileHandler.count = 3 +java.util.logging.FileHandler.append = true +java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter +############ +java.util.logging.ConsoleHandler.level =INFO +java.util.logging.ConsoleHandler.formatter =java.util.logging.SimpleFormatter +############# diff --git a/log/log4j.properties b/log/log4j.properties new file mode 100644 index 0000000..7367bcf --- /dev/null +++ b/log/log4j.properties @@ -0,0 +1,15 @@ +log4j.debug=false +log4j.rootLogger=INFO, FILE, CONSOLE +############## +log4j.appender.FILE=org.apache.log4j.RollingFileAppender +log4j.appender.FILE.File=log/app.log +log4j.appender.FILE.maxFileSize=999KB +log4j.appender.FILE.maxBackupIndex=1 +log4j.appender.FILE.layout=org.apache.log4j.PatternLayout +log4j.appender.FILE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n +############# +############### +log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender +log4j.appender.CONSOLE.Encoding=utf-8 +log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout +log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n diff --git a/log/log4j.xml b/log/log4j.xml new file mode 100644 index 0000000..253122d --- /dev/null +++ b/log/log4j.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 373e83c..0874107 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,12 @@ + + log4j + log4j + 1.2.17 + + org.snmp4j snmp4j diff --git a/src/main/java/my/harp07/GenericPJ.java b/src/main/java/my/harp07/GenericPJ.java index 8bb948d..5eebebf 100644 --- a/src/main/java/my/harp07/GenericPJ.java +++ b/src/main/java/my/harp07/GenericPJ.java @@ -13,6 +13,7 @@ public class GenericPJ { public static SubnetUtils su; public static InetAddressValidator ipv = InetAddressValidator.getInstance(); public static DomainValidator dnsv = DomainValidator.getInstance(); + public static String ping_remark="Works well and tested with Root privileges on Linux ! Run as root/admin user !\n"; // ВИСНЕТ КОГДА НЕ РАБОТАЕТ DNS И БЛОКИ CATCH ПУСТЫЕ !!! public static Boolean pingIp(String ipad, int timeout) { diff --git a/src/main/java/my/harp07/PjFrame.java b/src/main/java/my/harp07/PjFrame.java index 450389e..291c0a4 100644 --- a/src/main/java/my/harp07/PjFrame.java +++ b/src/main/java/my/harp07/PjFrame.java @@ -29,6 +29,7 @@ import javax.swing.event.HyperlinkListener; import javax.swing.text.html.HTMLEditorKit; import static my.harp07.GenericPJ.ipv; +import static my.harp07.GenericPJ.ping_remark; import static my.harp07.PjCalc.CIDRS_MASKS; import static my.harp07.PjPing.COUNTS; import static my.harp07.PjPing.TIMEOUTS; @@ -51,7 +52,7 @@ public class PjFrame extends javax.swing.JFrame { public static String currentLAF = "de.muntjak.tinylookandfeel.TinyLookAndFeel"; public static String currentTheme = "lib/themes/Default.theme"; public static List tinyTemes = new ArrayList<>(); - public static String zagolovok = "Pure Java Network Tools, v1.0.62, build 02-05-2022"; + public static String zagolovok = "Pure Java Network Tools, v1.0.63, build 02-05-2022"; //public static String currentLAF = "org.pushingpixels.substance.api.skin.SubstanceSaharaLookAndFeel"; //public static String currentLAF = "javax.swing.plaf.metal.MetalLookAndFeel"; //public ImageIcon snmpIcon33 = new ImageIcon(getClass().getResource("/mib-tree-3.jpg")); @@ -123,7 +124,9 @@ public void paint(Graphics g) { }; */ PjSnmpOidHelp.runSnmpHelp(taSnmpOidHelp); taSnmpOidHelp.setEditable(false); - taPingFloodResult.setText("Works well and tested with Root privileges on Linux ! Run as root/admin user !\n"); + taPingResult.setText(ping_remark); + taPingScannerResult.setText(ping_remark); + taPingFloodResult.setText(ping_remark); } public static void MyInstLF(String lf) { diff --git a/src/main/java/my/harp07/PjPing.java b/src/main/java/my/harp07/PjPing.java index bf93208..d65d1df 100644 --- a/src/main/java/my/harp07/PjPing.java +++ b/src/main/java/my/harp07/PjPing.java @@ -9,6 +9,7 @@ import static my.harp07.GenericPJ.dnsv; import static my.harp07.GenericPJ.ipv; import static my.harp07.GenericPJ.pingIp; +import static my.harp07.GenericPJ.ping_remark; import static my.harp07.PjFrame.frame; import org.apache.commons.validator.routines.DomainValidator; import org.apache.commons.validator.routines.InetAddressValidator; @@ -50,7 +51,7 @@ public class PjPing { public static String getResult(String ip_dns, JTextArea ta) { pingtimeout=Integer.parseInt(PjFrame.comboPingTimeouts.getSelectedItem().toString()); int loss=0; - String result = "TimeOut = " + pingtimeout + " ms, Count = "+PjFrame.comboPingCounts.getSelectedItem().toString()+":\n\n"; + String result = ping_remark + "TimeOut = " + pingtimeout + " ms, Count = "+PjFrame.comboPingCounts.getSelectedItem().toString()+":\n\n"; if (ip_dns != null) { for (int j = 1; j <= 1+Integer.parseInt(PjFrame.comboPingCounts.getSelectedItem().toString()); j++) { if (j==1) { @@ -79,7 +80,8 @@ public static String getResult(String ip_dns, JTextArea ta) { } public static void runGetResult(JTextField ipq, JTextArea ta) { - ta.setText(""); + // ta.setText(""); + ta.setText("Works well and tested with Root privileges on Linux ! Run as root/admin user !\n"); String input = ipq.getText().trim(); //System.out.println(input); if (ipv.isValid(input)) { diff --git a/src/main/java/my/harp07/PjPingScanner.java b/src/main/java/my/harp07/PjPingScanner.java index 9fbb62c..c57305d 100644 --- a/src/main/java/my/harp07/PjPingScanner.java +++ b/src/main/java/my/harp07/PjPingScanner.java @@ -7,6 +7,7 @@ import javax.swing.JTextField; import static my.harp07.GenericPJ.ipv; import static my.harp07.GenericPJ.pingIp; +import static my.harp07.GenericPJ.ping_remark; import static my.harp07.GenericPJ.su; import static my.harp07.PjFrame.frame; import org.apache.commons.lang3.StringUtils; @@ -61,7 +62,7 @@ public static String getResult(String ipadr, JTextArea tap) { su = new SubnetUtils(ipadr); //su=new SubnetUtils("10.73.2.111/23"); //su=new SubnetUtils("10.73.2.111", "255.255.254.0"); - result = "\n Network IP-data:\n"; + result = ping_remark + "\n Network IP-data:\n"; result = result + "\n Low Address = " + su.getInfo().getLowAddress(); result = result + "\n High Address = " + su.getInfo().getHighAddress(); result = result + "\n Broadcast Address = " + su.getInfo().getBroadcastAddress(); @@ -88,7 +89,7 @@ public static String getResult(String ipadr, JTextArea tap) { resultDOWN = resultDOWN + j + ") " + x + " = DOWN\n"; j++; } - tap.setText("\nCheck by small 2 ping for every IP.\n\n Please Wait ! ........" + j); + tap.setText(ping_remark + "\nCheck by small 2 ping for every IP.\n\n Please Wait ! ........" + j); }); changeInterface(true); return result; diff --git a/src/main/java/my/harp07/TFTPServer.java b/src/main/java/my/harp07/TFTPServer.java new file mode 100644 index 0000000..be65f0d --- /dev/null +++ b/src/main/java/my/harp07/TFTPServer.java @@ -0,0 +1,790 @@ +package my.harp07; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketTimeoutException; +import java.util.HashSet; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.Scanner; +//import java.util.logging.Level; +//import java.util.logging.LogManager; +//import java.util.logging.Logger; + +import org.apache.commons.net.io.FromNetASCIIOutputStream; +import org.apache.commons.net.io.ToNetASCIIInputStream; +import org.apache.commons.net.tftp.TFTP; +import org.apache.commons.net.tftp.TFTPAckPacket; +import org.apache.commons.net.tftp.TFTPDataPacket; +import org.apache.commons.net.tftp.TFTPErrorPacket; +import org.apache.commons.net.tftp.TFTPPacket; +import org.apache.commons.net.tftp.TFTPPacketException; +import org.apache.commons.net.tftp.TFTPReadRequestPacket; +import org.apache.commons.net.tftp.TFTPWriteRequestPacket; +import org.apache.log4j.Level; +import org.apache.log4j.Logger; +import org.apache.log4j.PropertyConfigurator; + + /* 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, events are not logged to stdout/stderr. This can be changed with +the setLog and setLogError methods. + Example usage is below: +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); + */ + +public class TFTPServer implements Runnable { + + //public static java.util.logging.Logger jul; + public static org.apache.log4j.Logger jul; + + static { + // for java.util.logging: + /*try (FileInputStream ins = new FileInputStream("log/jul.properties")) { + LogManager.getLogManager().readConfiguration(ins); + jul = java.util.logging.Logger.getLogger(TFTPServer.class.getName()); + } catch (Exception ignore) { ignore.printStackTrace(); }*/ + + // for log4j: + //File propertiesFile = new File("cfg/log4j.properties"); + //PropertyConfigurator.configure(propertiesFile.toString()); + PropertyConfigurator.configure("log/log4j.properties"); + //DOMConfigurator.configure("cfg/log4j.xml"); + jul = org.apache.log4j.Logger.getLogger(TFTPServer.class.getName()); + + } + + private static final int DEFAULT_TFTP_PORT = 69; + + public enum ServerMode { + GET_ONLY, PUT_ONLY, GET_AND_PUT; + } + + private final HashSet transfers_ = new HashSet<>(); + private volatile boolean shutdownServer = false; + private TFTP serverTftp_; + private File serverReadDirectory_; + private File serverWriteDirectory_; + private final int port_; + private final InetAddress laddr_; + private Exception serverException = null; + private final ServerMode mode_; + private int maxTimeoutRetries_ = 3; + private int socketTimeout_; + private Thread serverThread; + + /* 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 serverReadDirectory directory for GET requests + * @param serverWriteDirectory directory for PUT requests + * @param mode A value as specified above. + * @throws IOException if the server directory is invalid or does not exist. + */ + + public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final ServerMode mode) + throws IOException { + //this(serverReadDirectory, serverWriteDirectory, DEFAULT_TFTP_PORT, mode, null, null); + this(serverReadDirectory, serverWriteDirectory, DEFAULT_TFTP_PORT, mode, jul, jul); + } + + + /* 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 serverReadDirectory directory for GET requests + * @param serverWriteDirectory directory for PUT requests + * @param port the port to use + * @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(final File serverReadDirectory, final File serverWriteDirectory, final int port, final ServerMode mode, + final Logger log, final Logger errorLog) throws IOException { + port_ = port; + mode_ = mode; + //log_ = log == null ? nullStream : log; + //logError_ = errorLog == null ? nullStream : errorLog; + laddr_ = null; + launch(serverReadDirectory, serverWriteDirectory); + } + + /* 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 serverReadDirectory directory for GET requests + * @param serverWriteDirectory directory for PUT requests + * @param port The local port to bind to. + * @param localaddr The local address to bind to. + * @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(final File serverReadDirectory, final File serverWriteDirectory, final int port, + final InetAddress localaddr, final ServerMode mode, final Logger log, final Logger errorLog) + throws IOException { + port_ = port; + mode_ = mode; + laddr_ = localaddr; + //log_ = log == null ? nullStream : log; + //logError_ = errorLog == null ? nullStream : errorLog; + launch(serverReadDirectory, serverWriteDirectory); + } + + /** + * 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 serverReadDirectory directory for GET requests + * @param serverWriteDirectory directory for PUT requests + * @param port the port to use + * @param localiface The local network interface to bind to. The interface's + * first address wil be used. + * @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(final File serverReadDirectory, final File serverWriteDirectory, final int port, + final NetworkInterface localiface, final ServerMode mode, final Logger log, final Logger errorLog) + throws IOException { + mode_ = mode; + port_ = port; + InetAddress iaddr = null; + if (localiface != null) { + final Enumeration ifaddrs = localiface.getInetAddresses(); + if (ifaddrs != null) { + if (ifaddrs.hasMoreElements()) { + iaddr = ifaddrs.nextElement(); + } + } + } + //log_ = log == null ? nullStream : log; + //logError_ = errorLog == null ? nullStream : errorLog; + laddr_ = iaddr; + launch(serverReadDirectory, serverWriteDirectory); + } + + /* Set the max number of retries in response to a timeout. Default 3. Min 0. + @param retries number of retries, must be > 0 */ + public void setMaxTimeoutRetries(final int retries) { + if (retries < 0) { + throw new RuntimeException("Invalid Value"); + } + maxTimeoutRetries_ = retries; + } + + /* Get the current value for maxTimeoutRetries + @return the max allowed number of retries */ + public int getMaxTimeoutRetries() { + return maxTimeoutRetries_; + } + + /* Set the socket timeout in milliseconds used in transfers. Defaults to the + * value here: + * https://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. + * @param timeout the timeout; must be larger than 10 + */ + public void setSocketTimeout(final int timeout) { + if (timeout < 10) { + throw new RuntimeException("Invalid Value"); + } + socketTimeout_ = timeout; + } + + /* The current socket timeout used during transfers in milliseconds. + * @return the timeout value + */ + public int getSocketTimeout() { + return socketTimeout_; + } + + // start the server, throw an error if it can't start. + private void launch(final File serverReadDirectory, final File serverWriteDirectory) throws IOException { + jul.log(Level.INFO, "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); + + if (laddr_ != null) { + serverTftp_.open(port_, laddr_); + } else { + serverTftp_.open(port_); + } + + serverThread = new Thread(this); + serverThread.setDaemon(true); + serverThread.start(); + } + + @Override + 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 (shutdownServer && serverException != null) { + throw serverException; + } + return !shutdownServer; + } + + @Override + public void run() { + try { + while (!shutdownServer) { + TFTPPacket tftpPacket; + + tftpPacket = serverTftp_.receive(); + + final TFTPTransfer tt = new TFTPTransfer(tftpPacket); + synchronized (transfers_) { + transfers_.add(tt); + } + + final Thread thread = new Thread(tt); + thread.setDaemon(true); + thread.start(); + } + } catch (final Exception e) { + if (!shutdownServer) { + serverException = e; + jul.log(Level.ERROR, "Unexpected Error in TFTP Server - Server shut down! + " + e); + } + } finally { + shutdownServer = 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() { + shutdownServer = true; + + synchronized (transfers_) { + final Iterator it = transfers_.iterator(); + while (it.hasNext()) { + it.next().shutdown(); + } + } + + try { + serverTftp_.close(); + } catch (final RuntimeException e) { + // noop + } + + try { + serverThread.join(); + } catch (final InterruptedException e) { + // we've done the best we could, return + } + } + + // An instance of an ongoing transfer. + private class TFTPTransfer implements Runnable { + + private final TFTPPacket tftpPacket_; + + private boolean shutdownTransfer = false; + + TFTP transferTftp_ = null; + + public TFTPTransfer(final TFTPPacket tftpPacket) { + tftpPacket_ = tftpPacket; + } + + public void shutdown() { + shutdownTransfer = true; + try { + transferTftp_.close(); + } catch (final RuntimeException e) { + // noop + } + } + + @Override + public void run() { + try { + transferTftp_ = newTFTP(); + + transferTftp_.beginBufferedOps(); + transferTftp_.setDefaultTimeout(socketTimeout_); + + transferTftp_.open(); + + if (tftpPacket_ instanceof TFTPReadRequestPacket) { + handleRead((TFTPReadRequestPacket) tftpPacket_); + } else if (tftpPacket_ instanceof TFTPWriteRequestPacket) { + handleWrite((TFTPWriteRequestPacket) tftpPacket_); + } else { + jul.log(Level.WARN, "Unsupported TFTP request (" + tftpPacket_ + ") - ignored."); + } + } catch (final Exception e) { + if (!shutdownTransfer) { + jul.log(Level.ERROR, "Unexpected Error in during TFTP file transfer. Transfer aborted. " + e); + } + } finally { + try { + if (transferTftp_ != null && transferTftp_.isOpen()) { + transferTftp_.endBufferedOps(); + transferTftp_.close(); + } + } catch (final Exception e) { + // noop + } + synchronized (transfers_) { + transfers_.remove(this); + } + } + } + + // Handle a tftp read request. + private void handleRead(final TFTPReadRequestPacket trrp) throws IOException, TFTPPacketException { + InputStream is = null; + try { + if (mode_ == ServerMode.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 (final FileNotFoundException e) { + transferTftp_.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp + .getPort(), TFTPErrorPacket.FILE_NOT_FOUND, e.getMessage())); + return; + } catch (final 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); + } + + final 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 && !shutdownTransfer) { + if (sendNext) { + readLength = is.read(temp); + if (readLength == -1) { + readLength = 0; + } + + lastSentData = new TFTPDataPacket(trrp.getAddress(), trrp.getPort(), block, + temp, 0, readLength); + sendData(transferTftp_, lastSentData); // send the data + } + + answer = null; + + int timeoutCount = 0; + + while (!shutdownTransfer + && (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. + jul.log(Level.INFO, "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 (final 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 (!shutdownTransfer) { + jul.log(Level.ERROR, "Unexpected response from tftp client during transfer (" + + answer + "). Transfer aborted."); + } + break; + } + // once we get here, we know we have an answer packet + // from the correct host. + final 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 (final IOException e) { + // noop + } + } + } + + // * handle a tftp write request. + private void handleWrite(final TFTPWriteRequestPacket twrp) throws IOException, + TFTPPacketException { + OutputStream bos = null; + try { + if (mode_ == ServerMode.GET_ONLY) { + transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp + .getPort(), TFTPErrorPacket.ILLEGAL_OPERATION, + "Write not allowed by server.")); + return; + } + + int lastBlock = 0; + final String fileName = twrp.getFilename(); + + try { + final 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 (final Exception e) { + transferTftp_.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp + .getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage())); + return; + } + + TFTPAckPacket lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0); + sendData(transferTftp_, lastSentAck); // send the data + + while (true) { + // get the response - ensure it is from the right place. + TFTPPacket dataPacket = null; + + int timeoutCount = 0; + + while (!shutdownTransfer + && (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. + jul.log(Level.INFO, "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 (final 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 (!shutdownTransfer) { + jul.log(Level.ERROR, "Unexpected response from tftp client during transfer (" + + dataPacket + "). Transfer aborted."); + } + break; + } else { + final int block = ((TFTPDataPacket) dataPacket).getBlockNumber(); + final byte[] data = ((TFTPDataPacket) dataPacket).getData(); + final int dataLength = ((TFTPDataPacket) dataPacket).getDataLength(); + final 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); + sendData(transferTftp_, lastSentAck); // send the data + 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 (final 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(final File serverDirectory, final String fileName, final 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(final File file) throws IOException { + final 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; + } + final 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(final File parent, final File child) { + final File childsParent = child.getParentFile(); + if (childsParent == null) { + return false; + } + if (childsParent.equals(parent)) { + return true; + } + return isSubdirectoryOf(parent, childsParent); + } + } + + // * Allow test code to customise the TFTP instance + TFTP newTFTP() { + return new TFTP(); + } + + /* + * Also allow customisation of sending data/ack so can generate errors if needed + */ + void sendData(final TFTP tftp, final TFTPPacket data) throws IOException { + tftp.bufferedSend(data); + } + + 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]), ServerMode.GET_AND_PUT); + ts.setSocketTimeout(2000); + + jul.log(Level.INFO, "Fully multi-threaded TFTP Server v1.0.2 running. Enter 'stop' to server stop."); + //new InputStreamReader(System.in).read(); + Scanner sc = new Scanner(System.in); + new Thread(() -> { + while (true) { + if (sc.next().toLowerCase().trim().equals("stop")) { + ts.shutdown(); + jul.log(Level.INFO, "Server shut down."); + System.exit(0); + } + } + }).start(); + } + +} diff --git a/src/main/resources/img/tftp-well-16.png b/src/main/resources/img/tftp-well-16.png new file mode 100644 index 0000000000000000000000000000000000000000..8438ed05854bce2237873380230c955988c2f1b5 GIT binary patch literal 770 zcmV+d1O5DoP)p73Py_-QJWG$OeiD>Mx+QrLB=UVO{1_M zF$r~1q>hUJ(#+`Cyf^Q*N(v?NZ13gcT<$pr{Gn4`!5(8Xj)d%|(_x2r*fWYb>{ewO zCD=!d&uon*Evd@TA}bX6uYKad=wz%1uL7ilbpcW7jg&n&S+XV+c|J(Zi@j{wm{V(+ z(e8FIZwK^Ap}@XyZ2HE*8&-tde3Uoj2Y@n_%s<`YjnzS+Ze=L=HsB3jb~||DF8}~A zH;QoGo~NSKJ^VT`4}M<(hyjCnp6s&^$^vBdeU@4ldnniQRc+8!zTy$ItPliQp)8WS161tE zr~CmO0HFCJ;9pEeeEAHI)e8X7v5dHC%ghjU7^$;VIG^Oj=Tvp-EQA z0s#CnvYMUIbpXKc%yH}tZ|Cla1@4Y$IwVOeO*pSsBvy!pVe!&f;yF4iD+Hd*7N7@iV)8CQ*CgmcBZxji4`d)1 zV%eTVm0}8~#K%V;ne>!PVu!3y{FZ}(1IM|`(#xG><5#h9TRGTCrJI7?J1jn2a8ZnJ zO08#|Tc;~yQ{Y?ArV?M<|Mfy8bMdTrFT35Ord|>IWrfnVMfpELnZ8k}b)lQA-f!wH zUE>C~gSFkYR3-7`lHq#JbJNDb8u62?PzC_}0z5n%PH%Jv_5c6?07*qoM6N<$f{OoG A*8l(j literal 0 HcmV?d00001