v2api/reverse: implement Lookup for longname

This commit is contained in:
Jakob Unterwurzacher 2020-08-02 19:33:12 +02:00
parent 4674bac838
commit 84ed139cd2
5 changed files with 148 additions and 40 deletions

View File

@ -10,7 +10,6 @@ import (
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/rfjakob/gocryptfs/internal/configfile"
"github.com/rfjakob/gocryptfs/internal/pathiv"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
)
@ -25,9 +24,16 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch
dirfd := int(-1)
pName := ""
t := n.lookupFileType(name)
// gocryptfs.conf
if t == typeConfig {
if t == typeDiriv {
// gocryptfs.diriv
return n.lookupDiriv(ctx, out)
} else if t == typeName {
// gocryptfs.longname.*.name
return n.lookupLongnameName(ctx, name, out)
} else if t == typeConfig {
// gocryptfs.conf
var err error
pName = configfile.ConfReverseName
rn := n.rootNode()
dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, "")
if err != nil {
@ -35,34 +41,6 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch
return
}
defer syscall.Close(dirfd)
pName = configfile.ConfReverseName
} else if t == typeDiriv {
// gocryptfs.diriv
dirfd, pName, errno = n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(dirfd)
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
errno = fs.ToErrno(err)
return
}
content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV)
var vf *virtualFile
vf, errno = n.newVirtualFile(content, st, inoTagDirIV)
if errno != 0 {
return nil, errno
}
out.Attr = vf.attr
// Create child node
id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino}
ch = n.NewInode(ctx, vf, id)
return
} else if t == typeName {
// gocryptfs.longname.*.name
// TODO
} else if t == typeReal {
// real file
dirfd, pName, errno = n.prepareAtSyscall(name)
@ -71,21 +49,17 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch
}
defer syscall.Close(dirfd)
}
// Get device number and inode number into `st`
st, err := syscallcompat.Fstatat2(dirfd, pName, 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
if t == typeReal {
// Translate ciphertext size in `out.Attr.Size` to plaintext size
n.translateSize(dirfd, pName, &out.Attr)
}
return ch, 0
}

View File

@ -5,8 +5,24 @@ import (
"path/filepath"
"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/pathiv"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
)
const (
// File names are padded to 16-byte multiples, encrypted and
// base64-encoded. We can encode at most 176 bytes to stay below the 255
// bytes limit:
// * base64(176 bytes) = 235 bytes
// * base64(192 bytes) = 256 bytes (over 255!)
// But the PKCS#7 padding is at least one byte. This means we can only use
// 175 bytes for the file name.
shortNameMax = 175
)
// translateSize translates the ciphertext size in `out` into plaintext size.
@ -36,13 +52,13 @@ func (n *Node) rootNode() *RootNode {
// 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) {
func (n *Node) prepareAtSyscall(child string) (dirfd int, pName string, errno syscall.Errno) {
p := n.Path()
if child != "" {
p = filepath.Join(p, child)
}
rn := n.rootNode()
dirfd, cName, err := rn.openBackingDir(p)
dirfd, pName, err := rn.openBackingDir(p)
if err != nil {
errno = fs.ToErrno(err)
}
@ -71,3 +87,67 @@ func (n *Node) isRoot() bool {
rn := n.rootNode()
return &rn.Node == n
}
func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
dirfd, pName1, errno := n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(dirfd)
// Find the file the gocryptfs.longname.XYZ.name file belongs to in the
// directory listing
fd, err := syscallcompat.Openat(dirfd, pName1, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil {
errno = fs.ToErrno(err)
return
}
diriv := pathiv.Derive(n.Path(), pathiv.PurposeDirIV)
rn := n.rootNode()
pName, cFullname, errno := rn.findLongnameParent(fd, diriv, nameFile)
if errno != 0 {
return
}
// Get attrs from parent file
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
errno = fs.ToErrno(err)
return
}
var vf *virtualFile
vf, errno = n.newVirtualFile([]byte(cFullname), st, inoTagNameFile)
if errno != 0 {
return nil, errno
}
out.Attr = vf.attr
// Create child node
id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino}
ch = n.NewInode(ctx, vf, id)
return
}
// lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`.
func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
dirfd, pName, errno := n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(dirfd)
st, err := syscallcompat.Fstatat2(dirfd, pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
errno = fs.ToErrno(err)
return
}
content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV)
var vf *virtualFile
vf, errno = n.newVirtualFile(content, st, inoTagDirIV)
if errno != 0 {
return nil, errno
}
out.Attr = vf.attr
// Create child node
id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino}
ch = n.NewInode(ctx, vf, id)
return
}

View File

@ -1,10 +1,19 @@
package fusefrontend_reverse
import (
"log"
"strings"
"syscall"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/fusefrontend"
"github.com/rfjakob/gocryptfs/internal/inomap"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/sabhiram/go-gitignore"
)
@ -37,3 +46,35 @@ func NewRootNode(args fusefrontend.Args, c *contentenc.ContentEnc, n nametransfo
excluder: prepareExcluder(args),
}
}
// You can pass either gocryptfs.longname.XYZ.name or gocryptfs.longname.XYZ.
func (rn *RootNode) findLongnameParent(fd int, diriv []byte, longname string) (pName string, cFullName string, errno syscall.Errno) {
if strings.HasSuffix(longname, nametransform.LongNameSuffix) {
longname = nametransform.RemoveLongNameSuffix(longname)
}
entries, err := syscallcompat.Getdents(fd)
if err != nil {
errno = fs.ToErrno(err)
return
}
for _, entry := range entries {
if len(entry.Name) <= shortNameMax {
continue
}
cFullName = rn.nameTransform.EncryptName(entry.Name, diriv)
if len(cFullName) <= unix.NAME_MAX {
// Entry should have been skipped by the "continue" above
log.Panic("logic error or wrong shortNameMax constant?")
}
hName := rn.nameTransform.HashLongName(cFullName)
if longname == hName {
pName = entry.Name
break
}
}
if pName == "" {
errno = syscall.ENOENT
return
}
return
}

View File

@ -47,11 +47,20 @@ func (rfs *RootNode) rDecryptName(cName string, dirIV []byte, pDir string) (pNam
return "", err
}
} else if nameType == nametransform.LongNameContent {
panic("todo")
//pName, err = rfs.findLongnameParent(pDir, dirIV, cName)
dirfd, err := syscallcompat.OpenDirNofollow(rfs.args.Cipherdir, filepath.Dir(pDir))
if err != nil {
return "", err
}
defer syscall.Close(dirfd)
fd, err := syscallcompat.Openat(dirfd, filepath.Base(pDir), syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil {
return "", err
}
var errno syscall.Errno
pName, _, errno = rfs.findLongnameParent(fd, dirIV, cName)
if errno != 0 {
return "", errno
}
} else {
// It makes no sense to decrypt a ".name" file. This is a virtual file
// that has no representation in the plaintext filesystem. ".name"

View File

@ -23,6 +23,10 @@ type NameTransformer interface {
DecryptName(cipherName string, iv []byte) (string, error)
EncryptName(plainName string, iv []byte) string
EncryptAndHashName(name string, iv []byte) (string, error)
// HashLongName - take the hash of a long string "name" and return
// "gocryptfs.longname.[sha256]"
//
// This function does not do any I/O.
HashLongName(name string) string
WriteLongNameAt(dirfd int, hashName string, plainName string) error
B64EncodeToString(src []byte) string