aboutsummaryrefslogtreecommitdiff
path: root/internal/nametransform/longnames.go
blob: 0746cd6a67a7366c388933929aa9c44bf2bfa575 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package nametransform

import (
	"crypto/sha256"
	"encoding/base64"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"syscall"

	"github.com/rfjakob/gocryptfs/internal/toggledlog"
)

const (
	// 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]"
func HashLongName(name string) string {
	hashBin := sha256.Sum256([]byte(name))
	hashBase64 := base64.URLEncoding.EncodeToString(hashBin[:])
	return longNamePrefix + hashBase64
}

// Values returned by IsLongName
const (
	LongNameContent  = iota
	LongNameFilename = iota
	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)
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.
func IsLongContent(cName string) bool {
	return NameType(cName) == LongNameContent
}

// ReadLongName - read path.name
func ReadLongName(path string) (string, error) {
	content, err := ioutil.ReadFile(path + LongNameSuffix)
	if err != nil {
		toggledlog.Warn.Printf("ReadLongName: %v", err)
	}
	return string(content), err
}

// DeleteLongName deletes "hashName.name".
func DeleteLongName(dirfd *os.File, hashName string) error {
	err := syscall.Unlinkat(int(dirfd.Fd()), hashName+LongNameSuffix)
	if err != nil {
		toggledlog.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
// converted internally.
func (n *NameTransform) WriteLongName(dirfd *os.File, hashName string, plainName string) (err error) {
	plainName = filepath.Base(plainName)

	// Encrypt the basename
	dirIV, err := ReadDirIVAt(dirfd)
	if err != nil {
		return err
	}
	cName := n.EncryptName(plainName, dirIV)

	// Write the encrypted name into hashName.name
	fdRaw, err := syscall.Openat(int(dirfd.Fd()), hashName+LongNameSuffix,
		syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, 0600)
	if err != nil {
		toggledlog.Warn.Printf("WriteLongName: Openat: %v", err)
		return err
	}
	fd := os.NewFile(uintptr(fdRaw), hashName+LongNameSuffix)
	defer fd.Close()
	_, err = fd.Write([]byte(cName))
	if err != nil {
		toggledlog.Warn.Printf("WriteLongName: Write: %v", err)
	}
	return err
}