blob: d78073135264161994f3951169d621e903d0562b [file] [log] [blame]
/*
* 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:dev@mina.apache.org">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, "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;
}
}