Skip to content

Commit d29ae24

Browse files
committed
GH-891: Add a test against ProFTPd
Use docker container instantlinux/proftpd to run ProFTPd with SFTP enabled (password auth since pubkey auth would be tedious to set up, but that's OK for this test). Configure the container to send ProFTPd logs to stderr, not to some files inside the container. On stderr the test container's log consumer will pick it up. The test sets a low IGNORE_MESSAGE_FREQUENCY (every 20th message), uploads a file, downloads it again, and compares the contents. The test fails without the fix from commit e7f66e7 with the server disconnecting midway through the upload and shows the expected server-side log line "message format error: unable to read 2594667651 bytes of string data (buflen = 20)" (or similar numbers). With the fix from commit e7f66e7 the test succeeds and the server-side log shows several lines "client sent SSH_MSG_IGNORE message (X bytes)" instead.
1 parent e7f66e7 commit d29ae24

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.sshd.sftp.client;
20+
21+
import java.io.ByteArrayInputStream;
22+
import java.io.ByteArrayOutputStream;
23+
import java.io.InputStream;
24+
import java.io.OutputStream;
25+
26+
import org.apache.sshd.client.SshClient;
27+
import org.apache.sshd.client.session.ClientSession;
28+
import org.apache.sshd.common.Factory;
29+
import org.apache.sshd.common.random.Random;
30+
import org.apache.sshd.common.util.io.IoUtils;
31+
import org.apache.sshd.core.CoreModuleProperties;
32+
import org.apache.sshd.util.test.JUnitTestSupport;
33+
import org.junit.jupiter.api.AfterEach;
34+
import org.junit.jupiter.api.BeforeEach;
35+
import org.junit.jupiter.api.Test;
36+
import org.slf4j.Logger;
37+
import org.slf4j.LoggerFactory;
38+
import org.testcontainers.containers.GenericContainer;
39+
import org.testcontainers.containers.output.Slf4jLogConsumer;
40+
import org.testcontainers.images.builder.ImageFromDockerfile;
41+
import org.testcontainers.junit.jupiter.Container;
42+
import org.testcontainers.junit.jupiter.Testcontainers;
43+
import org.testcontainers.utility.MountableFile;
44+
45+
@Testcontainers(disabledWithoutDocker = true)
46+
class ProFtpdTest extends JUnitTestSupport {
47+
48+
private static final Logger LOG = LoggerFactory.getLogger(ProFtpdTest.class);
49+
50+
private static final String RESOURCES = "/" + ProFtpdTest.class.getPackage().getName().replace('.', '/');
51+
52+
@Container
53+
static GenericContainer<?> server = new GenericContainer<>(
54+
new ImageFromDockerfile().withDockerfileFromBuilder(builder -> builder.from("instantlinux/proftpd:latest") //
55+
// Get SFTP logs on stderr
56+
.run("sed -i -e 's:^WtmpLog:AllowLogSymLinks on\\nSystemLog /dev/stderr\\nSFTPLog /dev/stderr\\nWtmpLog:' /etc/proftpd/proftpd.conf") //
57+
.build())) //
58+
.withEnv("PASV_ADDRESS", "127.0.0.1") //
59+
.withEnv("FTPUSER_NAME", "test") //
60+
.withEnv("SFTP_ENABLE", "on") //
61+
// Set it up for password auth
62+
.withCopyFileToContainer(MountableFile.forClasspathResource(RESOURCES + "/proftpd.pwd"),
63+
"/run/secrets/ftp-user-password-secret")
64+
.withExposedPorts(2222) //
65+
.withLogConsumer(new Slf4jLogConsumer(LOG));
66+
67+
private SshClient client;
68+
69+
@BeforeEach
70+
void setUp() throws Exception {
71+
client = SshClient.setUpDefaultClient();
72+
client.setServerKeyVerifier((s, a, k) -> true);
73+
client.start();
74+
}
75+
76+
@AfterEach
77+
void teardown() {
78+
if (client != null) {
79+
client.stop();
80+
}
81+
}
82+
83+
@Test // GH-891
84+
void ignoreMesssage() throws Exception {
85+
CoreModuleProperties.IGNORE_MESSAGE_FREQUENCY.set(client, 20L);
86+
CoreModuleProperties.IGNORE_MESSAGE_VARIANCE.set(client, 0);
87+
CoreModuleProperties.IGNORE_MESSAGE_SIZE.set(client, 16);
88+
byte[] expected = new byte[8 * 1024 * 1024];
89+
90+
Factory<? extends Random> factory = client.getRandomFactory();
91+
Random rnd = factory.create();
92+
rnd.fill(expected);
93+
94+
try (ClientSession sshSession = client.connect("test", server.getHost(), server.getMappedPort(2222)).verify()
95+
.getClientSession()) {
96+
sshSession.addPasswordIdentity("test");
97+
sshSession.auth().verify(2000);
98+
try (SftpClient sftp = SftpClientFactory.instance().createSftpClient(sshSession)) {
99+
// Copy some data onto the server
100+
try (InputStream in = new ByteArrayInputStream(expected);
101+
OutputStream out = sftp.write("/home/test/file.bin", SftpClient.OpenMode.Create,
102+
SftpClient.OpenMode.Write)) {
103+
IoUtils.copy(in, out);
104+
}
105+
byte[] actual;
106+
try (InputStream in = sftp.read("/home/test/file.bin");
107+
ByteArrayOutputStream buf = new ByteArrayOutputStream(expected.length)) {
108+
byte[] data = new byte[4096];
109+
for (int n = 0; n >= 0;) {
110+
n = in.read(data, 0, data.length);
111+
if (n > 0) {
112+
buf.write(data, 0, n);
113+
}
114+
}
115+
actual = buf.toByteArray();
116+
}
117+
assertArrayEquals(expected, actual);
118+
}
119+
}
120+
}
121+
122+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
$6$Q7V2n3q2GRVv5YeQ$/AhLu07H76Asojy7bxGXMY1caKLAbp5Vt82LOZYMkD/8uDzyMAEXwk0c1Bdz1DkBsk2Vh/9SF130mOPavRGMo.

0 commit comments

Comments
 (0)