serve ftp: update to goftp.io/server v2.0.1 - fixes #7237
This commit is contained in:
parent
7fc573db27
commit
94a320f23c
4 changed files with 74 additions and 91 deletions
|
@ -9,6 +9,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
iofs "io/fs"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
@ -29,7 +30,7 @@ import (
|
||||||
"github.com/rclone/rclone/vfs/vfsflags"
|
"github.com/rclone/rclone/vfs/vfsflags"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
ftp "goftp.io/server/core"
|
ftp "goftp.io/server/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options contains options for the http Server
|
// Options contains options for the http Server
|
||||||
|
@ -121,8 +122,8 @@ You can set a single username and password with the --user and --pass flags.
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// server contains everything to run the server
|
// driver contains everything to run the driver for the FTP server
|
||||||
type server struct {
|
type driver struct {
|
||||||
f fs.Fs
|
f fs.Fs
|
||||||
srv *ftp.Server
|
srv *ftp.Server
|
||||||
ctx context.Context // for global config
|
ctx context.Context // for global config
|
||||||
|
@ -130,12 +131,13 @@ type server struct {
|
||||||
vfs *vfs.VFS
|
vfs *vfs.VFS
|
||||||
proxy *proxy.Proxy
|
proxy *proxy.Proxy
|
||||||
useTLS bool
|
useTLS bool
|
||||||
|
lock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
var passivePortsRe = regexp.MustCompile(`^\s*\d+\s*-\s*\d+\s*$`)
|
var passivePortsRe = regexp.MustCompile(`^\s*\d+\s*-\s*\d+\s*$`)
|
||||||
|
|
||||||
// Make a new FTP to serve the remote
|
// Make a new FTP to serve the remote
|
||||||
func newServer(ctx context.Context, f fs.Fs, opt *Options) (*server, error) {
|
func newServer(ctx context.Context, f fs.Fs, opt *Options) (*driver, error) {
|
||||||
host, port, err := net.SplitHostPort(opt.ListenAddr)
|
host, port, err := net.SplitHostPort(opt.ListenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to parse host:port")
|
return nil, errors.New("failed to parse host:port")
|
||||||
|
@ -145,54 +147,58 @@ func newServer(ctx context.Context, f fs.Fs, opt *Options) (*server, error) {
|
||||||
return nil, errors.New("failed to parse host:port")
|
return nil, errors.New("failed to parse host:port")
|
||||||
}
|
}
|
||||||
|
|
||||||
s := &server{
|
d := &driver{
|
||||||
f: f,
|
f: f,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
opt: *opt,
|
opt: *opt,
|
||||||
}
|
}
|
||||||
if proxyflags.Opt.AuthProxy != "" {
|
if proxyflags.Opt.AuthProxy != "" {
|
||||||
s.proxy = proxy.New(ctx, &proxyflags.Opt)
|
d.proxy = proxy.New(ctx, &proxyflags.Opt)
|
||||||
} else {
|
} else {
|
||||||
s.vfs = vfs.New(f, &vfsflags.Opt)
|
d.vfs = vfs.New(f, &vfsflags.Opt)
|
||||||
}
|
}
|
||||||
s.useTLS = s.opt.TLSKey != ""
|
d.useTLS = d.opt.TLSKey != ""
|
||||||
|
|
||||||
// Check PassivePorts format since the server library doesn't!
|
// Check PassivePorts format since the server library doesn't!
|
||||||
if !passivePortsRe.MatchString(opt.PassivePorts) {
|
if !passivePortsRe.MatchString(opt.PassivePorts) {
|
||||||
return nil, fmt.Errorf("invalid format for passive ports %q", opt.PassivePorts)
|
return nil, fmt.Errorf("invalid format for passive ports %q", opt.PassivePorts)
|
||||||
}
|
}
|
||||||
|
|
||||||
ftpopt := &ftp.ServerOpts{
|
ftpopt := &ftp.Options{
|
||||||
Name: "Rclone FTP Server",
|
Name: "Rclone FTP Server",
|
||||||
WelcomeMessage: "Welcome to Rclone " + fs.Version + " FTP Server",
|
WelcomeMessage: "Welcome to Rclone " + fs.Version + " FTP Server",
|
||||||
Factory: s, // implemented by NewDriver method
|
Driver: d,
|
||||||
Hostname: host,
|
Hostname: host,
|
||||||
Port: portNum,
|
Port: portNum,
|
||||||
PublicIP: opt.PublicIP,
|
PublicIP: opt.PublicIP,
|
||||||
PassivePorts: opt.PassivePorts,
|
PassivePorts: opt.PassivePorts,
|
||||||
Auth: s, // implemented by CheckPasswd method
|
Auth: d,
|
||||||
|
Perm: ftp.NewSimplePerm("ftp", "ftp"), // fake user and group
|
||||||
Logger: &Logger{},
|
Logger: &Logger{},
|
||||||
TLS: s.useTLS,
|
TLS: d.useTLS,
|
||||||
CertFile: s.opt.TLSCert,
|
CertFile: d.opt.TLSCert,
|
||||||
KeyFile: s.opt.TLSKey,
|
KeyFile: d.opt.TLSKey,
|
||||||
//TODO implement a maximum of https://godoc.org/goftp.io/server#ServerOpts
|
//TODO implement a maximum of https://godoc.org/goftp.io/server#ServerOpts
|
||||||
}
|
}
|
||||||
s.srv = ftp.NewServer(ftpopt)
|
d.srv, err = ftp.NewServer(ftpopt)
|
||||||
return s, nil
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create new FTP server: %w", err)
|
||||||
|
}
|
||||||
|
return d, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// serve runs the ftp server
|
// serve runs the ftp server
|
||||||
func (s *server) serve() error {
|
func (d *driver) serve() error {
|
||||||
fs.Logf(s.f, "Serving FTP on %s", s.srv.Hostname+":"+strconv.Itoa(s.srv.Port))
|
fs.Logf(d.f, "Serving FTP on %s", d.srv.Hostname+":"+strconv.Itoa(d.srv.Port))
|
||||||
return s.srv.ListenAndServe()
|
return d.srv.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
// close stops the ftp server
|
// close stops the ftp server
|
||||||
//
|
//
|
||||||
//lint:ignore U1000 unused when not building linux
|
//lint:ignore U1000 unused when not building linux
|
||||||
func (s *server) close() error {
|
func (d *driver) close() error {
|
||||||
fs.Logf(s.f, "Stopping FTP on %s", s.srv.Hostname+":"+strconv.Itoa(s.srv.Port))
|
fs.Logf(d.f, "Stopping FTP on %s", d.srv.Hostname+":"+strconv.Itoa(d.srv.Port))
|
||||||
return s.srv.Shutdown()
|
return d.srv.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger ftp logger output formatted message
|
// Logger ftp logger output formatted message
|
||||||
|
@ -223,44 +229,17 @@ func (l *Logger) PrintResponse(sessionID string, code int, message string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPasswd handle auth based on configuration
|
// CheckPasswd handle auth based on configuration
|
||||||
//
|
func (d *driver) CheckPasswd(sctx *ftp.Context, user, pass string) (ok bool, err error) {
|
||||||
// This is not used - the one in Driver should be called instead
|
if d.proxy != nil {
|
||||||
func (s *server) CheckPasswd(user, pass string) (ok bool, err error) {
|
|
||||||
err = errors.New("internal error: server.CheckPasswd should never be called")
|
|
||||||
fs.Errorf(nil, "Error: %v", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDriver starts a new session for each client connection
|
|
||||||
func (s *server) NewDriver() (ftp.Driver, error) {
|
|
||||||
log.Trace("", "Init driver")("")
|
|
||||||
d := &Driver{
|
|
||||||
s: s,
|
|
||||||
vfs: s.vfs, // this can be nil if proxy set
|
|
||||||
}
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Driver implementation of ftp server
|
|
||||||
type Driver struct {
|
|
||||||
s *server
|
|
||||||
vfs *vfs.VFS
|
|
||||||
lock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckPasswd handle auth based on configuration
|
|
||||||
func (d *Driver) CheckPasswd(user, pass string) (ok bool, err error) {
|
|
||||||
s := d.s
|
|
||||||
if s.proxy != nil {
|
|
||||||
var VFS *vfs.VFS
|
var VFS *vfs.VFS
|
||||||
VFS, _, err = s.proxy.Call(user, pass, false)
|
VFS, _, err = d.proxy.Call(user, pass, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fs.Infof(nil, "proxy login failed: %v", err)
|
fs.Infof(nil, "proxy login failed: %v", err)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
d.vfs = VFS
|
d.vfs = VFS
|
||||||
} else {
|
} else {
|
||||||
ok = s.opt.BasicUser == user && (s.opt.BasicPass == "" || s.opt.BasicPass == pass)
|
ok = d.opt.BasicUser == user && (d.opt.BasicPass == "" || d.opt.BasicPass == pass)
|
||||||
if !ok {
|
if !ok {
|
||||||
fs.Infof(nil, "login failed: bad credentials")
|
fs.Infof(nil, "login failed: bad credentials")
|
||||||
return false, nil
|
return false, nil
|
||||||
|
@ -270,7 +249,7 @@ func (d *Driver) CheckPasswd(user, pass string) (ok bool, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stat get information on file or folder
|
// Stat get information on file or folder
|
||||||
func (d *Driver) Stat(path string) (fi ftp.FileInfo, err error) {
|
func (d *driver) Stat(sctx *ftp.Context, path string) (fi iofs.FileInfo, err error) {
|
||||||
defer log.Trace(path, "")("fi=%+v, err = %v", &fi, &err)
|
defer log.Trace(path, "")("fi=%+v, err = %v", &fi, &err)
|
||||||
n, err := d.vfs.Stat(path)
|
n, err := d.vfs.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -280,7 +259,7 @@ func (d *Driver) Stat(path string) (fi ftp.FileInfo, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeDir move current folder
|
// ChangeDir move current folder
|
||||||
func (d *Driver) ChangeDir(path string) (err error) {
|
func (d *driver) ChangeDir(sctx *ftp.Context, path string) (err error) {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
defer log.Trace(path, "")("err = %v", &err)
|
defer log.Trace(path, "")("err = %v", &err)
|
||||||
|
@ -295,7 +274,7 @@ func (d *Driver) ChangeDir(path string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListDir list content of a folder
|
// ListDir list content of a folder
|
||||||
func (d *Driver) ListDir(path string, callback func(ftp.FileInfo) error) (err error) {
|
func (d *driver) ListDir(sctx *ftp.Context, path string, callback func(iofs.FileInfo) error) (err error) {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
defer log.Trace(path, "")("err = %v", &err)
|
defer log.Trace(path, "")("err = %v", &err)
|
||||||
|
@ -318,7 +297,7 @@ func (d *Driver) ListDir(path string, callback func(ftp.FileInfo) error) (err er
|
||||||
// Account the transfer
|
// Account the transfer
|
||||||
tr := accounting.GlobalStats().NewTransferRemoteSize(path, node.Size())
|
tr := accounting.GlobalStats().NewTransferRemoteSize(path, node.Size())
|
||||||
defer func() {
|
defer func() {
|
||||||
tr.Done(d.s.ctx, err)
|
tr.Done(d.ctx, err)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, file := range dirEntries {
|
for _, file := range dirEntries {
|
||||||
|
@ -331,7 +310,7 @@ func (d *Driver) ListDir(path string, callback func(ftp.FileInfo) error) (err er
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDir delete a folder and his content
|
// DeleteDir delete a folder and his content
|
||||||
func (d *Driver) DeleteDir(path string) (err error) {
|
func (d *driver) DeleteDir(sctx *ftp.Context, path string) (err error) {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
defer log.Trace(path, "")("err = %v", &err)
|
defer log.Trace(path, "")("err = %v", &err)
|
||||||
|
@ -350,7 +329,7 @@ func (d *Driver) DeleteDir(path string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteFile delete a file
|
// DeleteFile delete a file
|
||||||
func (d *Driver) DeleteFile(path string) (err error) {
|
func (d *driver) DeleteFile(sctx *ftp.Context, path string) (err error) {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
defer log.Trace(path, "")("err = %v", &err)
|
defer log.Trace(path, "")("err = %v", &err)
|
||||||
|
@ -369,7 +348,7 @@ func (d *Driver) DeleteFile(path string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename rename a file or folder
|
// Rename rename a file or folder
|
||||||
func (d *Driver) Rename(oldName, newName string) (err error) {
|
func (d *driver) Rename(sctx *ftp.Context, oldName, newName string) (err error) {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
defer log.Trace(oldName, "newName=%q", newName)("err = %v", &err)
|
defer log.Trace(oldName, "newName=%q", newName)("err = %v", &err)
|
||||||
|
@ -377,7 +356,7 @@ func (d *Driver) Rename(oldName, newName string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeDir create a folder
|
// MakeDir create a folder
|
||||||
func (d *Driver) MakeDir(path string) (err error) {
|
func (d *driver) MakeDir(sctx *ftp.Context, path string) (err error) {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
defer log.Trace(path, "")("err = %v", &err)
|
defer log.Trace(path, "")("err = %v", &err)
|
||||||
|
@ -390,7 +369,7 @@ func (d *Driver) MakeDir(path string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFile download a file
|
// GetFile download a file
|
||||||
func (d *Driver) GetFile(path string, offset int64) (size int64, fr io.ReadCloser, err error) {
|
func (d *driver) GetFile(sctx *ftp.Context, path string, offset int64) (size int64, fr io.ReadCloser, err error) {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
defer log.Trace(path, "offset=%v", offset)("err = %v", &err)
|
defer log.Trace(path, "offset=%v", offset)("err = %v", &err)
|
||||||
|
@ -416,22 +395,23 @@ func (d *Driver) GetFile(path string, offset int64) (size int64, fr io.ReadClose
|
||||||
|
|
||||||
// Account the transfer
|
// Account the transfer
|
||||||
tr := accounting.GlobalStats().NewTransferRemoteSize(path, node.Size())
|
tr := accounting.GlobalStats().NewTransferRemoteSize(path, node.Size())
|
||||||
defer tr.Done(d.s.ctx, nil)
|
defer tr.Done(d.ctx, nil)
|
||||||
|
|
||||||
return node.Size(), handle, nil
|
return node.Size(), handle, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PutFile upload a file
|
// PutFile upload a file
|
||||||
func (d *Driver) PutFile(path string, data io.Reader, appendData bool) (n int64, err error) {
|
func (d *driver) PutFile(sctx *ftp.Context, path string, data io.Reader, offset int64) (n int64, err error) {
|
||||||
d.lock.Lock()
|
d.lock.Lock()
|
||||||
defer d.lock.Unlock()
|
defer d.lock.Unlock()
|
||||||
defer log.Trace(path, "append=%v", appendData)("err = %v", &err)
|
defer log.Trace(path, "offset=%d", offset)("err = %v", &err)
|
||||||
|
|
||||||
var isExist bool
|
var isExist bool
|
||||||
node, err := d.vfs.Stat(path)
|
fi, err := d.vfs.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
isExist = true
|
isExist = true
|
||||||
if node.IsDir() {
|
if fi.IsDir() {
|
||||||
return 0, errors.New("a dir has the same name")
|
return 0, errors.New("can't create file - directory exists")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
|
@ -441,41 +421,51 @@ func (d *Driver) PutFile(path string, data io.Reader, appendData bool) (n int64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if appendData && !isExist {
|
if offset > -1 && !isExist {
|
||||||
appendData = false
|
offset = -1
|
||||||
}
|
}
|
||||||
|
|
||||||
if !appendData {
|
var f vfs.Handle
|
||||||
|
|
||||||
|
if offset == -1 {
|
||||||
if isExist {
|
if isExist {
|
||||||
err = node.Remove()
|
err = d.vfs.Remove(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
f, err := d.vfs.OpenFile(path, os.O_RDWR|os.O_CREATE, 0660)
|
f, err = d.vfs.Create(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer closeIO(path, f)
|
defer fs.CheckClose(f, &err)
|
||||||
bytes, err := io.Copy(f, data)
|
n, err = io.Copy(f, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
return bytes, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
of, err := d.vfs.OpenFile(path, os.O_APPEND|os.O_RDWR, 0660)
|
f, err = d.vfs.OpenFile(path, os.O_APPEND|os.O_RDWR, 0660)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer closeIO(path, of)
|
defer fs.CheckClose(f, &err)
|
||||||
|
|
||||||
_, err = of.Seek(0, io.SeekEnd)
|
info, err := f.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if offset > info.Size() {
|
||||||
|
return 0, fmt.Errorf("offset %d is beyond file size %d", offset, info.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Seek(offset, io.SeekStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes, err := io.Copy(of, data)
|
bytes, err := io.Copy(f, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -521,10 +511,3 @@ func (f *FileInfo) Group() string {
|
||||||
func (f *FileInfo) ModTime() time.Time {
|
func (f *FileInfo) ModTime() time.Time {
|
||||||
return f.FileInfo.ModTime().UTC()
|
return f.FileInfo.ModTime().UTC()
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeIO(path string, c io.Closer) {
|
|
||||||
err := c.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Trace(path, "")("err = %v", &err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
"github.com/rclone/rclone/fs/config/configmap"
|
"github.com/rclone/rclone/fs/config/configmap"
|
||||||
"github.com/rclone/rclone/fs/config/obscure"
|
"github.com/rclone/rclone/fs/config/obscure"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
ftp "goftp.io/server/core"
|
ftp "goftp.io/server/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -64,7 +64,7 @@ require (
|
||||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a
|
||||||
github.com/yunify/qingstor-sdk-go/v3 v3.2.0
|
github.com/yunify/qingstor-sdk-go/v3 v3.2.0
|
||||||
go.etcd.io/bbolt v1.3.7
|
go.etcd.io/bbolt v1.3.7
|
||||||
goftp.io/server v1.0.0-rc1
|
goftp.io/server/v2 v2.0.1
|
||||||
golang.org/x/crypto v0.11.0
|
golang.org/x/crypto v0.11.0
|
||||||
golang.org/x/net v0.12.0
|
golang.org/x/net v0.12.0
|
||||||
golang.org/x/oauth2 v0.10.0
|
golang.org/x/oauth2 v0.10.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -542,8 +542,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
goftp.io/server v1.0.0-rc1 h1:gdu6Dq8dK4Qllrhc7oEclGUH+6gBYRQtO43ELnI7fjc=
|
goftp.io/server/v2 v2.0.1 h1:H+9UbCX2N206ePDSVNCjBftOKOgil6kQ5RAQNx5hJwE=
|
||||||
goftp.io/server v1.0.0-rc1/go.mod h1:hFZeR656ErRt3ojMKt7H10vQ5nuWV1e0YeUTeorlR6k=
|
goftp.io/server/v2 v2.0.1/go.mod h1:7+H/EIq7tXdfo1Muu5p+l3oQ6rYkDZ8lY7IM5d5kVdQ=
|
||||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
|
Loading…
Reference in a new issue