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 2013/01/17 14:05:51 UTC
svn commit: r1434657 [2/2] - in /mina/sshd/trunk: ./ assembly/
sshd-core/src/main/java/org/apache/sshd/server/filesystem/
sshd-core/src/main/java/org/apache/sshd/server/session/ sshd-pam/
sshd-sftp/ sshd-sftp/src/ sshd-sftp/src/main/ sshd-sftp/src/main...
Added: mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpWriteRequest.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpWriteRequest.java?rev=1434657&view=auto
==============================================================================
--- mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpWriteRequest.java (added)
+++ mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/SshFxpWriteRequest.java Thu Jan 17 13:05:48 2013
@@ -0,0 +1,104 @@
+/*
+* 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.sftp.request;
+
+import java.util.Arrays;
+
+import org.apache.sshd.sftp.Handle;
+
+/**
+ * Data container for 'SSH_FXP_WRITE' request.
+ *
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ */
+public class SshFxpWriteRequest extends Request {
+ private final String handleId;
+ private final long offset;
+ private final Handle handle;
+ private final byte[] data;
+
+ /**
+ * Creates a SshFxpWriteRequest instance.
+ *
+ * @param id The request id.
+ * @param handleId The according file handle id.
+ * @param offset The write offset.
+ * @param data The write data.
+ * @param handle The according file handle.
+ */
+ public SshFxpWriteRequest(
+ final int id, final String handleId, final long offset, final byte[] data, final Handle handle) {
+ super(id);
+ this.handleId = handleId;
+ this.offset = offset;
+ this.data = Arrays.copyOf(data, data.length);
+ this.handle = handle;
+ }
+
+ /**
+ * Returns the write data.
+ *
+ * @return The write data.
+ */
+ public byte[] getData() {
+ return Arrays.copyOf(data, data.length);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return "SSH_FXP_WRITE";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String toString() {
+ return "Status=" + getName() + "; Message=handle=" + handleId + ", file="
+ + handle.getFile().getAbsolutePath() + ", offset=" + offset + ";";
+ }
+
+ /**
+ * Returns the according handle.
+ *
+ * @return The according handle.
+ */
+ public Handle getHandle() {
+ return handle;
+ }
+
+ /**
+ * Returns the handle id.
+ *
+ * @return The handle id.
+ */
+ public String getHandleId() {
+ return handleId;
+ }
+
+ /**
+ * Returns the offset.
+ *
+ * @return The offset.
+ */
+ public long getOffset() {
+ return offset;
+ }
+}
Added: mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/UnsupportedRequest.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/UnsupportedRequest.java?rev=1434657&view=auto
==============================================================================
--- mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/UnsupportedRequest.java (added)
+++ mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/request/UnsupportedRequest.java Thu Jan 17 13:05:48 2013
@@ -0,0 +1,47 @@
+/*
+* 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.sftp.request;
+
+/**
+ * Data container for unknown or unsupported requests.
+ *
+ * @author <a href="http://mina.apache.org">Apache MINA Project</a>
+ */
+public class UnsupportedRequest extends Request {
+
+ private final int type;
+
+ /**
+ * Create UnsupportedRequest instance.
+ *
+ * @param id The request id.
+ * @param type The type code.
+ */
+ public UnsupportedRequest(final int id, final int type) {
+ super(id);
+ this.type = type;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getName() {
+ return "Unsupported request: " + type;
+ }
+}
Added: mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpSubsystem.java
URL: http://svn.apache.org/viewvc/mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpSubsystem.java?rev=1434657&view=auto
==============================================================================
--- mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpSubsystem.java (added)
+++ mina/sshd/trunk/sshd-sftp/src/main/java/org/apache/sshd/sftp/subsystem/SftpSubsystem.java Thu Jan 17 13:05:48 2013
@@ -0,0 +1,1374 @@
+/*
+ * 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.sftp.subsystem;
+
+import org.apache.sshd.common.NamedFactory;
+import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.common.util.IoUtils;
+import org.apache.sshd.common.util.SelectorUtils;
+import org.apache.sshd.server.*;
+import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.sftp.DefaultSftpletContainer;
+import org.apache.sshd.sftp.Handle;
+import org.apache.sshd.sftp.Sftplet;
+import org.apache.sshd.sftp.reply.*;
+import org.apache.sshd.sftp.request.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * SFTP subsystem
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class SftpSubsystem implements Command, Runnable, SessionAware, FileSystemAware {
+ private Sftplet sftpLet = new DefaultSftpletContainer();
+
+ protected final Logger log = LoggerFactory.getLogger(getClass());
+
+ public static class Factory implements NamedFactory<Command> {
+
+ public Factory() {
+ }
+
+ public Command create() {
+ return new SftpSubsystem();
+ }
+
+ public String getName() {
+ return "sftp";
+ }
+ }
+
+ /**
+ * Properties key for the maximum of available open handles per session.
+ */
+ public static final String MAX_OPEN_HANDLES_PER_SESSION = "max-open-handles-per-session";
+
+ public static final int LOWER_SFTP_IMPL = 3; // Working implementation from v3
+ public static final int HIGHER_SFTP_IMPL = 3; // .. up to
+ public static final String ALL_SFTP_IMPL = "3";
+ public static final int MAX_PACKET_LENGTH = 1024 * 16;
+
+ 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_ACMODTIME = 0x00000008; //v3 naming convention
+ 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 FileSystemView root;
+
+ private int version;
+ private Map<String, Handle> handles = new HashMap<String, Handle>();
+ private Request sftpRequest;
+
+
+ protected static int mapV4ToV3(int code) {
+ switch (code) {
+ case SSH_FX_INVALID_HANDLE:
+ return SSH_FX_FAILURE;
+ case SSH_FX_NO_SUCH_PATH:
+ return SSH_FX_NO_SUCH_FILE;
+ case SSH_FX_FILE_ALREADY_EXISTS:
+ return SSH_FX_FAILURE;
+ case SSH_FX_WRITE_PROTECT:
+ return SSH_FX_PERMISSION_DENIED;
+ case SSH_FX_NO_MEDIA:
+ return SSH_FX_FAILURE;
+ default:
+ return code;
+ }
+ }
+
+ protected static int mapV5ToV4(int code) {
+ switch (code) {
+ case SSH_FX_NO_SPACE_ON_FILESYSTEM:
+ return SSH_FX_FAILURE;
+ case SSH_FX_QUOTA_EXCEEDED:
+ return SSH_FX_FAILURE;
+ case SSH_FX_UNKNOWN_PRINCIPAL:
+ return SSH_FX_FAILURE;
+ case SSH_FX_LOCK_CONFLICT:
+ return SSH_FX_FAILURE;
+ default:
+ return code;
+ }
+ }
+
+ protected static int mapV6ToV5(int code) {
+ switch (code) {
+ case SSH_FX_DIR_NOT_EMPTY:
+ return SSH_FX_FAILURE;
+ case SSH_FX_NOT_A_DIRECTORY:
+ return SSH_FX_NO_SUCH_FILE;
+ case SSH_FX_INVALID_FILENAME:
+ return SSH_FX_NO_SUCH_FILE;
+ case SSH_FX_LINK_LOOP:
+ return SSH_FX_FAILURE;
+ case SSH_FX_CANNOT_DELETE:
+ return SSH_FX_PERMISSION_DENIED;
+ case SSH_FX_INVALID_PARAMETER:
+ return SSH_FX_FAILURE;
+ case SSH_FX_FILE_IS_A_DIRECTORY:
+ return SSH_FX_NO_SUCH_FILE;
+ case SSH_FX_BYTE_RANGE_LOCK_CONFLICT:
+ return SSH_FX_FAILURE;
+ case SSH_FX_BYTE_RANGE_LOCK_REFUSED:
+ return SSH_FX_FAILURE;
+ case SSH_FX_DELETE_PENDING:
+ return SSH_FX_FAILURE;
+ case SSH_FX_FILE_CORRUPT:
+ return SSH_FX_FAILURE;
+ case SSH_FX_OWNER_INVALID:
+ return SSH_FX_PERMISSION_DENIED;
+ case SSH_FX_GROUP_INVALID:
+ return SSH_FX_PERMISSION_DENIED;
+ case SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK:
+ return SSH_FX_FAILURE;
+ default:
+ return code;
+ }
+ }
+
+ protected static int mapToVersion(int code, int version) {
+ int mappedCode = code;
+ if (version < 6) {
+ mappedCode = mapV6ToV5(mappedCode);
+ }
+ if (version < 5) {
+ mappedCode = mapV5ToV4(mappedCode);
+ }
+ if (version < 4) {
+ mappedCode = mapV4ToV3(mappedCode);
+ }
+ return mappedCode;
+ }
+
+ protected int mapToVersion(int code) {
+ return mapToVersion(code, version);
+ }
+
+ protected static class DirectoryHandle extends Handle implements Iterator<SshFile> {
+ boolean done;
+ // the directory should be read once at "open directory"
+ List<SshFile> fileList = null;
+ int fileIndex;
+
+ public DirectoryHandle(SshFile file) {
+ super(file);
+ fileList = file.listSshFiles();
+ fileIndex = 0;
+ }
+
+ public boolean isDone() {
+ return done;
+ }
+
+ public void setDone(boolean done) {
+ this.done = done;
+ }
+
+ public boolean hasNext() {
+ return fileIndex < fileList.size();
+ }
+
+ public SshFile next() {
+ SshFile f = fileList.get(fileIndex);
+ fileIndex++;
+ return f;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clearFileList() {
+ // allow the garbage collector to do the job
+ fileList = null;
+ }
+ }
+
+ public static class FileHandle extends Handle {
+ int flags;
+ OutputStream output;
+ long outputPos;
+ InputStream input;
+ long inputPos;
+
+ public FileHandle(SshFile sshFile, int flags) {
+ super(sshFile);
+ this.flags = flags;
+ }
+
+ public int getFlags() {
+ return flags;
+ }
+
+ public int read(byte[] data, long offset) throws IOException {
+ if (input != null && offset != inputPos) {
+ IoUtils.closeQuietly(input);
+ input = null;
+ }
+ if (input == null) {
+ input = file.createInputStream(offset);
+ inputPos = offset;
+ }
+ int read = input.read(data);
+ inputPos += read;
+ return read;
+ }
+
+ public void write(byte[] data, long offset) throws IOException {
+ if (output != null && offset != outputPos) {
+ IoUtils.closeQuietly(output);
+ output = null;
+ }
+ if (output == null) {
+ output = file.createOutputStream(offset);
+ }
+ output.write(data);
+ outputPos += data.length;
+ }
+
+ @Override
+ public void close() throws IOException {
+ IoUtils.closeQuietly(output, input);
+ output = null;
+ input = null;
+ super.close();
+ }
+ }
+
+ public SftpSubsystem() {}
+
+ public void setSftpLet(final Sftplet sftpLet) {
+ this.sftpLet = sftpLet;
+ }
+
+ public void setSession(ServerSession session) {
+ sftpLet.onConnect(session);
+ this.session = session;
+ }
+
+ public void setFileSystemView(FileSystemView view) {
+ this.root = view;
+ }
+
+ 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 && !(t instanceof EOFException)) { // Ignore han
+ 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);
+ }
+ }
+
+ if (handles != null) {
+ for (Map.Entry<String, Handle> entry : handles.entrySet()) {
+ Handle handle = entry.getValue();
+ try {
+ handle.close();
+ } catch (IOException ioe) {
+ log.error("Could not close open handle: " + entry.getKey(), ioe);
+ }
+ }
+ }
+ dis = null;
+
+ callback.onExit(0);
+ sftpLet.onDisconnect(session);
+ }
+ }
+
+ public void process(Buffer buffer) throws IOException {
+ sftpRequest = getSftpRequest(new Buffer(buffer.array()));
+ Reply reply = sftpLet.beforeCommand(session, sftpRequest);
+ if (reply != null) {
+ sendReply(reply);
+ return;
+ }
+ int id = sftpRequest.getId();
+ if (sftpRequest instanceof SshFxpInitRequest) {
+ version = id;
+ if (version >= LOWER_SFTP_IMPL) {
+ version = Math.min(version, HIGHER_SFTP_IMPL);
+ Buffer sendBuffer = new Buffer();
+ sendBuffer.putByte((byte) SSH_FXP_VERSION);
+ sendBuffer.putInt(version);
+ send(sendBuffer);
+ } else {
+ // We only support version 3 (Version 1 and 2 are not common)
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "SFTP server only support versions " + ALL_SFTP_IMPL);
+ }
+ } else if (sftpRequest instanceof SshFxpOpenRequest) {
+ SshFxpOpenRequest sshFxpOpenRequest = (SshFxpOpenRequest) sftpRequest;
+ if (session.getFactoryManager().getProperties() != null) {
+ String maxHandlesString = session.getFactoryManager().getProperties().get(MAX_OPEN_HANDLES_PER_SESSION);
+ if (maxHandlesString != null) {
+ int maxHandleCount = Integer.parseInt(maxHandlesString);
+ if (handles.size() > maxHandleCount) {
+ sendStatus(id, SSH_FX_FAILURE, "Too many open handles");
+ return;
+ }
+ }
+ }
+
+ Integer accValue = sshFxpOpenRequest.getAcc();
+ if (accValue == null) {
+ String path = sshFxpOpenRequest.getPath();
+ int pflags = sshFxpOpenRequest.getFlags();
+ // attrs
+ try {
+ SshFile file = resolveFile(path);
+ if (file.doesExist()) {
+ 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.isWritable()) {
+ sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not create " + path);
+ return;
+ }
+ file.create();
+ }
+ }
+ String acc = ((pflags & (SSH_FXF_READ | SSH_FXF_WRITE)) != 0 ? "r" : "") +
+ ((pflags & SSH_FXF_WRITE) != 0 ? "w" : "");
+ if ((pflags & SSH_FXF_TRUNC) != 0) {
+ file.truncate();
+ }
+ String handle = UUID.randomUUID().toString();
+ handles.put(handle, new FileHandle(file, pflags)); // handle flags conversion
+ sendHandle(id, handle);
+ } catch (IOException e) {
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage() == null ? "" : e.getMessage());
+ }
+ } else {
+ String path = sshFxpOpenRequest.getPath();
+ int acc = accValue;
+ int flags = sshFxpOpenRequest.getFlags();
+ // attrs
+ try {
+ SshFile file = resolveFile(path);
+ switch (flags & SSH_FXF_ACCESS_DISPOSITION) {
+ case SSH_FXF_CREATE_NEW: {
+ if (file.doesExist()) {
+ sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, path);
+ return;
+ } else if (!file.isWritable()) {
+ sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not create " + path);
+ }
+ file.create();
+ break;
+ }
+ case SSH_FXF_CREATE_TRUNCATE: {
+ if (file.doesExist()) {
+ sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, path);
+ return;
+ } else if (!file.isWritable()) {
+ sendStatus(id, SSH_FX_PERMISSION_DENIED, "Can not create " + path);
+ }
+ file.truncate();
+ break;
+ }
+ case SSH_FXF_OPEN_EXISTING: {
+ if (!file.doesExist()) {
+ if (!file.getParentFile().doesExist()) {
+ sendStatus(id, SSH_FX_NO_SUCH_PATH, path);
+ } else {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
+ }
+ return;
+ }
+ break;
+ }
+ case SSH_FXF_OPEN_OR_CREATE: {
+ if (!file.doesExist()) {
+ file.create();
+ }
+ break;
+ }
+ case SSH_FXF_TRUNCATE_EXISTING: {
+ if (!file.doesExist()) {
+ if (!file.getParentFile().doesExist()) {
+ sendStatus(id, SSH_FX_NO_SUCH_PATH, path);
+ } else {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
+ }
+ return;
+ }
+ file.truncate();
+ break;
+ }
+ default:
+ throw new IllegalArgumentException("Unsupported open mode: " + flags);
+ }
+ String handle = UUID.randomUUID().toString();
+ handles.put(handle, new FileHandle(file, flags));
+ sendHandle(id, handle);
+ } catch (IOException e) {
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+ }
+ }
+ } else if (sftpRequest instanceof SshFxpCloseRequest) {
+ SshFxpCloseRequest sshFxpCloseRequest = (SshFxpCloseRequest) sftpRequest;
+ String handle = sshFxpCloseRequest.getHandleId();
+ 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());
+ }
+ } else if (sftpRequest instanceof SshFxpReadRequest) {
+ SshFxpReadRequest sshFxpReadRequest = (SshFxpReadRequest) sftpRequest;
+ String handle = sshFxpReadRequest.getHandleId();
+ long offset = sshFxpReadRequest.getOffset();
+ int len = sshFxpReadRequest.getLen();
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ FileHandle fh = (FileHandle) p;
+ byte[] b = new byte[Math.min(len, 1024 * 32)];
+ len = fh.read(b, offset);
+ if (len >= 0) {
+ SshFxpDataReply sftpReply = new SshFxpDataReply(id, b);
+ Buffer buf = new Buffer(len + 5);
+ buf.putByte((byte) SSH_FXP_DATA);
+ buf.putInt(id);
+ buf.putBytes(b, 0, len);
+ if (version >= 6) {
+ boolean lenFlag = len == 0;
+ buf.putBoolean(lenFlag);
+ sftpReply = new SshFxpDataReply(id, Arrays.copyOf(b, len), lenFlag);
+ }
+ if (processAfterCommand(session, sftpRequest, sftpReply)) return;
+ send(buf);
+ } else {
+ sendStatus(id, SSH_FX_EOF, "");
+ }
+ }
+ } catch (IOException e) {
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+ }
+ } else if (sftpRequest instanceof SshFxpWriteRequest) {
+ SshFxpWriteRequest sshFxpWriteRequest = (SshFxpWriteRequest) sftpRequest;
+ String handle = sshFxpWriteRequest.getHandleId();
+ long offset = sshFxpWriteRequest.getOffset();
+ byte[] data = sshFxpWriteRequest.getData();
+ try {
+ Handle p = handles.get(handle);
+ if (!(p instanceof FileHandle)) {
+ sendStatus(id, SSH_FX_INVALID_HANDLE, handle);
+ } else {
+ FileHandle fh = (FileHandle) p;
+ fh.write(data, offset);
+ SshFile sshFile = fh.getFile();
+
+ sshFile.setLastModified(new Date().getTime());
+
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+ }
+ } else if ((sftpRequest instanceof SshFxpLstatRequest)
+ || (sftpRequest instanceof SshFxpStatRequest)) {
+ String path;
+ if (sftpRequest instanceof SshFxpLstatRequest) {
+ SshFxpLstatRequest sshFxpLstatRequest = (SshFxpLstatRequest) sftpRequest;
+ path = sshFxpLstatRequest.getPath();
+ } else {
+ SshFxpStatRequest sshFxpStatRequest = (SshFxpStatRequest) sftpRequest;
+ path = sshFxpStatRequest.getPath();
+ }
+ try {
+ SshFile p = resolveFile(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());
+ }
+ } else if (sftpRequest instanceof SshFxpFstatRequest) {
+ SshFxpFstatRequest sshFxpFstatRequest = (SshFxpFstatRequest) sftpRequest;
+ String handle = sshFxpFstatRequest.getHandleId();
+ 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());
+ }
+ } else if (sftpRequest instanceof SshFxpOpendirRequest) {
+ SshFxpOpendirRequest sshFxpOpendirRequest = (SshFxpOpendirRequest) sftpRequest;
+ String path = sshFxpOpendirRequest.getPath();
+ try {
+ SshFile p = resolveFile(path);
+ if (!p.doesExist()) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, path);
+ } else if (!p.isDirectory()) {
+ sendStatus(id, SSH_FX_NOT_A_DIRECTORY, path);
+ } else if (!p.isReadable()) {
+ 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());
+ }
+ } else if (sftpRequest instanceof SshFxpReaddirRequest) {
+ SshFxpReaddirRequest sshFxpReaddirRequest = (SshFxpReaddirRequest) sftpRequest;
+ String handle = sshFxpReaddirRequest.getHandleId();
+ 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().doesExist()) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getFile().getAbsolutePath());
+ } else if (!p.getFile().isDirectory()) {
+ sendStatus(id, SSH_FX_NOT_A_DIRECTORY, p.getFile().getAbsolutePath());
+ } else if (!p.getFile().isReadable()) {
+ sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getFile().getAbsolutePath());
+ } else {
+ DirectoryHandle dh = (DirectoryHandle) p;
+ if (dh.hasNext()) {
+ // There is at least one file in the directory.
+ // Send only a few files at a time to not create packets of a too
+ // large size or have a timeout to occur.
+ sendName(id, dh);
+ if (!dh.hasNext()) {
+ // if no more files to send
+ dh.setDone(true);
+ dh.clearFileList();
+ }
+ } else {
+ // empty directory
+ dh.setDone(true);
+ dh.clearFileList();
+ sendStatus(id, SSH_FX_EOF, "", "");
+ }
+ }
+ } catch (IOException e) {
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+ }
+ } else if (sftpRequest instanceof SshFxpRemoveRequest) {
+ SshFxpRemoveRequest sshFxpRemoveRequest = (SshFxpRemoveRequest) sftpRequest;
+ String path = sshFxpRemoveRequest.getPath();
+ try {
+ SshFile p = resolveFile(path);
+ if (!p.doesExist()) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, p.getAbsolutePath());
+ } else if (p.isDirectory()) {
+ sendStatus(id, SSH_FX_FILE_IS_A_DIRECTORY, p.getAbsolutePath());
+ } 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());
+ }
+ } else if (sftpRequest instanceof SshFxpMkdirRequest) {
+ SshFxpMkdirRequest sshFxpMkdirRequest = (SshFxpMkdirRequest) sftpRequest;
+ String path = sshFxpMkdirRequest.getPath();
+ // attrs
+ try {
+ SshFile p = resolveFile(path);
+ if (p.doesExist()) {
+ if (p.isDirectory()) {
+ sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, p.getAbsolutePath());
+ } else {
+ sendStatus(id, SSH_FX_NOT_A_DIRECTORY, p.getAbsolutePath());
+ }
+ } else if (!p.isWritable()) {
+ sendStatus(id, SSH_FX_PERMISSION_DENIED, p.getAbsolutePath());
+ } else if (!p.mkdir()) {
+ throw new IOException("Error creating dir " + path);
+ } else {
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+ }
+ } else if (sftpRequest instanceof SshFxpRmdirRequest) {
+ SshFxpRmdirRequest sshFxpRmdirRequest = (SshFxpRmdirRequest) sftpRequest;
+ String path = sshFxpRmdirRequest.getPath();
+ // attrs
+ try {
+ SshFile p = resolveFile(path);
+ if (p.isDirectory()) {
+ if (p.doesExist()) {
+ if (p.listSshFiles().size() == 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.getAbsolutePath());
+ }
+ } catch (IOException e) {
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+ }
+ } else if (sftpRequest instanceof SshFxpRealpathRequest) {
+ SshFxpRealpathRequest sshFxpRealpathRequest = (SshFxpRealpathRequest) sftpRequest;
+ String path = sshFxpRealpathRequest.getPath();
+ if (path.trim().length() == 0) {
+ path = ".";
+ }
+ // TODO: handle optional args
+ try {
+ SshFile p = resolveFile(path);
+ sendPath(id, p);
+ } catch (FileNotFoundException e) {
+ log.error("error while resove file, cause: " + e.getMessage());
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, e.getMessage());
+ } catch (IOException e) {
+ log.error("error while resove file, cause: " + e.getMessage());
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+ }
+ } else if (sftpRequest instanceof SshFxpRenameRequest) {
+ SshFxpRenameRequest sshFxpRenameRequest = (SshFxpRenameRequest) sftpRequest;
+ String oldPath = sshFxpRenameRequest.getOldPath();
+ String newPath = sshFxpRenameRequest.getNewPath();
+ try {
+ SshFile o = resolveFile(oldPath);
+ SshFile n = resolveFile(newPath);
+ if (!o.doesExist()) {
+ sendStatus(id, SSH_FX_NO_SUCH_FILE, o.getAbsolutePath());
+ } else if (n.doesExist()) {
+ sendStatus(id, SSH_FX_FILE_ALREADY_EXISTS, n.getAbsolutePath());
+ } else if (!o.move(n)) {
+ sendStatus(id, SSH_FX_FAILURE, "Failed to rename file");
+ } else {
+ sendStatus(id, SSH_FX_OK, "");
+ }
+ } catch (IOException e) {
+ sendStatus(id, SSH_FX_FAILURE, e.getMessage());
+ }
+ } else if ((sftpRequest instanceof SshFxpSetstatRequest)
+ || (sftpRequest instanceof SshFxpFsetstatRequest)) {
+ // This is required for WinSCP / Cyberduck to upload properly
+ // Blindly reply "OK"
+ // TODO implement it
+ sendStatus(id, SSH_FX_OK, "");
+ } else {
+ log.error("Received: {}", sftpRequest);
+ sendStatus(id, SSH_FX_OP_UNSUPPORTED, "Command " + sftpRequest + " is unsupported or not implemented");
+ }
+ }
+
+ protected void sendHandle(int id, String handle) throws IOException {
+ if (processAfterCommand(session, sftpRequest, new SshFxpHandleReply(id, handle, handles.get(handle)))) return;
+ Buffer buffer = new Buffer();
+ buffer.putByte((byte) SSH_FXP_HANDLE);
+ buffer.putInt(id);
+ buffer.putString(handle);
+ send(buffer);
+ }
+
+ protected void sendAttrs(int id, SshFile file) throws IOException {
+ if (processAfterCommand(session, sftpRequest, new SshFxpAttrsReply(id, file))) return;
+ Buffer buffer = new Buffer();
+ buffer.putByte((byte) SSH_FXP_ATTRS);
+ buffer.putInt(id);
+ writeAttrs(buffer, file);
+ send(buffer);
+ }
+
+ protected void sendAttrs(int id, SshFile file, int flags) throws IOException {
+ if (processAfterCommand(session, sftpRequest, new SshFxpAttrsReply(id, file, flags))) return;
+ Buffer buffer = new Buffer();
+ buffer.putByte((byte) SSH_FXP_ATTRS);
+ buffer.putInt(id);
+ writeAttrs(buffer, file, flags);
+ send(buffer);
+ }
+
+
+ protected void sendPath(int id, SshFile f) throws IOException {
+ int count = 1;
+ SshFxpNameReply sshFxpNameReply = new SshFxpNameReply(id, true);
+ Buffer buffer = new Buffer();
+ buffer.putByte((byte) SSH_FXP_NAME);
+ buffer.putInt(id);
+ buffer.putInt(count);
+ //normalize the given path, use *nix style separator
+ String normalizedPath = SelectorUtils.normalizePath(f.getAbsolutePath(), "/");
+ if (normalizedPath.length() == 0) {
+ normalizedPath = "/";
+ }
+ buffer.putString(normalizedPath);
+ f = resolveFile(normalizedPath);
+ if (f.getName().length() == 0) {
+ f = resolveFile(".");
+ }
+ String longName;
+ if (version <= 3) {
+ longName = getLongName(f);
+ buffer.putString(longName); // Format specified in the specs
+ buffer.putInt(0);
+ sshFxpNameReply.addFile(f, normalizedPath, longName, 0);
+ } else {
+ longName = f.getName();
+ buffer.putString(longName); // Supposed to be UTF-8
+ writeAttrs(buffer, f);
+ sshFxpNameReply.addFile(f, normalizedPath, longName, null);
+ }
+
+ if (processAfterCommand(session, sftpRequest, sshFxpNameReply)) return;
+ send(buffer);
+ }
+
+ protected void sendName(int id, Iterator<SshFile> files) throws IOException {
+ SshFxpNameReply sshFxpNameReply = new SshFxpNameReply(id, false);
+ Buffer buffer = new Buffer();
+ buffer.putByte((byte) SSH_FXP_NAME);
+ buffer.putInt(id);
+ int wpos = buffer.wpos();
+ buffer.putInt(0);
+ int nb = 0;
+ while (files.hasNext() && buffer.wpos() < MAX_PACKET_LENGTH) {
+ SshFile f = files.next();
+ String filename = f.getName();
+ buffer.putString(filename);
+ String longname;
+ if (version <= 3) {
+ longname = getLongName(f);
+ buffer.putString(longname); // Format specified in the specs
+ } else {
+ longname = f.getName();
+ buffer.putString(longname); // Supposed to be UTF-8
+ }
+ sshFxpNameReply.addFile(f, filename, longname, null);
+ writeAttrs(buffer, f);
+ nb++;
+ }
+ int oldpos = buffer.wpos();
+ buffer.wpos(wpos);
+ buffer.putInt(nb);
+ buffer.wpos(oldpos);
+ if (processAfterCommand(session, sftpRequest, sshFxpNameReply)) return;
+ send(buffer);
+ }
+
+ private String getLongName(SshFile f) {
+ String username = f.getOwner();
+ if (username.length() > 8) {
+ username = username.substring(0, 8);
+ } else {
+ for (int i = username.length(); i < 8; i++) {
+ username = username + " ";
+ }
+ }
+
+ long length = f.getSize();
+ String lengthString = String.format("%1$8s", length);
+
+ StringBuilder sb = new StringBuilder();
+ sb.append((f.isDirectory() ? "d" : "-"));
+ sb.append((f.isReadable() ? "r" : "-"));
+ sb.append((f.isWritable() ? "w" : "-"));
+ sb.append((f.isExecutable() ? "x" : "-"));
+ sb.append((f.isReadable() ? "r" : "-"));
+ sb.append((f.isWritable() ? "w" : "-"));
+ sb.append((f.isExecutable() ? "x" : "-"));
+ sb.append((f.isReadable() ? "r" : "-"));
+ sb.append((f.isWritable() ? "w" : "-"));
+ sb.append((f.isExecutable() ? "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(getUnixDate(f.getLastModified()));
+ sb.append(" ");
+ sb.append(f.getName());
+
+ return sb.toString();
+ }
+
+ protected void writeAttrs(Buffer buffer, SshFile file) throws IOException {
+ writeAttrs(buffer, file, 0);
+ }
+
+
+ protected void writeAttrs(Buffer buffer, SshFile file, int flags) throws IOException {
+ if (!file.doesExist()) {
+ throw new FileNotFoundException(file.getAbsolutePath());
+ }
+ if (version >= 4) {
+ long size = file.getSize();
+ String username = session.getUsername();
+ long lastModif = file.getLastModified();
+ int p = 0;
+ if (file.isReadable()) {
+ p |= S_IRUSR;
+ }
+ if (file.isWritable()) {
+ p |= S_IWUSR;
+ }
+ if (file.isExecutable()) {
+ 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.isReadable()) {
+ p |= 0000400;
+ }
+ if (file.isWritable()) {
+ p |= 0000200;
+ }
+ if (file.isExecutable()) {
+ p |= 0000100;
+ }
+ if (file.isFile()) {
+ buffer.putInt(SSH_FILEXFER_ATTR_SIZE| SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME);
+ buffer.putLong(file.getSize());
+ buffer.putInt(p);
+ buffer.putInt(file.getLastModified()/1000);
+ buffer.putInt(file.getLastModified()/1000);
+ } else if (file.isDirectory()) {
+ buffer.putInt(SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME);
+ buffer.putInt(p);
+ buffer.putInt(file.getLastModified()/1000);
+ buffer.putInt(file.getLastModified()/1000);
+ } 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 {
+ if (processAfterCommand(session, sftpRequest, new SshFxpStatusReply(id, substatus, msg, lang))) return;
+ sendStatusStrict(id, substatus, msg, lang);
+ }
+
+ protected void sendStatusStrict(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;
+ }
+
+ private SshFile resolveFile(String path) {
+ return this.root.getFile(path);
+ }
+
+ private final static String[] MONTHS = { "Jan", "Feb", "Mar", "Apr", "May",
+ "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+ /**
+ * Get unix style date string.
+ */
+ private final static String getUnixDate(long millis) {
+ if (millis < 0) {
+ return "------------";
+ }
+
+ StringBuffer sb = new StringBuffer(16);
+ Calendar cal = new GregorianCalendar();
+ cal.setTimeInMillis(millis);
+
+ // month
+ sb.append(MONTHS[cal.get(Calendar.MONTH)]);
+ sb.append(' ');
+
+ // day
+ int day = cal.get(Calendar.DATE);
+ if (day < 10) {
+ sb.append(' ');
+ }
+ sb.append(day);
+ sb.append(' ');
+
+ long sixMonth = 15811200000L; // 183L * 24L * 60L * 60L * 1000L;
+ long nowTime = System.currentTimeMillis();
+ if (Math.abs(nowTime - millis) > sixMonth) {
+
+ // year
+ int year = cal.get(Calendar.YEAR);
+ sb.append(' ');
+ sb.append(year);
+ } else {
+
+ // hour
+ int hh = cal.get(Calendar.HOUR_OF_DAY);
+ if (hh < 10) {
+ sb.append('0');
+ }
+ sb.append(hh);
+ sb.append(':');
+
+ // minute
+ int mm = cal.get(Calendar.MINUTE);
+ if (mm < 10) {
+ sb.append('0');
+ }
+ sb.append(mm);
+ }
+ return sb.toString();
+ }
+
+
+ protected Request getSftpRequest(final Buffer buffer) {
+ Request sftpRequest;
+
+ int length = buffer.getInt();
+ int type = buffer.getByte();
+ int id = buffer.getInt();
+
+ switch (type) {
+ case SSH_FXP_INIT:
+ if (length != 5) {
+ throw new IllegalArgumentException();
+ }
+ sftpRequest = new SshFxpInitRequest(id);
+ break;
+ case SSH_FXP_OPEN:
+ if (version <= 4) {
+ String path = buffer.getString();
+ int pflags = buffer.getInt();
+ sftpRequest = new SshFxpOpenRequest(id, path, pflags);
+ } else {
+ String path = buffer.getString();
+ int acc = buffer.getInt();
+ int flags = buffer.getInt();
+ sftpRequest = new SshFxpOpenRequest(id, path, acc, flags);
+ }
+ break;
+ case SSH_FXP_CLOSE: {
+ String handleId = buffer.getString();
+ Handle handle = handles.get(handleId);
+ sftpRequest = new SshFxpCloseRequest(id, handleId, handle);
+ } break;
+ case SSH_FXP_READ: {
+ String handleId = buffer.getString();
+ long offset = buffer.getLong();
+ int len = buffer.getInt();
+ Handle handle = handles.get(handleId);
+ sftpRequest = new SshFxpReadRequest(id, handleId, offset, len, handle);
+ } break;
+ case SSH_FXP_WRITE: {
+ String handleId = buffer.getString();
+ long offset = buffer.getLong();
+ byte[] data = buffer.getBytes();
+ Handle handle = handles.get(handleId);
+ sftpRequest = new SshFxpWriteRequest(id, handleId, offset, data, handle);
+ } break;
+ case SSH_FXP_STAT: {
+ String path = buffer.getString();
+ sftpRequest = new SshFxpStatRequest(id, path);
+ } break;
+ case SSH_FXP_LSTAT: {
+ String path = buffer.getString();
+ sftpRequest = new SshFxpLstatRequest(id, path);
+ } break;
+ case SSH_FXP_FSTAT: {
+ String handle = buffer.getString();
+ Handle p = handles.get(handle);
+ sftpRequest = new SshFxpFstatRequest(id, handle, p);
+ } break;
+ case SSH_FXP_OPENDIR: {
+ String path = buffer.getString();
+ sftpRequest = new SshFxpOpendirRequest(id, path);
+ } break;
+ case SSH_FXP_READDIR:
+ String handle = buffer.getString();
+ Handle p = handles.get(handle);
+ sftpRequest = new SshFxpReaddirRequest(id, handle, p);
+ break;
+ case SSH_FXP_REMOVE: {
+ String path = buffer.getString();
+ sftpRequest = new SshFxpRemoveRequest(id, path);
+ } break;
+ case SSH_FXP_MKDIR: {
+ String path = buffer.getString();
+ sftpRequest = new SshFxpMkdirRequest(id, path);
+ } break;
+ case SSH_FXP_RMDIR: {
+ String path = buffer.getString();
+ sftpRequest = new SshFxpRmdirRequest(id, path);
+ } break;
+ case SSH_FXP_REALPATH:
+ String path = buffer.getString();
+ sftpRequest = new SshFxpRealpathRequest(id, path);
+ break;
+ case SSH_FXP_RENAME:
+ final String oldPath = buffer.getString();
+ final String newPath = buffer.getString();
+ sftpRequest = new SshFxpRenameRequest(id, oldPath, newPath);
+ break;
+ case SSH_FXP_SETSTAT:
+ sftpRequest = new SshFxpSetstatRequest(id);
+ break;
+ case SSH_FXP_FSETSTAT:
+ sftpRequest = new SshFxpFsetstatRequest(id);
+ break;
+ default:
+ sftpRequest = new UnsupportedRequest(id, type);
+ }
+
+ return sftpRequest;
+ }
+
+ private boolean processAfterCommand(final ServerSession session2, final Request sftpRequest2, final Reply sftpReply)
+ throws IOException {
+ Reply reply = sftpLet.afterCommand(session2, sftpRequest2, sftpReply);
+
+ if (reply == null) {
+ return false;
+ } else {
+ sendReply(sftpReply);
+ return true;
+ }
+ }
+
+ private void sendReply(final Reply sftpReply) throws IOException {
+ if (sftpReply instanceof SshFxpAttrsReply) {
+ SshFxpAttrsReply sshFxpAttrsReply = (SshFxpAttrsReply) sftpReply;
+ int id = sshFxpAttrsReply.getId();
+ SshFile file = sshFxpAttrsReply.getFile();
+ Integer flags = sshFxpAttrsReply.getFlags();
+ if (flags == null) {
+ sendAttrs(id, file);
+ } else {
+ sendAttrs(id, file, flags);
+ }
+ } else if (sftpReply instanceof SshFxpDataReply) {
+ SshFxpDataReply sshFxpDataReply = (SshFxpDataReply) sftpReply;
+ long id = sshFxpDataReply.getId();
+ byte[] data = sshFxpDataReply.getData();
+ Boolean lenflag = sshFxpDataReply.getLenFlag();
+
+ int len = data.length;
+ Buffer buf = new Buffer(len + 5);
+ buf.putByte((byte) SSH_FXP_DATA);
+ buf.putInt(id);
+ buf.putBytes(data, 0, len);
+ if (version >= 6) {
+ buf.putBoolean(lenflag);
+ }
+ send(buf);
+ } else if (sftpReply instanceof SshFxpHandleReply) {
+ SshFxpHandleReply sshFxpHandleReply = (SshFxpHandleReply) sftpReply;
+ int id = sshFxpHandleReply.getId();
+ String handle = sshFxpHandleReply.getHandle();
+
+ sendHandle(id, handle);
+ } else if (sftpReply instanceof SshFxpNameReply) {
+ SshFxpNameReply sshFxpNameReply = (SshFxpNameReply) sftpReply;
+ int id = sshFxpNameReply.getId();
+
+ Iterator<SshFile> files = sshFxpNameReply.getFiles();
+
+ if (sshFxpNameReply.isSendPath()) {
+ SshFile file = files.next();
+ sendPath(id, file);
+ } else {
+ sendName(id, files);
+ }
+ } else if (sftpReply instanceof SshFxpStatusReply) {
+ SshFxpStatusReply sshFxpStatusReply = (SshFxpStatusReply) sftpReply;
+
+ int id = sshFxpStatusReply.getId();
+ int substatus = sshFxpStatusReply.getSubstatus();
+ String msg = sshFxpStatusReply.getMsg();
+ String lang = sshFxpStatusReply.getLang();
+ sendStatusStrict(id, substatus, msg, lang);
+ } else if (sftpReply instanceof SshFxpVersionReply) {
+ SshFxpVersionReply sshFxpVersionReply = (SshFxpVersionReply) sftpReply;
+ Buffer buffer = new Buffer();
+ buffer.putByte((byte) SftpSubsystem.SSH_FXP_VERSION);
+ int version = sshFxpVersionReply.getVersion();
+ buffer.putInt(version);
+ send(buffer);
+ }
+ }
+}