mirror of
https://salsa.debian.org/mdosch/go-sendxmpp
synced 2024-11-18 21:25:31 +00:00
79897fd295
So it's clear that this is no clear text file and it is not supposed to be edited by the user.
253 lines
7.1 KiB
Go
253 lines
7.1 KiB
Go
// 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])
|
||
}
|