Repository: mina-sshd
Updated Branches:
  refs/heads/master 80d718f3d -> 8e255459a


[SSHD-731] Vulnerability in SimpleAccessControlSftpEventListener implementation


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/8e255459
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/8e255459
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/8e255459

Branch: refs/heads/master
Commit: 8e255459acd21cd67c579dbfc6576bddc7161d6c
Parents: 80d718f
Author: Lyor Goldstein <lyor.goldst...@gmail.com>
Authored: Thu Mar 2 20:57:17 2017 +0200
Committer: Lyor Goldstein <lyor.goldst...@gmail.com>
Committed: Thu Mar 2 21:07:53 2017 +0200

----------------------------------------------------------------------
 .../SimpleAccessControlSftpEventListener.java   |  26 +++-
 .../apache/sshd/common/util/GenericUtils.java   |  13 ++
 .../org/apache/sshd/common/util/io/IoUtils.java |  16 ++-
 .../sftp/AbstractSftpEventListenerAdapter.java  |   8 ++
 .../server/subsystem/sftp/DirectoryHandle.java  |   8 ++
 .../sshd/server/subsystem/sftp/FileHandle.java  | 133 ++++++++++++-------
 .../sshd/server/subsystem/sftp/Handle.java      |  13 ++
 .../subsystem/sftp/SftpEventListener.java       |  15 ++-
 .../server/subsystem/sftp/SftpSubsystem.java    |   4 -
 9 files changed, 178 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
----------------------------------------------------------------------
diff --git 
a/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
 
b/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
index 68fdb82..1a80bd0 100644
--- 
a/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
+++ 
b/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
@@ -23,9 +23,12 @@ import java.io.IOException;
 import java.nio.file.AccessDeniedException;
 import java.nio.file.CopyOption;
 import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.util.Collection;
 import java.util.Map;
 
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.session.ServerSession;
 
 /**
@@ -54,6 +57,28 @@ public abstract class SimpleAccessControlSftpEventListener 
extends AbstractSftpE
     }
 
     @Override
+    public void opening(ServerSession session, String remoteHandle, Handle 
localHandle)
+            throws IOException {
+        super.opening(session, remoteHandle, localHandle);
+        if (localHandle instanceof DirectoryHandle) {
+            if (!isAccessAllowed(session, remoteHandle, localHandle)) {
+                throw new AccessDeniedException(remoteHandle);
+            }
+        } else {
+            Collection<StandardOpenOption> options = ((FileHandle) 
localHandle).getOpenOptions();
+            if (GenericUtils.containsAny(options, 
IoUtils.WRITEABLE_OPEN_OPTIONS)) {
+                if (!isModificationAllowed(session, remoteHandle, 
localHandle.getFile())) {
+                    throw new AccessDeniedException(remoteHandle);
+                }
+            } else {
+                if (!isAccessAllowed(session, remoteHandle, localHandle)) {
+                    throw new AccessDeniedException(remoteHandle);
+                }
+            }
+        }
+    }
+
+    @Override
     public void read(ServerSession session, String remoteHandle, 
DirectoryHandle localHandle, Map<String, Path> entries)
             throws IOException {
         super.read(session, remoteHandle, localHandle, entries);
@@ -166,5 +191,4 @@ public abstract class SimpleAccessControlSftpEventListener 
extends AbstractSftpE
      * @throws IOException If failed to handle the call
      */
     protected abstract boolean isModificationAllowed(ServerSession session, 
String remoteHandle, Path localPath) throws IOException;
-
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java 
b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index 92d89b1..852cba6 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -365,6 +365,19 @@ public final class GenericUtils {
         return result;
     }
 
