sftp

package module
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2026 License: BSD-2-Clause Imports: 27 Imported by: 0

README

sftp

The sftp package provides support for file system operations on remote ssh servers using the SFTP subsystem. It also implements an SFTP server for serving files from the filesystem.

CI Status Go Reference

usage and examples

See https://pkg.go.dev/github.com/ad3n/sftp for examples and usage.

The basic operation of the package mirrors the facilities of the os package.

The Walker interface for directory traversal is heavily inspired by Keith Rarick's fs package.

roadmap

  • There is way too much duplication in the Client methods. If there was an unmarshal(interface{}) method this would reduce a heap of the duplication.

contributing

We welcome pull requests, bug fixes and issue reports.

Before proposing a large change, first please discuss your change by raising an issue.

For API/code bugs, please include a small, self contained code example to reproduce the issue. For pull requests, remember test coverage.

We try to handle issues and pull requests with a 0 open philosophy. That means we will try to address the submission as soon as possible and will work toward a resolution. If progress can no longer be made (eg. unreproducible bug) or stops (eg. unresponsive submitter), we will close the bug.

Thanks.

Documentation

Overview

Example
package main

import (
	"log"

	"github.com/ad3n/sftp"
	"golang.org/x/crypto/ssh"
)

func main() {
	var conn *ssh.Client

	client, err := sftp.NewClient(conn)
	if err != nil {
		log.Fatal(err)
	}

	defer client.Close()

	w := client.Walk("/home/user")
	for w.Step() {
		if w.Err() != nil {
			continue
		}

		log.Println(w.Path())
	}

	f, err := client.Create("hello.txt")
	if err != nil {
		log.Fatal(err)
	}

	if _, err := f.Write([]byte("Hello world!")); err != nil {
		log.Fatal(err)
	}

	f.Close()

	fi, err := client.Lstat("hello.txt")
	if err != nil {
		log.Fatal(err)
	}

	log.Println(fi)
}
Example (Reconnect)
package main

import (
	"log"
	"sync"
	"time"

	"github.com/ad3n/sftp"
	"golang.org/x/crypto/ssh"
)

type reconnectingClient struct {
	addr   string
	config *ssh.ClientConfig

	mu     sync.Mutex
	client *sftp.Client
	done   chan struct{}
}

func (rc *reconnectingClient) Client() (*sftp.Client, error) {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	if rc.client != nil {
		select {
		case <-rc.done:

		default:

			if _, err := rc.client.RealPath("."); err == nil {
				return rc.client, nil
			}

			rc.client.Close()
		}

		rc.client = nil
	}

	conn, err := ssh.Dial("tcp", rc.addr, rc.config)
	if err != nil {
		return nil, err
	}

	client, err := sftp.NewClient(conn)
	if err != nil {
		conn.Close()
		return nil, err
	}

	done := make(chan struct{})
	go func() {

		err := client.Wait()
		conn.Close()
		close(done)
		log.Print("sftp: disconnected: ", err)
	}()

	rc.client, rc.done = client, done
	return client, nil
}

func (rc *reconnectingClient) Close() error {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	if rc.client == nil {
		return nil
	}

	err := rc.client.Close()
	<-rc.done
	rc.client = nil

	return err
}

func main() {
	var config *ssh.ClientConfig

	rc := &reconnectingClient{
		addr:   "example.com:22",
		config: config,
	}
	defer rc.Close()

	for range time.Tick(time.Minute) {
		client, err := rc.Client()
		if err != nil {
			log.Print(err)
			continue
		}

		fi, err := client.Stat("/upload/ready")
		if err != nil {
			log.Print(err)
			continue
		}

		log.Print(fi.Name(), " is ready")
	}
}

Index

Examples

Constants

