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
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -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)
|
||||
}
|
||||
|
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
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user