diff --git a/cli_args.go b/cli_args.go index dd570d3..d80e937 100644 --- a/cli_args.go +++ b/cli_args.go @@ -20,6 +20,9 @@ type argContainer struct { // Configuration file name override config string notifypid, scryptn int + // _configCustom is true when the user sets a custom config file name. + // This is not a CLI option. + _configCustom bool } var flagSet *flag.FlagSet diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index 64ca899..204647d 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -14,4 +14,8 @@ type Args struct { // Should we chown a file after it has been created? // This only makes sense if (1) allow_other is set and (2) we run as root. PreserveOwner bool + // ConfigCustom is true when the user select a non-default config file + // location. If it is false, reverse mode maps ".gocryptfs.reverse.conf" + // to "gocryptfs.conf" in the plaintext dir. + ConfigCustom bool } diff --git a/internal/fusefrontend_reverse/rfs.go b/internal/fusefrontend_reverse/rfs.go index 1c143bf..36f49be 100644 --- a/internal/fusefrontend_reverse/rfs.go +++ b/internal/fusefrontend_reverse/rfs.go @@ -17,6 +17,7 @@ import ( "github.com/rfjakob/gocryptfs/internal/cryptocore" "github.com/rfjakob/gocryptfs/internal/fusefrontend" "github.com/rfjakob/gocryptfs/internal/nametransform" + "github.com/rfjakob/gocryptfs/internal/tlog" ) const ( @@ -110,13 +111,19 @@ func (rfs *reverseFS) dirIVAttr(relPath string, context *fuse.Context) (*fuse.At } // isDirIV determines if the path points to a gocryptfs.diriv file -func isDirIV(relPath string) bool { +func (rfs *reverseFS) isDirIV(relPath string) bool { + if rfs.args.PlaintextNames { + return false + } return filepath.Base(relPath) == nametransform.DirIVFilename } // isNameFile determines if the path points to a gocryptfs.longname.*.name // file -func isNameFile(relPath string) bool { +func (rfs *reverseFS) isNameFile(relPath string) bool { + if rfs.args.PlaintextNames { + return false + } fileType := nametransform.NameType(filepath.Base(relPath)) return fileType == nametransform.LongNameFilename } @@ -161,19 +168,15 @@ func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr if relPath == configfile.ConfDefaultName { return rfs.inoAwareStat(configfile.ConfReverseName) } - if rfs.isFiltered(relPath) { - return nil, fuse.EPERM - } - // Handle virtual files var f nodefs.File var status fuse.Status virtual := false - if isDirIV(relPath) { + if rfs.isDirIV(relPath) { virtual = true f, status = rfs.newDirIVFile(relPath) } - if isNameFile(relPath) { + if rfs.isNameFile(relPath) { virtual = true f, status = rfs.newNameFile(relPath) } @@ -204,7 +207,7 @@ func (rfs *reverseFS) GetAttr(relPath string, context *fuse.Context) (*fuse.Attr // Access - FUSE call func (rfs *reverseFS) Access(relPath string, mode uint32, context *fuse.Context) fuse.Status { - if isDirIV(relPath) { + if rfs.isDirIV(relPath) { return fuse.OK } if rfs.isFiltered(relPath) { @@ -223,10 +226,10 @@ func (rfs *reverseFS) Open(relPath string, flags uint32, context *fuse.Context) // gocryptfs.conf maps to .gocryptfs.reverse.conf in the plaintext directory return rfs.loopbackfs.Open(configfile.ConfReverseName, flags, context) } - if isDirIV(relPath) { + if rfs.isDirIV(relPath) { return rfs.newDirIVFile(relPath) } - if isNameFile(relPath) { + if rfs.isNameFile(relPath) { return rfs.newNameFile(relPath) } if rfs.isFiltered(relPath) { @@ -235,6 +238,31 @@ func (rfs *reverseFS) Open(relPath string, flags uint32, context *fuse.Context) return rfs.NewFile(relPath, flags) } +func (rfs *reverseFS) openDirPlaintextnames(relPath string, entries []fuse.DirEntry) ([]fuse.DirEntry, fuse.Status) { + if relPath != "" || rfs.args.ConfigCustom { + return entries, fuse.OK + } + // We are in the root dir and the default config file name + // ".gocryptfs.reverse.conf" is used. We map it to "gocryptfs.conf". + dupe := -1 + status := fuse.OK + for i := range entries { + if entries[i].Name == configfile.ConfReverseName { + entries[i].Name = configfile.ConfDefaultName + } else if entries[i].Name == configfile.ConfDefaultName { + dupe = i + } + } + if dupe >= 0 { + // Warn the user loudly: The gocryptfs.conf_NAME_COLLISION file will + // throw ENOENT errors that are hard to miss. + tlog.Warn.Printf("The file %s is mapped to %s and shadows another file. Please rename %s in %s .", + configfile.ConfReverseName, configfile.ConfDefaultName, configfile.ConfDefaultName, rfs.args.Cipherdir) + entries[dupe].Name = "gocryptfs.conf_NAME_COLLISION_" + fmt.Sprintf("%d", cryptocore.RandUint64()) + } + return entries, status +} + // OpenDir - FUSE readdir call func (rfs *reverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { relPath, err := rfs.decryptPath(cipherPath) @@ -246,6 +274,9 @@ func (rfs *reverseFS) OpenDir(cipherPath string, context *fuse.Context) ([]fuse. if entries == nil { return nil, status } + if rfs.args.PlaintextNames { + return rfs.openDirPlaintextnames(cipherPath, entries) + } // Allocate maximum possible number of virtual files. // If all files have long names we need a virtual ".name" file for each, // plus one for gocryptfs.diriv. diff --git a/main.go b/main.go index 467a143..5ae8146 100644 --- a/main.go +++ b/main.go @@ -155,6 +155,7 @@ func main() { os.Exit(ErrExitInit) } tlog.Info.Printf("Using config file at custom location %s", args.config) + args._configCustom = true } else if args.reverse { args.config = filepath.Join(args.cipherdir, configfile.ConfReverseName) } else { diff --git a/mount.go b/mount.go index 90443ff..000d632 100644 --- a/mount.go +++ b/mount.go @@ -125,6 +125,7 @@ func initFuseFrontend(key []byte, args *argContainer, confFile *configfile.ConfF PlaintextNames: args.plaintextnames, LongNames: args.longnames, CryptoBackend: cryptoBackend, + ConfigCustom: args._configCustom, } // confFile is nil when "-zerokey" or "-masterkey" was used if confFile != nil { diff --git a/tests/reverse/correctness_test.go b/tests/reverse/correctness_test.go index 40bd320..c75a1fd 100644 --- a/tests/reverse/correctness_test.go +++ b/tests/reverse/correctness_test.go @@ -1,6 +1,7 @@ package reverse_test import ( + "io/ioutil" "os" "testing" //"time" @@ -49,3 +50,17 @@ func TestSymlinks(t *testing.T) { t.Errorf("wrong symlink target: want=%q have=%q", target, actualTarget) } } + +// .gocryptfs.reverse.conf in the plaintext dir should be visible as +// gocryptfs.conf +func TestConfigMapping(t *testing.T) { + c := dirB + "/gocryptfs.conf" + test_helpers.VerifyExistence(c) + data, err := ioutil.ReadFile(c) + if err != nil { + t.Fatal(err) + } + if len(data) == 0 { + t.Errorf("empty file") + } +} diff --git a/tests/reverse/main_test.go b/tests/reverse/main_test.go index a42bb82..a2b7eb6 100644 --- a/tests/reverse/main_test.go +++ b/tests/reverse/main_test.go @@ -10,26 +10,38 @@ import ( var dirA, dirB, dirC string var x240 string +var plaintextnames bool func TestMain(m *testing.M) { x240 = string(bytes.Repeat([]byte("x"), 240)) - dirA = test_helpers.TmpDir + "/a" - dirB = test_helpers.TmpDir + "/b" - dirC = test_helpers.TmpDir + "/c" - if err := os.Mkdir(dirA, 0700); err != nil { - panic(err) + var r int + for _, plaintextnames = range []bool{false, true} { + argsA := []string{"-reverse"} + if plaintextnames { + argsA = append(argsA, "-plaintextnames") + } + dirA = test_helpers.InitFS(nil, argsA...) + dirB = test_helpers.TmpDir + "/b" + dirC = test_helpers.TmpDir + "/c" + if err := os.Mkdir(dirB, 0700); err != nil { + panic(err) + } + if err := os.Mkdir(dirC, 0700); err != nil { + panic(err) + } + test_helpers.MountOrExit(dirA, dirB, "-reverse", "-extpass", "echo test") + test_helpers.MountOrExit(dirB, dirC, "-extpass", "echo test") + r = m.Run() + test_helpers.UnmountPanic(dirC) + test_helpers.UnmountPanic(dirB) + + os.RemoveAll(dirA) + os.RemoveAll(dirB) + os.RemoveAll(dirC) + + if r != 0 { + os.Exit(r) + } } - if err := os.Mkdir(dirB, 0700); err != nil { - panic(err) - } - if err := os.Mkdir(dirC, 0700); err != nil { - panic(err) - } - test_helpers.MountOrExit(dirA, dirB, "-zerokey", "-reverse") - test_helpers.MountOrExit(dirB, dirC, "-zerokey", "-aessiv") - r := m.Run() - test_helpers.UnmountPanic(dirC) - test_helpers.UnmountPanic(dirB) - os.RemoveAll(test_helpers.TmpDir) os.Exit(r) }