View Source
const (
	ErrSSHFxOk = fxerr(sshFxOk)

	ErrSSHFxEOF = fxerr(sshFxEOF)

	ErrSSHFxNoSuchFile = fxerr(sshFxNoSuchFile)

	ErrSSHFxPermissionDenied = fxerr(sshFxPermissionDenied)

	ErrSSHFxFailure = fxerr(sshFxFailure)

	ErrSSHFxBadMessage = fxerr(sshFxBadMessage)

	ErrSSHFxNoConnection = fxerr(sshFxNoConnection)

	ErrSSHFxConnectionLost = fxerr(sshFxConnectionLost)

	ErrSSHFxOpUnsupported = fxerr(sshFxOPUnsupported)

	ErrSshFxOk = ErrSSHFxOk

	ErrSshFxEof = ErrSSHFxEOF

	ErrSshFxNoSuchFile = ErrSSHFxNoSuchFile

	ErrSshFxPermissionDenied = ErrSSHFxPermissionDenied

	ErrSshFxFailure = ErrSSHFxFailure

	ErrSshFxBadMessage = ErrSSHFxBadMessage

	ErrSshFxNoConnection = ErrSSHFxNoConnection

	ErrSshFxConnectionLost = ErrSSHFxConnectionLost

	ErrSshFxOpUnsupported = ErrSSHFxOpUnsupported
)
View Source
const EBADF = syscall.EBADF
View Source
const (
	S_IFMT = uint32(sshfx.ModeType)
)
View Source
const SftpServerWorkerCount = 8

Variables

View Source
var (
	ErrInternalInconsistency = errors.New("internal inconsistency")

	InternalInconsistency = ErrInternalInconsistency
)
View Source
var ErrBadPattern = path.ErrBadPattern
View Source
var MaxFilelist int64 = 100

Functions

func Join

func Join(elem ...string) string

func Match

func Match(pattern, name string) (matched bool, err error)

func SetSFTPExtensions

func SetSFTPExtensions(extensions ...string) error

func Split

func Split(p string) (dir, file string)

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewClient

func NewClient(conn *ssh.Client, opts ...ClientOption) (*Client, error)

func NewClientPipe

func NewClientPipe(rd io.Reader, wr io.WriteCloser, opts ...ClientOption) (*Client, error)
Example
package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"

	"github.com/ad3n/sftp"
)

func main() {
	cmd := exec.Command("ssh", "example.com", "-s", "sftp")

	cmd.Stderr = os.Stderr

	wr, err := cmd.StdinPipe()
	if err != nil {
		log.Fatal(err)
	}

	rd, err := cmd.StdoutPipe()
	if err != nil {
		log.Fatal(err)
	}

	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}

	defer cmd.Wait()

	client, err := sftp.NewClientPipe(rd, wr)
	if err != nil {
		log.Fatal(err)
	}

	list, err := client.ReadDir("/")
	if err != nil {
		log.Fatal(err)
	}

	for _, item := range list {
		fmt.Println(item.Name())
	}

	client.Close()
}
Example (Tls)
package main

import (
	"crypto/tls"
	"fmt"
	"log"

	"github.com/ad3n/sftp"
)

func main() {
	var clientCert tls.Certificate

	conn, err := tls.Dial("tcp", "example.com:2022", &tls.Config{
		Certificates: []tls.Certificate{clientCert},
	})
	if err != nil {
		log.Fatal(err)
	}

	client, err := sftp.NewClientPipe(conn, conn)
	if err != nil {
		log.Fatal(err)
	}

	defer client.Close()

	list, err := client.ReadDir("/")
	if err != nil {
		log.Fatal(err)
	}

	for _, item := range list {
		fmt.Println(item.Name())
	}
}

func (*Client) Chmod

func (c *Client) Chmod(path string, mode os.FileMode) error

func (*Client) Chown

func (c *Client) Chown(path string, uid, gid int) error

func (*Client) Chtimes

func (c *Client) Chtimes(path string, atime time.Time, mtime time.Time) error

func (*Client) Close

func (c *Client) Close() error

func (*Client) Create

func (c *Client) Create(path string) (*File, error)

func (*Client) Getwd

func (c *Client) Getwd() (string, error)

func (*Client) Glob

func (c *Client) Glob(pattern string) (matches []string, err error)

func (*Client) HasExtension

func (c *Client) HasExtension(name string) (string, bool)

func (*Client) Join