+    public static <T> boolean containsAny(Collection<? extends T> coll, 
Iterable<? extends T> values) {
+        if (isEmpty(coll)) {
+            return false;
+        }
+
+        for (T v : values) {
+            if (coll.contains(v)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
     public static <T> void forEach(Iterable<T> values, Consumer<T> consumer) {
         if (isNotEmpty(values)) {
             values.forEach(consumer);

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java 
b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
index fae069d..3f9bfcd 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/util/io/IoUtils.java
@@ -36,6 +36,7 @@ import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.OpenOption;
 import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.FileAttribute;
 import java.nio.file.attribute.PosixFilePermission;
 import java.nio.file.attribute.UserPrincipal;
@@ -73,7 +74,20 @@ public final class IoUtils {
     /**
      * The local O/S line separator
      */
-    public static final String EOL = System.getProperty("line.separator");
+    public static final String EOL = System.lineSeparator();
+
+    /**
+     * A {@link Set} of {@link StandardOpenOption}-s that indicate an intent
+     * to create/modify a file
+     */
+    public static final Set<StandardOpenOption> WRITEABLE_OPEN_OPTIONS =
+        Collections.unmodifiableSet(
+            EnumSet.of(
+                StandardOpenOption.APPEND, StandardOpenOption.CREATE,
+                StandardOpenOption.CREATE_NEW, 
StandardOpenOption.DELETE_ON_CLOSE,
+                StandardOpenOption.DSYNC, StandardOpenOption.SYNC,
+                StandardOpenOption.TRUNCATE_EXISTING, 
StandardOpenOption.WRITE));
+
     private static final byte[] EOL_BYTES = 
EOL.getBytes(StandardCharsets.UTF_8);
 
     private static final LinkOption[] NO_FOLLOW_OPTIONS = new 
LinkOption[]{LinkOption.NOFOLLOW_LINKS};

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerAdapter.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerAdapter.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerAdapter.java
index 56a12b0..6895bae 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerAdapter.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/AbstractSftpEventListenerAdapter.java
@@ -56,6 +56,14 @@ public abstract class AbstractSftpEventListenerAdapter 
extends AbstractLoggingBe
     }
 
     @Override
+    public void opening(ServerSession session, String remoteHandle, Handle 
localHandle) throws IOException {
+        if (log.isTraceEnabled()) {
+            Path path = localHandle.getFile();
+            log.trace("opening(" + session + ")[" + remoteHandle + "] " + 
(Files.isDirectory(path) ? "directory" : "file") + " " + path);
+        }
+    }
+
+    @Override
     public void open(ServerSession session, String remoteHandle, Handle 
localHandle) {
         if (log.isTraceEnabled()) {
             Path path = localHandle.getFile();

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
index 82e691e..0ae60cf 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/DirectoryHandle.java
@@ -39,6 +39,7 @@ public class DirectoryHandle extends Handle implements 
Iterator<Path> {
 
     public DirectoryHandle(SftpSubsystem subsystem, Path dir, String handle) 
throws IOException {
         super(dir, handle);
+        signalHandleOpening(subsystem);
 
         SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
         ServerSession session = subsystem.getServerSession();
@@ -49,6 +50,13 @@ public class DirectoryHandle extends Handle implements 
Iterator<Path> {
             sendDotDot = false;  // if no parent then no need to send ".."
         }
         fileList = ds.iterator();
+
+        try {
+            signalHandleOpen(subsystem);
+        } catch (IOException e) {
+            close();
+            throw e;
+        }
     }
 
     public boolean isDone() {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
index f737a91..b499524 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/FileHandle.java
@@ -27,6 +27,7 @@ import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.FileAttribute;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -41,79 +42,53 @@ import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.io.IoUtils;
 import org.apache.sshd.server.session.ServerSession;
 
-
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
  */
 public class FileHandle extends Handle {
-
     private final int access;
     private final SeekableByteChannel fileChannel;
     private final List<FileLock> locks = new ArrayList<>();
     private final SftpSubsystem subsystem;
+    private final Set<StandardOpenOption> openOptions;
+    private final Collection<FileAttribute<?>> fileAttributes;
 
     public FileHandle(SftpSubsystem subsystem, Path file, String handle, int 
flags, int access, Map<String, Object> attrs) throws IOException {
         super(file, handle);
         this.subsystem = Objects.requireNonNull(subsystem, "No subsystem 
instance provided");
         this.access = access;
+        this.openOptions = Collections.unmodifiableSet(getOpenOptions(flags, 
access));
+        this.fileAttributes = 
Collections.unmodifiableCollection(toFileAttributes(attrs));
+        signalHandleOpening(subsystem);
 
-        Set<StandardOpenOption> options = 
EnumSet.noneOf(StandardOpenOption.class);
-        if (((access & SftpConstants.ACE4_READ_DATA) != 0) || ((access & 
SftpConstants.ACE4_READ_ATTRIBUTES) != 0)) {
-            options.add(StandardOpenOption.READ);
-        }
-        if (((access & SftpConstants.ACE4_WRITE_DATA) != 0) || ((access & 
SftpConstants.ACE4_WRITE_ATTRIBUTES) != 0)) {
-            options.add(StandardOpenOption.WRITE);
-        }
-
-        int accessDisposition = flags & 
SftpConstants.SSH_FXF_ACCESS_DISPOSITION;
-        switch (accessDisposition) {
-            case SftpConstants.SSH_FXF_CREATE_NEW:
-                options.add(StandardOpenOption.CREATE_NEW);
-                break;
-            case SftpConstants.SSH_FXF_CREATE_TRUNCATE:
-                options.add(StandardOpenOption.CREATE);
-                options.add(StandardOpenOption.TRUNCATE_EXISTING);
-                break;
-            case SftpConstants.SSH_FXF_OPEN_EXISTING:
-                break;
-            case SftpConstants.SSH_FXF_OPEN_OR_CREATE:
-                options.add(StandardOpenOption.CREATE);
-                break;
-            case SftpConstants.SSH_FXF_TRUNCATE_EXISTING:
-                options.add(StandardOpenOption.TRUNCATE_EXISTING);
-                break;
-            default:    // ignored
-        }
-        if ((flags & SftpConstants.SSH_FXF_APPEND_DATA) != 0) {
-            options.add(StandardOpenOption.APPEND);
-        }
-
-        Collection<FileAttribute<?>> attributes = null;
-        // Cannot use forEach because the referenced attributes variable is 
not effectively final
-        for (Map.Entry<String, Object> attr : attrs.entrySet()) {
-            FileAttribute<?> fileAttr = toFileAttribute(attr.getKey(), 
attr.getValue());
-            if (fileAttr == null) {
-                continue;
-            }
-            if (attributes == null) {
-                attributes = new LinkedList<>();
-            }
-            attributes.add(fileAttr);
-        }
-
-        FileAttribute<?>[] fileAttrs = GenericUtils.isEmpty(attributes)
+        FileAttribute<?>[] fileAttrs = GenericUtils.isEmpty(fileAttributes)
                 ? IoUtils.EMPTY_FILE_ATTRIBUTES
-                : attributes.toArray(new FileAttribute<?>[attributes.size()]);
+                : fileAttributes.toArray(new 
FileAttribute<?>[fileAttributes.size()]);
         SftpFileSystemAccessor accessor = subsystem.getFileSystemAccessor();
         ServerSession session = subsystem.getServerSession();
         SeekableByteChannel channel;
         try {
-            channel = accessor.openFile(session, subsystem, file, handle, 
options, fileAttrs);
+            channel = accessor.openFile(session, subsystem, file, handle, 
openOptions, fileAttrs);
         } catch (UnsupportedOperationException e) {
-            channel = accessor.openFile(session, subsystem, file, handle, 
options, IoUtils.EMPTY_FILE_ATTRIBUTES);
+            channel = accessor.openFile(session, subsystem, file, handle, 
openOptions, IoUtils.EMPTY_FILE_ATTRIBUTES);
             subsystem.doSetAttributes(file, attrs);
         }
         this.fileChannel = channel;
+
+        try {
+            signalHandleOpen(subsystem);
+        } catch (IOException e) {
+            close();
+            throw e;
+        }
+    }
+
+    public final Set<StandardOpenOption> getOpenOptions() {
+        return openOptions;
+    }
+
+    public final Collection<FileAttribute<?>> getFileAttributes() {
+        return fileAttributes;
     }
 
     public final SeekableByteChannel getFileChannel() {
@@ -203,7 +178,28 @@ public class FileHandle extends Handle {
         lock.release();
     }
 
-    public static FileAttribute<?> toFileAttribute(final String key, final 
Object val) {
+    public static Collection<FileAttribute<?>> toFileAttributes(Map<String, 
Object> attrs) {
+        if (GenericUtils.isEmpty(attrs)) {
+            return Collections.emptyList();
+        }
+
+        Collection<FileAttribute<?>> attributes = null;
+        // Cannot use forEach because the referenced attributes variable is 
not effectively final
+        for (Map.Entry<String, Object> attr : attrs.entrySet()) {
+            FileAttribute<?> fileAttr = toFileAttribute(attr.getKey(), 
attr.getValue());
+            if (fileAttr == null) {
+                continue;
+            }
+            if (attributes == null) {
+                attributes = new LinkedList<>();
+            }
+            attributes.add(fileAttr);
+        }
+
+        return (attributes == null) ? Collections.emptyList() : attributes;
+    }
+
+    public static FileAttribute<?> toFileAttribute(String key, Object val) {
         // Some ignored attributes sent by the SFTP client
         if ("isOther".equals(key)) {
             if ((Boolean) val) {
@@ -236,4 +232,39 @@ public class FileHandle extends Handle {
             }
         };
     }
+
+    public static Set<StandardOpenOption> getOpenOptions(int flags, int 
access) {
+        Set<StandardOpenOption> options = 
EnumSet.noneOf(StandardOpenOption.class);
+        if (((access & SftpConstants.ACE4_READ_DATA) != 0) || ((access & 
SftpConstants.ACE4_READ_ATTRIBUTES) != 0)) {
+            options.add(StandardOpenOption.READ);
+        }
+        if (((access & SftpConstants.ACE4_WRITE_DATA) != 0) || ((access & 
SftpConstants.ACE4_WRITE_ATTRIBUTES) != 0)) {
+            options.add(StandardOpenOption.WRITE);
+        }
+
+        int accessDisposition = flags & 
SftpConstants.SSH_FXF_ACCESS_DISPOSITION;
+        switch (accessDisposition) {
+            case SftpConstants.SSH_FXF_CREATE_NEW:
+                options.add(StandardOpenOption.CREATE_NEW);
+                break;
+            case SftpConstants.SSH_FXF_CREATE_TRUNCATE:
+                options.add(StandardOpenOption.CREATE);
+                options.add(StandardOpenOption.TRUNCATE_EXISTING);
+                break;
+            case SftpConstants.SSH_FXF_OPEN_EXISTING:
+                break;
+            case SftpConstants.SSH_FXF_OPEN_OR_CREATE:
+                options.add(StandardOpenOption.CREATE);
+                break;
+            case SftpConstants.SSH_FXF_TRUNCATE_EXISTING:
+                options.add(StandardOpenOption.TRUNCATE_EXISTING);
+                break;
+            default:    // ignored
+        }
+        if ((flags & SftpConstants.SSH_FXF_APPEND_DATA) != 0) {
+            options.add(StandardOpenOption.APPEND);
+        }
+
+        return options;
+    }
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
index ad166a9..a860eec 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/Handle.java
@@ -24,6 +24,7 @@ import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.sshd.common.util.ValidateUtils;
+import org.apache.sshd.server.session.ServerSession;
 
 /**
  * @author <a href="mailto:d...@mina.apache.org";>Apache MINA SSHD Project</a>
@@ -38,6 +39,18 @@ public abstract class Handle implements 
java.nio.channels.Channel {
         this.handle = ValidateUtils.checkNotNullAndNotEmpty(handle, "No 
assigned handle for %s", file);
     }
 
+    protected void signalHandleOpening(SftpSubsystem subsystem) throws 
IOException {
+        SftpEventListener listener = subsystem.getSftpEventListenerProxy();
+        ServerSession session = subsystem.getServerSession();
+        listener.opening(session, handle, this);
+    }
+
+    protected void signalHandleOpen(SftpSubsystem subsystem) throws 
IOException {
+        SftpEventListener listener = subsystem.getSftpEventListenerProxy();
+        ServerSession session = subsystem.getServerSession();
+        listener.open(session, handle, this);
+    }
+
     public Path getFile() {
         return file;
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
index 6af7199..c518af3 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpEventListener.java
@@ -56,6 +56,19 @@ public interface SftpEventListener extends SshdEventListener 
{
     }
 
     /**
+     * Specified file / directory is being opened
+     *
+     * @param session      The {@link ServerSession} through which the request 
was handled
+     * @param remoteHandle The (opaque) assigned handle for the file / 
directory
+     * @param localHandle  The associated file / directory {@link Handle}
+     * @throws IOException If failed to handle the call
+     */
+    default void opening(ServerSession session, String remoteHandle, Handle 
localHandle)
+            throws IOException {
+        // ignored
+    }
+
+    /**
      * Specified file / directory has been opened
      *
      * @param session      The {@link ServerSession} through which the request 
was handled
@@ -65,7 +78,7 @@ public interface SftpEventListener extends SshdEventListener {
      */
     default void open(ServerSession session, String remoteHandle, Handle 
localHandle)
             throws IOException {
-                // ignored
+        // ignored
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/8e255459/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
index dab5960..fb41d33 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/subsystem/sftp/SftpSubsystem.java
@@ -1902,8 +1902,6 @@ public class SftpSubsystem
         } else {
             String handle = generateFileHandle(p);
             DirectoryHandle dirHandle = new DirectoryHandle(this, p, handle);
-            SftpEventListener listener = getSftpEventListenerProxy();
-            listener.open(getServerSession(), handle, dirHandle);
             handles.put(handle, dirHandle);
             return handle;
         }
@@ -2228,8 +2226,6 @@ public class SftpSubsystem
         Path file = resolveFile(path);
         String handle = generateFileHandle(file);
         FileHandle fileHandle = new FileHandle(this, file, handle, pflags, 
access, attrs);
-        SftpEventListener listener = getSftpEventListenerProxy();
-        listener.open(getServerSession(), handle, fileHandle);
         handles.put(handle, fileHandle);
         return handle;
     }

Reply via email to