From 10f38e88707f3a1f1ad69769219839a30a80c165 Mon Sep 17 00:00:00 2001
From: Jakob Unterwurzacher
Date: Fri, 2 Sep 2016 23:45:52 +0200
Subject: reverse: generate file header for Read()

Also create virtual gocryptfs.diriv entries (no content yet).
---
 internal/fusefrontend_reverse/rfile.go | 71 ++++++++++++++++++++++++++--------
 internal/fusefrontend_reverse/rfs.go   | 54 +++++++++++++++++++++++++-
 2 files changed, 107 insertions(+), 18 deletions(-)

diff --git a/internal/fusefrontend_reverse/rfile.go b/internal/fusefrontend_reverse/rfile.go
index 15f2764..d363df9 100644
--- a/internal/fusefrontend_reverse/rfile.go
+++ b/internal/fusefrontend_reverse/rfile.go
@@ -1,6 +1,7 @@
 package fusefrontend_reverse
 
 import (
+	"bytes"
 	"fmt"
 	"io"
 	"os"
@@ -12,10 +13,12 @@ import (
 	"github.com/rfjakob/gocryptfs/internal/tlog"
 )
 
-var zeroFileId []byte
+// File header that contains an all-zero File ID
+var zeroFileHeader *contentenc.FileHeader
 
 func init() {
-	zeroFileId = make([]byte, 16)
+	zeroFileHeader = contentenc.RandomHeader()
+	zeroFileHeader.Id = make([]byte, contentenc.HEADER_ID_LEN)
 }
 
 type reverseFile struct {
@@ -41,12 +44,12 @@ func (rf *reverseFile) GetAttr(*fuse.Attr) fuse.Status {
 	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)
+// readFile - read from the backing plaintext file, encrypt it, return the
+// ciphertext.
+// "off" ... ciphertext offset (must be >= HEADER_LEN)
+// "length" ... ciphertext length
+func (rf *reverseFile) readFile(off uint64, length uint64) (out []byte, err error) {
+	blocks := rf.contentEnc.ExplodeCipherRange(off, length)
 
 	// Read the backing plaintext in one go
 	alignedOffset, alignedLength := contentenc.JointPlaintextRange(blocks)
@@ -54,26 +57,62 @@ func (rf *reverseFile) Read(buf []byte, off int64) (resultData fuse.ReadResult,
 	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)
+		tlog.Warn.Printf("reverseFile.readFile: ReadAt: %s", err.Error())
+		return nil, err
 	}
 	// Truncate buffer down to actually read bytes
 	plaintext = plaintext[0:n]
 
 	// Encrypt blocks
-	ciphertext := rf.contentEnc.EncryptBlocks(plaintext, blocks[0].BlockNo, zeroFileId)
+	ciphertext := rf.contentEnc.EncryptBlocks(plaintext, blocks[0].BlockNo, zeroFileHeader.Id)
 
 	// 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]
+		out = ciphertext[skip:endWant]
 	} else if lenHave > int(skip) {
-		out = plaintext[skip:lenHave]
+		out = ciphertext[skip:lenHave]
+	} // else: out stays empty, file was smaller than the requested offset
+
+	return out, nil
+}
+
+// Read - FUSE call
+func (rf *reverseFile) Read(buf []byte, ioff int64) (resultData fuse.ReadResult, status fuse.Status) {
+	length := uint64(len(buf))
+	off := uint64(ioff)
+	var out bytes.Buffer
+	var headerPart []byte
+
+	// Create a virtual file header
+	if off < contentenc.HEADER_LEN {
+		headerPart = zeroFileHeader.Pack()
+		headerPart = headerPart[off:]
+		if off+length < contentenc.HEADER_LEN {
+			headerPart = headerPart[:length]
+		}
+	}
+	out.Write(headerPart)
+	hLen := uint64(len(headerPart))
+	off += hLen
+	length -= hLen
+
+	// Read actual file data
+	if length > 0 {
+		fileData, err := rf.readFile(off, length)
+		if err != nil {
+			return nil, fuse.ToStatus(err)
+		}
+		if len(fileData) == 0 {
+			// If we could not read any actual data, we also don't want to
+			// return the file header. An empty file stays empty in encrypted
+			// form.
+			return nil, fuse.OK
+		}
+		out.Write(fileData)
 	}
-	// else: out stays empty, file was smaller than the requested offset
 
-	return fuse.ReadResultData(out), fuse.OK
+	return fuse.ReadResultData(out.Bytes()), fuse.OK
 }
diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go
index 68a5ac8..3243dfd 100644
--- a/internal/fusefrontend_reverse/rfs.go
+++ b/internal/fusefrontend_reverse/rfs.go
@@ -1,7 +1,10 @@
 package fusefrontend_reverse
 
 import (
+	"fmt"
 	"os"
+	"path"
+	"strings"
 	"syscall"
 
 	"github.com/hanwen/go-fuse/fuse"
@@ -14,6 +17,10 @@ import (
 	"github.com/rfjakob/gocryptfs/internal/nametransform"
 )
 
+const (
+	DirIVMode = syscall.S_IFREG | 0400
+)
+
 type reverseFS struct {
 	// Embed pathfs.defaultFileSystem for a ENOSYS implementation of all methods
 	pathfs.FileSystem
@@ -44,6 +51,46 @@ func NewFS(args fusefrontend.Args) *reverseFS {
 }
 
 func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr, fuse.Status) {
+	// Handle gocryptfs.diriv
+	if relPath == nametransform.DirIVFilename ||
+		strings.HasSuffix(relPath, nametransform.DirIVFilename) {
+
+		fmt.Printf("Handling gocryptfs.diriv\n")
+
+		cDir := path.Dir(relPath)
+		if cDir == "." {
+			cDir = ""
+		}
+		dir, err := rfs.decryptPath(cDir)
+		if err != nil {
+			fmt.Printf("decrypt err %q\n", cDir)
+			return nil, fuse.ToStatus(err)
+		}
+		// Does the parent dir exist?
+		a, status := rfs.loopbackfs.GetAttr(dir, context)
+		if !status.Ok() {
+			fmt.Printf("missing parent\n")
+			return nil, status
+		}
+		// Is it a dir at all?
+		if !a.IsDir() {
+			fmt.Printf("not isdir\n")
+			return nil, fuse.ENOTDIR
+		}
+		// Does the user have execute permissions?
+		if a.Mode&syscall.S_IXUSR == 0 {
+			fmt.Printf("not exec")
+			return nil, fuse.EPERM
+		}
+		// All good. Let's fake the file.
+		// We use the inode number of the parent dir (can this cause problems?).
+		a.Mode = DirIVMode
+		a.Size = nametransform.DirIVLen
+		a.Nlink = 1
+
+		return a, fuse.OK
+	}
+
 	if rfs.isFiltered(relPath) {
 		return nil, fuse.EPERM
 	}
@@ -52,8 +99,8 @@ func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr
 		return nil, fuse.ToStatus(err)
 	}
 	a, status := rfs.loopbackfs.GetAttr(relPath, context)
-	if a == nil {
-		return a, status
+	if !status.Ok() {
+		return nil, status
 	}
 	// Calculate encrypted file size
 	if a.IsRegular() {
@@ -105,5 +152,8 @@ func (rfs *reverseFS) OpenDir(relPath string, context *fuse.Context) ([]fuse.Dir
 			return nil, fuse.ToStatus(err)
 		}
 	}
+	// Add virtual gocryptfs.diriv
+	entries = append(entries, fuse.DirEntry{syscall.S_IFREG | 0400, nametransform.DirIVFilename})
+
 	return entries, fuse.OK
 }
-- 
cgit v1.2.3