You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
246 lines
7.8 KiB
246 lines
7.8 KiB
package server
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"git.openprivacy.ca/cwtch.im/server/storage"
|
|
"git.openprivacy.ca/cwtch.im/tapir/primitives"
|
|
"git.openprivacy.ca/openprivacy/connectivity/tor"
|
|
"git.openprivacy.ca/openprivacy/log"
|
|
"github.com/gtank/ristretto255"
|
|
"golang.org/x/crypto/ed25519"
|
|
"io/ioutil"
|
|
"os"
|
|
"path"
|
|
"sync"
|
|
)
|
|
|
|
const (
|
|
// AttrAutostart is the attribute key for autostart setting
|
|
AttrAutostart = "autostart"
|
|
|
|
// AttrDescription is the attribute key for a user set server description
|
|
AttrDescription = "description"
|
|
|
|
// AttrStorageType is used by clients that may need info about stored server config types/styles
|
|
AttrStorageType = "storageType"
|
|
)
|
|
|
|
const (
|
|
// StorageTypeDefaultPassword is a AttrStorageType that indicated a app default password was used
|
|
StorageTypeDefaultPassword = "storage-default-password"
|
|
|
|
// StorageTypePassword is a AttrStorageType that indicated a user password was used to protect the profile
|
|
StorageTypePassword = "storage-password"
|
|
|
|
// StoreageTypeNoPassword is a AttrStorageType that indicated a no password was used to protect the profile
|
|
StoreageTypeNoPassword = "storage-no-password"
|
|
)
|
|
|
|
// Reporting is a struct for storing a the config a server needs to be a peer, and connect to a group to report
|
|
type Reporting struct {
|
|
LogMetricsToFile bool `json:"logMetricsToFile"`
|
|
}
|
|
|
|
// messages are ~4kb of storage
|
|
const MessagesPerMB = 250
|
|
|
|
// Config is a struct for storing basic server configuration
|
|
type Config struct {
|
|
ConfigDir string `json:"-"`
|
|
FilePath string `json:"-"`
|
|
Encrypted bool `json:"-"`
|
|
key [32]byte
|
|
MaxBufferLines int `json:"maxBufferLines"`
|
|
|
|
PublicKey ed25519.PublicKey `json:"publicKey"`
|
|
PrivateKey ed25519.PrivateKey `json:"privateKey"`
|
|
|
|
TokenServerPublicKey ed25519.PublicKey `json:"tokenServerPublicKey"`
|
|
TokenServerPrivateKey ed25519.PrivateKey `json:"tokenServerPrivateKey"`
|
|
|
|
TokenServiceK ristretto255.Scalar `json:"tokenServiceK"`
|
|
|
|
ServerReporting Reporting `json:"serverReporting"`
|
|
|
|
Attributes map[string]string `json:"attributes"`
|
|
|
|
// messages are ~4kb of storage
|
|
// -1 == infinite
|
|
MaxStorageMBs int `json:"maxStorageMBs"`
|
|
|
|
lock sync.Mutex
|
|
encFileStore storage.FileStore
|
|
}
|
|
|
|
// Identity returns an encapsulation of the servers keys
|
|
func (config *Config) Identity() primitives.Identity {
|
|
return primitives.InitializeIdentity("", &config.PrivateKey, &config.PublicKey)
|
|
}
|
|
|
|
// TokenServiceIdentity returns an encapsulation of the servers token server (experimental)
|
|
func (config *Config) TokenServiceIdentity() primitives.Identity {
|
|
return primitives.InitializeIdentity("", &config.TokenServerPrivateKey, &config.TokenServerPublicKey)
|
|
}
|
|
|
|
func initDefaultConfig(configDir, filename string, encrypted bool) *Config {
|
|
config := &Config{Encrypted: encrypted, ConfigDir: configDir, FilePath: filename, Attributes: make(map[string]string)}
|
|
|
|
id, pk := primitives.InitializeEphemeralIdentity()
|
|
tid, tpk := primitives.InitializeEphemeralIdentity()
|
|
config.PrivateKey = pk
|
|
config.PublicKey = id.PublicKey()
|
|
config.TokenServerPrivateKey = tpk
|
|
config.TokenServerPublicKey = tid.PublicKey()
|
|
config.MaxBufferLines = 100000
|
|
config.ServerReporting = Reporting{
|
|
LogMetricsToFile: false,
|
|
}
|
|
config.Attributes[AttrAutostart] = "false"
|
|
config.MaxStorageMBs = -1
|
|
|
|
k := new(ristretto255.Scalar)
|
|
b := make([]byte, 64)
|
|
_, err := rand.Read(b)
|
|
if err != nil {
|
|
// unable to generate secure random numbers
|
|
panic("unable to generate secure random numbers")
|
|
}
|
|
k.SetUniformBytes(b)
|
|
config.TokenServiceK = *k
|
|
return config
|
|
}
|
|
|
|
// LoadCreateDefaultConfigFile loads a Config from or creates a default config and saves it to a json file specified by filename
|
|
// if the encrypted flag is true the config is store encrypted by password
|
|
func LoadCreateDefaultConfigFile(configDir, filename string, encrypted bool, password string, defaultLogToFile bool) (*Config, error) {
|
|
if _, err := os.Stat(path.Join(configDir, filename)); os.IsNotExist(err) {
|
|
return CreateConfig(configDir, filename, encrypted, password, defaultLogToFile)
|
|
}
|
|
return LoadConfig(configDir, filename, encrypted, password)
|
|
}
|
|
|
|
// CreateConfig creates a default config and saves it to a json file specified by filename
|
|
// if the encrypted flag is true the config is store encrypted by password
|
|
func CreateConfig(configDir, filename string, encrypted bool, password string, defaultLogToFile bool) (*Config, error) {
|
|
log.Debugf("CreateConfig for server with configDir: %s\n", configDir)
|
|
os.MkdirAll(configDir, 0700)
|
|
config := initDefaultConfig(configDir, filename, encrypted)
|
|
config.ServerReporting.LogMetricsToFile = defaultLogToFile
|
|
if encrypted {
|
|
key, _, err := storage.InitV1Directory(configDir, password)
|
|
if err != nil {
|
|
log.Errorf("could not create server directory: %s", err)
|
|
return nil, err
|
|
}
|
|
config.key = key
|
|
config.encFileStore = storage.NewFileStore(configDir, ServerConfigFile, key)
|
|
}
|
|
|
|
config.Save()
|
|
return config, nil
|
|
}
|
|
|
|
// LoadConfig loads a Config from a json file specified by filename
|
|
func LoadConfig(configDir, filename string, encrypted bool, password string) (*Config, error) {
|
|
config := initDefaultConfig(configDir, filename, encrypted)
|
|
var raw []byte
|
|
var err error
|
|
if encrypted {
|
|
salt, err := ioutil.ReadFile(path.Join(configDir, storage.SaltFile))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
config.key = storage.CreateKey(password, salt)
|
|
config.encFileStore = storage.NewFileStore(configDir, ServerConfigFile, config.key)
|
|
raw, err = config.encFileStore.Read()
|
|
if err != nil {
|
|
// Not an error to log as load config is called blindly across all dirs with a password to see what it applies to
|
|
log.Debugf("read enc bytes failed: %s\n", err)
|
|
return nil, err
|
|
}
|
|
} else {
|
|
raw, err = ioutil.ReadFile(path.Join(configDir, filename))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
if err = json.Unmarshal(raw, &config); err != nil {
|
|
log.Errorf("reading config: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
// Always save (first time generation, new version with new variables populated)
|
|
config.Save()
|
|
return config, nil
|
|
}
|
|
|
|
// Save dumps the latest version of the config to a json file given by filename
|
|
func (config *Config) Save() error {
|
|
config.lock.Lock()
|
|
defer config.lock.Unlock()
|
|
bytes, _ := json.MarshalIndent(config, "", "\t")
|
|
if config.Encrypted {
|
|
return config.encFileStore.Write(bytes)
|
|
}
|
|
return ioutil.WriteFile(path.Join(config.ConfigDir, config.FilePath), bytes, 0600)
|
|
}
|
|
|
|
// CheckPassword returns true if the given password produces the same key as the current stored key, otherwise false.
|
|
func (config *Config) CheckPassword(checkpass string) bool {
|
|
config.lock.Lock()
|
|
defer config.lock.Unlock()
|
|
salt, err := ioutil.ReadFile(path.Join(config.ConfigDir, storage.SaltFile))
|
|
if err != nil {
|
|
return false
|
|
}
|
|
oldkey := storage.CreateKey(checkpass, salt[:])
|
|
return oldkey == config.key
|
|
}
|
|
|
|
// Onion returns the .onion url for the server
|
|
func (config *Config) Onion() string {
|
|
config.lock.Lock()
|
|
defer config.lock.Unlock()
|
|
return tor.GetTorV3Hostname(config.PublicKey) + ".onion"
|
|
}
|
|
|
|
// SetAttribute sets a server attribute
|
|
func (config *Config) SetAttribute(key, val string) {
|
|
config.lock.Lock()
|
|
config.Attributes[key] = val
|
|
config.lock.Unlock()
|
|
config.Save()
|
|
}
|
|
|
|
// GetAttribute gets a server attribute
|
|
func (config *Config) GetAttribute(key string) string {
|
|
config.lock.Lock()
|
|
defer config.lock.Unlock()
|
|
return config.Attributes[key]
|
|
}
|
|
|
|
// GetMaxMessages returns the config setting for Max messages converting from MaxMB to messages
|
|
// or -1 for infinite
|
|
func (config *Config) GetMaxMessages() int {
|
|
config.lock.Lock()
|
|
defer config.lock.Unlock()
|
|
if config.MaxStorageMBs == -1 {
|
|
return -1
|
|
}
|
|
return config.MaxStorageMBs * MessagesPerMB
|
|
}
|
|
|
|
func (config *Config) GetMaxMessageMBs() int {
|
|
config.lock.Lock()
|
|
defer config.lock.Unlock()
|
|
return config.MaxStorageMBs
|
|
}
|
|
|
|
func (config *Config) SetMaxMessageMBs(newval int) {
|
|
config.lock.Lock()
|
|
defer config.lock.Unlock()
|
|
config.MaxStorageMBs = newval
|
|
}
|