-
Notifications
You must be signed in to change notification settings - Fork 126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue131 #134
Issue131 #134
Conversation
fixed bugs that where found in the process - fixed bug in remove(String, String, String) leading to only every second host key being removed - ensure that there is a SHA1-hash-instance to prevent NullPointerExceptions - replaced all occurrences of STDOUT/ERR outputs in case of exceptions with log entries - added a default method to Logger to allow passing the exception to be logged with the message - put class under test
try (PrintWriter pw = new PrintWriter(sw, true)) { | ||
cause.printStackTrace(pw); | ||
} | ||
message += "\r\n" + sw.toString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should do this instead:
message += System.lineSeparator() + sw.toString();
That way it uses the correct line separator for whatever platform is in use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder if we should override this default method in the concrete implementations I added to utilize their specialized methods for logging exceptions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can change the line separator to be platform dependent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also wonder if we should override this default method in the concrete implementations I added to utilize their specialized methods for logging exceptions?
I think this makes sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed the line break and added specific log-methods to framework-specific implementations. I've also added test cases for all the logging frameworks that allow logger configuration in a programmatic way, i.e. there is no test for Log4j2.
added log-framework-specific implementations of log(int, String, Throwable) to pass the cause to the framework in a fitting way added tests for all log frameworks that can be configured in a programmatic way.
It looks like there is a test failure with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you also add specialization in the JplLogger
class too (under src/main/java9
)?
default: | ||
return logger.isTraceEnabled(); | ||
} | ||
return logger.isEnabledForLevel(getLevel(level)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to revert this part: I believe the isEnabledForLevel()
method is only available with SLFJ4 2.x, but we should keep compatibility with the SLF4J 1.x API as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed that slf4j 2.0 is used because it's in the pom.xml
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed that slf4j 2.0 is used because it's in the pom.xml
I switched the dependency to 2.x because the 1.x versions aren't modularized, so they caused warnings for module-info.java
for JPMS support.
But I made sure that the Slf4jLogger
was fully compatible with 1.x, allowing users to decide which version they want to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If backward compatibility to slf4j 1 is intended, the pom should be changed to use that library to make sure that the compiler always compiles against the correct methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If backward compatibility to slf4j 1 is intended, the pom should be changed to use that library to make sure that the compiler always compiles against the correct methods.
Except the project doesn't compile module-info.java
correctly if it is built with the 1.x version of the library, and then we lose JPMS support.
It doesn't seem that difficult to just leave the code as it is with the existing switch
statement (it works with both SLFJ4 1.x & 2.x), and the way I structured the project, users can make use of either version in their own projects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Except the project doesn't compile
module-info.java
correctly if it is built with the 1.x version of the library, and then we lose JPMS support.
OK. I've just been bitten once already when compiling against a newer API while executing with an older one. It's been a while but a lecture for life ;-) In the good old times when a kilobyte weighted a kilogram, java.lang.BigDecimal
got a new constructor BigDecimal(long)
. While I don't expect something like that took place in Slf4J, it's still dangerous to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I realize that and I wouldn't have changed the pom.xml to the 2.x version, except that I was also trying to make JSch fully modularized & JPMS compatible, and this was the only way I found to accomplish that.
I think as long as we are careful about what SLF4J methods we utilize, we can accomplish everything:
- SLF4j 1.x compability.
- SLF4j 2.x compability.
- Java 9+ JPMS support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I'm not that Maven-savvy but isn't it possible to define a test-only dependency. Can we use slf4j 2 for compiling and slf4j 1 for testing? With the test cases I've added any incompatibility (and be it compiler-related) should show up during test that way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the only way to do that would be to completely restructure as a multi-module project, with the library as one module and the tests as a different module, but that would be alot of work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've changed the implementation to only use the API of slf4j 1
actually is system dependent)
|
||
public class JulLogger implements com.jcraft.jsch.Logger { | ||
|
||
private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(JSch.class.getName()); | ||
private static final java.util.logging.Logger stlogger = java.util.logging.Logger.getLogger(JSch.class.getName()); | ||
private java.util.logging.Logger logger; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the can be a private final
field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
|
||
public class Slf4jLogger implements com.jcraft.jsch.Logger { | ||
|
||
private static final org.slf4j.Logger logger = LoggerFactory.getLogger(JSch.class); | ||
private static final Logger stlogger = LoggerFactory.getLogger(JSch.class); | ||
private Logger logger; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the can be a private final
field?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
use slf4j 1.x API for logging to keep backward compatibility
done |
case com.jcraft.jsch.Logger.INFO: | ||
return logger.isInfoEnabled(); | ||
case com.jcraft.jsch.Logger.WARN: | ||
return logger.isWarnEnabled(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The formatting for this no longer seems to follow Google Java Style Guide's guidelines in section 4.8.4.
Can we return the formatting back, since I created this file following their style guide?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I had purposely ordered the levels in the switch statement in order by severity (lowest to highest), can we also restore that order?
} | ||
return logger.isTraceEnabled(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should put this back as a default
case in the switch statement above, to match the switch statement in the new specialization for Throwable's below where it is in its default
case.
break; | ||
default: | ||
logger.trace(message); | ||
logger.trace(message, cause); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we order the cases in this switch statement to match original, which was by severity?
if (fos != null) { | ||
fos.close(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should change this to a try-with-resources statement?
private static final byte[] cr=Util.str2byte("\n"); | ||
private static final byte[] lf=Util.str2byte("\n"); | ||
|
||
@SuppressWarnings("unused") // to keep the method's signature without warning |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious as to why this is needed since the out
argument appears to be used in the call to dumpHostKey()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand why this warning suppression is required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
out
is used but no IOException
is thrown. You can't remove it from the method signature without potentially breaking things, so my solution was to suppress the warning and keep the method signature as it was before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not just remove the throws IOException
then from the method signature?
It’s a package private method, so it’s not part of the public facing API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've checked and you're right. Except the testcase I've created this didn't break anything else, so I've changed it
@@ -27,22 +41,30 @@ public boolean isEnabled(int level) { | |||
|
|||
@Override | |||
public void log(int level, String message) { | |||
log (level, message, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit: there appears to be an extraneous space character between log
and (
.
|
||
public class JulLogger implements com.jcraft.jsch.Logger { | ||
|
||
private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(JSch.class.getName()); | ||
private static final java.util.logging.Logger stlogger = java.util.logging.Logger.getLogger(JSch.class.getName()); | ||
private final java.util.logging.Logger logger; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think since you've added an import java.util.logging.Logger;
statement, there is no longer a need to specify the fully qualified package for these?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although I wonder if it would be better to drop the import java.util.logging.Logger
statement instead and just always fully qualify the package everywhere? That would make it slightly easier to reason about which Logger
class is being used.
@@ -1,12 +1,26 @@ | |||
package com.jcraft.jsch; | |||
|
|||
import org.slf4j.Logger; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if it would be better to drop this import statement and just always fully qualify the package for the Logger
references below so that it is easier to reason about which type of Logger
is being referred to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, the class is simple enough that one can be done by import and the other fully qualified. IMHO the fully qualified way is less readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it does lead to a bit of inconsistencies between various implementations, as some of the now have an import statement for the logging framework's Logger
class and some don't.
We should make it consistent between them all whether they do or don't import it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've checked the loggers and haven't found any inconsistencies
} | ||
logger.log(getLevel(level), message, cause); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we added an import
statement for the Logger
class for the other implementations, we should probably do the same thing for this class now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've changed that already without seeing your comment, haven't I?
jsch.getInstanceLogger().log(Logger.ERROR, "unable to instantiate HMAC-class " + hmacClassname + ", using SHA-1 as fallback", e); | ||
return new HMACSHA1(); // the default in config | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I've been thinking about this some more: I think instead of falling back to the com.jcraft.jsch.jce.HMACSHA1
implementation, I think we should simply throw a JSchException
if creating the hmacClassname
fails.
I think this will work because:
- The
KnownHosts
class is package private since the 0.2.1 release, so we have some API flexibility. - All the locations in the
JSch
andSession
classes where we instantiate aKnownHosts
instance all appear to be in methods that already allow throwing aJSchException
.
I also think this makes more sense from a user perspective, since it would immediately inform a user that the class they configured as hmac-sha1
was not usable, instead of hiding it away in a way that they may not realize (especially if they don't have a logger setup).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can do the change but a misconfigured hmac-class should lead to execption during SSH-handshakes as you've pointed out in the thread of the issue itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but I don't think we should rely on that behavior.
Also, whilst the KnownHosts
class isn't public, it is a HostKeyRepository
implementation, which a user can obtain access to via the JSch.getHostKeyRepository()
or Session.getHostKeyRepository()
, which they could then utilize standalone for their own purposes, before creating a session that would trigger the SSH-handshake issue.
Therefore, I think it's best that we not hide-away the potential hmac-sha1
misconfiguration issue inside the KnownHosts
class, but rather fail immediately when it is encountered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the locations in the JSch and Session classes where we instantiate a KnownHosts instance all appear to be in methods that already allow throwing a JSchException.
That's actually not the case:
public HostKeyRepository getHostKeyRepository(){ if(known_hosts==null) known_hosts=new KnownHosts(this); return known_hosts; }
I can put it into a RuntimeException and throw that instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, you are correct. Throwing a RuntimeException is probably the best choice, thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
constructor) if the HMAC-SHA1-class can't be instantiated.
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Vector; | ||
import com.jcraft.jsch.jce.HMACSHA1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this import can be removed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can. Missed that because there were other warnings of unused stuff. I've commented out the other lines containing unused stuff as well (not removed, maybe there is a reason why it's currently unused)
from seeing this earlier
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Vector; | ||
|
||
class KnownHosts implements HostKeyRepository{ | ||
private static final String _known_hosts="known_hosts"; | ||
// private static final String _known_hosts="known_hosts"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this isn't used, then I would say to remove it instead of commenting it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can delete it (as with the others) but my usual workflow is commenting out first and if nobody complains within some given time, I delete it in a later step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can just safely remove it since it is a private field.
@@ -290,7 +295,7 @@ public int check(String host, byte[] key){ | |||
public void add(HostKey hostkey, UserInfo userinfo){ | |||
int type=hostkey.type; | |||
String host=hostkey.getHost(); | |||
byte[] key=hostkey.key; | |||
// byte[] key=hostkey.key; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this isn't used, then I would say to remove it instead of commenting it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
key
is used in a code block that currently is commented out a couple of lines later. So the person who commented out that block simply forgot to do it with this particular line. That's why I'd keep it there until it's decided what should happen with the larger code block. If it's "reactivated", the line can be brought back to life as well and if it's removed that line has to go, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, you are correct on this one: it is best to just comment it out for now as you have done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
@mwiede I think we should do a squash merge of this so that's all contained with a single commit.
Hi, as announced I've put KnownHosts under test and implemented some of the issues discussed in #131:
null
now. If the instantiation of the configured class fails, the JSCH SHA1-class is used as fallback. This doesn't solve things because the SSH-handshake will fail later on (but the exception there is expected and should help troubleshoot). I haven't implemented a check if the configured SHA1-class is actually SHA1 as requested by @norrisjeremygetElementAt(i)
. If an element is removed,i
isn't decreased, so one entry to be checked is skipped that might have been removed otherwise.