sftpd: add SSH_FXP_FSETSTAT support
This change will fix file editing from sshfs, we need this patch https://github.com/pkg/sftp/pull/373 for pkg/sftp to support this feature
This commit is contained in:
parent
933427310d
commit
f41ce6619f
16 changed files with 222 additions and 45 deletions
|
@ -37,6 +37,7 @@ const (
|
|||
chownLogSender = "Chown"
|
||||
chmodLogSender = "Chmod"
|
||||
chtimesLogSender = "Chtimes"
|
||||
truncateLogSender = "Truncate"
|
||||
operationDownload = "download"
|
||||
operationUpload = "upload"
|
||||
operationDelete = "delete"
|
||||
|
@ -52,6 +53,7 @@ const (
|
|||
StatAttrUIDGID = 1
|
||||
StatAttrPerms = 2
|
||||
StatAttrTimes = 4
|
||||
StatAttrSize = 8
|
||||
)
|
||||
|
||||
// Transfer types
|
||||
|
@ -85,6 +87,8 @@ var (
|
|||
ErrQuotaExceeded = errors.New("denying write due to space limit")
|
||||
ErrSkipPermissionsCheck = errors.New("permission check skipped")
|
||||
ErrConnectionDenied = errors.New("You are not allowed to connect")
|
||||
errNoTransfer = errors.New("requested transfer not found")
|
||||
errTransferMismatch = errors.New("transfer mismatch")
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -141,6 +145,7 @@ type ActiveTransfer interface {
|
|||
GetVirtualPath() string
|
||||
GetStartTime() time.Time
|
||||
SignalClose()
|
||||
Truncate(fsPath string, size int64) error
|
||||
}
|
||||
|
||||
// ActiveConnection defines the interface for the current active connections
|
||||
|
@ -168,6 +173,7 @@ type StatAttributes struct {
|
|||
UID int
|
||||
GID int
|
||||
Flags int
|
||||
Size int64
|
||||
}
|
||||
|
||||
// ConnectionTransfer defines the trasfer details to expose
|
||||
|
|
|
@ -172,6 +172,20 @@ func (c *BaseConnection) SignalTransfersAbort() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *BaseConnection) truncateOpenHandle(fsPath string, size int64) error {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
for _, t := range c.activeTransfers {
|
||||
err := t.Truncate(fsPath, size)
|
||||
if err != errTransferMismatch {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return errNoTransfer
|
||||
}
|
||||
|
||||
// ListDir reads the directory named by fsPath and returns a list of directory entries
|
||||
func (c *BaseConnection) ListDir(fsPath, virtualPath string) ([]os.FileInfo, error) {
|
||||
if !c.User.HasPerm(dataprovider.PermListItems, virtualPath) {
|
||||
|
@ -200,7 +214,7 @@ func (c *BaseConnection) CreateDir(fsPath, virtualPath string) error {
|
|||
}
|
||||
vfs.SetPathPermissions(c.Fs, fsPath, c.User.GetUID(), c.User.GetGID())
|
||||
|
||||
logger.CommandLog(mkdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
|
||||
logger.CommandLog(mkdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -233,7 +247,7 @@ func (c *BaseConnection) RemoveFile(fsPath, virtualPath string, info os.FileInfo
|
|||
}
|
||||
}
|
||||
|
||||
logger.CommandLog(removeLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
|
||||
logger.CommandLog(removeLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1)
|
||||
if info.Mode()&os.ModeSymlink != os.ModeSymlink {
|
||||
vfolder, err := c.User.GetVirtualFolderForPath(path.Dir(virtualPath))
|
||||
if err == nil {
|
||||
|
@ -302,7 +316,7 @@ func (c *BaseConnection) RemoveDir(fsPath, virtualPath string) error {
|
|||
return c.GetFsError(err)
|
||||
}
|
||||
|
||||
logger.CommandLog(rmdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
|
||||
logger.CommandLog(rmdirLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -363,7 +377,7 @@ func (c *BaseConnection) Rename(fsSourcePath, fsTargetPath, virtualSourcePath, v
|
|||
c.updateQuotaAfterRename(virtualSourcePath, virtualTargetPath, fsTargetPath, initialSize) //nolint:errcheck
|
||||
}
|
||||
logger.CommandLog(renameLogSender, fsSourcePath, fsTargetPath, c.User.Username, "", c.ID, c.protocol, -1, -1,
|
||||
"", "", "")
|
||||
"", "", "", -1)
|
||||
action := newActionNotification(&c.User, operationRename, fsSourcePath, fsTargetPath, "", c.protocol, 0, nil)
|
||||
// the returned error is used in test cases only, we already log the error inside action.execute
|
||||
go action.execute() //nolint:errcheck
|
||||
|
@ -400,21 +414,27 @@ func (c *BaseConnection) CreateSymlink(fsSourcePath, fsTargetPath, virtualSource
|
|||
c.Log(logger.LevelWarn, "failed to create symlink %#v -> %#v: %+v", fsSourcePath, fsTargetPath, err)
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
logger.CommandLog(symlinkLogSender, fsSourcePath, fsTargetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "")
|
||||
logger.CommandLog(symlinkLogSender, fsSourcePath, fsTargetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", -1)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *BaseConnection) getPathForSetStatPerms(fsPath, virtualPath string) string {
|
||||
pathForPerms := virtualPath
|
||||
if fi, err := c.Fs.Lstat(fsPath); err == nil {
|
||||
if fi.IsDir() {
|
||||
pathForPerms = path.Dir(virtualPath)
|
||||
}
|
||||
}
|
||||
return pathForPerms
|
||||
}
|
||||
|
||||
// SetStat set StatAttributes for the specified fsPath
|
||||
func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAttributes) error {
|
||||
if Config.SetstatMode == 1 {
|
||||
return nil
|
||||
}
|
||||
pathForPerms := virtualPath
|
||||
if fi, err := c.Fs.Lstat(fsPath); err == nil {
|
||||
if fi.IsDir() {
|
||||
pathForPerms = path.Dir(virtualPath)
|
||||
}
|
||||
}
|
||||
pathForPerms := c.getPathForSetStatPerms(fsPath, virtualPath)
|
||||
|
||||
if attributes.Flags&StatAttrPerms != 0 {
|
||||
if !c.User.HasPerm(dataprovider.PermChmod, pathForPerms) {
|
||||
return c.GetPermissionDeniedError()
|
||||
|
@ -424,7 +444,7 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
|
|||
return c.GetFsError(err)
|
||||
}
|
||||
logger.CommandLog(chmodLogSender, fsPath, "", c.User.Username, attributes.Mode.String(), c.ID, c.protocol,
|
||||
-1, -1, "", "", "")
|
||||
-1, -1, "", "", "", -1)
|
||||
}
|
||||
|
||||
if attributes.Flags&StatAttrUIDGID != 0 {
|
||||
|
@ -437,12 +457,12 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
|
|||
return c.GetFsError(err)
|
||||
}
|
||||
logger.CommandLog(chownLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, attributes.UID, attributes.GID,
|
||||
"", "", "")
|
||||
"", "", "", -1)
|
||||
}
|
||||
|
||||
if attributes.Flags&StatAttrTimes != 0 {
|
||||
if !c.User.HasPerm(dataprovider.PermChtimes, pathForPerms) {
|
||||
return sftp.ErrSSHFxPermissionDenied
|
||||
return c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
if err := c.Fs.Chtimes(fsPath, attributes.Atime, attributes.Mtime); err != nil {
|
||||
|
@ -453,12 +473,37 @@ func (c *BaseConnection) SetStat(fsPath, virtualPath string, attributes *StatAtt
|
|||
accessTimeString := attributes.Atime.Format(chtimesFormat)
|
||||
modificationTimeString := attributes.Mtime.Format(chtimesFormat)
|
||||
logger.CommandLog(chtimesLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1,
|
||||
accessTimeString, modificationTimeString, "")
|
||||
accessTimeString, modificationTimeString, "", -1)
|
||||
}
|
||||
|
||||
if attributes.Flags&StatAttrSize != 0 {
|
||||
if !c.User.HasPerm(dataprovider.PermOverwrite, pathForPerms) {
|
||||
return c.GetPermissionDeniedError()
|
||||
}
|
||||
|
||||
if err := c.truncateFile(fsPath, attributes.Size); err != nil {
|
||||
c.Log(logger.LevelWarn, "failed to truncate path %#v, size: %v, err: %+v", fsPath, attributes.Size, err)
|
||||
return c.GetFsError(err)
|
||||
}
|
||||
logger.CommandLog(truncateLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "", attributes.Size)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *BaseConnection) truncateFile(fsPath string, size int64) error {
|
||||
// check first if we have an open transfer for the given path and try to truncate the file already opened
|
||||
// if we found no transfer we truncate by path.
|
||||
// pkg/sftp should expose an optional interface and call truncate directly on the opened handle ...
|
||||
// If we try to truncate by path an already opened file we get an error on Windows
|
||||
err := c.truncateOpenHandle(fsPath, size)
|
||||
if err == errNoTransfer {
|
||||
c.Log(logger.LevelDebug, "file path %#v not found in active transfers, execute trucate by path", fsPath)
|
||||
err = c.Fs.Truncate(fsPath, size)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *BaseConnection) checkRecursiveRenameDirPermissions(sourcePath, targetPath string) error {
|
||||
dstPerms := []string{
|
||||
dataprovider.PermCreateDirs,
|
||||
|
|
|
@ -529,6 +529,30 @@ func TestSetStat(t *testing.T) {
|
|||
Flags: StatAttrTimes,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
// truncate
|
||||
err = c.SetStat(filepath.Join(user.GetHomeDir(), "missing"), "/missing", &StatAttributes{
|
||||
Size: 1,
|
||||
Flags: StatAttrSize,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
err = c.SetStat(filepath.Join(dir3, "afile.txt"), "/dir3/afile.txt", &StatAttributes{
|
||||
Size: 1,
|
||||
Flags: StatAttrSize,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
filePath := filepath.Join(user.GetHomeDir(), "afile.txt")
|
||||
err = ioutil.WriteFile(filePath, []byte("hello"), os.ModePerm)
|
||||
assert.NoError(t, err)
|
||||
err = c.SetStat(filePath, "/afile.txt", &StatAttributes{
|
||||
Flags: StatAttrSize,
|
||||
Size: 1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
fi, err := os.Stat(filePath)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, int64(1), fi.Size())
|
||||
}
|
||||
|
||||
err = os.RemoveAll(user.GetHomeDir())
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -108,6 +108,22 @@ func (t *BaseTransfer) SetCancelFn(cancelFn func()) {
|
|||
t.cancelFn = cancelFn
|
||||
}
|
||||
|
||||
// Truncate changes the size of the opened file.
|
||||
// Supported for local fs only
|
||||
func (t *BaseTransfer) Truncate(fsPath string, size int64) error {
|
||||
if fsPath == t.GetFsPath() {
|
||||
if t.File != nil {
|
||||
return t.File.Truncate(size)
|
||||
}
|
||||
if size == 0 {
|
||||
// for cloud providers the file is always truncated to zero, we don't support append/resume for uploads
|
||||
return nil
|
||||
}
|
||||
return ErrOpUnsupported
|
||||
}
|
||||
return errTransferMismatch
|
||||
}
|
||||
|
||||
// TransferError is called if there is an unexpected error.
|
||||
// For example network or client issues
|
||||
func (t *BaseTransfer) TransferError(err error) {
|
||||
|
|
|
@ -81,6 +81,53 @@ func TestTransferThrottling(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestTruncate(t *testing.T) {
|
||||
testFile := filepath.Join(os.TempDir(), "transfer_test_file")
|
||||
fs := vfs.NewOsFs("123", os.TempDir(), nil)
|
||||
u := dataprovider.User{
|
||||
Username: "user",
|
||||
HomeDir: os.TempDir(),
|
||||
}
|
||||
u.Permissions = make(map[string][]string)
|
||||
u.Permissions["/"] = []string{dataprovider.PermAny}
|
||||
file, err := os.Create(testFile)
|
||||
if !assert.NoError(t, err) {
|
||||
assert.FailNow(t, "unable to open test file")
|
||||
}
|
||||
_, err = file.Write([]byte("hello"))
|
||||
assert.NoError(t, err)
|
||||
conn := NewBaseConnection(fs.ConnectionID(), ProtocolSFTP, u, fs)
|
||||
transfer := NewBaseTransfer(file, conn, nil, testFile, "/transfer_test_file", TransferUpload, 0, 0, true)
|
||||
|
||||
err = conn.SetStat(testFile, "/transfer_test_file", &StatAttributes{
|
||||
Size: 2,
|
||||
Flags: StatAttrSize,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
err = transfer.Close()
|
||||
assert.NoError(t, err)
|
||||
fi, err := os.Stat(testFile)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, int64(2), fi.Size())
|
||||
}
|
||||
|
||||
transfer = NewBaseTransfer(nil, conn, nil, testFile, "", TransferUpload, 0, 0, true)
|
||||
err = transfer.Truncate("mismatch", 0)
|
||||
assert.EqualError(t, err, errTransferMismatch.Error())
|
||||
err = transfer.Truncate(testFile, 0)
|
||||
assert.NoError(t, err)
|
||||
err = transfer.Truncate(testFile, 1)
|
||||
assert.EqualError(t, err, ErrOpUnsupported.Error())
|
||||
|
||||
err = transfer.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = os.Remove(testFile)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, conn.GetTransfers(), 0)
|
||||
}
|
||||
|
||||
func TestTransferErrors(t *testing.T) {
|
||||
isCancelled := false
|
||||
cancelFn := func() {
|
||||
|
|
|
@ -20,7 +20,7 @@ The logs can be divided into the following categories:
|
|||
- `connection_id` string. Unique connection identifier
|
||||
- `protocol` string. `SFTP` or `SCP`
|
||||
- **"command logs"**, SFTP/SCP command logs:
|
||||
- `sender` string. `Rename`, `Rmdir`, `Mkdir`, `Symlink`, `Remove`, `Chmod`, `Chown`, `Chtimes`, `SSHCommand`
|
||||
- `sender` string. `Rename`, `Rmdir`, `Mkdir`, `Symlink`, `Remove`, `Chmod`, `Chown`, `Chtimes`, `Truncate`, `SSHCommand`
|
||||
- `level` string
|
||||
- `username`, string
|
||||
- `file_path` string
|
||||
|
@ -30,6 +30,7 @@ The logs can be divided into the following categories:
|
|||
- `gid` integer. Valid for sender `Chown` otherwise -1
|
||||
- `access_time` datetime as YYYY-MM-DDTHH:MM:SS. Valid for sender `Chtimes` otherwise empty
|
||||
- `modification_time` datetime as YYYY-MM-DDTHH:MM:SS. Valid for sender `Chtimes` otherwise empty
|
||||
- `size` int64. Valid for sender `Truncate` otherwise -1
|
||||
- `ssh_command`, string. Valid for sender `SSHCommand` otherwise empty
|
||||
- `connection_id` string. Unique connection identifier
|
||||
- `protocol` string. `SFTP`, `SCP` or `SSH`
|
||||
|
|
16
go.mod
16
go.mod
|
@ -3,10 +3,10 @@ module github.com/drakkan/sftpgo
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.63.0 // indirect
|
||||
cloud.google.com/go v0.64.0 // indirect
|
||||
cloud.google.com/go/storage v1.10.0
|
||||
github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b
|
||||
github.com/aws/aws-sdk-go v1.34.5
|
||||
github.com/aws/aws-sdk-go v1.34.8
|
||||
github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d
|
||||
github.com/fclairamb/ftpserverlib v0.8.1-0.20200729230026-7f0ab9d81bb6
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
|
@ -17,7 +17,7 @@ require (
|
|||
github.com/grandcat/zeroconf v1.0.0
|
||||
github.com/jlaffaye/ftp v0.0.0-20200720194710-13949d38913e
|
||||
github.com/lib/pq v1.8.0
|
||||
github.com/mattn/go-sqlite3 v1.14.0
|
||||
github.com/mattn/go-sqlite3 v1.14.1
|
||||
github.com/miekg/dns v1.1.31 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/nathanaelle/password/v2 v2.0.1
|
||||
|
@ -26,7 +26,7 @@ require (
|
|||
github.com/pires/go-proxyproto v0.1.3
|
||||
github.com/pkg/sftp v1.11.1-0.20200819110714-3ee8d0ba91c0
|
||||
github.com/prometheus/client_golang v1.7.1
|
||||
github.com/prometheus/common v0.12.0 // indirect
|
||||
github.com/prometheus/common v0.13.0 // indirect
|
||||
github.com/rs/cors v1.7.1-0.20200626170627-8b4a00bd362b
|
||||
github.com/rs/xid v1.2.1
|
||||
github.com/rs/zerolog v1.19.0
|
||||
|
@ -41,17 +41,17 @@ require (
|
|||
go.etcd.io/bbolt v1.3.5
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc
|
||||
golang.org/x/sys v0.0.0-20200819091447-39769834ee22
|
||||
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d // indirect
|
||||
golang.org/x/sys v0.0.0-20200819171115-d785dc25833f
|
||||
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac // indirect
|
||||
google.golang.org/api v0.30.0
|
||||
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 // indirect
|
||||
gopkg.in/ini.v1 v1.58.0 // indirect
|
||||
gopkg.in/ini.v1 v1.60.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/fclairamb/ftpserverlib => github.com/drakkan/ftpserverlib v0.0.0-20200814103339-511fcfd63dfe
|
||||
github.com/jlaffaye/ftp => github.com/drakkan/ftp v0.0.0-20200730125632-b21eac28818c
|
||||
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20200820090459-de8eb908f763
|
||||
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20200731130417-7674a892f9b1
|
||||
golang.org/x/net => github.com/drakkan/net v0.0.0-20200807161257-daa5cda5ae27
|
||||
)
|
||||
|
|
34
go.sum
34
go.sum
|
@ -12,8 +12,8 @@ cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bP
|
|||
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
|
||||
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
|
||||
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
|
||||
cloud.google.com/go v0.63.0 h1:A+DfAZQ/eWca7gvu42CS6FNSDX4R8cghF+XfWLn4R6g=
|
||||
cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg=
|
||||
cloud.google.com/go v0.64.0 h1:xVP3LPvMjGT4J0a55y02Gw5y/dkY/rxGz58sfK1jqIo=
|
||||
cloud.google.com/go v0.64.0/go.mod h1:xfORb36jGvE+6EexW71nMEtL025s3x6xvuYUKM4JLv4=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
|
@ -60,8 +60,8 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
|
|||
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.34.5 h1:FwubVVX9u+kW9qDCjVzyWOdsL+W5wPq683wMk2R2GXk=
|
||||
github.com/aws/aws-sdk-go v1.34.5/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
|
||||
github.com/aws/aws-sdk-go v1.34.8 h1:GDfVeXG8XQDbpOeAj7415F8qCQZwvY/k/fj+HBqUnBA=
|
||||
github.com/aws/aws-sdk-go v1.34.8/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=
|
||||
|
@ -111,6 +111,8 @@ github.com/drakkan/ftpserverlib v0.0.0-20200814103339-511fcfd63dfe h1:yiliFcCxu5
|
|||
github.com/drakkan/ftpserverlib v0.0.0-20200814103339-511fcfd63dfe/go.mod h1:ShLpSOXbtoMDYxTb5eRs9wDBfkQ7VINYghclB4P2z4E=
|
||||
github.com/drakkan/net v0.0.0-20200807161257-daa5cda5ae27 h1:hh14GxmE3PMKL+4nvMmX7O8CUtbD/52IKDjbMTYX7IY=
|
||||
github.com/drakkan/net v0.0.0-20200807161257-daa5cda5ae27/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
github.com/drakkan/sftp v0.0.0-20200820090459-de8eb908f763 h1:2s1IvldI3h8S4FAF7K9uAB05/L0k8TlYF60GIEc2LUY=
|
||||
github.com/drakkan/sftp v0.0.0-20200820090459-de8eb908f763/go.mod h1:i24A96cQ6ZvWut9G/Uv3LvC4u3VebGsBR5JFvPyChLc=
|
||||
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=
|
||||
|
@ -291,8 +293,8 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
|||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
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/mattn/go-sqlite3 v1.14.1 h1:AHx9Ra40wIzl+GelgX2X6AWxmT5tfxhI1PL0523HcSw=
|
||||
github.com/mattn/go-sqlite3 v1.14.1/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
|
@ -364,9 +366,6 @@ github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||
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.20200819110714-3ee8d0ba91c0 h1:3LRfXAQrcWKQ0LQZ9wjp9wgzYRsxFKVjP5zZIEv9NFY=
|
||||
github.com/pkg/sftp v1.11.1-0.20200819110714-3ee8d0ba91c0/go.mod h1:i24A96cQ6ZvWut9G/Uv3LvC4u3VebGsBR5JFvPyChLc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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=
|
||||
|
@ -390,8 +389,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
|
|||
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/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.12.0 h1:mj4ewtVukAfkS37JU7IXPJPr7zwLEjwgWO6nZo8ROvk=
|
||||
github.com/prometheus/common v0.12.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||
github.com/prometheus/common v0.13.0 h1:vJlpe9wPgDRM1Z+7Wj3zUUjY1nr6/1jNKyl7llliccg=
|
||||
github.com/prometheus/common v0.13.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
|
||||
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=
|
||||
|
@ -578,8 +577,8 @@ golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200722175500-76b94024e4b6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200819091447-39769834ee22 h1:YUxhQGxYV280Da2a0XZiHblyJZN6NuXS1f4dahsm0SM=
|
||||
golang.org/x/sys v0.0.0-20200819091447-39769834ee22/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200819171115-d785dc25833f h1:KJuwZVtZBVzDmEDtB2zro9CXkD9O0dpCv4o2LHbQIAw=
|
||||
golang.org/x/sys v0.0.0-20200819171115-d785dc25833f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -638,8 +637,8 @@ golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roY
|
|||
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200814230902-9882f1d1823d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200817023811-d00afeaade8f/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||
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=
|
||||
|
@ -698,7 +697,6 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY
|
|||
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70 h1:wboULUXGF3c5qdUnKp+6gLAccE6PRpa/czkYvQ4UXv8=
|
||||
google.golang.org/genproto v0.0.0-20200815001618-f69a88009b70/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
|
@ -742,8 +740,8 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
|||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.58.0 h1:VdDvTzv/005R8vEFyQ56bpEnOKTNPbpJhL0VCohxlQw=
|
||||
gopkg.in/ini.v1 v1.58.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.60.0 h1:P5ZzC7RJO04094NJYlEnBdFK2wwmnCAy/+7sAzvWs60=
|
||||
gopkg.in/ini.v1 v1.60.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=
|
||||
|
|
|
@ -161,7 +161,8 @@ func TransferLog(operation string, path string, elapsed int64, size int64, user
|
|||
}
|
||||
|
||||
// CommandLog logs an SFTP/SCP/SSH command
|
||||
func CommandLog(command, path, target, user, fileMode, connectionID, protocol string, uid, gid int, atime, mtime, sshCommand string) {
|
||||
func CommandLog(command, path, target, user, fileMode, connectionID, protocol string, uid, gid int, atime, mtime,
|
||||
sshCommand string, size int64) {
|
||||
logger.Info().
|
||||
Timestamp().
|
||||
Str("sender", command).
|
||||
|
@ -173,6 +174,7 @@ func CommandLog(command, path, target, user, fileMode, connectionID, protocol st
|
|||
Int("gid", gid).
|
||||
Str("access_time", atime).
|
||||
Str("modification_time", atime).
|
||||
Int64("size", size).
|
||||
Str("ssh_command", sshCommand).
|
||||
Str("connection_id", connectionID).
|
||||
Str("protocol", protocol).
|
||||
|
|
|
@ -232,6 +232,10 @@ func (c *Connection) handleSFTPSetstat(filePath string, request *sftp.Request) e
|
|||
attrs.Atime = time.Unix(int64(request.Attributes().Atime), 0)
|
||||
attrs.Mtime = time.Unix(int64(request.Attributes().Mtime), 0)
|
||||
}
|
||||
if request.AttrFlags().Size {
|
||||
attrs.Flags |= common.StatAttrSize
|
||||
attrs.Size = int64(request.Attributes().Size)
|
||||
}
|
||||
|
||||
return c.SetStat(filePath, request.Filepath, &attrs)
|
||||
}
|
||||
|
|
|
@ -657,8 +657,22 @@ func TestStat(t *testing.T) {
|
|||
assert.Equal(t, newPerm, newFi.Mode().Perm())
|
||||
_, err = client.ReadLink(testFileName)
|
||||
assert.Error(t, err, "readlink is not supported and must fail")
|
||||
err = client.Truncate(testFileName, 0)
|
||||
newPerm = os.FileMode(0666)
|
||||
err = client.Chmod(testFileName, newPerm)
|
||||
assert.NoError(t, err)
|
||||
err = client.Truncate(testFileName, 100)
|
||||
assert.NoError(t, err)
|
||||
fi, err := client.Stat(testFileName)
|
||||
if assert.NoError(t, err) {
|
||||
assert.Equal(t, int64(100), fi.Size())
|
||||
}
|
||||
f, err := client.OpenFile(testFileName, os.O_WRONLY)
|
||||
if assert.NoError(t, err) {
|
||||
err = f.Truncate(5)
|
||||
assert.NoError(t, err)
|
||||
err = f.Close()
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
err = os.Remove(testFilePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -704,7 +704,7 @@ func (c *sshCommand) sendExitStatus(err error) {
|
|||
c.command, c.args, c.connection.User.Username, err)
|
||||
} else {
|
||||
logger.CommandLog(sshCommandLogSender, cmdPath, targetPath, c.connection.User.Username, "", c.connection.ID,
|
||||
common.ProtocolSSH, -1, -1, "", "", c.connection.command)
|
||||
common.ProtocolSSH, -1, -1, "", "", c.connection.command, -1)
|
||||
}
|
||||
exitStatus := sshSubsystemExitStatus{
|
||||
Status: status,
|
||||
|
|
|
@ -298,6 +298,13 @@ func (GCSFs) Chtimes(name string, atime, mtime time.Time) error {
|
|||
return errors.New("403 chtimes is not supported")
|
||||
}
|
||||
|
||||
// Truncate changes the size of the named file.
|
||||
// Truncate by path is not supported, while truncating an opened
|
||||
// file is handled inside base transfer
|
||||
func (GCSFs) Truncate(name string, size int64) error {
|
||||
return errors.New("403 truncate is not supported")
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns
|
||||
// a list of directory entries.
|
||||
func (fs GCSFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
|
|
|
@ -131,6 +131,11 @@ func (OsFs) Chtimes(name string, atime, mtime time.Time) error {
|
|||
return os.Chtimes(name, atime, mtime)
|
||||
}
|
||||
|
||||
// Truncate changes the size of the named file
|
||||
func (OsFs) Truncate(name string, size int64) error {
|
||||
return os.Truncate(name, size)
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns
|
||||
// a list of directory entries.
|
||||
func (OsFs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
|
|
|
@ -330,6 +330,13 @@ func (S3Fs) Chtimes(name string, atime, mtime time.Time) error {
|
|||
return errors.New("403 chtimes is not supported")
|
||||
}
|
||||
|
||||
// Truncate changes the size of the named file.
|
||||
// Truncate by path is not supported, while truncating an opened
|
||||
// file is handled inside base transfer
|
||||
func (S3Fs) Truncate(name string, size int64) error {
|
||||
return errors.New("403 truncate is not supported")
|
||||
}
|
||||
|
||||
// ReadDir reads the directory named by dirname and returns
|
||||
// a list of directory entries.
|
||||
func (fs S3Fs) ReadDir(dirname string) ([]os.FileInfo, error) {
|
||||
|
|
|
@ -31,6 +31,7 @@ type Fs interface {
|
|||
Chown(name string, uid int, gid int) error
|
||||
Chmod(name string, mode os.FileMode) error
|
||||
Chtimes(name string, atime, mtime time.Time) error
|
||||
Truncate(name string, size int64) error
|
||||
ReadDir(dirname string) ([]os.FileInfo, error)
|
||||
IsUploadResumeSupported() bool
|
||||
IsAtomicUploadSupported() bool
|
||||
|
|
Loading…
Reference in a new issue