diff options
| author | Jakob Unterwurzacher | 2016-09-22 23:28:11 +0200 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2016-09-25 16:43:17 +0200 | 
| commit | a6a7b424f8e8a0f8ddd1c94b7463250ef1337811 (patch) | |
| tree | 2281d7062d893d70209ecb7a82589e49decac164 /internal | |
| parent | 35bcc2dca2dc928e3b7c31e34d785b7a42c06722 (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.
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend_reverse/diriv.go | 42 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/reverse_diriv.go | 26 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/reverse_longnames.go | 61 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rfs.go | 38 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rpath.go | 41 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/virtualfile.go | 57 | ||||
| -rw-r--r-- | internal/nametransform/longnames.go | 9 | 
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 | 
