feat(develop): sshcmd vendor

This commit is contained in:
cuisongliu
2020-04-04 15:19:06 +08:00
parent c6f6025624
commit 2598691462
239 changed files with 37600 additions and 48660 deletions
-202
View File
@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-37
View File
@@ -1,37 +0,0 @@
package cmd
import (
"bytes"
"github.com/wonderivan/logger"
"os"
"os/exec"
)
//Cmd is exec on os ,no return
func Cmd(name string, arg ...string) {
logger.Info("[os]exec cmd is : ", name, arg)
cmd := exec.Command(name, arg[:]...)
cmd.Stdin = os.Stdin
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
err := cmd.Run()
if err != nil {
logger.Error("[os]os call error.", err)
}
}
//CmdToString is exec on os , return result
func CmdToString(name string, arg ...string) string {
logger.Info("[os]exec cmd is : ", name, arg)
cmd := exec.Command(name, arg[:]...)
cmd.Stdin = os.Stdin
var b bytes.Buffer
cmd.Stdout = &b
cmd.Stderr = &b
err := cmd.Run()
if err != nil {
logger.Error("[os]os call error.", err)
return ""
}
return b.String()
}
-58
View File
@@ -1,58 +0,0 @@
package cmd
import (
"fmt"
"github.com/wonderivan/logger"
"path"
"strconv"
"strings"
"time"
)
const oneMBByte = 1024 * 1024
//WatchFileSize is
func LoggerFileSize(filename string, size int) {
t := time.NewTicker(3 * time.Second) //every 3s check file
defer t.Stop()
for {
select {
case <-t.C:
length := CmdToString("/bin/sh", "-c", "ls -l "+filename+" | awk '{print $5}'", "")
length = strings.Replace(length, "\n", "", -1)
length = strings.Replace(length, "\r", "", -1)
lengthByte, _ := strconv.Atoi(length)
if lengthByte == size {
t.Stop()
}
lengthFloat := float64(lengthByte)
value, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", lengthFloat/oneMBByte), 64)
logger.Alert("[os][%s]transfer total size is: %.2f%s", filename, value, "MB")
}
}
}
//RemoteFilExist is
func IsFilExist(filepath string) bool {
// if remote file is
// ls -l | grep aa | wc -l
fileName := path.Base(filepath) // aa
fileDirName := path.Dir(filepath)
fileCommand := fmt.Sprintf("ls -l %s | grep %s | wc -l", fileDirName, fileName)
data := strings.Replace(CmdToString("/bin/sh", "-c", fileCommand), "\r", "", -1)
data = strings.Replace(data, "\n", "", -1)
count, err := strconv.Atoi(strings.TrimSpace(data))
defer func() {
if r := recover(); r != nil {
logger.Error("[os][%s]RemoteFilExist:%s", filepath, err)
}
}()
if err != nil {
panic(1)
}
if count == 0 {
return false
} else {
return true
}
}
-22
View File
@@ -1,22 +0,0 @@
package md5sum
import (
"fmt"
"github.com/wonderivan/logger"
"os/exec"
"strings"
)
func FromLocal(localPath string) string {
cmd := fmt.Sprintf("md5sum %s | cut -d\" \" -f1", localPath)
c := exec.Command("sh", "-c", cmd)
out, err := c.CombinedOutput()
if err != nil {
logger.Error(err)
}
md5 := string(out)
md5 = strings.ReplaceAll(md5, "\n", "")
md5 = strings.ReplaceAll(md5, "\r", "")
return md5
}
-90
View File
@@ -1,90 +0,0 @@
package sshutil
import (
"fmt"
"github.com/wonderivan/logger"
"golang.org/x/crypto/ssh"
"io/ioutil"
"net"
"os"
"strings"
"time"
)
/**
这里主要是做连接ssh操作的
*/
func (ss *SSH) Connect(host string) (*ssh.Session, error) {
auth := []ssh.AuthMethod{ss.sshAuthMethod(ss.Password, ss.PkFile)}
config := ssh.Config{
Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"},
}
DefaultTimeout := time.Duration(1) * time.Minute
if ss.Timeout == nil {
ss.Timeout = &DefaultTimeout
}
clientConfig := &ssh.ClientConfig{
User: ss.User,
Auth: auth,
Timeout: *ss.Timeout,
Config: config,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
addr := ss.addrReformat(host)
client, err := ssh.Dial("tcp", addr, clientConfig)
if err != nil {
return nil, err
}
session, err := client.NewSession()
if err != nil {
return nil, err
}
modes := ssh.TerminalModes{
ssh.ECHO: 0, // disable echoing
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}
if err := session.RequestPty("xterm", 80, 40, modes); err != nil {
return nil, err
}
return session, nil
}
func (ss *SSH) sshAuthMethod(passwd, pkFile string) ssh.AuthMethod {
var am ssh.AuthMethod
if passwd != "" {
am = ssh.Password(passwd)
} else {
pkData := ss.readFile(pkFile)
pk, err := ssh.ParsePrivateKey([]byte(pkData))
if err != nil {
logger.Error(err)
os.Exit(1)
}
am = ssh.PublicKeys(pk)
}
return am
}
func (ss *SSH) readFile(name string) string {
content, err := ioutil.ReadFile(name)
if err != nil {
logger.Error("[globals] read file err is : %s", err)
os.Exit(1)
}
return string(content)
}
func (ss *SSH) addrReformat(host string) string {
if strings.Index(host, ":") == -1 {
host = fmt.Sprintf("%s:22", host)
}
return host
}
-123
View File
@@ -1,123 +0,0 @@
package sshutil
import (
"fmt"
"github.com/cuisongliu/sshcmd/pkg/md5sum"
"github.com/pkg/sftp"
"github.com/wonderivan/logger"
"golang.org/x/crypto/ssh"
"net"
"os"
"strings"
"time"
)
//Copy is
func (ss *SSH) CopyForMD5(host, localFilePath, remoteFilePath, md5 string) bool {
//如果有md5则可以验证
//如果没有md5则拿到本地数据后验证
if md5 == "" {
md5 = md5sum.FromLocal(localFilePath)
}
logger.Debug("[ssh]source file md5 value is %s", md5)
ss.Copy(host, localFilePath, remoteFilePath)
remoteMD5 := ss.Md5Sum(host, remoteFilePath)
logger.Debug("[ssh]host: %s , remote md5: %s", host, remoteMD5)
remoteMD5 = strings.TrimSpace(remoteMD5)
md5 = strings.TrimSpace(md5)
if remoteMD5 == md5 {
logger.Info("[ssh]md5 validate true")
return true
}
logger.Error("[ssh]md5 validate false")
return false
}
func (ss *SSH) Md5Sum(host, remoteFilePath string) string {
cmd := fmt.Sprintf("md5sum %s | cut -d\" \" -f1", remoteFilePath)
remoteMD5 := ss.CmdToString(host, cmd, "")
return remoteMD5
}
//Copy is
func (ss *SSH) Copy(host, localFilePath, remoteFilePath string) {
sftpClient, err := ss.sftpConnect(host)
defer func() {
if r := recover(); r != nil {
logger.Error("[ssh][%s]scpCopy: %s", host, err)
}
}()
if err != nil {
panic(1)
}
defer sftpClient.Close()
srcFile, err := os.Open(localFilePath)
defer func() {
if r := recover(); r != nil {
logger.Error("[ssh][%s]scpCopy: %s", host, err)
}
}()
if err != nil {
panic(1)
}
defer srcFile.Close()
dstFile, err := sftpClient.Create(remoteFilePath)
defer func() {
if r := recover(); r != nil {
logger.Error("[ssh][%s]scpCopy: %s", host, err)
}
}()
if err != nil {
panic(1)
}
defer dstFile.Close()
buf := make([]byte, 100*oneMBByte) //100mb
totalMB := 0
for {
n, _ := srcFile.Read(buf)
if n == 0 {
break
}
length, _ := dstFile.Write(buf[0:n])
totalMB += length / oneMBByte
logger.Alert("[ssh][%s]transfer total size is: %d%s", host, totalMB, "MB")
}
}
//SftpConnect is
func (ss *SSH) sftpConnect(host string) (*sftp.Client, error) {
var (
auth []ssh.AuthMethod
addr string
clientConfig *ssh.ClientConfig
sshClient *ssh.Client
sftpClient *sftp.Client
err error
)
// get auth method
auth = make([]ssh.AuthMethod, 0)
auth = append(auth, ss.sshAuthMethod(ss.Password, ss.PkFile))
clientConfig = &ssh.ClientConfig{
User: ss.User,
Auth: auth,
Timeout: 30 * time.Second,
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
// connet to ssh
addr = ss.addrReformat(host)
if sshClient, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
return nil, err
}
// create sftp client
if sftpClient, err = sftp.NewClient(sshClient); err != nil {
return nil, err
}
return sftpClient, nil
}
-77
View File
@@ -1,77 +0,0 @@
package sshutil
import (
"bufio"
"github.com/wonderivan/logger"
"strings"
)
//Cmd is in host exec cmd
func (ss *SSH) Cmd(host string, cmd string) []byte {
logger.Info("[ssh][%s]exec cmd is : %s", host, cmd)
session, err := ss.Connect(host)
defer func() {
if r := recover(); r != nil {
logger.Error("[ssh][%s]Error create ssh session failed,%s", host, err)
}
}()
if err != nil {
panic(1)
}
defer session.Close()
b, err := session.CombinedOutput(cmd)
logger.Debug("[ssh][%s]command result is: %s", host, string(b))
defer func() {
if r := recover(); r != nil {
logger.Error("[ssh][%s]Error exec command failed: %s", host, err)
}
}()
if err != nil {
panic(1)
}
return b
}
func (ss *SSH) CmdAsync(host string, cmd string) error {
logger.Info("[ssh][%s]exec cmd is : %s", host, cmd)
session, err := ss.Connect(host)
if err != nil {
logger.Error("[ssh][%s]Error create ssh session failed,%s", host, err)
return err
}
defer session.Close()
stdout, err := session.StdoutPipe()
if err != nil {
logger.Error("[ssh][%s]Unable to request StdoutPipe(): %s", host, err)
return err
}
if err := session.Start(cmd); err != nil {
logger.Error("[ssh][%s]Unable to execute command: %s", host, err)
return err
}
done := make(chan bool, 1)
go func() {
for {
r := bufio.NewReader(stdout)
line, _, err := r.ReadLine()
if line == nil || err != nil {
done <- true
} else {
logger.Info("[ssh][%s]: %s", host, line)
}
}
}()
<-done
return nil
}
//CmdToString is in host exec cmd and replace to spilt str
func (ss *SSH) CmdToString(host, cmd, spilt string) string {
data := ss.Cmd(host, cmd)
if data != nil {
str := string(data)
str = strings.ReplaceAll(str, "\r\n", spilt)
return str
}
return ""
}
-10
View File
@@ -1,10 +0,0 @@
package sshutil
import "time"
type SSH struct {
User string
Password string
PkFile string
Timeout *time.Duration
}
-60
View File
@@ -1,60 +0,0 @@
package sshutil
import (
"fmt"
"github.com/wonderivan/logger"
"path"
"strconv"
"strings"
"time"
)
const oneMBByte = 1024 * 1024
//WatchFileSize is
func (ss *SSH) LoggerFileSize(host, filename string, size int) {
t := time.NewTicker(3 * time.Second) //every 3s check file
defer t.Stop()
for {
select {
case <-t.C:
length := ss.CmdToString(host, "ls -l "+filename+" | awk '{print $5}'", "")
length = strings.Replace(length, "\n", "", -1)
length = strings.Replace(length, "\r", "", -1)
lengthByte, _ := strconv.Atoi(length)
if lengthByte == size {
t.Stop()
}
lengthFloat := float64(lengthByte)
value, _ := strconv.ParseFloat(fmt.Sprintf("%.2f", lengthFloat/oneMBByte), 64)
logger.Alert("[ssh][%s]transfer total size is: %.2f%s", host, value, "MB")
}
}
}
//RemoteFilExist is
func (ss *SSH) IsFilExist(host, remoteFilePath string) bool {
// if remote file is
// ls -l | grep aa | wc -l
remoteFileName := path.Base(remoteFilePath) // aa
remoteFileDirName := path.Dir(remoteFilePath)
//it's bug: if file is aa.bak, `ls -l | grep aa | wc -l` is 1 ,should use `ll aa 2>/dev/null |wc -l`
//remoteFileCommand := fmt.Sprintf("ls -l %s| grep %s | grep -v grep |wc -l", remoteFileDirName, remoteFileName)
remoteFileCommand := fmt.Sprintf("ls -l %s/%s 2>/dev/null |wc -l", remoteFileDirName, remoteFileName)
data := ss.CmdToString(host, remoteFileCommand, " ")
count, err := strconv.Atoi(strings.TrimSpace(data))
defer func() {
if r := recover(); r != nil {
logger.Error("[ssh][%s]RemoteFilExist:%s", host, err)
}
}()
if err != nil {
panic(1)
}
if count == 0 {
return false
} else {
return true
}
}
+6 -6
View File
@@ -4,25 +4,25 @@ go_import_path: github.com/pkg/sftp
# current and previous stable releases, plus tip
# remember to exclude previous and tip for macs below
go:
- 1.11.x
- 1.12.x
- 1.13.x
- tip
os:
- linux
- osx
env:
global:
- GO111MODULE=on
matrix:
exclude:
- os: osx
go: 1.10.x
go: 1.12.x
- os: osx
go: tip
env:
global:
- GO111MODULE=on
addons:
ssh_known_hosts:
- bitbucket.org
+11
View File
@@ -0,0 +1,11 @@
integration:
go test -integration -v
go test -testserver -v
go test -integration -testserver -v
integration_w_race:
go test -race -integration -v
go test -race -testserver -v
go test -race -integration -testserver -v
+31 -28
View File
@@ -10,11 +10,14 @@ import (
)
const (
ssh_FILEXFER_ATTR_SIZE = 0x00000001
ssh_FILEXFER_ATTR_UIDGID = 0x00000002
ssh_FILEXFER_ATTR_PERMISSIONS = 0x00000004
ssh_FILEXFER_ATTR_ACMODTIME = 0x00000008
ssh_FILEXFER_ATTR_EXTENDED = 0x80000000
sshFileXferAttrSize = 0x00000001
sshFileXferAttrUIDGID = 0x00000002
sshFileXferAttrPermissions = 0x00000004
sshFileXferAttrACmodTime = 0x00000008
sshFileXferAttrExtented = 0x80000000
sshFileXferAttrAll = sshFileXferAttrSize | sshFileXferAttrUIDGID | sshFileXferAttrPermissions |
sshFileXferAttrACmodTime | sshFileXferAttrExtented
)
// fileInfo is an artificial type designed to satisfy os.FileInfo.
@@ -77,9 +80,9 @@ func fileInfoFromStat(st *FileStat, name string) os.FileInfo {
func fileStatFromInfo(fi os.FileInfo) (uint32, FileStat) {
mtime := fi.ModTime().Unix()
atime := mtime
var flags uint32 = ssh_FILEXFER_ATTR_SIZE |
ssh_FILEXFER_ATTR_PERMISSIONS |
ssh_FILEXFER_ATTR_ACMODTIME
var flags uint32 = sshFileXferAttrSize |
sshFileXferAttrPermissions |
sshFileXferAttrACmodTime
fileStat := FileStat{
Size: uint64(fi.Size()),
@@ -101,31 +104,31 @@ func unmarshalAttrs(b []byte) (*FileStat, []byte) {
func getFileStat(flags uint32, b []byte) (*FileStat, []byte) {
var fs FileStat
if flags&ssh_FILEXFER_ATTR_SIZE == ssh_FILEXFER_ATTR_SIZE {
fs.Size, b = unmarshalUint64(b)
if flags&sshFileXferAttrSize == sshFileXferAttrSize {
fs.Size, b, _ = unmarshalUint64Safe(b)
}
if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
fs.UID, b = unmarshalUint32(b)
if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
fs.UID, b, _ = unmarshalUint32Safe(b)
}
if flags&ssh_FILEXFER_ATTR_UIDGID == ssh_FILEXFER_ATTR_UIDGID {
fs.GID, b = unmarshalUint32(b)
if flags&sshFileXferAttrUIDGID == sshFileXferAttrUIDGID {
fs.GID, b, _ = unmarshalUint32Safe(b)
}
if flags&ssh_FILEXFER_ATTR_PERMISSIONS == ssh_FILEXFER_ATTR_PERMISSIONS {
fs.Mode, b = unmarshalUint32(b)
if flags&sshFileXferAttrPermissions == sshFileXferAttrPermissions {
fs.Mode, b, _ = unmarshalUint32Safe(b)
}
if flags&ssh_FILEXFER_ATTR_ACMODTIME == ssh_FILEXFER_ATTR_ACMODTIME {
fs.Atime, b = unmarshalUint32(b)
fs.Mtime, b = unmarshalUint32(b)
if flags&sshFileXferAttrACmodTime == sshFileXferAttrACmodTime {
fs.Atime, b, _ = unmarshalUint32Safe(b)
fs.Mtime, b, _ = unmarshalUint32Safe(b)
}
if flags&ssh_FILEXFER_ATTR_EXTENDED == ssh_FILEXFER_ATTR_EXTENDED {
if flags&sshFileXferAttrExtented == sshFileXferAttrExtented {
var count uint32
count, b = unmarshalUint32(b)
count, b, _ = unmarshalUint32Safe(b)
ext := make([]StatExtended, count)
for i := uint32(0); i < count; i++ {
var typ string
var data string
typ, b = unmarshalString(b)
data, b = unmarshalString(b)
typ, b, _ = unmarshalStringSafe(b)
data, b, _ = unmarshalStringSafe(b)
ext[i] = StatExtended{typ, data}
}
fs.Extended = ext
@@ -152,17 +155,17 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
flags, fileStat := fileStatFromInfo(fi)
b = marshalUint32(b, flags)
if flags&ssh_FILEXFER_ATTR_SIZE != 0 {
if flags&sshFileXferAttrSize != 0 {
b = marshalUint64(b, fileStat.Size)
}
if flags&ssh_FILEXFER_ATTR_UIDGID != 0 {
if flags&sshFileXferAttrUIDGID != 0 {
b = marshalUint32(b, fileStat.UID)
b = marshalUint32(b, fileStat.GID)
}
if flags&ssh_FILEXFER_ATTR_PERMISSIONS != 0 {
if flags&sshFileXferAttrPermissions != 0 {
b = marshalUint32(b, fileStat.Mode)
}
if flags&ssh_FILEXFER_ATTR_ACMODTIME != 0 {
if flags&sshFileXferAttrACmodTime != 0 {
b = marshalUint32(b, fileStat.Atime)
b = marshalUint32(b, fileStat.Mtime)
}
@@ -173,7 +176,7 @@ func marshalFileInfo(b []byte, fi os.FileInfo) []byte {
// toFileMode converts sftp filemode bits to the os.FileMode specification
func toFileMode(mode uint32) os.FileMode {
var fm = os.FileMode(mode & 0777)
switch mode & syscall.S_IFMT {
switch mode & S_IFMT {
case syscall.S_IFBLK:
fm |= os.ModeDevice
case syscall.S_IFCHR:
+1 -1
View File
@@ -10,7 +10,7 @@ import (
func fileStatFromInfoOs(fi os.FileInfo, flags *uint32, fileStat *FileStat) {
if statt, ok := fi.Sys().(*syscall.Stat_t); ok {
*flags |= ssh_FILEXFER_ATTR_UIDGID
*flags |= sshFileXferAttrUIDGID
fileStat.UID = statt.Uid
fileStat.GID = statt.Gid
}
+126 -61
View File
@@ -15,12 +15,18 @@ import (
"golang.org/x/crypto/ssh"
)
// InternalInconsistency indicates the packets sent and the data queued to be
// written to the file don't match up. It is an unusual error and usually is
// caused by bad behavior server side or connection issues. The error is
// limited in scope to the call where it happened, the client object is still
// OK to use as long as the connection is still open.
var InternalInconsistency = errors.New("internal inconsistency")
var (
// ErrInternalInconsistency indicates the packets sent and the data queued to be
// written to the file don't match up. It is an unusual error and usually is
// caused by bad behavior server side or connection issues. The error is
// limited in scope to the call where it happened, the client object is still
// OK to use as long as the connection is still open.
ErrInternalInconsistency = errors.New("internal inconsistency")
// InternalInconsistency alias for ErrInternalInconsistency.
//
// Deprecated: please use ErrInternalInconsistency
InternalInconsistency = ErrInternalInconsistency
)
// A ClientOption is a function which applies configuration to a Client.
type ClientOption func(*Client) error
@@ -45,6 +51,30 @@ func MaxPacketChecked(size int) ClientOption {
}
}
// UseFstat sets whether to use Fstat or Stat when File.WriteTo is called
// (usually when copying files).
// Some servers limit the amount of open files and calling Stat after opening
// the file will throw an error From the server. Setting this flag will call
// Fstat instead of Stat which is suppose to be called on an open file handle.
//
// It has been found that that with IBM Sterling SFTP servers which have
// "extractability" level set to 1 which means only 1 file can be opened at
// any given time.
//
// If the server you are working with still has an issue with both Stat and
// Fstat calls you can always open a file and read it until the end.
//
// Another reason to read the file until its end and Fstat doesn't work is
// that in some servers, reading a full file will automatically delete the
// file as some of these mainframes map the file to a message in a queue.
// Once the file has been read it will get deleted.
func UseFstat(value bool) ClientOption {
return func(c *Client) error {
c.useFstat = value
return nil
}
}
// MaxPacketUnchecked sets the maximum size of the payload, measured in bytes.
// It accepts sizes larger than the 32768 bytes all servers should support.
// Only use a setting higher than 32768 if your application always connects to
@@ -155,12 +185,17 @@ type Client struct {
maxPacket int // max packet size read or written.
nextid uint32
maxConcurrentRequests int
useFstat bool
}
// Create creates the named file mode 0666 (before umask), truncating it if it
// already exists. If successful, methods on the returned File can be used for
// I/O; the associated file descriptor has mode O_RDWR. If you need more
// control over the flags/mode used to open the file see client.OpenFile.
//
// Note that some SFTP servers (eg. AWS Transfer) do not support opening files
// read/write at the same time. For those services you will need to use
// `client.OpenFile(os.O_WRONLY|os.O_CREATE|os.O_TRUNC)`.
func (c *Client) Create(path string) (*File, error) {
return c.open(path, flags(os.O_RDWR|os.O_CREATE|os.O_TRUNC))
}
@@ -183,8 +218,8 @@ func (c *Client) recvVersion() error {
if err != nil {
return err
}
if typ != ssh_FXP_VERSION {
return &unexpectedPacketErr{ssh_FXP_VERSION, typ}
if typ != sshFxpVersion {
return &unexpectedPacketErr{sshFxpVersion, typ}
}
version, _ := unmarshalUint32(data)
@@ -222,7 +257,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
break
}
switch typ {
case ssh_FXP_NAME:
case sshFxpName:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
@@ -239,7 +274,7 @@ func (c *Client) ReadDir(p string) ([]os.FileInfo, error) {
}
attrs = append(attrs, fileInfoFromStat(attr, path.Base(filename)))
}
case ssh_FXP_STATUS:
case sshFxpStatus:
// TODO(dfc) scope warning!
err = normaliseError(unmarshalStatus(id, data))
done = true
@@ -263,14 +298,14 @@ func (c *Client) opendir(path string) (string, error) {
return "", err
}
switch typ {
case ssh_FXP_HANDLE:
case sshFxpHandle:
sid, data := unmarshalUint32(data)
if sid != id {
return "", &unexpectedIDErr{id, sid}
}
handle, _ := unmarshalString(data)
return handle, nil
case ssh_FXP_STATUS:
case sshFxpStatus:
return "", normaliseError(unmarshalStatus(id, data))
default:
return "", unimplementedPacketErr(typ)
@@ -289,14 +324,14 @@ func (c *Client) Stat(p string) (os.FileInfo, error) {
return nil, err
}
switch typ {
case ssh_FXP_ATTRS:
case sshFxpAttrs:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
}
attr, _ := unmarshalAttrs(data)
return fileInfoFromStat(attr, path.Base(p)), nil
case ssh_FXP_STATUS:
case sshFxpStatus:
return nil, normaliseError(unmarshalStatus(id, data))
default:
return nil, unimplementedPacketErr(typ)
@@ -315,14 +350,14 @@ func (c *Client) Lstat(p string) (os.FileInfo, error) {
return nil, err
}
switch typ {
case ssh_FXP_ATTRS:
case sshFxpAttrs:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
}
attr, _ := unmarshalAttrs(data)
return fileInfoFromStat(attr, path.Base(p)), nil
case ssh_FXP_STATUS:
case sshFxpStatus:
return nil, normaliseError(unmarshalStatus(id, data))
default:
return nil, unimplementedPacketErr(typ)
@@ -340,7 +375,7 @@ func (c *Client) ReadLink(p string) (string, error) {
return "", err
}
switch typ {
case ssh_FXP_NAME:
case sshFxpName:
sid, data := unmarshalUint32(data)
if sid != id {
return "", &unexpectedIDErr{id, sid}
@@ -351,13 +386,32 @@ func (c *Client) ReadLink(p string) (string, error) {
}
filename, _ := unmarshalString(data) // ignore dummy attributes
return filename, nil
case ssh_FXP_STATUS:
case sshFxpStatus:
return "", normaliseError(unmarshalStatus(id, data))
default:
return "", unimplementedPacketErr(typ)
}
}
// Link creates a hard link at 'newname', pointing at the same inode as 'oldname'
func (c *Client) Link(oldname, newname string) error {
id := c.nextID()
typ, data, err := c.sendPacket(sshFxpHardlinkPacket{
ID: id,
Oldpath: oldname,
Newpath: newname,
})
if err != nil {
return err
}
switch typ {
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
}
}
// Symlink creates a symbolic link at 'newname', pointing at target 'oldname'
func (c *Client) Symlink(oldname, newname string) error {
id := c.nextID()
@@ -370,7 +424,7 @@ func (c *Client) Symlink(oldname, newname string) error {
return err
}
switch typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -390,7 +444,7 @@ func (c *Client) setstat(path string, flags uint32, attrs interface{}) error {
return err
}
switch typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -404,7 +458,7 @@ func (c *Client) Chtimes(path string, atime time.Time, mtime time.Time) error {
Mtime uint32
}
attrs := times{uint32(atime.Unix()), uint32(mtime.Unix())}
return c.setstat(path, ssh_FILEXFER_ATTR_ACMODTIME, attrs)
return c.setstat(path, sshFileXferAttrACmodTime, attrs)
}
// Chown changes the user and group owners of the named file.
@@ -414,12 +468,12 @@ func (c *Client) Chown(path string, uid, gid int) error {
GID uint32
}
attrs := owner{uint32(uid), uint32(gid)}
return c.setstat(path, ssh_FILEXFER_ATTR_UIDGID, attrs)
return c.setstat(path, sshFileXferAttrUIDGID, attrs)
}
// Chmod changes the permissions of the named file.
func (c *Client) Chmod(path string, mode os.FileMode) error {
return c.setstat(path, ssh_FILEXFER_ATTR_PERMISSIONS, uint32(mode))
return c.setstat(path, sshFileXferAttrPermissions, uint32(mode))
}
// Truncate sets the size of the named file. Although it may be safely assumed
@@ -427,7 +481,7 @@ func (c *Client) Chmod(path string, mode os.FileMode) error {
// the SFTP protocol does not specify what behavior the server should do when setting
// size greater than the current size.
func (c *Client) Truncate(path string, size int64) error {
return c.setstat(path, ssh_FILEXFER_ATTR_SIZE, uint64(size))
return c.setstat(path, sshFileXferAttrSize, uint64(size))
}
// Open opens the named file for reading. If successful, methods on the
@@ -455,14 +509,14 @@ func (c *Client) open(path string, pflags uint32) (*File, error) {
return nil, err
}
switch typ {
case ssh_FXP_HANDLE:
case sshFxpHandle:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
}
handle, _ := unmarshalString(data)
return &File{c: c, path: path, handle: handle}, nil
case ssh_FXP_STATUS:
case sshFxpStatus:
return nil, normaliseError(unmarshalStatus(id, data))
default:
return nil, unimplementedPacketErr(typ)
@@ -482,7 +536,7 @@ func (c *Client) close(handle string) error {
return err
}
switch typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -499,14 +553,14 @@ func (c *Client) fstat(handle string) (*FileStat, error) {
return nil, err
}
switch typ {
case ssh_FXP_ATTRS:
case sshFxpAttrs:
sid, data := unmarshalUint32(data)
if sid != id {
return nil, &unexpectedIDErr{id, sid}
}
attr, _ := unmarshalAttrs(data)
return attr, nil
case ssh_FXP_STATUS:
case sshFxpStatus:
return nil, normaliseError(unmarshalStatus(id, data))
default:
return nil, unimplementedPacketErr(typ)
@@ -530,7 +584,7 @@ func (c *Client) StatVFS(path string) (*StatVFS, error) {
switch typ {
// server responded with valid data
case ssh_FXP_EXTENDED_REPLY:
case sshFxpExtendedReply:
var response StatVFS
err = binary.Read(bytes.NewReader(data), binary.BigEndian, &response)
if err != nil {
@@ -540,8 +594,8 @@ func (c *Client) StatVFS(path string) (*StatVFS, error) {
return &response, nil
// the resquest failed
case ssh_FXP_STATUS:
return nil, errors.New(fxp(ssh_FXP_STATUS).String())
case sshFxpStatus:
return nil, errors.New(fxp(sshFxpStatus).String())
default:
return nil, unimplementedPacketErr(typ)
@@ -562,7 +616,7 @@ func (c *Client) Remove(path string) error {
switch err.Code {
// some servers, *cough* osx *cough*, return EPERM, not ENODIR.
// serv-u returns ssh_FX_FILE_IS_A_DIRECTORY
case ssh_FX_PERMISSION_DENIED, ssh_FX_FAILURE, ssh_FX_FILE_IS_A_DIRECTORY:
case sshFxPermissionDenied, sshFxFailure, sshFxFileIsADirectory:
return c.RemoveDirectory(path)
}
}
@@ -579,7 +633,7 @@ func (c *Client) removeFile(path string) error {
return err
}
switch typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -597,7 +651,7 @@ func (c *Client) RemoveDirectory(path string) error {
return err
}
switch typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -616,7 +670,7 @@ func (c *Client) Rename(oldname, newname string) error {
return err
}
switch typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -636,7 +690,7 @@ func (c *Client) PosixRename(oldname, newname string) error {
return err
}
switch typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -653,7 +707,7 @@ func (c *Client) realpath(path string) (string, error) {
return "", err
}
switch typ {
case ssh_FXP_NAME:
case sshFxpName:
sid, data := unmarshalUint32(data)
if sid != id {
return "", &unexpectedIDErr{id, sid}
@@ -664,7 +718,7 @@ func (c *Client) realpath(path string) (string, error) {
}
filename, _ := unmarshalString(data) // ignore attributes
return filename, nil
case ssh_FXP_STATUS:
case sshFxpStatus:
return "", normaliseError(unmarshalStatus(id, data))
default:
return "", unimplementedPacketErr(typ)
@@ -690,7 +744,7 @@ func (c *Client) Mkdir(path string) error {
return err
}
switch typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
return normaliseError(unmarshalStatus(id, data))
default:
return unimplementedPacketErr(typ)
@@ -845,14 +899,14 @@ func (f *File) Read(b []byte) (int, error) {
}
delete(reqs, reqID)
switch res.typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
if firstErr.err == nil || req.offset < firstErr.offset {
firstErr = offsetErr{
offset: req.offset,
err: normaliseError(unmarshalStatus(reqID, res.data)),
}
}
case ssh_FXP_DATA:
case sshFxpData:
l, data := unmarshalUint32(data)
n := copy(req.b, data[:l])
read += n
@@ -884,15 +938,26 @@ func (f *File) Read(b []byte) (int, error) {
// maximise throughput for transferring the entire file (especially
// over high latency links).
func (f *File) WriteTo(w io.Writer) (int64, error) {
fi, err := f.c.Stat(f.path)
if err != nil {
return 0, err
var fileSize uint64
if f.c.useFstat {
fileStat, err := f.c.fstat(f.handle)
if err != nil {
return 0, err
}
fileSize = fileStat.Size
} else {
fi, err := f.c.Stat(f.path)
if err != nil {
return 0, err
}
fileSize = uint64(fi.Size())
}
inFlight := 0
desiredInFlight := 1
offset := f.offset
writeOffset := offset
fileSize := uint64(fi.Size())
// see comment on same line in Read() above
ch := make(chan result, f.c.maxConcurrentRequests+1)
type inflightRead struct {
@@ -934,7 +999,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
if inFlight == 0 {
if firstErr.err == nil && len(pendingWrites) > 0 {
return copied, InternalInconsistency
return copied, ErrInternalInconsistency
}
break
}
@@ -952,11 +1017,11 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
}
delete(reqs, reqID)
switch res.typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
if firstErr.err == nil || req.offset < firstErr.offset {
firstErr = offsetErr{offset: req.offset, err: normaliseError(unmarshalStatus(reqID, res.data))}
}
case ssh_FXP_DATA:
case sshFxpData:
l, data := unmarshalUint32(data)
if req.offset == writeOffset {
nbytes, err := w.Write(data)
@@ -1071,7 +1136,7 @@ func (f *File) Write(b []byte) (int, error) {
continue
}
switch res.typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
id, _ := unmarshalUint32(res.data)
err := normaliseError(unmarshalStatus(id, res.data))
if err != nil && firstErr == nil {
@@ -1139,7 +1204,7 @@ func (f *File) ReadFrom(r io.Reader) (int64, error) {
continue
}
switch res.typ {
case ssh_FXP_STATUS:
case sshFxpStatus:
id, _ := unmarshalUint32(res.data)
err := normaliseError(unmarshalStatus(id, res.data))
if err != nil && firstErr == nil {
@@ -1218,11 +1283,11 @@ func normaliseError(err error) error {
switch err := err.(type) {
case *StatusError:
switch err.Code {
case ssh_FX_EOF:
case sshFxEOF:
return io.EOF
case ssh_FX_NO_SUCH_FILE:
case sshFxNoSuchFile:
return os.ErrNotExist
case ssh_FX_OK:
case sshFxOk:
return nil
default:
return err
@@ -1260,24 +1325,24 @@ func flags(f int) uint32 {
var out uint32
switch f & os.O_WRONLY {
case os.O_WRONLY:
out |= ssh_FXF_WRITE
out |= sshFxfWrite
case os.O_RDONLY:
out |= ssh_FXF_READ
out |= sshFxfRead
}
if f&os.O_RDWR == os.O_RDWR {
out |= ssh_FXF_READ | ssh_FXF_WRITE
out |= sshFxfRead | sshFxfWrite
}
if f&os.O_APPEND == os.O_APPEND {
out |= ssh_FXF_APPEND
out |= sshFxfAppend
}
if f&os.O_CREATE == os.O_CREATE {
out |= ssh_FXF_CREAT
out |= sshFxfCreat
}
if f&os.O_TRUNC == os.O_TRUNC {
out |= ssh_FXF_TRUNC
out |= sshFxfTrunc
}
if f&os.O_EXCL == os.O_EXCL {
out |= ssh_FXF_EXCL
out |= sshFxfExcl
}
return out
}
+11 -11
View File
@@ -39,7 +39,7 @@ func newPktMgr(sender packetSender) *packetManager {
}
//// packet ordering
func (s *packetManager) newOrderId() uint32 {
func (s *packetManager) newOrderID() uint32 {
s.packetCount++
return s.packetCount
}
@@ -50,10 +50,10 @@ type orderedRequest struct {
}
func (s *packetManager) newOrderedRequest(p requestPacket) orderedRequest {
return orderedRequest{requestPacket: p, orderid: s.newOrderId()}
return orderedRequest{requestPacket: p, orderid: s.newOrderID()}
}
func (p orderedRequest) orderId() uint32 { return p.orderid }
func (p orderedRequest) setOrderId(oid uint32) { p.orderid = oid }
func (p orderedRequest) orderID() uint32 { return p.orderid }
func (p orderedRequest) setOrderID(oid uint32) { p.orderid = oid }
type orderedResponse struct {
responsePacket
@@ -64,18 +64,18 @@ func (s *packetManager) newOrderedResponse(p responsePacket, id uint32,
) orderedResponse {
return orderedResponse{responsePacket: p, orderid: id}
}
func (p orderedResponse) orderId() uint32 { return p.orderid }
func (p orderedResponse) setOrderId(oid uint32) { p.orderid = oid }
func (p orderedResponse) orderID() uint32 { return p.orderid }
func (p orderedResponse) setOrderID(oid uint32) { p.orderid = oid }
type orderedPacket interface {
id() uint32
orderId() uint32
orderID() uint32
}
type orderedPackets []orderedPacket
func (o orderedPackets) Sort() {
sort.Slice(o, func(i, j int) bool {
return o[i].orderId() < o[j].orderId()
return o[i].orderID() < o[j].orderID()
})
}
@@ -145,11 +145,11 @@ func (s *packetManager) controller() {
for {
select {
case pkt := <-s.requests:
debug("incoming id (oid): %v (%v)", pkt.id(), pkt.orderId())
debug("incoming id (oid): %v (%v)", pkt.id(), pkt.orderID())
s.incoming = append(s.incoming, pkt)
s.incoming.Sort()
case pkt := <-s.responses:
debug("outgoing id (oid): %v (%v)", pkt.id(), pkt.orderId())
debug("outgoing id (oid): %v (%v)", pkt.id(), pkt.orderID())
s.outgoing = append(s.outgoing, pkt)
s.outgoing.Sort()
case <-s.fini:
@@ -171,7 +171,7 @@ func (s *packetManager) maybeSendPackets() {
in := s.incoming[0]
// debug("incoming: %v", ids(s.incoming))
// debug("outgoing: %v", ids(s.outgoing))
if in.orderId() == out.orderId() {
if in.orderID() == out.orderID() {
debug("Sending packet: %v", out.id())
s.sender.sendPacket(out.(encoding.BinaryMarshaler))
// pop off heads
+23 -21
View File
@@ -49,8 +49,9 @@ func (p sshFxpOpendirPacket) getPath() string { return p.Path }
func (p sshFxpOpenPacket) getPath() string { return p.Path }
func (p sshFxpExtendedPacketPosixRename) getPath() string { return p.Oldpath }
func (p sshFxpExtendedPacketHardlink) getPath() string { return p.Oldpath }
// hasHandle
// getHandle
func (p sshFxpFstatPacket) getHandle() string { return p.Handle }
func (p sshFxpFsetstatPacket) getHandle() string { return p.Handle }
func (p sshFxpReadPacket) getHandle() string { return p.Handle }
@@ -68,6 +69,7 @@ func (p sshFxpRmdirPacket) notReadOnly() {}
func (p sshFxpRenamePacket) notReadOnly() {}
func (p sshFxpSymlinkPacket) notReadOnly() {}
func (p sshFxpExtendedPacketPosixRename) notReadOnly() {}
func (p sshFxpExtendedPacketHardlink) notReadOnly() {}
// some packets with ID are missing id()
func (p sshFxpDataPacket) id() uint32 { return p.ID }
@@ -82,45 +84,45 @@ func (p sshFxVersionPacket) id() uint32 { return 0 }
func makePacket(p rxPacket) (requestPacket, error) {
var pkt requestPacket
switch p.pktType {
case ssh_FXP_INIT:
case sshFxpInit:
pkt = &sshFxInitPacket{}
case ssh_FXP_LSTAT:
case sshFxpLstat:
pkt = &sshFxpLstatPacket{}
case ssh_FXP_OPEN:
case sshFxpOpen:
pkt = &sshFxpOpenPacket{}
case ssh_FXP_CLOSE:
case sshFxpClose:
pkt = &sshFxpClosePacket{}
case ssh_FXP_READ:
case sshFxpRead:
pkt = &sshFxpReadPacket{}
case ssh_FXP_WRITE:
case sshFxpWrite:
pkt = &sshFxpWritePacket{}
case ssh_FXP_FSTAT:
case sshFxpFstat:
pkt = &sshFxpFstatPacket{}
case ssh_FXP_SETSTAT:
case sshFxpSetstat:
pkt = &sshFxpSetstatPacket{}
case ssh_FXP_FSETSTAT:
case sshFxpFsetstat:
pkt = &sshFxpFsetstatPacket{}
case ssh_FXP_OPENDIR:
case sshFxpOpendir:
pkt = &sshFxpOpendirPacket{}
case ssh_FXP_READDIR:
case sshFxpReaddir:
pkt = &sshFxpReaddirPacket{}
case ssh_FXP_REMOVE:
case sshFxpRemove:
pkt = &sshFxpRemovePacket{}
case ssh_FXP_MKDIR:
case sshFxpMkdir:
pkt = &sshFxpMkdirPacket{}
case ssh_FXP_RMDIR:
case sshFxpRmdir:
pkt = &sshFxpRmdirPacket{}
case ssh_FXP_REALPATH:
case sshFxpRealpath:
pkt = &sshFxpRealpathPacket{}
case ssh_FXP_STAT:
case sshFxpStat:
pkt = &sshFxpStatPacket{}
case ssh_FXP_RENAME:
case sshFxpRename:
pkt = &sshFxpRenamePacket{}
case ssh_FXP_READLINK:
case sshFxpReadlink:
pkt = &sshFxpReadlinkPacket{}
case ssh_FXP_SYMLINK:
case sshFxpSymlink:
pkt = &sshFxpSymlinkPacket{}
case ssh_FXP_EXTENDED:
case sshFxpExtended:
pkt = &sshFxpExtendedPacket{}
default:
return nil, errors.Errorf("unhandled packet type: %s", p.pktType)
+94 -31
View File
@@ -13,11 +13,13 @@ import (
)
var (
errLongPacket = errors.New("packet too long")
errShortPacket = errors.New("packet too short")
errUnknownExtendedPacket = errors.New("unknown extended packet")
)
const (
maxMsgLength = 256 * 1024
debugDumpTxPacket = false
debugDumpRxPacket = false
debugDumpTxPacketBytes = false
@@ -143,6 +145,10 @@ func recvPacket(r io.Reader) (uint8, []byte, error) {
return 0, nil, err
}
l, _ := unmarshalUint32(b)
if l > maxMsgLength {
debug("recv packet %d bytes too long", l)
return 0, nil, errLongPacket
}
b = make([]byte, l)
if _, err := io.ReadFull(r, b); err != nil {
debug("recv packet %d bytes: err %v", l, err)
@@ -189,7 +195,7 @@ func (p sshFxInitPacket) MarshalBinary() ([]byte, error) {
}
b := make([]byte, 0, l)
b = append(b, ssh_FXP_INIT)
b = append(b, sshFxpInit)
b = marshalUint32(b, p.Version)
for _, e := range p.Extensions {
b = marshalString(b, e.Name)
@@ -216,9 +222,11 @@ func (p *sshFxInitPacket) UnmarshalBinary(b []byte) error {
type sshFxVersionPacket struct {
Version uint32
Extensions []struct {
Name, Data string
}
Extensions []sshExtensionPair
}
type sshExtensionPair struct {
Name, Data string
}
func (p sshFxVersionPacket) MarshalBinary() ([]byte, error) {
@@ -228,7 +236,7 @@ func (p sshFxVersionPacket) MarshalBinary() ([]byte, error) {
}
b := make([]byte, 0, l)
b = append(b, ssh_FXP_VERSION)
b = append(b, sshFxpVersion)
b = marshalUint32(b, p.Version)
for _, e := range p.Extensions {
b = marshalString(b, e.Name)
@@ -266,7 +274,7 @@ type sshFxpReaddirPacket struct {
func (p sshFxpReaddirPacket) id() uint32 { return p.ID }
func (p sshFxpReaddirPacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_READDIR, p.ID, p.Handle)
return marshalIDString(sshFxpReaddir, p.ID, p.Handle)
}
func (p *sshFxpReaddirPacket) UnmarshalBinary(b []byte) error {
@@ -281,7 +289,7 @@ type sshFxpOpendirPacket struct {
func (p sshFxpOpendirPacket) id() uint32 { return p.ID }
func (p sshFxpOpendirPacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_OPENDIR, p.ID, p.Path)
return marshalIDString(sshFxpOpendir, p.ID, p.Path)
}
func (p *sshFxpOpendirPacket) UnmarshalBinary(b []byte) error {
@@ -296,7 +304,7 @@ type sshFxpLstatPacket struct {
func (p sshFxpLstatPacket) id() uint32 { return p.ID }
func (p sshFxpLstatPacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_LSTAT, p.ID, p.Path)
return marshalIDString(sshFxpLstat, p.ID, p.Path)
}
func (p *sshFxpLstatPacket) UnmarshalBinary(b []byte) error {
@@ -311,7 +319,7 @@ type sshFxpStatPacket struct {
func (p sshFxpStatPacket) id() uint32 { return p.ID }
func (p sshFxpStatPacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_STAT, p.ID, p.Path)
return marshalIDString(sshFxpStat, p.ID, p.Path)
}
func (p *sshFxpStatPacket) UnmarshalBinary(b []byte) error {
@@ -326,7 +334,7 @@ type sshFxpFstatPacket struct {
func (p sshFxpFstatPacket) id() uint32 { return p.ID }
func (p sshFxpFstatPacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_FSTAT, p.ID, p.Handle)
return marshalIDString(sshFxpFstat, p.ID, p.Handle)
}
func (p *sshFxpFstatPacket) UnmarshalBinary(b []byte) error {
@@ -341,7 +349,7 @@ type sshFxpClosePacket struct {
func (p sshFxpClosePacket) id() uint32 { return p.ID }
func (p sshFxpClosePacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_CLOSE, p.ID, p.Handle)
return marshalIDString(sshFxpClose, p.ID, p.Handle)
}
func (p *sshFxpClosePacket) UnmarshalBinary(b []byte) error {
@@ -356,7 +364,7 @@ type sshFxpRemovePacket struct {
func (p sshFxpRemovePacket) id() uint32 { return p.ID }
func (p sshFxpRemovePacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_REMOVE, p.ID, p.Filename)
return marshalIDString(sshFxpRemove, p.ID, p.Filename)
}
func (p *sshFxpRemovePacket) UnmarshalBinary(b []byte) error {
@@ -371,7 +379,7 @@ type sshFxpRmdirPacket struct {
func (p sshFxpRmdirPacket) id() uint32 { return p.ID }
func (p sshFxpRmdirPacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_RMDIR, p.ID, p.Path)
return marshalIDString(sshFxpRmdir, p.ID, p.Path)
}
func (p *sshFxpRmdirPacket) UnmarshalBinary(b []byte) error {
@@ -392,7 +400,7 @@ func (p sshFxpSymlinkPacket) MarshalBinary() ([]byte, error) {
4 + len(p.Linkpath)
b := make([]byte, 0, l)
b = append(b, ssh_FXP_SYMLINK)
b = append(b, sshFxpSymlink)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Targetpath)
b = marshalString(b, p.Linkpath)
@@ -411,6 +419,30 @@ func (p *sshFxpSymlinkPacket) UnmarshalBinary(b []byte) error {
return nil
}
type sshFxpHardlinkPacket struct {
ID uint32
Oldpath string
Newpath string
}
func (p sshFxpHardlinkPacket) id() uint32 { return p.ID }
func (p sshFxpHardlinkPacket) MarshalBinary() ([]byte, error) {
const ext = "hardlink@openssh.com"
l := 1 + 4 + // type(byte) + uint32
4 + len(ext) +
4 + len(p.Oldpath) +
4 + len(p.Newpath)
b := make([]byte, 0, l)
b = append(b, sshFxpExtended)
b = marshalUint32(b, p.ID)
b = marshalString(b, ext)
b = marshalString(b, p.Oldpath)
b = marshalString(b, p.Newpath)
return b, nil
}
type sshFxpReadlinkPacket struct {
ID uint32
Path string
@@ -419,7 +451,7 @@ type sshFxpReadlinkPacket struct {
func (p sshFxpReadlinkPacket) id() uint32 { return p.ID }
func (p sshFxpReadlinkPacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_READLINK, p.ID, p.Path)
return marshalIDString(sshFxpReadlink, p.ID, p.Path)
}
func (p *sshFxpReadlinkPacket) UnmarshalBinary(b []byte) error {
@@ -434,7 +466,7 @@ type sshFxpRealpathPacket struct {
func (p sshFxpRealpathPacket) id() uint32 { return p.ID }
func (p sshFxpRealpathPacket) MarshalBinary() ([]byte, error) {
return marshalIDString(ssh_FXP_REALPATH, p.ID, p.Path)
return marshalIDString(sshFxpRealpath, p.ID, p.Path)
}
func (p *sshFxpRealpathPacket) UnmarshalBinary(b []byte) error {
@@ -464,7 +496,7 @@ type sshFxpNamePacket struct {
func (p sshFxpNamePacket) MarshalBinary() ([]byte, error) {
b := []byte{}
b = append(b, ssh_FXP_NAME)
b = append(b, sshFxpName)
b = marshalUint32(b, p.ID)
b = marshalUint32(b, uint32(len(p.NameAttrs)))
for _, na := range p.NameAttrs {
@@ -493,7 +525,7 @@ func (p sshFxpOpenPacket) MarshalBinary() ([]byte, error) {
4 + 4
b := make([]byte, 0, l)
b = append(b, ssh_FXP_OPEN)
b = append(b, sshFxpOpen)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Path)
b = marshalUint32(b, p.Pflags)
@@ -530,7 +562,7 @@ func (p sshFxpReadPacket) MarshalBinary() ([]byte, error) {
8 + 4 // uint64 + uint32
b := make([]byte, 0, l)
b = append(b, ssh_FXP_READ)
b = append(b, sshFxpRead)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Handle)
b = marshalUint64(b, p.Offset)
@@ -566,7 +598,7 @@ func (p sshFxpRenamePacket) MarshalBinary() ([]byte, error) {
4 + len(p.Newpath)
b := make([]byte, 0, l)
b = append(b, ssh_FXP_RENAME)
b = append(b, sshFxpRename)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Oldpath)
b = marshalString(b, p.Newpath)
@@ -601,7 +633,7 @@ func (p sshFxpPosixRenamePacket) MarshalBinary() ([]byte, error) {
4 + len(p.Newpath)
b := make([]byte, 0, l)
b = append(b, ssh_FXP_EXTENDED)
b = append(b, sshFxpExtended)
b = marshalUint32(b, p.ID)
b = marshalString(b, ext)
b = marshalString(b, p.Oldpath)
@@ -626,7 +658,7 @@ func (p sshFxpWritePacket) MarshalBinary() ([]byte, error) {
len(p.Data)
b := make([]byte, 0, l)
b = append(b, ssh_FXP_WRITE)
b = append(b, sshFxpWrite)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Handle)
b = marshalUint64(b, p.Offset)
@@ -667,7 +699,7 @@ func (p sshFxpMkdirPacket) MarshalBinary() ([]byte, error) {
4 // uint32
b := make([]byte, 0, l)
b = append(b, ssh_FXP_MKDIR)
b = append(b, sshFxpMkdir)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Path)
b = marshalUint32(b, p.Flags)
@@ -709,7 +741,7 @@ func (p sshFxpSetstatPacket) MarshalBinary() ([]byte, error) {
4 // uint32 + uint64
b := make([]byte, 0, l)
b = append(b, ssh_FXP_SETSTAT)
b = append(b, sshFxpSetstat)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Path)
b = marshalUint32(b, p.Flags)
@@ -723,7 +755,7 @@ func (p sshFxpFsetstatPacket) MarshalBinary() ([]byte, error) {
4 // uint32 + uint64
b := make([]byte, 0, l)
b = append(b, ssh_FXP_FSETSTAT)
b = append(b, sshFxpFsetstat)
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Handle)
b = marshalUint32(b, p.Flags)
@@ -763,7 +795,7 @@ type sshFxpHandlePacket struct {
}
func (p sshFxpHandlePacket) MarshalBinary() ([]byte, error) {
b := []byte{ssh_FXP_HANDLE}
b := []byte{sshFxpHandle}
b = marshalUint32(b, p.ID)
b = marshalString(b, p.Handle)
return b, nil
@@ -775,7 +807,7 @@ type sshFxpStatusPacket struct {
}
func (p sshFxpStatusPacket) MarshalBinary() ([]byte, error) {
b := []byte{ssh_FXP_STATUS}
b := []byte{sshFxpStatus}
b = marshalUint32(b, p.ID)
b = marshalStatus(b, p.StatusError)
return b, nil
@@ -788,7 +820,7 @@ type sshFxpDataPacket struct {
}
func (p sshFxpDataPacket) MarshalBinary() ([]byte, error) {
b := []byte{ssh_FXP_DATA}
b := []byte{sshFxpData}
b = marshalUint32(b, p.ID)
b = marshalUint32(b, p.Length)
b = append(b, p.Data[:p.Length]...)
@@ -823,7 +855,7 @@ func (p sshFxpStatvfsPacket) MarshalBinary() ([]byte, error) {
len("statvfs@openssh.com")
b := make([]byte, 0, l)
b = append(b, ssh_FXP_EXTENDED)
b = append(b, sshFxpExtended)
b = marshalUint32(b, p.ID)
b = marshalString(b, "statvfs@openssh.com")
b = marshalString(b, p.Path)
@@ -856,10 +888,10 @@ func (p *StatVFS) FreeSpace() uint64 {
return p.Frsize * p.Bfree
}
// Convert to ssh_FXP_EXTENDED_REPLY packet binary format
// MarshalBinary converts to ssh_FXP_EXTENDED_REPLY packet binary format
func (p *StatVFS) MarshalBinary() ([]byte, error) {
var buf bytes.Buffer
buf.Write([]byte{ssh_FXP_EXTENDED_REPLY})
buf.Write([]byte{sshFxpExtendedReply})
err := binary.Write(&buf, binary.BigEndian, p)
return buf.Bytes(), err
}
@@ -903,6 +935,8 @@ func (p *sshFxpExtendedPacket) UnmarshalBinary(b []byte) error {
p.SpecificPacket = &sshFxpExtendedPacketStatVFS{}
case "posix-rename@openssh.com":
p.SpecificPacket = &sshFxpExtendedPacketPosixRename{}
case "hardlink@openssh.com":
p.SpecificPacket = &sshFxpExtendedPacketHardlink{}
default:
return errors.Wrapf(errUnknownExtendedPacket, "packet type %v", p.SpecificPacket)
}
@@ -957,3 +991,32 @@ func (p sshFxpExtendedPacketPosixRename) respond(s *Server) responsePacket {
err := os.Rename(p.Oldpath, p.Newpath)
return statusFromError(p, err)
}
type sshFxpExtendedPacketHardlink struct {
ID uint32
ExtendedRequest string
Oldpath string
Newpath string
}
// https://github.com/openssh/openssh-portable/blob/master/PROTOCOL
func (p sshFxpExtendedPacketHardlink) id() uint32 { return p.ID }
func (p sshFxpExtendedPacketHardlink) readonly() bool { return true }
func (p *sshFxpExtendedPacketHardlink) UnmarshalBinary(b []byte) error {
var err error
if p.ID, b, err = unmarshalUint32Safe(b); err != nil {
return err
} else if p.ExtendedRequest, b, err = unmarshalStringSafe(b); err != nil {
return err
} else if p.Oldpath, b, err = unmarshalStringSafe(b); err != nil {
return err
} else if p.Newpath, _, err = unmarshalStringSafe(b); err != nil {
return err
}
return nil
}
func (p sshFxpExtendedPacketHardlink) respond(s *Server) responsePacket {
err := os.Link(p.Oldpath, p.Newpath)
return statusFromError(p, err)
}
+14 -14
View File
@@ -5,7 +5,7 @@ package sftp
// request and AttrFlags() and Attributes() when working with SetStat requests.
import "os"
// File Open and Write Flags. Correlate directly with with os.OpenFile flags
// FileOpenFlags defines Open and Write Flags. Correlate directly with with os.OpenFile flags
// (https://golang.org/pkg/os/#pkg-constants).
type FileOpenFlags struct {
Read, Write, Append, Creat, Trunc, Excl bool
@@ -13,12 +13,12 @@ type FileOpenFlags struct {
func newFileOpenFlags(flags uint32) FileOpenFlags {
return FileOpenFlags{
Read: flags&ssh_FXF_READ != 0,
Write: flags&ssh_FXF_WRITE != 0,
Append: flags&ssh_FXF_APPEND != 0,
Creat: flags&ssh_FXF_CREAT != 0,
Trunc: flags&ssh_FXF_TRUNC != 0,
Excl: flags&ssh_FXF_EXCL != 0,
Read: flags&sshFxfRead != 0,
Write: flags&sshFxfWrite != 0,
Append: flags&sshFxfAppend != 0,
Creat: flags&sshFxfCreat != 0,
Trunc: flags&sshFxfTrunc != 0,
Excl: flags&sshFxfExcl != 0,
}
}
@@ -28,7 +28,7 @@ func (r *Request) Pflags() FileOpenFlags {
return newFileOpenFlags(r.Flags)
}
// Flags that indicate whether SFTP file attributes were passed. When a flag is
// FileAttrFlags that indicate whether SFTP file attributes were passed. When a flag is
// true the corresponding attribute should be available from the FileStat
// object returned by Attributes method. Used with SetStat.
type FileAttrFlags struct {
@@ -37,14 +37,14 @@ type FileAttrFlags struct {
func newFileAttrFlags(flags uint32) FileAttrFlags {
return FileAttrFlags{
Size: (flags & ssh_FILEXFER_ATTR_SIZE) != 0,
UidGid: (flags & ssh_FILEXFER_ATTR_UIDGID) != 0,
Permissions: (flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0,
Acmodtime: (flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0,
Size: (flags & sshFileXferAttrSize) != 0,
UidGid: (flags & sshFileXferAttrUIDGID) != 0,
Permissions: (flags & sshFileXferAttrPermissions) != 0,
Acmodtime: (flags & sshFileXferAttrACmodTime) != 0,
}
}
// FileAttrFlags returns a FileAttrFlags boolean struct based on the
// AttrFlags returns a FileAttrFlags boolean struct based on the
// bitmap/uint32 file attribute flags from the SFTP packaet.
func (r *Request) AttrFlags() FileAttrFlags {
return newFileAttrFlags(r.Flags)
@@ -55,7 +55,7 @@ func (a FileStat) FileMode() os.FileMode {
return os.FileMode(a.Mode)
}
// Attributres parses file attributes byte blob and return them in a
// Attributes parses file attributes byte blob and return them in a
// FileStat object.
func (r *Request) Attributes() *FileStat {
fs, _ := getFileStat(r.Flags, r.Attrs)
+32 -20
View File
@@ -1,40 +1,52 @@
package sftp
type fxerr uint32
// Error types that match the SFTP's SSH_FXP_STATUS codes. Gives you more
// direct control of the errors being sent vs. letting the library work them
// out from the standard os/io errors.
type fxerr uint32
const (
ErrSshFxOk = fxerr(ssh_FX_OK)
ErrSshFxEof = fxerr(ssh_FX_EOF)
ErrSshFxNoSuchFile = fxerr(ssh_FX_NO_SUCH_FILE)
ErrSshFxPermissionDenied = fxerr(ssh_FX_PERMISSION_DENIED)
ErrSshFxFailure = fxerr(ssh_FX_FAILURE)
ErrSshFxBadMessage = fxerr(ssh_FX_BAD_MESSAGE)
ErrSshFxNoConnection = fxerr(ssh_FX_NO_CONNECTION)
ErrSshFxConnectionLost = fxerr(ssh_FX_CONNECTION_LOST)
ErrSshFxOpUnsupported = fxerr(ssh_FX_OP_UNSUPPORTED)
ErrSSHFxOk = fxerr(sshFxOk)
ErrSSHFxEOF = fxerr(sshFxEOF)
ErrSSHFxNoSuchFile = fxerr(sshFxNoSuchFile)
ErrSSHFxPermissionDenied = fxerr(sshFxPermissionDenied)
ErrSSHFxFailure = fxerr(sshFxFailure)
ErrSSHFxBadMessage = fxerr(sshFxBadMessage)
ErrSSHFxNoConnection = fxerr(sshFxNoConnection)
ErrSSHFxConnectionLost = fxerr(sshFxConnectionLost)
ErrSSHFxOpUnsupported = fxerr(sshFxOPUnsupported)
)
// Deprecated error types, these are aliases for the new ones, please use the new ones directly
const (
ErrSshFxOk = ErrSSHFxOk
ErrSshFxEof = ErrSSHFxEOF
ErrSshFxNoSuchFile = ErrSSHFxNoSuchFile
ErrSshFxPermissionDenied = ErrSSHFxPermissionDenied
ErrSshFxFailure = ErrSSHFxFailure
ErrSshFxBadMessage = ErrSSHFxBadMessage
ErrSshFxNoConnection = ErrSSHFxNoConnection
ErrSshFxConnectionLost = ErrSSHFxConnectionLost
ErrSshFxOpUnsupported = ErrSSHFxOpUnsupported
)
func (e fxerr) Error() string {
switch e {
case ErrSshFxOk:
case ErrSSHFxOk:
return "OK"
case ErrSshFxEof:
case ErrSSHFxEOF:
return "EOF"
case ErrSshFxNoSuchFile:
case ErrSSHFxNoSuchFile:
return "No Such File"
case ErrSshFxPermissionDenied:
case ErrSSHFxPermissionDenied:
return "Permission Denied"
case ErrSshFxBadMessage:
case ErrSSHFxBadMessage:
return "Bad Message"
case ErrSshFxNoConnection:
case ErrSSHFxNoConnection:
return "No Connection"
case ErrSshFxConnectionLost:
case ErrSSHFxConnectionLost:
return "Connection Lost"
case ErrSshFxOpUnsupported:
case ErrSSHFxOpUnsupported:
return "Operation Unsupported"
default:
return "Failure"
+53 -13
View File
@@ -11,6 +11,7 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"sync"
"syscall"
"time"
@@ -90,18 +91,51 @@ func (fs *root) Filecmd(r *Request) error {
file.name = r.Target
fs.files[r.Target] = file
delete(fs.files, r.Filepath)
if file.IsDir() {
for path, file := range fs.files {
if strings.HasPrefix(path, r.Filepath+"/") {
file.name = r.Target + path[len(r.Filepath):]
fs.files[r.Target+path[len(r.Filepath):]] = file
delete(fs.files, path)
}
}
}
case "Rmdir", "Remove":
_, err := fs.fetch(filepath.Dir(r.Filepath))
file, err := fs.fetch(filepath.Dir(r.Filepath))
if err != nil {
return err
}
if file.IsDir() {
for path := range fs.files {
if strings.HasPrefix(path, r.Filepath+"/") {
return &os.PathError{
Op: "remove",
Path: r.Filepath + "/",
Err: fmt.Errorf("directory is not empty"),
}
}
}
}
delete(fs.files, r.Filepath)
case "Mkdir":
_, err := fs.fetch(filepath.Dir(r.Filepath))
if err != nil {
return err
}
fs.files[r.Filepath] = newMemFile(r.Filepath, true)
case "Link":
file, err := fs.fetch(r.Filepath)
if err != nil {
return err
}
if file.IsDir() {
return fmt.Errorf("hard link not allowed for directory")
}
fs.files[r.Target] = file
case "Symlink":
_, err := fs.fetch(r.Filepath)
if err != nil {
@@ -147,15 +181,15 @@ func (fs *root) Filelist(r *Request) (ListerAt, error) {
if !file.IsDir() {
return nil, syscall.ENOTDIR
}
ordered_names := []string{}
for fn, _ := range fs.files {
orderedNames := []string{}
for fn := range fs.files {
if filepath.Dir(fn) == r.Filepath {
ordered_names = append(ordered_names, fn)
orderedNames = append(orderedNames, fn)
}
}
sort.Strings(ordered_names)
list := make([]os.FileInfo, len(ordered_names))
for i, fn := range ordered_names {
sort.Strings(orderedNames)
list := make([]os.FileInfo, len(orderedNames))
for i, fn := range orderedNames {
list[i] = fs.files[fn]
}
return listerat(list), nil
@@ -199,13 +233,15 @@ func (fs *root) fetch(path string) (*memFile, error) {
// Implements os.FileInfo, Reader and Writer interfaces.
// These are the 3 interfaces necessary for the Handlers.
// Implements the optional interface TransferError.
type memFile struct {
name string
modtime time.Time
symlink string
isdir bool
content []byte
contentLock sync.RWMutex
name string
modtime time.Time
symlink string
isdir bool
content []byte
transferError error
contentLock sync.RWMutex
}
// factory to make sure modtime is set
@@ -265,3 +301,7 @@ func (f *memFile) WriteAt(p []byte, off int64) (int, error) {
copy(f.content[off:], p)
return len(p), nil
}
func (f *memFile) TransferError(err error) {
f.transferError = err
}
+10 -1
View File
@@ -25,6 +25,8 @@ type FileReader interface {
// The request server code will call Close() on the returned io.WriterAt
// ojbect if an io.Closer type assertion succeeds.
// Note in cases of an error, the error text will be sent to the client.
// Note when receiving an Append flag it is important to not open files using
// O_APPEND if you plan to use WriteAt, as they conflict.
// Called for Methods: Put, Open
type FileWriter interface {
Filewrite(*Request) (io.WriterAt, error)
@@ -32,7 +34,7 @@ type FileWriter interface {
// FileCmder should return an error
// Note in cases of an error, the error text will be sent to the client.
// Called for Methods: Setstat, Rename, Rmdir, Mkdir, Symlink, Remove
// Called for Methods: Setstat, Rename, Rmdir, Mkdir, Link, Symlink, Remove
type FileCmder interface {
Filecmd(*Request) error
}
@@ -53,3 +55,10 @@ type FileLister interface {
type ListerAt interface {
ListAt([]os.FileInfo, int64) (int, error)
}
// TransferError is an optional interface that readerAt and writerAt
// can implement to be notified about the error causing Serve() to exit
// with the request still open
type TransferError interface {
TransferError(err error)
}
+28 -8
View File
@@ -116,11 +116,7 @@ func (rs *RequestServer) Serve() error {
if err != nil {
switch errors.Cause(err) {
case errUnknownExtendedPacket:
if err := rs.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
debug("failed to send err packet: %v", err)
rs.conn.Close() // shuts down recvPacket
break
}
// do nothing
default:
debug("makePacket err: %v", err)
rs.conn.Close() // shuts down recvPacket
@@ -137,6 +133,20 @@ func (rs *RequestServer) Serve() error {
// make sure all open requests are properly closed
// (eg. possible on dropped connections, client crashes, etc.)
for handle, req := range rs.openRequests {
if err != nil {
req.state.RLock()
writer := req.state.writerAt
reader := req.state.readerAt
req.state.RUnlock()
if t, ok := writer.(TransferError); ok {
debug("notify error: %v to writer: %v\n", err, writer)
t.TransferError(err)
}
if t, ok := reader.(TransferError); ok {
debug("notify error: %v to reader: %v\n", err, reader)
t.TransferError(err)
}
}
delete(rs.openRequests, handle)
req.close()
}
@@ -148,10 +158,16 @@ func (rs *RequestServer) packetWorker(
ctx context.Context, pktChan chan orderedRequest,
) error {
for pkt := range pktChan {
if epkt, ok := pkt.requestPacket.(*sshFxpExtendedPacket); ok {
if epkt.SpecificPacket != nil {
pkt.requestPacket = epkt.SpecificPacket
}
}
var rpkt responsePacket
switch pkt := pkt.requestPacket.(type) {
case *sshFxInitPacket:
rpkt = sshFxVersionPacket{Version: sftpProtocolVersion}
rpkt = sshFxVersionPacket{Version: sftpProtocolVersion, Extensions: sftpExtensions}
case *sshFxpClosePacket:
handle := pkt.getHandle()
rpkt = statusFromError(pkt, rs.closeRequest(handle))
@@ -174,6 +190,10 @@ func (rs *RequestServer) packetWorker(
request = NewRequest("Stat", request.Filepath)
rpkt = request.call(rs.Handlers, pkt)
}
case *sshFxpExtendedPacketPosixRename:
request := NewRequest("Rename", pkt.Oldpath)
request.Target = pkt.Newpath
rpkt = request.call(rs.Handlers, pkt)
case hasHandle:
handle := pkt.getHandle()
request, ok := rs.getRequest(handle)
@@ -187,11 +207,11 @@ func (rs *RequestServer) packetWorker(
rpkt = request.call(rs.Handlers, pkt)
request.close()
default:
return errors.Errorf("unexpected packet type %T", pkt)
rpkt = statusFromError(pkt, ErrSSHFxOpUnsupported)
}
rs.pktMgr.readyPacket(
rs.pktMgr.newOrderedResponse(rpkt, pkt.orderId()))
rs.pktMgr.newOrderedResponse(rpkt, pkt.orderID()))
}
return nil
}
+2 -2
View File
@@ -14,10 +14,10 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error {
fstat := sys.(*FileStat)
if fstat.UID != uint32(65534) {
return errors.New("Uid failed to match.")
return errors.New("Uid failed to match")
}
if fstat.GID != uint32(65534) {
return errors.New("Gid failed to match:")
return errors.New("Gid failed to match")
}
return nil
}
+6 -2
View File
@@ -18,7 +18,7 @@ var MaxFilelist int64 = 100
// Request contains the data and state for the incoming service request.
type Request struct {
// Get, Put, Setstat, Stat, Rename, Remove
// Rmdir, Mkdir, List, Readlink, Symlink
// Rmdir, Mkdir, List, Readlink, Link, Symlink
Method string
Filepath string
Flags uint32
@@ -56,6 +56,8 @@ func requestFromPacket(ctx context.Context, pkt hasPath) *Request {
request.Target = cleanPath(p.Newpath)
case *sshFxpSymlinkPacket:
request.Target = cleanPath(p.Linkpath)
case *sshFxpExtendedPacketHardlink:
request.Target = cleanPath(p.Newpath)
}
return request
}
@@ -158,7 +160,7 @@ func (r *Request) call(handlers Handlers, pkt requestPacket) responsePacket {
return fileget(handlers.FileGet, r, pkt)
case "Put":
return fileput(handlers.FilePut, r, pkt)
case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
case "Setstat", "Rename", "Rmdir", "Mkdir", "Link", "Symlink", "Remove":
return filecmd(handlers.FileCmd, r, pkt)
case "List":
return filelist(handlers.FileList, r, pkt)
@@ -378,6 +380,8 @@ func requestMethod(p requestPacket) (method string) {
method = "Readlink"
case *sshFxpMkdirPacket:
method = "Mkdir"
case *sshFxpExtendedPacketHardlink:
method = "Link"
}
return method
}
+55 -45
View File
@@ -18,6 +18,7 @@ import (
)
const (
// SftpServerWorkerCount defines the number of workers for the SFTP server
SftpServerWorkerCount = 8
)
@@ -141,7 +142,7 @@ func (svr *Server) sftpServerWorker(pktChan chan orderedRequest) error {
if !readonly && svr.readOnly {
svr.sendPacket(orderedResponse{
responsePacket: statusFromError(pkt, syscall.EPERM),
orderid: pkt.orderId()})
orderid: pkt.orderID()})
continue
}
@@ -156,7 +157,10 @@ func handlePacket(s *Server, p orderedRequest) error {
var rpkt responsePacket
switch p := p.requestPacket.(type) {
case *sshFxInitPacket:
rpkt = sshFxVersionPacket{Version: sftpProtocolVersion}
rpkt = sshFxVersionPacket{
Version: sftpProtocolVersion,
Extensions: sftpExtensions,
}
case *sshFxpStatPacket:
// stat the requested file
info, err := os.Stat(p.Path)
@@ -246,7 +250,7 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = sshFxpOpenPacket{
ID: p.ID,
Path: p.Path,
Pflags: ssh_FXF_READ,
Pflags: sshFxfRead,
}.respond(s)
}
case *sshFxpReadPacket:
@@ -276,13 +280,19 @@ func handlePacket(s *Server, p orderedRequest) error {
_, err = f.WriteAt(p.Data, int64(p.Offset))
}
rpkt = statusFromError(p, err)
case *sshFxpExtendedPacket:
if p.SpecificPacket == nil {
rpkt = statusFromError(p, ErrSSHFxOpUnsupported)
} else {
rpkt = p.respond(s)
}
case serverRespondablePacket:
rpkt = p.respond(s)
default:
return errors.Errorf("unexpected packet type %T", p)
}
s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, p.orderId()))
s.pktMgr.readyPacket(s.pktMgr.newOrderedResponse(rpkt, p.orderID()))
return nil
}
@@ -315,11 +325,11 @@ func (svr *Server) Serve() error {
if err != nil {
switch errors.Cause(err) {
case errUnknownExtendedPacket:
if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
debug("failed to send err packet: %v", err)
svr.conn.Close() // shuts down recvPacket
break
}
//if err := svr.serverConn.sendError(pkt, ErrSshFxOpUnsupported); err != nil {
// debug("failed to send err packet: %v", err)
// svr.conn.Close() // shuts down recvPacket
// break
//}
default:
debug("makePacket err: %v", err)
svr.conn.Close() // shuts down recvPacket
@@ -354,7 +364,7 @@ type sshFxpStatResponse struct {
}
func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
b := []byte{ssh_FXP_ATTRS}
b := []byte{sshFxpAttrs}
b = marshalUint32(b, p.ID)
b = marshalFileInfo(b, p.info)
return b, nil
@@ -363,7 +373,7 @@ func (p sshFxpStatResponse) MarshalBinary() ([]byte, error) {
var emptyFileStat = []interface{}{uint32(0)}
func (p sshFxpOpenPacket) readonly() bool {
return !p.hasPflags(ssh_FXF_WRITE)
return !p.hasPflags(sshFxfWrite)
}
func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
@@ -377,27 +387,27 @@ func (p sshFxpOpenPacket) hasPflags(flags ...uint32) bool {
func (p sshFxpOpenPacket) respond(svr *Server) responsePacket {
var osFlags int
if p.hasPflags(ssh_FXF_READ, ssh_FXF_WRITE) {
if p.hasPflags(sshFxfRead, sshFxfWrite) {
osFlags |= os.O_RDWR
} else if p.hasPflags(ssh_FXF_WRITE) {
} else if p.hasPflags(sshFxfWrite) {
osFlags |= os.O_WRONLY
} else if p.hasPflags(ssh_FXF_READ) {
} else if p.hasPflags(sshFxfRead) {
osFlags |= os.O_RDONLY
} else {
// how are they opening?
return statusFromError(p, syscall.EINVAL)
}
if p.hasPflags(ssh_FXF_APPEND) {
osFlags |= os.O_APPEND
}
if p.hasPflags(ssh_FXF_CREAT) {
// Don't use O_APPEND flag as it conflicts with WriteAt.
// The sshFxfAppend flag is a no-op here as the client sends the offsets.
if p.hasPflags(sshFxfCreat) {
osFlags |= os.O_CREATE
}
if p.hasPflags(ssh_FXF_TRUNC) {
if p.hasPflags(sshFxfTrunc) {
osFlags |= os.O_TRUNC
}
if p.hasPflags(ssh_FXF_EXCL) {
if p.hasPflags(sshFxfExcl) {
osFlags |= os.O_EXCL
}
@@ -439,19 +449,19 @@ func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
var err error
debug("setstat name \"%s\"", p.Path)
if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
if (p.Flags & sshFileXferAttrSize) != 0 {
var size uint64
if size, b, err = unmarshalUint64Safe(b); err == nil {
err = os.Truncate(p.Path, int64(size))
}
}
if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
if (p.Flags & sshFileXferAttrPermissions) != 0 {
var mode uint32
if mode, b, err = unmarshalUint32Safe(b); err == nil {
err = os.Chmod(p.Path, os.FileMode(mode))
}
}
if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
if (p.Flags & sshFileXferAttrACmodTime) != 0 {
var atime uint32
var mtime uint32
if atime, b, err = unmarshalUint32Safe(b); err != nil {
@@ -462,7 +472,7 @@ func (p sshFxpSetstatPacket) respond(svr *Server) responsePacket {
err = os.Chtimes(p.Path, atimeT, mtimeT)
}
}
if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
if (p.Flags & sshFileXferAttrUIDGID) != 0 {
var uid uint32
var gid uint32
if uid, b, err = unmarshalUint32Safe(b); err != nil {
@@ -486,19 +496,19 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
var err error
debug("fsetstat name \"%s\"", f.Name())
if (p.Flags & ssh_FILEXFER_ATTR_SIZE) != 0 {
if (p.Flags & sshFileXferAttrSize) != 0 {
var size uint64
if size, b, err = unmarshalUint64Safe(b); err == nil {
err = f.Truncate(int64(size))
}
}
if (p.Flags & ssh_FILEXFER_ATTR_PERMISSIONS) != 0 {
if (p.Flags & sshFileXferAttrPermissions) != 0 {
var mode uint32
if mode, b, err = unmarshalUint32Safe(b); err == nil {
err = f.Chmod(os.FileMode(mode))
}
}
if (p.Flags & ssh_FILEXFER_ATTR_ACMODTIME) != 0 {
if (p.Flags & sshFileXferAttrACmodTime) != 0 {
var atime uint32
var mtime uint32
if atime, b, err = unmarshalUint32Safe(b); err != nil {
@@ -509,7 +519,7 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
err = os.Chtimes(f.Name(), atimeT, mtimeT)
}
}
if (p.Flags & ssh_FILEXFER_ATTR_UIDGID) != 0 {
if (p.Flags & sshFileXferAttrUIDGID) != 0 {
var uid uint32
var gid uint32
if uid, b, err = unmarshalUint32Safe(b); err != nil {
@@ -526,30 +536,30 @@ func (p sshFxpFsetstatPacket) respond(svr *Server) responsePacket {
func translateErrno(errno syscall.Errno) uint32 {
switch errno {
case 0:
return ssh_FX_OK
return sshFxOk
case syscall.ENOENT:
return ssh_FX_NO_SUCH_FILE
return sshFxNoSuchFile
case syscall.EPERM:
return ssh_FX_PERMISSION_DENIED
return sshFxPermissionDenied
}
return ssh_FX_FAILURE
return sshFxFailure
}
func statusFromError(p ider, err error) sshFxpStatusPacket {
ret := sshFxpStatusPacket{
ID: p.id(),
StatusError: StatusError{
// ssh_FX_OK = 0
// ssh_FX_EOF = 1
// ssh_FX_NO_SUCH_FILE = 2 ENOENT
// ssh_FX_PERMISSION_DENIED = 3
// ssh_FX_FAILURE = 4
// ssh_FX_BAD_MESSAGE = 5
// ssh_FX_NO_CONNECTION = 6
// ssh_FX_CONNECTION_LOST = 7
// ssh_FX_OP_UNSUPPORTED = 8
Code: ssh_FX_OK,
// sshFXOk = 0
// sshFXEOF = 1
// sshFXNoSuchFile = 2 ENOENT
// sshFXPermissionDenied = 3
// sshFXFailure = 4
// sshFXBadMessage = 5
// sshFXNoConnection = 6
// sshFXConnectionLost = 7
// sshFXOPUnsupported = 8
Code: sshFxOk,
},
}
if err == nil {
@@ -557,7 +567,7 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
}
debug("statusFromError: error is %T %#v", err, err)
ret.StatusError.Code = ssh_FX_FAILURE
ret.StatusError.Code = sshFxFailure
ret.StatusError.msg = err.Error()
switch e := err.(type) {
@@ -573,9 +583,9 @@ func statusFromError(p ider, err error) sshFxpStatusPacket {
default:
switch e {
case io.EOF:
ret.StatusError.Code = ssh_FX_EOF
ret.StatusError.Code = sshFxEOF
case os.ErrNotExist:
ret.StatusError.Code = ssh_FX_NO_SUCH_FILE
ret.StatusError.Code = sshFxNoSuchFile
}
}
+144 -102
View File
@@ -9,139 +9,148 @@ import (
)
const (
ssh_FXP_INIT = 1
ssh_FXP_VERSION = 2
ssh_FXP_OPEN = 3
ssh_FXP_CLOSE = 4
ssh_FXP_READ = 5
ssh_FXP_WRITE = 6
ssh_FXP_LSTAT = 7
ssh_FXP_FSTAT = 8
ssh_FXP_SETSTAT = 9
ssh_FXP_FSETSTAT = 10
ssh_FXP_OPENDIR = 11
ssh_FXP_READDIR = 12
ssh_FXP_REMOVE = 13
ssh_FXP_MKDIR = 14
ssh_FXP_RMDIR = 15
ssh_FXP_REALPATH = 16
ssh_FXP_STAT = 17
ssh_FXP_RENAME = 18
ssh_FXP_READLINK = 19
ssh_FXP_SYMLINK = 20
ssh_FXP_STATUS = 101
ssh_FXP_HANDLE = 102
ssh_FXP_DATA = 103
ssh_FXP_NAME = 104
ssh_FXP_ATTRS = 105
ssh_FXP_EXTENDED = 200
ssh_FXP_EXTENDED_REPLY = 201
sshFxpInit = 1
sshFxpVersion = 2
sshFxpOpen = 3
sshFxpClose = 4
sshFxpRead = 5
sshFxpWrite = 6
sshFxpLstat = 7
sshFxpFstat = 8
sshFxpSetstat = 9
sshFxpFsetstat = 10
sshFxpOpendir = 11
sshFxpReaddir = 12
sshFxpRemove = 13
sshFxpMkdir = 14
sshFxpRmdir = 15
sshFxpRealpath = 16
sshFxpStat = 17
sshFxpRename = 18
sshFxpReadlink = 19
sshFxpSymlink = 20
sshFxpStatus = 101
sshFxpHandle = 102
sshFxpData = 103
sshFxpName = 104
sshFxpAttrs = 105
sshFxpExtended = 200
sshFxpExtendedReply = 201
)
const (
ssh_FX_OK = 0
ssh_FX_EOF = 1
ssh_FX_NO_SUCH_FILE = 2
ssh_FX_PERMISSION_DENIED = 3
ssh_FX_FAILURE = 4
ssh_FX_BAD_MESSAGE = 5
ssh_FX_NO_CONNECTION = 6
ssh_FX_CONNECTION_LOST = 7
ssh_FX_OP_UNSUPPORTED = 8
sshFxOk = 0
sshFxEOF = 1
sshFxNoSuchFile = 2
sshFxPermissionDenied = 3
sshFxFailure = 4
sshFxBadMessage = 5
sshFxNoConnection = 6
sshFxConnectionLost = 7
sshFxOPUnsupported = 8
// see draft-ietf-secsh-filexfer-13
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1
ssh_FX_INVALID_HANDLE = 9
ssh_FX_NO_SUCH_PATH = 10
ssh_FX_FILE_ALREADY_EXISTS = 11
ssh_FX_WRITE_PROTECT = 12
ssh_FX_NO_MEDIA = 13
ssh_FX_NO_SPACE_ON_FILESYSTEM = 14
ssh_FX_QUOTA_EXCEEDED = 15
ssh_FX_UNKNOWN_PRINCIPAL = 16
ssh_FX_LOCK_CONFLICT = 17
ssh_FX_DIR_NOT_EMPTY = 18
ssh_FX_NOT_A_DIRECTORY = 19
ssh_FX_INVALID_FILENAME = 20
ssh_FX_LINK_LOOP = 21
ssh_FX_CANNOT_DELETE = 22
ssh_FX_INVALID_PARAMETER = 23
ssh_FX_FILE_IS_A_DIRECTORY = 24
ssh_FX_BYTE_RANGE_LOCK_CONFLICT = 25
ssh_FX_BYTE_RANGE_LOCK_REFUSED = 26
ssh_FX_DELETE_PENDING = 27
ssh_FX_FILE_CORRUPT = 28
ssh_FX_OWNER_INVALID = 29
ssh_FX_GROUP_INVALID = 30
ssh_FX_NO_MATCHING_BYTE_RANGE_LOCK = 31
sshFxInvalidHandle = 9
sshFxNoSuchPath = 10
sshFxFileAlreadyExists = 11
sshFxWriteProtect = 12
sshFxNoMedia = 13
sshFxNoSpaceOnFilesystem = 14
sshFxQuotaExceeded = 15
sshFxUnlnownPrincipal = 16
sshFxLockConflict = 17
sshFxDitNotEmpty = 18
sshFxNotADirectory = 19
sshFxInvalidFilename = 20
sshFxLinkLoop = 21
sshFxCannotDelete = 22
sshFxInvalidParameter = 23
sshFxFileIsADirectory = 24
sshFxByteRangeLockConflict = 25
sshFxByteRangeLockRefused = 26
sshFxDeletePending = 27
sshFxFileCorrupt = 28
sshFxOwnerInvalid = 29
sshFxGroupInvalid = 30
sshFxNoMatchingByteRangeLock = 31
)
const (
ssh_FXF_READ = 0x00000001
ssh_FXF_WRITE = 0x00000002
ssh_FXF_APPEND = 0x00000004
ssh_FXF_CREAT = 0x00000008
ssh_FXF_TRUNC = 0x00000010
ssh_FXF_EXCL = 0x00000020
sshFxfRead = 0x00000001
sshFxfWrite = 0x00000002
sshFxfAppend = 0x00000004
sshFxfCreat = 0x00000008
sshFxfTrunc = 0x00000010
sshFxfExcl = 0x00000020
)
var (
// supportedSFTPExtensions defines the supported extensions
supportedSFTPExtensions = []sshExtensionPair{
{"hardlink@openssh.com", "1"},
{"posix-rename@openssh.com", "1"},
}
sftpExtensions = supportedSFTPExtensions
)
type fxp uint8
func (f fxp) String() string {
switch f {
case ssh_FXP_INIT:
case sshFxpInit:
return "SSH_FXP_INIT"
case ssh_FXP_VERSION:
case sshFxpVersion:
return "SSH_FXP_VERSION"
case ssh_FXP_OPEN:
case sshFxpOpen:
return "SSH_FXP_OPEN"
case ssh_FXP_CLOSE:
case sshFxpClose:
return "SSH_FXP_CLOSE"
case ssh_FXP_READ:
case sshFxpRead:
return "SSH_FXP_READ"
case ssh_FXP_WRITE:
case sshFxpWrite:
return "SSH_FXP_WRITE"
case ssh_FXP_LSTAT:
case sshFxpLstat:
return "SSH_FXP_LSTAT"
case ssh_FXP_FSTAT:
case sshFxpFstat:
return "SSH_FXP_FSTAT"
case ssh_FXP_SETSTAT:
case sshFxpSetstat:
return "SSH_FXP_SETSTAT"
case ssh_FXP_FSETSTAT:
case sshFxpFsetstat:
return "SSH_FXP_FSETSTAT"
case ssh_FXP_OPENDIR:
case sshFxpOpendir:
return "SSH_FXP_OPENDIR"
case ssh_FXP_READDIR:
case sshFxpReaddir:
return "SSH_FXP_READDIR"
case ssh_FXP_REMOVE:
case sshFxpRemove:
return "SSH_FXP_REMOVE"
case ssh_FXP_MKDIR:
case sshFxpMkdir:
return "SSH_FXP_MKDIR"
case ssh_FXP_RMDIR:
case sshFxpRmdir:
return "SSH_FXP_RMDIR"
case ssh_FXP_REALPATH:
case sshFxpRealpath:
return "SSH_FXP_REALPATH"
case ssh_FXP_STAT:
case sshFxpStat:
return "SSH_FXP_STAT"
case ssh_FXP_RENAME:
case sshFxpRename:
return "SSH_FXP_RENAME"
case ssh_FXP_READLINK:
case sshFxpReadlink:
return "SSH_FXP_READLINK"
case ssh_FXP_SYMLINK:
case sshFxpSymlink:
return "SSH_FXP_SYMLINK"
case ssh_FXP_STATUS:
case sshFxpStatus:
return "SSH_FXP_STATUS"
case ssh_FXP_HANDLE:
case sshFxpHandle:
return "SSH_FXP_HANDLE"
case ssh_FXP_DATA:
case sshFxpData:
return "SSH_FXP_DATA"
case ssh_FXP_NAME:
case sshFxpName:
return "SSH_FXP_NAME"
case ssh_FXP_ATTRS:
case sshFxpAttrs:
return "SSH_FXP_ATTRS"
case ssh_FXP_EXTENDED:
case sshFxpExtended:
return "SSH_FXP_EXTENDED"
case ssh_FXP_EXTENDED_REPLY:
case sshFxpExtendedReply:
return "SSH_FXP_EXTENDED_REPLY"
default:
return "unknown"
@@ -152,23 +161,23 @@ type fx uint8
func (f fx) String() string {
switch f {
case ssh_FX_OK:
case sshFxOk:
return "SSH_FX_OK"
case ssh_FX_EOF:
case sshFxEOF:
return "SSH_FX_EOF"
case ssh_FX_NO_SUCH_FILE:
case sshFxNoSuchFile:
return "SSH_FX_NO_SUCH_FILE"
case ssh_FX_PERMISSION_DENIED:
case sshFxPermissionDenied:
return "SSH_FX_PERMISSION_DENIED"
case ssh_FX_FAILURE:
case sshFxFailure:
return "SSH_FX_FAILURE"
case ssh_FX_BAD_MESSAGE:
case sshFxBadMessage:
return "SSH_FX_BAD_MESSAGE"
case ssh_FX_NO_CONNECTION:
case sshFxNoConnection:
return "SSH_FX_NO_CONNECTION"
case ssh_FX_CONNECTION_LOST:
case sshFxConnectionLost:
return "SSH_FX_CONNECTION_LOST"
case ssh_FX_OP_UNSUPPORTED:
case sshFxOPUnsupported:
return "SSH_FX_OP_UNSUPPORTED"
default:
return "unknown"
@@ -214,4 +223,37 @@ type StatusError struct {
msg, lang string
}
func (s *StatusError) Error() string { return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code)) }
func (s *StatusError) Error() string {
return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code))
}
// FxCode returns the error code typed to match against the exported codes
func (s *StatusError) FxCode() fxerr {
return fxerr(s.Code)
}
func getSupportedExtensionByName(extensionName string) (sshExtensionPair, error) {
for _, supportedExtension := range supportedSFTPExtensions {
if supportedExtension.Name == extensionName {
return supportedExtension, nil
}
}
return sshExtensionPair{}, fmt.Errorf("Unsupported extension: %v", extensionName)
}
// SetSFTPExtensions allows to customize the supported server extensions.
// See the variable supportedSFTPExtensions for supported extensions.
// This method accepts a slice of sshExtensionPair names for example 'hardlink@openssh.com'.
// If an invalid extension is given an error will be returned and nothing will be changed
func SetSFTPExtensions(extensions ...string) error {
tempExtensions := []sshExtensionPair{}
for _, extension := range extensions {
sftpExtension, err := getSupportedExtensionByName(extension)
if err != nil {
return err
}
tempExtensions = append(tempExtensions, sftpExtension)
}
sftpExtensions = tempExtensions
return nil
}
+9
View File
@@ -0,0 +1,9 @@
// +build plan9 windows js,wasm
// Go defines S_IFMT on windows, plan9 and js/wasm as 0x1f000 instead of
// 0xf000. None of the the other S_IFxyz values include the "1" (in 0x1f000)
// which prevents them from matching the bitmask.
package sftp
const S_IFMT = 0xf000
+8
View File
@@ -0,0 +1,8 @@
// +build !plan9,!windows
// +build !js !wasm
package sftp
import "syscall"
const S_IFMT = syscall.S_IFMT