// isNameFile determines if the path points to a gocryptfs.longname.*.name // file func (rfs *ReverseFS) isNameFile(relPath string) bool { if rfs.args.PlaintextNames { return false } fileType := nametransform.NameType(filepath.Base(relPath)) return fileType == nametransform.LongNameFilename }
func (rfs *ReverseFS) decryptPath(relPath string) (string, error) { if rfs.args.PlaintextNames || relPath == "" { return relPath, nil } var err error var transformedParts []string parts := strings.Split(relPath, "/") for i, part := range parts { // Start at the top and recurse currentCipherDir := filepath.Join(parts[:i]...) nameType := nametransform.NameType(part) dirIV := derivePathIV(currentCipherDir, ivPurposeDirIV) var transformedPart string if nameType == nametransform.LongNameNone { transformedPart, err = rfs.nameTransform.DecryptName(part, 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.EINVAL { return "", syscall.ENOENT } return "", err } } else if nameType == nametransform.LongNameContent { currentPlainDir := filepath.Join(transformedParts[:i]...) transformedPart, err = rfs.findLongnameParent(currentPlainDir, dirIV, part) if err != nil { return "", err } } else { // It makes no sense to decrypt a ".name" file tlog.Warn.Printf("decryptPath: tried to decrypt %q!? Returning EINVAL.", part) return "", syscall.EINVAL } transformedParts = append(transformedParts, transformedPart) } return filepath.Join(transformedParts...), nil }
// OpenDir implements pathfs.FileSystem func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, fuse.Status) { tlog.Debug.Printf("OpenDir(%s)", dirName) cDirName, err := fs.encryptPath(dirName) if err != nil { return nil, fuse.ToStatus(err) } // Read ciphertext directory cipherEntries, status := fs.FileSystem.OpenDir(cDirName, context) if cipherEntries == nil { return nil, status } // Get DirIV (stays nil if PlaintextNames is used) var cachedIV []byte var cDirAbsPath string if !fs.args.PlaintextNames { // Read the DirIV once and use it for all later name decryptions cDirAbsPath = filepath.Join(fs.args.Cipherdir, cDirName) cachedIV, err = nametransform.ReadDirIV(cDirAbsPath) if err != nil { // This can happen during normal operation when the directory has // been deleted concurrently. But it can also mean that the // gocryptfs.diriv is missing due to an error, so log the event // at "info" level. tlog.Info.Printf("OpenDir: %v", err) return nil, fuse.ToStatus(err) } } // Decrypted directory entries var plain []fuse.DirEntry var errorCount int // Filter and decrypt filenames for i := range cipherEntries { cName := cipherEntries[i].Name if dirName == "" && cName == configfile.ConfDefaultName { // silently ignore "gocryptfs.conf" in the top level dir continue } if !fs.args.PlaintextNames && cName == nametransform.DirIVFilename { // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled continue } if fs.args.PlaintextNames { plain = append(plain, cipherEntries[i]) continue } // Handle long file name isLong := nametransform.LongNameNone if fs.args.LongNames { isLong = nametransform.NameType(cName) } if isLong == nametransform.LongNameContent { cNameLong, err := nametransform.ReadLongName(filepath.Join(cDirAbsPath, cName)) if err != nil { tlog.Warn.Printf("Skipping entry %q in dir %q: Could not read .name: %v", cName, cDirName, err) errorCount++ continue } cName = cNameLong } else if isLong == nametransform.LongNameFilename { // ignore "gocryptfs.longname.*.name" continue } name, err := fs.nameTransform.DecryptName(cName, cachedIV) if err != nil { tlog.Warn.Printf("Skipping entry %q in dir %q: %s", cName, cDirName, err) errorCount++ continue } cipherEntries[i].Name = name plain = append(plain, cipherEntries[i]) } if errorCount > 0 && len(plain) == 0 { // Don't let the user stare on an empty directory. Report that things went // wrong. tlog.Warn.Printf("All %d entries in directory %q were invalid, returning EIO", errorCount, cDirName) status = fuse.EIO } return plain, status }