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