func (c *Client) Join(elem ...string) string
func (c *Client) Link(oldname, newname string) error

func (*Client) Lstat

func (c *Client) Lstat(p string) (os.FileInfo, error)

func (*Client) Mkdir

func (c *Client) Mkdir(path string) error
Example (Parents)
package main

import (
	"fmt"
	"log"
	"os"
	"path"
	"strings"

	"github.com/ad3n/sftp"
	"golang.org/x/crypto/ssh"
)

func main() {
	var conn *ssh.Client

	client, err := sftp.NewClient(conn)
	if err != nil {
		log.Fatal(err)
	}

	defer client.Close()

	sshFxFailure := uint32(4)
	mkdirParents := func(client *sftp.Client, dir string) (err error) {
		var parents string

		if path.IsAbs(dir) {
			parents = "/"
		}

		for name := range strings.SplitSeq(dir, "/") {
			if name == "" {
				continue
			}

			parents = path.Join(parents, name)
			err = client.Mkdir(parents)
			if status, ok := err.(*sftp.StatusError); ok {
				if status.Code == sshFxFailure {
					var fi os.FileInfo
					fi, err = client.Stat(parents)
					if err == nil {
						if !fi.IsDir() {
							return fmt.Errorf("file exists: %s", parents)
						}
					}
				}
			}

			if err != nil {
				break
			}
		}

		return err
	}

	err = mkdirParents(client, "/tmp/foo/bar")
	if err != nil {
		log.Fatal(err)
	}
}

func (*Client) MkdirAll

func (c *Client) MkdirAll(path string) error

func (*Client) Open

func (c *Client) Open(path string) (*File, error)

func (*Client) OpenFile

func (c *Client) OpenFile(path string, f int) (*File, error)

func (*Client) PosixRename

func (c *Client) PosixRename(oldname, newname string) error

func (*Client) ReadDir

func (c *Client) ReadDir(p string) ([]os.FileInfo, error)

func (*Client) ReadDirContext

func (c *Client) ReadDirContext(ctx context.Context, p string) ([]os.FileInfo, error)
func (c *Client) ReadLink(p string) (string, error)

func (*Client) RealPath

func (c *Client) RealPath(path string) (string, error)

func (*Client) Remove

func (c *Client) Remove(path string) error

func (*Client) RemoveAll

func (c *Client) RemoveAll(path string) error

func (*Client) RemoveDirectory

func (c *Client) RemoveDirectory(path string) error

func (*Client) Rename

func (c *Client) Rename(oldname, newname string) error

func (*Client) SetExtendedData

func (c *Client) SetExtendedData(path string, extended []StatExtended) error

func (*Client) Stat

func (c *Client) Stat(p string) (os.FileInfo, error)

func (*Client) StatVFS

func (c *Client) StatVFS(path string) (*StatVFS, error)
func (c *Client) Symlink(oldname, newname string) error

func (*Client) Truncate

func (c *Client) Truncate(path string, size int64) error

func (*Client) Wait

func (c *Client) Wait() error

func (*Client) Walk

func (c *Client) Walk(root string) *fs.Walker

type ClientOption

type ClientOption func(*Client) error

func CopyStderrTo

func CopyStderrTo(wr io.Writer) ClientOption

func ForceSingleFile added in v0.0.7

func ForceSingleFile() ClientOption

func MaxConcurrentRequestsPerFile

func MaxConcurrentRequestsPerFile(n int) ClientOption

func MaxPacket

func MaxPacket(size int) ClientOption

func MaxPacketChecked

func MaxPacketChecked(size int) ClientOption

func MaxPacketUnchecked

func MaxPacketUnchecked(size int) ClientOption

func UseConcurrentReads

func UseConcurrentReads(value bool) ClientOption

func UseConcurrentWrites

func UseConcurrentWrites(value bool) ClientOption

func UseFstat

func UseFstat(value bool) ClientOption

type File

type File struct {
	// contains filtered or unexported fields
}

func (*File) Chmod

func (f *File) Chmod(mode os.FileMode) error

func (*File) Chown

func (f *File) Chown(uid, gid int) error

