diff options
| -rw-r--r-- | internal/fusefrontend_reverse/rfile.go | 64 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rfs.go | 52 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rpath.go | 39 | 
3 files changed, 119 insertions, 36 deletions
| diff --git a/internal/fusefrontend_reverse/rfile.go b/internal/fusefrontend_reverse/rfile.go index 746a0d6..15f2764 100644 --- a/internal/fusefrontend_reverse/rfile.go +++ b/internal/fusefrontend_reverse/rfile.go @@ -1,27 +1,79 @@  package fusefrontend_reverse  import ( +	"fmt" +	"io"  	"os"  	"github.com/hanwen/go-fuse/fuse"  	"github.com/hanwen/go-fuse/fuse/nodefs"  	"github.com/rfjakob/gocryptfs/internal/contentenc" +	"github.com/rfjakob/gocryptfs/internal/tlog"  ) -type file struct { +var zeroFileId []byte + +func init() { +	zeroFileId = make([]byte, 16) +} + +type reverseFile struct { +	// Embed nodefs.defaultFile for a ENOSYS implementation of all methods +	nodefs.File +	// Backing FD  	fd *os.File  	// Content encryption helper  	contentEnc *contentenc.ContentEnc - -	// nodefs.defaultFile returns ENOSYS for all operations -	nodefs.File  }  func NewFile(fd *os.File, contentEnc *contentenc.ContentEnc) (nodefs.File, fuse.Status) { -	return &file{ +	return &reverseFile{ +		File:       nodefs.NewDefaultFile(),  		fd:         fd,  		contentEnc: contentEnc, -		File:       nodefs.NewDefaultFile(),  	}, fuse.OK  } + +// GetAttr - FUSE call +func (rf *reverseFile) GetAttr(*fuse.Attr) fuse.Status { +	fmt.Printf("reverseFile.GetAttr fd=%d\n", rf.fd.Fd()) +	return fuse.ENOSYS +} + +// Read - FUSE call +func (rf *reverseFile) Read(buf []byte, off int64) (resultData fuse.ReadResult, status fuse.Status) { +	// TODO prefix file header + +	length := uint64(len(buf)) +	blocks := rf.contentEnc.ExplodeCipherRange(uint64(off), length) + +	// Read the backing plaintext in one go +	alignedOffset, alignedLength := contentenc.JointPlaintextRange(blocks) +	tlog.Warn.Printf("alignedOffset=%d, alignedLength=%d\n", alignedOffset, alignedLength) +	plaintext := make([]byte, int(alignedLength)) +	n, err := rf.fd.ReadAt(plaintext, int64(alignedOffset)) +	if err != nil && err != io.EOF { +		tlog.Warn.Printf("reverseFile.Read: ReadAt: %s", err.Error()) +		return nil, fuse.ToStatus(err) +	} +	// Truncate buffer down to actually read bytes +	plaintext = plaintext[0:n] + +	// Encrypt blocks +	ciphertext := rf.contentEnc.EncryptBlocks(plaintext, blocks[0].BlockNo, zeroFileId) + +	// Crop down to the relevant part +	var out []byte +	lenHave := len(ciphertext) +	skip := blocks[0].Skip +	endWant := int(skip + length) +	if lenHave > endWant { +		out = plaintext[skip:endWant] +	} else if lenHave > int(skip) { +		out = plaintext[skip:lenHave] +	} +	// else: out stays empty, file was smaller than the requested offset + +	return fuse.ReadResultData(out), fuse.OK +} diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 914dccb..68a5ac8 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -2,6 +2,7 @@ package fusefrontend_reverse  import (  	"os" +	"syscall"  	"github.com/hanwen/go-fuse/fuse"  	"github.com/hanwen/go-fuse/fuse/nodefs" @@ -13,9 +14,11 @@ import (  	"github.com/rfjakob/gocryptfs/internal/nametransform"  ) -type FS struct { -	// loopbackFileSystem, see go-fuse/fuse/pathfs/loopback.go +type reverseFS struct { +	// Embed pathfs.defaultFileSystem for a ENOSYS implementation of all methods  	pathfs.FileSystem +	// pathfs.loopbackFileSystem, see go-fuse/fuse/pathfs/loopback.go +	loopbackfs pathfs.FileSystem  	// Stores configuration arguments  	args fusefrontend.Args  	// Filename encryption helper @@ -25,43 +28,56 @@ type FS struct {  }  // Encrypted FUSE overlay filesystem -func NewFS(args fusefrontend.Args) *FS { +func NewFS(args fusefrontend.Args) *reverseFS {  	cryptoCore := cryptocore.New(args.Masterkey, args.OpenSSL, true)  	contentEnc := contentenc.New(cryptoCore, contentenc.DefaultBS)  	nameTransform := nametransform.New(cryptoCore, args.LongNames) -	return &FS{ -		FileSystem:    pathfs.NewLoopbackFileSystem(args.Cipherdir), +	return &reverseFS{ +		// pathfs.defaultFileSystem returns ENOSYS for all operations +		FileSystem:    pathfs.NewDefaultFileSystem(), +		loopbackfs:    pathfs.NewLoopbackFileSystem(args.Cipherdir),  		args:          args,  		nameTransform: nameTransform,  		contentEnc:    contentEnc,  	}  } -func (fs *FS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { -	if fs.isFiltered(relPath) { +func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) { +	if rfs.isFiltered(relPath) {  		return nil, fuse.EPERM  	} -	relPath, err := fs.decryptPath(relPath) +	relPath, err := rfs.decryptPath(relPath)  	if err != nil {  		return nil, fuse.ToStatus(err)  	} -	a, status := fs.FileSystem.GetAttr(relPath, context) +	a, status := rfs.loopbackfs.GetAttr(relPath, context)  	if a == nil {  		return a, status  	}  	// Calculate encrypted file size  	if a.IsRegular() { -		a.Size = fs.contentEnc.PlainSizeToCipherSize(a.Size) +		a.Size = rfs.contentEnc.PlainSizeToCipherSize(a.Size)  	}  	return a, fuse.OK  } -func (fs *FS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { -	if fs.isFiltered(relPath) { +func (rfs *reverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status { +	if rfs.isFiltered(relPath) { +		return fuse.EPERM +	} +	cPath, err := rfs.abs(rfs.encryptPath(relPath)) +	if err != nil { +		return fuse.ToStatus(err) +	} +	return fuse.ToStatus(syscall.Access(cPath, mode)) +} + +func (rfs *reverseFS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { +	if rfs.isFiltered(relPath) {  		return nil, fuse.EPERM  	} -	absPath, err := fs.abs(fs.decryptPath(relPath)) +	absPath, err := rfs.abs(rfs.decryptPath(relPath))  	if err != nil {  		return nil, fuse.ToStatus(err)  	} @@ -69,22 +85,22 @@ func (fs *FS) Open(relPath string, flags uint32, context *fuse.Context) (fuseFil  	if err != nil {  		return nil, fuse.ToStatus(err)  	} -	return NewFile(f, fs.contentEnc) +	return NewFile(f, rfs.contentEnc)  } -func (fs *FS) OpenDir(relPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { -	relPath, err := fs.decryptPath(relPath) +func (rfs *reverseFS) OpenDir(relPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { +	relPath, err := rfs.decryptPath(relPath)  	if err != nil {  		return nil, fuse.ToStatus(err)  	}  	// Read plaintext dir -	entries, status := fs.FileSystem.OpenDir(relPath, context) +	entries, status := rfs.loopbackfs.OpenDir(relPath, context)  	if entries == nil {  		return nil, status  	}  	// Encrypt names  	for i := range entries { -		entries[i].Name, err = fs.encryptPath(entries[i].Name) +		entries[i].Name, err = rfs.encryptPath(entries[i].Name)  		if err != nil {  			return nil, fuse.ToStatus(err)  		} diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go index 9377958..7e11ca3 100644 --- a/internal/fusefrontend_reverse/rpath.go +++ b/internal/fusefrontend_reverse/rpath.go @@ -1,15 +1,24 @@  package fusefrontend_reverse  import ( +	"encoding/base64" +	"fmt"  	"path/filepath"  	"strings" +	"syscall"  ) -func (fs *FS) abs(relPath string, err error) (string, error) { +var zeroDirIV []byte + +func init() { +	zeroDirIV = make([]byte, 16) +} + +func (rfs *reverseFS) abs(relPath string, err error) (string, error) {  	if err != nil {  		return "", err  	} -	return filepath.Join(fs.args.Cipherdir, relPath), nil +	return filepath.Join(rfs.args.Cipherdir, relPath), nil  }  const ( @@ -17,30 +26,36 @@ const (  	DECRYPT  ) -func (fs *FS) encryptPath(relPath string) (string, error) { -	return fs.transformPath(relPath, ENCRYPT) +func (rfs *reverseFS) encryptPath(relPath string) (string, error) { +	return rfs.transformPath(relPath, ENCRYPT)  } -func (fs *FS) decryptPath(relPath string) (string, error) { -	return fs.transformPath(relPath, DECRYPT) +func (rfs *reverseFS) decryptPath(relPath string) (string, error) { +	return rfs.transformPath(relPath, DECRYPT)  } -func (fs *FS) transformPath(relPath string, direction int) (string, error) { -	if fs.args.PlaintextNames { +func (rfs *reverseFS) transformPath(relPath string, direction int) (string, error) { +	if rfs.args.PlaintextNames || relPath == "" {  		return relPath, nil  	}  	var err error  	var transformedParts []string -	iv := make([]byte, 16)  	parts := strings.Split(relPath, "/")  	for _, part := range parts {  		var transformedPart string  		switch direction {  		case ENCRYPT: -			transformedPart = fs.nameTransform.EncryptName(part, iv) +			transformedPart = rfs.nameTransform.EncryptName(part, zeroDirIV)  		case DECRYPT: -			transformedPart, err = fs.nameTransform.DecryptName(part, iv) +			transformedPart, err = rfs.nameTransform.DecryptName(part, zeroDirIV)  			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 { +					fmt.Printf("converting to ENOENT\n") +					return "", syscall.ENOENT +				}  				return "", err  			}  		default: @@ -51,6 +66,6 @@ func (fs *FS) transformPath(relPath string, direction int) (string, error) {  	return filepath.Join(transformedParts...), nil  } -func (fs *FS) isFiltered(relPath string) bool { +func (rfs *reverseFS) isFiltered(relPath string) bool {  	return false  } | 
