diff --git a/internal/fusefrontend_reverse/diriv.go b/internal/fusefrontend_reverse/diriv.go deleted file mode 100644 index c4a93e4..0000000 --- a/internal/fusefrontend_reverse/diriv.go +++ /dev/null @@ -1,42 +0,0 @@ -package fusefrontend_reverse - -import ( - "crypto/sha256" - - "github.com/hanwen/go-fuse/fuse" - "github.com/hanwen/go-fuse/fuse/nodefs" - - "github.com/rfjakob/gocryptfs/internal/nametransform" -) - -// deriveDirIV derives the DirIV from the directory path by simply hashing it -func deriveDirIV(dirPath string) []byte { - hash := sha256.Sum256([]byte(dirPath)) - return hash[:nametransform.DirIVLen] -} - -type dirIVFile struct { - // Embed nodefs.defaultFile for a ENOSYS implementation of all methods - nodefs.File - // file content - content []byte -} - -func NewDirIVFile(dirPath string) (nodefs.File, fuse.Status) { - return &dirIVFile{ - File: nodefs.NewDefaultFile(), - content: deriveDirIV(dirPath), - }, fuse.OK -} - -// Read - FUSE call -func (f *dirIVFile) Read(buf []byte, off int64) (resultData fuse.ReadResult, status fuse.Status) { - if off >= int64(len(f.content)) { - return nil, fuse.OK - } - end := int(off) + len(buf) - if end > len(f.content) { - end = len(f.content) - } - return fuse.ReadResultData(f.content[off:end]), fuse.OK -} diff --git a/internal/fusefrontend_reverse/reverse_diriv.go b/internal/fusefrontend_reverse/reverse_diriv.go new file mode 100644 index 0000000..df3a4d1 --- /dev/null +++ b/internal/fusefrontend_reverse/reverse_diriv.go @@ -0,0 +1,26 @@ +package fusefrontend_reverse + +import ( + "crypto/sha256" + + "github.com/hanwen/go-fuse/fuse" + "github.com/hanwen/go-fuse/fuse/nodefs" + + "github.com/rfjakob/gocryptfs/internal/nametransform" +) + +// deriveDirIV derives the DirIV from the encrypted directory path by +// hashing it +func deriveDirIV(dirPath string) []byte { + hash := sha256.Sum256([]byte(dirPath)) + return hash[:nametransform.DirIVLen] +} + +func (rfs *reverseFS) newDirIVFile(cRelPath string) (nodefs.File, fuse.Status) { + cDir := saneDir(cRelPath) + absDir, err := rfs.abs(rfs.decryptPath(cDir)) + if err != nil { + return nil, fuse.ToStatus(err) + } + return rfs.NewVirtualFile(deriveDirIV(cDir), absDir) +} diff --git a/internal/fusefrontend_reverse/reverse_longnames.go b/internal/fusefrontend_reverse/reverse_longnames.go new file mode 100644 index 0000000..46e1791 --- /dev/null +++ b/internal/fusefrontend_reverse/reverse_longnames.go @@ -0,0 +1,61 @@ +package fusefrontend_reverse + +import ( + "os" + "path/filepath" + "syscall" + + "github.com/hanwen/go-fuse/fuse" + "github.com/hanwen/go-fuse/fuse/nodefs" + + "github.com/rfjakob/gocryptfs/internal/nametransform" +) + +const ( + shortNameMax = 176 +) + +func (rfs *reverseFS) findLongnameParent(dir string, dirIV []byte, longname string) (string, error) { + absDir := filepath.Join(rfs.args.Cipherdir, dir) + dirfd, err := os.Open(absDir) + if err != nil { + return "", err + } + dirEntries, err := dirfd.Readdirnames(-1) + if err != nil { + return "", err + } + for _, e := range dirEntries { + if len(e) <= shortNameMax { + continue + } + cName := rfs.nameTransform.EncryptName(e, dirIV) + if len(cName) <= syscall.NAME_MAX { + panic("logic error or wrong shortNameMax constant?") + } + hName := nametransform.HashLongName(cName) + if longname == hName { + return e, nil + } + } + return "", syscall.ENOENT +} + +func (rfs *reverseFS) newNameFile(relPath string) (nodefs.File, fuse.Status) { + dotName := filepath.Base(relPath) // gocryptfs.longname.XYZ.name + longname := dotName[:len(dotName)-len(nametransform.LongNameSuffix)] // gocryptfs.longname.XYZ + + cDir := saneDir(relPath) + pDir, err := rfs.decryptPath(cDir) + if err != nil { + return nil, fuse.ToStatus(err) + } + dirIV := deriveDirIV(cDir) + e, err := rfs.findLongnameParent(pDir, dirIV, longname) + if err != nil { + return nil, fuse.ToStatus(err) + } + content := []byte(rfs.nameTransform.EncryptName(e, dirIV)) + parentFile := filepath.Join(rfs.args.Cipherdir, pDir) + return rfs.NewVirtualFile(content, parentFile) +} diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 9fac591..7305687 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -108,6 +108,13 @@ func isDirIV(relPath string) bool { return filepath.Base(relPath) == nametransform.DirIVFilename } +// isNameFile determines if the path points to a gocryptfs.longname.*.name +// file +func isNameFile(relPath string) bool { + fileType := nametransform.NameType(filepath.Base(relPath)) + return fileType == nametransform.LongNameFilename +} + func (rfs *reverseFS) inoAwareStat(relPlainPath string) (*fuse.Attr, fuse.Status) { absPath, err := rfs.abs(relPlainPath, nil) if err != nil { @@ -148,12 +155,32 @@ func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr if relPath == configfile.ConfDefaultName { return rfs.inoAwareStat(configfile.ConfReverseName) } - if isDirIV(relPath) { - return rfs.dirIVAttr(relPath, context) - } if rfs.isFiltered(relPath) { return nil, fuse.EPERM } + + // Handle virtual files + var f nodefs.File + var status fuse.Status + virtual := false + if isDirIV(relPath) { + virtual = true + f, status = rfs.newDirIVFile(relPath) + } + if isNameFile(relPath) { + virtual = true + f, status = rfs.newNameFile(relPath) + } + if virtual { + if !status.Ok() { + fmt.Printf("GetAttr %q: newXFile failed: %v\n", relPath, status) + return nil, status + } + var a fuse.Attr + status = f.GetAttr(&a) + return &a, status + } + cPath, err := rfs.decryptPath(relPath) if err != nil { return nil, fuse.ToStatus(err) @@ -191,7 +218,10 @@ func (rfs *reverseFS) Open(relPath string, flags uint32, context *fuse.Context) return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context) } if isDirIV(relPath) { - return NewDirIVFile(relDir(relPath)) + return rfs.newDirIVFile(relPath) + } + if isNameFile(relPath) { + return rfs.newNameFile(relPath) } if rfs.isFiltered(relPath) { return nil, fuse.EPERM diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index a15b31a..19539bb 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -5,8 +5,19 @@ import ( "path/filepath" "strings" "syscall" + + "github.com/rfjakob/gocryptfs/internal/nametransform" ) +// saneDir is like filepath.Dir but returns "" instead of "." +func saneDir(path string) string { + d := filepath.Dir(path) + if d == "." { + return "" + } + return d +} + func (rfs *reverseFS) abs(relPath string, err error) (string, error) { if err != nil { return "", err @@ -22,17 +33,29 @@ func (rfs *reverseFS) decryptPath(relPath string) (string, error) { var transformedParts []string parts := strings.Split(relPath, "/") for i, part := range parts { + // Start at the top and recurse + currentDir := filepath.Join(parts[:i]...) + nameType := nametransform.NameType(part) + dirIV := deriveDirIV(currentDir) var transformedPart string - dirIV := deriveDirIV(filepath.Join(parts[:i]...)) - transformedPart, err = rfs.nameTransform.DecryptName(part, dirIV) - if err != nil { - // We get lots of decrypt requests for names like ".Trash" that - // are invalid base64. Convert them to ENOENT so the correct - // error gets returned to the user. - if _, ok := err.(base64.CorruptInputError); ok { - return "", syscall.ENOENT + if nameType == nametransform.LongNameNone { + transformedPart, err = rfs.nameTransform.DecryptName(part, dirIV) + if err != nil { + // We get lots of decrypt requests for names like ".Trash" that + // are invalid base64. Convert them to ENOENT so the correct + // error gets returned to the user. + if _, ok := err.(base64.CorruptInputError); ok { + return "", syscall.ENOENT + } + return "", err } - return "", err + } else if nameType == nametransform.LongNameContent { + transformedPart, err = rfs.findLongnameParent(currentDir, dirIV, part) + if err != nil { + return "", err + } + } else { + panic("longname bug, .name files should have been handled earlier") } transformedParts = append(transformedParts, transformedPart) } diff --git a/internal/fusefrontend_reverse/virtualfile.go b/internal/fusefrontend_reverse/virtualfile.go new file mode 100644 index 0000000..5373b48 --- /dev/null +++ b/internal/fusefrontend_reverse/virtualfile.go @@ -0,0 +1,57 @@ +package fusefrontend_reverse + +import ( + "fmt" + "syscall" + + "github.com/hanwen/go-fuse/fuse" + "github.com/hanwen/go-fuse/fuse/nodefs" +) + +type virtualFile struct { + // Embed nodefs.defaultFile for a ENOSYS implementation of all methods + nodefs.File + // file content + content []byte + // absolute path to a parent file + parentFile string + // inode number + ino uint64 +} + +func (rfs *reverseFS) NewVirtualFile(content []byte, parentFile string) (nodefs.File, fuse.Status) { + return &virtualFile{ + File: nodefs.NewDefaultFile(), + content: content, + parentFile: parentFile, + ino: rfs.inoGen.next(), + }, fuse.OK +} + +// Read - FUSE call +func (f *virtualFile) Read(buf []byte, off int64) (resultData fuse.ReadResult, status fuse.Status) { + if off >= int64(len(f.content)) { + return nil, fuse.OK + } + end := int(off) + len(buf) + if end > len(f.content) { + end = len(f.content) + } + return fuse.ReadResultData(f.content[off:end]), fuse.OK +} + +// GetAttr - FUSE call +func (f *virtualFile) GetAttr(a *fuse.Attr) fuse.Status { + var st syscall.Stat_t + err := syscall.Lstat(f.parentFile, &st) + if err != nil { + fmt.Printf("Lstat %q: %v\n", f.parentFile, err) + return fuse.ToStatus(err) + } + st.Ino = f.ino + st.Size = int64(len(f.content)) + st.Mode = syscall.S_IFREG | 0400 + st.Nlink = 1 + a.FromStat(&st) + return fuse.OK +} diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go index cf7f34c..e61e21b 100644 --- a/internal/nametransform/longnames.go +++ b/internal/nametransform/longnames.go @@ -31,9 +31,14 @@ func HashLongName(name string) string { // Values returned by IsLongName const ( - LongNameContent = iota + // File that stores the file content. + // Example: gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro= + LongNameContent = iota + // File that stores the full encrypted filename. + // Example: gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=.name LongNameFilename = iota - LongNameNone = iota + // Example: i1bpTaVLZq7sRNA9mL_2Ig== + LongNameNone = iota ) // NameType - detect if cName is