naujoks-stefan opened a new issue, #534:
URL: https://github.com/apache/mina-sshd/issues/534

   ### Version
   
   1.7.0
   
   ### Bug description
   
   Hello everyone,
   
   I'm working on a project where I need to set up an SFTP server using Apache 
SSHD, which allows users to access multiple directories within a specified root 
directory. The root directory should be restricted to C:/FTP, and users should 
only be able to see and access their designated subdirectories within this root 
directory.
   
   Despite setting the C:/FTP as the root directory and specifying the user 
directories, users are seeing the entire C:/ drive instead of being restricted 
to C:/FTP and their specific directories.
   
   ### Actual behavior
   
   Current Setup
   I have implemented a custom SFTP server using Apache SSHD, and it 
successfully authenticates users. However, I'm facing issues with restricting 
the user's view to their specific directories. Instead of seeing their 
designated directories within C:/FTP, users can see the entire C:/ drive.
   
   Here's the code I am using:
   
   public class MySftpServer {
   
       private static final Log log = LogFactory.getLog(MySftpServer.class);
       private static final AttributeKey<UserManager.User> USER_KEY = new 
AttributeKey<>();
   
       @PostConstruct
       public void startServer() throws IOException {
           start();
       }
   
       private void start() throws IOException {
           SshServer sshd = SshServer.setUpDefaultServer();
           sshd.setHost("localhost");
           sshd.setPort(2222);
           sshd.setKeyPairProvider(new 
PEMKeyPairProvider("src/main/resources/host_key.ser"));
           sshd.setSubsystemFactories(Collections.singletonList(new 
SftpSubsystemFactory()));
           sshd.setPasswordAuthenticator(new PasswordAuthenticator() {
               @Override
               public boolean authenticate(String username, String password, 
ServerSession session) {
                   UserManager.User user = UserManager.authenticate(username, 
password);
                   if (user != null) {
                       session.setAttribute(USER_KEY, user);
                       return true;
                   }
                   return false;
               }
           });
           sshd.setPublickeyAuthenticator(new 
AuthorizedKeysAuthenticator(Paths.get("src/main/resources/authorized_keys")));
   
           // Set idle timeout to 30 minutes
           sshd.getProperties().put(SshServer.IDLE_TIMEOUT, 
TimeUnit.MINUTES.toMillis(30));
           // Set authentication timeout to 2 minutes
           sshd.getProperties().put(SshServer.AUTH_TIMEOUT, 
TimeUnit.MINUTES.toMillis(2));
   
           // Set up the custom file system factory with user home directories
           sshd.setFileSystemFactory(new 
CustomFileSystemFactory(Paths.get("C:/FTP")));
   
           sshd.start();
           log.info("SFTP server started");
       }
   
       public static class CustomFileSystemFactory implements FileSystemFactory 
{
           private final Path rootDir;
   
           public CustomFileSystemFactory(Path rootDir) {
               this.rootDir = rootDir;
           }
   
           @Override
           public FileSystem createFileSystem(Session session) throws 
IOException {
               UserManager.User user = session.getAttribute(USER_KEY);
               if (user == null) {
                   throw new IOException("No user found in session");
               }
   
               List<Path> userDirs = user.getDirectories().stream()
                   .map(dir -> 
rootDir.resolve(dir).toAbsolutePath().normalize())
                   .collect(Collectors.toList());
               return new MultiDirectoryFileSystem(userDirs);
           }
       }
   
       public static class MultiDirectoryFileSystem extends FileSystem {
           private final List<Path> roots;
   
           public MultiDirectoryFileSystem(List<Path> directories) {
               this.roots = directories;
           }
   
           @Override
           public FileSystemProvider provider() {
               return new MultiDirectoryFileSystemProvider(roots);
           }
   
           @Override
           public void close() throws IOException {
               // Implement close logic if needed
           }
   
           @Override
           public boolean isOpen() {
               return true;
           }
   
           @Override
           public boolean isReadOnly() {
               return false;
           }
   
           @Override
           public String getSeparator() {
               return FileSystems.getDefault().getSeparator();
           }
   
           @Override
           public Iterable<Path> getRootDirectories() {
               return roots;
           }
   
           @Override
           public Iterable<FileStore> getFileStores() {
               return Collections.emptyList();
           }
   
           @Override
           public Set<String> supportedFileAttributeViews() {
               return FileSystems.getDefault().supportedFileAttributeViews();
           }
   
           @Override
           public Path getPath(String first, String... more) {
               Path path = FileSystems.getDefault().getPath(first, 
more).toAbsolutePath().normalize();
               for (Path root : roots) {
                   if (path.startsWith(root)) {
                       return path;
                   }
               }
               throw new IllegalArgumentException("Path is not under any root: 
" + path);
           }
   
           @Override
           public PathMatcher getPathMatcher(String syntaxAndPattern) {
               return FileSystems.getDefault().getPathMatcher(syntaxAndPattern);
           }
   
           @Override
           public UserPrincipalLookupService getUserPrincipalLookupService() {
               return FileSystems.getDefault().getUserPrincipalLookupService();
           }
   
           @Override
           public WatchService newWatchService() throws IOException {
               return FileSystems.getDefault().newWatchService();
           }
       }
   
       public static class MultiDirectoryFileSystemProvider extends 
FileSystemProvider {
           private final List<Path> roots;
   
           public MultiDirectoryFileSystemProvider(List<Path> roots) {
               this.roots = roots;
           }
   
           @Override
           public String getScheme() {
               return "multi-dir";
           }
   
           @Override
           public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws 
IOException {
               throw new UnsupportedOperationException("Creating new file 
systems is not supported.");
           }
   
           @Override
           public FileSystem getFileSystem(URI uri) {
               throw new UnsupportedOperationException("Getting file system by 
URI is not supported.");
           }
   
           @Override
           public Path getPath(URI uri) {
               return Paths.get(uri).toAbsolutePath().normalize();
           }
   
           @Override
           public FileChannel newFileChannel(Path path, Set<? extends 
OpenOption> options, FileAttribute<?>... attrs) throws IOException {
               validateAccess(path);
               return FileSystems.getDefault().provider().newFileChannel(path, 
options, attrs);
           }
   
           @Override
           public AsynchronousFileChannel newAsynchronousFileChannel(Path path, 
Set<? extends OpenOption> options, ExecutorService executor, 
FileAttribute<?>... attrs) throws IOException {
               validateAccess(path);
               return 
FileSystems.getDefault().provider().newAsynchronousFileChannel(path, options, 
executor, attrs);
           }
   
           @Override
           public DirectoryStream<Path> newDirectoryStream(Path dir, 
DirectoryStream.Filter<? super Path> filter) throws IOException {
               validateAccess(dir);
               return 
FileSystems.getDefault().provider().newDirectoryStream(dir, filter);
           }
   
           @Override
           public void createDirectory(Path dir, FileAttribute<?>... attrs) 
throws IOException {
               validateAccess(dir);
               FileSystems.getDefault().provider().createDirectory(dir, attrs);
           }
   
           @Override
           public void delete(Path path) throws IOException {
               validateAccess(path);
               FileSystems.getDefault().provider().delete(path);
           }
   
           @Override
           public void copy(Path source, Path target, CopyOption... options) 
throws IOException {
               validateAccess(source);
               validateAccess(target);
               FileSystems.getDefault().provider().copy(source, target, 
options);
           }
   
           @Override
           public void move(Path source, Path target, CopyOption... options) 
throws IOException {
               validateAccess(source);
               validateAccess(target);
               FileSystems.getDefault().provider().move(source, target, 
options);
           }
   
           @Override
           public boolean isSameFile(Path path, Path path2) throws IOException {
               validateAccess(path);
               validateAccess(path2);
               return FileSystems.getDefault().provider().isSameFile(path, 
path2);
           }
   
           @Override
           public boolean isHidden(Path path) throws IOException {
               validateAccess(path);
               return FileSystems.getDefault().provider().isHidden(path);
           }
   
           @Override
           public FileStore getFileStore(Path path) throws IOException {
               validateAccess(path);
               return FileSystems.getDefault().provider().getFileStore(path);
           }
   
           @Override
           public void checkAccess(Path path, AccessMode... modes) throws 
IOException {
               validateAccess(path);
               FileSystems.getDefault().provider().checkAccess(path, modes);
           }
   
           @Override
           public <V extends FileAttributeView> V getFileAttributeView(Path 
path, Class<V> type, LinkOption... options) {
               try {
                   validateAccess(path);
               } catch (IOException e) {
                   e.printStackTrace();
               }
               return 
FileSystems.getDefault().provider().getFileAttributeView(path, type, options);
           }
   
           @Override
           public <A extends BasicFileAttributes> A readAttributes(Path path, 
Class<A> type, LinkOption... options) throws IOException {
               validateAccess(path);
               return FileSystems.getDefault().provider().readAttributes(path, 
type, options);
           }
   
           @Override
           public Map<String, Object> readAttributes(Path path, String 
attributes, LinkOption... options) throws IOException {
               validateAccess(path);
               return FileSystems.getDefault().provider().readAttributes(path, 
attributes, options);
           }
   
           @Override
           public void setAttribute(Path path, String attribute, Object value, 
LinkOption... options) throws IOException {
               validateAccess(path);
               FileSystems.getDefault().provider().setAttribute(path, 
attribute, value, options);
           }
   
           private void validateAccess(Path path) throws IOException {
               for (Path root : roots) {
                   if (path.startsWith(root)) {
                       return;
                   }
               }
               throw new IOException("Access denied to path: " + path);
           }
   
           @Override
           public SeekableByteChannel newByteChannel(Path path, Set<? extends 
OpenOption> options,
                                                     FileAttribute<?>... attrs) 
throws IOException {
               validateAccess(path);
               return FileSystems.getDefault().provider().newByteChannel(path, 
options, attrs);
           }
       }
   
       public static class PEMKeyPairProvider implements KeyPairProvider {
   
           private final KeyPair keyPair;
   
           public PEMKeyPairProvider(String privateKeyPath) throws IOException {
               try (PEMParser pemParser = new PEMParser(new 
FileReader(privateKeyPath))) {
                   JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
                   Object object = pemParser.readObject();
                   if (object instanceof PEMKeyPair) {
                       this.keyPair = converter.getKeyPair((PEMKeyPair) object);
                   } else if (object instanceof PrivateKeyInfo) {
                       PrivateKey privateKey = 
converter.getPrivateKey((PrivateKeyInfo) object);
                       SubjectPublicKeyInfo publicKeyInfo = 
SubjectPublicKeyInfo.getInstance(pemParser.readObject());
                       PublicKey publicKey = 
converter.getPublicKey(publicKeyInfo);
                       this.keyPair = new KeyPair(publicKey, privateKey);
                   } else {
                       throw new IllegalArgumentException("Invalid key format");
                   }
               }
           }
   
           @Override
           public Iterable<KeyPair> loadKeys() {
               return Collections.singletonList(keyPair);
           }
       }
   }
   
   ### Expected behavior
   
   The SFTP root directory should be C:/FTP.
   Users should only see and access their designated directories within C:/FTP.
   For example, user1 should only see C:/FTP/001 and C:/FTP/002.
   
   ### Relevant log output
   
   _No response_
   
   ### Other information
   
   _No response_


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscr...@mina.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@mina.apache.org
For additional commands, e-mail: dev-h...@mina.apache.org

Reply via email to