func (*File) Close

func (f *File) Close() error

func (*File) Name

func (f *File) Name() string

func (*File) Read

func (f *File) Read(b []byte) (int, error)

func (*File) ReadAt

func (f *File) ReadAt(b []byte, off int64) (int, error)

func (*File) ReadFrom

func (f *File) ReadFrom(r io.Reader) (int64, error)
Example (Bufio)
package main

import (
	"bufio"
	"io"

	"github.com/ad3n/sftp"
)

func main() {
	var data_source io.Reader
	var f *sftp.File
	type writerOnly struct{ io.Writer }
	bw := bufio.NewWriter(writerOnly{f})
	bw.ReadFrom(data_source)
}

func (*File) ReadFromWithConcurrency

func (f *File) ReadFromWithConcurrency(r io.Reader, concurrency int) (read int64, err error)

func (*File) Seek

func (f *File) Seek(offset int64, whence int) (int64, error)

func (*File) SetExtendedData

func (f *File) SetExtendedData(path string, extended []StatExtended) error

func (*File) Stat

func (f *File) Stat() (os.FileInfo, error)

func (*File) Sync

func (f *File) Sync() error

func (*File) Truncate

func (f *File) Truncate(size int64) error

func (*File) Write

func (f *File) Write(b []byte) (int, error)

func (*File) WriteAt

func (f *File) WriteAt(b []byte, off int64) (written int, err error)

func (*File) WriteTo

func (f *File) WriteTo(w io.Writer) (written int64, err error)

type FileAttrFlags

type FileAttrFlags struct {
	Size, UidGid, Permissions, Acmodtime bool
}

type FileCmder

type FileCmder interface {
	Filecmd(*Request) error
}

type FileInfoExtendedData

type FileInfoExtendedData interface {
	os.FileInfo
	Extended() []StatExtended
}

type FileInfoUidGid

type FileInfoUidGid interface {
	os.FileInfo
	Uid() uint32
	Gid() uint32
}

type FileLister

type FileLister interface {
	Filelist(*Request) (ListerAt, error)
}

type FileOpenFlags

type FileOpenFlags struct {
	Read, Write, Append, Creat, Trunc, Excl bool
}

type FileReader

type FileReader interface {
	Fileread(*Request) (io.ReaderAt, error)
}

type FileStat

type FileStat struct {
	Size     uint64
	Mode     uint32
	Mtime    uint32
	Atime    uint32
	UID      uint32
	GID      uint32
	Extended []StatExtended
}

func (*FileStat) AccessTime

func (fs *FileStat) AccessTime() time.Time

func (*FileStat) FileMode

func (fs *FileStat) FileMode() os.FileMode

func (*FileStat) ModTime

func (fs *FileStat) ModTime() time.Time

type FileWriter

type FileWriter interface {
	Filewrite(*Request) (io.WriterAt, error)
}

type Handlers

type Handlers struct {
	FileGet  FileReader
	FilePut  FileWriter
	FileCmd  FileCmder
	FileList FileLister
}

func InMemHandler

func InMemHandler() Handlers

type ListerAt

type ListerAt interface {
	ListAt([]os.FileInfo, int64) (int, error)
}

type LstatFileLister

type LstatFileLister interface {
	FileLister
	Lstat(*Request) (ListerAt, error)
}

type NameLookupFileLister

type NameLookupFileLister interface {
	FileLister
	LookupUserName(string) string
	LookupGroupName(string) string
}

type OpenFileWriter

type OpenFileWriter interface {
	FileWriter
	OpenFile(*Request) (WriterAtReaderAt, error)
}

type PosixRenameFileCmder

type PosixRenameFileCmder interface {
	FileCmder
	PosixRename(*Request) error
}

type ReadlinkFileLister

type ReadlinkFileLister interface {
	FileLister
	Readlink(string) (string, error)
}

type RealPathFileLister

type RealPathFileLister interface {
	FileLister
	RealPath(string) (string, error)
}

type Request

type Request struct {
	Method   string
	Filepath string

	Flags  uint32
	Attrs  []byte
	Target string
	// contains filtered or unexported fields
}

