View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.tftp;
19  
20  import java.io.BufferedInputStream;
21  import java.io.BufferedOutputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileNotFoundException;
25  import java.io.FileOutputStream;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.OutputStream;
29  import java.io.PrintStream;
30  import java.net.InetAddress;
31  import java.net.NetworkInterface;
32  import java.net.SocketTimeoutException;
33  import java.time.Duration;
34  import java.util.Enumeration;
35  import java.util.HashSet;
36  
37  import org.apache.commons.io.IOUtils;
38  import org.apache.commons.net.io.FromNetASCIIOutputStream;
39  import org.apache.commons.net.io.ToNetASCIIInputStream;
40  
41  /**
42   * 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.
43   *
44   * 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,
45   * etc.
46   *
47   * To stop, use the shutdown method.
48   *
49   * To check to see if the server is still running (or if it stopped because of an error), call the isRunning() method.
50   *
51   * By default, events are not logged to stdout/stderr. This can be changed with the setLog and setLogError methods.
52   *
53   * <p>
54   * Example usage is below:
55   *
56   * <code>
57   * public static void main(String[] args) throws Exception {
58   *      if (args.length != 1) {
59   *          System.out.println("You must provide 1 argument - the base path for the server to serve from.");
60   *          System.exit(1);
61   *      }
62   *
63   *      try (TFTPServer ts = new TFTPServer(new File(args[0]), new File(args[0]), GET_AND_PUT)) {
64   *        ts.setSocketTimeout(2000);
65   *        System.out.println("TFTP Server running.  Press enter to stop.");
66   *        new InputStreamReader(System.in).read();
67   *      }
68   *
69   *      System.out.println("Server shut down.");
70   *      System.exit(0);
71   * }
72   * </code>
73   *
74   * @since 2.0
75   */
76  public class TFTPServer implements Runnable, AutoCloseable {
77  
78      public enum ServerMode {
79          GET_ONLY, PUT_ONLY, GET_AND_PUT
80      }
81  
82      /*
83       * An ongoing transfer.
84       */
85      private final class TFTPTransfer implements Runnable {
86          private final TFTPPacket tftpPacket;
87  
88          private boolean shutdownTransfer;
89  
90          TFTP transferTftp;
91  
92          public TFTPTransfer(final TFTPPacket tftpPacket) {
93              this.tftpPacket = tftpPacket;
94          }
95  
96          /*
97           * Makes sure that paths provided by TFTP clients do not get outside of the serverRoot directory.
98           */
99          private File buildSafeFile(final File serverDirectory, final String fileName, final boolean createSubDirs) throws IOException {
100             final File temp = new File(serverDirectory, fileName).getCanonicalFile();
101 
102             if (!isSubdirectoryOf(serverDirectory, temp)) {
103                 throw new IOException("Cannot access files outside of TFTP server root.");
104             }
105 
106             // ensure directory exists (if requested)
107             if (createSubDirs) {
108                 createDirectory(temp.getParentFile());
109             }
110 
111             return temp;
112         }
113 
114         /*
115          * Creates subdirectories recursively.
116          */
117         private void createDirectory(final File file) throws IOException {
118             final File parent = file.getParentFile();
119             if (parent == null) {
120                 throw new IOException("Unexpected error creating requested directory");
121             }
122             if (!parent.exists()) {
123                 // recurse...
124                 createDirectory(parent);
125             }
126 
127             if (!parent.isDirectory()) {
128                 throw new IOException("Invalid directory path - file in the way of requested folder");
129             }
130             if (file.isDirectory()) {
131                 return;
132             }
133             final boolean result = file.mkdir();
134             if (!result) {
135                 throw new IOException("Couldn't create requested directory");
136             }
137         }
138 
139         /*
140          * Handles a tftp read request.
141          */
142         private void handleRead(final TFTPReadRequestPacket trrp) throws IOException, TFTPPacketException {
143             if (mode == ServerMode.PUT_ONLY) {
144                 transferTftp
145                         .bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp.getPort(), TFTPErrorPacket.ILLEGAL_OPERATION, "Read not allowed by server."));
146                 return;
147             }
148             InputStream inputStream = null;
149             try {
150                 try {
151                     inputStream = new BufferedInputStream(new FileInputStream(buildSafeFile(serverReadDirectory, trrp.getFilename(), false)));
152                 } catch (final FileNotFoundException e) {
153                     transferTftp.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp.getPort(), TFTPErrorPacket.FILE_NOT_FOUND, e.getMessage()));
154                     return;
155                 } catch (final Exception e) {
156                     transferTftp.bufferedSend(new TFTPErrorPacket(trrp.getAddress(), trrp.getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage()));
157                     return;
158                 }
159 
160                 if (trrp.getMode() == TFTP.NETASCII_MODE) {
161                     inputStream = new ToNetASCIIInputStream(inputStream);
162                 }
163 
164                 final byte[] temp = new byte[TFTPDataPacket.MAX_DATA_LENGTH];
165 
166                 TFTPPacket answer;
167 
168                 int block = 1;
169                 boolean sendNext = true;
170 
171                 int readLength = TFTPDataPacket.MAX_DATA_LENGTH;
172 
173                 TFTPDataPacket lastSentData = null;
174 
175                 // We are reading a file, so when we read less than the
176                 // requested bytes, we know that we are at the end of the file.
177                 while (readLength == TFTPDataPacket.MAX_DATA_LENGTH && !shutdownTransfer) {
178                     if (sendNext) {
179                         readLength = inputStream.read(temp);
180                         if (readLength == -1) {
181                             readLength = 0;
182                         }
183 
184                         lastSentData = new TFTPDataPacket(trrp.getAddress(), trrp.getPort(), block, temp, 0, readLength);
185                         sendData(transferTftp, lastSentData); // send the data
186                     }
187 
188                     answer = null;
189 
190                     int timeoutCount = 0;
191 
192                     while (!shutdownTransfer && (answer == null || !answer.getAddress().equals(trrp.getAddress()) || answer.getPort() != trrp.getPort())) {
193                         // listen for an answer.
194                         if (answer != null) {
195                             // The answer that we got didn't come from the
196                             // expected source, fire back an error, and continue
197                             // listening.
198                             log.println("TFTP Server ignoring message from unexpected source.");
199                             transferTftp.bufferedSend(
200                                     new TFTPErrorPacket(answer.getAddress(), answer.getPort(), TFTPErrorPacket.UNKNOWN_TID, "Unexpected Host or Port"));
201                         }
202                         try {
203                             answer = transferTftp.bufferedReceive();
204                         } catch (final SocketTimeoutException e) {
205                             if (timeoutCount >= maxTimeoutRetries) {
206                                 throw e;
207                             }
208                             // didn't get an ack for this data. need to resend
209                             // it.
210                             timeoutCount++;
211                             transferTftp.bufferedSend(lastSentData);
212                             continue;
213                         }
214                     }
215 
216                     if (answer == null || !(answer instanceof TFTPAckPacket)) {
217                         if (!shutdownTransfer) {
218                             logError.println("Unexpected response from tftp client during transfer (" + answer + ").  Transfer aborted.");
219                         }
220                         break;
221                     }
222                     // once we get here, we know we have an answer packet
223                     // from the correct host.
224                     final TFTPAckPacket ack = (TFTPAckPacket) answer;
225                     if (ack.getBlockNumber() != block) {
226                         /*
227                          * The origional tftp spec would have called on us to resend the previous data here, however, that causes the SAS Syndrome.
228                          * 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
229                          * lost, we will time out on receive, and resend the previous data at that point.
230                          */
231                         sendNext = false;
232                     } else {
233                         // send the next block
234                         block++;
235                         if (block > 65535) {
236                             // wrap the block number
237                             block = 0;
238                         }
239                         sendNext = true;
240                     }
241                 }
242             } finally {
243                 IOUtils.closeQuietly(inputStream);
244             }
245         }
246 
247         /*
248          * handle a TFTP write request.
249          */
250         private void handleWrite(final TFTPWriteRequestPacket twrp) throws IOException, TFTPPacketException {
251             OutputStream bos = null;
252             try {
253                 if (mode == ServerMode.GET_ONLY) {
254                     transferTftp.bufferedSend(
255                             new TFTPErrorPacket(twrp.getAddress(), twrp.getPort(), TFTPErrorPacket.ILLEGAL_OPERATION, "Write not allowed by server."));
256                     return;
257                 }
258 
259                 int lastBlock = 0;
260                 final String fileName = twrp.getFilename();
261 
262                 try {
263                     final File temp = buildSafeFile(serverWriteDirectory, fileName, true);
264                     if (temp.exists()) {
265                         transferTftp.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp.getPort(), TFTPErrorPacket.FILE_EXISTS, "File already exists"));
266                         return;
267                     }
268                     bos = new BufferedOutputStream(new FileOutputStream(temp));
269 
270                     if (twrp.getMode() == TFTP.NETASCII_MODE) {
271                         bos = new FromNetASCIIOutputStream(bos);
272                     }
273                 } catch (final Exception e) {
274                     transferTftp.bufferedSend(new TFTPErrorPacket(twrp.getAddress(), twrp.getPort(), TFTPErrorPacket.UNDEFINED, e.getMessage()));
275                     return;
276                 }
277 
278                 TFTPAckPacket lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0);
279                 sendData(transferTftp, lastSentAck); // send the data
280 
281                 while (true) {
282                     // get the response - ensure it is from the right place.
283                     TFTPPacket dataPacket = null;
284 
285                     int timeoutCount = 0;
286 
287                     while (!shutdownTransfer
288                             && (dataPacket == null || !dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket.getPort() != twrp.getPort())) {
289                         // listen for an answer.
290                         if (dataPacket != null) {
291                             // The data that we got didn't come from the
292                             // expected source, fire back an error, and continue
293                             // listening.
294                             log.println("TFTP Server ignoring message from unexpected source.");
295                             transferTftp.bufferedSend(
296                                     new TFTPErrorPacket(dataPacket.getAddress(), dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID, "Unexpected Host or Port"));
297                         }
298 
299                         try {
300                             dataPacket = transferTftp.bufferedReceive();
301                         } catch (final SocketTimeoutException e) {
302                             if (timeoutCount >= maxTimeoutRetries) {
303                                 throw e;
304                             }
305                             // It didn't get our ack. Resend it.
306                             transferTftp.bufferedSend(lastSentAck);
307                             timeoutCount++;
308                             continue;
309                         }
310                     }
311 
312                     if (dataPacket instanceof TFTPWriteRequestPacket) {
313                         // it must have missed our initial ack. Send another.
314                         lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), 0);
315                         transferTftp.bufferedSend(lastSentAck);
316                     } else if (dataPacket == null || !(dataPacket instanceof TFTPDataPacket)) {
317                         if (!shutdownTransfer) {
318                             logError.println("Unexpected response from tftp client during transfer (" + dataPacket + ").  Transfer aborted.");
319                         }
320                         break;
321                     } else {
322                         final int block = ((TFTPDataPacket) dataPacket).getBlockNumber();
323                         final byte[] data = ((TFTPDataPacket) dataPacket).getData();
324                         final int dataLength = ((TFTPDataPacket) dataPacket).getDataLength();
325                         final int dataOffset = ((TFTPDataPacket) dataPacket).getDataOffset();
326 
327                         if (block > lastBlock || lastBlock == 65535 && block == 0) {
328                             // it might resend a data block if it missed our ack
329                             // - don't rewrite the block.
330                             bos.write(data, dataOffset, dataLength);
331                             lastBlock = block;
332                         }
333 
334                         lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), block);
335                         sendData(transferTftp, lastSentAck); // send the data
336                         if (dataLength < TFTPDataPacket.MAX_DATA_LENGTH) {
337                             // end of stream signal - The tranfer is complete.
338                             bos.close();
339 
340                             // But my ack may be lost - so listen to see if I
341                             // need to resend the ack.
342                             for (int i = 0; i < maxTimeoutRetries; i++) {
343                                 try {
344                                     dataPacket = transferTftp.bufferedReceive();
345                                 } catch (final SocketTimeoutException e) {
346                                     // this is the expected route - the client
347                                     // shouldn't be sending any more packets.
348                                     break;
349                                 }
350 
351                                 if (dataPacket != null && (!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket.getPort() != twrp.getPort())) {
352                                     // make sure it was from the right client...
353                                     transferTftp.bufferedSend(new TFTPErrorPacket(dataPacket.getAddress(), dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID,
354                                             "Unexpected Host or Port"));
355                                 } else {
356                                     // This means they sent us the last
357                                     // datapacket again, must have missed our
358                                     // ack. resend it.
359                                     transferTftp.bufferedSend(lastSentAck);
360                                 }
361                             }
362 
363                             // all done.
364                             break;
365                         }
366                     }
367                 }
368             } finally {
369                 IOUtils.close(bos);
370             }
371         }
372 
373         /*
374          * recursively check to see if one directory is a parent of another.
375          */
376         private boolean isSubdirectoryOf(final File parent, final File child) {
377             final File childsParent = child.getParentFile();
378             if (childsParent == null) {
379                 return false;
380             }
381             if (childsParent.equals(parent)) {
382                 return true;
383             }
384             return isSubdirectoryOf(parent, childsParent);
385         }
386 
387         @Override
388         public void run() {
389             try {
390                 transferTftp = newTFTP();
391 
392                 transferTftp.beginBufferedOps();
393                 transferTftp.setDefaultTimeout(socketTimeout);
394 
395                 transferTftp.open();
396 
397                 if (tftpPacket instanceof TFTPReadRequestPacket) {
398                     handleRead((TFTPReadRequestPacket) tftpPacket);
399                 } else if (tftpPacket instanceof TFTPWriteRequestPacket) {
400                     handleWrite((TFTPWriteRequestPacket) tftpPacket);
401                 } else {
402                     log.println("Unsupported TFTP request (" + tftpPacket + ") - ignored.");
403                 }
404             } catch (final Exception e) {
405                 if (!shutdownTransfer) {
406                     logError.println("Unexpected Error in during TFTP file transfer.  Transfer aborted. " + e);
407                 }
408             } finally {
409                 try {
410                     if (transferTftp != null && transferTftp.isOpen()) {
411                         transferTftp.endBufferedOps();
412                         transferTftp.close();
413                     }
414                 } catch (final Exception e) {
415                     // noop
416                 }
417                 synchronized (transfers) {
418                     transfers.remove(this);
419                 }
420             }
421         }
422 
423         public void shutdown() {
424             shutdownTransfer = true;
425             try {
426                 transferTftp.close();
427             } catch (final RuntimeException e) {
428                 // noop
429             }
430         }
431     }
432 
433     private static final int DEFAULT_TFTP_PORT = 69;
434     /* /dev/null output stream (default) */
435     private static final PrintStream nullStream = new PrintStream(new OutputStream() {
436         @Override
437         public void write(final byte[] b) throws IOException {
438         }
439 
440         @Override
441         public void write(final int b) {
442         }
443     });
444 
445     private final HashSet<TFTPTransfer> transfers = new HashSet<>();
446     private volatile boolean shutdownServer;
447     private TFTP serverTftp;
448     private File serverReadDirectory;
449     private File serverWriteDirectory;
450     private final int port;
451     private final InetAddress localAddress;
452 
453     private Exception serverException;
454 
455     private final ServerMode mode;
456     // don't have access to a logger api, so we will log to these streams, which
457     // by default are set to a no-op logger
458     private PrintStream log;
459 
460     private PrintStream logError;
461     private int maxTimeoutRetries = 3;
462     private int socketTimeout;
463 
464     private Thread serverThread;
465 
466     /**
467      * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory.
468      *
469      * The server will start in another thread, allowing this constructor to return immediately.
470      *
471      * 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.
472      *
473      * 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.
474      *
475      * @param serverReadDirectory  directory for GET requests
476      * @param serverWriteDirectory directory for PUT requests
477      * @param port                 The local port to bind to.
478      * @param localAddress            The local address to bind to.
479      * @param mode                 A value as specified above.
480      * @param log                  Stream to write log message to. If not provided, uses System.out
481      * @param errorLog             Stream to write error messages to. If not provided, uses System.err.
482      * @throws IOException if the server directory is invalid or does not exist.
483      */
484     public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final int port, final InetAddress localAddress, final ServerMode mode,
485             final PrintStream log, final PrintStream errorLog) throws IOException {
486         this.port = port;
487         this.mode = mode;
488         this.localAddress = localAddress;
489         this.log = log == null ? nullStream : log;
490         this.logError = errorLog == null ? nullStream : errorLog;
491         launch(serverReadDirectory, serverWriteDirectory);
492     }
493 
494     /**
495      * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory.
496      *
497      * The server will start in another thread, allowing this constructor to return immediately.
498      *
499      * 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.
500      *
501      * 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.
502      *
503      * @param serverReadDirectory  directory for GET requests
504      * @param serverWriteDirectory directory for PUT requests
505      * @param port                 the port to use
506      * @param localiface           The local network interface to bind to. The interface's first address wil be used.
507      * @param mode                 A value as specified above.
508      * @param log                  Stream to write log message to. If not provided, uses System.out
509      * @param errorLog             Stream to write error messages to. If not provided, uses System.err.
510      * @throws IOException if the server directory is invalid or does not exist.
511      */
512     public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final int port, final NetworkInterface localiface, final ServerMode mode,
513             final PrintStream log, final PrintStream errorLog) throws IOException {
514         this.mode = mode;
515         this.port = port;
516         InetAddress inetAddress = null;
517         if (localiface != null) {
518             final Enumeration<InetAddress> ifaddrs = localiface.getInetAddresses();
519             if (ifaddrs != null && ifaddrs.hasMoreElements()) {
520                 inetAddress = ifaddrs.nextElement();
521             }
522         }
523         this.log = log == null ? nullStream : log;
524         this.logError = errorLog == null ? nullStream : errorLog;
525         this.localAddress = inetAddress;
526         launch(serverReadDirectory, serverWriteDirectory);
527     }
528 
529     /**
530      * Start a TFTP Server on the specified port. Gets and Puts occur in the specified directory.
531      *
532      * The server will start in another thread, allowing this constructor to return immediately.
533      *
534      * 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.
535      *
536      * 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.
537      *
538      * @param serverReadDirectory  directory for GET requests
539      * @param serverWriteDirectory directory for PUT requests
540      * @param port                 the port to use
541      * @param mode                 A value as specified above.
542      * @param log                  Stream to write log message to. If not provided, uses System.out
543      * @param errorLog             Stream to write error messages to. If not provided, uses System.err.
544      * @throws IOException if the server directory is invalid or does not exist.
545      */
546     public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final int port, final ServerMode mode, final PrintStream log,
547             final PrintStream errorLog) throws IOException {
548         this.port = port;
549         this.mode = mode;
550         this.log = log == null ? nullStream : log;
551         this.logError = errorLog == null ? nullStream : errorLog;
552         this.localAddress = null;
553         launch(serverReadDirectory, serverWriteDirectory);
554     }
555 
556     /**
557      * Start a TFTP Server on the default port (69). Gets and Puts occur in the specified directories.
558      *
559      * The server will start in another thread, allowing this constructor to return immediately.
560      *
561      * 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.
562      *
563      * 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.
564      *
565      * @param serverReadDirectory  directory for GET requests
566      * @param serverWriteDirectory directory for PUT requests
567      * @param mode                 A value as specified above.
568      * @throws IOException if the server directory is invalid or does not exist.
569      */
570     public TFTPServer(final File serverReadDirectory, final File serverWriteDirectory, final ServerMode mode) throws IOException {
571         this(serverReadDirectory, serverWriteDirectory, DEFAULT_TFTP_PORT, mode, null, null);
572     }
573 
574     /**
575      * Closes the TFTP server (and any currently running transfers) and release all opened network resources.
576      *
577      * @since 3.10.0
578      */
579     @Override
580     public void close() {
581         shutdownServer = true;
582 
583         synchronized (transfers) {
584             transfers.forEach(TFTPTransfer::shutdown);
585         }
586 
587         try {
588             serverTftp.close();
589         } catch (final RuntimeException e) {
590             // noop
591         }
592 
593         try {
594             serverThread.join();
595         } catch (final InterruptedException e) {
596             // we've done the best we could, return
597         }
598     }
599 
600     @Override
601     protected void finalize() throws Throwable {
602         close();
603         super.finalize();
604     }
605 
606     /**
607      * Gets the current value for maxTimeoutRetries
608      *
609      * @return the max allowed number of retries
610      */
611     public int getMaxTimeoutRetries() {
612         return maxTimeoutRetries;
613     }
614 
615     /**
616      * Gets the server port number
617      *
618      * @return the server port number
619      */
620     public int getPort() {
621         return port;
622     }
623 
624     /**
625      * Gets the current socket timeout used during transfers in milliseconds.
626      *
627      * @return the timeout value
628      */
629     public int getSocketTimeout() {
630         return socketTimeout;
631     }
632 
633     /**
634      * check if the server thread is still running.
635      *
636      * @return true if running, false if stopped.
637      * @throws Exception throws the exception that stopped the server if the server is stopped from an exception.
638      */
639     public boolean isRunning() throws Exception {
640         if (shutdownServer && serverException != null) {
641             throw serverException;
642         }
643         return !shutdownServer;
644     }
645 
646     /*
647      * start the server, throw an error if it can't start.
648      */
649     private void launch(final File newServerReadDirectory, final File newServerWriteDirectory) throws IOException {
650         log.println("Starting TFTP Server on port " + port + ".  Read directory: " + newServerReadDirectory + " Write directory: " + newServerWriteDirectory
651                 + " Server Mode is " + mode);
652 
653         serverReadDirectory = newServerReadDirectory.getCanonicalFile();
654         if (!serverReadDirectory.exists() || !newServerReadDirectory.isDirectory()) {
655             throw new IOException("The server read directory " + serverReadDirectory + " does not exist");
656         }
657 
658         serverWriteDirectory = newServerWriteDirectory.getCanonicalFile();
659         if (!serverWriteDirectory.exists() || !newServerWriteDirectory.isDirectory()) {
660             throw new IOException("The server write directory " + serverWriteDirectory + " does not exist");
661         }
662 
663         serverTftp = new TFTP();
664 
665         // This is the value used in response to each client.
666         socketTimeout = serverTftp.getDefaultTimeout();
667 
668         // we want the server thread to listen forever.
669         serverTftp.setDefaultTimeout(Duration.ZERO);
670 
671         if (localAddress != null) {
672             serverTftp.open(port, localAddress);
673         } else {
674             serverTftp.open(port);
675         }
676 
677         serverThread = new Thread(this);
678         serverThread.setDaemon(true);
679         serverThread.start();
680     }
681 
682     /*
683      * Allow test code to customize the TFTP instance
684      */
685     TFTP newTFTP() {
686         return new TFTP();
687     }
688 
689     @Override
690     public void run() {
691         try {
692             while (!shutdownServer) {
693                 final TFTPPacket tftpPacket;
694 
695                 tftpPacket = serverTftp.receive();
696 
697                 final TFTPTransfer tt = new TFTPTransfer(tftpPacket);
698                 synchronized (transfers) {
699                     transfers.add(tt);
700                 }
701 
702                 final Thread thread = new Thread(tt);
703                 thread.setDaemon(true);
704                 thread.start();
705             }
706         } catch (final Exception e) {
707             if (!shutdownServer) {
708                 serverException = e;
709                 logError.println("Unexpected Error in TFTP Server - Server shut down! + " + e);
710             }
711         } finally {
712             shutdownServer = true; // set this to true, so the launching thread can check to see if it started.
713             if (serverTftp != null && serverTftp.isOpen()) {
714                 serverTftp.close();
715             }
716         }
717     }
718 
719     /*
720      * Also allow customization of sending data/ack so can generate errors if needed
721      */
722     void sendData(final TFTP tftp, final TFTPPacket data) throws IOException {
723         tftp.bufferedSend(data);
724     }
725 
726     /**
727      * Sets the stream object to log debug / informational messages. By default, this is a no-op
728      *
729      * @param log the stream to use for logging
730      */
731     public void setLog(final PrintStream log) {
732         this.log = log;
733     }
734 
735     /**
736      * Sets the stream object to log error messsages. By default, this is a no-op
737      *
738      * @param logError the stream to use for logging errors
739      */
740     public void setLogError(final PrintStream logError) {
741         this.logError = logError;
742     }
743 
744     /**
745      * Sets the max number of retries in response to a timeout. Default 3. Min 0.
746      *
747      * @param retries number of retries, must be &gt; 0
748      * @throws IllegalArgumentException if {@code retries} is less than 0.
749      */
750     public void setMaxTimeoutRetries(final int retries) {
751         if (retries < 0) {
752             throw new IllegalArgumentException("Invalid Value");
753         }
754         maxTimeoutRetries = retries;
755     }
756 
757     /**
758      * Sets the socket timeout in milliseconds used in transfers.
759      * <p>
760      * Defaults to the value {@link TFTP#DEFAULT_TIMEOUT}. Minimum value of 10.
761      * </p>
762      * @param timeout the timeout; must be equal to or larger than 10.
763      * @throws IllegalArgumentException if {@code timeout} is less than 10.
764      */
765     public void setSocketTimeout(final int timeout) {
766         if (timeout < 10) {
767             throw new IllegalArgumentException("Invalid Value");
768         }
769         socketTimeout = timeout;
770     }
771 
772     /**
773      * Closes the TFTP server (and any currently running transfers) and release all opened network resources.
774      *
775      * @deprecated Use {@link #close()}.
776      */
777     @Deprecated
778     public void shutdown() {
779         close();
780     }
781 }