mirror of
https://github.com/containers/podman.git
synced 2025-07-18 10:08:07 +08:00
podman ssh work, using new c/common interface
implement new ssh interface into podman this completely redesigns the entire functionality of podman image scp, podman system connection add, and podman --remote. All references to golang.org/x/crypto/ssh have been moved to common as have native ssh/scp execs and the new usage of the sftp package. this PR adds a global flag, --ssh to podman which has two valid inputs `golang` and `native` where golang is the default. Users should not notice any difference in their everyday workflows if they continue using the golang option. UNLESS they have been using an improperly verified ssh key, this will now fail. This is because podman was incorrectly using the ssh callback method to IGNORE the ssh known hosts file which is very insecure and golang tells you not yo use this in production. The native paths allows for immense flexibility, with a new containers.conf field `SSH_CONFIG` that specifies a specific ssh config file to be used in all operations. Else the users ~/.ssh/config file will be used. podman --remote currently only uses the golang path, given its deep interconnection with dialing multiple clients and urls. My goal after this PR is to go back and abstract the idea of podman --remote from golang's dialed clients, as it should not be so intrinsically connected. Overall, this is a v1 of a long process of offering native ssh, and one that covers some good ground with podman system connection add and podman image scp. Signed-off-by: Charlie Doern <cdoern@redhat.com>
This commit is contained in:
616
vendor/github.com/pkg/sftp/server.go
generated
vendored
Normal file
616
vendor/github.com/pkg/sftp/server.go
generated
vendored
Normal file
@ -0,0 +1,616 @@
|
||||
package sftp
|
||||
|
||||
// sftp server counterpart
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// SftpServerWorkerCount defines the number of workers for the SFTP server
|
||||
SftpServerWorkerCount = 8
|
||||
)
|
||||
|
||||
// Server is an SSH File Transfer Protocol (sftp) server.
|
||||
// This is intended to provide the sftp subsystem to an ssh server daemon.
|
||||
// This implementation currently supports most of sftp server protocol version 3,
|
||||
// as specified at http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
||||
type Server struct {
|
||||
*serverConn
|
||||
debugStream io.Writer
|
||||
readOnly bool
|
||||
pktMgr *packetManager
|
||||
openFiles map[string]*os.File
|
||||
openFilesLock sync.RWMutex
|
||||
handleCount int
|
||||
}
|
||||
|
||||
func (svr *Server) nextHandle(f *os.File) string {
|
||||
svr.openFilesLock.Lock()
|
||||
defer svr.openFilesLock.Unlock()
|
||||
svr.handleCount++
|
||||
handle := strconv.Itoa(svr.handleCount)
|
||||
svr.openFiles[handle] = f
|
||||
return handle
|
||||
}
|
||||
|
||||
func (svr *Server) closeHandle(handle string) error {
|
||||
svr.openFilesLock.Lock()
|
||||
defer svr.openFilesLock.Unlock()
|
||||
if f, ok := svr.openFiles[handle]; ok {
|
||||
delete(svr.openFiles, handle)
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
return EBADF
|
||||
}
|
||||
|
||||
func (svr *Server) getHandle(handle string) (*os.File, bool) {
|
||||
svr.openFilesLock.RLock()
|
||||
defer svr.openFilesLock.RUnlock()
|
||||
f, ok := svr.openFiles[handle]
|
||||
return f, ok
|
||||
}
|
||||
|
||||
type serverRespondablePacket interface {
|
||||
encoding.BinaryUnmarshaler
|
||||
id() uint32
|
||||
respond(svr *Server) responsePacket
|
||||
}
|
||||
|
||||
// NewServer creates a new Server instance around the provided streams, serving
|
||||
// content from the root of the filesystem. Optionally, ServerOption
|
||||
// functions may be specified to further configure the Server.
|
||||
//
|
||||
// A subsequent call to Serve() is required to begin serving files over SFTP.
|
||||
func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) {
|
||||
svrConn := &serverConn{
|
||||
conn: conn{
|
||||
Reader: rwc,
|
||||
WriteCloser: rwc,
|
||||
},
|
||||
}
|
||||
s := &Server{
|
||||
serverConn: svrConn,
|
||||
debugStream: ioutil.Discard,
|
||||
pktMgr: newPktMgr(svrConn),
|
||||
openFiles: make(map[string]*os.File),
|
||||
}
|
||||
|
||||
for _, o := range options {
|
||||
if err := o(s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// A ServerOption is a function which applies configuration to a Server.
|
||||
type ServerOption func(*Server) error
|
||||
|
||||
// WithDebug enables Server debugging output to the supplied io.Writer.
|
||||
func WithDebug(w io.Writer) ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.debugStream = w
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ReadOnly configures a Server to serve files in read-only mode.
|
||||
func ReadOnly() ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.readOnly = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAllocator enable the allocator.
|
||||
// After processing a packet we keep in memory the allocated slices
|
||||
// and we reuse them for new packets.
|
||||
// The allocator is experimental
|
||||
func WithAllocator() ServerOption {
|
||||
return func(s *Server) error {
|
||||
alloc := newAllocator()
|
||||
s.pktMgr.alloc = alloc
|
||||
s.conn.alloc = alloc
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type rxPacket struct {
|
||||
pktType fxp
|
||||
pktBytes []byte
|
||||
}
|
||||
|
||||
// Up to N parallel servers
|
||||
func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
|
||||
for pkt := range pktChan {
|
||||
// readonly checks
|
||||
readonly := true
|
||||
switch pkt := pkt.requestPacket.(type) {
|
||||
case notReadOnly:
|
||||
readonly = false
|
||||
case *sshFxpOpenPacket:
|
||||
readonly = pkt.readonly()
|
||||
case *sshFxpExtendedPacket:
|
||||
readonly = pkt.readonly()
|
||||
}
|
||||
|
||||
// If server is operating read-only and a write operation is requested,
|
||||
// return permission denied
|
||||
if !readonly && svr.readOnly {
|
||||
svr.pktMgr.readyPacket(
|
||||
svr.pktMgr.newOrderedResponse(statusFromError(pkt.id(), syscall.EPERM), pkt.orderID()),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := handlePacket(svr, pkt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handlePacket(s *Server, p orderedRequest) error {
|
||||
var rpkt responsePacket
|
||||
orderID := p.orderID()
|
||||
switch p := p.requestPacket.(type) {
|
||||
case *sshFxInitPacket:
|
||||
rpkt = &sshFxVersionPacket{
|
||||
Version: sftpProtocolVersion,
|
||||
Extensions: sftpExtensions,
|
||||
}
|
||||
case *sshFxpStatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Stat(toLocalPath(p.Path))
|
||||
rpkt = &sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
}
|
||||
case *sshFxpLstatPacket:
|
||||
// stat the requested file
|
||||
info, err := os.Lstat(toLocalPath(p.Path))
|
||||
rpkt = &sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
}
|
||||
case *sshFxpFstatPacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
var err error = EBADF
|
||||
var info os.FileInfo
|
||||
if ok {
|
||||
info, err = f.Stat()
|
||||
rpkt = &sshFxpStatResponse{
|
||||
ID: p.ID,
|
||||
info: info,
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
}
|
||||
case *sshFxpMkdirPacket:
|
||||
// TODO FIXME: ignore flags field
|
||||
err := os.Mkdir(toLocalPath(p.Path), 0755)
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
case *sshFxpRmdirPacket:
|
||||
err := os.Remove(toLocalPath(p.Path))
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
case *sshFxpRemovePacket:
|
||||
err := os.Remove(toLocalPath(p.Filename))
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
case *sshFxpRenamePacket:
|
||||
err := os.Rename(toLocalPath(p.Oldpath), toLocalPath(p.Newpath))
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
case *sshFxpSymlinkPacket:
|
||||
err := os.Symlink(toLocalPath(p.Targetpath), toLocalPath(p.Linkpath))
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
case *sshFxpClosePacket:
|
||||
rpkt = statusFromError(p.ID, s.closeHandle(p.Handle))
|
||||
case *sshFxpReadlinkPacket:
|
||||
f, err := os.Readlink(toLocalPath(p.Path))
|
||||
rpkt = &sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []*sshFxpNameAttr{
|
||||
{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
}
|
||||
case *sshFxpRealpathPacket:
|
||||
f, err := filepath.Abs(toLocalPath(p.Path))
|
||||
f = cleanPath(f)
|
||||
rpkt = &sshFxpNamePacket{
|
||||
ID: p.ID,
|
||||
NameAttrs: []*sshFxpNameAttr{
|
||||
{
|
||||
Name: f,
|
||||
LongName: f,
|
||||
Attrs: emptyFileStat,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
}
|
||||
case *sshFxpOpendirPacket:
|
||||
p.Path = toLocalPath(p.Path)
|
||||
|
||||
if stat, err := os.Stat(p.Path); err != nil {
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
} else if !stat.IsDir() {
|
||||
rpkt = statusFromError(p.ID, &os.PathError{
|
||||
Path: p.Path, Err: syscall.ENOTDIR})
|
||||
} else {
|
||||
rpkt = (&sshFxpOpenPacket{
|
||||
ID: p.ID,
|
||||
Path: p.Path,
|
||||
Pflags: sshFxfRead,
|
||||
}).respond(s)
|
||||
}
|
||||
case *sshFxpReadPacket:
|
||||
var err error = EBADF
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
if ok {
|
||||
err = nil
|
||||
data := p.getDataSlice(s.pktMgr.alloc, orderID)
|
||||
n, _err := f.ReadAt(data, int64(p.Offset))
|
||||
if _err != nil && (_err != io.EOF || n == 0) {
|
||||
err = _err
|
||||
}
|
||||
rpkt = &sshFxpDataPacket{
|
||||
ID: p.ID,
|
||||
Length: uint32(n),
|
||||
Data: data[:n],
|
||||
// do not use data[:n:n] here to clamp the capacity, we allocated extra capacity above to avoid reallocations
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
}
|
||||
|
||||
case *sshFxpWritePacket:
|
||||
f, ok := s.getHandle(p.Handle)
|
||||
var err error = EBADF
|
||||
if ok {
|
||||
_, err = f.WriteAt(p.Data, int64(p.Offset))
|
||||
}
|
||||
rpkt = statusFromError(p.ID, err)
|
||||
case *sshFxpExtendedPacket:
|
||||
if p.SpecificPacket == nil {
|
||||
rpkt = statusFromError(p.ID, ErrSSHFxOpUnsupported)
|
||||
} else {
|
||||
rpkt = p.respond(s)
|
||||
}
|
||||
case serverRespondablePacket:
|
||||
rpkt = p.respond(s)
|
||||
default:
|
||||
return fmt.Errorf("unexpected packet type %T", p)
|
||||
}
|
||||
|
||||
s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, orderID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serve serves SFTP connections until the streams stop or the SFTP subsystem
|
||||
// is stopped.
|
||||
func (svr *Server) Serve() error {
|
||||
defer func() {
|
||||
if svr.pktMgr.alloc != nil {
|
||||
svr.pktMgr.alloc.Free()
|
||||
}
|
||||
}()
|
||||
var wg sync.WaitGroup
|
||||
runWorker := func(ch chan orderedRequest) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := svr.sftpServerWorker(ch); err != nil {
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
}
|
||||
}()
|
||||
}
|
||||
pktChan := svr.pktMgr.workerChan(runWorker)
|
||||
|
||||
var err error
|
||||
var pkt requestPacket
|
||||
var pktType uint8
|
||||
var pktBytes []byte
|
||||
for {
|
||||
pktType, pktBytes, err = svr.serverConn.recvPacket(svr.pktMgr.getNextOrderID())
|
||||
if err != nil {
|
||||
// we don't care about releasing allocated pages here, the server will quit and the allocator freed
|
||||
break
|
||||
}
|
||||
|
||||
pkt, err = makePacket(rxPacket{fxp(pktType), pktBytes})
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, errUnknownExtendedPacket):
|
||||
//if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
|
||||
// debug("failed to send err packet: %v", err)
|
||||
// svr.conn.Close() // shuts down recvPacket
|
||||
// break
|
||||
//}
|
||||
default:
|
||||
debug("makePacket err: %v", err)
|
||||
svr.conn.Close() // shuts down recvPacket
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pktChan <- svr.pktMgr.newOrderedRequest(pkt)
|
||||
}
|
||||
|
||||
close(pktChan) // shuts down sftpServerWorkers
|
||||
wg.Wait() // wait for all workers to exit
|
||||
|
||||
// close any still-open files
|
||||
for handle, file := range svr.openFiles {
|
||||
fmt.Fprintf(svr.debugStream, "sftp server file with handle %q left open: %v\n", handle, file.Name())
|
||||
file.Close()
|
||||
}
|
||||
return err // error from recvPacket
|
||||
}
|
||||
|
||||
type ider interface {
|
||||
id() uint32
|
||||
}
|
||||
|
||||
// The init packet has no ID, so we just return a zero-value ID
|
||||
func (p *sshFxInitPacket) id() uint32 { return 0 }
|
||||
|
||||
type sshFxpStatResponse struct {
|
||||
ID uint32
|
||||
info os.FileInfo
|
||||
}
|
||||
|
||||
func (p *sshFxpStatResponse) marshalPacket() ([]byte, []byte, error) {
|
||||
l := 4 + 1 + 4 // uint32(length) + byte(type) + uint32(id)
|
||||
|
||||
b := make([]byte, 4, l)
|
||||
b = append(b, sshFxpAttrs)
|
||||
b = marshalUint32(b, p.ID)
|
||||
|
||||
var payload []byte
|
||||
payload = marshalFileInfo(payload, p.info)
|
||||
|
||||
return b, payload, nil
|
||||
}
|
||||
|
||||
func (p *sshFxpStatResponse) MarshalBinary() ([]byte, error) {
|
||||
header, payload, err := p.marshalPacket()
|
||||
return append(header, payload...), err
|
||||
}
|
||||
|
||||
var emptyFileStat = []interface{}{uint32(0)}
|
||||
|
||||
func (p *sshFxpOpenPacket) readonly() bool {
|
||||
return !p.hasPflags(sshFxfWrite)
|
||||
}
|
||||
|
||||
func (p *sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
|
||||
for _, f := range flags {
|
||||
if p.Pflags&f == 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
|
||||
var osFlags int
|
||||
if p.hasPflags(sshFxfRead, sshFxfWrite) {
|
||||
osFlags |= os.O_RDWR
|
||||
} else if p.hasPflags(sshFxfWrite) {
|
||||
osFlags |= os.O_WRONLY
|
||||
} else if p.hasPflags(sshFxfRead) {
|
||||
osFlags |= os.O_RDONLY
|
||||
} else {
|
||||
// how are they opening?
|
||||
return statusFromError(p.ID, syscall.EINVAL)
|
||||
}
|
||||
|
||||
// Don't use O_APPEND flag as it conflicts with WriteAt.
|
||||
// The sshFxfAppend flag is a no-op here as the client sends the offsets.
|
||||
|
||||
if p.hasPflags(sshFxfCreat) {
|
||||
osFlags |= os.O_CREATE
|
||||
}
|
||||
if p.hasPflags(sshFxfTrunc) {
|
||||
osFlags |= os.O_TRUNC
|
||||
}
|
||||
if p.hasPflags(sshFxfExcl) {
|
||||
osFlags |= os.O_EXCL
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(toLocalPath(p.Path), osFlags, 0644)
|
||||
if err != nil {
|
||||
return statusFromError(p.ID, err)
|
||||
}
|
||||
|
||||
handle := svr.nextHandle(f)
|
||||
return &sshFxpHandlePacket{ID: p.ID, Handle: handle}
|
||||
}
|
||||
|
||||
func (p *sshFxpReaddirPacket) respond(svr *Server) responsePacket {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return statusFromError(p.ID, EBADF)
|
||||
}
|
||||
|
||||
dirents, err := f.Readdir(128)
|
||||
if err != nil {
|
||||
return statusFromError(p.ID, err)
|
||||
}
|
||||
|
||||
idLookup := osIDLookup{}
|
||||
|
||||
ret := &sshFxpNamePacket{ID: p.ID}
|
||||
for _, dirent := range dirents {
|
||||
ret.NameAttrs = append(ret.NameAttrs, &sshFxpNameAttr{
|
||||
Name: dirent.Name(),
|
||||
LongName: runLs(idLookup, dirent),
|
||||
Attrs: []interface{}{dirent},
|
||||
})
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket {
|
||||
// additional unmarshalling is required for each possibility here
|
||||
b := p.Attrs.([]byte)
|
||||
var err error
|
||||
|
||||
p.Path = toLocalPath(p.Path)
|
||||
|
||||
debug("setstat name \"%s\"", p.Path)
|
||||
if (p.Flags & sshFileXferAttrSize) != 0 {
|
||||
var size uint64
|
||||
if size, b, err = unmarshalUint64Safe(b); err == nil {
|
||||
err = os.Truncate(p.Path, int64(size))
|
||||
}
|
||||
}
|
||||
if (p.Flags & sshFileXferAttrPermissions) != 0 {
|
||||
var mode uint32
|
||||
if mode, b, err = unmarshalUint32Safe(b); err == nil {
|
||||
err = os.Chmod(p.Path, os.FileMode(mode))
|
||||
}
|
||||
}
|
||||
if (p.Flags & sshFileXferAttrACmodTime) != 0 {
|
||||
var atime uint32
|
||||
var mtime uint32
|
||||
if atime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else {
|
||||
atimeT := time.Unix(int64(atime), 0)
|
||||
mtimeT := time.Unix(int64(mtime), 0)
|
||||
err = os.Chtimes(p.Path, atimeT, mtimeT)
|
||||
}
|
||||
}
|
||||
if (p.Flags & sshFileXferAttrUIDGID) != 0 {
|
||||
var uid uint32
|
||||
var gid uint32
|
||||
if uid, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else if gid, _, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else {
|
||||
err = os.Chown(p.Path, int(uid), int(gid))
|
||||
}
|
||||
}
|
||||
|
||||
return statusFromError(p.ID, err)
|
||||
}
|
||||
|
||||
func (p *sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
|
||||
f, ok := svr.getHandle(p.Handle)
|
||||
if !ok {
|
||||
return statusFromError(p.ID, EBADF)
|
||||
}
|
||||
|
||||
// additional unmarshalling is required for each possibility here
|
||||
b := p.Attrs.([]byte)
|
||||
var err error
|
||||
|
||||
debug("fsetstat name \"%s\"", f.Name())
|
||||
if (p.Flags & sshFileXferAttrSize) != 0 {
|
||||
var size uint64
|
||||
if size, b, err = unmarshalUint64Safe(b); err == nil {
|
||||
err = f.Truncate(int64(size))
|
||||
}
|
||||
}
|
||||
if (p.Flags & sshFileXferAttrPermissions) != 0 {
|
||||
var mode uint32
|
||||
if mode, b, err = unmarshalUint32Safe(b); err == nil {
|
||||
err = f.Chmod(os.FileMode(mode))
|
||||
}
|
||||
}
|
||||
if (p.Flags & sshFileXferAttrACmodTime) != 0 {
|
||||
var atime uint32
|
||||
var mtime uint32
|
||||
if atime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else if mtime, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else {
|
||||
atimeT := time.Unix(int64(atime), 0)
|
||||
mtimeT := time.Unix(int64(mtime), 0)
|
||||
err = os.Chtimes(f.Name(), atimeT, mtimeT)
|
||||
}
|
||||
}
|
||||
if (p.Flags & sshFileXferAttrUIDGID) != 0 {
|
||||
var uid uint32
|
||||
var gid uint32
|
||||
if uid, b, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else if gid, _, err = unmarshalUint32Safe(b); err != nil {
|
||||
} else {
|
||||
err = f.Chown(int(uid), int(gid))
|
||||
}
|
||||
}
|
||||
|
||||
return statusFromError(p.ID, err)
|
||||
}
|
||||
|
||||
func statusFromError(id uint32, err error) *sshFxpStatusPacket {
|
||||
ret := &sshFxpStatusPacket{
|
||||
ID: id,
|
||||
StatusError: StatusError{
|
||||
// sshFXOk = 0
|
||||
// sshFXEOF = 1
|
||||
// sshFXNoSuchFile = 2 ENOENT
|
||||
// sshFXPermissionDenied = 3
|
||||
// sshFXFailure = 4
|
||||
// sshFXBadMessage = 5
|
||||
// sshFXNoConnection = 6
|
||||
// sshFXConnectionLost = 7
|
||||
// sshFXOPUnsupported = 8
|
||||
Code: sshFxOk,
|
||||
},
|
||||
}
|
||||
if err == nil {
|
||||
return ret
|
||||
}
|
||||
|
||||
debug("statusFromError: error is %T %#v", err, err)
|
||||
ret.StatusError.Code = sshFxFailure
|
||||
ret.StatusError.msg = err.Error()
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
ret.StatusError.Code = sshFxNoSuchFile
|
||||
return ret
|
||||
}
|
||||
if code, ok := translateSyscallError(err); ok {
|
||||
ret.StatusError.Code = code
|
||||
return ret
|
||||
}
|
||||
|
||||
switch e := err.(type) {
|
||||
case fxerr:
|
||||
ret.StatusError.Code = uint32(e)
|
||||
default:
|
||||
if e == io.EOF {
|
||||
ret.StatusError.Code = sshFxEOF
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
Reference in New Issue
Block a user