You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@mina.apache.org by Davis Ford <df...@axeda.com> on 2011/09/14 17:21:59 UTC
SSHD/SFTP PasswordAuthenticator issues
Hi all, so I wrote my own class that looks like this (stripped down):
public class SftpServerImpl implements PasswordAuthenticator {
private SshServer sshd;
/** holds user/pass authentication info for each SFTP session */
private final ConcurrentMap<String, String> authMap = new ConcurrentHashMap<String, String>();
/* constructor */
public SftpServerImpl( ... params ... ) {
// configure server with sane defaults
sshd = SshServer.setUpDefaultServer();
// the port we listen on
sshd.setPort(port);
// the private RSA key
sshd.setKeyPairProvider(new FileKeyPairProvider(new String[]{privateKey.getAbsolutePath()}));
// SFTP subsystem
sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new SftpSubsystem.Factory()));
// echo shell
sshd.setShellFactory(new EchoShellFactory());
// this class manages authentication
sshd.setPasswordAuthenticator(this);
// file system view - uses our custom SFTP file system view
sshd.setFileSystemFactory(new SftpFileSystemFactory(rootDir));
}
It implements the PasswordAuthenticator interface =>
public boolean authenticate(String username, String password, ServerSession session) {
System.err.println(String.format("AUTH: u=%s, p=%s, map=> %s, map-hashcode=%s, this-hashcode=%s", username, password, authMap, System.identityHashCode(authMap), System.identityHashCode(this)));
if(authMap.containsKey(username)) {
return authMap.get(username).equals(password);
}
return false;
}
}
So, this class holds an instance of the Ssh server, and it implements PasswordAuthenticator interface. I have removed a lot of other code that is not relevant for this example. You'll see that I have a private final instance variable (ConcurrentHashMap) that holds the username/password for authentication.
While writing test cases for this class, I encountered a troubling issue. In my JUnit 4 @Before, I spin up a new instance of this class so I can test against it:
private SftpServerImpl server;
@Before
public void setUp() throws Exception {
server = new SftpServerImpl(port, privateKey, rootDir, timeoutSeconds, hostname);
}
I also properly shutdown the server on @After.
If I write a test case that uses a single session, and upload a file, everything works fine. Here's a sample test case:
@Test
public void testCreateSessionAndUploadFile() throws Exception {
// server should be up and allow me to connect and upload a file or two
String url = server.createSession(new SessionRequest(username, password));
assertEquals("url is incorrect ", "sftp://"+hostname+":"+port, url);
// you can breakpoint this test here, and try to connect with an external client like FileZilla
System.out.println("SFTP server available at "+url);
// connect and upload a file
session = getSession(username, password, hostname);
sendFile(session, uploadFile1, targetFile1.getName());
assertTrue(targetFile1.getAbsolutePath()+" target file was not written ", targetFile1.exists());
assertFilesAreEqual(uploadFile1, targetFile1);
}
The method createSession( ) will end up adding a new entry in the map, so later when authenticate( ) is called when the client session connects, it will work. I am using JSch as the client here just like the Apache SftpTest.java
However, if I try to create two client sessions, I am running into an issue. Here is an example:
@Test
public void testTwoSessionAndUploadTwoFiles() throws Exception {
// create the usual session
server.createSession(new SessionRequest(username, password));
final Session session1 = getSession(username, password, hostname);
// create a new session for a different user
String username2 = "larry";
String password2 = "david";
server.createSession(new SessionRequest(username2, password2));
final Session session2 = getSession(username2, password2, hostname); <= FAILS AUTHENTICATION
}
The last line there fails authentication. If I print/inspect the map, I can see that the entry is not there. But I also print/inspect the map on the line above it and the entry IS there.
The map itself is private final so it cannot be changed, and I am not explicitly changing it anywhere...which means that the actual SftpServerImpl instance object itself is DIFFERENT.
This is why I'm printing System.identityHashcode(this) in that class, and indeed, the hashcode changes between calls to the authenticate method, which means the Sshd.java class itself is swapping instances internally.
Sshd.java has the getPasswordAuthenticator( ) / setPasswordAuthenticator( ) methods. I only ever set it once.
In UserAuthPassword.java, I see this method:
private boolean checkPassword(ServerSession session, String username, String password) throws Exception {
PasswordAuthenticator auth = session.getServerFactoryManager().getPasswordAuthenticator();
if (auth != null) {
return auth.authenticate(username, password, session);
}
throw new Exception("No PasswordAuthenticator configured");
}
Somehow, the first line there is delivering a different instance of my class across successive calls - it is not consistent. I have only set the PasswordAuthenticator one time and one time only, but it seems to have more than one copy of my class in different states, which is very bizarre. I'm not sure if it is a different ServerFactoryManager or not, but it is not returning the same instance of my own SftpServerImpl.java class - I have validated this by printing out the System.identityHashcode(this) in that class itself in multiple places.
Any ideas on what is going on here. I aim to debug this further, but I'd love to hear any viable explanation for why this is happening.
I can, of course, hack-fix it by making my map static, but this is ugly, and it means I can only have one instance of this class in any JVM, and it can potentially have other bad side effects.
Regards,
Davis