package setup import ( "context" "crypto/tls" "crypto/x509" "fmt" "os" "sync" "github.com/dariuszjaniak/warp/server/internal/federationapi" "github.com/dariuszjaniak/warp/server/internal/keyserver" "github.com/dariuszjaniak/warp/server/internal/matrix/event" "github.com/dariuszjaniak/warp/server/internal/roomserver" "github.com/dariuszjaniak/warp/server/internal/storage" "github.com/dariuszjaniak/warp/server/internal/userapi" ) // fanout is the roomserver's emit stage when more than one thing listens: // the sync notifier, which wakes long polls, and the federation queue, // which sends events to other servers. // // Each listener is held to the roomserver's rule for a Notifier — never // block — so calling them in turn under the room lock is fine. type fanout struct { mu sync.RWMutex listeners []roomserver.Notifier } func (f *fanout) add(n roomserver.Notifier) { f.mu.Lock() defer f.mu.Unlock() f.listeners = append(f.listeners, n) } // EventPersisted implements roomserver.Notifier. func (f *fanout) EventPersisted(ctx context.Context, n roomserver.Notification) { f.mu.RLock() defer f.mu.RUnlock() for _, l := range f.listeners { l.EventPersisted(ctx, n) } } // keyMemberships is the keyserver's listener on the emit stage: a // membership ending is when a remote user may stop sharing a room with // anybody here, and the keyserver drops its cached copy of their keys once // they have. MembershipLeft does not block, as the emit stage requires. type keyMemberships struct{ keys *keyserver.API } // EventPersisted implements roomserver.Notifier. func (l keyMemberships) EventPersisted(_ context.Context, n roomserver.Notification) { if n.Event == nil || n.Event.Type() != event.TypeMember || !n.StateChanged { return } userID, ok := n.Event.StateKey() if !ok { return } switch membership, _ := n.Event.Membership(); membership { case event.MembershipLeave, event.MembershipBan: l.keys.MembershipLeft(userID) } } // deviceObservers fans the userapi's device changes out to every // component that keeps state per device: the keyserver's keys and the push // server's pushers. type deviceObservers []userapi.DeviceObserver func (o deviceObservers) DevicesDeleted(ctx context.Context, localpart string, deviceIDs []string) { for _, observer := range o { observer.DevicesDeleted(ctx, localpart, deviceIDs) } } func (o deviceObservers) DeviceRenamed(ctx context.Context, localpart, deviceID string) { for _, observer := range o { observer.DeviceRenamed(ctx, localpart, deviceID) } } // federationStatus adapts the module to the admin API. type federationStatus struct { module *federationapi.Module } func (s federationStatus) Running() bool { return s.module.Running() } func (s federationStatus) Destinations(ctx context.Context) ([]storage.DestinationSummary, error) { return s.module.Queue().Destinations(ctx) } func (s federationStatus) ResetBackoff(ctx context.Context, serverName string) error { return s.module.Queue().ResetBackoff(ctx, serverName) } // federationTLS builds the client TLS configuration for outbound // federation: the system roots, plus a configured CA bundle. func federationTLS(caFile string) (*tls.Config, error) { cfg := &tls.Config{MinVersion: tls.VersionTLS12} if caFile == "" { return cfg, nil } pool, err := x509.SystemCertPool() if err != nil || pool == nil { pool = x509.NewCertPool() } pem, err := os.ReadFile(caFile) if err != nil { return nil, fmt.Errorf("read federation.tls_ca_file: %w", err) } if !pool.AppendCertsFromPEM(pem) { return nil, fmt.Errorf("federation.tls_ca_file %s contains no PEM certificates", caFile) } cfg.RootCAs = pool return cfg, nil }