From c7065eab39e0657879b6c5ed59830c0cdaac4bb2 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Sat, 6 Jan 2024 13:04:19 +0100 Subject: [PATCH 01/11] server.go: "/" for windows --- server.go | 56 ++++++++++++++++++++++++++++++--- server_posix.go | 13 ++++++++ server_windows.go | 80 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 server_posix.go diff --git a/server.go b/server.go index 2e419f59..0c990be8 100644 --- a/server.go +++ b/server.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "io" + "io/fs" "io/ioutil" "os" "path/filepath" @@ -21,6 +22,51 @@ const ( SftpServerWorkerCount = 8 ) +type FileLike interface { + Stat() (os.FileInfo, error) + ReadAt(b []byte, off int64) (int, error) + WriteAt(b []byte, off int64) (int, error) + Readdir(int) ([]os.FileInfo, error) + Name() string + Truncate(int64) error + Chmod(mode fs.FileMode) error + Chown(uid, gid int) error + Close() error +} + +type dummyFile struct { +} + +func (f *dummyFile) Stat() (os.FileInfo, error) { + return nil, os.ErrPermission +} +func (f *dummyFile) ReadAt(b []byte, off int64) (int, error) { + return 0, os.ErrPermission +} +func (f *dummyFile) WriteAt(b []byte, off int64) (int, error) { + return 0, os.ErrPermission +} +func (f *dummyFile) Readdir(int) ([]os.FileInfo, error) { + return nil, os.ErrPermission +} +func (f *dummyFile) Name() string { + return "dummyFile" +} +func (f *dummyFile) Truncate(int64) error { + return os.ErrPermission +} +func (f *dummyFile) Chmod(mode fs.FileMode) error { + return os.ErrPermission +} +func (f *dummyFile) Chown(uid, gid int) error { + return os.ErrPermission +} +func (f *dummyFile) Close() error { + return os.ErrPermission +} + +var _ = dummyFile{} // ignore unused + // Server is an SSH File Transfer Protocol (sftp) server. // This is intended to provide the sftp subsystem to an ssh server daemon. // This implementation currently supports most of sftp server protocol version 3, @@ -30,13 +76,13 @@ type Server struct { debugStream io.Writer readOnly bool pktMgr *packetManager - openFiles map[string]*os.File + openFiles map[string]FileLike openFilesLock sync.RWMutex handleCount int workDir string } -func (svr *Server) nextHandle(f *os.File) string { +func (svr *Server) nextHandle(f FileLike) string { svr.openFilesLock.Lock() defer svr.openFilesLock.Unlock() svr.handleCount++ @@ -56,7 +102,7 @@ func (svr *Server) closeHandle(handle string) error { return EBADF } -func (svr *Server) getHandle(handle string) (*os.File, bool) { +func (svr *Server) getHandle(handle string) (FileLike, bool) { svr.openFilesLock.RLock() defer svr.openFilesLock.RUnlock() f, ok := svr.openFiles[handle] @@ -85,7 +131,7 @@ func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) serverConn: svrConn, debugStream: ioutil.Discard, pktMgr: newPktMgr(svrConn), - openFiles: make(map[string]*os.File), + openFiles: make(map[string]FileLike), } for _, o := range options { @@ -462,7 +508,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket { osFlags |= os.O_EXCL } - f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, 0o644) + f, err := openFileLike(svr.toLocalPath(p.Path), osFlags, 0o644) if err != nil { return statusFromError(p.ID, err) } diff --git a/server_posix.go b/server_posix.go new file mode 100644 index 00000000..0fbb056c --- /dev/null +++ b/server_posix.go @@ -0,0 +1,13 @@ +//go:build !windows +// +build !windows + +package sftp + +import ( + "io/fs" + "os" +) + +func openFileLike(path string, flag int, mode fs.FileMode) (FileLike, error) { + return os.OpenFile(path, flag, mode) +} diff --git a/server_windows.go b/server_windows.go index b35be730..68bf4a87 100644 --- a/server_windows.go +++ b/server_windows.go @@ -1,8 +1,15 @@ +//go:build go1.18 + package sftp import ( + "fmt" + "io/fs" + "os" "path" "path/filepath" + "syscall" + "time" ) func (s *Server) toLocalPath(p string) string { @@ -37,3 +44,76 @@ func (s *Server) toLocalPath(p string) string { return lp } + +var kernel32, _ = syscall.LoadLibrary("kernel32.dll") +var getLogicalDrivesHandle, _ = syscall.GetProcAddress(kernel32, "GetLogicalDrives") + +func bitsToDrives(bitmap uint32) []string { + var drive rune = 'A' + var drives []string + + for bitmap != 0 { + if bitmap&1 == 1 { + drives = append(drives, string(drive)) + } + drive++ + bitmap >>= 1 + } + + return drives +} + +func getDrives() ([]string, error) { + if ret, _, callErr := syscall.Syscall(uintptr(getLogicalDrivesHandle), 0, 0, 0, 0); callErr != 0 { + return nil, fmt.Errorf("GetLogicalDrives: %w", callErr) + } else { + drives := bitsToDrives(uint32(ret)) + return drives, nil + } +} + +type dummyDriveStat struct { + name string +} + +func (s *dummyDriveStat) Name() string { + return s.name +} +func (s *dummyDriveStat) Size() int64 { + return 1024 +} +func (s *dummyDriveStat) Mode() os.FileMode { + return os.FileMode(0755) +} +func (s *dummyDriveStat) ModTime() time.Time { + return time.Now() +} +func (s *dummyDriveStat) IsDir() bool { + return true +} +func (s *dummyDriveStat) Sys() any { + return nil +} + +type WinRoot struct { + dummyFile +} + +func (f *WinRoot) Readdir(int) ([]os.FileInfo, error) { + drives, err := getDrives() + if err != nil { + return nil, err + } + infos := []os.FileInfo{} + for _, drive := range drives { + infos = append(infos, &dummyDriveStat{drive}) + } + return infos, nil +} + +func openFileLike(path string, flag int, mode fs.FileMode) (FileLike, error) { + if path == "/" { + return &WinRoot{}, nil + } + return os.OpenFile(path, flag, mode) +} From 931e169fabea7189f423e164f67202ace617e5c9 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Sun, 7 Jan 2024 12:49:09 +0100 Subject: [PATCH 02/11] review adjustments --- server.go | 12 +++---- server_posix.go | 2 +- server_windows.go | 79 +++++++++++++++++++++++++---------------------- 3 files changed, 49 insertions(+), 44 deletions(-) diff --git a/server.go b/server.go index 0c990be8..24f5f4e9 100644 --- a/server.go +++ b/server.go @@ -22,7 +22,7 @@ const ( SftpServerWorkerCount = 8 ) -type FileLike interface { +type file interface { Stat() (os.FileInfo, error) ReadAt(b []byte, off int64) (int, error) WriteAt(b []byte, off int64) (int, error) @@ -76,13 +76,13 @@ type Server struct { debugStream io.Writer readOnly bool pktMgr *packetManager - openFiles map[string]FileLike + openFiles map[string]file openFilesLock sync.RWMutex handleCount int workDir string } -func (svr *Server) nextHandle(f FileLike) string { +func (svr *Server) nextHandle(f file) string { svr.openFilesLock.Lock() defer svr.openFilesLock.Unlock() svr.handleCount++ @@ -102,7 +102,7 @@ func (svr *Server) closeHandle(handle string) error { return EBADF } -func (svr *Server) getHandle(handle string) (FileLike, bool) { +func (svr *Server) getHandle(handle string) (file, bool) { svr.openFilesLock.RLock() defer svr.openFilesLock.RUnlock() f, ok := svr.openFiles[handle] @@ -131,7 +131,7 @@ func NewServer(rwc io.ReadWriteCloser, options ...ServerOption) (*Server, error) serverConn: svrConn, debugStream: ioutil.Discard, pktMgr: newPktMgr(svrConn), - openFiles: make(map[string]FileLike), + openFiles: make(map[string]file), } for _, o := range options { @@ -508,7 +508,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket { osFlags |= os.O_EXCL } - f, err := openFileLike(svr.toLocalPath(p.Path), osFlags, 0o644) + f, err := openfile(svr.toLocalPath(p.Path), osFlags, 0o644) if err != nil { return statusFromError(p.ID, err) } diff --git a/server_posix.go b/server_posix.go index 0fbb056c..935a7aa2 100644 --- a/server_posix.go +++ b/server_posix.go @@ -8,6 +8,6 @@ import ( "os" ) -func openFileLike(path string, flag int, mode fs.FileMode) (FileLike, error) { +func openfile(path string, flag int, mode fs.FileMode) (file, error) { return os.OpenFile(path, flag, mode) } diff --git a/server_windows.go b/server_windows.go index 68bf4a87..fad5e188 100644 --- a/server_windows.go +++ b/server_windows.go @@ -4,12 +4,13 @@ package sftp import ( "fmt" + "io" "io/fs" "os" "path" "path/filepath" - "syscall" - "time" + + "golang.org/x/sys/windows" ) func (s *Server) toLocalPath(p string) string { @@ -45,16 +46,13 @@ func (s *Server) toLocalPath(p string) string { return lp } -var kernel32, _ = syscall.LoadLibrary("kernel32.dll") -var getLogicalDrivesHandle, _ = syscall.GetProcAddress(kernel32, "GetLogicalDrives") - func bitsToDrives(bitmap uint32) []string { - var drive rune = 'A' + var drive rune = 'a' var drives []string for bitmap != 0 { if bitmap&1 == 1 { - drives = append(drives, string(drive)) + drives = append(drives, string(drive)+":") } drive++ bitmap >>= 1 @@ -64,56 +62,63 @@ func bitsToDrives(bitmap uint32) []string { } func getDrives() ([]string, error) { - if ret, _, callErr := syscall.Syscall(uintptr(getLogicalDrivesHandle), 0, 0, 0, 0); callErr != 0 { - return nil, fmt.Errorf("GetLogicalDrives: %w", callErr) - } else { - drives := bitsToDrives(uint32(ret)) - return drives, nil + mask, err := windows.GetLogicalDrives() + if err != nil { + return nil, fmt.Errorf("GetLogicalDrives: %w", err) } + return bitsToDrives(mask), nil } -type dummyDriveStat struct { +type driveInfo struct { + fs.FileInfo name string } -func (s *dummyDriveStat) Name() string { - return s.name -} -func (s *dummyDriveStat) Size() int64 { - return 1024 -} -func (s *dummyDriveStat) Mode() os.FileMode { - return os.FileMode(0755) -} -func (s *dummyDriveStat) ModTime() time.Time { - return time.Now() -} -func (s *dummyDriveStat) IsDir() bool { - return true -} -func (s *dummyDriveStat) Sys() any { - return nil +func (i *driveInfo) Name() string { + return i.name // since the Name() returned from a os.Stat("C:\\") is "\\" } -type WinRoot struct { +type winRoot struct { dummyFile + doneDirs int } -func (f *WinRoot) Readdir(int) ([]os.FileInfo, error) { +func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { drives, err := getDrives() if err != nil { return nil, err } - infos := []os.FileInfo{} - for _, drive := range drives { - infos = append(infos, &dummyDriveStat{drive}) + + if f.doneDirs >= len(drives) { + return nil, io.EOF } + drives = drives[f.doneDirs:] + + var infos []os.FileInfo + for i, drive := range drives { + if i >= n { + break + } + + fi, err := os.Stat(drive) + if err != nil { + return nil, err + } + + di := &driveInfo{ + FileInfo: fi, + name: drive, + } + infos = append(infos, di) + } + + f.doneDirs += len(infos) return infos, nil } -func openFileLike(path string, flag int, mode fs.FileMode) (FileLike, error) { +func openfile(path string, flag int, mode fs.FileMode) (file, error) { if path == "/" { - return &WinRoot{}, nil + return &winRoot{}, nil } return os.OpenFile(path, flag, mode) } From 2a3c7ed21de514f2c138e2efa95906fac880320e Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Tue, 9 Jan 2024 21:53:27 +0100 Subject: [PATCH 03/11] review adjustments 2 --- server_windows.go | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/server_windows.go b/server_windows.go index fad5e188..5aabf05f 100644 --- a/server_windows.go +++ b/server_windows.go @@ -1,5 +1,3 @@ -//go:build go1.18 - package sftp import ( @@ -80,26 +78,33 @@ func (i *driveInfo) Name() string { type winRoot struct { dummyFile - doneDirs int + drives []string } -func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { +func newWinRoot() (*winRoot, error) { drives, err := getDrives() if err != nil { return nil, err } + return &winRoot{ + drives: drives, + }, nil +} - if f.doneDirs >= len(drives) { - return nil, io.EOF +func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { + drives := f.drives + if n > 0 { + if len(drives) > n { + drives = drives[:n] + } + f.drives = f.drives[len(drives):] + if len(drives) == 0 { + return nil, io.EOF + } } - drives = drives[f.doneDirs:] var infos []os.FileInfo - for i, drive := range drives { - if i >= n { - break - } - + for _, drive := range drives { fi, err := os.Stat(drive) if err != nil { return nil, err @@ -112,13 +117,12 @@ func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { infos = append(infos, di) } - f.doneDirs += len(infos) return infos, nil } func openfile(path string, flag int, mode fs.FileMode) (file, error) { if path == "/" { - return &winRoot{}, nil + return newWinRoot() } return os.OpenFile(path, flag, mode) } From 38928181c7bd078b84cb6a2ec054971bd437cbf3 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Wed, 10 Jan 2024 13:32:51 +0100 Subject: [PATCH 04/11] review adjustments 3 --- server.go | 11 ++++++++++- server_posix.go | 2 +- server_windows.go | 31 +++++++++++++++++++------------ 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/server.go b/server.go index 24f5f4e9..6df82ee1 100644 --- a/server.go +++ b/server.go @@ -80,6 +80,7 @@ type Server struct { openFilesLock sync.RWMutex handleCount int workDir string + winRoot bool } func (svr *Server) nextHandle(f file) string { @@ -162,6 +163,14 @@ func ReadOnly() ServerOption { } } +// WinRoot configures a Server to serve a virtual '/' for windows that lists all drives +func WinRoot() ServerOption { + return func(s *Server) error { + s.winRoot = true + return nil + } +} + // WithAllocator enable the allocator. // After processing a packet we keep in memory the allocated slices // and we reuse them for new packets. @@ -508,7 +517,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket { osFlags |= os.O_EXCL } - f, err := openfile(svr.toLocalPath(p.Path), osFlags, 0o644) + f, err := svr.openfile(svr.toLocalPath(p.Path), osFlags, 0o644) if err != nil { return statusFromError(p.ID, err) } diff --git a/server_posix.go b/server_posix.go index 935a7aa2..6ca70199 100644 --- a/server_posix.go +++ b/server_posix.go @@ -8,6 +8,6 @@ import ( "os" ) -func openfile(path string, flag int, mode fs.FileMode) (file, error) { +func (s *Server) openfile(path string, flag int, mode fs.FileMode) (file, error) { return os.OpenFile(path, flag, mode) } diff --git a/server_windows.go b/server_windows.go index 5aabf05f..3a829ed5 100644 --- a/server_windows.go +++ b/server_windows.go @@ -18,7 +18,11 @@ func (s *Server) toLocalPath(p string) string { lp := filepath.FromSlash(p) - if path.IsAbs(p) { + if path.IsAbs(p) { // starts with '/' + if len(p) == 1 && s.winRoot { + return `\\.\` // for openfile + } + tmp := lp for len(tmp) > 0 && tmp[0] == '\\' { tmp = tmp[1:] @@ -39,6 +43,11 @@ func (s *Server) toLocalPath(p string) string { // e.g. "/C:" to "C:\\" return tmp } + + if s.winRoot { + // Make it so that "/Windows" is not found, and "/c:/Windows" has to be used + return `\\.\` + tmp + } } return lp @@ -93,19 +102,17 @@ func newWinRoot() (*winRoot, error) { func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { drives := f.drives - if n > 0 { - if len(drives) > n { - drives = drives[:n] - } - f.drives = f.drives[len(drives):] - if len(drives) == 0 { - return nil, io.EOF - } + if n > 0 && len(drives) > n { + drives = drives[:n] + } + f.drives = f.drives[len(drives):] + if len(drives) == 0 { + return nil, io.EOF } var infos []os.FileInfo for _, drive := range drives { - fi, err := os.Stat(drive) + fi, err := os.Stat(drive + `\`) if err != nil { return nil, err } @@ -120,8 +127,8 @@ func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { return infos, nil } -func openfile(path string, flag int, mode fs.FileMode) (file, error) { - if path == "/" { +func (s *Server) openfile(path string, flag int, mode fs.FileMode) (file, error) { + if path == `\\.\` && s.winRoot { return newWinRoot() } return os.OpenFile(path, flag, mode) From 35c82d76ebd1f6f6a855de71d34d4e3b5e0f3638 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:16:37 +0100 Subject: [PATCH 05/11] review adjustments 4 --- server.go | 37 ++----------------------------------- server_windows.go | 26 +++++++++++++++++++++++++- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/server.go b/server.go index 6df82ee1..242a7732 100644 --- a/server.go +++ b/server.go @@ -34,39 +34,6 @@ type file interface { Close() error } -type dummyFile struct { -} - -func (f *dummyFile) Stat() (os.FileInfo, error) { - return nil, os.ErrPermission -} -func (f *dummyFile) ReadAt(b []byte, off int64) (int, error) { - return 0, os.ErrPermission -} -func (f *dummyFile) WriteAt(b []byte, off int64) (int, error) { - return 0, os.ErrPermission -} -func (f *dummyFile) Readdir(int) ([]os.FileInfo, error) { - return nil, os.ErrPermission -} -func (f *dummyFile) Name() string { - return "dummyFile" -} -func (f *dummyFile) Truncate(int64) error { - return os.ErrPermission -} -func (f *dummyFile) Chmod(mode fs.FileMode) error { - return os.ErrPermission -} -func (f *dummyFile) Chown(uid, gid int) error { - return os.ErrPermission -} -func (f *dummyFile) Close() error { - return os.ErrPermission -} - -var _ = dummyFile{} // ignore unused - // Server is an SSH File Transfer Protocol (sftp) server. // This is intended to provide the sftp subsystem to an ssh server daemon. // This implementation currently supports most of sftp server protocol version 3, @@ -163,8 +130,8 @@ func ReadOnly() ServerOption { } } -// WinRoot configures a Server to serve a virtual '/' for windows that lists all drives -func WinRoot() ServerOption { +// configures a Server to serve a virtual '/' for windows that lists all drives +func WindowsRootEnumeratesDrives() ServerOption { return func(s *Server) error { s.winRoot = true return nil diff --git a/server_windows.go b/server_windows.go index 3a829ed5..f992b392 100644 --- a/server_windows.go +++ b/server_windows.go @@ -86,7 +86,6 @@ func (i *driveInfo) Name() string { } type winRoot struct { - dummyFile drives []string } @@ -127,6 +126,31 @@ func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { return infos, nil } +func (f *winRoot) Stat() (os.FileInfo, error) { + return nil, os.ErrPermission +} +func (f *winRoot) ReadAt(b []byte, off int64) (int, error) { + return 0, os.ErrPermission +} +func (f *winRoot) WriteAt(b []byte, off int64) (int, error) { + return 0, os.ErrPermission +} +func (f *winRoot) Name() string { + return "/" +} +func (f *winRoot) Truncate(int64) error { + return os.ErrPermission +} +func (f *winRoot) Chmod(mode fs.FileMode) error { + return os.ErrPermission +} +func (f *winRoot) Chown(uid, gid int) error { + return os.ErrPermission +} +func (f *winRoot) Close() error { + return nil +} + func (s *Server) openfile(path string, flag int, mode fs.FileMode) (file, error) { if path == `\\.\` && s.winRoot { return newWinRoot() From 0395c127894d80daf4b9a73db00efc083e15a5d5 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Sun, 14 Jan 2024 09:01:02 +0100 Subject: [PATCH 06/11] review adjustments 5 --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 242a7732..766de00e 100644 --- a/server.go +++ b/server.go @@ -130,7 +130,7 @@ func ReadOnly() ServerOption { } } -// configures a Server to serve a virtual '/' for windows that lists all drives +// WindowsRootEnumeratesDrives configures a Server to serve a virtual '/' for windows that lists all drives func WindowsRootEnumeratesDrives() ServerOption { return func(s *Server) error { s.winRoot = true From e52de81b009d13f4852dc59de4463e60834573d4 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Tue, 9 Apr 2024 18:08:42 +0200 Subject: [PATCH 07/11] Fix merge with master --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 64cd8c24..20b1bc7f 100644 --- a/server.go +++ b/server.go @@ -495,7 +495,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket { mode = fs.FileMode() & os.ModePerm } - f, err := os.OpenFile(svr.toLocalPath(p.Path), osFlags, mode) + f, err := svr.openfile(svr.toLocalPath(p.Path), osFlags, mode) if err != nil { return statusFromError(p.ID, err) } From d8192421c2a62ae40336a5257de157c78279d46c Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Wed, 1 Jan 2025 16:06:54 +0100 Subject: [PATCH 08/11] examples/go-sftp-server: add '-wr' --- examples/go-sftp-server/main.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/go-sftp-server/main.go b/examples/go-sftp-server/main.go index ba902b6f..aef436cb 100644 --- a/examples/go-sftp-server/main.go +++ b/examples/go-sftp-server/main.go @@ -20,10 +20,13 @@ func main() { var ( readOnly bool debugStderr bool + winRoot bool ) flag.BoolVar(&readOnly, "R", false, "read-only server") flag.BoolVar(&debugStderr, "e", false, "debug to stderr") + flag.BoolVar(&winRoot, "wr", false, "windows root") + flag.Parse() debugStream := io.Discard @@ -128,6 +131,11 @@ func main() { fmt.Fprintf(debugStream, "Read write server\n") } + if winRoot { + serverOptions = append(serverOptions, sftp.WindowsRootEnumeratesDrives()) + fmt.Fprintf(debugStream, "Windows root enabled\n") + } + server, err := sftp.NewServer( channel, serverOptions..., From 8dcbf480f2e8f0e262767b39e1c453a3dc46b74e Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Wed, 1 Jan 2025 16:08:40 +0100 Subject: [PATCH 09/11] windows root: add stat for '/' --- server.go | 4 ++-- server_posix.go | 8 ++++++++ server_windows.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 20b1bc7f..386003dd 100644 --- a/server.go +++ b/server.go @@ -217,7 +217,7 @@ func handlePacket(s *Server, p orderedRequest) error { } case *sshFxpLstatPacket: // stat the requested file - info, err := os.Lstat(s.toLocalPath(p.Path)) + info, err := s.lstat(s.toLocalPath(p.Path)) rpkt = &sshFxpStatResponse{ ID: p.ID, info: info, @@ -291,7 +291,7 @@ func handlePacket(s *Server, p orderedRequest) error { case *sshFxpOpendirPacket: lp := s.toLocalPath(p.Path) - if stat, err := os.Stat(lp); err != nil { + if stat, err := s.stat(lp); err != nil { rpkt = statusFromError(p.ID, err) } else if !stat.IsDir() { rpkt = statusFromError(p.ID, &os.PathError{ diff --git a/server_posix.go b/server_posix.go index 6ca70199..c07d70a0 100644 --- a/server_posix.go +++ b/server_posix.go @@ -11,3 +11,11 @@ import ( func (s *Server) openfile(path string, flag int, mode fs.FileMode) (file, error) { return os.OpenFile(path, flag, mode) } + +func (s *Server) lstat(name string) (os.FileInfo, error) { + return os.Lstat(name) +} + +func (s *Server) stat(name string) (os.FileInfo, error) { + return os.Stat(name) +} diff --git a/server_windows.go b/server_windows.go index f992b392..6ca82ca8 100644 --- a/server_windows.go +++ b/server_windows.go @@ -7,6 +7,7 @@ import ( "os" "path" "path/filepath" + "time" "golang.org/x/sys/windows" ) @@ -157,3 +158,34 @@ func (s *Server) openfile(path string, flag int, mode fs.FileMode) (file, error) } return os.OpenFile(path, flag, mode) } + +type winRootFileInfo struct { + fs.FileInfo + name string +} + +func (w winRootFileInfo) Name() string { return w.name } +func (w winRootFileInfo) Size() int64 { return 0 } +func (w winRootFileInfo) Mode() fs.FileMode { return fs.ModeDir | 0555 } // read+execute for all +func (w winRootFileInfo) ModTime() time.Time { return time.Time{} } +func (w winRootFileInfo) IsDir() bool { return true } +func (w winRootFileInfo) Sys() interface{} { return nil } + +// Create a new root FileInfo +var rootFileInfo = winRootFileInfo{ + name: "/", +} + +func (s *Server) lstat(name string) (os.FileInfo, error) { + if name == `\\.\` && s.winRoot { + return rootFileInfo, nil + } + return os.Lstat(name) +} + +func (s *Server) stat(name string) (os.FileInfo, error) { + if name == `\\.\` && s.winRoot { + return rootFileInfo, nil + } + return os.Stat(name) +} From 33e8fe86c202bdb89bb88b58ae6ef37b77526188 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Wed, 1 Jan 2025 16:16:23 +0100 Subject: [PATCH 10/11] go.mod: add golang.org/x/sys --- go.mod | 1 + 1 file changed, 1 insertion(+) diff --git a/go.mod b/go.mod index 0a2de367..ac5ee715 100644 --- a/go.mod +++ b/go.mod @@ -6,4 +6,5 @@ require ( github.com/kr/fs v0.1.0 github.com/stretchr/testify v1.8.0 golang.org/x/crypto v0.31.0 + golang.org/x/sys v0.28.0 // indirect ) From 6be1dd2db9ed752cc7de9132ddeaa5562d32d731 Mon Sep 17 00:00:00 2001 From: powellnorma <101364699+powellnorma@users.noreply.github.com> Date: Thu, 2 Jan 2025 20:15:18 +0100 Subject: [PATCH 11/11] review adjustments --- server_windows.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/server_windows.go b/server_windows.go index 6ca82ca8..e940dba1 100644 --- a/server_windows.go +++ b/server_windows.go @@ -58,7 +58,7 @@ func bitsToDrives(bitmap uint32) []string { var drive rune = 'a' var drives []string - for bitmap != 0 { + for bitmap != 0 && drive <= 'z' { if bitmap&1 == 1 { drives = append(drives, string(drive)+":") } @@ -128,7 +128,7 @@ func (f *winRoot) Readdir(n int) ([]os.FileInfo, error) { } func (f *winRoot) Stat() (os.FileInfo, error) { - return nil, os.ErrPermission + return rootFileInfo, nil } func (f *winRoot) ReadAt(b []byte, off int64) (int, error) { return 0, os.ErrPermission @@ -149,6 +149,7 @@ func (f *winRoot) Chown(uid, gid int) error { return os.ErrPermission } func (f *winRoot) Close() error { + f.drives = nil return nil } @@ -160,20 +161,21 @@ func (s *Server) openfile(path string, flag int, mode fs.FileMode) (file, error) } type winRootFileInfo struct { - fs.FileInfo - name string + name string + modTime time.Time } -func (w winRootFileInfo) Name() string { return w.name } -func (w winRootFileInfo) Size() int64 { return 0 } -func (w winRootFileInfo) Mode() fs.FileMode { return fs.ModeDir | 0555 } // read+execute for all -func (w winRootFileInfo) ModTime() time.Time { return time.Time{} } -func (w winRootFileInfo) IsDir() bool { return true } -func (w winRootFileInfo) Sys() interface{} { return nil } +func (w *winRootFileInfo) Name() string { return w.name } +func (w *winRootFileInfo) Size() int64 { return 0 } +func (w *winRootFileInfo) Mode() fs.FileMode { return fs.ModeDir | 0555 } // read+execute for all +func (w *winRootFileInfo) ModTime() time.Time { return w.modTime } +func (w *winRootFileInfo) IsDir() bool { return true } +func (w *winRootFileInfo) Sys() interface{} { return nil } // Create a new root FileInfo -var rootFileInfo = winRootFileInfo{ - name: "/", +var rootFileInfo = &winRootFileInfo{ + name: "/", + modTime: time.Now(), } func (s *Server) lstat(name string) (os.FileInfo, error) {