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 > 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 }