2016-06-15 13:50:56 +02:00
// Copyright (c) 2012-2014 Jeremy Latt
// Copyright (c) 2014-2015 Edmund Huber
2017-03-27 14:15:02 +02:00
// Copyright (c) 2016-2017 Daniel Oaks <daniel@danieloaks.net>
2016-06-15 13:50:56 +02:00
// released under the MIT license
2012-12-09 07:54:58 +01:00
package irc
2014-02-22 21:49:33 +01:00
import (
2016-10-11 15:51:46 +02:00
"fmt"
2014-02-22 21:49:33 +01:00
"strconv"
2019-01-02 23:52:36 +01:00
"strings"
2016-06-26 13:06:28 +02:00
"time"
2016-10-16 04:54:15 +02:00
2017-01-10 17:09:08 +01:00
"sync"
2021-06-18 08:41:57 +02:00
"github.com/ergochat/irc-go/ircutils"
2021-03-18 08:49:12 +01:00
2021-05-25 06:34:38 +02:00
"github.com/ergochat/ergo/irc/caps"
2023-01-04 11:06:21 +01:00
"github.com/ergochat/ergo/irc/datastore"
2021-05-25 06:34:38 +02:00
"github.com/ergochat/ergo/irc/history"
"github.com/ergochat/ergo/irc/modes"
"github.com/ergochat/ergo/irc/utils"
2017-11-09 04:19:50 +01:00
)
2020-02-19 01:38:42 +01:00
type ChannelSettings struct {
2021-01-21 03:13:18 +01:00
History HistoryStatus
QueryCutoff HistoryCutoff
2020-02-19 01:38:42 +01:00
}
2017-04-16 03:31:33 +02:00
// Channel represents a channel that clients can join.
2012-12-09 07:54:58 +01:00
type Channel struct {
2019-03-12 00:24:45 +01:00
flags modes . ModeSet
2018-02-03 11:21:32 +01:00
lists map [ modes . Mode ] * UserMaskSet
2017-11-07 20:38:18 +01:00
key string
2020-12-14 11:00:21 +01:00
forward string
2017-11-07 20:38:18 +01:00
members MemberSet
name string
nameCasefolded string
server * Server
createdTime time . Time
2017-11-09 04:19:50 +01:00
registeredFounder string
registeredTime time . Time
2019-12-17 01:50:15 +01:00
transferPendingTo string
2017-11-07 20:38:18 +01:00
topic string
topicSetBy string
topicSetTime time . Time
2018-12-28 19:45:55 +01:00
userLimit int
2018-04-04 03:49:40 +02:00
accountToUMode map [ string ] modes . Mode
2018-11-26 11:23:27 +01:00
history history . Buffer
2022-09-02 10:00:38 +02:00
stateMutex sync . RWMutex // tier 1
writebackLock sync . Mutex // tier 1.5
joinPartMutex sync . Mutex // tier 3
2019-03-12 00:24:45 +01:00
dirtyBits uint
2020-02-19 01:38:42 +01:00
settings ChannelSettings
2023-01-04 11:06:21 +01:00
uuid utils . UUID
2023-04-14 08:15:56 +02:00
// these caches are paired to allow iteration over channel members without holding the lock
membersCache [ ] * Client
memberDataCache [ ] * memberData
2012-12-09 07:54:58 +01:00
}
2014-02-05 04:28:24 +01:00
// NewChannel creates a new channel from a `Server` and a `name`
// string, which must be unique on the server.
2023-01-04 11:06:21 +01:00
func NewChannel ( s * Server , name , casefoldedName string , registered bool , regInfo RegisteredChannel ) * Channel {
2019-12-17 01:50:15 +01:00
config := s . Config ( )
2016-10-11 15:51:46 +02:00
2012-12-15 23:34:20 +01:00
channel := & Channel {
2022-09-02 10:00:38 +02:00
createdTime : time . Now ( ) . UTC ( ) , // may be overwritten by applyRegInfo
members : make ( MemberSet ) ,
name : name ,
nameCasefolded : casefoldedName ,
server : s ,
2012-12-09 21:51:50 +01:00
}
2014-02-25 20:11:34 +01:00
2019-12-17 01:50:15 +01:00
channel . initializeLists ( )
2020-02-19 01:38:42 +01:00
channel . history . Initialize ( 0 , 0 )
2019-03-12 00:24:45 +01:00
2023-01-04 11:06:21 +01:00
if registered {
channel . applyRegInfo ( regInfo )
} else {
2020-02-19 01:38:42 +01:00
channel . resizeHistory ( config )
2018-11-26 11:23:27 +01:00
for _ , mode := range config . Channels . defaultModes {
2018-04-23 00:47:10 +02:00
channel . flags . SetMode ( mode , true )
2016-04-21 11:29:50 +02:00
}
2023-01-04 11:06:21 +01:00
channel . uuid = utils . GenerateUUIDv4 ( )
}
2018-11-26 11:23:27 +01:00
2012-12-15 23:34:20 +01:00
return channel
2012-12-09 07:54:58 +01:00
}
2019-12-17 01:50:15 +01:00
func ( channel * Channel ) initializeLists ( ) {
channel . lists = map [ modes . Mode ] * UserMaskSet {
modes . BanMask : NewUserMaskSet ( ) ,
modes . ExceptMask : NewUserMaskSet ( ) ,
modes . InviteMask : NewUserMaskSet ( ) ,
}
channel . accountToUMode = make ( map [ string ] modes . Mode )
}
2020-02-19 01:38:42 +01:00
func ( channel * Channel ) resizeHistory ( config * Config ) {
2021-01-21 03:13:18 +01:00
status , _ , _ := channel . historyStatus ( config )
2020-02-24 20:09:00 +01:00
if status == HistoryEphemeral {
2020-05-19 13:57:44 +02:00
channel . history . Resize ( config . History . ChannelLength , time . Duration ( config . History . AutoresizeWindow ) )
2020-02-19 01:38:42 +01:00
} else {
channel . history . Resize ( 0 , 0 )
}
}
2017-11-09 04:19:50 +01:00
// read in channel state that was persisted in the DB
2019-03-12 00:24:45 +01:00
func ( channel * Channel ) applyRegInfo ( chanReg RegisteredChannel ) {
2020-02-19 01:38:42 +01:00
defer channel . resizeHistory ( channel . server . Config ( ) )
2019-03-12 00:24:45 +01:00
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
2023-01-04 11:06:21 +01:00
channel . uuid = chanReg . UUID
2017-11-09 04:19:50 +01:00
channel . registeredFounder = chanReg . Founder
channel . registeredTime = chanReg . RegisteredAt
channel . topic = chanReg . Topic
channel . topicSetBy = chanReg . TopicSetBy
channel . topicSetTime = chanReg . TopicSetTime
channel . name = chanReg . Name
channel . createdTime = chanReg . RegisteredAt
2018-04-04 03:49:40 +02:00
channel . key = chanReg . Key
2020-01-08 08:14:41 +01:00
channel . userLimit = chanReg . UserLimit
2020-02-19 01:38:42 +01:00
channel . settings = chanReg . Settings
2020-12-14 11:00:21 +01:00
channel . forward = chanReg . Forward
2018-04-04 03:49:40 +02:00
for _ , mode := range chanReg . Modes {
2018-04-23 00:47:10 +02:00
channel . flags . SetMode ( mode , true )
2018-04-04 03:49:40 +02:00
}
for account , mode := range chanReg . AccountToUMode {
channel . accountToUMode [ account ] = mode
}
2019-10-10 10:17:44 +02:00
channel . lists [ modes . BanMask ] . SetMasks ( chanReg . Bans )
channel . lists [ modes . InviteMask ] . SetMasks ( chanReg . Invites )
channel . lists [ modes . ExceptMask ] . SetMasks ( chanReg . Excepts )
2017-11-09 04:19:50 +01:00
}
// obtain a consistent snapshot of the channel state that can be persisted to the DB
2023-01-04 11:06:21 +01:00
func ( channel * Channel ) ExportRegistration ( ) ( info RegisteredChannel ) {
2017-11-09 04:19:50 +01:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
info . Name = channel . name
2023-01-04 11:06:21 +01:00
info . UUID = channel . uuid
2017-11-09 04:19:50 +01:00
info . Founder = channel . registeredFounder
info . RegisteredAt = channel . registeredTime
2023-01-04 11:06:21 +01:00
info . Topic = channel . topic
info . TopicSetBy = channel . topicSetBy
info . TopicSetTime = channel . topicSetTime
2018-04-04 03:49:40 +02:00
2023-01-04 11:06:21 +01:00
info . Key = channel . key
info . Forward = channel . forward
info . Modes = channel . flags . AllModes ( )
info . UserLimit = channel . userLimit
2018-04-04 03:49:40 +02:00
2023-01-04 11:06:21 +01:00
info . Bans = channel . lists [ modes . BanMask ] . Masks ( )
info . Invites = channel . lists [ modes . InviteMask ] . Masks ( )
info . Excepts = channel . lists [ modes . ExceptMask ] . Masks ( )
info . AccountToUMode = utils . CopyMap ( channel . accountToUMode )
2017-11-09 04:19:50 +01:00
2023-01-04 11:06:21 +01:00
info . Settings = channel . settings
return
}
func ( channel * Channel ) exportSummary ( ) ( info RegisteredChannel ) {
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
info . Name = channel . name
info . Founder = channel . registeredFounder
info . RegisteredAt = channel . registeredTime
2020-02-19 01:38:42 +01:00
2017-11-09 04:19:50 +01:00
return
}
2019-03-12 00:24:45 +01:00
// begin: asynchronous database writeback implementation, modeled on irc/socket.go
// MarkDirty marks part (or all) of a channel's data as needing to be written back
// to the database, then starts a writer goroutine if necessary.
// This is the equivalent of Socket.Write().
func ( channel * Channel ) MarkDirty ( dirtyBits uint ) {
channel . stateMutex . Lock ( )
isRegistered := channel . registeredFounder != ""
channel . dirtyBits = channel . dirtyBits | dirtyBits
channel . stateMutex . Unlock ( )
if ! isRegistered {
return
}
channel . wakeWriter ( )
}
// IsClean returns whether a channel can be safely removed from the server.
// To avoid the obvious TOCTOU race condition, it must be called while holding
// ChannelManager's lock (that way, no one can join and make the channel dirty again
// between this method exiting and the actual deletion).
func ( channel * Channel ) IsClean ( ) bool {
2022-09-02 10:00:38 +02:00
if ! channel . writebackLock . TryLock ( ) {
2019-03-12 00:24:45 +01:00
// a database write (which may fail) is in progress, the channel cannot be cleaned up
return false
}
2022-09-02 10:00:38 +02:00
defer channel . writebackLock . Unlock ( )
2019-03-12 00:24:45 +01:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2020-03-19 12:26:17 +01:00
if len ( channel . members ) != 0 {
return false
}
2021-02-04 21:26:03 +01:00
// see #1507 and #704 among others; registered channels should never be removed
return channel . registeredFounder == ""
2019-03-12 00:24:45 +01:00
}
func ( channel * Channel ) wakeWriter ( ) {
2022-09-02 10:00:38 +02:00
if channel . writebackLock . TryLock ( ) {
2019-03-12 00:24:45 +01:00
go channel . writeLoop ( )
}
}
// equivalent of Socket.send()
func ( channel * Channel ) writeLoop ( ) {
for {
// TODO(#357) check the error value of this and implement timed backoff
channel . performWrite ( 0 )
2022-09-02 10:00:38 +02:00
channel . writebackLock . Unlock ( )
2019-03-12 00:24:45 +01:00
channel . stateMutex . RLock ( )
isDirty := channel . dirtyBits != 0
isEmpty := len ( channel . members ) == 0
channel . stateMutex . RUnlock ( )
if ! isDirty {
if isEmpty {
channel . server . channels . Cleanup ( channel )
}
return // nothing to do
} // else: isDirty, so we need to write again
2022-09-02 10:00:38 +02:00
if ! channel . writebackLock . TryLock ( ) {
2019-03-12 00:24:45 +01:00
return
}
}
}
// Store writes part (or all) of the channel's data back to the database,
// blocking until the write is complete. This is the equivalent of
// Socket.BlockingWrite.
func ( channel * Channel ) Store ( dirtyBits uint ) ( err error ) {
defer func ( ) {
channel . stateMutex . Lock ( )
isDirty := channel . dirtyBits != 0
isEmpty := len ( channel . members ) == 0
channel . stateMutex . Unlock ( )
if isDirty {
channel . wakeWriter ( )
} else if isEmpty {
channel . server . channels . Cleanup ( channel )
}
} ( )
2022-09-02 10:00:38 +02:00
channel . writebackLock . Lock ( )
defer channel . writebackLock . Unlock ( )
2019-03-12 00:24:45 +01:00
return channel . performWrite ( dirtyBits )
}
// do an individual write; equivalent of Socket.send()
func ( channel * Channel ) performWrite ( additionalDirtyBits uint ) ( err error ) {
channel . stateMutex . Lock ( )
dirtyBits := channel . dirtyBits | additionalDirtyBits
channel . dirtyBits = 0
isRegistered := channel . registeredFounder != ""
channel . stateMutex . Unlock ( )
if ! isRegistered || dirtyBits == 0 {
return
}
2023-01-04 11:06:21 +01:00
var success bool
info := channel . ExportRegistration ( )
if b , err := info . Serialize ( ) ; err == nil {
if err := channel . server . dstore . Set ( datastore . TableChannels , info . UUID , b , time . Time { } ) ; err == nil {
success = true
} else {
channel . server . logger . Error ( "internal" , "couldn't persist channel" , info . Name , err . Error ( ) )
}
} else {
channel . server . logger . Error ( "internal" , "couldn't serialize channel" , info . Name , err . Error ( ) )
}
if ! success {
2019-03-12 00:24:45 +01:00
channel . stateMutex . Lock ( )
channel . dirtyBits = channel . dirtyBits | dirtyBits
channel . stateMutex . Unlock ( )
}
return
}
2017-11-09 04:19:50 +01:00
// SetRegistered registers the channel, returning an error if it was already registered.
func ( channel * Channel ) SetRegistered ( founder string ) error {
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
if channel . registeredFounder != "" {
2018-02-03 13:03:36 +01:00
return errChannelAlreadyRegistered
2017-11-09 04:19:50 +01:00
}
channel . registeredFounder = founder
2019-05-12 09:12:50 +02:00
channel . registeredTime = time . Now ( ) . UTC ( )
2018-04-04 03:49:40 +02:00
channel . accountToUMode [ founder ] = modes . ChannelFounder
2017-11-09 04:19:50 +01:00
return nil
}
2018-06-04 11:02:22 +02:00
// SetUnregistered deletes the channel's registration information.
2019-02-12 05:30:49 +01:00
func ( channel * Channel ) SetUnregistered ( expectedFounder string ) {
2023-01-04 11:06:21 +01:00
uuid := utils . GenerateUUIDv4 ( )
2018-06-04 11:02:22 +02:00
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
2019-02-12 05:30:49 +01:00
if channel . registeredFounder != expectedFounder {
return
}
2018-06-04 11:02:22 +02:00
channel . registeredFounder = ""
var zeroTime time . Time
channel . registeredTime = zeroTime
channel . accountToUMode = make ( map [ string ] modes . Mode )
2023-01-04 11:06:21 +01:00
// reset the UUID so that any re-registration will persist under
// a separate key:
channel . uuid = uuid
2018-06-04 11:02:22 +02:00
}
2019-12-17 01:50:15 +01:00
// implements `CHANSERV CLEAR #chan ACCESS` (resets bans, invites, excepts, and amodes)
func ( channel * Channel ) resetAccess ( ) {
defer channel . MarkDirty ( IncludeLists )
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
channel . initializeLists ( )
if channel . registeredFounder != "" {
channel . accountToUMode [ channel . registeredFounder ] = modes . ChannelFounder
}
}
2017-11-09 04:19:50 +01:00
// IsRegistered returns whether the channel is registered.
func ( channel * Channel ) IsRegistered ( ) bool {
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
return channel . registeredFounder != ""
}
2019-12-17 01:50:15 +01:00
type channelTransferStatus uint
const (
channelTransferComplete channelTransferStatus = iota
channelTransferPending
channelTransferCancelled
channelTransferFailed
)
// Transfer transfers ownership of a registered channel to a different account
func ( channel * Channel ) Transfer ( client * Client , target string , hasPrivs bool ) ( status channelTransferStatus , err error ) {
status = channelTransferFailed
defer func ( ) {
if status == channelTransferComplete && err == nil {
2020-03-02 07:46:22 +01:00
channel . Store ( IncludeAllAttrs )
2019-12-17 01:50:15 +01:00
}
} ( )
cftarget , err := CasefoldName ( target )
if err != nil {
err = errAccountDoesNotExist
return
}
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
if channel . registeredFounder == "" {
err = errChannelNotOwnedByAccount
return
}
if hasPrivs {
channel . transferOwnership ( cftarget )
return channelTransferComplete , nil
} else {
if channel . registeredFounder == cftarget {
// transferring back to yourself cancels a pending transfer
channel . transferPendingTo = ""
return channelTransferCancelled , nil
} else {
channel . transferPendingTo = cftarget
return channelTransferPending , nil
}
}
}
func ( channel * Channel ) transferOwnership ( newOwner string ) {
delete ( channel . accountToUMode , channel . registeredFounder )
channel . registeredFounder = newOwner
channel . accountToUMode [ channel . registeredFounder ] = modes . ChannelFounder
channel . transferPendingTo = ""
}
// AcceptTransfer implements `CS TRANSFER #chan ACCEPT`
func ( channel * Channel ) AcceptTransfer ( client * Client ) ( err error ) {
defer func ( ) {
if err == nil {
2020-03-02 07:46:22 +01:00
channel . Store ( IncludeAllAttrs )
2019-12-17 01:50:15 +01:00
}
} ( )
account := client . Account ( )
if account == "" {
return errAccountNotLoggedIn
}
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
if account != channel . transferPendingTo {
return errChannelTransferNotOffered
}
channel . transferOwnership ( account )
return nil
}
2018-04-24 09:11:11 +02:00
func ( channel * Channel ) regenerateMembersCache ( ) {
channel . stateMutex . RLock ( )
2023-04-14 08:15:56 +02:00
membersCache := make ( [ ] * Client , len ( channel . members ) )
dataCache := make ( [ ] * memberData , len ( channel . members ) )
2017-10-23 01:50:16 +02:00
i := 0
2023-04-14 08:15:56 +02:00
for client , info := range channel . members {
membersCache [ i ] = client
dataCache [ i ] = info
2017-10-23 01:50:16 +02:00
i ++
}
2018-04-24 09:11:11 +02:00
channel . stateMutex . RUnlock ( )
channel . stateMutex . Lock ( )
2023-04-14 08:15:56 +02:00
channel . membersCache = membersCache
channel . memberDataCache = dataCache
2018-04-24 09:11:11 +02:00
channel . stateMutex . Unlock ( )
2012-12-10 06:46:22 +01:00
}
2017-03-27 06:29:51 +02:00
// Names sends the list of users joined to the channel to the given client.
2018-02-05 15:21:08 +01:00
func ( channel * Channel ) Names ( client * Client , rb * ResponseBuffer ) {
2020-10-02 14:13:52 +02:00
channel . stateMutex . RLock ( )
2021-01-21 03:13:18 +01:00
clientData , isJoined := channel . members [ client ]
2023-04-14 08:15:56 +02:00
chname := channel . name
membersCache , memberDataCache := channel . membersCache , channel . memberDataCache
2020-10-02 14:13:52 +02:00
channel . stateMutex . RUnlock ( )
2021-02-07 04:45:34 +01:00
isOper := client . HasRoleCapabs ( "sajoin" )
2020-10-02 14:13:52 +02:00
respectAuditorium := channel . flags . HasMode ( modes . Auditorium ) && ! isOper &&
2021-01-21 03:13:18 +01:00
( ! isJoined || clientData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) )
2019-04-12 06:08:46 +02:00
isMultiPrefix := rb . session . capabilities . Has ( caps . MultiPrefix )
isUserhostInNames := rb . session . capabilities . Has ( caps . UserhostInNames )
2018-04-24 11:46:01 +02:00
2023-04-14 08:15:56 +02:00
maxNamLen := 480 - len ( client . server . name ) - len ( client . Nick ( ) ) - len ( chname )
var tl utils . TokenLineBuilder
tl . Initialize ( maxNamLen , " " )
2019-04-29 03:09:56 +02:00
if isJoined || ! channel . flags . HasMode ( modes . Secret ) || isOper {
2023-04-14 08:15:56 +02:00
for i , target := range membersCache {
if ! isJoined && target . HasMode ( modes . Invisible ) && ! isOper {
continue
}
2019-04-23 07:52:26 +02:00
var nick string
if isUserhostInNames {
nick = target . NickMaskString ( )
} else {
nick = target . Nick ( )
}
2023-04-14 08:15:56 +02:00
memberData := memberDataCache [ i ]
if respectAuditorium && memberData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-10-02 14:13:52 +02:00
continue
}
2023-04-14 08:15:56 +02:00
tl . AddParts ( memberData . modes . Prefixes ( isMultiPrefix ) , nick )
2018-04-24 11:46:01 +02:00
}
2016-06-19 06:55:24 +02:00
}
2023-04-14 08:15:56 +02:00
for _ , line := range tl . Lines ( ) {
rb . Add ( nil , client . server . name , RPL_NAMREPLY , client . nick , "=" , chname , line )
2018-04-24 11:46:01 +02:00
}
2023-04-14 08:15:56 +02:00
rb . Add ( nil , client . server . name , RPL_ENDOFNAMES , client . nick , chname , client . t ( "End of NAMES list" ) )
2013-06-03 07:07:50 +02:00
}
2019-04-23 06:05:12 +02:00
// does `clientMode` give you privileges to grant/remove `targetMode` to/from people,
// or to kick them?
func channelUserModeHasPrivsOver ( clientMode modes . Mode , targetMode modes . Mode ) bool {
switch clientMode {
case modes . ChannelFounder :
return true
case modes . ChannelAdmin , modes . ChannelOperator :
// admins cannot kick other admins, operators *can* kick other operators
return targetMode != modes . ChannelFounder && targetMode != modes . ChannelAdmin
case modes . Halfop :
// halfops cannot kick other halfops
2019-05-24 00:33:41 +02:00
return targetMode == modes . Voice || targetMode == modes . Mode ( 0 )
2019-04-23 06:05:12 +02:00
default :
// voice and unprivileged cannot kick anyone
2018-08-17 18:44:49 +02:00
return false
}
2019-04-23 06:05:12 +02:00
}
// ClientIsAtLeast returns whether the client has at least the given channel privilege.
func ( channel * Channel ) ClientIsAtLeast ( client * Client , permission modes . Mode ) bool {
channel . stateMutex . RLock ( )
2023-04-14 08:15:56 +02:00
memberData , present := channel . members [ client ]
2020-05-11 05:17:09 +02:00
founder := channel . registeredFounder
2019-04-23 06:05:12 +02:00
channel . stateMutex . RUnlock ( )
2018-04-23 00:47:10 +02:00
2020-05-11 05:17:09 +02:00
if founder != "" && founder == client . Account ( ) {
return true
}
2023-04-14 08:15:56 +02:00
if ! present {
return false
}
2018-05-23 21:35:50 +02:00
for _ , mode := range modes . ChannelUserModes {
2021-01-21 03:13:18 +01:00
if memberData . modes . HasMode ( mode ) {
2016-10-22 16:45:51 +02:00
return true
}
if mode == permission {
break
}
}
return false
2014-02-10 04:59:59 +01:00
}
2017-10-23 01:50:16 +02:00
func ( channel * Channel ) ClientPrefixes ( client * Client , isMultiPrefix bool ) string {
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2021-01-21 03:13:18 +01:00
memberData , present := channel . members [ client ]
2017-10-23 01:50:16 +02:00
if ! present {
return ""
} else {
2021-01-21 03:13:18 +01:00
return memberData . modes . Prefixes ( isMultiPrefix )
2017-10-23 01:50:16 +02:00
}
}
2021-01-21 03:13:18 +01:00
func ( channel * Channel ) ClientStatus ( client * Client ) ( present bool , joinTimeSecs int64 , cModes modes . Modes ) {
2020-04-15 10:14:17 +02:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2021-01-21 03:13:18 +01:00
memberData , present := channel . members [ client ]
return present , time . Unix ( 0 , memberData . joinTime ) . Unix ( ) , memberData . modes . AllModes ( )
2020-04-15 10:14:17 +02:00
}
2020-12-02 09:56:00 +01:00
// helper for persisting channel-user modes for always-on clients;
// return the channel name and all channel-user modes for a client
2021-01-21 03:13:18 +01:00
func ( channel * Channel ) alwaysOnStatus ( client * Client ) ( chname string , status alwaysOnChannelStatus ) {
2020-12-02 09:56:00 +01:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
chname = channel . name
2021-01-21 03:13:18 +01:00
data := channel . members [ client ]
status . Modes = data . modes . String ( )
status . JoinTime = data . joinTime
2020-12-02 09:56:00 +01:00
return
}
// overwrite any existing channel-user modes with the stored ones
2021-01-21 03:13:18 +01:00
func ( channel * Channel ) setMemberStatus ( client * Client , status alwaysOnChannelStatus ) {
2020-12-02 09:56:00 +01:00
newModes := modes . NewModeSet ( )
2021-01-21 03:13:18 +01:00
for _ , mode := range status . Modes {
2020-12-02 09:56:00 +01:00
newModes . SetMode ( modes . Mode ( mode ) , true )
}
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
2023-04-14 08:15:56 +02:00
if mData , ok := channel . members [ client ] ; ok {
mData . modes . Clear ( )
for _ , mode := range status . Modes {
mData . modes . SetMode ( modes . Mode ( mode ) , true )
}
mData . joinTime = status . JoinTime
2020-12-02 09:56:00 +01:00
}
}
2017-10-23 01:50:16 +02:00
func ( channel * Channel ) ClientHasPrivsOver ( client * Client , target * Client ) bool {
channel . stateMutex . RLock ( )
2020-04-23 03:52:24 +02:00
founder := channel . registeredFounder
2023-04-14 08:15:56 +02:00
clientData , clientOK := channel . members [ client ]
targetData , targetOK := channel . members [ target ]
2019-02-13 20:38:10 +01:00
channel . stateMutex . RUnlock ( )
2020-09-10 05:15:00 +02:00
if founder != "" {
if founder == client . Account ( ) {
return true // #950: founder can take any privileged action without actually having +q
} else if founder == target . Account ( ) {
return false // conversely, only the founder can kick the founder
}
2020-04-23 03:52:24 +02:00
}
2023-04-14 08:15:56 +02:00
return clientOK && targetOK &&
channelUserModeHasPrivsOver (
clientData . modes . HighestChannelUserMode ( ) ,
targetData . modes . HighestChannelUserMode ( ) ,
)
2017-10-23 01:50:16 +02:00
}
func ( channel * Channel ) hasClient ( client * Client ) bool {
channel . stateMutex . RLock ( )
_ , present := channel . members [ client ]
2019-04-23 06:05:12 +02:00
channel . stateMutex . RUnlock ( )
2017-10-23 01:50:16 +02:00
return present
2014-02-05 04:28:24 +01:00
}
2014-02-09 03:14:39 +01:00
// <mode> <mode params>
2017-10-23 01:50:16 +02:00
func ( channel * Channel ) modeStrings ( client * Client ) ( result [ ] string ) {
2021-02-07 04:45:34 +01:00
hasPrivs := client . HasRoleCapabs ( "sajoin" )
2020-05-17 20:02:59 +02:00
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2021-01-21 03:13:18 +01:00
isMember := hasPrivs || channel . members . Has ( client )
2014-02-22 21:49:33 +01:00
showKey := isMember && ( channel . key != "" )
showUserLimit := channel . userLimit > 0
2020-12-14 11:00:21 +01:00
showForward := channel . forward != ""
2014-02-22 21:49:33 +01:00
2020-12-14 11:00:21 +01:00
var mods strings . Builder
mods . WriteRune ( '+' )
2017-10-23 01:50:16 +02:00
2014-02-22 21:49:33 +01:00
// flags with args
if showKey {
2020-12-14 11:00:21 +01:00
mods . WriteRune ( rune ( modes . Key ) )
2014-02-09 08:33:56 +01:00
}
2014-02-22 21:49:33 +01:00
if showUserLimit {
2020-12-14 11:00:21 +01:00
mods . WriteRune ( rune ( modes . UserLimit ) )
}
if showForward {
mods . WriteRune ( rune ( modes . Forward ) )
2014-02-22 21:49:33 +01:00
}
2014-02-15 06:57:08 +01:00
2020-12-14 11:00:21 +01:00
for _ , m := range channel . flags . AllModes ( ) {
mods . WriteRune ( rune ( m ) )
}
2018-04-23 00:47:10 +02:00
2020-12-14 11:00:21 +01:00
result = [ ] string { mods . String ( ) }
2014-02-15 06:57:08 +01:00
2014-02-22 21:49:33 +01:00
// args for flags with args: The order must match above to keep
// positional arguments in place.
if showKey {
2017-10-23 01:50:16 +02:00
result = append ( result , channel . key )
2014-02-15 06:57:08 +01:00
}
2014-02-22 21:49:33 +01:00
if showUserLimit {
2018-12-28 19:45:55 +01:00
result = append ( result , strconv . Itoa ( channel . userLimit ) )
2014-02-22 21:49:33 +01:00
}
2020-12-14 11:00:21 +01:00
if showForward {
result = append ( result , channel . forward )
}
2014-02-15 06:57:08 +01:00
2017-10-23 01:50:16 +02:00
return
2014-02-09 03:14:39 +01:00
}
2017-10-30 10:21:47 +01:00
func ( channel * Channel ) IsEmpty ( ) bool {
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
return len ( channel . members ) == 0
}
2020-02-19 01:38:42 +01:00
// figure out where history is being stored: persistent, ephemeral, or neither
// target is only needed if we're doing persistent history
2021-01-21 03:13:18 +01:00
func ( channel * Channel ) historyStatus ( config * Config ) ( status HistoryStatus , target string , restrictions HistoryCutoff ) {
2020-02-24 20:09:00 +01:00
if ! config . History . Enabled {
2021-01-21 03:13:18 +01:00
return HistoryDisabled , "" , HistoryCutoffNone
2020-02-19 01:38:42 +01:00
}
channel . stateMutex . RLock ( )
target = channel . nameCasefolded
2021-01-21 03:13:18 +01:00
settings := channel . settings
2020-02-19 01:38:42 +01:00
registered := channel . registeredFounder != ""
channel . stateMutex . RUnlock ( )
2021-01-21 03:13:18 +01:00
restrictions = settings . QueryCutoff
if restrictions == HistoryCutoffDefault {
restrictions = config . History . Restrictions . queryCutoff
}
return channelHistoryStatus ( config , registered , settings . History ) , target , restrictions
}
func ( channel * Channel ) joinTimeCutoff ( client * Client ) ( present bool , cutoff time . Time ) {
account := client . Account ( )
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
if data , ok := channel . members [ client ] ; ok {
present = true
// report a cutoff of zero, i.e., no restriction, if the user is privileged
if ! ( ( account != "" && account == channel . registeredFounder ) || data . modes . HasMode ( modes . ChannelFounder ) || data . modes . HasMode ( modes . ChannelAdmin ) || data . modes . HasMode ( modes . ChannelOperator ) ) {
cutoff = time . Unix ( 0 , data . joinTime )
}
}
return
2020-03-19 12:26:17 +01:00
}
func channelHistoryStatus ( config * Config , registered bool , storedStatus HistoryStatus ) ( result HistoryStatus ) {
if ! config . History . Enabled {
return HistoryDisabled
}
2020-02-19 01:38:42 +01:00
// ephemeral history: either the channel owner explicitly set the ephemeral preference,
// or persistent history is disabled for unregistered channels
if registered {
2020-03-19 12:26:17 +01:00
return historyEnabled ( config . History . Persistent . RegisteredChannels , storedStatus )
2020-02-19 01:38:42 +01:00
} else {
2020-02-24 20:09:00 +01:00
if config . History . Persistent . UnregisteredChannels {
2020-03-19 12:26:17 +01:00
return HistoryPersistent
2020-02-24 20:09:00 +01:00
} else {
2020-03-19 12:26:17 +01:00
return HistoryEphemeral
2020-02-24 20:09:00 +01:00
}
2020-02-19 01:38:42 +01:00
}
}
2020-05-12 18:05:40 +02:00
func ( channel * Channel ) AddHistoryItem ( item history . Item , account string ) ( err error ) {
2020-07-10 00:36:45 +02:00
if ! itemIsStorable ( & item , channel . server . Config ( ) ) {
2020-02-19 01:38:42 +01:00
return
}
2021-01-21 03:13:18 +01:00
status , target , _ := channel . historyStatus ( channel . server . Config ( ) )
2020-02-24 20:09:00 +01:00
if status == HistoryPersistent {
2020-05-12 18:05:40 +02:00
err = channel . server . historyDB . AddChannelItem ( target , item , account )
2020-02-24 20:09:00 +01:00
} else if status == HistoryEphemeral {
2020-02-19 01:38:42 +01:00
channel . history . Add ( item )
}
2020-02-24 20:09:00 +01:00
return
2020-02-19 01:38:42 +01:00
}
2017-03-27 06:29:51 +02:00
// Join joins the given client to this channel (if they can be joined).
2020-12-14 11:00:21 +01:00
func ( channel * Channel ) Join ( client * Client , key string , isSajoin bool , rb * ResponseBuffer ) ( joinErr error , forward string ) {
2019-01-01 19:15:38 +01:00
details := client . Details ( )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
2018-12-31 00:28:56 +01:00
2018-05-25 08:46:36 +02:00
channel . stateMutex . RLock ( )
chname := channel . name
2018-12-23 19:25:02 +01:00
chcfname := channel . nameCasefolded
2018-05-25 08:46:36 +02:00
founder := channel . registeredFounder
2020-10-26 01:40:41 +01:00
createdAt := channel . createdTime
2018-12-28 19:45:55 +01:00
chkey := channel . key
limit := channel . userLimit
chcount := len ( channel . members )
_ , alreadyJoined := channel . members [ client ]
2019-01-01 19:15:38 +01:00
persistentMode := channel . accountToUMode [ details . account ]
2020-12-14 11:00:21 +01:00
forward = channel . forward
2018-05-25 08:46:36 +02:00
channel . stateMutex . RUnlock ( )
2018-12-28 19:45:55 +01:00
if alreadyJoined {
// no message needs to be sent
2020-12-14 11:00:21 +01:00
return nil , ""
2018-12-28 19:45:55 +01:00
}
2020-07-01 01:24:56 +02:00
// 0. SAJOIN always succeeds
// 1. the founder can always join (even if they disabled auto +q on join)
// 2. anyone who automatically receives halfop or higher can always join
// 3. people invited with INVITE can join
hasPrivs := isSajoin || ( founder != "" && founder == details . account ) ||
( persistentMode != 0 && persistentMode != modes . Voice ) ||
2020-10-26 01:40:41 +01:00
client . CheckInvited ( chcfname , createdAt )
2020-07-01 01:24:56 +02:00
if ! hasPrivs {
if limit != 0 && chcount >= limit {
2020-12-14 11:00:21 +01:00
return errLimitExceeded , forward
2020-07-01 01:24:56 +02:00
}
2018-04-24 09:11:11 +02:00
2020-07-01 01:24:56 +02:00
if chkey != "" && ! utils . SecretTokensMatch ( chkey , key ) {
2020-12-14 11:00:21 +01:00
return errWrongChannelKey , forward
2020-07-01 01:24:56 +02:00
}
2014-02-22 21:49:33 +01:00
2022-01-19 06:54:03 +01:00
// #1901: +h and up exempt from all restrictions, but +v additionally exempts from +i:
if channel . flags . HasMode ( modes . InviteOnly ) && persistentMode == 0 &&
2020-07-01 01:24:56 +02:00
! channel . lists [ modes . InviteMask ] . Match ( details . nickMaskCasefolded ) {
2020-12-14 11:00:21 +01:00
return errInviteOnly , forward
2020-07-01 01:24:56 +02:00
}
2014-02-21 03:56:13 +01:00
2020-07-01 01:24:56 +02:00
if channel . lists [ modes . BanMask ] . Match ( details . nickMaskCasefolded ) &&
! channel . lists [ modes . ExceptMask ] . Match ( details . nickMaskCasefolded ) &&
! channel . lists [ modes . InviteMask ] . Match ( details . nickMaskCasefolded ) {
2020-12-14 11:00:21 +01:00
// do not forward people who are banned:
return errBanned , ""
2020-07-01 01:24:56 +02:00
}
2014-03-08 02:09:49 +01:00
2020-07-08 11:32:14 +02:00
if details . account == "" &&
2021-12-16 04:52:11 +01:00
( channel . flags . HasMode ( modes . RegisteredOnly ) || channel . server . Defcon ( ) <= 2 ) &&
! channel . lists [ modes . InviteMask ] . Match ( details . nickMaskCasefolded ) {
2020-12-14 11:00:21 +01:00
return errRegisteredOnly , forward
2020-07-01 01:24:56 +02:00
}
2014-03-08 02:09:49 +01:00
}
2020-07-01 01:24:56 +02:00
if joinErr := client . addChannel ( channel , rb == nil ) ; joinErr != nil {
2020-12-14 11:00:21 +01:00
return joinErr , ""
2019-05-12 10:49:45 +02:00
}
2021-02-04 21:26:03 +01:00
client . server . logger . Debug ( "channels" , fmt . Sprintf ( "%s joined channel %s" , details . nick , chname ) )
2017-03-06 13:11:10 +01:00
2019-02-13 19:22:00 +01:00
givenMode := func ( ) ( givenMode modes . Mode ) {
2018-04-24 09:11:11 +02:00
channel . joinPartMutex . Lock ( )
defer channel . joinPartMutex . Unlock ( )
func ( ) {
channel . stateMutex . Lock ( )
defer channel . stateMutex . Unlock ( )
channel . members . Add ( client )
firstJoin := len ( channel . members ) == 1
2019-02-13 19:22:00 +01:00
newChannel := firstJoin && channel . registeredFounder == ""
2018-04-24 09:11:11 +02:00
if newChannel {
givenMode = modes . ChannelOperator
2018-02-05 15:21:08 +01:00
} else {
2018-12-31 00:28:56 +01:00
givenMode = persistentMode
2018-02-05 15:21:08 +01:00
}
2018-04-24 09:11:11 +02:00
if givenMode != 0 {
2021-01-21 03:13:18 +01:00
channel . members [ client ] . modes . SetMode ( givenMode , true )
2018-02-05 15:21:08 +01:00
}
2018-04-24 09:11:11 +02:00
} ( )
2016-06-22 13:35:26 +02:00
2018-04-24 09:11:11 +02:00
channel . regenerateMembersCache ( )
2019-01-01 19:15:38 +01:00
2018-04-24 09:11:11 +02:00
return
} ( )
2017-10-23 01:50:16 +02:00
2020-02-20 08:57:39 +01:00
var message utils . SplitMessage
2020-10-02 14:13:52 +02:00
respectAuditorium := givenMode == modes . Mode ( 0 ) && channel . flags . HasMode ( modes . Auditorium )
2021-05-05 16:00:19 +02:00
message = utils . MakeMessage ( "" )
2020-02-20 08:57:39 +01:00
// no history item for fake persistent joins
2020-10-02 14:13:52 +02:00
if rb != nil && ! respectAuditorium {
2020-02-20 08:57:39 +01:00
histItem := history . Item {
Type : history . Join ,
Nick : details . nickMask ,
AccountName : details . accountName ,
Message : message ,
2021-03-17 19:36:52 +01:00
IsBot : isBot ,
2020-02-20 08:57:39 +01:00
}
histItem . Params [ 0 ] = details . realname
2020-05-12 18:05:40 +02:00
channel . AddHistoryItem ( histItem , details . account )
2020-02-20 08:57:39 +01:00
}
2020-02-20 09:02:39 +01:00
if rb == nil {
2020-12-14 11:00:21 +01:00
return nil , ""
2020-02-20 09:02:39 +01:00
}
2018-04-24 09:11:11 +02:00
var modestr string
if givenMode != 0 {
modestr = fmt . Sprintf ( "+%v" , givenMode )
2017-11-09 04:19:50 +01:00
}
2018-04-24 09:11:11 +02:00
2020-11-27 06:13:47 +01:00
// cache the most common case (JOIN without extended-join)
var cache MessageCache
2021-03-17 19:36:52 +01:00
cache . Initialize ( channel . server , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "JOIN" , chname )
2020-07-17 07:55:13 +02:00
isAway , awayMessage := client . Away ( )
2018-04-24 09:11:11 +02:00
for _ , member := range channel . Members ( ) {
2020-10-02 14:13:52 +02:00
if respectAuditorium {
channel . stateMutex . RLock ( )
2021-01-21 03:13:18 +01:00
memberData , ok := channel . members [ member ]
2020-10-02 14:13:52 +02:00
channel . stateMutex . RUnlock ( )
2021-01-21 03:13:18 +01:00
if ! ok || memberData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-10-02 14:13:52 +02:00
continue
}
}
2019-04-12 06:08:46 +02:00
for _ , session := range member . Sessions ( ) {
2020-02-20 09:02:39 +01:00
if session == rb . session {
2019-04-12 06:08:46 +02:00
continue
} else if client == session . client {
channel . playJoinForSession ( session )
continue
}
if session . capabilities . Has ( caps . ExtendedJoin ) {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "JOIN" , chname , details . accountName , details . realname )
2019-04-12 06:08:46 +02:00
} else {
2020-11-27 06:13:47 +01:00
cache . Send ( session )
2019-04-12 06:08:46 +02:00
}
if givenMode != 0 {
session . Send ( nil , client . server . name , "MODE" , chname , modestr , details . nick )
}
2020-07-17 07:55:13 +02:00
if isAway && session . capabilities . Has ( caps . AwayNotify ) {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , time . Time { } , "" , details . nickMask , details . accountName , isBot , nil , "AWAY" , awayMessage )
2020-07-17 07:55:13 +02:00
}
2018-04-24 09:11:11 +02:00
}
2017-11-09 04:19:50 +01:00
}
2014-02-17 02:23:47 +01:00
2020-02-20 09:02:39 +01:00
if rb . session . capabilities . Has ( caps . ExtendedJoin ) {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "JOIN" , chname , details . accountName , details . realname )
2016-08-14 03:59:33 +02:00
} else {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "JOIN" , chname )
2016-08-14 03:59:33 +02:00
}
2018-04-24 09:11:11 +02:00
2022-03-30 21:35:28 +02:00
if rb . session . capabilities . Has ( caps . ReadMarker ) {
rb . Add ( nil , client . server . name , "MARKREAD" , chname , client . GetReadMarker ( chcfname ) )
}
2020-02-20 09:02:39 +01:00
if rb . session . client == client {
2019-04-12 06:08:46 +02:00
// don't send topic and names for a SAJOIN of a different client
channel . SendTopic ( client , rb , false )
channel . Names ( client , rb )
2020-11-12 17:57:30 +01:00
} else {
// ensure that SAJOIN sends a MODE line to the originating client, if applicable
if givenMode != 0 {
rb . Add ( nil , client . server . name , "MODE" , chname , modestr , details . nick )
}
2019-04-12 06:08:46 +02:00
}
2018-04-24 09:11:11 +02:00
2018-12-28 19:45:55 +01:00
// TODO #259 can be implemented as Flush(false) (i.e., nonblocking) while holding joinPartMutex
rb . Flush ( true )
2020-02-20 09:02:39 +01:00
channel . autoReplayHistory ( client , rb , message . Msgid )
2020-12-14 11:00:21 +01:00
return nil , ""
2019-05-30 01:23:46 +02:00
}
func ( channel * Channel ) autoReplayHistory ( client * Client , rb * ResponseBuffer , skipMsgid string ) {
2019-05-21 01:08:57 +02:00
// autoreplay any messages as necessary
var items [ ] history . Item
2020-02-19 01:38:42 +01:00
2020-07-21 22:33:17 +02:00
hasAutoreplayTimestamps := false
2020-02-28 01:07:49 +01:00
var start , end time . Time
2020-02-27 20:43:59 +01:00
if rb . session . zncPlaybackTimes . ValidFor ( channel . NameCasefolded ( ) ) {
2020-07-21 22:33:17 +02:00
hasAutoreplayTimestamps = true
2020-02-28 01:07:49 +01:00
start , end = rb . session . zncPlaybackTimes . start , rb . session . zncPlaybackTimes . end
2020-02-27 08:13:31 +01:00
} else if ! rb . session . autoreplayMissedSince . IsZero ( ) {
2020-02-19 01:38:42 +01:00
// we already checked for history caps in `playReattachMessages`
2020-07-21 22:33:17 +02:00
hasAutoreplayTimestamps = true
2020-02-28 01:07:49 +01:00
start = time . Now ( ) . UTC ( )
end = rb . session . autoreplayMissedSince
2020-02-19 01:38:42 +01:00
}
2020-07-21 22:33:17 +02:00
if hasAutoreplayTimestamps {
2021-11-01 06:23:07 +01:00
_ , seq , _ := channel . server . GetHistorySequence ( channel , client , "" )
2020-02-19 01:38:42 +01:00
if seq != nil {
zncMax := channel . server . Config ( ) . History . ZNCMax
2021-04-06 06:46:07 +02:00
items , _ = seq . Between ( history . Selector { Time : start } , history . Selector { Time : end } , zncMax )
2020-02-19 01:38:42 +01:00
}
2019-05-30 01:23:46 +02:00
} else if ! rb . session . HasHistoryCaps ( ) {
2019-05-21 01:08:57 +02:00
var replayLimit int
customReplayLimit := client . AccountSettings ( ) . AutoreplayLines
if customReplayLimit != nil {
replayLimit = * customReplayLimit
maxLimit := channel . server . Config ( ) . History . ChathistoryMax
if maxLimit < replayLimit {
replayLimit = maxLimit
}
} else {
replayLimit = channel . server . Config ( ) . History . AutoreplayOnJoin
}
if 0 < replayLimit {
2021-11-01 06:23:07 +01:00
_ , seq , _ := channel . server . GetHistorySequence ( channel , client , "" )
2020-02-19 01:38:42 +01:00
if seq != nil {
2021-04-06 06:46:07 +02:00
items , _ = seq . Between ( history . Selector { } , history . Selector { } , replayLimit )
2020-02-19 01:38:42 +01:00
}
2019-05-07 05:17:57 +02:00
}
2018-12-28 19:45:55 +01:00
}
2019-05-21 01:08:57 +02:00
// remove the client's own JOIN line from the replay
numItems := len ( items )
for i := len ( items ) - 1 ; 0 <= i ; i -- {
2019-05-30 01:23:46 +02:00
if items [ i ] . Message . Msgid == skipMsgid {
2019-05-21 01:08:57 +02:00
// zero'ed items will not be replayed because their `Type` field is not recognized
items [ i ] = history . Item { }
numItems --
break
}
}
if 0 < numItems {
2021-11-01 06:24:14 +01:00
channel . replayHistoryItems ( rb , items , false )
2019-05-21 01:08:57 +02:00
rb . Flush ( true )
}
2014-02-17 02:23:47 +01:00
}
2019-04-12 06:08:46 +02:00
// plays channel join messages (the JOIN line, topic, and names) to a session.
// this is used when attaching a new session to an existing client that already has
// channels, and also when one session of a client initiates a JOIN and the other
// sessions need to receive the state change
func ( channel * Channel ) playJoinForSession ( session * Session ) {
client := session . client
sessionRb := NewResponseBuffer ( session )
2019-05-22 22:10:56 +02:00
details := client . Details ( )
2022-03-30 21:35:28 +02:00
chname := channel . Name ( )
2019-04-12 06:08:46 +02:00
if session . capabilities . Has ( caps . ExtendedJoin ) {
2022-03-30 21:35:28 +02:00
sessionRb . Add ( nil , details . nickMask , "JOIN" , chname , details . accountName , details . realname )
2019-04-12 06:08:46 +02:00
} else {
2022-03-30 21:35:28 +02:00
sessionRb . Add ( nil , details . nickMask , "JOIN" , chname )
}
if session . capabilities . Has ( caps . ReadMarker ) {
chcfname := channel . NameCasefolded ( )
sessionRb . Add ( nil , client . server . name , "MARKREAD" , chname , client . GetReadMarker ( chcfname ) )
2019-04-12 06:08:46 +02:00
}
channel . SendTopic ( client , sessionRb , false )
channel . Names ( client , sessionRb )
sessionRb . Send ( false )
}
2017-03-27 06:29:51 +02:00
// Part parts the given client from this channel, with the given message.
2018-02-05 15:21:08 +01:00
func ( channel * Channel ) Part ( client * Client , message string , rb * ResponseBuffer ) {
2020-10-02 14:13:52 +02:00
channel . stateMutex . RLock ( )
chname := channel . name
2021-01-21 03:13:18 +01:00
clientData , ok := channel . members [ client ]
2020-10-02 14:13:52 +02:00
channel . stateMutex . RUnlock ( )
if ! ok {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , client . server . name , ERR_NOTONCHANNEL , client . Nick ( ) , chname , client . t ( "You're not on that channel" ) )
2012-12-09 07:54:58 +01:00
return
}
2018-04-24 09:11:11 +02:00
channel . Quit ( client )
2020-01-19 05:47:05 +01:00
splitMessage := utils . MakeMessage ( message )
2019-05-07 05:17:57 +02:00
2019-01-01 19:15:38 +01:00
details := client . Details ( )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
2019-12-03 03:13:09 +01:00
params := make ( [ ] string , 1 , 2 )
params [ 0 ] = chname
if message != "" {
params = append ( params , message )
}
2020-10-02 14:13:52 +02:00
respectAuditorium := channel . flags . HasMode ( modes . Auditorium ) &&
2021-01-21 03:13:18 +01:00
clientData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 )
2020-11-27 06:13:47 +01:00
var cache MessageCache
2021-03-17 19:36:52 +01:00
cache . Initialize ( channel . server , splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , isBot , nil , "PART" , params ... )
2017-10-23 01:50:16 +02:00
for _ , member := range channel . Members ( ) {
2020-10-02 14:13:52 +02:00
if respectAuditorium {
channel . stateMutex . RLock ( )
2021-01-21 03:13:18 +01:00
memberData , ok := channel . members [ member ]
2020-10-02 14:13:52 +02:00
channel . stateMutex . RUnlock ( )
2021-01-21 03:13:18 +01:00
if ! ok || memberData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-10-02 14:13:52 +02:00
continue
}
}
2020-11-27 06:13:47 +01:00
for _ , session := range member . Sessions ( ) {
cache . Send ( session )
}
2014-02-20 07:20:34 +01:00
}
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , isBot , nil , "PART" , params ... )
2019-04-12 06:08:46 +02:00
for _ , session := range client . Sessions ( ) {
if session != rb . session {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , splitMessage . Time , splitMessage . Msgid , details . nickMask , details . accountName , isBot , nil , "PART" , params ... )
2019-04-12 06:08:46 +02:00
}
}
2017-03-06 13:11:10 +01:00
2020-10-02 14:13:52 +02:00
if ! respectAuditorium {
channel . AddHistoryItem ( history . Item {
Type : history . Part ,
Nick : details . nickMask ,
AccountName : details . accountName ,
Message : splitMessage ,
2021-03-17 19:36:52 +01:00
IsBot : isBot ,
2020-10-02 14:13:52 +02:00
} , details . account )
}
2018-11-26 11:23:27 +01:00
2021-02-04 21:26:03 +01:00
client . server . logger . Debug ( "channels" , fmt . Sprintf ( "%s left channel %s" , details . nick , chname ) )
2012-12-09 07:54:58 +01:00
}
2021-11-01 06:24:14 +01:00
func ( channel * Channel ) replayHistoryItems ( rb * ResponseBuffer , items [ ] history . Item , chathistoryCommand bool ) {
2020-02-21 05:47:13 +01:00
// send an empty batch if necessary, as per the CHATHISTORY spec
2018-11-26 11:23:27 +01:00
chname := channel . Name ( )
2018-12-28 19:45:55 +01:00
client := rb . target
2019-05-07 05:17:57 +02:00
eventPlayback := rb . session . capabilities . Has ( caps . EventPlayback )
extendedJoin := rb . session . capabilities . Has ( caps . ExtendedJoin )
2019-12-18 23:38:14 +01:00
var playJoinsAsPrivmsg bool
if ! eventPlayback {
2021-11-01 06:24:14 +01:00
if chathistoryCommand {
2019-12-18 23:38:14 +01:00
playJoinsAsPrivmsg = true
2021-11-01 06:24:14 +01:00
} else {
switch client . AccountSettings ( ) . ReplayJoins {
case ReplayJoinsCommandsOnly :
playJoinsAsPrivmsg = false
case ReplayJoinsAlways :
playJoinsAsPrivmsg = true
}
2019-12-18 23:38:14 +01:00
}
2019-05-07 05:17:57 +02:00
}
2019-12-18 23:38:14 +01:00
2023-06-02 12:56:45 +02:00
batchID := rb . StartNestedBatch ( "chathistory" , chname )
2019-05-07 05:17:57 +02:00
defer rb . EndNestedBatch ( batchID )
2018-12-28 19:45:55 +01:00
2019-05-07 05:17:57 +02:00
for _ , item := range items {
2020-12-14 21:23:01 +01:00
nick := NUHToNick ( item . Nick )
2018-11-26 11:23:27 +01:00
switch item . Type {
case history . Privmsg :
2021-03-17 19:36:52 +01:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . IsBot , item . Tags , "PRIVMSG" , chname , item . Message )
2018-11-26 11:23:27 +01:00
case history . Notice :
2021-03-17 19:36:52 +01:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . IsBot , item . Tags , "NOTICE" , chname , item . Message )
2019-05-07 05:17:57 +02:00
case history . Tagmsg :
2020-05-22 16:58:46 +02:00
if eventPlayback {
2021-03-17 19:36:52 +01:00
rb . AddSplitMessageFromClient ( item . Nick , item . AccountName , item . IsBot , item . Tags , "TAGMSG" , chname , item . Message )
2021-11-01 06:24:14 +01:00
} else if chathistoryCommand {
// #1676, we have to send something here or else it breaks pagination
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , fmt . Sprintf ( client . t ( "%s sent a TAGMSG" ) , nick ) )
2019-05-07 05:17:57 +02:00
}
2018-11-26 11:23:27 +01:00
case history . Join :
2019-05-07 05:17:57 +02:00
if eventPlayback {
if extendedJoin {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "JOIN" , chname , item . AccountName , item . Params [ 0 ] )
2019-05-07 05:17:57 +02:00
} else {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "JOIN" , chname )
2019-05-07 05:17:57 +02:00
}
2018-11-26 11:23:27 +01:00
} else {
2019-05-19 10:27:44 +02:00
if ! playJoinsAsPrivmsg {
2019-05-07 05:17:57 +02:00
continue // #474
}
var message string
if item . AccountName == "*" {
message = fmt . Sprintf ( client . t ( "%s joined the channel" ) , nick )
} else {
message = fmt . Sprintf ( client . t ( "%[1]s [account: %[2]s] joined the channel" ) , nick , item . AccountName )
}
2021-11-01 06:24:14 +01:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2018-11-26 11:23:27 +01:00
}
case history . Part :
2019-05-07 05:17:57 +02:00
if eventPlayback {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "PART" , chname , item . Message . Message )
2019-05-07 05:17:57 +02:00
} else {
2019-05-19 10:27:44 +02:00
if ! playJoinsAsPrivmsg {
2019-05-07 05:17:57 +02:00
continue // #474
}
message := fmt . Sprintf ( client . t ( "%[1]s left the channel (%[2]s)" ) , nick , item . Message . Message )
2021-11-01 06:24:14 +01:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2019-05-07 05:17:57 +02:00
}
2018-11-26 11:23:27 +01:00
case history . Kick :
2019-05-07 05:17:57 +02:00
if eventPlayback {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "KICK" , chname , item . Params [ 0 ] , item . Message . Message )
2019-05-07 05:17:57 +02:00
} else {
message := fmt . Sprintf ( client . t ( "%[1]s kicked %[2]s (%[3]s)" ) , nick , item . Params [ 0 ] , item . Message . Message )
2021-11-01 06:24:14 +01:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2019-05-07 05:17:57 +02:00
}
case history . Quit :
if eventPlayback {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "QUIT" , item . Message . Message )
2019-05-07 05:17:57 +02:00
} else {
2019-05-19 10:27:44 +02:00
if ! playJoinsAsPrivmsg {
2019-05-07 05:17:57 +02:00
continue // #474
}
message := fmt . Sprintf ( client . t ( "%[1]s quit (%[2]s)" ) , nick , item . Message . Message )
2021-11-01 06:24:14 +01:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2019-05-07 05:17:57 +02:00
}
case history . Nick :
if eventPlayback {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "NICK" , item . Params [ 0 ] )
2019-05-07 05:17:57 +02:00
} else {
message := fmt . Sprintf ( client . t ( "%[1]s changed nick to %[2]s" ) , nick , item . Params [ 0 ] )
2021-11-01 06:24:14 +01:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2019-05-07 05:17:57 +02:00
}
2020-04-23 04:51:19 +02:00
case history . Topic :
if eventPlayback {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "TOPIC" , chname , item . Message . Message )
2020-04-23 04:51:19 +02:00
} else {
message := fmt . Sprintf ( client . t ( "%[1]s set the channel topic to: %[2]s" ) , nick , item . Message . Message )
2021-11-01 06:24:14 +01:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2020-04-23 04:51:19 +02:00
}
case history . Mode :
params := make ( [ ] string , len ( item . Message . Split ) + 1 )
params [ 0 ] = chname
for i , pair := range item . Message . Split {
params [ i + 1 ] = pair . Message
}
if eventPlayback {
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( item . Message . Time , item . Message . Msgid , item . Nick , item . AccountName , item . IsBot , nil , "MODE" , params ... )
2020-04-23 04:51:19 +02:00
} else {
message := fmt . Sprintf ( client . t ( "%[1]s set channel modes: %[2]s" ) , nick , strings . Join ( params [ 1 : ] , " " ) )
2021-11-01 06:24:14 +01:00
rb . AddFromClient ( item . Message . Time , history . HistservMungeMsgid ( item . Message . Msgid ) , histservService . prefix , "*" , false , nil , "PRIVMSG" , chname , message )
2020-04-23 04:51:19 +02:00
}
2018-11-26 11:23:27 +01:00
}
}
}
2017-10-23 01:50:16 +02:00
// SendTopic sends the channel topic to the given client.
2019-02-13 19:22:00 +01:00
// `sendNoTopic` controls whether RPL_NOTOPIC is sent when the topic is unset
func ( channel * Channel ) SendTopic ( client * Client , rb * ResponseBuffer , sendNoTopic bool ) {
2017-10-23 01:50:16 +02:00
channel . stateMutex . RLock ( )
name := channel . name
topic := channel . topic
topicSetBy := channel . topicSetBy
topicSetTime := channel . topicSetTime
2019-04-12 06:08:46 +02:00
_ , hasClient := channel . members [ client ]
2017-10-23 01:50:16 +02:00
channel . stateMutex . RUnlock ( )
2019-04-12 06:08:46 +02:00
if ! hasClient {
rb . Add ( nil , client . server . name , ERR_NOTONCHANNEL , client . Nick ( ) , channel . name , client . t ( "You're not on that channel" ) )
return
}
2017-10-23 01:50:16 +02:00
if topic == "" {
2019-02-13 19:22:00 +01:00
if sendNoTopic {
rb . Add ( nil , client . server . name , RPL_NOTOPIC , client . nick , name , client . t ( "No topic is set" ) )
}
2012-12-09 07:54:58 +01:00
return
}
2018-02-05 15:21:08 +01:00
rb . Add ( nil , client . server . name , RPL_TOPIC , client . nick , name , topic )
rb . Add ( nil , client . server . name , RPL_TOPICTIME , client . nick , name , topicSetBy , strconv . FormatInt ( topicSetTime . Unix ( ) , 10 ) )
2014-02-17 07:20:42 +01:00
}
2017-03-27 06:29:51 +02:00
// SetTopic sets the topic of this channel, if the client is allowed to do so.
2018-02-05 15:21:08 +01:00
func ( channel * Channel ) SetTopic ( client * Client , topic string , rb * ResponseBuffer ) {
2021-02-07 04:45:34 +01:00
if ! channel . hasClient ( client ) {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , client . server . name , ERR_NOTONCHANNEL , client . Nick ( ) , channel . Name ( ) , client . t ( "You're not on that channel" ) )
2014-02-17 07:20:42 +01:00
return
}
2021-02-07 04:45:34 +01:00
if channel . flags . HasMode ( modes . OpOnlyTopic ) && ! ( channel . ClientIsAtLeast ( client , modes . Halfop ) || client . HasRoleCapabs ( "samode" ) ) {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , client . server . name , ERR_CHANOPRIVSNEEDED , client . Nick ( ) , channel . Name ( ) , client . t ( "You're not a channel operator" ) )
2014-02-16 04:56:38 +01:00
return
}
2021-03-18 08:49:12 +01:00
topic = ircutils . TruncateUTF8Safe ( topic , client . server . Config ( ) . Limits . TopicLen )
2016-09-12 04:22:50 +02:00
2017-10-23 01:50:16 +02:00
channel . stateMutex . Lock ( )
2020-04-23 04:51:19 +02:00
chname := channel . name
2014-02-17 07:20:42 +01:00
channel . topic = topic
2017-03-24 04:44:54 +01:00
channel . topicSetBy = client . nickMaskString
2019-05-12 09:12:50 +02:00
channel . topicSetTime = time . Now ( ) . UTC ( )
2017-10-23 01:50:16 +02:00
channel . stateMutex . Unlock ( )
2014-02-21 05:47:05 +01:00
2020-04-23 04:51:19 +02:00
details := client . Details ( )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
2020-04-23 04:51:19 +02:00
message := utils . MakeMessage ( topic )
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "TOPIC" , chname , topic )
2017-10-23 01:50:16 +02:00
for _ , member := range channel . Members ( ) {
2019-04-12 06:08:46 +02:00
for _ , session := range member . Sessions ( ) {
2020-04-23 04:51:19 +02:00
if session != rb . session {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "TOPIC" , chname , topic )
2019-04-12 06:08:46 +02:00
}
2018-02-05 15:21:08 +01:00
}
2014-02-20 07:20:34 +01:00
}
2017-03-24 04:44:54 +01:00
2020-04-23 04:51:19 +02:00
channel . AddHistoryItem ( history . Item {
Type : history . Topic ,
Nick : details . nickMask ,
AccountName : details . accountName ,
Message : message ,
2021-03-18 00:01:38 +01:00
IsBot : isBot ,
2020-05-12 18:05:40 +02:00
} , details . account )
2020-04-23 04:51:19 +02:00
2019-03-12 00:24:45 +01:00
channel . MarkDirty ( IncludeTopic )
2012-12-15 23:34:20 +01:00
}
2020-09-19 20:01:58 +02:00
// CanSpeak returns true if the client can speak on this channel, otherwise it returns false along with the channel mode preventing the client from speaking.
func ( channel * Channel ) CanSpeak ( client * Client ) ( bool , modes . Mode ) {
2017-10-23 01:50:16 +02:00
channel . stateMutex . RLock ( )
2021-01-21 03:13:18 +01:00
memberData , hasClient := channel . members [ client ]
2020-10-21 17:08:55 +02:00
channel . stateMutex . RUnlock ( )
2023-05-22 18:29:55 +02:00
highestMode := func ( ) modes . Mode {
if ! hasClient {
return modes . Mode ( 0 )
}
return memberData . modes . HighestChannelUserMode ( )
}
2017-01-10 17:09:08 +01:00
2020-10-21 17:08:55 +02:00
if ! hasClient && channel . flags . HasMode ( modes . NoOutside ) {
// TODO: enforce regular +b bans on -n channels?
2020-09-19 20:01:58 +02:00
return false , modes . NoOutside
2014-02-22 21:49:33 +01:00
}
2023-05-22 18:29:55 +02:00
if channel . isMuted ( client ) && highestMode ( ) == modes . Mode ( 0 ) {
2020-10-21 17:08:55 +02:00
return false , modes . BanMask
}
2023-05-22 18:29:55 +02:00
if channel . flags . HasMode ( modes . Moderated ) && highestMode ( ) == modes . Mode ( 0 ) {
2020-09-19 20:01:58 +02:00
return false , modes . Moderated
2014-02-22 21:49:33 +01:00
}
2020-10-21 17:08:55 +02:00
if channel . flags . HasMode ( modes . RegisteredOnlySpeak ) && client . Account ( ) == "" &&
2023-05-22 18:29:55 +02:00
highestMode ( ) == modes . Mode ( 0 ) {
2020-09-19 20:01:58 +02:00
return false , modes . RegisteredOnlySpeak
2017-03-28 09:32:03 +02:00
}
2020-09-19 20:01:58 +02:00
return true , modes . Mode ( '?' )
2014-02-22 21:49:33 +01:00
}
2020-10-21 17:08:55 +02:00
func ( channel * Channel ) isMuted ( client * Client ) bool {
muteRe := channel . lists [ modes . BanMask ] . MuteRegexp ( )
if muteRe == nil {
return false
}
2020-11-02 00:09:04 +01:00
nuh := client . NickMaskCasefolded ( )
2020-10-21 17:08:55 +02:00
return muteRe . MatchString ( nuh ) && ! channel . lists [ modes . ExceptMask ] . MatchMute ( nuh )
}
2021-03-11 07:21:03 +01:00
func ( channel * Channel ) relayNickMuted ( relayNick string ) bool {
relayNUH := fmt . Sprintf ( "%s!*@*" , relayNick )
return channel . lists [ modes . BanMask ] . MatchMute ( relayNUH ) &&
! channel . lists [ modes . ExceptMask ] . MatchMute ( relayNUH )
}
2019-12-23 21:26:37 +01:00
func msgCommandToHistType ( command string ) ( history . ItemType , error ) {
2019-03-07 08:31:46 +01:00
switch command {
case "PRIVMSG" :
2019-03-19 08:35:49 +01:00
return history . Privmsg , nil
2019-03-07 08:31:46 +01:00
case "NOTICE" :
2019-03-19 08:35:49 +01:00
return history . Notice , nil
2019-03-07 08:31:46 +01:00
case "TAGMSG" :
2019-03-19 08:35:49 +01:00
return history . Tagmsg , nil
2019-03-07 08:31:46 +01:00
default :
2019-03-19 08:35:49 +01:00
return history . ItemType ( 0 ) , errInvalidParams
}
}
2019-04-23 06:05:12 +02:00
func ( channel * Channel ) SendSplitMessage ( command string , minPrefixMode modes . Mode , clientOnlyTags map [ string ] string , client * Client , message utils . SplitMessage , rb * ResponseBuffer ) {
2019-12-23 21:26:37 +01:00
histType , err := msgCommandToHistType ( command )
2019-03-19 08:35:49 +01:00
if err != nil {
2014-02-09 08:33:56 +01:00
return
}
2017-01-10 17:09:08 +01:00
2020-09-19 20:01:58 +02:00
if canSpeak , mode := channel . CanSpeak ( client ) ; ! canSpeak {
2019-03-19 08:35:49 +01:00
if histType != history . Notice {
2020-09-19 20:01:58 +02:00
rb . Add ( nil , client . server . name , ERR_CANNOTSENDTOCHAN , client . Nick ( ) , channel . Name ( ) , fmt . Sprintf ( client . t ( "Cannot send to channel (+%s)" ) , mode ) )
2019-03-19 08:35:49 +01:00
}
2017-01-14 06:28:50 +01:00
return
}
2020-01-27 02:50:27 +01:00
isCTCP := message . IsRestrictedCTCPMessage ( )
if isCTCP && channel . flags . HasMode ( modes . NoCTCP ) {
if histType != history . Notice {
rb . Add ( nil , client . server . name , ERR_CANNOTSENDTOCHAN , client . Nick ( ) , channel . Name ( ) , fmt . Sprintf ( client . t ( "Cannot send to channel (+%s)" ) , "C" ) )
}
return
}
2020-05-12 18:05:40 +02:00
details := client . Details ( )
2021-03-18 00:01:38 +01:00
isBot := client . HasMode ( modes . Bot )
2019-04-12 06:08:46 +02:00
chname := channel . Name ( )
2021-03-05 04:29:34 +01:00
if ! client . server . Config ( ) . Server . Compatibility . allowTruncation {
if ! validateSplitMessageLen ( histType , details . nickMask , chname , message ) {
rb . Add ( nil , client . server . name , ERR_INPUTTOOLONG , details . nick , client . t ( "Line too long to be relayed without truncation" ) )
return
}
}
2019-04-23 06:05:12 +02:00
// STATUSMSG targets are prefixed with the supplied min-prefix, e.g., @#channel
if minPrefixMode != modes . Mode ( 0 ) {
chname = fmt . Sprintf ( "%s%s" , modes . ChannelModePrefixes [ minPrefixMode ] , chname )
2017-01-14 06:28:50 +01:00
}
2019-04-23 06:05:12 +02:00
2020-10-20 19:37:38 +02:00
if channel . flags . HasMode ( modes . OpModerated ) {
channel . stateMutex . RLock ( )
2023-04-14 08:15:56 +02:00
cuData , ok := channel . members [ client ]
2020-10-20 19:37:38 +02:00
channel . stateMutex . RUnlock ( )
2023-04-14 08:15:56 +02:00
if ! ok || cuData . modes . HighestChannelUserMode ( ) == modes . Mode ( 0 ) {
2020-10-20 19:37:38 +02:00
// max(statusmsg_minmode, halfop)
if minPrefixMode == modes . Mode ( 0 ) || minPrefixMode == modes . Voice {
minPrefixMode = modes . Halfop
}
}
}
2018-02-10 23:57:15 +01:00
// send echo-message
2020-07-24 08:55:46 +02:00
rb . addEchoMessage ( clientOnlyTags , details . nickMask , details . accountName , command , chname , message )
2019-04-12 06:08:46 +02:00
2020-11-27 06:13:47 +01:00
var cache MessageCache
2021-03-18 00:01:38 +01:00
cache . InitializeSplitMessage ( channel . server , details . nickMask , details . accountName , isBot , clientOnlyTags , command , chname , message )
2019-04-12 06:08:46 +02:00
for _ , member := range channel . Members ( ) {
2019-04-23 06:05:12 +02:00
if minPrefixMode != modes . Mode ( 0 ) && ! channel . ClientIsAtLeast ( member , minPrefixMode ) {
2020-10-20 19:37:38 +02:00
// STATUSMSG or OpModerated
2019-03-07 08:31:46 +01:00
continue
2017-01-14 12:48:57 +01:00
}
2019-04-12 06:08:46 +02:00
for _ , session := range member . Sessions ( ) {
2020-07-24 08:37:36 +02:00
if session == rb . session {
continue // we already sent echo-message, if applicable
}
2020-02-19 01:38:42 +01:00
if isCTCP && session . isTor {
continue // #753
}
2020-11-27 06:13:47 +01:00
cache . Send ( session )
2017-01-14 06:28:50 +01:00
}
}
2018-11-26 11:23:27 +01:00
2020-10-20 19:37:38 +02:00
// #959: don't save STATUSMSG (or OpModerated)
2020-04-24 07:33:21 +02:00
if minPrefixMode == modes . Mode ( 0 ) {
channel . AddHistoryItem ( history . Item {
Type : histType ,
Message : message ,
2020-05-12 18:05:40 +02:00
Nick : details . nickMask ,
AccountName : details . accountName ,
2020-04-24 07:33:21 +02:00
Tags : clientOnlyTags ,
2021-03-18 00:01:38 +01:00
IsBot : isBot ,
2020-05-12 18:05:40 +02:00
} , details . account )
2020-04-24 07:33:21 +02:00
}
2017-01-14 06:28:50 +01:00
}
2020-03-25 17:08:08 +01:00
func ( channel * Channel ) applyModeToMember ( client * Client , change modes . ModeChange , rb * ResponseBuffer ) ( applied bool , result modes . ModeChange ) {
target := channel . server . clients . Get ( change . Arg )
2019-12-05 12:52:07 +01:00
if target == nil {
2020-03-25 17:08:08 +01:00
rb . Add ( nil , client . server . name , ERR_NOSUCHNICK , client . Nick ( ) , utils . SafeErrorParam ( change . Arg ) , client . t ( "No such nick" ) )
return
2014-02-23 00:01:11 +01:00
}
2020-03-25 17:08:08 +01:00
change . Arg = target . Nick ( )
2014-02-23 00:01:11 +01:00
2017-10-23 01:50:16 +02:00
channel . stateMutex . Lock ( )
2021-01-21 03:13:18 +01:00
memberData , exists := channel . members [ target ]
2017-10-23 01:50:16 +02:00
if exists {
2021-01-21 03:13:18 +01:00
if memberData . modes . SetMode ( change . Mode , change . Op == modes . Add ) {
2020-03-25 17:08:08 +01:00
applied = true
result = change
2018-04-23 00:47:10 +02:00
}
2014-02-23 00:01:11 +01:00
}
2017-10-23 01:50:16 +02:00
channel . stateMutex . Unlock ( )
2014-02-23 00:01:11 +01:00
2017-10-23 01:50:16 +02:00
if ! exists {
2019-02-17 12:51:48 +01:00
rb . Add ( nil , client . server . name , ERR_USERNOTINCHANNEL , client . Nick ( ) , channel . Name ( ) , client . t ( "They aren't on that channel" ) )
2014-02-23 00:01:11 +01:00
}
2020-12-02 09:56:00 +01:00
if applied {
target . markDirty ( IncludeChannels )
}
2018-04-23 00:47:10 +02:00
return
2014-02-23 00:01:11 +01:00
}
2017-03-27 06:29:51 +02:00
// ShowMaskList shows the given list to the client.
2018-02-05 15:21:08 +01:00
func ( channel * Channel ) ShowMaskList ( client * Client , mode modes . Mode , rb * ResponseBuffer ) {
2017-03-26 12:37:13 +02:00
// choose appropriate modes
var rpllist , rplendoflist string
2018-02-03 11:21:32 +01:00
if mode == modes . BanMask {
2017-03-26 12:37:13 +02:00
rpllist = RPL_BANLIST
rplendoflist = RPL_ENDOFBANLIST
2018-02-03 11:21:32 +01:00
} else if mode == modes . ExceptMask {
2017-03-26 12:37:13 +02:00
rpllist = RPL_EXCEPTLIST
rplendoflist = RPL_ENDOFEXCEPTLIST
2018-02-03 11:21:32 +01:00
} else if mode == modes . InviteMask {
2017-03-26 12:37:13 +02:00
rpllist = RPL_INVITELIST
rplendoflist = RPL_ENDOFINVITELIST
}
2017-11-03 07:36:55 +01:00
nick := client . Nick ( )
2019-10-10 10:17:44 +02:00
chname := channel . Name ( )
for mask , info := range channel . lists [ mode ] . Masks ( ) {
rb . Add ( nil , client . server . name , rpllist , nick , chname , mask , info . CreatorNickmask , strconv . FormatInt ( info . TimeCreated . Unix ( ) , 10 ) )
2017-03-26 12:37:13 +02:00
}
2017-10-23 01:50:16 +02:00
2019-10-10 10:17:44 +02:00
rb . Add ( nil , client . server . name , rplendoflist , nick , chname , client . t ( "End of list" ) )
2014-03-08 02:35:58 +01:00
}
2014-03-08 02:09:49 +01:00
2017-10-23 01:50:16 +02:00
// Quit removes the given client from the channel
func ( channel * Channel ) Quit ( client * Client ) {
2018-04-24 09:11:11 +02:00
channelEmpty := func ( ) bool {
channel . joinPartMutex . Lock ( )
defer channel . joinPartMutex . Unlock ( )
2017-10-23 01:50:16 +02:00
2018-04-24 09:11:11 +02:00
channel . stateMutex . Lock ( )
channel . members . Remove ( client )
channelEmpty := len ( channel . members ) == 0
channel . stateMutex . Unlock ( )
channel . regenerateMembersCache ( )
return channelEmpty
} ( )
2018-03-01 16:37:30 +01:00
2018-04-24 09:11:11 +02:00
if channelEmpty {
2018-03-01 16:37:30 +01:00
client . server . channels . Cleanup ( channel )
}
2018-04-24 09:11:11 +02:00
client . removeChannel ( channel )
2014-02-17 08:29:11 +01:00
}
2019-12-17 01:50:15 +01:00
func ( channel * Channel ) Kick ( client * Client , target * Client , comment string , rb * ResponseBuffer , hasPrivs bool ) {
if ! hasPrivs {
if ! channel . ClientHasPrivsOver ( client , target ) {
rb . Add ( nil , client . server . name , ERR_CHANOPRIVSNEEDED , client . Nick ( ) , channel . Name ( ) , client . t ( "You don't have enough channel privileges" ) )
return
}
2014-02-17 08:29:11 +01:00
}
2017-10-23 01:50:16 +02:00
if ! channel . hasClient ( target ) {
2019-03-19 08:35:49 +01:00
rb . Add ( nil , client . server . name , ERR_USERNOTINCHANNEL , client . Nick ( ) , channel . Name ( ) , client . t ( "They aren't on that channel" ) )
2014-02-17 08:29:11 +01:00
return
}
2021-03-18 08:49:12 +01:00
comment = ircutils . TruncateUTF8Safe ( comment , channel . server . Config ( ) . Limits . KickLen )
2016-09-12 04:22:50 +02:00
2020-01-19 05:47:05 +01:00
message := utils . MakeMessage ( comment )
2020-05-12 18:05:40 +02:00
details := client . Details ( )
2021-03-17 19:36:52 +01:00
isBot := client . HasMode ( modes . Bot )
2019-05-07 05:17:57 +02:00
2017-11-03 07:36:55 +01:00
targetNick := target . Nick ( )
2019-04-12 06:08:46 +02:00
chname := channel . Name ( )
2017-10-23 01:50:16 +02:00
for _ , member := range channel . Members ( ) {
2019-04-12 06:08:46 +02:00
for _ , session := range member . Sessions ( ) {
if session != rb . session {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "KICK" , chname , targetNick , comment )
2019-04-12 06:08:46 +02:00
}
}
2014-02-20 07:20:34 +01:00
}
2021-03-17 19:36:52 +01:00
rb . AddFromClient ( message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "KICK" , chname , targetNick , comment )
2017-10-23 01:50:16 +02:00
2019-05-07 05:17:57 +02:00
histItem := history . Item {
2018-11-26 11:23:27 +01:00
Type : history . Kick ,
2020-05-12 18:05:40 +02:00
Nick : details . nickMask ,
AccountName : details . accountName ,
2019-03-07 08:31:46 +01:00
Message : message ,
2021-03-17 19:36:52 +01:00
IsBot : isBot ,
2019-05-07 05:17:57 +02:00
}
histItem . Params [ 0 ] = targetNick
2020-05-12 18:05:40 +02:00
channel . AddHistoryItem ( histItem , details . account )
2018-11-26 11:23:27 +01:00
2017-10-23 01:50:16 +02:00
channel . Quit ( target )
2014-02-17 02:23:47 +01:00
}
2014-02-25 16:28:09 +01:00
2021-02-04 21:26:03 +01:00
// handle a purge: kick everyone off the channel, clean up all the pointers between
// *Channel and *Client
func ( channel * Channel ) Purge ( source string ) {
if source == "" {
source = channel . server . name
}
channel . stateMutex . Lock ( )
chname := channel . name
members := channel . membersCache
channel . membersCache = nil
2023-04-14 08:15:56 +02:00
channel . memberDataCache = nil
2021-02-04 21:26:03 +01:00
channel . members = make ( MemberSet )
// TODO try to prevent Purge racing against (pending) Join?
channel . stateMutex . Unlock ( )
now := time . Now ( ) . UTC ( )
for _ , member := range members {
tnick := member . Nick ( )
msgid := utils . GenerateSecretToken ( )
for _ , session := range member . Sessions ( ) {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , now , msgid , source , "*" , false , nil , "KICK" , chname , tnick , member . t ( "This channel has been purged by the server administrators and cannot be used" ) )
2021-02-04 21:26:03 +01:00
}
member . removeChannel ( channel )
}
}
2017-03-27 06:29:51 +02:00
// Invite invites the given client to the channel, if the inviter can do so.
2018-02-05 15:21:08 +01:00
func ( channel * Channel ) Invite ( invitee * Client , inviter * Client , rb * ResponseBuffer ) {
2020-10-26 01:40:41 +01:00
channel . stateMutex . RLock ( )
chname := channel . name
chcfname := channel . nameCasefolded
createdAt := channel . createdTime
_ , inviterPresent := channel . members [ inviter ]
_ , inviteePresent := channel . members [ invitee ]
channel . stateMutex . RUnlock ( )
if ! inviterPresent {
rb . Add ( nil , inviter . server . name , ERR_NOTONCHANNEL , inviter . Nick ( ) , chname , inviter . t ( "You're not on that channel" ) )
2014-02-25 16:28:09 +01:00
return
}
2020-10-26 01:40:41 +01:00
inviteOnly := channel . flags . HasMode ( modes . InviteOnly )
2021-12-20 00:30:18 +01:00
hasPrivs := channel . ClientIsAtLeast ( inviter , modes . ChannelOperator )
if inviteOnly && ! hasPrivs {
2020-10-26 01:40:41 +01:00
rb . Add ( nil , inviter . server . name , ERR_CHANOPRIVSNEEDED , inviter . Nick ( ) , chname , inviter . t ( "You're not a channel operator" ) )
2020-03-18 11:13:57 +01:00
return
}
2020-10-26 01:40:41 +01:00
if inviteePresent {
2020-03-18 11:13:57 +01:00
rb . Add ( nil , inviter . server . name , ERR_USERONCHANNEL , inviter . Nick ( ) , invitee . Nick ( ) , chname , inviter . t ( "User is already on that channel" ) )
2014-02-25 16:28:09 +01:00
return
}
2021-12-20 00:30:18 +01:00
// #1876: INVITE should override all join restrictions, including +b and +l,
// not just +i. so we need to record it on a per-client basis iff the inviter
// is privileged:
if hasPrivs {
2020-10-26 01:40:41 +01:00
invitee . Invite ( chcfname , createdAt )
}
2014-03-08 02:09:49 +01:00
2020-11-30 04:12:06 +01:00
details := inviter . Details ( )
2021-03-17 19:36:52 +01:00
isBot := inviter . HasMode ( modes . Bot )
2020-11-30 04:12:06 +01:00
tDetails := invitee . Details ( )
tnick := invitee . Nick ( )
2020-12-14 14:24:38 +01:00
message := utils . MakeMessage ( chname )
2020-11-30 04:12:06 +01:00
item := history . Item {
Type : history . Invite ,
Message : message ,
}
2017-10-23 01:50:16 +02:00
for _ , member := range channel . Members ( ) {
2019-04-12 06:08:46 +02:00
if member == inviter || member == invitee || ! channel . ClientIsAtLeast ( member , modes . Halfop ) {
continue
}
for _ , session := range member . Sessions ( ) {
if session . capabilities . Has ( caps . InviteNotify ) {
2021-03-17 19:36:52 +01:00
session . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "INVITE" , tnick , chname )
2019-04-12 06:08:46 +02:00
}
2016-10-16 06:14:55 +02:00
}
}
2020-11-30 04:12:06 +01:00
rb . Add ( nil , inviter . server . name , RPL_INVITING , details . nick , tnick , chname )
2021-03-17 19:36:52 +01:00
for _ , iSession := range invitee . Sessions ( ) {
iSession . sendFromClientInternal ( false , message . Time , message . Msgid , details . nickMask , details . accountName , isBot , nil , "INVITE" , tnick , chname )
}
2020-07-17 07:55:13 +02:00
if away , awayMessage := invitee . Away ( ) ; away {
2020-11-30 04:12:06 +01:00
rb . Add ( nil , inviter . server . name , RPL_AWAY , details . nick , tnick , awayMessage )
2014-02-25 16:28:09 +01:00
}
2020-11-30 04:12:06 +01:00
inviter . addHistoryItem ( invitee , item , & details , & tDetails , channel . server . Config ( ) )
2014-02-25 16:28:09 +01:00
}
2020-06-29 21:41:29 +02:00
2020-10-26 03:16:19 +01:00
// Uninvite rescinds a channel invitation, if the inviter can do so.
func ( channel * Channel ) Uninvite ( invitee * Client , inviter * Client , rb * ResponseBuffer ) {
if ! channel . flags . HasMode ( modes . InviteOnly ) {
rb . Add ( nil , channel . server . name , "FAIL" , "UNINVITE" , "NOT_INVITE_ONLY" , channel . Name ( ) , inviter . t ( "Channel is not invite-only" ) )
return
}
if ! channel . ClientIsAtLeast ( inviter , modes . ChannelOperator ) {
2020-12-07 03:01:44 +01:00
rb . Add ( nil , channel . server . name , "FAIL" , "UNINVITE" , "PRIVS_NEEDED" , channel . Name ( ) , inviter . t ( "You're not a channel operator" ) )
2020-10-26 03:16:19 +01:00
return
}
invitee . Uninvite ( channel . NameCasefolded ( ) )
rb . Add ( nil , channel . server . name , "UNINVITE" , invitee . Nick ( ) , channel . Name ( ) )
}
2020-10-02 14:13:52 +02:00
// returns who the client can "see" in the channel, respecting the auditorium mode
func ( channel * Channel ) auditoriumFriends ( client * Client ) ( friends [ ] * Client ) {
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
2021-01-21 03:13:18 +01:00
clientData , found := channel . members [ client ]
if ! found {
2020-10-02 14:13:52 +02:00
return // non-members have no friends
}
if ! channel . flags . HasMode ( modes . Auditorium ) {
return channel . membersCache // default behavior for members
}
2021-01-21 03:13:18 +01:00
if clientData . modes . HighestChannelUserMode ( ) != modes . Mode ( 0 ) {
2020-10-02 14:13:52 +02:00
return channel . membersCache // +v and up can see everyone in the auditorium
}
// without +v, your friends are those with +v and up
2021-01-21 03:13:18 +01:00
for member , memberData := range channel . members {
if memberData . modes . HighestChannelUserMode ( ) != modes . Mode ( 0 ) {
2020-10-02 14:13:52 +02:00
friends = append ( friends , member )
}
}
return
}
2020-06-29 21:41:29 +02:00
// data for RPL_LIST
func ( channel * Channel ) listData ( ) ( memberCount int , name , topic string ) {
channel . stateMutex . RLock ( )
defer channel . stateMutex . RUnlock ( )
return len ( channel . members ) , channel . name , channel . topic
}