summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2016-09-22 23:28:11 +0200
committerJakob Unterwurzacher2016-09-25 16:43:17 +0200
commita6a7b424f8e8a0f8ddd1c94b7463250ef1337811 (patch)
tree2281d7062d893d70209ecb7a82589e49decac164
parent35bcc2dca2dc928e3b7c31e34d785b7a42c06722 (diff)
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.
-rw-r--r--internal/fusefrontend_reverse/diriv.go42
-rw-r--r--internal/fusefrontend_reverse/reverse_diriv.go26
-rw-r--r--internal/fusefrontend_reverse/reverse_longnames.go61
-rw-r--r--internal/fusefrontend_reverse/rfs.go38
-rw-r--r--internal/fusefrontend_reverse/rpath.go41
-rw-r--r--internal/fusefrontend_reverse/virtualfile.go57
-rw-r--r--internal/nametransform/longnames.go9
7 files changed, 217 insertions, 57 deletions
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
+ }
+ } else if nameType == nametransform.LongNameContent {
+ transformedPart, err = rfs.findLongnameParent(currentDir, dirIV, part)
+ if err != nil {
+ return "", err
}
- 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