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

Reply via email to