Author: gnodet
Date: Thu Apr 29 07:48:25 2010
New Revision: 939223

URL: http://svn.apache.org/viewvc?rev=939223&view=rev
Log:
SSHD-55: Support for SFTP on the server side (incomplete)

Added:
    mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/
    
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
    mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
Modified:
    
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java

Modified: 
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java
URL: 
http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java?rev=939223&r1=939222&r2=939223&view=diff
==============================================================================
--- 
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java 
(original)
+++ 
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/common/util/Buffer.java 
Thu Apr 29 07:48:25 2010
@@ -141,7 +141,7 @@ public final class Buffer {
     public int getInt() {
         return (int) getUInt();
     }
-    
+
     public long getUInt()
     {
         ensureAvailable(4);
@@ -152,6 +152,20 @@ public final class Buffer {
         return l;        
     }
 
+    public long getLong()
+    {
+        ensureAvailable(8);
+        long l = ((data[rpos++] << 56) & 0xff00000000000000L)|
+                 ((data[rpos++] << 48) & 0x00ff000000000000L)|
+                 ((data[rpos++] << 40) & 0x0000ff0000000000L)|
+                 ((data[rpos++] << 32) & 0x000000ff00000000L)|
+                 ((data[rpos++] << 24) & 0x00000000ff000000L)|
+                 ((data[rpos++] << 16) & 0x0000000000ff0000L)|
+                 ((data[rpos++] <<  8) & 0x000000000000ff00L)|
+                 ((data[rpos++]      ) & 0x00000000000000ffL);
+        return l;
+    }
+
     public boolean getBoolean() {
         return getByte() != 0;
     }

Added: 
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
URL: 
http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java?rev=939223&view=auto
==============================================================================
--- 
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
 (added)
+++ 
mina/sshd/trunk/sshd-core/src/main/java/org/apache/sshd/server/sftp/SftpSubsystem.java
 Thu Apr 29 07:48:25 2010
@@ -0,0 +1,881 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd.server.sftp;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.session.ServerSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * SFTP subsystem
+ *
+ * @author <a href="mailto:[email protected]";>Apache MINA SSHD Project</a>
+ */
+public class SftpSubsystem implements Command, Runnable, SessionAware {
+       
+       protected final Logger log = LoggerFactory.getLogger(getClass());
+    
+       public static class Factory implements NamedFactory<Command> {
+        public Command create() {
+            return new SftpSubsystem();
+        }
+        public String getName() {
+            return "sftp";
+        }
+    }
+
+       public static final int HIGHEST_SFTP_IMPL = 3; // Working 
implementation up to v3, v4 and v5 are work in progress
+
+       
+    public static final int SSH_FXP_INIT             =   1;
+    public static final int SSH_FXP_VERSION          =   2;
+    public static final int SSH_FXP_OPEN             =   3;
+    public static final int SSH_FXP_CLOSE            =   4;
+    public static final int SSH_FXP_READ             =   5;
+    public static final int SSH_FXP_WRITE            =   6;
+    public static final int SSH_FXP_LSTAT            =   7;
+    public static final int SSH_FXP_FSTAT            =   8;
+    public static final int SSH_FXP_SETSTAT          =   9;
+    public static final int SSH_FXP_FSETSTAT         =  10;
+    public static final int SSH_FXP_OPENDIR          =  11;
+    public static final int SSH_FXP_READDIR          =  12;
+    public static final int SSH_FXP_REMOVE           =  13;
+    public static final int SSH_FXP_MKDIR            =  14;
+    public static final int SSH_FXP_RMDIR            =  15;
+    public static final int SSH_FXP_REALPATH         =  16;
+    public static final int SSH_FXP_STAT             =  17;
+    public static final int SSH_FXP_RENAME           =  18;
+    public static final int SSH_FXP_READLINK         =  19;
+    public static final int SSH_FXP_LINK             =  21;
+    public static final int SSH_FXP_BLOCK            =  22;
+    public static final int SSH_FXP_UNBLOCK          =  23;
+
+    public static final int SSH_FXP_STATUS           = 101;
+    public static final int SSH_FXP_HANDLE           = 102;
+    public static final int SSH_FXP_DATA             = 103;
+    public static final int SSH_FXP_NAME             = 104;
+    public static final int SSH_FXP_ATTRS            = 105;
+
+    public static final int SSH_FXP_EXTENDED         = 200;
+    public static final int SSH_FXP_EXTENDED_REPLY   = 201;
+
+    public static final int SSH_FX_OK                           = 0;
+    public static final int SSH_FX_EOF                          = 1;
+    public static final int SSH_FX_NO_SUCH_FILE                 = 2;
+    public static final int SSH_FX_PERMISSION_DENIED            = 3;
+    public static final int SSH_FX_FAILURE                      = 4;
+    public static final int SSH_FX_BAD_MESSAGE                  = 5;
+    public static final int SSH_FX_NO_CONNECTION                = 6;
+    public static final int SSH_FX_CONNECTION_LOST              = 7;
+    public static final int SSH_FX_OP_UNSUPPORTED               = 8;
+    public static final int SSH_FX_INVALID_HANDLE               = 9;
+    public static final int SSH_FX_NO_SUCH_PATH                 = 10;
+    public static final int SSH_FX_FILE_ALREADY_EXISTS          = 11;
+    public static final int SSH_FX_WRITE_PROTECT                = 12;
+    public static final int SSH_FX_NO_MEDIA                     = 13;
+    public static final int SSH_FX_NO_SPACE_ON_FILESYSTEM       = 14;
+    public static final int SSH_FX_QUOTA_EXCEEDED               = 15;
+    public static final int SSH_FX_UNKNOWN_PRINCIPAL            = 16;
+    public static final int SSH_FX_LOCK_CONFLICT                = 17;
+    public static final int SSH_FX_DIR_NOT_EMPTY                = 18;
+    public static final int SSH_FX_NOT_A_DIRECTORY              = 19;
+    public static final int SSH_FX_INVALID_FILENAME             = 20;
+    public static final int SSH_FX_LINK_LOOP                    = 21;
+    public static final int SSH_FX_CANNOT_DELETE                = 22;
+    public static final int SSH_FX_INVALID_PARAMETER            = 23;
+    public static final int SSH_FX_FILE_IS_A_DIRECTORY          = 24;
+    public static final int SSH_FX_BYTE_RANGE_LOCK_CONFLICT     = 25;
+    public static final int SSH_FX_BYTE_RANGE_LOCK_REFUSED      = 26;
+    public static final int SSH_FX_DELETE_PENDING               = 27;
+    public static final int SSH_FX_FILE_CORRUPT                 = 28;
+    public static final int SSH_FX_OWNER_INVALID                = 29;
+    public static final int SSH_FX_GROUP_INVALID                = 30;
+    public static final int SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK  = 31;
+
+    public static final int SSH_FILEXFER_ATTR_SIZE              = 0x00000001;
+    public static final int SSH_FILEXFER_ATTR_PERMISSIONS       = 0x00000004;
+    public static final int SSH_FILEXFER_ATTR_ACCESSTIME        = 0x00000008;
+    public static final int SSH_FILEXFER_ATTR_CREATETIME        = 0x00000010;
+    public static final int SSH_FILEXFER_ATTR_MODIFYTIME        = 0x00000020;
+    public static final int SSH_FILEXFER_ATTR_ACL               = 0x00000040;
+    public static final int SSH_FILEXFER_ATTR_OWNERGROUP        = 0x00000080;
+    public static final int SSH_FILEXFER_ATTR_SUBSECOND_TIMES   = 0x00000100;
+    public static final int SSH_FILEXFER_ATTR_BITS              = 0x00000200;
+    public static final int SSH_FILEXFER_ATTR_ALLOCATION_SIZE   = 0x00000400;
+    public static final int SSH_FILEXFER_ATTR_TEXT_HINT         = 0x00000800;
+    public static final int SSH_FILEXFER_ATTR_MIME_TYPE         = 0x00001000;
+    public static final int SSH_FILEXFER_ATTR_LINK_COUNT        = 0x00002000;
+    public static final int SSH_FILEXFER_ATTR_UNTRANSLATED_NAME = 0x00004000;
+    public static final int SSH_FILEXFER_ATTR_CTIME             = 0x00008000;
+    public static final int SSH_FILEXFER_ATTR_EXTENDED          = 0x80000000;
+
+    public static final int SSH_FILEXFER_TYPE_REGULAR         = 1;
+    public static final int SSH_FILEXFER_TYPE_DIRECTORY       = 2;
+    public static final int SSH_FILEXFER_TYPE_SYMLINK         = 3;
+    public static final int SSH_FILEXFER_TYPE_SPECIAL         = 4;
+    public static final int SSH_FILEXFER_TYPE_UNKNOWN         = 5;
+    public static final int SSH_FILEXFER_TYPE_SOCKET          = 6;
+    public static final int SSH_FILEXFER_TYPE_CHAR_DEVICE     = 7;
+    public static final int SSH_FILEXFER_TYPE_BLOCK_DEVICE    = 8;
+    public static final int SSH_FILEXFER_TYPE_FIFO            = 9;
+
+
+    public static final int SSH_FXF_ACCESS_DISPOSITION      = 0x00000007;
+    public static final int SSH_FXF_CREATE_NEW              = 0x00000000;
+    public static final int SSH_FXF_CREATE_TRUNCATE         = 0x00000001;
+    public static final int SSH_FXF_OPEN_EXISTING           = 0x00000002;
+    public static final int SSH_FXF_OPEN_OR_CREATE          = 0x00000003;
+    public static final int SSH_FXF_TRUNCATE_EXISTING       = 0x00000004;
+    public static final int SSH_FXF_APPEND_DATA             = 0x00000008;
+    public static final int SSH_FXF_APPEND_DATA_ATOMIC      = 0x00000010;
+    public static final int SSH_FXF_TEXT_MODE               = 0x00000020;
+    public static final int SSH_FXF_BLOCK_READ              = 0x00000040;
+    public static final int SSH_FXF_BLOCK_WRITE             = 0x00000080;
+    public static final int SSH_FXF_BLOCK_DELETE            = 0x00000100;
+    public static final int SSH_FXF_BLOCK_ADVISORY          = 0x00000200;
+    public static final int SSH_FXF_NOFOLLOW                = 0x00000400;
+    public static final int SSH_FXF_DELETE_ON_CLOSE         = 0x00000800;
+    public static final int SSH_FXF_ACCESS_AUDIT_ALARM_INFO = 0x00001000;
+    public static final int SSH_FXF_ACCESS_BACKUP           = 0x00002000;
+    public static final int SSH_FXF_BACKUP_STREAM           = 0x00004000;
+    public static final int SSH_FXF_OVERRIDE_OWNER          = 0x00008000;
+
+    public static final int SSH_FXF_READ           = 0x00000001;
+    public static final int SSH_FXF_WRITE          = 0x00000002;
+    public static final int SSH_FXF_APPEND         = 0x00000004;
+    public static final int SSH_FXF_CREAT          = 0x00000008;
+    public static final int SSH_FXF_TRUNC          = 0x00000010;
+    public static final int SSH_FXF_EXCL           = 0x00000020;
+    public static final int SSH_FXF_TEXT           = 0x00000040;
+
+    public static final int ACE4_READ_DATA            = 0x00000001;
+    public static final int ACE4_LIST_DIRECTORY       = 0x00000001;
+    public static final int ACE4_WRITE_DATA           = 0x00000002;
+    public static final int ACE4_ADD_FILE             = 0x00000002;
+    public static final int ACE4_APPEND_DATA          = 0x00000004;
+    public static final int ACE4_ADD_SUBDIRECTORY     = 0x00000004;
+    public static final int ACE4_READ_NAMED_ATTRS     = 0x00000008;
+    public static final int ACE4_WRITE_NAMED_ATTRS    = 0x00000010;
+    public static final int ACE4_EXECUTE              = 0x00000020;
+    public static final int ACE4_DELETE_CHILD         = 0x00000040;
+    public static final int ACE4_READ_ATTRIBUTES      = 0x00000080;
+    public static final int ACE4_WRITE_ATTRIBUTES     = 0x00000100;
+    public static final int ACE4_DELETE               = 0x00010000;
+    public static final int ACE4_READ_ACL             = 0x00020000;
+    public static final int ACE4_WRITE_ACL            = 0x00040000;
+    public static final int ACE4_WRITE_OWNER          = 0x00080000;
+       
+    public static final int S_IRUSR = 0000400;
+    public static final int S_IWUSR = 0000200;
+    public static final int S_IXUSR = 0000100;
+    public static final int S_IRGRP = 0000040;
+    public static final int S_IWGRP = 0000020;
+    public static final int S_IXGRP = 0000010;
+    public static final int S_IROTH = 0000004;
+    public static final int S_IWOTH = 0000002;
+    public static final int S_IXOTH = 0000001;
+    public static final int S_ISUID = 0004000;
+    public static final int S_ISGID = 0002000;
+    public static final int S_ISVTX = 0001000;
+
+
+    private ExitCallback callback;
+    private InputStream in;
+    private OutputStream out;
+    private OutputStream err;
+    private Environment env;
+    private ServerSession session;
+    private boolean closed = false;
+
+
+    private int version;
+    private Map<String, Handle> handles = new HashMap<String, Handle>();
+
+
+    protected static abstract class Handle {
+        File file;
+
+        public Handle(File file) {
+            this.file = file;
+        }
+
+        public File getFile() {
+            return file;
+        }
+
+        public void close() throws IOException {
+        }
+
+    }
+
+    protected static class DirectoryHandle extends Handle {
+        boolean done;
+
+        public DirectoryHandle(File file) {
+            super(file);
+        }
+        public boolean isDone() {
+            return done;
+        }
+
+        public void setDone(boolean done) {
+            this.done = done;
+        }
+    }
+
+    protected static class FileHandle extends Handle {
+        RandomAccessFile raf;
+        int flags;
+
+        public FileHandle(File file, RandomAccessFile raf, int flags) {
+            super(file);
+            this.raf = raf;
+            this.flags = flags;
+        }
+
+        public RandomAccessFile getRaf() {
+            return raf;
+        }
+
+        public int getFlags() {
+            return flags;
+        }
+
+        @Override
+        public void close() throws IOException {
+            raf.close();
+        }
+    }
+
+       public void setSession(ServerSession session) {
+               this.session = session;
+       }
+
+    public void setExitCallback(ExitCallback callback) {
+        this.callback = callback;
+    }
+
+    public void setInputStream(InputStream in) {
+        this.in = in;
+    }
+
+    public void setOutputStream(OutputStream out) {
+        this.out = out;
+    }
+
+    public void setErrorStream(OutputStream err) {
+        this.err = err;
+    }
+
+    public void start(Environment env) throws IOException {
+        this.env = env;
+        new Thread(this).start();
+    }
+
+    public void run() {
+               DataInputStream dis = null;
+        try {
+            dis = new DataInputStream(in);
+            while (true) {
+                int  length = dis.readInt();
+                if (length < 5) {
+                    throw new IllegalArgumentException();
+                }
+                Buffer buffer = new Buffer(length + 4);
+                buffer.putInt(length);
+                int nb = length;
+                while (nb > 0) {
+                    int l = dis.read(buffer.array(), buffer.wpos(), nb);
+                    if (l < 0) {
+                        throw new IllegalArgumentException();
+                    }
+                    buffer.wpos(buffer.wpos() + l);
+                    nb -= l;
+                }
+                process(buffer);
+            }
+        } catch (Throwable t) {
+            if (!closed) {
+                log.error("Exception caught in SFTP subsystem", t);
+            }
+        } finally {
+                   if (dis != null) {
+                       try {
+                               dis.close();
+                       } catch (IOException ioe) {
+                               log.error("Could not close DataInputStream", 
ioe);
+                       }
+               }
+               dis = null;
+
+            callback.onExit(0);
+        }
+    }
+
+    protected void process(Buffer buffer) throws IOException {
+        int length = buffer.getInt();
+        int type   = buffer.getByte();
+        int id     = buffer.getInt();
+        switch (type) {
+            case SSH_FXP_INIT: {
+                if (length != 5) {
+                    throw new IllegalArgumentException();
+                }
+                version = id;
+                if (version >= HIGHEST_SFTP_IMPL) {
+                       buffer.clear();
+                       buffer.putByte((byte) SSH_FXP_VERSION);
+                       buffer.putInt(HIGHEST_SFTP_IMPL);
+                       send(buffer);
+                       version = HIGHEST_SFTP_IMPL;
+                } else {
+                       // We only support version 3 (Version 1 and 2 are not 
common)
+                       sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Babelway SFTP 
server only support SFTP up to version " + HIGHEST_SFTP_IMPL);
+                }
+
+                break;
+            }
+            case SSH_FXP_OPEN: {
+                if (version <= 4) {
+                    String path   = buffer.getString();
+                    int    pflags = buffer.getInt();
+                    // attrs
+                    try {
+                        File file = new File(path);
+                        RandomAccessFile raf;
+                        if (file.exists()) {
+                            if (((pflags & SSH_FXF_CREAT) != 0) && ((pflags & 
SSH_FXF_EXCL) != 0)) {
+                                sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, 
path);
+                                return;
+                            }
+                        } else {
+                            if (((pflags & SSH_FXF_CREAT) != 0)) {
+                                if (!file.createNewFile()) {
+                                    sendStatus(id, SSH_FX_FAILURE, "Can not 
create " + path);
+                                }
+                            }
+                        }
+                        String acc = ((pflags & (SSH_FXF_READ | 
SSH_FXF_WRITE)) != 0 ? "r" : "") +
+                                        ((pflags & SSH_FXF_WRITE) != 0 ? "w" : 
"");
+                        raf = new RandomAccessFile(file, acc);
+                        if ((pflags & SSH_FXF_TRUNC) != 0) {
+                            raf.setLength(0);
+                        }
+                        String handle = UUID.randomUUID().toString();
+                        handles.put(handle, new FileHandle(file, raf, 
pflags)); // handle flags conversion
+                        sendHandle(id, handle);
+                    } catch (IOException e) {
+                        sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                    }
+                } else {
+                    String path  = buffer.getString();
+                    int    acc   = buffer.getInt();
+                    int    flags = buffer.getInt();
+                    // attrs
+                    try {
+                        File file = new File(path);
+                        RandomAccessFile raf;
+                        switch (flags & SSH_FXF_ACCESS_DISPOSITION) {
+                            case SSH_FXF_CREATE_NEW: {
+                                if (file.exists()) {
+                                    sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, 
path);
+                                    return;
+                                } else if (!file.createNewFile()) {
+                                    sendStatus(id, SSH_FX_FAILURE, "Can not 
create " + path);
+                                }
+                                raf = new RandomAccessFile(file, "rw"); // 
TODO: handle access
+                                break;
+                            }
+                            case SSH_FXF_CREATE_TRUNCATE: {
+                                if (file.exists()) {
+                                    sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, 
path);
+                                    return;
+                                } else if (!file.createNewFile()) {
+                                    sendStatus(id, SSH_FX_FAILURE, "Can not 
create " + path);
+                                }
+                                raf = new RandomAccessFile(file, "rw"); // 
TODO: handle access
+                                raf.setLength(0);
+                                break;
+                            }
+                            case SSH_FXF_OPEN_EXISTING: {
+                                if (!file.exists()) {
+                                    if (!file.getParentFile().exists()) {
+                                        sendStatus(id, SSH_FX_NO_SUCH_PATH, 
path);
+                                    } else {
+                                        sendStatus(id, SSH_FX_NO_SUCH_FILE, 
path);
+                                    }
+                                    return;
+                                }
+                                raf = new RandomAccessFile(file, "rw"); // 
TODO: handle access
+                                break;
+                            }
+                            case SSH_FXF_OPEN_OR_CREATE: {
+                                raf = new RandomAccessFile(file, "rw"); // 
TODO: handle access
+                                break;
+                            }
+                            case SSH_FXF_TRUNCATE_EXISTING: {
+                                if (!file.exists()) {
+                                    if (!file.getParentFile().exists()) {
+                                        sendStatus(id, SSH_FX_NO_SUCH_PATH, 
path);
+                                    } else {
+                                        sendStatus(id, SSH_FX_NO_SUCH_FILE, 
path);
+                                    }
+                                    return;
+                                }
+                                raf = new RandomAccessFile(file, "rw"); // 
TODO: handle access
+                                raf.setLength(0);
+                                break;
+                            }
+                            default:
+                                throw new 
IllegalArgumentException("Unsupported open mode: " + flags);
+                        }
+                        String handle = UUID.randomUUID().toString();
+                        handles.put(handle, new FileHandle(file, raf, flags));
+                        sendHandle(id, handle);
+                    } catch (IOException e) {
+                        sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                    }
+                }
+                break;
+            }
+            case SSH_FXP_CLOSE: {
+                String handle = buffer.getString();
+                try {
+                    Handle h = handles.get(handle);
+                    if (h == null) {
+                        sendStatus(id, SSH_FX_INVALID_HANDLE, handle, "");
+                    } else {
+                        handles.remove(handle);
+                        h.close();
+                        sendStatus(id, SSH_FX_OK, "", "");
+                    }
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_READ: {
+                String handle = buffer.getString();
+                long   offset = buffer.getLong();
+                int    len    = buffer.getInt();
+                try {
+                    Handle p = handles.get(handle);
+                    if (!(p instanceof FileHandle)) {
+                        sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+                    } else {
+                        RandomAccessFile raf = ((FileHandle) p).getRaf();
+                        raf.seek(offset);
+                        byte[] b = new byte[Math.max(len, 1024 * 32)];
+                        len = raf.read(b);
+                        if (len >= 0) {
+                            Buffer buf = new Buffer(len + 5);
+                            buf.putByte((byte) SSH_FXP_DATA);
+                            buf.putInt(id);
+                            buf.putBytes(b, 0, len);
+                            buf.putBoolean(len == 0);
+                            send(buf);
+                        } else {
+                            sendStatus(id, SSH_FX_EOF, "");
+                        }
+                    }
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_WRITE: {
+                String handle = buffer.getString();
+                long   offset = buffer.getLong();
+                byte[] data   = buffer.getBytes();
+                try {
+                    Handle p = handles.get(handle);
+                    if (!(p instanceof FileHandle)) {
+                        sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+                    } else {
+                        RandomAccessFile raf = ((FileHandle) p).getRaf();
+                        raf.seek(offset); // TODO: handle append flags
+                        raf.write(data);
+                        sendStatus(id, SSH_FX_OK, "");
+                    }
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_LSTAT:
+            case SSH_FXP_STAT: {
+                String path = buffer.getString();
+                try {
+                    File p = new File(path);
+                    sendAttrs(id, p);
+                } catch (FileNotFoundException e) {
+                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_FSTAT: {
+                String handle = buffer.getString();
+                try {
+                    Handle p = handles.get(handle);
+                    if (p == null) {
+                        sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+                    } else {
+                        sendAttrs(id, p.getFile());
+                    }
+                } catch (FileNotFoundException e) {
+                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_OPENDIR: {
+                String path = buffer.getString();
+                try {
+                    File p = new File(path);
+                    if (!p.exists()) {
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
+                    } else if (!p.isDirectory()) {
+                        sendStatus(id, SSH_FX_NOT_A_DIRECTORY, path);
+                    } else  if (!p.canRead()) {
+                        sendStatus(id, SSH_FX_PERMISSION_DENIED, path);
+                    } else {
+                        String handle = UUID.randomUUID().toString();
+                        handles.put(handle, new DirectoryHandle(p));
+                        sendHandle(id, handle);
+                    }
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_READDIR: {
+                String handle = buffer.getString();
+                try {
+                    Handle p = handles.get(handle);
+                    if (!(p instanceof DirectoryHandle)) {
+                        sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+                    } else if (((DirectoryHandle) p).isDone()) {
+                        sendStatus(id, SSH_FX_EOF, "", "");
+                    } else if (!p.getFile().exists()) {
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, 
p.getFile().getPath());
+                    } else if (!p.getFile().isDirectory()) {
+                        sendStatus(id, SSH_FX_NOT_A_DIRECTORY, 
p.getFile().getPath());
+                    } else if (!p.getFile().canRead()) {
+                        sendStatus(id, SSH_FX_PERMISSION_DENIED, 
p.getFile().getPath());
+                    } else {
+                        sendName(id, p.getFile().listFiles());
+                        ((DirectoryHandle) p).setDone(true);
+                    }
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_REMOVE: {
+                String path = buffer.getString();
+                try {
+                    File p = new File(path);
+                    if (!p.exists()) {
+                        sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getPath());
+                    } else if (p.isDirectory()) {
+                        sendStatus(id, SSH_FX_FILE_IS_A_DIRECTORY, 
p.getPath());
+                    } else if (!p.delete()) {
+                                               sendStatus(id, SSH_FX_FAILURE, 
"Failed to delete file");
+                    } else {
+                                               sendStatus(id, SSH_FX_OK, "");
+                                       }
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_MKDIR: {
+                String path = buffer.getString();
+                // attrs
+                try {
+                    File p = new File(path);
+                    if (p.exists()) {
+                        if (p.isDirectory()) {
+                            sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, 
p.getPath());
+                        } else {
+                            sendStatus(id, SSH_FX_NOT_A_DIRECTORY, 
p.getPath());
+                        }
+                    } else if (!p.mkdir()) {
+                        throw new IOException("Error creating dir " + path);
+                    }
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_RMDIR: {
+                String path = buffer.getString();
+                // attrs
+                try {
+                    File p = new File(path);
+                    if (p.isDirectory()) {
+                        if (p.exists()) {
+                            if (p.listFiles().length == 0) {
+                                if (p.delete()) {
+                                    sendStatus(id, SSH_FX_OK, "");
+                                } else {
+                                    sendStatus(id, SSH_FX_FAILURE, "Unable to 
delete directory " + path);
+                                }
+                            } else {
+                                sendStatus(id, SSH_FX_DIR_NOT_EMPTY, path);
+                            }
+                        } else {
+                            sendStatus(id, SSH_FX_NO_SUCH_PATH, path);
+                        }
+                    } else {
+                        sendStatus(id, SSH_FX_NOT_A_DIRECTORY, p.getPath());
+                    }
+                } catch (IOException e) {
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+            case SSH_FXP_REALPATH: {
+                String path = buffer.getString();
+                if (path.trim().length() == 0) {
+                    path = ".";
+                }
+                // TODO: handle optional args
+                try {
+                    log.info("path="+path);
+                    File p = new File(path);
+                    sendName(id, p);
+                } catch (FileNotFoundException e) {
+                    e.printStackTrace();
+                    sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+                }
+                break;
+            }
+                       
+                       case SSH_FXP_SETSTAT:
+            case SSH_FXP_FSETSTAT: {
+               // This is required for WinSCP / Cyberduck to upload properly
+               // Blindly reply "OK"
+                               // TODO implement it
+                sendStatus(id, SSH_FX_OK, "");
+               break;
+            }   
+                       
+            default:
+                log.error("Received: {}", type);
+                sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + type + " is 
unsupported or not implemented");
+                throw new IllegalStateException();
+        }
+    }
+
+    protected void sendHandle(int id, String handle) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putByte((byte) SSH_FXP_HANDLE);
+        buffer.putInt(id);
+        buffer.putString(handle);
+        send(buffer);
+    }
+
+    protected void sendAttrs(int id, File file) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putByte((byte) SSH_FXP_ATTRS);
+        buffer.putInt(id);
+        writeAttrs(buffer, file);
+        send(buffer);
+    }
+       
+    protected void sendAttrs(int id, File file, int flags) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putByte((byte) SSH_FXP_ATTRS);
+        buffer.putInt(id);
+        writeAttrs(buffer, file, flags);
+        send(buffer);
+    }
+
+
+    protected void sendName(int id, File... files) throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putByte((byte) SSH_FXP_NAME);
+        buffer.putInt(id);
+        buffer.putInt(files.length);
+        for (File f : files) {
+                       buffer.putString(f.getName());
+            if (version <= 3) {
+                buffer.putString(getLongName(f)); // Format specified in the 
specs
+            } else {
+                               buffer.putString(f.getName()); // Supposed to 
be UTF-8
+                       }
+            writeAttrs(buffer, f);
+        }
+        send(buffer);
+    }    
+    private String getLongName(File f) {
+       String username = session.getUsername();
+       if (username.length() > 8) {
+               username = username.substring(0, 8);
+       } else {
+               for (int i=username.length(); i < 8; i++) {
+                       username = username + " ";
+               }
+       }
+       
+       long length = f.length();
+       String lengthString = String.format("%1$#8s", length);
+       
+       StringBuilder sb = new StringBuilder();
+       sb.append((f.isDirectory() ? "d" : "-")); 
+       sb.append((f.canRead() ? "r" : "-")); 
+       sb.append((f.canWrite() ? "w" : "-")); 
+       sb.append((f.canExecute() ? "x" : "-")); 
+       sb.append((f.canRead() ? "r" : "-")); 
+       sb.append((f.canWrite() ? "w" : "-")); 
+       sb.append((f.canExecute() ? "x" : "-")); 
+       sb.append((f.canRead() ? "r" : "-")); 
+       sb.append((f.canWrite() ? "w" : "-")); 
+       sb.append((f.canExecute() ? "x" : "-"));
+       sb.append(" ");
+       sb.append("  1");
+       sb.append(" ");
+       sb.append(username);
+       sb.append(" ");
+       sb.append(username);
+       sb.append(" ");
+       sb.append(lengthString);
+       sb.append(" ");
+       sb.append("Jan 01 00:00 ");
+       sb.append(f.getName());
+       
+       return sb.toString();
+    }
+    
+    protected void writeAttrs(Buffer buffer, File file) throws IOException {
+       writeAttrs(buffer, file, 0);
+    }
+
+
+    protected void writeAttrs(Buffer buffer, File file, int flags) throws 
IOException {
+        if (!file.exists()) {
+            throw new FileNotFoundException(file.getPath());
+        }
+        if (version >= 4) {
+                       long size = file.length();
+               String username = session.getUsername();
+               long lastModif = file.lastModified();
+            int p = 0;
+            if (file.canRead()) {
+                p |= S_IRUSR;
+            }
+            if (file.canWrite()) {
+                p |= S_IWUSR;
+            }
+            if (file.canExecute()) {
+                p |= S_IXUSR;
+            }
+            if (file.isFile()) {
+                buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS);
+                buffer.putByte((byte) SSH_FILEXFER_TYPE_REGULAR);
+                buffer.putInt(p);
+            } else if (file.isDirectory()) {
+                buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS);
+                buffer.putByte((byte) SSH_FILEXFER_TYPE_DIRECTORY);
+                buffer.putInt(p);
+            } else {
+                buffer.putInt(0);
+                buffer.putByte((byte) SSH_FILEXFER_TYPE_UNKNOWN);
+            }
+        } else {
+            int p = 0;
+            if (file.isFile()) {
+                p |= 0100000;
+            }
+            if (file.isDirectory()) {
+                p |= 0040000;
+            }
+            if (file.canRead()) {
+                p |= 0000400;
+            }
+            if (file.canWrite()) {
+                p |= 0000200;
+            }
+            if (file.canExecute()) {
+                p |= 0000100;
+            }
+            if (file.isFile()) {
+                buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS);
+                buffer.putInt(p);
+            } else if (file.isDirectory()) {
+                buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS);
+                buffer.putInt(p);
+            } else {
+                buffer.putInt(0);
+            }
+        }
+    }
+
+    protected void sendStatus(int id, int substatus, String msg) throws 
IOException {
+        sendStatus(id, substatus, msg, "");
+    }
+
+    protected void sendStatus(int id, int substatus, String msg, String lang) 
throws IOException {
+        Buffer buffer = new Buffer();
+        buffer.putByte((byte) SSH_FXP_STATUS);
+        buffer.putInt(id);
+        buffer.putInt(substatus);
+        buffer.putString(msg);
+        buffer.putString(lang);
+        send(buffer);
+    }
+
+    protected void send(Buffer buffer) throws IOException {
+        DataOutputStream dos = new DataOutputStream(out);
+        dos.writeInt(buffer.available());
+        dos.write(buffer.array(), buffer.rpos(), buffer.available());
+        dos.flush();
+    }
+
+    public void destroy() {
+        closed = true;
+    }
+
+
+}

Added: mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/SftpTest.java
URL: 
http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/SftpTest.java?rev=939223&view=auto
==============================================================================
--- mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/SftpTest.java 
(added)
+++ mina/sshd/trunk/sshd-core/src/test/java/org/apache/sshd/SftpTest.java Thu 
Apr 29 07:48:25 2010
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sshd;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.InputStream;
+import java.net.ServerSocket;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.server.Command;
+import org.apache.sshd.server.sftp.SftpSubsystem;
+import org.junit.*;
+
+import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.JSch;
+import com.jcraft.jsch.Logger;
+import com.jcraft.jsch.UserInfo;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.server.command.ScpCommandFactory;
+import org.apache.sshd.util.BogusPasswordAuthenticator;
+import org.apache.sshd.util.EchoShellFactory;
+
+import static org.junit.Assert.*;
+
+public class SftpTest {
+
+    private SshServer sshd;
+    private int port;
+    private com.jcraft.jsch.Session session;
+
+    @Before
+    public void setUp() throws Exception {
+        ServerSocket s = new ServerSocket(0);
+        port = s.getLocalPort();
+        s.close();
+
+        sshd = SshServer.setUpDefaultServer();
+        sshd.setPort(port);
+        sshd.setKeyPairProvider(new FileKeyPairProvider(new 
String[]{"src/test/resources/hostkey.pem"}));
+        sshd.setSubsystemFactories(Arrays.<NamedFactory<Command>>asList(new 
SftpSubsystem.Factory()));
+        sshd.setCommandFactory(new ScpCommandFactory());
+        sshd.setShellFactory(new EchoShellFactory());
+        sshd.setPasswordAuthenticator(new BogusPasswordAuthenticator());
+        sshd.start();
+
+        JSch sch = new JSch();
+        sch.setLogger(new Logger() {
+            public boolean isEnabled(int i) {
+                return true;
+            }
+
+            public void log(int i, String s) {
+                System.out.println("Log(jsch," + i + "): " + s);
+            }
+        });
+        session = sch.getSession("sshd", "localhost", port);
+        session.setUserInfo(new UserInfo() {
+            public String getPassphrase() {
+                return null;
+            }
+
+            public String getPassword() {
+                return "sshd";
+            }
+
+            public boolean promptPassword(String message) {
+                return true;
+            }
+
+            public boolean promptPassphrase(String message) {
+                return false;
+            }
+
+            public boolean promptYesNo(String message) {
+                return true;
+            }
+
+            public void showMessage(String message) {
+            }
+        });
+        session.connect();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        session.disconnect();
+        sshd.stop();
+    }
+
+    @Test
+    @Ignore
+    public void testExternal() throws Exception {
+        System.out.println("SFTP subsystem available on port " + port);
+        Thread.sleep(5 * 60000);
+    }
+
+    @Test
+    public void testSftp() throws Exception {
+        String d = "0123456789\n";
+
+        File root = new File("target/scp");
+        File target = new File("target/scp/out.txt");
+        root.mkdirs();
+        assertTrue(root.exists());
+
+        for (int j = 10; j <= 10; j++) {
+            String data = "";
+            for (int i = 0; i < j; i++) {
+                data = data + d;
+            }
+
+            target.delete();
+            assertFalse(target.exists());
+            sendFile("target/scp/out.txt", data);
+            assertFileLength(target, data.length(), 5000);
+
+            target.delete();
+            assertFalse(target.exists());
+        }
+        root.delete();
+    }
+
+    protected void assertFileLength(File file, long length, long timeout) 
throws Exception {
+        boolean ok = false;
+        while (timeout > 0) {
+            if (file.exists() && file.length() == length) {
+                if (!ok) {
+                    ok = true;
+                } else {
+                    return;
+                }
+            } else {
+                ok = false;
+            }
+            Thread.sleep(100);
+            timeout -= 100;
+        }
+        assertTrue(file.exists());
+        assertEquals(length, file.length());
+    }
+
+    protected String readFile(String path) throws Exception {
+        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
+        c.connect();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        InputStream is = c.get(path);
+        try {
+            byte[] buffer = new byte[256];
+            int count;
+            while (-1 != (count = is.read(buffer))) {
+                bos.write(buffer, 0, count);
+            }
+        } finally {
+            is.close();
+        }
+
+        c.disconnect();
+        return new String(bos.toByteArray());
+    }
+
+    protected void sendFile(String path, String data) throws Exception {
+        ChannelSftp c = (ChannelSftp) session.openChannel("sftp");
+        c.connect();
+        c.put(new ByteArrayInputStream(data.getBytes()), path);
+        c.disconnect();
+    }
+
+}


Reply via email to