2016-07-09 03:38:35 +08:00
|
|
|
package sftp
|
|
|
|
|
2016-07-09 08:22:52 +08:00
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path"
|
2016-07-15 12:11:34 +08:00
|
|
|
"path/filepath"
|
2016-07-30 06:57:06 +08:00
|
|
|
"sync"
|
2016-07-09 08:22:52 +08:00
|
|
|
"syscall"
|
|
|
|
)
|
|
|
|
|
2016-07-26 02:42:18 +08:00
|
|
|
// Request contains the data and state for the incoming service request.
|
2016-07-09 03:38:35 +08:00
|
|
|
type Request struct {
|
2016-07-15 03:07:16 +08:00
|
|
|
// Get, Put, SetStat, Stat, Rename, Remove
|
|
|
|
// Rmdir, Mkdir, List, Readlink, Symlink
|
2016-07-09 03:38:35 +08:00
|
|
|
Method string
|
|
|
|
Filepath string
|
|
|
|
Attrs []byte // convert to sub-struct
|
|
|
|
Target string // for renames and sym-links
|
2016-07-12 11:19:49 +08:00
|
|
|
// packet data
|
2016-07-30 06:57:06 +08:00
|
|
|
packets []packet_data
|
|
|
|
packetsLock sync.RWMutex
|
2016-07-12 11:19:49 +08:00
|
|
|
// reader/writer from handlers
|
2016-07-30 06:57:06 +08:00
|
|
|
put_writer io.WriterAt
|
|
|
|
get_reader io.ReaderAt
|
2016-07-15 05:45:50 +08:00
|
|
|
eof bool // hack for readdir to keep eof state
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
|
|
|
|
2016-07-30 06:57:06 +08:00
|
|
|
type packet_data struct {
|
|
|
|
id uint32
|
|
|
|
data []byte
|
|
|
|
length uint32
|
|
|
|
offset int64
|
|
|
|
}
|
|
|
|
|
2016-07-13 02:23:03 +08:00
|
|
|
// Here mainly to specify that Filepath is required
|
2016-07-12 11:19:49 +08:00
|
|
|
func newRequest(path string) *Request {
|
2016-07-19 02:06:53 +08:00
|
|
|
request := &Request{Filepath: filepath.Clean(path)}
|
2016-07-09 03:38:35 +08:00
|
|
|
return request
|
|
|
|
}
|
|
|
|
|
2016-07-30 06:57:06 +08:00
|
|
|
// push packet_data into fifo
|
|
|
|
func (r *Request) pushPacket(pd packet_data) {
|
|
|
|
r.packetsLock.Lock()
|
|
|
|
defer r.packetsLock.Unlock()
|
|
|
|
r.packets = append(r.packets, pd)
|
|
|
|
}
|
|
|
|
|
|
|
|
// pop packet_data into fifo
|
|
|
|
func (r *Request) popPacket() packet_data {
|
|
|
|
r.packetsLock.Lock()
|
|
|
|
defer r.packetsLock.Unlock()
|
|
|
|
var pd packet_data
|
|
|
|
pd, r.packets = r.packets[0], r.packets[1:]
|
|
|
|
return pd
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *Request) pkt_id() uint32 {
|
|
|
|
return r.packets[0].id
|
|
|
|
}
|
|
|
|
|
2016-07-13 02:23:03 +08:00
|
|
|
// called from worker to handle packet/request
|
2016-07-26 02:52:07 +08:00
|
|
|
func (r *Request) handle(handlers Handlers) (responsePacket, error) {
|
2016-07-12 11:19:49 +08:00
|
|
|
var err error
|
2016-07-26 02:52:07 +08:00
|
|
|
var rpkt responsePacket
|
2016-07-12 11:19:49 +08:00
|
|
|
switch r.Method {
|
|
|
|
case "Get":
|
|
|
|
rpkt, err = fileget(handlers.FileGet, r)
|
2016-07-25 07:58:47 +08:00
|
|
|
case "Put": // add "Append" to this to handle append only file writes
|
2016-07-12 11:19:49 +08:00
|
|
|
rpkt, err = fileput(handlers.FilePut, r)
|
2016-07-15 03:07:16 +08:00
|
|
|
case "SetStat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
|
2016-07-12 11:19:49 +08:00
|
|
|
rpkt, err = filecmd(handlers.FileCmd, r)
|
2016-07-15 12:11:34 +08:00
|
|
|
case "List", "Stat", "Readlink":
|
2016-07-12 11:19:49 +08:00
|
|
|
rpkt, err = fileinfo(handlers.FileInfo, r)
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
2016-07-13 07:50:59 +08:00
|
|
|
return rpkt, err
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
|
|
|
|
2016-07-13 02:23:03 +08:00
|
|
|
// wrap FileReader handler
|
2016-07-26 02:52:07 +08:00
|
|
|
func fileget(h FileReader, r *Request) (responsePacket, error) {
|
2016-07-12 11:19:49 +08:00
|
|
|
if r.get_reader == nil {
|
|
|
|
reader, err := h.Fileread(r)
|
2016-07-26 02:42:18 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, syscall.EBADF
|
|
|
|
}
|
2016-07-12 11:19:49 +08:00
|
|
|
r.get_reader = reader
|
|
|
|
}
|
|
|
|
reader := r.get_reader
|
2016-07-30 06:57:06 +08:00
|
|
|
|
|
|
|
pd := r.popPacket()
|
|
|
|
data := make([]byte, clamp(pd.length, maxTxPacket))
|
|
|
|
n, err := reader.ReadAt(data, pd.offset)
|
2016-07-26 02:42:18 +08:00
|
|
|
if err != nil && (err != io.EOF || n == 0) {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-07-12 04:56:47 +08:00
|
|
|
return &sshFxpDataPacket{
|
2016-07-30 06:57:06 +08:00
|
|
|
ID: pd.id,
|
2016-07-09 08:22:52 +08:00
|
|
|
Length: uint32(n),
|
2016-07-13 05:56:24 +08:00
|
|
|
Data: data[:n],
|
2016-07-12 04:56:47 +08:00
|
|
|
}, nil
|
2016-07-09 08:22:52 +08:00
|
|
|
}
|
2016-07-13 02:23:03 +08:00
|
|
|
|
|
|
|
// wrap FileWriter handler
|
2016-07-26 02:52:07 +08:00
|
|
|
func fileput(h FileWriter, r *Request) (responsePacket, error) {
|
2016-07-12 11:19:49 +08:00
|
|
|
if r.put_writer == nil {
|
|
|
|
writer, err := h.Filewrite(r)
|
2016-07-26 02:42:18 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, syscall.EBADF
|
|
|
|
}
|
2016-07-12 11:19:49 +08:00
|
|
|
r.put_writer = writer
|
|
|
|
}
|
|
|
|
writer := r.put_writer
|
|
|
|
|
2016-07-30 06:57:06 +08:00
|
|
|
pd := r.popPacket()
|
|
|
|
_, err := writer.WriteAt(pd.data, pd.offset)
|
2016-07-26 02:42:18 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-07-12 04:56:47 +08:00
|
|
|
return &sshFxpStatusPacket{
|
2016-07-30 06:57:06 +08:00
|
|
|
ID: pd.id,
|
2016-07-12 04:56:47 +08:00
|
|
|
StatusError: StatusError{
|
|
|
|
Code: ssh_FX_OK,
|
|
|
|
}}, nil
|
2016-07-09 08:22:52 +08:00
|
|
|
}
|
2016-07-13 02:23:03 +08:00
|
|
|
|
|
|
|
// wrap FileCmder handler
|
2016-07-26 02:52:07 +08:00
|
|
|
func filecmd(h FileCmder, r *Request) (responsePacket, error) {
|
2016-07-09 08:22:52 +08:00
|
|
|
err := h.Filecmd(r)
|
2016-07-26 02:42:18 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-07-13 05:56:24 +08:00
|
|
|
return &sshFxpStatusPacket{
|
2016-07-30 06:57:06 +08:00
|
|
|
ID: r.pkt_id(),
|
2016-07-12 04:56:47 +08:00
|
|
|
StatusError: StatusError{
|
|
|
|
Code: ssh_FX_OK,
|
|
|
|
}}, nil
|
2016-07-09 08:22:52 +08:00
|
|
|
}
|
2016-07-13 02:23:03 +08:00
|
|
|
|
|
|
|
// wrap FileInfoer handler
|
2016-07-26 02:52:07 +08:00
|
|
|
func fileinfo(h FileInfoer, r *Request) (responsePacket, error) {
|
2016-07-26 02:42:18 +08:00
|
|
|
if r.eof {
|
|
|
|
return nil, io.EOF
|
|
|
|
}
|
2016-07-09 08:22:52 +08:00
|
|
|
finfo, err := h.Fileinfo(r)
|
2016-07-26 02:42:18 +08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-07-09 08:22:52 +08:00
|
|
|
|
|
|
|
switch r.Method {
|
|
|
|
case "List":
|
|
|
|
dirname := path.Base(r.Filepath)
|
2016-07-30 06:57:06 +08:00
|
|
|
ret := &sshFxpNamePacket{ID: r.pkt_id()}
|
2016-07-09 08:22:52 +08:00
|
|
|
for _, fi := range finfo {
|
|
|
|
ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
|
|
|
|
Name: fi.Name(),
|
|
|
|
LongName: runLs(dirname, fi),
|
|
|
|
Attrs: []interface{}{fi},
|
|
|
|
})
|
|
|
|
}
|
2016-07-15 05:45:50 +08:00
|
|
|
r.eof = true
|
2016-07-13 05:56:24 +08:00
|
|
|
return ret, nil
|
2016-07-09 08:22:52 +08:00
|
|
|
case "Stat":
|
|
|
|
if len(finfo) == 0 {
|
2016-07-26 04:01:16 +08:00
|
|
|
err = &os.PathError{Op: "stat", Path: r.Filepath,
|
|
|
|
Err: syscall.ENOENT}
|
2016-07-12 04:56:47 +08:00
|
|
|
return nil, err
|
2016-07-09 08:22:52 +08:00
|
|
|
}
|
2016-07-12 04:56:47 +08:00
|
|
|
return &sshFxpStatResponse{
|
2016-07-30 06:57:06 +08:00
|
|
|
ID: r.pkt_id(),
|
2016-07-09 08:22:52 +08:00
|
|
|
info: finfo[0],
|
2016-07-12 04:56:47 +08:00
|
|
|
}, nil
|
2016-07-15 12:11:34 +08:00
|
|
|
case "Readlink":
|
2016-07-09 08:22:52 +08:00
|
|
|
if len(finfo) == 0 {
|
2016-07-26 04:01:16 +08:00
|
|
|
err = &os.PathError{Op: "readlink", Path: r.Filepath,
|
|
|
|
Err: syscall.ENOENT}
|
2016-07-12 04:56:47 +08:00
|
|
|
return nil, err
|
2016-07-09 08:22:52 +08:00
|
|
|
}
|
2016-07-23 08:11:09 +08:00
|
|
|
filename := finfo[0].Name()
|
2016-07-13 05:56:24 +08:00
|
|
|
return &sshFxpNamePacket{
|
2016-07-30 06:57:06 +08:00
|
|
|
ID: r.pkt_id(),
|
2016-07-09 08:22:52 +08:00
|
|
|
NameAttrs: []sshFxpNameAttr{{
|
2016-07-15 12:11:34 +08:00
|
|
|
Name: filename,
|
|
|
|
LongName: filename,
|
2016-07-09 08:22:52 +08:00
|
|
|
Attrs: emptyFileStat,
|
|
|
|
}},
|
2016-07-12 04:56:47 +08:00
|
|
|
}, nil
|
2016-07-09 08:22:52 +08:00
|
|
|
}
|
2016-07-12 04:56:47 +08:00
|
|
|
return nil, err
|
2016-07-09 08:22:52 +08:00
|
|
|
}
|
|
|
|
|
2016-07-13 02:23:03 +08:00
|
|
|
// populate attributes of request object from packet data
|
2016-07-09 03:38:35 +08:00
|
|
|
func (r *Request) populate(p interface{}) {
|
2016-07-13 05:56:24 +08:00
|
|
|
// r.Filepath should already be set
|
2016-07-30 06:57:06 +08:00
|
|
|
var pd packet_data
|
2016-07-09 03:38:35 +08:00
|
|
|
switch p := p.(type) {
|
|
|
|
case *sshFxpSetstatPacket:
|
|
|
|
r.Method = "Setstat"
|
|
|
|
r.Attrs = p.Attrs.([]byte)
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-09 03:38:35 +08:00
|
|
|
case *sshFxpFsetstatPacket:
|
|
|
|
r.Method = "Setstat"
|
|
|
|
r.Attrs = p.Attrs.([]byte)
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-09 03:38:35 +08:00
|
|
|
case *sshFxpRenamePacket:
|
|
|
|
r.Method = "Rename"
|
2016-07-19 02:06:53 +08:00
|
|
|
r.Target = filepath.Clean(p.Newpath)
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-09 03:38:35 +08:00
|
|
|
case *sshFxpSymlinkPacket:
|
|
|
|
r.Method = "Symlink"
|
2016-07-19 02:06:53 +08:00
|
|
|
r.Target = filepath.Clean(p.Linkpath)
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-12 05:54:46 +08:00
|
|
|
case *sshFxpReadPacket:
|
|
|
|
r.Method = "Get"
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.length = p.Len
|
|
|
|
pd.offset = int64(p.Offset)
|
|
|
|
pd.id = p.id()
|
2016-07-12 05:54:46 +08:00
|
|
|
case *sshFxpWritePacket:
|
|
|
|
r.Method = "Put"
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
|
|
|
pd.data = p.Data
|
|
|
|
pd.length = p.Length
|
|
|
|
pd.offset = int64(p.Offset)
|
2016-07-09 03:38:35 +08:00
|
|
|
case *sshFxpReaddirPacket:
|
|
|
|
r.Method = "List"
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-15 05:12:46 +08:00
|
|
|
case *sshFxpRemovePacket:
|
|
|
|
r.Method = "Remove"
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-15 05:12:46 +08:00
|
|
|
case *sshFxpStatPacket, *sshFxpLstatPacket, *sshFxpFstatPacket:
|
2016-07-09 03:38:35 +08:00
|
|
|
r.Method = "Stat"
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.(packet).id()
|
2016-07-09 03:38:35 +08:00
|
|
|
case *sshFxpRmdirPacket:
|
|
|
|
r.Method = "Rmdir"
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-09 03:38:35 +08:00
|
|
|
case *sshFxpReadlinkPacket:
|
|
|
|
r.Method = "Readlink"
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-09 03:38:35 +08:00
|
|
|
case *sshFxpMkdirPacket:
|
|
|
|
r.Method = "Mkdir"
|
2016-07-30 06:57:06 +08:00
|
|
|
pd.id = p.id()
|
2016-07-09 08:22:52 +08:00
|
|
|
//r.Attrs are ignored in ./packet.go
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|
2016-07-30 06:57:06 +08:00
|
|
|
r.pushPacket(pd)
|
2016-07-09 03:38:35 +08:00
|
|
|
}
|