mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00:00
parent
cc2f04b0e4
commit
93ce96d011
38 changed files with 3075 additions and 160 deletions
3
.github/workflows/development.yml
vendored
3
.github/workflows/development.yml
vendored
|
@ -61,6 +61,7 @@ jobs:
|
|||
go test -v -timeout 1m ./common -covermode=atomic
|
||||
go test -v -timeout 5m ./httpd -covermode=atomic
|
||||
go test -v -timeout 5m ./sftpd -covermode=atomic
|
||||
go test -v -timeout 5m ./ftpd -covermode=atomic
|
||||
env:
|
||||
SFTPGO_DATA_PROVIDER__DRIVER: bolt
|
||||
SFTPGO_DATA_PROVIDER__NAME: 'sftpgo_bolt.db'
|
||||
|
@ -177,4 +178,4 @@ jobs:
|
|||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@v1
|
||||
with:
|
||||
version: v1.27
|
||||
version: v1.29
|
||||
|
|
|
@ -33,9 +33,10 @@ Fully featured and highly configurable SFTP server, written in Go
|
|||
- Atomic uploads are configurable.
|
||||
- Support for Git repositories over SSH.
|
||||
- SCP and rsync are supported.
|
||||
- Support for serving local filesystem, S3 Compatible Object Storage and Google Cloud Storage over SFTP/SCP.
|
||||
- FTP/S is supported.
|
||||
- Support for serving local filesystem, S3 Compatible Object Storage and Google Cloud Storage over SFTP/SCP/FTP.
|
||||
- [Prometheus metrics](./docs/metrics.md) are exposed.
|
||||
- Support for HAProxy PROXY protocol: you can proxy and/or load balance the SFTP/SCP service without losing the information about the client's address.
|
||||
- Support for HAProxy PROXY protocol: you can proxy and/or load balance the SFTP/SCP/FTP service without losing the information about the client's address.
|
||||
- [REST API](./docs/rest-api.md) for users and folders management, backup, restore and real time reports of the active connections with possibility of forcibly closing a connection.
|
||||
- [Web based administration interface](./docs/web-admin.md) to easily manage users, folders and connections.
|
||||
- Easy [migration](./examples/rest-api-cli#convert-users-from-other-stores) from Linux system user accounts.
|
||||
|
@ -51,7 +52,8 @@ SFTPGo is developed and tested on Linux. After each commit, the code is automati
|
|||
## Requirements
|
||||
|
||||
- Go 1.13 or higher as build only dependency.
|
||||
- A suitable SQL server or key/value store to use as data provider: PostgreSQL 9.4+ or MySQL 5.6+ or SQLite 3.x or bbolt 1.3.x
|
||||
- A suitable SQL server to use as data provider: PostgreSQL 9.4+ or MySQL 5.6+ or SQLite 3.x.
|
||||
- The SQL server is optional: you can choose to use an embedded bolt database as key/value store or an in memory data provider.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/drakkan/sftpgo/common"
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/service"
|
||||
"github.com/drakkan/sftpgo/sftpd"
|
||||
|
@ -49,6 +50,9 @@ var (
|
|||
portableGCSAutoCredentials int
|
||||
portableGCSStorageClass string
|
||||
portableGCSKeyPrefix string
|
||||
portableFTPDPort int
|
||||
portableFTPSCert string
|
||||
portableFTPSKey string
|
||||
portableCmd = &cobra.Command{
|
||||
Use: "portable",
|
||||
Short: "Serve a single directory",
|
||||
|
@ -88,6 +92,14 @@ Please take a look at the usage below to customize the serving parameters`,
|
|||
portableGCSCredentials = base64.StdEncoding.EncodeToString(creds)
|
||||
portableGCSAutoCredentials = 0
|
||||
}
|
||||
if portableFTPDPort >= 0 && len(portableFTPSCert) > 0 && len(portableFTPSKey) > 0 {
|
||||
_, err := common.NewCertManager(portableFTPSCert, portableFTPSKey, "FTP portable")
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to load FTPS key pair, cert file %#v key file %#v error: %v\n",
|
||||
portableFTPSCert, portableFTPSKey, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
service := service.Service{
|
||||
ConfigDir: filepath.Clean(defaultConfigDir),
|
||||
ConfigFile: defaultConfigName,
|
||||
|
@ -133,10 +145,12 @@ Please take a look at the usage below to customize the serving parameters`,
|
|||
},
|
||||
},
|
||||
}
|
||||
if err := service.StartPortableMode(portableSFTPDPort, portableSSHCommands, portableAdvertiseService,
|
||||
portableAdvertiseCredentials); err == nil {
|
||||
if err := service.StartPortableMode(portableSFTPDPort, portableFTPDPort, portableSSHCommands, portableAdvertiseService,
|
||||
portableAdvertiseCredentials, portableFTPSCert, portableFTPSKey); err == nil {
|
||||
service.Wait()
|
||||
os.Exit(0)
|
||||
if service.Error == nil {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
os.Exit(1)
|
||||
},
|
||||
|
@ -150,7 +164,9 @@ func init() {
|
|||
This can be an absolute path or a path
|
||||
relative to the current directory
|
||||
`)
|
||||
portableCmd.Flags().IntVarP(&portableSFTPDPort, "sftpd-port", "s", 0, "0 means a random unprivileged port")
|
||||
portableCmd.Flags().IntVarP(&portableSFTPDPort, "sftpd-port", "s", 0, "0 means a random unprivileged port")
|
||||
portableCmd.Flags().IntVar(&portableFTPDPort, "ftpd-port", -1, `0 means a random unprivileged port,
|
||||
< 0 disabled`)
|
||||
portableCmd.Flags().StringSliceVarP(&portableSSHCommands, "ssh-commands", "c", sftpd.GetDefaultSSHCommands(),
|
||||
`SSH commands to enable.
|
||||
"*" means any supported SSH command
|
||||
|
@ -177,13 +193,13 @@ insensitive. The format is
|
|||
/dir::ext1,ext2.
|
||||
For example: "/somedir::.jpg,.png"`)
|
||||
portableCmd.Flags().BoolVarP(&portableAdvertiseService, "advertise-service", "S", false,
|
||||
`Advertise SFTP service using multicast
|
||||
DNS`)
|
||||
`Advertise SFTP/FTP service using
|
||||
multicast DNS`)
|
||||
portableCmd.Flags().BoolVarP(&portableAdvertiseCredentials, "advertise-credentials", "C", false,
|
||||
`If the SFTP service is advertised via
|
||||
multicast DNS, this flag allows to put
|
||||
username/password inside the advertised
|
||||
TXT record`)
|
||||
`If the SFTP/FTP service is
|
||||
advertised via multicast DNS, this
|
||||
flag allows to put username/password
|
||||
inside the advertised TXT record`)
|
||||
portableCmd.Flags().IntVarP(&portableFsProvider, "fs-provider", "f", 0, `0 means local filesystem,
|
||||
1 Amazon S3 compatible,
|
||||
2 Google Cloud Storage`)
|
||||
|
@ -210,6 +226,8 @@ file`)
|
|||
portableCmd.Flags().IntVar(&portableGCSAutoCredentials, "gcs-automatic-credentials", 1, `0 means explicit credentials using
|
||||
a JSON credentials file, 1 automatic
|
||||
`)
|
||||
portableCmd.Flags().StringVar(&portableFTPSCert, "ftpd-cert", "", "Path to the certificate file for FTPS")
|
||||
portableCmd.Flags().StringVar(&portableFTPSKey, "ftpd-key", "", "Path to the key file for FTPS")
|
||||
rootCmd.AddCommand(portableCmd)
|
||||
}
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@ func TestPreDeleteAction(t *testing.T) {
|
|||
c := NewBaseConnection("id", ProtocolSFTP, user, fs)
|
||||
|
||||
testfile := filepath.Join(user.HomeDir, "testfile")
|
||||
err = ioutil.WriteFile(testfile, []byte("test"), 0666)
|
||||
err = ioutil.WriteFile(testfile, []byte("test"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
info, err := os.Stat(testfile)
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
|
||||
// constants
|
||||
const (
|
||||
logSender = "common"
|
||||
uploadLogSender = "Upload"
|
||||
downloadLogSender = "Download"
|
||||
renameLogSender = "Rename"
|
||||
|
@ -35,7 +36,7 @@ const (
|
|||
operationRename = "rename"
|
||||
operationSSHCmd = "ssh_cmd"
|
||||
chtimesFormat = "2006-01-02T15:04:05" // YYYY-MM-DDTHH:MM:SS
|
||||
idleTimeoutCheckInterval = 5 * time.Minute
|
||||
idleTimeoutCheckInterval = 3 * time.Minute
|
||||
)
|
||||
|
||||
// Stat flags
|
||||
|
@ -56,6 +57,7 @@ const (
|
|||
ProtocolSFTP = "SFTP"
|
||||
ProtocolSCP = "SCP"
|
||||
ProtocolSSH = "SSH"
|
||||
ProtocolFTP = "FTP"
|
||||
)
|
||||
|
||||
// Upload modes
|
||||
|
@ -84,12 +86,13 @@ var (
|
|||
QuotaScans ActiveScans
|
||||
idleTimeoutTicker *time.Ticker
|
||||
idleTimeoutTickerDone chan bool
|
||||
supportedProcols = []string{ProtocolSFTP, ProtocolSCP, ProtocolSSH}
|
||||
supportedProcols = []string{ProtocolSFTP, ProtocolSCP, ProtocolSSH, ProtocolFTP}
|
||||
)
|
||||
|
||||
// Initialize sets the common configuration
|
||||
func Initialize(c Configuration) {
|
||||
Config = c
|
||||
Config.idleLoginTimeout = 2 * time.Minute
|
||||
Config.idleTimeoutAsDuration = time.Duration(Config.IdleTimeout) * time.Minute
|
||||
if Config.IdleTimeout > 0 {
|
||||
startIdleTimeoutTicker(idleTimeoutCheckInterval)
|
||||
|
@ -220,6 +223,7 @@ type Configuration struct {
|
|||
// connection will be rejected.
|
||||
ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
|
||||
idleTimeoutAsDuration time.Duration
|
||||
idleLoginTimeout time.Duration
|
||||
}
|
||||
|
||||
// IsAtomicUploadEnabled returns true if atomic upload is enabled
|
||||
|
@ -291,15 +295,35 @@ func (conns *ActiveConnections) Add(c ActiveConnection) {
|
|||
logger.Debug(c.GetProtocol(), c.GetID(), "connection added, num open connections: %v", len(conns.connections))
|
||||
}
|
||||
|
||||
// Remove removes a connection from the active ones
|
||||
func (conns *ActiveConnections) Remove(c ActiveConnection) {
|
||||
// Swap replaces an existing connection with the given one.
|
||||
// This method is useful if you have to change some connection details
|
||||
// for example for FTP is used to update the connection once the user
|
||||
// authenticates
|
||||
func (conns *ActiveConnections) Swap(c ActiveConnection) error {
|
||||
conns.Lock()
|
||||
defer conns.Unlock()
|
||||
|
||||
for idx, conn := range conns.connections {
|
||||
if conn.GetID() == c.GetID() {
|
||||
conn = nil
|
||||
conns.connections[idx] = c
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return errors.New("connection to swap not found")
|
||||
}
|
||||
|
||||
// Remove removes a connection from the active ones
|
||||
func (conns *ActiveConnections) Remove(connectionID string) {
|
||||
conns.Lock()
|
||||
defer conns.Unlock()
|
||||
|
||||
var c ActiveConnection
|
||||
indexToRemove := -1
|
||||
for i, v := range conns.connections {
|
||||
if v.GetID() == c.GetID() {
|
||||
for i, conn := range conns.connections {
|
||||
if conn.GetID() == connectionID {
|
||||
indexToRemove = i
|
||||
c = conn
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -307,19 +331,20 @@ func (conns *ActiveConnections) Remove(c ActiveConnection) {
|
|||
conns.connections[indexToRemove] = conns.connections[len(conns.connections)-1]
|
||||
conns.connections[len(conns.connections)-1] = nil
|
||||
conns.connections = conns.connections[:len(conns.connections)-1]
|
||||
metrics.UpdateActiveConnectionsSize(len(conns.connections))
|
||||
logger.Debug(c.GetProtocol(), c.GetID(), "connection removed, num open connections: %v",
|
||||
len(conns.connections))
|
||||
// we have finished to send data here and most of the time the underlying network connection
|
||||
// is already closed. Sometime a client can still be reading the last sended data, so we set
|
||||
// a deadline instead of directly closing the network connection.
|
||||
// Setting a deadline on an already closed connection has no effect.
|
||||
// We only need to ensure that a connection will not remain indefinitely open and so the
|
||||
// underlying file descriptor is not released.
|
||||
// This should protect us against buggy clients and edge cases.
|
||||
c.SetConnDeadline()
|
||||
} else {
|
||||
logger.Warn(c.GetProtocol(), c.GetID(), "connection to remove not found!")
|
||||
logger.Warn(logSender, "", "connection to remove with id %#v not found!", connectionID)
|
||||
}
|
||||
// we have finished to send data here and most of the time the underlying network connection
|
||||
// is already closed. Sometime a client can still be reading the last sended data, so we set
|
||||
// a deadline instead of directly closing the network connection.
|
||||
// Setting a deadline on an already closed connection has no effect.
|
||||
// We only need to ensure that a connection will not remain indefinitely open and so the
|
||||
// underlying file descriptor is not released.
|
||||
// This should protect us against buggy clients and edge cases.
|
||||
c.SetConnDeadline()
|
||||
}
|
||||
|
||||
// Close closes an active connection.
|
||||
|
@ -330,10 +355,10 @@ func (conns *ActiveConnections) Close(connectionID string) bool {
|
|||
|
||||
for _, c := range conns.connections {
|
||||
if c.GetID() == connectionID {
|
||||
defer func() {
|
||||
err := c.Disconnect()
|
||||
logger.Debug(c.GetProtocol(), c.GetID(), "close connection requested, close err: %v", err)
|
||||
}()
|
||||
defer func(conn ActiveConnection) {
|
||||
err := conn.Disconnect()
|
||||
logger.Debug(conn.GetProtocol(), conn.GetID(), "close connection requested, close err: %v", err)
|
||||
}(c)
|
||||
result = true
|
||||
break
|
||||
}
|
||||
|
@ -348,11 +373,19 @@ func (conns *ActiveConnections) checkIdleConnections() {
|
|||
|
||||
for _, c := range conns.connections {
|
||||
idleTime := time.Since(c.GetLastActivity())
|
||||
if idleTime > Config.idleTimeoutAsDuration {
|
||||
defer func() {
|
||||
err := c.Disconnect()
|
||||
logger.Debug(c.GetProtocol(), c.GetID(), "close idle connection, idle time: %v, close err: %v", idleTime, err)
|
||||
}()
|
||||
isUnauthenticatedFTPUser := (c.GetProtocol() == ProtocolFTP && len(c.GetUsername()) == 0)
|
||||
|
||||
if idleTime > Config.idleTimeoutAsDuration || (isUnauthenticatedFTPUser && idleTime > Config.idleLoginTimeout) {
|
||||
defer func(conn ActiveConnection, isFTPNoAuth bool) {
|
||||
err := conn.Disconnect()
|
||||
logger.Debug(conn.GetProtocol(), conn.GetID(), "close idle connection, idle time: %v, username: %#v close err: %v",
|
||||
idleTime, conn.GetUsername(), err)
|
||||
if isFTPNoAuth {
|
||||
logger.ConnectionFailedLog("", utils.GetIPFromRemoteAddress(c.GetRemoteAddress()),
|
||||
"no_auth_tryed", "client idle")
|
||||
metrics.AddNoAuthTryed()
|
||||
}
|
||||
}(c, isUnauthenticatedFTPUser)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
logSender = "common_test"
|
||||
logSenderTest = "common_test"
|
||||
httpAddr = "127.0.0.1:9999"
|
||||
httpProxyAddr = "127.0.0.1:7777"
|
||||
configDir = ".."
|
||||
|
@ -37,8 +37,18 @@ type fakeConnection struct {
|
|||
sshCommand string
|
||||
}
|
||||
|
||||
func (c *fakeConnection) AddUser(user dataprovider.User) error {
|
||||
fs, err := user.GetFilesystem(c.GetID())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.BaseConnection.User = user
|
||||
c.BaseConnection.Fs = fs
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeConnection) Disconnect() error {
|
||||
Connections.Remove(c)
|
||||
Connections.Remove(c.GetID())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -166,16 +176,30 @@ func TestIdleConnections(t *testing.T) {
|
|||
user := dataprovider.User{
|
||||
Username: username,
|
||||
}
|
||||
c := NewBaseConnection("id", ProtocolSFTP, user, nil)
|
||||
c := NewBaseConnection("id1", ProtocolSFTP, user, nil)
|
||||
c.lastActivity = time.Now().Add(-24 * time.Hour).UnixNano()
|
||||
fakeConn := &fakeConnection{
|
||||
BaseConnection: c,
|
||||
}
|
||||
Connections.Add(fakeConn)
|
||||
assert.Equal(t, Connections.GetActiveSessions(username), 1)
|
||||
c = NewBaseConnection("id2", ProtocolFTP, dataprovider.User{}, nil)
|
||||
c.lastActivity = time.Now().UnixNano()
|
||||
fakeConn = &fakeConnection{
|
||||
BaseConnection: c,
|
||||
}
|
||||
Connections.Add(fakeConn)
|
||||
assert.Equal(t, Connections.GetActiveSessions(username), 1)
|
||||
assert.Len(t, Connections.GetStats(), 2)
|
||||
|
||||
startIdleTimeoutTicker(100 * time.Millisecond)
|
||||
assert.Eventually(t, func() bool { return Connections.GetActiveSessions(username) == 0 }, 1*time.Second, 200*time.Millisecond)
|
||||
stopIdleTimeoutTicker()
|
||||
assert.Len(t, Connections.GetStats(), 1)
|
||||
c.lastActivity = time.Now().Add(-24 * time.Hour).UnixNano()
|
||||
startIdleTimeoutTicker(100 * time.Millisecond)
|
||||
assert.Eventually(t, func() bool { return len(Connections.GetStats()) == 0 }, 1*time.Second, 200*time.Millisecond)
|
||||
stopIdleTimeoutTicker()
|
||||
|
||||
Config = configCopy
|
||||
}
|
||||
|
@ -192,7 +216,34 @@ func TestCloseConnection(t *testing.T) {
|
|||
assert.Eventually(t, func() bool { return len(Connections.GetStats()) == 0 }, 300*time.Millisecond, 50*time.Millisecond)
|
||||
res = Connections.Close(fakeConn.GetID())
|
||||
assert.False(t, res)
|
||||
Connections.Remove(fakeConn)
|
||||
Connections.Remove(fakeConn.GetID())
|
||||
}
|
||||
|
||||
func TestSwapConnection(t *testing.T) {
|
||||
c := NewBaseConnection("id", ProtocolFTP, dataprovider.User{}, nil)
|
||||
fakeConn := &fakeConnection{
|
||||
BaseConnection: c,
|
||||
}
|
||||
Connections.Add(fakeConn)
|
||||
if assert.Len(t, Connections.GetStats(), 1) {
|
||||
assert.Equal(t, "", Connections.GetStats()[0].Username)
|
||||
}
|
||||
c = NewBaseConnection("id", ProtocolFTP, dataprovider.User{
|
||||
Username: userTestUsername,
|
||||
}, nil)
|
||||
fakeConn = &fakeConnection{
|
||||
BaseConnection: c,
|
||||
}
|
||||
err := Connections.Swap(fakeConn)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, Connections.GetStats(), 1) {
|
||||
assert.Equal(t, userTestUsername, Connections.GetStats()[0].Username)
|
||||
}
|
||||
res := Connections.Close(fakeConn.GetID())
|
||||
assert.True(t, res)
|
||||
assert.Eventually(t, func() bool { return len(Connections.GetStats()) == 0 }, 300*time.Millisecond, 50*time.Millisecond)
|
||||
err = Connections.Swap(fakeConn)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAtomicUpload(t *testing.T) {
|
||||
|
@ -255,8 +306,8 @@ func TestConnectionStatus(t *testing.T) {
|
|||
err = t2.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
Connections.Remove(fakeConn1)
|
||||
Connections.Remove(fakeConn2)
|
||||
Connections.Remove(fakeConn1.GetID())
|
||||
Connections.Remove(fakeConn2.GetID())
|
||||
stats = Connections.GetStats()
|
||||
assert.Len(t, stats, 0)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ type CertManager struct {
|
|||
func (m *CertManager) LoadCertificate(logSender string) error {
|
||||
newCert, err := tls.LoadX509KeyPair(m.certPath, m.keyPath)
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "unable to load X509 ket pair, cert file %#v key file %#v error: %v",
|
||||
logger.Warn(logSender, "", "unable to load X509 key pair, cert file %#v key file %#v error: %v",
|
||||
m.certPath, m.keyPath, err)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func TestLoadCertificate(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(keyPath, []byte(httpsKey), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
certManager, err := NewCertManager(certPath, keyPath, logSender)
|
||||
certManager, err := NewCertManager(certPath, keyPath, logSenderTest)
|
||||
assert.NoError(t, err)
|
||||
certFunc := certManager.GetCertificateFunc()
|
||||
if assert.NotNil(t, certFunc) {
|
||||
|
@ -63,7 +63,7 @@ func TestLoadCertificate(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoadInvalidCert(t *testing.T) {
|
||||
certManager, err := NewCertManager("test.crt", "test.key", logSender)
|
||||
certManager, err := NewCertManager("test.crt", "test.key", logSenderTest)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, certManager)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/drakkan/sftpgo/common"
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/ftpd"
|
||||
"github.com/drakkan/sftpgo/httpclient"
|
||||
"github.com/drakkan/sftpgo/httpd"
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
|
@ -28,13 +29,15 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
globalConf globalConfig
|
||||
defaultBanner = fmt.Sprintf("SFTPGo_%v", version.Get().Version)
|
||||
globalConf globalConfig
|
||||
defaultSFTPDBanner = fmt.Sprintf("SFTPGo_%v", version.Get().Version)
|
||||
defaultFTPDBanner = fmt.Sprintf("SFTPGo %v ready", version.Get().Version)
|
||||
)
|
||||
|
||||
type globalConfig struct {
|
||||
Common common.Configuration `json:"common" mapstructure:"common"`
|
||||
SFTPD sftpd.Configuration `json:"sftpd" mapstructure:"sftpd"`
|
||||
FTPD ftpd.Configuration `json:"ftpd" mapstructure:"ftpd"`
|
||||
ProviderConf dataprovider.Config `json:"data_provider" mapstructure:"data_provider"`
|
||||
HTTPDConfig httpd.Conf `json:"httpd" mapstructure:"httpd"`
|
||||
HTTPConfig httpclient.Config `json:"http" mapstructure:"http"`
|
||||
|
@ -55,7 +58,7 @@ func init() {
|
|||
ProxyAllowed: []string{},
|
||||
},
|
||||
SFTPD: sftpd.Configuration{
|
||||
Banner: defaultBanner,
|
||||
Banner: defaultSFTPDBanner,
|
||||
BindPort: 2022,
|
||||
BindAddress: "",
|
||||
MaxAuthTries: 0,
|
||||
|
@ -68,6 +71,20 @@ func init() {
|
|||
EnabledSSHCommands: sftpd.GetDefaultSSHCommands(),
|
||||
KeyboardInteractiveHook: "",
|
||||
},
|
||||
FTPD: ftpd.Configuration{
|
||||
BindPort: 0,
|
||||
BindAddress: "",
|
||||
Banner: defaultFTPDBanner,
|
||||
BannerFile: "",
|
||||
ActiveTransfersPortNon20: false,
|
||||
ForcePassiveIP: "",
|
||||
PassivePortRange: ftpd.PortRange{
|
||||
Start: 50000,
|
||||
End: 50100,
|
||||
},
|
||||
CertificateFile: "",
|
||||
CertificateKeyFile: "",
|
||||
},
|
||||
ProviderConf: dataprovider.Config{
|
||||
Driver: "sqlite",
|
||||
Name: "sftpgo.db",
|
||||
|
@ -136,6 +153,16 @@ func SetSFTPDConfig(config sftpd.Configuration) {
|
|||
globalConf.SFTPD = config
|
||||
}
|
||||
|
||||
// GetFTPDConfig returns the configuration for the FTP server
|
||||
func GetFTPDConfig() ftpd.Configuration {
|
||||
return globalConf.FTPD
|
||||
}
|
||||
|
||||
// SetFTPDConfig sets the configuration for the FTP server
|
||||
func SetFTPDConfig(config ftpd.Configuration) {
|
||||
globalConf.FTPD = config
|
||||
}
|
||||
|
||||
// GetHTTPDConfig returns the configuration for the HTTP server
|
||||
func GetHTTPDConfig() httpd.Conf {
|
||||
return globalConf.HTTPDConfig
|
||||
|
@ -193,7 +220,10 @@ func LoadConfig(configDir, configName string) error {
|
|||
}
|
||||
checkCommonParamsCompatibility()
|
||||
if strings.TrimSpace(globalConf.SFTPD.Banner) == "" {
|
||||
globalConf.SFTPD.Banner = defaultBanner
|
||||
globalConf.SFTPD.Banner = defaultSFTPDBanner
|
||||
}
|
||||
if strings.TrimSpace(globalConf.FTPD.Banner) == "" {
|
||||
globalConf.FTPD.Banner = defaultFTPDBanner
|
||||
}
|
||||
if len(globalConf.ProviderConf.UsersBaseDir) > 0 && !utils.IsFileInputValid(globalConf.ProviderConf.UsersBaseDir) {
|
||||
err = fmt.Errorf("invalid users base dir %#v will be ignored", globalConf.ProviderConf.UsersBaseDir)
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/drakkan/sftpgo/common"
|
||||
"github.com/drakkan/sftpgo/config"
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/ftpd"
|
||||
"github.com/drakkan/sftpgo/httpclient"
|
||||
"github.com/drakkan/sftpgo/httpd"
|
||||
"github.com/drakkan/sftpgo/sftpd"
|
||||
|
@ -36,11 +37,11 @@ func TestLoadConfigTest(t *testing.T) {
|
|||
configFilePath := filepath.Join(configDir, confName)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NotNil(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, []byte("{invalid json}"), 0666)
|
||||
err = ioutil.WriteFile(configFilePath, []byte("{invalid json}"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NotNil(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, []byte("{\"sftpd\": {\"bind_port\": \"a\"}}"), 0666)
|
||||
err = ioutil.WriteFile(configFilePath, []byte("{\"sftpd\": {\"bind_port\": \"a\"}}"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NotNil(t, err)
|
||||
|
@ -59,7 +60,7 @@ func TestEmptyBanner(t *testing.T) {
|
|||
c := make(map[string]sftpd.Configuration)
|
||||
c["sftpd"] = sftpdConf
|
||||
jsonConf, _ := json.Marshal(c)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NoError(t, err)
|
||||
|
@ -67,6 +68,20 @@ func TestEmptyBanner(t *testing.T) {
|
|||
assert.NotEmpty(t, strings.TrimSpace(sftpdConf.Banner))
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ftpdConf := config.GetFTPDConfig()
|
||||
ftpdConf.Banner = " "
|
||||
c1 := make(map[string]ftpd.Configuration)
|
||||
c1["ftpd"] = ftpdConf
|
||||
jsonConf, _ = json.Marshal(c1)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NoError(t, err)
|
||||
ftpdConf = config.GetFTPDConfig()
|
||||
assert.NotEmpty(t, strings.TrimSpace(ftpdConf.Banner))
|
||||
err = os.Remove(configFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInvalidUploadMode(t *testing.T) {
|
||||
|
@ -81,7 +96,7 @@ func TestInvalidUploadMode(t *testing.T) {
|
|||
c["common"] = commonConf
|
||||
jsonConf, err := json.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NotNil(t, err)
|
||||
|
@ -101,7 +116,7 @@ func TestInvalidExternalAuthScope(t *testing.T) {
|
|||
c["data_provider"] = providerConf
|
||||
jsonConf, err := json.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NotNil(t, err)
|
||||
|
@ -121,7 +136,7 @@ func TestInvalidCredentialsPath(t *testing.T) {
|
|||
c["data_provider"] = providerConf
|
||||
jsonConf, err := json.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NotNil(t, err)
|
||||
|
@ -141,7 +156,7 @@ func TestInvalidProxyProtocol(t *testing.T) {
|
|||
c["common"] = commonConf
|
||||
jsonConf, err := json.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NotNil(t, err)
|
||||
|
@ -161,7 +176,7 @@ func TestInvalidUsersBaseDir(t *testing.T) {
|
|||
c["data_provider"] = providerConf
|
||||
jsonConf, err := json.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NotNil(t, err)
|
||||
|
@ -187,7 +202,7 @@ func TestCommonParamsCompatibility(t *testing.T) {
|
|||
c["sftpd"] = sftpdConf
|
||||
jsonConf, err := json.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NoError(t, err)
|
||||
|
@ -223,7 +238,7 @@ func TestHostKeyCompatibility(t *testing.T) {
|
|||
c["sftpd"] = sftpdConf
|
||||
jsonConf, err := json.Marshal(c)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||
err = ioutil.WriteFile(configFilePath, jsonConf, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = config.LoadConfig(configDir, tempConfigName)
|
||||
assert.NoError(t, err)
|
||||
|
@ -252,4 +267,10 @@ func TestSetGetConfig(t *testing.T) {
|
|||
commonConf.IdleTimeout = 10
|
||||
config.SetCommonConfig(commonConf)
|
||||
assert.Equal(t, commonConf.IdleTimeout, config.GetCommonConfig().IdleTimeout)
|
||||
ftpdConf := config.GetFTPDConfig()
|
||||
ftpdConf.CertificateFile = "cert"
|
||||
ftpdConf.CertificateKeyFile = "key"
|
||||
config.SetFTPDConfig(ftpdConf)
|
||||
assert.Equal(t, ftpdConf.CertificateFile, config.GetFTPDConfig().CertificateFile)
|
||||
assert.Equal(t, ftpdConf.CertificateKeyFile, config.GetFTPDConfig().CertificateKeyFile)
|
||||
}
|
||||
|
|
|
@ -46,13 +46,14 @@ const (
|
|||
PermChtimes = "chtimes"
|
||||
)
|
||||
|
||||
// Available SSH login methods
|
||||
// Available login methods
|
||||
const (
|
||||
SSHLoginMethodPublicKey = "publickey"
|
||||
SSHLoginMethodPassword = "password"
|
||||
SSHLoginMethodKeyboardInteractive = "keyboard-interactive"
|
||||
SSHLoginMethodKeyAndPassword = "publickey+password"
|
||||
SSHLoginMethodKeyAndKeyboardInt = "publickey+keyboard-interactive"
|
||||
FTPLoginMethodPassword = "ftp-password"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -78,6 +78,16 @@ The configuration file contains the following sections:
|
|||
- `keyboard_interactive_auth_hook`, string. Absolute path to an external program or an HTTP URL to invoke for keyboard interactive authentication. See the "Keyboard Interactive Authentication" paragraph for more details.
|
||||
- `proxy_protocol`, integer. Deprecated, please use the same key in `common` section.
|
||||
- `proxy_allowed`, list of strings. Deprecated, please use the same key in `common` section.
|
||||
- **"ftpd"**, the configuration for the FTP server
|
||||
- `bind_port`, integer. The port used for serving FTP requests. 0 means disabled. Default: 0.
|
||||
- `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "".
|
||||
- `banner`, string. Greeting banner displayed when a connection first comes in. Leave empty to use the default banner. Default `SFTPGo <version> ready`, for example `SFTPGo 1.0.0-dev ready`.
|
||||
- `banner_file`, path to the banner file. The contents of the specified file, if any, are diplayed when someone connects to the server. It can be a path relative to the config dir or an absolute one. If set, it overrides the banner string provided by the `banner` option. Leave empty to disable.
|
||||
- `active_transfers_port_non_20`, boolean. Do not impose the port 20 for active data transfers. Enabling this option allows to run SFTPGo with less privilege. Default: false.
|
||||
- `force_passive_ip`, ip address. External IP address to expose for passive connections. Leavy empty to autodetect. Defaut: "".
|
||||
- `passive_port_range`, struct containing the key `start` and `end`. Port Range for data connections. Random if not specified. Default range is 50000-50100.
|
||||
- `certificate_file`, string. Certificate for FTPS. This can be an absolute path or a path relative to the config dir.
|
||||
- `certificate_key_file`, string. Private key matching the above certificate. This can be an absolute path or a path relative to the config dir. If both the certificate and the private key are provided, the server will accept both plain FTP an explicit FTP over TLS. Certificate and key files can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows.
|
||||
- **"data_provider"**, the configuration for the data provider
|
||||
- `driver`, string. Supported drivers are `sqlite`, `mysql`, `postgresql`, `bolt`, `memory`
|
||||
- `name`, string. Database name. For driver `sqlite` this can be the database name relative to the config dir or the absolute path to the SQLite database. For driver `memory` this is the (optional) path relative to the config dir or the absolute path to the users dump, obtained using the `dumpdata` REST API, to load. This dump will be loaded at startup and can be reloaded on demand sending a `SIGHUP` signal on Unix based systems and a `paramchange` request to the running service on Windows. The `memory` provider will not modify the provided file so quota usage and last login will not be persisted
|
||||
|
|
|
@ -15,12 +15,12 @@ Usage:
|
|||
sftpgo portable [flags]
|
||||
|
||||
Flags:
|
||||
-C, --advertise-credentials If the SFTP service is advertised via
|
||||
multicast DNS, this flag allows to put
|
||||
username/password inside the advertised
|
||||
TXT record
|
||||
-S, --advertise-service Advertise SFTP service using multicast
|
||||
DNS
|
||||
-C, --advertise-credentials If the SFTP/FTP service is
|
||||
advertised via multicast DNS, this
|
||||
flag allows to put username/password
|
||||
inside the advertised TXT record
|
||||
-S, --advertise-service Advertise SFTP/FTP service using
|
||||
multicast DNS
|
||||
--allowed-extensions stringArray Allowed file extensions case
|
||||
insensitive. The format is
|
||||
/dir::ext1,ext2.
|
||||
|
@ -36,6 +36,10 @@ Flags:
|
|||
-f, --fs-provider int 0 means local filesystem,
|
||||
1 Amazon S3 compatible,
|
||||
2 Google Cloud Storage
|
||||
--ftpd-cert string Path to the certificate file for FTPS
|
||||
--ftpd-key string Path to the key file for FTPS
|
||||
--ftpd-port int 0 means a random unprivileged port,
|
||||
< 0 disabled (default -1)
|
||||
--gcs-automatic-credentials int 0 means explicit credentials using
|
||||
a JSON credentials file, 1 automatic
|
||||
(default 1)
|
||||
|
@ -67,7 +71,7 @@ Flags:
|
|||
parallel (default 2)
|
||||
--s3-upload-part-size int The buffer size for multipart uploads
|
||||
(MB) (default 5)
|
||||
-s, --sftpd-port int 0 means a random unprivileged port
|
||||
-s, --sftpd-port int 0 means a random unprivileged port
|
||||
-c, --ssh-commands strings SSH commands to enable.
|
||||
"*" means any supported SSH command
|
||||
including scp
|
||||
|
@ -76,9 +80,9 @@ Flags:
|
|||
value
|
||||
```
|
||||
|
||||
In portable mode, SFTPGo can advertise the SFTP service and, optionally, the credentials via multicast DNS, so there is a standard way to discover the service and to automatically connect to it.
|
||||
In portable mode, SFTPGo can advertise the SFTP/FTP services and, optionally, the credentials via multicast DNS, so there is a standard way to discover the service and to automatically connect to it.
|
||||
|
||||
Here is an example of the advertised service including credentials as seen using `avahi-browse`:
|
||||
Here is an example of the advertised SFTP service including credentials as seen using `avahi-browse`:
|
||||
|
||||
```console
|
||||
= enp0s31f6 IPv4 SFTPGo portable 53705 SFTP File Transfer local
|
||||
|
|
82
ftpd/ftpd.go
Normal file
82
ftpd/ftpd.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
// Package ftpd implements the FTP protocol
|
||||
package ftpd
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
logSender = "ftpd"
|
||||
)
|
||||
|
||||
var (
|
||||
server *Server
|
||||
)
|
||||
|
||||
// PortRange defines a port range
|
||||
type PortRange struct {
|
||||
// Range start
|
||||
Start int `json:"start" mapstructure:"start"`
|
||||
// Range end
|
||||
End int `json:"end" mapstructure:"end"`
|
||||
}
|
||||
|
||||
// Configuration defines the configuration for the ftp server
|
||||
type Configuration struct {
|
||||
// The port used for serving FTP requests
|
||||
BindPort int `json:"bind_port" mapstructure:"bind_port"`
|
||||
// The address to listen on. A blank value means listen on all available network interfaces.
|
||||
BindAddress string `json:"bind_address" mapstructure:"bind_address"`
|
||||
// External IP address to expose for passive connections.
|
||||
ForcePassiveIP string `json:"force_passive_ip" mapstructure:"force_passive_ip"`
|
||||
// Greeting banner displayed when a connection first comes in
|
||||
Banner string `json:"banner" mapstructure:"banner"`
|
||||
// the contents of the specified file, if any, are diplayed when someone connects to the server.
|
||||
// If set, it overrides the banner string provided by the banner option
|
||||
BannerFile string `json:"banner_file" mapstructure:"banner_file"`
|
||||
// If files containing a certificate and matching private key for the server are provided the server will accept
|
||||
// both plain FTP an explicit FTP over TLS.
|
||||
// Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
|
||||
// "paramchange" request to the running service on Windows.
|
||||
CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
|
||||
CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
|
||||
// Do not impose the port 20 for active data transfer. Enabling this option allows to run SFTPGo with less privilege
|
||||
ActiveTransfersPortNon20 bool `json:"active_transfers_port_non_20" mapstructure:"active_transfers_port_non_20"`
|
||||
// Port Range for data connections. Random if not specified
|
||||
PassivePortRange PortRange `json:"passive_port_range" mapstructure:"passive_port_range"`
|
||||
}
|
||||
|
||||
// Initialize configures and starts the FTP server
|
||||
func (c *Configuration) Initialize(configDir string) error {
|
||||
var err error
|
||||
logger.Debug(logSender, "", "initializing FTP server with config %+v", c)
|
||||
server, err = NewServer(c, configDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ftpServer := ftpserver.NewFtpServer(server)
|
||||
return ftpServer.ListenAndServe()
|
||||
}
|
||||
|
||||
// ReloadTLSCertificate reloads the TLS certificate and key from the configured paths
|
||||
func ReloadTLSCertificate() error {
|
||||
if server != nil && server.certMgr != nil {
|
||||
return server.certMgr.LoadCertificate(logSender)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getConfigPath(name, configDir string) string {
|
||||
if !utils.IsFileInputValid(name) {
|
||||
return ""
|
||||
}
|
||||
if len(name) > 0 && !filepath.IsAbs(name) {
|
||||
return filepath.Join(configDir, name)
|
||||
}
|
||||
return name
|
||||
}
|
1263
ftpd/ftpd_test.go
Normal file
1263
ftpd/ftpd_test.go
Normal file
File diff suppressed because it is too large
Load diff
387
ftpd/handler.go
Normal file
387
ftpd/handler.go
Normal file
|
@ -0,0 +1,387 @@
|
|||
package ftpd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/drakkan/sftpgo/common"
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/vfs"
|
||||
)
|
||||
|
||||
var (
|
||||
errNotImplemented = errors.New("Not implemented")
|
||||
)
|
||||
|
||||
// Connection details for an FTP connection.
|
||||
// It implements common.ActiveConnection and ftpserver.ClientDriver interfaces
|
||||
type Connection struct {
|
||||
*common.BaseConnection
|
||||
clientContext ftpserver.ClientContext
|
||||
}
|
||||
|
||||
// GetClientVersion returns the connected client's version.
|
||||
// It returns "Unknown" if the client does not advertise its
|
||||
// version
|
||||
func (c *Connection) GetClientVersion() string {
|
||||
version := c.clientContext.GetClientVersion()
|
||||
if len(version) > 0 {
|
||||
return version
|
||||
}
|
||||
return "Unknown"
|
||||
}
|
||||
|
||||
// GetRemoteAddress return the connected client's address
|
||||
func (c *Connection) GetRemoteAddress() string {
|
||||
return c.clientContext.RemoteAddr().String()
|
||||
}
|
||||
|
||||
// SetConnDeadline does nothing
|
||||
func (c *Connection) SetConnDeadline() {}
|
||||
|
||||
// Disconnect disconnects the client
|
||||
func (c *Connection) Disconnect() error {
|
||||
return c.clientContext.Close(ftpserver.StatusServiceNotAvailable, "connection closed")
|
||||
}
|
||||
|
||||
// GetCommand returns an empty string
|
||||
func (c *Connection) GetCommand() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Create is not implemented we use ClientDriverExtentionFileTransfer
|
||||
func (c *Connection) Create(name string) (afero.File, error) {
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
// Mkdir creates a directory using the connection filesystem
|
||||
func (c *Connection) Mkdir(name string, perm os.FileMode) error {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
p, err := c.Fs.ResolvePath(name)
|
||||
if err != nil {
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
return c.CreateDir(p, name)
|
||||
}
|
||||
|
||||
// MkdirAll is not implemented, we don't need it
|
||||
func (c *Connection) MkdirAll(path string, perm os.FileMode) error {
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
// Open is not implemented we use ClientDriverExtentionFileTransfer and ClientDriverExtensionFileList
|
||||
func (c *Connection) Open(name string) (afero.File, error) {
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
// OpenFile is not implemented we use ClientDriverExtentionFileTransfer
|
||||
func (c *Connection) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
|
||||
return nil, errNotImplemented
|
||||
}
|
||||
|
||||
// Remove removes a file or an empty directory
|
||||
func (c *Connection) Remove(name string) error {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
p, err := c.Fs.ResolvePath(name)
|
||||
if err != nil {
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
|
||||
var fi os.FileInfo
|
||||
if fi, err = c.Fs.Lstat(p); err != nil {
|
||||
c.Log(logger.LevelWarn, "failed to remove a file %#v: stat error: %+v", p, err)
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
|
||||
if fi.IsDir() && fi.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||
return c.RemoveDir(p, name)
|
||||
}
|
||||
return c.RemoveFile(p, name, fi)
|
||||
}
|
||||
|
||||
// RemoveAll is not implemented, we don't need it
|
||||
func (c *Connection) RemoveAll(path string) error {
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
// Rename renames a file or a directory
|
||||
func (c *Connection) Rename(oldname, newname string) error {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
p, err := c.Fs.ResolvePath(oldname)
|
||||
if err != nil {
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
t, err := c.Fs.ResolvePath(newname)
|
||||
if err != nil {
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
|
||||
if err = c.BaseConnection.Rename(p, t, oldname, newname); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
vfs.SetPathPermissions(c.Fs, t, c.User.GetUID(), c.User.GetGID())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stat returns a FileInfo describing the named file/directory, or an error,
|
||||
// if any happens
|
||||
func (c *Connection) Stat(name string) (os.FileInfo, error) {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
if !c.User.HasPerm(dataprovider.PermListItems, path.Dir(name)) {
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
p, err := c.Fs.ResolvePath(name)
|
||||
if err != nil {
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
fi, err := c.Fs.Stat(p)
|
||||
if err != nil {
|
||||
c.Log(logger.LevelWarn, "error running stat on path: %+v", err)
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
// Name returns the name of this connection
|
||||
func (c *Connection) Name() string {
|
||||
return c.GetID()
|
||||
}
|
||||
|
||||
// Chmod changes the mode of the named file/directory
|
||||
func (c *Connection) Chmod(name string, mode os.FileMode) error {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
p, err := c.Fs.ResolvePath(name)
|
||||
if err != nil {
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
attrs := common.StatAttributes{
|
||||
Flags: common.StatAttrPerms,
|
||||
Mode: mode,
|
||||
}
|
||||
return c.SetStat(p, name, &attrs)
|
||||
}
|
||||
|
||||
// Chtimes changes the access and modification times of the named file
|
||||
func (c *Connection) Chtimes(name string, atime time.Time, mtime time.Time) error {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
p, err := c.Fs.ResolvePath(name)
|
||||
if err != nil {
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
attrs := common.StatAttributes{
|
||||
Flags: common.StatAttrTimes,
|
||||
Atime: atime,
|
||||
Mtime: mtime,
|
||||
}
|
||||
return c.SetStat(p, name, &attrs)
|
||||
}
|
||||
|
||||
// AllocateSpace implements ClientDriverExtensionAllocate
|
||||
func (c *Connection) AllocateSpace(size int) error {
|
||||
c.UpdateLastActivity()
|
||||
// we don't have a path here so we check home dir and any virtual folders
|
||||
// we return no error if there is space in any folder
|
||||
folders := []string{"/"}
|
||||
for _, v := range c.User.VirtualFolders {
|
||||
// the space is checked for the parent folder
|
||||
folders = append(folders, path.Join(v.VirtualPath, "fakefile.txt"))
|
||||
}
|
||||
for _, f := range folders {
|
||||
quotaResult := c.HasSpace(false, f)
|
||||
if quotaResult.HasSpace {
|
||||
if quotaResult.QuotaSize == 0 {
|
||||
// unlimited size is allowed
|
||||
return nil
|
||||
}
|
||||
if quotaResult.GetRemainingSize() > int64(size) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return common.ErrQuotaExceeded
|
||||
}
|
||||
|
||||
// ReadDir implements ClientDriverExtensionFilelist
|
||||
func (c *Connection) ReadDir(name string) ([]os.FileInfo, error) {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
p, err := c.Fs.ResolvePath(name)
|
||||
if err != nil {
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
return c.ListDir(p, name)
|
||||
}
|
||||
|
||||
// GetHandle implements ClientDriverExtentionFileTransfer
|
||||
func (c *Connection) GetHandle(name string, flags int) (ftpserver.FileTransfer, error) {
|
||||
c.UpdateLastActivity()
|
||||
|
||||
p, err := c.Fs.ResolvePath(name)
|
||||
if err != nil {
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
if flags&os.O_WRONLY != 0 {
|
||||
return c.uploadFile(p, name, flags)
|
||||
}
|
||||
return c.downloadFile(p, name)
|
||||
}
|
||||
|
||||
func (c *Connection) downloadFile(fsPath, ftpPath string) (ftpserver.FileTransfer, error) {
|
||||
if !c.User.HasPerm(dataprovider.PermDownload, path.Dir(ftpPath)) {
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
if !c.User.IsFileAllowed(ftpPath) {
|
||||
c.Log(logger.LevelWarn, "reading file %#v is not allowed", ftpPath)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
file, r, cancelFn, err := c.Fs.Open(fsPath)
|
||||
if err != nil {
|
||||
c.Log(logger.LevelWarn, "could not open file %#v for reading: %+v", fsPath, err)
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, fsPath, ftpPath, common.TransferDownload,
|
||||
0, 0, false)
|
||||
t := newTransfer(baseTransfer, nil, r, 0)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (c *Connection) uploadFile(fsPath, ftpPath string, flags int) (ftpserver.FileTransfer, error) {
|
||||
if !c.User.IsFileAllowed(ftpPath) {
|
||||
c.Log(logger.LevelWarn, "writing file %#v is not allowed", ftpPath)
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
filePath := fsPath
|
||||
if common.Config.IsAtomicUploadEnabled() && c.Fs.IsAtomicUploadSupported() {
|
||||
filePath = c.Fs.GetAtomicUploadPath(fsPath)
|
||||
}
|
||||
|
||||
stat, statErr := c.Fs.Lstat(fsPath)
|
||||
if (statErr == nil && stat.Mode()&os.ModeSymlink == os.ModeSymlink) || c.Fs.IsNotExist(statErr) {
|
||||
if !c.User.HasPerm(dataprovider.PermUpload, path.Dir(ftpPath)) {
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
return c.handleFTPUploadToNewFile(fsPath, filePath, ftpPath)
|
||||
}
|
||||
|
||||
if statErr != nil {
|
||||
c.Log(logger.LevelError, "error performing file stat %#v: %+v", fsPath, statErr)
|
||||
return nil, c.GetFsError(statErr)
|
||||
}
|
||||
|
||||
// This happen if we upload a file that has the same name of an existing directory
|
||||
if stat.IsDir() {
|
||||
c.Log(logger.LevelWarn, "attempted to open a directory for writing to: %#v", fsPath)
|
||||
return nil, c.GetOpUnsupportedError()
|
||||
}
|
||||
|
||||
if !c.User.HasPerm(dataprovider.PermOverwrite, path.Dir(ftpPath)) {
|
||||
return nil, c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
return c.handleFTPUploadToExistingFile(flags, fsPath, filePath, stat.Size(), ftpPath)
|
||||
}
|
||||
|
||||
func (c *Connection) handleFTPUploadToNewFile(resolvedPath, filePath, requestPath string) (ftpserver.FileTransfer, error) {
|
||||
quotaResult := c.HasSpace(true, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
}
|
||||
file, w, cancelFn, err := c.Fs.Create(filePath, 0)
|
||||
if err != nil {
|
||||
c.Log(logger.LevelWarn, "error creating file %#v: %+v", resolvedPath, err)
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
|
||||
vfs.SetPathPermissions(c.Fs, filePath, c.User.GetUID(), c.User.GetGID())
|
||||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, requestPath,
|
||||
common.TransferUpload, 0, 0, true)
|
||||
t := newTransfer(baseTransfer, w, nil, quotaResult.GetRemainingSize())
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (c *Connection) handleFTPUploadToExistingFile(flags int, resolvedPath, filePath string, fileSize int64,
|
||||
requestPath string) (ftpserver.FileTransfer, error) {
|
||||
var err error
|
||||
quotaResult := c.HasSpace(false, requestPath)
|
||||
if !quotaResult.HasSpace {
|
||||
c.Log(logger.LevelInfo, "denying file write due to quota limits")
|
||||
return nil, common.ErrQuotaExceeded
|
||||
}
|
||||
minWriteOffset := int64(0)
|
||||
|
||||
if flags&os.O_APPEND != 0 && flags&os.O_TRUNC == 0 && !c.Fs.IsUploadResumeSupported() {
|
||||
c.Log(logger.LevelInfo, "upload resume requested for path: %#v but not supported in fs implementation", resolvedPath)
|
||||
return nil, c.GetOpUnsupportedError()
|
||||
}
|
||||
|
||||
if common.Config.IsAtomicUploadEnabled() && c.Fs.IsAtomicUploadSupported() {
|
||||
err = c.Fs.Rename(resolvedPath, filePath)
|
||||
if err != nil {
|
||||
c.Log(logger.LevelWarn, "error renaming existing file for atomic upload, source: %#v, dest: %#v, err: %+v",
|
||||
resolvedPath, filePath, err)
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
}
|
||||
|
||||
file, w, cancelFn, err := c.Fs.Create(filePath, flags)
|
||||
if err != nil {
|
||||
c.Log(logger.LevelWarn, "error opening existing file, flags: %v, source: %#v, err: %+v", flags, filePath, err)
|
||||
return nil, c.GetFsError(err)
|
||||
}
|
||||
|
||||
initialSize := int64(0)
|
||||
// if there is a size limit remaining size cannot be 0 here, since quotaResult.HasSpace
|
||||
// will return false in this case and we deny the upload before
|
||||
maxWriteSize := quotaResult.GetRemainingSize()
|
||||
if flags&os.O_APPEND != 0 && flags&os.O_TRUNC == 0 {
|
||||
c.Log(logger.LevelDebug, "upload resume requested, file path: %#v initial size: %v", filePath, fileSize)
|
||||
minWriteOffset = fileSize
|
||||
} else {
|
||||
if vfs.IsLocalOsFs(c.Fs) {
|
||||
vfolder, err := c.User.GetVirtualFolderForPath(path.Dir(requestPath))
|
||||
if err == nil {
|
||||
dataprovider.UpdateVirtualFolderQuota(vfolder.BaseVirtualFolder, 0, -fileSize, false) //nolint:errcheck
|
||||
if vfolder.IsIncludedInUserQuota() {
|
||||
dataprovider.UpdateUserQuota(c.User, 0, -fileSize, false) //nolint:errcheck
|
||||
}
|
||||
} else {
|
||||
dataprovider.UpdateUserQuota(c.User, 0, -fileSize, false) //nolint:errcheck
|
||||
}
|
||||
} else {
|
||||
initialSize = fileSize
|
||||
}
|
||||
if maxWriteSize > 0 {
|
||||
maxWriteSize += fileSize
|
||||
}
|
||||
}
|
||||
|
||||
vfs.SetPathPermissions(c.Fs, filePath, c.User.GetUID(), c.User.GetGID())
|
||||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, requestPath,
|
||||
common.TransferUpload, minWriteOffset, initialSize, false)
|
||||
t := newTransfer(baseTransfer, w, nil, maxWriteSize)
|
||||
|
||||
return t, nil
|
||||
}
|
441
ftpd/internal_test.go
Normal file
441
ftpd/internal_test.go
Normal file
|
@ -0,0 +1,441 @@
|
|||
package ftpd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/eikenb/pipeat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/drakkan/sftpgo/common"
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/vfs"
|
||||
)
|
||||
|
||||
const (
|
||||
configDir = ".."
|
||||
)
|
||||
|
||||
type mockFTPClientContext struct {
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) Path() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) SetDebug(debug bool) {}
|
||||
|
||||
func (cc mockFTPClientContext) Debug() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) ID() uint32 {
|
||||
return 1
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) RemoteAddr() net.Addr {
|
||||
return &net.IPAddr{IP: []byte("127.0.0.1")}
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) LocalAddr() net.Addr {
|
||||
return &net.IPAddr{IP: []byte("127.0.0.1")}
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) GetClientVersion() string {
|
||||
return "mock version"
|
||||
}
|
||||
|
||||
func (cc mockFTPClientContext) Close(code int, message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MockOsFs mockable OsFs
|
||||
type MockOsFs struct {
|
||||
vfs.Fs
|
||||
err error
|
||||
statErr error
|
||||
isAtomicUploadSupported bool
|
||||
}
|
||||
|
||||
// Name returns the name for the Fs implementation
|
||||
func (fs MockOsFs) Name() string {
|
||||
return "mockOsFs"
|
||||
}
|
||||
|
||||
// IsUploadResumeSupported returns true if upload resume is supported
|
||||
func (MockOsFs) IsUploadResumeSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAtomicUploadSupported returns true if atomic upload is supported
|
||||
func (fs MockOsFs) IsAtomicUploadSupported() bool {
|
||||
return fs.isAtomicUploadSupported
|
||||
}
|
||||
|
||||
// Stat returns a FileInfo describing the named file
|
||||
func (fs MockOsFs) Stat(name string) (os.FileInfo, error) {
|
||||
if fs.statErr != nil {
|
||||
return nil, fs.statErr
|
||||
}
|
||||
return os.Stat(name)
|
||||
}
|
||||
|
||||
// Lstat returns a FileInfo describing the named file
|
||||
func (fs MockOsFs) Lstat(name string) (os.FileInfo, error) {
|
||||
if fs.statErr != nil {
|
||||
return nil, fs.statErr
|
||||
}
|
||||
return os.Lstat(name)
|
||||
}
|
||||
|
||||
// Remove removes the named file or (empty) directory.
|
||||
func (fs MockOsFs) Remove(name string, isDir bool) error {
|
||||
if fs.err != nil {
|
||||
return fs.err
|
||||
}
|
||||
return os.Remove(name)
|
||||
}
|
||||
|
||||
// Rename renames (moves) source to target
|
||||
func (fs MockOsFs) Rename(source, target string) error {
|
||||
if fs.err != nil {
|
||||
return fs.err
|
||||
}
|
||||
return os.Rename(source, target)
|
||||
}
|
||||
|
||||
func newMockOsFs(err, statErr error, atomicUpload bool, connectionID, rootDir string) vfs.Fs {
|
||||
return &MockOsFs{
|
||||
Fs: vfs.NewOsFs(connectionID, rootDir, nil),
|
||||
err: err,
|
||||
statErr: statErr,
|
||||
isAtomicUploadSupported: atomicUpload,
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitialization(t *testing.T) {
|
||||
c := &Configuration{
|
||||
BindPort: 2121,
|
||||
CertificateFile: "acert",
|
||||
CertificateKeyFile: "akey",
|
||||
}
|
||||
err := c.Initialize(configDir)
|
||||
assert.Error(t, err)
|
||||
c.CertificateFile = ""
|
||||
c.CertificateKeyFile = ""
|
||||
c.BannerFile = "afile"
|
||||
server, err := NewServer(c, configDir)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, "", server.initialMsg)
|
||||
_, err = server.GetTLSConfig()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
err = ReloadTLSCertificate()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestServerGetSettings(t *testing.T) {
|
||||
oldConfig := common.Config
|
||||
c := &Configuration{
|
||||
BindPort: 2121,
|
||||
PassivePortRange: PortRange{
|
||||
Start: 10000,
|
||||
End: 11000,
|
||||
},
|
||||
}
|
||||
server, err := NewServer(c, configDir)
|
||||
assert.NoError(t, err)
|
||||
settings, err := server.GetSettings()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 10000, settings.PassiveTransferPortRange.Start)
|
||||
assert.Equal(t, 11000, settings.PassiveTransferPortRange.End)
|
||||
|
||||
common.Config.ProxyProtocol = 1
|
||||
common.Config.ProxyAllowed = []string{"invalid"}
|
||||
_, err = server.GetSettings()
|
||||
assert.Error(t, err)
|
||||
server.config.BindPort = 8021
|
||||
_, err = server.GetSettings()
|
||||
assert.Error(t, err)
|
||||
|
||||
common.Config = oldConfig
|
||||
}
|
||||
|
||||
func TestUserInvalidParams(t *testing.T) {
|
||||
u := dataprovider.User{
|
||||
HomeDir: "invalid",
|
||||
}
|
||||
c := &Configuration{
|
||||
BindPort: 2121,
|
||||
PassivePortRange: PortRange{
|
||||
Start: 10000,
|
||||
End: 11000,
|
||||
},
|
||||
}
|
||||
server, err := NewServer(c, configDir)
|
||||
assert.NoError(t, err)
|
||||
_, err = server.validateUser(u, mockFTPClientContext{})
|
||||
assert.Error(t, err)
|
||||
|
||||
u.Username = "a"
|
||||
u.HomeDir = filepath.Clean(os.TempDir())
|
||||
subDir := "subdir"
|
||||
mappedPath1 := filepath.Join(os.TempDir(), "vdir1")
|
||||
vdirPath1 := "/vdir1"
|
||||
mappedPath2 := filepath.Join(os.TempDir(), "vdir1", subDir)
|
||||
vdirPath2 := "/vdir2"
|
||||
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
||||
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
||||
MappedPath: mappedPath1,
|
||||
},
|
||||
VirtualPath: vdirPath1,
|
||||
})
|
||||
u.VirtualFolders = append(u.VirtualFolders, vfs.VirtualFolder{
|
||||
BaseVirtualFolder: vfs.BaseVirtualFolder{
|
||||
MappedPath: mappedPath2,
|
||||
},
|
||||
VirtualPath: vdirPath2,
|
||||
})
|
||||
_, err = server.validateUser(u, mockFTPClientContext{})
|
||||
assert.Error(t, err)
|
||||
u.VirtualFolders = nil
|
||||
_, err = server.validateUser(u, mockFTPClientContext{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestClientVersion(t *testing.T) {
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
user := dataprovider.User{}
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, nil),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
common.Connections.Add(connection)
|
||||
stats := common.Connections.GetStats()
|
||||
if assert.Len(t, stats, 1) {
|
||||
assert.Equal(t, "mock version", stats[0].ClientVersion)
|
||||
common.Connections.Remove(connection.GetID())
|
||||
}
|
||||
assert.Len(t, common.Connections.GetStats(), 0)
|
||||
}
|
||||
|
||||
func TestDriverMethodsNotImplemented(t *testing.T) {
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
user := dataprovider.User{}
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, nil),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
_, err := connection.Create("")
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
err = connection.MkdirAll("", os.ModePerm)
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
_, err = connection.Open("")
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
_, err = connection.OpenFile("", 0, os.ModePerm)
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
err = connection.RemoveAll("")
|
||||
assert.EqualError(t, err, errNotImplemented.Error())
|
||||
assert.Equal(t, connection.GetID(), connection.Name())
|
||||
}
|
||||
|
||||
func TestResolvePathErrors(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
HomeDir: "invalid",
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := vfs.NewOsFs(connID, user.HomeDir, nil)
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
err := connection.Mkdir("", os.ModePerm)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
err = connection.Remove("")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
err = connection.Rename("", "")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
_, err = connection.Stat("")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
err = connection.Chmod("", os.ModePerm)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
err = connection.Chtimes("", time.Now(), time.Now())
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
_, err = connection.ReadDir("")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
_, err = connection.GetHandle("", 0)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrGenericFailure.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadFileStatError(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("this test is not available on Windows")
|
||||
}
|
||||
user := dataprovider.User{
|
||||
Username: "user",
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := vfs.NewOsFs(connID, user.HomeDir, nil)
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
testFile := filepath.Join(user.HomeDir, "test", "testfile")
|
||||
err := os.MkdirAll(filepath.Dir(testFile), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(testFile, []byte("data"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = os.Chmod(filepath.Dir(testFile), 0001)
|
||||
assert.NoError(t, err)
|
||||
_, err = connection.uploadFile(testFile, "test", 0)
|
||||
assert.Error(t, err)
|
||||
err = os.Chmod(filepath.Dir(testFile), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(filepath.Dir(testFile))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUploadOverwriteErrors(t *testing.T) {
|
||||
user := dataprovider.User{
|
||||
Username: "user",
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
flags := 0
|
||||
flags |= os.O_APPEND
|
||||
_, err := connection.handleFTPUploadToExistingFile(flags, "", "", 0, "")
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrOpUnsupported.Error())
|
||||
}
|
||||
|
||||
f, err := ioutil.TempFile("", "temp")
|
||||
assert.NoError(t, err)
|
||||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
flags = 0
|
||||
flags |= os.O_CREATE
|
||||
flags |= os.O_TRUNC
|
||||
tr, err := connection.handleFTPUploadToExistingFile(flags, f.Name(), f.Name(), 123, f.Name())
|
||||
if assert.NoError(t, err) {
|
||||
transfer := tr.(*transfer)
|
||||
transfers := connection.GetTransfers()
|
||||
if assert.Equal(t, 1, len(transfers)) {
|
||||
assert.Equal(t, transfers[0].ID, transfer.GetID())
|
||||
assert.Equal(t, int64(123), transfer.InitialSize)
|
||||
err = transfer.Close()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(connection.GetTransfers()))
|
||||
}
|
||||
}
|
||||
err = os.Remove(f.Name())
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = connection.handleFTPUploadToExistingFile(0, filepath.Join(os.TempDir(), "sub", "file"),
|
||||
filepath.Join(os.TempDir(), "sub", "file1"), 0, "/sub/file1")
|
||||
assert.Error(t, err)
|
||||
connection.Fs = vfs.NewOsFs(connID, user.GetHomeDir(), nil)
|
||||
_, err = connection.handleFTPUploadToExistingFile(0, "missing1", "missing2", 0, "missing")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestTransferErrors(t *testing.T) {
|
||||
testfile := "testfile"
|
||||
file, err := os.Create(testfile)
|
||||
assert.NoError(t, err)
|
||||
user := dataprovider.User{
|
||||
Username: "user",
|
||||
HomeDir: filepath.Clean(os.TempDir()),
|
||||
}
|
||||
user.Permissions = make(map[string][]string)
|
||||
user.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
mockCC := mockFTPClientContext{}
|
||||
connID := fmt.Sprintf("%v", mockCC.ID())
|
||||
fs := newMockOsFs(nil, nil, false, connID, user.GetHomeDir())
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, fs),
|
||||
clientContext: mockCC,
|
||||
}
|
||||
baseTransfer := common.NewBaseTransfer(file, connection.BaseConnection, nil, file.Name(), testfile, common.TransferDownload,
|
||||
0, 0, false)
|
||||
tr := newTransfer(baseTransfer, nil, nil, 0)
|
||||
err = tr.Close()
|
||||
assert.NoError(t, err)
|
||||
buf := make([]byte, 64)
|
||||
_, err = tr.Read(buf)
|
||||
assert.Error(t, err)
|
||||
err = tr.Close()
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrTransferClosed.Error())
|
||||
}
|
||||
assert.Len(t, connection.GetTransfers(), 0)
|
||||
|
||||
r, _, err := pipeat.Pipe()
|
||||
assert.NoError(t, err)
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testfile, testfile,
|
||||
common.TransferUpload, 0, 0, false)
|
||||
tr = newTransfer(baseTransfer, nil, r, 0)
|
||||
err = tr.closeIO()
|
||||
assert.NoError(t, err)
|
||||
|
||||
r, w, err := pipeat.Pipe()
|
||||
assert.NoError(t, err)
|
||||
pipeWriter := vfs.NewPipeWriter(w)
|
||||
baseTransfer = common.NewBaseTransfer(nil, connection.BaseConnection, nil, testfile, testfile,
|
||||
common.TransferUpload, 0, 0, false)
|
||||
tr = newTransfer(baseTransfer, pipeWriter, nil, 0)
|
||||
_, err = tr.Seek(1, 0)
|
||||
if assert.Error(t, err) {
|
||||
assert.EqualError(t, err, common.ErrOpUnsupported.Error())
|
||||
}
|
||||
|
||||
err = r.Close()
|
||||
assert.NoError(t, err)
|
||||
errFake := fmt.Errorf("fake upload error")
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
pipeWriter.Done(errFake)
|
||||
}()
|
||||
err = tr.closeIO()
|
||||
assert.EqualError(t, err, errFake.Error())
|
||||
err = os.Remove(testfile)
|
||||
assert.NoError(t, err)
|
||||
}
|
190
ftpd/server.go
Normal file
190
ftpd/server.go
Normal file
|
@ -0,0 +1,190 @@
|
|||
package ftpd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"path/filepath"
|
||||
|
||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||
|
||||
"github.com/drakkan/sftpgo/common"
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/metrics"
|
||||
"github.com/drakkan/sftpgo/utils"
|
||||
)
|
||||
|
||||
// Server implements the ftpserverlib MainDriver interface
|
||||
type Server struct {
|
||||
config *Configuration
|
||||
certMgr *common.CertManager
|
||||
initialMsg string
|
||||
}
|
||||
|
||||
// NewServer returns a new FTP server driver
|
||||
func NewServer(config *Configuration, configDir string) (*Server, error) {
|
||||
var err error
|
||||
server := &Server{
|
||||
config: config,
|
||||
certMgr: nil,
|
||||
initialMsg: config.Banner,
|
||||
}
|
||||
certificateFile := getConfigPath(config.CertificateFile, configDir)
|
||||
certificateKeyFile := getConfigPath(config.CertificateKeyFile, configDir)
|
||||
if len(certificateFile) > 0 && len(certificateKeyFile) > 0 {
|
||||
server.certMgr, err = common.NewCertManager(certificateFile, certificateKeyFile, logSender)
|
||||
if err != nil {
|
||||
return server, err
|
||||
}
|
||||
}
|
||||
if len(config.BannerFile) > 0 {
|
||||
bannerFilePath := config.BannerFile
|
||||
if !filepath.IsAbs(bannerFilePath) {
|
||||
bannerFilePath = filepath.Join(configDir, bannerFilePath)
|
||||
}
|
||||
bannerContent, err := ioutil.ReadFile(bannerFilePath)
|
||||
if err == nil {
|
||||
server.initialMsg = string(bannerContent)
|
||||
} else {
|
||||
logger.WarnToConsole("unable to read FTPD banner file: %v", err)
|
||||
logger.Warn(logSender, "", "unable to read banner file: %v", err)
|
||||
}
|
||||
}
|
||||
return server, err
|
||||
}
|
||||
|
||||
// GetSettings returns FTP server settings
|
||||
func (s *Server) GetSettings() (*ftpserver.Settings, error) {
|
||||
var portRange *ftpserver.PortRange = nil
|
||||
if s.config.PassivePortRange.Start > 0 && s.config.PassivePortRange.End > s.config.PassivePortRange.Start {
|
||||
portRange = &ftpserver.PortRange{
|
||||
Start: s.config.PassivePortRange.Start,
|
||||
End: s.config.PassivePortRange.End,
|
||||
}
|
||||
}
|
||||
var ftpListener net.Listener
|
||||
if common.Config.ProxyProtocol > 0 {
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.config.BindAddress, s.config.BindPort))
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "error starting listener on address %s:%d: %v", s.config.BindAddress, s.config.BindPort, err)
|
||||
return nil, err
|
||||
}
|
||||
ftpListener, err = common.Config.GetProxyListener(listener)
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "error enabling proxy listener: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &ftpserver.Settings{
|
||||
Listener: ftpListener,
|
||||
ListenAddr: fmt.Sprintf("%s:%d", s.config.BindAddress, s.config.BindPort),
|
||||
PublicHost: s.config.ForcePassiveIP,
|
||||
PassiveTransferPortRange: portRange,
|
||||
ActiveTransferPortNon20: s.config.ActiveTransfersPortNon20,
|
||||
IdleTimeout: -1,
|
||||
ConnectionTimeout: 30,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ClientConnected is called to send the very first welcome message
|
||||
func (s *Server) ClientConnected(cc ftpserver.ClientContext) (string, error) {
|
||||
connID := fmt.Sprintf("%v", cc.ID())
|
||||
user := dataprovider.User{}
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(connID, common.ProtocolFTP, user, nil),
|
||||
clientContext: cc,
|
||||
}
|
||||
common.Connections.Add(connection)
|
||||
return s.initialMsg, nil
|
||||
}
|
||||
|
||||
// ClientDisconnected is called when the user disconnects, even if he never authenticated
|
||||
func (s *Server) ClientDisconnected(cc ftpserver.ClientContext) {
|
||||
connID := fmt.Sprintf("%v_%v", common.ProtocolFTP, cc.ID())
|
||||
common.Connections.Remove(connID)
|
||||
}
|
||||
|
||||
// AuthUser authenticates the user and selects an handling driver
|
||||
func (s *Server) AuthUser(cc ftpserver.ClientContext, username, password string) (ftpserver.ClientDriver, error) {
|
||||
remoteAddr := cc.RemoteAddr().String()
|
||||
user, err := dataprovider.CheckUserAndPass(username, password)
|
||||
if err != nil {
|
||||
updateLoginMetrics(username, remoteAddr, dataprovider.FTPLoginMethodPassword, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connection, err := s.validateUser(user, cc)
|
||||
|
||||
defer updateLoginMetrics(username, remoteAddr, dataprovider.FTPLoginMethodPassword, err)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connection.Fs.CheckRootPath(connection.GetUsername(), user.GetUID(), user.GetGID())
|
||||
connection.Log(logger.LevelInfo, "User id: %d, logged in with FTP, username: %#v, home_dir: %#v remote addr: %#v",
|
||||
user.ID, user.Username, user.HomeDir, remoteAddr)
|
||||
dataprovider.UpdateLastLogin(user) //nolint:errcheck
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
// GetTLSConfig returns a TLS Certificate to use
|
||||
func (s *Server) GetTLSConfig() (*tls.Config, error) {
|
||||
if s.certMgr != nil {
|
||||
return &tls.Config{
|
||||
GetCertificate: s.certMgr.GetCertificateFunc(),
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("no TLS certificate configured")
|
||||
}
|
||||
|
||||
func (s *Server) validateUser(user dataprovider.User, cc ftpserver.ClientContext) (*Connection, error) {
|
||||
connectionID := fmt.Sprintf("%v_%v", common.ProtocolFTP, cc.ID())
|
||||
if !filepath.IsAbs(user.HomeDir) {
|
||||
logger.Warn(logSender, connectionID, "user %#v has an invalid home dir: %#v. Home dir must be an absolute path, login not allowed",
|
||||
user.Username, user.HomeDir)
|
||||
return nil, fmt.Errorf("cannot login user with invalid home dir: %#v", user.HomeDir)
|
||||
}
|
||||
if user.MaxSessions > 0 {
|
||||
activeSessions := common.Connections.GetActiveSessions(user.Username)
|
||||
if activeSessions >= user.MaxSessions {
|
||||
logger.Debug(logSender, connectionID, "authentication refused for user: %#v, too many open sessions: %v/%v", user.Username,
|
||||
activeSessions, user.MaxSessions)
|
||||
return nil, fmt.Errorf("too many open sessions: %v", activeSessions)
|
||||
}
|
||||
}
|
||||
if dataprovider.GetQuotaTracking() > 0 && user.HasOverlappedMappedPaths() {
|
||||
logger.Debug(logSender, connectionID, "cannot login user %#v, overlapping mapped folders are allowed only with quota tracking disabled",
|
||||
user.Username)
|
||||
return nil, errors.New("overlapping mapped folders are allowed only with quota tracking disabled")
|
||||
}
|
||||
remoteAddr := cc.RemoteAddr().String()
|
||||
if !user.IsLoginFromAddrAllowed(remoteAddr) {
|
||||
logger.Debug(logSender, connectionID, "cannot login user %#v, remote address is not allowed: %v", user.Username, remoteAddr)
|
||||
return nil, fmt.Errorf("Login for user %#v is not allowed from this address: %v", user.Username, remoteAddr)
|
||||
}
|
||||
fs, err := user.GetFilesystem(connectionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
connection := &Connection{
|
||||
BaseConnection: common.NewBaseConnection(fmt.Sprintf("%v", cc.ID()), common.ProtocolFTP, user, fs),
|
||||
clientContext: cc,
|
||||
}
|
||||
err = common.Connections.Swap(connection)
|
||||
if err != nil {
|
||||
return nil, errors.New("Internal authentication error")
|
||||
}
|
||||
return connection, nil
|
||||
}
|
||||
|
||||
func updateLoginMetrics(username, remoteAddress, method string, err error) {
|
||||
metrics.AddLoginAttempt(method)
|
||||
if err != nil {
|
||||
logger.ConnectionFailedLog(username, utils.GetIPFromRemoteAddress(remoteAddress), method, err.Error())
|
||||
}
|
||||
metrics.AddLoginResult(method, err)
|
||||
}
|
128
ftpd/transfer.go
Normal file
128
ftpd/transfer.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
package ftpd
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/eikenb/pipeat"
|
||||
|
||||
"github.com/drakkan/sftpgo/common"
|
||||
"github.com/drakkan/sftpgo/vfs"
|
||||
)
|
||||
|
||||
// transfer contains the transfer details for an upload or a download.
|
||||
// It implements the ftpserver.FileTransfer interface to handle files downloads and uploads
|
||||
type transfer struct {
|
||||
*common.BaseTransfer
|
||||
writer io.WriteCloser
|
||||
reader io.ReadCloser
|
||||
isFinished bool
|
||||
maxWriteSize int64
|
||||
}
|
||||
|
||||
func newTransfer(baseTransfer *common.BaseTransfer, pipeWriter *vfs.PipeWriter, pipeReader *pipeat.PipeReaderAt,
|
||||
maxWriteSize int64) *transfer {
|
||||
var writer io.WriteCloser
|
||||
var reader io.ReadCloser
|
||||
if baseTransfer.File != nil {
|
||||
writer = baseTransfer.File
|
||||
reader = baseTransfer.File
|
||||
} else if pipeWriter != nil {
|
||||
writer = pipeWriter
|
||||
} else if pipeReader != nil {
|
||||
reader = pipeReader
|
||||
}
|
||||
return &transfer{
|
||||
BaseTransfer: baseTransfer,
|
||||
writer: writer,
|
||||
reader: reader,
|
||||
isFinished: false,
|
||||
maxWriteSize: maxWriteSize,
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads the contents to downloads.
|
||||
func (t *transfer) Read(p []byte) (n int, err error) {
|
||||
t.Connection.UpdateLastActivity()
|
||||
var readed int
|
||||
var e error
|
||||
|
||||
readed, e = t.reader.Read(p)
|
||||
atomic.AddInt64(&t.BytesSent, int64(readed))
|
||||
|
||||
if e != nil && e != io.EOF {
|
||||
t.TransferError(e)
|
||||
return readed, e
|
||||
}
|
||||
t.HandleThrottle()
|
||||
return readed, e
|
||||
}
|
||||
|
||||
// Write writes the uploaded contents.
|
||||
func (t *transfer) Write(p []byte) (n int, err error) {
|
||||
t.Connection.UpdateLastActivity()
|
||||
var written int
|
||||
var e error
|
||||
|
||||
written, e = t.writer.Write(p)
|
||||
atomic.AddInt64(&t.BytesReceived, int64(written))
|
||||
|
||||
if t.maxWriteSize > 0 && e == nil && atomic.LoadInt64(&t.BytesReceived) > t.maxWriteSize {
|
||||
e = common.ErrQuotaExceeded
|
||||
}
|
||||
if e != nil {
|
||||
t.TransferError(e)
|
||||
return written, e
|
||||
}
|
||||
t.HandleThrottle()
|
||||
return written, e
|
||||
}
|
||||
|
||||
// Seek sets the offset to resume an upload or a download
|
||||
func (t *transfer) Seek(offset int64, whence int) (int64, error) {
|
||||
if t.File != nil {
|
||||
return t.File.Seek(offset, whence)
|
||||
}
|
||||
return 0, common.ErrOpUnsupported
|
||||
}
|
||||
|
||||
// Close it is called when the transfer is completed.
|
||||
func (t *transfer) Close() error {
|
||||
if err := t.setFinished(); err != nil {
|
||||
return err
|
||||
}
|
||||
err := t.closeIO()
|
||||
errBaseClose := t.BaseTransfer.Close()
|
||||
if errBaseClose != nil {
|
||||
err = errBaseClose
|
||||
}
|
||||
return t.Connection.GetFsError(err)
|
||||
}
|
||||
|
||||
func (t *transfer) closeIO() error {
|
||||
var err error
|
||||
if t.File != nil {
|
||||
err = t.File.Close()
|
||||
} else if t.writer != nil {
|
||||
err = t.writer.Close()
|
||||
t.Lock()
|
||||
// we set ErrTransfer here so quota is not updated, in this case the uploads are atomic
|
||||
if err != nil && t.ErrTransfer == nil {
|
||||
t.ErrTransfer = err
|
||||
}
|
||||
t.Unlock()
|
||||
} else if t.reader != nil {
|
||||
err = t.reader.Close()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *transfer) setFinished() error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if t.isFinished {
|
||||
return common.ErrTransferClosed
|
||||
}
|
||||
t.isFinished = true
|
||||
return nil
|
||||
}
|
10
go.mod
10
go.mod
|
@ -8,12 +8,14 @@ require (
|
|||
github.com/alexedwards/argon2id v0.0.0-20200522061839-9369edc04b05
|
||||
github.com/aws/aws-sdk-go v1.33.1
|
||||
github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d
|
||||
github.com/fclairamb/ftpserverlib v0.8.0
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/render v1.0.1
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
|
||||
github.com/grandcat/zeroconf v1.0.0
|
||||
github.com/jlaffaye/ftp v0.0.0-20200720194710-13949d38913e
|
||||
github.com/lib/pq v1.7.0
|
||||
github.com/mattn/go-sqlite3 v1.14.0
|
||||
github.com/miekg/dns v1.1.29 // indirect
|
||||
|
@ -26,7 +28,7 @@ require (
|
|||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/rs/zerolog v1.19.0
|
||||
github.com/spf13/afero v1.3.1 // indirect
|
||||
github.com/spf13/afero v1.3.2
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
|
@ -47,4 +49,8 @@ require (
|
|||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
replace golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20200705203859-05ad140ecdbd
|
||||
replace (
|
||||
github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20200729185904-a61d63fc1db1
|
||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20200727182237-9cca2b71337f
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20200705203859-05ad140ecdbd
|
||||
)
|
||||
|
|
152
go.sum
152
go.sum
|
@ -35,8 +35,13 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
|
|||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
|
||||
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
|
@ -44,18 +49,25 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
|
|||
github.com/alexedwards/argon2id v0.0.0-20200522061839-9369edc04b05 h1:votg1faEmwABhCeJ4tiBrvwk4BWftQGkEtFy5iuI7rU=
|
||||
github.com/alexedwards/argon2id v0.0.0-20200522061839-9369edc04b05/go.mod h1:GFtu6vaWaRJV5EvSFaVqgq/3Iq95xyYElBV/aupGzUo=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
|
||||
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.33.1 h1:yz9XmNzPshz/lhfAZvLfMnIS9HPo8+boGRcWqDVX+T0=
|
||||
github.com/aws/aws-sdk-go v1.33.1/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
|
@ -66,17 +78,24 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL
|
|||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
|
||||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -84,13 +103,29 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
|
|||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/drakkan/crypto v0.0.0-20200705203859-05ad140ecdbd h1:7DQ8ayx6QylOxQrQ6oHdO+gk1cqtaINUekLrwD9gGNc=
|
||||
github.com/drakkan/crypto v0.0.0-20200705203859-05ad140ecdbd/go.mod h1:v3bhWOXGYda7H5d2s5t9XA6th3fxW3s0MQxU1R96G/w=
|
||||
github.com/drakkan/ftp v0.0.0-20200727182237-9cca2b71337f h1:O58KACT8jWFlYJ3VSNFk+T+s7eX67LQwkC+FTRmCPa8=
|
||||
github.com/drakkan/ftp v0.0.0-20200727182237-9cca2b71337f/go.mod h1:2lmrmq866uF2tnje75wQHzmPXhmSWUt7Gyx2vgK1RCU=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20200728193209-1f77405f11b5 h1:pAThLlS4VfkAOPk/tex9/qVwsbb1CRT9YkBjsBVBFNI=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20200728193209-1f77405f11b5/go.mod h1:Jwd+zOP3T0kwiCQcgjpu3VWtc7AI6Nu4UPN2HYqaniM=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20200729180240-d4d48e974eed h1:mIaBQdWAOkh6Rt+O9sEo/gGJaC7DnPuxpqVpPZYz0z8=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20200729180240-d4d48e974eed/go.mod h1:Jwd+zOP3T0kwiCQcgjpu3VWtc7AI6Nu4UPN2HYqaniM=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20200729185904-a61d63fc1db1 h1:PkZAqIdsFJpfyRLmCOHZHsu3VUDgqClze0+hai6HBL8=
|
||||
github.com/drakkan/ftpserverlib v0.0.0-20200729185904-a61d63fc1db1/go.mod h1:Jwd+zOP3T0kwiCQcgjpu3VWtc7AI6Nu4UPN2HYqaniM=
|
||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d h1:8RvCRWer7TB2n+DKhW4uW15hRiqPmabSnSyYhju/Nuw=
|
||||
github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d/go.mod h1:+JPhBw5JdJrSF80r6xsSg1TYHjyAGxYs4X24VyUdMZU=
|
||||
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
|
||||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
|
@ -104,14 +139,20 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2
|
|||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
@ -137,6 +178,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
|||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -157,19 +199,28 @@ github.com/google/pprof v0.0.0-20200507031123-427632fa3b1c/go.mod h1:ZgVRPoUq/hf
|
|||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
|
||||
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
|
@ -180,6 +231,7 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
|
|||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
@ -189,12 +241,18 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
|||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
|
||||
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
|
@ -211,11 +269,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
|
@ -243,23 +306,51 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
|||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nathanaelle/password/v2 v2.0.1 h1:ItoCTdsuIWzilYmllQPa3DR3YoCXcpfxScWLqr8Ii2s=
|
||||
github.com/nathanaelle/password/v2 v2.0.1/go.mod h1:eaoT+ICQEPNtikBRIAatN8ThWwMhVG+r1jTw60BvPJk=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
|
||||
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
|
||||
github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k=
|
||||
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
|
||||
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pires/go-proxyproto v0.1.3 h1:2XEuhsQluSNA5QIQkiUv8PfgZ51sNYIQkq/yFquiSQM=
|
||||
github.com/pires/go-proxyproto v0.1.3/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
|
||||
github.com/pkg/sftp v1.11.1-0.20200716191756-97b9df616e69 h1:qbrvNcVkxFdNuawO0LsSRRVMubCHOHdDTbb5EO/hiPE=
|
||||
github.com/pkg/sftp v1.11.1-0.20200716191756-97b9df616e69/go.mod h1:PIrgHN0+qgDmYTNiwryjoEqmXo9tv8aMwQ//Yg1xwIs=
|
||||
|
@ -267,26 +358,35 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
|
||||
github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||
|
@ -296,7 +396,9 @@ github.com/rs/zerolog v1.19.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJ
|
|||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/secsy/goftp v0.0.0-20190720192957-f31499d7c79a/go.mod h1:MnkX001NG75g3p8bhFycnyIjeQoOjGL6CEIsdE/nKSY=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
@ -304,24 +406,30 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
|
|||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.3.1 h1:GPTpEAuNr98px18yNQ66JllNil98wfRZ/5Ukny8FeQA=
|
||||
github.com/spf13/afero v1.3.1/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/afero v1.3.2 h1:GDarE4TJQI52kYSbSAmLiId1Elfj+xgSDqrUZxFhxlU=
|
||||
github.com/spf13/afero v1.3.2/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
|
@ -332,24 +440,36 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -383,11 +503,13 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
|
@ -398,6 +520,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
|
|||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -428,9 +551,11 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -440,10 +565,12 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -467,10 +594,12 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
|
|||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
|
@ -488,6 +617,8 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
@ -497,6 +628,7 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
|
@ -517,6 +649,7 @@ golang.org/x/tools v0.0.0-20200702044944-0cc1aa72b347/go.mod h1:EkVYQZoAsY45+roY
|
|||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
|
@ -533,6 +666,7 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M
|
|||
google.golang.org/api v0.28.0 h1:jMF5hhVfMkTZwHW1SDpKq5CkgWLXOb31Foaca9Zr3oM=
|
||||
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
|
@ -543,6 +677,7 @@ google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRn
|
|||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
|
@ -567,11 +702,15 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc
|
|||
google.golang.org/genproto v0.0.0-20200626011028-ee7919e894b5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200702021140-07506425bd67 h1:4BC1C1i30F3MZeiIO6y6IIo4DxrtOwITK87bQl3lhFA=
|
||||
google.golang.org/genproto v0.0.0-20200702021140-07506425bd67/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
|
@ -595,13 +734,19 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
|
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/dutchcoders/goftp.v1 v1.0.0-20170301105846-ed59a591ce14/go.mod h1:nzmlZQ+UqB5+55CRTV/dOaiK8OrPl6Co96Ob8lH4Wxw=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
|
||||
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
@ -611,6 +756,7 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
|||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
@ -621,3 +767,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
|
|
|
@ -1274,14 +1274,14 @@ func TestProviderErrors(t *testing.T) {
|
|||
backupContent, err := json.Marshal(backupData)
|
||||
assert.NoError(t, err)
|
||||
backupFilePath := filepath.Join(backupsPath, "backup.json")
|
||||
err = ioutil.WriteFile(backupFilePath, backupContent, 0666)
|
||||
err = ioutil.WriteFile(backupFilePath, backupContent, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
|
||||
assert.NoError(t, err)
|
||||
backupData.Folders = append(backupData.Folders, vfs.BaseVirtualFolder{MappedPath: os.TempDir()})
|
||||
backupContent, err = json.Marshal(backupData)
|
||||
assert.NoError(t, err)
|
||||
err = ioutil.WriteFile(backupFilePath, backupContent, 0666)
|
||||
err = ioutil.WriteFile(backupFilePath, backupContent, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "", "", http.StatusInternalServerError)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1422,7 +1422,7 @@ func TestLoaddata(t *testing.T) {
|
|||
backupContent, err := json.Marshal(backupData)
|
||||
assert.NoError(t, err)
|
||||
backupFilePath := filepath.Join(backupsPath, "backup.json")
|
||||
err = ioutil.WriteFile(backupFilePath, backupContent, 0666)
|
||||
err = ioutil.WriteFile(backupFilePath, backupContent, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "a", "", http.StatusBadRequest)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1489,7 +1489,7 @@ func TestLoaddataMode(t *testing.T) {
|
|||
backupData.Users = append(backupData.Users, user)
|
||||
backupContent, _ := json.Marshal(backupData)
|
||||
backupFilePath := filepath.Join(backupsPath, "backup.json")
|
||||
err := ioutil.WriteFile(backupFilePath, backupContent, 0666)
|
||||
err := ioutil.WriteFile(backupFilePath, backupContent, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
_, _, err = httpd.Loaddata(backupFilePath, "0", "0", http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
|
@ -2821,7 +2821,7 @@ func createTestFile(path string, size int64) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
return ioutil.WriteFile(path, content, 0666)
|
||||
return ioutil.WriteFile(path, content, os.ModePerm)
|
||||
}
|
||||
|
||||
func getMultipartFormData(values url.Values, fileFieldName, filePath string) (bytes.Buffer, string, error) {
|
||||
|
|
|
@ -441,7 +441,7 @@ func TestBasicAuth(t *testing.T) {
|
|||
oldAuthPassword := authPassword
|
||||
authUserFile := filepath.Join(os.TempDir(), "http_users.txt")
|
||||
authUserData := []byte("test1:$2y$05$bcHSED7aO1cfLto6ZdDBOOKzlwftslVhtpIkRhAtSa4GuLmk5mola\n")
|
||||
err := ioutil.WriteFile(authUserFile, authUserData, 0666)
|
||||
err := ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
httpAuth, _ = newBasicAuthProvider(authUserFile)
|
||||
_, _, err = GetVersion(http.StatusUnauthorized)
|
||||
|
@ -454,7 +454,7 @@ func TestBasicAuth(t *testing.T) {
|
|||
defer resp.Body.Close()
|
||||
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
|
||||
authUserData = append(authUserData, []byte("test2:$apr1$gLnIkRIf$Xr/6aJfmIrihP4b2N2tcs/\n")...)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, 0666)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
SetBaseURLAndCredentials(httpBaseURL, "test2", "password2")
|
||||
_, _, err = GetVersion(http.StatusOK)
|
||||
|
@ -463,31 +463,31 @@ func TestBasicAuth(t *testing.T) {
|
|||
_, _, err = GetVersion(http.StatusOK)
|
||||
assert.Error(t, err)
|
||||
authUserData = append(authUserData, []byte("test3:$apr1$gLnIkRIf$Xr/6$aJfmIr$ihP4b2N2tcs/\n")...)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, 0666)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
SetBaseURLAndCredentials(httpBaseURL, "test3", "wrong_password")
|
||||
_, _, err = GetVersion(http.StatusUnauthorized)
|
||||
assert.NoError(t, err)
|
||||
authUserData = append(authUserData, []byte("test4:$invalid$gLnIkRIf$Xr/6$aJfmIr$ihP4b2N2tcs/\n")...)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, 0666)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
SetBaseURLAndCredentials(httpBaseURL, "test3", "password2")
|
||||
_, _, err = GetVersion(http.StatusUnauthorized)
|
||||
assert.NoError(t, err)
|
||||
if runtime.GOOS != "windows" {
|
||||
authUserData = append(authUserData, []byte("test5:$apr1$gLnIkRIf$Xr/6aJfmIrihP4b2N2tcs/\n")...)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, 0666)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = os.Chmod(authUserFile, 0001)
|
||||
assert.NoError(t, err)
|
||||
SetBaseURLAndCredentials(httpBaseURL, "test5", "password2")
|
||||
_, _, err = GetVersion(http.StatusUnauthorized)
|
||||
assert.NoError(t, err)
|
||||
err = os.Chmod(authUserFile, 0666)
|
||||
err = os.Chmod(authUserFile, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
authUserData = append(authUserData, []byte("\"foo\"bar\"\r\n")...)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, 0666)
|
||||
err = ioutil.WriteFile(authUserFile, authUserData, os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
SetBaseURLAndCredentials(httpBaseURL, "test2", "password2")
|
||||
_, _, err = GetVersion(http.StatusUnauthorized)
|
||||
|
|
|
@ -36,40 +36,40 @@ var (
|
|||
Help: "Total number of logged in users",
|
||||
})
|
||||
|
||||
// totalUploads is the metric that reports the total number of successful SFTP/SCP uploads
|
||||
// totalUploads is the metric that reports the total number of successful uploads
|
||||
totalUploads = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_uploads_total",
|
||||
Help: "The total number of successful SFTP/SCP uploads",
|
||||
Help: "The total number of successful uploads",
|
||||
})
|
||||
|
||||
// totalDownloads is the metric that reports the total number of successful SFTP/SCP downloads
|
||||
// totalDownloads is the metric that reports the total number of successful downloads
|
||||
totalDownloads = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_downloads_total",
|
||||
Help: "The total number of successful SFTP/SCP downloads",
|
||||
Help: "The total number of successful downloads",
|
||||
})
|
||||
|
||||
// totalUploadErrors is the metric that reports the total number of SFTP/SCP upload errors
|
||||
// totalUploadErrors is the metric that reports the total number of upload errors
|
||||
totalUploadErrors = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_upload_errors_total",
|
||||
Help: "The total number of SFTP/SCP upload errors",
|
||||
Help: "The total number of upload errors",
|
||||
})
|
||||
|
||||
// totalDownloadErrors is the metric that reports the total number of SFTP/SCP download errors
|
||||
// totalDownloadErrors is the metric that reports the total number of download errors
|
||||
totalDownloadErrors = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_download_errors_total",
|
||||
Help: "The total number of SFTP/SCP download errors",
|
||||
Help: "The total number of download errors",
|
||||
})
|
||||
|
||||
// totalUploadSize is the metric that reports the total SFTP/SCP uploads size as bytes
|
||||
// totalUploadSize is the metric that reports the total uploads size as bytes
|
||||
totalUploadSize = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_upload_size",
|
||||
Help: "The total SFTP/SCP upload size as bytes, partial uploads are included",
|
||||
Help: "The total upload size as bytes, partial uploads are included",
|
||||
})
|
||||
|
||||
// totalDownloadSize is the metric that reports the total SFTP/SCP downloads size as bytes
|
||||
// totalDownloadSize is the metric that reports the total downloads size as bytes
|
||||
totalDownloadSize = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_download_size",
|
||||
Help: "The total SFTP/SCP download size as bytes, partial downloads are included",
|
||||
Help: "The total download size as bytes, partial downloads are included",
|
||||
})
|
||||
|
||||
// totalSSHCommands is the metric that reports the total number of executed SSH commands
|
||||
|
@ -90,6 +90,13 @@ var (
|
|||
Help: "The total number of login attempts",
|
||||
})
|
||||
|
||||
// totalNoAuthTryed is te metric that reports the total number of clients disconnected
|
||||
// for inactivity before trying to login
|
||||
totalNoAuthTryed = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_no_auth_total",
|
||||
Help: "The total number of clients disconnected for inactivity before trying to login",
|
||||
})
|
||||
|
||||
// totalLoginOK is the metric that reports the total number of successful logins
|
||||
totalLoginOK = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "sftpgo_login_ok_total",
|
||||
|
@ -604,6 +611,12 @@ func AddLoginResult(authMethod string, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
// AddNoAuthTryed increments the metric for clients disconnected
|
||||
// for inactivity before trying to login
|
||||
func AddNoAuthTryed() {
|
||||
totalNoAuthTryed.Inc()
|
||||
}
|
||||
|
||||
// HTTPRequestServed increments the metrics for HTTP requests
|
||||
func HTTPRequestServed(status int) {
|
||||
totalHTTPRequests.Inc()
|
||||
|
|
|
@ -60,6 +60,10 @@ func AddLoginAttempt(authMethod string) {}
|
|||
// AddLoginResult increments the metrics for login results
|
||||
func AddLoginResult(authMethod string, err error) {}
|
||||
|
||||
// AddNoAuthTryed increments the metric for clients disconnected
|
||||
// for inactivity before trying to login
|
||||
func AddNoAuthTryed() {}
|
||||
|
||||
// HTTPRequestServed increments the metrics for HTTP requests
|
||||
func HTTPRequestServed(status int) {}
|
||||
|
||||
|
|
|
@ -77,12 +77,6 @@ func (s *Service) Start() error {
|
|||
return err
|
||||
}
|
||||
|
||||
httpConfig := config.GetHTTPConfig()
|
||||
httpConfig.Initialize(s.ConfigDir)
|
||||
|
||||
sftpdConf := config.GetSFTPDConfig()
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
|
||||
if s.PortableMode == 1 {
|
||||
// create the user for portable mode
|
||||
err = dataprovider.AddUser(s.PortableUser)
|
||||
|
@ -92,6 +86,19 @@ func (s *Service) Start() error {
|
|||
}
|
||||
}
|
||||
|
||||
httpConfig := config.GetHTTPConfig()
|
||||
httpConfig.Initialize(s.ConfigDir)
|
||||
|
||||
s.startServices()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) startServices() {
|
||||
sftpdConf := config.GetSFTPDConfig()
|
||||
ftpdConf := config.GetFTPDConfig()
|
||||
httpdConf := config.GetHTTPDConfig()
|
||||
|
||||
go func() {
|
||||
logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf)
|
||||
if err := sftpdConf.Initialize(s.ConfigDir); err != nil {
|
||||
|
@ -117,7 +124,18 @@ func (s *Service) Start() error {
|
|||
logger.DebugToConsole("HTTP server not started, disabled in config file")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
if ftpdConf.BindPort > 0 {
|
||||
go func() {
|
||||
if err := ftpdConf.Initialize(s.ConfigDir); err != nil {
|
||||
logger.Error(logSender, "", "could not start FTP server: %v", err)
|
||||
logger.ErrorToConsole("could not start FTP server: %v", err)
|
||||
s.Error = err
|
||||
}
|
||||
s.Shutdown <- true
|
||||
}()
|
||||
} else {
|
||||
logger.Debug(logSender, "", "FTP server not started, disabled in config file")
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the service exits
|
||||
|
|
|
@ -23,7 +23,8 @@ import (
|
|||
)
|
||||
|
||||
// StartPortableMode starts the service in portable mode
|
||||
func (s *Service) StartPortableMode(sftpdPort int, enabledSSHCommands []string, advertiseService, advertiseCredentials bool) error {
|
||||
func (s *Service) StartPortableMode(sftpdPort, ftpPort int, enabledSSHCommands []string, advertiseService, advertiseCredentials bool,
|
||||
ftpsCert, ftpsKey string) error {
|
||||
if s.PortableMode != 1 {
|
||||
return fmt.Errorf("service is not configured for portable mode")
|
||||
}
|
||||
|
@ -67,14 +68,44 @@ func (s *Service) StartPortableMode(sftpdPort int, enabledSSHCommands []string,
|
|||
}
|
||||
config.SetSFTPDConfig(sftpdConf)
|
||||
|
||||
if ftpPort >= 0 {
|
||||
ftpConf := config.GetFTPDConfig()
|
||||
if ftpPort > 0 {
|
||||
ftpConf.BindPort = ftpPort
|
||||
} else {
|
||||
ftpConf.BindPort = 49152 + rand.Intn(15000)
|
||||
}
|
||||
ftpConf.Banner = fmt.Sprintf("SFTPGo portable %v ready", version.Get().Version)
|
||||
ftpConf.CertificateFile = ftpsCert
|
||||
ftpConf.CertificateKeyFile = ftpsKey
|
||||
config.SetFTPDConfig(ftpConf)
|
||||
}
|
||||
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var mDNSService *zeroconf.Server
|
||||
|
||||
s.advertiseServices(advertiseService, advertiseCredentials)
|
||||
|
||||
var ftpInfo string
|
||||
if config.GetFTPDConfig().BindPort > 0 {
|
||||
ftpInfo = fmt.Sprintf("FTP port: %v", config.GetFTPDConfig().BindPort)
|
||||
}
|
||||
logger.InfoToConsole("Portable mode ready, SFTP port: %v, user: %#v, password: %#v, public keys: %v, directory: %#v, "+
|
||||
"permissions: %+v, enabled ssh commands: %v file extensions filters: %+v %v", sftpdConf.BindPort, s.PortableUser.Username,
|
||||
printablePassword, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions,
|
||||
sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FileExtensions, ftpInfo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) advertiseServices(advertiseService, advertiseCredentials bool) {
|
||||
var mDNSServiceSFTP *zeroconf.Server
|
||||
var mDNSServiceFTP *zeroconf.Server
|
||||
var err error
|
||||
if advertiseService {
|
||||
meta := []string{
|
||||
fmt.Sprintf("version=%v", version.GetAsString()),
|
||||
fmt.Sprintf("version=%v", version.Get().Version),
|
||||
}
|
||||
if advertiseCredentials {
|
||||
logger.InfoToConsole("Advertising credentials via multicast DNS")
|
||||
|
@ -85,7 +116,8 @@ func (s *Service) StartPortableMode(sftpdPort int, enabledSSHCommands []string,
|
|||
logger.InfoToConsole("Unable to advertise key based credentials via multicast DNS, we don't have the private key")
|
||||
}
|
||||
}
|
||||
mDNSService, err = zeroconf.Register(
|
||||
sftpdConf := config.GetSFTPDConfig()
|
||||
mDNSServiceSFTP, err = zeroconf.Register(
|
||||
fmt.Sprintf("SFTPGo portable %v", sftpdConf.BindPort), // service instance name
|
||||
"_sftp-ssh._tcp", // service type and protocol
|
||||
"local.", // service domain
|
||||
|
@ -94,28 +126,41 @@ func (s *Service) StartPortableMode(sftpdPort int, enabledSSHCommands []string,
|
|||
nil, // register on all network interfaces
|
||||
)
|
||||
if err != nil {
|
||||
mDNSService = nil
|
||||
mDNSServiceSFTP = nil
|
||||
logger.WarnToConsole("Unable to advertise SFTP service via multicast DNS: %v", err)
|
||||
} else {
|
||||
logger.InfoToConsole("SFTP service advertised via multicast DNS")
|
||||
}
|
||||
ftpdConf := config.GetFTPDConfig()
|
||||
mDNSServiceFTP, err = zeroconf.Register(
|
||||
fmt.Sprintf("SFTPGo portable %v", ftpdConf.BindPort),
|
||||
"_ftp._tcp",
|
||||
"local.",
|
||||
ftpdConf.BindPort,
|
||||
meta,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
mDNSServiceFTP = nil
|
||||
logger.WarnToConsole("Unable to advertise FTP service via multicast DNS: %v", err)
|
||||
} else {
|
||||
logger.InfoToConsole("FTP service advertised via multicast DNS")
|
||||
}
|
||||
}
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sig
|
||||
if mDNSService != nil {
|
||||
logger.InfoToConsole("unregistering multicast DNS service")
|
||||
mDNSService.Shutdown()
|
||||
if mDNSServiceSFTP != nil {
|
||||
logger.InfoToConsole("unregistering multicast DNS SFTP service")
|
||||
mDNSServiceSFTP.Shutdown()
|
||||
}
|
||||
if mDNSServiceFTP != nil {
|
||||
logger.InfoToConsole("unregistering multicast DNS FTP service")
|
||||
mDNSServiceFTP.Shutdown()
|
||||
}
|
||||
s.Stop()
|
||||
}()
|
||||
|
||||
logger.InfoToConsole("Portable mode ready, SFTP port: %v, user: %#v, password: %#v, public keys: %v, directory: %#v, "+
|
||||
"permissions: %+v, enabled ssh commands: %v file extensions filters: %+v", sftpdConf.BindPort, s.PortableUser.Username,
|
||||
printablePassword, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions,
|
||||
sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FileExtensions)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) getPortableDirToServe() string {
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"golang.org/x/sys/windows/svc/mgr"
|
||||
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/ftpd"
|
||||
"github.com/drakkan/sftpgo/httpd"
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
)
|
||||
|
@ -93,6 +94,10 @@ loop:
|
|||
if err != nil {
|
||||
logger.Warn(logSender, "", "error reloading TLS certificate: %v", err)
|
||||
}
|
||||
err = ftpd.ReloadTLSCertificate()
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "error reloading FTPD TLS certificate: %v", err)
|
||||
}
|
||||
case rotateLogCmd:
|
||||
logger.Debug(logSender, "", "Received log file rotation request")
|
||||
err := logger.RotateLogFile()
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"syscall"
|
||||
|
||||
"github.com/drakkan/sftpgo/dataprovider"
|
||||
"github.com/drakkan/sftpgo/ftpd"
|
||||
"github.com/drakkan/sftpgo/httpd"
|
||||
"github.com/drakkan/sftpgo/logger"
|
||||
)
|
||||
|
@ -26,6 +27,10 @@ func registerSigHup() {
|
|||
if err != nil {
|
||||
logger.Warn(logSender, "", "error reloading TLS certificate: %v", err)
|
||||
}
|
||||
err = ftpd.ReloadTLSCertificate()
|
||||
if err != nil {
|
||||
logger.Warn(logSender, "", "error reloading FTPD TLS certificate: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -74,7 +74,7 @@ func (c *Connection) Fileread(request *sftp.Request) (io.ReaderAt, error) {
|
|||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, p, request.Filepath, common.TransferDownload,
|
||||
0, 0, false)
|
||||
t := newTranfer(baseTransfer, nil, r, 0)
|
||||
t := newTransfer(baseTransfer, nil, r, 0)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
@ -164,14 +164,6 @@ func (c *Connection) Filecmd(request *sftp.Request) error {
|
|||
return sftp.ErrSSHFxOpUnsupported
|
||||
}
|
||||
|
||||
var fileLocation = p
|
||||
if target != "" {
|
||||
fileLocation = target
|
||||
}
|
||||
|
||||
// we return if we remove a file or a dir so source path or target path always exists here
|
||||
vfs.SetPathPermissions(c.Fs, fileLocation, c.User.GetUID(), c.User.GetGID())
|
||||
|
||||
return sftp.ErrSSHFxOk
|
||||
}
|
||||
|
||||
|
@ -276,7 +268,7 @@ func (c *Connection) handleSFTPUploadToNewFile(resolvedPath, filePath, requestPa
|
|||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, requestPath,
|
||||
common.TransferUpload, 0, 0, true)
|
||||
t := newTranfer(baseTransfer, w, nil, quotaResult.GetRemainingSize())
|
||||
t := newTransfer(baseTransfer, w, nil, quotaResult.GetRemainingSize())
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
@ -343,7 +335,7 @@ func (c *Connection) handleSFTPUploadToExistingFile(pflags sftp.FileOpenFlags, r
|
|||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, requestPath,
|
||||
common.TransferUpload, minWriteOffset, initialSize, false)
|
||||
t := newTranfer(baseTransfer, w, nil, maxWriteSize)
|
||||
t := newTransfer(baseTransfer, w, nil, maxWriteSize)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ func TestUploadResumeInvalidOffset(t *testing.T) {
|
|||
fs := vfs.NewOsFs("", os.TempDir(), nil)
|
||||
conn := common.NewBaseConnection("", common.ProtocolSFTP, user, fs)
|
||||
baseTransfer := common.NewBaseTransfer(file, conn, nil, file.Name(), testfile, common.TransferUpload, 10, 0, false)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, 0)
|
||||
_, err = transfer.WriteAt([]byte("test"), 0)
|
||||
assert.Error(t, err, "upload with invalid offset must fail")
|
||||
if assert.Error(t, transfer.ErrTransfer) {
|
||||
|
@ -187,7 +187,7 @@ func TestReadWriteErrors(t *testing.T) {
|
|||
fs := vfs.NewOsFs("", os.TempDir(), nil)
|
||||
conn := common.NewBaseConnection("", common.ProtocolSFTP, user, fs)
|
||||
baseTransfer := common.NewBaseTransfer(file, conn, nil, file.Name(), testfile, common.TransferDownload, 0, 0, false)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, 0)
|
||||
err = file.Close()
|
||||
assert.NoError(t, err)
|
||||
_, err = transfer.WriteAt([]byte("test"), 0)
|
||||
|
@ -201,7 +201,7 @@ func TestReadWriteErrors(t *testing.T) {
|
|||
r, _, err := pipeat.Pipe()
|
||||
assert.NoError(t, err)
|
||||
baseTransfer = common.NewBaseTransfer(nil, conn, nil, file.Name(), testfile, common.TransferDownload, 0, 0, false)
|
||||
transfer = newTranfer(baseTransfer, nil, r, 0)
|
||||
transfer = newTransfer(baseTransfer, nil, r, 0)
|
||||
err = transfer.closeIO()
|
||||
assert.NoError(t, err)
|
||||
_, err = transfer.ReadAt(buf, 0)
|
||||
|
@ -211,7 +211,7 @@ func TestReadWriteErrors(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
pipeWriter := vfs.NewPipeWriter(w)
|
||||
baseTransfer = common.NewBaseTransfer(nil, conn, nil, file.Name(), testfile, common.TransferDownload, 0, 0, false)
|
||||
transfer = newTranfer(baseTransfer, pipeWriter, nil, 0)
|
||||
transfer = newTransfer(baseTransfer, pipeWriter, nil, 0)
|
||||
|
||||
err = r.Close()
|
||||
assert.NoError(t, err)
|
||||
|
@ -243,7 +243,7 @@ func TestTransferCancelFn(t *testing.T) {
|
|||
fs := vfs.NewOsFs("", os.TempDir(), nil)
|
||||
conn := common.NewBaseConnection("", common.ProtocolSFTP, user, fs)
|
||||
baseTransfer := common.NewBaseTransfer(file, conn, cancelFn, file.Name(), testfile, common.TransferDownload, 0, 0, false)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, 0)
|
||||
|
||||
errFake := errors.New("fake error, this will trigger cancelFn")
|
||||
transfer.TransferError(errFake)
|
||||
|
@ -273,7 +273,7 @@ func TestMockFsErrors(t *testing.T) {
|
|||
}
|
||||
testfile := filepath.Join(u.HomeDir, "testfile")
|
||||
request := sftp.NewRequest("Remove", testfile)
|
||||
err := ioutil.WriteFile(testfile, []byte("test"), 0666)
|
||||
err := ioutil.WriteFile(testfile, []byte("test"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
_, err = c.Filewrite(request)
|
||||
assert.EqualError(t, err, sftp.ErrSSHFxFailure.Error())
|
||||
|
@ -979,7 +979,7 @@ func TestSystemCommandErrors(t *testing.T) {
|
|||
sshCmd.connection.channel = &mockSSHChannel
|
||||
baseTransfer := common.NewBaseTransfer(nil, sshCmd.connection.BaseConnection, nil, "", "", common.TransferDownload,
|
||||
0, 0, false)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, 0)
|
||||
destBuff := make([]byte, 65535)
|
||||
dst := bytes.NewBuffer(destBuff)
|
||||
_, err = transfer.copyFromReaderToWriter(dst, sshCmd.connection.channel)
|
||||
|
@ -1308,7 +1308,7 @@ func TestSCPErrorsMockFs(t *testing.T) {
|
|||
assert.EqualError(t, err, errFake.Error())
|
||||
|
||||
testfile := filepath.Join(u.HomeDir, "testfile")
|
||||
err = ioutil.WriteFile(testfile, []byte("test"), 0666)
|
||||
err = ioutil.WriteFile(testfile, []byte("test"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
stat, err := os.Stat(u.HomeDir)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1476,7 +1476,7 @@ func TestSCPDownloadFileData(t *testing.T) {
|
|||
args: []string{"-r", "-f", "/tmp"},
|
||||
},
|
||||
}
|
||||
err := ioutil.WriteFile(testfile, []byte("test"), 0666)
|
||||
err := ioutil.WriteFile(testfile, []byte("test"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
stat, err := os.Stat(testfile)
|
||||
assert.NoError(t, err)
|
||||
|
@ -1531,7 +1531,7 @@ func TestSCPUploadFiledata(t *testing.T) {
|
|||
|
||||
baseTransfer := common.NewBaseTransfer(file, scpCommand.connection.BaseConnection, nil, file.Name(),
|
||||
"/"+testfile, common.TransferDownload, 0, 0, true)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, 0)
|
||||
|
||||
err = scpCommand.getUploadFileData(2, transfer)
|
||||
assert.Error(t, err, "upload must fail, we send a fake write error message")
|
||||
|
@ -1563,7 +1563,7 @@ func TestSCPUploadFiledata(t *testing.T) {
|
|||
file, err = os.Create(testfile)
|
||||
assert.NoError(t, err)
|
||||
baseTransfer.File = file
|
||||
transfer = newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer = newTransfer(baseTransfer, nil, nil, 0)
|
||||
transfer.Connection.AddTransfer(transfer)
|
||||
err = scpCommand.getUploadFileData(2, transfer)
|
||||
assert.Error(t, err, "upload must fail, we have not enough data to read")
|
||||
|
@ -1615,7 +1615,7 @@ func TestUploadError(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
baseTransfer := common.NewBaseTransfer(file, connection.BaseConnection, nil, testfile,
|
||||
testfile, common.TransferUpload, 0, 0, true)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, 0)
|
||||
|
||||
errFake := errors.New("fake error")
|
||||
transfer.TransferError(errFake)
|
||||
|
@ -1678,7 +1678,7 @@ func TestLoadHostKeys(t *testing.T) {
|
|||
err := c.checkAndLoadHostKeys(configDir, serverConfig)
|
||||
assert.Error(t, err)
|
||||
testfile := filepath.Join(os.TempDir(), "invalidkey")
|
||||
err = ioutil.WriteFile(testfile, []byte("some bytes"), 0666)
|
||||
err = ioutil.WriteFile(testfile, []byte("some bytes"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
c.HostKeys = []string{testfile}
|
||||
err = c.checkAndLoadHostKeys(configDir, serverConfig)
|
||||
|
@ -1726,7 +1726,7 @@ func TestCertCheckerInitErrors(t *testing.T) {
|
|||
err := c.initializeCertChecker("")
|
||||
assert.Error(t, err)
|
||||
testfile := filepath.Join(os.TempDir(), "invalidkey")
|
||||
err = ioutil.WriteFile(testfile, []byte("some bytes"), 0666)
|
||||
err = ioutil.WriteFile(testfile, []byte("some bytes"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
c.TrustedUserCAKeys = []string{testfile}
|
||||
err = c.initializeCertChecker("")
|
||||
|
|
|
@ -30,7 +30,7 @@ type scpCommand struct {
|
|||
|
||||
func (c *scpCommand) handle() error {
|
||||
common.Connections.Add(c.connection)
|
||||
defer common.Connections.Remove(c.connection)
|
||||
defer common.Connections.Remove(c.connection.GetID())
|
||||
|
||||
var err error
|
||||
destPath := c.getDestPath()
|
||||
|
@ -226,7 +226,7 @@ func (c *scpCommand) handleUploadFile(resolvedPath, filePath string, sizeToRead
|
|||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.connection.BaseConnection, cancelFn, resolvedPath, requestPath,
|
||||
common.TransferUpload, 0, initialSize, isNewFile)
|
||||
t := newTranfer(baseTransfer, w, nil, maxWriteSize)
|
||||
t := newTransfer(baseTransfer, w, nil, maxWriteSize)
|
||||
|
||||
return c.getUploadFileData(sizeToRead, t)
|
||||
}
|
||||
|
@ -484,7 +484,7 @@ func (c *scpCommand) handleDownload(filePath string) error {
|
|||
|
||||
baseTransfer := common.NewBaseTransfer(file, c.connection.BaseConnection, cancelFn, p, filePath,
|
||||
common.TransferDownload, 0, 0, false)
|
||||
t := newTranfer(baseTransfer, nil, r, 0)
|
||||
t := newTransfer(baseTransfer, nil, r, 0)
|
||||
|
||||
err = c.sendDownloadFileData(p, stat, t)
|
||||
// we need to call Close anyway and return close error if any and
|
||||
|
|
|
@ -223,7 +223,7 @@ func (c Configuration) configureLoginBanner(serverConfig *ssh.ServerConfig, conf
|
|||
return banner
|
||||
}
|
||||
} else {
|
||||
logger.WarnToConsole("unable to read login banner file: %v", err)
|
||||
logger.WarnToConsole("unable to read SFTPD login banner file: %v", err)
|
||||
logger.Warn(logSender, "", "unable to read login banner file: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -269,6 +269,7 @@ func (c Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Server
|
|||
logger.Warn(logSender, "", "failed to accept an incoming connection: %v", err)
|
||||
if _, ok := err.(*ssh.ServerAuthError); !ok {
|
||||
logger.ConnectionFailedLog("", utils.GetIPFromRemoteAddress(remoteAddr.String()), "no_auth_tryed", err.Error())
|
||||
metrics.AddNoAuthTryed()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -348,7 +349,7 @@ func (c Configuration) AcceptInboundConnection(conn net.Conn, config *ssh.Server
|
|||
|
||||
func (c Configuration) handleSftpConnection(channel ssh.Channel, connection *Connection) {
|
||||
common.Connections.Add(connection)
|
||||
defer common.Connections.Remove(connection)
|
||||
defer common.Connections.Remove(connection.GetID())
|
||||
|
||||
// Create a new handler for the currently logged in user's server.
|
||||
handler := c.createHandler(connection)
|
||||
|
|
|
@ -1405,6 +1405,8 @@ func TestPreLoginUserCreation(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(users))
|
||||
user := users[0]
|
||||
_, err = httpd.RemoveUser(user, http.StatusOK)
|
||||
assert.NoError(t, err)
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
err = dataprovider.Close()
|
||||
|
@ -4567,7 +4569,7 @@ func TestPermChmod(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
err = sftpUploadFile(testFilePath, testFileName, testFileSize, client)
|
||||
assert.NoError(t, err)
|
||||
err = client.Chmod(testFileName, 0666)
|
||||
err = client.Chmod(testFileName, os.ModePerm)
|
||||
assert.Error(t, err, "chmod without permission should not succeed")
|
||||
err = client.Remove(testFileName)
|
||||
assert.NoError(t, err)
|
||||
|
@ -7084,7 +7086,7 @@ func createTestFile(path string, size int64) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path, content, 0666)
|
||||
return ioutil.WriteFile(path, content, os.ModePerm)
|
||||
}
|
||||
|
||||
func appendToTestFile(path string, size int64) error {
|
||||
|
@ -7093,7 +7095,7 @@ func appendToTestFile(path string, size int64) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0666)
|
||||
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ func processSSHCommand(payload []byte, connection *Connection, channel ssh.Chann
|
|||
|
||||
func (c *sshCommand) handle() error {
|
||||
common.Connections.Add(c.connection)
|
||||
defer common.Connections.Remove(c.connection)
|
||||
defer common.Connections.Remove(c.connection.GetID())
|
||||
|
||||
c.connection.UpdateLastActivity()
|
||||
if utils.IsStringInSlice(c.command, sshHashCommands) {
|
||||
|
@ -354,7 +354,7 @@ func (c *sshCommand) executeSystemCommand(command systemCommand) error {
|
|||
defer stdin.Close()
|
||||
baseTransfer := common.NewBaseTransfer(nil, c.connection.BaseConnection, nil, command.fsPath, sshDestPath,
|
||||
common.TransferUpload, 0, 0, false)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, remainingQuotaSize)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, remainingQuotaSize)
|
||||
|
||||
w, e := transfer.copyFromReaderToWriter(stdin, c.connection.channel)
|
||||
c.connection.Log(logger.LevelDebug, "command: %#v, copy from remote command to sdtin ended, written: %v, "+
|
||||
|
@ -367,7 +367,7 @@ func (c *sshCommand) executeSystemCommand(command systemCommand) error {
|
|||
go func() {
|
||||
baseTransfer := common.NewBaseTransfer(nil, c.connection.BaseConnection, nil, command.fsPath, sshDestPath,
|
||||
common.TransferDownload, 0, 0, false)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, 0)
|
||||
|
||||
w, e := transfer.copyFromReaderToWriter(c.connection.channel, stdout)
|
||||
c.connection.Log(logger.LevelDebug, "command: %#v, copy from sdtout to remote command ended, written: %v err: %v",
|
||||
|
@ -381,7 +381,7 @@ func (c *sshCommand) executeSystemCommand(command systemCommand) error {
|
|||
go func() {
|
||||
baseTransfer := common.NewBaseTransfer(nil, c.connection.BaseConnection, nil, command.fsPath, sshDestPath,
|
||||
common.TransferDownload, 0, 0, false)
|
||||
transfer := newTranfer(baseTransfer, nil, nil, 0)
|
||||
transfer := newTransfer(baseTransfer, nil, nil, 0)
|
||||
|
||||
w, e := transfer.copyFromReaderToWriter(c.connection.channel.Stderr(), stderr)
|
||||
c.connection.Log(logger.LevelDebug, "command: %#v, copy from sdterr to remote command ended, written: %v err: %v",
|
||||
|
|
|
@ -32,7 +32,7 @@ type transfer struct {
|
|||
maxWriteSize int64
|
||||
}
|
||||
|
||||
func newTranfer(baseTransfer *common.BaseTransfer, pipeWriter *vfs.PipeWriter, pipeReader *pipeat.PipeReaderAt,
|
||||
func newTransfer(baseTransfer *common.BaseTransfer, pipeWriter *vfs.PipeWriter, pipeReader *pipeat.PipeReaderAt,
|
||||
maxWriteSize int64) *transfer {
|
||||
var writer writerAtCloser
|
||||
var reader readerAtCloser
|
||||
|
|
14
sftpgo.json
14
sftpgo.json
|
@ -30,6 +30,20 @@
|
|||
],
|
||||
"keyboard_interactive_auth_hook": ""
|
||||
},
|
||||
"ftpd": {
|
||||
"bind_port": 0,
|
||||
"bind_address": "",
|
||||
"banner": "",
|
||||
"banner_file": "",
|
||||
"active_transfers_port_non_20": false,
|
||||
"force_passive_ip": "",
|
||||
"passive_port_range": {
|
||||
"start": 50000,
|
||||
"end": 50100
|
||||
},
|
||||
"certificate_file": "",
|
||||
"certificate_key_file": ""
|
||||
},
|
||||
"data_provider": {
|
||||
"driver": "sqlite",
|
||||
"name": "sftpgo.db",
|
||||
|
|
|
@ -71,7 +71,7 @@ func (OsFs) Create(name string, flag int) (*os.File, *PipeWriter, func(), error)
|
|||
if flag == 0 {
|
||||
f, err = os.Create(name)
|
||||
} else {
|
||||
f, err = os.OpenFile(name, flag, 0666)
|
||||
f, err = os.OpenFile(name, flag, os.ModePerm)
|
||||
}
|
||||
return f, nil, nil, err
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue