Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bugfix] Windows paths don’t work with Server #445

Merged
merged 2 commits into from
Jun 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion request-plan9.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

package sftp

import "syscall"
import (
"path"
"path/filepath"
"syscall"
)

func fakeFileInfoSys() interface{} {
return &syscall.Dir{}
Expand All @@ -11,3 +15,20 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error {
return nil
}

func toLocalPath(p string) string {
lp := filepath.FromSlash(p)

if path.IsAbs(p) {
tmp := lp[1:]

if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/#s/boot" to "#s/boot"
return tmp
}
}

return lp
}
4 changes: 2 additions & 2 deletions request-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,9 +288,9 @@ func cleanPath(p string) string {
}

func cleanPathWithBase(base, p string) string {
p = filepath.ToSlash(p)
p = filepath.ToSlash(filepath.Clean(p))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The order here really doesn’t matter except for Windows, and only for UNC paths. “Let’s just not support UNC paths“ doesn’t work, because OpenSSH’s client does not sanitize the path first, so UNC paths would still work with that client anyways. 😭

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linux-machine $ sftp -P 31337 windows-machine
sftp> ls //wsl$/openSUSE-Leap-15.3
//wsl$/openSUSE-Leap-15.3/bin           //wsl$/openSUSE-Leap-15.3/dev           //wsl$/openSUSE-Leap-15.3/etc           
//wsl$/openSUSE-Leap-15.3/home          //wsl$/openSUSE-Leap-15.3/init          //wsl$/openSUSE-Leap-15.3/lib           
//wsl$/openSUSE-Leap-15.3/lib64         //wsl$/openSUSE-Leap-15.3/lost+found    //wsl$/openSUSE-Leap-15.3/mnt           
//wsl$/openSUSE-Leap-15.3/opt           //wsl$/openSUSE-Leap-15.3/proc          //wsl$/openSUSE-Leap-15.3/root          
//wsl$/openSUSE-Leap-15.3/run           //wsl$/openSUSE-Leap-15.3/sbin          //wsl$/openSUSE-Leap-15.3/selinux       
//wsl$/openSUSE-Leap-15.3/srv           //wsl$/openSUSE-Leap-15.3/sys           //wsl$/openSUSE-Leap-15.3/tmp           
//wsl$/openSUSE-Leap-15.3/usr           //wsl$/openSUSE-Leap-15.3/var

🤷‍♀️ alright.

if !path.IsAbs(p) {
return path.Join(base, p)
}
return path.Clean(p)
return p
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some basic tests and all worked, anyway since path.Join calls Clean internally, I'll leave path.Clean(p) here too

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm… the point here in removing this path.Clean() is that PuTTY sftp sanitizes names with FXP_REALPATH and this removes the double slash at the start of a //UNC/Path this means that while on my Linux machine, I could do ls //wsl$/openSUSE-Leap-15.3 attempting to do the same on my PuTTY sftp client, resulted in the path being cleaned in the FXP_REALPATH, and then failing with cannot open: /wsl$/openSUSE-Leap-15.3

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we could have the same issue if the path is not absolute and we hit return path.Join(base, p)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but this should only apply in the case where the base being added is a UNC address. So, for example cleanPathWithBase("//wsl$/openSUSE-Leap-15.3", aPath)

I think in this case, we’re kind of a bit stuck on “having a non-default base paths is not particularly robust and a little bit of a hack”. 🤔 For this particular case, I’m a bit more willing to let the inconsistent behavior slide.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Allowing non cleaned paths could break some usages, let's merge this patch and see if someone complaints. I'll try to do some tests myself using wsl (I never tested it) as soon as I have some free time, it could take a while

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this is why I think keeping the path.Join which will auto-clean as well is the better choice for now, even though it would break UNC base paths. Getting some sort of logic to correctly handle all the nuances of properly rooting all reasonable base/path combos would be 😩 .

It’s at least pretty nice that we can do UNC paths with a “local” datastore with WSL, if I had to setup a remote SMB server to test any of this, I might very well tear my hair out. 😆

}
4 changes: 4 additions & 0 deletions request-unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ func testOsSys(sys interface{}) error {
}
return nil
}

func toLocalPath(p string) string {
return p
}
35 changes: 34 additions & 1 deletion request_windows.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package sftp

import "syscall"
import (
"path"
"path/filepath"
"syscall"
)

func fakeFileInfoSys() interface{} {
return syscall.Win32FileAttributeData{}
Expand All @@ -9,3 +13,32 @@ func fakeFileInfoSys() interface{} {
func testOsSys(sys interface{}) error {
return nil
}

func toLocalPath(p string) string {
lp := filepath.FromSlash(p)

if path.IsAbs(p) {
tmp := lp
for len(tmp) > 0 && tmp[0] == '\\' {
tmp = tmp[1:]
}

if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes is absolute,
// then we have a filepath encoded with a prefix '/'.
// e.g. "/C:/Windows" to "C:\\Windows"
return tmp
}

tmp += "\\"

if filepath.IsAbs(tmp) {
// If the FromSlash without any starting slashes but with extra end slash is absolute,
// then we have a filepath encoded with a prefix '/' and a dropped '/' at the end.
// e.g. "/C:" to "C:\\"
return tmp
}
}

return lp
}
24 changes: 14 additions & 10 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func handlePacket(s *Server, p orderedRequest) error {
}
case *sshFxpStatPacket:
// stat the requested file
info, err := os.Stat(p.Path)
info, err := os.Stat(toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
Expand All @@ -185,7 +185,7 @@ func handlePacket(s *Server, p orderedRequest) error {
}
case *sshFxpLstatPacket:
// stat the requested file
info, err := os.Lstat(p.Path)
info, err := os.Lstat(toLocalPath(p.Path))
rpkt = &sshFxpStatResponse{
ID: p.ID,
info: info,
Expand All @@ -209,24 +209,24 @@ func handlePacket(s *Server, p orderedRequest) error {
}
case *sshFxpMkdirPacket:
// TODO FIXME: ignore flags field
err := os.Mkdir(p.Path, 0755)
err := os.Mkdir(toLocalPath(p.Path), 0755)
rpkt = statusFromError(p.ID, err)
case *sshFxpRmdirPacket:
err := os.Remove(p.Path)
err := os.Remove(toLocalPath(p.Path))
rpkt = statusFromError(p.ID, err)
case *sshFxpRemovePacket:
err := os.Remove(p.Filename)
err := os.Remove(toLocalPath(p.Filename))
rpkt = statusFromError(p.ID, err)
case *sshFxpRenamePacket:
err := os.Rename(p.Oldpath, p.Newpath)
err := os.Rename(toLocalPath(p.Oldpath), toLocalPath(p.Newpath))
rpkt = statusFromError(p.ID, err)
case *sshFxpSymlinkPacket:
err := os.Symlink(p.Targetpath, p.Linkpath)
err := os.Symlink(toLocalPath(p.Targetpath), toLocalPath(p.Linkpath))
rpkt = statusFromError(p.ID, err)
case *sshFxpClosePacket:
rpkt = statusFromError(p.ID, s.closeHandle(p.Handle))
case *sshFxpReadlinkPacket:
f, err := os.Readlink(p.Path)
f, err := os.Readlink(toLocalPath(p.Path))
rpkt = &sshFxpNamePacket{
ID: p.ID,
NameAttrs: []*sshFxpNameAttr{
Expand All @@ -241,7 +241,7 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpRealpathPacket:
f, err := filepath.Abs(p.Path)
f, err := filepath.Abs(toLocalPath(p.Path))
f = cleanPath(f)
rpkt = &sshFxpNamePacket{
ID: p.ID,
Expand All @@ -257,6 +257,8 @@ func handlePacket(s *Server, p orderedRequest) error {
rpkt = statusFromError(p.ID, err)
}
case *sshFxpOpendirPacket:
p.Path = toLocalPath(p.Path)

if stat, err := os.Stat(p.Path); err != nil {
rpkt = statusFromError(p.ID, err)
} else if !stat.IsDir() {
Expand Down Expand Up @@ -445,7 +447,7 @@ func (p *sshFxpOpenPacket) respond(svr *Server) responsePacket {
osFlags |= os.O_EXCL
}

f, err := os.OpenFile(p.Path, osFlags, 0644)
f, err := os.OpenFile(toLocalPath(p.Path), osFlags, 0644)
if err != nil {
return statusFromError(p.ID, err)
}
Expand Down Expand Up @@ -482,6 +484,8 @@ func (p *sshFxpSetstatPacket) respond(svr *Server) responsePacket {
b := p.Attrs.([]byte)
var err error

p.Path = toLocalPath(p.Path)

debug("setstat name \"%s\"", p.Path)
if (p.Flags & sshFileXferAttrSize) != 0 {
var size uint64
Expand Down
2 changes: 1 addition & 1 deletion stat_plan9.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,5 @@ func fromFileMode(mode os.FileMode) uint32 {
const (
s_ISUID = 04000
s_ISGID = 02000
S_ISVTX = 01000
s_ISVTX = 01000
)