Unit testing FTP in Java (JSch & MockFtpServer) - java

I'm using JCraft's JSch library for as a FTP client for my program, it's working fine. But I had difficulties creating a mock (MockFtpServer) for unit testing.
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockftpserver.fake.FakeFtpServer;
import org.mockftpserver.fake.UserAccount;
import org.mockftpserver.fake.filesystem.FileEntry;
import org.mockftpserver.fake.filesystem.FileSystem;
import org.mockftpserver.fake.filesystem.UnixFakeFileSystem;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Properties;
#RunWith(SpringRunner.class)
#SpringBootTest
public class SFTPManagerTest {
static FakeFtpServer fakeFtpServer;
#BeforeClass
public static void startFTP() throws Exception {
fakeFtpServer = new FakeFtpServer();
fakeFtpServer.setServerControlPort(9999);
FileSystem fileSystem = new UnixFakeFileSystem();
fileSystem.add(new FileEntry("/data/JFS_HCID_APPLICATION_STATUS_20190109.TXT", "<schema/>"));
fakeFtpServer.setFileSystem(fileSystem);
UserAccount userAccount = new UserAccount("user", "password", "/");
fakeFtpServer.addUserAccount(userAccount);
fakeFtpServer.start();
}
#Test
public void getFileListing() {
try {
JSch jsch = new JSch();
Session jschSession = jsch.getSession("user", "localhost", 9999);
jschSession.setPassword("password");
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
jschSession.setConfig(config);
jschSession.connect();
Channel jschChannel = jschSession.openChannel("ftp");
jschChannel.connect();
ChannelSftp channel = (ChannelSftp) jschChannel;
Vector v = channel.ls("/data");
Assert.assertNotNull(v);
} catch (Exception ex) {
ex.printStackTrace();
}
}
#AfterClass
public static void stopFTP() {
fakeFtpServer.stop();
}
}
Dependencies:
com.jcraft:jsch:0.1.55
org.mockftpserver:MockFtpServer:2.7.1
Output: it always stuck/stall during executing test method, not finishing. When stuck/stalling I could access the MockFtpServer with WinSCP or FTP CLI. From the log it seems the command from JSch not recognized.
2019-02-18 17:54:15,996 INFO [Thread-0] org.mockftpserver.core.server.AbstractFtpServer: Connection accepted from host /127.0.0.1
2019-02-18 17:54:16,002 INFO [Thread-6] org.mockftpserver.core.command.AbstractTrackingCommandHandler: Sending reply [220 Service ready for new user. (MockFtpServer 2.7.1; see http://mockftpserver.sourceforge.net)]
2019-02-18 17:54:16,004 INFO [Thread-6] org.mockftpserver.core.session.DefaultSession: Received command: [SSH-2.0-JSCH-0.1.54]
2019-02-18 17:54:16,004 WARN [Thread-6] org.mockftpserver.core.command.UnsupportedCommandHandler: No CommandHandler is defined for command [SSH-2.0-JSCH-0.1.54]
2019-02-18 17:54:16,005 INFO [Thread-6] org.mockftpserver.core.command.AbstractTrackingCommandHandler: Sending reply [502 Command not implemented: SSH-2.0-JSCH-0.1.54.]
Where did i go wrong?

JSch is an SSH/SFTP library. You cannot use an FTP server to test it. You need to use an SFTP server. FTP and SFTP are two completely unrelated and different protocols.

Related

Connecting to ES with Spring Data Elasticsearch (reactive) gives error host not reachable

I'm running on an aws-elasticsearch (with OpenSearch 1.1.x) service and im trying to connect with it from a spring application using spring-data-elasticsearch, according to the doc i configured the bean as it says.
on my local i used a ssh tunnel from my aws account.
i used this command:
ssh -4 -i my-creds.pem ec2-user#xxxx.xxxx.xxxx.xxxx -N -L 9200:vpc-my-custom-domain-etc.us-east-1.es.amazonaws.com:443
so i can connect with OpenSearch dashboard over localhost in my browser through port 9200.
Using the OpenSearch RestHighLevelClient from OpenSearch and disabling the ssl i can connect and it works just fine here the config with OS RHLC:
import org.apache.http.HttpHost;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
public class OSSCLientWorks{
private static final Logger log = LoggerFactory.getLogger(ClientAutoWrapper.class);
public void request(String indexName, Map<String, Object> doc) throws IOException {
//Create a client.
RestClientBuilder builder = RestClient.builder(new HttpHost("localhost", 9200, "https"))
.setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder
//.addInterceptorFirst(interceptor) //-> for AwsRequestInterceptor due to some struggles i had, not necessary to work with localhost
.setSSLHostnameVerifier((hostname, session) -> true));
try (RestHighLevelClient hlClient = new RestHighLevelClient(builder)) {
CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
var createIndexResp = hlClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
log.info("Create index resp {}", createIndexResp);
IndexRequest indexRequest = new IndexRequest(createIndexResp.index())
.id(String.valueOf(doc.get("id")))
.source(doc);
var response = hlClient.index(indexRequest, RequestOptions.DEFAULT);
var resp = response.toString();
log.info("response is {}", json);
}
}
}
, but when i try with spring and its reactive client i get this error:
reactor.core.Exceptions$ErrorCallbackNotImplemented: org.springframework.data.elasticsearch.client.NoReachableHostException: Host 'localhost:9200' not reachable. Cluster state is offline.
Caused by: org.springframework.data.elasticsearch.client.NoReachableHostException: Host 'localhost:9200' not reachable. Cluster state is offline.
here is the config i used to work with spring-data-elasticsearch:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.client.reactive.ReactiveRestClients;
import org.springframework.data.elasticsearch.config.AbstractReactiveElasticsearchConfiguration;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchTemplate;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
#Configuration
#EnableReactiveElasticsearchRepositories(basePackages = {"com.elastic.repo"})
public class ElasticRestHighLevelClientConfig extends AbstractReactiveElasticsearchConfiguration {
#Override
#Bean
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return ReactiveRestClients.create(clientConfiguration);
}
#Bean
public ReactiveElasticsearchOperations elasticsearchOperations(ReactiveElasticsearchClient reactiveElasticsearchClient) {
return new ReactiveElasticsearchTemplate(reactiveElasticsearchClient);
}
}
i also tried some solutions other people posted here on SO and Github, but the problem persists, does anybody have a workaround for this? what am i doing wrong?
here i did a demo for the trouble
Thank you very much in advance!
EDIT: clarity
You have to configure to use SSL for the reactive client with one of the usingSsl()methods:
#Override
#Bean
public ReactiveElasticsearchClient reactiveElasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.usingSsl() // <--
.build();
return ReactiveRestClients.create(clientConfiguration);
}
NoReachableHostException is just a generic error they throw when lookupActiveHost(HostProvider interface) fails.
You should debug what happens before - for you it's probably here:
#Override
public Mono clusterInfo() {
return createWebClient(endpoint) //
.head().uri("/") //
.exchangeToMono(it -> {
if (it.statusCode().isError()) {
state = ElasticsearchHost.offline(endpoint);
} else {
state = ElasticsearchHost.online(endpoint);
}
return Mono.just(state);
}).onErrorResume(throwable -> {
state = ElasticsearchHost.offline(endpoint);
clientProvider.getErrorListener().accept(throwable);
return Mono.just(state);
}).map(elasticsearchHost -> new ClusterInformation(Collections.singleton(elasticsearchHost)));
}
see what is the real exception on error resume.
I bet you will get SSL Handshake Exception, you can fix it in the clientConfiguration with .usingSsl({SSL CONTEXT HERE})
You can create insecure context like this(convert to java if needed):
SSLContext.getInstance("TLS")
.apply { init(null, InsecureTrustManagerFactory.INSTANCE.trustManagers, SecureRandom()) }

