1
0
Fork 0
forked from External/ergo

Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
Daniel Oaks
b4cb12a203 Rename types.go -> stringset.go for consistency 2021-09-13 06:53:58 +10:00
Daniel Oaks
e6ee9e50e0 Use our own stringset instead of menge's 2021-09-13 06:51:43 +10:00
Daniel Oaks
060d06ba6a Only stage some metadata changes for now, not the vendor update 2021-09-12 21:05:48 +10:00
Daniel Oaks
512d89ba18 Fucking Windows saving with some dodgy encoding 2021-09-11 12:05:12 +10:00
Daniel Oaks
ca98dcef55 Add metadata caps/numerics 2021-09-11 11:50:55 +10:00
13 changed files with 337 additions and 28 deletions

View file

@ -364,6 +364,50 @@ server:
# the default value of 512. DO NOT change this on a public server: # the default value of 512. DO NOT change this on a public server:
# max-line-len: 512 # 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 # account options
accounts: accounts:
# is account authentication enabled, i.e., can users log into existing accounts? # is account authentication enabled, i.e., can users log into existing accounts?

View file

@ -87,6 +87,18 @@ CAPDEFS = [
url="https://ircv3.net/specs/extensions/message-tags.html", url="https://ircv3.net/specs/extensions/message-tags.html",
standard="IRCv3", 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( CapDef(
identifier="MultiPrefix", identifier="MultiPrefix",
name="multi-prefix", name="multi-prefix",

View file

@ -7,7 +7,7 @@ package caps
const ( const (
// number of recognized capabilities: // number of recognized capabilities:
numCapabs = 28 numCapabs = 30
// length of the uint64 array that represents the bitset: // length of the uint64 array that represents the bitset:
bitsetLen = 1 bitsetLen = 1
) )
@ -61,6 +61,10 @@ const (
// https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6 // https://gist.github.com/DanielOaks/8126122f74b26012a3de37db80e4e0c6
Languages Capability = iota 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": // Multiline is the proposed IRCv3 capability named "draft/multiline":
// https://github.com/ircv3/ircv3-specifications/pull/398 // https://github.com/ircv3/ircv3-specifications/pull/398
Multiline Capability = iota Multiline Capability = iota
@ -89,6 +93,10 @@ const (
// https://ircv3.net/specs/extensions/message-tags.html // https://ircv3.net/specs/extensions/message-tags.html
MessageTags Capability = iota 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": // MultiPrefix is the IRCv3 capability named "multi-prefix":
// https://ircv3.net/specs/extensions/multi-prefix-3.1.html // https://ircv3.net/specs/extensions/multi-prefix-3.1.html
MultiPrefix Capability = iota MultiPrefix Capability = iota
@ -141,6 +149,7 @@ var (
"draft/event-playback", "draft/event-playback",
"draft/extended-monitor", "draft/extended-monitor",
"draft/languages", "draft/languages",
"draft/metadata",
"draft/multiline", "draft/multiline",
"draft/relaymsg", "draft/relaymsg",
"echo-message", "echo-message",
@ -148,6 +157,7 @@ var (
"invite-notify", "invite-notify",
"labeled-response", "labeled-response",
"message-tags", "message-tags",
"metadata-notify",
"multi-prefix", "multi-prefix",
"oragono.io/nope", "oragono.io/nope",
"sasl", "sasl",

View file

@ -174,6 +174,9 @@ type Session struct {
capState caps.State capState caps.State
capVersion caps.Version capVersion caps.Version
stateMutex sync.RWMutex // tier 1
subscribedMetadataKeys utils.StringSet
registrationMessages int registrationMessages int
zncPlaybackTimes *zncPlaybackTimes zncPlaybackTimes *zncPlaybackTimes
@ -359,6 +362,7 @@ func (server *Server) RunClient(conn IRCConn) {
proxiedIP: proxiedIP, proxiedIP: proxiedIP,
isTor: wConn.Config.Tor, isTor: wConn.Config.Tor,
hideSTS: wConn.Config.Tor || wConn.Config.HideSTS, hideSTS: wConn.Config.Tor || wConn.Config.HideSTS,
subscribedMetadataKeys: make(utils.StringSet),
} }
client.sessions = []*Session{session} client.sessions = []*Session{session}

View file

@ -178,6 +178,10 @@ func init() {
handler: lusersHandler, handler: lusersHandler,
minParams: 0, minParams: 0,
}, },
"METADATA": {
handler: metadataHandler,
minParams: 2,
},
"MODE": { "MODE": {
handler: modeHandler, handler: modeHandler,
minParams: 1, minParams: 1,

View file

@ -300,6 +300,38 @@ func (t *ThrottleConfig) UnmarshalYAML(unmarshal func(interface{}) error) (err e
return 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 { type AccountConfig struct {
Registration AccountRegistrationConfig Registration AccountRegistrationConfig
AuthenticationEnabled bool `yaml:"authentication-enabled"` AuthenticationEnabled bool `yaml:"authentication-enabled"`
@ -621,6 +653,8 @@ type Config struct {
MySQL mysql.Config MySQL mysql.Config
} }
Metadata MetadataConfig
Accounts AccountConfig Accounts AccountConfig
Channels struct { Channels struct {
@ -1228,6 +1262,26 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Server.capValues[caps.Multiline] = multilineCapValue 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: // handle legacy name 'bouncer' for 'multiclient' section:
if config.Accounts.Bouncer != nil { if config.Accounts.Bouncer != nil {
config.Accounts.Multiclient = *config.Accounts.Bouncer config.Accounts.Multiclient = *config.Accounts.Bouncer

View file

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"regexp"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"runtime/pprof" "runtime/pprof"
@ -1709,6 +1710,95 @@ func lusersHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
return false 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>...]] // MODE <target> [<modestring> [<mode arguments>...]]
func modeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool { func modeHandler(server *Server, client *Client, msg ircmsg.Message, rb *ResponseBuffer) bool {
if 0 < len(msg.Params[0]) && msg.Params[0][0] == '#' { if 0 < len(msg.Params[0]) && msg.Params[0][0] == '#' {

View file

@ -347,6 +347,14 @@ Lists all the nicknames you are currently monitoring.
MONITOR S MONITOR S
Lists whether each nick in your MONITOR list is online or offline.`, 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": { "motd": {
text: `MOTD [server] text: `MOTD [server]

4
irc/metadata.go Normal file
View file

@ -0,0 +1,4 @@
// Copyright (c) 2021 Daniel Oaks <daniel@danieloaks.net>
// released under the MIT license
package irc

View file

@ -183,6 +183,21 @@ const (
RPL_MONLIST = "732" RPL_MONLIST = "732"
RPL_ENDOFMONLIST = "733" RPL_ENDOFMONLIST = "733"
ERR_MONLISTFULL = "734" 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_LOGGEDIN = "900"
RPL_LOGGEDOUT = "901" RPL_LOGGEDOUT = "901"
ERR_NICKLOCKED = "902" ERR_NICKLOCKED = "902"

37
irc/utils/stringset.go Normal file
View 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
}

View file

@ -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{}
}

View file

@ -337,6 +337,50 @@ server:
# the default value of 512. DO NOT change this on a public server: # the default value of 512. DO NOT change this on a public server:
# max-line-len: 512 # 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 # account options
accounts: accounts:
# is account authentication enabled, i.e., can users log into existing accounts? # is account authentication enabled, i.e., can users log into existing accounts?