Skip to content

Commit c7065ea

Browse files
committed
server.go: "/" for windows
1 parent 22452ea commit c7065ea

File tree

3 files changed

+144
-5
lines changed

3 files changed

+144
-5
lines changed

server.go

+51-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"errors"
88
"fmt"
99
"io"
10+
"io/fs"
1011
"io/ioutil"
1112
"os"
1213
"path/filepath"
@@ -21,6 +22,51 @@ const (
2122
SftpServerWorkerCount = 8
2223
)
2324

25+
type FileLike interface {
26+
Stat() (os.FileInfo, error)
27+
ReadAt(b []byte, off int64) (int, error)
28+
WriteAt(b []byte, off int64) (int, error)
29+
Readdir(int) ([]os.FileInfo, error)
30+
Name() string
31+
Truncate(int64) error
32+
Chmod(mode fs.FileMode) error
33+
Chown(uid, gid int) error
34+
Close() error
35+
}
36+
37+
type dummyFile struct {
38+
}
39+
40+
func (f *dummyFile) Stat() (os.FileInfo, error) {
41+
return nil, os.ErrPermission
42+
}
43+
func (f *dummyFile) ReadAt(b []byte, off int64) (int, error) {
44+
return 0, os.ErrPermission
45+
}
46+
func (f *dummyFile) WriteAt(b []byte, off int64) (int, error) {
47+
return 0, os.ErrPermission
48+
}
49+
func (f *dummyFile) Readdir(int) ([]os.FileInfo, error) {
50+
return nil, os.ErrPermission
51+
}
52+
func (f *dummyFile) Name() string {
53+
return "dummyFile"
54+
}
55+
func (f *dummyFile) Truncate(int64) error {
56+
return os.ErrPermission
57+
}
58+
func (f *dummyFile) Chmod(mode fs.FileMode) error {
59+
return os.ErrPermission
60+
}
61+
func (f *dummyFile) Chown(uid, gid int) error {
62+
return os.ErrPermission
63+
}
64+
func (f *dummyFile) Close() error {
65+
return os.ErrPermission
66+
}
67+
68+
var _ = dummyFile{} // ignore unused
69+
2470
// Server is an SSH File Transfer Protocol (sftp) server.
2571
// This is intended to provide the sftp subsystem to an ssh server daemon.
2672
// This implementation currently supports most of sftp server protocol version 3,
@@ -30,13 +76,13 @@ type Server struct {
3076
debugStream io.Writer
3177
readOnly bool
3278
pktMgr *packetManager
33-
openFiles map[string]*os.File
79+
openFiles map[string]FileLike
3480
openFilesLock sync.RWMutex
3581
handleCount int
3682
workDir string
3783
}
3884

39-
func (svr *Server) nextHandle(f *os.File) string {
85+
func (svr *Server) nextHandle(f FileLike) string {
4086
svr.openFilesLock.Lock()
4187
defer svr.openFilesLock.Unlock()
4288
svr.handleCount++
@@ -56,7 +102,7 @@ func (svr *Server) closeHandle(handle string) error {
56102
return EBADF
57103
}
58104

59-
func (svr *Server) getHandle(handle string) (*os.File, bool) {
105+
func (svr *Server) getHandle(handle string) (FileLike, bool) {
60106
svr.openFilesLock.RLock()
61107
defer svr.openFilesLock.RUnlock()
62108
f, ok := svr.openFiles[handle]
@@ -85,7 +131,7 @@ func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error)
85131
serverConn: svrConn,
86132
debugStream: ioutil.Discard,
87133
pktMgr: newPktMgr(svrConn),
88-
openFiles: make(map[string]*os.File),
134+
openFiles: make(map[string]FileLike),
89135
}
90136

91137
for _, o := range options {
@@ -462,7 +508,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
462508
osFlags |= os.O_EXCL
463509
}
464510

465-
f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, 0o644)
511+
f, err := openFileLike(svr.toLocalPath(p.Path), osFlags, 0o644)
466512
if err != nil {
467513
return statusFromError(p.ID, err)
468514
}

server_posix.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
package sftp
5+
6+
import (
7+
"io/fs"
8+
"os"
9+
)
10+
11+
func openFileLike(path string, flag int, mode fs.FileMode) (FileLike, error) {
12+
return os.OpenFile(path, flag, mode)
13+
}

server_windows.go

+80
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
//go:build go1.18
2+
13
package sftp
24

35
import (
6+
"fmt"
7+
"io/fs"
8+
"os"
49
"path"
510
"path/filepath"
11+
"syscall"
12+
"time"
613
)
714

815
func (s *Server) toLocalPath(p string) string {
@@ -37,3 +44,76 @@ func (s *Server) toLocalPath(p string) string {
3744

3845
return lp
3946
}
47+
48+
var kernel32, _ = syscall.LoadLibrary("kernel32.dll")
49+
var getLogicalDrivesHandle, _ = syscall.GetProcAddress(kernel32, "GetLogicalDrives")
50+
51+
func bitsToDrives(bitmap uint32) []string {
52+
var drive rune = 'A'
53+
var drives []string
54+
55+
for bitmap != 0 {
56+
if bitmap&1 == 1 {
57+
drives = append(drives, string(drive))
58+
}
59+
drive++
60+
bitmap >>= 1
61+
}
62+
63+
return drives
64+
}
65+
66+
func getDrives() ([]string, error) {
67+
if ret, _, callErr := syscall.Syscall(uintptr(getLogicalDrivesHandle), 0, 0, 0, 0); callErr != 0 {
68+
return nil, fmt.Errorf("GetLogicalDrives: %w", callErr)
69+
} else {
70+
drives := bitsToDrives(uint32(ret))
71+
return drives, nil
72+
}
73+
}
74+
75+
type dummyDriveStat struct {
76+
name string
77+
}
78+
79+
func (s *dummyDriveStat) Name() string {
80+
return s.name
81+
}
82+
func (s *dummyDriveStat) Size() int64 {
83+
return 1024
84+
}
85+
func (s *dummyDriveStat) Mode() os.FileMode {
86+
return os.FileMode(0755)
87+
}
88+
func (s *dummyDriveStat) ModTime() time.Time {
89+
return time.Now()
90+
}
91+
func (s *dummyDriveStat) IsDir() bool {
92+
return true
93+
}
94+
func (s *dummyDriveStat) Sys() any {
95+
return nil
96+
}
97+
98+
type WinRoot struct {
99+
dummyFile
100+
}
101+
102+
func (f *WinRoot) Readdir(int) ([]os.FileInfo, error) {
103+
drives, err := getDrives()
104+
if err != nil {
105+
return nil, err
106+
}
107+
infos := []os.FileInfo{}
108+
for _, drive := range drives {
109+
infos = append(infos, &dummyDriveStat{drive})
110+
}
111+
return infos, nil
112+
}
113+
114+
func openFileLike(path string, flag int, mode fs.FileMode) (FileLike, error) {
115+
if path == "/" {
116+
return &WinRoot{}, nil
117+
}
118+
return os.OpenFile(path, flag, mode)
119+
}

0 commit comments

Comments
 (0)