JSch - How to let user confirm host fingerprint? - java

In an Android app, I am attempting to connect to an SSH server using the JSch library. The remote server is specified by the user, so I don't know the remote fingerprint in advance. At the same time I don't want to set StrictHostKeyChecking to no as I see in so many examples.
I'd like to get the remote server fingerprint, show it to the user for acceptance. Is this possible either with JSch or regular Java, perhaps with sockets?
Here's an example you can try, just paste it in the onCreate of an Android activity:
new Thread(new Runnable() {
#Override
public void run() {
com.jcraft.jsch.Session session;
JSch jsch;
try {
jsch = new JSch();
jsch.setLogger(new MyLogger());
session = jsch.getSession("git", "github.com", 22);
session.setPassword("hunter2");
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "yes");
session.setConfig(prop);
//**Get a host key and show it to the user**
session.connect(); // reject HostKey: github.com
}
catch (Exception e){
LOG.error("Could not JSCH", e);
}
}
}).start();

OK I've found a way to do this. It may not be the best way but it is a way. Using the UserInfo.promptYesNo required looping at the expense of CPU while waiting for user response or with the overhead of an Executor/FutureTask/BlockingQueue. Instead the async thread which executes the connection (since network tasks cannot occur on UI thread) is more conducive to doing this twice - once to 'break' and get the user to accept, second to succeed. I guess this is the 'Android way'. For this, the hostkey needs storing somewhere. Suppose I store it in Android's PreferenceManager, then to start with grab the key from there, defaulting to empty if not available
String keystring = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).getString("target_hostkey","");
if(!Strings.isNullOrEmpty(keystring)){
byte[] key = Base64.decode ( keystring, Base64.DEFAULT );
jsch.getHostKeyRepository().add(new HostKey("github.com", key ), null);
}
Next, proceed as usual to connect to the server
session = jsch.getSession("git", "github.com", 22);
session.setPassword("hunter2");
Properties prop = new Properties();
prop.put("StrictHostKeyChecking", "yes");
session.setConfig(prop);
session.connect();
But this time, catch the JSchException. In there, the session has a HostKey available.
catch(final JSchException jex){
LOG.debug(session.getHostKey().getKey());
final com.jcraft.jsch.Session finalSession = session;
runOnUiThread(new Runnable() {
#Override
public void run() {
new MaterialDialog.Builder(MyActivity.this)
.title("Accept this host with fingerprint?")
.negativeText(R.string.cancel)
.positiveText(R.string.ok)
.content(finalSession.getHostKey().getFingerPrint(jsch))
.onPositive(new MaterialDialog.SingleButtonCallback() {
#Override
public void onClick(#NonNull MaterialDialog dialog, #NonNull DialogAction which) {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).edit().putString("target_hostkey", finalSession.getHostKey().getKey()).apply();
}
}).show();
}
});
}
After this, it's a matter of re-invoking the Thread or AsyncTask but this time the hostkey is added to the hostkey repository for JSch.

Two possibilities:
When StrictHostKeyChecking is set to ask, JSch calls UserInfo.promptYesNo with a confirmation prompt. Implement the UserInfo interface to display the confirmation to the user. Disadvantage is that you cannot customize the message in any way (of course, unless you try to parse it, relying on a hard-coded template).
The message is like:
WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that the -key_type- host key has just been changed.
The fingerprint for the -key_type- key sent by the remote host is
-key_fprint-
Please contact your system administrator.
Add correct host key in -file- to get rid of this message.
For an example implementation, see the official JSch KnownHosts.java example.
Even before the above, JSch calls HostKeyRepository.check, passing it hostname and the key.
You can implement that interface/method, to do any prompt you like.
Check Session.checkHost implementation.

Related

how to search for network in android studio while using socket?

