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.net.io.FromNetASCIIOutputStream;
38 import org.apache.commons.net.io.ToNetASCIIInputStream;
39
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 public class TFTPServer implements Runnable, AutoCloseable {
76
77 public enum ServerMode {
78 GET_ONLY, PUT_ONLY, GET_AND_PUT
79 }
80
81
82
83
84 private final 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
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
106 if (createSubDirs) {
107 createDirectory(temp.getParentFile());
108 }
109
110 return temp;
111 }
112
113
114
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
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
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
175
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);
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
193 if (answer != null) {
194
195
196
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
208
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
222
223 final TFTPAckPacket ack = (TFTPAckPacket) answer;
224 if (ack.getBlockNumber() != block) {
225
226
227
228
229
230 sendNext = false;
231 } else {
232
233 block++;
234 if (block > 65535) {
235
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
248 }
249 }
250 }
251
252
253
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);
285
286 while (true) {
287
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
295 if (dataPacket != null) {
296
297
298
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
311 transferTftp.bufferedSend(lastSentAck);
312 timeoutCount++;
313 continue;
314 }
315 }
316
317 if (dataPacket instanceof TFTPWriteRequestPacket) {
318
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
334
335 bos.write(data, dataOffset, dataLength);
336 lastBlock = block;
337 }
338
339 lastSentAck = new TFTPAckPacket(twrp.getAddress(), twrp.getPort(), block);
340 sendData(transferTftp, lastSentAck);
341 if (dataLength < TFTPDataPacket.MAX_DATA_LENGTH) {
342
343 bos.close();
344
345
346
347 for (int i = 0; i < maxTimeoutRetries; i++) {
348 try {
349 dataPacket = transferTftp.bufferedReceive();
350 } catch (final SocketTimeoutException e) {
351
352
353 break;
354 }
355
356 if (dataPacket != null && (!dataPacket.getAddress().equals(twrp.getAddress()) || dataPacket.getPort() != twrp.getPort())) {
357
358 transferTftp.bufferedSend(new TFTPErrorPacket(dataPacket.getAddress(), dataPacket.getPort(), TFTPErrorPacket.UNKNOWN_TID,
359 "Unexpected Host or Port"));
360 } else {
361
362
363
364 transferTftp.bufferedSend(lastSentAck);
365 }
366 }
367
368
369 break;
370 }
371 }
372 }
373 } finally {
374 if (bos != null) {
375 bos.close();
376 }
377 }
378 }
379
380
381
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
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
436 }
437 }
438 }
439
440 private static final int DEFAULT_TFTP_PORT = 69;
441
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
464
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
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
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
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
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
538
539
540
541
542
543
544
545
546
547
548
549
550
551
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
565
566
567
568
569
570
571
572
573
574
575
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
583
584
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
598 }
599
600 try {
601 serverThread.join();
602 } catch (final InterruptedException e) {
603
604 }
605 }
606
607 @Override
608 protected void finalize() throws Throwable {
609 close();
610 }
611
612
613
614
615
616
617 public int getMaxTimeoutRetries() {
618 return maxTimeoutRetries;
619 }
620
621
622
623
624
625
626 public int getPort() {
627 return port;
628 }
629
630
631
632
633
634
635 public int getSocketTimeout() {
636 return socketTimeout;
637 }
638
639
640
641
642
643
644
645 public boolean isRunning() throws Exception {
646 if (shutdownServer && serverException != null) {
647 throw serverException;
648 }
649 return !shutdownServer;
650 }
651
652
653
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
672 socketTimeout = serverTftp.getDefaultTimeout();
673
674
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
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;
719 if (serverTftp != null && serverTftp.isOpen()) {
720 serverTftp.close();
721 }
722 }
723 }
724
725
726
727
728 void sendData(final TFTP tftp, final TFTPPacket data) throws IOException {
729 tftp.bufferedSend(data);
730 }
731
732
733
734
735
736
737 public void setLog(final PrintStream log) {
738 this.log = log;
739 }
740
741
742
743
744
745
746 public void setLogError(final PrintStream logError) {
747 this.logError = logError;
748 }
749
750
751
752
753
754
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
765
766
767
768
769
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
780
781
782
783 @Deprecated
784 public void shutdown() {
785 close();
786 }
787 }