You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by gn...@apache.org on 2014/10/17 16:06:40 UTC

[2/2] git commit: [SSHD-355] Correctly handle permissions when opening files through SFTP [SSHD-364] Fix SFTP writes with offset

[SSHD-355] Correctly handle permissions when opening files through SFTP
[SSHD-364] Fix SFTP writes with offset

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

Branch: refs/heads/master
Commit: d4a524e428449f53a0e4b2402ca5419a436eb77d
Parents: 8e99ed7
Author: Guillaume Nodet <gn...@apache.org>
Authored: Fri Oct 17 15:32:16 2014 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Fri Oct 17 16:00:17 2014 +0200

----------------------------------------------------------------------
 .../sshd/client/sftp/DefaultSftpClient.java     |  2 +-
 .../common/file/nativefs/NativeSshFile.java     | 34 ++++----
 .../common/file/nativefs/NativeSshFileNio.java  | 37 +++++++++
 .../apache/sshd/server/sftp/SftpSubsystem.java  | 37 +++++++--
 .../src/test/java/org/apache/sshd/SftpTest.java | 81 +++++++++++++++++++-
 5 files changed, 163 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/d4a524e4/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
index 9a945cf..8eb6b52 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/sftp/DefaultSftpClient.java
@@ -700,7 +700,7 @@ public class DefaultSftpClient implements SftpClient {
     }
 
     public OutputStream write(final String path) throws IOException {
-        return write(path, EnumSet.of(OpenMode.Write));
+        return write(path, EnumSet.of(OpenMode.Write, OpenMode.Create, OpenMode.Truncate));
     }
 
     public OutputStream write(final String path, final EnumSet<OpenMode> mode) throws IOException {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/d4a524e4/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
index d63d446..8b336ef 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFile.java
@@ -22,6 +22,7 @@ package org.apache.sshd.common.file.nativefs;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -318,13 +319,8 @@ public class NativeSshFile implements SshFile {
     /**
      * Truncate file to length 0.
      */
-    public void truncate() throws IOException{
-        RandomAccessFile tempFile = new RandomAccessFile(file, "rw");
-        try {
-            tempFile.setLength(0);
-        } finally {
-            tempFile.close();
-        }
+    public void truncate() throws IOException {
+        new FileWriter(file).close();
     }
 
     /**
@@ -412,9 +408,12 @@ public class NativeSshFile implements SshFile {
         }
 
         // move to the appropriate offset and create output stream
+        final boolean canRead = file.canRead();
+        if (!canRead) {
+            file.setReadable(true, true);
+        }
         final RandomAccessFile raf = new RandomAccessFile(file, "rw");
         try {
-            raf.setLength(offset);
             raf.seek(offset);
 
             // The IBM jre needs to have both the stream and the random access file
@@ -423,6 +422,9 @@ public class NativeSshFile implements SshFile {
                 public void close() throws IOException {
                     super.close();
                     raf.close();
+                    if (!canRead) {
+                        file.setReadable(false, true);
+                    }
                 }
             };
         } catch (IOException e) {
@@ -442,20 +444,12 @@ public class NativeSshFile implements SshFile {
         }
 
         // move to the appropriate offset and create input stream
-        final RandomAccessFile raf = new RandomAccessFile(file, "r");
+        final FileInputStream fis = new FileInputStream(file);
         try {
-            raf.seek(offset);
-
-            // The IBM jre needs to have both the stream and the random access file
-            // objects closed to actually close the file
-            return new FileInputStream(raf.getFD()) {
-                public void close() throws IOException {
-                    super.close();
-                    raf.close();
-                }
-            };
+            fis.getChannel().position(offset);
+            return fis;
         } catch (IOException e) {
-            raf.close();
+            fis.close();
             throw e;
         }
     }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/d4a524e4/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
index 1cfef6b..9e67c72 100644
--- a/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
+++ b/sshd-core/src/main/java/org/apache/sshd/common/file/nativefs/NativeSshFileNio.java
@@ -22,11 +22,15 @@ package org.apache.sshd.common.file.nativefs;
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
 import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
 import java.nio.file.Files;
 import java.nio.file.LinkOption;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.GroupPrincipal;
 import java.nio.file.attribute.PosixFilePermission;
@@ -212,4 +216,37 @@ public class NativeSshFileNio extends NativeSshFile {
         return set;
     }
 
+    @Override
+    public OutputStream createOutputStream(long offset) throws IOException {
+        Path path = file.toPath();
+        final SeekableByteChannel sbc = Files.newByteChannel(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
+        if (offset > 0) {
+            sbc.position(offset);
+        }
+        return new OutputStream() {
+            @Override
+            public void write(int b) throws IOException {
+                write(new byte[] { (byte) b }, 0, 1);
+            }
+
+            @Override
+            public void write(byte[] b, int off, int len) throws IOException {
+                if (b == null) {
+                    throw new NullPointerException();
+                } else if ((off < 0) || (off > b.length) || (len < 0) ||
+                        ((off + len) > b.length) || ((off + len) < 0)) {
+                    throw new IndexOutOfBoundsException();
+                } else if (len == 0) {
+                    return;
+                }
+                sbc.write(ByteBuffer.wrap(b, off, len));
+            }
+
+            @Override
+            public void close() throws IOException {
+                sbc.close();
+            }
+        };
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/d4a524e4/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
index 063ae17..fbc27cd 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
@@ -228,17 +228,22 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
     }
 
     protected static class FileHandle extends Handle {
+        int flags;
         OutputStream output;
         long outputPos;
         InputStream input;
         long inputPos;
         long length;
 
-        public FileHandle(SshFile sshFile) {
+        public FileHandle(SshFile sshFile, int flags) {
             super(sshFile);
+            this.flags = flags;
         }
 
         public int read(byte[] data, long offset) throws IOException {
+            if ((flags & SSH_FXF_READ) == 0) {
+                throw new IOException("File has not been opened for reading");
+            }
             if (input != null && offset >= length) {
                 return -1;
             }
@@ -260,6 +265,12 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
         }
 
         public void write(byte[] data, long offset) throws IOException {
+            if ((flags & SSH_FXF_WRITE) == 0) {
+                throw new IOException("File has not been opened for writing");
+            }
+            if ((flags & SSH_FXF_APPEND) != 0) {
+                offset = (output != null) ? outputPos : file.getSize();
+            }
             if (output != null && offset != outputPos) {
                 IoUtils.closeQuietly(output);
                 output = null;
@@ -406,6 +417,14 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                 try {
                     SshFile file = resolveFile(path);
                     if (file.doesExist()) {
+                        if ((pflags & SSH_FXP_READ) != 0 && !file.isReadable()) {
+                            sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not read " + path);
+                            return;
+                        }
+                        if ((pflags & SSH_FXP_WRITE) != 0 && !file.isWritable()) {
+                            sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not write " + path);
+                            return;
+                        }
                         if (((pflags & SSH_FXF_CREAT) != 0) && ((pflags & SSH_FXF_EXCL) != 0)) {
                             sendStatus(id, SSH_FX_FAILURE, path);
                             return;
@@ -416,19 +435,27 @@ public class SftpSubsystem implements Command, Runnable, SessionAware, FileSyste
                                 sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not create " + path);
                                 return;
                             }
-                            file.create();
+                            if (!file.create()) {
+                                sendStatus(id, SSH_FX_NO_SUCH_FILE, "No such file " + path);
+                                return;
+                            }
+                        } else {
+                            sendStatus(id, SSH_FX_NO_SUCH_FILE, "No such file " + path);
+                            return;
                         }
                     }
-                    String acc = ((pflags & (SSH_FXF_READ | SSH_FXF_WRITE)) != 0 ? "r" : "") +
-                            ((pflags & SSH_FXF_WRITE) != 0 ? "w" : "");
                     if ((pflags & SSH_FXF_TRUNC) != 0) {
+                        if (!file.isWritable()) {
+                            sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not truncate " + path);
+                            return;
+                        }
                         file.truncate();
                     }
                     if ((pflags & SSH_FXF_CREAT) != 0) {
                         file.setAttributes(attrs);
                     }
                     String handle = UUID.randomUUID().toString();
-                    handles.put(handle, new FileHandle(file));
+                    handles.put(handle, new FileHandle(file, pflags));
                     sendHandle(id, handle);
                 } catch (IOException e) {
                     sendStatus(id, SSH_FX_FAILURE, e.getMessage() == null ? "" : e.getMessage());

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/d4a524e4/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/test/java/org/apache/sshd/SftpTest.java b/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
index 62c72a5..78aa49b 100644
--- a/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
+++ b/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
@@ -21,6 +21,8 @@ package org.apache.sshd;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOError;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.net.URI;
@@ -52,6 +54,7 @@ import static junit.framework.Assert.assertNull;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 public class SftpTest extends BaseTest {
 
@@ -93,6 +96,80 @@ public class SftpTest extends BaseTest {
     }
 
     @Test
+    public void testOpen() throws Exception {
+        SshClient client = SshClient.setUpDefaultClient();
+        client.start();
+        ClientSession session = client.connect("x", "localhost", port).await().getSession();
+        session.addPasswordIdentity("x");
+        session.auth().verify();
+
+        String file = "target/sftp/client/test.txt";
+
+        new File(file).getParentFile().mkdirs();
+        new File(file).createNewFile();
+        new File(file).setWritable(false, false);
+        new File(file).setReadable(false, false);
+
+        SftpClient sftp = session.createSftpClient();
+        SftpClient.Handle h;
+
+        try {
+            sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read));
+            fail("Should have failed");
+        } catch (IOException e) {
+            // ok
+        }
+
+        try {
+            sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write));
+            fail("Should have failed");
+        } catch (IOException e) {
+            // ok
+        }
+
+        try {
+            sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate));
+            fail("Should have failed");
+        } catch (IOException e) {
+            // ok
+        }
+
+        Assert.assertEquals(0, (sftp.stat(file).perms & (SftpClient.S_IWUSR | SftpClient.S_IRUSR)));
+
+        new File(file).setWritable(true, false);
+
+        h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Truncate, SftpClient.OpenMode.Write));
+        sftp.close(h);
+
+        h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write));
+        byte[] d = "0123456789\n".getBytes();
+        sftp.write(h, 0, d, 0, d.length);
+        sftp.write(h, d.length, d, 0, d.length);
+        sftp.close(h);
+        h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write));
+        sftp.write(h, d.length * 2, d, 0, d.length);
+        sftp.close(h);
+        h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Write));
+        sftp.write(h, 3, "-".getBytes(), 0, 1);
+        sftp.close(h);
+
+        try {
+            sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read));
+            fail("Should have failed");
+        } catch (IOException e) {
+            // ok
+        }
+
+        new File(file).setReadable(true, false);
+
+        h = sftp.open(file, EnumSet.of(SftpClient.OpenMode.Read));
+        byte[] buf = new byte[3];
+        int l = sftp.read(h, 2l, buf, 0, 3);
+        assertEquals("2-4", new String(buf, 0, l));
+        sftp.close(h);
+    }
+
+    @Test
     public void testClient() throws Exception {
         SshClient client = SshClient.setUpDefaultClient();
         client.start();
@@ -109,7 +186,7 @@ public class SftpTest extends BaseTest {
 
         sftp.mkdir("target/sftp/client");
 
-        SftpClient.Handle h = sftp.open("target/sftp/client/test.txt", EnumSet.of(SftpClient.OpenMode.Write));
+        SftpClient.Handle h = sftp.open("target/sftp/client/test.txt", EnumSet.of(SftpClient.OpenMode.Write, SftpClient.OpenMode.Create));
         byte[] d = "0123456789\n".getBytes();
         sftp.write(h, 0, d, 0, d.length);
         sftp.write(h, d.length, d, 0, d.length);
@@ -208,7 +285,7 @@ public class SftpTest extends BaseTest {
         c.disconnect();
 
         assertTrue(target.exists());
-        assertEquals("01234a", readFile(unixPath));
+        assertEquals("01234a6789", readFile(unixPath));
 
         target.delete();
         assertFalse(target.exists());