mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-22 18:52:41 +01:00
Merge pull request #1458 from slingamn/issue1260_channel_forward.1
fix #1260
This commit is contained in:
commit
0d1521d4c4
@ -94,6 +94,13 @@ def convert(infile):
|
||||
out['channels'][chname]['topicSetBy'] = parts[3]
|
||||
elif category == 'private:topic:ts':
|
||||
out['channels'][chname]['topicSetAt'] = to_unixnano(parts[3])
|
||||
elif category == 'private:mlockext':
|
||||
# the channel forward mode is +L on insp/unreal, +f on charybdis
|
||||
# charybdis has a +L ("large banlist") taking no argument
|
||||
# and unreal has a +f ("flood limit") taking two colon-delimited numbers,
|
||||
# so check for an argument that starts with a #
|
||||
if parts[3].startswith('L#') or parts[3].startswith('f#'):
|
||||
out['channels'][chname]['forward'] = parts[3][1:]
|
||||
elif category == 'CA':
|
||||
# channel access lists
|
||||
# CA #mychannel shivaram +AFORafhioqrstv 1600134478 shivaram
|
||||
|
@ -28,6 +28,7 @@ type Channel struct {
|
||||
flags modes.ModeSet
|
||||
lists map[modes.Mode]*UserMaskSet
|
||||
key string
|
||||
forward string
|
||||
members MemberSet
|
||||
membersCache []*Client // allow iteration over channel members without holding the lock
|
||||
name string
|
||||
@ -133,6 +134,7 @@ func (channel *Channel) applyRegInfo(chanReg RegisteredChannel) {
|
||||
channel.key = chanReg.Key
|
||||
channel.userLimit = chanReg.UserLimit
|
||||
channel.settings = chanReg.Settings
|
||||
channel.forward = chanReg.Forward
|
||||
|
||||
for _, mode := range chanReg.Modes {
|
||||
channel.flags.SetMode(mode, true)
|
||||
@ -163,6 +165,7 @@ func (channel *Channel) ExportRegistration(includeFlags uint) (info RegisteredCh
|
||||
|
||||
if includeFlags&IncludeModes != 0 {
|
||||
info.Key = channel.key
|
||||
info.Forward = channel.forward
|
||||
info.Modes = channel.flags.AllModes()
|
||||
info.UserLimit = channel.userLimit
|
||||
}
|
||||
@ -612,20 +615,27 @@ func (channel *Channel) modeStrings(client *Client) (result []string) {
|
||||
isMember := hasPrivs || channel.members[client] != nil
|
||||
showKey := isMember && (channel.key != "")
|
||||
showUserLimit := channel.userLimit > 0
|
||||
showForward := channel.forward != ""
|
||||
|
||||
mods := "+"
|
||||
var mods strings.Builder
|
||||
mods.WriteRune('+')
|
||||
|
||||
// flags with args
|
||||
if showKey {
|
||||
mods += modes.Key.String()
|
||||
mods.WriteRune(rune(modes.Key))
|
||||
}
|
||||
if showUserLimit {
|
||||
mods += modes.UserLimit.String()
|
||||
mods.WriteRune(rune(modes.UserLimit))
|
||||
}
|
||||
if showForward {
|
||||
mods.WriteRune(rune(modes.Forward))
|
||||
}
|
||||
|
||||
mods += channel.flags.String()
|
||||
for _, m := range channel.flags.AllModes() {
|
||||
mods.WriteRune(rune(m))
|
||||
}
|
||||
|
||||
result = []string{mods}
|
||||
result = []string{mods.String()}
|
||||
|
||||
// args for flags with args: The order must match above to keep
|
||||
// positional arguments in place.
|
||||
@ -635,6 +645,9 @@ func (channel *Channel) modeStrings(client *Client) (result []string) {
|
||||
if showUserLimit {
|
||||
result = append(result, strconv.Itoa(channel.userLimit))
|
||||
}
|
||||
if showForward {
|
||||
result = append(result, channel.forward)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@ -694,7 +707,7 @@ func (channel *Channel) AddHistoryItem(item history.Item, account string) (err e
|
||||
}
|
||||
|
||||
// Join joins the given client to this channel (if they can be joined).
|
||||
func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *ResponseBuffer) error {
|
||||
func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *ResponseBuffer) (joinErr error, forward string) {
|
||||
details := client.Details()
|
||||
|
||||
channel.stateMutex.RLock()
|
||||
@ -707,11 +720,12 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
||||
chcount := len(channel.members)
|
||||
_, alreadyJoined := channel.members[client]
|
||||
persistentMode := channel.accountToUMode[details.account]
|
||||
forward = channel.forward
|
||||
channel.stateMutex.RUnlock()
|
||||
|
||||
if alreadyJoined {
|
||||
// no message needs to be sent
|
||||
return nil
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
// 0. SAJOIN always succeeds
|
||||
@ -723,32 +737,33 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
||||
client.CheckInvited(chcfname, createdAt)
|
||||
if !hasPrivs {
|
||||
if limit != 0 && chcount >= limit {
|
||||
return errLimitExceeded
|
||||
return errLimitExceeded, forward
|
||||
}
|
||||
|
||||
if chkey != "" && !utils.SecretTokensMatch(chkey, key) {
|
||||
return errWrongChannelKey
|
||||
return errWrongChannelKey, forward
|
||||
}
|
||||
|
||||
if channel.flags.HasMode(modes.InviteOnly) &&
|
||||
!channel.lists[modes.InviteMask].Match(details.nickMaskCasefolded) {
|
||||
return errInviteOnly
|
||||
return errInviteOnly, forward
|
||||
}
|
||||
|
||||
if channel.lists[modes.BanMask].Match(details.nickMaskCasefolded) &&
|
||||
!channel.lists[modes.ExceptMask].Match(details.nickMaskCasefolded) &&
|
||||
!channel.lists[modes.InviteMask].Match(details.nickMaskCasefolded) {
|
||||
return errBanned
|
||||
// do not forward people who are banned:
|
||||
return errBanned, ""
|
||||
}
|
||||
|
||||
if details.account == "" &&
|
||||
(channel.flags.HasMode(modes.RegisteredOnly) || channel.server.Defcon() <= 2) {
|
||||
return errRegisteredOnly
|
||||
return errRegisteredOnly, forward
|
||||
}
|
||||
}
|
||||
|
||||
if joinErr := client.addChannel(channel, rb == nil); joinErr != nil {
|
||||
return joinErr
|
||||
return joinErr, ""
|
||||
}
|
||||
|
||||
client.server.logger.Debug("join", fmt.Sprintf("%s joined channel %s", details.nick, chname))
|
||||
@ -795,7 +810,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
||||
}
|
||||
|
||||
if rb == nil {
|
||||
return nil
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
var modestr string
|
||||
@ -858,7 +873,7 @@ func (channel *Channel) Join(client *Client, key string, isSajoin bool, rb *Resp
|
||||
rb.Flush(true)
|
||||
|
||||
channel.autoReplayHistory(client, rb, message.Msgid)
|
||||
return nil
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
func (channel *Channel) autoReplayHistory(client *Client, rb *ResponseBuffer, skipMsgid string) {
|
||||
|
@ -83,12 +83,12 @@ func (cm *ChannelManager) Get(name string) (channel *Channel) {
|
||||
}
|
||||
|
||||
// Join causes `client` to join the channel named `name`, creating it if necessary.
|
||||
func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) error {
|
||||
func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin bool, rb *ResponseBuffer) (err error, forward string) {
|
||||
server := client.server
|
||||
casefoldedName, err := CasefoldChannel(name)
|
||||
skeleton, skerr := Skeleton(name)
|
||||
if err != nil || skerr != nil || len(casefoldedName) > server.Config().Limits.ChannelLen {
|
||||
return errNoSuchChannel
|
||||
return errNoSuchChannel, ""
|
||||
}
|
||||
|
||||
channel, err := func() (*Channel, error) {
|
||||
@ -128,15 +128,15 @@ func (cm *ChannelManager) Join(client *Client, name string, key string, isSajoin
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return err, ""
|
||||
}
|
||||
|
||||
channel.EnsureLoaded()
|
||||
err = channel.Join(client, key, isSajoin, rb)
|
||||
err, forward = channel.Join(client, key, isSajoin, rb)
|
||||
|
||||
cm.maybeCleanup(channel, true)
|
||||
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
func (cm *ChannelManager) maybeCleanup(channel *Channel, afterJoin bool) {
|
||||
|
@ -35,6 +35,7 @@ const (
|
||||
keyChannelAccountToUMode = "channel.accounttoumode %s"
|
||||
keyChannelUserLimit = "channel.userlimit %s"
|
||||
keyChannelSettings = "channel.settings %s"
|
||||
keyChannelForward = "channel.forward %s"
|
||||
|
||||
keyChannelPurged = "channel.purged %s"
|
||||
)
|
||||
@ -56,6 +57,7 @@ var (
|
||||
keyChannelAccountToUMode,
|
||||
keyChannelUserLimit,
|
||||
keyChannelSettings,
|
||||
keyChannelForward,
|
||||
}
|
||||
)
|
||||
|
||||
@ -94,6 +96,8 @@ type RegisteredChannel struct {
|
||||
Modes []modes.Mode
|
||||
// Key represents the channel key / password
|
||||
Key string
|
||||
// Forward is the forwarding/overflow (+f) channel
|
||||
Forward string
|
||||
// UserLimit is the user limit (0 for no limit)
|
||||
UserLimit int
|
||||
// AccountToUMode maps user accounts to their persistent channel modes (e.g., +q, +h)
|
||||
@ -208,6 +212,7 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC
|
||||
password, _ := tx.Get(fmt.Sprintf(keyChannelPassword, channelKey))
|
||||
modeString, _ := tx.Get(fmt.Sprintf(keyChannelModes, channelKey))
|
||||
userLimitString, _ := tx.Get(fmt.Sprintf(keyChannelUserLimit, channelKey))
|
||||
forward, _ := tx.Get(fmt.Sprintf(keyChannelForward, channelKey))
|
||||
banlistString, _ := tx.Get(fmt.Sprintf(keyChannelBanlist, channelKey))
|
||||
exceptlistString, _ := tx.Get(fmt.Sprintf(keyChannelExceptlist, channelKey))
|
||||
invitelistString, _ := tx.Get(fmt.Sprintf(keyChannelInvitelist, channelKey))
|
||||
@ -249,6 +254,7 @@ func (reg *ChannelRegistry) LoadChannel(nameCasefolded string) (info RegisteredC
|
||||
AccountToUMode: accountToUMode,
|
||||
UserLimit: int(userLimit),
|
||||
Settings: settings,
|
||||
Forward: forward,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -361,6 +367,7 @@ func (reg *ChannelRegistry) saveChannel(tx *buntdb.Tx, channelInfo RegisteredCha
|
||||
modeString := modes.Modes(channelInfo.Modes).String()
|
||||
tx.Set(fmt.Sprintf(keyChannelModes, channelKey), modeString, nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelUserLimit, channelKey), strconv.Itoa(channelInfo.UserLimit), nil)
|
||||
tx.Set(fmt.Sprintf(keyChannelForward, channelKey), channelInfo.Forward, nil)
|
||||
}
|
||||
|
||||
if includeFlags&IncludeLists != 0 {
|
||||
|
@ -517,3 +517,9 @@ func (channel *Channel) SetSettings(settings ChannelSettings) {
|
||||
channel.stateMutex.Unlock()
|
||||
channel.MarkDirty(IncludeSettings)
|
||||
}
|
||||
|
||||
func (channel *Channel) setForward(forward string) {
|
||||
channel.stateMutex.Lock()
|
||||
channel.forward = forward
|
||||
channel.stateMutex.Unlock()
|
||||
}
|
||||
|
@ -1192,9 +1192,16 @@ func joinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Resp
|
||||
if len(keys) > i {
|
||||
key = keys[i]
|
||||
}
|
||||
err := server.channels.Join(client, name, key, false, rb)
|
||||
err, forward := server.channels.Join(client, name, key, false, rb)
|
||||
if err != nil {
|
||||
sendJoinError(client, name, rb, err)
|
||||
if forward != "" {
|
||||
rb.Add(nil, server.name, ERR_LINKCHANNEL, client.Nick(), utils.SafeErrorParam(name), forward, client.t("Forwarding to another channel"))
|
||||
name = forward
|
||||
err, _ = server.channels.Join(client, name, key, false, rb)
|
||||
}
|
||||
if err != nil {
|
||||
sendJoinError(client, name, rb, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
@ -1255,7 +1262,7 @@ func sajoinHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Re
|
||||
|
||||
channels := strings.Split(channelString, ",")
|
||||
for _, chname := range channels {
|
||||
err := server.channels.Join(target, chname, "", true, rb)
|
||||
err, _ := server.channels.Join(target, chname, "", true, rb)
|
||||
if err != nil {
|
||||
sendJoinError(client, chname, rb, err)
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ type channelImport struct {
|
||||
Modes string
|
||||
Key string
|
||||
Limit int
|
||||
Forward string
|
||||
}
|
||||
|
||||
type databaseImport struct {
|
||||
@ -187,6 +188,11 @@ func doImportDBGeneric(config *Config, dbImport databaseImport, credsType Creden
|
||||
if chInfo.Limit > 0 {
|
||||
tx.Set(fmt.Sprintf(keyChannelUserLimit, cfchname), strconv.Itoa(chInfo.Limit), nil)
|
||||
}
|
||||
if chInfo.Forward != "" {
|
||||
if _, err := CasefoldChannel(chInfo.Forward); err == nil {
|
||||
tx.Set(fmt.Sprintf(keyChannelForward, cfchname), chInfo.Forward, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if warnSkeletons {
|
||||
|
25
irc/modes.go
25
irc/modes.go
@ -12,6 +12,7 @@ import (
|
||||
|
||||
"github.com/oragono/oragono/irc/modes"
|
||||
"github.com/oragono/oragono/irc/sno"
|
||||
"github.com/oragono/oragono/irc/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -254,6 +255,28 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
|
||||
applied = append(applied, change)
|
||||
}
|
||||
|
||||
case modes.Forward:
|
||||
switch change.Op {
|
||||
case modes.Add:
|
||||
ch := client.server.channels.Get(change.Arg)
|
||||
if ch == nil {
|
||||
rb.Add(nil, client.server.name, ERR_INVALIDMODEPARAM, details.nick, utils.SafeErrorParam(change.Arg), fmt.Sprintf(client.t("No such channel")))
|
||||
} else if ch == channel {
|
||||
rb.Add(nil, client.server.name, ERR_INVALIDMODEPARAM, details.nick, utils.SafeErrorParam(change.Arg), fmt.Sprintf(client.t("You can't forward a channel to itself")))
|
||||
} else {
|
||||
if !ch.ClientIsAtLeast(client, modes.ChannelOperator) {
|
||||
rb.Add(nil, client.server.name, ERR_CHANOPRIVSNEEDED, details.nick, ch.Name(), client.t("You must be a channel operator in the channel you are forwarding to"))
|
||||
} else {
|
||||
change.Arg = ch.Name()
|
||||
channel.setForward(change.Arg)
|
||||
applied = append(applied, change)
|
||||
}
|
||||
}
|
||||
case modes.Remove:
|
||||
channel.setForward("")
|
||||
applied = append(applied, change)
|
||||
}
|
||||
|
||||
case modes.Key:
|
||||
switch change.Op {
|
||||
case modes.Add:
|
||||
@ -302,7 +325,7 @@ func (channel *Channel) ApplyChannelModeChanges(client *Client, isSamode bool, c
|
||||
case modes.BanMask, modes.ExceptMask, modes.InviteMask:
|
||||
includeFlags |= IncludeLists
|
||||
case modes.ChannelFounder, modes.ChannelAdmin, modes.ChannelOperator, modes.Halfop, modes.Voice:
|
||||
// these are never persisted currently, but might be in the future (see discussion on #729)
|
||||
// these are persisted on the client object, via (*Channel).applyModeToMember
|
||||
default:
|
||||
includeFlags |= IncludeModes
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ var (
|
||||
SupportedChannelModes = Modes{
|
||||
BanMask, ChanRoleplaying, ExceptMask, InviteMask, InviteOnly, Key,
|
||||
Moderated, NoOutside, OpOnlyTopic, RegisteredOnly, RegisteredOnlySpeak,
|
||||
Secret, UserLimit, NoCTCP, Auditorium, OpModerated,
|
||||
Secret, UserLimit, NoCTCP, Auditorium, OpModerated, Forward,
|
||||
}
|
||||
)
|
||||
|
||||
@ -130,6 +130,7 @@ const (
|
||||
UserLimit Mode = 'l' // flag arg
|
||||
NoCTCP Mode = 'C' // flag
|
||||
OpModerated Mode = 'U' // flag
|
||||
Forward Mode = 'f' // flag arg
|
||||
)
|
||||
|
||||
var (
|
||||
@ -277,7 +278,7 @@ func ParseChannelModeChanges(params ...string) (ModeChanges, map[rune]bool) {
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
case UserLimit:
|
||||
case UserLimit, Forward:
|
||||
// don't require value when removing
|
||||
if change.Op == Add {
|
||||
if len(params) > skipArgs {
|
||||
@ -445,7 +446,7 @@ func RplMyInfo() (param1, param2, param3 string) {
|
||||
sort.Sort(ByCodepoint(channelModes))
|
||||
|
||||
// XXX enumerate these by hand, i can't see any way to DRY this
|
||||
channelParametrizedModes := Modes{BanMask, ExceptMask, InviteMask, Key, UserLimit}
|
||||
channelParametrizedModes := Modes{BanMask, ExceptMask, InviteMask, Key, UserLimit, Forward}
|
||||
channelParametrizedModes = append(channelParametrizedModes, ChannelUserModes...)
|
||||
sort.Sort(ByCodepoint(channelParametrizedModes))
|
||||
|
||||
@ -459,7 +460,7 @@ func ChanmodesToken() (result string) {
|
||||
// type B: modes with parameters
|
||||
B := Modes{Key}
|
||||
// type C: modes that take a parameter only when set, never when unset
|
||||
C := Modes{UserLimit}
|
||||
C := Modes{UserLimit, Forward}
|
||||
// type D: modes without parameters
|
||||
D := Modes{InviteOnly, Moderated, NoOutside, OpOnlyTopic, ChanRoleplaying, Secret, NoCTCP, RegisteredOnly, RegisteredOnlySpeak, Auditorium, OpModerated}
|
||||
|
||||
|
@ -149,6 +149,7 @@ const (
|
||||
ERR_YOUWILLBEBANNED = "466"
|
||||
ERR_KEYSET = "467"
|
||||
ERR_INVALIDUSERNAME = "468"
|
||||
ERR_LINKCHANNEL = "470"
|
||||
ERR_CHANNELISFULL = "471"
|
||||
ERR_UNKNOWNMODE = "472"
|
||||
ERR_INVITEONLYCHAN = "473"
|
||||
|
2
irctest
2
irctest
@ -1 +1 @@
|
||||
Subproject commit 5aeb297de549c714ca692eda87d72b179d039ca6
|
||||
Subproject commit 40ac45cdbe8ba3165487b2af6fb9d8684ec3a297
|
Loading…
Reference in New Issue
Block a user