diff options
| -rw-r--r-- | internal/fusefrontend_reverse/node.go | 66 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/node_helpers.go | 67 | ||||
| -rw-r--r-- | internal/fusefrontend_reverse/rpath.go | 102 | 
3 files changed, 235 insertions, 0 deletions
| diff --git a/internal/fusefrontend_reverse/node.go b/internal/fusefrontend_reverse/node.go index 6a1294a..a102a66 100644 --- a/internal/fusefrontend_reverse/node.go +++ b/internal/fusefrontend_reverse/node.go @@ -1,7 +1,15 @@  package fusefrontend_reverse  import ( +	"context" +	"syscall" + +	"golang.org/x/sys/unix" +  	"github.com/hanwen/go-fuse/v2/fs" +	"github.com/hanwen/go-fuse/v2/fuse" + +	"github.com/rfjakob/gocryptfs/internal/syscallcompat"  )  // Node is a file or directory in the filesystem tree @@ -9,3 +17,61 @@ import (  type Node struct {  	fs.Inode  } + +// Lookup - FUSE call for discovering a file. +// TODO handle virtual files +func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { +	dirfd, cName, errno := n.prepareAtSyscall(name) +	if errno != 0 { +		return +	} +	defer syscall.Close(dirfd) + +	// Get device number and inode number into `st` +	st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW) +	if err != nil { +		return nil, fs.ToErrno(err) +	} + +	// Create new inode and fill `out` +	ch = n.newChild(ctx, st, out) + +	// Translate ciphertext size in `out.Attr.Size` to plaintext size +	n.translateSize(dirfd, cName, &out.Attr) + +	return ch, 0 +} + +// GetAttr - FUSE call for stat()ing a file. +// +// GetAttr is symlink-safe through use of openBackingDir() and Fstatat(). +func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) (errno syscall.Errno) { +	// If the kernel gives us a file handle, use it. +	if f != nil { +		return f.(fs.FileGetattrer).Getattr(ctx, out) +	} + +	dirfd, cName, errno := n.prepareAtSyscall("") +	if errno != 0 { +		return +	} +	defer syscall.Close(dirfd) + +	st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW) +	if err != nil { +		return fs.ToErrno(err) +	} + +	// Fix inode number +	rn := n.rootNode() +	rn.inoMap.TranslateStat(st) +	out.Attr.FromStat(st) + +	// Translate ciphertext size in `out.Attr.Size` to plaintext size +	n.translateSize(dirfd, cName, &out.Attr) + +	if rn.args.ForceOwner != nil { +		out.Owner = *rn.args.ForceOwner +	} +	return 0 +} diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go new file mode 100644 index 0000000..a26ee81 --- /dev/null +++ b/internal/fusefrontend_reverse/node_helpers.go @@ -0,0 +1,67 @@ +package fusefrontend_reverse + +import ( +	"context" +	"path/filepath" +	"syscall" + +	"github.com/hanwen/go-fuse/v2/fs" +	"github.com/hanwen/go-fuse/v2/fuse" +) + +// translateSize translates the ciphertext size in `out` into plaintext size. +func (n *Node) translateSize(dirfd int, cName string, out *fuse.Attr) { +	if out.IsRegular() { +		rn := n.rootNode() +		out.Size = rn.contentEnc.PlainSizeToCipherSize(out.Size) +	} else if out.IsSymlink() { +		panic("todo: call readlink once it is implemented") +	} +} + +// Path returns the relative plaintext path of this node +func (n *Node) Path() string { +	return n.Inode.Path(n.Root()) +} + +// rootNode returns the Root Node of the filesystem. +func (n *Node) rootNode() *RootNode { +	return n.Root().Operations().(*RootNode) +} + +// prepareAtSyscall returns a (dirfd, cName) pair that can be used +// with the "___at" family of system calls (openat, fstatat, unlinkat...) to +// access the backing encrypted directory. +// +// If you pass a `child` file name, the (dirfd, cName) pair will refer to +// a child of this node. +// If `child` is empty, the (dirfd, cName) pair refers to this node itself. +func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) { +	p := n.Path() +	if child != "" { +		p = filepath.Join(p, child) +	} +	rn := n.rootNode() +	dirfd, cName, err := rn.openBackingDir(p) +	if err != nil { +		errno = fs.ToErrno(err) +	} +	return +} + +// newChild attaches a new child inode to n. +// The passed-in `st` will be modified to get a unique inode number. +func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.EntryOut) *fs.Inode { +	// Get unique inode number +	rn := n.rootNode() +	rn.inoMap.TranslateStat(st) +	out.Attr.FromStat(st) +	// Create child node +	id := fs.StableAttr{ +		Mode: uint32(st.Mode), +		Gen:  1, +		Ino:  st.Ino, +	} +	node := &Node{} +	return n.NewInode(ctx, node, id) +} diff --git a/internal/fusefrontend_reverse/rpath.go b/internal/fusefrontend_reverse/rpath.go new file mode 100644 index 0000000..ed37847 --- /dev/null +++ b/internal/fusefrontend_reverse/rpath.go @@ -0,0 +1,102 @@ +package fusefrontend_reverse + +import ( +	"encoding/base64" +	"path/filepath" +	"strings" +	"syscall" + +	"github.com/rfjakob/gocryptfs/internal/nametransform" +	"github.com/rfjakob/gocryptfs/internal/pathiv" +	"github.com/rfjakob/gocryptfs/internal/syscallcompat" +	"github.com/rfjakob/gocryptfs/internal/tlog" +) + +// abs basically returns storage dir + "/" + relPath. +// It takes an error parameter so it can directly wrap decryptPath like this: +// a, err := rfs.abs(rfs.decryptPath(relPath)) +// abs never generates an error on its own. In other words, abs(p, nil) never +// fails. +func (rfs *RootNode) abs(relPath string, err error) (string, error) { +	if err != nil { +		return "", err +	} +	return filepath.Join(rfs.args.Cipherdir, relPath), nil +} + +// rDecryptName decrypts the ciphertext name "cName", given the dirIV of the +// directory "cName" lies in. The relative plaintext path to the directory +// "pDir" is used if a "gocryptfs.longname.XYZ.name" must be resolved. +func (rfs *RootNode) rDecryptName(cName string, dirIV []byte, pDir string) (pName string, err error) { +	nameType := nametransform.NameType(cName) +	if nameType == nametransform.LongNameNone { +		pName, err = rfs.nameTransform.DecryptName(cName, 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 +			} +			// Stat attempts on the link target of encrypted symlinks. +			// These are always valid base64 but the length is not a +			// multiple of 16. +			if err == syscall.EBADMSG { +				return "", syscall.ENOENT +			} +			return "", err +		} +	} else if nameType == nametransform.LongNameContent { +		panic("todo") +		//pName, err = rfs.findLongnameParent(pDir, dirIV, cName) +		if err != nil { +			return "", err +		} +	} else { +		// It makes no sense to decrypt a ".name" file. This is a virtual file +		// that has no representation in the plaintext filesystem. ".name" +		// files should have already been handled in virtualfile.go. +		tlog.Warn.Printf("rDecryptName: cannot decrypt virtual file %q", cName) +		return "", syscall.EINVAL +	} +	return pName, nil +} + +// decryptPath decrypts a relative ciphertext path to a relative plaintext +// path. +func (rn *RootNode) decryptPath(relPath string) (string, error) { +	if rn.args.PlaintextNames || relPath == "" { +		return relPath, nil +	} +	parts := strings.Split(relPath, "/") +	var transformedParts []string +	for i := range parts { +		// Start at the top and recurse +		currentCipherDir := filepath.Join(parts[:i]...) +		currentPlainDir := filepath.Join(transformedParts[:i]...) +		dirIV := pathiv.Derive(currentCipherDir, pathiv.PurposeDirIV) +		transformedPart, err := rn.rDecryptName(parts[i], dirIV, currentPlainDir) +		if err != nil { +			return "", err +		} +		transformedParts = append(transformedParts, transformedPart) +	} +	pRelPath := filepath.Join(transformedParts...) +	return pRelPath, nil +} + +// openBackingDir receives an already decrypted relative path +// "pRelPath", opens the directory that contains the target file/dir +// and returns the fd to the directory and the decrypted name of the +// target file. The fd/name pair is intended for use with fchownat and +// friends. +func (rn *RootNode) openBackingDir(pRelPath string) (dirfd int, pName string, err error) { +	// Open directory, safe against symlink races +	pDir := filepath.Dir(pRelPath) +	dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, pDir) +	if err != nil { +		return -1, "", err +	} +	pName = filepath.Base(pRelPath) +	return dirfd, pName, nil +} | 
