1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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
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
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
107 if (createSubDirs) {
108 createDirectory(temp.getParentFile());
109 }
110
111 return temp;
112 }
113
114
115
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
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
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
176
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);
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
194 if (answer != null) {
195
196
197
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
209
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
223
224 final TFTPAckPacket ack = (TFTPAckPacket) answer;
225 if (ack.getBlockNumber() != block) {
226
227
228
229
230
231 sendNext = false;
232 } else {
233
234 block++;
235 if (block > 65535) {
236
237 block = 0;
238 }
239 sendNext = true;
240 }
241 }
242 } finally {
243 IOUtils.closeQuietly(inputStream);
244 }
245 }
246
247
248
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);
280
281 while (true) {
282
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
290 if (dataPacket != null) {
291
292
293
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
306 transferTftp.bufferedSend(lastSentAck);
307 timeoutCount++;
308 continue;
309 }
310 }
311
312 if (dataPacket instanceof TFTPWriteRequestPacket) {
313
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
329
330 bos.write(data, dataOffset, dataLength);
331 lastBlock = block;
332 }
333
334 lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), block);
335 sendData(transferTftp, lastSentAck);
336 if (dataLength < TFTPDataPacket.MAX_DATA_LENGTH) {
337
338 bos.close();
339
340
341
342 for (int i = 0; i < maxTimeoutRetries; i++) {
343 try {
344 dataPacket = transferTftp.bufferedReceive();
345 } catch (final SocketTimeoutException e) {
346
347
348 break;
349 }
350
351 if (dataPacket != null && (!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket.getPort() != twrp.getPort())) {
352
353 transferTftp.bufferedSend(new TFTPErrorPacket(dataPacket.getAddress(), dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID,
354 "Unexpected Host or Port"));
355 } else {
356
357
358
359 transferTftp.bufferedSend(lastSentAck);
360 }
361 }
362
363
364 break;
365 }
366 }
367 }
368 } finally {
369 IOUtils.close(bos);
370 }
371 }
372
373
374
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
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
429 }
430 }
431 }
432
433 private static final int DEFAULT_TFTP_PORT = 69;
434
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
457
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
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
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
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
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
531
532
533
534
535
536
537
538
539
540
541
542
543
544
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
558
559
560
561
562
563
564
565
566
567
568
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
576
577
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
591 }
592
593 try {
594 serverThread.join();
595 } catch (final InterruptedException e) {
596
597 }
598 }
599
600 @Override
601 protected void finalize() throws Throwable {
602 close();
603 super.finalize();
604 }
605
606
607
608
609
610
611 public int getMaxTimeoutRetries() {
612 return maxTimeoutRetries;
613 }
614
615
616
617
618
619
620 public int getPort() {
621 return port;
622 }
623
624
625
626
627
628
629 public int getSocketTimeout() {
630 return socketTimeout;
631 }
632
633
634
635
636
637
638
639 public boolean isRunning() throws Exception {
640 if (shutdownServer && serverException != null) {
641 throw serverException;
642 }
643 return !shutdownServer;
644 }
645
646
647
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
666 socketTimeout = serverTftp.getDefaultTimeout();
667
668
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
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;
713 if (serverTftp != null && serverTftp.isOpen()) {
714 serverTftp.close();
715 }
716 }
717 }
718
719
720
721
722 void sendData(final TFTP tftp, final TFTPPacket data) throws IOException {
723 tftp.bufferedSend(data);
724 }
725
726
727
728
729
730
731 public void setLog(final PrintStream log) {
732 this.log = log;
733 }
734
735
736
737
738
739
740 public void setLogError(final PrintStream logError) {
741 this.logError = logError;
742 }
743
744
745
746
747
748
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
759
760
761
762
763
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
774
775
776
777 @Deprecated
778 public void shutdown() {
779 close();
780 }
781 }