How to send an asynchrone email with spring boot

I'm using spring boot 1.5. I'm trying to send async mail when user create an alert but it doesnt work and it doesn't display any error even when I'm debugging but in vain, so here's what I obtain. My question is how to test if my implementation of mail is correct or not, because I don't have any controller of it. Please help. Thank you for any suggestions
You can send asynchronous emails easily by using #Async annotation on your mail sending method. To enable Async support in your spring boot application use #EnableAsync like this:
#SpringBootApplication
#EnableAsync
public class SpringTestAppApplication {
public static void main(String[] args) {
SpringApplication.run(SpringTestAppApplication.class, args);
}
}
Create a MailSender service and annotate mail sender method with #Async like this:
#Service
public class MailSenderService {
#Async
public void sendMail(....) {
// your code
}
}
Autowire the above service in your service and then call sendMessage method, example:
#Service
public class UserService {
#Autowired
private MailSenderService mailSenderService;
User exampleMethod() {
..
..
mailSenderService.sendMail(...);
}
}
You can check this link for more information: https://www.baeldung.com/spring-async
UPDATE:
If you want to use java mail api . Then you can take reference of below code:
import java.util.Properties;
import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.activation.FileDataSource;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
#Service
public class MailService {
#Autowired
EnvConfiguration envConfiguration;
private static final Logger LOGGER = LoggerFactory.getLogger(MailService.class);
#Async
public void sendMail(String to, String subject, String htmlText, String fileName) {
Properties props = System.getProperties();
props.put("mail.transport.protocol", "smtps");
props.put("mail.smtp.port", envConfiguration.getSMTPPort());
props.put("mail.smtp.auth", envConfiguration.smtpAuthorized());
props.put("mail.smtp.starttls.enable", envConfiguration.isStarTlsEnabled());
props.put("mail.smtp.starttls.required", envConfiguration.isStarTlsRequired());
Session session = Session.getDefaultInstance(props);
Transport transport = null;
try {
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(envConfiguration.getEmailFrom().replaceAll("\"", "")));
msg.setRecipient(Message.RecipientType.TO, new InternetAddress(to));
msg.setSubject(subject);
MimeBodyPart messageBodyPart = new MimeBodyPart();
messageBodyPart.setContent(htmlText, "text/html");
transport = session.getTransport();
transport.connect(envConfiguration.getEmailHost(), envConfiguration.getEmailUserName(),
envConfiguration.getEmailPassword());
transport.sendMessage(msg, msg.getAllRecipients());
LOGGER.info("Mail send successfully");
} catch (MessagingException e) {
LOGGER.error("Unable to send email trace" + e);
} finally {
try {
transport.close();
} catch (MessagingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

Connect a server through another server in putty/jump server using java

I need to connect to another server after i logged into putty server using java code and I need to download files from second server after I connected. Do anyone have the code.
Once the putty server screen appears, I enter username and password then i pass the command "ssh servername" then i need to enter yes for the question appears.
Once the above done, I have to navigate to different folders and download the files to Server1.
I used the below code to connect to server1 and it connected, After that I am not sure of jumping to server 2.
I am getting the error as below when i am trying a remote server through another server. The error and code I have entered below
Error:
Exception in thread "main" com.jcraft.jsch.JSchException: connection is closed by foreign host
at com.jcraft.jsch.Session.connect(Session.java:236)
at com.jcraft.jsch.Session.connect(Session.java:150)
at putty.putty.main(putty.java:42)
Code I used:
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
import org.apache.commons.io.IOUtils;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
public class putty {
public static String user = "username";
public static String host = "server1.server.net";
public static String password = "Password";
public static String secondpassword = "Password";
public static String tunnelRemoteHost = "server2.server.net";
public static void main(String[] args) throws IOException, JSchException, SftpException {
StringBuilder outputBuffer = new StringBuilder();
int port = 22;
JSch jsch = new JSch();
Session session = jsch.getSession(user, host, port);
session.setPassword(password);
session.setConfig("StrictHostKeyChecking", "no");
session.connect();
int forwardPort = 2222;
session.setPortForwardingL(forwardPort, tunnelRemoteHost, 22);
Session secondSession = jsch.getSession(user, "localhost", forwardPort);
secondSession.setPassword(secondpassword);
secondSession.setConfig("StrictHostKeyChecking", "no");
secondSession.connect();
session.openChannel("sftp");
// now we're connected to the secondary system
Channel channel = secondSession.openChannel("sftp");
channel.connect();
ChannelSftp channelSftp = (ChannelSftp)channel;
channel.disconnect();
session.disconnect();
System.out.print(outputBuffer.toString());
}
}
I am using latest jsch jar 0.1.55. Can someone look into and let me what I am missing here.
Try to update jsch to 0.1.55 https://mvnrepository.com/artifact/com.jcraft/jsch/0.1.55
Your current version 0.1.42 is almost 10 years old. It may not change anything or may help.
I am able to achieve what i wanted through Windows Commandline with plink command

Vertx 3.6.3: Unable to launch HTTPS server with PFX option

I am using Vertx 3.6.3. I am trying to run an HTTPS server verticle, but unfortunately, verticle is not getting deployed. Could you please let me know where I am doing it wrong?
Here is my verticle:
HTTPSVerticle:
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Future;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.PfxOptions;
public class HTTPSVerticle extends AbstractVerticle {
#Override
public void start(Future<Void> httpsServerStarted) throws Exception {
int port = config().getJsonObject("http", new JsonObject()).getInteger("port", 8000);
boolean useSsl = config().getJsonObject("http", new JsonObject()).getBoolean("useSsl", false);
String sslCertPath = config().getJsonObject("http", new JsonObject()).getString("sslCertPath", "");
String sslCertPassword = config().getJsonObject("http", new JsonObject()).getString("sslCertPassword", "");
HttpServerOptions httpServerOptions = new HttpServerOptions();
System.out.println(useSsl);
if (useSsl)
httpServerOptions
.setSsl(true)
//.setClientAuth(ClientAuth.REQUIRED)
.setPfxTrustOptions(
new PfxOptions().setPath(sslCertPath).setPassword(sslCertPassword)
);
vertx.createHttpServer(httpServerOptions).requestHandler(httpReq -> {
httpReq.response().end("Hello encrypted world");
}).listen(port, fut -> {
if (fut.succeeded()) {
System.out.println("Verticle now listening on port: " + port);
httpsServerStarted.complete();
}
else {
httpsServerStarted.fail(fut.cause());
System.out.println("Error while starting HTTP server");
}
});
}
}
Here is my test case:
TestHTTPSVerticle:
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.unit.Async;
import io.vertx.ext.unit.TestContext;
import io.vertx.ext.unit.junit.VertxUnitRunner;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
#RunWith(VertxUnitRunner.class)
public class TestHTTPSVerticle {
private static Vertx vertx;
#BeforeClass
public static void setUp(TestContext context) {
DeploymentOptions opts = new DeploymentOptions()
.setConfig(new JsonObject().put("http", new JsonObject()
.put("useSsl", true)
.put("sslCertPath", "test.pfx")
.put("sslCertPassword", "abcd")));
vertx = Vertx.vertx();
vertx.deployVerticle(HTTPSVerticle.class.getName(), opts, context.asyncAssertSuccess());
}
#AfterClass
public static void tearDown(TestContext context) {
vertx.close(context.asyncAssertSuccess());
}
#Test
public void testHttpsServerMessage(TestContext context) {
Async async = context.async();
System.out.println("Connecting to server...");
vertx.createHttpClient().get(8000, "localhost", "/loremipsum", respHandler -> respHandler.bodyHandler(respBody -> {
System.out.println(respBody);
context.assertTrue(respBody.toString().equals("Hello encrypted world"));
async.complete();
})).end();
}
}
Its not letting me submit it without elaborating, so redundant elaboration follows:
I am using vertx config mechanism to fetch port, useSsl, sslCertPath and sslCertPassword
I am using HttpServerOptions for configuring SSL settings for http server
When server is started successfully, it should print Verticle now listening on port: 8000
In case, server fails to start, it should print Error while starting HTTP server
But, It never invokes listen's handler with AsyncResult.

Running programs using JSch in the background

I am trying to connect to a remote machine using JSch. All my systems are running on ubuntu including my production machines. I am able to successfully connect and run the jar file. But when i close the window the program automatically shuts down. What i'm trying to achieve now is to run this jar file in the background.
I have appended the sudo command with '&' and the server doesn't even start in this case. I have tried using a shell channel but here to the server doesn't startup or closes on stopping my program.
I've also tried the nohup and disown commands to run in the background. When i get the process state using ps ux the STAT column shows T which according to this means the process has stopped. I've been stuck here since 3 days.
Is there anything wrong with the code i've written? why is the server unable to run in the background?
setPty(true) -> what does this do?
is there a way to stop the password from printing to the console when i enter it?
I have been testing this code in my local network. while connecting to our production servers we pass a permissions file along like this: sudo ssh -i permissionFilePath serverName#ipAddress. Will i have to change my approach again for passing this permission file to gain access to production systems?
i am using the following version of JSch:
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.53</version>
</dependency>
I am using the following code:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
public class App extends Application {
#Override
public void start(Stage primaryStage) {
Button btn = new Button();
btn.setText("click");
btn.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent arg0) {
try {
JSch objJSch = new JSch();
Session objSession;
objSession = objJSch.getSession("userName", "host");
objSession.setPassword("password");
objSession.setConfig("StrictHostKeyChecking", "no");
System.out.println("Establishing Connection...");
objSession.connect();
System.out.println("Connection established.");
Channel objChannel = objSession.openChannel("exec");
((ChannelExec) objChannel).setCommand("cd {{path to my jar file}};sudo java -jar start.jar");
((ChannelExec) objChannel).setErrStream(System.err);
((ChannelExec) objChannel).setPty(true);//why should i use this??
InputStream in = objChannel.getInputStream();
OutputStream out = objChannel.getOutputStream();
objChannel.connect();
out.write(("password" + "\n").getBytes());
out.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String s;
while ((s = br.readLine()) != null) {
System.out.println(s);
}
while (!objChannel.isClosed()) {
System.out.println("Waiting to close channel");
}
System.out.println("disconnecting...");
objChannel.disconnect();
objSession.disconnect();
System.out.println("disconnected.");
} catch (JSchException e) {
if (e.getMessage().equalsIgnoreCase("Auth fail"))
System.out.println("Authorization failed...");
else
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
});
StackPane root = new StackPane();
root.getChildren().add(btn);
Scene scene = new Scene(root, 300, 250);
primaryStage.setTitle("");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Your Code
((ChannelExec) objChannel).setCommand("cd {{path to my jar file}};sudo java -jar start.jar");
Modify it to :
((ChannelExec) objChannel).setCommand("cd {{path to my jar file}};sudo java -jar start.jar > /dev/null 2>&1 &");
Which will resolve your issue.

Categories