reverse: resolve long names in Open and GetAttr
The last patch added functionality for generating gocryptfs.longname.* files, this patch adds support for mapping them back to the full filenames. Note that resolving a long name needs a full readdir. A cache will be implemented later on to improve performance.
This commit is contained in:
parent
35bcc2dca2
commit
a6a7b424f8
@ -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
|
|
||||||
}
|
|
26
internal/fusefrontend_reverse/reverse_diriv.go
Normal file
26
internal/fusefrontend_reverse/reverse_diriv.go
Normal file
@ -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)
|
||||||
|
}
|
61
internal/fusefrontend_reverse/reverse_longnames.go
Normal file
61
internal/fusefrontend_reverse/reverse_longnames.go
Normal file
@ -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)
|
||||||
|
}
|
@ -108,6 +108,13 @@ func isDirIV(relPath string) bool {
|
|||||||
return filepath.Base(relPath) == nametransform.DirIVFilename
|
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) {
|
func (rfs *reverseFS) inoAwareStat(relPlainPath string) (*fuse.Attr, fuse.Status) {
|
||||||
absPath, err := rfs.abs(relPlainPath, nil)
|
absPath, err := rfs.abs(relPlainPath, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -148,12 +155,32 @@ func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
|
|||||||
if relPath == configfile.ConfDefaultName {
|
if relPath == configfile.ConfDefaultName {
|
||||||
return rfs.inoAwareStat(configfile.ConfReverseName)
|
return rfs.inoAwareStat(configfile.ConfReverseName)
|
||||||
}
|
}
|
||||||
if isDirIV(relPath) {
|
|
||||||
return rfs.dirIVAttr(relPath, context)
|
|
||||||
}
|
|
||||||
if rfs.isFiltered(relPath) {
|
if rfs.isFiltered(relPath) {
|
||||||
return nil, fuse.EPERM
|
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)
|
cPath, err := rfs.decryptPath(relPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fuse.ToStatus(err)
|
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)
|
return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context)
|
||||||
}
|
}
|
||||||
if isDirIV(relPath) {
|
if isDirIV(relPath) {
|
||||||
return NewDirIVFile(relDir(relPath))
|
return rfs.newDirIVFile(relPath)
|
||||||
|
}
|
||||||
|
if isNameFile(relPath) {
|
||||||
|
return rfs.newNameFile(relPath)
|
||||||
}
|
}
|
||||||
if rfs.isFiltered(relPath) {
|
if rfs.isFiltered(relPath) {
|
||||||
return nil, fuse.EPERM
|
return nil, fuse.EPERM
|
||||||
|
@ -5,8 +5,19 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"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) {
|
func (rfs *reverseFS) abs(relPath string, err error) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -22,17 +33,29 @@ func (rfs *reverseFS) decryptPath(relPath string) (string, error) {
|
|||||||
var transformedParts []string
|
var transformedParts []string
|
||||||
parts := strings.Split(relPath, "/")
|
parts := strings.Split(relPath, "/")
|
||||||
for i, part := range parts {
|
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
|
var transformedPart string
|
||||||
dirIV := deriveDirIV(filepath.Join(parts[:i]...))
|
if nameType == nametransform.LongNameNone {
|
||||||
transformedPart, err = rfs.nameTransform.DecryptName(part, dirIV)
|
transformedPart, err = rfs.nameTransform.DecryptName(part, dirIV)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We get lots of decrypt requests for names like ".Trash" that
|
// We get lots of decrypt requests for names like ".Trash" that
|
||||||
// are invalid base64. Convert them to ENOENT so the correct
|
// are invalid base64. Convert them to ENOENT so the correct
|
||||||
// error gets returned to the user.
|
// error gets returned to the user.
|
||||||
if _, ok := err.(base64.CorruptInputError); ok {
|
if _, ok := err.(base64.CorruptInputError); ok {
|
||||||
return "", syscall.ENOENT
|
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)
|
transformedParts = append(transformedParts, transformedPart)
|
||||||
}
|
}
|
||||||
|
57
internal/fusefrontend_reverse/virtualfile.go
Normal file
57
internal/fusefrontend_reverse/virtualfile.go
Normal file
@ -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
|
||||||
|
}
|
@ -31,9 +31,14 @@ func HashLongName(name string) string {
|
|||||||
|
|
||||||
// Values returned by IsLongName
|
// Values returned by IsLongName
|
||||||
const (
|
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
|
LongNameFilename = iota
|
||||||
LongNameNone = iota
|
// Example: i1bpTaVLZq7sRNA9mL_2Ig==
|
||||||
|
LongNameNone = iota
|
||||||
)
|
)
|
||||||
|
|
||||||
// NameType - detect if cName is
|
// NameType - detect if cName is
|
||||||
|
Loading…
x
Reference in New Issue
Block a user