forked from External/ergo
Compare commits
5 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4cb12a203 | ||
|
|
e6ee9e50e0 | ||
|
|
060d06ba6a | ||
|
|
512d89ba18 | ||
|
|
ca98dcef55 |
13 changed files with 337 additions and 28 deletions
44
default.yaml
44
default.yaml
|
|
@ -364,6 +364,50 @@ server:
|
|||
# the default value of 512. DO NOT change this on a public server:
|
||||
# max-line-len: 512
|
||||
|
||||
# metadata options
|
||||
metadata:
|
||||
# maximum number of keys users can have set.
|
||||
# this excludes admin-set keys which users cannot set or view
|
||||
max-keys: 16
|
||||
|
||||
# maximum number of keys users can subscribe to at one time.
|
||||
# opers can subscribe to more than this though
|
||||
max-subs: 16
|
||||
|
||||
users:
|
||||
# uncomment this if you ONLY want these keys to be settable
|
||||
# allowed-keys:
|
||||
# - avatar
|
||||
# - color
|
||||
# - display-name
|
||||
# - homepage
|
||||
# - status
|
||||
|
||||
# uncomment this if you want all BUT these keys to be settable
|
||||
# blocked-keys:
|
||||
# - example-bad-key
|
||||
# - another-bad-example
|
||||
|
||||
# these keys are restricted to opers
|
||||
restricted-keys:
|
||||
- admin:*
|
||||
- server:*
|
||||
|
||||
channels:
|
||||
# uncomment this if you ONLY want these keys to be settable
|
||||
# allowed-keys:
|
||||
# - url
|
||||
|
||||
# uncomment this if you want all BUT these keys to be settable
|
||||
# blocked-keys:
|
||||
# - example-bad-key
|
||||
# - another-bad-example
|
||||
|
||||
# these keys are restricted to opers
|
||||
restricted-keys:
|
||||
- admin:*
|
||||
- server:*
|
||||
|
||||
# account options
|
||||
accounts:
|
||||
# is account authentication enabled, i.e., can users log into existing accounts?
|
||||
|
|
|
|||
|
|
@ -87,6 +87,18 @@ CAPDEFS = [
|
|||
url="https://ircv3.net/specs/extensions/message-tags.html",
|
||||
standard="IRCv3",
|
||||
),
|
||||
CapDef(
|
||||
identifier="Metadata",
|
||||
name="draft/metadata",
|
||||
url="https://github.com/DanielOaks/ircv3-specifications/blob/metadata/core/metadata.md",
|
||||
standard="draft IRCv3",
|
||||
),
|
||||
CapDef(
|
||||
identifier="MetadataNotify",
|
||||
name="metadata-notify",
|
||||
url="https://github.com/DanielOaks/ircv3-specifications/blob/metadata/core/metadata.md",
|
||||
standard="draft IRCv3",
|
||||
),
|
||||
CapDef(
|
||||
identifier="MultiPrefix",
|
||||
name="multi-prefix",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ package caps
|
|||
|
||||
const (
|
||||
// number of recognized capabilities:
|
||||
numCapabs = 28
|
||||
numCapabs = 30
|
||||
// length of the uint64 array that represents the bitset:
|
||||
bitsetLen = 1
|
||||
)
|
||||
|
|
@ -61,6 +61,10 @@ const (
|
|||
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
|
||||
Languages Capability = iota
|
||||
|
||||
// Metadata is the draft IRCv3 capability named "draft/metadata":
|
||||
// https://github.com/DanielOaks/ircv3-specifications/blob/metadata/core/metadata.md
|
||||
Metadata Capability = iota
|
||||
|
||||
// Multiline is the proposed IRCv3 capability named "draft/multiline":
|
||||
// https://github.com/ircv3/ircv3-specifications/pull/398
|
||||
Multiline Capability = iota
|
||||
|
|
@ -89,6 +93,10 @@ const (
|
|||
// https://ircv3.net/specs/extensions/message-tags.html
|
||||
MessageTags Capability = iota
|
||||
|
||||
// MetadataNotify is the draft IRCv3 capability named "metadata-notify":
|
||||
// https://github.com/DanielOaks/ircv3-specifications/blob/metadata/core/metadata.md
|
||||
MetadataNotify Capability = iota
|
||||
|
||||
// MultiPrefix is the IRCv3 capability named "multi-prefix":
|
||||
// https://ircv3.net/specs/extensions/multi-prefix-3.1.html
|
||||
MultiPrefix Capability = iota
|
||||
|
|
@ -141,6 +149,7 @@ var (
|
|||
"draft/event-playback",
|
||||
"draft/extended-monitor",
|
||||
"draft/languages",
|
||||
"draft/metadata",
|
||||
"draft/multiline",
|
||||
"draft/relaymsg",
|
||||
"echo-message",
|
||||
|
|
@ -148,6 +157,7 @@ var (
|
|||
"invite-notify",
|
||||
"labeled-response",
|
||||
"message-tags",
|
||||
"metadata-notify",
|
||||
"multi-prefix",
|
||||
"oragono.io/nope",
|
||||
"sasl",
|
||||
|
|
|
|||
|
|
@ -174,6 +174,9 @@ type Session struct {
|
|||
capState caps.State
|
||||
capVersion caps.Version
|
||||
|
||||
stateMutex sync.RWMutex // tier 1
|
||||
subscribedMetadataKeys utils.StringSet
|
||||
|
||||
registrationMessages int
|
||||
|
||||
zncPlaybackTimes *zncPlaybackTimes
|
||||
|
|
@ -349,16 +352,17 @@ func (server *Server) RunClient(conn IRCConn) {
|
|||
}
|
||||
client.history.Initialize(config.History.ClientLength, time.Duration(config.History.AutoresizeWindow))
|
||||
session := &Session{
|
||||
client: client,
|
||||
socket: socket,
|
||||
capVersion: caps.Cap301,
|
||||
capState: caps.NoneState,
|
||||
ctime: now,
|
||||
lastActive: now,
|
||||
realIP: realIP,
|
||||
proxiedIP: proxiedIP,
|
||||
isTor: wConn.Config.Tor,
|
||||
hideSTS: wConn.Config.Tor || wConn.Config.HideSTS,
|
||||
client: client,
|
||||
socket: socket,
|
||||
capVersion: caps.Cap301,
|
||||
capState: caps.NoneState,
|
||||
ctime: now,
|
||||
lastActive: now,
|
||||
realIP: realIP,
|
||||
proxiedIP: proxiedIP,
|
||||
isTor: wConn.Config.Tor,
|
||||
hideSTS: wConn.Config.Tor || wConn.Config.HideSTS,
|
||||
subscribedMetadataKeys: make(utils.StringSet),
|
||||
}
|
||||
client.sessions = []*Session{session}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,6 +178,10 @@ func init() {
|
|||
handler: lusersHandler,
|
||||
minParams: 0,
|
||||
},
|
||||
"METADATA": {
|
||||
handler: metadataHandler,
|
||||
minParams: 2,
|
||||
},
|
||||
"MODE": {
|
||||
handler: modeHandler,
|
||||
minParams: 1,
|
||||
|
|
|
|||
|
|
@ -300,6 +300,38 @@ func (t *ThrottleConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err e
|
|||
return
|
||||
}
|
||||
|
||||
type MetadataKeyConfig struct {
|
||||
AllowedKeys []string `yaml:"allowed-keys"`
|
||||
AllowedKeysMatcher *regexp.Regexp
|
||||
BlockedKeys []string `yaml:"blocked-keys"`
|
||||
BlockedKeysMatcher *regexp.Regexp
|
||||
RestrictedKeys []string `yaml:"restricted-keys"`
|
||||
RestrictedKeysMatcher *regexp.Regexp
|
||||
}
|
||||
|
||||
func (mkc *MetadataKeyConfig) compileMatchers() (err error) {
|
||||
mkc.AllowedKeysMatcher, err = utils.CompileMasks(mkc.AllowedKeys)
|
||||
if err != nil {
|
||||
err = errors.New("allowed-keys")
|
||||
}
|
||||
mkc.BlockedKeysMatcher, err = utils.CompileMasks(mkc.BlockedKeys)
|
||||
if err != nil {
|
||||
err = errors.New("blocked-keys")
|
||||
}
|
||||
mkc.RestrictedKeysMatcher, err = utils.CompileMasks(mkc.RestrictedKeys)
|
||||
if err != nil {
|
||||
err = errors.New("restricted-keys")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
type MetadataConfig struct {
|
||||
MaxKeys int `yaml:"max-keys"`
|
||||
MaxSubs int `yaml:"max-subs"`
|
||||
Users MetadataKeyConfig
|
||||
Channels MetadataKeyConfig
|
||||
}
|
||||
|
||||
type AccountConfig struct {
|
||||
Registration AccountRegistrationConfig
|
||||
AuthenticationEnabled bool `yaml:"authentication-enabled"`
|
||||
|
|
@ -621,6 +653,8 @@ type Config struct {
|
|||
MySQL mysql.Config
|
||||
}
|
||||
|
||||
Metadata MetadataConfig
|
||||
|
||||
Accounts AccountConfig
|
||||
|
||||
Channels struct {
|
||||
|
|
@ -1228,6 +1262,26 @@ func LoadConfig(filename string) (config *Config, err error) {
|
|||
config.Server.capValues[caps.Multiline] = multilineCapValue
|
||||
}
|
||||
|
||||
// confirm that we don't have both allowed and blocked metadata keys set
|
||||
config.Server.capValues[caps.Metadata] = fmt.Sprintf("maxsub=%d,maxkey=%d", config.Metadata.MaxSubs, config.Metadata.MaxKeys)
|
||||
|
||||
if len(config.Metadata.Users.AllowedKeys) > 0 && len(config.Metadata.Users.BlockedKeys) > 0 {
|
||||
return nil, errors.New("You can only set either allowed-keys or blocked-keys in metadata.users, not both")
|
||||
}
|
||||
if len(config.Metadata.Channels.AllowedKeys) > 0 && len(config.Metadata.Channels.BlockedKeys) > 0 {
|
||||
return nil, errors.New("You can only set either allowed-keys or blocked-keys in metadata.channels, not both")
|
||||
}
|
||||
|
||||
err = config.Metadata.Users.compileMatchers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not compile metadata.users.%s", err.Error())
|
||||
}
|
||||
|
||||
err = config.Metadata.Channels.compileMatchers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Could not compile metadata.channels.%s", err.Error())
|
||||
}
|
||||
|
||||
// handle legacy name 'bouncer' for 'multiclient' section:
|
||||
if config.Accounts.Bouncer != nil {
|
||||
config.Accounts.Multiclient = *config.Accounts.Bouncer
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"runtime/pprof"
|
||||
|
|
@ -1709,6 +1710,95 @@ func lusersHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
|
|||
return false
|
||||
}
|
||||
|
||||
// METADATA <Target> <Subcommand> [<Param 1> ... [<Param n>]]
|
||||
func metadataHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
|
||||
// target := msg.Params[0]
|
||||
subCommand := strings.ToUpper(msg.Params[1])
|
||||
|
||||
config := server.Config().Metadata
|
||||
|
||||
//TODO: do this once when the server boots?
|
||||
keyNameMatcher, _ := regexp.Compile(`^[a-z0-9_.-][a-z0-9_.\-:]*$`)
|
||||
|
||||
switch subCommand {
|
||||
// these subcommands affect the client itself, or other targets
|
||||
//
|
||||
|
||||
// these subcommands affect the current session
|
||||
//
|
||||
case "SUB":
|
||||
var addedKeys []string
|
||||
|
||||
rb.session.stateMutex.Lock()
|
||||
defer rb.session.stateMutex.Unlock()
|
||||
for i, key := range msg.Params {
|
||||
if i < 2 {
|
||||
// skip target and subcommand
|
||||
continue
|
||||
}
|
||||
|
||||
if rb.session.subscribedMetadataKeys.Size() > config.MaxSubs {
|
||||
rb.Add(nil, server.name, ERR_METADATATOOMANYSUBS, client.nick, key)
|
||||
break
|
||||
}
|
||||
|
||||
if !keyNameMatcher.MatchString(key) {
|
||||
rb.Add(nil, server.name, ERR_KEYINVALID, client.nick, key)
|
||||
continue
|
||||
}
|
||||
|
||||
// see if key is restricted
|
||||
if client.Oper() == nil && (config.Users.RestrictedKeysMatcher.MatchString(key) || config.Channels.RestrictedKeysMatcher.MatchString(key)) {
|
||||
rb.Add(nil, server.name, ERR_KEYNOPERMISSION, client.nick, "*", key, "permission denied")
|
||||
// still let the user subscribe to the key, don't continue on to the next one
|
||||
}
|
||||
|
||||
addedKeys = append(addedKeys, key)
|
||||
rb.session.subscribedMetadataKeys.Add(key)
|
||||
}
|
||||
|
||||
if len(addedKeys) > 0 {
|
||||
rb.Add(nil, server.name, RPL_METADATASUBOK, client.nick, strings.Join(addedKeys, " "))
|
||||
}
|
||||
rb.Add(nil, server.name, RPL_METADATAEND, client.nick, "end of metadata")
|
||||
|
||||
case "UNSUB":
|
||||
var removedKeys []string
|
||||
|
||||
rb.session.stateMutex.Lock()
|
||||
defer rb.session.stateMutex.Unlock()
|
||||
for i, key := range msg.Params {
|
||||
if i < 2 {
|
||||
// skip target and subcommand
|
||||
continue
|
||||
}
|
||||
|
||||
if !keyNameMatcher.MatchString(key) {
|
||||
rb.Add(nil, server.name, ERR_KEYINVALID, client.nick, key)
|
||||
continue
|
||||
}
|
||||
|
||||
removedKeys = append(removedKeys, key)
|
||||
rb.session.subscribedMetadataKeys.Remove(key)
|
||||
}
|
||||
|
||||
if len(removedKeys) > 0 {
|
||||
rb.Add(nil, server.name, RPL_METADATAUNSUBOK, client.nick, strings.Join(removedKeys, " "))
|
||||
}
|
||||
rb.Add(nil, server.name, RPL_METADATAEND, client.nick, "end of metadata")
|
||||
|
||||
case "SUBS":
|
||||
rb.session.stateMutex.RLock()
|
||||
defer rb.session.stateMutex.RUnlock()
|
||||
if rb.session.subscribedMetadataKeys.Size() > 0 {
|
||||
//TODO: loop and return subscriptions with multiple numerics if we need to
|
||||
rb.Add(nil, server.name, RPL_METADATASUBS, client.nick, strings.Join(rb.session.subscribedMetadataKeys.Keys(), " "))
|
||||
}
|
||||
rb.Add(nil, server.name, RPL_METADATAEND, client.nick, "end of metadata")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MODE <target> [<modestring> [<mode arguments>...]]
|
||||
func modeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
|
||||
if 0 < len(msg.Params[0]) && msg.Params[0][0] == '#' {
|
||||
|
|
|
|||
|
|
@ -347,6 +347,14 @@ Lists all the nicknames you are currently monitoring.
|
|||
|
||||
MONITOR S
|
||||
Lists whether each nick in your MONITOR list is online or offline.`,
|
||||
},
|
||||
"metadata": {
|
||||
text: `METADATA <target> <subcmd>
|
||||
|
||||
Lets you add metadata to yourself and channels, as well as subscribe
|
||||
to metadata changes. The subcommands are:
|
||||
|
||||
//TODO`,
|
||||
},
|
||||
"motd": {
|
||||
text: `MOTD [server]
|
||||
|
|
|
|||
4
irc/metadata.go
Normal file
4
irc/metadata.go
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) 2021 Daniel Oaks <daniel@danieloaks.net>
|
||||
// released under the MIT license
|
||||
|
||||
package irc
|
||||
|
|
@ -183,6 +183,21 @@ const (
|
|||
RPL_MONLIST = "732"
|
||||
RPL_ENDOFMONLIST = "733"
|
||||
ERR_MONLISTFULL = "734"
|
||||
RPL_WHOISKEYVALUE = "760"
|
||||
RPL_KEYVALUE = "761"
|
||||
RPL_METADATAEND = "762"
|
||||
ERR_METADATALIMIT = "764"
|
||||
ERR_TARGETINVALID = "765"
|
||||
ERR_NOMATCHINGKEY = "766"
|
||||
ERR_KEYINVALID = "767"
|
||||
ERR_KEYNOTSET = "768"
|
||||
ERR_KEYNOPERMISSION = "769"
|
||||
RPL_METADATASUBOK = "770"
|
||||
RPL_METADATAUNSUBOK = "771"
|
||||
RPL_METADATASUBS = "772"
|
||||
ERR_METADATATOOMANYSUBS = "773"
|
||||
ERR_METADATASYNCLATER = "774"
|
||||
ERR_METADATARATELIMIT = "775"
|
||||
RPL_LOGGEDIN = "900"
|
||||
RPL_LOGGEDOUT = "901"
|
||||
ERR_NICKLOCKED = "902"
|
||||
|
|
|
|||
37
irc/utils/stringset.go
Normal file
37
irc/utils/stringset.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) 2020 Shivaram Lingamneni
|
||||
// Copyright (c) 2021 Daniel Oaks
|
||||
// released under the MIT license
|
||||
|
||||
package utils
|
||||
|
||||
type empty struct{}
|
||||
|
||||
type StringSet map[string]empty
|
||||
|
||||
func (s StringSet) Has(str string) bool {
|
||||
_, ok := s[str]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s StringSet) Add(str string) {
|
||||
s[str] = empty{}
|
||||
}
|
||||
|
||||
func (s StringSet) Remove(str string) {
|
||||
_, ok := s[str]
|
||||
if ok {
|
||||
delete(s, str)
|
||||
}
|
||||
}
|
||||
|
||||
func (s StringSet) Size() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s StringSet) Keys() (keys []string) {
|
||||
for key := range s {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
// Copyright (c) 2020 Shivaram Lingamneni
|
||||
// released under the MIT license
|
||||
|
||||
package utils
|
||||
|
||||
type empty struct{}
|
||||
|
||||
type StringSet map[string]empty
|
||||
|
||||
func (s StringSet) Has(str string) bool {
|
||||
_, ok := s[str]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (s StringSet) Add(str string) {
|
||||
s[str] = empty{}
|
||||
}
|
||||
|
|
@ -337,6 +337,50 @@ server:
|
|||
# the default value of 512. DO NOT change this on a public server:
|
||||
# max-line-len: 512
|
||||
|
||||
# metadata options
|
||||
metadata:
|
||||
# maximum number of keys users can have set.
|
||||
# this excludes admin-set keys which users cannot set or view
|
||||
max-keys: 16
|
||||
|
||||
# maximum number of keys users can subscribe to at one time.
|
||||
# opers can subscribe to more than this though
|
||||
max-subs: 16
|
||||
|
||||
users:
|
||||
# uncomment this if you ONLY want these keys to be settable
|
||||
# allowed-keys:
|
||||
# - avatar
|
||||
# - color
|
||||
# - display-name
|
||||
# - homepage
|
||||
# - status
|
||||
|
||||
# uncomment this if you want all BUT these keys to be settable
|
||||
# blocked-keys:
|
||||
# - example-bad-key
|
||||
# - another-bad-example
|
||||
|
||||
# these keys are restricted to opers
|
||||
restricted-keys:
|
||||
- admin:*
|
||||
- server:*
|
||||
|
||||
channels:
|
||||
# uncomment this if you ONLY want these keys to be settable
|
||||
# allowed-keys:
|
||||
# - url
|
||||
|
||||
# uncomment this if you want all BUT these keys to be settable
|
||||
# blocked-keys:
|
||||
# - example-bad-key
|
||||
# - another-bad-example
|
||||
|
||||
# these keys are restricted to opers
|
||||
restricted-keys:
|
||||
- admin:*
|
||||
- server:*
|
||||
|
||||
# account options
|
||||
accounts:
|
||||
# is account authentication enabled, i.e., can users log into existing accounts?
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue