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  package org.apache.commons.net.tftp;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertThrows;
22  import static org.junit.jupiter.api.Assertions.fail;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.nio.charset.StandardCharsets;
28  import java.nio.file.Files;
29  import java.nio.file.Path;
30  import java.time.Duration;
31  import java.util.UUID;
32  
33  import org.apache.commons.io.FileUtils;
34  import org.apache.commons.net.tftp.TFTPServer.ServerMode;
35  import org.junit.jupiter.api.AfterEach;
36  import org.junit.jupiter.api.BeforeEach;
37  import org.junit.jupiter.api.Test;
38  
39  /**
40   * Basic tests to ensure that the TFTP Server is honoring its read/write mode, and preventing files from being read or written from outside of the assigned
41   * roots.
42   */
43  public class TFTPServerPathTest {
44  
45      private static final int SERVER_PORT = 6901;
46      private static final String FILE_PREFIX = "TFTPServerPathTest_";
47  
48      private static Path createFileInDir(final Path directory, final String fileName) throws IOException {
49          final Path filePath = directory.resolve(fileName);
50          if (Files.exists(filePath)) {
51              Files.delete(filePath);
52          }
53          return Files.createFile(filePath);
54      }
55  
56      private static void deleteFile(final Path path, final boolean retry) throws IOException {
57          if (path != null) {
58              try {
59                  Files.deleteIfExists(path);
60              } catch (IOException e) {
61                  if (retry) {
62                      try {
63                          Thread.sleep(500);
64                      } catch (InterruptedException e1) {
65                          fail(e);
66                      }
67                      Files.deleteIfExists(path);
68                  }
69                  throw e;
70              }
71          }
72      }
73  
74      private static String getRandomFileName(final String suffix) {
75          return FILE_PREFIX + UUID.randomUUID() + suffix;
76      }
77  
78      private TFTPServer tftpServer;
79      private Path serverDirectory;
80  
81      private TFTPClient tftpClient;
82      private Path fileToRead;
83      private Path fileToWrite;
84  
85      @AfterEach
86      public void afterEach() throws IOException {
87          if (tftpClient != null) {
88              tftpClient.close();
89          }
90          if (tftpServer != null) {
91              tftpServer.close();
92          }
93          deleteFile(fileToRead, true);
94          deleteFile(fileToWrite, true);
95      }
96  
97      @BeforeEach
98      public void beforeEach() throws IOException {
99          serverDirectory = FileUtils.getTempDirectory().toPath();
100         fileToRead = createFileInDir(serverDirectory, getRandomFileName(".source.txt"));
101         fileToWrite = createFileInDir(serverDirectory, getRandomFileName(".out"));
102     }
103 
104     private TFTPServer startTftpServer(final ServerMode serverMode) throws IOException {
105         return new TFTPServer(serverDirectory.toFile(), serverDirectory.toFile(), SERVER_PORT, serverMode, null, null);
106     }
107 
108     @Test
109     public void testReadOnly() throws IOException {
110         // Start a read-only server
111         tftpServer = startTftpServer(ServerMode.GET_ONLY);
112         final String serverAddress = "localhost";
113         final int serverPort = tftpServer.getPort();
114 
115         // write some data to verify read
116         Files.write(fileToRead, "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8));
117         final long fileToReadContentLength = Files.size(fileToRead);
118 
119         tftpClient = new TFTPClient();
120         tftpClient.open();
121         tftpClient.setSoTimeout(Duration.ofMillis(2000));
122 
123         // we can read file
124         try (final OutputStream os = Files.newOutputStream(fileToWrite)) {
125             final String writeFileName = fileToRead.getFileName().toString();
126             final int bytesRead = tftpClient.receiveFile(writeFileName, TFTP.BINARY_MODE, os, serverAddress, serverPort);
127             assertEquals(fileToReadContentLength, bytesRead);
128         }
129 
130         // but we cannot write to it
131         try (final InputStream is = Files.newInputStream(fileToRead)) {
132             final String readFileName = fileToRead.getFileName().toString();
133             final IOException exception = assertThrows(IOException.class, () -> tftpClient.sendFile(readFileName, TFTP.BINARY_MODE, is, serverAddress, serverPort));
134             assertEquals("Error code 4 received: Write not allowed by server.", exception.getMessage());
135         }
136     }
137 
138     @Test
139     public void testWriteOnly() throws IOException {
140         // Start a write-only server
141         tftpServer = startTftpServer(ServerMode.PUT_ONLY);
142         final String serverAddress = "localhost";
143         final int serverPort = tftpServer.getPort();
144 
145         tftpClient = new TFTPClient();
146         tftpClient.open();
147         tftpClient.setSoTimeout(Duration.ofMillis(2000));
148 
149         // we cannot read file
150         try (final OutputStream os = Files.newOutputStream(fileToWrite)) {
151             final String readFileName = fileToRead.getFileName().toString();
152             final IOException exception = assertThrows(IOException.class, () -> tftpClient.receiveFile(readFileName, TFTP.BINARY_MODE, os, serverAddress, serverPort));
153             assertEquals("Error code 4 received: Read not allowed by server.", exception.getMessage());
154         }
155 
156         // but we can write to it
157         try (final InputStream is = Files.newInputStream(fileToRead)) {
158             deleteFile(fileToWrite, false);
159             final String writeFileName = fileToWrite.getFileName().toString();
160             tftpClient.sendFile(writeFileName, TFTP.BINARY_MODE, is, serverAddress, serverPort);
161         }
162     }
163 
164     @Test
165     public void testWriteOutsideHome() throws IOException {
166         // Start a read/write server
167         tftpServer = startTftpServer(ServerMode.GET_AND_PUT);
168         final String serverAddress = "localhost";
169         final int serverPort = tftpServer.getPort();
170 
171         tftpClient = new TFTPClient();
172         tftpClient.open();
173 
174         try (final InputStream is = Files.newInputStream(fileToRead)) {
175             final IOException exception = assertThrows(IOException.class, () -> tftpClient.sendFile("../foo", TFTP.BINARY_MODE, is, serverAddress, serverPort));
176             assertEquals("Error code 0 received: Cannot access files outside of TFTP server root.", exception.getMessage());
177             assertFalse(Files.exists(serverDirectory.resolve("foo")), "file created when it should not have been");
178         }
179     }
180 
181     @Test
182     public void testWriteVerifyContents() throws IOException {
183         // Start a write-only server
184         tftpServer = startTftpServer(ServerMode.GET_AND_PUT);
185         final String serverAddress = "localhost";
186         final int serverPort = tftpServer.getPort();
187 
188         tftpClient = new TFTPClient();
189         tftpClient.open();
190         tftpClient.setSoTimeout(Duration.ofMillis(2000));
191 
192         // write some data to file
193         final byte[] content = "TFTP write test!".getBytes(StandardCharsets.UTF_8);
194         Files.write(fileToRead, content);
195 
196         // send file
197         try (final InputStream is = Files.newInputStream(fileToRead)) {
198             deleteFile(fileToWrite, false);
199             final String writeFileName = fileToWrite.getFileName().toString();
200             tftpClient.sendFile(writeFileName, TFTP.BINARY_MODE, is, serverAddress, serverPort);
201         }
202 
203         // then verify it contents
204         try (final OutputStream os = Files.newOutputStream(fileToWrite)) {
205             final String readFileName = fileToRead.getFileName().toString();
206             final int readBytes = tftpClient.receiveFile(readFileName, TFTP.BINARY_MODE, os, serverAddress, serverPort);
207             assertEquals(content.length, readBytes);
208         }
209     }
210 
211 }