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();
+ }
+
+}