2017-06-16 15:52:03 +00:00
|
|
|
// Copyright 2017 Vector Creations Ltd
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package config
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/pem"
|
|
|
|
"fmt"
|
2017-09-28 16:00:23 +00:00
|
|
|
"io"
|
2017-06-16 15:52:03 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"path/filepath"
|
2018-02-08 11:02:48 +00:00
|
|
|
"regexp"
|
2017-06-16 15:52:03 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
2017-07-18 12:40:03 +00:00
|
|
|
|
2017-11-29 09:43:03 +00:00
|
|
|
"github.com/matrix-org/dendrite/clientapi/auth/authtypes"
|
2017-07-18 12:40:03 +00:00
|
|
|
"github.com/matrix-org/gomatrixserverlib"
|
2017-11-16 10:12:02 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
2017-07-18 12:40:03 +00:00
|
|
|
"golang.org/x/crypto/ed25519"
|
2018-05-30 12:43:13 +00:00
|
|
|
yaml "gopkg.in/yaml.v2"
|
2017-09-28 16:00:23 +00:00
|
|
|
|
|
|
|
jaegerconfig "github.com/uber/jaeger-client-go/config"
|
|
|
|
jaegermetrics "github.com/uber/jaeger-lib/metrics"
|
2017-06-16 15:52:03 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Version is the current version of the config format.
|
|
|
|
// This will change whenever we make breaking changes to the config format.
|
2017-07-05 12:10:28 +00:00
|
|
|
const Version = 0
|
2017-06-16 15:52:03 +00:00
|
|
|
|
|
|
|
// Dendrite contains all the config used by a dendrite process.
|
|
|
|
// Relative paths are resolved relative to the current working directory
|
|
|
|
type Dendrite struct {
|
|
|
|
// The version of the configuration file.
|
|
|
|
// If the version in a file doesn't match the current dendrite config
|
|
|
|
// version then we can give a clear error message telling the user
|
|
|
|
// to update their config file to the current version.
|
|
|
|
// The version of the file should only be different if there has
|
|
|
|
// been a breaking change to the config file format.
|
2017-07-05 12:10:28 +00:00
|
|
|
Version int `yaml:"version"`
|
2017-06-16 15:52:03 +00:00
|
|
|
|
|
|
|
// The configuration required for a matrix server.
|
|
|
|
Matrix struct {
|
|
|
|
// The name of the server. This is usually the domain name, e.g 'matrix.org', 'localhost'.
|
|
|
|
ServerName gomatrixserverlib.ServerName `yaml:"server_name"`
|
|
|
|
// Path to the private key which will be used to sign requests and events.
|
|
|
|
PrivateKeyPath Path `yaml:"private_key"`
|
|
|
|
// The private key which will be used to sign requests and events.
|
|
|
|
PrivateKey ed25519.PrivateKey `yaml:"-"`
|
|
|
|
// An arbitrary string used to uniquely identify the PrivateKey. Must start with the
|
|
|
|
// prefix "ed25519:".
|
|
|
|
KeyID gomatrixserverlib.KeyID `yaml:"-"`
|
|
|
|
// List of paths to X509 certificates used by the external federation listeners.
|
|
|
|
// These are used to calculate the TLS fingerprints to publish for this server.
|
|
|
|
// Other matrix servers talking to this server will expect the x509 certificate
|
|
|
|
// to match one of these certificates.
|
|
|
|
// The certificates should be in PEM format.
|
|
|
|
FederationCertificatePaths []Path `yaml:"federation_certificates"`
|
|
|
|
// A list of SHA256 TLS fingerprints for the X509 certificates used by the
|
|
|
|
// federation listener for this server.
|
|
|
|
TLSFingerPrints []gomatrixserverlib.TLSFingerprint `yaml:"-"`
|
|
|
|
// How long a remote server can cache our server key for before requesting it again.
|
|
|
|
// Increasing this number will reduce the number of requests made by remote servers
|
|
|
|
// for our key, but increases the period a compromised key will be considered valid
|
|
|
|
// by remote servers.
|
|
|
|
// Defaults to 24 hours.
|
|
|
|
KeyValidityPeriod time.Duration `yaml:"key_validity_period"`
|
2017-09-11 18:18:19 +00:00
|
|
|
// List of domains that the server will trust as identity servers to
|
|
|
|
// verify third-party identifiers.
|
|
|
|
// Defaults to an empty array.
|
|
|
|
TrustedIDServers []string `yaml:"trusted_third_party_id_servers"`
|
2017-09-22 15:13:19 +00:00
|
|
|
// If set, allows registration by anyone who also has the shared
|
|
|
|
// secret, even if registration is otherwise disabled.
|
|
|
|
RegistrationSharedSecret string `yaml:"registration_shared_secret"`
|
2017-12-05 16:16:14 +00:00
|
|
|
// This Home Server's ReCAPTCHA public key.
|
|
|
|
RecaptchaPublicKey string `yaml:"recaptcha_public_key"`
|
|
|
|
// This Home Server's ReCAPTCHA private key.
|
|
|
|
RecaptchaPrivateKey string `yaml:"recaptcha_private_key"`
|
|
|
|
// Boolean stating whether catpcha registration is enabled
|
|
|
|
// and required
|
|
|
|
RecaptchaEnabled bool `yaml:"enable_registration_captcha"`
|
|
|
|
// Secret used to bypass the captcha registration entirely
|
|
|
|
RecaptchaBypassSecret string `yaml:"captcha_bypass_secret"`
|
|
|
|
// HTTP API endpoint used to verify whether the captcha response
|
|
|
|
// was successful
|
|
|
|
RecaptchaSiteVerifyAPI string `yaml:"recaptcha_siteverify_api"`
|
2017-12-04 17:07:45 +00:00
|
|
|
// If set disables new users from registering (except via shared
|
|
|
|
// secrets)
|
|
|
|
RegistrationDisabled bool `yaml:"registration_disabled"`
|
2020-04-20 16:42:34 +00:00
|
|
|
// Perspective keyservers, to use as a backup when direct key fetch
|
|
|
|
// requests don't succeed
|
|
|
|
KeyPerspectives KeyPerspectives `yaml:"key_perspectives"`
|
2017-06-16 15:52:03 +00:00
|
|
|
} `yaml:"matrix"`
|
|
|
|
|
|
|
|
// The configuration specific to the media repostitory.
|
|
|
|
Media struct {
|
|
|
|
// The base path to where the media files will be stored. May be relative or absolute.
|
|
|
|
BasePath Path `yaml:"base_path"`
|
|
|
|
// The absolute base path to where media files will be stored.
|
|
|
|
AbsBasePath Path `yaml:"-"`
|
|
|
|
// The maximum file size in bytes that is allowed to be stored on this server.
|
|
|
|
// Note: if max_file_size_bytes is set to 0, the size is unlimited.
|
|
|
|
// Note: if max_file_size_bytes is not set, it will default to 10485760 (10MB)
|
|
|
|
MaxFileSizeBytes *FileSizeBytes `yaml:"max_file_size_bytes,omitempty"`
|
|
|
|
// Whether to dynamically generate thumbnails on-the-fly if the requested resolution is not already generated
|
|
|
|
DynamicThumbnails bool `yaml:"dynamic_thumbnails"`
|
|
|
|
// The maximum number of simultaneous thumbnail generators. default: 10
|
|
|
|
MaxThumbnailGenerators int `yaml:"max_thumbnail_generators"`
|
|
|
|
// A list of thumbnail sizes to be pre-generated for downloaded remote / uploaded content
|
|
|
|
ThumbnailSizes []ThumbnailSize `yaml:"thumbnail_sizes"`
|
|
|
|
} `yaml:"media"`
|
|
|
|
|
2020-04-14 14:54:35 +00:00
|
|
|
// The configuration to use for Prometheus metrics
|
|
|
|
Metrics struct {
|
|
|
|
// Whether or not the metrics are enabled
|
|
|
|
Enabled bool `yaml:"enabled"`
|
|
|
|
// Use BasicAuth for Authorization
|
|
|
|
BasicAuth struct {
|
|
|
|
// Authorization via Static Username & Password
|
|
|
|
// Hardcoded Username and Password
|
|
|
|
Username string `yaml:"username"`
|
|
|
|
Password string `yaml:"password"`
|
|
|
|
} `yaml:"basic_auth"`
|
|
|
|
} `yaml:"metrics"`
|
|
|
|
|
2017-06-16 15:52:03 +00:00
|
|
|
// The configuration for talking to kafka.
|
|
|
|
Kafka struct {
|
|
|
|
// A list of kafka addresses to connect to.
|
|
|
|
Addresses []string `yaml:"addresses"`
|
2017-08-16 12:36:41 +00:00
|
|
|
// Whether to use naffka instead of kafka.
|
|
|
|
// Naffka can only be used when running dendrite as a single monolithic server.
|
|
|
|
// Kafka can be used both with a monolithic server and when running the
|
|
|
|
// components as separate servers.
|
|
|
|
UseNaffka bool `yaml:"use_naffka,omitempty"`
|
2017-06-16 15:52:03 +00:00
|
|
|
// The names of the topics to use when reading and writing from kafka.
|
|
|
|
Topics struct {
|
|
|
|
// Topic for roomserver/api.OutputRoomEvent events.
|
|
|
|
OutputRoomEvent Topic `yaml:"output_room_event"`
|
2017-08-02 15:21:35 +00:00
|
|
|
// Topic for sending account data from client API to sync API
|
|
|
|
OutputClientData Topic `yaml:"output_client_data"`
|
2020-03-30 14:02:20 +00:00
|
|
|
// Topic for eduserver/api.OutputTypingEvent events.
|
2018-08-02 17:22:44 +00:00
|
|
|
OutputTypingEvent Topic `yaml:"output_typing_event"`
|
2017-07-11 13:14:06 +00:00
|
|
|
// Topic for user updates (profile, presence)
|
|
|
|
UserUpdates Topic `yaml:"user_updates"`
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
|
|
|
} `yaml:"kafka"`
|
|
|
|
|
|
|
|
// Postgres Config
|
|
|
|
Database struct {
|
|
|
|
// The Account database stores the login details and account information
|
|
|
|
// for local users. It is accessed by the ClientAPI.
|
|
|
|
Account DataSource `yaml:"account"`
|
|
|
|
// The Device database stores session information for the devices of logged
|
|
|
|
// in local users. It is accessed by the ClientAPI, the MediaAPI and the SyncAPI.
|
|
|
|
Device DataSource `yaml:"device"`
|
|
|
|
// The MediaAPI database stores information about files uploaded and downloaded
|
|
|
|
// by local users. It is only accessed by the MediaAPI.
|
|
|
|
MediaAPI DataSource `yaml:"media_api"`
|
|
|
|
// The ServerKey database caches the public keys of remote servers.
|
|
|
|
// It may be accessed by the FederationAPI, the ClientAPI, and the MediaAPI.
|
|
|
|
ServerKey DataSource `yaml:"server_key"`
|
|
|
|
// The SyncAPI stores information used by the SyncAPI server.
|
|
|
|
// It is only accessed by the SyncAPI server.
|
|
|
|
SyncAPI DataSource `yaml:"sync_api"`
|
|
|
|
// The RoomServer database stores information about matrix rooms.
|
|
|
|
// It is only accessed by the RoomServer.
|
|
|
|
RoomServer DataSource `yaml:"room_server"`
|
2017-06-28 15:10:17 +00:00
|
|
|
// The FederationSender database stores information used by the FederationSender
|
|
|
|
// It is only accessed by the FederationSender.
|
|
|
|
FederationSender DataSource `yaml:"federation_sender"`
|
2018-07-05 16:34:59 +00:00
|
|
|
// The AppServices database stores information used by the AppService component.
|
|
|
|
// It is only accessed by the AppService component.
|
|
|
|
AppService DataSource `yaml:"appservice"`
|
2017-08-22 10:12:51 +00:00
|
|
|
// The PublicRoomsAPI database stores information used to compute the public
|
|
|
|
// room directory. It is only accessed by the PublicRoomsAPI server.
|
|
|
|
PublicRoomsAPI DataSource `yaml:"public_rooms_api"`
|
2017-11-16 17:35:28 +00:00
|
|
|
// The Naffka database is used internally by the naffka library, if used.
|
|
|
|
Naffka DataSource `yaml:"naffka,omitempty"`
|
2020-05-01 12:34:53 +00:00
|
|
|
// Maximum open connections to the DB (0 = use default, negative means unlimited)
|
|
|
|
MaxOpenConns int `yaml:"max_open_conns"`
|
|
|
|
// Maximum idle connections to the DB (0 = use default, negative means unlimited)
|
|
|
|
MaxIdleConns int `yaml:"max_idle_conns"`
|
|
|
|
// maximum amount of time (in seconds) a connection may be reused (<= 0 means unlimited)
|
|
|
|
ConnMaxLifetimeSec int `yaml:"conn_max_lifetime"`
|
2017-06-16 15:52:03 +00:00
|
|
|
} `yaml:"database"`
|
|
|
|
|
2017-11-09 09:58:45 +00:00
|
|
|
// TURN Server Config
|
|
|
|
TURN struct {
|
|
|
|
// TODO Guest Support
|
|
|
|
// Whether or not guests can request TURN credentials
|
|
|
|
//AllowGuests bool `yaml:"turn_allow_guests"`
|
|
|
|
// How long the authorization should last
|
|
|
|
UserLifetime string `yaml:"turn_user_lifetime"`
|
|
|
|
// The list of TURN URIs to pass to clients
|
|
|
|
URIs []string `yaml:"turn_uris"`
|
|
|
|
|
|
|
|
// Authorization via Shared Secret
|
|
|
|
// The shared secret from coturn
|
|
|
|
SharedSecret string `yaml:"turn_shared_secret"`
|
|
|
|
|
|
|
|
// Authorization via Static Username & Password
|
|
|
|
// Hardcoded Username and Password
|
|
|
|
Username string `yaml:"turn_username"`
|
|
|
|
Password string `yaml:"turn_password"`
|
2017-12-19 17:00:44 +00:00
|
|
|
} `yaml:"turn"`
|
2017-11-09 09:58:45 +00:00
|
|
|
|
2017-06-16 15:52:03 +00:00
|
|
|
// The internal addresses the components will listen on.
|
|
|
|
// These should not be exposed externally as they expose metrics and debugging APIs.
|
2019-10-02 09:29:27 +00:00
|
|
|
// Falls back to addresses listed in Listen if not specified
|
|
|
|
Bind struct {
|
|
|
|
MediaAPI Address `yaml:"media_api"`
|
|
|
|
ClientAPI Address `yaml:"client_api"`
|
|
|
|
FederationAPI Address `yaml:"federation_api"`
|
|
|
|
AppServiceAPI Address `yaml:"appservice_api"`
|
|
|
|
SyncAPI Address `yaml:"sync_api"`
|
|
|
|
RoomServer Address `yaml:"room_server"`
|
|
|
|
FederationSender Address `yaml:"federation_sender"`
|
|
|
|
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
2020-03-30 14:02:20 +00:00
|
|
|
EDUServer Address `yaml:"edu_server"`
|
2020-05-14 13:05:14 +00:00
|
|
|
KeyServer Address `yaml:"key_server"`
|
2019-10-02 09:29:27 +00:00
|
|
|
} `yaml:"bind"`
|
|
|
|
|
|
|
|
// The addresses for talking to other microservices.
|
2017-06-16 15:52:03 +00:00
|
|
|
Listen struct {
|
2017-06-28 15:10:17 +00:00
|
|
|
MediaAPI Address `yaml:"media_api"`
|
|
|
|
ClientAPI Address `yaml:"client_api"`
|
|
|
|
FederationAPI Address `yaml:"federation_api"`
|
2018-07-17 14:36:04 +00:00
|
|
|
AppServiceAPI Address `yaml:"appservice_api"`
|
2017-06-28 15:10:17 +00:00
|
|
|
SyncAPI Address `yaml:"sync_api"`
|
|
|
|
RoomServer Address `yaml:"room_server"`
|
|
|
|
FederationSender Address `yaml:"federation_sender"`
|
2017-08-22 10:12:51 +00:00
|
|
|
PublicRoomsAPI Address `yaml:"public_rooms_api"`
|
2020-03-30 14:02:20 +00:00
|
|
|
EDUServer Address `yaml:"edu_server"`
|
2020-05-14 13:05:14 +00:00
|
|
|
KeyServer Address `yaml:"key_server"`
|
2017-06-16 15:52:03 +00:00
|
|
|
} `yaml:"listen"`
|
2017-09-28 16:00:23 +00:00
|
|
|
|
|
|
|
// The config for tracing the dendrite servers.
|
|
|
|
Tracing struct {
|
Add peer-to-peer support into Dendrite via libp2p and fetch (#880)
* Use a fork of pq which supports userCurrent on wasm
* Use sqlite3_js driver when running in JS
* Add cmd/dendritejs to pull in sqlite3_js driver for wasm only
* Update to latest go-sqlite-js version
* Replace prometheus with a stub. sigh
* Hard-code a config and don't use opentracing
* Latest go-sqlite3-js version
* Generate a key for now
* Listen for fetch traffic rather than HTTP
* Latest hacks for js
* libp2p support
* More libp2p
* Fork gjson to allow us to enforce auth checks as before
Previously, all events would come down redacted because the hash
checks would fail. They would fail because sjson.DeleteBytes didn't
remove keys not used for hashing. This didn't work because of a build
tag which included a file which no-oped the index returned.
See https://github.com/tidwall/gjson/issues/157
When it's resolved, let's go back to mainline.
* Use gjson@1.6.0 as it fixes https://github.com/tidwall/gjson/issues/157
* Use latest gomatrixserverlib for sig checks
* Fix a bug which could cause exclude_from_sync to not be set
Caused when sending events over federation.
* Use query variadic to make lookups actually work!
* Latest gomatrixserverlib
* Add notes on getting p2p up and running
Partly so I don't forget myself!
* refactor: Move p2p specific stuff to cmd/dendritejs
This is important or else the normal build of dendrite will fail
because the p2p libraries depend on syscall/js which doesn't work
on normal builds.
Also, clean up main.go to read a bit better.
* Update ho-http-js-libp2p to return errors from RoundTrip
* Add an LRU cache around the key DB
We actually need this for P2P because otherwise we can *segfault*
with things like: "runtime: unexpected return pc for runtime.handleEvent"
where the event is a `syscall/js` event, caused by spamming sql.js
caused by "Checking event signatures for 14 events of room state" which
hammers the key DB repeatedly in quick succession.
Using a cache fixes this, though the underlying cause is probably a bug
in the version of Go I'm on (1.13.7)
* breaking: Add Tracing.Enabled to toggle whether we do opentracing
Defaults to false, which is why this is a breaking change. We need
this flag because WASM builds cannot do opentracing.
* Start adding conditional builds for wasm to handle lib/pq
The general idea here is to have the wasm build have a `NewXXXDatabase`
that doesn't import any postgres package and hence we never import
`lib/pq`, which doesn't work under WASM (undefined `userCurrent`).
* Remove lib/pq for wasm for syncapi
* Add conditional building to remaining storage APIs
* Update build script to set env vars correctly for dendritejs
* sqlite bug fixes
* Docs
* Add a no-op main for dendritejs when not building under wasm
* Use the real prometheus, even for WASM
Instead, the dendrite-sw.js must mock out `process.pid` and
`fs.stat` - which must invoke the callback with an error (e.g `EINVAL`)
in order for it to work:
```
global.process = {
pid: 1,
};
global.fs.stat = function(path, cb) {
cb({
code: "EINVAL",
});
}
```
* Linting
2020-03-06 10:23:55 +00:00
|
|
|
// Set to true to enable tracer hooks. If false, no tracing is set up.
|
|
|
|
Enabled bool `yaml:"enabled"`
|
2017-09-28 16:00:23 +00:00
|
|
|
// The config for the jaeger opentracing reporter.
|
|
|
|
Jaeger jaegerconfig.Configuration `yaml:"jaeger"`
|
2017-12-19 17:00:44 +00:00
|
|
|
} `yaml:"tracing"`
|
|
|
|
|
|
|
|
// Application Services
|
|
|
|
// https://matrix.org/docs/spec/application_service/unstable.html
|
|
|
|
ApplicationServices struct {
|
|
|
|
// Configuration files for various application services
|
|
|
|
ConfigFiles []string `yaml:"config_files"`
|
|
|
|
} `yaml:"application_services"`
|
2017-11-29 09:43:03 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// The config for logging informations. Each hook will be added to logrus.
|
|
|
|
Logging []LogrusHook `yaml:"logging"`
|
|
|
|
|
2017-11-29 09:43:03 +00:00
|
|
|
// Any information derived from the configuration options for later use.
|
|
|
|
Derived struct {
|
|
|
|
Registration struct {
|
|
|
|
// Flows is a slice of flows, which represent one possible way that the client can authenticate a request.
|
|
|
|
// http://matrix.org/docs/spec/HEAD/client_server/r0.3.0.html#user-interactive-authentication-api
|
|
|
|
// As long as the generated flows only rely on config file options,
|
|
|
|
// we can generate them on startup and store them until needed
|
|
|
|
Flows []authtypes.Flow `json:"flows"`
|
|
|
|
|
|
|
|
// Params that need to be returned to the client during
|
|
|
|
// registration in order to complete registration stages.
|
|
|
|
Params map[string]interface{} `json:"params"`
|
|
|
|
}
|
2017-12-19 17:00:44 +00:00
|
|
|
|
2018-07-05 16:34:59 +00:00
|
|
|
// Application services parsed from their config files
|
2017-12-19 17:00:44 +00:00
|
|
|
// The paths of which were given above in the main config file
|
|
|
|
ApplicationServices []ApplicationService
|
2018-02-08 11:02:48 +00:00
|
|
|
|
2018-07-05 16:34:59 +00:00
|
|
|
// Meta-regexes compiled from all exclusive application service
|
2018-05-30 12:43:13 +00:00
|
|
|
// Regexes.
|
|
|
|
//
|
|
|
|
// When a user registers, we check that their username does not match any
|
2018-07-05 16:34:59 +00:00
|
|
|
// exclusive application service namespaces
|
2018-02-08 11:02:48 +00:00
|
|
|
ExclusiveApplicationServicesUsernameRegexp *regexp.Regexp
|
2018-05-30 12:43:13 +00:00
|
|
|
// When a user creates a room alias, we check that it isn't already
|
|
|
|
// reserved by an application service
|
|
|
|
ExclusiveApplicationServicesAliasRegexp *regexp.Regexp
|
|
|
|
// Note: An Exclusive Regex for room ID isn't necessary as we aren't blocking
|
|
|
|
// servers from creating RoomIDs in exclusive application service namespaces
|
2017-12-19 17:00:44 +00:00
|
|
|
} `yaml:"-"`
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
|
|
|
|
2020-04-20 16:42:34 +00:00
|
|
|
// KeyPerspectives are used to configure perspective key servers for
|
|
|
|
// retrieving server keys.
|
|
|
|
type KeyPerspectives []struct {
|
|
|
|
// The server name of the perspective key server
|
|
|
|
ServerName gomatrixserverlib.ServerName `yaml:"server_name"`
|
|
|
|
// Server keys for the perspective user, used to verify the
|
|
|
|
// keys have been signed by the perspective server
|
|
|
|
Keys []struct {
|
|
|
|
// The key ID, e.g. ed25519:auto
|
|
|
|
KeyID gomatrixserverlib.KeyID `yaml:"key_id"`
|
|
|
|
// The public key in base64 unpadded format
|
|
|
|
PublicKey string `yaml:"public_key"`
|
|
|
|
} `yaml:"keys"`
|
|
|
|
}
|
|
|
|
|
2017-06-16 15:52:03 +00:00
|
|
|
// A Path on the filesystem.
|
|
|
|
type Path string
|
|
|
|
|
|
|
|
// A DataSource for opening a postgresql database using lib/pq.
|
|
|
|
type DataSource string
|
|
|
|
|
|
|
|
// A Topic in kafka.
|
|
|
|
type Topic string
|
|
|
|
|
|
|
|
// An Address to listen on.
|
|
|
|
type Address string
|
|
|
|
|
|
|
|
// FileSizeBytes is a file size in bytes
|
|
|
|
type FileSizeBytes int64
|
|
|
|
|
|
|
|
// ThumbnailSize contains a single thumbnail size configuration
|
|
|
|
type ThumbnailSize struct {
|
|
|
|
// Maximum width of the thumbnail image
|
|
|
|
Width int `yaml:"width"`
|
|
|
|
// Maximum height of the thumbnail image
|
|
|
|
Height int `yaml:"height"`
|
|
|
|
// ResizeMethod is one of crop or scale.
|
|
|
|
// crop scales to fill the requested dimensions and crops the excess.
|
|
|
|
// scale scales to fit the requested dimensions and one dimension may be smaller than requested.
|
|
|
|
ResizeMethod string `yaml:"method,omitempty"`
|
|
|
|
}
|
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// LogrusHook represents a single logrus hook. At this point, only parsing and
|
|
|
|
// verification of the proper values for type and level are done.
|
|
|
|
// Validity/integrity checks on the parameters are done when configuring logrus.
|
|
|
|
type LogrusHook struct {
|
|
|
|
// The type of hook, currently only "file" is supported.
|
|
|
|
Type string `yaml:"type"`
|
|
|
|
|
|
|
|
// The level of the logs to produce. Will output only this level and above.
|
|
|
|
Level string `yaml:"level"`
|
|
|
|
|
|
|
|
// The parameters for this hook.
|
|
|
|
Params map[string]interface{} `yaml:"params"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// configErrors stores problems encountered when parsing a config file.
|
|
|
|
// It implements the error interface.
|
|
|
|
type configErrors []string
|
|
|
|
|
2017-08-16 12:36:41 +00:00
|
|
|
// Load a yaml config file for a server run as multiple processes.
|
|
|
|
// Checks the config to ensure that it is valid.
|
|
|
|
// The checks are different if the server is run as a monolithic process instead
|
|
|
|
// of being split into multiple components
|
2017-06-16 15:52:03 +00:00
|
|
|
func Load(configPath string) (*Dendrite, error) {
|
|
|
|
configData, err := ioutil.ReadFile(configPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
basePath, err := filepath.Abs(".")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Pass the current working directory and ioutil.ReadFile so that they can
|
|
|
|
// be mocked in the tests
|
2017-08-16 12:36:41 +00:00
|
|
|
monolithic := false
|
|
|
|
return loadConfig(basePath, configData, ioutil.ReadFile, monolithic)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LoadMonolithic loads a yaml config file for a server run as a single monolith.
|
|
|
|
// Checks the config to ensure that it is valid.
|
|
|
|
// The checks are different if the server is run as a monolithic process instead
|
|
|
|
// of being split into multiple components
|
|
|
|
func LoadMonolithic(configPath string) (*Dendrite, error) {
|
|
|
|
configData, err := ioutil.ReadFile(configPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
basePath, err := filepath.Abs(".")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// Pass the current working directory and ioutil.ReadFile so that they can
|
|
|
|
// be mocked in the tests
|
|
|
|
monolithic := true
|
|
|
|
return loadConfig(basePath, configData, ioutil.ReadFile, monolithic)
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func loadConfig(
|
|
|
|
basePath string,
|
|
|
|
configData []byte,
|
|
|
|
readFile func(string) ([]byte, error),
|
2017-08-16 12:36:41 +00:00
|
|
|
monolithic bool,
|
2017-06-16 15:52:03 +00:00
|
|
|
) (*Dendrite, error) {
|
|
|
|
var config Dendrite
|
|
|
|
var err error
|
|
|
|
if err = yaml.Unmarshal(configData, &config); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
Add peer-to-peer support into Dendrite via libp2p and fetch (#880)
* Use a fork of pq which supports userCurrent on wasm
* Use sqlite3_js driver when running in JS
* Add cmd/dendritejs to pull in sqlite3_js driver for wasm only
* Update to latest go-sqlite-js version
* Replace prometheus with a stub. sigh
* Hard-code a config and don't use opentracing
* Latest go-sqlite3-js version
* Generate a key for now
* Listen for fetch traffic rather than HTTP
* Latest hacks for js
* libp2p support
* More libp2p
* Fork gjson to allow us to enforce auth checks as before
Previously, all events would come down redacted because the hash
checks would fail. They would fail because sjson.DeleteBytes didn't
remove keys not used for hashing. This didn't work because of a build
tag which included a file which no-oped the index returned.
See https://github.com/tidwall/gjson/issues/157
When it's resolved, let's go back to mainline.
* Use gjson@1.6.0 as it fixes https://github.com/tidwall/gjson/issues/157
* Use latest gomatrixserverlib for sig checks
* Fix a bug which could cause exclude_from_sync to not be set
Caused when sending events over federation.
* Use query variadic to make lookups actually work!
* Latest gomatrixserverlib
* Add notes on getting p2p up and running
Partly so I don't forget myself!
* refactor: Move p2p specific stuff to cmd/dendritejs
This is important or else the normal build of dendrite will fail
because the p2p libraries depend on syscall/js which doesn't work
on normal builds.
Also, clean up main.go to read a bit better.
* Update ho-http-js-libp2p to return errors from RoundTrip
* Add an LRU cache around the key DB
We actually need this for P2P because otherwise we can *segfault*
with things like: "runtime: unexpected return pc for runtime.handleEvent"
where the event is a `syscall/js` event, caused by spamming sql.js
caused by "Checking event signatures for 14 events of room state" which
hammers the key DB repeatedly in quick succession.
Using a cache fixes this, though the underlying cause is probably a bug
in the version of Go I'm on (1.13.7)
* breaking: Add Tracing.Enabled to toggle whether we do opentracing
Defaults to false, which is why this is a breaking change. We need
this flag because WASM builds cannot do opentracing.
* Start adding conditional builds for wasm to handle lib/pq
The general idea here is to have the wasm build have a `NewXXXDatabase`
that doesn't import any postgres package and hence we never import
`lib/pq`, which doesn't work under WASM (undefined `userCurrent`).
* Remove lib/pq for wasm for syncapi
* Add conditional building to remaining storage APIs
* Update build script to set env vars correctly for dendritejs
* sqlite bug fixes
* Docs
* Add a no-op main for dendritejs when not building under wasm
* Use the real prometheus, even for WASM
Instead, the dendrite-sw.js must mock out `process.pid` and
`fs.stat` - which must invoke the callback with an error (e.g `EINVAL`)
in order for it to work:
```
global.process = {
pid: 1,
};
global.fs.stat = function(path, cb) {
cb({
code: "EINVAL",
});
}
```
* Linting
2020-03-06 10:23:55 +00:00
|
|
|
config.SetDefaults()
|
2017-06-16 15:52:03 +00:00
|
|
|
|
2017-08-16 12:36:41 +00:00
|
|
|
if err = config.check(monolithic); err != nil {
|
2017-06-16 15:52:03 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
privateKeyPath := absPath(basePath, config.Matrix.PrivateKeyPath)
|
|
|
|
privateKeyData, err := readFile(privateKeyPath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Matrix.KeyID, config.Matrix.PrivateKey, err = readKeyPEM(privateKeyPath, privateKeyData); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, certPath := range config.Matrix.FederationCertificatePaths {
|
|
|
|
absCertPath := absPath(basePath, certPath)
|
2017-12-19 17:00:44 +00:00
|
|
|
var pemData []byte
|
|
|
|
pemData, err = readFile(absCertPath)
|
2017-06-16 15:52:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
fingerprint := fingerprintPEM(pemData)
|
|
|
|
if fingerprint == nil {
|
|
|
|
return nil, fmt.Errorf("no certificate PEM data in %q", absCertPath)
|
|
|
|
}
|
|
|
|
config.Matrix.TLSFingerPrints = append(config.Matrix.TLSFingerPrints, *fingerprint)
|
|
|
|
}
|
|
|
|
|
|
|
|
config.Media.AbsBasePath = Path(absPath(basePath, config.Media.BasePath))
|
|
|
|
|
2017-11-29 09:43:03 +00:00
|
|
|
// Generate data from config options
|
Add peer-to-peer support into Dendrite via libp2p and fetch (#880)
* Use a fork of pq which supports userCurrent on wasm
* Use sqlite3_js driver when running in JS
* Add cmd/dendritejs to pull in sqlite3_js driver for wasm only
* Update to latest go-sqlite-js version
* Replace prometheus with a stub. sigh
* Hard-code a config and don't use opentracing
* Latest go-sqlite3-js version
* Generate a key for now
* Listen for fetch traffic rather than HTTP
* Latest hacks for js
* libp2p support
* More libp2p
* Fork gjson to allow us to enforce auth checks as before
Previously, all events would come down redacted because the hash
checks would fail. They would fail because sjson.DeleteBytes didn't
remove keys not used for hashing. This didn't work because of a build
tag which included a file which no-oped the index returned.
See https://github.com/tidwall/gjson/issues/157
When it's resolved, let's go back to mainline.
* Use gjson@1.6.0 as it fixes https://github.com/tidwall/gjson/issues/157
* Use latest gomatrixserverlib for sig checks
* Fix a bug which could cause exclude_from_sync to not be set
Caused when sending events over federation.
* Use query variadic to make lookups actually work!
* Latest gomatrixserverlib
* Add notes on getting p2p up and running
Partly so I don't forget myself!
* refactor: Move p2p specific stuff to cmd/dendritejs
This is important or else the normal build of dendrite will fail
because the p2p libraries depend on syscall/js which doesn't work
on normal builds.
Also, clean up main.go to read a bit better.
* Update ho-http-js-libp2p to return errors from RoundTrip
* Add an LRU cache around the key DB
We actually need this for P2P because otherwise we can *segfault*
with things like: "runtime: unexpected return pc for runtime.handleEvent"
where the event is a `syscall/js` event, caused by spamming sql.js
caused by "Checking event signatures for 14 events of room state" which
hammers the key DB repeatedly in quick succession.
Using a cache fixes this, though the underlying cause is probably a bug
in the version of Go I'm on (1.13.7)
* breaking: Add Tracing.Enabled to toggle whether we do opentracing
Defaults to false, which is why this is a breaking change. We need
this flag because WASM builds cannot do opentracing.
* Start adding conditional builds for wasm to handle lib/pq
The general idea here is to have the wasm build have a `NewXXXDatabase`
that doesn't import any postgres package and hence we never import
`lib/pq`, which doesn't work under WASM (undefined `userCurrent`).
* Remove lib/pq for wasm for syncapi
* Add conditional building to remaining storage APIs
* Update build script to set env vars correctly for dendritejs
* sqlite bug fixes
* Docs
* Add a no-op main for dendritejs when not building under wasm
* Use the real prometheus, even for WASM
Instead, the dendrite-sw.js must mock out `process.pid` and
`fs.stat` - which must invoke the callback with an error (e.g `EINVAL`)
in order for it to work:
```
global.process = {
pid: 1,
};
global.fs.stat = function(path, cb) {
cb({
code: "EINVAL",
});
}
```
* Linting
2020-03-06 10:23:55 +00:00
|
|
|
err = config.Derive()
|
2017-12-19 17:00:44 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-11-29 09:43:03 +00:00
|
|
|
|
2017-06-16 15:52:03 +00:00
|
|
|
return &config, nil
|
|
|
|
}
|
|
|
|
|
Add peer-to-peer support into Dendrite via libp2p and fetch (#880)
* Use a fork of pq which supports userCurrent on wasm
* Use sqlite3_js driver when running in JS
* Add cmd/dendritejs to pull in sqlite3_js driver for wasm only
* Update to latest go-sqlite-js version
* Replace prometheus with a stub. sigh
* Hard-code a config and don't use opentracing
* Latest go-sqlite3-js version
* Generate a key for now
* Listen for fetch traffic rather than HTTP
* Latest hacks for js
* libp2p support
* More libp2p
* Fork gjson to allow us to enforce auth checks as before
Previously, all events would come down redacted because the hash
checks would fail. They would fail because sjson.DeleteBytes didn't
remove keys not used for hashing. This didn't work because of a build
tag which included a file which no-oped the index returned.
See https://github.com/tidwall/gjson/issues/157
When it's resolved, let's go back to mainline.
* Use gjson@1.6.0 as it fixes https://github.com/tidwall/gjson/issues/157
* Use latest gomatrixserverlib for sig checks
* Fix a bug which could cause exclude_from_sync to not be set
Caused when sending events over federation.
* Use query variadic to make lookups actually work!
* Latest gomatrixserverlib
* Add notes on getting p2p up and running
Partly so I don't forget myself!
* refactor: Move p2p specific stuff to cmd/dendritejs
This is important or else the normal build of dendrite will fail
because the p2p libraries depend on syscall/js which doesn't work
on normal builds.
Also, clean up main.go to read a bit better.
* Update ho-http-js-libp2p to return errors from RoundTrip
* Add an LRU cache around the key DB
We actually need this for P2P because otherwise we can *segfault*
with things like: "runtime: unexpected return pc for runtime.handleEvent"
where the event is a `syscall/js` event, caused by spamming sql.js
caused by "Checking event signatures for 14 events of room state" which
hammers the key DB repeatedly in quick succession.
Using a cache fixes this, though the underlying cause is probably a bug
in the version of Go I'm on (1.13.7)
* breaking: Add Tracing.Enabled to toggle whether we do opentracing
Defaults to false, which is why this is a breaking change. We need
this flag because WASM builds cannot do opentracing.
* Start adding conditional builds for wasm to handle lib/pq
The general idea here is to have the wasm build have a `NewXXXDatabase`
that doesn't import any postgres package and hence we never import
`lib/pq`, which doesn't work under WASM (undefined `userCurrent`).
* Remove lib/pq for wasm for syncapi
* Add conditional building to remaining storage APIs
* Update build script to set env vars correctly for dendritejs
* sqlite bug fixes
* Docs
* Add a no-op main for dendritejs when not building under wasm
* Use the real prometheus, even for WASM
Instead, the dendrite-sw.js must mock out `process.pid` and
`fs.stat` - which must invoke the callback with an error (e.g `EINVAL`)
in order for it to work:
```
global.process = {
pid: 1,
};
global.fs.stat = function(path, cb) {
cb({
code: "EINVAL",
});
}
```
* Linting
2020-03-06 10:23:55 +00:00
|
|
|
// Derive generates data that is derived from various values provided in
|
2017-11-29 09:43:03 +00:00
|
|
|
// the config file.
|
Add peer-to-peer support into Dendrite via libp2p and fetch (#880)
* Use a fork of pq which supports userCurrent on wasm
* Use sqlite3_js driver when running in JS
* Add cmd/dendritejs to pull in sqlite3_js driver for wasm only
* Update to latest go-sqlite-js version
* Replace prometheus with a stub. sigh
* Hard-code a config and don't use opentracing
* Latest go-sqlite3-js version
* Generate a key for now
* Listen for fetch traffic rather than HTTP
* Latest hacks for js
* libp2p support
* More libp2p
* Fork gjson to allow us to enforce auth checks as before
Previously, all events would come down redacted because the hash
checks would fail. They would fail because sjson.DeleteBytes didn't
remove keys not used for hashing. This didn't work because of a build
tag which included a file which no-oped the index returned.
See https://github.com/tidwall/gjson/issues/157
When it's resolved, let's go back to mainline.
* Use gjson@1.6.0 as it fixes https://github.com/tidwall/gjson/issues/157
* Use latest gomatrixserverlib for sig checks
* Fix a bug which could cause exclude_from_sync to not be set
Caused when sending events over federation.
* Use query variadic to make lookups actually work!
* Latest gomatrixserverlib
* Add notes on getting p2p up and running
Partly so I don't forget myself!
* refactor: Move p2p specific stuff to cmd/dendritejs
This is important or else the normal build of dendrite will fail
because the p2p libraries depend on syscall/js which doesn't work
on normal builds.
Also, clean up main.go to read a bit better.
* Update ho-http-js-libp2p to return errors from RoundTrip
* Add an LRU cache around the key DB
We actually need this for P2P because otherwise we can *segfault*
with things like: "runtime: unexpected return pc for runtime.handleEvent"
where the event is a `syscall/js` event, caused by spamming sql.js
caused by "Checking event signatures for 14 events of room state" which
hammers the key DB repeatedly in quick succession.
Using a cache fixes this, though the underlying cause is probably a bug
in the version of Go I'm on (1.13.7)
* breaking: Add Tracing.Enabled to toggle whether we do opentracing
Defaults to false, which is why this is a breaking change. We need
this flag because WASM builds cannot do opentracing.
* Start adding conditional builds for wasm to handle lib/pq
The general idea here is to have the wasm build have a `NewXXXDatabase`
that doesn't import any postgres package and hence we never import
`lib/pq`, which doesn't work under WASM (undefined `userCurrent`).
* Remove lib/pq for wasm for syncapi
* Add conditional building to remaining storage APIs
* Update build script to set env vars correctly for dendritejs
* sqlite bug fixes
* Docs
* Add a no-op main for dendritejs when not building under wasm
* Use the real prometheus, even for WASM
Instead, the dendrite-sw.js must mock out `process.pid` and
`fs.stat` - which must invoke the callback with an error (e.g `EINVAL`)
in order for it to work:
```
global.process = {
pid: 1,
};
global.fs.stat = function(path, cb) {
cb({
code: "EINVAL",
});
}
```
* Linting
2020-03-06 10:23:55 +00:00
|
|
|
func (config *Dendrite) Derive() error {
|
2017-11-29 09:43:03 +00:00
|
|
|
// Determine registrations flows based off config values
|
|
|
|
|
|
|
|
config.Derived.Registration.Params = make(map[string]interface{})
|
|
|
|
|
|
|
|
// TODO: Add email auth type
|
|
|
|
// TODO: Add MSISDN auth type
|
|
|
|
|
2017-12-05 16:16:14 +00:00
|
|
|
if config.Matrix.RecaptchaEnabled {
|
|
|
|
config.Derived.Registration.Params[authtypes.LoginTypeRecaptcha] = map[string]string{"public_key": config.Matrix.RecaptchaPublicKey}
|
|
|
|
config.Derived.Registration.Flows = append(config.Derived.Registration.Flows,
|
2017-12-06 13:55:51 +00:00
|
|
|
authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeRecaptcha}})
|
2017-12-05 16:16:14 +00:00
|
|
|
} else {
|
|
|
|
config.Derived.Registration.Flows = append(config.Derived.Registration.Flows,
|
2017-12-06 13:55:51 +00:00
|
|
|
authtypes.Flow{Stages: []authtypes.LoginType{authtypes.LoginTypeDummy}})
|
2017-12-05 16:16:14 +00:00
|
|
|
}
|
2017-12-19 17:00:44 +00:00
|
|
|
|
|
|
|
// Load application service configuration files
|
2018-07-17 14:36:04 +00:00
|
|
|
if err := loadAppServices(config); err != nil {
|
2017-12-19 17:00:44 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2017-11-29 09:43:03 +00:00
|
|
|
}
|
|
|
|
|
Add peer-to-peer support into Dendrite via libp2p and fetch (#880)
* Use a fork of pq which supports userCurrent on wasm
* Use sqlite3_js driver when running in JS
* Add cmd/dendritejs to pull in sqlite3_js driver for wasm only
* Update to latest go-sqlite-js version
* Replace prometheus with a stub. sigh
* Hard-code a config and don't use opentracing
* Latest go-sqlite3-js version
* Generate a key for now
* Listen for fetch traffic rather than HTTP
* Latest hacks for js
* libp2p support
* More libp2p
* Fork gjson to allow us to enforce auth checks as before
Previously, all events would come down redacted because the hash
checks would fail. They would fail because sjson.DeleteBytes didn't
remove keys not used for hashing. This didn't work because of a build
tag which included a file which no-oped the index returned.
See https://github.com/tidwall/gjson/issues/157
When it's resolved, let's go back to mainline.
* Use gjson@1.6.0 as it fixes https://github.com/tidwall/gjson/issues/157
* Use latest gomatrixserverlib for sig checks
* Fix a bug which could cause exclude_from_sync to not be set
Caused when sending events over federation.
* Use query variadic to make lookups actually work!
* Latest gomatrixserverlib
* Add notes on getting p2p up and running
Partly so I don't forget myself!
* refactor: Move p2p specific stuff to cmd/dendritejs
This is important or else the normal build of dendrite will fail
because the p2p libraries depend on syscall/js which doesn't work
on normal builds.
Also, clean up main.go to read a bit better.
* Update ho-http-js-libp2p to return errors from RoundTrip
* Add an LRU cache around the key DB
We actually need this for P2P because otherwise we can *segfault*
with things like: "runtime: unexpected return pc for runtime.handleEvent"
where the event is a `syscall/js` event, caused by spamming sql.js
caused by "Checking event signatures for 14 events of room state" which
hammers the key DB repeatedly in quick succession.
Using a cache fixes this, though the underlying cause is probably a bug
in the version of Go I'm on (1.13.7)
* breaking: Add Tracing.Enabled to toggle whether we do opentracing
Defaults to false, which is why this is a breaking change. We need
this flag because WASM builds cannot do opentracing.
* Start adding conditional builds for wasm to handle lib/pq
The general idea here is to have the wasm build have a `NewXXXDatabase`
that doesn't import any postgres package and hence we never import
`lib/pq`, which doesn't work under WASM (undefined `userCurrent`).
* Remove lib/pq for wasm for syncapi
* Add conditional building to remaining storage APIs
* Update build script to set env vars correctly for dendritejs
* sqlite bug fixes
* Docs
* Add a no-op main for dendritejs when not building under wasm
* Use the real prometheus, even for WASM
Instead, the dendrite-sw.js must mock out `process.pid` and
`fs.stat` - which must invoke the callback with an error (e.g `EINVAL`)
in order for it to work:
```
global.process = {
pid: 1,
};
global.fs.stat = function(path, cb) {
cb({
code: "EINVAL",
});
}
```
* Linting
2020-03-06 10:23:55 +00:00
|
|
|
// SetDefaults sets default config values if they are not explicitly set.
|
|
|
|
func (config *Dendrite) SetDefaults() {
|
2017-06-16 15:52:03 +00:00
|
|
|
if config.Matrix.KeyValidityPeriod == 0 {
|
|
|
|
config.Matrix.KeyValidityPeriod = 24 * time.Hour
|
|
|
|
}
|
|
|
|
|
2017-09-11 18:18:19 +00:00
|
|
|
if config.Matrix.TrustedIDServers == nil {
|
|
|
|
config.Matrix.TrustedIDServers = []string{}
|
|
|
|
}
|
|
|
|
|
2017-06-16 15:52:03 +00:00
|
|
|
if config.Media.MaxThumbnailGenerators == 0 {
|
|
|
|
config.Media.MaxThumbnailGenerators = 10
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Media.MaxFileSizeBytes == nil {
|
|
|
|
defaultMaxFileSizeBytes := FileSizeBytes(10485760)
|
|
|
|
config.Media.MaxFileSizeBytes = &defaultMaxFileSizeBytes
|
|
|
|
}
|
2020-05-01 12:34:53 +00:00
|
|
|
|
|
|
|
if config.Database.MaxIdleConns == 0 {
|
|
|
|
config.Database.MaxIdleConns = 2
|
|
|
|
}
|
|
|
|
|
|
|
|
if config.Database.MaxOpenConns == 0 {
|
|
|
|
config.Database.MaxOpenConns = 100
|
|
|
|
}
|
|
|
|
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// Error returns a string detailing how many errors were contained within a
|
|
|
|
// configErrors type.
|
|
|
|
func (errs configErrors) Error() string {
|
|
|
|
if len(errs) == 1 {
|
|
|
|
return errs[0]
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
|
|
|
return fmt.Sprintf(
|
2018-04-10 12:21:20 +00:00
|
|
|
"%s (and %d other problems)", errs[0], len(errs)-1,
|
2017-06-16 15:52:03 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// Add appends an error to the list of errors in this configErrors.
|
|
|
|
// It is a wrapper to the builtin append and hides pointers from
|
|
|
|
// the client code.
|
|
|
|
// This method is safe to use with an uninitialized configErrors because
|
|
|
|
// if it is nil, it will be properly allocated.
|
|
|
|
func (errs *configErrors) Add(str string) {
|
|
|
|
*errs = append(*errs, str)
|
|
|
|
}
|
2017-06-16 15:52:03 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// checkNotEmpty verifies the given value is not empty in the configuration.
|
|
|
|
// If it is, adds an error to the list.
|
|
|
|
func checkNotEmpty(configErrs *configErrors, key, value string) {
|
|
|
|
if value == "" {
|
|
|
|
configErrs.Add(fmt.Sprintf("missing config key %q", key))
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
2018-04-10 12:21:20 +00:00
|
|
|
}
|
2017-06-16 15:52:03 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// checkNotZero verifies the given value is not zero in the configuration.
|
|
|
|
// If it is, adds an error to the list.
|
|
|
|
func checkNotZero(configErrs *configErrors, key string, value int64) {
|
|
|
|
if value == 0 {
|
|
|
|
configErrs.Add(fmt.Sprintf("missing config key %q", key))
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
2018-04-10 12:21:20 +00:00
|
|
|
}
|
2017-06-16 15:52:03 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// checkPositive verifies the given value is positive (zero included)
|
|
|
|
// in the configuration. If it is not, adds an error to the list.
|
|
|
|
func checkPositive(configErrs *configErrors, key string, value int64) {
|
|
|
|
if value < 0 {
|
|
|
|
configErrs.Add(fmt.Sprintf("invalid value for config key %q: %d", key, value))
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
2018-04-10 12:21:20 +00:00
|
|
|
}
|
2017-06-16 15:52:03 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// checkTurn verifies the parameters turn.* are valid.
|
|
|
|
func (config *Dendrite) checkTurn(configErrs *configErrors) {
|
|
|
|
value := config.TURN.UserLifetime
|
|
|
|
if value != "" {
|
|
|
|
if _, err := time.ParseDuration(value); err != nil {
|
|
|
|
configErrs.Add(fmt.Sprintf("invalid duration for config key %q: %s", "turn.turn_user_lifetime", value))
|
2017-11-09 09:58:45 +00:00
|
|
|
}
|
|
|
|
}
|
2018-04-10 12:21:20 +00:00
|
|
|
}
|
2017-11-09 09:58:45 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// checkMatrix verifies the parameters matrix.* are valid.
|
|
|
|
func (config *Dendrite) checkMatrix(configErrs *configErrors) {
|
|
|
|
checkNotEmpty(configErrs, "matrix.server_name", string(config.Matrix.ServerName))
|
|
|
|
checkNotEmpty(configErrs, "matrix.private_key", string(config.Matrix.PrivateKeyPath))
|
|
|
|
checkNotZero(configErrs, "matrix.federation_certificates", int64(len(config.Matrix.FederationCertificatePaths)))
|
2019-08-15 17:59:17 +00:00
|
|
|
if config.Matrix.RecaptchaEnabled {
|
|
|
|
checkNotEmpty(configErrs, "matrix.recaptcha_public_key", string(config.Matrix.RecaptchaPublicKey))
|
|
|
|
checkNotEmpty(configErrs, "matrix.recaptcha_private_key", string(config.Matrix.RecaptchaPrivateKey))
|
|
|
|
checkNotEmpty(configErrs, "matrix.recaptcha_siteverify_api", string(config.Matrix.RecaptchaSiteVerifyAPI))
|
|
|
|
}
|
2018-04-10 12:21:20 +00:00
|
|
|
}
|
2017-06-16 15:52:03 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// checkMedia verifies the parameters media.* are valid.
|
|
|
|
func (config *Dendrite) checkMedia(configErrs *configErrors) {
|
|
|
|
checkNotEmpty(configErrs, "media.base_path", string(config.Media.BasePath))
|
|
|
|
checkPositive(configErrs, "media.max_file_size_bytes", int64(*config.Media.MaxFileSizeBytes))
|
|
|
|
checkPositive(configErrs, "media.max_thumbnail_generators", int64(config.Media.MaxThumbnailGenerators))
|
2017-11-09 09:58:45 +00:00
|
|
|
|
2017-06-16 15:52:03 +00:00
|
|
|
for i, size := range config.Media.ThumbnailSizes {
|
2018-04-10 12:21:20 +00:00
|
|
|
checkPositive(configErrs, fmt.Sprintf("media.thumbnail_sizes[%d].width", i), int64(size.Width))
|
|
|
|
checkPositive(configErrs, fmt.Sprintf("media.thumbnail_sizes[%d].height", i), int64(size.Height))
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
2018-04-10 12:21:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// checkKafka verifies the parameters kafka.* and the related
|
|
|
|
// database.naffka are valid.
|
|
|
|
func (config *Dendrite) checkKafka(configErrs *configErrors, monolithic bool) {
|
|
|
|
|
2017-08-16 12:36:41 +00:00
|
|
|
if config.Kafka.UseNaffka {
|
|
|
|
if !monolithic {
|
2018-04-10 12:21:20 +00:00
|
|
|
configErrs.Add(fmt.Sprintf("naffka can only be used in a monolithic server"))
|
2017-08-16 12:36:41 +00:00
|
|
|
}
|
2017-11-16 17:35:28 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
checkNotEmpty(configErrs, "database.naffka", string(config.Database.Naffka))
|
2017-08-16 12:36:41 +00:00
|
|
|
} else {
|
|
|
|
// If we aren't using naffka then we need to have at least one kafka
|
|
|
|
// server to talk to.
|
2018-04-10 12:21:20 +00:00
|
|
|
checkNotZero(configErrs, "kafka.addresses", int64(len(config.Kafka.Addresses)))
|
2017-08-16 12:36:41 +00:00
|
|
|
}
|
2018-04-10 12:21:20 +00:00
|
|
|
checkNotEmpty(configErrs, "kafka.topics.output_room_event", string(config.Kafka.Topics.OutputRoomEvent))
|
|
|
|
checkNotEmpty(configErrs, "kafka.topics.output_client_data", string(config.Kafka.Topics.OutputClientData))
|
2018-08-02 17:22:44 +00:00
|
|
|
checkNotEmpty(configErrs, "kafka.topics.output_typing_event", string(config.Kafka.Topics.OutputTypingEvent))
|
2018-04-10 12:21:20 +00:00
|
|
|
checkNotEmpty(configErrs, "kafka.topics.user_updates", string(config.Kafka.Topics.UserUpdates))
|
|
|
|
}
|
2017-08-16 12:36:41 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// checkDatabase verifies the parameters database.* are valid.
|
|
|
|
func (config *Dendrite) checkDatabase(configErrs *configErrors) {
|
|
|
|
checkNotEmpty(configErrs, "database.account", string(config.Database.Account))
|
|
|
|
checkNotEmpty(configErrs, "database.device", string(config.Database.Device))
|
|
|
|
checkNotEmpty(configErrs, "database.server_key", string(config.Database.ServerKey))
|
|
|
|
checkNotEmpty(configErrs, "database.media_api", string(config.Database.MediaAPI))
|
|
|
|
checkNotEmpty(configErrs, "database.sync_api", string(config.Database.SyncAPI))
|
|
|
|
checkNotEmpty(configErrs, "database.room_server", string(config.Database.RoomServer))
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkListen verifies the parameters listen.* are valid.
|
|
|
|
func (config *Dendrite) checkListen(configErrs *configErrors) {
|
|
|
|
checkNotEmpty(configErrs, "listen.media_api", string(config.Listen.MediaAPI))
|
|
|
|
checkNotEmpty(configErrs, "listen.client_api", string(config.Listen.ClientAPI))
|
|
|
|
checkNotEmpty(configErrs, "listen.federation_api", string(config.Listen.FederationAPI))
|
|
|
|
checkNotEmpty(configErrs, "listen.sync_api", string(config.Listen.SyncAPI))
|
|
|
|
checkNotEmpty(configErrs, "listen.room_server", string(config.Listen.RoomServer))
|
2020-03-30 14:02:20 +00:00
|
|
|
checkNotEmpty(configErrs, "listen.edu_server", string(config.Listen.EDUServer))
|
2018-04-10 12:21:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// checkLogging verifies the parameters logging.* are valid.
|
|
|
|
func (config *Dendrite) checkLogging(configErrs *configErrors) {
|
|
|
|
for _, logrusHook := range config.Logging {
|
|
|
|
checkNotEmpty(configErrs, "logging.type", string(logrusHook.Type))
|
|
|
|
checkNotEmpty(configErrs, "logging.level", string(logrusHook.Level))
|
2017-08-16 12:36:41 +00:00
|
|
|
}
|
2018-04-10 12:21:20 +00:00
|
|
|
}
|
2017-06-16 15:52:03 +00:00
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
// check returns an error type containing all errors found within the config
|
|
|
|
// file.
|
|
|
|
func (config *Dendrite) check(monolithic bool) error {
|
|
|
|
var configErrs configErrors
|
|
|
|
|
|
|
|
if config.Version != Version {
|
|
|
|
configErrs.Add(fmt.Sprintf(
|
|
|
|
"unknown config version %q, expected %q", config.Version, Version,
|
|
|
|
))
|
|
|
|
return configErrs
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
|
|
|
|
2018-04-10 12:21:20 +00:00
|
|
|
config.checkMatrix(&configErrs)
|
|
|
|
config.checkMedia(&configErrs)
|
|
|
|
config.checkTurn(&configErrs)
|
|
|
|
config.checkKafka(&configErrs, monolithic)
|
|
|
|
config.checkDatabase(&configErrs)
|
|
|
|
config.checkLogging(&configErrs)
|
|
|
|
|
|
|
|
if !monolithic {
|
|
|
|
config.checkListen(&configErrs)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Due to how Golang manages its interface types, this condition is not redundant.
|
2019-06-19 13:05:03 +00:00
|
|
|
// In order to get the proper behaviour, it is necessary to return an explicit nil
|
2018-04-10 12:21:20 +00:00
|
|
|
// and not a nil configErrors.
|
|
|
|
// This is because the following equalities hold:
|
|
|
|
// error(nil) == nil
|
|
|
|
// error(configErrors(nil)) != nil
|
|
|
|
if configErrs != nil {
|
|
|
|
return configErrs
|
|
|
|
}
|
2017-06-16 15:52:03 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-11-29 09:43:03 +00:00
|
|
|
// absPath returns the absolute path for a given relative or absolute path.
|
2017-06-16 15:52:03 +00:00
|
|
|
func absPath(dir string, path Path) string {
|
|
|
|
if filepath.IsAbs(string(path)) {
|
|
|
|
// filepath.Join cleans the path so we should clean the absolute paths as well for consistency.
|
|
|
|
return filepath.Clean(string(path))
|
|
|
|
}
|
|
|
|
return filepath.Join(dir, string(path))
|
|
|
|
}
|
|
|
|
|
|
|
|
func readKeyPEM(path string, data []byte) (gomatrixserverlib.KeyID, ed25519.PrivateKey, error) {
|
|
|
|
for {
|
|
|
|
var keyBlock *pem.Block
|
|
|
|
keyBlock, data = pem.Decode(data)
|
|
|
|
if data == nil {
|
|
|
|
return "", nil, fmt.Errorf("no matrix private key PEM data in %q", path)
|
|
|
|
}
|
2018-03-03 12:18:28 +00:00
|
|
|
if keyBlock == nil {
|
|
|
|
return "", nil, fmt.Errorf("keyBlock is nil %q", path)
|
|
|
|
}
|
2017-06-16 15:52:03 +00:00
|
|
|
if keyBlock.Type == "MATRIX PRIVATE KEY" {
|
|
|
|
keyID := keyBlock.Headers["Key-ID"]
|
|
|
|
if keyID == "" {
|
|
|
|
return "", nil, fmt.Errorf("missing key ID in PEM data in %q", path)
|
|
|
|
}
|
|
|
|
if !strings.HasPrefix(keyID, "ed25519:") {
|
|
|
|
return "", nil, fmt.Errorf("key ID %q doesn't start with \"ed25519:\" in %q", keyID, path)
|
|
|
|
}
|
|
|
|
_, privKey, err := ed25519.GenerateKey(bytes.NewReader(keyBlock.Bytes))
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, err
|
|
|
|
}
|
|
|
|
return gomatrixserverlib.KeyID(keyID), privKey, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func fingerprintPEM(data []byte) *gomatrixserverlib.TLSFingerprint {
|
|
|
|
for {
|
|
|
|
var certDERBlock *pem.Block
|
|
|
|
certDERBlock, data = pem.Decode(data)
|
|
|
|
if data == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
if certDERBlock.Type == "CERTIFICATE" {
|
|
|
|
digest := sha256.Sum256(certDERBlock.Bytes)
|
2017-09-20 12:40:22 +00:00
|
|
|
return &gomatrixserverlib.TLSFingerprint{SHA256: digest[:]}
|
2017-06-16 15:52:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-19 14:21:04 +00:00
|
|
|
|
2018-07-17 14:36:04 +00:00
|
|
|
// AppServiceURL returns a HTTP URL for where the appservice component is listening.
|
|
|
|
func (config *Dendrite) AppServiceURL() string {
|
2020-03-30 14:02:20 +00:00
|
|
|
// Hard code the appservice server to talk HTTP for now.
|
2018-07-17 14:36:04 +00:00
|
|
|
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
|
|
|
// People setting up servers shouldn't need to get a certificate valid for the public
|
|
|
|
// internet for an internal API.
|
|
|
|
return "http://" + string(config.Listen.AppServiceAPI)
|
|
|
|
}
|
|
|
|
|
2017-06-19 14:21:04 +00:00
|
|
|
// RoomServerURL returns an HTTP URL for where the roomserver is listening.
|
|
|
|
func (config *Dendrite) RoomServerURL() string {
|
|
|
|
// Hard code the roomserver to talk HTTP for now.
|
|
|
|
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
|
|
|
// People setting up servers shouldn't need to get a certificate valid for the public
|
|
|
|
// internet for an internal API.
|
|
|
|
return "http://" + string(config.Listen.RoomServer)
|
|
|
|
}
|
2017-09-28 16:00:23 +00:00
|
|
|
|
2020-03-30 14:02:20 +00:00
|
|
|
// EDUServerURL returns an HTTP URL for where the EDU server is listening.
|
|
|
|
func (config *Dendrite) EDUServerURL() string {
|
|
|
|
// Hard code the EDU server to talk HTTP for now.
|
2018-07-24 14:49:49 +00:00
|
|
|
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
|
|
|
// People setting up servers shouldn't need to get a certificate valid for the public
|
|
|
|
// internet for an internal API.
|
2020-03-30 14:02:20 +00:00
|
|
|
return "http://" + string(config.Listen.EDUServer)
|
2018-07-24 14:49:49 +00:00
|
|
|
}
|
|
|
|
|
2019-10-01 16:09:47 +00:00
|
|
|
// FederationSenderURL returns an HTTP URL for where the federation sender is listening.
|
|
|
|
func (config *Dendrite) FederationSenderURL() string {
|
2020-03-30 14:02:20 +00:00
|
|
|
// Hard code the federation sender server to talk HTTP for now.
|
2019-10-01 16:09:47 +00:00
|
|
|
// If we support HTTPS we need to think of a practical way to do certificate validation.
|
|
|
|
// People setting up servers shouldn't need to get a certificate valid for the public
|
|
|
|
// internet for an internal API.
|
|
|
|
return "http://" + string(config.Listen.FederationSender)
|
|
|
|
}
|
|
|
|
|
2017-09-28 16:00:23 +00:00
|
|
|
// SetupTracing configures the opentracing using the supplied configuration.
|
|
|
|
func (config *Dendrite) SetupTracing(serviceName string) (closer io.Closer, err error) {
|
Add peer-to-peer support into Dendrite via libp2p and fetch (#880)
* Use a fork of pq which supports userCurrent on wasm
* Use sqlite3_js driver when running in JS
* Add cmd/dendritejs to pull in sqlite3_js driver for wasm only
* Update to latest go-sqlite-js version
* Replace prometheus with a stub. sigh
* Hard-code a config and don't use opentracing
* Latest go-sqlite3-js version
* Generate a key for now
* Listen for fetch traffic rather than HTTP
* Latest hacks for js
* libp2p support
* More libp2p
* Fork gjson to allow us to enforce auth checks as before
Previously, all events would come down redacted because the hash
checks would fail. They would fail because sjson.DeleteBytes didn't
remove keys not used for hashing. This didn't work because of a build
tag which included a file which no-oped the index returned.
See https://github.com/tidwall/gjson/issues/157
When it's resolved, let's go back to mainline.
* Use gjson@1.6.0 as it fixes https://github.com/tidwall/gjson/issues/157
* Use latest gomatrixserverlib for sig checks
* Fix a bug which could cause exclude_from_sync to not be set
Caused when sending events over federation.
* Use query variadic to make lookups actually work!
* Latest gomatrixserverlib
* Add notes on getting p2p up and running
Partly so I don't forget myself!
* refactor: Move p2p specific stuff to cmd/dendritejs
This is important or else the normal build of dendrite will fail
because the p2p libraries depend on syscall/js which doesn't work
on normal builds.
Also, clean up main.go to read a bit better.
* Update ho-http-js-libp2p to return errors from RoundTrip
* Add an LRU cache around the key DB
We actually need this for P2P because otherwise we can *segfault*
with things like: "runtime: unexpected return pc for runtime.handleEvent"
where the event is a `syscall/js` event, caused by spamming sql.js
caused by "Checking event signatures for 14 events of room state" which
hammers the key DB repeatedly in quick succession.
Using a cache fixes this, though the underlying cause is probably a bug
in the version of Go I'm on (1.13.7)
* breaking: Add Tracing.Enabled to toggle whether we do opentracing
Defaults to false, which is why this is a breaking change. We need
this flag because WASM builds cannot do opentracing.
* Start adding conditional builds for wasm to handle lib/pq
The general idea here is to have the wasm build have a `NewXXXDatabase`
that doesn't import any postgres package and hence we never import
`lib/pq`, which doesn't work under WASM (undefined `userCurrent`).
* Remove lib/pq for wasm for syncapi
* Add conditional building to remaining storage APIs
* Update build script to set env vars correctly for dendritejs
* sqlite bug fixes
* Docs
* Add a no-op main for dendritejs when not building under wasm
* Use the real prometheus, even for WASM
Instead, the dendrite-sw.js must mock out `process.pid` and
`fs.stat` - which must invoke the callback with an error (e.g `EINVAL`)
in order for it to work:
```
global.process = {
pid: 1,
};
global.fs.stat = function(path, cb) {
cb({
code: "EINVAL",
});
}
```
* Linting
2020-03-06 10:23:55 +00:00
|
|
|
if !config.Tracing.Enabled {
|
|
|
|
return ioutil.NopCloser(bytes.NewReader([]byte{})), nil
|
|
|
|
}
|
2017-09-28 16:00:23 +00:00
|
|
|
return config.Tracing.Jaeger.InitGlobalTracer(
|
|
|
|
serviceName,
|
|
|
|
jaegerconfig.Logger(logrusLogger{logrus.StandardLogger()}),
|
|
|
|
jaegerconfig.Metrics(jaegermetrics.NullFactory),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-05-01 12:34:53 +00:00
|
|
|
// MaxIdleConns returns maximum idle connections to the DB
|
|
|
|
func (config Dendrite) MaxIdleConns() int {
|
|
|
|
return config.Database.MaxIdleConns
|
|
|
|
}
|
|
|
|
|
|
|
|
// MaxOpenConns returns maximum open connections to the DB
|
|
|
|
func (config Dendrite) MaxOpenConns() int {
|
|
|
|
return config.Database.MaxOpenConns
|
|
|
|
}
|
|
|
|
|
|
|
|
// ConnMaxLifetime returns maximum amount of time a connection may be reused
|
|
|
|
func (config Dendrite) ConnMaxLifetime() time.Duration {
|
|
|
|
return time.Duration(config.Database.ConnMaxLifetimeSec) * time.Second
|
|
|
|
}
|
|
|
|
|
|
|
|
// DbProperties functions return properties used by database/sql/DB
|
|
|
|
type DbProperties interface {
|
|
|
|
MaxIdleConns() int
|
|
|
|
MaxOpenConns() int
|
|
|
|
ConnMaxLifetime() time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
// DbProperties returns cfg as a DbProperties interface
|
|
|
|
func (config Dendrite) DbProperties() DbProperties {
|
|
|
|
return config
|
|
|
|
}
|
|
|
|
|
2017-09-28 16:00:23 +00:00
|
|
|
// logrusLogger is a small wrapper that implements jaeger.Logger using logrus.
|
|
|
|
type logrusLogger struct {
|
|
|
|
l *logrus.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l logrusLogger) Error(msg string) {
|
|
|
|
l.l.Error(msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l logrusLogger) Infof(msg string, args ...interface{}) {
|
|
|
|
l.l.Infof(msg, args...)
|
|
|
|
}
|