From 527609266386890fe1657c225c643ca448bcd71d Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 8 Aug 2020 18:45:47 +0200 Subject: v2api/reverse: implement Readlink --- internal/fusefrontend_reverse/node.go | 28 +++++++++++++++---- internal/fusefrontend_reverse/node_api_check.go | 37 +++++++++++++++++++++++++ internal/fusefrontend_reverse/node_helpers.go | 31 +++++++++++++++++++-- 3 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 internal/fusefrontend_reverse/node_api_check.go diff --git a/internal/fusefrontend_reverse/node.go b/internal/fusefrontend_reverse/node.go index 713beee..884a97e 100644 --- a/internal/fusefrontend_reverse/node.go +++ b/internal/fusefrontend_reverse/node.go @@ -2,6 +2,7 @@ package fusefrontend_reverse import ( "context" + "path/filepath" "syscall" "golang.org/x/sys/unix" @@ -20,16 +21,16 @@ type Node struct { } // Lookup - FUSE call for discovering a file. -func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { +func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) { dirfd := int(-1) pName := "" - t := n.lookupFileType(name) + t := n.lookupFileType(cName) if t == typeDiriv { // gocryptfs.diriv return n.lookupDiriv(ctx, out) } else if t == typeName { // gocryptfs.longname.*.name - return n.lookupLongnameName(ctx, name, out) + return n.lookupLongnameName(ctx, cName, out) } else if t == typeConfig { // gocryptfs.conf var err error @@ -43,7 +44,7 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch defer syscall.Close(dirfd) } else if t == typeReal { // real file - dirfd, pName, errno = n.prepareAtSyscall(name) + dirfd, pName, errno = n.prepareAtSyscall(cName) if errno != 0 { return } @@ -58,7 +59,7 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch ch = n.newChild(ctx, st, out) // Translate ciphertext size in `out.Attr.Size` to plaintext size if t == typeReal { - n.translateSize(dirfd, pName, &out.Attr) + n.translateSize(dirfd, cName, pName, &out.Attr) } return ch, 0 } @@ -89,10 +90,25 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) out.Attr.FromStat(st) // Translate ciphertext size in `out.Attr.Size` to plaintext size - n.translateSize(dirfd, pName, &out.Attr) + cName := filepath.Base(n.Path()) + n.translateSize(dirfd, cName, pName, &out.Attr) if rn.args.ForceOwner != nil { out.Owner = *rn.args.ForceOwner } return 0 } + +// Readlink - FUSE call. +// +// Symlink-safe through openBackingDir() + Readlinkat(). +func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) { + dirfd, pName, errno := n.prepareAtSyscall("") + if errno != 0 { + return + } + defer syscall.Close(dirfd) + + cName := filepath.Base(n.Path()) + return n.readlink(dirfd, cName, pName) +} diff --git a/internal/fusefrontend_reverse/node_api_check.go b/internal/fusefrontend_reverse/node_api_check.go new file mode 100644 index 0000000..e926fc3 --- /dev/null +++ b/internal/fusefrontend_reverse/node_api_check.go @@ -0,0 +1,37 @@ +package fusefrontend_reverse + +import ( + "github.com/hanwen/go-fuse/v2/fs" +) + +// Check that we have implemented the fs.Node* interfaces +var _ = (fs.NodeGetattrer)((*Node)(nil)) +var _ = (fs.NodeLookuper)((*Node)(nil)) +var _ = (fs.NodeReaddirer)((*Node)(nil)) +var _ = (fs.NodeReadlinker)((*Node)(nil)) + +/* TODO +var _ = (fs.NodeOpener)((*Node)(nil)) +var _ = (fs.NodeStatfser)((*Node)(nil)) +var _ = (fs.NodeMknoder)((*Node)(nil)) +var _ = (fs.NodeGetxattrer)((*Node)(nil)) +var _ = (fs.NodeListxattrer)((*Node)(nil)) +*/ + +/* Not needed +var _ = (fs.NodeOpendirer)((*Node)(nil)) +*/ + +/* Will not implement these - reverse mode is read-only! +var _ = (fs.NodeCreater)((*Node)(nil)) +var _ = (fs.NodeMkdirer)((*Node)(nil)) +var _ = (fs.NodeRmdirer)((*Node)(nil)) +var _ = (fs.NodeUnlinker)((*Node)(nil)) +var _ = (fs.NodeSetattrer)((*Node)(nil)) +var _ = (fs.NodeLinker)((*Node)(nil)) +var _ = (fs.NodeSymlinker)((*Node)(nil)) +var _ = (fs.NodeRenamer)((*Node)(nil)) +var _ = (fs.NodeSetxattrer)((*Node)(nil)) +var _ = (fs.NodeRemovexattrer)((*Node)(nil)) +var _ = (fs.NodeCopyFileRanger)((*Node)(nil)) +*/ diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go index df9de9b..7669a17 100644 --- a/internal/fusefrontend_reverse/node_helpers.go +++ b/internal/fusefrontend_reverse/node_helpers.go @@ -26,12 +26,13 @@ const ( ) // translateSize translates the ciphertext size in `out` into plaintext size. -func (n *Node) translateSize(dirfd int, cName string, out *fuse.Attr) { +func (n *Node) translateSize(dirfd int, cName string, pName 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") + cLink, _ := n.readlink(dirfd, cName, pName) + out.Size = uint64(len(cLink)) } } @@ -151,3 +152,29 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod ch = n.NewInode(ctx, vf, id) return } + +// readlink reads and encrypts a symlink. Used by Readlink, Getattr, Lookup. +func (n *Node) readlink(dirfd int, cName string, pName string) (out []byte, errno syscall.Errno) { + plainTarget, err := syscallcompat.Readlinkat(dirfd, pName) + if err != nil { + errno = fs.ToErrno(err) + return + } + rn := n.rootNode() + if rn.args.PlaintextNames { + return []byte(plainTarget), 0 + } + // Nonce is derived from the relative *ciphertext* path + p := filepath.Join(n.Path(), cName) + nonce := pathiv.Derive(p, pathiv.PurposeSymlinkIV) + // Symlinks are encrypted like file contents and base64-encoded + cBinTarget := rn.contentEnc.EncryptBlockNonce([]byte(plainTarget), 0, nil, nonce) + cTarget := rn.nameTransform.B64EncodeToString(cBinTarget) + // The kernel will reject a symlink target above 4096 chars and return + // and I/O error to the user. Better emit the proper error ourselves. + if len(cTarget) > syscallcompat.PATH_MAX { + errno = syscall.ENAMETOOLONG + return + } + return []byte(cTarget), 0 +} -- cgit v1.2.3