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 (final IOException e) {
61                  if (!retry) {
62                      throw e;
63                  }
64                  System.err.println("Retrying delete failure: " + e);
65                  try {
66                      Thread.sleep(500);
67                  } catch (final InterruptedException e1) {
68                      fail(e);
69                  }
70                  Files.deleteIfExists(path);
71              }
72          }
73      }
74  
75      private static String getRandomFileName(final String suffix) {
76          return FILE_PREFIX + UUID.randomUUID() + suffix;
77      }
78  
79      private TFTPServer tftpServer;
80      private Path serverDirectory;
81  
82      private TFTPClient tftpClient;
83      private Path fileToRead;
84      private Path fileToWrite;
85  
86      @AfterEach
87      public void afterEach() throws IOException {
88          if (tftpClient != null) {
89              tftpClient.close();
90          }
91          if (tftpServer != null) {
92              tftpServer.close();
93          }
94          deleteFile(fileToRead, true);
95          deleteFile(fileToWrite, true);
96      }
97  
98      @BeforeEach
99      public void beforeEach() throws IOException {
100         serverDirectory = FileUtils.getTempDirectory().toPath();
101         fileToRead = createFileInDir(serverDirectory, getRandomFileName(".source.txt"));
102         fileToWrite = createFileInDir(serverDirectory, getRandomFileName(".out"));
103     }
104 
105     private TFTPServer startTftpServer(final ServerMode serverMode) throws IOException {
106         return new TFTPServer(serverDirectory.toFile(), serverDirectory.toFile(), SERVER_PORT, serverMode, null, null);
107     }
108 
109     @Test
110     public void testReadOnly() throws IOException {
111         // Start a read-only server
112         tftpServer = startTftpServer(ServerMode.GET_ONLY);
113         final String serverAddress = "localhost";
114         final int serverPort = tftpServer.getPort();
115 
116         // write some data to verify read
117         Files.write(fileToRead, "The quick brown fox jumps over the lazy dog".getBytes(StandardCharsets.UTF_8));
118         final long fileToReadContentLength = Files.size(fileToRead);
119 
120         tftpClient = new TFTPClient();
121         tftpClient.open();
122         tftpClient.setSoTimeout(Duration.ofMillis(2000));
123 
124         // we can read file
125         try (final OutputStream os = Files.newOutputStream(fileToWrite)) {
126             final String writeFileName = fileToRead.getFileName().toString();
127             final int bytesRead = tftpClient.receiveFile(writeFileName, TFTP.BINARY_MODE, os, serverAddress, serverPort);
128             assertEquals(fileToReadContentLength, bytesRead);
129         }
130 
131         // but we cannot write to it
132         try (final InputStream is = Files.newInputStream(fileToRead)) {
133             final String readFileName = fileToRead.getFileName().toString();
134             final IOException exception = assertThrows(IOException.class, () -> tftpClient.sendFile(readFileName, TFTP.BINARY_MODE, is, serverAddress, serverPort));
135             assertEquals("Error code 4 received: Write not allowed by server.", exception.getMessage());
136         }
137     }
138 
139     @Test
140     public void testWriteOnly() throws IOException {
141         // Start a write-only server
142         tftpServer = startTftpServer(ServerMode.PUT_ONLY);
143         final String serverAddress = "localhost";
144         final int serverPort = tftpServer.getPort();
145 
146         tftpClient = new TFTPClient();
147         tftpClient.open();
148         tftpClient.setSoTimeout(Duration.ofMillis(2000));
149 
150         // we cannot read file
151         try (final OutputStream os = Files.newOutputStream(fileToWrite)) {
152             final String readFileName = fileToRead.getFileName().toString();
153             final IOException exception = assertThrows(IOException.class, () -> tftpClient.receiveFile(readFileName, TFTP.BINARY_MODE, os, serverAddress, serverPort));
154             assertEquals("Error code 4 received: Read not allowed by server.", exception.getMessage());
155         }
156 
157         // but we can write to it
158         try (final InputStream is = Files.newInputStream(fileToRead)) {
159             deleteFile(fileToWrite, false);
160             final String writeFileName = fileToWrite.getFileName().toString();
161             tftpClient.sendFile(writeFileName, TFTP.BINARY_MODE, is, serverAddress, serverPort);
162         }
163     }
164 
165     @Test
166     public void testWriteOutsideHome() throws IOException {
167         // Start a read/write server
168         tftpServer = startTftpServer(ServerMode.GET_AND_PUT);
169         final String serverAddress = "localhost";
170         final int serverPort = tftpServer.getPort();
171 
172         tftpClient = new TFTPClient();
173         tftpClient.open();
174 
175         try (final InputStream is = Files.newInputStream(fileToRead)) {
176             final IOException exception = assertThrows(IOException.class, () -> tftpClient.sendFile("../foo", TFTP.BINARY_MODE, is, serverAddress, serverPort));
177             assertEquals("Error code 0 received: Cannot access files outside of TFTP server root.", exception.getMessage());
178             assertFalse(Files.exists(serverDirectory.resolve("foo")), "file created when it should not have been");
179         }
180     }
181 
182     @Test
183     public void testWriteVerifyContents() throws IOException {
184         // Start a write-only server
185         tftpServer = startTftpServer(ServerMode.GET_AND_PUT);
186         final String serverAddress = "localhost";
187         final int serverPort = tftpServer.getPort();
188 
189         tftpClient = new TFTPClient();
190         tftpClient.open();
191         tftpClient.setSoTimeout(Duration.ofMillis(2000));
192 
193         // write some data to file
194         final byte[] content = "TFTP write test!".getBytes(StandardCharsets.UTF_8);
195         Files.write(fileToRead, content);
196 
197         // send file
198         try (final InputStream is = Files.newInputStream(fileToRead)) {
199             deleteFile(fileToWrite, false);
200             final String writeFileName = fileToWrite.getFileName().toString();
201             tftpClient.sendFile(writeFileName, TFTP.BINARY_MODE, is, serverAddress, serverPort);
202         }
203 
204         // then verify it contents
205         try (final OutputStream os = Files.newOutputStream(fileToWrite)) {
206             final String readFileName = fileToRead.getFileName().toString();
207             final int readBytes = tftpClient.receiveFile(readFileName, TFTP.BINARY_MODE, os, serverAddress, serverPort);
208             assertEquals(content.length, readBytes);
209         }
210     }
211 
212 }