package main import ( "C" "fmt" "io" "strings" "syscall" "unsafe" "golang.org/x/sys/unix" "libgocryptfs/v2/allocator" "libgocryptfs/v2/internal/configfile" "libgocryptfs/v2/internal/cryptocore" "libgocryptfs/v2/internal/nametransform" "libgocryptfs/v2/internal/syscallcompat" ) func mkdirWithIv(dirfd int, cName string, mode uint32) error { // Between the creation of the directory and the creation of gocryptfs.diriv // the directory is inconsistent. Take the lock to prevent other readers // from seeing it. err := unix.Mkdirat(dirfd, cName, mode) if err != nil { return err } dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscallcompat.O_PATH, 0) if err == nil { // Create gocryptfs.diriv err = nametransform.WriteDirIVAt(dirfd2) syscall.Close(dirfd2) } if err != nil { // Delete inconsistent directory (missing gocryptfs.diriv!) syscallcompat.Unlinkat(dirfd, cName, unix.AT_REMOVEDIR) } return err } //export gcf_list_dir func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) { volume := OpenedVolumes[sessionID] parentDirFd, cDirName, err := volume.prepareAtSyscall(dirName) if err != nil { return nil, nil, 0 } defer syscall.Close(parentDirFd) // Read ciphertext directory fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) if err != nil { return nil, nil, 0 } defer syscall.Close(fd) cipherEntries, err := syscallcompat.Getdents(fd) if err != nil { return nil, nil, 0 } // Get DirIV (stays nil if PlaintextNames is used) var cachedIV []byte if !OpenedVolumes[sessionID].plainTextNames { // Read the DirIV from disk cachedIV, err = volume.nameTransform.ReadDirIVAt(fd) if err != nil { return nil, nil, 0 } } // Decrypted directory entries var plain strings.Builder var modes []uint32 // 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 OpenedVolumes[sessionID].plainTextNames { plain.WriteString(cipherEntries[i].Name + "\x00") modes = append(modes, cipherEntries[i].Mode) continue } if cName == nametransform.DirIVFilename { // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled continue } // Handle long file name isLong := nametransform.NameType(cName) if isLong == nametransform.LongNameContent { cNameLong, err := nametransform.ReadLongNameAt(fd, cName) if err != nil { continue } cName = cNameLong } else if isLong == nametransform.LongNameFilename { // ignore "gocryptfs.longname.*.name" continue } name, err := OpenedVolumes[sessionID].nameTransform.DecryptName(cName, cachedIV) if err != nil { continue } // Override the ciphertext name with the plaintext name but reuse the rest // of the structure cipherEntries[i].Name = name plain.WriteString(cipherEntries[i].Name + "\x00") modes = append(modes, cipherEntries[i].Mode) } p := allocator.Malloc(len(modes)) for i := 0; i < len(modes); i++ { offset := C.sizeof_int * uintptr(i) *(*C.int)(unsafe.Pointer(uintptr(p) + offset)) = (C.int)(modes[i]) } return C.CString(plain.String()), (*C.int)(p), (C.int)(len(modes)) } //export gcf_mkdir func gcf_mkdir(sessionID int, path string, mode uint32) bool { volume := OpenedVolumes[sessionID] dirfd, cName, err := volume.prepareAtSyscall(path) if err != nil { return false } defer syscall.Close(dirfd) if volume.plainTextNames { err = unix.Mkdirat(dirfd, cName, mode) if err != nil { return false } var ust unix.Stat_t err = syscallcompat.Fstatat(dirfd, cName, &ust, unix.AT_SYMLINK_NOFOLLOW) if err != nil { return false } } else { // We need write and execute permissions to create gocryptfs.diriv. // Also, we need read permissions to open the directory (to avoid // race-conditions between getting and setting the mode). origMode := mode mode := mode | 0700 // Handle long file name if nametransform.IsLongContent(cName) { // Create ".name" err = OpenedVolumes[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, path) if err != nil { return false } // Create directory err = mkdirWithIv(dirfd, cName, mode) if err != nil { nametransform.DeleteLongNameAt(dirfd, cName) return false } } else { err = mkdirWithIv(dirfd, cName, mode) if err != nil { return false } } fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) if err != nil { return false } defer syscall.Close(fd) var st syscall.Stat_t err = syscall.Fstat(fd, &st) if err != nil { return false } // Fix permissions if origMode != mode { // Preserve SGID bit if it was set due to inheritance. origMode = uint32(st.Mode&^0777) | origMode syscall.Fchmod(fd, origMode) } } return true } //export gcf_rmdir func gcf_rmdir(sessionID int, relPath string) bool { volume := OpenedVolumes[sessionID] parentDirFd, cName, err := volume.openBackingDir(relPath) if err != nil { return false } defer syscall.Close(parentDirFd) if volume.plainTextNames { // Unlinkat with AT_REMOVEDIR is equivalent to Rmdir err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) return errToBool(err) } dirfd, err := syscallcompat.Openat(parentDirFd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) if err != nil { return false } defer syscall.Close(dirfd) // Check directory contents children, err := syscallcompat.Getdents(dirfd) if err == io.EOF { // The directory is empty err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) return errToBool(err) } if err != nil { return false } // If the directory is not empty besides gocryptfs.diriv, do not even // attempt the dance around gocryptfs.diriv. if len(children) > 1 { return false } // Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ" tmpName := fmt.Sprintf("%s.rmdir.%d", nametransform.DirIVFilename, cryptocore.RandUint64()) err = syscallcompat.Renameat(dirfd, nametransform.DirIVFilename, parentDirFd, tmpName) if err != nil { return false } // Actual Rmdir err = syscallcompat.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) if err != nil { // This can happen if another file in the directory was created in the // meantime, undo the rename syscallcompat.Renameat(parentDirFd, tmpName, dirfd, nametransform.DirIVFilename) return errToBool(err) } // Delete "gocryptfs.diriv.rmdir.XYZ" syscallcompat.Unlinkat(parentDirFd, tmpName, 0) // Delete .name file if nametransform.IsLongContent(cName) { nametransform.DeleteLongNameAt(parentDirFd, cName) } return true }