04858ddd22
xfstests generic/523 discovered that we allowed to set xattrs with "/" in the name, but did not allow to read them later. With this change we do not allow to set them in the first place.
169 lines
5.1 KiB
Go
169 lines
5.1 KiB
Go
package nametransform
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
|
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
|
)
|
|
|
|
const (
|
|
// LongNameSuffix is the suffix used for files with long names.
|
|
// Files with long names are stored in two files:
|
|
// gocryptfs.longname.[sha256] <--- File content, prefix = gocryptfs.longname.
|
|
// gocryptfs.longname.[sha256].name <--- File name, suffix = .name
|
|
LongNameSuffix = ".name"
|
|
longNamePrefix = "gocryptfs.longname."
|
|
)
|
|
|
|
// HashLongName - take the hash of a long string "name" and return
|
|
// "gocryptfs.longname.[sha256]"
|
|
//
|
|
// This function does not do any I/O.
|
|
func (n *NameTransform) HashLongName(name string) string {
|
|
hashBin := sha256.Sum256([]byte(name))
|
|
hashBase64 := n.B64.EncodeToString(hashBin[:])
|
|
return longNamePrefix + hashBase64
|
|
}
|
|
|
|
// Values returned by IsLongName
|
|
const (
|
|
// LongNameContent is the file that stores the file content.
|
|
// Example: gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=
|
|
LongNameContent = iota
|
|
// LongNameFilename is the file that stores the full encrypted filename.
|
|
// Example: gocryptfs.longname.URrM8kgxTKYMgCk4hKk7RO9Lcfr30XQof4L_5bD9Iro=.name
|
|
LongNameFilename = iota
|
|
// LongNameNone is used when the file does not have a long name.
|
|
// Example: i1bpTaVLZq7sRNA9mL_2Ig==
|
|
LongNameNone = iota
|
|
)
|
|
|
|
// NameType - detect if cName is
|
|
// gocryptfs.longname.[sha256] ........ LongNameContent (content of a long name file)
|
|
// gocryptfs.longname.[sha256].name .... LongNameFilename (full file name of a long name file)
|
|
// else ................................ LongNameNone (normal file)
|
|
//
|
|
// This function does not do any I/O.
|
|
func NameType(cName string) int {
|
|
if !strings.HasPrefix(cName, longNamePrefix) {
|
|
return LongNameNone
|
|
}
|
|
if strings.HasSuffix(cName, LongNameSuffix) {
|
|
return LongNameFilename
|
|
}
|
|
return LongNameContent
|
|
}
|
|
|
|
// IsLongContent returns true if "cName" is the content store of a long name
|
|
// file (looks like "gocryptfs.longname.[sha256]").
|
|
//
|
|
// This function does not do any I/O.
|
|
func IsLongContent(cName string) bool {
|
|
return NameType(cName) == LongNameContent
|
|
}
|
|
|
|
// RemoveLongNameSuffix removes the ".name" suffix from cName, returning the corresponding
|
|
// content file name.
|
|
// No check is made if cName actually is a LongNameFilename.
|
|
func RemoveLongNameSuffix(cName string) string {
|
|
return cName[:len(cName)-len(LongNameSuffix)]
|
|
}
|
|
|
|
// ReadLongName - read cName + ".name" from the directory opened as dirfd.
|
|
//
|
|
// Symlink-safe through Openat().
|
|
func ReadLongNameAt(dirfd int, cName string) (string, error) {
|
|
cName += LongNameSuffix
|
|
var f *os.File
|
|
{
|
|
fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
f = os.NewFile(uintptr(fd), "")
|
|
// fd runs out of scope here
|
|
}
|
|
defer f.Close()
|
|
// 256 (=255 padded to 16) bytes base64-encoded take 344 bytes: "AAAAAAA...AAA=="
|
|
lim := 344
|
|
// Allocate a bigger buffer so we see whether the file is too big
|
|
buf := make([]byte, lim+1)
|
|
n, err := f.ReadAt(buf, 0)
|
|
if err != nil && err != io.EOF {
|
|
return "", err
|
|
}
|
|
if n == 0 {
|
|
return "", fmt.Errorf("ReadLongName: empty file")
|
|
}
|
|
if n > lim {
|
|
return "", fmt.Errorf("ReadLongName: size=%d > limit=%d", n, lim)
|
|
}
|
|
return string(buf[0:n]), nil
|
|
}
|
|
|
|
// DeleteLongName deletes "hashName.name" in the directory opened at "dirfd".
|
|
//
|
|
// This function is symlink-safe through the use of Unlinkat().
|
|
func DeleteLongNameAt(dirfd int, hashName string) error {
|
|
err := syscallcompat.Unlinkat(dirfd, hashName+LongNameSuffix, 0)
|
|
if err != nil {
|
|
tlog.Warn.Printf("DeleteLongName: %v", err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// WriteLongName encrypts plainName and writes it into "hashName.name".
|
|
// For the convenience of the caller, plainName may also be a path and will be
|
|
// Base()named internally.
|
|
//
|
|
// This function is symlink-safe through the use of Openat().
|
|
func (n *NameTransform) WriteLongNameAt(dirfd int, hashName string, plainName string) (err error) {
|
|
plainName = filepath.Base(plainName)
|
|
|
|
// Encrypt the basename
|
|
dirIV, err := ReadDirIVAt(dirfd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cName, err := n.EncryptName(plainName, dirIV)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write the encrypted name into hashName.name
|
|
fdRaw, err := syscallcompat.Openat(dirfd, hashName+LongNameSuffix,
|
|
syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, namePerms)
|
|
if err != nil {
|
|
// Don't warn if the file already exists - this is allowed for renames
|
|
// and should be handled by the caller.
|
|
if err != syscall.EEXIST {
|
|
tlog.Warn.Printf("WriteLongName: Openat: %v", err)
|
|
}
|
|
return err
|
|
}
|
|
fd := os.NewFile(uintptr(fdRaw), hashName+LongNameSuffix)
|
|
_, err = fd.Write([]byte(cName))
|
|
if err != nil {
|
|
fd.Close()
|
|
tlog.Warn.Printf("WriteLongName: Write: %v", err)
|
|
// Delete incomplete longname file
|
|
syscallcompat.Unlinkat(dirfd, hashName+LongNameSuffix, 0)
|
|
return err
|
|
}
|
|
err = fd.Close()
|
|
if err != nil {
|
|
tlog.Warn.Printf("WriteLongName: Close: %v", err)
|
|
// Delete incomplete longname file
|
|
syscallcompat.Unlinkat(dirfd, hashName+LongNameSuffix, 0)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|