func NewRequest

func NewRequest(method, path string) *Request

func (*Request) AttrFlags

func (r *Request) AttrFlags() FileAttrFlags

func (*Request) Attributes

func (r *Request) Attributes() *FileStat

func (*Request) Context

func (r *Request) Context() context.Context

func (*Request) Pflags

func (r *Request) Pflags() FileOpenFlags

func (*Request) WithContext

func (r *Request) WithContext(ctx context.Context) *Request

type RequestServer

type RequestServer struct {
	Handlers Handlers
	// contains filtered or unexported fields
}

func NewRequestServer

func NewRequestServer(rwc io.ReadWriteCloser, h Handlers, options ...RequestServerOption) *RequestServer

func (*RequestServer) Close

func (rs *RequestServer) Close() error

func (*RequestServer) Serve

func (rs *RequestServer) Serve() error

type RequestServerOption

type RequestServerOption func(*RequestServer)

func WithRSAllocator

func WithRSAllocator() RequestServerOption

func WithRSMaxTxPacket

func WithRSMaxTxPacket(size uint32) RequestServerOption

func WithStartDirectory

func WithStartDirectory(startDirectory string) RequestServerOption

type Server

type Server struct {
	// contains filtered or unexported fields
}

func NewServer

func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error)
Example
package main

import (
	"crypto/tls"
	"crypto/x509"
	"io"
	"log"

	"github.com/ad3n/sftp"
)

func main() {
	var serverCert tls.Certificate
	var clientCAs *x509.CertPool

	listener, err := tls.Listen("tcp", ":2022", &tls.Config{
		Certificates: []tls.Certificate{serverCert},
		ClientAuth:   tls.RequireAndVerifyClientCert,
		ClientCAs:    clientCAs,
	})
	if err != nil {
		log.Fatal(err)
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Fatal(err)
		}

		go func() {
			server, err := sftp.NewServer(conn)
			if err != nil {
				log.Print(err)
				conn.Close()
				return
			}

			if err := server.Serve(); err != nil && err != io.EOF {
				log.Print("sftp server completed with error: ", err)
			}

			conn.Close()
		}()
	}
}

func (*Server) Serve

func (svr *Server) Serve() error

type ServerOption

type ServerOption func(*Server) error

func ReadOnly

func ReadOnly() ServerOption

func WindowsRootEnumeratesDrives

func WindowsRootEnumeratesDrives() ServerOption

func WithAllocator

func WithAllocator() ServerOption

func WithDebug

func WithDebug(w io.Writer) ServerOption

func WithMaxTxPacket

func WithMaxTxPacket(size uint32) ServerOption

func WithServerWorkingDirectory

func WithServerWorkingDirectory(workDir string) ServerOption

type StatExtended

type StatExtended struct {
	ExtType string
	ExtData string
}

type StatVFS

type StatVFS struct {
	ID      uint32
	Bsize   uint64
	Frsize  uint64
	Blocks  uint64
	Bfree   uint64
	Bavail  uint64
	Files   uint64
	Ffree   uint64
	Favail  uint64
	Fsid    uint64
	Flag    uint64
	Namemax uint64
}

func (*StatVFS) FreeSpace

func (p *StatVFS) FreeSpace() uint64

func (*StatVFS) MarshalBinary

func (p *StatVFS) MarshalBinary() ([]byte, error)

func (*StatVFS) TotalSpace

func (p *StatVFS) TotalSpace() uint64

type StatVFSFileCmder

type StatVFSFileCmder interface {
	FileCmder
	StatVFS(*Request) (*StatVFS, error)
}

type StatusError

type StatusError struct {
	Code uint32
	// contains filtered or unexported fields
}

func (*StatusError) Error

func (s *StatusError) Error() string

func (*StatusError) FxCode

func (s *StatusError) FxCode() fxerr

type TransferError

type TransferError interface {
	TransferError(err error)
}

type WriterAtReaderAt

type WriterAtReaderAt interface {
	io.WriterAt
	io.ReaderAt
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
⌂ 换网址