public void connect() {
final String msg = "";
Thread thread = new Thread(new Runnable() {
#Override
public void run() {
try {
s = new Socket("192.168.1.3", 1337);
textView.setText("Connection acquired");
out = s.getOutputStream();
output = new PrintWriter(out);
output.println(msg);
textView.setText("message sent : " + msg.length());
output.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
I am using the above code to connect to desired IP [192.168.1.3]... I have created another program at the other end... The problem is that the IP sometimes changes to 192.168.1.4 or 192.168.1.6... and when I use another network it changes to 192.168.43.2... now due to this I have to go every time into the code and change it manually....
Is there any function or any other thing that might search for IPs available then check if desired port exists and then connect... Thanks in advance
That's why DNS names exist. If you use an IP address, you have to use that exact address. If you use DNS, you do a name->IP lookup. Get a dynamic DNS provider and use that to give your server a name.
You really do not want to start port scanning to find open ports. You will be treated as an attacker and kicked off the network, because you really would be doing something attackers do.

Connections with JavaMail

I am working with JavaMail for my plugin, within this plugin I am trying to send an email but my issue lies around the client. The client can't handle the plugin connecting to the email server and sending an email it either crashes the entire server or the client gets kicked out. My fix for this was instead of constantly connecting to the email server and sending an email why not simply keep one connection open when the plugin starts and grab that connection when I am wanting to send an email hopefully this will help in allowing the client and server to stay stable without any crashes. If anyone can help me I am just curious on how I can go about keeping a a single connection open and grabbing it when it is needed and then closing it when the plugin gets disabled.
What I have tried:
private Session session;
public void connect() {
String provider = plugin.getConfig().getString("EmailProvider");
String email = plugin.getConfig().getString("Email");
String password = plugin.getConfig().getString("Password");
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", provider);
props.put("mail.smtp.port", "25");
session = Session.getInstance(props,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(email, password);
}
});
}
private boolean checkSession() {
try {
if (session != null) {
return true;
} else {
return false;
}
}
return false;
}
public void sendEmail(String to, String from, String subject, String text) {
if (!checkSession()) {
connect();
System.out.println("connecting");
}
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(to));
message.setSubject(subject);
message.setText(text);
Transport.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
The simple answer is, you can't.
There's no way to force a connection to remain open. Connections can be closed for all sorts of reasons and your program needs to be prepared for that.
You can, however, cache an open connection and reuse it as long as it's still connected. But without knowing what your plugin plugs in to, it's hard to describe the best strategy for that.
Note also that mail servers really don't want you to keep a connection open if you're not using it, which is why they'll close it out from under you if you leave it open but idle for too long. And artificially keeping the connection active won't win you any points with the mail server either.
If your mail server crashes when you connect to it, it's probably time to get a new mail server.

Whats the best way to notify admin about new exceptions of the Java application?

My question is that whats the best way to keep track of the exceptions for the administrator of the application. (Notify administrator of the thrown exceptions for maintenance purposes).
To users of the system, I believe should catch the exceptions and show an appropriate error message.
To admin of the system, I suppose, best method is to have a messaging system to send details of each exception as a message to the receiver. Once receiver received a new error message persists it in the database or pings the admin an email with details of the exception.
try{
....
}
catch(Exception e){
//what to do here? how to notify admin?
}
I'd suggest using log4j, configured with an SMTPAppender listening to fatal logs. Then, just log a fatal level message (containing any useful information you can get) for any unhandled exception reaching your global try/catch block.
See also : What is the proper way to configure SMTPAppender in log4j?
Enterprise solution:
Use SL4J and save all messages to your logs.
Use MDC to add tags to your log messages. Have these tags describe who should be notified and the nature of the error:
2014-05-24 [SystemCAD][NOTIFY=ADMIN], [ACCOUNTID=123], [SEVERITY=SEVERE], [MESSAGE="Cannot contact Google.com"]
2014-05-24 [SystemCAD][NOTIFY=USER], [ACCOUNTID=123], [SEVERITY=SEVERE], [MESSAGE="Could not save document to Google. Support has been notified."]
Get Splunk or some product similar to index all your logs for easy searching and to create events which can be used to notify your admins. Use PagerDutty to notify your admins and create escalation, avoid duplicates, create triggers, etc.
First, do not try to solve the notification problem in the application itself.
The recommended approach is to catch the exception at an appropriate point in the application and generate a log event that captures the details (including the exception) of the failure. The primary logging should be done using a standard logging system. There are a number of viable options (e.g. java.util.logging, log4j, logback, log4j2, slf4j), each with pro's and con's, but the most important thing is to not attempt to "roll your own".
That's the easy part.
The hard part is figuring out how to get the notification from the logging system to the admin in a way that is appropriate. There are many things that need to be considered:
The admin does not be woken up at 2am by a page reporting an over-temperature in the office water cooler.
The admin does not want 50 SMS message all reporting the same problem. The system needs to be able to filter out duplicates.
The admin needs to be able to tell the system to "shut up" about a certain problem / issue.
The system needs to recognize that certain events are more important than others, and that business hours versus after hours affects prioritization.
What is the most appropriate way to notify the admin? Email? SMS? Pager?
Escalation - what if the primary (on-call) admin does not respond to the notification?
The system also needs to be integrated with other monitoring; e.g. checking service availability, network connectivity, file system levels, CPU / load average measures, checking that important events DO happen.
All of this needs to be configurable, independent of the application that generated the event in the first place.
Ideally, you need integration with operational issue tracking system ... to help the admin relate the event to previous problems, etc.
This is a really big problem space. Fortunately, there are products out there that do this kind of thing. Too many to list here.
(IMO, it doesn't make sense to recommend a solution for you. We don't know your organization's requirements. This is something that needs to be sorted out in conjunction with operational staff & management.)
I have done notification on exception in my application using spring AOP.
For example
#Aspect
public class ExceptionAspect {
#AfterThrowing(
pointcut = "execution(* com.suren.customer.bo.CustomerBo.addCustomerThrowException(..))",
throwing= "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
// Notify admin in email
sendEmail(joinPoint,error);
}
}
Common AspectJ annotations :
#Before – Run before the method execution
#After – Run after the method returned a result
#AfterReturning – Run after the method returned a result, intercept the returned result as well.
#AfterThrowing – Run after the method throws an exception
#Around – Run around the method execution, combine all three advices above.
When you design an application you need to consider two types of exceptions
User defined business exception
Unexpected system exception
User defined exceptions
User defined exceptions are used to pass negative conditions from one layer to another (service to web). For example in a banking application, if there is no balance in an account and if you try to withdraw money, WithdrawService might throw NoBalanceException. The web layer would catch this exception and display appropriate message to the user.
These type of exceptions are of no interest to the administrators and no alert is required. You may simply log it as info.
Unexpected system exception
Unexpected system exceptions are exceptions like database connectivity or JMS conncetivity or NullPointException or invalid message received from external system. Basically any unexpected (non-business) exceptions are classified as system exceptions.
According to Joshua Bloch in Effective Java, it is advisable not to catch system exception as you might do more harm than good. Instead allow it to propagate to the highest level (web layer).
In my applications, I provide a global exception handler (supported by Spring / Struts 2) on the web layer and send a detailed email to the ops team including the exception stack trace and redirect the request to a standard error page which says something like "Unexpected internal error occurred. Please try again".
Using this option is more secured as it will not expose the ugly exception stack trace to the user in any situation.
Struts2 reference:
http://struts.apache.org/release/2.3.x/docs/exception-handling.html
Consider using standard logging (like log4j) and using appender suited for you - either SMTP mentioned before, or custom one. There exists solutions called logging servers - they provide high flexibility in terms of notifications, filtering, storing, processing etc. Good place to start reading and investigating are Scribe and Flume. A great discussion on this subject may be found here.
There are also some cloud solutions available, from automated ones like Sentry through LogDigger (your own installation) to more low-level setups like Amazon SQS.
You should use a logging facility to log every exception in a file system so if Admin want they can view it through file-system.
ErrorUtil
public class ErrorLogUtil {
public static File createErrorFile(String fileName, String productName,
String regionName) {
File fileErrorLogs = new File("Error Logs");
if (!fileErrorLogs.isDirectory()) {
fileErrorLogs.mkdir();
}
File fileProductName = new File(fileErrorLogs, productName);
if (!fileProductName.isDirectory()) {
fileProductName.mkdir();
}
File fileDate = null;
if (regionName != null && regionName.trim().length() != 0) {
File fileRegionName = new File(fileProductName, regionName);
if (!fileRegionName.isDirectory()) {
fileRegionName.mkdir();
}
fileDate = new File(fileRegionName, new SimpleDateFormat(
"dd-MM-yyyy").format(new Date()));
if (!fileDate.isDirectory()) {
fileDate.mkdir();
}
} else {
fileDate = new File(fileProductName, new SimpleDateFormat(
"dd-MM-yyyy").format(new Date()));
if (!fileDate.isDirectory()) {
fileDate.mkdir();
}
}
File errorFile = new File(fileDate, fileName + "-errors.txt");
try {
if (!errorFile.exists()) {
errorFile.createNewFile();
System.out.println("New Error File created=>"+errorFile.getAbsolutePath());
}
} catch (IOException e) {
e.printStackTrace();
}
return errorFile;
}
public static void writeError(File errorFile, String error) {
try {
FileOutputStream fileOutputStream = new FileOutputStream(errorFile,
true);
DataOutputStream out = new DataOutputStream(fileOutputStream);
BufferedWriter bufferedWriter = new BufferedWriter(
new OutputStreamWriter(out));
bufferedWriter.append((new Date())+" - "+error);
bufferedWriter.newLine();
bufferedWriter.flush();
bufferedWriter.close();
fileOutputStream.flush();
fileOutputStream.close();
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void printStackTrace(File errorFile, String message, Throwable error) {
try {
FileOutputStream fileOutputStream = new FileOutputStream(errorFile,
true);
DataOutputStream out = new DataOutputStream(fileOutputStream);
PrintWriter bufferedWriter = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(out)));
bufferedWriter.println(new Date() + " : "+ message);
error.printStackTrace(bufferedWriter);
bufferedWriter.println();
bufferedWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Sending mail will not be good because it may fill Admin's mail box but if you really need this you can create a MailUtil and send emails to the user or keep it in a log.
MailUtil
public class MailUtil {
public static void sendEmail(String messageString, String subject, Properties props) {
try {
Session mailSession = null;
final String userName = props.getProperty("mail.from");
final String password = props.getProperty("mail.from.password");
mailSession = Session.getInstance(props, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(userName, password);
}
});
Transport transport = mailSession.getTransport();
MimeMessage message = new MimeMessage(mailSession);
message.setSubject(subject);
message.setFrom(new InternetAddress(props.getProperty("mail.from")));
String[] to = props.getProperty("mail.to").split(",");
for (String email : to) {
message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
}
String body = messageString;
message.setContent(body, "text/html");
transport.connect();
transport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
transport.close();
} catch (Exception exception) {
exception.printStackTrace();
}
}
public static void sendEmail(String subject, String messageString) {
try {
Session mailSession = null;
Properties props=new Properties();
FileInputStream fileInputStream = new FileInputStream(new File("mail-config.properties"));
props.load(fileInputStream);
fileInputStream.close();
final String fromUsername = props.getProperty("mail.from");
final String fromPassword = props.getProperty("mail.from.password");
mailSession = Session.getInstance(props, new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(fromUsername, fromPassword);
}
});
Transport transport = mailSession.getTransport();
MimeMessage message = new MimeMessage(mailSession);
message.setSubject(subject);
message.setFrom(new InternetAddress(fromUsername));
String[] to = props.getProperty("mail.to").split(",");
for (String email : to) {
message.addRecipient(Message.RecipientType.TO, new InternetAddress(email));
}
String body = messageString;
message.setContent(body, "text/html");
transport.connect();
transport.sendMessage(message, message.getRecipients(Message.RecipientType.TO));
transport.close();
} catch (Exception exception) {
exception.printStackTrace();
}
}
}
You should use a property to manage if mail is required or not so in future you can stop mails by just changing the property file.
you can create exception log table. There, Write a code to insert exception with "Pending" status into database whatever exception raised in application.
Create a cron job (linux) or quartz scheduler that will fired in certain period and send mail of "pending" status exception with predefined format to admin user.
Update database entry with "sent" status so it will not send again.
In code, To save exception create super class,i.e.
class UserDao extends CommonDao
{
try
{
}catch(Exception e)
{
saveException(e);
}
}
class CommonDao
{
public void saveException(Exception e)
{
//write code to insert data into database
}
}
For me is not a good idea to put that behavior directly in the code of the application. It is clear that simply call to a function that sends an email in the catch clause it is easy, fast and direct. If you haven't so much time go for it.
But then you will realize that will produce some not expected collateral effects you will need to
Control the performance on exception parsing
Control what exceptions are interesting to notify and what not
Control not send lot of emails because a bug in the application producing exceptions constantly.
For that I prefer to use http://logstash.net/ That allows to put all your logs in a common noSQL database, and then you can use logstash to make dashboards or even create your own applications to send well designed reports about specific events. It require a bit more work at the beginning but after that I'm sure you will have more control about what is important to see in the logs and what not.

Configure an App to use the proxy from settings

In my android application I need to connect to the internet to check for the time. This snippet works very nicely in mobile networks and in WiFi-Networks with no proxy enabled:
public class MyTimeGetterTask {
#Override
protected Long doInBackground(Void... params) {
WebTimeSntpClient client = new WebTimeSntpClient();
if (client.requestTime("time-d.nist.gov", 3000)) {
long now = client.getNtpTime() + SystemClock.elapsedRealtime()
- client.getNtpTimeReference();
return now;
}
else {
return null;
}
}
}
The core elements of the WebTimeSntpClient are as follows:
public class WebTimeSntpClient {
public boolean requestTime(String host, int timeout) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
socket.setSoTimeout(timeout);
InetAddress address = InetAddress.getByName(host);
byte[] buffer = new byte[NTP_PACKET_SIZE];
DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
...
socket.send(request);
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
...
} catch (IOException ex) {
return false;
} finally {
if (socket != null) {
socket.close();
}
}
return true;
}
}
However when I'm in the office and the WiFi requires me to configure a proxy (which I did in the settings by long-pressing on the network and then clicking "modify network" - as of Android API level 17) the connection fails.
Now I have looked up quite a lot of very good posts about proxies on the internet and especially here on SO, but absolutely none of them seem to answer this (to me) very simple question:
How do I force my application to use the proxy that is already configured in the settings?
Instead, they focus on more advanced issues like:
How to GET a proxy from the system
How to SET ip information to the system yourself
and much much more about how to make existing applications from the play store use a proxy
Again: I want to stress that this is not my intention, I simply want my app to connect to the internet, no matter what. Is there some System.useWifiProxyIfAvailable(true) method? I'm sure I must have missed a post somewhere here...
You are trying to use SNTP trough a proxy that only allows HTTP/HTTPS.
Your alternative is to use some HTTP service providing the current time, which will be more than enough for most user level applications.
Give http://www.timeapi.org/utc/now a try, however if you are publishing an application using this service you should check the terms and conditions.

