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.
 
 
 

231 lines
7.3 KiB

package server
import (
"crypto/ed25519"
"cwtch.im/cwtch/model"
"encoding/base64"
"errors"
"fmt"
"git.openprivacy.ca/cwtch.im/server/metrics"
"git.openprivacy.ca/cwtch.im/server/storage"
"git.openprivacy.ca/cwtch.im/tapir"
"git.openprivacy.ca/cwtch.im/tapir/applications"
tor2 "git.openprivacy.ca/cwtch.im/tapir/networks/tor"
"git.openprivacy.ca/cwtch.im/tapir/persistence"
"git.openprivacy.ca/cwtch.im/tapir/primitives"
"git.openprivacy.ca/cwtch.im/tapir/primitives/privacypass"
"git.openprivacy.ca/openprivacy/connectivity"
"git.openprivacy.ca/openprivacy/connectivity/tor"
"git.openprivacy.ca/openprivacy/log"
"os"
"path"
"sync"
)
const (
// ServerConfigFile is the standard filename for a server's config to be written to in a directory
ServerConfigFile = "serverConfig.json"
)
// Server encapsulates a complete, compliant Cwtch server.
type Server interface {
Identity() primitives.Identity
Run(acn connectivity.ACN) error
KeyBundle() *model.KeyBundle
CheckStatus() (bool, error)
Stop()
Destroy()
GetStatistics() Statistics
Delete(password string) error
Onion() string
ServerBundle() string
TofuBundle() string
GetAttribute(string) string
SetAttribute(string, string)
}
type server struct {
service tapir.Service
config *Config
metricsPack metrics.Monitors
tokenTapirService tapir.Service
tokenServer *privacypass.TokenServer
tokenService primitives.Identity
tokenServicePrivKey ed25519.PrivateKey
tokenServiceStopped bool
onionServiceStopped bool
running bool
existingMessageCount int
lock sync.RWMutex
}
// NewServer creates and configures a new server based on the supplied configuration
func NewServer(serverConfig *Config) Server {
server := new(server)
server.running = false
server.config = serverConfig
server.tokenService = server.config.TokenServiceIdentity()
server.tokenServicePrivKey = server.config.TokenServerPrivateKey
bs := new(persistence.BoltPersistence)
bs.Open(path.Join(serverConfig.ConfigDir, "tokens.db"))
server.tokenServer = privacypass.NewTokenServerFromStore(&serverConfig.TokenServiceK, bs)
log.Infof("Y: %v", server.tokenServer.Y)
return server
}
// Identity returns the main onion identity of the server
func (s *server) Identity() primitives.Identity {
return s.config.Identity()
}
// Run starts a server with the given privateKey
func (s *server) Run(acn connectivity.ACN) error {
s.lock.Lock()
defer s.lock.Unlock()
if s.running {
return nil
}
identity := primitives.InitializeIdentity("", &s.config.PrivateKey, &s.config.PublicKey)
service := new(tor2.BaseOnionService)
service.Init(acn, s.config.PrivateKey, &identity)
s.service = service
log.Infof("cwtch server running on cwtch:%s\n", s.Onion())
s.metricsPack.Start(service, s.config.ConfigDir, s.config.ServerReporting.LogMetricsToFile)
ms, err := storage.InitializeSqliteMessageStore(path.Join(s.config.ConfigDir, "cwtch.messages"), s.metricsPack.MessageCounter)
if err != nil {
return fmt.Errorf("could not open database: %v", err)
}
// Needed because we only collect metrics on a per-session basis
// TODO fix metrics so they persist across sessions?
s.existingMessageCount = len(ms.FetchMessages())
s.tokenTapirService = new(tor2.BaseOnionService)
s.tokenTapirService.Init(acn, s.tokenServicePrivKey, &s.tokenService)
tokenApplication := new(applications.TokenApplication)
tokenApplication.TokenService = s.tokenServer
powTokenApp := new(applications.ApplicationChain).
ChainApplication(new(applications.ProofOfWorkApplication), applications.SuccessfulProofOfWorkCapability).
ChainApplication(tokenApplication, applications.HasTokensCapability)
go func() {
s.tokenTapirService.Listen(powTokenApp)
s.tokenServiceStopped = true
}()
go func() {
s.service.Listen(NewTokenBoardServer(ms, s.tokenServer))
s.onionServiceStopped = true
}()
s.running = true
return nil
}
// KeyBundle provides the signed keybundle of the server
func (s *server) KeyBundle() *model.KeyBundle {
kb := model.NewKeyBundle()
identity := s.config.Identity()
kb.Keys[model.KeyTypeServerOnion] = model.Key(identity.Hostname())
kb.Keys[model.KeyTypeTokenOnion] = model.Key(s.tokenService.Hostname())
kb.Keys[model.KeyTypePrivacyPass] = model.Key(s.tokenServer.Y.String())
kb.Sign(identity)
return kb
}
// CheckStatus returns true if the server is running and/or an error if any part of the server needs to be restarted.
func (s *server) CheckStatus() (bool, error) {
s.lock.RLock()
defer s.lock.RUnlock()
if s.onionServiceStopped || s.tokenServiceStopped {
return s.running, fmt.Errorf("one of more server components are down: onion:%v token service: %v", s.onionServiceStopped, s.tokenServiceStopped)
}
return s.running, nil
}
// Stop turns off the server so it cannot receive connections and frees most resourses.
// The server is still in a reRunable state and tokenServer still has an active persistance
func (s *server) Stop() {
log.Infof("Shutting down server")
s.lock.Lock()
defer s.lock.Unlock()
if s.running {
s.service.Shutdown()
s.tokenTapirService.Shutdown()
log.Infof("Closing Token server Database...")
s.metricsPack.Stop()
s.running = false
}
}
// Destroy frees the last of the resources the server has active (toklenServer persistance) leaving it un-re-runable and completely shutdown
func (s *server) Destroy() {
s.Stop()
s.lock.Lock()
defer s.lock.Unlock()
s.tokenServer.Close()
}
// Statistics is an encapsulation of information about the server that an operator might want to know at a glance.
type Statistics struct {
TotalMessages int
}
// GetStatistics is a stub method for providing some high level information about
// the server operation to bundling applications (e.g. the UI)
func (s *server) GetStatistics() Statistics {
// TODO Statistics from Metrics is very awkward. Metrics needs an overhaul to make safe
total := s.existingMessageCount
if s.metricsPack.TotalMessageCounter != nil {
total += s.metricsPack.TotalMessageCounter.Count()
}
return Statistics{
TotalMessages: total,
}
}
func (s *server) Delete(password string) error {
s.lock.Lock()
if s.config.Encrypted && !s.config.CheckPassword(password) {
s.lock.Unlock()
log.Errorf("encryped and checkpassword failed")
return errors.New("cannot delete server, passwords do not match")
}
s.lock.Unlock()
s.Destroy()
os.RemoveAll(s.config.ConfigDir)
return nil
}
func (s *server) Onion() string {
return s.config.Onion()
}
// ServerBundle returns a bundle of the server keys required to access it (torv3 keys are addresses)
func (s *server) ServerBundle() string {
bundle := s.KeyBundle().Serialize()
return fmt.Sprintf("server:%s", base64.StdEncoding.EncodeToString(bundle))
}
// TofuBundle returns a Server Bundle + a newly created group invite
func (s *server) TofuBundle() string {
group, _ := model.NewGroup(tor.GetTorV3Hostname(s.config.PublicKey))
invite, err := group.Invite()
if err != nil {
panic(err)
}
bundle := s.KeyBundle().Serialize()
return fmt.Sprintf("tofubundle:server:%s||%s", base64.StdEncoding.EncodeToString(bundle), invite)
}
// GetAttribute gets a server attribute
func (s *server) GetAttribute(key string) string {
return s.config.GetAttribute(key)
}
// SetAttribute sets a server attribute
func (s *server) SetAttribute(key, val string) {
s.config.SetAttribute(key, val)
}