Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ oper-classes:
- "history" # modify or delete history messages
- "defcon" # use the DEFCON command (restrict server capabilities)
- "massmessage" # message all users on the server
- "metadata" # modify arbitrary metadata on channels and users

# ircd operators
opers:
Expand Down Expand Up @@ -1087,15 +1088,14 @@ history:
# e.g., ERGO__SERVER__MAX_SENDQ=128k. see the manual for more details.
allow-environment-overrides: true

# experimental IRC metadata support for setting key/value data on channels and nicknames.
# metadata support for setting key/value data on channels and nicknames.
metadata:
# can clients store metadata?
enabled: true
# how many keys can a client subscribe to?
# set to 0 to disable subscriptions or -1 to allow unlimited subscriptions.
# how many keys can a client subscribe to?
max-subs: 100
# how many keys can a user store about themselves? set to -1 to allow unlimited keys.
max-keys: 1000
# how many keys can be stored per entity?
max-keys: 100

# experimental support for mobile push notifications
# see the manual for potential security, privacy, and performance implications.
Expand Down
15 changes: 15 additions & 0 deletions irc/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package irc

import (
"fmt"
"iter"
"maps"
"strconv"
"strings"
Expand Down Expand Up @@ -1676,6 +1677,20 @@ func (channel *Channel) auditoriumFriends(client *Client) (friends []*Client) {
return
}

func (channel *Channel) sessionsWithCaps(capabs ...caps.Capability) iter.Seq[*Session] {
return func(yield func(*Session) bool) {
for _, member := range channel.Members() {
for _, sess := range member.Sessions() {
if sess.capabilities.HasAll(capabs...) {
if !yield(sess) {
return
}
}
}
}
}
}

// returns whether the client is visible to unprivileged users in the channel
// (i.e., respecting auditorium mode). note that this assumes that the client
// is a member; if the client is not, it may return true anyway
Expand Down
4 changes: 4 additions & 0 deletions irc/channelmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ func (cm *ChannelManager) Cleanup(channel *Channel) {
}

func (cm *ChannelManager) SetRegistered(channelName string, account string) (err error) {
if account == "" {
return errAuthRequired // this is already enforced by ChanServ, but do a final check
}

if cm.server.Defcon() <= 4 {
return errFeatureDisabled
}
Expand Down
19 changes: 10 additions & 9 deletions irc/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ type Config struct {
Enabled bool
MaxSubs int `yaml:"max-subs"`
MaxKeys int `yaml:"max-keys"`
MaxValueBytes int `yaml:"max-value-length"` // todo: currently unenforced!!
MaxValueBytes int `yaml:"max-value-length"`
}

WebPush struct {
Expand Down Expand Up @@ -1649,19 +1649,20 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Server.supportedCaps.Disable(caps.Metadata)
} else {
var metadataValues []string
if config.Metadata.MaxSubs >= 0 {
metadataValues = append(metadataValues, fmt.Sprintf("max-subs=%d", config.Metadata.MaxSubs))
// these are required for normal operation, so set sane defaults:
if config.Metadata.MaxSubs == 0 {
config.Metadata.MaxSubs = 10
}
if config.Metadata.MaxKeys > 0 {
metadataValues = append(metadataValues, fmt.Sprintf("max-keys=%d", config.Metadata.MaxKeys))
metadataValues = append(metadataValues, fmt.Sprintf("max-subs=%d", config.Metadata.MaxSubs))
if config.Metadata.MaxKeys == 0 {
config.Metadata.MaxKeys = 10
}
metadataValues = append(metadataValues, fmt.Sprintf("max-keys=%d", config.Metadata.MaxKeys))
// this is not required since we enforce a hardcoded upper bound on key+value
if config.Metadata.MaxValueBytes > 0 {
metadataValues = append(metadataValues, fmt.Sprintf("max-value-bytes=%d", config.Metadata.MaxValueBytes))
}
if len(metadataValues) != 0 {
config.Server.capValues[caps.Metadata] = strings.Join(metadataValues, ",")
}

config.Server.capValues[caps.Metadata] = strings.Join(metadataValues, ",")
}

err = config.processExtjwt()
Expand Down
1 change: 1 addition & 0 deletions irc/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ var (
errAccountVerificationInvalidCode = errors.New("Invalid account verification code")
errAccountUpdateFailed = errors.New(`Error while updating your account information`)
errAccountMustHoldNick = errors.New(`You must hold that nickname in order to register it`)
errAuthRequired = errors.New("You must be logged into an account to do this")
errAuthzidAuthcidMismatch = errors.New(`authcid and authzid must be the same`)
errCertfpAlreadyExists = errors.New(`An account already exists for your certificate fingerprint`)
errChannelNotOwnedByAccount = errors.New("Channel not owned by the specified account")
Expand Down
40 changes: 32 additions & 8 deletions irc/getters.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,7 +894,7 @@ func (channel *Channel) GetMetadata(key string) (string, bool) {
return val, ok
}

func (channel *Channel) SetMetadata(key string, value string) {
func (channel *Channel) SetMetadata(key string, value string, limit int) (updated bool, err error) {
defer channel.MarkDirty(IncludeAllAttrs)

channel.stateMutex.Lock()
Expand All @@ -904,7 +904,15 @@ func (channel *Channel) SetMetadata(key string, value string) {
channel.metadata = make(map[string]string)
}

channel.metadata[key] = value
existing, ok := channel.metadata[key]
if !ok && len(channel.metadata) >= limit {
return false, errLimitExceeded
}
updated = !ok || value != existing
if updated {
channel.metadata[key] = value
}
return updated, nil
}

func (channel *Channel) ListMetadata() map[string]string {
Expand All @@ -914,13 +922,17 @@ func (channel *Channel) ListMetadata() map[string]string {
return maps.Clone(channel.metadata)
}

func (channel *Channel) DeleteMetadata(key string) {
func (channel *Channel) DeleteMetadata(key string) (updated bool) {
defer channel.MarkDirty(IncludeAllAttrs)

channel.stateMutex.Lock()
defer channel.stateMutex.Unlock()

delete(channel.metadata, key)
_, updated = channel.metadata[key]
if updated {
delete(channel.metadata, key)
}
return updated
}

func (channel *Channel) ClearMetadata() map[string]string {
Expand Down Expand Up @@ -949,15 +961,23 @@ func (client *Client) GetMetadata(key string) (string, bool) {
return val, ok
}

func (client *Client) SetMetadata(key string, value string) {
func (client *Client) SetMetadata(key string, value string, limit int) (updated bool, err error) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()

if client.metadata == nil {
client.metadata = make(map[string]string)
}

client.metadata[key] = value
existing, ok := client.metadata[key]
if !ok && len(client.metadata) >= limit {
return false, errLimitExceeded
}
updated = !ok || value != existing
if updated {
client.metadata[key] = value
}
return updated, nil
}

func (client *Client) ListMetadata() map[string]string {
Expand All @@ -967,11 +987,15 @@ func (client *Client) ListMetadata() map[string]string {
return maps.Clone(client.metadata)
}

func (client *Client) DeleteMetadata(key string) {
func (client *Client) DeleteMetadata(key string) (updated bool) {
client.stateMutex.Lock()
defer client.stateMutex.Unlock()

delete(client.metadata, key)
_, updated = client.metadata[key]
if updated {
delete(client.metadata, key)
}
return updated
}

func (client *Client) ClearMetadata() map[string]string {
Expand Down
Loading