How to change JavaMail port

I'm writing a small Java app using JavaMail that sends the user an automated email. They can choose between (for now) two ports: 25 and 587. The port can be selected via a radio button on the GUI.
I added a test button to allow the user to test the email settings (including port). However, for some reason, once the user tries to send a test email, the port can't be changed. Javamail will always use the port of the original test email.
Example: User tries to send an email on port 25 and JavaMail says it can not connect on port 25 (for example, the SMTP host uses another port). User clicks port 587, and tries to send a new email. JavaMail throws an error saying it can not connect on port 25, again.
I'm kind of stumped as to why. Every time a new test email is sent an entirely new SendMailUsingAuthentication object is created. Within that class the properties are always reset to the proper port. Whenever I debug, as far as I can see, all variables are correct and associated with the correct port. Is there something going on inside of Transport that I'm missing?
In the front end GUI:
private void testButtonActionPerformed(java.awt.event.ActionEvent evt) {
int port = port25RadioButton.isSelected() ? PORT_25 : PORT_587;
notifier = new SendMailUsingAuthentication(hostNameTextField.getText(),
userTextField.getText(), getPassword(), emailTextField.getText().split(","),port);
Thread wait = new Thread(new Runnable() {
public void run() {
try {
changeStatusText("Sending test email...");
notifier.postTestMail();
changeStatusText("Test email sent.");
} catch (AddressException ex) {
changeStatusText("Error. Invalid email address name.");
} catch (MessagingException ex) {
changeStatusText("SMTP host connection refused.");
System.err.println(ex.getMessage());
} catch (Exception ex) {
System.err.println(ex);
}
}
});
wait.start();
}
In the email sender class:
public void postTestMail() throws MessagingException, AddressException{
String[] testReciever = new String[1];
testReciever[0] = emailList[0];
postMail(testReciever, "Test email.", "Your email settings are successfully set up.", emailFromAddress);
}
private void postMail(String recipients[], String subject,
String message, String from) throws MessagingException, AddressException {
//Set the host smtp address
Properties props = new Properties();
props.put("mail.smtp.port", smtpPort);
props.put("mail.smtp.host", smtpHostName);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", true);
Authenticator auth = new SMTPAuthenticator();
Session session = Session.getDefaultInstance(props, auth);
session.setDebug(false);
// create a message
Message msg = new MimeMessage(session);
// set the from and to address
InternetAddress addressFrom = new InternetAddress(from);
msg.setFrom(addressFrom);
InternetAddress[] addressTo = new InternetAddress[recipients.length];
for (int i = 0; i < recipients.length; i++) {
addressTo[i] = new InternetAddress(recipients[i]);
}
msg.setRecipients(Message.RecipientType.TO, addressTo);
// Setting the Subject and Content Type
msg.setSubject(subject);
msg.setContent(message, "text/plain");
Transport.send(msg);
}
This happens because you're using getDefaultInstance() which says:
Get the default Session object. If a default has not yet been setup, a new Session object is created and installed as the default.
And that the Properties argument is "used only if a new Session object is created."
So the first time you invoke getDefaultInstance it uses your specified port. After that, the Session has already been created, and subsequent calls to getDefaultInstance will return that same session, and ignore the changed properties.
Try using Session.getInstance() instead of getDefaultInstance(), which creates a new Session each time, using the supplied properties.
It pays to read the javadocs very carefully.
Tip for anyone else still having issues, we were using Session.getInstance and the port was still defaulting to 25.
Turns out, we were setting the prop value as a Long when it needs to be a String
It didn't error, warn or log, just defaulted to 25.
I think "Transport.send(msg)" wont be taking into account the connection details that you are providing in your properties. It will use its connection that is defined by default.
The java doc says
"Note that send is a static method that creates and manages its own connection. **Any connection associated with any Transport instance used to invoke this method is ignored and not used. This method should only be invoked using the form Transport.send(msg);, and should never be invoked using an instance variable. "**
Instead, I have tried with Transport.connect(smtphost,smtpport,user,password) and it works pretty well.
Plz compare two methods of Session class: Session.getDefaultInstance(Properties, Authenticator) and Session.getInstance(Properties, Authenticator)

Categories