go-sendxmpp/helpers.go
Martin Dosch 79897fd295
Use fast.bin for fast cache file
So it's clear that this is no clear text file and it is not supposed to
be edited by the user.
2024-04-10 15:50:58 +02:00

253 lines
7.1 KiB
Go
Raw Blame History

// Copyright Martin Dosch.
// Use of this source code is governed by the BSD-2-clause
// license that can be found in the LICENSE file.
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/gob"
"errors"
"fmt"
"log"
"math/big"
"net/url"
"os"
"regexp"
"runtime"
"strings"
"github.com/google/uuid" // BSD-3-Clause
"github.com/xmppo/go-xmpp" // BSD-3-Clause
"golang.org/x/crypto/scrypt" // BSD-3-Clause
)
func validUTF8(s string) string {
// Remove invalid code points.
s = strings.ToValidUTF8(s, "<22>")
reg := regexp.MustCompile(`[\x{0000}-\x{0008}\x{000B}\x{000C}\x{000E}-\x{001F}]`)
s = reg.ReplaceAllString(s, "<22>")
return s
}
func validURI(s string) (*url.URL, error) {
// Check if URI is valid
uri, err := url.ParseRequestURI(s)
return uri, fmt.Errorf("validURI: %w", err)
}
func readFile(path string) (*bytes.Buffer, error) {
file, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("readFile: %w", err)
}
buffer := new(bytes.Buffer)
_, err = buffer.ReadFrom(file)
if err != nil {
return nil, fmt.Errorf("readFile: %w", err)
}
err = file.Close()
if err != nil {
fmt.Println("error while closing file:", err)
}
return buffer, nil
}
func getFastData(jid string, password string) (xmpp.Fast, error) {
folder := strings.Replace(strings.Replace(jid, "@", "_at_", -1), ".", "_", -1)
var fast xmpp.Fast
fastPath, err := getDataPath(folder)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache folder: %w", err)
}
fastFileLoc := fastPath + "fast.bin"
buf, err := readFile(fastFileLoc)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
decBuf := bytes.NewBuffer(buf.Bytes())
decoder := gob.NewDecoder(decBuf)
err = decoder.Decode(&fast)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
salt := make([]byte, 32)
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to create aes key: %w", err)
}
c, err := aes.NewCipher([]byte(key))
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
nonceSize := gcm.NonceSize()
cryptBuf := []byte(fast.Token)
nonce, cryptBuf := cryptBuf[:nonceSize], cryptBuf[nonceSize:]
tokenBuf, err := gcm.Open(nil, []byte(nonce), cryptBuf, nil)
if err != nil {
return xmpp.Fast{}, fmt.Errorf("getFastData: failed to read fast cache file: %w", err)
}
fast.Token = string(tokenBuf)
return fast, nil
}
func writeFastData(jid string, password string, fast xmpp.Fast) error {
var encBuf bytes.Buffer
folder := strings.Replace(strings.Replace(jid, "@", "_at_", -1), ".", "_", -1)
fastPath, err := getDataPath(folder)
if err != nil {
return fmt.Errorf("writeFastData: failed to write fast cache file: %w", err)
}
fastFileLoc := fastPath + "fast.bin"
salt := make([]byte, 32)
key, err := scrypt.Key([]byte(password), salt, 32768, 8, 1, 32)
if err != nil {
return fmt.Errorf("writeFastData: failed to create aes cipher: %w", err)
}
c, err := aes.NewCipher(key)
if err != nil {
return fmt.Errorf("writeFastData: failed to create aes cipher: %w", err)
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return fmt.Errorf("writeFastData: failed to create aes cipher: %w", err)
}
nonce := make([]byte, gcm.NonceSize())
_, err = rand.Read(nonce)
if err != nil {
return fmt.Errorf("writeFastData: failed to create aes cipher: %w", err)
}
buf := gcm.Seal(nonce, nonce, []byte(fast.Token), nil)
fast.Token = string(buf)
encode := gob.NewEncoder(&encBuf)
err = encode.Encode(fast)
if err != nil {
return fmt.Errorf("writeFastData: failed to create fast token file: %w", err)
}
file, err := os.Create(fastFileLoc)
if err != nil {
return fmt.Errorf("writeFastData: failed to create fast token file: %w", err)
}
defer file.Close()
if runtime.GOOS != "windows" {
_ = file.Chmod(os.FileMode(defaultFileRights))
} else {
_ = file.Chmod(os.FileMode(defaultFileRightsWin))
}
_, err = file.Write(encBuf.Bytes())
if err != nil {
return fmt.Errorf("writeFastData: failed to write fast token file: %w", err)
}
return nil
}
func getClientID(jid string) (string, error) {
var clientID string
folder := strings.Replace(strings.Replace(jid, "@", "_at_", -1), ".", "_", -1)
clientIDLoc, err := getClientIDLoc(folder)
if err != nil {
return strError, err
}
buf, err := readFile(clientIDLoc)
if err != nil {
clientID = uuid.NewString()
file, err := os.Create(clientIDLoc)
if err != nil {
return strEmpty, fmt.Errorf("getClientID: failed to create clientid file: %w", err)
}
defer file.Close()
if runtime.GOOS != "windows" {
_ = file.Chmod(os.FileMode(defaultFileRights))
} else {
_ = file.Chmod(os.FileMode(defaultFileRightsWin))
}
_, err = file.Write([]byte(clientID))
if err != nil {
return strEmpty, fmt.Errorf("getClientID: failed to write client id file: %w", err)
}
} else {
clientID = buf.String()
}
return clientID, nil
}
func getDataPath(folder string) (string, error) {
var err error
var homeDir, dataDir string
switch {
case os.Getenv("$XDG_DATA_HOME") != "":
dataDir = os.Getenv("$XDG_DATA_HOME")
case os.Getenv("$XDG_HOME") != "":
homeDir = os.Getenv("$XDG_HOME")
dataDir = homeDir + "/.local/share"
case os.Getenv("$HOME") != "":
homeDir = os.Getenv("$HOME")
dataDir = homeDir + "/.local/share"
default:
homeDir, err = os.UserHomeDir()
if err != nil {
return strError, fmt.Errorf("getDataPath: failed to determine user dir: %w", err)
}
if homeDir == "" {
return strError, errors.New("getDataPath: received empty string for home directory")
}
dataDir = homeDir + "/.local/share"
}
if folder != "" && !strings.HasSuffix(folder, "/") {
folder = fmt.Sprintf("%s/", folder)
}
dataDir = fmt.Sprintf("%s/go-sendxmpp/%s", dataDir, folder)
if _, err = os.Stat(dataDir); os.IsNotExist(err) {
err = os.MkdirAll(dataDir, defaultDirRights)
if err != nil {
return strError, fmt.Errorf("getDataPath: could not create folder: %w", err)
}
}
return dataDir, nil
}
func getClientIDLoc(folder string) (string, error) {
dataDir, err := getDataPath(folder)
if err != nil {
return strError, fmt.Errorf("getClientIDLoc: %w", err)
}
dataFile := dataDir + "clientid"
return dataFile, nil
}
func getRpad(messageLength int) string {
rpadRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
length := defaultRpadMultiple - messageLength%defaultRpadMultiple
max := big.NewInt(int64(len(rpadRunes)))
rpad := make([]rune, length)
for i := range rpad {
randInt, err := rand.Int(rand.Reader, max)
if err != nil {
log.Fatal(err)
}
rpad[i] = rpadRunes[randInt.Int64()]
}
return string(rpad)
}
func getID() string {
return uuid.NewString()
}
func getShortID() string {
id := make([]byte, defaultShortIDBytes)
_, err := rand.Read(id)
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("%x", id[0:4])
}